Harder es una maquina de TryHackMe, utilizamos GitTools para obtener el codigo fuente de la pagina lo cual nos dio credenciales para acceder al portal el cual realizamos Bypass al WAF con ello obtuvimos acceso a la maquina para luego cambiar al siguiente usuario en contraseñas almacenadas. Escalamos privilegios utilizando gpg y un binario con permisos SUID.
Escaneo de puertos tcp, nmap nos muestra el puerto http (80) y el puerto ssh (22) abiertos.
1
2
3
4
5
6
7
8
9
10
# Nmap 7.80 scan initiated Tue Aug 18 20:32:35 2020 as: nmap -sV -o mini_scan harder.thmNmap scan report for harder.thm (10.10.254.174)Host is up (0.25s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.3 (protocol 2.0)80/tcp open http nginx 1.18.0
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Aug 18 20:33:24 2020 -- 1 IP address (1 host up) scanned in 48.83 seconds
HTTP
Encontramos una pagina web en el puerto 80.
En las cookies de la solicitud a la pagina encontramos un subdominio nuevo el cual agregamos a nuestro archivo /etc/hosts.
HTTP - PWD
En la pagina encontramos un panel de logeo, al ingresar credenciales por default nos muestra un mensaje.
Al ingresar admin:admin nos muestra:
1
extra security in place. our source code will be reviewed soon ...
GOBUSTER
Utilizamos gobuster para busqueda de directorios y archivos en el nuevo dominio.
Vemos que existe un directorio de .git o más bien un repositorio expuesto. Utilizamos GitTools para obtener lo que se pueda de este repositorio.
Despues de realizar un git checkout -- . para recuperar algunos archivos vemos 3 de ellos .php.
En el archivo .gitignore vemos dos nombres de archivos, uno de ellos no lo tenemos.
El archivo index.php contiene tres parametros que son obtenidos a partir de la variable/array $creds y que seguramente se encuentran en credentials.php además de ello obtiene auth.php, hmac.php y credentials.php. El archivo auth.php vemos el codigo que realiza login, logout, verifica la contraseña y contine el formulario de logeo.
<?phpdefine('LOGIN_USER',"admin");define('LOGIN_PASS',"admin");define('LOGOUT_COMPLETE',"You've been successfully logged out.");define('INCORRECT_USERNAME_PASSWORD',"Invalid login credentials!");define('STARTER_GREETING',"Harder Corp. - Password Manager");define('USERNAME',"Username");define('PASSWORD',"Password");define('ENTER_USERNAME',"Enter Username");define('ENTER_PASSWORD',"Enter Password");define('REMEMBER_THIS_COMPUTER',"Remember this computer");define('BUTTON_LOGIN',"Log in →");// ================================================================================================
// ### DO NOT TOUCH ANYTHING BELOW THIS LINE ###
// ================================================================================================
classLogin{// unique prefix that is used with this object (on cookies and password salt)
var$prefix="login_";// days "remember me" cookies will remain
var$cookie_duration=21;// temporary values for comparing login are auto set here. do not set your own $user or $pass here
var$user="";var$pass="";functionauthorize(){//save cookie info to session
if(isset($_COOKIE[$this->prefix.'user'])){$_SESSION[$this->prefix.'user']=$_COOKIE[$this->prefix.'user'];$_SESSION[$this->prefix.'pass']=$_COOKIE[$this->prefix.'pass'];}//if setting vars
if(isset($_POST['action'])&&$_POST['action']=="set_login"){$this->user=$_POST['user'];$this->pass=md5($this->prefix.$_POST['pass']);//hash password. salt with prefix
$this->check();//dies if incorrect
//if "remember me" set cookie
if(isset($_POST['remember'])){setcookie($this->prefix."user",$this->user,time()+($this->cookie_duration*86400));// (d*24h*60m*60s)
setcookie($this->prefix."pass",$this->pass,time()+($this->cookie_duration*86400));// (d*24h*60m*60s)
}//set session
$_SESSION[$this->prefix.'user']=$this->user;$_SESSION[$this->prefix.'pass']=$this->pass;}//if forced log in
elseif(isset($_GET['action'])&&$_GET['action']=="prompt"){session_unset();session_destroy();//destroy any existing cookie by setting time in past
if(!empty($_COOKIE[$this->prefix.'user']))setcookie($this->prefix."user","blanked",time()-(3600*25));if(!empty($_COOKIE[$this->prefix.'pass']))setcookie($this->prefix."pass","blanked",time()-(3600*25));$this->prompt();}//if clearing the login
elseif(isset($_GET['action'])&&$_GET['action']=="clear_login"){session_unset();session_destroy();//destroy any existing cookie by setting time in past
if(!empty($_COOKIE[$this->prefix.'user']))setcookie($this->prefix."user","blanked",time()-(3600*25));if(!empty($_COOKIE[$this->prefix.'pass']))setcookie($this->prefix."pass","blanked",time()-(3600*25));$msg='<span class="green">'.LOGOUT_COMPLETE.'</span>';$this->prompt($msg);}//prompt for
elseif(!isset($_SESSION[$this->prefix.'pass'])||!isset($_SESSION[$this->prefix.'user'])){$this->prompt();}//check the pw
else{$this->user=$_SESSION[$this->prefix.'user'];$this->pass=$_SESSION[$this->prefix.'pass'];$this->check();//dies if incorrect
}}functioncheck(){if(md5($this->prefix.LOGIN_PASS)!=$this->pass||LOGIN_USER!=$this->user){//destroy any existing cookie by setting time in past
if(!empty($_COOKIE[$this->prefix.'user']))setcookie($this->prefix."user","blanked",time()-(3600*25));if(!empty($_COOKIE[$this->prefix.'pass']))setcookie($this->prefix."pass","blanked",time()-(3600*25));session_unset();session_destroy();$msg='<span class="red">'.INCORRECT_USERNAME_PASSWORD.'</span>';$this->prompt($msg);}}functionprompt($msg=''){?><html><head><title><?phpechoSTARTER_GREETING;?></title><style>[...REDACTED...]</style></head><body><divclass="wrapper"><divclass="highlight"><divclass="center"><formclass="pure-form pure-form-stacked"action="<?phpecho$_SERVER['SCRIPT_NAME'];?>"method="post"><fieldset><legend><?phpif($msg!==''){echo$msg;}else{echoSTARTER_GREETING;}?></legend><inputtype="hidden"name="action"value="set_login"><!-- <label for="username"><strong><?phpechoUSERNAME;?>:</strong></label> --><inputid="username"type="text"name="user"placeholder="<?phpechoENTER_USERNAME;?>"class="pure-input-1"><!-- <label for="password"><strong><?phpechoPASSWORD;?>:</strong></label> --><inputid="password"type="password"name="pass"placeholder="<?phpechoENTER_PASSWORD;?>"class="pure-input-1"><labelfor="remember"class="pure-checkbox"><inputid="remember"name="remember"type="checkbox"><?phpechoREMEMBER_THIS_COMPUTER;?></label><buttontype="submit"class="pure-button pure-button-primary"><?phpechoBUTTON_LOGIN;?></button></fieldset></form></div></div></div></body></html><?phpexit;}}?>
Finalmente tenemos el archivo hmac.php, segun el codigo, este requiere de los parametros h, host y n, tambien utiliza el archivo secret.php para obtener la variable $secret que es utilizada para crear un hash sha256 con el parametro n y en la variable $hm.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?phpif(empty($_GET['h'])||empty($_GET['host'])){header('HTTP/1.0 400 Bad Request');print("missing get parameter");die();}require("secret.php");//set $secret var
if(isset($_GET['n'])){$secret=hash_hmac('sha256',$_GET['n'],$secret);}$hm=hash_hmac('sha256',$_GET['host'],$secret);if($hm!==$_GET['h']){header('HTTP/1.0 403 Forbidden');print("extra security check failed");die();}?>
La solicitudo a la url quedaria de la siguiente forma /index.php?n=param1&h=hashdeHOST&host=HOST si deseamos obtener el contenido de index.php. Para ver el contenido necesitamos la variable $secret pero no tenemos acceso a dicha variable para crear el hash de $secret y $m. Necesitamos de alguna forma saltarnos $secret = hash_hmac('sha256', $_GET['n'], $secret); para que el parametro host (hash) sea el mismo que h (texto).
Segun la documentacion de hash_hmac() se puede saltar esta funcion pasandole un array solo aparecería un error y la aplicacion continuaria ejecutandose. En el caso de $secret el valor de este se volveria NULL. Este se volveria False en $hm = hash_hmac('sha256', $_GET['host'], False /*$secret*/ ); por lo que podriamos crear un hash y pasarlo en h. Para pasar un array por una URL se puede realizar de la siguiente forma?param[]=1¶m[]=2.
Creamos un hash con $hm = hash_hmac('sha256', 'sckull.io', False); utilizando PHP, segun lo anterior, nuestros parametros quedarian de la siguiente forma: n[]=1&h=33e89644bd7110e227fcddfa7a12b8a1c250e508b3f201532014a8ff63b67d5d&host=sckull.io. Al pasarlos al index nos muestra una tabla, donde vemos un subdominio y unas credenciales.
HTTP HEADERS
Visitamos el nuevo subdominio y nos muestra nuevamente el panel de pwd.
Ingresamos con las credenciales y nos muestra un mensaje en el que pide que la direccion IP esta permitida 10.10.10.X.
1
Your IP is not allowed to use this webservice. Only 10.10.10.x is allowed
GOBUSTER
Utilizamos gobuster para busqueda de directorios y archivos en el nuevo dominio, pero no encontramos algo interesante.
Utilizamos esos HEADERS con una direccion IP (10.10.10.10) permitida y logramos encontrar que uno de estos funciona y logramos ver una pagina diferente.
USER - WWW
En esta pagina encontramos una webshell en la cual podemos ejecutar comandos.
Ejecutamos una shell inversa y logramos obtener una shell con el usuario www.
Logramos obtener nuestra flag user.txt.
USER - EVS
Encontramos un archivo el cual contiene la contraseña del usuario evs el cual utilizamos para obtener una shell en el servicio SSH.
PRIVILEGE ESCALATION
Hacemos una pequeña enumeracion de archivos SUID y encontramos el archivo /usr/local/bin/execute-crypted, al ver las strings de este archivo vemos un archivo sh.
Utilizamos Ghidra y vemos el codigo del archivo el cual ejecuta /usr/local/bin/run-crypted.sh con el parametro que le pasemos.
undefined8main(intparam_1,longparam_2){longin_FS_OFFSET;char*local_18;longlocal_10;local_10=*(long*)(in_FS_OFFSET+0x28);setuid(0);if(param_1==2){asprintf(&local_18,"/usr/local/bin/run-crypted.sh %s",*(undefined8*)(param_2+8));system(local_18);free(local_18);}else{system("/usr/local/bin/run-crypted.sh");}if(local_10!=*(long*)(in_FS_OFFSET+0x28)){/* WARNING: Subroutine does not return */__stack_chk_fail();}return0;}
El archivo run-crypted.sh ejecuta gpg junto con algunos parametros y el parametro que le se le pasa a execute-crypted.
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh
if[$# -eq 0]thenecho -n "[*] Current User: "; whoami;echo"[-] This program runs only commands which are encypted for root@harder.local using gpg."echo"[-] Create a file like this: echo -n whoami > command"echo"[-] Encrypt the file and run the command: execute-crypted command.gpg"elseexportGNUPGHOME=/root/.gnupg/
gpg --decrypt --no-verbose "$1"| ash
fi
En este caso necesitamos la llave (.pub) para poder ejecutar comandos. Realizamos una busqueda de estos archivos (.pub) y logramos encontrar uno que podria ayudarmos.
Creamos el archivo que contiene el comando whoami, con este archivo creamos una llave, lo ejecutamos y logramos ver que se ejecuta nuestro comando como root.
Ejecutamos un nuevo comando para cambiarle los permisos al contenido de la carpeta /root/*, logramos obtener la flag root.txt.
SERVER FILES
Vemos los archivos que no logramos ver en el repositorio.