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