mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 19:21:52 +08:00
feat(proxy): 增强代理端口扫描的深度验证机制
- 新增4阶段深度验证:Banner读取→探测发送→响应等待→最终判定 - 新增SOCKS5错误码和代理错误文本检测 - 优化ProbeProxyBehavior探测逻辑,发送数据验证连接可达性 - 解决透明代理/全回显代理导致的假阳性问题
This commit is contained in:
+53
-3
@@ -365,7 +365,7 @@ func (s *socks5Dialer) updateAverageConnectTime(duration time.Duration) {
|
||||
}
|
||||
|
||||
// ProbeProxyBehavior 探测代理是否存在"全回显"问题
|
||||
// 通过连接一个几乎肯定不可达的地址来判断代理行为
|
||||
// 通过连接一个几乎肯定不可达的地址,并尝试发送数据来判断代理行为
|
||||
// 返回 true 表示代理可靠,false 表示代理存在全回显问题
|
||||
func ProbeProxyBehavior(dialer Dialer, timeout time.Duration) bool {
|
||||
// 使用 RFC 5737 保留的测试 IP (TEST-NET-1) + 高端口
|
||||
@@ -380,8 +380,58 @@ func ProbeProxyBehavior(dialer Dialer, timeout time.Duration) bool {
|
||||
// 连接失败 = 正常代理行为,代理可靠
|
||||
return true
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 连接"成功" = 代理存在全回显问题,不可靠
|
||||
_ = conn.Close()
|
||||
// 连接"成功",进一步验证:尝试发送数据检查是否真的可达
|
||||
// 全回显代理会接受连接,但数据无法到达目标
|
||||
|
||||
// 设置短超时
|
||||
_ = conn.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
_, writeErr := conn.Write([]byte("PROBE\r\n"))
|
||||
|
||||
if writeErr != nil {
|
||||
// 写入失败 = 连接不可用,但这是预期的(目标不可达)
|
||||
// 某些代理会在写入时才报告真实错误
|
||||
return true
|
||||
}
|
||||
|
||||
// 等待响应或错误
|
||||
_ = conn.SetReadDeadline(time.Now().Add(300 * time.Millisecond))
|
||||
buf := make([]byte, 64)
|
||||
n, readErr := conn.Read(buf)
|
||||
|
||||
if readErr != nil {
|
||||
// 读取超时或错误 = 目标不可达,但代理行为正常
|
||||
// 说明代理没有伪造响应
|
||||
errStr := readErr.Error()
|
||||
if contains(errStr, "timeout", "i/o timeout") {
|
||||
// 超时可能意味着代理接受了连接但没有伪造响应
|
||||
// 这是边界情况,暂时认为不可靠
|
||||
return false
|
||||
}
|
||||
// 其他错误(reset, refused等)说明代理正确报告了目标不可达
|
||||
return true
|
||||
}
|
||||
|
||||
// 收到数据 = 代理伪造了响应,不可靠
|
||||
if n > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// 代理接受连接且不报错也不响应 = 全回显行为
|
||||
return false
|
||||
}
|
||||
|
||||
// contains 检查字符串是否包含任一子串
|
||||
func contains(s string, substrs ...string) bool {
|
||||
for _, sub := range substrs {
|
||||
if len(sub) > 0 && len(s) >= len(sub) {
|
||||
for i := 0; i <= len(s)-len(sub); i++ {
|
||||
if s[i:i+len(sub)] == sub {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+120
-24
@@ -353,8 +353,10 @@ func scanSinglePort(host string, port int, addr string, timeout time.Duration, c
|
||||
return
|
||||
}
|
||||
|
||||
// 步骤1.5:代理连接验证(防止非标准SOCKS5代理的"全回显"问题)
|
||||
if !verifyProxyConnection(conn, addr) {
|
||||
// 步骤1.5:代理连接深度验证(防止透明代理/全回显代理的假连接问题)
|
||||
valid, verifyMethod := verifyProxyConnectionDeep(conn, addr)
|
||||
if !valid {
|
||||
common.LogDebug(fmt.Sprintf("代理验证失败 %s: %s", addr, verifyMethod))
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
@@ -385,46 +387,140 @@ func isTimeoutError(err error) bool {
|
||||
return err != nil && strings.Contains(err.Error(), "i/o timeout")
|
||||
}
|
||||
|
||||
// verifyProxyConnection 验证代理连接是否真正可用
|
||||
// 防止非标准SOCKS5代理的"全回显"问题:代理连接成功但目标实际不可达
|
||||
// 返回 true 表示连接有效,false 表示连接无效(目标不可达)
|
||||
func verifyProxyConnection(conn net.Conn, addr string) bool {
|
||||
// verifyProxyConnectionDeep 深度验证代理连接是否真正可用(4阶段验证)
|
||||
// 防止透明代理/全回显代理的假连接问题
|
||||
// 返回: (是否有效, 验证方式)
|
||||
func verifyProxyConnectionDeep(conn net.Conn, addr string) (bool, string) {
|
||||
// 如果没有使用代理,跳过验证
|
||||
if !common.IsProxyEnabled() {
|
||||
return true
|
||||
return true, "direct"
|
||||
}
|
||||
|
||||
// 如果代理不可靠(存在全回显问题),直接返回 false
|
||||
if !common.IsProxyReliable() {
|
||||
common.LogDebug(fmt.Sprintf("代理不可靠,跳过端口 %s", addr))
|
||||
return false
|
||||
// 阶段1: 读取 Banner (200ms)
|
||||
// 某些服务会主动发送欢迎消息
|
||||
_ = conn.SetReadDeadline(time.Now().Add(200 * time.Millisecond))
|
||||
buf := make([]byte, 256)
|
||||
n, _ := conn.Read(buf)
|
||||
|
||||
if n > 0 {
|
||||
// 收到数据,检查是否为代理错误响应
|
||||
if isProxyErrorResponse(buf[:n]) {
|
||||
common.LogDebug(fmt.Sprintf("代理返回错误响应 %s", addr))
|
||||
return false, "proxy_error"
|
||||
}
|
||||
// 收到有效 Banner,确认端口开放
|
||||
return true, "banner"
|
||||
}
|
||||
|
||||
// 设置短超时进行连接验证(100ms)
|
||||
// 如果目标端口真的开放,不会在这么短时间内收到错误
|
||||
// 如果目标不可达,非标准代理可能会立即返回错误
|
||||
_ = conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
// 阶段2: 发送探测数据 (HTTP OPTIONS)
|
||||
// OPTIONS 是安全的 HTTP 方法,不会修改服务器状态
|
||||
_ = conn.SetWriteDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
_, writeErr := conn.Write([]byte("OPTIONS / HTTP/1.0\r\n\r\n"))
|
||||
|
||||
// 尝试读取(非阻塞检查)
|
||||
buf := make([]byte, 1)
|
||||
_, err := conn.Read(buf)
|
||||
if writeErr != nil && isConnectionClosed(writeErr) {
|
||||
// 写入失败(broken pipe/reset)= 连接已被拒绝
|
||||
common.LogDebug(fmt.Sprintf("探测写入失败 %s: %v", addr, writeErr))
|
||||
return false, "write_failed"
|
||||
}
|
||||
|
||||
// 重置超时设置
|
||||
// 阶段3: 等待探测响应 (500ms)
|
||||
_ = conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
|
||||
n, err := conn.Read(buf)
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
|
||||
if n > 0 {
|
||||
// 收到响应,检查是否为代理错误
|
||||
if isProxyErrorResponse(buf[:n]) {
|
||||
common.LogDebug(fmt.Sprintf("代理探测返回错误 %s", addr))
|
||||
return false, "proxy_error"
|
||||
}
|
||||
// 收到有效响应,确认端口开放
|
||||
return true, "probe"
|
||||
}
|
||||
|
||||
// 阶段4: 最终判断
|
||||
if err != nil {
|
||||
errLower := strings.ToLower(err.Error())
|
||||
for _, pattern := range proxyFailurePatterns {
|
||||
if strings.Contains(errLower, pattern) {
|
||||
common.LogDebug(fmt.Sprintf("代理连接验证失败 %s: %v", addr, err))
|
||||
return false
|
||||
common.LogDebug(fmt.Sprintf("代理连接被拒绝 %s: %v", addr, err))
|
||||
return false, "proxy_reject"
|
||||
}
|
||||
}
|
||||
// 超时错误是正常的(目标没有主动发送数据)
|
||||
// EOF 也可能是正常的(某些服务的行为)
|
||||
}
|
||||
|
||||
return true
|
||||
// 代理不可靠 + 无响应 = 假连接(全回显代理的典型行为)
|
||||
if !common.IsProxyReliable() {
|
||||
common.LogDebug(fmt.Sprintf("代理不可靠且无响应,判定为假连接 %s", addr))
|
||||
return false, "no_response_unreliable"
|
||||
}
|
||||
|
||||
// 代理可靠 + 无响应 = 保守确认开放(可能是静默服务)
|
||||
return true, "uncertain"
|
||||
}
|
||||
|
||||
// isProxyErrorResponse 检查是否为代理错误响应
|
||||
// 支持 SOCKS5 错误码和常见代理错误模式
|
||||
func isProxyErrorResponse(data []byte) bool {
|
||||
if len(data) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// SOCKS5 错误响应检查
|
||||
// SOCKS5 响应格式: [VER][REP][RSV][ATYP]...
|
||||
// REP 字段: 0x00=成功, 0x01-0x08=各种失败
|
||||
if len(data) >= 2 && data[0] == 0x05 {
|
||||
rep := data[1]
|
||||
if rep >= 0x01 && rep <= 0x08 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 检查常见的代理错误文本
|
||||
dataStr := strings.ToLower(string(data))
|
||||
proxyErrorTexts := []string{
|
||||
"connection refused",
|
||||
"host unreachable",
|
||||
"network unreachable",
|
||||
"connection timed out",
|
||||
"proxy error",
|
||||
"gateway error",
|
||||
"bad gateway",
|
||||
"502",
|
||||
"503",
|
||||
}
|
||||
|
||||
for _, errText := range proxyErrorTexts {
|
||||
if strings.Contains(dataStr, errText) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isConnectionClosed 检查错误是否表示连接已关闭
|
||||
func isConnectionClosed(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
errStr := strings.ToLower(err.Error())
|
||||
closedPatterns := []string{
|
||||
"broken pipe",
|
||||
"connection reset",
|
||||
"connection refused",
|
||||
"use of closed network connection",
|
||||
"connection was forcibly closed",
|
||||
}
|
||||
|
||||
for _, pattern := range closedPatterns {
|
||||
if strings.Contains(errStr, pattern) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// saveOpenPort 保存开放端口结果
|
||||
|
||||
Reference in New Issue
Block a user