CVE-2016-15026 (Other, published 2023-02-20T11:15Z)
Description: value: A vulnerability was found in 3breadt dd-plist 1.17 and classified as problematic. Affected by this issue is some unknown functionality. The manipulation leads to xml external entity reference. An attack has to be approached locally. Upgrading to version 1.18 is able to address this issue. The patch is identified as 8c954e8d9f6f6863729e50105a8abf3f87fff74c. It is recommended to upgrade the affected component. VDB-221486 is the identifier assigned to this vulnerability.
Severity: nan (CVSS v3 base score: 7.8)
Weakness: CWE-611 \u2014 Improper Restriction of XML External Entity Reference
The product processes an XML document that can contain XML entities with URIs that resolve to documents outside of the intended sphere of control, causing the product to embed incorrect documents into its output.
Repository: https://github.com/3breadt/dd-plist
Files changed:
build.xml
src/main/java/com/dd/plist/PropertyList-1.0.dtd
src/main/java/com/dd/plist/XMLPropertyListParser.java
Commit message: 1) Take steps to guard against external XXE attacks (except, note that <!DOCTYPE> cannot be disabled in XML plists).
2) Resolve the actual PLIST DTD from inside the JAR file itself, and prevent resolution of other external XML resources.
3) Make XMLPlistParser.getDocBuilder public


Vulnerable code:
     <fileset dir="${build}/classes"/>

    private static DocumentBuilderFactory docBuilderFactory = null;

    /**
     * Instantiation is prohibited by outside classes.
     */
    protected XMLPropertyListParser() {
        /** empty **/
    }

    /**
     * Initialize the document builder factory so that it can be reused and does not need to
     * be reinitialized for each parse action.
     */
    private static synchronized void initDocBuilderFactory() {
        docBuilderFactory = DocumentBuilderFactory.newInstance();
        docBuilderFactory.setIgnoringComments(true);
        docBuilderFactory.setCoalescing(true);
    private static synchronized DocumentBuilder getDocBuilder() throws ParserConfigurationException {
        if (docBuilderFactory == null)
            initDocBuilderFactory();
        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
        docBuilder.setEntityResolver(new EntityResolver() {
            public InputSource resolveEntity(String publicId, String systemId) {
                if ("-//Apple Computer//DTD PLIST 1.0//EN".equals(publicId) || // older publicId
                        "-//Apple//DTD PLIST 1.0//EN".equals(publicId)) { // newer publicId
                    // return a dummy, zero length DTD so we don't have to fetch
                    // it from the network.
                    return new InputSource(new ByteArrayInputStream(new byte[0]));
                }
                return null;
            }
        });
        return docBuilder;
    public static NSObject parse(File f) throws ParserConfigurationException, IOException, SAXException, PropertyListFormatException, ParseException {
        DocumentBuilder docBuilder = getDocBuilder();

        Document doc = docBuilder.parse(f);
        return parse(doc);
    public static NSObject parse(final byte[] bytes) throws ParserConfigurationException, ParseException, SAXException, PropertyListFormatException, IOException {
        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
        return parse(bis);
    public static NSObject parse(InputStream is) throws ParserConfigurationException, IOException, SAXException, PropertyListFormatException, ParseException {
        DocumentBuilder docBuilder = getDocBuilder();

        Document doc = docBuilder.parse(is);
        return parse(doc);

Fixed code:
      <fileset dir="${build}/classes"/>
      <fileset dir="${source}" includes="**/*.dtd"/>
<!ENTITY % plistObject "(array | data | date | dict | real | integer | string | true | false )" >
<!ELEMENT plist %plistObject;>
<!ATTLIST plist version CDATA "1.0" >

<!-- Collections -->
<!ELEMENT array (%plistObject;)*>
<!ELEMENT dict (key, %plistObject;)*>
<!ELEMENT key (#PCDATA)>

<!--- Primitive types -->
<!ELEMENT string (#PCDATA)>
<!ELEMENT data (#PCDATA)> <!-- Contents interpreted as Base-64 encoded -->
<!ELEMENT date (#PCDATA)> <!-- Contents should conform to a subset of ISO 8601 (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'.  Smaller units may be omitted with a loss of precision) -->

<!-- Numerical primitives -->
<!ELEMENT true EMPTY>  <!-- Boolean constant true -->
<!ELEMENT false EMPTY> <!-- Boolean constant false -->
<!ELEMENT real (#PCDATA)> <!-- Contents should represent a floating point number matching ("+" | "-")? d+ ("."d*)? ("E" ("+" | "-") d+)? where d is a digit 0-9.  -->
<!ELEMENT integer (#PCDATA)> <!-- Contents should represent a (possibly signed) integer number in base 10 -->
import java.io.FileInputStream;
    private static final DocumentBuilderFactory FACTORY = DocumentBuilderFactory.newInstance();
    static {
        try {
            FACTORY.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
            Float v1_7 = new Float("1.7");
            Float javaVersion = new Float(System.getProperty("java.specification.version"));
            if (javaVersion.compareTo(v1_7) >= 0) {
                FACTORY.setFeature("http://xml.org/sax/features/external-general-entities", false);
                FACTORY.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            }
            FACTORY.setXIncludeAware(false);
            FACTORY.setExpandEntityReferences(false);
            FACTORY.setNamespaceAware(false);
            FACTORY.setIgnoringComments(true);
            FACTORY.setCoalescing(true);
            FACTORY.setValidating(false);
        } catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        }
    public static synchronized DocumentBuilder getDocBuilder() throws ParserConfigurationException {
        DocumentBuilder builder = FACTORY.newDocumentBuilder();
        builder.setEntityResolver(new PlistDTDResolver());
        return builder;
    public static NSObject parse(File f)
                throws ParserConfigurationException, IOException, SAXException, PropertyListFormatException, ParseException {
        return parse(getDocBuilder().parse(new FileInputStream(f)));
    public static NSObject parse(final byte[] bytes)
                throws ParserConfigurationException, ParseException, SAXException, PropertyListFormatException, IOException {

        return parse(new ByteArrayInputStream(bytes));
    public static NSObject parse(InputStream is)
                throws ParserConfigurationException, IOException, SAXException, PropertyListFormatException, ParseException {
        return parse(getDocBuilder().parse(is));

    /**
     * Resolves only the Apple PLIST DTD.
     */
    static class PlistDTDResolver implements EntityResolver {
        private static final String PLIST_SYSTEMID_1 = "-//Apple Computer//DTD PLIST 1.0//EN";
        private static final String PLIST_SYSTEMID_2 = "-//Apple//DTD PLIST 1.0//EN";

        PlistDTDResolver() {
        }

        // Implement EntityResolver

        public InputSource resolveEntity(String publicId, String systemId) {
            if (PLIST_SYSTEMID_1.equals(publicId) || PLIST_SYSTEMID_2.equals(publicId)) {
                return new InputSource(XMLPropertyListParser.class.getResourceAsStream("PropertyList-1.0.dtd"));
            }
            return null;
        }
    }