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的其餘類型的漏洞,但願能將這些漏洞進行歸類,找出一些通用性的修復方案

相關文章
相關標籤/搜索