Merge pull request #548 from zgordan-vv/ru

Ru
This commit is contained in:
astaxie
2015-10-11 16:15:49 +08:00
4 changed files with 303 additions and 0 deletions

67
ru/04.3.md Normal file
View File

@@ -0,0 +1,67 @@
# 4.3 Межсайтовый скриптинг
Для совершенствования взаимодействия с пользователем современные сайты содержат все больше динамического контента, что означает, что мы должны предоставлять информацию динамически в зависимости от поведения каждого пользователя. К сожалению, существует такое явление как "межсайтовый скриптинг" (известный как "XSS"), с помощью которого осуществляются постоянные атаки на динамические сайты, в то время как сайты со статическим содержимым этим атакам не подвержены.
Злоумышленники посылают на сайты, подверженные межсайтовому скриптингу, скрипты на JavaScript, VBScript, ActiveX или Flash. Если скрипт удачно вторгся на сайт, пользовательская информация может быть похищена, а сайт наполнен спамом. Злоумышленники могут также изменить настройки пользователя на те, которые захотят.
Если Вы хотите предотвратить этот тип атаки, Вам нужно комбинировать два следующих подхода:
- Проверка всех данных, идущих от пользователя, о чем мы поговорили в предыдущей главе.
- Обработка всех данных, посылаемых клиенту, для того, чтобы предотвратить запуск опасных скриптов в браузере.
Итак, как нам осуществить эти два пункта в Go? К счастью, пакет `html/template` имеет в своем распоряжении несколько полезных функций, чтобы обезопасить данные:
- `func HTMLEscape(w io.Writer, b []byte)` отправляет в w версию b с заменой потенциально опасных символов на их escape-последовательности.
- `func HTMLEscapeString(s string) string` возвращает версию s с заменой потенциально опасных символов на их escape-последовательности.
- `func HTMLEscaper(args ...interface{}) string` формирует строку из множества аргументов с заменой потенциально опасных символов на escape-последовательности.
Давайте изменим пример из раздела 4.1:
fmt.Println("Имя пользователя:", template.HTMLEscapeString(r.Form.Get("username"))) // печатает на стороне сервера
fmt.Println("Пароль:", template.HTMLEscapeString(r.Form.Get("password")))
template.HTMLEscape(w, []byte(r.Form.Get("username"))) // отправляет клиенту
Если кто-то попробует ввести в поле для ввода имени пользователя `<script>alert()</script>`, мы увидим следующую картину в браузере:
![](images/4.3.escape.png?raw=true)
Рисунок 4.3 JavaScript после обработки escape-последовательностью
Функции пакета `html/template` помогут Вам заменить все теги HTML на их безопасные аналоги. Но что, если Вам нужно передать в браузер `<script>alert()</script>`? В этом случае нужно использовать пакет `text/template`:
import "text/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Привет, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('Вы попались!')</script>")
Вывод:
Привет, <script>alert('Вы попались!')</script>!
Или можно использовать тип `template.HTML`. Содержимое переменной типа `template.HTML` не изменяется с учетом escape-последовательностей:
import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Привет, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", template.HTML("<script>alert('Вы попались!')</script>"))
Вывод:
Привет, <script>alert('Вы попались!')</script>!
Еще один пример эскейпинга:
import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Привет, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('Вы попались!')</script>")
Вывод:
Привет, &lt;script&gt;alert(&#39;Вы попались!&#39;)&lt;/script&gt;!
## Ссылки
- [Содержание](preface.md)
- Предыдущий раздел: [Проверка введенных данных](04.2.md)
- Следующий раздел: [Дублирование отправки](04.4.md)

57
ru/04.4.md Normal file
View File

@@ -0,0 +1,57 @@
# 4.4 Дублирование отправки
Не знаю, встречали ли Вы, как на каком-либо блоге или форуме размещено несколько постов подряд с одинаковым содержимым, но я могу сказать Вам, что это происходит по причине того, что отправка постов дублируется пользователем. Это может произойти по многим причинам; иногда пользователь отправляет форму двойным щелчком, или он после отправки решает исправить содержимое поста и нажимает кнопку браузера "Назад". А иногда это - намереные действия злоумышленников. Понятно, что дублирование отправки может привести ко многим проблемам. Поэтому нам нужно принимать эффективные меры для его предотвращения.
Решением этой задачи является добавление в форму скрытого поля с уникальным токеном и проверка этого токена перед перед обработкой введенных данных. А если для отправки формы Вы используете Ajax, можно после того, как данные отправлены, сделать кнопку отправки неактивной.
Давайте усовершенствуем пример из раздела 4.2:
<input type="checkbox" name="interest" value="football">Футбол
<input type="checkbox" name="interest" value="basketball">Баскетбол
<input type="checkbox" name="interest" value="tennis">Теннис
Имя:<input type="text" name="username">
Пароль:<input type="password" name="password">
<input type="hidden" name="token" value="{{.}}">
<input type="submit" value="Login">
Для того, чтобы сгенерировать токен, мы используем хэш MD5 (временная отметка), и добавляем его как в скрытое поле формы ввода данных на стороне клиента, так и в сессионный куки на стороне сервера (см. Раздел 6). Мы можем использовать этот токен для того, чтобы проверить, отправлялись ли уже данные с этой формы:
func login(w http.ResponseWriter, r *http.Request) {
fmt.Println("method:", r.Method) // получаем метод запроса
if r.Method == "GET" {
crutime := time.Now().Unix()
h := md5.New()
io.WriteString(h, strconv.FormatInt(crutime, 10))
token := fmt.Sprintf("%x", h.Sum(nil))
t, _ := template.ParseFiles("login.gtpl")
t.Execute(w, token)
} else {
// запрос данных о входе
r.ParseForm()
token := r.Form.Get("token")
if token != "" {
// проверяем валидность токена
} else {
// если нет токена, возвращаем ошибку
}
fmt.Println("username length:", len(r.Form["username"][0]))
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) // печатаем на стороне сервера
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
template.HTMLEscape(w, []byte(r.Form.Get("username"))) // отвечаем клиенту
}
}
![](images/4.4.token.png?raw=true)
Рисунок 4.4 Содержимое браузера после добавления токена
Если обновлять страницу, можно видеть каждый раз новый токен. Это обеспечивает то, что каждая форма уникальна.
На данный момент Вы можете предотвращать множество атак на основе дублирования отправки посредством добавления в формы токенов, но все атаки такого типа предотвратить таким образом нельзя. Для этого нужно проделать еще больше работы.
## Ссылки
- [Содержание](preface.md)
- Предыдущий раздел: [Межсайтовый скриптинг](04.3.md)
- Следующий раздел: [Загрузка файлов](04.5.md)

168
ru/04.5.md Normal file
View File

@@ -0,0 +1,168 @@
# 4.5 Загрузка файлов
Предположим, у Вас есть веб-сайт наподобие Instagram, и Вы хотите, чтобы пользователи закачивали туда свои фортографии. Как можно реализовать эту функцию?
Для этого нужно добавить в форму, через которую будут закачиваться фотографии, свойство `enctype`. Оно имеет три значения:
```
application/x-www-form-urlencoded Кодировать все символы перед закачкой (по умолчанию).
multipart/form-data Не кодировать. Если в форме есть функционал закачки файлов, Вы должны использовать это значение.
text/plain Конвертировать пробелы в "+", но не кодировать специальные символы.
```
Поэтому, содержимое HTML формы для загрузки файлов должно выглядеть так:
```
<html>
<head>
<title>Загрузка файлов</title>
</head>
<body>
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
<input type="file" name="uploadfile" />
<input type="hidden" name="token" value="{{.}}"/>
<input type="submit" value="upload" />
</form>
</body>
</html>
```
Для работы с этой формой мы должны добавить функцию на сервере:
```
http.HandleFunc("/upload", upload)
// обработка закачки
func upload(w http.ResponseWriter, r *http.Request) {
fmt.Println("Метод:", r.Method)
if r.Method == "GET" {
crutime := time.Now().Unix()
h := md5.New()
io.WriteString(h, strconv.FormatInt(crutime, 10))
token := fmt.Sprintf("%x", h.Sum(nil))
t, _ := template.ParseFiles("upload.gtpl")
t.Execute(w, token)
} else {
r.ParseMultipartForm(32 << 20)
file, handler, err := r.FormFile("uploadfile")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
fmt.Fprintf(w, "%v", handler.Header)
f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
io.Copy(f, file)
}
}
```
Как Вы можете видеть, для загрузки файлов нужно вызвать функцию `r.ParseMultipartForm`. Эта функция имеет аргумент `maxMemory`. После вызова `ParseMultipartForm` файл будет сохранен в памяти сервера с размером `maxMemory`. Если размер файла больше, чем `maxMemory`, остальная часть данных будет сохранена во временном файле в системе. Вы можете использовать `r.FormFile` для того, чтобы работать с файлом, и `io.Copy` для того, чтобы сохранить файл в файловой системе.
Для того, чтобы получить доступ к другим полям формы, не относящимся к загрузке файлов, Вам не нужно вызывать `r.ParseForm`, так как Go вызовет эту функцию, когда понадобится. Также, вызов `ParseMultipartForm` один раз достаточен - многократные вызовы ничего не меняют.
Для загрузки файлов мы используем следующие три шага:
1. Добавить в форму `enctype="multipart/form-data"`.
2. Вызвать на стороне сервера `r.ParseMultipartForm`, чтобы сохранить файл в память или во временный файл.
3. Вызвать `r.FormFile` для обработки файла и сохранения его в файловую систему.
Хэндлером для файла является `multipart.FileHeader`. У него следующая структура:
```
type FileHeader struct {
Filename string
Header textproto.MIMEHeader
// соджержит отфильтрованные или неэкспортируемые поля
}
```
![](images/4.5.upload2.png?raw=true)
Рисунок 4.5 Вывод информации на сервере после получения файла
## Загрузка файлов с помощью клиента
Я показал Вам пример, как можно использовать форму для загрузки файлов. Мы можем сделать так, чтобы загружать файлы через форму без участия человека:
```
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
)
func postFile(filename string, targetUrl string) error {
bodyBuf := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)
// этот шаг очень важен
fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
if err != nil {
fmt.Println("ошибка записи в буфер")
return err
}
// процедура открытия файла
fh, err := os.Open(filename)
if err != nil {
fmt.Println("ошибка открытия файла")
return err
}
//iocopy
_, err = io.Copy(fileWriter, fh)
if err != nil {
return err
}
contentType := bodyWriter.FormDataContentType()
bodyWriter.Close()
resp, err := http.Post(targetUrl, contentType, bodyBuf)
if err != nil {
return err
}
defer resp.Body.Close()
resp_body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
fmt.Println(resp.Status)
fmt.Println(string(resp_body))
return nil
}
// пример использования
func main() {
target_url := "http://localhost:9090/upload"
filename := "./astaxie.pdf"
postFile(filename, target_url)
}
```
Этот пример показывает, как можно использовать клиента для загрузки файлов. Он использует `multipart.Write` для того, чтобы записывать файлы в кэш, и посылает их на сервер посредством метода POST.
Если у Вас есть другие поля, которые нужно писать в данные, такие, как имя пользователя, вызывайте по необходимости метод `multipart.WriteField`.
## Ссылки
- [Содержание](preface.md)
- Предыдущий раздел: [Дублирование отправки](04.4.md)
- Следующий раздел: [Итоги раздела](04.6.md)

11
ru/04.6.md Normal file
View File

@@ -0,0 +1,11 @@
# 4.6 Итоги раздела
В этом разделе мы изучили основные моменты того, как работать с данными в Go, посредством нескольких примеров, таких как обработка входа пользователей и загрузка файлов. Мы также заострили внимание на том, что проверка данных крайне важна для безопасности сайта, а также посвятили одну секцию тому, как фильтровать входные данные посредством регулярных выражений.
Я надеюсь, что теперь Вы больше знаете о процессе коммуникации между клиентом и сервером.
## Ссылки
- [Содержание](preface.md)
- Предыдущий раздел: [Загрузка файлов](04.5.md)
- Следужщий раздел: [Базы данных](05.0.md)