We often want to know whether a user has visited a website before. Is there any way or technology to achieve this?
Preliminary exploration
Note that in most browser default settings, the colors of links accessed by users and those not accessed are different, as shown in the figure below:
That is, the link accessed by the user. By default, computed color is purple (or other colors specified in CSS ):
For unaccessed links, computed color is blue by default (or other colors specified in CSS ):
That is not to say, we can add links we are interested in on the page, and then use JavaScript to obtain the actual color of the link text to know if the user has accessed the specified URL?
This method is really effective. In 2010, a security article introduced this method and called it "historical sniffing" (history sniffing ).
Unfortunately, all major browser vendors have noticed this problem. According to my tests, the latest browsers currently guard against this issue. When obtaining the hyperlink Computed Style, no matter whether the link is accessed or not, the obtained color is the color that is out of date.
It seems that the scheme obtained by color does not work now, but the magic is that now we have another scheme.
RequestAnimationFrame
Context Information Security recently published a report titled pixel perfect timing attacks with HTML5, which mentions a very creative solution: using requestAnimationFrame in HTML5, determine whether or not the specified link has been accessed based on the time difference between the rendered and unaccessed links in the browser.
The process of drawing each frame in modern browsers is roughly as follows:
The general process is: JS modifies the style of an element, the browser recalculates the appearance and position of the corresponding element, and then draws them out. This process is a frame. The function of requestAnimationFrame is to register a function and call it before the next frame is drawn.
The original intention of requestAnimationFrame is to allow developers to better manage animations and draw smoother animations, as described in this blog. However, this interface also makes it possible to get the rendering time of different elements.
Working Principle
Before starting, we need to know how the browser renders the accessed and unaccessed links.
When a browser renders a page, the browser must distinguish whether a link has been accessed. Each browser has a database that records the Accessed links. In this case, it needs to query
Whether the specified URL exists.
In IE and Firefox, if the link has been rendered to the page and the query is not completed, the browser will first use the "unaccessed" style for rendering. When the query result is returned, if the specified link has been accessed, the browser willRedrawThe specified link. This "re-painting" takes time and can be monitored using requestAnimationFrame.
Unlike Firefox and IE, Chrome will render the link to the screen until the URL query of the database is complete.
In addition to the initial rendering, using JavaScript to modify the href of the link may also lead to browser re-painting. The test shows that in Firefox, modifying the href of a link will cause re-painting if it changes its "accessed" status. However, this cannot work in IE. Once a link is created, changing href will never change its "accessed" status at the same time.
Except for Chrome, only changing href does not cause re-painting. However, if you change href, you can also "touch" the link style (but do not modify it ), when the new href needs to change the "accessed" status, the browser redraws the corresponding link.
The so-called "touch" refers to an operation similar to this:
|
1
2
3
4
5
6
7
8
9
10
|
<
Word ">
Href
=
Http://www.google.com"
Id
=
"Link1"
############ </
A
>
<
Script
>
Var el = document. getElementById ('link1 ');
El. href = 'http: // www.yahoo.com ';
// Below lines are required for Chrome to force style update
El. style. color = 'red ';
El. style. color = '';
</
Script
>
|
To put it simply, the basic principle is as follows: create a link, change its href, use requestAnimationFrame to monitor the time consumption of the next several frames, and determine whether re-painting has occurred. If re-painting has occurred, if the "accessed" status of the specified link changes, you can determine whether the specified link has been accessed.
Of course, there are still many problems to consider in the actual operation process. For example, browser rendering is usually very fast, and the re-painting time may be very short, resulting in completely undistinguished. There are two solutions. One is to increase the number of links, create multiple A links, point to the same URL, and use JS to change the href attribute of these A links to another value when necessary. Another solution is to add time-consuming styles to the elements, such as text shadows, and make the blur radius as big as possible, so that the time required for re-painting will be much longer.
Practice
I wrote a demo for Chrome browser. You can use Chrome to access
<! DOCTYPE html>
<Html>
<Head>
<Meta charset = "UTF-8"/>
<Title> Browser History Sniffing </title>
<Script src = "http://codeorigin.jquery.com/jquery-1.10.2.min.js"> </script>
<Style>
Html, body {
Padding: 0;
Margin: 0;
}
# Wrapper {
Padding: 16px 32px;
}
Ul # test-area {
Opacity: 0.1;
Float: right;
}
Ul # test-area {
Text-shadow: 5px 5px 500px #999;
}
# Operation {
Display: none;
Padding-left: 4em;
}
# Links {
Line-height: 20px;
Width: 60%;
}
H1 {
Font-size: 24px;
Margin: 0;
Padding: 0;
Line-height: 48px;
}
# Desc {
Font-size: 12px;
Line-height: 20px;
Background: # f5f5f5;
Padding: 8px;
Margin-bottom: 40px;
}
. Inform {
Background: # ff0;
}
Span. time {
Color: # ccc;
Padding-left: 2em;
Font-size: 12px;
}
</Style>
<Script type = "text/javascript">
// Var _ bdhmProtocol = ("https:" = document. location. protocol )? "Https: //": "http ://");
// Document. write (unescape ("% 3 Cscript src = '" + _ bdhmProtocol + "plain type = 'text/javascript' % 3E % 3C/script % 3E "));
</Script>
</Head>
<Body>
<Div id = "wrapper">
<H1> Browser History Sniffing
<Div id = "desc">
<P> browser history sniffing demo. <span class = "inform"> Only Chrome </span> is supported.
Mac 10.8.4/Chrome 28.0,
Windows XP SP3/Chrome 28.0,
Ubuntu 12.4/Chrome 28.0
.
</P>
<P> About: <a href = "http://oldj.net/article/browser-history-sniffing/"> http://oldj.net/article/browser-history-sniffing/ </a>
</P>
</Div>
<Ul id = "test-area"> </ul>
<Ul id = "links"> </ul>
<Div id = "operation">
<Input id = "url" placeholder = "http: //" size = "50"/>
<Input id = "add" type = "button" value = "TEST"/>
</Div>
</Div>
<Script>
(Function (){
// Var known_visited_url = "http://oldj.net/static/history-sniffing/test.html ";
Var known_visited_url = location. href;
Var links = [
Known_visited_url,
"Http://www.taobao.com ",
Http://www.111cn.net ",
"Http://a.com ",
"Http://www.tmall.com ",
"Http://www.alipay.com ",
"Http://www.111cn.net ",
"Http://list.tmall.com/search_product.htm? Spm = 1.1000386.a2145lv.2.b5GVjI & from = sn_1_prop & area_code = 310000 & auction_tag = 835 & vmarket = 0 & style = g & sort = s & n = 60 & s = 0 & cat = 50024400 # j_crumbs ",
"Http://www.facebook.com ",
Http://www.google.com"
];
Var last_time = 0;
Var current_is_visited = false;
Var min_large_time =-1;
Function checkIsLinkVisted (url, callback ){
Var I _count = 10;
Var times = [];
// $ ("# Test-area a"). each (function (){
// This. href = known_visited_url;
//});
Function check (time ){
Var delay = parseInt (time-last_time );
Times. push (delay );
Last_time = time;
If (I _count> 0 ){
RequestAnimationFrame (check );
} Else {
CheckEnd ();
}
I _count --;
}
Function checkEnd (){
Var large_time_count = 0;
Var large_time = 60;
For (var I = 0; I <times. length; I ++ ){
If (times [I]> large_time) large_time_count ++;
}
Console. log (times );
If (min_large_time =-1 ){
Min_large_time = large_time_count;
If (min_large_time <2) min_large_time = 2;
Current_is_visited = true;
} Else if (large_time_count> = min_large_time ){
Current_is_visited =! Current_is_visited;
}
$ ("# Links"). append ("<li class = '"
+ (Current_is_visited? "Visited": "unvisited") + "'>"
+ (Current_is_visited? "[V]": "[]") + ""
+ "<A href = '" + url + "'>"
+ Url + "</a>"
+ "<Span class = 'time'>"
+ Times. join (",")
+ "</Span>"
+ "</Li> ");
If (callback & typeof callback = "function") callback ();
}
SetTimeout (function (){
$ ("# Test-area a"). each (function (){
This. href = url;
This. style. color = "red ";
This. style. color = "";
});
RequestAnimationFrame (check );
},500 );
}
Function initTestArea (){
Var htmls = [];
For (var I = 0; I <500; I ++ ){
Htmls. push ("<li> <a href = '" + known_visited_url + "' >##########</a> </li> ");
}
$ ("# Test-area"). append (htmls. join ("\ n "));
}
Function checkByTurn (list ){
Var current = list. shift ();
If (current ){
CheckIsLinkVisted (current, function (){
CheckByTurn (list );
});
} Else {
// $ ("# Test-area" pai.html ("");
$ ("# Operation"). slideDown ();
}
}
$ (Document). ready (function (){
InitTestArea ();
CheckByTurn (links );
// CheckIsLinkVisted (links [3]);
});
$ ("Input # url"). keydown (function (e ){
If (e. keyCode = 13 ){
CheckIsLinkVisted (this. value );
}
});
$ ("Input # add"). click (function (){
Var url = $ ("input # url"). val ();
CheckIsLinkVisted (url );
});
})();
</Script>
</Body>
</Html>
Test.
This test requires a known visited URL as the benchmark link. Since you are reading this blog, you may click the test link above, then I will use the test link above as the benchmark link. Next, test each link in sequence to see if it will trigger re-painting, and then determine whether you have accessed the specified link.
The effect is shown in the following figure:
In addition to the purple display (this is a built-in function of the browser), I also added a "[v]" mark in front of the Accessed link, before a link that has not been accessed, it is "[]". In addition, you can enter a new URL in the input box below to check whether the URL is correct.
The principle is as mentioned above. After the link address is modified, redraw is determined based on the next frame time, the Accessed status of the link is changed.
This is an example of Chrome. According to the contextis White Paper, a slight transformation can be applied to IE and Firefox.
More
This method of retrieving information by judging the rendering time is terrible. Its strength is that it can obtain access records of third-party websites without the user's knowledge. For example, you can use this method to determine whether a user has accessed a competitor's website or whether the user has accessed a specified restricted website.
A 2000 paper by Princeton University titled Timing Attacks on Web Privacy has pointed out that such Timing Attacks may leak user Privacy. This paper also proposes a method to determine whether a user has accessed a specified URL, the method is to load the fixed resources of the specified website on the current page (resources that will be downloaded by users who have accessed the website), such as the logo or js file. If the user has visited the website before, the browser reads the corresponding resources from the local cache, which is much faster; otherwise, the browser downloads the resources again. Using JavaScript or Java applet (a paper published in 2000, Java applet was still popular at that time) can measure this time to determine whether a user has visited a specified website.
The solution proposed more than a decade ago is still effective, but it is more advanced and more efficient.
Besides the client time, the server time may also leak unexpected data. For example, as mentioned in Stanford's paper "Exposing Private Information by Timing Web Applications", The backend processes different types of data with different logic and time consumption. Therefore, as long as you construct an appropriate request, execute it repeatedly, and measure the time, you may obtain a lot of privacy information.
How can we prevent such timed attacks? At present, there seems to be no good way. For the server, the best solution may be to use some means to normalize the return time of all requests, for example, to 500 milliseconds. If a request is completed earlier than 500 milliseconds, it will sleep for a while, it is returned until 500 milliseconds. However, the problem is that the response speed of the entire site depends on the slowest page, which may be intolerable for efficiency-oriented teams. If it is not a constant, it is impossible to sleep for a random period of time after each request, because completely random means that there are traces in statistics, as long as there are enough samples, attackers can remove random factors.
For client timing attacks, there may be fewer defense methods, perhaps only when browser vendors notice such problems and make modifications to browser features, just like a computed color with an access link cannot be obtained.