Graves vulnerabilidades solucionadas en la versión 4.1.5.3 del complemento SEO todo en uno

Publicado: 2021-12-15

Durante una auditoría interna del complemento All In One SEO, descubrimos una vulnerabilidad de inyección SQL y un error de escalada de privilegios.

Si se explota, la vulnerabilidad de inyección SQL podría otorgar a los atacantes acceso a información privilegiada de la base de datos del sitio afectado (por ejemplo, nombres de usuario y contraseñas cifradas).

El error de escalada de privilegios que descubrimos puede otorgar a los malos acceso a los puntos finales protegidos de la API REST a los que no deberían tener acceso. En última instancia, esto podría permitir a los usuarios con cuentas de privilegios bajos, como los suscriptores, realizar la ejecución remota de código en los sitios afectados.

Informamos las vulnerabilidades al autor del complemento por correo electrónico y recientemente lanzaron la versión 4.1.5.3 para abordarlas. Le recomendamos enfáticamente que actualice a la última versión del complemento y que tenga una solución de seguridad establecida en su sitio, como Jetpack Security.

Detalles

Nombre del complemento: SEO todo en uno
URI del complemento: https://wordpress.org/plugins/all-in-one-seo-pack/
Autor: https://aioseo.com/

Las vulnerabilidades

Escalada de privilegios autenticado

Versiones afectadas: todas las versiones entre 4.0.0 y 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;
	}

Las verificaciones de privilegios aplicadas por All In One SEO para asegurar los puntos finales de la API REST contenían un error muy sutil que podría haber otorgado a los usuarios con cuentas con pocos privilegios (como suscriptores) acceso a cada punto final que registra el complemento.

El método Api::validateAccess() se basa en la solicitud de la ruta REST API para saber qué verificaciones de privilegios aplicar en una solicitud determinada. Dado que no tuvo en cuenta el hecho de que WordPress trata las rutas de la API REST como cadenas que no distinguen entre mayúsculas y minúsculas, cambiar un solo carácter a mayúsculas omitiría por completo la rutina de verificación de privilegios.

Esto es particularmente preocupante porque algunos de los puntos finales del complemento son bastante sensibles. Por ejemplo, el aioseo/v1/htaccess puede reescribir el .htaccess de un sitio con contenido arbitrario. Un atacante podría abusar de esta función para ocultar puertas traseras .htaccess y ejecutar código malicioso en el servidor.

Inyección SQL autenticada

Versiones afectadas: Todas las versiones entre 4.1.3.1 y 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();

El método PostsTerms::searchForObjects(), al que se puede acceder a través de la ruta /wp-json/aioseo/v1/objects REST API solo escapó de la entrada del usuario usando wpdb::esc_like() antes de agregar dicha entrada a una consulta SQL. Dado que este método no está diseñado para escapar de las comillas, un atacante aún podría inyectarlas y obligar a la consulta a filtrar información confidencial de la base de datos, como las credenciales de los usuarios.

Si bien este punto final no estaba destinado a ser accesible para usuarios con cuentas de privilegios bajos, el vector de ataque de escalada de privilegios mencionado anteriormente les permitió abusar de esta vulnerabilidad.

Cronología

2021-12-01 – Contacto inicial con All In One SEO
2021-12-02 – Les enviamos detalles sobre estas vulnerabilidades
2021-12-08 – Se lanza All In One SEO 4.1.5.3

Conclusión

Le recomendamos que verifique qué versión del complemento All In One SEO está utilizando su sitio y, si está dentro del rango afectado, ¡actualícelo lo antes posible!

En Jetpack, trabajamos arduamente para asegurarnos de que sus sitios web estén protegidos contra este tipo de vulnerabilidades. Le recomendamos que tenga un plan de seguridad para su sitio que incluya escaneo de archivos maliciosos y copias de seguridad. Jetpack Security es una excelente opción de seguridad de WordPress para garantizar que su sitio y los visitantes estén seguros.

Créditos

Investigador original: Marc Montpas

Gracias al resto del equipo de Jetpack Scan por sus comentarios, ayuda y correcciones.