Beego allows Reflected/Stored XSS in Beego's RenderForm() Function Due to Unescaped User Input
Description
Beego is an open-source web framework for the Go programming language. Prior to 2.3.6, a Cross-Site Scripting (XSS) vulnerability exists in Beego's RenderForm() function due to improper HTML escaping of user-controlled data. This vulnerability allows attackers to inject malicious JavaScript code that executes in victims' browsers, potentially leading to session hijacking, credential theft, or account takeover. The vulnerability affects any application using Beego's RenderForm() function with user-provided data. Since it is a high-level function generating an entire form markup, many developers would assume it automatically escapes attributes (the way most frameworks do). This vulnerability is fixed in 2.3.6.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Beego prior to 2.3.6 has an XSS vulnerability in RenderForm() due to improper HTML escaping, allowing attackers to inject malicious JavaScript via user-controlled data.
Vulnerability
Overview
CVE-2025-30223 is a Cross-Site Scripting (XSS) vulnerability in Beego's RenderForm() function, present in versions prior to 2.3.6. The root cause lies in the renderFormField() helper, which directly interpolates user-controlled values (such as label, name, id, class, and value) into HTML without proper escaping [2][3]. Because RenderForm() returns template.HTML, Go's automatic HTML escaping is bypassed, making the flaw particularly dangerous [2].
Exploitation
An attacker can exploit this vulnerability by injecting malicious payloads into any user-controlled field that is rendered via RenderForm(). For example, injecting JavaScript into a DisplayName field can break out of an HTML attribute context, while injecting HTML tags into a textarea content field can directly execute scripts [2]. No special authentication is required if the application renders user-supplied data through this function, making the attack surface broad [1].
Impact
Successful exploitation allows an attacker to execute arbitrary JavaScript in the context of a victim's browser. This can lead to session hijacking, credential theft, or account takeover, as the injected script can access cookies, local storage, and perform actions on behalf of the user [1][2].
Mitigation
The vulnerability is fixed in Beego version 2.3.6. The fix, introduced in commit 939bb18, adds proper HTML escaping using template.HTMLEscapeString() for all user-controlled attributes and values [3]. Users are strongly advised to upgrade to the latest version or apply the patch manually [1][4].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/beego/beego/v2Go | < 2.3.6 | 2.3.6 |
github.com/beego/beegoGo | <= 1.12.14 | — |
Affected products
2- beego/beegov5Range: < 2.3.6
Patches
1939bb18c6640fix: add proper HTML escaping in renderFormField
2 files changed · +71 −10
server/web/templatefunc.go+29 −10 modified@@ -314,43 +314,62 @@ func RenderForm(obj interface{}) template.HTML { // renderFormField returns a string containing HTML of a single form field. In case of select fType, it will retrun // select tag with options. Value for select fType must be comma separated string which are use are func renderFormField(label, name, fType string, value interface{}, id string, class string, required bool) string { + // Format attributes with spaces first + idAttr := "" if id != "" { - id = " id=\"" + id + "\"" + idAttr = " id=\"" + template.HTMLEscapeString(id) + "\"" } + classAttr := "" if class != "" { - class = " class=\"" + class + "\"" + classAttr = " class=\"" + template.HTMLEscapeString(class) + "\"" } - requiredString := "" + requiredAttr := "" if required { - requiredString = " required" + requiredAttr = " required" + } + + // Escape all string values + escapedName := template.HTMLEscapeString(name) + escapedLabel := template.HTMLEscapeString(label) + escapedType := template.HTMLEscapeString(fType) + + // Handle value specially as it's an interface{} + escapedValue := "" + if value != nil { + escapedValue = template.HTMLEscapeString(fmt.Sprintf("%v", value)) } if isValidForInput(fType) { - return fmt.Sprintf(`%v<input%v%v name="%v" type="%v" value="%v"%v>`, label, id, class, name, fType, value, requiredString) + return fmt.Sprintf(`%v<input%v%v name="%v" type="%v" value="%v"%v>`, + escapedLabel, idAttr, classAttr, escapedName, escapedType, escapedValue, requiredAttr) } if fType == "select" { - valueStr, ok := value.(string) + rawValueStr, ok := value.(string) if !ok { logs.Error("for select value must comma separated string that are the options for select") return "" } var selectBuilder strings.Builder - selectBuilder.WriteString(fmt.Sprintf(`%v<select%v%v name="%v"></br>`, label, id, class, name)) + selectBuilder.WriteString(fmt.Sprintf(`%v<select%v%v name="%v"></br>`, + escapedLabel, idAttr, classAttr, escapedName)) - for _, option := range strings.Split(valueStr, ",") { - selectBuilder.WriteString(fmt.Sprintf(` <option value="%v"> %v </option></br>`, option, option)) + for _, option := range strings.Split(rawValueStr, ",") { + escapedOption := template.HTMLEscapeString(option) + selectBuilder.WriteString(fmt.Sprintf(` <option value="%v"> %v </option></br>`, + escapedOption, escapedOption)) } selectBuilder.WriteString(`</select>`) return selectBuilder.String() } - return fmt.Sprintf(`%v<%v%v%v name="%v"%v>%v</%v>`, label, fType, id, class, name, requiredString, value, fType) + return fmt.Sprintf(`%v<%v%v%v name="%v"%v>%v</%v>`, + escapedLabel, escapedType, idAttr, classAttr, escapedName, requiredAttr, escapedValue, escapedType) } // isValidForInput checks if fType is a valid value for the `type` property of an HTML input element.
server/web/templatefunc_test.go+42 −0 modified@@ -18,6 +18,7 @@ import ( "html/template" "net/url" "reflect" + "strings" "testing" "time" ) @@ -583,3 +584,44 @@ func Test_lt(t *testing.T) { } } } + +func TestRenderFormSecurity(t *testing.T) { + type UserProfile struct { + DisplayName string `form:"displayName,text,Name:"` + Bio string `form:",textarea"` + } + + // Test case 1: Test proper escaping of special characters in attributes + specialCharsProfile := UserProfile{ + DisplayName: `Special " ' < > & Characters`, + Bio: "Normal text content", + } + + output := string(RenderForm(&specialCharsProfile)) + + // Verify the output has all special characters properly escaped + if strings.Contains(output, `"Special "`) { + t.Errorf("Quotation mark not properly escaped in attribute") + } + + if !strings.Contains(output, `value="Special`) { + t.Errorf("Expected escaped attribute value not found") + } + + // Test case 2: Test proper escaping of HTML-like content + htmlContentProfile := UserProfile{ + DisplayName: "Normal Name", + Bio: `<div>Sample HTML content</div>`, + } + + output = string(RenderForm(&htmlContentProfile)) + + // Verify the output has HTML content properly escaped + if strings.Contains(output, `<div>`) { + t.Errorf("HTML tags not properly escaped in content") + } + + if !strings.Contains(output, `<div>`) { + t.Errorf("Expected escaped HTML not found") + } +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-2j42-h78h-q4fgghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-30223ghsaADVISORY
- github.com/beego/beego/commit/939bb18c66406466715ddadd25dd9ffa6f169e25ghsax_refsource_MISCWEB
- github.com/beego/beego/security/advisories/GHSA-2j42-h78h-q4fgghsax_refsource_CONFIRMWEB
- pkg.go.dev/vuln/GO-2025-3585ghsaWEB
News mentions
0No linked articles in our index yet.