Skip to content

Cleaning up the IP.Board url4short mess

XDebug to the rescue…

The condensed, I-just-want-to-fix-my-site version:

On your server, try:

grep ‐ri \$mds /wherever/your/website/folder/is

to locate the injected code, and while file it resides in. You can then go into that file and remove it.

Also try re-caching all the skins and languages in the Admin Control Panel. Make sure all IP.Board updates and patches are applied to prevent the compromise happening again.

Reset your passwords and keys. Take measures to detect and continue detecting other infiltrations.

My friend Niall Brady dropped me an email, saying that some of the users of his Windows-Noob forums were reporting getting redirected to a spammy-looking site (url4short dot info) when clicking on search results to the site.

The forums run the Invision Power Board (IP.Board) software. There had been some reports of vBulletin boards being hit with this kind of spammy redirect, but fewer suggestions that this was an IPB problem. There had been a patch for a critical IPB issue released in December, but that had, obviously, been applied to the site as part of normal good practice.

Nevertheless, I was concerned. Clicking on a search engine result should definitely not be redirect somewhere other than the result page!

Without evidence that the issue was not limited to one machine, or one connection, however, it could not be ruled out that it was just malware on that visitor’s machine.

Searching for Evidence

Why base64_decode()?

Injected code wants to hide from people like me, who are looking for it so it can be removed.

Instead of being ‘normal’ looking PHP code, the actual payload of the injected code will be encoded somehow, so it is difficult to find in a search. Often, base64_decode() and eval() are used to then decode the malicious code at runtime, so it can actually do its dirty work!

When there is a suggestion that a site has been subject to some kind of malicious code injection, which was what the report hinted at, the first port of call is to search the filesystem for things that look suspicious.

In the case of PHP injection, the usual suspects are odd-looking calls to base64_decode() and/or eval(). (There are plenty of legitimate uses of those functions, but, in combination with obviously obfuscated code, they should arouse suspicion.)

So, I logged on and started hunting through the website files for these strings, using grep:

grep -r base64 /where/the/websites/live/ | less
grep -r eval /where/the/websites/live/ | less

These commands produced some results, but… nothing that looked like it wasn’t obviously a legitimate part of IP.Board, which does make use of these PHP functions!

This was a little frustrating. It pointed to a much more sophisticated attack, or, at least, an attack that was more sophisticated at hiding itself.

Please support this work!

There used to be advertising here, but I no longer feel sure that advertising delivers the best experience and truly reflects the values of this site.

Keeping things running, however, is not without financial cost. If you would like to support the time and effort I have put into my tutorials and writing, please
consider making a donation.

The Code, or the Database?

IP.Board makes some interesting (and I would say unorthodox) use of file caching in the database. This meant that possibly, the injection had occurred inside the database, so a search of the filesystem would not find it.

So, I dumped the database:

mysqldump -u some_user -p some_database > database.sql

and then ran similar grep commands there, too.

grep -i base64 database.sql

Nothing odd!

Alternative base64 Hunting

My next thoughts were that perhaps this code was obfuscated and/or encoded, but that somehow it managed not to use the base64_decode() and eval() functions directly. Entirely possible.

So, I set about trying to search for base64 encoded code in a different way.

One hallmark that suggests such encoded information is the presence of two or more equals signs (searching for one would bring too many false positives!), which are ‘padding’ for when the input doesn’t fit into base64’s fixed block size:

If there was only one significant input byte, only the first two base64 digits are picked, and if there were two significant input bytes, the first three base64 digits are picked. ‘=’ characters might be added to make the last block contain four base64 characters.

grep -r == /where/the/websites/live/

Nothing but false positives there, but perhaps the encoded data had a deliberately perfect input size to avoid this tell-tale sign!

“Works for Me”

One of the issues so far, is that I had’t yet been able to reproduce the spammy redirect for myself. It happened only some of the time, and even though I cleared cookies and cache, and even swapped internet connections to use a fresh IP address, I couldn’t seem to get the spammy redirect code delivered to me. A very sophisticated attack that was able to hide from known administrators?

So, I was stuck, not being able to search for anything related to the output of the injected PHP — I hadn’t seen it! I was still stabbing in the dark looking for things that were obviously suspect, but I hadn’t found any.

Then, a breakthrough. Niall had the bad redirect happen to him, for the first time.

A little while later, and he had been able to reproduce it several times, and provide a Wireshark packet dump of the whole process, start to finish — from search engine result page to the redirect, and to the spammy page itself.

Analysing the Output

Armed with this new packet dump, I could now see actually what was happening to users, although not yet the code that was causing this to happen. But we were somewhere!

An additional JavaScript file was being referenced on the generated HTML page itself, which went to /forums/index.php?ipbv=[normal caching string]&g=js. That request itself looks (almost) quite normal, and something that is part of IPB.

However, the server’s response to this request for the extra JavaScript file was most definitely the thing we were looking for:

The injected JavaScript

Yep, document.location = spammy spammy spam is asking the browser to immediately redirect to our not-so-friendly spam site!

This was, oddly, reassuring to me.

Yes, we knew that it was the site that was compromised with this code, which is deeply uncomfortable and not what we ever wanted to happen, but at least we did know for sure what was wrong.

I could now reproduce it by running:

curl -b "session_id=some_session_id" -A "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 1.1.4322)" -e "" "[caching string]&g=js"

Please support this work!

There used to be advertising here, but I no longer feel sure that advertising delivers the best experience and truly reflects the values of this site.

Keeping things running, however, is not without financial cost. If you would like to support the time and effort I have put into my tutorials and writing, please
consider making a donation.

Getting a grep

So, back to grep it was. Still no obfuscated code in sight, I tried searching for what I now knew the output looked like, hoping that the code was unobfuscated, and that is why I had missed it.

grep -ri document.location /where/the/websites/live/

Nothing suspicious.

grep -ri "\$_GET['g']" /where/the/websites/live/


I tried a few more searches, but to no avail. A different tactic was needed.

Unleashing the Debuggers

So, I could now reproduce the issue — if I supplied the right session cookie and requested that special g=js URL, I got the spammy redirect spat back at me. Perhaps I could use this to start tracing where this code was executing.

This was an issue on a live website, yes, but what I needed was a debugging tool that would normally be used in site development.

Fun with register_shutdown_function()

My first thought now was that this code must be the last, or nearly the last, thing that runs when I request this g=js page. So, there must be a way to trace what the last thing PHP was doing in that request.

So, I hooked up a shutdown function in one of the files IPB loads early in the process:

[php]function peters_shutdown_function() {
echo debug_backtrace();
register_shutdown_function( ‘peters_shutdown_function’ );

This did print out a backtrace when I requested the g=js URL again, showing me what had immediately run after the JS redirect, but again, there was nothing suspicious. It clearly wasn’t able to trace back far enough, or trace in the right file.

I needed to turn to more heavy-duty tools.

XDebug to the Rescue!

XDebug is a PHP extension that vastly enhances the debugging capabilities of the runtime. It is very useful for providing more detailed error messages during development, or tracing through a program as it is running to see what is actually going on.

So, I installed and enabled it, and requested the same JavaScript page again, this time, enabling an XDebug trace file to be created.

This trace file would show me every function call that happened in order to generate our spammy JavaScript link page. That must reveal where the injected code is that is doing the deed!

File in hand, I ran the trusty old base64_decode search against it.


XDebug trace showing eval and base64

So, it was base64_encoded, and it was using eval() to execute itself once unpacked. It was just, until now, well hidden!

More importantly, it shows us that the code was injected into /forums/cache/skin_cache/cacheid_1/skin_global.php, on line 814.

I opened up that file, immediately found the injected code, and disabled it. No-one could then reproduce the spammy redirect issue. Win.

How Did it Hide?

The code, as obfuscated, lived in the cache file in this form (I commented it out as of taking this screenshot, but it was not commented out when the attack was ‘live’):

The obfuscated code

The $k variable was actually a ‘key’ that the attacker could use to come back, send it to the script and, oh, and run any code they wanted. Not good.

The rest of it was the encoded form of what we saw in the XDebug trace.

It seemed to be able to hide by not ever using base64_decode() or eval() directly. Instead, there is clever use of regular expressions to somehow unpack the encoded malicious stuff.

How Did It Get In?

Another important question is how the injection was possible in the first place. A week later, and after much further analysis of a number of sources of information, it cannot be absolutely determined with the information I have available to me.

I can tell you what I think is most likely.

The attack likely happened around Christmas 2012, when a number of other forum sites were hit, exploiting either VB.SEO plugin vulnerabilities in the case of vBulletin, or the critical vulnerability in IP.Board in our case.

It seems most likely that the site was compromised, and this PHP was injected, in the 24-hour latency between the patch being made available and it being applied, or perhaps before the patch was available (zero-day).

This should underline, if it is not already obvious, the important of prompt application of security updates.

Niall and myself have taken measures to ensure either of us can respond even faster to a critical security fix such as this one, and apply it quicker than 24 hours after release, to minimise that window. That would have closed the hole that likely allowed this in.

There is no evidence of the attacker being able to privilege escalate to do anything else on the system, and no evidence that anything else was done other than the injection of the redirect. To the greatest degree that it is possible to know, nothing else bad was done to the system. It continues to be monitored closely.

Moving On

It is uncomfortable and regrettable when any site gets compromised, but it is a fact of life that software has vulnerabilities, some known and some as yet unknown. Software that is exposed to the world via the internet therefore can be exploited, if the bad guy knows about a vulnerability that the good guys don’t.

Twenty-four hours of latency is plenty enough for the attacker, and everyone is a target, because exploits can be automated to require no human input.

On the positive side, I learned much about detecting and removing such exploits through this process. When things do go badly in one place, you get an opportunity to learn how attacks work, where their weaknesses are, and what security measures you can improve across the whole gamut of computer systems over which you have influence.

How Do I Get Rid of It?

On your IP.Board web server, try:

grep ‐ri \$mds /wherever/your/website/folder/is

to locate the injected code, and while file it resides in. You can then go into that file and remove it.

($mds is the unique name of one of the variables this particular variant used. You might also want to try searching for \$jsa, \$jsb, or for odd uses of preg_replace combined with strtr.)

Also try re-caching all the skins and languages in the Admin Control Panel. Make sure all IP.Board updates and patches are applied to prevent the compromise happening again.

Reset your passwords and keys. Take measures to detect and continue detecting other infiltrations.

Quite an adventure.

Like this post?

If you would like to support the time and effort I have put into my tutorials and writing, please consider making a donation.


  1. andrew wrote:

    Thanks for taking the time to write this up.

    Most useful for cleaning up an Invisionboard site I have.

    Sunday, January 20, 2013 at 22:07 | Permalink |
  2. Will wrote:

    legend – thanks

    Sunday, January 20, 2013 at 22:09 | Permalink |
  3. Al wrote:

    For us this patch wasn’t enough. The table ibf_skin_cache also contained the JS code so patching the file wasn’t enough as it was reintroduced from there.

    Please search that table and remove offending code there also.

    Monday, December 16, 2013 at 09:11 | Permalink |
  4. M. wrote:

    The American Distilling Institute forums have been “infected” with this. I was under the impression that this was some Russian scam artist redirecting people to a page full of ads in a scheme to botch online marketing agencies out of their money.

    Saturday, January 11, 2014 at 19:59 | Permalink |
  5. alban.montaigu wrote:

    Thanks a lot for this help !

    Juste some more information :

    On my site, I had to clean the table ips_skin_templates too.

    Have a nice day

    Friday, January 17, 2014 at 19:49 | Permalink |
  6. Fred wrote:

    Thanks for this, I just found this infection on our site.

    Sunday, February 2, 2014 at 19:17 | Permalink |
  7. Rob wrote:

    I don’t know how long we had this problem for, we’ve only just had some people run into it and we’ve had that patch applied for a very long time. But thanks, you did a great job analysing this.

    Tuesday, April 1, 2014 at 01:48 | Permalink |
  8. Benjamin wrote:

    This was sooo helpful! I was experiencing the same problem and this document helped me remove the bad code.
    Thanks a million for taking the time to document all of this.

    Sunday, June 29, 2014 at 06:04 | Permalink |
  9. Simon wrote:

    This post is still very relevant. We had a customer complain today about google redirecting to url4short.
    Finding this post solved the problem for us!
    The string in our case was: klf=preg_replace($f,strtr($rsa,$pka,$pkb),’html’);

    Recaching the skin fixed the issue.

    Wednesday, January 14, 2015 at 17:50 | Permalink |
  10. DanWex wrote:

    Thanks a lot for this topic. In my case the variable was $pk, not $k. And don’t forget to clear and fix template in ibf_skin_cache table, because it has injection too.

    Friday, January 16, 2015 at 13:49 | Permalink |
  11. Geo wrote:

    We’ve been hit with this too, started around a week ago and despite following this blog and comments the code gets re-injected again within 24 hours.

    For us the following strings are being used to identify the code:


    What I’ve noticed is that after removing the offending code from the cache table (skin_cache entry for skin_global) and re-caching skin and language sets, the skin_global record disappears from the skin_cache table altogether. It only reappears when the malware code is re-injected at some point.

    Also, for anyone interested in what the code looks like decoded:

    I believe this is a new iteration of the vulnerability and the issue needs to be flagged to IPS for resolution.

    Tuesday, January 27, 2015 at 13:09 | Permalink |
  12. Tim wrote:

    This fix is outdated, tried everything and nothing helped…

    Thursday, January 29, 2015 at 16:37 | Permalink |
  13. Craig wrote: is also an IP Board forum and seems to be infected as well. I’ve been redirected to the website masked by on four different computers.
    LavaSoft makes Ad Aware anti-malware software, which was the only software I could find to get rid of a similar virus on my son’s computer a couple of months ago.

    The problem is not on Google (see Simon’s post on 1/14/2014). The redirect occurs as soon as you reach the infected forum, so it looks like Google took you there.

    Thursday, February 5, 2015 at 17:02 | Permalink |
  14. Alycia wrote:

    We at Sucuri are seeing something similar to this and referenced this article.

    If you experiencing malicious activity with your IP.Board CMS, check out this post:

    Thank you Peter!

    Tuesday, February 10, 2015 at 16:50 | Permalink |
  15. Ben wrote:
    Tuesday, February 10, 2015 at 20:24 | Permalink |

15 Trackbacks/Pingbacks

  1. […] […]

  2. […] WordPress, it took quite a few time to find the source of the redirects. I should admit that this two year old Peter Upfold’s article really helped me (make sure to read it — I like his thought process and approach to […]

Post a Comment

On some sites, you must be logged in to post a comment. This is not the case on this site.
Your email address is not made public or shared. Required fields are marked with *.

Posting a comment signifies you accept the privacy policy.
Please note — your comment will not appear straight away, as all comments are held for approval.