Open Redirect Vulnerability

This post is about open redirect vulnerabilities; the story of two three vulnerable websites, why it’s bad, and how to prevent and detect abuse.

First, a primer on what an open redirect vulnerability is. As described by MITRE’s Common Weakness Enumeration (CWE), this attack (also known as a URL Redirection to Untrusted Site) is where a web application takes user-controlled input and redirects to the provided value.

Imagine if this URL on the example.com domain redirected to this blog:
http[:]//www.example.com/logout?redirect=https://blog.olliejc.uk

There are a number of flavours of the attack, user input could be set by different HTTP methods and there are a number of methods to legitimately cause a redirect in a web application. GET-based open redirects with query strings are probably the most dangerous as they are typically easy to exploit.

Methods for redirecting

Method 1: Location header and 3xx status response

Setting the Location response header with a 3xx status code (commonly 301 – Moved Permanently or 302 – Found) tells user agents (browsers) where to go before loading any of the body.
Location: https[:]//www.example.com

Method 2: Meta redirect in HTML head

Meta tag redirect runs after the user agent has processed the body.
<meta http-equiv="refresh" content="0;url=https://www.example.com" />

Method 3: JavaScript

JavaScript redirect that runs after the user agent has processed the body and where JavaScript is enabled.
window.location = 'https://www.example.com';

Two Three vulnerable websites

1. NCSC

(yes, that NCSC, the UK’s National Cyber Security Centre)

I found an open redirect vulnerability on NCSC website and reported to NCSC on the 13th July 2021.

The redirect was a result of a query string (curPage) that would set a location header in the response. It’s not clear what webserver or origin NCSC are using, as the site is fronted with AWS CloudFront, but it’s likely they implemented some logic in a proxy (maybe even using AWS Lambda@Edge) to handle redirects for old paths.

It wasn’t as obvious as setting curPage to http[:]//example.com as it looks like they are using a WAF – something was picking up “http” and blocking the request, however dropping the scheme and setting ?curPage=//example.com worked!
Fortunately, NCSC confirmed that the vulnerability was fixed recently so the above now doesn’t work.

My first reaction was that I didn’t expect NCSC to have such a vulnerability but after checking out their blog post Securing the NCSC’s web platform they explain being secure enough and not aiming for as secure as possible. The website just hosts public information, it doesn’t contain sensitive or personal data, or link to other systems; it’s more appropriate for NCSC to spend their (likely limited) resources focused on systems with a larger impact, such as Active Cyber Defence services.

Merit to NCSC for implementation of a security.txt and a vulnerability programme using HackerOne. They are also strong advocates of responsible disclosure and have a vulnerability disclosure toolkit to help others implement reporting programmes.

Having a clearly signposted reporting process demonstrates that your organisation takes security seriously.

ncsc.gov.uk

It’s a shame that the NCSC programme isn’t a funded bounty, monetary rewards make it less likely that someone would sell a vulnerability to a third party. The Ministry of Defence (MoD) has recently shown that it is possible with public funds – Ethical hackers collaborate with Defence to strengthen cyber security.

NCSC shared the following quote with me for this post.

Responsible vulnerability reporting is vital. Thanks to Ollie for alerting us to this vulnerability, it was quickly fixed on the site and we’ve flagged to our development teams so they can check other systems. It may be a good time for readers to check for open redirects on their own sites!

Dr Ian Levy, Technical Director, NCSC

2. US ISP (rhymes with salinity)

I found this one a few weeks back when I received a phishing email exploiting the vulnerable open redirect, I reported it (after reaching out on Twitter as they didn’t have a security.txt file) the same day. Unfortunately, it was deemed a duplicate and still hasn’t been fixed, so is still likely being used maliciously.
Sad times.

What’s interesting about this vulnerability is that it’s an example of method three. The page in question has ‘legitimate’ JavaScript that can be exploited to redirect to a chosen URL. It looks something like this (pro tip – don’t do this):

var location_search = "?continue=http://example.com";
var continue_url = /.*(^|$|\?)*continue\=(.+?)(\&|$).*/
  .exec(location_search);
var url = '';

if (continue_url && continue_url[2]) {
  url = decodeURIComponent(continue_url[2]);
}

if (url) {
  // window.location = url;
  console.log("Would have redirected to: "
    + url);
}

By setting the continue query string parameter, the JavaScript takes the unsanitised value and redirects using window.location, bleurgh. Also, that use of regular expressions… no.

Colin Farrell pulling a disgusted face

3. Canadian Media Site

As I was writing this post I received another phishing campaign using an open redirect vulnerability. Unfortunately, I’ve not been able to get a response from the vulnerable website yet. It’s an example of method one, the u query string parameter is set in the Location response header.

Screen shot showing a redacted curl -I command and result to the vulnerable website

Edit: while I haven’t had a response, it looks like this one is now fixed.

Why’s it bad?

For most websites it’s not intentional to have this vulnerability. Abuse can undermine message filtering systems, increase victim trust in phishing campaigns and result in legitimate websites being flagged as malicious.

Take the NCSC example, they host lots of content for people in the public and private sector to use and download (such as PDF threat reports – side note: it’d be amazing if these were HTML pages instead 🙏).

So imagine hosting a malicious PDF and crafting a URL like: https[:]//www.ncsc.gov.uk/pdfs/report/weekly-threat-report-6th-august-2021.pdf?curPage=//s3.amazonaws.com/hosted-content-gir82jf8/weekly-threat-report-6th-august.pdf
If that was sent in a malicious email to NCSC saying “Typo on page 6”, do you think they’ll notice? Would they click that link instead of finding the page themselves? Maybe the mention of S3 would be suspicious, but NCSC use S31 so they might not think twice.

Preventing

Don’t allow unsanitised or unverified user input to set the redirect URL. There’s a number of ways you could achieve this.

Here’s some example code for an origin response2 AWS Lambda@Edge function:

exports.handler = async (event) => {
    const response = event.Records[0].cf.response;
    let headers = response.headers;

    if ('location' in headers) {
        const location = headers['location'][0].value;
        const url = new URL(location);
        // if not ends with example.com
        if (!url.hostname.endsWith('example.com')) {
            delete headers['location'];

            // you may want to send a notification to
            // yourself here so you can fix the root
            // cause
        }
    }
}

Here’s what the US ISP could use in their client-side JavaScript to check that the output URL has a hostname which is expected:

const location_search = "?continue=http://example.com";

// use URLSearchParams to parse query strings
// instead of using regex
const qs = new URLSearchParams(location_search);

// get the expected query string
const continueValue = qs.get("continue");

// array of allowed hostnames
const allowedHostnames = ["example.com"];

if (continueValue) {
  // use URL to parse the value so that the
  // lowercase hostname attribute can be assessed
  const parsedUrl = new URL(continueValue);

  if (parsedUrl) {
    if (allowedHostnames.includes(parsedUrl.hostname)) {

      // window.location = continueValue;
      console.log("Would have redirected to: "
        + continueValue);

    }
  }
}

Another note, if you are using JavaScript and URL be careful using pathname and redirects.

For example:
(new URL("https://x.y//123.com")).pathname == //123.com.

Detecting as defenders

It can be quite difficult to detect if you have an open redirect vulnerability being exploited.

It may be possible if you can look at the response body from your origin servers – it is likely possible with most Web Application Firewalls (WAFs) or with edge functions such as AWS Lambda@Edge (CloudFront Functions can’t access the body) or Cloudflare Workers.

Method one can be spotted if you log response headers from your web server. You can look for a 3xx response status code and a Location header that isn’t expected.

It’s not typical to log the Location response header but the W3C Extended Log File Format suggests this field name will be sc(Location) which some monitoring tools (like Splunk) will likely rename sc-location.

Sigma

Here’s an example Sigma rule to assist detection:

title: Open redirect from web server logs
Id: tbc
status: experimental
description: Detects the open redirects
author: OllieJC
date: 2021/08/07
references:
  - https://cwe.mitre.org/data/definitions/601.html
logsource:
  category: webserver
detection:
    selection:
        sc-status: '3*'
        cs-method: 'GET'
    selection_header:
        sc-location:
            - 'http*'
            - '//*'
    selection_uri:
      uri: '*?*//*'
    selection_querystring:
      c-uri-query: '*//*'
    condition: selection and (selection_header or selection_uri or selection_querystring)
fields:
  - uri
  - sc-status
  - sc-Location
falsepositives:
  - Legitimate redirects are likely to be captured in this default rule so it likely needs tweaking to not match valid domains
level: medium
tags:
  - attack.initial_access
  - attack.t1190
  - attack.t1566.002

I’d love to hear about any other prevention or detection techniques for this vulnerability.

Thanks for reading!
Ollie


Footnotes

1 NCSC’s Content Security Policy response header mentions https[:]//s3-eu-west-1.amazonaws.com/ncsc-content/ so S3 is or was likely used for hosting static content.

2 A Lambda@Edge origin response will cause the response from the origin to be cached in CloudFront. This may or may not be desirable. The benefit is that the Lambda wouldn’t need to run every time.

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s