
Libreria Go per la generazione sicura di YAML e shell, che utilizza template sensibili alla sintassi per rilevare e bloccare attacchi di injection tramite annotazioni per dati affidabili.
Questo non è un prodotto ufficialmente supportato da Google.
Librerie sicure per costruzione per la produzione di formati come YAML, per sostituire librerie non consapevoli della sintassi come text/template e sprintf che sono a rischio di vulnerabilità di injection.
Poiché text/template non conosce la sintassi dei formati che produce, non offre alcuna protezione contro le vulnerabilità di injection.
Considera la seguente funzione produceConfig che utilizza text/template per generare YAML:
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)
}
}
Questo programma dimostra come un input addressee dannoso possa causare l'iniezione di nuove chiavi YAML nel risultato dell'esecuzione del template.
Con text/template, non verranno riscontrati errori quando ciò accade e l'output del programma sarà:
{ hello: safe }
{ hello: world, oops: true }
Passando invece da text/template a safetext/yamltemplate, l'iniezione sarebbe stata prevenuta, con l'output che sarebbe stato:
{ hello: safe }
Error: YAML Injection Detected
text/templateIl rilevamento delle injection viene applicato automaticamente quando si accede ai campi dei dati di input.
Può anche essere abilitato manualmente sul risultato di qualsiasi chiamata di funzione:
{{ RetrieveUntrustedData | ApplyInjectionDetection }}
La logica di injection può essere disabilitata su determinati campi applicando l'annotazione StructuralData:
{{ (StructuralData .x) }}
L'annotazione StructuralData è necessaria anche quando si passa un input a una funzione in cui l'input non deve essere modificato, ad esempio per eseguire una sorta di ricerca:
name: {{ readFile (StructuralData .pathToName) | ApplyInjectionDetection }}
Si consiglia di utilizzare appieno le funzionalità di text/template come espressioni condizionali, cicli range, ecc. per evitare l'annotazione StructuralData dove possibile. Ad esempio, invece di:
properties:
{{ (StructuralData .PropertiesYaml) }}
Considera:
properties:{{ range .Properties }}
- {{ . }}{{ end }}
yamltemplateL'intento di yamltemplate è garantire che, per impostazione predefinita, nessuna delle stringhe nei dati di input influisca sulla struttura dello YAML risultante (solo sui valori).
Ad esempio, il template seguente sarebbe compatibile con yamltemplate così com'è, prevenendo automaticamente qualsiasi injection dall'input Name:
name: {{.Name}}
Tuttavia, qualsiasi nodo del template che dovrebbe modificare la struttura dello YAML risultante, come l'inserimento di configurazione YAML arbitraria, dovrà essere annotato esplicitamente come StructuralData:
config: {{ (StructuralData .Config) }}
Un altro caso in cui è necessaria l'annotazione StructuralData è quando devi includere una mappa completa nella struttura YAML. L'uso del solo StructuralData potrebbe consentire il passaggio di injection tramite la chiave, quindi qui è necessario un ulteriore livello di validazione:
labels:
{{- range $key, $value := .Labels }}
{{ (StructuralData $key | MapKey) }}: {{ $value }}
{{- end }}
Il lato golang corrispondente potrebbe essere simile al seguente:
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))
yamltemplateYAML con chiavi duplicate. Le chiavi duplicate sono YAML non standard e non sono supportate da questa libreria. Ti preghiamo di rifattorizzare il tuo template YAML per rimuovere le chiavi duplicate. Ad esempio:
- project:
members: member-a
members: member-b
In:
- project:
members: member-b
shtemplateshtemplate è progettato per consentirti di generare script di shell con la garanzia che nessuna delle stringhe dei dati di input possa iniettare nuovi comandi o flag, senza annotazioni esplicite.
Ad esempio, uno script template progettato per stampare una sola stringa non verrà renderizzato se quella stringa inietta un nuovo comando `./evil`:
echo "{{ .addressee }}"
Per consentire esplicitamente a una stringa di input di contenere nuovi comandi non presenti nella stringa del template, si può usare l'annotazione StructuralData:
{{ (StructuralData .commands) }}
Anche i flag (argomenti che iniziano con -) sono vietati per impostazione predefinita. Ad esempio, il template seguente non verrà renderizzato se Filename è --interactive:
git add {{ .Filename }}
Per consentire esplicitamente a una stringa di input passata come argomento di un comando di essere un flag, si può usare l'annotazione AllowFlags:
git add {{ (AllowFlags .FilenameOrGitAddFlag) }}
Anche più argomenti da un'unica stringa di input sono vietati per impostazione predefinita. Questa costruzione dovrebbe invece essere implementata usando un array e l'espressione :
text/templateLogica di escaping al di fuori del sistema di template. Dovresti invece annotare la logica di escaping nel tuo template (ad es.: .UntrustedField | escape).
Formati parziali. Le librerie sono progettate per essere utilizzate per generare file completi. Se generi segmenti e poi li concateni, dovresti invece spostare questa logica nel sistema di template stesso (usando costrutti come if o range).
Funzioni con effetti collaterali. Le librerie funzionano eseguendo più esecuzioni di template, quindi se registri funzioni che hanno effetti collaterali, ciò potrebbe causare comportamenti imprevisti (ad es.: id: {{ AllocateID }}).
shsprintfshsprintf è progettato per consentirti di generare script di shell con la garanzia che nessuna delle stringhe dei dati di input possa iniettare nuovi comandi o flag, indipendentemente da un potenziale escaping errato. Vedi l'esempio seguente, che restituirà l'errore shsprintf.ErrShInjection invece dello script con un comando iniettato:
message := "`whoami`"
result, err := shsprintf.Sprintf("git commit -m %s", message)
shsprintf.Sprintf aggiunge un valore di ritorno di errore rispetto a fmt.Sprintf, ma per il resto l'API è la stessa. shsprintf.MustSprintf è disponibile per i casi in cui il panico è accettabile.
shsprintf include una funzione di escaping il cui uso è raccomandato:
message := "`whoami`"
result := shsprintf.MustSprintf("git commit -m %s", shsprintf.EscapeDefaultContext(message))
A differenza di text/template, non ci sono annotazioni speciali. Se ad esempio devi passare più argomenti, ciò dovrebbe essere fatto modificando la stringa del formato:
files := []any{ "file1", "file2", "file3" }
result, err := shsprintf.Sprintf("cat" + strings.Repeat(" %s", len(files)), files...)
Puoi combinare yamltemplate con shprintf. Considera il seguente template YAML cloud-init:
---
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 }}
Valutando questo template con safetext/yamltemplate, verranno prevenute sia le injection di comandi shell che quelle YAML.
Per fare ciò, devi configurare il lato golang in questo modo:
tmp:= addons.WithShsprintf(template.New("something"))
tmpl := template.Must(tmp.Parse(yamlTemplate))
rangels {{ range .Paths }}{{.}} {{end}}