33

As part of the ASP.NET MVC 2 Beta 2 update, JSON GET requests are disallowed by default. It appears that you need to set the JsonRequestBehavior field to JsonRequestBehavior.AllowGet before returning a JsonResult object from your controller.

public JsonResult IsEmailValid(...)
{
    JsonResult result = new JsonResult();

    result.Data = ..... ;
    result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;

    return result;
}

What is the reasoning behind this? If I am using JSON GET to try and do some remote validation, should I be using a different technique instead?

sharptooth
  • 167,383
  • 100
  • 513
  • 979
Jedidja
  • 16,610
  • 17
  • 73
  • 112
  • 2
    Is "JsonRequestBehavior" property have been added only in mvc2. Becoz I tried searching this on mvc 1.0 and i couldn't found out. – Santhosh Dec 17 '09 at 11:09
  • Yes, it was added in v2. At least, the 1.0 docs here (http://msdn.microsoft.com/en-us/library/system.web.mvc.jsonresult_members.aspx) do not list it. – Jedidja Dec 17 '09 at 12:34
  • Honestly it was just a very, very bad decision by Microsoft. – Chris Marisic Jan 02 '15 at 19:55

4 Answers4

23

The reason for the DenyGet default is on MSDN with a link to Phil Haack's blog for further details. Looks like a Cross-Site scripting vulnerability.

TJB
  • 13,367
  • 4
  • 34
  • 46
Chris Shaffer
  • 32,199
  • 5
  • 49
  • 61
  • so if I JsonRequestBehavior.AllowGet ... will this vulnerability continue to exist or it produces some protection or what ? If I need to return Json data then I would return Json data without Explicity repeat myself ???!!!! – Jalal El-Shaer Jan 12 '10 at 09:38
  • 1
    @jalchr: using AllowGet the vulnerability will still exist, you are just telling the framework that you don't care (i.e., you are OK with it, because you don't send sensitive data) – Pawel Krakowiak Feb 12 '11 at 09:06
  • it's a CSRF Vulnerability, not Cross-Site scripting! – actual_kangaroo Sep 17 '14 at 02:11
8

HTTP GET is disabled by default as part of ASP.NET's Cross-Site Request Forgery (CSRF/XSRF) protections. If your web services accept GET requests, then they can be vulnerable to 3rd party sites making requests via <script /> tags and potentially harvesting the response by modifying JavaScript setters.

It is worth noting however that disabling GET requests is not enough to prevent CSRF attacks, nor is it the only way to protect your service against the type of attack outlined above. See Robust Defenses for Cross-Site Request Forgery for a good analysis of the different attack vectors and how to protect against them.

Annabelle
  • 10,596
  • 5
  • 27
  • 26
3

I also had your problem when I migrated my MVC website from Visual Studio 2008 to Visual Studio 2010.

The main aspx is below, it has an ViewData which calls a Category Controller in order to fill up ViewData["Categories"] with SelectList collection. There's also a script to call a Subcategory Controller to fill up the second combo with javascript. Now I was able to fix it adding up AlloGet attribute on this second controller.

Here's the aspx and javascript

<head>
<script type="text/javascript" src="../../Scripts/jquery-1.4.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("#CategoryId").change(function () {

    var categoryId = $(this)[0].value;

    $("#ctl00_MainContent_SubcategoryId").empty();
    $("#ctl00_MainContent_SubcategoryId").append("<option value=''>-- select a category --</option>");
    var url = "/Subcategory/Subcategories/" + categoryId;

    $.getJSON(url, { "selectedItem": "" }, function (data) {
        $.each(data, function (index, optionData) {
            $("#ctl00_MainContent_SubcategoryId").append("<option value='" + optionData.SubcategoryId + "'>" + optionData.SubcategoryName + "</option>");
        });
        //feed our hidden html field
        var selected = $("#chosenSubcategory") ? $("#chosenSubcategory").val() : '';
        $("#ctl00_MainContent_SubcategoryId").val(selected);

    });

}).change();
});
</script>
<body>
<% using (Html.BeginForm()) {%>
<label for="CategoryId">Category:</label></td>
<%= Html.DropDownList("CategoryId", (SelectList)ViewData["Categories"], "--categories--") %>
<%= Html.ValidationMessage("category","*") %>
<br/>
<label class="formlabel" for="SubcategoryId">Subcategory:</label><div id="subcategoryDiv"></div>
<%=Html.Hidden("chosenSubcategory", TempData["subcategory"])%>
<select id="SubcategoryId" runat="server">
</select><%= Html.ValidationMessage("subcategory", "*")%>
<input type="submit" value="Save" />
<%}%>                

here's my controller for subcategories

public class SubcategoryController : Controller
{
    private MyEntities db = new MyEntities();

    public int SubcategoryId { get; set; }
    public int SubcategoryName { get; set; }
    public JsonResult Subcategories(int? categoryId)
    {
        try
        {
            if (!categoryId.HasValue)
                categoryId = Convert.ToInt32(RouteData.Values["id"]);
            var subcategories = (from c in db.Subcategories.Include("Categories")
                                 where c.Categories.CategoryId == categoryId && c.Active && !c.Deleted
                                  && c.Categories.Active && !c.Categories.Deleted
                                 orderby c.SubcategoryName
                                 select new { SubcategoryId = c.SubcategoryId, SubcategoryName = c.SubcategoryName }
            );
            //just added the allow get attribute
            return this.Json(subcategories, JsonRequestBehavior.AllowGet);
        }
        catch { return this.Json(null); }

    }
Junior Mayhé
  • 16,144
  • 26
  • 115
  • 161
2

I don't know if this is the reason they chose to change that default, but here's my experience:

When some browsers see a GET, they think they can cache the result. Since AJAX is usually used for small requests to get the most up-to-date information from the server, caching these results usually ends up causing unexpected behavior. If you know that a given input will return the same result every time (e.g. "password" cannot be used as a password, no matter when you ask me), then a GET is just fine, and browser caching can actually improve performance in case someone tries validating the same input multiple times. If, on the other hand, you expect a different answer depending on the current state of the server-side data ("myfavoriteusername" may have been available 2 minutes ago, but it's been taken since then), you should use POST to avoid having the browser thinking that the first response is still the correct one.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • Very interesting idea... do you have any links to which browsers might try to cache a GET result and under what circumstances? – Jedidja Oct 26 '09 at 16:46
  • 1
    The most prevalent example is Internet Explorer (http://greenash.net.au/posts/thoughts/an-ie-ajax-gotcha-page-caching), but it's not so much a question of which browsers do it as the fact that the HTTP specification says specifically that data retrieved with a GET request is cacheable. The ASP.NET's AJAX framework may set headers to tell the browser that the content shouldn't be cached, though, so that may not have anything to do with this particular issue. I ran into this problem when I was writing my own javascript and servlet pair to communicate via AJAX. – StriplingWarrior Oct 27 '09 at 20:52
  • I have run into the caching issue several times now and using POST (even disregarding the security issues) is definitely the way to go. – Jedidja Jul 22 '10 at 11:25