Cable modems are usually intrinsic insecure; ISPs control the firmware and settings (in my case, even the built-in router's password is configured remotely by the ISP), and in order to allow ISPs to "manage" modems remotely, one or more backdoors are packed into them.
Some ISPs don't even bother to change seeds used to generate passwords of the day, allowing users/attackers to easily access and modify advanced settings within the modem.
While we can't do anything other than contact the ISP when the modem is accessible through the local ISP node, we can however, block LAN clients from accessing the modem with a main router firewall rule such as this:
config rule
option name 'Block-Modem'
option family 'ipv4'
option src 'lan'
option dest 'wan'
list dest_ip '192.168.100.1'
option target 'REJECT'
list proto 'all'
In the other hand, sometimes it's useful to access the modem's information, e.g. check if the cable connection is down, check events to figure out the time when the service went offline, etc.
I didn't feel like messing with the firewall entry each time I wanted to see this data, so I wrote this basic shell script to download, parse and print this data for me through the SSH shell of the main router.
#!/bin/sh
MODEM_PATH="http://192.168.100.1/cgi-bin"
HTML_DIR="/tmp/modem_html"
HTML_FILE="data.html"
HTML_PATH="$HTML_DIR/$HTML_FILE"
get_html() {
mkdir -p "$HTML_DIR"
rm -rf "$HTML_PATH"
wget -q -4 -T 10 -P "$HTML_DIR" -O "$HTML_FILE" "$MODEM_PATH/$1" > /dev/null 2>&1
}
del_html() {
rm -rf "$HTML_DIR"
}
compact_html() {
tr -d '\r\n' < "$HTML_PATH"
}
delimit_html() {
echo -e "$1" | awk "{ gsub(/$2/, \"$2\n\") } { print }"
}
get_text() {
echo -e "$1" | sed -rn "s/$2/$3/p"
}
get_events() {
pattern='^.*?<tr>\s*<td align=center>([0-9\/: ]+)<\/td>\s*<td align=center>\d+<\/td>\s*<td align=left>(.+?)<\/td>\s*<\/tr>.*$'
text='(\1) \2'
echo -e "$1" | sed -rn "s/$pattern/$text/p"
}
case "$1" in
status)
get_html "status_cgi" || exit 2
html=$(compact_html)
del_html
[ -z "$html" ] && exit 2
pattern='^.*?<tr>\s*<td.*?>System Uptime: <\/td>\s*<td.*?>(\d+)\s+d:\s+(\d+)\s+h:\s+(\d+)\s+m<\/td>\s*<\/tr>.*$'
uptime=$(get_text "$html" "$pattern" 'Uptime: \1 days, \2 hours, \3 minutes')
pattern='^.*?<tr>\s*<td>LAN Port 1<\/td>\s*<td>Enabled<\/td>\s*<td>Up<\/td>\s*<td>([A-Za-z0-9()]+)<\/td>\s*<td>[A-Fa-f0-9:]+<\/td>\s*<\/tr>.*$'
lan=$(get_text "$html" "$pattern" 'LAN:\\t\1')
pattern='^.*?<tr>\s*<td>CABLE<\/td>\s*<td>Enabled<\/td>\s*<td>(\w+)<\/td>\s*<td>.*?<\/td>\s*<td>[A-Fa-f0-9:]+<\/td>\s*<\/tr>.*$'
cable=$(get_text "$html" "$pattern" 'CABLE:\\t\1')
pattern='^.*?<tr>\s*<td>MTA<\/td>\s*<td>\w+<\/td>\s*<td>(\w+)<\/td>\s*<td>.*?<\/td>\s*<td>[A-Fa-f0-9:]+<\/td>\s*<\/tr>.*$'
mta=$(get_text "$html" "$pattern" 'MTA:\\t\1')
[ -n "$uptime" ] && echo $uptime && echo
[ -n "$lan" ] && echo -e $lan
[ -n "$cable" ] && echo -e $cable
[ -n "$mta" ] && echo -e $mta
;;
events)
get_html "event_cgi" || exit 2
html=$(compact_html)
del_html
[ -z "$html" ] && exit 2
html=$(delimit_html "$html" '<\/tr>')
get_events "$html" | while read -r event; do
echo -e $event
done
;;
*)
echo "Usage: $0 status|events" && exit 1
;;
esac
I didn't want to install any extra packages (my main router doesn't have an USB port) and I didn't feel like wasting my time on something like this in C, so I hacked my way around with tr, sed and awk.
Usage example:
root@router1:~# modem-info status
Uptime: 15 days, 16 hours, 29 minutes
LAN: 1000(Full)
CABLE: Up
MTA: Up
The script was written based on the pages and HTML served by the Arris TG1692A model, but SHOULD work with minimal/no changes on most Arris cable models.
NOTE: Usually, the cable modem captures WAN traffic targeted at 192.168.100.1, but I've found this to be inconsistent/buggy with my Arris TG1692A model. I've added the following static routing entry to avoid routing these packets to the edge gateway.
config route
option interface 'wan'
option target '192.168.100.1/32'
option gateway '0.0.0.0'
option table 'main'