WebSec 101: JuiceShop ⭐⭐⭐⭐ challenges 3/3


Welcome back, to the third, and the last part of my web sec journey through Juice Shop ⭐⭐⭐⭐ challenges!

Quick reminder: there are 24 ⭐⭐⭐⭐ challenges and I’ve already finished 16 of them and today I’m planning to solve the last 8 from categories: XSS (wow!), Vulnerable Components, Broken Authentication, and Unvalidated Redirects!

Without further ado let’s put the pedal to the metal!


As I mentioned, previous parts, covered 16 challenges in such categories as:

Today I’m going to complete the rest of them:

  • XSS 3
  • Vulnerable Components 2
  • Broken Authentication 2
  • Unvalidated Redirects 1

And let’s begin with XSS!

⭐⭐⭐⭐CSP Bypass (XSS)

To be honest with you, I was afraid of this one. All this CSP stuff is overwhelming for me. But it occurred that the devil’s not so black as he is painted!

So, we have to bypass the Content Security Policy and perform an XSS attack with <script>alert(`xss`)</script> on a legacy page within the application.

From previous challenges, I know that this legacy page points to is /profile page, we attacked with WebSec 101: JuiceShop ⭐⭐⭐ challenges 2/2.

When we try to change username to <script>alert(`xss`)</script> input is sanitized into lert(`xss`)</script>.

After few attempts we know that <<a|ascript>alert(`xss`)</script> would do the trick, but the username is changed to / instead.

Also, we can observe an error in the developer tools console – we are violating Content Security Policy.

What is the Content Security policy then?

Is a W3C specification offering the possibility to instruct the client browser from which location and/or which type of resources are allowed to be loaded. To define a loading behavior, the CSP specification use “directive” where a directive defines a loading behavior for a target resource type.

Directives can be specified using HTTP response header (a server may send more than one CSP HTTP header field with a given resource representation and a server may send different CSP header field values with different representations of the same resource or with different resources) or HTML Meta tag, the HTTP headers below are defined by the specs:

◉ Content-Security-Policy: Defined by W3C Specs as standard header, used by Chrome version 25 and later, Firefox version 23 and later, Opera version 19 and later.

◉ X-Content-Security-Policy: Used by Firefox until version 23, and Internet Explorer version 10 (which partially implements Content Security Policy).

◉ X-WebKit-CSP: Used by Chrome until version 25.

Content Security Policy – OWASP

Sure, so basically for my understanding the most important part is from which location and/or which type of resources are allowed to be loaded.

The header (in response) is very simple:

Content Security Policy Header

Although, the concept is new to me and I’ve started Googling and found out the fantastic and informative article, which simplifies the topic for me – How to trick CSP in letting you run whatever you want, by bo0om, Wallam research, – it’s pointing the possibility to bypass CSP via images (it’s close, but the challenge is not about it).

The profile page allows us to provide pictures – by upload or image URL. I’ve started to fool around with the URL option, and after a while, I’ve discovered, those invalid URLs are added to CSP!

The little payload, like:

https://a.png; script-src 'unsafe-inline' 'self' 'unsafe-eval' 

Is added to CSP and triggers the XSS we put into username!

Content Security Policy after correctness

And the challenge is solved 😊

⭐⭐⭐⭐HTTP-Header XSS (XSS)

In this challenge we have to perform a persisted XSS attack with well-known <iframe src="javascript:alert(`xss`)"> through an HTTP header.
The hint is to find a piece of displayed information that could originate from an HTTP header.
As we can read in ‘more hints’- The difficulty lies in finding the attack path whereas the actual exploit is rather business as usual.

During my voyage around the Juice Shop, the Last Login IP bookmark got my attention. After the first login to the app, the Last Login IP was set to, and after relogin it is correct:

Yep, localhost is correct value!

The value is set by corresponding GET /rest/saveLoginIp request with an empty body. I’ll try a request with an additional HTTP header:


But the response is HTTP/1.1 304 Not Modified – unfortunately ☹

There is another header, not very popular – True-Client-IP. As we can read in What is True-Client-IP? – Cloudflare:

True-Client-IP is a solution that allows Cloudflare users to see the end user’s IP address, even when the traffic to the origin is sent directly from Cloudflare.

After try with True-Client-IP response is HTTP/1.1 200 OK. After little change of payload into:

True-Client-IP:  <iframe src="javascript:alert(`xss`)"> 

And relogging – our challenge is solved 😊

⭐⭐⭐⭐Legacy Typosquatting (Vulnerable Components)

Again we have to inform the shop about a typosquatting trick it has been a victim of at least in v6.2.0-SNAPSHOT.
We are advised to investigate the forgotten developer’s backup file. We have obtained this backup file in WebSec 101: JuiceShop ⭐⭐⭐⭐ challenges 1/3 – it was package.json.bak downloaded with NullByte Injection trick.

Extended hints tell us that it might be interesting to read Malicious packages in npm. Here’s what to do – Ivan’s Akulov interesting article about npm packages from dependencies.

We have a few dependencies to check:

"dependencies": {
    "body-parser": "~1.18",
    "colors": "~1.1",
    "config": "~1.28",
    "cookie-parser": "~1.4",
    "cors": "~2.8",
    "dottie": "~2.0",
    "epilogue-js": "~0.7",
    "errorhandler": "~1.5",
    "express": "~4.16",
    "express-jwt": "0.1.3",
    "fs-extra": "~4.0",
    "glob": "~5.0",
    "grunt": "~1.0",
    "grunt-angular-templates": "~1.1",
    "grunt-contrib-clean": "~1.1",
    "grunt-contrib-compress": "~1.4",
    "grunt-contrib-concat": "~1.0",
    "grunt-contrib-uglify": "~3.2",
    "hashids": "~1.1",
    "helmet": "~3.9",
    "html-entities": "~1.2",
    "jasmine": "^2.8.0",
    "js-yaml": "3.10",
    "jsonwebtoken": "~8",
    "jssha": "~2.3",
    "libxmljs": "~0.18",
    "marsdb": "~0.6",
    "morgan": "~1.9",
    "multer": "~1.3",
    "pdfkit": "~0.8",
    "replace": "~0.3",
    "request": "~2",
    "sanitize-html": "1.4.2",
    "sequelize": "~4",
    "serve-favicon": "~2.4",
    "serve-index": "~1.9",
    "socket.io": "~2.0",
    "sqlite3": "~3.1.13",
    "z85": "~0.0"

After (not so long) brute force check, we are visting https://www.npmjs.com/package/epilogue-js site.

Typosquatted library

And we know, that epilogue-js is typo squatted dependency we are looking for 😉

⭐⭐⭐⭐Login Bjoern (Broken Authentication)

This one I challenging, because we have to log in with Bjoern’s Gmail account without previously changing his password, applying SQL Injection, or hacking his Google account. The hints are pointing to OAuth, so let’s look at OAuth implementation here.

After searching for OAuth in main-es2015.js we can find an interesting function:

login(t) {
    email: t.email,
    password: btoa(t.email.split("").reverse().join("")),
    oauth: !0

We may find out that Bjoern’s real account is : bjoern.kimminich@gmail.com

I do not like to reinvent the wheel so with simple JS script:

<!DOCTYPE html>

<h2>HFOC Solver</h2>

<p id="demo"></p>

var x = 'bjoern.kimminich@gmail.com';
var z = btoa(x.split("").reverse().join(""))
document.getElementById("demo").innerHTML = "The value of z is: " + z;


Note: you can give it a try on W3Schools
Note2: btoa is Base64 encoding (of ‘bjoern.kimminich@gmail.com’ reversed)

The output of the code above

Login with credentials:

Login: bjoern.kimminich@gmail.com
Password: bW9jLmxpYW1nQGhjaW5pbW1pay5ucmVvamI=

Solves the challenge 😊

⭐⭐⭐⭐Reset Bender’s Password (Broken Authentication)

Again we are dealing with Futurama character – Bender Bending Rodriguez, and we have to reset his password with the correct answer to the security question. The question is:

Company you first work as an adult? 

As we can read on Bender wiki page Bender had a job at the metalworking factory, bending steel girders for the construction of suicide booths.

Suicide booths lead us to In fiction section of the Euthanasia device wiki (damn… is it the end of the Internet?)

Futurama’s first paragraph starts with ‘In the world of Futurama, Stop-and-Drop suicide booths resemble phone booths and cost one quarter per use.

So after checking Stop-and-Drop challenge is NOT SOLVED, but writing it as Stop'n'Drop is the correct answer to Bender’s security question.

⭐⭐⭐⭐Server-side XSS Protection (XSS)

XSS again… I love those challenges, but again? Now we have to perform persistent XSS with the well-known payload <iframe src="javascript:alert(`xss`)"> bypassing a server-side security mechanism.

We are advised to focus on Comment field in the Customer Feedback.

Since it’s our third XSS in this article, let’s find out what does server-side XSS mean:

[Types of Cross-Site Scripting]

For years, most people thought of these (Stored, Reflected, DOM) as three different types of XSS, but in reality, they overlap. You can have both Stored and Reflected DOM Based XSS. You can also have Stored and Reflected Non-DOM Based XSS too, but that’s confusing, so to help clarify things, starting about mid 2012, the research community proposed and started using two new terms to help organize the types of XSS that can occur:

◉ Server XSS

◉ Client XSS

[Server XSS]

Server XSS occurs when untrusted user supplied data is included in an HTTP response generated by the server. The source of this data could be from the request, or from a stored location. As such, you can have both Reflected Server XSS and Stored Server XSS.

In this case, the entire vulnerability is in server-side code, and the browser is simply rendering the response and executing any valid script embedded in it.

[Recommended Server XSS Defenses]

Server XSS is caused by including untrusted data in an HTML response. The easiest and strongest defense against Server XSS in most cases is: Context-sensitive server side output encoding.

Input validation or data sanitization can also be performed to help prevent Server XSS, but it’s much more difficult to get correct than context-sensitive output encoding.

Types of Cross Site Scripting – OWASP

Trying naive comment: <iframe src="javascript:alert(`xss`)"> with feedback is sanitized and shows as empty string.

But after solving previous challenge, we have a whole list of dependencies, and we can read that for package responsible for HTML sanitization is named "sanitize-html": "1.4.2"!

Googling for  “sanitize-html: 1.4.2”, gives us first lucky shot affecting versions <1.4.3 (that’s us! :D)

Since the sanitize-html module trusts ‘text’ coming from htmlparser2, and outputs it without further escaping (because htmlparser2 does not decode entities in text before delivering it), this results in an XSS attack vector if sanitize-html ignores the img tag (according to user-configured filter rules) but passes the text intact, as it must do to keep any text in documents

[It is also illustrated by bkimminich comment]

Sanitization is not applied recursively, leading to a vulnerability to certain masking attacks. Example:

I am not harmless: <<img src="csrf-attack"/>img src="csrf-attack"/> is sanitized to I am not harmless: <img src="csrf-attack"/>

sanitize-html vulnerabilities | Snyk

Knowing that, our new payload:

<<script>HFOC</script>iframe src="javascript:alert(`xss`)">

Solves the challenge 😊

Server-side XSS

⭐⭐⭐⭐Vulnerable Library (Vulnerable Components)

Again, we have to take a peek on vulnerable components. Now we have to inform the shop about the vulnerable library it is using, with name and version.

But as we know from the previous challenge, sanitize-html 1.4.2 is a vulnerable library and we know its exact name and version!

Sending sanitize-html 1.4.2 as customer feedback is the right answer!

⭐⭐⭐⭐Whitelist Bypass (Unvalidated Redirects)

The last one! Let’s get over it quickly. Here we have to enforce a redirect to a page we are not supposed to redirect.

One of the redirecting links is GitHub in Navigation Bar.

GitHub redirect element

And it’s redirecting us to (which works like a charm)

Let’s try to navigate to headfullofciphers.com with

But this results with 406 Error: Unrecognized target URL for redirect: https://headfullofciphers.com

Sometimes parser can be tricked by adding an expected (whitelisted) parameter in the end. When we craft: 
(and we have to change parameter name)

With we’ve bypassed correctly 😉


Its’ been a while, but I’ve finally managed to finish all ⭐⭐⭐⭐ challenges!

Finally, ⭐⭐⭐⭐- all green!

My scoreboard is 69% completed, and there are 17 ⭐⭐⭐⭐⭐ and 11 ⭐⭐⭐⭐⭐⭐ challenges left!

To be honest, they look way more advanced than those previously finished, so I’m not sure when the time comes to tackle them 😀 but I’ll try someday!

That’s it for today, I hope you enjoyed this article!

If you have any questions, do not hesitate to contact me!


Reference list:

  1. Content Security Policy – OWASP
  2. How to trick CSP in letting you run whatever you want, by bo0om, Wallam research
  3. What is True-Client-IP? – Cloudflare
  4. Malicious packages in npm. Here’s what to do – Ivan Akulov
  5. Bender (Futurama) – Wikipedia
  6. Euthanasia device – Wikipedi
  7. Types of Cross Site Scripting – OWASP
  8. sanitize-html vulnerabilities – Snyk

Check out related posts:

Leave a 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 )

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

Blog at WordPress.com.

Up ↑

%d bloggers like this: