0

I'm trying to create a script using the Node ZAP SDK. The script needs to perform an active scan of the site on localhost while signed in as a super admin; if no user is signed in, it can only reach about 3 of the 50 or so available pages.

I have the entire script working, except for signing in as the user. Now, when I do this in the ZAP desktop client, I just open up Firefox and sign in to the site myself and the session is automatically set up as an HTTP Session and I'm trying to duplicate that using the API. Here is what I currently have:

const ZapClient = require('zaproxy');
const cliArguments = require('minimist')(process.argv.slice(2));
const execSync  = require('child_process').execSync;

const zapOptions  = {
    apiKey: cliArguments.apikey,
    proxy:  'http://127.0.0.1:8080'
};
const zaproxy     = new ZapClient(zapOptions);
const site        = 'https://127.0.0.1';
const sessionName = 'zap_session';
const cookieName  = 'site_session';

function createSession(resp) {
    // generate a valid cookie for a user that exists in the system with admin rights
    const userCookie = execSync(
        `/command/to/generate/valid/cookie ${cliArguments.userid}`,
        {cwd: __dirname}
    ).toString();

    return zaproxy.httpSessions.createEmptySession(site, sessionName)
        .then(resp => zaproxy.httpSessions.setActiveSession(site, sessionName), handleError)
        .then(resp => zaproxy.httpSessions.addSessionToken(site, cookieName), handleError)
        .then(resp => zaproxy.httpSessions.setSessionTokenValue(site, sessionName, cookieName, userCookie), handleError);
}

There's a lot more to the script, but this is the session creation. If I call for the activeSession after this, I get a response of zap_session like I would expect. But when I run it and log all cookies in my application, no cookies are passed to the site and both the Spider and Active Scan fail to scan anything beyond the login page. I've managed to get it working a few times, but it only ever works for one run and then fails every subsequent run (it seems to work when also removing the session before createEmptySession, but that doesn't consistently happen either).

Does anyone have any insight into what the correct process for this is? The API documentation is extremely poor and I've had trouble finding any examples of this online anywhere (even using other languages). It seems strange to me that I need to make 4+ API calls to set a single session token which makes me feel I am completely wrong with my approach, but I just have nothing to judge myself against.

2 Answers 2

1

"I just open up Firefox and sign in to the site myself" - this can actually cover a huge multitude of sins.

There is no one standard way that websites implement authentication, there are lots. While we would love ZAP to be able to understand all of them we just don't have enough people to implement that.

Which means that you will need to understand how your application authenticates you. If it is a simple form then it might be relatively straight forward - have a look at https://www.zaproxy.org/faq/how-can-zap-automatically-authenticate-via-forms/

If it is more complicated then ZAP should still be able to handle it but we'll need more info. I've started covering authentication in the ZAP in Ten videos: https://www.alldaydevops.com/zap-in-ten

If you need more help the ZAP User Group is probably a better forum: https://groups.google.com/group/zaproxy-users

2
  • 1
    Your answer didn't have a definitive "do this" solution, but did point me in the right direction of abandoning http sessions in favor of Form-Based Auth. Digging through the docs and your ZAP in Ten videos + digging through the source code to find the loginPageUrl param allowed me to find a solution that works (see my answer to the question). Thanks for the direction, as well as your work on ZAP itself and with Mozilla. It is all greatly appreciated.
    – Josh
    Mar 26, 2020 at 21:50
  • 1
    A pleasure! Good to hear its working for you now :) Mar 31, 2020 at 10:00
1

Thanks to the pointers from @Simon Bennetts, I was able to come up with a solution that works. Instead of using httpSessions, I switched to form-based authentication. I now run this method in my Node script instead of the previous createSession method.

function loginViaForm() {
    let formBasedConfig  = 'loginUrl=' + encodeURIComponent(site + '/api/login') // the endpoint to POST to for auth
        // This argument maps to the "Login POST Request Data (if any)" field
        + '&loginRequestData=' + encodeURIComponent(`email={%username%}&password={%password%}&remember_me=1&_token=test`)
        // This argument maps to the "URL to GET login page" field in the GUI, which is crucial if you are using CSRF
        + '&loginPageUrl=' + encodeURIComponent(site + '/login');

    // Set the actual username and password to login with
    let userAuthConfig = `username=${encodeURIComponent(cliArguments.email)}&password=${encodeURIComponent(cliArguments.password)}`;

    // Tell ZAP what CSRF tokens to look out for
    return zaproxy.acsrf.addOptionToken('_token')
        // Tell ZAP to use Form-Based Auth
        .then(resp => zaproxy.authentication.setAuthenticationMethod(contextId, 'formBasedAuthentication', formBasedConfig), handleError)
        // Look for "Log Out" on the page to indicate the user is logged in. This won't be anywhere in the page if not signed in
        .then(resp => zaproxy.authentication.setLoggedInIndicator(contextId, 'Log Out'), handleError)
        // Create a new user
        .then(resp => zaproxy.users.newUser(contextId, sessionUser), handleError)
        // Set the user's credentials
        .then(resp => {
            // save the user id globally so we can use it in spider.scanAsUser() and ascan.scanAsUser()
            sessionUserId = resp.userId;
            return zaproxy.users.setAuthenticationCredentials(contextId, sessionUserId, userAuthConfig);
        });
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .