雷池(SafeLine)是长亭科技耗时近 10 年倾情打造的 WAF,核心检测能力由智能语义分析算法驱动。由于不使用规则进行检测,雷池WAF的性能与误报率相对更低,拦截率更高。在使用雷池前,我使用ModSecurity作为源服务器的WAF使用,。经过BASE64编码的攻击载荷未能被其识别,而雷池精准的拦截了此次请求。经过测试,Fortigate WAF(未测试FortiWEB)、宝塔WAF、NAXSI均不能很好的拦截类似请求。同时,NAXSI的误报率很高,类似......的省略号会被认为成目录穿越攻击

长亭雷池WAF社区版,使用反向代理接入站点,当使用443或8443等常见SSL端口提供服务时,SSL证书的信息可能会被「互联网空间搜索引擎」 记录,导致源站IP地址泄漏。「互联网空间搜索引擎」是一种特殊类型的搜索引擎,它通过特定的计算机程序从互联网上采集信息,然后对这些信息进行组织和处理,为用户提供检索服务,将检索的相关信息展示给用户。这种搜索引擎的目标主要在全球的IP地址上,即搜索范围在1.1.1.1-255.255.255.255的所有设备及服务上。例如,Shodan是全球第一个网络设备搜索引擎,它可以发现从电厂、手机、冰箱到Minecraft服务器等各种联网设备。ZoomEye(“钟馗之眼”)是知道创宇旗下404实验室驱动打造的中国第一款网站空间搜索引擎。FOFA是白帽汇推出的一款网络空间搜索引擎。Quake是360网络安全响应中心自主研发设计的全网空间测绘系统,而Censys.io和微步则被广泛用于安全数据分析。

攻击者会从「互联网空间搜索引擎」搜索与域名相关的IP地址,并尝试从中找出源服务器的地址,进行进一步的探测,绕过CDN/Cloud WAF的防护,甚至进行泛洪攻击,会对安全性造成严重影响。

通常情况下建议配置防火墙白名单仅允许CDN/Cloud WAF的IP地址段访问,但鉴于国内CDN特殊情况,IP地址段并非固定,所以大部分情况下无法完成此配置。

Tengine作为雷池WAF的7层服务器,我们需要在雷池的安装目录 (即第一次使用docker安装雷池时填写的目录)中,找到Tengine (Nginx)的目录,如”/www/safeline/resources/nginx“。找到Nginx目录后,在sites-enabled 下存放着各个站点的配置文件,打开其中一个,如IF_backend_1,在文件最后增加如下的配置文件:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    ssl_certificate /etc/nginx/certs/de.pem;
    ssl_certificate_key /etc/nginx/certs/de.key;
#此处可更改为444或其他未定义代码,403会返回雷池拦截页面
    location / {
        return 418;
    }
    access_log /etc/nginx/logs/de.log;
}

接下来,你需要确保”/etc/nginx/certs/de.pem“”/etc/nginx/certs/de.key“以及”/etc/nginx/logs“的存在。SafeLine的容器中,”/etc/nginx“ 被映射为安装目录,如”/www/safeline/resources/nginx“,所以你需要在”/www/safeline/resources/nginx“中创建logs目录,并在certs下放入默认证书。一张空的自签名证书示例如下,可以作为默认证书使用。密钥在前,证书在后。

-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDXyF6m81zOeoOPvfk6nGKtyfczRG6/yeSkcc+66vGvq0s8oB7V
cCzLl1YcNsru3ixelPR2z1zvjKqa9/Aqh8+TvP1kGGbLD/mynjnj8l+0vVzZ+vnz
AH0RN9fpqzlpHmFBHQzQ25AtIAH8pXOL1541YN0TNPRA3kHUCL0FH8CkwwIDAQAB
AoGAQ4ejh6AV5VCWJ8AOZXdXsofIYzUBa+glNAmiNx8b8BwteZWq0KVAf56nBkFn
lQXW4OrA7wXKUfW11rXNZaIHJePJXv1swkN9+Em18Hon6BrtcqnKAwzAbhok3SzY
IVjI/zrgOABH6+ii77xCRBzI1itVPNN88DAUHC7PYLYiaaECQQD7PSoij37+kMc/
wPeEkl9r3vzU0OrsCsjU8Ev714OaoL/SIuAh6nsiRh9rcbUrrpGSSzIcmsk9HMDa
hXBNkNl5AkEA298yQvssaUc4tbEWxAVfd9DsHJdCdbXfgf9Dy5/tpCzYncY7T0du
VVHqKu3jXWoMc5XlesiCOerU/DIlMM8dGwJBANQn7GLO5iC1xWvS2bF7oVSIMtzL
pvW4jaszWBbNAPccc59RkA9T4LMqn/GtTZ4bhhYRpbl+BB21IC3nrNPzU5ECQG8T
Ln0QDruQs2F2eR3F6RjKfr1i3LxCiQtPPZycypzp2vS5tDS0zVRk8XuGehoy/N9X
lnqU2NURgU92tbsWpokCQQDdc9tU3B/OM/YfzUNwvOLmUVwrJX6PFSFsOn+XHrCC
q9LcGEAHyzaf5GEWje84ee4rkv5oaZcwll3dg4IioBnC
-----END RSA PRIVATE KEY-----

-----BEGIN CERTIFICATE-----
MIIBkjCB/AIJAI3bCYqa39hiMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNVBAYTAiAg
MCAXDTE4MTEyNDA5MDMzOFoYDzIwOTkxMjMxMDkwMzM4WjANMQswCQYDVQQGEwIg
IDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA18hepvNcznqDj735Opxircn3
M0Ruv8nkpHHPuurxr6tLPKAe1XAsy5dWHDbK7t4sXpT0ds9c74yqmvfwKofPk7z9
ZBhmyw/5sp454/JftL1c2fr58wB9ETfX6as5aR5hQR0M0NuQLSAB/KVzi9eeNWDd
EzT0QN5B1Ai9BR/ApMMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQBiqHZsuVP09ubT
GzBSlAFEoqbM63sU51nwQpzkVObgGm9v9nnxS8Atid4be0THsz8nVjWcDym3Tydp
lznrhoSrHyqAAlK3/WSMwyuPnDCNM5g1RdsV40TjZXk9/md8xWxGJ6n1MoBdlK8T
H6h2ROkf59bb096TttB8lxXiT0uiDQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBkjCB/AIJAI3bCYqa39hiMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNVBAYTAiAg
MCAXDTE4MTEyNDA5MDMzOFoYDzIwOTkxMjMxMDkwMzM4WjANMQswCQYDVQQGEwIg
IDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA18hepvNcznqDj735Opxircn3
M0Ruv8nkpHHPuurxr6tLPKAe1XAsy5dWHDbK7t4sXpT0ds9c74yqmvfwKofPk7z9
ZBhmyw/5sp454/JftL1c2fr58wB9ETfX6as5aR5hQR0M0NuQLSAB/KVzi9eeNWDd
EzT0QN5B1Ai9BR/ApMMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQBiqHZsuVP09ubT
GzBSlAFEoqbM63sU51nwQpzkVObgGm9v9nnxS8Atid4be0THsz8nVjWcDym3Tydp
lznrhoSrHyqAAlK3/WSMwyuPnDCNM5g1RdsV40TjZXk9/md8xWxGJ6n1MoBdlK8T
H6h2ROkf59bb096TttB8lxXiT0uiDQ==
-----END CERTIFICATE-----

# 可选| 配置替换真实IP请求头

注:该配置需要在CDN处配置连接IP header为connect-ip,不同cdn配置方式不同,如Cloudflare在托管转换中配置动态字段,具体请参考CDN官方文档。

为了防止假IP欺骗,除了可以在CDN处hide掉有关于IP的header,也可以使用自定义请求头记录IP(雷池控制台内设置connect-ip)。考虑到雷池后应用兼容性,可以把标准IP header覆盖替换。

在Tengine配置文件开始后插入:

#设置真实IP
map $http_connect_ip $custom_x_forwarded_for {
    default $proxy_add_x_forwarded_for;
    "~^(\d+\.\d+\.\d+\.\d+)$" $1;
    "~^([0-9A-Fa-f:]+)$" $1;
}

map $http_connect_ip $custom_x_real_ip {
    default $remote_addr;
    "~^(\d+\.\d+\.\d+\.\d+)$" $1;
    "~^([0-9A-Fa-f:]+)$" $1;
}

这段配置文件意思是,如果有connect-ip,则用connect-ip的值,如果没有,则用原来的header的值。

location ^~ / { }块中,替换修改相关header的值

 proxy_set_header X-Real-IP $custom_x_real_ip;
 proxy_set_header X-Forwarded-For $custom_x_forwarded_for;
 proxy_set_header REMOTE-HOST $custom_x_real_ip;

完成配置后,你需要重启Tengine使配置生效,值得注意的是,雷池的Tengine配置文件在容器重启后或更改了站点配置,会重新生成新的配置文件并覆盖,所以需要在雷池容器内执行nginx -t,检查无误后执行nginx -s reload命令重启nginx。建议备份配置文件至其他目录,在重启服务器、容器,或更改了站点配置后,需要重新覆盖配置文件并在容器内重启nginx。以下是如何操作的示例。

op@bj-0002-TS:~$ sudo docker ps -a
[sudo] op 的密码: 
CONTAINER ID   IMAGE                                 COMMAND                   CREATED      STATUS                PORTS                                       NAMES
7ef59a03fe31   chaitin/safeline-tengine:latest       "docker-entrypoint.s…"   4 days ago   Up 4 days (healthy)                                               safeline-tengine
ff8c5af19387   chaitin/safeline-mario:latest         "/mario/entrypoint.sh"    4 days ago   Up 2 days (healthy)                                               safeline-mario
0172e26fe8f5   postgres:15.2                         "docker-entrypoint.s…"   4 days ago   Up 4 days             5432/tcp                                    safeline-postgres
1b857054f8bd   chaitin/safeline-fvm-manager:latest   "/app/entrypoint.sh"      4 days ago   Up 4 days                                                         safeline-fvm-manager
78f39efeb529   chaitin/safeline-mgt-api:latest       "/app/entrypoint.sh"      4 days ago   Up 4 days             0.0.0.0:9443->1443/tcp, :::9443->1443/tcp   safeline-mgt-api
54b84762b2a6   chaitin/safeline-detector:latest      "/detector/entrypoin…"   4 days ago   Up 4 days (healthy)   8000-8001/tcp                               safeline-detector
op@bj-0002-TS:~$ sudo docker container exec -it 7ef59a03fe31 /bin/bash
root@bj-0002-TS:/# nginx -t       
nginx: [warn] conflicting server name "" on 0.0.0.0:443, ignored
nginx: [warn] conflicting server name "" on 0.0.0.0:80, ignored
nginx: [warn] conflicting server name "" on [::]:80, ignored
nginx: [warn] conflicting server name "" on [::]:443, ignored
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
root@bj-0002-TS:/# nginx -s reload
nginx: [warn] conflicting server name "" on 0.0.0.0:443, ignored
nginx: [warn] conflicting server name "" on 0.0.0.0:80, ignored
nginx: [warn] conflicting server name "" on [::]:80, ignored
nginx: [warn] conflicting server name "" on [::]:443, ignored
root@bj-0002-TS:/# exit
exit
op@bj-0002-TS:~$ 

配置完毕后,直接访问IP地址或不存在的网站,则会返回一个空证书,并且拦截返回418状态码。

雷池的人机验证功能可以很好的防护机器人扫描以及HTTP Flood攻击,以下是一些针对User-Agent的验证规则。

匹配旧版本Chrome:

^Mozilla\/5\.0 \(.+?\) AppleWebKit\/.+? \(KHTML, like Gecko\) Chrome\/([1-7]\d|[1-9])\..+? Safari\/.+?$

匹配旧版本Firefox:

^Mozilla\/5\.0 \(.+?\) Gecko\/.+? Firefox\/([1-9]|[1-5]\d|6[0-3])\..+?$

匹配旧版本Safari:

^Mozilla\/5.0 (.+?) AppleWebKit\/.+? (KHTML, like Gecko) Version\/([1-9]|1[0-1])..+? Safari\/.+?$

匹配不包含Mozilla/5.0

^(?!.*Mozilla/5\.0).*$

匹配地区(如果CDN会返回IP属地信息的话):

(MX|SG|TW|NL|DE|PH|JP|...)

###

#按要求删除此段内容#


在我们的日常使用中,会直接在Nginx配置一些拦截规则,而被拦截的请求不会单独记录日志,例如我的香港中转服务器依然使用NAXSI进行基础过滤。如果规则有误则很难排查问题,在研究雷池的拦截页面后,我按照雷池的记录拦截ID的思路,使用LUA编写了一段代码以完成我的需求。需要注意,Nginx必须安装了LUA模块才能使用此代码,Tengine和Openresty已经默认安装LUA模块,执行nginx -V (大写) 检查是否安装了LUA模块。

local function generate_unique_id()
    -- 生成一个基于时间和随机数的唯一ID
    local time_in_ms = ngx.now() * 1000
    local random_num = math.random(10000, 99999)
    return string.format("%s-%s", time_in_ms, random_num)
end

local function log_request_and_generate_hash()
    -- 使用Nginx的变量获取请求IP
    local client_ip = ngx.var.custom_x_real_ip
    local request_time = os.date("%Y-%m-%d %H:%M:%S", ngx.time())
    local request_url = ngx.var.request_uri
    local user_agent = ngx.var.http_user_agent
    local host = ngx.var.http_host

    -- 生成唯一的错误ID
    local error_id = generate_unique_id()

    -- 使用Lua的table和concat函数生成一个简单的哈希
    local hash_table = {host,client_ip, request_time, request_url, user_agent, error_id}
    local hash_value = table.concat(hash_table, "|")

    -- 返回请求信息、哈希值和错误ID
    return host,client_ip, request_time, request_url, user_agent, hash_value, error_id
end

-- 获取当前状态码
local status = ngx.var.status

-- 检查状态码是否为418
if status == "418" then
    -- 记录请求并生成哈希
    local host,client_ip, request_time, request_url, user_agent, hash_value, error_id = log_request_and_generate_hash()

    -- 打开文件以追加模式
    local file, err = io.open("/www/err/418/error.log", "a")
    if not file then
        ngx.log(ngx.ERR, "无法打开文件 /www/err/418/error.log: ", err)
        return
    end

    -- 记录请求信息、哈希值和错误ID
    -- 记录哈希值和错误ID
local log_entry = string.format("%s\n", hash_value)
local success, err = file:write(log_entry)

    if not success then
        ngx.log(ngx.ERR, "无法写入文件 /www/err/418/error.log: ", err)
        return
    end
    file:close()

    -- 读取418.html文件内容
    local error_file, err = io.open("/www/err/418.html", "r")
    if not error_file then
        ngx.log(ngx.ERR, "无法打开文件 /www/err/418.html: ", err)
        return
    end
    local file_content = error_file:read("*a")
    error_file:close()

    -- 在错误页面中添加错误ID的注释
    file_content = file_content .. "\n<!-- event_id: " .. error_id .. " -->"

    -- 输出418.html文件内容和错误ID
    ngx.say(file_content)
else
    ngx.log(ngx.ERR, "状态码不是418,当前状态码为: ", status)
end

需要确保/www/err/418.html和日志目录/www/err/418/error.log真实存在,418状态码为我自定义的拦截代码,该代码为一个愚人节玩笑而非非,I'M A TEAPOT,虽然418状态码在RFC 7231中并未明确定义,但它仍然被4xx(客户端错误)类别所覆盖。然而,这个状态码并不是用来严肃对待的,也不会对客户端或SEO产生实质性影响。这个状态码在实际应用中很少使用,但在一些特定的情况下,开发者可能会使用它来进行一些特殊的处理。例如,如果服务器收到了一个无法处理的请求,它可能会返回418状态码,表示"我是茶壶,找咖啡壶去"。这就是说,这个请求应该由其他能够处理的服务器(咖啡壶)来处理。使用418状态码为了不影响403页面的正常响应。

在418.html中,使用如下JavaScript代码获取拦截ID:

   <script>
//此处为获取访客IP
        async function getIPInfo() {
            const response = await fetch('https://api.gymxbl.com/ip/get');
            const data = await response.json();
            return data;
        }

        function ipinfo() {
            getIPInfo().then(data => {
                const li = document.getElementById('ip-info');
                li.textContent += `${data.cip}  Location: ${data.clo}`;
            });
        }

        function setEventIDAndType() {
            var nodes = document.getElementsByTagName("body")[0].childNodes;
            var fit2inserts = null;
            for (var i = 0; i < nodes.length; i++) {
                if (
                    nodes[i].nodeType == 8 &&
                    nodes[i].data.trimLeft().startsWith("event_id")
                ) {
                    fit2inserts = nodes[i];
                }
            }

            try {
                var inserts =
                    document.getElementsByTagName("html")[0].nextSibling || fit2inserts;
                var insertsData = inserts && (inserts.data || "");
                var incertDataList = insertsData.split(" ");
                var getVal = function (key) {
                    return incertDataList[incertDataList.indexOf(key + ":") + 1];
                };
                var event_id = getVal("event_id");
                var type = getVal("type");
                // var anymore = getVal('anymore') // 新增参数示例
            } catch (e) {
                console.log(e);
            }
            if (event_id) {
                document.getElementById("EventID").innerText = "你的ID(•ω•):" + event_id;
            }
            if (type) {
                document.getElementById("TYPE").innerText = "TYPE: " + type;
            }
        }

        // 使用addEventListener添加事件监听器
        window.addEventListener('load', function() {
            ipinfo();
            setEventIDAndType();
        });
//这一段基本是从雷池的拦截页抄的
    </script>

接下来在希望向访客展示错误的地方将ID填入: id="ip-info" id="EventID"

	<div class="md:flex min-h-screen main-body">
			<div class="w-full md:w-1/2 bg-white flex items-center justify-center left-sec">
				<div class="max-w-sm m-8 content-body">
					<div class="text-black text-5xl md:text-15xl font-black">访问被拦截了哦 ヽ(*。>Д<)o゜</div>

							  <p class="text-grey-darker text-2xl md:text-3xl mb-7 leading-normal">
							</p>
							<p class="sub-header">
								<h1>好好反省为什么会被拦截叭!(ノ`Д)ノ</h1>
							</p>
							<br>
							<br>
							我是一个茶壶!I'm a teapot!

							<div class="sub-footer">
								<div class="w-full h-2 bg-grey-light my-3 md:my-6"></div>
								<ul>
									<p></p>
									<li id="EventID"></li>
									<br>
									<li id="ip-info">你的IP: </li>
									<br>
									<li>发生时间: <script type="text/javascript">
											document.write(Date())
										</script>
									</li>
									<br>
									<li>拦截由 Lua 脚本</li>
								</ul>
							</div>

					</div>
				</div>

				<div class="relative pb-full md:flex md:pb-0 md:min-h-screen w-full md:w-1/2 right-sec">
				</div>
			</div>

此HTML仅为示例,使用时需要自行定制。

在你希望应用该页面的域名所属Server块中添加:

location = /418.html
{
#交给LUA处理
content_by_lua_file /www/lua/418.lua;
#禁止外部访问,404错误码
error_page 404 /404.html;
#仅允许内部访问
internal;
}
#哦对了别忘了把errpage加上,特意回来加一句.....
error_page 418 /418.html;

重启Nginx使能生效。

本文介绍了如何使用和配置雷池WAF,包括设置默认站点的重要性,配置Tengine的方法,以及一些有用的人机验证正则表达式。我们还讨论了如何自定义Nginx的拦截记录日志,并提供了代码部署的步骤。希望这些信息能帮助你更好地使用雷池WAF,提升你的网络安全防护能力。感谢你的阅读。

关于今年一年没有正式更新,我很抱歉,因为身体抱恙,并且在深入了解了许多领域后,我发现我懂得不够多,很多内容是其他人做过的,所以我不需要在做一遍。目前仍然在深度了解学习商用设备,包括Fortinet,思科等厂商的商业设备,学习配置SSL-VPN和IPSec以及BGP路由,了解真正的UTM统一威胁管理是如何配置以及运作。

另外,好哥们送了我一台Lenovo SystemX3650M5 TYPE 8871的2U机架式服务器,我打算使用该服务器进行深度学习领域探索,将原先服务器上的Tesla P4计算卡安装在了该台服务器中,并使用iSCSI协议共享原服务器的存储空间。目前仍在了解GLM与RWKV等LLM模型,期待我能有更好的突破吧。

版权声明:转载时请以超链接形式标明文章原始出处和作者信息,来源孤影墨香
本文链接: https://www.iloli.xin/3951.html
访问时间:2024-02-29 03:24:41


正因为知道可以在空中翱翔,才会畏惧展翅的那一刻而忘却疾风 努力学习ing