Linux防火墙限制PHP访问
之前,WordpPress遭到了黑客入侵,服务器被利用攻击其他服务器了。
虽然重新安装了WordPress,但为了防止还有残留或者未来再次遭到入侵,因此要想办法限制PHP程序对外部网络的访问。为此研究了一下,下面是利用Linux防火墙实现的具体步骤。
PHP配置
为了实现限制功能,首先需要让PHP以专门的用户来运行。而能实现这一目的的只有PHP FPM(PHP FastCGI Process Manager),这种方式Nginx和Apache httpd都能调用,具体安装方法请自行搜索其他资料,下面介绍安装后的配置过程。
新建专门用户
因为是专门用于运行PHP FPM的,因此这个用户不需要登录、不需要密码,新建一个系统用户就行:
sudo useradd -MrU -s /sbin/nologin php-fpm
useradd
是创建用户的命令,选项-M
表示不创建用户目录,-r
表示创建系统用户,-U
表示创建同名用户组,-s
表示设置该用户的shell,后面的/sbin/nologin
表示禁止用户登录的shell,php-fpm
是用户(和用户组)名。
修改PHP配置
然后修改PHP FPM进程池配置文件,对于Ubuntu,其位置在/etc/php/<ver>/fpm/pool.d/
下,<ver>
是安装的PHP FPM版本号,默认的进程池配置文件是www.conf
,用习惯的文本编辑器打开它。这里我的PHP FPM版本是8.4
,使用vim
编辑器:
sudo vim /etc/php/8.4/fpm/pool.d/www.conf
打开后找到[www]
块下的user
和group
两项配置,这就是PHP FPM进程的运行用户和组,改成上面将新建的用户和组。
user = php-fpm
group = php-fpm
注意,不要修改listen.owner
和listen.group
两项,这是socket监听的用户和组,默认是和Nginx的运行用户和组一致(对于Ubuntu均为www-data
)。
修改之后重启PHP FPM服务即可:
sudo systemctl restart php8.4-fpm
修改网站脚本用户和组
为了使修改用户之后的PHP FPM程序能够正确运行脚本和执行文件操作,还要修改对应目录和文件的所属用户和用户组。
如文件在/path/to/dir_of_php_scripts/
下,只需要执行如下命令:
sudo chown -R php-fpm:php-fpm /path/to/dir_of_php_scripts/
chown
是修改文件和目录所属的命令,选项-R
表示对目录下的所有子目录和文件递归执行,php-fpm:php-fpm
冒号前后分别是要修改为的用户和组,最后是要修改的路径。
防火墙配置
防火墙使用iptables
和ip6tables
命令来配置,后者用于IPv6防火墙的配置。对于两者一样的部分就不再赘述了,把iptables
命令改为ip6tables
命令即可,不一样的部分会单独提出。
准备工作
iptables
的配置不是持久化的,会在下次重启时丢失,还需要安装另一个软件包来实现持久化:
sudo apt install iptables-persistent
iptables
的规则是存储在不同的链(Chain)里,不同链对应不同的功能。其中INPUT
链对应入站连接,OUTPUT
链对应出站连接,FORWARD
对应端口转发、NAT等功能。因为和本文无关,不多赘述。
iptables
需要在根用户权限下执行。有几个参数是用于查看已有规则的,下面配置完成后需要用这些参数命令来检查,如无特殊情况就不再单独说明。
sudo iptables -L <chian> -v -n --line-numbers
-L <chian>
用于查看对应链下的规则,<chian>
留空则查看所有链;-v
用于查看详细信息,一般情况下不用那么详细;-n
是不解析所有内容,直接输出数字,会被解析的内容有:端口号(如80
会显示为http
、22
会显示为ssh
等)、协议号(如6
会被解析为tcp
、1
解析为icmp
等)、IP地址(会被逆向解析为对应的域名)等,这个有时会需要与解析过的输出对照;--line-numbers
用于给规则条目前加上编号,方便插入或删除,注意这个编号不是永久的,修改过规则后再次执行这个参数都会变动。
此外,还有四个重要的参数:-A
用于附加规则(即插入到链的末尾),-I
用于插入规则(即插入到链的指定位置或开头),-D
用于删除规则,-R
用于替换规则,-F
用于清空链下的所有规则,-P
用于修改链的默认策略。还有其他对应具体设置内容的参数,下面用到的时候会具体介绍。
iptables
的执行方式是从前往后执行,遇到匹配的条目就执行对应规则,后面的不再执行。所以要把更具体的规则放在前面,更宽泛的规则放在后面。比如禁止所有IP的访问,某个IP除外,但又只禁止访问这个IP的特定端口;那么就要把禁止某IP某端口的规则放到最前面,然后是允许对某IP访问的规则,最后是禁止所有IP访问的规则。
最后,在配置完之后不要忘了持久化:
sudo netfilter-persistent save
新建专用链
为了方便后面配置及和其他链隔离开,最好是新建一个链用于PHP:
sudo iptables -N PHPFPM_OUTPUT
为了生效,需要把这个链附加到用于控制出站的OUTPUT
链上:
sudo iptables -I OUTPUT -j PHPFPM_OUTPUT
-I <chian>
表示插入到链<chain>
的最前面,-j
表示执行的动作,-j
后跟着别的链名就表示执行别的链的规则。
默认禁止所有对外访问
首先,是禁止php-fpm
用户对外的所有访问,因为这条规则最宽泛,需要放在最后,所以就最先添加,后面其他规则都插入到其前面。
sudo iptables -A PHPFPM_OUTPUT -m owner --uid-owner php-fpm -j DROP
-a <chian>
表示附加到链<chian>
的最后;-m <module>
表示使用模块来匹配,owner
就是匹配数据包的发出者;--uid-owner <user>
就是匹配的用户名,-j DROP
就是执行丢弃动作。
允许对本地环回地址和本机IP的访问
因为某些功能通过本地环回地址来实现(比如DNS解析),WordPress也有功能需要访问本机IP来实现,所以需要允许这两项(这里假设本机公网IP是11.22.33.44
:
sudo iptables -I PHPFPM_OUTPUT -m owner --uid-owner php-fpm -o lo -j ACCEPT
sudo iptables -I PHPFPM_OUTPUT -m owner --uid-owner php-fpm -d 11.22.33.44 -j ACCEPT
-o <interface>
表示出站界面;lo
就是本地环回界面。这里没有指定具体的IP,就是对所有环回地址都生效,对于IPv4就是127.0.0.0/8
整个网段(即127.0.0.1
~127.255.255.254
,常用的有127.0.0.1
用于本地服务,127.0.0.53
用于本地DNS服务),对于IPv6就是::1
;-j ACCEPT
表示执行允许动作。-d <destnation>
表示目标IP地址是<destnation>
。
允许对特定IPv4地址的访问
某些服务需要WordPress访问外部API,因此需要对允许这些地址的访问,因为iptables
不能设置域名,所有还要先获取域名对应的IP地址。
这里使用dig
命令来实现,如果没有这个命令可以先安装dnsutils
包:
sudo apt install dnsutils
比如WordPress更新要访问downloads.wordpress.com
,就使用如下命令:
dig downloads.wordpress.com A
最后的A
表示要获取A记录,即IPv4记录。如果有CNAME跳转,dig
会自动完成,不用额外操作,结果如下:
; <<>> DiG 9.18.30-0ubuntu0.24.04.2-Ubuntu <<>> downloads.wordpress.com A
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40182
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;downloads.wordpress.com. IN A
;; ANSWER SECTION:
downloads.wordpress.com. 116 IN CNAME lb.wordpress.com.
lb.wordpress.com. 116 IN A 192.0.78.13
lb.wordpress.com. 116 IN A 192.0.78.12
;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Sun May 11 17:07:54 HKT 2025
;; MSG SIZE rcvd: 101
这里只要关注ANSWER SECTION
的部分就i行了,可以看到这里得到了两个A记录,即192.0.78.13
和192.0.78.12
,需要把两个地址都加入规则:
sudo iptables -I PHPFPM_OUTPUT -m owner --uid-owner php-fpm -d 192.0.78.13 -j ACCEPT
sudo iptables -I PHPFPM_OUTPUT -m owner --uid-owner php-fpm -d 192.0.78.12 -j ACCEPT
这里还可以更严格地限制,因为现在网站基本都是HTTPS的,所以可以干脆地把端口限制为443
:
sudo iptables -I PHPFPM_OUTPUT -m owner --uid-owner php-fpm -d 192.0.78.13 -p tcp --dport 443 -j ACCEPT
sudo iptables -I PHPFPM_OUTPUT -m owner --uid-owner php-fpm -d 192.0.78.12 -p tcp --dport 443 -j ACCEPT
-p <protocol>
表示协议为<protocol>
,--dport <port>
表示目标端口为<port>
,--dport
必须与-p
一起设置。HTTPS就是443端口的TCP协议。对于某些使用了HTTP3的网站,还可以允许443端口上的UDP协议(-p udp --dport 443
)。
允许对特定IPv6地址的访问
有的网站是有IPv6地址的,如果要允许IPv6的访问,还是类似的操作,比如www.feedburner.com
:
dig www.feedburner.com
结果和上面类似还是看ANSWER SECTION
:
; <<>> DiG 9.18.30-0ubuntu0.24.04.2-Ubuntu <<>> www.feedburner.com AAAA
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10650
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;www.feedburner.com. IN AAAA
;; ANSWER SECTION:
www.feedburner.com. 300 IN CNAME www3.l.google.com.
www3.l.google.com. 300 IN AAAA 2404:6800:400a:804::200e
;; Query time: 13 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Sun May 11 17:28:41 HKT 2025
;; MSG SIZE rcvd: 103
可以看到IPv6地址是2404:6800:400a:804::200e
, 类似的设置:
sudo ip6tables -I PHPFPM_OUTPUT -m owner --uid-owner php-fpm -d 2404:6800:400a:804::200e -j ACCEPT
除了命令改成ip6tables
外没有任何区别。
有的网站同时有IPv4和IPv6的,最好都添加,避免意外。
允许一个网段
比如要允许11.22.33.44
~11.22.33.47
这整个网段的地址,可以通过掩码来表示,即11.22.33.44/30
。关于网段包含什么地址,可以在网上找到子网计算器方便计算,这里就不多说了。设置方法和前面没有任何区别:
sudo iptables -I PHPFPM_OUTPUT -m owner --uid-owner php-fpm -d 11.22.33.44/30 -j ACCEPT
通常不建议这么做,因为范围越大,后面要做微调就越麻烦。还有可以设置多个端口的方法,也是一样的理由就不赘述了。
修改、插入和删除规则
首先需要带上--line-number
参数来查看规则:
sudo iptables -L PHPFPM_OUTPUT --line-numbers
得到结果例如:
Chain PHPFPM_OUTPUT (1 references)
num target prot opt source destination
1 ACCEPT all -- anywhere kix07s06-in-f14.1e100.net owner UID match php-fpm
2 ACCEPT all -- anywhere kix07s06-in-f4.1e100.net owner UID match php-fpm
3 ACCEPT all -- anywhere api.akismet.com owner UID match php-fpm
4 ACCEPT all -- anywhere wordpress.org owner UID match php-fpm
5 ACCEPT all -- anywhere api.wordpress.org owner UID match php-fpm
6 ACCEPT all -- anywhere downloads.wordpress.org owner UID match php-fpm
7 ACCEPT all -- anywhere 45.117.101.119.static.xtom.com owner UID match php-fpm
8 ACCEPT all -- anywhere anywhere owner UID match php-fpm
9 DROP all -- anywhere anywhere owner UID match php-fpm
可见iptables
把IP地址都反向解析为域名了,如果不确定是哪一条,可以加上-n
参数来对照。
比如要修改第6条规则,就用如下命令即可:
sudo iptables -R PHPFPM_OUTPUT 6 -m owner --uid-owner php-fpm -d 66.66.66.66 -j ACCEPT
-R <chian> <num>
就是替换链<chain>
的第<num>
条规则,之后的和前面一样。
如果要在第6条规则处插入新规则(第6-9条就往下顺移为第7-10条),就用如下命令:
sudo iptables -I PHPFPM_OUTPUT 1 -m owner --uid-owner php-fpm -d 66.66.66.66 -j ACCEPT
可见这里的-I
参数和在最前面插入的有所不同,-I <chain> <num>
表示在链<chain>
的第<num>
条处插入新规则,其后的规则向下顺移一位。
删除第6条规则:
sudo iptables -D PHPFPM_OUTPUT 6
-D <chain> <num>
表示删除链<chian>
的第<num>
条规则,其后的规则向上顺移一位。
结论
没啥好结论的,下面记录一下WordPress会访问到的几个域名吧:
download.wordpress.org
api.wordpress.org
www.wordpress.org
rest.akismet.com
Akismet垃圾评论拦截工具www.wordfence.com
Wordfence安全工具noc1.wordfence.com
Wordfence安全工具ping.feedburner.com
FeedBurner通告服务rpc.pingomatic.com
Ping-O-Matic通告服务