XUT_ / COURSES / PENETRATION TESTER

Exploitation - Property Injection

Algunas Funciones Importantes

  • Object Recursive Merge: Esta función involucra combinar propiedades de forma recursiva de un objeto "fuente" hacia un objeto "objetivo". Un atacante puede explotar esta funcionalidad si la función de combinar no valida los inputs y permite mezclar propiedades en la cadena del prototipo. Considerando el mismo código de una red social, supón que la aplicación tiene una función para combinar ajustes de usuario.
javascript
// Vulnerable recursive merge function
function recursiveMerge(target, source) {
    for (let key in source) {
        if (source[key] instanceof Object) {
            if (!target[key]) target[key] = {};
            recursiveMerge(target[key], source[key]);
        } else {
            target[key] = source[key];
        }
    }
}

// Endpoint to update user settings
app.post('/updateSettings', (req, res) => {
    const userSettings = req.body; // User-controlled input
    recursiveMerge(globalUserSettings, userSettings);
    res.send('Settings updated!');
});

Un atacante manda una petición con un objeto anidado que contiene __proto__:

javascript
{"__proto__": { "newProperty": "value" } } 
  • Object Clone: La clonación de objetos es una funcionalidad similar que permite que operaciones profundas de clonación copien propiedades de una cadena de prototipos a otra sin avisar. Estas funciones sólo deberían clonar las propiedades definidas por el usuario de un objeto y filtrar las palabras especiales como __proto__, constructor, etc.

Ejemplo Práctico

Ahora veremos de forma práctica cómo los atacantes explotan esta vulnerabilidad. Exploraremos la funcionalidad de clonar álbum. Esta permite a los usuarios clonar un álbum dando un nuevo nombre.

Exploitation - Property Injection

Exploraremos el código del lado del cliente y del servidor.

html
<form action="/clone-album/1" method="post" class="mb-4">
	<h2 class="mb-3">Clone Album of Josh</h2>
	<div class="form-group">
		<label for="selectedAlbum">Select an Album to Clone:</label>
		<select class="form-control" name="selectedAlbum" id="selectedAlbum">
				<option value="Trip to US">
					Trip to US
				</option>
		</select>
	</div>
	<div class="form-group">
		<label for="newAlbumName">New Album Name:</label>
		<input type="text" class="form-control" name="newAlbumName" id="newAlbumName"
			placeholder="Enter new album name">
	</div>
	<button type="submit" class="btn btn-primary">Clone Album</button>
</form>

El código del lado del cliente toma el nombre como input y llama al endpoint de la API /clone-album/{album_ID} para clonarlo. Como hemos visto, PP es raramente explotable por sí sola. Sin embargo, al combinarse con otros vectores de ataque puede resultar en una mayor superficie de ataque. Veamos ahora el código del lado del servidor.

javascript
app.post("/clone-album/:friendId", (req, res) => {
  const { friendId } = req.params;
  const { selectedAlbum, newAlbumName } = req.body;
  const friend = friends.find((f) => f.id === parseInt(friendId));
  if (!friend) {
    console.log("Friend not found");
    return res.status(404).send("Friend not found");
  }
  const albumToClone = friend.albums.find(
    (album) => album.name === selectedAlbum
  );
  if (albumToClone && newAlbumName) {
    let clonedAlbum = { ...albumToClone };
    try {
      const payload = JSON.parse(newAlbumName);
      merge(clonedAlbum, payload);
    } catch (e) {
    }

function merge(to, from) {
  for (let key in from) {
    if (typeof to[key] == "object" && typeof from[key] == "object") {
      merge(to[key], from[key]);
    } else {
      to[key] = from[key];
    }
  }
  return to;
}

En el código de arriba, los servidores reciben un objeto JSON con el nombre del álbum, copian dicho álbum en otro objeto y cambian el nombre del álbum recién creado llamando a la función merge.

Sabemos que la función merge es un candidato ideal para PP si copia los objetos y propiedades sin sanitizar el input. ¿Que pasaría por lo tanto si mandamos una petición que contenga __proto__ con newProperty?

javascript
{"__proto__": {"newProperty": "hacked"}}

La función merge considerará __proto__ como una propiedad y llamará a obj.__proto__.newProperty=value. Haciendo esto, newProperty no se añade directamente al objeto friend. En su lugar, se añade al prototipo del objeto. Esto significa que newProperty no es visiblemente parte de las propiedades de friend (como sí lo son name, age, etc) pero sigue siendo accesible. Clonemos un álbum visitando el perfil de Josh y usando el payload de arriba como nombre del álbum.

Exploitation - Property Injection

¡Ahí está! Tenemos una nueva propiedad para todos los objetos friend.

  • Efecto sobre todos los objetos del mismo tipo: Como todos los objetos frind se crean del mismo prototipo, añadir newProperty al prototipo significa que todos los objetos friend ahora tienen acceso a newProperty.
  • Observar el cambio: Aunque newProperty no es visible directamente al imprimir el objeto friend, está ahí. Puedes acceder a él llamando a friend.newProperty, lo que mostrará testValue.
  • Cómo se vuelve visible newProperty: Cuando añades newProperty vía el prototipo, no existe directamente en los objetos individuales. Sin embargo, cuando accedes a una propiedad del objeto, JavaScript busca primero entre las propiedades del objeto y si no la encuentra busca en las propiedades del prototipo. Si no lo encuentra mira en la cadena de prototipos hasta que lo encuentra o llega al final.
  • Renderizado en pantalla: En la plantilla EJS, cuando ciclas todas las propiedades de un objeto friend usando un bucle for...in (<% for (let key in friend) { %> ...<% } %>) y las muestra, este bucle itera sobre todas las propiedades enumerables de un objeto, incluyendo aquellas heredadas del prototipo.

En esta tarea hemos aprendido cómo añadir nuevas propiedades al prototipo de un objeto para contaminar la estructura general. En la siguiente tarea veremos como crashear una aplicación si el desarrollador no toma las medidas necesarias