svg-captcha的巨大漏洞发现日志

最新更新,svg-captcha即将发布3.0,修复该漏洞

详情issue:https://github.com/produck/sv...


以下原文

可能有部分nodejs开发者因为安装图形库很麻烦,都用svg-captcha来生成图形验证码

svg-captcha: https://www.npmjs.com/package...

它有不少优点,具体看官方文档。然而它最大的一个缺点就是,太容易被破解了,是我刚破解的(其实是我在2019年,第一次知道它后没多久,无意中发现破解方法的,直到现在才有空提交代码)

破解指的是,很容易被机器识别,识别率达100%,并且不需要任何机器学习有关的知识,更不需要任何图形识别库,你可能根本想象不到破解方法有多简单。

如果没有兴趣看下面那么长,可以直接看识别代码:

https://github.com/haua/svg-c...

发现流程

刚接触svg-captcha的时候就在想,它到底为防止识别做了哪些操作,然后仔细对比相同字母,发现每次生成相同字母的轮廓,不一致的地方相当多:

image.png

这样看来,即使是相同字母,它的svg path,也是完全不一样的,看来如果要破解,确实只能先把它转为位图,再做图像识别了。

然而,有一次无意中生成了两个首字母相同的验证码,看到了如下画面:
image.png

图中上下两行分别是两次生成的svg,位置均是第二个字母,很快意识到,两个验证码,第一个字母的svg path长度是完全一样的。

(如果不清楚svg path是啥,可以去看看svg的组成,svg可以看作是一个html标签,path是它的一个重要属性)

那既然如此,是不是可以根据svg path长度来区分它是哪个字母??

于是用js写了个脚本,遍历调用svg-captcha来生成验证码,把每个验证码中的字母path长度做个统计,一共只有26个字母+10个数字,结果如下

{
  '0': { '2382': 10819, '2580': 3769 },
  '1': { '998': 10861, '1081': 3621 },
  '2': { '2546': 11012, '2758': 3676 },
  '3': { '3878': 10913, '4201': 3599 },
  '4': { '2140': 10985, '2318': 3674 },
  '5': { '2606': 10925, '2823': 3623 },
  '6': { '2632': 10834, '2851': 3615 },
  '7': { '2042': 10956, '2212': 3636 },
  '8': { '3414': 10742, '3698': 3638 },
  '9': { '2800': 5343, '3033': 1811 },
  A: { '1840': 3618, '1844': 1825, '1993': 1885 },
  B: { '3054': 11034, '3308': 3710 },
  C: { '2198': 8820, '2199': 1536, '2200': 538, '2201': 92, '2202': 4, '2381': 3579 },
  D: { '1996': 10745, '2162': 3651 },
  E: { '2246': 10939, '2433': 3569 },
  F: { '1754': 10911, '1900': 3689 },
  G: { '3266': 10989, '3538': 3579 },
  H: { '1922': 7220, '1928': 3663, '2082': 3668 },
  I: { '986': 11125, '1068': 3628 },
  J: { '1610': 10965, '1744': 3557 },
  K: { '1706': 7291, '1709': 3534, '1848': 3656 },
  L: { '1274': 10949, '1380': 3722 },
  M: { '2279': 3560, '2282': 3675, '2321': 3666, '2466': 3649 },
  N: { '1598': 7429, '1614': 30, '1615': 379, '1616': 1312, '1617': 1728, '1618': 330, '1731': 3553 },
  O: { '2164': 10695, '2344': 3602 },
  P: { '1960': 10949, '2123': 3630 },
  Q: { '3244': 7162, '3254': 3616, '3514': 3676 },
  R: { '2104': 7284, '2107': 3580, '2279': 3682 },
  S: { '3038': 10732, '3291': 3631 },
  T: { '1478': 11016, '1601': 3623 },
  U: { '2294': 7360, '2301': 3585, '2485': 3640 },
  V: { '1298': 7251, '1311': 3589, '1406': 3666 },
  W: { '2310': 3664, '2318': 3644, '2345': 2516, '2346': 1114, '2503': 3624 },
  X: { '1598': 7382, '1604': 3635, '1731': 3614 },
  Y: { '1130': 7310, '1134': 3611, '1224': 3597 },
  Z: { '1850': 7223, '1853': 3620, '2004': 3595 },
  a: { '2332': 10944, '2526': 3711 },
  b: { '2380': 10844, '2578': 3617 },
  c: { '2498': 10924, '2706': 3720 },
  d: { '2272': 10828, '2461': 3601 },
  e: { '2501': 10802, '2709': 3558 },
  f: { '2210': 11041, '2394': 3612 },
  g: { '3160': 11074, '3423': 3708 },
  h: { '1886': 10731, '2043': 3546 },
  i: { '1360': 11038, '1473': 3605 },
  j: { '2080': 10842, '2253': 3581 },
  k: { '1634': 7341, '1637': 3601, '1770': 3664 },
  l: { '986': 10975, '1068': 3589 },
  m: { '3663': 7200, '3667': 3638, '3968': 3747 },
  n: { '2198': 10825, '2381': 3668 },
  o: { '2260': 11180, '2448': 3740 },
  p: { '2464': 10860, '2669': 3653 },
  q: { '2512': 10844, '2721': 3641 },
  r: { '1491': 11032, '1615': 3659 },
  s: { '2366': 10812, '2563': 3604 },
  t: { '1694': 10790, '1835': 3615 },
  u: { '1838': 10953, '1991': 3656 },
  v: { '1082': 10959, '1172': 3543 },
  w: { '2018': 7242, '2035': 3735, '2183': 3672 },
  x: { '1610': 7350, '1613': 3706, '1744': 3762 },
  y: { '1274': 10830, '1380': 3490 },
  z: { '1694': 11224, '1835': 3701 }
}

每个字母的值都是一个对象,这个对象的key是path长度,value是这个长度出现的次数,因为还想看看每个长度出现的概率,所以跑了几十万次,也为了防止有些长度没出现过。

可以看到,每个字母的path长度,也就那几种。这样看来,svg-captcha也太容易破解了吧!

根据以上的统计,有15个字母的path长度存在相同的情况,所以用这个方法的准确率应该不到50%

继续看看那些有相同path长度的字母,发现它们还有很大的不同,比如Il都有相同的path长度(986),但是对比一下:

image.png

左边是I,右边是l,可以看到l的最上面,要比I要高一点,虽然直接根据这个特征判断I还是l,似乎很没说服力,但是试了生成几万个Il,这个的差别都是一样的,这样的话这个特征肯定能拿来用了。

其它字母也差不多是这种细微,但可靠的特征,最终做到了100%准确识别率。

如此看来,svg-captcha也只是做到了看起来比位图验证码要难识别,实际上它更容易识别,识别方法也更原始。

建议

如果你还是想使用svg-captcha,那我建议可以多准备几套字体,写个逻辑,让它每小时换一种字体,这样生成的字母svg path会完全不一样。

你可能感兴趣的:(node.js,验证码)