扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • 宝典简介

    • Go代码安全指南
  • 宝典内容

    • 后台类
    • 通用类

扫码订阅《 》或入驻星球,即可阅读文章!

后台类


腾讯

# 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
        //停止执行
	}
	// 验证通过,继续执行
    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 无法通过白名单校验的应使用html.EscapeString、text/template或bluemonday对<, >, &, ',"等字符进行过滤或编码
  import(
  	"text/template"
  )
  
  // TestHTMLEscapeString HTML特殊字符转义
  func main(inputValue string) string{
  	escapedResult := template.HTMLEscapeString(inputValue)
  	return escapedResult
  }
1
2
3
4
5
6
7
8
9

# 1.2 SQL操作

# 1.2.1【必须】SQL语句默认使用预编译并绑定变量

  • 使用database/sql的prepare、Query或使用GORM等ORM执行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)
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 使用参数化查询,禁止拼接SQL语句,另外对于传入参数用于order by或表名的需要通过校验
// 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 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,获取HOST指向的IP地址转换成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
      
      1
      2
      3
      4
      5
    • 第 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# 1.5 Web跨域

# 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
2
3
4
5
6
7
8
9

# 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文档 (opens new window)

# 1.6.3【必须】外部输入拼接到HTTP响应头中需进行过滤

  • 应尽量避免外部可控参数拼接到HTTP响应头中,如业务需要则需要过滤掉\r、\n等换行符,或者拒绝携带换行符号的外部输入。

# 1.6.4【必须】外部输入拼接到response页面前进行编码处理

  • 直出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
2
3
4
5
6
7
8
9
10

# 1.7 会话管理

# 1.7.1【必须】安全维护session信息

  • 用户登录时应重新生成session,退出登录后应清理session。
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 1.8 访问控制

# 1.8.1【必须】默认鉴权

  • 除非资源完全可对外开放,否则系统默认进行身份认证,使用白名单的方式放开不需要认证的接口或页面。

  • 根据资源的机密程度和用户角色,以最小权限原则,设置不同级别的权限,如完全公开、登录可读、登录可写、特定用户可读、特定用户可写等

  • 涉及用户自身相关的数据的读写必须验证登录态用户身份及其权限,避免越权操作

    -- 伪代码
    select id from table where id=:id and userid=session.userid
    
    1
    2
  • 没有独立账号体系的外网服务使用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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 1.9.2【必须】禁止并发写map

  • 并发写map容易造成程序崩溃并异常退出,建议加锁保护
// bad
func main() {
	m := make(map[int]int)
	//并发读写
	go func() {
		for {
			_ = m[1] 
		}
	}()
	go func() {
		for {
			m[2] = 1
		}
	}()
	select {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 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
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  • 使用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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  • 1.1 输入校验
  • 1.1.1【必须】按类型进行数据校验
  • 1.2 SQL操作
  • 1.2.1【必须】SQL语句默认使用预编译并绑定变量
  • 1.3 网络请求
  • 1.3.1【必须】资源请求过滤验证
  • 1.4 服务器端渲染
  • 1.4.1【必须】模板渲染过滤验证
  • 1.5 Web跨域
  • 1.5.1【必须】跨域资源共享CORS限制请求来源
  • 1.6 响应输出
  • 1.6.1 【必须】设置正确的HTTP响应包类型
  • 1.6.2 【必须】添加安全响应头
  • 1.6.3【必须】外部输入拼接到HTTP响应头中需进行过滤
  • 1.6.4【必须】外部输入拼接到response页面前进行编码处理
  • 1.7 会话管理
  • 1.7.1【必须】安全维护session信息
  • 1.7.2【必须】CSRF防护
  • 1.8 访问控制
  • 1.8.1【必须】默认鉴权
  • 1.9 并发保护
  • 1.9.1【必须】禁止在闭包中直接调用循环变量
  • 1.9.2【必须】禁止并发写map
  • 1.9.3【必须】确保并发安全