package honeypot import ( "net/http" "net/http/httptest" "strings" "testing" ) func TestMatch(t *testing.T) { h := New(Options{Enabled: true}) hit := []string{ "/wp-admin", "/wp-admin/install.php", "/wp-login.php", "/wp-content/plugins/", "/.ssh/id_rsa", "/.env", "/.git/config", "/.aws/credentials", "/administrator", "/phpmyadmin", "/actuator/env", "/swagger-ui.html", "/xmlrpc.php", "/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/wp-admin/../wp-content/x.php", // 后缀匹配:备份/配置/密钥泄露扫描 "/backup.zip", "/www.zip", "/site.tar.gz", "/db.sql", "/dump.sql", "/x.bak", "/config.php.bak", "/.env.bak", "/id_rsa.pem", "/private.key", "/server.jks", "/data.ovpn", } for _, p := range hit { if !h.Match(p) { t.Errorf("Match(%q) = false, want true", p) } } // 业务路由必须一律不接管 —— 这是蜜罐的红线。 miss := []string{ "/", "/login", "/register", "/logout", "/_/alice", "/user", "/user/settings", "/api/v1/questions", "/static/css/base.css", "/favicon.ico", "/robots.txt", "/about", "/rules", "/sponsor", "/change-logs.html", "/vote/abc", "/explore", "/i18n/en", // 业务 URL 空间里不存在这些后缀 "/_/admin.ini", "/api/v1/questions.json", "/user/settings.yaml", } for _, p := range miss { if h.Match(p) { t.Errorf("Match(%q) = true, want false(业务路由不得被蜜罐接管)", p) } } } func TestMatchDisabled(t *testing.T) { h := New(Options{Enabled: false}) if h.Match("/wp-admin") { t.Error("蜜罐未启用时不应匹配任何路径") } } func TestMatchEncoded(t *testing.T) { h := New(Options{Enabled: true}) // %2f 编码绕过 if !h.Match("/wp-admin%2f..%2fetc") { t.Error("编码后的 wp-admin 路径应被匹配") } // 大小写 if !h.Match("/WP-ADMIN") { t.Error("大小写不同的 WP-ADMIN 应被匹配") } // 查询串不应影响判定 if !h.Match("/wp-login.php?redirect_to=/wp-admin") { t.Error("带查询串的 wp-login.php 应被匹配") } } func TestExtraPaths(t *testing.T) { h := New(Options{Enabled: true, ExtraPaths: []string{"/secret-backdoor"}}) if !h.Match("/secret-backdoor") { t.Error("自定义诱饵路径应被匹配") } if !h.Match("/secret-backdoor/x.php") { t.Error("自定义诱饵路径的子路径应被匹配") } } func TestExtractCredentialsForm(t *testing.T) { h := New(Options{Enabled: true}) req := httptest.NewRequest(http.MethodPost, "/wp-login.php", strings.NewReader("log=admin&pwd=admin123&redirect_to=%2Fwp-admin%2F&wp-submit=Log+In")) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") creds := h.ExtractCredentials(req) if creds.Username != "admin" { t.Errorf("Username = %q, want admin", creds.Username) } if creds.Password != "admin123" { t.Errorf("Password = %q, want admin123", creds.Password) } if !creds.HadFields { t.Error("HadFields = false, want true") } } func TestExtractCredentialsJSON(t *testing.T) { h := New(Options{Enabled: true}) req := httptest.NewRequest(http.MethodPost, "/wp-login.php", strings.NewReader(`{"username":"root","password":"toor","extra":1}`)) req.Header.Set("Content-Type", "application/json") creds := h.ExtractCredentials(req) if creds.Username != "root" { t.Errorf("Username = %q, want root", creds.Username) } if creds.Password != "toor" { t.Errorf("Password = %q, want toor", creds.Password) } } func TestExtractCredentialsQuery(t *testing.T) { h := New(Options{Enabled: true}) req := httptest.NewRequest(http.MethodGet, "/wp-login.php?log=admin&pwd=secret", nil) creds := h.ExtractCredentials(req) if creds.Username != "admin" || creds.Password != "secret" { t.Errorf("got %q/%q, want admin/secret", creds.Username, creds.Password) } } func TestExtractCredentialsBasicAuth(t *testing.T) { h := New(Options{Enabled: true}) req := httptest.NewRequest(http.MethodGet, "/wp-admin", nil) req.SetBasicAuth("admin", "hunter2") creds := h.ExtractCredentials(req) if creds.Username != "admin" || creds.Password != "hunter2" { t.Errorf("got %q/%q, want admin/hunter2", creds.Username, creds.Password) } } func TestExtractCredentialsEmpty(t *testing.T) { h := New(Options{Enabled: true}) req := httptest.NewRequest(http.MethodGet, "/wp-admin", nil) creds := h.ExtractCredentials(req) if creds.HadFields { t.Error("空请求不应标记为携带凭据") } } func TestExtractCredentialsMalformed(t *testing.T) { h := New(Options{Enabled: true}) // 畸形 body 不应 panic req := httptest.NewRequest(http.MethodPost, "/wp-login.php", strings.NewReader("%zz&log=admin&&==")) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") creds := h.ExtractCredentials(req) if creds.Username != "admin" { t.Errorf("Username = %q, want admin(畸形 body 仍应尽量提取)", creds.Username) } }