寒假主要是先出完了之前心心念念的typescript 題目,之后主要是抽點(diǎn)時(shí)間學(xué)golang.同學(xué)推薦的倉庫講的還是很不錯(cuò)的
https://github.com/unknwon/the-way-to-go_ZH_CN
從簡(jiǎn)單的開發(fā)與學(xué)習(xí)過程中可以感覺到golang的確非常安全,很多問題由于作為強(qiáng)類型的靜態(tài)語言,從編譯過程就能防止不安全的操作。因此大部分漏洞產(chǎn)生還是歸結(jié)于依賴庫(通常已經(jīng)發(fā)展為CVE或者issue)以及開發(fā)者自身的操作漏洞。這里就作為小結(jié),簡(jiǎn)單談一談
interger overflow/truncation
golang 存在非常經(jīng)典的整數(shù)反轉(zhuǎn)/溢出問題
對(duì)無符號(hào)數(shù)的反轉(zhuǎn)
func main() {
var a uint32 = 2147483648
var b uint32 = 2147483648
var sum = a + b
fmt.Println(reflect.TypeOf(sum))
fmt.Printf("Sum is : %d",sum)
}
//uint32
//Sum is : 0
象要直接聲明一個(gè)大小已經(jīng)溢出的數(shù)自然不會(huì)通過編譯,因此出現(xiàn)反轉(zhuǎn)的話,主要是在變量的相加這樣的計(jì)算才會(huì)會(huì)導(dǎo)致標(biāo)志CF位反轉(zhuǎn)
有符號(hào)數(shù)的溢出
var a int8 = 127
var b int8 = 1
var sum int8 = a + b
fmt.Println(reflect.TypeOf(sum))
fmt.Printf("Sum is : %d",sum)
//int8
//-128
具體的值可以看到math包中的常量定義。

在類型轉(zhuǎn)換中,也會(huì)出現(xiàn)較大整型向較小整型轉(zhuǎn)換的截?cái)鄦栴}
var a int16 = 256
var b = int8(a)
fmt.Println(b)
// 0
// high-order byte is truncated
我們可以看到整型安全的檢查也出現(xiàn)在了gosec中,一個(gè)比較經(jīng)典的例子就是:kubectl命令行中出現(xiàn)了一個(gè)strconv.Atoi導(dǎo)致的截?cái)鄦栴}。當(dāng)我們傳入port參數(shù)的對(duì)應(yīng)字符串后,容器啟動(dòng)的端口這一參數(shù)會(huì)將經(jīng)Atoi處理后的字符串進(jìn)行int32的類型轉(zhuǎn)換。由于64位系統(tǒng)的int是int64類型。轉(zhuǎn)int32的話會(huì)出現(xiàn)明顯截?cái)?br>
可以簡(jiǎn)化成以下代碼:
v , _ := strconv.Atoi("4294967377")
s := int32(v)
fmt.Println(s)
// 81
這樣就有可能導(dǎo)致81端口的服務(wù)啟動(dòng),或者被停止。所以使用ParseInt ,ParseUInt會(huì)比較好。或者對(duì)端口號(hào)進(jìn)行限制。
CTF中出現(xiàn)整數(shù)溢出的scenario 一般都是 xx shop之類的。通過重復(fù)的加數(shù)進(jìn)行溢出然后鑒權(quán)。
pseudo-rand
golang 的math/rand 包中,我們可以看到隨機(jī)數(shù)生成的函數(shù)形式

跟進(jìn)一下函數(shù)與結(jié)構(gòu)體
var globalRand = New(&lockedSource{src: NewSource(1).(*rngSource)})
......
func NewSource(seed int64) Source {
var rng rngSource
rng.Seed(seed)
return &rng
}
可以看到,這些隨機(jī)數(shù)函數(shù)的seed默認(rèn)為1.也就是說如果不使用rand.Seed()確認(rèn)種子的話,生成的只是所謂的偽隨機(jī)數(shù)。
這種問題可能會(huì)出現(xiàn)在開發(fā)者直接用rand生成的字符串作為密鑰的場(chǎng)景,比如gin的cookie的key.從而導(dǎo)致本地復(fù)現(xiàn),直接鑒權(quán)。
go net/http
net/http < 1.11 CRLF
低版本的http庫會(huì)導(dǎo)致CRLF.比如http.NewRequest()。貌似是原本沒有問題,但是在一次升級(jí)中疏忽了導(dǎo)致重新出現(xiàn)

現(xiàn)在會(huì)看到存在限制,我們無法傳入\r\n的字符
WMCTF中的gogogo出現(xiàn)過這一漏洞,利用自然是通過CRLF發(fā)送POST請(qǐng)求進(jìn)行繞過與上傳。
weird stuff
在之前的justCTF中,出現(xiàn)了一道go題。題目原本的漏洞是由出題人發(fā)現(xiàn)的一個(gè)issuehttps://github.com/golang/go/issues/40940 加上其對(duì)fileServer一些代碼的魔改組合的。
簡(jiǎn)單陳述下的話,題目提供了一個(gè)go http起的FileServer
http.Handle("/", http.FileServer(http.Dir("/tmp")))
flag就在其提供文件服務(wù)的文件夾下,但是,出題人加上了web服務(wù)的flag路由,從而使得我們沒法通過直接訪問/flag來獲取文件。而是得到/flag路由的回顯。
出題人的意圖是利用其挖掘到的這個(gè)漏洞,造成錯(cuò)誤的文件讀取范圍。通過訪問其他文件越界讀到flag.(http的Fileserver在我們?cè)L問時(shí),會(huì)先根據(jù)我們?cè)L問的url進(jìn)行一系列處理,杜絕路徑穿越的url,之后進(jìn)行文件讀取返回給用戶)
但是比較有意思的時(shí),比賽中出現(xiàn)了一個(gè)非預(yù)期讀flag的方式
curl --path-as-is -X CONNECT http://gofs.web.jctf.pro/../flag
簡(jiǎn)單說就是用CONNECT請(qǐng)求+路徑穿越的url讀取到了文件。我們看看源碼是怎么處理的

如果是CONNECT方式請(qǐng)求,就不會(huì)處理url中的特殊字符。導(dǎo)致直接讀取flag.其他的請(qǐng)求方法都會(huì)在
cleanPath中被處理url
算是一個(gè)很有意思的問題。不過golang1.16似乎已經(jīng)處理了。但是包括我在內(nèi)的大部分應(yīng)該還是使用的go1.15及以下版本,所以還是值得注意的
feature of slice
之前在做buu上的roarCTF時(shí)遇到一道go題,其中一個(gè)利用點(diǎn)來自Confidence CTF,其中用到了golang slice的一個(gè)feature
a := make([]uint64, 0)
a = append(a, 1)
a = append(a, 2)
a = append(a, 3)
b := append(a, 4)
c := append(a, 5)
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
//result:
//[1 2 3]
//[1 2 3 5]
//[1 2 3 5]
按照直覺來說,b這里應(yīng)該是[1,2,3,4],但實(shí)際上卻是[1,2,3,5]
這就與golang的slice也就是切片的結(jié)構(gòu)相關(guān)了
type slice struct {
array unsafe.Pointer // ptr
len int
cap int
}

slice結(jié)構(gòu)中的cap是按2的倍數(shù)擴(kuò)容的。所以說當(dāng)我們append(3)時(shí)會(huì)發(fā)生第一次擴(kuò)容,此時(shí)len為3,cap為2*2=4.
執(zhí)行b := append(a, 4)時(shí),我們的4會(huì)被放在指針ptr的第四個(gè)位置。然后返回ptr len=4 cap=4給b。不過這并沒有改變a的結(jié)構(gòu)(slice只是指向內(nèi)存的指針)之后進(jìn)行c := append(a, 5)時(shí),由于a沒變,新元素只會(huì)覆蓋之前b那放上的4.
那一道題目就是利用這點(diǎn),可以通過先beg三次構(gòu)造出len=3,cap=4的切片,通過題目中一個(gè)append超大隨機(jī)數(shù)的機(jī)會(huì),在下一次beg時(shí)達(dá)成元素覆蓋。
這個(gè)可以算是slice的feature,還是很有意思的。
reDOS
原本是以為golang不存在reDOS的問題的。當(dāng)初閱讀lmt-swallow的文章時(shí)也看到他提到golang有l(wèi)inear time matching。 不過后來發(fā)現(xiàn)還是有途徑的。
當(dāng)然,reDOS主要還是在js里影響比較大,因?yàn)槠涫录?duì)列阻塞對(duì)于web服務(wù)的影響是巨大的。相比之下其他的DOS影響可能就較小。用nginx做個(gè)處理基本就沒有影響。不過,如果是以Blind regex injection為目的考慮reDOS的話,就另當(dāng)別論了。
回到golang上。golang使用的正則引擎是re2。但是用的是無回溯機(jī)制(DFA)。
所以之前的payload如^(?=(PAYLOAD))((.*)*)*salt$就無效了
但是,通過其他方法制造延時(shí)仍然是可行的。比如re2對(duì)于{n}這樣的正則,n存在有上限1000,且對(duì)于DFA狀態(tài)數(shù)沒有限制.我們可以通過不斷重復(fù)(.?){1000}來制造足夠的DFA狀態(tài)進(jìn)行一定程度上的延時(shí)
reDOS其實(shí)一直是一個(gè)很有意思的點(diǎn),除了喜聞樂見的nodejs,前端中xss也可以導(dǎo)致這個(gè)問題,從而xsleak ,ruby中也有類似問題。當(dāng)然,如果是從reDOS引申到正則盲注的話,sql注入中倒也有類似的情況。總之就是利用差異性進(jìn)行數(shù)據(jù)提取。可惜因?yàn)樘菀讐沫h(huán)境了,所以很難出道題到比賽中 :(
SSTI
最后一個(gè)就簡(jiǎn)單提一下ssti.要說golang這種靜態(tài)編譯還會(huì)出ssti還是挺難的( 除非源碼中用sprintf 或者說拼接來構(gòu)建template的參數(shù)
var tmpl = fmt.Sprintf(`<h2>No search results for %s</h2>`, xxx)
golang的template跟很多模板引擎的語法差不多,比如{{}}指定可解析的對(duì)象。{{.xxx}}表示一個(gè)對(duì)象的xxx屬性。.就是當(dāng)前作用域的當(dāng)前對(duì)象,基本就跟handlebars里的this差不多。
假如我們傳入的參數(shù)是可解析的,就有可能泄露template在執(zhí)行過程中引入的對(duì)象內(nèi)容(執(zhí)行的本質(zhì)就是合并替換)
簡(jiǎn)單的demo可以參考這里https://blog.takemyhand.xyz/2020/05/ssti-breaking-gos-template-engine-to.html
如果傳入的struct屬性含有指針的話(非常常見,因?yàn)橛弥羔樋梢怨?jié)省很很多空間),我們ssti的回顯就只是一個(gè)地址。需要我們手動(dòng)去訪問屬性才會(huì)解引
Summary && Reference
簡(jiǎn)單的梳理下自己見過的golang安全問題(CTF-based web go)。其實(shí)像之前dasCTF中也有g(shù)olang的題目,用gob序列化存內(nèi)容到cookie,不過利用點(diǎn)與go的關(guān)系為0,所以不談。
自己golang的開發(fā)還有很多值得去學(xué)的,這門語言確實(shí)有其優(yōu)越之處以及一些可能存在并被挖掘的安全問題
https://github.com/tsg-ut/tsgctf2020
https://annevi.cn/2020/08/14/wmctf2020-gogogo-writeup/
https://github.com/golang/go/issues/30794
https://github.com/golang/go/issues/40940
https://github.com/mwarzynski/confidence2019_teaser_lottery