前書き
Javaプログラムのセキュリティを確保するために、外部ユーザーからの入力はすべて悪意があると見なされ、すべてのユーザー入力をある程度確認する必要があります。
この記事では、ユーザー入力検証のいくつかのシナリオについて説明します。見てみましょう。
文字列の標準化後に検証
通常、文字列検証を実行するときにいくつかの特殊文字をフィルタリングし、フィルタリング後に文字列検証を実行する必要があります。
Javaの文字はUnicodeに基づいてエンコードされています。ただし、Unicodeでは、同じ文字の表現が異なる場合があります。したがって、文字を標準化する必要があります。
文字の標準化の問題に対処するために、Javaには特別なクラスNormalizerがあります。
次の例を見てみましょう。
public void testNormalizer(){
System.out.println(Normalizer.normalize("\u00C1", Normalizer.Form.NFKC));
System.out.println(Normalizer.normalize("\u0041\u0301", Normalizer.Form.NFKC));
}
出力結果:
Á
Á
2つのUnicodeは同じではありませんが、表される最終的な文字は同じであることがわかります。したがって、文字検証を実行するときは、まず正規化する必要があります。
次の例について考えてみます。
public void falseNormalize(){
String s = "\uFE64" + "script" + "\uFE65";
Pattern pattern = Pattern.compile("[<>]"); // 检查是否有尖括号
Matcher matcher = pattern.matcher(s);
if (matcher.find()) {
throw new IllegalStateException();
}
s = Normalizer.normalize(s, Normalizer.Form.NFKC);
}
このうち、\ uFE64は<を、\ uFE65は>を意味します。プログラムの本来の目的は、入力文字列に山括弧が含まれているかどうかを判断することですが、直接入力はUnicode文字であるため、直接コンパイルを検出できません。
コードに次の変更を加える必要があります。
public void trueNormalize(){
String s = "\uFE64" + "script" + "\uFE65";
s = Normalizer.normalize(s, Normalizer.Form.NFKC);
Pattern pattern = Pattern.compile("[<>]"); // 检查是否有尖括号
Matcher matcher = pattern.matcher(s);
if (matcher.find()) {
throw new IllegalStateException();
}
}
最初に正規化操作を実行してから、文字検証を実行します。
信頼できない文字列のフォーマットに注意してください
文字列のフォーマットにはフォーマットを使用することがよくあります。フォーマットした文字列にフォーマット時のユーザー入力情報が含まれている場合は、注意が必要です。
次の例を見てください。
public void wrongFormat(){
Calendar c = new GregorianCalendar(2020, GregorianCalendar.JULY, 27);
String input=" %1$tm";
System.out.format(input + " 时间不匹配,应该是某个月的第 %1$terd 天", c);
}
一見問題はありませんが、入力にはフォーマット情報が含まれており、最終的な出力は次のとおりです。
07 时间不匹配,应该是某个月的第 27rd 天
偽装された形でシステムの内部情報を取得しており、場合によってはシステムの内部ロジックが漏洩している可能性があります。
上記の例では、次に示すように、パラメーターとして入力も使用する必要があります。
public void rightFormat(){
Calendar c = new GregorianCalendar(2020, GregorianCalendar.JULY, 27);
String input=" %1$tm";
System.out.format("%s 时间不匹配,应该是某个月的第 %terd 天",input, c);
}
出力結果:
%1$tm 时间不匹配,应该是某个月的第 27rd 天
Runtime.exec()は慎重に使用してください
システムコマンドを呼び出すためにRuntime.exec()が使用されていることはわかっていますが、悪意のあるユーザーが「rm -rf /」を呼び出すと、すべてがなくなります。
したがって、Runtime.exec()を呼び出すときは、ユーザー入力を検出するように注意する必要があります。
次の例について考えてみます。
public void wrongExec() throws IOException {
String dir = System.getProperty("dir");
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(new String[] {
"sh", "-c", "ls " + dir});
}
上記の例では、システムプロパティからdirを読み取り、次にsystem lsコマンドを実行してdirの内容を表示します。
悪意のあるユーザーがdirを割り当てた場合:
/usr & rm -rf /
次に、システムによって実際に実行されるコマンドは次のとおりです。
sh -c 'ls /usr & rm -rf /'
これは悪意のある削除につながります。
上記の問題を解決するにはいくつかの方法があります。最初の方法は入力をチェックすることです。たとえば、特定の文字を含めるためにdirのみを実行します。
public void correctExec1() throws IOException {
String dir = System.getProperty("dir");
if (!Pattern.matches("[0-9A-Za-z@.]+", dir)) {
// Handle error
}
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(new String[] {
"sh", "-c", "ls " + dir});
}
2番目の方法は、switchステートメントを使用して特定の入力を制限することです。
public void correctExec2(){
String dir = System.getProperty("dir");
switch (dir){
case "/usr":
System.out.println("/usr");
break;
case "/local":
System.out.println("/local");
break;
default:
break;
}
}
もう1つは、Runtime.exec()メソッドを使用するのではなく、javaに付属するメソッドを使用することです。
正規表現マッチング
正規表現の構築プロセスでは、ユーザー定義の入力が使用される場合、入力の検証も必要です。
次の正規表現について考えてみます。
(.*? +public\[\d+\] +.*<SEARCHTEXT>.*)
上記の式は、public [1234]などのログ情報でユーザー入力を検索するためのものです。
ただし、ユーザーは実際に次の情報を入力できます。
.*)|(.*
最終的に正規表現は次のようになります。
(.*? +public\[\d+\] +.*.*)|(.*.*)
これにより、すべてのログ情報が照合されます。
また、2つの解決策があります。1つは、ホワイトリストを使用してユーザー入力を決定することです。1つは、Pattern.quote()を使用して悪意のある文字をエスケープすることです。
この記事のコード:
learn-java-base-9-to-20 / tree / master / security
この記事はhttp://www.flydean.com/java-security-code-line-input/に含まれています
最も人気のある解釈、最も深遠な乾物、最も簡潔なチュートリアル、そしてあなたが知らない多くのヒントが、あなたが発見するのを待っています!
私の公式アカウントに注意を払うことを歓迎します:「それらをプログラムする」、テクノロジーを知っている、あなたをもっとよく知っている!