Skip to content
Advertisement

Unable to login to router page using python

I have a ISP provided router which does not allow me ssh or telnet access but I have access to login page using user credentials. I want to setup a python script that can login to the router and restart it at fixed intervals. I have tried to replicate the process in code but as I am not knowledgeable with Javascript I am not sure where the issue is. Here is the router page

The username and password is encoded in submit function with token and nonce variables.

function submit() { 
    var username = $(":input[id=username]").val();
    var password = $(":input[id=password]").val();
    var nonce = "xdtQP+ohCWNJ+cFPgHA+6METS83JPNO8qwrmFRV0Fos=";   
    var token ="jFwIaetZSYKVzzDg";

        var base64 = sjcl.codec.base64;
        var dec_key = base64.fromBits(sjcl.random.randomWords(4, 0));
        var dec_iv = base64.fromBits(sjcl.random.randomWords(4, 0));
        var postdata  = '&username=' + username + '&password=' + encodeURIComponent(password) + '&csrf_token=' + token + '&nonce=' + nonce+'&enckey='+crypto_page.base64url_escape(dec_key)+'&enciv='+crypto_page.base64url_escape(dec_iv); 
        
        var encryptdata = crypto_page.encrypt_post_data(pubkey, postdata);

The encrytion takes place in crypto_page.js as follows:

var encrypt = function(pubkey, plaintext) {
        var aeskey = sjcl.random.randomWords(4, 0);
        var iv = sjcl.random.randomWords(4, 0);
        var pt = sjcl.codec.utf8String.toBits(plaintext);
        var aes = new sjcl.cipher.aes(aeskey);
        var ct = sjcl.mode.cbc.encrypt(aes, pt, iv);
        
        var rsa = new JSEncrypt(); 
        if(rsa.setPublicKey(pubkey) == false)
            return fasle;

        var base64url = sjcl.codec.base64url;
        var base64 = sjcl.codec.base64;
        var aesinfo = base64.fromBits(aeskey) + ' ' + base64.fromBits(iv);
        var ck = rsa.encrypt(aesinfo);
        if(ck == false)
            return false;

        return {
            ct:base64url.fromBits(ct),
            ck:base64url_escape(ck)
        };
    };

Stanford Javascript Crypto Library(SJCL) is used as well for RNG and base64 encoding.

I monitored the request in my browser and got the following curl for login POST request:

curl "https://192.168.1.254/login.cgi" -X POST -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0" -H "Accept: */*" -H "Accept-Language: en-GB,en;q=0.5" -H "Accept-Encoding: gzip, deflate, br" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "X-Requested-With: XMLHttpRequest" -H "Origin: https://192.168.1.254" -H "Connection: keep-alive" -H "Cookie: admin=deleted; lang=eng" -H "Sec-Fetch-Dest: empty" -H "Sec-Fetch-Mode: cors" -H "Sec-Fetch-Site: same-origin" --data-raw "encrypted=1&ct=YjxWNkPl51dGvfiKc2Rh9lmqNhrsAc0rRbIg0V1kuA5355vMdOOB85gv6skt4O--KlWE-h-mToXTnosw2YmcM7g2pn8YgmGyWGRHMiusRhUy9Qo3moXaysyGtIWGvqALeJIKXlI6NrqvtZO2UyIAguNizN__3E2b1JsRHZPwsuSC9joz0GVj7HgM3YyZm2L-IZk5E-Ge5lTwipvtvvKwFxlij_2raWRJuXzPssF62BLCJ33KLSs69Qdwxm8opTDg&ck=F7GyQZi4xH924EvF2RO9ZFRNDzZ2MTyLD_U5lrw2pofQ73xt0FNxuKLEiOvHYIlb_2mqaazr80sZlonLYRqYrFEoBkulpVa1PAzt6fzoH1n8wPN0mb4moKWDt5b0pT5SJHPIRub_sd6La96_mQvQFaJGm6_MeItaTMw8DLIvLag."

With this said, here is how I am replicating this in my python code:

import requests,re,json,base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad
from urllib import parse
from config import username, password
from pprint import pprint
import warnings
import time
warnings.filterwarnings('ignore')

def randomWords(n):
    return get_random_bytes(n)
    
def base64url_escape(b64):
    out=""
    b64 = b64.decode('utf-8')
    for i in range(len(b64)):
        c = b64[i]
        if c == '+':
            out += '-'
        elif c == '/':
            out += '_'
        elif c == '=':
            out += '.'
        else:
            out += c
    return out.encode('utf-8')

def encrypt_post_data(pubkey, plaintext):
    aeskey = randomWords(16)
    iv = randomWords(16)
    pt = pad(plaintext.encode('utf-8'), AES.block_size)
    aes = AES.new(aeskey, AES.MODE_CBC, iv=iv)
    ct = aes.encrypt(pt)
    
    recipient_key = RSA.import_key(pubkey)
    rsa = PKCS1_OAEP.new(recipient_key)
    aesinfo = base64.b64encode(aeskey) + ' '.encode('utf-8') + base64.b64encode(iv)
    # aesinfo = aeskey + ' '.encode('utf-8') + iv
    ck = rsa.encrypt(aesinfo)
    return {
        'encrypted': '1',
        'ct': base64.urlsafe_b64encode(ct).decode('utf-8'),
        'ck': base64url_escape(base64.b64encode(ck)).decode('utf-8'),#base64.urlsafe_b64encode(ck).decode('utf-8'),
        }


def main():
    url = 'https://192.168.1.254/'
    with requests.Session() as session:
        cookies = {
            'admin': 'deleted',
            'lang': 'eng',
        }

        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
            'Accept-Language': 'en-GB,en;q=0.5',
            'Connection': 'keep-alive',
            'Upgrade-Insecure-Requests': '1',
            'Sec-Fetch-Dest': 'document',
            'Sec-Fetch-Mode': 'navigate',
            'Sec-Fetch-Site': 'none',
            'Sec-Fetch-User': '?1',
        }

        response = session.get(url, cookies=cookies, headers=headers, verify=False)
        print(response.status_code)
        print(response.headers)
        pubkey=re.findall(r"var pubkey = '[Ss]+n'",response.text)[0].split("'")[-2]
        pubkey = re.sub(r"\","",pubkey)
        nonce=re.findall(r"var nonce = "[S]+"",response.text)[0].split('"')[-2]
        token=re.findall(r"var token ="[S]+"",response.text)[0].split('"')[-2]

        dec_key = base64url_escape(base64.b64encode(randomWords(16)))
        dec_iv = base64url_escape(base64.b64encode(randomWords(16)))

        postdata  = '&username=' + username + '&password=' + parse.quote(password) + '&csrf_token=' + token + '&nonce=' + nonce + '&enckey=' + dec_key.decode('utf-8') +'&enciv=' + dec_iv.decode('utf-8')
        
        data = encrypt_post_data(pubkey, postdata)
        print(data)
        cookies = {
            'admin': 'deleted',
            'lang': 'eng',
        }

        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0',
            'Accept': '*/*',
            'Accept-Language': 'en-GB,en;q=0.5',
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'X-Requested-With': 'XMLHttpRequest',
            'Origin': 'https://192.168.1.254',
            'Connection': 'keep-alive',
            'Sec-Fetch-Dest': 'empty',
            'Sec-Fetch-Mode': 'cors',
            'Sec-Fetch-Site': 'same-origin',
        }
        time.sleep(5)
        response = session.post(url+'login.cgi', cookies=cookies, headers=headers, data=data, verify=False)
        print(response.status_code)

At this stage I expect status code 299 to suggest I have successful login and then I can use the Sid from cookie for further requests, but I get the login page again with 200 code.

Here is what data I am generating with my code:

{'encrypted': '1', 'ct': '6gnxUtvKvU8URRNtvbR0wzQXSflWZeJMKKykcpYhHjD7Bq5SZfHF0qONXj1iLpbR1WhbCNDcCxnI9ETs8bnzzCS4dxFVxL3qk6MplnngNHcmQXRE93vF49VlhjaBNG3SbLUZaLIeTNFf2pAypWZ2ZC6CEXy_j46MOGq0uAeQcmx2_gqywEcXd2Qsr54Q9Vs0mCLeukVo-CvgFkGYfX4VvCUdru2FBh1pjipwwWHsE-UMg8SZm50lr7EscEIblzte', 'ck': 'cJUmZmywxagF2diMCHGiYepOaNkqIZOrQr_jTwGxsEJ-vWF5ewCHyqV5_FPn3YcNIuMv97WlWbSsz6fftIXVzOKxpT2f-S0yu4DzkseJheA44lTaNbXB_9k4V-rF9q1Gnrjx8ZFeefRUghIW6eVY64uuMG4M-aXcButYnowDG3o.'}

Advertisement

Answer

So I looked into using selenium on headless raspberry pi on @ahmed-mani suggestion and after much trial and error I got it working. First, most of the current selenium supported browsers are useless and memory hogs for a pi zero w. Firefox and Chromium are 200-400MB, then a desktop environment like XFCE is another 150MB, add a VNC server thats another ~50MB. And I can’t actually use the browsers (Firefix refuses to open, Chromium gets stuck on loading webpages).

Finally found about phantomJS (long deprecated) for RPi. Removed the previous browsers, desktop and vnc server. Installed selenium 3.141 (version with PhantomJS support). Only downside with PhantonJs is it does not handle window.alert & window.confirm, so I need to workaround by inserting javascript at that step. Here is my working script at a fraction of the memory:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import warnings
from config import username, password
warnings.filterwarnings('ignore')

def main():
    driver = webdriver.PhantomJS(executable_path="/usr/local/share/phantomjs-c2.1.1/bin/phantomjs")

    try:
        driver.get('http://192.168.1.254/')
        print('loading page')

        uname_input = '//*[@id="username"]'
        pass_input = '//*[@id="password"]'
        submit = '//*[@id="loginBT"]'

        WebDriverWait(driver, 60).until(EC.presence_of_element_located((By.XPATH, uname_input)))
        print('login form loaded ',driver.title)
        driver.find_element_by_xpath(uname_input).send_keys(username)
        driver.find_element_by_xpath(pass_input).send_keys(password)
        driver.find_element_by_xpath(submit).click()

        maintenance = '/html/body/div/section/div[1]/div/div[1]/ul[5]'
        reboot = '//*[@id="do_reboot"]'

        WebDriverWait(driver, 30).until(EC.presence_of_element_located((By.XPATH, maintenance)))
        print('login success')
        driver.get('http://192.168.1.254/reboot.cgi')

        WebDriverWait(driver, 30).until(EC.presence_of_element_located((By.XPATH, reboot)))
        print('found reboot button')
        js_confirm = 'window.confirm = function(){return true;}'
        driver.execute_script(js_confirm)
        driver.find_element_by_xpath(reboot).click()
        driver.execute_script('return window.confirm')
        print('reboot done')

    except Exception as e:
        print(e)

    finally:
        driver.quit()

if __name__ == "__main__":
    main()
User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement