Html form type json

System Overlord

A blog about security engineering, research, and general hacking.

Posting JSON with an HTML Form

A coworker and I were looking at an application today that, like so many other modern web applications, offers a RESTful API with JSON being used for serialization of requests/responses. She noted that the application didn’t include any sort of CSRF token and didn’t seem to use any of the headers (X-Requested-With, Referer, Origin, etc.) as a “poor man’s CSRF token”, but since it was posting JSON, was it really vulnerable to CSRF? Yes, yes, definitely yes!

Interestingly, this is reminiscent of many of the confusions between server and browser that are described in Michal Zalewski’s The Tangled Web.

The idea that the use of a particular encoding is a security boundary is, at worst, a completely wrong notion of security, and at best, a stopgap until W3C, browser vendors, or a clever attacker gets hold of your API. Let’s examine JSON encoding as a protection against CSRF and demonstrate a mini-PoC.

The Application

We have a basic application written in Go. Authentication checking is elided for post size, but this is not just an unauthenticated endpoint.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
package main import ( "encoding/json" "fmt" "net/http" ) type Secrets struct  Secret int > var storage Secrets func handler(w http.ResponseWriter, r *http.Request)  if r.Method == "POST"  json.NewDecoder(r.Body).Decode(&storage) > fmt.Fprintf(w, "The secret is %d", storage.Secret) > func main()  http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) > 

As you can see, it basically serves a secret number that can be updated via HTTP POST of a JSON object. If we attempt a URL-encoded or multipart POST, the JSON decoding fails miserably and the secret remains unchanged. We must POST JSON in order to get the secret value changed.

Exploring Options

So let’s explore our options here. The site can locally use AJAX via the XMLHTTPRequest API, but due to the Same-Origin Policy, an attacker’s site cannot use this. For most CSRF, the way to get around this is plain HTML forms, since form submission is not subject to the Same-Origin Policy. The W3C had a draft specification for JSON forms, but that has been abandoned since late 2015, and isn’t supported in any browsers. There are probably some techniques that can make use of Flash or other browser plugins (aren’t there always?) but it can even be done with basic forms, it just takes a little work.

JSON in Forms

Normally, if we try to POST JSON as, say, a form value, it ends up being URL encoded, not to mention including the field name.

 method='POST'>  name='json' value=''>  type='submit'>  

Results in a POST body of:

Источник

Html form type json

This specification defines a new form encoding algorithm that enables the transmission of form data as JSON. Instead of capturing form data as essentially an array of key-value pairs which is the bread and butter of existing form encodings, it relies on a simple name attribute syntax that makes it possible to capture rich data structures as JSON directly.

This specification is an extension specification to HTML.

Introduction

JSON is commonly used as an exchange format between Web client and backend services. Enabling HTML forms to submit JSON directly simplifies implementation as it enables backend services to operate by accepting a single input format that is what's more able to encode richer structure than other form encodings (where structure has traditional had to be emulated).

User agents that implement this specification will transmit JSON data from their forms whenever the form's enctype attribute is set to application/json . During the transition period, user agents that do not support this encoding will fall back to using application/x-www-form-urlencoded . This can be detected on the server side, and the conversion algorithm described in this specification can be used to convert such data to JSON.

The path format used in input name s is straightforward. To begin with, when no structuring information is present, the information will simply be captured as keys in a JSON object:

// produces

If a path is repeated, its value is captured as an array:

Deeper structures can be produced using sub-keys in the path, using either string keys for objects or integer keys for arrays:

    // produces < "pet": < "species": "Dahut" , "name": "Hypatia" >, "kids": ["Ashley", "Thelma"] >

As you can see above, the keys for array values can be in any order. If the array is somehow sparse, then null values are inserted:

Paths can cause structures to nest to arbitrary depths:

Really, any depth you might need.

The algorithm does not lose data in that every piece of information ends up being submitted. But given the path syntax, it is possible to introduce clashes such that one may attempt to set an object, an array, and a scalar value on the same key.

As seen in a previous example, trying to set multiple scalars on the same key will convert the value into an array. Trying to set a scalar value at a path that also contains an object will cause the scalar to be set on that object with the empty string key. Trying to set an array value at a path that also contains an object will cause the non-null values of that array to be set on the object using their array indices as keys. This is exemplified below:

This may seem somewhat convoluted but it should be considered as a resilience mechanism meant to ensure that data is not lost rather than the normal usage of the JSON encoding.

As we have seen above, multiple values with the same key are upgraded to an array, and it is also possible to directly use array offsets. However there are cases in which when generating a form from existing data, one may not know if there will be one or more instances of a given key (so that without using indices one will get back at times a scalar, at times an array) and it can be slightly cumbersome to properly generate array indices (especially if the field may be modified on the client side, which would mean maintaining array indices properly there). In order to indicate that a given path must contain an array irrespective of the number of its items, and without resorting to indices, one may use the append notation (only as the final step in a path):

The JSON encoding also supports file uploads. The values of files are themselves structured as objects and contain a type field indicating the MIME type, a name field containing the file name, and a body field with the file's content as base64.

 // assuming the user has selected two text files, produces: < "file": [ < "type": "text/plain", "name": "dahut.txt", "body": "REFBQUFBQUFIVVVVVVVVVVVVVCEhIQo=" >, < "type": "text/plain", "name": "litany.txt", "body": "SSBtdXN0IG5vdCBmZWFyLlxuRmVhciBpcyB0aGUgbWluZC1raWxsZXIuCg= error": < "good": "BOOM!" >, "error[bad": "BOOM BOOM!" >

Terminology

The following terms are defined in the HTML specification. [[!html51]]

The following terms are defined in ECMAScript. [[!ECMA-262]]

The

For the purposes of the algorithms below, an corresponds to the in-memory representation for a JSONObject and an corresponds to the in-memory representation for a JSONArray .

The following algorithm encodes form data as application/json . It operates on the form data set obtained from constructing the form data set.

  1. Let resulting object be a new Object .
  2. For each entry in the form data set , perform these substeps:
    1. If the entry's type is file , set the is file flag.
    2. Let steps be the result of running the steps to parse a JSON encoding path on the entry's name.
    3. Let context be set to the value of resulting object .
    4. For each step in the list of steps , run the following subsubsteps:
      1. Let the current value be the value obtained by getting the step's key from the current context .
      2. Run the steps to set a JSON encoding value with the current context , the step, the current value , the entry's value, and the is file flag.
      3. Update context to be the value returned by the steps to set a JSON encoding value ran above.

      The algorithm above deliberately ignores any charset information (e.g. from accept-charset ) and always encodes the resulting JSON as UTF-8. This is an intentionally sane behaviour.

      1. Let path be the path we are to parse.
      2. Let original be a copy of path .
      3. Let steps be an empty list of steps.
      4. Let first key be the result of collecting a sequence of characters that are not U+005B LEFT SQUARE BRACKET ("[") from the path.
      5. If first key is empty, jump to the step labelled failure below.
      6. Otherwise remove the collected characters from path and push a step onto steps with its type set to "object", its key set to the collected characters, and its last flag unset.
      7. If the path is empty, set the last flag on the last step in steps and return steps .
      8. Loop: While path is not an empty string, run these substeps:
        1. If the first two characters in path are U+005B LEFT SQUARE BRACKET ("[") followed by U+005D RIGHT SQUARE BRACKET ("]"), run these subsubsteps:
          1. Set the append flag on the last step in steps .
          2. Remove those two characters from path .
          3. If there are characters left in path , jump to the step labelled failure below.
          4. Otherwise jump to the step labelled loop above.
          1. Remove the first character from path .
          2. Collect a sequence of characters being ASCII digits, remove them from path , and let numeric key be the result of interpreting them as a base-ten integer.
          3. Remove the following character from path .
          4. Push a step onto steps with its type set to "array", its key set to the numeric key , and its last flag unset.
          5. Jump to the step labelled loop above.
          1. Remove the first character from path .
          2. Collect a sequence of characters that are not U+005D RIGHT SQUARE BRACKET, remove them from path , and let object key be the result.
          3. Remove the following character from path .
          4. Push a step onto steps with its type set to "object", its key set to the object key , and its last flag unset.
          5. Jump to the step labelled loop above.
          1. If the step is the last step, set its last flag.
          2. Otherwise, set its next type to the type of the next step in steps .
          1. Let context be the context this algorithm is called with.
          2. Let step be the step of the path this algorithm is called with.
          3. Let current value be the current value this algorithm is called with.
          4. Let entry value be the entry value this algorithm is called with.
          5. Let is file be the is file flag this algorithm is called with.
          6. If is file is set then replace entry value with an Object have its "name" property set to the file's name, its "type" property set to the file's type, and its "body" property set to the Base64 encoding of the file's body. [[!RFC2045]]
          7. If step has its last flag set, run the following substeps:
            1. If current value is undefined , run the following subsubsteps:
              1. If step 's append flag is set, set the context 's property named by the step 's key to a new Array containing entry value as its only member.
              2. Otherwise, set the context 's property named by the step 's key to entry value .
              1. If current value is undefined , run the following subsubsteps:
                1. If step 's next type is "array", set the context 's property named by the step 's key to a new empty Array and return it.
                2. Otherwise,set the context 's property named by the step 's key to a new empty Object and return it.
                1. If step 's next type is "array", return current value .
                2. Otherwise, run the following subsubsubsteps:
                  1. Let object be a new empty Object .
                  2. For each item and zero-based index i in current value , if item is not undefined then set a property of object named i to item .
                  3. Otherwise, set the context 's property named by the step 's key to object .
                  4. Return object .
                  1. Let object be a new Object with a property named by the empty string set to current value .
                  2. Set the context 's property named by the step 's key to object .
                  3. Return object .

                  Form Submission

                  Given that there exist deployed services using JSON and ambient authentication, and given that form requests are not protected by the same-origin policy by default, if this encoding were left wide open then a number of attacks would become possible. Because of this, when using the application/json form encoding the same-origin policy is enforced.

                  Acknowledgements

                  Thanks to Philippe Le Hégaret for serving as a sounding board for the first version of the encoding algorithm.

                  Источник

                  Читайте также:  Как в css wheel
Оцените статью