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
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¶mB=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 item2. 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.
No comments:
Post a Comment