PHP代码审计分段讲解(2)

03 多重加密

源代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
    include 'common.php';
    $requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);
    //把一个或多个数组合并为一个数组
    class db
    {
        public $where;
        function __wakeup()
        {
            if(!empty($this->where))
            {
                $this->select($this->where);
            }
        }
        function select($where)
        {
            $sql = mysql_query('select * from user where '.$where);
            //函数执行一条 MySQL 查询。
            return @mysql_fetch_array($sql);
            //从结果集中取得一行作为关联数组,或数字数组,或二者兼有返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
        }
    }
 
    if(isset($requset['token']))
    //测试变量是否已经配置。若变量已存在则返回 true 值。其它情形返回 false 值。
    {
        $login = unserialize(gzuncompress(base64_decode($requset['token'])));
        //gzuncompress:进行字符串压缩
        //unserialize: 将已序列化的字符串还原回 PHP 的值
 
        $db = new db();
        $row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\'');
        //mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。
 
        if($login['user'] === 'ichunqiu')
        {
            echo $flag;
        }else if($row['pass'] !== $login['pass']){
            echo 'unserialize injection!!';
        }else{
            echo "(╯‵□′)╯︵┴─┴ ";
        }
    }else{
        header('Location: index.php?error=1');
    }
 
?>

这道题目直接部署的话会有一些配置问题,根据报错修改了配置文件之后,在同目录文件夹下创建了common.php,设置$flag=123456

可以看到获取flag的操作为:

1
2
3
4
5
6
7
8
if($login['user'] === 'ichunqiu')
        {
            echo $flag;
        }else if($row['pass'] !== $login['pass']){
            echo 'unserialize injection!!';
        }else{
            echo "(╯‵□′)╯︵┴─┴ ";
        }

当$login['user']==='ichunqiu'的时候,就输出flag,继续往上看$login的赋值位置

1
$login = unserialize(gzuncompress(base64_decode($requset['token'])));

进行了base64解密,gzuncompress字符串压缩和字符串反序列化。

而$requset的赋值在:

1
$requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);

将从GET,POST或者COOKIE中获取到的值合并到request里面

为了满足:

1
$login['user'] === 'ichunqiu'

我们需要令

1
$token=array(['user']==='ichunqiu');

然后再对其进行相应的加密,最后的token为:

1
2
3
$token=array(['user']==='ichunqiu');
$token=base64_encode(gzcompress(serialize($token)));
echo $token;

得到payload为:

1
eJxLtDK0qs60MrBOAuJaAB5uBBQ=

最后本地输出还是有问题,就写写解题思路趴

 

04 SQL注入_WITH ROLLUP绕过

源代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?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);
 
//设置活动的 MySQL 数据库
 
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
 
//执行一条 MySQL 查询
 
if (mysql_num_rows($query) == 1) {
 
//返回结果集中行的数目
 
    $key = mysql_fetch_array($query);
 
//返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
 
    if($key['pwd'] == $_POST['pwd']) {
        print "CTF{XXXXXX}";
    }else{
        print "亦可赛艇!";
    }
}else{
    print "一颗赛艇!";
}
mysql_close($con);
?>

本地环境的原因,只能直接分析代码了。

首先是登录框POST输入用户名和密码

1
2
3
4
5
6
7
8
9
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;
}

然后对POST的值使用AttackFilter函数进行过滤

1
2
3
4
5
6
7
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){
 
//遍历数组
 
    AttackFilter($key,$value,$filter);
}

AttackFilter函数为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function AttackFilter($StrKey,$StrValue,$ArrReq){ 
    if (is_array($StrValue)){
 
//检测变量是否是数组
 
        $StrValue=implode($StrValue);
 
//返回由数组元素组合成的字符串
 
    }
    if (preg_match("/".$ArrReq."/is",$StrValue)==1){  
 
//匹配成功一次后就会停止匹配
 
        print "水可载舟,亦可赛艇!";
        exit();
    }
}

匹配到了黑名单中的元素时就会退出

1
2
3
4
5
6
$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
    die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);

这里是链接本地数据库的操作,接着查询输入的uname的相关数据

1
2
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);

返回结果集中行的数目为1,才能进入if,也就是说interest表中不止一行,然后将值赋给$key

1
2
3
4
5
if (mysql_num_rows($query) == 1) {
 
//返回结果集中行的数目
 
    $key = mysql_fetch_array($query);

如果输入的密码和数据库中的密码是相同的就输出flag,否则输出提示信息

1
2
3
4
5
6
7
8
9
10
    if($key['pwd'] == $_POST['pwd']) {
        print "CTF{XXXXXX}";
    }else{
        print "亦可赛艇!";
    }
}else{
    print "一颗赛艇!";
}
mysql_close($con);
?>

这里使用到的绕过技巧是GROUP BY WITH ROLLUP

关于函数的介绍可以看这个:

https://blog.csdn.net/id19870510/article/details/6254358

分组后会多一行进行统计,而多出的一行的pwd会是NULL!而user会是数据库表中已存在的字段。

因为存在限制

1
mysql_num_rows($query) == 1

所以我们使用

1
2
3
limit m offset n
m: 展示m条
n: 跳过n条

来筛选出每一条数据,直到筛选出为user存在,pwd为空的那一行

最终的payload为:

1
1' or 1 group by pwd with rollup limit 1 offset 2#

而密码栏不需要输入,不输入则为NULL,在该位置:

1
2
if($key['pwd'] == $_POST['pwd']) {
        print "CTF{XXXXXX}";

即可满足条件输出flag

 

参考链接:

http://www.bubuko.com/infodetail-2169730.html

https://blog.csdn.net/qq_35078631/article/details/54772798

https://blog.csdn.net/id19870510/article/details/6254358

 

 


__EOF__

本文作者春告鳥
本文链接https://www.cnblogs.com/Cl0ud/p/13229966.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   春告鳥  阅读(352)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示