Site cover image

Site icon image vicevirus’ Blog

Yo, welcome to my blog! I write tech stuff and play CTFs for fun. (still a noob)

Post title icon [Misc] Nuclei TCP1P CTF 2023 Writeup

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)


Image in a image block
Image in a image block
Yay!

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.]+)\""

ℹ️
Nuclei powerful open-source tool used to detect and exploit potential security vulnerabilities in web applications and websites.
ℹ️
At the core of Nuclei's functionality are templates, which allow users to define specific checks and rules for security testing.


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 :

  1. 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 the X.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.
  2. 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 pattern TCP1P{[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).

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)

Image in a image block

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.

ℹ️
To run the code above:
uvicorn <filenamewithoutdotpy>:app --host 0.0.0.0 --port 8000
👀
Tried to use Ngrok for hosting but it doesn’t work for me. Nuclei request is blocked by the Ngrok warning, not sure if there’s any ‘quick’ workaround.
Final Step:

Pass in your deployed website url and click on submit, and we will get the flag!

Image in a image block
Image in a image block


Thanks for reading my write-up and have a nice day!