Hacking my cable modem for fun and profit

This is a story of why you should always check permissions on the backend!

Hacking my cable modem for fun and profit

I switched from my trusty DSL to a cable ISP a while ago and decided to take a deeper look at their modem - an Arris TG1692A. Fancy thing, but for obvious reasons I cannot disassemble it. But it has a crappy web UI.. we surely can play with it, right?

Background story: I have a Pi-hole running at home and it listens to both IPv4 and IPv6. However, the IPv6 range is given by my modem through Router Advertisements packets, and it sends itself as DNS server, essentially making all IPv6-enabled devices bypass the damn blocker. I needed to change that!

Ricardo from the future here: if you're here to just change the IPv6 DNS on your modem and need a simple route, you can go straight there - it's at the bottom of this post. But feel free to read the rest, as it's pretty interesting what you can get.

That would, normally, be very easy - just open the web UI, LAN setup and change the DNS servers on IPv6:

Except there's a bug in this interface: any change you try to make, it will reply you with an error: IP address must be in the subnet of [whatever]::/64.

This is because the first field in this page, the IP address, is writeable and is filled with the IP address we got from the ISP. The subnet checking algorithm they have, however, is broken (or the arguments to it - I don't recall it properly). That, however, will block the page from saving any other changes.

We don't care about that setting (it will be overwritten once it gets online anyway), but we need to be able to enable DNS override and set a local IPv6 DNS server. And that, my friends, is where backend validation comes into play. Well, I mean, the lack of it.

You see, by playing a few hours with the web interface JavaScript code, I've managed to figure out the commands that will render those fields. Using Burp I was able to remove them completly from the page's source, and this way making the page just bypass that configuration and carry on with the others:

## Replace all of these matches with empty strings on Burp ##

# LAN IPv6 Address field (regex)
  fieldset\("LANIPSettingsV6",.+?  \),\n

# Disable LAN IPv6 address validation
  validateIpv6(lan, ag.IPAddressV6.toLowerCase());

# Disable LAN IPv6 saving
  arLanGatewayIp2.set(lan, ag.IPAddressV6, "IPAddressV6");

This will make sure the option will go away and nothing will go crazy when we save the options. By using only the last two we can even leave the field there, but it'll be ignored by the scripts, allowing me to save my shiny Pi-hole IPv6 local address as the propagated DNS server on my IPv6 network:

And done! IPv6 DNS addresses changed!

But you might be wondering: I didn't create a post only to talk about IPv6 shitty stuff, right? I mean, there must be something else.

And there is! I've found a bunch of hidden features and disabled options (for normal users at least) that we can reenable by just changing the frontend code! There is no backend validation on this! I mean, on some fields, sure, it validates the arguments (as its some kind of SNMP anyway), but no checks on user permissions!

Applying them on Burp everytime I want to play with it is annoying, so I've decided to create a nginx site config for my modem that does everything for me. Yeah, I'm that type of person. Here is it:

server {
    listen 443 ssl;
    server_name modem.some.internal.domain;

    location / {
        proxy_pass http://my.modem.ip.address:80;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        sub_filter_once off;
        sub_filter_types "*";

        # Mod: Show it's being modded!
        sub_filter "<title></title>" "<title>This is modded</title>";

        # Mod: Disable LAN IPv6 address validation
        sub_filter "validateIpv6(lan, ag.IPAddressV6.toLowerCase());" "";

        # Mod: Disable LAN IPv6 saving
        sub_filter "arLanGatewayIp2.set(lan, ag.IPAddressV6, \"IPAddressV6\");" "";

        # Mod: Technician override
        sub_filter "return isLoggedIn() && attrs[\"Technician\"];" "return true;";

        # Mod: Allow lan_static page
        sub_filter "//    { id: \"RoutingTable\", page:\"lan_static\"}" "    ,{ id: \"RoutingTable\", page:\"lan_static\"},";

        # Mod: Allow lan_networks page
        sub_filter "//{ id: \"LANNetworks\", page:\"lan_networks\" }" "{ id: \"LANNetworks\", page:\"lan_networks\" },";

        # Mod: Allow lan_staticrouting page
        sub_filter "!isNET() ? { id: \"StaticRouting\", page:\"lan_staticrouting\" } : null" "{ id: \"StaticRouting\", page:\"lan_staticrouting\" },";

        # Mod: Allow wifi_guest page
        sub_filter "( !isPuma5Wifi() && (isNA() || isCox() || isIZZI()) ) ? { id:\"GuestSSID" "true ? { id:\"GuestSSID";

        # Mod: Allow wifi_atm page
        sub_filter "showATM() ? { id:\"AirtimeManagement\"" "true ? { id:\"AirtimeManagement\"";

        # Mod: Allow wifi_bandsteeringexclusion page
        sub_filter "(!isNET() && isMTKDBC()) ? { id:\"BandSteeringExclusion" "true ? { id:\"BandSteeringExclusion";

        # Mod: Allow usb page
        sub_filter "!isNET() ? { id: \"USB\"" "true ? { id: \"USB\"";

        # Mod: Allow wan_dns page
        sub_filter "//{ id: \"DNS\", page: \"wan_dns\" }" "{ id: \"DNS\", page: \"wan_dns\" },";

        # Mod: Allow wan_routingv6 page
        sub_filter "//isIPV6() && isTechnician() ? { id: \"RoutingV6\", page: \"wan_routingv6\"} : null" ",{id: \"RoutingV6\", page: \"wan_routingv6\"},";

        # Mod: Allow wifi_wds page
        sub_filter "//   { id: \"WDS\", page:\"wifi_wds\" }," "   { id: \"WDS\", page:\"wifi_wds\" },";

        # Mod: Allow wifi_wps page
        sub_filter "//   { id: \"WPS\", page:\"wifi_wps\" }," "   { id: \"WPS\", page:\"wifi_wps\" },";

        # Mod: Allow util_speedtest page
        sub_filter "//{ id: \"SpeedTest\", page: \"util_speedtest\"}" "{ id: \"SpeedTest\", page: \"util_speedtest\"},";

        # Mod: Allow util_logconf page
        sub_filter "// UNIHAN PROD00195563//!isMG() ? { id: \"SysLogConfig\", page: \"util_logconf\" } : null," "{ id: \"SysLogConfig\", page: \"util_logconf\" },";

You might be asking yourself so many overrides. Well, honestly, I just decided to play with it: anything that was commented out, disabled for my user or even my ISP, I just reenabled it. This gave me access to diagnostics tools, hidden settings, all sorts of things. It's fun, it's like a whole new modem! I can access routing tables, extra wireless settings, some cable diagnostics stuff, all sorts of interesting things!

Some of these don't work in the latest firmware, such as USB settings, but I might fix them in the future. And by leaving these on my nginx's reverse proxy I can easily apply all of them and play with whatever I need!

The lesson here folks is to always have backend checks on permissions. I can some of the settings that I've forcefully reenabled (some won't work because it's not a valid configuration on my use case), leading me to believe that there's no backend validation on this. And nevertheless, here's a quick tip:

You should block not only writing, but reading data I'm not supposed to have access to.

That's it!

The future!

Hi! I'm Ricardo, from the future! If you ended here because you wanted to change the IPv6 DNS on the same modem as I have, you might be wondering if there's an easier route. And in fact, there is! Open the web interface, navigate to the LAN IPv6 Settings page. There, just run this script at your browser's console (developer tools):


  Do this at your own risk, as pretty much anything I post here! I'm not
  responsible if shit goes sideways and you need to reset the modem, call
  your ISP to fix it or move out of the country because someone decided to
  hunt you down. It should work though :)

validateIpv6 = () => true;
arLanGatewayIp2.original_set = arLanGatewayIp2.set;
arLanGatewayIp2.set = function (index, value, label, forceSubmit) {
  if (label == "IPAddressV6") { return; }
  arLanGatewayIp2.original_set.call(this, index, value, label, forceSubmit);

This script will disable the validateIpv6 function (bypassing the bug) and also avoid the modem from saving the IPAddressV6 property, which is dynamic and I honestly don't wanna test to see what happens. That's it! Simple as that!