請告訴我,你們有沒有經常被蹭網的經歷?
簡單地說,就是路由器 WIFI 密碼設定得太簡單,很容易就被別人猜到了,然後就悲劇了。
這種情況下通常好像我們能做的也就是重新修改密碼。
然而過了一段時間,悲劇又戲劇性地再次上演。
老是這樣可不行啊!
即使是設定一個超長超強的密碼,一旦有人知道了,那麼後果就是用不了幾天,整層樓甚至整棟樓的人可能就都知道了。
這時只有一個變相的辦法,就是頻繁修改密碼。
似乎有很多安全專家也鼓勵我們要經常性地更新自己的密碼。
道理我懂,可我總不能一天到晚動不動就去修改密碼啊!
要是手上有一二十個路由器也這麼幹?
難道要逼我躺平?
哎,先別慌,想辦法讓它自動更新密碼不就得了!
於是我就開始動腦筋了,思考著有沒有一個好一點的辦法,能讓路由器定時地自動地修改密碼。
最初我想到的是,能不能透過 Telnet 遠端連線到路由器,然後使用 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