会社メールなのですが、ここのとこ、Spamメールが頻繁に送られてくるようになりました。
ほとんどが、外国エロサイトへの誘導なので、簡単に対処出来るのですが、まれに凄いのが来ます。
最近、コロナ過で、Emotet攻撃が増殖しているのが原因かな。
なんとかしないと、ひっかかる人が出て来ます。なんでクリックしちゃうのなんて責めても無理な人がいますからね。
対策として、procmailからフィルタを呼び出して判定を行い、結果によりマークする方法を取りました。
procmailなので、そのまま捨ててしまったり、ごみ箱へ移動するなり応用が効くので便利ですな。
で、肝心のフィルタですが、githubに公開しているので、参考にして下さい。
https://github.com/T-Nosaka/SpamFireNetPipe
procmailを使用するので、大概のUnixシステムで使えますが、フィルタは、dotnet coreで作成しました。
dotnet coreなので、Windowsだけでなく、Ubuntu等のLinuxでも動作するので、汎用的なのではないでしょうか。
スパムメールは、試行錯誤して送信して来るので、後に柔軟に対応出来るように自分が得意なdotnet c#を選択したというのが本音ですな。
フィルタは、TCP 8888番ポートを受け付けるサーバとして動作します。
ここら辺は、適当に作ったので、都合に合うように改造して下さい。
procmailの詳細は、ぐぐっていただければ色々と情報が出てくるので、調べてみて下さいね <- 手抜き
とりあえず、サンプルを記載します。
SUBJECT=`formail -c -xSubject:`
:0 cw
*
{
:0 f
|nc [サーバーのIPアドレス] 8888
:0 f
* (1)
{ EXITCODE=1 }
LOGFILE=/dev/null
HOST
}
:0 ef
|formail -i "Subject: [SpamFire] $SUBJECT" |formail -A "X-Spam-Check: SpamFire"
一番簡単なのは、各ユーザーのホームディレクトリに上記を.procmailrcとして保存すればフィルタを通してくれるようになります。
このサンプルは、Spam判定されるとタイトルに[SpamFire]を追記して、ヘッダにX-Spam-Check: SpamFireを追加します。
全体に適用するには、システムのホームディレクトリ直下にprocmailrcとして保存すれば良いです。
フィルタの動作仕様
1番目
ヘッダのReceived をDNSBL により、判定を行います。
2番目
ボディがHTMLの場合、リンク先を抽出し、DNSBL により、判定を行います。
3番目
ボディがHTMLの場合、リンク先を抽出し、固定の文字列で部分一致し判定します。
4番目
ボディがHTMLの場合、リンク先を抽出し、派生クラス側で判定します。
これだけで十分対応出来るのですが、まれによく出来たフィッシングサイトに誘導するスパムメールが来ます。
普通の人は、ひっかかりそうな精巧な作りで、所々、本家のリンク先が紛れているぐらいよく出来ています。
ほとんどが、ブラウザで止められて警告が出ますが、フィッシングサイトとして登録されていないゼロディ状態の場合、ログインパスワード、住所、氏名、クレジット情報の入力を催促されます。
ITに精通している人だと、ドメインを確認すればすぐにわかるのですが、普通の人だと、全部あるいは、一部でも入力してしまう可能性があるでしょう。
こういったサイトを見つけた時は、Fortiguardという所に報告すれば、数時間でフィッシングサイトとして登録されるので、どんどん報告してしまいましょう。
で、そんな Fortiguardですが、リンク先を確認出来るページがあります。
https://fortiguard.com/webfilter
ここの Search URLに対象リンク先を入力して、虫眼鏡をクリックするとフィッシングサイトか判定出来るになっています。
折角なので、このFortiguardを使ってフィルタを拡張してしまいましょう。
SpamFilterEx.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using Newtonsoft.Json;
using HtmlAgilityPack;
using Newtonsoft.Json.Linq;
namespace SpamFire
{
public class SpamFilterEx : SpamFilter
{
public SpamFilterEx()
{
}
public override void CacheClear()
{
base.CacheClear();
lock (m_fortiguardwhitelist)
{
m_fortiguardwhitelist.Clear();
}
}
public override void CacheOver(long overvalue)
{
base.CacheOver(overvalue);
var nowticks = DateTime.Now.Ticks;
lock (m_fortiguardwhitelist)
{
var deletelist = (from rec in m_fortiguardwhitelist
where rec.Value < (nowticks - overvalue)
select rec.Key).ToList();
deletelist.ForEach(rec => m_fortiguardwhitelist.Remove(rec));
}
}
protected override bool AnalyzaHTML(string html, OnHrefAnalyzaHTMLDelegate hrefcallback = null)
{
return base.AnalyzaHTML(html, (targethref) =>
{
//FortiGuard Web Filter
if (ChkFortiguard(targethref) == true)
return true;
return false;
});
}
protected Dictionary<string, long> m_fortiguardwhitelist = new Dictionary<string, long>();
protected bool ChkFortiguard(string url)
{
lock (m_blackresultlist)
{
if (m_blackresultlist.ContainsKey(url) == true)
return true;
}
lock (m_fortiguardwhitelist)
{
if (m_fortiguardwhitelist.ContainsKey(url) == true)
return false;
}
var result = ChkFortiguardInner(url);
if (result == true)
{
lock (m_blackresultlist)
{
m_blackresultlist[url] = DateTime.Now.Ticks;
return true;
}
}
lock (m_fortiguardwhitelist)
{
m_fortiguardwhitelist[url] = DateTime.Now.Ticks;
}
return false;
}
protected bool ChkFortiguardInner(string url)
{
try
{
var parameters = new Dictionary<string, string>() {
{ "q", url }
};
var paramstr = string.Join("&", parameters.Select(pair =>
{
return string.Format("{0}={1}",
Uri.EscapeDataString(pair.Key),
Uri.EscapeDataString(pair.Value.ToString()));
}).ToArray());
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
Encoding enc = Encoding.UTF8;
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("https://fortiguard.com/webfilter?" + paramstr);
req.Method = "GET";
req.UserAgent = @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106";
WebResponse res = req.GetResponse();
var stream = new MemoryStream();
res.GetResponseStream().CopyTo(stream);
stream.Seek(0, SeekOrigin.Begin);
var result = Encoding.UTF8.GetString(stream.ToArray());
var doc = new HtmlDocument();
doc.LoadHtml(result);
var categoryval = doc.DocumentNode.SelectNodes(@"//*[@class=""ency_content""]/*[@class=""well""]/div/div/h4")[0].InnerText.Trim();
var category = categoryval.Split(':')[1].Trim();
Console.WriteLine($"FortiGuard:{category}:{url}");
switch (category)
{
case "Phishing":
{
return true;
}
}
}
catch { }
return false;
}
}
}
Program.cs
旧
var spamfilter = new SpamFilter();
新
var spamfilter = new SpamFilterEx();
これで、安心して眠れますな