XSS跨站脚本攻击基础

CookieSession

客户端的cookie

在http协议的特点文章中我们介绍了http的每一次请求都是独立的,协议对于事务处理没有记忆能力,所以在后续数据传输需要前面的信息的时候,例如需要登录的网页,信息必须重传,这样非常的繁琐。cookie可以识别用户,实现持久会话。

cookie是服务器发送到用户浏览器并保存在本地的一小块数据,一般不超过4kb,它会在浏览器下次向同一服务器在发起请求时被携带并发送到服务器上。包括会话型cookie持久型cookie会话型cookie储存在临时储存中,关闭浏览器的时候就会消失,而持久型cookie储存在硬盘中。但是持久型的cookie并非永久的,也会在一段时间之后失效。

代码语言:javascript
复制
GET /home/other/data/weatherInfo?city=%E4%B9%9D%E6%B1%9F&indextype=manht&_req_seqid=0xaa4e1afd0028b863&asyn=1&t=1660183623070&sid= HTTP/1.1
Accept: text/plain, */*; q=0.01
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
Cookie: BIDUPSID=119642CD178BFA21589E1CB22D5CA9A7; PSTM=1659494222; BAIDUID=119642CD178BFA2112CD19D23E02A3D4:FG=1; BD_UPN=12314753; BA_HECTOR=008ka121a1208h2l2h8m58dv1hf7bh617; ZFY=sJnijftb:Bjr6fSMibAhSFO1sZ:AqUSVSzic:C; BDUSS_BFESS=i1DcM2toQ0h-Q093Skc5YlkybmlYUnkteVlSZ1lpcPeHRqSUFBQUFBJCQAAAAAAAAAAAEAAAAXz79~tPLG37j2urrX1tXmxNEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALOu82KzrvNiT; BDRCVFR[5ig7pqb-tu6]=mk3SLVN4HKm; BD_HOME=1; H_PS_PSSID=
Host: www.baidu.com
Referer: https://www.baidu.com/?tn=49055317_36_hao_pg
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
X-Requested-With: XMLHttpRequest
sec-ch-ua: ".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"

cookie用于告知服务器两个请求是否来自同一浏览器,如保持用户的登录状态。它可以用于:

  • 会话状态管理(比如用户登录、购物车、游戏分数或者其他需要记录的信息)
  • 浏览器行为跟踪(比如跟踪分析用户行为等)

cookie的组成:

  1. Name/Value:设置Cookie的名称及相对应的值,对于认证Cookie,Value值包括Web服务器所提供的访问令牌。
  2. Expires:设置Cookie的生存期。有两种存储类型的Cookie:会话性与持久性。Expires属性缺省时,为会话型Cookie,仅保存在客户端内存中,并在用户关闭浏览器时失效;持久型Cookie会保存在用户的硬盘中,直至生存期到或用户直接在网页中单击“注销”等按钮结束会话时才会失效。
  3. Path:定义了Web站点上可以访问该Cookie的目录。
  4. Domain:指定了可以访问该 Cookie 的 Web 站点或域。Cookie 机制并未遵循严格的同源策略,允许一个子域可以设置或获取其父域的 Cookie。当需要实现单点登录方案时,Cookie 的上述特性非常有用,然而也增加了 Cookie受攻击的危险,比如攻击者可以借此发动会话定置攻击。因而,浏览器禁止在 Domain属性中设置.org、.com 等通用顶级域名、以及在国家及地区顶级域下注册的二级域名,以减小攻击发生的范围。
  5. Secure:指定是否使用HTTPS安全协议发送Cookie。使用HTTPS安全协议,可以保护Cookie在浏览器和Web服务器间的传输过程中不被窃取和篡改。该方法也可用于Web站点的身份鉴别,即在HTTPS的连接建立阶段,浏览器会检查Web网站的SSL证书的有效性。但是基于兼容性的原因(比如有些网站使用自签署的证书)在检测到SSL证书无效时,浏览器并不会立即终止用户的连接请求,而是显示安全风险信息,用户仍可以选择继续访问该站点。由于许多用户缺乏安全意识,因而仍可能连接到Pharming攻击所伪造的网站。
  6. HTTPOnly :用于防止客户端脚本通过document.cookie属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改。但是,HTTPOnly的应用仍存在局限性,一些浏览器可以阻止客户端脚本对Cookie的读操作,但允许写操作;此外大多数浏览器仍允许通过XMLHTTP对象读取HTTP响应中的Set-Cookie头。

实际上cookie就是一些字符串信息,这些信息放在客户端的计算机上,用于客户端计算机和服务器之间传递信息。我们可以使用 alert(typeof document.cookie)来检测。由于不同的浏览器对Cookie的解析不同,所以Cookie不能跨浏览器存储,也就是说在chrome中登录的网页,在firefox中不会存储登录的信息。

在JavaScript中可以通过 document.cookie 来读取或设置这些信息,由于cookie 多用在客户端和服务器之间传递信息,所以除了JavaScript之外,服务器端的语言如PHP也可以存取cookie。

由于cookie最终都是以文件形式存放在客户端,所以查看和修改cookie都是十分方便的,所以常被作为XSS的攻击对象。

cookie操作实例

接下来用一个实例来展示Cookie的工作原理

如下图所示,我们在chrome浏览器登陆了百度账号。

通过chrome的扩展插件cookie editor导出该网页的cookie,这样cookie就在我们的粘贴板上了。

粘贴下来,发现是一串json字符串。

JSON教程

代码语言:javascript
复制
[
    {
        "domain": "www.baidu.com",
        "expirationDate": 166198279,
        "hostOnly": true,
        "httpOnly": false,
        "name": "BD_UPN",
        "path": "/",
        "sameSite": null,
        "secure": false,
        "session": false,
        "storeId": null,
        "value": "12314753"
    },
    {
        "domain": ".baidu.com",
        "expirationDate": 160221.753011,
        "hostOnly": false,
        "httpOnly": false,
        "name": "BAIDUID",
        "path": "/",
        "sameSite": null,
        "secure": false,
        "session": false,
        "storeId": null,
        "value": "119642CD178D23E02A3D4:FG=1"
    },
    {
        "domain": ".baidu.com",
        "expirationDate": 1975594281.308069,
        "hostOnly": false,
        "httpOnly": true,
        "name": "BDUSS_BFESS",
        "path": "/",
        "sameSite": "no_restriction",
        "secure": true,
        "session": false,
        "storeId": null,
        "value": "i1DcHRPdkMtM2toQ0h-Q093Skc5YlkybmlYUnkteVlSZ1lPeHRqSUFBQUFBJCQAAAAAAAAAAAEAAAAXz79~tPLG37j2urrX1tXmxNEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzrvNiT"
    },
    {
        "domain": ".baidu.com",
        "expirationDate": 1691672997.869722,
        "hostOnly": false,
        "httpOnly": false,
        "name": "ZFY",
        "path": "/",
        "sameSite": "no_restriction",
        "secure": true,
        "session": false,
        "storeId": null,
        "value": "sJnijfqrf2I9EhSFO1sZ:AqUSVSzic:C"
    },
    {
        "domain": ".baidu.com",
        "expirationDate": 1660315286,
        "hostOnly": false,
        "httpOnly": false,
        "name": "BA_HECTOR",
        "path": "/",
        "sameSite": null,
        "secure": false,
        "session": false,
        "storeId": null,
        "value": "058585a425088i1hfa58n17"
    },
    {
        "domain": ".baidu.com",
        "expirationDate": 3877868.752971,
        "hostOnly": false,
        "httpOnly": false,
        "name": "BIDUPD",
        "path": "/",
        "sameSite": null,
        "secure": false,
        "session": false,
        "storeId": null,
        "value": "119642CD178589E1CA9A7"
    },
    {
        "domain": "www.baidu.com",
        "hostOnly": true,
        "httpOnly": false,
        "name": "BD_HOME",
        "path": "/",
        "sameSite": null,
        "secure": false,
        "session": true,
        "storeId": null,
        "value": "1"
    },
    {
        "domain": ".baidu.com",
        "hostOnly": false,
        "httpOnly": false,
        "name": "BDRCVFR[5igtu6]",
        "path": "/",
        "sameSite": null,
        "secure": false,
        "session": true,
        "storeId": null,
        "value": "mk3SLVN4HKm"
    },
    {
        "domain": ".baidu.com",
        "hostOnly": false,
        "httpOnly": false,
        "name": "H_PS_PSSID",
        "path": "/",
        "sameSite": null,
        "secure": false,
        "session": true,
        "storeId": null,
        "value": ""
    },
    {
        "domain": ".baidu.com",
        "expirationDate": 3807868.7994,
        "hostOnly": false,
        "httpOnly": false,
        "name": "PSTM",
        "path": "/",
        "sameSite": null,
        "secure": false,
        "session": false,
        "storeId": null,
        "value": "165922"
    }
]

打开firefox浏览器,此时我们并没有登录百度账号。

打开firefox的扩展插件cookie editor,注意要与前面使用的是同一插件。将刚才复制下来的Cookie导入,然后刷新网页。

然后发现firefox上也登陆了百度账号。

服务端的session

什么是session

除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,也就是说session是存储在服务器内存中的。session使用上比Cookie简单一些,相应的也增加了服务器的存储压力。

客户端只保存sessionidcookie中,而不会保存sessionsession销毁只能通过invalidate或超时(默认30分钟),关掉浏览器并不会关闭session

session的读取

Session对应的类为javax.servlet.http.HttpSession类。 每个来访者对应一个Session对象,所有该客户的状态信息都保存在这个Session对象里。 Session对象是在客户端第一次请求服务器的时候创建的。 Session也是一种key-value的属性对,通过getAttribute(Stringkey)setAttribute(String key,Objectvalue)方法读写客户状态信息。 Servlet里通过request.getSession()方法获取该客户的Session

代码语言:javascript
复制
HttpSession session = request.getSession();
//设置属性
session.setAttribute("name","xx");
//读取属性
session.getAttribute("name");
//移除属性
session.removeAttribute("name");
//设置有效期,单位为秒,-1代表永不过期
session.setMaxInactiveInterval(1000);
//使其失效
session.invalidate();
代码语言:javascript
复制
session_start(); 
// 服务端创建session
$_SESSION['username'] = $username;
// 服务端写入session值
if(isset($_SESSION['USERNAME'])){};
// 服务网段校验session
session_destory();
// 服务端销毁session

JavaScript操作cookie

基本语法

获取:document.cookie; 设置:document.cookie="username=xxx"; 修改:将修改之后的cookie覆盖原cookie 删除:设置过期时间早于当前的时间

获取不同的cookie

将这个包含了分号及空格的字符串使用 split() 方法按分号分隔转换为一个字符串数组,然后再对这个字符串数组进行遍历即可得到每个名/值对,对这个名/值对再次使用 split() 方法按等号分隔转换为一个包含名称和值的数组,就可以得到指定 cookie 名称的值了。

代码语言:javascript
复制
document.cookie = "username=xxx; age=19";
var ar = document.cookie.split(';');
for(var i = 0; i < ar.length; i++){
     var arr = ar[i].split('=');
     alert(arr[1]);
}

设置cookie有效时间

代码语言:javascript
复制
document.cookie = "name=xxx;expires="+字符串格式的时间;

实例:

代码语言:javascript
复制
var oDate = new Date(); // 当前时间
oDate.setDate(oDate.getDate()+10); // 访问页面后的10天过期
// 设置cookie的有效时间,时间为字符串格式
document.cookie = 'username=abc;expires='+oDate.toGMTString();

删除cookie

cookie的有效时间设置为过去的某个时间即可

代码语言:javascript
复制
var oDate = new Date(); // 当前时间
oDate.setDate(oDate.getDate()-1); // 访问页面的前一天
document.cookie = 'username=abc;expires='+oDate.toGMTString();

汇总

代码语言:javascript
复制
//获取指定名称的cookie值
function getCookie(key){
	var ar = document.cookie.split(';');
	for(var i = 0; i < ar.length; i++){
		var arr = ar[i].split('=');
		if(arr[0] == key){
			return unescape(arr[1]);//对编码后的内容进行解码
		}           
	}
}    
//设置cookie,同时设置cookie的有效时间
function setCookie(key,value,t){
	var oDate = new Date();
	oDate.setDate(oDate.getDate()+t);
	//使用escape()对内容进行编码
	document.cookie = key+'='+escape(value)+';expires='+oDate.toGMTString();         
}    
//移除cookie
function removeCookie(key){
	setCookie(key,'',-1);
}

XSS

了解了上面的内容之后,我们就知道了可以利用web页面的漏洞,巧妙插入一些恶意代码,当用户访问页面的时候,代码就会执行,这个时候就达到了攻击的目的,例如获取用户的cookie、私密网页内容、会话和获取更高权限。 这就是跨站脚本攻击(Cross-Site Scripting,XSS),属于代码注入的一种类型。XSS漏洞的根源在于,没有很好的区分开代码和数据,导致攻击者可以利用系统的缺陷,构造输入,进而在系统上执行任意代码。 这些恶意网页程序通常是JavaScript,但也包括JavaVBScriptActiveXFlashHTML。 XSS漏洞有两种类型,反射型存储型

XSS之反射型

反射型XSS通常是指恶意代码未被服务器存储,每次触发漏洞的时候都将恶意代码通过GET/POST方式提交,然后触发漏洞。 反射型XSS将用户输入的内容作为代码让浏览器执行达到攻击目的,一般需要让用户访问攻击者构造的URL。这种类型的攻击只发生在客户端上,并且需要从带有恶意脚本参数的特定URL进入,所以也称为非持久型XSS。

输入的内容被拼接到HTML内容中时,有时被输出到一些特殊的位置,如标签属性、JavaScript变量的值,此时可以构造输入,闭合标签或者语句来实现恶意代码的引入。

例题

题目来源:CTFHUB

随便输入一串字符,查看源码。

尝试输入一串JavaScript代码:<script>alert('xss')</script>

发现存在XSS漏洞。

前往XSS Platform,创建一个项目。

将这段脚本插入,提交后复制url。

将url粘贴到下方的输入框,然后发送。

前往XSS Platform查看,发现flag在cookie中。

XSS之存储型

攻击者向服务器注入一段恶意代码,代码存储到服务器的数据库中。每当用户访问服务器当中带有恶意代码的数据时,服务器将响应用户,返回给用户一个带有该恶意代码的页面,恶意代码就会执行。(如留言板等场景)由于恶意代码是储存在服务器中的,能够持续攻击访问改页面的用户,所以这类XSS漏洞也称为持久型XSS漏洞,它的影响和攻击性要远远大于反射型XSS漏洞。

靶场实操

  • 靶场搭建参考pikachu靶场环境搭建。

进入靶场>存储型XSS。

发现了一个留言板,上传'"<script>

发现我们的输入直接拼接到<p>元素中,<p>元素中是可以写入JavaScript代码的。同时发现没有任何过滤,所以输入一段特制的JavaScript代码。

输入<script>alert(/xss/)</script>,弹出窗口,执行成功。每次刷新都会重新弹出,说明这段代码储存到了服务器数据库中,每次访问页面的时候都会执行该代码。

XSS之DOM型

DOM(Document Object Model)。 DOM是一个平台和语言都中立的接口,可以使程序和脚本能够动态访问和更新文档的内容、结构以及样式。

当页面到达浏览器时浏览器会为页面创建一个顶级的Document object文档对象,接着生成各个子文档对象,每个页面元素对应一个文档对象,每个文档对象包含属性、方法和事件。

DOM将前端HTML代码化为一个树状结构,方便程序和脚本能够轻松的动态访问和更新这个树状结构的内容、结构以及样式,且不需要经过服务端。

DOM型XSS可以在前端通过渲染来完成数据的交互,自己就可以完成数据的输入输出,不与服务器产生交互。

客户端的JavaScript脚本程序可以通过DOM来动态修改页面内容,从客户端获取DOM中的数据并在本地执行。基于这个特性,就可以利用JavaScript脚本来实现XSS漏洞的利用。

靶场实操

进入先随便传一个字符串,查看what do you see?处的源代码。

发现我们上传的字符经过前端JavaScript的渲染的部分源代码如下。可知点击按钮时调用了domxss()函数。

代码语言:javascript
复制
<input id="text" name="text" type="text" value="">
<input id="button" type="button" value="click me!" onclick="domxss()">
<div id="dom">
    <a href="dom">what do you see?</a>
</div>

domxss()函数如下所示。

代码语言:javascript
复制
function domxss(){
	var str = document.getElementById("text").value;
 	document.getElementById("dom").innerHTML = "<a href='"+str+"'>what do you see?</a>";
}
                

JavaScript渲染的过程:

首先通过<input id="text" name="text" type="text" value="">将我们输入的dom赋予给text。 调用domxss()函数,var str = document.getElementById("text").value;dom赋值给strdocument.getElementById("dom").innerHTML = "<a href='"+str+"'>what do you see?</a>";获取id="dom"的元素,并在元素中插入<a href='"+str+"'>what do you see?</a>,即<a href="dom">what do you see?</a>

所以这里存在不经过服务器的DOM XSS,接下来我们可以构造payload利用这个DOM XSS漏洞。

构造payload产生闭合:' onclick="alert(/xss/)">或者1' onclick="alert(/xss/)">。之后前端渲染过程在赋值处理中造成闭合形成一个新的标签再次解析。

查看源码发现拼接成功。

点击即可执行插入的代码。

XSS的防御&总结

XSS漏洞存在的根本原因是,对URL中的参数和用户输入提交给web服务器的内容没有进行充分的过滤。如果我们能够在web程序中,对用户提交的URL中的参数,和提交的所有内容,进行充分的过滤,将所有的不合法的参数和输入内容过滤掉,那么就不会导致在用户的浏览器中执行攻击者自己定制的脚本。 虽然充分完全的过滤是不存在的,攻击者有着各种各样神奇的绕过方法让你措手不及。例如各种对URL参数的编码,例如escapeencodeURIencodeURIComponent16进制10进制8进制等等。但是输入过滤还是会拦截相当一部分XSS攻击。

XSS防御的总体思路是:对输入(和URL参数)进行过滤,对输出进行编码

对提交的所有内容进行过滤,对url中的参数进行过滤,对动态输出到页面的内容进行html编码,使脚本无法在浏览器中执行。XSS的防御是非常复杂的。流行的浏览器都内置了一些对抗XSS的措施,比如Firefox的CSP,Noscript扩展,IE8内置的XSS Filter等,还有前文描述客户端的cookie提到的HttpOnly。除了浏览器,网站也需要寻找可行的防御方案来保护用户不被XSS攻击。