Restrict sending custom request headers to allowed hosts

Problem

DAST sends user configured custom request headers with every request, regardless of the host. This can cause the following issues:

  • A request that was previously a simple CORS request becomes a preflighted request
  • Scans are slower due to the extra preflighted HTTP request (one per external resource request)
  • If the external host rejects the preflight request due to the additional header, it's likely the user's scan will fail (missing JavaScript, CSS, etc)
  • If the external host accepts the preflight request despite the additional header, the the request header is sent to the external host. This can be a security issue, depending on what was configured in the request headers.

This only affects requests made by a browser.

Proposal

Limit custom request headers to only be sent on DAST_BROWSER_ALLOWED_HOSTS. This is a breaking change.

This applies to the CI/CD variables DAST_REQUEST_HEADERS_BASE64, DAST_REQUEST_HEADER and DAST_REQUEST_HEADERS.

Proof that tokens may be leaked

The following redacted log was taken from a customer scan:

2022-10-06T05:30:16.000 TRC CHROM event received  {"method":"Network.requestWillBeSent","params":
{"requestId":"240.6","loaderId":"4E1105698FC8652047FB127FDC470C3B","documentURL":"<redacted>","request":
{"url":"https://use.fontawesome.com/releases/v6.2.0/css/all.css","method":"GET","headers":{"Authorization":"Bearer <secret-token>",...

Reference

From customer issue, see internal Slack thread.

Example

A user has a target website, that loads bootstrap JavaScript from an external CDN. A simple CORS request is made, to get the JavaScript. This is a GET HTTP request, where the response is status 200, the resource body contains the JavaScript and there is a Access-Control-Allow-Origin response header.

Page source Network requests
<html>
<body>
Hello world!

<script>
  let host = "maxcdn.bootstrapcdn.com"
  let url = "https://" + host + "/bootstrap/3.3.7/js/bootstrap.min.js"
  fetch(url)
</script>
</body>
</html>

Suppose the user adds a custom authorization token header, DAST_REQUEST_HEADERS: "Authorization: Bearer secret.token". The following network requests show that an extra request is made, a preflight CORS request. In this case, maxcdn.bootstrapcdn.com rejects the preflight request, therefore the JavaScript is unable to load.

Page source Network requests
<html>
<body>
Hello world!

<script>
  let host = "maxcdn.bootstrapcdn.com"
  let url = "https://" + host + "/bootstrap/3.3.7/js/bootstrap.min.js"
  fetch(url, {headers: {"Authorization": "Bearer secret.token"}})
</script>
</body>
</html>

Implementation plan

  • Remove the call to Network.setExtraHTTPHeaders (DevTools), as this indiscriminately adds headers to every request made by the current browser tab (https://gitlab.com/gitlab-org/security-products/analyzers/browserker/-/merge_requests/881/)
  • Store the custom headers and allowed hosts as new fields on the browser.Tab (see Tab.Init/setCustomHeaders) (https://gitlab.com/gitlab-org/security-products/analyzers/browserker/-/merge_requests/881/)
  • The DevTools event Fetch.requestPaused (DevTools) is used to intercept all requests and responses. When a request is continued, add the custom headers to ContinueRequestWithParams if the request host is an allowed host. The headers/allowed hosts can be retrieved from the tab. (part 1: https://gitlab.com/gitlab-org/security-products/analyzers/browserker/-/merge_requests/881/, part 2: https://gitlab.com/gitlab-org/security-products/analyzers/browserker/-/merge_requests/885)
  • Using an integration test, verify that extra headers are sent, but only to allowed hosts
    • TestCookiesAreRecordedWhenNavigationIsExecuted is a good example of such a test. More than one HTTP server could be created, so that there is more than one host. The second server could load a CSS file from the first server, emulating the problem.
Edited Oct 13, 2022 by Philip Cunningham
Assignee Loading
Time tracking Loading