【Step-By-Step】第 三 周

本周面试题一览:javascript

  • 什么是XSS攻击,XSS 攻击能够分为哪几类?咱们如何防范XSS攻击?
  • 如何隐藏页面中的某个元素?
  • 浏览器事件代理机制的原理是什么?
  • setTimeout 倒计时为何会出现偏差?

11. 什么是XSS攻击,XSS攻击能够分为哪几类?咱们如何防范XSS攻击?

1. XSS攻击

XSS(Cross-Site Scripting,跨站脚本攻击)是一种代码注入攻击。攻击者在目标网站上注入恶意代码,当被攻击者登录网站时就会执行这些恶意代码,这些脚本能够读取 cookie,session tokens,或者其它敏感的网站信息,对用户进行钓鱼欺诈,甚至发起蠕虫攻击等。css

XSS 的本质是:恶意代码未通过滤,与网站正常的代码混在一块儿;浏览器没法分辨哪些脚本是可信的,致使恶意脚本被执行。因为直接在用户的终端执行,恶意代码可以直接获取用户的信息,利用这些信息冒充用户向网站发起攻击者定义的请求。html

XSS分类

根据攻击的来源,XSS攻击能够分为存储型(持久性)、反射型(非持久型)和DOM型三种。下面咱们来详细了解一下这三种XSS攻击:前端

1.1 反射型XSS

当用户点击一个恶意连接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。Web服务器将注入脚本,好比一个错误信息,搜索结果等,未进行过滤直接返回到用户的浏览器上。java

反射型 XSS 的攻击步骤:git

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操做。

反射型 XSS 漏洞常见于经过 URL 传递参数的功能,如网站搜索、跳转等。因为须要用户主动打开恶意的 URL 才能生效,攻击者每每会结合多种手段诱导用户点击。github

POST 的内容也能够触发反射型 XSS,只不过其触发条件比较苛刻(须要构造表单提交页面,并引导用户点击),因此很是少见。web

若是不但愿被前端拿到cookie,后端能够设置 httpOnly (不过这不是 XSS攻击 的解决方案,只能下降受损范围)面试

如何防范反射型XSS攻击数据库

对字符串进行编码。

对url的查询参数进行转义后再输出到页面。

app.get('/welcome', function(req, res) {
    //对查询参数进行编码,避免反射型 XSS攻击
    res.send(`${encodeURIComponent(req.query.type)}`); 
});

1.2 DOM 型 XSS

DOM 型 XSS 攻击,实际上就是前端 JavaScript 代码不够严谨,把不可信的内容插入到了页面。在使用 .innerHTML.outerHTML.appendChilddocument.write()等API时要特别当心,不要把不可信的数据做为 HTML 插到页面上,尽可能使用 .innerText.textContent.setAttribute() 等。

DOM 型 XSS 的攻击步骤:

  1. 攻击者构造出特殊数据,其中包含恶意代码。
  2. 用户浏览器执行了恶意代码。
  3. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操做。

如何防范 DOM 型 XSS 攻击

防范 DOM 型 XSS 攻击的核心就是对输入内容进行转义(DOM 中的内联事件监听器和连接跳转都能把字符串做为代码运行,须要对其内容进行检查)。

1.对于url连接(例如图片的src属性),那么直接使用 encodeURIComponent 来转义。

2.非url,咱们能够这样进行编码:

function encodeHtml(str) {
    return str.replace(/"/g, '"')
            .replace(/'/g, ''')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;');
}

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

1.3 存储型XSS

恶意脚本永久存储在目标服务器上。当浏览器请求数据时,脚本从服务器传回并执行,影响范围比反射型和DOM型XSS更大。存储型XSS攻击的缘由仍然是没有作好数据过滤:前端提交数据至服务端时,没有作好过滤;服务端在接受到数据时,在存储以前,没有作过滤;前端从服务端请求到数据,没有过滤输出。

存储型 XSS 的攻击步骤:

  1. 攻击者将恶意代码提交到目标网站的数据库中。
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操做。

这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。

如何防范存储型XSS攻击:

  1. 前端数据传递给服务器以前,先转义/过滤(防范不了抓包修改数据的状况)
  2. 服务器接收到数据,在存储到数据库以前,进行转义/过滤
  3. 前端接收到服务器传递过来的数据,在展现到页面前,先进行转义/过滤

除了谨慎的转义,咱们还须要其余一些手段来防范XSS攻击:

1.Content Security Policy

在服务端使用 HTTP的 Content-Security-Policy 头部来指定策略,或者在前端设置 meta 标签。

例以下面的配置只容许加载同域下的资源:

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

前端和服务端设置 CSP 的效果相同,可是meta没法使用report

严格的 CSP 在 XSS 的防范中能够起到如下的做用:

  1. 禁止加载外域代码,防止复杂的攻击逻辑。
  2. 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
  3. 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。
  4. 禁止未受权的脚本执行(新特性,Google Map 移动版在使用)。
  5. 合理使用上报能够及时发现 XSS,利于尽快修复问题。

2.输入内容长度控制

对于不受信任的输入,都应该限定一个合理的长度。虽然没法彻底防止 XSS 发生,但能够增长 XSS 攻击的难度。

3.输入内容限制

对于部分输入,能够限定不能包含特殊字符或者仅能输入数字等。

4.其余安全措施

  • HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也没法窃取此 Cookie。
  • 验证码:防止脚本冒充用户提交危险操做。

点击查看更多

12.如何隐藏页面中的某个元素?

隐藏类型

屏幕并非惟一的输出机制,好比说屏幕上看不见的元素(隐藏的元素),其中一些依然可以被读屏软件阅读出来(由于读屏软件依赖于可访问性树来阐述)。为了消除它们之间的歧义,咱们将其归为三大类:

  • 彻底隐藏:元素从渲染树中消失,不占据空间。
  • 视觉上的隐藏:屏幕中不可见,占据空间。
  • 语义上的隐藏:读屏软件不可读,但正常占据空。

彻底隐藏

1.display 属性(不占据空间)
display: none;
2.hidden 属性 (不占据空间)

HTML5 新增属性,至关于 display: none

<div hidden> </div>

视觉上的隐藏

1.利用 position 和 盒模型 将元素移出可视区范围
  1. 设置 posoitionabsolutefixed,经过设置 topleft 等值,将其移出可视区域。(可视区域不占位)
position:absolute; left: -99999px;
  1. 设置 positionrelative,经过设置 topleft 等值,将其移出可视区域。(可视区域占位)
position: relative; left: -99999px;

如但愿其在可视区域不占位置,需同时设置 height: 0;

  1. 设置 margin 值,将其移出可视区域范围(可视区域占位)。
margin-left: -99999px;

若是但愿其在可视区域不占位,需同时设置 height: 0;

2.利用 transfrom
  1. 缩放(占据空间)
transform: scale(0);

若是但愿不占据空间,需同时设置 height: 0

  1. 移动 translateX, translateY(占据空间)
transform: translateX(-99999px);

若是但愿不占据空间,需同时设置 height: 0

  1. 旋转 rotate (占据空间)
transform: rotateY(90deg);
3.设置其大小为0

宽高为0,字体大小为0:

height: 0; width: 0; font-size: 0;

宽高为0,超出隐藏:

height: 0; width: 0; overflow: hidden;
4.设置透明度为0 (占据空间)
opacity: 0;
5.visibility属性 (占据空间)
visibility: hidden
6.层级覆盖,z-index 属性 (占据空间)
position: relative;
z-index: -999;

再设置一个层级较高的元素覆盖在此元素上。

7.clip-path 裁剪 (占据空间)
clip-path: polygon(0 0, 0 0, 0 0, 0 0);

语义上的隐藏

aria-hidden 属性 (占据空间)

读屏软件不可读,占据空间,可见。

<div aria-hidden="true">
</div>

11. 使用JS将元素从页面中移除

点击查看更多

13.浏览器事件代理机制的原理是什么?

事件流

在说浏览器事件代理机制原理以前,咱们首先了解一下事件流的概念,早期浏览器,IE采用的是事件捕获事件流,而Netscape采用的则是事件冒泡。"DOM2级事件"把事件流分为三个阶段,捕获阶段、目标阶段、冒泡阶段。现代浏览器也都遵循此规范。

 

 

事件代理机制的原理

事件代理又称为事件委托,在祖先级 DOM 元素绑定一个事件,当触发子孙级DOM元素的事件时,利用事件冒泡的原理来触发绑定在祖先级 DOM 的事件。由于事件会从目标元素一层层冒泡至 document 对象。

为何要事件代理?

  1. 添加到页面上的事件数量会影响页面的运行性能,若是添加的事件过多,会致使网页的性能降低。采用事件代理的方式,能够大大减小注册事件的个数。

  2. 事件代理的当时,某个子孙元素是动态增长的,不须要再次对其进行事件绑定。

  3. 不用担忧某个注册了事件的DOM元素被移除后,可能没法回收其事件处理程序,咱们只要把事件处理程序委托给更高层级的元素,就能够避免此问题。

  4. 容许给一个事件注册多个监听。

  5. 提供了一种更精细的手段控制 listener 的触发阶段(能够选择捕获或者是冒泡)。

  6. 对任何 DOM 元素都是有效的,而不只仅是对 HTML 元素有效。

addEventListener

addEventListener 接受3个参数,分别是要处理的事件名、实现了 EventListener 接口的对象或者是一个函数、一个对象/一个布尔值。

target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);

options(对象) | 可选

  • capture: Boolean。true 表示在捕获阶段触发,false表示在冒泡阶段触发。默认是 false。

  • once:Boolean。true 表示listener 在添加以后最多只调用一次,listener 会在其被调用以后自动移除。默认是 false。

  • passive: Boolean。true 表示 listener 永远不会调用 preventDefault()。若是 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。默认是 false。

useCapture(Boolean) | 可选

useCapture 默认为 false。表示冒泡阶段调用事件处理程序,若设置为 true,表示在捕获阶段调用事件处理程序。

如将页面中的全部click事件都代理到document上:

document.addEventListener('click', function (e) {
    console.log(e.target);
    /**
    * 捕获阶段调用调用事件处理程序,eventPhase是 1; 
    * 处于目标,eventPhase是2 
    * 冒泡阶段调用事件处理程序,eventPhase是 3;
    */ 
    console.log(e.eventPhase);
    
}, false);

addEventListener 相对应的是 removeEventListener,用于移除事件监听。

点击查看更多

14. setTimeout 倒计时为何会出现偏差?

setTimeout 只能保证延时或间隔不小于设定的时间。由于它实际上只是将回调添加到了宏任务队列中,可是若是主线程上有任务尚未执行完成,它必需要等待。

若是你对前面这句话不是很是理解,那么有必要了解一下 JS的运行机制。

JS的运行机制

(1)全部同步任务都在主线程上执行,造成一个执行栈(execution context stack)。

(2)主线程以外,还存在"任务队列"(task queue)。

(3)一旦"执行栈"中的全部同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,因而结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

setTimeout(()=>{callback();}, 1000) ,即表示在1s以后将 callback 放到宏任务队列中,当1s的时间到达时,若是主线程上有其它任务在执行,那么 callback 就必需要等待,另外 callback 的执行也须要时间,所以 setTimeout 的时间间隔是有偏差的,它只能保证延时不小于设置的时间。

如何减小 setTimeout 的偏差

咱们只能减小执行屡次的 setTimeout 的偏差,例如倒计时功能。

倒计时的时间一般都是从服务端获取的。形成偏差的缘由:

1.没有考虑偏差时间(函数执行的时间/其它代码的阻塞)

2.没有考虑浏览器的“休眠”

彻底消除 setTimeout的偏差是不可能的,可是咱们减小 setTimeout 的偏差。经过对下一次任务的调用时间进行修正,来减小偏差。

let count = 0;
let countdown = 5000; //服务器返回的倒计时时间
let interval = 1000;
let startTime = new Date().getTime();
let timer = setTimeout(countDownStart, interval); //首次执行
//定时器测试
function countDownStart() {
    count++;
    const offset = new Date().getTime() - (startTime + count * 1000);
    const nextInterval = interval - offset; //修正后的延时时间
    if (nextInterval < 0) {
        nextInterval = 0;
    }
    countdown -= interval;
    console.log("偏差:" + offset + "ms,下一次执行:" + nextInterval + "ms后,离活动开始还有:" + countdown + "ms");
    if (countdown <= 0) {
        clearTimeout(timer);
    } else {
        timer = setTimeout(countDownStart, nextInterval);
    }
}
 

若是当前页面是不可见的,那么倒计时会出现大于100ms的偏差时间。所以在页面显示时,应该从新从服务端获取剩余时间进行倒计时。固然,为了更好的性能,当倒计时不可见(Tab页切换/倒计时内容不在可视区时),能够选择中止倒计时。

为此,咱们能够监听 visibityChange 事件进行处理。


连接: https://juejin.im/post/5cfd278051882574ce013b2d
相关文章
相关标签/搜索