下一个泡泡玛特工程师是谁呢?

收集一些自己用到的prompt

2023-04-05 · 1 min read

机械键盘相关参数

机械键盘相关参数记录

2023-04-05 · 1 min read

Web 前端性能优化

常用的web前端性能优化手段

2023-03-19 · 3 min read

Blender常用快捷键

自己经常用到的一些快捷键记录

2022-12-11 · 1 min read

Docker 常用命令

精简 Docker 常用命令

2022-11-06 · 1 min read

项目管理

项目管理相关知识

2022-11-06 · 1 min read

JS沙箱sandbox的各种实现

我们把Js隔离机制常常称作沙箱

2022-09-18 · 1 min read

Amazing!Solid 比react还react

今天来介绍2个amazing的东东

2022-09-12 · 1 min read

puppeteer应用

使用傀儡师来操作浏览器这个傀儡吧

2022-09-11 · 1 min read

做一个web termianl

前端react, 后端nodejs, 直接可用版web termianl

2022-07-17 · 1 min read

TypeScript里常用的工具类型

经常用到的工具类型,提取抽离出来,供以后复用

2022-06-26 · 1 min read

Vim大法好

想要丢掉鼠标,试试Vim

2022-06-26 · 1 min read

碧血丹心

无论时光如何沾染风霜,也永似红日光

2022-05-10 · 1 min read

chrome V8 引擎中的垃圾回收机制

V8引擎就是nodejs的发动机

2022-03-03 · 1 min read

如何开发一个cli

命令行交互界面是程序员必备的工具,如何开发一个呢?

2022-02-21 · 1 min read

Javascript中的哲学

道生一,一生二,二生三,三生万物

2022-02-20 · 1 min read

React Redux 实现 (Context 版)

React Redux 实现 (Context 版)

2022-02-16 · 1 min read

mobx-react 使用

虽然不常用,但是可以学一下

2022-02-16 · 1 min read

Less中的for和forEach循环

循环的使用是保持代码干燥和避免重复的好方法

2022-02-15 · 1 min read

Webpack Splitchunks 详解

webpack 优化

2022-02-15 · 1 min read

ssh-keygen命令详解

为ssh生成、管理和转换认证密钥

2022-02-15 · 1 min read

使用CURL发送POST请求

curl 是常用的命令行工具,用来请求 Web 服务器。

2022-02-15 · 1 min read

Webpack Plugin 开发

让我们来学一下如何开发一个webpack插件

2022-02-14 · 1 min read

JS 实现两个大数相加?

algo-adding-large-numbers

2022-02-10 · 1 min read

字典树 trie

字典树 trie

2022-02-09 · 1 min read

浏览器原理问题

浏览器原理问题

2022-01-11 · 1 min read

MacBook快速进入一个文件夹目录

mac如何快速进入一个文件夹

2021-10-26 · 1 min read

react合成事件

react-synthetic-event

2021-10-25 · 1 min read

mini webpack实现

通过babel核心来实现迷你版的webpack

2021-10-10 · 1 min read

设计模式

在程序设计中有很多实用的设计模式,而其中大部分语言的实现都是基于类

2021-10-10 · 1 min read

babel核心

babel核心介绍

2021-10-07 · 1 min read

React 15 和 React 16 的区别

react-15-16

2021-10-06 · 1 min read

React性能优化

浅谈react性能优化的方法

2021-10-05 · 1 min read

交通信号灯实现

如何用js来实现交通信号灯呢

2021-09-25 · 1 min read

内存管理

前端中的内存管理

2021-09-25 · 1 min read

前端安全

前端关于安全方面的知识

2021-09-25 · 2 min read

网络和并发

http各版本对于并发的支持,前端如何控制并发量?

2021-09-25 · 1 min read

跨域方法

总结了9种跨域方法

2021-09-24 · 1 min read

react virtualList 虚拟列表无限滚动实现

用react实现虚拟滚动

2021-09-16 · 1 min read

监控埋点方案

前端监控埋点方案

2021-09-16 · 1 min read

Mini useEffect实现

如何实现useEffect?

2021-09-11 · 1 min read

Mini useState 实现

我们来思考一下useState是怎么实现的呢?

2021-09-11 · 1 min read

React Fiber

react-fiber

2021-09-11 · 1 min read

React class组件和function组件异同

类组件和函数组件有何相同点有何不同点呢?

2021-09-11 · 1 min read

Lodash Get 实现

algo-lodash-get

2021-09-08 · 1 min read

手写reduce实现

algo-reduce

2021-09-08 · 1 min read

Hooks 原理概览

react-hooks

2021-09-05 · 1 min read

Hook原理——状态Hook

react-hook-state

2021-09-05 · 1 min read

手写Mini Redux实现

手写一个简易版的redux实现,包含了核心逻辑

2021-09-04 · 1 min read

legacy和concurrent模式

react-legacy-concurrent

2021-09-01 · 1 min read

react架构

总体 react 的核心可以用 ui=fn(state)来表示 3 大核心对象、3 大核心阶段、2 大工作循环 Scheduler(调度器): 排序优先级,让优先级高的任务先进行 reconcile Reconciler…

2021-09-01 · 2 min read

react核心api和jsx

为什么要有jsx,为什么会有虚拟bom

2021-09-01 · 1 min read

setState是同步的还是异步的

react-setstate-usestate

2021-08-31 · 1 min read

如何使用NodeJs创建HTTP服务?

如何使用NodeJs创建HTTP服务?

2021-08-17 · 1 min read

NodeJS 事件循环模型

nodejs-eventloop

2021-08-11 · 2 min read

Buffer

nodejs中的内存管理

2021-08-10 · 1 min read

微前端解决方案-qiankun

目前国内最好的微前端解决方案-qiankun

2021-08-10 · 1 min read

React Mini版实现(1)

学一门技术最好的方法就是做一个其玩具版的实现,我们来尝试实现一下react和react-dom最简单版本吧

2021-08-04 · 1 min read

brew安装

brew 是 MacOS 上的包管理工具,可以简化 macOS 和 Linux 操作系统上软件的安装。

2021-08-04 · 1 min read

CommonJS简易版实现

CommonJS我们经常用,如何实现一个简易版的commonJS呢?

2021-08-01 · 1 min read

极品透明Dashboard样式分享

一个极品透明Dashboard样式分享

2021-07-30 · 1 min read

Stream

nodejs中的流

2021-07-11 · 1 min read

NodeJS全局对象

JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。

2021-05-18 · 1 min read

如何部署Nodejs服务

如何快速的部署一个Nodejs服务到公网呢?

2021-05-12 · 1 min read

Events

events模块是node的核心模块之一,几乎所有常用的node模块都继承了events模块,比如http、fs等。

2021-05-11 · 1 min read

用JS绘制背景,让CSS直接使用 'background:paint(xxx)'

CSS对象新特性,新的background设置方式

2021-04-30 · 1 min read

Block Formatting Context 块级格式化上下文

可以将BFC看成是元素的一种属性,拥有了这种属性的元素就会使他的子元素与世隔绝,不会影响到外部其他元素

2021-04-17 · 1 min read

如何使用Nodejs来创建一个TCP/UDP服务?

如何使用Nodejs来创建一个TCP/UDP服务?

2021-04-17 · 1 min read

Mac使用tree生成目录结构

程序员经常会有需求,需要列出项目的结构树。Mac或者Linux下可以使用tree列出项目结构

2021-04-04 · 1 min read

常见算法

一些比较常见算法

2021-03-20 · 1 min read

前端缓存

对于性能优化离不开缓存

2021-02-28 · 1 min read

前端答疑

一些比较常见的问题

2020-09-02 · 1 min read

Nodejs 网络 & HTTP

nodejs-network

2020-08-14 · 1 min read

JavaScript AST 抽象语法树

源代码的抽象语法结构的树状表现形式

2020-08-02 · 1 min read

nodejs里面向切面编程的一种范式

在一些场景下我们可能需要一种面向切面的编程方式

2020-08-01 · 1 min read

各种JS模块化特性

AMD、CMD、CJS、ESM

2020-05-30 · 1 min read

手写Ajax实现

使用HMR一步步实现Ajax

2020-05-30 · min read

手写PromiseA+实现

如何自己实现promiseA+规范,手写一个promise实现

2020-04-30 · 1 min read

debug和内存泄露

nodejs的debug方法

2020-04-08 · 1 min read

Javascript prototype 原型链

js-prototype

2019-09-07 · 1 min read

this指针、作用域

this是在执行时动态读取上下文决定的,不是在定义时决定

2019-06-14 · 1 min read

call、apply、bind的极简实现

使用symbol实

2019-06-03 · 1 min read

CSS联合选择器区分列表元素个数不同所要求的不同样式

对于列表,在有些时候针对于不同个数的item会有不同的显示,比如col份数,用js固然可以,是否可以用css更简便的实现呢?

2019-04-30 · 1 min read

TypeScript基础

介绍TypeScript基础知识

2019-04-30 · 1 min read

JS中的变量提升

为什么js当时要这样设计

2019-03-07 · 1 min read

ES6之Class

关于ES6里的class, 我们有什么不知道的事?

2019-03-01 · 1 min read

HTTP详解

HTTP协议(HyperText Transfer Protocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传输协议

2019-02-17 · 2 min read

极简代码实现节流Throttle和防抖Debounce

使用各9行代码实现节流和防抖函数

2019-01-30 · 1 min read

Javascript 事件循环 EventLoop

js-eventloop

2018-09-07 · 1 min read

POST和GET区别

POST和GET区别是什么呢?

2018-08-14 · 1 min read

正则表达式

正则表达式一锅端

2018-07-30 · 1 min read

cloneDeep 深克隆实现

algo-clonedeep

2017-09-07 · 1 min read

webpack

engin-webpack

2017-09-07 · 1 min read

http1.1和http2.0有什么区别

http1.1和http2.0有什么区别

2017-06-14 · 1 min read

手写EventEmitter事件巴士

咱们来手写实现一个EventEmitter事件巴士

2017-01-10 · 1 min read

OOP 面向对象编程

对象是什么?为什么要面向对象?

2016-05-08 · 1 min read
Stay hungry & Stay foolish
战歌
16
The Reluctant Warrior
Immediate Music
To Glory
Two Steps From Hell
Victory
Two Steps From Hell
Empire of Angels
Thomas Bergersen
Serenata Immortale
Immediate Music
Cornfield Chase
Hans Zimmer
Tennessee
Hans Zimmer
He's a Pirate
Martin Ermen
Rise
Hans Zimmer
On Thin Ice
Hans Zimmer
Angels Will Rise
Twisted Jukebo
When It All Falls Down
Audiomachine
Icarus
Ivan Torrent
Star Sky - Instrumental
Two Steps From Hell
亡灵序曲
L
Up Is Down
Hans Zimm
回到首页

前端安全

袁官东
September 25th, 2021 · 2 min read
图:Mako Tsereteli

总览

浏览器相关:

  1. XSS
  2. CSRF
  3. HTTPS
  4. CSP (内容安全策略, 可以禁止加载外域代码, 禁止外域提交等等)
  5. HSTS (强制客户端使用HTTPS与服务端建立连接)
  6. X-Frame-Options (控制当前页面是否可以被嵌入到Ifrmae中)
  7. SRI (subresource intergrity 子资源完整性, 前端可以用webpack-subresource-integrity插件, 在每个script上添加hash值, 校验加载的资源是否和当时打包生成的一致)
  8. Referrer-Policy (控制referer的携带策略)

Node(服务端)相关

  1. 本地文件操作相关:路径拼接导致的文件泄露
  2. ReDOS
  3. 时序攻击
  4. ip origin referrer等request headers的校验(在做爬虫应用的时候对此会有深刻的体会)

XSS

Cross-site scripting, 跨站脚本, 通常简称为XSS. 

为什么简写为XSS而不是CSS? 因为CSS被广泛应用于样式的称呼里, 而Cross意味交叉, X字母正好是符合交叉的含义, 所以简称为XSS。

说白了就是攻击者想尽一切办法将可执行代码注入到网页中, 而恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。

外在表现上, 都有哪些攻击场景?

  1. 评论区植入js代码(即可输入的地方植入js代码)
  2. url上拼接js代码
  • TIPS: 有点同学可能觉得在这种场景下, 用户能输入的代码长度有限, 根本构不成什么威胁? 然而攻击者是可以通过引入外部脚本来实现复杂攻击的.

具体从技术角度上分析, 都有哪些xss攻击的类型呢?

1、 存储型 Server

  • 场景:常见于带有用户保存数据的网站功能,攻击者通过可输入区域来注入恶意代码, 如论坛发帖、商品评论、用户私信等。
  • 攻击步骤:
    1. 攻击者将恶意代码提交到目标网站的数据库中
    2. 用户打开目标网站时,服务端将恶意代码从数据库中取出来,拼接在HTML中返回给浏览器(因为用户之间是可以相互看到帖子、评论等的)
    3. 用户浏览器在收到响应后解析执行,混在其中的恶意代码也同时被执行
    4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户行为,调用目标网站的接口执行攻击者指定的操作。

2、 反射型 Server

与存储型的区别在于,存储型的恶意代码通过可输入区域, 存储在数据库中,而反射型的恶意代码拼接在URL上。 由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。

  • 场景:通过 URL 传递参数的功能,如网站搜索、跳转等。
  • 攻击步骤:
    1. 攻击者构造出特殊的 URL,其中包含恶意代码。
    2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
    3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
    4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站的接口执行攻击者指定的操作。

3、 Dom型 Browser

DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。

  • 场景:通过 URL 传递参数的功能,如网站搜索、跳转等。
  • 攻击步骤:
    1. 攻击者构造出特殊的 URL,其中包含恶意代码。
    2. 用户打开带有恶意代码的 URL。
    3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
    4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站的接口执行攻击者指定的操作。

简单模拟一下Dom型XSS攻击?

  1. index.html
1<!DOCTYPE html>
2<html lang="zh">
3 <head>
4 <meta charset="UTF-8" />
5 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 <meta
7 name="viewport"
8 content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"
9 />
10 <title>XSS</title>
11 </head>
12 <body>
13 <a href="">跳转到新地址</a>
14 </body>
15 <script src="type-dom.js"></script>
16</html>
  1. type-dom.js
1const a = document.getElementsByTagName('a')[0];
2const queryObject = {};
3const search = window.location.search;
4search.replace(/([^&=?]+)=([^&]+)/g, (m, $1, $2) => (queryObject[$1] = decodeURIComponent($2)));
5
6a.href = queryObject.redirectUrl;
  1. 打开当前index.html, 添加参数redirectUrl访问

比如

redirectUrl=javascript:alert('哈哈哈笨蛋!!')

  1. 点击a链接发现已经被xss攻击了.

  2. 长度有限制

比如我们写这样一段脚本, 目的是创建一个script标签并且引入remote.js文件, 将这一串代码作为redirectUrl的值访问链接.

1var script = document.createElement('script');
2script.type = 'text/javascript';
3script.async = true;
4script.src = 'remote.js';
5var s = document.getElementsByTagName('script')[0];
6s.parentNode.insertBefore(script, s);

新建remote.js文件, 来尝试耗性能的代码.

1let count = 0;
2console.log(window.navigator.userAgent);
3
4while(count++ < 100) {
5 console.log('我要通过巨量运算把你搞崩溃')
6}
  1. 而如果我们网站的cookie里有重要的用户信息, 那么攻击者是否就可以通过document.cookie获取到了?\

比如我们在原来的网站脚本type-dom.js里设置一下cookie, 然后在攻击脚本remote.js里获取.

document.cookie="name=john12313";

console.log(document.cookie);

但是聪明的同学应该也想到了, 只要我们给重要的cookie设置了http-only, 就算被dom xss攻击了, 攻击者也造不成大的影响.

  1. 再来试试访问url就直接出发的xss攻击.

type-dom.js

1document.write(queryObject.name)

添加参数访问url, name=<script>window.alert(1)</script>

  1. 可以试一下这个网站, https://alf.nu/alert1

如何防范XSS攻击呢?

主旨:防止攻击者提交恶意代码,防止浏览器执行恶意代码

  1. 对数据进行严格的输出编码:如HTML元素的编码,JS编码,CSS编码,URL编码等等

    • 避免拼接 HTML;Vue/React 技术栈,避免使用 v-html / dangerouslySetInnerHTML
  2. CSP HTTP Header,即 Content-Security-Policy(不支持CSP的旧版浏览器可以设置X-XSS-Protection)

    • 增加攻击难度,配置CSP(本质是建立白名单,由浏览器进行拦截)
    • Content-Security-Policy: default-src ‘self’ -所有内容均来自站点的同一个源(不包括其子域名)
    • Content-Security-Policy: default-src ‘self’ *.trusted.com -允许内容来自信任的域名及其子域名 (域名不必须与CSP设置所在的域名相同)
    • Content-Security-Policy: default-src https://john.com -该服务器仅允许通过HTTPS方式并仅从john.com域名来访问文档

    可以做到很多事情,比如: 禁止加载外域代码,防止复杂的攻击逻辑。 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。

  3. 输入验证:比如一些常见的数字、URL、电话号码、邮箱地址等等做校验判断

  4. 开启浏览器XSS防御:Http Only cookie,禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。

  5. 验证码

CSRF

Cross-site request forgery, 跨站请求伪造.

攻击者诱导受害者进入恶意网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

攻击步骤

  1. 受害者登录 a.com,并保留了登录凭证(Cookie)
  2. 攻击者引诱受害者访问了b.com
  3. b.com 向 a.com 发送了一个请求:a.com/act=xx浏览器会默认携带a.com的Cookie
  4. a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求
  5. a.com以受害者的名义执行了act=xx
  6. 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作

攻击类型

  • GET型:如在页面的某个 img 中发起一个 get 请求

<img src="http://bank.example/withdraw?name=xxx&amount=xxxx" />

  • POST型:通过自动提交表单到恶意网站
1<form action="http://bank.example/withdraw" method=POST>
2 <input type="hidden" name="account" value="john" />
3 <input type="hidden" name="amount" value="10000" />
4 <input type="hidden" name="for" value="hacker" />
5</form>
6<script> document.forms[0].submit(); </script>
  • 链接型:需要诱导用户点击链接
1<a href="http://bank.example/withdraw?name=xxx&amount=xxxx" taget="_blank">
2错过再等一年!!!快来看看
3</a>

如何防范CSRF的攻击呢?

首先咱们通过上面的举例可以知道, CSRF一般都是发生在第三方域名, 攻击者也无法获取到Cookie信息, 只是可以利用浏览器机制去使用Cookie.

所以咱们可以针对这两点来看防范策略:

阻止第三方域名的访问

  1. Cookie SameSite

SameSite有3个值: Strict, Lax和None

  • Strict:浏览器会完全禁止第三方cookie。比如a.com的页面中访问 b.com 的资源,那么a.com中的cookie不会被发送到 b.com服务器,只有从b.com的站点去请求b.com的资源,才会带上这些Cookie
  • Lax:在跨站点的情况下,从第三方站点链接打开和从第三方站点提交 Get方式的表单这两种方式都会携带Cookie。但如果在第三方站点中使用POST方法或者通过 img、Iframe等标签加载的URL,这些场景都不会携带Cookie
  • None:任何情况下都会发送 Cookie数据
  1. 同源检测

通过检测request header中的origin referer等, 来确定发送请求的站点是否是自己期望中站点.

比如referer, 我们可以在服务端去判断referer是否来自可信域, 同样也可以做一些referer发送时的设置:

对于同源的链接和引用,会发送Referer,referer值为Host不带Path;跨域访问则不携带Referer。例如:aaa.com引用bbb.com的资源,不会发送Referer(服务端在接收到没有referer的请求时就不做响应)

这就提到了Referrer-Policy这个请求头. https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Referrer-Policy, 控制什么情况下应该携带/不携带referer

提交请求时附加额外信息

因为攻击者无法通过csrf来获取本域下的cookie等信息, 所以可以利用这一点来做防范.

  1. CSRF Token
  • 用户打开页面的时候, 服务器利用加密算法给当前用户生成一个ToKen
  • 每次页面加载时, 前端把获取到的Token加到所有的能发请求的html元素上, 比如form, a
  • 每次前端发起请求, 都携带Token参数
  • 服务端每次接收请求, 都校验Token的有效性.
  1. 双重Cookie
  • 用户访问网站的时候, 服务器向浏览器注入一个额外的cookie, 内容随便, 比如csrfcookie=johnxzxfasdfasfew
  • 每次前端发起请求, 都在请求上拼接上csrfcookie这个参数, 参数值就从cookie里获取
  • 服务端每次收到请求, 就去校验请求参数里的值和cookie里的值是否一致

但是这种方式安全性不如CSRF Token. 咱们来看一下原因:

  1. 如果前端域名和服务端域名不一样, 比如前端 fe.a.com, 后端 rd.a.com, 那如果服务端希望前端能拿到csrfcookie, 就只能把这个cookie设置到a.com域下, 并且不能设置为http-only
  2. 那么a.com的每个子域名就都可以获取到这个cookie
  3. 一旦某个子域名遭到xss攻击, cookie就很容易被窃取或者被篡改.
  4. 攻击者利用篡改或者窃取的csrfcookie, 就可以攻击fe.a.com了

Node(服务端)相关的安全问题

本地文件操作

比如我们提供一个静态服务, 通过请求的参数url来返回给用户或者前端想要的资源.

  1. 新建文件node/static-sever-dangerous.js
1const fs = require('fs');
2const http = require('http');
3const path = require('path');
4
5http
6 .createServer(function (req, res) {
7 const file = path.join(__dirname, 'static', req.url);
8
9 fs.readFile(file, function (err, data) {
10 if (err) {
11 res.writeHead(404, { "Content-Type": "text/plain;charset=utf-8" });
12 res.end('找不到对应的资源');
13 return;
14 }
15 res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8" });
16 res.end(data);
17 });
18 })
19 .listen(8080);
20 console.log('server listening on port 8080');
  1. 新建文件夹static, 里面随便放点文件, 提供给外界请求

比如 test.json

1{
2 "name": "john"
3}
  1. 启动服务, 本地请求url

http://localhost:8080/test.json

http://localhost:8080/test1.json

没有任何问题对吧? 存在的资源正常返回了, 不存在的资源返回404了.

  1. 请求一个不一样的url
  • http://localhost:8080/?/../../../questions.md

  • http://localhost:8080/?/../../../private.js

同级新建一个private.js, 里面写上一些私密信息

1// 这是一个私密文件

可以看到, 攻击者可以通过拼接相对路径, 一次次猜你项目的结构, 并且可以访问到你服务器上的各种资源!

  1. 怎么解决这个问题?

很多node框架都自带插件来屏蔽这个问题的发生

比如express.static, koa-static, 当然也有第三方包支持(resolve-path)

咱们用resove-path来解决一下这个问题.

1const fs = require('fs');
2const http = require('http');
3const path = require('path');
4// 引入resolve-path
5const resolvePath = require('resolve-path');
6
7http
8 .createServer(function (req, res) {
9 try {
10 // 先获取rootDir
11 const rootDir = path.join(__dirname, 'static');
12 // 调用resolvePath
13 const file = resolvePath(rootDir, req.url);
14
15 fs.readFile(file, function (err, data) {
16 if (err) {
17 // 把错误抛出去
18 throw err;
19 }
20 res.writeHead(200, { "Content-Type": "text/plain;charset=utf-8" });
21 res.end(data);
22 });
23 } catch(e) {
24 // catch住错误, 防止服务直接挂掉
25 console.log(e);
26 res.writeHead(404, { "Content-Type": "text/plain;charset=utf-8" });
27 res.end('找不到对应的资源');
28 }
29
30 })
31 .listen(8081); // 改成8081端口
32 console.log('server listening on port 8081');
  1. 可以看到resolve-path的源码对path做了严格的限制.

截图服务

比如咱们来实现一个简单的截图服务

  1. 安装好必要的npm包

yarn add puppeteer-chromium-resolver koa --registry=https://registry.npm.taobao.org

  1. 新建 screen-shot.js
1(async () => {
2 const PCR = require('puppeteer-chromium-resolver');
3 const stats = await PCR();
4
5 const browser = await stats.puppeteer
6 .launch({
7 headless: true,
8 args: ['--no-sandbox'],
9 executablePath: stats.executablePath,
10 })
11 .catch(function (error) {
12 console.log(error);
13 });
14
15 const Koa = require('koa');
16 const app = new Koa();
17
18 app.use(async ctx => {
19 const { url } = ctx.query;
20
21 // 这里演示不合理的Url校验
22 if (!url) {
23 ctx.body = 'Invalid url';
24
25 return;
26 }
27
28 ctx.set('Content-Type', 'image/png');
29
30 const page = await browser.newPage();
31 await page.goto(url, { waitUntil: 'networkidle2' });
32
33 ctx.body = await page.screenshot({ encoding: 'binary', type: 'png' });
34
35 await page.close();
36 });
37 app.listen(8083);
38
39 console.log('server listening on port 8083');
40 })();
  1. 访问url
  • http://localhost:8083/?url=http://localhost:8080/test.json

这里咱们传入url为之前启动的不安全的静态服务. 可以看到我们直接截图了对应的资源

  • http://localhost:8083/x?url=file:///etc/zshrc

这里咱们传入url为文件系统的zshrc, 可以发现我们直接截图了系统文件的内容.

ReDOS

新建redos.js, 分别看一下这三个例子的执行时间

1console.time('case-1');
2// 能够匹配成功
3/A(B|C+)+D/.test('ACCCCCCCCCCCCCCCCCCCCCCCCCCCCD');
4console.timeEnd('case-1');
5
6console.time('case-2');
7// 不能匹配成功
8/A(B|C+)+D/.test('ACCCCCCCCCCCCCCCCCCCCCCCCCCCCX');
9console.timeEnd('case-2');
10
11console.time('case-3');
12// 不能匹配成功
13/A(B|C+)+D/.test(`A${'C'.repeat(30)}X`);
14console.timeEnd('case-3');

可以看到, 当不能匹配成功的时候, 每多一个字符, 所消耗是时间都是指数增长的.

因为咱们服务器经常会有正则去匹配一些传入的参数, 所以攻击者就可以利用正在表达式的这个特性, 来一直占用服务器运算资源, 造成服务器宕机.

具体原理可以看这篇文章:https://snyk.io/node-js/connect

正则表达式一般情况下会去匹配第一种可能性, 比如一个正确的字符串 ACCCD, 那么直接匹配到最后发现成功了, 耗时就很短.

而比如ACCCX这样一个字符, 每当一次匹配不成功, 就会尝试回溯到上一个字符, 看看能不能有其他的组合来匹配到这个字符串.

比如刚才说的ACCCX这样一个字符串, 会去尝试匹配四种不同的”C”字母组合来与其他字母组合, 看是否符合条件.

  1. CCC
  2. CC+C
  3. C+CC
  4. C+C+C.

可以在写完正则后去这个网址测试一下 https://regex.rip/? 测试是否会遭到reDos.

时序攻击

这种攻击方式在咱们编码过程中可能很少见, 看下面这个例子

比如咱们要匹配接收的数组和咱们定义好的数组是否完全一致, 如果一致才可以进行之后的操作.

1function compareArray(realArray, userInputArrary) {
2 for (let i = 0; i < realArray.length; i++) {
3 if (realArray[i] !== userInputArray[i]) {
4 return false;
5 }
6 }
7 return true;
8}

这样写有没有什么问题? 逻辑上没有任何问题, 但是有安全问题.

因为我们如果判断到一个字符不相等, 就提前返回了!!

这给攻击者提供了一种方式, 就是根据服务器的响应时间来碰撞出realArray的值.

比如realArray = [2,3,6,1]

攻击者开始尝试 inputArray = [1,2] inputArray = [1,3]

发现这两种的响应时间几乎一致, 则可以认为第一个数字不是1

inputArray = [2, 2]

发现响应时间延长了, 则可以认为第一个数字是2.

长而久之, 就可以碰撞出真实的realArray了.

More articles from Favori 重剑

网络和并发

http各版本对于并发的支持,前端如何控制并发量?

September 25th, 2021 · 1 min read

跨域方法

总结了9种跨域方法

September 24th, 2021 · 1 min read
© 2016–2023 Favori 重剑
Link to $https://github.com/yuanguandongLink to $https://favori.zcool.com.cn/Link to $https://codepen.io/favori