Building a Concurrent Firebase Scanner: Learning from firebaseio-checker-go
Hook
Over 2,700 Firebase Realtime Databases were found publicly exposed in a single security research campaign. The attack vector? A simple HTTP GET request to /.json that anyone can make.
Context
Firebase Realtime Database makes it dangerously easy to misconfigure security rules. The default behavior during development allows public read/write access, and many developers ship these permissive rules to production. Unlike traditional databases hidden behind application servers, Firebase databases are directly accessible via predictable URLs: https://[project-id].firebaseio.com/. This architectural decision—while enabling Firebase's real-time sync magic—creates a massive attack surface. Security researchers and bug bounty hunters need efficient tools to scan large lists of potential Firebase targets during reconnaissance. Manual checking is impractical when you're validating hundreds or thousands of discovered Firebase URLs from public GitHub repositories, mobile app decompilation, or subdomain enumeration. The firebaseio-checker-go tool emerged from this need: a purpose-built scanner that leverages Go's concurrency model to rapidly identify databases with world-readable permissions.
Technical Insight
The tool's architecture is refreshingly straightforward, demonstrating how Go's concurrency primitives enable high-performance security scanning with minimal code. At its core, the scanner reads newline-delimited Firebase URLs from a text file, spawns worker goroutines controlled by a sized wait group, and tests each database's root endpoint.
The concurrency model uses a semaphore pattern implemented with a buffered channel to limit simultaneous requests. Here's the essential pattern from the codebase:
func main() {
concurrency := flag.Int("c", 10, "Concurrency level")
flag.Parse()
semaphore := make(chan struct{}, *concurrency)
var wg sync.WaitGroup
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
url := scanner.Text()
wg.Add(1)
semaphore <- struct{}{} // Acquire semaphore
go func(targetURL string) {
defer wg.Done()
defer func() { <-semaphore }() // Release semaphore
checkFirebase(targetURL)
}(url)
}
wg.Wait()
}
This pattern solves a critical problem: launching unlimited goroutines would exhaust system resources and trigger rate limiting. The buffered channel acts as a counting semaphore—when it's full, the main goroutine blocks until a worker completes and releases a slot. The sync.WaitGroup ensures the program waits for all checks to complete before exiting.
The actual vulnerability check is elegantly simple. Firebase Realtime Databases expose their entire contents at the /.json endpoint when security rules permit public reads. The tool makes an HTTP GET request with a configured timeout:
func checkFirebase(baseURL string) {
client := &http.Client{
Timeout: 10 * time.Second,
}
targetURL := strings.TrimSuffix(baseURL, "/") + "/.json"
resp, err := client.Get(targetURL)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", au.Red("ERROR"), err)
return
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
body, _ := ioutil.ReadAll(resp.Body)
if len(body) > 2 { // More than just "{}"
fmt.Println(au.Green("FOUND:"), targetURL)
saveResult(baseURL, body)
}
}
}
The status code check is the vulnerability detection mechanism. A 200 OK response with substantial JSON content indicates misconfigured security rules. Properly secured Firebase databases return 401 Unauthorized or 403 Forbidden when accessed without credentials. The tool filters out empty databases (just {}) because while technically exposed, they contain no sensitive data.
Output handling demonstrates practical security tooling design. Rather than dumping everything to stdout, discovered databases are saved to individual JSON files named after the Firebase project ID. This approach enables post-scan analysis without scrolling through terminal output:
func saveResult(url string, data []byte) {
projectID := extractProjectID(url)
filename := fmt.Sprintf("firebase_%s_%d.json", projectID, time.Now().Unix())
if err := ioutil.WriteFile(filename, data, 0644); err != nil {
fmt.Fprintf(os.Stderr, "Failed to save %s: %v\n", filename, err)
}
}
The Aurora library provides colored terminal output, making it trivial to spot successful finds during scans. This UX consideration matters when you're monitoring a scan that might run for hours against thousands of targets. Green "FOUND" messages immediately draw attention to actionable results.
Performance characteristics are impressive for such simple code. With default concurrency of 10 workers, the scanner can process hundreds of URLs per minute, limited primarily by network latency and Firebase's response times rather than CPU or memory. Increasing concurrency to 50-100 workers yields near-linear speedup until you hit rate limiting or network bandwidth constraints.
Gotcha
The tool's simplicity is both its strength and weakness. There's zero error resilience—network timeouts, DNS failures, and transient errors result in targets being skipped with no retry mechanism. When scanning thousands of URLs, you'll inevitably miss some vulnerable databases due to temporary network issues. The tool provides no way to resume interrupted scans or track which URLs have been processed.
Rate limiting will bite you on large scans. Firebase implements per-IP rate limits, and aggressive concurrent scanning can trigger these protections, resulting in temporary bans or throttling. The tool has no built-in backoff strategy or request spacing. You'll need to manually tune concurrency levels and potentially rotate through proxy servers for serious reconnaissance work. Additionally, the output format lacks crucial metadata. Saved JSON files have no timestamps (beyond the filename), no record of the original URL structure, and no context about the scan parameters. If you're conducting security assessments that require detailed documentation, you'll need to build your own metadata tracking layer around this tool.
Verdict
Use if: You need a quick, disposable scanner for initial Firebase reconnaissance during bug bounty hunting or security assessments. It excels at rapid triage when you've extracted hundreds of Firebase URLs from GitHub repositories or mobile apps and want to quickly identify the low-hanging fruit. The tool is perfect for one-off scans where you don't need integration with existing security workflows. Skip if: You're building production security monitoring, need comprehensive Firebase security testing beyond read access, require detailed audit logs and reporting, or want integration with vulnerability management platforms. For serious security operations, invest in nuclei templates or custom scripts using the Firebase Admin SDK that offer proper authentication testing, write permission checks, and robust error handling. The lack of resumability and rate limit handling makes this unsuitable for large-scale continuous monitoring.