{"id":13469417,"url":"https://github.com/OliverBrotchie/CSS-Fingerprint","last_synced_at":"2025-03-26T06:32:11.806Z","repository":{"id":42969387,"uuid":"357365835","full_name":"OliverBrotchie/CSS-Fingerprint","owner":"OliverBrotchie","description":"Pure CSS device fingerprinting.","archived":false,"fork":false,"pushed_at":"2022-01-04T15:55:34.000Z","size":813,"stargazers_count":284,"open_issues_count":2,"forks_count":22,"subscribers_count":5,"default_branch":"main","last_synced_at":"2024-10-29T23:55:29.478Z","etag":null,"topics":["browser-fingerprinting","cookies","css","fingerprinting","noscript","privacy","tracking"],"latest_commit_sha":null,"homepage":"https://csstracking.dev","language":"Sass","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/OliverBrotchie.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-04-12T23:22:31.000Z","updated_at":"2024-09-11T06:05:58.000Z","dependencies_parsed_at":"2022-07-10T21:46:14.689Z","dependency_job_id":null,"html_url":"https://github.com/OliverBrotchie/CSS-Fingerprint","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OliverBrotchie%2FCSS-Fingerprint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OliverBrotchie%2FCSS-Fingerprint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OliverBrotchie%2FCSS-Fingerprint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OliverBrotchie%2FCSS-Fingerprint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/OliverBrotchie","download_url":"https://codeload.github.com/OliverBrotchie/CSS-Fingerprint/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245604177,"owners_count":20642957,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["browser-fingerprinting","cookies","css","fingerprinting","noscript","privacy","tracking"],"created_at":"2024-07-31T15:01:38.729Z","updated_at":"2025-03-26T06:32:10.236Z","avatar_url":"https://github.com/OliverBrotchie.png","language":"Sass","funding_links":[],"categories":["Sass"],"sub_categories":[],"readme":"\u003cbr/\u003e\n\n\u003cimg src=\"/img/logo.svg\" height=\"150\" width=\"675\" margin=\"30px\"/\u003e\n\n#\n\n\u003cbr/\u003e\n\n**An experimental method for CSS based fingerprinting and a pure CSS 'supercookie'.**\n\n### Links:\n\n- [Live Demonstration](https://csstracking.dev)\n- [Study Repository](https://github.com/OliverBrotchie/CSS-Fingerprint-Study)\n\n## Contents\n\n- [Contents](#contents)\n- [What is it?](#what-is-it)\n- [How does it work?](#how-does-it-work)\n- [CSS Cookie](#css-cookie)\n- [Why is this important?](#why-is-this-important)\n- [Examples](#examples)\n- [Calculating Device Uniqueness](#calculating-device-uniqueness)\n  - [Example](#example)\n- [Improvements and Further Research](#improvements-and-further-research)\n  - [NoScript Detection](#noscript-detection)\n  - [Attribute Profiling](#attribute-profiling)\n  - [Async Loading and JS Interaction](#async-loading-and-js-interaction)\n  - [OS and Browser Detection](#os-and-browser-detection)\n  - [XSS Attacks](#xss-attacks)\n- [Contributing](#contributing)\n- [License](#license)\n\n## What is it?\n\nCSS Fingerprinting is a technique of tracking and gathering information on site visitors. \nThis method exploits the nature of CSS to collect various characteristics about the visitor's \nbrowser and device, which can later be used to either identify or track said visitor.\n\n## How does it work?\n\nBy sending a variety of media queries that apply to specific browser characteristics, \nthe browser will select a set of styles that apply to itself. We then trick the browser \ninto sending this information back to the server by setting the background-image of these \nstyles to a specific URL. The server will then respond with HTTP Status 410 (Gone) to \navoid any requests of these characteristics on subsequent reloads.\n\n**For example, to detect the type of pointer input:**\n\n```css\n\n.pointer {\n  background-image: url('/some/url/pointer=none');\n}\n\n// Coarse (touchscreen)\n@media (any-pointer: coarse) {\n  .pointer {\n    background-image: url('/some/url/pointer=coarse');\n  }\n}\n\n// Fine (mouse)\n@media (any-pointer: fine) {\n  .pointer {\n    background-image: url('/some/url/pointer=fine');\n  }\n}\n\n```\n          \n**Installed fonts can also be detected in a similar manner:**\n\n```css\n\n@font-face {\n  font-family: 'some-font';\n  src: local(some font), url('some/url/some-font');\n}\n\n.some-font {\n  font-family:'some-font';\n  view raw;\n}\n\n```\n\nHowever, this works a little differently; every font not installed on device will send a \nrequest. By comparing the differences between the requests and the full list of fonts, we can\nconclude what fonts are installed.\n\n## CSS Cookie\n\nWe can also track visitors cross-origin by requesting an endpoint on the server \nthat will return a permanent redirect (HTTP status 308) to a unique address. The browser will\nthen permanently make requests to the previously generated unique address whenever the endpoint \nis requested. This creates a pure CSS cookie that is reminisent of the '[supercookie](https://supercookie.me/)' exploit. This cookie is stored for an unlimited amount of time; the only way to remove it\nis to fully clear the browser's cache.\n\n\u003cimg src=\"/img/diagram.png\" title=\"Diagram of 308 redirect\" width=\"500\" height=\"400\" /\u003e\n                    \n## Why is this important?\n\nThis technique avoids anti-tracking methods such as NoScript, VPNs or browser extensions, as it\nrequires no Javascript or Cookies to function.\n\nCurrently, this method is not scalable as it requires over 1MB of CSS downloads and hundreds of \nrequests per user. However, with the next upcoming draft of the CSS specification, \n[CSS Values 4](https://www.w3.org/TR/css-values-4/), it may dramatically shrink the\nnumber of requests per user by allowing the use of custom variables in URLs.\n\n```css\n\n.body {\n  --unique-identifier: 'foo'; // unique generated ID\n  --pointer: 'none';\n  --theme-preference: 'none';\n  \n  // Only make one request\n  background-image: url(\"/some/url/?\" + var(--unique-identifier) + \"\u0026\" + var(--pointer) + \"\u0026\" + var(--theme-preference));\n}\n\n// Detect pointer type and theme\n@media (any-pointer: coarse){\n  body {\n    --pointer: 'coarse';\n  }\n}\n\n@media (prefers-color-scheme: dark) {\n  body {\n    --theme-preference: 'dark';\n  }\n}\n\n```       \n                    \nNot only will the upcoming draft make this method scalable, but it will also increase its precision. \nCurrently, without alternative means, it is hard to conclusively link every request to a specific visitor\nas the only feasible way to determine their origin is to group the requests by the IP address of \nthe connection. However, with the new draft, by generating a randomized string and interpolating \nit into the URL tag for every visitor, we can accurately identify all requests from said visitor.\n\n## Examples\n\nIncluded in this repository you will find an implementation of CSS Fingerprinting using the old\nmethod, [fingerprint.sass](/fingerprint.sass), and an example of how to instantiate it, [example.sass](/example.sass).\n\nYou can find examples of different css-tracking servers in the [examples](/examples) directory.\n\nTo see a complete example (HTML/CSS/Server) check  out the [study repository](https://github.com/OliverBrotchie/CSS-Fingerprint-Study).\n\n\n## Calculating Device Uniqueness\n\nShannon Entropy is used to quantify how identifiable fingerprint is. Let H be the entropy, X a discrete \nrandom variable with possible values `{x1,..., xn }` and `P(X)` a probability mass function. \n\nShannon Entropy takes the following formula:\n\n\u003cimg src=\"https://render.githubusercontent.com/render/math?math=H(x) = -\\sum_{i}{P(x_i)log_bP(x_i)}\" /\u003e\n\nThe entropy of Shannon is in bits where b = 2. One bit of entropy reduces by half the probability \nof an event occurring.\n\n### Example\n\n**Rust**\n\n```rs\n\ntype Fingerprint\u003c'a\u003e = Vec\u003c(\u0026'a str, \u0026'a str)\u003e;\ntype DataSet\u003c'a\u003e = Vec\u003cFingerprint\u003c'a\u003e\u003e;\n\nfn shannon_entropy(data: DataSet, value: usize) -\u003e f64 {\n\n    let key_occurances = data\n        .iter()\n        .flatten()\n        .fold(HashMap::new(), |mut acc, \u0026(key, _)| {\n            *acc.entry(key).or_insert(0) += 1;\n            acc\n        });\n\n    let kv_occurances = data\n        .iter()\n        .flatten()\n        .fold(HashMap::new(), |mut acc, \u0026(key, val)| {\n            *acc.entry((key, val)).or_insert(0) += 1;\n            acc\n        });\n\n    let mut entropy = 0.0;\n\n    for kv in \u0026data[value] {\n        let p = *kv_occurances.get(kv).unwrap() as f64 / *key_occurances.get(kv.0).unwrap() as f64;\n        entropy -= p * p.log2();\n    }\n\n    entropy\n}\n\n```\n\n## Improvements and Further Research\n\nA set of performance and accuracy improvements that could be made to the method.\n\n### NoScript Detection\n\nWhilst many privacy browsers such as Brave will attempt to mask the use of NoScript to avoid fingerprinting,\nthese attempts could be thwarted by applying styles that will only be rendered in `noscript` tags:\n\n```html\n\n\u003cnoscript\u003e\n  \u003cp style='background-image: url(\"/some/url/noscript=true\")'\u003e\n    NoScript Detected\n  \u003c/p\u003e\n\u003c/noscript\u003e\n\n\n```\n\n### Attribute Profiling\n\nCurrently the `fingerprinting.sass` example will test all values between one and an arbitrary limit.\nThis method is highly inefficient and a little inaccurate.\n\n**For example:** CSS pixels in actuality are split into fractions when resolved by the browser and hence two devices\nwith similar, but non-identical dimensions will be counted as the same.\n\nNot only does this method cause inaccuracy but it also is inefficient.\nMost devices can be grouped into categories of similar dimensions.\nIn the case of phones and tablets the differences between their dimensions will be extremely small and a higher\naccuracy is needed to identify the differences. However, there large gaps in size between the different groups (for example between Tablet and Desktop resolutions), which means there is little need for accurate testing between those ranges.\n\n**Further Research:** \nDetermine the optimal precision parameters for both intra and inter group testing.\n\n### Async Loading and JS Interaction\n\nThrough the use of Javascript, we can do several things to improve the accuracy and \nperformance of this technique:\n\n- **Delayed/Async Loading** - By delaying the loading of fingerprinting files with JS, we can ensure \nthat the browser loads the rest of the page before these files, improving page responsiveness.\n- **Sharding** - By splitting the fingerprinting files into component groups we can again reduce the \nperformance cost by downloading them in parallel.\n- **Conditional Execution** - Sharding the files, also opens the possibility of conditional execution.\nIf a fingerprint can be uniquely identified by a subset of shards, there is no need to burden the server\nwith the overhead of loading the full set.\n\n**Further Research:**\nDevelop standardised sharding and conditional execution practices to improve performance and reduce \nserver load.\n\n### OS and Browser Detection\n\nMost operating systems ship with a certain set of default fonts and display configurations.\nBy testing a subset of known default fonts that are included on different operating systems, \nwe can, with a certain degree of confidence, determine which OS is installed on the device.\n\nIf this could be implemented it would dramatically reduce the number of requests per user\nas font-detection is the most costly part of the process.\n\n**Further Research:**\nDetermine a standardized subset to test for. This set should test for the key differences between\nthe defualts of different operating systems.\n\n### XSS Attacks\n\nIf user generated CSS is displayed on websites it may give attackers the ability to track other visitors.\n\n## Contributing\n\nIf you have any problems, changes or additions, please just open an issue or pull request!\n\n## License\n\nAll content is licensed under the [MIT license](https://mit-license.org/) and is purely for educational purposes.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FOliverBrotchie%2FCSS-Fingerprint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FOliverBrotchie%2FCSS-Fingerprint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FOliverBrotchie%2FCSS-Fingerprint/lists"}