3

I currently have a simple xml endpoint (example) created using the jersey-server 1.1 framework. it consumes and produces XML using the following notation:

@POST
@Path("/post")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public Response getEmployee(Employee employee) {
     return Response.status(Status.OK).entity(employee).build();
}

however the endpoint is vulnerable to XXE attacks. (example) its also possible to get my server to talk to request any endpoint using this notation...

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [
<!ENTITY % a SYSTEM "file:///etc/passwd">
%a;
]>

I want a way to protect the server and not allow it to call out to other servers/serve up files to attackers.

Is there a way to do this, since everything including the XML reading is coming from the framework itself ? @Consumes(MediaType.APPLICATION_XML)

The only way I think I could do this is to use regex on the body of the request somehow with a filter? to block DOCTYPE, SYSTEM, ENTITY requests and return an error but I am wondering is there a simpler way to do this and override the default behavior of @Consumes(MediaType.APPLICATION_XML)?

kolossus
  • 20,559
  • 3
  • 52
  • 104
user2950720
  • 931
  • 2
  • 10
  • 26
  • Sorry, I somehow thought your question was tagged `dropwizard`. I will delete my answer. You should check which XML binding library is used by Jersey and if there are any known XXE vulnerabilities. –  Jun 01 '19 at 14:19
  • Basically all xml vulnerabilities come from the use of DTD, so disabling DTD support is the way to go. – Svetlin Zarev Jun 03 '19 at 06:14

3 Answers3

1

I'm going to address just the XXE concern because the question isn't entirely clear on other specific authentication/authorization concerns to address.

Starting from Blaise's approach to prevent XXE with basic JAXB, what needs to be done is to get lower-level access to the supplied XML. Good thing Jersey supports this out of the box. One way to do this is to replace your Employee argument with a StreamSource.

  1. First get a hold of the existing JAXBContext:

    private final @Context Providers providers; //full list of all providers available
    
  2. Modify your interface to accept a StreamSource so you have access to the raw incoming XML:

    @POST
    @Path("/post")
    @Consumes(MediaType.APPLICATION_XML)
    @Produces(MediaType.APPLICATION_XML)
    public Response getEmployee(StreamSource employeeStreamSource)
    
  3. Configure the JAXBContext unmarshaller to ignore DTDs:

    public Response getEmployee(StreamSource employeeStreamSource){
        //we try to get a hold of the JAXBContext
        ContextResolver<JAXBContext> jaxbResolver = provider.getContextResolver(JAXBContext.class, MediaType.APPLICATION_XML_TYPE);
        JAXBContext jaxbContext= null;
        if(null != jaxbResolver) {
            jaxbContext = jaxbResolver.getContext(Employee.class);
        }
        if(null == jaxbContext) {
            jaxbContext = JAXBContext.newInstance(Employee.class);
        }
    
        XMLInputFactory xif = XMLInputFactory.newFactory();
        xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); //Don't blindly parse entities
        xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); //suppress DTD
        XMLStreamReader xsr = xif.createXMLStreamReader(employeeStreamSource); //beging parsing incoming XML
    
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        Employee employee= (Employee) unmarshaller.unmarshal(xsr);  //manually unmarshal
    
        return Response.status(Status.OK).entity(employee).build();
    
    }
    
kolossus
  • 20,559
  • 3
  • 52
  • 104
0

You need to disable http://apache.org/xml/features/nonvalidating/load-external-dtd and http://xml.org/sax/features/validation features as described in details

  /* Set features to turn off loading of external DTDs. */
    mDocumentBuilderFactoryDelegateBuilderFactory.setFeature(
       LOADEXTERNALDTD_FEATURE, false);
    mDocumentBuilderFactoryDelegateBuilderFactory.setFeature(
       XML_VALIDATION_FEATURE, false);

Java system properties can be manipulated programmatically at runtime, I came up with the idea to dynamically replace the current document builder factory with a wrapper that sets certain features on new instances of the document builder factory. Setting those features were said to cause references to DTD documents to be ignored when parsing XML that contained such references.

Shorter code:

  DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
     dbf.setValidating(false);
     dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
     DocumentBuilder db = dbf.newDocumentBuilder();
     Document doc = db.parse(iStream);
Ori Marko
  • 56,308
  • 23
  • 131
  • 233
0

I had the same problem and unfortunately above described solution is only conditionally suitable for the services with only XML transfer type. The services that support both XML and JSON the implementation is not efficient.

I found this discussion https://github.com/eclipse-ee4j/jersey/issues/3446 on Github in jersey repository and the problem seems to be already fixed in version 2.33 since March 2020:

Upgade Jersey dependency to 2.33

I think it is much easier than modifying every service.