
Scanner e analisador de web shells.
O analisador de web shell é um binário independente multiplataforma construído exclusivamente para identificar, decodificar e marcar arquivos suspeitos de serem web shells. O analisador de web shell é o irmão mais velho do projeto web shell scanner (http://github.com/tstillz/webshell-scan), que apenas escaneia arquivos via regex, sem decodificação ou análise de atributos.
O regex e suas rotinas de decodificação internas fornecidas com o scanner não garantem encontrar todos os web shells no disco e podem identificar alguns falsos positivos. Recomenda-se também testar o analisador e avaliar seu impacto antes de executar em sistemas de produção. O analisador não tem garantia, use por sua conta e risco.
PHP, ASP/X. JSP/X, CFM e outros tipos estão em desenvolvimento.Todo arquivo escaneado pode ser processado por ações PRE e/ou POST:
A ideia por trás das funções PreDecodeActions era usar regex para identificar uma string ou padrão correspondente, adquirir seu conteúdo bruto, realizar etapas de decodificação/limpeza definidas e enviar a saída final de volta ao mecanismo de análise para re-varredura/processamento.
Um exemplo muito simples disso é a decodificação Base64. Para verificar qualquer lógica de detecção contra um web shell codificado em base64, devemos primeiro remover todas as camadas de base64. Para fazer isso, podemos usar a seguinte PreDecodeAction:
{
Name: "PHP_Base64Decode",
Regex: *regexp.MustCompile(`(?i)(?:=|\s+)(base64_decode\('('?\"?[A-Za-z0-9+\/=]+'?\"?))`),
DataCapture: *regexp.MustCompile(`(?i)((?:'|")[A-Za-z0-9+\/=]+(?:'|"))`),
PreDecodeActions: []cm.Action{
{Function: cm.StringReplace, Arguments: []interface{}{"\"", "", -1}},
{Function: cm.StringReplace, Arguments: []interface{}{"'", "", -1}},
},
Functions: []cm.Base_Func{cm.DecodeBase64},
},
Observando o bloco acima, primeiro temos o nome da função, o regex usado para corresponder, o regex de captura de dados (às vezes você pode querer ajustar o que capturar versus o que corresponde) e PreDecodeActions. Neste caso, ANTES da função cm.DecodeBase64 ser aplicada ao texto correspondente, o sistema primeiro removerá os seguintes itens " e '.
PostDecodeActions funciona ao contrário, onde a saída é verificada APÓS a decodificação ser realizada. Usando este modelo, podemos criar vários decodificadores personalizados que possuem infinitas funções PRE/POST e de decodificação para lidar com a maioria das necessidades de análise de web shell.
Uma detecção é um regex acompanhado de um nome e descrição. A ideia por trás deste modelo era tornar as detecções modulares e escaláveis, mantendo o contexto com a detecção real. As detecções compartilham o mesmo formato que os atributos, exceto que atributos não podem gerar uma detecção, eles só podem adicionar contexto a uma detecção existente. Vejamos o exemplo de bloco de lógica de detecção abaixo:
{
Name: "Generic_Embedded_Executable",
Description: "Looks for magic bytes associated with a PE file",
Regex: *regexp.MustCompile(`(?i)(?:(?:0x)?4d5a)`),
},
Com base no regex, podemos ver que está procurando por um arquivo PE do Windows embutido com base nos bytes mágicos de cabeçalho 4D 5A. Se encontrado, isso levaria a uma detecção e um relatório JSON seria gerado para o arquivo.
Atualmente, as detecções são aplicadas com base na extensão do arquivo ou genericamente para todos os tipos de arquivo. Por exemplo, rotinas de decodificação para PHP são definidas sob cm.GlobalMap.Function_Php e tags para atributos são definidas sob cm.GlobalMap.Tags_Php.
As funções cm.GlobalMap.Function_Generics e tags sob cm.GlobalMap.Tags_Generics se aplicam a TODAS as extensões de web shell como uma captura geral.
A marcação de atributos é um novo conceito que criei que adiciona "contexto" a uma detecção de web shell existente. Atributos sozinhos não podem atualmente gerar uma detecção por conta própria. Em um mecanismo de varredura tradicional, um scanner alertaria apenas se um web shell fosse detectado, mas forneceria pouco ou nenhum contexto adicional sobre quais capacidades (atributos) o web shell potencialmente possui. As tags de atributos funcionam da mesma forma que a lógica de detecção, no entanto, elas só aparecem após uma detecção ter sido identificada e não podem gerar detecções por conta própria. Observando o exemplo de lógica abaixo:
cm.GlobalMap.Tags_Php = []cm.TagDef{
{
Name: "PHP_Database_Operations",
Description: "Looks for common PHP functions used for interacting with a database.",
Regex: *regexp.MustCompile(`(?i)(?:'mssql_connect\|mysql_exec\()`),
Attribute: true,
},
}
Vemos que sob a struct Tags_Php, criamos uma nova tag PHP. Quando uma correspondência é encontrada durante a varredura, a flag Attribute é verificada e se definida como True, o web shell detectado terá a tag
PHP_Database_Operations anexada ao seu relatório JSON, juntamente com a frequência e o bloco de texto correspondente, conforme mostrado no exemplo de saída abaixo:
{
"filePath": "/testers/1.php",
"size": 66109,
"md5": "6793d8ebab93e5a0f91e5a331221f331",
"timestamps": {
"birth": "2019-02-03 02:02:22",
"created": "2020-07-29 02:50:15",
"modified": "2019-02-03 02:02:22",
"accessed": "2020-07-29 02:51:07"
},
"matches": {
"FilesMAn": 5,
"FilesMan": 29,
"cmd": 20,
"eval(": 4,
"exec(": 2,
"ipconfig": 1,
"netstat": 2,
"passthru(": 1,
"shell_exec(": 1
},
"decodes": {
"Generic_Base64Decode": 40,
"Generic_Multiline_Base64Decode": 165
},
"tags": {
"Generic_Embedding_Code_C": {
"bind(": 2,
"listen(": 2
},
"PHP_Banned_Function": {
"exec(": 3,
"get_current_user(": 1,
"getmyuid(": 1,
"link(": 7,
"listen(": 2,
"passthru(": 1,
"realpath(": 1,
"set_time_limit(": 1
},
"PHP_Database_Operations": {
"mysql_query(": 1
},
"PHP_Disk_Operations": {
"@chmod(": 1,
"@filegroup(": 4,
"@fileowner(": 4,
"@rename(": 2,
"fopen(": 7,
"fwrite(": 6
}
}
}
Essas tags não apenas ajudam a definir o que um web shell pode fazer, mas também auxiliam equipes como consultores de IR realizando compromissos de resposta ao vivo a ter um ponto de inflexão para onde potencialmente procurar em seguida.
Nenhum! Basta baixar o binário para seu SO, fornecer o diretório que deseja escanear (outros argumentos são opcionais) e deixar rodar.
Executar wsa sem argumentos mostra as seguintes opções:
/Users/beastmode$ ./wsa
Options:
-dir string
Directory to scan for web shells
-pretty
If set to true, the analyzer will output the results in a json indented form
-raw_contents
If a match is found, grab the raw contents and base64 + gzip compress the file into the JSON object.
-size int
Specify max file size to scan (default is 10 MB) (default 10)
-verbose
If set to true, the analyzer will print all files analyzer, not just matches
O único argumento obrigatório é dir. Você pode substituir os outros padrões do programa, se desejar.
A saída do analisador será escrita no console (saída padrão). Exemplo abaixo (Para melhores resultados, envie stdout para um arquivo json e revise/processe offline):
Linux: ./wsa -dir /opt/www
Windows: wsa.exe -dir C:\Windows\Inetput\wwwroot
### Com STDOUT e arquivo web shell completo codificado e compactado:
Linux: ./wsa -dir /opt/www -raw_contents=true > scan_results.json
Assim que o analisador terminar, ele exibirá as métricas gerais da varredura para STDOUT, conforme mostrado no exemplo abaixo:
{"scanned":311,"matches":122,"noMatches":189,"directory":"/webshell-master/php","scanDuration":1.4757737378333333,"systemInfo":{"hostname":"Beast","envVars":[""],"username":"beastmode","userID":"501","realName":"The Beast","userHomeDir":"/Users/beastmode"}}