Tighten opaque plugin content boundaries
This commit is contained in:
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
@@ -23,12 +22,6 @@ const (
|
||||
maxGameClientBridgeSessionLength = 4096
|
||||
)
|
||||
|
||||
var (
|
||||
gameClientBridgeAcronymBoundary = regexp.MustCompile(`([A-Z]+)([A-Z][a-z])`)
|
||||
gameClientBridgeCamelBoundary = regexp.MustCompile(`([a-z0-9])([A-Z])`)
|
||||
gameClientBridgeNonWord = regexp.MustCompile(`[^A-Za-z0-9]+`)
|
||||
)
|
||||
|
||||
func ValidateGameClientBridgeQueueRequest(request domain.GameClientBridgeQueueRequest) error {
|
||||
var violations []string
|
||||
violations = appendGameClientBridgeIdentifier(violations, "serverInstanceId", request.ServerInstanceID, true)
|
||||
@@ -185,13 +178,6 @@ func appendGameClientBridgeText(violations []string, field, value string, maximu
|
||||
if !utf8.ValidString(value) || strings.TrimSpace(value) != value || utf8.RuneCountInString(value) > maximum || containsControlCharacter(value) {
|
||||
violations = append(violations, field+" is invalid")
|
||||
}
|
||||
lowered := strings.ToLower(strings.TrimSpace(value))
|
||||
if containsUnsafeRuntimeSecret(value) || looksLikeRawHostPath(value) || hasUnsafeGameClientBridgeReference(lowered) || containsEmbeddedGameClientBridgeHostPath(lowered) {
|
||||
violations = append(violations, field+" contains unsafe connection or host material")
|
||||
}
|
||||
for _, reason := range unsafePluginStringReasons(value) {
|
||||
violations = append(violations, field+": "+reason)
|
||||
}
|
||||
return violations
|
||||
}
|
||||
|
||||
@@ -268,10 +254,6 @@ func validateGameClientBridgePayloadValue(field string, value any, depth int, bu
|
||||
violations = append(violations, field+" key "+fmt.Sprintf("%q", key)+" is invalid")
|
||||
continue
|
||||
}
|
||||
if unsafeGameClientBridgePayloadKey(key) {
|
||||
violations = append(violations, field+" contains forbidden key "+key)
|
||||
continue
|
||||
}
|
||||
violations = append(violations, validateGameClientBridgePayloadValue(field+"."+key, item, depth+1, budget)...)
|
||||
}
|
||||
return violations
|
||||
@@ -300,13 +282,6 @@ func validateGameClientBridgePayloadString(field, value string) []string {
|
||||
if containsControlCharacter(value) {
|
||||
violations = append(violations, field+" contains control characters")
|
||||
}
|
||||
for _, reason := range unsafePluginStringReasons(value) {
|
||||
violations = append(violations, field+": "+reason)
|
||||
}
|
||||
lowered := strings.ToLower(strings.TrimSpace(value))
|
||||
if containsUnsafeRuntimeSecret(value) || hasUnsafeGameClientBridgeReference(lowered) || containsEmbeddedGameClientBridgeHostPath(lowered) {
|
||||
violations = append(violations, field+" contains unsafe connection material")
|
||||
}
|
||||
return violations
|
||||
}
|
||||
|
||||
@@ -321,113 +296,3 @@ func validGameClientBridgePayloadKey(key string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func unsafeGameClientBridgePayloadKey(key string) bool {
|
||||
tokens := gameClientBridgePayloadKeyTokens(key)
|
||||
if len(tokens) == 0 {
|
||||
return true
|
||||
}
|
||||
normalized := strings.Join(tokens, "")
|
||||
for _, exact := range []string{
|
||||
"absolutepath", "apikey", "commandline", "componentkey", "credential", "credentials", "directsocket", "dsn", "hostpath", "password", "passwd", "rawpath", "rawsql", "runendpoint", "runsocket", "script", "secret", "sessiontoken", "shell", "socket", "sql", "statement", "terminalcommand",
|
||||
} {
|
||||
if normalized == strings.ReplaceAll(exact, " ", "") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if last := tokens[len(tokens)-1]; last == "password" || last == "passwd" || last == "secret" || last == "credential" || last == "credentials" || last == "dsn" {
|
||||
return true
|
||||
}
|
||||
for _, sequence := range [][]string{
|
||||
{"api", "key"},
|
||||
{"access", "key"},
|
||||
{"private", "key"},
|
||||
{"auth", "token"},
|
||||
{"access", "token"},
|
||||
{"client", "secret"},
|
||||
{"storage", "credential"},
|
||||
{"component", "key"},
|
||||
{"session", "token"},
|
||||
{"host", "path"},
|
||||
{"raw", "path"},
|
||||
{"absolute", "path"},
|
||||
{"file", "system", "path"},
|
||||
{"direct", "socket"},
|
||||
{"socket", "path"},
|
||||
{"socket", "address"},
|
||||
{"socket", "url"},
|
||||
{"socket", "endpoint"},
|
||||
{"run", "endpoint"},
|
||||
{"run", "url"},
|
||||
{"run", "socket"},
|
||||
{"run", "token"},
|
||||
{"run", "credential"},
|
||||
{"raw", "sql"},
|
||||
{"raw", "query"},
|
||||
{"sql", "text"},
|
||||
{"sql", "query"},
|
||||
{"sql", "statement"},
|
||||
{"arbitrary", "sql"},
|
||||
{"shell", "command"},
|
||||
{"shell", "script"},
|
||||
{"script", "body"},
|
||||
{"terminal", "command"},
|
||||
{"command", "line"},
|
||||
{"arbitrary", "shell"},
|
||||
} {
|
||||
if gameClientBridgeContainsSensitiveSequence(tokens, sequence) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsEmbeddedGameClientBridgeHostPath(value string) bool {
|
||||
for _, marker := range []string{"/etc/", "/var/", "/tmp/", "/home/", "/root/", "/private/", "/users/", "/volumes/", "/opt/", `:\\`} {
|
||||
if strings.Contains(value, marker) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func gameClientBridgeContainsSensitiveSequence(tokens, sequence []string) bool {
|
||||
for start := 0; start+len(sequence) <= len(tokens); start++ {
|
||||
matched := true
|
||||
for index, expected := range sequence {
|
||||
if tokens[start+index] != expected {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
end := start + len(sequence)
|
||||
if end == len(tokens) {
|
||||
return true
|
||||
}
|
||||
switch tokens[end] {
|
||||
case "address", "body", "content", "material", "path", "raw", "ref", "text", "url", "value":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func gameClientBridgePayloadKeyTokens(key string) []string {
|
||||
withAcronymBoundaries := gameClientBridgeAcronymBoundary.ReplaceAllString(key, `${1} ${2}`)
|
||||
withCamelBoundaries := gameClientBridgeCamelBoundary.ReplaceAllString(withAcronymBoundaries, `${1} ${2}`)
|
||||
return strings.Fields(strings.ToLower(gameClientBridgeNonWord.ReplaceAllString(withCamelBoundaries, " ")))
|
||||
}
|
||||
|
||||
func hasUnsafeGameClientBridgeReference(value string) bool {
|
||||
for _, fragment := range []string{
|
||||
"unix://", "tcp://", "mysql://", "postgres://", "postgresql://", "mongodb://", "redis://", "sqlite://", "sqlserver://", "mssql://", "odbc:", "secret://", "vault://", "env://", "http://127.", "https://127.", "http://localhost", "https://localhost",
|
||||
} {
|
||||
if strings.Contains(value, fragment) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user