DEDECMS模板原理、模板标签学习

什么是编译式模板、解释式模板,它们的区别是什么?php

模板标签有哪些种类,它们的区别是什么,都应用在哪些场景?css

学习模板的机制原理对咱们修复目前CMS中常出现的模板类代码执行的漏洞能起到怎样的帮助?html

带着这些问题,咱们进入今天的代码研究,just hacking for fun!!前端

文章主要分为如下几个部分程序员

1. 模板基本知识介绍 2. 怎么使用模板机制、模板标签的使用方法 3. DEDE模板原理学习   1) 编译式模板   2) 解释式模板   3) 视图类模板 4. 针对模板解析底层代码的Hook Patch对CMS漏洞修复的解决方案

http://www.phpchina.com/archives/view-42534-1.htmlsql

http://tools.dedecms.com/uploads/docs/dede_tpl/index.htm数据库

1. 模板基本知识介绍express

cms模板是以cms为程序架构,就是在对应CMS系统的基础上制做的各种CMS内容管理系统的样式,页面模板等。业内对于CMS模板的定义亦是经过对于CMS系统的标签调用语言,实现CMS系统的前端展现风格,就像与一我的的外衣。编程

简单来讲,模板技术就是将业务逻辑代码和前台的UI逻辑进行了有效分离,使CMS的UI呈现和代码可以最大程序的解耦和,和MVC中的View层和Control层的思想很相似数组

系统的模板目录在系统根目录下的templets内,下面是模板目录的文件目录结构。

/templets
├─default·· 默认模板目录
│  ├─images·· 模板图片目录
│  │  ├─mood··
│  │  └─photo··
│  ├─js·· 模板JS脚本目录 │ └─style·· 模板CSS样式目录 ├─lurd·· LURD系统模板 ├─plus·· 插件模板目录 ├─system·· 系统底层模板目录 └─wap·· WAP模块模板目录

  DedeCMS 从 V5 开始采用了解析式引擎与编译式引擎并存的模式,因为在生成 HTML 时,解析式引擎拥有巨大的优点,但对于动态浏览的互动性质的页面,编译式引擎更实用高效,织梦 CMS 采用双引擎并存的模式,事实上还有另外一种模板的使用方法,即视图类,不过它是对解释式模板的代码复用而成的,咱们接下来会注意学习它们

2.  怎么使用模板机制、模板标签的使用方法

在了解了模板的基本知识以后,咱们接下来学习一下在DEDECMS中的模板机制、以及模板标签的使用方法

整体来讲,目前DEDECMS有如下三种模板机制

1. 编译式模板   1) 核心文件:   include/dedetemplate.class.php   /include/tpllib   2) 标签使用方法     2.1) 配置变量     {dede:config name='' value=''/}     配置变量能够在载入模板后经过 $tpl->GetConfig($name) 得到,仅做为配置,不在模板中显示。     2.2) 短标记     {dede:global.name/} 外部变量 等同于     {dede:var.name/} var数组 等同于 'name']; ?>     {dede:field.name/} field数组 等同于 'name']; ?>     {dede:cfg.name/} 系统配置变量 等同于     考虑到大多数状况下都会在函数或类中调用模板,所以 $_vars、$fields 数组必须声明为 global 数组,不然模板引擎没法得到它的值从而致使产生错误。     2.3) 自由调用块标记     {tag:blockname bind='GetArcList' bindtype='class'} 循环代码 {/tag:blockname}     必要属性:     bind 数据源来源函数     bindtype 函数类型,默认是 class 可选为 sub     rstype 返回结果类型,默认是 array ,可选项为 string     自定义函数格式必须为 function(array $atts,object $refObj, array $fields);     在没有指定 bind 绑定的函数的状况下,默认指向 MakePublicTag($atts,$tpl->refObj,$fields) 统一管理。     2.4) 固定块标记       2.4.1) datalist       从绑定类成员函数GetArcList中获取数组并输出       {dede:datalist} 循环代码 {/dede:datalist}       遍历一个二给维数组,数据源是固定的,只适用用类调用。       等同于       {tag:blockname bind='GetArcList' bindtype='class' rstype='arrayu'} 循环代码 {/tag:blockname}       2.4.2) label       从绑定函数中获取字符串值并输出       等同于 {tag:blockname bind='func' bindtype='sub' rstype='string'/}       2.4.3) pagelist       从绑定类成员函数GetPageList中获取字符串值并输出       等同于 {tag:blockname bind='GetPageList' bindtype='class' rstype='string'/}       2.4.4) include       {dede:include file=''/}       {dede:include filename=''/}       2.4.5) php       {dede:php php 代码 /}       或       {dede:php} php代码 {/dede:php}       2.4.6) If       仅支持 if ,else ,else 直接用{else}表示,但不支持{else if}这样的语法 ,通常建议模板中不要使用太复杂的条件语法,若是确实有须要,能够直接使用 php 语法。       {dede:if 条件} a-block {else} b-block {/dede:if}       条件中容许使用 var.name 、global.name 、field.name、cfg.name 表示相应的变量。       如:       {dede:if field.id>10 }....{/dede:if}       2.4.7) 遍历一个 array 数组       {dede:array.name}        {dede:key/} = {dede:value/}       {/dede:array}       各类语法的具体编译后的代码,可查看dedetemplate.class.php的function CompilerOneTag(&$cTag) 2. 解释式模板   1) 核心文件:   include/dedetag.class.php   /include/taglib   2) 标签使用方法     2.1) 内置系统标记       2.1.1) global 标记,表示获取一个外部变量,除了数据库密码以外,能调用系统的任何配置参数,形式为:       {dede:global name='变量名称'}{/dede:global}       或       {dede:global name='变量名称'/}       其中变量名称不能加$符号,如变量$cfg_cmspath,应该写成{dede:global name='cfg_cmspath'/}。       2.1.2) foreach 用来输出一个数组,形式为:       {dede:foreach array='数组名称'}[field:key/] [field:value/]{/dede:foreach}       2.1.3) include 引入一个文件,形式为:       {dede:include file='文件名称' ismake='是否为dede板块模板(yes/no)'/}       对文件的搜索路径为顺序为:绝对路径、include文件夹,CMS安装目录,CMS主模板目录     2.2) 自定义函数使用(以后在学习视图类的时候,会发现视图类的就是复用了解释式模板标签的这个自定义函数的标签用法)     {dede:标记名称 属性='' function='youfunction("参数一","参数二","@me")'/}     其中 @me 用于表示当前标记的值,其它参数由你的函数决定是否存在,例如:     {dede:field name='pubdate' function='strftime("%Y-%m-%d %H:%M:%S","@me")'/}     2.3) 织梦标记容许有限的编程扩展     格式为:     {dede:tagname runphp='yes'}      $aaa = @me;     @me = "123456";     {/dede:tagname}     @me 表示这个标记自己的值,所以标记内编程是不能使用echo之类的语句的,只能把全部返回值传递给@me。     此外因为程序代码占用了底层模板InnerText的内容,所以需编程的标记只能使用默认的InnerText。 3. 视图类模板   1) 核心文件   ....   arc.partview.class.php   ...   channelunit.class.php   channelunit.func.php   channelunit.helper.php   /include/taglib   2) 标签使用方法     2.1) 复用解释式模板标签的自定义函数标签,即钩子技术     {dede:php}...{/dede:php}  

3. DEDE模板原理学习

要使用模板机制,咱们就必须有一个代码层,负责提供数据,还得有一个UI层,负责调用模板标签进行UI显示,而模板标签的底层解析DEDECMS的核心库已经提供了,咱们只要在咱们的代码层进行引入就能够了,牢记这一点对咱们理解模板标签的使用、以及模板解析的原理颇有帮助

3.1 编译式模板

先来写个程序(之后root表明根目录) 
root/code.php

php
    //利用dedecms写php时,基本都要引入common.inc.php require_once (dirname(__FILE__) . '/include/common.inc.php'); //利用编译式模板所需的文件 require_once (DEDEINC.'/dedetemplate.class.php'); //生成编译模板引擎类对象 $tpl = new DedeTemplate(dirname(__file__)); //装载网页模板 $tpl->LoadTemplate('code.tpl.htm'); //把php值传到html $title = 'Hello World'; $tpl->SetVar('title',$title); $tpl->Display(); //把编译好的模板缓存作成code.html,就能够直接调用 $tpl->SaveTo(dirname(__FILE__).'/code.html'); ?>

root/code.tpl.htm

"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> "Content-Type" content="text/html; charset=utf-8" />

{dede:var.title/}

 {dede:php echo "Little"; /} {dede:php} echo "Hann"; {/dede:php} 

这两个文件编写完成后,访问code.php

 

同时,在当前目录下也生成了静态的html文件

code.html

 

这也是所谓的"编译式模板"的意思,联想咱们在写C程序的时候,编译器会根据你的C代码编译出exe静态文件,dede的编译式引擎这里也采起了相似的思路。

咱们前面说过,编译式模板和标签解释的文件都放在/include/ tpllib 下,因此若是咱们须要编写、实现咱们本身的自定义标签,就须要按照DEDE的代码架构,在这个文件夹下添加新的标签处理代码逻辑

在include/tpllib中找一个文件来仿制。如plus_ask(咱们编写的自定义标签的解析逻辑须要知足DEDE的代码架构,这点在编写插件的时候也是一样的思路,由于咱们是在别人的基础上进行二次开发) 
root/include/tpllib/plus_hello

php
if(!defined('DEDEINC')) exit('Request Error!'); /** * 动态模板hello标签 * * @version $Id: plus_ask.php 1 13:58 2010年7月5日Z tianya $ * @package DedeCMS.Tpllib * @copyright Copyright (c) 2007 - 2010, DesDev, Inc. * @license http://help.dedecms.com/usersguide/license.html * @link http://www.dedecms.com */ function plus_hello(&$atts,&$refObj,&$fields) { global $dsql,$_vars; //给出标签的属性默认参数值列表,以’,’分隔,即便不设置默认参数也要给出属性名 $attlist = "name="; FillAtts($atts,$attlist); FillFields($atts,$fields,$refObj); extract($atts, EXTR_OVERWRITE); //返回处理结果,以替换标签 return 'hello!'.$name; } ?>

仍是一样的思路,编写模板文件,去调用这个自定义标签

root/code.tpl.htm

"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> "Content-Type" content="text/html; charset=utf-8" />

{dede:hello name='LittleHann' rstype='string'/}

这两个文件都编写完毕以后,访问code.php

 

访问静态html文件

 

了解了编译式模板的使用方法,接下来咱们要一块儿深刻DEDECMS的源代码,来看看DEDE在底层是怎么去实现这些方便的模板机制的,使用的版本为

DedeCMS-V5.7-GBK-SP1.tar

这里容许我再复制一遍code.php的代码,咱们对照着它的代码来一行一行的解释

php
    //利用dedecms写php时,基本都要引入common.inc.php require_once (dirname(__FILE__) . '/include/common.inc.php'); //利用编译式模板所需的文件 require_once (DEDEINC.'/dedetemplate.class.php'); //生成编译模板引擎类对象 $tpl = new DedeTemplate(dirname(__file__)); //装载网页模板 $tpl->LoadTemplate('code.tpl.htm'); //把php值传到html $title = 'Hello World'; $tpl->SetVar('title',$title); $tpl->Display(); //把编译好的模板缓存作成code.html,就能够直接调用 $tpl->SaveTo(dirname(__FILE__).'/code.html'); ?>

//生成编译模板引擎类对象 
$tpl = new DedeTemplate(dirname(__file__));

function __construct($templatedir='',$refDir='') { //缓存目录 if($templatedir=='') { $this->templateDir = DEDEROOT.'/templates'; } else { //接收用户指定的模板目录 $this->templateDir = $templatedir; } //模板include目录 if($refDir=='') { if(isset($GLOBALS['cfg_df_style'])) { //根据用户在后台风格设置所选择风格设置模板 $this->refDir = $this->templateDir.'/'.$GLOBALS['cfg_df_style'].'/'; } else { $this->refDir = $this->templateDir; } } //设置模板编译缓存文件目录 $this->cacheDir = DEDEROOT.$GLOBALS['cfg_tplcache_dir']; }

//装载网页模板 
$tpl->LoadTemplate('code.tpl.htm');

function LoadTemplate($tmpfile) { if(!file_exists($tmpfile)) { echo " Template Not Found! "; exit(); } //对用户传入的路径参数进行规范化 $tmpfile = preg_replace("/[\\/]{1,}/", "/", $tmpfile); $tmpfiles = explode('/',$tmpfile); $tmpfileOnlyName = preg_replace("/(.*)\//", "", $tmpfile); $this->templateFile = $tmpfile; $this->refDir = ''; for($i=0; $i < count($tmpfiles)-1; $i++) { $this->refDir .= $tmpfiles[$i].'/'; } //设置缓存目录 if(!is_dir($this->cacheDir)) { $this->cacheDir = $this->refDir; } if($this->cacheDir!='') { $this->cacheDir = $this->cacheDir.'/'; } if(isset($GLOBALS['_DEBUG_CACHE'])) { $this->cacheDir = $this->refDir; } //生成对应的高速缓存的文件名 $this->cacheFile = $this->cacheDir.preg_replace("/\.(wml|html|htm|php)$/", "_".$this->GetEncodeStr($tmpfile).'.inc', $tmpfileOnlyName); $this->configFile = $this->cacheDir.preg_replace("/\.(wml|html|htm|php)$/", "_".$this->GetEncodeStr($tmpfile).'_config.inc', $tmpfileOnlyName); /* 1. 不开启缓存 2. 当缓存文件不存在 3. 及模板未更新(即未被改动过)的文件的时候才载入模板并进行解析 */ if($this->isCache==FALSE || !file_exists($this->cacheFile) || filemtime($this->templateFile) > filemtime($this->cacheFile)) { $t1 = ExecTime(); //debug $fp = fopen($this->templateFile,'r'); $this->sourceString = fread($fp,filesize($this->templateFile)); fclose($fp); //对模板源文件进行解析,接下来重点分析 $this->ParseTemplate(); //模板解析时间 //echo ExecTime() - $t1; } else { //若是存在config文件,则载入此文件,该文件用于保存 $this->tpCfgs的内容,以供扩展用途 //模板中用{tag:config name='' value=''/}来设定该值 if(file_exists($this->configFile)) { //当前高速缓存文件有效命中(即在有效期以内),则引入之 include($this->configFile); } } }

//对模板源文件进行解析 
$this->ParseTemplate();

function ParseTemplate() { if($this->makeLoop > 5) { return ; } //当前模板文件中的模板标签个数 $this->count = -1; //保存解析出的模板标签数组 $this->cTags = array(); $this->isParse = TRUE; $sPos = 0; $ePos = 0; //模板标签的开始定界符 $tagStartWord = $this->tagStartWord; //模板标签的结束定界符 $fullTagEndWord = $this->fullTagEndWord; $sTagEndWord = $this->sTagEndWord; $tagEndWord = $this->tagEndWord; $startWordLen = strlen($tagStartWord); //保存模板原始文件的字符串 $sourceLen = strlen($this->sourceString); //检测当前模板文件是不是有效模板文件 if( $sourceLen <= ($startWordLen + 3) ) { return; } //实例化标签属性解析对象 $cAtt = new TagAttributeParse(); $cAtt->CharToLow = TRUE; //遍历模板字符串,请取标记及其属性信息 $t = 0; $preTag = ''; $tswLen = strlen($tagStartWord); for($i=0; $i<$sourceLen; $i++) { $ttagName = ''; //若是不进行此判断,将没法识别相连的两个标记 if($i-1>=0) { $ss = $i-1; } else { $ss = 0; } $tagPos = strpos($this->sourceString,$tagStartWord,$ss); //判断后面是否还有模板标记 if($tagPos==0 && ($sourceLen-$i < $tswLen || substr($this->sourceString,$i,$tswLen) != $tagStartWord )) { $tagPos = -1; break; } //获取TAG基本信息 for($j = $tagPos+$startWordLen; $j < $tagPos+$startWordLen+$this->tagMaxLen; $j++) { if(preg_match("/[ >\/\r\n\t\}\.]/", $this->sourceString[$j])) { break; } else { $ttagName .= $this->sourceString[$j]; } } if($ttagName!='') { $i = $tagPos + $startWordLen; $endPos = -1; //判断 '/}' '{tag:下一标记开始' '{/tag:标记结束' 谁最靠近 $fullTagEndWordThis = $fullTagEndWord.$ttagName.$tagEndWord; $e1 = strpos($this->sourceString, $sTagEndWord, $i); $e2 = strpos($this->sourceString, $tagStartWord, $i); $e3 = strpos($this->sourceString, $fullTagEndWordThis, $i); $e1 = trim($e1); $e2 = trim($e2); $e3 = trim($e3); $e1 = ($e1=='' ? '-1' : $e1); $e2 = ($e2=='' ? '-1' : $e2); $e3 = ($e3=='' ? '-1' : $e3); if($e3==-1) { //不存在'{/tag:标记' $endPos = $e1; $elen = $endPos + strlen($sTagEndWord); } else if($e1==-1) { //不存在 '/}' $endPos = $e3; $elen = $endPos + strlen($fullTagEndWordThis); } //同时存在 '/}' 和 '{/tag:标记' else { //若是 '/}' 比 '{tag:'、'{/tag:标记' 都要靠近,则认为结束标志是 '/}',不然结束标志为 '{/tag:标记' if($e1 < $e2 && $e1 < $e3 ) { $endPos = $e1; $elen = $endPos + strlen($sTagEndWord); } else { $endPos = $e3; $elen = $endPos + strlen($fullTagEndWordThis); } } //若是找不到结束标记,则认为这个标记存在错误 if($endPos==-1) { echo "Tpl Character postion $tagPos, '$ttagName' Error!
\r\n"; break; } $i = $elen; //分析所找到的标记位置等信息 $attStr = ''; $innerText = ''; $startInner = 0; for($j = $tagPos+$startWordLen; $j < $endPos; $j++) { if($startInner==0) { if($this->sourceString[$j]==$tagEndWord) { $startInner=1; continue; } else { $attStr .= $this->sourceString[$j]; } } else { $innerText .= $this->sourceString[$j]; } } $ttagName = strtolower($ttagName); /* 1. if标记,把整个属性串视为属性 2. 注意到preg_replace的$format参数最后有一个"i",表明执行正则替换的同时,进行代码执行,也就是以PHP的方式对IF语句进行执行 */ if(preg_match("/^if[0-9]{0,}$/", $ttagName)) { $cAtt->cAttributes = new TagAttribute(); $cAtt->cAttributes->count = 2; $cAtt->cAttributes->items['tagname'] = $ttagName; $cAtt->cAttributes->items['condition'] = preg_replace("/^if[0-9]{0,}[\r\n\t ]/", "", $attStr); $innerText = preg_replace("/\{else\}/i", '<'."?php\r\n}\r\nelse{\r\n".'?'.'>', $innerText); } /* 1. php标记 2. 注意到preg_replace的$format参数最后有一个"i",表明执行正则替换的同时,并"不"进行代码执行,只是简单地将标签内的内容翻译为等价的PHP语法 */ else if($ttagName=='php') { $cAtt->cAttributes = new TagAttribute(); $cAtt->cAttributes->count = 2; $cAtt->cAttributes->items['tagname'] = $ttagName; $cAtt->cAttributes->items['code'] = '<'."?php\r\n".trim(preg_replace("/^php[0-9]{0,}[\r\n\t ]/", "",$attStr))."\r\n?".'>'; } else { //普通标记,解释属性 $cAtt->SetSource($attStr); } $this->count++; $cTag = new Tag(); $cTag->tagName = $ttagName; $cTag->startPos = $tagPos; $cTag->endPos = $i; $cTag->cAtt = $cAtt->cAttributes; $cTag->isCompiler = FALSE; $cTag->tagID = $this->count; $cTag->innerText = $innerText; $this->cTags[$this->count] = $cTag; } else { $i = $tagPos+$startWordLen; break; } }//结束遍历模板字符串 if( $this->count > -1 && $this->isCompiler ) { //调用/include/tplib/下的对应标签解析文件对指定标签进行解析 $this->CompilerAll(); } }

回到code.php的代码分析上来,咱们已经知道引擎会把php标签内的内容翻译为等价的<?php .. ?>代码

//把php值传到html 
$title = 'Hello World'; 
$tpl->SetVar('title',$title);

function SetVar($k, $v) { /* 1. 所谓的从代码层向UI层传值,本质上就是利用超全局变量进行变量共享 2. 模板标签的本质就是等价的值替换 */ $GLOBALS['_vars'][$k] = $v; }

回到code.php

//显示编译后的模板文件

$tpl->Display();

function Display() { global $gtmpfile; //进行一次全局数组的变量注册 extract($GLOBALS, EXTR_SKIP); //将编译后的模板文件写进告诉缓存文件中,以备下一次访问的时候加速访问速度 $this->WriteCache(); /* 1. 编译好的文件include引入进来 2. 这一步是代码可以执行的关键,由于咱们知道,编译式模板引擎在上一步翻译标签的时候只是单纯地将php标签内的内容翻译为等价的"",并不提供执行 3. include进来后,代码就获得了执行 */ include $this->cacheFile; }

回到code.php

//把编译好的模板缓存作成code.html,就能够直接调用 
$tpl->SaveTo(dirname(__FILE__).'/code.html');

function SaveTo($savefile) { extract($GLOBALS, EXTR_SKIP); //这就是为何咱们在访问了一次编译式模板.php代码后,能够继而访问已经生成了静态html文件 $this->WriteCache(); ob_start(); //再次引入一次 include $this->cacheFile; $okstr = ob_get_contents(); ob_end_clean(); $fp = @fopen($savefile,"w") or die(" Tag Engine Create File FALSE! "); fwrite($fp,$okstr); fclose($fp); }

3.2 解释式模板

首先须要解释一下这个名词,为何要称之为解释式模板引擎呢?咱们都知道C语言属于编译式的语言,须要将源代码一次所有编译成exe文件才能够统一执行,而PHP属于解释式语言,zend引擎在解释的时候是逐条读取PHP源代码,而后逐条执行。

而回想咱们以前学习编译式模板引擎的时候,编译式引擎会先将全部的php执行标签所有先翻译为等价的php可执行语法,而后在最后一个统一的include进行代码执行,这不就是编译式的思想吗?

而咱们接下来要学习的解释式模板引擎,是逐个检测php执行标签,在解析的同时就直接进行eval执行,这刚好体现了解释式语言的思想,这就是编译式、解释式名词的由来

咱们先来学习一下解释式标签的使用方法

编写/root/code.php,仍是同样,记住模板的两个关键要素,代码层、UI层

php
    require_once (dirname(__file__).'/include/common.inc.php'); //利用解析式模板所需的文件 require_once (dirname(__file__).'/include/dedetag.class.php'); $dtp=new DedeTagParse(); $dtp->LoadTemplate(dirname(__file__).'\code.tpl.htm '); foreach ($dtp->CTags as $id=>$tag) { if($tag->GetName()=='my') //把id为$id的tag翻译成这是my标签
$dtp->Assign($id,'this is my tag
'); else if($tag->GetName()=='test') $dtp->Assign($id,'this is test tag
'); } $dtp->Display(); ?>

编写code.tpl.htm文件

"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> "Content-Type" content="text/html; charset=utf-8" /> {dede:my att1=1 att2='2'} [field:my/] {/dede:my} {dede:test att1=1 att2='2'} [field:test/] {/dede:test} {dede:tagname runphp='yes'} echo "LittleHann" . "
"; {/dede:tagname}

这两个文件都编写好以后,访问code.php

 

解释式模板引擎并不会产生静态html文件,即时解释,即时生效,并不保存

 

了解了解释式模板标签的使用方法后,咱们接下来学习一下解释式模板引擎的代码原理

请容许我再次将code.php的代码复制出来,咱们逐条的分析它的代码

php
    require_once (dirname(__file__).'/include/common.inc.php'); //利用解析式模板所需的文件 require_once (dirname(__file__).'/include/dedetag.class.php'); //实例化一个DedeTagParse对象 $dtp=new DedeTagParse(); //加载模板 $dtp->LoadTemplate(dirname(__file__).'\code.tpl.htm '); foreach ($dtp->CTags as $id=>$tag) { if($tag->GetName()=='my') //把id为$id的tag翻译成这是my标签
$dtp->Assign($id,'this is my tag
'); else if($tag->GetName()=='test') $dtp->Assign($id,'this is test
'); } $dtp->Display(); ?>

//实例化一个DedeTagParse对象 
$dtp=new DedeTagParse();

function __construct() { //设置是否保存高速缓存文件 if(!isset($GLOBALS['cfg_tplcache'])) { $GLOBALS['cfg_tplcache'] = 'N'; } if($GLOBALS['cfg_tplcache']=='Y') { $this->IsCache = TRUE; } else { $this->IsCache = FALSE; } //设置默认命名空间为dede $this->NameSpace = 'dede'; //设置模板标签开始定界符 $this->TagStartWord = '{'; //设置模板标签结束定界符 $this->TagEndWord = '}'; //模板标签最大长度 $this->TagMaxLen = 64; $this->CharToLow = TRUE; //保存模板源文件 $this->SourceString = ''; //保存解析后的标签对象数组 $this->CTags = Array(); $this->Count = -1; $this->TempMkTime = 0; $this->CacheFile = ''; }

//加载模板 
$dtp->LoadTemplate(dirname(__file__).'\code.tpl.htm ');

function LoadTemplate($filename) { //设置默认模板文件路径 $this->SetDefault(); //检测模板文件是否存在 if(!file_exists($filename)) { $this->SourceString = " $filename Not Found! "; $this->ParseTemplet(); } else { $fp = @fopen($filename, "r"); while($line = fgets($fp,1024)) {   $this->SourceString .= $line; } fclose($fp); //若是高速缓存命中,则直接返回,加快访问速度 if($this->LoadCache($filename)) {   return ''; } else {   //对模板源文件进行标签解析   $this->ParseTemplet(); } } }

//对模板源文件进行标签解析 
$this->ParseTemplet();

function ParseTemplet() { //模板标签开始定界符 $TagStartWord = $this->TagStartWord; //模板标签结束定界符 $TagEndWord = $this->TagEndWord; $sPos = 0; $ePos = 0; //命名空间的拼接 $FullTagStartWord = $TagStartWord.$this->NameSpace.":"; $sTagEndWord = $TagStartWord."/".$this->NameSpace.":"; $eTagEndWord = "/".$TagEndWord; $tsLen = strlen($FullTagStartWord); $sourceLen=strlen($this->SourceString); //检测原始模板文件是否符合规范 if( $sourceLen <= ($tsLen + 3) ) { return; } //实例化一个标签属性解析对象 $cAtt = new DedeAttributeParse(); $cAtt->charToLow = $this->CharToLow; //遍历模板字符串,请取标记及其属性信息 for($i=0; $i < $sourceLen; $i++) { $tTagName = ''; //若是不进行此判断,将没法识别相连的两个标记 if($i-1 >= 0) { $ss = $i-1; } else { $ss = 0; } $sPos = strpos($this->SourceString,$FullTagStartWord,$ss); $isTag = $sPos; if($i==0) { $headerTag = substr($this->SourceString,0,strlen($FullTagStartWord)); if($headerTag==$FullTagStartWord) { $isTag=TRUE; $sPos=0; } } if($isTag===FALSE) { break; } //开始遍历模板源文件 for($j=($sPos+$tsLen); $j<($sPos+$tsLen+$this->TagMaxLen); $j++) { if($j>($sourceLen-1)) { break; } else if( preg_match("/[\/ \t\r\n]/", $this->SourceString[$j]) || $this->SourceString[$j] == $this->TagEndWord ) { break; } else { $tTagName .= $this->SourceString[$j]; } } //对标签的开始和结束、嵌套标签进行定位 if($tTagName != '') { $i = $sPos + $tsLen; $endPos = -1; $fullTagEndWordThis = $sTagEndWord.$tTagName.$TagEndWord; $e1 = strpos($this->SourceString,$eTagEndWord, $i); $e2 = strpos($this->SourceString,$FullTagStartWord, $i); $e3 = strpos($this->SourceString,$fullTagEndWordThis,$i); //$eTagEndWord = /} $FullTagStartWord = {tag: $fullTagEndWordThis = {/tag:xxx] $e1 = trim($e1); $e2 = trim($e2); $e3 = trim($e3); $e1 = ($e1=='' ? '-1' : $e1); $e2 = ($e2=='' ? '-1' : $e2); $e3 = ($e3=='' ? '-1' : $e3); //not found '{/tag:' if($e3==-1) { $endPos = $e1; $elen = $endPos + strlen($eTagEndWord); } //not found '/}' else if($e1==-1) { $endPos = $e3; $elen = $endPos + strlen($fullTagEndWordThis); } //found '/}' and found '{/dede:' else { //if '/}' more near '{dede:'、'{/dede:' , end tag is '/}', else is '{/dede:' if($e1 < $e2 && $e1 < $e3 ) { $endPos = $e1; $elen = $endPos + strlen($eTagEndWord); } else { $endPos = $e3; $elen = $endPos + strlen($fullTagEndWordThis); } } //not found end tag , error if($endPos==-1) { echo "Tag Character postion $sPos, '$tTagName' Error!
\r\n"; break; } $i = $elen; $ePos = $endPos; //分析所找到的标记位置等信息 $attStr = ''; $innerText = ''; $startInner = 0; for($j=($sPos+$tsLen);$j < $ePos;$j++) { if($startInner==0 && ($this->SourceString[$j]==$TagEndWord && $this->SourceString[$j-1]!="\\") ) { $startInner=1; continue; } if($startInner==0) { $attStr .= $this->SourceString[$j]; } else { $innerText .= $this->SourceString[$j]; } } //echo "<span class="hljs-comment">$attStr</span>\r\n"; /* 朋友们看到这里能够稍微停一下,咱们将dedetag.class.php和dedetemplate.class.php进行一下横向对比 1. 编译式模板引擎在loadTemplate的时候就会将全部的标签都翻译为等价的PHP代码,至关于一个编译的过程,等待以后的include进行引入执行 2. 解释式模板引擎在laodTemplate的时候只是进行单纯的标签解析、提取出有效内容,并不作实际的翻译。而具体的解释和执行是在后面的Display中进行的,即边解释,边执行 3. 在学习这两种模板机制的时候多多和传统编程中的概念进行对比,可以帮助咱们更加深刻地理解概念 */ $cAtt->SetSource($attStr); if($cAtt->cAttributes->GetTagName()!='') { $this->Count++; $CDTag = new DedeTag(); $CDTag->TagName = $cAtt->cAttributes->GetTagName(); $CDTag->StartPos = $sPos; $CDTag->EndPos = $i; $CDTag->CAttribute = $cAtt->cAttributes; $CDTag->IsReplace = FALSE; $CDTag->TagID = $this->Count; $CDTag->InnerText = $innerText; $this->CTags[$this->Count] = $CDTag; } } else { $i = $sPos+$tsLen; break; } } //结束遍历模板字符串 if($this->IsCache) { //保存标签解释完毕后的模板文件到高速缓存中(注意,由于这是解释式引擎,因此此时保存的cache中并非PHP代码,而是附带标签的模板文件) $this->SaveCache(); } }

回到code.php上来

$dtp->Display();

function Display() { echo $this->GetResult(); }

echo $this->GetResult();

function GetResult() { $ResultString = ''; if($this->Count==-1) { return $this->SourceString; } //进行标签的解释、并执行。这里就至关于解释器的做用了 $this->AssignSysTag(); $nextTagEnd = 0; $strok = ""; for($i=0;$i<=$this->Count;$i++) { $ResultString .= substr($this->SourceString,$nextTagEnd,$this->CTags[$i]->StartPos-$nextTagEnd); $ResultString .= $this->CTags[$i]->GetValue(); $nextTagEnd = $this->CTags[$i]->EndPos; } $slen = strlen($this->SourceString); if($slen>$nextTagEnd) { $ResultString .= substr($this->SourceString,$nextTagEnd,$slen-$nextTagEnd); } //返回解释执行后的返回结果 return $ResultString; }

//进行标签的解释、并执行。这里就至关于解释器的做用了 
$this->AssignSysTag();

function AssignSysTag() { global $_sys_globals; for($i=0;$i<=$this->Count;$i++) { $CTag = $this->CTags[$i]; $str = ''; //获取一个外部变量 if( $CTag->TagName == 'global' ) { $str = $this->GetGlobals($CTag->GetAtt('name')); if( $this->CTags[$i]->GetAtt('function')!='' ) { //$str = $this->EvalFunc( $this->CTags[$i]->TagValue, $this->CTags[$i]->GetAtt('function'),$this->CTags[$i] ); $str = $this->EvalFunc( $str, $this->CTags[$i]->GetAtt('function'),$this->CTags[$i] ); } $this->CTags[$i]->IsReplace = TRUE; $this->CTags[$i]->TagValue = $str; } //引入静态文件 else if( $CTag->TagName == 'include' ) {   $filename = ($CTag->GetAtt('file')=='' ? $CTag->GetAtt('filename') : $CTag->GetAtt('file') );   $str = $this->IncludeFile($filename,$CTag->GetAtt('ismake'));   $this->CTags[$i]->IsReplace = TRUE;   $this->CTags[$i]->TagValue = $str; } //循环一个普通数组 else if( $CTag->TagName == 'foreach' ) { $arr = $this->CTags[$i]->GetAtt('array'); if(isset($GLOBALS[$arr])) { foreach($GLOBALS[$arr] as $k=>$v) { $istr = ''; $istr .= preg_replace("/\[field:key([\r\n\t\f ]+)\/\]/is",$k,$this->CTags[$i]->InnerText); $str .= preg_replace("/\[field:value([\r\n\t\f ]+)\/\]/is",$v,$istr); } } $this->CTags[$i]->IsReplace = TRUE; $this->CTags[$i]->TagValue = $str; } //设置/获取变量值 else if( $CTag->TagName == 'var' ) { $vname = $this->CTags[$i]->GetAtt('name'); if($vname=='') { $str = ''; } else if($this->CTags[$i]->GetAtt('value')!='') { $_vars[$vname] = $this->CTags[$i]->GetAtt('value'); } else { $str = (isset($_vars[$vname]) ? $_vars[$vname] : ''); }   $this->CTags[$i]->IsReplace = TRUE;   $this->CTags[$i]->TagValue = $str; } /* 运行PHP接口 当检测到有runphp这种标签属性的时候,则对这个标签进行PHP解析 */ if( $CTag->GetAtt('runphp') == 'yes' ) {   $this->RunPHP($CTag, $i); } if(is_array($this->CTags[$i]->TagValue)) {   $this->CTags[$i]->TagValue = 'array'; } } }

$this->RunPHP($CTag, $i);

function RunPHP(&$refObj, $i) { $DedeMeValue = $phpcode = ''; if($refObj->GetAtt('source')=='value') { $phpcode = $this->CTags[$i]->TagValue; } else { $DedeMeValue = $this->CTags[$i]->TagValue; //获取标签内的内容 $phpcode = $refObj->GetInnerText(); } //将@me替换成$DedeMeValue标签值 $phpcode = preg_replace("/'@me'|\"@me\"|@me/i", '$DedeMeValue', $phpcode); /* 这句是关键,对php执行标签内的内容直接调用eval进行执行\ 体会一下这是否是边解释、边执行的效果 */ @eval($phpcode); //or die("<span class="hljs-comment">$phpcode</span>"); //保存执行的结果 $this->CTags[$i]->TagValue = $DedeMeValue; $this->CTags[$i]->IsReplace = TRUE; }

3.3 视图类模板

接下来要学习的第三种模板称之为视图类模板,严格来讲,它不能算是一种新的模板机制,由于它复用了不少解释式模板的代码逻辑

先来学习一下视图类模板的使用方法

在根目录下编写code.php

php
    require_once (dirname(__file__).'/include/common.inc.php'); //利用解析式模板所需的文件 require_once(DEDEINC.'/arc.partview.class.php'); //实例化一个PartView对象 $pv = new PartView(); $tagbody = file_get_contents("code.tpl.htm"); //加载模板 $pv->SetTemplet($tagbody, 'string');
   echo $pv->GetResult(); ?>

而后编写模板文件 code.tpl.htm

"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> "Content-Type" content="text/html; charset=utf-8" />

{dede:php} echo "LittleHann"; {/dede:php}

这两个文件都准备好以后,访问code.php

 

接下来,咱们来分析一下这个视图类模板的解析原理

//实例化一个PartView对象 
$pv = new PartView();

function __construct($typeid=0,$needtypelink=TRUE) { global $_sys_globals,$ftp; $this->TypeID = $typeid; $this->dsql = $GLOBALS['dsql']; /* 实例化一个解释式模板引擎对象 这句要重点注意,咱们以后会看到视图类模板对象复用了解释式模板引擎的部分代码逻辑 */ $this->dtp = new DedeTagParse(); //设置模板标签的命名空间 $this->dtp->SetNameSpace("dede","{","}"); $this->dtp->SetRefObj($this); $this->ftp = &$ftp; $this->remoteDir = ''; if($needtypelink) { $this->TypeLink = new TypeLink($typeid); if(is_array($this->TypeLink->TypeInfos)) { foreach($this->TypeLink->TypeInfos as $k=>$v) { if(preg_match("/[^0-9]/", $k)) { $this->Fields[$k] = $v; } } } $_sys_globals['curfile'] = 'partview'; $_sys_globals['typename'] = $this->Fields['typename']; //设置环境变量 SetSysEnv($this->TypeID,$this->Fields['typename'],0,'','partview'); } SetSysEnv($this->TypeID,'',0,'','partview'); $this->Fields['typeid'] = $this->TypeID; //设置一些全局参数的值 foreach($GLOBALS['PubFields'] as $k=>$v) { $this->Fields[$k] = $v; } }

回到code.php上来

//加载模板 
$pv->SetTemplet($tagbody, 'string');

function SetTemplet($temp,$stype="file") { if($stype=="string") { //复用解释式模板引擎的LoadSource方法,去加载、匹配标签 $this->dtp->LoadSource($temp); } else { $this->dtp->LoadTemplet($temp); } if($this->TypeID > 0) { $this->Fields['position'] = $this->TypeLink->GetPositionLink(TRUE); $this->Fields['title'] = $this->TypeLink->GetPositionLink(false); } //调用视图类模板引擎本身的标签解释方法ParseTemplet $this->ParseTemplet(); }

//复用解释式模板引擎的LoadSource方法,去加载、匹配标签 
$this->dtp->LoadSource($temp);

咱们知道,对于解释式模板引擎来讲,LoadSource只是在在加载模板,并对模板文件中的标签进行提取并保存,而具体的标签解析、执行要在Display中进行。

因此,视图类模板引擎复用了解释式模板引擎的这个LoadSource逻辑

接下来,视图类模板引擎调用了本身的 ParseTemplet 方法,进行具体的标签解析、执行

//调用视图类模板引擎本身的标签解释方法ParseTemplet 
$this->ParseTemplet();

function ParseTemplet() { $GLOBALS['envs']['typeid'] = $this->TypeID; if($this->TypeID>0) { $GLOBALS['envs']['topid'] = GetTopid($this->TypeID); } else { $GLOBALS['envs']['topid'] = 0; } if(isset($this->TypeLink->TypeInfos['reid'])) { $GLOBALS['envs']['reid'] = $this->TypeLink->TypeInfos['reid']; } if(isset($this->TypeLink->TypeInfos['channeltype'])) { $GLOBALS['envs']['channelid'] = $this->TypeLink->TypeInfos['channeltype']; } /* 这个函数放在 channelunit.func.php 文件中 视图类模板引擎使用了钩子技术来对标签进行动态地解析 这个函数是钩子的入口 */ MakeOneTag($this->dtp,$this); }

MakeOneTag($this->dtp,$this);

在arc.partview.class.php的开头,include了channelunit.class.php,而channelunit.class.php又引入了channelunit.func.php,在channelunit.func.php中加载了一个辅助类

helper('channelunit');

这个辅助类的加载函数helper的实如今common.func.php中

$_helpers = array();
function helper($helpers) { //若是是数组,则进行递归操做 if (is_array($helpers)) { foreach($helpers as $dede) { helper($dede); } return; } if (isset($_helpers[$helpers])) { continue; } if (file_exists(DEDEINC.'/helpers/'.$helpers.'.helper.php')) { include_once(DEDEINC.'/helpers/'.$helpers.'.helper.php'); $_helpers[$helpers] = TRUE; } // 没法载入小助手 if ( ! isset($_helpers[$helpers])) { exit('Unable to load the requested file: helpers/'.$helpers.'.helper.php'); } }

这样,经过调用helper('channelunit')成功加载了channelunit.helper.php文件, MakeOneTag 的实现就在这个文件中

function MakeOneTag(&$dtp, &$refObj, $parfield='Y') { global $cfg_disable_tags; //检测用户是否设置了禁用{dede:php}模板标签 $cfg_disable_tags = isset($cfg_disable_tags)? $cfg_disable_tags : 'php'; $disable_tags = explode(',', $cfg_disable_tags); $alltags = array(); $dtp->setRefObj($refObj); //读取自由调用tag列表 $dh = dir(DEDEINC.'/taglib'); while($filename = $dh->read()) { if(preg_match("/\.lib\./", $filename)) { $alltags[] = str_replace('.lib.php','',$filename); } } $dh->Close(); //遍历tag元素 if(!is_array($dtp->CTags)) { return ''; } foreach($dtp->CTags as $tagid=>$ctag) { $tagname = $ctag->GetName(); if($tagname=='field' && $parfield=='Y') { $vname = $ctag->GetAtt('name'); if( $vname=='array' && isset($refObj->Fields) ) { $dtp->Assign($tagid,$refObj->Fields); } else if(isset($refObj->Fields[$vname])) { $dtp->Assign($tagid,$refObj->Fields[$vname]); } else if($ctag->GetAtt('noteid') != '') { if( isset($refObj->Fields[$vname.'_'.$ctag->GetAtt('noteid')]) ) { $dtp->Assign($tagid, $refObj->Fields[$vname.'_'.$ctag->GetAtt('noteid')]); } } continue; } //因为考虑兼容性,原来文章调用使用的标记别名统一保留,这些标记实际调用的解析文件为inc_arclist.php if(preg_match("/^(artlist|likeart|hotart|imglist|imginfolist|coolart|specart|autolist)$/", $tagname)) { $tagname='arclist'; } if($tagname=='friendlink') { $tagname='flink'; } if(in_array($tagname,$alltags)) { if(in_array($tagname, $disable_tags)) { echo 'DedeCMS Error:Tag disabled:"'.$tagname.'"           target="_blank">more...!'; return FALSE; } if (DEBUG_LEVEL==TRUE) { $ttt1 = ExecTime(); } /* 从这里开始就是关于钩子技术的实现了 1. 根据标签动态地决定要加载什么标签解析文件。咱们知道,和解释式标签有关的解释代码都在/include/taglib/中 2. 根据标签动态的拼接要调用的函数名,即PHP的动态函数执行,这是一种典型的钩子技术 */ $filename = DEDEINC.'/taglib/'.$tagname.'.lib.php'; include_once($filename); $funcname = 'lib_'.$tagname; //调用动态函数进行执行,并将返回结果传给UI层 $dtp->Assign($tagid,$funcname($ctag,$refObj)); if (DEBUG_LEVEL==TRUE) { $queryTime = ExecTime() - $ttt1; echo '标签:'.$tagname.'载入花费时间:'.$queryTime."
\r\n"; } } } }

例如,咱们的模板文件的内容是{dede:php}echo 2;{/dede:php}

则在钩子函数MakeOneTag这里就会动态的去include引入php_lib.php的这个文件,并调用php_lib方法对这个标签进行解析,具体怎么解析的逻辑都在php_lib.php这个文件中

function lib_php(&$ctag, &$refObj) { global $dsql; global $db; $phpcode = trim($ctag->GetInnerText()); if ($phpcode == '') return ''; ob_start(); //再次进行一次本地啊变量注册 extract($GLOBALS, EXTR_SKIP); //这句是关键,直接对标签内部的内容调用eval进行执行 @eval($phpcode); //只不过和解释式模板引擎不一样的是,这里并非直接返回执行结果,而是将执行结果缓存起来,返回给调用方 $revalue = ob_get_contents(); ob_clean(); return $revalue; }

回到code.php上来

//模板标签的执行结果已经保存起来了,须要咱们本身去显示出来 
echo $pv->GetResult();

总结一下和三种模板标签和代码执行有关的的PHP代码执行标签的用法

1. 编译式标签:
{dede:php php代码 /}{dede:php} php代码 {/dede:php}
2. 解释式标签 {dede:tagname runphp='yes'}   php代码 {/dede:tagname}
3. 视图类标签 {dede:php} php代码 {/dede:php}

黑客要利用模板类的漏洞进行代码执行,所会使用的模板标签就是这三种

4. 针对模板解析底层代码的Hook Patch对CMS漏洞修复的解决方案

全部模板类相关的漏洞都有一个共同的特色,就是代码执行,而在模板引擎中,进行代码执行的底层文件是比较集中的,咱们能够针对某几个特定的文件进行Hook Patch,检测流经其中的数据是否包含敏感关键字,而从从底层来防护这种模板类漏洞,固然从原则上,CMS的其余漏洞也是能够采起相同的思路,这里面咱们要作的就是对这些漏洞进行分类,从而找出漏洞的共同点

我但愿在本文的研究中作出一些试探性的尝试,同时也但愿引起你们的共同思考,对目前CMS漏洞的修复除了单纯地针对某个文件去作针对性的修复,而后每次ODAY爆发,再急忙到官网删去下补丁(或者站长本身就是程序员,本身作手工patch),这样带来的问题就是补丁的修复具备滞后性,若是能从根源上去思考漏洞的成因,在代码层的底部作一个总览性、归类性的防护,是否是能更好地解决目前CMS漏洞的发生呢?

从目前的状况来看,个人思考结果是,能够在两种模板引擎的解析函数中进行Hook,就能够达到目的了,由于视图类模板复用了解释式模板引擎的模板解析代码,因此也包含在这两个

dedetag.class.php -> ParseTemplet dedetemplate.class.php -> ParseTemplate

咱们能够在其中的关键代码位置Hook上这个函数

function find_tag_payload($tagbody) { $express = "/<\?(php){0,1}(.*)/i"; if (preg_match($express, $tagbody)) { die("Request Error!"); } }

咱们来作一些尝试

1. dedetag.class.php -> ParseTemplet

在匹配、提取模板标签的位置作Hook

..
$cAtt->SetSource($attStr); 
if($cAtt->cAttributes->GetTagName()!='') { $this->Count++; $CDTag = new DedeTag(); $CDTag->TagName = $cAtt->cAttributes->GetTagName(); $CDTag->StartPos = $sPos; $CDTag->EndPos = $i; $CDTag->CAttribute = $cAtt->cAttributes; $CDTag->IsReplace = FALSE; $CDTag->TagID = $this->Count; $this->find_tag_payload($innerText); $CDTag->InnerText = $innerText; $this->CTags[$this->Count] = $CDTag; } .... function find_tag_payload($tagbody) { $express = "/<\?(php){0,1}(.*)/i"; if (preg_match($express, $tagbody)) { die("Request Error!"); } }

2. dedetemplate.class.php -> ParseTemplate

在if、php标签的地方都作Hook

...
if(preg_match("/^if[0-9]{0,}$/", $ttagName)) { $cAtt->cAttributes = new TagAttribute(); $cAtt->cAttributes->count = 2; $cAtt->cAttributes->items['tagname'] = $ttagName; $cAtt->cAttributes->items['condition'] = preg_replace("/^if[0-9]{0,}[\r\n\t ]/", "", $attStr); $this->find_tag_payload($innerText); $innerText = preg_replace("/\{else\}/i", '<'."?php\r\n}\r\nelse{\r\n".'?'.'>', $innerText); } /* 1. php标记 2. 注意到preg_replace的$format参数最后有一个"i",表明执行正则替换的同时,并"不"进行代码执行,只是简单地将标签内的内容翻译为等价的PHP语法 */ else if($ttagName=='php') { $cAtt->cAttributes = new TagAttribute(); $cAtt->cAttributes->count = 2; $cAtt->cAttributes->items['tagname'] = $ttagName; $this->find_tag_payload($attStr); $cAtt->cAttributes->items['code'] = '<'."?php\r\n".trim(preg_replace("/^php[0-9]{0,}[\r\n\t ]/", "",$attStr))."\r\n?".'>'; } ... function find_tag_payload($tagbody) { $express = "/<\?(php){0,1}(.*)/i"; if (preg_match($express, $tagbody)) { die("Request Error!"); } }

这样作好Hook Patch以后,咱们使用dede的一个颇有名的模板类执行漏洞进行测试

http://ha.cker.in/1006.seo

http://www.i0day.com/1403.html

这是一个利用注入漏洞向数据库打入模板执行rootkit,而后再触发模板执行,从而进行写磁盘GETSHELL

 

访问

http://localhost/dede5.7/plus/mytag_js.php?aid=1

 

攻击被成功地防护住了。我以为这有点相似于堡垒主机的思惟方式,将复杂多变的上层漏洞风险集中到相对较少数量的底层代码逻辑上,在全部代码流都必须流经的关键点作攻击检测,从而从根本上防护一些已知、甚至未知的CMS漏洞攻击。

目前这种方法还处在完善中,也但愿搞CMS、WEB漏洞攻防的朋友能分享一些更好的思路,代码的安全问题的路还很长。

今天的文章就到这里了,下一步调研一下DEDECMS的其余类型的漏洞,但愿能将这些漏洞进行归类,找出一些通用性的修复方案

相关文章
相关标签/搜索