python-gnupg audit
Table of Contents
- 1 gnugp._main_()
- 2 class Verify(object)
- 3 class ImportResult(object)
- 4 class ListKeys(list):
- 5 class Crypt(Verify):
- 6 class GenKey(object)
- 7 class DeleteResult(object)
- 8 class Sign(object)
- 9 class GPG(object)
- 9.1 def _init_(self, gpgbinary='gpg', gnupghome=None, verbose=False, useagent=False, keyring=None)
- 9.2 def opensubprocess(self, args, passphrase=False)
- 9.3 def collectoutput(self, process, result, writer=None, stdin=None)
- 9.4 def handleio(self, args, file, result, passphrase=None, binary=False)
- 9.5 def sign(self, message, **kwargs)
- 9.6 def signfile(self, file, keyid=None, passphrase=None, clearsign=True, detach=False, binary=False)
- 9.7 def verify(self, data):
- 9.8 def verifyfile(self, file, datafilename=None)
- 9.9 def importkeys(self, keydata)
- 9.10 def recievekeys(self, keyserver, *keyids)
- 9.11 def exportkeys(self, keyids, secret=False)
- 9.12 def listkeys(self, secret=False)
- 9.13 def genkey(self, input)
- 9.14 def genkeyinput(self, **kwargs)
- 9.15 def encryptfile(self, file, recipiencts, sign=None, …)
- 9.16 def encrypt(self, data, recipients, **kwargs):
- 9.17 def decrypt(self, message **kwargs):
- 9.18 def decryptfile(self, file, alwaystrust=False, passphrase=None, output=None)
- 10 POC
1 gnugp._main_()
1.1 comments
L58 NullHandler?? see self.writepassphrase L61 there nifty check for p3k
1.2 def copydata(instream, outstream) cleanup
copies data from one stream to another, 1024 bytes at a time.
1.2.1 L79: bad_logic
instream is apparently a file descriptor, but is not checked nor encased in a try/except block.
1.2.2 L78: hanging_fd bad_logic
while True: loop, should be
with open(instream) as instrm:
1.2.3 L88: bad_exception_handling
except:
should catch an IOError, or whatever specific error is raised for broken pipes.
1.3 def threadedcopydata(instream, outstream):
1.3.1 L99:
this just wraps self.copydata in a thread
1.4 def writepassphrase(stream, passphrase, encoding): vuln cleanup
1.4.1 L110: writes_passphrase_to_disk
logger writes passphrase into debug log. this should be patched.
2 class Verify(object)
basic parsing class, no errors found
3 class ImportResult(object)
basic parsing class, no errors found
4 class ListKeys(list):
basic parsing class, no errors found
5 class Crypt(Verify):
basic parsing class, no errors found
5.1 def _init_(self, gpg) cleanup
5.1.1 L338 mro_conflict
Verify.__init__(self,gpg)
should be changed to:
super(Verify, self).__init__(gpg)
6 class GenKey(object)
basic parsing class, no errors found
7 class DeleteResult(object)
basic parsing class, no errors found
8 class Sign(object)
basic parsing class, no errors found
9 class GPG(object) exploitable
9.1 L474: cleanup
cls.__doc__
should go directly underneath class signature
9.1 def _init_(self, gpgbinary='gpg', gnupghome=None, verbose=False, useagent=False, keyring=None) bug
9.1.1 L494-495: type_error
if gnupghome and not os.path.isdir(self.gnupghome): os.makedirs(self.gnupghome,0x1C0)
In [20]: os.makedirs? Type: function String Form:<function makedirs at 0x7f8ddeb6cc08> File: /usr/lib/python2.7/os.py Definition: os.makedirs(name, mode=511) Docstring: makedirs(path [, mode=0777]) Super-mkdir; create a leaf directory and all intermediate ones. Works like mkdir, except that any intermediate path segment (not just the rightmost) will be created if it does not exist. This is recursive. setting mode=0x1c0 is equivalent to mode=hex(0700), which may cause bugs on some systems, see http://ubuntuforums.org/showthread.php?t=2044879 this could be do to the complete lack of input validation in os.makedirs, and it's calling of the os.mkdir() built-in, which may vary depending on the python compilation:
Source: def makedirs(name, mode=0777): """makedirs(path [, mode=0777]) Super-mkdir; create a leaf directory and all intermediate ones. Works like mkdir, except that any intermediate path segment (not just the rightmost) will be created if it does not exist. This is recursive. """ head, tail = path.split(name) if not tail: head, tail = path.split(head) if head and tail and not path.exists(head): try: makedirs(head, mode) except OSError, e: # be happy if someone already created the path if e.errno != errno.EEXIST: raise if tail == curdir: # xxx/newdir/. exists if xxx/newdir exists return mkdir(name, mode)
9.2 def opensubprocess(self, args, passphrase=False) vuln
9.2.1 L515: unvalidated_user_input
cmd.extend(args)
cmd is a list of strings, eventually joined with cmd=' '.join(cmd), and the args are unvalidated in this function. Then this concatenation of args is fed directly into subprocess.Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE). THIS SHOULD BE PATCHED.
9.3 def collectoutput(self, process, result, writer=None, stdin=None)
sends stdout to self.readdata() and stderr to self.readresponse()
9.4 def handleio(self, args, file, result, passphrase=None, binary=False) vuln cleanup
9.4.1 L601: unvalidated_user_input type_check_in_call
p = self._open_subprocess(args, passphrase is not None)
you shouldn't assign or type check in a function call
9.5 def sign(self, message, **kwargs) cleanup
9.5.1 L617-619: hanging_fd
calls self.makebinarystream(), which leaves the file descriptor for the encoded message to be encrypted hanging between scopes.
9.6 def signfile(self, file, keyid=None, passphrase=None, clearsign=True, detach=False, binary=False) cleanup
9.6.1 L632-635: bad_logic
if detach: args.append("--detach-sign") elif clearsign: args.append("--clearsign")
the logic here allows that if a user erroneously specifies both options, rather than doing what the system gnupg would do (that is, do –clearsign, and ignore the –attach-sign), python-gnupg would ignore both.
9.6.2 L626-641:
input 'args' into self.opensubprocess() is defined as static strings.
9.7 def verify(self, data): cleanup
9.7.1 L668-670: hanging_fd
same hanging file descriptor problem as in self.sign()
9.8 def verifyfile(self, file, datafilename=None) vuln cleanup
9.8.1 L683: hanging_fd
more potentially hanging file descriptors…
9.8.2 L684: hanging_fd
oh look, another hanging file descriptor. imagine that.
9.8.3 L690: unvalidated_user_input
args.append('"%s"' % data_filename)
well, there's the exploit. see included POC script.
9.9 def importkeys(self, keydata) vuln
9.9.1 L749: unvalidated_user_input
this function could potentially allow an attacker with a GPG exploit to use it, because it passes key generation parameter directly into the internal packet parsers of GPG. however, without a GPG exploit for one of the GPG packet parsers (for explanation of GPG packets look into pgpdump), this function alone is not exploitable.
9.10 def recievekeys(self, keyserver, *keyids) vuln
9.10.1 L770: unvalidated_user_input
args.extend(keyids)
9.11 def exportkeys(self, keyids, secret=False) vuln
9.11.1 L795-796: unvalidated_user_input
args problem again. exploitable though parameter ``keyids``.
9.12 def listkeys(self, secret=False)
9.12.1 L827:
args is static string.
9.13 def genkey(self, input) cleanup
9.13.1 L864:
args, passed to self.handleio(), which in turn passes args directly to Popen(), is set to a static string. this function is halfway okay, though it really could be more careful with the ``input`` parameter.
9.14 def genkeyinput(self, **kwargs) vuln
9.14.1 L981-983: unvalidated_user_input
this function could potentially allow an attacker with a GPG exploit to use it, because it passes key generation parameter directly into the internal packet parsers of GPG. however, without a GPG exploit for one of the GPG packet parsers (for explanation of GPG packets look into pgpdump), this function alone is not exploitable.
9.15 def encryptfile(self, file, recipiencts, sign=None, …) vuln
9.15.1 L939: unvalidated_user_input
several of the inputs to this function are unvalidated, turned into strings, and passed to Popen(). exploitable.
9.16 def encrypt(self, data, recipients, **kwargs): vuln
9.16.1 L997: unvalidated_user_input
exploitable, passes kwargs to self.encryptfile()
9.17 def decrypt(self, message **kwargs): vuln
9.17.1 L1003: unvalidated_user_input
kwargs are passed to self.decryptfile(), unvalidated, making this function also exploitable
9.18 def decryptfile(self, file, alwaystrust=False, passphrase=None, output=None) vuln
9.18.1 L1013: unvalidated_user_input
unvalidated user input: this function is also exploitable
10 POC
CANNOT INCLUDE FILE ../python-gnupg-0.3.1/python-gnupg-exploit.py