02Dec 2020

Application to Demonstrate Browser Fingerprinting

In the first part, we explained the theoretical principles of Browser Fingerprinting. In this part, we will realize a concept-proof application to demonstrate the principle.

Running the Experiment

Running the experiment for browser fingerprint

We will, therefore, run a website that will collect browser information. We choose to code the server-side in PHP. Our probes will collect information about the browser on parallel and upload the information to the server-side.

Headers Analyze (PHP)

The function AnalyzeHeaders will process the headers sent by the browser (user-agent etc…)

function AnalyzeHeaders($headers,$uuid)
{
include("Variables.php");
/*
$test_success
$Connection 
$User-Agent          
$Accept              
$Accept-Encoding     
$Accept-Language     
$Accept-Charset      
$Cache-Control       
$Content-Length      
$Content-Type        
$Expect              
$If-Match            
$If-Modified-Since   
$If-None-Match       
$If-Range            
$If-Unmodified-Since 
$Max-Forwards        
$Pragma              
$Range               
$TE                  
$Upgrade             
$Via                 
$Warning             
$misc_headers        
*/	
	
	 foreach ($headers as $name => $value) {
    echo "<div>$name: $value</div>";
    Tracer("Header data $name : $value",1,$uuid);	
	 }
	
	$headers["uuid"]=$uuid;
	$headers["testname"]="headers";
	$headers["test_success"]=1;
	Tracer("post Header data to ".$hostserver."/test_result_sql.php",1,$uuid);	
		curl_post_async($hostserver."/test_result_sql.php",$headers);
}

Javascript

javascript in browser fingerprinting

We use javascript to collect primary information about plugins and micro-versions of the plugins. Most of the information is obtained through the navigator object.

function getNavInfos()
{
	
	var plugs="<table border='1'><tr><th>plug-in name</th><th>description</th><th>filename</th></tr>";
	var plug2="";
	for(i=0;i<navigator.plugins.length;i++)
		{
		plugs=plugs+"<tr id='description'><td>"+navigator.plugins[i].name+"</td><td>"+navigator.plugins[i].description+"</td><td>"+navigator.plugins[i].filename+"</td></tr>";
		plug2=plug2+"[PLUGIN NAME="+navigator.plugins[i].name+"][PLUGIN DESCRIPTION="+navigator.plugins[i].description+"][PLUGIN FILENAME="+navigator.plugins[i].filename+"])";
		 
		//level2 mime types
	     var nav2=navigator.plugins[i];
	     if(nav2.length != null)
	    	 {
			     for(j=0;j<nav2.length;j++)
			    	 {
			    	 if((nav2[j].type!=null)&&(nav2[j].description!=null))
			    		 {
			    		 plugs=plugs+"<tr><td>"+nav2[j].type+"</td><td>"+nav2[j].description+"</td><td>"+nav2[j].suffixes+"</td></tr>";
			    		 plug2=plug2+"([mime type="+nav2[j].type+"][mime description="+nav2[j].description+"][mime suffixes="+nav2[j].suffixes+"])";
			    		 }
			    	 }
	    	 }
		
		}
	
	plugs=plugs+"</table>";
	
	
	
	var language=navigator.language;
	var product=navigator.product;
	var mimeTypes=null;
	var appVersion=navigator.appVersion;
	var plugins=plug2;
	var onLine=navigator.onLine;
	var platform=navigator.platform;
	var vendor=navigator.vendor;
	var appCodeName=navigator.appCodeName;
	var cookieEnabled=navigator.cookieEnabled;
	var geolocationEnabled=((navigator.geolocation==null)?"false":"true");
	var appName=navigator.appName;
	var productSub=navigator.productSub;
	var userAgent=navigator.userAgent ;
	var vendorSub=navigator.vendorSub;
	
	var test_success=1;

	if(product==null)
	{
		test_success=0;
	}
	
	//post to sql
	if (typeof console == "undefined") var console = { log: function() {} };


	jQuery.post("test_result_sql.php", { testname: 'navigator_javascript' , test_success: test_success , var_language:language ,  var_product:product ,  var_mimeTypes:mimeTypes ,  var_appVersion:appVersion ,  var_plugins:plugins ,  var_onLine:onLine ,  var_platform:platform ,  var_vendor:vendor ,  var_appCodeName:appCodeName ,  var_cookieEnabled:cookieEnabled ,  var_geolocationEnabled:geolocationEnabled ,  var_appName:appName ,  var_productSub:productSub ,  var_userAgent:userAgent ,  var_vendorSub:vendorSub , uuid:window.uuid},function( data ) {
	         Tracer("test navigator javascript post:"+ data,1,window.uuid);  
	       } );
	
	
	
	//alert(plugs);
	
return "language="+navigator.language+"<br>"+
"product="+navigator.product+"<br>"+
"mimeTypes="+navigator.mimeTypes+"<br>"+
"appVersion="+navigator.appVersion+"<br>"+
"plugins="+plugs+"<br>"+
"onLine="+navigator.onLine+"<br>"+
"platform="+navigator.platform+"<br>"+
"vendor="+navigator.vendor+"<br>"+
"appCodeName="+navigator.appCodeName+"<br>"+
"cookieEnabled="+navigator.cookieEnabled+"<br>"+
"geolocation Enabled="+((navigator.geolocation==null)?"false":"true")+"<br>"+
"appName="+navigator.appName+"<br>"+
"productSub="+navigator.productSub+"<br>"+
"userAgent="+navigator.userAgent +"<br>"+
"vendorSub="+navigator.vendorSub+"<br>";
}

function getNavInfosTXT()
{
	
	plugs="";
	for(i=0;i<navigator.plugins.length;i++)
		{
		plugs=plugs+"decription="+navigator.plugins[i].name+",name="+navigator.plugins[i].description+",filename="+navigator.plugins[i].filename+"%";
		
		//level2 mime types
	     var nav2=navigator.plugins[i];
	     if(nav2.length != null)
	    	 {
			     for(j=0;j<nav2.length;j++)
			    	 {
			    	 if((nav2[j].type!=null)&&(nav2[j].description!=null))
			    		 {
			    		 plugs=plugs+",type="+nav2[j].type+",description="+nav2[j].description+",suffixes="+nav2[j].suffixes+",";
			    		 }
			    	 }
	    	 }
		
		}
	
	
	//alert(plugs);
	
return "language="+navigator.language+"<br>"+
"product="+navigator.product+"<br>"+
"mimeTypes="+navigator.mimeTypes+"<br>"+
"appVersion="+navigator.appVersion+"<br>"+
"plugins="+plugs+"<br>"+
"onLine="+navigator.onLine+"<br>"+
"platform="+navigator.platform+"<br>"+
"vendor="+navigator.vendor+"<br>"+
"appCodeName="+navigator.appCodeName+"<br>"+
"cookieEnabled="+navigator.cookieEnabled+"<br>"+
"geolocation Enabled="+((navigator.geolocation==null)?"true":"false")+"<br>"+
"appName="+navigator.appName+"<br>"+
"productSub="+navigator.productSub+"<br>"+
"userAgent="+navigator.userAgent +"<br>"+
"vendorSub="+navigator.vendorSub+"<br>";
}

Javascript will run a certain amount of checks. For example, it can detect the OS version:

function os_test(uuid)
{
	try{
	var os_js_test_success=1;
	var os_js=null;
	if (typeof BrowserDetect == "undefined")
	{
	os_js_test_success=0;
	os_js=null;
	}else if(BrowserDetect.OS==null)
	{
	os_js_test_success=0;
	os_js=null;
	}
	else
	{
	os_js=BrowserDetect.OS;
	window.document.getElementById('OS').innerHTML=os_js;
	}

		jQuery.post("test_result_sql.php", { testname: 'os_javascript' , test_success: os_js_test_success ,os:os_js, uuid:uuid},function( data ) {
	   	         Tracer("test os javascript post:"+ data,1,window.uuid);  
	   	       } );


	}
	catch(err)
	{
		Tracer(err.description,3,window.uuid);
	}

}

Our javascript probes can also detect the information related to the screen:

function getViewPort()
 {
 var viewportwidth;
 var viewportheight;
  
  if (typeof window.innerWidth != 'undefined')
 {
      viewportwidth = window.innerWidth,
      viewportheight = window.innerHeight
 }
  
 else if (typeof document.documentElement != 'undefined'
     && typeof document.documentElement.clientWidth !=
     'undefined' && document.documentElement.clientWidth != 0)
 {
       viewportwidth = document.documentElement.clientWidth,
       viewportheight = document.documentElement.clientHeight
 }
  
 // older versions of IE
  
 else
 {
       viewportwidth = document.getElementsByTagName('body')[0].clientWidth,
       viewportheight = document.getElementsByTagName('body')[0].clientHeight
 }
return viewportwidth+'x'+viewportheight;
}
 
 function getScreenInfos(session_id)
 {

	 jQuery.post("test_result_sql.php", { testname: 'screen' , test_success: 1 , Height: screen.availHeight, Width: screen.availWidth ,colorDepth:screen.colorDepth,totalheight:screen.height,pixelDepth:screen.pixelDepth,totalwidth: screen.width ,uuid:session_id},function( data ) {
       console.log("test screen post:"+ data);  
     } );
	 
	 
	 
return	 "Height="+screen.availHeight+",Width="+screen.availWidth+",colorDepth="+screen.colorDepth+",totalheight="+screen.height+",pixelDepth="+screen.pixelDepth+",totalwidth="+screen.width;
	 
 }

Flash

Flash

If the Flash plugin is present (and enabled) in the browser we check the hardware information. They are reached through the Capabilities object.

public function hardware():Array {
      
      var hardw:Array = new Array();
      
	  hardw[0]=Capabilities.avHardwareDisable;
	  hardw[1]=Capabilities.hasAccessibility;
	  hardw[2]=Capabilities.hasAudio;
	  hardw[3]=Capabilities.hasAudioEncoder;
	  hardw[4]=Capabilities.hasEmbeddedVideo
	 hardw[5]=Capabilities.hasMP3
	  hardw[6]=Capabilities.hasPrinting
	  hardw[7]=Capabilities.hasScreenBroadcast
	  hardw[8]=Capabilities.hasScreenPlayback
	  hardw[9]=Capabilities.hasStreamingAudio
	  hardw[10]=Capabilities.hasVideoEncoder
	  hardw[11]=Capabilities.isDebugger
	  hardw[12]=Capabilities.language
	   hardw[13]=Capabilities.localFileReadDisable
	   hardw[14]=Capabilities.manufacturer
	   hardw[15]=Capabilities.os
	   hardw[16]=Capabilities.pixelAspectRatio
	  hardw[17]=Capabilities.playerType
	   hardw[18]=Capabilities.screenColor
	  hardw[19]= Capabilities.screenDPI
	  hardw[20]= Capabilities.screenResolutionX
	  hardw[21]= Capabilities.screenResolutionY
	   hardw[22]=Capabilities.serverString
	  hardw[23]= Capabilities.version;
      
      return hardw;
      
    }

As well as the font information:

package {
  
  import flash.display.Sprite; 
  import flash.display.LoaderInfo;
  import flash.text.Font;
  import flash.external.ExternalInterface;  
  
  public class FontList extends Sprite {
    
    public function FontList() {   
      var params:Object = loadParams();
      loadExternalInterface(params);
    }    
    
    private function loadParams():Object {
      return LoaderInfo(this.root.loaderInfo).parameters;
    }
    
    private function loadExternalInterface(params:Object):void {
      ExternalInterface.marshallExceptions = true;
      ExternalInterface.addCallback("fonts", fonts);      
      ExternalInterface.call(params.onReady, params.swfObjectId);
    }
    
    public function fonts():Array {
      return Font.enumerateFonts(true).sortOn("fontName", Array.CASEINSENSITIVE);
    }
        
  }
  
}

Java

If java is present and enabled in the browser, we use it to retrieve hardware information and general information about the system:

import com.sun.servicetag.SystemEnvironment;
import java.lang.management.ManagementFactory;
public class Config extends java.applet.Applet{
   public void paint(java.awt.Graphics g){
	   try{
        g.drawString("System Informations: ",50,10);
        SystemEnvironment se = SystemEnvironment.getSystemEnvironment();
        g.drawString("CpuManufacturer:"+se.getCpuManufacturer(),50,40);
        g.drawString("HostID:"+se.getHostId(),50,60);
        g.drawString("Host Name:"+se.getHostname(),50,80);
        g.drawString("OS Architecture:"+se.getOsArchitecture(),50,100);
        g.drawString("OS Name:"+se.getOsName(),50,120);
        g.drawString("OS Verstion:"+se.getOsVersion(),50,140);
        g.drawString("Serial no:"+se.getSerialNumber(),50,160);
        g.drawString("System model:"+se.getSystemModel(),50,180);
        g.drawString("System Manufacturer:"+se.getSystemManufacturer(),50,200);
        com.sun.management.OperatingSystemMXBean mxbean = (com.sun.management.OperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean();
       // g.drawString("Available Processors:"+mxbean.getAvailableProcessors(),50,220);
 g.drawString("TotalRAM:"+mxbean.getTotalSwapSpaceSize()/(1024*1024*1024)+""+"GB",50,240);
  g.drawString("RAM SIZE :" + (mxbean.getTotalPhysicalMemorySize()/(1024*1024*1024))+ " GB ",50,260);
	   }
	   catch(Exception e)
	   {
		   g.drawString(e.getMessage()); 
	   
	   }
}

Additionally, we also get a list of fonts:

public class listFonts extends Applet {
  

  public void init() {
    setBackground(Color.white); 
    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    String [] fonts = ge.getAvailableFontFamilyNames();
    String fontslist="";
    List lst = new List(fonts.length,false);
    for ( int i = 0 ; i < fonts.length ; i++ )
    {
      lst.add(fonts[i]);
      fontslist=fontslist+fonts[i]+";";
    }
    setLayout(new BorderLayout());
    add("Center",lst);
    
    try {
        getAppletContext().showDocument
          (new URL("javascript:postFontJava(\"" + fontslist +"\")"));
        
    }
      catch (MalformedURLException me) { }
    
    
    
  }

  public String getAppletInfo() {
    return "Font list:";
  }

Silverlight

using Silverlight in browser fingerprinting

Finally, if Silverlight is present and enabled in the navigator, we use it to get system information:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Text;

using System.IO;

using System.Windows.Browser;
namespace SilverlightApplication1
{
    public partial class MainPage : UserControl
    {


        public string Encode(string str)
        {
            byte[] encbuff = System.Text.Encoding.UTF8.GetBytes(str);
            return Convert.ToBase64String(encbuff);
        }


        public MainPage()
        {
            InitializeComponent();

            // Collect system info
            StringBuilder sbStringBuilder = new StringBuilder();

            sbStringBuilder.AppendLine("-------BROWSER INFO---------");
            sbStringBuilder.AppendLine("Browser Name = " + HtmlPage.BrowserInformation.Name);
            String BrowserName = HtmlPage.BrowserInformation.Name;
            sbStringBuilder.AppendLine("Browser Version = " + HtmlPage.BrowserInformation.BrowserVersion.ToString());
            String BrowserVersion = HtmlPage.BrowserInformation.BrowserVersion.ToString();
            sbStringBuilder.AppendLine("UserAgent = " + HtmlPage.BrowserInformation.UserAgent);
            String UserAgent = HtmlPage.BrowserInformation.UserAgent;
            sbStringBuilder.AppendLine("Platform = " + HtmlPage.BrowserInformation.Platform);
            String Platform = HtmlPage.BrowserInformation.Platform;
            sbStringBuilder.AppendLine("CookiesEnabled = " + HtmlPage.BrowserInformation.CookiesEnabled.ToString());
            String CookiesEnabled = HtmlPage.BrowserInformation.CookiesEnabled.ToString();
            sbStringBuilder.AppendLine("ProductName = " + HtmlPage.BrowserInformation.ProductName.ToString());
            String ProductName = HtmlPage.BrowserInformation.ProductName.ToString();
            sbStringBuilder.AppendLine("ProductVersion = " + HtmlPage.BrowserInformation.ProductVersion.ToString());
            String ProductVersion = HtmlPage.BrowserInformation.ProductVersion.ToString();
            sbStringBuilder.AppendLine("-------O.S INFO---------");

            sbStringBuilder.AppendLine("OSVersion: " + Environment.OSVersion);
            String OSVersion = ""+Environment.OSVersion;
            //sbStringBuilder.AppendLine("System start: " +
            //   Environment.TickCount.ConvertToNiceTime());
            sbStringBuilder.AppendLine("CLR Version: " + Environment.Version);
            String CLRVersion = ""+Environment.Version;
            sbStringBuilder.AppendLine("Number of processors: " + Environment.ProcessorCount);
            String Numberofprocessors = ""+Environment.ProcessorCount;
            //  Environment.

            //Get GPU infos
            System.Collections.ObjectModel.ReadOnlyCollection<String> ClientInfos = Analytics.ClientInformation;
            String sClientInfos = "";
            sbStringBuilder.AppendLine("-------CLIENT INFOS---------");

            try
            {
                IEnumerator<String> cienum = ClientInfos.GetEnumerator();


                while (cienum.MoveNext())
                {
                    String cinfo = cienum.Current;
                    sbStringBuilder.AppendLine(cinfo);
                    sClientInfos = sClientInfos + cinfo + ";";
                }
            }
            catch (Exception e)
            {
                // e.StackTrace();
            }

            System.Collections.ObjectModel.ReadOnlyCollection<AudioCaptureDevice> ccd = CaptureDeviceConfiguration.GetAvailableAudioCaptureDevices();
            String audioCaptureDevices = "";
            IEnumerator<AudioCaptureDevice> ccdie = ccd.GetEnumerator();
            sbStringBuilder.AppendLine("-------AUDIO CAPTURE DEVICES---------");
            while (ccdie.MoveNext())
            {
                sbStringBuilder.AppendLine("---------------");
                AudioCaptureDevice acd = ccdie.Current;
                sbStringBuilder.AppendLine("name: " + acd.FriendlyName);
                audioCaptureDevices = audioCaptureDevices + acd.FriendlyName + ";";
            }

            System.Collections.ObjectModel.ReadOnlyCollection<VideoCaptureDevice> cvcd = CaptureDeviceConfiguration.GetAvailableVideoCaptureDevices();

            String videoCaptureDevices = "";

            IEnumerator<VideoCaptureDevice> vcdie = cvcd.GetEnumerator();


            sbStringBuilder.AppendLine("-------VIDEO CAPTURE DEVICES---------");
            while (vcdie.MoveNext())
            {
                sbStringBuilder.AppendLine("---------------");
                VideoCaptureDevice vcd = vcdie.Current;
                sbStringBuilder.AppendLine("name: " + vcd.FriendlyName);
                videoCaptureDevices = videoCaptureDevices + vcd.FriendlyName + ";";
            }

            Analytics an = new Analytics();
            System.Collections.ObjectModel.ReadOnlyCollection<GpuInformation> gpucollect = an.GpuCollection;

            IEnumerator<GpuInformation> gpuenum = gpucollect.GetEnumerator();
            String GPU = "";
            sbStringBuilder.AppendLine("-------GPU---------");
            while (gpuenum.MoveNext())
            {
                sbStringBuilder.AppendLine("---------------");

                GpuInformation gpuinfo = gpuenum.Current;
                sbStringBuilder.AppendLine("Device ID=" + gpuinfo.DeviceId);
                GPU = GPU + "Device ID=[" + gpuinfo.DeviceId + "]";
                sbStringBuilder.AppendLine("Vendor ID=" + gpuinfo.VendorId);
                GPU = GPU + "Vendor ID=[" + gpuinfo.VendorId + "]";
                sbStringBuilder.AppendLine("Driver=" + gpuinfo.DriverVersion);
                GPU = GPU + "Driver=[" + gpuinfo.DriverVersion + "]";
                GPU = GPU + "//";
            }
            this.CPU_INFOS.Text = sbStringBuilder.ToString();
            //GET THE LIST OF FONTS
            String fonts = "";
            var typefaces = System.Windows.Media.Fonts.SystemTypefaces;
            sbStringBuilder.AppendLine("---- Fonts -----");
            foreach (System.Windows.Media.Typeface face in typefaces)
            {
                System.Windows.Media.GlyphTypeface g;
                //    ComboBoxItem comboBoxItem = new ComboBoxItem();


                face.TryGetGlyphTypeface(out g);

                FontSource fs = new FontSource(g);
                var fontname = g.FontFileName;
                fonts = fonts + fontname.ToString() + ";";
                sbStringBuilder.AppendLine(fontname.ToString());
            }
            this.CPU_INFOS.Text = sbStringBuilder.ToString();


            //export the data to javascript
            try
            {
             
this.log.Text = "Execution of postSilverlightTest in javascript:   ";
                HtmlPage.Window.Eval("postSilverlightTest('" + BrowserName + "','" + BrowserVersion + "','" + UserAgent + "','" + Platform + "','" + CookiesEnabled + "','" + ProductName + "','" + ProductVersion + "','" + OSVersion + "','" + CLRVersion + "','" + Numberofprocessors + "','" + sClientInfos + "','" +  audioCaptureDevices + "','" + videoCaptureDevices + "','" + GPU + "','" + fonts + "');");
                this.log.Text = this.log.Text +"OK";
            }
            catch (Exception e)
            {

                string error = e.ToString();

               this.log.Text = this.log.Text + error;

            }
          
        }
    }

   
}

Once all our probes are coded and the “glue” between the plug-ins, javascript and PHP have been created, we run our experiment, we gather and collect visitor’s IPs, geolocation and browser fingerprint in a SQL database.

Here is an example of the computation of a browser fingerprint:

Conclusion: When running the experiment, we did not found so far duplicated fingerprint and we were able to identify returning visitors who had changed just a few parameters, showing how flexible the fingerprint was. It demonstrates that tracking cookies are not necessarily needed to uniquely identify visitors. Such techniques should be taken seriously because they have very strong implications in terms of privacy.

Annex 1: full link for project code source download: 

https://drive.google.com/file/d/1dmjCfyarnb6THjlHE6Iq9G5Q_dk6YhcV/view?usp=sharing

Acodez is a leading website design and web development company in India. We offer all kinds of web design and web development services to our clients using the latest technologies. We are also a top digital marketing agency providing SEO, SMM, SEM, Inbound marketing services, etc at affordable prices. For further information, please contact us.

Looking for a good team
for your next project?

Contact us and we'll give you a preliminary free consultation
on the web & mobile strategy that'd suit your needs best.

Contact Us Now!
Jamsheer K

Jamsheer K

Jamsheer K, is the Tech Lead at Acodez. With his rich and hands-on experience in various technologies, his writing normally comes from his research and experience in mobile & web application development niche.

Get a free quote!

Brief us your requirements & let's connect

Leave a Comment

Your email address will not be published. Required fields are marked *