mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-26 13:11:53 +08:00
fix: 外部审查 8 项修复 + 国密 TLS 按需回退
- UserAgent 默认值回退 + 注册 -ua flag (#2) - README 编译命令 main.go → . (#3) - README 版本号同步 rc.1 (#4) - Client.go gmtls stdout 劫持删除 (#5) - ms17010 smb1GetResponse size<32 越界 panic (#6) - SSH 拨号超时统一 ModuleTimeout (#8) - AddPorts 死字段删除 (#9) - 国密 TLS 按需回退:标准 TLS 握手失败时仅在错误为 cipher/protocol 不兼容时尝试国密,跳过超时/拒绝等连接级错误
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
内网综合扫描工具,一键自动化漏扫。
|
内网综合扫描工具,一键自动化漏扫。
|
||||||
|
|
||||||
**版本**: 2.2.0-rc
|
**版本**: 2.2.0-rc.1
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
@@ -186,10 +186,10 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 标准编译
|
# 标准编译
|
||||||
go build -ldflags="-s -w" -trimpath -o fscan main.go
|
go build -ldflags="-s -w" -trimpath -o fscan .
|
||||||
|
|
||||||
# 带Web管理界面
|
# 带Web管理界面
|
||||||
go build -tags web -ldflags="-s -w" -trimpath -o fscan main.go
|
go build -tags web -ldflags="-s -w" -trimpath -o fscan-web .
|
||||||
```
|
```
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|||||||
+3
-3
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Comprehensive intranet scanning tool for automated vulnerability assessment.
|
Comprehensive intranet scanning tool for automated vulnerability assessment.
|
||||||
|
|
||||||
**Version**: 2.2.0-rc
|
**Version**: 2.2.0-rc.1.1
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -185,10 +185,10 @@ Comprehensive intranet scanning tool for automated vulnerability assessment.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Standard build
|
# Standard build
|
||||||
go build -ldflags="-s -w" -trimpath -o fscan main.go
|
go build -ldflags="-s -w" -trimpath -o fscan .
|
||||||
|
|
||||||
# With Web UI
|
# With Web UI
|
||||||
go build -tags web -ldflags="-s -w" -trimpath -o fscan main.go
|
go build -tags web -ldflags="-s -w" -trimpath -o fscan-web .
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ func Flag(Info *HostInfo) error {
|
|||||||
flag.StringVar(&fv.TargetURL, "u", "", i18n.GetText("flag_target_url"))
|
flag.StringVar(&fv.TargetURL, "u", "", i18n.GetText("flag_target_url"))
|
||||||
flag.StringVar(&fv.URLsFile, "uf", "", i18n.GetText("flag_urls_file"))
|
flag.StringVar(&fv.URLsFile, "uf", "", i18n.GetText("flag_urls_file"))
|
||||||
flag.StringVar(&fv.Cookie, "cookie", "", i18n.GetText("flag_cookie"))
|
flag.StringVar(&fv.Cookie, "cookie", "", i18n.GetText("flag_cookie"))
|
||||||
|
flag.StringVar(&fv.UserAgent, "ua", "", i18n.GetText("flag_user_agent"))
|
||||||
flag.Int64Var(&fv.WebTimeout, "wt", 5, i18n.GetText("flag_web_timeout"))
|
flag.Int64Var(&fv.WebTimeout, "wt", 5, i18n.GetText("flag_web_timeout"))
|
||||||
flag.IntVar(&fv.MaxRedirects, "max-redirect", 10, i18n.GetText("flag_max_redirects"))
|
flag.IntVar(&fv.MaxRedirects, "max-redirect", 10, i18n.GetText("flag_max_redirects"))
|
||||||
flag.StringVar(&fv.HTTPProxy, "proxy", "", i18n.GetText("flag_http_proxy"))
|
flag.StringVar(&fv.HTTPProxy, "proxy", "", i18n.GetText("flag_http_proxy"))
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ type FlagVars struct {
|
|||||||
ExcludeHostsFile string
|
ExcludeHostsFile string
|
||||||
Ports string
|
Ports string
|
||||||
ExcludePorts string
|
ExcludePorts string
|
||||||
AddPorts string
|
|
||||||
HostsFile string
|
HostsFile string
|
||||||
PortsFile string
|
PortsFile string
|
||||||
|
|
||||||
@@ -226,7 +225,7 @@ func BuildConfigFromFlags(fv *FlagVars) *Config {
|
|||||||
},
|
},
|
||||||
HTTP: HTTPConfig{
|
HTTP: HTTPConfig{
|
||||||
Cookie: fv.Cookie,
|
Cookie: fv.Cookie,
|
||||||
UserAgent: fv.UserAgent,
|
UserAgent: defaultUserAgent(fv.UserAgent),
|
||||||
Accept: fv.Accept,
|
Accept: fv.Accept,
|
||||||
},
|
},
|
||||||
LocalExploit: LocalExploitConfig{
|
LocalExploit: LocalExploitConfig{
|
||||||
@@ -246,3 +245,11 @@ func BuildConfigFromFlags(fv *FlagVars) *Config {
|
|||||||
func isStdoutTerminal() bool {
|
func isStdoutTerminal() bool {
|
||||||
return term.IsTerminal(int(os.Stdout.Fd()))
|
return term.IsTerminal(int(os.Stdout.Fd()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defaultUserAgent 用户未通过 -ua 指定时回退到默认 UA,避免发送空 User-Agent 被 WAF 识别
|
||||||
|
func defaultUserAgent(ua string) string {
|
||||||
|
if ua != "" {
|
||||||
|
return ua
|
||||||
|
}
|
||||||
|
return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||||
|
}
|
||||||
|
|||||||
+3
-2
@@ -955,8 +955,9 @@ func TestBuildConfigFromFlags_BoundaryValues(t *testing.T) {
|
|||||||
if cfg.HTTP.Cookie != "" {
|
if cfg.HTTP.Cookie != "" {
|
||||||
t.Errorf("Cookie 应该为空")
|
t.Errorf("Cookie 应该为空")
|
||||||
}
|
}
|
||||||
if cfg.HTTP.UserAgent != "" {
|
// 空输入回退到默认 UA,避免发送空 User-Agent
|
||||||
t.Errorf("UserAgent 应该为空")
|
if cfg.HTTP.UserAgent == "" {
|
||||||
|
t.Errorf("UserAgent 空输入应回退到默认 UA")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ flag_urls_file:
|
|||||||
other: "URLs file"
|
other: "URLs file"
|
||||||
flag_cookie:
|
flag_cookie:
|
||||||
other: "HTTP Cookie"
|
other: "HTTP Cookie"
|
||||||
|
flag_user_agent:
|
||||||
|
other: "Custom User-Agent header"
|
||||||
flag_web_timeout:
|
flag_web_timeout:
|
||||||
other: "Web timeout"
|
other: "Web timeout"
|
||||||
flag_max_redirects:
|
flag_max_redirects:
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ flag_urls_file:
|
|||||||
other: "URL文件"
|
other: "URL文件"
|
||||||
flag_cookie:
|
flag_cookie:
|
||||||
other: "HTTP Cookie"
|
other: "HTTP Cookie"
|
||||||
|
flag_user_agent:
|
||||||
|
other: "自定义 User-Agent 请求头"
|
||||||
flag_web_timeout:
|
flag_web_timeout:
|
||||||
other: "Web超时时间"
|
other: "Web超时时间"
|
||||||
flag_max_redirects:
|
flag_max_redirects:
|
||||||
|
|||||||
+29
-13
@@ -56,19 +56,21 @@ func DetectHTTPSchemeContext(ctx context.Context, host string, port int, config
|
|||||||
return "https"
|
return "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 第二步:尝试国密TLS握手(GM TLS fallback)
|
// 第二步:仅在标准 TLS 握手级别失败(cipher/protocol 不兼容)时尝试国密
|
||||||
gmConn, gmErr := gmtls.DialWithDialer(
|
// 连接级别失败(timeout/refused/非 TLS 端口)不需要尝试
|
||||||
tlsDialer,
|
if maybeGMTLS(err) {
|
||||||
"tcp", addr,
|
gmConn, gmErr := gmtls.DialWithDialer(
|
||||||
&gmtls.Config{
|
tlsDialer,
|
||||||
GMSupport: gmtls.NewGMSupport(),
|
"tcp", addr,
|
||||||
InsecureSkipVerify: true,
|
&gmtls.Config{
|
||||||
},
|
GMSupport: gmtls.NewGMSupport(),
|
||||||
)
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
if gmErr == nil {
|
)
|
||||||
_ = gmConn.Close()
|
if gmErr == nil {
|
||||||
return "https-gm"
|
_ = gmConn.Close()
|
||||||
|
return "https-gm"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLS和GM TLS都失败,尝试HTTP
|
// TLS和GM TLS都失败,尝试HTTP
|
||||||
@@ -491,6 +493,20 @@ func (s *WebScanStrategy) createTargetFromURLWithSession(baseInfo common.HostInf
|
|||||||
return &urlInfo
|
return &urlInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maybeGMTLS 判断标准 TLS 握手错误是否可能是国密服务端
|
||||||
|
// 只有 cipher/protocol 层面的不兼容才值得尝试国密回退
|
||||||
|
// 连接超时、拒绝、非 TLS 端口等连接级错误直接跳过
|
||||||
|
func maybeGMTLS(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s := err.Error()
|
||||||
|
return strings.Contains(s, "handshake failure") ||
|
||||||
|
strings.Contains(s, "protocol version") ||
|
||||||
|
strings.Contains(s, "no mutual") ||
|
||||||
|
strings.Contains(s, "cipher suite")
|
||||||
|
}
|
||||||
|
|
||||||
func hasMalformedURLPort(host string) bool {
|
func hasMalformedURLPort(host string) bool {
|
||||||
if strings.HasPrefix(host, "[") {
|
if strings.HasPrefix(host, "[") {
|
||||||
end := strings.LastIndexByte(host, ']')
|
end := strings.LastIndexByte(host, ']')
|
||||||
|
|||||||
@@ -198,6 +198,10 @@ func smb1GetResponse(conn net.Conn) ([]byte, *smbHeader, error) {
|
|||||||
sizeBuf := make([]byte, 4)
|
sizeBuf := make([]byte, 4)
|
||||||
copy(sizeBuf[1:], buf[1:])
|
copy(sizeBuf[1:], buf[1:])
|
||||||
size := int(binary.BigEndian.Uint32(sizeBuf))
|
size := int(binary.BigEndian.Uint32(sizeBuf))
|
||||||
|
// 畸形响应(size < SMB 头长度)会导致后续 buf[:smbHeaderSize] 越界 panic
|
||||||
|
if size < smbHeaderSize {
|
||||||
|
return nil, nil, fmt.Errorf("SMB1 response too short: %d bytes", size)
|
||||||
|
}
|
||||||
// SMB
|
// SMB
|
||||||
buf = make([]byte, size)
|
buf = make([]byte, size)
|
||||||
_, err = io.ReadFull(conn, buf)
|
_, err = io.ReadFull(conn, buf)
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 建立TCP连接
|
// 建立TCP连接
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, moduleTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &AuthResult{
|
return &AuthResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
@@ -277,7 +277,7 @@ func (p *SSHPlugin) scanWithKey(ctx context.Context, info *common.HostInfo, sess
|
|||||||
func (p *SSHPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *SSHPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
|
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{
|
return &ScanResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
|
|||||||
+1
-14
@@ -11,7 +11,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
@@ -32,8 +31,6 @@ const (
|
|||||||
ProxySocks5URL = "socks5://127.0.0.1:1080"
|
ProxySocks5URL = "socks5://127.0.0.1:1080"
|
||||||
)
|
)
|
||||||
|
|
||||||
var gmtlsStdoutMu sync.Mutex
|
|
||||||
|
|
||||||
// 全局HTTP客户端变量
|
// 全局HTTP客户端变量
|
||||||
var (
|
var (
|
||||||
Client *http.Client // 标准HTTP客户端
|
Client *http.Client // 标准HTTP客户端
|
||||||
@@ -205,20 +202,10 @@ func InitHTTPClient(ThreadsNum int, DownProxy string, Timeout time.Duration, max
|
|||||||
Timeout: dialTimeout,
|
Timeout: dialTimeout,
|
||||||
KeepAlive: keepAlive,
|
KeepAlive: keepAlive,
|
||||||
}
|
}
|
||||||
// 抑制 gmtls 库的 fmt.Println("handshake error") 噪声
|
return gmtls.DialWithDialer(dialer, network, addr, &gmtls.Config{
|
||||||
gmtlsStdoutMu.Lock()
|
|
||||||
orig := os.Stdout
|
|
||||||
if devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0); err == nil {
|
|
||||||
os.Stdout = devNull
|
|
||||||
defer devNull.Close()
|
|
||||||
}
|
|
||||||
conn, err := gmtls.DialWithDialer(dialer, network, addr, &gmtls.Config{
|
|
||||||
GMSupport: gmtls.NewGMSupport(),
|
GMSupport: gmtls.NewGMSupport(),
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
})
|
})
|
||||||
os.Stdout = orig
|
|
||||||
gmtlsStdoutMu.Unlock()
|
|
||||||
return conn, err
|
|
||||||
},
|
},
|
||||||
MaxConnsPerHost: 20,
|
MaxConnsPerHost: 20,
|
||||||
MaxIdleConns: 20,
|
MaxIdleConns: 20,
|
||||||
|
|||||||
+14
-2
@@ -508,8 +508,9 @@ func DoRequest(req *http.Request, redirect bool, session *common.ScanSession) (*
|
|||||||
oResp, err = requestClient(false).Do(req)
|
oResp, err = requestClient(false).Do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标准TLS连接失败时,尝试国密TLS客户端
|
// 标准TLS握手级别失败时,尝试国密TLS客户端
|
||||||
if err != nil && req.URL.Scheme == "https" {
|
// 跳过连接超时、拒绝等非 TLS 相关错误,避免无意义的国密握手尝试
|
||||||
|
if err != nil && req.URL.Scheme == "https" && maybeGMTLSError(err) {
|
||||||
if req.GetBody != nil {
|
if req.GetBody != nil {
|
||||||
if body, bodyErr := req.GetBody(); bodyErr == nil {
|
if body, bodyErr := req.GetBody(); bodyErr == nil {
|
||||||
req.Body = body
|
req.Body = body
|
||||||
@@ -682,3 +683,14 @@ func getRespBody(oResp *http.Response) ([]byte, error) {
|
|||||||
|
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func maybeGMTLSError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s := err.Error()
|
||||||
|
return strings.Contains(s, "handshake failure") ||
|
||||||
|
strings.Contains(s, "protocol version") ||
|
||||||
|
strings.Contains(s, "no mutual") ||
|
||||||
|
strings.Contains(s, "cipher suite")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1238,7 +1238,7 @@ func TestDoRequestSkipsNilGMTLSFallback(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
ClientNoRedirect = &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
|
ClientNoRedirect = &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
|
||||||
return nil, errors.New("standard tls failed")
|
return nil, errors.New("tls: handshake failure")
|
||||||
})}
|
})}
|
||||||
ClientNoRedirectGM = nil
|
ClientNoRedirectGM = nil
|
||||||
|
|
||||||
@@ -1261,7 +1261,7 @@ func TestDoRequestReplaysBodyForGMTLSFallback(t *testing.T) {
|
|||||||
|
|
||||||
ClientNoRedirect = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
ClientNoRedirect = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
_, _ = io.ReadAll(req.Body)
|
_, _ = io.ReadAll(req.Body)
|
||||||
return nil, errors.New("standard tls failed")
|
return nil, errors.New("tls: handshake failure")
|
||||||
})}
|
})}
|
||||||
|
|
||||||
var gotBody string
|
var gotBody string
|
||||||
|
|||||||
Reference in New Issue
Block a user