Browser DevTools
DevTools is underrated in bug bounty circles. Everyone talks about Burp but the browser itself exposes things Burp can't easily see - live DOM state, event listeners, service worker internals, WebSocket frames. Learn to use it well and you'll find XSS, auth bugs, and logic flaws that proxy-only hunters miss.
I work in Firefox and Chrome. Firefox's DevTools are better for JS debugging; Chrome's are better for performance and memory analysis. Keep both installed.
Network Tab
Request replay. Right-click any request > "Copy as fetch" or "Copy as cURL". Faster than repro-ing from scratch. For quick parameter testing without leaving the browser, use "Edit and Resend" (Firefox) - it's a lightweight Repeater.
Header analysis. I scan response headers in the Network tab while browsing, looking for: internal hostnames in Server or custom headers, version disclosure, inconsistent security headers across subdomains, X-Forwarded-For reflection.
WebSocket inspection. Network tab > filter "WS". Click the WebSocket connection to see the Frames tab. Every message, both directions, timestamped. Look for:
- Auth tokens sent in WS messages (often weaker than HTTP auth)
- User IDs or role claims in message bodies
- Server-side logic errors exposed by unexpected message sequences
- Unauthenticated channels (open the WS URL in a fresh incognito window)
For replaying WebSocket messages, I use the browser console: right-click the WS connection and copy the WebSocket URL, then:
const ws = new WebSocket('wss://target.com/ws');
ws.onopen = () => ws.send(JSON.stringify({action: "getUser", id: 2}));
ws.onmessage = (e) => console.log(e.data);Console: DOM XSS Hunting
The console is your runtime analysis environment. I use it constantly for XSS hunting and understanding app logic.
Find all event listeners on a suspicious element:
// Chrome only
getEventListeners(document.querySelector('#search-input'))
// Returns object with event types and handlersTrace where user input goes:
// Hook input fields to watch value flow
const input = document.querySelector('input[name="q"]');
const orig = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
Object.defineProperty(input, 'value', {
set(v) {
console.trace('value set to:', v);
orig.set.call(this, v);
}
});Check for dangerous sinks receiving user-controlled data:
// Hook innerHTML to trace DOM writes
const origSet = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML').set;
Object.defineProperty(Element.prototype, 'innerHTML', {
set(v) {
if (v.includes(location.hash.slice(1)) || v.includes(location.search.slice(1))) {
console.warn('Potential DOM XSS sink:', v);
console.trace();
}
origSet.call(this, v);
}
});Find all eval calls and document.write usage:
// Audit dangerous functions passively
['eval', 'document.write', 'document.writeln'].forEach(fn => {
const parts = fn.split('.');
const obj = parts.length > 1 ? window[parts[0]] : window;
const name = parts[parts.length - 1];
const orig = obj[name];
obj[name] = function() { console.trace(fn, arguments); return orig.apply(this, arguments); };
});Application Tab
This is where auth bugs live.
localStorage and sessionStorage. Check both for tokens, user data, and anything that shouldn't be client-side. Look for JWTs, API keys, user role claims. If you see a role or permission stored here, it's worth testing whether the server actually validates server-side or trusts the client value.
Cookies. Application > Cookies. Check each one:
HttpOnlymissing on session tokens? XSS can steal it.Securemissing? Downgrade risk.SameSitemissing orNone? CSRF surface.- Expiry far in the future on sensitive tokens? Account takeover persistence risk.
- Token format: JWT? Click Inspect > decode it in console with
atob(token.split('.')[1]).
Service Workers. Application > Service Workers. Service workers intercept all network requests. Check registered workers for:
- Caching sensitive responses (auth tokens, API responses)
- Serving stale auth states (logout bypass)
- Fetch event handlers that modify requests
To force a service worker update or unregister for clean testing:
navigator.serviceWorker.getRegistrations().then(regs => regs.forEach(r => r.unregister()));IndexedDB and Cache Storage. Frequently overlooked. Apps store more here than developers realize - full API responses, user data, session artifacts.
Sources Tab: JS Analysis
Pretty-print minified JS with the {} button in the bottom toolbar. Then set breakpoints on interesting functions.
Find all URLs and endpoints in loaded JS:
// Run in console after page loads
performance.getEntriesByType('resource')
.filter(r => r.initiatorType === 'script')
.map(r => r.name)Then open each one, pretty-print, and search for fetch(, XMLHttpRequest, axios, /api/, endpoint, token.
Conditional breakpoints. Right-click a line number in Sources > Add conditional breakpoint. Break only when req.userId !== currentUser.id - saves enormous time over stepping through everything.
Performance Tab: Race Condition Timing
Record a transaction (login, purchase, submit) and examine the timeline. Look for parallel requests that modify shared state. If the app fires two state-mutating requests within the same frame, it's worth testing with Turbo Intruder or a script sending concurrent requests.
// Rough race condition test in console
const requests = Array(20).fill(null).map(() =>
fetch('/api/coupon/apply', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({code: 'SAVE50', orderId: '12345'})
}).then(r => r.json())
);
Promise.all(requests).then(results => console.log(results));Check the Network tab timing after - did multiple requests get 200? Did the discount apply multiple times?
Linked Notes
- Burp Suite - proxy complement, deeper request manipulation
- AI Assisted - feed JS to LLMs after extracting with DevTools
- XSS - DOM XSS hunting techniques