There are countless discussions about how XSS is formed, how it is injected, how it can be done, and how to prevent it.
Almost every article that talks about XSS will mention how to prevent it at the end. However, most of them remain unchanged. Escape, filter, or forget something. Despite all the well-known principles, XSS vulnerabilities have almost never been interrupted for more than a decade, and many large websites are also frequently exposed. Small websites are even more common.
This article introduces another preventive approach-using a browser monitoring script to allow every user to participate in vulnerability reporting.
Warning System
As a matter of fact, there are still no solutions once and for all, so we should avoid using the oldest method to filter them one by one. However, when people are always negligent, some new fields will inevitably be missed during product iteration and the vulnerability will be introduced.
Even if there is a loss of the sage, the program bug is completely understandable and can be fixed in a timely manner. But what is puzzling is that it takes a long time to discover the problem. For example, not long ago, we posted the XSS worm script, which was not known until it was reported by users after the outbreak of a large scale. The majority of other websites are similar. vendors are not notified until white hats discover vulnerabilities and submit them to the security platform. If hackers keep these vulnerabilities in private and take advantage of them, they will have to leave them alone.
Therefore, it would be better to have a real-time warning system. Even if the vulnerability cannot be prevented, developers can be notified immediately after the vulnerability is triggered to fix the vulnerability in the shortest time to minimize the loss. Various Application layer firewalls are also generated.
However, unlike traditional system vulnerabilities, XSS is triggered on user pages. Therefore, we may try to use the frontend idea for online defense.
XSS inline event
Assume that a background with a bug does not properly process user input data, and XSS can be injected to the page:
Only escape angle brackets, but forget the quotation marks, which is the most common in XSS. Attackers can disable attributes in advance and add an easily triggered inline event, so that cross-site scripting is easily executed.
So can we use the front-end scripts to capture or even intercept them?
Passive Scan
The simplest way is to scan all the elements on the page and check the inline attributes starting with on to see if there is any exception:
For example, the number of characters is very large, which is rarely used under normal circumstances, but the XSS sometimes has a long encoding to avoid escaping. For example, some keywords frequently used by XSS appear, but it is rarely used in actual products. These can be used as a warning to notify developers.
However, the soil solution has many limitations. In today's Ajax era, page elements have never been fixed. With various user interactions, new content may be dynamically added at any time. Even if it is replaced by a regular scan, XSS may trigger at the timer interval and destroy itself, which will never be traced. Moreover, frequent scans have a huge impact on performance.
Like early security software, scanning the Registry Startup item every few seconds not only charges performance, but also does not work for malware. However, the subsequent active defense systems are different, analysis is performed only when the API is actually called. If the API fails, it is intercepted directly, completely avoiding the omission of the timer interval.
Therefore, we need this similar latency policy-analyze it only when XSS is about to trigger, block or release elements that do not comply with the policy, and send an alarm to the background log.
Active Defense
The concept of "active defense" seems a bit mysterious in front-end scripts. But it is not hard to find that this is just a matter of priority-as long as the defense program can run before other programs, we have the initiative to go in and out. These concepts can be turned out to be incredibly powerful HTML5 and flexible JavaScript.
Continue back to the inline event XSS discussed just now. Although the browser does not provide an interface for manipulating inline events, the nature of inline events is still an event. No matter how it changes, it cannot do without the DOM event model.
When we pull it over the model, everything is about to be solved. Model is the most reliable way to solve the problem, especially the long-established model like DOM-3-Event, its stability is beyond doubt.
Even if you have not carefully read the official documentation, anyone who has made a webpage knows that there is an addeventlistener interface that replaces the old one called attachevent. Although only a new parameter is added, it is the difference that has become a hot topic. When talking about the event during the interview, we always need to examine the purpose of this new parameter. Although it is rarely used in daily development.
We will not discuss much about event capture and bubbling. The following code may inspire your imagination about "active defense.
<button onclick="console.log('target')">CLICK ME</button><script>document.addEventListener('click', function(e) {console.log('bubble');});document.addEventListener('click', function(e) {console.log('capture');//e.stopImmediatePropagation();}, true);</script>
Run
Even though an inline event is directly bound to the button, the event model does not buy it, and you still have to follow the standard process. Capture, target, and bubble. The model is so stubborn.
However, the result of restoring the annotated code is only capture. Everyone understands this simple truth, and there is nothing to explain.
But I want to figure it out carefully. Isn't that the concept of "active defense? The capture program runs before an inline event is triggered, and has the ability to intercept subsequent calls.
The demo above only intercepts all events without thinking about it. If we add some policy judgments, we may be more clear:
<Button onclick = "console. log ('xsss') "> click me </button> <SCRIPT> document. addeventlistener ('click', function (e) {console. log ('bucket') ;}); document. addeventlistener ('click', function (e) {var element = e.tar get; var code = element. getattribute ('onclick'); If (/XSS /. test (CODE) {e. stopimmediatepropagation (); console. log ('intercept suspicious events: ', code) ;}}, true); </SCRIPT>
Run
We first scan the inline event character in the capture phase. If the keyword "XSS" is displayed, the subsequent event will be blocked; if it is changed to another character, it will continue to be executed. Similarly, we can also determine whether there are too many characters in length, as well as more detailed black/white list regular expressions.
What happened? A prototype of active defense was born.
However, there is also a small problem with the above fragments, that is, the event bubble process is blocked, and we just want to intercept inline events. The solution is also very simple. Replace E. stopimmediatepropagation () with element. onclick = NULL.
Of course, at present, this can only protect onclick, but in reality there are too many inline events. Mouse, keyboard, touch screen, network status, and so on. events supported by different browsers are different, and even private events are listed and captured in advance? Yes, it can be captured, but it does not need to be listed in advance.
Because we listen to the Document Object, all the inline events in the browser correspond to the document. onxxx attribute, so we only need to traverse the Document Object at runtime to get all the event names.
<SCRIPT> function hookevent (onevent) {document. addeventlistener (onevent. substr (2), function (e) {var element = e.tar get; If (element. nodetype! = Node. element_node) {return;} var code = element. getattribute (onevent); If (Code &/XSS /. test (CODE) {element [onevent] = NULL; alert ('intercept suspicious events: ', code) ;}, true) ;}console. time ('timeout'); For (var k in document) {If (/^ on /. test (k) {// console. log ('monitoring: ', k); hookevent (k) ;}} console. timeend ('timeout'); </SCRIPT>
Run
Now, no matter which element on the page triggers any inline event, it can be captured in advance and can be retired according to the policy.
Performance Optimization
Some events may not need to be captured, such as video playback and volume adjustment. However, even if they are all captured, the time is usually around 1 ms.
Of course, registration events will not take much time, and the actual consumption is counted in callback. Although most events are not triggered frequently, additional scans are negligible. However, the events related to mouse movement cannot be ignored. Therefore, you must consider performance optimization.
Obviously, the inline Event code is almost impossible to change during running. Inline events are mostly used for simplicity. It is totally unreasonable to change the Inline code using setattribute at runtime. Therefore, we only need to scan a specific event of an element once. Then, you can skip this step based on the flag.
In fact, there is no need to use the event name for the flag. Use a serial number that is not repeated.
<Div style = "width: 100%; Height: 100%; position: absolute; Background: Red" onmousemove = "alert ('xss ') "> </div> <SCRIPT> function hookevent (onevent, eventhash) {function scanelement (element) {// skip the scanned event var flags = element ['_ flag']; If (! Flags) {flags = element ['_ flag'] ={};} if (typeof flags [eventhash]! = 'Undefined') {return;} flags [eventhash] = true; If (element. nodetype! = Node. element_node) {return;} var code = element. getattribute (onevent); If (Code &/XSS /. test (CODE) {element [onevent] = NULL; alert ('intercept suspicious code: ', Code);} // scan the parent element scanelement (element. parentnode);} document. addeventlistener (onevent. substr (2), function (e) {scanelement(e.tar get) ;}, true) ;}var I = 0; For (VAR K in document) {If (/^ on /. test (k) {hookevent (K, I ++) ;}</SCRIPT>
Run
In this way, the subsequent scan only judges the tag in the target object. Even if the mouse shakes wildly, the CPU usage is ignored.
At this point, we have implemented active defense in the XSS inline event.
For strings that have a large number of characters or appear similar. fromcharcode, $. getscript can intercept typical XSS code. Test codes such as alert (/XSS/) and alert (123) can be temporarily released and logs can be sent to the background, determine whether the data can be reproduced.
If it occurs again, it indicates that XSS has been detected and injected successfully, but it has not been used on a large scale yet. Programmers need to fix bugs as soon as possible, so that hackers can find that the vulnerability has been fixed after a while :)
Character policy Defects
However, there are still omissions in the code string alone. Especially when hackers know that such a thing exists, they will be more careful. Escape the code to avoid keywords and store the characters elsewhere to avoid length detection. This completely bypasses our monitoring:
Therefore, we not only need to analyze keywords. During callback execution, you also need to monitor Eval, setTimeout ('...') and other functions that can parse code to be called.
However, it usually does not inject too much code, but directly introduces an external script, which is simple and reliable, and can modify the attack content in Real Time:
The next article will discuss how to intercept suspicious external modules.