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):
+
-
+
- 5.1 def _init_(self, gpg)
+
-
+
- 5.1.1 L338 +
+
+ - 5.1 def _init_(self, gpg)
+
- 6 class GenKey(object) +
- 7 class DeleteResult(object) +
- 8 class Sign(object) +
- 9 class GPG(object)
+
-
+
-
+
-
+
- 9.1 L474: +
+ - 9.1 def _init_(self, gpgbinary='gpg', gnupghome=None, verbose=False, useagent=False, keyring=None) + + +
- 9.2 def opensubprocess(self, args, passphrase=False)
+
-
+
- 9.2.1 L515: +
+ - 9.3 def collectoutput(self, process, result, writer=None, stdin=None) +
- 9.4 def handleio(self, args, file, result, passphrase=None, binary=False)
+
-
+
- 9.4.1 L601: +
+ - 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.9.1 L749: +
+ - 9.10 def recievekeys(self, keyserver, *keyids)
+
-
+
- 9.10.1 L770: +
+ - 9.11 def exportkeys(self, keyids, secret=False) + + +
- 9.12 def listkeys(self, secret=False)
+
-
+
- 9.12.1 L827: +
+ - 9.13 def genkey(self, input)
+
-
+
- 9.13.1 L864: +
+ - 9.14 def genkeyinput(self, **kwargs) + + +
- 9.15 def encryptfile(self, file, recipiencts, sign=None, …)
+
-
+
- 9.15.1 L939: +
+ - 9.16 def encrypt(self, data, recipients, **kwargs):
+
-
+
- 9.16.1 L997: +
+ - 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 +