手把手发起 XSS 攻击 + 如何防御

手把手发起 XSS 攻击 + 如何防御

本文内容听译自 Youtube 视频:Running a XSS Attack + How to defend 并编写完善。

原作者:Maximilian Schwarzmüller

前言


如果你是一个 Web 开发者,那么你自然希望创建出安全的 Web 应用。跨站脚本攻击(Cross-Site Scripting Attack,简写为XSS)是现代 Web 应用中最大的威胁之一。因此很重要的一点就是你所编写的代码不易于被 XSS 攻击,而你也需要对什么是 XSS 攻击、在你的应用中哪里有潜在的安全漏洞有一个基本的了解。这就是在本期视频中我们将详细探索的内容。

这里有一个非常简易的 Web 应用代码,只有三个文件,而且只在浏览器里运行。想要发起 XSS 攻击的话还需要一个服务器,不过我也会展示如何基于此代码发起一个攻击,这也是我使用这个简易示例的原因。

基本界面

我们可以发送以一些文本和一个图片构成的信息,这些信息会进而在下方依次简单地渲染出来。很容易想到,这个应用不可能只是在浏览器上运行,而是应该将文本和图片 URL 传输并储存到服务器的数据库里。没错,绝大多数网站就是这样做的。

比如一个用户可以购买商品,那么就会发起一个请求发送给服务器,来储存这一次购买信息。

再比如,有一个博客网站,你写了一篇博客并发表出去,那么就会被储存在数据库中,其它用户就能够访问到。

再比如,有一个公共论坛,用户可以相互交流,那么任何人发送消息,都会被存储到服务器数据库里,其它用户通过各种设备访问论坛的时候就会加载这些信息,从而展示在用户面前。

Web 应用的运作模式就是这样对吧。我们这里的这个应用也是这样,填写的信息也会被储存在数据库中。

发起攻击!


什么是 XSS 攻击

XSS 攻击指的是在我们用户使用的设备上执行恶意的 Javascript 代码。

简单的攻击策略

蹲点

我们首先通过页面审查,来到源代码标签,看到使整个网站运作起来的代码。

有的时候源代码会被压缩,从而很难读,不过最终依然可以读到。相关方法的介绍请查看我的其它视频。这里的代码其实就是 app.js 文件中的源码。

如下面代码所示,在用户提交时,输入的信息会进行校验,然后添加到 userMessages 数组中。

function formSubmitHandler(event) {
  event.preventDefault();
  const userMessageInput = event.target.querySelector('textarea');
  const messageImageInput = event.target.querySelector('input');
  const userMessage = userMessageInput.value;
  const imageUrl = messageImageInput.value;

  if (
    !userMessage ||
    !imageUrl ||
    userMessage.trim().length === 0 ||
    imageUrl.trim().length === 0
  ) {
    alert('Please insert a valid message and image.');
    return;
  }

  userMessages.push({
    text: userMessage,
    image: imageUrl,
  });

  userMessageInput.value = '';
  messageImageInput.value = '';

  renderMessages();
}

如果是现实环境,信息就会发送到服务器,存储到数据库,在页面加载时获取。

无论是现在这种傻乎乎的加入数组,还是从服务器获取,得到内容后,在下面这段代码里就会进行渲染。简单的渲染方式就是使用一个循环,并且最终构建出这样一串字符串,然后添加到 <ul> 元素的 innerHTML 中。

function renderMessages() {
  let messageItems = '';
  for (const message of userMessages) {
    messageItems = `
      ${messageItems}
      <li class="message-item">
        <div class="message-image">
          <img src="${message.image}" alt="${message.text}">
        </div>
        <p>${message.text}</p>
      </li>
    `;
  }

  userMessagesList.innerHTML = messageItems;
}

攻击

你能想到的其中一个简易的模式,应该是在 Your Message 一栏写入 <script> 标签,然后在其中写一些恶意代码。

在这个例子中,我就只写了一行 alert,弹出“你被黑了!”。

正因为使用了 innerHTML 的缘故,我们加入的 <script> 标签应该会被转译成 HTML 然后被执行吧?

没起作用?

尝试之后发现,并没有什么效果。但是如果这个时候再去页面元素中看,我们会发现 <script> 标签已经被渲染出来了, 如下图所示。

没有生效的原因是,现代浏览器知道这样一种攻击模式漏洞,并且已经做出了防御。防御手段就是任何加入到 innerHTML 中的代码都不会被执行

不过我下面将展示一种能够成功让代码得到执行的方式。

代码成功注入的后果

我们都知道如果注入的代码成功被执行将会带来什么后果。这里的示例是一个纯前端的东西,不会往服务器中存储,所以我们只能自己黑自己。一旦存储到了服务器里,其它用户就也会得到执行注入代码的机会,这样一来就能把坏事做尽。

我们能够偷取到 Local Storage 或者 Cookies 中的内容;

我们能够把偷来的数据存储到自己的服务器上,

或者伪造他人身份来发送请求,在他人不知情的情况下替代他人买东西。

这些都是潜在的威胁。

换一个攻击策略

这一次我们在 Your Message 一栏中输入普通的内容,但是 Message Image 这一栏呢?我们原本需要填入的是图片链接,而且这个链接同样也会通过 innerHTML 被渲染到 <img> 标签里面,也就是之前看到的这一行字符串代码:

`<img src="${message.image}" alt="${message.text}">`

如果我们能够改变这一行字符串最终形成的内容,把这一整个 <img> 元素都换掉呢?

改造图片链接

首先,我们先填入一个无效的图片链接,比如:

some-page.com/no-image.jpg

接下来,在后面加上一个双引号 ",这样就能够把 <img> 标签中的 src 属性闭合。实际渲染出来的结果就会变成:

<img src="some-page.com/no-image.jpg" /*剩余内容*/ alt="${message.text}">

对于 <img> 标签来说,有一个独特的属性是 onerror,这个属性也是被官方所支持的。onerror 中需要的是 Javascript 代码,当获取图片失败的时候将被调用。因此我们提供了一个无效链接后,就能强制执行 onerror 中的内容,不需要 <script> 标签了。因此改造完成后,我们填入的内容就是:

some-page.com/no-image.jpg“ onerror="alert("你被黑了!")"

现在页面上就真的弹出了提示框,这就是一次成功的 XSS 攻击了。

有必要重申一遍,在这个示例中,我们只能对自己黑着玩,但是这样的数据是能够存储在数据库中的,也会有机会在其它用户的设备上执行。

这就是一个简单的 XSS 攻击。在任何网站中找到这样脆弱的攻击点并不难,而且一瞬间就能让网站出现巨大的问题。

发起防御!


那么作为网站的开发者,如何才能在开发 Web 应用时建起围墙呢?有许多方法。

首先,你自然不能采取上面构建 innerHTML 的方式把用户输入的内容直接塞到一个标签里。

不过还有更为重要的步骤需要做。

清理用户的脏输入

如果你有着使用 Node.js 编写后端代码的经验,那么你可以搜索一下 node sanitize,类似的包在 php 等语言上都有。

清理的意思是,用户输入的内容会通过某种模式进行检查,然后将其中的恶意部分移除,这样最终储存的内容就是“干净”的。

这样的清理包无论是在浏览器还是在服务器中都是推荐使用的,简言之,在储存用户输入的数据之前,有这么一个流程能够将用户输入的内容进行清理是十分重要的。

使用框架

除了清洁输入以外,在客户端也同样可以进行一层保护。如果你使用的是类似 React,Angular 或者 Vue 的框架或库,这些框架也已经在内部集成了 HTML 的转译。

但是这并不意味着使用了框架就不需要再在后端进行输入清理。这样为整体应用的安全性能多加了一层保护措施。

第三方库攻击

除此之外,还存在着一类攻击途径,这一类更具有技巧性。我们这里使用的是一个使用原生 Javascript 的应用,但是在现实生活中我们都知道,一个大型项目是由数百个不同的包、数百个第三方库构成的,比如 reactreact-router, 组件库等。

我们需要知道的是,所有这些库都会在整个项目中加入新的会被执行的 Javascript 代码。如果某一个库或者框架中就存在着恶意代码,这样以来就能够正常执行,也不会被清理。

换句话说,你使用了一个被认可使用,却具有恶意代码的第三方库,那么整个项目的风险系数就很高了。

为此,npm 的审计(audit)功能能够让你对自己使用的第三方库进行已知薄弱点审查。那么对于未知的其它薄弱点,依然存在着风险。

庆幸如今有很多开源的项目,我们可以进入代码中来看到哪些内容将会在我们自己的项目中执行,不过实际上我们并不会特地这样做。

对于 Angular 这样的库,我们能够放心使用,因为 Google 还没对攻击我们这些开发者有什么计划。对于小型一些的库来说,如果存在着恶意代码,有可能并不是因为这些库的作者是个坏蛋,而有可能是因为他们引入了其它地方的代码,从而进一步在不知情的情况下引入了恶意代码。

综上,第三方库也是攻击来源之一,但这么说的意思并不是不让你用,而是在使用的时候要有意识地做到防范。使用前请三思,也许你并不是一定需要引入某个库来做具有动画效果的提示文本,也许你可以缩减所使用到的第三方库的数量,这样对于用户的压力也更小一些,还能给页面提速。

小结

如今,大型且流行的库和框架自然是很安全的,但是依然是无法达到 100% 的安全。我们自身依然需要对于先前提到过的几类攻击模式进行防御,通过清理用户输入和提高潜在风险的安全意识。

以上就是一个最为基础和简易的 XSS 攻击示例以及一个常见的薄弱漏洞。

我的频道里有着更多关于 Local StorageCookie、身份验证、数据存储相关的视频,希望这些能够帮助你构建出一个具有更高安全性的 Web 应用。

(完)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容