Руководство по безопасности кода языка Go с открытым исходным кодом Tencent

задняя часть Go
Руководство по безопасности кода языка Go с открытым исходным кодом Tencent

Привет всем, сегодня я представляю вам подробное введение в «Руководство по безопасности кода языка Go с открытым исходным кодом Tencent», составленное golangroadmap.com Go Collection.

Прочитайте исходный текст:woo woo woo.go wolf roadmap.com/books/ вяжущие кишки...

В ежедневном процессе разработки Gopher будет задействовано много обработки данных и соответствующей логики кода.Если вы не будете осторожны, это может привести к ошибкам или даже злонамеренному использованию уязвимостей. Например, переполнение памяти, выход индекса за пределы, исключение нулевого указателя и так далее.

Для опытного разработчика они будут подсознательно сталкиваться с этими проблемами, когда будут писать свой собственный код.Это ценный опыт старших разработчиков, поэтому после того, как их код выходит в сеть, проблем немного.Для более NB разработки у них даже есть свои собственный набор инструментов для кода, который представляет собой итог их многолетнего опыта кодирования, готовый к использованию. Наоборот, для младшего разработчика с этим можно не справиться или даже подумать, поэтому ценность старшей разработки может быть хорошо отражена.

Однако команда не может всегда требовать, чтобы все были старшими разработчиками, и то же самое верно для всей компании и всей отрасли, поэтому существует множество фреймворков, инструментов и спецификаций, которые могут позволить нам встать на плечи наших предшественников, а также Может писать отличный код.

Недавно Tencent выпустила руководство по безопасности кода, целью которого является определение точек риска на уровне API и предоставление подробных и осуществимых решений для безопасного кодирования.

Основываясь на концепции DevSecOps, мы надеемся объяснить схему кодирования безопасности более понятным для разработчиков способом и помочь избежать уязвимостей из источника.

GitHub.com/Tencent/цвет…

Tencent выпустила рекомендации по безопасности кода на 6 языках: C/C++, Go, Java, Node, JavaScript и Python. В основном мы представляем рекомендации по безопасности кода для языка Go. Подробности следующие:

универсальный класс

1. Класс реализации кода

1.1 Проверка ввода

1.1.1 [Обязательно] Верификация данных по типу

  • Должны использоваться все внешние входные параметрыvalidatorВыполните проверку белого списка. Содержимое проверки включает, помимо прочего, длину данных, диапазон данных, тип данных и формат. Если проверка не пройдена, ее следует отклонить.
// good
import (
	"fmt"
	"github.com/go-playground/validator/v10"
)

var validate *validator.Validate
validate = validator.New()
func validateVariable() {
	myEmail := "abc@tencent.com"
	errs := validate.Var(myEmail, "required,email")
	if errs != nil {
		fmt.Println(errs)
		return
        //停止执行
	}
	// 验证通过,继续执行
    ...
}
  • Те, которые не могут пройти проверку белого списка, должны использоватьсяhtml.EscapeString,text/templateилиbluemondayправильно<, >, &, ',"и другие символы для фильтрации или кодирования
  import(
  	"text/template"
  )
  
  // TestHTMLEscapeString HTML特殊字符转义
  func main(inputValue string) string{
  	escapedResult := template.HTMLEscapeString(inputValue)
  	return escapedResult
  }

1.2 SQL-операции

1.2.1 [Обязательно] В операторах SQL по умолчанию используются предварительно скомпилированные переменные и переменные связывания.

  • использоватьdatabase/sqlподготовить, запросить или использовать ORM, например GORM, для выполнения операций SQL
  import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
  )
  
  type Product struct {
    gorm.Model
    Code string
    Price uint
  }
  ...
  var product Product
  db.First(&product, 1)
  • Используйте параметризованные запросы, запретите объединение операторов SQL и пройдите проверку входящих параметров для упорядочения по имени или имени таблицы.
// bad
  import (
  	"database/sql"
  	"fmt"
  	"net/http"
  )
  
  func handler(db *sql.DB, req *http.Request) {
  	q := fmt.Sprintf("SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='%s' ORDER BY PRICE",
  		req.URL.Query()["category"])
  	db.Query(q)
  }

// good
func handlerGood(db *sql.DB, req *http.Request) {
    //使用?占位符
  	q := "SELECT ITEM,PRICE FROM PRODUCT WHERE ITEM_CATEGORY='?' ORDER BY PRICE"
  	db.Query(q, req.URL.Query()["category"])
}

1.3 Сетевой запрос

1.3.1 [Требуется] Фильтрация и проверка запросов ресурсов

  • использовать"net/http"метод нижеhttp.Get(url),http.Post(url, contentType, body),http.Head(url ),http.PostForm(url, data),http.Do(req)Когда значение переменной управляется извне (имеется в виду динамически получаемое из параметра), цель запроса должна строго проверяться на предмет безопасности.

  • Например, если доменное имя запрашиваемого ресурса принадлежит к фиксированному диапазону, если только разрешитьa.qq.comа такжеb.qq.com, надо делать ограничение по белому списку. Если белый список неприменим, рекомендуемые логические шаги для проверки:

    • Шаг 1. Разрешить только протокол HTTP или HTTPS

    • Шаг 2, проанализируйте целевой URL, чтобы получить его HOST

    • Шаг 3, проанализируйте HOST, получите IP-адрес, на который указывает HOST, и преобразуйте его в тип Long.

    • Шаг 4. Проверьте, является ли IP-адрес IP-адресом интрасети, а сегмент сети:

      // 以RFC定义的专有网络为例,如有自定义私有网段亦应加入禁止访问列表。
      10.0.0.0/8
      172.16.0.0/12
      192.168.0.0/16
      127.0.0.0/8
      
    • Шаг 5. Запросить URL-адрес

    • Шаг 6. Если есть переход, выполнить 1 после перехода, в противном случае привязать проверенный IP и доменное имя, и инициировать запрос к URL

  • Официальная библиотекаencoding/xmlСсылки на внешние объекты не поддерживаются, используйте эту библиотеку, чтобы избежать уязвимостей xxe.

  import (
  	"encoding/xml"
  	"fmt"
      "os"
  )
  
  func main() {
  	type Person struct {
  		XMLName   xml.Name `xml:"person"`
  		Id        int      `xml:"id,attr"`
  		UserName string   `xml:"name>first"`
  		Comment string `xml:",comment"`
  	}
  
  	v := &Person{Id: 13, UserName: "John"}
  	v.Comment = " Need more details. "
  
  	enc := xml.NewEncoder(os.Stdout)
  	enc.Indent("  ", "    ")
  	if err := enc.Encode(v); err != nil {
  		fmt.Printf("error: %v\n", err)
  	}
  
  }

1.4 Рендеринг на стороне сервера

1.4.1 [Требуется] Проверка фильтра рендеринга шаблона

  • использоватьtext/templateилиhtml/templateПри рендеринге шаблона запрещается вводить в шаблон внешние входные параметры, либо разрешать вводить только символы из белого списка.
   // bad
    func handler(w http.ResponseWriter, r *http.Request) {
      r.ParseForm()
      x := r.Form.Get("name")
     
      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")
    }

// 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:
            ...
    }
    

1.5 Междоменный веб-сайт

1.5.1 [Требуется] Совместное использование ресурсов между источниками CORS ограничивает источник запросов

  • Неправильная защита запросов CORS может привести к утечке конфиденциальной информации, поэтому Access-Control-Allow-Origin должен быть строго настроен на использование политики того же источника для защиты.
 // good
  c := cors.New(cors.Options{
      AllowedOrigins: []string{"http://qq.com", "https://qq.com"},
      AllowCredentials: true,
      Debug: false,
  })
  
  //引入中间件
  handler = c.Handler(handler)

1.6 Вывод ответа

1.6.1 [Требуется] Установите правильный тип пакета ответа HTTP

  • Заголовок ответа Content-Type должен соответствовать фактическому содержимому ответа. Например: тип данных ответа API — json, тогда в заголовке ответа используетсяapplication/json; если xml, установить вtext/xml.

1.6.2 [Требуется] Добавить заголовок ответа безопасности

  • Все интерфейсы, страницы, добавить заголовки ответаX-Content-Type-Options: nosniff.
  • Все интерфейсы, страницы, добавить заголовки ответаX-Frame-Options . При необходимости установите допустимый диапазон, в том числе:DENY,SAMEORIGIN,ALLOW-FROM origin. Ссылка на использование:Документация MDN

1.6.3 [Требуется] Внешний вход должен быть отфильтрован при вставке его в заголовок ответа HTTP.

  • Избегайте встраивания внешних контролируемых параметров в заголовки ответов HTTP, насколько это возможно, и отфильтровывайте их, если это необходимо для бизнеса.\r,\nдождитесь новой строки или отклоните внешний ввод, который содержит новую строку.

1.6.4 [Требуется] Кодировать внешний ввод перед его соединением со страницей ответа

  • Рекомендуется использовать прямые html-страницы или использовать шаблоны для создания html-страниц.text/templateавтоматическое кодирование или использованиеhtml.EscapeStringилиtext/templateправильно<, >, &, ',"кодировка символов.
import(
	"html/template"
)        

func outtemplate(w http.ResponseWriter,r *http.Request) {
    param1 := r.URL.Query().Get("param1")
    tmpl := template.New("hello")
    tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`)
    tmpl.ExecuteTemplate(w, "T", param1)
}

1.7 Управление сеансом

1.7.1 [Требуется] Безопасное хранение информации о сеансе

  • Сеанс должен быть перегенерирован, когда пользователь входит в систему, и сеанс должен быть очищен после выхода из системы.
import (
	"net/http"
	"github.com/gorilla/mux"
	"github.com/gorilla/handlers"
)

//创建cookie
func setToken(res http.ResponseWriter, req *http.Request) {
    expireToken := time.Now().Add(time.Minute * 30).Unix()
    expireCookie := time.Now().Add(time.Minute * 30)
    ...
    cookie := http.Cookie{
        Name: "Auth",
        Value: signedToken,
        Expires: expireCookie, // 过期失效
        HttpOnly: true,
        Path: "/",
        Domain: "127.0.0.1",
        Secure: true
    }

    http.SetCookie(res, &cookie)
    http.Redirect(res, req, "/profile", 307)
}
// 删除cookie
func logout(res http.ResponseWriter, req *http.Request) {
    deleteCookie := http.Cookie{
        Name: "Auth",
        Value: "none",
        Expires: time.Now()
    }
    http.SetCookie(res, &deleteCookie)
    return
}

1.7.2 [Требуется] Защита от CSRF

  • Интерфейсы, связанные с системными операциями или читаемой конфиденциальной информацией, должны быть проверены.Refererили добавитьcsrf_token.
// good
import (
    "net/http"
    "github.com/gorilla/csrf"
    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/signup", ShowSignupForm)
    r.HandleFunc("/signup/post", SubmitSignupForm)
    //使用csrf_token验证
    http.ListenAndServe(":8000",
        csrf.Protect([]byte("32-byte-long-auth-key"))(r))
}

1.8 Контроль доступа

1.8.1 [Требуется] Аутентификация по умолчанию

  • Если ресурс полностью не открыт для внешнего мира, система по умолчанию выполняет аутентификацию личности и использует белый список для освобождения интерфейсов или страниц, не требующих аутентификации.

  • В соответствии со степенью конфиденциальности ресурсов и ролей пользователей и на основе принципа наименьших привилегий установите различные уровни разрешений, такие как полное раскрытие, доступ для чтения при входе в систему, доступ для записи при входе в систему, доступ для чтения определенными пользователями и доступ для записи определенными пользователями, и т.п.

  • Чтение и запись данных, связанных с пользователем, должны проверять личность вошедшего в систему пользователя и его разрешения, чтобы избежать несанкционированных операций.

    -- 伪代码
      select id from table where id=:id and userid=session.userid
    
  • Использование экстранет-сервисов без независимой системы учетных записейQQили微信Вход, использование интранет-сервисов统一登录服务Войдите в систему, другие службы, которые используют учетную запись и пароль для входа, должны добавить код подтверждения и другую вторичную проверку.

1.9 Защита от параллелизма

1.9.1 [Требуется] Прямые вызовы переменных цикла в замыканиях запрещены

  • Запустите сопрограмму в цикле, когда значение индекса цикла используется в сопрограмме, поскольку несколько сопрограмм используют одну и ту же переменную одновременно, произойдет гонка данных, что приведет к ненормальным результатам выполнения.
// bad
func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    var group sync.WaitGroup

    for i := 0; i < 5; i++ {
        group.Add(1)
        go func() {
            defer group.Done()
            fmt.Printf("%-2d", i) //这里打印的i不是所期望的
        }()
    }
    group.Wait()
}

// good
func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    var group sync.WaitGroup

    for i := 0; i < 5; i++ {
        group.Add(1)
        go func(j int) {
            defer func() {
                if r := recover(); r != nil {
                    fmt.Println("Recovered in start()")
                }
                group.Done()
            }()
        fmt.Printf("%-2d", j) // 闭包内部使用局部变量
        }(i)  // 把循环变量显式地传给协程
    }
    group.Wait()
}

1.9.2 [Требуется] Параллельная запись карты запрещена

  • Параллельная запись на карту может легко привести к сбою программы и аварийному завершению работы. Рекомендуется защита от блокировки.
// 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 // used only by writers
	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) // 导入struct当前数据
		m2 := make(Map)      // 创建新值
		for k, v := range m1 {
			m2[k] = v
		}
		m2[key] = val
		m.Store(m2)   // 用新的替代当前对象
	}
	_, _ = read, insert
}

Фоновый класс

1 класс реализации кода

1.1 Управление памятью

1.1.1 [Обязательно] Проверка длины среза

  • При работе со срезом необходимо оценить допустимость длины, чтобы предотвратить панику программы.
// bad: 未判断data的长度,可导致 index out of range 
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
}

// bad: slice bounds out of range
func foo() {
    var slice = []int{0, 1, 2, 3, 4, 5, 6}
    fmt.Println(slice[:10]) 
}

// good: 使用data前应判断长度是否合法 
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.
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,那么不会new Data
    if len(b) > 2 {
        p.Data = new(Data)
        // Unmarshal(b[i:], p.Data)
    }
  
    return nil
}

// bad: 未判断指针是否为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)
}

// good: 判断Data指针是否未nil
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 [Требуется] Целочисленная безопасность

  • При выполнении цифровых операций необходимо ограничить длину, чтобы внешние операции ввода не вызывали исключений:

    • Убедитесь, что операции с целыми числами без знака не меняются местами.
    • Обеспечение отсутствия переполнения в целочисленной арифметике со знаком
    • Обеспечьте отсутствие ошибок усечения во время целочисленных преобразований
    • Убедитесь, что при преобразовании в целые числа нет ошибок со знаком
  • Следующие сценарии должны быть строго ограничены по продолжительности:

    • как индекс массива
    • как длина или размер объекта
    • как граница массива (например, как счетчик циклов)
// bad: 未限制长度,导致整数溢出
func overflow(numControlByUser int32) {
    var numInt int32 = 0
    numInt = numControlByUser + 1
    //对长度限制不当,导致整数溢出
    fmt.Printf("%d\n", numInt)
    //使用numInt,可能导致其他错误
}

func main() {
    overflow(2147483647)
}

// good: 
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 [Требуется] сделать проверку длины распределения

  • При выделении памяти необходимо проверять управляемую извне длину, чтобы программа не паниковала.
// bad
func parse(lenControlByUser int, data[] byte) {
    size := lenControlByUser
    //对外部传入的size,进行长度判断以免导致panic
    buffer := make([]byte, size)
    copy(buffer, data)
}

// good
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
}

1.1.5 [Должен] запрещать одновременное использование SetFinalizer и циклических ссылок указателя

  • Когда объект выбран сборщиком мусора до тех пор, пока память не будет удалена, runtime.SetFinalizer() не будет выполняться, даже если программа завершается нормально или возникает ошибка. Хотя «циклическая ссылка», состоящая из указателей, может быть правильно обработана GC, поскольку порядок зависимостей финализатора не может быть определен, runtime.SetFinalizer() не может быть вызвана, поэтому целевой объект не может стать доступным, а память не может быть переработана.
// 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) // 重复释放channel
        return
    }
    c <- 1
}

// good
func foo(c chan int) {
    defer close(c) // 使用defer延迟关闭channel
    err := processBusiness()
    if err != nil {
        c <- 0
        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 обходит принципы безопасности памяти Golang, использовать эту библиотеку, как правило, небезопасно, что может привести к повреждению памяти. Старайтесь не использовать этот пакет. Если вы должны использовать указатель небезопасной операции, вы должны выполнить проверку безопасности.
// bad: 通过unsafe操作原始指针
func unsafePointer() {
    b := make([]byte, 1)
    foo := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(0xfffffffe)))
    fmt.Print(*foo + 1)
}

// [signal SIGSEGV: segmentation violation code=0x1 addr=0xc100068f55 pc=0x49142b]

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

  • Срез является ссылочным типом, при его использовании в качестве входного параметра функции передается адрес, модификация среза повлияет и на исходные данные.
  // bad
  // slice作为函数入参时是地址传递
  func modify(array []int) {
      array[0] = 10 // 对入参slice的元素修改会影响原始数据
  }
  
  func main() {
      array := []int{1, 2, 3, 4, 5}
  
      modify(array)
      fmt.Println(array) // output:[10 2 3 4 5]
  }

  // good
  // 数组作为函数入参时,而不是slice
  func modify(array [5]int) {
    array[0] = 10
  }

  func main() {
      // 传入数组,注意数组与slice的区别
      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 {
		p, _ := filepath.Abs(f.Name)
		if !strings.Contains(p, "..") {
			ioutil.WriteFile(p, []byte("present"), 0640)
		}
	}
	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При создании оболочки параметр (arg) после -c объединяет внешний ввод и должен фильтровать потенциально вредоносные символы, такие как \n $ & ; | ' " ( ) `;
// bad
func foo() {
	userInputedVal := "&& echo 'hello'" // 假设外部传入该变量值
	cmdName := "ping " + userInputedVal

	//未判断外部输入是否存在命令注入字符,结合sh可造成命令注入
	cmd := exec.Command("sh", "-c", cmdName)
	output, _ := cmd.CombinedOutput()
	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 используется для связи по сети

  • Было проверено, что протокол связи, передаваемый в виде открытого текста, имеет большой риск для безопасности.После захвата посредником он может привести ко многим угрозам безопасности.Поэтому для обеспечения безопасности связи необходимо использовать безопасный метод связи, по крайней мере, TLS.Для например, gRPC/Websocket используют TLS1.3.
// 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 Включить проверку сертификата

  • Сертификат 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 [Требуется] Обработка исключений и регистрация

  • Разумно используйте панику, восстановление и отсрочку для обработки системных исключений, чтобы избежать вывода сообщений об ошибках во внешний интерфейс.
defer func () {
        if r := recover(); r != nil {
            fmt.Println("Recovered in start()")
        }
    }()
  • Запрещено включать режим отладки во внешнюю среду или выводить лог работы программы на фронтенд

Пример ошибки:

dlv --listen=:2345 --headless=true --api-version=2 debug test.go

Правильный пример:

dlv debug test.go

1.6 Шифрование и дешифрование

1.6.1 [Требуется] Нет жестко запрограммированных паролей/ключей

  • При выполнении таких операций, как вход пользователя в систему, алгоритмы шифрования и дешифрования и т. д., ключ или пароль не должны быть жестко закодированы в коде, а пароль или ключ можно установить, изменив алгоритм или конфигурацию.
// bad
const (
	user     = "dbuser"
	password = "s3cretp4ssword"
)

func connect() *sql.DB {
	connStr := fmt.Sprintf("postgres://%s:%s@localhost/pqgotest", user, password)
	db, err := sql.Open("postgres", connStr)
	if err != nil {
		return nil
	}
	return db
}

// bad
var (
	commonkey = []byte("0123456789abcdef")
)

func AesEncrypt(plaintext string) (string, error) {
	block, err := aes.NewCipher(commonkey)
	if err != nil {
		return "", err
	}
}

1.6.2 [Требуется] Безопасность хранения ключей

  • При использовании симметричного криптографического алгоритма ключ шифрования должен быть защищен. Когда алгоритм включает конфиденциальные бизнес-данные, ключ шифрования можно согласовать с помощью асимметричного алгоритма. Для другого менее конфиденциального шифрования данных ключ может быть защищен путем преобразования алгоритма и т.д.

1.6.3 [Рекомендуется] Не используйте слабые алгоритмы паролей

  • При использовании алгоритмов шифрования не рекомендуется использовать алгоритмы со слабой стойкостью шифрования.

Пример ошибки:

crypto/des,crypto/md5,crypto/sha1,crypto/rc4等。

1.7 Регулярные выражения

1.7.1 [Рекомендуется] Используйте регулярное выражение для сопоставления с регулярным выражением

  • Неправильно написанные регулярные выражения могут быть использованы для DoS-атак, что приведет к недоступности сервиса.Рекомендуется использовать пакет regexp для сопоставления регулярных выражений. Регулярное выражение гарантирует линейное время выполнения и плавный отказ: ограничения памяти накладываются на синтаксический анализатор, компилятор и механизм выполнения. Однако регулярное выражение не поддерживает следующие функции регулярных выражений. Если бизнес зависит от этих функций, регулярное выражение не подходит для использования.
// good
matched, err := regexp.MatchString(`a.b`, "aaxbb")
fmt.Println(matched) // true
fmt.Println(err)     // nil (regexp is valid)

Дополнительные книги по Go см.www.golangroadmap.comПерейти Коллекция