├── LICENSE ├── README.md └── SystemController.cs /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Phil Haack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASP.NET MVC Controller Action Security Checker 2 | 3 | This is a [Drop in ASP.NET MVC Controller and Action](https://raw.githubusercontent.com/Haacked/aspnetmvc-action-checker/master/SystemController.cs) that displays any actions that modify resources (HTTP POST, PUT, DELETE, and PATCH) that do not have an Authorize or ValidateAniForgeryToken attributes applied. 4 | 5 | ## Usage 6 | 7 | Add the [`SystemController` file](https://raw.githubusercontent.com/Haacked/aspnetmvc-action-checker/master/SystemController.cs) to your ASP.NET MVC project, make sure there's a route that'll reach it, and then visit it in a local instance of your site. It only shows up for localhost requests for security reasons. 8 | -------------------------------------------------------------------------------- /SystemController.cs: -------------------------------------------------------------------------------- 1 | /* Copyright Phil Haack 2 | * 3 | * Licensed under the MIT License: https://github.com/Haacked/aspnetmvc-action-checker/blob/master/LICENSE 4 | */ 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Text; 11 | using System.Web.Mvc; 12 | 13 | public class SystemController : Controller 14 | { 15 | protected override void OnActionExecuting(ActionExecutingContext filterContext) 16 | { 17 | if (!ControllerContext.HttpContext.Request.IsLocal) 18 | { 19 | filterContext.HttpContext.Response.StatusCode = 404; 20 | filterContext.HttpContext.Response.Flush(); 21 | filterContext.HttpContext.Response.End(); 22 | return; 23 | } 24 | base.OnActionExecuting(filterContext); 25 | } 26 | 27 | public ActionResult Index(string ignore) 28 | { 29 | ignore = ignore ?? ""; 30 | if (!ControllerContext.HttpContext.Request.IsLocal) 31 | return HttpNotFound(); 32 | 33 | var assembly = Assembly.GetExecutingAssembly(); 34 | 35 | var controllers = assembly.GetTypes() 36 | .Where(type => typeof(Controller).IsAssignableFrom(type)) //filter controllers 37 | .Select(type => new ReflectedControllerDescriptor(type)); 38 | 39 | var controllerIssues = new Dictionary>>(); 40 | 41 | foreach (var controller in controllers) 42 | { 43 | var actions = controller.GetCanonicalActions(); 44 | foreach(var action in actions) 45 | { 46 | var attributes = action.GetCustomAttributes(true); 47 | if (!ignore.Contains("antiforgery")) 48 | { 49 | CheckAction(() => (ContainsHttpMutateAttribute(attributes) 50 | && !ContainsAttribute(attributes)) 51 | , controller 52 | , action 53 | , "HTTP attribute that could mutate a resource does not have a [ValidateAntiForgeryToken] attribute applied." 54 | , controllerIssues); 55 | } 56 | 57 | if (!ignore.Contains("authorization")) 58 | { 59 | CheckAction(() => (ContainsHttpMutateAttribute(attributes) 60 | && !ContainsAttribute(attributes) 61 | && !ContainsAttribute(controller.GetCustomAttributes(true))) 62 | , controller 63 | , action 64 | , "HTTP attribute that could mutate a resource does not have an [Authorize] attribute applied. You may also want to apply the attribute to the GET action (if any) that corresponds to this action." 65 | , controllerIssues); 66 | } 67 | } 68 | } 69 | 70 | var response = new StringBuilder(); 71 | response.Append(""); 72 | response.Append("System Check"); 73 | response.Append(""); 77 | response.Append(""); 78 | response.Append(""); 79 | response.Append("

System Check: Potential Issues Found

"); 80 | response.Append($"

Reflecting over controllers and actions in the assembly {assembly.FullName} found the following potential issues. Note that some of these issues may be by design. For example, you probably DO NOT want an Authorize attribute on a Login action.

"); 81 | response.Append(@"

To ignore antiforgery issues click here.

"); 82 | response.Append(@"

To ignore authorization issues click here

"); 83 | response.Append(@"

To clear the ignore filters click here

"); 84 | 85 | foreach (var controller in controllerIssues.Keys) 86 | { 87 | response.Append($"

{controller}Controller

"); 88 | foreach (var action in controllerIssues[controller]) 89 | { 90 | response.Append($"

{action.Key}

"); 91 | response.Append("
    "); 92 | foreach (var issue in action.Value) 93 | { 94 | response.Append($"
  • {issue}
  • "); 95 | } 96 | response.Append("
"); 97 | 98 | } 99 | } 100 | response.Append(""); 101 | response.Append(""); 102 | 103 | return Content(response.ToString()); 104 | } 105 | 106 | static bool ContainsAttribute(object[] attributes) where T : Attribute 107 | { 108 | return attributes.Any(attr => attr as T != null); 109 | } 110 | 111 | static bool ContainsHttpMutateAttribute(object[] attributes) 112 | { 113 | return ContainsAttribute(attributes) 114 | || ContainsAttribute(attributes) 115 | || ContainsAttribute(attributes) 116 | || ContainsAttribute(attributes); 117 | } 118 | 119 | static void CheckAction(Func check, ControllerDescriptor controller, ActionDescriptor action, string checkTrueMessage, Dictionary>> controllerIssues) 120 | { 121 | if (check != null && check()) 122 | { 123 | if (!controllerIssues.ContainsKey(controller.ControllerName)) 124 | { 125 | controllerIssues[controller.ControllerName] = new Dictionary>(); 126 | } 127 | var actionDictionary = controllerIssues[controller.ControllerName]; 128 | if (!actionDictionary.ContainsKey(action.ActionName)) 129 | { 130 | actionDictionary[action.ActionName] = new List(); 131 | } 132 | actionDictionary[action.ActionName].Add(checkTrueMessage); 133 | } 134 | } 135 | } 136 | --------------------------------------------------------------------------------