Skip to content

Instantly share code, notes, and snippets.

@ghills
Last active January 6, 2023 22:26
Show Gist options
  • Select an option

  • Save ghills/5d2b4cba725485b97a306d989687d245 to your computer and use it in GitHub Desktop.

Select an option

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
<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