Introduction
This weekend, my team (M53) and I participated in a CTF called TCP1P CTF and we made it to 6th place out of 499 teams! (full credit and kudos to the team for solving most of the challenges! 👏)
Even though this was TCP1P community first time holding an international CTF, the challenges were pretty interesting.
I could only spend a day max on solving their challenges because I needed to study for my final exam, which was scheduled for the following Monday 🙃
But, still overall it was a great experience! I’ve learned a lot from the challenges especially the web
category challenges.
Nuclei (Misc)
In this challenge, we were given a source code zipped in a file called dist.zip
Unzipping the file, we will have :
- Source code of a Flask web.
- Nuclei YAML Template
#app.py
from flask import Flask, render_template, request, redirect, url_for
import subprocess
import re
app = Flask(__name__)
@app.route('/', methods=['GET'])
def index():
return render_template('index.html')
@app.route('/submit', methods=['POST'])
def submit():
url_pattern = re.compile(r'^(https?://[A-Za-z0-9\-._~:/?#\[\]@!$&\'()*+,;=]+)$')
url = request.form.get('url')
if url is None:
return "No URL provided.", 400
if not url_pattern.match(url):
return "Invalid URL format.", 400
if url:
command = ['./nuclei', '--silent', '-u', url, '-t', 'custom-templates.yaml']
try:
result = subprocess.run(command, capture_output=True, text=True)
print(result.stdout)
if 'info' in result.stdout and '/api/v2/echo' in result.stdout and 'custom-templates' in result.stdout:
return "TCP1P{fake_flag}"
else:
return "Your website isn't vulnerable"
except subprocess.CalledProcessError:
return "Error occurred while running command"
return "Invalid request"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
#custom-templates.yaml
id: custom-templates
info:
name: Testing nuclei templates
author: daffainfo
severity: info
reference: https://daffa.info
metadata:
max-request: 2
tags: ctf,tcp1p
http:
- raw:
# Detect API Version
- |
GET /api/v1/version/ HTTP/1.1
Host: {{Hostname}}
Referer: https://daffa.info/
# XSS and Path Traversal
- |
GET /api/v2/echo/?name=<script>alert(1)</script>&file=/etc/passwd HTTP/1.1
Host: {{Hostname}}
Referer: https://daffa.info
req-condition: true
matchers-condition: and
matchers:
- type: dsl
dsl:
- compare_versions(version, '<= 10.0.5', '> 10.0.1')
- type: word
part: body_1
words:
- "\"NAME\":\"TCP1P\""
- "\"msg\":\"success\""
condition: and
case-insensitive: true
- type: dsl
dsl:
- "regex('TCP1P{[a-z]}', body_2)"
- 'contains(body_2, "<script>alert(1)</script>")'
- "status_code_2 == 200"
condition: and
- type: status
status:
- 200
extractors:
- type: regex
name: version
group: 1
internal: true
part: body_1
regex:
- "\"version\":\"([0-9.]+)\""
if url:
command = ['./nuclei', '--silent', '-u', url, '-t', 'custom-templates.yaml']
try:
result = subprocess.run(command, capture_output=True, text=True)
print(result.stdout)
if 'info' in result.stdout and '/api/v2/echo' in result.stdout and 'custom-templates' in result.stdout:
return "TCP1P{fake_flag}"
else:
return "Your website isn't vulnerable"
except subprocess.CalledProcessError:
return "Error occurred while running command"
return "Invalid request"
By reading the app.py
code block above, this seems like a simple website where it will receive a url input
and run it to Nuclei with the custom-templates.yaml
Which then, will show us the flag if Nuclei finds a vulnerability on the specified url
Well, what does the custom-templates.yaml
checks for?
Here’s a breakdown :
-
API Version Detection:
-
Route:
/api/v1/version/
-
Reading from the Route: This route reads the response from the
/api/v1/version/
endpoint. -
Condition for Matching:
-
It checks the response for the API version by extracting the version number using a regular expression. It looks for a pattern like
"version":"X.X.X"
and extracts theX.X.X
part as the version number. - Then, it compares this extracted version to ensure that it is less than or equal to 10.0.5 and greater than 10.0.1.
-
It checks the response for the API version by extracting the version number using a regular expression. It looks for a pattern like
-
Route:
-
XSS and Path Traversal Check:
-
Route:
/api/v2/echo/
-
Reading from the Route: This route reads the response from the
/api/v2/echo/
endpoint. -
Condition for Matching:
-
It applies several matchers to the response:
-
It checks if the response body (
body_1
) contains specific content, including"NAME":"TCP1P"
and"msg":"success"
. This check is case-insensitive. -
It performs regex matching on
body_2
, looking for the patternTCP1P{[a-z]}
. -
It checks if
body_2
contains the string"<script>alert(1)</script>"
. -
It ensures that the HTTP status code is 200 (
status_code_2 == 200
).
-
It checks if the response body (
-
It applies several matchers to the response:
-
Route:
Solution
Now, the only way to get the flag is that Nuclei must return info after scanning (the website that we are scanning matches the rules specified in the yaml above).
I am pretty sure it’s impossible to find a website which matches the rules above, especially the TCP1P{[a-z]}
regex part.
Well, there’s no other way…
Let’s create and deploy our own website! (which matches the template rules)
But dont worry! It’s not gonna be difficult.
You don’t really need to code a whole working vulnerable website.
You just have to create a website that returns exactly what the template yaml rules looks for.
For this challenge, I decided to use FastAPI
because it’s much easier to code and setup quickly.
from fastapi import FastAPI, Request
app = FastAPI()
@app.get('/api/v1/version/')
def api_version():
response = {
"version": "10.0.3",
"NAME": "TCP1P",
"msg": "success"
}
return response
@app.get('/api/v2/echo/')
async def api_echo(request: Request):
return "TCP1P{a} <script>alert(1)</script>"
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Now, deploy the website code above to a cloud instance or your own portforwarded server.
uvicorn <filenamewithoutdotpy>:app --host 0.0.0.0 --port 8000
Final Step:
Pass in your deployed website url and click on submit, and we will get the flag!
Thanks for reading my write-up and have a nice day!