mirror of
https://github.com/Cisco-Talos/clamav.git
synced 2025-10-19 18:33:16 +00:00
bb12506: Fix phishing/heuristic alert verbosity
Some detections, like phishing, are considered heuristic alerts because they match based on behavior more than on content. A subset of these are considered "potentially unwanted" (low-severity). These low-severity alerts include: - phishing - PDFs with obfuscated object names - bytecode signature alerts that start with "BC.Heuristics" The concept is that unless you enable "heuristic precedence" (a method of lowing the threshold to immediateley alert on low-severity detections), the scan should continue after a match in case a higher severity match is found. Only at the end will it print the low-severity match if nothing else was found. The current implementation is buggy though. Scanning of archives does not correctly bail out for the entire archive if one email contains a phishing link. Instead, it sets the "heuristic found" flag then and alerts for every subsequent file in the archive because it doesn't know if the heuristic was found in an embedded file or the target file. Because it's just a heuristic and the status is "clean", it keeps scanning. This patch corrects the behavior by checking if a low-severity alerts were found at the end of scanning the target file, instead of at the end of each embedded file. Additionally, this patch fixes an in issue with phishing alerts wherein heuristic precedence mode did not cause a scan to stop after the first alert. The above changes required restructuring to create an fmap inside of cl_scandesc_callback() so that scan_common() could be modified to require an fmap and set up so that the current *ctx->fmap pointer is never NULL when scan_common() evaluates match results. Also fixed a couple minor bugs in the phishing unit tests and cleaned up the test code for improved legitibility and type safety.
This commit is contained in:
parent
053ce64c6f
commit
e01ba94e36
14 changed files with 522 additions and 334 deletions
10
NEWS.md
10
NEWS.md
|
@ -25,6 +25,12 @@ ClamAV 0.103.0 includes the following improvements and changes.
|
|||
limited when compared with `clamdtop` on Linux. PDCurses is required to
|
||||
build `clamdtop.exe` for ClamAV on Windows.
|
||||
|
||||
- The phishing detection module will now print "Suspicious link found!" along
|
||||
with the "Real URL" and "Display URL" each time phishing is detected. In a
|
||||
future version, we would like to print out alert-related metadata like this
|
||||
at the end of a scan, but for now this detail will help users understand why
|
||||
a given file is being flagged as phishing.
|
||||
|
||||
### Other improvements
|
||||
|
||||
- Added ability for freshclam and clamsubmit to override default use of openssl
|
||||
|
@ -44,6 +50,10 @@ ClamAV 0.103.0 includes the following improvements and changes.
|
|||
|
||||
- Fixed behavior of `freshclam --quiet` option.
|
||||
|
||||
- Fixed an error in the heuristic alert mechanism that would cause a single
|
||||
detection within an archive to alert once for every subsequent file scanned,
|
||||
potentially resulting in thousands of alerts for a single scan.
|
||||
|
||||
### New Requirements
|
||||
|
||||
### Acknowledgements
|
||||
|
|
|
@ -263,7 +263,7 @@ void cl_cleanup_crypto(void);
|
|||
* @param initoptions Unused.
|
||||
* @return cl_error_t CL_SUCCESS if everything initalized correctly.
|
||||
*/
|
||||
extern int cl_init(unsigned int initoptions);
|
||||
extern cl_error_t cl_init(unsigned int initoptions);
|
||||
|
||||
/**
|
||||
* @brief Allocate a new scanning engine and initialize default settings.
|
||||
|
@ -349,7 +349,7 @@ typedef struct cli_stats_sections {
|
|||
* @return cl_error_t CL_EARG if the field number was incorrect.
|
||||
* @return cl_error_t CL_ENULLARG null arguments were provided.
|
||||
*/
|
||||
extern int cl_engine_set_num(struct cl_engine *engine, enum cl_engine_field field, long long num);
|
||||
extern cl_error_t cl_engine_set_num(struct cl_engine *engine, enum cl_engine_field field, long long num);
|
||||
|
||||
/**
|
||||
* @brief Get a numerical engine option.
|
||||
|
@ -377,7 +377,7 @@ extern long long cl_engine_get_num(const struct cl_engine *engine, enum cl_engin
|
|||
* @return cl_error_t CL_EMEM if a memory allocation error occurred.
|
||||
* @return cl_error_t CL_ENULLARG null arguments were provided.
|
||||
*/
|
||||
extern int cl_engine_set_str(struct cl_engine *engine, enum cl_engine_field field, const char *str);
|
||||
extern cl_error_t cl_engine_set_str(struct cl_engine *engine, enum cl_engine_field field, const char *str);
|
||||
|
||||
/**
|
||||
* @brief Get a string engine option.
|
||||
|
@ -409,7 +409,7 @@ extern struct cl_settings *cl_engine_settings_copy(const struct cl_engine *engin
|
|||
* @return cl_error_t CL_SUCCESS if successful.
|
||||
* @return cl_error_t CL_EMEM if a memory allocation error occurred.
|
||||
*/
|
||||
extern int cl_engine_settings_apply(struct cl_engine *engine, const struct cl_settings *settings);
|
||||
extern cl_error_t cl_engine_settings_apply(struct cl_engine *engine, const struct cl_settings *settings);
|
||||
|
||||
/**
|
||||
* @brief Free a settings struct pointer.
|
||||
|
@ -418,7 +418,7 @@ extern int cl_engine_settings_apply(struct cl_engine *engine, const struct cl_se
|
|||
* @return cl_error_t CL_SUCCESS if successful.
|
||||
* @return cl_error_t CL_ENULLARG null arguments were provided.
|
||||
*/
|
||||
extern int cl_engine_settings_free(struct cl_settings *settings);
|
||||
extern cl_error_t cl_engine_settings_free(struct cl_settings *settings);
|
||||
|
||||
/**
|
||||
* @brief Prepare the scanning engine.
|
||||
|
@ -430,7 +430,7 @@ extern int cl_engine_settings_free(struct cl_settings *settings);
|
|||
* @return cl_error_t CL_SUCCESS if successful.
|
||||
* @return cl_error_t CL_ENULLARG null arguments were provided.
|
||||
*/
|
||||
extern int cl_engine_compile(struct cl_engine *engine);
|
||||
extern cl_error_t cl_engine_compile(struct cl_engine *engine);
|
||||
|
||||
/**
|
||||
* @brief Add a reference count to the engine.
|
||||
|
@ -444,7 +444,7 @@ extern int cl_engine_compile(struct cl_engine *engine);
|
|||
* @return cl_error_t CL_SUCCESS if successful.
|
||||
* @return cl_error_t CL_ENULLARG null arguments were provided.
|
||||
*/
|
||||
extern int cl_engine_addref(struct cl_engine *engine);
|
||||
extern cl_error_t cl_engine_addref(struct cl_engine *engine);
|
||||
|
||||
/**
|
||||
* @brief Free an engine.
|
||||
|
@ -456,7 +456,7 @@ extern int cl_engine_addref(struct cl_engine *engine);
|
|||
* @return cl_error_t CL_SUCCESS if successful.
|
||||
* @return cl_error_t CL_ENULLARG null arguments were provided.
|
||||
*/
|
||||
extern int cl_engine_free(struct cl_engine *engine);
|
||||
extern cl_error_t cl_engine_free(struct cl_engine *engine);
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Callback function type definitions.
|
||||
|
@ -939,7 +939,7 @@ extern cl_error_t cl_scanfile_callback(const char *filename, const char **virnam
|
|||
/* ----------------------------------------------------------------------------
|
||||
* Database handling.
|
||||
*/
|
||||
extern int cl_load(const char *path, struct cl_engine *engine, unsigned int *signo, unsigned int dboptions);
|
||||
extern cl_error_t cl_load(const char *path, struct cl_engine *engine, unsigned int *signo, unsigned int dboptions);
|
||||
extern const char *cl_retdbdir(void);
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
|
@ -987,7 +987,7 @@ extern struct cl_cvd *cl_cvdparse(const char *head);
|
|||
* @param file Filepath of CVD file.
|
||||
* @return cl_error_t CL_SUCCESS if success, else a CL_E* error code.
|
||||
*/
|
||||
extern int cl_cvdverify(const char *file);
|
||||
extern cl_error_t cl_cvdverify(const char *file);
|
||||
|
||||
/**
|
||||
* @brief Free a CVD header struct.
|
||||
|
@ -1017,7 +1017,7 @@ struct cl_stat {
|
|||
* @param[out] dbstat dbstat handle.
|
||||
* @return cl_error_t CL_SUCCESS if successfully initialized.
|
||||
*/
|
||||
extern int cl_statinidir(const char *dirname, struct cl_stat *dbstat);
|
||||
extern cl_error_t cl_statinidir(const char *dirname, struct cl_stat *dbstat);
|
||||
|
||||
/**
|
||||
* @brief Check the database directory for changes.
|
||||
|
@ -1035,7 +1035,7 @@ extern int cl_statchkdir(const struct cl_stat *dbstat);
|
|||
* @return cl_error_t CL_SUCCESS
|
||||
* @return cl_error_t CL_ENULLARG
|
||||
*/
|
||||
extern int cl_statfree(struct cl_stat *dbstat);
|
||||
extern cl_error_t cl_statfree(struct cl_stat *dbstat);
|
||||
|
||||
/**
|
||||
* @brief Count the number of signatures in a database file or directory.
|
||||
|
@ -1045,7 +1045,7 @@ extern int cl_statfree(struct cl_stat *dbstat);
|
|||
* @param[out] sigs The number of sigs.
|
||||
* @return cl_error_t CL_SUCCESS if success, else a CL_E* error type.
|
||||
*/
|
||||
extern int cl_countsigs(const char *path, unsigned int countoptions, unsigned int *sigs);
|
||||
extern cl_error_t cl_countsigs(const char *path, unsigned int countoptions, unsigned int *sigs);
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Software versions.
|
||||
|
|
|
@ -572,11 +572,12 @@ static int cli_cvdverify(FILE *fs, struct cl_cvd *cvdpt, unsigned int skipsig)
|
|||
return CL_SUCCESS;
|
||||
}
|
||||
|
||||
int cl_cvdverify(const char *file)
|
||||
cl_error_t cl_cvdverify(const char *file)
|
||||
{
|
||||
struct cl_engine *engine;
|
||||
FILE *fs;
|
||||
int ret, dbtype = 0;
|
||||
cl_error_t ret;
|
||||
int dbtype = 0;
|
||||
|
||||
if ((fs = fopen(file, "rb")) == NULL) {
|
||||
cli_errmsg("cl_cvdverify: Can't open file %s\n", file);
|
||||
|
|
|
@ -1039,9 +1039,11 @@ int fmap_fd(fmap_t *m)
|
|||
int fd;
|
||||
if (NULL == m) {
|
||||
cli_errmsg("fmap_fd: Attempted to get fd for NULL fmap\n");
|
||||
}
|
||||
if (!m->handle_is_fd)
|
||||
return -1;
|
||||
}
|
||||
if (!m->handle_is_fd) {
|
||||
return -1;
|
||||
}
|
||||
fd = (int)(ptrdiff_t)m->handle;
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
return fd;
|
||||
|
|
|
@ -284,9 +284,9 @@ const char *cl_strerror(int clerror)
|
|||
}
|
||||
}
|
||||
|
||||
int cl_init(unsigned int initoptions)
|
||||
cl_error_t cl_init(unsigned int initoptions)
|
||||
{
|
||||
int rc;
|
||||
cl_error_t rc;
|
||||
struct timeval tv;
|
||||
unsigned int pid = (unsigned int)getpid();
|
||||
|
||||
|
@ -470,7 +470,7 @@ struct cl_engine *cl_engine_new(void)
|
|||
return new;
|
||||
}
|
||||
|
||||
int cl_engine_set_num(struct cl_engine *engine, enum cl_engine_field field, long long num)
|
||||
cl_error_t cl_engine_set_num(struct cl_engine *engine, enum cl_engine_field field, long long num)
|
||||
{
|
||||
if (!engine)
|
||||
return CL_ENULLARG;
|
||||
|
@ -731,7 +731,7 @@ long long cl_engine_get_num(const struct cl_engine *engine, enum cl_engine_field
|
|||
}
|
||||
}
|
||||
|
||||
int cl_engine_set_str(struct cl_engine *engine, enum cl_engine_field field, const char *str)
|
||||
cl_error_t cl_engine_set_str(struct cl_engine *engine, enum cl_engine_field field, const char *str)
|
||||
{
|
||||
if (!engine)
|
||||
return CL_ENULLARG;
|
||||
|
@ -853,7 +853,7 @@ struct cl_settings *cl_engine_settings_copy(const struct cl_engine *engine)
|
|||
return settings;
|
||||
}
|
||||
|
||||
int cl_engine_settings_apply(struct cl_engine *engine, const struct cl_settings *settings)
|
||||
cl_error_t cl_engine_settings_apply(struct cl_engine *engine, const struct cl_settings *settings)
|
||||
{
|
||||
engine->ac_only = settings->ac_only;
|
||||
engine->ac_mindepth = settings->ac_mindepth;
|
||||
|
@ -927,7 +927,7 @@ int cl_engine_settings_apply(struct cl_engine *engine, const struct cl_settings
|
|||
return CL_SUCCESS;
|
||||
}
|
||||
|
||||
int cl_engine_settings_free(struct cl_settings *settings)
|
||||
cl_error_t cl_engine_settings_free(struct cl_settings *settings)
|
||||
{
|
||||
if (!settings)
|
||||
return CL_ENULLARG;
|
||||
|
@ -1134,11 +1134,11 @@ void cli_virus_found_cb(cli_ctx *ctx)
|
|||
|
||||
cl_error_t cli_append_possibly_unwanted(cli_ctx *ctx, const char *virname)
|
||||
{
|
||||
if (SCAN_ALLMATCHES)
|
||||
if (SCAN_ALLMATCHES) {
|
||||
return cli_append_virus(ctx, virname);
|
||||
else if (SCAN_HEURISTIC_PRECEDENCE)
|
||||
} else if (SCAN_HEURISTIC_PRECEDENCE) {
|
||||
return cli_append_virus(ctx, virname);
|
||||
else if (ctx->num_viruses == 0 && ctx->virname != NULL && *ctx->virname == NULL) {
|
||||
} else if (ctx->num_viruses == 0 && ctx->virname != NULL && *ctx->virname == NULL) {
|
||||
ctx->found_possibly_unwanted = 1;
|
||||
ctx->num_viruses++;
|
||||
*ctx->virname = virname;
|
||||
|
@ -1148,13 +1148,19 @@ cl_error_t cli_append_possibly_unwanted(cli_ctx *ctx, const char *virname)
|
|||
|
||||
cl_error_t cli_append_virus(cli_ctx *ctx, const char *virname)
|
||||
{
|
||||
if (ctx->virname == NULL)
|
||||
if (ctx->virname == NULL) {
|
||||
return CL_CLEAN;
|
||||
if (ctx->fmap != NULL && (*ctx->fmap) != NULL && CL_VIRUS != cli_checkfp_virus((*ctx->fmap)->maphash, (*ctx->fmap)->len, ctx, virname))
|
||||
}
|
||||
if ((ctx->fmap != NULL) &&
|
||||
((*ctx->fmap) != NULL) &&
|
||||
(CL_VIRUS != cli_checkfp_virus((*ctx->fmap)->maphash, (*ctx->fmap)->len, ctx, virname))) {
|
||||
return CL_CLEAN;
|
||||
if (!SCAN_ALLMATCHES && ctx->num_viruses != 0)
|
||||
if (SCAN_HEURISTIC_PRECEDENCE)
|
||||
}
|
||||
if (!SCAN_ALLMATCHES && ctx->num_viruses != 0) {
|
||||
if (SCAN_HEURISTIC_PRECEDENCE) {
|
||||
return CL_CLEAN;
|
||||
}
|
||||
}
|
||||
if (ctx->limit_exceeded == 0 || SCAN_ALLMATCHES) {
|
||||
ctx->num_viruses++;
|
||||
*ctx->virname = virname;
|
||||
|
|
|
@ -636,7 +636,42 @@ static inline void cli_writeint32(void *offset, uint32_t value)
|
|||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Append an alert.
|
||||
*
|
||||
* An FP-check will verify that the file is not whitelisted.
|
||||
* The whitelist check does not happen before the scan because file whitelisting
|
||||
* is so infrequent that such action would be detrimental to performance.
|
||||
*
|
||||
* TODO: Replace implementation with severity scale, and severity threshold
|
||||
* wherein signatures that do not meet the threshold are documented in JSON
|
||||
* metadata but do not halt the scan.
|
||||
*
|
||||
* @param ctx The scan context.
|
||||
* @param virname The alert name.
|
||||
* @return cl_error_t CL_VIRUS if scan should be halted due to an alert, CL_CLEAN if scan should continue.
|
||||
*/
|
||||
cl_error_t cli_append_virus(cli_ctx *ctx, const char *virname);
|
||||
|
||||
/**
|
||||
* @brief Append a PUA (low severity) alert.
|
||||
*
|
||||
* This function will return CLEAN unless in all-match or Heuristic-precedence
|
||||
* modes. The intention is for the scan to continue in case something more
|
||||
* malicious is found.
|
||||
*
|
||||
* TODO: Replace implementation with severity scale, and severity threshold
|
||||
* wherein signatures that do not meet the threshold are documented in JSON
|
||||
* metadata but do not halt the scan.
|
||||
*
|
||||
* BUG: In normal scan mode (see above), the alert is not FP-checked!
|
||||
*
|
||||
* @param ctx The scan context.
|
||||
* @param virname The alert name.
|
||||
* @return cl_error_t CL_VIRUS if scan should be halted due to an alert, CL_CLEAN if scan should continue.
|
||||
*/
|
||||
cl_error_t cli_append_possibly_unwanted(cli_ctx *ctx, const char *virname);
|
||||
|
||||
const char *cli_get_last_virus(const cli_ctx *ctx);
|
||||
const char *cli_get_last_virus_str(const cli_ctx *ctx);
|
||||
void cli_virus_found_cb(cli_ctx *ctx);
|
||||
|
@ -853,7 +888,6 @@ int cli_matchregex(const char *str, const char *regex);
|
|||
void cli_qsort(void *a, size_t n, size_t es, int (*cmp)(const void *, const void *));
|
||||
void cli_qsort_r(void *a, size_t n, size_t es, int (*cmp)(const void *, const void *, const void *), void *arg);
|
||||
cl_error_t cli_checktimelimit(cli_ctx *ctx);
|
||||
cl_error_t cli_append_possibly_unwanted(cli_ctx *ctx, const char *virname);
|
||||
|
||||
/* symlink behaviour */
|
||||
#define CLI_FTW_FOLLOW_FILE_SYMLINK 0x01
|
||||
|
|
|
@ -203,8 +203,8 @@ static int string_assign_concatenated(struct string* dest, const char* prefix, c
|
|||
static void string_assign_null(struct string* dest);
|
||||
static char* rfind(char* start, char c, size_t len);
|
||||
static char hex2int(const unsigned char* src);
|
||||
static enum phish_status phishingCheck(const struct cl_engine* engine, struct url_check* urls);
|
||||
static const char* phishing_ret_toString(enum phish_status rc);
|
||||
static enum phish_status phishingCheck(cli_ctx* ctx, struct url_check* urls);
|
||||
static const char* phishing_ret_toString(enum phish_status phishing_verdict);
|
||||
|
||||
static void url_check_init(struct url_check* urls)
|
||||
{
|
||||
|
@ -721,53 +721,23 @@ cleanupURL(struct string* URL, struct string* pre_URL, int isReal)
|
|||
}
|
||||
|
||||
/* -------end runtime disable---------*/
|
||||
int phishingScan(cli_ctx* ctx, tag_arguments_t* hrefs)
|
||||
cl_error_t phishingScan(cli_ctx* ctx, tag_arguments_t* hrefs)
|
||||
{
|
||||
cl_error_t status = CL_CLEAN;
|
||||
/* TODO: get_host and then apply regex, etc. */
|
||||
int i;
|
||||
struct phishcheck* pchk = (struct phishcheck*)ctx->engine->phishcheck;
|
||||
/* check for status of whitelist fatal error, etc. */
|
||||
if (!pchk || pchk->is_disabled)
|
||||
return CL_CLEAN;
|
||||
if (!pchk || pchk->is_disabled) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!ctx->found_possibly_unwanted && !SCAN_ALLMATCHES)
|
||||
*ctx->virname = NULL;
|
||||
#if 0
|
||||
FILE *f = fopen("/home/edwin/quarantine/urls","r");
|
||||
if(!f)
|
||||
abort();
|
||||
while(!feof(f)) {
|
||||
struct url_check urls;
|
||||
char line1[4096];
|
||||
char line2[4096];
|
||||
char line3[4096];
|
||||
|
||||
fgets(line1, sizeof(line1), f);
|
||||
fgets(line2, sizeof(line2), f);
|
||||
fgets(line3, sizeof(line3), f);
|
||||
if(strcmp(line3, "\n") != 0) {
|
||||
strcpy(line1, line2);
|
||||
strcpy(line2, line3);
|
||||
fgets(line3, sizeof(line3), f);
|
||||
while(strcmp(line3, "\n") != 0) {
|
||||
fgets(line3, sizeof(line3),f);
|
||||
}
|
||||
}
|
||||
urls.flags = CL_PHISH_ALL_CHECKS;
|
||||
urls.link_type = 0;
|
||||
string_init_c(&urls.realLink, line1);
|
||||
string_init_c(&urls.displayLink, line2);
|
||||
string_init_c(&urls.pre_fixup.pre_displayLink, NULL);
|
||||
urls.realLink.refcount=-1;
|
||||
urls.displayLink.refcount=-1;
|
||||
int rc = phishingCheck(ctx->engine, &urls);
|
||||
}
|
||||
fclose(f);
|
||||
return 0;
|
||||
#endif
|
||||
for (i = 0; i < hrefs->count; i++) {
|
||||
struct url_check urls;
|
||||
enum phish_status rc;
|
||||
enum phish_status phishing_verdict;
|
||||
urls.flags = strncmp((char*)hrefs->tag[i], href_text, href_text_len) ? (CL_PHISH_ALL_CHECKS & ~CHECK_SSL) : CL_PHISH_ALL_CHECKS;
|
||||
urls.link_type = 0;
|
||||
if (!strncmp((char*)hrefs->tag[i], src_text, src_text_len)) {
|
||||
|
@ -795,43 +765,49 @@ int phishingScan(cli_ctx* ctx, tag_arguments_t* hrefs)
|
|||
urls.displayLink.data = url;
|
||||
}
|
||||
|
||||
rc = phishingCheck(ctx->engine, &urls);
|
||||
if (pchk->is_disabled)
|
||||
return CL_CLEAN;
|
||||
phishing_verdict = phishingCheck(ctx, &urls);
|
||||
free_if_needed(&urls);
|
||||
cli_dbgmsg("Phishcheck: Phishing scan result: %s\n", phishing_ret_toString(rc));
|
||||
switch (rc) /*TODO: support flags from ctx->options,*/
|
||||
if (pchk->is_disabled) {
|
||||
return CL_CLEAN;
|
||||
}
|
||||
cli_dbgmsg("Phishcheck: Phishing scan result: %s\n", phishing_ret_toString(phishing_verdict));
|
||||
switch (phishing_verdict) /*TODO: support flags from ctx->options,*/
|
||||
{
|
||||
case CL_PHISH_CLEAN:
|
||||
continue;
|
||||
case CL_PHISH_NUMERIC_IP:
|
||||
cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.Cloaked.NumericIP");
|
||||
status = cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.Cloaked.NumericIP");
|
||||
break;
|
||||
case CL_PHISH_CLOAKED_NULL:
|
||||
cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.Cloaked.Null"); /*fakesite%01%00@fake.example.com*/
|
||||
status = cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.Cloaked.Null"); /*fakesite%01%00@fake.example.com*/
|
||||
break;
|
||||
case CL_PHISH_SSL_SPOOF:
|
||||
cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.SSL-Spoof");
|
||||
status = cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.SSL-Spoof");
|
||||
break;
|
||||
case CL_PHISH_CLOAKED_UIU:
|
||||
cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.Cloaked.Username"); /*http://banksite@fake.example.com*/
|
||||
status = cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.Cloaked.Username"); /*http://banksite@fake.example.com*/
|
||||
break;
|
||||
case CL_PHISH_HASH0:
|
||||
cli_append_possibly_unwanted(ctx, "Heuristics.Safebrowsing.Suspected-malware_safebrowsing.clamav.net");
|
||||
status = cli_append_possibly_unwanted(ctx, "Heuristics.Safebrowsing.Suspected-malware_safebrowsing.clamav.net");
|
||||
break;
|
||||
case CL_PHISH_HASH1:
|
||||
cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.URL.Blacklisted");
|
||||
status = cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.URL.Blacklisted");
|
||||
break;
|
||||
case CL_PHISH_HASH2:
|
||||
cli_append_possibly_unwanted(ctx, "Heuristics.Safebrowsing.Suspected-phishing_safebrowsing.clamav.net");
|
||||
status = cli_append_possibly_unwanted(ctx, "Heuristics.Safebrowsing.Suspected-phishing_safebrowsing.clamav.net");
|
||||
break;
|
||||
case CL_PHISH_NOMATCH:
|
||||
default:
|
||||
cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.SpoofedDomain");
|
||||
status = cli_append_possibly_unwanted(ctx, "Heuristics.Phishing.Email.SpoofedDomain");
|
||||
break;
|
||||
}
|
||||
if (CL_CLEAN != status && !SCAN_ALLMATCHES) {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
return CL_CLEAN;
|
||||
|
||||
done:
|
||||
return status;
|
||||
}
|
||||
|
||||
static char hex2int(const unsigned char* src)
|
||||
|
@ -1089,9 +1065,14 @@ static int isNumericURL(const struct phishcheck* pchk, const char* URL)
|
|||
return URL ? !cli_regexec(&pchk->preg_numeric, URL, 0, NULL, 0) : 0;
|
||||
}
|
||||
|
||||
/* Cleans up @urls
|
||||
* If URLs are identical after cleanup it will return CL_PHISH_CLEANUP_OK.
|
||||
* */
|
||||
/**
|
||||
* @brief Cleans up @urls
|
||||
*
|
||||
* If URLs are identical after cleanup it will return CL_PHISH_CLEAN.
|
||||
*
|
||||
* @param urls
|
||||
* @return enum phish_status
|
||||
*/
|
||||
static enum phish_status cleanupURLs(struct url_check* urls)
|
||||
{
|
||||
if (urls->flags & CLEANUP_URL) {
|
||||
|
@ -1165,7 +1146,13 @@ static cl_error_t whitelist_check(const struct cl_engine* engine, struct url_che
|
|||
return whitelist_match(engine, urls->realLink.data, urls->displayLink.data, hostOnly);
|
||||
}
|
||||
|
||||
static cl_error_t hash_match(const struct regex_matcher* rlist, const char* host, size_t hlen, const char* path, size_t plen, int* prefix_matched)
|
||||
static cl_error_t hash_match(const struct regex_matcher* rlist,
|
||||
const char* host,
|
||||
size_t hlen,
|
||||
const char* path,
|
||||
size_t plen,
|
||||
int* prefix_matched,
|
||||
enum phish_status* phishing_verdict)
|
||||
{
|
||||
const char* virname;
|
||||
#if 0
|
||||
|
@ -1177,6 +1164,12 @@ static cl_error_t hash_match(const struct regex_matcher* rlist, const char* host
|
|||
#endif
|
||||
UNUSEDPARAM(prefix_matched);
|
||||
|
||||
if ((NULL == host) || (NULL == path) || (NULL == phishing_verdict)) {
|
||||
return CL_ENULLARG;
|
||||
}
|
||||
|
||||
*phishing_verdict = CL_PHISH_NODECISION;
|
||||
|
||||
if (rlist->sha256_hashes.bm_patterns) {
|
||||
const char hexchars[] = "0123456789ABCDEF";
|
||||
unsigned char h[65];
|
||||
|
@ -1214,11 +1207,11 @@ static cl_error_t hash_match(const struct regex_matcher* rlist, const char* host
|
|||
cli_dbgmsg("Hash is whitelisted, skipping\n");
|
||||
break;
|
||||
case '1':
|
||||
return CL_PHISH_HASH1;
|
||||
*phishing_verdict = CL_PHISH_HASH1;
|
||||
case '2':
|
||||
return CL_PHISH_HASH2;
|
||||
*phishing_verdict = CL_PHISH_HASH2;
|
||||
default:
|
||||
return CL_PHISH_HASH0;
|
||||
*phishing_verdict = CL_PHISH_HASH0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1331,8 +1324,13 @@ enum phish_status cli_url_canon(const char* inurl, size_t len, char* urlbuff, si
|
|||
return CL_PHISH_NODECISION;
|
||||
}
|
||||
|
||||
static cl_error_t url_hash_match(const struct regex_matcher* rlist, const char* inurl, size_t len)
|
||||
static cl_error_t url_hash_match(
|
||||
const struct regex_matcher* rlist,
|
||||
const char* inurl,
|
||||
size_t len,
|
||||
enum phish_status* phishing_verdict)
|
||||
{
|
||||
cl_error_t status = CL_SUCCESS;
|
||||
size_t j, k, ji, ki;
|
||||
char* host_begin;
|
||||
const char* path_begin;
|
||||
|
@ -1351,14 +1349,20 @@ static cl_error_t url_hash_match(const struct regex_matcher* rlist, const char*
|
|||
if (!rlist || !rlist->sha256_hashes.bm_patterns) {
|
||||
/* no hashes loaded -> don't waste time canonicalizing and
|
||||
* looking up */
|
||||
return CL_SUCCESS;
|
||||
goto done;
|
||||
}
|
||||
if (!inurl)
|
||||
return CL_EMEM;
|
||||
if ((NULL == inurl) || (NULL == phishing_verdict)) {
|
||||
status = CL_ENULLARG;
|
||||
goto done;
|
||||
}
|
||||
|
||||
*phishing_verdict = CL_PHISH_NODECISION;
|
||||
|
||||
phish_rc = cli_url_canon(inurl, len, urlbuff, sizeof(urlbuff), &host_begin, &host_len, &path_begin, &path_len);
|
||||
if (phish_rc == CL_PHISH_CLEAN)
|
||||
return CL_CLEAN;
|
||||
if (phish_rc == CL_PHISH_CLEAN) {
|
||||
*phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* get last 5 components of hostname */
|
||||
j = COMPONENTS;
|
||||
|
@ -1390,8 +1394,10 @@ static cl_error_t url_hash_match(const struct regex_matcher* rlist, const char*
|
|||
} else
|
||||
break;
|
||||
}
|
||||
} else
|
||||
} else {
|
||||
k = 1;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
for (ki = k; ki > 0;) {
|
||||
--ki;
|
||||
|
@ -1403,9 +1409,15 @@ static cl_error_t url_hash_match(const struct regex_matcher* rlist, const char*
|
|||
--ji;
|
||||
assert(pp[ki] <= path_len);
|
||||
/* lookup prefix/suffix hashes of URL */
|
||||
rc = hash_match(rlist, lp[ji], host_begin + host_len - lp[ji] + 1, path_begin, pp[ki],
|
||||
need_prefixmatch ? &prefix_matched : NULL);
|
||||
if (CL_CLEAN != rc) {
|
||||
rc = hash_match(rlist,
|
||||
lp[ji],
|
||||
host_begin + host_len - lp[ji] + 1,
|
||||
path_begin,
|
||||
pp[ki],
|
||||
need_prefixmatch ? &prefix_matched : NULL,
|
||||
phishing_verdict);
|
||||
if ((CL_SUCCESS == rc) &&
|
||||
(CL_PHISH_NODECISION != *phishing_verdict)) {
|
||||
return rc;
|
||||
}
|
||||
count++;
|
||||
|
@ -1420,52 +1432,80 @@ static cl_error_t url_hash_match(const struct regex_matcher* rlist, const char*
|
|||
#endif
|
||||
}
|
||||
}
|
||||
return CL_SUCCESS;
|
||||
|
||||
done:
|
||||
return status;
|
||||
}
|
||||
|
||||
/* urls can't contain null pointer, caller must ensure this */
|
||||
static enum phish_status phishingCheck(const struct cl_engine* engine, struct url_check* urls)
|
||||
/**
|
||||
* @brief Check if a displayLink & realLink URL pair are an attempt at phishing.
|
||||
*
|
||||
* The urls structure must not contain null pointers, caller must ensure this.
|
||||
*
|
||||
* @param ctx scan context
|
||||
* @param urls struct url_check containing real & display URLs (eg from html href tag)
|
||||
* @return enum phish_status
|
||||
*/
|
||||
static enum phish_status phishingCheck(cli_ctx* ctx, struct url_check* urls)
|
||||
{
|
||||
struct url_check host_url;
|
||||
int rc = CL_PHISH_NODECISION;
|
||||
int phishy = 0;
|
||||
const struct phishcheck* pchk = (const struct phishcheck*)engine->phishcheck;
|
||||
struct url_check domain_url;
|
||||
|
||||
enum phish_status phishing_verdict = CL_PHISH_NODECISION;
|
||||
cl_error_t status = CL_SUCCESS;
|
||||
int phishy = 0;
|
||||
const struct phishcheck* pchk = (const struct phishcheck*)ctx->engine->phishcheck;
|
||||
|
||||
char* realData = NULL;
|
||||
char* displayData = NULL;
|
||||
|
||||
if (!urls->realLink.data)
|
||||
return CL_PHISH_CLEAN;
|
||||
url_check_init(&host_url);
|
||||
url_check_init(&domain_url);
|
||||
|
||||
if (!urls->realLink.data) {
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
cli_dbgmsg("Phishcheck:Checking url %s->%s\n", urls->realLink.data,
|
||||
urls->displayLink.data);
|
||||
|
||||
if (!isURL(urls->realLink.data, 0)) {
|
||||
cli_dbgmsg("Real 'url' is not url:%s\n", urls->realLink.data);
|
||||
return CL_PHISH_CLEAN;
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((rc = url_hash_match(engine->domainlist_matcher, urls->realLink.data, strlen(urls->realLink.data)))) {
|
||||
if (rc == CL_PHISH_CLEAN) {
|
||||
cli_dbgmsg("not analyzing, not a real url: %s\n", urls->realLink.data);
|
||||
return CL_PHISH_CLEAN;
|
||||
if (CL_SUCCESS != (status = url_hash_match(ctx->engine->domainlist_matcher,
|
||||
urls->realLink.data,
|
||||
strlen(urls->realLink.data),
|
||||
&phishing_verdict))) {
|
||||
cli_dbgmsg("Error occured in url_hash_match\n");
|
||||
goto done;
|
||||
} else if (phishing_verdict != CL_PHISH_NODECISION) {
|
||||
if (phishing_verdict == CL_PHISH_CLEAN) {
|
||||
cli_dbgmsg("Not analyzing, not a real url: %s\n", urls->realLink.data);
|
||||
goto done;
|
||||
} else {
|
||||
cli_dbgmsg("Hash matched for: %s\n", urls->realLink.data);
|
||||
return rc;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcmp(urls->realLink.data, urls->displayLink.data))
|
||||
return CL_PHISH_CLEAN; /* displayed and real URL are identical -> clean */
|
||||
|
||||
if (urls->displayLink.data[0] == '\0') {
|
||||
return CL_PHISH_CLEAN;
|
||||
if (!strcmp(urls->realLink.data, urls->displayLink.data)) {
|
||||
/* displayed and real URL are identical -> clean */
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((rc = cleanupURLs(urls))) {
|
||||
/* it can only return an error, or say its clean;
|
||||
* it is not allowed to decide it is phishing */
|
||||
return rc < 0 ? rc : CL_PHISH_CLEAN;
|
||||
if (urls->displayLink.data[0] == '\0') {
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((phishing_verdict = cleanupURLs(urls)) == CL_PHISH_CLEAN) {
|
||||
/* displayed and real URL are identical after cleanup -> clean */
|
||||
goto done;
|
||||
}
|
||||
|
||||
cli_dbgmsg("Phishcheck:URL after cleanup: %s->%s\n", urls->realLink.data,
|
||||
|
@ -1475,7 +1515,8 @@ static enum phish_status phishingCheck(const struct cl_engine* engine, struct ur
|
|||
((phishy & PHISHY_NUMERIC_IP && !isNumericURL(pchk, urls->displayLink.data)) ||
|
||||
!(phishy & PHISHY_NUMERIC_IP))) {
|
||||
cli_dbgmsg("Displayed 'url' is not url:%s\n", urls->displayLink.data);
|
||||
return CL_PHISH_CLEAN;
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1483,8 +1524,10 @@ static enum phish_status phishingCheck(const struct cl_engine* engine, struct ur
|
|||
* Eg:
|
||||
* X:.+\.benign\.com([/?].*)?:.+\.benign\.de
|
||||
*/
|
||||
if (whitelist_check(engine, urls, 0))
|
||||
return CL_PHISH_CLEAN; /* if url is whitelisted don't perform further checks */
|
||||
if (whitelist_check(ctx->engine, urls, 0)) { /* if url is whitelisted don't perform further checks */
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
* Match R-type PDB signatures: R:RealURL:DisplayedURL
|
||||
|
@ -1496,38 +1539,37 @@ static enum phish_status phishingCheck(const struct cl_engine* engine, struct ur
|
|||
realData = cli_strdup(urls->realLink.data);
|
||||
if (!realData) {
|
||||
cli_errmsg("Phishcheck: Failed to allocate memory for temporary real link string.\n");
|
||||
return CL_PHISH_CLEAN;
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
displayData = cli_strdup(urls->displayLink.data);
|
||||
if (!displayData) {
|
||||
cli_errmsg("Phishcheck: Failed to allocate memory for temporary display link string.\n");
|
||||
return CL_PHISH_CLEAN;
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
if (domainlist_match(engine, realData, displayData, &urls->pre_fixup, 0)) {
|
||||
if (domainlist_match(ctx->engine, realData, displayData, &urls->pre_fixup, 0)) {
|
||||
phishy |= DOMAIN_LISTED;
|
||||
}
|
||||
free(realData);
|
||||
free(displayData);
|
||||
|
||||
/*
|
||||
* Get copy of URLs stripped down to just the FQDN.
|
||||
*/
|
||||
url_check_init(&host_url);
|
||||
if ((rc = url_get_host(urls, &host_url, DOMAIN_DISPLAY, &phishy))) {
|
||||
free_if_needed(&host_url);
|
||||
return rc < 0 ? rc : CL_PHISH_CLEAN;
|
||||
if ((phishing_verdict = url_get_host(urls, &host_url, DOMAIN_DISPLAY, &phishy))) {
|
||||
phishing_verdict = phishing_verdict < 0 ? phishing_verdict : CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
if ((rc = url_get_host(urls, &host_url, DOMAIN_REAL, &phishy))) {
|
||||
free_if_needed(&host_url);
|
||||
return rc < 0 ? rc : CL_PHISH_CLEAN;
|
||||
if ((phishing_verdict = url_get_host(urls, &host_url, DOMAIN_REAL, &phishy))) {
|
||||
phishing_verdict = phishing_verdict < 0 ? phishing_verdict : CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
* Exit early if the realLink and displayLink are the same.
|
||||
*/
|
||||
if (!strcmp(urls->realLink.data, urls->displayLink.data)) {
|
||||
free_if_needed(&host_url);
|
||||
return CL_PHISH_CLEAN;
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1535,9 +1577,9 @@ static enum phish_status phishingCheck(const struct cl_engine* engine, struct ur
|
|||
* Eg:
|
||||
* M:email.isbenign.com:benign.com
|
||||
*/
|
||||
if (whitelist_check(engine, &host_url, 1)) {
|
||||
free_if_needed(&host_url);
|
||||
return CL_PHISH_CLEAN;
|
||||
if (whitelist_check(ctx->engine, &host_url, 1)) {
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1545,7 +1587,7 @@ static enum phish_status phishingCheck(const struct cl_engine* engine, struct ur
|
|||
* Eg:
|
||||
* H:malicious.com
|
||||
*/
|
||||
if (domainlist_match(engine, host_url.displayLink.data, host_url.realLink.data, &urls->pre_fixup, 1)) {
|
||||
if (domainlist_match(ctx->engine, host_url.displayLink.data, host_url.realLink.data, &urls->pre_fixup, 1)) {
|
||||
phishy |= DOMAIN_LISTED;
|
||||
} else {
|
||||
urls->flags &= urls->always_check_flags;
|
||||
|
@ -1554,50 +1596,68 @@ static enum phish_status phishingCheck(const struct cl_engine* engine, struct ur
|
|||
|
||||
/* link type filtering must occur after last domainlist_match */
|
||||
if (urls->link_type & LINKTYPE_IMAGE && !(urls->flags & CHECK_IMG_URL)) {
|
||||
free_if_needed(&host_url);
|
||||
return CL_PHISH_CLEAN; /* its listed, but this link type is filtered */
|
||||
/* its listed, but this link type is filtered */
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (urls->flags & CHECK_CLOAKING) {
|
||||
/*Checks if URL is cloaked.
|
||||
Should we check if it contains another http://, https://?
|
||||
No because we might get false positives from redirect services.*/
|
||||
/*
|
||||
* Checks if URL is cloaked.
|
||||
* Should we check if it contains another http://, https://?
|
||||
* No because we might get false positives from redirect services.
|
||||
*/
|
||||
if (strchr(urls->realLink.data, 0x1)) {
|
||||
free_if_needed(&host_url);
|
||||
return CL_PHISH_CLOAKED_NULL;
|
||||
phishing_verdict = CL_PHISH_CLOAKED_NULL;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (urls->flags & CHECK_SSL && isSSL(urls->displayLink.data) && !isSSL(urls->realLink.data)) {
|
||||
free_if_needed(&host_url);
|
||||
return CL_PHISH_SSL_SPOOF;
|
||||
phishing_verdict = CL_PHISH_SSL_SPOOF;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!(phishy & DOMAIN_LISTED)) {
|
||||
free_if_needed(&host_url);
|
||||
return CL_PHISH_CLEAN;
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
{
|
||||
struct url_check domain_url;
|
||||
url_check_init(&domain_url);
|
||||
url_get_domain(&host_url, &domain_url);
|
||||
if (!strcmp(domain_url.realLink.data, domain_url.displayLink.data)) {
|
||||
free_if_needed(&host_url);
|
||||
free_if_needed(&domain_url);
|
||||
return CL_PHISH_CLEAN;
|
||||
}
|
||||
free_if_needed(&domain_url);
|
||||
url_get_domain(&host_url, &domain_url);
|
||||
if (!strcmp(domain_url.realLink.data, domain_url.displayLink.data)) {
|
||||
phishing_verdict = CL_PHISH_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
* We failed to find a reason why the 2 URLs are different.
|
||||
* This is probably phishing.
|
||||
*/
|
||||
phishing_verdict = phishy_map(phishy, CL_PHISH_NOMATCH);
|
||||
|
||||
done:
|
||||
if (phishing_verdict != CL_PHISH_CLEAN && phishing_verdict != CL_PHISH_NODECISION) {
|
||||
cli_infomsg(ctx, "Suspicious link found!\n");
|
||||
cli_infomsg(ctx, " Real URL: %s\n", urls->realLink.data);
|
||||
cli_infomsg(ctx, " Display URL: %s\n", urls->displayLink.data);
|
||||
}
|
||||
|
||||
if (NULL != realData) {
|
||||
free(realData);
|
||||
}
|
||||
if (NULL != displayData) {
|
||||
free(displayData);
|
||||
}
|
||||
|
||||
free_if_needed(&domain_url);
|
||||
free_if_needed(&host_url);
|
||||
/*we failed to find a reason why the 2 URLs are different, this is definitely phishing*/
|
||||
return phishy_map(phishy, CL_PHISH_NOMATCH);
|
||||
|
||||
return phishing_verdict;
|
||||
}
|
||||
|
||||
static const char* phishing_ret_toString(enum phish_status rc)
|
||||
static const char* phishing_ret_toString(enum phish_status phishing_verdict)
|
||||
{
|
||||
switch (rc) {
|
||||
switch (phishing_verdict) {
|
||||
case CL_PHISH_CLEAN:
|
||||
return "Clean";
|
||||
case CL_PHISH_CLOAKED_NULL:
|
||||
|
|
|
@ -74,7 +74,7 @@ struct url_check {
|
|||
unsigned short link_type;
|
||||
};
|
||||
|
||||
int phishingScan(cli_ctx* ctx, tag_arguments_t* hrefs);
|
||||
cl_error_t phishingScan(cli_ctx* ctx, tag_arguments_t* hrefs);
|
||||
|
||||
void phish_disable(struct cl_engine* engine, const char* reason);
|
||||
/* Global, non-thread-safe functions, call only once! */
|
||||
|
|
|
@ -4701,7 +4701,7 @@ done:
|
|||
return ret;
|
||||
}
|
||||
|
||||
int cl_load(const char *path, struct cl_engine *engine, unsigned int *signo, unsigned int dboptions)
|
||||
cl_error_t cl_load(const char *path, struct cl_engine *engine, unsigned int *signo, unsigned int dboptions)
|
||||
{
|
||||
STATBUF sb;
|
||||
int ret;
|
||||
|
@ -4799,7 +4799,7 @@ const char *cl_retdbdir(void)
|
|||
return DATADIR;
|
||||
}
|
||||
|
||||
int cl_statinidir(const char *dirname, struct cl_stat *dbstat)
|
||||
cl_error_t cl_statinidir(const char *dirname, struct cl_stat *dbstat)
|
||||
{
|
||||
DIR *dd;
|
||||
struct dirent *dent;
|
||||
|
@ -4953,9 +4953,8 @@ void cli_pwdb_list_free(struct cl_engine *engine, struct cli_pwdb *pwdb)
|
|||
}
|
||||
}
|
||||
|
||||
int cl_statfree(struct cl_stat *dbstat)
|
||||
cl_error_t cl_statfree(struct cl_stat *dbstat)
|
||||
{
|
||||
|
||||
if (dbstat) {
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@ -4990,7 +4989,7 @@ int cl_statfree(struct cl_stat *dbstat)
|
|||
return CL_SUCCESS;
|
||||
}
|
||||
|
||||
int cl_engine_free(struct cl_engine *engine)
|
||||
cl_error_t cl_engine_free(struct cl_engine *engine)
|
||||
{
|
||||
unsigned int i, j;
|
||||
struct cli_matcher *root;
|
||||
|
@ -5190,10 +5189,10 @@ int cl_engine_free(struct cl_engine *engine)
|
|||
return CL_SUCCESS;
|
||||
}
|
||||
|
||||
int cl_engine_compile(struct cl_engine *engine)
|
||||
cl_error_t cl_engine_compile(struct cl_engine *engine)
|
||||
{
|
||||
unsigned int i;
|
||||
int ret;
|
||||
cl_error_t ret;
|
||||
struct cli_matcher *root;
|
||||
|
||||
if (!engine)
|
||||
|
@ -5288,7 +5287,7 @@ int cl_engine_compile(struct cl_engine *engine)
|
|||
return CL_SUCCESS;
|
||||
}
|
||||
|
||||
int cl_engine_addref(struct cl_engine *engine)
|
||||
cl_error_t cl_engine_addref(struct cl_engine *engine)
|
||||
{
|
||||
if (!engine) {
|
||||
cli_errmsg("cl_engine_addref: engine == NULL\n");
|
||||
|
@ -5372,13 +5371,13 @@ static int countsigs(const char *dbname, unsigned int options, unsigned int *sig
|
|||
return CL_SUCCESS;
|
||||
}
|
||||
|
||||
int cl_countsigs(const char *path, unsigned int countoptions, unsigned int *sigs)
|
||||
cl_error_t cl_countsigs(const char *path, unsigned int countoptions, unsigned int *sigs)
|
||||
{
|
||||
STATBUF sb;
|
||||
char fname[1024];
|
||||
struct dirent *dent;
|
||||
DIR *dd;
|
||||
int ret;
|
||||
cl_error_t ret;
|
||||
|
||||
if (!sigs)
|
||||
return CL_ENULLARG;
|
||||
|
|
|
@ -629,7 +629,6 @@ static void list_add_tail(struct regex_list_ht *ht, struct regex_list *regex)
|
|||
ht->tail = regex;
|
||||
}
|
||||
|
||||
/* returns 0 on success, clamav error code otherwise */
|
||||
static cl_error_t add_pattern_suffix(void *cbdata, const char *suffix, size_t suffix_len, const struct regex_list *iregex)
|
||||
{
|
||||
struct regex_matcher *matcher = cbdata;
|
||||
|
|
|
@ -29,6 +29,19 @@ struct regex_list {
|
|||
regex_t *preg;
|
||||
struct regex_list *nxt;
|
||||
};
|
||||
|
||||
typedef cl_error_t (*suffix_callback)(void *cbdata, const char *suffix, size_t len, const struct regex_list *regex);
|
||||
|
||||
/**
|
||||
* @brief Build a suffix tree given a regex pattern
|
||||
*
|
||||
* @param pattern The regex pattern
|
||||
* @param pregs
|
||||
* @param cb
|
||||
* @param cbdata
|
||||
* @return cl_error_t May return a clam error type or may return a regex error integer.
|
||||
* TODO: separate regex error code from clam error code into out param.
|
||||
*/
|
||||
cl_error_t cli_regex2suffix(const char *pattern, regex_t *preg, suffix_callback cb, void *cbdata);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3495,6 +3495,12 @@ cl_error_t cli_magic_scan(cli_ctx *ctx, cli_file_t type)
|
|||
goto early_ret;
|
||||
}
|
||||
|
||||
if ((*ctx->fmap)->len <= 5) {
|
||||
cli_dbgmsg("cli_magic_scandesc: File is too too small (%zu bytes), ignoring.\n", (*ctx->fmap)->len);
|
||||
ret = CL_CLEAN;
|
||||
goto early_ret;
|
||||
}
|
||||
|
||||
if (cli_updatelimits(ctx, (*ctx->fmap)->len) != CL_CLEAN) {
|
||||
emax_reached(ctx);
|
||||
ret = CL_CLEAN;
|
||||
|
@ -4273,7 +4279,6 @@ done:
|
|||
#endif
|
||||
|
||||
if (ret == CL_CLEAN && ctx->found_possibly_unwanted) {
|
||||
cli_virus_found_cb(ctx);
|
||||
cb_retcode = CL_VIRUS;
|
||||
} else {
|
||||
if (ret == CL_CLEAN && ctx->num_viruses != 0)
|
||||
|
@ -4554,10 +4559,9 @@ cl_error_t cli_magic_scan_buff(const void *buffer, size_t length, cli_ctx *ctx,
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief The main function to initiate a scan, that may be invoked with a file descriptor or a file map.
|
||||
* @brief The main function to initiate a scan of an fmap.
|
||||
*
|
||||
* @param desc File descriptor of an open file. The caller must provide this or the map.
|
||||
* @param map File map. The caller must provide this or the desc.
|
||||
* @param map File map.
|
||||
* @param filepath (optional, recommended) filepath of the open file descriptor or file map.
|
||||
* @param[out] virname Will be set to a statically allocated (i.e. needs not be freed) signature name if the scan matches against a signature.
|
||||
* @param[out] scanned The number of bytes scanned.
|
||||
|
@ -4566,11 +4570,10 @@ cl_error_t cli_magic_scan_buff(const void *buffer, size_t length, cli_ctx *ctx,
|
|||
* @param[inout] context An opaque context structure allowing the caller to record details about the sample being scanned.
|
||||
* @return int CL_CLEAN, CL_VIRUS, or an error code if an error occured during the scan.
|
||||
*/
|
||||
static cl_error_t scan_common(int desc, cl_fmap_t *map, const char *filepath, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, struct cl_scan_options *scanoptions, void *context)
|
||||
static cl_error_t scan_common(cl_fmap_t *map, const char *filepath, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, struct cl_scan_options *scanoptions, void *context)
|
||||
{
|
||||
cli_ctx ctx;
|
||||
int rc;
|
||||
STATBUF sb;
|
||||
cl_error_t rc;
|
||||
|
||||
char *target_basename = NULL;
|
||||
char *new_temp_prefix = NULL;
|
||||
|
@ -4580,18 +4583,14 @@ static cl_error_t scan_common(int desc, cl_fmap_t *map, const char *filepath, co
|
|||
time_t current_time;
|
||||
struct tm tm_struct;
|
||||
|
||||
/* We have a limit of around 2.17GB (INT_MAX - 2). Enforce it here. */
|
||||
if (map != NULL) {
|
||||
if ((size_t)(map->real_len) > (size_t)(INT_MAX - 2))
|
||||
return CL_CLEAN;
|
||||
} else {
|
||||
if (FSTAT(desc, &sb))
|
||||
return CL_ESTAT;
|
||||
|
||||
if ((unsigned long long)(sb.st_size) > (unsigned long long)(INT_MAX - 2))
|
||||
return CL_CLEAN;
|
||||
if (NULL == map) {
|
||||
return CL_ENULLARG;
|
||||
}
|
||||
|
||||
/* We have a limit of around 2.17GB (INT_MAX - 2). Enforce it here. */
|
||||
if ((size_t)(map->real_len) > (size_t)(INT_MAX - 2))
|
||||
return CL_CLEAN;
|
||||
|
||||
memset(&ctx, '\0', sizeof(cli_ctx));
|
||||
ctx.engine = engine;
|
||||
ctx.virname = virname;
|
||||
|
@ -4605,13 +4604,23 @@ static cl_error_t scan_common(int desc, cl_fmap_t *map, const char *filepath, co
|
|||
cli_set_container(&ctx, CL_TYPE_ANY, 0);
|
||||
ctx.dconf = (struct cli_dconf *)engine->dconf;
|
||||
ctx.cb_ctx = context;
|
||||
ctx.fmap = cli_calloc(sizeof(fmap_t *), ctx.engine->maxreclevel + 2);
|
||||
ctx.fmap = cli_calloc(sizeof(fmap_t *), ctx.engine->maxreclevel + 3);
|
||||
if (!ctx.fmap)
|
||||
return CL_EMEM;
|
||||
if (!(ctx.hook_lsig_matches = cli_bitset_init())) {
|
||||
free(ctx.fmap);
|
||||
return CL_EMEM;
|
||||
}
|
||||
|
||||
/*
|
||||
* The first fmap in ctx.fmap must be NULL so we can fmap-- while not NULL.
|
||||
* But we need an fmap to be set so we can append viruses or report the
|
||||
* fmap's file descriptor in the virus found callback (like for deferred
|
||||
* low-seveerity alerts).
|
||||
*/
|
||||
ctx.fmap++;
|
||||
*ctx.fmap = map;
|
||||
|
||||
perf_init(&ctx);
|
||||
|
||||
if (ctx.engine->maxscantime != 0) {
|
||||
|
@ -4691,8 +4700,12 @@ static cl_error_t scan_common(int desc, cl_fmap_t *map, const char *filepath, co
|
|||
}
|
||||
|
||||
cli_logg_setup(&ctx);
|
||||
rc = map ? cli_magic_scan_nested_fmap_type(map, 0, map->len, &ctx, CL_TYPE_ANY, target_basename)
|
||||
: cli_magic_scan_desc(desc, ctx.target_filepath, &ctx, target_basename);
|
||||
|
||||
rc = cli_magic_scan_nested_fmap_type(map, 0, map->len, &ctx, CL_TYPE_ANY, target_basename);
|
||||
|
||||
if (rc == CL_CLEAN && ctx.found_possibly_unwanted) {
|
||||
cli_virus_found_cb(&ctx);
|
||||
}
|
||||
|
||||
#if HAVE_JSON
|
||||
if (ctx.options->general & CL_SCAN_GENERAL_COLLECT_METADATA && (ctx.properties != NULL)) {
|
||||
|
@ -4732,30 +4745,8 @@ static cl_error_t scan_common(int desc, cl_fmap_t *map, const char *filepath, co
|
|||
cli_errmsg("scan_common: can't allocate memory for bc_ctx\n");
|
||||
rc = CL_EMEM;
|
||||
} else {
|
||||
fmap_t *pc_map = map;
|
||||
|
||||
if (!pc_map) {
|
||||
perf_start(&ctx, PERFT_MAP);
|
||||
if (!(pc_map = fmap(desc, 0, sb.st_size, NULL))) {
|
||||
perf_stop(&ctx, PERFT_MAP);
|
||||
rc = CL_EMEM;
|
||||
}
|
||||
perf_stop(&ctx, PERFT_MAP);
|
||||
}
|
||||
|
||||
if (pc_map) {
|
||||
ctx.fmap++;
|
||||
ctx.recursion++;
|
||||
*ctx.fmap = pc_map;
|
||||
cli_bytecode_context_setctx(bc_ctx, &ctx);
|
||||
rc = cli_bytecode_runhook(&ctx, ctx.engine, bc_ctx, BC_PRECLASS, pc_map);
|
||||
*ctx.fmap = NULL;
|
||||
ctx.fmap--;
|
||||
ctx.recursion--;
|
||||
if (!map) {
|
||||
funmap(pc_map);
|
||||
}
|
||||
}
|
||||
cli_bytecode_context_setctx(bc_ctx, &ctx);
|
||||
rc = cli_bytecode_runhook(&ctx, ctx.engine, bc_ctx, BC_PRECLASS, map);
|
||||
cli_bytecode_context_destroy(bc_ctx);
|
||||
}
|
||||
|
||||
|
@ -4822,6 +4813,7 @@ static cl_error_t scan_common(int desc, cl_fmap_t *map, const char *filepath, co
|
|||
}
|
||||
free(ctx.containers);
|
||||
cli_bitset_free(ctx.hook_lsig_matches);
|
||||
ctx.fmap--; /* Restore original fmap pointer */
|
||||
free(ctx.fmap);
|
||||
free(ctx.options);
|
||||
cli_logg_unsetup();
|
||||
|
@ -4832,12 +4824,48 @@ static cl_error_t scan_common(int desc, cl_fmap_t *map, const char *filepath, co
|
|||
|
||||
cl_error_t cl_scandesc_callback(int desc, const char *filename, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, struct cl_scan_options *scanoptions, void *context)
|
||||
{
|
||||
return scan_common(desc, NULL, filename, virname, scanned, engine, scanoptions, context);
|
||||
cl_error_t status = CL_SUCCESS;
|
||||
cl_fmap_t *map = NULL;
|
||||
STATBUF sb;
|
||||
char *filename_base = NULL;
|
||||
|
||||
if (FSTAT(desc, &sb) == -1) {
|
||||
cli_errmsg("cl_scandesc_callback: Can't fstat descriptor %d\n", desc);
|
||||
status = CL_ESTAT;
|
||||
goto done;
|
||||
}
|
||||
if (sb.st_size <= 5) {
|
||||
cli_dbgmsg("cl_scandesc_callback: File too small (%u bytes), ignoring\n", (unsigned int)sb.st_size);
|
||||
status = CL_CLEAN;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (NULL != filename) {
|
||||
(void)cli_basename(filename, strlen(filename), &filename_base);
|
||||
}
|
||||
|
||||
if (NULL == (map = fmap(desc, 0, sb.st_size, filename_base))) {
|
||||
cli_errmsg("CRITICAL: fmap() failed\n");
|
||||
status = CL_EMEM;
|
||||
goto done;
|
||||
}
|
||||
|
||||
status = scan_common(map, filename, virname, scanned, engine, scanoptions, context);
|
||||
|
||||
done:
|
||||
if (NULL != map) {
|
||||
funmap(map);
|
||||
}
|
||||
if (NULL != filename_base) {
|
||||
free(filename_base);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
cl_error_t cl_scanmap_callback(cl_fmap_t *map, const char *filename, const char **virname, unsigned long int *scanned, const struct cl_engine *engine, struct cl_scan_options *scanoptions, void *context)
|
||||
{
|
||||
return scan_common(-1, map, filename, virname, scanned, engine, scanoptions, context);
|
||||
return scan_common(map, filename, virname, scanned, engine, scanoptions, context);
|
||||
}
|
||||
|
||||
cl_error_t cli_found_possibly_unwanted(cli_ctx *ctx)
|
||||
|
|
|
@ -1032,6 +1032,7 @@ static fc_error_t getcvd(
|
|||
int logerr)
|
||||
{
|
||||
fc_error_t ret;
|
||||
cl_error_t cl_ret;
|
||||
fc_error_t status = FC_EARG;
|
||||
|
||||
struct cl_cvd *cvd = NULL;
|
||||
|
@ -1068,13 +1069,13 @@ static fc_error_t getcvd(
|
|||
goto done;
|
||||
}
|
||||
|
||||
if ((ret = cl_cvdverify(tmpfile_with_extension))) {
|
||||
logg("!getcvd: Verification: %s\n", cl_strerror(ret));
|
||||
if (CL_SUCCESS != (cl_ret = cl_cvdverify(tmpfile_with_extension))) {
|
||||
logg("!getcvd: Verification: %s\n", cl_strerror(cl_ret));
|
||||
status = FC_EBADCVD;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!(cvd = cl_cvdhead(tmpfile_with_extension))) {
|
||||
if (NULL == (cvd = cl_cvdhead(tmpfile_with_extension))) {
|
||||
logg("!getcvd: Can't read CVD header of new %s database.\n", cvdfile);
|
||||
status = FC_EBADCVD;
|
||||
goto done;
|
||||
|
|
|
@ -46,6 +46,11 @@ static size_t cb_called = 0;
|
|||
|
||||
static cl_error_t cb_fail(void *cbdata, const char *suffix, size_t len, const struct regex_list *regex)
|
||||
{
|
||||
UNUSEDPARAM(cbdata);
|
||||
UNUSEDPARAM(suffix);
|
||||
UNUSEDPARAM(len);
|
||||
UNUSEDPARAM(regex);
|
||||
|
||||
ck_abort_msg("this pattern is not supposed to have a suffix");
|
||||
return CL_EMEM;
|
||||
}
|
||||
|
@ -53,9 +58,13 @@ static cl_error_t cb_fail(void *cbdata, const char *suffix, size_t len, const st
|
|||
static cl_error_t cb_expect_single(void *cbdata, const char *suffix, size_t len, const struct regex_list *regex)
|
||||
{
|
||||
const char *expected = cbdata;
|
||||
|
||||
UNUSEDPARAM(len);
|
||||
UNUSEDPARAM(regex);
|
||||
|
||||
cb_called++;
|
||||
ck_assert_msg(suffix && strcmp(suffix, expected) == 0,
|
||||
"suffix mismatch, was: %s, expected: %s\n", suffix, expected);
|
||||
"suffix mismatch, was: %s, expected: %s\n", suffix, expected);
|
||||
return CL_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -63,7 +72,7 @@ static struct regex_list regex;
|
|||
START_TEST(empty)
|
||||
{
|
||||
const char pattern[] = "";
|
||||
int rc;
|
||||
cl_error_t rc;
|
||||
regex_t *preg;
|
||||
|
||||
errmsg_expected();
|
||||
|
@ -79,13 +88,13 @@ END_TEST
|
|||
START_TEST(one)
|
||||
{
|
||||
char pattern[] = "a";
|
||||
int rc;
|
||||
cl_error_t rc;
|
||||
regex_t *preg;
|
||||
|
||||
preg = malloc(sizeof(*regex.preg));
|
||||
ck_assert_msg(!!preg, "malloc");
|
||||
rc = cli_regex2suffix(pattern, preg, cb_expect_single, pattern);
|
||||
ck_assert_msg(rc == 0, "single character pattern");
|
||||
ck_assert_msg(rc == CL_SUCCESS, "single character pattern");
|
||||
cli_regfree(preg);
|
||||
free(preg);
|
||||
ck_assert_msg(cb_called == 1, "callback should be called once");
|
||||
|
@ -104,12 +113,15 @@ static const char **tests[] = {
|
|||
static cl_error_t cb_expect_multi(void *cbdata, const char *suffix, size_t len, const struct regex_list *r)
|
||||
{
|
||||
const char **exp = cbdata;
|
||||
|
||||
UNUSEDPARAM(r);
|
||||
|
||||
ck_assert_msg(!!exp, "expected data");
|
||||
exp++;
|
||||
ck_assert_msg(!!*exp, "expected no suffix, got: %s\n", suffix);
|
||||
ck_assert_msg(!!exp[cb_called], "expected less suffixes, but already got: %d\n", cb_called);
|
||||
ck_assert_msg(strcmp(exp[cb_called], suffix) == 0,
|
||||
"suffix mismatch, was: %s, expected: %s\n", suffix, exp[cb_called]);
|
||||
"suffix mismatch, was: %s, expected: %s\n", suffix, exp[cb_called]);
|
||||
ck_assert_msg(strlen(suffix) == len, "incorrect suffix len, expected: %d, got: %d\n", strlen(suffix), len);
|
||||
cb_called++;
|
||||
return CL_SUCCESS;
|
||||
|
@ -117,7 +129,7 @@ static cl_error_t cb_expect_multi(void *cbdata, const char *suffix, size_t len,
|
|||
|
||||
START_TEST(test_suffix)
|
||||
{
|
||||
int rc;
|
||||
cl_error_t rc;
|
||||
regex_t *preg;
|
||||
const char *pattern = tests[_i][0];
|
||||
size_t n = 0;
|
||||
|
@ -127,13 +139,13 @@ START_TEST(test_suffix)
|
|||
preg = malloc(sizeof(*regex.preg));
|
||||
ck_assert_msg(!!preg, "malloc");
|
||||
rc = cli_regex2suffix(pattern, preg, cb_expect_multi, tests[_i]);
|
||||
ck_assert_msg(rc == 0, "single character pattern");
|
||||
ck_assert_msg(rc == CL_SUCCESS, "single character pattern");
|
||||
cli_regfree(preg);
|
||||
free(preg);
|
||||
p++;
|
||||
while (*p++) n++;
|
||||
ck_assert_msg(cb_called == n,
|
||||
"suffix number mismatch, expected: %d, was: %d\n", n, cb_called);
|
||||
"suffix number mismatch, expected: %d, was: %d\n", n, cb_called);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
|
@ -150,12 +162,12 @@ static struct regex_matcher matcher;
|
|||
|
||||
static void rsetup(void)
|
||||
{
|
||||
int rc;
|
||||
cl_error_t rc;
|
||||
#ifdef USE_MPOOL
|
||||
matcher.mempool = mpool_create();
|
||||
#endif
|
||||
rc = init_regex_list(&matcher, 1);
|
||||
ck_assert_msg(rc == 0, "init_regex_list");
|
||||
ck_assert_msg(rc == CL_SUCCESS, "init_regex_list");
|
||||
}
|
||||
|
||||
static void rteardown(void)
|
||||
|
@ -166,106 +178,112 @@ static void rteardown(void)
|
|||
#endif
|
||||
}
|
||||
|
||||
typedef enum rtest_result {
|
||||
RTR_PHISH,
|
||||
RTR_WHITELISTED,
|
||||
RTR_CLEAN,
|
||||
RTR_BLACKLISTED, // if 2nd db is loaded
|
||||
RTR_INVALID_REGEX
|
||||
} rtr_t;
|
||||
|
||||
static const struct rtest {
|
||||
const char *pattern; /* NULL if not meant for whitelist testing */
|
||||
const char *realurl;
|
||||
const char *displayurl;
|
||||
int result; /* 0 - phish, 1 - whitelisted, 2 - clean,
|
||||
3 - blacklisted if 2nd db is loaded,
|
||||
4 - invalid regex*/
|
||||
rtr_t result;
|
||||
} rtests[] = {
|
||||
{NULL, "http://fake.example.com", "http://foo@key.com/", 0},
|
||||
{NULL, "http://fake.example.com", "foo.example.com@key.com", 0},
|
||||
{NULL, "http://fake.example.com", "foo@key.com", 2},
|
||||
{NULL, "http://fake.example.com", "=====key.com", 0},
|
||||
{NULL, "http://key.com", "=====key.com", 2},
|
||||
{NULL, " http://key.com", "=====key.com", 2},
|
||||
{NULL, "http://key.com@fake.example.com", "key.com", 0},
|
||||
{NULL, " http://key.com@fake.example.com", "key.com", 0},
|
||||
{NULL, " http://key.com@fake.example.com ", "key.com", 0},
|
||||
{NULL, "http://fake.example.com", "http://foo@key.com/", RTR_PHISH},
|
||||
{NULL, "http://fake.example.com", "foo.example.com@key.com", RTR_PHISH},
|
||||
{NULL, "http://fake.example.com", "foo@key.com", RTR_CLEAN},
|
||||
{NULL, "http://fake.example.com", "=====key.com", RTR_PHISH},
|
||||
{NULL, "http://key.com", "=====key.com", RTR_CLEAN},
|
||||
{NULL, " http://key.com", "=====key.com", RTR_CLEAN},
|
||||
{NULL, "http://key.com@fake.example.com", "key.com", RTR_PHISH},
|
||||
{NULL, " http://key.com@fake.example.com", "key.com", RTR_PHISH},
|
||||
{NULL, " http://key.com@fake.example.com ", "key.com", RTR_PHISH},
|
||||
/* entry taken from .wdb with a / appended */
|
||||
{".+\\.ebayrtm\\.com([/?].*)?:.+\\.ebay\\.(de|com|co\\.uk)([/?].*)?/",
|
||||
"http://srx.main.ebayrtm.com",
|
||||
"pages.ebay.de",
|
||||
1 /* should be whitelisted */},
|
||||
RTR_WHITELISTED /* should be whitelisted */},
|
||||
{".+\\.ebayrtm\\.com([/?].*)?:.+\\.ebay\\.(de|com|co\\.uk)([/?].*)?/",
|
||||
"http://srx.main.ebayrtm.com.evil.example.com",
|
||||
"pages.ebay.de",
|
||||
0},
|
||||
RTR_PHISH},
|
||||
{".+\\.ebayrtm\\.com([/?].*)?:.+\\.ebay\\.(de|com|co\\.uk)([/?].*)?/",
|
||||
"www.www.ebayrtm.com?somecgi",
|
||||
"www.ebay.com/something", 1},
|
||||
"www.ebay.com/something", RTR_WHITELISTED},
|
||||
{NULL,
|
||||
"http://key.com", "go to key.com", 2},
|
||||
"http://key.com", "go to key.com", RTR_CLEAN},
|
||||
{":.+\\.paypal\\.(com|de|fr|it)([/?].*)?:.+\\.ebay\\.(at|be|ca|ch|co\\.uk|de|es|fr|ie|in|it|nl|ph|pl|com(\\.(au|cn|hk|my|sg))?)([/?].*)?/",
|
||||
"http://www.paypal.com", "pics.ebay.com", 1},
|
||||
{NULL, "http://somefakeurl.example.com", "someotherdomain-key.com", 2},
|
||||
{NULL, "http://somefakeurl.example.com", "someotherdomain.key.com", 0},
|
||||
{NULL, "http://1.test.example.com/something", "test", 3},
|
||||
{NULL, "http://1.test.example.com/2", "test", 3},
|
||||
{NULL, "http://user@1.test.example.com/2", "test", 3},
|
||||
{NULL, "http://user@1.test.example.com/2/test", "test", 3},
|
||||
{NULL, "http://user@1.test.example.com/", "test", 3},
|
||||
{NULL, "http://x.exe", "http:///x.exe", 2},
|
||||
"http://www.paypal.com", "pics.ebay.com", RTR_WHITELISTED},
|
||||
{NULL, "http://somefakeurl.example.com", "someotherdomain-key.com", RTR_CLEAN},
|
||||
{NULL, "http://somefakeurl.example.com", "someotherdomain.key.com", RTR_PHISH},
|
||||
{NULL, "http://1.test.example.com/something", "test", RTR_BLACKLISTED},
|
||||
{NULL, "http://1.test.example.com/2", "test", RTR_BLACKLISTED},
|
||||
{NULL, "http://user@1.test.example.com/2", "test", RTR_BLACKLISTED},
|
||||
{NULL, "http://user@1.test.example.com/2/test", "test", RTR_BLACKLISTED},
|
||||
{NULL, "http://user@1.test.example.com/", "test", RTR_BLACKLISTED},
|
||||
{NULL, "http://x.exe", "http:///x.exe", RTR_CLEAN},
|
||||
{".+\\.ebayrtm\\.com([/?].*)?:[^.]+\\.ebay\\.(de|com|co\\.uk)/",
|
||||
"http://srx.main.ebayrtm.com",
|
||||
"pages.ebay.de",
|
||||
1 /* should be whitelisted */},
|
||||
RTR_WHITELISTED /* should be whitelisted */},
|
||||
{".+\\.ebayrtm\\.com([/?].*)?:.+[r-t]\\.ebay\\.(de|com|co\\.uk)/",
|
||||
"http://srx.main.ebayrtm.com",
|
||||
"pages.ebay.de",
|
||||
1 /* should be whitelisted */},
|
||||
RTR_WHITELISTED /* should be whitelisted */},
|
||||
{".+\\.ebayrtm\\.com([/?].*)?:.+[r-t]\\.ebay\\.(de|com|co\\.uk)/",
|
||||
"http://srx.main.ebayrtm.com",
|
||||
"pages.ebay.de",
|
||||
1 /* should be whitelisted */},
|
||||
{"[t-", "", "", 4},
|
||||
{NULL, "http://co.uk", "http:// co.uk", 2},
|
||||
{NULL, "http://co.uk", " ", 2},
|
||||
{NULL, "127.0.0.1", "pages.ebay.de", 2},
|
||||
RTR_WHITELISTED /* should be whitelisted */},
|
||||
{"[t-", "", "", RTR_INVALID_REGEX},
|
||||
{NULL, "http://co.uk", "http:// co.uk", RTR_CLEAN},
|
||||
{NULL, "http://co.uk", " ", RTR_CLEAN},
|
||||
{NULL, "127.0.0.1", "pages.ebay.de", RTR_CLEAN},
|
||||
{".+\\.ebayrtm\\.com([/?].*)?:.+\\.ebay\\.(de|com|co\\.uk)([/?].*)?/",
|
||||
"http://pages.ebay.de@fake.example.com", "pages.ebay.de", 0},
|
||||
{NULL, "http://key.com", "https://key.com", 0},
|
||||
{NULL, "http://key.com%00fake.example.com", "https://key.com", 0},
|
||||
{NULL, "http://key.com.example.com", "key.com.invalid", 0}};
|
||||
"http://pages.ebay.de@fake.example.com", "pages.ebay.de", RTR_PHISH},
|
||||
{NULL, "http://key.com", "https://key.com", RTR_PHISH},
|
||||
{NULL, "http://key.com%00fake.example.com", "https://key.com", RTR_PHISH},
|
||||
{NULL, "http://key.com.example.com", "key.com.invalid", RTR_PHISH}};
|
||||
|
||||
START_TEST(regex_list_match_test)
|
||||
{
|
||||
const char *info;
|
||||
const struct rtest *rtest = &rtests[_i];
|
||||
char *pattern, *realurl;
|
||||
int rc;
|
||||
cl_error_t rc;
|
||||
|
||||
if (!rtest->pattern) {
|
||||
ck_assert_msg(rtest->result != 1,
|
||||
"whitelist test must have pattern set");
|
||||
ck_assert_msg(rtest->result != RTR_WHITELISTED,
|
||||
"whitelist test must have pattern set");
|
||||
/* this test entry is not meant for whitelist testing */
|
||||
return;
|
||||
}
|
||||
|
||||
ck_assert_msg(rtest->result == 0 || rtest->result == 1 || rtest->result == 4,
|
||||
"whitelist test result must be either 0 or 1 or 4");
|
||||
ck_assert_msg(rtest->result == RTR_PHISH || rtest->result == RTR_WHITELISTED || rtest->result == RTR_INVALID_REGEX,
|
||||
"whitelist test result must be either RTR_PHISH or RTR_WHITELISTED or RTR_INVALID_REGEX");
|
||||
pattern = cli_strdup(rtest->pattern);
|
||||
ck_assert_msg(!!pattern, "cli_strdup");
|
||||
|
||||
rc = regex_list_add_pattern(&matcher, pattern);
|
||||
if (rtest->result == 4) {
|
||||
if (rtest->result == RTR_INVALID_REGEX) {
|
||||
ck_assert_msg(rc, "regex_list_add_pattern should return error");
|
||||
free(pattern);
|
||||
return;
|
||||
} else
|
||||
ck_assert_msg(rc == 0, "regex_list_add_pattern");
|
||||
ck_assert_msg(rc == CL_SUCCESS, "regex_list_add_pattern");
|
||||
free(pattern);
|
||||
|
||||
matcher.list_loaded = 1;
|
||||
|
||||
rc = cli_build_regex_list(&matcher);
|
||||
ck_assert_msg(rc == 0, "cli_build_regex_list");
|
||||
ck_assert_msg(rc == CL_SUCCESS, "cli_build_regex_list");
|
||||
|
||||
ck_assert_msg(is_regex_ok(&matcher), "is_regex_ok");
|
||||
|
||||
realurl = cli_strdup(rtest->realurl);
|
||||
rc = regex_list_match(&matcher, realurl, rtest->displayurl, NULL, 1, &info, 1);
|
||||
realurl = cli_strdup(rtest->realurl);
|
||||
rc = regex_list_match(&matcher, realurl, rtest->displayurl, NULL, 1, &info, 1);
|
||||
ck_assert_msg(rc == rtest->result, "regex_list_match");
|
||||
/* regex_list_match is not supposed to modify realurl in this case */
|
||||
ck_assert_msg(!strcmp(realurl, rtest->realurl), "realurl altered");
|
||||
|
@ -279,7 +297,7 @@ static int loaded_2 = 0;
|
|||
static void psetup_impl(int load2)
|
||||
{
|
||||
FILE *f;
|
||||
int rc;
|
||||
cl_error_t rc;
|
||||
unsigned signo = 0;
|
||||
|
||||
engine = cl_engine_new();
|
||||
|
@ -289,13 +307,13 @@ static void psetup_impl(int load2)
|
|||
ck_assert_msg(!!engine->phishcheck, "phishing_init");
|
||||
|
||||
rc = init_domainlist(engine);
|
||||
ck_assert_msg(rc == 0, "init_domainlist");
|
||||
ck_assert_msg(rc == CL_SUCCESS, "init_domainlist");
|
||||
|
||||
f = fdopen(open_testfile("input/daily.pdb"), "r");
|
||||
ck_assert_msg(!!f, "fopen daily.pdb");
|
||||
|
||||
rc = load_regex_matcher(engine, engine->domainlist_matcher, f, &signo, 0, 0, NULL, 1);
|
||||
ck_assert_msg(rc == 0, "load_regex_matcher");
|
||||
ck_assert_msg(rc == CL_SUCCESS, "load_regex_matcher");
|
||||
fclose(f);
|
||||
|
||||
ck_assert_msg(signo == 201, "Incorrect number of signatures: %u, expected %u", signo, 201);
|
||||
|
@ -306,7 +324,7 @@ static void psetup_impl(int load2)
|
|||
|
||||
signo = 0;
|
||||
rc = load_regex_matcher(engine, engine->domainlist_matcher, f, &signo, 0, 0, NULL, 1);
|
||||
ck_assert_msg(rc == 0, "load_regex_matcher");
|
||||
ck_assert_msg(rc == CL_SUCCESS, "load_regex_matcher");
|
||||
fclose(f);
|
||||
|
||||
ck_assert_msg(signo == 4, "Incorrect number of signatures: %u, expected %u", signo, 4);
|
||||
|
@ -314,21 +332,21 @@ static void psetup_impl(int load2)
|
|||
loaded_2 = load2;
|
||||
|
||||
rc = init_whitelist(engine);
|
||||
ck_assert_msg(rc == 0, "init_whitelist");
|
||||
ck_assert_msg(rc == CL_SUCCESS, "init_whitelist");
|
||||
|
||||
f = fdopen(open_testfile("input/daily.wdb"), "r");
|
||||
signo = 0;
|
||||
rc = load_regex_matcher(engine, engine->whitelist_matcher, f, &signo, 0, 1, NULL, 1);
|
||||
ck_assert_msg(rc == 0, "load_regex_matcher");
|
||||
ck_assert_msg(rc == CL_SUCCESS, "load_regex_matcher");
|
||||
fclose(f);
|
||||
|
||||
ck_assert_msg(signo == 31, "Incorrect number of signatures: %u, expected %u", signo, 31);
|
||||
|
||||
rc = cli_build_regex_list(engine->whitelist_matcher);
|
||||
ck_assert_msg(rc == 0, "cli_build_regex_list");
|
||||
ck_assert_msg(rc == CL_SUCCESS, "cli_build_regex_list");
|
||||
|
||||
rc = cli_build_regex_list(engine->domainlist_matcher);
|
||||
ck_assert_msg(rc == 0, "cli_build_regex_list");
|
||||
ck_assert_msg(rc == CL_SUCCESS, "cli_build_regex_list");
|
||||
|
||||
ck_assert_msg(is_regex_ok(engine->whitelist_matcher), "is_regex_ok");
|
||||
ck_assert_msg(is_regex_ok(engine->domainlist_matcher), "is_regex_ok");
|
||||
|
@ -359,7 +377,7 @@ static void do_phishing_test(const struct rtest *rtest)
|
|||
struct cl_scan_options options;
|
||||
const char *virname = NULL;
|
||||
tag_arguments_t hrefs;
|
||||
int rc;
|
||||
cl_error_t rc;
|
||||
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
memset(&options, 0, sizeof(struct cl_scan_options));
|
||||
|
@ -387,35 +405,39 @@ static void do_phishing_test(const struct rtest *rtest)
|
|||
html_tag_arg_free(&hrefs);
|
||||
ck_assert_msg(rc == CL_CLEAN, "phishingScan");
|
||||
switch (rtest->result) {
|
||||
case 0:
|
||||
case RTR_PHISH:
|
||||
ck_assert_msg(ctx.found_possibly_unwanted,
|
||||
"this should be phishing, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
"this should be phishing, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
break;
|
||||
case 1:
|
||||
case RTR_WHITELISTED:
|
||||
ck_assert_msg(!ctx.found_possibly_unwanted,
|
||||
"this should be whitelisted, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
"this should be whitelisted, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
break;
|
||||
case 2:
|
||||
case RTR_CLEAN:
|
||||
ck_assert_msg(!ctx.found_possibly_unwanted,
|
||||
"this should be clean, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
"this should be clean, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
break;
|
||||
case 3:
|
||||
case RTR_BLACKLISTED:
|
||||
if (!loaded_2)
|
||||
ck_assert_msg(!ctx.found_possibly_unwanted,
|
||||
"this should be clean, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
"this should be clean, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
else {
|
||||
ck_assert_msg(ctx.found_possibly_unwanted,
|
||||
"this should be blacklisted, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
if (*ctx.virname)
|
||||
ck_assert_msg(!strstr((const char *)*ctx.virname, "Blacklisted"),
|
||||
"should be blacklisted, but is: %s\n", ctx.virname);
|
||||
"this should be blacklisted, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
if (*ctx.virname) {
|
||||
char *phishingFound = strstr((const char *)*ctx.virname, "Heuristics.Safebrowsing.Suspected-malware_safebrowsing.clamav.net");
|
||||
ck_assert_msg(phishingFound != NULL, "\n\t should be: Heuristics.Safebrowsing.Suspected-malware_safebrowsing.clamav.net,\n\t but is: %s\n", *ctx.virname);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RTR_INVALID_REGEX:
|
||||
/* don't worry about it, this was tested in regex_list_match_test() */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -425,7 +447,7 @@ static void do_phishing_test_allscan(const struct rtest *rtest)
|
|||
cli_ctx ctx;
|
||||
const char *virname = NULL;
|
||||
tag_arguments_t hrefs;
|
||||
int rc;
|
||||
cl_error_t rc;
|
||||
struct cl_scan_options options;
|
||||
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
|
@ -453,37 +475,51 @@ static void do_phishing_test_allscan(const struct rtest *rtest)
|
|||
rc = phishingScan(&ctx, &hrefs);
|
||||
|
||||
html_tag_arg_free(&hrefs);
|
||||
ck_assert_msg(rc == CL_CLEAN, "phishingScan");
|
||||
if (rtest->result == RTR_PHISH || (loaded_2 != 0 && rtest->result == RTR_BLACKLISTED)) {
|
||||
ck_assert_msg(rc == CL_VIRUS, "phishingScan returned \"%s\", expected \"%s\". \n\trealURL: %s \n\tdisplayURL: %s",
|
||||
cl_strerror(rc),
|
||||
cl_strerror(CL_VIRUS),
|
||||
rtest->realurl, rtest->displayurl);
|
||||
} else {
|
||||
ck_assert_msg(rc == CL_CLEAN, "phishingScan returned \"%s\", expected \"%s\". \n\trealURL: %s \n\tdisplayURL: %s",
|
||||
cl_strerror(rc),
|
||||
cl_strerror(CL_CLEAN),
|
||||
rtest->realurl, rtest->displayurl);
|
||||
}
|
||||
switch (rtest->result) {
|
||||
case 0:
|
||||
case RTR_PHISH:
|
||||
ck_assert_msg(ctx.num_viruses,
|
||||
"this should be phishing, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
"this should be phishing, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
break;
|
||||
case 1:
|
||||
case RTR_WHITELISTED:
|
||||
ck_assert_msg(!ctx.num_viruses,
|
||||
"this should be whitelisted, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
"this should be whitelisted, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
break;
|
||||
case 2:
|
||||
case RTR_CLEAN:
|
||||
ck_assert_msg(!ctx.num_viruses,
|
||||
"this should be clean, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
"this should be clean, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
break;
|
||||
case 3:
|
||||
case RTR_BLACKLISTED:
|
||||
if (!loaded_2)
|
||||
ck_assert_msg(!ctx.num_viruses,
|
||||
"this should be clean, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
"this should be clean, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
else {
|
||||
ck_assert_msg(ctx.num_viruses,
|
||||
"this should be blacklisted, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
if (*ctx.virname)
|
||||
ck_assert_msg(!strstr((const char *)*ctx.virname, "Blacklisted"),
|
||||
"should be blacklisted, but is: %s\n", ctx.virname);
|
||||
"this should be blacklisted, realURL: %s, displayURL: %s",
|
||||
rtest->realurl, rtest->displayurl);
|
||||
if (*ctx.virname) {
|
||||
char *phishingFound = strstr((const char *)*ctx.virname, "Heuristics.Safebrowsing.Suspected-malware_safebrowsing.clamav.net");
|
||||
ck_assert_msg(phishingFound != NULL, "\n\t should be: Heuristics.Safebrowsing.Suspected-malware_safebrowsing.clamav.net,\n\t but is: %s\n", *ctx.virname);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RTR_INVALID_REGEX:
|
||||
/* don't worry about it, this was tested in regex_list_match_test() */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -567,7 +603,7 @@ START_TEST(phishing_fake_test)
|
|||
ck_assert_msg(!!pdb, "missing : in pdb");
|
||||
rtest.realurl = pdb;
|
||||
rtest.displayurl = pdb;
|
||||
rtest.result = 2;
|
||||
rtest.result = RTR_CLEAN;
|
||||
do_phishing_test(&rtest);
|
||||
rtest.realurl = "http://fake.example.com";
|
||||
rtest.result = 0;
|
||||
|
@ -588,7 +624,7 @@ START_TEST(phishing_fake_test_allscan)
|
|||
ck_assert_msg(!!pdb, "missing : in pdb");
|
||||
rtest.realurl = pdb;
|
||||
rtest.displayurl = pdb;
|
||||
rtest.result = 2;
|
||||
rtest.result = RTR_CLEAN;
|
||||
do_phishing_test_allscan(&rtest);
|
||||
rtest.realurl = "http://fake.example.com";
|
||||
rtest.result = 0;
|
||||
|
@ -639,7 +675,6 @@ Suite *test_regex_suite(void)
|
|||
|
||||
tcase_add_loop_test(tc_phish, test_url_canon, 0, sizeof(uc) / sizeof(uc[0]));
|
||||
|
||||
|
||||
tc_regex = tcase_create("cli_regcomp/execute");
|
||||
suite_add_tcase(s, tc_regex);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue