Monthly Archives: September 2014

CSRF and PageMethods / WebMethods

If you’re interested in the code shown in this post, right-click here, click Save As and rename to zip.

In short, Cross-Site Request Forgery (CSRF) attack is one that uses a malicious website to send requests to a targeted website that the user is logged into. For example, the user is logged-in to a bank in one browser tab and uses a second tab to view a different (malicious) website, sent via email or social network. The malicious website invokes actions on the target website, using the fact that the user is logged into it in the first tab. Example for such attacks can be found on the internet (see Wikipedia).

CSRF prevention is quite demanding. If you follow the Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet you’ll noticed that the general recommendation is to use a “Synchronizer Token Pattern”. There are other prevention techniques listed but also specified are their disadvantages. The Synchronizer Token Pattern requires that request calls will have an anti-forgery token that will be tested on the server side. A lack of such token or an invalid one will result in a failure of the request.

ASP.NET WebForms are supposed to prevent attacks on two levels: “full postback” levels (which you can read here how to accomplish) and on Ajax calls. According to a post made by Scott Gu several years ago, ASP.NET Ajax web methods are CSRF-safe because they handle only POST by default (which is known today as an insufficient CSRF prevention technique) and because they require a content type header of application/json, which are not added by the browser when using html element tags for such an attack. It is more than possible that I am missing something but in my tests I found this claim to be incorrect. I found no problem invoking a web method from a different website using GET from a script tag. Unfortunately ASP.NET didn’t detect nor raise any problem doing so (as will be shown below).

Therefore I was looking into adding a Synchronizer Token Pattern onto the requests. In order not to add token to every server side methods’ arguments, one technique is to add the CSRF token to your request’s headers. There are several advantages to using this technique: you don’t need to specify a token argument in your server and client method calls, and more importantly: you do not need to modify your existing website web methods. If you’re using MVC and jQuery Ajax you can achieve this quite easily as can be shown here or you can follow this guide. However if you are using PageMethods/WebMethods, the Synchronizer Token Pattern can prove more difficult as you’ll need to intercept the http request to add that header.

Test websites
I set up a couple of websites for this solution. One website simulates the bank and the other simulates an attacker.
The bank website has PageMethods and WebMethods. The reasons I am setting up a PageMethod and a WebMethod are to demonstrate both and because the CSRF token is stored in session and for WebMethods session is not available by default (as opposed to PageMethods).

public partial class _Default : System.Web.UI.Page
    public static bool TransferMoney(int fromAccount, int toAccount, int amount)
        // logic

        return true;
public class MyWebService  : System.Web.Services.WebService {

    public bool TransferMoney(int fromAccount, int toAccount, int amount)
        // logic

        return true;

A bank sample code for invoking both methods:

<asp:ScriptManager runat="server" EnablePageMethods="true">
        <asp:ServiceReference Path="~/MyWebService.asmx" />
<input type="button" value='WebMethod' onclick='useWebMethod()' />
<input type="button" value='PageMethod' onclick='usePageMethod()' />
<script type="text/javascript">
    function usePageMethod() {
        PageMethods.TransferMoney(123, 456, 789, function (result) {


    function useWebMethod() {
        MyWebService.TransferMoney(123, 456, 789, function (result) {


This is how it looks like:

The web.config allows invoking via GET (note: you don’t have to allow GET; I’m deliberately allowing a GET to demonstrate an easy CSRF attack and how this solution attempts to block such calls):

    <compilation debug="true" targetFramework="4.0" />
        <add name="HttpGet" />
        <add name="HttpPost" />

The attacking website is quite simple and demonstrates a GET attack via the script tag:

<script src='http://localhost:55555/MyWebsite/MyWebService.asmx/TransferMoney?fromAccount=111&toAccount=222&amount=333'></script>
<script src='http://localhost:55555/MyWebsite/Default.aspx/TransferMoney?fromAccount=111&toAccount=222&amount=333'></script>

As you can see, running the attacking website easily calls the bank’s WebMethod:

The prevention technique is as follows:

  1. When the page is rendered, generate a unique token which will be inserted into the session.
  2. On the client side, add the token to the request headers.
  3. On the server side, validate the token.

Step 1: Generate a CSRF validation token and store it in the session.

public static class Utils
    public static string GenerateToken()
        var token = Guid.NewGuid().ToString();
        HttpContext.Current.Session["RequestVerificationToken"] = token;
        return token;
  • Line 5: I used a Guid, but any unique token generation function can be used here.
  • Line 6: Insert token into the session. Note: you might consider ensuring having a Session.

Note: you might have to “smarten up” this method to handle error pages and internal redirects as you might want to skip token generation in certain circumstances. You may also check if a token already exists prior to generating one.

Step 2: Add the token to client requests.

<script type="text/javascript">
    // CSRF
    Sys.Net.WebRequestManager.add_invokingRequest(function (sender, networkRequestEventArgs) {
        var request = networkRequestEventArgs.get_webRequest();
        var headers = request.get_headers();
        headers['RequestVerificationToken'] = '<%= Utils.GenerateToken() %>';

    function usePageMethod() {
        PageMethods.TransferMoney(123, 456, 789, function (result) {


    function useWebMethod() {
        MyWebService.TransferMoney(123, 456, 789, function (result) {

  • Line 3: Luckily ASP.NET provides a client side event to intercept the outgoing request.
  • Line 6: Add the token to the request headers. The server side call to Utils.GenerateToken() executes on the server side as shown above, and the token is rendered onto the client to be used here.

Step 3: Validate the token on the server side.

To analyze and validate the request on the server side, we can use the Global.asax file and the Application_AcquireRequestState event (which is supposed to have the Session object available by now). You may choose a different location to validate the request.

protected void Application_AcquireRequestState(object sender, EventArgs e)
    var context = HttpContext.Current;
    HttpRequest request = context.Request;

    // ensure path exists
    if (string.IsNullOrWhiteSpace(request.PathInfo))

    if (context.Session == null)
    // get session token
    var sessionToken = context.Session["RequestVerificationToken"] as string;

    // get header token
    var token = request.Headers["RequestVerificationToken"];

    // validate
    if (sessionToken == null || sessionToken != token)
        context.Response.StatusCode = 403;
  • Line 7: Ensure we’re operating on WebMethods/PageMethods. For urls such as: http://localhost:55555/MyWebsite/Default.aspx/TransferMoney, TransferMoney is the PathInfo.
  • Line 10: We must have the session available to retrieve the token from. You may want to add an exception here too if the session is missing.
  • Line 14: Retrieve the session token.
  • Line 17: Retrieve the client request’s token.
  • Line 20: Token validation.
  • Line 22-24: Decide what to do if the token is invalid. One option would be to return a 403 Forbidden (you can also customize the text or provide a subcode).

When running our bank website now invoking the PageMethods you can see the token (the asmx WebMethod at this point doesn’t have a Session so it can’t be properly validated). Note that the request ended successfully.

When running the attacking website, note that the PageMethod was blocked, but not the asmx WebMethod.


  • The first TransferMoney is the unblocked asmx WebMethod, as it lacks Session support vital to retrieve the cookie.
  • The second TransferMoney was blocked as desired.

Finally we need to add Session support to our asmx WebMethods. Instead of going thru all the website’s asmx WebMethods modifying them to require the Session, we can add the session from a single location in our Global.asax file:

private static readonly HashSet<string> allowedPathInfo = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) { "/js", "/jsdebug" };
protected void Application_BeginRequest(Object sender, EventArgs e)
    if (".asmx".Equals(Context.Request.CurrentExecutionFilePathExtension) && !allowedPathInfo.Contains(Context.Request.PathInfo))
  • Line 1: Paths that we would like to exclude can be listed here. asmx js and jsdebug paths render the client side proxies and do not need to be validated.
  • Line 4-5: If asmx and not js/jsdebug, add the Session requirement so it becomes available for the token validation later on.

Now running the attacking website we can see that our asmx was also blocked:


  • Naturally you can add additional hardening as you see fit.
  • Also important is to note that using the Session and the suggested technique has an issue when working in the same website with several tabs, as each time the Utils.GenerateToken is called it replaces the token in the session. So might consider also checking the referrer header to see whether to issue a warning instead of throwing an exception, or simply generating the token on the server side only once (i.e. checking if the token exists and only if not then generate it.)
  • Consider adding a “turn off” switch to CSRF in case you run into situations that you need cancel this validation.
  • Moreover: consider creating an attribute over methods or web services that will allow skipping the validation. You can never know when these might come in handy.

Posted by on 07/09/2014 in Software Development


Tags: , ,