一次诡异的 root 权限被篡改事件

最近在新服务器上装了宝塔面板,用了几天发现确实很方便,大部分 Linux 系统常用的操作都可以可视化,UI 也简洁直观,比敲命令要方便不少。

但是好景不长,有一天我突然无法通过 SSH 登录我的服务器,一直报错 Permission denied (publickey)😭 Debug 一通之后,发现宝塔面板的一个迷惑操作 😖

文中所有操作在 Debian 11 和宝塔 7.9.4 版本下运行。

检查客户端 SSH 公钥

既然是 ssh 登录报错,直觉上当然是首先怀疑客户端 ssh 操作出了问题。

因为我配置了 ssh 免密登录,并且关闭了密码登录,所以第一时间想办法检查客户端的公钥 id_ras.pub 是否还能对应上服务器 ~/.ssh/authorized_keys 中的配置。检查完后发现公钥在服务器上的配置没问题。

检查服务器 SSHD 配置

既然客户端的公钥没问题,那我开始怀疑是不是服务器上 sshd 服务的配置文件出了什么幺蛾子。

sshd 作为 Linux 系统最核心最基本的服务之一,基本所有发行版本中,配置文件都是 /etc/ssh/sshd.config

一番检查后发现与 ssh 密钥登录有关的关键配置 PubkeyAuthentication yesRSAAuthentication yes 都没有有问题,包括 root 账号登录配置 PermitRootLogin yes 也没问题。

检查客户端 SSH 登录日志

客户端和服务器的配置都没问题,常规检查已经没办法了,只能查日志了。

查日志之前最好是对 ssh 登录的过程有一个基本的了解,否则日志可能也看不大明白。推荐学习阮一峰先生写的这个 SSH 教程

客户端 ssh 密钥登录的基本流程如下:

  1. 客户端向服务器发起 SSH 登录的请求。
  2. 服务器收到用户 SSH 登录的请求,发送一些随机数据给用户,要求用户证明自己的身份。
  3. 客户端收到服务器发来的数据,使用私钥对数据进行签名,然后再发还给服务器。
  4. 服务器收到客户端发来的加密签名后,使用对应的公钥解密,然后跟原始数据比较。如果一致,就允许用户登录。

在客户端 ssh 登录的时候加上 -v 参数,即可查看详细的登录日志:

OpenSSH_8.6p1, LibreSSL 2.8.3
debug1: Reading configuration data /Users/asimov/.ssh/config
debug1: /Users/asimov/.ssh/config line 43: Applying options for deskmini-lan
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 21: include /etc/ssh/ssh_config.d/* matched no files
debug1: /etc/ssh/ssh_config line 54: Applying options for *
debug1: Authenticator provider $SSH_SK_PROVIDER did not resolve; disabling
debug1: Connecting to 192.168.1.6 [192.168.1.6] port 22.
debug1: Connection established.
# 此处省略一些不太重要的日志内容。。。。。。
debug1: SSH2_MSG_EXT_INFO received
debug1: kex_input_ext_info: server-sig-algs=<ssh-ed25519,sk-ssh-ed25519@openssh.com,ssh-rsa,rsa-sha2-256,rsa-sha2-512,ssh-dss,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ecdsa-sha2-nistp256@openssh.com,webauthn-sk-ecdsa-sha2-nistp256@openssh.com>
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey
debug1: Next authentication method: publickey
debug1: Offering public key: /Users/asimov/.ssh/id_rsa RSA SHA256:2feuRGdWIrQv7w4bEhPS+qqslDeLuyWP4kX+gFK7uRU
debug1: Authentications that can continue: publickey
debug1: Trying private key: /Users/asimov/.ssh/id_dsa
debug1: Trying private key: /Users/asimov/.ssh/id_ecdsa
debug1: Trying private key: /Users/asimov/.ssh/id_ecdsa_sk
debug1: Trying private key: /Users/asimov/.ssh/id_ed25519
debug1: Trying private key: /Users/asimov/.ssh/id_ed25519_sk
debug1: Trying private key: /Users/asimov/.ssh/id_xmss
debug1: No more authentication methods to try.
root@192.168.1.6: Permission denied (publickey).

从客户端日志上看到,从读取配置,到建立连接,再到发送登录请求,再到接收签名数据都是正常的,但是最后无法获得服务器的授权,而是得到 root@192.168.1.6: Permission denied (publickey),登录失败。

正常情况下在 SSH2_MSG_SERVICE_ACCEPT received 之后应该是下面这样

debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey
debug1: Next authentication method: publickey
debug1: Offering public key: /Users/asimov/.ssh/id_rsa RSA SHA256:2feuRGdWIrQv7w4bEhPS+qqslDeLuyWP4kX+gFK7uRU
debug1: Server accepts key: /Users/asimov/.ssh/id_rsa RSA SHA256:2feuRGdWIrQv7w4bEhPS+qqslDeLuyWP4kX+gFK7uRU
debug1: Authentication succeeded (publickey).
Authenticated to 192.168.1.6 ([192.168.1.6]:22).
debug1: channel 0: new [client-session]
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: pledge: filesystem full
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug1: client_input_hostkeys: searching /Users/asimov/.ssh/known_hosts for 192.168.1.6 / (none)
debug1: client_input_hostkeys: searching /Users/asimov/.ssh/known_hosts2 for 192.168.1.6 / (none)
debug1: client_input_hostkeys: host key found matching a different name/address, skipping UserKnownHostsFile update
debug1: Remote: /root/.ssh/authorized_keys:2: key options: agent-forwarding port-forwarding pty user-rc x11-forwarding
debug1: Remote: /root/.ssh/authorized_keys:2: key options: agent-forwarding port-forwarding pty user-rc x11-forwarding
debug1: Sending environment.
debug1: channel 0: setting env LANG = "zh_CN.UTF-8"

根据日志分析,基本可以判断,问题大概出在服务器验证身份的过程中。

检查服务器 SSHD 服务日志

前面已经检查过客户端公钥、服务器配置以及客户端登录过程,都没问题,那就只能再查查服务器上 sshd 的日志看看了。

/var/log/auth.log 这个文件保存着所有服务器上 ssh 登录日志,在客户端上再尝试登录一次,然后拉到最后看到报错:

debian sshd[2178]: rexec line 126: Deprecated option RSAAuthentication
debian sshd[2178]: reprocess config line 126: Deprecated option RSAAuthentication
debian sshd[2178]: Authentication refused: bad ownership or modes for file /root/.ssh/authorized_keys
debian sshd[2178]: Connection closed by authenticating user root 192.168.1.4 port 56782 [preauth]

关键信息是这一句 Authentication refused: bad ownership or modes for file /root/.ssh/authorized_keys,意思是 authorized_keys 的权限不对,拒绝登录。

Linux 系统为了保证 ssh 登录的安全,对每个用户的 .ssh 目录及里面的文件有一些权限上的要求,推荐的权限配置为:.ssh 目录对用户读/写/执行,并且不能被其他所有用户访问,目录中的文件对用户读/写,并且不能被其他所有用户访问。
也就是 .ssh 700.ssh/* 600,且 ~/.ssh 目录及其文件拥有者必须与当前登录用户一致

于是我查看 .ssh 的权限如下:

# ~/.ssh 目录权限
drwx------  2 www  www   4096 Sep 21 10:26 .ssh

# ~/.ssh 中文件的权限
-rw-r--r-- 1 www www  565 Sep 21 10:26 id_rsa.pub
-rw------- 1 www www 2602 Sep 21 10:26 id_rsa
-rw------- 1 www www 1308 Sep 21 12:26 authorized_keys

# /root 目录权限
dr-xr-x---   7 www  www   4096 Sep 21 13:02 root

看得出来,~/.ssh 和里面的文件权限没问题,但是我登录的是 root 用户,目录和文件的所有者却是 www,而且整个 /root 文件夹的所有者都变成了 www,这应该就是问题所在了。

于是我用 chown -R root:root /root/ 将整个 /root 文件夹的拥有者改了回来,再次尝试 ssh 登录,成功登录。


至此 ssh 登录认证失败的问题解决了,但是更大的问题来了:是谁改了 /root 的权限 ?!😖

分析 www 用户行为

上面看到 /root 的拥有者被改成了 www 用户,而这个用户我记得是宝塔面板创建的,看名字应该是用来执行 web 服务的。

于是我开始检查在宝塔上创建的 web 服务。

我一共在宝塔上部署了 4 web 个服务:1 个 PHP项目 AriaNg,2 个 Go 项目 Alist 和 Frp,1 个 其他项目 Aria2

一番检查后果然发现,宝塔进程本身虽然是 root 用户持有的,但是宝塔中的 Go 项目其他服务 默认都是用 www 用户执行的
宝塔截图

从进程中也能看到 aria2 服务进程的持有者是 www 用户

root@debian:~# ps -ef | grep aria2
www         5034       1  0 13:03 ?        00:00:00 /opt/aria2/aria2c --conf-path=/opt/aria2/aria2.conf
root        5247    1325  0 13:37 pts/1    00:00:00 grep aria2

于是我一个一个的开关这几个项目(还好不多😂)反复对比前后 /root 目录的权限变化。

最后锁定到 其他服务 中的这个 Aria2 项目,只要这个项目一启动,整个 /root 就会立刻被改成 www 用户拥有。

宝塔面板的迷惑操作

于是我又花了很多时间检查 aria2c,确认无论是二进制文件还是运行的配置文件,确实没有一个地方有涉及到操作 /root 的权限,甚至没有地方用到 /root 目录,所有操作都在 /opt/aria2 目录下进行。

进行到到这儿,我懵逼了一阵,不知道下一步该从何下手 😖

最后我灵机一动,想会不会是宝塔面板在启动项目的时候做了什么奇怪的操作。

于是我找到了宝塔面板中 其他项目 这个功能下对应的目录 /www/server/other_project,这里面有一个 log 文件夹,一个 scripts 文件夹

root@debian:/www/server/other_project# pwd
/www/server/other_project

root@debian:/www/server/other_project# ls
logs  scripts

进入 scripts 目录一看,果然有个 Aria2.sh 文件,打开一看,好家伙:

#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
 cd /root
nohup /opt/aria2/aria2c --conf-path=/opt/aria2/aria2.conf 2>&1 >> /www/server/other_project/logs/Aria2.log &
echo $! > /var/tmp/other_project/Aria2.pid

脚本很简单,就是启动在宝塔面板里配置的 aria2 项目,但是这个 cd /root 让人有些迷惑了,为什么要进入到 /root 目录下启动项目?

更让人费解的是,这个 cd /root 操作虽然迷惑,可并没有操作 /root 的权限,而且根据上面的分析,执行这个脚本,持有这个进程的用户是 www,这个用户并没有修改 /root 目录 owner 的权限。

那究竟宝塔面板究竟是 为何 以及 如何 修改 /root owner 权限的呢?

我把这个项目换用 Go 项目 功能部署就没有上面那些奇怪的问题了。

分析到这儿基本就结束了,再往下就要扒宝塔的代码了,这工程量就有点大了,我打算去官方论坛问问,问出结果了再来更新本文。

更新:在宝塔 官方论坛 和 V2EX 都没问出个所以然,算了😐

评论