* feat(modules): tcp module executor dial+probe+banner with word/regex/size matchers, ctx-honest watcher, port required. * feat(modules): add redis unauthenticated access tcp module first shipped tcp module: sends INFO to redis (6379) and matches redis_version in the reply, which an auth-required server never returns (it answers -NOAUTH). extracts the leaked version. * docs(modules): document the tcp module type flip the http-only note and add a tcp config section (port, data, word/regex/size matchers and regex extractors against the banner), pointing at the redis demo module. * feat(modules): support matchers-condition in tcp modules tcp matchers were and-only; add the matchers-condition key (and default, or) mirroring the http executor's checkMatchers, validated at load via the shared validateMatchersCondition. documents the override in the combining matchers section. * feat(modules): decode escape sequences in tcp data payloads decode C-style escapes (\\ \a \b \f \n \r \t \v \xHH) in a tcp module's data value before it goes on the wire, so a single-quoted or plain yaml scalar reaches the service with the same control bytes a double-quoted one already would. an unrecognized escape is kept verbatim so no bytes are silently lost. * feat(modules): add tcp recon modules for common services add five built-in tcp modules alongside the redis one so the executor ships a real service set: memcached (unauth version probe), ssh and ftp (passive banner version disclosure), mysql/mariadb (handshake exposure, matched with matchers-condition: or across both auth plugin names), and vnc (rfb handshake exposure). each fingerprint tracks the documented on-wire protocol and pulls the version into an extractor. * feat(modules): add mail and rsync tcp recon modules add four more passive-banner tcp modules: smtp, pop3, and imap read the greeting each mail service sends on connect (the smtp matcher requires an esmtp/smtp marker so it does not fire on a bare 220 ftp greeting), and rsync detects an exposed daemon by its @RSYNCD handshake. each pulls the banner or protocol version into an extractor.
9.3 KiB
writing sif modules
sif modules are yaml files that define security checks. they're similar to nuclei templates but designed specifically for sif.
module locations
- built-in:
modules/directory in the sif installation - user-defined:
~/.config/sif/modules/(linux/macos) or%LOCALAPPDATA%\sif\modules\(windows)
user modules can override built-in modules with the same id.
basic structure
id: unique-module-id
info:
name: human readable name
author: your-name
severity: low|medium|high|critical|info
description: what this module checks for
tags: [tag1, tag2, tag3]
type: http
http:
method: GET
paths:
- "{{BaseURL}}/path"
matchers:
- type: status
status:
- 200
fields
id (required)
unique identifier for the module. use lowercase with hyphens.
id: sqli-error-based
info (required)
metadata about the module.
info:
name: SQL Injection Detection
author: sif
severity: high
description: detects sql injection via error messages
tags: [sqli, injection, owasp-top10]
severity levels:
info- informational findinglow- minor issuemedium- moderate security concernhigh- serious vulnerabilitycritical- critical security flaw
type (required)
module type. http and tcp are supported.
type: http
http
http request configuration.
method
http method to use.
http:
method: GET
supported: GET, POST, PUT, DELETE, HEAD, OPTIONS
paths
urls to check. use {{BaseURL}} as placeholder for the target.
http:
paths:
- "{{BaseURL}}/.git/HEAD"
- "{{BaseURL}}/.git/config"
- "{{BaseURL}}/admin"
payloads
values to inject into paths. use {{payload}} as placeholder.
http:
paths:
- "{{BaseURL}}/?id={{payload}}"
payloads:
- "'"
- "1' OR '1'='1"
- "1; DROP TABLE--"
each payload creates a separate request for each path.
attack
how paths and payloads combine into requests.
http:
attack: pitchfork
clusterbomb(default) - every path is tried with every payloadpitchfork- path and payload are paired by index, stopping at the shorter list
wordlist
a local file whose non-empty lines fuzz the {{word}} placeholder, one request
per word. paths without {{word}} are still requested as-is.
http:
wordlist: /usr/share/wordlists/dirs.txt
paths:
- "{{BaseURL}}/{{word}}"
headers
custom headers to send.
http:
headers:
User-Agent: "Mozilla/5.0"
X-Custom-Header: "value"
body
request body for POST/PUT requests.
http:
method: POST
body: '{"username": "admin", "password": "{{payload}}"}'
threads
concurrent requests (default: 10).
http:
threads: 5
tcp
raw tcp configuration. connects to a port, optionally sends a payload, and runs matchers and extractors against the response banner.
port
the tcp port to connect to (required, 1-65535). the port selects the service, so the target's own scheme and port are ignored.
tcp:
port: 6379
data
an optional payload sent after connecting. sif decodes C-style escapes in the
value, so \r, \n, \t, \\ and \xHH reach the wire as the raw bytes no
matter how the yaml scalar is quoted; an unrecognized escape is left verbatim. a
server that only banners (ssh, smtp) needs no data at all.
tcp:
port: 6379
data: "INFO\r\n"
matchers and extractors
tcp runs word, regex and size matchers (no status/favicon, those are
http only) against the banner string, and regex extractors pull values out of
it. there is no part selector: the banner is the only stream.
tcp:
port: 6379
data: "INFO\r\n"
matchers:
- type: word
words:
- "redis_version:"
extractors:
- type: regex
name: redis_version
regex:
- "redis_version:([0-9.]+)"
group: 1
see modules/recon/redis-unauth-exposure.yaml for the full module.
matchers
matchers determine if a response indicates a finding.
status matcher
match http status codes.
matchers:
- type: status
status:
- 200
- 301
- 302
word matcher
match words in response.
matchers:
- type: word
part: body
words:
- "admin"
- "login"
condition: or
parts:
body- response bodyheader- response headers
conditions:
or- match any word (default)and- match all words
regex matcher
match regex patterns.
matchers:
- type: regex
part: body
regex:
- "SQL syntax.*MySQL"
- "ORA-[0-9]+"
- "PostgreSQL.*ERROR"
condition: or
size matcher
match the response body length in bytes (measured after the 5 MB response cap, so larger sizes never match).
matchers:
- type: size
size:
- 0
- 1337
favicon matcher
match the shodan-style mmh3 hash of the response body. point the module at a favicon and list the hashes of the tech you want to fingerprint.
http:
paths:
- "{{BaseURL}}/favicon.ico"
matchers:
- type: status
status:
- 200
- type: favicon
hash:
- -235701012 # jenkins
- 1278322581 # grafana
the hash is shodan's http.favicon.hash value. paste it signed or unsigned;
both 32-bit forms are accepted, so values from shodan or any favicon-hash tool
drop in without conversion. pair it with a status: 200 matcher so an error
page served for /favicon.ico is not hashed. a finding fires when the body
hashes to any listed value.
combining matchers
multiple matchers are combined with AND logic by default.
matchers:
- type: status
status:
- 200
- type: word
part: body
words:
- "ref: refs/"
condition: or
this matches responses with status 200 AND containing "ref: refs/".
set matchers-condition: or to fire when any matcher hits instead of all; it
applies to http and tcp modules alike.
http:
matchers-condition: or
matchers:
- type: status
status:
- 401
- type: status
status:
- 403
this matches a 401 OR a 403 response. matchers-condition accepts and (the
default) or or; any other value fails at load.
extractors
extractors pull data from responses.
regex extractor
extractors:
- type: regex
name: version
part: body
regex:
- "version[\"']?\\s*[:=]\\s*[\"']?([0-9.]+)"
group: 1
group: capture group to extract (0 = full match, 1+ = groups)
kv extractor
record every response header as a key-value pair, namespaced by name.
extractors:
- type: kv
name: headers
part: header
json extractor
extract values from a json body by gjson path (github.com/tidwall/gjson); the first path that exists is stored under name.
extractors:
- type: json
name: version
part: body
json:
- "version"
- "data.version"
examples
exposed git repository
id: git-exposed
info:
name: exposed git repository
author: sif
severity: high
description: detects exposed .git directories
tags: [git, exposure, source-code]
type: http
http:
method: GET
paths:
- "{{BaseURL}}/.git/HEAD"
- "{{BaseURL}}/.git/config"
matchers:
- type: word
part: body
words:
- "ref: refs/"
- "[core]"
condition: or
- type: status
status:
- 200
extractors:
- type: regex
name: branch
part: body
regex:
- "ref: refs/heads/(.+)"
group: 1
sql injection detection
id: sqli-error-based
info:
name: sql injection (error-based)
author: sif
severity: high
description: detects sql injection via database errors
tags: [sqli, injection, database]
type: http
http:
method: GET
paths:
- "{{BaseURL}}/?id={{payload}}"
- "{{BaseURL}}/search?q={{payload}}"
payloads:
- "'"
- "1' OR '1'='1"
- "1; SELECT * FROM--"
threads: 10
matchers:
- type: regex
part: body
regex:
- "SQL syntax.*MySQL"
- "ORA-[0-9]+"
- "PostgreSQL.*ERROR"
- "Microsoft SQL Server"
condition: or
security headers check
id: security-headers
info:
name: security headers analysis
author: sif
severity: info
description: checks for missing security headers
tags: [headers, security, info]
type: http
http:
method: GET
paths:
- "{{BaseURL}}/"
matchers:
- type: status
status:
- 200
extractors:
- type: kv
name: headers
part: header
tips
-
use specific paths - don't just check
/, be specific about what you're looking for -
combine matchers - use status + content matchers together to reduce false positives
-
limit payloads - too many payloads slow down scans, pick the most effective ones
-
tag properly - use consistent tags so modules can be filtered with
-mt -
test locally - run your module against a test target before sharing
running modules
# list all modules
./sif -lm
# run specific module
./sif -u https://example.com -m git-exposed
# run multiple modules
./sif -u https://example.com -m git-exposed,sqli-error-based
# run by tag
./sif -u https://example.com -mt owasp-top10
# run all modules
./sif -u https://example.com -am