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]块下的usergroup两项配置,这就是PHP FPM进程的运行用户和组,改成上面将新建的用户和组。

user = php-fpm
group = php-fpm

注意,不要修改listen.ownerlisten.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冒号前后分别是要修改为的用户和组,最后是要修改的路径。

防火墙配置

防火墙使用iptablesip6tables命令来配置,后者用于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会显示为http22会显示为ssh等)、协议号(如6会被解析为tcp1解析为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.13192.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通告服务