两个角度看web安全
假如你是一个hacker——攻击者
跨站脚本攻击(Cross-Site Scripting XSS)
xss主要攻击方式是想方设法的把恶意的脚本语言放进我们的页面中
主要利用的是我们盲目的新人用户提交的内容
二是我们直接把字符串读出来生成dom结构
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有以下几个特点
- 恶意脚本被存在数据库中
- 访问页面-> 读取数据 == 被攻击
- 危害最大,对全部用户可见
举个栗子:
某天你下了班,开开心心打开爱奇艺准备追剧追综艺,如果爱奇艺被XSS黑了,就会有恶意的脚本来到你打开的爱奇艺网站上,窃取你的用户信息,用户名和密码,如果你是一个爱奇艺会员,那么很开心的告诉你,你的会员账号和密码就被被窃取,放在电商网站上低价出售。(这就是之前某宝上面大量低价会员的来源)
反射型XSS(Reflected XSS)
反射型的XSS就不会设计数据库,是直接从url上进行攻击
举个栗子
现在有一个URL,URL上的参数被攻击者设计成了一段恶意脚本 ,那么在服务端,会在URL上读取参数,这样恶意代码又攻进来了~!
public async render(ctx) {
const {param} = ctx.query
ctx.status = 200
ctx.body = `<div>${param}</div>`
}
复制代码
基于DOM的XSS攻击方式(DOM-based XSS)
基于DOM的XSS攻击会直接略过服务器,直接在浏览器上完成攻击,接着看栗子
还是相同的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)
角度最刁钻的攻击方式
接着举栗子:
这是一个看上去没什么异常的代码片段,一个noscript标签里面有个p标签,p里面有title属性,大多数的防御XSS软件都会予以通过,但是由于浏览器对DOM的特殊优化,在Chrome浏览器中上面代码片段就会优化成下面这样
是不是很神奇,img标签的src属性是个随意的字符串,onerror就会触发,XSS攻击成功,离谱
跨站伪造请求(Cross-site request forgery CSRF)
实际上就是用户不知情的情况下,利用用户的权限,伪造用户请求来窃取或者修改用户的敏感信息
举个栗子:
假如有一封垃圾邮件或者垃圾短信,里面有一个恶意链接B,点进去之后,恶意的页面B里面包含了一个请求A,这时候虽然你没有点击请求A,但是B会自动帮你请求,这时候A请求就会带着你进入到B页面的cookie等个人信息,从而达到访问A服务器的目的
所以所有的未知的链接一定要慎点,即使你想着你就进去看看,不乱点,也会在不知情的情况下被窃取信息访问别的服务器
构造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的参数,这个参数就会被攻击者利用
举个栗子:
这是一个服务端接口,服务端接口会从请求中读取字段,以字符串的形式拼接到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的内容可以是任意字符串,下面这个就能达到删库的目的
注入攻击(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 — 读取、修改
刚才讲的是执行,还有读取和修改。攻击者可以对你的服务端敏感文件读取和修改,比如密码文件,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次再试试? 回溯
上面左边可以看到,匹配‘ab’这两个一起的字符,1组,5组都能非常快的匹配到,但是突然在非常多的ab的最后加了一个a,这个时候正则就有点懵了,就会触发后面的回溯
Distributed DDoS
短时间内,来自大量的将是设备的请求流量,服务器不能及时完成全部请求,导致请求堆积,进而雪崩效应,无法响应心情求
不搞复杂的,量大就完事了
台湾有一个小的组织,会通过这种方式攻击我们大陆的很多游戏小公司,前端时间的taptap上的一个新开了一天的游戏,就因为这个攻击停服了,非常可恨,主要还没有特别好的方法避免这个
中间人攻击
中间人攻击,顾名思义就是攻击者在客户端和服务端担当了第三者的角色,服务端以为自己在和客户端沟通,其实是蒙着脸的中间人,客户端以为自己发请求返回的是服务端,其实也是中间人。中间人之所以能成功在中间横插一脚,利用的是以下三点:
- 信息明文传输
- 信息篡改不可知
- 对方身份未验证
简单来说就是客户端和服务端两个马大哈,没有安全意识,被中间人截胡,实在是不可取
假如你是一个开发者——防御者
XSS
xss的防御方法核心理念就是永远不信任用户提交的内容
永远不会把用户提交的东西直接转成DOM
现在大部分的主流框架都默认防御XSS,如果你非要自己操作DOM,谷歌也为我们提供了一些安全的操作DOM的方法
如果用户需求不讲武德,非要动态生成DOM怎么办。在这里教你两招
1.对用户上传的string进行过滤
2.用户上传svg,因为svg里面也是可以嵌套script脚本代码的,所以也要对svg进行过滤
3.用户自定义的链接,a标签一定要慎重,最好不要让用户可以自定义链接
4.自定义的样式也是重灾区,一定要慎重,因为background中也可以嵌套URL
上面就是一个栗子,只有当用户点击了月收入大于10k的时候,才会触发background中的URL,泄露用户信息,专宰肥羊。。
Content Security Police(CSP)
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是通过伪造请求,那么我们可以通过限制请求来进行防御,伪造请求就判定为异常来源
除了origin + referer还有没有其他的方式来限定呢?
token
在我们平时浏览网页时,如果是一些需要登录的网站,那么在登录的时候,浏览器发送给服务器我们的账号密码,然后服务器通过我们的账号密码生成一个唯一标志服token,这个token是通过特殊加密的方式,不能反推出用户的账号密码。那么服务器返回的这个token就会在我们的每一个网站内的网页间跳转携带,从而每次加载页面都可以验证token,验证不通过就会拒绝请求,保证我们的信息唯一。通过token就可以有效防止伪装的信息
iframe攻击
iframe类似于一个透明的遮罩,当用户点击页面的button时,实际上是点击的那个透明的iframe,这个时候就能拿到用户的token,请求地址也是合法的
这个就可以通过下面的一个选项,页面能不能通过iframe,deny就是不允许,通过设置也可以限制iframe的攻击
避免用户信息被携带SameSite Cookie
如果csrf利用的信息在cookie中的话,可以不让攻击者发送cookie,SameSite Cookie由此孕育而生
它的本质就是只有同源的请求才能发送当前源的cookie,跨域请求不能带上当前域的cookie
上古时期基本都是none,就是不限制,现在基本都是strict,最严格模式
就是当用户去请求一个URL访问页面的时候,首次导航中,就算这个cookie是同源的,在strict模式下,这里的cookie也是不允许携带的,只有当首次导航结束,页面再次发送请求的时候,才可以。主要是为了避免敏感操作跳过二次确认
防御CSRF的正确姿势
CSRF主要是攻击各个接口,我们不能一个个接口去写,主要是在一个中间件中统一处理
Injection防御方式
首先防御数据库被注入
使用prepared的含义就是提前将SQL语句提前编译一遍,这样的话在传入查询参数的时候,就可以依赖数据库自带的特性防御注入攻击
其他的注入防御主要注意以下几点
DDOS的防御
日常基本就是运维的同学去防御,开发基本不做
传输层 — 中间人防御
HTTPS协议
HTTPS的一些特性
http协议后面再写,写文章实在是太累了QAQ
尾声
-
安全无小事
-
使用的依赖(npm package,甚至是NodeJS)可能成为最薄弱的环节
- left-pad事件
- eslint-scope事件
- event-stream事件
-
保持学习心态