Stoned's Blog

A startup hacker.

漫漫破解路

前言

11年是个值得纪录的时刻,12306的推出改变了人们购买火车票的方式,以前是挤火车站,现在是挤12306。于是在这个中药的时刻,各路人马各显神通,自动登录,自动购票,自动刷票等应运而生。最著名的要数zzdhidden的项目,一时间不管IE,firefox,Chrome,只要是想要买火车票的,基本都装上了那个脚本,好不风光。

但好景不长,由于铁道部发现了验证码不刷新的漏洞之后,以上方法通通失效。但我还是不甘心,我还是希望能够有一个东西至少能实现自动登录。

思路

铁道部的验证码其实并不算复杂,我觉得只要能对它进行OCR,我应该就能实现自动登录。我特意在在线OCR的网站上进行了测试,结果还是比较理想的,自动登录的验证码是可以被识别的。于是就进行了漫漫破解路,我到目前为止还是不清楚,最终能不能成功。

难度

铁道部用了HTTPS的方式访问,连图片也是HTTPS的方式,而且在上回修复验证码不刷新的问题之后,验证码已经没有可能性越过,只能硬碰硬的方式进行破解了。

尝试

第一次

我的第一次尝试确定验证码跟的确定跟什么有关,cookie?page?js的隐藏的东西?还是说通通相关。我的做法是打开浏览器,打开登录页面,再打开另外一个页面,再输入验证码的地址。然后在之前打开的登录页面中输入后一个页面的验证码,结果登录成功。由此可以得出结论,cookie只跟session相关,跟page是无关的。

第二次

第二次尝试,是测试是否只跟session相关。我在Chrome中打开登陆界面,然后用ruby模拟同一个session去抓取验证码。结果却很让我失望,登录失败。所以,由这个试验可以得出的是因为是HTTPS的协议,在每次连接的时候就默认有个session的概念,所以用cookie的那个session是模拟不了的

接下来

在经历了前两次的试验之后,我觉得还剩下两条路:

  1. 获取cache中的图片,然后传给验证码破解服务网站,得到验证码之后,提交
  2. 做一个https代理,拦截验证码的请求,破解后传给用户。

从用户的角度来讲,我觉得用户很难相信一个代理,第二如果真以这种方式实现的话,我的服务器压力也受不了。所以我优先去尝试第一种方式。于是就有了我的第三次尝试。

第三次

用户缓存中的图片。通过搜索,发现在IE下可以用cache api,或者可以将图片拷贝到剪切板。Chrome下可以通过访问chrome://cache/。但怎么把这些图片传给我的服务器呢?我觉得无论是IE还是Chrome,肯定有权限的限制,于是没往这条路上深入。

第四次

还是想要在客户端传图片给我的解析服务。于是想到了HTML5的本地存储。于是在测试之后发现这个办法还是不可行。因为碰到了安全沙箱问题。在调用canvas.toDataURL()时会报SECURITY_ERR: DOM Exception 18错误。

1
2
3
4
5
6
7
  var img = new Image();   // Create new img element 
  img.onload = function(){
    // execute drawImage statements here 
    ctx.drawImage(img,0,0);
    var imgData = canvas.toDataURL("image/jpg");;
  };
  img.src = 'https://dynamic.12306.cn/otsweb/passCodeAction.do?rand=lrand'; // Set     source path

第五次

但突然峰回路转,后来我分析了沙箱问题的原因是我在本地文件做的测试,而图片的域名是12306的,所以会造成跨域的问题。所以后来我小测试了一下,在Chrome的插件Script里执行上述代码,结果发现不再出现上述错误了。解决这最头疼的跨域问题以及图片获取问题之后,接下来的问题就是怎么把图片数据传到我的OCR服务器了。

于是我也是先做了个简单的试验,将获取到的base64数据post到我的OCR服务器,OCR服务base64解码,再存成文件。我一看是张完整的完整的图片,高兴坏了。

第六次

但当我把OCR的结果传回到客户端时,意外发生了,客户端怎么也没有收到。通过抓包显示,post请求被cancel掉了。后google之,发现正是js的跨域问题,即一个站点的js无法请求另一站点的js的数据。幸好这块都有成熟的解决办法,通过伪装成Script请求的方式进行数据提交。或者直接就用JSONP的方式。这个方式有一个问题,就是不能使用POST方式进行数据的提交了。但图片的大小(base64)有4K以上,怎么才能把图片完整地传到服务器端呢?(因为get有2K的大小限制)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  var JSONP = document.createElement("script") ;
   JSONP.onload = JSONP.onreadystatechange = function(data){
        if (result.length == 4){
             $("#randCode").val(result);
             submitForm();
        }else{
             reLogin();
        }

        // alert(result);
   }
   JSONP.type = "text/javascript";
   JSONP.src = "http://localhost:3000/ocr";
   document.head.appendChild(JSONP);

第七次

get方式提交我想了两个办法,一是多次分批提交数据,服务端再进行组合。而是利用多个cookie进行提交,这样只用一次请求就可以传完了(cookie的长度限制是4K)。但我在进行cookie测试的时候,失败了,原因是我在12306的域名下无法访问另外一个站点的cookie。所以只能采用第一个办法。

第八次

那下面就是苦力活了。只要传好数据,ORC之后回传给浏览器,再进行登录就好了。我在做这一步的时候碰到挺多问题的。

  1. 是jquery的getJSON方法总是无法触发callback方法,不知何故。没办法,我只能用complete方法代替callback方法进行触发。
  2. 由于是分批次的上传图片数据,要注意图片的顺序问题。所以应该是一个接一个地传。
  3. ruby的默认session同样有4K的大小限制,因此需要改成数据库的方式进行sesison的存储。http://www.cslog.cn/Content/ruby_on_rails_sessions/ https://github.com/nmerouze/mongo_session_store/issues/8
  4. 提交之前需要encodeURIComponent一下

结束

至此,破解12306从设想变成了现实。其实回过头来看看,其实也不是很难,但充满了惊险刺激。比大学时做ACM的题目更加兴奋。

Github:http://github.com/huangxiangdan/12306