Obfuscating a url - asp.net

I'm working on an asset management website where in whenever an asset is issued to a user the system would send an email notification with a url in it that would show the user all the assets issued to him. I could have used the query string to pass the user ID but again people could abuse it to view assets issued to other users. My client doesn't wants the user to authenticate themselves when they click on the link. So i need something that would hide the parameters being passed in the query string or at least make them obscure. I've read about url encoding, GUID etc. but i'm not sure what to do. I'm just a beginner. Please pardon my ignorance and point me in the right direction.

Taken what you have said, that you're just a beginner, and assuming that this will be public, you can do the easiest way:
Create a new table in your database and called for example tbl_links, as columns just add 3
user_id (foreigner key to the user table)
guid (primary key, unique)
settings (nvarchar(250)
When you need to send an email, create a new row for the user, for example:
Guid guid = Guid.New();
String settings = "date_from:2012/01/01;date_to:2013/01/01";
And insert it one the database, where the link that you put in the email, should have the guid, for example, http://domain.com/info/?g=....
You could append Json to that settings column and parse it into an object again in the code, ask a new question if you want to take this route.
I personally use a security algorithm to pass only the user_id but you did said you're a beginner, so I only showed you the easy and still valid way.
P.S. for security reasons, you should say in the email that your link is only valid for the next 4 hours or so you can prevent people from generating GUIDs in order to try and get some information.... Simple add a create_date column of type datetime and use that to see if the link already expired or not...

For obscuring URL parameters, you want to use a modified Base64 encoding. Please keep in mind that obscurity is not security, and Base64 encoding something does not in any way make anything secure.
If you're intending to use this for authentication purposes, I think you should reconsider. Look into public key encryption and digital signatures as a starting point.

Trying to secure access to a URL is not the right approach. Give the urls away freely and authenticate your users instead.
I would also highly recommend using SSL for serving up this data.
Security through obscurity fails 100% of the time once the obscurity is not longer obscure.

What you can do is to add some prefix and suffix to the id and the encrypt that string. Something like this:
static public string EncodeTo64(string toEncode)
{
byte[] toEncodeAsBytes
= System.Text.ASCIIEncoding.ASCII.GetBytes(toEncode);
string returnValue
= System.Convert.ToBase64String(toEncodeAsBytes);
return returnValue;
}
static public string DecodeFrom64(string encodedData)
{
byte[] encodedDataAsBytes
= System.Convert.FromBase64String(encodedData);
string returnValue =
System.Text.ASCIIEncoding.ASCII.GetString(encodedDataAsBytes);
return returnValue;
}
string prefix = "lhdsjñsdgñdfj";
string suffix = "dfknsfñn3ih";
var strToEncode = prefix + "|" + id + "|" + suffix;
var encoded = EncodeTo64(str);
var decoded = DecodeFrom64(encoded).Split('|');
if( decoded.length != 3 || decoded[0] != prefix || decoded[2] != suffix )
throw new InvalidArgumentException("id");
var decodedId = decoded[1];

Related

returnUrl drops second querystring parameter

I am using MVC 5. The problem is that after SSO redirects back to the app after authentication the login method returnUrl drops the applicaitonId querystring parameter. Please help!
Here is the flow.
The app redirects unauthorized users to a login method, preserving the original request in the returnUrl.
The original request is
http://localhost:25451/shared/download?documentGroup=133&applicationId=3153
the returnUrl is
/shared/download?documentGroup=133&applicationId=3153
The app redirects to a SSO CAS server, sending along the HttpUtility.Encode returnUrl as a parameter along with login Url both part of the service parameters.
https://{redacted}/cas/login?service=http://localhost:25451/account/login%3freturnUrl%3d%2fshared%2fdownload%3fdocumentGroup%3d133%26applicationId%3d3153
After authentication, the CAS server appends the authorized ticket and redirects back to the service URL. This is what fiddler shows.
http://localhost:25451/account/login?returnUrl=/shared/download?documentGroup=133&applicationId=3153&ticket={redacted}
Here is the issue. The returnuRL in the login method is simply
/shared/download?documentGroup=133.
The returnUrl no longer has the applicationId.
Interestingly enough, the line works just fine.
var ticket = Request.QueryString.Get("ticket");
I have tried to encode the whole serviceUrl and tried to encode just the returnUrl(see below) but I get the same missing ApplicationId issue.
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
var ticket = Request.QueryString.Get("ticket");
if (!string.IsNullOrEmpty(ticket))
{
//verify the ticket...
return RedirectToLocal(returnUrl);
}
var serviceUrl = Request.Url.Scheme + System.Uri.SchemeDelimiter + Request.Url.Host + (Request.Url.IsDefaultPort ? "" : ":" + Request.Url.Port) + "/account/login" + "?returnUrl=" + HttpUtility.UrlEncode(returnUrl);
var authenCasUrl = string.Format("{0}login?service={1}", "https://{redacted}/", serviceUrl);
return Redirect(authenCasUrl);
}
Since this site will be actually called by your URL, I don't think they just throw away parts of it.
Lets try something here since I have encountered a similar problem with parameter in url strings in combination with asp.NET.
First, lets get the unedited URL from your Request:
string UneditedUrl = Request.RawUrl;
Since we are not needing anything before the ? mark, we shorten it a little bit:
string QueryString = (UneditedUrl.IndexOf('?') < UneditedUrl.Length - 1) ? UneditedUrl.Substring(UneditedUrl.IndexOf('?') + 1) : String.Empty;
This line also includes the possibility on neither having a ? mark or parameters and will return an empty string if so. Just for good measure, we don't want any exceptions here. Here you can check QueryString if it has both or more of your parameters you entered.
If there are not complete here, its not your code at fault. Something will already work on your URL before you do, probably your host then. Maybe check the settings of your IIS.
If your parameters are correctly in the edited QueryString, you can continue getting them by following this:
I learned that there is a way to let your framework do the job of parsing parameters into name/value collections. So lets give it a go:
NameValueCollection ParaCollection = HttpUtility.ParseQueryString(QueryString);
You can now check you params and their values by either using an index like ParaCollection[0] or ParaCollection["documentGroup"].
EDIT:
I've found the question which brought me to the conclusion of using Request.RawUrl. Since this may not be the answer, it will maybe help a little bit more to understand that Request.RawUrl is the actual URL the user called and not the one the server executes: RawURL vs URL
I have no experience with asp or SSO, but you may need to also HttpUtility.UrlEncode the value of the serviceUrl variable?
var authenCasUrl = string.Format("{0}login?service={1}", "https://{redacted}/", HttpUtility.UrlEncode(serviceUrl));
Since the service parameter is decoded by the CAS once, and then the value of returnUrl gets decoded by your server.
var returnUrl = "/shared/download?documentGroup=133&applicationId=3153";
var serviceUrl = "http://localhost:25451/account/login?returnUrl=" + HttpUtility.UrlEncode(returnUrl);
var casUrl = "https://{redacted}/cas/login?service=" + HttpUtility.UrlEncode(serviceUrl);
Which gives:
serviceUrl = http://localhost:25451/account/login?returnUrl=%2Fshared%2Fdownload%3FdocumentGroup%3D133%26applicationId%3D3153
casUrl = https://{redacted}/cas/login?service=http%3A%2F%2Flocalhost%3A25451%2Faccount%2Flogin%3FreturnUrl%3D%252Fshared%252Fdownload%253FdocumentGroup%253D133%2526applicationId%253D3153
Explanation attempt:
You make a HTTP request to the CAS server. It's implementation splits the query parameters and decodes each value (and possibly key). One of which is the service parameter and is now (after decoding) a valid URL.
The CAS server makes a HTTP request with the URL from the service parameter (to your server) with the ticket appended.
You split the query parameters and decode each value (and possibly key).
If you only encoded the returnUrl once, your serviceUrl will look like what you showed in your third point:
http://localhost:25451/account/login?returnUrl=/shared/download?documentGroup=133&applicationId=3153&ticket={redacted}
How does the algorithm splitting the query string differentiate between a ? or & in the serviceUrl and the ones in the returnUrl?
How should it know that ticket does not belong to the returnUrl?
As you can see in my code above, you are not encoding the returnUrl twice.
You are putting one URL in the parameters of another URL and then you put that URL in the parameters of a third URL.
You need to call UrlEncode for each value (and possibly key) when you put together a query. It does not matter whether that value is a URL, JSON, or arbitrary user input.

HTTP get request won't submit with a URL encoded parameter

I'm currently writing an ASP.NET Core web API that has an action with a encrypted value as a parameter.
I'm trying to test this action and the URL won't even submit in the web browser, at first I thought it could be due to the URL being too long but I've found this answer and my URL is well below the 2000 character limit. I've changed the parameter to a trivial string ("hello") and it submits fine and runs the code. I've tried in both Edge and IE11 whilst debugging my application, in Edge nothing happens at all, in IE11 I get a message saying:
Windows cannot find 'http://localhost:5000/api/...' Check the spelling and try again
In either case the code in the application doesn't execute (I've put a breakpoint on the first line of the controllers constructor which isn't being hit).
I've included an example of one of the URLs that isn't working below, as well as the code I'm using to generate the encrypted string, it uses HttpUtility.UrlEncode to convert the encrypted byte[] array to a string.
Example URL (one that doesn't work):
http://localhost:5000/api/testcontroller/doaction/%95%d6%f8%97%84K%1f%d4%40P%f0%8d%de%27%19%ed%ffAR%9c%c6%d4%b1%83%1e%9fX%ce%9b%ca%0e%d4j%d3Rlz%89%19%96%5dL%b1%16%e9V%14u%c7W%ee%89p%3f%f7%e6d%60%13%e5%ca%00%e9%a2%27%cb%d3J%94%a6%e1%b9%9c%914%06y%7e%0bn%ce%00%e5%7d%98b%85c%fa6m%7d%f7%f1%7b8%26%22%5e%1et%5e%10%0c%05%dd%deFAR%bb%93L%b9-W%e1K%82%d8%cc8%ce%e0%0c%2b%bc%19
Action:
[HttpGet("[action]/{encrypted}")]
public string DoAction(string encrypted)
{
return "Executed";
}
Generate encrypted string:
private string GenerateEncryptedString()
{
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();
byte[] data = HttpUtility.UrlDecodeToBytes("AHMW9GMXQZXYL04EYBIW");
byte[] encryptedData = rsaProvider.Encrypt(data, true);
string encryptedString = HttpUtility.UrlEncode(encryptedData);
return encryptedString;
}
Not sure if I'm going wrong in my methodology for converting the encrypted data to a string but I would appreciate any feedback on how to fix this issue.
I think you should try to pass this data in the query string and not in the location (path) part of the url (some characters may be forbidden in paths as a security layer), so add a ?data= before the encoded data.
http://localhost:5000/api/testcontroller/doaction/?data=%95%d6%f8%97%84K%1f%d4%40P%f0%8d%de%27%19%ed%ffAR%9c%c6%d4%b1%83%1e%9fX%ce%9b%ca%0e%d4j%d3Rlz%89%19%96%5dL%b1%16%e9V%14u%c7W%ee%89p%3f%f7%e6d%60%13%e5%ca%00%e9%a2%27%cb%d3J%94%a6%e1%b9%9c%914%06y%7e%0bn%ce%00%e5%7d%98b%85c%fa6m%7d%f7%f1%7b8%26%22%5e%1et%5e%10%0c%05%dd%deFAR%bb%93L%b9-W%e1K%82%d8%cc8%ce%e0%0c%2b%bc%19

Putting ID in Get request

I have a following url:
/Reports?reportId={value}
The problem is that not every user can see every report, but with this kind of url he can actually enter whatever ID he wants and access that report.
So my question is should I just add a server side validation to check if currently logged user has access to this report and if not return some Anauthorized response, or send this via POST so he cannot see the url and change it (if this approach is safe enough).
And generally I want to know if it is bad idea to send ID in url? Maybe it is better to use Guids for ID-s then instead of integers?
So my question is should I just add a server side validation to check if currently logged user has access to this report and if not return some Anauthorized response
Yes, a thousand times this.
or send this via POST so he cannot see the url and change it (if this approach is safe enough).
No. Never this. This is security through obscurity. POST data is not hidden in any way.
if it is bad idea to send ID in url? Maybe it is better to use Guids for ID-s then instead of integers?
No, that does not matter at all. That also is security through obscurity. The GUIDs still show up in plaintext, so anyone with access to browser history, firewalls, or the network in general can inspect and replay the traffic containing GUIDs.
See also Why not expose a primary key - Programmers Stack Exchange.
In addition to CodeCaster's answer:
IDs in URLs can indirectly reveal some business related information. For example from contract ID=963 your competitor may learn that you did since last month 40 new contracts (ID was e.g. 923) and earned cca 50k. This is sometimes not desired.
It's quite common though to mask ID e.g. by converting into a masked string. I usually use openssl_encrypt and openssl_decrypt respectively.
Here's example for converting ID to/from masked string:
public static function encryptOpenssl($textToEncrypt, $encryptionMethod = 'AES-256-CFB', $secretHash = "12#rk!", $raw = false, $password = ''){
$length = openssl_cipher_iv_length($encryptionMethod);
$iv = substr(md5($password), 0, $length);
return openssl_encrypt($textToEncrypt, $encryptionMethod, $secretHash, $raw, $iv);
}
public static function decryptOpenssl($textToDecrypt, $encryptionMethod = 'AES-256-CFB', $secretHash = "12#rk!", $raw = false, $password = ''){
$length = openssl_cipher_iv_length($encryptionMethod);
$iv = substr(md5($password), 0, $length);
return openssl_decrypt($textToDecrypt, $encryptionMethod, $secretHash, $raw, $iv);
}
You can of course use any other obfuscating algo, e.g. rot13, exchanging character positions, character mapping, prepending/appending irrelevant chars etc.

How to pull user info from twitter api

I trying to implement login using twitter in my asp.net application. I am following this article to do this functionality:
Login with twitter using Oauth
It give me only screen name and user id. i want to get more info like username, location and email(if possible). Can anyone tell me how to get these info.
The field screen_name is Twitter's user id. You cannot get the email. You can get the location of the user in a String field called location in the same object, user.
You can find the full description of the object user in this link.
As a side note, you can get the location as geo. coordinates in every tweet. It is the field coordinates in the object tweet. But in my experience it is hardly ever informed.
As for the code for the actual access, looking at the link you provide in your question, you could build up a method like the one I've tried to write below. Sorry I cannot test it (actually I don't know much of .NET), but I hope it won't get you long to get it from this point. This would be calling GET users show and the implementation would be similar to this:
public static HttpWebRequest getUserData(string oauth_consumer_key, string oauth_consumer_secret, string oauth_access_token, string oauth_token_secret, string user_screen_name)
{
string updateStatusURL = "https://api.twitter.com/1.1/users/show.json?screen_name=" + user_screen_name;
string outUrl;
string OAuthHeaderPOST = OAuthUtility.GetAuthorizationHeaderForPost_OR_QueryParameterForGET(new Uri(updateStatusURL), callbackUrl, httpMethod.POST.ToString(), oauth_consumer_key, oauth_consumer_secret, oauth_access_token, oauth_token_secret, out outUrl);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(outUrl);
request.Method = httpMethod.GET.ToString();
request.Headers["Authorization"] = OAuthHeaderPOST;
return request;
}
}
And afterwards you will have to read the results of the request.
Hope it helps!

Encrypt URL in asp.net

My site is in asp.net 3.5 and C#. I am sending link to my user through mail, now I want to send each user a specific URL. So instead of sending the clear text I want to send link with encrypted string URL, which I will decrypt on my home page.
Like instead of www.mysite.aspx\mypage?userId=12 I'll send www.mysite.aspx\mypage?UserId=)#kasd12
and the same I'll decrypt on my page so that I'll get the userId = 12.
Please let me know if my approach is correct and not and how can I encrypt & decrypt the string in simplest and easier manner.
isn't it more appropiate to generate a temporary access key?
Generate a random string value instead of encryption/decryption :) And make it at least 6 or 7 characters long. Store the the value in the database and once the value is received through a query string, run a SQL query to do whatever for the corresponding row :)
Page_Load()
string x = Request.QueryString["UserID"];
SqlCommand x = new SqlCommand("UPDATE UserTable SET UserStatus='Activated' WHERE RandomKey='x'", connection);
I'm pretty sure this code project page is what your after. Its basically a HttpModule that can be used to encrypt querystrings.

Resources