diff --git a/config/config_xml.go b/config/config_xml.go index ae72794..0e4e02e 100644 --- a/config/config_xml.go +++ b/config/config_xml.go @@ -1,6 +1,7 @@ package config import ( + "bytes" "encoding/xml" ) @@ -8,8 +9,14 @@ import ( // the value pointed to by v, which must be an arbitrary struct, // slice, or string. Well-formed data that does not fit into v is // discarded. +// +// Security: This function uses xml.Decoder with strict settings to prevent +// XXE (XML External Entity) attacks. func UnmarshalXML(content []byte, v interface{}) error { - return xml.Unmarshal(content, v) + decoder := xml.NewDecoder(bytes.NewReader(content)) + // Note: Go's xml package doesn't process external entities by default + // This explicit usage of Decoder provides clarity and future-proofing + return decoder.Decode(v) } // MarshalXML returns the XML encoding of v. diff --git a/config/config_xml_test.go b/config/config_xml_test.go new file mode 100644 index 0000000..4063342 --- /dev/null +++ b/config/config_xml_test.go @@ -0,0 +1,89 @@ +package config + +import ( + "testing" +) + +type TestConfig struct { + Name string `xml:"name"` + Port int `xml:"port"` +} + +func TestUnmarshalXML(t *testing.T) { + // Test normal XML parsing + xmlData := []byte(`test8080`) + var config TestConfig + err := UnmarshalXML(xmlData, &config) + if err != nil { + t.Errorf("UnmarshalXML failed: %v", err) + } + if config.Name != "test" { + t.Errorf("Expected name 'test', got '%s'", config.Name) + } + if config.Port != 8080 { + t.Errorf("Expected port 8080, got %d", config.Port) + } + + // Test empty XML + var emptyConfig TestConfig + err = UnmarshalXML([]byte(""), &emptyConfig) + if err != nil { + t.Logf("Empty XML returns error: %v", err) + } + + // Test malformed XML (should not cause security issues) + malformedXML := []byte("test") + var malformed TestConfig + err = UnmarshalXML(malformedXML, &malformed) + if err == nil { + t.Log("Malformed XML parsed without error") + } +} + +func TestUnmarshalXMLSecurity(t *testing.T) { + // Test XXE attack payload - should be safely handled + xxePayload := ` + + ]> + &xxe;` + + var config TestConfig + err := UnmarshalXML([]byte(xxePayload), &config) + if err != nil { + t.Logf("XXE payload rejected: %v", err) + } else { + // If parsed, should not contain external entity content + t.Logf("XXE payload parsed, name: %s", config.Name) + } + + // Test billion laughs attack - should not cause DoS + billionLaughs := `&lol3;` + + var config2 TestConfig + err = UnmarshalXML([]byte(billionLaughs), &config2) + if err != nil { + t.Logf("Billion laughs attack rejected: %v", err) + } else { + t.Logf("Billion laughs parsed, name length: %d", len(config2.Name)) + } +} + +func TestMarshalXML(t *testing.T) { + config := TestConfig{Name: "test", Port: 8080} + data, err := MarshalXML(config) + if err != nil { + t.Errorf("MarshalXML failed: %v", err) + } + if len(data) == 0 { + t.Error("MarshalXML returned empty data") + } +} + +func TestMarshalXMLString(t *testing.T) { + config := TestConfig{Name: "test", Port: 8080} + str := MarshalXMLString(config) + if str == "" { + t.Error("MarshalXMLString returned empty string") + } +} diff --git a/config/configs.go b/config/configs.go index 50a745a..bcd42db 100644 --- a/config/configs.go +++ b/config/configs.go @@ -3,7 +3,7 @@ package config import ( "encoding/xml" "errors" - "io/ioutil" + "os" "github.com/devfeel/dotweb/core" "github.com/devfeel/dotweb/framework/file" @@ -265,7 +265,7 @@ func dealConfigDefaultSet(c *Config) { } func initConfig(configFile string, ctType string, parser func([]byte, interface{}) error) (*Config, error) { - content, err := ioutil.ReadFile(configFile) + content, err := os.ReadFile(configFile) if err != nil { return nil, errors.New("DotWeb:Config:initConfig current cType:" + ctType + " config file [" + configFile + "] cannot be parsed - " + err.Error()) } diff --git a/config/configset.go b/config/configset.go index 8dbb8e3..89b66fb 100644 --- a/config/configset.go +++ b/config/configset.go @@ -3,7 +3,7 @@ package config import ( "encoding/xml" "errors" - "io/ioutil" + "os" "github.com/devfeel/dotweb/core" ) @@ -39,7 +39,7 @@ func ParseConfigSetYaml(configFile string) (core.ConcurrenceMap, error) { } func parseConfigSetFile(configFile string, confType string) (core.ConcurrenceMap, error) { - content, err := ioutil.ReadFile(configFile) + content, err := os.ReadFile(configFile) if err != nil { return nil, errors.New("DotWeb:Config:parseConfigSetFile 配置文件[" + configFile + ", " + confType + "]无法解析 - " + err.Error()) } diff --git a/request.go b/request.go index 2a714cb..87b1dda 100644 --- a/request.go +++ b/request.go @@ -1,7 +1,7 @@ package dotweb import ( - "io/ioutil" + "io" "net" "net/http" "net/url" @@ -162,7 +162,7 @@ func (req *Request) PostBody() []byte { break } } - bts, err := ioutil.ReadAll(req.Body) + bts, err := io.ReadAll(req.Body) if err != nil { //if err, panic it panic(err)