Penyelidik telah melaporkan kelemahan penyahserialisasian dalam Apache OFBiz. Kerentanan disebabkan oleh berbilang isu penyahserialisasian Java dan mungkin dicetuskan apabila kod tersebut mengendalikan permintaan kepada /webtools/control/xmlrpc. Penyerang jauh yang tidak disahkan boleh mencetuskan dan mengeksploitasi kelemahan ini dan mencapai pelaksanaan kod sewenang-wenangnya dengan menghantar permintaan berniat jahat yang dibuat.
Apache OFBiz ialah sistem perancangan sumber perusahaan (ERP) sumber terbuka yang menyediakan satu siri aplikasi perusahaan untuk membantu perusahaan mengautomasikan banyak proses perniagaan. Ia termasuk rangka kerja yang menyediakan model data biasa dan proses perniagaan yang perlu digunakan oleh semua aplikasi dalam perusahaan untuk menggunakan data biasa, logik dan komponen pemprosesan perniagaan. Selain rangka kerja itu sendiri, Apache OFBiz juga menyediakan perkhidmatan termasuk perakaunan (perjanjian kontrak, invois, pengurusan pembekal, lejar am), penyelenggaraan aset, klasifikasi item, pengurusan produk, pengurusan peralatan, sistem pengurusan gudang (WMS), pelaksanaan/pengilangan. Pengurusan operasi (MES/MOM) dan fungsi pemprosesan pesanan, sebagai tambahan kepada pengurusan inventori, penambahan semula inventori automatik, sistem pengurusan kandungan (CMS), sumber manusia (HR), pengurusan orang dan pasukan, pengurusan projek, tenaga jualan Pelbagai ciri seperti automasi , pengurusan beban kerja, titik jualan elektronik (ePOS), e-dagang (e-dagang) dan scrum (pembangunan).
Apache OFBiz menggunakan pelbagai teknologi dan piawaian sumber terbuka, seperti Java, JavaEE, XML dan SOAP.
Protokol Pemindahan Hiperteks ialah protokol permintaan/tindak balas yang diterangkan secara terperinci dalam RFC 7230-7237. Permintaan dihantar dari peranti klien ke pelayan, dan selepas pelayan menerima dan memproses permintaan, ia menghantar respons kembali kepada klien. Permintaan HTTP terdiri daripada kandungan permintaan, pelbagai pengepala, baris kosong dan badan mesej pilihan:
Request = Request-Line headers CRLF [message-body] Request-Line = Method SP Request-URI SP HTTP-Version CRLF Headers = *[Header] Header = Field-Name “:” Field-Value CRLF
CRLF mewakili aksara pemulangan pengangkutan (CR) jujukan baris baharu, diikuti dengan aksara suapan baris (LF), SP mewakili watak ruang. Parameter akan dihantar daripada klien ke pelayan dalam bentuk pasangan nilai kunci melalui Request-URI atau message-body, bergantung pada parameter yang ditakrifkan dalam pengepala Kaedah dan Jenis Kandungan. Sebagai contoh, dalam sampel permintaan HTTP berikut, terdapat parameter bernama "param" dengan nilai "1" dan kaedah POST digunakan:
POST /my_webapp/mypage.htm HTTP/1.1 Host: www.myhost.com Content-Type: application/x-www-form-urlencoded Content-Length: 7 param=1
Java Menyokong siri operasi pada objek supaya ia boleh diwakili sebagai aliran bait yang padat dan mudah alih ini kemudiannya boleh dihantar melalui rangkaian dan dinyahsiri untuk digunakan oleh servlet atau applet penerima. Contoh berikut menunjukkan cara untuk mensiri kelas dan seterusnya mengekstrak data:
public static void main(String args[]) throws Exception{ //This is the object we're going to serialize. MyObject1 myObj = new MyObject1(); MyObject2 myObj2 = new MyObject2(); myObj2.name = "calc"; myObj.test = myObj2; //We'll write the serialized data to a file "object.ser" FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(myObj); os.close(); //Read the serialized data back in from the file "object.ser" FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); //Read the object from the data stream, and convert it back to a String MyObject1 objectFromDisk = (MyObject1)ois.readObject(); ois.close(); }
Semua objek Java perlu diserikan melalui antara muka Serializable atau Externalizable, yang melaksanakan writeObject()/writeExternal () dan readObject()/readExternal () kaedah, yang akan dipanggil apabila objek disiri atau dinyahsiri. Kaedah ini mendayakan tingkah laku tersuai dengan mengubah suai kod semasa penyirian dan penyahsirilan.
XML-RPC ialah protokol panggilan prosedur jauh (RPC) yang menggunakan XML untuk mengekod panggilannya dan HTTP sebagai mekanisme pengangkutan. Ia adalah spesifikasi standard dan menyediakan pelaksanaan siap sedia yang membolehkannya dijalankan pada sistem pengendalian dan persekitaran yang berbeza. Dalam XML-RPC, pelanggan melaksanakan RPC dengan menghantar permintaan HTTP kepada pelayan yang melaksanakan XML-RPC dan menerima respons HTTP.
Setiap permintaan XML-RPC bermula dengan elemen XML "
Seperti yang ditunjukkan dalam contoh berikut, jenis data biasa boleh ditukar kepada jenis XML yang sepadan:
<array> <data> <value><i4>1404</i4></value> <value><string>Something here</string></value> <value><i4>1</i4></value> </data> </array>
Contoh pengekodan pelbagai primitif adalah seperti berikut:
<boolean>1</boolean> <double>-12.53</double> <int>42</int>
String The encoding contoh adalah seperti berikut:
<string>Hello world!</string>
Contoh pengekodan struktur adalah seperti berikut:
<struct> <member> <name>foo</name> <value><i4>1</i4></value> </member> <member> <name>bar</name> <value><i4>2</i4></value> </member> </struct>
Data bersiri diwakili oleh elemen XML "" dan "" Dalam Apache OFBiz, kod bersiri Dilaksanakan dalam kelas Java org.apache.xmlrpc.parser.SerializableParser.
Walau bagaimanapun, terdapat kelemahan penyahserikatan yang tidak selamat dalam Apache OFBiz Kerentanan ini disebabkan OFBiz dikonfigurasikan untuk menggunakan XML-RPC untuk memintas dan mengubah HTTP apabila dihantar ke URL "/webtools/control/xmlrpc". Disebabkan oleh data XML dalam badan. Permintaan yang dihantar ke titik akhir ini pada mulanya dikendalikan oleh kelas Java org.apache.ofbiz.webapp.control.RequestHandler, yang menentukan cara URL dipetakan. Seterusnya, kelas org.apache.ofbiz.webapp.event.XmlRpcEventHandler akan memanggil kaedah execute() penghuraian XML terlebih dahulu perlu memanggil kaedah parse() melalui kelas XMLReader, dan kaedah ini perlu berada dalam org.apache. ofbiz.webapp.event Dipanggil dalam kaedah getRequest() kelas .XmlRpcEventHandler.
Elemen dalam permintaan XML-RPC akan dihuraikan dalam kelas berikut:
org.apache.xmlrpc.parser.XmlRpcRequestParser org.apache.xmlrpc.parser.RecursiveTypeParserImpl org.apache.xmlrpc.parser.MapParser
不安全的序列化问题存在于org.apache.xmlrpc.parser.SerializableParser类的getResult()方法之中。一个未经身份验证的远程攻击者可以利用该漏洞来发送包含了定制XML Payload的恶意HTTP请求。由于OFBiz使用了存在漏洞的Apache Commons BeanUtils库和Apache ROME库,攻击者将能够使用ysoserial工具以XML格式来构建恶意Payload。该漏洞的成功利用将导致攻击者在目标应用程序中实现任意代码执行。
下列代码段取自Apache OFBiz v17.12.03版本,并添加了相应的注释。
public void doRequest(HttpServletRequest request, HttpServletResponse response, String chain, GenericValue userLogin, Delegator delegator) throws RequestHandlerException, RequestHandlerExceptionAllowExternalRequests { ConfigXMLReader.RequestResponse eventReturnBasedRequestResponse; if (!this.hostHeadersAllowed.contains(request.getServerName())) { Debug.logError("Domain " + request.getServerName() + " not accepted to prevent host header injection ", module); throw new RequestHandlerException("Domain " + request.getServerName() + " not accepted to prevent host header injection "); } boolean throwRequestHandlerExceptionOnMissingLocalRequest = EntityUtilProperties.propertyValueEqualsIgnoreCase("requestHandler", "throwRequestHandlerExceptionOnMissingLocalRequest", "Y", delegator); long startTime = System.currentTimeMillis(); HttpSession session = request.getSession(); ConfigXMLReader.ControllerConfig controllerConfig = getControllerConfig(); Map<String, ConfigXMLReader.RequestMap> requestMapMap = null; String statusCodeString = null; try { requestMapMap = controllerConfig.getRequestMapMap(); statusCodeString = controllerConfig.getStatusCode(); } catch (WebAppConfigurationException e) { Debug.logError((Throwable)e, "Exception thrown while parsing controller.xml file: ", module); throw new RequestHandlerException(e); } if (UtilValidate.isEmpty(statusCodeString)) statusCodeString = this.defaultStatusCodeString; String cname = UtilHttp.getApplicationName(request); String defaultRequestUri = getRequestUri(request.getPathInfo()); if (request.getAttribute("targetRequestUri") == null) if (request.getSession().getAttribute("_PREVIOUS_REQUEST_") != null) { request.setAttribute("targetRequestUri", request.getSession().getAttribute("_PREVIOUS_REQUEST_")); } else { request.setAttribute("targetRequestUri", "/" + defaultRequestUri); } String overrideViewUri = getOverrideViewUri(request.getPathInfo()); String requestMissingErrorMessage = "Unknown request [" + defaultRequestUri + "]; this request does not exist or cannot be called directly."; ConfigXMLReader.RequestMap requestMap = null; if (defaultRequestUri != null) //get the mapping for the URI requestMap = requestMapMap.get(defaultRequestUri); if (requestMap == null) { String defaultRequest; //[...truncated for readability.....] ConfigXMLReader.RequestResponse nextRequestResponse = null; if (eventReturn == null && requestMap.event != null && requestMap.event.type != null && requestMap.event.path != null && requestMap.event.invoke != null) try { long eventStartTime = System.currentTimeMillis(); //call XmlRpcEventHandler eventReturn = runEvent(request, response, requestMap.event, requestMap, "request");
public void execute(XmlRpcStreamRequestConfig pConfig, ServerStreamConnection pConnection) throws XmlRpcException { try { ByteArrayOutputStream baos; OutputStream initialStream; Object result = null; boolean foundError = false; try (InputStream istream = getInputStream(pConfig, pConnection)) { XmlRpcRequest request = getRequest(pConfig, istream); result = execute(request); } catch (Exception e) { Debug.logError(e, module); foundError = true; } if (isContentLengthRequired(pConfig)) { baos = new ByteArrayOutputStream(); initialStream = baos; } else { baos = null; initialStream = pConnection.newOutputStream(); } try (OutputStream ostream = getOutputStream(pConnection, pConfig, initialStream)) { if (!foundError) { writeResponse(pConfig, ostream, result); } else { writeError(pConfig, ostream, new Exception("Failed to read XML-RPC request. Please check logs for more information")); } } if (baos != null) try (OutputStream dest = getOutputStream(pConfig, pConnection, baos.size())) { baos.writeTo(dest); } pConnection.close(); pConnection = null; } catch (IOException e) { throw new XmlRpcException("I/O error while processing request: " + e.getMessage(), e); } finally { if (pConnection != null) try { pConnection.close(); } catch (IOException e) { Debug.logError(e, "Unable to close stream connection"); } } } protected XmlRpcRequest getRequest(final XmlRpcStreamRequestConfig pConfig, InputStream pStream) throws XmlRpcException { final XmlRpcRequestParser parser = new XmlRpcRequestParser((XmlRpcStreamConfig)pConfig, getTypeFactory()); XMLReader xr = SAXParsers.newXMLReader(); xr.setContentHandler((ContentHandler)parser); try { xr.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); xr.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); xr.setFeature("http://xml.org/sax/features/external-general-entities", false); xr.setFeature("http://xml.org/sax/features/external-parameter-entities", false); //the parsing of XML in the HTTP body starts in this function xr.parse(new InputSource(pStream)); //truncated } }
public void endElement(String pURI, String pLocalName, String pQName) throws SAXException { //XML-RPC parsing happens here switch(--level) { case 0: break; case 1: if (inMethodName) { if ("".equals(pURI) && "methodName".equals(pLocalName)) { if (methodName == null) { methodName = ""; } } else { throw new SAXParseException("Expected /methodName, got " + new QName(pURI, pLocalName), getDocumentLocator()); } inMethodName = false; } else if (!"".equals(pURI) || !"params".equals(pLocalName)) { throw new SAXParseException("Expected /params, got " + new QName(pURI, pLocalName), getDocumentLocator()); } break; case 2: if (!"".equals(pURI) || !"param".equals(pLocalName)) { throw new SAXParseException("Expected /param, got " + new QName(pURI, pLocalName), getDocumentLocator()); } break; case 3: if (!"".equals(pURI) || !"value".equals(pLocalName)) { throw new SAXParseException("Expected /value, got " + new QName(pURI, pLocalName), getDocumentLocator()); } endValueTag(); break; default: super.endElement(pURI, pLocalName, pQName); break; } }
public class SerializableParser extends ByteArrayParser { public Object getResult() throws XmlRpcException { try { byte[] res = (byte[]) super.getResult(); ByteArrayInputStream bais = new ByteArrayInputStream(res); ObjectInputStream ois = new ObjectInputStream(bais); //insecure deserialization happens here return ois.readObject(); } catch (IOException e) { throw new XmlRpcException("Failed to read result object: " + e.getMessage(), e); } catch (ClassNotFoundException e) { throw new XmlRpcException("Failed to load class for result object: " + e.getMessage(), e); } } }
为了触发该漏洞,攻击者需要以XML格式在HTTP请求中携带定制的序列化对象,并发送给存在漏洞的目标应用程序,当服务器端在序列化XML数据时,便会触发该漏洞。
Atas ialah kandungan terperinci APACHE OFBIZ XMLRPC Analisis Contoh Kerentanan Pelaksanaan Kod Jauh. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!