Last active
January 6, 2023 22:26
-
-
Save ghills/5d2b4cba725485b97a306d989687d245 to your computer and use it in GitHub Desktop.
Single page utility to decode hex-encoded attributes in the LastPass vault XML dump
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <html> | |
| <head> | |
| <title>Decode LastPass Dump XML</title> | |
| </head> | |
| <body> | |
| <h4>Instructions</h4> | |
| <ol> | |
| <li>Run the JS code snippet below in the developer console for a LastPass extension vault tab.</li> | |
| <li>Copy the results to the clipboard.</li> | |
| <li>Paste the contents into the Encoded XML text area below.</li> | |
| <li>Click the Decode button.</li> | |
| </ol> | |
| <a href="#" onclick="copySnippet()">Copy code</a> | |
| <pre class="code" id="fetchSnippet"> | |
| fetch("https://lastpass.com/getaccts.php", {method: "POST"}) | |
| .then(response => response.text()) | |
| .then(text => console.log(text.replace(/>/g, ">\n")));</pre> | |
| <h4>Encoded XML</h4> | |
| <textarea id="encodedArea"></textarea><br /> | |
| <button onclick="decodeClicked()">Decode</button> | |
| <h4>Results</h4> | |
| <a href="#" onclick="copyResults()">Copy results to clipboard</a> | |
| <pre id="decodedContents">Click 'Decode' to process</pre> | |
| <div id="alertBox"></div> | |
| </body> | |
| <script type="text/javascript"> | |
| function decodeClicked() { | |
| const encoded = document.getElementById("encodedArea").value; | |
| const outputArea = document.getElementById("decodedContents"); | |
| outputArea.innerText = "Running..."; | |
| outputArea.innerText = decodeXml(encoded); | |
| } | |
| function copyResults() { | |
| const outputArea = document.getElementById("decodedContents"); | |
| navigator.clipboard.writeText(outputArea.innerText) | |
| .then(() => showMessage('output copied')); | |
| } | |
| function copySnippet() { | |
| const outputArea = document.getElementById("fetchSnippet"); | |
| navigator.clipboard.writeText(outputArea.innerText) | |
| .then(() => showMessage('snippet copied')); | |
| } | |
| function showMessage(text) { | |
| const msgArea = document.getElementById("alertBox"); | |
| msgArea.innerText = text; | |
| msgArea.style.display = "block"; | |
| setTimeout(() => { | |
| msgArea.style.display = "none"; | |
| msgArea.innerText = ''; | |
| }, 3000); | |
| } | |
| // hex-ish strings. At least two characters as each hex | |
| // digit is half a character | |
| const hexRe = /^[0-9a-fA-F]{2,}$/; | |
| // add any other desired attributes to decode here | |
| const decodableAttributes = [ | |
| 'url', | |
| 'domain' | |
| ]; | |
| // decode the XML string from LP vault by looking for hex encoded | |
| // ascii attributes | |
| function decodeXml(encodedStr) { | |
| const domParser = new DOMParser(); | |
| const xmlDocument = domParser.parseFromString(encodedStr,"text/xml"); | |
| // traverse entire dom | |
| const nodesToProc = [xmlDocument]; | |
| while(nodesToProc.length > 0) { | |
| const node = nodesToProc.pop(); | |
| // add all children to process next | |
| for(let i = 0; i < node.children.length; i++) { | |
| nodesToProc.push(node.children[i]); | |
| } | |
| handleNode(node); | |
| } | |
| return new XMLSerializer().serializeToString(xmlDocument.documentElement); | |
| } | |
| function hex2a(hexx) { | |
| let hex = hexx.toString();//force conversion | |
| let str = ''; | |
| for (let i = 0; i < hexx.length; i += 2) { | |
| str += String.fromCharCode(parseInt(hexx.substr(i, 2), 16)); | |
| } | |
| return str; | |
| } | |
| function handleNode(node) { | |
| // check each attribute for decodable strings | |
| for(let i = 0; node.attributes && i < node.attributes.length; i++) { | |
| const curVal = node.attributes[i].value; | |
| // 1) attribute name is in allowlist of known hex encoded values | |
| // 2) value looks like a hex string | |
| if(decodableAttributes.indexOf(node.attributes[i].name) !== -1 | |
| && hexRe.test(curVal)) { | |
| node.attributes[i].value = hex2a(curVal); | |
| } | |
| } | |
| } | |
| </script> | |
| <style> | |
| .code { | |
| border-width: 1px; | |
| border-style: solid; | |
| border-color: grey; | |
| padding: 10px; | |
| } | |
| #encodedArea { | |
| width: 100%; | |
| height: 500px; | |
| } | |
| #alertBox { | |
| position: fixed; | |
| top: 0; | |
| right: 0; | |
| padding: 5 20 5 20; | |
| background-color: greenyellow; | |
| border-color: black; | |
| border-width: 1; | |
| border-style: solid; | |
| font-size: x-large; | |
| display: none; | |
| } | |
| </style> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment