001package com.streamconverter.security; 002 003import com.streamconverter.config.SecurityConfigurationManager; 004import java.util.regex.Pattern; 005import org.slf4j.Logger; 006import org.slf4j.LoggerFactory; 007 008/** 009 * XPathインジェクション攻撃を防ぐためのバリデータークラス 010 * 011 * <p>このクラスは、XPath式の安全性を検証し、 悪意のあるXPath式を検出・ブロックします。 012 * 013 * <p>主な機能: 014 * 015 * <ul> 016 * <li>XPathインジェクション攻撃パターンの検出 017 * <li>危険な関数の使用制限 018 * <li>特殊文字のエスケープ処理 019 * <li>XPath式の構造的検証 020 * <li>セキュリティ設定による動的制御 021 * </ul> 022 * 023 * @since 1.0.0 024 */ 025public class SecureXPathValidator { 026 027 private static final Logger logger = LoggerFactory.getLogger(SecureXPathValidator.class); 028 private static final Logger securityLogger = 029 LoggerFactory.getLogger("com.streamConverter.security"); 030 031 private static final SecurityConfigurationManager securityConfig = 032 SecurityConfigurationManager.getInstance(); 033 034 // XPathインジェクション攻撃パターン(エスケープクォートも含む) 035 private static final Pattern XPATH_INJECTION_PATTERN = 036 Pattern.compile( 037 ".*(;|'\\s*or\\s*'|\"\\s*or\\s*\"|\\\\['\"\\\\]|\\b(union|drop|insert|delete)\\b).*", 038 Pattern.CASE_INSENSITIVE); 039 040 // 危険な関数パターン 041 private static final Pattern DANGEROUS_FUNCTIONS_PATTERN = 042 Pattern.compile( 043 ".*\\b(document|unparsed-text|collection|doc|fn:doc|fn:collection|sql:execute)\\s*\\(.*", 044 Pattern.CASE_INSENSITIVE); 045 046 // 外部参照パターン 047 private static final Pattern EXTERNAL_REFERENCE_PATTERN = 048 Pattern.compile(".*(http://|https://|file://|ftp://|\\.\\./).*", Pattern.CASE_INSENSITIVE); 049 050 // SQLインジェクション類似パターン 051 private static final Pattern SQL_LIKE_INJECTION_PATTERN = 052 Pattern.compile( 053 ".*(union|select|insert|update|delete|drop|create|alter|exec|execute)\\s+.*", 054 Pattern.CASE_INSENSITIVE); 055 056 private SecureXPathValidator() { 057 // ユーティリティクラスのため、インスタンス化を禁止 058 } 059 060 /** 061 * XPath式の安全性を検証します 062 * 063 * @param xpath 検証対象のXPath式 064 * @throws SecurityException XPath式が安全でない場合 065 * @throws IllegalArgumentException xpath引数がnullまたは空の場合 066 */ 067 public static void validateXPath(String xpath) { 068 if (xpath == null || xpath.trim().isEmpty()) { 069 throw new IllegalArgumentException("TreePath expression cannot be null or empty"); 070 } 071 072 // XPath検証が無効な場合はスキップ 073 if (!securityConfig.isXPathValidationEnabled()) { 074 logger.debug("TreePath validation is disabled"); 075 return; 076 } 077 078 String trimmedXpath = xpath.trim(); 079 080 // 基本的なインジェクションパターンチェック 081 validateBasicInjectionPatterns(trimmedXpath); 082 083 // 危険な関数の使用チェック 084 validateDangerousFunctions(trimmedXpath); 085 086 // 外部参照の検出 087 validateExternalReferences(trimmedXpath); 088 089 // SQLインジェクション類似パターンの検出 090 validateSqlLikeInjection(trimmedXpath); 091 092 // 厳格モードでの追加検証 093 if (securityConfig.isXPathStrictModeEnabled()) { 094 validateStrictMode(trimmedXpath); 095 } 096 097 securityLogger.debug("TreePath validation passed: {}", sanitizeForLogging(trimmedXpath)); 098 } 099 100 /** 101 * XPath式をサニタイズします(エスケープ処理) 102 * 103 * @param xpath サニタイズ対象のXPath式 104 * @return サニタイズされたXPath式 105 */ 106 public static String sanitizeXPath(String xpath) { 107 if (xpath == null) { 108 return null; 109 } 110 111 String sanitized = xpath; 112 113 // 危険な文字を除去(エスケープではなく除去) 114 sanitized = sanitized.replaceAll("[';\"]", ""); 115 sanitized = sanitized.replace(";", ""); 116 117 // 改行コードの除去 118 sanitized = sanitized.replace("\\r", ""); 119 sanitized = sanitized.replace("\\n", " "); 120 121 // 連続する空白の正規化 122 sanitized = sanitized.replaceAll("\\s+", " "); 123 124 securityLogger.debug( 125 "TreePath sanitized: {} -> {}", sanitizeForLogging(xpath), sanitizeForLogging(sanitized)); 126 127 return sanitized; 128 } 129 130 /** 131 * XPath式が安全かどうかを判定します(例外を投げない版) 132 * 133 * @param xpath 検証対象のXPath式 134 * @return 安全な場合true、そうでない場合false 135 */ 136 public static boolean isXPathSafe(String xpath) { 137 try { 138 validateXPath(xpath); 139 return true; 140 } catch (SecurityException | IllegalArgumentException e) { 141 securityLogger.warn("Unsafe TreePath detected: {}", sanitizeForLogging(xpath)); 142 return false; 143 } 144 } 145 146 /** 147 * 許可されたXPath関数のリストを取得します 148 * 149 * @return 許可された関数の配列 150 */ 151 public static String[] getAllowedXPathFunctions() { 152 return new String[] { 153 "text()", 154 "node()", 155 "position()", 156 "last()", 157 "count()", 158 "name()", 159 "local-name()", 160 "namespace-uri()", 161 "boolean()", 162 "number()", 163 "string()", 164 "string-length()", 165 "concat()", 166 "substring()", 167 "substring-before()", 168 "substring-after()", 169 "normalize-space()", 170 "translate()", 171 "contains()", 172 "starts-with()", 173 "sum()", 174 "floor()", 175 "ceiling()", 176 "round()" 177 }; 178 } 179 180 // =========================================== 181 // Private Helper Methods 182 // =========================================== 183 184 private static void validateBasicInjectionPatterns(String xpath) { 185 if (XPATH_INJECTION_PATTERN.matcher(xpath).matches()) { 186 securityLogger.warn( 187 "Basic TreePath injection pattern detected: {}", sanitizeForLogging(xpath)); 188 throw new SecurityException( 189 "Potentially malicious TreePath expression detected: basic injection pattern"); 190 } 191 } 192 193 private static void validateDangerousFunctions(String xpath) { 194 if (DANGEROUS_FUNCTIONS_PATTERN.matcher(xpath).matches()) { 195 securityLogger.warn("Dangerous TreePath function detected: {}", sanitizeForLogging(xpath)); 196 throw new SecurityException( 197 "Potentially malicious TreePath expression detected: dangerous function usage"); 198 } 199 } 200 201 private static void validateExternalReferences(String xpath) { 202 if (EXTERNAL_REFERENCE_PATTERN.matcher(xpath).matches()) { 203 securityLogger.warn("External reference in TreePath detected: {}", sanitizeForLogging(xpath)); 204 throw new SecurityException( 205 "Potentially malicious TreePath expression detected: external reference"); 206 } 207 } 208 209 private static void validateSqlLikeInjection(String xpath) { 210 if (SQL_LIKE_INJECTION_PATTERN.matcher(xpath).matches()) { 211 securityLogger.warn( 212 "SQL-like injection pattern in TreePath detected: {}", sanitizeForLogging(xpath)); 213 throw new SecurityException( 214 "Potentially malicious TreePath expression detected: SQL-like injection pattern"); 215 } 216 } 217 218 private static void validateStrictMode(String xpath) { 219 // 厳格モードでの追加検証 220 221 // 長すぎるXPath式を拒否 222 if (xpath.length() > 1000) { 223 securityLogger.warn( 224 "TreePath expression too long in strict mode: {} characters", xpath.length()); 225 throw new SecurityException("TreePath expression exceeds maximum length in strict mode"); 226 } 227 228 // 深いネストを拒否 229 long nestingLevel = xpath.chars().filter(ch -> ch == '[').count(); 230 if (nestingLevel > 10) { 231 securityLogger.warn( 232 "TreePath expression has too deep nesting in strict mode: {} levels", nestingLevel); 233 throw new SecurityException("TreePath expression has excessive nesting in strict mode"); 234 } 235 236 // 複雑な演算子の組み合わせを制限 237 if (xpath.contains("and") && xpath.contains("or") && xpath.contains("not")) { 238 securityLogger.warn( 239 "Complex operator combination in TreePath in strict mode: {}", sanitizeForLogging(xpath)); 240 throw new SecurityException("Complex operator combinations not allowed in strict mode"); 241 } 242 243 // ワイルドカードの過度な使用を制限 244 long wildcardCount = xpath.chars().filter(ch -> ch == '*').count(); 245 if (wildcardCount > 5) { 246 securityLogger.warn( 247 "Excessive wildcard usage in TreePath in strict mode: {} wildcards", wildcardCount); 248 throw new SecurityException("Excessive wildcard usage not allowed in strict mode"); 249 } 250 } 251 252 private static String sanitizeForLogging(String xpath) { 253 if (xpath == null) { 254 return "null"; 255 } 256 257 // ログ出力用のサニタイズ(機密情報や長すぎる文字列の対策) 258 String sanitized = xpath; 259 260 // 長すぎる場合は省略 261 if (sanitized.length() > 100) { 262 sanitized = sanitized.substring(0, 97) + "..."; 263 } 264 265 // 制御文字を除去 266 sanitized = sanitized.replaceAll("[\\p{Cntrl}\\p{Cc}\\p{Cf}\\p{Co}\\p{Cn}]", ""); 267 268 return sanitized; 269 } 270 271 /** セキュリティ設定の現在の状態をログに出力します */ 272 public static void logSecurityStatus() { 273 if (securityConfig.isSecurityLoggingEnabled()) { 274 securityLogger.info("=== TreePath Security Configuration Status ==="); 275 securityLogger.info( 276 "TreePath Validation Enabled: {}", securityConfig.isXPathValidationEnabled()); 277 securityLogger.info( 278 "TreePath Strict Mode Enabled: {}", securityConfig.isXPathStrictModeEnabled()); 279 securityLogger.info("Production Environment: {}", securityConfig.isProductionEnvironment()); 280 securityLogger.info("==========================================="); 281 } 282 } 283}