11. 事项 - wwj-2017-1117/AES_OK GitHub Wiki

注意 1:map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作: _ = &ages["bob"] // compile error: cannot take address of map element 禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效。

注意 2: Map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。所以map遍历的顺序是随机的,每一次遍历的顺序都不相同。想要按顺序遍历key/value对,就必须显式地对key进行排序,可以使用sort包的Strings函数对字符串slice进行排序。

2 漏洞类型及危险函数 2.1 xss go语言同样是存在XSS漏洞的,黑盒攻击模式和其他语言并没有差别,前端js等框架导致的XSS漏洞请参考具体的前端攻击模式库,本文主要介绍go语言自身导致的XSS漏洞。

为了防止XSS,go语言提供EscapeString和template功能,下面分开描述。 go语言内置封装html.EscapeString和html.UnescapeString,其中html.EscapeString会对’、”、&、<、>这五个字符进行转义。

完整代码实现请参考下面的连接: https://golang.org/src/html/escape.go?s=4699:4735#L177 EscapeString直接字符串替换的HTML转义,能有效防范字符串输入的XSS,但是具体的HTML有多种场景,需要合适的 HTML 转义可以有效避免 XSS 漏洞,完善的转义库需要针对上下文制定多种规则,例如 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等等。业务需要根据每个插入点所处的上下文,选取不同的转义规则。 通常,转义库是不能判断插入点上下文的(Not Context-Aware),实施转义规则的责任就落到了业务身上,需要每个业务都充分理解 XSS 的各种情况,并且需要保证每一个插入点使用了正确的转义规则。这种机制工作量大,全靠人工保证,很容易造成 XSS 漏洞,安全人员也很难发现隐患。 为了有效防止XSS,go语言template采用了Google 提出了一个概念Automatic Context-Aware Escaping。Context-Aware,就是说模板引擎在解析模板字符串的时候,就解析模板语法,分析出每个插入点所处的上下文,据此自动选用不同的转义规则。这样就减轻了业务的工作负担,也减少了人为带来的疏漏。 由于beego框架使用了template,所以beego可以有效防止XSS。

2.2 命令注入 go语言提供多个进行系统命令调用的API,另外,还有第三方封装的方法进行系统命令调用,具体的API如下:

os.StartProcess对syscall.StartProcess进行封装; os.exec.Command和os.exec.CommandContext 又是对 os.StartProcess进行包装使得它更容易映射到stdin和stdout, 并且利用pipe连接i/o。StartProcess 是一个低级别的接口。 os/exec 包提供了高级别的接口,一般常用 os/exec 包。对os.exec.Command和os.exec.CommandContext下面分别举例:

2.3 sql注入 go语言执行拼接的sql语句,语句参数可控,则存在sql注入。包database/sql提供了SQL操作的函数:

在源代码中以这些函数名称做关键字进行搜索,然后对这些函数执行的SQL已经进行排查。框架一般会对这些函数进行封装。例如:对于Beego框架,提供了github.com/astaxie/beego/orm包,提供了Raw函数,直接进行sql语句操作。所以对于Beego框架的web,直接搜索(.Raw),查看sql语句是否拼接,如果是拼接且参数可控,则存在sql注入 1.采用拼接的SQL语句,存在sql注入. 2.预编译SQL语句如下,不存在SQL注入 3.除了其中beego orm是beego自带的orm框架库外,go语言还有下面的数据库框架列表(Stars和Forks属于变动数据,供参考)

2.4 xml注入 使用未经校验数据来构造XML会导致XML注入漏洞。如果用户被允许输入结构化的XML片段,则他可以在XML的数据域中注入XML标签来改写目标XML文档的结构和内容,XML解析器会对注入的标签进行识别和解释,引起注入问题。encoding/xml提供XML编解码能力,编码函数有

白盒测试查看代码import中是否有encoding/xml包,如果有,再进一步判断是否字符串拼接XML语句,存在拼接XML语句,则可能存在注入。go语言的encoding/xml包实现了一个简单的XML 1.0 解析器,该解析器不会处理DTD声明实体,不存在XXE漏洞。 下面是不存在XXE漏洞的调试 在XML中定义一个DTD,不引用时Unmarshal不会报错;引用时,Unmarshal会报错. 虽然encoding/xml包不存在XXE注入,但是通用的XML注入还是存在的,下面是个例子: 前台正常提交:

66 Jason 123456

前台故意如下提交: ID:66 名字:Jason 密码:87654321250Stella Chan250250

后台调试userdata字段是xml拼接处理 66 Jason 87654321250Stella Chan250250

一条输入,产生了两个User,并且XML数据结构能正常使用。从而产生xml注入。

2.5 代码注入 go语言是编译语言,运行时自身不存在代码注入,在编译时存在任意代码执行漏洞 CVE-2018-6574(此漏洞下面有详细介绍)。另外,go语言的内嵌脚本语言有很多,内嵌脚本内容受攻击者控制,可能存在内嵌脚本的代码注入。 go语言使用lua内嵌脚本的例子:

    func f5()  {
        L:=lua.NewState()
        defer L.close()
        // 下面会打开 win7-计算器
        if err := L.DoString(`print("hello lua")`);err!=nil {
            panic(err)
        }
        if err := L.DoString(`os.execute("calc")`);err!=nil {
            panic(err)
        }
    }

2.10.2 内存泄露 内存泄露指的是程序运行过程中已不再使用的内存,没有被释放掉,导致这些内存无法被使用,直到程序结束这些内存才被释放的问题。 go语言大部分内存泄露的问题都是来自于协程的使用不当。包括协程内部channel无限阻塞、http请求没有设置超时等等众多协程无法退出的问题。channel阻塞的场景主要包括以下四种: 1) 无缓冲channel进行写操作但是没有协程读 2) 有缓冲channel满了的时候进行写操作 3) 读channel时没有协程写 4) select语句所有case的操作阻塞 在查找这类问题时,可以关注协程创建之后是如何退出的,是否可能存在阻塞的可能,是否可通过外部不断触发创建无法退出的协程。

另外可能造成的内存泄露的原因有很多,但是并不常见,举一个简单的例子如下:

func main() {
 
	go func() {
		MB := 1024 * 1024
		tick := time.Tick(time.Millisecond * 2)
		var buf []byte
		for range tick {
			buf = append(buf, make([]byte, 1*MB)...) // 撑爆
		}
	}()
     
	var ch1 chan int
	<-ch1
}

上面这段程序使用time.Tick创建了一个定时器,每秒执行一次append,并make很大的内存,这样就导致很快服务器的内存就被占满了。 time.Tick、time.NewTicker是定时器,每隔一段时间向返回的Ticker的C通道写入一个元素:

2.10.3 死锁 死锁不是go语言特有的问题,但是由于go语言经常是高并发,经常存在资源阻塞等待的场景,因此就很容易产生死锁的问题。 死锁是指所有并发协程都在彼此等待的状态。对于go语言来说,如果一个程序的所有协程都挂起相互等待,会有自动检测死锁的机制,使程序崩溃退出,而且这种崩溃无法recover,比如: package main

import "fmt"

func deallockTest() { defer func() { if err := recover(); err != nil { fmt.Println(err) } }() // channel ch1 := make(chan int) ch2 := make(chan int) go func() { for { select { case <-ch1: ch2 <- 777 } } }() // 主-master for { select { case <-ch2: ch1 <- 666 } } }

func main() { deallockTest() for i := 1; i < 10; i++ { fmt.Println(i) } }

在deadlocktest函数中定义了两个channel,两个channel相互等待产生死锁,最终被go语言检测出来崩溃退出,且无法被recover。而如果是http服务单次请求造成的死锁,由于不是所有协程都挂起,单次请求就会无限阻塞,最终即可产生内存泄露的问题。 go语言产生的死锁通常是channel死锁或者互斥锁、读写锁的死锁。上面已经写了一个channel死锁的示例,两个channel相互等待,这里再列举两个简单的示例:

1) 同一个goroutine中,使用同一个channel读写: func main() { ch:=make(chan int) ch<-6 // 这里会发生阻塞 <-ch }

2) 2个以上goroutine中,使用同一个channel通信时,逻辑上读写的操作先于goroutine的创建: func main() { ch:=make(chan int) ch<-6666 // 这里会发生阻塞 go func(){ <-ch // 这里虽然创建的子进程,但是上面阻塞了,运行不到下面来。 }() } 上面这种死锁在web服务中就容易造成内存泄露的问题,因此要关注channel的读写操作是否存在这种死锁的问题。

除了channel死锁还有互斥锁、读写锁的死锁,互斥锁是 sync.Mutex,读写锁是 sync.RWMutex。 互斥锁的使用方法是在goroutine中使用Mutex.Lock()加锁,那么下一次使用同一个Mutex执行Lock()函数时就会阻塞,等待该Mutex执行了Unlock解锁才会继续执行。 读写锁添加了专门的读锁函数RWMutex.RLock()、RWMutex.RUnlock()和写锁函数RWMutex.Lock()、RWMutex.Unlock()。 实际上如果把读锁函数或写锁函数单独使用和Mutex并没有任何不同,只是如果进行混合使用,在调用了写加锁Lock时,会导致读加锁RLock函数的阻塞,直到写解锁Unlock后才会继续执行。 这样就产生了一种新的死锁模式,比如:

package main

import ( "fmt" "math/rand" "runtime" "sync" "time" )

var rwMutex sync.RWMutex

// 读 go func readGO(idx int, in <-chan int) { for { time.Sleep(time.Millisecond) rwMutex.RLock() // 读模式加锁 num := <-in fmt.Printf("%dth 读 go 程: 读到%d\n", idx, num) rwMutex.RUnlock() // 读模式解锁 } }

// 写 go func writeGO(idx int, out chan<- int) { for { num := rand.Intn(500) rwMutex.Lock() // 写模式,加锁 out <- num fmt.Printf(" ------- %dth 写 go 程: 写入 %d\n", idx, num) rwMutex.Unlock() // 写模式,解锁 } }

func main() { rand.Seed(time.Now().UnixNano()) ch := make(chan int, 5) go readGO(1, ch) go writeGO(1, ch) // 防止,主 go 退出 for { runtime.GC() } }

上面代码就产生了死锁,死锁原因在于写函数在不断地写入并将channel写满,又执行了Lock准备写入; 而读函数的RLock函数被写函数的Lock函数阻塞掉无法向下执行进行读取,这样写函数的channel写入操作也会阻塞,这样两个协程就都阻塞了,产生了死锁。

注意, channel 和 锁 不同时使用; 注意, channel 和 锁 不同时使用; 注意, channel 和 锁 不同时使用;

检测死锁可以使用科夫曼条件逐一进行考虑,科夫曼条件是产生死锁的必要条件,打破了一个就不会产生死锁,如果都满足则可能产生死锁: 1) 相互排斥:并发协程在任何时候都希望拥有共享资源的独占权 2) 等待条件:并发协程持有一组不完整的资源并在等待额外的资源时保留它们 3) 没有抢占:一旦协程持有资源,其他协程就不能再获取该资源,并且该协程不会在使用完毕之前主动放弃资源 4) 循环等待:资源分配图中存在一个循环。存在一组协程{P1, P2, …},使得P1正在等待P2使用的资源、P2正在等待P3使用的资源、…、Pn正在等待P1使用的资源。

2.10.4 http慢攻击 go语言的http服务也存在http慢攻击的问题,尤其是原生的http.ListenAndServe函数,直接使用该函数不具备为Handler设置超时机制的能力。如果不特意设置超时,则http服务默认是无限等待客户端的。因此最好使用http.Server{}的方式创建服务器,从而可以有针对性地设置超时,比如:

srv := &http.Server{ ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, Addr: ":8083" } srv.ListenAndServe()

Server中与超时有关的参数包括:ReadTimeout、WriteTimeout、ReadHeaderTimeout、IdleTimeout,推荐一篇文章,介绍go语言超时机制非常详细: https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/ beego框架中,也提供了ServerTimeOut参数,可以设置超时时间: func main() { beego.BConfig.Listen.ServerTimeOut = 5 beego.Run() }

2.10.5 其他DOS攻击模式 go语言也可能会存在包括死循环、zipbomb等DOS攻击模式,与其他语言没有太大的不同,请参考DoS攻击模式库。不过go语言不存在ReDOS问题,原因是普通的正则是采用递归回溯的匹配方式,而go语言使用了RE2,采用的是Thompson NFA构造法,可以保证在线性时间内匹配完成,不会回溯。

2.11 模板注入 go语言自带有两个模板包,text/template和html/template,text/template是基础包,html/template是在其上实现的专为html提供的模板。 先介绍一下text/template,该模板可以执行的函数非常有限,默认只提供了如下函数:

2.12 整数溢出 整数溢出是C语言常见的一种漏洞,也是go语言存在的一种漏洞。原理即是在整数运算的过程中,得到的结果超出了整型的范围,包括向上越界和向下越界,从而导致溢出。整数溢出极有可能造成程序逻辑错误,产生不可预知的安全的问题。

无符号整数溢出就会产生数值翻转问题,而有符号整数溢出则意味着符号的翻转,比如: 除了整数溢出,整型转换时也存在一定的安全风险,可分为两种,第一种是较大整型转换为较小整型,并且该数的原值超出较小整型的表示范围,就会发生数据丢失,比如: a:=65023 fmt.Println(uint8(a))

第二种是无符号整型和有符号整型数互转,无符号整型的较大的数值会转换成负值,比如: var a uint8 = 253 fmt.Println(uint8(a))

【golang】进阶篇 -- 无锁读写安全HashMap http://3ms.huawei.com/km/blogs/details/5204979

Golang并发:再也不愁选channel还是选锁。 Golang并发:再也不愁选channel还是选锁。 Golang并发:再也不愁选channel还是选锁。

go-json-rest库,是一款非常精炼的restful json api 引擎架构。 可以非常方便的构建出json api接口。 开源地址: https://github.com/ant0ine/go-json-rest golang本身不支持模板(template),所以在以往需要使用模板的场景下,通常使用reflect。反射非常好用,在工程代码中,不用考虑协程安全,不用考虑是否初始化(可以对实例,也可以对struct结构对象)进行反射。使用多了会让人上瘾。因此,这个“破坏性”编程方式,一直被架构、技术大神鄙视。

反射普遍带来的问题: Clear is better than clever. Reflection is never clear. A large amount of reflection will result in a loss of performance.

golang标准库binary学习7 https://studygolang.com/articles/13476

⚠️ **GitHub.com Fallback** ⚠️