From 06c6256b59289e47209d2d03bb7946944d4eacda Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Mon, 8 Jan 2024 00:47:56 -0500
Subject: [PATCH 1/7] Enable Django Autoescape by default
---
django/django.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/django/django.go b/django/django.go
index 030fbe2..0374452 100644
--- a/django/django.go
+++ b/django/django.go
@@ -101,7 +101,8 @@ func (e *Engine) Load() error {
pongoset := pongo2.NewSet("default", pongoloader)
// Set template settings
pongoset.Globals.Update(e.Funcmap)
- pongo2.SetAutoescape(false)
+ // Enable autoescaping
+ pongo2.SetAutoescape(true)
// Loop trough each Directory and register template files
walkFn := func(path string, info os.FileInfo, err error) error {
From d7d74b530af04e6a13e12ae77ce1a189b2dd393d Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Wed, 10 Jan 2024 09:19:19 -0500
Subject: [PATCH 2/7] Add unit-test for xss, and workaround for safe variables
---
django/django.go | 11 ++++++++++-
django/django_test.go | 15 +++++++++++++++
2 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/django/django.go b/django/django.go
index 0374452..9db5a3b 100644
--- a/django/django.go
+++ b/django/django.go
@@ -232,7 +232,16 @@ func (e *Engine) Render(out io.Writer, name string, binding interface{}, layout
if bind == nil {
bind = make(map[string]interface{}, 1)
}
- bind[e.LayoutName] = parsed
+
+ // Wrokaround for custom {{embed}} tag
+ // Mark the `embed` variable as safe
+ // it has already been escaped above
+ // e.LayoutName will be 'embed'
+ safeEmbed := pongo2.AsSafeValue(parsed)
+
+ // Add the safe value to the binding map
+ bind[e.LayoutName] = safeEmbed
+
lay := e.Templates[layout[0]]
if lay == nil {
return fmt.Errorf("LayoutName %s does not exist", layout[0])
diff --git a/django/django_test.go b/django/django_test.go
index 5f62186..a586a5f 100644
--- a/django/django_test.go
+++ b/django/django_test.go
@@ -311,6 +311,21 @@ func Test_Invalid_Layout(t *testing.T) {
require.Error(t, err)
}
+func Test_XSS(t *testing.T) {
+ engine := New("./views", ".django")
+ require.NoError(t, engine.Load())
+
+ var buf bytes.Buffer
+ err := engine.Render(&buf, "index", map[string]interface{}{
+ "Title": "",
+ }, "layouts/main")
+ require.NoError(t, err)
+
+ expect := `
MainHeader
<script>alert('XSS')</script>
Footer
`
+ result := trim(buf.String())
+ require.Equal(t, expect, result)
+}
+
func Benchmark_Django(b *testing.B) {
expectSimple := `Hello, World!
`
expectExtended := `MainHeader
Hello, Admin!
Footer
`
From 5be100c6b5a1b5fed5b370136c002d2380353e49 Mon Sep 17 00:00:00 2001
From: RW
Date: Wed, 10 Jan 2024 16:01:59 +0100
Subject: [PATCH 3/7] Update django/django.go
spelling fix
---
django/django.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/django.go b/django/django.go
index 9db5a3b..54bacb1 100644
--- a/django/django.go
+++ b/django/django.go
@@ -233,7 +233,7 @@ func (e *Engine) Render(out io.Writer, name string, binding interface{}, layout
bind = make(map[string]interface{}, 1)
}
- // Wrokaround for custom {{embed}} tag
+ // Workaround for custom {{embed}} tag
// Mark the `embed` variable as safe
// it has already been escaped above
// e.LayoutName will be 'embed'
From 3e00a49db89d8dbd3ba63236dcdff48df7b676f5 Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Wed, 10 Jan 2024 23:02:53 -0500
Subject: [PATCH 4/7] Make autoescape configurable
---
django/django.go | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/django/django.go b/django/django.go
index 54bacb1..50b937d 100644
--- a/django/django.go
+++ b/django/django.go
@@ -23,6 +23,8 @@ type Engine struct {
forwardPath bool
// templates
Templates map[string]*pongo2.Template
+ // set auto escape globally
+ AutoEscape bool
}
// New returns a Django render engine for Fiber
@@ -36,6 +38,7 @@ func New(directory, extension string) *Engine {
LayoutName: "embed",
Funcmap: make(map[string]interface{}),
},
+ AutoEscape: true,
}
return engine
}
@@ -52,6 +55,7 @@ func NewFileSystem(fs http.FileSystem, extension string) *Engine {
LayoutName: "embed",
Funcmap: make(map[string]interface{}),
},
+ AutoEscape: true,
}
return engine
}
@@ -70,6 +74,7 @@ func NewPathForwardingFileSystem(fs http.FileSystem, directory, extension string
LayoutName: "embed",
Funcmap: make(map[string]interface{}),
},
+ AutoEscape: true,
forwardPath: true,
}
return engine
@@ -101,8 +106,8 @@ func (e *Engine) Load() error {
pongoset := pongo2.NewSet("default", pongoloader)
// Set template settings
pongoset.Globals.Update(e.Funcmap)
- // Enable autoescaping
- pongo2.SetAutoescape(true)
+ // Set autoescaping
+ pongo2.SetAutoescape(e.AutoEscape)
// Loop trough each Directory and register template files
walkFn := func(path string, info os.FileInfo, err error) error {
@@ -208,6 +213,11 @@ func isValidKey(key string) bool {
return true
}
+// SetAutoEscape sets the auto-escape property of the template engine
+func (e *Engine) SetAutoEscape(autoEscape bool) {
+ e.AutoEscape = autoEscape
+}
+
// Render will render the template by name
func (e *Engine) Render(out io.Writer, name string, binding interface{}, layout ...string) error {
if !e.Loaded || e.ShouldReload {
From f02c83a5f0983d5cb55580532f0cc5d468539c95 Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Wed, 10 Jan 2024 23:17:19 -0500
Subject: [PATCH 5/7] Update django docs, add unit-test for XSS without
escaping
---
django/README.md | 27 +++++++++++++++++++++++----
django/django_test.go | 16 ++++++++++++++++
2 files changed, 39 insertions(+), 4 deletions(-)
diff --git a/django/README.md b/django/README.md
index ce7799d..eab51e9 100644
--- a/django/README.md
+++ b/django/README.md
@@ -59,9 +59,9 @@ func main() {
// Create a new engine
engine := django.New("./views", ".django")
- // Or from an embedded system
- // See github.com/gofiber/embed for examples
- // engine := html.NewFileSystem(http.Dir("./views", ".django"))
+ // Or from an embedded system
+ // See github.com/gofiber/embed for examples
+ // engine := html.NewFileSystem(http.Dir("./views", ".django"))
// Pass the engine to the Views
app := fiber.New(fiber.Config{
@@ -194,4 +194,23 @@ If you need to access a value in the template that doesn't adhere to the key nam
c.Render("index", fiber.Map{
"Fiber": "Hello, World!\n\nGreetings from Fiber Team",
"MyKey": c.Locals("my-key"),
-})
\ No newline at end of file
+})
+
+### AutoEscape is enabled by default
+
+When you create a new instance of the `Engine`, the auto-escape is **enabled by default**. This setting automatically escapes output, providing a critical security measure against Cross-Site Scripting (XSS) attacks.
+
+### Disabling Auto-Escape
+
+Auto-escaping can be disabled if necessary, using the `SetAutoEscape` method:
+
+```go
+engine := django.New("./views", ".django")
+engine.SetAutoEscape(false)
+```
+
+### Security Implications of Disabling Auto-Escape
+
+Disabling auto-escape should be approached with caution. It can expose your application to XSS attacks, where malicious scripts are injected into web pages. Without auto-escaping, there is a risk of rendering harmful HTML or JavaScript from user-supplied data.
+
+It is advisable to keep auto-escape enabled unless there is a strong reason to disable it. If you do disable it, ensure all user-supplied content is thoroughly sanitized and validated to avoid XSS vulnerabilities.
diff --git a/django/django_test.go b/django/django_test.go
index a586a5f..91cac79 100644
--- a/django/django_test.go
+++ b/django/django_test.go
@@ -326,6 +326,22 @@ func Test_XSS(t *testing.T) {
require.Equal(t, expect, result)
}
+func Test_XSS_WithAutoEscapeDisabled(t *testing.T) {
+ engine := New("./views", ".django")
+ engine.SetAutoEscape(false)
+ require.NoError(t, engine.Load())
+
+ var buf bytes.Buffer
+ err := engine.Render(&buf, "index", map[string]interface{}{
+ "Title": "",
+ }, "layouts/main")
+ require.NoError(t, err)
+
+ expect := `MainHeader
Footer
`
+ result := trim(buf.String())
+ require.Equal(t, expect, result)
+}
+
func Benchmark_Django(b *testing.B) {
expectSimple := `Hello, World!
`
expectExtended := `MainHeader
Hello, Admin!
Footer
`
From 36d033b7260bca205fc665073be9c8775e0b6477 Mon Sep 17 00:00:00 2001
From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Date: Thu, 11 Jan 2024 00:26:31 -0500
Subject: [PATCH 6/7] Add section about using Django built-in template tags
---
django/README.md | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/django/README.md b/django/README.md
index eab51e9..e483fef 100644
--- a/django/README.md
+++ b/django/README.md
@@ -209,6 +209,27 @@ engine := django.New("./views", ".django")
engine.SetAutoEscape(false)
```
+### Setting AutoEscape using Django built-in template tags
+
+- Explicitly turning off autoescaping for a section:
+```django
+ {% autoescape off %}
+ {{ "" }}
+ {% endautoescape %}
+```
+
+- Turning autoescaping back on for a section:
+```django
+ {% autoescape on %}
+ {{ "" }}
+ {% endautoescape %}
+```
+- It can also be done on a per variable basis using the *safe* built-in:
+```django
+{{ someSafeVar | safe }}
+{{ "