Ehole 指纹探测工具源码分析

一、项目简介

EHole 是一款域名资产系统指纹识别的工具。
github 地址 https://github.com/EdgeSecurityTeam/EHole

二、项目结构

项目基于 cobra 命令行框架进行编写,程序执行指令和参数配置在 cmd 目录下。

├── cmd
│   ├── finger.go              # finger 命令行参数配置
│   ├── fofaext.go             # fofaext 命令行参数配置
│   └── root.go                # 命令行入口
├── config.ini                 # fofa 配置文件
├── finger.json                # 指纹库 
├── go.mod
├── go.sum
├── images
│   ├── 16106897804249.jpg
│   └── 16106898229421.jpg
├── LICENSE
├── main.go                   # 主程序入口
├── module
│   ├── finger
│   │   ├── encoding.go       # 编码转换
│   │   ├── faviconhash.go    # 计算站点 favicon.ico 的 hash 值
│   │   ├── finger.go         # 创建执行扫描任务
│   │   ├── getfingerfile.go  # 加载指纹库
│   │   ├── http.go           # http 工具函数。获取站点标题、favicon.ico hash、http 请求
│   │   ├── jsjump.go         # 提取 js 代码里的跳转 url
│   │   ├── matchfinger.go    # 指纹匹配函数
│   │   ├── output.go         # 格式化输出函数
│   │   └── source
│   │       ├── fofa.go       # fofa 检索函数
│   │       └── localfile.go  # 本地文件读取函数
│   ├── fofaext
│   │   └── fofaext.go        # 存储 fofa 查询到的资产
│   └── queue
│       ├── queue.go          # 队列
│       └── queue_test.go
└── README.md

三、具体功能

3.1 指纹库

指纹库文件为 json 格式,参数包括 cms 名称、method 匹配方式、location 匹配位置、keyword 关键字组成。其中 method 分为两种,第一种是 keyword ,从网络请求中匹配特征值,location 位置参数可选 title 标题、header 请求头以及 body 请求体。

{
  "cms": "Kibana",
  "method": "keyword",
  "location": "title",
  "keyword": ["Kibana"]
}, {
  "cms": "Swagger UI",
  "method": "keyword",
  "location": "body",
  "keyword": ["Swagger UI"]
}, {
  "cms": "ThinkPHP",
  "method": "keyword",
  "location": "header",
  "keyword": ["ThinkPHP"]
},

第二种是 faviconhash,通过匹配 favicon.ico 的 hash 值进行 cms 识别,location 位置参数填写 body。

{
  "cms": "Jenkins",
  "method": "faviconhash",
  "location": "body",
  "keyword": ["81586312"]
}

*FinScan.fingerScan 方法中有提供 regular 正则的匹配方式,但指纹库中未提供对应规则。

3.2 创建扫描任务

当我们输入 finger 命令执行指纹识别扫描任务时,都会使用到 NewScan 函数、StartScan 方法。

var fingerCmd = &cobra.Command{
    ... 
    s := finger.NewScan(urls, thread, output,proxy)
    s.StartScan()
    ...
}

跟进 NewScan,这里创建了 FinScan 类型对象,参数包括
(1)UrlQueue 用于存储待扫描的 url
(2)Ch 通道,缓冲区大小与线程数一致
(3)Wg 用于等待所有扫描任务完成
(4)Thread 用于控制扫描的线程数,默认值是100
(5)Output 用于设置扫描结果输出路径及格式
(6)Proxy 用于设置网络代理
(7)AllResult 是所有资产扫描结果的切片
(8)FocusResult 是基于指纹库能识别到的重点资产扫描结果的切片
后续调用 LoadWebfingerprint 加载指纹库,再通过 GetWebfingerprint 加载指纹信息,最后将 url 插入队列。

func NewScan(urls []string, thread int, output string, proxy string) *FinScan {
	s := &FinScan{
		UrlQueue:    queue.NewQueue(),
		Ch:          make(chan []string, thread),
		Wg:          sync.WaitGroup{},
		Thread:      thread,
		Output:      output,
		Proxy:       proxy,
		AllResult:   []Outrestul{},
		FocusResult: []Outrestul{},
	}
	err := LoadWebfingerprint(source.GetCurrentAbPathByExecutable() + "/finger.json")
	if err != nil {
		color.RGBStyleFromString("237,64,35").Println("[error] fingerprint file error!!!")
		os.Exit(1)
	}
	s.Finpx = GetWebfingerprint()
	for _, url := range urls {
		s.UrlQueue.Push([]string{url,"0"})
	}
	return s
}

StartScan 主要是起多线程扫描任务,扫描由 fingerScan 执行。

func (s *FinScan)StartScan() {
	for i := 0; i <= s.Thread; i++ {
		s.Wg.Add(1)
		go func() {
			defer s.Wg.Done()
			s.fingerScan()
		}()
	}
	s.Wg.Wait()
	...
}

3.3 获取站点指纹

想要识别站点系统,首先肯定需要获取站点指纹特征。
(1)获取站点 title

func gettitle(httpbody string) string {
	doc, err := goquery.NewDocumentFromReader(strings.NewReader(httpbody))
	if err != nil {
		return "Not found"
	}
	title := doc.Find("title").Text()
	title = strings.Replace(title, "\n", "", -1)
	title = strings.Trim(title, " ")
	return title
}

(2)获取网络请求 header
同时从 header 提取 Server 和 X-Powered-By 作为 server 信息。

	resp, err := client.Do(req)
	httpheader := resp.Header
	var server string
	capital, ok := httpheader["Server"]
	if ok {
		server = capital[0]
	} else {
		Powered, ok := httpheader["X-Powered-By"]
		if ok {
			server = Powered[0]
		} else {
			server = "None"
		}
	}

(3)获取网络请求 body
调用 toUtf8 函数对 body 进行编码转换

	resp, err := client.Do(req)
	result, _ := ioutil.ReadAll(resp.Body)
	httpbody := string(result)
	httpbody = toUtf8(httpbody, contentType)

(4)获取站点 favicon.ico 并计算 hash
getfavicon 函数用于获取 favicon.ico。先通过 js 代码正则检索是否有 favicon 文件,若存在,则将域名与 favicon 文件拼接作为站点 favicon 访问路径,若不存在,则站点 favicon 访问路径为 http://host/favicon.ico。

func getfavicon(httpbody string, turl string) string {
	faviconpaths := xegexpjs(`href="(.*?favicon....)"`, httpbody)
	var faviconpath string
	u, err := url.Parse(turl)
	if err != nil {
		panic(err)
	}
	turl = u.Scheme + "://" + u.Host
	if len(faviconpaths) > 0 {
		fav := faviconpaths[0][1]
		if fav[:2] == "//" {
			faviconpath = "http:" + fav
		} else {
			if fav[:4] == "http" {
				faviconpath = fav
			} else {
				faviconpath = turl + "/" + fav
			}

		}
	} else {
		faviconpath = turl + "/favicon.ico"
	}
	return favicohash(faviconpath)
}

后续到 favicohash 里计算 favicon.ico 的 hash 值。成功读取到 favicon.ico 文件后,先进行 base64 编码,需要注意的是每76个字符后需要插入一次换行符,处理完毕后结尾再插入一次换行符。

// module/finger/faviconhash.go:27
func StandBase64(braw []byte) []byte {
	bckd := base64.StdEncoding.EncodeToString(braw)
	var buffer bytes.Buffer
	for i := 0; i < len(bckd); i++ {
		ch := bckd[i]
		buffer.WriteByte(ch)
		if (i+1)%76 == 0 {
			buffer.WriteByte('\n')
		}
	}
	buffer.WriteByte('\n')
	return buffer.Bytes()
}

再调用 Mmh3Hash32 进行 Hash 运算,得到最终的 favicon.ico hash 值。

import (
    "github.com/twmb/murmur3"
)

func Mmh3Hash32(raw []byte) string {
	var h32 hash.Hash32 = murmur3.New32()
	_, err := h32.Write([]byte(raw))
	if err == nil {
		return fmt.Sprintf("%d", int32(h32.Sum32()))
	} else {
		return "0"
	}
}

3.4 匹配指纹特征
匹配指纹目前由 iskeyword 执行,主要就是使用 strings.Contains 进行检测,查看从站点获取到的指纹特征是否与指纹库中的 keyword 完全匹配。

// module/finger/finger.go:95
func (s *FinScan)fingerScan() {
    ...
            for _, finp := range s.Finpx.Fingerprint {
				if finp.Location == "body" {
					if finp.Method == "keyword" {
						if iskeyword(data.body, finp.Keyword) {
							cms = append(cms, finp.Cms)
						}
    ...
}

// module/finger/matchfinger.go:8
func iskeyword(str string, keyword []string) bool {
    var x bool
    x = true
    for _, k := range keyword {
        if strings.Contains(str, k) {
            x = x && true
        } else {
            x = x && false
        }
    }
    return x
}

四、总结

Web 站点指纹识别的工具很多,例如 WhatWeb、TideFinger等,原理大同小异,只是实现的方式不一样,重要的还是指纹规则库的积累。

posted @ 2023-08-31 21:30  cijian9000  阅读(232)  评论(0编辑  收藏  举报