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?

konijn
34.2k5 gold badges70 silver badges267 bronze badges
asked Mar 17, 2014 at 15:47
\$\endgroup\$

4 Answers 4

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.

answered Mar 17, 2014 at 19:25
\$\endgroup\$
2
  • \$\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\$ Commented Mar 17, 2014 at 20:37
  • \$\begingroup\$ Added to github.com/martin-wiseweb/import-base64-script \$\endgroup\$ Commented Apr 4, 2014 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.

answered Jun 21, 2018 at 20:54
\$\endgroup\$
1
  • \$\begingroup\$ Related issue at GitHub project. \$\endgroup\$ Commented Jun 27, 2018 at 10:40
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.
answered Jun 21, 2018 at 19:49
\$\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>
answered Jun 21, 2018 at 19:19
\$\endgroup\$
1
  • 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\$ Commented Jun 21, 2018 at 19:34

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.