【API Management】リクエストURLに制御文字が入り込んでAPIが投げられなかったときの話【Azure】

# はじめに

私の組織では、Azure API Managementを活用したAPIサーバ（社内向け）を運用して３ヶ月ほど経つのですが、突如「APIが投げられないんだが」と利用者から連絡がありました。

結論として「リクエストURLに制御文字が入り込んでいた」ことでAPI Managementが正しいリクエストとしてそもそも受領できていないことが発覚し、それを突き止めるまで工数がかかってしまいました...

その際の備忘録です。

# 運用しているシステムの概要

ユーザ情報を格納するDBを管理している社内向けの既存APIサーバ（ここではサーバAとします）があり、私たちの組織では、そのサーバからユーザ情報を取得して活用するシステム（ここではサーバBとします）を開発・運用しています。

ユーザ情報には以下が含まれています。

- ユーザのグループ名（例：「社員グループA」）
- ユーザの姓と名（例：「山田」「太郎」）

以下のような流れで、私たちが運用しているシステムにユーザ情報が連携される仕組みです。
サーバBは、サーバAに格納されているユーザのグループ名や、そのグループに所属するユーザを一覧で返すAPIを実装しています。

手順1. サーバA管理者がサーバA操作端末を使い、グループ名を指定して新規ユーザを登録
手順2. サーバB利用者が専用モバイルアプリを使い、サーバAに登録されているグループの一覧をサーバB経由で取得(自動で取得される)
手順3. サーバB利用者が専用モバイルアプリを使い、2.で得たグループのうち特定のグループに所属するユーザ一覧をサーバB経由で取得

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1373557/7e935bb7-a6f3-3402-f63c-dd05d72c33ab.png)


# 起きた現象

前提として、モバイルアプリがサーバ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リクエストを投げると、正常に処理ができていました。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1373557/629f3b8d-f069-d6e7-eea4-9ed8f37dbeea.png)


グループ名はモバイルアプリに表示されるため、それを確認すると、不自然に名称の前にスペースが空いていることがわかりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1373557/fa29d608-c45a-56e7-26d6-65b433f67e8d.png)


# 調査結果

URLエンコードした結果をよく見ると、冒頭に「`%09`」とあります。
これは、ASCIIの制御文字「水平タブ」をURLエンコードしたものを示しています。

ASCIIにおいて、制御文字は`0x00`:NUL〜`0x1f`:US、`0x7f`:DELとして定義されていて、`0x20`以降に通常使う文字(半角スペース、!、"、#、...）と文字が続きます。
これらの制御文字はUnicodeに引き継がれています。

http://www3.nit.ac.jp/~tamura/ex2/ascii.html


（中略）
0x07	BEL（警告）
0x08	BS（後退）
**0x09	HT（水平タブ）**
0x0a	LF（改行）
0x0b	VT（垂直タブ） 
（中略）

API Managementでは、**制御文字などの特殊な機能を持つ文字がURLに入っていたら遮断する、という挙動によって拒否されている**と分かりました。（おそらく、API Managementデプロイ時に自動で構成されたWAFのセキュリティ対策機能だと思います）

したがって、意図せずリクエストURLに制御文字（`0x00`〜`0x1f`、`0x7f`）の文字が入らないよう、サーバ、クライアント両方で対策が必要です。
（もちろん、リクエストボディやヘッダーに含まれる情報も確認が必要です）

`%09`、`%0A`などのように、通常の文字と同じように制御文字をエンコードすること自体はできてしまうため、簡単にURLに制御文字が紛れ込んでしまいます。

```
# URLとしての見た目は普通だが、制御文字の一つ「水平タブ」0x09が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
```

# グループ名に制御文字が入り込んだ原因

- 手順1.「サーバA管理者がサーバA操作端末を使い、グループ名を指定して新規ユーザを登録」の際に、グループ名のテキストボックスで誤ってタブキーを押してしまい、それがDBにもエスケープシーケンスの`\t`がそのまま反映されてしまったようです。
- 手順2. 手順3.では、グループ名に紛れた「`\t`」をそのまま引き継いでしまい、API Managementに送信するURLにもタブのURLエンコード文字列`%09`がそのまま混じってしまいました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1373557/595f9ffd-5140-f451-d2fa-fc34e64d1a16.png)


# 結論

システム開発においては当たり前といえば当たり前ですが、実際に体験して実感しました。。

- サーバ側
  - DBに保存可能な文字種、HTTPリクエストで受け入れる文字種を制限しましょう。
- クライアントアプリ側
  - APIをたたく前にテキストボックスの入力値チェックは入念に行いましょう。
  - APIで取得した文字列に不正な文字が含まれていないかチェックしましょう。

# おわりに

運用開始からしばらくこのような不具合は起きていなかったため、原因調査に時間がかかってしまいました。
システムが受け入れる文字種の制限やクライアント側の入力値チェックがいかに重要か思わせる不具合対応でした。