package main
import (
"fmt"
"github.com/PaulXu-cn/goeval"
)
func main() {
if re, err := goeval.Eval("", "fmt.Print("Hello World!")", "fmt"); nil == err {
fmt.Println(string(re))
} else {
fmt.Println(err.Error())
}
}
package main
import (
eval "github.com/PaulXu-cn/goeval"
"github.com/gin-gonic/gin"
"regexp"
)
func main() {
r := gin.Default()
r.LoadHTMLFiles("html/index.html", "html/result.html")
r.GET("/", func(c *gin.Context) {
c.Header("server", "Gin")
c.HTML(200, "index.html", "")
})
r.POST("/parse", func(c *gin.Context) {
expression := c.DefaultPostForm("expression", "6")
Package := c.DefaultPostForm("Package", "fmt")
match, _ := regexp.MatchString("([a-zA-Z]+)", expression)
if match {
c.String(200, "Hacker????")
return
} else {
if res, err := eval.Eval("", "fmt.Print("+expression+")", Package); nil == err {
c.HTML(200, "result.html", gin.H{"result": string(res)})
} else {
c.HTML(200, "result.html", err.Error())
}
}
})
r.Run()
}
package goeval
import (
"fmt"
"go/format"
"math/rand"
"os"
"os/exec"
"strings"
"time"
)
const (
letterBytes = "abcdefghijklmnopqrstuvwxyz"
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
var (
dirSeparator = "/"
tempDir = os.TempDir()
src = rand.NewSource(time.Now().UnixNano())
)
// 参考: https://colobu.com/2018/09/02/generate-random-string-in-Go/
func RandString(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
func Eval(defineCode string, code string, imports ...string) (re []byte, err error) {
var (
tmp = `package main
%s
%s
func main() {
%s
}
`
importStr string
fullCode string
newTmpDir = tempDir + dirSeparator + RandString(8)
)
if 0 < len(imports) {
importStr = "import ("
for _, item := range imports {
if blankInd := strings.Index(item, " "); -1 < blankInd {
importStr += fmt.Sprintf("n %s "%s"", item[:blankInd], item[blankInd+1:])
} else {
importStr += fmt.Sprintf("n"%s"", item)
}
}
importStr += "n)"
}
fullCode = fmt.Sprintf(tmp, importStr, defineCode, code)
var codeBytes = []byte(fullCode)
// 格式化输出的代码
if formatCode, err := format.Source(codeBytes); nil == err {
// 格式化失败,就还是用 content 吧
codeBytes = formatCode
}
// fmt.Println(string(codeBytes))
// 创建目录
if err = os.Mkdir(newTmpDir, os.ModePerm); nil != err {
return
}
defer os.RemoveAll(newTmpDir)
// 创建文件
tmpFile, err := os.Create(newTmpDir + dirSeparator + "main.go")
if err != nil {
return re, err
}
defer os.Remove(tmpFile.Name())
// 代码写入文件
tmpFile.Write(codeBytes)
tmpFile.Close()
// 运行代码
cmd := exec.Command("go", "run", tmpFile.Name())
res, err := cmd.CombinedOutput()
return res, err
}
func Eval(defineCode string, code string, imports ...string) (re []byte, err error) {
var (
tmp = `package main
%s
%s
func main() {
%s
}
`
importStr string
fullCode string
newTmpDir = tempDir + dirSeparator + RandString(8)
)
if 0 < len(imports) {
importStr = "import ("
for _, item := range imports {
if blankInd := strings.Index(item, " "); -1 < blankInd {
importStr += fmt.Sprintf("n %s "%s"", item[:blankInd], item[blankInd+1:])
} else {
importStr += fmt.Sprintf("n"%s"", item)
}
}
importStr += "n)"
}
fullCode = fmt.Sprintf(tmp, importStr, defineCode, code)
import()
中间的,我们需要逃逸单引号和括弧。需要注意的是要逃逸imoort中最后一个括弧是大家都知道是使用(将其补全,但是在func外面我们是无法使用函数进行补全的,这时候我们需要定义一个常量,使用coust
进行闭合,这样就不会产生语法错误了。我们写个脚本进行简单的逃逸测试:package main
import (
"fmt"
"strings"
"os/exec"
)
func Eval(imports ...string) (re []byte, err error) {
var importStr string
if 0 < len(imports) {
importStr = "import ("
for _, item := range imports {
if blankInd := strings.Index(item, " "); -1 < blankInd {
importStr += fmt.Sprintf("n %s "%s"", item[:blankInd], item[blankInd+1:])
} else {
importStr += fmt.Sprintf("n"%s"", item)
}
}
importStr += "n)"
}
fmt.Println(importStr)
cmd := exec.Command("ls")
res, err := cmd.CombinedOutput()
return res, err
}
func main() {
Eval("os/exec"n"fmt")nfunctinit(){ncmdt:=exec.Command("ls")nres,errt:=tcmd.CombinedOutput()nfmt.Println(string(res))nfmt.Println(err)n}nconst(nMessage="fmt")
}
原文始发于微信公众号(山石网科安全技术研究院):GO语言安全 — 沙箱逃逸题目分析