Plusnet Hub One

Your external IP can be a useful thing to know.
Perhaps you want to SSH into a box within your network, access a web server or some other service, or even update a domain’s DNS to point to your home IP address.

Normally, this can be gotten in plain-text format using a few services which return just the IP that you are visiting from, such as http://icanhazip.com and http://ipecho.net/plain using cURL or wget. However, if you plan on regular checking, you have to a) ensure that the external site is going to be up, and b) avoid hitting it with too many requests (as Major Hayden asks on his FAQ)

On some routers which have SNMP enabled, then the External IP Address is actually available within the network, and can be obtained using a command such as:

snmpwalk -v2c -c public your_router_here ip | grep ipAdEntAddr

Or some variation thereof.

If you have a Plusnet Hub One, however:Plusnet Hub OneThen you are not quite so lucky. Your external IP address is available within your network, but only on one of the Broadband settings pages. (results partially obscured below, but it is there)

Plusnet Hub One Connection Screen

Problem is… this page sits behind the Advanced Settings page, which for security reasons is password protected. Visiting the page and reading off the External IP address is fine, but we want to use it in some other way, so we want it programmatically so we can use it for other things, right?

Right.

The login page has some interesting functions and hidden form fields:

  • md5.js – provides some md5 hashing implementations within JavaScript
  • md5_pass – calculated in Javascript and then sent to the router
  • post_token – a 64-character string that changes every session
  • request_id – a 10-character string that changes every non-cached page access
  • auth_key – a 9-character string that changes every session
  • password_xxxxxxxxxx – the name of the password field changes every page access, along with request_id

It’s interesting to note that the password is never sent to the router – the JavaScript on the page does the following:

function SendPassword()
{
 var tmp;
 document.form_contents.elements['md5_pass'].value=document.form_contents.elements['password_1020071056'].value+document.form_contents.elements['auth_key'].value
 tmp=hex_md5(document.form_contents.elements['md5_pass'].value);
 document.form_contents.elements['md5_pass'].value=tmp;
 document.form_contents.elements['password_1020071056'].value="";
 mimic_button('submit_button_login_submit: ..', 1);
}

This takes the password you’ve entered, salts it with the auth_key value, and then runs it through the hex_md5 function from md5.js. This md5 hash is then the value that is passed back from the clientside browser to the server on the router. At least they’re not sending the password in plaintext in a a URL querystring. the hex_md5 function gives an output identical to running the salted password through

echo -n $pass |openssl dgst -md5|awk '{print $2}

I confirmed this by running a few test strings through both functions and getting identical results.

So we have our method for calculating the md5_pass in bash. What we need now is to get a copy of the login page, capture the cookie (rg_cookie_session_id) and parse the page for the fields that we will need to send back to the router to authenticate.

page=`curl -Ls 'http://$routerip/index.cgi?active_page=9148' -H 'Cookie: rg_cookie_session_id=' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Cache-Control: max-age=0' -H 'DNT: 1' --data 'active_page=9121' --cookie-jar cookies.txt`

Here’s the first step, we grab the contents of the login page, ensuring we save the cookie along the way, so we can pick it up later.

We can then parse for the needed variables using grep and awk to strip away the extras that we don’t need:

posttoken=`echo $page |grep post_token |awk 'BEGIN { FS = "\"post_token\" value=\"" } ; {print $2}'|awk 'BEGIN { FS = "\"" } ; {print $1}'|xargs`

requestid=`echo $page |grep request_id |awk 'BEGIN { FS = "\"request_id\" value=\"" } ; {print $2}'|awk 'BEGIN { FS = "\"" } ; {print $1}'|xargs`

authkey=`echo $page |grep auth_key |awk 'BEGIN { FS = "\"auth_key\" value=\"" } ; {print $2}'|awk 'BEGIN { FS = "\"" } ; {print $1}'|xargs`

passwordid=`echo $page |grep password_ |awk 'BEGIN { FS = "\"password_" } ; {print $2}'| awk 'BEGIN { FS = "\"" } ; {print $1}'|xargs`

The final results are pushed through xargs as a decent/quick/hacky way to strip whitespace at the start and ends of the strings.

Next we build our POST data:

postvars="request_id=$requestid&active_page=9148&active_page_str=bt_login&mimic_button_field=submit_button_login_submit%3A+..&button_value=&post_token=$posttoken&password_$passwordid=&md5_pass=$passhash&auth_key=$authkey"

and then push it using cURL to the login page:

curl -Ls 'http://$routerip/index.cgi'  -H 'Content-Type: application/x-www-form-urlencoded' -H 'Cache-Control: max-age=0' -H 'Referer: http://192.168.1.254/index.cgi?active_page=9121' -H 'Connection: keep-alive' -H 'DNT: 1' --data "$postvars" --cookie cookies.txt >/dev/null

The -L flag on cURL is to get it to follow 302 redirects, as the login page throws one of these in as it validates.

Finally we can make the request to the page we actually need, and pull the External IP address! Yay!

IP=`curl -s  'http://$routerip/index.cgi?active_page=9121&nav_clear=1' -H 'DNT: 1' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en-GB,en-US;q=0.8,en;q=0.6' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Connection: keep-alive' -H 'Cache-Control: max-age=0' --cookie cookies.txt --compressed | grep -o "[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" |head -n 1`

Here’s the listing in full:

#!/bin/bash
routerip='192.168.1.254'
pass='<PASSWORD>'
page=`curl -Ls 'http://$routerip/index.cgi?active_page=9148' -H 'Cookie: rg_cookie_session_id=' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Cache-Control: max-age=0' -H 'DNT: 1' --data 'active_page=9121' --cookie-jar cookies.txt`
posttoken=`echo $page |grep post_token |awk 'BEGIN { FS = "\"post_token\" value=\"" } ; {print $2}'|awk 'BEGIN { FS = "\"" } ; {print $1}'|xargs`
requestid=`echo $page |grep request_id |awk 'BEGIN { FS = "\"request_id\" value=\"" } ; {print $2}'|awk 'BEGIN { FS = "\"" } ; {print $1}'|xargs`
authkey=`echo $page |grep auth_key |awk 'BEGIN { FS = "\"auth_key\" value=\"" } ; {print $2}'|awk 'BEGIN { FS = "\"" } ; {print $1}'|xargs`
passwordid=`echo $page |grep password_ |awk 'BEGIN { FS = "\"password_" } ; {print $2}'| awk 'BEGIN { FS = "\"" } ; {print $1}'|xargs`
pass+=$authkey
passhash=`echo -n $pass |openssl dgst -md5|awk '{print $2}'`
postvars="request_id=$requestid&active_page=9148&active_page_str=bt_login&mimic_button_field=submit_button_login_submit%3A+..&button_value=&post_token=$posttoken&password_$passwordid=&md5_pass=$passhash&auth_key=$authkey"

curl -Ls 'http://$routerip/index.cgi' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Cache-Control: max-age=0' -H 'Referer: http://$routerip/index.cgi?active_page=9121' -H 'Connection: keep-alive' -H 'DNT: 1' --data "$postvars" --cookie cookies.txt >/dev/null

IP=`curl -s 'http://$routerip/index.cgi?active_page=9121&nav_clear=1' -H 'DNT: 1' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en-GB,en-US;q=0.8,en;q=0.6' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Connection: keep-alive' -H 'Cache-Control: max-age=0' --cookie cookies.txt --compressed | grep -o "[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" |head -n 1`

echo $IP

By Admin

7 thoughts on “The Plusnet Hub One and webscraping for fun and profit”
  1. Hi I’ve entered your command and tried running in Terminal on the Mac, it runs but has no output with no error.

    Do you know what might be the problem?

    1. Ryan, while I’m not familiar with the mac, you could try adding a few echo statements to the script after a few of the variables are set – e.g. after the posttoken=xxxxx line, add a line that says:

      echo $posttoken

      which should give you the content of that variable in the terminal.

      You could also try just entering into the terminal:

      curl ‘http://192.168.1.254/index.cgi’

      to check that curl is operating correctly on your system also.

  2. I had the same problem as Ryan on Debian 8, I found I had to put the router IP in the curl commands directly, the variable didn’t work for some reason. The output also had 5 IP addresses so I had to use cut to just take the first IP given.

    1. I took the head command off the end for testing! I put the head -n 1 command back and I get only one IP as the output.

      Thanks for the script!

  3. Works like a charm. Nice Work! Is there any way this script could be modified to get to the “Troubleshooting:Help Desk” page? I would like to be able to scrape the connection data.

  4. The problem Darren met above is currently single-quotes are used around urls and other args that contain shell expansions. For these to work, at least on bash, they must be double-quotes. With single-quotes, the dollar and the env-var name are kept as literals and passed to the subprocess unchanged.

Leave a Reply

Your email address will not be published. Required fields are marked *