欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

华为路由器后台登录协议实现

程序员文章站 2024-02-17 12:34:24
...

实际使用路由器为荣耀路由Pro

下面所述过程,很大部分参考了下面两篇博客
https://blog.csdn.net/Touale/article/details/113571862
https://www.ruterfu.com/2021/04/19/20210419-login-by-codes-huawei-router-WS5200/
其中第一篇博客稍显不好,只是给出简略的过程,若是对于http, js(javascript)不熟悉的人很难上手,实际实现的代码也没有,但还是给出的登录过程的关键数据。第二篇博客就很好的了,给出了整个的加密过程还有最终实现的代码。由于本人多用python,对javascript及java不是很熟悉,稍做尝试,还是用回python。感谢两位博主,特此写上自己的python实现,希望有需要的用得上。

实现过程

导入库
 

import sys
import requests
from lxml import etree
import json

import hashlib
import hmac
from Crypto import Random

其中requests用于http的交互,etree用于解析网页源码html,并获取相应字段,hashlib和hmac用于实现加密算法,Crypto也是加密算法库,但这里用的不多,仅是用来生成随机字符串
Crypto的安装为pip3 install pycryptodome

获取第一次post中需要的csrf_param,csrf_token,username,firstnonce数据

可查看第二篇博客,有关于post的抓包,这里就不放我的抓包的,用wireshark即可。
重点说明这几个数据来源:
csrf_param,csrf_token为上一次访问回复中的数据,所以第一次post时,应先访问首页来获得这两个数据
username则默认为admin,我的路由器是只有密码输入的。
firstnonce则32位长的随机字节流,在表现上可能是64位的hex码,如在post上,抓包也是,64位(只含0-9,a-f的)
 

    firstNonce = Random.get_random_bytes(32)
    result = sess.get("http://192.168.3.1/html/index.html")
    update_Information(result)

    request_header = {
    'Connection': 'keep-alive',
    'Cache-Control': 'max-age=0',
    'Origin': 'http://192.168.3.1',
    'Content-Type': 'application/json;charset=UTF-8',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'X-Requested-With': 'XMLHttpRequest',
    '_ResponseFormat': 'JSON',
    'Referer': 'http://192.168.3.1/html/index.html',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
    }

    firstNonce_str = Random.get_random_bytes(32).hex()
    firstNonce = bytes(firstNonce_str, encoding='utf-8')
    
    login_post_data = {

        'csrf': {'csrf_param': gl_csrf_param, 'csrf_token': gl_csrf_token},
        'data': {'username': 'admin', 'firstnonce': firstNonce_str},
    }

    url_routerlogin = 'http://192.168.3.1/api/system/user_login_nonce'  #/api/system/user_login_nonce
    deviceLogin = sess.post(url_routerlogin,
                                headers=request_header,  # headers http 头部信息
                                data=json.dumps(login_post_data, ensure_ascii=True),
                             #    params = {'_':int(time.time())}, #params 参数
                                cookies = gl_cookies,				  #cookies cookie
                                # allow_redirects = False,		  #allow_redirects 禁用跳转
                              #  timeout=0.5

                                )
    login_respon = json.loads(deviceLogin.text)
    save_html(deviceLogin)
    # print(deviceLogin.url)
    if(login_respon['err'] == 0):
        print('first post success')
    else:
        print('first post failed')

获取第二次post中需要的csrf_param,csrf_token,finalnonce,clientproof数据

其中csrf_param,csrf_token由上一次的post的回复可获得,finalnonce在上一次的post的回复的servernonce字段,clientproof则是密码加密后的数据,也是不同编程语言下的最大差异地方。
如第二篇博客提到的,这里实际使用的加密算法为PBKDF2,对应在python中,则为hashlib.pbkdf2_hmac

def salt_password(password, salt, iter_times):
    return hashlib.pbkdf2_hmac('sha256', password, salt, iter_times)

其过程为,

    gl_csrf_param = login_respon['csrf_param']  #字符串 特别信息,包含所有字符,不是hex表示
    gl_csrf_token = login_respon['csrf_token']  #字符串 特别信息,包含所有字符,不是hex表示
    salt = bytes.fromhex(login_respon['salt'])    #只含0-9,a-f为hex表示,str转为bytes   #firstnonce也是
    finalNonce = login_respon['servernonce']
    authMsg = firstNonce_str + ',' + finalNonce + ',' + finalNonce
    iterations_rece = int(login_respon['iterations'])

    passwd = passwd.encode()  #str to bytes

    saltPassword = salt_password(passwd, salt, iterations_rece)  # 加盐算法不同语言命名有差异hashlib.pbkdf2_hmac
    mac = hmac.new(b'Client Key', saltPassword, hashlib.sha256)  #b'Client Key'作key,msg加密
    clientKey = mac.digest()
    storeKey = hashlib.sha256(clientKey).digest()
    mac = hmac.new(bytes(authMsg, encoding='utf-8'), storeKey, hashlib.sha256)
    clientSignature = mac.digest()
    clientKey = bytearray(clientKey)
    for i in range(len(clientKey)):
        clientKey[i] = clientKey[i] ^ clientSignature[i]
    clientProof = bytes(clientKey)

salt+passwd+iterations_rece(迭代次数)---PBKDF2加密--->saltPassword

'Client Key'+saltPassword----哈希算法?hmac, sha256--->clientKey

clientKey+clientSignature----做位或运算---->clientProof

这里的关键要理清每一步操作的对象的要求,是bytes还是string,是hex的64位,还是string的32位

    login_post_data = {   #要求为字符串
        'csrf': {'csrf_param': gl_csrf_param, 'csrf_token': gl_csrf_token},
        'data': {'finalnonce': finalNonce, 'clientproof': clientProof.hex()},    #传输时为hex码,不会出现异常码,出现不是ascii的情况
    }

    url_routerlogin = 'http://192.168.3.1/api/system/user_login_proof'  # /api/system/user_login_nonce
    deviceLogin = sess.post(url_routerlogin,
                            headers=request_header,  # headers http 头部信息
                            data=json.dumps(login_post_data),
                            #    params = {'_':int(time.time())}, #params 参数
                         #   cookies=gl_cookies,  # cookies cookie
                            # allow_redirects = False,		  #allow_redirects 禁用跳转
                            #  timeout=0.5

                            )
    login_respon = json.loads(deviceLogin.text)
    if(login_respon['err']==0):
        print('login success')
    else:
        print('login failed')
    return

获取路由器网页信息

在网页中,不同页面,如连接设备,设置等,其url是
http://192.168.3.1/html/advance.html#home
http://192.168.3.1/html/advance.html#devicecontrol
其中#号是标签,实际上使用是的同一个url,http://192.168.3.1/html/advance

在chrome浏览器,按F12在Elements选项中,可以看到很复杂的html信息。但是在requests.get()及右键的"查看网页源代码"可看到实际传入的html是很简单,缺失很多信息的。
其原因在于浏览器执行了许多js脚本,在一开始获得的静态网页中,以修改的形式加载了众多的内容。找到对应加载的内容,可以在F12中network选项找,主要是XHR选项中的内容。
对于连接设备页面,js下访问的页面为http://192.168.3.1/api/system/HostInfo

完整python代码

#!/usr/bin/python
# coding=utf-8

import sys
import requests
from lxml import etree
import json

import hashlib
import hmac
from Crypto import Random

gl_cookies = {}
gl_cookie = ''
gl_cookies_path = "cookies"
gl_csrf_param = ''
gl_csrf_token = ''
gl_count = 0

firstNonce = ''
finalNonce = ''
salt = ''

#由首页获取信息
def update_Information(html):
#   html = requests.get("http://192.168.3.1/html/index.html")
    global gl_cookies
    global gl_csrf_param
    global gl_csrf_token
    
    gl_cookies = html.cookies
    url_tree = etree.HTML(html.text)
    gl_csrf_param = url_tree.xpath("//meta[@name='csrf_param']/@content")[0]
    gl_csrf_token = url_tree.xpath("//meta[@name='csrf_token']/@content")[0]

def save_html(html):
    global gl_count
    gl_count += 1
    with open("H:%d.html" % gl_count, "wb") as f:
        f.write(bytes(html.text, encoding='utf-8'))

def salt_password(password, salt, iter_times):
    return hashlib.pbkdf2_hmac('sha256', password, salt, iter_times)

def Main_Login(sess, username, passwd):
    global gl_cookie
    global gl_cookies
    global gl_csrf_param
    global gl_csrf_token

    firstNonce = Random.get_random_bytes(32)
    result = sess.get("http://192.168.3.1/html/index.html")
    update_Information(result)

    request_header = {
    'Connection': 'keep-alive',
    'Cache-Control': 'max-age=0',
    'Origin': 'http://192.168.3.1',
    'Content-Type': 'application/json;charset=UTF-8',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'X-Requested-With': 'XMLHttpRequest',
    '_ResponseFormat': 'JSON',
    'Referer': 'http://192.168.3.1/html/index.html',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
    }

    firstNonce_str = Random.get_random_bytes(32).hex()
    firstNonce = bytes(firstNonce_str, encoding='utf-8')
    
    login_post_data = {

        'csrf': {'csrf_param': gl_csrf_param, 'csrf_token': gl_csrf_token},
        'data': {'username': 'admin', 'firstnonce': firstNonce_str},
    }

    url_routerlogin = 'http://192.168.3.1/api/system/user_login_nonce'  #/api/system/user_login_nonce
    deviceLogin = sess.post(url_routerlogin,
                                headers=request_header,  # headers http 头部信息
                                data=json.dumps(login_post_data, ensure_ascii=True),
                             #    params = {'_':int(time.time())}, #params 参数
                                cookies = gl_cookies,				  #cookies cookie
                                # allow_redirects = False,		  #allow_redirects 禁用跳转
                              #  timeout=0.5

                                )
    login_respon = json.loads(deviceLogin.text)
    save_html(deviceLogin)
    # print(deviceLogin.url)
    if(login_respon['err'] == 0):
        print('first post success')
    else:
        print('first post failed')

    #  第二次post
    gl_csrf_param = login_respon['csrf_param']  #字符串 特别信息,包含所有字符,不是hex表示
    gl_csrf_token = login_respon['csrf_token']  #字符串 特别信息,包含所有字符,不是hex表示
    salt = bytes.fromhex(login_respon['salt'])    #只含0-9,a-f为hex表示,str转为bytes   #firstnonce也是
    finalNonce = login_respon['servernonce']
    authMsg = firstNonce_str + ',' + finalNonce + ',' + finalNonce
    iterations_rece = int(login_respon['iterations'])

    passwd = passwd.encode()  #str to bytes

    saltPassword = salt_password(passwd, salt, iterations_rece)  # 加盐算法不同语言命名有差异hashlib.pbkdf2_hmac
    mac = hmac.new(b'Client Key', saltPassword, hashlib.sha256)  #b'Client Key'作key,msg加密
    clientKey = mac.digest()
    storeKey = hashlib.sha256(clientKey).digest()
    mac = hmac.new(bytes(authMsg, encoding='utf-8'), storeKey, hashlib.sha256)
    clientSignature = mac.digest()
    clientKey = bytearray(clientKey)
    for i in range(len(clientKey)):
        clientKey[i] = clientKey[i] ^ clientSignature[i]
    clientProof = bytes(clientKey)

    login_post_data = {   #要求为字符串
        'csrf': {'csrf_param': gl_csrf_param, 'csrf_token': gl_csrf_token},
        'data': {'finalnonce': finalNonce, 'clientproof': clientProof.hex()},    #传输时为hex码,不会出现异常码,出现不是ascii的情况
    }

    url_routerlogin = 'http://192.168.3.1/api/system/user_login_proof'  # /api/system/user_login_nonce
    deviceLogin = sess.post(url_routerlogin,
                            headers=request_header,  # headers http 头部信息
                            data=json.dumps(login_post_data),
                            #    params = {'_':int(time.time())}, #params 参数
                         #   cookies=gl_cookies,  # cookies cookie
                            # allow_redirects = False,		  #allow_redirects 禁用跳转
                            #  timeout=0.5

                            )
    login_respon = json.loads(deviceLogin.text)
    if(login_respon['err']==0):
        print('login success')
    else:
        print('login failed')
    return


if __name__ == "__main__":
    sess = requests.Session()

    username = 'admin'  # str(input('帐号:'))
    passwd = '123456'  # str(input('密码:'))
    Main_Login(sess, username, passwd)

    result = sess.get('http://192.168.3.1/api/system/HostInfo')
    false = False
    true = True
    result = eval(result.text)
 #   save_html(result)
    for device in result:
        if(device['Active']):
            print(device['HostName'])
  #  print(result.text)
    sys.exit(0)

 

相关标签: 树莓派 python