需要帮助理解这个简短的 C++ 程序及其漏洞
Need help understing this short C++ program and its vulnerability
如果有人能准确地向我解释代码的作用,我将不胜感激。我知道存在缓冲区溢出和 bash 命令执行漏洞 - 但由于我是网络专家而不是程序员,所以我真的需要一些帮助来理解整个代码。提前致谢!
int main () {
int status;
char t[1024]="ps -eo lstart,cmd | grep ";
cout << "Content-type:text/html\r\n\r\n"<<endl;
char *value = getenv("QUERY_STRING");
strcat(t,value);
status = system(strcat(t," | grep -v grep | head -n 1 | awk '{ print \" \"\" \"\" \"\" \"}'"));
return 0;
}
这只是声明了变量status
int status;
这用 C 字符串 "ps -eo lstart,cmd | grep " 声明了 t。
t
的最大容量为 1024 字节(字符串 1023 + [=18=]
1 字节)
char t[1024]="ps -eo lstart,cmd | grep ";
打印下面的字符串
cout << "Content-type:text/html\r\n\r\n"<<endl;
获取环境变量QUERY_STRING
char *value = getenv("QUERY_STRING");
将上面的值连接到 t
。这里你可能有缓冲区溢出,因为你不知道 value
中字符串的大小,它可能比 1024 字节长
strcat(t,value);
调用 system()
函数,将之前的 t
与所有这些 grep 等命令进行另一个串联...这里你可能会遇到另一个缓冲区溢出,一旦 t
也可能在这里溢出它的 1024 字节。
status = system(strcat(t," | grep -v grep | head -n 1 | awk '{ print \" \"\" \"\" \"\" \"}'"));
tl;dr: 这就是您的代码所做的,作为 shell 脚本:
#!/bin/bash
echo -en "Content-type:text/html\r\n\r\n"
ps -eo lstart,cmd | grep init | grep -v $QUERY_STRING | \
head -n 1 | awk '{ print " "" "" "" "}'
现在是更长的答案。
重写代码
首先,让我们把它变成 C++ 而不是 C(就像你的标签暗示你正在询问的那样),并进行一些错误处理,然后谈谈发生了什么:
#include <iostream>
#include <string>
#include <string_view>
int main () {
auto query_string = getenv("QUERY_STRING");
if (query_string == nullptr) {
std::cerr << "Couldn't obtain QUERY_STRING environment variable\n";
return EXIT_FAILURE;
}
if (std::string_view{query_string}.empty()) {
std::cerr << "Empty query string (QUERY_STRING environment variable)\n";
return EXIT_FAILURE;
}
std::stringstream command_line;
command_line
<< "ps -eo lstart,cmd | grep "
<< query_string
<< " | grep -v grep | head -n 1 | awk '{ print \" \"\" \"\" \"\" \"}'";
std::cout << "Content-type:text/html\r\n\r\n";
return system(command_line.str()); // security vulnerability, see below
}
我们在这里做什么?
所以,我们在这里创建一个 command-line,然后我们使用 system()
函数执行它。它调用带有一些开关的 ps
命令,然后使用 grep
、head
和 awk
进行一些文本处理 - 使用管道机制移动每个命令的输出到下一个。他们的关键部分是我们使用环境变量 QUERY_STRING
来过滤 ps
结果,即我们列出匹配某些短语的进程。如果我们编译这个程序,设置环境变量和运行,这就是它的样子:
$ export QUERY_STRING=init
$ ./the_program
Content-type:text/html
Sun 3 Jun 2018 21:48:56
这给我们的是 command-line 不包含短语 "init" 的第一个进程的开始时间。所以现在你可以猜到我的系统从昨天开始就一直在运行...
最后,作为一个网络人,你可能意识到 "Content-type" mumbo-jumbo 和 double-newline 是一个 MIME header,所以这个输出可能是用作 HTTP 响应。可能这是某种 CGI 脚本。
安全漏洞
- 在原始代码中,缓冲区大小被任意限制为 1024 - 但没有任何限制 QUERY_SIZE 不能超过该值。如果它更长,就会出现内存损坏,这可能会带来安全隐患;并且攻击者很可能能够弄清楚你的内存布局,所以它更危险。这与 C++ 版本一起消失了。
第二个漏洞与 system
命令有关。我们将任意字符串注入到我们正在创建的字符串中;并且没有什么可以阻止某人设置
$export QUERY_STRING="dummy; rm -rf $HOME ; echo"
在这种情况下你会 运行:
ps -eo lstart,cmd | grep dummy; rm -rf $HOME ; echo | grep -v init | head -n 1 | awk '{ print " "" "" "" "}'
这将删除有效用户主目录下的所有内容。或者它可以是任何命令,包括将自定义 C/C++ 程序编译为 运行 系统上的一些任意代码。很差
- 即使您将 QUERY_STRING 清理为仅是一个有效的 grep 模式,如果有人以某种方式提供复杂的 ultra-long grep 模式,仍然可能会有 denial-of-service 攻击.所以限制长度也是一个好主意。
如果有人能准确地向我解释代码的作用,我将不胜感激。我知道存在缓冲区溢出和 bash 命令执行漏洞 - 但由于我是网络专家而不是程序员,所以我真的需要一些帮助来理解整个代码。提前致谢!
int main () {
int status;
char t[1024]="ps -eo lstart,cmd | grep ";
cout << "Content-type:text/html\r\n\r\n"<<endl;
char *value = getenv("QUERY_STRING");
strcat(t,value);
status = system(strcat(t," | grep -v grep | head -n 1 | awk '{ print \" \"\" \"\" \"\" \"}'"));
return 0;
}
这只是声明了变量status
int status;
这用 C 字符串 "ps -eo lstart,cmd | grep " 声明了 t。
t
的最大容量为 1024 字节(字符串 1023 + [=18=]
1 字节)
char t[1024]="ps -eo lstart,cmd | grep ";
打印下面的字符串
cout << "Content-type:text/html\r\n\r\n"<<endl;
获取环境变量QUERY_STRING
char *value = getenv("QUERY_STRING");
将上面的值连接到 t
。这里你可能有缓冲区溢出,因为你不知道 value
中字符串的大小,它可能比 1024 字节长
strcat(t,value);
调用 system()
函数,将之前的 t
与所有这些 grep 等命令进行另一个串联...这里你可能会遇到另一个缓冲区溢出,一旦 t
也可能在这里溢出它的 1024 字节。
status = system(strcat(t," | grep -v grep | head -n 1 | awk '{ print \" \"\" \"\" \"\" \"}'"));
tl;dr: 这就是您的代码所做的,作为 shell 脚本:
#!/bin/bash
echo -en "Content-type:text/html\r\n\r\n"
ps -eo lstart,cmd | grep init | grep -v $QUERY_STRING | \
head -n 1 | awk '{ print " "" "" "" "}'
现在是更长的答案。
重写代码
首先,让我们把它变成 C++ 而不是 C(就像你的标签暗示你正在询问的那样),并进行一些错误处理,然后谈谈发生了什么:
#include <iostream>
#include <string>
#include <string_view>
int main () {
auto query_string = getenv("QUERY_STRING");
if (query_string == nullptr) {
std::cerr << "Couldn't obtain QUERY_STRING environment variable\n";
return EXIT_FAILURE;
}
if (std::string_view{query_string}.empty()) {
std::cerr << "Empty query string (QUERY_STRING environment variable)\n";
return EXIT_FAILURE;
}
std::stringstream command_line;
command_line
<< "ps -eo lstart,cmd | grep "
<< query_string
<< " | grep -v grep | head -n 1 | awk '{ print \" \"\" \"\" \"\" \"}'";
std::cout << "Content-type:text/html\r\n\r\n";
return system(command_line.str()); // security vulnerability, see below
}
我们在这里做什么?
所以,我们在这里创建一个 command-line,然后我们使用 system()
函数执行它。它调用带有一些开关的 ps
命令,然后使用 grep
、head
和 awk
进行一些文本处理 - 使用管道机制移动每个命令的输出到下一个。他们的关键部分是我们使用环境变量 QUERY_STRING
来过滤 ps
结果,即我们列出匹配某些短语的进程。如果我们编译这个程序,设置环境变量和运行,这就是它的样子:
$ export QUERY_STRING=init
$ ./the_program
Content-type:text/html
Sun 3 Jun 2018 21:48:56
这给我们的是 command-line 不包含短语 "init" 的第一个进程的开始时间。所以现在你可以猜到我的系统从昨天开始就一直在运行...
最后,作为一个网络人,你可能意识到 "Content-type" mumbo-jumbo 和 double-newline 是一个 MIME header,所以这个输出可能是用作 HTTP 响应。可能这是某种 CGI 脚本。
安全漏洞
- 在原始代码中,缓冲区大小被任意限制为 1024 - 但没有任何限制 QUERY_SIZE 不能超过该值。如果它更长,就会出现内存损坏,这可能会带来安全隐患;并且攻击者很可能能够弄清楚你的内存布局,所以它更危险。这与 C++ 版本一起消失了。
第二个漏洞与
system
命令有关。我们将任意字符串注入到我们正在创建的字符串中;并且没有什么可以阻止某人设置$export QUERY_STRING="dummy; rm -rf $HOME ; echo"
在这种情况下你会 运行:
ps -eo lstart,cmd | grep dummy; rm -rf $HOME ; echo | grep -v init | head -n 1 | awk '{ print " "" "" "" "}'
这将删除有效用户主目录下的所有内容。或者它可以是任何命令,包括将自定义 C/C++ 程序编译为 运行 系统上的一些任意代码。很差
- 即使您将 QUERY_STRING 清理为仅是一个有效的 grep 模式,如果有人以某种方式提供复杂的 ultra-long grep 模式,仍然可能会有 denial-of-service 攻击.所以限制长度也是一个好主意。