这里记录工作中遇到的技术点,以及自己对生活的一些思考,周三或周五发布。

封面图

第八十二期:Node和前端安全(漏洞检测)_加载

检测依赖漏洞

node生态的完善以及各种包的出现,让我们可以专注于业务逻辑的开发。但是对于这个庞大的生态,大量的依赖树被开发和使用,我们也需要关注一下这些依赖中有可能存在的问题和漏洞。

怎么检测我们的依赖包中是否存在漏洞或着其他问题呢?我们需要用到auditjs这个依赖包。

我们简单创建一个项目:

mkdir app
cd app
npm init -y
npm install express
npm install --save-dev auditjs

然后在package.json中添加脚本

"scripts":{
"text":"echo \"error:no test specified\" && exit",
"audit":"auditjs ossi"
}

然后直接执行:

npm run audit

我们可以看到下面的内容

第八十二期:Node和前端安全(漏洞检测)_3c_02

auditjs会对我们所有的依赖包进行递归检测,标注出有漏洞的包。

这个工具经常被用在ci/cd的持续集成中,在项目发布时对项目做一次全面的扫描。

当然,借助其他工具, 我们甚至可以不安装依赖包就可以对其进行扫描。

比如我们可以使用snky这个开源扫描平台。

npm install -g snyk

然后执行:

snyk wizard

他会让我们先去授权,授权就是注册一个账号,进行验证

第八十二期:Node和前端安全(漏洞检测)_3c_03

授权过后我们可以随意测试一个框架, 只需要执行:

snyk test name

第八十二期:Node和前端安全(漏洞检测)_依赖包_04

限制核心模块的使用

某些核心模块儿的功能非常强大,而且我们经常使用一些第三方的模块,但是我们对这些第三方模块中使用了哪些核心模块并不清楚。

这有可能会触发一些意向不到的问题。比如用户可以通过在依赖包的命令中输入我们不知到的命令在控制我们的服务。

这种情况虽然不常见,但是一旦出现问题,将会是非常严重的问题,值得我们去研究并且解决这个问题。

我们创建一个index.js

module.exports = function(name){
require.cache[name] = {}
Object.defineProperty(require.cache[name],'exports',{
get:()=>{
throw Error(`the ${name} module is restricted`)
}
})
}

然后创建example.js

const restrict = require('./')
restrict('child_process')

const cp = require('child_process')

然后我们执行:

node example.js

我们会看到一个报错

第八十二期:Node和前端安全(漏洞检测)_JavaScript_05

​Error: the child_process module is restricted​

index.js这种写法利用了Node的模块加载算法,在试图加载内置模块之前,它会检查加载的模块缓存(通过require.cache访问它)的名称空间。我们用该名称空间覆盖这个缓存,并且使用Object.defineProperty对exports键进行属性定义,在访问时抛出对应的错误。

web框架的强化

通常情况下,我们创建node应用,比如我们创建用express-generator创建一个node应用。

# 安装脚手架
npm install -g express-generator
# 生成应用
express my-app
# 进入项目文件
cd my-app
# 安装依赖
npm install
# 启动应用
npm start

然后我们发起请求

curl -i http://localhost:3000

在终端中我们可以看到如下信息:

第八十二期:Node和前端安全(漏洞检测)_前端_06

这时候我们可以看到x-powered-by:expressd等头部信息。

我们接下来安装helmet包,然后修改app.js

npm install --save helmet

app.js中加入

var helmet = require('helmet')
// helmet 强化
app.use(helmet());

这时候重启应用,重新发送请求,会发现变成了下面的结果。

第八十二期:Node和前端安全(漏洞检测)_依赖包_07

仔细看会发现移除了x-powered-by,但是添加了几个新的以x打头的信息。

helmet是一个中间件,它提供了一系列的默认安全措施,当然我们可以手动进行配置。

上面的图中,移除了x-powered-by 隐藏了是哪种框架提供的服务。

x-dns-prefetch-control:off 告诉浏览器不要去预取页面中的dns记录。dns预解析虽然对客户端的性能提升有一定的好处,但是也有可能会产生一些其他的不可预测的问题,因为解析后发起的请求获取的内容我们不可控,有可能会泄露一些我们自身的数据或其他信息。

X-Frame-Options: SAMEORIGIN。告诉浏览器我们的界面是否可以在iframe标签中展示,可以防止我们的网站被加载到别人的网站中。

其他的有兴趣的话可以自己查一下。

跨站脚本攻击 xss

跨站点脚本攻击是最常见、最严重的攻击之一。XSS漏洞可能会严重危害用户和声誉,但漏洞很容易出现,尤其是当我们没有意识到这一方面的时候。

举个例子,我们用node起个简单的服务:

const express = require('express')
const app =express()

app.get('/',(req,res)=>{
const {prev = '',handoverToken='',lang='en'} = req.query

pretendDbquery((err,status)=>{
if(err){
res.sendStatus(500)
return
}
res.send(`
<div>你好:我是terrence</div>
<div id="stat">
${status}
</div>
<div>
<a href="${prev}${handoverToken}/${lang}">back to control hq</a>
</div>
`)
})
})

function pretendDbquery(cb){
const status = '我现在很好'
cb(null,status)

}

app.listen(3000)

正常情况下,我们发起请求,界面显示:

第八十二期:Node和前端安全(漏洞检测)_前端_08

但是,加入我们请求参数中带有可执行的代码,比如:

http://localhost:3000/?prev=%22%3E%3Cscri&handoverToken=pt%3Estat.innerHTML=%22%E5%93%88%E5%93%88%E5%93%88,%E8%A2%AB%E5%8A%AB%E6%8C%81%E4%BA%86%E5%90%A7%3C/br%3E%E8%B5%B6%E5%BF%AB%E4%BA%A4%E9%92%B1%3C/br%3E%E8%B5%B6%E5%BF%AB%E4%BA%A4%E9%92%B1%3C/br%3E%E8%B5%B6%E5%BF%AB%E4%BA%A4%E9%92%B1%3C/br%3E%E8%B5%B6%E5%BF%AB%E4%BA%A4%E9%92%B1%3C/br%3E%E8%B5%B6%E5%BF%AB%E4%BA%A4%E9%92%B1%3C/br%3E%E8%B5%B6%E5%BF%AB%E4%BA%A4%E9%92%B1%22%3C&lang=script%3E%3Ca%20href=%22.

这时候再发请求,会看到如下内容

第八十二期:Node和前端安全(漏洞检测)_依赖包_09

xss攻击有两种类型,持久型和反射型。持久型指的是攻击者可以在我们应用的持久层比如服务器上植入代码,当然也有可能在浏览器或者数据中植入代码。反射型的则依赖于和服务器进行交互,让服务器返回的数据带有其他内容。

这个例子很明显是个反射型的例子。我们在请求中拼接了可以执行的js脚本,然后服务返回时渲染到浏览器上直接执行,从而对用户界面进行劫持。

想要解决这个问题,我们需要对发送给服务器的字符进行encode,从而降低被攻击的风险。

最后

  • 公众号《JavaScript高级程序设计》
  • 公众号内回复”vue-router“ 或 ”router“即可收到 VueRouter源码分析的文档。
  • 回复”vuex“ 或 ”Vuex“即可收到 Vuex 源码分析的文档。

全文完,如果喜欢。