Created
August 30, 2025 23:22
-
-
Save diggit/761722a3048e42e45dd78735965f2469 to your computer and use it in GitHub Desktop.
Vodafone CZ Cable modem firewall (re)disable tool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python | |
| # publisneed under CC0 license | |
| # vodafone_disable_firewall - Script to disable the firewall on Vodafone cable network modem | |
| # Written in 2025 by diggit @ github | |
| # To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. | |
| # You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>. | |
| # WHAT? | |
| # Simple script to disable firewall on the Vodafone cable network modem. | |
| # WHY? | |
| # Because this piece of cr*p re-activates firewall within 24h and | |
| # describes this behavior as "safety" feature. | |
| # HOW? | |
| # After spending ~4 hours with Firefox debugger, wireshark, python cryptography and requests docs, | |
| # I figured out how to authenticate and send requests to the modem. | |
| # In theory, other features could be added, but those persist, | |
| # so you can configure them via web UI. | |
| # How to use? | |
| # Update `password` and `host` variables below and execute the script. | |
| # Do this periodically to keep firewall disabled. | |
| # Curse you Vodafone for this piece of cr*p! | |
| # possible systemd unit file | |
| # [Unit] | |
| # Description=Vodafone modem firewall reset | |
| # After=network.target | |
| # [Service] | |
| # Type=simple | |
| # ExecStart=/usr/bin/python /path/to/modem_disable_firewall.py | |
| # possible systemd timer file | |
| # [Unit] | |
| # Description=Reset Vodafone modem FW every 6h | |
| # [Timer] | |
| # # every 6h | |
| # OnCalendar=*-*-* 00/06:00:00 | |
| # [Install] | |
| # WantedBy=timers.target | |
| # systemctl enable --now your.timer | |
| # tested with: | |
| # - Firmware version: AR01.04.137.08_072324_7249.PC20.10 | |
| # - DsLite (IPv6 + CGNAT IPv4) mode | |
| password = "FILL_IN" | |
| host = "http://192.168.0.1" | |
| import requests | |
| import re | |
| from cryptography.hazmat.primitives import hashes | |
| from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC | |
| from cryptography.hazmat.primitives.ciphers.aead import AESCCM | |
| from random import randint | |
| ar_nonce = f"{randint(10000, 99999)}" | |
| # too lazy to exract those, should be constant anyway | |
| PBKDF2_KEYSIZE_BITS = 128 | |
| PBKDF2_ITERATIONS = 1000 | |
| session = requests.Session() | |
| # ajaky is CORS sensitive and thus requires these headers | |
| headers = { | |
| "csrfNonce" : "undefined", # updated after login | |
| "X-Requested-With" : "XMLHttpRequest", | |
| "Content-Type" : "application/json", | |
| "Referer" : "http://192.168.0.1/", | |
| "Origin" : "http://192.168.0.1" | |
| } | |
| session.headers.update(headers) | |
| session.params = {"_n": ar_nonce} | |
| def getVariables(session : requests.Session) -> dict[str, str]: | |
| print("get variables", end = " ", flush = True) | |
| index_response = session.get(host) | |
| print(index_response.reason) | |
| # print(session.cookies) | |
| # yes this is quite crude approach to extarct data from html webpage... | |
| js_vars : list[tuple[str, str]] = re.findall(r'var\s+([a-zA-Z0-9_]+)\s*=\s*([\'\"].*?[\'\"]|[^;\n]+);', index_response.text) | |
| variables : dict[str, str]= {} | |
| for var, val in js_vars: | |
| if var not in ["currentSessionId", "myIv", "mySalt", "encryptflag", "loginUserName", ]: | |
| continue | |
| variables[var] = val.strip('\'"') | |
| # this is how it is defined in javascript sources... | |
| variables["plain_text"] = f'{{"Password": "PLACEHOLDER", "Nonce": "{variables["currentSessionId"]}"}}' | |
| variables["authData"] = "loginPassword" | |
| # print("extracted variables") | |
| # for k, v in variables.items(): | |
| # print(f"{k} = {v}") | |
| variables["plain_text"] = variables["plain_text"].replace("PLACEHOLDER", password) | |
| return variables | |
| def login(session: requests.Session, variables: dict[str, str]): | |
| print("login", end = " ", flush = True) | |
| key = PBKDF2HMAC( | |
| algorithm=hashes.SHA256(), | |
| length=PBKDF2_KEYSIZE_BITS//8, | |
| salt=bytes.fromhex(variables["mySalt"]), | |
| iterations=PBKDF2_ITERATIONS | |
| ).derive(password.encode()) | |
| # print(f"derived key = {key.hex()}") | |
| aesccm = AESCCM(key) | |
| ct = aesccm.encrypt( | |
| bytes.fromhex(variables["myIv"]), | |
| variables["plain_text"].encode(), | |
| variables["authData"].encode()) | |
| # print(f"ct = {ct.hex()}") | |
| login_url = f"{host}/php/ajaxSet_Password.php" | |
| # print(f"login_url = {login_url}") | |
| login_response = session.post(login_url, json={ | |
| "AuthData" : variables["authData"], | |
| "EncryptData" : ct.hex(), | |
| "Name" : variables["loginUserName"] | |
| }) | |
| # print(session.cookies) | |
| print(login_response.reason) | |
| # print(login_response.content) | |
| # NOTE: another good indicaton of successful login i new cookies value | |
| login_response_json = login_response.json() | |
| # print(login_response_json) | |
| match login_response_json: | |
| case {"p_status" : "Lockout"} : | |
| print("Wrong password!") | |
| exit(1) | |
| case {"p_status" : "AdminMatch"} : | |
| pass | |
| case {"p_status" : unknown} : | |
| print(f"WARNING: Unknown status: {unknown}, trying to continue anyway!") | |
| # exit(1) | |
| response_ct : str = login_response.json().get("encryptData") | |
| # print(f"response_ct = {response_ct}") | |
| csrf_nonce_raw = aesccm.decrypt( | |
| bytes.fromhex(variables["myIv"]), | |
| bytes.fromhex(response_ct), | |
| "nonce".encode() | |
| ) | |
| csrf_nonce = csrf_nonce_raw.decode() | |
| # print(f"csrf_nonce_raw = {csrf_nonce}") | |
| headers["csrfNonce"] = csrf_nonce | |
| session.headers.update(headers) | |
| def isFirewallEnabled(session : requests.Session) -> bool: | |
| print("fw get", end = " ", flush = True) | |
| firewall_get_url = f"{host}/php/net_firewall_data.php" | |
| # print(f"firewall_get_url = {firewall_get_url}") | |
| firewall_get_response = session.get(firewall_get_url) | |
| print(firewall_get_response.reason) | |
| # print(firewall_get_response.content) | |
| firewall_get_response_json = firewall_get_response.json() | |
| # print(firewall_get_response_json) | |
| return firewall_get_response_json["Enable"] == "true" | |
| def firewallConfigure(session : requests.Session, enabled : bool): | |
| print(f"fw set ({enabled})", end = " ", flush = True) | |
| firewall_set_url = f"{host}/php/ajaxSet_net_firewall_data.php" | |
| # print(f"firewall_set_url = {firewall_set_url}") | |
| firewall_set_response = session.post(firewall_set_url, json= { | |
| "Enable": enabled | |
| }) | |
| print(firewall_set_response.reason) | |
| # print(firewall_set_response.content) | |
| def isLoggedIn(session : requests.Session) -> bool: | |
| print("session check", end = " ", flush = True) | |
| # not sure if this is needed | |
| session_url = f"{host}/php/ajaxSet_Session.php" | |
| # print(f"session_url = {session_url}") | |
| session_response = session.post(session_url) | |
| print(session_response.reason) | |
| # print(session_response.content) | |
| session_response_json = session_response.json() | |
| # print(session_response_json) | |
| return session_response_json.get("LoginStatus", 'no') == 'yes' | |
| def logout(session : requests.Session): | |
| print("logout", end = " ", flush = True) | |
| # not sure if this is needed | |
| logout_url = f"{host}/php/logout.php" | |
| # print(f"logout_url = {logout_url}") | |
| logout_response = session.post(logout_url) | |
| print(logout_response.reason) | |
| # print(logout_response.content) | |
| # print(logout_response.json()) | |
| variables = getVariables(session) | |
| login(session, variables) | |
| logged_in = isLoggedIn(session) | |
| if not logged_in: | |
| print("Not logged in, exiting!") | |
| exit(1) | |
| fw_enabled_before = isFirewallEnabled(session) | |
| print(f"Firewall enabled before: {fw_enabled_before}") | |
| firewallConfigure(session, False) | |
| fw_enabled_after = isFirewallEnabled(session) | |
| print(f"Firewall enabled after: {fw_enabled_after}") | |
| logout(session) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment