6
\$\begingroup\$

So I noticed Chrome has quirky behaviour when it encounters script tags whose src is a base64 value. I decided to write a quick jQuery method that is supposed to work around it:

jQuery.extend({

    /**
     * Takes a script decodes the base64 src, puts it into the body of the script tag,
     * then puts it in whatever parent specified.
     *
     * @requires https://plugins.jquery.com/base64/
     * 
     * @param  {Object} script The script tag that should be manipulated
     * @param  {Object} parent The parent element to append the final script tag to.
     * 
     * @return {Object}        The script tag in question
     */
    importBase64Script : function ( script, parent ) {

        // Check for base64 library
        if ( typeof $.base64 === 'undefined' )
            throw 'No $.base64 found!';

        // Sanitize our script var
        // Normalize our script object
        script = ( script instanceof jQuery ? script : $(script) );

        // Check if it is a script tag
        if ( script[0].tagName !== "SCRIPT" )
            throw "Not a script tag";

        // Set default parent value
        parent = parent || $('head');
        // Normalize our parent var
        parent = ( parent instanceof jQuery ? parent : $(parent) );

        // We're gonna extract the base64 value
        var re        = /data:[a-z]+\/[a-z]+;base64,([0-9a-zA-Z\=\+]+)/,
        base64Content = script.prop('src').match(re)[1],
        scriptContent = $.base64.decode( base64Content );

        // Drop the decoded javascript into the contents of the script tag
        script.html( scriptContent );
        // Clear src value
        script.prop('src','');

        // Append it to the parent
        parent.append(script);

        return script;

    }
});

I tested a few of the conditions on JsPerf to see which is better performance wise. Granted, I didn't do a full sweep on every browser.

Any suggestions that anybody could make?

\$\endgroup\$
2
\$\begingroup\$

Awesome,

  • Well commented
  • Nothing bad on JsHint.com
  • Quiet and robust handling of parameters
  • It does exactly what it says on the tin

Unrelated to CR, but this is github worthy.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the positive feedback! I'll wait a bit to see if anybody might spot some details that could improve the code but if not I'll mark your answer as correct \$\endgroup\$ – Martin Mar 17 '14 at 20:37
  • \$\begingroup\$ Added to github.com/martin-wiseweb/import-base64-script \$\endgroup\$ – Martin Apr 4 '14 at 16:15
5
\$\begingroup\$

Watch out!

jQuery is full of secrets. html method is not just a simple wrapper around innerHTML property.

Possible fail condition:

var src = `if ("<x/>" !== "<" + "x" + "/" + ">") {
  console.log("Math gone wrong.");
}`;

$("<script>").html(src).appendTo(document.head)
<!-- latest jQuery -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

Let's see what exactly is happening here:

console.log($("<i>").html("<x/>")[0].innerHTML)
<!-- latest jQuery -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

jQuery html method replaces <x/> with <x></x>. I guess it is perfectly valid for some HTML, but it fails with our JavaScript input.

The π robbery

Let's try to exploit this with malicious input and steal some valuable data:

var src = `var username = "Kevin <x\";alert(Math.PI);\"/> Mitnick";`;

$("<script>").html(src).appendTo(document.head);
<!-- latest jQuery -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

I suggest you to carefully check jQuery html source code.

Also you may replace it with plain old innerHTML.

\$\endgroup\$
2
\$\begingroup\$
// We're gonna extract the base64 value
var re        = /data:[a-z]+\/[a-z]+;base64,([0-9a-zA-Z\=\+]+)/,

This regex is too limited. The "data" URL scheme is specified in RFC 2397, which says:

The URLs are of the form:

data:[<mediatype>][;base64],<data>

The <mediatype> is an Internet media type specification (with optional parameters.) The appearance of ;base64 means that the data is encoded as base64. Without ;base64, the data (as a sequence of octets) is represented using ASCII encoding for octets inside the range of safe URL characters and using the standard %xx hex encoding of URLs for octets outside that range. If <mediatype> is omitted, it defaults to text/plain;charset=US-ASCII. As a shorthand, text/plain can be omitted but the charset parameter supplied.

Furthermore, it says:

 dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
 mediatype  := [ type "/" subtype ] *( ";" parameter )
 data       := *urlchar
 parameter  := attribute "=" value

where urlchar is imported from RFC2396, and type, subtype, attribute and value are the corresponding tokens from RFC2045, represented using URL escaped encoding of RFC2396 as necessary.

RFC 2045 Section 5.1 says:

The type, subtype, and parameter names are not case sensitive.

In summary,

  • The mediatype is optional, is case-insensitive, may contain parameters, and may be URL-escaped.
  • For completeness, you should probably also support percent-encoded data in addition to base64-encoded data.
\$\endgroup\$
0
\$\begingroup\$

Thanks for the post.

Here, I refine this. If you have a string that may be a url or a base64 encoded script, it will be added as a normal script with src or as a script with the source decoded and embeded. I am using this within the context of nunjucks but it may prove useful elsewhere. jQuery not used.

Note: <script src="{{my_script}}"></script> does not seem to work with base64 encoded script URIs. I am using Chrome v67.0.3396.87

<script>
  var addScript = function(data) {
    var m = data.match(/data:[a-z]+\/[a-z]+;base64,([0-9a-zA-Z\=\+]+)/);
    if(m) document.write('<'+'SCRIPT>'+atob(m[1])+'<'+'/SCRIPT>');
    else document.write('<'+'SCRIPT src="'+data+'"><"+"/SCRIPT>');
  }
  addScript("{{my_script}}");
</script>
\$\endgroup\$
  • 3
    \$\begingroup\$ Welcome to Code Review! You have presented an alternative solution, but haven't reviewed the code. Please explain your reasoning (how your solution works and how it improves upon the original) so that the author can learn from your thought process. \$\endgroup\$ – Vogel612 Jun 21 '18 at 19:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.