Aug 24, 2015

CVE-2015-3269: Apache Flex BlazeDS XXE Vulnerabilty

This was originally posted on blogger here.

In a recent Product Security Review, Code White Researchers discovered a XXE vulnerability in Apache Flex BlazeDS/Adobe (see ASF Advisory). The vulnerable code can be found in the BlazeDS Remoting/AMF protocol implementation.

All versions before 4.7.1 are vulnerable. Software products providing BlazeDS Remoting destinations might be also affected by the vulnerability (e.g. Adobe LiveCycle Data Services, see APSB15-20).

Vulnerability Details

An AMF message has a header and a body. To parse the body, the method readBody() of AmfMessageDeserializer is called. In this method, the targetURI, responseURI and the length of the body are read. Afterwards, the method readObject() is called which eventually calls the method readObject() of an ActionMessageInput instance (either Amf0Input or Amf3Input).

/*     */   public void readBody(MessageBody body, int index)
/*     */     throws ClassNotFoundException, IOException
/*     */   {
/* 158 */     String targetURI = amfIn.readUTF();
/* 159 */     body.setTargetURI(targetURI);
/* 160 */     String responseURI = amfIn.readUTF();
/* 161 */     body.setResponseURI(responseURI);
/*     */     
/* 163 */     amfIn.readInt();
/*     */     
/* 165 */     amfIn.reset();
/*     */     
/*     */ 
/* 168 */     if (isDebug) {
/* 169 */       debugTrace.startMessage(targetURI, responseURI, index);
/*     */     }
/*     */     Object data;
/*     */     try {
/* 173 */       data = readObject();
/*     */     }
/*     */     catch (RecoverableSerializationException ex)
/*     */     {
/* 177 */       ex.setCode("Client.Message.Encoding");
/* 178 */       data = ex;
/*     */     }
/*     */     catch (MessageException ex)
/*     */     {
/* 182 */       ex.setCode("Client.Message.Encoding");
/* 183 */       throw ex;
/*     */     }
/*     */     
/* 186 */     body.setData(data);
/*     */     
/* 188 */     if (isDebug) {
/* 189 */       debugTrace.endMessage();
/*     */     }
/*     */   }
/*     */   
/*     */ 
/*     */ 
/*     */ 
/*     */   public Object readObject()
/*     */     throws ClassNotFoundException, IOException
/*     */   {
/* 199 */     return amfIn.readObject();
/*     */   }
/*     */ }

In case of an Amf0Input instance, the type of the object is read from the next byte. If type has the value 15, the following bytes of the body are parsed in method readXml() as a UTF string.

/*     */   public Object readObject()
/*     */     throws ClassNotFoundException, IOException
/*     */   {
/*  91 */     int type = in.readByte();
/*     */     
/*  93 */     Object value = readObjectValue(type);
/*  94 */     return value;
/*     */   }
/*     */   
/*     */   protected Object readObjectValue(int type) throws ClassNotFoundException, IOException
/*     */   {
/*  99 */     Object value = null;
/* 100 */     switch (type)
/*     */     {
/*     */     case 0: 
/* 103 */       value = Double.valueOf(readDouble());
/* 104 */       break;
/*     */     
               ...
/*     */     
/*     */     case 15: 
/* 147 */       value = readXml();
/* 148 */       break;
/*     */     
              ....
/*     */   protected Object readXml() throws IOException
/*     */   {
/* 511 */     String xml = readLongUTF();
/*     */     
/* 513 */     if (isDebug) {
/* 514 */       trace.write(xml);
/*     */     }
/* 516 */     return stringToDocument(xml);
/*     */   }
/*     */   

The xml string gets passed to method stringToDocument of class XMLUtil where the Document is created using the DocumentBuilder.

/*     */ 
/*     */   public static Document stringToDocument(String xml, boolean nameSpaceAware)
/*     */   {
/* 116 */     ClassUtil.validateCreation(Document.class);
/*     */     
/* 118 */     Document document = null;
/*     */     try
/*     */     {
/* 121 */       if (xml != null)
/*     */       {
/* 123 */         StringReader reader = new StringReader(xml);
/* 124 */         InputSource input = new InputSource(reader);
/* 125 */         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
/* 126 */         factory.setNamespaceAware(nameSpaceAware);
/* 127 */         factory.setValidating(false);
/* 128 */         DocumentBuilder builder = factory.newDocumentBuilder();
/*     */         
/* 130 */         document = builder.parse(input);
/*     */       }
/*     */     }
/*     */     catch (Exception ex)
/*     */     {
/* 135 */       throw new MessageException("Error deserializing XML type " + ex.getMessage());
/*     */     }
/*     */     
/* 138 */     return document;
/*     */   }
/*     */ }

When a DocumentBuilder is created through the DocumentBuilderFactory, external entities are allowed by default. The developer needs to configure the parser to prevent XXE.

Exploitation

Exploitation is easy, just send the XXE vector of your choice.