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}