0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はじめてのスクレイピング ASUS製ルーターから情報を取得

Posted at

ASUSルーターのスクレイピング

ASUSのルーター上から、HTTPS/SSL証明書ファイルを自動で取得することを考えて作ってみました。

経緯

DDNSをASUSルーターのサービスで設定し、「Let's Encryptから無料の証明書を取得」というオプションを選択していると、ASUSルーターがHTTPS/SSL証明書を自動で取得してくれます。
そして証明書を使用したい場合は、この画面からエクスポートする必要があります。

ルーター管理ページ -> WAN -> DDNS より
image.png

私はHome Assistantというスマート家電系の制御を統括してくれるサービスにてHTTPS接続を利用しているのですが、90日ごとに自動で更新される証明書を、更新が行われるたびにルーターの管理ページへアクセスし、エクスポート、ダウンロードして解凍、そして証明書をHome Assistantのsslフォルダへ移動…といった手順を踏んでいて非常に面倒でした。

ルーター上で更新されるこの証明書ファイルをなんとか自動で持ってこれないか?というのが今回の経緯・目的となっています。

HTTPS/SSL証明書をASUS任せにしない方が、Home Assistant側のアドオンなりを使って楽に解決できそうだけど…

ウェブページの動作を確認

まずはFirefox上で管理ページの動作を確認します。
ウェブ開発者ツールを開きます。
ネットワークタブを開き「永続ログ」を有効にしたら、ログインしてみます。
image.png
結果はこのようになっていました。メソッドに注目すると、初めにlogin.cgiへPOSTを行っているようです。その後index.aspGETし、ログイン後のメインページを読み込んでいることがわかります。
image.png
POSTの詳細を見てみましょう。クリックすると右側へ細かい情報が表示されます。
image.png
応答ヘッダーを見ると、Set-Cookie:としてasus_tokenというのが帰ってきているのがわかります。これを使ってログインするのでしょうか。

「要求」タブも見てみます。
image.png
送信したフォームデータには以上のものが含まれているようです。重要なのはlogin_authorizationでしょう。ランダムな文字列が設定されていたので、おそらくログインユーザー名とパスワードを暗号化した文字列だと思われます。Main_Login.aspの中を見ると、どうやらlogin()というjsの関数で暗号化してPOSTしていそうな様子が見れました。

以上を踏まえて、index.aspGETを詳しく見てみましょう。
image.png
先ほどPOSTの返答で帰ってきていたasus_tokenをCookieに含めていることがわかります。

何回かログインを試すとわかりますが、このトークンはログインを行うたびに変わっていることがわかります。

そして件のサーバー証明書ですが、ただルートディレクトリにあるcert_key.tarGETしているだけでした。
image.png
要求ヘッダーにはやはりasus_tokenが設定されていたので、試しにログインせずにこのURLをGETしたところ、ログインページへリダイレクトされるだけでした。
image.png

ウェブページ動作のまとめ

あくまで私の推測ですが、大体このような形かと思われます。

ファイル 役割・動作
Main_Login.asp ログインフォームの提供
ログイン情報の暗号化
login.cgiへログイン情報のPOST
login.cgi ログイン情報の受け取り
ルーター内のユーザー情報と照合
正しければログイントークンを返答
index.asp メインページ
cert_key.tar 目的のファイル
??? ログイントークンの照合
間違っていたらリダイレクト

以上より、login.cgiへ「暗号化されたログイン情報」であるlogin_authorizationPOSTし、返答のasus_tokenをクッキーへ含めてcert_key.tarGETリクエストを送れば、目的のファイルを取得できそうです。
メインページの情報についても、同様に色々取れそうですね。

コーディング

以上の予想について、Pythonを用いてコードにしてみます。

ログイン部分

ルーターへログイン
import requests
import tarfile

# 接続先のURL
login_url = 'http://router.asus.com/login.cgi'

login_header = { # ブラウザの要求ヘッダーをコピペ
	"Host": "router.asus.com",
	"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0",
	"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
	"Accept-Language": "ja,en-US;q=0.7,en;q=0.3",
	"Accept-Encoding": "gzip, deflate",
	"Referer": "http://router.asus.com/Main_Login.asp",
	"Content-Type": "application/x-www-form-urlencoded",
	"Content-Length": "157",
	"Origin": "http://router.asus.com",
	"DNT": "1",
	"Connection": "keep-alive",
	"Upgrade-Insecure-Requests": "1",
	"Pragma": "no-cache",
	"Cache-Control": "no-cache",
}
login_data = { # ブラウザの「要求」タブから確認したフォームデータ
	"group_id": "",
	"action_mode": "",
	"action_script": "",
	"action_wait": "5",
	"current_page": "Main_Login.asp",
	"next_page": "index.asp",
	"login_authorization": "ログイン情報", # 暗号化されたユーザー名+パスワードの文字列
	"login_captcha": ""
}

# URLへデータをPOSTした結果を保存
login_res = requests.post(
  login_url, # 目的のURLへ
  data = login_data, # フォームデータを送信
  headers = login_header # ブラウザのヘッダーを使用
)
login_res.raise_for_status() # 例外処理

# 結果のクッキーからトークンが取得できたら成功
login_token = login_res.cookies.get('asus_token')
if login_token:
	print('login success!')
    print('asus_token =',asus_token)
else:
	print('login failed...')

以上のコードを実行した結果、asus_tokenを無事取得できました。

実行結果
login success!
asus_token = 8NSx7HO~~~MaUpTUj

ところでこういうトークンは別に晒しても問題ないはずですが、なんとなく隠したくなります。

それでは得られたasus_tokenを用いて目的のファイルを入手したいと思います。

証明書ファイルダウンロード部分

証明書ファイルのダウンロード
# 証明書ファイルのURLを設定
cert_url = 'http://router.asus.com/cert_key.tar'
# 先ほど同様にヘッダーをブラウザからコピペ
# Cookieに関してのみ、取得したasus_tokenを使用
cert_header = {
	"Host": "router.asus.com",
	"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0",
	"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
	"Accept-Language": "ja,en-US;q=0.7,en;q=0.3",
	"Accept-Encoding": "gzip, deflate",
	"DNT": "1",
	"Connection": "keep-alive",
	"Cookie": "asus_token=" + login_cookie + "; clickedItem_tab=0", # 取得したasus_tokenは変数login_cookieに格納されている
	"Upgrade-Insecure-Requests": "1",
	"Pragma": "no-cache",
	"Cache-Control": "no-cache",
}
# URLにGETした結果を保存
get_cert_res = requests.get(
  cert_url,
  headers = cert_header,
)
get_cert_res.raise_for_status() # 例外処理

# 結果のコンテンツをtarとして扱う
with open('cert_key.tar', "wb") as f:
	f.write(get_cert_res.content)
try: # 解凍できるか試す。できなかったら取得に失敗している
	with tarfile.open('cert_key.tar', 'r') as tar: # 解凍
		tar.extractall(path="cert_key') # cert_keyというフォルダに保存
	print('got a certification')
except:
	# traceback.print_exc()
	print('getting certification failed...')
	exit()

結果、pythonスクリプトのあるフォルダに.tarファイルと解凍された.pemファイルが確認できました。
image.png
これだけでもかなりの時短になりますが、ついでなのでHome AssistantのSambaサーバーへファイルを送りましょう。

SMBでのファイル送信
samba_target_ip = '192.168.50.133' # 接続先のIP
samba_connection_port = 445 # Sambaのポート デフォルトで445
conn = SMBConnection( # Sambaコネクション用の情報
    'user', # サーバーへログインするユーザー
    'password', # パスワード
    'python_user', # おそらくクライアントの名前…?
    'workgroup' # ワークグループの名前のはず…?
)
try: # つなぐ
	conn.connect( samba_target_ip, samba_connection_port )
	print(conn.echo('connection success'))
except:
  print('connect error...')
  exit()

# それぞれのファイルをsslフォルダへ転送
with open('./cert_key/cert.pem', 'rb') as file:
	try:
		conn.storeFile('ssl', 'cert.pem', file)
		print(file.name,'transfered successfully')
	except:
		print(file.name,' transfered error...')
		exit()
    
with open('./cert_key/key.pem', 'rb') as file:
	try:
		conn.storeFile('ssl', 'key.pem', file)
		print(file.name,'transfered successfully')
	except:
		print(file.name,' transfered error...')
		exit()
    
conn.close() # 終わったらコネクションを閉じる

# ローカルのゴミを削除
try:
	os.remove('./cert_key/cert.pem')
	os.remove('./cert_key/key.pem')
	os.remove('./cert_key.tar')
except:
  print('error occured while removing a file')
finally:
	print('remove success!\nall steps finished successfully')

実行した結果無事に目的のフォルダへファイルを転送できました。

これでとりあえずスクリプトを実行しさえすれば一瞬で証明書ファイルの更新ができる…
image.png

あとはHome Assistant側で証明書のセンサーを作成し、expiredが検知されたら自動でこのスクリプトを実行する形にすればよさそうです。Home Assistant側にpythonスクリプトを実行させるか、またはセンサー値表示用ラズパイ等にREST API機能を載せて、HAから呼ばれたらこのスクリプトを実行するという形も面白そうです。
これでようやくアレクサに声をかけても応答しないという煩わしさから解放されます!

終わり?

スクレイピングに興味があったためこのような力業で解決しましたが、Home Assistantの証明書ファイルは本来 Let's encryptのアドオンなどを使用して自動で更新させるもののはずなので、余裕が生まれたらそのやり方に変えたいです……。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?