1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mirrors-secguide

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Go安全指南.md 24 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 30.11.2024 00:55 2e0ef3e

1. Общие классы

  • I. Реализация кода
    • 1.1 Управление памятью
      • 1.1.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)
    }
}

1.1.6【必须】Запретить повторное освобождение канала

  • Повторное освобождение обычно происходит в процессе обработки исключений. Если злоумышленник намеренно создаёт условия для исключения, это может привести к DoS-атаке.
// bad
func foo(c chan int) {
    defer close(c)
    err := processBusiness()
    if err != nil {
        c <- 0
        close(c) // Повторное освобождение канала
        return
    }
    c <- 1
}

1.1.7【必须】Убедиться, что каждый поток может завершиться

  • Запуск потока приводит к операции push стека. В случае, когда программа не завершается, а поток не имеет условий для завершения, поток теряет управление, его ресурсы не освобождаются, что может привести к утечке памяти.
// bad: Поток не имеет условий для завершения
func doWaiter(name string, second int) {
    for {
        time.Sleep(time.Duration(second) * time.Second)
        fmt.Println(name, " is ready!")
    }
}

1.1.8【Рекомендуется】Не использовать пакет unsafe

  • Поскольку пакет unsafe обходит принципы безопасности памяти Golang, его использование обычно небезопасно и может привести к повреждению памяти. Рекомендуется избегать использования этого пакета, если это возможно. Если необходимо использовать операции с указателями через unsafe, следует провести тщательную проверку безопасности.
// bad: Использование unsafe для работы с исходным указателем
func unsafePointer() {
    b := make([]byte, 1)
    foo := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(0xfffffffe)))
    fmt.Print(*foo + 1)
}

1.1.9【Рекомендуется】Не использовать слайсы в качестве параметров функций

  • При использовании слайсов в качестве параметров функции изменения внутри функции могут повлиять на исходные данные.
  // 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)
  }

1.2 Операции с файлами

1.2.1【Необходимо】Проверка на пересечение путей

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

1.2.2【Необходимо】Права доступа к файлам

  • В зависимости от чувствительности создания файла, установите разные уровни прав доступа, чтобы предотвратить доступ неавторизованных пользователей к конфиденциальным данным. Например, установите права доступа к файлу: -rw-r-----.
ioutil.WriteFile(p, []byte("present"), 0640)

1.3 Системные интерфейсы

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 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/mirrors-secguide.git
git@api.gitlife.ru:oschina-mirror/mirrors-secguide.git
oschina-mirror
mirrors-secguide
mirrors-secguide
main