
सुरक्षित YAML और shell निर्माण के लिए Go लाइब्रेरी, जो सिंटैक्स-जागरूक टेम्पलेट्स का उपयोग करके भरोसेमंद डेटा के एनोटेशन के माध्यम से इंजेक्शन हमलों का पता लगाती और रोकती है।
यह कोई आधिकारिक रूप से समर्थित Google उत्पाद नहीं है।
YAML जैसे प्रारूप बनाने के लिए सुरक्षित-निर्माण (safe-by-construction) लाइब्रेरीज़, जो text/template और sprintf जैसी सिंटैक्स-अनभिज्ञ लाइब्रेरीज़ को प्रतिस्थापित करने के लिए हैं, जो इंजेक्शन भेद्यताओं के जोखिम में हैं।
चूँकि text/template अपने द्वारा उत्पन्न किए जाने वाले प्रारूपों के सिंटैक्स से अवगत नहीं है, यह इंजेक्शन भेद्यताओं के विरुद्ध कोई सुरक्षा प्रदान नहीं करता है।
निम्नलिखित produceConfig फ़ंक्शन पर विचार करें जो YAML उत्पन्न करने के लिए text/template का उपयोग करता है:
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)
}
}
यह प्रोग्राम दर्शाता है कि कैसे एक दुर्भावनापूर्ण addressee इनपुट टेम्पलेट निष्पादन परिणाम में नई YAML कुंजियों का इंजेक्शन पैदा कर सकता है।
text/template के साथ, ऐसा होने पर कोई त्रुटि सामने नहीं आएगी, और प्रोग्राम का आउटपुट होगा:
{ hello: safe }
{ hello: world, oops: true }
इसके बजाय text/template से safetext/yamltemplate पर स्विच करने से, इंजेक्शन को रोका जा सकता था, और आउटपुट इसके बजाय होगा:
{ hello: safe }
Error: YAML Injection Detected
text/template प्रतिस्थापनों के लिए निर्देशइनपुट डेटा फ़ील्ड्स तक पहुँचने पर इंजेक्शन का पता लगाना स्वचालित रूप से लागू होता है।
इसे किसी भी फ़ंक्शन कॉल के परिणाम पर मैन्युअल रूप से भी सक्षम किया जा सकता है:
{{ RetrieveUntrustedData | ApplyInjectionDetection }}
कुछ फ़ील्ड्स पर StructuralData एनोटेशन लागू करके इंजेक्शन लॉजिक को अक्षम किया जा सकता है:
{{ (StructuralData .x) }}
StructuralData एनोटेशन की आवश्यकता तब भी होती है जब किसी इनपुट को किसी फ़ंक्शन में पास किया जा रहा हो जहाँ इनपुट को परिवर्तित (mutate) नहीं किया जाना चाहिए, जैसे कि किसी प्रकार की लुकअप करना:
name: {{ readFile (StructuralData .pathToName) | ApplyInjectionDetection }}
यह अनुशंसा की जाती है कि जहाँ संभव हो StructuralData एनोटेशन से बचने के लिए text/template की सुविधाओं जैसे कंडीशनल एक्सप्रेशन, range लूप आदि का पूरा उपयोग करें। उदाहरण के लिए, इसके बजाय:
properties:
{{ (StructuralData .PropertiesYaml) }}
इस पर विचार करें:
properties:{{ range .Properties }}
- {{ . }}{{ end }}
yamltemplateyamltemplate का उद्देश्य यह सुनिश्चित करना है कि डिफ़ॉल्ट रूप से इनपुट डेटा में से कोई भी स्ट्रिंग परिणामी YAML की संरचना (केवल मान) को प्रभावित न करे।
उदाहरण के लिए, नीचे दिया गया टेम्पलेट yamltemplate के साथ वैसे ही संगत होगा, साथ ही Name इनपुट से किसी भी इंजेक्शन को स्वचालित रूप से रोकेगा:
name: {{.Name}}
हालाँकि, कोई भी टेम्पलेट नोड जिनसे अपेक्षा की जाती है कि वे परिणामी YAML संरचना को बदलेंगे, जैसे कि मनमाना YAML कॉन्फ़िग डालना, उन्हें स्पष्ट रूप से StructuralData के रूप में एनोटेट करने की आवश्यकता होगी:
config: {{ (StructuralData .Config) }}
StructuralData एनोटेशन की आवश्यकता का एक और मामला तब होगा जब आपको yaml संरचना में एक पूरा मैप शामिल करने की आवश्यकता हो। केवल StructuralData का उपयोग करने से कुंजी के माध्यम से इंजेक्शन पास हो सकता है, इसलिए हमें यहाँ सत्यापन की एक अतिरिक्त परत की आवश्यकता है:
labels:
{{- range $key, $value := .Labels }}
{{ (StructuralData $key | MapKey) }}: {{ $value }}
{{- end }}
संबंधित golang पक्ष कुछ इस तरह दिख सकता है:
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))
yamltemplate के लिए असमर्थित उपयोग-मामलेडुप्लिकेट कुंजियों वाला YAML। डुप्लिकेट कुंजियाँ गैर-मानक YAML हैं, और इस लाइब्रेरी द्वारा समर्थित नहीं हैं। कृपया डुप्लिकेट कुंजियों को हटाने के लिए अपने YAML टेम्पलेट को रिफैक्टर करें। उदाहरण के लिए:
- project:
members: member-a
members: member-b
इसे:
- project:
members: member-b
shtemplateshtemplate आपको शेल स्क्रिप्ट उत्पन्न करने की अनुमति देने के लिए डिज़ाइन किया गया है, जिसमें यह गारंटी होती है कि इनपुट डेटा स्ट्रिंग्स में से कोई भी, बिना स्पष्ट एनोटेशन के, नई कमांड या फ़्लैग इंजेक्ट नहीं कर पाएगी।
उदाहरण के लिए, केवल एक स्ट्रिंग प्रिंट करने के लिए डिज़ाइन की गई टेम्पलेट स्क्रिप्ट रेंडर करने में विफल हो जाएगी यदि वह स्ट्रिंग एक नई कमांड `./evil` इंजेक्ट करती है:
echo "{{ .addressee }}"
किसी इनपुट स्ट्रिंग को टेम्पलेट स्ट्रिंग से नहीं आने वाली नई कमांड शामिल करने की स्पष्ट अनुमति देने के लिए, StructuralData एनोटेशन का उपयोग किया जा सकता है:
{{ (StructuralData .commands) }}
फ़्लैग (- से शुरू होने वाले तर्क) भी डिफ़ॉल्ट रूप से निषिद्ध हैं। उदाहरण के लिए, यदि Filename --interactive है तो नीचे दिया गया टेम्पलेट रेंडर करने में विफल हो जाएगा:
git add {{ .Filename }}
कमांड तर्क के रूप में पास किए गए इनपुट स्ट्रिंग को स्पष्ट रूप से फ़्लैग होने की अनुमति देने के लिए, AllowFlags एनोटेशन का उपयोग किया जा सकता है:
git add {{ (AllowFlags .FilenameOrGitAddFlag) }}
एकल इनपुट स्ट्रिंग से कई तर्क भी डिफ़ॉल्ट रूप से निषिद्ध हैं। इस संरचना को इसके बजाय एक ऐरे और range एक्सप्रेशन का उपयोग करके लागू किया जाना चाहिए:
text/template प्रतिस्थापनों के लिए असमर्थित उपयोग-मामलेटेम्पलेटिंग सिस्टम के बाहर एस्केपिंग लॉजिक। इसके बजाय, आपको एस्केपिंग लॉजिक को अपने टेम्पलेट में एनोटेट करना चाहिए (जैसे: .UntrustedField | escape)।
आंशिक प्रारूप। लाइब्रेरीज़ पूर्ण फ़ाइलें उत्पन्न करने के लिए उपयोग किए जाने हेतु डिज़ाइन की गई हैं। यदि आप सेगमेंट उत्पन्न करते हैं और फिर उन्हें एक साथ जोड़ते हैं, तो आपको इस लॉजिक को टेम्पलेटिंग सिस्टम में ही स्थानांतरित करना चाहिए (if या range जैसे कंस्ट्रक्ट्स का उपयोग करके)।
साइड इफेक्ट्स वाले फ़ंक्शन। लाइब्रेरीज़ कई टेम्पलेट निष्पादन करके काम करती हैं, इसलिए यदि आप ऐसे फ़ंक्शन पंजीकृत करते हैं जिनके साइड इफेक्ट हैं, तो यह अप्रत्याशित व्यवहार पैदा कर सकता है (जैसे: id: {{ AllocateID }})।
shsprintfshsprintf आपको शेल स्क्रिप्ट उत्पन्न करने की अनुमति देने के लिए डिज़ाइन किया गया है, जिसमें यह गारंटी होती है कि इनपुट डेटा स्ट्रिंग्स में से कोई भी, संभावित रूप से गलत एस्केपिंग के बावजूद, नई कमांड या फ़्लैग इंजेक्ट नहीं कर पाएगी। नीचे दिए गए उदाहरण को देखें, जो इंजेक्टेड कमांड वाली स्क्रिप्ट के बजाय shsprintf.ErrShInjection त्रुटि लौटाएगा:
message := "`whoami`"
result, err := shsprintf.Sprintf("git commit -m %s", message)
shsprintf.Sprintf fmt.Sprintf की तुलना में एक अतिरिक्त त्रुटि रिटर्न मान जोड़ता है, लेकिन API अन्यथा समान है। shsprintf.MustSprintf उन मामलों के लिए उपलब्ध है जहाँ panic स्वीकार्य है।
shsprintf एक एस्केपिंग फ़ंक्शन के साथ आता है जिसका उपयोग करने की अनुशंसा की जाती है:
message := "`whoami`"
result := shsprintf.MustSprintf("git commit -m %s", shsprintf.EscapeDefaultContext(message))
text/template के विपरीत, यहाँ कोई विशेष एनोटेशन नहीं हैं। उदाहरण के लिए, यदि आपको कई तर्क पास करने की आवश्यकता है, तो यह फॉर्मेट स्ट्रिंग को बदलकर किया जाना चाहिए:
files := []any{ "file1", "file2", "file3" }
result, err := shsprintf.Sprintf("cat" + strings.Repeat(" %s", len(files)), files...)
आप yamltemplate को shprintf के साथ जोड़ सकते हैं। निम्नलिखित cloud-init yaml टेम्पलेट पर विचार करें:
---
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 }}
इस टेम्पलेट को safetext/yamltemplate के साथ मूल्यांकन करने पर, शेल कमांड और YAML दोनों इंजेक्शन रोके जाएँगे।
ऐसा करने के लिए, आपको golang पक्ष को इस तरह सेट करना होगा:
tmp:= addons.WithShsprintf(template.New("something"))
tmpl := template.Must(tmp.Parse(yamlTemplate))
ls {{ range .Paths }}{{.}} {{end}}