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
data/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# rack-libinjection
|
|
2
|
+
|
|
3
|
+
`rack-libinjection` is a small native Ruby binding plus Rack middleware for
|
|
4
|
+
[libinjection](https://github.com/libinjection/libinjection).
|
|
5
|
+
|
|
6
|
+
It adds a tokenizer/fingerprint-based SQLi/XSS **signal layer** to Rack and
|
|
7
|
+
Rails applications. It is report-only by default.
|
|
8
|
+
|
|
9
|
+
## Important scope note
|
|
10
|
+
|
|
11
|
+
This is not a full WAF. The middleware scans only the configured Rack surfaces.
|
|
12
|
+
The default is:
|
|
13
|
+
|
|
14
|
+
- parsed Rack params (`scan: [:params]`)
|
|
15
|
+
|
|
16
|
+
Optional surfaces are available for raw query strings, path, headers, and cookies:
|
|
17
|
+
|
|
18
|
+
- `scan: [:query]` for a fast raw query-string signal that avoids Rack nested params parsing
|
|
19
|
+
- `scan: [:params, :path, :headers, :cookies]` for semantic params plus other Rack surfaces
|
|
20
|
+
|
|
21
|
+
The middleware can scan both SQLi and XSS signals by default, or only one class through `threats: [:sqli]` / `threats: [:xss]`. Path and raw-query values are decoded up to `path_decode_depth` times for detection inside the native extension, avoiding Ruby `gsub`/regex allocation on this hot path. Query/path/header-only configurations are scanned directly from the Rack env without constructing `Rack::Request`; params and cookies still use Rack parsers. Header
|
|
22
|
+
scanning skips common low-signal protocol/browser headers by default through
|
|
23
|
+
`ignore_headers`. Cookie values are scanned when `:cookies` is enabled; cookie
|
|
24
|
+
names are skipped unless `scan_cookie_names: true` is configured. In `mode: :block`, native/Rack parser errors fail closed by default through `parser_errors: :auto`, and values skipped by `max_value_bytes` / `max_depth` fail closed by default through `skipped_inputs: :auto`.
|
|
25
|
+
|
|
26
|
+
Raw JSON body scanning is not part of the current middleware. JSON bodies,
|
|
27
|
+
large request bodies, multipart file contents, and application-specific decoding
|
|
28
|
+
need separate design. See [GET_STARTED.md](GET_STARTED.md) before deploying.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
All installation, middleware configuration, low-level API examples, vendoring,
|
|
33
|
+
system-library mode, GVL notes, threat model, and operational guidance live in
|
|
34
|
+
[GET_STARTED.md](GET_STARTED.md).
|
|
35
|
+
|
|
36
|
+
## What this is
|
|
37
|
+
|
|
38
|
+
- A native binding to vendored `libinjection` `v4.0.0`.
|
|
39
|
+
- A Rack/Rails middleware that emits attack signals for configured request
|
|
40
|
+
surfaces.
|
|
41
|
+
- A diagnostic API for SQLi fingerprints, parser contexts, SQL tokens, XSS
|
|
42
|
+
contexts, and HTML5 tokens.
|
|
43
|
+
- A small signal layer that can feed logs, notifications, or `Rack::Attack`-style
|
|
44
|
+
scoring.
|
|
45
|
+
|
|
46
|
+
## What this is not
|
|
47
|
+
|
|
48
|
+
- Not a full WAF.
|
|
49
|
+
- Not a replacement for Rails escaping, bind params, CSP, authorization, or
|
|
50
|
+
upstream WAF/rate-limit protections.
|
|
51
|
+
- Not a promise to catch every SQLi/XSS payload.
|
|
52
|
+
- Not enabled in blocking mode by default.
|
|
53
|
+
- Not a JSON-body scanner yet.
|
|
54
|
+
|
|
55
|
+
## Native dependency
|
|
56
|
+
|
|
57
|
+
The default build uses pinned, vendored `libinjection` sources. Source checkouts
|
|
58
|
+
can regenerate or verify the vendored tree through `script/vendor_libs.rb`; see
|
|
59
|
+
[GET_STARTED.md](GET_STARTED.md) for commands and system-library mode.
|
|
60
|
+
|
|
61
|
+
## Security policy
|
|
62
|
+
|
|
63
|
+
Report suspected vulnerabilities privately. See [SECURITY.md](SECURITY.md).
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
The Ruby gem is MIT-licensed. The vendored upstream `libinjection` sources are
|
|
68
|
+
BSD-3-Clause licensed and included as `LICENSE-libinjection.txt`.
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
`rack-libinjection` is a security signal layer, so vulnerability reports are
|
|
4
|
+
welcome even for edge cases that look minor.
|
|
5
|
+
|
|
6
|
+
## Supported versions
|
|
7
|
+
|
|
8
|
+
The project is pre-1.0. Security fixes are applied to the latest released
|
|
9
|
+
version only until a stable compatibility policy exists.
|
|
10
|
+
|
|
11
|
+
## Reporting a vulnerability
|
|
12
|
+
|
|
13
|
+
Please do not open a public issue for a suspected vulnerability.
|
|
14
|
+
|
|
15
|
+
Send a report to:
|
|
16
|
+
|
|
17
|
+
- `romanhajdarov@gmail.com`
|
|
18
|
+
|
|
19
|
+
Include:
|
|
20
|
+
|
|
21
|
+
- affected version / commit;
|
|
22
|
+
- Ruby, Rack, and OS versions;
|
|
23
|
+
- minimal reproduction;
|
|
24
|
+
- whether the issue is in the native binding, Rack middleware, vendoring, or
|
|
25
|
+
documentation;
|
|
26
|
+
- expected vs actual behavior.
|
|
27
|
+
|
|
28
|
+
## Scope
|
|
29
|
+
|
|
30
|
+
Useful reports include:
|
|
31
|
+
|
|
32
|
+
- crashes or memory-safety issues in the native extension;
|
|
33
|
+
- incorrect use of vendored libinjection sources;
|
|
34
|
+
- middleware bypasses caused by this gem's Rack integration;
|
|
35
|
+
- false claims in documentation that can lead to unsafe deployment;
|
|
36
|
+
- unsafe default behavior around notifications, blocking mode, or skipped input.
|
|
37
|
+
|
|
38
|
+
Bypasses in upstream `libinjection` itself should also be reported upstream, but
|
|
39
|
+
it is still useful to notify this project when the Rack integration makes them
|
|
40
|
+
worse or hides the limitation.
|
|
41
|
+
|
|
42
|
+
## Native hardening expectations
|
|
43
|
+
|
|
44
|
+
The native extension is expected to tolerate empty strings, binary strings,
|
|
45
|
+
invalid UTF-8 byte sequences, null bytes, and large inputs without crashing.
|
|
46
|
+
Public low-level scans copy large inputs before releasing the GVL, so the C
|
|
47
|
+
scanner does not read directly from a Ruby `String` buffer while Ruby code may
|
|
48
|
+
mutate the same object from another thread. Copied native buffers are released
|
|
49
|
+
through `rb_ensure` cleanup paths.
|
|
50
|
+
|
|
51
|
+
The project includes a `security:smoke` task with random/binary inputs and a CI
|
|
52
|
+
sanitizer job for AddressSanitizer/UBSan builds. These checks are not a
|
|
53
|
+
replacement for upstream libinjection fuzzing, but regressions in the Ruby
|
|
54
|
+
binding or middleware integration should be caught here.
|
|
55
|
+
|
|
56
|
+
The binding scans bytes. It does not perform Unicode normalization or transcode
|
|
57
|
+
UTF-16/UTF-32 payloads into UTF-8. Applications that accept non-UTF-8 text must
|
|
58
|
+
normalize before scanning if they expect semantic text detection rather than raw
|
|
59
|
+
byte scanning.
|
|
60
|
+
|
|
61
|
+
## Fail-closed behavior in blocking mode
|
|
62
|
+
|
|
63
|
+
Vendored libinjection v4 can return parser errors, and Rack can raise parameter/cookie parser errors before values are available for semantic scanning. The middleware exposes `parser_errors:` so applications can choose report, block, or raise behavior. The default `:auto` policy reports parser errors in report mode and blocks them in block mode.
|
|
64
|
+
|
|
65
|
+
Inputs beyond `max_value_bytes` or deeper than `max_depth` are not scanned. The middleware exposes `skipped_inputs:` so applications can choose report, block, or allow behavior. The default `:auto` policy reports skipped input in report mode and blocks it in block mode.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mkmf"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
|
|
6
|
+
USE_SYSTEM = arg_config("--use-system-libinjection") || ENV["LIBINJECTION_USE_SYSTEM"] == "1"
|
|
7
|
+
|
|
8
|
+
EXT_DIR = __dir__
|
|
9
|
+
PRIMARY_VENDOR_DIR = File.join(EXT_DIR, "vendor", "libinjection")
|
|
10
|
+
|
|
11
|
+
REQUIRED_VENDOR_FILES = %w[
|
|
12
|
+
src/libinjection.h
|
|
13
|
+
src/libinjection_error.h
|
|
14
|
+
src/libinjection_sqli.h
|
|
15
|
+
src/libinjection_sqli_data.h
|
|
16
|
+
src/libinjection_xss.h
|
|
17
|
+
src/libinjection_html5.h
|
|
18
|
+
src/libinjection_sqli.c
|
|
19
|
+
src/libinjection_xss.c
|
|
20
|
+
src/libinjection_html5.c
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def compiler_version
|
|
25
|
+
cc = RbConfig::CONFIG.fetch("CC", "cc")
|
|
26
|
+
`#{cc} --version 2>&1`
|
|
27
|
+
rescue StandardError
|
|
28
|
+
""
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def gcc_without_clang?
|
|
32
|
+
version = compiler_version.downcase
|
|
33
|
+
version.include?("gcc") && !version.include?("clang")
|
|
34
|
+
end
|
|
35
|
+
def vendor_ready?(dir)
|
|
36
|
+
File.file?(File.join(dir, ".vendored")) && REQUIRED_VENDOR_FILES.all? { |path| File.file?(File.join(dir, path)) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def abort_missing_vendor!
|
|
40
|
+
abort <<~MSG
|
|
41
|
+
libinjection vendored sources are missing.
|
|
42
|
+
|
|
43
|
+
Run:
|
|
44
|
+
ruby script/vendor_libs.rb
|
|
45
|
+
|
|
46
|
+
Security note: extconf.rb does not auto-download native sources during build.
|
|
47
|
+
MSG
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def find_vendor_dir
|
|
51
|
+
candidates = [PRIMARY_VENDOR_DIR]
|
|
52
|
+
|
|
53
|
+
dir = __dir__
|
|
54
|
+
6.times do
|
|
55
|
+
candidates << File.join(dir, "ext", "libinjection", "vendor", "libinjection")
|
|
56
|
+
dir = File.dirname(dir)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
candidates.map! { |path| File.expand_path(path) }
|
|
60
|
+
candidates.uniq!
|
|
61
|
+
|
|
62
|
+
abort_missing_vendor! unless vendor_ready?(PRIMARY_VENDOR_DIR)
|
|
63
|
+
candidates.find { |path| vendor_ready?(path) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def configure_system!
|
|
67
|
+
puts "Building with SYSTEM libinjection"
|
|
68
|
+
|
|
69
|
+
if find_executable("pkg-config")
|
|
70
|
+
cflags = `pkg-config --cflags libinjection 2>/dev/null`.strip
|
|
71
|
+
libs = `pkg-config --libs libinjection 2>/dev/null`.strip
|
|
72
|
+
$CPPFLAGS << " #{cflags}" unless cflags.empty?
|
|
73
|
+
$libs << " #{libs}" unless libs.empty?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
abort "libinjection.h is required" unless have_header("libinjection.h")
|
|
77
|
+
abort "libinjection_sqli.h is required" unless have_header("libinjection_sqli.h")
|
|
78
|
+
abort "libinjection_xss.h is required" unless have_header("libinjection_xss.h")
|
|
79
|
+
abort "libinjection library is required" unless have_library("injection", "libinjection_sqli")
|
|
80
|
+
abort "libinjection_version() is required" unless have_func("libinjection_version")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def configure_vendored!(vendor_dir)
|
|
84
|
+
abort "libinjection vendored sources are missing" unless vendor_dir
|
|
85
|
+
|
|
86
|
+
versions = File.read(File.join(vendor_dir, ".vendored"))
|
|
87
|
+
puts "Building with VENDORED libinjection from #{vendor_dir}"
|
|
88
|
+
puts " #{versions.tr("\n", ", ")}"
|
|
89
|
+
|
|
90
|
+
src_dir = File.join(vendor_dir, "src")
|
|
91
|
+
$CPPFLAGS << " -I#{src_dir}"
|
|
92
|
+
$srcs = %w[libinjection_ext.c libinjection_sqli.c libinjection_xss.c libinjection_html5.c]
|
|
93
|
+
$VPATH = [EXT_DIR, src_dir]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
$CFLAGS << " -std=c99 -Wall -Wextra -O3"
|
|
97
|
+
$CFLAGS << " -Wno-unused-function -Wno-unused-parameter"
|
|
98
|
+
|
|
99
|
+
if ENV["LIBINJECTION_SANITIZE"] == "1"
|
|
100
|
+
$CFLAGS << " -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer"
|
|
101
|
+
$LDFLAGS << " -fsanitize=address,undefined"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
$CFLAGS << " -Wno-enum-int-mismatch" if gcc_without_clang?
|
|
105
|
+
$warnflags = ""
|
|
106
|
+
|
|
107
|
+
unless $CFLAGS.include?("LI_NOGVL_THRESHOLD")
|
|
108
|
+
$CFLAGS << " -DLI_NOGVL_THRESHOLD=1024"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
USE_SYSTEM ? configure_system! : configure_vendored!(find_vendor_dir)
|
|
112
|
+
|
|
113
|
+
create_makefile("libinjection/libinjection_native")
|