Efficacy and Purpose of AntiForgery.Validate in AJAX calls - ajax

The MVC4 SPA template has a ValidateHttpAntiForgeryTokenAttribute class with a ValidateRequestHeader function that parses out 2 halves of a RequestVerificationToken header constructed by the client when it assembles the data for an AJAX call. The client AJAX code gets its value from a field on the form that combines a form token and a cookie token. I feel like combining these into a single value loses sight of the purpose of an AntiForgery token; they were separate for a reason. Do we gain any security by using anti-forgery tokens in this way?
Server .vbhtml code (Razor-based):
Public Function GetAntiForgeryToken() As String
Dim cookieToken As String = String.Empty
Dim formToken As String = String.Empty
AntiForgery.GetTokens(Nothing, cookieToken, formToken)
Return cookieToken & ":" & formToken
End Function
...
#If User.Identity.IsAuthenticated Then
#<input id="antiForgeryToken" type="hidden" value="#GetAntiForgeryToken()" />
End If
Client AJAX code:
function ajaxRequest(type, url, data, dataType) { // Ajax helper
var options = {
dataType: dataType || "json",
contentType: "application/json",
cache: false,
type: type,
data: data ? ko.toJSON(data) : null
};
var antiForgeryToken = $("#antiForgeryToken").val();
if (antiForgeryToken) {
options.headers = {
'RequestVerificationToken': antiForgeryToken
}
}
return $.ajax(url, options);
}
Server validation code:
Private Sub ValidateRequestHeader(request As HttpRequestMessage)
Dim cookieToken As String = String.Empty
Dim formToken As String = String.Empty
Dim tokenHeaders As IEnumerable(Of String) = Nothing
If request.Headers.TryGetValues("RequestVerificationToken", tokenHeaders) Then
Dim tokenValue As String = tokenHeaders.FirstOrDefault()
If Not String.IsNullOrEmpty(tokenValue) Then
Dim tokens As String() = tokenValue.Split(":"c)
If tokens.Length = 2 Then
cookieToken = tokens(0).Trim()
formToken = tokens(1).Trim()
End If
End If
End If
AntiForgery.Validate(cookieToken, formToken)
End Sub
What prevents a client from picking any arbitrary pair of cookieToken and formToken that was used in the past, and submitting them together in an AJAX call to get it to go through? Isn't that what anti-forgery functions are supposed to prevent? Is this just a lot of stupid overhead that doesn't improve security, or is there a piece of it that I'm missing?

What prevents a client from picking any arbitrary pair of cookieToken and formToken that was used in the past, and submitting them together in an AJAX call to get it to go through? Isn't that what anti-forgery functions are supposed to prevent? Is this just a lot of stupid overhead that doesn't improve security, or is there a piece of it that I'm missing?
The anti-forgery token is not designed to prevent a Replay Attack. This is where old values are reused to create another request where the aim is to fool the target machine into accepting a valid instruction from the past.
The anti-forgery token is designed to prevent Cross Site Request Forgery attacks.
A simple example is as follows:
You're logged into bank.com on one tab.
You get an email to view a funny video by clicking on a link that redirects you to
evil.com
The web page on evil.com contains a hidden form that is submited by JavaScript to bank.com/make_money_transfer
As you are logged into bank.com and your cookies are sent by the browser, bank.com thinks that you made the request and initiates the money transfer without your knowledge, because from the server's point of view, all is well.
The token is designed to prevent this by having something included in the request payload that cannot be auto submitted by a domain that is not the current domain. Due to the Same Origin Policy another domain cannot access the token value and therefore cannot send a legitimate request via a hidden form, or by any other means. The token is unique per log in session so the attacker could not get a valid combination of token and cookie which can be sent to the server.
Looking at the source code for TokenValidator.cs (albeit C# instead of VB.NET) we can see that the ValidateTokens method checks that the username encoded in the token matches the one of the current HTTP request:
if (!String.Equals(fieldToken.Username, currentUsername, (useCaseSensitiveUsernameComparison) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
{
throw HttpAntiForgeryException.CreateUsernameMismatchException(fieldToken.Username, currentUsername);
}
This is what will stop an attacker grabbing an old version of the form field value and submitting that in their CSRF attack - their encoded username will not match the logged in user of their victim.

Related

Read Session Value In .Net Core Web Api [duplicate]

This question already has answers here:
Accessing Session Using ASP.NET Web API
(13 answers)
Closed 3 years ago.
In my Web api when a user login successfully I set session with some values like
HttpContext.Session.SetObject("CurrentUserID", user.Id);
HttpContext.Session.SetObject("CurrentUserRoles",user.Roles);
and just return token and some values to save in cookie
return Ok(new
{
Id = user.Id,
Username = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
Token = tokenString,
role = user.Roles
});
But when the client hit api action which has this line
List<string> userRolesList = HttpContext.Session.GetObject<List<string>>("CurrentUserRoles");
Then always get null value even I have added session inside Startup >Configure
like
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
and ConfigureService also
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds( 60 * 60);
options.Cookie.HttpOnly = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
but does work still... Please help.
HTTP is a stateless protocol. Sessions are fake state, enabled by both a server-side and client-side component. The client-side component is a cookie: specifically a Set-Cookie response header. In order for the session to be restored on the next request, the value of this Set-Cookie response header must be sent back via the Cookie request header with each request. A web browser (the client) will do all this automatically, including persisting the cookie locally. However, a thin client like HttpClient, Postman, etc. will not. You would need to independently persist the cookie from the response header and then attach it to each request via the Cookie header in order to maintain the session between requests.
That said, this is a major reason why APIs typically do not, and honestly should not make use of sessions. It's simply a pattern that doesn't make much sense in an API context, and only adds a potential point of failure, since clients must pay attention to the cookie headers, and take manual actions to handle the cookies.

springsecurity: using CSRF token obtained from CrossOrigin REST service to POST data

I have this REST service on domainA:
#CrossOrigin(origins={"http://domainB"})
#RequestMapping(value="/csrf", method=RequestMethod.GET)
public #ResponseBody
CsrfToken getCsrfToken(HttpServletRequest request) {
CsrfToken token = (CsrfToken)request.getAttribute(CsrfToken.class.getName());
return token;
}
Then I want to obtain CSRF token from above service (by using javascript on domainB) and add it to a <form action="http://domainA> on domainB and send this form to domainA (it is a simple form that has a submit button).
The problem is I get HTTP Status 403 – Forbidden.
As the opposite: when I manually set the _csrf value (obtained manually in the other browser tab pointing to domainA/csrf) in the <form action="http://domainA> and submit it then it works.
The difference which I noticed is that when I manually refresh browser's tab domainA/csrf then I get constantly the same value (and this value works), but when the domainA/csrf is obtained by the javascript from the domainB it is each time different and when using it - it does not work.
Can anyone help?
domainA: www.fridayweekend.com/rest/csrf
domainB: www.friwee.com/register (hit F12 and observe what call to www.fridayweekend.com/rest/csrf returns....)
As #dur said - the problem was in the JavaScript code. I used:
$.getJSON(domainA/csrf, callback)
which was ending up each time with a new session and a new CSRF token for it.
The solution was to use cors_ajax_call function except $.getJSON, defined as below:
var cors_ajax_call = function(address, callback){
$.ajax({
url: address,
context: document.body,
xhrFields: {
withCredentials: true
}
}).success(callback);
}
Thank you for your input! Hope this help someone :)

How to protect against CSRF on a static site?

I have a static website, being served from a CDN, that communicates with an API via AJAX. How do I protect against CSRF?
Since I do not have control over how the static website is served, I cannot generate a CSRF token when someone loads my static website (and insert the token into forms or send it with my AJAX requests). I could create a GET endpoint to retrieve the token, but it seems like an attacker could simply access that endpoint and use the token it provides?
Is there an effective way to prevent against CSRF with this stack?
Additional details: authentication is completely separate here. Some of the API requests for which I want CSRF protection are authenticated endpoints, and some are public POST requests (but I want to confirm that they are coming from my site, not someone else's)
I could create a GET endpoint to retrieve the token, but it seems like an attacker could simply access that endpoint and use the token it provides?
Correct. But CSRF tokens are not meant to be secret. They only exist to confirm an action is performed in the order expected by one user (e.g. a form POST only follows a GET request for the form). Even on a dynamic website an attacker could submit their own GET request to a page and parse out the CSRF token embedded in a form.
From OWASP:
CSRF is an attack that tricks the victim into submitting a malicious request. It inherits the identity and privileges of the victim to perform an undesired function on the victim's behalf.
It's perfectly valid to make an initial GET request on page load to get a fresh token and then submit it with the request performing an action.
If you want to confirm the identity of the person making the request you'll need authentication, which is a separate concern from CSRF.
My solution is as follows
Client [static html]
<script>
// Call script to GET Token and add to the form
fetch('https:/mysite/csrf.php')
.then(resp => resp.json())
.then(resp => {
if (resp.token) {
const csrf = document.createElement('input');
csrf.name = "csrf";
csrf.type = "hidden";
csrf.value = resp.token;
document.forms[0].appendChild(csrf);
}
});
</script>
The above can be modified to target a pre-existing csrf field. I use this to add to may pages with forms. The script assumes the first form on the page is the target so this would also need to be changed if required.
On the server to generate the CSRF (Using PHP : assumes > 7)
[CSRFTOKEN is defined in a config file. Example]
define('CSRFTOKEN','__csrftoken');
Server:
$root_domain = $_SERVER['HTTP_HOST'] ?? false;
$referrer = $_SERVER['HTTP_REFERER'] ?? false;
// Check that script was called by page from same origin
// and generate token if valid. Save token in SESSION and
// return to client
$token = false;
if ($root_domain &&
$referrer &&
parse_url($referrer, PHP_URL_HOST) == $root_domain) {
$token = bin2hex(random_bytes(16));
$_SESSION[CSRFTOKEN] = $token;
}
header('Content-Type: application/json');
die(json_encode(['token' => $token]));
Finally in the code that processes the form
session_start();
// Included for clarity - this would typically be in a config
define('CSRFTOKEN', '__csrftoken');
$root_domain = $_SERVER['HTTP_HOST'] ?? false;
$referrer = parse_url($_SERVER['HTTP_REFERER'] ?? '', PHP_URL_HOST);
// Check submission was from same origin
if ($root_domain !== $referrer) {
// Invalid attempt
die();
}
// Extract and validate token
$token = $_POST[CSRFTOKEN] ?? false;
$sessionToken = $_SESSION[CSRFTOKEN] ?? false;
if (!empty($token) && $token === $sessionToken) {
// Request is valid so process it
}
// Invalidate the token
$_SESSION[CSRFTOKEN] = false;
unset($_SESSION[CSRFTOKEN]);
There is very good explanation for same, Please check
https://cloudunder.io/blog/csrf-token/
from my understanding it seems static site won't face any issue with CSRF due to CORS restriction, if we have added X-Requested-With flag.
There is one more issue i would like to highlight here, How to protect your api which is getting called from Mobile app as well as Static site?
As api is publicly exposed and you want to make sure only allowed user's should be calling it.
There is some check we can add at our API service layer for same
1) For AJAX request(From Static site) check for requesting domain, so only allowed sites can access it
2) For Mobile request use HMAC token, read more here
http://googleweblight.com/i?u=http://www.9bitstudios.com/2013/07/hmac-rest-api-security/&hl=en-IN

Is Ajax POST an acceptable technique for changing server state?

I am designing a new website and I am considering using AJAX post requests for better user experience. Is using AJAX POST requests for changing server state an acceptable design practice? Are their any security concerns in using AJAX POST requests? Is it recommended to restrict the server state changes to HTTP POST only?
EDIT
I am using ASP.NET MVC web framework for implementation.
Post, Put, Patch and Delete (although the last one is barely used) are all request types that traditionally alter the server state.
In order to answer your question, it is important to consider which framework you are using, as each one might have different best practices.
From a technical point of view, they all do practically the same, they only have different semantic meanings and conventions attached to them. If you were to use Post for everything, I doubt that anybody would complain
Post back is traditional way to doing things on web application where whole page re-load on form submission. In this approach most of the codes runs at sever side.
AJAX is a modern way to building web application where most of the code runs at client side for better performance and user experience. Only required data post to server instead of posting whole page.
Post back & Ajax both create HTTP request so it is not right to say one is less secure than other. In both request attacker can inject script using cross-site scripting (XSS) or CSRF (Cross-site request forgery).
AJAX calls are itself protect CSRF using “Common Origin Policy” when CORS is disabled and JSONP requests are blocked. To prevent CSRF attack one step ahead, you can implement Anti Forgery token like in MVC framework. AJAX calls can be called from web application as well as from MVC.
In MVC, #html.antiforgerytoken() can be called on form load which store one key in hidden field and other key in cookie and using ValidateAntiForgeryToken filter, we can validate that CSRF token. The form token can be a problem for AJAX requests, because an AJAX request might send JSON data, not HTML form data. One solution is to send the tokens in a custom HTTP header.
Here is sample code snippet for more details…
Sample Server side Code to generate Anti forgery token.
/// <summary>
/// Get Anti Forgery token
/// </summary>
/// <returns></returns>
public static string GetAntiXsrfToken()
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
var responseCookie = new HttpCookie("__AntiXsrfToken")
{
HttpOnly = true,
Value = cookieToken
};
if (FormsAuthentication.RequireSSL && HttpContext.Current.Request.IsSecureConnection)
{
responseCookie.Secure = true;
}
HttpContext.Current.Response.Cookies.Set(responseCookie);
return formToken;
}
Sample Server side Code to validate Anti forgery token.
/// <summary>
/// Validate Anti Forgery token coming from secure cookie & request header
/// </summary>
static void ValidateAntiXsrfToken()
{
string tokenHeader, tokenCookie;
try
{
// get header token
tokenHeader = HttpContext.Current.Request.Headers.Get("__RequestVerificationToken");
// get cookie token
var requestCookie = HttpContext.Current.Request.Cookies["__AntiXsrfToken"];
tokenCookie = requestCookie.Value;
AntiForgery.Validate(tokenCookie, tokenHeader);
}
catch
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.StatusCode = 403;
HttpContext.Current.Response.End();
}
}
Sample code to get Anti forgery token (one part) and save into hidden field
<input name="__RequestVerificationToken" type="hidden" value="<%= CommonUtils.GetAntiXsrfToken() %>" />
Sample client side code to pass one part to Anti Forgery token into request header from hidden field and another part will go automatically from client cookie if request is generated from same origin.
function CallServer(baseUrl, methodName, MethodArgument, callback) {
$.ajax({
type: "POST",
url: baseUrl + methodName,
data: MethodArgument,
contentType: "application/json; charset=utf-8",
async: false,
dataType: "json",
headers: {'__RequestVerificationToken': $("input[name='__RequestVerificationToken']").val()
},
success: function (data) {
if (callback != undefined && typeof (callback) === "function") {
callback(data.d);
}
},
error: function (data) {
if (data.status == 401 || data.status == 403)
window.location.href = "../Common/accessdenied";
else if (data.status == 419) {
displayUserMessage(commonMessage.RE_SESSIONINFO_NOT_FOUND, true);
window.location.href = "../Common/logout";
}
else
displayUserMessage(commonMessage.SERVICE_NOT_RESPONDING, true);
}
});
}
Finally, Call ValidateAntiXsrfToken() function before processing the each AJAX request at server side.
You can find more details here…
Which one is better? Ajax post or page post[Controller httppost] when only one form is there in a page?
http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-csrf-attacks
https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet

CSRF for Ajax request across applications in one Domain

We have MVC 4 application which is hosted on Web Farm. Site has 5 applications hosted under one domain. These applications communicate with each other. We are implementing Cross Site Request Forgery for our application. We have added AntiForgeyToken(#Html.AntiForgeryToken()) on Layout page. When we try to post data actions across applications using Ajax request, we are facing below exception-
Exception:
The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster.
For Ajax request we have added “__RequestVerificationToken” value into prefilter as shown below-
Client side implementation:
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
if (options.type.toLowerCase() == "post") {
if ($('input[name="__RequestVerificationToken"]').length > 0)
jqXHR.setRequestHeader('__RequestVerificationToken',
$('input[name="__RequestVerificationToken"]').val());
}});
On Server side we validated this token as shown below-
string cookie = "";
Dictionary<string, object> cookieCollection = new Dictionary<string, object>();
foreach (var key in HttpContext.Current.Request.Cookies.AllKeys)
{
cookieCollection.Add(key, (HttpContext.Current.Request.Cookies[key]));
}
var res= cookieCollection.Where(x => x.Key.Contains("RequestVerificationToken")).First();
cookie = ((System.Web.HttpCookie)(res.Value)).Value;
string formToken = Convert.ToString(HttpContext.Current.Request.Headers["__RequestVerificationToken"]);
if(string.IsNullOrEmpty(formToken))
{
//To validate HTTP Post request
AntiForgery.Validate();
}
else
{
//To validate Ajax request
AntiForgery.Validate(cookie, formToken);
}
Other configurations which we have done are as below-
We have machine key in config which is same for all applications as well as on all web servers.
We have set AntiForgeryConfig.CookieName = "__RequestVerificationToken" + "_XYZ" cookie name across all applications which is mandatory to access token across applications.
Changes which we tried to resolve this issue-
We tried to post “__RequestVerificationToken” inside each ajax request’s data so that we can access it using Request.Form but no success on this.
We have verified that Content-Type header is appearing with each request.
Please suggest if you have any other way to implement CSRF functionality for Ajax POST requests across multiple applications.

Resources