Category: Go

10
Апр
2021

⚖ Golang против Python: какой язык программирования выбрать?

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

13
Мар
2021

🕵 Примеры атак XSS и способов их ослабления

Понимание межсайтового скриптинга и способов борьбы с ним необходимо каждому веб-разработчику. Это один из самых распространенных видов уязвимостей – злоумышленники часто проводят атаки XSS для кражи данных и нарушения работоспособности сервисов.

Перевод публикуется с сокращениями, автор оригинальной статьи 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 не могут
выдавать запросы к любому источнику, они могут только читать ответ на запросы
от того же источника.

Что же такое происхождение? Это кортеж протокола, имени хоста и порта документа.

Фрагмент 1: Кортеж из схемы, хоста и порта этого URL-адреса.
        https://www.example.com:443/app
^^^^^   ^^^^^^^^^^^^^^^ ^^^
Scheme  Host            Port
    
Рис. 1: Иллюстрация Same-Origin в действии. JavaScript работает на www.evil.com и не может получить доступ к DOM на www.example.com.
Рис. 1: Иллюстрация Same-Origin в действии. JavaScript работает на www.evil.com и не может получить доступ к DOM на www.example.com.

Политика Same-Origin
отлично помогает смягчать атаки на статические сайты, как показано на рисунке выше.
Однако с атаками на динамические ресурсы, принимающие пользовательский ввод, ситуация
немного сложнее из-за смешивания кода и данных, которая позволяет злоумышленнику
выполнять контролируемый ввод в исходном документе.

Атаки XSS обычно бывают трех видов: рефлективными, хранимыми и основанными на DOM.

Рефлективные и хранимые
XSS-атаки принципиально одинаковы, поскольку полагаются на вредоносный ввод,
отправляемый на бекенд и представляющий этот ввод пользователю сервер.
Рефлективные XSS обычно возникают в виде злонамеренно созданной злоумышленником
ссылки, по которой затем переходит жертва. Хранимые XSS происходят, когда
злоумышленник загружает вредоносный ввод. Атаки на основе DOM отличаются тем,
что они происходят исключительно на стороне клиента и включают вредоносный ввод, манипулирующий DOM.

Примеры

Рефлективные атаки XSS

Ниже можно увидеть
простое веб-приложение на Go, которое отражает свой ввод (даже если
это вредоносный скрипт) обратно пользователю. Вы можете использовать это
приложение, сохранив его в файле xss1.go и запустив go run xss1.go.

Фрагмент 3: Пример веб-приложения с рефлективной (отраженной) XSS-атакой.
        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. Обратите внимание, как смешение кода и данных позволило произойти этой
атаке.

Фрагмент 4: Пример вывода уязвимого для XSS веб-приложения.
        <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.

Фрагмент 5: Хранимая XSS-атака.
        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.

Фрагмент 6: Пример веб-приложения с XSS-атакой на основе DOM.
        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 принимает элементы
скрипта напрямую.

Фрагмент 7: Еще один пример атаки XSS на основе DOM.
        <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).

Фрагмент 8: XSS-атака, введенная через схему URL-адресов.
        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\.].

Фрагмент 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 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.

Фрагмент 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 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 подробнее.

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

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

Дополнительные материалы:

13
Мар
2021

🕵 Примеры атак XSS и способов их устранения

Понимание межсайтового скриптинга и способов борьбы с ним необходимо каждому веб-разработчику. Это один и самых распространенных видов уязвимостей – злоумышленники часто проводят атаки XSS для кражи данных и нарушения работоспособности сервисов.

Перевод публикуется с сокращениями, автор оригинальной статьи 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 не могут
выдавать запросы к любому источнику, они могут только читать ответ на запросы
от того же источника.

Что же такое происхождение? Это кортеж протокола, имени хоста и порта документа.

Фрагмент 1: Кортеж из схемы, хоста и порта этого URL-адреса.
        https://www.example.com:443/app
^^^^^   ^^^^^^^^^^^^^^^ ^^^
Scheme  Host            Port
    
Рис. 1: Иллюстрация Same-Origin в действии. JavaScript работает на www.evil.com и не может получить доступ к DOM на www.example.com.
Рис. 1: Иллюстрация Same-Origin в действии. JavaScript работает на www.evil.com и не может получить доступ к DOM на www.example.com.

Политика Same-Origin
отлично помогает смягчать атаки на статические сайты, как показано на рисунке выше.
Однако с атаками на динамические ресурсы, принимающие пользовательский ввод, ситуация
немного сложнее из-за смешивания кода и данных, которая позволяет злоумышленнику
выполнять контролируемый ввод в исходном документе.

Атаки XSS обычно бывают трех видов: рефлективными, хранимыми и основанными на DOM.

Рефлективные и хранимые
XSS-атаки принципиально одинаковы, поскольку полагаются на вредоносный ввод,
отправляемый на бекенд и представляющий этот ввод пользователю сервер.
Рефлективные XSS обычно возникают в виде злонамеренно созданной злоумышленником
ссылки, по которой затем переходит жертва. Хранимые XSS происходят, когда
злоумышленник загружает вредоносный ввод. Атаки на основе DOM отличаются тем,
что они происходят исключительно на стороне клиента и включают вредоносный ввод, манипулирующий DOM.

Примеры

Рефлективные атаки XSS

Ниже можно увидеть
простое веб-приложение на Go, которое отражает свой ввод (даже если
это вредоносный скрипт) обратно пользователю. Вы можете использовать это
приложение, сохранив его в файле xss1.go и запустив go run xss1.go.

Фрагмент 3: Пример веб-приложения с рефлективной (отраженной) XSS-атакой.
        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. Обратите внимание, как смешение кода и данных позволило произойти этой
атаке.

Фрагмент 4: Пример вывода уязвимого для XSS веб-приложения.
        <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.

Фрагмент 5: Хранимая XSS-атака.
        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.

Фрагмент 6: Пример веб-приложения с XSS-атакой на основе DOM.
        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 принимает элементы
скрипта напрямую.

Фрагмент 7: Еще один пример атаки XSS на основе DOM.
        <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).

Фрагмент 8: XSS-атака, введенная через схему URL-адресов.
        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\.].

Фрагмент 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 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.

Фрагмент 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 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 подробнее.

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

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

Дополнительные материалы:

24
Фев
2021

🏃 Где используется язык программирования Go?

Говорят, будто Golang – язык будущего. Он простой, эффективный и очень дружественный к новичкам. В мини-обзоре попробуем разобраться с основными особенностями и сферой применения набирающего популярность языка программирования.

26
Дек
2020

🏃 Конкурентность в Golang и WorkerPool [Часть 2]

В первой статье мы строили Worker Pool для оптимизации производительности. Во второй части мы создадим надежное решение для работы со структурами конкурентности.

Перевод публикуетс…

26
Дек
2020

🏃 Параллелизм в Golang и WorkerPool [Часть 2]

В первой статье мы строили Worker Pool для оптимизации производительности. Во второй части мы создадим надежное решение для работы со структурами параллелизма.

Перевод публикуется …

24
Дек
2020

🏃 Параллелизм в Golang и WorkerPool [Часть 1]

В современных языках программирования параллелизм стал безусловной потребностью. В этой статье речь пойдет об устройстве и использовании concurrency в Go.

Перевод публикуется с сок…

27
Сен
2020

Книги по языку Go с лучшими оценками читателей. Расставлены в порядке возрастания сложности, обобщены указанные читателями преимущества и недостатки.

Книги по языку Go для началь…

21
Июн
2020

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

28
Май
2020

Рассказываем о методах инструментирования Go-кода, контекстной трассировке и специальном средстве лаконичного и гибкого инструментирования gtrace.

76331ef0-7c12-4d04-9ce9-fc153ee…

15
Янв
2020

Опыт разработки: почему мы пишем инфраструктуру машинного обучения на Go, а не на Python

Разработчик Cortex Калеб Кайзер делится соображениями о преимуществах применения Go для инфраструктурных решений в ML-проектах и о том, как два языка могут дополнять друг друга.

Python – самый популярный яз…

06
Ноя
2019

Трансляция HighLoad++ 2019

В программе: архитектура и эксплуатация проектов, базы данных и системы хранения, системное администрирование, нагрузочное тестирование и не только.
— Читать дальше «Трансляция HighLoad++ 2019»

25
Окт
2019

7–8 ноября, Сколково: конференция HighLoad++ 2019

Мероприятие направлено на обмен знаниями о технологиях, позволяющих одновременно обслуживать многие тысячи и миллионы пользователей.
— Читать дальше «Конференция HighLoad++ 2019»

14
Окт
2019

1 ноября, Санкт-Петербург: конференция Golang Piter 2019

Международная техническая конференция по Go. Обсудят лучшие практики, удачные техники работы со встроенными инструментами и эффективное применение в работе.
— Читать дальше «Конференция Golang Piter 2019»

25
Июн
2019

11–12 июля, Санкт-Петербург: конференция Hydra 2019

Конференция о современных параллельных и распределённых системах, а также о научных подходах и теоремах, лежащих в их основе.
— Читать дальше «Конференция Hydra 2019»

15
Мар
2019

Конференция Saint HighLoad++ 2019

Спикеры вместе с гостями обсудят аспекты highload-разработки: оптимальную архитектуру, быстрые базы данных, качественное «железо», тесты и выбор платформы.
— Читать дальше «Конференция Saint HighLoad++ 2019»

01
Мар
2019

Разработка веб-серверов на Go

Стандартная библиотека языка Go включает в себя множество полезных и функциональных компонентов «из коробки», которые позволяют легко разрабатывать серверные приложения. В статье мы изучим, как написать веб-сервер на Go. Начнем
— Читать дальше «Разрабо…

27
Ноя
2018

Митап iTechMeetup. Vitebsk #1

13 декабря в Витебске компания iTechArt проведёт встречу разработчиков iTechMeetup. Vitebsk #1. Что будет на встрече? Два доклада от разработчиков компании: Роман Захаренко, «To Go or not to GO?». Роман обсудит перспективы языка Go и его использование …

30
Окт
2018

REST API приложение на Go: пошаговый туториал

REST API приложение на Go: пошаговый туториалВ этой статье мы рассмотрим, как создать REST API приложение на языке Go: лучшая пошаговая инструкция с использованием популярной архитектуры. Причина популярности Go заключается в его простоте, мощных функциях и производительности. В качестве первого контакта с этим языком предлагаем написать небольшое REST API приложение, в котором мы сможем просматривать список книг или одну книгу, удалять, […]

Запись REST API приложение на Go: пошаговый туториал впервые появилась Библиотека программиста.

06
Окт
2018

Октябрьский рейтинг языков программирования от TIOBE: Python снова вне тройки лидеров

Эксперты TIOBE опубликовали рейтинг языков программирования за октябрь 2018 года. Они сообщили, что 9 языков в топ-10 несколько месяцев подряд занимают свои позиции, а десятая строчка меняется практически постоянно. Python потерял позицию в топ-3 и вер…

31
Авг
2018

Google представила открытую криптографическую библиотеку Tink

Google представила открытую криптографическую библиотеку Tink с поддержкой языков Java, C++ и Objective-C, а также экспериментальной поддержкой Go и JavaScript. Примитивы продукта используют форк BoringSSL и фреймворк Java Cryptography Architecture. Ti…

07
Авг
2018

Программирование на Go с нуля: 9 полезных видеоуроков

Кроссплатформенный, компилируемый и статически типизированный – это все о языке Go. Изучаем программирование на Go по этому курсу.

Ранее мы уже затрагивали блоги, статьи и книги по Go. Поговорим же теперь о видеоуроках с наиболее понятной подачей материала.

23
Июл
2017

IEEE Spectrum опубликовал рейтинг языков программирования за 2017 год

Журнал Spectrum, издаваемый Институтом инженеров электротехники и электроники (IEEE), опубликовал новую редакцию рейтинга популярности языков программирования. Что изменилось? Популярность Python продолжила расти, и он переместился на два места вперед….