长亭雷池 WAF 优化拦截响应内容

文章内容可能会违法雷池的许可证条款,仅供学习交流使用,切勿用于非法用途。如果你也需要这个功能,请记得前往 https://github.com/chaitin/SafeLine/issues/165 给楼主点赞,推动官方早日实现此需求。

我在很多场合讲过,现在业务服务器我会定义成 API 服务器,他们只提供 API 接口的服务,不提供任何静态资源的服务,静态资源全部交给 CDN 来处理,这样可以有效的减少业务服务器的压力,也可以有效的减少业务服务器的带宽消耗。

在服务器的前置加上雷池后,恶意的请求会被拦截,但是拦截后的响应内容是一个 HTML 页面,目前大小是有 17 KB 的,在恶意请求较多的场景下会给我们的业务服务器带来很大的压力,所以我们需要对这个响应内容进行优化。

这个痛点很早就有了,我找到了响应的 HTML 文件位于 /root/safeline/resources/nginx/forbidden_pages 下面,进行修改后是可以生效的,但过一会又被重新写入了,一直没找到合适的处理方法。

通过公众号文章发现了 fsnotify 这个开源项目,可以监听文件的变化,然后我们再根据变化来进行处理,这样就可以实现我们的需求了。因为实现起来非常简单,就直接贴代码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package main

import (
"github.com/fsnotify/fsnotify"
"log"
"os"
"strings"
"time"
)

// SAFELINE_DIR 监听的目录
var SAFELINE_DIR = os.Getenv("SAFELINE_DIR")

// Content 需要强制修改的文件内容
var Content = map[string]string{
"/data/forbidden_pages/default_forbidden_page.html": `{"code": 0, "msg": "风控拦截"}`,
}

func init() {
if strings.HasSuffix(SAFELINE_DIR, "/") {
SAFELINE_DIR = SAFELINE_DIR[:len(SAFELINE_DIR)-1]
}

// 初始化阶段强制修改一次
go func() {
time.Sleep(time.Second * 30)

for path := range Content {
_ = forceContent(path)
}
}()
}

func main() {
err := handle()

if err != nil {
log.Fatal(err)
}
}

func handle() error {
watcher, err := fsnotify.NewWatcher()

if err != nil {
return err
}

defer watcher.Close()

go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}

// 只监听写入事件
if !event.Has(fsnotify.Write) {
continue
}

err = forceContent(event.Name)

if err != nil {
watcher.Errors <- err
}
case err, ok := <-watcher.Errors:
if !ok {
return
}

log.Println("error:", err)
}
}
}()

err = watcher.Add(SAFELINE_DIR)

if err != nil {
return err
}

<-make(chan struct{})

return nil
}

func forceContent(path string) error {
log.Printf("强制修改文件: %s", path)

content, ok := Content[path]

if !ok {
return nil
}

body, err := os.ReadFile(path)

if err != nil {
return err
}

if string(body) == content {
return nil
}

return os.WriteFile(path, []byte(content), 0644)
}

你可以自行编译后进行部署使用,记得要挂载卷和设置监听地址。

通过打印的日志,可以发现雷池每30秒会重新修改一次文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2023/08/28 12:39:00 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:39:00 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:39:30 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:39:30 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:39:30 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:40:00 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:40:00 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:40:00 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:40:30 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:40:30 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:40:30 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:41:00 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:41:00 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:41:00 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:41:30 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:41:30 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:41:30 强制修改文件: /data/forbidden_pages/default_forbidden_page.html
2023/08/28 12:42:00 强制修改文件: /data/forbidden_pages/default_forbidden_page.html

另外响应内容不是正常的 JSON 内容,雷池还会赋值一个事件 ID 进去,这块还需要前端进行兼容处理,暂时是没办法在服务器层面来调整的。

1
2
{"code": 0, "msg": "风控拦截"}
<!-- event_id: c64c3add8f4f4a0eb3fe718adc66defd -->
往上