An (mildly) interesting XSS vector
Context

Consider a web application endpoint:


https://pequod.ship?item_function_suffix=group


This URL generates a button within the DOM, such as:


<button onclick="add_new_{item_function_suffix}()">Add</button>
                

The server-side code might look something like this:


@app.route('/')
def dynamic_button():
    # Get the 'item_function_suffix' parameter from the URL
    item_function_suffix = request.args.get('item_function_suffix', 'default')
    
    # Sanitize
    item_function_suffix = item_function_suffix.replace('"', '').replace("'", '')

    # Generate the HTML for the button with a dynamic onclick attribute
    button_html = f'<button onclick="add_new_{item_function_suffix}()">Click me</button>'
    
    return button_html
                

In this setup, quotation marks are sanitized, preventing injection attacks that work outside the context of the onclick attribute. However, it's still possible to inject JavaScript code.

Exploit

A direct approach to injecting JavaScript via the item_function_suffix parameter is challenging because any syntax error in the static portion of the function name ("add_new_") would cause an exception, preventing the execution of the injected payload. Simply appending commands with a semicolon (;) is not effective, as it could lead to uncaught reference exceptions.

To bypass this, we can manipulate the function name by closing it prematurely and appending the XSS payload. JavaScript’s untyped nature allows for a simple yet powerful solution: assigning the static portion of the function name to an arbitrary value using the following injection.


={};alert(0);//
            

This injection modifies the output to:


<button onclick="add_new_={};alert(0);//()">Add</button>
            

This works because JavaScript does not require variable declarations like var, allowing the injection to execute arbitrary code. However, this technique is limited to static strings that contain characters valid in JavaScript variable names.
For more information on the chaos that is allowed characters in JavaScript identifiers (because apparently, anything goes), check out this guide.