2006/06/14 COREBlog2スパム対策(まとめ)

Category: 'Plone'

最近仕事の影響で がかなり疎かになってしまっています。そんな中、 takanory.netさんからtrackbackをもらった のでこれに反応して、自分が行っているCOREBlog2スパム対策をまとめてみます。これを読めば一通り設定できるはず>for 未来の自分

# 参考: spamとの戦い(回顧編)

スパム対策共通BuzzWordチェックスクリプト

以前にblogに書いたスクリプト と同一です。スパマーがこのエントリを見ているとは思いませんが、念のためbuzzwordは省略します。とはいえ、大抵のスパム投稿には url=href が入っているので、この2つを設定しておけばほとんど防げると思います。

以下のコードをportal_skins/custom等に置きます。

## Script (Python) "validateBuzzWords"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=text, moderated=True
##title=COREBlog2: buzzword checker
##
buzz_words = (
  'casino',
)

try:
    if not moderated:
        return moderated

    s = str(text).lower()
    for w in buzz_words:
        if s.find(w) >= 0:
            moderated = False
            break
    else:
        moderated = True

except:
    pass

return moderated

コメントスパム対策

まずはcbaddCommentの直接呼び出しを拒否するようにカスタマイズします。これが必要になるシーンは滅多にないとは思いますが、自分は喰らってしまったので対策しています。22行目~27行目が追加されています。

## Controller Python Script "cbaddComment"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind state=state
##bind subpath=traverse_subpath
##parameters=
##title=COREBlog2: modify: refuce direct access
##
from Products.CMFPlone import transaction_note
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.utils import log

cbtool = getToolByName(context, 'coreblog2_tool')

REQUEST = context.REQUEST
form = REQUEST.form
RESPONSE = context.REQUEST.RESPONSE
entry = context

# refuse direct access.
if script is REQUEST.get('PUBLISHED',None):
    RESPONSE.setStatus(403)
    return '403 Forbidden'
    #return RESPONSE.redirect('403 Forbidden', 403)

if REQUEST.form.has_key('remember_cookie'):
    #Set cookie
    for key in ['author','email','url']:
        if REQUEST.form.has_key(key):
            REQUEST.RESPONSE.setCookie(key,REQUEST.form[key],
                        path='/'.join(context.blog_object().getPhysicalPath()),
                        expires='Sun, 01-Dec-2099 12:00:00 GMT')

#Try to add comment
entry.addComment2Entry(author=form['author'],email=form['email'],
                        url=form['url'],title=form['title'],
                        body=form['body'],REQUEST=REQUEST)

#Send notify mail if need
if context.getSend_comment_notification():
    try:
        to_addr   = context.getNotify_to()
        from_addr = context.getNotify_to()
        msgbody = context.translate('comment_notify_body')
        elements = {}
        for k in ('title','author','url','body'):
            if REQUEST.form.has_key(k):
                elements[k] = REQUEST.form[k]
            else:
                elements[k] = ''
        elements['post_ip'] = REQUEST.getClientAddr()
        elements['entry_url'] = context.absolute_url()
        msgbody = msgbody % (elements)
        msgsubject = context.translate('comment_notify_title')
        mgsheader = """To: %s
From: %s
Mime-Version: 1.0
Content-Type: text/plain; Charset=utf-8

""" % (to_addr,from_addr)
        cbtool.send_mail(mgsheader+msgbody, to_addr, from_addr, msgsubject)

    except Exception,e:
        log( 'COREBlog2/cbaddComment: '
                 'Some exception occured, %s' % e )

#Set next action
state.setNextAction('redirect_to:string:')

#Display message for user
state.setKwargs({'portal_status_message':'A comment successfully added.'})
return state



return state

validateCommentにBuzzWordをチェックするコードを追加しています。以下のカスタマイズでは、smapの傾向や元IP収集のためにBuzzWordに引っかかった場合に、投稿内容を管理者にメール送信し、投稿フォームにはエラーを表示して投稿自体はされないようにしています。

## Controller Validator "validateComment"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind state=state
##bind subpath=traverse_subpath
##parameters=
##title=COREBlog2: modify: add buzzword check
##
from Products.CMFPlone import transaction_note
REQUEST=context.REQUEST
moderated = True

reqs = ['title','body']

#See setting and append required field list
if context.getComment_require_author():
    reqs.append('author')

if context.getComment_require_email():
    reqs.append('email')

if context.getComment_require_url():
    reqs.append('url')

for key in reqs:
    if REQUEST.has_key(key) and not REQUEST[key]:
        state.setError(key, 'Please enter a value', new_status='failure')

for key in ['title', 'body', 'author', 'email', 'url']:
    if REQUEST.has_key(key):
        m = context.validateBuzzWords(REQUEST[key], True)
        if not m:
            state.setError(key, 'Please remove NG words.', new_status='failure')
            moderated = False

#Try to send mail for Bad comment
if not moderated:
    context.addCommentMail(
                        author=REQUEST['author'],email=REQUEST['email'],
                        url=REQUEST['url'],title=REQUEST['title'],
                        body=REQUEST['body'], moderated=moderated,
                        remoteip=REQUEST.getClientAddr())

if state.getErrors():
    state.set(portal_status_message='Please correct the errors shown.')

return state

BuzzWordコメント時のメール送信用スクリプトです。これはCOREBlog2がメール送信によるコメント通知をサポートする前に作ったものですが、アクセス元IPを通知してくれるあたりがスパム対策っぽい感じです。

## Script (Python) "addCommentMail"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=author,email,url,title,body,moderated,remoteip='',message=''
##title=
##
try:
    mailhost=getattr(context, \
                     context.superValues(['Secure Mail Host', 'Mail Host'])[0].id)
except:
    raise AttributeError, "Mail Host object cant be found."


mMsg = """To: %s
From: %s
Mime-Version: 1.0
Content-Type: text/plain;

Moderate : %s
ManageURL: http://www.freia.jp/taka/blog/%s/entry_comments
ViewURL  : http://www.freia.jp/taka/blog/%s
RemoteIP : %s
Author   : %s
Title    : %s
URL      : %s
EMail    : %s
EntryID  : %s
Body     :
%s

Additional message:
%s
"""

try:
    to_addr   = "admin@example.jp"
    from_addr = "admin@example.jp"
    parent_id = context.getId()

    mTo   = to_addr
    mFrom = from_addr
    mSubj = 'blog: A comment %s' % (moderated and 'added!' or 'NEED MODERATE.')
    mMsg  = mMsg % (to_addr, from_addr, str(moderated), parent_id, parent_id, \
                    remoteip, author, title, url, email, parent_id, body, message )

    mailhost.send(mMsg, mTo, mFrom, mSubj)

except:
    raise

トラックバックスパム対策

tbpingをカスタマイズして、validateBuzzWordsとスパム時のメール送信を呼び出すようにしています。

## Script (Python) "tbping"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=
##title=Receive trackback: COREBlog2: modify: check buzzwords
##
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.utils import log

cbtool = getToolByName(context, 'coreblog2_tool')

REQUEST = context.REQUEST
form = REQUEST.form
RESPONSE = context.REQUEST.RESPONSE
entry = context

excerpt = ''
if form.has_key('excerpt'):
    excerpt = form['excerpt']

title = cbtool.convert_charcode(form['title'])
blog_name = cbtool.convert_charcode(form['blog_name'])
excerpt = cbtool.convert_charcode(excerpt)

#Try to add trackback
try:
    # !!!STAART modify by shimizukawa!!!
    moderated = True
    for text in [title, blog_name, excerpt]:
        m = context.validateBuzzWords(text, True)
        if not m:
            state.setError(key, 'Please remove NG words.', new_status='failure')
            moderated = False

    #Try to send mail for Bad comment
    if not moderated:
        context.addTrackbackMail(
                            title=title, url='',
                            blog_name=blog_name,
                            excerpt=excerpt,
                            moderated=moderated,
                            remoteip=REQUEST.getClientAddr(),
                            message='NEED MODERATE',)
        raise 'NEED MODERATE'
    # !!!END modify by shimizukawa!!!

    #Send notify mail if need
    if context.getSend_trackback_notification():
        try:
            to_addr   = context.getNotify_to()
            from_addr = context.getNotify_to()
            msgbody = context.translate('trackback_notify_body')
            elements = {}
            for k in ('blog_name','title','excerpt','url','excerpt'):
                if form.has_key(k):
                    elements[k] = REQUEST.form[k]
                else:
                    elements[k] = ''
            elements['post_ip'] = REQUEST.getClientAddr()
            elements['entry_url'] = context.absolute_url()
            msgbody = msgbody % (elements)
            msgsubject = context.translate('trackback_notify_title')
            mgsheader = """To: %s
From: %s
Mime-Version: 1.0
Content-Type: text/plain; Charset=utf-8

""" % (to_addr,from_addr)
            cbtool.send_mail(mgsheader+msgbody, to_addr, from_addr, msgsubject)
        except Exception,e:
            log( 'COREBlog2/tbping: '
                     'Some exception occured, %s' % e )

    entry.addTrackback2Entry(title=title,url=form['url'],\
                            blog_name=blog_name,excerpt=excerpt)

    return context.tbping_result(client=context,REQUEST=REQUEST,\
                                        error_code=0,message='Thanks :-)')
except:
    return context.tbping_result(client=context,REQUEST=REQUEST,\
                                    error_code=1,message='Error occured!')

addCommentMailとほぼ同一のスクリプト。トラックバック用。芸のないコピペコード。

## Script (Python) "addTrackbackMail"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=title,url,blog_name,excerpt,moderated,remoteip='',message=''
##title=
##
try:
    mailhost=getattr(context, \
                     context.superValues(['Secure Mail Host', 'Mail Host'])[0].id)
except:
    raise AttributeError, "Mail Host object cant be found."

mMsg = """To: %s
From: %s
Mime-Version: 1.0
Content-Type: text/plain;

Moderate : %s
ManageURL: http://www.freia.jp/taka/blog/%s/entry_trackbacks
ViewURL  : http://www.freia.jp/taka/blog/%s
RemoteIP : %s
Title    : %s
URL      : %s
BlogName : %s
EntryID  : %s
Excerpt  :
%s

Additional message:
%s
"""

try:
    to_addr   = "admin@example.jp"
    from_addr = "admin@example.jp"
    parent_id = context.getId()

    mTo   = to_addr
    mFrom = from_addr
    mSubj = 'blog: A trackback %s' % (moderated and 'added!' or 'NEED MODERATE.')
    mMsg  = mMsg % (to_addr, from_addr, str(moderated), parent_id, parent_id, \
                    remoteip, title, url, blog_name, parent_id, excerpt, message )

    mailhost.send(mMsg, mTo, mFrom, mSubj)

except:
    raise

ApacheのIPアドレス制限

ログの出力を標準のアクセスと別にしたり、アクセス時にZopeにアクセスに行かないように設定したりしてます。httpd.confの書き方を全然調査してないので冗長な感じです。あと本当はエラーページじゃなくて403を返すように設定したい。

SetEnvIf Remote_addr "(24\.244\.170\.180|81\.177\.8\.26)" spam1
CustomLog /var/log/httpd/www.freia.jp-access.log combined env=!spam1
CustomLog /var/log/httpd/www.freia.jp-access-spam1.log combined env=spam1
ErrorLog /var/log/httpd/www.freia.jp-error.log

RewriteEngine On

# for spam filtering.
RewriteCond %{REMOTE_HOST}  ^(24\.244\.170\.180|81\.177\.8\.26)
RewriteRule ^/(.*) http://localhost:80/underconstruction/ [P,L]

# rewrite standard zope server.
RewriteRule ^/(.*) http://localhost:8080/VirtualHostBase/http/www.freia.jp:80/VirtualHostRoot/$1 [P,L]

上記のhttpd.conf、見やすくするためにIPアドレス制限を2つだけ書いていますが、本当は以下のIPを制限しています。

24.244.170.180 65.214.44.212 66.246.218.107 69.50.167.122 81.177.7.108 81.177.7.154 81.177.7.37 81.177.7.81 81.177.8.26 85.255.117.18 194.117.134.72 195.39.170.102 200.79.91.5 202.56.253.184 209.190.4.10 209.190.4.106 209.67.219.178