On the built-in security mechanisms ASP .NET Core written few articles. Even the official documentation has spaces. In this article we will go through all the main components related to security, and analyze how it works inside.
If you are using the good old ASP .NET, then the information on the internal structure of security components and best practices for their use will be useful for you. Here you will find answers to the following questions: how are modern anti-XSS mechanisms implemented and how to use them correctly in ASP .NET Core? How to work with cookies and what pitfalls can there be? How was the protection mechanism for CSRF rewritten? How to work with cryptographic algorithms? In addition, it tells about the experience of participating in Bug Bounty in searching for vulnerabilities in ASP. NET Core.
Before reading it is recommended to refresh the attacks from the list of OWASP Top 10 .
The prototype of the article is the report of Mikhail Shcherbakov at the DotNext 2017 Moscow Conference. Michael is a Microsoft .NET MVP, a member of the .NET Core Bug Bounty Program, a co-organizer of the .NET community of programmers (the Moscow community is called MskDotNet, a St. Petersburg community is SpbDotNet). At work the last 5 years engaged in security. He worked at Positive Technologies, at Cezurity, now as a consultant working directly with customers, mostly in the same field. Professional interests: static and dynamic code analysis, information security, code debugging automation, .NET CLR internal device research.
In this text, a huge number of pictures from the slides. Caution traffic!
In this article we will talk about attacks and defense mechanisms, namely, about Open Redirect, about changes in the Crypto API in .NET Core, about XSS and Client-side attacks, about setting up CSP, CSRF, CORS and in general about the correct patterns for using cookies.
Microsoft is now open indefinite Bug Bounty program, and I participate in it. If any of you are interested in and engaged in security, you can search for vulnerabilities in the .NET platform, send them a report. They have a pleasant reward, somewhere on average 5-10 thousand they are willing to pay for the verified verified vulnerability. And it's nice that Microsoft is now putting quite a lot of effort into the security of the .NET platform and the .NET Core in particular.
Let's start with the prevention of Open Redirect attacks. I think many people know about him, but they don’t know that this is what is called Open Redirect. Let's look at the demo. I think most of you will understand at once what the attack is about.
So, we have a certain site which is written on ASP.NET, it is called my-telegram.com, we have a certain malefactor who makes the following url on the login form. This is a completely standard ASP.NET login form. The attacker sends this link to the victim and somehow makes this link go.
Usually it is simple, with the help of social engineering or something else. The victim, following the link, sees a familiar site, my-telegram, enters his login and password. These actions are completely valid for the user. After he logs in, he gets the following message:
"Something went wrong, re-enter the password and login." The usual reaction is “I probably entered something wrong somewhere,” and the user repeats the input. But if you noticed, the top line is not my-telegram, but my-telergam, a small game with symbols. What happened? The first login form on the trusted site logged in perfectly normally on this site, then there was a redirect. And in the redirect was the url here on this site. If we do not check the url in the redirect, then it is quite a valid one that sends us to another site, where by using some social engineering it forces us to re-enter the login and password that go to the attacker's site, and the attacker can work with it further.
How to prevent this? ASP.NET has a method in the controller, LocalRedirect, I think everyone has seen it, it just protects against such cases, checks that yours is not an absolute path, that it is relative and can redirect only to this site.
Or you can write your own implementation, if you want it to be defaulted, when the site is wrong, do not throw exceptions, but simply redirect to the home page. This is all protection from this attack. Now you all know what Open Redirect is.
This year, OWASP collected its statistics, you can see the link to GitHub. With all the simplicity of protection, Open Redirect ranks third in popularity in the world.
From the same attack began my participation in the Bug Bounty. My first report, which was fixed and paid for, was just about bypassing the built-in Open redirect mechanism. But now everything is fine there, it is fixed. LocalRedirect really protects 100% of the possible redirect to a third-party site.
The next thing I would like to talk about is Data Protection. The Crypto API had huge changes in .NET Core. In fact, the entire API has been completely rewritten, so let's quickly go through the main points and see what has changed.
First, abandoned the use of Machine Keys. Machine Keys used to be the master key for all cryptography. There were a lot of problems with this, especially in a distributed system, you had to write some kind of keystore so that what was encrypted by one node could be decrypted by another node. There was also no normal possibility to revoke, for example, encrypted cookies. You would need to change Machine Keys. There were so many inconveniences. Now they refused it, made normal high-level API out of the box. Previously, System.Security.Cryptography was a set of tools that made it very easy to make a mistake.
Often the right case is to use some kind of third-party library that already provides high-level APIs, and you don’t need to take care of all the nuances. Now it is in .NET Core out of the box. There is a keystore from the box: you can configure some kind of remote storage where you will have all the keys and all the nodes will go there for them. It easily expands, you can use both existing ones in Redis or Azure, and write your own. Rotation of keys is supported, the key changes every 90 days, and a new key will already be used to create a new ciphertext, and the old key will be used only to decrypt the old one. Isolation of subsystems out of the box is also provided. You can customize, that is, use your custom encryption algorithms, a little later I will show an example.
So, what does the new Crypto API look like?
Quite primitive, easy, since we have a DI in .NET Core, you can simply transfer the IDataProtectionProvider to the controller's constructor, create a protector from it, and use the Protect and Unprotect method for encryption and decryption. Everything, you don't need to take care of anything else. And one more parameter I still wanted to dwell on is the very isolation at the protector level, where each protector with different string identifiers can decrypt the data that only he encrypted. If you want to encrypt data for different users and be sure that they can not decipher someone else's encrypted data in your account, then this is a handy feature that is available from the box.
There is such a cherry:
You can use time-limited protector to create tokens. When you need to determine the lifetime of your key, for example, a token, you simply encrypt it with a time-limited protector, determine its lifetime, and when decrypted, if this time has expired, you just get an exception.
The next point is password storage. As everyone knows, we never store passwords in the clear, the question of how to do this correctly. Again, we have a mechanism in .NET Core, a pattern for properly creating hashes:
First, you use a random generator from the Crypto API to create salt. The salt he created is passed along with the key to the method, which, using the specified hash function, creates you a hash. This hash with the salt you already quietly save to the database and already use this pair further to verify the passwords entered by the user.
All this is configured by the standard method in ConfigureServices. You can set the isolation level for the entire application so that different applications cannot decrypt data to each other if necessary. You can set the life time of the master key, you can configure where your keys will be stored.
You can customize the encryption algorithms you want to use. The default will be AES-256 with the addition of CBC. These are block encryption algorithms, and without an additional signature they are vulnerable to Oracle Padding attacks. This problem was just in .NET in a large framework due to improper use of System.Security.Cryptography. Here, the ciphertext from the box will also be signed by the hash, and in this situation, Padding Oracle is impossible, so you can not think about it. There is another nuance: each ciphertext will have a header that consists of a 32-bit magic header followed by an ID key. This is sometimes useful for debugging when you want to understand what key the text was encrypted with. You just look at the first 20 bytes and see whether they are encrypted with the same or different keys depending on whether they are the same or different.
We turn to a large part of the Client-side vulnerabilities. Let's start with XSS. Everyone knows what XSS is. But still, let's start from the very beginning, because understanding how the Same-origin policy (SOP) works in the browser is very important for a complete understanding of the attacks on the Client-side. Consider this case:
We have a browser, in one tab there is a foo-example site with JavaScript code that performs a GET request to another domain. There is another site for this domain bar.other, which Json returns to this request for us, some product prices. And the question is - will the browser send this request? In this case, it looks logical that it would be good to send this request and get an answer. What is the problem with this?
If in our example we simply change sites, and instead of a data provider, we have some mail.google, and here we have some attacker site that the attacker made us open in our browser, then if this request were sent and the response received, the attacker would receive data from our mailbox, which is bad. Therefore, there is a Same-origin policy that prevents data from being read in such a case. And the correct answer to my initial question is whether the browser will send a request - yes, it will. The browser sends POST / GET requests in this situation, but does not allow JavaScript to read the response of this request, so data leakage cannot occur here due to the Same-origin policy. But actually requests will be sent, it will be important for understanding other Client-side attacks.
What is the Same-origin policy in general? These are certain restrictions that are imposed on a downloaded document or script from one source on interaction with resources from another source.
What is a source, what is origin? Origin is a set of scheme http, https or some other domain and port. That is, a script running on one domain, on one Origin with a port and a schema, cannot receive data from another Origin without any additional actions. How can an attacker still get data, what attacks on the Client-side exist?
The first is the XSS we started with. The essence of the attack - if we can’t get data from another Origin, then let's put our script directly into the site’s Origin, inject our malicious script into a trusted web page, and then we can already make a request from Origin within Origin and receive the page data . This is the essence of XSS.
With all the fame and simplicity, it remains the most popular attack in the world. It is a very good entry point for other attacks. There are many attacks that can be deployed starting with XSS. It is well-known, while there are a lot of projects where developers do not sufficiently use the built-in XSS protection. And do not give it a serious meaning.
Let's see what we have in ASP.NET in order to properly build protection against this type of attack. Take two such synthetic examples of the XSS variant:
The first option: we have a certain script, it has a source, some path is inserted into the source. If an attacker can manipulate this path (load his source) or inject the whole line into the document, respectively, he injects his script. This is the XSS attack.
The second option is when we don’t have the script parameter, but the attacker can inject some code into some html tag, for example, directly into <script>
, or in tag attributes, or somewhere else. The context is not important to us now, it is important that these are two fundamentally different attacks. What to do with it? As I said, the XSS attack is an injection type attack. What to do with injections? Briefly - validate at the entrance, sanitize at the exit.
Validation and filtering data at the entrance, preferably on white lists, if not - then on strict business requirements for this data. Sanitization - depending on the context of the grammar, in which we insert this data so as not to go beyond the limits of one token of this grammar.
Briefly about what we have encoders for sanitization. There is a built-in HtmlEncoder, JavaScriptEncoder, UrlEncoder, and there are third-party sanitizers - for example, HtmlSanitizer, which allows you to have quite a few settings, you can clearly describe the rules for grammar, where this data will be inserted. The library, which can greatly help you with understanding how data can be encoded for a specific grammar, is LibProtection. She takes part of the work for herself.
Let's return to our two first examples. Data validation, data sanitization is good, but it still remains on the developer’s side, which means that we must rely on a good developer level, that he will take the trouble and use the necessary sanitizers and do the necessary validation. It's 21st century, and I would like to forget about XSS once and for all, to cut them down at the browser level. Is there any possibility for this? Yes.
There is such a thing called Content Security Policy. As the browser is guided by the Same-origin policy, determining which requests are possible and impossible, so you can configure another policy, Content Security Policy, explaining where it can be downloaded from scripts and executed. This policy is configured through heders. In ASP.NET, your settings will look something like this:
You simply register the Content Security Policy header in the response, specify from which domains you can download resources. There are much more settings for resources here, I did not mention everything, they have complex hierarchies of inheritance. Now a little bit about it. You also have the opportunity to ask report-uri. This is a kind of uri, on which the browser will be knocked off, there is something gone wrong. For example, he wanted to download the script, but this source was not registered in the Content Security Policy, and therefore he cannot download it. This is especially important at the initial stage, when you embed Content Security Policy, and sometimes from time to time JavaScript just stops working for you, and you don’t quite understand why, where and what to do with it. It is convenient to use in a situation when the script is not loaded. The browser will be tapped on your server.
To accept this request, the easiest way to do this is the class that parses the data that the browser sent:
And create a method in the controller that already receives this data. Then you will either log the data, or somehow you will analyze them. The incoming request to this controller tells you about two possible things: either the Content Security Policy is poorly configured, someone added a script from a source that was not registered, or you are being attacked, that is, the attacker is now trying to make XSS. Therefore, when you do this, if you have a heavily visited site, be careful: you may receive a lot of requests at the time of a real attack. If there is some kind of stored XSS and there is an attempt to execute on tens of thousands of browsers, you will make such a DDoS yourself. That is, either to transfer this controller to a separate node, or, at least, not to forget about this danger of DDoS.
There is such a handy tool - cspvalidator.org. If you have any particularly complicated Content Security Policy settings, it will help to find inconsistencies in these settings and give some report about the policies you have configured.
As always, a silver bullet does not exist. But properly configured Content Security Policy protects you from XSS. However, this does not mean that in general no injection into the page code is possible. For example, here is the standard login form from ASP.NET, it has an action, the standard relative path:
And if an attacker can inject the base tag into the body of the same page, which defines the prefix for all relative paths on the page, then if the user uses this login form and sends the login and password, they will go to the attacker's site because of this injection.
This is not XSS, the script does not execute here, but it is a working injection. If you set up Content Security Policy, of course, it’s not worth scoring on validation and sanitization. These methods only complement each other.
Another browser protection mechanism that is available to you and which you can configure is XSS-protection on the browser side. All modern browsers have a built-in filter from XSS. To enable it, you need to send it to the header. When you do this, be sure to test it, because it’s some kind of heuristics on the browser side when it tries to defend against XSS.
The next topic continues the Client-side attack - this is CSRF (Cross-Site Request Forgery).
Remember, when we talked about the Same-origin policy, we wondered if the GET and POST request would be sent. And they found out what they will send, but will not receive the answer. So on this feature and the attack is built CSRF. If a POST request can be sent (and POST requests from us are usually requests from forms), then it is not always important for an attacker to receive a response, it is enough that the action will occur on the server. What this attack looks like: we have a user, it logs in to some system. For simplicity, let's take Internet banking. He logged in, received authorization cookies.
After that, we have an attacker on the scene who sends a link to the user. The user opens it in his same browser receives a certain page from the server controlled by the attacker. Now in the browser, where the user is logged in to his Internet bank, he received a certain page on which the attacker's script can execute a POST request to this Internet bank.
Since the request is made to the bank's domain, it takes the cookies belonging to this domain and sends them along with the request. A POST request can, for example, transfer money from one account to another, it is not important for us to get the result of this request. If this fact happens - it is already a successful attack.
How to protect yourself from this? The attack is also very old, and there are built-in mechanisms in almost all web frameworks, including ASP.NET Core. The main thing is not to forget to turn them on. Protection is based on the principle that your browser in a GET request returns two identical tokens. One in cookies, protected, which cannot be read from a JavaScript code, the other in a hidden form field. It sends these two tokens, and when the user clicks the send button in the form, the two tokens fly back. One of the hidden field, another flies in cookies. The server compares these tokens and, if they converged, then the GET-POST chain was implemented, the user pressed the button, performed this action, and it is correct. Then the server continues to execute the request.
Accordingly, if the site is an attacker, the script is loaded from another domain. This script cannot access content from another page. This is another Origin, and here the Same-origin policy saves us from reading tokens, so this protection works. How is this configured?
In ASP.NET Core it looks like this. There are AntiForgery cookies that contain a token, and there are those hidden fields that I talked about (but they must be in all forms that send POST requests to your server).
The token itself is a random random token that is generated by a random generator, and then it is encrypted using the very same Crypto API that we talked about.
Therefore, if you have distributed systems, you need to have a remote keystore so that your tokens can be scripted on different nodes. One node can create this token, and the other can decrypt it.
The next point: forms that do not have an explicitly specified action, for example, the asp-action attribute in CSHTML, automatically add a token, you don’t need to worry about it.
Or you can add this token manually if you need it.
How to set up check at the level of middleware, if y you MVC? The easiest way is to set the AutoValidateAntiforgeryTokenAttribute at the filter level (it is not set by default). He says that you have all requests except GET, OPTIONS, HEAD and TRACE, will be necessarily validated to match these tokens, and the CSRF attack will be impossible.
By default, attributes are used for a specific controller, for a specific action. Once again: it's easier to set for everything, and if you don’t need it somewhere, turn it off.
You also have the option to manually set the attributes:
For example, you can show the controller which method to ignore, if necessary. But it should be borne in mind that with automatic validation, GET requests will not be validated, and this is correct. Therefore, if your method handles a POST request, it should not process a GET request. Your routing should not be so configured.
The next frequent question that arises is if I have AJAX, JavaScript, and requests often leave not from a form, but from, for example, an AJAX request, what to do about it?
It depends on how your authorization is done. If you do not have cookies, you use tokens. Each page has a certain token that is used for authorization to send this AJAX request. Then you have no problems, this token can not be considered an attacker, and it can not be used to attack, because it can not be read.
If you use authentication cookies, then you can explicitly specify in the header which parameter will contain this token.
With this C # code, get the value of the current token for the user and add it to all requests. You will need to register on the server that you need to take a token for this header and collate it. But this is one configuration parameter when setting up the AntiXsrf mechanism.
The next topic you want to touch is Cross-origin requests. If you still need to implement requests between domains for something - what to do with it, how to configure, what kind of tools do we have?
That very first example. He looks quite a valid. If you have some kind of data provider, and you want to load some real-time data from JavaScript, show it, and you have a provider on another domain, then why not, but how can you set it up ?
There is such a tool, CORS (Cross-Origin Request Sharing). This thing is supported, again, by all modern browsers. The bottom line is that you can set up your server so that it allows reading data in the browser for certain Origin or for all. For example, for our past example, the following server response can be configured. When the browser sends a POST request, our server validates Origin and sends Access-Control-Allow-Origin, indicating for a particular Origin or for all, as in this situation.
Then the browser allows JavaScript to parse this data, process it and display, for example, on the page.
Here is the next moment. But how does it work for put requests, for delete requests? We remember that the Same-origin policy may allow us to send only GET and POST requests. In fact, it may allow sending requests that meet these requirements, which are called simple requests. This is GET, head, POST, which does not have any additional headers, whose content-type does not correspond to those listed. There are a few exceptions, but we do not need them yet. How the browser behaves for other requests:
In other cases, he first sends the request options and asks if it is allowed for a POST request with an additional header (or for a put request) to perform cross-domain interactions. To which your server must verify Origin and the transferred parameters, the header and method, and in case the cross-domain request is allowed, the following headers should send: Access-Control-Allow-Origin for which Origin, for which methods it is allowed, for which headers.
The browser on its side validates this answer. If it matches, it already sends an honest POST request with an additional header, or a put request, in general, the request you specified. And already on its main request receives a response from the server.
How is this configured? Fortunately, in .NET Core you do not need to worry about these nuances - that there are different types of requests that you need to validate Origin. You have the middleware for configuring CORS, which you add to your project, and using the UseCors methods in Configure, you can either allow all Origin to access your server, or write only those Origin that have this access allowed.
There is a second setting option. You can define a certain policy or a set of policies with different permissions. For example, for different Origin to open some specific controllers and using attributes already at the controller level, you can assign this or that policy to a specific method in the controller or the entire controller. And then all his methods will be available, in accordance with the policy, for those of Origin.
A more interesting example is when we have some kind of authorization on the very second site that we want to access crossdomain from JavaScript. This site may set a certain cookie: the user has logged in on this site, has received a cookie and now we have a third-party site that wants to access data from this site. For example, you have some short ex.com domain, and he wants to read data from your main site, where the user from example.com is authorized. To do this, it needs to set the withCredentials = true parameters, that is, tell the browser that it wants to send a request with all the Credentials, with all the cookies that exist.
This request will come to you, and on the server side you need to answer what you allow for a particular Origin this answer and another Access-Control-Allow-Credentials to pass.
Here there is an important nuance: when you transfer Credentials to the server, you need to clearly transfer the Origin to which it is allowed. There is no longer possible to put an asterisk, in this situation additional protection is turned on by the browser, he considers that requests with Credentials for all are impossible, never. Here you obviously need to verify Origin. If you trust him (for example, this is some kind of your site, but located in a different domain), then you already explicitly indicate that this request is possible for this site, and send the result.
Configuring this again is easier than it works. You simply specify for which Origin, and indicate that it is AllowCredentials, and it works.
In addition to CORS, about which we talked, there are other mechanisms for interdomain interaction. I think everyone knows about WebSockets. JSONP, POSTMessage mechanisms, when you have an iframe on the same page, and you can send a Message from it to another domain, but it is on the same page. We will not talk about them in detail now, but there is an important point here that is worth noting, it concerns the validation of Origin.
If you use everything on the right, you need to validate Origin, so as not to send the data to someone else. So that an attacker, for example, could not create his own page with his own script that works with your web sockets, and connect there. If your authentication is also done by cookies, this will also be possible. You definitely need to check Origin, which the browser puts, - where did this request come from, whether you trust this domain, whether it is your domain. In the case of JSONP, you need to screw up some other protection already, be sure to use tokens in requests, for which you also verify that the request came from you (from a site you trust).
The final part of the Client-side attacks is the best practices in the use of cookies.
Cookies are a small piece of data that the server sends in the response, in the Set-cookie header, this data contains the name of the cookie, some value and parameters. The browser, guided by these parameters, saves cookies and uses, depending on these parameters. Now we will analyze these parameters in detail.
What are cookies used for in general? This may be some Client-side storage. When you do not want to store any small data on your server, you can simply give it to the client, he will send it back to you in cookies, and you use it as a kind of storage. This can be used for session management, I don’t have time to tell about it today, but there the principles of working with cookies are about the same.
Let's take a look at how to properly do Client-side storage, how to transfer data to the client side, given that cookies are some kind of untrusted storage for you. So let's start with the parameters.
You have a standard set of parameters - this is the Domain, the Path for which the cookie is valid, the browser will check before sending any request if it has cookies for this domain. If there is, only in this situation will send it. Same check on Path. By default, the values ​​for these parameters are not defined in ASP.NET Core and in ASP.NET, which causes some difficulties related not so much to Security, but to the authentication process. For example, until recently, the behavior in Internet Explorer was different from the behavior in Chrome. And he used in an undefined domain, when obviously the user did not specify it, the domain for the current domain and all subdomains, and Chrome and Mozilla only for the current domain. , , , , Chrome , Internet Explorer . , . ASP.NET Core . cookie ( , - ), Domain Path , , - . .
cookies (, ):
Domain Path .
cookies ASP.NET Core. , key value . Uri.EscapeDataString, Domain Pass .
— Secure HttpOnly. Secure , cookie https, Man in the Middle cookie http. HttpOnly , cookie . JavaScript- , cookie. .
, HttpOnly .
, , Client-side, cookie , . .
, .
, , cookie . ? , HttpOnly cookie, , - . session cookies, ASP.NET Core. . session cookie. , , , cookie , , cookie, , . cookie HttpOnly, . cookies, HttpOnly. , cookie ID , username, Claim ID.
anti-XSRF , Claim ID, username cookie, cookie. , cookie , .
, Client-side , . , , . , - .
Security .NET, , - . .NET Core, , , .
OWASP Developer Guide — , .
OWASP Testing Guide, , , . , , , .
Minute advertising. As you probably know, we do conferences. .NET — DotNext 2018 Piter , 22-23 2018 -. , ( — ), . In short, come in, we are waiting for you!
Source: https://habr.com/ru/post/350188/