InfoSec Institute – CTF Level 2

This article is a solution to Level 2 of InfoSecInstitute’s CTF challenge. The challenge can be found here.

Although the solution is actually very simple, I’m going to describe a number of other steps to consider. If you just want the final answer, feel free to skip to the end.

Step 1 – Static Source Code Analysis

The first step is to look at the underlying source code. I’ll typically do a quick scan to see if anything is out of place or poorly-hidden. Incomplete or shoddy cover-ups can serve as a red flag to identify areas that need further investigation.

There’s some obfuscated JavaScript near the bottom of the page, but it’s easily recognized as just a Google Analytics snippet. If I wasn’t aware of that, I’d search for the string “function(i,s,o,g,r,a,m)”, which, sure enough, yields plenty of results.

The next chunk of code is a generic asynchronous loader function for the domain Oh, it’s just some marketing code from a (probably legitimate) company. I’ve never heard of them, but I’m not immediately suspicious.

There are a couple of unaccounted-for variables in the second snippet, but they appear to be consistent across pageviews, so I’m assuming it’s a unique identifier of some sort. I’ll mark a note to return here later, if needed.

Finally, there’s this:

Screenshot from 2015-03-12 19:28:31

The “leveltwo.jpeg” image is inside the “lvlone” div. This could certainly be a simple typo, but typographical errors break things, too. Since there’s nothing super solid to go on, and the message references the image file, I’ll start the next step with this.

Step 2 – Checking Dependencies

Any number of scripts (or other resources) can be included in the request. Although it’s not at all uncommon for people to host their own libraries versus using a content delivery network (CDN), seeing local paths to scripts and CSS files for common libraries should be investigated further.

Screenshot from 2015-03-12 19:41:52

If we trust the CDN, we can trust that the content it delivers is the actual jQuery library or Bootstrap CSS file, and not a malicious script that someone simply named “bootstrap.min.css”.

Screenshot from 2015-03-12 19:42:58

This doesn’t account for MITM-style vulnerabilities, where someone listening on your network could supply an altered copy of these libraries. 

The local files could be verified with an md5 hash, but I’m more interested in the image at this point. I’ll save that for later if nothing else turns up.

Open up the developer console (F12 in Chrome), and view the “Network” tab. This tab shows all of the individual components required to render the page.

Screenshot from 2015-03-12 19:48:43


The most interesting thing to me is the 200 (OK) HTTP response from the server. This indicates that the image does exist, but it’s still not showing up. If the image didn’t exist, I’d expect a 404 (Not Found) response, which is what is returned with this image tag:

You shouldn't actually see an image here

You’ll see the following requests for this page:

Screenshot from 2015-03-12 19:50:39

Note: If you receive a 302 (Not Modified) response instead of a 200, it simply indicates that the image is already saved in your browser’s cache. The server is telling your browser there’s no need to download a new copy, since the image hasn’t changed. Ctrl+F5 will force the browser to get the image from the server.

Screenshot from 2015-03-12 19:53:19

This leaves us with a missing image that isn’t actually missing. Since the image can’t be right-clicked on and saved, we can rule out a fake image of a missing image, like this one:

Screenshot from 2015-03-12 20:09:37

Open the image in a new tab by right-clicking on it and selecting “Open in New Tab/Window”. Developer tools confirms that the image exists, since we get another 200/302 response:

Screenshot from 2015-03-12 20:16:05

The response headers are curiously missing a Content-Type header, which is usually present. Specifically, I’d expect:

Content-Type: image/jpeg

Again, this is nothing super-concerning, just another subtle clue that something is amiss. It could be a simple oversight or server misconfiguration that’s causing the image to not be displayed, or it could be a corrupt image.

By clicking on the “Response” tab, we can view the raw data received from the server, which is:

Base64 Result

This is an easily-identifiable base64-encoded string, which is a valid way of sending images on the web. If you’re not familiar with this method, open the following link and inspect the Response.

Notice the difference in the string lengths? There’s not enough data in the leveltwo.jpeg file to be a valid image (probably). Instead, it looks like an encoded string (text).

The image above (apple.png) isn’t actually an image file. It’s a PHP script that outputs a base64-encoded image.
I’ve used an .htaccess rewrite to make it appear as an image at first glance, and for all intents and purposes, it is an image!

The real file is located at:

Not linked to an image file!

Remember that missing Content-Type header? That’s the missing piece that tells the browser to interpret it as image data, and not a bunch of garbled characters. Here’s the same script, but without the Content-Type header:

Here’s a comparison of the two, with and without the Content-Type header:

Screenshot from 2015-03-12 20:43:13
This script includes the Content-Type header, thus displaying an image.


Screenshot from 2015-03-12 20:43:25
Without the appropriate header, the image data is interpreted literally, as text.

You can find the source code for these test files on GitHub.



The solution is to simply decode the string that’s posing as an image. You can use an online tool, or switch over to the “Console” tab of the Developer Tools and decode it in JavaScript with the window.atob() method.

Screenshot from 2015-03-12 20:57:05

That’s all there is to it! Now, go find the string and decode it!

(If you’re too lazy for that, click the image below)

Screenshot from 2015-03-13 08:32:33
Solution (Click to enlarge)
InfoSec Institute – CTF Level 2

Leave a Reply