CVE-2024-41667 OpenAM FreeMarker 模板注入分析

OpenAM是一个开放式的访问管理解决方案。在版本15.0.3及之前的版本中,存在一个模板注入漏洞(CVE-2024-41667)。RealmOAuth2ProviderSettings.java中的`getCustomLoginUrlTemplate`方法由于使用了用户输入而容易受到模板注入攻击。

前言

OpenAM是一个开放式的访问管理解决方案。在版本15.0.3及之前的版本中,存在一个模板注入漏洞(CVE-2024-41667)。RealmOAuth2ProviderSettings.java中的getCustomLoginUrlTemplate方法由于使用了用户输入而容易受到模板注入攻击。虽然开发者意图实现自定义URL以处理登录,以覆盖默认的PingOne Advanced Identity Cloud登录页面,但他们没有对CustomLoginUrlTemplate进行限制,使其可以自由设置。

影响版本:<= 15.0.3

环境搭建

//docker-compose.yml
version: '3'
services:
  openam:
    image: openidentityplatform/openam:15.0.3
    ports:
      - "5005:5005"
      - "8080:8080"
    command:
      /opt/java/openjdk/bin/java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager --add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.tools.keytool=ALL-UNNAMED --add-exports java.xml/com.sun.org.apache.xerces.internal.dom=ALL-UNNAMED -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dcom.iplanet.services.configpath=/usr/openam/config -Dcom.sun.identity.configuration.directory=/usr/openam/config -Dignore.endorsed.dirs= -classpath /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap start

docker-compose 启动之后访问http://127.0.0.1:8080/openam/,默认配置搭建即可,调试端口为5005

image-20240829111300913.png

漏洞复现

登录到后台

image-20240829114932966.png

创建了一个Connect Client

image-20240829121408200.png

image-20240829121434435.png

点击刚才创建的Client配置信息,修改Redirection URIs和Scope的值

image-20240829144902182.png

配置一个OAuth2 Provider,在Custom Login URL Template 处填入payload

&lt;#assign value="freemarker.template.utility.Execute"?new()&gt;${value("touch /tmp/vuln")}

image-20240829121553319.png

image-20240829121629286.png

image-20240829144822825.png

在给出的 POC中在请求时需要加上csrf参数来绕过CSRF检测,这里只要退出openAM平台的登录再发送请求就不用添加csrf参数,这样并不会影响payload的执行,只是页面不会跳转到 https://baidu.com

http://127.0.0.1:8080/openam/oauth2/realms/root/authorize?client_id=1&amp;scope=employeenumber&amp;redirect_uri=https://baidu.com&amp;response_type=code&amp;max_age=200

image-20240829150138114.png

执行之后,进入到docker容器中,在/tmp目录下可以看到已经成功创建了文件

image-20240829141854086.png

分析过程

路由分析

根据openam-server-only模块下的web.xml文件可以知道当访问/oauth2/realms/root/authorize接口时,会交由RestEndpointServlet处理

image-20240828173419208.png

跟进到org.forgerock.openam.rest.RestEndpointServlet,在该Servlet中不存在doGet()和doPost(),容器会调用到service方法

在该方法中,会判断ServletPath的值调用到不同的方法,这里会进入到restletOAuth2ServiceServlet.service()

image-20240828173950868.png

在service()方法中,初始化了一个HttpServerHelper对象,这是Restlet框架下的一个类,它充当了Restlet应用于HTTP服务器之间的桥梁,用来处理HTTP请求并将其转换为Restlet请求

image-20240828174347971.png

继续向下执行到关键代码处,将断点下在依赖org.restlet-2.4.0.jar包下的org.restlet.routing.Router#handle()方法下,该方法主要用来处理请求并控制请求的流向

程序会接受request对象作为参数,调用this.getNext()来根据上下文返回下一个处理器,如果不存在next下一个处理器的话会将请求的状态设置为404

image-20240829103820852.png

访问接口http://127.0.0.1:8080/openam/oauth2/realms/root/authorize,观察next的值

image-20240829104306081.png

image-20240829104649198.png

image-20240829104708347.png

image-20240829104728498.png

在Restlet 程序初始化时,会通过一系列的工厂类进行路由注册并将其抽象成树状结构(如下图),假设用户访问的接口为/openam/test/stop,在/openam/test父树下不存在/stop这个子树,那么就证明/openam/test/stop这个请求是不合法的,程序就会返回404

image-20240829105819218.png

跟进到this.getNext(),可以查看到某个父控制器下面的所有子树控制器,这是/openam/oauth2父控制器下的所有子控制器

image-20240829112705316.png

POC中的漏洞接口为/openam/oauth2/realms/root/authorize,通过上面的this.routes可以知道,实际上/openam/oauth2/authorize接口也是可以调用到漏洞类的

/authorize路由的注册实现在org.forgerock.openam.oauth2.rest.OAuth2RouterProvider#get(),可以看到当调用到authorize控制器时会交给AuthorizeResource类处理

image-20240829151229853.png

所以当访问/openam/oauth2/realms/root/authorize,最终的业务逻辑由AuthorizeResource#authorize()处理

漏洞分析

在该方法中首先调用requestFactory.create()将原始的http请求转换成OAuth2Request类型的,接着调用到authorizationService.authorize()

image-20240829151729099.png

在该方法中程序会获取请求时参数的值,调用validateAuthorizationScope()验证传入的值与配置的值是否一致

image-20240829155612722.png

跟进到validate(),由于获取到的token的值为null,会进入到下面的else语句中

image-20240829155009514.png

isRefreshToken()用来检查传入的 OAuth2 请求是否是一个刷新令牌请求,这里返回的值为false,所以会进入到authenticationRequired()

image-20240829155027233.png

最终会进入到getCustomLoginUrlTemplate()中,首先从设置中获取自定义登录 URL 模板字符串loginUrlTemplateString,接着使用该字符串创建一个模板对象返回

image-20240829155057611.png

image-20240829155133830.png

漏洞修复

设置了一个新的类解析器,用于控制模板中可以使用的类

https://github.com/OpenIdentityPlatform/OpenAM/commit/fcb8432aa77d5b2e147624fe954cb150c568e0b8

image-20240829161427965.png

参考

https://github.com/OpenIdentityPlatform/OpenAM/security/advisories/GHSA-7726-43hg-m23v

  • 发表于 2024-09-10 08:00:00
  • 阅读 ( 10050 )
  • 分类:Web应用

0 条评论

请先 登录 后评论
kakakakaxi
kakakakaxi

7 篇文章

站长统计