Skip to main content

CWE 601: OPEN REDIRECTS

Flaw

CWE 601: Open Redirects are security weaknesses that allow attackers to use your site to redirect users to malicious sites. Because your trusted domain is in the link, this can damage your organization’s reputation, or lend legitimacy to a phishing campaign that steals credentials from your users.

Consider the following code:

public void simpleRedirect(HttpServletRequest req, HttpServletResponse res) throws ServletException,IOException {
    res.setContextType("text/html");
    PrintWriter pw=res.getWriter();
    
    String target = request.getParameter("url");
    response.sendRedirect(target);
    
    pw.close();
}

This code allows an application to redirect the browser to a provided URL. For example, if the application is on trustworthy.org, a URL like http://www.trustworthy.org/#/redirect?url=stealyourmoney.org will redirect the browser to a page on stealyourmoney.org.

Server-side code defines the target variable as a value taken from the user's inbound request, then passes that value to the response.sendRedirect() method, to be used as its target location. Once the user clicks this link, the page sends them to the location that is specified in the URL's single parameter. Web applications frequently use this approach for legitimate redirects. But without some constraints on allowed values for the url parameter, an attacker can use it to fool other users into visiting an untrustworthy site.

Unlike many other flaws related to data validation, the risk is not that the attacker will send malformed data; the attack actually requires well-formed data to succeed.

For example, an application is hosted on a trusted domain: trustworthy.org. The attacker hosts a near-identical clone of this site at stealyourmoney.org. The two sites' login pages are visually indistinguishable, but the attacker controls the stealyourmoney.org application, and retains all input sent to it. They want to trick users into logging onto their site instead, in order to steal their credentials.

Some visitors would turn back after noticing the suspicious domain name in their URL bar, but a less-experienced user (or one in a hurry) might only see the trustworthy.org domain and assume the site is safe.

The attacker's goal is to use this redirect method on trustworthy.org to quietly direct a user to their stealyourmoney.org site. They construct a malicious URL:

https://www.trustworthy.org/#/redirect?url=stealyourmoney.org

To hide the stealyourmoney.org domain information from more-attentive users, they URL-encode the domain name, which then looks like this:

https://www.trustworthy.org/#/redirect?url=%73%74%65%61%6C%79%6F%75%72%6D%6F%6E%65%79%2E%6F%72%67

This link can be included in an email message as part of a phishing campaign.

Your account is expiring, and you'll lose access to this service. Log in at

https://www.trustworthy.org/#/redirect?url=%73%74%65%61%6C%79%6F%75%72%6D%6F%6E%65%79%2E%6F%72%67

to keep your account active.

This text grabs attention and creates a sense of urgency based on fear, so it's more likely to be clicked in context. Once clicked, despite having started on trustworthy.org, the user has somehow landed at stealyourmoney.org. Because the user is worried, they are less likely to notice they've been redirected -- after all, stealyourmoney.org looks just like trustworthy.org. The new site asks them to re-authenticate, and once they do, the attacker captures their credentials. They have stolen the trust that the first site's users placed in it, and weaponized it against them.

Fix

Because the url parameter is controlled by the client, it can be controlled by attackers. Therefore, the code must ensure that any URL it receives is safe. One of the most-reliable ways to do this is to create a table of allowed URLs, and have the url parameter only contain an integer that serves as an index to those allowed URLs.

public void simpleRedirect(HttpServletRequest req, HttpServletResponse res) throws ServletException,IOException {
    res.setContextType("text/html");
    PrintWriter pw=res.getWriter();
-   
-   String target = request.getParameter("url");
-   response.sendRedirect(target);
-   
+       
+   String[] safeURLs = new String[]{getArrayOfSafeURLs()};
+   HashMap<String, Integer> urlMap = new HashMap<String, Integer()>;
+       
+   for (int i = 1; i <= safeURLs.length(); i++) {
+       urlMap.put(safeURLs[i], i);
+   }
+       
+   String targetIndex = new String(request.getParameter("url"));
+   if(urlMap.values().containsKey(targetIndex) {
+       response.sendRedirect(urlMap.get(targetIndex));
+   }
+       
    pw.close();
 }
view fixed code only

Here, the code's behavior is changed to only allow targets from a group of destinations that are defined in advance on the server side. Instead of permitting access to any URL that a user provides, the new function uses a list of all permissible target URLs (a whitelist), then assigns an "indirect" value to each target. These indirect values will now appear in the URL instead of the literal destination. A valid link, such as http://www.trustworthy.org/#/redirect?url=5, sends the user to the URL represented by the index 5. If the attacker tries to repeat their exploit, the URL http://www.trustworthy.org/#/redirect?url=stealyourmoney.org will not perform the redirect. Doing it this way prevents the attacker from tricking someone into visiting any URL that they type.

References

CWE ↪WASC ↪