]> source.dussan.org Git - rspamd.git/commitdiff
[Feature] Validate BTC addresses in LEAKED_PASSWORD_SCAM
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 19 Mar 2019 16:15:20 +0000 (16:15 +0000)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 19 Mar 2019 16:15:20 +0000 (16:15 +0000)
conf/composites.conf
rules/regexp/misc.lua

index 3436b44ae13c4dddd32045d1b56820c76842d48b..96ef9d58e786ed090f9b971fbe7bb0e018a754ef 100644 (file)
@@ -135,6 +135,13 @@ composites {
     score = 3.5;
   }
 
+  LEAKED_PASSWORD_SPAM_FP {
+    description = "Looks like a BTC pattern but address syntax is invalid",
+    expression = "LEAKED_PASSWORD_SCAM_INVALID & LEAKED_PASSWORD_SCAM";
+    policy = "remove_all";
+    score = 0.0; # To negate LEAKED_PASSWORD_SCAM
+  }
+
   .include(try=true; priority=1; duplicate=merge) "$LOCAL_CONFDIR/local.d/composites.conf"
   .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/composites.conf"
 }
index 642a02e21068a8f7228bd01674088aeea9aa923a..f7ea49bc2926c48731895bc5ccd424b6b5205372 100644 (file)
@@ -65,12 +65,12 @@ local my_victim = [[/(?:victim|prey)/{words}]]
 local your_webcam = [[/webcam/{words}]]
 local your_onan = [[/(?:mast[ur]{2}bati(?:on|ng)|onanism|solitary)/{words}]]
 local password_in_words = [[/^pass(?:(?:word)|(?:phrase))$/i{words}]]
-local btc_wallet_address = [[/^[13][0-9a-zA-Z]{25,34}$/{words}]]
+local btc_wallet_address = [[/^[13][1-9a-km-zA-HJ-NP-Z]{25,34}$/]]
 local wallet_word = [[/^wallet$/{words}]]
 local broken_unicode = [[has_flag(bad_unicode)]]
 
 reconf['LEAKED_PASSWORD_SCAM'] = {
-  re = string.format('%s & (%s | %s | %s | %s | %s | %s | lua:check_data_images)',
+  re = string.format('%s{words} & (%s | %s | %s | %s | %s | %s | lua:check_data_images)',
       btc_wallet_address, password_in_words, wallet_word,
       my_victim, your_webcam, your_onan, broken_unicode),
   description = 'Contains password word and BTC wallet address',
@@ -94,3 +94,85 @@ reconf['LEAKED_PASSWORD_SCAM'] = {
   score = 7.0,
   group = 'scams'
 }
+
+-- Special routine to validate bitcoin wallets
+-- Prepare base58 alphabet
+local fun = require "fun"
+local off = 0
+local base58_dec = fun.tomap(fun.map(
+    function(c)
+      off = off + 1
+      return c,(off - 1)
+    end,
+    "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"))
+
+local id = rspamd_config:register_symbol{
+  name = 'LEAKED_PASSWORD_SCAM_VALIDATED',
+  callback = function(task)
+    local rspamd_re = require "rspamd_regexp"
+    local hash = require "rspamd_cryptobox_hash"
+    local rspamd_logger = require "rspamd_logger"
+
+    if task:has_symbol('LEAKED_PASSWORD_SCAM') then
+      -- Perform BTC wallet check (quite expensive)
+      local wallet_re = rspamd_re.create_cached(btc_wallet_address)
+      local seen_valid = false
+      for _,tp in ipairs(task:get_text_parts()) do
+
+        local words = tp:get_words('raw') or {}
+
+        for _,word in ipairs(words) do
+          if wallet_re:match(word) then
+            -- We have something that looks like a BTC address
+            local bytes = {}
+            for i=1,25 do bytes[i] = 0 end
+            -- Base58 decode loop
+            fun.each(function(ch)
+              local acc = base58_dec[ch] or 0
+              for i=25,1,-1 do
+                acc = acc + (58 * bytes[i]);
+                bytes[i] = math.fmod(acc, 256);
+                acc = math.modf(acc / 256);
+              end
+            end, fun.tail(word)) -- Tail due to first byte is version
+            -- Now create a validation tag
+            local sha256 = hash.create_specific('sha256')
+            for i=1,21 do
+              sha256:update(string.char(bytes[i]))
+            end
+            sha256 = hash.create_specific('sha256', sha256:bin()):bin()
+
+            -- Compare tags
+            local valid = true
+            for i=1,4 do
+              if string.sub(sha256, i, i) ~= string.char(bytes[21 + i]) then
+                valid = false
+              end
+            end
+
+            if valid then
+              task:insert_result('LEAKED_PASSWORD_SCAM_VALIDATED', 1.0, word)
+              seen_valid = true
+            end
+          end
+        end
+      end
+
+      if not seen_valid then
+        task:insert_result('LEAKED_PASSWORD_SCAM_INVALID', 1.0)
+      end
+    end
+  end,
+  score = 0.0,
+  group = 'scams'
+}
+
+rspamd_config:register_symbol{
+  type = 'virtual',
+  name = 'LEAKED_PASSWORD_SCAM_INVALID',
+  parent = id,
+  score = 0.0,
+}
+
+rspamd_config:register_dependency('LEAKED_PASSWORD_SCAM_VALIDATED',
+    'LEAKED_PASSWORD_SCAM')
\ No newline at end of file