Low severityNVD Advisory· Published May 6, 2025· Updated Apr 15, 2026
CVE-2025-46735
CVE-2025-46735
Description
Terraform WinDNS Provider allows users to manage their Windows DNS server resources through Terraform. A security issue has been found in Terraform WinDNS Provider before version 1.0.5. The windns_record resource did not sanitize the input variables. This could lead to authenticated command injection in the underlyding powershell command prompt. Version 1.0.5 contains a fix for the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/nrkno/terraform-provider-windnsGo | <= 1.0.4 | — |
Patches
1c76f69610c1bfix: better input validation
4 files changed · +107 −34
internal/dnshelper/dns.go+32 −9 modified@@ -62,22 +62,40 @@ func (r *Record) Id() string { } // NewDNSRecordFromResource returns a new Record struct populated from resource data -func NewDNSRecordFromResource(d *schema.ResourceData) *Record { +func NewDNSRecordFromResource(d *schema.ResourceData) (*Record, error) { var records []string recordsSet := d.Get("records").(*schema.Set) + recordType := d.Get("type").(string) for _, v := range recordsSet.List() { - records = append(records, SanitiseString(v.(string))) + sanitizedInput, err := SanitizeInputString(recordType, v.(string)) + if err != nil { + return nil, err + } + records = append(records, sanitizedInput) + } + + sanitizedZoneName, err := SanitiseTFInput(d, "zone_name") + if err != nil { + return nil, err + } + sanitizedHostName, err := SanitiseTFInput(d, "name") + if err != nil { + return nil, err + } + sanitizedRecordType, err := SanitiseTFInput(d, "type") + if err != nil { + return nil, err } return &Record{ - ZoneName: SanitiseTFInput(d, "zone_name"), - HostName: SanitiseTFInput(d, "name"), - RecordType: SanitiseTFInput(d, "type"), + ZoneName: sanitizedZoneName, + HostName: sanitizedHostName, + RecordType: sanitizedRecordType, CreatePtr: d.Get("create_ptr").(bool), // TTL: d.Get("ttl").(int64), Records: records, - } + }, nil } func GetDNSRecordFromId(ctx context.Context, conf *config.ProviderConf, id string) (*Record, error) { @@ -175,7 +193,11 @@ func (r *Record) Update(ctx context.Context, conf *config.ProviderConf, changes expectedRecords := changes["records"].(*schema.Set) for _, v := range expectedRecords.List() { - records = append(records, SanitiseString(v.(string))) + sanitizedInput, err := SanitizeInputString(existing.RecordType, v.(string)) + if err != nil { + return err + } + records = append(records, sanitizedInput) } toAdd, toRemove := diffRecordLists(records, existing.Records) @@ -214,7 +236,7 @@ func (r *Record) addRecordData(conf *config.ProviderConf, recordData string) err } else if r.RecordType == RecordTypeAAAA { cmd = fmt.Sprintf("%s -IPv6Address %s", cmd, strings.ToLower(recordData)) } else if r.RecordType == RecordTypeTXT { - cmd = fmt.Sprintf("%s -DescriptiveText %s", cmd, recordData) + cmd = fmt.Sprintf("%s -DescriptiveText \"%s\"", cmd, recordData) } else if r.RecordType == RecordTypePTR { cmd = fmt.Sprintf("%s -PtrDomainName %s", cmd, recordData) } else if r.RecordType == RecordTypeCNAME { @@ -249,7 +271,8 @@ func (r *Record) addRecordData(conf *config.ProviderConf, recordData string) err } func (r *Record) removeRecordData(conf *config.ProviderConf, recordData string) error { - cmd := fmt.Sprintf("Remove-DnsServerResourceRecord -Force -ZoneName %s -RRType %s -Name %s -RecordData %s", r.ZoneName, r.RecordType, r.HostName, recordData) + cmd := fmt.Sprintf("Remove-DnsServerResourceRecord -Force -ZoneName %s -RRType %s -Name %s -RecordData \"%s\"", r.ZoneName, r.RecordType, r.HostName, recordData) + conn, err := conf.AcquireSshClient() if err != nil { return fmt.Errorf("while acquiring ssh client: %s", err)
internal/dnshelper/dnshelper.go+27 −17 modified@@ -3,30 +3,40 @@ package dnshelper import ( + "fmt" + "regexp" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func SanitiseTFInput(d *schema.ResourceData, key string) string { - return SanitiseString(d.Get(key).(string)) +var recordInputPattern = regexp.MustCompile(`^[a-zA-Z0-9:.\-_]+$`) + +func SanitizeInputString(recordType string, input string) (string, error) { + if recordType == "TXT" { + if len(input) > 255 { + return "", fmt.Errorf("TXT record can only be 255 characters long") + } + return escapePowerShellInput(input), nil + } + + if recordInputPattern.MatchString(input) { + return input, nil + } + return "", fmt.Errorf("invalid characters detected in input: %s", input) +} + +func SanitiseTFInput(d *schema.ResourceData, key string) (string, error) { + return SanitizeInputString(d.Get("type").(string), d.Get(key).(string)) } -func SanitiseString(key string) string { - cleanupReplacer := strings.NewReplacer( +func escapePowerShellInput(input string) string { + replacer := strings.NewReplacer( "`", "``", - `"`, "`\"", - "$", "`$", - "\x00", "`0", - "\x07", "`a", - "\x08", "`b", - "\x1f", "`e", - "\x0c", "`f", - "\n", "`n", - "\r", "`r", - "\t", "`t", - "\v", "`v", + ";", "`;", + "&", "`&", + "(", "`(", + ")", "`)", ) - out := cleanupReplacer.Replace(key) - return out + return replacer.Replace(input) }
internal/provider/resource_win_dns_record.go+15 −5 modified@@ -76,7 +76,10 @@ func resourceDNSRecord() *schema.Resource { } func resourceDNSRecordCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - record := dnshelper.NewDNSRecordFromResource(d) + record, err := dnshelper.NewDNSRecordFromResource(d) + if err != nil { + return diag.Errorf("error when mapping input data: %s", err) + } id, err := record.Create(meta.(*config.ProviderConf)) if err != nil { @@ -112,7 +115,10 @@ func resourceDNSRecordRead(ctx context.Context, d *schema.ResourceData, meta int } func resourceDNSRecordUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - record := dnshelper.NewDNSRecordFromResource(d) + record, err := dnshelper.NewDNSRecordFromResource(d) + if err != nil { + return diag.Errorf("error when mapping input data: %s", err) + } keys := []string{"records"} changes := make(map[string]interface{}) for _, key := range keys { @@ -121,7 +127,7 @@ func resourceDNSRecordUpdate(ctx context.Context, d *schema.ResourceData, meta i } } - err := record.Update(ctx, meta.(*config.ProviderConf), changes) + err = record.Update(ctx, meta.(*config.ProviderConf), changes) if err != nil { return diag.Errorf("error while updating record with id %q: %s", d.Id(), err) } @@ -132,8 +138,12 @@ func resourceDNSRecordDelete(ctx context.Context, d *schema.ResourceData, meta i if d.Id() == "" { return nil } - record := dnshelper.NewDNSRecordFromResource(d) - err := record.Delete(meta.(*config.ProviderConf)) + record, err := dnshelper.NewDNSRecordFromResource(d) + if err != nil { + return diag.Errorf("error when mapping input data: %s", err) + } + + err = record.Delete(meta.(*config.ProviderConf)) if err != nil { return diag.Errorf("error while deleting a record object with id %q: %s", d.Id(), err) }
internal/provider/resource_win_dns_record_test.go+33 −3 modified@@ -5,6 +5,7 @@ package provider import ( "context" "fmt" + "regexp" "strings" "testing" @@ -85,7 +86,7 @@ resource "windns_record" "r1" { name = var.windns_record_name zone_name = "example.com" type = "TXT" - records = ["TxTdAtA"] + records = ["TxTdATa9 &!#$%&'()*+,-./:;<=>?@[]^_{|}~"] } ` @@ -150,6 +151,17 @@ resource "windns_record" "r1" { } ` +const testAccResourceDNSRecordConfigIllegalCharacter = ` +variable "windns_record_name" {} + +resource "windns_record" "r1" { + name = var.windns_record_name + zone_name = "example.com;" + type = "CNAME" + records = ["cname.example.com"] +} +` + func TestAccResourceDNSRecord_BasicPTR(t *testing.T) { envVars := []string{"TF_VAR_windns_record_name"} @@ -282,13 +294,13 @@ func TestAccResourceDNSRecord_BasicTXT(t *testing.T) { PreCheck: func() { testAccPreCheck(t, envVars) }, ProviderFactories: testAccProviderFactories, CheckDestroy: resource.ComposeTestCheckFunc( - testAccResourceDNSRecordExists("windns_record.r1", []string{"TxTdAtA"}, dnshelper.RecordTypeTXT, false), + testAccResourceDNSRecordExists("windns_record.r1", []string{"TxTdATa9 &!#$%&'()*+,-./:;<=>?@[]^_{|}~"}, dnshelper.RecordTypeTXT, false), ), Steps: []resource.TestStep{ { Config: testAccResourceDNSRecordConfigBasicTXT, Check: resource.ComposeTestCheckFunc( - testAccResourceDNSRecordExists("windns_record.r1", []string{"TxTdAtA"}, dnshelper.RecordTypeTXT, true), + testAccResourceDNSRecordExists("windns_record.r1", []string{"TxTdATa9 &!#$%&'()*+,-./:;<=>?@[]^_{|}~"}, dnshelper.RecordTypeTXT, true), ), }, { @@ -396,6 +408,24 @@ func TestAccResourceDNSRecord_CNAME(t *testing.T) { }) } +func TestAccResourceDNSRecord_IllegalCharacter(t *testing.T) { + envVars := []string{"TF_VAR_windns_record_name"} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t, envVars) }, + ProviderFactories: testAccProviderFactories, + CheckDestroy: resource.ComposeTestCheckFunc( + testAccResourceDNSRecordExists("windns_record.r1", []string{"cname.example.com"}, dnshelper.RecordTypeCNAME, false), + ), + Steps: []resource.TestStep{ + { + Config: testAccResourceDNSRecordConfigIllegalCharacter, + ExpectError: regexp.MustCompile(".*invalid characters detected in input.*"), + }, + }, + }) +} + func testAccResourceDNSRecordExists(resource string, expectedRecords []string, expectedRecordType string, expected bool) resource.TestCheckFunc { ctx := context.Background() return func(s *terraform.State) error {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.