登录华科校园网,我用Socket

导语:

找一个华科学生问一问,学校的网络怎么样?得到的大多数是负面回答。其实不论是从覆盖区域、网络稳定性、还是速度来说,华科做的都还是可以的(24:00断网除外)。可是有一点我从进校以来就一直不爽,那就是校园网的认证方式是有线锐捷+无线web页面组合,并且无线网不能输入MAC来指定无感认证设备。真的是非常的安(má)全(fàn)啊!

这就意味着像esp32这类MCU没法使用无线网,特别是大一学生不能开通有线也没法装路由器,当时想用esp32做点东西的我十分郁闷。我从来到华科的第一天就想搞它了。使用Socket直接模拟网页认证,让esp32也能直接联网。

补充 :

做完之后也看了网上类似的博客,其他学校的同学也用Socket进行过类似的认证,可大部分没有提及跳转重定向加密等重要部分,而且也都比较简短,没有分析整个认证过程,所以这一篇就尽量详细的还原整个过程,并且使用ESP32+micropython进行测试通过。所以,多图预警

工具 :

FireFox浏览器、WireShark、Python3



0x00 观察

登录过程

这就是认证页面,在手机端上的模样与电脑端大同小异。一般我们输入正确的用户名(学号)、密码再点击按钮就能跳转到认证成功的页面上去了:

一般的,我会给电脑和手机开启无感认证,每次连接到校园网就不必手动认证,缺点在于不支持输入MAC进行无感认证。这就意味着设备必须支持浏览器才能进行认证,我们的目标也在于破除这一限制。

页面后台

单看网页前台能获得的信息十分有限,接下来就要去页面的实现代码上看一看了。按下F12,进入火狐的开发者工具:

因为页面非常的简洁,所以html内容较少,在调试器下我们能找到几个独立的JavaScript文件:

不难发现,登录认证的核心在于红框内的三个文件。他们的名字非常的坦白明晰啊,authinterface应该是负责认证的接口,security可能是负责加密,login_bch肯定也和登录脱不了干系,统统拿下来研究。

交待一下,我之前从没接触过JavaScript,HTML也只是了解几个标签的运作方式,为了能看懂这几个js,就连夜预习最终达到了能看懂的水平。

其中security.js开头注释就说明了用途:

/*
* RSA, a suite of routines for performing RSA public-key computations in JavaScript.
* Copyright 1998-2005 David Shapiro.
* Dave Shapiro
* dave@ohdave.com
* changed by Fuchun, 2010-05-06
* fcrpg2005@gmail.com
*/

后面我们能看到,这是对密码传输进行加密的RSA算法。

然而三个文件加起来超过2300行,并且注释量不多,我还是决定通过调试找出整个登录的函数调用路线,去除无关内容的干扰。

刚开始做东西的时候不爱用调试工具,这一两年却是越来越喜欢了。不论软硬件都是开发一小时,调试一整天。


0x01 尝试

网络监控

仅仅是打开这些网页就有如下网络请求:

我随机写了账号和密码,点击了登录按钮:

出现了一个POST一个GET,通过类型我们能知道,GET是用来获取那张验证码图片的。那么重点就在于GET,其中一定包含了账户密码的上传认证。

果不其然,在POST的请求里我们看到了我输入的账号“1234567”,以及加密后的密码。这几个字段里queryString包含了大量对于我本机的描述,IP、mac、网络名称等信息。最后一个字段passwordEncryptTure按照字面意思来讲是开启密码加密。

以为成功了?

据此,我做了一个尝试,利用浏览器的编辑并重发功能,将这条POST中的passworEncrypt改为false,并用我的账户替换了userId字段,用我的密码明文替换了password字段内容并重新发送。我发现我已经获得了互联网连接,只是页面不会自动跳转。

进一步为了验证登录过程对其他的步骤有没有依赖,确保只发送这一个POST就可以完成认证,我用WireShark把这个POST的TCP包拿了出来,并掏出了高中以后再没用过的Python试了一下:

import socket,time

Host='172.18.18.60'
Port=8080 context='内容'
byte=context.encode() def connect(byte):
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((Host,Port))
print('[*]\r\n'+context)
s.sendall(byte)
time.sleep(0.5)
re=s.recv(1024).decode()
if("success" in re):
s.close()
return 1
else:
print('\r\n'+re)
s.close()
return 0 print(connect(byte))

结果还真的成了!?当时是晚上,我就挺高兴:没想到,这么个事这么快就解决了。

远远没有!

第二天晚上我再一次运行了脚本,却得到了如下信息:

{"userIndex":null,"result":"fail","message":"您当前使用的源IP与设备重定向地址中用户IP不一致,请重新认证!","forwordurl":null,"keepaliveInterval":0,"validCodeUrl":""}

果然人欢无好事,仅仅靠投机取巧获得的结论总是靠不住的。

感觉得出这与queryString里一长串的设备信息有关,要想发出正确的POST就必须有正确的queryString,可是queryString如何获取呢?要知道里面不仅仅有非常多的自身设备信息,更有AP设备的mac等信息,组织起来极其困难。除此以外,这些信息是加密过的,用站长工具尝试无果,除了一小部分字段是两次urlEncode,其他的加密方法即使到写这篇文章时都不得而知。

情况陷入了僵局,这就倒逼我回到我一度不愿意阅读的源码上去,毕竟源码之下无秘密。


0x02 调试

其实我从小就对浏览器按下F12的开发者工具很感兴趣,一直到这次摸索校园网登录才算是真正的用起来了。不由感叹网页的调试器真的很强大。

充分的运用搜索和倒推调用技巧之后,三个JavaScript文件的基本函数功能算是了解了,接着花了点时间学习浏览器的调试功能就直接上手了。

由于拙劣的技术,在打了无数个断点和中断事件之后,终于摸索出了整个流程。流程如下图所示:

  • 从上带下表示调用的先后顺序
  • 注意:图中对一些过程进行了简化,保留了核心功能,并不能完全代表整个过程。

流程分析

基本流程已经通过图片展示出来了,现在对图中标有数字序号的地方进行展开分析。

  1. document.location.search
  2. 隐藏的文本框
  3. passwordmac和encryptedpassword()
  4. 回调处理

1. document.location.search

这个值是对于当前的html页面来说的,也就是下图所示:

把这些值给到了queryString

2. 隐藏的文本框

看似简洁的登陆页面,在后台可以发现不少隐藏的文本框:

图中展现三个带有初始值的文本框,将他们标签中的hidden去掉以后就可以看到了。

第一个框中的true是passwordEncrypt的值,也就是默认加密。

而后两个框中的值:1000194dd2a8675fb779e6b9f7103698634cd400f27a154afa67af6166a43fc26417222a79506d34cacc7641946abda1785b7acf9910ad6a0978c91ec84d40b71d2891379af19ffb333e7517e390bd26ac312fe940c340466b4a5d4af1d65c3b5944078f96a1a51a5a53e4bc302818b7c9f63c4a1b07bd7d874cef1c3d4b2f5eb7871组成了RSA加密的公钥,这就是华科校园网加密的公钥。

3. passwordmac和encryptedpassword()

这是一个变量的名称,它的定义是: var passwordMac = password+">"+macString;

也就是,接下来被处理的不是咱们的密码,而是:'密码>mac'

这还没完,我们继续追踪下去追踪到在AuthInterFace.js里的Encryptedpassword()函数:

function encryptedPassword(password){//有删减

	var passwordEncode = password.split("").reverse().join("");//反转字符

	 var key = new RSAUtils.getKeyPair(publicKeyExponent, "", publicKeyModulus); //rsa加密公钥
var passwordEncry = RSAUtils.encryptedString(key,passwordEncode);//这里要对字符串进行反转,否则解密的密码是反的 return passwordEncry;
}

直观体会一下这个操作:

没搞明白这么做的目的是什么。。。

4. 回调处理

在发出包含一切信息的POST之后,我们会收到验证服务器JSON格式的回应,成功也好,不成功也罢,需要对响应进行处理。比如:

{"userIndex":null,"result":"fail","message":"您当前使用的源IP与设备重定向地址中用户IP不一致,请重新认证!","forwordurl":null,"keepaliveInterval":0,"validCodeUrl":""}

通过result=fail能知道认证失败,message可以告诉我们原因。

分析结果

至此,我们弄明白了网页认证的主干流程,也明白我们要做什么:

发送一个内容正确的POST给认证服务器,所以就要组织出正确的queryString。

可是分析一圈下来我们知道queryString来自于网页的的URL,可是这页面也是自己弹出来的啊!

这让网络技术薄弱的我陷入思考。。。


0x03 重定向

在这个过程中,不断地用WireShark抓包,遇到了不少的困难,好在最后找到了重定向的地址。

既然这个登陆页面可以自己弹出来,那么我们的电脑是从哪里获得这个页面的网址?百度之后,结论如下:

连接WiFi之后,系统会自动访问一些地址,比如获取时间或者专门验证是否联网的页面,在Windows下这个网址是:

http://www.msftconnecttest.com/redirect如果有网络的话最终会被转到MSN中国的页面上去。

那如果AP设置了登录页面,就会在系统自动访问上述页面的时候,通过一些手段给客户端强制返回登陆页面(重定向),然后就是我们看见的登陆页面。

为了能亲眼看看这个过程,漫长的抓包开始了。

一开始并没有什么收获,因为HTTP包数量非常多,而且我们不知道重定向页面的IP地址,也就没法进一步筛选。于是我又转向了浏览器。

我发现,如果在地址框里直接输入172.18.18.60:8080,也能跳转到带有一长串queryString的页面,毫不犹豫我勾选了调试器中的“在任何网址处暂停”

然后输入并访问172.18.18.60:8080。

页面暂停在了一个陌生的地址:123.123.123.123

这个网页没有其他内容,只有一句js:

<script>top.self.location.href='http://172.18.18.60:8080/eportal/index.jsp?wlanuserip=xxxxxxxxxxx&wlanacname=xxxxxxxxxxxxx&ssid=&nasip=xxxxxxxxxxx&snmpagentip=&mac=xxxxxxxxxxxx&t=wireless-v2&url=xxxxxxxxxxxxx&apmac=&nasid=xxxxxxxxxxxx&vid=xxxxxxxx&port=xxxxxxxxxx&nasportid=xxxxxxxxxxxxxxxx'</script>

这不就是queryString的来源吗!?

于是又发挥传统艺能验证了一下:

import socket

Host='123.123.123.123'
Port=80 con="GET / HTTP/1.1\r\nHost: 123.123.123.123\r\nUser-Agent: Python Socket\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\nAccept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\n\r\n" byte=con.encode() def connect(byte):
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((Host,Port)) s.sendall(byte) re=s.recv(1024).decode()
s.close()
if re != '' :
print(re)
href=re[(re.find('http://172')):(re.find('\'</script>'))]
print('\r\n'+href)
querystr=re[(re.find('wlanuserip')):(re.find('\'</script>'))]
print('\r\n'+querystr)
return('\r\n 1') print(connect(byte))

结果喜人,验证通过。

除此以外,还发现123.123.123.123只能在未认证的情况下访问,在后面WireShark的抓包下,又发现了几个功能类似的地址,但是他们的地址显然没有123.123.123.123这么 讨人喜欢


0x04 整合

将整个过程了解之后,实现自然是非常简单,将前文中的几个段落拼接修改之后不难得出最终版本:

直接使用Python3的socket与重定向和认证服务器建立TCP连接。

import socket,time

redirect_host='123.123.123.123'
redirect_port=80 login_host='172.18.18.60'
login_port=8080 redirect_request_str='GET / HTTP/1.1\r\nHost: 123.123.123.123\r\nUser-Agent: Python Socket\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\nAccept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\n\r\n' login_str_line='POST /eportal/InterFace.do?method=login HTTP/1.1\r\n' login_str_headers='Host: 172.18.18.60:8080\r\nUser-Agent: Python Socket\r\nAccept: */*\r\nAccept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\r\nAccept-Encoding: gzip, deflate\r\nContent-Type: application/x-www-form-urlencoded; charset=UTF-8\r\nContent-Length: duetocontent\r\nOrigin: http://172.18.18.60:8080\r\nConnection: keep-alive\r\n\r\n' login_str_content_head='userId=theuserid&password=thepassword&service=&queryString='
login_str_content_tail='&operatorPwd=&operatorUserId=&validcode=&passwordEncrypt=false' def info_request(redirect_host,redirect_port,redirect_request_str):
#在重定向处获取queryString
print('[*]requesting redirection : \r\n')
flag=0
while(1):
print('[*]trying \r\n')
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((redirect_host,redirect_port))
s.sendall(redirect_request_str.encode())
re=s.recv(1024).decode()
s.close() if(re == ''):
flag=flag+1
if(flag == 3):
return 0
continue
else:
querystr=re[(re.find('wlanuserip')):(re.find('\'</script>'))]
print('[*]requesting success \r\n') print(querystr+'\r\n')
return querystr def login(login_host,login_port,querystr,id=None,pwd=None): if(id == None or pwd == None):
print('[*]Please check the account.\r\n')
return 0 print('[*]trying to login \r\n')
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((login_host,login_port)) global login_str_headers
global login_str_content_head
login_str_content_head=login_str_content_head.replace('theuserid',id).replace('thepassword',pwd)
querystr=querystr.replace('=','%253D')
querystr=querystr.replace('&','%2526')
content=login_str_content_head+querystr+login_str_content_tail
login_str_headers=login_str_headers.replace('duetocontent',str(len(content))) login_str=login_str_line+login_str_headers+content
print(login_str+'\r\n')
s.sendall(login_str.encode())
#time.sleep(0.5)
re=s.recv(1024).decode()
s.close()
if("success" in re):
print('[*]login Successfully \r\n')
return 1
else:
print('[*]login failed \r\n')
print(re)
return 0 query=querystr=info_request(redirect_host,redirect_port,redirect_request_str)
print(login(login_host,login_port,query,id='',pwd=''))

Python水平也蛮差的,也就只能应付这样的小场面了。。。。。


0x05 测试

为了不影响正常使用,我整个过程研究的都是2.4G的信号,这也正好符合咱们的目标是ESP32这一类设备。

实际测试,两个信号都可以用脚本登录。

ESP32测试

不能忘记咱们是为什么开始的呀,ESP32才是事情的起源,也是咱们的目标。

micropython真的是非常方便,之前的代码几乎直接复制,再加一个WiFi连接就可以了,一遍过。

这个RT-Thread发布的micropython插件真的挺好用的,编辑器竟然支持代码补全。


0x06 总结

总体来讲,整个持续时间只有四五天,正值期末考试周。这个小项目成了我放(huá)水(shuǐ)的好机会。

第一次读JavaScript,第一次运用浏览器调试,第一次使用WireShark。有些过程在文章里展示的不多,但的确耗费了大量的时间。

这一次,对网络的认识又加深了几分。

在查找资料的时候,我了解到mentohust这个由10多年前的华科大神编写的校园网认证软件,了解到现在很多学校的学生都在用mentohust来进行锐捷认证,算是受到一些感召。期间,联系到一位今年刚毕业的华科计科学长,也非常感谢前辈们的帮助。

代码虽短,但也放到了GitHub上去了。如果有同好想移植到不同的平台或者用其他语言实现可以汇总起来方便查找。

仓库地址:https://github.com/HuXioAn/HUST_Wireless_login_by_socket

接下来可能会去折腾一下梅林固件、dd-wrt上的mentohust认证。


技术新人,水平一般,能力有限,如果文章中或者代码有任何疑问或者错误请不吝赐教,一定指出!

看更多相关文章或者联系我请看公众号,来找我聊聊天吧:


欢迎转载,请注明

作者:胡小安 https://blog.csdn.net/qq_28039135?spm=1000.2115.3001.5343

原文链接:https://www.cnblogs.com/huxiaoan/p/14986211.html

登录华科校园网,我用Socket的更多相关文章

  1. 致敬mentohust,路由器使用Socket认证华科校园网

    致敬mentohust,路由器使用Socket认证华科校园网 前言: 上一篇文章中,为了解决ESP32华科无线网认证的问题,我成功把网页认证机制用Python+Socket复现.但痛点依然存在,无线网 ...

  2. ssh无法登录,提示Connection closing...Socket close.

    一.问题无法ssh直接连接到服务器 [C:\~]$ ssh 192.168.7.77 Connecting to ... Connection established. To escape to lo ...

  3. Socket网络编程--FTP客户端

    Socket网络编程--FTP客户端(1)(Windows) 已经好久没有写过博客进行分享了.具体原因,在以后说. 这几天在了解FTP协议,准备任务是写一个FTP客户端程序.直接上干货了. 0.了解F ...

  4. Mir2源码详解之服务端-登录网关(LoginGate)

    传奇这款游戏,一直对我的影响很大.当年为了玩传奇,逃课,被老师叫过N次家长.言归正传,网上有很多源码,当然了,都是delphi的.并且很多源码还不全, 由于一直学习的c.c++.delphi还真不懂. ...

  5. 【MySQL案件】mysql登录-S失败

    1.1.1. mysql登录mysql时间,-S参数失效 [环境的叙述性说明] mysql5.5.14 [问题叙述性说明] 配置多个实例 实例1 实例2 datadir /home/mysql_330 ...

  6. Socket的应用案例

    java提供网络功能的四大类1.InetAddress :用于标识网络上的硬件资源.2.URL:统一资源定位符,通过URL可以直接读取和写入网络上的数据.3.Socket:使用TCP协议实现网络通信的 ...

  7. 示例:Socket应用之简易聊天室

    在实际应用中,Server总是在指定的端口上监听是否有Client请求,一旦监听到Client请求,Server就会启动一个线程来响应该请求,而Server本身在启动完线程之后马上又进入监听状态. 示 ...

  8. nodejs之socket.io 聊天实现

    写在前面:最近很火的“996”话题,可谓是引起一片热议,马老师说:能够996应该是幸运的,996是对奋斗者的一种机遇(记得不是很清楚).996缺少的是自己的空闲时间了,当我是空闲的时候偶尔996挺好的 ...

  9. 初识Socket通信:基于TCP和UDP协议学习网络编程

    学习笔记: 1.基于TCP协议的Socket网络编程: (1)Socket类构造方法:在客户端和服务器端建立连接 Socket s = new Socket(hostName,port);以主机名和端 ...

随机推荐

  1. 小米华为vivooppo手机记录隐私证据查询

    1.在拨号界面输入:*#*#4636#*#* 2.在输入代码之后 手机会自动跳转到下面这个页面 就可以查看她到底拿着手机在干嘛 2 输入下面代码可以检测小米手机的各种信息 *#*#64663#*#* 

  2. CentOS、RHEL、Asianux、Neokylin、湖南麒麟、BC Linux、普华、EulerOS请参考“1.1 CentOS本地源配置”;

      本文档适用于CentOS.RHEL.Asianux.Neokylin.湖南麒麟.BC Linux.普华.EulerOS.SLES.Ubuntu.Deepin.银河麒麟. CentOS.RHEL.A ...

  3. shell判断一个变量是否为空方法总结

    shell中如何判断一个变量是否为空 shell编程中,对参数的错误检查项中,包含了变量是否赋值(即一个变量是否为空),判断变量为空方法如下: 1.变量通过" "引号引起来 1 2 ...

  4. Linux_yum命令详解

    一.yum命令语法 yum [options] [command] [package ...] 二.yum命令常用的选项: yum options -y //自动回答为"yes" ...

  5. Nginx|Apache目录权限禁止执行PHP设置

    Ngnix: location ~ /upload/.*.(php|php5)?$ { deny all; } 这就是禁止upload内执行php,但是图片可以打开哦 多目录禁止: location ...

  6. element-ui 的el-select如何不显示value,显示value对应的label值

    有时根据需要,我们根据v-model的值绑定option, 想要的效果: 实际的效果: 原因: value的格式存在问题,数据库读取到的数据不一定为number类型,需要手动转换. 第一种 <t ...

  7. KMP算法中我对获取next数组的理解

    之前在学KMP算法时一直理解不了获取next数组的函数是如何实现的,现在大概知道怎么一回事了,记录一下我对获取next数组的理解. KMP算法实现的原理就不再赘述了,先上KMP代码: 1 void g ...

  8. Navigation 实现不同fragment之间的view的共享(含动画过渡)

    以imageView的共享举例 两个fragment都要有各自的imageview视图,id可以不同,但transitonName一定要相同, 都要指定相同的src 例如: fragment A &l ...

  9. githubssh配置

  10. [leetcode] 90. 子集 II.md

    90. 子集 II 78. 子集题的扩展,其中的元素可能会出现重复了 我们仍沿用78题的代码,稍作改动即可: 此时需要对nums先排个序,方便我们后面跳过选取相同的子集. 跳过选取相同的子集.当选取完 ...