XSS系列一:什么是XSS攻击

1,285 阅读3分钟

1.关于XSS攻击

XSS全称Cross Site Scripting(跨站脚本),为了与“CSS”区别,就使用XSS作为简称。

XSS攻击指恶意用户在html中注入含恶意的JavaScript代码或者恶意的HTML代码。在其他用户浏览该页面时,浏览器会直接编译处理所有代码包括恶意代码,从而作出损害用户利益的攻击。

下面通过一个简单的前后端数据交互例子进行叙述,先贴上代码

//页面代码
<!DOCTYPE html>
<html>
  <head>
  <meta charset="utf-8" />
  <title>存储型XSS测试</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
  <style type="text/css">
    .demo{
    width: 450px;
    padding: 1.5rem;
    margin-right: 0;
    margin-left: 0;
    }
  </style>
</head>
  <body>
  <div class="container">
    <div class="demo">
      <div class="d-flex justify-content-around mb-3">
        <input type="text" class="form-control mr-3" placeholder="输入添加信息">
        <button type="button" class="btn btn-primary flex-shrink-0" onclick="addMessage()">添加</button>
      </div>
      <div>
        <table class="table table-bordered">
          <thead>
          <tr>
            <th>信息</th>
          </tr>
          </thead>
          <tbody></tbody>
        </table>
      </div>
    </div>
  </div>
  <script type="text/javascript">
  const input=document.querySelector('input')
  const tbody=document.querySelector('tbody')
  function getMessage(){
    fetch('http://127.0.0.1:9500/message',{mode:'cors'})
    .then(res=>res.json())
    .then(res=>{
      let html=res.reduce((cur,el)=>{
        cur+=`
        <tr>
        <td>${el}</td>
        </tr>
        `
        return cur
      },'')
      tbody.innerHTML=html
    })
  }
  getMessage()
  function addMessage(){
    fetch('http://127.0.0.1:9500/message',{
      mode:'cors',
      method:'POST',
      body:JSON.stringify({message:input.value}),
      headers: {
      'content-type': 'application/json'
      },
    })
    .then(()=>getMessage())
  }
  </script>
  </body>
</html>

//后端代码node.js
const express=require('express')
const bodyParser = require("body-parser")
const app=express()
const router=express.Router()
const jsonParser = bodyParser.json()

let message=['John']

router.get('/',function(req,res){
    res.status(200).json(message)
})

router.post('/',jsonParser,function(req,res){
    message.push(req.body.message)
    res.status(200).end()
})
//中间件解决跨域问题
function cors(req,res,next){
    res.header('Access-Control-Allow-Origin','*')
    res.header('Access-Control-Allow-Headers','Content-Type')
    res.header('Access-Control-Allow-Methods','PUT,POST,GET,DELETE,OPTIONS')
    next()
}

app.use(cors)
app.use('/message',router)

app.use(function(req,res){
    res.status(404).end()
})

app.listen(9500,function(){})

交互效果如下:

此时,如果我们插入带恶意的html片段,如:

<img src="whatever" onerror=alert('attack')>

因为src中是一个无效链接,加载失败必然会执行onerror中的回调函数,因此则会出现以下效果:

这就是其中一种XSS攻击的原理的呈现。一般恶意用户会通过onerror回调函数插入script如下:

<img src="whatever" onerror="javascript:window.el=document.createElement('script'),window.el.src='wrong.js',document.head.appendChild(window.el)">

通过引入来自外部的恶意脚本,在恶意脚本里就可以进行恶意操作,例如:

  1. 窃取用户Cookie:document.cookie收集用户cookie或者sessionStorage然后发送到别的服务器里
  2. 监听用户行为:通过登陆的键入字符串窃取用户信息
  3. 插入恶意广告:插入恶意iframe或者监听鼠标点击跳转页面

**拓展:**有人会问为什么onerror函数中不这么写

javascript:document.head.innerHTML+="<script src='wrong.js'></script>"

那是因为HTML5中指定不执行由innerHTML插入的script标签。

2.XSS攻击的分类

1.存储型XSS攻击

开头的例子就是存储型XSS攻击,存储型的攻击主要步骤是:

  1. 恶意用户把恶意代码片段提交到服务器的数据库中
  2. 普通用户请求网站,网站把包含恶意代码的数据加载从页面中加载出来
  3. 恶意代码开始运行

2.反射型XSS攻击

有些页面交互中存在用户输入数据后,页面响应时会原封不动地返回这个数据到页面上。例如...还是先贴代码吧:

//后端node.js代码
const express=require('express')
const app=express()

app.get('/main/:keyword',function(req,res){
    res.send(`搜索内容:${req.params.keyword}`)
})

app.use(function(req,res){
    res.status(404).end()
})

app.listen(9501)

正常交互效果:

带XSS攻击的交互效果:

3.DOM型XSS攻击

DOM型XSS攻击与反射型XSS攻击类似,都是通过把恶意代码作为参数纳入到url中,诱发用户点击。不过区别在于反射型XSS攻击的页面是通过后端的模板引擎渲染的,而DOM型XSS攻击中的页面是通过前端取出URL中的参数里的恶意代码后,然后通过document.write或者innerHTML等方法渲染到页面上的。

3.XSS攻击预防措施

总结说,一般XSS攻击中注入的恶意代码有两种类型:

  • 在img或者iframe等标签中,后面接着代码,可能以javascript:开头,。

**拓展:**注意上述两种写法不区分大小写,“javascript:”也可以写成“JaVaScRiPt:”,因为XSS的恶意代码一般都是插入在html标签里,而HTML不区分大小写。

1.关键字转义:

我们可以在后端对"<",">"等符号进行转译成HTML能试别的转义符,如下:

  1. ”<“转译成“<”

  2. “>”转译成“>”

  3. 引号“"”转译成“"”

转译过程最好在后端进行,如果在前端进行转义,恶意用户可以通过postman等工具直接发起请求绕过前端转义过滤。

**存在缺点:**有时候前端如果需要对数据进行处理,例如统计字符串长度等,要再次把特殊字符转义回原来的格式。而且也不能针对以javascript:开头的恶意代码。

2.长度限制

对于输入的信息,进行长度限制(前后端都要对提交信息进行校验)。一般XSS嵌入的恶意代码无论是那种类型,都需要相当的长度才能实施。尽管有些代码可以拆分,例如开头举例说明的储存型XSS攻击中的恶意img标签:

<img src="whatever" onerror="javascript:window.el=document.createElement('script'),window.el.src='wrong.js',document.head.appendChild(window.el)">

可以把他拆分成三个短的信息:

<img src="whatever" onerror="javascript:window.el=document.createElement('script')">
<img src="whatever" onerror="javascript:window.el.src='wrong.js'">
<img src="whatever" onerror="javascript:document.head.appendChild(window.el)">

甚者可以把代码再拆分成好几段字符串,然后最后拼起来用eval运行。但仍然可以增加XSS攻击的难度。

3.Content Security Policy

CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。---阮一峰的网络日志

我们可以通过两种方法来开启CSP:

(1)后端配置响应头content-security-policy,例如

//中间件配置csp
function csp(req,res,next){
    res.header('Content-Security-Policy',"default-src 'self';script-src 'self'")
    next()
}
app.use(csp)

发出请求时在响应头里可看到:

(2)在网页的head设置meta标签,如下:

<meta http-equiv="Content-Security-Policy" content="default-src 'self' ;">

在开头的例子里,我们可以如上述设置。把default-src设置为’self‘,代表只能加载自己当前域名下的资源。这样子可以防止开头说的引入外部恶意脚本的XSS的攻击:

<img src="whatever" onerror="javascript:window.el=document.createElement('script'),window.el.src='wrong.js',document.head.appendChild(window.el)">

4.设置cookie的httpOnly属性

若cookie的httpOnly属性为true,则浏览器的cookie不能通过js脚本获取,即不能通过document.cookie获取。这样子可以防止恶意代码获取用户的cookie。

2.拓展

XSS系列二:基于vue搭建的网站如何防范XSS攻击