防止命令行注入攻击

时间:2020-03-05 18:47:54  来源:igfitidea点击:

我们目前正在构建一个执行许多外部工具的应用程序。我们经常必须将用户输入到系统中的信息传递给这些工具。

显然,这是一场巨大的安全噩梦。

不幸的是,我们尚未在.NET Framework中找到任何执行命令行程序的类,它们提供与IDbCommand对象对数据库提供的相同类型的防范注入攻击的措施。

现在,我们正在使用非常原始的字符串替换,我怀疑这是不够的:

protected virtual string Escape(string value)
{
      return value
        .Replace(@"\", @"\")
        .Replace(@"$", @"$")
        .Replace(@"""", @"\""")
        .Replace("`", "'")
      ;
}

你们如何防止命令行注入攻击?我们正计划实施一个非常严格的正则表达式,并且只允许一小部分字符通过,但是我想知道是否有更好的方法。

一些说明:

  • 其中一些工具没有我们可以编程的API。如果他们做到了,我们将不会遇到这个问题。
  • 用户不选择执行工具,而是输入我们选择的工具使用的元数据(例如,将诸如版权声明之类的元数据注入目标文件中)。

解决方案

回答

我们是直接执行程序还是通过外壳执行程序?如果我们总是通过给可执行文件提供完整路径名而将外壳程序排除在外而启动外部程序,那么我们实际上就不会受到任何类型的命令行注入的影响。

编辑:DrFloyd,外壳负责评估诸如反引号之类的东西。没有外壳,没有外壳评估。显然,我们仍然需要了解正在调用的程序中是否存在任何潜在的安全隐患-但我认为这个问题并不关乎此。

回答

好吧,如果我们可以在不使用命令行的情况下以编程方式调用工具,那可能是最佳选择。否则,我们可能会通过绝对无权执行任何操作的用户来执行命令行工具(除了可能对其无害的单个目录除外)……尽管这最终可能会破坏该工具,具体取决于该工具的功能。

请注意,我从来没有遇到过这个问题,因为我从来没有真正从一个面向外部的应用程序调用命令行工具,而该工具需要用户输入。

回答

嗯...

听起来我们有用户可以执行的有效命令列表。但是我们不希望它们执行所有操作。

我们可以尝试使用实际的命令行,并至少确认该文件位于"安全"位置。

我们还可以通过更多界面解决问题,提供它们可以使用的命令和参数的下拉列表。这需要我们做更多的工作,但最终会为用户提供帮助。

回答

Are you executing the programs directly or going through the shell? If you always launch an external program by giving the full path name to the executable and leaving the shell out of the equation, then you aren't really susceptible to any kind of command line injection.

@Curt Hagenlocher反讽可以杀死你。如果Windows系统设置为"错误",或者Unix系统允许,则为dir&bt; del *&bt;。将首先执行del *命令,然后使用输出代替del *,在这种情况下,这将无关紧要,因为dir(或者ls)没有任何内容

回答

当我们启动新进程时,请在其Parameters参数中提供参数,而不要自己构建整个命令行。

尚无时间进行适当的测试,但我认为这应该有助于在某种程度上进行保护。

明天将对此进行测试。

编辑:啊,有人再次击败了我。但是,还有另一点:尝试使用Console.InputStream(不记得确切的名称)来提供数据而不是传递参数,这是否可能解决方案?像修复命令一样,以便从CON设备读取数据,然后通过输入流提供数据。

回答

在Windows上的C ++中,只需在需要的位置转义\和",对参数加引号,然后执行ShellExecute。然后,将引号内的所有内容都视为文本。

这应该说明:

#include <iostream>
#include <string>
#include <windows.h>
#include <cstdlib>
using namespace std;

// Escape and quote string for use as Windows command line argument
string qEscape(const string& s) {
    string result("\"");
    for (string::const_iterator i = s.begin(); i != s.end(); ++i) {
        const char c = *i;
        const string::const_iterator next = i + 1;
        if (c == '"' || (c == '\' && (next == s.end() || *next == '"'))) {
            result += '\';
        }
        result += c;
    }
    result += '"';
    return result;
}

int main() {
    // Argument value to pass: c:\program files\test\test.exe
    const string safe_program = qEscape("c:\program files\test\test.exe");
    cout << safe_program << " ";

    // Argument value to pass: You're the "best" around.
    const string safe_arg0 = qEscape("You're the \"best\" around.");

    // Argument value to pass: "Nothing's" gonna ever keep you down.
    const string safe_arg1 = qEscape("\"Nothing's\" gonna ever keep you down.");

    const string safe_args = safe_arg0 + " " + safe_arg1;
    cout << safe_args << "\n\n";

    // c:\program files\test\  to pass.
    const string bs_at_end_example = qEscape("c:\program files\test\");
    cout << bs_at_end_example << "\n\n";

    const int result = reinterpret_cast<int>(ShellExecute(NULL, "open", safe_program.c_str(), safe_args.c_str(), NULL, SW_SHOWNORMAL));
    if (result < 33) {
        cout << "ShellExecute failed with Error code " << result << "\n";
        return EXIT_FAILURE;
    }
}

但是,无论使用哪种方法,都应该对其进行测试,以确保它确实可以防止注入。

回答

不要使用黑名单来防止注射。如果有n种注入代码的方式,我们会想到n m,其中m> 0。

使用接受参数(或者模式)的白名单。从本质上讲,它的限制要多得多,但这就是安全性的本质。