php创建流和.htaccess文件的妙用

前言

今天遇到一个神奇的题目,虽然成立条件在生产环境中基本不可能实现,但是能扩展扩展思路

大体过程是利用题目自带的Streams流找到并读取flag,Streams流的利用需要用到上传.htaccess文件。

sshop

打开题目链接,是一个简易的商城,

有登陆和注册界面,于是先注册个账号,看到有个空是要填推荐人,也不知道推荐人是谁,先空着

登陆进入个人中心查看,发现有上传头像的地方,但是并不能点进去,因为积分不够。怎么增加积分?当时考虑的

是注入,但是测了半天没发现注入点,后来才想起来有个推荐人这么个东西。

再注册个号,推荐人填第一次注册的号,再登陆第一个号,果然积分多了10分。

积分涨到100后,就可以上传头像了,上传并没有过滤,php文件可以上传,但是上传上去的php文件部分符号被过滤了

这样的php文件就无法执行了

在这里卡了半天,请教后得知,扫描网站,发现有robots.txt文件,打开后指向config.txt,应该是config.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
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
<?php

class master
{
private $path;
private $name;

function __construct()
{

}

function stream_open($path)
{
if(!preg_match('/(.*)\/(.*)$/s',$path,$array,0,9))
return 1;
$a=$array[1];
parse_str($array[2],$array);

if(isset($array['path']))
{
$this->path=$array['path'];
}
else
return 1;
if(isset($array['name']))
{
$this->name=$array['name'];
}
else
return 1;

if($a==='upload')
{
return $this->upload($this->path,$this->name);
}
elseif($a==='search')
{
return $this->search($this->path,$this->name);
}
else
return 1;
}
function upload($path,$name)
{
if(!preg_match('/^uploads\/[a-z]{10}\/$/is',$path)||empty($_FILES[$name]['tmp_name']))
return 1;

$filename=$_FILES[$name]['name'];
echo $filename;

$file=file_get_contents($_FILES[$name]['tmp_name']);

$file=str_replace('<','!',$file);
$file=str_replace(urldecode('%03'),'!',$file);
$file=str_replace('"','!',$file);
$file=str_replace("'",'!',$file);
$file=str_replace('.','!',$file);
if(preg_match('/file:|http|pre|etc/is',$file))
{
echo 'illegalbbbbbb!';
return 1;
}

file_put_contents($path.$filename,$file);
file_put_contents($path.'user.jpg',$file);


echo 'upload success!';
return 1;
}
function search($path,$name)
{
if(!is_dir($path))
{
echo 'illegal!';
return 1;
}
$files=scandir($path);
echo '</br>';
foreach($files as $k=>$v)
{
if(str_ireplace($name,'',$v)!==$v)
{
echo $v.'</br>';
}
}

return 1;
}

function stream_eof()
{
return true;
}
function stream_read()
{
return '';
}
function stream_stat()
{
return '';
}

}

stream_wrapper_unregister('php');
stream_wrapper_unregister('phar');
stream_wrapper_unregister('zip');
stream_wrapper_register('master','master');

?>

可以看到,这个文件创建了一个php流

stream_wrapper_register — 注册一个用 PHP 类实现的 URL 封装协议

bool stream_wrapper_register ( string $protocol , string $classname [, int $flags = 0 ] )

允许用户实现自定义的协议处理器和流,用于所有其它的文件系统函数中(例如 fopen()fread() 等)。

参数

  • protocol

    待注册的封装的名字。

  • classname

    实现了protocol的类名。

  • flags

    Should be set to STREAM_IS_URL if protocol is a URL protocol. Default is 0, local stream.

也就是说,创建后的协议处理器和流有跟php自带协议处理器和流相似的用法

例如

1
include 'master://upload/path='.$userpath.'&name=filename';

这条语句就相当于调用了master类中的upload方法,并赋予参数path和name

那么,可以看到,config.php还定义了一个search方法,这个方法可以扫描目录,获取文件名

也就是说,用这条语句我们就可以不触发过滤规则的情况下扫描目录

但是,如何才能要这条语句生效呢?

由于文件上传没有限制,我们可以上传.htaccess文件,利用.htaccess文件添加规则,在当前目录下的每个php文件后都包含一个文件(语句?)

.htaccess文件内容如下:

  • 注意path里路径的斜杠要URL编码

php_value auto_append_file master://search/path=路径&name=flag

php_value即修改php文件配置(与修改php.ini作用效果相同,范围不同)

auto_prepend_file 在页面顶部加载文件

auto_append_file 在页面底部加载文件

加上auto_prepend_fileauto_append_file语句的目录就相当于自动在php文件前/后加上了

<?php require 文件 ?>

这样,我们只需要上传一个内容为

php_value auto_append_file master://search/path=%2f路径%2f&name=flag

的.htaccess文件,再任意上传一个php文件,这样访问php文件的时候就能看到目录的扫描结果

1565683707069

扫描到flag文件后,再上传一个内容为

php_value auto_append_file /home/hiahiahia_flag

的.htaccess文件,即可读取到flag