本記事は、Javaのセキュアコーディングについてのメモとして書かれています。
主にアスキー・メディアワークスから出版されている「Javaセキュアコーディングスタンダード CERT/ Oracle版」を中心に学習をしていきます。
今回は、入力値の検証について学んでいきます。
正規化(標準化)とは
正規化(normalization)とは、データなどを統一的なルールに基づいて扱えるように変形させることを言います。
Webの世界では、ユーザから入力されたデータなどを扱うことが多く、これらの中にはそのまま扱うと危険なデータが混入している可能性があります。
そのため、それらのデータを統一的に扱える形式へと変形させ、その上でデータを無害化しなければなりません。
一般的に、テキスト正規化処理にはUnicode正規化(Unicode Normalization)を行います。
Unicode正規化には以下の4種類の形式があります。
- NFD(Normalization Form Canonical Decomposition):正規化形式D
- NFC(Normalization Form Canonical Composition):正規化形式C
- NFKD(Normalization Form Compatibility(K) Decomposition):正規化形式KD
- NFKC(Normalization Form Compatibility(K) Composition):正規化形式KC
上記の中でKCが最も適した正規化の形式とされているため、本記事ではこちらを使用します。
入力値の検証方法
正規化には、java.text
パッケージのNormalizer
クラスを利用します。
しかし、入力値の検証と正規化の手順を間違えてしまうと意味が無くなってしまいます。
以下は、正規化を入力値の検証後に行ってしまった例になります。
//CheckBeforeNorm.java import java.text.Normalizer; import java.text.Normalizer.Form; import java.util.regex.Matcher; import java.util.regex.Pattern; public class CheckBeforeNorm { public static void checkString(String str) throws IllegalStateException { Pattern pat = Pattern.compile("[<>]"); Matcher mat = pat.matcher(str); if(mat.find()){ throw new IllegalStateException(); } } public static void main(String[] args) { String str = "\uFE64" + "script" + "\uFE65"; checkString(str); str = Normalizer.normalize(str, Form.NFKC); } }
上記の\uFE64
及び\uFE65
は、Unicode表現でそれぞれ﹤
と﹥
を表しており、以下のコードで自環境での符号化方式を確認することができます。
System.out.println(System.getProperty("file.encoding"));
自環境ではutf-8
となっており、正規表現上での<
及び>
はそれぞれ、0X3C
及び0X3E
で符号化されます。
しかし、上記の環境依存(機種依存)による小なり(less than)及び大なり(greater than)はそれぞれ0XEFB9A4
及び0XEFB9A5
でエンコードされるため、正規表現のパターンマッチをすり抜け、IllegalStateException
の例外がスローされることなく実行されてしまいます。
そのため、以下のようにチェックをする前に正規化により、統一的な形式に整える必要があります。
// CheckAfterNorm.java import java.text.Normalizer; import java.text.Normalizer.Form; import java.util.regex.Matcher; import java.util.regex.Pattern; public class CheckAfterNorm { public static void checkString(String str) throws IllegalStateException { Pattern pat = Pattern.compile("[<>]"); Matcher mat = pat.matcher(str); if(mat.find()){ throw new IllegalStateException(); } } public static void main(String[] args) { String str = "\uFE64" + "script" + "\uFE65"; str = Normalizer.normalize(str, Form.NFKC); checkString(str); } }
上記の手順で正規化及びパターンマッチを行うことで、データの検証(validation)を行い、必要に応じてデータの無害化(sanitization)を行う必要があります。
最後に
今回は入力値の検証について学びました。
基本的に入力値の検証や無害化の箇所に問題があると、クロスサイトスクリプティング(XSS)攻撃の脆弱性が生まれてしまうため、正しい手順でデータを検証し、必要に応じて無害化をしなければいけません。
参考書籍
Javaセキュアコーディングスタンダード CERT/ Oracle版