Skip to content

Commit 9b961ca

Browse files
committed
Merge pull request astaxie#548 from zgordan-vv/ru
Ru
2 parents 30f517b + 23cff0c commit 9b961ca

File tree

4 files changed

+303
-0
lines changed

4 files changed

+303
-0
lines changed

ru/04.3.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# 4.3 Межсайтовый скриптинг
2+
3+
Для совершенствования взаимодействия с пользователем современные сайты содержат все больше динамического контента, что означает, что мы должны предоставлять информацию динамически в зависимости от поведения каждого пользователя. К сожалению, существует такое явление как "межсайтовый скриптинг" (известный как "XSS"), с помощью которого осуществляются постоянные атаки на динамические сайты, в то время как сайты со статическим содержимым этим атакам не подвержены.
4+
5+
Злоумышленники посылают на сайты, подверженные межсайтовому скриптингу, скрипты на JavaScript, VBScript, ActiveX или Flash. Если скрипт удачно вторгся на сайт, пользовательская информация может быть похищена, а сайт наполнен спамом. Злоумышленники могут также изменить настройки пользователя на те, которые захотят.
6+
7+
Если Вы хотите предотвратить этот тип атаки, Вам нужно комбинировать два следующих подхода:
8+
9+
- Проверка всех данных, идущих от пользователя, о чем мы поговорили в предыдущей главе.
10+
- Обработка всех данных, посылаемых клиенту, для того, чтобы предотвратить запуск опасных скриптов в браузере.
11+
12+
Итак, как нам осуществить эти два пункта в Go? К счастью, пакет `html/template` имеет в своем распоряжении несколько полезных функций, чтобы обезопасить данные:
13+
14+
- `func HTMLEscape(w io.Writer, b []byte)` отправляет в w версию b с заменой потенциально опасных символов на их escape-последовательности.
15+
- `func HTMLEscapeString(s string) string` возвращает версию s с заменой потенциально опасных символов на их escape-последовательности.
16+
- `func HTMLEscaper(args ...interface{}) string` формирует строку из множества аргументов с заменой потенциально опасных символов на escape-последовательности.
17+
18+
Давайте изменим пример из раздела 4.1:
19+
20+
fmt.Println("Имя пользователя:", template.HTMLEscapeString(r.Form.Get("username"))) // печатает на стороне сервера
21+
fmt.Println("Пароль:", template.HTMLEscapeString(r.Form.Get("password")))
22+
template.HTMLEscape(w, []byte(r.Form.Get("username"))) // отправляет клиенту
23+
24+
Если кто-то попробует ввести в поле для ввода имени пользователя `<script>alert()</script>`, мы увидим следующую картину в браузере:
25+
26+
![](images/4.3.escape.png?raw=true)
27+
28+
Рисунок 4.3 JavaScript после обработки escape-последовательностью
29+
30+
Функции пакета `html/template` помогут Вам заменить все теги HTML на их безопасные аналоги. Но что, если Вам нужно передать в браузер `<script>alert()</script>`? В этом случае нужно использовать пакет `text/template`:
31+
32+
import "text/template"
33+
...
34+
t, err := template.New("foo").Parse(`{{define "T"}}Привет, {{.}}!{{end}}`)
35+
err = t.ExecuteTemplate(out, "T", "<script>alert('Вы попались!')</script>")
36+
37+
Вывод:
38+
39+
Привет, <script>alert('Вы попались!')</script>!
40+
41+
Или можно использовать тип `template.HTML`. Содержимое переменной типа `template.HTML` не изменяется с учетом escape-последовательностей:
42+
43+
import "html/template"
44+
...
45+
t, err := template.New("foo").Parse(`{{define "T"}}Привет, {{.}}!{{end}}`)
46+
err = t.ExecuteTemplate(out, "T", template.HTML("<script>alert('Вы попались!')</script>"))
47+
48+
Вывод:
49+
50+
Привет, <script>alert('Вы попались!')</script>!
51+
52+
Еще один пример эскейпинга:
53+
54+
import "html/template"
55+
...
56+
t, err := template.New("foo").Parse(`{{define "T"}}Привет, {{.}}!{{end}}`)
57+
err = t.ExecuteTemplate(out, "T", "<script>alert('Вы попались!')</script>")
58+
59+
Вывод:
60+
61+
Привет, &lt;script&gt;alert(&#39;Вы попались!&#39;)&lt;/script&gt;!
62+
63+
## Ссылки
64+
65+
- [Содержание](preface.md)
66+
- Предыдущий раздел: [Проверка введенных данных](04.2.md)
67+
- Следующий раздел: [Дублирование отправки](04.4.md)

ru/04.4.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# 4.4 Дублирование отправки
2+
3+
Не знаю, встречали ли Вы, как на каком-либо блоге или форуме размещено несколько постов подряд с одинаковым содержимым, но я могу сказать Вам, что это происходит по причине того, что отправка постов дублируется пользователем. Это может произойти по многим причинам; иногда пользователь отправляет форму двойным щелчком, или он после отправки решает исправить содержимое поста и нажимает кнопку браузера "Назад". А иногда это - намереные действия злоумышленников. Понятно, что дублирование отправки может привести ко многим проблемам. Поэтому нам нужно принимать эффективные меры для его предотвращения.
4+
5+
Решением этой задачи является добавление в форму скрытого поля с уникальным токеном и проверка этого токена перед перед обработкой введенных данных. А если для отправки формы Вы используете Ajax, можно после того, как данные отправлены, сделать кнопку отправки неактивной.
6+
7+
Давайте усовершенствуем пример из раздела 4.2:
8+
9+
<input type="checkbox" name="interest" value="football">Футбол
10+
<input type="checkbox" name="interest" value="basketball">Баскетбол
11+
<input type="checkbox" name="interest" value="tennis">Теннис
12+
Имя:<input type="text" name="username">
13+
Пароль:<input type="password" name="password">
14+
<input type="hidden" name="token" value="{{.}}">
15+
<input type="submit" value="Login">
16+
17+
Для того, чтобы сгенерировать токен, мы используем хэш MD5 (временная отметка), и добавляем его как в скрытое поле формы ввода данных на стороне клиента, так и в сессионный куки на стороне сервера (см. Раздел 6). Мы можем использовать этот токен для того, чтобы проверить, отправлялись ли уже данные с этой формы:
18+
19+
func login(w http.ResponseWriter, r *http.Request) {
20+
fmt.Println("method:", r.Method) // получаем метод запроса
21+
if r.Method == "GET" {
22+
crutime := time.Now().Unix()
23+
h := md5.New()
24+
io.WriteString(h, strconv.FormatInt(crutime, 10))
25+
token := fmt.Sprintf("%x", h.Sum(nil))
26+
27+
t, _ := template.ParseFiles("login.gtpl")
28+
t.Execute(w, token)
29+
} else {
30+
// запрос данных о входе
31+
r.ParseForm()
32+
token := r.Form.Get("token")
33+
if token != "" {
34+
// проверяем валидность токена
35+
} else {
36+
// если нет токена, возвращаем ошибку
37+
}
38+
fmt.Println("username length:", len(r.Form["username"][0]))
39+
fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) // печатаем на стороне сервера
40+
fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
41+
template.HTMLEscape(w, []byte(r.Form.Get("username"))) // отвечаем клиенту
42+
}
43+
}
44+
45+
![](images/4.4.token.png?raw=true)
46+
47+
Рисунок 4.4 Содержимое браузера после добавления токена
48+
49+
Если обновлять страницу, можно видеть каждый раз новый токен. Это обеспечивает то, что каждая форма уникальна.
50+
51+
На данный момент Вы можете предотвращать множество атак на основе дублирования отправки посредством добавления в формы токенов, но все атаки такого типа предотвратить таким образом нельзя. Для этого нужно проделать еще больше работы.
52+
53+
## Ссылки
54+
55+
- [Содержание](preface.md)
56+
- Предыдущий раздел: [Межсайтовый скриптинг](04.3.md)
57+
- Следующий раздел: [Загрузка файлов](04.5.md)

ru/04.5.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# 4.5 Загрузка файлов
2+
3+
Предположим, у Вас есть веб-сайт наподобие Instagram, и Вы хотите, чтобы пользователи закачивали туда свои фортографии. Как можно реализовать эту функцию?
4+
5+
Для этого нужно добавить в форму, через которую будут закачиваться фотографии, свойство `enctype`. Оно имеет три значения:
6+
7+
```
8+
application/x-www-form-urlencoded Кодировать все символы перед закачкой (по умолчанию).
9+
multipart/form-data Не кодировать. Если в форме есть функционал закачки файлов, Вы должны использовать это значение.
10+
text/plain Конвертировать пробелы в "+", но не кодировать специальные символы.
11+
```
12+
13+
14+
Поэтому, содержимое HTML формы для загрузки файлов должно выглядеть так:
15+
16+
```
17+
<html>
18+
<head>
19+
<title>Загрузка файлов</title>
20+
</head>
21+
<body>
22+
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
23+
<input type="file" name="uploadfile" />
24+
<input type="hidden" name="token" value="{{.}}"/>
25+
<input type="submit" value="upload" />
26+
</form>
27+
</body>
28+
</html>
29+
```
30+
31+
32+
Для работы с этой формой мы должны добавить функцию на сервере:
33+
34+
```
35+
http.HandleFunc("/upload", upload)
36+
37+
// обработка закачки
38+
func upload(w http.ResponseWriter, r *http.Request) {
39+
fmt.Println("Метод:", r.Method)
40+
if r.Method == "GET" {
41+
crutime := time.Now().Unix()
42+
h := md5.New()
43+
io.WriteString(h, strconv.FormatInt(crutime, 10))
44+
token := fmt.Sprintf("%x", h.Sum(nil))
45+
46+
t, _ := template.ParseFiles("upload.gtpl")
47+
t.Execute(w, token)
48+
} else {
49+
r.ParseMultipartForm(32 << 20)
50+
file, handler, err := r.FormFile("uploadfile")
51+
if err != nil {
52+
fmt.Println(err)
53+
return
54+
}
55+
defer file.Close()
56+
fmt.Fprintf(w, "%v", handler.Header)
57+
f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
58+
if err != nil {
59+
fmt.Println(err)
60+
return
61+
}
62+
defer f.Close()
63+
io.Copy(f, file)
64+
}
65+
}
66+
```
67+
68+
69+
Как Вы можете видеть, для загрузки файлов нужно вызвать функцию `r.ParseMultipartForm`. Эта функция имеет аргумент `maxMemory`. После вызова `ParseMultipartForm` файл будет сохранен в памяти сервера с размером `maxMemory`. Если размер файла больше, чем `maxMemory`, остальная часть данных будет сохранена во временном файле в системе. Вы можете использовать `r.FormFile` для того, чтобы работать с файлом, и `io.Copy` для того, чтобы сохранить файл в файловой системе.
70+
71+
Для того, чтобы получить доступ к другим полям формы, не относящимся к загрузке файлов, Вам не нужно вызывать `r.ParseForm`, так как Go вызовет эту функцию, когда понадобится. Также, вызов `ParseMultipartForm` один раз достаточен - многократные вызовы ничего не меняют.
72+
73+
Для загрузки файлов мы используем следующие три шага:
74+
75+
1. Добавить в форму `enctype="multipart/form-data"`.
76+
2. Вызвать на стороне сервера `r.ParseMultipartForm`, чтобы сохранить файл в память или во временный файл.
77+
3. Вызвать `r.FormFile` для обработки файла и сохранения его в файловую систему.
78+
79+
Хэндлером для файла является `multipart.FileHeader`. У него следующая структура:
80+
81+
```
82+
type FileHeader struct {
83+
Filename string
84+
Header textproto.MIMEHeader
85+
// соджержит отфильтрованные или неэкспортируемые поля
86+
}
87+
```
88+
89+
![](images/4.5.upload2.png?raw=true)
90+
91+
Рисунок 4.5 Вывод информации на сервере после получения файла
92+
93+
## Загрузка файлов с помощью клиента
94+
95+
Я показал Вам пример, как можно использовать форму для загрузки файлов. Мы можем сделать так, чтобы загружать файлы через форму без участия человека:
96+
97+
```
98+
package main
99+
100+
import (
101+
"bytes"
102+
"fmt"
103+
"io"
104+
"io/ioutil"
105+
"mime/multipart"
106+
"net/http"
107+
"os"
108+
)
109+
110+
func postFile(filename string, targetUrl string) error {
111+
bodyBuf := &bytes.Buffer{}
112+
bodyWriter := multipart.NewWriter(bodyBuf)
113+
114+
// этот шаг очень важен
115+
fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
116+
if err != nil {
117+
fmt.Println("ошибка записи в буфер")
118+
return err
119+
}
120+
121+
// процедура открытия файла
122+
fh, err := os.Open(filename)
123+
if err != nil {
124+
fmt.Println("ошибка открытия файла")
125+
return err
126+
}
127+
128+
//iocopy
129+
_, err = io.Copy(fileWriter, fh)
130+
if err != nil {
131+
return err
132+
}
133+
134+
contentType := bodyWriter.FormDataContentType()
135+
bodyWriter.Close()
136+
137+
resp, err := http.Post(targetUrl, contentType, bodyBuf)
138+
if err != nil {
139+
return err
140+
}
141+
defer resp.Body.Close()
142+
resp_body, err := ioutil.ReadAll(resp.Body)
143+
if err != nil {
144+
return err
145+
}
146+
fmt.Println(resp.Status)
147+
fmt.Println(string(resp_body))
148+
return nil
149+
}
150+
151+
// пример использования
152+
func main() {
153+
target_url := "http://localhost:9090/upload"
154+
filename := "./astaxie.pdf"
155+
postFile(filename, target_url)
156+
}
157+
```
158+
159+
160+
Этот пример показывает, как можно использовать клиента для загрузки файлов. Он использует `multipart.Write` для того, чтобы записывать файлы в кэш, и посылает их на сервер посредством метода POST.
161+
162+
Если у Вас есть другие поля, которые нужно писать в данные, такие, как имя пользователя, вызывайте по необходимости метод `multipart.WriteField`.
163+
164+
## Ссылки
165+
166+
- [Содержание](preface.md)
167+
- Предыдущий раздел: [Дублирование отправки](04.4.md)
168+
- Следующий раздел: [Итоги раздела](04.6.md)

ru/04.6.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# 4.6 Итоги раздела
2+
3+
В этом разделе мы изучили основные моменты того, как работать с данными в Go, посредством нескольких примеров, таких как обработка входа пользователей и загрузка файлов. Мы также заострили внимание на том, что проверка данных крайне важна для безопасности сайта, а также посвятили одну секцию тому, как фильтровать входные данные посредством регулярных выражений.
4+
5+
Я надеюсь, что теперь Вы больше знаете о процессе коммуникации между клиентом и сервером.
6+
7+
## Ссылки
8+
9+
- [Содержание](preface.md)
10+
- Предыдущий раздел: [Загрузка файлов](04.5.md)
11+
- Следужщий раздел: [Базы данных](05.0.md)

0 commit comments

Comments
 (0)