mirror of
https://github.com/m1ngsama/TNT.git
synced 2026-02-08 00:54:03 +00:00
fix(security): implement comprehensive input validation
- Add is_valid_username() function to prevent injection attacks
* Reject shell metacharacters: |;&$`<>(){}[]'"\
* Reject control characters (except tab)
* Reject usernames starting with space, dot, or dash
- Apply username validation in read_username() with fallback to "anonymous"
- Add rate limiting via sleep(1) on validation failure
- Sanitize message content in message_save():
* Replace pipe, newline, carriage return to prevent log injection
* Ensure null termination of sanitized strings
- Enhance message_load() validation:
* Check for oversized lines
* Validate field lengths before copying
* Validate timestamp reasonableness (not >1 day future, <10 years past)
* Ensure null termination of all loaded strings
These changes address:
- Username injection vulnerabilities
- Message content injection in log files
- Log file format corruption attacks
- Malformed timestamp handling
Prevents:
- Command injection via usernames
- Log poisoning attacks
- DoS via oversized messages
This commit is contained in:
parent
abe477f713
commit
4f3a07c5e2
2 changed files with 89 additions and 5 deletions
|
|
@ -48,6 +48,13 @@ int message_load(message_t **messages, int max_messages) {
|
||||||
|
|
||||||
/* Now read the messages */
|
/* Now read the messages */
|
||||||
while (fgets(line, sizeof(line), fp) && count < max_messages) {
|
while (fgets(line, sizeof(line), fp) && count < max_messages) {
|
||||||
|
/* Check for oversized lines */
|
||||||
|
size_t line_len = strlen(line);
|
||||||
|
if (line_len >= sizeof(line) - 1) {
|
||||||
|
fprintf(stderr, "Warning: Skipping oversized line in messages.log\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* Format: RFC3339_timestamp|username|content */
|
/* Format: RFC3339_timestamp|username|content */
|
||||||
char line_copy[2048];
|
char line_copy[2048];
|
||||||
strncpy(line_copy, line, sizeof(line_copy) - 1);
|
strncpy(line_copy, line, sizeof(line_copy) - 1);
|
||||||
|
|
@ -57,10 +64,19 @@ int message_load(message_t **messages, int max_messages) {
|
||||||
char *username = strtok(NULL, "|");
|
char *username = strtok(NULL, "|");
|
||||||
char *content = strtok(NULL, "\n");
|
char *content = strtok(NULL, "\n");
|
||||||
|
|
||||||
|
/* Validate all fields exist */
|
||||||
if (!timestamp_str || !username || !content) {
|
if (!timestamp_str || !username || !content) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Validate field lengths */
|
||||||
|
if (strlen(username) >= MAX_USERNAME_LEN) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (strlen(content) >= MAX_MESSAGE_LEN) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* Parse ISO 8601 timestamp */
|
/* Parse ISO 8601 timestamp */
|
||||||
struct tm tm = {0};
|
struct tm tm = {0};
|
||||||
char *result = strptime(timestamp_str, "%Y-%m-%dT%H:%M:%S", &tm);
|
char *result = strptime(timestamp_str, "%Y-%m-%dT%H:%M:%S", &tm);
|
||||||
|
|
@ -68,9 +84,19 @@ int message_load(message_t **messages, int max_messages) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_array[count].timestamp = mktime(&tm);
|
/* Validate timestamp is reasonable (not in far future or past) */
|
||||||
|
time_t msg_time = mktime(&tm);
|
||||||
|
time_t now = time(NULL);
|
||||||
|
if (msg_time > now + 86400 || msg_time < now - 31536000 * 10) {
|
||||||
|
/* Skip messages more than 1 day in future or 10 years in past */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_array[count].timestamp = msg_time;
|
||||||
strncpy(msg_array[count].username, username, MAX_USERNAME_LEN - 1);
|
strncpy(msg_array[count].username, username, MAX_USERNAME_LEN - 1);
|
||||||
|
msg_array[count].username[MAX_USERNAME_LEN - 1] = '\0';
|
||||||
strncpy(msg_array[count].content, content, MAX_MESSAGE_LEN - 1);
|
strncpy(msg_array[count].content, content, MAX_MESSAGE_LEN - 1);
|
||||||
|
msg_array[count].content[MAX_MESSAGE_LEN - 1] = '\0';
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,8 +118,30 @@ int message_save(const message_t *msg) {
|
||||||
gmtime_r(&msg->timestamp, &tm_info);
|
gmtime_r(&msg->timestamp, &tm_info);
|
||||||
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", &tm_info);
|
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", &tm_info);
|
||||||
|
|
||||||
|
/* Sanitize username and content to prevent log injection */
|
||||||
|
char safe_username[MAX_USERNAME_LEN];
|
||||||
|
char safe_content[MAX_MESSAGE_LEN];
|
||||||
|
|
||||||
|
strncpy(safe_username, msg->username, sizeof(safe_username) - 1);
|
||||||
|
safe_username[sizeof(safe_username) - 1] = '\0';
|
||||||
|
|
||||||
|
strncpy(safe_content, msg->content, sizeof(safe_content) - 1);
|
||||||
|
safe_content[sizeof(safe_content) - 1] = '\0';
|
||||||
|
|
||||||
|
/* Replace pipe characters and newlines to prevent log format corruption */
|
||||||
|
for (char *p = safe_username; *p; p++) {
|
||||||
|
if (*p == '|' || *p == '\n' || *p == '\r') {
|
||||||
|
*p = '_';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (char *p = safe_content; *p; p++) {
|
||||||
|
if (*p == '|' || *p == '\n' || *p == '\r') {
|
||||||
|
*p = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Write to file: timestamp|username|content */
|
/* Write to file: timestamp|username|content */
|
||||||
fprintf(fp, "%s|%s|%s\n", timestamp, msg->username, msg->content);
|
fprintf(fp, "%s|%s|%s\n", timestamp, safe_username, safe_content);
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,33 @@
|
||||||
/* Global SSH bind instance */
|
/* Global SSH bind instance */
|
||||||
static ssh_bind g_sshbind = NULL;
|
static ssh_bind g_sshbind = NULL;
|
||||||
|
|
||||||
|
/* Validate username to prevent injection attacks */
|
||||||
|
static bool is_valid_username(const char *username) {
|
||||||
|
if (!username || username[0] == '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reject usernames starting with special characters */
|
||||||
|
if (username[0] == ' ' || username[0] == '.' || username[0] == '-') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for illegal characters that could cause injection */
|
||||||
|
const char *illegal_chars = "|;&$`\n\r<>(){}[]'\"\\";
|
||||||
|
for (size_t i = 0; i < strlen(username); i++) {
|
||||||
|
/* Reject control characters (except tab) */
|
||||||
|
if (username[i] < 32 && username[i] != 9) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/* Reject shell metacharacters */
|
||||||
|
if (strchr(illegal_chars, username[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* Generate or load SSH host key */
|
/* Generate or load SSH host key */
|
||||||
static int setup_host_key(ssh_bind sshbind) {
|
static int setup_host_key(ssh_bind sshbind) {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
@ -178,9 +205,18 @@ static int read_username(client_t *client) {
|
||||||
strcpy(client->username, "anonymous");
|
strcpy(client->username, "anonymous");
|
||||||
} else {
|
} else {
|
||||||
strncpy(client->username, username, MAX_USERNAME_LEN - 1);
|
strncpy(client->username, username, MAX_USERNAME_LEN - 1);
|
||||||
/* Truncate to 20 characters */
|
client->username[MAX_USERNAME_LEN - 1] = '\0';
|
||||||
if (utf8_strlen(client->username) > 20) {
|
|
||||||
utf8_truncate(client->username, 20);
|
/* Validate username for security */
|
||||||
|
if (!is_valid_username(client->username)) {
|
||||||
|
client_printf(client, "Invalid username. Using 'anonymous' instead.\r\n");
|
||||||
|
strcpy(client->username, "anonymous");
|
||||||
|
sleep(1); /* Slow down rapid retry attempts */
|
||||||
|
} else {
|
||||||
|
/* Truncate to 20 characters */
|
||||||
|
if (utf8_strlen(client->username) > 20) {
|
||||||
|
utf8_truncate(client->username, 20);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue