v0.7.7 - Prevent sql attacks and rate limiting on subscriptions
This commit is contained in:
148
src/websockets.c
148
src/websockets.c
@@ -1200,6 +1200,11 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Parameter binding helpers
|
||||
char** bind_params = NULL;
|
||||
int bind_param_count = 0;
|
||||
int bind_param_capacity = 0;
|
||||
|
||||
int total_count = 0;
|
||||
|
||||
// Process each filter in the array
|
||||
@@ -1210,6 +1215,15 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reset bind params for this filter
|
||||
for (int j = 0; j < bind_param_count; j++) {
|
||||
free(bind_params[j]);
|
||||
}
|
||||
free(bind_params);
|
||||
bind_params = NULL;
|
||||
bind_param_count = 0;
|
||||
bind_param_capacity = 0;
|
||||
|
||||
// Build SQL COUNT query based on filter - exclude ephemeral events (kinds 20000-29999) from historical queries
|
||||
char sql[1024] = "SELECT COUNT(*) FROM events WHERE 1=1 AND (kind < 20000 OR kind >= 30000)";
|
||||
char* sql_ptr = sql + strlen(sql);
|
||||
@@ -1249,56 +1263,88 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
|
||||
// Handle authors filter
|
||||
cJSON* authors = cJSON_GetObjectItem(filter, "authors");
|
||||
if (authors && cJSON_IsArray(authors)) {
|
||||
int author_count = cJSON_GetArraySize(authors);
|
||||
int author_count = 0;
|
||||
// Count valid authors
|
||||
for (int a = 0; a < cJSON_GetArraySize(authors); a++) {
|
||||
cJSON* author = cJSON_GetArrayItem(authors, a);
|
||||
if (cJSON_IsString(author)) {
|
||||
author_count++;
|
||||
}
|
||||
}
|
||||
if (author_count > 0) {
|
||||
snprintf(sql_ptr, remaining, " AND pubkey IN (");
|
||||
sql_ptr += strlen(sql_ptr);
|
||||
remaining = sizeof(sql) - strlen(sql);
|
||||
|
||||
for (int a = 0; a < author_count; a++) {
|
||||
cJSON* author = cJSON_GetArrayItem(authors, a);
|
||||
if (cJSON_IsString(author)) {
|
||||
if (a > 0) {
|
||||
snprintf(sql_ptr, remaining, ",");
|
||||
sql_ptr++;
|
||||
remaining--;
|
||||
}
|
||||
snprintf(sql_ptr, remaining, "'%s'", cJSON_GetStringValue(author));
|
||||
sql_ptr += strlen(sql_ptr);
|
||||
remaining = sizeof(sql) - strlen(sql);
|
||||
if (a > 0) {
|
||||
snprintf(sql_ptr, remaining, ",");
|
||||
sql_ptr++;
|
||||
remaining--;
|
||||
}
|
||||
snprintf(sql_ptr, remaining, "?");
|
||||
sql_ptr += strlen(sql_ptr);
|
||||
remaining = sizeof(sql) - strlen(sql);
|
||||
}
|
||||
snprintf(sql_ptr, remaining, ")");
|
||||
sql_ptr += strlen(sql_ptr);
|
||||
remaining = sizeof(sql) - strlen(sql);
|
||||
|
||||
// Add author values to bind params
|
||||
for (int a = 0; a < cJSON_GetArraySize(authors); a++) {
|
||||
cJSON* author = cJSON_GetArrayItem(authors, a);
|
||||
if (cJSON_IsString(author)) {
|
||||
if (bind_param_count >= bind_param_capacity) {
|
||||
bind_param_capacity = bind_param_capacity == 0 ? 16 : bind_param_capacity * 2;
|
||||
bind_params = realloc(bind_params, bind_param_capacity * sizeof(char*));
|
||||
}
|
||||
bind_params[bind_param_count++] = strdup(cJSON_GetStringValue(author));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle ids filter
|
||||
cJSON* ids = cJSON_GetObjectItem(filter, "ids");
|
||||
if (ids && cJSON_IsArray(ids)) {
|
||||
int id_count = cJSON_GetArraySize(ids);
|
||||
int id_count = 0;
|
||||
// Count valid ids
|
||||
for (int i = 0; i < cJSON_GetArraySize(ids); i++) {
|
||||
cJSON* id = cJSON_GetArrayItem(ids, i);
|
||||
if (cJSON_IsString(id)) {
|
||||
id_count++;
|
||||
}
|
||||
}
|
||||
if (id_count > 0) {
|
||||
snprintf(sql_ptr, remaining, " AND id IN (");
|
||||
sql_ptr += strlen(sql_ptr);
|
||||
remaining = sizeof(sql) - strlen(sql);
|
||||
|
||||
for (int i = 0; i < id_count; i++) {
|
||||
cJSON* id = cJSON_GetArrayItem(ids, i);
|
||||
if (cJSON_IsString(id)) {
|
||||
if (i > 0) {
|
||||
snprintf(sql_ptr, remaining, ",");
|
||||
sql_ptr++;
|
||||
remaining--;
|
||||
}
|
||||
snprintf(sql_ptr, remaining, "'%s'", cJSON_GetStringValue(id));
|
||||
sql_ptr += strlen(sql_ptr);
|
||||
remaining = sizeof(sql) - strlen(sql);
|
||||
if (i > 0) {
|
||||
snprintf(sql_ptr, remaining, ",");
|
||||
sql_ptr++;
|
||||
remaining--;
|
||||
}
|
||||
snprintf(sql_ptr, remaining, "?");
|
||||
sql_ptr += strlen(sql_ptr);
|
||||
remaining = sizeof(sql) - strlen(sql);
|
||||
}
|
||||
snprintf(sql_ptr, remaining, ")");
|
||||
sql_ptr += strlen(sql_ptr);
|
||||
remaining = sizeof(sql) - strlen(sql);
|
||||
|
||||
// Add id values to bind params
|
||||
for (int i = 0; i < cJSON_GetArraySize(ids); i++) {
|
||||
cJSON* id = cJSON_GetArrayItem(ids, i);
|
||||
if (cJSON_IsString(id)) {
|
||||
if (bind_param_count >= bind_param_capacity) {
|
||||
bind_param_capacity = bind_param_capacity == 0 ? 16 : bind_param_capacity * 2;
|
||||
bind_params = realloc(bind_params, bind_param_capacity * sizeof(char*));
|
||||
}
|
||||
bind_params[bind_param_count++] = strdup(cJSON_GetStringValue(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1311,29 +1357,50 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
|
||||
const char* tag_name = filter_key + 1; // Get the tag name (e, p, t, type, etc.)
|
||||
|
||||
if (cJSON_IsArray(filter_item)) {
|
||||
int tag_value_count = cJSON_GetArraySize(filter_item);
|
||||
int tag_value_count = 0;
|
||||
// Count valid tag values
|
||||
for (int i = 0; i < cJSON_GetArraySize(filter_item); i++) {
|
||||
cJSON* tag_value = cJSON_GetArrayItem(filter_item, i);
|
||||
if (cJSON_IsString(tag_value)) {
|
||||
tag_value_count++;
|
||||
}
|
||||
}
|
||||
if (tag_value_count > 0) {
|
||||
// Use EXISTS with JSON extraction to check for matching tags
|
||||
snprintf(sql_ptr, remaining, " AND EXISTS (SELECT 1 FROM json_each(json(tags)) WHERE json_extract(value, '$[0]') = '%s' AND json_extract(value, '$[1]') IN (", tag_name);
|
||||
// Use EXISTS with parameterized query
|
||||
snprintf(sql_ptr, remaining, " AND EXISTS (SELECT 1 FROM json_each(json(tags)) WHERE json_extract(value, '$[0]') = ? AND json_extract(value, '$[1]') IN (");
|
||||
sql_ptr += strlen(sql_ptr);
|
||||
remaining = sizeof(sql) - strlen(sql);
|
||||
|
||||
for (int i = 0; i < tag_value_count; i++) {
|
||||
cJSON* tag_value = cJSON_GetArrayItem(filter_item, i);
|
||||
if (cJSON_IsString(tag_value)) {
|
||||
if (i > 0) {
|
||||
snprintf(sql_ptr, remaining, ",");
|
||||
sql_ptr++;
|
||||
remaining--;
|
||||
}
|
||||
snprintf(sql_ptr, remaining, "'%s'", cJSON_GetStringValue(tag_value));
|
||||
sql_ptr += strlen(sql_ptr);
|
||||
remaining = sizeof(sql) - strlen(sql);
|
||||
if (i > 0) {
|
||||
snprintf(sql_ptr, remaining, ",");
|
||||
sql_ptr++;
|
||||
remaining--;
|
||||
}
|
||||
snprintf(sql_ptr, remaining, "?");
|
||||
sql_ptr += strlen(sql_ptr);
|
||||
remaining = sizeof(sql) - strlen(sql);
|
||||
}
|
||||
snprintf(sql_ptr, remaining, "))");
|
||||
sql_ptr += strlen(sql_ptr);
|
||||
remaining = sizeof(sql) - strlen(sql);
|
||||
|
||||
// Add tag name and values to bind params
|
||||
if (bind_param_count >= bind_param_capacity) {
|
||||
bind_param_capacity = bind_param_capacity == 0 ? 16 : bind_param_capacity * 2;
|
||||
bind_params = realloc(bind_params, bind_param_capacity * sizeof(char*));
|
||||
}
|
||||
bind_params[bind_param_count++] = strdup(tag_name);
|
||||
for (int i = 0; i < cJSON_GetArraySize(filter_item); i++) {
|
||||
cJSON* tag_value = cJSON_GetArrayItem(filter_item, i);
|
||||
if (cJSON_IsString(tag_value)) {
|
||||
if (bind_param_count >= bind_param_capacity) {
|
||||
bind_param_capacity = bind_param_capacity == 0 ? 16 : bind_param_capacity * 2;
|
||||
bind_params = realloc(bind_params, bind_param_capacity * sizeof(char*));
|
||||
}
|
||||
bind_params[bind_param_count++] = strdup(cJSON_GetStringValue(tag_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1395,6 +1462,11 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
|
||||
continue;
|
||||
}
|
||||
|
||||
// Bind parameters
|
||||
for (int i = 0; i < bind_param_count; i++) {
|
||||
sqlite3_bind_text(stmt, i + 1, bind_params[i], -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
int filter_count = 0;
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
filter_count = sqlite3_column_int(stmt, 0);
|
||||
@@ -1431,5 +1503,11 @@ int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, st
|
||||
}
|
||||
cJSON_Delete(count_response);
|
||||
|
||||
// Cleanup bind params
|
||||
for (int i = 0; i < bind_param_count; i++) {
|
||||
free(bind_params[i]);
|
||||
}
|
||||
free(bind_params);
|
||||
|
||||
return total_count;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user