首页 > 数据库 >java基础漏洞学习----SQL注入漏洞

java基础漏洞学习----SQL注入漏洞

时间:2023-10-17 22:55:24浏览次数:35  
标签:username java String ---- 漏洞 sql import id

java基础漏洞学习----SQL注入漏洞

前置基础知识

https://www.cnblogs.com/thebeastofwar/p/17759805.html

执行SQL语句的几种方式

1.Statement执行SQL语句
java.sql.Statement是Java JDBC下执行SQL语句的一种原生方式,执行语句时需要通过拼接来执行
若拼接的语句没有经过过滤,将出现SQL注入漏洞
事例代码:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class StatementSQL {
    public static void main(String[] args) throws ClassNotFoundException , SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/security?&useSSL=false&serverTimezone=UTC", "root", "root");
        String id = "2";
        String sql = "select * from users where id = " + id;
        Statement ps = conn.createStatement();
        ResultSet rs = ps.executeQuery(sql);
        while (rs.next()) {
            System.out.println("id: " + rs.getInt("id") + " username: " + rs.getString("username") + " password: " + rs.getString("password"));
        }
    }
}


SQL注入利用方法1:
万能密码(or 1=1)

SQL注入利用方法2:
利用盲注
2.PreparedStatement执行SQL语句
PreparedStatement是继承statement的子接口,包含已编译的SQL语句,PreparedStatement会预处理SQL语句,SQL语句可具有一个或多个IN参数。
IN参数的值在SQL语句创建时未被指定,而是为每一个IN参数保留一个问号(?)作为占位符。每个问号的值,必须在该语句执行之前通过适当的setXXX方法来退供。
如果是int型则用setInt方法,如果是string型则用setString方法。
PreparedStatement预编译的特性使得其执行SQL语句要比Statement块,SQL语句会编译在数据库系统中,执行计划会被缓存起来,使用预处理语句比普通语句更快。
PreparedStatement预编译还有另一个优势,可以有效地防止SQL注入攻击,其相当于Statement的升级版
事例代码

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;

public class PreparedStatementSQL {
    public static void main(String[] args) throws ClassNotFoundException , SQLException{
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/security?&useSSL=false&serverTimezone=UTC","root","root");
        //实例化 PraparedStatement对象
        String sql = "SELECT * FROM users WHERE id = ?";
        PreparedStatement preparedStatement = conn.prepareStatement(sql);

        int id = 2; // 假设你要查询id为2a的用户
        //设置占位符为id变量
        preparedStatement.setInt(1, id);

        ResultSet rs = preparedStatement.executeQuery();
        while (rs.next()) {
            System.out.println("id: " + rs.getInt("id") + " username: " + rs.getString("username") + " password: " + rs.getString("password"));
        }
    }
}

3.MyBatis执行SQL语句
导入依赖(参考文章 https://blog.csdn.net/weixin_45991687/article/details/129351818 )

MyBatis存储SQL语句(UserMapper.java)

public interface UserMapper {
    User getUser(int id);
}

MyBatis映射存储SQL语句(UserMapper.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="UserMapper">
    <select id="getUser" resultType="User">
        SELECT * FROM users WHERE id = #{id}
    </select>
</mapper>

定义MyBatis配置文件(mybatis-config.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/security"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

定义主体代码(MyBatisSQL.java)

import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisSQL {
    public static void main(String[] args) {
        // 加载 MyBatis 配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        // 创建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 创建 SqlSession
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 从控制台输入要查询的用户 ID
            Scanner scanner = new Scanner(System.in);
            System.out.print("请输入要查询的用户 ID:");
            int userId = scanner.nextInt();
            scanner.close();

            // 执行 SQL 查询语句
            User user = session.selectOne("UserMapper.getUser", userId);

            // 输出结果
            if (user != null) {
                System.out.println("用户信息:");
                System.out.println("ID: " + user.getId());
                System.out.println("用户名: " + user.getUsername());
                System.out.println("密码: " + user.getPassword());
            } else {
                System.out.println("未找到该用户");
            }
        }
    }
}


4.Hibernate
下载并导入jar包 (lib\required里所有的jar包)
https://hibernate.org/orm/releases/5.2/
hibernate.cfg.xml 其中org.hibernate.dialect.MySQL5Dialect中的数字为mysql版本,请根据自己的mysql版本进行调整

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
        <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/security</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">root</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.hbm2ddl.auto">update</property>
        <mapping class="User"/>
    </session-factory>
</hibernate-configuration>

User.java

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

    public User() {
    }

    public User(int id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

HibernateSQL.java

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.Query;
import java.util.logging.Logger;
import java.util.logging.Level;

import java.util.List;

public class HibernateSQL {

    public static void main(String[] args) {

        // 关闭 Hibernate 日志输出
        Logger.getLogger("org.hibernate").setLevel(Level.OFF);
        // 关闭 MySQL 驱动程序的日志输出
        Logger.getLogger("com.mysql").setLevel(Level.OFF);
        // 创建Hibernate配置对象
        Configuration configuration = new Configuration().configure("hibernate.cfg.xml");
        // 创建SessionFactory
        SessionFactory sessionFactory = configuration.buildSessionFactory();
        // 创建Session
        Session session = sessionFactory.openSession();

        try {
            // 开启事务
            session.beginTransaction();

            // 执行查询语句
            String queryString = "SELECT * FROM Users u WHERE u.id = :id";
            Query query = session.createNativeQuery(queryString);
            query.setParameter("id", 2); // 设置参数名称和参数值
            List<Object[]> users = query.list();

            // 打印查询结果
            for (Object[] userData : users) {
                int id = (int) userData[0];
                String username = (String) userData[1];
                String password =(String) userData[2];
                System.out.println("ID: " + id);
                System.out.println("Username: "+username);
                System.out.println("Password: " + password);
            }

            // 提交事务
            session.getTransaction().commit();
        } catch (Exception e) {
            // 回滚事务
            session.getTransaction().rollback();
            e.printStackTrace();
        } finally {
            // 关闭Session
            session.close();
        }
    }
}

常见SQL注入

1.SQL语句参数直接动态拼接

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class StatementSQL {
    public static void main(String[] args) throws ClassNotFoundException , SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/security?&useSSL=false&serverTimezone=UTC", "root", "root");
        String id = "2";
        String sql = "select * from users where id = " + id;
        Statement ps = conn.createStatement();
        ResultSet rs = ps.executeQuery(sql);
        while (rs.next()) {
            System.out.println("id: " + rs.getInt("id") + " username: " + rs.getString("username") + " password: " + rs.getString("password"));
        }
    }
}

如果输入"2 or 1=1"的话会返回全部内容

2.预编译有误
上面上述了实验Statement执行SQL语句存在动态拼接的SQL注入漏洞,那么如果使用PrepareStatement执行SQL语句就一定能防止SQL注入吗
答案是否定的,在预编译语句中使用错误的编程方式,也可能会产生SQL注入漏洞

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;

public class PreparedStatementSQL2 {
    public static void main(String[] args) throws ClassNotFoundException , SQLException{
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/security?&useSSL=false&serverTimezone=UTC","root","root");

        String username = "user%' or '1'='1'#";
        int id = 2;

        String sql = "SELECT * FROM users WHERE id = ?";
        sql+= " and username like '%" + username + "%'";
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        preparedStatement.setInt(1, id);
        ResultSet rs = preparedStatement.executeQuery();
        while (rs.next()) {
            System.out.println("id: " + rs.getInt("id") + " username: " + rs.getString("username") + " password: " + rs.getString("password"));
        }
    }
}


虽然前面使用了预编译,但是后面使用了变量拼接的方式
3.order by 注入
是否在预编译语句中按规范编程就能方法SQL注入?答案也是否定的,因为在某些特殊情况下不能使用PrepareStatement,比如order by字句排序
这是因为order by字句后面需要加字段名或者字段位置,二字段名是不能带引号的,否则就会被认为是一个字符串而不是字段名
PrepareStatement是使用占位符传入参数的,传递的参数就会有单引号包裹,这样会导致order by语句失效,所以order by语句只能使用拼接

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class StatementSQL {
    public static void main(String[] args) throws ClassNotFoundException , SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/security?&useSSL=false&serverTimezone=UTC", "root", "root");
        String id = "2 or 1=1";
        String sql = "select * from users order by " + id;
        Statement ps = conn.createStatement();
        ResultSet rs = ps.executeQuery(sql);
        while (rs.next()) {
            System.out.println("id: " + rs.getInt("id") + " username: " + rs.getString("username") + " password: " + rs.getString("password"));
        }
    }
}


4.%和_模糊查询
在java预编译中不会对%和_进行转义处理,而%和_刚好是like查询的通配符,因此可能会造成恶意查询

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;

public class PreparedStatementSQL2 {
    public static void main(String[] args) throws ClassNotFoundException , SQLException{
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/security?&useSSL=false&serverTimezone=UTC","root","root");
        String username = "%%";
        String sql = "SELECT * FROM users WHERE username like ?";
        PreparedStatement preparedStatement = conn.prepareStatement(sql);
        preparedStatement.setString(1, username);
        ResultSet rs = preparedStatement.executeQuery();
        while (rs.next()) {
            System.out.println("id: " + rs.getInt("id") + " username: " + rs.getString("username") + " password: " + rs.getString("password"));
        }
    }
}


5.MyBatis中#{}和${}的区别
#{}在底层实现上使用'?'作为占位符来生成PreparedStatement,也就是预编译
${}则相当于字符串拼接
此时将UserMapper.xml改为这样的话

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="UserMapper">
    <select id="getUser" resultType="User">
        SELECT * FROM users WHERE id = ${id}
    </select>
</mapper>

User.java内容不变

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

    public User() {
    }

    public User(int id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

然后MyBatisSQL.java

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MyBatisSQL {
    public static void main(String[] args) throws ClassNotFoundException {
        // 加载 MyBatis 配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }

        // 创建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 创建 SqlSession
        try (SqlSession session = sqlSessionFactory.openSession()) {

            String userId = "2 or 1=1";

            // 执行 SQL 查询语句
            List<User> userList = session.selectList("UserMapper.getUser", userId);

            // 输出结果
            if (!userList.isEmpty()) {
                System.out.println("用户信息:");
                for (User user : userList) {
                    System.out.println("ID: " + user.getId());
                    System.out.println("用户名: " + user.getUsername());
                    System.out.println("密码: " + user.getPassword());
                    System.out.println();
                }
            } else {
                System.out.println("未找到该用户");
            }
        }
    }
}


如果把${}改回#{}的话,看到虽然还是2 or 1=1,但是仍然只查询了2

在order by查询的时候只能用${}

SELECT * FROM users order by ${id}


在like查询的时使用#{}或报错

SELECT * FROM users where username like '%#{username}%'

然后主代码稍微改一下

String username = "A";
List<User> userList = session.selectList("UserMapper.getUser", username);

报错

此时把#{}改为${}
成功查询,但有很大的SQL注入威胁

在in查询的时候

SELECT * FROM users where username in (#{username})

修改一下主代码

import java.util.HashMap;
import java.util.Map;

...

String usernames = "'Angelina','superman'";
// 构建参数对象
Map<String, Object> parameter = new HashMap<>();
parameter.put("usernames", usernames);
// 执行 SQL 查询语句
List<User> userList = session.selectList("UserMapper.getUser", parameter);


因为在使用#{}时候,会把usernames数组当成一个整体,也就是说查询了in "Angelinasuperman",显然偏离了原先的逻辑
但是如果#{}是这种情况的话就没问题了

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="UserMapper">
    <!-- SQL语句定义 -->
    <select id="getUser" parameterType="java.util.Map" resultType="User">
        SELECT * FROM users WHERE username IN
        <foreach collection="usernames" item="username" open="(" close=")" separator=",">
            #{username}
        </foreach>
    </select>
</mapper>

但如果是这样的话可能会存在SQL注入漏洞

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="UserMapper">
    <!-- SQL语句定义 -->
    <select id="getUser" parameterType="java.util.Map" resultType="User">
        SELECT * FROM users WHERE username IN (${usernames})
    </select>
</mapper>

代码审计技巧

1.搜索'${'
2.搜索Statement,createStatement,PrepareStatement
3.搜索order by,in,like或者in (${,like '%${

标签:username,java,String,----,漏洞,sql,import,id
From: https://www.cnblogs.com/thebeastofwar/p/17767455.html

相关文章

  • vulnhub-Billu_b0x
    十一周信息搜集arp-scan-l找到主机扫描一下服务nmap-p-192.168.88.134访问一下80端口扫描一下目录dirsearch-uhttp://192.168.88.134扫到挺多东西的逐个访问一下目前来看有一个phpmyadmin和一个add.php有用试一下弱口令和注入webshell发现都没用......
  • 自动化测试有必要学吗?
    最近收到不少小伙伴私信提问,其中问得比较多的就是“学习自动化测试有那么重要吗?”。我的回答是肯定的——很重要。相信不少同学都有诸如此类的疑问,例如:“日常工作中好像用不上自动化?”、“手工点点点好像也可以”、“为什么还要学自动化呢?”等等……其实不然,以下就从多......
  • Leetcode24. 两两交换链表中的节点
    题目描述给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。示例提交的代码classSolution{ListNodenextNode;publicListNodeswapPairs(ListNodehead){//特殊化处理......
  • Mac电脑80端口被占用的2种解决办法
    前提:确认占用80端口的程序(sudolsof-i:80)我研发用的是Mac电脑,查看80端口被占用的进程命令:sudolsof-i:80我Mac电脑80端口占用进程如下:httpd进程占用了80端口,httpd进程即为Apache服务,下面提供了2种解决办法。OXL-MacBook:~obullxl$sudolsof-i:80COMMANDPIDUSERF......
  • 最长上升子序列
    引入以下记\(s\)的长度为\(n\),\(t\)的长度为\(m\)。一些问题:什么是子序列?称\(t\)是\(s\)的子序列,即是\(s\)删掉一些元素(可以什么都不删)后可以得到\(t\)。什么是上升子序列?称\(t\)是上升子序列,仅当\(s\)的子序列\(t\)满足\(\foralli\in[1,m),t_i<t_{......
  • Go 提取字符串中url,转换为markdown格式并替换
     Go提取字符串中url,转换为markdown格式并替换//MakeContentUrlToMarkDown将字符串中url非markdown格式转[](url)格式funcMakeContentUrlToMarkDown(sourceStringstring)(resultStringstring){//urlReMustCompile:=regexp.MustCompile(".*(?P<URL>(http|https|......
  • 自动配置原理的初步总结
    启动类里面的@SpringBootApplication注解封装了三个注解 1.@SpringBootConfiguration声明配置类 2.@ComponentScan组件扫描,默认本包和其子包 3.@EnableAutoConfiguration封装了@import注解,可以直接导入 @Bean,将当前方法交给容器管理,成为IOC容器的Bean(针对第三方......
  • Codeforces Round 888 (Div. 3) C. Tiles Comeback
    有\(n\)块瓷砖和一个正整数\(k\),第\(i\)块瓷砖染色为\(c_i\)。一开始站在第\(1\)块瓷砖往,然后可以开始往右跳吗,到第\(n\)块瓷砖停止。你可以得到的路径长度\(p\)为你从\(1\)到\(n\)踩过瓷砖的数量。你需要确定是否存在一条长度为\(p\)的路径满足。\(k\mid......
  • 进程
    进程的基本概念同一个程序3个进程同时进行(比如同时打开3个QQ),它们的PID也是不一样的。同一个程序执行一次进程,然后结束掉这个进程,然后再执行一次进程,则两次进程的PID是不一样的进程的状态转换运行态到就绪态:比如CPU收到一个时钟中断的信号,CPU会让当前进程下CPU,该进......
  • 30 透传属性
    知道即可,没什么意义也是组件之间的数据交流,但没什么意义......