This page looks best with JavaScript enabled

TryHackMe - Harder

 ·  ☕ 9 min read  ·  ✍️ sckull

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.

Room

Titulo harder box_img_maker
Descripción Real pentest findings combined
Puntos 160
Dificultad Media
Maker

arcc

NMAP

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.thm
Nmap 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.

image

En las cookies de la solicitud a la pagina encontramos un subdominio nuevo el cual agregamos a nuestro archivo /etc/hosts.

image

HTTP - PWD

En la pagina encontramos un panel de logeo, al ingresar credenciales por default nos muestra un mensaje.

image

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.

1
2
3
4
5
6
kali@kali:~/thm/harder$ gobuster dir -u http://pwd.harder.local/ -w /usr/share/wordlists/dirb/common.txt -t 250 -q -x php,html,txt -k
/.git/HEAD (Status: 200)
/auth.php (Status: 200)
/index.php (Status: 200)
/index.php (Status: 200)
/secret.php (Status: 200)

GITTOOLS

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.

image

Despues de realizar un git checkout -- . para recuperar algunos archivos vemos 3 de ellos .php.

image

En el archivo .gitignore vemos dos nombres de archivos, uno de ellos no lo tenemos.

image

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    <?php
    session_start();
    require("auth.php");
    $login = new Login;
    $login->authorize();
    require("hmac.php");
    require("credentials.php");
  ?> 
    <table style="border: 1px solid;">
       <tr>
         <td style="border: 1px solid;">url</td>
         <td style="border: 1px solid;">username</td>
         <td style="border: 1px solid;">password (cleartext)</td>
       </tr>
       <tr>
         <td style="border: 1px solid;"><?php echo $creds[0]; ?></td>
         <td style="border: 1px solid;"><?php echo $creds[1]; ?></td>
         <td style="border: 1px solid;"><?php echo $creds[2]; ?></td>
       </tr>
     </table>
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
  <?php
define('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 &rarr;");

// ================================================================================================
// ### DO NOT TOUCH ANYTHING BELOW THIS LINE ###
// ================================================================================================

class Login {
  // 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 = "";

function authorize() {
  //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
  }

}

function check(){

  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);
  }
}

function prompt($msg=''){
?>
<html><head><title><?php echo STARTER_GREETING; ?></title>	<style>
[... REDACTED ...]
</style></head><body>
<div class="wrapper"><div class="highlight"><div class="center">
<form class="pure-form pure-form-stacked" action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="post">
  <fieldset>
      <legend><?php if ($msg !== '') { echo $msg; } else { echo STARTER_GREETING; } ?></legend>
      <input type="hidden" name="action" value="set_login">
      <!-- <label for="username"><strong><?php echo USERNAME; ?>:</strong></label> -->
      <input id="username" type="text" name="user" placeholder="<?php echo ENTER_USERNAME; ?>" class="pure-input-1">
      <!-- <label for="password"><strong><?php echo PASSWORD; ?>:</strong></label> -->
      <input id="password" type="password" name="pass" placeholder="<?php echo ENTER_PASSWORD; ?>" class="pure-input-1">
      <label for="remember" class="pure-checkbox">
          <input id="remember" name="remember" type="checkbox"> <?php echo REMEMBER_THIS_COMPUTER; ?>
      </label>
      <button type="submit" class="pure-button pure-button-primary"><?php echo BUTTON_LOGIN; ?></button>
  </fieldset>
</form>
</div></div></div>
</body></html>

<?php exit;}} ?>

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
<?php
if (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&param[]=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.

image

HTTP HEADERS

Visitamos el nuevo subdominio y nos muestra nuevamente el panel de pwd.

image

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.

1
2
3
4
5
6
kali@kali:~/tools/GitTools/Extractor$ gobuster dir -u http://shell.harder.local/ -w $LIST/dirb/common.txt -q -t 250 -x php,html,txt
/auth.php (Status: 200)
/index.php (Status: 200)
/index.php (Status: 200)
/ip.php (Status: 200)
/vendor (Status: 301)

Encontramos WAF BYPASS - OWASP (pag 16) en donde vemos como se puede realizar bypass a ciertas IPS que estan configuradas en el WAF, con los HEADER:

X-Originating-IP
X-Forwarded-For
X-Remote-IP
X-Remote-Addr

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.

image

USER - WWW

En esta pagina encontramos una webshell en la cual podemos ejecutar comandos.

image

Ejecutamos una shell inversa y logramos obtener una shell con el usuario www.

image

Logramos obtener nuestra flag user.txt.

image

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.

image

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.

image

Utilizamos Ghidra y vemos el codigo del archivo el cual ejecuta /usr/local/bin/run-crypted.sh con el parametro que le pasemos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
undefined8 main(int param_1,long param_2)

{
  long in_FS_OFFSET;
  char *local_18;
  long local_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();
  }
  return 0;
}

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
14
#!/bin/sh

if [ $# -eq 0 ]
  then
    echo -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"
  else
    export GNUPGHOME=/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.

image

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.

image

Ejecutamos un nuevo comando para cambiarle los permisos al contenido de la carpeta /root/*, logramos obtener la flag root.txt.

image

SERVER FILES

Vemos los archivos que no logramos ver en el repositorio.

1
2
3
<?php
$creds = array("http://shell.harder.local", "evs", "9FRe8VUuhFhd3GyAtjxWn0e9RfSGv7xm");
?>
1
2
3
4
5
<?php

$secret = "68b329da98[... REDACTED ...]b9c940";

?>
Share on

sckull
WRITTEN BY
sckull
Pentester wannabe

THM: Harder