Last active
November 16, 2024 17:20
-
-
Save HorlogeSkynet/703c1546ae2e70fc6cf6f04b8eb5a9d1 to your computer and use it in GitHub Desktop.
A Python script to programmatically shutdown a Buffalo TeraStation NAS
This file contains 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 python3 | |
""" | |
A Python script to programmatically shutdown a Buffalo TeraStation NAS. | |
=== | |
Usage documentation : | |
1. Install dependency : `pip3 install requests` | |
2. Configure credentials : Add to your `~/.netrc` something like : | |
''' | |
machine your.nas.ip.address (or host-name) | |
login admin | |
password A_VERY_SECURE_ADMIN_PASSWORD | |
''' | |
3. Run the script : `python3 terastation_shutdown.py your.nas.ip.address` (or host-name) | |
""" | |
# /// script | |
# dependencies = [ | |
# "requests", | |
# ] | |
# /// | |
import argparse | |
import netrc | |
import re | |
import ssl | |
import sys | |
from urllib.parse import urlunparse | |
import requests | |
import urllib3 | |
from requests.adapters import HTTPAdapter | |
from requests.exceptions import RequestException | |
from urllib3.exceptions import InsecureRequestWarning | |
from urllib3.poolmanager import PoolManager | |
__author__ = "Samuel FORESTIER" | |
__copyright__ = "Copyright 2019-2024, TeraStation NAS shutdown script" | |
__license__ = "MIT" | |
__status__ = "Production" | |
__date__ = "2024-11-16" | |
__version__ = "v1.3.0" | |
class TLSv1Adapter(HTTPAdapter): | |
"""A dummy HTTP transport adapter allowing us to use TLSv1""" | |
def __init__(self, ssl_context=None, **kwargs): | |
self.ssl_context = ssl_context | |
super().__init__(**kwargs) | |
def init_poolmanager(self, *args, **kwargs): | |
self.poolmanager = PoolManager( | |
*args, | |
**kwargs, | |
ssl_version=ssl.PROTOCOL_TLSv1, | |
ssl_context=self.ssl_context, | |
) | |
def main(): | |
"""Simple entry point""" | |
# Retrieve NAS IP address/host-name from CLI. | |
parser = argparse.ArgumentParser(description="Buffalo TeraStation NAS shutdown script") | |
parser.add_argument("hostname", help="NAS IP address (or resolvable host-name)") | |
args = parser.parse_args() | |
# Retrieve NAS credentials from `~/.netrc` file. | |
try: | |
net_rc = netrc.netrc() | |
except (FileNotFoundError, netrc.NetrcParseError) as error: | |
print(f"[-] Couldn't parse netrc data : {error}", file=sys.stderr) | |
sys.exit(1) | |
netrc_authenticator = net_rc.authenticators(args.hostname) | |
if netrc_authenticator is None: | |
print( | |
f"[-] Couldn't retrieve NAS credentials for {args.hostname}", | |
file=sys.stderr, | |
) | |
sys.exit(1) | |
(nas_login, _, nas_password) = netrc_authenticator | |
# Hide urllib3 warning messages about unsigned certificate. | |
urllib3.disable_warnings(InsecureRequestWarning) | |
# Create a new `Requests` session. | |
with requests.Session() as session: | |
# Force Requests module to use TLSv1 (the TeraStation is an old device). | |
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) | |
ctx.check_hostname = False # Certificate is (very) likely self-signed ! | |
ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT (for OpenSSL >= 3). | |
ctx.set_ciphers("ALL:@SECLEVEL=0") # Prevent DH_KEY_TOO_SMALL error. | |
session.mount("https://", TLSv1Adapter(ctx)) | |
# Fake the authentication process. | |
try: | |
response = session.post( | |
urlunparse(("https", args.hostname, "/cgi-bin/top.cgi", "", "", "")), | |
data={ | |
"txtAuthLoginUser": nas_login, | |
"txtAuthLoginPassword": nas_password, | |
"gPage": "top", | |
"gMode": "auth", | |
}, | |
timeout=5, | |
verify=False, | |
) | |
response.raise_for_status() | |
except RequestException as error: | |
print( | |
f"[-] Couldn't authenticate on the TeraStation : {error}", | |
file=sys.stderr, | |
) | |
sys.exit(1) | |
# CSRF tokens extraction... | |
grrr_token = re.search( | |
r'<input\s+type="hidden"\s+id="gRRR"\s+name="gRRR"\s+value="(.+?)"\s+\/>', | |
response.text, | |
) | |
gsss_token = re.search( | |
r'<input\s+type="hidden"\s+id="gSSS"\s+name="gSSS"\s+value="(.+?)"\s+\/>', | |
response.text, | |
) | |
if grrr_token is None or gsss_token is None: | |
print( | |
"[-] Couldn't extract CSRF tokens from authentication response.", | |
file=sys.stderr, | |
) | |
sys.exit(1) | |
print("[+] Successfully authenticated on the TeraStation.") | |
# Submit a fake shutdown maintenance action. | |
try: | |
response = session.post( | |
urlunparse(("https", args.hostname, "/cgi-bin/top.cgi", "", "", "")), | |
data={ | |
"gPage": "maintenance", | |
"gMode": "shutdown", | |
"gType": "shutdown", | |
"gRRR": grrr_token.group(1), | |
"gSSS": gsss_token.group(1), | |
}, | |
timeout=5, | |
verify=False, | |
) | |
response.raise_for_status() | |
except RequestException as error: | |
print( | |
f"[-] Couldn't send shutdown command to the TeraStation : {error}", | |
file=sys.stderr, | |
) | |
sys.exit(1) | |
if "Shutting Down the TeraStation..." not in response.text: | |
print( | |
"[-] An unknown error occurred while shutting down the TeraStation...", | |
file=sys.stderr, | |
) | |
sys.exit(1) | |
print("[+] The shutdown procedure has been successfully started on the TeraStation.") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment