java 防止sql注入
什么是SQL注入? (What is SQL Injection?)
SQL Injection is one of the top 10 web application vulnerabilities. In simple words, SQL Injection means injecting/inserting SQL code in a query via user-inputted data. It can occur in any applications using relational databases like Oracle, MySQL, PostgreSQL and SQL Server.
SQL注入是十大Web应用程序漏洞之一。 简而言之,SQL注入意味着通过用户输入的数据在查询中注入/插入SQL代码。 它可以在使用关系数据库的任何应用程序中发生,例如Oracle,MySQL,PostgreSQL和SQL Server。
To perform SQL Injection, a malicious user first tries to find a place in the application where he can embed SQL code along with data. It can be the login page of any web application or any other place. So when data embedded with SQL code is received by the application, SQL code will be executed along with the application query.
为了执行SQL注入,恶意用户首先尝试在应用程序中找到一个可以将SQL代码与数据一起嵌入的位置。 它可以是任何Web应用程序或任何其他地方的登录页面 。 因此,当应用程序接收到嵌入有SQL代码的数据时,SQL代码将与应用程序查询一起执行。
SQL注入的影响 (Impact of SQL Injection)
- A malicious user can obtain unauthorized access to your application and steal data. 恶意用户可以未经授权访问您的应用程序并窃取数据。
- They can alter, delete data in your database and take your application down. 他们可以更改,删除数据库中的数据并关闭您的应用程序。
- A hacker can also get control of the system on which database server is running by executing database specific system commands. 黑客还可以通过执行数据库特定的系统命令来控制运行数据库服务器的系统。
SQL注入如何工作? (How Does SQL Injection Works?)
Suppose we have a database table named tbluser which stores data of application users. The userId is the primary column of the table. We have functionality in the application, which lets you get information via userId. The value of userId is received from the user request.
假设我们有一个名为tbluser的数据库表,用于存储应用程序用户的数据。 userId是表的主列。 我们在应用程序中具有功能,使您可以通过userId获取信息。 从用户请求中接收到userId的值。
Let’s have a look at the below example code.
让我们看下面的示例代码。
String userId = {get data from end user};
String sqlQuery = "select * from tbluser where userId = " + userId;
1.有效的用户输入 (1. Valid User Input)
When the above query is executed with valid data i.e. userId value 132, it will look like below.
当使用有效数据(即userId值132)执行上述查询时,它将如下所示。
Input Data: 132
输入数据: 132
Executed Query: select * from tbluser where userId=132
执行的查询: 从tbluser中选择*,其中userId = 132
Result: Query will return data of user having userId 132. No SQL Injection is happening in this case.
结果:查询将返回具有userId 132的用户的数据。在这种情况下,没有SQL注入发生。
2.黑客用户输入 (2. Hacker User Input)
A hacker can alter user requests using tools like Postman, cURL, etc. to send SQL code as data and this way bypassing any UI side validations.
黑客可以使用Postman,cURL等工具更改用户请求,以将SQL代码作为数据发送,并以此方式绕过任何UI端验证。
Input Data: 2 or 1=1
输入数据: 2或1 = 1
Executed Query: select * from tbluser where userId=2 or 1=1
执行的查询: 从tbluser中选择*,其中userId = 2或1 = 1
Result: Now the above query is having two conditions with SQL OR expression.
结果:现在上面的查询具有两个带有SQL OR表达式的条件。
- userId=2: This part will match table rows having userId value as ‘2’. userId = 2 :这部分将匹配userId值为'2'的表行。
- 1=1: This part will be always evaluate as true. So Query will return all the rows of the table. 1 = 1 :此部分将始终被评估为true。 因此查询将返回表的所有行。
SQL注入的类型 (Types of SQL Injection)
Let’s look at the four types of SQL injections.
让我们看一下SQL注入的四种类型。
1.基于布尔SQL注入 (1. Boolean Based SQL Injection)
The above example is a case of Boolean Based SQL Injection. It uses a boolean expression that evaluates to true or false. It can be used to get additional information from the database. For example;
上面的示例是基于布尔值SQL注入的情况。 它使用布尔表达式,其结果为true或false。 它可以用来从数据库中获取其他信息。 例如;
Input Data: 2 or 1=1
输入数据:2或1 = 1
SQL Query: select first_name, last_name from tbl_employee where empId=2 or 1=1
SQL查询:从tbl_employee中选择first_name,last_name,其中empId = 2或1 = 1
2. 基于联合SQL注入 (2. Union Based SQL Injection)
SQL union operator combines data from two different queries with the same number of columns. In this case, the union operator is used to get data from other tables.
SQL联合运算符将来自两个不同查询的数据与相同数量的列组合在一起。 在这种情况下,并集运算符用于从其他表获取数据。
Input Data: 2 union select username, password from tbluser
输入数据: 2位联合选择用户名,tbluser的密码
Query: Select first_name, last_name from tbl_employee where empId=2 union select username, password from tbluser
查询:从tbl_employee中选择first_name,last_name,其中empId = 2并选择tbluser的用户名和密码
By using Union Based SQL Injection, an attacker can obtain user credentials.
通过使用基于联合SQL注入,攻击者可以获得用户凭据。
3. 基于时间SQL注入 (3. Time-Based SQL Injection)
In Time Based SQL Injection, special functions are injected in the query which can pause execution for a specified amount of time. This attack slows down the database server. It can bring down your application by affecting the database server performance. For example, In MySQL:
在基于时间SQL注入中 ,特殊函数被注入查询中,这些函数可以在指定的时间内暂停执行。 这种攻击会降低数据库服务器的速度。 它可以通过影响数据库服务器性能来降低应用程序的性能。 例如,在MySQL中:
Input Data: 2 + SLEEP(5)
输入数据: 2 + SLEEP(5)
Query: select emp_id, first_name, last_name from tbl_employee where empId=2 + SLEEP(5)
查询: 从tbl_employee中选择emp_id,first_name,last_name,其中empId = 2 + SLEEP(5)
In the above example, query execution will pause for 5 seconds.
在上面的示例中,查询执行将暂停5秒钟。
4. 基于错误SQL注入 (4. Error Based SQL Injection)
In this variation, the attacker tries to get information like an error code and a message from the database. The attacker injects SQL which are syntactically incorrect so database server will return error code and messages which can be used to get database and system information.
在这种变体中,攻击者试图从数据库中获取信息,例如错误代码和消息。 攻击者注入SQL在语法上是不正确的,因此数据库服务器将返回错误代码和消息,这些错误代码和消息可用于获取数据库和系统信息。
Java SQL注入示例 (Java SQL Injection Example)
We will use a simple Java Web application to demonstrate SQL Injection. We have Login.html, which is a basic login page that takes username and password from the user and submit them to LoginServlet.
我们将使用一个简单的Java Web应用程序来演示SQL注入。 我们有Login.html ,这是一个基本的登录页面,该页面从用户那里获取用户名和密码,并将其提交给LoginServlet 。
The LoginServlet gets username and password from request and validates them against database values. If authentication is successful then Servlet redirects the user to the home page otherwise it will return an error.
LoginServlet从请求中获取用户名和密码,并根据数据库值对其进行验证。 如果身份验证成功,则Servlet会将用户重定向到主页,否则它将返回错误。
Login.html Code:
Login.html 代码 :
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sql Injection Demo</title>
</head>
<body>
<form name="frmLogin" method="POST" action="https://localhost:8080/Web1/LoginServlet">
<table>
<tr>
<td>Username</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>Password</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2"><button type="submit">Login</button></td>
</tr>
</table>
</form>
</body>
</html>
LoginServlet.java Code:
LoginServlet.java 代码 :
package com.journaldev.examples;
import java.io.IOException;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (Exception e) {}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
boolean success = false;
String username = request.getParameter("username");
String password = request.getParameter("password");
// Unsafe query which uses string concatenation
String query = "select * from tbluser where username='" + username + "' and password = '" + password + "'";
Connection conn = null;
Statement stmt = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/user", "root", "root");
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query);
if (rs.next()) {
// Login Successful if match is found
success = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
stmt.close();
conn.close();
} catch (Exception e) {}
}
if (success) {
response.sendRedirect("home.html");
} else {
response.sendRedirect("login.html?error=1");
}
}
}
Database Queries [MySQL]:
数据库查询[MySQL]:
create database user;
create table tbluser(username varchar(32) primary key, password varchar(32));
insert into tbluser (username,password) values ('john','secret');
insert into tbluser (username,password) values ('mike','pass10');
1.从登录页面输入有效用户名和密码时 (1. When Valid Username and Password is entered from the login page)
Input username: john
输入用户名 :john
Input username: secret
输入用户名 :秘密
Query: select * from tbluser where username=’john’ and password = ‘secret’
查询 :从tbluser中选择*,其中username ='john'和password ='secret'
Result: Username and Password exists in the database so authentication is successful. The user will be redirected to the home page.
结果 :数据库中存在用户名和密码,因此身份验证成功。 用户将被重定向到主页。
2.使用SQL Injection获得未经授权的系统访问 ()
Input username: dummy
输入用户名 :哑
Input password: ‘ or ‘1’=’1
输入密码 :'或'1'='1
Query: select * from tbluser where username=’dummy’ and password = ” or ‘1’=’1′
查询 :从tbluser中选择*,其中username ='dummy'和password =”或'1'='1'
Result: Inputted Username and Password doesn’t exist in the database but authentication is successful. Why?
结果 :输入的用户名和密码在数据库中不存在,但认证成功。 为什么?
It is due to SQL Injection as we have entered ‘ or ‘1’=’1 as password. There are 3 conditions in the query.
这是由于SQL注入,因为我们输入了'或'1'='1作为密码。 查询中有3个条件。
- username=’dummy’: It will be evaluated to false as there is no user with username dummy in the table. username ='dummy' :由于表中没有用户名是dummy的用户,它将被评估为false。
- password = ”: It will be evaluated to false as there is no empty password in the table. password =” :由于表中没有空密码,它将被评估为false。
- ‘1’=’1′: It will be evaluated to true as this is static string comparison. '1'='1' :因为这是静态字符串比较,它将被评估为true。
Now combining all 3 conditions i.e false and false or true => Final result will be true.
现在结合所有三个条件,即false和false或true =>最终结果将为true 。
In the above scenario, we have used the boolean expression to perform SQL Injection. There are some other ways to do SQL Injection. In the next section, we will see ways to prevent SQL injection in our Java application.
在上述情况下,我们使用了布尔表达式来执行SQL注入。 还有其他一些方法可以执行SQL注入。 在下一节中,我们将介绍在Java应用程序中防止SQL注入的方法。
防止Java代码中SQL注入 (Preventing SQL Injection in Java Code)
The simplest solution is to use PreparedStatement instead of Statement to execute the query.
最简单的解决方案是使用PreparedStatement而不是Statement来执行查询。
Instead of concatenating username and password into the query, we provide them to query via PreparedStatement’s setter methods.
我们没有将用户名和密码串联到查询中,而是提供它们通过PreparedStatement的setter方法进行查询。
Now, the value of username and password received from the request is treated as only data so no SQL Injection will happen.
现在,从请求接收的用户名和密码的值仅被视为数据,因此不会发生SQL注入。
Let’s look at the modified servlet code.
让我们看一下修改后的servlet代码。
String query = "select * from tbluser where username=? and password = ?";
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/user", "root", "root");
stmt = conn.prepareStatement(query);
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
// Login Successful if match is found
success = true;
}
rs.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
stmt.close();
conn.close();
} catch (Exception e) {
}
}
Let’s understand what’s happening in this case.
让我们了解在这种情况下发生了什么。
Query: select * from tbluser where username = ? and password = ?
查询 : 从tbluser中选择*,其中username =? 和密码=?
The question mark (?) in the above query is called a positional parameter. There are 2 positional parameters in the above query. We don’t concatenate username and password to query. We use methods available in the PreparedStatement to provide user Input.
以上查询中的问号(?)称为位置参数。 上述查询中有2个位置参数。 我们不会将用户名和密码连接在一起进行查询。 我们使用PreparedStatement中可用的方法来提供用户输入。
We have set the first Parameter by using stmt.setString(1, username)
and the second parameter by using stmt.setString(2, password)
. The underlying JDBC API takes care of sanitizing the values to avoid SQL injection.
我们已经使用stmt.setString(1, username)
设置了第一个参数,并使用了stmt.setString(2, password)
了第二个参数。 底层的JDBC API负责清理值以避免SQL注入。
避免SQL注入的最佳实践 (Best Practices to avoid SQL Injection)
- Validate data before using them in the query. 在查询中使用数据之前,请先对其进行验证。
- Do not use common words as your table name or column name. For example, many applications use tbluser or tblaccount to store user data. Email, firstname, lastname are common column names. 不要使用常用词作为表名或列名。 例如,许多应用程序使用tbluser或tblaccount来存储用户数据。 电子邮件,名,姓是常用的列名。
- Do not directly concatenate data ( received as user input) to create SQL queries. 不要直接串联数据(作为用户输入接收)来创建SQL查询。
- Use frameworks like Hibernate and Spring Data JPA for the data layer of an application. 将Hibernate和Spring Data JPA之类的框架用于应用程序的数据层。
- Use positional parameters in the query. If you are using plain JDBC, then use PreparedStatement to execute the query. 在查询中使用位置参数。 如果您使用的是普通JDBC ,则使用PreparedStatement执行查询。
- Limit the application’s access to the database via permissions & grants. 通过权限和授予限制应用程序对数据库的访问。
- Do not return sensitive error code and message to the end-user. 不要将敏感的错误代码和消息返回给最终用户。
- Do proper code review so that no developer accidentally write unsafe SQL code. 请进行正确的代码审查,以免开发人员意外地编写不安全SQL代码。
- Use tools like SQLMap to find and fix SQL Injection vulnerabilities in your application. 使用SQLMap之类的工具来查找和修复应用程序中SQL Injection漏洞。
That’s all for Java SQL Injection, I hope nothing important got missed here.
Java SQL注入就这些了,我希望这里没有重要的事情。
You can download the sample java web application project from the below link.
您可以从下面的链接下载示例Java Web应用程序项目。
java 防止sql注入