diff options
author | Vsevolod Stakhov <vsevolod@highsecure.ru> | 2020-05-14 10:33:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-05-14 10:33:54 +0100 |
commit | b9a659c5d01a92d124344d62dff70da431c6443c (patch) | |
tree | 7f3b66959670ff95de6afcb32d9301b916039b0a /lualib | |
parent | 1d56b11fbd7a65708cdc39f2f38ceb8959ba2b5a (diff) | |
parent | fc1ee19704c0ba2a01a8e36dbb0acd7b24535e40 (diff) | |
download | rspamd-b9a659c5d01a92d124344d62dff70da431c6443c.tar.gz rspamd-b9a659c5d01a92d124344d62dff70da431c6443c.zip |
Merge pull request #3368 from HeinleinSupport/oletools_rework
lua_scanner - oletools / common rework
Diffstat (limited to 'lualib')
-rw-r--r-- | lualib/lua_scanners/common.lua | 84 | ||||
-rw-r--r-- | lualib/lua_scanners/oletools.lua | 254 |
2 files changed, 183 insertions, 155 deletions
diff --git a/lualib/lua_scanners/common.lua b/lualib/lua_scanners/common.lua index 60e5c2cdf..a162828fb 100644 --- a/lualib/lua_scanners/common.lua +++ b/lualib/lua_scanners/common.lua @@ -325,11 +325,15 @@ local function create_regex_table(patterns) return regex_table end -local function match_filter(task, found, patterns) - if type(patterns) ~= 'table' or not found then return false end +local function match_filter(task, rule, found, patterns, pat_type) + if type(patterns) ~= 'table' or not found then + return false + end if not patterns[1] then for _, pat in pairs(patterns) do - if pat:match(found) then + if pat_type == 'ext' and tostring(pat) == tostring(found) then + return true + elseif pat_type == 'regex' and pat:match(found) then return true end end @@ -337,7 +341,9 @@ local function match_filter(task, found, patterns) else for _, p in ipairs(patterns) do for _, pat in ipairs(p) do - if pat:match(found) then + if pat_type == 'ext' and tostring(pat) == tostring(found) then + return true + elseif pat_type == 'regex' and pat:match(found) then return true end end @@ -366,37 +372,28 @@ local function check_parts_match(task, rule) local detected_ext = p:get_detected_ext() local fname = p:get_filename() local ext, ext2 - local extension_check = false - local content_type_check = false - local attachment_check = false - local text_part_min_words_check = true if rule.scan_all_mime_parts == false then -- check file extension and filename regex matching + --lua_util.debugm(rule.name, task, '%s: filename: |%s|%s|', rule.log_prefix, fname) if fname ~= nil then ext,ext2 = gen_extension(fname) - if match_filter(task, ext, rule.mime_parts_filter_ext) - or match_filter(task, ext2, rule.mime_parts_filter_ext) then - lua_util.debugm(rule.name, task, '%s: extension matched: %s', - rule.log_prefix, ext) - extension_check = true - end - if match_filter(task, detected_ext, rule.mime_parts_filter_ext) then - lua_util.debugm(rule.name, task, '%s: detected extension matched: %s', - rule.log_prefix, detected_ext) - extension_check = true - ext = detected_ext - end - if match_filter(task, fname, rule.mime_parts_filter_regex) then - content_type_check = true + --lua_util.debugm(rule.name, task, '%s: extension, fname: |%s|%s|%s|', rule.log_prefix, ext, ext2, fname) + if match_filter(task, rule, ext, rule.mime_parts_filter_ext, 'ext') + or match_filter(task, rule, ext2, rule.mime_parts_filter_ext, 'ext') then + lua_util.debugm(rule.name, task, '%s: extension matched: |%s|%s|', rule.log_prefix, ext, ext2) + return true + elseif match_filter(task, rule, fname, rule.mime_parts_filter_regex, 'regex') then + lua_util.debugm(rule.name, task, '%s: filname regex matched', rule.log_prefix) + return true end end -- check content type string regex matching if mtype ~= nil and msubtype ~= nil then local ct = string.format('%s/%s', mtype, msubtype):lower() - if match_filter(task, ct, rule.mime_parts_filter_regex) then + if match_filter(task, rule, ct, rule.mime_parts_filter_regex, 'regex') then lua_util.debugm(rule.name, task, '%s: regex content-type: %s', rule.log_prefix, ct) - content_type_check = true + return true end end -- check detected content type (libmagic) regex matching @@ -405,7 +402,7 @@ local function check_parts_match(task, rule) if match_filter(task, magic.ct, rule.mime_parts_filter_regex) then lua_util.debugm(rule.name, task, '%s: regex detected libmagic content-type: %s', rule.log_prefix, magic.ct) - content_type_check = true + return true end end -- check filenames in archives @@ -414,41 +411,44 @@ local function check_parts_match(task, rule) local filelist = arch:get_files_full(1000) for _,f in ipairs(filelist) do ext,ext2 = gen_extension(f.name) - if match_filter(task, ext, rule.mime_parts_filter_ext) - or match_filter(task, ext2, rule.mime_parts_filter_ext) then - lua_util.debugm(rule.name, task, '%s: extension matched in archive: %s', rule.log_prefix, ext) - extension_check = true - end - if match_filter(task, f.name, rule.mime_parts_filter_regex) then - content_type_check = true + if match_filter(task, rule, ext, rule.mime_parts_filter_ext, 'ext') + or match_filter(task, rule, ext2, rule.mime_parts_filter_ext, 'ext') then + lua_util.debugm(rule.name, task, '%s: extension matched in archive: |%s|%s|', rule.log_prefix, ext, ext2) + --lua_util.debugm(rule.name, task, '%s: extension matched in archive: %s', rule.log_prefix, ext) + return true + elseif match_filter(task, rule, f.name, rule.mime_parts_filter_regex, 'regex') then + lua_util.debugm(rule.name, task, '%s: filename regex matched in archive', rule.log_prefix) + return true end end end end -- check text_part has more words than text_part_min_words_check - if rule.text_part_min_words and p:is_text() then - text_part_min_words_check = p:get_words_count() >= tonumber(rule.text_part_min_words) + if rule.scan_text_mime and rule.text_part_min_words and p:is_text() and + p:get_words_count() >= tonumber(rule.text_part_min_words) then + return true + end + + if rule.scan_image_mime and p:is_image() then + return true end + if rule.scan_all_mime_parts ~= false then if detected_ext then -- We know what to scan! local magic = lua_magic_types[detected_ext] or {} if p:is_attachment() or magic.av_check ~= false then - extension_check = true + return true end - else + elseif p:is_attachment() then -- Just rely on attachment property - extension_check = p:is_attachment() + return true end end - return (rule.scan_image_mime and p:is_image()) - or (rule.scan_text_mime and text_part_min_words_check) - or attachment_check - or extension_check - or content_type_check + return false end return fun.filter(filter_func, task:get_parts()) diff --git a/lualib/lua_scanners/oletools.lua b/lualib/lua_scanners/oletools.lua index 05ef15ad9..5d2bc78fd 100644 --- a/lualib/lua_scanners/oletools.lua +++ b/lualib/lua_scanners/oletools.lua @@ -143,8 +143,8 @@ local function oletools_check(task, content, digest, rule) lua_util.debugm(rule.name, task, '%s: no stop word: add_read - #json: %s / current packet: %s', rule.log_prefix, #json_response, #data) conn:add_read(oletools_callback) - else + else local ucl_parser = ucl.parser() local ok, ucl_err = ucl_parser:parse_string(tostring(json_response)) if not ok then @@ -169,125 +169,153 @@ local function oletools_check(task, content, digest, rule) [9] = 'RETURN_ENCRYPTED', } - if result[1].error ~= nil then - rspamd_logger.errx(task, '%s: ERROR found: %s', rule.log_prefix, - result[1].error) - if result[1].error == 'File too small' then - common.save_cache(task, digest, rule, 'OK') - common.log_clean(task, rule, 'File too small to be scanned for macros') - else - oletools_requery(result[1].error) - end - elseif result[3]['return_code'] == 9 then - rspamd_logger.warnx(task, '%s: File is encrypted.', rule.log_prefix) - common.yield_result(task, rule, 'failed - err: ' .. oletools_rc[result[3]['return_code']], 0.0, 'encrypted') - common.save_cache(task, digest, rule, 'encrypted') - elseif result[3]['return_code'] == 5 then - rspamd_logger.warnx(task, '%s: olefy could not open the file - error: %s', rule.log_prefix, - result[2]['message']) - common.yield_result(task, rule, 'failed - err: ' .. oletools_rc[result[3]['return_code']], 0.0, 'fail') - elseif result[3]['return_code'] > 6 then - rspamd_logger.errx(task, '%s: Error Returned: %s', - rule.log_prefix, oletools_rc[result[3]['return_code']]) - rspamd_logger.errx(task, '%s: Error message: %s', - rule.log_prefix, result[2]['message']) - common.yield_result(task, rule, 'failed - err: ' .. oletools_rc[result[3]['return_code']], 0.0, 'fail') - elseif result[3]['return_code'] > 1 then - rspamd_logger.errx(task, '%s: Error message: %s', - rule.log_prefix, result[2]['message']) - oletools_requery(oletools_rc[result[3]['return_code']]) - elseif type(result[2]['analysis']) == 'table' and #result[2]['analysis'] == 0 - and #result[2]['macros'] == 0 then - rspamd_logger.warnx(task, '%s: maybe unhandled python or oletools error', rule.log_prefix) - common.yield_result(task, rule, 'oletools unhandled error', 0.0, 'fail') - elseif type(result[2]['analysis']) ~= 'table' and #result[2]['macros'] == 0 then - common.save_cache(task, digest, rule, 'OK') - common.log_clean(task, rule, 'No macro found') - elseif #result[2]['macros'] > 0 then - -- M=Macros, A=Auto-executable, S=Suspicious keywords, I=IOCs, - -- H=Hex strings, B=Base64 strings, D=Dridex strings, V=VBA strings - local m_exist = 'M' - local m_autoexec = '-' - local m_suspicious = '-' - local m_iocs = '-' - local m_hex = '-' - local m_base64 = '-' - local m_dridex = '-' - local m_vba = '-' - - lua_util.debugm(rule.name, task, - '%s: filename: %s', rule.log_prefix, result[2]['file']) - lua_util.debugm(rule.name, task, - '%s: type: %s', rule.log_prefix, result[2]['type']) - - for _,m in ipairs(result[2]['macros']) do - lua_util.debugm(rule.name, task, '%s: macros found - code: %s, ole_stream: %s, '.. - 'vba_filename: %s', rule.log_prefix, m.code, m.ole_stream, m.vba_filename) - end + -- M=Macros, A=Auto-executable, S=Suspicious keywords, I=IOCs, + -- H=Hex strings, B=Base64 strings, D=Dridex strings, V=VBA strings + local analysis_cat_table = { + macro_exist = '-', + autoexec = '-', + suspicious = '-', + iocs = '-', + hex = '-', + base64 = '-', + dridex = '-', + vba = '-' + } + local analysis_keyword_table = {} + + for _, v in ipairs(result) do + + if v.error ~= nil and v.type ~= 'error' then + -- olefy, not oletools error + rspamd_logger.errx(task, '%s: ERROR found: %s', rule.log_prefix, + v.error) + if v.error == 'File too small' then + common.save_cache(task, digest, rule, 'OK') + common.log_clean(task, rule, 'File too small to be scanned for macros') + return + else + oletools_requery(v.error) + end + + elseif tostring(v.type) == "MetaInformation" and v.version ~= nil then + -- if MetaInformation section - check and print script and version + + lua_util.debugm(N, task, '%s: version: %s %s', rule.log_prefix, + tostring(v.script_name), tostring(v.version)) + + elseif tostring(v.type) == "MetaInformation" and v.return_code ~= nil then + -- if MetaInformation section - check return_code + + local oletools_rc_code = tonumber(v.return_code) + if oletools_rc_code == 9 then + rspamd_logger.warnx(task, '%s: File is encrypted.', rule.log_prefix) + common.yield_result(task, rule, 'failed - err: ' .. oletools_rc[oletools_rc_code], 0.0, 'encrypted') + common.save_cache(task, digest, rule, 'encrypted') + return + elseif oletools_rc_code == 5 then + rspamd_logger.warnx(task, '%s: olefy could not open the file - error: %s', rule.log_prefix, + result[2]['message']) + common.yield_result(task, rule, 'failed - err: ' .. oletools_rc[oletools_rc_code], 0.0, 'fail') + return + elseif oletools_rc_code > 6 then + rspamd_logger.errx(task, '%s: MetaInfo section error code: %s', + rule.log_prefix, oletools_rc[oletools_rc_code]) + rspamd_logger.errx(task, '%s: MetaInfo section message: %s', + rule.log_prefix, result[2]['message']) + common.yield_result(task, rule, 'failed - err: ' .. oletools_rc[oletools_rc_code], 0.0, 'fail') + return + elseif oletools_rc_code > 1 then + rspamd_logger.errx(task, '%s: Error message: %s', + rule.log_prefix, result[2]['message']) + oletools_requery(oletools_rc[oletools_rc_code]) + end + + elseif tostring(v.type) == "error" then + -- error section found - check message + rspamd_logger.errx(task, '%s: Error section error code: %s', + rule.log_prefix, v.error) + rspamd_logger.errx(task, '%s: Error section message: %s', + rule.log_prefix, v.message) + --common.yield_result(task, rule, 'failed - err: ' .. v.error, 0.0, 'fail') + + elseif type(v.analysis) == 'table' and type(v.macros) == 'table' then + -- analysis + macro found - evaluate response + + if type(v.analysis) == 'table' and #v.analysis == 0 and #v.macros == 0 then + rspamd_logger.warnx(task, '%s: maybe unhandled python or oletools error', rule.log_prefix) + oletools_requery('oletools unhandled error') + + elseif #v.macros > 0 then + + analysis_cat_table.macro_exist = 'M' - local analysis_keyword_table = {} - - for _,a in ipairs(result[2]['analysis']) do - lua_util.debugm(rule.name, task, '%s: threat found - type: %s, keyword: %s, '.. - 'description: %s', rule.log_prefix, a.type, a.keyword, a.description) - if a.type == 'AutoExec' then - m_autoexec = 'A' - table.insert(analysis_keyword_table, a.keyword) - elseif a.type == 'Suspicious' then - if rule.extended == true or - (a.keyword ~= 'Base64 Strings' and a.keyword ~= 'Hex Strings') - then - m_suspicious = 'S' - table.insert(analysis_keyword_table, a.keyword) + lua_util.debugm(rule.name, task, + '%s: filename: %s', rule.log_prefix, result[2]['file']) + lua_util.debugm(rule.name, task, + '%s: type: %s', rule.log_prefix, result[2]['type']) + + for _,m in ipairs(v.macros) do + lua_util.debugm(rule.name, task, '%s: macros found - code: %s, ole_stream: %s, '.. + 'vba_filename: %s', rule.log_prefix, m.code, m.ole_stream, m.vba_filename) + end + + for _,a in ipairs(v.analysis) do + lua_util.debugm(rule.name, task, '%s: threat found - type: %s, keyword: %s, '.. + 'description: %s', rule.log_prefix, a.type, a.keyword, a.description) + if a.type == 'AutoExec' then + analysis_cat_table.autoexec = 'A' + table.insert(analysis_keyword_table, a.keyword) + elseif a.type == 'Suspicious' then + if rule.extended == true or + (a.keyword ~= 'Base64 Strings' and a.keyword ~= 'Hex Strings') + then + analysis_cat_table.suspicious = 'S' + table.insert(analysis_keyword_table, a.keyword) + end + elseif a.type == 'IOC' then + analysis_cat_table.iocs = 'I' + elseif a.type == 'Hex strings' then + analysis_cat_table.hex = 'H' + elseif a.type == 'Base64 strings' then + analysis_cat_table.base64 = 'B' + elseif a.type == 'Dridex strings' then + analysis_cat_table.dridex = 'D' + elseif a.type == 'VBA strings' then + analysis_cat_table.vba = 'V' + end end - elseif a.type == 'IOC' then - m_iocs = 'I' - elseif a.type == 'Hex strings' then - m_hex = 'H' - elseif a.type == 'Base64 strings' then - m_base64 = 'B' - elseif a.type == 'Dridex strings' then - m_dridex = 'D' - elseif a.type == 'VBA strings' then - m_vba = 'V' end end + end - --lua_util.debugm(N, task, '%s: analysis_keyword_table: %s', rule.log_prefix, analysis_keyword_table) - - if rule.extended == false and m_autoexec == 'A' and m_suspicious == 'S' then - -- use single string as virus name - local threat = 'AutoExec + Suspicious (' .. table.concat(analysis_keyword_table, ',') .. ')' - lua_util.debugm(rule.name, task, '%s: threat result: %s', rule.log_prefix, threat) - common.yield_result(task, rule, threat, rule.default_score) - common.save_cache(task, digest, rule, threat, rule.default_score) - - elseif rule.extended == true and #analysis_keyword_table > 0 then - -- report any flags (types) and any most keywords as individual virus name - - local flags = m_exist .. - m_autoexec .. - m_suspicious .. - m_iocs .. - m_hex .. - m_base64 .. - m_dridex .. - m_vba - table.insert(analysis_keyword_table, 1, flags) - - lua_util.debugm(rule.name, task, '%s: extended threat result: %s', - rule.log_prefix, table.concat(analysis_keyword_table, ',')) - - common.yield_result(task, rule, analysis_keyword_table, rule.default_score) - common.save_cache(task, digest, rule, analysis_keyword_table, rule.default_score) - else - common.save_cache(task, digest, rule, 'OK') - common.log_clean(task, rule, 'Scanned Macro is OK') - end + lua_util.debugm(N, task, '%s: analysis_keyword_table: %s', rule.log_prefix, analysis_keyword_table) + lua_util.debugm(N, task, '%s: analysis_cat_table: %s', rule.log_prefix, analysis_cat_table) + + if rule.extended == false and analysis_cat_table.autoexec == 'A' and analysis_cat_table.suspicious == 'S' then + -- use single string as virus name + local threat = 'AutoExec + Suspicious (' .. table.concat(analysis_keyword_table, ',') .. ')' + lua_util.debugm(rule.name, task, '%s: threat result: %s', rule.log_prefix, threat) + common.yield_result(task, rule, threat, rule.default_score) + common.save_cache(task, digest, rule, threat, rule.default_score) + + elseif rule.extended == true and #analysis_keyword_table > 0 then + -- report any flags (types) and any most keywords as individual virus name + + table.insert(analysis_keyword_table, 1, table.concat(analysis_cat_table)) + + lua_util.debugm(rule.name, task, '%s: extended threat result: %s', + rule.log_prefix, table.concat(analysis_keyword_table, ',')) + + common.yield_result(task, rule, analysis_keyword_table, rule.default_score) + common.save_cache(task, digest, rule, analysis_keyword_table, rule.default_score) + + elseif analysis_cat_table.macro_exist == '-' and #analysis_keyword_table == 0 then + common.save_cache(task, digest, rule, 'OK') + common.log_clean(task, rule, 'No macro found') else - rspamd_logger.warnx(task, '%s: unhandled response', rule.log_prefix) - common.yield_result(task, rule, 'unhandled error', 0.0, 'fail') + common.save_cache(task, digest, rule, 'OK') + common.log_clean(task, rule, 'Scanned Macro is OK') end end end |