Saltar al contenido principal
en/blog/obscura/research/network-fingerprinting/

obscura/research/network-fingerprinting

5 min read

Overview

Signals visible at the TCP/IP and transport layer, before any HTTP or JS execution.


1. TCP/IP Stack Fingerprint (JA4T / Satori / p0f)

Mechanism

Passive OS fingerprinting reads the TCP SYN packet to identify the operating system. Each OS TCP/IP stack has a unique combination of:

  • Initial TTL: 64 (Linux/macOS), 128 (Windows), 255 (Some UNIX)
  • Window size: 64240 (Chrome/Linux), 65535 (macOS), 8192 (older systems)
  • MSS (Maximum Segment Size): Derived from MTU
  • TCP options: Order and selection of MSS, SACK_PERM, TIMESTAMP, NOP, WINDOW_SCALE
  • IP flags: DF (Don't Fragment) bit behavior

Formats

JA4T: 64240_2-4-8-1-3_1452_10

Component Meaning Example
64240 TCP Window Size Windows default
2-4-8-1-3 TCP option order (MSS, SACK, TIMESTAMP, NOP, WINDOW) Chrome/Linux order
1452 MSS Derived from MTU
10 Window Scale shift count 2^10 = 1024 multiplier

Satori fingerprint: 64240:64:1:60:M1452,S,T,N,W10:.

OS Detection

OS Window TTL MSS Options
Linux 29200-65535 64 1460 M,S,T,N,W(7-8)
Windows 10/11 64240 128 1460 M,S,T,N,W(10)
macOS 65535 64 1460 M,S,T,N,W(6-7)
Android 29200-65535 64 1460 M,S,T,N,W(7-8)

Impact on Obscura

Client  Obscura Container (TCP termination)  Internet
                                     
     Client TCP stack            Container TCP stack
     (visible to Obscura)        (visible to destination)

The destination server sees Obscura's TCP stack, not the client's. This is unavoidable because the container terminates the client TCP connection.

Mitigation: None at TCP level. However, most fingerprinting services focus on application-level fingerprints (TLS, HTTP) rather than TCP-level. TCP fingerprint alone rarely identifies a user.


2. TLS Fingerprint (JA3 / JA4)

See tls-fingerprinting.md for full details.

Key points for network layer:

  • TLS handshake is the most important network-level signal
  • Fully controllable via utls (Go) or custom Rust TLS
  • Critical for Obscura must spoof or the proxy is revealed

3. HTTP/2 Fingerprint

Mechanism

HTTP/2 connection preface and initial frames vary by implementation.

Signals:

  • SETTINGS frame parameters (order matters):
    • SETTINGS_HEADER_TABLE_SIZE
    • SETTINGS_ENABLE_PUSH
    • SETTINGS_MAX_CONCURRENT_STREAMS
    • SETTINGS_INITIAL_WINDOW_SIZE
    • SETTINGS_MAX_FRAME_SIZE
    • SETTINGS_MAX_HEADER_LIST_SIZE
  • WINDOW_UPDATE initial frame
  • PING behavior
  • PRIORITY frame structure

Browser Examples

Browser Initial Window Max Concurrent Header Table Size
Chrome 6291456 1000 65536
Firefox 131072 100 65536
Safari 1048576 100 65536

Impact on Obscura

If using Go's net/http for HTTP/2, the server sees Go's HTTP/2 fingerprint. Must implement custom HTTP/2 or use a library that allows SETTINGS override.


4. QUIC / HTTP/3 Fingerprint

Problem

HTTP/3 runs over QUIC (UDP). QUIC connections are encrypted end-to-end including the transport layer:

Client  QUIC (UDP 443)  Server
                         
    Encrypted            Encrypted
    (cannot inspect)     (cannot inspect)

QUIC transport parameters, connection IDs, and version information are visible in the initial handshake, but the proxy cannot modify them.

Strategy

Block UDP 443 to force fallback to HTTP/2 or HTTP/1.1 over TCP:

iptables -A FORWARD -p udp --dport 443 -j DROP

This prevents QUIC connections from bypassing the proxy.

Trade-off: Slightly higher latency (TCP vs QUIC), no 0-RTT, no connection migration.


5. Timing-Based Fingerprints

5.1 Performance Timing

performance.now()  // High-resolution timestamp in ms (sub-millisecond precision)
performance.timeOrigin
performance.getEntriesByType('navigation')

Mitigation: Round performance.now() to 100ms resolution via JS injection:

const originalNow = performance.now.bind(performance)
performance.now = () => Math.round(originalNow() / 100) * 100

5.2 Clock Skew

Servers can measure clock skew by comparing Date.now() values across requests.

Mitigation: Round Date.now() resolution.

5.3 Network Timing via Resource Loading

Sites measure latency by timing how long resources take to load:

const start = performance.now()
fetch('/image.png').then(() => {
  const rtt = performance.now() - start
})

Mitigation: Cannot fully prevent the proxy adds its own latency but the relative timing still leaks information.


6. WebRTC IP Leak

Mechanism

WebRTC bypasses the proxy by using STUN/TURN servers to discover the real IP.

const pc = new RTCPeerConnection({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
})
pc.createDataChannel('test')
pc.createOffer().then(d => pc.setLocalDescription(d))
pc.onicecandidate = e => {
  if (e.candidate) {
    // e.candidate.candidate contains IP address
    console.log(e.candidate.candidate)
  }
}

Mitigation via JS Injection

// Block WebRTC completely
Object.defineProperty(window, 'RTCPeerConnection', { value: undefined })
Object.defineProperty(window, 'RTCDataChannel', { value: undefined })
Object.defineProperty(window, 'RTCSessionDescription', { value: undefined })

Effect

Setting WebRTC Leak Detection
WebRTC available IP leaks Not detected
WebRTC blocked No leak Detectable (feature absence)

7. DNS Fingerprint

Signals

  • DNS resolver IP (Cloudflare 1.1.1.1 vs Google 8.8.8.8 vs ISP)
  • DNS over HTTPS vs DNS over TLS vs plain DNS
  • EDNS0 client subnet (reveals approximate network location)
  • DNSSEC support

Mitigation

Obscura controls DNS completely. Use a consistent, privacy-focused upstream (Quad9 9.9.9.9 or Cloudflare 1.1.1.1) and strip EDNS0 client subnet.


Conclusions for Obscura

Vector Controllable Priority Notes
TCP/IP fingerprint No (inherited from container) Low Low entropy, rarely used alone
TLS fingerprint Yes (via utls) Critical Must spoof or proxy is revealed
HTTP/2 fingerprint Partial Medium Requires custom HTTP/2 impl
QUIC/HTTP/3 Block or inherit Medium Block UDP 443
Timing attacks Partial Low Round performance.now()
WebRTC IP leak Yes (block via JS) High Critical for VPN users
DNS fingerprint Yes High Obscura controls DNS