<!-- Kamailio Pull Request Template -->
<!-- IMPORTANT: - for detailed contributing guidelines, read: https://github.com/kamailio/kamailio/blob/master/.github/CONTRIBUTING.md - pull requests must be done to master branch, unless they are backports of fixes from master branch to a stable branch - backports to stable branches must be done with 'git cherry-pick -x ...' - code is contributed under BSD for core and main components (tm, sl, auth, tls) - code is contributed GPLv2 or a compatible license for the other components - GPL code is contributed with OpenSSL licensing exception -->
#### Pre-Submission Checklist <!-- Go over all points below, and after creating the PR, tick all the checkboxes that apply --> <!-- All points should be verified, otherwise, read the CONTRIBUTING guidelines from above--> <!-- If you're unsure about any of these, don't hesitate to ask on sr-dev mailing list --> - [x] Commit message has the format required by CONTRIBUTING guide - [x] Commits are split per component (core, individual modules, libs, utils, ...) - [x] Each component has a single commit (if not, squash them into one commit) - [x] No commits to README files for modules (changes must be done to docbook files in `doc/` subfolder, the README file is autogenerated)
#### Type Of Change - [ ] Small bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds new functionality) - [ ] Breaking change (fix or feature that would change existing functionality)
#### Checklist: <!-- Go over all points below, and after creating the PR, tick the checkboxes that apply --> - [x] PR should be backported to stable branches - [x] Tested changes locally - [ ] Related to issue #XXXX (replace XXXX with an open issue number)
#### Description <!-- Describe your changes in detail --> This module has been designed to offer an additional layer of security over our communications. To achieve this, the following features are available: Blacklist to filter user agents, IP addresses, countries, domains and users. Whitelist to filter user agents, IP addresses, countries, domains and users. Blacklist of destinations where calling is not allowed. SQL injection attacks prevention. You can view, comment on, or merge this pull request online at:
https://github.com/kamailio/kamailio/pull/1755
-- Commit Summary --
* Module security: create database SQL * Module security: database schema * Module security: new module * Change dst_exact_match from string to integer * fix Makefile description * update descriptions * Now it is possible to define the names of the columns in as parameters of the module * Update description * Update description * Fix sqli checks for some headers
-- File Changes --
A src/lib/srdb1/schema/security.xml (65) A src/modules/security/Makefile (14) A src/modules/security/README (461) A src/modules/security/doc/Makefile (3) A src/modules/security/doc/security.xml (33) A src/modules/security/doc/security_admin.xml (601) A src/modules/security/security.c (376) A src/modules/security/security.h (64) A src/modules/security/security_db.c (408) A src/modules/security/security_hdr.c (294) A src/modules/security/security_rpc.c (212) A utils/kamctl/db_sqlite/security-create.sql (11) A utils/kamctl/mysql/security-create.sql (11) A utils/kamctl/oracle/security-create.sql (19) A utils/kamctl/postgres/security-create.sql (11)
-- Patch Links --
https://github.com/kamailio/kamailio/pull/1755.patch https://github.com/kamailio/kamailio/pull/1755.diff
Thanks for this submission!
Few remarks after a very brief look at the patch:
* global variables must be declared in a .c file and the .h file has to list them with `external`, otherwise they get defined in each .c file where the .h file is included * it is recommended to use a prefix for all global variables and functions (which are not declared static) in order to avoid symbol conflicts. It can be the module name or a short form of it (e.g., `sec`)
One thing that is not something critical, but maybe worth discussing it before merging: is the name of the module `security` too generic for what the modules does? It does mainly filters, so eventually can be named `secfilter` (from security filter) in order to suggest better its purpose... I would like to avoid very generic names that can induce the perception it does everything expected in the scope of that name.
@Pepelux pushed 7 commits.
cb37158884016320912bdcb3b3c9377a1956edde Rename module from security to secfilter 61cd525dc0355769d6d91e8f1a44d0d1bcaa69d3 Rename module to secfilter (doc files) 417e8d815c154d39a7fee1d428d5e6f02b5e09f8 Rename module to secfilter (schema) 722cebdb11c627fdf726fed1c8686856241d0619 Rename module to secfilter (schema) fc17aca5f77d353df39a053cb15cf41361a3dbfa Rename module to secfilter (database create scripts) 7b622ae03785606686228435a6b31fde7d2bab75 Rename module to secfilter (database create scripts) 83dbb963d7be1d1157c79eaae0f25b6b2070f3cc Change references from security to secfilter. Declare global variables in the .c file. Create sec_ prefix for non static variables
Thanks for the quick response
- Module has been renamed to secfilter - Global variables are now declared in the .c files - All non static variables now have _sec__ prefix
Thank you for the contribution. I will also do a quick review of the code today.
@Pepelux pushed 1 commit.
5e65a792724d8bdd08e8769849b0152f94168b53 Fix variable name adding sec_ prefix
henningw commented on this pull request.
Some generic comments, which should be easy to fix:
* Good code structure and good error handling * rename the "security.*" file as well to secfilter.*, including the doc sources * rename the security_rpc.* files as well to secfilter_rpc.* * Fix all the remaining security strings in code and logs
Some more detailed comments, which are harder to fix:
I would suggest to unify all the different check_sqli* functions into one, that is then called with a simple pseudo-variable. This way you save a lot of implementation effort, and your function can easily work with all kind of headers. These different functions are in the end almost identical. I have also added some more review comments to the respective functions. If you don't like this for some reason, then at least unify the functions that the common parts are not redundant, e.g. implement a common sqli_check utility function which is then called from all the different functions. If you need more input about this approach, let me know.
I did not understand how the w_check* functions actually work. You seem to not do something with the SIP msg structure, or did I understood it wrong?
What happens if you start the module, it loads data from the database and strdup all strings. Then you execute a reload RPC comment, it will load the data again and again strdup all strings, or I am wrong? This way you will have a big memory leak. You need to cleanup in the reload the string memory that you allocated earlier.
There is also a logic error related to the memory handling. If you allocate the arrays in shm memory, you can't allocate the strings with malloc (via strdup). There are utility functions in the core to copy a string to shm memory.
A last comment, If I understood it correctly, your arrays are allocated with a fixed size of 1024. Then you should detect this in the db_load function, and stop loading after the arrays are full (with an error). Otherwise you will overrun the memory area. You should also document this limitation in the README.
Again, if you need some more feedback or some hints, we can also discuss on our sr-dev developer list.
- */
+ +#include <string.h> + +#include "../../core/dprint.h" +#include "../../core/mem/mem.h" +#include "../../core/parser/parse_uri.h" +#include "../../core/parser/parse_param.h" +#include "../../core/parser/parse_from.h" +#include "../../core/parser/parse_to.h" +#include "../../core/parser/contact/parse_contact.h" +#include "../../core/sr_module.h" +#include "secfilter.h" + + +int count_chars(char *val, char c);
forward definition not needed here
+ if (strstr(val, "'") || strstr(val, """) || strstr(val, "--") || strstr(val, "=") || strstr(val, "#") || strstr(val, "+") || strstr(val, "%27") || strstr(val, "%24") || strstr(val, "%60")) + { + LM_ERR("Possible SQLi detected in to domain (%s)\n", val); + pkg_free(val); + return 0; + } + } + } + + if (val) pkg_free(val); + return 1; +} + +/* Search for illegal characters in From header */ +int check_sqli_from(struct sip_msg *msg)
unify to one function, that uses $fn, $fd PVs in the kamailio configuration
if (strstr(val, "'") || strstr(val, "\"") || strstr(val, "--") || strstr(val, "=") || strstr(val, "#") || strstr(val, "+") || strstr(val, "%27") || strstr(val, "%24") || strstr(val, "%60"))
+ { + LM_ERR("Possible SQLi detected in from domain (%s)\n", val); + pkg_free(val); + return 0; + } + } + } + + if (val) pkg_free(val); + return 1; +} + + +/* Search for illegal characters in Contact header */ +int check_sqli_contact(struct sip_msg *msg)
See above, could be done with PVs and transformations: $ct{uri.domain}, $ct{uri.user}
+int count_chars(char *val, char c)
+{ + int cont = 0; + int i; + + for (i = 0; i < strlen(val); i++) + { + if (val[i] == c) cont++; + } + + return cont; +} + + +/* Search for illegal characters in User-agent header */ +int check_sqli_ua(struct sip_msg *msg)
See below, PV $ua
- val[msg->user_agent->body.len] = '\0';
+ + if (strstr(val, "'") || strstr(val, """) || strstr(val, "--") || strstr(val, "=") || strstr(val, "#") || strstr(val, "%27") || strstr(val, "%24") || strstr(val, "%60")) + { + LM_ERR("User-agent header (%s) has illegal characters. Posible SQLi\n", val); + pkg_free(val); + return 0; + } + + if (val) pkg_free(val); + return 1; +} + + +/* Search for illegal characters in To header */ +int check_sqli_to(struct sip_msg *msg)
See below, there is a PV also for To header $tu
+};
+ +/* Exported module parameters */ +static param_export_t params[]={ + {"db_url", PARAM_STRING, &db_url}, + {"table_name", PARAM_STR, &table_name }, + {"action_col", PARAM_STR, &action_col }, + {"type_col", PARAM_STR, &type_col }, + {"data_col", PARAM_STR, &data_col }, + {"dst_exact_match", PARAM_INT, &dst_exact_match }, + {0, 0, 0} +}; + +/* Module exports definition */ +struct module_exports exports={ + "security", /* module name */
Old name
+void uppercase(char *sPtr)
+{ + while(*sPtr != '\0') + { + *sPtr = toupper((unsigned char) *sPtr); + ++sPtr; + } +} + + +/* Module init function */ +static int mod_init(void) +{ + int i; + + LM_INFO("SECURITY module init\n");
Name
+ +/* Module child init function */ +static int child_init(int rank) +{ + if (rank==PROC_INIT || rank==PROC_MAIN || rank==PROC_TCP_MAIN) + return 0; /* do nothing for the main process */ + + return 1; +} + + +/* Module destroy function */ +static void mod_destroy(void) +{ + LM_INFO("SECURITY module destroy\n");
Name
+#include <string.h>
+ +#include "security.h" + + +/* RPC commands */ + +/* Add blacklist values to database */ +void rpc_add_bl(rpc_t *rpc, void *ctx) +{ + char *type; + char *value; + + if(rpc->scan(ctx, "ss", (char*)(&type), (char*)(&value))<2) + { + rpc->fault(ctx, 0, "Invalid Parameters. Usage: security.add_bl type value\n Example: security.add_bl user sipvicious");
Name
{
+ rpc->rpl_printf(ctx, "Error insert values in blacklist table"); + } + } +} + + +/* Add whitelist values to database */ +void rpc_add_wl(rpc_t *rpc, void *ctx) +{ + char *type; + char *value; + + if(rpc->scan(ctx, "ss", (char*)(&type), (char*)(&value))<2) + { + rpc->fault(ctx, 0, "Invalid Parameters. Usage: security.add_wl type value\n Example: security.add_wl user trusted_user");
Name
@@ -0,0 +1,64 @@
+#ifndef _SECURITY_H
Name
+static int w_check_ua(struct sip_msg* msg, char *val);
+static int w_check_domain(struct sip_msg* msg, char *val); +static int w_check_country(struct sip_msg* msg, char *val); +static int w_check_user(struct sip_msg* msg, char *val); +static int w_check_ip(struct sip_msg* msg, char *val); +static int w_check_dst(struct sip_msg* msg, char *val); +static int w_check_sqli(struct sip_msg* msg); + +/* Database variables */ +db_func_t db_funcs; /* Database API functions */ +db1_con_t* db_handle=0; /* Database connection handle */ + +/* Exported module parameters - default values */ +int dst_exact_match = 1; +str db_url = {NULL, 0}; +str table_name = str_init("security");
table name
+/* RPC exported commands */ +static const char *rpc_reload_doc[2] = { + "Reload values from database", NULL}; + +static const char *rpc_print_doc[2] = { + "Print values from database", NULL}; + +static const char *rpc_add_bl_doc[2] = { + "Add new values to blacklist", NULL}; + +static const char *rpc_add_wl_doc[2] = { + "Add new values to whitelist", NULL}; + +rpc_export_t security_rpc[] = { + { "security.reload", rpc_reload, rpc_reload_doc, 0},
Command names
+#include "../../core/dprint.h"
+#include "../../core/mem/mem.h" +#include "../../core/parser/parse_uri.h" +#include "../../core/parser/parse_param.h" +#include "../../core/parser/parse_from.h" +#include "../../core/parser/parse_to.h" +#include "../../core/parser/contact/parse_contact.h" +#include "../../core/sr_module.h" +#include "security.h" + + +int count_chars(char *val, char c); + + +/* Count chars of a string */ +int count_chars(char *val, char c)
add a prefix here as well, there is already a count_chars() in another module
- */
+ +#include <string.h> + +#include "../../core/dprint.h" +#include "../../core/mem/mem.h" +#include "../../core/parser/parse_uri.h" +#include "../../core/parser/parse_param.h" +#include "../../core/parser/parse_from.h" +#include "../../core/parser/parse_to.h" +#include "../../core/parser/contact/parse_contact.h" +#include "../../core/sr_module.h" +#include "security.h" + + +int count_chars(char *val, char c);
forward definition not needed here
+#include "../../core/dprint.h"
+#include "../../core/mem/mem.h" +#include "../../core/parser/parse_uri.h" +#include "../../core/parser/parse_param.h" +#include "../../core/parser/parse_from.h" +#include "../../core/parser/parse_to.h" +#include "../../core/parser/contact/parse_contact.h" +#include "../../core/sr_module.h" +#include "secfilter.h" + + +int count_chars(char *val, char c); + + +/* Count chars of a string */ +int count_chars(char *val, char c)
redundant implementation, move this to a header file
LM_DBG("No data found in database\n");
+ if (db_res!=NULL && db_funcs.free_result(db_handle, db_res) < 0) + LM_DBG("Failed to free the result\n"); + db_funcs.close(db_handle); + return 0; + } + + /* Add values to array */ + for (i=0; i<rows; i++) + { + /* Blacklist */ + if (action == 0) + { + if (!strcmp(ctype, "ua")) + { + sec_bl_ua_list[*sec_nblUa]=strtok(strdup((char*)RES_ROWS(db_res)[i].values[0].val.string_val), "\n");
strdup works with malloc, we need here shm_malloc as well
+ /* Add values to array */ + for (i=0; i<rows; i++) + { + /* Blacklist */ + if (action == 0) + { + if (!strcmp(ctype, "ua")) + { + sec_bl_ua_list[*sec_nblUa]=strtok(strdup((char*)RES_ROWS(db_res)[i].values[0].val.string_val), "\n"); + uppercase(sec_bl_ua_list[*sec_nblUa]); + *sec_nblUa = *sec_nblUa + 1; + } + if (!strcmp(ctype, "country")) + { + sec_bl_country_list[*sec_nblCountry]=strtok(strdup((char*)RES_ROWS(db_res)[i].values[0].val.string_val), "\n");
See above, for all strupd() calls
Thank you very much for the review. I am working on solving each point.
I would also like to suggest to replace that `1024` in the code with a define, it makes it easier to change if proves to be a small number.
Then the functions exported to the config should be also prefixed with `sec_` or `secf_` (e.g., secf_check_ua(...)) -- in the past few years, the functions exported by modules to config use a common prefix to be suggestive where they belong to.
@Pepelux pushed 1 commit.
d1ea1e1db9e389cda3af5c8530466c6cbe785f28 Several changes
@Pepelux pushed 1 commit.
929295966582d6ebdf17db34c0cb1385cb74be32 fixes
All code have beed reprogrammed from scratch. Now:
- No limits to store data into blacklist or whitelist - All module functions starts with secf_ - Change of data type and now strlist is used instead of char ** - It is possible to check SQLi in several headers and it is also possible to check only a value - For functions that check headers, they are obtained from the structure sip_msg
Thanks a lot to @linuxmaniac for the help
I haven't gone into deep analysis, but the new code looks clean/well indented, exported functions have prefix, ..., so from my point of view, I am happy to merge it. Bugs can be discovered and fixed later, usually they pop up quicker when using it rather than reading code. If someone else wants to read more the code, can do it.
Anyhow, I would like to merge it manually (keeping you as the author), not from github UI (not even squashing), to have single commits for the latest version and per component. Once merged, you will get commit access so fixes and enhancements can be pushed directly by you.
@Pepelux Thank you for the new version, this looks really a lot better! No objections from my side for merge into the git master. @miconda - let me know if I should take care of this.
@henningw - thanks for your help here, as I said before, I wanted to merge it manually to split it in commits per component.
@Pepelux - I just merged the module, you should get soon an invitation on github account to join the dev team. You will be able to push commits directly, you can still use pull requests if you want others to have a look before merging. Pull requests are still recommended if you have commits for other components than secfilter module. Thanks for the contribution!
Closed #1755.
thanks all