001package com.streamconverter.security; 002 003import java.io.InputStream; 004import javax.xml.XMLConstants; 005import javax.xml.parsers.DocumentBuilder; 006import javax.xml.parsers.DocumentBuilderFactory; 007import javax.xml.parsers.ParserConfigurationException; 008import javax.xml.stream.XMLInputFactory; 009import javax.xml.transform.TransformerFactory; 010import javax.xml.validation.SchemaFactory; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014/** 015 * XML処理のセキュリティ設定を提供するユーティリティクラス 016 * 017 * <p>このクラスは、XXE(XML外部エンティティ)攻撃や その他のXML関連のセキュリティ脆弱性を防ぐため、 安全なXML処理設定を提供します。 018 * 019 * <p>主な機能: 020 * 021 * <ul> 022 * <li>XXE攻撃防止のためのDocumentBuilderFactory設定 023 * <li>安全なXMLInputFactory設定 024 * <li>安全なTransformerFactory設定 025 * <li>安全なSchemaFactory設定 026 * <li>セキュリティ設定の動的調整 027 * </ul> 028 * 029 * @since 1.0.0 030 */ 031public class SecureXmlConfiguration { 032 033 private static final Logger logger = LoggerFactory.getLogger(SecureXmlConfiguration.class); 034 private static final Logger securityLogger = 035 LoggerFactory.getLogger("com.streamConverter.security"); 036 037 private SecureXmlConfiguration() { 038 // ユーティリティクラスのため、インスタンス化を禁止 039 } 040 041 /** 042 * XXE攻撃を防ぐように設定されたDocumentBuilderFactoryを作成します 043 * 044 * @return 安全に設定されたDocumentBuilderFactory 045 * @throws ParserConfigurationException 設定に失敗した場合 046 */ 047 public static DocumentBuilderFactory createSecureDocumentBuilderFactory() 048 throws ParserConfigurationException { 049 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 050 051 // XXE攻撃防止設定(設定失敗はXXE脆弱性のまま継続するため例外をスロー) 052 // DOCTYPE宣言を無効化 053 factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 054 securityLogger.debug("DOCTYPE declarations disabled"); 055 056 // 外部一般エンティティを無効化 057 factory.setFeature("http://xml.org/sax/features/external-general-entities", false); 058 // 外部パラメータエンティティを無効化 059 factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); 060 // 外部DTDロードを無効化 061 factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); 062 securityLogger.debug("External entities disabled"); 063 064 // JAXP標準の外部リソースアクセス制限 065 // setAttribute は未サポート属性時に IllegalArgumentException を投げる可能性があるため 066 // ParserConfigurationException にラップして fail-fast を維持する 067 try { 068 factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); 069 factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); 070 } catch (IllegalArgumentException e) { 071 ParserConfigurationException pce = 072 new ParserConfigurationException( 073 "Failed to configure secure XML external access attributes"); 074 pce.initCause(e); 075 throw pce; 076 } 077 078 // 追加のセキュリティ設定 079 factory.setNamespaceAware(true); 080 factory.setValidating(false); 081 082 // XMLリーダーのセキュリティ制限 083 factory.setXIncludeAware(false); 084 factory.setExpandEntityReferences(false); 085 086 securityLogger.info("Secure DocumentBuilderFactory created with XXE protection"); 087 return factory; 088 } 089 090 /** 091 * 安全に設定されたDocumentBuilderを作成します 092 * 093 * @return 安全に設定されたDocumentBuilder 094 * @throws ParserConfigurationException 設定に失敗した場合 095 */ 096 public static DocumentBuilder createSecureDocumentBuilder() throws ParserConfigurationException { 097 DocumentBuilderFactory factory = createSecureDocumentBuilderFactory(); 098 return factory.newDocumentBuilder(); 099 } 100 101 /** 102 * XXE攻撃を防ぐように設定されたXMLInputFactoryを作成します 103 * 104 * @return 安全に設定されたXMLInputFactory 105 */ 106 public static XMLInputFactory createSecureXMLInputFactory() { 107 XMLInputFactory factory = XMLInputFactory.newInstance(); 108 109 // 外部エンティティの処理を無効化 110 factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); 111 factory.setProperty(XMLInputFactory.SUPPORT_DTD, false); 112 113 // 追加のセキュリティ設定 114 factory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false); 115 factory.setProperty(XMLInputFactory.IS_VALIDATING, false); 116 117 securityLogger.info("Secure XMLInputFactory created with XXE protection"); 118 return factory; 119 } 120 121 /** 122 * 安全に設定されたTransformerFactoryを作成します 123 * 124 * <p>XXE攻撃防止設定(設定失敗はXXE脆弱性のまま継続するため例外をスロー) 125 * 126 * @return 安全に設定されたTransformerFactory 127 * @throws IllegalStateException セキュリティ設定の適用に失敗した場合 128 */ 129 public static TransformerFactory createSecureTransformerFactory() { 130 TransformerFactory factory = TransformerFactory.newInstance(); 131 132 try { 133 // XMLTransform攻撃を防ぐための設定 134 factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); 135 factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); 136 // 機能制限 137 factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 138 } catch (Exception e) { 139 throw new IllegalStateException( 140 "Failed to configure secure TransformerFactory: XXE protection could not be applied", e); 141 } 142 143 securityLogger.info("Secure TransformerFactory created with XXE protection"); 144 return factory; 145 } 146 147 /** 148 * 安全に設定されたSchemaFactoryを作成します 149 * 150 * <p>XXE攻撃防止設定(設定失敗はXXE脆弱性のまま継続するため例外をスロー) 151 * 152 * @return 安全に設定されたSchemaFactory 153 * @throws IllegalStateException セキュリティ設定の適用に失敗した場合 154 */ 155 public static SchemaFactory createSecureSchemaFactory() { 156 SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 157 158 try { 159 // 外部リソースアクセスを制限 160 factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); 161 factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); 162 // セキュアプロセシング機能を有効化 163 factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 164 } catch (Exception e) { 165 throw new IllegalStateException( 166 "Failed to configure secure SchemaFactory: XXE protection could not be applied", e); 167 } 168 169 securityLogger.info("Secure SchemaFactory created with XXE protection"); 170 return factory; 171 } 172 173 /** 174 * ストリーム処理用に安全なDocumentBuilderを作成します。 175 * 176 * <p>引数の {@code inputStream} はnullチェックにのみ使用され、実際のXML解析はこのメソッド内では行いません。 返された {@code 177 * DocumentBuilder} を使って呼び出し元が解析を行います。 178 * 179 * @param inputStream nullチェック対象のInputStream(nullの場合は {@link IllegalArgumentException} をスロー) 180 * @return ストリーム処理用に安全に設定されたDocumentBuilder 181 * @throws ParserConfigurationException XML設定エラーが発生した場合 182 * @throws IllegalArgumentException inputStreamがnullの場合 183 */ 184 public static DocumentBuilder createSecureDocumentBuilderForStream(InputStream inputStream) 185 throws ParserConfigurationException { 186 187 // 入力検証 188 if (inputStream == null) { 189 throw new IllegalArgumentException("InputStream cannot be null"); 190 } 191 192 DocumentBuilder builder = createSecureDocumentBuilder(); 193 194 // エラーハンドラーの設定(セキュリティ上の理由により詳細エラー情報を制限) 195 builder.setErrorHandler(new SecurityAwareErrorHandler()); 196 197 securityLogger.debug("Secure DocumentBuilder created for InputStream processing"); 198 return builder; 199 } 200} 201 202/** 203 * セキュリティを考慮したXMLエラーハンドラー。 204 * 205 * <p>詳細なエラー情報の外部漏洩を防ぐため、外部向けメッセージには一般的な内容のみを使用する。 内部ログには行・列情報を記録し、デバッグを可能にする。 206 * 207 * <p>このクラスは {@link 208 * SecureXmlConfiguration#createSecureDocumentBuilderForStream(java.io.InputStream)} 209 * 内部での使用を想定してpackage-privateとしており、外部から直接インスタンス化されることを想定していない。 210 */ 211class SecurityAwareErrorHandler implements org.xml.sax.ErrorHandler { 212 private static final Logger logger = LoggerFactory.getLogger(SecurityAwareErrorHandler.class); 213 private static final Logger securityLogger = 214 LoggerFactory.getLogger("com.streamConverter.security"); 215 216 /** 217 * XML解析の警告を処理する。 218 * 219 * <p>セキュリティ上の理由から詳細は抑制し、内部ロガーにはDEBUGレベルで一般的なメッセージのみを記録する。 セキュリティロガーにはWARNレベルで警告発生を記録する。 220 * 221 * @param exception 発生した警告の詳細(ログには詳細情報を含めない) 222 */ 223 @Override 224 public void warning(org.xml.sax.SAXParseException exception) { 225 logger.debug("XML parsing warning (details suppressed for security)"); 226 securityLogger.warn("XML parsing warning detected during secure processing"); 227 } 228 229 /** 230 * XML解析の回復可能エラーを処理する。 231 * 232 * <p>セキュリティコンテキストでは回復可能エラーも失敗として扱う(不正XMLを後続処理に渡さない)。 内部ログには行・列情報を記録するが、外部エラーメッセージには詳細を含めない。 233 * 234 * @param exception 発生したエラーの詳細 235 * @throws org.xml.sax.SAXException 常にスローし、XML処理を中断する 236 */ 237 @Override 238 public void error(org.xml.sax.SAXParseException exception) throws org.xml.sax.SAXException { 239 logger.warn( 240 "XML parsing error at line {}, column {}", 241 exception.getLineNumber(), 242 exception.getColumnNumber()); 243 securityLogger.warn("XML parsing error detected during secure processing"); 244 throw new org.xml.sax.SAXException("XML processing failed: parsing error detected", exception); 245 } 246 247 /** 248 * XML解析の致命的エラーを処理する。 249 * 250 * <p>セキュリティ制約違反とみなし、詳細情報を外部に漏らさずに処理を中断する。 251 * 252 * @param exception 発生した致命的エラーの詳細 253 * @throws org.xml.sax.SAXException 常にスローし、XML処理を中断する 254 */ 255 @Override 256 public void fatalError(org.xml.sax.SAXParseException exception) throws org.xml.sax.SAXException { 257 securityLogger.error("Fatal XML parsing error detected during secure processing"); 258 throw new org.xml.sax.SAXException("XML processing failed due to security constraints"); 259 } 260}