Let's say I have a simple web application which uses a single JavaScript (JS) file, loaded from its own domain, and has implemented the restrictive Content Security Policy (CSP) of default-src 'self'
. There's a stored XSS in it whereby the JS file will make an Ajax call to an API which would return some content stored in a database, and that content (which came from untrusted user input) has inline JavaScript in it. The JS file creates an element in the page's document and sets its HTML content to the retrieved content. Let's assume that this is the necessary way of doing what it needs to do, and let's assume that sanitising/encoding the input is unfeasible. I know that user input should always be sanitised, just for the purposes of this question, skip this suggestion as a solution.
Is there any way to set a CSP such that this inline JavaScript, dynamically put onto the page by trusted JavaScript, is blocked?
Here's a minimal working example (you may need to serve it from a simple HTTP server, e.g. php -S localhost:58000
, rather than loading as an .html
file)
csp-test.html
:
<!DOCTYPE html><html><head><meta charset="UTF-8" /><meta http-equiv="Content-Security-Policy" content="default-src 'self'"><script charset="utf-8"> console.log('script') // blocked, OK</script><script src="csp-test.js" charset="utf-8"></script></head><body><img src="x" onerror="console.log('img')"/> <!-- blocked, OK --></body></html>
csp-test.js
:
window.addEventListener('load', () => { console.log('trusted ext script') // executed, OK i = document.createElement('img') i.src = 'y' i.addEventListener('error', function(){ console.log('img by trusted ext script'); }) // executed, HOW TO BLOCK THIS? document.body.append(i)})
result:
Image may be NSFW.
Clik here to view.
EDIT:
As @user2313067 pointed out, it was not being blocked because the event handler was registered by the script and not by an <img
attribute. When I create an element from a string, which contains such an inline JavaScript, it is blocked, as in:
window.addEventListener('load', () => { console.log('trusted ext script') // executed, OK p = document.createElement('p') p.innerHTML = "<img src='y' onerror=\"console.log('img by trusted ext script')\"/>" // blocked, OK document.body.append(p)})
I guess this solves this question. I was under the impression that the behaviour I was seeing in the web application in question (where the API fetches HTML as a JSON from an API and renders it onto the page) uses exactly innerHTML
as in my second example. So I was perplexed as to why the inline JavaScript which the API serves is not blocked. But apparently, it does a more complicated transformation before rendering onto the page...