Серьезные уязвимости исправлены в плагине All In One SEO версии 4.1.5.3

Опубликовано: 2021-12-15

Во время внутреннего аудита плагина All In One SEO мы обнаружили уязвимость SQL Injection и ошибку повышения привилегий.

В случае эксплуатации уязвимость «Внедрение SQL» может предоставить злоумышленникам доступ к привилегированной информации из базы данных уязвимого сайта (например, к именам пользователей и хешированным паролям).

Обнаруженная нами ошибка повышения привилегий может предоставить злоумышленникам доступ к защищенным конечным точкам REST API, к которым у них не должно быть доступа. В конечном итоге это может позволить пользователям с учетными записями с низким уровнем привилегий, таким как подписчики, выполнять удаленное выполнение кода на затронутых сайтах.

Мы сообщили об уязвимостях автору плагина по электронной почте, и недавно они выпустили версию 4.1.5.3 для их устранения. Мы настоятельно рекомендуем вам обновиться до последней версии плагина и иметь установленное решение безопасности на вашем сайте, такое как Jetpack Security.

Подробности

Название плагина: Все в одном SEO
URI плагина: https://wordpress.org/plugins/all-in-one-seo-pack/
Автор: https://aioseo.com/

Уязвимости

Повышение привилегий с проверкой подлинности

Затрагиваемые версии: все версии от 4.0.0 до 4.1.5.2 включительно.
CVE-ID: CVE-2021-25036
CVSSv3.1: 9,9
КВСС: 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;
	}

Проверки привилегий, применяемые All In One SEO для защиты конечных точек REST API, содержали очень тонкую ошибку, которая могла предоставить пользователям с учетными записями с низкими привилегиями (например, подписчикам) доступ к каждой конечной точке, регистрируемой плагином.

Метод Api::validateAccess() зависит от запрашиваемого маршрута REST API, чтобы узнать, какие проверки привилегий применять для данного запроса. Поскольку не учитывался тот факт, что WordPress обрабатывает маршруты REST API как строки без учета регистра, замена одного символа на верхний регистр полностью обошла бы процедуру проверки привилегий.

Это особенно беспокоит, потому что некоторые конечные точки плагина довольно чувствительны. Например, конечная точка aioseo/v1/htaccess может перезаписать .htaccess сайта с произвольным содержимым. Злоумышленник может использовать эту функцию, чтобы скрыть бэкдоры .htaccess и выполнить вредоносный код на сервере.

Аутентифицированная SQL-инъекция

Затрагиваемые версии: все версии от 4.1.3.1 до 4.1.5.2 включительно.
CVE-ID: CVE-2021-25037
CVSSv3.1: 7.7
КВСС: 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();

Метод PostsTerms::searchForObjects(), доступный через маршрут REST API /wp-json/aioseo/v1/objects , избегал пользовательского ввода только с помощью wpdb::esc_like() перед добавлением указанного ввода в SQL-запрос. Поскольку этот метод не предназначен для экранирования кавычек, злоумышленник все равно может внедрить их и заставить запрос выдать конфиденциальную информацию из базы данных, например учетные данные пользователя.

Хотя эта конечная точка не должна была быть доступна пользователям с учетными записями с низким уровнем привилегий, вышеупомянутый вектор атаки с повышением привилегий позволил им злоупотребить этой уязвимостью.

График

01.12.2021 — Первый контакт с All In One SEO
2021-12-02 — Мы отправляем им подробную информацию об этих уязвимостях.
2021-12-08 — Выпущен All In One SEO 4.1.5.3

Вывод

Мы рекомендуем вам проверить, какую версию плагина All In One SEO использует ваш сайт, и, если она находится в пределах затронутого диапазона, обновить ее как можно скорее!

В Jetpack мы прилагаем все усилия, чтобы ваши веб-сайты были защищены от уязвимостей такого типа. Мы рекомендуем вам иметь план безопасности для вашего сайта, который включает сканирование вредоносных файлов и резервное копирование. Jetpack Security — это отличный вариант безопасности WordPress для обеспечения безопасности вашего сайта и посетителей.

Кредиты

Оригинальный исследователь: Марк Монпас

Спасибо остальной команде Jetpack Scan за отзывы, помощь и исправления.