Skip to content

Instantly share code, notes, and snippets.

@magicshui
Created July 20, 2012 13:49
Show Gist options
  • Save magicshui/3150835 to your computer and use it in GitHub Desktop.
Save magicshui/3150835 to your computer and use it in GitHub Desktop.
河北大学校外查成绩的原理

##前言 什么是自由?自由不是想做什么都能做什么,而是当你不想做什么的时候可以选择不做。 ##正题 其实你知道,所有的成绩都是以数据的形式存放在学校的数据库中,而学校通过IP显示、网络端口的限制等一系列手段来保证数据的安全,当然,这些做法是无可厚非的,但是你不然学生查到自己的成绩就有点……

首先看下学校的查成绩方式:

  1. 登陆学校的教务系统。在这一次的登陆中,你会输入用户名、密码,学校的后台程序在接收到你的登陆请求以后就会验证,通过验证则会在本地保存一个cookie,在服务器端维护一个session。这些是你已经登陆的证明。

  2. 点击查成绩。学校的教务系统使用的iframe的嵌入方式,也就是说他其实就是外边一个框,当你点击里面链接的时候只变换里面的内容。但是你会发现这样页面的url地址并没有变化,然后就有异步刷新的假象……额,这种方式多年以前很流行,我只能这样说

  3. 成绩结果显示。点击完查成绩以后,后台相应的程序会相应请求,首先自然收先验证登陆与否,然后从数据库拉取相关数据返回给前台。

这就是你完成一次查询所执行的动作。

那么我们在外网如何查成绩呢?

首先,必须要登陆。这就要求你有一台能够和学校教务系统进行数据通信的主机,显然你不太可能只通过外网的服务器进行,因为教务系统是不直接和外边通信的,没有端口开放出来。那么,你就需要曲线救国,找一台对外网通信的校内主机,一般满足这种条件的机器就是各个学院的网站、学校一些新闻网站等等,然后利用这些主机进行通信。

当你找到这些主机以后,你所需要做的就是获取用户名、密码,然后将这些数据传给学校的登陆认证url,在这里,这个地址是http://202.206.1.163/loginAction.do,你需要一种称之为POST的http语义来进行这个操作,所传递的数据对象是这样的: zjh=uid&mm=password

你怎么知道传递这样的数据?

额……好吧,这个就是你首先开启一个网络监测的软件,然后本地在校内登陆教务系统,抓取数据包,然后分析一下就看到这样的数据了~

额,如下是代码:

        private static string Login(string uid,string password)//这里我构造了一个私有函数
    {
        string paramList = "zjh="+uid+"&mm="+password;//构造post的数据
        string url="http://202.206.1.163/loginAction.do";//所要post的url地址
        HttpWebResponse res = null;//返回的结果类
        string result = "";//结果
        try//捕捉异常
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);//建立请求
            req.Method = "POST";//设置请求的方式
            req.ContentType = "application/x-www-form-urlencoded";//设置内容类型,这些都是抓包获取的
            req.AllowAutoRedirect = false;
            CookieContainer cookieContainer = new CookieContainer();//设置cookie容器
            req.CookieContainer = cookieContainer;//将cookie容器和请求类绑定再一起
            StringBuilder urlEn = new StringBuilder();//构建一个stringbuilder类
            char[] reserved = { '?', '=', '&' };//如下的一大堆就是对post的数据进行urlencode,这个是程序化步骤,当然你也可以用更简单的方式,随便,没啥影响
            byte[] bs = null;
            if (paramList != null)
            {
                int i = 0, j;
                while (i < paramList.Length)
                {
                    j = paramList.IndexOfAny(reserved, i);
                    if (j == -1)
                    {
                        urlEn.Append(HttpUtility.UrlEncode(paramList.Substring(i, paramList.Length - i)));
                        break;
                    }
                    urlEn.Append(HttpUtility.UrlEncode(paramList.Substring(i, j - i)));
                    urlEn.Append(paramList.Substring(j, 1));
                    i = j + 1;
                }
                bs = Encoding.UTF8.GetBytes(urlEn.ToString());
                req.ContentLength = bs.Length;
                Stream newStream = req.GetRequestStream();
                newStream.Write(bs, 0, bs.Length);
                newStream.Close();
            }

            else
            {
                req.ContentLength = 0;
            }//终于构建完了

            res = (HttpWebResponse)req.GetResponse();//发送请求
            cookieheader = req.CookieContainer.GetCookieHeader(new Uri(url));//获取请求的url的cookie信息,注意这个cookieheader是全局变量,目的是两次http请求只要再程序没有释放内存的时候都能够获取到cookie并从内存中还原出来向服务器进行请求数据
            Stream recStream = res.GetResponseStream();//读入流
            Encoding encode = System.Text.Encoding.GetEncoding("GBK");//设置编码格式
            StreamReader sr = new StreamReader(recStream, encode);//将流读入到相应的读取类
            Char[] read = new Char[256];//下面是开始获取数据了
            int count = sr.Read(read, 0, 256);
            while (count > 0)
            {
                String str = new String(read, 0, count);
                result += str;
                count = sr.Read(read, 0, 256);
            }

        }
        catch (Exception)//当你捕捉到异常以后这个执行
        {

            result = "";
        }
        finally//最终这个会执行
        {
            if (res != null)
            {
                res.Close();
            }
        }
        return result;
    }

回到正题,我们刚才说道学校会使用cookie和session来维持这样的一个状态,进而确认用户的登陆,因此,当你把用户名、密码给了服务器端口以后,它会给你写入一个cookie,这个cookie是你已经登陆的凭证,一般都具有过期时间。

你所需要的是获取这个cookie,在我写的这个C#程序中,我通过定义一个全局的cookieheader序列化这个cookie信息,使得这个cookie存活在内存中。

这样做是基于如下两个考虑:

  1. 不干扰别的用户
  2. 对程序友好……这一点说的有些牵强,简单说来就是写起程序来简单,你不需要再去费力操作代理服务器上的cookie文件

OK,在获得这个cookie以后,我就已经登陆进来了,那么剩下的就是查询数据,这一步是自动完成得,即用户输入用户名、密码以后,系统向教务系统POST数据,成功以后,再向教务系统的成绩URL获取数据。

所以,你这个时候就是从内存中还原刚才获取的cookie,构造一个http请求,向这个端口:

http://202.206.1.163/bxqcjcxAction.do

发送请求,这个时候其实就是发送一个get请求,然后读取数据。如下

        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);// 创建一个请求对象
        req.Referer = "http://102.106.1.163/menu/s_menu.jsp";//ref,这样是为了欺骗后台说你是从正常的前段页面点击了menu进行过来的请求
        CookieContainer cookieCon = new CookieContainer();// 创建一个cookie容器
        req.CookieContainer = cookieCon;//额,这个为空就好
        req.CookieContainer.SetCookies(new Uri(url), cookieheader);//从全局变量中(内存)还原
        HttpWebResponse res = (HttpWebResponse)req.GetResponse();//获取请求结果
        StreamReader sr = new StreamReader(res.GetResponseStream(), Encoding.Default);//将请求结果读入到stream类中
        string strResult = sr.ReadToEnd();//将流转为字符串
        sr.Close();//关闭流
        return strResult;//返回结果

刚才说道学校的教务系统使用的是iframe,这样就可以在请求中只是返回成绩那个table dom的东东,然后嵌入到原来的ingle界面中了。这样给我们的便利就是这个返回的请求不需要再reg就直接作为结果显示给用户就好了。

至此,你就看到了成绩了~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment