Meoware
In Whitehacks 2022, 1000 points
SpaceY Corp. has been hit by a meoware attack. Wait a second, what even is a meoware? Nevertheless, your job here is simple. Retrieve the stolen flag from their web servers before they vanish for good.
This is a Web challenge created by the Govtech team. We were the only team to solve this challenge in the closing minutes of the competition. We did not make much progress on the challenge until the source code was released, about 45 minutes before the end of the competition.
The challenge consists of a site containing two images. On further inspection, the bottom right corner of the second image contains the second half of the flag:
which my teammates helpfully transcribed as m30w_mao0o_m3oWwwW}
.
Now to find the first part of the flag! Inspecting the network requests, we discover that the second image is actually loaded from http://challenges1.whitehacks.xyz:21999/?url=http://challenges1.whitehacks.xyz:21888
which is obviously really suspicious as it has a url
parameter in the query string, which could potentially be an indication of File Inclusion or SSRF vulnerabilities. Interestingly, the site implements a filter on the url
parameter, seemingly blocking all sites except http://challenges1.whitehacks.xyz
.
We were stuck at this stage until the source was released. Several files were released but only one is relevant, proxy.py
which runs http://challenges1.whitehacks.xyz:21999
. Let's look at the source:
FLAG = os.getenv('FLAG')
HOST_NAME = os.getenv('HOST_NAME', 'meoware-api.local')
headers = {'Flag': FLAG}
@app.route("/")
def index():
if request.args.get('url', type = str):
url = request.args.get('url', type = str)
parser_results = tldextract.extract(url)
if parser_results.domain == HOST_NAME:
print(headers)
r = requests.get(url, headers=headers, verify=False)
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
h = [(name, value) for (name, value) in r.raw.headers.items()
if name.lower() not in excluded_headers]
return Response(r.content, r.status_code, h)
return "Error"
The crucial filter consists of using tldextract
to extract the domain from the URL parameter and ensuring it matches with the domain of the site the challenge is running on, which is whitehacks
. If we are able to somehow trick the parser into returning whitehacks
on a domain that we own instead, we can solve the challenge. However, tldextract
seems to be a really reputable library with no such vulnerabilities. So we decided to take a more unconventional route to solve this challenge.
Instead of tricking the parser, we could just register a domain which passes the filter! As it turns out, whitehacks.me
was available on namecheap for $2.98, which is a small price to pay to solve this challenge. This passes the filter as the domain is whitehacks
as well. We then pointed the domain to a Google Cloud server that we had setup for the CTF, which runs a simple script to log all requests, including their headers. After setting the url
parameter to whitehacks.me
, we received the remainder of the flag in request logs:
WH2022{cAts_ar3_aW3s0me_
In total, including the server costs, the cost for solving this challenge came up to be about $4. Although it didn't change the final rankings, it did make for an unconventional solve that we will remember for some time.