月度归档:2013年07月

『孤独六讲』

『孤独六讲』说起孤独,想当然地觉得就是寂寞,这两者在精神层面上的含义大不一样,寂寞体现的是内心的不安,是感到被动的;孤独则是一种主动性很强的圆满状态,比如“天地孤影我独行”、“会当凌绝顶,一览众山小”……比如阮籍、李白……比如黄药师、西门吹雪……追逐孤独感本质是追逐美,追逐一种理想主义。我确信自己不是因为寂寞才翻看『孤独六讲』,而是比较向往那种孤独感。

人性中都有追求特立独行的一面,但因儒家文化根深蒂固,中庸之道不仅封杀了特立独行的可能性,也使得追求孤独期望得到内心宁静成为奢望。特立独行意味着思想独立,这是统治阶级不愿看到的,统治者为了维护自己的利益通过政治和道德手段,大到社会伦理、君臣礼法,小到市井装束、地方习俗,无不一一进行约束。所以在这样背景下,“子非鱼,安知鱼之乐”、“独与天地精神往来”的老庄总是处在边缘的位置,一直得不到正统的认可,但是他的辩证思维却影响了大批追求个体自由,追求个性解放的人。

由此引申开来,几千年来中国人的思维方式并没有多少改变,循规蹈矩,信奉权威。人们一直将"己所不欲,勿施于人"这样的小道理奉为经典,并熟记于心,必要时作为引经据典之用,却没有探究这个结论背后的原因。一直到现在,即使是最严谨的理科教材,也充斥着结论式的定理和论断。这与国外的经典教材大相径庭,西方教材繁杂冗余的原因,除了之前有人提过的英文语言本身表达力有限的原因之外,更重要的是,这背后体现了中西方人之间思辨能力的差别。西方文明自古希腊哲学诞生以来,推理和思辨一直是哲学中重点探讨的内容,柏拉图、亚里士多德开创的逻辑学影响了现代文明的方方面面。反观儒家哲学,一直被统治阶级拿来当工具,维护其统治的合法性,曾经我认为儒学为人们的道德行为提供了准则,实际上所谓的传统美德在每个现代文明中都存在,因为这些价值观是有普世意义的,出现是一种必然。并非因为儒学大家才懂得尊老爱幼,而是因为尊老爱幼是美德,所以儒学才去加强它。如果把儒学对人们遵循传统美德的约束作用剥离开来,那么剩下还有什么可取之处呢?柏杨在『丑陋的中国人』中细数了儒学的危害,非常适合喜欢多方位看待问题的人花时间一读。

--EOF--

Erlang列表操作性能分析

Erlang里通过尾递归方式对列表中元素依次进行操作时,程序员们采用的方法总是先在尾递归中将处理后的元素加在已处理列表的头部,最后通过lists:reverse(List)来恢复原来次序。为什么不直接以自然顺序将表头元素加到已处理列表的尾部呢?这里面都是有故事的。

先看两个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-module(list_time).
-export([add_list_head/1, add_list_tail/1]).
 
add_list_head(N) ->
     add_list_head(N, []).
add_list_head(0, L) ->
     lists:reverse(L),
     ok;
add_list_head(N, L) ->
     add_list_head(N-1, [65 | L]).
 
add_list_tail(N) ->
     add_list_tail(N, []).
add_list_tail(0, L) ->
     ok;
add_list_tail(N, L) ->
     add_list_tail(N-1, L ++ [65]).

add_list_head/1和add_list_tail/1都用于依次向列表中加入一个元素,元素个数由参数N指定。add_list_head/1中,新加入的元素放在当前列表的表头,最后调用lists:reverse(L)恢复顺序;add_list_tail/1中,新加入的元素直接放在当前列表的表尾。add_list_tail/1的实现方法更符合我们的逻辑,但效率上却是add_list_head/1远远占优。有如下基准测试数据为证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1> c (list_time).
{ok,list_time}
2> timer:tc(list_time, add_list_head, [10000]).
{564,ok}
3> timer:tc(list_time, add_list_head, [100000]).
{8875,ok}
4> timer:tc(list_time, add_list_head, [1000000]).
{98275,ok}
5> timer:tc(list_time, add_list_head, [10000000]).
{1745467,ok} 
6>
6> timer:tc(list_time, add_list_tail, [10000]).
{561634,ok}
7> timer:tc(list_time, add_list_tail, [100000]).
{59663921,ok}
8> timer:tc(list_time, add_list_tail, [1000000]).  %半小时仍未返回 =。=

从测试数据看,add_list_head/1在依次添加10k, 100k, 1m, 10m个元素的时间分别为0.56ms, 8.87ms, 98.27ms, 1745.46ms。而add_list_tail/1添加10k, 100k个元素的时间分别为561.63ms和59663.92ms,当元素个数为1m时,运行半个小时仍未返回。造成性能差距如此之大的原因得从列表在Erlang里的实现原理中找。

在Erlang里,空列表用[]表示,所有非空列表都可以用[Element | Remain]的方式表示,比如,[a]可以表示成[a | []],[a, b, c]等价于[a | [b | [c | []]]]。但列表不是通过数组实现的,实际上,列表的底层数据结构是一个单链表(singly-linked list)。如下列表定义:

1
Foo = [a, b].

其对应的存储结构为:

1
2
3
Foo
 ↓
 a → b → null

其中,Foo可以看做是单链表a→b的表头指针。

现在分析向表头添加元素生成列表的情况,向Foo表头添加元素c:

1
Bar = [c | Foo].

它的存储结构为:

1
2
3
Bar Foo
 ↓   ↓
 c → a → b → null

向表头添加元素c只需把该节点的next指针指向Foo列表的表头即可,时间复杂度是O(1)。如果再向Foo的表头添加其他元素,如:

1
Baz = [d, e | Foo].

那么,Baz的存储结构为:

1
2
3
4
5
6
7
Bar Foo
 ↓   ↓
 c → a → b → null
     ↑
 d → e
 ↑
Baz

这个例子中, Bar, Baz直接重用了Foo的元素(存储空间),因为Foo是不可变的,所以这样的处理方式没有副作用。由此可以看出,通过向表头添加元素生成列表的方式的时间复杂度是O(n)。

现在再来分析向表尾添加元素的处理流程。实际上,Erlang中构造列表总是从表尾开始,然后依次在表头添加元素。通过++操作符和append/2函数向表尾“追加“元素只是一种假象,例如:

1
2
3
List1 = [a, b],
List2 = [c, d],
List3 =  List1 ++ List2.

构造列表List3的过程中,List1的数据被复制了一份,依次加到列表List2的表头,每次++操作,都会产生一个新的临时列表!从底层的指令看,每次表尾追加元素总是相当于先遍历一遍List1,所以向表尾追加元素的时间复杂度是O(n2)!

通过表头和表尾来操作列表的时间复杂度分别是O(n)和O(n2),因此,提倡以add_list_head/1的方式来操作列表。

--EOF--

使用CSS改变被选中文本样式

默认情况下,网页中文本被选中时的样式由操作系统决定,比如Mac下是淡蓝色背景黑色字体,而Windows下是深蓝色背景白色字体。但是经常看到有些网站,比如伯乐在线,在文本被选中情况下是蓝色背景和白色字体的,搭配网站的整体色调,看着非常舒服。于是好奇心起找了下资料,原来可以通过CSS3的::selection伪类选择器实现。

具体实现:
在css文件中加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
*::selection {
    background:#da2b2e;
    color:#fff;
}
 
*::-moz-selection {
    background:#da2b2e;
    color:#fff;
}
 
*::-webkit-selection {
    background:#da2b2e;
    color:#fff;
}

::selection加在一个*号后面表示匹配整站所有的元素。大家可以根据需求将::selection加在任意标签、class或者id选择器后边。::selection中可以定义的属性较少,只有color(文字颜色), background(文字背景), cursor(鼠标样式)和outline(文字边框),常用的也就color和background。目前高版本的主流浏览器均已支持该选择器。

效果图如下,感觉非常的高端洋气:
Snip20130722_62

--EOF--

密码重置功能漏洞分析和解决思路[下]

上篇文章『密码重置功能漏洞分析和解决思路[上]』总结了几个互联网服务已爆出的密码重置功能漏洞类型,本文来说说在我项目中密码重置功能的实现思路。

上次说到的几种安全漏洞类型,归纳起来其实就两个问题:

1. 密码重置者身份的唯一性标识太容易伪造。比如手机验证码、安全问题答案存客户端等方式。
2. 未做密码重置的水平权限控制。比如允许某个用户越权重置另一个用户的密码。

要解决第一个问题,可以为每次用户密码重置请求生成一个复杂的唯一链接,发送到用户邮箱或手机(效果一样,但链接会较长,相比6位验证码,用户体验会差很多)。要解决第二个问题,最简单的方法就是不信任用户自己的身份声明。他说自己是张三不管用,我们有自己的判断依据。

有一种方式可以统一这两个问题的解决方案:对称密钥加密运算。它的加密算法等同于解密算法,使用相同的密钥,连续加密两次后会得到原始的明文,详见Wiki。用在密码重置的场景里,我们可以:

1. 服务器端对申请密码重置者的身份(userName)和当前时间戳(t)做个对称加密运算,得到一个复杂字符串密文{encryptedString}。加密算法可采用3DES或AES等。
2. 用第1步得到的密文构造此用户的密码重置链接http://domain/{encryptedString},发送到用户邮箱或手机。
3. 用户点击链接后进入密码重置页面,输入用户名、新密码。提交。
4. 服务器端得到来源URL中的{encryptedString},以及用户提交的用户名和新密码。首先使用相同的密钥解密,得到原始明文中的userName和时间戳t。如果解密失败,返回错误。
5. 如果解析出来的userName与用户输入的用户名不匹配,表示此请求非法,此请求可能是非用户本人发出,返回错误。
6. 否则,如果当前时间戳t'与解析出来的t的间隔大于某个阈值,比如24小时,则表示此链接已失效,返回错误。
7. 否则,确定为有效的密码重置请求,重置用户密码,返回成功。

另外,这是一种“无状态”的方案,把密码重置链接的过期时间信息打包在密文中,通过URL来回传递,使服务器无需维护用户名、重置链接标识({encryptedString}、token之类的)和过期时间等信息,降低对缓存和数据库的依赖。

--EOF--

密码重置功能漏洞分析和解决思路[上]

今天看到乌云上一篇关于网站密码找回功能的漏洞分析文章『密码找回功能可能存在的问题』,我几乎是带着震惊的表情看完的,真是触目惊心,这些中枪的网站包括微信、搜狐、当当、360、携程等等。即使CSDN、人人网曾经爆出过密码泄露事件,我心里还是比较信赖大型互联网企业在网络安全方面所提供的保障,现在看来,连密码找回这种基础服务都能爆出这么多的安全漏洞,这不得不让我认识到需要更新下安全观了。这篇文章先把那几个低级漏洞重新拿出来鞭一下尸,以警示自己和后人。

1. 手机验证码设置太弱,并且允许频繁试错。比如当当网微信的这两个漏洞,只要用户输入手机上收到的验证码,就能改密码,而验证码设置又非常简单,是6位的纯数字,暴力搜索一小会儿的事情。这种方式进行密码重置功能应该至少加个验证码或者服务器端判定该用户的修改请求是否太频繁的机制,当当网是两者都缺失,微信则是后一种机制没落实好,留下bug。

2. 找回密码的凭证在客户端可以直接得到。比如走秀网,直接将验证码返回到页面,然后等待与用户输入的进行匹配。搜狐邮箱也是直接将安全问题的答案在HTML中返回。我在想到底是出于什么样的考虑才会做出这么脑残的设计,难道为了让用户体验更好更流畅?完全把用户当傻子。

3. 重置密码的链接太容易伪造。360的漏洞在于密码重置链接很容易伪造,链接的唯一性标识就是当前时间戳的MD5值,只要知道邮箱,对近期的时间戳依次做MD5运算,暴力破解不是问题。

4. 水平权限控制不到位。比如身份通网易邮箱携程网的这几个漏洞,之前有篇文章提到过,基于role的垂直权限控制比较好做,只要分配不同的role即可,但是相同role之间的权限控制就繁琐很多,是与业务紧耦合的。回到重置密码链接/账号绑定的问题,上面几个漏洞都是服务器端只验证了链接唯一性标识(token)的有效性,而没有去验证这个token是否有权去修改指定的账号,根本没做水平的权限控制。

账户安全问题不做好其他都是白搭,以上这些漏洞都是些非常低级的设计逻辑缺陷,破解这些漏洞甚至都不需要有非常高的计算机专业素养。下篇文章会讲讲一种我认为的比较安全的密码重置功能解决方案。

--EOF--