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?
4 Answers 4
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.
-
\$\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\$xyhhx– xyhhx2014年03月17日 20:37:47 +00:00Commented Mar 17, 2014 at 20:37
-
\$\begingroup\$ Added to github.com/martin-wiseweb/import-base64-script \$\endgroup\$xyhhx– xyhhx2014年04月04日 16:15:48 +00:00Commented Apr 4, 2014 at 16:15
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
.
-
\$\begingroup\$ Related issue at GitHub project. \$\endgroup\$sineemore– sineemore2018年06月27日 10:40:03 +00:00Commented Jun 27, 2018 at 10:40
// 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 totext/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, andtype
,subtype
,attribute
andvalue
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.
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>
-
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– Vogel6122018年06月21日 19:34:47 +00:00Commented Jun 21, 2018 at 19:34