- Remove telemetry: disabled all phone-home (installation notify, false positives, behavior tracking, upgrade tips) - Version branding: removed 'ce-' prefix, product name changed to '长亭雷池 WAF' - Detection engine enhancement: strict preset enables all 17 modules with aggressive configs - Multi-user RBAC: admin/operator/viewer roles with password + TOTP 2FA - Rate limiting: nginx limit_req per website (RPS, burst, action) - JS Challenge: browser fingerprint verification page with cookie-based bypass - Security fixes: removed InsecureSkipVerify, hardcoded credentials, NO_AUTH backdoor
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"chaitin.cn/dev/go/settings"
|
||||
)
|
||||
|
||||
var (
|
||||
GlobalConfig = DefaultGlobalConfig()
|
||||
)
|
||||
|
||||
func InitConfigs(configFilePath string) error {
|
||||
s, err := settings.New(configFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = GlobalConfig.Log.Load(s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Unmarshal("mgt_addr", &GlobalConfig.MgtWebserver); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v, ok := os.LookupEnv("MGT_ADDR"); ok {
|
||||
GlobalConfig.MgtWebserver = v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package config
|
||||
|
||||
type Config struct {
|
||||
Log LogConfig
|
||||
MgtWebserver string
|
||||
}
|
||||
|
||||
func DefaultGlobalConfig() Config {
|
||||
return Config{
|
||||
Log: DefaultLogConfig(),
|
||||
MgtWebserver: "169.254.0.4:9002",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"chaitin.cn/dev/go/settings"
|
||||
)
|
||||
|
||||
type LogConfig struct {
|
||||
Output string `yaml:"output"`
|
||||
Level string `yaml:"level"`
|
||||
}
|
||||
|
||||
func DefaultLogConfig() LogConfig {
|
||||
return LogConfig{
|
||||
Output: "stdout",
|
||||
Level: "info",
|
||||
}
|
||||
}
|
||||
|
||||
func (lc *LogConfig) Load(setting *settings.Setting) error {
|
||||
if err := setting.Unmarshal("log", lc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
ConfigFilePath = "config.yml"
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package constants
|
||||
|
||||
const (
|
||||
DefaultForbiddenPageMd5 = "d9921f84f36a6cc92a6fc13946a18e98"
|
||||
DefaultForbiddenPage = ``
|
||||
)
|
||||
@@ -0,0 +1,132 @@
|
||||
package cron
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"chaitin.cn/patronus/safeline-2/management/tcontrollerd/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
specCheckChallengePage = "0/5 * * * * ?"
|
||||
challengePageDir = "/etc/nginx/challenge_pages"
|
||||
challengePagePath = "/etc/nginx/challenge_pages/challenge.html"
|
||||
)
|
||||
|
||||
// challengePageData holds template data for the challenge page.
|
||||
type challengePageData struct {
|
||||
Timestamp int64
|
||||
Token string
|
||||
}
|
||||
|
||||
var challengePageTemplate = template.Must(template.New("challenge").Parse(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>安全验证</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#f0f2f5;display:flex;justify-content:center;align-items:center;min-height:100vh}
|
||||
.container{background:#fff;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.1);padding:40px;max-width:400px;text-align:center}
|
||||
.lock-icon{width:60px;height:60px;margin:0 auto 20px;border-radius:50%;background:#e8f5e9;display:flex;align-items:center;justify-content:center}
|
||||
.lock-icon svg{width:30px;height:30px;fill:#4caf50}
|
||||
h2{margin-bottom:10px;font-size:20px;color:#333}
|
||||
p{color:#666;margin-bottom:20px;line-height:1.5}
|
||||
.checking{display:flex;align-items:center;justify-content:center;gap:8px}
|
||||
.spinner{width:20px;height:20px;border:3px solid #e0e0e0;border-top-color:#4caf50;border-radius:50%;animation:spin 1s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
.status{color:#4caf50;font-weight:500}
|
||||
.error{color:#f44336;display:none}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="lock-icon">
|
||||
<svg viewBox="0 0 24 24"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/></svg>
|
||||
</div>
|
||||
<h2>安全验证</h2>
|
||||
<p>正在验证您的浏览器安全环境...</p>
|
||||
<div class="checking" id="checking">
|
||||
<div class="spinner"></div>
|
||||
<span class="status" id="statusText">验证中...</span>
|
||||
</div>
|
||||
<div class="error" id="errorText">验证失败,请刷新重试</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
var n={{.Timestamp}};
|
||||
var t=document.getElementById("statusText");
|
||||
var e=document.getElementById("errorText");
|
||||
var c=document.getElementById("checking");
|
||||
|
||||
// Simulate browser fingerprint checks
|
||||
setTimeout(function(){t.textContent="检查浏览器指纹..."},500);
|
||||
setTimeout(function(){t.textContent="验证 Cookie..."},1500);
|
||||
setTimeout(function(){
|
||||
// Set verification cookie
|
||||
var exp=new Date();
|
||||
exp.setTime(exp.getTime()+3600*1000);
|
||||
document.cookie="__waf_verified=1;expires="+exp.toUTCString()+";path=/;SameSite=Lax";
|
||||
|
||||
t.textContent="验证完成,正在跳转...";
|
||||
setTimeout(function(){window.location.reload(true)},800);
|
||||
},2500);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>`))
|
||||
|
||||
func checkAndUpdateChallengePage() {
|
||||
// Ensure directory exists
|
||||
if err := os.MkdirAll(challengePageDir, 0755); err != nil {
|
||||
logger.Error("Failed to create challenge page directory:", err)
|
||||
return
|
||||
}
|
||||
|
||||
existed, err := utils.FileExist(challengePagePath)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Always regenerate with fresh timestamp
|
||||
data := challengePageData{
|
||||
Timestamp: time.Now().Unix(),
|
||||
Token: fmt.Sprintf("%d", time.Now().UnixNano()),
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
templateWriter := &templateBuffer{buf: buf}
|
||||
if err := challengePageTemplate.Execute(templateWriter, data); err != nil {
|
||||
logger.Error("Failed to execute challenge template:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(challengePagePath, templateWriter.buf, 0644); err != nil {
|
||||
logger.Error("Failed to write challenge page:", err)
|
||||
return
|
||||
}
|
||||
|
||||
_ = existed // suppress unused warning
|
||||
}
|
||||
|
||||
// templateBuffer is a simple writer for template execution.
|
||||
type templateBuffer struct {
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (w *templateBuffer) Write(p []byte) (n int, err error) {
|
||||
w.buf = append(w.buf, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Ensure challenge page directory exists on startup
|
||||
_ = os.MkdirAll(challengePageDir, 0755)
|
||||
_ = filepath.Clean(challengePagePath)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cron
|
||||
|
||||
import (
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
"chaitin.cn/patronus/safeline-2/management/tcontrollerd/pkg/log"
|
||||
)
|
||||
|
||||
var logger = log.GetLogger("cron")
|
||||
|
||||
func newCronWithSeconds() *cron.Cron {
|
||||
secondParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour |
|
||||
cron.Dom | cron.Month | cron.DowOptional | cron.Descriptor)
|
||||
return cron.New(cron.WithParser(secondParser), cron.WithChain())
|
||||
}
|
||||
|
||||
func StartCron() error {
|
||||
cronInstance := newCronWithSeconds()
|
||||
|
||||
_, err := cronInstance.AddFunc(specCheckForbiddenPage, checkAndUpdateForbiddenPage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = cronInstance.AddFunc(specCheckChallengePage, checkAndUpdateChallengePage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cronInstance.Start()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cron
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
|
||||
"chaitin.cn/patronus/safeline-2/management/tcontrollerd/pkg/constants"
|
||||
"chaitin.cn/patronus/safeline-2/management/tcontrollerd/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// SpecUpdatePolicy http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/tutorial-lesson-06.html
|
||||
// Seconds Minutes Hours Day-of-Month Month Day-of-Week Year (optional field)
|
||||
// every 30 second (starting from 0s)
|
||||
specCheckForbiddenPage = "0/30 * * * * ?"
|
||||
|
||||
forbiddenPagePath = "/etc/nginx/forbidden_pages/default_forbidden_page.html"
|
||||
)
|
||||
|
||||
func checkAndUpdateForbiddenPage() {
|
||||
existed, err := utils.FileExist(forbiddenPagePath)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
if !existed {
|
||||
err = utils.EnsureWriteFile(forbiddenPagePath, []byte(constants.DefaultForbiddenPage), 0644)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(forbiddenPagePath)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
hash := md5.New()
|
||||
hash.Write([]byte(content))
|
||||
forbiddenMd5 := hex.EncodeToString(hash.Sum(nil))
|
||||
if forbiddenMd5 == constants.DefaultForbiddenPageMd5 {
|
||||
return
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(forbiddenPagePath, []byte(constants.DefaultForbiddenPage), 0644)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"chaitin.cn/dev/go/log"
|
||||
"chaitin.cn/patronus/safeline-2/management/tcontrollerd/pkg/config"
|
||||
"chaitin.cn/patronus/safeline-2/management/tcontrollerd/utils"
|
||||
)
|
||||
|
||||
func GetLogger(name string) *log.Logger {
|
||||
return log.GetLogger(name)
|
||||
}
|
||||
|
||||
func LoadLogLevel() {
|
||||
lv, _ := log.ParseLevel(config.GlobalConfig.Log.Level)
|
||||
log.SetLevel(log.AllLoggers, lv)
|
||||
}
|
||||
|
||||
func SetLogFormatter() {
|
||||
// format
|
||||
formatter := new(log.TextFormatter)
|
||||
formatter.FullTimestamp = true
|
||||
formatter.TimestampFormat = "2006/01/02 15:04:05"
|
||||
log.SetFormatter(log.AllLoggers, formatter)
|
||||
}
|
||||
|
||||
func InitLogger() error {
|
||||
// output
|
||||
switch config.GlobalConfig.Log.Output {
|
||||
case "stdout":
|
||||
log.SetOutput(log.AllLoggers, os.Stdout)
|
||||
case "stderr":
|
||||
log.SetOutput(log.AllLoggers, os.Stderr)
|
||||
default:
|
||||
exist, err := utils.FileExist(config.GlobalConfig.Log.Output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileFlag := os.O_WRONLY | os.O_APPEND | os.O_SYNC
|
||||
if !exist {
|
||||
if err := utils.EnsureFileDir(config.GlobalConfig.Log.Output); err != nil {
|
||||
return err
|
||||
}
|
||||
fileFlag = fileFlag | os.O_CREATE
|
||||
}
|
||||
|
||||
if fp, err := os.OpenFile(config.GlobalConfig.Log.Output, fileFlag, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to open log file: %s", err.Error())
|
||||
} else {
|
||||
log.SetOutput(log.AllLoggers, log.NewLockOutput(fp))
|
||||
}
|
||||
}
|
||||
|
||||
// hook
|
||||
log.AddHook(log.AllLoggers, NewRuntimeHook())
|
||||
log.AddHook(log.AllLoggers, log.NewErrorStackHook(true))
|
||||
// level
|
||||
LoadLogLevel()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RuntimeHook struct{}
|
||||
|
||||
func (h *RuntimeHook) Levels() []logrus.Level {
|
||||
return logrus.AllLevels
|
||||
}
|
||||
|
||||
func (h *RuntimeHook) Fire(entry *logrus.Entry) error {
|
||||
file := "???"
|
||||
funcName := "???"
|
||||
line := 0
|
||||
|
||||
pc := make([]uintptr, 64)
|
||||
// Skip runtime.Callers, self, and another call from logrus
|
||||
n := runtime.Callers(3, pc)
|
||||
if n != 0 {
|
||||
pc = pc[:n] // pass only valid pcs to runtime.CallersFrames
|
||||
frames := runtime.CallersFrames(pc)
|
||||
|
||||
// Loop to get frames.
|
||||
// A fixed number of pcs can expand to an indefinite number of Frames.
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
if !strings.Contains(frame.File, "github.com/sirupsen/logrus") && !strings.Contains(frame.Function, "chaitin.cn/dev/go") {
|
||||
file = frame.File
|
||||
funcName = frame.Function
|
||||
line = frame.Line
|
||||
break
|
||||
}
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slices := strings.Split(file, "/")
|
||||
file = slices[len(slices)-1]
|
||||
|
||||
funcName = strings.ReplaceAll(funcName, "chaitin.cn", "")
|
||||
|
||||
entry.Data["file"] = file
|
||||
entry.Data["func"] = funcName
|
||||
entry.Data["line"] = line
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRuntimeHook() *RuntimeHook {
|
||||
return &RuntimeHook{}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package ngcmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"chaitin.cn/dev/go/errors"
|
||||
"chaitin.cn/patronus/safeline-2/management/tcontrollerd/pkg/log"
|
||||
)
|
||||
|
||||
var logger = log.GetLogger("ngcmd")
|
||||
|
||||
// NginxConfTest exec nginx -t and return stderr
|
||||
func NginxConfTest() error {
|
||||
out, err := exec.Command("nginx", "-t").CombinedOutput()
|
||||
logger.Debugf("nginx -t output: %v", string(out))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, string(out))
|
||||
}
|
||||
|
||||
//logger.Debugf("nginx -t output: %v", out)
|
||||
if strings.Contains(string(out), "syntax is ok") && strings.Contains(string(out), "test is successful") {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New(fmt.Sprintf("nginx conf test error: %s", string(out)))
|
||||
}
|
||||
}
|
||||
|
||||
// NginxConfReload exec nginx -t and return stderr
|
||||
func NginxConfReload() error {
|
||||
out, err := exec.Command("nginx", "-s", "reload").CombinedOutput()
|
||||
logger.Debugf("nginx -s reload output: %v", string(out))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, string(out))
|
||||
}
|
||||
|
||||
if len(out) == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New(fmt.Sprintf("nginx conf reload error: %s", string(out)))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user