diff --git a/content/posts/scriptctf2025/index.md b/content/posts/scriptctf2025/index.md index 3d0fe8c..9daa2f2 100644 --- a/content/posts/scriptctf2025/index.md +++ b/content/posts/scriptctf2025/index.md @@ -10,9 +10,7 @@ tags = ["ctf", "rev", "web", "forensics"] # ScriptCTF 2025 – Challenge Writeup ![][image1] -Over the weekend, I participated in **ScriptCTF**, a capture-the-flag competition packed with diverse challenges spanning cryptography, forensics, reverse engineering, and web exploitation. - -Our team managed position \_\_\_\_\_\_ out of \_\_\_ teams with non zero points. +Over the weekend, I participated in ScriptCTF, a capture-the-flag competition packed with diverse challenges spanning cryptography, forensics, reverse engineering, and web exploitation. In this write-up, I’ll walk through some of the problems I solved, breaking down my approach, the tools I used, and the reasoning behind each step. Rather than just dropping final answers, my goal is to document the problem-solving process in a way that’s reproducible and useful for others looking to sharpen their cybersecurity skills. @@ -23,97 +21,97 @@ In this write-up, I’ll walk through some of the problems I solved, breaking do This is the first web challenge; it launches an instance, and the source code is provided for manual code review. There are three important files: two Jinja templates and a Python file. upload.html -\`\`\`html -\<\!DOCTYPE html\> -\ -\ - \Upload Image\ -\ -\ - \Upload Images\ - \This free to use website allows you to render your images\!\ - \
- \ - \ - \ -\ -\ -\`\`\` +```html + + + + Upload Image + + +

Upload Images

+

This free to use website allows you to render your images!

+ + + +

+ + +``` display.html -\`\`\`html -\<\!DOCTYPE html\> -\ -\ - \View Image\ -\ +```html + + + + View Image + + + +

Uploaded Image

+ +

Upload another image

+ + +``` -\`[app.py](http://app.py)\` +`[app.py](http://app.py)` -\`\`\`python -from flask import Flask, request, redirect, render\_template, make\_response, url\_for -app \= Flask(\_\_name\_\_) +```python +from flask import Flask, request, redirect, render_template, make_response, url_for +app = Flask(__name__) from hashlib import sha256 import os def allowed(name): - if name.split('.')\[1\] in \['jpg','jpeg','png','svg'\]: + if name.split('.')[1] in ['jpg','jpeg','png','svg']: return True return False -@app.route('/',methods=\['GET','POST'\]) +@app.route('/',methods=['GET','POST']) def upload(): - if request.method \== 'POST': + if request.method == 'POST': if 'file' not in request.files: return redirect(request.url) - file \= request.files\['file'\] - if file.filename \== '': + file = request.files['file'] + if file.filename == '': return redirect(request.url) if file and allowed(file.filename): - filename \= file.filename - hash \= sha256(os.urandom(32)).hexdigest() - filepath \= f'./static/uploads/{hash}.{filename.split(".")\[1\]}' + filename = file.filename + hash = sha256(os.urandom(32)).hexdigest() + filepath = f'./static/uploads/{hash}.{filename.split(".")[1]}' file.save(filepath) - return redirect(f'/render/{hash}.{filename.split(".")\[1\]}') - return render\_template('upload.html') + return redirect(f'/render/{hash}.{filename.split(".")[1]}') + return render_template('upload.html') -@app.route('/render/\') +@app.route('/render/') def render(filename): - return render\_template('display.html', filename=filename) + return render_template('display.html', filename=filename) @app.route('/developer') def developer(): - cookie \= request.cookies.get("developer\_secret\_cookie") - correct \= open('./static/uploads/secrets/secret\_cookie.txt').read() - if correct \== '': - c \= open('./static/uploads/secrets/secret\_cookie.txt','w') + cookie = request.cookies.get("developer_secret_cookie") + correct = open('./static/uploads/secrets/secret_cookie.txt').read() + if correct == '': + c = open('./static/uploads/secrets/secret_cookie.txt','w') c.write(sha256(os.urandom(16)).hexdigest()) c.close() - correct \= open('./static/uploads/secrets/secret\_cookie.txt').read() - if cookie \== correct: - c \= open('./static/uploads/secrets/secret\_cookie.txt','w') + correct = open('./static/uploads/secrets/secret_cookie.txt').read() + if cookie == correct: + c = open('./static/uploads/secrets/secret_cookie.txt','w') c.write(sha256(os.urandom(16)).hexdigest()) c.close() - return f"Welcome\! There is currently 1 unread message: {open('flag.txt').read()}" + return f"Welcome! There is currently 1 unread message: {open('flag.txt').read()}" else: - return "You are not a developer\!" + return "You are not a developer!" -if \_\_name\_\_ \== '\_\_main\_\_': +if __name__ == '__main__': app.run(host='0.0.0.0', port=1337) -\`\`\` +``` From the files, we can see that this is an app built using Flask. What the app does: @@ -139,12 +137,12 @@ Key Observations 1. File upload: Only the extension is checked; there is no content verification. So you can upload anything with .jpg, .png, etc. -2. Render template: The render() route calls render\_template('display.html', filename=filename). +2. Render template: The render() route calls render_template('display.html', filename=filename). -3. Secrets: The /developer route requires knowing secret\_cookie.txt. We need a way to leak it. +3. Secrets: The /developer route requires knowing secret_cookie.txt. We need a way to leak it. Navigating to the developer endpoint without the cookie returns this request. -\`\`\` +``` HTTP/1.1 200 OK Server: Werkzeug/3.1.3 Python/3.10.16 Date: Sun, 17 Aug 2025 11:51:49 GMT @@ -152,37 +150,37 @@ Content-Type: text/html; charset=utf-8 Content-Length: 24 Connection: close -You are not a developer\! -\`\`\` +You are not a developer! +``` Now I tried some path traversal techniques, but I couldn't reach the cookie. I turned my focus on how the uploaded images were being rendered and saw that it was an inline iframe. Hmm… interesting, what if I could get code execution on this? I thought of uploading a malicious SVG. Here is an innocent-looking SVG -\`\`\` -\ - \