1

I started using ZAP and I really like it so far but I miss an option or maybe I don't find it. Burp has an payload mode called "pitchfork" where you can increment two payloads at a time. Got ZAP anything like this? Thanks

2 Answers2

0

I just realized that what I'd given you was actually Battering ram not Pitchfork. https://portswigger.net/burp/documentation/desktop/tools/intruder/attack-types

For Pitchfork you'd simply define two fuzz locations and specify two different lists. Easy peasy.


Here's how you'd accomplish what you need.

Assume the following request for my answer/example:

GET http://localhost:8090/bodgeit/product.jsp?typeid=3&foo=3 HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Host: localhost:8090

Let's say that typeid and foo are the param values that you want to pitchfork. Your going to create a Payload Generator script in ZAP, such as the following (this is a simple minor tweak to the default template, the important differences are outlined below after the code sample):

// Auxiliary variables/constants for payload generation.
var NUMBER_OF_PAYLOADS = 10;
var INITIAL_VALUE = 1;
var count = INITIAL_VALUE;
var MID= '&foo='

/**
 * Returns the number of generated payloads, zero to indicate unknown number.
 * The number is used as a hint for progress calculations.
 * 
 * @return {number} The number of generated payloads.
 */
function getNumberOfPayloads() {
    return NUMBER_OF_PAYLOADS;
}

/**
 * Returns true if there are still payloads to generate, false otherwise.
 * 
 * Called before each call to next().
 * 
 * @return {boolean} If there are still payloads to generate.
 */
function hasNext() {
    return (count <= NUMBER_OF_PAYLOADS);
}

/**
 * Returns the next generated payload.
 * 
 * This method is called while hasNext() returns true.
 * 
 * @return {string} The next generated payload.
 */
function next() {
    payload = count;
    count++;
    return payload+MID+payload;
}

/**
 * Resets the internal state of the payload generator, as if no calls to
 * hasNext() or next() have been previously made.
 * 
 * Normally called once the method hasNext() returns false and while payloads
 * are still needed.
 */
function reset() {
    count = INITIAL_VALUE;
}

/**
 * Releases any resources used for generation of payloads (for example, a file).
 * 
 * Called once the payload generator is no longer needed.
 */
function close() {
}

Note: Declaration of the MID constant, which is the middle part of the string between the two param values. Modification of the next() method which returns the same value for both param values with the "MID" string inserted between.

In the request highlight 3&foo=3 right click and select "Fuzz...". Click the "Payloads" button, click the "Add" button, set the "Type" dropdown as "Script", select your "Script" by name in the dropdown (I called mine "Pitchfork"). ("Generate Preview" if you like.) Click the "Add" button. Click the "Ok" button. Click "Start Fuzzer". You've now run a "Pitchfork" fuzz in ZAP.

Results in the following payloads:

1&foo=1
2&foo=2
3&foo=3
4&foo=4
5&foo=5
6&foo=6
7&foo=7
8&foo=8
9&foo=9
10&foo=10

Things to keep in mind:

  1. Assuming you're fuzzing a normal GET or POST you should be able to order the params however you like. (Targets "shouldn't" care which order params are in, you can copy/paste them into whatever order you need and send the request manually.) If it's some sort of well formed content (JSON/XML, or whatever) then you can just turn MID into a huge string...
  2. You can install/use a scripting add-on such as Python (Jython) if you want to access payloads from a file.

If you wanted to process a header based on the same payload as the initial injection then you'd do a slight variation.

Create a "Fuzzer HTTP Processor" script, which is just a slight variation on the template. The following example simply checks the value of the payload in foo and uses it in a header:

/**
 * Processes the fuzzed message (payloads already injected).
 * 
 * Called before forwarding the message to the server.
 * 
 * @param {HttpFuzzerTaskProcessorUtils} utils - A utility object that contains functions that ease common tasks.
 * @param {HttpMessage} message - The fuzzed message, that will be forward to the server.
 */
function processMessage(utils, message) {
    // To obtain the list of payloads:
    //    utils.getPayloads()
    // To obtain original message:
    //    utils.getOriginalMessage()
    // To stop fuzzer:
    //    utils.stopFuzzer()
    // To increases the error count with a reason:
    //    utils.increaseErrorCount("Reason Error Message...")
    // To send a message, following redirects:
    //    utils.sendMessage(myMessage)
    // To send a message, not following redirects:
    //    utils.sendMessage(myMessage, false)
    // To add a message previously sent to results:
    //    utils.addMessageToResults("Type Of Message", myMessage)
    // To add a message previously sent to results, with custom state:
    //    utils.addMessageToResults("Type Of Message", myMessage, "Key Custom State", "Value Custom State")
    // The states' value is shown in the column 'State' of fuzzer results tab
    // To get the values of the parameters configured in the Add Message Processor Dialog.
    //    utils.getParameters() 
    // A map is returned, having as keys the parameters names (as returned by the getRequiredParamsNames()
    // and getOptionalParamsNames() functions below)
    // To get the value of a specific configured script parameter
    //    utils.getParameters().get("exampleParam1")

    // Process fuzzed message...
     var payload = null;
    
    for (var iterator = message.getUrlParams().iterator(); iterator.hasNext();) {
        var urlParam = iterator.next();
        
        if (urlParam.getName() == 'foo') {
            payload = urlParam.getValue();
            break;
        }
    }
    message.getRequestHeader().setHeader("X-Some-Id", payload);
}

/**
 * Processes the fuzz result.
 * 
 * Called after receiving the fuzzed message from the server.
 * 
 * @param {HttpFuzzerTaskProcessorUtils} utils - A utility object that contains functions that ease common tasks.
 * @param {HttpFuzzResult} fuzzResult - The result of sending the fuzzed message.
 * @return {boolean} Whether the result should be accepted, or discarded and not shown.
 */
function processResult(utils, fuzzResult){
    // All the above 'utils' functions are available plus:
    // To raise an alert:
    //    utils.raiseAlert(risk, confidence, name, description)
    // To obtain the fuzzed message, received from the server:
    //    fuzzResult.getHttpMessage()
    // To get the values of the parameters configured in the Add Message Processor Dialog.
    //    utils.getParameters() 
    // A map is returned, having as keys the parameters names (as returned by the getRequiredParamsNames()
    // and getOptionalParamsNames() functions below)
    // To get the value of a specific configured script parameter
    //    utils.getParameters().get("exampleParam1")
    return true;
}

/**
 * This function is called during the script loading to obtain a list of the names of the required configuration parameters,
 * that will be shown in the Add Message Processor Dialog for configuration. They can be used
 * to input dynamic data into the script, from the user interface
*/
function getRequiredParamsNames(){
    return [];
}

/**
 * This function is called during the script loading to obtain a list of the names of the optional configuration parameters,
 * that will be shown in the Add Message Processor Dialog for configuration. They can be used
 * to input dynamic data into the script, from the user interface
*/
function getOptionalParamsNames(){
    return [];
}

You'd select just the param value you want to fuzz. In the above example if you wanted to fuzz foo you'd just select the 3. Setup the fuzzer much as above (you could use a built-in generator instead of a script), but add your "Message Processor" in the "Message Processors" tab, run the fuzzer.

Based on this example foo should get the values 1 thru 10 and each request will have a header such as X-Some-Id: 1 added (where the Id is 1 to 10 kept in pace with the payload).

Of course you could also do a substring, encoding, etc. it doesn't have to be exactly the same.

kingthorin
  • 1,419
  • 9
  • 18
  • Thank you very much for your reply. I'm not sure if this is what I meant, but maybe I don't understand it right. For example: If I want to fuzz two parameters in the POST request, lets say the value of the X-Forwarded-For header and the username. I want that in each turn it changes the username and in the same time increments the X-Forwarded-For header value. If I understand it right then is your script only for one parameter in the POST request, right? Sorry but I'm an absolute beginner. – RolfNibotski Mar 14 '21 at 18:17
  • The example above change two parameter values in a GET request, you could easily do exactly the same thing in a POST. If it need to sync the payloads for a header and and body, it'd be a bit more difficult. You'd have to set a globalvar from the payload generator and then process the header in a message processor retrieving the global var and setting the header. I'll add further details on that. Either way it's still doable. – kingthorin Mar 14 '21 at 23:53
  • Answer updated with message processor details. – kingthorin Mar 16 '21 at 01:10
  • Thank you very much for your effort. To be honest it looks a bit difficult to me to reproduce your instructions. I'll have a try. Maybe I need to ask again... – RolfNibotski Mar 17 '21 at 12:17
  • No problem, let me know if you get stuck. – kingthorin Mar 17 '21 at 13:23
0

For your specific case (fuzzing the username plus X-Forwarded-For header) you can simply choose to fuzz a single location for username enumeration and let the header be set by following community script: https://github.com/zaproxy/community-scripts/blob/main/httpfuzzerprocessor/random_x_forwarded_for_ip.js

Note that you have to install the community script via market place before.

Timo
  • 1
  • 1