This document contains written techniques to successfully exploit the “Chainsaw” box, commencing from a command injection through Ethereum’s Remote Procedure Call (RPC) interface, using the InterPlanetary File System (IPFS) protocol to retrieve Secure Shell (SSH) private keys, followed by escalating to root shell through making a valid transaction to steal funds from a smart contract, and finally performing file system forensics to get the root flag hidden in slack space.
1. Information Gathering
As usually, we start with nmap to check which ports are open on the server:
There are three (3) services running in the box. The first interesting one is FTP running on port 21 which seems to have anonymous login enabled with the following files: WeaponizedPing.json, WeaponizedPing.sol, and address.txt. Moreover, besides SSH running on port 22 which has the password authentication mechanism disabled (implies key usage), there is one more unknown service running on port 9810 which nmap could not get fingerprint information from.
We will first download the public files using ftp with download confirmation prompt disabled (-i):
We commence by taking a look at WeaponizedPing.sol:
The above source code seems to be written for Solidity version 0.4.24 that represents a simple smart contract built on top of the Ethereum technology. A contract in the sense of Solidity is a collection of code (its functions) and data (its state) that resides at a specific address on the blockchain. There are two functions implemented in the contract, getDomain() which returns the value of the store variable (in this case, initial value is “google.com”) and setDomain() which allows you to override said value.
On the other hand, WeaponizedPing.json holds the configuration file for the smart contract in the JSON format, and address.txt the address value to uniquely identify the contract, generated by computer program, where storage can be fetched or set – in our case, a domain name.
Based on the functionality of the source code and its name, WeaponizedPing, which may or may not be a ping service to test if a domain or IP address is reachable, then we might look into command injection – which is common in these instances.
2. Command Injection
Visiting http://10.10.10.142:9810/ will simply output a HTTP bad request error code of 400. Taking into consideration information gathered from the reconnaissance process, the mention of blockchain technology and Ethereum on top of it may imply that port 9810 can be a potentially RPC interface to Ethereum clients – a service which became popular in 2017 due to the cryptocurrency publicity.
Our main objective is to gain the ability to modify and manipulate the domain value, and for that, we will develop a script in Python 3 to craft our request.
To interact with Ethereum, we will use the Web3 library (which can be installed using pip) and we will use two essential elements, besides contract’s address, from the configuration file provided:
Ethereum bytecode for WeaponizedPing – The executable code of smart contract running on the stack-based Ethereum Virtual Machine (EVM);
Application Binary Interface (ABI) – Which allows us to contextualize the contract and call its functions.
We will be using various functions from Web3 to establish a connection with the RPC interface using Web3.HTTPProvider, load necessary values through eth.defaultAccount and finally the custom function setDomain() to override “google.com” to “hackthebox.eu”:
To confirm our code is working, we will set our IP address as the domain value:
And sure enough, a single ICMP request will appear in our tcpdump:
This means that the software is most likely running a system command to ping once a retrieved value of the domain. Taking this fact into consideration, we can then inject arbitrary commands after the domain name through piping, such as using netcat (assuming it is installed in the box) to spawn a reverse shell:
This will pop a shell as user administrator in which no user flag is found, meaning that we need to continue with local enumeration.
Fully automated and formatted code for the exploit can be found in my public gist. As you may notice, the script also leverages ftplib to fetch the newly generated blockchain address each time the machine is reset; this is a detail quite a number of people had to learn the hard way:
Note: You can also use the NodeJS Web3 or geth command line interface implemented in Go language to interact with the JSON RPC; the same result can be also achieved through curl:
3. Local Enumeration
By a quick overview of administrator’s home folder, we will notice a CSV file, a maintain directory and a couple of hidden directories.
The CSV file holds information about Chainsaw employees with their usernames, role status, and role descriptions. It seems that only the user bobby, who is a smart contract auditor, is the only one active at the moment, and we can confirm this by his home directory presence in /home and a valid Unix shell in /etc/passwd. In conclusion, we need to escalate to bobby to grab the user flag.
The maintain directory seems to contain employees public RSA keys (which were probably generated from the gen.py script – which also generates encrypted private keys, except, they are the ones missing).
Furthermore, among hidden directories in which mostly have little to no value, .ipfs is an interesting one which may give us necessary information to proceed further in this box. InterPlanetary File System (IPFS) is a peer-to-peer network protocol that utilizes distributed systems (including blockchain) with its main objective of hypermedia sharing.
With the aim of replacing HTTP, it is an open source project available on GitHub, therefore, it is already available for installation in different systems, including Linux – where we can confirm its presence by issuing a simple command to retrieve our own peer information:
In IPFS, every link/file is identified with a unique hash. However, as seen from the process list, IPFS daemon is not running which means that it is only running locally.
To list all objects stored in the local IPFS repository, we will use the following command:
These objects could be either files or directories. To skip a lot of unnecessary output that the files might have, we will try to list (ipfs ls) information about these hashes.
We see a bunch of files and directories, some which are already familiar from administrator’s home directory. However, there is an extra directory, mail-log, which seems to have a couple of EML (email message) files. They must be emails previously sent to the employees which are stored in blockchain at the moment. Since our target user is bobby, we will try to print the content of bobbyaxelrod600-protonmail-2018-12-13-T20_28_54+01_00.eml hash:
The resulting content will display information about both email message header and email content which was sent from firstname.lastname@example.org to email@example.com. The body part consists of a body message and an attachment included in the email – both encoded in Base64 by the email program (ProtonMail).
An alternative way would be to recursively grep for keywords such as “protonmail” to find the relevant chunk of data from /home/bobby/.ipfs/blocks/BLOCKID/CHUNKID.data.
Body message (decoded):
Attachment “bobby.key.enc” (decoded):
Now that we managed to grab the private RSA key for user bobby, we need to decrypt it since it is protected with a passphrase (using triple DES as the encryption algorithm with CBC mode). We will use ssh2john to extract the hash, and john to brute force the value (which will eventually give us the password “jackychain”):
After logging in, we get the user flag (af8d9d…).
4. Privilege Escalation
After we SSH in as bobby, we notice the following folders in his home directory:
• projects – contains a folder named ChainsawClub which bobby is working in;
• resources – contains some PDFs for the IPFS protocol (not important).
There is yet another smart contract in the ChainsawClub project, this time it is longer in code and seems to make credit transactions. Besides the smart contract (ChainsawClub.sol), there is its configuration file (ChainsawClub.json) and a binary file with sticky bit set. Running it will generate a new Ethereum address in the current working directory and will ask for credentials (banner will show we need to create a user first), so that is why we start analyzing the code of the contract to get a better understanding.
We notice a bunch of getters and setters by breaking down the code:
setUsername() and setPassword() which allows us to basically create a ‘new’ account;
setApprove() to possibly approve the user. Default is false so we may need to overwrite;
getSupply() to get total supply from the application and getBalance() to get user balance;
transfer() which actually performs a simple, logical transaction;
reset() which resets all variable to default values.
For the exploit development part, we need to use the Web3 library again to interact with the Ethereum interface, however, this time we do not have any information with reference to another RPC interface within the box. If we check the ChainsawClub binary shared object dependencies using ldd, we can notice an unusual preloaded library named chainsaw.so before default system objects are invoked:
Since we are using 64-bit ELF files, the $LIB variable will expand to lib/x86_64-linux-gnu/ in our Debian case. If we disassemble the chainsaw.so binary, we will first notice hardcoded declared variables:
What stands out is that “process_to_filter” has a value of the string “node” which is later referenced in the following procedure:
The logic is fairly simple – the structure makes use of two functions by iterating the get_dir_name() function which returns a relevant directory name given the DIR* handle, and get_process_name() that finds the correct process name given a PID number used in /proc/PID. The process is basically overriding libc’s readdir() function so that everytime the /proc/PID directory is read from a binary (which by default loads this shared library before the normal system’s ones), the access to that specific PID belonging to process_to_filter is skipped/blocked.
Simply put, any process initiated with “node” is not shown in our typical process listing tools such as ps. Consequently, we do not know the port number the RPC uses since ganache-cli uses node. However, if we check ports listening internally within the machine using netstat, we will see a queer port number of 63991. We can only assume that it belongs to Ethereum RPC for now.
Moving on, we will develop another script to try and interact with the higher port number – and indeed, confirm that it is an RPC interface. To summarize the exploit script, which needs quite some attempts to get it right through playing around contract’s functions, we need to fulfill four conditions:
Set a new username and password. Default values equal to blank, which the program returns a “Blank credentials not allowed”
Match the credentials from the smart contract, otherwise you get a “Wrong credentials” message
Approve our user since the program was returning a “User is not approved” message
Transfer enough (all) funds from supply to our user’s balance in order to enter the club (root shell), otherwise you get a “Not enough funds” message
The result is seen in the following picture:
Properly formatted code for privilege escalation can be found in my public gist.
Note: It is also worth stating that there is a unintended solution for escalation in root shell through path hijacking. If we open the SUID file with radare2, we will notice that the program runs /root/ChainsawClub/dist/ChainsawClub/ChainsawClub with sudo privileges:
Since sudo is lacking the full path of /usr/bin/sudo, we can make a custom file/script in the current directory called “sudo” – for which the program will run with root privileges; since the first path lookup is always the current working directory.
After we get a root shell, if we print the content of root.txt we will get the following:
That means that our work is not done yet and that we need further enumeration within the box. One of the last resorts after we run out of options for low hanging fruits, is to check default binaries paths in case there is an interesting program installed or programmed in the machine.
I usually list items based in reverse modified date and time since the default installed binaries are usually the oldest and not much of an interest. In the /sbin directory, we can notice an unusual program called bmap:
A google search about it will bring results related to a generic tool for creating the block map for a file or copying files using the block map, and digital forensics. In simple terms from operating system concepts, blocks are specific sized containers used by file system to store data. Blocks can also be defined as the smallest pieces of data that a file system can use to store information. Files can consist of a single or multiple block in order to fulfill the size requirements of the file.
When data is stored in these blocks, two mutually exclusive conditions can occur:
The block is completely full – most optimal situation for the file system has occurred
The block is partially full – in which the area between the end of file content and the end of the container is referred to as slack space (in other words, null data)
From a forensic perspective, there is a GitHub repository which utilizes slack space in blocks to hide data (one of many interesting functions to the forensic community this tool can perform).
In our Linux file system, we have a root.txt which contains 52 characters (52 bytes) from a total of 4096 bytes (4kb) block size. This means that slack space consists of 4044 bytes in which data can be hidden and not seen from tools such as cat. Because bmap is installed (which is the hint for the slack space technique), we are able to retrieve the root flag by showing slack space content:
Note: Root flag can also be found by digging file system’s offsets around the root.txt file, or even easier: