Goal: Rapidly protect against known threats with minimal code changes
Timeline: 1-2 weeks
Security improvement: Good protection against known dangerous classes
This phase provides immediate security benefits by switching to BlocklistRestrictedXStream in blocklist mode, which blocks known dangerous classes while maintaining broad compatibility.
Replace existing XStream dependency in your pom.xml:
1 2<!-- Remove or comment out existing XStream dependency --> <!-- <dependency> <groupId>com.thoughtworks</groupId> <artifactId>xstream</artifactId> <version>1.4.x</version> </dependency> --> <!-- Add blocklist adapter --> <dependency> <groupId>com.atlassian.security.serialblocklist</groupId> <artifactId>blocklist-xstream-adapter</artifactId> <version>${serialblocklist.version}</version> <scope>provided</scope> </dependency>
For Gradle projects:
1 2// Remove existing XStream dependency // implementation 'com.thoughtworks:xstream:1.4.x' // Add blocklist adapter implementation 'com.atlassian.security.serialblocklist:blocklist-xstream-adapter:${serialblocklist.version}'
1 2XStream xstream = new XStream(); xstream.addPermission(AnyTypePermission.ANY);
1 2import com.atlassian.security.serialblocklist.xstream.BlocklistRestrictedXStream; XStream xstream = new BlocklistRestrictedXStream(); xstream.addPermission(AnyTypePermission.ANY); // Blocklist mode
1 2// Before XStream xstream = new XStream(new DomDriver()); // After XStream xstream = new BlocklistRestrictedXStream(new DomDriver()); xstream.addPermission(AnyTypePermission.ANY);
1 2// Before XStream xstream = new XStream(new PureJavaReflectionProvider()); // After XStream xstream = new BlocklistRestrictedXStream(new PureJavaReflectionProvider()); xstream.addPermission(AnyTypePermission.ANY);
If you use factory methods or dependency injection:
1 2@Bean public XStream xstream() { XStream xstream = new XStream(); xstream.addPermission(AnyTypePermission.ANY); // Existing configuration continues to work xstream.alias("user", User.class); xstream.omitField(User.class, "password"); return xstream; }
1 2@Bean public XStream xstream() { XStream xstream = new BlocklistRestrictedXStream(); xstream.addPermission(AnyTypePermission.ANY); // Blocklist mode // All existing configuration continues to work unchanged xstream.alias("user", User.class); xstream.omitField(User.class, "password"); return xstream; }
1 2public class XStreamFactory { public static XStream createXStream() { XStream xstream = new XStream(); xstream.addPermission(AnyTypePermission.ANY); configureAliases(xstream); return xstream; } }
1 2public class XStreamFactory { public static XStream createXStream() { XStream xstream = new BlocklistRestrictedXStream(); xstream.addPermission(AnyTypePermission.ANY); // Blocklist mode configureAliases(xstream); // Existing configuration works return xstream; } }
1 2public class XStreamSingleton { private static final XStream INSTANCE = createInstance(); private static XStream createInstance() { XStream xstream = new XStream(); xstream.addPermission(AnyTypePermission.ANY); return xstream; } public static XStream getInstance() { return INSTANCE; } }
1 2public class XStreamSingleton { private static final XStream INSTANCE = createInstance(); private static XStream createInstance() { XStream xstream = new BlocklistRestrictedXStream(); xstream.addPermission(AnyTypePermission.ANY); // Blocklist mode return xstream; } public static XStream getInstance() { return INSTANCE; } }
Verify existing functionality still works:
1 2@Test public void shouldMaintainExistingFunctionality() { XStream xstream = new BlocklistRestrictedXStream(); xstream.addPermission(AnyTypePermission.ANY); // Configure as before xstream.alias("user", User.class); User user = new User("john", "john@example.com"); String xml = xstream.toXML(user); User restored = (User) xstream.fromXML(xml); assertEquals("john", restored.getUsername()); assertEquals("john@example.com", restored.getEmail()); }
Verify dangerous classes are now blocked:
1 2@Test public void shouldBlockDangerousClasses() { XStream xstream = new BlocklistRestrictedXStream(); xstream.addPermission(AnyTypePermission.ANY); // This should now throw ForbiddenClassException assertThrows(ForbiddenClassException.class, () -> { String maliciousXml = "<java.lang.ProcessBuilder>" + "<command><string>calc.exe</string></command>" + "</java.lang.ProcessBuilder>"; xstream.fromXML(maliciousXml); }); } @Test public void shouldBlockOtherDangerousClasses() { XStream xstream = new BlocklistRestrictedXStream(); xstream.addPermission(AnyTypePermission.ANY); // Test various dangerous classes String[] dangerousClasses = { "java.lang.Runtime", "java.lang.ProcessBuilder", "javax.script.ScriptEngineManager" // Add more based on your security requirements }; for (String className : dangerousClasses) { assertThrows(ForbiddenClassException.class, () -> { String xml = "<" + className + "></" + className + ">"; xstream.fromXML(xml); }, "Should block " + className); } }
Test end-to-end workflows:
1 2@SpringBootTest @TestPropertySource(properties = { "app.xstream.mode=blocklist" // Test configuration }) class XStreamIntegrationTest { @Autowired private XStream xstream; @Test void shouldProcessLegitimateData() { // Test your actual application data flows ApplicationData data = createTestData(); String xml = xstream.toXML(data); ApplicationData restored = (ApplicationData) xstream.fromXML(xml); assertThat(restored).isEqualTo(data); } @Test void shouldRejectMaliciousData() { String maliciousXml = loadMaliciousTestPayload(); assertThrows(ForbiddenClassException.class, () -> { xstream.fromXML(maliciousXml); }); } }
Deploy to a small subset of traffic first:
1 2@Component @ConditionalOnProperty("feature.secure-xstream.enabled") public class SecureXStreamConfiguration { @Bean @Primary public XStream secureXStream() { XStream xstream = new BlocklistRestrictedXStream(); xstream.addPermission(AnyTypePermission.ANY); // Configure as needed return xstream; } }
1 2@Component public class XStreamFactory { @Value("${features.secure-xstream:false}") private boolean useSecureXStream; public XStream createXStream() { if (useSecureXStream) { XStream xstream = new BlocklistRestrictedXStream(); xstream.addPermission(AnyTypePermission.ANY); return xstream; } else { // Fallback to old implementation during transition XStream xstream = new XStream(); xstream.addPermission(AnyTypePermission.ANY); return xstream; } } }
Add monitoring to track the migration:
1 2@Component public class XStreamMigrationMonitor { private final MeterRegistry meterRegistry; @EventListener public void onSecurityBlock(ForbiddenClassException e) { // Track blocked attempts meterRegistry.counter("xstream.security.blocked", "class", e.getMessage(), "phase", "migration-phase1").increment(); // Log for analysis logger.info("Phase 1 migration blocked dangerous class: {}", e.getMessage()); } @EventListener public void onSuccessfulProcessing(XStreamProcessingEvent e) { meterRegistry.counter("xstream.processing.success", "phase", "migration-phase1").increment(); } }
1 2public class Phase1SecurityValidation { @Test public void validateSecurityImprovement() { XStream oldXStream = new XStream(); oldXStream.addPermission(AnyTypePermission.ANY); XStream newXStream = new BlocklistRestrictedXStream(); newXStream.addPermission(AnyTypePermission.ANY); String[] attackPayloads = loadKnownAttackPayloads(); for (String payload : attackPayloads) { // Old XStream should be vulnerable assertDoesNotThrow(() -> oldXStream.fromXML(payload)); // New XStream should block the attack assertThrows(ForbiddenClassException.class, () -> newXStream.fromXML(payload)); } } }
NoSuchMethodError or ClassNotFoundExceptionCause: Dependency conflicts or missing dependencies
Solution: Check dependency tree and resolve conflicts:
1 2mvn dependency:tree | grep -i xstream
Ensure you're not mixing different XStream versions.
Cause: Missing AnyTypePermission.ANY permission
Solution: Ensure you add the permission for blocklist mode:
1 2// Don't forget this line for Phase 1 xstream.addPermission(AnyTypePermission.ANY);
Cause: Converter priority conflicts
Solution: Ensure custom converters use appropriate priority:
1 2// Use priority lower than blocklist converter xstream.registerConverter(new MyConverter(), XStream.PRIORITY_NORMAL);
✅ Phase 1 is complete when:
BlocklistRestrictedXStreamAnyTypePermission.ANY (blocklist mode)After successfully completing Phase 1:
Phase 1 provides immediate security benefits while maintaining full compatibility, making it an ideal first step in the migration process.
Rate this page: