Forms with Username and Passwords
Overview
A common way to authenticate to a web application is by POST
ing a username and password which can be verified by your server.
Upon verification the server returns a cookie or token to the requesting client.
When giving HawkScan Username and Password credentials to login to your app, HawkScan needs to know if the server will return a cookie or a token to properly maintain the session.
The authentication
section of the stackhawk.yml
will have 4 parts:
- Logged in/out indicators
- How HawkScan checks it is logged in throughout the scan.
- Auth(N)
- Your form type and login credentials.
- Auth(Z)
- How you maintain the session. Either a Cookie or Token.
- Test Path
- How HawkScan sees if it successfully logged in
Make sure your file has all 4 of these parts filled out.
YAML by Form Type
HTTP Parameter Form:
stackhawk.yml
app:
applicationId: kkAAAKAW-kAWW-kkAA-WWwW-kAAkkAAAAwWW
env: Test
host: ${APP_HOST:http://localhost:3000}
antiCsrfParam: csrfToken
authentication:
# Paths that HawkScan checks to see if it is still logged in during the scan
loggedInIndicator: "HTTP.*2[0-9][0-9]\\s*O[kK](\\s*)|HTTP.*3[0-9][0-9].*" # Change me
loggedOutIndicator: "HTTP.*4[0-9][0-9](\\s*)Unauthorized.*" # Change me
# Auth(N) HTTP Form
usernamePassword:
type: FORM
loginPagePath: /login # Your login form path
loginPath: /login # Your login form path
usernameField: email # Field name for the account username/email
passwordField: password # Field name for the password
scanUsername: ${SCAN_USERNAME} # Inject variable at runtime or place your username here
scanPassword: ${SCAN_PASSWORD} # Inject variable at runtime or place your password here
otherParams:
- name: rememberMe
val: "true"
# (REQUIRED) Add your Auth(Z) here. Either Cookie or Token
#
#A path that can only be seen when successfully logged in. HawkScan will check this path to see if log in was successfull
testPath:
path: /mysettings # Change me
success: ".*200.*"
requestMethod: GET
Form with API Call / JSON Payload
stackhawk.yml
app:
applicationId: xxXXXXXX-xXXX-xxXX-XXxX-xXXxxXXXXxXX
env: Test
host: ${APP_HOST:http://localhost:3000}
authentication:
# Paths that HawkScan checks to see if it is still logged in during the scan
loggedInIndicator: "HTTP.*2[0-9][0-9]\\s*O[kK](\\s*)|HTTP.*3[0-9][0-9].*" # Change me
loggedOutIndicator: "HTTP.*4[0-9][0-9](\\s*)Unauthorized.*" # Change me
# Auth(N) Form with API Call / JSON Payload
usernamePassword:
type: JSON
loginPath: /login # Your login form path
usernameField: email # Field name for the account username/email
passwordField: password # Field name for the password
scanUsername: ${SCAN_USERNAME} # Inject variable at runtime or place your username here
scanPassword: ${SCAN_PASSWORD} # Inject variable at runtime or place your password here
otherParams:
- name: rememberMe
val: "true"
# Auth(Z) Token
tokenAuthorization:
type: HEADER
value: Authorization
tokenType: Bearer
# Additional configuration to tell HawkScan how to get the token from the response body.
tokenExtraction:
type: TOKEN_PATH
value: "authentication.token"
# A path that can only be seen when successfully logged in. HawkScan will check this path to see if log in was successfull
testPath:
path: /mysettings #Change me
success: ".*200.*"
requestMethod: GET
YAML Sections in Detail
Giving HawkScan Form Credentials
There are two ways to give HawkScan the Username and Password credentials to login to your application.
- HawkScan best practices is using environment variable runtime overrides. This is the most secure way to keep valid credentials to your application secret. These can be pulled in from your run command or a stored secret in your pipeline.
stackhawk.yml
app:
applicationId: xxXXXXXX-xXXX-xxXX-XXxX-xXXxxXXXXxXX
env: Test
host: ${APP_HOST:http://localhost:3000}
authentication:
usernamePassword:
scanUsername: ${SCAN_USERNAME}
scanPassword: ${SCAN_PASSWORD}
- The simpler, but less secure way, to do this is to directly put the Username and Password into the YAML file.
Add the desired Username and Password to the corresponding
.scanUsername
and.scanPassword
fields.
Adding otherParams
Sometimes login forms have more than just a Username and Password required to login. A common example of this is “Remember Me” checkboxes.
If your form has more fields that need to be filled out, you can do so under otherParams
.
Give the name
of the field and the required val
.
app:
applicationId: xxXXXXXX-xXXX-xxXX-XXxX-xXXxxXXXXxXX
env: Test
host: ${APP_HOST:http://localhost:3000}
authentication:
usernamePassword:
otherParams:
- name: rememberMe
val: "true"
- name: extraParam
val: "myString"
Maintaining the Session
Once HawkScan has logged in with the username and password form, add the following YAML snippet depending on if your application is using cookies or tokens.
Cookies:
stackhawk.yml
app:
applicationId: xxXXXXXX-xXXX-xxXX-XXxX-xXXxxXXXXxXX
env: Test
host: ${APP_HOST:http://localhost:3000}
authentication:
cookieAuthorization:
cookieNames:
- sessionid
Tokens:
stackhawk.yml
app:
applicationId: xxXXXXXX-xXXX-xxXX-XXxX-xXXxxXXXXxXX
env: Test
host: ${APP_HOST:http://localhost:3000}
authentication:
tokenAuthorization:
type: HEADER
value: Authorization
tokenType: Bearer
Custom Script:
Custom authentication and session management scripts can be used to handle complex authentication and authorization scenarios. If a preconfigured authentication and/or authorization style doesn’t meet your needs you can replace either with a custom script. Visit our GitHub repo to get started.
Test Paths and Logged In/Out Indicators
No matter what type of Authorization/Authentication your app is using, HawkScan requires .testPath
, .loggedInIndicator
and .loggedOutIndicator
.
Test Path:
A testPath
configuration needs to 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
.
Your testPath
should be a path that HawkScan can only access when successfully logged in.
This could be a path like “/dashboard” or “/accountdetails”
stackhawk.yml
app:
applicationId: xxXXXXXX-xXXX-xxXX-XXxX-xXXxxXXXXxXX
env: Test
host: ${APP_HOST:http://localhost:3000}
authentication:
testPath:
path: /mysettings
success: ".*200.*"
requestMethod: GET
Logged In/Out Indicators
Throughout the scan, HawkScan will check to see if it is still logged in by the .loggedInIndicator
and .loggedOutIndicator
.
These are regex strings to match against http responses from requests in the web application.
A .loggedInIndicator
could be a “Log Out” or ”Sign Out” button a user would see if logged in.
An example of a .loggedOutIndicator
would be a “Log In” button on the sign in page.
These can also leverage http status codes from the response.
stackhawk.yml
app:
applicationId: xxXXXXXX-xXXX-xxXX-XXxX-xXXxxXXXXxXX
env: Test
host: ${APP_HOST:http://localhost:3000}
authentication:
loggedInIndicator: "HTTP.*2[0-9][0-9]\\s*O[kK](\\s*)|HTTP.*3[0-9][0-9].*"
loggedOutIndicator: "HTTP.*4[0-9][0-9](\\s*)Unauthorized.*"
Example Form with HTTP Parameters with Cookie Authorization
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
.
The cookie verifying the user’s 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 stackhawk.yml
to add support for scanning as an authenticated user.
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
Detailed Configuration
You would want a stackhawk.yml
configuration like this.
stackhawk.yml
app:
applicationId: kkAAAKAW-kAWW-kkAA-WWwW-kAAkkAAAAwWW
env: Test
host: ${APP_HOST:http://localhost:3000}
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 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.
Example Form with API Call / JSON Payload with Token Authorization
Many modern web application backends are APIs that serve data to more than just html based web browsers. These apps 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, and others.
A common approach for authentication in this scenario is to create an API route that accepts a user’s 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 setting usernamePassword.type=JSON
will cause the credential
and otherParams
to be POST
ed 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.
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.
Note: In this 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.
POST /login
GET /home
GET /comments
GET|POST /mysettings <- protected route
POST /comments <- protected route
GET|POST /privatemessage <- protected routes
Detailed Configuration
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:
loggedInIndicator: "HTTP.*2[0-9][0-9]\\s*O[kK](\\s*)|HTTP.*3[0-9][0-9].*"
loggedOutIndicator: "HTTP.*4[0-9][0-9](\\s*)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
}
}
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.