Skip to content

Commit 98ba117

Browse files
committed
new updates
1 parent ba07101 commit 98ba117

37 files changed

Lines changed: 6546 additions & 513 deletions

bughunt.db-shm

-32 KB
Binary file not shown.

bughunt.db-wal

Whitespace-only changes.

cmd/autoar/main.go

Lines changed: 216 additions & 14 deletions
Large diffs are not rendered by default.

env.example

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,20 @@ AWS_SECRET_ACCESS_KEY=your_aws_secret_key_here
153153
AWS_SESSION_TOKEN=your_aws_session_token_here
154154
AWS_DEFAULT_REGION=us-east-1
155155

156+
# ============================================================================
157+
# CLOUDFLARE R2 STORAGE CONFIGURATION (Optional)
158+
# ============================================================================
159+
# Enable R2 storage for large files (files > 20MB will be uploaded to R2 instead of Discord)
160+
# This helps avoid Discord's 25MB file size limit and saves local storage space
161+
USE_R2_STORAGE=false
162+
R2_BUCKET_NAME=autoar
163+
R2_ACCOUNT_ID=your_r2_account_id_here
164+
R2_ACCESS_KEY_ID=your_r2_access_key_id_here
165+
R2_SECRET_KEY=your_r2_secret_key_here
166+
# Optional: Custom public URL for R2 bucket (if you have a custom domain)
167+
# If not set, will use default R2 public URL format: https://pub-{ACCOUNT_ID}.r2.dev
168+
R2_PUBLIC_URL=
169+
156170
# ============================================================================
157171
# TOOL-SPECIFIC CONFIGURATION
158172
# ============================================================================

internal/modules/aem/aem.go

Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
package aem
2+
3+
import (
4+
"bufio"
5+
"encoding/json"
6+
"fmt"
7+
"log"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
"time"
12+
13+
"github.com/h0tak88r/AutoAR/v3/internal/modules/utils"
14+
)
15+
16+
// Options controls how the AEM scan runs
17+
type Options struct {
18+
Domain string // Domain to scan
19+
LiveHostsFile string // File with live hosts/URLs to scan
20+
OutputDir string // Output directory for results
21+
Threads int // Number of parallel workers
22+
SSRFHost string // Hostname/IP for SSRF detection (VPS required)
23+
SSRFPort int // Port for SSRF detection
24+
Proxy string // HTTP/HTTPS proxy
25+
Debug bool // Enable debug output
26+
Handlers []string // Specific handlers to run (empty = all)
27+
}
28+
29+
// Result contains the scan results
30+
type Result struct {
31+
OutputDir string
32+
ResultsFile string
33+
LogFile string
34+
DiscoveredFile string // File with discovered AEM instances
35+
ScannedFile string // File with scan results
36+
DiscoveredCount int
37+
Vulnerabilities int
38+
Duration time.Duration
39+
}
40+
41+
// Finding represents a discovered vulnerability
42+
type Finding struct {
43+
Name string `json:"name"`
44+
URL string `json:"url"`
45+
Description string `json:"description"`
46+
Severity string `json:"severity,omitempty"`
47+
}
48+
49+
// DiscoverAEM scans URLs and discovers AEM webapps using native Go implementation
50+
func DiscoverAEM(opts Options) ([]string, error) {
51+
// Create HTTP client
52+
client, err := NewHTTPClient(opts.Proxy, opts.Debug)
53+
if err != nil {
54+
return nil, fmt.Errorf("failed to create HTTP client: %w", err)
55+
}
56+
57+
// Get list of URLs to scan
58+
var urls []string
59+
if opts.LiveHostsFile != "" {
60+
file, err := os.Open(opts.LiveHostsFile)
61+
if err != nil {
62+
return nil, fmt.Errorf("failed to open live hosts file: %w", err)
63+
}
64+
defer file.Close()
65+
66+
scanner := bufio.NewScanner(file)
67+
for scanner.Scan() {
68+
url := strings.TrimSpace(scanner.Text())
69+
if url != "" {
70+
urls = append(urls, url)
71+
}
72+
}
73+
if err := scanner.Err(); err != nil {
74+
return nil, fmt.Errorf("failed to read live hosts file: %w", err)
75+
}
76+
} else if opts.Domain != "" {
77+
// Add common protocol variations
78+
urls = []string{
79+
fmt.Sprintf("https://%s", opts.Domain),
80+
fmt.Sprintf("http://%s", opts.Domain),
81+
}
82+
} else {
83+
return nil, fmt.Errorf("either Domain or LiveHostsFile must be provided")
84+
}
85+
86+
// Create output directory
87+
resultsDir := os.Getenv("AUTOAR_RESULTS_DIR")
88+
if resultsDir == "" {
89+
resultsDir = "new-results"
90+
}
91+
92+
outputDir := opts.OutputDir
93+
if outputDir == "" {
94+
if opts.Domain != "" {
95+
sanitizedDomain := sanitizeDomainForPath(opts.Domain)
96+
outputDir = filepath.Join(resultsDir, sanitizedDomain, "aem")
97+
} else {
98+
outputDir = filepath.Join(resultsDir, "aem")
99+
}
100+
}
101+
if err := os.MkdirAll(outputDir, 0755); err != nil {
102+
return nil, fmt.Errorf("failed to create output directory: %w", err)
103+
}
104+
105+
discoveredFile := filepath.Join(outputDir, "discovered-aem.txt")
106+
107+
// Discover AEM instances using native Go implementation
108+
log.Printf("[AEM] Starting AEM discovery (native Go)...")
109+
discovered := DiscoverAEMFromURLs(urls, client, opts.Threads)
110+
111+
// Save discovered instances
112+
if len(discovered) > 0 {
113+
discoveredFH, err := os.Create(discoveredFile)
114+
if err == nil {
115+
for _, url := range discovered {
116+
fmt.Fprintln(discoveredFH, url)
117+
}
118+
discoveredFH.Close()
119+
}
120+
121+
// Send findings to Discord webhook if configured
122+
webhookURL := os.Getenv("DISCORD_WEBHOOK")
123+
if webhookURL != "" {
124+
if info, err := os.Stat(discoveredFile); err == nil && info.Size() > 0 {
125+
domain := opts.Domain
126+
if domain == "" && opts.LiveHostsFile != "" {
127+
domain = "targets"
128+
}
129+
utils.SendWebhookFileAsync(discoveredFile, fmt.Sprintf("AEM Discovery: AEM instances found (%d discovered)", len(discovered)))
130+
utils.SendWebhookLogAsync(fmt.Sprintf("AEM discovery: %d AEM instance(s) found", len(discovered)))
131+
}
132+
}
133+
} else {
134+
// No findings
135+
webhookURL := os.Getenv("DISCORD_WEBHOOK")
136+
if webhookURL != "" {
137+
utils.SendWebhookLogAsync("AEM discovery completed with 0 AEM instances found")
138+
}
139+
}
140+
141+
return discovered, nil
142+
}
143+
144+
// ScanAEM scans a single AEM instance for vulnerabilities using native Go implementation
145+
func ScanAEM(url string, opts Options) ([]Finding, error) {
146+
// Create HTTP client
147+
client, err := NewHTTPClient(opts.Proxy, opts.Debug)
148+
if err != nil {
149+
return nil, fmt.Errorf("failed to create HTTP client: %w", err)
150+
}
151+
152+
// Build SSRF host string if provided
153+
ssrfHost := ""
154+
if opts.SSRFHost != "" {
155+
if opts.SSRFPort > 0 {
156+
ssrfHost = fmt.Sprintf("%s:%d", opts.SSRFHost, opts.SSRFPort)
157+
} else {
158+
ssrfHost = opts.SSRFHost
159+
}
160+
}
161+
162+
// Create output directory
163+
resultsDir := os.Getenv("AUTOAR_RESULTS_DIR")
164+
if resultsDir == "" {
165+
resultsDir = "new-results"
166+
}
167+
168+
outputDir := opts.OutputDir
169+
if outputDir == "" {
170+
sanitizedURL := sanitizeDomainForPath(url)
171+
outputDir = filepath.Join(resultsDir, "aem", sanitizedURL)
172+
}
173+
if err := os.MkdirAll(outputDir, 0755); err != nil {
174+
return nil, fmt.Errorf("failed to create output directory: %w", err)
175+
}
176+
177+
resultsFile := filepath.Join(outputDir, "findings.json")
178+
179+
// Scan using native Go implementation
180+
log.Printf("[AEM] Scanning %s for vulnerabilities (native Go)...", url)
181+
findings := ScanAEMInstance(url, ssrfHost, client, opts.Handlers)
182+
183+
// Save findings as JSON
184+
if len(findings) > 0 {
185+
if data, err := json.MarshalIndent(findings, "", " "); err == nil {
186+
os.WriteFile(resultsFile, data, 0644)
187+
}
188+
189+
// Send findings to Discord webhook if configured
190+
webhookURL := os.Getenv("DISCORD_WEBHOOK")
191+
if webhookURL != "" {
192+
if info, err := os.Stat(resultsFile); err == nil && info.Size() > 0 {
193+
utils.SendWebhookFileAsync(resultsFile, fmt.Sprintf("AEM Finding: Vulnerabilities found for %s (%d findings)", url, len(findings)))
194+
}
195+
utils.SendWebhookLogAsync(fmt.Sprintf("AEM scan completed for %s - %d vulnerability/vulnerabilities found", url, len(findings)))
196+
}
197+
} else {
198+
// No findings
199+
webhookURL := os.Getenv("DISCORD_WEBHOOK")
200+
if webhookURL != "" {
201+
utils.SendWebhookLogAsync(fmt.Sprintf("AEM scan completed for %s with 0 findings", url))
202+
}
203+
}
204+
205+
return findings, nil
206+
}
207+
208+
// Run executes the full AEM scan workflow: discovery + scanning
209+
func Run(opts Options) (*Result, error) {
210+
startTime := time.Now()
211+
212+
// Allow both Domain and LiveHostsFile
213+
if opts.LiveHostsFile == "" && opts.Domain == "" {
214+
return nil, fmt.Errorf("either Domain or LiveHostsFile must be provided")
215+
}
216+
217+
// Set defaults
218+
if opts.Threads == 0 {
219+
opts.Threads = 50
220+
}
221+
222+
resultsDir := os.Getenv("AUTOAR_RESULTS_DIR")
223+
if resultsDir == "" {
224+
resultsDir = "new-results"
225+
}
226+
227+
// Determine output directory
228+
outputDir := opts.OutputDir
229+
if outputDir == "" {
230+
if opts.Domain != "" {
231+
sanitizedDomain := sanitizeDomainForPath(opts.Domain)
232+
outputDir = filepath.Join(resultsDir, sanitizedDomain, "aem")
233+
} else {
234+
outputDir = filepath.Join(resultsDir, "aem")
235+
}
236+
}
237+
if err := os.MkdirAll(outputDir, 0755); err != nil {
238+
return nil, fmt.Errorf("failed to create output directory: %w", err)
239+
}
240+
241+
resultsFile := filepath.Join(outputDir, "results.json")
242+
discoveredFile := filepath.Join(outputDir, "discovered-aem.txt")
243+
scannedFile := filepath.Join(outputDir, "scanned-results.json")
244+
logFile := filepath.Join(outputDir, "aem-scan.log")
245+
246+
res := &Result{
247+
OutputDir: outputDir,
248+
ResultsFile: resultsFile,
249+
LogFile: logFile,
250+
DiscoveredFile: discoveredFile,
251+
ScannedFile: scannedFile,
252+
}
253+
254+
// Step 1: Discover AEM instances
255+
log.Printf("[AEM] Step 1: Discovering AEM webapps...")
256+
discovered, err := DiscoverAEM(opts)
257+
if err != nil {
258+
log.Printf("[AEM] Discovery failed: %v", err)
259+
// Continue anyway, might have partial results
260+
}
261+
res.DiscoveredCount = len(discovered)
262+
263+
if len(discovered) == 0 {
264+
log.Printf("[AEM] No AEM instances discovered")
265+
res.Duration = time.Since(startTime)
266+
return res, nil
267+
}
268+
269+
log.Printf("[AEM] Discovered %d AEM instances", len(discovered))
270+
271+
// Step 2: Scan each discovered AEM instance
272+
log.Printf("[AEM] Step 2: Scanning discovered AEM instances for vulnerabilities...")
273+
allFindings := []Finding{}
274+
for i, url := range discovered {
275+
log.Printf("[AEM] Scanning %d/%d: %s", i+1, len(discovered), url)
276+
findings, err := ScanAEM(url, opts)
277+
if err != nil {
278+
log.Printf("[AEM] Failed to scan %s: %v", url, err)
279+
continue
280+
}
281+
allFindings = append(allFindings, findings...)
282+
}
283+
284+
res.Vulnerabilities = len(allFindings)
285+
286+
// Save all results
287+
allResults := map[string]interface{}{
288+
"discovered_count": res.DiscoveredCount,
289+
"vulnerabilities": res.Vulnerabilities,
290+
"discovered": discovered,
291+
"findings": allFindings,
292+
"scan_time": time.Now().Format(time.RFC3339),
293+
}
294+
295+
if data, err := json.MarshalIndent(allResults, "", " "); err == nil {
296+
os.WriteFile(resultsFile, data, 0644)
297+
os.WriteFile(scannedFile, data, 0644)
298+
}
299+
300+
// Send findings to Discord webhook if configured
301+
webhookURL := os.Getenv("DISCORD_WEBHOOK")
302+
if webhookURL != "" {
303+
if res.Vulnerabilities > 0 {
304+
if info, err := os.Stat(resultsFile); err == nil && info.Size() > 0 {
305+
domain := opts.Domain
306+
if domain == "" && opts.LiveHostsFile != "" {
307+
domain = "targets"
308+
}
309+
utils.SendWebhookFileAsync(resultsFile, fmt.Sprintf("AEM Scan Summary: %d AEM instances, %d vulnerabilities for %s", res.DiscoveredCount, res.Vulnerabilities, domain))
310+
}
311+
utils.SendWebhookLogAsync(fmt.Sprintf("AEM scan completed: %d AEM instance(s), %d vulnerability/vulnerabilities found", res.DiscoveredCount, res.Vulnerabilities))
312+
} else {
313+
domain := opts.Domain
314+
if domain == "" && opts.LiveHostsFile != "" {
315+
domain = "targets"
316+
}
317+
utils.SendWebhookLogAsync(fmt.Sprintf("AEM scan completed for %s: %d AEM instance(s), 0 vulnerabilities", domain, res.DiscoveredCount))
318+
}
319+
}
320+
321+
res.Duration = time.Since(startTime)
322+
log.Printf("[AEM] Scan completed: %d AEM instances, %d vulnerabilities found", res.DiscoveredCount, res.Vulnerabilities)
323+
324+
return res, nil
325+
}
326+
327+
// Helper functions
328+
329+
func sanitizeDomainForPath(domain string) string {
330+
// Remove protocol
331+
domain = strings.TrimPrefix(domain, "http://")
332+
domain = strings.TrimPrefix(domain, "https://")
333+
// Replace invalid filesystem characters
334+
domain = strings.ReplaceAll(domain, ":", "-")
335+
domain = strings.ReplaceAll(domain, "/", "-")
336+
domain = strings.ReplaceAll(domain, "?", "-")
337+
domain = strings.ReplaceAll(domain, "&", "-")
338+
domain = strings.ReplaceAll(domain, "=", "-")
339+
return domain
340+
}
341+
342+
func extractFindingName(line string) string {
343+
// Try to extract finding name from log line
344+
// Format varies, but typically contains keywords
345+
keywords := []string{
346+
"Exposed DefaultGetServlet",
347+
"Exposed QueryBulderJsonServlet",
348+
"Exposed GQLServlet",
349+
"Ability to create new JCR nodes",
350+
"Exposed POSTServlet",
351+
"Exposed LoginStatusServlet",
352+
"Users with default password",
353+
"Exposed Felix Console",
354+
"Enabled WCMDebugFilter",
355+
"Exposed WCMSuggestionsServlet",
356+
"Exposed CRXDE",
357+
"SSRF",
358+
"Exposed Webdav",
359+
"Exposed Groovy Console",
360+
"Exposed ACS AEM Tools",
361+
"VULNERABLE",
362+
"EXPOSED",
363+
}
364+
365+
for _, keyword := range keywords {
366+
if strings.Contains(line, keyword) {
367+
return keyword
368+
}
369+
}
370+
371+
return "Unknown vulnerability"
372+
}
373+

0 commit comments

Comments
 (0)