Client-Side Fuzzing: A JavaScript Approach for Web Vulnerability Testing

Client-side fuzzing can be a fascinating approach to discovering web vulnerabilities by leveraging the browser’s capabilities to test for issues such as SQL Injection (SQLi) and Cross-Site Scripting (XSS). This post explores a JavaScript code snippet designed to perform fuzzing directly from the browser, discusses the key functions involved, and evaluates the pros and cons of client-side fuzzing from the perspective of a pentester or red teamer.

In the world of web security, fuzzing is a critical technique used to discover vulnerabilities by injecting a variety of inputs into the system to see how it reacts. While traditionally performed on the server-side, this post explores an interesting yet potentially error-prone method of conducting fuzzing directly from the client-side using JavaScript.

What Does This Code Do?

The provided JavaScript code is designed to perform client-side fuzzing by modifying URL parameters and sending HTTP requests with these modifications. This approach can be interesting as it leverages the browser’s capabilities to test for vulnerabilities such as SQL Injection and XSS. However, it is essential to note that this method can be prone to failures and may not be as reliable as server-side fuzzing.

Fuzzing: An Overview

Fuzzing is a technique used to discover vulnerabilities by injecting a wide range of inputs (payloads) into an application and observing its behavior. It’s particularly effective in uncovering security flaws like SQL Injection and Cross-Site Scripting (XSS).

Why Use Fuzzing?

  • Automated Testing: Fuzzing automates the process of inputting and testing different payloads, making it faster and more efficient than manual testing.
  • Discover Hidden Bugs: It can uncover edge cases and hidden bugs that might not be discovered through traditional testing methods.
  • Wide Coverage: By testing a wide range of inputs, fuzzing can provide extensive coverage of the application, increasing the likelihood of finding vulnerabilities.

Client-Side Fuzzing: Pros and Cons

  • Pros
    -Leverages Browser Capabilities: Client-side fuzzing can take advantage of the browser’s capabilities to interact with web applications in real-time.
    • Real-World Scenarios: It can simulate real-world scenarios where a user might input various payloads into the web application.
  • Cons
    • Prone to Failures: Client-side fuzzing can be unreliable due to the limitations of the browser environment and network variability.
    • Limited by Browser Security: Modern browsers have security mechanisms that might prevent certain types of fuzzing attacks from being executed effectively.
    • Resource Intensive: Running extensive fuzzing in the browser can consume significant system resources and affect performance.

AUJSDITOR - SIMPLE EXEMPLE OF JS FUZZING

Key Functions

  1. Random User Agent Generator:

This function generates a random User-Agent string to simulate requests from different browsers. This can help in bypassing certain filters or protections based on User-Agent.


function getRandomUserAgent() {
    const browsers = Object.keys(userAgents);
    const randomBrowser = browsers[Math.floor(Math.random() * browsers.length)];
    const agents = userAgents[randomBrowser];
    const randomAgent = agents[Math.floor(Math.random() * agents.length)];
    return randomAgent;
}

  1. Original Response Fetcher:

This function fetches the original response from the given URL using the specified cookies and HTTP method. It helps to understand how the server normally responds before any payloads are injected.


function getOriginalResponse(url, cookies, method) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method, url);
        xhr.setRequestHeader('Cookie', cookies);
        xhr.responseType = 'text';
    
        xhr.onload = function () {
            if (xhr.status >= 200 && xhr.status < 300) {
                resolve(xhr.responseText);
            } else {
                reject(new Error(`Request failed with status ${xhr.status}`));
            }
        };
    
        xhr.onerror = function () {
            reject(new Error('Request failed'));
        };
    
        xhr.send();
    });
}

  1. Payload Checker:

This function checks if the injected payload results in any errors or noticeable delays, which can indicate the presence of vulnerabilities. It supports different types of payloads such as Error-Based and Time-Based SQL Injections.

function checkPayload(url, cookies, dbType, payloadType) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        const timeout = 5000;
        let timedOut = false;
    
        const handleTimeout = () => {
            timedOut = true;
            reject({ timeout: true });
            xhr.abort();
        };
    
        const handleResponse = () => {
            if (timedOut) return;
    
            if (xhr.status >= 200 && xhr.status < 300) {
                if (payloadType === 'ErrorBased') {
                    const errorKeywords = ['error', 'sql syntax', 'database error', 'syntax error', 'query error'];
                    const responseText = xhr.responseText.toLowerCase();
    
                    const hasError = errorKeywords.some(keyword => responseText.includes(keyword));
                    if (hasError) {
                        console.log('[JSQLMAP] POSSIBLE ' + dbType.toUpperCase() + ' ERROR BASED SQL INJECTION IN: ' + url);
                        resolve();
                    }
                } else if (payloadType === 'TimeBased') {
                    const responseTimeHeader = xhr.getResponseHeader('X-Response-Time');
                    if (responseTimeHeader) {
                        const responseTime = parseFloat(responseTimeHeader);
                        if (responseTime >= 5000) {
                            console.log('[JSQLMAP] POSSIBLE ' + dbType.toUpperCase() + ' BLIND SQL INJECTION IN: ' + url);
                            resolve();
                        }
                    } else {
                        const startTime = Date.now();
                        const endTime = Date.now();
                        const elapsedTime = endTime - startTime;
                        if (elapsedTime >= 5000) {
                            console.log('[JSQLMAP] POSSIBLE ' + dbType.toUpperCase() + ' BLIND SQL INJECTION IN: ' + url);
                            resolve();
                        }
                    }
                } else {
                    console.log('[JSQLMAP] ERROR - Unsupported payload type:', payloadType);
                    resolve();
                }
            } else {
                reject(new Error('Request failed with status ' + xhr.status));
            }
        };
    
        xhr.onreadystatechange = () => {
            if (xhr.readyState === XMLHttpRequest.DONE) {
                handleResponse();
            }
        };
    
        xhr.ontimeout = handleTimeout;
    
        xhr.open('GET', url);
        xhr.setRequestHeader('Cookies', cookies);
        xhr.setRequestHeader('User-Agent', getRandomUserAgent());
        xhr.timeout = timeout;
    
        xhr.send();
    });
}

This function replaces the URL parameters with payloads designed to test for various types of vulnerabilities. It iterates over all possible payloads for each parameter and checks if any payload triggers a vulnerability.

  1. Replace URL Parameters with Payloads:
function replaceGETParamsWithPayload(url) {
    const urlObj = new URL(url);
    const searchParams = urlObj.searchParams;
    
    for (const [param, value] of searchParams.entries()) {
        const originalValue = searchParams.get(param);
    
        for (const dbType in DB_TYPEPayloadsArray) {
            const payloads = DB_TYPEPayloadsArray[dbType];

            for (const payloadType in payloads) {
                console.log()
                const payloadArray = payloads[payloadType];
                for (const payload of payloadArray) {
                    searchParams.set(param, payload);
                    console.log(`[+] Launching ${dbType} ${payloadType} attack.`);

                    if (dbType === "XSS"){
                        console.log("XSSTEST")
                    }else{
                        checkPayload(urlObj.toString(), galletas, dbType, payloadType);
                    }
                }
            }
        }
        searchParams.set(param, originalValue);
    }
    
    return 1;
}

Usage Example


let url = 'URL';
let galletas = "COOKIES";
let metod = "GET"
let originalResponse = "";

getOriginalResponse(url, galletas, metod)
  .then(response => {
    console.log('Response:', response);
    originalResponse = response;
  })
  .catch(error => {
    console.error('Error:', error);
  });

const modifiedUrl = replaceGETParamsWithPayload(url);
console.log(modifiedUrl);

Advanced HTTP Request Simulation

To make the testing even more comprehensive, you can include a function like the following to simulate complex HTTP requests, similar to what SQLMap does:


function sendHttpRequest(host) {
    const reqTxt = `POST /uploader/newAjaxUpload.php HTTP/2
    Host: fnpix.com
    Cookie: PHPSESSID=qered8dolnqvbbseju1evugiil
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
    Accept: */*
    Accept-Language: es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3
    Accept-Encoding: gzip, deflate
    X-Requested-With: XMLHttpRequest
    Content-Type: multipart/form-data; 
    Content-Length: 971
    Origin: https://fnpix.com
    Dnt: 1
    Referer: https://fnpix.com/uploader/
    Sec-Fetch-Dest: empty
    Sec-Fetch-Mode: cors
    Sec-Fetch-Site: same-origin
    Te: trailers

    paramm1=value1¶m2=value2¶m3=value3`;

    const lines = reqTxt.split('\n');
    const method = lines[0].split(' ')[0].trim();
    const path = lines[0].split(' ')[1].trim();

    const fullPath = `${host}${path}`;

    const headers = {};
    for (let i = 1; i < lines.length; i++) {
        const line = lines[i];
        const colonIndex = line.indexOf(':');
        if (colonIndex !== -1) {
            const key = line.substring(0, colonIndex).trim();
            const value = line.substring(colonIndex + 1).trim();
            if (key.toLowerCase() !== 'host') {
                headers[key] = value;
            }
        }
    }

    fetch(fullPath, {
        method: method,
        headers: headers,
        credentials: 'include',
        body: lines[lines.length - 1].trim()
    })
    .then(response => {
        if (response.ok) {
            return response.text();
        } else {
            throw new Error('Request failed with status: ' + response.status);
        }
    })
    .then(data => {
        console.log('Response:', data);
    })
    .catch(error => {
        console.error('Request error:', error.message);
    });
}

// Example usage:
const host = 'https://loveisinthe.net';
sendHttpRequest(host);


Performing client-side fuzzing using JavaScript can be an intriguing approach to discover vulnerabilities in web applications. By leveraging the browser’s capabilities, this method can simulate various attack vectors directly from the client’s side. However, due to the inherent limitations and potential unreliability of client-side testing, it is recommended to use it in conjunction with server-side fuzzing tools for more comprehensive security assessments.