Code
function clock() { | |
"use strict"; | |
var strOnError, lngInitClk, lngDiffClk, lngTmp, strOut, i; | |
| |
strOnError = "<clkhz>error</clkhz>"; | |
lngInitClk = 0; | |
lngDiffClk = 0; | |
lngTmp = 0; | |
strOut = ""; | |
i = 0; | |
| |
try { | |
lngInitClk = performance.now() / 1000; | |
lngDiffClk = performance.now() / 1000 - lngInitClk; | |
for (i = 0; i < 20; i++) { | |
lngDiffClk = clockcalc(lngDiffClk, performance.now() / 1000 - lngInitClk); | |
} | |
lngTmp = (Math.round(1 / lngDiffClk)); | |
strOut = "<clkMHz>" + lngTmp + "</clkMHz>"; | |
return strOut; | |
} catch (err) { | |
return strOnError; | |
} | |
} | |
| |
function clockcalc(lngDiff, lngElapse) { | |
var lngrOnError; | |
lngOnError = 0; | |
try { | |
if (lngDiff < 0.00000001) { | |
return lngElapse; | |
} | |
if (lngDiff < lngElapse) { | |
return clockcalc(lngElapse - Math.floor(lngElapse / lngDiff) * lngDiff, lngDiff); | |
} else if (lngDiff == lngElapse) { | |
return lngDiff; | |
} else { | |
return clockcalc(lngElapse, lngDiff); | |
} | |
} catch (err) { | |
return lngOnError; | |
} | |
} |
Validation
Unlike other code on the Internet we do everything possible to verify our code for you. In order to minimize problems and maximize compatibility this code has been verified with JSLint and has been extensively tested with over 1100 OS/Browser combinations using BrowserStack.
Code
function fp_sessionstorage() { | |
"use strict"; | |
var strOnError, strOut; | |
| |
strOnError = "<sessionstorage>true</sessionstorage>"; | |
strOut = ""; | |
| |
try { | |
strOut = "<sessionstorage>" + !!window.sessionStorage + "</sessionstorage>"; | |
return strOut; | |
} catch (err) { // Error when referencing it confirms existence | |
return strOnError; | |
} | |
} |
Validation
Unlike other code on the Internet we do everything possible to verify our code for you. In order to minimize problems and maximize compatibility this code has been verified with JSLint and has been extensively tested with over 1100 OS/Browser combinations using BrowserStack.
Code
function fp_indexedDB() { | |
"use strict"; | |
var strOnError, strOut; | |
| |
strOnError = "<indexedDB>true</indexedDB>"; | |
strOut = ""; | |
| |
try { | |
strOut = "<indexedDB>" + !!window.indexedDB + "</indexedDB>"; | |
return strOut; | |
} catch (err) { // Error when referencing it confirms existence | |
return strOnError; | |
} | |
} |
Validation
Unlike other code on the Internet we do everything possible to verify our code for you. In order to minimize problems and maximize compatibility this code has been verified with JSLint and has been extensively tested with over 1100 OS/Browser combinations using BrowserStack.
Code
function fp_timezone() { | |
"use strict"; | |
var strOnError, dtDate1, dtDate2, strOffset1, strOffset2, strOut; | |
| |
strOnError = "<timezone>Error</timezone>"; | |
dtDate1 = null; | |
dtDate2 = null; | |
strOffset1 = ""; | |
strOffset2 = ""; | |
strOut = ""; | |
| |
try { | |
dtDate1 = new Date(2018, 0, 1); | |
dtDate2 = new Date(2018, 6, 1); | |
strOffset1 = dtDate1.getTimezoneOffset(); | |
strOffset2 = dtDate2.getTimezoneOffset(); | |
strOut = "<timezone>" + strOffset1 + "|" + strOffset2 + "</timezone>"; | |
return strOut; | |
} catch (err) { | |
return strOnError; | |
} | |
} |
Validation
Unlike other code on the Internet we do everything possible to verify our code for you. In order to minimize problems and maximize compatibility this code has been verified with JSLint and has been extensively tested with over 1100 OS/Browser combinations using BrowserStack.
Code
function fp_mathroutines() { | |
"use strict"; | |
var strOnError, strOut; | |
| |
strOnError = "<mathroutines>Error</mathroutines>"; | |
strOut = ""; | |
| |
try { | |
strOut = "<mathroutines>" + ((Math.exp(10) + 1 / Math.exp(10)) / 2) + "|" + Math.tan(-1e300) + "</mathroutines>"; | |
return strOut; | |
} catch (err) { | |
return strOnError; | |
} | |
} |
Validation
Unlike other code on the Internet we do everything possible to verify our code for you. In order to minimize problems and maximize compatibility this code has been verified with JSLint and has been extensively tested with over 1100 OS/Browser combinations using BrowserStack.
Typically in the device fingerprint world, one would query a number of common navigator objects and from that attempt to determine if the device was one used by the account holder in the past. I liken this to attempting to uniquely identify a car, but you are saddled by only querying a few parts of the car. For example, color, number of wheels, number of doors. On the surface, it works until you get two sports cars that are red. A better technique is to take all the data points accessible and log those, not all data points, of course, would be applicable such as tint level of the windows. For cars with no tint, it would be a waste to collect a value of zero, so for those cars, it's simply not logged.
The technique presented is similar to the above idea, instead of collecting only a handful of common elements, collect everything offered. We have been saddled with grabbing a subset of navigator values which limit the amount of entropy we can pick up. This was mostly done to avoid the complexity around rule-based antifraud systems, machine learning techniques don't suffer from the same limitations. My solution to this is to walk the navigator object and retrieve values. This as you will see yields far more entropy than simply querying individual properties.
How It Works
The javascript code presented is straightforward. The function below grabs the navigator object and walks each available property and the associated values. We skip properties that return objects, such as screen settings, for now, we'll cover that in part 2. What you will notice is that browser families tend to have one or more unique properties, properties may changes across versions as features are added and removed, and the order of the properties helps identify the browser family. So even if an attacker spoof the values, the order gives away the fact that the spoof was done. As far as I am aware, there are no add-ins that let you alter every value or change the order. This would require a malicious actor to do some work to replicate the device fingerprint. The output of this function is a string containing the property and value in the form of property=value, with each entry delimited by the '|' character. The typical properties are as follows:
Code
activeVRDisplays (Edge, Firefox) | |
appCodeName (Chrome, Edge, IE, iOS, Silk) | |
appMinorVersion (IE) | |
appName (Chrome, Edge, IE, iOS, Silk) | |
appVersion (Chrome, Edge, IE, iOS, Silk) | |
browserVersion (IE) | |
cookieEnabled (Firefox, IE, iOS, Silk) | |
cpuClass (IE) | |
doNotTrack (Chrome, Firefox, Silk) | |
hardwareConcurrency (Chrome, Edge, Silk) | |
language (Chrome, Edge, IE, iOS, Silk) | |
languages (Chrome) | |
maxTouchPoints (Chrome, Edge, IE, Silk) | |
msManipulationViewsEnabled (Edge) | |
msMaxTouchPoints (IE) | |
msPointerEnabled (IE) | |
onLine (Chrome, Edge, IE, iOS, Silk) | |
oscpu (Firefox) | |
platform (Chrome, Edge, IE, iOS, Silk) | |
pointerEnabled (Edge, IE) | |
product (Chrome, Edge, IE, iOS, Silk) | |
productSub (Chrome, Edge, Firefox, iOS, Silk) | |
standalone (iOS) | |
systemLanguage (IE) | |
userAgent (Chrome, Edge, IE, iOS, Silk) | |
userLanguage (IE) | |
vendor (Chrome, Edge, Firefox, IE, iOS, Silk) | |
vendorSub (Chrome, Edge, Firefox, iOS, Silk) | |
webdriver (Edge, IE, iOS) |
Entropy Estimate: TBD, likely 33+
Code
The javascript function below enumerates all navigator properties with a fixed value. You may also download this code here: fp_nav.zip. Note: Depending on your output method you may need to URL encode the returned results.
Code
function fp_nav() { | |
"use strict"; | |
| |
var strOnError, strKey, Value, strValue, strTmp, strOut; | |
| |
strOnError = "Error"; | |
strKey = ""; | |
Value = ""; | |
strValue = ""; | |
strTmp = ""; | |
strOut = ""; | |
| |
try { | |
for (strKey in navigator) { | |
Value = navigator[strKey]; | |
if (Value === null || (typeof Value !== "function" && typeof Value !== "object")) { | |
strValue = String(Value); | |
if (strValue === "null") { | |
strValue = "NULL"; | |
} | |
if (strValue === "") { | |
strValue = "_"; | |
} | |
strTmp = strTmp + "<" + strKey + ">" + strValue + "</" + strKey + ">"; | |
} | |
} | |
strOut = strTmp.slice(0, strTmp.length - 1); | |
return strOut; | |
} catch (err) { | |
return strOnError; | |
} | |
} |
Validation
Unlike other code on the Internet we do everything possible to verify our code for you. In order to minimize problems and maximize compatibility this code has been verified with JSLint and has been extensively tested with over 1100 OS/Browser combinations using BrowserStack.
Reference
Navigator Object in javascript. How to determine all properties. (Aug 18, 2011). In StackOverflow. Retrieved September 22, 2017, from https://stackoverflow.com/questions/7101751/navigator-object-in-javascript-how-to-determine-all-properties/
This technique received media coverage in 2016 after the publication of the research paper "Online Tracking: A 1-million-site Measurement and Analysis" by Steven Englehardt and Arvind Narayanan at Princeton University.
In a study of 18,000 machines, the AudioContext device fingerprint was resulting in approximately 5.4 bits of entropy, however it's beleived that the entropy would be higher in a larger data set. It may also be possible to optimize the input data to improve entropy further.
How It Works
There are two common methods in the wild to fingerprint the audio stack, they are slightly different in their approach but both are equally effective.
The graphic above shows the use of the AudioContext property to device fingerprint the audio stack. First, a triangle wave is generated using an OscillatorNode. This signal is passed through an AnalyserNode and a ScriptProcessorNode. Finally, the signal is passed into a through a GainNode with gain set to zero to mute any output before being connect to the AudioContext destination (e.g. the computer speakers). The AnalyserNode provides access to a Fast Fourier Transform (FFT) of the audio signal, which is captured using the onaudioprocess event handler added by the ScriptProcessorNode. The resulting FFT is fed into a hash and used as a fingerprint.
The graphic above shows the use of the OfflineAudioContext property to device fingerprint the audio stack. First, a sine wave is generated using an OscillatorNode. The output signal is connected to a DynamicsCompressorNode, in order to increase differences in processed audio between systems. The output of this compressor is passed to the buffer of an OfflineAudioContext. A hash of the sum of values from the buffer is then used as a fingerprint.
Below you can see a visualization of the processed OscillatorNode from a test fingerprinting script. It's clear there is enough variation to use this function as a device fingerprint.
Further research needs to be done to determine if either method described above is significantly better, and if a sine, square, sawtooth, triangle wave or custom wave provides better fingerprinting.
Entropy Estimate: 8.1 bits
Code
The javascript function below fingerprints the browser languages. You may also download this code here: TBD
Code
TBD |
Validation
Unlike other code on the Internet we do everything possible to verify our code for you. In order to minimize problems and maximize compatibility this code has been verified with JSLint and has been extensively tested with over 1100 OS/Browser combinations using BrowserStack.
Reference
Online tracking:A 1-million-site measurement and analysis (n.d.). In Princeton Web Census. Retrieved September 01, 2017, from http://randomwalker.info/publications/OpenWPM_1_million_site_tracking_measurement.pdf
The technique received wide media coverage in 2014 after the publication of the reasearch paper "The Web never forgets: Persistant tracking mechanisms in the wild" by researchers from Princeton University and KU Leuven University.
Canvas device fingerprinting has a sizable benefit over other traditional methods, in that it changes much slower than other prints. For example, if you are on Firefox 30 and upgrade to Firefox 42, the print will remain the same. This is due to the fact that the canvas element is essentially device pritning the rendering engine, the graphics driver and GPU. Most people do not change their GPU or graphics driver frequently, that leaves just the rendering engine as the main variable. However, rendering engines tend to be reused from browser version to browser version. Additionally, because headless browsers can't handle graphics, the lack of returned data is a great indicator that a Trojan or RAT may be on your client's computer and being controlled by a miscreant.
In a small study, the canvas device print was resulting in about 5.7 bits of entropy, however it's believed that the entropy would be much higher in larger data sets.
How It Works
In the figure below (1) the browser is requested to draw text on the screen in a hidden box. This text is enlarged, drawn in multiple colors and consists of a large range of text samples. Next (2) the drawing is converted to a base64 URL. Finally, (3) in order to shorten the output the base64 URL is hashed. This output is then used as the canvas device fingerprint.
Code
The javascript function below fingerprints the browser rendering engine. You may also download this code here: fp_canvas.zip
Code
function fingerprint_canvas() { | |
"use strict"; | |
var strOnError, canvas, strCText, strText, strOut; | |
| |
strOnError = "Error"; | |
canvas = null; | |
strCText = null; | |
strText = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`~1!2@3#4$5%6^7&8*9(0)-_=+[{]}|;:',<.>/?"; | |
strOut = null; | |
| |
try { | |
canvas = document.createElement('canvas'); | |
strCText = canvas.getContext('2d'); | |
strCText.textBaseline = "top"; | |
strCText.font = "14px 'Arial'"; | |
strCText.textBaseline = "alphabetic"; | |
strCText.fillStyle = "#f60"; | |
strCText.fillRect(125, 1, 62, 20); | |
strCText.fillStyle = "#069"; | |
strCText.fillText(strText, 2, 15); | |
strCText.fillStyle = "rgba(102, 204, 0, 0.7)"; | |
strCText.fillText(strText, 4, 17); | |
strOut = canvas.toDataURL(); | |
return strOut; | |
} catch (err) { | |
return strOnError; | |
} | |
} |
Validation
Unlike other code on the Internet we do everything possible to verify our code for you. In order to minimize problems and maximize compatibility this code has been verified with JSLint and has been extensively tested with over 1100 OS/Browser combinations using BrowserStack.
Reference
Pixel Perfect: Fingerprinting Canvas in HTML5 (2012). In UCSanDiego Computer Science and Engineering. Retrieved September 01, 2017, from http://cseweb.ucsd.edu/~hovav/dist/canvas.pdf
Browser fingerprints - the invisible cookie you can't delete (December 01,2014). In naked security by Sophos. Retrieved September 01, 2017, from https://nakedsecurity.sophos.com/2014/12/01/browser-fingerprints-the-invisible-cookies-you-cant-delete/
]]>