Link

Authenticated Scanning

Most web applications will have specific pages that are only accessible to authenticated users. To effectively scan for vulnerabilities it is important to test all paths, including the authenticated routes.

HawkScan can support authentication via usernamePassword or an external supplied authorization token. In addition, you can configure how HawkScan maintains authentication throughout the scan via a cookieAuthorization or tokenAuthorization configuration.

A testPath configuration may also be provided to verify HawkScan authenticated its session correctly before scanning the application. The testPath configuration also provides requestMethod and requestBody options to support alternate HTTP request verbs, such as POST or PUT. The default action is GET.

The authentication and authorization configurations are defined separately to support a variety of web application needs. Below are some examples of common authentication and authorization combinations; example stackhawk.yml files demonstrating different combinations of authentication and authorization are available at HawkScan Examples.

A common way to authenticate to a web application is by POSTing a username and password which can be verified by your server. Upon verification the server returns a new cookie to the requesting client.

This is the standard behavior for cookie based authorization schemes. The cookie is used to track your session on the server with the expectation that subsequent requests send the cookie back via the Set-Cookie response header. This allows the server to track requests and maintain the session.

The POST request for sending the username and password expects a payload type of application/x-www-form-urlencoded and the cookie verifying the users session will be named sessionid. When a request with an invalid, or non-present, cookie is sent the server will respond by redirecting the user to the /login page.

Create or use an existing a stackhawk.yml to add support for scanning as an authenticated user.

Example Scenario #1

Let’s say your web application has a path structure like the following, with each request returning standard html content type text/html.

GET|POST  /login
GET       /home
GET       /comments
GET|POST  /mysettings <- protected route
POST      /comments <- protected route
GET|POST  /privatemessage <- protected routes

You would want a stackhawk.yml configuration like this.

stackhawk.yml

app:
    applicationId: xxXXXXXX-xXXX-xxXX-XXxX-xXXxxXXXXxXX
    env: Test
    host: http://localhost:9000
    antiCsrfParam: csrfToken
    authentication:
        loggedInIndicator: "\\QLog out\\E"
        loggedOutIndicator: "\\QLog in\\E"
        usernamePassword:
            type: FORM
            loginPagePath: /login
            loginPath: /login
            usernameField: email
            passwordField: password
            scanUsername: ${SCAN_USERNAME}
            scanPassword: ${SCAN_PASSWORD}      
            otherParams:
                - name: rememberMe
                  val: "true"
        cookieAuthorization:
            cookieNames:
                - sessionid
        testPath:
            path: /mysettings
            fail: ".*302.*Location:.*/login.*"

The authentication request/response would then look like the following. The response payloads have been truncated for brevity denoted by ...

Auth Request Step #1

GET /login HTTP/1.1
Host: localhost:9000
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9

Auth Response Step #1

HTTP/1.1 200 OK
Date: Thu, 24 Oct 2019 21:31:50 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 6058

...
<form action="/login" method="POST">
    <input type="hidden" name="csrfToken"
           value="c9daeffb9679563ec924ac110b44677b5e3710af-1571951867944-5df593a128612bdbb7c76af9"/>
    <div class="form-group  " id="email_field">

        <label class="control-label sr-only" for="email">Email</label>
        <input type="email" id="email" name="email" value="" class="form-control form-control input-lg"
               placeholder="Email">
    </div>
    <div class="form-group  " id="password_field">
        <label class="control-label sr-only" for="password">Password</label>
        <input type="password" id="password" name="password" value="" required="true"
               class="form-control form-control input-lg" placeholder="Password">
    </div>
...

The step #1 request is a GET request to the app.authentication.loginPath to return the login form containing the hidden input field csrfToken. The csrfToken is often required for form based authentication as a means of ensuring the form POST originated from the intended web form.

Auth Request Step #2

POST /login HTTP/1.1
Host: localhost:9000
Connection: keep-alive
Content-Length: 149
Origin: http://localhost:9000
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9

csrfToken=c9daeffb9679563ec924ac110b44677b5e3710af-1571951867944-5df593a128612bdbb7c76af9&email=user%40example.com&password=s00p3rS33kretP@55w0rd&rememberMe=true

Auth Response Step #2

HTTP/1.1 303 See Other
Location: /
Set-Cookie: sessionid=1-36b182ae988ccc397683f134a3a508e0a246ee2c-1-ZUdGVa7DthmVGlbnOs+ckrnmOUdoJb5vMHxIlwPeriJflTOPqxjc9SnQ0X5AhY+VY9Mtow4YPDwp5RXCiau96MKKCCxRZuiovGU+kDKtrz+9NdvSmCNZiGRtOLaWXliOvBefeiet2EkaNpH3EeKVa2rLCM5aYctB75tdDo8X6xHyh4bc1CxJy9IXDABZ1eGXhmxAfBFjowRHdPcbCi68MMKHgQZOG1xvHfIR9bCaQw66sdqrjP0jb6mXhrdYz7F1wxfSUVMUQwIa8bveXxvMmh8kqc76XBRZ+wZwSb7CTO4iIk6DXItpSzqiumRv6gdxyGILUe5hoOS+CoSsDZPI5Q/qwaKce7XhXQy9YWYZ1wwf7cbfn9W5W83jTwqZKCf34W5OxDwgFO/8qvsBBCUT+sEpW+5j9hz0Y25raTNxCi450cYjEu+w0gCVnqXeA2vfRFqsKILaW58mxJuuux51NpXuVPnPgbESkKMgfonuce2TK6YVurHF5z+Rduwe/VfaEx48sT+TuPOg6gIV46j7fKrdJXH4+I3jHX9upMi/k9FS31I8IYFjdx6VkXFsH8hwz2AN2l0xwMqtFxwUIRNtqOpTwODb1F1NMYw/2Ku4VTR0rVTRj/mAeWt4OsRJHROs7JKKFYPVYD4swerTuMU3l+QbAQUJrJDZRKW5qjbrXVQ+DNCcsPlkGTPXhU1vA5eXJVdES0AeN+Zp1lE4Vzl4reXxgEE26wxWQ+CyStnkBt8=; Max-Age=2592000; Expires=Sat, 23 Nov 2019 21:30:19 GMT; SameSite=Lax; Path=/; HTTPOnly
Referrer-Policy: origin-when-cross-origin, strict-origin-when-cross-origin
Date: Thu, 24 Oct 2019 21:30:19 GMT
Content-Length: 0

The step two request is a POST as a form-urlencoded payload with the fields specified in your stackhawk.yml as a well as the csrfToken value gathered from step one. The returning payload also contains the Set-Cookie header with a cookie value that will be sent on subsequent authenticated requests. The cookie name and csrf token name are specified in the stackhawk.yml as app.cookAuthorization.cookieNames and app.antiCsrfParam respectively.

Verifying authentication

The testPath configuration will tell HawkScan how to verify if authentication has been configured correctly. A GET request will be sent to testPath whose response will be verified against the testPath.fail or testPath.success criteria. In this scenario you can define fail criteria as the regex .*302.*Location.*/login.* which will consider a Location redirect to the /login page an indication of failed authentication. If the regex does not match the response header from testPath.path authentication will be considered a success. See more detail on testPath in the Configuration docs.

Authorized Request to /mysettings

GET /mysettings HTTP/1.1
Host: localhost:9000
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: sessionid=1-36b182ae988ccc397683f134a3a508e0a246ee2c-1-ZUdGVa7DthmVGlbnOs+ckrnmOUdoJb5vMHxIlwPeriJflTOPqxjc9SnQ0X5AhY+VY9Mtow4YPDwp5RXCiau96MKKCCxRZuiovGU+kDKtrz+9NdvSmCNZiGRtOLaWXliOvBefeiet2EkaNpH3EeKVa2rLCM5aYctB75tdDo8X6xHyh4bc1CxJy9IXDABZ1eGXhmxAfBFjowRHdPcbCi68MMKHgQZOG1xvHfIR9bCaQw66sdqrjP0jb6mXhrdYz7F1wxfSUVMUQwIa8bveXxvMmh8kqc76XBRZ+wZwSb7CTO4iIk6DXItpSzqiumRv6gdxyGILUe5hoOS+CoSsDZPI5Q/qwaKce7XhXQy9YWYZ1wwf7cbfn9W5W83jTwqZKCf34W5OxDwgFO/8qvsBBCUT+sEpW+5j9hz0Y25raTNxCi450cYjEu+w0gCVnqXeA2vfRFqsKILaW58mxJuuux51NpXuVPnPgbESkKMgfonuce2TK6YVurHF5z+Rduwe/VfaEx48sT+TuPOg6gIV46j7fKrdJXH4+I3jHX9upMi/k9FS31I8IYFjdx6VkXFsH8hwz2AN2l0xwMqtFxwUIRNtqOpTwODb1F1NMYw/2Ku4VTR0rVTRj/mAeWt4OsRJHROs7JKKFYPVYD4swerTuMU3l+QbAQUJrJDZRKW5qjbrXVQ+DNCcsPlkGTPXhU1vA5eXJVdES0AeN+Zp1lE4Vzl4reXxgEE26wxWQ+CyStnkBt8=

Requests to protected routes like /mysettings, /comments and /privatemessages will then respect the value supplied by the sessionid cookie allowing for access to protected routes during a scan.

Changes to the sessionid cookie via the Set-Cookie header will be updated automatically and used as the new cookie value across multiple authenticate scan requests.

If the response payload contains text matching the regex supplied in the app.authentication.loggedOutIndicator, the scanner will presume its session cookie has become invalid and will perform the authentication steps described above to ensure it can continue making authenticated requests with a new authenticated session.

Username/Password Authentication + Bearer Token Authorization

Many modern web application backends are API’s that serve data to more than just html based web browsers and, therefore, do not rely on web forms for authentication. This approach is common for single page applications that use modern javascript frameworks like Angular, React, Vue.js, etc. as well.

A common approach for authentication in this scenario is to create an API route that accepts a users credentials via a POST request of JSON data with the request returning an Authorization token as part of the JSON response payload. The API then expects the token be sent on all subsequent requests to protected API routes as an Authorization header. This method of authorization is commonly referred to as bearer token authorization.

HawkScan supports this scenario by adding usernamePassword, tokenExtraction and tokenAuthorization configurations to app.authentication.

Configuring usernamePassword for this scenario is similar to the first scenario , but instead the usernamePassword.type=JSON which will cause the credential and otherParams to be POSTed as application/json instead of application/x-www-form-urlencoded. Successful authentication will return a JSON payload containing the bearer token that will need to be passed with every request to an authenticated route. To support scenario the tokenExtraction configuration will be used to describe how to acquire the token. The tokenAuthorization configuration will be used to describe how to pass the extracted token on all scan requests.

Example Scenario #2

Let’s say your web application API has a path structure like the following, with each route expecting application/json as request and response content types.

POST      /login
GET       /home
GET       /comments
GET|POST  /mysettings <- protected route
POST      /comments <- protected route
GET|POST  /privatemessage <- protected routes

You would want a stackhawk.yml configuration like this.

stackhawk.yml

app:
    applicationId: xxXXXXXX-xXXX-xxXX-XXxX-xXXxxXXXXxXX
    env: Test
    host: http://localhost:3000
    authentication:
      loggedInIndicator: ".*2[0-9][0-9].*OK.*|.*3[0-9][0-9].*"
      loggedOutIndicator: ".*4[0-9][0-9].*Unauthorized.*"
      usernamePassword:
        type: JSON
        loginPath: /login
        usernameField: email
        passwordField: password
        scanUsername: ${SCAN_USERNAME}
        scanPassword: ${SCAN_PASSWORD}
        otherParams:
          - name: rememberMe
            val: "true"
      tokenExtraction:
        type: TOKEN_PATH
        value: "authentication.token"
      tokenAuthorization:
        type: HEADER
        value: Authorization
        tokenType: Bearer
      testPath:
        path: /mysettings
        success: ".*200.*"

The authentication request/response would then look something like.

Auth Request

POST /login HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Content-Length: 83
Accept: application/json, text/plain, */*

{"email":"user@example.com","password":"s00p3rS33kretP@55w0rd","rememberMe":"true"}

Auth Response

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 477
Date: Thu, 24 Oct 2019 17:53:50 GMT
Connection: keep-alive

{
  "authentication": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJ1aWQiOjExMSwidXNlcm5hbWUiOiIiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJyb2xlIjoidXNlciIsImlzQWN0aXZlIjp0cnVlLCJjcmVhdGVkQXQiOiIyMDE5LTEwLTI0IDE3OjM5OjAyLjI3NCArMDA6MDAiLCJ1cGRhdGVkQXQiOiIyMDE5LTEwLTI0IDE3OjM5OjAyLjI3NCArMDA6MDAiLCJkZWxldGVkQXQiOm51bGx9LCJpYXQiOjE1NzE5Mzg3OTQsImV4cCI6MTU3MTk1Njc5NH0.Y7y2YOKNn-DCarSp_n8zZkFuXJbL0QoCmCKvmE7bCZg",
    "uid": 111
  }
}

Verifying authentication

As seen in the first scenario the testPath configuration will tell HawkScan how to verify if authentication has been configured correctly. In this scenario you can supply testPath.success criteria as the regex .*200.* which will only consider a 200 response code as success. If the regex matches the response header from testPath.path authentication will be considered a success. See more detail on testPath in the Configuration docs.

Authorized Request to /mysettings

GET /mysettings HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Accept: application/json, text/plain, */*
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJ1aWQiOjExMSwidXNlcm5hbWUiOiIiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJyb2xlIjoidXNlciIsImlzQWN0aXZlIjp0cnVlLCJjcmVhdGVkQXQiOiIyMDE5LTEwLTI0IDE3OjM5OjAyLjI3NCArMDA6MDAiLCJ1cGRhdGVkQXQiOiIyMDE5LTEwLTI0IDE3OjM5OjAyLjI3NCArMDA6MDAiLCJkZWxldGVkQXQiOm51bGx9LCJpYXQiOjE1NzE5Mzg3OTQsImV4cCI6MTU3MTk1Njc5NH0.Y7y2YOKNn-DCarSp_n8zZkFuXJbL0QoCmCKvmE7bCZg

Requests to protected routes like /mysettings, /comments and /privatemessages will have the Authorization header added to each request as specified in the tokenAuthorization configuration. With the type header describing the token being as a request header, the value being the name of the header Authorization , and tokenType ,Bearer prepended to the token.

External Token Authentication + Custom Token Authorization

As shown in the second scenario some applications maintain authorization by requiring that an authorization token be passed with every request. However sometimes authentication is not performed with just a username and password for example API key access or third party authentication services like OAuth. To support this HawkScan supports externally supplying an authorization token with the authentication.external configuration.

Theexternal supplied authorization token can be used in conjunction with either cookieAuthorization or tokenAuthorization . This scenario will usetokenAuthorization and a custom header for your authorization token as well as passing an externally generated token from our web applications jwt library. This will allow us to run HawkScan as any user of our application in a programatic fashion.

Example scenario #3

This scenario will use the same route layout as the previous scenarios.

POST      /login
GET       /home
GET       /comments
GET|POST  /mysettings <- protected route
POST      /comments <- protected route
GET|POST  /privatemessage <- protected routes

You would want a stackhawk.yml configuration like this.

stackhawk.yml

app:
  applicationId: xxXXXXXX-xXXX-xxXX-XXxX-xXXxxXXXXxXX
  env: Test
  host: ${APP_HOST:http://localhost:3000}
  authentication:
    external:
      type: TOKEN
      value: ${AUTH_TOKEN}
    tokenAuthorization:
      type: HEADER
      value: X-APIKey
    testPath:
      path: /mysettings
      success: ".*200.*"
      requestMethod: GET

In the above configuration we are specifying external.value=${AUTH_TOKEN} to allow the authorization token to be passed in as an environment variable at runtime.
Also we’re specifying that the authorization token should be passed as the custom header X-APIKey.

Now let’s create a script to generate an authorization token. This is an example using jsonwebtoken to create a valid JWT token but this can be done using whatever token library or framework you application uses.

const jwt = require('jsonwebtoken');
const privateKey = ''; //<- this should be your real private key.
var user = {
  "id": 1,
  "username": "",
  "email": "user1@example.com",
  "role": "user",
  "isActive": true,
};
var token = jwt.sign(user, privateKey, { expiresIn: 3600 * 5, algorithm: 'RS256' });
console.log(token);

Next create a script to invoke HawkScan with a token generated by gen-auth-token.js.

#!/usr/bin/env bash

token=$(node gen-auth-token.js)
docker run --rm -v $(pwd):/hawk:rw -e API_KEY=hawk.xxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxx -e AUTH_TOKEN=${token} -it stackhawk/hawkscan

In the above script we first call gen-auth-token.js which will output the token. This value is then passed to HawkScan via -e AUTH_TOKEN=${token}. Now you can run hawkscan.sh to kick off a scan. As with the previous scenarios you can use the provided testPath configuration to verify the token we generated can be used to validate requests.

Authorized GET Request to /mysettings

GET /mysettings HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Accept: application/json, text/plain, */*
X-APIKey: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiIiLCJlbWFpbCI6InVzZXIxQGV4YW1wbGUuY29tIiwicm9sZSI6InVzZXIiLCJpc0FjdGl2ZSI6dHJ1ZSwiaWF0IjoxNTczNTgyOTM1LCJleHAiOjE1NzM2MDA5MzV9.bj7bJJ149RcdipxO6B457ZAVaUcFE8zqD6CHEnK2tRH_XlwGTOv_PAXCGHHrBUHVPAJujo_ThMDJd_zv6q6edPX-iYJYH2UfmjA-vYC6KgOk1eUoAs8qzn0x4UwnO4LgLgTVIYilcWiIpdtG404jIKIGxeBAB4TavsOk0m8-ZsY

POST Authorization

A POST may also be configured in the above example to perform the same authentication verification against an endpoint such as /commnents. To do this, provide the requestMethod and requestBody options to the testPath configuration.

For example:

  ...
    testPath:
      path: /mysettings
      success: ".*200.*"
      requestMethod: POST
      requestBody: user=hawk&comment=KAKAAAW!

Authorized POST Request to /comments

As with the previous example, the authorization verification request will appear as follows.

POST /comments HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Accept: application/json, text/plain, */*
X-APIKey: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiIiLCJlbWFpbCI6InVzZXIxQGV4YW1wbGUuY29tIiwicm9sZSI6InVzZXIiLCJpc0FjdGl2ZSI6dHJ1ZSwiaWF0IjoxNTczNTgyOTM1LCJleHAiOjE1NzM2MDA5MzV9.bj7bJJ149RcdipxO6B457ZAVaUcFE8zqD6CHEnK2tRH_XlwGTOv_PAXCGHHrBUHVPAJujo_ThMDJd_zv6q6edPX-iYJYH2UfmjA-vYC6KgOk1eUoAs8qzn0x4UwnO4LgLgTVIYilcWiIpdtG404jIKIGxeBAB4TavsOk0m8-ZsY
user=hawk&comment=KAKAAAW!

Copyright © 2019-2020 StackHawk, Inc.