Compare commits

..

94 Commits

Author SHA1 Message Date
vmfunc 60b2dd2804 Merge pull request #63 from 0x4bs3nt/feat/builtin-whois
feat(modules): builtin whois scan as module
2026-02-08 14:12:27 +01:00
vmfunc 73af6bc868 Merge pull request #64 from 0x4bs3nt/feat/builtin-frameworks
feat(modules): builtin framework detection as module
2026-02-08 14:11:56 +01:00
vmfunc 88fb01d70a Merge branch 'main' into feat/builtin-frameworks 2026-01-12 11:22:56 +01:00
vmfunc 8c4d6d81a7 fix: rename to snakecase 2026-01-12 11:19:54 +01:00
vmfunc b6ad6f7a3e fix: renamed whois module file
Renamed whois scan module file to differentiate from legacy whois scan
file.
2026-01-12 11:19:54 +01:00
vmfunc 26ea7c0ee6 fix(conflicts): fix PR conflicts on 2026-01-12 11:19:48 +01:00
vmfunc 976a2d4390 Merge pull request #61 from 0x4bs3nt/feat/builtin-nuclei
feat(modules): builtin nuclei scan as module
2026-01-11 16:39:18 -08:00
vmfunc c62409bf0a fix: rename to snakecase 2026-01-07 22:39:56 +01:00
vmfunc 280e6ad8b0 fix: rename to snakecase 2026-01-07 22:39:19 +01:00
vmfunc 2ee2283412 fix: frameworks module file rename
Renamed frameworks module file to differentiate from legacy framework
scans.
2026-01-07 22:34:53 +01:00
vmfunc 748f320e59 fix: renamed nuclei module file
Renamed the nuclei module file to differentiate from the nuclei legacy
scan file.
2026-01-07 22:33:49 +01:00
vmfunc ad9a98b132 fix: colorizer exception
Fixed Nuclei giving off exception for missing Colorizer on the executor
options.
2026-01-07 19:06:51 +01:00
vmfunc 6d6a57a0e0 fix(nuclei): logdir, headless option and hosterrorscache
Set the HostErrorsCache executor option, cache is created but not passed
as option.
Headless initialization is required even without browser templates.
Nuclei expects project file to be set
2026-01-07 17:01:22 +01:00
vmfunc 53687e4bd4 fix: nuclei scan nil pointer dereference
Fixed nil pointer dereference issues in the nuclei scan running as a
module
2026-01-07 15:09:49 +01:00
vmfunc 4331929bd0 feat(modules): legacy nuclei scan
Converted nuclei scan to be able to run as module.
2026-01-07 13:07:35 +01:00
vmfunc 9290bbe6a0 feat(modules): legacy framework scan
Converted legacy framework scan to be able to run as module.
2026-01-07 13:02:40 +01:00
vmfunc 0958a2c19c feat(modules): infra for builtin modules
Infrastructure preparation for builtin complex Go module registration.
2026-01-07 12:56:17 +01:00
vmfunc 4e97da6863 Merge pull request #56 from 0x4bs3nt/feat/astro-framework-detection
feat(frameworks): add Astro framework detection
2026-01-06 12:10:34 -08:00
vmfunc e5c88b754c fix: adjust generator meta weight
Adjusted generator meta weight to remain consistent with other meta-framework detectors.

Co-authored-by: Celeste Hickenlooper <celeste@router.sex>
2026-01-06 14:45:03 +01:00
vmfunc 2389901614 Merge pull request #55 from 0x4bs3nt/docs/contributing-update
docs: update CONTRIBUTING.md
2026-01-05 23:50:02 -08:00
vmfunc 1f73a0dd8f fix: discord invite
Fixed discord invite to official server invite url.
2026-01-06 06:35:32 +01:00
vmfunc 4de9786e99 fix: use dynamic versioning for debian packages 2026-01-05 20:55:30 -08:00
vmfunc e5510a4a16 docs: update CONTRIBUTING.md
Update CONTRIBUTING.md docs with up to date data:
 - Discord invite to new sif server
 - Update URL-s to new vmfunc/sif repository
 - Update guidelines on contributing framework detection patterns
2026-01-06 05:30:34 +01:00
vmfunc 8928ac7e99 docs: add apt/cloudsmith installation instructions and badge 2026-01-05 20:28:30 -08:00
vmfunc 137ba0c89a ci: push debian packages to cloudsmith 2026-01-05 20:28:07 -08:00
vmfunc 9ea0805090 ci: add debian package builds to releases 2026-01-05 20:13:18 -08:00
vmfunc df6bfc91f0 docs: add 0xatrilla to contributors for AUR packaging 2026-01-05 19:51:50 -08:00
vmfunc 255b67dff6 docs: add AUR and Homebrew badges to readme 2026-01-05 19:48:51 -08:00
vmfunc 9c3a5fe1f0 Merge pull request #53 from 0xatrilla/add-aur-install-instructions
docs: add AUR installation instructions
2026-01-05 19:44:43 -08:00
vmfunc df274328ee chore: revise arch linux installation section in README
Updated Arch Linux installation instructions in README.md.
2026-01-05 19:44:15 -08:00
vmfunc 487b440e52 feat(frameworks): add Astro framework detection
Add detection support for the Astro meta framework.

Includes signature detection, version extraction and tests with full
signature coverage.
2026-01-06 04:40:15 +01:00
acxtrilla bf613c3aaf docs: add AUR installation instructions
Added Arch Linux (AUR) installation section to README with instructions
for installing via AUR helpers (yay/paru) or manually with makepkg.

Package available at: https://aur.archlinux.org/packages/sif
2026-01-06 01:56:40 +00:00
vmfunc 2922481be3 docs: add homebrew installation instructions 2026-01-05 16:53:26 -08:00
vmfunc e27476652f chore: readme inconsistency 2026-01-03 06:14:40 -08:00
vmfunc 5ffea24182 ci: upgrade to go 1.24 in all workflows 2026-01-03 06:04:33 -08:00
vmfunc 67288577a6 chore: add license headers to missing files 2026-01-03 06:01:00 -08:00
vmfunc 7beb68e145 feat(output): add styled console output with module loggers
- Add output package with colored prefixes and module loggers
- Each module gets unique background color based on name hash
- Add spinner for indeterminate operations
- Add progress bar for known-count operations
- Update all scan files to use ModuleLogger pattern
- Add clean PrintSummary for scan completion
2026-01-03 05:57:10 -08:00
vmfunc d2f3a42c43 docs: add comprehensive documentation and fix github actions
- add docs/ with installation, usage, modules, scans, and api docs
- add docs link to main readme
- fix release.yml to bundle modules directory with releases
- add module system tests to runtest.yml
- standardize go version to 1.23 across workflows
2026-01-03 05:57:10 -08:00
vmfunc c33896a45a docs: update readme and add module documentation 2026-01-03 05:57:10 -08:00
vmfunc 652f4f0c7c feat: show module loading and execution logs by default 2026-01-03 05:57:10 -08:00
vmfunc bf65820ff1 feat: add debug logging for module execution 2026-01-03 05:57:10 -08:00
vmfunc 0d4c10e6ff refactor: move pkg/scan to internal/scan 2026-01-03 05:57:10 -08:00
vmfunc 0eba16bc4d fix: add io.LimitReader and proper error handling to shodan.go
Add io.LimitReader with 5MB limit to prevent memory exhaustion and
fix ignored error in queryShodanHost. The error from io.ReadAll was
previously being discarded with _, which could mask read failures.
2026-01-03 05:57:10 -08:00
vmfunc c067eafed0 fix: add io.LimitReader to prevent memory exhaustion
Add io.LimitReader with 5MB limit to all HTTP response body reads
to prevent potential memory exhaustion from maliciously large responses.

Affected files:
- pkg/scan/cms.go
- pkg/scan/subdomaintakeover.go
- pkg/scan/js/scan.go
- pkg/scan/js/supabase.go
2026-01-03 05:57:10 -08:00
vmfunc f20198dd26 fix: regex compilation performance
Move regex compilation from inside functions to package level to avoid
recompiling on every function call. This improves performance by
compiling the regex patterns once at package initialization.

- Move jwtRegex to package level in supabase.go
- Move nextPagesRegex to package level in next.go
- Use strings.Builder instead of string concatenation in next.go
2026-01-03 05:57:10 -08:00
vmfunc a86f117658 feat: implement loadYAML in module loader 2026-01-03 05:57:10 -08:00
vmfunc a614b2ee8a feat: integrate module system into sif.go
Add module system integration allowing users to run YAML-defined security
modules via CLI flags. Implements --list-modules to display available modules,
and supports running modules by ID, tags, or all at once.
2026-01-03 05:57:10 -08:00
vmfunc a8686c1e4a feat: add module cli flags 2026-01-03 05:57:10 -08:00
vmfunc c3f824e1e3 feat: add built-in yaml modules for security scanning 2026-01-03 05:57:10 -08:00
vmfunc 57acc6d37c feat: add yaml module parser and http executor 2026-01-03 05:57:10 -08:00
vmfunc 2596ce1ea2 feat: add module system infrastructure 2026-01-03 05:57:10 -08:00
vmfunc abce1405ca refactor: move config to internal 2026-01-03 05:57:10 -08:00
vmfunc f212aed50a refactor: move logger to internal 2026-01-03 05:57:10 -08:00
vmfunc 7e27e73554 refactor: rewrite framework detection with modular detector architecture
- create detector interface and registry for extensibility
- extract detectors to separate files: backend.go, frontend.go, cms.go, meta.go
- reduce detect.go from 785 lines to 178 lines (pure orchestrator)
- export VersionMatch and ExtractVersionOptimized for detector use
- create result.go with NewFrameworkResult and WithVulnerabilities helpers
- add url validation to New() for early error detection
- add sif_test.go with main package tests
- update detect_test.go to use external test package pattern
2026-01-03 05:57:09 -08:00
vmfunc da645ee42f feat: add generic types and type-safe result handling
introduce ScanResult interface and generic NewModuleResult constructor
for compile-time type safety when creating module results.

- add pkg/scan/result.go with ScanResult interface and named slice types
- add typed shodanMetadata struct to replace map[string]interface{}
- refactor supabase.go with typed response structs and json.RawMessage
- add ResultType() methods to all scan result types
- update sif.go to use NewModuleResult generic constructor

this provides type safety without breaking JSON serialization.
2026-01-03 05:57:09 -08:00
vmfunc 16c191fbaa refactor: extract cve database to separate file
move CVEEntry struct and knownCVEs map to cve.go for better
organization. this reduces detect.go by another 170 lines and makes
the CVE database easier to maintain and extend.
2026-01-03 05:57:09 -08:00
vmfunc 697c7def57 perf: precompile framework version regex patterns
move version extraction patterns to version.go and compile them at init
time instead of recompiling on every check. this significantly improves
framework detection performance.

- add version.go with pre-compiled regex patterns for all frameworks
- update detect.go to use extractVersionOptimized
- remove duplicate extractVersionWithConfidence and isValidVersion functions
- add io.LimitReader to prevent memory exhaustion on large responses
- update tests to use the optimized version extraction
2026-01-03 05:57:09 -08:00
vmfunc ff002f43f7 fix: response body leaks in cms.go and sql.go
close response bodies immediately after reading instead of deferring
inside loops, which delays closure until function exit
2026-01-03 05:57:09 -08:00
vmfunc 30482ecbce fix: response body leak in scan.go robots processing
move resp.body.close() inside the loop after use instead of deferring,
which would only run when the outer function exits
2026-01-03 05:57:09 -08:00
vmfunc ce07ac8b16 feat: add generic worker pool for concurrent task processing
implement channel-based work distribution with generics for type-safe
concurrent processing, includes run, runwithfilter, and foreach methods
with comprehensive test coverage
2026-01-03 05:57:09 -08:00
vmfunc 8b056231f7 perf: optimize deduplication with map-based o(1) lookups in lfi and sql
replace o(n) slice iteration with map lookups for checking duplicates,
preallocate result slices, reduce lock hold time by separating map check
from result append
2026-01-03 05:57:09 -08:00
vmfunc c517e2eb1f fix: data races and slice preallocation in dirlist and dnslist
add mutex protection for concurrent slice appends, preallocate result
slices with reasonable capacity, use logger instead of direct file i/o
2026-01-03 05:57:09 -08:00
vmfunc 0dd533446a fix: error patterns and string building in sif.go and js/scan.go
replace errors.new(fmt.sprintf()) with fmt.errorf, use strings.builder
instead of string concatenation in loop, fix defer in loop issue,
preallocate slices where size is estimable
2026-01-03 05:57:09 -08:00
vmfunc 1c0ad454dc test: add logger tests for buffered write functionality
covers initialization, write, flush, close, concurrent writes, and
file creation with proper cleanup verification
2026-01-03 05:57:09 -08:00
vmfunc 17aa3c00f0 refactor: logger to use buffered file handles
replace per-write file open/close with cached file handles and buffered
writers for significantly reduced i/o overhead. adds flush and close
methods for proper cleanup at program exit.
2026-01-03 05:57:09 -08:00
vmfunc b6b0a5a782 chore: remove unused utils package
the returnApiOutput function was never used and contained only
hardcoded test data
2026-01-03 05:57:09 -08:00
vmfunc 24b3b43b57 ci: add test coverage reporting to workflow
run tests with race detector and coverage profiling, upload results
to codecov for visibility into test coverage metrics
2026-01-03 05:57:09 -08:00
vmfunc 1550202e7a ci: enhance golangci-lint with additional linters
add gocritic, revive, unconvert, prealloc, bodyclose, noctx, and
exportloopref for better code quality detection
2026-01-03 05:57:09 -08:00
vmfunc a8eb319efe Merge pull request #51 from andrewgazelka/chore/modernize-nix-flake
chore(nix): modernize flake to use buildGoModule
2026-01-03 00:38:59 -08:00
Andrew Gazelka 43d5f7383b chore(nix): modernize flake to use buildGoModule
- Remove flake-utils dependency (use local forAllSystems helper)
- Remove gomod2nix dependency (use native buildGoModule)
- Add overlay export for easy consumption
- Update nixpkgs to latest unstable
- Disable tests in nix build (require network access)
2026-01-03 00:25:37 -08:00
vmfunc 1447485af9 docs: update contributor name and add vxfemboy 2026-01-02 19:56:44 -08:00
vmfunc 6e1d5cf488 chore: fix contributorrc 2026-01-02 19:55:31 -08:00
vmfunc 1583e00478 chore: fix contributorrc 2026-01-02 19:51:03 -08:00
vmfunc 40db023632 Merge pull request #40 from vmfunc/feat/framework-detection
feat: framework detection module
2026-01-02 19:15:07 -08:00
vmfunc 5868deef21 fix: adjust sif logo alignment 2026-01-02 19:12:28 -08:00
vmfunc b43e54bf60 fix: improve version detection and add documentation
- fix version detection to validate reasonable version numbers (major < 100)
- remove overly permissive patterns that caused false positives
- add comprehensive framework contribution documentation to CONTRIBUTING.md
- document signature patterns, version detection, and CVE data format
- add configuration documentation for flags and env vars
- outline future enhancements for community contributions
2026-01-02 19:04:37 -08:00
vmfunc c75ebccb27 docs: add framework detection to readme 2026-01-02 18:54:24 -08:00
vmfunc 858555bc47 feat: expand framework detection with cvs, version confidence, concurrency
- add 20+ new framework signatures (vue, angular, react, svelte, sveltekit,
  remix, gatsby, joomla, magento, shopify, ghost, ember, backbone, meteor,
  strapi, adonisjs, cakephp, codeigniter, asp.net core, spring boot)
- add version confidence scoring with multiple detection sources
- add concurrent framework scanning for better performance
- expand cve database with 15+ known vulnerabilities (spring4shell, etc.)
- add risk level assessment based on cve severity
- add comprehensive security recommendations
- add new tests for all features
2026-01-02 18:52:15 -08:00
vmfunc 51ee65c5df chore: add license header to detect.go 2026-01-02 18:52:15 -08:00
vmfunc 035e9406d9 feat: improve framework detection with more signatures and tests
- use math.Exp instead of custom exp implementation
- add more framework signatures: next.js, nuxt.js, wordpress, drupal,
  symfony, fastapi, gin, phoenix
- fix header detection to check both header names and values
- simplify version detection (remove unnecessary padding)
- add comprehensive test suite for framework detection
- fix formatting in dork.go
2026-01-02 18:52:15 -08:00
vmfunc ba5468725e chore(actions): add framework to CI 2026-01-02 18:52:15 -08:00
vmfunc 49b081dc30 feat(framework-detection): weighted bayesian detection algorithm
- weighted signature matching for more accurate framework detection
- sigmoid normalization for confidence scores
- version detection with semantic versioning support
- header-only pattern
2026-01-02 18:52:15 -08:00
vmfunc 127eeff265 feat: framework detection module 2026-01-02 18:52:15 -08:00
vmfunc 1b493e9572 fix: use static discord badge instead of server id 2026-01-02 18:45:07 -08:00
vmfunc 74b044ce59 docs: update readme with new modules and discord link 2026-01-02 18:42:45 -08:00
vmfunc 2a2bcf5b92 feat: add lfi reconnaissance module (#49)
adds a new --lfi flag for local file inclusion vulnerability scanning:
- tests common lfi parameters with directory traversal payloads
- detects /etc/passwd, /etc/shadow, windows system files
- identifies php wrappers and encoded content
- supports various bypass techniques (null bytes, encoding)

closes #4
2026-01-02 18:41:30 -08:00
vmfunc 166e1b82c2 feat: add sql reconnaissance module (#48)
adds a new --sql flag that performs sql reconnaissance on target urls:
- detects common database admin panels (phpmyadmin, adminer, pgadmin, etc.)
- identifies database error disclosure (mysql, postgresql, mssql, oracle, sqlite)
- scans common paths for sql injection indicators

closes #3
2026-01-02 18:40:06 -08:00
vmfunc be9c02e8ba fix: remove duplicate subdomain takeover call and add config tests (#46)
- remove duplicate SubdomainTakeover call that ran twice when both
  dns scan and --st flag were enabled
- add comprehensive tests for config settings defaults and behavior
- fix formatting in dork.go

closes #1
2026-01-02 18:38:47 -08:00
vmfunc 018af224d6 Merge pull request #47 from vmfunc/feat/shodan-integration
feat: add shodan integration for host reconnaissance
2026-01-02 18:35:56 -08:00
vmfunc 97aeb4c8b0 feat: add shodan integration for host reconnaissance
adds a new --shodan flag that queries the shodan api for information
about the target host. requires SHODAN_API_KEY environment variable.

features:
- resolves hostnames to ip addresses
- queries shodan host api for reconnaissance data
- displays organization, isp, location, ports, services, and vulns
- logs results to file when logdir is specified

closes #2
2026-01-02 18:24:37 -08:00
vmfunc 2d38d3fea5 fix: update dependencies to address security vulnerabilities
- golang.org/x/crypto v0.26.0 -> v0.46.0 (critical: ssh auth bypass)
- golang.org/x/net v0.28.0 -> v0.48.0 (medium: xss vulnerability)
- golang.org/x/oauth2 v0.11.0 -> v0.34.0 (high: input validation)
- quic-go v0.48.2 -> v0.58.0 (high: panic on undecryptable packets)
- golang-jwt/jwt v4.5.1 -> v4.5.2 (high: memory allocation)
- cloudflare/circl v1.3.7 -> v1.6.2 (low: validation issues)
- refraction-networking/utls v1.5.4 -> v1.8.1 (medium: tls downgrade)
- ulikunitz/xz v0.5.11 -> v0.5.15 (medium: memory leak)
- klauspost/compress v1.16.7 -> v1.17.4

also fixes go vet warnings for non-constant format strings
2026-01-02 18:03:27 -08:00
vmfunc 3df0064e4b fix: update readme badges and use banner image
- update badges to point to vmfunc/sif
- replace ascii art with banner image
- fix header check action to check first 5 lines
- remove obsolete LICENSE.md
2026-01-02 17:54:17 -08:00
vmfunc c20c37463a chore: delete old license 2026-01-02 17:45:14 -08:00
vmfunc 9190fa4741 license: switch to bsd 3-clause, update headers and readme
- replace proprietary license with bsd 3-clause
- update all go file headers with new retro terminal style
- add header-check github action to enforce license headers
- completely rewrite readme to be modern, sleek, and lowercase
- fix broken badges
2026-01-02 17:41:18 -08:00
106 changed files with 1292 additions and 4745 deletions
-1
View File
@@ -1 +0,0 @@
* @vmfunc
-15
View File
@@ -1,15 +0,0 @@
# These are supported funding model platforms
github: vmfunc
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
-17
View File
@@ -1,17 +0,0 @@
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
labels:
- deps
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
labels:
- deps
-44
View File
@@ -1,44 +0,0 @@
ci:
- changed-files:
- any-glob-to-any-file: ".github/**"
deps:
- changed-files:
- any-glob-to-any-file:
- "go.mod"
- "go.sum"
- "flake.nix"
- "flake.lock"
scan:
- changed-files:
- any-glob-to-any-file: "internal/scan/**"
nuclei:
- changed-files:
- any-glob-to-any-file: "internal/nuclei/**"
modules:
- changed-files:
- any-glob-to-any-file:
- "internal/modules/**"
- "internal/scan/builtin/**"
- "internal/scan/js/**"
- "modules/**"
docs:
- changed-files:
- any-glob-to-any-file:
- "**/*.md"
- "docs/**"
tests:
- changed-files:
- any-glob-to-any-file: "**/*_test.go"
config:
- changed-files:
- any-glob-to-any-file:
- "internal/config/**"
- ".golangci.yml"
- ".editorconfig"
+3 -8
View File
@@ -1,12 +1,7 @@
name: automatic rebase
name: Automatic Rebase
on:
issue_comment:
types: [created]
permissions:
contents: write
pull-requests: write
jobs:
rebase:
name: Rebase
@@ -14,10 +9,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the latest code
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: automatic rebase
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+7 -18
View File
@@ -1,29 +1,18 @@
name: check large files
name: Check Large Files
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check-large-files:
name: check for large files
name: Check for large files
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: check for large files
- uses: actions/checkout@v4
- name: Check for large files
run: |
large_files=$(find . -path ./.git -prune -o -type f -size +5M -print)
if [ -n "$large_files" ]; then
echo "$large_files" | while read -r file; do
echo "::error file=${file}::File ${file} is larger than 5MB"
done
exit 1
fi
find . -type f -size +5M | while read file; do
echo "::error file=${file}::File ${file} is larger than 5MB"
done
+12 -27
View File
@@ -1,39 +1,24 @@
name: code quality
name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
schedule:
- cron: "0 6 * * 1" # monday 06:00 UTC
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
codeql:
qodana:
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
contents: write
pull-requests: write
checks: write
steps:
- uses: actions/checkout@v6
- name: set up go
uses: actions/setup-go@v5
- uses: actions/checkout@v4
with:
go-version: "1.25"
- name: initialize codeql
uses: github/codeql-action/init@v4
with:
languages: go
- name: build
run: go build ./...
- name: perform codeql analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:go"
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2024.3
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
+6 -10
View File
@@ -1,4 +1,4 @@
name: dependency review
name: "Dependency Review"
on:
pull_request:
push:
@@ -7,20 +7,16 @@ on:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: checkout repository
uses: actions/checkout@v6
- name: dependency review
uses: actions/dependency-review-action@v5
- name: "Checkout Repository"
uses: actions/checkout@v4
- name: "Dependency Review"
uses: actions/dependency-review-action@v4
continue-on-error: ${{ github.event_name == 'push' }}
- name: check dependency review outcome
- name: "Check Dependency Review Outcome"
if: github.event_name == 'push' && failure()
run: |
echo "::warning::Dependency review failed. Please check the dependencies for potential issues."
+9 -36
View File
@@ -1,51 +1,24 @@
name: go
name: Go
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: set up go
uses: actions/setup-go@v6
with:
go-version: "1.25"
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.11.4
build:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ["1.25"]
steps:
- uses: actions/checkout@v6
- name: set up go
uses: actions/setup-go@v6
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
- name: build
go-version: "1.24"
- name: Build
run: make
- name: run tests with coverage
- name: Run tests with coverage
run: go test -race -coverprofile=coverage.out -covermode=atomic ./...
- name: upload coverage to codecov
uses: codecov/codecov-action@v6
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./coverage.out
fail_ci_if_error: false
- name: run integration tests
run: go test -tags=integration -race ./internal/scan/...
-27
View File
@@ -1,27 +0,0 @@
name: govulncheck
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: "0 6 * * 1" # monday 06:00 UTC
permissions:
contents: read
jobs:
govulncheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: set up go
uses: actions/setup-go@v5
with:
go-version: "1.25"
- name: install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@v1.1.4
- name: run govulncheck
run: govulncheck ./...
continue-on-error: true
+2 -5
View File
@@ -8,14 +8,11 @@ on:
paths:
- '**.go'
permissions:
contents: read
jobs:
check-headers:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: check license headers
run: |
@@ -44,7 +41,7 @@ jobs:
echo ': █▀ █ █▀▀ · Blazing-fast pentesting suite :'
echo ': ▄█ █ █▀ · BSD 3-Clause License :'
echo ': :'
echo ': (c) 2022-2026 vmfunc, xyzeva, :'
echo ': (c) 2022-2025 vmfunc (vmfunc), xyzeva, :'
echo ': lunchcat alumni & contributors :'
echo ': :'
echo '·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·'
+2 -8
View File
@@ -1,4 +1,4 @@
name: mind your language
name: Mind your language
on:
issues:
types:
@@ -12,19 +12,13 @@ on:
types:
- created
- edited
permissions:
contents: read
issues: write
pull-requests: write
jobs:
echo_issue_comment:
runs-on: ubuntu-latest
name: profanity check
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Profanity check step
uses: tailaiw/mind-your-language-action@v1.0.3
env:
+3 -7
View File
@@ -1,22 +1,18 @@
name: markdown lint
name: Markdown Lint
on:
pull_request:
paths:
- "**/*.md"
permissions:
contents: read
pull-requests: write
jobs:
markdownlint:
name: runner / markdownlint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: markdownlint
uses: reviewdog/action-markdownlint@v0.26.2
uses: reviewdog/action-markdownlint@v0.24.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
+3 -11
View File
@@ -1,26 +1,18 @@
name: misspell check
name: Misspell Check
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
misspell:
name: runner / misspell
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: misspell
uses: reviewdog/action-misspell@v1.27.0
uses: reviewdog/action-misspell@v1.26.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
-139
View File
@@ -1,139 +0,0 @@
name: pr bot
on:
pull_request:
types: [opened, synchronize, reopened, edited]
permissions:
contents: read
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v6
with:
configuration-path: .github/labeler.yml
size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: label pr size
uses: actions/github-script@v9
with:
script: |
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
per_page: 100,
});
const changes = files.reduce((sum, f) => sum + f.additions + f.deletions, 0);
let size;
if (changes < 10) size = "size/xs";
else if (changes < 50) size = "size/s";
else if (changes < 200) size = "size/m";
else if (changes < 500) size = "size/l";
else size = "size/xl";
const sizeLabels = ["size/xs", "size/s", "size/m", "size/l", "size/xl"];
const currentLabels = context.payload.pull_request.labels.map(l => l.name);
const toRemove = currentLabels.filter(l => sizeLabels.includes(l) && l !== size);
for (const label of toRemove) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
name: label,
}).catch(() => {});
}
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: [size],
});
ci-summary:
runs-on: ubuntu-latest
needs: [label, size]
if: always()
steps:
- uses: actions/github-script@v9
with:
script: |
const pr = context.payload.pull_request;
const { data: checks } = await github.rest.checks.listForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: pr.head.sha,
per_page: 100,
});
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100,
});
const additions = files.reduce((sum, f) => sum + f.additions, 0);
const deletions = files.reduce((sum, f) => sum + f.deletions, 0);
const fileCount = files.length;
let body = `### pr summary\n\n`;
body += `**${fileCount}** files changed (+${additions} -${deletions})\n\n`;
const goFiles = files.filter(f => f.filename.endsWith('.go')).length;
const testFiles = files.filter(f => f.filename.endsWith('_test.go')).length;
const ciFiles = files.filter(f => f.filename.startsWith('.github/')).length;
const modFiles = files.filter(f => f.filename === 'go.mod' || f.filename === 'go.sum').length;
if (goFiles > 0 || testFiles > 0 || ciFiles > 0 || modFiles > 0) {
body += `| category | files |\n|----------|-------|\n`;
if (goFiles > 0) body += `| go source | ${goFiles} |\n`;
if (testFiles > 0) body += `| tests | ${testFiles} |\n`;
if (ciFiles > 0) body += `| ci/workflows | ${ciFiles} |\n`;
if (modFiles > 0) body += `| deps | ${modFiles} |\n`;
body += `\n`;
}
// find existing bot comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
});
const marker = '<!-- sif-pr-bot -->';
body = marker + '\n' + body;
const existing = comments.find(c =>
c.user.type === 'Bot' && c.body.includes(marker)
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body,
});
}
+50 -105
View File
@@ -1,9 +1,8 @@
name: release
name: Release
on:
push:
tags:
- "v*"
branches: [main]
permissions:
contents: write
@@ -19,35 +18,29 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- name: set up go
uses: actions/setup-go@v5
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.25"
go-version: "1.24"
- name: extract version
- name: Build for Windows
run: |
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
# single source of truth so the cross-compile steps can't drift
echo "LDFLAGS=-s -w -X main.version=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
GOOS=windows GOARCH=amd64 go build -o sif-windows-amd64.exe ./cmd/sif
GOOS=windows GOARCH=386 go build -o sif-windows-386.exe ./cmd/sif
- name: build for windows
- name: Build for macOS
run: |
GOOS=windows GOARCH=amd64 go build -ldflags="${{ env.LDFLAGS }}" -o sif-windows-amd64.exe ./cmd/sif
GOOS=windows GOARCH=386 go build -ldflags="${{ env.LDFLAGS }}" -o sif-windows-386.exe ./cmd/sif
GOOS=darwin GOARCH=amd64 go build -o sif-macos-amd64 ./cmd/sif
GOOS=darwin GOARCH=arm64 go build -o sif-macos-arm64 ./cmd/sif
- name: build for macOS
- name: Build for Linux
run: |
GOOS=darwin GOARCH=amd64 go build -ldflags="${{ env.LDFLAGS }}" -o sif-macos-amd64 ./cmd/sif
GOOS=darwin GOARCH=arm64 go build -ldflags="${{ env.LDFLAGS }}" -o sif-macos-arm64 ./cmd/sif
GOOS=linux GOARCH=amd64 go build -o sif-linux-amd64 ./cmd/sif
GOOS=linux GOARCH=386 go build -o sif-linux-386 ./cmd/sif
GOOS=linux GOARCH=arm64 go build -o sif-linux-arm64 ./cmd/sif
- name: build for linux
run: |
GOOS=linux GOARCH=amd64 go build -ldflags="${{ env.LDFLAGS }}" -o sif-linux-amd64 ./cmd/sif
GOOS=linux GOARCH=386 go build -ldflags="${{ env.LDFLAGS }}" -o sif-linux-386 ./cmd/sif
GOOS=linux GOARCH=arm64 go build -ldflags="${{ env.LDFLAGS }}" -o sif-linux-arm64 ./cmd/sif
- name: package releases with modules
- name: Package releases with modules
run: |
for binary in sif-linux-amd64 sif-linux-386 sif-linux-arm64 sif-macos-amd64 sif-macos-arm64; do
mkdir -p "dist/${binary}"
@@ -62,8 +55,10 @@ jobs:
cd dist && zip -r "../${binary}.zip" "${binary}" && cd ..
done
- name: build debian packages
- name: Build Debian packages
run: |
VERSION="0.1.0-$(git rev-parse --short HEAD)"
declare -A arch_map=(
["sif-linux-amd64"]="amd64"
["sif-linux-386"]="i386"
@@ -72,7 +67,7 @@ jobs:
for binary in sif-linux-amd64 sif-linux-386 sif-linux-arm64; do
arch="${arch_map[$binary]}"
pkg_dir="sif_${{ env.VERSION }}_${arch}"
pkg_dir="sif_${VERSION}_${arch}"
mkdir -p "${pkg_dir}/DEBIAN"
mkdir -p "${pkg_dir}/usr/bin"
@@ -84,11 +79,11 @@ jobs:
cat > "${pkg_dir}/DEBIAN/control" << EOF
Package: sif
Version: ${{ env.VERSION }}
Version: ${VERSION}
Section: security
Priority: optional
Architecture: ${arch}
Maintainer: vmfunc <celeste@linux.com>
Maintainer: vmfunc <celeste@router.sex>
Homepage: https://github.com/vmfunc/sif
Description: Modular pentesting toolkit
sif is a fast, concurrent, and extensible pentesting toolkit written in Go.
@@ -99,90 +94,43 @@ jobs:
dpkg-deb --build "${pkg_dir}"
done
- name: generate checksums
run: |
sha256sum \
sif-windows-amd64.zip \
sif-windows-386.zip \
sif-macos-amd64.tar.gz \
sif-macos-arm64.tar.gz \
sif-linux-amd64.tar.gz \
sif-linux-386.tar.gz \
sif-linux-arm64.tar.gz \
sif_*.deb \
> checksums-sha256.txt
- name: Set release version
run: echo "RELEASE_VERSION=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: generate SBOM
uses: anchore/sbom-action@v0
- name: Create Release and Upload Assets
uses: softprops/action-gh-release@v2
with:
artifact-name: sbom.spdx.json
output-file: sbom.spdx.json
- name: generate changelog
id: changelog
uses: actions/github-script@v9
with:
result-encoding: string
script: |
const { data: releases } = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 1,
});
const prev = releases.length > 0 ? releases[0].tag_name : '';
const range = prev ? `${prev}...${context.ref}` : '';
const { data: commits } = await github.rest.repos.compareCommitsWithBasehead({
owner: context.repo.owner,
repo: context.repo.repo,
basehead: prev ? `${prev}...${{ github.ref_name }}` : `${{ github.sha }}~10...${{ github.sha }}`,
}).catch(() => ({ data: { commits: [] } }));
let log = '';
for (const c of commits.commits || []) {
const msg = c.commit.message.split('\n')[0];
const sha = c.sha.substring(0, 7);
log += `- ${msg} (${sha})\n`;
}
return log || 'initial release';
- name: create release
uses: softprops/action-gh-release@v3
with:
name: sif v${{ env.VERSION }}
tag_name: automated-release-${{ env.RELEASE_VERSION }}
name: Release ${{ env.RELEASE_VERSION }}
body: |
## what's changed
Automated release v${{ env.RELEASE_VERSION }}
${{ steps.changelog.outputs.result }}
## Assets
## install
Each archive contains the sif binary and built-in modules.
**homebrew / linuxbrew**
```bash
# coming soon
```
- Windows (64-bit): `sif-windows-amd64.zip`
- Windows (32-bit): `sif-windows-386.zip`
- macOS (64-bit Intel): `sif-macos-amd64.tar.gz`
- macOS (64-bit ARM): `sif-macos-arm64.tar.gz`
- Linux (64-bit): `sif-linux-amd64.tar.gz`
- Linux (32-bit): `sif-linux-386.tar.gz`
- Linux (64-bit ARM): `sif-linux-arm64.tar.gz`
- Debian/Ubuntu (64-bit): `sif_*_amd64.deb`
- Debian/Ubuntu (32-bit): `sif_*_i386.deb`
- Debian/Ubuntu (64-bit ARM): `sif_*_arm64.deb`
**debian / ubuntu**
```bash
sudo dpkg -i sif_${{ env.VERSION }}_amd64.deb
```
**go install**
```bash
go install github.com/dropalldatabases/sif/cmd/sif@v${{ env.VERSION }}
```
**binary download** - grab the right archive from below.
## verification
## Installation
```bash
sha256sum -c checksums-sha256.txt
tar -xzf sif-linux-amd64.tar.gz
cd sif-linux-amd64
./sif -h
```
For more details, check the [commit history](https://github.com/${{ github.repository }}/commits/main).
draft: false
prerelease: ${{ contains(github.ref_name, '-') }}
prerelease: false
files: |
sif-windows-amd64.zip
sif-windows-386.zip
@@ -194,13 +142,10 @@ jobs:
sif_*_amd64.deb
sif_*_i386.deb
sif_*_arm64.deb
checksums-sha256.txt
sbom.spdx.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: push to cloudsmith
if: ${{ !contains(github.ref_name, '-') }}
- name: Push to Cloudsmith
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
run: |
+3 -10
View File
@@ -1,4 +1,4 @@
name: update report card
name: Update Report Card
on:
push:
@@ -7,17 +7,10 @@ on:
branches: [main]
workflow_call:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
update-report-card:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: update go report card
- uses: actions/checkout@v4
- name: Update Go Report Card
uses: creekorful/goreportcard-action@v1.0
+8 -11
View File
@@ -1,4 +1,4 @@
name: functional test
name: Functional Test
on:
push:
@@ -7,21 +7,18 @@ on:
branches: [main]
workflow_call:
permissions:
contents: read
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: set up go
uses: actions/setup-go@v5
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.25"
- name: build sif
go-version: "1.24"
- name: Build Sif
run: make
- name: run sif with features
- name: Run Sif with features
run: |
./sif -u https://example.com -dnslist small -dirlist small -dork -git -whois -cms -framework
if [ $? -eq 0 ]; then
@@ -31,7 +28,7 @@ jobs:
exit 1
fi
- name: test module system
- name: Test module system
run: |
echo "Listing modules..."
./sif -lm
-30
View File
@@ -1,30 +0,0 @@
name: scorecard
on:
push:
branches: [main]
schedule:
- cron: "0 6 * * 1" # monday 06:00 UTC
permissions: read-all
jobs:
analysis:
runs-on: ubuntu-latest
permissions:
security-events: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: run scorecard
uses: ossf/scorecard-action@v2.4.3
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- name: upload sarif results
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: results.sarif
+3 -7
View File
@@ -1,22 +1,18 @@
name: shell check
name: Shell Check
on:
pull_request:
paths:
- "**/*.sh"
permissions:
contents: read
pull-requests: write
jobs:
shellcheck:
name: runner / shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: shellcheck
uses: reviewdog/action-shellcheck@v1.32.0
uses: reviewdog/action-shellcheck@v1.27.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
+3 -7
View File
@@ -1,4 +1,4 @@
name: yaml lint
name: YAML Lint
on:
pull_request:
@@ -6,18 +6,14 @@ on:
- "**/*.yml"
- "**/*.yaml"
permissions:
contents: read
pull-requests: write
jobs:
yamllint:
name: runner / yamllint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: yamllint
uses: reviewdog/action-yamllint@v1.21.0
uses: reviewdog/action-yamllint@v1.19.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
+22 -77
View File
@@ -1,94 +1,39 @@
---
version: "2"
run:
timeout: 5m
issues-exit-code: 1
linters:
enable:
- errcheck # check error returns
- govet # suspicious constructs
- staticcheck # advanced static analysis (absorbs gosimple in v2)
- staticcheck # advanced static analysis
- unused # unused code
- gosimple # simplifications
- ineffassign # useless assignments
- misspell # spelling mistakes
- gocritic # opinionated lints
- revive # replacement for golint
- unconvert # unnecessary type conversions
- prealloc # slice preallocation hints
- bodyclose # http response body not closed
- noctx # http requests without context
- gosec # security issues
- errorlint # error wrapping and comparison
- nilnil # return nil, nil
- wastedassign # assignments to variables never read
- usetesting # os.Setenv in tests instead of t.Setenv, etc.
- exportloopref # loop variable capture
settings:
govet:
enable-all: true
disable:
# too many structs to reorder, risks breaking serialization
- fieldalignment
- shadow # common Go pattern, too noisy
- unusedwrite # false positives on test data structs
errcheck:
check-blank: false
exclude-functions:
# log writes are best-effort
- github.com/dropalldatabases/sif/internal/logger.Write
# Close on io.Closer is idiomatic best-effort
- (io.Closer).Close
- (*os.File).Close
- (*net/http.Response).Body.Close
# fmt.Fprint* returns are rarely actionable
- fmt.Fprint
- fmt.Fprintf
- fmt.Fprintln
staticcheck:
# QF1003/QF1012 are v2 quickfix suggestions, not bugs.
# ST1000/ST1003 were the stylecheck linter in v1
# (not previously enabled); skipping to match prior parity.
checks:
- all
- -QF1003
- -QF1012
- -ST1000
- -ST1003
revive:
rules:
# stuttering names (scan.ScanResult) need breaking API changes
- name: exported
disabled: true
gocritic:
enabled-tags:
- diagnostic
- style
- performance
disabled-checks:
- commentedOutCode # too opinionated for a project with TODOs
- paramTypeCombine # style-only, not worth churn
- unnamedResult # style-only
- unnecessaryDefer # common pattern in tests
# inverting conditions in scan logic hurts readability
- nestingReduce
gosec:
excludes:
- G104 # errcheck covers this
- G107 # pentesting tool -- variable URLs are the whole point
- G110 # nuclei template decompression, acceptable context
- G304 # sif reads user-supplied wordlist paths -- intentional
- G305 # tar extraction is traversal-guarded (HasPrefix on the
# cleaned target); gosec flags filepath.Join regardless
exclusions:
linters-settings:
govet:
enable-all: true
errcheck:
check-blank: false
revive:
rules:
# test files get some slack
- path: _test\.go
linters:
- errcheck
- noctx
- name: exported
arguments: [checkPrivateReceivers]
gocritic:
enabled-tags:
- diagnostic
- style
- performance
run:
timeout: 5m
issues-exit-code: 1
issues:
max-issues-per-linter: 50
max-same-issues: 50
max-same-issues: 3
+1 -1
View File
@@ -1,6 +1,6 @@
BSD 3-Clause License
Copyright (c) 2022-2025 vmfunc, xyzeva, lunchcat alumni,
Copyright (c) 2022-2025 vmfunc (vmfunc), xyzeva, lunchcat alumni,
and other sif contributors.
Redistribution and use in source and binary forms, with or without
+1 -11
View File
@@ -9,12 +9,6 @@ RM ?= rm
GOFLAGS ?=
PREFIX ?= /usr/local
BINDIR ?= bin
MANDIR ?= share/man/man1
# stamp local builds with the nearest v* tag (or short sha), matching the
# release ci. --match keeps the automated-release-* tags out of the version.
VERSION ?= $(shell git describe --tags --match 'v*' --always --dirty 2>/dev/null | sed 's/^v//')
GO_LDFLAGS = -X main.version=$(VERSION)
define COPYRIGHT_ASCII
@@ -62,7 +56,7 @@ sif: check_go_version
@echo "📁 Current directory: $$(pwd)"
@echo "🔧 Go flags: $(GOFLAGS)"
@echo "📦 Building package: ./cmd/sif"
$(GO) build -v $(GOFLAGS) -ldflags "$(GO_LDFLAGS)" ./cmd/sif
$(GO) build -v $(GOFLAGS) ./cmd/sif
@echo "📊 Build info:"
@$(GO) version -m sif
@echo "✅ sif built successfully! 🚀"
@@ -82,9 +76,6 @@ install: check_go_version
fi
@mkdir -p $(DESTDIR)$(PREFIX)/$(BINDIR) || (echo "🔒 Permission denied. Trying with sudo..." && sudo mkdir -p $(DESTDIR)$(PREFIX)/$(BINDIR))
@cp -f sif $(DESTDIR)$(PREFIX)/$(BINDIR) || (echo "🔒 Permission denied. Trying with sudo..." && sudo cp -f sif $(DESTDIR)$(PREFIX)/$(BINDIR))
@echo "📖 Installing man page..."
@mkdir -p $(DESTDIR)$(PREFIX)/$(MANDIR) || (echo "🔒 Permission denied. Trying with sudo..." && sudo mkdir -p $(DESTDIR)$(PREFIX)/$(MANDIR))
@cp -f man/sif.1 $(DESTDIR)$(PREFIX)/$(MANDIR) || (echo "🔒 Permission denied. Trying with sudo..." && sudo cp -f man/sif.1 $(DESTDIR)$(PREFIX)/$(MANDIR))
@echo "✅ sif installed successfully! 🎊"
uninstall:
@@ -95,7 +86,6 @@ uninstall:
exit 1; \
fi
@$(RM) $(DESTDIR)$(PREFIX)/$(BINDIR)/sif || (echo "🔒 Permission denied. Trying with sudo..." && sudo $(RM) $(DESTDIR)$(PREFIX)/$(BINDIR)/sif)
@$(RM) $(DESTDIR)$(PREFIX)/$(MANDIR)/sif.1 || (echo "🔒 Permission denied. Trying with sudo..." && sudo $(RM) $(DESTDIR)$(PREFIX)/$(MANDIR)/sif.1)
@echo "✅ sif uninstalled successfully!"
.PHONY: all check_go_version sif clean install uninstall
+4 -44
View File
@@ -8,10 +8,9 @@
[![build](https://img.shields.io/github/actions/workflow/status/vmfunc/sif/go.yml?style=flat-square)](https://github.com/vmfunc/sif/actions)
[![license](https://img.shields.io/badge/license-BSD--3--Clause-blue?style=flat-square)](LICENSE)
[![aur](https://img.shields.io/aur/version/sif?style=flat-square&logo=archlinux&logoColor=white&color=1793D1)](https://aur.archlinux.org/packages/sif)
[![nixpkgs](https://img.shields.io/badge/nixpkgs-sif-5277C3?style=flat-square&logo=nixos&logoColor=white)](https://search.nixos.org/packages?query=sif)
[![homebrew](https://img.shields.io/badge/homebrew-tap-FBB040?style=flat-square&logo=homebrew&logoColor=white)](https://github.com/vmfunc/homebrew-sif)
[![apt](https://img.shields.io/badge/apt-cloudsmith-2A5ADF?style=flat-square&logo=debian&logoColor=white)](https://cloudsmith.io/~sif/repos/deb/packages/)
[![discord](https://img.shields.io/badge/discord-join-5865F2?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/Yksy9J2BvE)
[![discord](https://img.shields.io/badge/discord-join-5865F2?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/sifcli)
**[install](#install) · [usage](#usage) · [modules](#modules) · [docs](docs/) · [contribute](#contribute)**
@@ -24,7 +23,7 @@
sif is a modular pentesting toolkit written in go. it's designed to be fast, concurrent, and extensible. run multiple scan types against targets with a single command.
```bash
./sif -u https://example.com -headers -sh -cms -framework -git
./sif -u https://example.com -all
```
## install
@@ -46,25 +45,6 @@ yay -S sif
paru -S sif
```
### nix
```bash
# nixpkgs (declarative — add to configuration.nix or home-manager)
environment.systemPackages = [ pkgs.sif ];
# or imperatively
nix profile install nixpkgs#sif
# or just run it without installing
nix run nixpkgs#sif -- -u https://example.com -headers -sh -framework
```
the repo also ships a flake if you want to build from source:
```bash
nix run github:vmfunc/sif
```
### debian/ubuntu (apt)
```bash
@@ -115,36 +95,18 @@ makepkg -si
# shodan host intelligence (requires SHODAN_API_KEY env var)
./sif -u https://example.com -shodan
# securitytrails domain discovery (requires SECURITYTRAILS_API_KEY env var)
# discovers subdomains + associated domains, then scans all of them
./sif -u https://example.com -securitytrails -headers
# sql recon + lfi scanning
./sif -u https://example.com -sql -lfi
# framework detection (with cve lookup)
./sif -u https://example.com -framework
# a broad sweep
./sif -u https://example.com -dirlist small -dnslist small -ports common -headers -sh -cms -framework -git -whois
# everything
./sif -u https://example.com -all
```
run `./sif -h` for all options.
## commands
a couple of subcommands run without scanning:
```bash
# print the version (release builds are stamped; local builds use git describe)
./sif version
# show the latest release notes (also -pn)
./sif patchnote
```
the first time you run a new release, sif prints that release's notes once. set `SIF_NO_PATCHNOTES=1` to turn that off.
## modules
sif has a modular architecture. modules are defined in yaml and can be extended by users.
@@ -161,13 +123,11 @@ sif has a modular architecture. modules are defined in yaml and can be extended
| `-js` | javascript analysis |
| `-c3` | cloud storage misconfiguration |
| `-headers` | http header analysis |
| `-sh` | security header analysis (missing/weak headers) |
| `-st` | subdomain takeover detection |
| `-cms` | cms detection |
| `-whois` | whois lookups |
| `-git` | exposed git repository detection |
| `-shodan` | shodan lookup (requires SHODAN_API_KEY) |
| `-securitytrails` | domain discovery + target expansion (requires SECURITYTRAILS_API_KEY) |
| `-sql` | sql recon |
| `-lfi` | local file inclusion |
| `-framework` | framework detection with cve lookup |
-15
View File
@@ -1,15 +0,0 @@
# security policy
## reporting a vulnerability
if you find a security issue in sif, email celeste@linux.com directly.
don't open a public issue.
expect a response within 48 hours. if it's confirmed, i'll push a fix
and credit you in the release notes (unless you'd rather stay anonymous).
## scope
sif is a pentesting tool — "it can scan things" is not a vulnerability.
actual bugs: command injection in user input handling, path traversal in
template extraction, credential leaks, that kind of thing.
+1 -28
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -13,38 +13,15 @@
package main
import (
"fmt"
"os"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif"
"github.com/dropalldatabases/sif/internal/config"
"github.com/dropalldatabases/sif/internal/patchnotes"
ver "github.com/dropalldatabases/sif/internal/version"
// Register framework detectors
_ "github.com/dropalldatabases/sif/internal/scan/frameworks/detectors"
)
// version is stamped at release time via -ldflags "-X main.version=...";
// ver.Resolve falls back to the build info or "dev" for other builds.
var version = "dev"
func main() {
version = ver.Resolve(version)
sif.Version = version
if len(os.Args) > 1 {
switch os.Args[1] {
case "patchnote", "patchnotes", "-pn", "--patchnotes":
patchnotes.Print("")
return
case "version", "-version", "--version":
fmt.Printf("sif %s\n", version)
return
}
}
settings := config.Parse()
app, err := sif.New(settings)
@@ -52,10 +29,6 @@ func main() {
log.Fatal(err)
}
if !settings.ApiMode {
patchnotes.ShowOnce(version)
}
err = app.Run()
if err != nil {
log.Fatal(err)
+3 -11
View File
@@ -4,7 +4,7 @@ setting up a development environment for sif.
## prerequisites
- go 1.25 or later
- go 1.23 or later
- git
- make
@@ -28,7 +28,8 @@ sif/
│ ├── logger/ # logging utilities
│ ├── modules/ # module system
│ ├── scan/ # built-in scans
── styles/ # terminal styling
── styles/ # terminal styling
│ └── worker/ # worker pool
├── modules/ # built-in yaml modules
│ ├── http/ # http-based modules
│ ├── info/ # information gathering
@@ -137,15 +138,6 @@ the module system is in `internal/modules/`:
go test ./internal/...
```
### integration tests
run the scanners against a local testbed that plants the artifacts each one
should find (network-free, behind a build tag):
```bash
go test -tags=integration ./internal/scan/...
```
### functional test
```bash
+1 -1
View File
@@ -36,7 +36,7 @@ download `sif-windows-amd64.exe` from releases and add to your PATH.
## from source
requires go 1.25+
requires go 1.23+
```bash
git clone https://github.com/dropalldatabases/sif.git
+4 -15
View File
@@ -98,27 +98,16 @@ analyzes javascript files for security issues.
## http headers (-headers)
dumps the target's response headers.
## security headers (-sh)
flags missing or weak security headers and headers that leak server internals.
analyzes security headers.
### checks
- strict-transport-security (https only)
- content-security-policy
- x-frame-options
- x-content-type-options (expects nosniff)
- referrer-policy
- x-content-type-options
- strict-transport-security
- x-xss-protection
- permissions-policy
- cross-origin-opener-policy
### flagged as disclosure
- server
- x-powered-by
- x-aspnet-version / x-aspnetmvc-version
## cms detection (-cms)
+1 -31
View File
@@ -95,20 +95,12 @@ scopes: `common` (top ports), `full` (all ports)
### http headers
`-headers` - dump the target's response headers
`-headers` - analyze security headers
```bash
./sif -u https://example.com -headers
```
### security headers
`-sh` - flag missing/weak security headers (hsts, csp, x-frame-options, ...) and headers that leak server internals
```bash
./sif -u https://example.com -sh
```
### cloud storage
`-c3` - check for cloud storage misconfigurations
@@ -259,28 +251,6 @@ enable api mode for json output:
output is a json object with scan results.
## commands
these run without scanning a target.
### version
print the sif version. release builds are stamped via ldflags, local `make` builds derive it from `git describe`, and `go install`ed builds read it from the module build info:
```bash
./sif version
```
### patchnote
show the latest release's notes, fetched from github (also `-pn`):
```bash
./sif patchnote
```
the first time you run a new release sif also prints that release's notes once. set `SIF_NO_PATCHNOTES=1` to disable that.
## examples
### quick recon
+138 -311
View File
@@ -1,396 +1,223 @@
module github.com/dropalldatabases/sif
go 1.25.7
go 1.24.0
require (
github.com/antchfx/htmlquery v1.3.6
github.com/charmbracelet/glamour v0.10.0
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
github.com/charmbracelet/log v1.0.0
github.com/likexian/whois v1.15.7
github.com/projectdiscovery/goflags v0.1.74
github.com/projectdiscovery/nuclei/v3 v3.8.0
github.com/projectdiscovery/utils v0.10.1
github.com/antchfx/htmlquery v1.3.0
github.com/charmbracelet/lipgloss v0.8.0
github.com/charmbracelet/log v0.2.4
github.com/likexian/whois v1.15.1
github.com/projectdiscovery/goflags v0.1.54
github.com/projectdiscovery/nuclei/v2 v2.9.14
github.com/projectdiscovery/ratelimit v0.0.9
github.com/projectdiscovery/utils v0.1.1
github.com/rocketlaunchr/google-search v1.1.6
gopkg.in/yaml.v3 v3.0.1
)
require (
aead.dev/minisign v0.3.0 // indirect
carvel.dev/ytt v0.52.0 // indirect
code.gitea.io/sdk/gitea v0.17.0 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.1 // indirect
aead.dev/minisign v0.2.0 // indirect
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect
github.com/Azure/go-ntlmssp v0.1.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 // indirect
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect
github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d // indirect
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697 // indirect
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/PuerkitoBio/goquery v1.11.0 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/PuerkitoBio/goquery v1.8.1 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/akrylysov/pogreb v0.10.2 // indirect
github.com/akrylysov/pogreb v0.10.1 // indirect
github.com/alecthomas/chroma v0.10.0 // indirect
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
github.com/alecthomas/jsonschema v0.0.0-20211022214203-8b29eab41725 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/alexsnet/go-vnc v0.1.0 // indirect
github.com/alitto/pond v1.9.2 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/andygrunwald/go-jira v1.16.1 // indirect
github.com/antchfx/xmlquery v1.4.4 // indirect
github.com/antchfx/xpath v1.3.6 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/andygrunwald/go-jira v1.16.0 // indirect
github.com/antchfx/xmlquery v1.3.15 // indirect
github.com/antchfx/xpath v1.2.4 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.17 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.99.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect
github.com/aws/smithy-go v1.24.2 // indirect
github.com/aws/aws-sdk-go-v2 v1.19.0 // indirect
github.com/aws/aws-sdk-go-v2/config v1.18.28 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.27 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/bits-and-blooms/bloom/v3 v3.5.0 // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/brianvoe/gofakeit/v7 v7.2.1 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/caddyserver/certmagic v0.25.0 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/censys/censys-sdk-go v0.19.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.3.2 // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250908092851-c2208eb08494 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cheggaaa/pb/v3 v3.1.7 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/caddyserver/certmagic v0.19.2 // indirect
github.com/charmbracelet/glamour v0.6.0 // indirect
github.com/cheggaaa/pb/v3 v3.1.4 // indirect
github.com/cloudflare/cfssl v1.6.4 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
github.com/corpix/uarand v0.2.0 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c // indirect
github.com/djherbis/times v1.6.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/docker/docker v28.3.3+incompatible // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/dlclark/regexp2 v1.8.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gaissmai/bart v0.26.1 // indirect
github.com/geoffgarside/ber v1.1.0 // indirect
github.com/getkin/kin-openapi v0.132.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.9.0 // indirect
github.com/go-git/go-git/v5 v5.19.1 // indirect
github.com/go-ldap/ldap/v3 v3.4.11 // indirect
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-pdf/fpdf v0.9.0 // indirect
github.com/go-pg/pg/v10 v10.15.0 // indirect
github.com/go-pg/zerochecker v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/go-rod/rod v0.116.2 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/go-playground/validator/v10 v10.14.1 // indirect
github.com/go-rod/rod v0.114.0 // indirect
github.com/goburrow/cache v0.1.4 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/gobwas/ws v1.2.1 // indirect
github.com/gocolly/colly/v2 v2.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/certificate-transparency-go v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/certificate-transparency-go v1.1.4 // indirect
github.com/google/go-github v17.0.0+incompatible // indirect
github.com/google/go-github/v30 v30.1.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gosimple/slug v1.15.0 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/h2non/filetype v1.1.3 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect
github.com/hdm/jarm-go v0.0.7 // indirect
github.com/iangcarroll/cookiemonster v1.6.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/invopop/yaml v0.3.1 // indirect
github.com/itchyny/gojq v0.12.17 // indirect
github.com/itchyny/timefmt-go v0.1.6 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
github.com/itchyny/gojq v0.12.13 // indirect
github.com/itchyny/timefmt-go v0.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect
github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 // indirect
github.com/kataras/jwt v0.1.10 // indirect
github.com/kataras/jwt v0.1.8 // indirect
github.com/kennygrant/sanitize v1.2.4 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kitabisa/go-ci v1.0.3 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labstack/echo/v4 v4.13.4 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa // indirect
github.com/lib/pq v1.11.2 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/libdns/libdns v0.2.1 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/logrusorgru/aurora/v4 v4.0.0 // indirect
github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
github.com/mackerelio/go-osstat v0.2.6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mackerelio/go-osstat v0.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.28 // indirect
github.com/maypok86/otter/v2 v2.2.1 // indirect
github.com/mholt/acmez/v3 v3.1.3 // indirect
github.com/mholt/archives v0.1.5 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/microsoft/go-mssqldb v1.9.2 // indirect
github.com/miekg/dns v1.1.68 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mholt/acmez v1.2.0 // indirect
github.com/mholt/archiver v3.1.1+incompatible // indirect
github.com/mholt/archiver/v3 v3.5.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.25 // indirect
github.com/miekg/dns v1.1.56 // indirect
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
github.com/olekukonko/tablewriter v1.0.8 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.23 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/praetorian-inc/fingerprintx v1.1.15 // indirect
github.com/projectdiscovery/asnmap v1.1.1 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/projectdiscovery/asnmap v1.1.0 // indirect
github.com/projectdiscovery/blackrock v0.0.1 // indirect
github.com/projectdiscovery/cdncheck v1.2.31 // indirect
github.com/projectdiscovery/clistats v0.1.1 // indirect
github.com/projectdiscovery/dsl v0.8.14 // indirect
github.com/projectdiscovery/fastdialer v0.5.6 // indirect
github.com/projectdiscovery/cdncheck v1.0.9 // indirect
github.com/projectdiscovery/clistats v0.0.19 // indirect
github.com/projectdiscovery/dsl v0.0.20 // indirect
github.com/projectdiscovery/fastdialer v0.1.1 // indirect
github.com/projectdiscovery/fasttemplate v0.0.2 // indirect
github.com/projectdiscovery/freeport v0.0.7 // indirect
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c // indirect
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb // indirect
github.com/projectdiscovery/gologger v1.1.68 // indirect
github.com/projectdiscovery/gostruct v0.0.2 // indirect
github.com/projectdiscovery/gozero v0.1.1-0.20251027191944-a4ea43320b81 // indirect
github.com/projectdiscovery/hmap v0.0.100 // indirect
github.com/projectdiscovery/httpx v1.9.0 // indirect
github.com/projectdiscovery/interactsh v1.3.1 // indirect
github.com/projectdiscovery/freeport v0.0.5 // indirect
github.com/projectdiscovery/gologger v1.1.12 // indirect
github.com/projectdiscovery/gostruct v0.0.1 // indirect
github.com/projectdiscovery/hmap v0.0.45 // indirect
github.com/projectdiscovery/httpx v1.3.4 // indirect
github.com/projectdiscovery/interactsh v1.2.0 // indirect
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb // indirect
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect
github.com/projectdiscovery/mapcidr v1.1.97 // indirect
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 // indirect
github.com/projectdiscovery/networkpolicy v0.1.36 // indirect
github.com/projectdiscovery/ratelimit v0.0.85 // indirect
github.com/projectdiscovery/rawhttp v0.1.90 // indirect
github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 // indirect
github.com/projectdiscovery/mapcidr v1.1.34 // indirect
github.com/projectdiscovery/networkpolicy v0.0.8 // indirect
github.com/projectdiscovery/rawhttp v0.1.18 // indirect
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917 // indirect
github.com/projectdiscovery/retryabledns v1.0.114 // indirect
github.com/projectdiscovery/retryablehttp-go v1.3.8 // indirect
github.com/projectdiscovery/retryabledns v1.0.62 // indirect
github.com/projectdiscovery/retryablehttp-go v1.0.63 // indirect
github.com/projectdiscovery/sarif v0.0.1 // indirect
github.com/projectdiscovery/tlsx v1.2.2 // indirect
github.com/projectdiscovery/uncover v1.2.0 // indirect
github.com/projectdiscovery/useragent v0.0.107 // indirect
github.com/projectdiscovery/wappalyzergo v0.2.76 // indirect
github.com/projectdiscovery/yamldoc-go v1.0.6 // indirect
github.com/redis/go-redis/v9 v9.11.0 // indirect
github.com/refraction-networking/utls v1.8.2 // indirect
github.com/projectdiscovery/tlsx v1.1.4 // indirect
github.com/projectdiscovery/yamldoc-go v1.0.4 // indirect
github.com/refraction-networking/utls v1.8.1 // indirect
github.com/remeh/sizedwaitgroup v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/sashabaranov/go-openai v1.37.0 // indirect
github.com/sashabaranov/go-openai v1.14.2 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect
github.com/sijms/go-ora/v2 v2.9.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/shirou/gopsutil/v3 v3.23.7 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.9.2 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/temoto/robotstxt v1.1.2 // indirect
github.com/tidwall/btree v1.8.1 // indirect
github.com/tidwall/buntdb v1.3.2 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/btree v1.6.0 // indirect
github.com/tidwall/buntdb v1.3.0 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/grect v0.1.4 // indirect
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect
github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/trivago/tgo v1.0.7 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vmihailenco/bufpool v0.1.11 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/vulncheck-oss/go-exploit v1.51.0 // indirect
github.com/weppos/publicsuffix-go v0.50.3 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yassinebenaid/godump v0.11.1 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db // indirect
github.com/xanzy/go-gitlab v0.84.0 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/yl2chen/cidranger v1.0.2 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/goob v0.4.0 // indirect
github.com/ysmood/got v0.40.0 // indirect
github.com/ysmood/got v0.34.1 // indirect
github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.9.0 // indirect
github.com/yuin/goldmark v1.7.13 // indirect
github.com/yuin/goldmark-emoji v1.0.6 // indirect
github.com/ysmood/leakless v0.8.0 // indirect
github.com/yuin/goldmark v1.5.4 // indirect
github.com/yuin/goldmark-emoji v1.0.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zcalusic/sysinfo v1.1.3 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
github.com/zcalusic/sysinfo v1.0.2 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect
github.com/zmap/zcrypto v0.0.0-20240803002437-3a861682ac77 // indirect
github.com/zmap/zgrab2 v0.1.8 // indirect
gitlab.com/gitlab-org/api/client-go v0.130.1 // indirect
go.etcd.io/bbolt v1.4.3 // indirect
go.mongodb.org/mongo-driver v1.17.9 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/otel v1.41.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect
go.opentelemetry.io/otel/trace v1.41.0 // indirect
github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
go.uber.org/zap v1.25.0 // indirect
goftp.io/server/v2 v2.0.1 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.30.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.44.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/term v0.38.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.39.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.36.6 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
mellium.im/sasl v0.3.2 // indirect
moul.io/http2curl v1.0.0 // indirect
)
+309 -1191
View File
File diff suppressed because it is too large Load Diff
+1 -5
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -39,11 +39,9 @@ type Settings struct {
Template string
CMS bool
Headers bool
SecurityHeaders bool
CloudStorage bool
SubdomainTakeover bool
Shodan bool
SecurityTrails bool
SQL bool
LFI bool
Framework bool
@@ -91,11 +89,9 @@ func Parse() *Settings {
flagSet.BoolVar(&settings.JavaScript, "js", false, "Enable JavaScript scans"),
flagSet.BoolVar(&settings.CMS, "cms", false, "Enable CMS detection"),
flagSet.BoolVar(&settings.Headers, "headers", false, "Enable HTTP Header Analysis"),
flagSet.BoolVarP(&settings.SecurityHeaders, "security-headers", "sh", false, "Enable security header analysis (missing/weak headers)"),
flagSet.BoolVar(&settings.CloudStorage, "c3", false, "Enable C3 Misconfiguration Scan"),
flagSet.BoolVar(&settings.SubdomainTakeover, "st", false, "Enable Subdomain Takeover Check"),
flagSet.BoolVar(&settings.Shodan, "shodan", false, "Enable Shodan lookup (requires SHODAN_API_KEY env var)"),
flagSet.BoolVar(&settings.SecurityTrails, "securitytrails", false, "Enable SecurityTrails domain discovery (requires SECURITYTRAILS_API_KEY env var)"),
flagSet.BoolVar(&settings.SQL, "sql", false, "Enable SQL reconnaissance (admin panels, error disclosure)"),
flagSet.BoolVar(&settings.LFI, "lfi", false, "Enable LFI (Local File Inclusion) reconnaissance"),
flagSet.BoolVar(&settings.Framework, "framework", false, "Enable framework detection"),
+1 -1
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
+4 -7
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -37,7 +37,7 @@ var defaultLogger = &Logger{
// Init creates the log directory if it doesn't exist.
func Init(dir string) error {
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.Mkdir(dir, 0o750); err != nil {
if err = os.Mkdir(dir, 0755); err != nil {
return err
}
}
@@ -62,7 +62,7 @@ func (l *Logger) getWriter(path string) (*bufio.Writer, error) {
return w, nil
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return nil, err
}
@@ -124,10 +124,7 @@ func (l *Logger) Close() error {
// CreateFile initializes a log file for the given URL and writes the header.
func CreateFile(logFiles *[]string, url string, dir string) error {
sanitizedURL := url
if _, after, ok := strings.Cut(url, "://"); ok {
sanitizedURL = after
}
sanitizedURL := strings.Split(url, "://")[1]
path := filepath.Join(dir, sanitizedURL+".log")
header := fmt.Sprintf(" _____________\n__________(_)__ __/\n__ ___/_ /__ /_ \n_(__ )_ / _ __/ \n/____/ /_/ /_/ \n\nsif log file for %s\nhttps://sif.sh\n\n", url)
+1 -1
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
+7 -6
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -228,9 +228,9 @@ func checkMatchers(matchers []Matcher, resp *http.Response, body string) bool {
}
// Default to AND condition across matchers
for i := range matchers {
matched := checkMatcher(&matchers[i], resp, body)
if matchers[i].Negative {
for _, m := range matchers {
matched := checkMatcher(m, resp, body)
if m.Negative {
matched = !matched
}
if !matched {
@@ -242,7 +242,7 @@ func checkMatchers(matchers []Matcher, resp *http.Response, body string) bool {
}
// checkMatcher evaluates a single matcher.
func checkMatcher(m *Matcher, resp *http.Response, body string) bool {
func checkMatcher(m Matcher, resp *http.Response, body string) bool {
part := getPart(m.Part, resp, body)
switch m.Type {
@@ -352,7 +352,8 @@ func runExtractors(extractors []Extractor, resp *http.Response, body string) map
for _, e := range extractors {
part := getPart(e.Part, resp, body)
if e.Type == "regex" {
switch e.Type {
case "regex":
for _, pattern := range e.Regex {
re, err := regexp.Compile(pattern)
if err != nil {
+1 -1
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
+1 -1
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
+1 -1
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
+2 -2
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -16,7 +16,7 @@
: SIF - Blazing-fast pentesting suite :
: Blaze - BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
-------------------------------------------------------------------------------------------------
+11 -9
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -13,23 +13,25 @@
package format
import (
"fmt"
"github.com/dropalldatabases/sif/internal/styles"
nucleiout "github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/output"
)
func FormatLine(event *nucleiout.ResultEvent) string {
line := event.TemplateID
func FormatLine(event *output.ResultEvent) string {
output := event.TemplateID
if event.MatcherName != "" {
line += ":" + styles.Highlight.Render(event.MatcherName)
output += ":" + styles.Highlight.Render(event.MatcherName)
} else if event.ExtractorName != "" {
line += ":" + styles.Highlight.Render(event.ExtractorName)
output += ":" + styles.Highlight.Render(event.ExtractorName)
}
line += " [" + event.Type + "]"
line += " [" + formatSeverity(event.Info.SeverityHolder.Severity.String()) + "]"
output += " [" + event.Type + "]"
output += " [" + formatSeverity(fmt.Sprintf("%s", event.Info.SeverityHolder.Severity)) + "]"
return line
return output
}
func formatSeverity(severity string) string {
+7 -33
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -15,14 +15,11 @@ package templates
import (
"archive/tar"
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/charmbracelet/log"
)
@@ -40,12 +37,7 @@ func Install(logger *log.Logger) error {
logger.Infof("nuclei-templates directory not found. Installing...")
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, fmt.Sprintf(archive, ref), http.NoBody)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
resp, err := http.Get(fmt.Sprintf(archive, ref))
if err != nil {
return err
}
@@ -55,55 +47,37 @@ func Install(logger *log.Logger) error {
if err != nil {
return err
}
defer func() {
if cerr := tarball.Close(); cerr != nil {
logger.Warnf("closing gzip reader: %v", cerr)
}
}()
defer tarball.Close()
data := tar.NewReader(tarball)
dest, err := os.Getwd()
if err != nil {
return err
}
cleanDest := filepath.Clean(dest)
for {
header, err := data.Next()
if errors.Is(err, io.EOF) {
if errors.Is(io.EOF, err) {
break
}
if err != nil {
return err
}
// guard against path traversal ("Zip Slip"): the resolved path must
// stay within the extraction directory before any filesystem op.
target := filepath.Join(cleanDest, header.Name)
if !strings.HasPrefix(target, cleanDest+string(os.PathSeparator)) {
return fmt.Errorf("invalid archive entry %q: escapes extraction directory", header.Name)
}
switch header.Typeflag {
case tar.TypeDir:
if err := os.Mkdir(target, 0o750); err != nil {
if err := os.Mkdir(header.Name, 0755); err != nil {
return err
}
case tar.TypeReg:
file, err := os.Create(target)
file, err := os.Create(header.Name)
if err != nil {
return err
}
if _, err := io.Copy(file, data); err != nil {
file.Close()
return err
}
file.Close()
}
}
if err := os.Rename(fmt.Sprintf("nuclei-templates-%s", ref), "nuclei-templates"); err != nil {
if err = os.Rename(fmt.Sprintf("nuclei-templates-%s", ref), "nuclei-templates"); err != nil {
return err
}
+1 -1
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
+4 -8
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -105,9 +105,6 @@ func (p *Progress) render() {
if !IsTTY {
current := atomic.LoadInt64(&p.current)
total := p.total
if total <= 0 {
return
}
percent := int(current * 100 / total)
// Print at 0%, 25%, 50%, 75%, 100%
@@ -141,12 +138,11 @@ func (p *Progress) render() {
bar := ""
for i := 0; i < progressWidth; i++ {
switch {
case i < filled:
if i < filled {
bar += progressFilled
case i == filled && current < total:
} else if i == filled && current < total {
bar += progressCurrent
default:
} else {
bar += progressEmpty
}
}
-34
View File
@@ -1,34 +0,0 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package output
import "testing"
// the non-tty milestone path divides current*100/total, so a zero-total bar
// used to panic with integer divide-by-zero when piped or redirected.
func TestProgressZeroTotalNoPanic(t *testing.T) {
p := NewProgress(0, "scanning")
p.Increment("item")
p.Set(0, "item")
p.Done()
}
func TestProgressCounts(t *testing.T) {
p := NewProgress(4, "scanning")
for i := 0; i < 4; i++ {
p.Increment("x")
}
if p.current != 4 {
t.Errorf("current = %d, want 4", p.current)
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
-145
View File
@@ -1,145 +0,0 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
// Package patchnotes shows release notes pulled from the github releases.
package patchnotes
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/charmbracelet/glamour"
)
const releasesAPI = "https://api.github.com/repos/vmfunc/sif/releases"
type release struct {
TagName string `json:"tag_name"`
Name string `json:"name"`
Body string `json:"body"`
URL string `json:"html_url"`
}
func fetch(ctx context.Context, path string) (*release, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, releasesAPI+path, http.NoBody)
if err != nil {
return nil, err
}
req.Header.Set("Accept", "application/vnd.github+json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("github returned %s", resp.Status)
}
body, err := io.ReadAll(io.LimitReader(resp.Body, 5*1024*1024))
if err != nil {
return nil, err
}
var r release
if err := json.Unmarshal(body, &r); err != nil {
return nil, err
}
return &r, nil
}
// render turns a release's markdown body into styled terminal output, falling
// back to the raw body if glamour can't render it.
func render(r *release) string {
out, err := glamour.Render(r.Body, "dark")
if err != nil {
return r.Body
}
return fmt.Sprintf("%s\n%s", r.TagName, out)
}
// Print fetches the latest release and writes its notes to stdout. tag may be
// empty for the latest release, or a "vX" tag for a specific one.
func Print(tag string) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
path := "/latest"
if tag != "" {
path = "/tags/" + tag
}
r, err := fetch(ctx, path)
if err != nil {
fmt.Printf("couldn't fetch patch notes: %v\n", err)
return
}
fmt.Print(render(r))
}
// ShowOnce prints the running version's notes the first time that version runs,
// then records it so it isn't shown again. best-effort: dev builds, the
// SIF_NO_PATCHNOTES opt-out, and any network failure stay silent.
func ShowOnce(version string) {
// only clean release tags (e.g. 2026.6.7) map to a github release; skip dev
// and pseudo-versions (a commit/dirty build) so we don't make a doomed call.
if version == "" || version == "dev" || strings.ContainsAny(version, "-+") || os.Getenv("SIF_NO_PATCHNOTES") != "" {
return
}
path, err := statePath()
if err != nil || hasSeen(path, version) {
return
}
// record before fetching so a flaky network doesn't nag on every run
recordSeen(path, version)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
r, err := fetch(ctx, "/tags/v"+version)
if err != nil {
return
}
fmt.Printf("\nwhat's new in this release:\n%s", render(r))
}
func statePath() (string, error) {
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "sif", "seen_version"), nil
}
func hasSeen(path, version string) bool {
data, err := os.ReadFile(path)
if err != nil {
return false
}
return strings.TrimSpace(string(data)) == version
}
func recordSeen(path, version string) {
if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
return
}
_ = os.WriteFile(path, []byte(version), 0o600)
}
-42
View File
@@ -1,42 +0,0 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package patchnotes
import (
"path/filepath"
"strings"
"testing"
)
func TestSeenRoundTrip(t *testing.T) {
path := filepath.Join(t.TempDir(), "sif", "seen_version")
if hasSeen(path, "2026.6.7") {
t.Fatal("nothing recorded yet, hasSeen should be false")
}
recordSeen(path, "2026.6.7")
if !hasSeen(path, "2026.6.7") {
t.Error("recorded version should read back as seen")
}
if hasSeen(path, "2026.6.8") {
t.Error("a different version should not be seen")
}
}
func TestRenderIncludesTag(t *testing.T) {
out := render(&release{TagName: "v2026.6.7", Body: "## what's changed\n- a thing"})
if !strings.Contains(out, "v2026.6.7") {
t.Errorf("rendered notes should include the tag, got %q", out)
}
}
+2 -3
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -15,10 +15,9 @@ package builtin
import (
"context"
"fmt"
"strings"
"github.com/dropalldatabases/sif/internal/modules"
"github.com/dropalldatabases/sif/internal/scan/frameworks"
"strings"
)
type FrameworksModule struct{}
+2 -4
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -15,7 +15,6 @@ package builtin
import (
"context"
"fmt"
"github.com/dropalldatabases/sif/internal/modules"
"github.com/dropalldatabases/sif/internal/scan"
)
@@ -52,8 +51,7 @@ func (m *NucleiModule) Execute(ctx context.Context, target string, opts modules.
}
// Process nuclei results into module findings
for i := range nucleiResults {
event := &nucleiResults[i]
for _, event := range nucleiResults {
severity := "info"
switch event.Info.SeverityHolder.Severity.String() {
+1 -3
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -17,9 +17,7 @@ import "github.com/dropalldatabases/sif/internal/modules"
// Register registers all Go-based built-in scans as modules.
// Allows complex Go scans to participate in the module system
func Register() {
modules.Register(&ShodanModule{})
modules.Register(&FrameworksModule{})
modules.Register(&NucleiModule{})
modules.Register(&WhoisModule{})
modules.Register(&SecurityTrailsModule{})
}
@@ -1,79 +0,0 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package builtin
import (
"context"
"fmt"
"strings"
"github.com/dropalldatabases/sif/internal/modules"
"github.com/dropalldatabases/sif/internal/scan"
)
type SecurityTrailsModule struct{}
func (m *SecurityTrailsModule) Info() modules.Info {
return modules.Info{
ID: "securitytrails-lookup",
Name: "SecurityTrails Domain Discovery",
Author: "sif",
Severity: "info",
Description: "Queries SecurityTrails API for subdomains and associated domains (requires SECURITYTRAILS_API_KEY)",
Tags: []string{"recon", "osint", "dns", "subdomains"},
}
}
func (m *SecurityTrailsModule) Type() modules.ModuleType {
return modules.TypeScript
}
func (m *SecurityTrailsModule) Execute(ctx context.Context, target string, opts modules.Options) (*modules.Result, error) {
stResult, err := scan.SecurityTrails(target, opts.Timeout, opts.LogDir)
if err != nil {
return nil, err
}
result := &modules.Result{
ModuleID: m.Info().ID,
Target: target,
Findings: []modules.Finding{},
}
if stResult == nil {
return result, nil
}
finding := modules.Finding{
URL: target,
Severity: "info",
Evidence: fmt.Sprintf("discovered %d subdomains and %d associated domains",
len(stResult.Subdomains), len(stResult.AssociatedDomains)),
Extracted: map[string]string{
"domain": stResult.Domain,
"subdomain_count": fmt.Sprintf("%d", len(stResult.Subdomains)),
"associated_count": fmt.Sprintf("%d", len(stResult.AssociatedDomains)),
},
}
if len(stResult.Subdomains) > 0 {
finding.Extracted["subdomains"] = strings.Join(stResult.Subdomains, ", ")
}
if len(stResult.AssociatedDomains) > 0 {
finding.Extracted["associated_domains"] = strings.Join(stResult.AssociatedDomains, ", ")
}
result.Findings = append(result.Findings, finding)
return result, nil
}
-158
View File
@@ -1,158 +0,0 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package builtin
import (
"context"
"fmt"
"strings"
"github.com/dropalldatabases/sif/internal/modules"
"github.com/dropalldatabases/sif/internal/scan"
)
type ShodanModule struct{}
func (m *ShodanModule) Info() modules.Info {
return modules.Info{
ID: "shodan-lookup",
Name: "Shodan Host Intelligence",
Author: "sif",
Severity: "info",
Description: "Queries Shodan API for host information, open ports, and vulnerabilities (requires SHODAN_API_KEY)",
Tags: []string{"recon", "osint", "shodan", "vulns"},
}
}
func (m *ShodanModule) Type() modules.ModuleType {
return modules.TypeScript
}
func (m *ShodanModule) Execute(ctx context.Context, target string, opts modules.Options) (*modules.Result, error) {
// Call existing legacy scan.Shodan function
shodanResult, err := scan.Shodan(target, opts.Timeout, opts.LogDir)
if err != nil {
return nil, err
}
result := &modules.Result{
ModuleID: m.Info().ID,
Target: target,
Findings: []modules.Finding{},
}
// If nothing returned, return empty result
if shodanResult == nil || shodanResult.IP == "" {
return result, nil
}
// Create main finding
evidence := fmt.Sprintf("Shodan data found for %s", shodanResult.IP)
severity := "info"
if len(shodanResult.Vulns) > 0 {
severity = "high"
evidence = fmt.Sprintf("Host %s has %d known vulnerabilities", shodanResult.IP, len(shodanResult.Vulns))
}
finding := modules.Finding{
URL: target,
Severity: severity,
Evidence: evidence,
Extracted: map[string]string{
"ip": shodanResult.IP,
},
}
// Add hostnames
if len(shodanResult.Hostnames) > 0 {
finding.Extracted["hostnames"] = strings.Join(shodanResult.Hostnames, ", ")
}
// Add organization info
if shodanResult.Organization != "" {
finding.Extracted["organization"] = shodanResult.Organization
}
// Add ISP info
if shodanResult.ISP != "" {
finding.Extracted["isp"] = shodanResult.ISP
}
// Add ASN
if shodanResult.ASN != "" {
finding.Extracted["asn"] = shodanResult.ASN
}
// Add location
if shodanResult.Country != "" {
location := shodanResult.Country
if shodanResult.City != "" {
location = shodanResult.City + ", " + shodanResult.Country
}
finding.Extracted["location"] = location
}
// Add OS
if shodanResult.OS != "" {
finding.Extracted["os"] = shodanResult.OS
}
// Add open ports
if len(shodanResult.Ports) > 0 {
portStrs := make([]string, len(shodanResult.Ports))
for i, port := range shodanResult.Ports {
portStrs[i] = fmt.Sprintf("%d", port)
}
finding.Extracted["open_ports"] = strings.Join(portStrs, ", ")
finding.Extracted["port_count"] = fmt.Sprintf("%d", len(shodanResult.Ports))
}
// Add vulnerabilities
if len(shodanResult.Vulns) > 0 {
finding.Extracted["vulnerabilities"] = strings.Join(shodanResult.Vulns, ", ")
finding.Extracted["vuln_count"] = fmt.Sprintf("%d", len(shodanResult.Vulns))
}
// Add last update
if shodanResult.LastUpdate != "" {
finding.Extracted["last_update"] = shodanResult.LastUpdate
}
// Add service count
if len(shodanResult.Services) > 0 {
finding.Extracted["service_count"] = fmt.Sprintf("%d", len(shodanResult.Services))
// Add service details
serviceDetails := make([]string, 0, len(shodanResult.Services))
for _, service := range shodanResult.Services {
detail := fmt.Sprintf("%d/%s", service.Port, service.Protocol)
if service.Product != "" {
detail += " " + service.Product
if service.Version != "" {
detail += " " + service.Version
}
}
serviceDetails = append(serviceDetails, detail)
}
finding.Extracted["services"] = strings.Join(serviceDetails, "; ")
}
result.Findings = append(result.Findings, finding)
return result, nil
}
+1 -2
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -14,7 +14,6 @@ package builtin
import (
"context"
"github.com/dropalldatabases/sif/internal/modules"
"github.com/dropalldatabases/sif/internal/scan"
)
+15 -16
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -13,7 +13,6 @@
package scan
import (
"context"
"fmt"
"net/http"
"os"
@@ -31,9 +30,9 @@ type CloudStorageResult struct {
}
func CloudStorage(url string, timeout time.Duration, logdir string) ([]CloudStorageResult, error) {
fmt.Println(styles.Separator.Render("Starting " + styles.Status.Render("Cloud Storage Misconfiguration Scan") + "..."))
fmt.Println(styles.Separator.Render("☁️ Starting " + styles.Status.Render("Cloud Storage Misconfiguration Scan") + "..."))
sanitizedURL := stripScheme(url)
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "Cloud Storage Misconfiguration Scan"); err != nil {
@@ -43,7 +42,7 @@ func CloudStorage(url string, timeout time.Duration, logdir string) ([]CloudStor
}
cloudlog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "C3",
Prefix: "C3 ☁️",
}).With("url", url)
client := &http.Client{
@@ -55,7 +54,7 @@ func CloudStorage(url string, timeout time.Duration, logdir string) ([]CloudStor
var results []CloudStorageResult
for _, bucket := range potentialBuckets {
isPublic, err := checkS3Bucket(context.TODO(), bucket, client)
isPublic, err := checkS3Bucket(bucket, client)
if err != nil {
cloudlog.Errorf("Error checking S3 bucket %s: %v", bucket, err)
continue
@@ -70,7 +69,7 @@ func CloudStorage(url string, timeout time.Duration, logdir string) ([]CloudStor
if isPublic {
cloudlog.Warnf("Public S3 bucket found: %s", styles.Highlight.Render(bucket))
if logdir != "" {
_ = logger.Write(sanitizedURL, logdir, fmt.Sprintf("Public S3 bucket found: %s\n", bucket))
logger.Write(sanitizedURL, logdir, fmt.Sprintf("Public S3 bucket found: %s\n", bucket))
}
} else {
cloudlog.Infof("S3 bucket is not public/found: %s", bucket)
@@ -81,27 +80,27 @@ func CloudStorage(url string, timeout time.Duration, logdir string) ([]CloudStor
}
func extractPotentialBuckets(url string) []string {
// TODO: handle non-adjacent label combos and strip the tld
// This is a simple implementation.
// TODO: add more cases
parts := strings.Split(url, ".")
var buckets []string
for i, part := range parts {
buckets = append(buckets, part, part+"-s3", "s3-"+part)
buckets = append(buckets, part)
buckets = append(buckets, part+"-s3")
buckets = append(buckets, "s3-"+part)
if i < len(parts)-1 {
domainExtension := part + "-" + parts[i+1]
buckets = append(buckets, domainExtension, parts[i+1]+"-"+part)
buckets = append(buckets, domainExtension)
buckets = append(buckets, parts[i+1]+"-"+part)
}
}
return buckets
}
func checkS3Bucket(ctx context.Context, bucket string, client *http.Client) (bool, error) {
func checkS3Bucket(bucket string, client *http.Client) (bool, error) {
url := fmt.Sprintf("https://%s.s3.amazonaws.com", bucket)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
if err != nil {
return false, err
}
resp, err := client.Do(req)
resp, err := client.Get(url)
if err != nil {
return false, err
}
+5 -16
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -13,7 +13,6 @@
package scan
import (
"context"
"io"
"net/http"
"strings"
@@ -35,7 +34,7 @@ func CMS(url string, timeout time.Duration, logdir string) (*CMSResult, error) {
spin := output.NewSpinner("Detecting content management system")
spin.Start()
sanitizedURL := stripScheme(url)
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "CMS detection"); err != nil {
@@ -49,13 +48,7 @@ func CMS(url string, timeout time.Duration, logdir string) (*CMSResult, error) {
Timeout: timeout,
}
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, http.NoBody)
if err != nil {
spin.Stop()
return nil, err
}
resp, err := client.Do(req)
resp, err := client.Get(url)
if err != nil {
spin.Stop()
return nil, err
@@ -99,7 +92,7 @@ func CMS(url string, timeout time.Duration, logdir string) (*CMSResult, error) {
spin.Stop()
log.Info("No CMS detected")
log.Complete(0, "detected")
return nil, nil //nolint:nilnil // no CMS found is not an error
return nil, nil
}
func detectWordPress(url string, client *http.Client, bodyString string) bool {
@@ -125,11 +118,7 @@ func detectWordPress(url string, client *http.Client, bodyString string) bool {
}
for _, file := range wpFiles {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url+file, http.NoBody)
if err != nil {
continue
}
resp, err := client.Do(req)
resp, err := client.Get(url + file)
if err == nil {
found := resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusFound
resp.Body.Close()
+10 -23
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -14,10 +14,10 @@ package scan
import (
"bufio"
"context"
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"time"
@@ -26,13 +26,11 @@ import (
"github.com/dropalldatabases/sif/internal/output"
)
// directoryURL is a var so integration tests can repoint it at a fixture.
var directoryURL = "https://raw.githubusercontent.com/dropalldatabases/sif-runtime/main/dirlist/"
const (
smallFile = "directory-list-2.3-small.txt"
mediumFile = "directory-list-2.3-medium.txt"
bigFile = "directory-list-2.3-big.txt"
directoryURL = "https://raw.githubusercontent.com/dropalldatabases/sif-runtime/main/dirlist/"
smallFile = "directory-list-2.3-small.txt"
mediumFile = "directory-list-2.3-medium.txt"
bigFile = "directory-list-2.3-big.txt"
)
type DirectoryResult struct {
@@ -45,7 +43,7 @@ func Dirlist(size string, url string, timeout time.Duration, threads int, logdir
log := output.Module("DIRLIST")
log.Start()
sanitizedURL := stripScheme(url)
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, size+" directory fuzzing"); err != nil {
@@ -64,12 +62,7 @@ func Dirlist(size string, url string, timeout time.Duration, threads int, logdir
list = directoryURL + bigFile
}
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, list, http.NoBody)
if err != nil {
log.Error("Error creating directory list request: %s", err)
return nil, err
}
resp, err := http.DefaultClient.Do(req)
resp, err := http.Get(list)
if err != nil {
log.Error("Error downloading directory list: %s", err)
return nil, err
@@ -106,12 +99,7 @@ func Dirlist(size string, url string, timeout time.Duration, threads int, logdir
progress.Increment(directory)
charmlog.Debugf("%s", directory)
dirReq, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, url+"/"+directory, http.NoBody)
if err != nil {
charmlog.Debugf("Error creating request for %s: %s", directory, err)
continue
}
resp, err := client.Do(dirReq)
resp, err := client.Get(url + "/" + directory)
if err != nil {
charmlog.Debugf("Error %s: %s", directory, err)
continue
@@ -123,7 +111,7 @@ func Dirlist(size string, url string, timeout time.Duration, threads int, logdir
progress.Resume()
if logdir != "" {
_ = logger.Write(sanitizedURL, logdir, fmt.Sprintf("%s [%s]\n", strconv.Itoa(resp.StatusCode), directory))
logger.Write(sanitizedURL, logdir, fmt.Sprintf("%s [%s]\n", strconv.Itoa(resp.StatusCode), directory))
}
result := DirectoryResult{
@@ -134,7 +122,6 @@ func Dirlist(size string, url string, timeout time.Duration, threads int, logdir
results = append(results, result)
mu.Unlock()
}
resp.Body.Close()
}
}(thread)
}
+8 -27
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -14,9 +14,9 @@ package scan
import (
"bufio"
"context"
"fmt"
"net/http"
"strings"
"sync"
"time"
@@ -25,10 +25,8 @@ import (
"github.com/dropalldatabases/sif/internal/output"
)
// dnsURL is a var so integration tests can repoint it at a fixture.
var dnsURL = "https://raw.githubusercontent.com/dropalldatabases/sif-runtime/main/dnslist/"
const (
dnsURL = "https://raw.githubusercontent.com/dropalldatabases/sif-runtime/main/dnslist/"
dnsSmallFile = "subdomains-100.txt"
dnsMediumFile = "subdomains-1000.txt"
dnsBigFile = "subdomains-10000.txt"
@@ -49,12 +47,7 @@ func Dnslist(size string, url string, timeout time.Duration, threads int, logdir
list = dnsURL + dnsBigFile
}
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, list, http.NoBody)
if err != nil {
log.Error("Error creating request: %s", err)
return nil, err
}
resp, err := http.DefaultClient.Do(req)
resp, err := http.Get(list)
if err != nil {
log.Error("Error downloading DNS list: %s", err)
return nil, err
@@ -68,7 +61,7 @@ func Dnslist(size string, url string, timeout time.Duration, threads int, logdir
dns = append(dns, scanner.Text())
}
sanitizedURL := stripScheme(url)
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, size+" subdomain fuzzing"); err != nil {
@@ -102,19 +95,13 @@ func Dnslist(size string, url string, timeout time.Duration, threads int, logdir
charmlog.Debugf("Looking up: %s", domain)
// Check HTTP
httpReq, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, "http://"+domain+"."+sanitizedURL, http.NoBody)
if err != nil {
charmlog.Debugf("Error %s: %s", domain, err)
continue
}
resp, err := client.Do(httpReq)
resp, err := client.Get("http://" + domain + "." + sanitizedURL)
if err != nil {
charmlog.Debugf("Error %s: %s", domain, err)
} else {
mu.Lock()
urls = append(urls, resp.Request.URL.String())
mu.Unlock()
resp.Body.Close()
progress.Pause()
log.Success("found: %s.%s [http]", output.Highlight.Render(domain), sanitizedURL)
@@ -126,26 +113,20 @@ func Dnslist(size string, url string, timeout time.Duration, threads int, logdir
}
// Check HTTPS
httpsReq, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, "https://"+domain+"."+sanitizedURL, http.NoBody)
if err != nil {
charmlog.Debugf("Error %s: %s", domain, err)
continue
}
resp, err = client.Do(httpsReq)
resp, err = client.Get("https://" + domain + "." + sanitizedURL)
if err != nil {
charmlog.Debugf("Error %s: %s", domain, err)
} else {
mu.Lock()
urls = append(urls, resp.Request.URL.String())
mu.Unlock()
resp.Body.Close()
progress.Pause()
log.Success("found: %s.%s [https]", output.Highlight.Render(domain), sanitizedURL)
progress.Resume()
if logdir != "" {
_ = logger.Write(sanitizedURL, logdir, fmt.Sprintf("[https] %s.%s\n", domain, sanitizedURL))
logger.Write(sanitizedURL, logdir, fmt.Sprintf("[https] %s.%s\n", domain, sanitizedURL))
}
}
}
+6 -16
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -17,10 +17,10 @@ package scan
import (
"bufio"
"context"
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"time"
@@ -59,7 +59,7 @@ func Dork(url string, timeout time.Duration, threads int, logdir string) ([]Dork
spin := output.NewSpinner("Running Google dorks")
spin.Start()
sanitizedURL := stripScheme(url)
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "URL dorking"); err != nil {
@@ -69,14 +69,7 @@ func Dork(url string, timeout time.Duration, threads int, logdir string) ([]Dork
}
}
ctx := context.TODO()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, dorkURL+dorkFile, http.NoBody)
if err != nil {
spin.Stop()
output.Error("Error creating dork list request: %s", err)
return nil, err
}
resp, err := http.DefaultClient.Do(req)
resp, err := http.Get(dorkURL + dorkFile)
if err != nil {
spin.Stop()
output.Error("Error downloading dork list: %s", err)
@@ -92,7 +85,6 @@ func Dork(url string, timeout time.Duration, threads int, logdir string) ([]Dork
// util.InitProgressBar()
var wg sync.WaitGroup
var mu sync.Mutex
wg.Add(threads)
dorkResults := []DorkResult{}
@@ -106,7 +98,7 @@ func Dork(url string, timeout time.Duration, threads int, logdir string) ([]Dork
continue
}
results, err := googlesearch.Search(context.TODO(), fmt.Sprintf("%s %s", dork, sanitizedURL))
results, err := googlesearch.Search(nil, fmt.Sprintf("%s %s", dork, sanitizedURL))
if err != nil {
log.Debugf("error searching for dork %s: %v", dork, err)
continue
@@ -116,7 +108,7 @@ func Dork(url string, timeout time.Duration, threads int, logdir string) ([]Dork
output.Success("%s dork results found for dork %s", output.Status.Render(strconv.Itoa(len(results))), output.Highlight.Render(dork))
spin.Start()
if logdir != "" {
_ = logger.Write(sanitizedURL, logdir, strconv.Itoa(len(results))+" dork results found for dork ["+dork+"]\n")
logger.Write(sanitizedURL, logdir, strconv.Itoa(len(results))+" dork results found for dork ["+dork+"]\n")
}
result := DorkResult{
@@ -124,9 +116,7 @@ func Dork(url string, timeout time.Duration, threads int, logdir string) ([]Dork
Count: len(results),
}
mu.Lock()
dorkResults = append(dorkResults, result)
mu.Unlock()
}
}
}(thread)
+1 -1
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -1,36 +0,0 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package frameworks
import "testing"
func TestVersionAffected(t *testing.T) {
tests := []struct {
version string
affected string
want bool
}{
{"4.2", "4.2", true},
{"4.2.1", "4.2", true},
{"4.2.13", "4.2", true},
{"4.20", "4.2", false}, // the boundary bug: 4.20 is not a 4.2.x release
{"4.20.0", "4.2", false},
{"5.0", "4.2", false},
}
for _, tt := range tests {
if got := versionAffected(tt.version, tt.affected); got != tt.want {
t.Errorf("versionAffected(%q, %q) = %v, want %v", tt.version, tt.affected, got, tt.want)
}
}
}
+10 -21
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -13,11 +13,9 @@
package frameworks
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"sync"
"time"
@@ -49,12 +47,7 @@ func DetectFramework(url string, timeout time.Duration, logdir string) (*Framewo
client := &http.Client{Timeout: timeout}
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, url, http.NoBody)
if err != nil {
spin.Stop()
return nil, err
}
resp, err := client.Do(req) //nolint:bodyclose // closed via defer below
resp, err := client.Get(url)
if err != nil {
spin.Stop()
return nil, err
@@ -73,7 +66,7 @@ func DetectFramework(url string, timeout time.Duration, logdir string) (*Framewo
if len(detectors) == 0 {
spin.Stop()
log.Warn("No framework detectors registered")
return nil, nil //nolint:nilnil // no detectors registered is not an error
return nil, nil
}
// Run all detectors concurrently
@@ -100,11 +93,9 @@ func DetectFramework(url string, timeout time.Duration, logdir string) (*Framewo
}()
// Find the best match
// results arrive in goroutine-completion order; tie-break on name so the
// winner is deterministic when two detectors land on the same confidence.
var best detectionResult
for r := range results {
if r.confidence > best.confidence || (r.confidence == best.confidence && r.name < best.name) {
if r.confidence > best.confidence {
best = r
}
}
@@ -114,7 +105,7 @@ func DetectFramework(url string, timeout time.Duration, logdir string) (*Framewo
if best.confidence <= detectionThreshold {
log.Info("No framework detected with sufficient confidence")
log.Complete(0, "detected")
return nil, nil //nolint:nilnil // no framework detected is not an error
return nil, nil
}
// Get version match details
@@ -133,7 +124,7 @@ func DetectFramework(url string, timeout time.Duration, logdir string) (*Framewo
logEntry += fmt.Sprintf(" CVEs: %v\n", cves)
logEntry += fmt.Sprintf(" Recommendations: %v\n", suggestions)
}
_ = logger.Write(url, logdir, logEntry)
logger.Write(url, logdir, logEntry)
}
log.Success("Detected %s framework (version: %s, confidence: %.2f)",
@@ -172,7 +163,7 @@ func getVulnerabilities(framework, version string) ([]string, []string) {
for _, entry := range entries {
for _, affectedVer := range entry.AffectedVersions {
if versionAffected(version, affectedVer) {
if version == affectedVer || hasPrefix(version, affectedVer) {
cves = append(cves, fmt.Sprintf("%s (%s)", entry.CVE, entry.Severity))
for _, rec := range entry.Recommendations {
if !seenRecs[rec] {
@@ -188,9 +179,7 @@ func getVulnerabilities(framework, version string) ([]string, []string) {
return cves, recommendations
}
// versionAffected reports whether version falls under an affected-version
// entry. the entry is a version prefix, matched only on dotted boundaries, so
// "4.2" covers 4.2 and 4.2.1 but not 4.20.
func versionAffected(version, affected string) bool {
return version == affected || strings.HasPrefix(version, affected+".")
// hasPrefix is a simple prefix check without importing strings.
func hasPrefix(s, prefix string) bool {
return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}
+1 -1
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
+2 -2
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -13,7 +13,7 @@
/*
BSD 3-Clause License
(c) 2022-2026 vmfunc, xyzeva & contributors
(c) 2022-2025 vmfunc, xyzeva & contributors
*/
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -13,7 +13,7 @@
/*
BSD 3-Clause License
(c) 2022-2026 vmfunc, xyzeva & contributors
(c) 2022-2025 vmfunc, xyzeva & contributors
*/
@@ -47,11 +47,9 @@ func init() {
fw.Register(&codeigniterDetector{})
}
// sigmoidConfidence maps the matched-weight fraction to a 0-1 confidence,
// centered at 0.3 so a single weak signature match no longer clears the 0.5
// detection threshold (it used to: sigmoid(0) was 0.5, so any match "detected").
// sigmoidConfidence converts a weighted score to a 0-1 confidence value.
func sigmoidConfidence(score float32) float32 {
return float32(1.0 / (1.0 + math.Exp(-(float64(score)-0.3)*10.0)))
return float32(1.0 / (1.0 + math.Exp(-float64(score)*6.0)))
}
// laravelDetector detects Laravel framework.
+2 -2
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -13,7 +13,7 @@
/*
BSD 3-Clause License
(c) 2022-2026 vmfunc, xyzeva & contributors
(c) 2022-2025 vmfunc, xyzeva & contributors
*/
@@ -1,30 +0,0 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package detectors
import "testing"
func TestSigmoidConfidence(t *testing.T) {
// a weak match (small matched-weight fraction) must stay below the 0.5
// detection threshold; a strong match must clear it. the old curve put any
// match above 0.5, which is what false-detected magento on a plain page.
if c := sigmoidConfidence(0); c >= 0.5 {
t.Errorf("no match conf = %.3f, want < 0.5", c)
}
if c := sigmoidConfidence(0.2); c >= 0.5 {
t.Errorf("weak match conf = %.3f, want < 0.5", c)
}
if c := sigmoidConfidence(0.5); c <= 0.5 {
t.Errorf("strong match conf = %.3f, want > 0.5", c)
}
}
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -13,7 +13,7 @@
/*
BSD 3-Clause License
(c) 2022-2026 vmfunc, xyzeva & contributors
(c) 2022-2025 vmfunc, xyzeva & contributors
*/
+2 -2
View File
@@ -4,7 +4,7 @@
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
@@ -13,7 +13,7 @@
/*
BSD 3-Clause License
(c) 2022-2026 vmfunc, xyzeva & contributors
(c) 2022-2025 vmfunc, xyzeva & contributors
*/
-46
View File
@@ -1,46 +0,0 @@
/*
··
: :
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
··
*/
package frameworks
import "testing"
func FuzzIsValidVersionString(f *testing.F) {
f.Add("1.0")
f.Add("1.0.0")
f.Add("10.2.3.4")
f.Add("999.999.999")
f.Add("")
f.Add("abc")
f.Add("1.")
f.Add(".1")
f.Add("1.2.3.4.5")
f.Add("aaaaaaaaaaaaaaaaaaaaaaaaa")
f.Fuzz(func(t *testing.T, v string) {
// should never panic
isValidVersionString(v)
})
}
func FuzzExtractVersionOptimized(f *testing.F) {
f.Add("<meta name=\"generator\" content=\"WordPress 6.4.2\">", "WordPress")
f.Add("Laravel v10.0.1", "Laravel")
f.Add("<html>nothing</html>", "Django")
f.Add("", "unknown")
f.Add("X-Powered-By: Express/4.18.2", "Express")
f.Fuzz(func(t *testing.T, body string, framework string) {
// should never panic
ExtractVersionOptimized(body, framework)
})
}
+2 -2
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -13,7 +13,7 @@
/*
BSD 3-Clause License
(c) 2022-2026 vmfunc, xyzeva & contributors
(c) 2022-2025 vmfunc, xyzeva & contributors
*/
+2 -2
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -202,7 +202,7 @@ func ExtractVersionOptimized(body string, framework string) VersionMatch {
// isValidVersionString checks if a version string looks like a valid semver
func isValidVersionString(v string) bool {
if v == "" || len(v) > 20 {
if len(v) == 0 || len(v) > 20 {
return false
}
-63
View File
@@ -1,63 +0,0 @@
/*
··
: :
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
··
*/
package scan
import "testing"
func FuzzDetectLFIFromResponse(f *testing.F) {
f.Add("root:x:0:0:root:/root:/bin/bash")
f.Add("<html><body>Hello World</body></html>")
f.Add("[boot loader]\ntimeout=30")
f.Add("DOCUMENT_ROOT=/var/www/html")
f.Add("<?php echo 'hello'; ?>")
f.Add("127.0.0.1 localhost")
f.Add("")
f.Add("PD9waHAgZWNobyAnaGVsbG8nOyA/Pg==")
f.Fuzz(func(t *testing.T, body string) {
// should never panic
DetectLFIFromResponse(body)
})
}
func FuzzIsAdminPanel(f *testing.F) {
f.Add("<html>phpMyAdmin</html>", "phpMyAdmin")
f.Add("<html>adminer</html>", "Adminer")
f.Add("<html>pgadmin</html>", "pgAdmin")
f.Add("<html>nothing here</html>", "phpMyAdmin")
f.Add("", "unknown")
f.Add("<html>database query mysql</html>", "generic")
f.Fuzz(func(t *testing.T, body string, panelType string) {
// should never panic
isAdminPanel(body, panelType)
})
}
func FuzzDatabaseErrorPatterns(f *testing.F) {
f.Add("you have an error in your sql syntax")
f.Add("Warning: mysql_fetch_array()")
f.Add("postgresql error at character 42")
f.Add("ORA-12345: some oracle error")
f.Add("sqlite3_prepare_v2 failed")
f.Add("document bson error in mongodb")
f.Add("<html>normal page</html>")
f.Add("")
f.Fuzz(func(t *testing.T, body string) {
// should never panic on any input
for _, pattern := range databaseErrorPatterns {
pattern.pattern.MatchString(body)
}
})
}
+8 -25
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -14,7 +14,6 @@ package scan
import (
"bufio"
"context"
"net/http"
"strconv"
"strings"
@@ -26,10 +25,10 @@ import (
"github.com/dropalldatabases/sif/internal/output"
)
// gitURL is a var so integration tests can repoint it at a fixture.
var gitURL = "https://raw.githubusercontent.com/dropalldatabases/sif-runtime/main/git/"
const gitFile = "git.txt"
const (
gitURL = "https://raw.githubusercontent.com/dropalldatabases/sif-runtime/main/git/"
gitFile = "git.txt"
)
func Git(url string, timeout time.Duration, threads int, logdir string) ([]string, error) {
log := output.Module("GIT")
@@ -38,7 +37,7 @@ func Git(url string, timeout time.Duration, threads int, logdir string) ([]strin
spin := output.NewSpinner("Scanning for exposed git repositories")
spin.Start()
sanitizedURL := stripScheme(url)
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "git directory fuzzing"); err != nil {
@@ -48,13 +47,7 @@ func Git(url string, timeout time.Duration, threads int, logdir string) ([]strin
}
}
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, gitURL+gitFile, http.NoBody)
if err != nil {
spin.Stop()
log.Error("Error creating git list request: %s", err)
return nil, err
}
resp, err := http.DefaultClient.Do(req)
resp, err := http.Get(gitURL + gitFile)
if err != nil {
spin.Stop()
log.Error("Error downloading git list: %s", err)
@@ -74,7 +67,6 @@ func Git(url string, timeout time.Duration, threads int, logdir string) ([]strin
}
var wg sync.WaitGroup
var mu sync.Mutex
wg.Add(threads)
foundUrls := []string{}
@@ -88,15 +80,9 @@ func Git(url string, timeout time.Duration, threads int, logdir string) ([]strin
}
charmlog.Debugf("%s", repourl)
gitReq, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, url+"/"+repourl, http.NoBody)
if err != nil {
charmlog.Debugf("Error creating request for %s: %s", repourl, err)
continue
}
resp, err := client.Do(gitReq)
resp, err := client.Get(url + "/" + repourl)
if err != nil {
charmlog.Debugf("Error %s: %s", repourl, err)
continue
}
if resp.StatusCode == 200 && !strings.HasPrefix(resp.Header.Get("Content-Type"), "text/html") {
@@ -107,11 +93,8 @@ func Git(url string, timeout time.Duration, threads int, logdir string) ([]strin
logger.Write(sanitizedURL, logdir, strconv.Itoa(resp.StatusCode)+" git found at ["+repourl+"]\n")
}
mu.Lock()
foundUrls = append(foundUrls, resp.Request.URL.String())
mu.Unlock()
}
resp.Body.Close()
}
}(thread)
}
+4 -8
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -13,8 +13,8 @@
package scan
import (
"context"
"net/http"
"strings"
"time"
"github.com/dropalldatabases/sif/internal/logger"
@@ -30,7 +30,7 @@ func Headers(url string, timeout time.Duration, logdir string) ([]HeaderResult,
log := output.Module("HEADERS")
log.Start()
sanitizedURL := stripScheme(url)
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "HTTP Header Analysis"); err != nil {
@@ -43,11 +43,7 @@ func Headers(url string, timeout time.Duration, logdir string) ([]HeaderResult,
Timeout: timeout,
}
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, url, http.NoBody)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
resp, err := client.Get(url)
if err != nil {
return nil, err
}
-219
View File
@@ -1,219 +0,0 @@
//go:build integration
/*
··
: :
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
··
*/
// These tests run the real scanners against a local server standing in for a
// deliberately-vulnerable app, asserting the findings each one should produce.
// They're behind the `integration` build tag so the default `go test` stays
// network-free; run with `go test -tags=integration ./internal/scan/...`.
package scan
import (
"context"
"net"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
)
// newVulnApp serves the planted artifacts each scanner is meant to find, plus
// the wordlists the remote-list scanners fetch.
func newVulnApp() *httptest.Server {
mux := http.NewServeMux()
// wordlists the remote-list scanners download
mux.HandleFunc("/git.txt", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(".git/HEAD\n.git/config\n"))
})
mux.HandleFunc("/directory-list-2.3-small.txt", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("admin\nlogin\nnope\n"))
})
// an exposed git repo: HEAD is a real find, config is html so it's excluded
mux.HandleFunc("/.git/HEAD", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
w.Write([]byte("ref: refs/heads/main\n"))
})
mux.HandleFunc("/.git/config", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.Write([]byte("<html>nope</html>"))
})
// live directories for dirlist
mux.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
// an exposed db admin panel for sql recon
mux.HandleFunc("/phpmyadmin/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("<title>phpMyAdmin</title>"))
})
// homepage doubles as the cms fingerprint and the lfi sink
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
if strings.Contains(r.URL.RawQuery, "passwd") || strings.Contains(r.URL.RawQuery, "etc") {
w.Write([]byte("root:x:0:0:root:/root:/bin/bash\n"))
return
}
w.Header().Set("X-Powered-By", "PHP/8.1.0")
w.Write([]byte(`<html><head><link href="/wp-content/themes/x/style.css"></head><body>hi</body></html>`))
})
return httptest.NewServer(mux)
}
func TestIntegrationGit(t *testing.T) {
srv := newVulnApp()
defer srv.Close()
orig := gitURL
gitURL = srv.URL + "/"
defer func() { gitURL = orig }()
found, err := Git(srv.URL, 5*time.Second, 2, "")
if err != nil {
t.Fatalf("Git: %v", err)
}
if len(found) != 1 {
t.Fatalf("expected 1 git find (HEAD, not the html config), got %d: %v", len(found), found)
}
if !strings.HasSuffix(found[0], ".git/HEAD") {
t.Errorf("expected .git/HEAD, got %s", found[0])
}
}
func TestIntegrationDirlist(t *testing.T) {
srv := newVulnApp()
defer srv.Close()
orig := directoryURL
directoryURL = srv.URL + "/"
defer func() { directoryURL = orig }()
results, err := Dirlist("small", srv.URL, 5*time.Second, 3, "")
if err != nil {
t.Fatalf("Dirlist: %v", err)
}
got := map[string]bool{}
for _, r := range results {
got[r.Url] = true
}
if !hasSuffixIn(got, "/admin") || !hasSuffixIn(got, "/login") {
t.Errorf("expected admin and login to be found, got %v", results)
}
if hasSuffixIn(got, "/nope") {
t.Errorf("404 path nope should not be reported, got %v", results)
}
}
func TestIntegrationCMS(t *testing.T) {
srv := newVulnApp()
defer srv.Close()
result, err := CMS(srv.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("CMS: %v", err)
}
if result == nil || result.Name != "WordPress" {
t.Errorf("expected WordPress, got %+v", result)
}
}
func TestIntegrationHeaders(t *testing.T) {
srv := newVulnApp()
defer srv.Close()
results, err := Headers(srv.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("Headers: %v", err)
}
if len(results) == 0 {
t.Error("expected at least one header back")
}
}
func TestIntegrationSQL(t *testing.T) {
srv := newVulnApp()
defer srv.Close()
result, err := SQL(srv.URL, 5*time.Second, 5, "")
if err != nil {
t.Fatalf("SQL: %v", err)
}
if result == nil || len(result.AdminPanels) == 0 {
t.Fatalf("expected an admin panel finding, got %+v", result)
}
if result.AdminPanels[0].Type != "phpMyAdmin" {
t.Errorf("expected phpMyAdmin, got %s", result.AdminPanels[0].Type)
}
}
func TestIntegrationLFI(t *testing.T) {
srv := newVulnApp()
defer srv.Close()
result, err := LFI(srv.URL, 5*time.Second, 5, "")
if err != nil {
t.Fatalf("LFI: %v", err)
}
if result == nil || len(result.Vulnerabilities) == 0 {
t.Errorf("expected an lfi finding from the passwd sink, got %+v", result)
}
}
func TestIntegrationPorts(t *testing.T) {
// a real listener stands in for an open port; a tiny server hands its number
// to Ports via the commonPorts wordlist.
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
defer ln.Close()
port := ln.Addr().(*net.TCPAddr).Port
list := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(strconv.Itoa(port) + "\n"))
}))
defer list.Close()
orig := commonPorts
commonPorts = list.URL
defer func() { commonPorts = orig }()
open, err := Ports(context.Background(), "common", "tcp://127.0.0.1", 2*time.Second, 1, "")
if err != nil {
t.Fatalf("Ports: %v", err)
}
found := false
for _, p := range open {
if p == strconv.Itoa(port) {
found = true
}
}
if !found {
t.Errorf("expected open port %d in %v", port, open)
}
}
func hasSuffixIn(set map[string]bool, suffix string) bool {
for k := range set {
if strings.HasSuffix(k, suffix) {
return true
}
}
return false
}
+2 -9
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -24,7 +24,6 @@ package frameworks
import (
"bufio"
"context"
"fmt"
"net/http"
"regexp"
@@ -42,13 +41,7 @@ func GetPagesRouterScripts(scriptUrl string) ([]string, error) {
return nil, err
}
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, scriptUrl, http.NoBody)
if err != nil {
fmt.Println(err)
return nil, err
}
resp, err := http.DefaultClient.Do(req)
resp, err := http.Get(scriptUrl)
if err != nil {
fmt.Println(err)
return nil, err
+4 -15
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -14,7 +14,6 @@ package js
import (
"bufio"
"context"
"io"
"net/http"
"slices"
@@ -48,12 +47,7 @@ func JavascriptScan(url string, timeout time.Duration, threads int, logdir strin
spin.Stop()
return nil, err
}
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, url, http.NoBody)
if err != nil {
spin.Stop()
return nil, err
}
resp, err := http.DefaultClient.Do(req)
resp, err := http.Get(url)
if err != nil {
spin.Stop()
return nil, err
@@ -115,12 +109,7 @@ func JavascriptScan(url string, timeout time.Duration, threads int, logdir strin
supabaseResults := make([]supabaseScanResult, 0, len(scripts))
for _, script := range scripts {
charmlog.Debugf("Scanning %s", script)
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, script, http.NoBody)
if err != nil {
charmlog.Warnf("Failed to create request: %s", err)
continue
}
resp, err := http.DefaultClient.Do(req)
resp, err := http.Get(script)
if err != nil {
charmlog.Warnf("Failed to fetch script: %s", err)
continue
@@ -135,7 +124,7 @@ func JavascriptScan(url string, timeout time.Duration, threads int, logdir strin
content := string(bodyBytes)
charmlog.Debugf("Running supabase scanner on %s", script)
scriptSupabaseResults, err := ScanSupabase(content, script, timeout)
scriptSupabaseResults, err := ScanSupabase(content, script)
if err != nil {
charmlog.Errorf("Error while scanning supabase: %s", err)
+15 -16
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -16,7 +16,6 @@ package js
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
@@ -70,8 +69,8 @@ type supabaseOpenAPIResponse struct {
}
// getSupabaseArrayResponse fetches a Supabase endpoint that returns an array.
func getSupabaseArrayResponse(projectId, path, apikey string, auth *string, timeout time.Duration) (*supabaseArrayResponse, error) {
body, resp, err := doSupabaseRequest(projectId, path, apikey, auth, timeout) //nolint:bodyclose // closed in doSupabaseRequest
func getSupabaseArrayResponse(projectId, path, apikey string, auth *string) (*supabaseArrayResponse, error) {
body, resp, err := doSupabaseRequest(projectId, path, apikey, auth)
if err != nil {
return nil, err
}
@@ -95,8 +94,8 @@ func getSupabaseArrayResponse(projectId, path, apikey string, auth *string, time
}
// getSupabaseOpenAPI fetches the OpenAPI spec from Supabase.
func getSupabaseOpenAPI(projectId, apikey string, auth *string, timeout time.Duration) (*supabaseOpenAPIResponse, error) {
body, _, err := doSupabaseRequest(projectId, "/rest/v1/", apikey, auth, timeout) //nolint:bodyclose // closed in doSupabaseRequest
func getSupabaseOpenAPI(projectId, apikey string, auth *string) (*supabaseOpenAPIResponse, error) {
body, _, err := doSupabaseRequest(projectId, "/rest/v1/", apikey, auth)
if err != nil {
return nil, err
}
@@ -109,10 +108,10 @@ func getSupabaseOpenAPI(projectId, apikey string, auth *string, timeout time.Dur
}
// doSupabaseRequest performs a GET request to the Supabase API.
func doSupabaseRequest(projectId, path, apikey string, auth *string, timeout time.Duration) ([]byte, *http.Response, error) {
client := http.Client{Timeout: timeout}
func doSupabaseRequest(projectId, path, apikey string, auth *string) ([]byte, *http.Response, error) {
client := http.Client{}
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, "https://"+projectId+".supabase.co"+path, http.NoBody)
req, err := http.NewRequest("GET", "https://"+projectId+".supabase.co"+path, nil)
if err != nil {
return nil, nil, err
}
@@ -142,9 +141,9 @@ func doSupabaseRequest(projectId, path, apikey string, auth *string, timeout tim
return body, resp, nil
}
func ScanSupabase(jsContent string, jsUrl string, timeout time.Duration) ([]supabaseScanResult, error) {
func ScanSupabase(jsContent string, jsUrl string) ([]supabaseScanResult, error) {
supabaselog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "JavaScript > Supabase",
Prefix: "🚧 JavaScript > Supabase ⚡️",
}).With("url", jsUrl)
var results = []supabaseScanResult{}
@@ -171,7 +170,7 @@ func ScanSupabase(jsContent string, jsUrl string, timeout time.Duration) ([]supa
supabaselog.Debugf("JWT body: %s", decoded)
var supabaseJwt *supabaseJwtBody
err = json.Unmarshal(decoded, &supabaseJwt)
err = json.Unmarshal([]byte(decoded), &supabaseJwt)
if err != nil {
supabaselog.Debugf("Failed to json parse JWT %s: %s", jwt, err)
continue
@@ -182,9 +181,9 @@ func ScanSupabase(jsContent string, jsUrl string, timeout time.Duration) ([]supa
}
supabaselog.Infof("Found valid supabase project %s with role %s", *supabaseJwt.ProjectId, *supabaseJwt.Role)
client := http.Client{Timeout: timeout}
client := http.Client{}
req, err := http.NewRequestWithContext(context.TODO(), http.MethodPost, "https://"+*supabaseJwt.ProjectId+".supabase.co/auth/v1/signup", bytes.NewBufferString(`{"email":"automated`+strconv.Itoa(int(time.Now().Unix()))+`@sif.sh","password":"automatedacct"}`))
req, err := http.NewRequest("POST", "https://"+*supabaseJwt.ProjectId+".supabase.co/auth/v1/signup", bytes.NewBufferString(`{"email":"automated`+strconv.Itoa(int(time.Now().Unix()))+`@sif.sh","password":"automatedacct"}`))
if err != nil {
supabaselog.Errorf("Error while creating HTTP req for creating user: %s", err)
continue
@@ -219,7 +218,7 @@ func ScanSupabase(jsContent string, jsUrl string, timeout time.Duration) ([]supa
var collections = []supabaseCollection{}
openAPI, err := getSupabaseOpenAPI(*supabaseJwt.ProjectId, jwt, &auth, timeout)
openAPI, err := getSupabaseOpenAPI(*supabaseJwt.ProjectId, jwt, &auth)
if err != nil {
return nil, err
}
@@ -238,7 +237,7 @@ func ScanSupabase(jsContent string, jsUrl string, timeout time.Duration) ([]supa
continue
}
sampleResp, err := getSupabaseArrayResponse(*supabaseJwt.ProjectId, "/rest/v1"+path, jwt, &auth, timeout)
sampleResp, err := getSupabaseArrayResponse(*supabaseJwt.ProjectId, "/rest/v1"+path, jwt, &auth)
if err != nil {
continue
}
+5 -10
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -13,12 +13,12 @@
package scan
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
"time"
@@ -118,7 +118,7 @@ func LFI(targetURL string, timeout time.Duration, threads int, logdir string) (*
spin := output.NewSpinner("Scanning for LFI vulnerabilities")
spin.Start()
sanitizedURL := stripScheme(targetURL)
sanitizedURL := strings.Split(targetURL, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "LFI reconnaissance"); err != nil {
@@ -214,12 +214,7 @@ func LFI(targetURL string, timeout time.Duration, threads int, logdir string) (*
parsedURL.Path,
testParams.Encode())
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, testURL, http.NoBody)
if err != nil {
charmlog.Debugf("Error creating request for %s: %v", testURL, err)
continue
}
resp, err := client.Do(req)
resp, err := client.Get(testURL)
if err != nil {
charmlog.Debugf("Error testing %s: %v", testURL, err)
continue
@@ -299,7 +294,7 @@ func LFI(targetURL string, timeout time.Duration, threads int, logdir string) (*
} else {
log.Info("No LFI vulnerabilities detected")
log.Complete(0, "found")
return nil, nil //nolint:nilnil // no LFI found is not an error
return nil, nil
}
return result, nil
+1 -1
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
+100 -43
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -14,16 +14,33 @@ package scan
import (
"context"
"fmt"
"os"
"sync"
"strings"
"time"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/nuclei/format"
"github.com/dropalldatabases/sif/internal/nuclei/templates"
sifoutput "github.com/dropalldatabases/sif/internal/output"
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/logrusorgru/aurora"
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/disk"
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v2/pkg/core"
"github.com/projectdiscovery/nuclei/v2/pkg/core/inputs"
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/parsers"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolinit"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v2/pkg/reporting"
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"github.com/projectdiscovery/ratelimit"
)
func Nuclei(url string, timeout time.Duration, threads int, logdir string) ([]output.ResultEvent, error) {
@@ -32,59 +49,99 @@ func Nuclei(url string, timeout time.Duration, threads int, logdir string) ([]ou
spin := sifoutput.NewSpinner("Running nuclei templates")
spin.Start()
sanitizedURL := strings.Split(url, "://")[1]
nucleilog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "nuclei",
}).With("url", url)
_ = templates.Install(nucleilog)
// Apply threads, timeout, log settings
options := types.DefaultOptions()
options.TemplateThreads = threads
options.Timeout = int(timeout.Seconds())
if logdir != "" {
options.ProjectPath = logdir
}
options.Headless = false
// Get templates
templates.Install(nucleilog)
pwd, err := os.Getwd()
if err != nil {
spin.Stop()
return nil, err
return nil, fmt.Errorf("failed to get working directory: %w", err)
}
config.DefaultConfig.SetTemplatesDir(pwd)
catalog := disk.NewCatalog(pwd)
ctx := context.Background()
ne, err := nuclei.NewNucleiEngineCtx(ctx,
nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{
Templates: []string{pwd + "/nuclei-templates"},
}),
nuclei.WithConcurrency(nuclei.Concurrency{
TemplateConcurrency: threads,
HostConcurrency: 1,
HeadlessHostConcurrency: 1,
HeadlessTemplateConcurrency: 1,
JavascriptTemplateConcurrency: 1,
TemplatePayloadConcurrency: 25,
ProbeConcurrency: 50,
}),
nuclei.WithNetworkConfig(nuclei.NetworkConfig{
Timeout: int(timeout.Seconds()),
}),
nuclei.DisableUpdateCheck(),
)
if err != nil {
spin.Stop()
return nil, err
}
defer ne.Close()
sanitizedURL := stripScheme(url)
ne.LoadTargets([]string{sanitizedURL}, false)
var results []output.ResultEvent
var mu sync.Mutex
err = ne.ExecuteCallbackWithCtx(ctx, func(event *output.ResultEvent) {
results := []output.ResultEvent{}
// Custom output
outputWriter := testutils.NewMockOutputWriter()
outputWriter.WriteCallback = func(event *output.ResultEvent) {
if event.Matched != "" {
nucleilog.Infof("%s", format.FormatLine(event))
mu.Lock()
results = append(results, *event)
mu.Unlock()
// TODO: metasploit
}
})
}
cache := hosterrorscache.New(30, hosterrorscache.DefaultMaxHostsCount, nil)
defer cache.Close()
progressClient := &testutils.MockProgressClient{}
reportingClient, err := reporting.New(&reporting.Options{}, "")
if err != nil {
return nil, fmt.Errorf("failed to create reporting client: %w", err)
}
defer reportingClient.Close()
interactOpts := interactsh.DefaultOptions(outputWriter, reportingClient, progressClient)
interactClient, err := interactsh.New(interactOpts)
if err != nil {
return nil, err
}
defer interactClient.Close()
protocolstate.Init(options)
protocolinit.Init(options)
executorOpts := protocols.ExecutorOptions{
Colorizer: aurora.NewAurora(false),
Output: outputWriter,
Progress: progressClient,
Catalog: catalog,
Options: options,
IssuesClient: reportingClient,
RateLimiter: ratelimit.New(context.Background(), 150, time.Second),
Interactsh: interactClient,
HostErrorsCache: cache,
ResumeCfg: types.NewResumeCfg(),
}
engine := core.New(options)
engine.SetExecuterOptions(executorOpts)
workflowLoader, err := parsers.NewLoader(&executorOpts)
if err != nil {
return nil, err
}
executorOpts.WorkflowLoader = workflowLoader
store, err := loader.New(loader.NewConfig(options, catalog, executorOpts))
if err != nil {
return nil, err
}
store.Load()
inputArgs := []*contextargs.MetaInput{{Input: sanitizedURL}}
input := &inputs.SimpleInputProvider{Inputs: inputArgs}
_ = engine.Execute(store.Templates(), input)
engine.WorkPool().Wait()
spin.Stop()
sifoutput.ScanComplete("nuclei template scanning", len(results), "found")
return results, err
return results, nil
}
+8 -15
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -14,11 +14,11 @@ package scan
import (
"bufio"
"context"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"sync"
"time"
@@ -27,14 +27,13 @@ import (
"github.com/dropalldatabases/sif/internal/output"
)
// commonPorts is a var so integration tests can repoint it at a fixture.
var commonPorts = "https://raw.githubusercontent.com/dropalldatabases/sif-runtime/main/ports/top-ports.txt"
const commonPorts = "https://raw.githubusercontent.com/dropalldatabases/sif-runtime/main/ports/top-ports.txt"
func Ports(ctx context.Context, scope string, url string, timeout time.Duration, threads int, logdir string) ([]string, error) {
func Ports(scope string, url string, timeout time.Duration, threads int, logdir string) ([]string, error) {
log := output.Module("PORTS")
log.Start()
sanitizedURL := stripScheme(url)
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, scope+" port scanning"); err != nil {
log.Error("Error creating log file: %v", err)
@@ -45,12 +44,7 @@ func Ports(ctx context.Context, scope string, url string, timeout time.Duration,
var ports []int
switch scope {
case "common":
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, commonPorts, http.NoBody)
if err != nil {
log.Error("Error creating request: %s", err)
return nil, err
}
resp, err := http.DefaultClient.Do(req)
resp, err := http.Get(commonPorts)
if err != nil {
log.Error("Error downloading ports list: %s", err)
return nil, err
@@ -89,8 +83,7 @@ func Ports(ctx context.Context, scope string, url string, timeout time.Duration,
progress.Increment(strconv.Itoa(port))
charmlog.Debugf("Looking up: %d", port)
addr := fmt.Sprintf("%s:%d", sanitizedURL, port)
tcp, err := (&net.Dialer{Timeout: timeout}).DialContext(ctx, "tcp", addr)
tcp, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", sanitizedURL, port), timeout)
if err != nil {
charmlog.Debugf("Error %d: %v", port, err)
} else {
@@ -101,7 +94,7 @@ func Ports(ctx context.Context, scope string, url string, timeout time.Duration,
mu.Lock()
openPorts = append(openPorts, strconv.Itoa(port))
mu.Unlock()
_ = tcp.Close()
tcp.Close()
}
}
}(thread)
+6 -10
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -16,7 +16,6 @@ package scan
// These provide better type safety and allow method implementations.
type (
HeaderResults []HeaderResult
SecurityHeaderResults []SecurityHeaderResult
DirectoryResults []DirectoryResult
CloudStorageResults []CloudStorageResult
DorkResults []DorkResult
@@ -24,6 +23,7 @@ type (
)
// ScanResult is the interface that all scan result types implement.
// This enables type-safe handling of heterogeneous scan results.
type ScanResult interface {
// ResultType returns the unique identifier for this result type.
ResultType() string
@@ -31,16 +31,14 @@ type ScanResult interface {
// ResultType implementations for pointer result types.
func (r *ShodanResult) ResultType() string { return "shodan" }
func (r *SQLResult) ResultType() string { return "sql" }
func (r *LFIResult) ResultType() string { return "lfi" }
func (r *CMSResult) ResultType() string { return "cms" }
func (r *SecurityTrailsResult) ResultType() string { return "securitytrails" }
func (r *ShodanResult) ResultType() string { return "shodan" }
func (r *SQLResult) ResultType() string { return "sql" }
func (r *LFIResult) ResultType() string { return "lfi" }
func (r *CMSResult) ResultType() string { return "cms" }
// ResultType implementations for slice result types.
func (r HeaderResults) ResultType() string { return "headers" }
func (r SecurityHeaderResults) ResultType() string { return "security_headers" }
func (r DirectoryResults) ResultType() string { return "dirlist" }
func (r CloudStorageResults) ResultType() string { return "cloudstorage" }
func (r DorkResults) ResultType() string { return "dork" }
@@ -52,9 +50,7 @@ var (
_ ScanResult = (*SQLResult)(nil)
_ ScanResult = (*LFIResult)(nil)
_ ScanResult = (*CMSResult)(nil)
_ ScanResult = (*SecurityTrailsResult)(nil)
_ ScanResult = HeaderResults(nil)
_ ScanResult = SecurityHeaderResults(nil)
_ ScanResult = DirectoryResults(nil)
_ ScanResult = CloudStorageResults(nil)
_ ScanResult = DorkResults(nil)
+4 -24
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -19,7 +19,6 @@ package scan
import (
"bufio"
"context"
"net/http"
"strconv"
"strings"
@@ -31,22 +30,8 @@ import (
"github.com/dropalldatabases/sif/internal/output"
)
// stripScheme drops the scheme:// prefix from url, or returns it unchanged when
// there's no scheme (so a bare host doesn't panic).
func stripScheme(url string) string {
if _, rest, ok := strings.Cut(url, "://"); ok {
return rest
}
return url
}
func fetchRobotsTXT(url string, client *http.Client) *http.Response {
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, url, http.NoBody)
if err != nil {
log.Debugf("Error creating request for robots.txt: %s", err)
return nil
}
resp, err := client.Do(req)
resp, err := client.Get(url)
if err != nil {
log.Debugf("Error fetching robots.txt: %s", err)
return nil
@@ -76,7 +61,7 @@ func fetchRobotsTXT(url string, client *http.Client) *http.Response {
func Scan(url string, timeout time.Duration, threads int, logdir string) {
output.ScanStart("base URL scanning")
sanitizedURL := stripScheme(url)
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "URL scanning"); err != nil {
@@ -125,12 +110,7 @@ func Scan(url string, timeout time.Duration, threads int, logdir string) {
_, sanitizedRobot, _ := strings.Cut(robot, ": ")
log.Debugf("%s", robot)
robotReq, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, url+"/"+sanitizedRobot, http.NoBody)
if err != nil {
log.Debugf("Error creating request for %s: %s", sanitizedRobot, err)
continue
}
resp, err := client.Do(robotReq)
resp, err := client.Get(url + "/" + sanitizedRobot)
if err != nil {
log.Debugf("Error %s: %s", sanitizedRobot, err)
continue
-21
View File
@@ -200,24 +200,3 @@ func TestHeaderResult(t *testing.T) {
t.Errorf("expected value 'application/json', got '%s'", result.Value)
}
}
func TestStripScheme(t *testing.T) {
tests := []struct {
name string
url string
want string
}{
{"https with path", "https://example.com/path", "example.com/path"},
{"http", "http://example.com", "example.com"},
{"no scheme stays put", "example.com", "example.com"},
{"empty", "", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := stripScheme(tt.url); got != tt.want {
t.Errorf("stripScheme(%q) = %q, want %q", tt.url, got, tt.want)
}
})
}
}
-162
View File
@@ -1,162 +0,0 @@
/*
··
: :
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
··
*/
package scan
import (
"context"
"net/http"
"strconv"
"strings"
"time"
"github.com/dropalldatabases/sif/internal/logger"
"github.com/dropalldatabases/sif/internal/output"
)
type SecurityHeaderResult struct {
Header string `json:"header"`
Present bool `json:"present"`
Value string `json:"value,omitempty"`
Severity string `json:"severity"`
Note string `json:"note"`
}
type recommendedHeader struct {
name string
severity string
}
var recommendedHeaders = []recommendedHeader{
{"Strict-Transport-Security", "high"},
{"Content-Security-Policy", "medium"},
{"X-Frame-Options", "medium"},
{"X-Content-Type-Options", "low"},
{"Referrer-Policy", "low"},
{"Permissions-Policy", "low"},
{"Cross-Origin-Opener-Policy", "low"},
}
// headers that leak server/framework details when present.
var disclosureHeaders = []string{"Server", "X-Powered-By", "X-AspNet-Version", "X-AspNetMvc-Version"}
const hstsMinMaxAge = 31536000 // a year, in seconds
func SecurityHeaders(url string, timeout time.Duration, logdir string) (SecurityHeaderResults, error) {
log := output.Module("SECHEADERS")
log.Start()
sanitizedURL := stripScheme(url)
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "Security Header Analysis"); err != nil {
log.Error("Error creating log file: %v", err)
return nil, err
}
}
client := &http.Client{
Timeout: timeout,
}
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, url, http.NoBody)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
results := gradeSecurityHeaders(resp.Header, strings.HasPrefix(url, "https://"))
for _, r := range results {
line := r.Header + " " + r.Note
log.Warn("%s [%s]", line, r.Severity)
if logdir != "" {
_ = logger.Write(sanitizedURL, logdir, line+" ["+r.Severity+"]\n")
}
}
if len(results) == 0 {
log.Success("all recommended security headers present")
}
log.Complete(len(results), "issues")
return results, nil
}
func gradeSecurityHeaders(header http.Header, https bool) SecurityHeaderResults {
var results SecurityHeaderResults
for _, h := range recommendedHeaders {
// hsts does nothing over plain http, so don't flag its absence there
if h.name == "Strict-Transport-Security" && !https {
continue
}
value := header.Get(h.name)
switch {
case value == "":
results = append(results, SecurityHeaderResult{
Header: h.name,
Severity: h.severity,
Note: "missing",
})
case h.name == "Strict-Transport-Security" && hstsMaxAge(value) < hstsMinMaxAge:
results = append(results, SecurityHeaderResult{
Header: h.name,
Present: true,
Value: value,
Severity: h.severity,
Note: "max-age too short",
})
case h.name == "X-Content-Type-Options" && !strings.EqualFold(value, "nosniff"):
results = append(results, SecurityHeaderResult{
Header: h.name,
Present: true,
Value: value,
Severity: "low",
Note: "should be nosniff",
})
}
}
for _, name := range disclosureHeaders {
if value := header.Get(name); value != "" {
results = append(results, SecurityHeaderResult{
Header: name,
Present: true,
Value: value,
Severity: "low",
Note: "discloses " + value,
})
}
}
return results
}
// hstsMaxAge returns the max-age seconds from an hsts value, or 0 if absent.
func hstsMaxAge(value string) int {
for _, part := range strings.Split(value, ";") {
if age, ok := strings.CutPrefix(strings.ToLower(strings.TrimSpace(part)), "max-age="); ok {
n, err := strconv.Atoi(strings.TrimSpace(age))
if err != nil {
return 0
}
return n
}
}
return 0
}
-154
View File
@@ -1,154 +0,0 @@
/*
··
: :
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
··
*/
package scan
import (
"net/http"
"net/http/httptest"
"testing"
"time"
)
func buildHeader(kv map[string]string) http.Header {
h := http.Header{}
for k, v := range kv {
h.Set(k, v)
}
return h
}
func findFinding(results SecurityHeaderResults, name string) (SecurityHeaderResult, bool) {
for _, r := range results {
if r.Header == name {
return r, true
}
}
return SecurityHeaderResult{}, false
}
func TestGradeSecurityHeaders_MissingOverHTTPS(t *testing.T) {
results := gradeSecurityHeaders(http.Header{}, true)
for _, h := range recommendedHeaders {
f, ok := findFinding(results, h.name)
if !ok {
t.Errorf("expected %s to be flagged", h.name)
continue
}
if f.Present {
t.Errorf("%s should not be marked present", h.name)
}
if f.Severity != h.severity {
t.Errorf("%s severity = %q, want %q", h.name, f.Severity, h.severity)
}
}
}
func TestGradeSecurityHeaders_HSTSSkippedOverHTTP(t *testing.T) {
results := gradeSecurityHeaders(http.Header{}, false)
if _, ok := findFinding(results, "Strict-Transport-Security"); ok {
t.Error("HSTS should only be graded for https targets")
}
}
func TestGradeSecurityHeaders_AllPresent(t *testing.T) {
h := buildHeader(map[string]string{
"Strict-Transport-Security": "max-age=63072000; includeSubDomains",
"Content-Security-Policy": "default-src 'self'",
"X-Frame-Options": "DENY",
"X-Content-Type-Options": "nosniff",
"Referrer-Policy": "no-referrer",
"Permissions-Policy": "geolocation=()",
"Cross-Origin-Opener-Policy": "same-origin",
})
if results := gradeSecurityHeaders(h, true); len(results) != 0 {
t.Errorf("expected no findings, got %d: %+v", len(results), results)
}
}
func TestGradeSecurityHeaders_ContentTypeNotNosniff(t *testing.T) {
h := buildHeader(map[string]string{
"Strict-Transport-Security": "max-age=63072000",
"Content-Security-Policy": "default-src 'self'",
"X-Frame-Options": "DENY",
"X-Content-Type-Options": "sniff",
"Referrer-Policy": "no-referrer",
"Permissions-Policy": "geolocation=()",
"Cross-Origin-Opener-Policy": "same-origin",
})
f, ok := findFinding(gradeSecurityHeaders(h, true), "X-Content-Type-Options")
if !ok {
t.Fatal("expected X-Content-Type-Options to be flagged when not nosniff")
}
if !f.Present || f.Value != "sniff" {
t.Errorf("finding = %+v, want present with value sniff", f)
}
}
func TestGradeSecurityHeaders_WeakHSTS(t *testing.T) {
// max-age=0 actively disables hsts, so a present header still has to be flagged
h := buildHeader(map[string]string{"Strict-Transport-Security": "max-age=0"})
f, ok := findFinding(gradeSecurityHeaders(h, true), "Strict-Transport-Security")
if !ok {
t.Fatal("expected a short-lived hsts header to be flagged")
}
if !f.Present || f.Severity != "high" {
t.Errorf("finding = %+v, want present high", f)
}
}
func TestGradeSecurityHeaders_Disclosure(t *testing.T) {
h := buildHeader(map[string]string{
"Server": "Apache/2.4.1 (Ubuntu)",
"X-Powered-By": "PHP/8.1.2",
})
results := gradeSecurityHeaders(h, false)
for _, name := range []string{"Server", "X-Powered-By"} {
f, ok := findFinding(results, name)
if !ok {
t.Errorf("expected disclosure finding for %s", name)
continue
}
if !f.Present || f.Severity != "low" {
t.Errorf("%s finding = %+v, want present low", name, f)
}
}
}
func TestSecurityHeaders_LiveResponse(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
w.Header().Set("Server", "nginx/1.25.3")
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
results, err := SecurityHeaders(server.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("SecurityHeaders returned error: %v", err)
}
if _, ok := findFinding(results, "X-Frame-Options"); ok {
t.Error("X-Frame-Options was set, should not be flagged")
}
if _, ok := findFinding(results, "Content-Security-Policy"); !ok {
t.Error("expected missing Content-Security-Policy to be flagged")
}
if _, ok := findFinding(results, "Server"); !ok {
t.Error("expected Server disclosure to be flagged")
}
}
-253
View File
@@ -1,253 +0,0 @@
/*
··
: :
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
··
*/
package scan
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/dropalldatabases/sif/internal/logger"
"github.com/dropalldatabases/sif/internal/output"
)
const securityTrailsBaseURL = "https://api.securitytrails.com/v1"
// SecurityTrailsResult holds discovered domains from SecurityTrails API
type SecurityTrailsResult struct {
Domain string `json:"domain"`
Subdomains []string `json:"subdomains,omitempty"`
AssociatedDomains []string `json:"associated_domains,omitempty"`
}
// stSubdomainsResponse is the raw response from the subdomains endpoint -
// returns prefix labels, not FQDNs
type stSubdomainsResponse struct {
Subdomains []string `json:"subdomains"`
}
type stAssociatedResponse struct {
Records []stAssociatedRecord `json:"records"`
}
type stAssociatedRecord struct {
Hostname string `json:"hostname"`
}
// SecurityTrails queries the SecurityTrails API for subdomains and associated domains.
// API key should be provided via the SECURITYTRAILS_API_KEY environment variable.
func SecurityTrails(targetURL string, timeout time.Duration, logdir string) (*SecurityTrailsResult, error) {
output.ScanStart("SecurityTrails lookup")
spin := output.NewSpinner("querying SecurityTrails API")
spin.Start()
apiKey := os.Getenv("SECURITYTRAILS_API_KEY")
if apiKey == "" {
spin.Stop()
output.Warn("SECURITYTRAILS_API_KEY environment variable not set, skipping SecurityTrails lookup")
return nil, fmt.Errorf("SECURITYTRAILS_API_KEY environment variable not set")
}
parsedURL, err := url.Parse(targetURL)
if err != nil {
spin.Stop()
return nil, fmt.Errorf("failed to parse URL: %w", err)
}
hostname := parsedURL.Hostname()
client := &http.Client{Timeout: timeout}
result := &SecurityTrailsResult{
Domain: hostname,
}
// fetch subdomains
spin.Update("fetching subdomains for " + hostname)
subs, err := querySTSubdomains(client, hostname, apiKey)
if err != nil {
// non-fatal - still try associated domains
output.Warn("SecurityTrails subdomains failed: %v", err)
} else {
result.Subdomains = subs
}
// fetch associated domains
spin.Update("fetching associated domains for " + hostname)
assoc, err := querySTAssociated(client, hostname, apiKey)
if err != nil {
output.Warn("SecurityTrails associated domains failed: %v", err)
} else {
result.AssociatedDomains = assoc
}
spin.Stop()
if logdir != "" {
sanitizedURL := stripScheme(targetURL)
if err := logger.WriteHeader(sanitizedURL, logdir, "SecurityTrails lookup"); err != nil {
output.Error("error writing log header: %v", err)
}
logSecurityTrailsResults(sanitizedURL, logdir, result)
}
printSecurityTrailsResults(result)
total := len(result.Subdomains) + len(result.AssociatedDomains)
output.ScanComplete("SecurityTrails lookup", total, "domains discovered")
return result, nil
}
// DiscoveredURLs returns all discovered domains as https:// URLs.
// used by the orchestration layer for target expansion.
func (r *SecurityTrailsResult) DiscoveredURLs() []string {
seen := make(map[string]struct{})
var urls []string
for _, sub := range r.Subdomains {
fqdn := sub + "." + r.Domain
if _, ok := seen[fqdn]; !ok {
seen[fqdn] = struct{}{}
urls = append(urls, "https://"+fqdn)
}
}
for _, assoc := range r.AssociatedDomains {
if _, ok := seen[assoc]; !ok {
seen[assoc] = struct{}{}
urls = append(urls, "https://"+assoc)
}
}
return urls
}
func querySTSubdomains(client *http.Client, hostname, apiKey string) ([]string, error) {
reqURL := fmt.Sprintf("%s/domain/%s/subdomains", securityTrailsBaseURL, hostname)
body, err := doSTRequest(client, reqURL, apiKey)
if err != nil {
return nil, err
}
var resp stSubdomainsResponse
if err := json.Unmarshal(body, &resp); err != nil {
return nil, fmt.Errorf("parse subdomains response: %w", err)
}
return resp.Subdomains, nil
}
func querySTAssociated(client *http.Client, hostname, apiKey string) ([]string, error) {
reqURL := fmt.Sprintf("%s/domain/%s/associated", securityTrailsBaseURL, hostname)
body, err := doSTRequest(client, reqURL, apiKey)
if err != nil {
return nil, err
}
var resp stAssociatedResponse
if err := json.Unmarshal(body, &resp); err != nil {
return nil, fmt.Errorf("parse associated response: %w", err)
}
domains := make([]string, 0, len(resp.Records))
for _, rec := range resp.Records {
if rec.Hostname != "" {
domains = append(domains, rec.Hostname)
}
}
return domains, nil
}
// doSTRequest makes an authenticated GET to the SecurityTrails API
func doSTRequest(client *http.Client, reqURL, apiKey string) ([]byte, error) {
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, reqURL, http.NoBody)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
req.Header.Set("APIKEY", apiKey)
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("SecurityTrails request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusUnauthorized {
return nil, fmt.Errorf("invalid SecurityTrails API key (status %d)", resp.StatusCode)
}
if resp.StatusCode == http.StatusTooManyRequests {
return nil, fmt.Errorf("SecurityTrails rate limit exceeded")
}
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
return nil, fmt.Errorf("SecurityTrails API error (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(io.LimitReader(resp.Body, 5*1024*1024))
if err != nil {
return nil, fmt.Errorf("read response: %w", err)
}
return body, nil
}
func printSecurityTrailsResults(result *SecurityTrailsResult) {
output.Info("Domain: %s", output.Highlight.Render(result.Domain))
if len(result.Subdomains) > 0 {
output.Info("Subdomains found: %d", len(result.Subdomains))
for _, sub := range result.Subdomains {
output.Success(" %s.%s", sub, result.Domain)
}
}
if len(result.AssociatedDomains) > 0 {
output.Info("Associated domains found: %d", len(result.AssociatedDomains))
for _, assoc := range result.AssociatedDomains {
output.Success(" %s", assoc)
}
}
}
func logSecurityTrailsResults(sanitizedURL, logdir string, result *SecurityTrailsResult) {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("Domain: %s\n", result.Domain))
if len(result.Subdomains) > 0 {
sb.WriteString(fmt.Sprintf("\nSubdomains (%d):\n", len(result.Subdomains)))
for _, sub := range result.Subdomains {
sb.WriteString(fmt.Sprintf(" %s.%s\n", sub, result.Domain))
}
}
if len(result.AssociatedDomains) > 0 {
sb.WriteString(fmt.Sprintf("\nAssociated Domains (%d):\n", len(result.AssociatedDomains)))
for _, assoc := range result.AssociatedDomains {
sb.WriteString(fmt.Sprintf(" %s\n", assoc))
}
}
logger.Write(sanitizedURL, logdir, sb.String())
}
-208
View File
@@ -1,208 +0,0 @@
package scan
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestSecurityTrailsResult_DiscoveredURLs(t *testing.T) {
result := &SecurityTrailsResult{
Domain: "example.com",
Subdomains: []string{"www", "api", "mail"},
AssociatedDomains: []string{"example.org", "example.net"},
}
urls := result.DiscoveredURLs()
if len(urls) != 5 {
t.Errorf("expected 5 URLs, got %d: %v", len(urls), urls)
}
expected := map[string]bool{
"https://www.example.com": false,
"https://api.example.com": false,
"https://mail.example.com": false,
"https://example.org": false,
"https://example.net": false,
}
for _, u := range urls {
if _, ok := expected[u]; !ok {
t.Errorf("unexpected URL: %s", u)
}
expected[u] = true
}
for u, seen := range expected {
if !seen {
t.Errorf("missing expected URL: %s", u)
}
}
}
func TestSecurityTrailsResult_DiscoveredURLs_Dedup(t *testing.T) {
result := &SecurityTrailsResult{
Domain: "example.com",
Subdomains: []string{"www"},
AssociatedDomains: []string{"www.example.com"},
}
urls := result.DiscoveredURLs()
if len(urls) != 1 {
t.Errorf("expected 1 URL (deduped), got %d: %v", len(urls), urls)
}
}
func TestSecurityTrailsResult_DiscoveredURLs_Empty(t *testing.T) {
result := &SecurityTrailsResult{
Domain: "example.com",
}
urls := result.DiscoveredURLs()
if len(urls) != 0 {
t.Errorf("expected 0 URLs, got %d: %v", len(urls), urls)
}
}
func TestDoSTRequest_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("APIKEY") != "test-key" {
w.WriteHeader(http.StatusForbidden)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"test": true}`))
}))
defer server.Close()
client := &http.Client{Timeout: 5 * time.Second}
body, err := doSTRequest(client, server.URL, "test-key")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(body) == 0 {
t.Error("expected non-empty body")
}
}
func TestDoSTRequest_Unauthorized(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
}))
defer server.Close()
client := &http.Client{Timeout: 5 * time.Second}
_, err := doSTRequest(client, server.URL, "bad-key")
if err == nil {
t.Error("expected error for forbidden response")
}
}
func TestDoSTRequest_RateLimit(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTooManyRequests)
}))
defer server.Close()
client := &http.Client{Timeout: 5 * time.Second}
_, err := doSTRequest(client, server.URL, "test-key")
if err == nil {
t.Error("expected error for rate limit response")
}
}
func TestQuerySTSubdomains(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("APIKEY") != "test-key" {
w.WriteHeader(http.StatusForbidden)
return
}
resp := stSubdomainsResponse{
Subdomains: []string{"www", "api", "mail", "dev"},
}
json.NewEncoder(w).Encode(resp)
}))
defer server.Close()
client := &http.Client{Timeout: 5 * time.Second}
// query the mock server directly via doSTRequest + unmarshal
body, err := doSTRequest(client, server.URL, "test-key")
if err != nil {
t.Fatalf("request failed: %v", err)
}
var resp stSubdomainsResponse
if err := json.Unmarshal(body, &resp); err != nil {
t.Fatalf("unmarshal failed: %v", err)
}
if len(resp.Subdomains) != 4 {
t.Errorf("expected 4 subdomains, got %d", len(resp.Subdomains))
}
}
func TestQuerySTAssociated(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("APIKEY") != "test-key" {
w.WriteHeader(http.StatusForbidden)
return
}
resp := stAssociatedResponse{
Records: []stAssociatedRecord{
{Hostname: "related.com"},
{Hostname: "sibling.net"},
{Hostname: ""},
},
}
json.NewEncoder(w).Encode(resp)
}))
defer server.Close()
client := &http.Client{Timeout: 5 * time.Second}
body, err := doSTRequest(client, server.URL, "test-key")
if err != nil {
t.Fatalf("request failed: %v", err)
}
var resp stAssociatedResponse
if err := json.Unmarshal(body, &resp); err != nil {
t.Fatalf("unmarshal failed: %v", err)
}
// should have 3 records total (including empty one)
if len(resp.Records) != 3 {
t.Errorf("expected 3 records, got %d", len(resp.Records))
}
// filter empty hostnames like the real code does
var domains []string
for _, rec := range resp.Records {
if rec.Hostname != "" {
domains = append(domains, rec.Hostname)
}
}
if len(domains) != 2 {
t.Errorf("expected 2 non-empty domains, got %d", len(domains))
}
}
func TestSecurityTrailsResult_ResultType(t *testing.T) {
result := &SecurityTrailsResult{}
if result.ResultType() != "securitytrails" {
t.Errorf("expected ResultType 'securitytrails', got '%s'", result.ResultType())
}
}
func TestSecurityTrailsIntegration(t *testing.T) {
t.Skip("integration test - requires valid SECURITYTRAILS_API_KEY")
_, err := SecurityTrails("https://example.com", 10*time.Second, "")
if err != nil {
t.Logf("SecurityTrails lookup failed: %v", err)
}
}
+17 -21
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -13,7 +13,6 @@
package scan
import (
"context"
"encoding/json"
"fmt"
"io"
@@ -73,6 +72,7 @@ type shodanHostResponse struct {
}
// shodanMetadata represents the _shodan field in Shodan API responses.
// This provides type safety instead of using map[string]interface{}.
type shodanMetadata struct {
Module string `json:"module"`
Crawler string `json:"crawler,omitempty"`
@@ -134,7 +134,7 @@ func Shodan(targetURL string, timeout time.Duration, logdir string) (*ShodanResu
// log results
if logdir != "" {
sanitizedURL := stripScheme(targetURL)
sanitizedURL := strings.Split(targetURL, "://")[1]
if err := logger.WriteHeader(sanitizedURL, logdir, "Shodan lookup"); err != nil {
output.Error("Error writing log header: %v", err)
}
@@ -159,20 +159,20 @@ func resolveHostname(hostname string) (string, error) {
return hostname, nil
}
addrs, err := net.DefaultResolver.LookupIPAddr(context.TODO(), hostname)
ips, err := net.LookupIP(hostname)
if err != nil {
return "", err
}
// prefer IPv4
for _, addr := range addrs {
if addr.IP.To4() != nil {
return addr.IP.String(), nil
for _, ip := range ips {
if ip.To4() != nil {
return ip.String(), nil
}
}
if len(addrs) > 0 {
return addrs[0].IP.String(), nil
if len(ips) > 0 {
return ips[0].String(), nil
}
return "", fmt.Errorf("no IP addresses found for %s", hostname)
@@ -182,11 +182,7 @@ func queryShodanHost(ip string, apiKey string, timeout time.Duration) (*ShodanRe
client := &http.Client{Timeout: timeout}
reqURL := fmt.Sprintf("%s/shodan/host/%s?key=%s", shodanBaseURL, ip, apiKey)
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, reqURL, http.NoBody)
if err != nil {
return nil, fmt.Errorf("failed to create Shodan request: %w", err)
}
resp, err := client.Do(req)
resp, err := client.Get(reqURL)
if err != nil {
return nil, fmt.Errorf("failed to query Shodan: %w", err)
}
@@ -236,14 +232,14 @@ func queryShodanHost(ip string, apiKey string, timeout time.Duration) (*ShodanRe
Services: make([]ShodanService, 0, len(shodanResp.Data)),
}
for i := range shodanResp.Data {
for _, data := range shodanResp.Data {
service := ShodanService{
Port: shodanResp.Data[i].Port,
Protocol: shodanResp.Data[i].Transport,
Product: shodanResp.Data[i].Product,
Version: shodanResp.Data[i].Version,
Banner: truncateBanner(shodanResp.Data[i].Data, 200),
Module: shodanResp.Data[i].Shodan.Module,
Port: data.Port,
Protocol: data.Transport,
Product: data.Product,
Version: data.Version,
Banner: truncateBanner(data.Data, 200),
Module: data.Shodan.Module,
}
result.Services = append(result.Services, service)
}
+1 -1
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
+5 -15
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -13,7 +13,6 @@
package scan
import (
"context"
"io"
"net/http"
"regexp"
@@ -121,7 +120,7 @@ func SQL(targetURL string, timeout time.Duration, threads int, logdir string) (*
spin := output.NewSpinner("Scanning for SQL exposures")
spin.Start()
sanitizedURL := stripScheme(targetURL)
sanitizedURL := strings.Split(targetURL, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "SQL reconnaissance"); err != nil {
@@ -165,12 +164,7 @@ func SQL(targetURL string, timeout time.Duration, threads int, logdir string) (*
adminPath := sqlAdminPaths[idx]
checkURL := strings.TrimSuffix(targetURL, "/") + adminPath.path
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, checkURL, http.NoBody)
if err != nil {
charmlog.Debugf("Error creating request for %s: %v", checkURL, err)
continue
}
resp, err := client.Do(req)
resp, err := client.Get(checkURL)
if err != nil {
charmlog.Debugf("Error checking %s: %v", checkURL, err)
continue
@@ -249,7 +243,7 @@ func SQL(targetURL string, timeout time.Duration, threads int, logdir string) (*
if totalFindings == 0 {
log.Info("No SQL exposures found")
log.Complete(0, "found")
return nil, nil //nolint:nilnil // no SQLi found is not an error
return nil, nil
}
log.Complete(totalFindings, "found")
@@ -291,11 +285,7 @@ func isAdminPanel(body string, panelType string) bool {
}
func checkDatabaseErrors(client *http.Client, checkURL, sanitizedURL string, result *SQLResult, logdir string, mu *sync.Mutex, seen map[string]bool) {
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, checkURL, http.NoBody)
if err != nil {
return
}
resp, err := client.Do(req)
resp, err := client.Get(checkURL)
if err != nil {
return
}
+1 -1
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
+21 -17
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -13,8 +13,10 @@
package scan
import (
"context"
"fmt"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/logger"
"github.com/dropalldatabases/sif/internal/styles"
"io"
"net"
"net/http"
@@ -22,10 +24,6 @@ import (
"strings"
"sync"
"time"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/logger"
"github.com/dropalldatabases/sif/internal/styles"
)
// SubdomainTakeoverResult represents the outcome of a subdomain takeover vulnerability check.
@@ -36,12 +34,22 @@ type SubdomainTakeoverResult struct {
Service string `json:"service,omitempty"`
}
// SubdomainTakeover checks dnsResults for dangling subdomains pointing at
// unclaimed third-party services.
// SubdomainTakeover checks for potential subdomain takeover vulnerabilities.
//
// Parameters:
// - url: the target URL to scan
// - dnsResults: a slice of subdomains to check (typically from Dnslist function)
// - timeout: maximum duration for each subdomain check
// - threads: number of concurrent threads to use
// - logdir: directory to store log files (empty string for no logging)
//
// Returns:
// - []SubdomainTakeoverResult: a slice of results for each checked subdomain
// - error: any error encountered during the scan
func SubdomainTakeover(url string, dnsResults []string, timeout time.Duration, threads int, logdir string) ([]SubdomainTakeoverResult, error) {
fmt.Println(styles.Separator.Render("Starting " + styles.Status.Render("Subdomain Takeover Vulnerability Check") + "..."))
fmt.Println(styles.Separator.Render("🔍 Starting " + styles.Status.Render("Subdomain Takeover Vulnerability Check") + "..."))
sanitizedURL := stripScheme(url)
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "Subdomain Takeover Vulnerability Check"); err != nil {
@@ -51,7 +59,7 @@ func SubdomainTakeover(url string, dnsResults []string, timeout time.Duration, t
}
subdomainlog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "Subdomain Takeover",
Prefix: "Subdomain Takeover 🔍",
})
client := &http.Client{
@@ -106,15 +114,11 @@ func SubdomainTakeover(url string, dnsResults []string, timeout time.Duration, t
}
func checkSubdomainTakeover(subdomain string, client *http.Client) (bool, string) {
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, "http://"+subdomain, http.NoBody)
if err != nil {
return false, ""
}
resp, err := client.Do(req)
resp, err := client.Get("http://" + subdomain)
if err != nil {
if strings.Contains(err.Error(), "no such host") {
// Check if CNAME exists
cname, err := net.DefaultResolver.LookupCNAME(context.TODO(), subdomain)
cname, err := net.LookupCNAME(subdomain)
if err == nil && cname != "" {
return true, "Dangling CNAME"
}
+4 -2
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
@@ -13,6 +13,8 @@
package scan
import (
"strings"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/logger"
"github.com/dropalldatabases/sif/internal/output"
@@ -22,7 +24,7 @@ import (
func Whois(url string, logdir string) {
output.ScanStart("WHOIS lookup")
sanitizedURL := stripScheme(url)
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, " WHOIS scanning"); err != nil {
output.Error("Error creating log file: %v", err)
+1 -1
View File
@@ -4,7 +4,7 @@
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
··
-67
View File
@@ -1,67 +0,0 @@
/*
··
: :
: · Blazing-fast pentesting suite :
: · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
··
*/
// Package version resolves sif's version from the build.
package version
import (
"runtime/debug"
"strings"
)
// Resolve returns the best version available: the build-time ldflag if it was
// stamped, else the go build info (module tag or vcs revision), else "dev". the
// leading v is dropped so it matches the bare form the rest of sif uses.
func Resolve(ldflag string) string {
if ldflag != "" && ldflag != "dev" {
return normalize(ldflag)
}
if v := fromBuildInfo(); v != "" {
return normalize(v)
}
return "dev"
}
func fromBuildInfo() string {
info, ok := debug.ReadBuildInfo()
if !ok {
return ""
}
if v := info.Main.Version; v != "" && v != "(devel)" {
return v
}
// no module tag (a local build) - fall back to the commit it was built from
var revision, modified string
for _, s := range info.Settings {
switch s.Key {
case "vcs.revision":
revision = s.Value
case "vcs.modified":
modified = s.Value
}
}
if revision == "" {
return ""
}
if len(revision) > 12 {
revision = revision[:12]
}
if modified == "true" {
revision += "-dirty"
}
return revision
}
func normalize(v string) string {
return strings.TrimPrefix(v, "v")
}

Some files were not shown because too many files have changed in this diff Show More