Python から WordPress に下書きを自動投稿する|画像だけ WAF に 403 で弾かれた

Python から WordPress に下書きを自動投稿する|画像だけ WAF に 403 で弾かれた 開発記録

開発記録(第10回)。AI に日本株の売買判断をさせる投資システム investTool を個人で開発しています。全体像は目次記事にまとめています。

この記事の位置づけ:本体の開発と並行して進めた、ブログの立ち上げの話

この記事の要点
  • WordPress の REST API とアプリケーションパスワードで、Python から記事を下書きで投稿しています(プラグインは不要)
  • 投稿に使うユーザーは、管理者ではなく編集者の権限にしています
  • 画像を送ると5枚中1枚だけサーバーの WAF に 403 で弾かれ、multipart/form-data で送ることで通りました

動作環境

項目 内容
サーバー ConoHa WING(共用のレンタルサーバー)
WordPress アプリケーションパスワードは WordPress 5.6 以降の標準機能
送る側 Python 3.12。確認用のスクリプトは標準ライブラリだけで書いた

やりたかったこと

前回書いたとおり、このブログは AI が書いた記事を API で下書きとして投稿し、人が確認して公開します。そのために、Python から次のことができる必要がありました。

やりたいこと エンドポイント
記事を下書きで作る・更新する POST /wp-json/wp/v2/posts
画像をアップロードする POST /wp-json/wp/v2/media
アイキャッチを付ける 記事の featured_media に画像の ID を入れる

手順

1. 先に6段階で疎通を確かめる

記事を作る仕組みを作り込む前に、サーバーで API が本当に使えるかを確かめるスクリプトを書きました。共用のサーバーでは、認証の情報がサーバーの手前で落とされたり、セキュリティの設定で API が止められたりすることがあるためです。

疎通確認の6段階。REST API に届くか、認証なしは弾かれるか、認証できるか、下書きを作れるか、画像を送れるか、アイキャッチを付けられるか

失敗した段階ごとに、考えられる原因と対処を表示するようにしました。たとえば、パーマリンクの設定が「基本」だと /wp-json/ が使えないため、その場合は設定の変更を案内します。

ConoHa WING では、そのまま通った

環境によっては、認証の情報(Authorization ヘッダー)がサーバーで落とされ、.htaccess に転送の設定を足す必要があります。ConoHa WING では、設定を足さずに6段階すべてが通りました。

2. 投稿用のユーザーは編集者にする

アプリケーションパスワードは、ユーザーごとに発行します。管理者のユーザーで発行すると、そのパスワードが漏れたときにサイトを乗っ取られます(プラグインの追加やユーザーの作成ができるため)。

そこで、投稿専用のユーザーを「編集者」の権限で作り、そのユーザーでパスワードを発行しました。編集者は記事と画像を扱えますが、サイトの設定は変えられません。投稿者(author)ではなく編集者にしたのは、タグを新しく作るのに編集者の権限が必要なためです。

パスワードはコードに書かず、.env から読みます。

3. 記事を下書きで投稿する

data = {
    "title": title,
    "content": html,
    "slug": slug,                 # 必ず指定する
    "status": "draft",            # 自動では公開しない
    "categories": [category_id],
    "excerpt": excerpt,
    "featured_media": eyecatch_id,
}
スラッグは必ず指定する

slug を指定しないと、WordPress はタイトルから日本語の URL を作ります。日本語の URL は、共有したときに %E5%B8%82… のような長い文字列になって読めなくなります。

更新のときは状態を送らない

すでに公開した記事を API で更新するとき(前後の回へのリンクを足すときなど)に status: draft を送ると、公開中の記事が下書きに戻ります。更新では status を送らないようにしています。

つまずいた点と解決

画像だけ、5枚中1枚が 403 で弾かれた

症状
画像を5枚アップロードしたところ、3枚目だけが HTTP 403 になった
原因
サーバーの WAF(不正なアクセスを防ぐ仕組み)が、画像のバイト列を攻撃のパターンと誤って判定したとみられる
対処
本文にバイナリをそのまま載せる送り方をやめ、管理画面と同じ multipart/form-data で送る

疎通の確認では画像のアップロードも通っていたので、問題ないと考えていました。ところが本番の記事の図を5枚送ると、1・2枚目は通ったのに、3枚目だけが 403 になりました。

返ってきたのは WordPress の JSON ではなく、次のような HTML でした。

閲覧できません (Forbidden access)
……
Powered by (WAF の製品名)

WordPress のプラグインではなく、サーバー側の WAF が止めていました。送った画像は図の PNG で、コードも文字列も含んでいません。画像のバイト列が、たまたま攻撃のパターンに一致したと考えられます。

画像の送り方の違い。本文に画像のバイナリをそのまま載せる送り方では5枚中1枚が403になった。multipart/form-data で送ると同じ画像が通った

WordPress の REST API は、画像のバイナリを本文にそのまま載せ、ファイル名を Content-Disposition で渡す送り方を受け付けます。疎通の確認はこの方法でした。これを、ブラウザの管理画面と同じ multipart/form-data に変えたところ、同じ画像が通りました。

boundary = "----investToolBoundary"
body = (
    f"--{boundary}\r\n"
    f'Content-Disposition: form-data; name="file"; filename="{name}"\r\n'
    f"Content-Type: image/png\r\n\r\n"
).encode() + png_bytes + f"\r\n--{boundary}--\r\n".encode()
headers = {"Content-Type": f"multipart/form-data; boundary={boundary}"}
疎通の確認で通った、は本番で通る、ではない

画像しだいで起きる、たまにしか出ない種類の失敗でした。記事を自動で作るようになると、図は毎回変わります。確認で1回通ったことを頼りにせず、WAF に止められにくい送り方にそろえておくことにしました。

実際にどう使っているか

この連載の記事は、すべてこの方法で下書きとして投稿しています。記事の元は Markdown の文書で、投稿のときに HTML への変換、図のアップロード、アイキャッチの設定までをまとめて行います。

まとめ

REST API での投稿そのものは、プラグインなしで簡単に動きました。つまずいたのは、サーバーの手前にある WAF と、公開済みの記事を下書きに戻さないといった運用の細部でした。

この記事の内容に近い書籍

※ Amazon のアソシエイトリンクを含みます。


前回:記事を自動投稿するなら、WordPress のテーマ選びの基準が変わる|無料の Cocoon にした理由

次回:WordPress を立ち上げたら、ログイン名が外から見えていた|見えていた4つの経路と、確かめ方の落とし穴

当サイトは情報提供を目的としており、投資助言を行うものではありません。掲載している売買記録は仮想売買(ペーパートレード)であり、実際の取引の結果ではありません。投資判断はご自身の責任で行ってください。詳しくは免責事項をご覧ください。

タイトルとURLをコピーしました