
Go-Bibliothek für sichere YAML- und Shell-Generierung, die syntaxbewusste Vorlagen verwendet, um Injection-Angriffe über Annotationen für vertrauenswürdige Daten zu erkennen und zu blockieren.
Dies ist kein offiziell unterstütztes Google-Produkt.
Safe-by-construction-Bibliotheken zur Erzeugung von Formaten wie YAML, um syntaxunbewusste Bibliotheken wie text/template und sprintf zu ersetzen, die dem Risiko von Injection-Schwachstellen ausgesetzt sind.
Da text/template die von ihm erzeugten Formate nicht syntaxbewusst verarbeitet, bietet es keinen Schutz gegen Injection-Schwachstellen.
Betrachten Sie die folgende Funktion produceConfig, die text/template zum Erzeugen von YAML verwendet:
package main
import (
"bytes"
"fmt"
"text/template"
)
func produceConfig(params any) (error, string) {
tmpl, _ := template.New("test").Parse("{ hello: {{ .addressee }} }")
var buf bytes.Buffer
err := tmpl.Execute(&buf, params)
if err != nil {
return err, ""
}
return nil, buf.String()
}
func main() {
goodReplacements := map[string]interface{}{
"addressee": "safe",
}
err, config := produceConfig(goodReplacements)
if err == nil {
fmt.Println(config)
} else {
fmt.Printf("Error: %v\n", err)
}
badReplacements := map[string]interface{}{
"addressee": "world, oops: true",
}
err, config = produceConfig(badReplacements)
if err == nil {
fmt.Println(config)
} else {
fmt.Printf("Error: %v\n", err)
}
}
Dieses Programm zeigt, wie eine böswillige addressee-Eingabe zur Injection neuer YAML-Schlüssel im Ergebnis der Template-Ausführung führen kann.
Mit text/template werden dabei keine Fehler auftreten, und die Programmausgabe sieht wie folgt aus:
{ hello: safe }
{ hello: world, oops: true }
Wenn man stattdessen von text/template zu safetext/yamltemplate wechselt, wäre die Injection verhindert worden, wobei die Ausgabe stattdessen wie folgt aussähe:
{ hello: safe }
Error: YAML Injection Detected
text/template-ErsetzungenDie Injection-Erkennung wird automatisch angewendet, wenn auf Eingabedatenfelder zugegriffen wird.
Sie kann auch manuell für das Ergebnis eines beliebigen Funktionsaufrufs aktiviert werden:
{{ RetrieveUntrustedData | ApplyInjectionDetection }}
Die Injection-Logik kann für bestimmte Felder deaktiviert werden, indem die Annotation StructuralData angewendet wird:
{{ (StructuralData .x) }}
Die Annotation StructuralData wird auch benötigt, wenn eine Eingabe an eine Funktion übergeben wird, bei der die Eingabe nicht verändert werden soll, beispielsweise für eine Art Nachschlageoperation:
name: {{ readFile (StructuralData .pathToName) | ApplyInjectionDetection }}
Es wird empfohlen, die Funktionen von text/template wie bedingte Ausdrücke, range-Schleifen usw. voll auszuschöpfen, um die Annotation StructuralData nach Möglichkeit zu vermeiden. Anstatt beispielsweise:
properties:
{{ (StructuralData .PropertiesYaml) }}
Betrachten Sie:
properties:{{ range .Properties }}
- {{ . }}{{ end }}
yamltemplateDie Absicht von yamltemplate ist es, sicherzustellen, dass standardmäßig keiner der Strings in den Eingabedaten die Struktur des resultierenden YAML beeinflusst (nur die Werte).
Beispielsweise wäre die folgende Vorlage ohne Änderungen mit yamltemplate kompatibel und würde gleichzeitig automatisch jede Injection über die Eingabe Name verhindern:
name: {{.Name}}
Allerdings müssten alle Vorlagenknoten, die die resultierende YAML-Struktur verändern sollen, etwa durch Einfügen beliebiger YAML-Konfiguration, explizit als StructuralData annotiert werden:
config: {{ (StructuralData .Config) }}
Ein weiterer Fall, in dem die Annotation StructuralData benötigt wird, ist, wenn eine vollständige Map in die YAML-Struktur aufgenommen werden soll. Die alleinige Verwendung von StructuralData kann Injection-Angriffe über den Schlüssel durchlassen, daher ist hier eine zusätzliche Validierungsebene erforderlich:
labels:
{{- range $key, $value := .Labels }}
{{ (StructuralData $key | MapKey) }}: {{ $value }}
{{- end }}
Die entsprechende Golang-Seite könnte wie folgt aussehen:
func mapKeyFunc(data any) (string, error) {
if v, ok := data.(string); ok {
matched, err := regexp.MatchString(`^[a-zA-Z0-9/\-.]+$`, v)
if err != nil {
return "", err
}
if !matched {
return "", fmt.Errorf("invalid characters in the key: %v", v)
}
return v, nil
}
return "", errors.New("invalid input")
} ...
tmp:= template.New("something")
tmp.Funcs(map[string]any{"MapKey":mapKeyFunc})
tmpl := template.Must(tmp.Parse(yamlTemplate))
Sie können yamltemplate mit shprintf kombinieren. Betrachten Sie die folgende Cloud-Init-YAML-Vorlage:
---
write_files:
- path: /etc/nginx/refresh.sh
owner: root:root
permissions: 0755 # Don't forget the 0 (you are probably using octal...)
content: |
#!/bin/bash
set -euo pipefail
{{ shprintf `curl %s > /tmp/something` .userInput }}
Wenn diese Vorlage mit safetext/yamltemplate ausgewertet wird, werden sowohl Shell-Befehls- als auch YAML-Injection-Angriffe verhindert.
Dazu müssen Sie die Golang-Seite wie folgt einrichten:
tmp:= addons.WithShsprintf(template.New("something"))
tmpl := template.Must(tmp.Parse(yamlTemplate))
yamltemplateYAML mit doppelten Schlüsseln. Doppelte Schlüssel sind nicht standardkonformes YAML und werden von dieser Bibliothek nicht unterstützt. Bitte überarbeiten Sie Ihre YAML-Vorlage, um doppelte Schlüssel zu entfernen. Zum Beispiel:
- project:
members: member-a
members: member-b
In:
- project:
members: member-b
shtemplateshtemplate ist darauf ausgelegt, Ihnen die Erstellung von Shell-Skripten mit der Garantie zu ermöglichen, dass ohne explizite Annotation keiner der Eingabedaten-Strings neue Befehle oder Flags injizieren kann.
Beispielsweise schlägt ein Vorlagenskript, das nur einen String ausgeben soll, beim Rendern fehl, wenn dieser String einen neuen Befehl `./evil` injiziert:
echo "{{ .addressee }}"
Um einer Eingabezeichenfolge ausdrücklich zu erlauben, neue Befehle zu enthalten, die nicht aus der Vorlagenzeichenfolge stammen, kann die Annotation StructuralData verwendet werden:
{{ (StructuralData .commands) }}
Flags (Argumente, die mit - beginnen) sind standardmäßig ebenfalls verboten. Beispielsweise schlägt die folgende Vorlage beim Rendern fehl, wenn Filename gleich --interactive ist:
git add {{ .Filename }}
Um einer Eingabezeichenfolge, die als Befehlsargument übergeben wird, ausdrücklich zu erlauben, ein Flag zu sein, kann die Annotation AllowFlags verwendet werden:
git add {{ (AllowFlags .FilenameOrGitAddFlag) }}
Mehrere Argumente aus einer einzelnen Eingabezeichenfolge sind standardmäßig ebenfalls verboten. Dieses Konstrukt sollte stattdessen mithilfe eines Arrays und eines range-Ausdrucks umgesetzt werden:
ls {{ range .Paths }}{{.}} {{end}}
text/template-Ersetzungen.UntrustedField | escape).