Merge pull request #987 from easonlin404/secure-json

feat(render): add SecureJSON func to prevent json hijacking
This commit is contained in:
Javier Provecho Fernandez 2017-07-08 10:41:41 +02:00 committed by GitHub
commit c4249f923f
6 changed files with 101 additions and 9 deletions

View File

@ -616,6 +616,13 @@ func (c *Context) IndentedJSON(code int, obj interface{}) {
c.Render(code, render.IndentedJSON{Data: obj})
}
// SecureJSON serializes the given struct as Secure JSON into the response body.
// Default prepends "while(1)," to response body if the given struct is array values.
// It also sets the Content-Type as "application/json".
func (c *Context) SecureJSON(code int, obj interface{}) {
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj})
}
// JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json".
func (c *Context) JSON(code int, obj interface{}) {

View File

@ -598,6 +598,32 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) {
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
}
// Tests that the response is serialized as Secure JSON
// and Content-Type is set to application/json
func TestContextRenderSecureJSON(t *testing.T) {
w := httptest.NewRecorder()
c, router := CreateTestContext(w)
router.SecureJsonPrefix("&&&START&&&")
c.SecureJSON(201, []string{"foo", "bar"})
assert.Equal(t, w.Code, 201)
assert.Equal(t, w.Body.String(), "&&&START&&&[\"foo\",\"bar\"]")
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
}
// Tests that no Custom JSON is rendered if code is 204
func TestContextRenderNoContentSecureJSON(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.SecureJSON(204, []string{"foo", "bar"})
assert.Equal(t, 204, w.Code)
assert.Equal(t, "", w.Body.String())
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
}
// Tests that the response executes the templates
// and responds with Content-Type set to text/html
func TestContextRenderHTML(t *testing.T) {

7
gin.go
View File

@ -45,6 +45,7 @@ type RoutesInfo []RouteInfo
type Engine struct {
RouterGroup
delims render.Delims
secureJsonPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
allNoRoute HandlersChain
@ -121,6 +122,7 @@ func New() *Engine {
UnescapePathValues: true,
trees: make(methodTrees, 0, 9),
delims: render.Delims{"{{", "}}"},
secureJsonPrefix: "while(1);",
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
@ -145,6 +147,11 @@ func (engine *Engine) Delims(left, right string) *Engine {
return engine
}
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
engine.secureJsonPrefix = prefix
return engine
}
func (engine *Engine) LoadHTMLGlob(pattern string) {
if IsDebugging() {
debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern)))

View File

@ -5,6 +5,7 @@
package render
import (
"bytes"
"encoding/json"
"net/http"
)
@ -17,6 +18,13 @@ type IndentedJSON struct {
Data interface{}
}
type SecureJSON struct {
Prefix string
Data interface{}
}
type SecureJSONPrefix string
var jsonContentType = []string{"application/json; charset=utf-8"}
func (r JSON) Render(w http.ResponseWriter) (err error) {
@ -53,3 +61,21 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error {
func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType)
}
func (r SecureJSON) Render(w http.ResponseWriter) error {
r.WriteContentType(w)
jsonBytes, err := json.Marshal(r.Data)
if err != nil {
return err
}
// if the jsonBytes is array values
if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) {
w.Write([]byte(r.Prefix))
}
w.Write(jsonBytes)
return nil
}
func (r SecureJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType)
}

View File

@ -14,6 +14,7 @@ type Render interface {
var (
_ Render = JSON{}
_ Render = IndentedJSON{}
_ Render = SecureJSON{}
_ Render = XML{}
_ Render = String{}
_ Render = Redirect{}

View File

@ -66,6 +66,31 @@ func TestRenderIndentedJSON(t *testing.T) {
assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8")
}
func TestRenderSecureJSON(t *testing.T) {
w1 := httptest.NewRecorder()
data := map[string]interface{}{
"foo": "bar",
}
err1 := (SecureJSON{"while(1);", data}).Render(w1)
assert.NoError(t, err1)
assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
w2 := httptest.NewRecorder()
datas := []map[string]interface{}{{
"foo": "bar",
}, {
"bar": "foo",
}}
err2 := (SecureJSON{"while(1);", datas}).Render(w2)
assert.NoError(t, err2)
assert.Equal(t, "while(1);[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]", w2.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type"))
}
type xmlmap map[string]interface{}
// Allows type H to be used with xml.Marshal