登錄華科校園網,我用Socket


登錄華科校園網,我用Socket

導語:

找一個華科學生問一問,學校的網絡怎么樣?得到的大多數是負面回答。其實不論是從覆蓋區域、網絡穩定性、還是速度來說,華科做的都還是可以的(24:00斷網除外)。可是有一點我從進校以來就一直不爽,那就是校園網的認證方式是有線銳捷+無線web頁面組合,並且無線網不能輸入MAC來指定無感認證設備。真的是非常的安(má)全(fàn)啊!

這就意味着像esp32這類MCU沒法使用無線網,特別是大一學生不能開通有線也沒法裝路由器,當時想用esp32做點東西的我十分郁悶。我從來到華科的第一天就想搞它了。使用Socket直接模擬網頁認證,讓esp32也能直接聯網。

補充 :

做完之后也看了網上類似的博客,其他學校的同學也用Socket進行過類似的認證,可大部分沒有提及跳轉重定向加密等重要部分,而且也都比較簡短,沒有分析整個認證過程,所以這一篇就盡量詳細的還原整個過程,並且使用ESP32+micropython進行測試通過。所以,多圖預警

工具 :

FireFox瀏覽器、WireShark、Python3



0x00 觀察

登錄過程

華科校園網認證頁面

這就是認證頁面,在手機端上的模樣與電腦端大同小異。一般我們輸入正確的用戶名(學號)、密碼再點擊按鈕就能跳轉到認證成功的頁面上去了:

認證成功頁面

一般的,我會給電腦和手機開啟無感認證,每次連接到校園網就不必手動認證,缺點在於不支持輸入MAC進行無感認證。這就意味着設備必須支持瀏覽器才能進行認證,我們的目標也在於破除這一限制。

頁面后台

單看網頁前台能獲得的信息十分有限,接下來就要去頁面的實現代碼上看一看了。按下F12,進入火狐的開發者工具:

頁面html

因為頁面非常的簡潔,所以html內容較少,在調試器下我們能找到幾個獨立的JavaScript文件:

關鍵的三個js

不難發現,登錄認證的核心在於紅框內的三個文件。他們的名字非常的坦白明晰啊,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 嘗試

網絡監控

僅僅是打開這些網頁就有如下網絡請求:

網絡請求1

我隨機寫了賬號和密碼,點擊了登錄按鈕:

登錄請求gif

出現了一個POST一個GET,通過類型我們能知道,GET是用來獲取那張驗證碼圖片的。那么重點就在於GET,其中一定包含了賬戶密碼的上傳認證。

POST

果不其然,在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頁面來說的,也就是下圖所示:

頁面URL

把這些值給到了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

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這一類設備。

image-20210708110429180

實際測試,兩個信號都可以用腳本登錄。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM