fix(waf): 修复P3安全审计缺陷
P3-1: logclient 复用 TCP 长连接,失败时重建,消除 TIME_WAIT 堆积 P3-2: TLS 显式限定 CipherSuites + CurvePreferences,排除弱加密套件 P3-3: CheckInput query 编码改用 url.Parse + Query().Encode() 修复率 76% -> 94%(P3 全部清零,仅剩 P0-3 为设计依赖)
This commit is contained in:
1 parent
dd9f8e51ff
commit
00ce3eac6a
5 files changed
+86
-33
No files matched your search
@@ -136,28 +136,16 @@ func (e *Engine) CheckInput(input string) []int {
|
||||
uri = "/" + uri
|
||||
}
|
||||
|
||||
// 分离 path 与 query,对 query 做合法 URL 编码(空格等非法字符需转义)
|
||||
// 用 net/url 解析并重新编码 query,正确处理无值 key、重复 key、特殊字符
|
||||
// 这样 coraza ProcessURI 内部的 url.ParseRequestURI 不会失败,ARGS 才能被正确提取
|
||||
fullURI := uri
|
||||
if idx := strings.Index(uri, "?"); idx >= 0 {
|
||||
path := uri[:idx]
|
||||
rawQuery := uri[idx+1:]
|
||||
// 用 net/url 编码 query:保留 & = ,其他特殊字符转义
|
||||
var parts []string
|
||||
for _, kv := range strings.Split(rawQuery, "&") {
|
||||
eq := strings.Index(kv, "=")
|
||||
if eq < 0 {
|
||||
parts = append(parts, url.QueryEscape(kv))
|
||||
} else {
|
||||
parts = append(parts, url.QueryEscape(kv[:eq])+"="+url.QueryEscape(kv[eq+1:]))
|
||||
}
|
||||
}
|
||||
fullURI = path + "?" + strings.Join(parts, "&")
|
||||
if u, err := url.Parse(uri); err == nil && u.RawQuery != "" {
|
||||
u.RawQuery = u.Query().Encode()
|
||||
uri = u.String()
|
||||
}
|
||||
|
||||
// 照搬 coraza/http/middleware.go 的 processRequest 流程
|
||||
tx.ProcessConnection("127.0.0.1", 8080, "127.0.0.1", 12345)
|
||||
tx.ProcessURI(fullURI, "GET", "HTTP/1.1")
|
||||
tx.ProcessURI(uri, "GET", "HTTP/1.1")
|
||||
tx.AddRequestHeader("Host", "localhost")
|
||||
tx.SetServerName("localhost")
|
||||
if it := tx.ProcessRequestHeaders(); it != nil {
|
||||
|
||||
@@ -63,12 +63,14 @@ type dbV2Response struct {
|
||||
}
|
||||
|
||||
// Client 异步批量日志推送客户端。
|
||||
// send 仅在 flushLoop 单 goroutine 中调用,conn 复用无需额外加锁。
|
||||
type Client struct {
|
||||
ch chan Entry
|
||||
stop chan struct{}
|
||||
wg sync.WaitGroup
|
||||
stopped bool
|
||||
mu sync.Mutex
|
||||
conn net.Conn // 复用的 pymsg 长连接,失败时重建
|
||||
}
|
||||
|
||||
// global 全局单例。
|
||||
@@ -222,8 +224,8 @@ func (c *Client) sendOne(entry Entry, ts string) {
|
||||
return
|
||||
}
|
||||
|
||||
// pymsg 协议:4 字节 big-endian 长度 + UTF-8 载荷
|
||||
resp, err := pymsgCall(data)
|
||||
// pymsg 协议:4 字节 big-endian 长度 + UTF-8 载荷(复用长连接,失败时重连一次)
|
||||
resp, err := c.pymsgCall(data)
|
||||
if err != nil {
|
||||
return // fail-safe
|
||||
}
|
||||
@@ -234,14 +236,24 @@ func (c *Client) sendOne(entry Entry, ts string) {
|
||||
}
|
||||
}
|
||||
|
||||
// pymsgCall 发送一条 pymsg 请求并读取响应(短连接模式)。
|
||||
func pymsgCall(payload []byte) ([]byte, error) {
|
||||
conn, err := net.DialTimeout("tcp", dbAddr, socketTimeout)
|
||||
// pymsgCall 复用长连接发送一条 pymsg 请求并读取响应。
|
||||
// 首次失败时关闭旧连接重建一次,避免高并发拦截场景下大量 TIME_WAIT 短连接。
|
||||
func (c *Client) pymsgCall(payload []byte) ([]byte, error) {
|
||||
resp, err := c.doPymsg(payload)
|
||||
if err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
// 旧连接可能已断开(db 重启等),关闭后重连一次
|
||||
c.closeConn()
|
||||
return c.doPymsg(payload)
|
||||
}
|
||||
|
||||
// doPymsg 在当前连接上执行一次请求-响应交互。
|
||||
func (c *Client) doPymsg(payload []byte) ([]byte, error) {
|
||||
conn, err := c.getConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
conn.SetDeadline(time.Now().Add(socketTimeout))
|
||||
|
||||
// 发送:4 字节长度前缀 + 载荷
|
||||
@@ -270,6 +282,27 @@ func pymsgCall(payload []byte) ([]byte, error) {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// getConn 返回当前复用连接,不存在时建立。
|
||||
func (c *Client) getConn() (net.Conn, error) {
|
||||
if c.conn != nil {
|
||||
return c.conn, nil
|
||||
}
|
||||
conn, err := net.DialTimeout("tcp", dbAddr, socketTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.conn = conn
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// closeConn 关闭并置空复用连接。
|
||||
func (c *Client) closeConn() {
|
||||
if c.conn != nil {
|
||||
_ = c.conn.Close()
|
||||
c.conn = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) flush() {
|
||||
c.mu.Lock()
|
||||
if c.stopped {
|
||||
@@ -281,4 +314,5 @@ func (c *Client) flush() {
|
||||
|
||||
close(c.stop)
|
||||
c.wg.Wait()
|
||||
c.closeConn()
|
||||
}
|
||||
Reference in new issue
Block a user