其实书中写的是Shadon的客户端,但是由于国内还是Fofa用的比较习惯,再加上没有Shadon的API,所以这里还是用Fofa来做替代。
在我们构建API客户端时,应该对其进行结构设计,使函数调用和逻辑独立,这样我们就可以将其实现作为其他项目中的库重用,这样将来就不必要重新发明轮子。
|---cmd
| |---fofa
| |---main.go
|---fofa
|---fofa_api.go
main.go文件定义的main包主要要是用其与客户端进行交互
fofa目录中的文件定义了fofa包,其中包含与fofa之间进行通信所需要的类型和函数,这个包可以成为我们的独立库,我们可以在其他项目中导入并使用这个包。
在完成书上的该项目时,其实代码一直没有出现什么大问题,主要问题还是集中在go的module管理上。我在本次项目中,经过各种尝试,使用的方式与书中有些不同的。我将我完成的fofa包扩展为一个go_hack包,并上传到自己的github上,通过go.mod管理方式引用自己在github上的包。后续也计划根据所学知识不断对其进行扩充。
而对于go的mod管理,也是踩了一些坑,最难受的就是,明明刚刚还能运行的文件,当第二天重新运行时,就会报错。最后终于是弄懂了go.mod的管理方式,并且发现自己之前在github上的上传方式也有一定的问题,最后得以解决。
首先go提供了2种包管理方式,一种是go.path,一种是go.mod。其通过参数GO111MODULE="on/auto/no"来控制,当使用on时,则为go.mod管理,no时则为go.path管理。
在go path管理中,go会去$gopath环境变量定义的路径下寻找对应的包。但是,已写的包和网络上的包都放在一切,不方便管理。
而go mod管理中,为了应对go path的缺点,从go1.11版本就推出了go modules包管理方式。开启了这种方式后,寻找出了标准库包时,就会到$GOPATH/pkg/mod目录下查找。
而我们如果需要使用go mod,我们首先需要对go进行一定的设置。
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
然后我们需要在我们的包的根目录下,使用命令创建go.mod
go mod init github.com/项目分类/项目名
此时,会生成一个go.mod文件。这样我们就创建好了一个mod。然后我们就可以编写我们的mod文件中的内容了,这里有一个注意点,虽然我们创建的包时github.com/项目分类/项目名的方式,但是我们在go代码中仍需要按照package 项目名的方式对这个项目进行编写。
package modname
创建github库并上传的操作就不多说了,此时我们已经创建好了一个gomod,并且将其上传到了github上。然后我们对其修改后,需要提交代码,生成tag。
git add .
git commit -am "add stringsx package content"
git push -u origin master
git tag v1.0.0
git push --tags
这里为什么要弄一个tag出来呢,因为我们可以使用go get -u xxx来获取包最新的tag或者使用go get [email protected]来获取对应的tag。
go get -u xxx (- u 获取包的最新tag)
go get [email protected] (version 即 tag)
这里就是第二个坑了,我们需要在对应的go的项目下,再次创建我们对应的mod,这个mod不是指我们上传到github上的库的项目,而是我们引用这个库的项目,在该项目的根目录下使用这些命令来进行获取。
首先,在2022年,fofa的api地址改为了https://api.fofa.info。所以我们首先编写一个常量来存储fofaapi的地址,后续关于api的调用,都需要通过该api地址进行,go创建常量的关键词为const。
首先我们尝试获取用户的身份信息。
我们根据fofa的api信息可以知道,我们首先需要传入两个参数,一个为用户的email,一个为用户的key。所以现在我们需要定义一个结构体,用户可以直接通过定义这个结构体,来传入其相应的email和key。然后编写一个用来创建FoFa_Client的方法。用户通过这个方法,传入email和key,可以直接获取一个定义好的FoFa_Client结构体。
type FoFa_Client struct {
email string
apiKey string
}func New_FoFa_Client(email string, apiKey string) *FoFa_Client {
return &FoFa_Client{email: email, apiKey: apiKey}
}
然后我们来看我们的查询语法,我们根据fofa的api可以知道,用户信息获取的接口为:
https://fofa.info/api/v1/info/my?email=your_email&key=your_key
并且返回值为下面响应的内容,包含了error、email、username等字段。于是我们定义一个结构体,用于接收fofaapi给我们响应的内容。在go中,我们可以在结构体中的变量后面加上`json:"xxx"`的方式来定义该变量对应的json参数为xxx,这样在我们将json内容转换为结构体或将结构体转换为json时便会十分方便。
type FoFa_APIInfo struct {
Error bool `json:"error"`
Email string `json:"email"`
UserName string `json:"username"`
Fcoin int `json:"fcoin"`
Isvip bool `json:"isvip"`
VipLevel int `json:"vip_level"`
IsVerfied bool `json:"is_verified"`
Avatar string `json:"avatar"`
Message string `json:"message"`
FofaCliVer string `json:"fofacli_ver"`
FofaServer bool `json:"fofa_server"`
}
然后我们继续完成我们的查询方法,因为我们的查询方法需要使用的FoFa_Clinent中的内容,所以我们需要将该方法定义为FoFa_Client的方法(类似于其他语言中的类)我们首先使用fmt.Sprintf()方法,来完成我们对于api接口url及其参数的定义,然后使用http.Get将其发送,会返回两个返回值,一个是接口给我们返回的内容,一个是错误信息。当错误信息为空(nil)时,我们可以认为访问成功,此时定义一个FoFa_APIInfo来接收我们服务器给我们返回的用户数据,并通过json.NewDecoder(a).Decode(b)的方式来将收到的json内容解析到我们的结构体,其中a为我们接收的json内容,b为结构体的地址,这样go会将我们的json解析,并依次给我们的结构体中的参数进行赋值。因为我们这里传入的是地址,所以我们后面只需要返回这个地址,即可将我们刚刚定义的FoFaAPIInfo返回。
func (s *FoFa_Client) APIInfo() (*FoFa_APIInfo, error) {
res, err := http.Get(fmt.Sprintf("%s/api/v1/info/my?email=%s&key=%s", BaseURL, s.email, s.apiKey))
if err != nil {
return nil, err
}
defer res.Body.Close() var ret FoFa_APIInfo
if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
return nil, err
}
return &ret, nil
}
此时我们便可以调用这个api接口,来获取用户信息了。
package mainimport (
"fmt"
"github.com/haochen1204/go_hack"
)
func main() {
s := go_hack.New_FoFa_Client("xxx", "xxx")
ret, _ := s.APIInfo()
fmt.Println("emali is ", ret.Email)
fmt.Println("username is ", ret.UserName)
如上,我们编写了一个test.go,首先利用New_FoFa_Client创建一个FoFa_Client,然后使用其方法APIInfo来获取信息,并且返回一个FoFa_APIInfo结构体,我们调用其中的Email和UserName,并打印出来。然后我们使用命令创建gomod,使用go get加载我们的github上的go_hack包。
go mod init test
go get github.com/haochen1204/go_hack
go run test.go
成功获取到邮箱和用户名信息。
在上面我们成功获取了用户的个人身份信息,接下来我们需要获取我们查询的信息,首先还是先根据FoFa的API指南来看fofa对于信息查询API接口的定义。
如图,以上内容是我们可以在fofa的查询接口api中传入的参数,fofa会根据我们传入的这些参数对我们的查询数据的返回内容进行修改。其中我们常用的参数为qbase64,这个也是必须参数,我们通过对查询语句的base64加密来查询对应的信息。剩下的一些内容则为一些辅助的参数信息,比我们的fields,可选参数,我们控制该参数可以让fofa返回我们想要的一些主机信息,比如国家、地区等等。还有一个size参数比较重要,我们往往需要根据自己的会员等级对其进行设置,来保证我们能获取到我们所需要的全部信息。
所以,我们首先还是需要创建一个结构体以及对其的New函数,来存储我们需要的查询信息。我们需要注意的是在New_FoFa_InfoSearch中,我们只传入了必须参数qbase64,也就是我们的查询语句,并且对其进行base64加密。至于其他参数,我们都设置为no、0或者false,这样设置的目的是在我们后续的查询时,如果为我们上述设定的值,则默认不启用这些参数,如果用户需要使用这些参数,则需要在New_FoFa_InfoSearch后自行对其进行修改。
type FoFa_InfoSearch struct {
Qbase64 string
Fields string
Page int
Size int
Full bool
}func New_FoFa_InfoSearch(q string) *FoFa_InfoSearch {
q = base64.StdEncoding.EncodeToString([]byte(q))
p := FoFa_InfoSearch{Qbase64: q, Fields: "no", Page: 0, Size: 0, Full: false}
return &p
}
我们现在完成了FoFa的查询参数,然后看一下响应事例,根据该示例我们可以看到我们查询成功后返回的信息。其中主要的是error,如果error的内容为false,则证明我们查询成功,下面的results中会以二维列表(python说法?)的形式存在,所以我们在go中用于接收时也需要使用二维列表进行接收。
type FoFa_Host struct {
Error bool `json:"error"`
Size int `json:"size"`
Page int `json:"page"`
Mode string `json:"mode"`
Query string `json:"query"`
Results [][]string `json:"results"`
}
然后我们这时便可以将我们刚刚来编写我们的搜索函数了。我们因为在进行信息查询时还需要FoFa的用户身份信息,所以这里还是将HostSearch方法写为FoFa_Client的方法。我们需要传入刚刚上面定义好的查询参数结构体,其会根据我们上面查询参数结构体中的内容编辑我们的API请求,并将返回的json信息进行处理,放到我们刚刚定义好的FoFa_Host结构体中,并将该结构体的地址返回。
具体来看代码,首先我们定义了默认的api请求url,并且在其中增加了用户的邮箱、apikey、加密后的查询语句等必须参数。然后根据我们传入的查询信息结构体中其他可选参数的值进行判断,如果不是我们刚刚设定的默认值,说明用户对其进行了设定,那么我们便需要在请求时加上他,所以我们使用fmt.Sprintf对其进行格式化后加在我们刚刚到url后面。然后我们使用get请求向服务器进行请求,将请求到的数据使用json进行格式化,并存入我们创建的FoFa_Host结构体中,返回该结构体的地址。
func (s *FoFa_Client) HostSearch(q *FoFa_InfoSearch) (*FoFa_Host, error) {
api_url := fmt.Sprintf("%s/api/v1/search/all?email=%s&key=%s&qbase64=%s", BaseURL, s.email, s.apiKey, q.Qbase64)
if q.Fields != "no" {
api_url = api_url + fmt.Sprintf("&fields=%s", q.Fields)
}
if q.Page != 0 {
api_url = api_url + fmt.Sprintf("&page=%d", q.Page)
}
if q.Size != 0 {
api_url = api_url + fmt.Sprintf("&size=%d", q.Page)
}
if q.Full != false {
api_url = api_url + "&full=ture"
}
res, err := http.Get(api_url)
if err != nil {
return nil, err
}
defer res.Body.Close()
var ret FoFa_Host
if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
return nil, err
}
return &ret, err
}
此时,我们便完成了对于fofa_api的编写,我们仍需要将其上传到我们的github上,并为其创建新的tag。
git add .
git commit -am "add stringsx package content"
git push -u origin master
git tag v1.0.0
git push --tags
然后我们创建一个新的项目,并对其进行inti
go mod init fofa
go get github.com/haochen1204/go_hack
首先我们复制之前的代码,用来获取用户信息,然后使用New_FoFa_InfoSearch来创建一个查询语句,并且修改查询内容,设置为查询ip/port/host/country四个信息,然后调用HostSearch方法进行查询,如果查询的结果正确,那么我们的Results会是一个二维数组,我们通过range来获取他其中的每一个值,也就是一维数组,然后分别打印即可。
package mainimport (
"fmt"
"github.com/haochen1204/go_hack"
)
func main() {
s := go_hack.New_FoFa_Client("[email protected]", "1664027a75442dde2fbd7f8825397186")
ret, _ := s.APIInfo()
fmt.Printf("email is %s and UserName is %s\n", ret.Email, ret.UserName)
search_msg := go_hack.New_FoFa_InfoSearch("www.haochen1204.com")
search_msg.Fields = "ip,port,host,country"
msg, err := s.HostSearch(search_msg)
if err != nil {
fmt.Println("Fofa error ! ", err)
} else {
fmt.Println(msg.Results)
for _, value := range msg.Results {
fmt.Println(value[0], value[1], value[2],value[3])
}
}
package go_hackimport (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
)
const BaseURL = "https://api.fofa.info"
type FoFa_Client struct {
email string
apiKey string
}
type FoFa_APIInfo struct {
Error bool `json:"error"`
Email string `json:"email"`
UserName string `json:"username"`
Fcoin int `json:"fcoin"`
Isvip bool `json:"isvip"`
VipLevel int `json:"vip_level"`
IsVerfied bool `json:"is_verified"`
Avatar string `json:"avatar"`
Message string `json:"message"`
FofaCliVer string `json:"fofacli_ver"`
FofaServer bool `json:"fofa_server"`
}
type FoFa_Host struct {
Error bool `json:"error"`
Size int `json:"size"`
Page int `json:"page"`
Mode string `json:"mode"`
Query string `json:"query"`
Results [][]string `json:"results"`
}
type FoFa_InfoSearch struct {
Qbase64 string
Fields string
Page int
Size int
Full bool
}
func New_FoFa_Client(email string, apiKey string) *FoFa_Client {
return &FoFa_Client{email: email, apiKey: apiKey}
}
func New_FoFa_InfoSearch(q string) *FoFa_InfoSearch {
q = base64.StdEncoding.EncodeToString([]byte(q))
p := FoFa_InfoSearch{Qbase64: q, Fields: "no", Page: 0, Size: 0, Full: false}
return &p
}
func (s *FoFa_Client) HostSearch(q *FoFa_InfoSearch) (*FoFa_Host, error) {
api_url := fmt.Sprintf("%s/api/v1/search/all?email=%s&key=%s&qbase64=%s", BaseURL, s.email, s.apiKey, q.Qbase64)
if q.Fields != "no" {
api_url = api_url + fmt.Sprintf("&fields=%s", q.Fields)
}
if q.Page != 0 {
api_url = api_url + fmt.Sprintf("&page=%d", q.Page)
}
if q.Size != 0 {
api_url = api_url + fmt.Sprintf("&size=%d", q.Page)
}
if q.Full != false {
api_url = api_url + "&full=ture"
}
res, err := http.Get(api_url)
if err != nil {
return nil, err
}
defer res.Body.Close()
var ret FoFa_Host
if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
return nil, err
}
return &ret, err
}
func (s *FoFa_Client) APIInfo() (*FoFa_APIInfo, error) {
res, err := http.Get(fmt.Sprintf("%s/api/v1/info/my?email=%s&key=%s", BaseURL, s.email, s.apiKey))
if err != nil {
return nil, err
}
defer res.Body.Close()
var ret FoFa_APIInfo
if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
return nil, err
}
return &ret, nil
}
星 球 免 费 福 利
转发公众号本文到朋友圈
截图到公众号后台第1、3、5名获取免费进入星球
欢 迎 加 入 星 球 !
关 注 有 礼
还在等什么?赶紧点击下方名片关注学习吧!
推荐阅读