代码复制不了移步到其他博客中复制
-
• 博客园: https://www.cnblogs.com/dhan/p/18767391
-
• CSDN: https://blog.csdn.net/weixin_60521036/article/details/146203024
web网络编程
tips:这一章的铺垫比较重要,这章过后就是一些安全工具如何编写以及一些poc、exp的工具编写。
Req
简单的请求
-
• 客户端创建 -
• 请求设置
funcreqHttp() { client := req.C() //客户端创建 res, err := client.R(). //请求设置,这个点的意思链式调用,后续在控制请求中会讲 Get("https://httpbin.org/uuid")if err != nil { log.Println("请求失败:", err) } fmt.Println(res)}
快速请求
-
• MustGet测试使用可以,正式开发不建议使用,可控性差
// 快速使用,一般用在test的时候functestHttp() { resp := req.MustGet("https://httpbin.org/uuid") //这里就是发起了一次请求 fmt.Println(resp.String()) //第一种打印*req.Response类型的响应体 fmt.Println(string(resp.Bytes())) //第二种打印*req.Response类型的响应体}
调试
DevMode
DevMode是直接开启全局调试,自动打印出来
// 调试模式funcdevModeReq() {//使用req进行请求,所以req的调试模式也是在req启动 req.DevMode() //开启调试模式,就会打印出来请求的过程以及响应内容 req.SetCommonBasicAuth("username", "password"). //设置用户名和密码 SetTimeout(5 * time.Second). //设置超时时间 SetUserAgent("my-ua") resp := req.MustGet("https://httpbin.org/uuid")//没有开启调试模式但是想打印的话就正常打印 fmt.Println(resp.String()) //第一种打印*req.Response类型的响应体 fmt.Println(string(resp.Bytes())) //第二种打印*req.Response类型的响应体}
DebugLog
DebugLog 是跟踪请求的过程
他可以看到你整个请求的过程,重定向的信息等等
// 查看请求的过程发生了什么funcdeLog() { client := req.C(). EnableDebugLog() //开启DebugLog client.R().Get("http://baidu.com/s?wd=req")}
TraceInfo瓶颈分析
trace跟踪信息
functraceReq() {// Enable trace at request level client := req.C() resp, err := client.R(). EnableTrace(). //开启瓶颈分析 Get("https://api.github.com/users/imroc")if err != nil { log.Fatal(err) } trace := resp.TraceInfo() //trace跟踪信息 fmt.Println(trace.Blame()) //分析总结(请求减慢的原因归咎) fmt.Println(trace) // 打印内容}
控制请求与响应
控制请求的字段内容
这里做一个了解,后面会详细说一下作用范围,这里就过一遍即可,知道哪些字段可控(其实都可控)
funccontrolReq() { client := req.C(). SetUserAgent("my-ua"). //设置ua头,在client中设置,在下面的R()中设置不了//EnableDumpAllToFile("log.txt") //将请求的信息写到该文件中//捕获请求和响应,// 想要看的更加详细就可以开启dump所有内容,// 就能够看到我们是不是真的改变了请求内容 EnableDumpAll() parms := map[string]string{"a": "123","b": "hello", } resp, err := client.R(). //拿到请求体 SetPathParam("usernamae", "imroc"). //设置请求路径,用username作为占位符 SetPathParam("xxx", "test"). //再次设置,用xxx作为占位符 SetQueryParam("a", "12"). //设置请求参数,a=12 SetQueryParams(parms). //用map来作为请求参数,用于多个参数的时候 SetHeader("mycookie", "test"). //设置请求头 SetHeader("mysession", "test2"). //设置请求头 SetBody("body=world"). //设置请求体 Get("https://httpbin.org/uuid")//以上设置暂时在初期阶段够用了。if err != nil { log.Println("请求出错:", err) } fmt.Println(resp)}
控制调试打印的内容
-
• SetCommonDumpOptions:控制输出的内容 -
• EnableDumpAllToFile:dump到文件中
输出的log2.txt文件内容如下
// 控制调试打印的内容// 全局应用funccontrolDevOptions() { client := req.C() opt := &req.DumpOptions{ Output: os.Stdout, //标准输出 RequestHeader: false, //不输出请求头 RequestBody: false, //不输出请求体 ResponseHeader: true, //输出响应头 ResponseBody: true, //输出响应体 Async: false, //不进行异步输出 } client.SetCommonDumpOptions(opt). EnableDumpAllToFile("log2.txt") client.R().Get("https://httpbin.org/uuid")}
分开dump请求与响应部分
-
• var bufReq, bufResp bytes.Buffer
:需要用变量来接收请求与响应不同部分 -
• SetDumpOptions:控制dump导出部分
// 分别打印请求与响应部分// 在R中控制调试打印的内容,只作用与本次R请求,与上面的client直接全局设置的不同funccontrolReqRespOutPut() { client := req.C()var bufReq, bufResp bytes.Buffer client.R().// 不开启的话就会导致无法转储出去单独打印请求体响应体 EnableDump(). //EnableDump启用转储,包括请求和响应的所有内容。 SetDumpOptions(&req.DumpOptions{ RequestOutput: &bufReq, //将请求体输出到这里 ResponseOutput: &bufResp, //将响应体输出到这里 RequestHeader: true, RequestBody: true, ResponseHeader: true, ResponseBody: true, }). SetBody("body=hello+world!"). Get("https://httpbin.org/uuid") fmt.Println("请求体") fmt.Println(bufReq.String()) fmt.Println("响应体") fmt.Println(bufResp.String())}
请求体设置
SetBody 可以接受任意类型
PS:EnableDumpAllWithoutResponse
因为开启了调试模式,所以会打印很多东西,但是可以忽略响应打印出来结果,所以这函数就是忽略响应结果,只看请求。
在编写一些poc/exp的时候可能需要在body部分,需要加入一些结构体或者map数据,他会根据你设置的content-type来判断解析成json还是xml格式,如果都不设置的话他会默认将这些类型解析为json数据body。
添加上content-type类型后就自动转了,比较方便(后面还有更方便的)
-
• SetBodyXXX不用设置content-type也能直接转想要的格式了 400
以下是代码:
// 设置请求体中的一些骚姿势funcsetbodyHttp() {type test struct { Name string Age int } t := test{ Name: "zhangsan", Age: 123, } client := req.C().DevMode().EnableDumpAllWithoutResponse() client.R(). SetBody(t). SetContentType("application/xml"). Get("https://httpbin.org/uuid") client2 := req.C().DevMode().EnableDumpAllWithoutResponse() client2.R(). SetBodyXmlMarshal(t). Get("https://httpbin.org/uuid")}
作用范围级别
在设置请求参数的时候,有分两种作用范围设置,一种全局(客户端级别),另一种就是只作用与这一个client的对象(请求级别)。
以下就尽量快速过为妙,上面学的时候已经有很多函数都用过了,没必要花太多时间,知道即可,下面作为一个知识字典,以后方便查阅。
请记住:
-
• 用req.C().R()来设置的请求级别
-
• 用req.C()来设置的是全局级别(也即客户端级别client)
设置参数查询
请求级别:
-
• SetQueryParam: SetQueryParam("test", "123")
即 httpxxx?test=123设置多个的时候仅仅对最后一个设置的生效 -
• SetQueryParams:SetQueryParams接收map类型设置多个变量,由于map的键唯一,所以多个同名的话也只会作用一个。 -
• SetQueryString:直接给查询参数即可, SetQueryString("test1=123&test2=456")
这个不会产生什么重复覆盖问题,字符串给啥他就拼接到你url中去 -
• AddQueryParam: AddQueryParam("key", "value1")
如果该参数已存在,它不会覆盖原有的值,这个一般用在你临时需要添加什么查询参数的时候可以用,而且不会覆盖原有的同名键值
全局级别:
-
• SetCommonQueryParam -
• SetCommonQueryParams -
• SetCommonQueryString -
• AddCommonQueryParam
URL 路径参数
请求级别:
-
• SetPathParam: SetPathParam("username", "zhangsan")
-
• SetPathParams:接收map类型
全局级别:
-
• SetCommonPathParam -
• SetCommonPathParams
表单请求设置
请求级别:
-
• SetFormData:接收map类型,但是同一个键只能有一个 -
• SetFormDataFromValues::代码如下所示,但是注意的是url.Values是net/url包,所以要注意这个url不是我定义的,拿来就用即可。
client3 := req.C().DevMode().EnableDumpAllWithoutResponse()v := url.Values{"p": []string{"hello", "world", "!"},}client3.R(). SetFormDataFromValues(v). Post("https://httpbin.org/post")
-
• multipart ⽅式提交表单: EnableForceMultipart
client3 := req.C().DevMode().EnableDumpAllWithoutResponse()v := url.Values{"p": []string{"hello", "world", "!"},}client3.R(). EnableForceMultipart(). SetFormDataFromValues(v). Post("https://httpbin.org/post")
全局级别:
-
• SetCommonFormData -
• SetCommonFormDataFromValues
请求头设置
请求级别:
-
• SetHeader:自动将你传进的header键的首字母大写 -
• SetHeaders:接收map类型,自动将你传进的header键的首字母大写 -
• SetHeaderNonCanonical:你给什么样就输出什么样,不会自动给你首字母大写 -
• SetHeadersNonCanonical:你给什么样就输出什么样,不会自动给你首字母大写 -
• SetHeaderOrder:控制header的顺序,因为有的服务端可能会对header的顺序判断是否允许请求,设置了SetHeaderOrder,他就会按照你给定的顺序进行排序请求过去。
request.SetHeaderOrder( "cookie", "ssession", "test","ua", )
全局级别
-
• SetCommonHeaderNonCanonical -
• SetCommonHeadersNonCanonical
判断响应状态码
没啥好说的,都到这了这些应该对于各位师傅来说都是一眼就学会的基操了。
-
• resp.IsSuccessState -
• resp.IsErrorState -
• resp.StatusCode
// 判断响应状态码funcjudgeStatusCode() { client := req.C() resp, err := client.R().Get("http://www.baidu.com")if err != nil { log.Println("请求失败:", err) }if resp.IsSuccessState() { fmt.Println("ok") }if resp.IsErrorState() { fmt.Println("error") }//可以通过响应对应的代码判断if resp.StatusCode != http.StatusOK { //http有一个const变量,里面有很多对应的响应码,自行查看即可(文件:httpstatus.go) fmt.Println("error") }//打印响应体 fmt.Println(resp.Bytes()) //bytes打印 fmt.Println(resp.String()) //string打印}
解析数据
SetSuccessResult
-
• SetSuccessResult:SetSuccessResult会⾃动解析你给的结构体或map -
• SetErrorResult这里一同把SetErrorResult也讲了,可以自定义错误解析到你自己定义的错误中去,没啥好说感觉也时,定义好了就直接给到SetErrorResult即可(详细见代码处)
运行结果如下图所示:
// 接收响应回来的json数据type user struct { Login string`json:login` Id int`json:id` Name string`json:name`}// 接收错误响应type errorResp struct { Mess string`json:message`}// 解析响应的json数据// SetSuccessResult⾃动解析结构体或mapfuncgetHttpJson() { client := req.C()var respUser uservar respError errorResp res, err := client.R(). SetSuccessResult(&respUser). //接收响应回来的json数据,同时也可以解析map SetErrorResult(&respError). //若请求错误将错误信息存储到该结构体中 Get("https://api.github.com/users/whoisdhan")if err != nil { log.Println("代码请求出错:", err)return }if res.IsSuccessState() { fmt.Println("请求成功") fmt.Println(respUser.Login) fmt.Println(respUser.Id) fmt.Println(respUser.Name) } elseif res.IsErrorState() { fmt.Println("网络请求出错:", respError.Mess) } else { fmt.Println("未知错误:", res.StatusCode) }}
gjson
-
• 字段名
、.
:表示读取该字段,字段名.0
表示读取该键名下的数据的第一个字段,字段名.1
读取第二个 -
• *
、?
:gjson支持通配符:像在linux命令行中使用的通配符那样用就行,a*a
收尾为a的都匹配 -
• #
:表示该字段所有元素。 -
• 在结尾: 字段名1.字段名2.#
表示返回字段名2的数组长度 -
• 在中间,即 #
后面还有内容:字段名1.#.字段名2
表示返回字段名2所有元素 -
• 原因:这也是为啥我叫他 #
的意思是表示该字段所有元素,在结尾就是返回长度,不在就是返回整个数组列表
gjson解析json数据可提取指定字段,不用定义结构体
gjson非常适合提取几个字段出来之类的,方便的一笔。
// 由于我们的http请求回来的数据能方便的转为string所以也能用gjson// 模拟数据const httpjson = `{ "name":{"first":"Tom", "last": "Anderson"}, "age": 37, "children": ["Sara", "Alex", "Jack"], "fav.movie": "Dear Hunter", "friends": [ {"first": "Dale", "last":"Murphy", "age": 44, "nets": ["ig", "fb", "tw"]}, {"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]}, {"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]} ]}`funcgjsonHttp() {//先简单用正常的httpjson数据尝试一下 client := req.C() resp, _ := client.R(). Get("https://api.github.com/users/whoisdhan") login := gjson.Get(resp.String(), "login") id := gjson.Get(resp.String(), "id") name := gjson.Get(resp.String(), "name") fmt.Println(login, id, name)//用httpjson测试数据 fmt.Println(gjson.Get(httpjson, "name.first")) fmt.Println(gjson.Get(httpjson, "friends.#.first")) fmt.Println(gjson.Get(httpjson, "friends.#.nets")) fmt.Println(gjson.Get(httpjson, "fav\.movie")) //字段存在符号的话用\转义}
响应数据解析练习
这里的解析可能有点主观了,可以按照自己的想法,尽量不要被我带偏,我的不一定是最佳的
// 练习// 1. 获取⽤户信息 https://api.github.com/users/{username}// 2. 获取仓库列表信息 https://api.github.com/users/{username}/repos// 3. 用户与仓库列表信息:普通读取json、Unmarshal转结构体⽅式解析、gjson读取// 4. 用户与仓库列表信息:格式化输出到控制台// 5. 仓库列表信息保存到本地 JSON ⽂件中。//这里就固定拿仓库列表信息如下信息:// (也可以随便获取你想要的字段)// 用户信息{login、id、url、name、email}// 仓库列表信息{name、owner.login、description}
-
• myMarshalIndent:为了方便格式化,我自己写了一个格式化函数方便不同方式保存的时候进行格式化格式化都需要先转到map中存储才能够格式化正常的json数据出来,格式化好了就任意你想干啥就干啥了 -
• 需要注意的细节:有的json他是列表包裹着 []
,所以转的时候要注意判断用map[string]interface{}
还是[]map[string]interface{}
-
• json.MarshalIndent:他可以对map列表进行格式化的,因为那样也时一个正常的json数据,只不过你需要用 []map[string]interface{}
列表类型进行存储(这里是一个很重要的细节,开发过程中如果不注意很容易导致崩溃)
我用了三个函数完成这个练习:
-
• normalGetJson:普通读取,就是解析出来后整个data就直接write到文件中 -
• UnmarshalToStruct:Unmarshal转结构体⽅式解析,这里使用了上面学到的 SetSuccessResult
,给到结构体后进行解析,这样也很方便 -
• gjsonOutput:这里就是使用gjson了,gjson很方便,但是面对比较多字段提取的时候还是比较繁琐。这里复习了一个细节:map想要后续不断地赋值的话就需要进行make空间出来才行,如果是[]map的话需要给定一个空间范围,0也行,你要说明这是一个切片。如果你只是单单定义一个map变量或者map切片变量,后续是无法使用的。还有一个细节就是:make出来的空间是固定的,如果你要make出来的空间作为一个临时变量赋值给其他变量的时候要注意了,用了一个空间就不要继续用了,因为你将一个空间给了多个变量的话,那么那些变量都指向你这一个空间,那么他们的值其实都一样。
运行结果:这里就放几个保存出来的文件截图
-
• normalGetJson: -
• UnmarshalToStruct -
• gjsonOutput
示例代码:
// 用户信息{login、id、url、name、email}// 仓库列表信息{name、owner.login、description}funcgjsonOutput(username string) {//gjson就十分简单了,只需要拿到响应json数据即可取出来看 mapUserData := make(map[string]interface{}) //用户信息存储//仓库信息存储,// 由于仓库是数组列表所以要给一个初始长度// 因为可能仓库为空的,所以就初始化为0即可 mapReposData := make([]map[string]interface{}, 0) fmt.Println("-------------------用户信息-------------------") client := req.C() resp, err := client.R(). SetPathParam("username", username). Get("https://api.github.com/users/{username}")if err != nil { log.Println("代码请求失败:", err) }//gjson想要写入文件就只能转储到struct或者map中 mapUserData["login"] = gjson.Get(resp.String(), "login").String() mapUserData["id"] = gjson.Get(resp.String(), "id").String() mapUserData["url"] = gjson.Get(resp.String(), "url").String() mapUserData["name"] = gjson.Get(resp.String(), "name").String() mapUserData["email"] = gjson.Get(resp.String(), "email").String() data, err := json.MarshalIndent(mapUserData, "", "t")if err != nil { log.Println("格式化失败:", err) } fmt.Println(string(data))// test := gjson.Get(resp.String(), "login")// fmt.Println(test) fmt.Println("---------------------------------------------") fmt.Println("-------------------仓库信息-------------------") client = req.C() resp, err = client.R(). SetPathParam("username", username). Get("https://api.github.com/users/{username}/repos")if err != nil { log.Println("代码请求失败:", err) }// 仓库列表信息{name、owner.login、description} arr := gjson.Get(resp.String(), "#.name")for i, j := range arr.Array() {//一定要放到这里来,因为make指向同一个空间,// 如果你在for外面定义mapTmpRepos造成取到的值会全部变成最后一个值,// 因为同一个空间他会同步改变你map切片append里面所有的内容,// 因为同一个空间嘛 mapTmpRepos := make(map[string]interface{}) mapTmpRepos["name"] = j.String() mapTmpRepos["login"] = gjson.Get(resp.String(), (strconv.Itoa(i) + ".owner.login")).String() mapTmpRepos["description"] = gjson.Get(resp.String(), (strconv.Itoa(i) + ".description")).String() mapReposData = append(mapReposData, mapTmpRepos) } data, err = json.MarshalIndent(mapReposData, "", "t")if err != nil { fmt.Println("格式化失败:", err) } fmt.Println(string(data)) //格式化的内容打印到终端 file, err := os.OpenFile("gjson.json", os.O_CREATE|os.O_RDWR|os.O_RDWR, 0666)if err != nil { log.Println("打开文件失败:", err) } _, err = file.Write(data)if err != nil { log.Println("导出json失败:", err) }}
Cookie
请求级别:
-
• SetCookies
SetCookies(&http.Cookie{ Name: "hacker", Value: "aaa",})
全局级别:
-
• SetCommonCookies
SetCommonCookies(&http.Cookie{ Name: "Global", Value: "dddd",})
默认行为
就是当你请求的服务端响应了set-cookie回来后,当你再次请求的时候就会携带上这个set-cookie回来的cookie键值去请求。
禁⽤Cookie
-
• SetCookieJar(nil)
存储Cookie
安装
go get -u github.com/juju/persistent-cookiejar
使用
funcsaveCookies() { jar, err := cookiejar.New(&cookiejar.Options{ Filename: "cookies.json", })if err != nil { log.Println("保存失败") }defer jar.Save() client := req.C().SetCookieJar(jar).DevMode() client.R().Get("http://www.baidu.com")}
证书校验
无视风险
针对一些不安全的网站请求可能会请求失败,所以需要忽略证书的校验
两种方式可以忽略:
client := req.C().DevMode().EnableDumpAllWithoutResponse()//忽略证书风险//第一种//client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})//第二种client.TLSClientConfig.InsecureSkipVerify = true_, err := client.R(). Get("https://self-signed.badssl.com/")if err != nil { fmt.Println("代码请求失败:", err)}
配置证书
配置好风险站点的证书即可访问风险站点证书需要访问网站的时候在浏览器的小锁中下载即可(具体不演示了,自行百度网站证书如何下载)
-
• 证书文件配置
client := req.C().DevMode().EnableDumpAllWithoutResponse()//可以同时配置多个网站的证书client.SetRootCertsFromFile("cert1.crt","cert2.crt","cert3.crt")
-
• 证书内容配置在下载了证书之后,可以编辑证书将里面的内容配置进来也行
client := req.C().DevMode().EnableDumpAllWithoutResponse()client.SetRootCertFromString("-----BEGIN CERTIFICATE-----")
Auth身份认证
-
• SetBasicAuth -
• SetDigestAuth区别:一个Basic认证,一个Digest认证Digest认证比较安全,请求被拦截了攻击者无法直接获取密码但是Basic的请求被拦截了就是直接获取到密码服务端支持哪一个?检查 WWW-Authenticate
响应头,返回Basic 还是 Digest就知道支持哪一个。
funcauthHttp() { client := req.C().DevMode().EnableDumpAllWithoutResponse() client.R(). SetBasicAuth("username", "password"). Get("https://httpbin.org/uuid") client2 := req.C().DevMode().EnableDumpAllWithoutResponse() client2.R(). SetDigestAuth("username2", "password2"). Get("https://httpbin.org/uuid")}
文件上传下载
上传文件
-
• SetFile -
• SetFiles:接收map类型,上传多个文件 -
• SetUploadCallback:显示上传进度
funcuploadFileHttp() {//简单上传 client := req.C().DevMode().EnableDumpAllWithoutResponse() callback := func(info req.UploadInfo) { //显示上传进度 fmt.Printf("n文件名:%qn已上传:%.2f%%n", info.FileName,float64(info.UploadedSize)/float64(info.FileSize)*100.0) } client.R().//SetFile("filename", "cookies.json"). SetFiles(map[string]string{"test": "test.txt","test2": "test2.txt","test3": "test3.txt", }). SetUploadCallback(callback). //使用该函数:显示上传进度 Post("https://httpbin.org/post")}
下载文件
-
• SetOutputFile:这里是指定下载的文件路径 -
• SetOutputDirectory:设置下载的默认路径,即SetOutputFile可以只给文件名,自动存在该路径下 -
• SetDownloadCallback:显示下载进度
funcdownloadFileHttp() { client := req.C() //.DevMode().EnableDumpAllWithoutResponse() callback := func(info req.DownloadInfo) {if info.Response.Response != nil { //响应不为空 fmt.Printf("n已下载:%.2f%%n",float64(info.DownloadedSize)/float64(info.Response.ContentLength)*100.0) } } client.R().//这里是指定文件路径 SetOutputFile("./baidu.html").Get("http://127.0.0.1/xxx.txt") client2 := req.C().//这里可以设置默认下载目录,后面直接download的时候就不用指定路径了,给文件名即可 SetOutputDirectory("./"). DevMode(). EnableDumpAllWithoutResponse() client2.R(). SetOutputFile("baidu2.html"). SetDownloadCallback(callback). Get("http://127.0.0.1/xxx.txt")}
多线程下载练习
-
• NewParallelDownload:创建一个多线程下载客户端 -
• 其他函数在注释中标明了。
functhreatDownload() { client := req.C() err := client.NewParallelDownload("http://xxxxx.xxxx.xxx/xxx.iso"). SetConcurrency(5). //设置 5 个线程 并行下载 SetFileMode(0777). //设置 文件权限(可读可写可执行)。 SetOutputFile("xxx.iso"). //设定最终 存储文件名 SetSegmentSize(1024 * 1024 * 5). //每个线程下载 5MB 的数据块 SetTempRootDir("./tmp"). //这个是下载的时候指定的临时存储目录 Do()if err != nil { log.Println("下载失败:", err)return }}
原文始发于微信公众号(竹等寒):Go红队开发—web网络编程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论