【青训营】-Web开发的安全之旅

两个角度看web安全

假如你是一个hacker——攻击者

跨站脚本攻击(Cross-Site Scripting XSS)

xss主要攻击方式是想方设法的把恶意的脚本语言放进我们的页面中

image-20210906112331388.png

主要利用的是我们盲目的新人用户提交的内容

二是我们直接把字符串读出来生成dom结构

image-20210906112345704.png

XSS的一些特点

  • 通常很难从UI上感知(暗地执行脚本)
  • 窃取用户信息(cookie/token)
  • 绘制UI(例如弹窗),诱骗用户点击/填写表单

XSS demo

public async submit(ctx) {
    const { content,id } = ctx.request.body
    //没有对content过滤
    await db.save({
        content,
        id
    })
}
​
public async render(ctx){
    const { content } = await db.query({
        id:ctx.query.id
    })
    //没有对content过滤
    ctx.body = `<div>${content}</div>`
}
复制代码

这个demo是模仿接受用户请求,然后把用户请求的内容存进数据库中,render接口是从数据库中读取内容,返回给HTML

这两次操作都没有对用户提交的内容进行任何过滤

如果我是攻击者,就可以在我提交的内容content中做手脚

//提交的时候
fetch("/submit",{
    body:JSON.stringfy({
        id:"1",
        content:`<script>alert("XSS");</script>`
    })
})
​
//读取
ctx.body = `
    <div>
        <script>alert("XSS");</script>
    </div>
`
复制代码

那么数据把我的content内容返给HTML的时候,页面就会出现“XSS”的弹窗

这就是最简单的XSS的攻击方式

存储型XSS (Stored XSS)

刚才演示的demo就属于存储型XSS攻击方式,就是把恶意代码存储在数据库中

存储型XSS有以下几个特点

  • 恶意脚本被存在数据库中
  • 访问页面-> 读取数据 == 被攻击
  • 危害最大,对全部用户可见

image-20210906113915255.png

举个栗子:

某天你下了班,开开心心打开爱奇艺准备追剧追综艺,如果爱奇艺被XSS黑了,就会有恶意的脚本来到你打开的爱奇艺网站上,窃取你的用户信息,用户名和密码,如果你是一个爱奇艺会员,那么很开心的告诉你,你的会员账号和密码就被被窃取,放在电商网站上低价出售。(这就是之前某宝上面大量低价会员的来源)

反射型XSS(Reflected XSS)

反射型的XSS就不会设计数据库,是直接从url上进行攻击

image-20210906114401114.png

举个栗子

image-20210906114427862.png

现在有一个URL,URL上的参数被攻击者设计成了一段恶意脚本 ,那么在服务端,会在URL上读取参数,这样恶意代码又攻进来了~!

public async render(ctx) {
    const {param} = ctx.query
    ctx.status = 200
    ctx.body = `<div>${param}</div>`
}
复制代码

基于DOM的XSS攻击方式(DOM-based XSS)

image-20210906114931104.png

基于DOM的XSS攻击会直接略过服务器,直接在浏览器上完成攻击,接着看栗子

image-20210906115031062.png

还是相同的URL,上一个栗子是在服务端读取参数,这个可以直接在浏览器中读取参数

const content = new URL(location.href).searchParam.get("param")
const div = document.crateElement("div")
​
//恶意脚本注入
div.innerHTML = content
document.body.append(div)
复制代码

基于mutation的XSS攻击(mutation-based XSS)

角度最刁钻的攻击方式

image-20210906115421097.png

接着举栗子:

image-20210906115639244.png

这是一个看上去没什么异常的代码片段,一个noscript标签里面有个p标签,p里面有title属性,大多数的防御XSS软件都会予以通过,但是由于浏览器对DOM的特殊优化,在Chrome浏览器中上面代码片段就会优化成下面这样

image-20210906115828647.png

是不是很神奇,img标签的src属性是个随意的字符串,onerror就会触发,XSS攻击成功,离谱

跨站伪造请求(Cross-site request forgery CSRF)

实际上就是用户不知情的情况下,利用用户的权限,伪造用户请求来窃取或者修改用户的敏感信息

image-20210906120009274.png

举个栗子:

假如有一封垃圾邮件或者垃圾短信,里面有一个恶意链接B,点进去之后,恶意的页面B里面包含了一个请求A,这时候虽然你没有点击请求A,但是B会自动帮你请求,这时候A请求就会带着你进入到B页面的cookie等个人信息,从而达到访问A服务器的目的

image-20210906120420258.png

所以所有的未知的链接一定要慎点,即使你想着你就进去看看,不乱点,也会在不知情的情况下被窃取信息访问别的服务器

构造get请求(CSRF-GET)

<a href = "https://blank.com/transfer?to=hacker&amount=100">点我抽奖</a>
复制代码

上面这个主动的请求,需要用户主动点击;下面这个就是在用户访问页面的时候就会自动发起的请求,攻击者创建一个看不见的img标签,img的src属性就是恶意请求,从而达到用户一访问页面就会发送请求的目的

<img style = "dispaly:none" src="https://blank.com/transfer?to=hacker&amount=100" />
复制代码

构造post请求(CSRF-beyond GET)

攻击者可以伪造form表单发送post请求,从post发送任何自己想要发送的数据

<form action = "https://blank.com/transfer?to=hacker&amount=100" method="POST">
    <input name="amount" value="10000000000" type="hidden">
    <input name="to" value="hacker" type="hidden">
</form>
复制代码

注入攻击(Injection)

注入攻击一般是sql注入,在请求上会有sql的参数,这个参数就会被攻击者利用

image-20210906121409332.png

举个栗子:

这是一个服务端接口,服务端接口会从请求中读取字段,以字符串的形式拼接到sql查询语句

public async renderForm(ctx) {
    const { username,form_id } = ctx.query
    const result = await sql.query(`
        SELECT a, b c FROM table
        WHERE username = ${username}
        AND form_id = ${form_id}
    `)
    ctx.body = rederForm(result)
}
复制代码

攻击者可以构造一个请求,传递一个username,username的内容可以是任意字符串,下面这个就能达到删库的目的

image-20210906133858193.png

注入攻击(Injection)不止于SQL

  • CLI
  • OS command
  • Server-Side Request Forgery(SSRF) 服务端伪造请求;严格来说,SSRF不是Injection,但是原理类似

demo — 执行

public async convertVideo(ctx){
    const{ video,option } = ctx.request.body
    exec(`convert-cli ${video} -o ${options}`)
    ctx.body = "ok"
}
复制代码

这里调用了一个虚拟的视频格式转换的脚手架,脚手架可以接收一些参数,参数允许用户在请求中自定义;就是上面的options字段

攻击者就可以把options字段拼接成攻击语句,用&符拼接rm命令,到服务端就执行删除命令

fetch("/api",{
    method:"POST",
    body:JSON.stringfy({
        options:`' && rm - rf xxx`
    })
})
​
​
//到服务端就拼接成这样const command = `covert-cli video -o && rm -rf xxx`
复制代码

demo2 — 读取、修改

image-20210906135558956.png

刚才讲的是执行,还有读取和修改。攻击者可以对你的服务端敏感文件读取和修改,比如密码文件,ssh文件,nginx配置。如果攻击者可以攻击更改Nginx配置,就可以更改为右边这段代码,会把你服务端的所有的请求流量代理到一个第三方的网址

SSRF demo

这里有一个webhook接口,允许用户自定义了一个callback字段,相当于每次调用者该接口都会向用户自定义的callback发出请求;但是,如果callback是一个内网的URL,就会把内网的配置暴露在外界,非常可怕

public async webhook(ctx){
    //callback可能是内网URL
    //e.g http://secret.com/get_employ_playrolls
    ctx.body = await fetch(ctx.query.callback)
}
复制代码

服务拒绝(Denial of Service DoS)

攻击者会通过某种方式(构造特定请求),导致服务器资源被显著消耗,来不及响应更多请求,导致请求挤压进而服务器崩溃

插播:正则表达式 — 贪婪模式

贪婪模式就是在重复匹配时会不会用到问号,有问号就是匹配一个就行,没有问号就是全部匹配 [?] vs [no?] : 满足“一个”即可 vs 尽量多

const greedyReExp = /a+/    //有多少匹配多少
const nonGreedyReExp = /a+/? //有一个就可以
const str = "aaaaaaa";
​
console.log(str.match(greedyRegExp)[0])   //aaaaaaa
console.log(str.match(nonGreedyRegExp)[0])   //a
复制代码

基于正则的DoS ReDoS

贪婪:n次不行? n-1次再试试? 回溯

image-20210906141139708.png

上面左边可以看到,匹配‘ab’这两个一起的字符,1组,5组都能非常快的匹配到,但是突然在非常多的ab的最后加了一个a,这个时候正则就有点懵了,就会触发后面的回溯

Distributed DDoS

短时间内,来自大量的将是设备的请求流量,服务器不能及时完成全部请求,导致请求堆积,进而雪崩效应,无法响应心情求

不搞复杂的,量大就完事了

台湾有一个小的组织,会通过这种方式攻击我们大陆的很多游戏小公司,前端时间的taptap上的一个新开了一天的游戏,就因为这个攻击停服了,非常可恨,主要还没有特别好的方法避免这个

image-20210906194933340.png

中间人攻击

image-20210906195247465.png

中间人攻击,顾名思义就是攻击者在客户端和服务端担当了第三者的角色,服务端以为自己在和客户端沟通,其实是蒙着脸的中间人,客户端以为自己发请求返回的是服务端,其实也是中间人。中间人之所以能成功在中间横插一脚,利用的是以下三点:

  • 信息明文传输
  • 信息篡改不可知
  • 对方身份未验证

简单来说就是客户端和服务端两个马大哈,没有安全意识,被中间人截胡,实在是不可取

假如你是一个开发者——防御者

XSS

xss的防御方法核心理念就是永远不信任用户提交的内容

image-20210906195627616.png

永远不会把用户提交的东西直接转成DOM

image-20210906195731844.png

现在大部分的主流框架都默认防御XSS,如果你非要自己操作DOM,谷歌也为我们提供了一些安全的操作DOM的方法

如果用户需求不讲武德,非要动态生成DOM怎么办。在这里教你两招

1.对用户上传的string进行过滤

2.用户上传svg,因为svg里面也是可以嵌套script脚本代码的,所以也要对svg进行过滤

3.用户自定义的链接,a标签一定要慎重,最好不要让用户可以自定义链接

4.自定义的样式也是重灾区,一定要慎重,因为background中也可以嵌套URL

image-20210906200425741.png

上面就是一个栗子,只有当用户点击了月收入大于10k的时候,才会触发background中的URL,泄露用户信息,专宰肥羊。。

Content Security Police(CSP)

image-20210906200715483.png

csp就是规定好一些安全的域名,然后统一排斥其他陌生域名,避免陌生链接的跳转,主流的配置就是在服务器的响应头部配置

Content-Security-Police: script-src 'seft'   同源
Content-Security-Police: script-src 'seft' http://domain.com
复制代码

上面第一个配置就是只允许同源,第二个是加了允许的域名

前端可以在HTML中写浏览器meta信息

<meta> http-equiv = "Content-Srcurity-Police" content = "script-src self"</meta>
复制代码

CSRF的防御

我们知道,csrf是通过伪造请求,那么我们可以通过限制请求来进行防御,伪造请求就判定为异常来源

image-20210907195505699.png

image-20210907195609973.png

除了origin + referer还有没有其他的方式来限定呢?

token

在我们平时浏览网页时,如果是一些需要登录的网站,那么在登录的时候,浏览器发送给服务器我们的账号密码,然后服务器通过我们的账号密码生成一个唯一标志服token,这个token是通过特殊加密的方式,不能反推出用户的账号密码。那么服务器返回的这个token就会在我们的每一个网站内的网页间跳转携带,从而每次加载页面都可以验证token,验证不通过就会拒绝请求,保证我们的信息唯一。通过token就可以有效防止伪装的信息

image-20210907200004219.png

iframe攻击

iframe类似于一个透明的遮罩,当用户点击页面的button时,实际上是点击的那个透明的iframe,这个时候就能拿到用户的token,请求地址也是合法的

image-20210907200129006.png

这个就可以通过下面的一个选项,页面能不能通过iframe,deny就是不允许,通过设置也可以限制iframe的攻击

避免用户信息被携带SameSite Cookie

image-20210907200547977.png

如果csrf利用的信息在cookie中的话,可以不让攻击者发送cookie,SameSite Cookie由此孕育而生

image-20210907200645839.png

它的本质就是只有同源的请求才能发送当前源的cookie,跨域请求不能带上当前域的cookie

上古时期基本都是none,就是不限制,现在基本都是strict,最严格模式

就是当用户去请求一个URL访问页面的时候,首次导航中,就算这个cookie是同源的,在strict模式下,这里的cookie也是不允许携带的,只有当首次导航结束,页面再次发送请求的时候,才可以。主要是为了避免敏感操作跳过二次确认

防御CSRF的正确姿势

CSRF主要是攻击各个接口,我们不能一个个接口去写,主要是在一个中间件中统一处理

image-20210907201118484.png

Injection防御方式

首先防御数据库被注入

image-20210907201143733.png

使用prepared的含义就是提前将SQL语句提前编译一遍,这样的话在传入查询参数的时候,就可以依赖数据库自带的特性防御注入攻击

其他的注入防御主要注意以下几点

image-20210907201252252.png

DDOS的防御

日常基本就是运维的同学去防御,开发基本不做

image-20210907201348619.png

传输层 — 中间人防御

HTTPS协议

image-20210907201441705.png

HTTPS的一些特性

image-20210907201523887.png

http协议后面再写,写文章实在是太累了QAQ

尾声

  • 安全无小事

  • 使用的依赖(npm package,甚至是NodeJS)可能成为最薄弱的环节

    • left-pad事件
    • eslint-scope事件
    • event-stream事件
  • 保持学习心态

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