Skip to content

Commit f53eab5

Browse files
Mikhail EfremovMikhail Efremov
authored andcommitted
Add capture of request_body value to substitute in postgres_query
1 parent 1b95970 commit f53eab5

8 files changed

+592
-13
lines changed

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,32 @@ all empty strings are by default escaped to `NULL` value. This behavior can be
145145
disabled by prefixing `$unescaped` string with `=` sign.
146146

147147

148+
postgres_escape_request_body
149+
-----------------------
150+
* **syntax**: `postgres_request_body_escape on/off`
151+
* **default**: `on`
152+
* **context**: `location`
153+
154+
The flag provides to double each single quote symbol
155+
in the complex SQL statements that contain substitutions from client http request body.
156+
Disabling this option is pretty dangerous.
157+
158+
Please make sure you are NOT using postgres configuration option `standard_conforming_strings: off` (default for PostgreSQL < 9.1).
159+
Using `standard_conforming_strings: off` will make completely worthless the injection protection and allows an attacker to use control characters to change a query string.
160+
161+
To check this option use: (correct setting below)
162+
```
163+
postgres=# SHOW standard_conforming_strings;
164+
165+
Set timeout for receiving result from the database.
166+
167+
standard_conforming_strings
168+
-----------------------------
169+
on
170+
(1 row)
171+
```
172+
173+
148174
postgres_connect_timeout
149175
------------------------
150176
* **syntax**: `postgres_connect_timeout timeout`
@@ -357,6 +383,24 @@ Required modules (other than `ngx_postgres`):
357383

358384
- [ngx_set_misc](http://github.com/agentzh/set-misc-nginx-module).
359385

386+
387+
Sample configuration #7
388+
-----------------------
389+
Use POST data to parameter in SQL query.
390+
391+
location /sms/(?<id>\d+) {
392+
postgres_pass database;
393+
postgres_escape $id;
394+
postgres_query POST "UPDATE sms SET sms_recipt = '$request_body' WHERE id=$id RETURNING 'ACK'";
395+
postgres_rewrite POST changes 200;
396+
postgres_rewrite POST no_changes 410;
397+
postgres_output value;
398+
}
399+
400+
The variable `request_body` cannot be processed by the `postgres_escape` directive during the rewrite phase. Therefore, for security reasons, the `request_body` value that is used in `postgres_query` is always processing to escape string literals during the content phase without the need for `postgres_escape`.
401+
402+
This behavior can be disabled by directive `postgres_escape_request_body off`.
403+
360404
Testing
361405
=======
362406
`ngx_postgres` comes with complete test suite based on [Test::Nginx](http://github.com/agentzh/test-nginx).

src/ngx_postgres_escape.c

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@
3131
#include "ngx_postgres_ddebug.h"
3232
#include "ngx_postgres_escape.h"
3333
#include "ngx_postgres_module.h"
34+
#include "ngx_postgres_util.h"
3435

3536
#include <libpq-fe.h>
3637

37-
38+
#define SINGLE_QUOTE_CHAR 39 /* ASCII code of Apostrophe = 39 */
3839
uintptr_t ngx_postgres_script_exit_code = (uintptr_t) NULL;
3940

4041

@@ -95,3 +96,134 @@ ngx_postgres_escape_string(ngx_http_script_engine_t *e)
9596
v->no_cacheable = 0;
9697
v->not_found = 0;
9798
}
99+
100+
101+
/* Purpose: calculate new length (with doubled quotes) for variable value.
102+
* This method returns number of the single qoute characters (apostrophes)
103+
* plus original length of text value */
104+
size_t
105+
ngx_postgres_upstream_var_len_with_quotes(ngx_http_script_engine_t *e)
106+
{
107+
size_t i;
108+
size_t result;
109+
size_t count;
110+
ngx_http_script_var_code_t *code;
111+
ngx_http_variable_value_t *value;
112+
ngx_http_core_main_conf_t *cmcf;
113+
ngx_http_variable_t *v;
114+
ngx_http_request_t *r;
115+
116+
dd("entering");
117+
result = 0;
118+
count = 0;
119+
120+
if ((e != NULL) && (e->ip != NULL) && (e->request != NULL)) {
121+
code = (ngx_http_script_var_code_t *) e->ip;
122+
e->ip += sizeof(ngx_http_script_var_code_t);
123+
124+
if (!e->skip) {
125+
if (e->flushed) {
126+
value = ngx_http_get_indexed_variable(e->request, code->index);
127+
128+
} else {
129+
value = ngx_http_get_flushed_variable(e->request, code->index);
130+
}
131+
132+
if ((value != NULL) && (!value->not_found)) {
133+
r = e->request;
134+
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
135+
v = cmcf->variables.elts;
136+
137+
if (v[code->index].get_handler ==
138+
(ngx_http_get_variable_pt)ngx_postgres_rewrite_var) {
139+
/* Quotes already escaped. Do nothing. */
140+
return value->len;
141+
}
142+
143+
for (i = 0; i < value->len; i++) {
144+
if (value->data[i] == SINGLE_QUOTE_CHAR) {
145+
count++;
146+
}
147+
}
148+
149+
result = value->len + count;
150+
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log,
151+
0, "http script: postgres: count to replace = %d", count);
152+
}
153+
}
154+
}
155+
dd("returning");
156+
157+
return result;
158+
}
159+
160+
161+
/* Purpose: replace quotes in variable values before substitute them in a query
162+
* This method doubles the single qoute character (example: Can't -> Can''t) */
163+
void
164+
ngx_postgres_upstream_replace_quotes(ngx_http_script_engine_t *e)
165+
{
166+
int i;
167+
int k;
168+
u_char *p;
169+
u_char *data;
170+
ngx_http_script_var_code_t *code;
171+
ngx_http_variable_value_t *value;
172+
ngx_http_core_main_conf_t *cmcf;
173+
ngx_http_variable_t *v;
174+
ngx_http_request_t *r;
175+
176+
dd("entering");
177+
178+
if ((e != NULL) && (e->ip != NULL) && (e->request != NULL)) {
179+
code = (ngx_http_script_var_code_t *) e->ip;
180+
181+
e->ip += sizeof(ngx_http_script_var_code_t);
182+
183+
if (!e->skip) {
184+
if (e->flushed) {
185+
value = ngx_http_get_indexed_variable(e->request, code->index);
186+
187+
} else {
188+
value = ngx_http_get_flushed_variable(e->request, code->index);
189+
}
190+
191+
if ((value != NULL) && (!value->not_found)
192+
&& (e->buf.data != NULL)) {
193+
p = e->pos;
194+
data = value->data;
195+
196+
r = e->request;
197+
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
198+
v = cmcf->variables.elts;
199+
200+
if (v[code->index].get_handler ==
201+
(ngx_http_get_variable_pt) ngx_postgres_rewrite_var) {
202+
/* Quotes already escaped. Do nothing. */
203+
return;
204+
}
205+
206+
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
207+
"http script: postgres: replace quotes in $%V (\"%v\")",
208+
&v[code->index].name, value);
209+
210+
/* copy var value like ngx_http_script_copy_var_code(e) does */
211+
for (i = 0, k = 0; i < value->len; i++, k++) {
212+
/* doubles all the qoute characters,
213+
* do not double backslashes */
214+
if (data[i] == SINGLE_QUOTE_CHAR) {
215+
p[k] = data[i];
216+
k++;
217+
}
218+
219+
p[k] = data[i];
220+
}
221+
222+
e->pos += k;
223+
}
224+
}
225+
}
226+
227+
dd("returning");
228+
}
229+

src/ngx_postgres_escape.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <ngx_http.h>
3232

3333

34-
void ngx_postgres_escape_string(ngx_http_script_engine_t *);
35-
34+
void ngx_postgres_escape_string(ngx_http_script_engine_t *);
35+
size_t ngx_postgres_upstream_var_len_with_quotes(ngx_http_script_engine_t *e);
36+
void ngx_postgres_upstream_replace_quotes(ngx_http_script_engine_t *e);
3637
#endif /* _NGX_POSTGRES_ESCAPE_H_ */

src/ngx_postgres_handler.c

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ ngx_postgres_handler(ngx_http_request_t *r)
4545
ngx_postgres_ctx_t *pgctx;
4646
ngx_http_core_loc_conf_t *clcf;
4747
ngx_http_upstream_t *u;
48-
ngx_connection_t *c;
4948
ngx_str_t host;
5049
ngx_url_t url;
5150
ngx_int_t rc;
@@ -82,12 +81,6 @@ ngx_postgres_handler(ngx_http_request_t *r)
8281
return NGX_HTTP_INTERNAL_SERVER_ERROR;
8382
}
8483

85-
rc = ngx_http_discard_request_body(r);
86-
if (rc != NGX_OK) {
87-
dd("returning rc:%d", (int) rc);
88-
return rc;
89-
}
90-
9184
#if defined(nginx_version) \
9285
&& (((nginx_version >= 7063) && (nginx_version < 8000)) \
9386
|| (nginx_version >= 8007))
@@ -209,8 +202,110 @@ ngx_postgres_handler(ngx_http_request_t *r)
209202
r->main->count++;
210203
#endif
211204

212-
ngx_http_upstream_init(r);
205+
rc = (ngx_int_t) ngx_postgres_read_req_body(r);
206+
207+
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
208+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
209+
"postgres: Where is a special response while reading request body. "
210+
"Return code rc=%d", rc);
211+
return rc;
212+
}
213+
214+
return NGX_DONE;
215+
}
216+
217+
218+
ngx_int_t
219+
ngx_postgres_read_req_body(ngx_http_request_t *r)
220+
{
221+
ngx_int_t rc;
222+
ngx_postgres_ctx_t *ctx;
223+
rc = NGX_OK;
224+
225+
if (r == NULL) {
226+
/* postgres: Error. First argument (ngx_http_request_t *r) in function
227+
ngx_postgres_read_req_body() can not be NULL (r = NULL) */
228+
return NGX_HTTP_INTERNAL_SERVER_ERROR;
229+
}
230+
231+
r->request_body_in_single_buf = 1;
232+
r->request_body_in_persistent_file = 1;
233+
r->request_body_in_clean_file = 1;
234+
235+
#if 1
236+
if (r->request_body_in_file_only) {
237+
r->request_body_file_log_level = 0;
238+
}
239+
#endif
240+
241+
ctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
242+
if (ctx == NULL) {
243+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
244+
"postgres: Error. Unable to get module context. "
245+
"Method ngx_http_get_module_ctx(r, ngx_postgres_module) "
246+
"returns ctx == NULL", rc);
247+
return NGX_HTTP_INTERNAL_SERVER_ERROR;
248+
}
249+
250+
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
251+
"postgres: start to read buffered request body");
252+
253+
rc = ngx_http_read_client_request_body(r, ngx_postgres_body_handler);
213254

255+
#if (nginx_version < 1002006) || \
256+
(nginx_version >= 1003000 && nginx_version < 1003009)
257+
r->main->count--;
258+
#endif
259+
260+
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
261+
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
262+
"postgres: http read client request body returned error code %i", rc);
263+
264+
return NGX_DONE;
265+
}
266+
267+
#if (nginx_version >= 1002006 && nginx_version < 1003000) || \
268+
nginx_version >= 1003009
269+
r->main->count--;
270+
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
271+
"postgres: decrement r->main->count: %d", (int) r->main->count);
272+
#endif
273+
274+
if (rc == NGX_AGAIN) {
275+
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
276+
"postgres: read buffered request body requires I/O interruptions");
277+
278+
ctx->waiting_more_body = 1;
279+
return NGX_AGAIN;
280+
}
281+
282+
/* rc == NGX_OK */
283+
284+
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
285+
"postgres: has read buffered request body in a single run");
286+
return NGX_DONE;
287+
}
288+
289+
290+
void
291+
ngx_postgres_body_handler(ngx_http_request_t *r)
292+
{
293+
ngx_postgres_ctx_t *ctx;
294+
ngx_http_upstream_t *u;
295+
ngx_connection_t *c;
296+
297+
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
298+
"postgres: req body post read, c:%ud", r->main->count);
299+
300+
ctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
301+
302+
if (ctx->waiting_more_body) {
303+
ctx->waiting_more_body = 0;
304+
r->read_event_handler = ngx_http_block_reading;
305+
}
306+
307+
ngx_http_upstream_init(r);
308+
u = r->upstream;
214309
/* override the read/write event handler to our own */
215310
u->write_event_handler = ngx_postgres_wev_handler;
216311
u->read_event_handler = ngx_postgres_rev_handler;
@@ -236,14 +331,15 @@ ngx_postgres_handler(ngx_http_request_t *r)
236331
#if defined(nginx_version) && (nginx_version >= 8017)
237332
NGX_HTTP_SERVICE_UNAVAILABLE);
238333
#else
239-
pgctx->status ? pgctx->status : NGX_HTTP_INTERNAL_SERVER_ERROR);
334+
ctx->status ? ctx->status : NGX_HTTP_INTERNAL_SERVER_ERROR);
240335
#endif
241336
}
242337

243338
dd("returning NGX_DONE");
244-
return NGX_DONE;
339+
return;
245340
}
246341

342+
247343
void
248344
ngx_postgres_wev_handler(ngx_http_request_t *r, ngx_http_upstream_t *u)
249345
{

src/ngx_postgres_handler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434

3535

3636
ngx_int_t ngx_postgres_handler(ngx_http_request_t *);
37+
ngx_int_t ngx_postgres_read_req_body(ngx_http_request_t *r);
38+
void ngx_postgres_body_handler(ngx_http_request_t *r);
3739
void ngx_postgres_wev_handler(ngx_http_request_t *,
3840
ngx_http_upstream_t *);
3941
void ngx_postgres_rev_handler(ngx_http_request_t *,

0 commit comments

Comments
 (0)