├── README.md ├── config └── ngx_http_csrf_prevent_filter_module.c /README.md: -------------------------------------------------------------------------------- 1 | ## Prevent CSRF with nginx 2 | 3 | This is a simple nginx module which compares either the referer or the origin header to the host header. If the domain name doesn't match, HTTP response 403 is returned. This action takes place before the request is processed and will terminate any additional processing. If the request lacks both a referer and a origin header, no action will be taken. 4 | 5 | To activate this check, place `csrf_prevent on;` in the block you wish to protect. You can put it in the `server`, `location`, or `if` blocks. If activated in a parent block, all child blocks will also be protected. You can disable the check for a specific block with `csrf_prevent off;`. 6 | 7 | ## Examples 8 | 9 | Apply to all requests 10 | 11 | ``` 12 | location / { 13 | csrf_prevent on; 14 | proxy_pass http://localhost:5000/; 15 | } 16 | ``` 17 | 18 | Only apply to POST: 19 | 20 | ``` 21 | location / { 22 | if ($request_method = POST) 23 | { 24 | csrf_prevent on; 25 | } 26 | proxy_pass http://localhost:5000/; 27 | } 28 | ``` 29 | 30 | ## Installation 31 | 32 | In order to install a nginx module, you'll have to compile nginx from source. Download the source from https://nginx.org/en/download.html. You'll also need to clone this repo. Inside the nginx source directory run `./configure --add-module=/path/to/my-module`. If you have nginx currently installed, you can get the existing configure options with `nginx -V`. It's annoying that you have to recompile nginx to add a module, but I don't have any other way. 33 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | 2 | ngx_addon_name=ngx_http_csrf_prevent_filter_module 3 | HTTP_MODULES="$HTTP_MODULES $ngx_addon_name" 4 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_csrf_prevent_filter_module.c" 5 | -------------------------------------------------------------------------------- /ngx_http_csrf_prevent_filter_module.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef struct { 7 | ngx_flag_t enable; 8 | } ngx_csrf_prevent_conf_t; 9 | 10 | static ngx_int_t ngx_http_csrf_prevent_filter_init(ngx_conf_t *cf); 11 | static void* ngx_http_csrf_prevent_filter_create_conf(ngx_conf_t *cf); 12 | static char* ngx_http_csrf_prevent_filter_merge_conf(ngx_conf_t *cf, void* parent, void* child); 13 | 14 | 15 | static ngx_command_t ngx_http_csrf_prevent_filter_commands[] = { 16 | { 17 | ngx_string("csrf_prevent"), 18 | NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_FLAG, 19 | ngx_conf_set_flag_slot, 20 | NGX_HTTP_LOC_CONF_OFFSET, 21 | offsetof(ngx_csrf_prevent_conf_t, enable), 22 | NULL 23 | 24 | }, 25 | 26 | ngx_null_command 27 | }; 28 | 29 | 30 | static ngx_http_module_t ngx_http_csrf_prevent_filter_module_ctx = { 31 | NULL, /* preconfiguration */ 32 | ngx_http_csrf_prevent_filter_init, /* postconfiguration */ 33 | 34 | NULL, /* create main configuration */ 35 | NULL, /* init main configuration */ 36 | 37 | NULL, /* create server configuration */ 38 | NULL, /* merge server configuration */ 39 | 40 | ngx_http_csrf_prevent_filter_create_conf, /* create location configuration */ 41 | ngx_http_csrf_prevent_filter_merge_conf /* merge location configuration */ 42 | }; 43 | 44 | 45 | ngx_module_t ngx_http_csrf_prevent_filter_module = { 46 | NGX_MODULE_V1, 47 | &ngx_http_csrf_prevent_filter_module_ctx, /* module context */ 48 | ngx_http_csrf_prevent_filter_commands, /* module directives */ 49 | NGX_HTTP_MODULE, /* module type */ 50 | NULL, /* init master */ 51 | NULL, /* init module */ 52 | NULL, /* init process */ 53 | NULL, /* init thread */ 54 | NULL, /* exit thread */ 55 | NULL, /* exit process */ 56 | NULL, /* exit master */ 57 | NGX_MODULE_V1_PADDING 58 | }; 59 | 60 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 61 | 62 | //from https://www.nginx.com/resources/wiki/start/topics/examples/headers_management/ 63 | static ngx_table_elt_t * 64 | search_headers_in(ngx_http_request_t *r, u_char *name, size_t len) { 65 | ngx_list_part_t *part; 66 | ngx_table_elt_t *h; 67 | ngx_uint_t i; 68 | 69 | /* 70 | Get the first part of the list. There is usual only one part. 71 | */ 72 | part = &r->headers_in.headers.part; 73 | h = part->elts; 74 | 75 | /* 76 | Headers list array may consist of more than one part, 77 | so loop through all of it 78 | */ 79 | for (i = 0; /* void */ ; i++) { 80 | if (i >= part->nelts) { 81 | if (part->next == NULL) { 82 | /* The last part, search is done. */ 83 | break; 84 | } 85 | 86 | part = part->next; 87 | h = part->elts; 88 | i = 0; 89 | } 90 | 91 | /* 92 | Just compare the lengths and then the names case insensitively. 93 | */ 94 | if (len != h[i].key.len || ngx_strcasecmp(name, h[i].key.data) != 0) { 95 | /* This header doesn't match. */ 96 | continue; 97 | } 98 | 99 | /* 100 | Ta-da, we got one! 101 | Note, we'v stop the search at the first matched header 102 | while more then one header may fit. 103 | */ 104 | return &h[i]; 105 | } 106 | 107 | /* 108 | No headers was found 109 | */ 110 | return NULL; 111 | } 112 | 113 | ngx_int_t 114 | prefix_cmp(const u_char* s1, const u_char* s2) 115 | { 116 | while(*s1) 117 | { 118 | if (*s1 == *s2) 119 | { 120 | ++s1; 121 | ++s2; 122 | } 123 | else 124 | return 0; 125 | } 126 | return 1; 127 | } 128 | 129 | static ngx_int_t 130 | ngx_http_csrf_prevent_header_filter(ngx_http_request_t *r) 131 | { 132 | ngx_csrf_prevent_conf_t *conf; 133 | conf = ngx_http_get_module_loc_conf(r, ngx_http_csrf_prevent_filter_module); 134 | if (!conf->enable) 135 | return NGX_OK; 136 | 137 | ngx_http_headers_in_t headers = r->headers_in; 138 | u_char* host = headers.host->value.data; 139 | //ngx_log_stderr(0, "Host: %s", host); 140 | 141 | u_char* str = NULL; 142 | if (r->headers_in.referer) 143 | { 144 | str = r->headers_in.referer->value.data; 145 | //ngx_log_stderr(0, "Referer set: %s", str); 146 | } 147 | else 148 | { 149 | u_char *origin_str = (u_char *) "Origin"; 150 | ngx_table_elt_t* origin = search_headers_in(r, origin_str, ngx_strlen(origin_str)); 151 | if (origin) 152 | { 153 | str = origin->value.data; 154 | //ngx_log_stderr(0, "Origin set: %s", str); 155 | } 156 | } 157 | 158 | if (str) 159 | { 160 | //+1 for null terminator 161 | ngx_int_t len = ngx_strlen(str) + 1; 162 | u_char* new = ngx_alloc(len, r->connection->log); 163 | 164 | int offset = 0; 165 | 166 | if (prefix_cmp((u_char *)"http://", str)) 167 | offset = 7; 168 | else if (prefix_cmp((u_char *)"https://", str)) 169 | offset = 8; 170 | 171 | ngx_memcpy(new, str + offset, len-offset); 172 | 173 | u_char* pos = (u_char*) ngx_strchr(new, '/'); 174 | if (pos) 175 | *pos = '\0'; 176 | 177 | //ngx_log_stderr(0, "Resulting string: %s", new); 178 | 179 | //return 403 if our new != host 180 | if (ngx_strcmp(new, host) != 0) 181 | { 182 | ngx_free(new); 183 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "**possible CSRF attempt**"); 184 | return NGX_HTTP_FORBIDDEN; 185 | } 186 | ngx_free(new); 187 | } 188 | 189 | 190 | return NGX_OK; 191 | } 192 | 193 | 194 | static ngx_int_t 195 | ngx_http_csrf_prevent_filter_init(ngx_conf_t *cf) 196 | { 197 | /* 198 | ngx_http_next_header_filter = ngx_http_top_header_filter; 199 | ngx_http_top_header_filter = ngx_http_csrf_prevent_header_filter; 200 | */ 201 | 202 | ngx_http_handler_pt *h; 203 | ngx_http_core_main_conf_t *cmcf; 204 | 205 | cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 206 | 207 | h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); 208 | if (h == NULL) { 209 | return NGX_ERROR; 210 | } 211 | 212 | *h = ngx_http_csrf_prevent_header_filter; 213 | 214 | return NGX_OK; 215 | } 216 | 217 | static void * 218 | ngx_http_csrf_prevent_filter_create_conf(ngx_conf_t *cf) 219 | { 220 | ngx_csrf_prevent_conf_t *conf; 221 | 222 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_csrf_prevent_conf_t)); 223 | if (conf == NULL) { 224 | return NULL; 225 | } 226 | 227 | conf->enable = NGX_CONF_UNSET; 228 | 229 | return conf; 230 | } 231 | 232 | static char * 233 | ngx_http_csrf_prevent_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) 234 | { 235 | ngx_csrf_prevent_conf_t *prev = parent; 236 | ngx_csrf_prevent_conf_t *conf = child; 237 | 238 | ngx_conf_merge_value(conf->enable, prev->enable, 0); 239 | 240 | return NGX_CONF_OK; 241 | } 242 | 243 | 244 | --------------------------------------------------------------------------------