Zero-Day Research: Ezgif Server Side Request Forgery

Web applications are quite powerful and diverse in their functionality. I can’t imagine anyone could have predicted how fast web technology could forge its way into every sector of business in the world. It seems as if every day a new type of widget, app, and social networking site are created to appeal to a new group of consumers. Driven by our urge to create something new and captivating, the forward push of web and mobile technology has without a doubt created the largest landscape for security and privacy abuse. As new software comes out, new vulnerabilities follow. Server Side Request Forgery is one of those web vulnerabilities that aren’t particularly new but have managed to stick around for the long haul.

Disclosure and Disclaimer

The Server Side Request Forgery vulnerability was responsibly disclosed to the Ezgif security team. This post was intended for web developers who are interested in keeping their applications secure and is for EDUCATIONAL PURPOSES ONLY. I do not condone illegal activity and cannot be held responsible for the misuse of this information.

What is Server Side Request Forgery?

According to the Open Web Application Security Project, “SSRF flaws occur whenever a web application is fetching a remote resource without validating the user-supplied URL. It allows an attacker to coerce the application to send a crafted request to an unexpected destination, even when protected by a firewall, VPN, or another type of network access control list (ACL).”

Essentially this vulnerability class enables an attacker to convince a web server to reach out and grab content on the attacker’s behalf. Let’s break this down with an example using the following URL:

http://example.com?url=http://evilurl.com
  1. The attacker makes a request to http://example.com?url=http://evilurl.com.
  2. The web server extracts the value of the URL parameter (http://evilurl.com).
  3. The web server makes an HTTP request of its own, reaching out to http://evilurl.com and downloading the necessary content from that page.
  4. The web server displays the downloaded content back to the attacker.

Why is Server Side Request Forgery a Security Bug?

SSRF is a security issue because the attacker can convince the web server to leak sensitive information they typically would not have access to. The best way to understand this is to give a few scenarios using the example URL from above:

Scenario 1: Using alternate URL schemes to download certificates, SSH Keys, passwords, and other sensitive files.

http://example.com?url=file:///etc/passwd
http://example.com?url=ldap://localhost:11211/%0astats%0aquit

Scenario 2: Port scanning internal networks.

http://example.com?url=http://localhost:22
http://example.com?url=http://localhost:443
http://example.com?url=http://localhost:445

Scenario 3: Accessing data on cloud instances

http://example.com?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/s3access

These examples only scratch the surface of what’s possible with SSRF. For substantial list of possibilities, check out Payloads All The Things.

Ezgif Server Side Request Forgery

While I was converting a GIF to an MP4 file I discovered an SSRF vulnerability in the file upload page of Ezgif’s website. Any time you allow a user to upload from a URL without sanitizing the input you run the risk of SSRF.

To confirm this vulnerability we set up a publicly routable reverse proxy, input the address of our reverse proxy, and wait for the request to come through.

Mitigations

OWASP regularly posts mitigations for SSRF vulnerabilities. A snippet of those mitigations can be seen below:

HackTheBox: Baby Todo or Not Todo Challenge

Practice can be quite a double-edged sword. Most of us know that creating long-term behaviors and skills only comes through the reinforcement of those skills through practice. We often spend too little time thinking about how we practice and which behaviors are being reinforced during our practice sessions. For us to become good at analyzing web applications we have to practice not only the ‘hacking’ portion of the exercise but also how we deal with frustration and other negative emotions in the process. I was presented with a great opportunity to practice all of these skills in the ‘Baby Todo or Not Todo’ web challenge from hackthebox. Let’s take a crack at this challenge.

Browsing the Website

With most challenges, I start by using the application as an average consumer would. I click all of the buttons and explore all of the pages to see what functionality is made available to me. Being patient in this phase is key, and we must allow ourselves enough time to get a good understanding of the application without feeling rushed to find vulnerabilities. Without a doubt, the vulnerabilities will present themselves as we explore the terrain.

Analyzing the Source Code

There doesn’t seem to be a whole lot of functionality packed into this application. It looks like a simple ‘to-do list’ that allows you to add and delete tasks. If we take a moment to do some critical thinking we conclude that there must be some mechanism in the background that is enabling the application to keep track of each ‘to-do list’ on a per client basis. This suspicion is confirmed if we browse the website from another browser or private window because we are presented with a fresh ‘to-do list’ every time. Our next step is to take a look at the web page source in our browser to see if we can glean more information about the page.

Looking at the page source, we find a script that contains the following code:

// don't use getstatus('all') until we get the verify_integrity() patched
const update = () => getTasks('user4f375000')
update()
setInterval(update, 3000)

Command breakdown

The first line defines an update function that will get the tasks for the specified user, which we can assume is a randomly generated user name for each client that connects to the web page. We can confirm this by connecting to the page from another browser or private window and view the user value defined in this script.

const update = () => getTasks('user4f375000')

The second line calls the update function previously defined. Once this function is called it will retrieve all the tasks for the defined user.

update()

The last line will define an update interval. In this case, the update function will be called every 3 seconds (3000 milliseconds).

setInterval(update, 3000)

The comment at the beginning of the script is quite interesting. It seems to imply that there is a vulnerability in the verify_integrity() function that will allow a user to retrieve more information than they should be allowed to. For now, we will keep that comment in the back of our mind and explore the main.js script to understand the inner workings of the getTasks() function.

Looking at the source of this function, we can confirm that ‘getTasks’ is making a request to the API at ‘/api/list/${endpoint}/’ to retrieve our tasks. The request also passes a secret which is used to verify the integrity of the request.

fetch(`/api/list/${endpoint}/?secret=${secret}`)

Dynamic Analysis Solution

Let’s review what we know so far:

  1. This application is a to-do list that stores entries on a per-user basis.
  2. There is an update function that runs every 3 seconds to populate the tasks from the backend database.
  3. The update/getTasks functions include a secret in the request to access the tasks for the specified user from the API.

Let’s spin up Burp Suite and see if we can capture the update request that happens every 3 seconds.

If we dig through our memory and think about the ‘getTasks’ function for one moment we remember the following notation.

fetch(`/api/list/${endpoint}/?secret=${secret}`)

The ‘${endpoint}’ portion of the URL seems to imply that there are other endpoints that can be used. This is similar to the end of the request where we know the ‘${secret}’ portion means that the secret will change per user. Why not guess and see what other endpoints are available? Wfuzz is the perfect tool for this because we can guess the ‘${endpoint}’ portion of the request (while leaving everything else in the request the same) to see if there are any other endpoints that we can access with our secret. When we intercepted our request in Burp Suite we noticed that there was a cookie bundled with the request. We should make sure to include that cookie when we make a request with Wfuzz.

wfuzz -w /usr/share/wordlists/SecLists/Discovery/Web-Content/api/objects.txt -u http://206.189.113.236:32119/api/list/FUZZ/?secret=d0daC83Aa4B4719 -H "Cookie: session=eyJhdXRoZW50aWNhdGlvbiI6InVzZXJBRWViMTE2RiJ9.YUuLGg.8hb4Rle7WzIlDcAQzwrJKgVauzY" --hh 24

Command breakdown:

  • A wordlist of common API names from SecLists to use for guessing.
-w /usr/share/wordlists/SecLists/Discovery/Web-Content/api/objects.txt
  • The URL we want to use for our requests. The ‘FUZZ’ portion of the URL will be replaced with the API names from the wordlist we chose. We include our secret just like the request we captured from burp.
-u http://206.189.113.236:32119/api/list/FUZZ/?secret=d0daC83Aa4B4719
  • We need to add our cookie to ensure the webserver doesn’t deny our requests.
-H "Cookie: session=eyJhdXRoZW50aWNhdGlvbiI6InVzZXJBRWViMTE2RiJ9.YUuLGg.8hb4Rle7WzIlDcAQzwrJKgVauzY"
  • A normal failed response contains 24 characters. We can verify this by omitting the –hh flag the first time we run the command. We will see that every failed request has 24 characters. This flag will hide every request that contains 24 characters in the response. Alternatively, we could have hidden every response that returned an HTTP status code 403 (not allowed/forbidden) with the –hc flag. By eliminating all of the failed API requests we are left only with endpoints we can successfully access.
--hh 24

Looking at the results we see two endpoints available to us.

Static Analysis Solution

If we want to take a white box approach to solve the challenge we can peek at the source code of the webserver. The two files of interest are routes.py and util.py.

Routes.py

The web framework being used here is flask, which means the routes.py file contains all the routes for the application (read the flask documentation). Taking a glance at the code we see all the possible API routes, including ‘/list/all’ that will potentially dump all of the tasks in the database. There is also a comment that hints us towards the ‘verify_integrity’ function.

Util.py

Reading through the source code we can summarize the verify_integrity function.

  1. check_secret() will verify that a valid secret was passed with the request
  2. check_integrity() will verify if the request has view arguments or contains JSON data. If it has view arguments or JSON data it will do further filtering on the data. If the request has neither of those two things it will do nothing and call the check_secret() function.

Based on this information we need to form a request that has no view arguments or JSON data with our secret embedded in the request. Repeating some of the steps from the dynamic analysis solution we can arrive at the same conclusion and request ‘/api/list/all’ with our captured secret attached.

Victory

Requesting ‘/api/list/all’ with our secret we find our flag with all ‘to-do’ items from every user in the database.

HackTheBox: Looking Glass Web Challenge

Today we will be walking through the ‘Looking Glass’ web challenge from HackTheBox. This specific challenge is quite simple but provides great insight into common web security flaws that you might find in custom-built applications. HackTheBox is an online platform that hosts various penetration testing challenges ranging anywhere from binary exploitation, web security, Windows Active Directory, Internet of Things, and much more.

Browsing the Website

Once we deploy the challenge we are presented with a basic web page that allows the user to run a ping or traceroute command against an IP.

Static Analysis

We can do some basic static analysis by viewing the page source.

There doesn’t seem to be anything interesting going on. We can move on to dynamic analysis from here.

Dynamic Analysis

Let’s do some dynamic analysis by clicking the ‘Test’ button on the web page and intercept the request with burp.

This challenge screams OS command injection. The page simply sends the parameters from the form to the server and (likely) runs the ping or traceroute binary on the file system with the parameters from the POST request. There is probably some PHP code in the background that resembles the following:

$ip=$_POST['ip_address'];
system("traceroute $ip");

This would result in the following Linux command being run:

traceroute 178.62.0.100

If you are not familiar with OS command injection I would highly suggest reading the official OWASP article about it. For this challenge, we can add a semicolon behind the IP in the ‘ip_address’ POST parameter and run extra commands to find the flag. Let’s start with a simple ‘ls’ command (don’t forget to URL encode the parameter).

//Payload
test=ping&ip_address=178.62.0.100;ls&submit=Test
//URL Encoded payload
test=ping&ip_address=178.62.0.100%3B+ls&submit=Test

It worked! We can see the file index.php listed in the results box. Let’s list the contents of the root directory and see what files exist on the file system.

It looks like the flag is in the root directory. We can view the contents of that file and and complete our challenge.