WordPressのXML-RPCの設定見直し

ふつーにPingback飛んできてた。こういうことがあるからWebサーバレベルで塞いでたんだけど、JetpackプラグインでWordpress.comと接続するのにXMLRPCを使っているらしく制限を取っ払ってた。他にも設定してたので大丈夫だろうという判断だったんけど大甘だった。

何を信用していいかわからなかったのでコードを読んで、実際に実行して確認した。そもそも前回なぜ俺は動作確認をちゃんとしなかったのか。

なお、これから書くのは全て WordPress 3.9.1 における情報となる。

xmlrpc_enabledフィルタでPinbackは防げない

add_filter('xmlrpc_enabled', '__return_false');

これで防げると思ってたんだけど 実際 pingback.ping は呼び出せた。WordPressのソースを確認したら xmlrpc_enabled フィルタでチェックしているのは認証が必要なパターンのときだった。pingback.pingは防げない・・・

というわけでPinbackを無効にするにはやっぱりxmlrpc_methodsフィルタを使うのが良い。

if ( function_exists( 'add_filter' ) ) {
    add_filter( 'xmlrpc_methods', 'remove_xmlrpc_pingback_ping' );
}
function remove_xmlrpc_pingback_ping($methods)
{
    unset($methods['pingback.ping']);
    return $methods;
}

しかしながら xmlrpc_enabled の方が包括してくれると誤解を招くトラップ。名前が悪いのか、動作が悪いのか。

デフォルトの投稿設定は過去の投稿には影響しない

投稿のデフォルト設定他のブログからの通知 (ピンバック・トラックバック) を受け付ける のチェックを外していたんだけど、この設定はこれから作成する投稿のデフォルト設定なので、 過去の投稿の設定には影響しない。

WordPressは投稿ごとにコメントやピングバックを受け付けるかを設定できる。投稿のデフォルト設定を変えても過去の投稿のその設定は変わらないのである。過去の投稿はまったくの無防備だった。

wp-config.php でもフィルタが動く

最初Pingbackが飛んできて疑ったのは、そもそも「XMLRPCの呼び出し時にテーマのfunction.phpは参照されるのか」と。しかし、実際XMLRPCを呼び出して確認したところ呼ばれていた。

テーマで制御したくなかったのはテーマを変えると設定が失われてしまうからである。そこでwp-config.phpにフィルタの処理を入れてみたがこれが動作した。というわけでxmlrpc_methodsフィルタの指定はwp-config.phpのファイルの最後に入れることにした。

wp-config.php
/** Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');

add_filter('xmlrpc_enabled', '__return_false');
add_filter('xmlrpc_methods', 'remove_xmlrpc_pingback_ping');
function remove_xmlrpc_pingback_ping($methods)
{
    unset($methods['pingback.ping']);
    return $methods;
}

やっぱりWebサーバで塞ぐ

と、ここまで調べたけどWordPressのバージョンが変わって動作が変わることもありえるので、やっぱりWebサーバで塞ぐのが確実。面倒だけど必要な時だけNginxの以下の設定をコメントアウトしてxmlrpc.phpへのアクセスを有効にすることに。

# disable XML-RPC
location = /xmlrpc.php {
    deny all;
}

踏み台攻撃怖い

http://codex.wordpress.org/XML-RPC_Pingback_API
string sourceUri
string targetUri

Pingback_APIの動作確認していてわかったけど sourceUriのページの中身にtargetUri が含まれていないとエラーになる。つまり相手先がちゃんとこちらのブログのページのUrlをリンクしていないとそのPingbackは無効になる。この場合スパムとしても残らない。

また踏み台にされた場合にsourceUriの中身をの取りに行く(これが攻撃になる)がこれはクロールに等しくリソースの取得やJavascriptの実行は行われないのでGoogle Analyticsなどのアクセス数に加算されない。

つまりサーバの監視をしていなければお互い踏み台にされていることも攻撃されていることに気がつかないわけである。しかし普通のブロガーにサーバの監視まで要求するのは難しい。

あとsourceUriの中身をの取りに行くまでにtargetUriのhostがget_option('home')と一致しているか、targetUriの投稿がPingbackを受け付ける設定なっているかが検証されているのでここでエラーになれば踏み台にならなくて済むけど、単純にこっちがDOS攻撃されるだけにもなる。

おまけ

仮想環境に環境構築してデータベースをコピーして、ひたすらXMLRPCを呼び出してた。

XMLRPCのメソッド一覧の取得。

curl localhost/xmlrpc.php -d '<methodCall>
  <methodName>system.listMethods</methodName>
  <params></params>
</methodCall>'

結果。これ見るとウェブサーバで塞ぎたくなるわ。

<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
  <params>
    <param>
      <value>
      <array><data>
  <value><string>system.multicall</string></value>
  <value><string>system.listMethods</string></value>
  <value><string>system.getCapabilities</string></value>
  <value><string>demo.addTwoNumbers</string></value>
  <value><string>demo.sayHello</string></value>
  <value><string>pingback.extensions.getPingbacks</string></value>
  <value><string>pingback.ping</string></value>
  <value><string>mt.publishPost</string></value>
  <value><string>mt.getTrackbackPings</string></value>
  <value><string>mt.supportedTextFilters</string></value>
  <value><string>mt.supportedMethods</string></value>
  <value><string>mt.setPostCategories</string></value>
  <value><string>mt.getPostCategories</string></value>
  <value><string>mt.getRecentPostTitles</string></value>
  <value><string>mt.getCategoryList</string></value>
  <value><string>metaWeblog.getUsersBlogs</string></value>
  <value><string>metaWeblog.deletePost</string></value>
  <value><string>metaWeblog.newMediaObject</string></value>
  <value><string>metaWeblog.getCategories</string></value>
  <value><string>metaWeblog.getRecentPosts</string></value>
  <value><string>metaWeblog.getPost</string></value>
  <value><string>metaWeblog.editPost</string></value>
  <value><string>metaWeblog.newPost</string></value>
  <value><string>blogger.deletePost</string></value>
  <value><string>blogger.editPost</string></value>
  <value><string>blogger.newPost</string></value>
  <value><string>blogger.getRecentPosts</string></value>
  <value><string>blogger.getPost</string></value>
  <value><string>blogger.getUserInfo</string></value>
  <value><string>blogger.getUsersBlogs</string></value>
  <value><string>wp.restoreRevision</string></value>
  <value><string>wp.getRevisions</string></value>
  <value><string>wp.getPostTypes</string></value>
  <value><string>wp.getPostType</string></value>
  <value><string>wp.getPostFormats</string></value>
  <value><string>wp.getMediaLibrary</string></value>
  <value><string>wp.getMediaItem</string></value>
  <value><string>wp.getCommentStatusList</string></value>
  <value><string>wp.newComment</string></value>
  <value><string>wp.editComment</string></value>
  <value><string>wp.deleteComment</string></value>
  <value><string>wp.getComments</string></value>
  <value><string>wp.getComment</string></value>
  <value><string>wp.setOptions</string></value>
  <value><string>wp.getOptions</string></value>
  <value><string>wp.getPageTemplates</string></value>
  <value><string>wp.getPageStatusList</string></value>
  <value><string>wp.getPostStatusList</string></value>
  <value><string>wp.getCommentCount</string></value>
  <value><string>wp.uploadFile</string></value>
  <value><string>wp.suggestCategories</string></value>
  <value><string>wp.deleteCategory</string></value>
  <value><string>wp.newCategory</string></value>
  <value><string>wp.getTags</string></value>
  <value><string>wp.getCategories</string></value>
  <value><string>wp.getAuthors</string></value>
  <value><string>wp.getPageList</string></value>
  <value><string>wp.editPage</string></value>
  <value><string>wp.deletePage</string></value>
  <value><string>wp.newPage</string></value>
  <value><string>wp.getPages</string></value>
  <value><string>wp.getPage</string></value>
  <value><string>wp.editProfile</string></value>
  <value><string>wp.getProfile</string></value>
  <value><string>wp.getUsers</string></value>
  <value><string>wp.getUser</string></value>
  <value><string>wp.getTaxonomies</string></value>
  <value><string>wp.getTaxonomy</string></value>
  <value><string>wp.getTerms</string></value>
  <value><string>wp.getTerm</string></value>
  <value><string>wp.deleteTerm</string></value>
  <value><string>wp.editTerm</string></value>
  <value><string>wp.newTerm</string></value>
  <value><string>wp.getPosts</string></value>
  <value><string>wp.getPost</string></value>
  <value><string>wp.deletePost</string></value>
  <value><string>wp.editPost</string></value>
  <value><string>wp.newPost</string></value>
  <value><string>wp.getUsersBlogs</string></value>
</data></array>
      </value>
    </param>
  </params>
</methodResponse>

Pingbackリクエスト。これを成功するには1番目のURLの内容に2番目のURLを含んでいて、2番目のURLの投稿が存在してPingbackを受け付ける設定になっていないといけない。結構ちゃんとテストするの面倒。

curl -D - localhost/xmlrpc.php  -d '<methodCall>
  <methodName>pingback.ping</methodName>
  <params>
    <param>
      <value>
        <string>http://example.com/</string>
      </value>
    </param>
    <param>
      <value>
        <string>http://localhost/?p=1</string>
      </value>
    </param>
  </params>
</methodCall>'

オレ、WordPressニ、RPCイラナイ。