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 := `Main

Header

<script>alert('XSS')</script>

Footer

` + result := trim(buf.String()) + require.Equal(t, expect, result) +} + func Benchmark_Django(b *testing.B) { expectSimple := `

Hello, World!

` expectExtended := `Main

Header

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 := `Main

Header

Footer

` + result := trim(buf.String()) + require.Equal(t, expect, result) +} + func Benchmark_Django(b *testing.B) { expectSimple := `

Hello, World!

` expectExtended := `Main

Header

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 }}

+{{ "