OWASP Juice Shop – CSRF

Hellow world!

In today’s write-up, I will explain to you in a very detailed and informative fashion the steps I took, or didn’t, in order to achieve a successful Cross Site Request Forgery Attack (aka CSRF) on OWASP’s Juice Shop application. It is going to be a long one, so buckle up…

To be perfectly honest this one tripped me a little. After the initial findings of an XSS, SQLi, and an Open Redirect, I decided to look for CSRF.(btw I know I still haven’t wrote about the SQLi, but hopefully I will in the next write-up )

If you are familiar with CSRF attacks, or participated in any kind of bug bounties, you should know that CSRF is only valuable on state [important] changing actions, as these are usually the ones that lead to bigger problems. Functionalities like change password (leading to ATO, after changing email with new password), change email (leading to ATO via reset password) etc. On the other hand a CSRF on a logout functionality is practically worthless. All you can do is force an authenticated user to logout of their account by visiting a malicious page or clicking on a malicious link. So to make a long story short, when hunting for CSRF, you should look for functionalities (state changing actions) that might have a potential impact. This is exactly what tripped me about this challenge, as you will read.

For the reasons explained, the first thing I went for was the change password functionality because as far as I was concerned, that was the only functionality that could have a big impact on this particular application (more on this later).

As you can see on this page, the front-end application would ask you to provide the current password, and if only you knew the correct pass you would be able to reset it to a new one. This seemed to be a little problematic to perform via csrf as we wouldn’t be able to pass the current password of the victim to the back-end once the victim visits our malicious web page, in order to change their password to our desired value.

By the way, the reason I will censor the images is that I am hosting Juice Shop on my remote VPS, and since this is an intentionally vulnerable app, IF somebody finds an RCE on the app, they can basically achieve command execution on my box and we don’t want that! (Also I am not paranoid at all )

With that in mind, I decided to intercept the change password request maybe there was a way around this.

So this is how the normal (successful) request looks like:

If you are like me, you should immediately be thinking: “Okay but what happens if I remove the current value (which provides the current password to the endpoint) and submit the request again?

And as you can see, absolutely nothing! The application doesn’t care about the current password not being provided and resets the password anyway. This is crucial to us as we now should be able to reset any users password (without prior knowledge of their current password) via CSRF. It goes something like this:

  • The unsuspecting user visits your website
  • You hosted a malicious script on your website which runs in the background
  • The script loads in the victims browser and makes an XMLHttpRequest request in background to /rest/user/change-password?new=hacked123&repeat=hacked123
  • And without them even noticing their password has been “hacked123”

But before you get too excited, there’s a little problem in here. If you look at the change password request closely, you can see that in order for the server to verify our request, the browser is sending a custom Authorization header containing our Bearer token to the back-end. Sad part is, even tho its value is exactly the same as the token value in our Cookie header, removing it will trigger an error from the server.

You could remove the Cookie header or the token part and leave the Authorization header, and the server would respond just fine, but not the other way around.

With this new realization, we can pretty much kiss goodbye CSRF on password change.

That is because, when using Javascript or XMLHttpRequests to make cross-origin requests, we are only allowed to make a simple request. Meaning we simply cannot include our Authorization header in the request, and without that the server would not process our request and throw an error.

It’s helpful to remember that a simple request can only be of the following methods:

  • GET
  • HEAD
  • POST

and only of the following content-types:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

Therefore by adding a custom http header (Authorization) the browser would first send a pre-flight OPTION request to the server, which is not one of the 3 methods we can supply in a simple request. That is why adding an Authorization header completely messes up our chain of attack. For more info check out here.

At this point, I was a little bit confused. I was thinking maybe there is a way around this that I just didn’t know about. I tried a zillion different methods from trying Fetch request in the browser, to cURL and etc. Until I decided to check the score-board and look at the CSRF challenge to see if it can give me an insight…

Yes you are seeing that right! Only one CSRF challenge and it is on a name changing functionality…

TBh I’m not sure why the developers decided to setup the challenge this way, as I would personally be hesitant to even report such a bug on a public bug bounty program thinking it would just be closed as informational, since I do not see any impact on it. But whatever, we are here to learn and let’s just get over with this.

I quickly went to the /profile page, changed my username and captured the request in burp:

and as you can see, unlike the change password functionality, here we do not have to supply and Authorization header to the server. This is actually crucial to us as we can now perform our CSRF attack.

So all we have to do at this point, is to capture the request in burp and generate a proof of concept:

Right click on the request → Engagement tools → Generate CSRF PoC

You can then go ahead and host this on your website. Once the victim visits the page and clicks on the submit button, their username will be changed to asbx99 or any value you provide it. (you can tinker with the code to make it submit the form automatically on page load)

NOTE: This CSRF attack is not possible (or at least I wasn’t able to pull it off) via an XMLHttpRequest.

The server sets Access-Control-Allow-Origin header to * which in turn disables accepting credentials/cookies as this would pose a great danger on the target.

@Firefox: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘xyz/profile’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’)

This is however still possible via browser’s form submit and to be entirely honest I don’t know or understand why!

Okay I think that’s it for today. If you read your way all the way down here, thank you very much. This was by far the most interesting challenge I solved on Juice Shop, and I hope you enjoyed reading about it and learnt something as well.

Peace

Leave a Comment