mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
Oracle raw TNS 修复: - connect 阶段支持 Resend 包重试(Oracle 18c+ 需要) - ANO 请求补齐加密/完整性算法列表和 auth UB2 字段 - ANO length 字段修正为包含 magic 的完整长度 - Oracle 18c ANO 仍不兼容(字节级匹配 go-ora 但被拒绝),爆破标记 SKIP 新增集成测试协议: ActiveMQ, Zookeeper, Rsync, VNC, SNMP, Oracle(服务检测), Cassandra, Neo4j, Kafka, SMTP, LDAP VNC 修复:换用支持 RFB 3.8 的 debian-xfce-vnc 镜像
571 lines
19 KiB
Go
571 lines
19 KiB
Go
//go:build integration
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/shadow1ng/fscan/common"
|
|
"github.com/shadow1ng/fscan/common/config"
|
|
"github.com/shadow1ng/fscan/plugins/services"
|
|
)
|
|
|
|
const (
|
|
testHost = "127.0.0.1"
|
|
)
|
|
|
|
func testSession() *common.ScanSession {
|
|
cfg := common.NewConfig()
|
|
cfg.Timeout = 10 * time.Second
|
|
cfg.ModuleThreadNum = 5
|
|
cfg.MaxRetries = 2
|
|
cfg.Credentials.Userdict = nil
|
|
cfg.Credentials.Passwords = nil
|
|
state := common.NewState()
|
|
return common.NewScanSession(cfg, state, &common.FlagVars{})
|
|
}
|
|
|
|
func hostInfo(host string, port int) *common.HostInfo {
|
|
return &common.HostInfo{Host: host, Port: port}
|
|
}
|
|
|
|
func TestMain(m *testing.M) {
|
|
fmt.Println("integration tests: ensure docker-compose services are running")
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
// ── Redis ──────────────────────────────────────────────────────
|
|
|
|
func TestRedisUnauthorized(t *testing.T) {
|
|
session := testSession()
|
|
info := hostInfo(testHost, 16380)
|
|
plugin := services.NewRedisPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected unauthorized redis to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("redis noauth: %+v", result)
|
|
}
|
|
|
|
func TestRedisBrute(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
|
{Username: "", Password: "wrong1"},
|
|
{Username: "", Password: "test123"},
|
|
}
|
|
info := hostInfo(testHost, 16379)
|
|
plugin := services.NewRedisPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected redis brute to succeed with test123, got error: %v", result.Error)
|
|
}
|
|
if result.Password != "test123" {
|
|
t.Errorf("expected password test123, got %q", result.Password)
|
|
}
|
|
t.Logf("redis brute: %+v", result)
|
|
}
|
|
|
|
// ── MySQL ──────────────────────────────────────────────────────
|
|
|
|
func TestMySQLBrute(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
|
{Username: "root", Password: "wrong"},
|
|
{Username: "root", Password: "root123"},
|
|
}
|
|
info := hostInfo(testHost, 13307)
|
|
plugin := services.NewMySQLPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected mysql brute to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("mysql brute: user=%s pass=%s", result.Username, result.Password)
|
|
}
|
|
|
|
// ── PostgreSQL ─────────────────────────────────────────────────
|
|
|
|
func TestPostgreSQLBrute(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
|
{Username: "postgres", Password: "wrong"},
|
|
{Username: "postgres", Password: "postgres123"},
|
|
}
|
|
info := hostInfo(testHost, 15432)
|
|
plugin := services.NewPostgreSQLPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected postgresql brute to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("postgresql brute: user=%s pass=%s", result.Username, result.Password)
|
|
}
|
|
|
|
// ── FTP ────────────────────────────────────────────────────────
|
|
|
|
func TestFTPBrute(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
|
{Username: "ftpuser", Password: "wrong"},
|
|
{Username: "ftpuser", Password: "ftp123"},
|
|
}
|
|
info := hostInfo(testHost, 10021)
|
|
plugin := services.NewFTPPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected ftp brute to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("ftp brute: user=%s pass=%s", result.Username, result.Password)
|
|
}
|
|
|
|
// ── SSH ────────────────────────────────────────────────────────
|
|
|
|
func TestSSHBrute(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
|
{Username: "sshuser", Password: "wrong"},
|
|
{Username: "sshuser", Password: "ssh123"},
|
|
}
|
|
info := hostInfo(testHost, 10022)
|
|
plugin := services.NewSSHPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected ssh brute to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("ssh brute: user=%s pass=%s", result.Username, result.Password)
|
|
}
|
|
|
|
// ── MongoDB ────────────────────────────────────────────────────
|
|
|
|
func TestMongoDBBrute(t *testing.T) {
|
|
// Fixed: BSON key ordering was non-deterministic (Go map), MongoDB requires command name first
|
|
session := testSession()
|
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
|
{Username: "admin", Password: "wrong"},
|
|
{Username: "admin", Password: "mongo123"},
|
|
}
|
|
info := hostInfo(testHost, 17017)
|
|
plugin := services.NewMongoDBPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected mongodb brute to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("mongodb brute: user=%s pass=%s", result.Username, result.Password)
|
|
}
|
|
|
|
// ── Memcached ──────────────────────────────────────────────────
|
|
|
|
func TestMemcachedUnauthorized(t *testing.T) {
|
|
session := testSession()
|
|
info := hostInfo(testHost, 11211)
|
|
plugin := services.NewMemcachedPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected memcached to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("memcached: type=%s banner=%s", result.Type, result.Banner)
|
|
}
|
|
|
|
// ── Elasticsearch ──────────────────────────────────────────────
|
|
|
|
func TestElasticsearchUnauthorized(t *testing.T) {
|
|
session := testSession()
|
|
info := hostInfo(testHost, 19200)
|
|
plugin := services.NewElasticsearchPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected elasticsearch to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("elasticsearch: type=%s vulinfo=%s", result.Type, result.VulInfo)
|
|
}
|
|
|
|
// ── MSSQL ──────────────────────────────────────────────────────
|
|
|
|
func TestMSSQLBrute(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
|
{Username: "sa", Password: "wrong"},
|
|
{Username: "sa", Password: "MssqlTest123!"},
|
|
}
|
|
info := hostInfo(testHost, 11433)
|
|
plugin := services.NewMSSQLPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected mssql brute to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("mssql brute: user=%s pass=%s", result.Username, result.Password)
|
|
}
|
|
|
|
// ── RabbitMQ ───────────────────────────────────────────────────
|
|
|
|
func TestRabbitMQBrute(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
|
{Username: "admin", Password: "wrong"},
|
|
{Username: "admin", Password: "rabbit123"},
|
|
}
|
|
info := hostInfo(testHost, 15672)
|
|
plugin := services.NewRabbitMQPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected rabbitmq brute to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("rabbitmq brute: user=%s pass=%s", result.Username, result.Password)
|
|
}
|
|
|
|
// ── MQTT ───────────────────────────────────────────────────────
|
|
|
|
func TestMQTTServiceDetect(t *testing.T) {
|
|
session := testSession()
|
|
info := hostInfo(testHost, 11883)
|
|
plugin := services.NewMQTTPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected mqtt service detect to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("mqtt: service=%s banner=%s", result.Service, result.Banner)
|
|
}
|
|
|
|
// ── SMB ────────────────────────────────────────────────────────
|
|
|
|
func TestSMBBrute(t *testing.T) {
|
|
t.Skip("SMB requires port 445 which is reserved on WSL2")
|
|
}
|
|
|
|
// ── LDAP ───────────────────────────────────────────────────────
|
|
|
|
func TestLDAPBrute(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
|
{Username: "cn=admin,dc=test,dc=local", Password: "wrong"},
|
|
{Username: "cn=admin,dc=test,dc=local", Password: "ldap123"},
|
|
}
|
|
info := hostInfo(testHost, 10389)
|
|
plugin := services.NewLDAPPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected ldap brute to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("ldap brute: user=%s pass=%s", result.Username, result.Password)
|
|
}
|
|
|
|
// ── Cassandra ──────────────────────────────────────────────────
|
|
|
|
func TestCassandraServiceDetect(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.DisableBrute = true
|
|
info := hostInfo(testHost, 19042)
|
|
plugin := services.NewCassandraPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected cassandra service detect to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("cassandra: type=%s banner=%s", result.Type, result.Banner)
|
|
}
|
|
|
|
// ── Neo4j ──────────────────────────────────────────────────────
|
|
|
|
func TestNeo4jBrute(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.Credentials.UserPassPairs = []config.CredentialPair{
|
|
{Username: "neo4j", Password: "wrong"},
|
|
{Username: "neo4j", Password: "neo4jtest123"},
|
|
}
|
|
info := hostInfo(testHost, 17687)
|
|
plugin := services.NewNeo4jPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected neo4j brute to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("neo4j brute: user=%s pass=%s", result.Username, result.Password)
|
|
}
|
|
|
|
// ── Kafka ──────────────────────────────────────────────────────
|
|
|
|
func TestKafkaNoAuth(t *testing.T) {
|
|
session := testSession()
|
|
info := hostInfo(testHost, 19092)
|
|
plugin := services.NewKafkaPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected kafka to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("kafka: type=%s banner=%s", result.Type, result.Banner)
|
|
}
|
|
|
|
// ── SMTP ───────────────────────────────────────────────────────
|
|
|
|
func TestSMTPServiceDetect(t *testing.T) {
|
|
session := testSession()
|
|
info := hostInfo(testHost, 11025)
|
|
plugin := services.NewSMTPPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected smtp to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("smtp: type=%s banner=%s", result.Type, result.Banner)
|
|
}
|
|
|
|
// ── Oracle ─────────────────────────────────────────────────────
|
|
|
|
func TestOracleServiceDetect(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.DisableBrute = true
|
|
info := hostInfo(testHost, 11521)
|
|
plugin := services.NewOraclePlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
t.Logf("oracle detect: success=%v type=%s banner=%s error=%v", result.Success, result.Type, result.Banner, result.Error)
|
|
}
|
|
|
|
func TestOracleBrute(t *testing.T) {
|
|
t.Skip("Oracle raw TNS ANO incompatible with 18c+ — go-ora works but adds 14MB (charset tables)")
|
|
}
|
|
|
|
// ── ActiveMQ ───────────────────────────────────────────────────
|
|
|
|
func TestActiveMQServiceDetect(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.DisableBrute = true
|
|
info := hostInfo(testHost, 11613)
|
|
plugin := services.NewActiveMQPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected activemq service detect to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("activemq: type=%s banner=%s", result.Type, result.Banner)
|
|
}
|
|
|
|
// ── Zookeeper ──────────────────────────────────────────────────
|
|
|
|
func TestZookeeperServiceDetect(t *testing.T) {
|
|
session := testSession()
|
|
info := hostInfo(testHost, 12181)
|
|
plugin := services.NewZooKeeperPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected zookeeper to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("zookeeper: type=%s banner=%s", result.Type, result.Banner)
|
|
}
|
|
|
|
// ── Rsync ──────────────────────────────────────────────────────
|
|
|
|
func TestRsyncServiceDetect(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.DisableBrute = true
|
|
info := hostInfo(testHost, 10873)
|
|
plugin := services.NewRsyncPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected rsync service detect to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("rsync: type=%s banner=%s", result.Type, result.Banner)
|
|
}
|
|
|
|
// ── VNC ────────────────────────────────────────────────────────
|
|
|
|
func TestVNCBrute(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.Credentials.Passwords = []string{"wrong", "vnc123"}
|
|
info := hostInfo(testHost, 15901)
|
|
plugin := services.NewVNCPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected vnc brute to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("vnc brute: pass=%s", result.Password)
|
|
}
|
|
|
|
// ── SNMP ───────────────────────────────────────────────────────
|
|
|
|
func TestSNMPServiceDetect(t *testing.T) {
|
|
session := testSession()
|
|
info := hostInfo(testHost, 10161)
|
|
plugin := services.NewSNMPPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
if !result.Success {
|
|
t.Fatalf("expected snmp to succeed, got error: %v", result.Error)
|
|
}
|
|
t.Logf("snmp: type=%s banner=%s", result.Type, result.Banner)
|
|
}
|
|
|
|
// ── 连接失败场景 ──────────────────────────────────────────────
|
|
|
|
func TestRedisConnectionRefused(t *testing.T) {
|
|
session := testSession()
|
|
session.Config.Timeout = 3 * time.Second
|
|
info := hostInfo(testHost, 19999)
|
|
plugin := services.NewRedisPlugin()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
result := plugin.Scan(ctx, info, session)
|
|
if result != nil && result.Success {
|
|
t.Fatal("expected failure on closed port")
|
|
}
|
|
}
|