Fingerprinting 101, Part 2

Device Fingerprinting
Member News
Blog
Nethone
Feb 10, 2021
Blog

By Marcin Mostek, Security R&D expert at Nethone

In the first part of "Fingerprinting 101" we answered a few questions about this technique, including what is a fingerprint, who should use them, and how do you make them? To recap, a fingerprint is a user-specific set of data downloaded from a browser, which can be used to confirm with a high probability the user's identity between visits to a website. It is commonly used in anti-fraud payment systems of the "card-not-present" type (i.e., all those where we pay by card on the Internet). In this post, we will dig in a little deeper into the details.

Fingerprinting has its shortcomings. How can they be addressed?

In the real world, where the user's browser does not move in a uniformly accelerated motion in the void, the problem of a decrease in granularity with an increase in the amount of traffic has to be fixed. The way that this may be solved is by combining many types of fingerprints. Naturally, the more fingerprints we use, the more we will reduce stability -- determining the golden mean is a non-trivial optimization problem. It should be remembered that finding such an optimum is not constant over time.

For example, if we rely heavily on the presence of fonts in the system for our fingerprint, then along with a significant change in the geographical source of traffic (e.g., from the USA to China), it may turn out that most of the fonts selected by us somehow did not become popular among the inhabitants of China. This will reduce the number of information bits that we can read, and thus the granularity of the user distinction itself. More on fonts later.

Examples of fingerprinting techniques

User-Agent

The simplest thing that is suitable for fingerprinting is User-Agent, which is how the browser presents itself to the server it connects to. This value can be obtained independently from two places:

  • headers sent on the HTTP request
OR

  • using the javascript API window.navigator.userAgent
However, this is not a very good field to use to distinguish or identify the user, as it can be easily replaced in a browser or outside of it.

An example User-Agent (Chrome on Windows 7) looks like this:

Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36

Wait a minute -- if the browser is Chrome, why are Mozilla, Apple, Gecko, and Safari listed here? Usually, the answer is one -- if in the world of browsers you do not know what is going on, it is about backward compatibility (and money). And here is where the fun begins...

One of the first browsers was Mosaic, whose User-agent was:

Mosaic / 0.9

Simple right? But in life, nothing stays that simple for too long. The next major novelty on the market was the product of the Netscape Communications company with the code name Mozilla (by the way, from the combination of the words Mosaic Killa). Of course, Mosaic was not happy about this choice, so the official name was changed to Netscape. However, spiteful developers have left the codename in the User-Agent:

Mozilla/2.02 [fr] (WinNT; I)

As you can see, information about the language, platform, and encryption method has been added (and in this case, it means 40-bit encryption).

The next larger browser that appeared on the market was Internet Explorer, adored in JavaScript programmers' circles. This browser did not support some of the technologies supported by the competition, so website owners began to filter site visitors depending on the User-Agent. In the case of IE, they served a different version of the site or did not serve it at all. This fact did not increase the popularity of the new browser, so Microsoft came up with a brilliant idea to pretend to be other browsers by modifying User-Agent (most of the checks only analyzed the first part of the UA):

Mozilla/2.0 (compatible; MSIE 3.02; Windows 95)

The trend of pretending to be other browser is still alive, so Chrome User-Agent should be understood as follows:

  1. Chrome used the Webkit rendering engine (AppleWebKit / 537.36
  2. ...which was invented by Apple in Safari and actually worked like in Safari (Safari / 537.36),
  3. The Webkit, in turn, pretended to be KHTML, which was used in Linux Konqueror (KHTML),
  4. KHTML pretended to be Gecko - Firefox's rendering engine (KHTML, like Gecko),
  5. ...and then all of the browsers pretended to be Mozilla, so this part could not be missing (Mozilla / 5.0).

Interesting fact:

You can find a more detailed description of these complexities (which are really fun until you have to write a User-agent parser) here and here, as well as in the oldie but goodie book "Tangled Web" from Michał Zalewski.

The example of Chrome is, of course, only the beginning of a great adventure in the world of User-Agents, because in the meantime...

  • device type (tablet, cell phone, etc.) has been added,
  • some companies use their own naming scheme, which they later do not use themselves,
  • some WAP devices issue X-WAP profiles with device properties,
  • older IE versions display information about supported net-framework versions,
  • the User-Agent scheme is not coherent between manufacturers (game consoles or TVs are leading the way here), and the whole Internet of Things will bring a lot of fun here ;)
As you can see, we have, relatively speaking, many bits of information here.


Plugins

Plugins (not to be confused with extensions) are another property of the browser that can be used for fingerprinting. Plugins, as the name implies, are used to expand the browser with new previously unavailable functions. An example here may be Flash or Java, very popular at that time. It turns out that the number of plugins are relatively large, so their list can identify the user.

Plugins can be extracted using the following code:

var detectedPlugins = [];
var pluginsLength = navigator.plugins[0].length;

for(var i=0; i < pluginsLength; i++){
detectedPlugins.push(navigator.plugins[0][i]);
}


The returned information may include the plugin name, description, file name associated with the plugin, and version.

Interestingly, in the case of Internet Explorer, this process looks a little different -- the browser requires from the user the name of the plugin and only after providing it returns information whether the plugin is installed:

var plugins = [];
var names = [
'AcroPDF.PDF',
'WMPlayer.OCX'
// tu należy wstawić więcej nazw pluginów
];
for (var i = 0; i < names.length; i++) {
try {
// sprawdź czy plugin jest dostępny za pomocą odwołania ActiveX
new window.ActiveXObject(names[i])
// jeśli nie nastąpił wyjątek plugin jest obecny
plugins.push(names[i])
} catch (e) {
// jeśli nie ma pluginu obsłuż wyjątek
}
}



Screen properties

Another interesting API useful in attempts to identify the user is window.screen. It is strongly correlated with the type of device that the user uses (a laptop will have a different resolution than a mobile phone). What's more, the API can carry information about what operating system the user uses -- it has properties that speak about the total height/width of the device screen, as well as the height/width actually available to the browser. Based on the difference in this data, it can be determined what part of the screen is covered by menu bars in the most popular operating systems. More about this API can be found here.


WebGL

WebGL is an attempt to enable web developers to render 3D images in a browser using a graphics card. In its assumption, it was to be as similar as possible to OpenGL which was used for graphics rendering on desktopL (WebGL was based on OpenGL ES 2.0). However, in addition to rendering cool looking demoscene's visualizations like this or that, WebGL technology can be used for other purposes. As you can easily guess, it can be used for fingerprinting, too.

The first method is based on the properties provided by the API, e.g. the maximum supported texture size (MAX_TEXTURE_SIZE). There are a lot of such properties and most of them are associated with the graphics card or the driver version for it (and thus things that do not change quite quickly). Those properties were meant to be used as the way to decide if an end-user has sufficient hardware capabilities to run 3D graphics on a page, or should programmer, for example, should deteriorate the parameters of the displayed image to meet hardware power.

More properties can be found on this page (only some of them are suitable for fingerprinting; the rest are ordinary constants). It should be remembered that WebGL is quite a capricious technology and can go down at random moments (for example, due to insufficient free graphics memory). There are also situations in which the browser decides that it is worth switching to software mode, thus changing all visible properties of WebGL (e.g., due to problems with the driver).

The second, more advanced way is to dynamically create the image using code and apply various transforms to it (e.g., rotating the image or adding shading, etc.). Due to the differences described earlier in this article, there is a good chance that two different users will not have rendered the same image, which will allow them to be distinguished.

Interesting fact

WebGL can also be used for other, more exotic attacks, e.g. Rowhammer (the marketing name for a class of attacks potentially allowing to read and even modify the memory of another process by the browser).


Fonts

Checking which fonts are installed on the system is a very popular user fingerprinting vector. It can be accessed in at least two ways.

The simpler of the two is embedding it on the Flash application page. Flash has a simple appropriate API to read the list of fonts available in the system. This method is slowly becoming obsolete due to the withdrawal of major browsers from Flash support (by default it is disabled or even not installed) and slow migration to HTML5.

The second method is much smarter -- in the case of browsers, there is a being that can be simply called "default fonts." These are types of fonts that can be found in the browser regardless of the manufacturer, version, or operating system (e.g., serif, sans-serif, and monospace). The specific fonts behind each type vary by the operating system, e.g. the default serif font for Firefox will usually be:

  • Windows - Times New Roman,
  • Mac OS X - Times,
  • Linux - serif.
"Okay, but what do we do after this?" a confused reader will ask.

Well, browsers were implemented with a fallback mechanism, in case the too avant-garde designer chose such an elite and hipster font that only they and Salvador Dali himself have it in the system. It looks something like this:

font-family: Lato, Sans-Serif;

What does it mean for the browser: display this text with the font "Lato", and if it is not there, use the font marked by the system as the default for sans-serif font type. Where is the enumeration? Well, you can:

  • choose a long and characteristic text,
  • program browser to render only selected fallback (e.g. sans-serif for "Lato"),
  • measure the length of the rendered text,
  • choose the font you want to test for presence in the system,
  • render the selected font with fallback,
  • measure the width of the result obtained.
If the width is the same as the fallback font, we are unlucky -- font is not in the system (the sans-serif selected by us previously rendered). Otherwise, the font is on the system (width value other than fallback).

This approach has quite a few obvious traps, e.g.:

  • we must have a font list prepared in advance,
  • the list may change its fingerprinting efficiency due to the origin of the traffic (e.g., in the case of Arab countries it should be different than European),
  • we cannot add entries to font lists on the basis of "how much the factory gave" -- sudden loading of a million fonts from the drive will block the user's browser,
  • on the other hand, browsers would not be themselves if each of them displayed the same element in the same way, so collecting the width gives new possibilities of fingerprinting (the differences are due to the browser's rendering engines, as well as the properties associated with the graphics card and system).

WebRTC

This technology is mainly used for hole punching, i.e. establishing a direct connection between two endpoints (in this case between two browsers). This type of solution is useful mainly for audio and video-audio communication, where any delay drastically reduces the quality of service. WebRTC works, in general, in the following way:

  • Two browsers appear that want to connect to each other.
  • Both browsers know nothing about each other and do not have the appropriate network information needed to establish a direct connection. Instead, they have information about the shared STUN server and the signal server.
  • Browsers connect to the STUN server, obtaining information about the external network address.
  • Browsers connect to the signal server and exchange the necessary metadata to establish a direct connection.
It turns out that the connection negotiation (ICE in the picture) works in several modes. Some modes guarantee better connection performance at the expense of privacy.

Fingerprinting 101 Part 2

Negotiations may result in the disclosure of all IP addresses on the browser route (even if a VPN or proxy was used). Local IP addresses may also leak, which -- especially in the case of IPv6 -- can be used for fingerprinting (nowadays some browsers fixed that leak).


Canvas fingerprint

This is another fingerprinting method based on anomalies associated with the graphic stack (similar to WebGL fingerprinting). HTML5 introduced a canvas mechanism that allows one to program 2D graphics using JavaScript. The sample fingerprinting code might look like this:

// we create a canvas object
var canvas = document.createElement ('canvas');


// we inform the browser that we will "draw" in 2d

var context = canvas.getContext ('2d');

// we set the text we want to "draw" in the canvas

var textToRender = 'I like pies!';

// we set the font and size of the text
context.font = "14px 'Arial'";

// we draw the text inside canvas with black
context.fillText (textToRender, 10.50);

// we replace the rendered image with a text form so that you can send or compare it
return canvas.toDataURL ();



Of course, it is okay to vent your artistic vision and modify the inscription more abruptly or even focus on the graphic itself -- the more complicated the picture, the more granular the fingerprint should be.


Audio fingerprint

The last example of fingerprint technique is an audio fingerprint. It is a transfer of the concept of fingerprinting based on a graphic stack to... a sound stack. In the case of WebGL or canvas, we used the interaction specifics between:

  • browser,
  • operating system,
  • graphics card driver,
  • the graphics card itself.
However, here we do the same with the use of a sound card, using the API available in HTML5 -- more specifically AudioContext. Examples of such a fingerprint with visualization can be found here. The code requires a longer introduction to the AudioAPI nuances, so for the sake of sanity of the reader, it will not be quoted and analyzed here.


Nethone is a global provider of AI-driven KYU (Know Your Users) solutions that allows online merchants to understand their end-users and prevent online fraud. By using machine learning technology, Nethone is able to detect and prevent card-not-present fraud as well as account takeover. Founded in 2016 by data scientists and security experts, Nethone serves eCommerce, digital goods, travel, and financial industries on a global scale.

Blue-tinted background of a man watching a webinar

Host a Webinar with the MRC

Help the MRC community stay current on relevant fraud, payments, and law enforcement topics.
Submit a Request

Publish Your Document with the MRC

Feature your case studies, surveys, and whitepapers in the MRC Resource Center.
Submit Your Document