背景

学习Golang并尝试应用于实际工作中。

入门基础

网站 网址
Golang 官方文档 https://golang.org/doc/
Go 语言之旅 https://tour.go-zh.org/welcome/1
Go 语言中文开源图书、资料或文档 http://books.studygolang.com/

在编写之前,我们需要理解以下问题:

  1. 什么是TCP/UDP,各自的特点是什么
  2. 什么是线程/协程/进程,各自的特点是什么
  3. 什么是HTTP协议,什么是响应码

在理解以上内容后,我们就可以开始了。

TCP

建立TCP扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"net"
)

func main() {
//net.Dial 链接成功返回接口,失败返回错误,成功错误为空 <nil>, net.DialTimeout 更为常见
_, err := net.Dial("tcp", "scanme.nmap.org:80")
if err == nil {
fmt.Println("Connection successful")
}
}

TCP连接可用于端口扫描,在增加并发后一般能达到2000/1s左右的速度,想要更快就需要使用SYN扫描或者在线程池等方面想办法了。其中常用的并发方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"sync"
"time"
)

func worker(ports chan int, wg *sync.WaitGroup) {
for p := range ports {
fmt.Println(p)
time.Sleep(1e9) //这是延时是为了突出效果
wg.Done()
}
}

func main() {
ports := make(chan int, 10) //创建 channel通道 默认为200
var wg sync.WaitGroup //创建 wg 计数器
for i := 0; i < cap(ports); i++ { // 利用 cap() 数组切片 来遍历 ports ,当 i 为 100(满足管道上线后),执行main() 中下面的循环
go worker(ports, &wg)
}
for i := 1; i <= 1024; i++ {
wg.Add(1)
ports <- i //向管道传递 i ,这时候管道是 0 ,在循环 100 满了后,在执行 main() 中第一个循环 ,从而达到控制 协程数量的效果
}
wg.Wait() //等待结束
close(ports) //关闭 channel通道
}

HTTP - GET

在Golang中主要使用net/http库来发送请求,具体可以学习下源代码。

发起常见的请求如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

package main

import (
"fmt"
"net/http"
)

func main() {
resp, err := http.Get("http://www.jljcxy.com/") //发起HTTP GET 请求
if err != nil {
fmt.Println("HTTP GET ",err)
}
fmt.Println(resp.Status) //输出状态码
defer resp.Body.Close() //完成后关闭请求
}

增加客户端(在HTTP头信息,cookie等等):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

func request360() (messsage []byte) {
var req *http.Request
var err error

req, err = http.NewRequest("GET", urlpath, nil) // http.NewRequest 就是在http库中http.Get 的下一层,因此可以这么用
if err != nil {
fmt.Println("请求失败 !",err)
}

// 设置 HTTP 头
req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0")
req.Header.Add("Referer", "https://zhongce.360.cn/hacker/project/")

// 设置 Cookie 信息
req.AddCookie(&http.Cookie{
Name: "homepage-frontend",
Value: "511qjjea6ga89hjaldqb0vguf3",
Domain: "zhongce.360.cn",
Path: "/cookies",
})


client := createHTTPClient() // client是HTTP客户端,可用于定制代理,超时等等功能。
resp, err1 := client.Do(req)
if err1 != nil {
return
}

defer resp.Body.Close()
reader := bufio.NewReader(resp.Body)
e := determineEncoding(reader)
utf8Reader := transform.NewReader(reader, e.NewDecoder())
bodyss, err := ioutil.ReadAll(utf8Reader)
if err != nil {
bodyss = []byte("")
}
return bodyss
}

另一种客户端的使用方式:

1
2
3
4
import "net/http"
...
clt := http.Client{}
resp, err := clt.Get("https://zhongce.360.cn/hacker/project/")

HTTP - POST

POST的主要问题在于POST数据的格式(相对python比较严格),在POST这些数据的时候要注意数据类型.

json数据上传类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

package main

import (
"fmt"
"bytes"
"net/http"
"io/ioutil"
"encoding/json"
)

func main() {
// 跳过报错部分了
requestBody, _ := json.Marshal(map[string]string{
"name":"shangzeng",
"url" :"https://www.shangzeng.club",
})

// 发起POST请求,跳过报错
resp, _ := http.Post("https://www.shangzeng.club", "application/json", bytes.NewBuffer(requestBody))
defer resp.Body.Close()

body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}

请求数据格式如下:

1
2
3
4
5
6
7
8
POST / HTTP/1.1
Host: www.jljcxy.com
User-Agent: Go-http-client/1.1
Content-Length: 55
Content-Type: application/json
Accept-Encoding: gzip

{"name":"shangzeng","url":"https://www.shangzeng.club"}

文件上传类型

文件上传主要注意对 mime/multipart 库的应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package main

import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)

func postFile(filename string, target_url string) (*http.Response, error) {
body_buf := bytes.NewBufferString("")
body_writer := multipart.NewWriter(body_buf)

// uploadfile 这里需要修改响应参数
_, err := body_writer.CreateFormFile("uploadfile", filename)
if err != nil {
fmt.Println("error writing to buffer")
return nil, err
}

// 打开当前目录文件名,用于上传请求体
fh, err := os.Open(filename)
if err != nil {
fmt.Println("error opening file")
return nil, err
}

boundary := body_writer.Boundary()
close_buf := bytes.NewBufferString(fmt.Sprintf("\r\n--%s--\r\n", boundary))
request_reader := io.MultiReader(body_buf, fh, close_buf)
fi, err := fh.Stat()
if err != nil {
fmt.Printf("Error Stating file: %s", filename)
return nil, err
}
req, err := http.NewRequest("POST", target_url, request_reader)
if err != nil {
return nil, err
}

// 设置请求头 ,请求cookie等参数
req.Header.Add("Content-Type", "multipart/form-data; boundary="+boundary)

req.AddCookie(&http.Cookie{
Name: "UserName",
Value: "admin",
Domain: "47.240.173.41:9090",
Path: "/cookies",
})

req.AddCookie(&http.Cookie{
Name: "SessionID",
Value: "YWRtaW5AcXEuY29t",
Domain: "47.240.173.41:9090",
Path: "/cookies",
})

req.AddCookie(&http.Cookie{
Name: "UserID",
Value: "4",
Domain: "47.240.173.41:9090",
Path: "/cookies",
})
req.ContentLength = fi.Size() + int64(body_buf.Len()) + int64(close_buf.Len())
return http.DefaultClient.Do(req)
}

func main() {
target_url := "http://47.240.173.41:9090/profile/edit/upload"
filename := "mysqltest.txt"
postFile(filename, target_url)
}

wireshark抓包分析如下:

1
2
3
4
5
6
7
8
9
10
11
POST /profile/edit/upload HTTP/1.1
Host: 47.240.173.41:9090
User-Agent: Go-http-client/1.1
Content-Length: 697
Content-Type: multipart/form-data; boundary=ce0b8388989bdd76e4bcdb0e0973980618e8a583ff70e5b9df3176ef76ce
Cookie: UserName=admin; SessionID=YWRtaW5AcXEuY29t; UserID=4
Accept-Encoding: gzip

--ce0b8388989bdd76e4bcdb0e0973980618e8a583ff70e5b9df3176ef76ce
Content-Disposition: form-data; name="userfile"; filename="mysqltest.txt"
Content-Type: application/octet-stream

其他格式类型

1
2
3
4
5
6
7
// go doc http.Request.Method 查看其他模式

req, err := http.NewRequest("GET", "http://www.baidu.com", nil)

//然后http.client 结构体的 Do 方法
//http.DefaultClient可以换为另外一个http.client
resp, err := http.DefaultClient.Do(req)

HTTP - HEAD

请求方式类似于GET,主要用于banner扫描(有一部分防火墙HEAD请求不会触发)。

1
2
r, err := http.DefaultClient.Head("http://httpbin.org/get")
r, err := http.Head("http://httpbin.org/get")

常用编译

编译不同版本:

1
2
3
4
5
// linux 编译
GOOS="linux" GOARCH="amd64" go build js_find-1.0.go

// windows 编译
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

一些常用命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 查看汇编指令
GOOS=linux GOARCH=386 go tool compile -S main.go >> main.S

// 获得最小的二进制文件 (有一定静态免杀效果)
go build -ldflags "-w -s" js_find-1.0.go

// 获取相关函数信息
go doc fmt.Println

// 规范代码格式
go fmt js_find-1.0.go

// 整理响应规范注释
go vet js_find-1.0.go

进行代理

还在学习中~

参考/学习

  1. 在Golang中发出HTTP请求
  2. net/http客户端的使用
  3. GO爬虫必备之HTTP请求QuickStart
  4. Golang的HTTP操作大全