XSS Lab 13
Skills
- Stored DOM 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()
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