0

If so, please let me know what those security risks are, and, for bonus points, whether those security risks can be avoided by using the safest known function-cloning solutions that use eval(), new Function(), .bind, .call, or .apply... in the context of unknown user input... considering the below function.

Here's a simplified, shallow, dirty cloning function (preceded by an example function-to-clone and an example class-to-clone) to demonstrate what I mean by dirty cloning:

function doStuff(x, y){
  console.log("did stuff with x: " + x + " and y: " + y);
}

let clonedFunc = dirtyClone(doStuff); // shallow-clone; for brevity
clonedFunc( 5, "because" );


class Animal{
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

let clonedClass = dirtyClone(Animal); // shallow-clone; for brevity
let anInstance = new clonedClass("Cloned-Class Instance");
anInstance.speak();




function dirtyClone(class_or_function){ // shallow-clone; for brevity

  if(typeof class_or_function !== "function"){

    console.log("wrong input type");

    return false;
  }


  let stringVersion = class_or_function.toString();
  let preCurl = stringVersion.match(/[^{]*/)[0];
  let funcType;
  
  if(preCurl.indexOf("function") !== -1) funcType = "function";
  else if(preCurl.indexOf("class") !== -1) funcType = "class";
  else {
    console.log("wrong input type");
    return false;
  }
  
  // uuid adapted from: https://stackoverflow.com/a/21963136
  let lut = []; for (let i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); }
  let d0 = Math.random()*0xffffffff|0;
  let d1 = Math.random()*0xffffffff|0;
  let d2 = Math.random()*0xffffffff|0;
  let d3 = Math.random()*0xffffffff|0;
  let aUUID = "a_" + lut[d0&0xff]+lut[d0>>8&0xff]+lut[d0>>16&0xff]+lut[d0>>24&0xff]+'_'+
    lut[d1&0xff]+lut[d1>>8&0xff]+'_'+lut[d1>>16&0x0f|0x40]+lut[d1>>24&0xff]+'_'+
    lut[d2&0x3f|0x80]+lut[d2>>8&0xff]+'_'+lut[d2>>16&0xff]+lut[d2>>24&0xff]+
    lut[d3&0xff]+lut[d3>>8&0xff]+lut[d3>>16&0xff]+lut[d3>>24&0xff];
  let bUUID = "b_" + lut[d0&0xff]+lut[d0>>8&0xff]+lut[d0>>16&0xff]+lut[d0>>24&0xff]+'_'+
    lut[d1&0xff]+lut[d1>>8&0xff]+'_'+lut[d1>>16&0x0f|0x40]+lut[d1>>24&0xff]+'_'+
    lut[d2&0x3f|0x80]+lut[d2>>8&0xff]+'_'+lut[d2>>16&0xff]+lut[d2>>24&0xff]+
    lut[d3&0xff]+lut[d3>>8&0xff]+lut[d3>>16&0xff]+lut[d3>>24&0xff];
  

  switch(funcType){
      
    case "function":
      let functionName = stringVersion.match(/[^(]*/);
      let functionBod = stringVersion.replace(functionName,"");
      let newFunction = "function " + aUUID + " " + functionBod;

      let funScript = document.createElement("SCRIPT");
      funScript.text = newFunction;
      document.body.append(funScript);
      
      break;
    
    case "class":
      let classNameDec = stringVersion.match(/[^{]*/);
      let classBod = stringVersion.replace(classNameDec,"");
      let newClass = "class " + bUUID + " " + classBod;

      let classScript = document.createElement("SCRIPT");
      classScript.text = aUUID + " = " + newClass;
      document.body.append(classScript);
      
      break;
      
    case "child_class":
        // excluded; for brevity
        break;
  
    default:
      console.log("wrong input type");
      return false;
  }
  
  return window[aUUID];
}
Ed_Johnsen
  • 136
  • 8
  • It all depends on how you do things. Just like there's safe and unsafe ways to use eval, there are also safe and unsafe ways to clone a function. Is user input involved in any of this? If so, how? – Scotty Jamison Jun 30 '21 at 03:04
  • @ScottyJamison Fair enough; any thoughts on the safety of the way things are done in the included example... if given user input? (also, thanks; I've edited my question to clarify a scenario). – Ed_Johnsen Jun 30 '21 at 03:09

1 Answers1

1

There's many angles to take this from, each with their own answer. I'm going to try and take this from the angle that you're a library author, and you're wanting to duplicate callbacks passed in. What are the security implications of doing this? Let's assume your library consumers aren't aware that you're doing this.

I'll list out some potential bugs that may come from this. Bugs are often a doorway into security vulnerabilities, so it's good to be aware of these.

  • The client's function may have been written in strict-mode, while the duplicate function may not be. Or vice-versa.
  • The client's function may rely on variables from an outer scope that are inaccessible in the duplicate's location.
  • An instance of one class will fail an instanceof check against it's duplicate class.
  • Built-in functions can't be duplicated, and will just throw a syntax error if you try to do so.
  • There's probably a ton more pitfalls

None of this in-and-of-themself will cause security issues, but if the library user is really unaware that function duplication is happening - then it's possible that the library user will write what looks like working code, but really has some deep bugs that a hacker could find and exploit.

For example:

let name = escapeHTMLChars(await fetchArticleNameFromServer())

function getName() {
  return name
}

// ...

const authorName = await callFunctionUsingYourMagicLibrary(getName)
headerElement.innerHTML = authorName

This chunk of code looks harmless. Maybe the code author shouldn't be using innerHTML here, but they are, and it doesn't look like there would be any issues to do so in this particular scenario.

But, suppose your callFunctionUsingYourMagicLibrary() function copy-pasted the getName() function before calling it. Now, it doesn't return the name property from the local scope, instead, it returns the global window.name property, which eventually will get inserted into headerElement's .innerHTML. Often, window.name is just an empty string, so it means this page will be left with a simple bug that causes the author's name to always be blank - something that should have been caught, but maybe it slipped through.

Now, suppose you're allowed to leave comments on this website, with links to other pages. In the comment form, you write the links like this: [<NAME>](<URL>), and the resulting HTML that other viewers see will have the following:

<a href="javascript:void(0)" onclick="window.open(<URL>, <NAME>)"> <NAME> </a>

where <url> is filled in with the URL the comment author wishes to leave, and <name> is filled in with the name the comment author chose. Notice that we're using window.open() to open the links.

Now we have an XSS vunerability.

A comment author could leave a comment, with the name set to something like <script>...</script>, and link the viewer to one of the pages that uses the copy-pasted function. When anyone clicks on this link, the linked page's window.name property will be set to the second argument of window.open() (which is the <script> stuff), which ultimately findd its way into the DOM via header-element's innerHTML property.

Ok, ok, I know a lot of stuff has to line up just right for that particular example to happen, and I know that this example involves some not-so-great programming, but it's the best I could think of. And if this example exists, I'm sure others do too, and one of them is bound to actually happen if we're being too careless.

Scotty Jamison
  • 10,498
  • 2
  • 24
  • 30