← Back to index

DOM-based test cases — appendChild()

High: 13 Medium: 0 Low: 5 Safe: 1 Total:

Payloads are converted into DOM nodes via appendChild, using innerHTML under the hood to keep the sink realistic.

NOTE: This is the case where we can expect 4 additional findings for DOM-based JavaScript Injection (taint flow). It happens due to createContextualFragment and appendChild combination being modelled as 2 separate sinks in the SAST engine. In a real-world scenario, these 2 calls always happen together, so the findings are effectively duplicates. We keep both findings here to demonstrate the issue.

The appendChildUtils.appendChildSink utility function used in these test cases is defined as follows:

  function appendChildSink(payload, targetId) {
    var target = ensureTarget(targetId);
    const fragment = document.createRange().createContextualFragment(payload);
    target.appendChild(fragment);
  }
  

Use query payload

Inline scenarios

INLINE-1 (High): Query/hash → appendChild
// inline-script[#1]
(function(){
  const target = 'inline-appendchild-target';
  function render(payload) {
    appendChildUtils.clear(target);
    appendChildUtils.appendChildSink(payload, target);
  }
  const params = new URLSearchParams(location.search);
  if (params.has('payload')) render(params.get('payload'));
  if (location.hash) {
    try { render(decodeURIComponent(location.hash.slice(1))); }
    catch (err) { render(location.hash.slice(1)); }
  }
  window.addEventListener('hashchange', () => {
    if (!location.hash) return;
    try { render(decodeURIComponent(location.hash.slice(1))); }
    catch (err) { render(location.hash.slice(1)); }
  });
})();
Waiting for payload…
INLINE-2 (High): Field → appendChild
// inline-script[#2]
function inlineAppendChildFromField(fieldId, targetId) {
  const el = document.getElementById(fieldId);
  if (!el) return;
  appendChildUtils.appendChildSink(el.value, targetId);
}
INLINE-3 (High): jQuery → append
// inline-script[#2]
function jQueryAppend(fieldId, targetId) {
  $(`#${targetId}`).append(document.getElementById(fieldId).value);
}
INLINE-4 (Safe): Create element + textContent
// inline-script[#3]
(function(){
  const params = new URLSearchParams(location.search);
  const safe = params.get('safe');
  const target = document.getElementById('inline-safe-target');
  if (!target || !safe) return;
  const span = document.createElement('span');
  span.textContent = safe;
  target.appendChild(span);
})();
Waiting for safe payload…

External scenarios (click to run)

Direct DOM sources
onclick="extAppendChildFromLocalStorage()"
External DOM source target