Skip to content

Commit 2428bf1

Browse files
committed
fix: multiple issues in urls + catastrophic backtracking in Regex, freezes the bot
1 parent ba9db2f commit 2428bf1

5 files changed

Lines changed: 158 additions & 73 deletions

File tree

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ ENV PYTHONUNBUFFERED=1
1010
RUN apt-get update -y && apt-get install -y --no-install-recommends \
1111
curl ca-certificates \
1212
git gcc build-essential \
13+
iputils-ping \
1314
&& rm -rf /var/lib/apt/lists/*
1415

1516
# install uv

wbb/modules/misc.py

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2222
SOFTWARE.
2323
"""
24+
2425
import re
2526
import secrets
2627
import string
@@ -34,8 +35,6 @@
3435
from wbb.core.decorators.errors import capture_err
3536
from wbb.utils import random_line
3637
from wbb.utils.http import get
37-
from wbb.utils.json_prettify import json_prettify
38-
from wbb.utils.pastebin import paste
3938

4039
__MODULE__ = "Misc"
4140
__HELP__ = """
@@ -61,9 +60,6 @@
6160
Translate A Message
6261
Ex: /tr en
6362
64-
/json [URL]
65-
Get parsed JSON response from a rest API.
66-
6763
/arq
6864
Statistics Of ARQ API.
6965
@@ -166,9 +162,7 @@ async def rtfm(_, message):
166162
await message.delete()
167163
if not message.reply_to_message:
168164
return await message.reply_text("Reply To A Message lol")
169-
await message.reply_to_message.reply_text(
170-
"Are You Lost? READ THE FUCKING DOCS!"
171-
)
165+
await message.reply_to_message.reply_text("Are You Lost? READ THE FUCKING DOCS!")
172166

173167

174168
@app.on_message(filters.command("runs"))
@@ -222,16 +216,12 @@ async def getid(client, message):
222216
@capture_err
223217
async def random(_, message):
224218
if len(message.command) != 2:
225-
return await message.reply_text(
226-
'"/random" Needs An Argurment.' " Ex: `/random 5`"
227-
)
219+
return await message.reply_text('"/random" Needs An Argurment. Ex: `/random 5`')
228220
length = message.text.split(None, 1)[1]
229221
try:
230222
if 1 < int(length) < 1000:
231223
alphabet = string.ascii_letters + string.digits
232-
password = "".join(
233-
secrets.choice(alphabet) for i in range(int(length))
234-
)
224+
password = "".join(secrets.choice(alphabet) for i in range(int(length)))
235225
await message.reply_text(f"`{password}`")
236226
else:
237227
await message.reply_text("Specify A Length Between 1-1000")
@@ -265,30 +255,8 @@ async def tr(_, message):
265255
await message.reply_text(result.result.translatedText)
266256

267257

268-
@app.on_message(filters.command("json"))
269-
@capture_err
270-
async def json_fetch(_, message):
271-
if len(message.command) != 2:
272-
return await message.reply_text("/json [URL]")
273-
url = message.text.split(None, 1)[1]
274-
m = await message.reply_text("Fetching")
275-
try:
276-
data = await get(url)
277-
data = await json_prettify(data)
278-
if len(data) < 4090:
279-
await m.edit(data)
280-
else:
281-
link = await paste(data)
282-
await m.edit(
283-
f"[OUTPUT_TOO_LONG]({link})",
284-
disable_web_page_preview=True,
285-
)
286-
except Exception as e:
287-
await m.edit(str(e))
288-
289-
290258
@app.on_message(filters.command(["kickme", "banme"]))
291259
async def kickbanme(_, message):
292260
await message.reply_text(
293-
"Haha, it doesn't work that way, You're stuck with everyone here."
261+
"Haha, it doesn't work that way, You're stuck here with everyone."
294262
)

wbb/modules/regex.py

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# https://github.com/PaulSonOfLars/tgbot/blob/master/tg_bot/modules/sed.py
2+
import asyncio
3+
import multiprocessing as mp
24
import re
3-
import sre_constants
45

56
from pyrogram import filters
67

@@ -11,6 +12,62 @@
1112
__HELP__ = "**Usage:**\ns/foo/bar"
1213

1314
DELIMITERS = ("/", ":", "|", "_")
15+
REGEX_TIMEOUT_SECONDS = 5
16+
17+
18+
def _regex_sub_worker(
19+
pattern: str,
20+
replacement: str,
21+
source_text: str,
22+
ignore_case: bool,
23+
replace_all: bool,
24+
result_queue,
25+
):
26+
flags = re.I if ignore_case else 0
27+
count = 0 if replace_all else 1
28+
29+
try:
30+
result = re.sub(pattern, replacement, source_text, count=count, flags=flags)
31+
result_queue.put(("ok", result))
32+
except re.error:
33+
result_queue.put(("regex_error", ""))
34+
35+
36+
def run_regex_with_timeout(
37+
pattern: str,
38+
replacement: str,
39+
source_text: str,
40+
ignore_case: bool,
41+
replace_all: bool,
42+
) -> str:
43+
result_queue = mp.Queue(maxsize=1)
44+
process = mp.Process(
45+
target=_regex_sub_worker,
46+
args=(
47+
pattern,
48+
replacement,
49+
source_text,
50+
ignore_case,
51+
replace_all,
52+
result_queue,
53+
),
54+
)
55+
process.start()
56+
process.join(REGEX_TIMEOUT_SECONDS)
57+
58+
if process.is_alive():
59+
process.terminate()
60+
process.join()
61+
raise asyncio.TimeoutError
62+
63+
if result_queue.empty():
64+
return ""
65+
66+
status, result = result_queue.get()
67+
if status == "regex_error":
68+
raise re.error("invalid regex")
69+
70+
return result
1471

1572

1673
@app.on_message(
@@ -31,31 +88,31 @@ async def sed(_, message):
3188
to_fix = message.reply_to_message.caption
3289
else:
3390
return
34-
try:
35-
repl, repl_with, flags = sed_result
36-
except Exception:
91+
if not sed_result:
3792
return
93+
repl, repl_with, flags = sed_result
3894

3995
if not repl:
4096
return await message.reply_text(
41-
"You're trying to replace... " "nothing with something?"
97+
"You're trying to replace... nothing with something?"
4298
)
4399

44100
try:
45101
if infinite_checker(repl):
46102
return await message.reply_text("Nice try -_-")
47103

48-
if "i" in flags and "g" in flags:
49-
text = re.sub(repl, repl_with, to_fix, flags=re.I).strip()
50-
elif "i" in flags:
51-
text = re.sub(
52-
repl, repl_with, to_fix, count=1, flags=re.I
53-
).strip()
54-
elif "g" in flags:
55-
text = re.sub(repl, repl_with, to_fix).strip()
56-
else:
57-
text = re.sub(repl, repl_with, to_fix, count=1).strip()
58-
except sre_constants.error:
104+
text = await asyncio.to_thread(
105+
run_regex_with_timeout,
106+
repl,
107+
repl_with,
108+
to_fix,
109+
"i" in flags,
110+
"g" in flags,
111+
)
112+
text = text.strip()
113+
except asyncio.TimeoutError:
114+
return await message.reply_text("Regex took too long to compute.")
115+
except re.error:
59116
return
60117

61118
# empty string errors -_-
@@ -75,8 +132,9 @@ def infinite_checker(repl):
75132
r"\(.{1,}\)\{.{1,}(,)?\}\(.*\)(\+|\* |\{.*\})",
76133
]
77134
for match in regex:
78-
status = re.search(match, repl)
79-
return bool(status)
135+
if re.search(match, repl):
136+
return True
137+
return False
80138

81139

82140
def separate_sed(sed_string):

wbb/modules/rss.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919
remove_rss_feed,
2020
update_rss_feed,
2121
)
22-
from wbb.utils.functions import get_http_status_code, get_urls_from_text
22+
from wbb.utils.functions import (
23+
get_http_status_code,
24+
get_urls_from_text,
25+
is_safe_url,
26+
)
2327
from wbb.utils.rss import Feed
2428

2529
__MODULE__ = "RSS"
@@ -34,6 +38,13 @@
3438
"""
3539

3640

41+
def get_parsed_feed_url(parsed, fallback: str) -> str:
42+
href = parsed.get("href")
43+
if isinstance(href, str) and href:
44+
return href
45+
return fallback
46+
47+
3748
async def rss_worker():
3849
log.info("RSS Worker started")
3950
while not await sleep(RSS_DELAY):
@@ -47,9 +58,20 @@ async def rss_worker():
4758
chat = _feed["chat_id"]
4859
try:
4960
url = _feed["url"]
61+
if not is_safe_url(url):
62+
await remove_rss_feed(chat)
63+
log.info(f"Removed RSS Feed from {chat} (Unsafe URL)")
64+
continue
65+
5066
last_title = _feed.get("last_title")
5167

5268
parsed = await loop.run_in_executor(None, parse, url)
69+
final_url = get_parsed_feed_url(parsed, url)
70+
if not is_safe_url(final_url):
71+
await remove_rss_feed(chat)
72+
log.info(f"Removed RSS Feed from {chat} (Unsafe redirect)")
73+
continue
74+
5375
feed = Feed(parsed)
5476

5577
if feed.title == last_title:
@@ -91,6 +113,9 @@ async def add_feed_func(_, m: Message):
91113
return await m.reply("[ERROR]: Invalid URL")
92114

93115
url = urls[0]
116+
if not is_safe_url(url):
117+
return await m.reply("[ERROR]: URL is not allowed (SSRF protection).")
118+
94119
status = await get_http_status_code(url)
95120
if status != 200:
96121
return await m.reply("[ERROR]: Invalid Url")
@@ -99,6 +124,10 @@ async def add_feed_func(_, m: Message):
99124
try:
100125
loop = get_event_loop()
101126
parsed = await loop.run_in_executor(None, parse, url)
127+
final_url = get_parsed_feed_url(parsed, url)
128+
if not is_safe_url(final_url):
129+
return await m.reply("[ERROR]: URL is not allowed (SSRF protection).")
130+
102131
feed = Feed(parsed)
103132
except Exception:
104133
return await m.reply(ns)
@@ -112,7 +141,7 @@ async def add_feed_func(_, m: Message):
112141
await m.reply(feed.parsed(), disable_web_page_preview=True)
113142
except Exception:
114143
return await m.reply(ns)
115-
await add_rss_feed(chat_id, parsed.url, feed.title)
144+
await add_rss_feed(chat_id, final_url, feed.title)
116145

117146

118147
@app.on_message(filters.command("rm_feed"))

0 commit comments

Comments
 (0)