package honeypot import ( "html" "net/http" "strings" ) // LoginPageVars 是假登录页的渲染参数。 type LoginPageVars struct { // Action 是表单提交地址(当前诱饵路径本身)。 Action string // Title / Heading 覆盖家族默认的标题与品牌名(留空即用家族设定)。 Title string Heading string // Notice 是自定义提示语;留空时由 Failed 决定是否显示家族的失败文案。 Notice string // Failed 表示这是一次「提交后」的响应,需要显示登录失败提示。 Failed bool // Redir 为原始请求的查询串(WordPress 风格的 ?redirect_to=),原样回填。 Redir string } // LoginPageTitle 是 WordPress 家族假登录页的标题(与真实登录页一致)。 // 抽成常量:之前 route 层手抄了一份字面量,一处写 `‹`、一处写 `\u2039`, // 已经出现漂移。 const LoginPageTitle = "Log In ‹ WordPress" // LoginFailureNotice 是 WordPress 提交凭据后的提示 // (不区分「用户不存在」与「密码错误」,避免响应差异让爆破脚本调整策略)。 const LoginFailureNotice = "ERROR: The password you entered for the username is incorrect." // baseLoginCSS 是全部家族共用的骨架样式,各家族只叠加自己的配色。 const baseLoginCSS = `*{box-sizing:border-box}body{margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;background:#f0f0f1;color:#3c434a}` + `.card{width:340px;margin:8vh auto;background:#fff;border:1px solid #dcdcde;border-radius:4px;padding:26px 24px 30px}` + `h1{margin:0 0 6px;font-size:22px;font-weight:400;text-align:center}` + `.tag{margin:0 0 16px;text-align:center;font-size:11px;color:#787c82;letter-spacing:.3px}` + `label{display:block;margin-top:12px;font-size:13px}` + `input[type=text],input[type=password]{display:block;width:100%;margin-top:5px;padding:7px 9px;border:1px solid #c3c4c7;border-radius:3px;font-size:14px}` + `button{display:block;width:100%;margin-top:18px;padding:9px;border:0;border-radius:3px;color:#fff;font-size:14px;cursor:pointer}` + `.err{border-left:4px solid #d63638;background:#fcf0f1;padding:10px 12px;margin:0 0 16px;font-size:13px;line-height:1.5}` + `.foot{margin:22px auto 0;text-align:center;font-size:12px;color:#787c82;line-height:1.7}` // fieldIDReplacer 把 user[login] 这类字段名转成合法的 HTML id。 // 提到包级:每次渲染新建一个 Replacer 要分配十几次,而蜜罐是被高频命中的。 var fieldIDReplacer = strings.NewReplacer("[", "_", "]", "") // RenderFamily 按家族渲染假登录页。 // // 资源占用刻意压到最低:全部是编译期常量 + 一次 strings.Builder 拼接, // 没有 html/template 解析、没有文件读取、没有外部请求。一个页面约 1.5–2.5 KB。 // // 写的时候刻意不用 `"a" + b + "c"`:每个 + 都是一次临时分配。 // 连续 WriteString 走 append,配合一次 Grow,整页只有 2–3 次分配 // (见 family_test.go 的 BenchmarkRenderFamily)。 // // canary 只放 HTML 注释里,不放进 : // generator 是扫描器的必查项,必须伪装成真实产品,否则等于自曝蜜罐。 func RenderFamily(f Family, vars LoginPageVars) []byte { s := skinOf(f) action := html.EscapeString(vars.Action) title := vars.Title if title == "" { title = s.title } brand := vars.Heading if brand == "" { brand = s.brand } notice := vars.Notice if notice == "" && vars.Failed { notice = s.failure } var b strings.Builder b.Grow(3072 + len(s.css)) b.WriteString("\n\n
\n") b.WriteString("\n") b.WriteString("\n") b.WriteString("\n") if s.gen != "" { b.WriteString("\n") } b.WriteString("") b.WriteString(s.tagline) b.WriteString("
\n") } if notice != "" { b.WriteString("") b.WriteString(s.footer) b.WriteString("
\n") } b.WriteString("