Skip to content

Instantly share code, notes, and snippets.

@HorlogeSkynet
Last active November 13, 2023 19:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HorlogeSkynet/703c1546ae2e70fc6cf6f04b8eb5a9d1 to your computer and use it in GitHub Desktop.
Save HorlogeSkynet/703c1546ae2e70fc6cf6f04b8eb5a9d1 to your computer and use it in GitHub Desktop.
A Python script to programmatically shutdown a Buffalo TeraStation NAS
#!/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)
"""
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-2023, TeraStation NAS shutdown script"
__license__ = "MIT"
__status__ = "Production"
__date__ = "2023-11-13"
__version__ = "v1.2.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)
(nas_login, _, nas_password) = net_rc.authenticators(args.hostname)
if not nas_login:
print(
f"[-] Couldn't retrieve NAS credentials for {args.hostname}",
file=sys.stderr,
)
sys.exit(1)
# 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