Overview
The CVE-2026-39340 incident demonstrates how a SQL injection flaw in a real-world web app can expose sensitive data even when input is filtered for HTML. In ChurchCRM, before version 7.1.0, user-supplied values from Name and Description fields were concatenated directly into raw INSERT and UPDATE queries without proper SQL escaping, enabling authenticated non-admin users with MenuOptions to perform time-based blind injections and exfiltrate data such as password hashes. The root cause was a sanitation step that removed HTML but did not neutralize SQL control characters, coupled with building SQL statements via string concatenation. This is a canonical example of CWE-89 (SQL Injection) and highlights why relying on escaping or HTML-sanitization alone is insufficient for SQL correctness and security in web apps.
Affected Versions
ChurchCRM < 7.1.0 (CVE-2026-39340)
Code Fix Example
Go (Gin) API Security Remediation
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
func initDB() {
var err error
// Replace with real DSN for your environment
db, err = sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
if err != nil {
log.Fatal(err)
}
if err = db.Ping(); err != nil {
log.Fatal(err)
}
}
type Input struct {
Name string
Description string
}
// Vulnerable: concatenates input directly into SQL (demonstration only)
func vulnerableCreatePropertyType(c *gin.Context) {
name := c.PostForm("name")
desc := c.PostForm("description")
sqlStr := fmt.Sprintf("INSERT INTO property_types (name, description) VALUES ('%s', '%s')", name, desc)
if _, err := db.Exec(sqlStr); err != nil {
c.String(http.StatusInternalServerError, `db error`)
return
}
c.Status(http.StatusCreated)
}
// Fixed: parameterized query to prevent SQL injection
func fixedCreatePropertyType(c *gin.Context) {
name := c.PostForm("name")
desc := c.PostForm("description")
sqlStr := `INSERT INTO property_types (name, description) VALUES (?, ?)`
if _, err := db.Exec(sqlStr, name, desc); err != nil {
c.String(http.StatusInternalServerError, `db error`)
return
}
c.Status(http.StatusCreated)
}
func main() {
initDB()
r := gin.Default()
r.POST("/vulnerable", vulnerableCreatePropertyType)
r.POST("/fixed", fixedCreatePropertyType)
if err := r.Run(":8080"); err != nil {
log.Fatal(err)
}
}