Website downtime

My website went down for around 1:30 to 2 hours today, which was a little odd. After it came back up I noticed the 15min load was > 30. Turns out someone tried to log into WordPress which isn’t that odd. It happens all the time, so much so that I run Limit Login Attempts plugin to apply a 20min timeout a IP address after 4 incorrect attempts (and then a 24 hour timeout after 16 attempts). This time though, it was a kind of a large attack.

In 118 minutes, 2337 different IP addresses tried to log into WordPress. Most were timed-out after 4 attempts, but due to oom-killer sometimes the timeout details weren’t added to the database and more than 4 attempts were made (Two ips made > 26 attempts each, though only 8 IPs made 7 or more attempts). Using a Geo IP database we can quickly get a breakdown of where the IPs came from:

<th>
  Attempts
</th>
<td>
  1786
</td>
<td>
  1225
</td>
<td>
  909
</td>
<td>
  817
</td>
<td>
  613
</td>
<td>
  380
</td>
<td>
  379
</td>
<td>
  342
</td>
<td>
  278
</td>
<td>
  276
</td>
<td>
  241
</td>
<td>
  164
</td>
<td>
  159
</td>
<td>
  146
</td>
<td>
  137
</td>
<td>
  122
</td>
<td>
  121
</td>
<td>
  115
</td>
<td>
  115
</td>
<td>
  1762
</td>
Country
Russia
Ukraine
Vietnam
Thailand
Taiwan
Romania
Turkey
Bulgaria
Iran
Belarus
India
Poland
Serbia
Egypt
Hungary
Brazil
United States of America
Canada
South Africa
Other (73 other unique countries)

The user agent for every attempt was “Mozilla/5.0 (Windows NT 6.1; rv:19.0) Gecko/20100101 Firefox/19.0″ which is a valid user agent for FF19 running Windows 7. I’m guessing it was someone’s small botnet. Though the distribution of the countries seems a bit off? Maybe it’s a compromised FF addon? If it continues I may need to switch to a whitelist for logins, or move the admin login page elsewhere as current login limit plugin isn’t really suited to an attack from such a large number of IP addresses.

Code

__author__ = 'Matthew'

import pygeoip

ACCESS_FILENAME = 'access.log'

GEOIP = pygeoip.Database('GeoIP.dat')

ip_breakdown = {}
country_breakdown = {}

for line in open(ACCESS_FILENAME):
    if 'wp-login' not in line:
        continue

    ip = line[0:line.find(' ')]

    if not ip_breakdown.has_key(ip):
        ip_breakdown[ip] = 0

    ip_breakdown[ip] += 1

    ########

    info = GEOIP.lookup(ip)
    if not info.country:
        country = 'unknown'
    else:
        country = info.country

    if not country_breakdown.has_key(country):
        country_breakdown[country] = 0

    country_breakdown[country] += 1


print 'Number ips:', len(ip_breakdown.keys())
print 'Number countries:', len(country_breakdown.keys())

for country in sorted(country_breakdown, key=country_breakdown.get, reverse=True):
    print country, country_breakdown[country]

Output

Number ips: 2337
Number countries: 92
RU 1786
UA 1225
VN 909
TH 817
TW 613
RO 380
TR 379
BG 342
IR 278
BY 276
IN 241
PL 164
RS 159
EG 146
HU 137
BR 122
US 121
CN 115
SA 115
PK 93
HK 91
CZ 90
ID 82
KZ 78
GE 74
GB 73
SK 63
MD 59
DE 57
AE 56
GR 53
AZ 50
IQ 45
ES 44
BD 43
IL 43
CA 39
AR 36
NL 34
CO 34
CL 32
LV 32
MY 30
AT 29
IT 28
KG 22
BE 21
DK 19
JP 18
QA 17
ZA 15
LK 15
AU 15
LT 14
MX 13
MO 12
KH 12
PH 11
NO 11
PS 10
SE 10
MA 9
MN 9
CY 9
AM 9
BA 8
PY 8
FR 8
GH 7
EE 7
YE 7
DZ 7
PT 6
A2 6
OM 4
HR 4
EC 4
NZ 4
LB 4
LA 3
JO 2
PE 2
MK 2
MZ 2
BH 1
AO 1
ET 1
UZ 1
NP 1
CD 1
SY 1
SD 1

Posted on

MetaBright PHP Challenge

MetaBright is a quiz/community-challenges website focusing on programming and development. I recently found out about a new general PHP section and gave it a go. Overall it was a fun diversion, though a handful of questions were a little odd (like the hash size for RIPEMD vs Whirlpool vs GOST vs MD5). I managed to get to second place on the leader-board, and with a itsy-little bug managed to take first place (of course this is a new section so there isn’t much competition).

Posted on

Bliss n Eso – Circus in the Sky (Official Trailer)

The trailer for the new Bliss n Eso album Circus in the Sky, based/inspired by Kid’s Story from the Animatrix (the Animatrix DVD is visible falling from the locker).

Posted on

Diablo3 Farming + Python

I haven’t played that much Diablo 3 recently, but since the last patch I’ve done a little bit of Demonic Essence farming in Warrior’s Rest. To get an idea of how efficient it is, I wrote a really hack’ish Python script to track some stats and used Tim Golden’s example code for catching global key presses in Windows. The script allowed me to press Shift+F1 or Shift+F2 while inside Diablo 3 to record a time stamp and whether or not I got an essence in the previous run. Note that the code is pretty horrible, but it was a 20 minute project and I only needed something quick and dirty.

Code

import os
import sys
import ctypes
from ctypes import wintypes
import win32con
import time
import datetime
import winsound

byref = ctypes.byref
user32 = ctypes.windll.user32

HOTKEYS = {
    1: (win32con.VK_F3, win32con.MOD_WIN),
    2: (win32con.VK_F4, win32con.MOD_WIN),
    3: (win32con.VK_F1, win32con.MOD_SHIFT),
    4: (win32con.VK_F2, win32con.MOD_SHIFT)
}


def handle_win_f3():
    os.startfile(os.environ['TEMP'])


def handle_win_f4():
    user32.PostQuitMessage(0)

################

LAST_TIMESTAMP = False
RUNS = 0
ESSENCES = 0


def handle_shift_f1():
    record_run(True)


def handle_shift_f2():
    record_run(False)


def record_run(got_essence):
    global LAST_TIMESTAMP
    global RUNS
    global ESSENCES

    RUNS += 1
    if got_essence:
        ESSENCES += 1

    timestamp = int(time.time())
    date_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")

    drop_rate = 0
    if ESSENCES > 0:
        drop_rate = (ESSENCES / float(RUNS)) * 100

    run_length = 0
    if LAST_TIMESTAMP is not False:
        run_length = timestamp - LAST_TIMESTAMP

    if got_essence:
        print 'DROP - %d - %s - %d/%d - %0.2f%% - %d' % (timestamp, date_str, ESSENCES, RUNS, drop_rate, run_length)
    else:
        print 'NULL - %d - %s - %d/%d - %0.2f%% - %d' % (timestamp, date_str, ESSENCES, RUNS, drop_rate, run_length)

    winsound.PlaySound('CallWaiting.wav', winsound.SND_FILENAME)

    LAST_TIMESTAMP = timestamp


################


HOTKEY_ACTIONS = {
    1: handle_win_f3,
    2: handle_win_f4,
    3: handle_shift_f1,
    4: handle_shift_f2
}


# RegisterHotKey takes:
#  Window handle for WM_HOTKEY messages (None = this thread)
#  arbitrary id unique within the thread
#  modifiers (MOD_SHIFT, MOD_ALT, MOD_CONTROL, MOD_WIN)
#  VK code (either ord ('x') or one of win32con.VK_*)
for id, (vk, modifiers) in HOTKEYS.items ():
    print "Registering id", id, "for key", vk
    if not user32.RegisterHotKey (None, id, modifiers, vk):
        print "Unable to register id", id


# Home-grown Windows message loop: does
#  just enough to handle the WM_HOTKEY
#  messages and pass everything else along.
try:
    msg = wintypes.MSG()
    while user32.GetMessageA(byref(msg), None, 0, 0) != 0:
        if msg.message == win32con.WM_HOTKEY:
            action_to_take = HOTKEY_ACTIONS.get(msg.wParam)
            if action_to_take:
                action_to_take()

        user32.TranslateMessage(byref(msg))
        user32.DispatchMessageA(byref(msg))

finally:
    for id in HOTKEYS.keys():
        user32.UnregisterHotKey(None, id)

Runs

C:\Python27\python.exe D:/Dropbox/Code/py-farm-companion/companion.py
Registering id 1 for key 114
Registering id 2 for key 115
Registering id 3 for key 112
Registering id 4 for key 113
DROP - 1363409462 - 2013-03-16 15:51 - 1/1 - 100.00% - 0
NULL - 1363409526 - 2013-03-16 15:52 - 1/2 - 50.00% - 64
DROP - 1363409594 - 2013-03-16 15:53 - 2/3 - 66.67% - 68
NULL - 1363409660 - 2013-03-16 15:54 - 2/4 - 50.00% - 66
NULL - 1363409742 - 2013-03-16 15:55 - 2/5 - 40.00% - 82
NULL - 1363409808 - 2013-03-16 15:56 - 2/6 - 33.33% - 66
DROP - 1363409870 - 2013-03-16 15:57 - 3/7 - 42.86% - 62
NULL - 1363409923 - 2013-03-16 15:58 - 3/8 - 37.50% - 53
NULL - 1363409980 - 2013-03-16 15:59 - 3/9 - 33.33% - 57
NULL - 1363410035 - 2013-03-16 16:00 - 3/10 - 30.00% - 55
DROP - 1363410098 - 2013-03-16 16:01 - 4/11 - 36.36% - 63
DROP - 1363410158 - 2013-03-16 16:02 - 5/12 - 41.67% - 60
DROP - 1363410216 - 2013-03-16 16:03 - 6/13 - 46.15% - 58
NULL - 1363410277 - 2013-03-16 16:04 - 6/14 - 42.86% - 61
DROP - 1363410340 - 2013-03-16 16:05 - 7/15 - 46.67% - 63
NULL - 1363410456 - 2013-03-16 16:07 - 7/16 - 43.75% - 116
NULL - 1363410515 - 2013-03-16 16:08 - 7/17 - 41.18% - 59
NULL - 1363410578 - 2013-03-16 16:09 - 7/18 - 38.89% - 63
DROP - 1363410656 - 2013-03-16 16:10 - 8/19 - 42.11% - 78
DROP - 1363410768 - 2013-03-16 16:12 - 9/20 - 45.00% - 112
DROP - 1363410837 - 2013-03-16 16:13 - 10/21 - 47.62% - 69
DROP - 1363410899 - 2013-03-16 16:14 - 11/22 - 50.00% - 62
NULL - 1363410953 - 2013-03-16 16:15 - 11/23 - 47.83% - 54
NULL - 1363411016 - 2013-03-16 16:16 - 11/24 - 45.83% - 63
DROP - 1363411076 - 2013-03-16 16:17 - 12/25 - 48.00% - 60
NULL - 1363411168 - 2013-03-16 16:19 - 12/26 - 46.15% - 92
NULL - 1363411244 - 2013-03-16 16:20 - 12/27 - 44.44% - 76
NULL - 1363411306 - 2013-03-16 16:21 - 12/28 - 42.86% - 62
DROP - 1363411375 - 2013-03-16 16:22 - 13/29 - 44.83% - 69
NULL - 1363411454 - 2013-03-16 16:24 - 13/30 - 43.33% - 79
NULL - 1363411523 - 2013-03-16 16:25 - 13/31 - 41.94% - 69
NULL - 1363411582 - 2013-03-16 16:26 - 13/32 - 40.62% - 59
DROP - 1363411645 - 2013-03-16 16:27 - 14/33 - 42.42% - 63
NULL - 1363411699 - 2013-03-16 16:28 - 14/34 - 41.18% - 54
NULL - 1363411764 - 2013-03-16 16:29 - 14/35 - 40.00% - 65
NULL - 1363411821 - 2013-03-16 16:30 - 14/36 - 38.89% - 57
DROP - 1363411879 - 2013-03-16 16:31 - 15/37 - 40.54% - 58
DROP - 1363411942 - 2013-03-16 16:32 - 16/38 - 42.11% - 63
DROP - 1363412001 - 2013-03-16 16:33 - 17/39 - 43.59% - 59
NULL - 1363412067 - 2013-03-16 16:34 - 17/40 - 42.50% - 66
DROP - 1363412145 - 2013-03-16 16:35 - 18/41 - 43.90% - 78
DROP - 1363412223 - 2013-03-16 16:37 - 19/42 - 45.24% - 78
NULL - 1363412293 - 2013-03-16 16:38 - 19/43 - 44.19% - 70
NULL - 1363412360 - 2013-03-16 16:39 - 19/44 - 43.18% - 67
DROP - 1363412422 - 2013-03-16 16:40 - 20/45 - 44.44% - 62
NULL - 1363412485 - 2013-03-16 16:41 - 20/46 - 43.48% - 63
DROP - 1363412548 - 2013-03-16 16:42 - 21/47 - 44.68% - 63
DROP - 1363412611 - 2013-03-16 16:43 - 22/48 - 45.83% - 63
NULL - 1363412681 - 2013-03-16 16:44 - 22/49 - 44.90% - 70
DROP - 1363412752 - 2013-03-16 16:45 - 23/50 - 46.00% - 71
NULL - 1363412815 - 2013-03-16 16:46 - 23/51 - 45.10% - 63
NULL - 1363412880 - 2013-03-16 16:48 - 23/52 - 44.23% - 65
DROP - 1363412953 - 2013-03-16 16:49 - 24/53 - 45.28% - 73
DROP - 1363413020 - 2013-03-16 16:50 - 25/54 - 46.30% - 67
DROP - 1363413090 - 2013-03-16 16:51 - 26/55 - 47.27% - 70
NULL - 1363413156 - 2013-03-16 16:52 - 26/56 - 46.43% - 66
NULL - 1363413209 - 2013-03-16 16:53 - 26/57 - 45.61% - 53
DROP - 1363413274 - 2013-03-16 16:54 - 27/58 - 46.55% - 65
NULL - 1363413349 - 2013-03-16 16:55 - 27/59 - 45.76% - 75
NULL - 1363413417 - 2013-03-16 16:56 - 27/60 - 45.00% - 68
DROP - 1363413479 - 2013-03-16 16:57 - 28/61 - 45.90% - 62
DROP - 1363413548 - 2013-03-16 16:59 - 29/62 - 46.77% - 69
NULL - 1363413608 - 2013-03-16 17:00 - 29/63 - 46.03% - 60
NULL - 1363413669 - 2013-03-16 17:01 - 29/64 - 45.31% - 61
NULL - 1363413735 - 2013-03-16 17:02 - 29/65 - 44.62% - 66
DROP - 1363413801 - 2013-03-16 17:03 - 30/66 - 45.45% - 66

Summary

In the end I ran Warrior’s Rest on MP9 66 times with my

Barbarian. The average run time was 67 seconds (timed from pressing resume to existing back to the lobby screen). I got 30 essences (45%) which is quite a bit higher than the expected drop rate (35%). Overall I got 1 essences every 2:24, which doesn’t really sound that great. I think I need to tweak my build and items (I really want Earthquake, but I don’t want to give up the healing from Rend). Maybe I’ll time some Vault of the Assassin runs next time.

Posted on

Portsea

Edit: 2022-04-10 - New Gallery link

Posted on

Mouth of the Powlett River

From Christmas 2012.

Edit: 2022-04-10 - New Gallery link

Posted on

Christmas 2012

Edit: 2022-04-10 - New Gallery link

I swear I just don’t like people or something.

Posted on

DCUO: Healing Weapon Skills

Just for my own reference:

<th>
  Restoration
</th>

<th>
  Crit Chance
</th>

<th>
  Crit Multiplier
</th>
<td>
  3
</td>

<td>
  1
</td>

<td>
  12
</td>
<td>
  3
</td>

<td>
</td>

<td>
  14
</td>
<td>
  45
</td>

<td>
  1
</td>

<td>
</td>
<td>
  45
</td>

<td>
  1
</td>

<td>
</td>
<td>
  45
</td>

<td>
</td>

<td>
  2
</td>
<td>
  47
</td>

<td>
</td>

<td>
</td>
<td>
</td>

<td>
  4
</td>

<td>
  2
</td>
<td>
  3
</td>

<td>
  3
</td>

<td>
  2
</td>
<td>
</td>

<td>
  3
</td>

<td>
  2
</td>
<td>
</td>

<td>
</td>

<td>
  14
</td>
<td>
  3
</td>

<td>
</td>

<td>
  12
</td>
Type
Hand Blasters
One Handed
Dual Pistol
Shield
Martial Arts
Dual Wield
Two Handed
Brawling
Rifle
Staff
Bow

Posted on

Firewall Fun: pf, tables, and state

I hate firewalls. Hate them. If you are using pf and wish to block a specific ip address using a simple table and a block quick rule it turns out just adding an ip to the table isn’t enough… pf wont actually block a new ip if it already has state information about it, instead it continues to let all traffic through. The solution is to remove all the state rules for the ip too:

pfctl -t blacklist -T add 123.123.123.123
pfctl -k 123.123.123.123

Posted on

LoL + Firewall Problems

Since upgrading my router to a Billion BiPAC 7800N I’ve been having trouble joining a new game in LoL; it sometimes takes up to 3 retries just to get to the loading screen. After connecting all is well but it’s a little annoying having to wait around every game. After doing some searching this seems to be a common problem with this router. Luckily there is a easy fix (even if it makes little sense). In the router configuration under Advanced -> Configuration -> QoS add a new QoS item.

  • Application: LoL
  • Direction: LAN to WAN
  • Protocol: TCP
  • Priority: High
  • Internal Port: 5000 ~ 5500
  • External Port: 5000 ~ 5500 Leave the all the other questions default and click
  • Add

With any luck, LoL will be back to normal.

Posted on