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
登录到后台
创建了一个Connect Client
点击刚才创建的Client配置信息,修改Redirection URIs和Scope的值
配置一个OAuth2 Provider,在Custom Login URL Template 处填入payload
<#assign value="freemarker.template.utility.Execute"?new()>${value("touch /tmp/vuln")}
在给出的 POC中在请求时需要加上csrf参数来绕过CSRF检测,这里只要退出openAM平台的登录再发送请求就不用添加csrf参数,这样并不会影响payload的执行,只是页面不会跳转到 https://baidu.com
http://127.0.0.1:8080/openam/oauth2/realms/root/authorize?client_id=1&scope=employeenumber&redirect_uri=https://baidu.com&response_type=code&max_age=200
执行之后,进入到docker容器中,在/tmp目录下可以看到已经成功创建了文件
根据openam-server-only模块下的web.xml文件可以知道当访问/oauth2/realms/root/authorize接口时,会交由RestEndpointServlet处理
跟进到org.forgerock.openam.rest.RestEndpointServlet,在该Servlet中不存在doGet()和doPost(),容器会调用到service方法
在该方法中,会判断ServletPath的值调用到不同的方法,这里会进入到restletOAuth2ServiceServlet.service()
在service()方法中,初始化了一个HttpServerHelper对象,这是Restlet框架下的一个类,它充当了Restlet应用于HTTP服务器之间的桥梁,用来处理HTTP请求并将其转换为Restlet请求
继续向下执行到关键代码处,将断点下在依赖org.restlet-2.4.0.jar包下的org.restlet.routing.Router#handle()方法下,该方法主要用来处理请求并控制请求的流向
程序会接受request对象作为参数,调用this.getNext()来根据上下文返回下一个处理器,如果不存在next下一个处理器的话会将请求的状态设置为404
访问接口http://127.0.0.1:8080/openam/oauth2/realms/root/authorize,观察next的值
在Restlet 程序初始化时,会通过一系列的工厂类进行路由注册并将其抽象成树状结构(如下图),假设用户访问的接口为/openam/test/stop,在/openam/test父树下不存在/stop这个子树,那么就证明/openam/test/stop这个请求是不合法的,程序就会返回404
跟进到this.getNext(),可以查看到某个父控制器下面的所有子树控制器,这是/openam/oauth2父控制器下的所有子控制器
POC中的漏洞接口为/openam/oauth2/realms/root/authorize,通过上面的this.routes可以知道,实际上/openam/oauth2/authorize接口也是可以调用到漏洞类的
/authorize路由的注册实现在org.forgerock.openam.oauth2.rest.OAuth2RouterProvider#get(),可以看到当调用到authorize控制器时会交给AuthorizeResource类处理
所以当访问/openam/oauth2/realms/root/authorize,最终的业务逻辑由AuthorizeResource#authorize()处理
在该方法中首先调用requestFactory.create()将原始的http请求转换成OAuth2Request类型的,接着调用到authorizationService.authorize()
在该方法中程序会获取请求时参数的值,调用validateAuthorizationScope()验证传入的值与配置的值是否一致
跟进到validate(),由于获取到的token的值为null,会进入到下面的else语句中
isRefreshToken()用来检查传入的 OAuth2 请求是否是一个刷新令牌请求,这里返回的值为false,所以会进入到authenticationRequired()
最终会进入到getCustomLoginUrlTemplate()中,首先从设置中获取自定义登录 URL 模板字符串loginUrlTemplateString,接着使用该字符串创建一个模板对象返回
设置了一个新的类解析器,用于控制模板中可以使用的类
https://github.com/OpenIdentityPlatform/OpenAM/commit/fcb8432aa77d5b2e147624fe954cb150c568e0b8
https://github.com/OpenIdentityPlatform/OpenAM/security/advisories/GHSA-7726-43hg-m23v
7 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!