fix: support SOCKS5 proxy auth

Fixes #585
This commit is contained in:
ZacharyZcR
2026-06-01 01:59:08 +08:00
parent 40b0eee654
commit bfc0e0fb52
6 changed files with 169 additions and 21 deletions
+2
View File
@@ -215,6 +215,8 @@ yay -S fscan-git
`fscan.exe -h 192.168.x.x -p80 -proxy http://127.0.0.1:8080`
![](image/2020-12-12-13-34-44.png)
`fscan.exe -h 192.168.x.x -socks5 socks5://user:[email protected]:1080` (SOCKS5认证代理)
`fscan.exe -h 192.168.x.x -p 139 -m netbios`
![](image/netbios.png)
+2
View File
@@ -214,6 +214,8 @@ yay -S fscan-git
`fscan.exe -h 192.168.x.x -p80 -proxy http://127.0.0.1:8080`
![](image/2020-12-12-13-34-44.png)
`fscan.exe -h 192.168.x.x -socks5 socks5://user:[email protected]:1080` (SOCKS5 proxy with authentication)
`fscan.exe -h 192.168.x.x -p 139 -m netbios`
![](image/netbios.png)
+1 -1
View File
@@ -71,7 +71,7 @@ flag_max_redirects:
flag_http_proxy:
other: "HTTP proxy"
flag_socks5_proxy:
other: "Use SOCKS5 proxy (e.g.: 127.0.0.1:1080)"
other: "Use SOCKS5 proxy (e.g.: 127.0.0.1:1080 or socks5://user:[email protected]:1080)"
flag_iface:
other: "Specify local interface IP address (VPN scenario, e.g.: 10.8.0.5)"
flag_poc_path:
+1 -1
View File
@@ -71,7 +71,7 @@ flag_max_redirects:
flag_http_proxy:
other: "HTTP代理"
flag_socks5_proxy:
other: "使用SOCKS5代理 (如: 127.0.0.1:1080)"
other: "使用SOCKS5代理 (如: 127.0.0.1:1080 或 socks5://user:[email protected]:1080)"
flag_iface:
other: "指定本地网卡IP地址 (VPN场景,如: 10.8.0.5)"
flag_poc_path:
+7 -19
View File
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"net"
"net/url"
"sync"
"sync/atomic"
"time"
@@ -156,6 +155,9 @@ func (m *manager) createDirectDialer() Dialer {
func (m *manager) createSOCKS5Dialer() (Dialer, error) {
// 检查缓存
cacheKey := fmt.Sprintf(CacheKeySOCKS5, m.config.Address)
if m.config.Username != "" || m.config.Password != "" {
cacheKey = fmt.Sprintf("%s_%s_%s", cacheKey, m.config.Username, m.config.Password)
}
m.cacheMu.RLock()
if time.Now().Before(m.cacheExpiry) {
if cached, exists := m.dialerCache[cacheKey]; exists {
@@ -165,18 +167,6 @@ func (m *manager) createSOCKS5Dialer() (Dialer, error) {
}
m.cacheMu.RUnlock()
// 解析代理地址
proxyURL := fmt.Sprintf(SOCKS5URLFormat, m.config.Address)
if m.config.Username != "" {
proxyURL = fmt.Sprintf(SOCKS5URLAuthFormat,
m.config.Username, m.config.Password, m.config.Address)
}
u, err := url.Parse(proxyURL)
if err != nil {
return nil, NewProxyError(ErrTypeConfig, ErrMsgSOCKS5ParseFailed, ErrCodeSOCKS5ParseFailed, err)
}
// 创建基础拨号器
baseDial := &net.Dialer{
Timeout: m.config.Timeout,
@@ -185,16 +175,14 @@ func (m *manager) createSOCKS5Dialer() (Dialer, error) {
// 创建SOCKS5拨号器
var auth *proxy.Auth
if u.User != nil {
if m.config.Username != "" || m.config.Password != "" {
auth = &proxy.Auth{
User: u.User.Username(),
}
if password, hasPassword := u.User.Password(); hasPassword {
auth.Password = password
User: m.config.Username,
Password: m.config.Password,
}
}
socksDialer, err := proxy.SOCKS5(NetworkTCP, u.Host, auth, baseDial)
socksDialer, err := proxy.SOCKS5(NetworkTCP, m.config.Address, auth, baseDial)
if err != nil {
return nil, NewProxyError(ErrTypeConnection, ErrMsgSOCKS5CreateFailed, ErrCodeSOCKS5CreateFailed, err)
}
+156
View File
@@ -1,6 +1,9 @@
package proxy
import (
"fmt"
"io"
"net"
"testing"
"time"
)
@@ -242,6 +245,73 @@ func TestGetDialer_HTTPS(t *testing.T) {
t.Logf("✓ GetDialer 返回HTTPS代理拨号器")
}
func TestGetDialer_SOCKS5AuthSpecialChars(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen failed: %v", err)
}
defer ln.Close()
type credential struct {
user string
pass string
}
authCh := make(chan credential, 1)
errCh := make(chan error, 1)
go func() {
conn, err := ln.Accept()
if err != nil {
errCh <- err
return
}
defer conn.Close()
user, pass, err := handleTestSOCKS5Auth(conn)
if err != nil {
errCh <- err
return
}
authCh <- credential{user: user, pass: pass}
}()
origProbed := IsProxyProbed()
SetProxyProbed(true)
defer SetProxyProbed(origProbed)
config := &ProxyConfig{
Type: ProxyTypeSOCKS5,
Address: ln.Addr().String(),
Username: "user",
Password: "p@ss:word#1",
Timeout: time.Second,
}
manager := NewProxyManager(config)
dialer, err := manager.GetDialer()
if err != nil {
t.Fatalf("GetDialer failed: %v", err)
}
conn, err := dialer.Dial("tcp", "127.0.0.1:80")
if err != nil {
t.Fatalf("SOCKS5 dial failed: %v", err)
}
_ = conn.Close()
select {
case got := <-authCh:
if got.user != config.Username || got.pass != config.Password {
t.Fatalf("auth = %q/%q, want %q/%q", got.user, got.pass, config.Username, config.Password)
}
case err := <-errCh:
t.Fatalf("SOCKS5 test server failed: %v", err)
case <-time.After(time.Second):
t.Fatal("timed out waiting for SOCKS5 auth")
}
t.Logf("✓ SOCKS5认证支持特殊字符密码")
}
// =============================================================================
// GetTLSDialer - TLS拨号器获取测试
// =============================================================================
@@ -559,3 +629,89 @@ func TestDirectDialer_LocalAddr_Loopback(t *testing.T) {
t.Logf("✓ LocalAddr 绑定正常工作(连接失败是预期的): %v", err)
}
}
func handleTestSOCKS5Auth(conn net.Conn) (string, string, error) {
header := make([]byte, 2)
if _, err := io.ReadFull(conn, header); err != nil {
return "", "", err
}
if header[0] != 0x05 {
return "", "", fmt.Errorf("unexpected socks version: %d", header[0])
}
methods := make([]byte, int(header[1]))
if _, err := io.ReadFull(conn, methods); err != nil {
return "", "", err
}
hasAuth := false
for _, method := range methods {
if method == 0x02 {
hasAuth = true
break
}
}
if !hasAuth {
return "", "", fmt.Errorf("client did not offer username/password auth")
}
if _, err := conn.Write([]byte{0x05, 0x02}); err != nil {
return "", "", err
}
authHeader := make([]byte, 2)
if _, err := io.ReadFull(conn, authHeader); err != nil {
return "", "", err
}
if authHeader[0] != 0x01 {
return "", "", fmt.Errorf("unexpected auth version: %d", authHeader[0])
}
userBytes := make([]byte, int(authHeader[1]))
if _, err := io.ReadFull(conn, userBytes); err != nil {
return "", "", err
}
passLen := make([]byte, 1)
if _, err := io.ReadFull(conn, passLen); err != nil {
return "", "", err
}
passBytes := make([]byte, int(passLen[0]))
if _, err := io.ReadFull(conn, passBytes); err != nil {
return "", "", err
}
if _, err := conn.Write([]byte{0x01, 0x00}); err != nil {
return "", "", err
}
reqHeader := make([]byte, 4)
if _, err := io.ReadFull(conn, reqHeader); err != nil {
return "", "", err
}
if reqHeader[0] != 0x05 || reqHeader[1] != 0x01 {
return "", "", fmt.Errorf("unexpected request header: %v", reqHeader)
}
if err := discardSOCKS5Address(conn, reqHeader[3]); err != nil {
return "", "", err
}
if _, err := conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}); err != nil {
return "", "", err
}
return string(userBytes), string(passBytes), nil
}
func discardSOCKS5Address(conn net.Conn, atyp byte) error {
switch atyp {
case 0x01:
_, err := io.CopyN(io.Discard, conn, 6)
return err
case 0x03:
length := make([]byte, 1)
if _, err := io.ReadFull(conn, length); err != nil {
return err
}
_, err := io.CopyN(io.Discard, conn, int64(length[0])+2)
return err
case 0x04:
_, err := io.CopyN(io.Discard, conn, 18)
return err
default:
return fmt.Errorf("unsupported atyp: %d", atyp)
}
}