I have a Watchguard Firebox T40-W. It has a web login where you can access all kinds of informations.
My goal is to duplicate that same web request with nodejs to be able to get those informations via code. Since the Firebox doesn't have some kind of api documentation (Yes, I know watchguard cloud has an api interface), I tried to just copy everything that happens on the login page to get logged in.
The login has two parts. First the pre login and second the actual login.
When looking inside the browser when accessing the login page, I can see a login.js
gets loaded. That file has everything I need to know.
The pre login does look like this:
login : function () { if (!LOGIN.validateForm()) { return false; } $('#submit').button('loading'); var username = $('#username').val(); var password = $('#password').val(); var domain = $('#domain').val(); var obj = {}; var agent_url = ''; var agent_methodName = ''; obj = {password: password, user: username, domain: domain, uitype: '2'}; agent_url = 'agent/login'; agent_methodName = 'login'; $.xmlrpc({ url: agent_url, methodName: agent_methodName, params: [obj], success: LOGIN.loginSuccess, error: LOGIN.loginError }); return false;},
This is a xmlrpc request to get needed data. That part was pretty easy to build in node:
var client = xmlrpc.createSecureClient({ host: this.host, port: this.port, path: '/agent/login'}); var obj = { password: password, user: username, domain: "Firebox-DB", uitype: '2' } // Sends a method call to the XML-RPC server return new Promise<void>((res, rej) => { client.methodCall('login', [obj], (error: any, value: any) => { if (error) { if (error.code === 409) { rej(error.faultString) } else { rej(error); } return; } this.sid = value.sid; this.wga_csrf_token = value.csrf_token; this.privilege = value.readwrite.privilege +""; res(); }); });
In this part a xmlrpc client gets created and a request will be made to the same location as the original script of the webpage does. This does work and will get me the needed informations for the actual login.
The original webpage login will run the function LOGIN.loginSuccess
if that xmlrpc request succedes. Lets take a look:
loginSuccess : function (data, textStatus, jqXHR) { var username = $('#username').val(); var password = $('#password').val(); var domain = $('#domain').val(); var from_page = $('#from_page').val(); var sid = ''; var wga_csrf_token = ''; var cp_csrf_token = $('#cp_csrf_token').val(); var privilege = 0; var req_id = ''; var mfa_options = ''; var authServer = $('#sel_auth_server').val(); if (data.length > 0) { sid = data[0].sid; wga_csrf_token = data[0].csrf_token; privilege = data[0].readwrite.privilege; } data = { username: username, password: password, domain: domain, sid: sid, wga_csrf_token: wga_csrf_token, cp_csrf_token: cp_csrf_token, privilege: privilege, from_page: from_page }; LOGIN.postLogin(data);},
A new object will be created an LOGIN.postLogin()
gets executed:
postLogin : function (params) { var form = document.createElement("form"); form.setAttribute("method", "post"); form.setAttribute("action", "auth/login"); var key, field; for (key in params) { if (params.hasOwnProperty(key)) { field = document.createElement("input"); field.setAttribute("type", "hidden"); field.setAttribute("name", key); field.setAttribute("value", params[key]); form.appendChild(field); } } document.body.appendChild(form); form.submit();}
This basically just converts the object to a invisible form and submits it to auth/login
as POST
.
Building this part with nodejs would look like that:
const params = new URLSearchParams(); params.append('username', username); params.append('password', password); params.append('domain', this.domain); params.append('sid', this.sid); params.append('wga_csrf_token', this.wga_csrf_token); params.append('cp_csrf_token', this.cp_csrf_token); params.append('privilage', this.privilege); params.append('from_page', '/'); const response = await fetch(`https://${this.host}:${this.port}/auth/login`, {method: 'POST', body: params, headers: this.httpHeaders }); const data = await response.text();
I got every parameter I need with the right value. The header gets set with an session_id=xxx
that is in every request on the actuall webpage.
Problem is that running the actual login will result in a 403 - forbidden
error. Seems like the request from the webpage does something else so I just copied every property of that header into the nodejs request:
private httpHeaders = {"Cookie": '', // Will be filled by code. Will look like 'session_id=xxx'"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36","Origin": 'https://10.10.10.1:8080',"Referer": 'https://10.10.10.1:8080/auth/login?from_page=/',"Accept": 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',"Accept-Encoding": 'gzip, deflate, br, zstd',"Accept-Language": 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',"Cache-Control": 'max-age=0',"Connection": 'keep-alive',"Sec-Ch-Ua": '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',"Sec-Ch-Ua-Mobile": '?0',"Sec-Ch-Ua-Platform": '"Windows"',"Sec-Fetch-Dest": 'document',"Sec-Fetch-Mode": 'navigate',"Sec-Fetch-Site": 'same-origin',"Sec-Fetch-User": '?1',"Upgrade-Insecure-Requests": '1'}
Doing that will get me the response <html><head><meta http-equiv="Refresh" content="0;url=/auth/login" /></head><body></body></html>
...
Right click the request inside chrome from the network tab and copy => as fetch (nodejs) will copy pretty much exactly what I did. Running that copied function will also result in that kind of refresh response. But I noticed when removing the Referer
from the header, the response switches back to 403 - forbidden
.
What am I missing here?