Примеры атак XSS и способов их ослабления. Часть 2

TG_hack_[ DR.Bro ]

Бот форума
Регистрация
26.02.21
Сообщения
43
Реакции
22
Кредиты
0 ₽
Баллы
18
Единого метода решения данной проблемы не существует, иначе XSS не был бы такой распространенной проблемой. Фундаментальная сложность вызвана отсутствием разделения между кодом и данными. Смягчение последствий XSS обычно включает очистку входных данных (нужно убедиться, что они не содержат кода), экранирование выходных данных (они также не должны содержать код) и реструктуризацию приложения таким образом, чтобы код загружался из строго определенных конечных точек.
Валидация входных данных

Первая линия защиты – проверка входных данных. Убедитесь, что их формат соответствует ожидаемым характеристикам – эдакий белый список, гарантирующий отсутствие у приложения возможности принимать код.

Валидация данных – сложная проблема. Не существует универсального инструмента или техники для всех ситуаций. Лучше всего структурировать приложение таким образом, чтобы оно требовало от разработчиков продумать тип принимаемых данных и обеспечить удобное место, где можно разместить валидатор.

Хороший тон написания приложений на Go состоит в том, чтобы не иметь никакой логики приложения в обработчиках запросов HTTP, а вместо этого использовать их для анализа и проверки входных данных. Затем данные отправляются в обрабатывающую логику структуру. Обработчики запросов становятся простыми и обеспечивают удобное централизованное расположение для контроля правильности очистки данных.

На фрагменте 9 показано, как можно переписать saveHandler для приема символов ASCII [A-Za-z\.].

Фрагмент 9: Пример использования обработчиков HTTP-запросов для проверки данных.
Код:
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.

Фрагмент 10: Использование экранирования для устранения хранимых XSS-атак.
Код:
package mainimport ("bytes""html/template""io""log""net/http""sync")var db []stringvar mu sync.Mutexvar 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.Buffererr = 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, перейдя по ссылке и введите <script>alert(1);</script>. Обратите внимание, что предупреждение не было вызвано.

Откройте консоль браузера и посмотрите на элемент li в DOM. Интерес представляют два свойства: innerHTML и innerText.

Фрагмент 11: Проверка DOM при использовании экранирования.
Код:
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.

Фрагмент 12: Пример CSP, смягчающего XSS-атаку.
Код:
package mainimport ("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.Buffert, 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))}

Чтобы попытаться использовать приложение, перейдите по ссылке: и попробуйте отправить <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 подробнее.

Фрагмент 13: Базовый CSP. Nonce повторно генерируется для каждого запроса.
Код:
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 трудно искоренить, поскольку приложения становятся все больше и все сложнее. Применяя упомянутые в статье методы, можно сделать жизнь злоумышленников трудной.

Удачи в борьбе и учебе!

Источник
 
Верх Низ