Wednesday, March 25, 2015

Fixing Open Redirect

Sometimes most common vulnerabilities are not that easy to fix or prevent, one of them can be "Open Redirect"

A short example is: you are shopping at an e-commerce, you visit an item and want to save that item for later, and click on "favourite". The sites redirects to the login; after the login you are redirected back to the item.
Keep in mind that the login and item page are not necessarily in the same domain.
(Example: youtube.com login is on google.com)

Note: More detail from OWASP can be found here

The easiest way to achieve this, is if the controller of authenticated actions, like "favourite" redirects to the login sending as a parameter de item page, so it know where to go back.

https://site.com/login?back_url=http://site.com/buy/itemX

Note: keep in mind that the back_url parametere must be URL Encoded.

A few of the risks of the abover solution are:

Abusing the trust a victim has in the domain so it clicks on a link that ends in a phishing or a malware download or a Browser exploit attempt.

Consider: If a site mitigates CSRF with Referer domain validation it can be faked with an Open Redirect.


Solution:
There are many solutions I'll describe just two.

1. URL HMAC Signing
In the "favourite" controller, do a 302 redirect to this URL:

http://site.com/login?back_url=http://site.com/buy/itemX&HMAC=af398bc82720a

HMAC is a signature of the back_url parameter. HMAC is very easy to use and available in most languages.

You have to decide wich hash algorithm to use. Use SHA2 if possible, but SHA1 will do the job as well.
You have to use a Secret Key, for this generate a 256 bit Secure Random.

example: HMAC(SHA2, Key, "http://site.com/buy/itemX") = af398bc82720a
NOTE: the result will be 160 bits long for SHA1 and 256 bits for SHA256, hex encoding will work just fine.

The Secret Key will have to be shared among the "login" and the controllers that perform de redirection to it.

Consideration: Don't use the Referer Header to generate the back_url, create the URL server side.

When the login receives the back_url, it recalculates the HMAC, if it matches with the one provided in the link it performs the redirection.



import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.xml.bind.DatatypeConverter;

public class SignURL {

  public static void main(String[] args) throws Exception {
    System.out.println(hmacDigest("http://site.com/ItemX", "SecurelyGeneratedKeyOf256SecureRandomBits", "HmacSHA256"));
  }

  public static String hmacDigest(String msg, String keyString, String algo) {
    String digest = null;
    try {
      SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algo);
      Mac mac = Mac.getInstance(algo);
      mac.init(key);

      byte[] bytes = mac.doFinal(msg.getBytes("ASCII"));

      digest = DatatypeConverter.printHexBinary(bytes);
      
    } catch (UnsupportedEncodingException e) {
    } catch (InvalidKeyException e) {
    } catch (NoSuchAlgorithmException e) {
    }
    return digest;
  }
}


Advantage:
1. Can easily fix existing open redirects.
2. It's not vulnerable to HTTP Response splitting
3. HMAC is easy to use and readily available.

Disadvantage:
1. Needs to share a Secret key among many controllers, and potentially many Developers.
2. Needs to change all the controllers that perform redirections ("login" and the ones that generates the redirects ("itemX")

2. Validating domains with tested libraries

When you receive this parameter in the "login" controller:

back_url=http://site.com/buy/itemX

You can validate that the domain "site.com" is legit.


import java.net.URL;
import java.net.MalformedURLException;

public class URLValidation{

     public static void main(String []args){
        
        try {
            //URL url = new URL("http://subdomain.site.com.hack.com/site.com");
            URL url = new URL("http://subdomain.site.com/pathA/Param1/pathB?paramA=A&paramB=B");        
            if (url.getHost().endsWith(".site.com") || url.getHost().equals("site.com") ){
                System.out.println("OK");
            } else {
                System.out.println("Fail");
            }
        }
        catch (MalformedURLException e){   
        }
     }
}

Note: use "endsWith", "contains" allows "site.com.hack.com". Since "getHost()" gets you also subdomains doing a straight comparison forces you to have a list of subdomains, instead of just domains. When using "endsWith" always include a dot "." at the beginning, if not, you would accept "http://hacker-site.com". If you want to accept "http://site.com" you have to add the or statement asking for equals.


Advantage:
1. You only have to make this validation in the redirection controllers, such as login

Disadvantage:
1. You have to keep an updated legit domain list.
2. If the library you used has a flaw you could be vulnerable.
3. If no library is available, making one with regex is extremely prone to error.
4. You have to canonicalize the input and do a validation of the characters, because the attacker might inject a end of line character, causing a header injection or worse, an HTTP Response Splitting.


Potential solutions that won't work or are prone to fail


1. In this situation OWASP first recommendation, avoid redirects, can't be easily achieved.
The second recommendation, don’t involve user parameters, can't be easily achieved neither.
One might think using "back_url=itemX" will solve the problem, the thing is that every section that is not an item that redirects to the login must have a mapping. This will turn out to be a mapping nightmare.

2. Encrypting the URL. Remember that encryption is hard to do it right, and it's targeted at confidentiality. And what you need in this case is integrity and authenticity.

3. Relatives URL won't work in different domains.

4. Create real time mapping server side.

The "favourite" controller generates a Random Token and stores it with the URL in server side (database, memcache, or whatever: it rises the questions of who is the owner, who is responsible for it's up time, monitoring, maintenance, repair)

Keep in mind that they might even be in different data centers. (think of Youtube and Google)

The back_url will be a Random Token like this:
http://site.com/login?back_url=0949jr2oi39493

Another issue with this is keeping all this information quickly accessible, and for a long time.


5. As told before doing your own domain validation is a bad idea, it's very prone to error or you will not accept every type of valid URLs.

6. Fuzzy logic at the front-end.

Scenario A:
1. User goes to see an item
2. Presses "buy"
3. A javascript determines if the users is logged or not by watching a Boolean flag cookie. (without HTTPOnly of course)
4A. If the flag says it's logged in, the "buy" actions hits the "buy" controller.
4B. If the flag says it's not logged in, the "buy" actions, performs a POST to the "login" controller, and sends with it, the URL of the visited item, so It knows where to go back.
5B. The login page is displayed, and in a hidden field resides the URL to go back.
6B. If the login is successful it does a redirect to the URL stored in the hidden field, which was send along with the user and password in the POST.

The good thing about this solution is that you don't share any information between "buy" and "login" through the back-end, and you don't store anything in the back-end either.

The first thing you might think is Tampering with the Boolean flag cookie, but if the cookie is forged to look like logged out, and you are logged in, it will send you to the login, nothing wrong with it. If the cookie is forged to look like logged in when you are not, you will go to the "buy" controller, which will tell very quickly that you are not logged in, no harm there.

A potential attacker might create a site that only does a POST to the login, sending the a fake URL to go back, potentially a forged clone of the original login. When the victim does a successful login, it's redirected to the fake login, where it's asked for the user and password again. This way the attacker can exploit this solution.

The big problem is that the login can't be sure who is sending the URL to go back! Using the "Referer" header is not safe as told before, and it's not always there (HTTPS link from one domain to another).



There's probable more bad solutions than good ones, so keep in mind which one you use.

Sunday, March 8, 2015

Port Monitor Tool

Someday I was thinking how can I monitor the status of a big pool of public IPs, and decided to look for something simple that can alert when something changes in an IP, for example a port that was closed is now open.

To be honest I didn't do a thorough google search, but a simple search didn't get me something simple, free and open source, so I decided to make one of my own.

The premises were:
1. If the software suddenly stops, there shouldn't be half a port scan in the database, that would create wrong alerts.
2. If the software suddenly stops, next time it should keep scanning the previous IP and keep the same order.
3. It must be possible to add new IPs, without restarting the software, without the hassle of connecting to the Database.
4. Every scan detail must be stored.

The premises are quite simple.

This script can be used by companies that want to continually audit and monitor they public IPs. They can cross data with tickets or with configuration management.

It can be also be used by consultants, so they can monitor their clients networks. And of course it can be used by attackers or people looking for entry points to look for vulnerabilities for a bug bounty.

I used ruby language, with the library "ruby-nmap" to manage nmap scans.
Every scan has an xml output that is stored in the output folder.

The script is very simple, and probably not very beautiful so it's easy to modify (I believe).

The code is here.

Considerations:
1. It's better to use an external server for the scanning, to be sure that there aren't odd firewall rules that show different results. An example would be an EC2 instance.
2. If you have an IPS and/or a firewall with IPS features, make sure to add the scanner IP in the white list.
3. Nmap is ran with its default Timing settings (-T3), using more aggressive timings can introduce false positives.
4. By default it scans ports: 1-65535.

Limitations:
1. Right now, it just scans 1 IP at a time, so if your pool of IP is very big, it can take a long time to do a full cycle.
2. The tool reports just port state changes, not if the software banner or fingerprint changed, this is in order to simplify and reduce false positives.
3. Output files are stored in the folder "xml", with time this folder can get very big.
4. Log files are in the "log" folder, and they are rotated daily.

How to make it work:

This code was ran with Ruby 2.1.2 at Ubuntu 14.04

The Library for using nmap is:
https://github.com/sophsec/ruby-nmap

These are the required dependencies

sudo apt-get install mysql-server mysql-client
sudo apt-get install libmysqlclient-dev
sudo apt-get install nmap
sudo apt-get install zlib1g-dev

gem install rprogram
gem install nokogiri
gem install ruby-nmap
gem install dbi
gem install mysql
gem install dbd-mysql
gem install mandrill-api
gem install mail

After ruby is working, you must create a database with the file database.sql

Before running the script you need to configure:

1. the database name, IP (usually localhost), username, and password. Line
2. e-mail settings, "from@gmail.com" (Line 246).
3. if you want to send e-mails with mandrill you need to get an api-key and replace it where it says 'madrill-api-key' (Line 146)

You will also need to change parameters for e-mail

Someday I'll include a config file, in order to make this easier.

As nmap needs high privileges, you need to run the script with sudo.

sudo ruby port_monitor.rb

Logs are rotated daily, but XML files will pile up with time.
log/activity.log - everything is logged
log/diff.log - just logs port status changes

In hosts.txt you can add new IPs, one IP per line. CIDR notation or any notation is not supported.
After a scan finishes, the script will process the hosts.txt file and rename it hosts.txt.processed.


Let me know if it works, and if there any thing you would change or add.