VYPR
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.

PackageAffected versionsPatched versions
github.com/nrkno/terraform-provider-windnsGo
<= 1.0.4

Patches

1
c76f69610c1b

fix: better input validation

https://github.com/nrkno/terraform-provider-windnsSjur FredriksenMay 6, 2025via ghsa
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

4

News mentions

0

No linked articles in our index yet.