mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
isolate session network checks
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -119,6 +120,43 @@ func (s *ScanSession) DialTCP(ctx context.Context, network, address string, time
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPDo executes an HTTP request with the session's packet limits and counters.
|
||||||
|
func (s *ScanSession) HTTPDo(client *http.Client, req *http.Request) (*http.Response, error) {
|
||||||
|
if ok, err := CanSendPacketWith(s.Config, s.State); !ok {
|
||||||
|
s.LogError(fmt.Sprintf("HTTP请求 %s 受限: %s", req.URL.String(), err.Error()))
|
||||||
|
return nil, fmt.Errorf("%s", i18n.Tr("network_rate_limited", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
s.State.IncrementTCPFailedPacketCount()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.State.IncrementTCPSuccessPacketCount()
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyEnabled reports whether this scan session uses a network proxy.
|
||||||
|
func (s *ScanSession) ProxyEnabled() bool {
|
||||||
|
if s == nil || s.Config == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s.Config.Network.Socks5Proxy != "" || s.Config.Network.HTTPProxy != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSOCKS5Proxy reports whether this scan session uses SOCKS5.
|
||||||
|
func (s *ScanSession) IsSOCKS5Proxy() bool {
|
||||||
|
return s != nil && s.Config != nil && s.Config.Network.Socks5Proxy != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyReliable reports whether the session proxy should be treated as reliable.
|
||||||
|
func (s *ScanSession) ProxyReliable() bool {
|
||||||
|
if !s.ProxyEnabled() || !s.IsSOCKS5Proxy() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return proxy.IsProxyReliable()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ScanSession) getDialer(timeout time.Duration) (proxy.Dialer, error) {
|
func (s *ScanSession) getDialer(timeout time.Duration) (proxy.Dialer, error) {
|
||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = s.Config.Timeout
|
timeout = s.Config.Timeout
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -65,3 +68,81 @@ func TestScanSessionDialerCacheIsTimeoutAware(t *testing.T) {
|
|||||||
t.Fatalf("proxy timeout = %v, want %v", got, shortTimeout)
|
t.Fatalf("proxy timeout = %v, want %v", got, shortTimeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestScanSessionHTTPDoUsesSessionState(t *testing.T) {
|
||||||
|
previousState := GetGlobalState()
|
||||||
|
globalState := NewState()
|
||||||
|
SetGlobalState(globalState)
|
||||||
|
t.Cleanup(func() { SetGlobalState(previousState) })
|
||||||
|
|
||||||
|
sessionState := NewState()
|
||||||
|
session := NewScanSession(NewConfig(), sessionState, &FlagVars{})
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusNoContent,
|
||||||
|
Body: io.NopCloser(strings.NewReader("")),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}, nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodHead, "http://example.com", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := session.HTTPDo(client, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
|
||||||
|
if got := sessionState.GetTCPSuccessPacketCount(); got != 1 {
|
||||||
|
t.Fatalf("session TCP success count = %d, want 1", got)
|
||||||
|
}
|
||||||
|
if got := globalState.GetTCPSuccessPacketCount(); got != 0 {
|
||||||
|
t.Fatalf("global TCP success count = %d, want 0", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanSessionProxyStateComesFromConfig(t *testing.T) {
|
||||||
|
direct := NewScanSession(NewConfig(), NewState(), &FlagVars{})
|
||||||
|
if direct.ProxyEnabled() {
|
||||||
|
t.Fatal("direct session should not report proxy enabled")
|
||||||
|
}
|
||||||
|
if direct.IsSOCKS5Proxy() {
|
||||||
|
t.Fatal("direct session should not report SOCKS5")
|
||||||
|
}
|
||||||
|
if !direct.ProxyReliable() {
|
||||||
|
t.Fatal("direct session should be reliable")
|
||||||
|
}
|
||||||
|
|
||||||
|
httpCfg := NewConfig()
|
||||||
|
httpCfg.Network.HTTPProxy = "http://127.0.0.1:8080"
|
||||||
|
httpSession := NewScanSession(httpCfg, NewState(), &FlagVars{})
|
||||||
|
if !httpSession.ProxyEnabled() {
|
||||||
|
t.Fatal("HTTP proxy session should report proxy enabled")
|
||||||
|
}
|
||||||
|
if httpSession.IsSOCKS5Proxy() {
|
||||||
|
t.Fatal("HTTP proxy session should not report SOCKS5")
|
||||||
|
}
|
||||||
|
if !httpSession.ProxyReliable() {
|
||||||
|
t.Fatal("HTTP proxy session should be reliable")
|
||||||
|
}
|
||||||
|
|
||||||
|
socksCfg := NewConfig()
|
||||||
|
socksCfg.Network.Socks5Proxy = "127.0.0.1:1080"
|
||||||
|
socksSession := NewScanSession(socksCfg, NewState(), &FlagVars{})
|
||||||
|
if !socksSession.ProxyEnabled() {
|
||||||
|
t.Fatal("SOCKS5 proxy session should report proxy enabled")
|
||||||
|
}
|
||||||
|
if !socksSession.IsSOCKS5Proxy() {
|
||||||
|
t.Fatal("SOCKS5 proxy session should report SOCKS5")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type roundTripFunc func(*http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return f(req)
|
||||||
|
}
|
||||||
|
|||||||
+5
-5
@@ -176,7 +176,7 @@ func EnhancedPortScan(ctx context.Context, hosts []string, ports string, timeout
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查代理可靠性,如果存在全回显问题则警告
|
// 检查代理可靠性,如果存在全回显问题则警告
|
||||||
if common.IsProxyEnabled() && !common.IsProxyReliable() {
|
if session.ProxyEnabled() && !session.ProxyReliable() {
|
||||||
session.LogError("检测到代理存在全回显问题,端口扫描结果可能不准确")
|
session.LogError("检测到代理存在全回显问题,端口扫描结果可能不准确")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,7 +465,7 @@ func scanSinglePort(ctx context.Context, host string, port int, addr string, ada
|
|||||||
adaptiveTO.Record(time.Since(start))
|
adaptiveTO.Record(time.Since(start))
|
||||||
|
|
||||||
// 步骤1.5:代理连接深度验证(防止透明代理/全回显代理的假连接问题)
|
// 步骤1.5:代理连接深度验证(防止透明代理/全回显代理的假连接问题)
|
||||||
valid, verifyMethod := verifyProxyConnectionDeep(conn, addr)
|
valid, verifyMethod := verifyProxyConnectionDeep(conn, addr, session)
|
||||||
if !valid {
|
if !valid {
|
||||||
session.LogDebug(fmt.Sprintf("代理验证失败 %s: %s", addr, verifyMethod))
|
session.LogDebug(fmt.Sprintf("代理验证失败 %s: %s", addr, verifyMethod))
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
@@ -474,7 +474,7 @@ func scanSinglePort(ctx context.Context, host string, port int, addr string, ada
|
|||||||
|
|
||||||
// 步骤1.6:如果使用了代理且进行了数据交互,需要重建连接
|
// 步骤1.6:如果使用了代理且进行了数据交互,需要重建连接
|
||||||
// 因为验证阶段可能读取了Banner或发送了HTTP GET探测,污染了连接状态
|
// 因为验证阶段可能读取了Banner或发送了HTTP GET探测,污染了连接状态
|
||||||
if common.IsProxyEnabled() && verifyMethod != "direct" {
|
if session.ProxyEnabled() && verifyMethod != "direct" {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
// 重新建立干净的连接用于服务识别
|
// 重新建立干净的连接用于服务识别
|
||||||
conn, err = connectWithRetry(ctx, session, addr, timeout, 2)
|
conn, err = connectWithRetry(ctx, session, addr, timeout, 2)
|
||||||
@@ -523,10 +523,10 @@ func handleConnectionFailure(err error, host string, port int, addr string, fail
|
|||||||
// 1. 快速 Banner 检测 (100ms) - 大部分服务会主动发送数据
|
// 1. 快速 Banner 检测 (100ms) - 大部分服务会主动发送数据
|
||||||
// 2. 轻量探测 (发送 \r\n) - 触发某些服务响应,同时不污染协议状态
|
// 2. 轻量探测 (发送 \r\n) - 触发某些服务响应,同时不污染协议状态
|
||||||
// 3. 短超时等待 (500ms) - 平衡准确性和性能
|
// 3. 短超时等待 (500ms) - 平衡准确性和性能
|
||||||
func verifyProxyConnectionDeep(conn net.Conn, addr string) (bool, string) {
|
func verifyProxyConnectionDeep(conn net.Conn, addr string, session *common.ScanSession) (bool, string) {
|
||||||
// 无代理或SOCKS5代理:跳过深度验证
|
// 无代理或SOCKS5代理:跳过深度验证
|
||||||
// SOCKS5协议层已验证连接可达性,连接成功即端口开放
|
// SOCKS5协议层已验证连接可达性,连接成功即端口开放
|
||||||
if !common.IsProxyEnabled() || common.IsSOCKS5Proxy() {
|
if !session.ProxyEnabled() || session.IsSOCKS5Proxy() {
|
||||||
return true, "direct"
|
return true, "direct"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-5
@@ -136,12 +136,12 @@ func (w *WebPortDetector) DetectHTTPServiceOnly(host string, port int, config *c
|
|||||||
client := createHTTPClient(config, session)
|
client := createHTTPClient(config, session)
|
||||||
|
|
||||||
// 尝试HTTP
|
// 尝试HTTP
|
||||||
if w.tryHTTP(client, host, port, "http") {
|
if w.tryHTTP(client, session, host, port, "http") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试HTTPS
|
// 尝试HTTPS
|
||||||
if w.tryHTTP(client, host, port, "https") {
|
if w.tryHTTP(client, session, host, port, "https") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ func isPortReachable(host string, port int, config *common.Config, session *comm
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tryHTTP 尝试HTTP请求 - 简化的核心逻辑
|
// tryHTTP 尝试HTTP请求 - 简化的核心逻辑
|
||||||
func (w *WebPortDetector) tryHTTP(client *http.Client, host string, port int, protocol string) bool {
|
func (w *WebPortDetector) tryHTTP(client *http.Client, session *common.ScanSession, host string, port int, protocol string) bool {
|
||||||
// 构造URL
|
// 构造URL
|
||||||
var url string
|
var url string
|
||||||
if (port == 80 && protocol == "http") || (port == 443 && protocol == "https") {
|
if (port == 80 && protocol == "http") || (port == 443 && protocol == "https") {
|
||||||
@@ -181,8 +181,7 @@ func (w *WebPortDetector) tryHTTP(client *http.Client, host string, port int, pr
|
|||||||
req.Header.Set("User-Agent", "fscan-web-detector/2.1")
|
req.Header.Set("User-Agent", "fscan-web-detector/2.1")
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
|
|
||||||
// 使用统一的SafeHTTPDo以确保遵循限速策略和代理设置
|
resp, err := session.HTTPDo(client, req)
|
||||||
resp, err := common.SafeHTTPDo(client, req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user