Pergunta

Atualmente estamos construindo um aplicativo que executa diversas ferramentas externas.Muitas vezes temos que passar informações inseridas em nosso sistema pelos usuários para essas ferramentas.

Obviamente, este é um grande pesadelo de segurança esperando para acontecer.

Infelizmente, ainda não encontramos nenhuma classe no .NET Framework que execute programas de linha de comando e forneça o mesmo tipo de proteção contra ataques de injeção que os objetos IDbCommand fazem para bancos de dados.

No momento, estamos usando uma substituição de string muito primitiva que suspeito ser bastante insuficiente:

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

O que vocês fazem para evitar ataques de injeção de linha de comando?Estamos planejando implementar um regex que seja muito rigoroso e permita apenas a passagem de um subconjunto muito pequeno de caracteres, mas gostaria de saber se existe uma maneira melhor.

Alguns esclarecimentos:

  • Algumas dessas ferramentas não possuem APIs nas quais possamos programar.Se o fizessem, não estaríamos tendo esse problema.
  • Os usuários não escolhem ferramentas para executar, eles inserem metadados que as ferramentas que escolhemos usam (por exemplo, injetando metadados como avisos de direitos autorais em arquivos de destino).
Foi útil?

Solução

Você está executando os programas diretamente ou pelo shell?Se você sempre inicia um programa externo fornecendo o nome completo do caminho para o executável e deixando o shell fora da equação, então você não estará realmente suscetível a qualquer tipo de injeção de linha de comando.

EDITAR:DrFloyd, o shell é responsável por avaliar coisas como o backtick.Sem shell, sem avaliação de shell.Obviamente, você ainda precisa estar ciente de possíveis pegadinhas de segurança nos programas que você está chamando - mas não acho que esta questão seja sobre isso.

Outras dicas

Quando você Processo.Iniciar um novo processo, forneça os parâmetros em seu argumento Parameters em vez de construir você mesmo toda a linha de comando.

Não tenho tempo para um teste adequado, mas acho que isso deve ajudar a protegê-lo até certo ponto.

Vou testar isso amanhã.

EDITAR: Ah, alguém me venceu de novo.Mas aqui está outro ponto:Tente usar o Console.InputStream (não lembro o nome exato) para fornecer dados em vez de passar parâmetros. Essa é uma solução possível?como corrigir o comando para que ele leia do dispositivo CON e você forneça os dados por meio do fluxo de entrada.

Em C++ ativado janelas, basta escapar \ e " quando necessário, citar o argumento e ShellExecute-o.Então, tudo que estiver entre aspas deve ser tratado como texto.

Isso deve ilustrar:


#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;
    }
}

Mas, com qualquer método que você usar, você deve testá-lo para ver se realmente evita a injeção.

Não use uma lista negra para prevenir injeções.Se houver n maneiras de injetar código, você pensará em n-m onde m > 0.

Use uma lista de permissões de parâmetros (ou padrões) aceitos.É muito mais restritivo por natureza, mas essa é a natureza da segurança.

Bem, se você puder invocar as ferramentas programaticamente sem a linha de comando, essa provavelmente seria sua melhor opção.Caso contrário, você poderia executar a ferramenta de linha de comando através de um usuário que não tem absolutamente nenhum acesso para fazer nada (exceto talvez um único diretório com o qual eles não possam causar nenhum dano)...embora isso possa acabar quebrando a ferramenta, dependendo do que ela faz.

Apenas observe que nunca tive que enfrentar esse problema, porque nunca tive que invocar uma ferramenta de linha de comando de um aplicativo externo em que a ferramenta exigisse entrada do usuário.

Hmmm...

Parece que você tem uma lista de comandos válidos que os usuários podem executar.Mas você não quer que eles executem todos eles.

Você pode tentar usar a linha de comando real e verificar se o arquivo existe pelo menos no local "seguro".

Você também pode resolver o problema com mais interface, fornecendo uma lista suspensa de comandos e parâmetros que eles podem usar.É mais trabalhoso para você, mas, em última análise, ajuda os usuários.

Você está executando os programas diretamente ou pelo shell?Se você sempre inicia um programa externo fornecendo o nome completo do caminho para o executável e deixando o shell fora da equação, então você não estará realmente suscetível a qualquer tipo de injeção de linha de comando.

@Curt Hagenlocher O backtick pode matar você.Se o sistema Windows estiver configurado "errado" ou se o sistema Unix permitir, um diretório &bt;del *&bt;irá primeiro executar o comando del * e depois usar a saída no lugar do del *, que neste caso não importa porque não há nada para dir (ou ls)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top