最近在查看 CloudFlare 请求日志的时候发现,网站存在被大量的脚本进行恶意请求的记录
记录显示脚本在不断的尝试各种 WordPress 资源以及配置文件的访问
虽然我的网站是通过 CloudFlarePage 部署的静态网站,不存在什么信息泄露风险
但是我还有其他网站是部署在服务器上的,一看果然也在一直被扫
于是果断行动起来打算根据这些请求记录的特点进行拦截
一、CloudFlare安全规则拦截非正常访问
通过查看请求日志,发现大量的恶意脚本连最简单的伪装的没有
从用户代理 User-agent 可以看出,这些脚本大部分由 Python Go编写
这两个语言发起 HTTP 请求时,用户代理通常包含 python-urllib python-requests 或 go-http-client
当然对于其他语言来说同样也是有着其独特的 User-agent
对于正常用户通过现代浏览器发起访问来说,用户代理中一定包含 Mozilla/5.0
或者绝对点说一定是 Mozilla/5.0 开头,这是基于历史的妥协也是一种规范
既然如此便可以通过拦截非浏览器标识的用户代理来做到初步的拦截
1.1 阻止脚本访问
对于用户代理来说,其并没有填写规范,甚至可以随意编写
同时对于很多编程语言来说,可能不同版本中用户代理的大小写也是不一定的
并且 CloudFlare 的安全规则对于非付费用户来说不提供正则表达式的书写,因此只能一项一项的进行枚举匹配
示例:要求用户代理中必须包含Mozilla,并且禁止空用户代理和常见的脚本用户代理
(not http.user_agent contains "Mozilla") or(http.user_agent eq "") or(http.user_agent contains "curl") or (http.user_agent contains "Curl") or(http.user_agent contains "wget") or (http.user_agent contains "Wget") or(http.user_agent contains "go-http-client") or (http.user_agent contains "Go-http-client") or(http.user_agent contains "python-requests") or (http.user_agent contains "Python-requests") or(http.user_agent contains "python-urllib") or (http.user_agent contains "Python-urllib") or(http.user_agent contains "urllib3") or (http.user_agent contains "Urllib3") or(http.user_agent contains "aiohttp") or (http.user_agent contains "Aiohttp") or(http.user_agent contains "httpx") or (http.user_agent contains "Httpx") or(http.user_agent contains "java/") or (http.user_agent contains "Java/") or(http.user_agent contains "Apache-HttpClient") or (http.user_agent contains "apache-httpclient") or(http.user_agent contains "okhttp") or (http.user_agent contains "OkHttp") or(http.user_agent contains "libwww") or (http.user_agent contains "Libwww") or(http.user_agent contains "node-fetch") or (http.user_agent contains "Node-fetch") or(http.user_agent contains "undici") or (http.user_agent contains "Undici") or(http.user_agent contains "axios") or (http.user_agent contains "Axios") or(http.user_agent contains "Scrapy") or (http.user_agent contains "scrapy") or(http.user_agent contains "scanner") or (http.user_agent contains "Scanner") or(http.user_agent contains "nmap") or (http.user_agent contains "Nmap") or(http.user_agent contains "sqlmap") or (http.user_agent contains "Sqlmap") or(http.user_agent contains "masscan") or (http.user_agent contains "Masscan") or(http.user_agent contains "nikto") or (http.user_agent contains "Nikto")NOTE需要注意的是,该规则基于一个前提假设:合法访问主要来自浏览器客户端
探活工具等访问可能由于设置了要求用户代理中必须包含
Mozilla而被拦截,如有需要可删除
部署完成运行了一段时间发现,部分脚本开始伪造用户代理
例如使用访问的 URL 的路径作为用户代理,这样的话也可以添加禁止用户代理是 http 开头的限制
(starts_with(http.user_agent, "http"))1.2 限制请求方式
如果网站为静态网站,那么可以一刀切的只允许进行 Get和 HEAD请求,其他请求全部进行拦截
如果网站需要跨域访问同根子域名的资源,还需要额外放行 OPTIONS 请求
(not http.request.method in {"GET" "HEAD" "OPTIONS"})如果网站提供 api 服务,建议严格限制可以访问的路径以及请求方式
如果是 WordPress 网站不建议限制请求方式,如果必须限制可以拦截不应该使用的请求方式
(http.request.method in {"CONNECT" "TRACE"})如果网站是 WordPress 或使用 PHP,那么可以拦截所有 .php 的访问
(ends_with(http.request.uri.path, ".php"))如果部分网站是 WordPress 或使用 PHP ,还可以排除使用 PHP 的网站的主机名来拦截
(http.host ne "php.example.com" and ends_with(http.request.uri.path, ".php"))1.3 部署安全规则
打开需要配置的域名页 → 安全性 → 安全规则 → 创建规则 → 自定义规则
输入规则名称,例如 恶意脚本访问拦截
下拉页面 → 编辑表达式 → 输入编辑完成的安全规则 → 使用表达式生成器
TIP
- 每一个子项规则都需要使用
()进行包裹- Cloudflare 的表达式生成器对复杂嵌套支持有限,在非付费方案下不适合编写复杂逻辑,更推荐通过多条规则配合执行顺序实现
- 非付费用户无法使用正则表达式
- 单个页面规则最大仅支持 4096 个字符
选择操作为 阻止 并自定义执行顺序,点击保存规则即可开始生效
虽然该拦截同样可以在 Nginx 上进行相关配置,但是最好还是通过 CloudFlare 进行前置拦截,减轻服务器的请求压力
1.4 WordPress 站点的额外拦截
在各类网站中,WordPress 是受到攻击与扫描最为频繁的站点之一
大量自动化脚本会针对 WordPress 常见的 PHP 文件与默认路径进行探测,这类请求通常不具备任何正常业务意义
其中常见的高风险访问路径包括:
-
xmlrpc.php WordPress 提供的远程调用接口,是常见攻击目标之一,历史上存在大量利用与放大攻击场景
在未使用 Jetpack、移动端 App 或第三方发布服务的情况下,建议直接拦截 -
wp-config.php WordPress 核心配置文件,正常情况下不应被 Web 直接访问,任何请求都应视为高风险行为
-
readme.html / install.php 用于安装与说明用途,生产环境中不具备访问意义,可能泄露 WordPress 版本与环境信息
-
phpinfo.php PHP 环境信息输出脚本,不属于 WordPress 组成部分,在生产环境中应视为敏感资源并无条件拦截
所以可以添加如下安全规则进行拦截:
(http.request.uri.path contains "xmlrpc.php") or(http.request.uri.path contains "wp-config.php") or(http.request.uri.path contains "readme.html") or(http.request.uri.path contains "install.php") or(http.request.uri.path contains "phpinfo.php")如果通过插件或配置修改了 WordPress 默认登录入口,并且不再使用 /wp-login.php 与 /wp-admin
则可以将其同步进行拦截
(http.request.uri.path contains "wp-login.php") or(starts_with(http.request.uri.path, "/wp-admin"))二、拦截恶意请求配置文件
完成以上配置并运行一段时间发现,大部分低级的脚本请求都已经进行了拦截
但是通过查阅请求记录发现还是有很多恶意请求是没有被拦截到的
他们为请求设置了正常的用户代理,使用其绕过了用户代理的检查
通过分析请求内容,发现大部分请求都是在请求配置文件,例如 .env .git等配置信息
我突然意识到把这些文件放在网站可访问目录也是极度不安全的
如果服务器没有正常配置防火墙,那么相当于所有的信息都在裸奔
2.1 宝塔 WAF 的拦截参考
在宝塔的企业版本中,其 WAF 功能提供了针对恶意下载和敏感资源扫描的拦截能力
但是此功能需要进行付费,非付费用户无法使用 WAF 功能
同时宝塔 WAF 本质上是通过向 Nginx 配置中注入相应的规则与处理逻辑来实现拦截的
因此,即使在非付费版本中,也可以参考其规则思路,通过 CloudFlare 的页面规则进行拦截或手动在 Nginx 配置文件中编写对应的拦截规则,以达到类似的拦截效果
以下是宝塔 WAF 在恶意下载拦截场景中使用的部分正则匹配规则示例:
# 匹配跨目录访问\.\./\.\./
# 匹配网站配置文件或命令执行历史\.(htaccess|mysql_history|bash_history|DS_Store|idea|user\.ini)
# 匹配备份文件及源码相关文件\.(bak|inc|old|mdb|sql|php~|swp|java|class)$
# 匹配常见目录下的压缩包与数据库备份文件^/(vhost|bbs|host|wwwroot|www|site|root|backup|data|ftp|db|admin|website|web).*\.(rar|sql|zip|tar\.gz|tar)$
# 匹配常见恶意脚本文件名/(hack|shell|spy|phpspy)\.php$
# 匹配 Java Actuator 敏感接口信息泄露/actuator/(env|loggers|shutdown|heapdump|threaddump|mappings|restart|configprops|conditions|refresh)$
# 匹配常见敏感配置文件\.(env|env\.dev|git|git/|git/config)$2.2 通过 CloudFlare进行拦截
正如上文提到的 CloudFlare 非付费用户无法使用正则表达式
因此无法完整的复刻此规则到 CloudFlare 的页面规则中
但是仍然可以参考正则表达式进行配置,实现必要的拦截
拦截规则如下
(http.request.uri.path contains "../") or (http.request.uri.path contains "..\\") or(http.request.uri.path contains "%2e%2e%2f") or (http.request.uri.path contains "%2e%2e%5c") or(http.request.uri.path contains "%2e%2e/") or (http.request.uri.path contains "%2e%2e\\") or(ends_with(http.request.uri.path, "/.htaccess")) or (ends_with(http.request.uri.path, "/.mysql_history")) or(ends_with(http.request.uri.path, "/.bash_history")) or (ends_with(http.request.uri.path, "/.DS_Store")) or(http.request.uri.path eq "/.idea") or (starts_with(http.request.uri.path, "/.idea/")) or(ends_with(http.request.uri.path, "/.user.ini")) or (ends_with(http.request.uri.path, "/.env")) or(ends_with(http.request.uri.path, "/env")) or (ends_with(http.request.uri.path, "/.env.dev")) or(http.request.uri.path eq "/.git") or (starts_with(http.request.uri.path, "/.git/")) or(ends_with(http.request.uri.path, ".bak")) or (ends_with(http.request.uri.path, ".inc")) or(ends_with(http.request.uri.path, ".old")) or (ends_with(http.request.uri.path, ".mdb")) or(ends_with(http.request.uri.path, ".sql")) or (ends_with(http.request.uri.path, ".php~")) or(ends_with(http.request.uri.path, ".swp")) or (ends_with(http.request.uri.path, ".java")) or(ends_with(http.request.uri.path, ".class")) or (ends_with(http.request.uri.path, "/hack.php")) or(ends_with(http.request.uri.path, "/shell.php")) or (ends_with(http.request.uri.path, "/spy.php")) or(ends_with(http.request.uri.path, "/phpspy.php")) or (ends_with(http.request.uri.path, "/actuator/env")) or(ends_with(http.request.uri.path, "/actuator/loggers")) or (ends_with(http.request.uri.path, "/actuator/shutdown")) or(ends_with(http.request.uri.path, "/actuator/heapdump")) or (ends_with(http.request.uri.path, "/actuator/threaddump")) or(ends_with(http.request.uri.path, "/actuator/mappings")) or (ends_with(http.request.uri.path, "/actuator/restart")) or(ends_with(http.request.uri.path, "/actuator/configprops")) or (ends_with(http.request.uri.path, "/actuator/conditions")) or(ends_with(http.request.uri.path, "/actuator/refresh"))TIP此安全规则主要采用根据 URL 结尾路径来匹配,可以比较好的覆盖恶意请求,并且不会误伤正常请求
安全规则部署方式参考上方,建议新建一个独立的安全规则,便于日志筛选
针对压缩文件下载的拦截
以上安全规则并未覆盖 .zip .rar .tar.gz .tar 的拦截
如果确认不提供压缩文件的下载,还可以添加如下规则进行拦截
(ends_with(http.request.uri.path, ".zip")) or (ends_with(http.request.uri.path, ".rar")) or(ends_with(http.request.uri.path, ".tgz")) or (ends_with(http.request.uri.path, ".tar.gz"))2.3 编写 Nginx 配置文件拦截
虽然以上规则已经可以比较良好的进行恶意请求的拦截,但是始终不如正则表达式匹配的完全
因此可以通过向 Nginx 中加载配置文件实现 WAF 同等功能的拦截
2.3.1 编写拦截规则配置文件
该配置需要通过 map 进行定义,才能在 Nginx 中实现全局生效
由于 map 只能定义在 http 作用域内,因此可以使用以下方式进行配置
-
直接编写在 Nginx 主配置文件 中(如
nginx.conf的http块内) -
将
map规则编写在独立的配置文件中,并在主配置文件中通过include引入 -
在对应站点的配置文件中,在 server 块之前定义
map规则 -
同样将
map规则编写在独立的配置文件中,但在对应站点的配置文件中通过include引入,注意同样需要位于server块之前定义
本文采用将 map 规则编写在独立的配置文件中,并在主配置文件中通过 include 引入
以文件名 waf_block_regex.conf 为例,推荐将其放置于 Nginx 的配置目录,如 /www/server/nginx/conf/waf_block_regex.conf
map 规则如下
map $uri $waf_block_by_regex { default 0;
~*\.\./+\.\./ 1; ~*\.(htaccess|mysql_history|bash_history|DS_Store|idea|user\.ini) 1; ~*\.(bak|inc|old|mdb|sql|php~|swp|java|class)$ 1; ~*^/(vhost|bbs|host|wwwroot|www|site|root|backup|data|ftp|db|admin|website|web).*\.(rar|sql|zip|tar\.gz|tar)$ 1; ~*/(hack|shell|spy|phpspy)\.php$ 1; ~*/actuator/(env|loggers|shutdown|heapdump|threaddump|mappings|restart|configprops|conditions|refresh)$ 1; ~*\.(env|env\.dev|git|git/|git/config)$ 1;}2.3.2 修改 nginx.conf 主配置文件
在主配置文件的 http 块中 include 引入 map 规则
实例如下
http { include /www/server/nginx/conf/waf_block_regex.conf;
include /www/server/nginx/conf/vhost/*.conf;}2.3.2 修改相应站点的配置文件
在 location 块中编写判断规则
server {
******
location / { if ($waf_block_by_regex) { return 403;} }}使用以下命令是 Nginx 重新加载配置即可使拦截规则生效
nginx -s reload访问相应网站并搭配 .env .git/ 等路径访问即可验证拦截效果
三、说明
本文提供的 Cloudflare 安全规则主要用于拦截常见的自动化恶意扫描行为
例如无伪装的脚本请求、对敏感路径(如 .env、.git、WordPress 后台文件)的暴力探测等
这些规则能在不改动源站代码的前提下,有效减少无效流量、降低服务器压力,并防止因配置疏忽导致的初级信息泄露
但需明确:
-
这些规则无法防御精心伪造的请求(如使用真实浏览器 UA 或模拟正常用户行为的攻击)
-
若服务依赖非浏览器客户端(如健康检查、API 调用、爬虫等),可能被误拦截
可根据实际 User-Agent 或路径调整规则 -
最根本的安全措施仍是确保敏感文件不被放置在 Web 可访问目录,并遵循最小权限原则配置 Web 服务器
这些规则是第一道过滤网 ,而非安全保险箱
防得住的是不想黑你的,防不住的是真想黑你的人
非商业用途可以使用,但必须注明出处;
若有改编需采用相同许可协议发布。