如何解析并白嫖xray yml V2 poc
從去年開始 xray的yml poc升級到了v2版本和v1版本相比,執行流程上有了較大變化,以較為簡單的thinkphp5的poc來看
v1版本
name: poc-yaml-thinkphp5-controller-rce
rules:
- method: GET
path: /index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=printf&vars[1][]=a29hbHIgaXMg%25%25d2F0Y2hpbmcgeW91
expression: |
response.body.bcontains(b"a29hbHIgaXMg%d2F0Y2hpbmcgeW9129")
detail:
links:
- https://github.com/vulhub/vulhub/tree/master/thinkphp/5-rce
v2版本
name: poc-yaml-thinkphp5-controller-rce
manual: true
transport: http
rules:
r0:
request:
cache: true
method: GET
path: /index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=printf&vars[1][]=a29hbHIgaXMg%25%25d2F0Y2hpbmcgeW91
expression: response.body.bcontains(b"a29hbHIgaXMg%d2F0Y2hpbmcgeW9129")
expression: r0()
detail:
links:
- https://github.com/vulhub/vulhub/tree/master/thinkphp/5-rce
最主要的區別是是新增了transport、expression兩個字段。
transport的取值范圍為tcp、udp、http,給xray賦予了探測tcp協議的漏洞。
expression字段改變了v1 poc的執行流程,可利用短路的邏輯來設計執行的流程。
為了徹底搞明白cel執行yml poc的流程,今天就寫一個最簡單的yml 執行引擎demo,來學習執行的整體流程以及思路。
xray是使用cel-go來做執行引擎的,所以需要cel-go和golang的基礎
關于cel語法的demo,可以查看
https://github.com/google/cel-go/blob/master/examples/README.md
https://codelabs.developers.google.com/codelabs/cel-go#0
1.反序列化yml文件
執行yml文件第一步是要把yml反序列化到golang的結構體,根據poc文件可以提取出如下結構體
package main
import (
"gopkg.in/yaml.v2"
"io/ioutil"
)
type Poc struct {
Name string `yaml:"name"`
Transport string `yaml:"transport"`
Set map[string]string `yaml:"set"`
Rules map[string]Rule `yaml:"rules"`
Expression string `yaml:"expression"`
Detail Detail `yaml:"detail"`
}
type Rule struct {
Request RuleRequest `yaml:"request"`
Expression string `yaml:"expression"`
}
type RuleRequest struct {
Cache bool `yaml:"cache"`
method string `yaml:"method"`
path string `yaml:"path"`
Expression string `yaml:"expression"`
}
type Detail struct {
Links []string `yaml:"links"`
}
func main() {
poc := Poc{}
pocFile, _ := ioutil.ReadFile("poc.yml")
err := yaml.Unmarshal(pocFile,&poc)
if err != nil{
println(err.Error())
}
println(pocFile)
}
符合預期

2.處理set 全局變量
盡管這個poc中沒有使用到set這個結構,但是其他poc中大量使用set結構來保存全局變量
如

所以需要一個定義一個map來保存變量,而變量的值就是來源于cel-go執行語句,并獲取out,可以定義如下函數
func execSetExpression(Expression string) (interface{}, error) {
//定義set 內部函數接口
setFuncsInterface := cel.Declarations(
decls.NewFunction("randomInt",
decls.NewOverload("randomInt_int_int",
[]*exprpb.Type{decls.Int, decls.Int},
decls.String)),
decls.NewFunction("randomLowercase",
decls.NewOverload("randomLowercase_string",
[]*exprpb.Type{decls.Int},
decls.String)),
)
//實現set 內部函數接口
setFuncsImpl := cel.Functions(
&functions.Overload{
Operator: "randomInt_int_int",
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
randSource := rand.New(rand.NewSource(time.Now().UnixNano()))
min := int(lhs.Value().(int64))
max := int(rhs.Value().(int64))
return types.String(strconv.Itoa(min + randSource.Intn(max-min)))
}},
&functions.Overload{
Operator: "randomLowercase_string",
Unary: func(lhs ref.Val) ref.Val {
n := lhs.Value().(int64)
letterBytes := "abcdefghijklmnopqrstuvwxyz"
randSource := rand.New(rand.NewSource(time.Now().UnixNano()))
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
randBytes := make([]byte, n)
for i, cache, remain := n-1, randSource.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = randSource.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
randBytes[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return types.String(randBytes)
}},
)
//創建set 執行環境
env, err := cel.NewEnv(setFuncsInterface)
if err != nil {
log.Fatalf("environment creation error: %v", err)
}
ast, iss := env.Compile(Expression)
if iss.Err() != nil {
log.Fatalln(iss.Err())
return nil, iss.Err()
}
prg, err := env.Program(ast, setFuncsImpl)
if err != nil {
return nil, errors.New(fmt.Sprintf("Program creation error: %v", err))
}
out, _, err := prg.Eval(map[string]interface{}{})
if err != nil {
log.Fatalf("Evaluation error: %v", err)
return nil, errors.New(fmt.Sprintf("Evaluation error: %v", err))
}
return out, nil
}
進行測試,符合預期


3.生成request和response

部分request中會{{rand}}這種格式來使用上一步中生成的全局變量,
可以定義如下渲染函數
// 渲染函數 渲染變量到request中
func render(v string, setMap map[string]interface{}) string {
for k1, v1 := range setMap {
_, isMap := v1.(map[string]string)
if isMap {
continue
}
v1Value := fmt.Sprintf("%v", v1)
t := "{{" + k1 + "}}"
if !strings.Contains(v, t) {
continue
}
v = strings.ReplaceAll(v, t, v1Value)
}
return v
}
再看expression字段中response.body.bcontains(b"a29hbHIgaXMg%d2F0Y2hpbmcgeW9129")
有一個response結構體,抽象成golang代碼,大概如下
type Response struct {
Body []byte
}
但是在cel中是不能直接使用golang的struct的,需要用proto來做一個轉換
定義如下proto文件
syntax = "proto3";
option go_package = "./;structs";
package structs;
message Response {
//數據類型 字段名稱 字段id
bytes body = 1;
}
通過protoc -I . --go_out=. requests.proto生成go文件

然后定義如下函數來執行單條rule的表達式,返回值為如bool,來判斷單條rule是否成立
func execRuleExpression(Expression string, variableMap map[string]interface{}) bool {
env, _ := cel.NewEnv(
cel.Container("structs"),
cel.Types(&structs.Response{}),
cel.Declarations(
decls.NewVar("response", decls.NewObjectType("structs.Response")),
decls.NewFunction("bcontains",
decls.NewInstanceOverload("bytes_bcontains_bytes",
[]*exprpb.Type{decls.Bytes, decls.Bytes},
decls.Bool)),
),
)
funcImpl := []cel.ProgramOption{
cel.Functions(
&functions.Overload{
Operator: "bytes_bcontains_bytes",
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
v1, ok := lhs.(types.Bytes)
if !ok {
return types.ValOrErr(lhs, "unexpected type '%v' passed to bcontains", lhs.Type())
}
v2, ok := rhs.(types.Bytes)
if !ok {
return types.ValOrErr(rhs, "unexpected type '%v' passed to bcontains", rhs.Type())
}
return types.Bool(bytes.Contains(v1, v2))
},
},
)}
ast, iss := env.Compile(Expression)
if iss.Err() != nil {
log.Fatalln(iss.Err())
}
prg, err := env.Program(ast, funcImpl...)
if err != nil {
log.Fatalf("Program creation error: %v", err)
}
out, _, err := prg.Eval(variableMap)
if err != nil {
log.Fatalf("Evaluation error: %v", err)
}
return out.Value().(bool)
}
然后根據request流程,可以抽象為如下匿名函數,方便最后執行poc中的Expression
var RequestsInvoke = func(target string, setMap map[string]interface{}, rule Rule) bool {
var req *http.Request
var err error
if rule.Request.Body == "" {
req, err = http.NewRequest(rule.Request.Method, target+render(rule.Request.Path, setMap), nil)
} else {
req, err = http.NewRequest(rule.Request.Method, target+render(rule.Request.Path, setMap), bytes.NewBufferString(render(rule.Request.Body, setMap)))
}
if err != nil {
log.Println(fmt.Sprintf("http request error: %s", err.Error()))
return false
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
println(err.Error())
return false
}
response := &structs.Response{}
response.Body, _ = ioutil.ReadAll(resp.Body)
return execRuleExpression(rule.Expression, map[string]interface{}{"response": response})
}
4.執行poc Expression
將前面生成的request匿名函數,按照rules中的key定義成函數。注入到cel執行環境中,即可實現短路的邏輯,避免無效請求。
func execPocExpression(target string, setMap map[string]interface{}, Expression string, rules map[string]Rule) bool { var funcsInterface []*exprpb.Decl var funcsImpl []*functions.Overload for key, rule := range rules { funcName := key funcRule := rule funcsInterface = append(funcsInterface, decls.NewFunction(key, decls.NewOverload(key, []*exprpb.Type{}, decls.Bool))) funcsImpl = append(funcsImpl, &functions.Overload{ Operator: funcName, Function: func(values ...ref.Val) ref.Val { return types.Bool(RequestsInvoke(target, setMap, funcRule)) }, }) } env, err := cel.NewEnv(cel.Declarations(funcsInterface...)) if err != nil { log.Fatalf("environment creation error: %v", err) } ast, iss := env.Compile(Expression) if iss.Err() != nil { log.Fatalln(iss.Err()) } prg, err := env.Program(ast, cel.Functions(funcsImpl...)) if err != nil { log.Fatalln(fmt.Sprintf("Program creation error: %v", err)) } out, _, err := prg.Eval(map[string]interface{}{}) return out.Value().(bool)}
代碼僅為學習使用,為了精簡代碼體量,絕大多數函數和結構體未實現。
5.測試

項目代碼全部開源在:
https://github.com/lanyi1998/yml-poc-demo
參考項目:
https://github.com/google/cel-go
https://github.com/jjf012/gopoc
https://github.com/WAY29/pocV
https://docs.xray.cool/#/guide/poc/v2