ehole简介

     ______    __         ______
    / ____/___/ /___ ____/_  __/__  ____ _____ ___
   / __/ / __  / __ `/ _ \/ / / _ \/ __ `/ __ `__ \
  / /___/ /_/ / /_/ /  __/ / /  __/ /_/ / / / / / /
 /_____/\__,_/\__, /\___/_/  \___/\__,_/_/ /_/ /_/
			 /____/ https://forum.ywhack.com  By:shihuang

EHole是一款对资产中重点系统指纹识别的工具,在红队作战中,信息收集是必不可少的环节,如何才能从大量的资产中提取有用的系统(如OA、VPN、Weblogic...)。EHole旨在帮助红队人员在信息收集期间能够快速从C段、大量杂乱的资产中精准定位到易被攻击的系统,从而实施进一步攻击。
抄自https://github.com/ShiHuang-ESec/EHole

ehole的fofaext功能粗略梳理

ehole3.0没找到源码,用的是上面git仓库里开源的2.0源码
image-1664524039892
ehole的fofa相关功能分fofaext导出模块和finger指纹识别模块两个部分,其中重要的api调用部分则是在finger之下的Fofaall_out语法查询函数实现,使用fofa_api函数组装api url,然后返回给fofahttp函数进行查询,Fafaips_out本地ip查询函数则是单独实现对本地文件中的ip的读取以及一些处理,然后交由Fofaall_out查询。fofaext使用finger中的相关函数来获取结果,然后储存到文件中。

开始改造

0zone api加装

直接用原来的fofa功能改造,fofa的api是用get方法来使用的,而0zone用的是post,所以要对fofa_api和fofahttp进行改造。先看关键部分请求的实现。
0zone api使用文档https://0.zone/applyParticulars?type=site
image-1664524455504

API请求部分

//请求api的json数据结构
type Zoneapi struct {
	Title       string `json:"title"`
	Title_type  string `json:"title_type"`
	Page        int    `json:"page"`
	Pagesize    int    `json:"pagesize"`
	Zone_key_id string `json:"zone_key_id"`
}

//接受data的结构体,0zone api返回来的data需要用一个结构体接,直接用字符串数组接不到
type Zoneapi_data struct {
	Ip          string `json:"ip"`
	Port        string `json:"port"`
	Url         string `json:"url"`
	Title       string `json:"title"`
	Cms         string `json:"cms"`
	Service     string `json:"service"`
	Banner      string `json:"banner"`
	Company     string `json:"company"`
	Html_banner string `json:"html_banner"`
}

//接受结果
type AutoGenerated_zone struct {
	Code    int            `json:"code"`
	Message string         `json:"message"`
	Page    int            `json:"page"`
	Size    int            `json:"pagesize"`
	Total   string         `json:"total"`
	Data    []Zoneapi_data `json:"data"`
}

//keyword是0zone的查询语句,这里的email其实是没用的,只是我懒得删,key是apitoken,这些参数由ext0zoneCmd方法传入
func zone_api(keyword string, email string, key string, page int, size int, timeout string) *AutoGenerated_zone {
	data := Zoneapi{
		Title:       keyword,
		Title_type:  "site",
		Page:        page,
		Pagesize:    size,
		Zone_key_id: key,
	}
	url := "https://0.zone/api/data/"
	var itime, err = strconv.Atoi(timeout)
	if err != nil {
		log.Println("zone超时参数错误: ", err)
	}
	transport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
	client := &http.Client{
		Timeout:   time.Duration(itime) * time.Second,
		Transport: transport,
	}
	payload, err := json.Marshal(&data)
	if err != nil {
		log.Fatal(err)
	}
	reader := bytes.NewReader(payload)
	req, err := http.NewRequest("POST", url, reader)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Accept", "*/*;q=0.8")
	req.Header.Set("Connection", "close")
	req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36")
	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	result, _ := ioutil.ReadAll(resp.Body)
	res := &AutoGenerated_zone{}
	json.Unmarshal(result, &res)
	return res
}

//把结构体里面的数据塞进字符串数组
func Zone2string(Data []Zoneapi_data) (result [][]string) {
	var tmp []string
	for i := 0; i < len(Data); i++ {
		tmp = append(tmp, Data[i].Ip)
		tmp = append(tmp, Data[i].Url)
		tmp = append(tmp, Data[i].Title)
		tmp = append(tmp, Data[i].Port)
		tmp = append(tmp, Data[i].Service)
		tmp = append(tmp, Data[i].Cms)
		tmp = append(tmp, Data[i].Company)
		result = append(result, tmp)
		tmp = nil
	}
	return result
}

func Zoneall_out(keyword string) (result [][]string) {
	zone := GetConfig()
	res := zone_api(keyword, zone.Email, zone.Zone_key, 1, 40, zone.Fofa_timeout)
	it, _ := strconv.Atoi(res.Total)
	rpage := math.Ceil(float64(it) / 40)
	for i := 1; i <= int(rpage); i++ {
		res = zone_api(keyword, zone.Email, zone.Zone_key, i, 40, zone.Fofa_timeout)
		tmp := Zone2string(res.Data)
		//res := zonehttp(url, zone.zone_timeout)
		if len(res.Data) > 0 {
			result = append(result, tmp...)
		} else {
			break
		}
	}
	return
}

储存结果

func Ext0zone(msg [][]string, filename string) {
	xlsx := excelize.NewFile()
	xlsx.SetCellValue("Sheet1", "A1", "ip")
	xlsx.SetCellValue("Sheet1", "B1", "url")
	xlsx.SetCellValue("Sheet1", "C1", "title")
	xlsx.SetCellValue("Sheet1", "D1", "port")
	xlsx.SetCellValue("Sheet1", "E1", "service")
	xlsx.SetCellValue("Sheet1", "F1", "cms")
	xlsx.SetCellValue("Sheet1", "G1", "company")
	for k, v := range msg {
		xlsx.SetCellValue("Sheet1", "A"+strconv.Itoa(k+2), v[0])
		xlsx.SetCellValue("Sheet1", "B"+strconv.Itoa(k+2), v[1])
		xlsx.SetCellValue("Sheet1", "C"+strconv.Itoa(k+2), v[2])
		xlsx.SetCellValue("Sheet1", "D"+strconv.Itoa(k+2), v[3])
		xlsx.SetCellValue("Sheet1", "E"+strconv.Itoa(k+2), v[4])
		xlsx.SetCellValue("Sheet1", "F"+strconv.Itoa(k+2), v[5])
		xlsx.SetCellValue("Sheet1", "G"+strconv.Itoa(k+2), v[6])
	}
	err := xlsx.SaveAs(filename)
	if err != nil {
		fmt.Println(err)
	}
}

整合到finger

先往finger上加参数

var fingerCmd = &cobra.Command{
	Use:   "finger",
	Short: "ehole的指纹识别模块",
	Long:  `从fofa或者本地文件获取资产进行指纹识别,支持单条url识别。`,
	Run: func(cmd *cobra.Command, args []string) {
		color.RGBStyleFromString("105,187,92").Println("\n     ______    __         ______                 \n" +
			"    / ____/___/ /___ ____/_  __/__  ____ _____ ___ \n" +
			"   / __/ / __  / __ `/ _ \\/ / / _ \\/ __ `/ __ `__ \\\n" +
			"  / /___/ /_/ / /_/ /  __/ / /  __/ /_/ / / / / / /\n" +
			" /_____/\\__,_/\\__, /\\___/_/  \\___/\\__,_/_/ /_/ /_/ \n" +
			"			 /____/ https://forum.ywhack.com  By:shihuang\n")
		if localfile != "" {
			urls := removeRepeatedElement(source.LocalFile(localfile))
			s := finger.NewScan(urls, thread, output, proxy)
			s.StartScan()
			os.Exit(1)
		}
		if fofaip != "" {
			urls := removeRepeatedElement(source.Fofaip(fofaip))
			s := finger.NewScan(urls, thread, output, proxy)
			s.StartScan()
			os.Exit(1)
		}
		if zoneip != "" {
			urls := removeRepeatedElement(source.Zoneip(zoneip))
			s := finger.NewScan(urls, thread, output, proxy)
			s.StartScan()
			os.Exit(1)
		}
		if fofasearche != "" {
			urls := removeRepeatedElement(source.Fafaall(fofasearche))
			s := finger.NewScan(urls, thread, output, proxy)
			s.StartScan()
			os.Exit(1)
		}
		if zonesearche != "" {
			urls := removeRepeatedElement(source.Zoneall(zonesearche))
			s := finger.NewScan(urls, thread, output, proxy)
			s.StartScan()
			os.Exit(1)
		}
		if urla != "" {
			s := finger.NewScan([]string{urla}, thread, output, proxy)
			s.StartScan()
			os.Exit(1)
		}
	},
}

var (
	fofaip      string
	zoneip      string
	fofasearche string
	zonesearche string
	localfile   string
	urla        string
	thread      int
	output      string
	proxy       string
)

func init() {
	rootCmd.AddCommand(fingerCmd)
	fingerCmd.Flags().StringVarP(&fofaip, "fip", "F", "", "从fofa提取资产,进行指纹识别,仅仅支持ip或者ip段,例如:192.168.1.1 | 192.168.1.0/24")
	fingerCmd.Flags().StringVarP(&zoneip, "zip", "Z", "", "从0zone提取资产,进行指纹识别,仅仅支持ip或者ip段,例如:192.168.1.1 | 192.168.1.0/24")
	fingerCmd.Flags().StringVarP(&fofasearche, "fofa", "s", "", "从fofa提取资产,进行指纹识别,支持fofa所有语法")
	fingerCmd.Flags().StringVarP(&zonesearche, "zone", "S", "", "从0zone提取资产,进行指纹识别,支持0zone所有语法")
	fingerCmd.Flags().StringVarP(&localfile, "localfofa", "l", "", "从本地文件读取资产,进行指纹识别,支持无协议,列如:192.168.1.1:9090 | http://192.168.1.1:9090")
	fingerCmd.Flags().StringVarP(&output, "output", "o", "", "输出所有结果,当前仅支持json和xlsx后缀的文件。")
	fingerCmd.Flags().IntVarP(&thread, "thread", "t", 100, "指纹识别线程大小。")
	fingerCmd.Flags().StringVarP(&proxy, "proxy", "p", "", "指定访问目标时的代理,支持http代理和socks5,例如:http://127.0.0.1:8080、socks5://127.0.0.1:8080")
}

然后去zone里面实现,由于0zone返回的结果里面直接就有url,所以就很简单了

func Zoneip(ips string) (urls []string) {
	color.RGBStyleFromString("244,211,49").Println("请耐心等待zone搜索......")
	zone := GetConfig()
	keyword := `ip=="` + ips + `"`
	res := zone_api(keyword, zone.Email, zone.Zone_key, 1, 40, zone.Fofa_timeout)
	it, _ := strconv.Atoi(res.Total)
	rpage := math.Ceil(float64(it) / 40)
	for i := 1; i <= int(rpage); i++ {
		res = zone_api(keyword, zone.Email, zone.Zone_key, i, 40, zone.Fofa_timeout)
		if len(res.Data) > 0 {
			for _, data := range res.Data {
				urls = append(urls, data.Url)
			}
		} else {
			break
		}
	}
	return

}

func Zoneall(keyword string) (urls []string) {
	color.RGBStyleFromString("244,211,49").Println("请耐心等待zone搜索......")
	zone := GetConfig()
	res := zone_api(keyword, zone.Email, zone.Zone_key, 1, 40, zone.Fofa_timeout)
	it, _ := strconv.Atoi(res.Total)
	rpage := math.Ceil(float64(it) / 40)
	for i := 1; i <= int(rpage); i++ {
		res = zone_api(keyword, zone.Email, zone.Zone_key, i, 40, zone.Fofa_timeout)
		if len(res.Data) > 0 {
			for _, data := range res.Data {
				urls = append(urls, data.Url)
			}
		} else {
			break
		}
	}
	return
}

go中结构体的tag

tag在结构体中起到类似注释和说明的作用,一些常见的tag选项可以用于一些函数

json - used by the encoding/json package, detailed at json.Marshal()

json - used by the encoding/json package, detailed at json.Marshal()

xml - used by the encoding/xml package, detailed at xml.Marshal()

bson - used by gobson, detailed at bson.Marshal()

protobuf - used by github.com/golang/protobuf/proto, detailed in the package doc

yaml - used by the gopkg.in/yaml.v2 package, detailed at yaml.Marshal()

db - used by the github.com/jmoiron/sqlx package; also used by github.com/go-gorp/gorp package

orm - used by the github.com/astaxie/beego/orm package, detailed at Models – Beego ORM

gorm - used by the github.com/jinzhu/gorm package, examples can be found in their doc: Models

valid - used by the github.com/asaskevich/govalidator package, examples can be found in the project page

datastore - used by appengine/datastore (Google App Engine platform, Datastore service), detailed at Properties

schema - used by github.com/gorilla/schema to fill a struct with HTML form values, detailed in the package doc

asn - used by the encoding/asn1 package, detailed at asn1.Marshal() and asn1.Unmarshal()