API叩くときはしっかりデコードしよう
概要
Hono + LINE Messaging APIを使ってコンビニスイーツをランダムに提供するLINE BOTを動かしていたら、あるときローソンの新商品が取得できない事象が発生しました。
ローソンの新商品と入力すれば、新商品が返信されるのに返信されない
LINE Messaging APIのエラーレスポンスを見ると下記メッセージが送信されており、リクエストボディの内容が不正だったことが分かります。
A message (messages[0]) in the request body is invalid
(訳 : リクエストボディのメッセージ(messages[0])が無効である。)
結論
デコード処理が充分ではなかったことが原因でした。
このLINE BOTでは事前にセブンイレブン、ローソン、ファミリーマートの新商品情報をCloudflare Workers KVに登録しておき、「〇〇(コンビニ名)の新商品」とLINEからメッセージを送信することでKVから新商品の情報を返却します。
KVへの事前登録時にはHTMLRewriter()
を使用して各企業ページのhtmlを解析してKVに登録しておりますが、htmlのデコード処理が充分ではなかったことが原因です。
parseName(text: string): string {
return text
.replace(/"/g, '"')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/ /g, ' ')
.trim();
}
リクエストボディを見返してみる
正常終了したセブンイレブンのリクエストボディとローソンのリクエストボディを比較して見ると、明らかにおかしい文字(é
)がいますね...
[
{
"id": "SevenEleven109ace55-73dd-4d84-8a3e-17df30884325",
"itemName": "ホイップで食べる コーヒーわらび餅",
"itemPrice": "270円(税込291.60円)",
"itemImage": "https://img.7api-01.dp1.sej.co.jp/item-image/112483/F85269A8CE1D29DE2AA4012549A9D8A6.jpg",
"itemHref": "https://www.sej.co.jp/products/a/item/112483/kanto/",
"storeType": "SevenEleven",
"metadata": {
"isNew": true,
"releasePeriod": "this_week"
}
},
{
"id": "SevenEleven62cac584-88bf-4a74-b758-e164cba77c31",
"itemName": "ホイップだけどら",
"itemPrice": "178円(税込192.24円)",
"itemImage": "https://img.7api-01.dp1.sej.co.jp/item-image/112482/C81899C7EB0877B9423CF47CCEDFF31C.jpg",
"itemHref": "https://www.sej.co.jp/products/a/item/112482/kanto/",
"storeType": "SevenEleven",
"metadata": {
"isNew": true,
"releasePeriod": "this_week"
}
}
]
[
{
"id": "Lawson0c75b876-b3d1-4c5a-8d03-3cf9b2c1b5c3",
"itemName": "Uchi Café×猿田彦珈琲 カフェラテロールケーキ(コーヒーゼリー入り)",
"itemPrice": "268円(税込)",
"itemImage": "https://www.lawson.co.jp/recommend/original/detail/img/l763344.jpg",
"itemHref": "https://www.lawson.co.jp/recommend/original/detail/1490171_1996.html",
"storeType": "Lawson",
"metadata": {
"isNew": true,
"releasePeriod": "this_week"
}
},
{
"id": "Lawson101f97c7-d5de-49d9-ae0d-0247c3c8d6e9",
"itemName": "Uchi Café×猿田彦珈琲 カフェラテどらもっち",
"itemPrice": "235円(税込)",
"itemImage": "https://www.lawson.co.jp/recommend/original/detail/img/l760181.jpg",
"itemHref": "https://www.lawson.co.jp/recommend/original/detail/1490172_1996.html",
"storeType": "Lawson",
"metadata": {
"isNew": true,
"releasePeriod": "this_week"
}
}
]
解決策
手動デコードを行っていたため、HTMLエンティティのエンコードとデコードをサポートするhtml-entities
を導入しました。
pnpm add html-entities
parseName(text: string): string {
return decode(text).trim();
}
再度「ローソンの新商品」を送信したところ、正常にデコードできていることが確認できました🎉
学び
デコードされていない文字列をAPIで許可してしまうことは、一般的にセキュリティ上のリスクを伴います。
クロスサイトスクリプティング(XSS)のリスク
デコードされていない文字列がそのまま受け入れられると、特にWebアプリケーションではXSS攻撃に対して脆弱になる可能性があります。
例えば、<script>
タグや他のHTMLエンティティがエスケープされないままブラウザでレンダリングされると、悪意のあるスクリプトが実行されてしまう危険があります。
SQLインジェクションのリスク
デコードされていない文字列がAPIを通じてデータベースに渡された場合、SQLインジェクションのリスクが高まります。
これは、攻撃者がデコードされていない文字列を利用して、データベースのクエリを不正に操作する可能性があるためです。
データ汚染のリスク
デコードされていない文字列がデータベースや他のストレージシステムに保存されると、後で取り出した際にデータが汚染されている可能性があります。
これにより、アプリケーションの正常な動作が妨げられるだけでなく、さらにセキュリティリスクが生じる可能性があります。
不正なリクエスト操作のリスク
デコードされていない文字列がそのままAPIで許可されると、攻撃者が意図的に特殊文字を含むリクエストを送信し、APIの動作を不正に操作するリスクがあります。
特にURLやクエリパラメータ内での処理において、このリスクは顕著です。
まとめ
デコード処理の不足が原因で、APIリクエストが不正とされる問題に直面しましたが、適切なデコード処理を導入することで解決に至りました。
この経験から学べるのは、APIやWebアプリケーションを開発する際にはデータの取り扱いにおいて細心の注意が必要ですね。
以上になります👊
Discussion