mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-27 05:31:53 +08:00
@@ -215,6 +215,8 @@ yay -S fscan-git
|
|||||||
`fscan.exe -h 192.168.x.x -p80 -proxy http://127.0.0.1:8080`
|
`fscan.exe -h 192.168.x.x -p80 -proxy http://127.0.0.1:8080`
|
||||||

|

|
||||||
|
|
||||||
|
`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`
|
`fscan.exe -h 192.168.x.x -p 139 -m netbios`
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -214,6 +214,8 @@ yay -S fscan-git
|
|||||||
`fscan.exe -h 192.168.x.x -p80 -proxy http://127.0.0.1:8080`
|
`fscan.exe -h 192.168.x.x -p80 -proxy http://127.0.0.1:8080`
|
||||||

|

|
||||||
|
|
||||||
|
`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`
|
`fscan.exe -h 192.168.x.x -p 139 -m netbios`
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ flag_max_redirects:
|
|||||||
flag_http_proxy:
|
flag_http_proxy:
|
||||||
other: "HTTP proxy"
|
other: "HTTP proxy"
|
||||||
flag_socks5_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:
|
flag_iface:
|
||||||
other: "Specify local interface IP address (VPN scenario, e.g.: 10.8.0.5)"
|
other: "Specify local interface IP address (VPN scenario, e.g.: 10.8.0.5)"
|
||||||
flag_poc_path:
|
flag_poc_path:
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ flag_max_redirects:
|
|||||||
flag_http_proxy:
|
flag_http_proxy:
|
||||||
other: "HTTP代理"
|
other: "HTTP代理"
|
||||||
flag_socks5_proxy:
|
flag_socks5_proxy:
|
||||||
other: "使用SOCKS5代理 (如: 127.0.0.1:1080)"
|
other: "使用SOCKS5代理 (如: 127.0.0.1:1080 或 socks5://user:[email protected]:1080)"
|
||||||
flag_iface:
|
flag_iface:
|
||||||
other: "指定本地网卡IP地址 (VPN场景,如: 10.8.0.5)"
|
other: "指定本地网卡IP地址 (VPN场景,如: 10.8.0.5)"
|
||||||
flag_poc_path:
|
flag_poc_path:
|
||||||
|
|||||||
+7
-19
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -156,6 +155,9 @@ func (m *manager) createDirectDialer() Dialer {
|
|||||||
func (m *manager) createSOCKS5Dialer() (Dialer, error) {
|
func (m *manager) createSOCKS5Dialer() (Dialer, error) {
|
||||||
// 检查缓存
|
// 检查缓存
|
||||||
cacheKey := fmt.Sprintf(CacheKeySOCKS5, m.config.Address)
|
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()
|
m.cacheMu.RLock()
|
||||||
if time.Now().Before(m.cacheExpiry) {
|
if time.Now().Before(m.cacheExpiry) {
|
||||||
if cached, exists := m.dialerCache[cacheKey]; exists {
|
if cached, exists := m.dialerCache[cacheKey]; exists {
|
||||||
@@ -165,18 +167,6 @@ func (m *manager) createSOCKS5Dialer() (Dialer, error) {
|
|||||||
}
|
}
|
||||||
m.cacheMu.RUnlock()
|
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{
|
baseDial := &net.Dialer{
|
||||||
Timeout: m.config.Timeout,
|
Timeout: m.config.Timeout,
|
||||||
@@ -185,16 +175,14 @@ func (m *manager) createSOCKS5Dialer() (Dialer, error) {
|
|||||||
|
|
||||||
// 创建SOCKS5拨号器
|
// 创建SOCKS5拨号器
|
||||||
var auth *proxy.Auth
|
var auth *proxy.Auth
|
||||||
if u.User != nil {
|
if m.config.Username != "" || m.config.Password != "" {
|
||||||
auth = &proxy.Auth{
|
auth = &proxy.Auth{
|
||||||
User: u.User.Username(),
|
User: m.config.Username,
|
||||||
}
|
Password: m.config.Password,
|
||||||
if password, hasPassword := u.User.Password(); hasPassword {
|
|
||||||
auth.Password = password
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
socksDialer, err := proxy.SOCKS5(NetworkTCP, u.Host, auth, baseDial)
|
socksDialer, err := proxy.SOCKS5(NetworkTCP, m.config.Address, auth, baseDial)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewProxyError(ErrTypeConnection, ErrMsgSOCKS5CreateFailed, ErrCodeSOCKS5CreateFailed, err)
|
return nil, NewProxyError(ErrTypeConnection, ErrMsgSOCKS5CreateFailed, ErrCodeSOCKS5CreateFailed, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -242,6 +245,73 @@ func TestGetDialer_HTTPS(t *testing.T) {
|
|||||||
t.Logf("✓ GetDialer 返回HTTPS代理拨号器")
|
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拨号器获取测试
|
// GetTLSDialer - TLS拨号器获取测试
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -559,3 +629,89 @@ func TestDirectDialer_LocalAddr_Loopback(t *testing.T) {
|
|||||||
t.Logf("✓ LocalAddr 绑定正常工作(连接失败是预期的): %v", err)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user