diff --git a/src/main/java/com/ankurm/jackson3/part7security/H01SafePolymorphismByAnnotation.java b/src/main/java/com/ankurm/jackson3/part7security/H01SafePolymorphismByAnnotation.java new file mode 100644 index 0000000..b820b9f --- /dev/null +++ b/src/main/java/com/ankurm/jackson3/part7security/H01SafePolymorphismByAnnotation.java @@ -0,0 +1,49 @@ +package com.ankurm.jackson3.part7security; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import tools.jackson.databind.json.JsonMapper; + +/** + * Post: Jackson Security Best Practices — https://ankurm.com/jackson-security-best-practices/ + * Section: "Use @JsonTypeInfo Instead of Default Typing" + * + * The safe pattern: the permitted types are fixed at compile time, so no JSON payload + * can introduce a class name of its own. + */ +public class H01SafePolymorphismByAnnotation { + + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") + @JsonSubTypes({ + @JsonSubTypes.Type(value = EmailNotification.class, name = "email"), + @JsonSubTypes.Type(value = SmsNotification.class, name = "sms") + }) + public abstract static class Notification { } + + public static class EmailNotification extends Notification { + public String recipientEmail; + @Override public String toString() { return "EmailNotification[" + recipientEmail + "]"; } + } + + public static class SmsNotification extends Notification { + public String recipientPhone; + @Override public String toString() { return "SmsNotification[" + recipientPhone + "]"; } + } + + public static void main(String[] args) { + JsonMapper mapper = JsonMapper.builder().build(); + + System.out.println("email : " + mapper.readValue( + "{\"type\":\"email\",\"recipientEmail\":\"a@example.com\"}", Notification.class)); + System.out.println("sms : " + mapper.readValue( + "{\"type\":\"sms\",\"recipientPhone\":\"+441234567890\"}", Notification.class)); + + // A class name supplied by an attacker is not a registered logical name. + try { + mapper.readValue("{\"type\":\"com.malicious.Gadget\"}", Notification.class); + System.out.println("attack: UNEXPECTEDLY ACCEPTED"); + } catch (Exception e) { + System.out.println("attack: rejected with " + e.getClass().getSimpleName()); + } + } +}