はじめに
私の組織では、Azure API Managementを活用したAPIサーバ(社内向け)を運用して3ヶ月ほど経つのですが、突如「APIが投げられないんだが」と利用者から連絡がありました。
結論として「リクエストURLに制御文字が入り込んでいた」ことでAPI Managementが正しいリクエストとしてそもそも受領できていないことが発覚し、それを突き止めるまで工数がかかってしまいました...
その際の備忘録です。
運用しているシステムの概要
ユーザ情報を格納するDBを管理している社内向けの既存APIサーバ(ここではサーバAとします)があり、私たちの組織では、そのサーバからユーザ情報を取得して活用するシステム(ここではサーバBとします)を開発・運用しています。
ユーザ情報には以下が含まれています。
- ユーザのグループ名(例:「社員グループA」)
- ユーザの姓と名(例:「山田」「太郎」)
以下のような流れで、私たちが運用しているシステムにユーザ情報が連携される仕組みです。
サーバBは、サーバAに格納されているユーザのグループ名や、そのグループに所属するユーザを一覧で返すAPIを実装しています。
手順1. サーバA管理者がサーバA操作端末を使い、グループ名を指定して新規ユーザを登録
手順2. サーバB利用者が専用モバイルアプリを使い、サーバAに登録されているグループの一覧をサーバB経由で取得(自動で取得される)
手順3. サーバB利用者が専用モバイルアプリを使い、2.で得たグループのうち特定のグループに所属するユーザ一覧をサーバB経由で取得
起きた現象
前提として、モバイルアプリがサーバBにAPIを投げる際のリクエストURLについては、正しくURLエンコードできている(日本語や記号類が混じった文字列をエンコード)と仮定します。
そんな中、 手順2で得られた 「ある特定のグループ名」 のユーザ一覧を手順3で取得しようとしたところ、毎回400 Bad Request
が返却されてしまいました。
一見、正常に処理ができたリクエストと、今回不具合が起きたリクエストを見ても全く違いがわかりません。
# 特定グループのユーザ一覧を取得するAPIのリクエストパスの形式
https://hogehoge.azure-api.net/v1/groups/<グループ名>/people
# 正常に処理ができたリクエストURL
https://hogehoge.azure-api.net/v1/groups/%E7%A4%BE%E5%93%A1%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97A/people
# 400エラーが起きたリクエストURL
https://hogehoge.azure-api.net/v1/groups/%09%E7%A4%BE%E5%93%A1%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97B/people
しかし、API Managementを経由せず、仮想ネットワーク内からcurlコマンドでHTTPリクエストを投げると、正常に処理ができていました。
グループ名はモバイルアプリに表示されるため、それを確認すると、不自然に名称の前にスペースが空いていることがわかりました。
調査結果
URLエンコードした結果をよく見ると、冒頭に「%09
」とあります。
これは、ASCIIの制御文字「水平タブ」をURLエンコードしたものを示しています。
(中略)
0x07 BEL(警告)
0x08 BS(後退)
0x09 HT(水平タブ)
0x0a LF(改行)
0x0b VT(垂直タブ)
(中略)
API Managementでは、制御文字などの特殊な機能を持つ文字がURLに入っていたら遮断する、という挙動によって拒否されていると分かりました。(おそらく、API Managementデプロイ時に自動で構成されたWAFのセキュリティ対策機能だと思います)
グループ名に制御文字が入り込んだ原因
- 手順1.「サーバA管理者がサーバA操作端末を使い、グループ名を指定して新規ユーザを登録」の際に、グループ名のテキストボックスで誤ってタブキーを押してしまい、それがDBにもエスケープシーケンスの
\t
がそのまま反映されてしまったようです。 - 手順2. 手順3.では、グループ名に紛れた「
\t
」をそのまま引き継いでしまい、API Managementに送信するURLにもタブのURLエンコード文字列%09
がそのまま混じってしまいました。
結論
システム開発においては当たり前といえば当たり前ですが、実際に体験して実感しました。。
- サーバ側
- DBに保存可能な文字種、HTTPリクエストで受け入れる文字種を制限しましょう。
- クライアントアプリ側
- APIをたたく前にテキストボックスの入力値チェックは入念に行いましょう。
- APIで取得した文字列に不正な文字が含まれていないかチェックしましょう。
おわりに
運用開始からしばらくこのような不具合は起きていなかったため、原因調査に時間がかかってしまいました。
システムが受け入れる文字種の制限やクライアント側の入力値チェックがいかに重要か思わせる不具合対応でした。