1.技术方案2.识别PHPTAG代码区域3.MD5HASH匹配4.一句话WEBSHELL匹配5.字符串+正则规则匹配6.SSDEEP模糊化HASH匹配大马7.PH7(AnEmbeddedImplementationofPHP(CLibrary))8.基于危险函数Hook的恶意污点数据追踪:WEBSHELL检测9.待解决的问题
1.技术方案
1.抽取出PHPtag区域1)HTML文件中插入PHP代码2)JPG、PNG中插入PHP代码2.MD5恶意HASH库匹配3.如果sizeof(File)<80bytesthen直接进行一句话WEBSHELL正则匹配4.采取字符串+正则规则对文件进行强特征匹配(只抽取强特征)5.为了保证SSDEEP的检测效果,只有当待检测文件大于1024byte,则进行SSDEEP模糊化HASH匹配大马:检测相似度是否>判定阈值6.基于PH7(一个嵌入式PHP编译、执行引擎),在敏感函数、流程中Hook,检测是否包含外部传入的参数7.对$_SERVER、$_POST等超全局变量进行污点打标,在例如eval函数中,如果检测到当前传入的参数存在污点标记,则认定敏感函数执行了外部传入的参数,当前为一条危险路径//其中每一步都是在此前的所有步骤都检测失败(即判定为白)的前提下,才继续接下来的检测
2.识别PHPTAG代码区域
0x1:需要处理的情况
1.一个文件中包含有多个
voidextraPhpTag(string&phpTagBuf,string&inFileBuf,stringfeaturestr_start="
3.MD5HASH匹配
和传统的AV杀毒一样,使用MD5HASH精确匹配会面临免杀、绕过的风险,但是在大规模集群环境下,基于大数据得出的AVHASH库就会发挥出相对较好的效果
0x1:可持续化运维方式
1.将Agent、Server的MD5恶意HASH库单独抽离出来2.人工确认是WEBSHELL后,通过自动化方式自动同步到Agent本地、Server上的MD5HASH库(文件)中
4.一句话WEBSHELL匹配
在实际的入侵攻防中我们会发现,由于批量工具以及攻击payload常常呈现变形程度梯度上升的趋势,简单形式的一句话占比较高,即
1.ifcount(file)<80bytes2.thenpattern:(eval|execute|assert)[^>]*(request|post|get|cookie|\\$_)//对小文件直接进行一句话正则匹配0x1:CodeExample
//simpleonesentencewebshell(like:eval($_POST['op']))intphpTagBufLength=phpTagBufMd5.length();//regularexpressionstd::regexpattern("(eval|execute|assert)[^>]*(request|post|get|cookie|\\$_)");//sameasstd::match_results 5.字符串+正则规则匹配 0x1:Rule //webshellcharacteristicsrulecheckintEvilWebshellRuleSize=(sizeof(EvilWebshellRule)/sizeof(EvilWebshellRule[0]));for(inti=0;i 6.SSDEEP模糊化HASH匹配大马 对于大马来说,使用特征字符串、ssdeep模糊化hash进行聚类分析,能得到较好的效果,并同时得到当前待检测样本的恶意webshell家族分类 0x1:匹配方案 1.对已知的大马样本进行SSDEEP预处理计算,得到不同分类的SSDEEPHASH,作为"黑库"2.将不同的SSDEEPHASH进行"病毒家族库"分类,分别标记为不同大马3.在进行模糊化Hash匹配前对文件大小进行判断,只有当文件字节数>4096字节时,才进行SSDEEP检测,这么做的目的是为了让一句话变形小马尽可能在词法分析环节被检测,而让大马尽可能在SSDEEP环节被检测4.将待检测样本和SSDEEPHASH黑库进行"逐一"SSDEEP计算,得到样本也黑库的"置信度数组"5.从置信度数组中挑选出置信度>85,且得分最高的聚类点,则对应聚类点的标签就是当前检测样本的"病毒种类"0x2:CodeExample #include"fuzzy.h"..staticstd::map 7.PH7(AnEmbeddedImplementationofPHP(CLibrary)) PH7isain-processsoftwareClibrarywhichimplementsahighly-efficientembeddablebytecodecompilerandavirtualmachineforthePHPprogramminglanguage.Inotherwords,PH7isaPHPenginewhichallowthehostapplicationtocompileandexecutePHPscriptsin-processPH7是一个进程中的开发包(SDK),实现了高效的嵌入式的字节码编译器和一个PHP编程语言的虚拟机。换一种说法:PH7是一个轻量级的PHP引擎,可让你的C/C++应用程序直接编译并执行PHP脚本,需要注意的是,PH7并不是一个词法/语法优化器,它并不能对脚本文件进行预处理(拼接、参数传递回溯、函数调研回溯),而是直接进行了编译中间代码,并模拟动态执行PH7implementsmostoftheconstructsintroducedbythePHP5.3releasesuchasheredoc,nowdoc,gotos,classes,anonymousfunctions,closuresandsoonandintroducesverypowerfulextensionstothePHPprogramminglanguagesuchas: 0x1:TestExampleCode:HowToUsePH7 0x4:dynamicexecutepreparerc=PH7_VmMakeReady(pVm); ph7_vm_exec(pVm,0) /**[CAPIREF:ph7_vm_exec()]*Pleaserefertotheofficialdocumentationforfunctionpurposeandexpectedparameters.*/intph7_vm_exec(ph7_vm*pVm,int*pExitStatus){intrc;/*Ticket1433-002:NULLVMisharmlessoperation*/if(PH7_VM_MISUSE(pVm)){returnPH7_CORRUPT;}#ifdefined(PH7_ENABLE_THREADS)/*AcquireVMmutex*/SyMutexEnter(sMPGlobal.pMutexMethods,pVm->pMutex);/*NO-OPifsMPGlobal.nThreadingLevel!=PH7_THREAD_LEVEL_MULTI*/if(sMPGlobal.nThreadingLevel>PH7_THREAD_LEVEL_SINGLE&&PH7_THRD_VM_RELEASE(pVm)){returnPH7_ABORT;/*Anotherthreadhavereleasedthisinstance*/}#endif/*ExecutePH7byte-code*/rc=PH7_VmByteCodeExec(&(*pVm));if(pExitStatus){/*Exitstatus*/*pExitStatus=pVm->iExitStatus;}#ifdefined(PH7_ENABLE_THREADS)/*LeaveVMmutex*/SyMutexLeave(sMPGlobal.pMutexMethods,pVm->pMutex);/*NO-OPifsMPGlobal.nThreadingLevel!=PH7_THREAD_LEVEL_MULTI*/#endif/*Executionresult*/returnrc;}rc=PH7_VmByteCodeExec(&(*pVm)); /**ExecuteasmuchofaPH7bytecodeprogramaswecanthenreturn.*Thisfunctionisawrapperaround[VmByteCodeExec()].*Seeblock-commentonthatfunctionforadditionalinformation.*/PH7_PRIVATEsxi32PH7_VmByteCodeExec(ph7_vm*pVm){/*Makesurewearereadytoexecutethisprogram*/if(pVm->nMagic!=PH7_VM_RUN){returnpVm->nMagic==PH7_VM_EXECSXERR_LOCKED/*LockedVM*/:SXERR_CORRUPT;/*StaleVM*/}/*Settheexecutionmagicnumber*/pVm->nMagic=PH7_VM_EXEC;/*Executetheprogram*/VmByteCodeExec(&(*pVm),(VmInstr*)SySetBasePtr(pVm->pByteContainer),pVm->aOps,-1,&pVm->sExec,0,FALSE);/*Invokeanyshutdowncallbacks*/VmInvokeShutdownCallbacks(&(*pVm));/**TICKET1433-100:DonotremovethePH7_VM_EXECmagicnumber*sothatanyfollowingcallto[ph7_vm_exec()]withoutcalling*[ph7_vm_reset()]firstwouldfail.*/returnSXRET_OK;}VmByteCodeExec(&(*pVm),(VmInstr*)SySetBasePtr(pVm->pByteContainer),pVm->aOps,-1,&pVm->sExec,0,FALSE);PHPOpcode是一种类似于汇编的中间语言,每个语句块都由多个"状态"成员组成(汇编特征),PH7根据这些状态进行相应的"跳转",即动态执行opcode /**ExecuteasmuchofaPH7bytecodeprogramaswecanthenreturn.**[PH7_VmMakeReady()]mustbecalledbeforethisroutineinorderto*closetheprogramwithafinalOP_DONEandtosetupthedefault*consumerroutinesandotherstuff.Refertotheimplementation*of[PH7_VmMakeReady()]foradditionalinformation.*IftheinstalledVMoutputconsumercallbackeverreturnsPH7_ABORT*thentheprogramexecutionishalted.*Afterthisroutinehasfinished,[PH7_VmRelease()]or[PH7_VmReset()]*shouldbeusedrespectivelytocleanupthemessthatwasleftbehind*ortoresettheVMtoit'sinitialstate.*/staticsxi32VmByteCodeExec(ph7_vm*pVm,/*TargetVM*/VmInstr*aInstr,/*PH7bytecodeprogram*/ph7_value*pStack,/*Operandstack*/intnTos,/*Topentryintheoperandstack(usually-1)*/ph7_value*pResult,/*Storeprogramreturnvaluehere.NULLotherwise*/sxu32*pLastRef,/*Lastreferencedph7_valueindex*/intis_callback/*TRUEifweareexecutingacallback*/){RelevantLink: 8.基于危险函数Hook的恶意污点数据追踪:WEBSHELL检测 1.vm_builtin_eval()API函数中,传入了外部参数($_POST、$_GET..等超全局变量)2."assert","system","exec","passthru","shell_exec","proc_open"这些危险内建函数,传入了外部参数($_POST、$_GET..等超全局变量)3.通过create_function()创建,然后通过call_user_func这类回调(callback)调用的函数中,传入了外部参数($_POST、$_GET..等超全局变量)4.preg_replace/e函数调用,传入了传入了外部参数($_POST、$_GET..等超全局变量)或恶意payload需要注意的是,PHP、Zend和PH7对大小写敏感问题的处理存在差异 0x1:Hook方案 PH7_PRIVATEsxi32PH7_HashmapCreateSuper(ph7_vm*pVm)这里负责创建$_POST、$_GET等全局变量并插入到$_GLOBAL超全局数组中,我们需要在初始化的同时,往$_POST、$_GET等全局变量中插入魔法键值 0x3:数组元素取值污点(伪造数据)标记 0x4:eval函数污点分析 从webshell变形执行的本质来看,instructionseval($_Payload)是它的本质形态,在大多数情况下,webshell都需要从外部变量($_POST、$_GET..)中获取指定的键值,即获取Payload或,通过指令管道得以执行,不管eval中的payload经过了怎样的变形,在vm_builtin_eval函数中看到的永远都是最后的原始形态,这也是动态沙箱检测相比于静态特征检测最大的优势基于这种理论,我们对vm_builtin_eval敏感函数进行参数检测,如果在其中找到了魔法数键值,说明当前变量是外部传入的变量,则判定为恶意行为 0x5:需要解决的变形场景 除了最基本的在eval函数中进行污点标记分析,PHP中还有例如动态执行、preg_replace/e、callback等方式可以作为WEBSHELL构造方式执行代码,接下来逐case分析 1.已识别 1.assert:boolassert(mixed$assertion[,string$description])2.system:stringsystem(string$command[,int&$return_var])3.exec:stringexec(string$command[,array&$output[,int&$return_var]])4.passthru:voidpassthru(string$command[,int&$return_var])5.shell_exec:stringshell_exec(string$cmd)6.proc_open:resourceproc_open(string$cmd,array$descriptorspec,array&$pipes[,string$cwd[,array$env[,array$other_options]]])7.popen:resourcepopen(string$command,string$mode)1.assert assert是一个断言函数,它同时具有代码执行的能力,对于assert这个函数,我们要分情况讨论 1.assert("eval($_POST[1])");assert里面直接进行eval,在PH7内核中,最终还是要调用到vm_builtin_eval,我们依然可以在vm_builtin_eval中进行污点分析2.assert("$_GET[op]");assert里面运行外部传入的参数,这种情况下,PH7在进行compile的时候,已经完成了$_GET['op']的取值过程,即assert最终获取到的参数字符串依然还会是污点打标字符串,我们同样可以进行污点分析3.assert("普通命令payload")这种情况我们判定为程序正常的行为,或者判定为非恶意的WEBSHELL,予以放行code /**boolassert(mixed$assertion)*ChecksifassertionisFALSE.*Parameter*$assertion*Theassertiontotest.*Return*FALSEiftheassertionisfalse,TRUEotherwise.*/staticintvm_builtin_assert(ph7_context*pCtx,intnArg,ph7_value**apArg){ph7_vm*pVm=pCtx->pVm;ph7_value*pAssert;intiFlags,iResult;if(nArg<1){/*Missingarguments,returnFALSE*/ph7_result_bool(pCtx,0);returnPH7_OK;}iFlags=pVm->iAssertFlags;if(iFlags&PH7_ASSERT_DISABLE){/*Assertionisdisabled,returnFALSE*/ph7_result_bool(pCtx,0);returnPH7_OK;}pAssert=apArg[0];if(ph7_value_is_string(pAssert)){if(strncmp((char*)pAssert->sBlob.pBlob,shelldet_globals_VALUE,pAssert->sBlob.nByte)==0){std::wcout<<"webshelldetected!"< 这些命令执行函数的利用只有两种方式 1.运行外部传入的参数,这种情况下,PH7在进行compile的时候,已经完成了$_GET['op']、$_POST['op']的取值过程,即命令执行函数最终获取到的参数字符串依然还会是污点打标字符串,我们同样可以进行污点分析3.运行"普通命令payload这种情况我们判定为程序正常的行为,或者判定为非恶意的WEBSHELL,予以放行0x6:动态函数执行污点分析 PHP的动态函数执行属于极其非常规的编码方式,一旦出现,则可以认为是高危WEBSHELL行为,在WEBSHELL变形中,动态函数执行有以下几种 1.执行函数和参数都从外部传入 //dirtyflagcheckvoidshelldet_check(ph7_value*pAssert){if(ph7_value_is_string(pAssert)){if(strncmp((char*)pAssert->sBlob.pBlob,shelldet_globals_VALUE,pAssert->sBlob.nByte)==0){std::wcout<<"webshelldetected!"< WEBSHELL的一种变形方式是使用外部输入文件流作为Payload的输入,即俗称的LFI漏洞,当include的参数来自外部参数,则判定为恶意,需要Hook的点包括 1.require2.require_once3.include4.include_oncecode 0x8:系统输出缓存污点分析 ob_start()会把自己接收到的字符串当作一个"回调函数callback_func",并将接下来的缓冲区输入,当作这个"回调函数"的参数对于ob_start()、ob_end_flush()的污点分析,需要分几种情况讨论 0x9:通过eval注册的匿名动态函数污点分析 这种情况比较特殊,见下面的例子 1.eval执行,注册了:'functionlambda_n(){eval($_POST[1]);}'这个函数2.lambda_n()匿名函数执行,执行lambda_n()内部的逻辑eval($_POST[1]);3.eval($_POST[1]);开始执行在这个过程中,lambda匿名函数通过eval注册的过程中,如果传入的函数逻辑是:eval($_POST[1]);,PH7会对$_POST进行污点标记,从而使WEBSHELL暴露出污点特征 //dirtyflagcheckvoidshelldet_check(ph7_value*pAssert){if(ph7_value_is_string(pAssert)){std::stringsAssert=(char*)pAssert->sBlob.pBlob;sAssert=sAssert.substr(0,pAssert->sBlob.nByte);//if(strncmp((char*)pAssert->sBlob.pBlob,shelldet_globals_VALUE,pAssert->sBlob.nByte)==0)if(sAssert.find(shelldet_globals_VALUE)!=std::string::npos){std::wcout<<"webshelldetected!"< 0x11:PHP的本地变量注册函数污点检测 PHP支持将字符串(可以是外部传入参数)解析成多个变量,这让WEBSHELL有能力将外部传入的参数转化为本地命名空间中的变量,常见的实现这一目的的方式有 1.parse_str2.extract3.foreach(..){$$key=$value;}1.parse_str:PH7不支持此语法 需要注意的是,extract传入的是一整个数组(而不是某个具体的键值),对应于vm_builtin_extract中传入的是一整个hashmap,这样,我们针对键值的污点打标,在vm_builtin_extract中就无法直接看到,而是要等到对hashmap解析完毕后才能看到特征,由于受到解析流程的影响,用于打标的污点字符串受到了裁剪 0x12:逻辑型WEBSHELL污点分析 沙箱的本质是按照待检测样本的逻辑,模拟Zend进行模拟执行,WEBSHELL为了规避沙箱的检测、并且隐藏自己不被管理员正常访问到,会对恶意WEBSHELL代码进行逻辑化处理(If条件判断) 1.对PH7_CompileIf进行Hook2.如果在当前if语句的expression中发现被比较的变量存在污点标记(外部传入参数),则强制当前控制流进入该if流支0x13:PHP自定义函数call_user_func回调污点分析 0x14:VFS、网络、数据库等危险API进行stub处理为了防止模拟执行中,PH7执行了危险函数,对本机造成了实际影响(例如写文件、发起数据库连接),需要对PH7中这些敏感函数进行stub处理,当执行到这些危险函数的时候,直接忽略跳过 1.文件操作:PH7支持2.网络操作:PH7不支持3.数据库DB操作:PH7不支持1.文件操作 高危操作重放风险 1)rmdir2)mkdir3)rename4)unlink5)delete6)chmod7)chown8)chgrp9)setenv10)putenv11)touch12)link13)symlink14)umask15)ftruncate16)file_put_contents17)copy18)fwrite19)fputs20)fputcsv21)fprintf22)vfprintf0x15:客户端沙箱性能控制 可能引起沙箱性能问题的API 1.sleep2.while3.dowhile4.for5.usleep如果黑客故意构造如下代码,可能会导致沙箱hang住,从而让其他的webshell都过沙箱检测 为此,需要对sleep进行stub处理,同时检测for循环的次数,当超过一定阈值的时候,强制跳出循环 9.待解决的问题 0x1:ob_start误报 在实际的业务场景中,ob_start、ob_end_clean被用来做HTML页面缓存,所以会造成误报 从攻防角度来看,include$GPC变量可以导致LFIWEBSHELL 0x3:外部GPC不可控参数污点误打标 VM沙箱检测的思想是对$_SERVER、$_POST、$_GET、$_REQUEST这些GPC的全部参数进行污点打标,然后在关键执行流进行Hook,检测是否发现污点标记,但是实际上,PHP中有一些GPC参数是外部不可控的,在进行污点打标的时候应该过滤掉这些参数 include$_SERVER['DOCUMENT_ROOT']RelevantLink: 0x5:对运算符支持不全导致漏报 0x6:extractHook导致误报 extract()常常在CMS中被用来进行外部参数自动化注册,但是也常常被WEBSHELL用俩进行参数隐藏,即将实际产生攻击的指令和payload都通过外部参数传入并通过extract本地注册,因此extract只是这种webshell触发攻击的一个必要不充分条件 0x7:借助编码、加密函数隐藏的大马SHELL 对于一句话变形来说,不管攻击者使用了何种变形隐藏方式,最终代码执行流都会到达VMHook点,但是WEBSHELL对抗还有另一个问题就是大马,这类WEBSHELL并不会调用到一句话木马常用的函数,所以也不会被沙箱检测到 1.通过PHP沙箱在关键的变形函数中加入内容检测逻辑,因为根据函数调用原理,不管使用了何种变形、加密,在函数Hook中看到的传入参数已经是解密后的明文了1)preg_replace2)gzinflate3)base64_decode4)rot132.针对解密后的明文进行正则规则匹配0x8:通过逻辑判断隐藏后门 黑客可以将畸形WEBSHELL放在例如IF条件语句中,如果VM沙箱不能预处理源代码的编译后汇编逻辑,使之进入所有流支,可能会导致漏报,但同时进入所有流支又可能带来性能损耗等原因 0x9:PHP的序列化、反序列化特性布置后门 0x10:利用运算符进行编码转换隐藏的WEBSHELL 0x11:filter_var、array_xxHook导致误报 1.如果黑客在filter_var中的第三个参数中传入了assert的回调,则最终VM会在assertHook点检测到污点标记2.如果黑客在filter_var中的第三个参数中传入的是一个外部参数(例如$_GET),则在filter_var的参数3中检测是否检测到污点标记0x12:逻辑后门