April 26, 2021

The #9th DOJO CHALLENGE was on finding a way to create a valid prototype pollution payload to exploit SSRF and retrieve a secret flag.


We are glad to announce the #9 DOJO Challenge winners list.

The FIRST and 4 best quality write-up reports:

  • The fastest hunter : Robin
  • The 4 most beautiful reports: Holme, Remmer, Marcosen, Rooteval

Subscribe to our Twitter or Linkedin feeds to be notified of the upcoming challenges.

Read on to find the best write-up as well as the challenge author’s recommendations.

The challenge

EvilCorp2.0 has a script to check the current state of an internal service. They want to make sure that this script, with all its security, cannot be used to retrieve the secret path…Find a way to bypass all security mechanisms to retrieve the /secret.

See the challenge page >

We asked you to produce a qualified write-up report explaining the logic allowing such exploitation. This write-up serves two purposes:

  • Ensure no copy-paste would occur.
  • Determine the contestant ability to properly describe a vulnerability and its vectors inside a professionally redacted report. This capacity gives us invaluable hints on your own, unique, talent as a bug hunter.


Remmer’s report was detailed, informative, and good at explaining the logic to construct the magic payload and to bypass every security mechanisms in script to finally obtain the secret flag hidden in /secret.

The others reports, notably Holme’s, Marcosen’s and Rootevals were also very nice, we’re sorry can’t publish them all because that’s where you clearly witness the outstanding creativity of our community.

Thank you all for playing with us!

Remmer‘s Write-Up

————– START OF Remmer REPORT ——————


User-supplied parameters are used to build an URL which is then be fetched by the server. Even if some protections were added to prevent users from fetching arbitrary resources, it is possible to bypass them.

Indeed, the script does not properly check that the supplied parameters are strings, and passing Javascript objects allows to bypass the WAF and fetch an arbitrary resource.


The script executes the following function on the provided parameters to check for injection attempts such as path traversal:

function WAF(s) {
  const forbiden = ["/", "\", "%"]

  if (Array.isArray(s)){
    throw Error("WAF: String expected")

  if (forbiden.some(c => s.includes(c))) {
    throw Error("WAF: Invalid char")
  return s

Even if a check is performed to ensure that the parameter is not an array, nothing prevents from sending an object. For instance, the following payload passes the first check:


The returned error is:

"s.includes is not a function"

The payload can be modified to define an includes function that always returns false:


The returned error is now:

"path.indexOf is not a function"

Similarly, the payload can also be modified to define an indexOf function that always returns 0:


Now, the script does not return an error, but it attempts to fetch http://localhost/[object%20Object]. We want to fetch http://localhost/secret. To achieve this, the payload can be modified to define a toString function that always returns secret:


Now, the path parameter was successfully injected, but there is a protection at the network level that returns this error:

"You can't access the flag from "localhost", try something else."

To bypass this protection, the port parameter can also be injected with the value pw@ The final payload is the following:


Once parsed, the script will attempt to fetch the URL http://localhost:foo@ In this case, localhost is the username and foo is the password that will be used to connect to, which is the same as localhost.


When issuing the following payload:


The flag is returned:

"flag{You just found a secret !}"


An attacker could perform an SSRF and force the server to execute arbitrary HTTP queries on internal hosts.


It is not sufficient to use the isArray method since it allows objects to be sent. Instead, the typeof operator could be used:

typeof s === 'string'

Furthermore, the port number should be an integer, not a string. A more secure way to build the URL would be using the URL function:

let url = new URL(path, 'http://localhost')
url.port = port

————– END OF Remmer REPORT ——————

Update: The DOJO #9 was using a “jsonify” filter, so as it was, the final payload from Remmer’s report could not work with this filter enabled, because the JSON was not valid. Because we appreciated Remmer’s writeup and explanations, that’s why we decided to publish it. After discussion with Remmer, we were able to validate the challenge and build the final payload, including the operation with the jsonify filter: