package honeypot import "strings" // Family 是诱饵页面模拟的产品类别。 // // 为什么要分类:扫描器与爆破脚本几乎都是「先按路径判断这是什么后台, // 再用它认识的字段名去填表」。所有路径都回同一个 WordPress 页面,等于在 // /phpmyadmin/index.php 上告诉对方「这里不是 PMA」—— 对方直接换目标, // 我们既拿不到凭据,也留不下有价值的取证。 // // 刻意只做「登录页」这一层:不需要真实的业务逻辑,只需要让对方向表单提交。 type Family int const ( // FamilyGeneric 是兜底:/admin、/console 这类通用后台。 FamilyGeneric Family = iota FamilyWordPress FamilyPhpMyAdmin FamilyTomcat FamilyJenkins FamilyGrafana FamilyGitLab FamilyLuCI FamilyJoomla ) func (f Family) String() string { switch f { case FamilyWordPress: return "wordpress" case FamilyPhpMyAdmin: return "phpmyadmin" case FamilyTomcat: return "tomcat" case FamilyJenkins: return "jenkins" case FamilyGrafana: return "grafana" case FamilyGitLab: return "gitlab" case FamilyLuCI: return "luci" case FamilyJoomla: return "joomla" default: return "generic" } } // skinField 是登录表单里的一个输入框。 type skinField struct { name string label string secret bool } // loginSkin 是一个家族的「伪装设定」:外观、字段名、失败文案、响应头。 // // 全部是编译期常量,渲染时只做一次字符串拼接 —— 没有模板解析、没有文件 IO。 type loginSkin struct { family Family routes []string // 段前缀,命中即归入该家族(顺序敏感,先精确后泛化) title string brand string tagline string // 品牌下方的版本行,扫描器常拿它判断产品 submit string class string form string // 表单 name/id gen string // ,空则不输出 css string // 家族配色(叠加在 baseLoginCSS 之后) fields []skinField hidden [][2]string // 值写成 "$redirect" 表示回填原始查询串 failure string footer string headers [][2]string cookies []string // basicRealm 非空时改用 HTTP 401 + Basic Auth(Tomcat Manager 的真实行为)。 // 凭据从 Authorization 头取,ExtractCredentials 已支持。 basicRealm string } // skins 按顺序匹配:越具体的路径要排在越前面。 // // 所有 routes 都必须与业务路由零交集(route 层的 forbiddenPrefixes 是第二道保险)。 var skins = []loginSkin{ { family: FamilyWordPress, routes: []string{ "/wp-login.php", "/wp-login", "/wp-admin", "/wp-content", "/wp-includes", "/wp-json", "/xmlrpc.php", "/xmlrpc", "/wp-cron.php", "/wp-trackback.php", "/wp-comments-post.php", "/wp-config", "/wp-mail.php", "/wp-signup.php", "/wp-activate.php", "/wp-links-opml.php", "/wp-blog-header.php", "/wp-load.php", "/wp-settings.php", }, title: LoginPageTitle, brand: "WordPress", tagline: "", submit: "Log In", class: "login", form: "loginform", gen: "WordPress 6.4.3", css: `h1 a{color:#3c434a;text-decoration:none}button{background:#2271b1}`, fields: []skinField{ {name: "log", label: "Username"}, {name: "pwd", label: "Password", secret: true}, }, hidden: [][2]string{ {"redirect_to", "$redirect"}, {"testcookie", "1"}, }, failure: `ERROR: The password you entered for the username is incorrect.`, // 真实 wp-login.php 底部没有 404 文案,只有「← 返回站点」; // 之前挂 NotFoundFooter 反而在一张登录页上写了「资源不存在」,很扎眼。 footer: "", headers: [][2]string{ {"X-Powered-By", "PHP/7.4.33"}, {"X-Pingback", "$base/xmlrpc.php"}, }, cookies: []string{"wordpress_test_cookie=WP%20Cookie%20check; path=/"}, }, { family: FamilyPhpMyAdmin, routes: []string{"/phpmyadmin", "/pma", "/myadmin", "/dbadmin", "/sqladmin", "/mysqladmin", "/adminer.php", "/adminer"}, title: "phpMyAdmin", brand: "phpMyAdmin", tagline: "Welcome to phpMyAdmin 5.2.1", submit: "Log in", class: "pma", form: "login_form", gen: "", css: `.card{width:360px}h1{color:#235a81;font-weight:700}.tag{color:#235a81;font-size:12px}button{background:#235a81}`, fields: []skinField{ {name: "pma_username", label: "Username:"}, {name: "pma_password", label: "Password:", secret: true}, }, hidden: [][2]string{ {"server", "1"}, {"target", "index.php"}, {"token", "$hex32"}, }, failure: `#1045 - Cannot log in to the MySQL server`, footer: "Language: English · Theme: pmahomme", headers: [][2]string{ {"X-Powered-By", "PHP/7.4.33"}, {"Set-Cookie", "pma_lang=en; path=/; HttpOnly"}, }, }, { family: FamilyTomcat, routes: []string{"/manager/html", "/manager", "/host-manager", "/examples", "/jmx-console", "/web-console", "/invoker"}, title: "401 Unauthorized", brand: "Tomcat Manager Application", tagline: "Apache Tomcat/9.0.83", submit: "", class: "tomcat", form: "login", css: `h1{font-size:19px;font-weight:700;text-align:left}.tag{text-align:left}button{background:#5b6b73}button:hover{background:#4a575e}`, footer: "401 Unauthorized · this request requires HTTP authentication", basicRealm: "Tomcat Manager Application", headers: [][2]string{ {"X-Powered-By", "Servlet/4.0 JSP/2.3"}, }, }, { family: FamilyJenkins, routes: []string{"/jenkins", "/jnlpJars", "/jenkins/", "/script", "/administrativeMonitor"}, title: "Jenkins", brand: "Jenkins", tagline: "Welcome to Jenkins 2.401.3", submit: "Sign in", class: "jenkins", form: "login", gen: "", css: `.card{width:420px}h1{text-align:left;font-weight:700;color:#333}.tag{text-align:left}button{background:#4477aa}`, fields: []skinField{ {name: "j_username", label: "Username"}, {name: "j_password", label: "Password", secret: true}, }, hidden: [][2]string{{"from", "/"}, {"Submit", "Sign in"}}, failure: "Invalid username or password", footer: "Jenkins ver. 2.401.3", headers: [][2]string{ {"X-Jenkins", "2.401.3"}, {"X-Jenkins-Session", "$hex8"}, }, }, { family: FamilyGrafana, routes: []string{"/grafana"}, title: "Grafana", brand: "Grafana", tagline: "Welcome to Grafana 10.2.3", submit: "Log in", class: "grafana", form: "loginForm", gen: "", css: `body{background:#111217;color:#d8d9da}.card{background:#181b1f;border-color:#2c3235}h1{color:#f4f5f5}.tag{color:#9da3a8}input[type=text],input[type=password]{background:#0b0c0e;color:#d8d9da;border-color:#3d444b}button{background:#3d71d9}.foot{color:#8e9297}`, fields: []skinField{ {name: "user", label: "Email or username"}, {name: "password", label: "Password", secret: true}, }, hidden: [][2]string{{"userlang", "en-US"}}, failure: "Invalid username or password", footer: "Grafana Labs · v10.2.3", headers: [][2]string{ {"X-Grafana-Org-Id", "1"}, }, cookies: []string{"grafana_sess=$hex32; Path=/; HttpOnly"}, }, { family: FamilyGitLab, routes: []string{"/users/sign_in", "/users/password", "/users/sign_up"}, title: "Sign in · GitLab", brand: "GitLab", tagline: "Open source DevSecOps platform", submit: "Sign in", class: "gitlab", form: "new_user", gen: "", css: `.card{width:380px;border-color:#e5e5e5}h1{font-weight:700;color:#1f1e24}.tag{text-align:left}button{background:#1f75cb}`, fields: []skinField{ {name: "user[login]", label: "Username or email"}, {name: "user[password]", label: "Password", secret: true}, }, hidden: [][2]string{{"user[remember_me]", "0"}}, failure: "Invalid Login or password.", footer: "GitLab Community Edition 16.8.1", headers: [][2]string{ {"X-Gitlab-Meta", `{"correlation_id":"$hex32"}`}, }, cookies: []string{"_gitlab_session=$hex32; path=/; HttpOnly"}, }, { family: FamilyLuCI, routes: []string{"/cgi-bin/luci", "/luci", "/luci-static"}, title: "LuCI - Lua Configuration Interface", brand: "LuCI", tagline: "OpenWrt 23.05 · Lua Configuration Interface", submit: "Login", class: "luci", form: "login", gen: "", css: `.card{width:360px;border-top:3px solid #4a9c3f}h1{color:#4a9c3f;font-size:19px}button{background:#4a9c3f}`, fields: []skinField{ {name: "luci_username", label: "Username"}, {name: "luci_password", label: "Password", secret: true}, }, failure: "Invalid username and/or password! Please try again.", footer: "Powered by LuCI openwrt-23.05 branch", headers: [][2]string{ {"X-Powered-By", "PHP/7.4.33"}, }, }, { family: FamilyJoomla, routes: []string{"/administrator"}, title: "Joomla! Administration Login", brand: "Joomla!", tagline: "Administration Login · Joomla! 4.4.2", submit: "Log in", class: "joomla", form: "form-login", gen: "Joomla! - Open Source Content Management", css: `.card{border-top:3px solid #1a3867}.card h1{color:#1a3867}button{background:#1a3867}`, fields: []skinField{ {name: "username", label: "Username"}, {name: "passwd", label: "Password", secret: true}, }, hidden: [][2]string{ {"option", "com_login"}, {"task", "login"}, {"return", "aW5kZXgucGhw"}, }, failure: "Username and password do not match or you do not have an account yet.", footer: "Joomla! 4.4.2 · Copyright (C) Open Source Matters", headers: [][2]string{ {"X-Powered-By", "PHP/7.4.33"}, }, }, { family: FamilyGeneric, routes: []string{"/admin", "/console", "/webadmin", "/login.php", "/admin.php", "/admin/login", "/admin/index.php", "/cpanel", "/whm", "/plesk", "/webmail", "/roundcube", "/zabbix", "/nagios", "/monitoring", "/jira", "/confluence", "/gitlab", "/nextcloud", "/owncloud"}, title: "Administration", brand: "Administration Console", tagline: "Sign in to continue", submit: "Sign in", class: "generic", form: "loginForm", gen: "", css: `.card{width:360px}h1{font-weight:600;font-size:19px}button{background:#3c434a}`, fields: []skinField{ {name: "username", label: "Username"}, {name: "password", label: "Password", secret: true}, }, hidden: [][2]string{{"csrf_token", "$hex16"}}, failure: "Invalid username or password.", footer: "Restricted area. Authorized personnel only.", headers: [][2]string{ {"X-Powered-By", "PHP/7.4.33"}, }, }, } // FamilyOf 判断路径属于哪个家族(p 需已 normalize:小写、斜杠已折叠)。 // // 段前缀语义:/wp-admin 匹配 /wp-admin 与 /wp-admin/setup-config.php, // 但不匹配 /wp-administrator。'.' 也算边界,兼容 /adminer.php 这类写法。 func FamilyOf(p string) Family { for i := range skins { for _, r := range skins[i].routes { if hasSegPrefix(p, r) { return skins[i].family } } } return FamilyGeneric } func hasSegPrefix(p, prefix string) bool { if p == prefix { return true } if !strings.HasPrefix(p, prefix) { return false } next := p[len(prefix)] return next == '/' || next == '.' } // skinOf 返回家族对应的伪装设定。 func skinOf(f Family) *loginSkin { for i := range skins { if skins[i].family == f { return &skins[i] } } return &skins[len(skins)-1] // FamilyGeneric 永远在最后 } // Families 返回全部家族(供 CLI / 测试枚举)。 func Families() []Family { out := make([]Family, 0, len(skins)) for i := range skins { out = append(out, skins[i].family) } return out }