Best way to handle security and avoid XSS with user entered URLs

SecurityUrlXssHtml Sanitizing

Security Problem Overview


We have a high security application and we want to allow users to enter URLs that other users will see.

This introduces a high risk of XSS hacks - a user could potentially enter javascript that another user ends up executing. Since we hold sensitive data it's essential that this never happens.

What are the best practices in dealing with this? Is any security whitelist or escape pattern alone good enough?

Any advice on dealing with redirections ("this link goes outside our site" message on a warning page before following the link, for instance)

Is there an argument for not supporting user entered links at all?


Clarification:

Basically our users want to input:

> stackoverflow.com

And have it output to another user:

<a href="http://stackoverflow.com">stackoverflow.com</a>

What I really worry about is them using this in a XSS hack. I.e. they input:

> alert('hacked!');

So other users get this link:

<a href="javascript:alert('hacked!');">stackoverflow.com</a>

My example is just to explain the risk - I'm well aware that javascript and URLs are different things, but by letting them input the latter they may be able to execute the former.

You'd be amazed how many sites you can break with this trick - HTML is even worse. If they know to deal with links do they also know to sanitise <iframe>, <img> and clever CSS references?

I'm working in a high security environment - a single XSS hack could result in very high losses for us. I'm happy that I could produce a Regex (or use one of the excellent suggestions so far) that could exclude everything that I could think of, but would that be enough?

Security Solutions


Solution 1 - Security

If you think URLs can't contain code, think again!

https://owasp.org/www-community/xss-filter-evasion-cheatsheet

Read that, and weep.

Here's how we do it on Stack Overflow:

/// <summary>
/// returns "safe" URL, stripping anything outside normal charsets for URL
/// </summary>
public static string SanitizeUrl(string url)
{
    return Regex.Replace(url, @"[^-A-Za-z0-9+&@#/%?=~_|!:,.;\(\)]", "");
}

Solution 2 - Security

The process of rendering a link "safe" should go through three or four steps:

  • Unescape/re-encode the string you've been given (RSnake has documented a number of tricks at http://ha.ckers.org/xss.html">http://ha.ckers.org/xss.html</a> that use escaping and UTF encodings).
  • Clean the link up: Regexes are a good start - make sure to truncate the string or throw it away if it contains a " (or whatever you use to close the attributes in your output); If you're doing the links only as references to other information you can also force the protocol at the end of this process - if the portion before the first colon is not 'http' or 'https' then append 'http://' to the start. This allows you to create usable links from incomplete input as a user would type into a browser and gives you a last shot at tripping up whatever mischief someone has tried to sneak in.
  • Check that the result is a well formed URL (protocol://host.domain[:port][/path][/[file]][?queryField=queryValue][#anchor]).
  • Possibly check the result against a site blacklist or try to fetch it through some sort of malware checker.

If security is a priority I would hope that the users would forgive a bit of paranoia in this process, even if it does end up throwing away some safe links.

Solution 3 - Security

Use a library, such as OWASP-ESAPI API:

Read the following:

For example:

$url = "http://stackoverflow.com"; // e.g., $_GET["user-homepage"];
$esapi = new ESAPI( "/etc/php5/esapi/ESAPI.xml" ); // Modified copy of ESAPI.xml
$sanitizer = ESAPI::getSanitizer();
$sanitized_url = $sanitizer->getSanitizedURL( "user-homepage", $url );

Another example is to use a built-in function. PHP's filter_var function is an example:

$url = "http://stackoverflow.com"; // e.g., $_GET["user-homepage"];
$sanitized_url = filter_var($url, FILTER_SANITIZE_URL);

Using filter_var allows javascript calls, and filters out schemes that are neither http nor https. Using the OWASP ESAPI Sanitizer is probably the best option.

Still another example is the code from WordPress:

Additionally, since there is no way of knowing where the URL links (i.e., it might be a valid URL, but the contents of the URL could be mischievous), Google has a safe browsing API you can call:

Rolling your own regex for sanitation is problematic for several reasons:

  • Unless you are Jon Skeet, the code will have errors.
  • Existing APIs have many hours of review and testing behind them.
  • Existing URL-validation APIs consider internationalization.
  • Existing APIs will be kept up-to-date with emerging standards.

Other issues to consider:

  • What schemes do you permit (are file:/// and telnet:// acceptable)?
  • What restrictions do you want to place on the content of the URL (are malware URLs acceptable)?

Solution 4 - Security

Just HTMLEncode the links when you output them. Make sure you don't allow javascript: links. (It's best to have a whitelist of protocols that are accepted, e.g., http, https, and mailto.)

Solution 5 - Security

You don't specify the language of your application, I will then presume ASP.NET, and for this you can use the Microsoft Anti-Cross Site Scripting Library

It is very easy to use, all you need is an include and that is it :)

While you're on the topic, why not given a read on http://msdn.microsoft.com/en-us/library/aa302420.aspx">Design Guidelines for Secure Web Applications

If any other language.... if there is a library for ASP.NET, has to be available as well for other kind of language (PHP, Python, ROR, etc)

Solution 6 - Security

For Pythonistas, try Scrapy's w3lib.

OWASP ESAPI pre-dates Python 2.7 and is archived on the now-defunct Google Code.

Solution 7 - Security

How about not displaying them as a link? Just use the text.

Combined with a warning to proceed at your own risk may be enough.

addition - see also https://stackoverflow.com/questions/176195/for-hosted-applications-should-i-be-sanitizing for a discussion on sanitizing user input

Solution 8 - Security

There is a library for javascript that solves this problem https://github.com/braintree/sanitize-url Try it =)

Solution 9 - Security

In my project written in JavaScript I use this regex as white list:

 url.match(/^((https?|ftp):\/\/|\.{0,2}\/)/)

the only limitation is that you need to put ./ in front for files in same directory but I think I can live with that.

Solution 10 - Security

Using Regular Expression to prevent XSS vulnerability is becoming complicated thus hard to maintain over time while it could leave some vulnerabilities behind. Having URL validation using regular expression is helpful in some scenarios but better not be mixed with vulnerability checks.

Solution probably is to use combination of an encoder like AntiXssEncoder.UrlEncode for encoding Query portion of the URL and QueryBuilder for the rest:

    public sealed class AntiXssUrlEncoder
    {
        public string EncodeUri(Uri uri, bool isEncoded = false)
        {
            // Encode the Query portion of URL to prevent XSS attack if is not already encoded. Otherwise let UriBuilder take care code it.
            var encodedQuery = isEncoded ? uri.Query.TrimStart('?') : AntiXssEncoder.UrlEncode(uri.Query.TrimStart('?'));
            var encodedUri = new UriBuilder
            {
                Scheme = uri.Scheme,
                Host = uri.Host,
                Path = uri.AbsolutePath,
                Query = encodedQuery.Trim(),
                Fragment = uri.Fragment
            };
            if (uri.Port != 80 && uri.Port != 443)
            {
                encodedUri.Port = uri.Port;
            }

            return encodedUri.ToString();
        }

        public static string Encode(string uri)
        {
            var baseUri = new Uri(uri);
            var antiXssUrlEncoder = new AntiXssUrlEncoder();
            return antiXssUrlEncoder.EncodeUri(baseUri);
        }
    }

You may need to include white listing to exclude some characters from encoding. That could become helpful for particular sites. HTML Encoding the page that render the URL is another thing you may need to consider too.

BTW. Please note that encoding URL may break Web Parameter Tampering so the encoded link may appear not working as expected. Also, you need to be careful about double encoding

P.S. AntiXssEncoder.UrlEncode was better be named AntiXssEncoder.EncodeForUrl to be more descriptive. Basically, It encodes a string for URL not encode a given URL and return usable URL.

Solution 11 - Security

You could use a hex code to convert the entire URL and send it to your server. That way the client would not understand the content in the first glance. After reading the content, you could decode the content URL = ? and send it to the browser.

Solution 12 - Security

Allowing a URL and allowing JavaScript are 2 different things.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKeithView Question on Stackoverflow
Solution 1 - SecurityJeff AtwoodView Answer on Stackoverflow
Solution 2 - SecurityBellView Answer on Stackoverflow
Solution 3 - SecurityDave JarvisView Answer on Stackoverflow
Solution 4 - SecurityPatrick McElhaneyView Answer on Stackoverflow
Solution 5 - SecuritybalexandreView Answer on Stackoverflow
Solution 6 - SecurityZach ValentaView Answer on Stackoverflow
Solution 7 - SecuritywarrenView Answer on Stackoverflow
Solution 8 - Securityhappy_cutmanView Answer on Stackoverflow
Solution 9 - SecurityjcubicView Answer on Stackoverflow
Solution 10 - SecuritybabakansariView Answer on Stackoverflow
Solution 11 - SecurityShashiView Answer on Stackoverflow
Solution 12 - SecurityNick StinematesView Answer on Stackoverflow