This commit is contained in:
overcuriousity 2025-09-04 23:02:39 +02:00
parent d654e32e8a
commit 6b2e4b4373
3 changed files with 545 additions and 33 deletions

View File

@ -24,6 +24,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
#include <sys/stat.h> // library für is_directory: Unterscheidung zwischen Dateien und Ordnern #include <sys/stat.h> // library für is_directory: Unterscheidung zwischen Dateien und Ordnern
#include <time.h> // um aktuelle Zeit zu generieren #include <time.h> // um aktuelle Zeit zu generieren
#include "gzunpack.h" #include "gzunpack.h"
#include "suspicious_detection.h"
#define INITIAL_ENTRIES 1000 // globale Variable zur initialen Speicherallokation in allocate_initial_memory(). Wird falls nötig um GROWTH_FACTOR erweitert #define INITIAL_ENTRIES 1000 // globale Variable zur initialen Speicherallokation in allocate_initial_memory(). Wird falls nötig um GROWTH_FACTOR erweitert
#define GROWTH_FACTOR 1.1 // wird in mem_expand_dynamically() genutzt, um den Speicher zu vergrößern #define GROWTH_FACTOR 1.1 // wird in mem_expand_dynamically() genutzt, um den Speicher zu vergrößern
@ -102,7 +103,7 @@ struct url_filter {
}; };
struct annotation_flag_filter { struct annotation_flag_filter {
int annotation_flag_is_set; int annotation_flag_is_present;
filter_mode_t mode; filter_mode_t mode;
}; };
@ -341,6 +342,7 @@ void annotate_entry(int index, char* annotation_string) {
} }
} }
// Muster hier KI-generiert.
void annotate_suspicious_entries(struct log_entry* dataset) { void annotate_suspicious_entries(struct log_entry* dataset) {
printf("DEBUG: Prüfe %d Einträge auf verdächtige Muster...\n", total_entries); printf("DEBUG: Prüfe %d Einträge auf verdächtige Muster...\n", total_entries);
@ -676,7 +678,7 @@ void load_log_file(char* path) {
while ((entry = readdir(dir)) != NULL) { while ((entry = readdir(dir)) != NULL) {
char* filename = (*entry).d_name; char* filename = (*entry).d_name;
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) { if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) {
printf("WARNING: Überspringe Datei %s, unerwartete Dateisignatur", filename); printf("WARNING: Überspringe Datei %s, unerwartete Dateisignatur\n", filename);
continue; continue;
} }
if (strstr(filename, "error")!=NULL){ if (strstr(filename, "error")!=NULL){
@ -1086,49 +1088,41 @@ int passes_filter(int entry_index) {
has_passing_filter = 1; has_passing_filter = 1;
} }
} }
if (filters.method_count > 0) { if (filters.method_count > 0) {
if (method_matches(all_entries[entry_index].request_method)) { if (method_matches(all_entries[entry_index].request_method)) {
has_passing_filter = 1; has_passing_filter = 1;
} }
} }
if (filters.ip_count > 0) { if (filters.ip_count > 0) {
if (ip_address_matches(all_entries[entry_index].ip_address)) { if (ip_address_matches(all_entries[entry_index].ip_address)) {
has_passing_filter = 1; has_passing_filter = 1;
} }
} }
if (filters.time_count > 0) { if (filters.time_count > 0) {
if (time_matches(all_entries[entry_index].time)) { if (time_matches(all_entries[entry_index].time)) {
has_passing_filter = 1; has_passing_filter = 1;
} }
} }
if (filters.user_agent_count > 0) { if (filters.user_agent_count > 0) {
if (user_agent_matches(all_entries[entry_index].user_agent)) { if (user_agent_matches(all_entries[entry_index].user_agent)) {
has_passing_filter = 1; has_passing_filter = 1;
} }
} }
if (filters.url_count > 0) { if (filters.url_count > 0) {
if (url_matches(all_entries[entry_index].url_path)) { if (url_matches(all_entries[entry_index].url_path)) {
has_passing_filter = 1; has_passing_filter = 1;
} }
} }
if (filters.annotation_flag_filter_enabled){ if (filters.annotation_flag_filter_enabled){
if (is_annotated(all_entries[entry_index].annotated_flag)) { if (is_annotated(all_entries[entry_index].annotated_flag)) {
has_passing_filter = 1; has_passing_filter = 1;
} }
} }
if (filters.annotation_count > 0){ if (filters.annotation_count > 0){
if (annotation_matches(all_entries[entry_index].annotation)){ if (annotation_matches(all_entries[entry_index].annotation)){
has_passing_filter = 1; has_passing_filter = 1;
} }
} }
return has_passing_filter; return has_passing_filter;
} }
} }
@ -1179,7 +1173,6 @@ void export_filtered_entries(char *filepath) {
int exported_count = 0; int exported_count = 0;
char iso_datetime[32]; char iso_datetime[32];
char message_text[300];
for (int i = 0; i < total_entries; i++) { for (int i = 0; i < total_entries; i++) {
if (passes_filter(i)) { if (passes_filter(i)) {
@ -1212,6 +1205,7 @@ struct ip_stat {
int count; int count;
}; };
// zeigt alle annotierten Einträge detailliert an
void show_annotated_entries() { void show_annotated_entries() {
printf("\nLOGEINTRÄGE MIT ANNOTATION\n"); printf("\nLOGEINTRÄGE MIT ANNOTATION\n");
printf("IP-Adresse | Methode | URL | Status | Bytes | User Agent | Zeit | Annotation\n"); printf("IP-Adresse | Methode | URL | Status | Bytes | User Agent | Zeit | Annotation\n");
@ -1249,6 +1243,7 @@ void show_annotated_entries() {
} }
} }
// TODO doku
void show_top_x_ips(){ void show_top_x_ips(){
struct ip_stat ip_stats[1000]; struct ip_stat ip_stats[1000];
int unique_ips = 0; int unique_ips = 0;
@ -1303,6 +1298,7 @@ void show_top_x_ips(){
} }
} }
// TODO doku
void show_top_user_agents(){ void show_top_user_agents(){
struct user_agent_stat { struct user_agent_stat {
char user_agent[256]; char user_agent[256];
@ -1362,12 +1358,14 @@ void show_top_user_agents(){
} }
} }
// Zeigt jeden Eintrag, der den Filter besteht. Wird ein int !=0 übergeben, wird nur die spezifizierte Anzahl an Einträgen dargestellt.
// Verwendung in einer Komplettausgabe sowie im Preview
void show_filtered_entries(int num_shown) { void show_filtered_entries(int num_shown) {
int shown_count = 0; int shown_count = 0;
printf("\nLOGDATEN:\n"); printf("\nLOGDATEN:\n");
printf("IP-Adresse | Methode | URL | Status | Bytes | User Agent | Zeit | Annotation\n"); printf("IP-Adresse | Methode | URL | Status | Bytes | User Agent | Zeit | Annotation\n");
printf("-----------------|---------|------------------------|--------|-------|--------------------------------------|------------------|--------------------\n"); printf("-----------------|---------|------------------------|--------|-------|--------------------------------------|---------------------|--------------------\n");
for (int i = 0; i < total_entries; i++) { for (int i = 0; i < total_entries; i++) {
if (!passes_filter(i)) continue; if (!passes_filter(i)) continue;
@ -1403,7 +1401,7 @@ void show_filtered_entries(int num_shown) {
} }
} }
// TODO annotationsfilter, Zeitraumfilter // Diese Funktion zeigt stets die gesetzten Filter in einem Format an, das aus dem interaktiven Modus reproduzierbar im cli-Modus wiederverwendet werden kann
void print_filter_args(){ void print_filter_args(){
int total_filters = filters.status_count + filters.method_count + filters.ip_count + filters.time_count + filters.user_agent_count + filters.url_count + filters.annotation_flag_filter_enabled + filters.annotation_count; int total_filters = filters.status_count + filters.method_count + filters.ip_count + filters.time_count + filters.user_agent_count + filters.url_count + filters.annotation_flag_filter_enabled + filters.annotation_count;
@ -1416,7 +1414,7 @@ void print_filter_args(){
if (filters.status_count > 0) { if (filters.status_count > 0) {
printf("--status="); printf("--status=");
for (int i = 0; i < filters.status_count; i++) { for (int i = 0; i < filters.status_count; i++) {
if (i > 0) printf(","); if (i > 0) {printf(",");};
if (filters.status_filters[i].mode == FILTER_EXCLUDE) printf("!"); if (filters.status_filters[i].mode == FILTER_EXCLUDE) printf("!");
printf("%d", filters.status_filters[i].code); printf("%d", filters.status_filters[i].code);
} }
@ -1426,7 +1424,7 @@ void print_filter_args(){
if (filters.method_count > 0) { if (filters.method_count > 0) {
printf("--method="); printf("--method=");
for (int i = 0; i < filters.method_count; i++) { for (int i = 0; i < filters.method_count; i++) {
if (i > 0) printf(","); if (i > 0) {printf(",");}
if (filters.method_filters[i].mode == FILTER_EXCLUDE) printf("!"); if (filters.method_filters[i].mode == FILTER_EXCLUDE) printf("!");
printf("%s", filters.method_filters[i].pattern); printf("%s", filters.method_filters[i].pattern);
} }
@ -1436,7 +1434,7 @@ void print_filter_args(){
if (filters.ip_count > 0) { if (filters.ip_count > 0) {
printf("--ip="); printf("--ip=");
for (int i = 0; i < filters.ip_count; i++) { for (int i = 0; i < filters.ip_count; i++) {
if (i > 0) printf(","); if (i > 0) {printf(",");};
if (filters.ip_filters[i].mode == FILTER_EXCLUDE) printf("!"); if (filters.ip_filters[i].mode == FILTER_EXCLUDE) printf("!");
printf("%s", filters.ip_filters[i].ip_address); printf("%s", filters.ip_filters[i].ip_address);
} }
@ -1446,7 +1444,7 @@ void print_filter_args(){
if (filters.user_agent_count > 0) { if (filters.user_agent_count > 0) {
printf("--useragent="); printf("--useragent=");
for (int i = 0; i < filters.user_agent_count; i++) { for (int i = 0; i < filters.user_agent_count; i++) {
if (i > 0) printf(","); if (i > 0) {printf(",");};
if (filters.user_agent_filters[i].mode == FILTER_EXCLUDE) printf("!"); if (filters.user_agent_filters[i].mode == FILTER_EXCLUDE) printf("!");
printf("%s", filters.user_agent_filters[i].pattern); printf("%s", filters.user_agent_filters[i].pattern);
} }
@ -1456,14 +1454,34 @@ void print_filter_args(){
if (filters.url_count > 0) { if (filters.url_count > 0) {
printf("--url="); printf("--url=");
for (int i = 0; i < filters.url_count; i++) { for (int i = 0; i < filters.url_count; i++) {
if (i > 0) printf(","); if (i > 0) {printf(",");};
if (filters.url_filters[i].mode == FILTER_EXCLUDE) printf("!"); if (filters.url_filters[i].mode == FILTER_EXCLUDE) printf("!");
printf("%s", filters.url_filters[i].pattern); printf("%s", filters.url_filters[i].pattern);
} }
printf(" "); printf(" ");
} }
// TODO timestamp if (filters.time_count >0){
printf("--timerange=");
for(int i =0; i < filters.time_count;i++){
if(i > 0) {printf(",");};
if (filters.time_filters[i].mode == FILTER_EXCLUDE) {printf("!");};
// die führenden 0 sind hier wichtig
printf("%04d-%02d-%02d-%02d-%02d-%02d:%04d-%02d-%02d-%02d-%02d-%02d ",
filters.time_filters[i].start_time.year,
filters.time_filters[i].start_time.month,
filters.time_filters[i].start_time.day,
filters.time_filters[i].start_time.hour,
filters.time_filters[i].start_time.minute,
filters.time_filters[i].start_time.second,
filters.time_filters[i].end_time.year,
filters.time_filters[i].end_time.month,
filters.time_filters[i].end_time.day,
filters.time_filters[i].end_time.hour,
filters.time_filters[i].end_time.minute,
filters.time_filters[i].end_time.second);
}
}
// macht nicht viel Sinn, diese hier zu integrieren, so lang der User nicht weiß, wie die Annotationen lauten // macht nicht viel Sinn, diese hier zu integrieren, so lang der User nicht weiß, wie die Annotationen lauten
if (filters.annotation_flag_filter_enabled) { if (filters.annotation_flag_filter_enabled) {
@ -1490,19 +1508,18 @@ void print_filter_args(){
printf("\n"); printf("\n");
// TODO
if (filters.time_count > 0) {
printf("HINWEIS: %d Zeitraum-Filter sind aktiv, aber nur im interaktiven Modus verfügbar.\n", filters.time_count);
}
} }
// Status-Anzeige, die in jedem Bildschirm dynamisch eine Vorschau des gefilterten Datensatzes, sowie eine Übersicht über die gesetzten Filter anzeigt.
void show_status(){ void show_status(){
printf("\nPREVIEW:\n"); printf("\nPREVIEW:\n");
// diese Funktion wird auch an anderer Stelle mit Parameter 0 verwendet und zeigt dann alles an
show_filtered_entries(10); show_filtered_entries(10);
printf("\nSTATUS\n"); printf("\nSTATUS\n");
if (total_entries > 0) { if (total_entries > 0) {
printf(" %d Logzeilen in Datenstruktur\n", total_entries); printf(" %d Logzeilen in Datenstruktur\n", total_entries);
//nicht verifiziert?
printf(" Speicherbelegung: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry))); printf(" Speicherbelegung: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
} else { } else {
printf(" ERROR: Keine Einträge in Datenstruktur!\n"); printf(" ERROR: Keine Einträge in Datenstruktur!\n");
@ -1533,6 +1550,7 @@ void show_status(){
} }
} }
// TRANSPARENZ: Dies wurde KI-generiert
void print_filter_examples(){ void print_filter_examples(){
printf("\nFILTER-DOKUMENTATION\n"); printf("\nFILTER-DOKUMENTATION\n");
printf("\nEXKLUSIONS-FILTER (immer OR-Logik, unabhängig vom Modus):\n"); printf("\nEXKLUSIONS-FILTER (immer OR-Logik, unabhängig vom Modus):\n");
@ -1696,6 +1714,8 @@ int read_menu_input(){
return (int)number; return (int)number;
} }
// Transparenz: Die visuelle Darstellung der Menus und dessen Formatierung, sowie repetitive schriftliche Wiedergabe wie etwa die interaktive Eingabe der Zeiträume ist teilweise KI-generiert.
void show_main_menu(){ void show_main_menu(){
printf("\nHAUPTMENÜ\n"); printf("\nHAUPTMENÜ\n");
printf("1. Filter verwalten\n"); printf("1. Filter verwalten\n");
@ -2293,7 +2313,7 @@ void menu_show_entries(){
} }
// Funktionen zum setzen der Filter (existierende Datenstrukturen) // Funktionen zum setzen der Filter (existierende Datenstrukturen)
void add_status_filter(char* value, filter_mode_t mode) { void add_parsed_status_filter(char* value, filter_mode_t mode) {
if (filters.status_count >= MAX_FILTERS) { if (filters.status_count >= MAX_FILTERS) {
printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value); printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value);
return; return;
@ -2317,7 +2337,7 @@ void add_status_filter(char* value, filter_mode_t mode) {
printf("DEBUG: Filter hinzugefügt: %s%d\n", mode == FILTER_EXCLUDE ? "!" : "", status_code); printf("DEBUG: Filter hinzugefügt: %s%d\n", mode == FILTER_EXCLUDE ? "!" : "", status_code);
} }
void add_ip_filter(char* value, filter_mode_t mode) { void add_parsed_ip_filter(char* value, filter_mode_t mode) {
if (filters.ip_count >= MAX_FILTERS) { if (filters.ip_count >= MAX_FILTERS) {
printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value); printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value);
return; return;
@ -2338,7 +2358,7 @@ void add_ip_filter(char* value, filter_mode_t mode) {
} }
// gleiche Mechanik wie bei IP-Adresse // gleiche Mechanik wie bei IP-Adresse
void add_method_filter(char* value, filter_mode_t mode) { void add_parsed_method_filter(char* value, filter_mode_t mode) {
if (filters.method_count >= MAX_FILTERS) { if (filters.method_count >= MAX_FILTERS) {
printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value); printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value);
return; return;
@ -2357,7 +2377,7 @@ void add_method_filter(char* value, filter_mode_t mode) {
} }
// gleiche Mechanik wie bei IP-Adresse // gleiche Mechanik wie bei IP-Adresse
void add_useragent_filter(char* value, filter_mode_t mode) { void add_parsed_useragent_filter(char* value, filter_mode_t mode) {
if (filters.user_agent_count >= MAX_FILTERS) { if (filters.user_agent_count >= MAX_FILTERS) {
printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value); printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value);
return; return;
@ -2376,7 +2396,7 @@ void add_useragent_filter(char* value, filter_mode_t mode) {
} }
// gleiche Mechanik wie bei IP-Adresse // gleiche Mechanik wie bei IP-Adresse
void add_url_filter(char* value, filter_mode_t mode) { void add_parsed_url_filter(char* value, filter_mode_t mode) {
if (filters.url_count >= MAX_FILTERS) { if (filters.url_count >= MAX_FILTERS) {
printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value); printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value);
return; return;
@ -2394,6 +2414,186 @@ void add_url_filter(char* value, filter_mode_t mode) {
printf("DEBUG: URL/Payload-Filter hinzugefügt: %s%s\n", mode == FILTER_EXCLUDE ? "!" : "", value); printf("DEBUG: URL/Payload-Filter hinzugefügt: %s%s\n", mode == FILTER_EXCLUDE ? "!" : "", value);
} }
// Parsen des Timestamp-Filters ist etwas komplexer, da er in die Datenstruktur geschrieben werden muss.
void add_parsed_timerange_filter(char* value, filter_mode_t mode) {
if (filters.time_count >= MAX_FILTERS) {
printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value);
return;
}
//lokale Kopie
struct time_filter new_time_filter = {0};
//Zur Position des :, der die Startzeit von der Endzeit trennt
char* colon_pos = strchr(value, ':');
if (colon_pos == NULL) {
printf("ERROR: Missing ':' separator\n");
return;
}
// ERsteze : durch Nullterminator. Das ist notwendig, damit strtok den Timestamp problemlos einlesen kann
*colon_pos = '\0';
//Für jeden Datenpunkt: Einlesen bis zum -, sowie Error handling, da atoi gar nix macht, wenn das Token nicht geparsed werden konnte
char* token = strtok(value, "-");
if (!token) return;
int start_year = atoi(token);
if (start_year < 1970 || start_year > 2100) {
printf("ERROR: Startjahr außerhalb des möglichen Bereichs: %d\n", start_year);
return;
}
token = strtok(NULL, "-");
if (!token) return;
int start_month = atoi(token);
if (start_month < 1 || start_month > 12) {
printf("ERROR: Startmonat außerhalb des möglichen Bereichs: %d\n", start_month);
return;
}
token = strtok(NULL, "-");
if (!token) return;
int start_day = atoi(token);
if (start_day < 1 || start_day > 31) {
printf("ERROR: Starttag außerhalb des möglichen Bereichs: %d\n", start_day);
return;
}
token = strtok(NULL, "-");
if (!token) return;
int start_hour = atoi(token);
if (start_hour < 0 || start_hour > 23) {
printf("ERROR: Startstunde außerhalb des möglichen Bereichs: %d\n", start_hour);
return;
}
token = strtok(NULL, "-");
if (!token) return;
int start_minute = atoi(token);
if (start_minute < 0 || start_minute > 59) {
printf("ERROR: Startminute außerhalb des möglichen Bereichs: %d\n", start_minute);
return;
}
token = strtok(NULL, "-");
if (!token) return;
int start_second = atoi(token);
if (start_second < 0 || start_second > 59) {
printf("ERROR: Startsekunde außerhalb des möglichen Bereichs: %d\n", start_second);
return;
}
token = strtok(colon_pos+1, "-");
if (!token) return;
int end_year = atoi(token);
if (end_year < 1970 || end_year > 2100) {
printf("ERROR: Endjahr außerhalb des möglichen Bereichs: %d\n", end_year);
return;
}
token = strtok(NULL, "-");
if (!token) return;
int end_month = atoi(token);
if (end_month < 1 || end_month > 12) {
printf("ERROR: Endmonat außerhalb des möglichen Bereichs: %d\n", end_month);
return;
}
token = strtok(NULL, "-");
if (!token) return;
int end_day = atoi(token);
if (end_day < 1 || end_day > 31) {
printf("ERROR: Endtag außerhalb des möglichen Bereichs: %d\n", end_day);
return;
}
token = strtok(NULL, "-");
if (!token) return;
int end_hour = atoi(token);
if (end_hour < 0 || end_hour > 23) {
printf("ERROR: Endstunde außerhalb des möglichen Bereichs: %d\n", end_hour);
return;
}
token = strtok(NULL, "-");
if (!token) return;
int end_minute = atoi(token);
if (end_minute < 0 || end_minute > 59) {
printf("ERROR: Endminute außerhalb des möglichen Bereichs: %d\n", end_minute);
return;
}
token = strtok(NULL, "-");
if (!token) return;
int end_second = atoi(token);
if (end_second < 0 || end_second > 59) {
printf("ERROR: Endsekunde außerhalb des möglichen Bereichs: %d\n", end_second);
return;
}
// integer-Variablen in die lokale Datenstruktur schreiben
new_time_filter.start_time.year = start_year;
new_time_filter.start_time.month = start_month;
new_time_filter.start_time.day = start_day;
new_time_filter.start_time.hour = start_hour;
new_time_filter.start_time.minute = start_minute;
new_time_filter.start_time.second = start_second;
new_time_filter.end_time.year = end_year;
new_time_filter.end_time.month = end_month;
new_time_filter.end_time.day = end_day;
new_time_filter.end_time.hour = end_hour;
new_time_filter.end_time.minute = end_minute;
new_time_filter.end_time.second = end_second;
new_time_filter.mode = mode;
// Logik hier: der neue Filter wird stets an einen neuen Index geschrieben. Dieser ergibt sich aus der aktuellen ANZAHL der existierenden Zeitfilter. Das funktioniert, weil der index bei 0 beginnt
filters.time_filters[filters.time_count] = new_time_filter;
filters.time_count++;
printf("DEBUG: Zeitraum-Filter hinzugefügt (%s): %04d-%02d-%02d %02d:%02d:%02d bis %04d-%02d-%02d %02d:%02d:%02d\n",
mode == FILTER_EXCLUDE ? "ausschließen" : "einschließen",
new_time_filter.start_time.year, new_time_filter.start_time.month, new_time_filter.start_time.day,
new_time_filter.start_time.hour, new_time_filter.start_time.minute, new_time_filter.start_time.second,
new_time_filter.end_time.year, new_time_filter.end_time.month, new_time_filter.end_time.day,
new_time_filter.end_time.hour, new_time_filter.end_time.minute, new_time_filter.end_time.second);
}
// recht einfache Mechanik für den Bool´schen Filter für annotierte Einträge
void add_parsed_annotated_filter(char* value, filter_mode_t mode) {
if (strcmp(value, "true") == 0) {
filters.annotation_flag_filter.annotation_flag_is_present = 1;
filters.annotation_flag_filter.mode = mode;
filters.annotation_flag_filter_enabled = 1;
printf("DEBUG: Annotations-Flag-Filter hinzugefügt: %s%s\n", mode == FILTER_EXCLUDE ? "!" : "", value);
// falls doch mal jemand false eingibt
} else if (strcmp(value, "false") == 0) {
filters.annotation_flag_filter.annotation_flag_is_present = 0;
filters.annotation_flag_filter.mode = mode;
filters.annotation_flag_filter_enabled = 1;
printf("DEBUG: Annotations-Flag-Filter hinzugefügt: %s%s\n", mode == FILTER_EXCLUDE ? "!" : "", value);
} else {
printf("WARNING: Ungültiger Wert für --annotated Filter: %s (nur 'true' unterstützt)\n", value);
}
}
// Wieder gleiche Mechanik wie beim User Agent etc
void add_parsed_annotation_filter(char* value, filter_mode_t mode) {
if (filters.annotation_count >= MAX_FILTERS) {
printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value);
return;
}
if (strlen(value) >= sizeof(filters.annotation_filters[0].pattern)) {
printf("WARNING: Annotations-Filterwert zu lang: %s\n", value);
return;
}
strcpy(filters.annotation_filters[filters.annotation_count].pattern, value);
filters.annotation_filters[filters.annotation_count].mode = mode;
filters.annotation_count++;
printf("DEBUG: Annotations-Filter hinzugefügt: %s%s\n",
mode == FILTER_EXCLUDE ? "!" : "", value);
}
// Funktion zum Parsen der Filter-Werte, die mit --<Filtertyp>=<Werte,Komma-getrennt> übergeben werden. // Funktion zum Parsen der Filter-Werte, die mit --<Filtertyp>=<Werte,Komma-getrennt> übergeben werden.
// values_str sind die Werte hinter dem =, filter_type die Werte vor dem = // values_str sind die Werte hinter dem =, filter_type die Werte vor dem =
// filter_type wird von parse_filter_argument() übergeben // filter_type wird von parse_filter_argument() übergeben
@ -2420,15 +2620,21 @@ void parse_filter_values(char* values_str, char* filter_type) {
if (strlen(token) > 0) { if (strlen(token) > 0) {
// wenn das token Werte hat, werden die entsprechenden Funktionen aufgerufen, die die Filter setzen // wenn das token Werte hat, werden die entsprechenden Funktionen aufgerufen, die die Filter setzen
if (strcmp(filter_type, "status") == 0) { if (strcmp(filter_type, "status") == 0) {
add_status_filter(token, mode); add_parsed_status_filter(token, mode);
} else if (strcmp(filter_type, "ip") == 0) { } else if (strcmp(filter_type, "ip") == 0) {
add_ip_filter(token, mode); add_parsed_ip_filter(token, mode);
} else if (strcmp(filter_type, "method") == 0) { } else if (strcmp(filter_type, "method") == 0) {
add_method_filter(token, mode); add_parsed_method_filter(token, mode);
} else if (strcmp(filter_type, "useragent") == 0) { } else if (strcmp(filter_type, "useragent") == 0) {
add_useragent_filter(token, mode); add_parsed_useragent_filter(token, mode);
} else if (strcmp(filter_type, "url") == 0) { } else if (strcmp(filter_type, "url") == 0) {
add_url_filter(token, mode); add_parsed_url_filter(token, mode);
} else if (strcmp(filter_type, "timerange") == 0) {
add_parsed_timerange_filter(token, mode);
} else if (strcmp(filter_type, "annotated") == 0) {
add_parsed_annotated_filter(token, mode);
} else if (strcmp(filter_type, "annotation") == 0) {
add_parsed_annotation_filter(token, mode);
} }
} }
// nächste Iteration - nächstes token einlesen, damit die while-Schleife weiteriteriert // nächste Iteration - nächstes token einlesen, damit die while-Schleife weiteriteriert
@ -2470,6 +2676,12 @@ int parse_filter_argument(char* arg) {
parse_filter_values(values, "useragent"); parse_filter_values(values, "useragent");
} else if (strstr(filter_type, "url") != NULL) { } else if (strstr(filter_type, "url") != NULL) {
parse_filter_values(values, "url"); parse_filter_values(values, "url");
} else if (strstr(filter_type, "timerange") != NULL) {
parse_filter_values(values, "timerange");
} else if (strstr(filter_type, "annotated") != NULL) {
parse_filter_values(values, "annotated");
} else if (strstr(filter_type, "annotation") != NULL) {
parse_filter_values(values, "annotation");
} else if (strstr(filter_type, "mode") != NULL) { } else if (strstr(filter_type, "mode") != NULL) {
if (strstr(values, "and") != NULL || strstr(values, "AND") != NULL) { if (strstr(values, "and") != NULL || strstr(values, "AND") != NULL) {
filters.combination_mode = 0; filters.combination_mode = 0;

259
src/suspicious-detection.c Normal file
View File

@ -0,0 +1,259 @@
/*
Copyright (c) 2025 Mario Stöckl (mstoeck3@hs-mittweida.de).
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// TRANSPARENZ
// Diese Funktion ist KI-generiert
#include "suspicious_detection.h"
// Main suspicious activity detection function
// Parameters made explicit to avoid global variable dependencies
void annotate_suspicious_entries(struct log_entry* dataset,
struct log_entry* all_entries,
int total_entries,
int* suspicious_patterns_count,
int suspicious_request_len_threshold) {
printf("DEBUG: Prüfe %d Einträge auf verdächtige Muster...\n", total_entries);
for (int i = 0; i < total_entries; i++) {
// Initialisierung der Annotation falls noch nicht gesetzt
if (all_entries[i].annotation[0] == '\0') {
all_entries[i].annotation[0] = '\0';
}
int is_suspicious = 0; // Flag um zu verfolgen ob bereits annotiert
// 1. PAYLOAD-GRÖßE: Sehr lange Requests
int url_length = strlen(all_entries[i].url_path);
if (url_length > suspicious_request_len_threshold) {
annotate_entry(i, "Long Payload", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
// 2. PFAD-BASIERTE ANGRIFFE: Häufige Angriffsziele und sensible Pfade
if (!is_suspicious) {
// Git-Repository Zugriffe (häufig bei Recon)
if (search_in_string(all_entries[i].url_path, ".git/") ||
search_in_string(all_entries[i].url_path, "/.git")) {
annotate_entry(i, "Git Access", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
// Environment-Dateien (kritische Konfigurationsdateien)
else if (search_in_string(all_entries[i].url_path, ".env") ||
search_in_string(all_entries[i].url_path, ".config") ||
search_in_string(all_entries[i].url_path, "config.php")) {
annotate_entry(i, "Config Access", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
// WordPress Admin-Bereiche (häufig attackiert)
else if (search_in_string(all_entries[i].url_path, "wp-admin") ||
search_in_string(all_entries[i].url_path, "wp-login") ||
search_in_string(all_entries[i].url_path, "wp-config")) {
annotate_entry(i, "WP Attack", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
// Database Management Tools
else if (search_in_string(all_entries[i].url_path, "phpmyadmin") ||
search_in_string(all_entries[i].url_path, "phpMyAdmin") ||
search_in_string(all_entries[i].url_path, "adminer")) {
annotate_entry(i, "DB Tool Access", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
// Admin/Management Interfaces
else if (search_in_string(all_entries[i].url_path, "/admin") ||
search_in_string(all_entries[i].url_path, "/manager") ||
search_in_string(all_entries[i].url_path, "/console")) {
annotate_entry(i, "Admin Access", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
}
// 3. DIRECTORY TRAVERSAL: Pfad-Traversal Versuche
if (!is_suspicious) {
if (search_in_string(all_entries[i].url_path, "../") ||
search_in_string(all_entries[i].url_path, "..\\") ||
search_in_string(all_entries[i].url_path, "%2e%2e%2f") ||
search_in_string(all_entries[i].url_path, "%2e%2e%5c")) {
annotate_entry(i, "Path Traversal", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
}
// 4. SQL INJECTION: SQL-Keywords in URLs
if (!is_suspicious) {
if (search_in_string(all_entries[i].url_path, "select%20") ||
search_in_string(all_entries[i].url_path, "union%20") ||
search_in_string(all_entries[i].url_path, "insert%20") ||
search_in_string(all_entries[i].url_path, "delete%20") ||
search_in_string(all_entries[i].url_path, "drop%20") ||
search_in_string(all_entries[i].url_path, "' or ") ||
search_in_string(all_entries[i].url_path, "' and ") ||
search_in_string(all_entries[i].url_path, "1=1") ||
search_in_string(all_entries[i].url_path, "1' or '1'='1")) {
annotate_entry(i, "SQL Injection", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
}
// 5. XSS ATTEMPTS: Cross-Site-Scripting Versuche
if (!is_suspicious) {
if (search_in_string(all_entries[i].url_path, "<script") ||
search_in_string(all_entries[i].url_path, "%3cscript") ||
search_in_string(all_entries[i].url_path, "javascript:") ||
search_in_string(all_entries[i].url_path, "onerror=") ||
search_in_string(all_entries[i].url_path, "onload=") ||
search_in_string(all_entries[i].url_path, "alert(")) {
annotate_entry(i, "XSS Attempt", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
}
// 6. BACKUP/SENSITIVE FILE ACCESS: Backup-Dateien und sensible Extensions
if (!is_suspicious) {
if (search_in_string(all_entries[i].url_path, ".bak") ||
search_in_string(all_entries[i].url_path, ".old") ||
search_in_string(all_entries[i].url_path, ".backup") ||
search_in_string(all_entries[i].url_path, ".sql") ||
search_in_string(all_entries[i].url_path, ".log") ||
search_in_string(all_entries[i].url_path, ".key") ||
search_in_string(all_entries[i].url_path, ".pem")) {
annotate_entry(i, "Sensitive File", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
}
// 7. BOT/SCANNER DETECTION: Verdächtige User-Agents
if (!is_suspicious) {
if (search_in_string(all_entries[i].user_agent, "nmap") ||
search_in_string(all_entries[i].user_agent, "sqlmap") ||
search_in_string(all_entries[i].user_agent, "nikto") ||
search_in_string(all_entries[i].user_agent, "dirb") ||
search_in_string(all_entries[i].user_agent, "gobuster") ||
search_in_string(all_entries[i].user_agent, "whatweb") ||
search_in_string(all_entries[i].user_agent, "masscan") ||
search_in_string(all_entries[i].user_agent, "python-requests") ||
search_in_string(all_entries[i].user_agent, "curl/") ||
search_in_string(all_entries[i].user_agent, "wget/")) {
annotate_entry(i, "Scanner/Bot", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
}
// 8. EXCESSIVE URL ENCODING: Verdächtig hohe Anzahl von URL-Encoding
if (!is_suspicious) {
char *url = all_entries[i].url_path;
int encoding_count = 0;
char *pos = url;
while ((pos = strstr(pos, "%")) != NULL) {
encoding_count++;
pos++;
}
// Wenn mehr als 10 URL-Encodings in einer URL, verdächtig
if (encoding_count > 10) {
annotate_entry(i, "Heavy Encoding", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
}
// 9. ATYPICAL HTTP METHODS: Ungewöhnliche oder fehlerhafte HTTP-Methoden
if (!is_suspicious) {
if (strcmp(all_entries[i].request_method, "ATYPICAL") == 0) {
annotate_entry(i, "Malformed Request", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
// Seltene aber potentiell gefährliche HTTP-Methoden
else if (strcmp(all_entries[i].request_method, "PROPFIND") == 0 ||
strcmp(all_entries[i].request_method, "MKCOL") == 0 ||
strcmp(all_entries[i].request_method, "COPY") == 0 ||
strcmp(all_entries[i].request_method, "MOVE") == 0 ||
strcmp(all_entries[i].request_method, "LOCK") == 0) {
annotate_entry(i, "Rare Method", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
}
// 10. STATUS CODE ANOMALIES: Verdächtige Status-Code Muster
if (!is_suspicious) {
// 403 auf sensible Pfade könnte Angriffserkennung bedeuten
if (all_entries[i].status_code == 403 &&
(search_in_string(all_entries[i].url_path, "admin") ||
search_in_string(all_entries[i].url_path, ".git") ||
search_in_string(all_entries[i].url_path, ".env"))) {
annotate_entry(i, "Blocked Access", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
// 429 Too Many Requests - Rate Limiting aktiviert
else if (all_entries[i].status_code == 429) {
annotate_entry(i, "Rate Limited", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
}
// 11. CREDENTIAL STUFFING: Wiederholte Login-Versuche mit verschiedenen Credentials
if (!is_suspicious) {
if ((search_in_string(all_entries[i].url_path, "login") ||
search_in_string(all_entries[i].url_path, "signin") ||
search_in_string(all_entries[i].url_path, "auth")) &&
(all_entries[i].status_code == 401 || all_entries[i].status_code == 403)) {
annotate_entry(i, "Failed Auth", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
}
// 12. SHELL/WEBSHELL ACCESS: Verdächtige Shell-bezogene Pfade
if (!is_suspicious) {
if (search_in_string(all_entries[i].url_path, ".php?") ||
search_in_string(all_entries[i].url_path, "shell") ||
search_in_string(all_entries[i].url_path, "backdoor") ||
search_in_string(all_entries[i].url_path, "cmd=") ||
search_in_string(all_entries[i].url_path, "exec=") ||
search_in_string(all_entries[i].url_path, "system=")) {
annotate_entry(i, "Shell Access", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
}
// 13. API ABUSE: Verdächtige API-Zugriffe
if (!is_suspicious) {
if (search_in_string(all_entries[i].url_path, "/api/") &&
(all_entries[i].status_code >= 400 && all_entries[i].status_code < 500)) {
annotate_entry(i, "API Error", all_entries);
(*suspicious_patterns_count)++;
is_suspicious = 1;
}
}
}
printf("DEBUG: Analyse abgeschlossen. %d verdächtige Muster erkannt.\n", *suspicious_patterns_count);
}

View File

@ -0,0 +1,41 @@
/*
Copyright (c) 2025 Mario Stöckl (mstoeck3@hs-mittweida.de).
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// TRANSPARENZ
// Diese Funktion ist KI-generiert
#ifndef SUSPICIOUS_DETECTION_H
#define SUSPICIOUS_DETECTION_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// Forward declaration of the log_entry struct
// (The full definition should be in main.c or a shared header)
struct log_entry;
// Function declarations
void annotate_suspicious_entries(struct log_entry* dataset,
struct log_entry* all_entries,
int total_entries,
int* suspicious_patterns_count,
int suspicious_request_len_threshold);
// Helper function declarations that need to be available
void annotate_entry(int index, char* annotation_string,
struct log_entry* entries);
int search_in_string(char* raw_string, char* search_string);
#endif // SUSPICIOUS_DETECTION_H