rack-libinjection 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +55 -0
- data/CHANGELOG.md +112 -0
- data/GET_STARTED.md +418 -0
- data/LICENSE-libinjection.txt +33 -0
- data/LICENSE.txt +21 -0
- data/README.md +68 -0
- data/SECURITY.md +65 -0
- data/ext/libinjection/extconf.rb +113 -0
- data/ext/libinjection/libinjection_ext.c +1132 -0
- data/ext/libinjection/vendor/libinjection/.vendored +5 -0
- data/ext/libinjection/vendor/libinjection/COPYING +33 -0
- data/ext/libinjection/vendor/libinjection/MIGRATION.md +393 -0
- data/ext/libinjection/vendor/libinjection/README.md +251 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection.h +70 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_error.h +26 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_html5.c +830 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_html5.h +56 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_sqli.c +2342 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_sqli.h +297 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_sqli_data.h +9651 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_xss.c +1203 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_xss.h +23 -0
- data/lib/libinjection/version.rb +6 -0
- data/lib/libinjection.rb +31 -0
- data/lib/rack/libinjection.rb +586 -0
- data/lib/rack-libinjection.rb +3 -0
- data/samples/README.md +67 -0
- data/samples/libinjection_detect_raw_hot_path.rb +161 -0
- data/samples/rack_all_surfaces_hot_path.rb +198 -0
- data/samples/rack_params_hot_path.rb +166 -0
- data/samples/rack_query_hot_path.rb +176 -0
- data/samples/results/.gitkeep +0 -0
- data/script/fuzz_smoke.rb +39 -0
- data/script/vendor_libs.rb +227 -0
- data/test/test_helper.rb +7 -0
- data/test/test_libinjection.rb +223 -0
- data/test/test_middleware.rb +404 -0
- metadata +148 -0
|
@@ -0,0 +1,1132 @@
|
|
|
1
|
+
#if defined(__clang__)
|
|
2
|
+
#pragma clang diagnostic push
|
|
3
|
+
#pragma clang diagnostic ignored "-Wunused-parameter"
|
|
4
|
+
#elif defined(__GNUC__)
|
|
5
|
+
#pragma GCC diagnostic push
|
|
6
|
+
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
#include "ruby.h"
|
|
10
|
+
#include "ruby/thread.h"
|
|
11
|
+
|
|
12
|
+
#if defined(__clang__)
|
|
13
|
+
#pragma clang diagnostic pop
|
|
14
|
+
#elif defined(__GNUC__)
|
|
15
|
+
#pragma GCC diagnostic pop
|
|
16
|
+
#endif
|
|
17
|
+
|
|
18
|
+
#include <string.h>
|
|
19
|
+
#include <stdlib.h>
|
|
20
|
+
|
|
21
|
+
#if defined(__clang__)
|
|
22
|
+
#pragma clang diagnostic push
|
|
23
|
+
#pragma clang diagnostic ignored "-Wunused-function"
|
|
24
|
+
#pragma clang diagnostic ignored "-Wunused-parameter"
|
|
25
|
+
#elif defined(__GNUC__)
|
|
26
|
+
#pragma GCC diagnostic push
|
|
27
|
+
#pragma GCC diagnostic ignored "-Wunused-function"
|
|
28
|
+
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
29
|
+
#endif
|
|
30
|
+
|
|
31
|
+
#include "libinjection.h"
|
|
32
|
+
#include "libinjection_html5.h"
|
|
33
|
+
#include "libinjection_sqli.h"
|
|
34
|
+
#include "libinjection_xss.h"
|
|
35
|
+
|
|
36
|
+
#if defined(__clang__)
|
|
37
|
+
#pragma clang diagnostic pop
|
|
38
|
+
#elif defined(__GNUC__)
|
|
39
|
+
#pragma GCC diagnostic pop
|
|
40
|
+
#endif
|
|
41
|
+
|
|
42
|
+
static VALUE mLibInjection;
|
|
43
|
+
static VALUE eError;
|
|
44
|
+
static VALUE eParserError;
|
|
45
|
+
static VALUE eArgumentError;
|
|
46
|
+
static VALUE sym_sqli;
|
|
47
|
+
static VALUE sym_xss;
|
|
48
|
+
|
|
49
|
+
#define LI_REQUIRED_LIBINJECTION_VERSION "4.0.0"
|
|
50
|
+
|
|
51
|
+
#define LI_ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
|
|
52
|
+
#define LI_SQLI_FINGERPRINT_SIZE (sizeof(((struct libinjection_sqli_state *)0)->fingerprint))
|
|
53
|
+
#ifndef LI_NOGVL_THRESHOLD
|
|
54
|
+
#define LI_NOGVL_THRESHOLD 1024
|
|
55
|
+
#endif
|
|
56
|
+
#ifndef LI_URL_DECODE_STACK_THRESHOLD
|
|
57
|
+
#define LI_URL_DECODE_STACK_THRESHOLD 8192
|
|
58
|
+
#endif
|
|
59
|
+
#if LI_URL_DECODE_STACK_THRESHOLD > 8192
|
|
60
|
+
#error "LI_URL_DECODE_STACK_THRESHOLD must stay <= 8192"
|
|
61
|
+
#endif
|
|
62
|
+
#ifndef LI_MAX_URL_DECODE_DEPTH
|
|
63
|
+
#define LI_MAX_URL_DECODE_DEPTH 32
|
|
64
|
+
#endif
|
|
65
|
+
|
|
66
|
+
struct li_named_int {
|
|
67
|
+
const char *name;
|
|
68
|
+
int value;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
struct li_named_char {
|
|
72
|
+
const char *name;
|
|
73
|
+
int value;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
static const struct li_named_int SQLI_CONTEXTS[] = {
|
|
77
|
+
{"none_ansi", FLAG_QUOTE_NONE | FLAG_SQL_ANSI},
|
|
78
|
+
{"none_mysql", FLAG_QUOTE_NONE | FLAG_SQL_MYSQL},
|
|
79
|
+
{"single_ansi", FLAG_QUOTE_SINGLE | FLAG_SQL_ANSI},
|
|
80
|
+
{"single_mysql", FLAG_QUOTE_SINGLE | FLAG_SQL_MYSQL},
|
|
81
|
+
{"double_ansi", FLAG_QUOTE_DOUBLE | FLAG_SQL_ANSI},
|
|
82
|
+
{"double_mysql", FLAG_QUOTE_DOUBLE | FLAG_SQL_MYSQL},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
static const struct li_named_int SQLI_QUOTES[] = {
|
|
86
|
+
{"none", FLAG_QUOTE_NONE},
|
|
87
|
+
{"single", FLAG_QUOTE_SINGLE},
|
|
88
|
+
{"double", FLAG_QUOTE_DOUBLE},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
static const struct li_named_int SQLI_DIALECTS[] = {
|
|
92
|
+
{"ansi", FLAG_SQL_ANSI},
|
|
93
|
+
{"mysql", FLAG_SQL_MYSQL},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
static const struct li_named_char SQLI_TOKEN_TYPES[] = {
|
|
97
|
+
{"keyword", 'k'}, {"union", 'U'}, {"group", 'B'}, {"expression", 'E'},
|
|
98
|
+
{"sqltype", 't'}, {"function", 'f'}, {"bareword", 'n'}, {"number", '1'},
|
|
99
|
+
{"variable", 'v'}, {"string", 's'}, {"operator", 'o'}, {"logic_operator", '&'},
|
|
100
|
+
{"comment", 'c'}, {"collate", 'A'}, {"left_parens", '('}, {"right_parens", ')'},
|
|
101
|
+
{"left_brace", '{'}, {"right_brace", '}'}, {"dot", '.'}, {"comma", ','},
|
|
102
|
+
{"colon", ':'}, {"semicolon", ';'}, {"tsql", 'T'}, {"unknown", '?'},
|
|
103
|
+
{"evil", 'X'}, {"fingerprint", 'F'}, {"backslash", '\\'},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
static const struct li_named_int HTML5_CONTEXTS[] = {
|
|
107
|
+
{"data", DATA_STATE},
|
|
108
|
+
{"value_no_quote", VALUE_NO_QUOTE},
|
|
109
|
+
{"value_single_quote", VALUE_SINGLE_QUOTE},
|
|
110
|
+
{"value_double_quote", VALUE_DOUBLE_QUOTE},
|
|
111
|
+
{"value_back_quote", VALUE_BACK_QUOTE},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
static const struct li_named_int HTML5_TOKEN_TYPES[] = {
|
|
115
|
+
{"data_text", DATA_TEXT},
|
|
116
|
+
{"tag_name_open", TAG_NAME_OPEN},
|
|
117
|
+
{"tag_name_close", TAG_NAME_CLOSE},
|
|
118
|
+
{"tag_name_selfclose", TAG_NAME_SELFCLOSE},
|
|
119
|
+
{"tag_data", TAG_DATA},
|
|
120
|
+
{"tag_close", TAG_CLOSE},
|
|
121
|
+
{"attr_name", ATTR_NAME},
|
|
122
|
+
{"attr_value", ATTR_VALUE},
|
|
123
|
+
{"tag_comment", TAG_COMMENT},
|
|
124
|
+
{"doctype", DOCTYPE},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
static VALUE li_str(VALUE input) {
|
|
128
|
+
StringValue(input);
|
|
129
|
+
return input;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
typedef struct {
|
|
133
|
+
VALUE str;
|
|
134
|
+
const char *ptr;
|
|
135
|
+
size_t len;
|
|
136
|
+
char *copy;
|
|
137
|
+
} li_input_t;
|
|
138
|
+
|
|
139
|
+
static li_input_t li_input_prepare(VALUE input) {
|
|
140
|
+
li_input_t in;
|
|
141
|
+
in.str = li_str(input);
|
|
142
|
+
in.len = (size_t)RSTRING_LEN(in.str);
|
|
143
|
+
in.copy = NULL;
|
|
144
|
+
|
|
145
|
+
if (in.len == 0) {
|
|
146
|
+
in.ptr = RSTRING_PTR(in.str);
|
|
147
|
+
return in;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (in.len >= (size_t)LI_NOGVL_THRESHOLD) {
|
|
151
|
+
in.copy = (char *)ruby_xmalloc(in.len);
|
|
152
|
+
memcpy(in.copy, RSTRING_PTR(in.str), in.len);
|
|
153
|
+
in.ptr = in.copy;
|
|
154
|
+
} else {
|
|
155
|
+
in.ptr = RSTRING_PTR(in.str);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return in;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
static void li_input_release(li_input_t *in) {
|
|
162
|
+
if (in->copy != NULL) {
|
|
163
|
+
ruby_xfree(in->copy);
|
|
164
|
+
in->copy = NULL;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
static VALUE li_id_sym(const char *name) {
|
|
169
|
+
return ID2SYM(rb_intern(name));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
static VALUE li_hash_aref(VALUE hash, const char *key) {
|
|
173
|
+
if (NIL_P(hash)) {
|
|
174
|
+
return Qundef;
|
|
175
|
+
}
|
|
176
|
+
return rb_hash_lookup2(hash, li_id_sym(key), Qundef);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static const char *li_key_name(VALUE key) {
|
|
180
|
+
if (SYMBOL_P(key)) {
|
|
181
|
+
return rb_id2name(SYM2ID(key));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
StringValue(key);
|
|
185
|
+
return StringValueCStr(key);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
static int li_lookup_named_int(const struct li_named_int *table, size_t table_len, VALUE key,
|
|
189
|
+
int *out) {
|
|
190
|
+
const char *name;
|
|
191
|
+
size_t i;
|
|
192
|
+
|
|
193
|
+
if (NIL_P(key) || key == Qundef) {
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (RB_INTEGER_TYPE_P(key)) {
|
|
198
|
+
*out = NUM2INT(key);
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
name = li_key_name(key);
|
|
203
|
+
for (i = 0; i < table_len; i++) {
|
|
204
|
+
if (strcmp(table[i].name, name) == 0) {
|
|
205
|
+
*out = table[i].value;
|
|
206
|
+
return 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
static const char *li_lookup_name_by_int(const struct li_named_int *table, size_t table_len,
|
|
214
|
+
int value) {
|
|
215
|
+
size_t i;
|
|
216
|
+
for (i = 0; i < table_len; i++) {
|
|
217
|
+
if (table[i].value == value) {
|
|
218
|
+
return table[i].name;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return NULL;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
static const char *li_lookup_name_by_char(const struct li_named_char *table, size_t table_len,
|
|
225
|
+
int value) {
|
|
226
|
+
size_t i;
|
|
227
|
+
for (i = 0; i < table_len; i++) {
|
|
228
|
+
if (table[i].value == value) {
|
|
229
|
+
return table[i].name;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return NULL;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
static VALUE li_symbol_for_int(const struct li_named_int *table, size_t table_len, int value) {
|
|
236
|
+
const char *name = li_lookup_name_by_int(table, table_len, value);
|
|
237
|
+
return name ? li_id_sym(name) : Qnil;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
static VALUE li_symbol_for_char(const struct li_named_char *table, size_t table_len, int value) {
|
|
241
|
+
const char *name = li_lookup_name_by_char(table, table_len, value);
|
|
242
|
+
return name ? li_id_sym(name) : Qnil;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
static VALUE li_char_string(int ch) {
|
|
246
|
+
char buf[1];
|
|
247
|
+
if (ch == 0) {
|
|
248
|
+
return Qnil;
|
|
249
|
+
}
|
|
250
|
+
buf[0] = (char)ch;
|
|
251
|
+
return rb_str_new(buf, 1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
static VALUE li_named_int_hash(const struct li_named_int *table, size_t table_len) {
|
|
255
|
+
VALUE hash = rb_hash_new();
|
|
256
|
+
size_t i;
|
|
257
|
+
for (i = 0; i < table_len; i++) {
|
|
258
|
+
rb_hash_aset(hash, li_id_sym(table[i].name), INT2NUM(table[i].value));
|
|
259
|
+
}
|
|
260
|
+
return rb_hash_freeze(hash);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
static VALUE li_named_char_hash(const struct li_named_char *table, size_t table_len) {
|
|
264
|
+
VALUE hash = rb_hash_new();
|
|
265
|
+
size_t i;
|
|
266
|
+
for (i = 0; i < table_len; i++) {
|
|
267
|
+
rb_hash_aset(hash, li_id_sym(table[i].name), li_char_string(table[i].value));
|
|
268
|
+
}
|
|
269
|
+
return rb_hash_freeze(hash);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
static VALUE li_hash_opts(VALUE opts) {
|
|
273
|
+
if (NIL_P(opts)) {
|
|
274
|
+
return Qnil;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
opts = rb_check_hash_type(opts);
|
|
278
|
+
if (NIL_P(opts)) {
|
|
279
|
+
rb_raise(eArgumentError, "options must be a Hash");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return opts;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
static void raise_on_error(injection_result_t result) {
|
|
286
|
+
if (result == LIBINJECTION_RESULT_ERROR) {
|
|
287
|
+
rb_raise(eParserError, "libinjection parser error");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
typedef struct {
|
|
292
|
+
const char *src;
|
|
293
|
+
size_t len;
|
|
294
|
+
|
|
295
|
+
int want_sqli;
|
|
296
|
+
int sqli_flags;
|
|
297
|
+
int sqli_detected;
|
|
298
|
+
char sqli_fingerprint[LI_SQLI_FINGERPRINT_SIZE];
|
|
299
|
+
injection_result_t sqli_result;
|
|
300
|
+
|
|
301
|
+
int want_xss;
|
|
302
|
+
int xss_detected;
|
|
303
|
+
injection_result_t xss_result;
|
|
304
|
+
} li_scan_work_t;
|
|
305
|
+
|
|
306
|
+
static void li_scan_perform(li_scan_work_t *work) {
|
|
307
|
+
if (work->want_sqli) {
|
|
308
|
+
if (work->sqli_flags == 0) {
|
|
309
|
+
work->sqli_result = libinjection_sqli(work->src, work->len, work->sqli_fingerprint);
|
|
310
|
+
work->sqli_detected = (work->sqli_result == LIBINJECTION_RESULT_TRUE);
|
|
311
|
+
} else {
|
|
312
|
+
struct libinjection_sqli_state state;
|
|
313
|
+
libinjection_sqli_init(&state, work->src, work->len, work->sqli_flags);
|
|
314
|
+
libinjection_sqli_fingerprint(&state, work->sqli_flags);
|
|
315
|
+
work->sqli_detected = libinjection_sqli_check_fingerprint(&state) ? 1 : 0;
|
|
316
|
+
work->sqli_result =
|
|
317
|
+
work->sqli_detected ? LIBINJECTION_RESULT_TRUE : LIBINJECTION_RESULT_FALSE;
|
|
318
|
+
memcpy(work->sqli_fingerprint, state.fingerprint, sizeof(work->sqli_fingerprint));
|
|
319
|
+
work->sqli_fingerprint[sizeof(work->sqli_fingerprint) - 1] = '\0';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (work->sqli_detected && work->want_xss) {
|
|
323
|
+
work->want_xss = 0;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (work->want_xss) {
|
|
328
|
+
work->xss_result = libinjection_xss(work->src, work->len);
|
|
329
|
+
work->xss_detected = (work->xss_result == LIBINJECTION_RESULT_TRUE);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
static void *li_scan_nogvl_thunk(void *data) {
|
|
334
|
+
li_scan_perform((li_scan_work_t *)data);
|
|
335
|
+
return NULL;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
static inline void li_scan_release_gvl(li_scan_work_t *work) {
|
|
339
|
+
#if defined(RB_NOGVL_OFFLOAD_SAFE)
|
|
340
|
+
rb_nogvl(li_scan_nogvl_thunk, work, RUBY_UBF_PROCESS, NULL, RB_NOGVL_OFFLOAD_SAFE);
|
|
341
|
+
#else
|
|
342
|
+
rb_thread_call_without_gvl(li_scan_nogvl_thunk, work, RUBY_UBF_PROCESS, NULL);
|
|
343
|
+
#endif
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
static inline void li_scan_run(li_scan_work_t *work) {
|
|
347
|
+
if (work->len >= (size_t)LI_NOGVL_THRESHOLD) {
|
|
348
|
+
li_scan_release_gvl(work);
|
|
349
|
+
} else {
|
|
350
|
+
li_scan_perform(work);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
#define LI_THREAT_SQLI 1
|
|
355
|
+
#define LI_THREAT_XSS 2
|
|
356
|
+
#define LI_THREAT_BOTH (LI_THREAT_SQLI | LI_THREAT_XSS)
|
|
357
|
+
|
|
358
|
+
typedef struct {
|
|
359
|
+
int found_type;
|
|
360
|
+
char sqli_fingerprint[LI_SQLI_FINGERPRINT_SIZE];
|
|
361
|
+
injection_result_t sqli_result;
|
|
362
|
+
injection_result_t xss_result;
|
|
363
|
+
} li_scan_out_t;
|
|
364
|
+
|
|
365
|
+
static void li_scan_out_reset(li_scan_out_t *out) {
|
|
366
|
+
out->found_type = 0;
|
|
367
|
+
out->sqli_fingerprint[0] = '\0';
|
|
368
|
+
out->sqli_result = LIBINJECTION_RESULT_FALSE;
|
|
369
|
+
out->xss_result = LIBINJECTION_RESULT_FALSE;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
static void li_scan_buffer(const char *src, size_t len, int threat_mask, li_scan_out_t *out) {
|
|
373
|
+
li_scan_work_t work = {0};
|
|
374
|
+
work.src = src;
|
|
375
|
+
work.len = len;
|
|
376
|
+
work.want_sqli = (threat_mask & LI_THREAT_SQLI) != 0;
|
|
377
|
+
work.want_xss = (threat_mask & LI_THREAT_XSS) != 0;
|
|
378
|
+
|
|
379
|
+
li_scan_run(&work);
|
|
380
|
+
|
|
381
|
+
out->sqli_result = work.sqli_result;
|
|
382
|
+
out->xss_result = work.xss_result;
|
|
383
|
+
|
|
384
|
+
if (work.sqli_detected) {
|
|
385
|
+
out->found_type = LI_THREAT_SQLI;
|
|
386
|
+
memcpy(out->sqli_fingerprint, work.sqli_fingerprint, sizeof(out->sqli_fingerprint));
|
|
387
|
+
} else if (work.xss_detected) {
|
|
388
|
+
out->found_type = LI_THREAT_XSS;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
static int li_hex_value(unsigned char ch) {
|
|
393
|
+
if (ch >= '0' && ch <= '9') {
|
|
394
|
+
return (int)(ch - '0');
|
|
395
|
+
}
|
|
396
|
+
if (ch >= 'a' && ch <= 'f') {
|
|
397
|
+
return (int)(ch - 'a' + 10);
|
|
398
|
+
}
|
|
399
|
+
if (ch >= 'A' && ch <= 'F') {
|
|
400
|
+
return (int)(ch - 'A' + 10);
|
|
401
|
+
}
|
|
402
|
+
return -1;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
static int li_url_encoded_candidate(const char *src, size_t len, int plus_as_space) {
|
|
406
|
+
size_t i;
|
|
407
|
+
if (len == 0) {
|
|
408
|
+
return 0;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (plus_as_space && memchr(src, '+', len) != NULL) {
|
|
412
|
+
return 1;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
for (i = 0; i < len; i++) {
|
|
416
|
+
if (src[i] == '%') {
|
|
417
|
+
return 1;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return 0;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
static int li_url_decode_into(char *dst, const char *src, size_t len, size_t *out_len,
|
|
424
|
+
int plus_as_space) {
|
|
425
|
+
size_t i = 0;
|
|
426
|
+
size_t j = 0;
|
|
427
|
+
|
|
428
|
+
while (i < len) {
|
|
429
|
+
unsigned char ch = (unsigned char)src[i];
|
|
430
|
+
|
|
431
|
+
if (ch == '%') {
|
|
432
|
+
int hi = -1;
|
|
433
|
+
int lo = -1;
|
|
434
|
+
if (i + 2 < len) {
|
|
435
|
+
hi = li_hex_value((unsigned char)src[i + 1]);
|
|
436
|
+
lo = li_hex_value((unsigned char)src[i + 2]);
|
|
437
|
+
}
|
|
438
|
+
if (hi >= 0 && lo >= 0) {
|
|
439
|
+
dst[j++] = (char)((hi << 4) | lo);
|
|
440
|
+
i += 3;
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
dst[j++] = (char)ch;
|
|
445
|
+
i++;
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (plus_as_space && ch == '+') {
|
|
450
|
+
dst[j++] = ' ';
|
|
451
|
+
} else {
|
|
452
|
+
dst[j++] = (char)ch;
|
|
453
|
+
}
|
|
454
|
+
i++;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
*out_len = j;
|
|
458
|
+
return 1;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
static VALUE li_scan_out_to_value(const li_scan_out_t *out) {
|
|
462
|
+
if (out->found_type == LI_THREAT_SQLI) {
|
|
463
|
+
return rb_ary_new3(2, sym_sqli, rb_str_new_cstr(out->sqli_fingerprint));
|
|
464
|
+
}
|
|
465
|
+
if (out->found_type == LI_THREAT_XSS) {
|
|
466
|
+
return rb_ary_new3(2, sym_xss, Qnil);
|
|
467
|
+
}
|
|
468
|
+
return Qnil;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
static int li_sqli_flags_from_opts(VALUE opts, int fallback) {
|
|
472
|
+
VALUE raw_flags;
|
|
473
|
+
VALUE context;
|
|
474
|
+
VALUE quote;
|
|
475
|
+
VALUE dialect;
|
|
476
|
+
int flags;
|
|
477
|
+
int quote_flags;
|
|
478
|
+
int dialect_flags;
|
|
479
|
+
|
|
480
|
+
opts = li_hash_opts(opts);
|
|
481
|
+
if (NIL_P(opts)) {
|
|
482
|
+
return fallback;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
raw_flags = li_hash_aref(opts, "flags");
|
|
486
|
+
if (raw_flags != Qundef) {
|
|
487
|
+
return NUM2INT(raw_flags);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
context = li_hash_aref(opts, "context");
|
|
491
|
+
if (context != Qundef) {
|
|
492
|
+
if (!li_lookup_named_int(SQLI_CONTEXTS, LI_ARRAY_LEN(SQLI_CONTEXTS), context, &flags)) {
|
|
493
|
+
rb_raise(eArgumentError, "unknown SQLi context");
|
|
494
|
+
}
|
|
495
|
+
return flags;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
quote_flags = FLAG_QUOTE_NONE;
|
|
499
|
+
dialect_flags = FLAG_SQL_ANSI;
|
|
500
|
+
|
|
501
|
+
quote = li_hash_aref(opts, "quote");
|
|
502
|
+
if (quote != Qundef &&
|
|
503
|
+
!li_lookup_named_int(SQLI_QUOTES, LI_ARRAY_LEN(SQLI_QUOTES), quote, "e_flags)) {
|
|
504
|
+
rb_raise(eArgumentError, "unknown SQLi quote option");
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
dialect = li_hash_aref(opts, "dialect");
|
|
508
|
+
if (dialect != Qundef &&
|
|
509
|
+
!li_lookup_named_int(SQLI_DIALECTS, LI_ARRAY_LEN(SQLI_DIALECTS), dialect, &dialect_flags)) {
|
|
510
|
+
rb_raise(eArgumentError, "unknown SQLi dialect option");
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return quote_flags | dialect_flags;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
static int li_html5_flags_from_opts(VALUE opts, int fallback) {
|
|
517
|
+
VALUE raw_flags;
|
|
518
|
+
VALUE context;
|
|
519
|
+
int flags;
|
|
520
|
+
|
|
521
|
+
opts = li_hash_opts(opts);
|
|
522
|
+
if (NIL_P(opts)) {
|
|
523
|
+
return fallback;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
raw_flags = li_hash_aref(opts, "flags");
|
|
527
|
+
if (raw_flags != Qundef) {
|
|
528
|
+
return NUM2INT(raw_flags);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
context = li_hash_aref(opts, "context");
|
|
532
|
+
if (context == Qundef) {
|
|
533
|
+
return fallback;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (!li_lookup_named_int(HTML5_CONTEXTS, LI_ARRAY_LEN(HTML5_CONTEXTS), context, &flags)) {
|
|
537
|
+
rb_raise(eArgumentError, "unknown HTML5/XSS context");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return flags;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
static VALUE li_sqli_token_hash(const stoken_t *token) {
|
|
544
|
+
VALUE hash = rb_hash_new();
|
|
545
|
+
|
|
546
|
+
rb_hash_aset(hash, li_id_sym("type"),
|
|
547
|
+
li_symbol_for_char(SQLI_TOKEN_TYPES, LI_ARRAY_LEN(SQLI_TOKEN_TYPES), token->type));
|
|
548
|
+
rb_hash_aset(hash, li_id_sym("code"), li_char_string(token->type));
|
|
549
|
+
rb_hash_aset(hash, li_id_sym("value"), rb_str_new(token->val, token->len));
|
|
550
|
+
rb_hash_aset(hash, li_id_sym("pos"), SIZET2NUM(token->pos));
|
|
551
|
+
rb_hash_aset(hash, li_id_sym("length"), SIZET2NUM(token->len));
|
|
552
|
+
rb_hash_aset(hash, li_id_sym("count"), INT2NUM(token->count));
|
|
553
|
+
rb_hash_aset(hash, li_id_sym("str_open"), li_char_string(token->str_open));
|
|
554
|
+
rb_hash_aset(hash, li_id_sym("str_close"), li_char_string(token->str_close));
|
|
555
|
+
|
|
556
|
+
return hash;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
static VALUE li_sqli_stats_hash(const struct libinjection_sqli_state *state) {
|
|
560
|
+
VALUE hash = rb_hash_new();
|
|
561
|
+
|
|
562
|
+
rb_hash_aset(hash, li_id_sym("reason"), INT2NUM(state->reason));
|
|
563
|
+
rb_hash_aset(hash, li_id_sym("comment_ddw"), INT2NUM(state->stats_comment_ddw));
|
|
564
|
+
rb_hash_aset(hash, li_id_sym("comment_ddx"), INT2NUM(state->stats_comment_ddx));
|
|
565
|
+
rb_hash_aset(hash, li_id_sym("comment_c"), INT2NUM(state->stats_comment_c));
|
|
566
|
+
rb_hash_aset(hash, li_id_sym("comment_hash"), INT2NUM(state->stats_comment_hash));
|
|
567
|
+
rb_hash_aset(hash, li_id_sym("folds"), INT2NUM(state->stats_folds));
|
|
568
|
+
rb_hash_aset(hash, li_id_sym("tokens"), INT2NUM(state->stats_tokens));
|
|
569
|
+
|
|
570
|
+
return hash;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
static VALUE li_sqli_result_hash(const struct libinjection_sqli_state *state,
|
|
574
|
+
injection_result_t result, int flags, VALUE context_name) {
|
|
575
|
+
VALUE hash = rb_hash_new();
|
|
576
|
+
const char *fingerprint = state->fingerprint;
|
|
577
|
+
|
|
578
|
+
rb_hash_aset(hash, li_id_sym("type"), sym_sqli);
|
|
579
|
+
rb_hash_aset(hash, li_id_sym("detected"), result == LIBINJECTION_RESULT_TRUE ? Qtrue : Qfalse);
|
|
580
|
+
rb_hash_aset(hash, li_id_sym("fingerprint"),
|
|
581
|
+
fingerprint[0] == '\0' ? Qnil : rb_str_new_cstr(fingerprint));
|
|
582
|
+
rb_hash_aset(hash, li_id_sym("flags"), INT2NUM(flags));
|
|
583
|
+
rb_hash_aset(hash, li_id_sym("context"), context_name);
|
|
584
|
+
rb_hash_aset(hash, li_id_sym("stats"), li_sqli_stats_hash(state));
|
|
585
|
+
|
|
586
|
+
return hash;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
static injection_result_t li_run_sqli_context(VALUE str, int flags,
|
|
590
|
+
struct libinjection_sqli_state *state) {
|
|
591
|
+
const char *fingerprint;
|
|
592
|
+
libinjection_sqli_init(state, RSTRING_PTR(str), (size_t)RSTRING_LEN(str), flags);
|
|
593
|
+
fingerprint = libinjection_sqli_fingerprint(state, flags);
|
|
594
|
+
(void)fingerprint;
|
|
595
|
+
return libinjection_sqli_check_fingerprint(state) ? LIBINJECTION_RESULT_TRUE
|
|
596
|
+
: LIBINJECTION_RESULT_FALSE;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
typedef struct {
|
|
600
|
+
VALUE input;
|
|
601
|
+
li_input_t in;
|
|
602
|
+
int input_prepared;
|
|
603
|
+
li_scan_work_t work;
|
|
604
|
+
} li_work_scan_args_t;
|
|
605
|
+
|
|
606
|
+
static VALUE li_work_scan_body(VALUE data) {
|
|
607
|
+
li_work_scan_args_t *args = (li_work_scan_args_t *)data;
|
|
608
|
+
|
|
609
|
+
args->in = li_input_prepare(args->input);
|
|
610
|
+
args->input_prepared = 1;
|
|
611
|
+
args->work.src = args->in.ptr;
|
|
612
|
+
args->work.len = args->in.len;
|
|
613
|
+
li_scan_run(&args->work);
|
|
614
|
+
|
|
615
|
+
return Qnil;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
static VALUE li_work_scan_ensure(VALUE data) {
|
|
619
|
+
li_work_scan_args_t *args = (li_work_scan_args_t *)data;
|
|
620
|
+
|
|
621
|
+
if (args->input_prepared) {
|
|
622
|
+
li_input_release(&args->in);
|
|
623
|
+
RB_GC_GUARD(args->in.str);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return Qnil;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
static VALUE rb_li_sqli_p(VALUE self, VALUE input) {
|
|
630
|
+
li_work_scan_args_t args;
|
|
631
|
+
|
|
632
|
+
(void)self;
|
|
633
|
+
memset(&args, 0, sizeof(args));
|
|
634
|
+
args.input = input;
|
|
635
|
+
args.work.want_sqli = 1;
|
|
636
|
+
|
|
637
|
+
rb_ensure(li_work_scan_body, (VALUE)&args, li_work_scan_ensure, (VALUE)&args);
|
|
638
|
+
raise_on_error(args.work.sqli_result);
|
|
639
|
+
|
|
640
|
+
return args.work.sqli_detected ? Qtrue : Qfalse;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
static VALUE rb_li_sqli_fingerprint(VALUE self, VALUE input) {
|
|
644
|
+
li_work_scan_args_t args;
|
|
645
|
+
char fingerprint[LI_SQLI_FINGERPRINT_SIZE];
|
|
646
|
+
|
|
647
|
+
(void)self;
|
|
648
|
+
memset(&args, 0, sizeof(args));
|
|
649
|
+
args.input = input;
|
|
650
|
+
args.work.want_sqli = 1;
|
|
651
|
+
memset(fingerprint, 0, sizeof(fingerprint));
|
|
652
|
+
|
|
653
|
+
rb_ensure(li_work_scan_body, (VALUE)&args, li_work_scan_ensure, (VALUE)&args);
|
|
654
|
+
memcpy(fingerprint, args.work.sqli_fingerprint, sizeof(fingerprint));
|
|
655
|
+
raise_on_error(args.work.sqli_result);
|
|
656
|
+
|
|
657
|
+
return args.work.sqli_detected ? rb_str_new_cstr(fingerprint) : Qnil;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
typedef struct {
|
|
661
|
+
VALUE input;
|
|
662
|
+
li_input_t in;
|
|
663
|
+
int input_prepared;
|
|
664
|
+
li_scan_out_t out;
|
|
665
|
+
} li_raw_scan_args_t;
|
|
666
|
+
|
|
667
|
+
static VALUE li_raw_scan_body(VALUE data) {
|
|
668
|
+
li_raw_scan_args_t *args = (li_raw_scan_args_t *)data;
|
|
669
|
+
|
|
670
|
+
args->in = li_input_prepare(args->input);
|
|
671
|
+
args->input_prepared = 1;
|
|
672
|
+
li_scan_out_reset(&args->out);
|
|
673
|
+
li_scan_buffer(args->in.ptr, args->in.len, LI_THREAT_BOTH, &args->out);
|
|
674
|
+
|
|
675
|
+
return Qnil;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
static VALUE li_raw_scan_ensure(VALUE data) {
|
|
679
|
+
li_raw_scan_args_t *args = (li_raw_scan_args_t *)data;
|
|
680
|
+
|
|
681
|
+
if (args->input_prepared) {
|
|
682
|
+
li_input_release(&args->in);
|
|
683
|
+
RB_GC_GUARD(args->in.str);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return Qnil;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
static VALUE rb_li_detect_raw(VALUE self, VALUE input) {
|
|
690
|
+
li_raw_scan_args_t args;
|
|
691
|
+
|
|
692
|
+
(void)self;
|
|
693
|
+
memset(&args, 0, sizeof(args));
|
|
694
|
+
args.input = input;
|
|
695
|
+
li_scan_out_reset(&args.out);
|
|
696
|
+
|
|
697
|
+
rb_ensure(li_raw_scan_body, (VALUE)&args, li_raw_scan_ensure, (VALUE)&args);
|
|
698
|
+
raise_on_error(args.out.sqli_result);
|
|
699
|
+
raise_on_error(args.out.xss_result);
|
|
700
|
+
|
|
701
|
+
return li_scan_out_to_value(&args.out);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
typedef struct {
|
|
705
|
+
VALUE input;
|
|
706
|
+
int depth;
|
|
707
|
+
int plus_as_space;
|
|
708
|
+
int threat_mask;
|
|
709
|
+
li_scan_out_t out;
|
|
710
|
+
li_input_t in;
|
|
711
|
+
int input_prepared;
|
|
712
|
+
char *decode_buffers[2];
|
|
713
|
+
int decode_buffer_heap[2];
|
|
714
|
+
injection_result_t sqli_error;
|
|
715
|
+
injection_result_t xss_error;
|
|
716
|
+
} li_url_scan_args_t;
|
|
717
|
+
|
|
718
|
+
static VALUE li_detect_url_encoded_raw_body(VALUE data) {
|
|
719
|
+
li_url_scan_args_t *args = (li_url_scan_args_t *)data;
|
|
720
|
+
const char *src;
|
|
721
|
+
size_t src_len;
|
|
722
|
+
size_t decode_capacity;
|
|
723
|
+
int level;
|
|
724
|
+
|
|
725
|
+
args->in = li_input_prepare(args->input);
|
|
726
|
+
args->input_prepared = 1;
|
|
727
|
+
decode_capacity = args->in.len == 0 ? 1 : args->in.len;
|
|
728
|
+
|
|
729
|
+
li_scan_out_reset(&args->out);
|
|
730
|
+
li_scan_buffer(args->in.ptr, args->in.len, args->threat_mask, &args->out);
|
|
731
|
+
if (args->out.sqli_result == LIBINJECTION_RESULT_ERROR ||
|
|
732
|
+
args->out.xss_result == LIBINJECTION_RESULT_ERROR || args->out.found_type != 0 ||
|
|
733
|
+
args->depth == 0 ||
|
|
734
|
+
!li_url_encoded_candidate(args->in.ptr, args->in.len, args->plus_as_space)) {
|
|
735
|
+
return Qnil;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
src = args->in.ptr;
|
|
739
|
+
src_len = args->in.len;
|
|
740
|
+
for (level = 0; level < args->depth; level++) {
|
|
741
|
+
int slot = level & 1;
|
|
742
|
+
char *dst;
|
|
743
|
+
size_t dst_len = 0;
|
|
744
|
+
|
|
745
|
+
if (!li_url_encoded_candidate(src, src_len, args->plus_as_space)) {
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
dst = args->decode_buffers[slot];
|
|
750
|
+
if (dst == NULL) {
|
|
751
|
+
if (decode_capacity <= (size_t)LI_URL_DECODE_STACK_THRESHOLD) {
|
|
752
|
+
dst = ALLOCA_N(char, decode_capacity);
|
|
753
|
+
} else {
|
|
754
|
+
dst = (char *)ruby_xmalloc(decode_capacity);
|
|
755
|
+
args->decode_buffer_heap[slot] = 1;
|
|
756
|
+
}
|
|
757
|
+
args->decode_buffers[slot] = dst;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
if (!li_url_decode_into(dst, src, src_len, &dst_len, args->plus_as_space)) {
|
|
761
|
+
break;
|
|
762
|
+
}
|
|
763
|
+
if (dst_len == src_len && memcmp(dst, src, src_len) == 0) {
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
li_scan_out_reset(&args->out);
|
|
768
|
+
li_scan_buffer(dst, dst_len, args->threat_mask, &args->out);
|
|
769
|
+
if (args->out.sqli_result == LIBINJECTION_RESULT_ERROR ||
|
|
770
|
+
args->out.xss_result == LIBINJECTION_RESULT_ERROR || args->out.found_type != 0) {
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
src = dst;
|
|
775
|
+
src_len = dst_len;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return Qnil;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
static VALUE li_detect_url_encoded_raw_ensure(VALUE data) {
|
|
782
|
+
li_url_scan_args_t *args = (li_url_scan_args_t *)data;
|
|
783
|
+
|
|
784
|
+
args->sqli_error = args->out.sqli_result;
|
|
785
|
+
args->xss_error = args->out.xss_result;
|
|
786
|
+
|
|
787
|
+
if (args->decode_buffer_heap[0] && args->decode_buffers[0] != NULL) {
|
|
788
|
+
ruby_xfree(args->decode_buffers[0]);
|
|
789
|
+
args->decode_buffers[0] = NULL;
|
|
790
|
+
}
|
|
791
|
+
if (args->decode_buffer_heap[1] && args->decode_buffers[1] != NULL) {
|
|
792
|
+
ruby_xfree(args->decode_buffers[1]);
|
|
793
|
+
args->decode_buffers[1] = NULL;
|
|
794
|
+
}
|
|
795
|
+
if (args->input_prepared) {
|
|
796
|
+
li_input_release(&args->in);
|
|
797
|
+
RB_GC_GUARD(args->in.str);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return Qnil;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
static VALUE rb_li_detect_url_encoded_raw(VALUE self, VALUE input, VALUE depth_value,
|
|
804
|
+
VALUE plus_as_space_value, VALUE threat_mask_value) {
|
|
805
|
+
li_url_scan_args_t args;
|
|
806
|
+
|
|
807
|
+
(void)self;
|
|
808
|
+
memset(&args, 0, sizeof(args));
|
|
809
|
+
args.input = input;
|
|
810
|
+
args.depth = NUM2INT(depth_value);
|
|
811
|
+
args.plus_as_space = RTEST(plus_as_space_value);
|
|
812
|
+
args.threat_mask = NUM2INT(threat_mask_value);
|
|
813
|
+
args.sqli_error = LIBINJECTION_RESULT_FALSE;
|
|
814
|
+
args.xss_error = LIBINJECTION_RESULT_FALSE;
|
|
815
|
+
li_scan_out_reset(&args.out);
|
|
816
|
+
|
|
817
|
+
if (args.depth < 0) {
|
|
818
|
+
rb_raise(eArgumentError, "depth must be >= 0");
|
|
819
|
+
}
|
|
820
|
+
if (args.depth > LI_MAX_URL_DECODE_DEPTH) {
|
|
821
|
+
rb_raise(eArgumentError, "depth must be <= %d", LI_MAX_URL_DECODE_DEPTH);
|
|
822
|
+
}
|
|
823
|
+
if ((args.threat_mask & ~LI_THREAT_BOTH) != 0 || args.threat_mask == 0) {
|
|
824
|
+
rb_raise(eArgumentError, "threat mask must include SQLi and/or XSS");
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
rb_ensure(li_detect_url_encoded_raw_body, (VALUE)&args, li_detect_url_encoded_raw_ensure,
|
|
828
|
+
(VALUE)&args);
|
|
829
|
+
raise_on_error(args.sqli_error);
|
|
830
|
+
raise_on_error(args.xss_error);
|
|
831
|
+
|
|
832
|
+
return li_scan_out_to_value(&args.out);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
static VALUE rb_li_sqli_result(int argc, VALUE *argv, VALUE self) {
|
|
836
|
+
VALUE input;
|
|
837
|
+
VALUE opts;
|
|
838
|
+
VALUE str;
|
|
839
|
+
VALUE context_name = Qnil;
|
|
840
|
+
int flags;
|
|
841
|
+
struct libinjection_sqli_state state;
|
|
842
|
+
injection_result_t result;
|
|
843
|
+
|
|
844
|
+
(void)self;
|
|
845
|
+
rb_scan_args(argc, argv, "11", &input, &opts);
|
|
846
|
+
str = li_str(input);
|
|
847
|
+
|
|
848
|
+
if (NIL_P(opts)) {
|
|
849
|
+
libinjection_sqli_init(&state, RSTRING_PTR(str), (size_t)RSTRING_LEN(str), 0);
|
|
850
|
+
result =
|
|
851
|
+
libinjection_is_sqli(&state) ? LIBINJECTION_RESULT_TRUE : LIBINJECTION_RESULT_FALSE;
|
|
852
|
+
raise_on_error(result);
|
|
853
|
+
return li_sqli_result_hash(&state, result, 0, Qnil);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
flags = li_sqli_flags_from_opts(opts, FLAG_QUOTE_NONE | FLAG_SQL_ANSI);
|
|
857
|
+
context_name = li_symbol_for_int(SQLI_CONTEXTS, LI_ARRAY_LEN(SQLI_CONTEXTS), flags);
|
|
858
|
+
result = li_run_sqli_context(str, flags, &state);
|
|
859
|
+
raise_on_error(result);
|
|
860
|
+
|
|
861
|
+
return li_sqli_result_hash(&state, result, flags, context_name);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
static VALUE rb_li_sqli_fingerprint_for(int argc, VALUE *argv, VALUE self) {
|
|
865
|
+
VALUE input;
|
|
866
|
+
VALUE opts;
|
|
867
|
+
VALUE str;
|
|
868
|
+
int flags;
|
|
869
|
+
struct libinjection_sqli_state state;
|
|
870
|
+
injection_result_t result;
|
|
871
|
+
|
|
872
|
+
(void)self;
|
|
873
|
+
rb_scan_args(argc, argv, "11", &input, &opts);
|
|
874
|
+
str = li_str(input);
|
|
875
|
+
flags = li_sqli_flags_from_opts(opts, FLAG_QUOTE_NONE | FLAG_SQL_ANSI);
|
|
876
|
+
|
|
877
|
+
result = li_run_sqli_context(str, flags, &state);
|
|
878
|
+
raise_on_error(result);
|
|
879
|
+
return state.fingerprint[0] == '\0' ? Qnil : rb_str_new_cstr(state.fingerprint);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
static VALUE rb_li_sqli_contexts(VALUE self, VALUE input) {
|
|
883
|
+
VALUE str;
|
|
884
|
+
VALUE out;
|
|
885
|
+
size_t i;
|
|
886
|
+
|
|
887
|
+
(void)self;
|
|
888
|
+
str = li_str(input);
|
|
889
|
+
out = rb_ary_new_capa((long)LI_ARRAY_LEN(SQLI_CONTEXTS));
|
|
890
|
+
|
|
891
|
+
for (i = 0; i < LI_ARRAY_LEN(SQLI_CONTEXTS); i++) {
|
|
892
|
+
struct libinjection_sqli_state state;
|
|
893
|
+
injection_result_t result = li_run_sqli_context(str, SQLI_CONTEXTS[i].value, &state);
|
|
894
|
+
raise_on_error(result);
|
|
895
|
+
rb_ary_push(out, li_sqli_result_hash(&state, result, SQLI_CONTEXTS[i].value,
|
|
896
|
+
li_id_sym(SQLI_CONTEXTS[i].name)));
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
return out;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
static VALUE rb_li_sqli_tokens(int argc, VALUE *argv, VALUE self) {
|
|
903
|
+
VALUE input;
|
|
904
|
+
VALUE opts;
|
|
905
|
+
VALUE str;
|
|
906
|
+
VALUE out;
|
|
907
|
+
VALUE fold_value;
|
|
908
|
+
int flags;
|
|
909
|
+
int folded;
|
|
910
|
+
struct libinjection_sqli_state state;
|
|
911
|
+
|
|
912
|
+
(void)self;
|
|
913
|
+
rb_scan_args(argc, argv, "11", &input, &opts);
|
|
914
|
+
str = li_str(input);
|
|
915
|
+
opts = li_hash_opts(opts);
|
|
916
|
+
flags = li_sqli_flags_from_opts(opts, FLAG_QUOTE_NONE | FLAG_SQL_ANSI);
|
|
917
|
+
fold_value = li_hash_aref(opts, "fold");
|
|
918
|
+
folded = fold_value == Qtrue;
|
|
919
|
+
|
|
920
|
+
libinjection_sqli_init(&state, RSTRING_PTR(str), (size_t)RSTRING_LEN(str), flags);
|
|
921
|
+
out = rb_ary_new();
|
|
922
|
+
|
|
923
|
+
if (folded) {
|
|
924
|
+
int tlen;
|
|
925
|
+
int i;
|
|
926
|
+
libinjection_sqli_fingerprint(&state, flags);
|
|
927
|
+
tlen = (int)strlen(state.fingerprint);
|
|
928
|
+
for (i = 0; i < tlen; i++) {
|
|
929
|
+
rb_ary_push(out, li_sqli_token_hash(&state.tokenvec[i]));
|
|
930
|
+
}
|
|
931
|
+
return out;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
state.current = &(state.tokenvec[0]);
|
|
935
|
+
while (libinjection_sqli_tokenize(&state)) {
|
|
936
|
+
rb_ary_push(out, li_sqli_token_hash(state.current));
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
return out;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
static VALUE rb_li_sqli_flags(int argc, VALUE *argv, VALUE self) {
|
|
943
|
+
VALUE opts;
|
|
944
|
+
(void)self;
|
|
945
|
+
rb_scan_args(argc, argv, "01", &opts);
|
|
946
|
+
return INT2NUM(li_sqli_flags_from_opts(opts, FLAG_QUOTE_NONE | FLAG_SQL_ANSI));
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
static VALUE rb_li_xss_p(VALUE self, VALUE input) {
|
|
950
|
+
li_work_scan_args_t args;
|
|
951
|
+
|
|
952
|
+
(void)self;
|
|
953
|
+
memset(&args, 0, sizeof(args));
|
|
954
|
+
args.input = input;
|
|
955
|
+
args.work.want_xss = 1;
|
|
956
|
+
|
|
957
|
+
rb_ensure(li_work_scan_body, (VALUE)&args, li_work_scan_ensure, (VALUE)&args);
|
|
958
|
+
raise_on_error(args.work.xss_result);
|
|
959
|
+
|
|
960
|
+
return args.work.xss_detected ? Qtrue : Qfalse;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
static VALUE li_xss_result_hash(injection_result_t result, int flags, VALUE context_name) {
|
|
964
|
+
VALUE hash = rb_hash_new();
|
|
965
|
+
|
|
966
|
+
rb_hash_aset(hash, li_id_sym("type"), sym_xss);
|
|
967
|
+
rb_hash_aset(hash, li_id_sym("detected"), result == LIBINJECTION_RESULT_TRUE ? Qtrue : Qfalse);
|
|
968
|
+
rb_hash_aset(hash, li_id_sym("flags"), INT2NUM(flags));
|
|
969
|
+
rb_hash_aset(hash, li_id_sym("context"), context_name);
|
|
970
|
+
|
|
971
|
+
return hash;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
static VALUE rb_li_xss_result(int argc, VALUE *argv, VALUE self) {
|
|
975
|
+
VALUE input;
|
|
976
|
+
VALUE opts;
|
|
977
|
+
VALUE str;
|
|
978
|
+
int flags;
|
|
979
|
+
injection_result_t result;
|
|
980
|
+
|
|
981
|
+
(void)self;
|
|
982
|
+
rb_scan_args(argc, argv, "11", &input, &opts);
|
|
983
|
+
str = li_str(input);
|
|
984
|
+
|
|
985
|
+
if (NIL_P(opts)) {
|
|
986
|
+
result = libinjection_xss(RSTRING_PTR(str), (size_t)RSTRING_LEN(str));
|
|
987
|
+
raise_on_error(result);
|
|
988
|
+
return li_xss_result_hash(result, -1, Qnil);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
flags = li_html5_flags_from_opts(opts, DATA_STATE);
|
|
992
|
+
result = libinjection_is_xss(RSTRING_PTR(str), (size_t)RSTRING_LEN(str), flags);
|
|
993
|
+
raise_on_error(result);
|
|
994
|
+
|
|
995
|
+
return li_xss_result_hash(
|
|
996
|
+
result, flags, li_symbol_for_int(HTML5_CONTEXTS, LI_ARRAY_LEN(HTML5_CONTEXTS), flags));
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
static VALUE rb_li_xss_contexts(VALUE self, VALUE input) {
|
|
1000
|
+
VALUE str;
|
|
1001
|
+
VALUE out;
|
|
1002
|
+
size_t i;
|
|
1003
|
+
|
|
1004
|
+
(void)self;
|
|
1005
|
+
str = li_str(input);
|
|
1006
|
+
out = rb_ary_new_capa((long)LI_ARRAY_LEN(HTML5_CONTEXTS));
|
|
1007
|
+
|
|
1008
|
+
for (i = 0; i < LI_ARRAY_LEN(HTML5_CONTEXTS); i++) {
|
|
1009
|
+
injection_result_t result = libinjection_is_xss(RSTRING_PTR(str), (size_t)RSTRING_LEN(str),
|
|
1010
|
+
HTML5_CONTEXTS[i].value);
|
|
1011
|
+
raise_on_error(result);
|
|
1012
|
+
rb_ary_push(out, li_xss_result_hash(result, HTML5_CONTEXTS[i].value,
|
|
1013
|
+
li_id_sym(HTML5_CONTEXTS[i].name)));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
return out;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
static VALUE li_html5_token_hash(const h5_state_t *state) {
|
|
1020
|
+
VALUE hash = rb_hash_new();
|
|
1021
|
+
|
|
1022
|
+
rb_hash_aset(
|
|
1023
|
+
hash, li_id_sym("type"),
|
|
1024
|
+
li_symbol_for_int(HTML5_TOKEN_TYPES, LI_ARRAY_LEN(HTML5_TOKEN_TYPES), state->token_type));
|
|
1025
|
+
rb_hash_aset(hash, li_id_sym("value"), rb_str_new(state->token_start, state->token_len));
|
|
1026
|
+
rb_hash_aset(hash, li_id_sym("pos"), SIZET2NUM((size_t)(state->token_start - state->s)));
|
|
1027
|
+
rb_hash_aset(hash, li_id_sym("length"), SIZET2NUM(state->token_len));
|
|
1028
|
+
rb_hash_aset(hash, li_id_sym("is_close"), state->is_close ? Qtrue : Qfalse);
|
|
1029
|
+
|
|
1030
|
+
return hash;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
static VALUE rb_li_html5_tokens(int argc, VALUE *argv, VALUE self) {
|
|
1034
|
+
VALUE input;
|
|
1035
|
+
VALUE opts;
|
|
1036
|
+
VALUE str;
|
|
1037
|
+
VALUE out;
|
|
1038
|
+
int flags;
|
|
1039
|
+
h5_state_t state;
|
|
1040
|
+
injection_result_t result;
|
|
1041
|
+
|
|
1042
|
+
(void)self;
|
|
1043
|
+
rb_scan_args(argc, argv, "11", &input, &opts);
|
|
1044
|
+
str = li_str(input);
|
|
1045
|
+
flags = li_html5_flags_from_opts(opts, DATA_STATE);
|
|
1046
|
+
|
|
1047
|
+
libinjection_h5_init(&state, RSTRING_PTR(str), (size_t)RSTRING_LEN(str), flags);
|
|
1048
|
+
out = rb_ary_new();
|
|
1049
|
+
|
|
1050
|
+
while ((result = libinjection_h5_next(&state)) == LIBINJECTION_RESULT_TRUE) {
|
|
1051
|
+
rb_ary_push(out, li_html5_token_hash(&state));
|
|
1052
|
+
}
|
|
1053
|
+
raise_on_error(result);
|
|
1054
|
+
|
|
1055
|
+
return out;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
static VALUE rb_li_xss_flags(int argc, VALUE *argv, VALUE self) {
|
|
1059
|
+
VALUE opts;
|
|
1060
|
+
(void)self;
|
|
1061
|
+
rb_scan_args(argc, argv, "01", &opts);
|
|
1062
|
+
return INT2NUM(li_html5_flags_from_opts(opts, DATA_STATE));
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
static void li_check_runtime_version(void) {
|
|
1066
|
+
const char *version = libinjection_version();
|
|
1067
|
+
if (strcmp(version, LI_REQUIRED_LIBINJECTION_VERSION) != 0) {
|
|
1068
|
+
rb_raise(eError, "libinjection runtime version mismatch: expected %s, got %s",
|
|
1069
|
+
LI_REQUIRED_LIBINJECTION_VERSION, version);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
static VALUE rb_li_lib_version(VALUE self) {
|
|
1074
|
+
(void)self;
|
|
1075
|
+
return rb_str_new_cstr(libinjection_version());
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
void Init_libinjection_native(void) {
|
|
1079
|
+
mLibInjection = rb_define_module("LibInjection");
|
|
1080
|
+
if (rb_const_defined(mLibInjection, rb_intern("Error"))) {
|
|
1081
|
+
eError = rb_const_get(mLibInjection, rb_intern("Error"));
|
|
1082
|
+
} else {
|
|
1083
|
+
eError = rb_define_class_under(mLibInjection, "Error", rb_eStandardError);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (rb_const_defined(mLibInjection, rb_intern("ParserError"))) {
|
|
1087
|
+
eParserError = rb_const_get(mLibInjection, rb_intern("ParserError"));
|
|
1088
|
+
} else {
|
|
1089
|
+
eParserError = rb_define_class_under(mLibInjection, "ParserError", eError);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
eArgumentError = rb_eArgError;
|
|
1093
|
+
sym_sqli = ID2SYM(rb_intern("sqli"));
|
|
1094
|
+
sym_xss = ID2SYM(rb_intern("xss"));
|
|
1095
|
+
|
|
1096
|
+
li_check_runtime_version();
|
|
1097
|
+
|
|
1098
|
+
rb_define_const(mLibInjection, "SQLI_CONTEXTS",
|
|
1099
|
+
li_named_int_hash(SQLI_CONTEXTS, LI_ARRAY_LEN(SQLI_CONTEXTS)));
|
|
1100
|
+
rb_define_const(mLibInjection, "SQLI_QUOTES",
|
|
1101
|
+
li_named_int_hash(SQLI_QUOTES, LI_ARRAY_LEN(SQLI_QUOTES)));
|
|
1102
|
+
rb_define_const(mLibInjection, "SQLI_DIALECTS",
|
|
1103
|
+
li_named_int_hash(SQLI_DIALECTS, LI_ARRAY_LEN(SQLI_DIALECTS)));
|
|
1104
|
+
rb_define_const(mLibInjection, "SQLI_TOKEN_TYPES",
|
|
1105
|
+
li_named_char_hash(SQLI_TOKEN_TYPES, LI_ARRAY_LEN(SQLI_TOKEN_TYPES)));
|
|
1106
|
+
rb_define_const(mLibInjection, "HTML5_CONTEXTS",
|
|
1107
|
+
li_named_int_hash(HTML5_CONTEXTS, LI_ARRAY_LEN(HTML5_CONTEXTS)));
|
|
1108
|
+
rb_define_const(mLibInjection, "XSS_CONTEXTS",
|
|
1109
|
+
li_named_int_hash(HTML5_CONTEXTS, LI_ARRAY_LEN(HTML5_CONTEXTS)));
|
|
1110
|
+
rb_define_const(mLibInjection, "HTML5_TOKEN_TYPES",
|
|
1111
|
+
li_named_int_hash(HTML5_TOKEN_TYPES, LI_ARRAY_LEN(HTML5_TOKEN_TYPES)));
|
|
1112
|
+
|
|
1113
|
+
rb_define_singleton_method(mLibInjection, "sqli?", rb_li_sqli_p, 1);
|
|
1114
|
+
rb_define_singleton_method(mLibInjection, "sqli_fingerprint", rb_li_sqli_fingerprint, 1);
|
|
1115
|
+
rb_define_singleton_method(mLibInjection, "detect_raw", rb_li_detect_raw, 1);
|
|
1116
|
+
rb_define_singleton_method(mLibInjection, "detect_url_encoded_raw",
|
|
1117
|
+
rb_li_detect_url_encoded_raw, 4);
|
|
1118
|
+
rb_define_singleton_method(mLibInjection, "sqli_result", rb_li_sqli_result, -1);
|
|
1119
|
+
rb_define_singleton_method(mLibInjection, "sqli_contexts", rb_li_sqli_contexts, 1);
|
|
1120
|
+
rb_define_singleton_method(mLibInjection, "sqli_tokens", rb_li_sqli_tokens, -1);
|
|
1121
|
+
rb_define_singleton_method(mLibInjection, "sqli_fingerprint_for", rb_li_sqli_fingerprint_for,
|
|
1122
|
+
-1);
|
|
1123
|
+
rb_define_singleton_method(mLibInjection, "sqli_flags", rb_li_sqli_flags, -1);
|
|
1124
|
+
|
|
1125
|
+
rb_define_singleton_method(mLibInjection, "xss?", rb_li_xss_p, 1);
|
|
1126
|
+
rb_define_singleton_method(mLibInjection, "xss_result", rb_li_xss_result, -1);
|
|
1127
|
+
rb_define_singleton_method(mLibInjection, "xss_contexts", rb_li_xss_contexts, 1);
|
|
1128
|
+
rb_define_singleton_method(mLibInjection, "html5_tokens", rb_li_html5_tokens, -1);
|
|
1129
|
+
rb_define_singleton_method(mLibInjection, "xss_flags", rb_li_xss_flags, -1);
|
|
1130
|
+
|
|
1131
|
+
rb_define_singleton_method(mLibInjection, "lib_version", rb_li_lib_version, 0);
|
|
1132
|
+
}
|