|
|
|
|
/* Copyright (C) 2007-2012 Open Information Security Foundation
|
|
|
|
|
*
|
|
|
|
|
* You can copy, redistribute or modify this Program under the terms of
|
|
|
|
|
* the GNU General Public License version 2 as published by the Free
|
|
|
|
|
* Software Foundation.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* version 2 along with this program; if not, write to the Free Software
|
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
|
* 02110-1301, USA.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \file
|
|
|
|
|
*
|
|
|
|
|
* \author Victor Julien <victor@inliniac.net>
|
|
|
|
|
* \author Pablo Rincon <pablo.rincon.crespo@gmail.com>
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "suricata-common.h"
|
|
|
|
|
#include "suricata.h"
|
|
|
|
|
#include "debug.h"
|
|
|
|
|
#include "flow.h"
|
|
|
|
|
#include "stream.h"
|
|
|
|
|
#include "util-hash.h"
|
|
|
|
|
#include "util-debug.h"
|
|
|
|
|
#include "util-memcmp.h"
|
|
|
|
|
#include "util-print.h"
|
|
|
|
|
#include "app-layer-parser.h"
|
|
|
|
|
#include "util-validate.h"
|
|
|
|
|
|
|
|
|
|
/** \brief switch to force magic checks on all files
|
|
|
|
|
* regardless of the rules.
|
|
|
|
|
*/
|
|
|
|
|
static int g_file_force_magic = 0;
|
|
|
|
|
|
|
|
|
|
/** \brief switch to force md5 calculation on all files
|
|
|
|
|
* regardless of the rules.
|
|
|
|
|
*/
|
|
|
|
|
static int g_file_force_md5 = 0;
|
|
|
|
|
|
|
|
|
|
/** \brief switch to force tracking off all files
|
|
|
|
|
* regardless of the rules.
|
|
|
|
|
*/
|
|
|
|
|
static int g_file_force_tracking = 0;
|
|
|
|
|
|
|
|
|
|
/* prototypes */
|
|
|
|
|
static void FileFree(File *);
|
|
|
|
|
static void FileDataFree(FileData *);
|
|
|
|
|
|
|
|
|
|
void FileForceMagicEnable(void) {
|
|
|
|
|
g_file_force_magic = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FileForceMd5Enable(void) {
|
|
|
|
|
g_file_force_md5 = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int FileForceMagic(void) {
|
|
|
|
|
return g_file_force_magic;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int FileForceMd5(void) {
|
|
|
|
|
return g_file_force_md5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FileForceTrackingEnable(void) {
|
|
|
|
|
g_file_force_tracking = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int FileMagicSize(void) {
|
|
|
|
|
/** \todo make this size configurable */
|
|
|
|
|
return 512;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int FileAppendFileDataFilePtr(File *ff, FileData *ffd) {
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
if (ff == NULL) {
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ff->chunks_tail == NULL) {
|
|
|
|
|
ff->chunks_head = ffd;
|
|
|
|
|
ff->chunks_tail = ffd;
|
|
|
|
|
} else {
|
|
|
|
|
ff->chunks_tail->next = ffd;
|
|
|
|
|
ff->chunks_tail = ffd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
|
ff->chunks_cnt++;
|
|
|
|
|
if (ff->chunks_cnt > ff->chunks_cnt_max)
|
|
|
|
|
ff->chunks_cnt_max = ff->chunks_cnt;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_NSS
|
|
|
|
|
if (ff->md5_ctx)
|
|
|
|
|
HASH_Update(ff->md5_ctx, ffd->data, ffd->len);
|
|
|
|
|
#endif
|
|
|
|
|
SCReturnInt(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int FileAppendFileData(FileContainer *ffc, FileData *ffd) {
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
if (ffc == NULL) {
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (FileAppendFileDataFilePtr(ffc->tail, ffd) == -1)
|
|
|
|
|
{
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCReturnInt(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void FilePruneFile(File *file) {
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
SCLogDebug("file %p, file->chunks_cnt %"PRIu64, file, file->chunks_cnt);
|
|
|
|
|
|
|
|
|
|
if (!(file->flags & FILE_NOMAGIC)) {
|
|
|
|
|
/* need magic but haven't set it yet, bail out */
|
|
|
|
|
if (file->magic == NULL)
|
|
|
|
|
SCReturn;
|
|
|
|
|
else
|
|
|
|
|
SCLogDebug("file->magic %s", file->magic);
|
|
|
|
|
} else {
|
|
|
|
|
SCLogDebug("file->flags & FILE_NOMAGIC == true");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* okay, we now know we can prune */
|
|
|
|
|
FileData *fd = file->chunks_head;
|
|
|
|
|
|
|
|
|
|
while (fd != NULL) {
|
|
|
|
|
SCLogDebug("fd %p", fd);
|
|
|
|
|
|
|
|
|
|
if (file->flags & FILE_NOSTORE || fd->stored == 1) {
|
|
|
|
|
file->chunks_head = fd->next;
|
|
|
|
|
if (file->chunks_tail == fd)
|
|
|
|
|
file->chunks_tail = fd->next;
|
|
|
|
|
|
|
|
|
|
FileDataFree(fd);
|
|
|
|
|
|
|
|
|
|
fd = file->chunks_head;
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
|
file->chunks_cnt--;
|
|
|
|
|
SCLogDebug("file->chunks_cnt %"PRIu64, file->chunks_cnt);
|
|
|
|
|
#endif
|
|
|
|
|
} else if (fd->stored == 0) {
|
|
|
|
|
fd = NULL;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCReturn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FilePrune(FileContainer *ffc) {
|
|
|
|
|
File *file;
|
|
|
|
|
|
|
|
|
|
for (file = ffc->head; file != NULL; file = file->next) {
|
|
|
|
|
FilePruneFile(file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief allocate a FileContainer
|
|
|
|
|
*
|
|
|
|
|
* \retval new newly allocated FileContainer
|
|
|
|
|
* \retval NULL error
|
|
|
|
|
*/
|
|
|
|
|
FileContainer *FileContainerAlloc(void) {
|
|
|
|
|
FileContainer *new = SCMalloc(sizeof(FileContainer));
|
|
|
|
|
if (unlikely(new == NULL)) {
|
|
|
|
|
SCLogError(SC_ERR_MEM_ALLOC, "Error allocating mem");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
memset(new, 0, sizeof(FileContainer));
|
|
|
|
|
new->head = new->tail = NULL;
|
|
|
|
|
return new;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief Recycle a FileContainer
|
|
|
|
|
*
|
|
|
|
|
* \param ffc FileContainer
|
|
|
|
|
*/
|
|
|
|
|
void FileContainerRecycle(FileContainer *ffc) {
|
|
|
|
|
if (ffc == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
File *cur = ffc->head;
|
|
|
|
|
File *next = NULL;
|
|
|
|
|
for (;cur != NULL; cur = next) {
|
|
|
|
|
next = cur->next;
|
|
|
|
|
FileFree(cur);
|
|
|
|
|
}
|
|
|
|
|
ffc->head = ffc->tail = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief Free a FileContainer
|
|
|
|
|
*
|
|
|
|
|
* \param ffc FileContainer
|
|
|
|
|
*/
|
|
|
|
|
void FileContainerFree(FileContainer *ffc) {
|
|
|
|
|
if (ffc == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
File *ptr = ffc->head;
|
|
|
|
|
File *next = NULL;
|
|
|
|
|
for (;ptr != NULL; ptr = next) {
|
|
|
|
|
next = ptr->next;
|
|
|
|
|
FileFree(ptr);
|
|
|
|
|
}
|
|
|
|
|
ffc->head = ffc->tail = NULL;
|
|
|
|
|
SCFree(ffc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \internal
|
|
|
|
|
*
|
|
|
|
|
* \brief allocate a FileData chunk and set it up
|
|
|
|
|
*
|
|
|
|
|
* \param data data chunk to store in the FileData
|
|
|
|
|
* \param data_len lenght of the data
|
|
|
|
|
*
|
|
|
|
|
* \retval new FileData object
|
|
|
|
|
*/
|
|
|
|
|
static FileData *FileDataAlloc(uint8_t *data, uint32_t data_len) {
|
|
|
|
|
FileData *new = SCMalloc(sizeof(FileData));
|
|
|
|
|
if (unlikely(new == NULL)) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
memset(new, 0, sizeof(FileData));
|
|
|
|
|
|
|
|
|
|
new->data = SCMalloc(data_len);
|
|
|
|
|
if (new->data == NULL) {
|
|
|
|
|
SCFree(new);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new->len = data_len;
|
|
|
|
|
memcpy(new->data, data, data_len);
|
|
|
|
|
|
|
|
|
|
new->next = NULL;
|
|
|
|
|
return new;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \internal
|
|
|
|
|
*
|
|
|
|
|
* \brief free a FileData object
|
|
|
|
|
*
|
|
|
|
|
* \param ffd the flow file data object to free
|
|
|
|
|
*/
|
|
|
|
|
static void FileDataFree(FileData *ffd) {
|
|
|
|
|
if (ffd == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (ffd->data != NULL) {
|
|
|
|
|
SCFree(ffd->data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCFree(ffd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief Alloc a new File
|
|
|
|
|
*
|
|
|
|
|
* \param name character array containing the name (not a string)
|
|
|
|
|
* \param name_len length in bytes of the name
|
|
|
|
|
*
|
|
|
|
|
* \retval new File object or NULL on error
|
|
|
|
|
*/
|
|
|
|
|
static File *FileAlloc(uint8_t *name, uint16_t name_len) {
|
|
|
|
|
File *new = SCMalloc(sizeof(File));
|
|
|
|
|
if (unlikely(new == NULL)) {
|
|
|
|
|
SCLogError(SC_ERR_MEM_ALLOC, "Error allocating mem");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
memset(new, 0, sizeof(File));
|
|
|
|
|
|
|
|
|
|
new->name = SCMalloc(name_len);
|
|
|
|
|
if (new->name == NULL) {
|
|
|
|
|
SCFree(new);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new->name_len = name_len;
|
|
|
|
|
memcpy(new->name, name, name_len);
|
|
|
|
|
|
|
|
|
|
return new;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void FileFree(File *ff) {
|
|
|
|
|
if (ff == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (ff->name != NULL)
|
|
|
|
|
SCFree(ff->name);
|
|
|
|
|
|
|
|
|
|
/* magic returned by libmagic is strdup'd by MagicLookup. */
|
|
|
|
|
if (ff->magic != NULL)
|
|
|
|
|
SCFree(ff->magic);
|
|
|
|
|
|
|
|
|
|
if (ff->chunks_head != NULL) {
|
|
|
|
|
FileData *ffd = ff->chunks_head;
|
|
|
|
|
|
|
|
|
|
while (ffd != NULL) {
|
|
|
|
|
FileData *next_ffd = ffd->next;
|
|
|
|
|
FileDataFree(ffd);
|
|
|
|
|
ffd = next_ffd;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_NSS
|
|
|
|
|
if (ff->md5_ctx)
|
|
|
|
|
HASH_Destroy(ff->md5_ctx);
|
|
|
|
|
#endif
|
|
|
|
|
SCLogDebug("ff chunks_cnt %"PRIu64", chunks_cnt_max %"PRIu64,
|
|
|
|
|
ff->chunks_cnt, ff->chunks_cnt_max);
|
|
|
|
|
SCFree(ff);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FileContainerAdd(FileContainer *ffc, File *ff) {
|
|
|
|
|
if (ffc->head == NULL || ffc->tail == NULL) {
|
|
|
|
|
ffc->head = ffc->tail = ff;
|
|
|
|
|
} else {
|
|
|
|
|
ffc->tail->next = ff;
|
|
|
|
|
ffc->tail = ff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief Tag a file for storing
|
|
|
|
|
*
|
|
|
|
|
* \param ff The file to store
|
|
|
|
|
*/
|
|
|
|
|
int FileStore(File *ff) {
|
|
|
|
|
ff->flags |= FILE_STORE;
|
|
|
|
|
SCReturnInt(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief Set the TX id for a file
|
|
|
|
|
*
|
|
|
|
|
* \param ff The file to store
|
|
|
|
|
* \param txid the tx id
|
|
|
|
|
*/
|
|
|
|
|
int FileSetTx(File *ff, uint64_t txid) {
|
|
|
|
|
SCLogDebug("ff %p txid %"PRIu64, ff, txid);
|
|
|
|
|
if (ff != NULL)
|
|
|
|
|
ff->txid = txid;
|
|
|
|
|
SCReturnInt(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief check if we have stored enough
|
|
|
|
|
*
|
|
|
|
|
* \param ff file
|
|
|
|
|
*
|
|
|
|
|
* \retval 0 limit not reached yet
|
|
|
|
|
* \retval 1 limit reached
|
|
|
|
|
*/
|
|
|
|
|
static int FileStoreNoStoreCheck(File *ff) {
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
if (ff == NULL) {
|
|
|
|
|
SCReturnInt(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ff->flags & FILE_NOSTORE) {
|
|
|
|
|
if (ff->state == FILE_STATE_OPENED &&
|
|
|
|
|
ff->size >= (uint64_t)FileMagicSize())
|
|
|
|
|
{
|
|
|
|
|
SCReturnInt(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCReturnInt(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief Store a chunk of file data in the flow. The open "flowfile"
|
|
|
|
|
* will be used.
|
|
|
|
|
*
|
|
|
|
|
* \param ffc the container
|
|
|
|
|
* \param data data chunk
|
|
|
|
|
* \param data_len data chunk len
|
|
|
|
|
*
|
|
|
|
|
* \retval 0 ok
|
|
|
|
|
* \retval -1 error
|
|
|
|
|
* \retval -2 no store for this file
|
|
|
|
|
*/
|
|
|
|
|
int FileAppendData(FileContainer *ffc, uint8_t *data, uint32_t data_len) {
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
if (ffc == NULL || ffc->tail == NULL || data == NULL || data_len == 0) {
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ffc->tail->state != FILE_STATE_OPENED) {
|
|
|
|
|
if (ffc->tail->flags & FILE_NOSTORE) {
|
|
|
|
|
SCReturnInt(-2);
|
|
|
|
|
}
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ffc->tail->size += data_len;
|
|
|
|
|
SCLogDebug("file size is now %"PRIu64, ffc->tail->size);
|
|
|
|
|
|
|
|
|
|
if (FileStoreNoStoreCheck(ffc->tail) == 1) {
|
|
|
|
|
#ifdef HAVE_NSS
|
|
|
|
|
/* no storage but forced md5 */
|
|
|
|
|
if (ffc->tail->md5_ctx) {
|
|
|
|
|
if (ffc->tail->md5_ctx)
|
|
|
|
|
HASH_Update(ffc->tail->md5_ctx, data, data_len);
|
|
|
|
|
|
|
|
|
|
SCReturnInt(0);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
if (g_file_force_tracking || (!(ffc->tail->flags & FILE_NOTRACK)))
|
|
|
|
|
SCReturnInt(0);
|
|
|
|
|
|
|
|
|
|
ffc->tail->state = FILE_STATE_TRUNCATED;
|
|
|
|
|
SCLogDebug("flowfile state transitioned to FILE_STATE_TRUNCATED");
|
|
|
|
|
SCReturnInt(-2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCLogDebug("appending %"PRIu32" bytes", data_len);
|
|
|
|
|
|
|
|
|
|
FileData *ffd = FileDataAlloc(data, data_len);
|
|
|
|
|
if (ffd == NULL) {
|
|
|
|
|
ffc->tail->state = FILE_STATE_ERROR;
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* append the data */
|
|
|
|
|
if (FileAppendFileData(ffc, ffd) < 0) {
|
|
|
|
|
ffc->tail->state = FILE_STATE_ERROR;
|
|
|
|
|
FileDataFree(ffd);
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
SCReturnInt(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief Open a new File
|
|
|
|
|
*
|
|
|
|
|
* \param ffc flow container
|
|
|
|
|
* \param name filename character array
|
|
|
|
|
* \param name_len filename len
|
|
|
|
|
* \param data initial data
|
|
|
|
|
* \param data_len initial data len
|
|
|
|
|
* \param flags open flags
|
|
|
|
|
*
|
|
|
|
|
* \retval ff flowfile object
|
|
|
|
|
*
|
|
|
|
|
* \note filename is not a string, so it's not nul terminated.
|
|
|
|
|
*/
|
|
|
|
|
File *FileOpenFile(FileContainer *ffc, uint8_t *name,
|
|
|
|
|
uint16_t name_len, uint8_t *data, uint32_t data_len, uint8_t flags)
|
|
|
|
|
{
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
//PrintRawDataFp(stdout, name, name_len);
|
|
|
|
|
|
|
|
|
|
File *ff = FileAlloc(name, name_len);
|
|
|
|
|
if (ff == NULL) {
|
|
|
|
|
SCReturnPtr(NULL, "File");
|
|
|
|
|
}
|
|
|
|
|
|
file handling: expand filestore keyword
Filestore keyword by default (... filestore; ... ) marks only the file in the
same direction as the rule match for storing. This makes sense when inspecting
individual files (filemagic, filename, etc) but not so much when looking at
suspicious file requests, where the actual file is in the response.
The filestore keyword now takes 2 optional options:
filestore:<direction>,<scope>;
By default the direction is "same as rule match", and scope is "currently
inspected file".
For direction the following values are possible: "request" and "to_server",
"response" and "to_client", "both".
For scope the following values are possible: "tx" for all files in the current
HTTP/1.1 transation, "ssn" and "flow" for all files in the session/flow.
For the above case, where a suspious request should lead to a response file
download, this would work:
alert http ... content:"/suspicious/"; http_uri; filestore:response; ...
15 years ago
|
|
|
if (flags & FILE_STORE) {
|
|
|
|
|
ff->flags |= FILE_STORE;
|
file handling: expand filestore keyword
Filestore keyword by default (... filestore; ... ) marks only the file in the
same direction as the rule match for storing. This makes sense when inspecting
individual files (filemagic, filename, etc) but not so much when looking at
suspicious file requests, where the actual file is in the response.
The filestore keyword now takes 2 optional options:
filestore:<direction>,<scope>;
By default the direction is "same as rule match", and scope is "currently
inspected file".
For direction the following values are possible: "request" and "to_server",
"response" and "to_client", "both".
For scope the following values are possible: "tx" for all files in the current
HTTP/1.1 transation, "ssn" and "flow" for all files in the session/flow.
For the above case, where a suspious request should lead to a response file
download, this would work:
alert http ... content:"/suspicious/"; http_uri; filestore:response; ...
15 years ago
|
|
|
} else if (flags & FILE_NOSTORE) {
|
|
|
|
|
SCLogDebug("not storing this file");
|
|
|
|
|
ff->flags |= FILE_NOSTORE;
|
|
|
|
|
}
|
|
|
|
|
if (flags & FILE_NOMAGIC) {
|
|
|
|
|
SCLogDebug("not doing magic for this file");
|
|
|
|
|
ff->flags |= FILE_NOMAGIC;
|
|
|
|
|
}
|
|
|
|
|
if (flags & FILE_NOMD5) {
|
|
|
|
|
SCLogDebug("not doing md5 for this file");
|
|
|
|
|
ff->flags |= FILE_NOMD5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_NSS
|
|
|
|
|
if (!(ff->flags & FILE_NOMD5) || g_file_force_md5) {
|
|
|
|
|
ff->md5_ctx = HASH_Create(HASH_AlgMD5);
|
|
|
|
|
if (ff->md5_ctx != NULL) {
|
|
|
|
|
HASH_Begin(ff->md5_ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
ff->state = FILE_STATE_OPENED;
|
|
|
|
|
SCLogDebug("flowfile state transitioned to FILE_STATE_OPENED");
|
|
|
|
|
|
|
|
|
|
FileContainerAdd(ffc, ff);
|
|
|
|
|
|
|
|
|
|
if (data != NULL) {
|
|
|
|
|
//PrintRawDataFp(stdout, data, data_len);
|
|
|
|
|
ff->size += data_len;
|
|
|
|
|
SCLogDebug("file size is now %"PRIu64, ff->size);
|
|
|
|
|
|
|
|
|
|
FileData *ffd = FileDataAlloc(data, data_len);
|
|
|
|
|
if (ffd == NULL) {
|
|
|
|
|
ff->state = FILE_STATE_ERROR;
|
|
|
|
|
SCReturnPtr(NULL, "File");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* append the data */
|
|
|
|
|
if (FileAppendFileData(ffc, ffd) < 0) {
|
|
|
|
|
ff->state = FILE_STATE_ERROR;
|
|
|
|
|
FileDataFree(ffd);
|
|
|
|
|
SCReturnPtr(NULL, "File");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCReturnPtr(ff, "File");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int FileCloseFilePtr(File *ff, uint8_t *data,
|
|
|
|
|
uint32_t data_len, uint8_t flags)
|
|
|
|
|
{
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
if (ff == NULL) {
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ff->state != FILE_STATE_OPENED) {
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ff->size += data_len;
|
|
|
|
|
SCLogDebug("file size is now %"PRIu64, ff->size);
|
|
|
|
|
|
|
|
|
|
if (data != NULL) {
|
|
|
|
|
//PrintRawDataFp(stdout, data, data_len);
|
|
|
|
|
|
|
|
|
|
if (ff->flags & FILE_NOSTORE) {
|
|
|
|
|
#ifdef HAVE_NSS
|
|
|
|
|
/* no storage but md5 */
|
|
|
|
|
if (ff->md5_ctx)
|
|
|
|
|
HASH_Update(ff->md5_ctx, data, data_len);
|
|
|
|
|
#endif
|
|
|
|
|
} else {
|
|
|
|
|
FileData *ffd = FileDataAlloc(data, data_len);
|
|
|
|
|
if (ffd == NULL) {
|
|
|
|
|
ff->state = FILE_STATE_ERROR;
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* append the data */
|
|
|
|
|
if (FileAppendFileDataFilePtr(ff, ffd) < 0) {
|
|
|
|
|
ff->state = FILE_STATE_ERROR;
|
|
|
|
|
FileDataFree(ffd);
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flags & FILE_TRUNCATED) {
|
|
|
|
|
ff->state = FILE_STATE_TRUNCATED;
|
|
|
|
|
SCLogDebug("flowfile state transitioned to FILE_STATE_TRUNCATED");
|
|
|
|
|
|
|
|
|
|
if (flags & FILE_NOSTORE) {
|
|
|
|
|
SCLogDebug("not storing this file");
|
|
|
|
|
ff->flags |= FILE_NOSTORE;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
ff->state = FILE_STATE_CLOSED;
|
|
|
|
|
SCLogDebug("flowfile state transitioned to FILE_STATE_CLOSED");
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_NSS
|
|
|
|
|
if (ff->md5_ctx) {
|
|
|
|
|
unsigned int len = 0;
|
|
|
|
|
HASH_End(ff->md5_ctx, ff->md5, &len, sizeof(ff->md5));
|
|
|
|
|
ff->flags |= FILE_MD5;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCReturnInt(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief Close a File
|
|
|
|
|
*
|
|
|
|
|
* \param ffc the container
|
|
|
|
|
* \param data final data if any
|
|
|
|
|
* \param data_len data len if any
|
|
|
|
|
* \param flags flags
|
|
|
|
|
*
|
|
|
|
|
* \retval 0 ok
|
|
|
|
|
* \retval -1 error
|
|
|
|
|
*/
|
|
|
|
|
int FileCloseFile(FileContainer *ffc, uint8_t *data,
|
|
|
|
|
uint32_t data_len, uint8_t flags)
|
|
|
|
|
{
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
if (ffc == NULL || ffc->tail == NULL) {
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (FileCloseFilePtr(ffc->tail, data, data_len, flags) == -1) {
|
|
|
|
|
SCReturnInt(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCReturnInt(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief disable file storage for a flow
|
|
|
|
|
*
|
|
|
|
|
* \param f *LOCKED* flow
|
|
|
|
|
* \param direction flow direction
|
|
|
|
|
*/
|
|
|
|
|
void FileDisableStoring(Flow *f, uint8_t direction) {
|
|
|
|
|
File *ptr = NULL;
|
|
|
|
|
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
DEBUG_ASSERT_FLOW_LOCKED(f);
|
|
|
|
|
|
|
|
|
|
if (direction == STREAM_TOSERVER)
|
|
|
|
|
f->flags |= FLOW_FILE_NO_STORE_TS;
|
|
|
|
|
else
|
|
|
|
|
f->flags |= FLOW_FILE_NO_STORE_TC;
|
|
|
|
|
|
|
|
|
|
FileContainer *ffc = AppLayerGetFilesFromFlow(f, direction);
|
|
|
|
|
if (ffc != NULL) {
|
|
|
|
|
for (ptr = ffc->head; ptr != NULL; ptr = ptr->next) {
|
|
|
|
|
/* if we're already storing, we'll continue */
|
|
|
|
|
if (!(ptr->flags & FILE_STORE)) {
|
|
|
|
|
SCLogDebug("not storing this file");
|
|
|
|
|
ptr->flags |= FILE_NOSTORE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SCReturn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief disable file magic lookups for this flow
|
|
|
|
|
*
|
|
|
|
|
* \param f *LOCKED* flow
|
|
|
|
|
* \param direction flow direction
|
|
|
|
|
*/
|
|
|
|
|
void FileDisableMagic(Flow *f, uint8_t direction) {
|
|
|
|
|
File *ptr = NULL;
|
|
|
|
|
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
DEBUG_ASSERT_FLOW_LOCKED(f);
|
|
|
|
|
|
|
|
|
|
if (direction == STREAM_TOSERVER)
|
|
|
|
|
f->flags |= FLOW_FILE_NO_MAGIC_TS;
|
|
|
|
|
else
|
|
|
|
|
f->flags |= FLOW_FILE_NO_MAGIC_TC;
|
|
|
|
|
|
|
|
|
|
FileContainer *ffc = AppLayerGetFilesFromFlow(f, direction);
|
|
|
|
|
if (ffc != NULL) {
|
|
|
|
|
for (ptr = ffc->head; ptr != NULL; ptr = ptr->next) {
|
|
|
|
|
SCLogDebug("disabling magic for file %p from direction %s",
|
|
|
|
|
ptr, direction == STREAM_TOSERVER ? "toserver":"toclient");
|
|
|
|
|
ptr->flags |= FILE_NOMAGIC;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCReturn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief disable file md5 calc for this flow
|
|
|
|
|
*
|
|
|
|
|
* \param f *LOCKED* flow
|
|
|
|
|
* \param direction flow direction
|
|
|
|
|
*/
|
|
|
|
|
void FileDisableMd5(Flow *f, uint8_t direction) {
|
|
|
|
|
File *ptr = NULL;
|
|
|
|
|
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
DEBUG_ASSERT_FLOW_LOCKED(f);
|
|
|
|
|
|
|
|
|
|
if (direction == STREAM_TOSERVER)
|
|
|
|
|
f->flags |= FLOW_FILE_NO_MD5_TS;
|
|
|
|
|
else
|
|
|
|
|
f->flags |= FLOW_FILE_NO_MD5_TC;
|
|
|
|
|
|
|
|
|
|
FileContainer *ffc = AppLayerGetFilesFromFlow(f, direction);
|
|
|
|
|
if (ffc != NULL) {
|
|
|
|
|
for (ptr = ffc->head; ptr != NULL; ptr = ptr->next) {
|
|
|
|
|
SCLogDebug("disabling md5 for file %p from direction %s",
|
|
|
|
|
ptr, direction == STREAM_TOSERVER ? "toserver":"toclient");
|
|
|
|
|
ptr->flags |= FILE_NOMD5;
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_NSS
|
|
|
|
|
/* destroy any ctx we may have so far */
|
|
|
|
|
if (ptr->md5_ctx != NULL) {
|
|
|
|
|
HASH_Destroy(ptr->md5_ctx);
|
|
|
|
|
ptr->md5_ctx = NULL;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCReturn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief disable file size tracking for this flow
|
|
|
|
|
*
|
|
|
|
|
* \param f *LOCKED* flow
|
|
|
|
|
* \param direction flow direction
|
|
|
|
|
*/
|
|
|
|
|
void FileDisableFilesize(Flow *f, uint8_t direction) {
|
|
|
|
|
File *ptr = NULL;
|
|
|
|
|
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
DEBUG_ASSERT_FLOW_LOCKED(f);
|
|
|
|
|
|
|
|
|
|
if (direction == STREAM_TOSERVER)
|
|
|
|
|
f->flags |= FLOW_FILE_NO_SIZE_TS;
|
|
|
|
|
else
|
|
|
|
|
f->flags |= FLOW_FILE_NO_SIZE_TC;
|
|
|
|
|
|
|
|
|
|
FileContainer *ffc = AppLayerGetFilesFromFlow(f, direction);
|
|
|
|
|
if (ffc != NULL) {
|
|
|
|
|
for (ptr = ffc->head; ptr != NULL; ptr = ptr->next) {
|
|
|
|
|
SCLogDebug("disabling size tracking for file %p from direction %s",
|
|
|
|
|
ptr, direction == STREAM_TOSERVER ? "toserver":"toclient");
|
|
|
|
|
ptr->flags |= FILE_NOTRACK;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCReturn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief set no store flag, close file if needed
|
|
|
|
|
*
|
|
|
|
|
* \param ff file
|
|
|
|
|
*/
|
|
|
|
|
void FileDisableStoringForFile(File *ff) {
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
if (ff == NULL) {
|
|
|
|
|
SCReturn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCLogDebug("not storing this file");
|
|
|
|
|
ff->flags |= FILE_NOSTORE;
|
|
|
|
|
|
|
|
|
|
if (ff->state == FILE_STATE_OPENED && ff->size >= (uint64_t)FileMagicSize()) {
|
|
|
|
|
(void)FileCloseFilePtr(ff, NULL, 0,
|
|
|
|
|
(FILE_TRUNCATED|FILE_NOSTORE));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief disable file storing for files in a transaction
|
|
|
|
|
*
|
|
|
|
|
* \param f *LOCKED* flow
|
|
|
|
|
* \param direction flow direction
|
|
|
|
|
* \param tx_id transaction id
|
|
|
|
|
*/
|
|
|
|
|
void FileDisableStoringForTransaction(Flow *f, uint8_t direction, uint64_t tx_id) {
|
|
|
|
|
File *ptr = NULL;
|
|
|
|
|
|
|
|
|
|
DEBUG_ASSERT_FLOW_LOCKED(f);
|
|
|
|
|
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
FileContainer *ffc = AppLayerGetFilesFromFlow(f, direction);
|
|
|
|
|
if (ffc != NULL) {
|
|
|
|
|
for (ptr = ffc->head; ptr != NULL; ptr = ptr->next) {
|
|
|
|
|
if (ptr->txid == tx_id) {
|
|
|
|
|
if (ptr->flags & FILE_STORE) {
|
|
|
|
|
/* weird, already storing -- let it continue*/
|
|
|
|
|
SCLogDebug("file is already being stored");
|
|
|
|
|
} else {
|
|
|
|
|
FileDisableStoringForFile(ptr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SCReturn;
|
|
|
|
|
}
|
file handling: expand filestore keyword
Filestore keyword by default (... filestore; ... ) marks only the file in the
same direction as the rule match for storing. This makes sense when inspecting
individual files (filemagic, filename, etc) but not so much when looking at
suspicious file requests, where the actual file is in the response.
The filestore keyword now takes 2 optional options:
filestore:<direction>,<scope>;
By default the direction is "same as rule match", and scope is "currently
inspected file".
For direction the following values are possible: "request" and "to_server",
"response" and "to_client", "both".
For scope the following values are possible: "tx" for all files in the current
HTTP/1.1 transation, "ssn" and "flow" for all files in the session/flow.
For the above case, where a suspious request should lead to a response file
download, this would work:
alert http ... content:"/suspicious/"; http_uri; filestore:response; ...
15 years ago
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* \brief flag a file with id "file_id" to be stored.
|
|
|
|
|
*
|
|
|
|
|
* \param fc file store
|
|
|
|
|
* \param file_id the file's id
|
|
|
|
|
*/
|
|
|
|
|
void FileStoreFileById(FileContainer *fc, uint16_t file_id) {
|
|
|
|
|
File *ptr = NULL;
|
|
|
|
|
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
if (fc != NULL) {
|
|
|
|
|
for (ptr = fc->head; ptr != NULL; ptr = ptr->next) {
|
|
|
|
|
if (ptr->file_id == file_id) {
|
|
|
|
|
ptr->flags |= FILE_STORE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
file handling: expand filestore keyword
Filestore keyword by default (... filestore; ... ) marks only the file in the
same direction as the rule match for storing. This makes sense when inspecting
individual files (filemagic, filename, etc) but not so much when looking at
suspicious file requests, where the actual file is in the response.
The filestore keyword now takes 2 optional options:
filestore:<direction>,<scope>;
By default the direction is "same as rule match", and scope is "currently
inspected file".
For direction the following values are possible: "request" and "to_server",
"response" and "to_client", "both".
For scope the following values are possible: "tx" for all files in the current
HTTP/1.1 transation, "ssn" and "flow" for all files in the session/flow.
For the above case, where a suspious request should lead to a response file
download, this would work:
alert http ... content:"/suspicious/"; http_uri; filestore:response; ...
15 years ago
|
|
|
void FileStoreAllFilesForTx(FileContainer *fc, uint16_t tx_id) {
|
|
|
|
|
File *ptr = NULL;
|
|
|
|
|
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
if (fc != NULL) {
|
|
|
|
|
for (ptr = fc->head; ptr != NULL; ptr = ptr->next) {
|
|
|
|
|
if (ptr->txid == tx_id) {
|
|
|
|
|
ptr->flags |= FILE_STORE;
|
file handling: expand filestore keyword
Filestore keyword by default (... filestore; ... ) marks only the file in the
same direction as the rule match for storing. This makes sense when inspecting
individual files (filemagic, filename, etc) but not so much when looking at
suspicious file requests, where the actual file is in the response.
The filestore keyword now takes 2 optional options:
filestore:<direction>,<scope>;
By default the direction is "same as rule match", and scope is "currently
inspected file".
For direction the following values are possible: "request" and "to_server",
"response" and "to_client", "both".
For scope the following values are possible: "tx" for all files in the current
HTTP/1.1 transation, "ssn" and "flow" for all files in the session/flow.
For the above case, where a suspious request should lead to a response file
download, this would work:
alert http ... content:"/suspicious/"; http_uri; filestore:response; ...
15 years ago
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FileStoreAllFiles(FileContainer *fc) {
|
|
|
|
|
File *ptr = NULL;
|
|
|
|
|
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
if (fc != NULL) {
|
|
|
|
|
for (ptr = fc->head; ptr != NULL; ptr = ptr->next) {
|
|
|
|
|
ptr->flags |= FILE_STORE;
|
file handling: expand filestore keyword
Filestore keyword by default (... filestore; ... ) marks only the file in the
same direction as the rule match for storing. This makes sense when inspecting
individual files (filemagic, filename, etc) but not so much when looking at
suspicious file requests, where the actual file is in the response.
The filestore keyword now takes 2 optional options:
filestore:<direction>,<scope>;
By default the direction is "same as rule match", and scope is "currently
inspected file".
For direction the following values are possible: "request" and "to_server",
"response" and "to_client", "both".
For scope the following values are possible: "tx" for all files in the current
HTTP/1.1 transation, "ssn" and "flow" for all files in the session/flow.
For the above case, where a suspious request should lead to a response file
download, this would work:
alert http ... content:"/suspicious/"; http_uri; filestore:response; ...
15 years ago
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FileTruncateAllOpenFiles(FileContainer *fc) {
|
|
|
|
|
File *ptr = NULL;
|
|
|
|
|
|
|
|
|
|
SCEnter();
|
|
|
|
|
|
|
|
|
|
if (fc != NULL) {
|
|
|
|
|
for (ptr = fc->head; ptr != NULL; ptr = ptr->next) {
|
|
|
|
|
if (ptr->state == FILE_STATE_OPENED) {
|
|
|
|
|
FileCloseFilePtr(ptr, NULL, 0, FILE_TRUNCATED);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|