網管小賈 / sysadm.cc
請告訴我,你們有沒有經常被蹭網的經歷?
簡單地說,就是路由器 wifi 密碼設置得太簡單,很容易就被別人猜到了,然后就悲劇了。
這種情況下通常好像我們能做的也就是重新修改密碼。
然而過了一段時間,悲劇又戲劇性地再次上演。
老是這樣可不行啊!
即使是設定一個超長超強的密碼,一旦有人知道了,那么后果就是用不了幾天,整層樓甚至整棟樓的人可能就都知道了。
這時只有一個變相的辦法,就是頻繁修改密碼。
似乎有很多安全專家也鼓勵我們要經常性地更新自己的密碼。
道理我懂,可我總不能一天到晚動不動就去修改密碼啊!
要是手上有一二十個路由器也這么干?
難道要逼我躺平?
哎,先別慌,想辦法讓它自動更新密碼不就得了!
于是我就開始動腦筋了,思考著有沒有一個好一點的辦法,能讓路由器定時地自動地修改密碼。
最初我想到的是,能不能通過 Te.NET 遠程連接到路由器,然后使用 Cli 命令修改密碼。
這招看似可行,雖然我可以控制程序自動 Telnet 連接,可是,大多數情況下我們用的都是家用路由器啊,那玩意根本就不支持 Telnet 連接啊喂!
好像只有所謂企業版的路由器有支持 Cli 指令,但這并沒有什么普遍性。
呵呵,此路不通!
兩千年后,我突發奇想、靈光乍現,有了一個奇葩想法。
既然我們平時修改密碼都是在路由器的管理頁面上點來點去的,那我們能不能通過模擬鼠標點擊來實現修改密碼的功能呢?
呃...這個想法好像有點瘋狂哈!
哥們靠譜不?
肚子里帶著那么一點點可憐的 JS 知識,我決定搏一把看看。
也沒有其他辦法了不是嗎,上吧!
于是我找來了一臺舊版式管理頁面的 Tp-link 路由器(型號 TL-WR880N ),就它了!
實驗準備工作:
- 一臺 windows 10 系統電腦
- 火狐(或谷歌)瀏覽器
- 一根網線連接到路由器的 LAN 口
- 設定路由器與電腦為可互訪的同一網段 IP 地址
組織代碼,編寫程序
首先,我們要搞定頁面自動登錄。
使用火狐打開路由器管理首頁,按下 F12 開啟調試控制臺界面,點擊 查看器 一項,再點下左邊的那個小箭頭,然后將鼠標定位到密碼輸入欄處。
OK,順利找到了密碼框的 id ,為 pcPassword 。
相應的代碼如下(假定登錄密碼是 123456 ),那么我們就可以自動填充密碼了。
document.getElementById('pcPassword').value = '123456';
密碼自動填上了,我們還需要點擊確定按鈕,有了這個動作才能正常登錄。
用相同的方法,定位確定按鈕的 id ,為 loginBtn 。
同樣用代碼來模擬點擊。
document.getElementById('loginBtn').click();
可以先測試一下,切換到 控制臺 標簽頁,然后輸入前面那兩行代碼。
然后回車,即可使用代碼自動登錄進入管理頁面。
這個過程中沒有真正用鼠標去點擊,而只是用了代碼,是不是很神奇?
這里需要說明一點,通常頁面登錄驗證信息是加密的,所以想通過直接提交驗證鏈接的方法來實現登錄有些困難。
好,登錄進來之后,我們就要找一找在哪里可以修改密碼,這才是我們的主要目的。
需要事先說明的是,通常 Tp-link 舊款式管理頁面使用的是框架結構,就是傳說中的 frameset 標簽元素,所以實際上它的左側菜單和右側內容是分別屬于不同的框架區域的。
類似于如下這個樣子。
<frameset>
<frameset>
<frame>頂部內容</frame>
</frameset>
<frameset>
<frameset>
<frame name="bottomLeftFrame">左側菜單</frame>
</frameset>
<frameset>
<frame name="mainFrame">右側內容</frame>
</frameset>
</frameset>
</frameset>
總之大框架里套小框架這樣子的形式,是不是有點眼花,所以說不太推薦用這種框架。
基于這種框架元素的間隔,左右兩側的代碼是有些不一樣的,而用名字 name 來區分,左側名字叫 bottomLeftFrame ,而右側名字叫 mainFrame 。
而左右兩側的元素就得跟著自己所在的框架走了,也就是它們的代碼前綴會有些不同,下面會詳細說到。
好,框架大概了解了,我們來找一下設置密碼的地方。
在左側菜單中,找到 無線安全設置 菜單項,它的 id 為 a9 。
如要點擊它,代碼應該是這個樣子,注意前面要加上所在框架(左側框架 bottomLeftFrame )。
parent.frames.bottomLeftFrame.document.getElementById('a9').click();
OK,我們來到了 WIFI 密碼設置頁面,接下自然就是想辦法修改這里的密碼了。
依舊如法炮制,獲取到密碼框的 id 為 pskSecret 。
給它重新賦值就很簡單了,用如下代碼,注意這次它是在右側主框架 mainFrame 中了。
parent.frames.mainFrame.document.getElementById('pskSecret').value = '66666666';
注意此處密碼要求 8 位以上,小于8位會出錯而導致程序執行失敗。
密碼改好了,接著我們要保存才能生效對吧,找一找保存按鈕吧。
頁面最下面有保存按鈕,id 也找到了,是 Save 。
parent.frames.mainFrame.document.getElementById('Save').click();
嗯,看到這兒是不是感覺還挺簡單的?
其實后面還有很多坑呢。
你瞧,這坑說來就來了!
坑之一,在點擊保存按鈕的代碼順利執行后,你會發現它會彈出個提示,告訴你重啟路由器密碼才能生效。
我用手機試著連接過,的確只有重啟后新密碼才有用。
所以接下來還得研究一下如何讓它重啟。
活還沒干完哈,繼續上路!
一般來說,系統管理頁面中是自帶有重啟路由器的菜單項的。
果不其然,找到了它,確認 id 為 a44 。
那么點擊它的代碼就是如下了,別忘記它是屬于左側框架中的哦。
parent.frames.bottomLeftFrame.document.getElementById('a44').click();
再找到 重啟路由器 按鈕的 id 為 reboot 。
parent.frames.mainFrame.document.getElementById('reboot').click();
在這兒看似坑一被填上了,嘿嘿,可惜別高興得太早。
雖然我們可以點擊重啟按鈕了,可是它喵的居然彈出個確認提示框來。
嘿,我勒個去啊!
這個確定要怎樣才能點擊上呢?
查了大半天的資料,有網友說可以這么搞,說是可以覆蓋原 windows.alert 方法,這樣它就不彈出來了。
類似于以下幾種都可以,通過覆蓋并返回 false 來規避。
@grant unsafeWindow
unsafeWindow.alert = function(){return false};
window.alert = function(){return false};
Window.prototype.alert = function(){return false};
可惜太扯了,這種方法是無效的,原因很簡單,有兩個。
一個是 alert 是阻塞式的,也就是說當彈出窗口時,后面的代碼就中斷了,根本就不執行,又如何把它關閉呢。
二個是無法覆蓋,反正我沒成功過,但再轉念一想,即使覆蓋成功了,也無法達到目的。
因為它是要確認 true 或 false 的,如果覆蓋了,之后代碼又如何走呢?
基于以上原因,我決定換個思路。
比如說,看我能不能修改原代碼,使其確認自動返回 true 不就行了!
這個...好使不?
你還別說,真讓我給找著了!
我將重啟路由器的頁面保存下來,順藤摸瓜找到了提交表單的元素項,最后定位到了其中有一個叫作 onsubmit 的標簽。
onsubmit="return doSubmit();"
很顯示,這玩意應該就是提交重啟時的函數代碼啊!
然后我接著找,找這個叫作 doSubmit() 的函數。
果然在隔壁胡同尋到了它的身影。
代碼整理如下:
function doSubmit(){
if(confirm("確認重新啟動路由器?")){
return true;
} else {
return false;
}
}
找到這兒就已經很清楚了,這個 doSubmit() 就是按確認提示后返回 true 或 false 來進行判斷是否重啟。
那么這下就簡單了,只要我主動返回給 onsubmit 這一元素 true 值不就行了唄。
那這代碼應該怎么寫呢?
我來來回回找了半天,也沒找著 form 的 id 是什么,這叫我怎么獲取 form 的元素節點呢?
世上無難事,只怕有心人啊,還好這個頁面相當簡單,只有一個 form 標簽,那么完全可以用 getElementsByTagName 來獲取標簽元素。
當然了,這個和 getElementsByName 一樣,獲取到的是一個數組,只有一個標簽的話那通常就是在數組的第一個成員了,也就是數組長度只有 1 。
所以代碼寫成了下面這個樣子。
parent.frames.mainFrame.document.getElementsByTagName("form")[0].onsubmit=true;
好,我們再來試一試哈。
先給 onsubmit 賦值 true ,然后再來點擊重啟按鈕。
哈哈,OK 了!成功無視確認直接重啟路由器!
哈哈,很興奮吧,可惜前面說了,這個只是坑一,騷年別激動,后面還有坑哩!
從坑里跳出來,我們接著說下一個坑。
自動化處理
前面這些代碼,實際上只能通過手動方式輸入到控制臺上執行。
可是我想要的是自動修改密碼的效果呀,怎么才能自動化處理執行呢?
這個時候就要請大名鼎鼎的油猴登場了!
油猴有很多,我用的是 Tamper Monkey 。
它是火狐或谷歌等瀏覽器的一個擴展或插件,用于自動執行用戶自定義 JS 代碼。
感覺評分好像最高,于是就選了它。
說實話,我也是第一次用它,對它的一切不是很熟悉,所以接下來的操作都非常適合新手小白。
如何安裝我就不說了,作為瀏覽器插件安裝起來非常簡單方便。
接下來還是說一說如何實現自動化處理 JS 代碼,這才是重點對吧。
頭一次,我簡單粗暴地把前面的那些代碼機械地羅列到了油猴中,可惜很快我就慘敗了。
原因很簡單,頁面加載往往需要一點的時間間隔,而在頁面加載完成前,代碼已經跑完了。
為了讓代碼能趕上上實際頁面加載情況,所以我們需要給代碼加上延時。
setTimeout(function() {
...
}, 1000);
這個其實就是坑二,延時是根據頁面加載的速度決定的,通常你可以設定得長一些,比如 3 到 5 秒的樣子。
另外在點擊或跳轉頁面時,也會出現加載頁面的情況,所以基本上每一步操作都要加上延遲。
之后的完整代碼會展示這一點。
如果這個時候你迫不及待地將代碼放到油猴里跑上一跑,你會發現似乎真的可以做到自動登錄、自動修改密碼、并自動重啟路由器。
哇!太棒了!這不就是我們想要的嗎!
我們成功了!
如果你發出如此感嘆,我只能說你還是太年輕了,至少文章在這里才剛過一半。
要知道,當路由器重啟后,頁面就會自動重新加載,而只要頁面加載,油猴中的代碼就會自動開始執行。
此時你的代碼就會再次執行一次,然后路由器又重啟了,如此往復、沒完沒了,讓人流淚,令人心碎。
沒錯,這就是接下來要說的第三個坑!
禁止頁面重新加載
為什么要禁止頁面重新加載,剛才也說了,就是防止因頁面重啟加載而導致程序重頭再跑一遍。
但是,我想你會說,不重啟 WIFI 密碼就無法生效啊。
其實事實并不完全是這樣的,事實真相是,路由器的確要重啟才能生效,與此同時頁面會重新加載。
而我們希望的是路由器可以重啟,但并不希望頁面重新加載或刷新。
怎么辦?
方法可能有很多種,只不過我是菜鳥小白,我實在找不出其他高明一些的辦法。
最后,我只能通過點擊其他菜單項來跳轉到其他頁面,從而通過這樣一種看似蠢笨的方式變相地改變觸發重啟倒計時。
比如在重啟倒計時時,點擊一下修改密碼的菜單項,回到密碼修改頁面。
測試的結果是,這樣做還真的可行!
// 跳轉到其他頁面,以防重啟而導致刷新頁面重新加載JS
parent.frames.bottomLeftFrame.document.getElementById('a9').click();
這下好了,只要頁面不重新加載刷新,程序就不會被初始化,我們也就不用擔心它無腦式地重復執行了。
不過,這樣就算完了嗎?
生成 WIFI 密碼的算法
我們修改 WIFI 密碼的最初目的是防止他人蹭網,但至少我們自己應該能用啊。
所以我們必須要有一套別人無法識別,但自己卻門兒清的密碼算法。
當然這種算法可以復雜也可以簡單。
舉例,我將日期作為密碼,比如今天是 2021年06月01日 ,那么密碼就是 20210601 。
到了第二天,那么密碼自動修改為 20210602 ,以此類推。
所以就很簡單了,只要程序能獲取到當前的日期即可。
可是還有一個問題,就是程序什么時候修改密碼。
總不能一會兒就改一次,路由器可是要重啟生效的。
所以必須要有一個判斷,即當天只能修改一次。
基于以上,我們可以得出結論,程序每間隔一定的時間循環判斷,當日期變動時,自動修改密碼并重啟生效。
好了,另一個問題也就隨之而來,程序如何判定日期已經變動了?
通常做法是設定一個變量,這個變量存放了當前日期。
等到了第二天,與這個變量對比,就修改密碼同時將新的日期放到這個變量中,以備后面再行判斷。
想法是不錯,可是油猴腳本并不提供數據持久化功能。
就是說,我想將某些信息保存到本地文件中,但這實現不了。
所以說,只能是一次程序跑到底,讓這個變量永遠保存到內存中不丟棄。
這也是前面不讓頁面重新加載的另一個原因。
完整代碼示例
有了算法,再加上前面雜七雜八的條件,終于第一版的代碼形成了。
// ==UserScript==
// TP-Link 路由器 型號 TL-WR880N 測試通過
// @name 定時修改路由器 WIFI 密碼
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 網管小賈的博客 / www.sysadm.cc
// @author @網管小賈
// @match http://192.168.1.1/
// @icon https://www.google.com/s2/favicons?domain=15.213
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Your code here...
//頁面完全加載后運行
window.onload=function autorun() {
console.log('頁面加載完畢,可以執行代碼!!');
Date.prototype.Format = function (fmt) {
let o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小時
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (let k in o) {
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
}
return fmt;
};
var currentDate = (new Date()).Format("yyyyMMdd");
var checkDate = '';
function changeWifi() {
currentDate = (new Date()).Format("yyyyMMdd");
if (currentDate != checkDate) {
console.log('Different! - currentDate: ' + currentDate + ' | checkDate: ' + checkDate);
setTimeout(function() {
try {
// 登錄
document.getElementById('pcPassword').value = '123456';
document.getElementById('loginBtn').click();
}
catch (e) {
}
setTimeout(function() {
try {
// 跳轉至修改 WIFI 密碼頁面
parent.frames.bottomLeftFrame.document.getElementById('a9').click();
currentDate = (new Date()).Format("yyyyMMdd");
setTimeout(function() {
try {
// 避免重復修改
if (parent.frames.mainFrame.document.getElementById('pskSecret').value != 'Sysadm' + currentDate) {
// 修改 WIFI 密碼
parent.frames.mainFrame.document.getElementById('pskSecret').value = 'Sysadm' + currentDate;
// 保存
parent.frames.mainFrame.document.getElementById('Save').click();
setTimeout(function() {
try {
// 跳轉至重啟頁面
parent.frames.bottomLeftFrame.document.getElementById('a44').click();
setTimeout(function() {
try {
// 修改重啟提示為 true
parent.frames.mainFrame.document.getElementsByTagName("form")[0].onsubmit = true;
// 確認重啟
parent.frames.mainFrame.document.getElementById('reboot').click();
setTimeout(function() {
// 跳轉到其他頁面,以防真的重啟而導致刷新頁面重新加載JS
parent.frames.bottomLeftFrame.document.getElementById('a9').click();
checkDate = currentDate;
}, 1000);
}
catch (e) {
checkDate = '';
}
}, 1000);
}
catch (e) {
checkDate = '';
}
}, 1000);
}
}
catch (e) {
checkDate = '';
}
}, 1000);
}
catch (e) {
checkDate = '';
}
}, 1000);
}, 2000);
} else {
console.log('Same! - currentDate: ' + currentDate + ' | checkDate: ' + checkDate);
}
}
var myVar;
myVar = setInterval(changeWifi, 1 * 10 * 1000);
// console.log(myVar);
}
})();
代碼中,默認管理員登錄密碼是 123456 ,修改的 WIFI 密碼是 前綴 Sysadm + 當前日期 。
程序每 10 秒循環執行一次。
同時判斷當前日期 currentDate 與 檢查日期 checkDate 是否一致。
如果兩者一致,則跳過待機,如果不一致,則修改密碼。
本程序代碼在Tp-link 路由器(型號 TL-WR880N )上測試通過。
完整 JS 代碼下載:
定時修改路由器 WIFI 密碼.7z (29.5K)
下載鏈接:
https://pan.baidu.com/s/1I-Vg-WWwwJbMfu0jXNp64A
提取碼:<關注公眾號,發送 000841>
最終效果動圖演示:
寫在最后
經過幾天的測試,其效果基本上可以做到自動修改為當天的密碼,程序高效、外貌拉風、省時省力,值得擁有!
本文測試所用 Tp-link 路由器為常見家用式路由器,管理網頁界面為舊版風格。
如果你用的就是這個舊版式風格的網頁管理,就可以直接拿來測試使用。
當然,有機會我還會做一篇新版風格界面的相關文章。
此外如果你是其他品牌的路由器,也可以利用本文的思路來定制適合自己品牌和型號路由器的程序代碼,從而實現最終想要的效果。
本文涉及的坑點比較多,故囿于語言組織能力,表述上可能有言不達意之處,還請小伙伴們海涵!
希望小伙伴們積極關注我,多多點贊轉發,多多批評指教!
網管小賈 / sysadm.cc