Категория > Новости > Cross-Site WebSocket Hijacking. Разбираемся, как работает атака на WebSocket - «Новости»

Cross-Site WebSocket Hijacking. Разбираемся, как работает атака на WebSocket - «Новости»


16-02-2021, 00:00. Автор: Лиана
Крис­тиана Шнай­дера и выс­тупле­ния Миха­ила Его­рова, но не обра­тил на нее вни­мания. Поз­же, читая ре­порт на HackerOne, оце­нен­ный в 800 дол­ларов, понял, что хочу разоб­рать­ся. На прос­торах Рунета под­робно­го опи­сания CSWSH не наш­лось, и я решил написать его самос­тоятель­но.

В этой статье мы раз­берем про­токол WebSocket, под­робно оста­новим­ся на уяз­вимос­ти CSWSH — нас­коль­ко она рас­простра­нена в откры­том интерне­те. Для тех, кто дочита­ет до кон­ца, я при­гото­вил бонус в виде ути­литы cswsh-scanner, с помощью которой ты можешь про­верить свои при­ложе­ния, работа­ющие с WebSocket, либо попытать уда­чи на баг‑баун­ти.



warning


Вся информа­ция пре­дос­тавле­на исклю­читель­но в озна­коми­тель­ных целях. Ни редак­ция, ни автор не несут ответс­твен­ности за любой воз­можный вред, при­чинен­ный матери­ала­ми дан­ной статьи.



 

Описание протокола


Итак, что такое WebSocket? Википе­дия дает сле­дующее опре­деле­ние: «WebSocket — про­токол свя­зи поверх TCP-соеди­нения, пред­назна­чен­ный для обме­на сооб­щени­ями меж­ду бра­узе­ром и веб‑сер­вером в режиме реаль­ного вре­мени». В отли­чие от син­хрон­ного про­токо­ла HTTP, пос­тро­енно­го по модели «зап­рос — ответ», WebSocket пол­ностью асин­хрон­ный и сим­метрич­ный. Он при­меня­ется для орга­низа­ции чатов, онлайн‑таб­ло и соз­дает пос­тоян­ное соеди­нение меж­ду кли­ентом и сер­вером, которое обе сто­роны могут исполь­зовать для отправ­ки дан­ных.


Про­токол WebSocket опре­делен в RFC 6455. Для про­токо­ла зарезер­вирова­ны две URI-схе­мы:



  • для обыч­ного соеди­нения: ws://host[:port]path[?query];

  • для соеди­нений через тун­нель TLS: wss://host[:port]path[?query].


WebSocket дос­таточ­но рас­простра­нен в сов­ремен­ной веб‑раз­работ­ке, есть под­дер­жка во всех популяр­ных язы­ках прог­рамми­рова­ния и бра­узе­рах. Его исполь­зуют в онлайн‑чатах, дос­ках объ­явле­ний, веб‑кон­солях, при­ложе­ниях трей­деров. С помощью поис­ковика shodan.io мож­но с лег­костью най­ти при­ложе­ния на WebSocket, дос­тупные из интерне­та. Дос­таточ­но сфор­мировать прос­той зап­рос. Я не поленил­ся и сде­лал:


Search for Sec-WebSocket-Version HTTP/1.1 400 Bad Request returned 55,461 results on 10-05-2020


В резуль­тате наш­лось 55 тысяч адре­сов с обширной геог­рафи­ей.


Cross-Site WebSocket Hijacking. Разбираемся, как работает атака на WebSocket - «Новости»
WebSocket в мире 

Установка соединения


Раз­берем теперь, как работа­ет WebSocket. Вза­имо­дей­ствие меж­ду кли­ентом и сер­вером начина­ется с рукопо­жатия. Для рукопо­жатия кли­ент и сер­вер исполь­зуют про­токол HTTP, но с некото­рыми отли­чиями в фор­мате переда­ваемых сооб­щений. Не соб­люда­ются все тре­бова­ния к HTTP-сооб­щени­ям. Нап­ример, отсутс­тву­ет заголо­вок Content-Length.


Для начала кли­ент ини­циирует соеди­нение и отправ­ляет зап­рос сер­веру:


GET /echo HTTP/1.1
Host: localhost:8081
Sec-WebSocket-Version: 13
Origin: http://localhost:8081
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Connection: keep-alive, Upgrade
Upgrade: websocket

За­голов­ки Sec-WebSocket-Version, Sec-WebSocket-Key, Connection: Upgrade и Upgrade: websocket обя­затель­ны, ина­че сер­вер воз­вра­щает ста­тус HTTP/1.1 400 Bad Request. Сер­вер отве­чает на зап­рос кли­ента сле­дующим обра­зом:


HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

За­голо­вок Sec-WebSocket-Key фор­миру­ется кли­ентом как слу­чай­ное 16-бай­товое зна­чение, закоди­рован­ное в Base64. Вари­ант фор­мирова­ния заголов­ка на Go:


func generateChallengeKey() (string, error) {
p := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, p); err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(p), nil
}

За­голо­вок Sec-WebSocket-Accept в отве­те фор­миру­ется по сле­дующе­му алго­рит­му. Берет­ся стро­ковое зна­чение из заголов­ка Sec-WebSocket-Key и объ­еди­няет­ся с GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11. Далее вычис­ляет­ся хеш SHA-1 от получен­ной в пер­вом пун­кте стро­ки. Хеш кодиру­ется в Base64. Вари­ант фор­мирова­ния заголов­ка на Go:


const GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
func computeAcceptKey(challengeKey string) string {
h := sha1.New()
h.Write([]byte(challengeKey + GUID))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

За­голов­ки Sec-WebSocket-Key и Sec-WebSocket-Accept не исполь­зуют­ся для авто­риза­ции и под­дер­жки сес­сий, они слу­жат для того, что­бы сто­роны убе­дились, что зап­рос и ответ отно­сят­ся к про­токо­лу WebSocket. Это помога­ет гаран­тировать, что сер­вер не при­нима­ет от кли­ентов зап­росы, не отно­сящи­еся к WebSocket.


Так­же RFC 6455 пред­полага­ет, что Sec-WebSocket-Key дол­жен быть выб­ран слу­чай­ным обра­зом для каж­дого соеди­нения. Это озна­чает, что любой кеширо­ван­ный резуль­тат от прок­си‑сер­вера будет содер­жать невалид­ный Sec-WebSocket-Accept и, сле­дова­тель­но, рукопо­жатие про­валит­ся вмес­то неп­редна­мерен­ного чте­ния кеширо­ван­ных дан­ных. Для успешно­го завер­шения рукопо­жатия кли­ент про­веря­ет зна­чение Sec-WebSocket-Accept и ожи­дает ста­тус‑код 101 Switching Protocols. Пос­ле того как рукопо­жатие выпол­нено, пер­воначаль­ное соеди­нение HTTP заменя­ется соеди­нени­ем по WebSocket, которое исполь­зует то же соеди­нение TCP/IP. На этом эта­пе любая из сто­рон может начать отправ­ку дан­ных.


Для монито­рин­га тра­фика WebSocket удоб­но исполь­зовать «Инс­тру­мен­ты раз­работ­чика», дос­тупные, к при­меру, в Chrome.


WebSocket в «Инс­тру­мен­тах раз­работ­чика» 

Передача данных


Как в WebSocket переда­ются сооб­щения? Дан­ные по про­токо­лу WebSocket переда­ются как пос­ледова­тель­ность фрей­мов. Фрейм име­ет заголо­вок, в котором содер­жится сле­дующая информа­ция:



  • фраг­менти­рова­но ли сооб­щение;

  • тип переда­ваемых дан­ных — all code;

  • под­верга­лось ли сооб­щение мас­киров­ке — флаг мас­ки;

  • раз­мер дан­ных;

  • ключ мас­ки (32 бита);

  • дру­гие управля­ющие дан­ные (ping, pong...).


Фор­мат фрей­ма пред­став­лен на рисун­ке.


Фор­мат фрей­ма WebSocket

Все сооб­щения, посыла­емые кли­ентом, дол­жны мас­кировать­ся. При­мер отправ­ки тес­тового сооб­щения «Hello world!» кли­ентом (дан­ные из tcpdump):


Fin: True
Reserved: 0x0
Opcode: Text (1)
Mask: True
Payload length: 12
Masking-Key: a2929b01
Payload: eaf7f76dcdb2ec6ed0feff20

Мас­киров­ка про­изво­дит­ся обыч­ным XOR с клю­чом мас­ки. Кли­ент дол­жен менять ключ для каж­дого передан­ного фрей­ма. Сер­вер не дол­жен мас­кировать свои сооб­щения. При­мер отправ­ки тес­тового сооб­щения «Hello world!» сер­вером:


Fin: True
Reserved: 0x0
Opcode: Text (1)
Mask: False
Payload length: 12
Payload: 48656c6c6f20776f726c6421

Мас­киров­ка переда­ваемых сооб­щений нек­риптос­той­кая, что­бы обес­печить кон­фиден­циаль­ность, для WebSocket сле­дует исполь­зовать про­токол TLS и схе­му WSS.


 

Как работает уязвимость


С про­токо­лом разоб­рались, самое вре­мя перей­ти к CSWSH. Про­токол WebSocket исполь­зует Origin-based модель безопас­ности при работе с бра­узе­рами. Дру­гие механиз­мы безопас­ности, нап­ример SOP (Same-origin policy), для WebSocket не при­меня­ются. RFC 6455 ука­зыва­ет, что при уста­нов­ке соеди­нения сер­вер может про­верять Origin, а может и нет:


По­ле заголов­ка Origin в рукопо­жатии кли­ента озна­чает про­исхожде­ние скрип­та, который уста­нав­лива­ет соеди­нение. Origin сери­али­зует­ся через ASCII и кон­верти­рует­ся в ниж­ний регистр. Сер­вер МОЖЕТ исполь­зовать эту информа­цию при при­нятии решения о том, при­нимать ли вхо­дящее соеди­нение. Если сер­вер не про­веря­ет Origin, он будет при­нимать соеди­нение отку­да угод­но. Если сер­вер реша­ет не при­нимать соеди­нение, он ОБЯ­ЗАН вер­нуть соот­ветс­тву­ющий номер ошиб­ки HTTP (то есть 403 Forbidden) и отме­нить рукопо­жатие по WebSocket, опи­сан­ное в этой сек­ции.


Уяз­вимость CSWSH свя­зана со сла­бой или невыпол­ненной про­вер­кой заголов­ка Origin в рукопо­жатии кли­ента. Это раз­новид­ность уяз­вимос­ти под­делки меж­сай­товых зап­росов (CSRF), толь­ко для WebSocket. Если при­ложе­ние WebSocket исполь­зует фай­лы cookie для управле­ния сеан­сами поль­зовате­ля, зло­умыш­ленник может под­делать зап­рос на рукопо­жатие с помощью ата­ки CSRF и кон­тро­лиро­вать сооб­щения, отправ­ляемые и получа­емые через соеди­нение WebSocket.



Перейти обратно к новости