Stored DOM XSS
Laboratorio de Portswigger sobre XSS
Certificaciones
- eWPT
- eWPTXv2
- OSWE
- BSCP
Descripción
Este laboratorio demuestra una vulnerabilidad de Stored DOM XSS en la funcionalidad de comentarios del blog. Para resolver este laboratorio, debemos explotar esta vulnerabilidad y llamar a la función alert()
Guía de XSS
Antes de completar este laboratorio es recomendable leerse esta guía de XSS https://justice-reaper.github.io/posts/XSS-Guide/
Resolución
Al acceder a la web nos sale esto
Si pinchamos sobre View post vemos una sección en la que podemos hacer comentarios
Si nos abrimos el inspector de Chrome vemos que hay un script
Accediendo a https://0a8d00c003e3219481205c76008f00e5.web-security-academy.net/resources/js/loadCommentsWithVulnerableEscapeHtml.js podemos ver el script, la parte más interesante es la función escapeHTML(html)
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
function loadComments(postCommentPath) {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
let comments = JSON.parse(this.responseText);
displayComments(comments);
}
};
xhr.open("GET", postCommentPath + window.location.search);
xhr.send();
function escapeHTML(html) {
return html.replace('<', '<').replace('>', '>');
}
function displayComments(comments) {
let userComments = document.getElementById("user-comments");
for (let i = 0; i < comments.length; ++i)
{
comment = comments[i];
let commentSection = document.createElement("section");
commentSection.setAttribute("class", "comment");
let firstPElement = document.createElement("p");
let avatarImgElement = document.createElement("img");
avatarImgElement.setAttribute("class", "avatar");
avatarImgElement.setAttribute("src", comment.avatar ? escapeHTML(comment.avatar) : "/resources/images/avatarDefault.svg");
if (comment.author) {
if (comment.website) {
let websiteElement = document.createElement("a");
websiteElement.setAttribute("id", "author");
websiteElement.setAttribute("href", comment.website);
firstPElement.appendChild(websiteElement)
}
let newInnerHtml = firstPElement.innerHTML + escapeHTML(comment.author)
firstPElement.innerHTML = newInnerHtml
}
if (comment.date) {
let dateObj = new Date(comment.date)
let month = '' + (dateObj.getMonth() + 1);
let day = '' + dateObj.getDate();
let year = dateObj.getFullYear();
if (month.length < 2)
month = '0' + month;
if (day.length < 2)
day = '0' + day;
dateStr = [day, month, year].join('-');
let newInnerHtml = firstPElement.innerHTML + " | " + dateStr
firstPElement.innerHTML = newInnerHtml
}
firstPElement.appendChild(avatarImgElement);
commentSection.appendChild(firstPElement);
if (comment.body) {
let commentBodyPElement = document.createElement("p");
commentBodyPElement.innerHTML = escapeHTML(comment.body);
commentSection.appendChild(commentBodyPElement);
}
commentSection.appendChild(document.createElement("p"));
userComments.appendChild(commentSection);
}
}
};
Si enviamos este payload <h1>test</h1> tanto en el campo nombre como en el campo comentario vemos que no aparece la etiqueta de cierre </h1>
Si inspeccionamos ambos elementos vemos que tampoco se muestran
Si enviamos este payload <div><h1>test</h1></div> se interpretará tanto el html en el campo de nombre como en el de comentario
Si nos dirigimos a la página del post que hemos publicado https://0a8d00c003e3219481205c76008f00e5.web-security-academy.net/post?postId=2, si recargamos con F5 y capturamos la segunda petición que se hace a la web con Burpsuite podremos ver los comentarios en formato JSON
Si nos abrimos la consola del navegador podemos ver que solo se ejecuta la función escapeHTML() sobre los primeros <> y no sobre los demás
Para solucionar esto podemos hacerlo de esta forma para encodear toda la cadena y no solo la primera parte o encodear todo y almacenarlo de esa forma en la base de datos para no depender de que JavaScript solucione el problema de seguridad
Podemos usar este payload <><img src=1 onerror=alert(4)> para que lo encodee los primeros <> y luego que nos interprete todo el payload restante










