问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
CVE-2025-27818 Apache Kafka Client LdapLoginModule 配置代码执行漏洞 分析
漏洞分析
CVE-2025-27818
CVE-2025-27818 ============== 一、漏洞简介 ------ 攻击者在可以控制Apache Kafka Connect 客户端的情况下,可通过SASL JAAS 配置和基于 SASL 的安全协议在其上创建或修改连接器相关配置,利用部分参数例如 LdapLoginModule,触发代码执行漏洞。 二、影响版本 ------ 2.3.0 <= Apache Kafka Client <= 3.9.0 三、漏洞分析 ------ 之前分析了CVE-2025-27817,和这个都是同一路由触发的,可以去看看之前的文章分析connectors路由 漏洞存在的原因:黑名单不全,看下图  对于sasl.jaas.config的配置只过滤了JndiLoginModule,没对LdapLoginModule进行过滤,在3.9.1版本,可以看到黑名单增加了LdapLoginModule:  好,开始分析调用链: 全局搜SASL\_JAAS\_CONFIG,发现存在一处引用  继续跟进   这里被create调用,继续跟进  到这里,每一处调用都设置断点,开始发包:  在设置了config的情况下,进入到createChannelBuilder,然后继续跟进  注意这里,设置security.protocol为SASL\_PLAINTEXT,进入到SASL\_PLAINTEXT的逻辑,继续跟进  之后就是load LdapLoginModule,进入到LdapLoginModule  这里Get(USER\_PROVIDER),注意设置userProvider,跟进到login();这里的话需要账号密码,并且在config里面配置不了,可以看看逻辑: ```java public boolean login() throws LoginException { if (userProvider \== null) { throw new LoginException ("Unable to locate the LDAP directory service"); } if (debug) { System.out.println("\\t\\t\[LdapLoginModule\] user provider: " + userProvider); } // attempt the authentication if (tryFirstPass) { try { // attempt the authentication by getting the // username and password from shared state attemptAuthentication(true); // authentication succeeded succeeded \= true; if (debug) { System.out.println("\\t\\t\[LdapLoginModule\] " + "tryFirstPass succeeded"); } return true; } catch (LoginException le) { // authentication failed -- try again below by prompting cleanState(); if (debug) { System.out.println("\\t\\t\[LdapLoginModule\] " + "tryFirstPass failed: " \+ le.toString()); } } } else if (useFirstPass) { try { // attempt the authentication by getting the // username and password from shared state attemptAuthentication(true); // authentication succeeded succeeded \= true; if (debug) { System.out.println("\\t\\t\[LdapLoginModule\] " + "useFirstPass succeeded"); } return true; } catch (LoginException le) { // authentication failed cleanState(); if (debug) { System.out.println("\\t\\t\[LdapLoginModule\] " + "useFirstPass failed"); } throw le; } } // attempt the authentication by prompting for the username and pwd try { attemptAuthentication(false); // authentication succeeded succeeded \= true; if (debug) { System.out.println("\\t\\t\[LdapLoginModule\] " + "authentication succeeded"); } return true; } catch (LoginException le) { cleanState(); if (debug) { System.out.println("\\t\\t\[LdapLoginModule\] " + "authentication failed"); } throw le; } } ``` ```java private void attemptAuthentication(boolean getPasswdFromSharedState) throws LoginException { // first get the username and password getUsernamePassword(getPasswdFromSharedState); if (password \== null || password.length \== 0) { throw (LoginException) new FailedLoginException("No password was supplied"); } ...... ``` 这里获取不到password直接就抛错了 getUsernamePassword: ```java private void getUsernamePassword(boolean getPasswdFromSharedState) throws LoginException { if (getPasswdFromSharedState) { // use the password saved by the first module in the stack username \= (String)sharedState.get(USERNAME\_KEY); password \= (char\[\])sharedState.get(PASSWORD\_KEY); return; } // prompt for a username and password if (callbackHandler \== null) throw new LoginException("No CallbackHandler available " + "to acquire authentication information from the user"); Callback\[\] callbacks = new Callback\[2\]; callbacks\[0\] \= new NameCallback(getAuthResourceString("username.")); callbacks\[1\] \= new PasswordCallback(getAuthResourceString("password."), false); try { callbackHandler.handle(callbacks); ``` 这里获取passwd有两种方式,读取sharedState里的passwd以及通过callback获取。如果设置useFirstPass or tryFirstPass为false的话会进入callback:   所以passwd的值依赖于sharedState: sharedState 是 **JAAS(Java Authentication and Authorization Service)** 提供的一个机制,用于**多个 LoginModule 之间共享认证信息**,如用户名、密码等。 所以这里可以想到在config里面配置两个module,一个用于初始化sharedState,让sharedState里面有passwd,一个就是通过ldap达到远程加载的目的,但是很残酷,他限制只能加载一个module:  如果传两个module的话,都不会执行。到了这里,LdapLoginModule只有在有passwd的情况下才会与远程ldap通信 也就是说,如果实际业务中用到了sharedState,并且在利用之前通过其他的LoginModule,设置了storePass=true的话,ldap就能成功被触发。 也可以全局搜sharedState.put,发现有四个module调用了:Krb5LoginModule,FileLoginModule,LdapLoginModule,JndiLoginModule 可以自己看看逻辑,都差不多,callback获取密码的也会写到sharedState,要看业务怎么写 *sink*点呢就是其他LoginModule在实际业务中设置了storePass为true,导致storePass能被攻击者恶意利用,达到ldap注入攻击,==> "new InitialDirContext(env);" 四、漏洞复现 ------ 自己写一个小demo,然后用callback交互获取passwd  可以看到,经过其他Module认证完后,sharedState有值,后面的就是分析里面走的逻辑,看结果吧:  可以看到触发了ldap连接情况,这里只验证个外联,ldap反序列化网上文章也很多,可以自己去看看。 五、修复 ---- 升级kafka版本 3.9.1 or 4.0.0 END --- CVE-2025-27818分析结束,这个洞微步给的中,如果我没分析错的话(菜,很可能分析错了),出现问题的点就是用这个组件的开发者,不熟悉kafka,错误使用LoginModule,导致的安全问题。调用链: ```json connectors(args) |...... LoginContext.login() └──> LdapLoginModule.initialize(subject, callbackHandler, sharedState, options) └──> LdapLoginModule.login() └──> getUsernamePassword(useShared) └──> use sharedState.get(...) └──> callbackHandler.handle(...) └──> connectToLdapServer() └──> InitialDirContext(env) └──> com.sun.jndi.ldap.LdapCtxFactory.getInitialContext() └──> 实际网络请求 + 凭据验证(Sink点) ```
发表于 2025-07-03 18:12:31
阅读 ( 157 )
分类:
开发框架
1 推荐
收藏
0 条评论
请先
登录
后评论
hahaha123
2 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!