Java 中的 SQL 注入以及如何轻松防止 SQL 注入

什么是SQL注射?

简而言之,SQL Injection意味着通过用户输入的数据在查询中注入/插入SQL代码,它可以在使用Oracle、MySQL、PostgreSQL 和 SQL Server等关系数据库的任何应用程序中发生。

要执行 SQL Injection,恶意用户首先尝试在应用程序中找到一个可以嵌入 SQL 代码和数据的地方。 它可以是任何 Web 应用程序或任何其他地方的 **login 页面。

对SQL注射的影响

恶意用户可以获得未经授权的访问您的应用程序并窃取数据

  • 他们可以更改、删除数据库中的数据并将应用程序下载
  • 黑客也可以通过执行数据库特定的系统命令来控制运行数据库服务器的系统

SQL 注射是如何工作的?

假设我们有一个名为 tbluser的数据库表,存储应用程序用户的数据。 userId是表的主要列。

让我们来看看下面的示例代码。

1String userId = {get data from end user}; 
2String sqlQuery = "select * from tbluser where userId = " + userId;

1、有效的用户输入

当上述查询以有效的数据(即 userId 值 132)执行时,它将看起来如下。

输入数据: 132

执行查询: *从 tbluser 中选择 ,其中 userId=132

结果: 查询将返回用户有 userId 132 的数据。

黑客用户输入

黑客可以使用 Postman、cURL 等工具更改用户请求,以将 SQL 代码作为数据发送,从而绕过任何 UI 侧验证。

输入数据: 2 或 1 = 1

執行查詢:從 tbluser 選取 ,其中 userId=2 或 1 = 1*

结果: 现在,上述查询有两个条件与 SQL OR 表达式。

  • userId=2:此部分将匹配具有 userId 值为 '2 的表行。
  • 1=1:此部分将始终被评估为 true。

类型 SQL 注射

让我们来看看SQL注射的四种类型。

基于Boolean SQL的注射

上面的例子是Boolean Based SQL Injection的例子,它使用一个Boolean表达式来评估为 true 或 false。

输入数据: 2 或 1 = 1

SQL 查询:从 tbl_employee 中选择 first_name, last_name,其中 empId=2 或 1=1

基于联盟的 SQL 注射

SQL union 运算符将来自两个不同的查询的数据与相同数量的列相结合,在这种情况下,使用 union 运算符来从其他表中获取数据。

输入数据: 2 联盟选择用户名,来自 tbluser 的密码

查询:从 tbl_employee 选择 first_name,从 tbl_employee 选择 last_name 到 empId=2 联盟选择用户名,从 tbluser 选择密码

通过使用基于联盟的 SQL Injection,攻击者可以获取用户凭据。

基于时间的 SQL 注射

Time Based SQL Injection中,特殊函数被注入到查询中,可以暂停执行一段时间. 此攻击会减慢数据库服务器。

输入数据: 2 + 睡眠(5)

查询: select emp_id, first_name, last_name from tbl_employee where empId=2 + SLEEP(5)

在上面的示例中,查询执行将暂停5秒。

基于错误的 SQL 注射

在这种变异中,攻击者试图从数据库中获取错误代码和消息等信息,攻击者注入语法不正确的SQL,以便数据库服务器返回错误代码和消息,可以用来获取数据库和系统信息。

Java SQL 注射示例

我们将使用一个简单的Java Web应用程序(/community/tutorials/java-web-application-tutorial-for-beginners)来演示SQL Injection. 我们有 Login.html,这是一个基本的登录页面,从用户身上拿出用户名和密码,并将其提交到 LoginServlet

LoginServlet从请求中获取用户名和密码,并对数据库值进行验证.如果验证成功,Servlet会将用户重定向到主页,否则将返回错误。

密码:Login.html 密码:

 1<!DOCTYPE html>
 2<html lang="en">
 3    <head>
 4        <title>Sql Injection Demo</title>
 5    </head>
 6    <body>
 7    <form name="frmLogin" method="POST" action="https://localhost:8080/Web1/LoginServlet">
 8        <table>
 9            <tr>
10                <td>Username</td>
11                <td><input type="text" name="username"></td>
12            </tr>
13            <tr>
14                <td>Password</td>
15                <td><input type="password" name="password"></td>
16            </tr>
17            <tr>
18                <td colspan="2"><button type="submit">Login</button></td>
19            </tr>
20        </table>
21    </form>
22    </body>
23</html>

密码:Java 密码:

 1package com.journaldev.examples;
 2import java.io.IOException;
 3import java.sql.*;
 4import javax.servlet.*;
 5import javax.servlet.annotation.WebServlet;
 6import javax.servlet.http.*;
 7
 8@WebServlet("/LoginServlet")
 9public class LoginServlet extends HttpServlet {
10    static {
11        try {
12            Class.forName("com.mysql.jdbc.Driver");
13        } catch (Exception e) {}
14    }
15
16    protected void doPost(HttpServletRequest request, HttpServletResponse response)
17            throws ServletException, IOException {
18        boolean success = false;
19        String username = request.getParameter("username");
20        String password = request.getParameter("password");
21        // Unsafe query which uses string concatenation
22        String query = "select * from tbluser where username='" + username + "' and password = '" + password + "'";
23        Connection conn = null;
24        Statement stmt = null;
25        try {
26            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/user", "root", "root");
27            stmt = conn.createStatement();
28            ResultSet rs = stmt.executeQuery(query);
29            if (rs.next()) {
30                // Login Successful if match is found
31                success = true;
32            }
33        } catch (Exception e) {
34            e.printStackTrace();
35        } finally {
36            try {
37                stmt.close();
38                conn.close();
39            } catch (Exception e) {}
40        }
41        if (success) {
42            response.sendRedirect("home.html");
43        } else {
44            response.sendRedirect("login.html?error=1");
45        }
46    }
47}

数据库查询(MySQL):

1create database user;
2
3create table tbluser(username varchar(32) primary key, password varchar(32));
4
5insert into tbluser (username,password) values ('john','secret');
6insert into tbluser (username,password) values ('mike','pass10');

1. 当从登录页面输入有效的用户名和密码时

用户名:John

用户名:秘密

Query:从 tbluser 中选择用户名='john' 和密码 ='secret'

** 结果**:用户名和密码存在于数据库中,因此验证成功,用户将被重定向到主页。

使用 SQL Injection 获取未经授权的系统访问

用户名:Dummy

输入密码:‘或‘1’=’1

Query:从 tbluser 中选择用户名='dummy' 和密码 = '' 或 '1'='1'

** 结果**:输入的用户名和密码不存在于数据库中,但验证成功。

这是由于SQL Injection,因为我们输入了‘或‘1’=’1作为密码。

  1. username='dummy':它将被评估为虚假,因为在表中没有使用者具有虚假的用户名
  2. 密码 = '':它将被评估为虚假,因为在表中没有空密码
  3. '1'='1':它将被评估为真,因为这是静态比较字符串

现在将所有 3 个条件相结合,即 ** false 和 false 或 true** => 最终结果将是 true**。

在上面的场景中,我们使用了布尔式表达式来执行SQL Injection. 还有其他一些方法来执行SQL Injection. 在下一节中,我们将看到如何在我们的Java应用程序中防止SQL injection。

在 Java 代码中防止 SQL 注入

最简单的解决方案是使用 PreparedStatement而不是 Statement来执行查询。

不是将用户名和密码连接到查询中,而是通过 PreparedStatement 的 setter 方法提供查询。

现在,从请求中获得的用户名和密码值被视为仅有的数据,因此不会发生 SQL 注入。

让我们来看看修改的服务器代码。

 1String query = "select * from tbluser where username=? and password = ?";
 2Connection conn = null;
 3PreparedStatement stmt = null;
 4try {
 5    conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/user", "root", "root");
 6    stmt = conn.prepareStatement(query);
 7    stmt.setString(1, username);
 8    stmt.setString(2, password);
 9    ResultSet rs = stmt.executeQuery();
10    if (rs.next()) {
11        // Login Successful if match is found
12        success = true;
13    }
14    rs.close();
15} catch (Exception e) {
16    e.printStackTrace();
17} finally {
18    try {
19        stmt.close();
20        conn.close();
21    } catch (Exception e) {
22    }
23}

让我们来了解在这种情况下发生了什么。

** Query**: *从 tbluser 中选择 ,用户名 =? 和密码 =?

上面的查询中的问号()被称为定位参数。上面的查询中有2个定位参数。我们不将用户名和密码连接到查询中。

我们通过使用stmt.setString(1,用户名)设置了第一个参数,第二个参数使用stmt.setString(2,密码)

避免 SQL 注射的最佳做法

例如,许多应用程序使用 tbluser 或 tblaccount 来存储用户数据。 电子邮件、 第一名、 最后名称是常用的列名 3. 不要直接连接数据(作为用户输入)来创建 SQL 查询 4. 使用位置参数(如 HibernateSpring Data JPA用于应用程序的数据层 5. 使用位置参数在查询中。 如果您使用的是返回 [JDBC/community/tutorials/hibernate-tutorial](/community/tutorials/jdb-tut

对于Java SQL Injection来说,这就是全部,我希望这里没有什么重要的错过。

您可以从下面的链接下载Java Web Application Project样本。

SQL Injection Java Project

Published At
Categories with 技术
Tagged with
comments powered by Disqus