TAGS: | |

No API? No Problem

Alan

Let’s face it, traditional management interfaces, CLI, SNMP, et al, are still commonplace throughout campus and datacenter networks. While first-class, customer-facing APIs are slowly gaining traction with vendors and operators alike, they are still the exception rather than the rule (in my experience at least).

So, where does that leave us? In lieu of NETCONF or similar protocol, it leaves us exactly where we have been for the last 20 years and sometimes that’s okay. However, if a device is capable of providing useful information from a web interface and it aligns with a use case, there might be a better way forward. You probably have some idea of where this is going, but it is not parsing styled HTML, which can be malformed or inconsistent.

By the way, the following screenshots and output have been sanitized, so if something looks off or incomplete, this is probably why. You’ve been warned.

Structured data

With a bit of good luck, you might discover the device in question is utilizing AJAX, Asynchronous JavaScript and XML, to pass structured, self-descriptive data from the back-end device to the front-end web interface for presentation. Despite the name, the data could be JSON or other format in place of XML, but the format should be of little consequence; after all, if the front-end developer was able to parse the data, we should be able to parse the data too.

Discovery

Fiddler Example

Fiddler in action taking a peek at the PacketPusher’s website.

Enter Fiddler. Fiddler is a free, cross-platform web proxy that can be installed on your workstation to intercept locally generated web traffic, and best yet, it works with any browser.

Fiddler will help us discover two things: first, how to authenticate with the device, and second, how to request the desired information from the web interface.

The device

Next, we need a device with a web interface, and for this example we will use the ZoneDirector, a wireless controller, from Ruckus Wireless. Now, before we go any further, to be clear, I’m not picking on Ruckus or the ZoneDirector. There are many devices from other manufacturers that could be used in this example, but requesting and parsing data from the ZoneDirector is dead simple. Besides, the SmartZone, another controller and what I see positioned as the successor to the ZoneDirector, has a full-fledged API, so good on Ruckus.

Putting it all together

Before we get started, I assume we have some familiarity with a scripting language. The following section will feature PowerShell snippets, primarily because PowerShell doesn’t need an external library to accomplish our goal. What follows is a no-frills implementation.

A word of warning because this never happens: do not experiment on production systems.

First, we will authenticate to the web interface using a browser and capture the exchange using Fiddler.

ZoneDirector Log In

After we complete the form and click Log In, the account information is submitted to /admin/login.jsp using the HTTP POST method. In addition to our account information, url and ok variables are also submitted.

ZoneDirector Fiddler Entry

Focus on url=undefined&username=admin&password=admin&ok=Log+In

By default, the ZoneDirector will use a self-signed certificate to secure the web interface. When the PowerShell script connects to the web interface, it will attempt to validate the certificate. If the certificate is not trusted, the script will fail, so if absolutely required, we can use the following statement to disable validation.

#Disable certificate validation
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

Only the username, password, and ok variables are required; we will omit the url variable and retain the value of the ok variable.

#Base URL of the controller
$BaseURL = 'https://x.x.x.x/admin/'

#User credentials
$Username = 'admin'
$Password = 'admin'

#Name of the login page
$LoginPage = 'login.jsp'

#The POST body
$LoginBody = "username=$($Username)&password=$($Password)&ok=Log+In"

Now that we have defined the variables necessary for authentication, let’s make the request.

#Authenticate and return the session variable Rks
$Request = Invoke-WebRequest ($BaseURL + $LoginPage) -SessionVariable Rks -Method Post -Body $LoginBody

It is important to note the SessionVariable named Rks. It will be required to authenticate subsequent requests.

Before moving forward with our script, let’s flip back to our browser and use Fiddler to learn how to construct the request to view active wireless clients.

ZoneDirector Clients

Based on the screenshot we have at least four authenticated clients, and yes, much of the information from the client table has been redacted. When we view the events in Fiddler, there will be multiple requests made to /admin/_cmdstat.jsp. To make sense of these requests, change the response body to XML and try to correlate entries from the body to the client table.

Fiddler ZoneDirector Clients Entry

The information in the body appears to match what is available in the client table, so let’s focus on the value of the POST body that resulted in the output above.

<ajax-request action="getstat" comp="stamgr" updater="clientsummary.1482428744654.781" GET_PREFERENCE="column-edit"><client LEVEL="1"/><pieceStat start="15" pid="2" number="300" requestId="clientsummary.1482428744654.781"/></ajax-request>

Unfortunately, we will not find documentation for what each parameter means, and if used in a POST without modification, it will not yield a result. However, after a bit of trial and error, we come away with the following string:

<ajax-request action="getstat" comp="stamgr"><client LEVEL="1"/><pieceStat start="0" pid="100" number="1" requestId="myrequest"/></ajax-request>
  1. The updater and GET_PREFERENCE keys were removed as they are were unnecessary.
  2. The start value was changed to “0” to begin with the first node or client, and the number value was changed to “1” to return a single client entry.
  3. The pid and requestId values can be customized.

Let’s build the request and execute it.

#Name of the stat page
$StatPage = '_cmdstat.jsp'

#The POST body
$StatBody = '<ajax-request action="getstat" comp="stamgr"><client LEVEL="1"/><pieceStat start="0" pid="100" number="1" requestId="myrequest"/></ajax-request>'

#Execute the request
$Request = Invoke-RestMethod ($BaseURL + $StatPage) -WebSession $Rks -Method Post -Body $StatBody

#Output the result the console
$Request.'ajax-response'.response.'apstamgr-stat'.ChildNodes

If we compare the last line with the XML structure depicted in the last Fiddler screenshot, we can see how we are moving down the node tree to output the result. When executed, the script will generate the following output for each client:

mac : f8:xx:xx:xx:xx:06
vap-mac : 54:xx:xx:xx:xx:4c
vap-nasid : 54-xx-xx-xx-xx-4C
ap : 54:xx:xx:xx:xx:40
wlan-id : 4
status : 1
first-assoc : 1482225314
vlan : 10
called-station-id-type : 0
ssid : ExampleSSID
wlan : ExampleWLAN
user : DemoUser
ip : 172.x.x.x
ipv6 : 
role-id : 1
channel : 36
description : ApDescription
dvcinfo-group : 4
dvcinfo : iOS
hostname : ClientHostname
channelization : 40
radio-type : 11na
ieee80211-radio-type : a/n
radio-type-text : 11na
rssi : 31
received-signal-strength : -81
noise-floor : -112
num-interval-stats : 0
location : Room X
auth-method : EAP
acct-multi-session-id : 543d374aab4cf82793c200065858f6a25f12
acct-session-id : 586f2ed8-1672a1

Conclusion

While this approach might not be suitable for all use cases, it does have value, especially compared to scraping a CLI or working with a poor SNMP implementation.