Vulnerabilidades graves corrigidas em All In One SEO Plugin Versão 4.1.5.3

Publicados: 2021-12-15

Durante uma auditoria interna do plugin All In One SEO, descobrimos uma vulnerabilidade de SQL Injection e um bug de Privilege Escalation.

Se explorada, a vulnerabilidade do SQL Injection pode conceder aos invasores acesso a informações privilegiadas do banco de dados do site afetado (por exemplo, nomes de usuário e senhas com hash).

O bug de escalonamento de privilégios que descobrimos pode conceder acesso a agentes mal-intencionados a endpoints protegidos da API REST aos quais eles não deveriam ter acesso. Isso pode permitir que usuários com contas com poucos privilégios, como assinantes, executem a execução remota de código nos sites afetados.

Relatamos as vulnerabilidades ao autor do plug-in por e-mail, e eles lançaram recentemente a versão 4.1.5.3 para resolvê-las. É altamente recomendável que você atualize para a versão mais recente do plug-in e tenha uma solução de segurança estabelecida em seu site, como o Jetpack Security.

Detalhes

Nome do plugin: Tudo em um SEO
URI do plug-in: https://wordpress.org/plugins/all-in-one-seo-pack/
Autor: https://aioseo.com/

As vulnerabilidades

Escalonamento de privilégio autenticado

Versões afetadas: Todas as versões entre 4.0.0 e 4.1.5.2 inclusive.
CVE-ID: CVE-2021-25036
CVSSv3.1: 9,9
CWSS: 92,1

	/**
	 * Validates access from the routes array.
	 *
	 * @since 4.0.0
	 *
	 * @param  \WP_REST_Request $request The REST Request.
	 * @return bool                      True if validated, false if not.
	 */
	public function validateAccess( $request ) {
		$route     = str_replace( '/' . $this->namespace . '/', '', $request->get_route() );
		$routeData = isset( $this->getRoutes()[ $request->get_method() ][ $route ] ) ? $this->getRoutes()[ $request->get_method() ][ $route ] : [];

		// No direct route name, let's try the regexes.
		if ( empty( $routeData ) ) {
			foreach ( $this->getRoutes()[ $request->get_method() ] as $routeRegex => $routeInfo ) {
				$routeRegex = str_replace( '@', '\@', $routeRegex );
				if ( preg_match( "@{$routeRegex}@", $route ) ) {
					$routeData = $routeInfo;
					break;
				}
			}
		}

		if ( empty( $routeData['access'] ) ) {
			return true;
		}

		// We validate with any of the access options.
		if ( ! is_array( $routeData['access'] ) ) {
			$routeData['access'] = [ $routeData['access'] ];
		}
		foreach ( $routeData['access'] as $access ) {
			if ( current_user_can( $access ) ) {
				return true;
			}
		}

		if ( current_user_can( apply_filters( 'aioseo_manage_seo', 'aioseo_manage_seo' ) ) ) {
			return true;
		}

		return false;
	}

As verificações de privilégios aplicadas pelo All In One SEO para proteger os endpoints da API REST continham um bug muito sutil que poderia conceder aos usuários com contas com poucos privilégios (como assinantes) acesso a todos os endpoints registrados pelo plug-in.

O método Api::validateAccess() depende da rota da API REST sendo solicitada para saber quais verificações de privilégio devem ser aplicadas em uma determinada solicitação. Como não leva em conta o fato de o WordPress tratar as rotas da API REST como strings que não diferenciam maiúsculas de minúsculas, alterar um único caractere para maiúscula ignoraria completamente a rotina de verificações de privilégios.

Isso é particularmente preocupante porque alguns dos terminais do plug-in são bastante sensíveis. Por exemplo, o aioseo/v1/htaccess pode reescrever o .htaccess de um site com conteúdo arbitrário. Um invasor pode abusar desse recurso para ocultar backdoors .htaccess e executar código malicioso no servidor.

Injeção SQL autenticada

Versões afetadas: Todas as versões entre 4.1.3.1 e 4.1.5.2 inclusive.
CVE-ID: CVE-2021-25037
CVSSv3.1: 7,7
CWSS: 80,4

/**
 * Searches for posts or terms by ID/name.
 *
 * @since 4.0.0
 *
 * @param  \WP_REST_Request  $request The REST Request
 * @return \WP_REST_Response          The response.
 */
public static function searchForObjects( $request ) {
    $body = $request->get_json_params();
 
    if ( empty( $body['query'] ) ) {
        return new \WP_REST_Response( [
            'success' => false,
            'message' => 'No search term was provided.'
        ], 400 );
    }
    if ( empty( $body['type'] ) ) {
        return new \WP_REST_Response( [
            'success' => false,
            'message' => 'No type was provided.'
        ], 400 );
    }
 
    $searchQuery = aioseo()->db->db->esc_like( $body['query'] );
 
    $objects        = [];
    $dynamicOptions = aioseo()->dynamicOptions->noConflict();
    if ( 'posts' === $body['type'] ) {
 
        $postTypes = aioseo()->helpers->getPublicPostTypes( true );
        foreach ( $postTypes as $postType ) {
            // Check if post type isn't noindexed.
            if ( $dynamicOptions->searchAppearance->postTypes->has( $postType ) && ! $dynamicOptions->searchAppearance->postTypes->$postType->show ) {
                $postTypes = aioseo()->helpers->unsetValue( $postTypes, $postType );
            }
        }
 
        $objects = aioseo()->db
            ->start( 'posts' )
            ->select( 'ID, post_type, post_title, post_name' )
            ->whereRaw( "( post_title LIKE '%{$searchQuery}%' OR post_name LIKE '%{$searchQuery}%' OR )" )
            ->whereIn( 'post_type', $postTypes )
            ->whereIn( 'post_status', [ 'publish', 'draft', 'future', 'pending' ] )
            ->orderBy( 'post_title' )
            ->limit( 10 )
            ->run()
            ->result();

O método PostsTerms::searchForObjects(), que é acessível através da rota da API REST /wp-json/aioseo/v1/objects escapou apenas da entrada do usuário usando wpdb::esc_like() antes de anexar essa entrada a uma consulta SQL. Como esse método não foi projetado para escapar de aspas, um invasor ainda pode injetá-las e forçar a consulta a vazar informações confidenciais do banco de dados, como credenciais do usuário.

Embora esse ponto de extremidade não fosse acessível a usuários com contas com poucos privilégios, o vetor de ataque de escalonamento de privilégios mencionado acima possibilitou que eles abusassem dessa vulnerabilidade.

Linha do tempo

2021-12-01 – Contato inicial com All In One SEO
2021-12-02 – Enviamos a eles detalhes sobre essas vulnerabilidades
2021-12-08 – All In One SEO 4.1.5.3 é lançado

Conclusão

Recomendamos que você verifique qual versão do plugin All In One SEO seu site está usando e, se estiver dentro do intervalo afetado, atualize-o o mais rápido possível!

Na Jetpack, trabalhamos duro para garantir que seus sites estejam protegidos contra esses tipos de vulnerabilidades. Recomendamos que você tenha um plano de segurança para seu site que inclua verificação e backups de arquivos maliciosos. O Jetpack Security é uma ótima opção de segurança do WordPress para garantir que seu site e visitantes estejam seguros.

Créditos

Pesquisador original: Marc Montpas

Agradecemos ao restante da equipe do Jetpack Scan pelos comentários, ajuda e correções.