1. Общие классы
Плохо | Хорошо |
---|---|
```go | |
// bad: не проверяется длина данных, что может привести к выходу индекса за пределы диапазона | |
func decode(data []byte) bool { |
if data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'E' && data[5] == 'R' {
fmt.Println("Bad")
return true
}
return false
}
|
| ```go
// good: перед использованием данных следует проверить их длину
func decode(data []byte) bool {
if len(data) == 6 {
if data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'E' && data[5] == 'R' {
fmt.Println("Good")
return true
}
}
return false
}
* 1.1.2 Обязательная проверка нулевого указателя
* При работе с указателями необходимо проверять их на нулевое значение, особенно при выполнении операции Unmarshal для структур.
Плохо | Хорошо |
---|---|
```go | |
type Packet struct { |
PackeyType uint8
PackeyVersion uint8
Data *Data
}
type Data struct { Stat uint8 Len uint8 Buf [8]byte }
func (p *Packet) UnmarshalBinary(b []byte) error { if len(b) < 2 { return io.EOF }
p.PackeyType = b[0]
p.PackeyVersion = b[1]
// Если длина равна 2, то Data не будет создана
if len(b) > 2 {
p.Data = new(Data)
}
return nil
}
// Плохо: не проверяется нулевой указатель func main() { packet := new(Packet) data := make([]byte, 2) if err := packet.UnmarshalBinary(data); err != nil { fmt.Println("Failed to unmarshal packet") return }
fmt.Printf("Stat: %v\n", packet.Data.Stat)
}
|
| ```go
func main() {
packet := new(Packet)
data := make([]byte, 2)
if err := packet.UnmarshalBinary(data); err != nil {
fmt.Println("Failed to unmarshal packet")
return
}
if packet.Data == nil {
return
}
fmt.Printf("Stat: %v\n", packet.Data.Stat)
}
* 1.1.3 Обязательная безопасность целых чисел
* При выполнении операций с целыми числами необходимо ограничивать их длину, чтобы предотвратить возможные ошибки из-за внешних входных данных.
Плохо | Хорошо |
---|---|
```go | |
// Плохо: длина не ограничена, что приводит к переполнению целого числа | |
func overflow(numControlByUser int32) { |
var numInt int32 = 0
numInt = numControlByUser + 1
// Неправильное ограничение длины приводит к переполнению целого числа
fmt.Printf("%d\n", numInt)
// Использование numInt может вызвать другие ошибки
}
func main() { overflow(2147483647) }
|
| ```go
func overflow(numControlByUser int32) {
var numInt int32 = 0
numInt = numControlByUser + 1
if numInt < 0 {
fmt.Println("integer overflow")
return
}
fmt.Println("integer ok")
}
func main() {
overflow(2147483647)
}
* 1.1.4 Обязательная проверка распределения памяти с помощью make
* Необходимо проверять длину внешних данных при использовании make для выделения памяти, чтобы избежать возможных ошибок.
Плохо | Хорошо |
---|---|
```go | |
// Плохо | |
func parse(lenControlByUser int, data []byte) { |
size := lenControlByUser
// Проверка длины внешних данных для предотвращения паники
buffer := make([]byte, size)
copy(buffer, data)
}
|
| ```go
func parse(lenControlByUser int, data []byte) ([]byte, error) {
size := lenControlByUser
// Ограничение размера внешних данных в допустимом диапазоне
if size > 64*1024*1024 {
return nil, errors.New("value too large")
}
buffer := make([]byte, size)
copy(buffer, data)
return buffer, nil
}
``` Когда объект выбирается сборщиком мусора (GC) для удаления из памяти, runtime.SetFinalizer() не выполняется, даже если программа завершается нормально или происходит ошибка. Хотя циклические ссылки, основанные на указателях, могут быть правильно обработаны GC, невозможно определить порядок зависимостей finalizer, что приводит к невозможности вызова runtime.SetFinalizer(), в результате чего целевой объект не может стать доступным, и память не может быть восстановлена.
```go
// bad
func foo() {
var a, b Data
a.o = &b
b.o = &a
// Указательная циклическая ссылка, хотя SetFinalizer() нельзя вызвать нормально
runtime.SetFinalizer(&a, func(d *Data) {
fmt.Printf("a %p final.\n", d)
})
runtime.SetFinalizer(&b, func(d *Data) {
fmt.Printf("b %p final.\n", d)
})
}
func main() {
for {
foo()
time.Sleep(time.Millisecond)
}
}
// bad
func foo(c chan int) {
defer close(c)
err := processBusiness()
if err != nil {
c <- 0
close(c) // Повторное освобождение канала
return
}
c <- 1
}
// bad: Поток не имеет условий для завершения
func doWaiter(name string, second int) {
for {
time.Sleep(time.Duration(second) * time.Second)
fmt.Println(name, " is ready!")
}
}
// bad: Использование unsafe для работы с исходным указателем
func unsafePointer() {
b := make([]byte, 1)
foo := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(0xfffffffe)))
fmt.Print(*foo + 1)
}
// bad
// Слайс используется как параметр функции, содержащий указатель на исходный массив
func modify(array []int) {
array[0] = 10 // Изменение элемента слайса влияет на исходные данные
}
func main() {
array := []int{1, 2, 3, 4, 5}
modify(array)
fmt.Println(array) // output:[10 2 3 4 5]
}
// good
// Массив используется как параметр функции вместо слайса
func modify(array [5]int) {
array[0] = 10
}
func main() {
// Передача массива вместо слайса, обратите внимание на разницу между массивом и слайсом
array := [5]int{1, 2, 3, 4, 5}
modify(array)
fmt.Println(array)
}
// bad: Произвольное чтение файла
func handler(w http.ResponseWriter, r *http.Request) {
path := r.URL.Query()["path"][0]
// Чтение файла без фильтрации пути, что может привести к произвольному чтению файла
data, _ := ioutil.ReadFile(path)
w.Write(data)
// Для внешних переданных имён файлов также требуется проверка на наличие ../ и других путей пересечения
data, _ = ioutil.ReadFile(filepath.Join("/home/user/", path))
w.Write(data)
}
// bad: Произвольная запись файла
func unzip(f string) {
r, _ := zip.OpenReader(f)
for _, f := range r.File {
p, _ := filepath.Abs(f.Name)
// Запись файла без проверки имени сжатого файла, что может привести к ../ и другим путям пересечения, произвольной записи файла
ioutil.WriteFile(p, []byte("present"), 0640)
}
}
// good: Проверка имени сжатого файла на наличие .. и других признаков пересечения путей, предотвращение произвольной записи
func unzipGood(f string) bool {
r, err := zip.OpenReader(f)
if err != nil {
fmt.Println("read zip file fail")
return false
}
for _, f := range r.File {
if !strings.Contains(f.Name, "..") {
p, _ := filepath.Abs(f.Name)
ioutil.WriteFile(p, []byte("present"), 0640)
} else {
return false
}
}
return true
}
-rw-r-----
.ioutil.WriteFile(p, []byte("present"), 0640)
1.3.1【Необходимо】Проверка выполнения команд
exec.Command
, exec.CommandContext
, syscall.StartProcess
и os.StartProcess
, если первый параметр (путь) напрямую принимает внешние входные значения, используйте белый список для ограничения исполняемых команд и запретите передачу команд bash
, cmd
и sh
;exec.Command
и exec.CommandContext
для создания оболочки с помощью bash
, cmd
или sh
, фильтруйте специальные символы, такие как \n $ & ; | ' " ( ) `, во втором параметре (arg), который объединяет внешние входные данные. ```
fmt.Println(string(output))cmdName := "ls" // 未判断外部输入是否是预期命令 cmd := exec.Command(cmdName) output, _ := cmd.CombinedOutput() fmt.Println(string(output)) }
// good func checkIllegal(cmdName string) bool { if strings.Contains(cmdName, "&") || strings.Contains(cmdName, "|") || strings.Contains(cmdName, ";") || strings.Contains(cmdName, "$") || strings.Contains(cmdName, "'") || strings.Contains(cmdName, "`") || strings.Contains(cmdName, "(") || strings.Contains(cmdName, ")") || strings.Contains(cmdName, """) { return true } return false }
func main() { userInputedVal := "&& echo 'hello'" cmdName := "ping " + userInputedVal
if checkIllegal(cmdName) { // 检查传给sh的命令是否有特殊字符
return // 存在特殊字符直接return
}
cmd := exec.Command("sh", "-c", cmdName)
output, _ := cmd.CombinedOutput()
fmt.Println(string(output))
}```
1.4. Коммуникационная безопасность
1.4.1. Обязательно: сетевое взаимодействие должно осуществляться с использованием TLS.
В настоящее время передача данных по открытым протоколам связи считается небезопасной и может привести к серьёзным рискам безопасности.
// good
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
w.Write([]byte("This is an example server.\n"))
})
// 服务器配置证书与 приватным ключом
log.Fatal(http.ListenAndServeTLS(":443", "yourCert.pem", "yourKey.pem", nil))
}
1.4.2. Рекомендуется: включить проверку подлинности TLS-сертификата.
Сертификаты должны быть действительными, не просроченными и соответствовать доменному имени. В рабочей среде рекомендуется включать проверку подлинности сертификата на стороне сервера.
// bad
import (
"crypto/tls"
"net/http"
)
func doAuthReq(authReq *http.Request) *http.Response {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
res, _ := client.Do(authReq)
return res
}
// good
import (
"crypto/tls"
"net/http"
)
func doAuthReq(authReq *http.Request) *http.Response {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
}
client := &http.Client{Transport: tr}
res, _ := client.Do(authReq)
return res
}
1.5. Защита конфиденциальных данных
1.5.1. Обязательно: запретить жёсткое кодирование конфиденциальной информации в коде.
Это может привести к раскрытию конфиденциальной информации злоумышленникам или усложнить управление и обслуживание кода.
1.5.2. Обязательно: минимизировать объём выводимых данных.
Не следует выводить избыточные поля, которые могут раскрыть конфиденциальную информацию. Если необходимо вывести конфиденциальные данные, их следует предварительно обработать.
// bad
func serve() {
http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
user := r.Form.Get("user")
pw := r.Form.Get("password")
log.Printf("Registering new user %s with password %s.\n", user, pw)
})
http.ListenAndServe(":80", nil)
}
// good
func serve1() {
http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
user := r.Form.Get("user")
pw := r.Form.Get("password")
log.Printf("Registering new user %s.\n", user)
// ...
use(pw)
})
http.ListenAndServe(":80", nil)
}
Следует избегать раскрытия конфиденциальной информации через методы GET, комментарии в коде, автозаполнение и кэширование.
1.5.3. Обязательно: шифровать конфиденциальные данные при хранении.
Конфиденциальные данные должны быть зашифрованы перед сохранением с использованием алгоритмов, таких как SHA2 или RSA.
1.5.4. Обязательно: обрабатывать исключения и вести логирование.
Необходимо использовать функции panic, recover и defer для обработки системных исключений и предотвращения вывода ошибочной информации на клиентскую сторону.
defer func () {
if r := recover(); r != nil {
fmt.Println("Recovered in start()")
}
}()
Также следует избегать включения режима отладки во внешней среде или вывода логов работы программы на клиентскую сторону.
1.6. Шифрование и дешифрование
1.6.1. Обязательно: не использовать жёстко закодированные пароли или ключи.
При выполнении операций, таких как аутентификация или шифрование, нельзя использовать жёстко заданные пароли или ключи в коде. ``` t := template.New("main") t, _ = t.Parse(tmpl) t.Execute(w, "Hello")
Это похоже на язык Go.
// good import ( "fmt" "github.com/go-playground/validator/v10" )
var validate *validator.Validate validate = validator.New()
func validateVariable(val) { errs := validate.Var(val, "gte=1,lte=100") // 限制必须是1-100的正整数 if errs != nil { fmt.Println(errs) return false } return true }
func handler(w http.ResponseWriter, r *http.Request) { r.ParseForm() x := r.Form.Get("name")
if validateVariable(x) {
var tmpl = `<!DOCTYPE html><html><body>
<form action="/" method="post">
First name:<br>
<input type="text" name="name" value="">
<input type="submit" value="Submit">
</form><p>` + x + ` </p></body></html>`
t := template.New("main")
t, _ = t.Parse(tmpl)
t.Execute(w, "Hello")
} else {
// ...
}
}
Здесь создаётся функция `handler`, которая принимает два аргумента: `w` и `r`. В функции происходит парсинг формы запроса, а затем вызывается функция `validateVariable`, которая проверяет значение переменной `x` на соответствие определённым условиям. Если проверка проходит успешно, то формируется HTML-код с формой и значением переменной `x`, который затем обрабатывается шаблонизатором.
В результате выполнения этого кода будет сформирована HTML-страница с формой, в которую пользователь сможет ввести своё имя. После отправки формы это имя будет выведено на экран. **1.9.2**
*Нельзя выполнять операции записи в map одновременно.*
Это может привести к сбою программы и аварийному завершению работы. Рекомендуется использовать блокировку для защиты данных.
```go
// bad
func main() {
m := make(map[int]int)
// одновременное чтение и запись
go func() {
for {
_ = m[1]
}
}()
go func() {
for {
m[2] = 1
}
}()
select {}
}
1.9.3
Необходимо обеспечить безопасность при параллельной работе. Если чувствительные операции не защищены должным образом, это может привести к ошибкам чтения и записи данных и нарушению бизнес-логики. Можно использовать синхронизацию или атомарные операции для обеспечения безопасности.
Синхронизация с использованием блокировок
// good
var count int
func Count(lock *sync.Mutex) {
lock.Lock() // блокировка записи
count++
fmt.Println(count)
lock.Unlock() // снятие блокировки записи, любой вызов Lock() или RLock() должен сопровождаться вызовом Unlock() или RUnlock()
}
func main() {
lock := &sync.Mutex{}
for i := 0; i < 10; i++ {
go Count(lock) // передача указателя на блокировку предотвращает несоответствие блокировок внутри функции и снаружи
}
for {
lock.Lock()
c := count
lock.Unlock()
runtime.Gosched() // передача управления другим горутинам
if c > 10 {
break
}
}
}
Использование пакета sync/atomic
для выполнения атомарных операций
// good
import (
"sync"
"sync/atomic"
)
func main() {
type Map map[string]string
var m atomic.Value
m.Store(make(Map))
var mu sync.Mutex // используется только для записи
read := func(key string) (val string) {
m1 := m.Load().(Map)
return m1[key]
}
insert := func(key, val string) {
mu.Lock() // синхронизация с потенциальной операцией записи
defer mu.Unlock()
m1 := m.Load().(Map) // загрузка текущих данных
m2 := make(Map) // создание нового значения
for k, v := range m1 {
m2[k] = v
}
m2[key] = val
m.Store(m2) // замена текущего объекта новым
}
_, _ = read, insert
}
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )