Insecure Deserialization in Cockroachdb
How Insecure Deserialization Manifests in Cockroachdb
Insecure deserialization in Cockroachdb environments typically occurs when applications accept serialized data from untrusted sources and deserialize it without proper validation. This vulnerability can lead to remote code execution, denial of service, or privilege escalation.
Cockroachdb itself uses protobuf for internal serialization, but applications built on top of Cockroachdb often implement their own serialization mechanisms. Common attack vectors include:
- Database connection strings containing serialized objects
- API endpoints accepting serialized payloads that interact with Cockroachdb
- ORM configurations that deserialize user-controlled data
- Custom serialization formats used for caching or session storage
A typical Cockroachdb application vulnerable to insecure deserialization might look like this:
package main
import (
"database/sql"
"fmt"
"github.com/cockroachdb/cockroach-go/v2/crdb"
"io/ioutil"
"net/http"
_ "github.com/lib/pq"
)
func vulnerableHandler(w http.ResponseWriter, r *http.Request) {
// Accept serialized data from client
data, _ := ioutil.ReadAll(r.Body)
// Deserialize without validation - DANGEROUS
var config map[string]interface{}
json.Unmarshal(data, &config)
// Use deserialized data to build connection string
connStr := fmt.Sprintf("postgresql://root@%s:%s/%s?sslmode=disable",
config["host"], config["port"], config["dbname"])
db, _ := sql.Open("postgres", connStr)
defer db.Close()
// Execute query - attacker could control SQL through deserialization
rows, _ := db.Query("SELECT * FROM users WHERE id = $1", config["userId"])
defer rows.Close()
}
This pattern is dangerous because an attacker can craft malicious serialized payloads that execute arbitrary SQL when deserialized. The vulnerability becomes more severe when combined with Cockroachdb's distributed architecture, potentially allowing lateral movement across nodes.
Cockroachdb-Specific Detection
Detecting insecure deserialization in Cockroachdb applications requires examining both the application code and the database interactions. Here are specific detection strategies:
Static Code Analysis
Look for patterns where serialized data flows into database operations:
# Search for dangerous deserialization patterns
grep -r "json\.Unmarshal\|gob\.Decode\|yaml\.Unmarshal" ./app
# Find database operations using deserialized data
grep -r "sql\.Open\|db\.Query\|db\.Exec" ./app |
xargs grep -l "config\|data\|payload"
Runtime Detection with middleBrick
middleBrick's black-box scanning can identify insecure deserialization vulnerabilities by testing API endpoints that interact with Cockroachdb:
# Scan a Cockroachdb-connected API endpoint
middlebrick scan https://api.example.com/users
# Check for deserialization-specific findings
middlebrick report --show-details
middleBrick tests for 12 security categories including input validation and data exposure, which catch deserialization issues. For Cockroachdb-specific contexts, it examines:
- Authentication bypass attempts through serialized payloads
- SQL injection patterns embedded in serialized data
- Property authorization violations when deserialized objects access restricted data
Database Log AnalysisMonitor Cockroachdb logs for unusual query patterns that might indicate deserialization attacks:
-- Look for unexpected query patterns
SELECT
query,
count(*) as execution_count,
avg(duration) as avg_duration
FROM
crdb_internal.exec_stats
WHERE
query LIKE '%unexpected_pattern%'
OR query LIKE '%suspicious_function%'
GROUP BY
query
ORDER BY
execution_count DESC
LIMIT 50;
Network Traffic Analysis
Monitor for serialized data patterns in API traffic:
# Detect suspicious serialized data in HTTP requests
tcpdump -A -s 0 'port 8080' |
grep -E "(\xAC\xED\x00\x05|\x7B\"type\"|YAML:)
Cockroachdb-Specific Remediation
Remediating insecure deserialization in Cockroachdb applications requires a defense-in-depth approach. Here are specific fixes:
Input Validation and Type Safety
Always validate and sanitize deserialized data before use:
package main
import (
"database/sql"
"fmt"
"github.com/cockroachdb/cockroach-go/v2/crdb"
"io/ioutil"
"net/http"
_ "github.com/lib/pq"
"github.com/go-playground/validator/v10"
)
type UserQuery struct {
Host string `json:"host" validate:"hostname|ip"`
Port int `json:"port" validate:"min=1,max=65535"`
DBName string `json:"dbname" validate:"alphanum"`
UserID int `json:"userId" validate:"min=1"`
}
func secureHandler(w http.ResponseWriter, r *http.Request) {
data, _ := ioutil.ReadAll(r.Body)
var query UserQuery
if err := json.Unmarshal(data, &query); err != nil {
http.Error(w, "Invalid data format", 400)
return
}
// Validate input
validate := validator.New()
if err := validate.Struct(query); err != nil {
http.Error(w, "Invalid input parameters", 400)
return
}
// Use parameterized queries - NOT string concatenation
connStr := fmt.Sprintf("postgresql://root@%s:%d/%s?sslmode=disable",
query.Host, query.Port, query.DBName)
db, _ := sql.Open("postgres", connStr)
defer db.Close()
// Always use parameterized queries
rows, _ := db.Query("SELECT * FROM users WHERE id = $1", query.UserID)
defer rows.Close()
}
Cockroachdb-Specific Security Features
Leverage Cockroachdb's built-in security mechanisms:
-- Create least-privilege roles
CREATE ROLE api_user WITH LOGIN PASSWORD 'secure_password';
GRANT SELECT ON users TO api_user;
-- Use row-level security for data isolation
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_isolation ON users
FOR SELECT USING (user_id = current_setting('app.current_user_id')::int);
-- Enable audit logging for suspicious queries
ALTER TABLE users EXPERIMENTAL AUDIT SET READ WRITE;
-- Use connection pooling with timeouts
SET CLUSTER SETTING server.http.idle_timeout = '30s';
SET CLUSTER SETTING server.http.read_timeout = '10s';
Safe Serialization Libraries
Use safe serialization formats and libraries:
package main
import (
"github.com/json-iterator/go"
"github.com/Shopify/sarama"
)
// Use JSON-iterator for safer JSON parsing
var json = jsoniter.ConfigCompatibleWithStandardLibrary
// Use protocol buffers for structured data
type UserData struct {
ID int64 `json:"id" protobuf:"varint,1,opt,name=id"`
Username string `json:"username" protobuf:"bytes,2,opt,name=username"`
}
// Implement safe deserialization
func safeDeserialize(data []byte) (*UserData, error) {
var user UserData
if err := json.Unmarshal(data, &user); err != nil {
return nil, fmt.Errorf("deserialization failed: %v", err)
}
// Additional validation
if user.ID <= 0 || len(user.Username) == 0 {
return nil, fmt.Errorf("invalid deserialized data")
}
return &user, nil
}
Runtime Protection
Implement runtime checks and monitoring:
package main
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
"time"
)
var (
deserializationFailures = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "deserialization_failures_total",
Help: "Total number of deserialization failures",
},
[]string{"endpoint", "reason"},
)
suspiciousQueries = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "suspicious_queries_total",
Help: "Total number of suspicious queries detected",
},
[]string{"query_type", "table"},
)
)
func init() {
prometheus.MustRegister(deserializationFailures)
prometheus.MustRegister(suspiciousQueries)
}
func monitorHandler(w http.ResponseWriter, r *http.Request) {
// Monitor for deserialization patterns
if containsSuspiciousPatterns(r.Body) {
deserializationFailures.WithLabelValues(r.URL.Path, "suspicious_pattern").Inc()
http.Error(w, "Suspicious data detected", 403)
return
}
// Monitor query patterns
if containsSQLInjectionPattern(r.Body) {
suspiciousQueries.WithLabelValues("injection_attempt", "users").Inc()
http.Error(w, "SQL injection attempt detected", 403)
return
}
w.WriteHeader(200)
}
func containsSuspiciousPatterns(data []byte) bool {
// Check for common deserialization attack patterns
patterns := [][]byte{
[]byte("ACED000549"), // Java serialization magic number
[]byte("7B22747970"), // JSON with suspicious structure
}
for _, pattern := range patterns {
if containsPattern(data, pattern) {
return true
}
}
return false
}