Skip to content
TarradeMarc edited this page Jul 10, 2024 · 6 revisions

The inject section defines when and how your decoy should be injected. You will usually inject the content in a HTTP response, either as a cookie or in the page body. The inject section is optional. You may want to skip it if the decoy is injected via another mean (for example: as a database record) or if you're only interested in detecting something (such as a violation of a client-side validation).

Here is an example of a basic injection:

"inject": {
  "store": {
    "inResponse": "/robots.txt$",
    "withVerb": "GET",
    "as": "body"
  }
}

This injection should be understood as: "if the user performs a GET /robots.txt request, then append the decoy to the body of the HTTP response"

inResponse

inResponse means that the decoy will be injected in the HTTP response. This being said, the content of inResponse corresponds to the URL submitted in the request. inResponse is treated as a regular expression - so please note that "/robots.txt" will match a request to "/robots.txt" nut also a request to "/robots.txt/whatever". To restrict the injection to "/robots.txt", then set the content of inResponse to "/robots.txt$" To inject the decoy in all responses, you can use ".*" as value. If you omit this parameter then nothing will be injected.

inRequest

inRequest works exactly like inResponse, with the difference that the decoy will be injected in the HTTP request itself. inRequest is treated as a regular expression, just like inResponse. To inject the decoy in all requests, you can use ".*" as value. If you omit this parameter then nothing will be injected. Please note that when injecting in the request, only the application itself will be able to see the injected content (since this takes place between the proxy and the application). It was not difficult to implement this feature, but you will likely never need it. It could be used in scenarios where you want the application to behave in a certain way depending on that injected parameter. If you get a cool idea about what this could be used for, let us know :)

withVerb

Use withVerb to filter only certain requests for injection. If you don't specify anything, then all verbs will be considered. Be careful that if you do this, you might have strange side effects, namely if your application has an endpoint for which more than one verb can be used. Note also that not specifying a verb will force Envoy to process all requests instead of only the relevant one. Last, an attacker may try to use an invalid verb such as TEST just to see if a decoy is returned. For all these reasons, while optional, we highly recommend that you set this option. withVerb takes one parameter and will consider it as a verb. The standard verbs can be used (GET, POST, UPDATE, DELETE, HEAD, OPTIONS) but you can set the value to anything you want. If you want a decoy to be injected for two different verbs, then create two different decoys.

as

as specifies where the injection should happen. Supported options are: body,header and cookie. This keyword is mandatory (if you try to inject something).

  • In practice, cookie is just a HTTP header, but since it has a special purpose compared to the other headers, it has its own keyword.
  • Envoy supports pseudo-headers, these can be manipulated as any other headers. These include:
    • :status : the returned status code (200, 404...)
    • :path : the URL path
    • :method : the request verb (GET, POST...)

at

at let you decide exactly where to inject the decoy. By default, decoys are appended to whatever is sent back. This means that if you inject in the body, the decoy will always be at the end, which is not always useful - attackers could also just assume that anything at the end of a body is a potential decoy. To give you the control over where to inject, you can use the at keyword. Let's revisit our example. Notice how at comprises a method and a property parameter. Both work in conjunction.

"inject": {
  "store": {
    "inResponse": "/robots.txt",
    "withVerb": "GET",
    "as": "body",
    "at": {
      "method": "replace",
      "property": "((.|\n)*)"
    }
  }
}

This injection should be understood in the following way: whenever you receive the HTTP response to a GET /robots.txt request, then match what is defined in the property (here: everything across multiple lines, e.g. the full page content) and replace it with the content of the decoy (since this is a request to robots.txt, and revisiting the first example of the README page, you may want to inject this string: "User-agent: *\nDisallow: /forbidden".

In this example, we replace the full content with the content of our decoy. Otherwise the expected 'Cannot GET /robots.txt' content would be visible on screen, breaking the illusion.

method can be one of the following: character, line, replace, always, after, before. Let's explore these options:

"method": "character"

If you use this method, then cloud active defense will expect the property to be an integer. When injecting, it will count as many characters as defined in the property and inject the decoy when the count is reached. This approach can be useful if you know the exact size of the response and if you know that this response is static. This can be used to inject in the body of a static page.

property can be set to a negative value, in which way the counting will be done from the end. -0 means that the decoy will be appended to the response content, this is the default behavior if neither method nor property are specified.

"method": "line"

If you use this method, then cloud active defense will expect the property to be an integer. When injecting, it will count as many lines as defined in the property and inject the decoy when the count is reached. This approach can be useful if you know the exact number of lines of the response and if you know that this response is static. This can be used to inject in the body of a static page, or as an HTTP response header if you don't want that header to appear last.

property can be set to a negative value, in which way the counting will be done from the end. -0 means that the decoy will be appended as a new line after the last line.

"method": "replace"

If you use this method, then cloud active defense will expect the property to be a regular expression. When injecting, it will try to match whatever is defined in the property, and replace the matched content with the content of the decoy. If there is more than one match, only the first match will be replaced.

"method": "always"

If you use this method, then cloud active defense will expect the property to be a regular expression. When injecting, it will try to match whatever is defined in the property, and replace the matched content with the content of the decoy. If there is more than one match, then the decoy will be injected multiple times (once per match).

"method": "after"

If you use this method, then cloud active defense will expect the property to be a regular expression. When injecting, it will try to match whatever is defined in the property, then append the decoy, then append the remainder of the received response. This method is useful if you want to add something looking like an HTML comment, a javascript parameter name, etc.

"method": "before"

If you use this method, then cloud active defense will expect the property to be a regular expression. When injecting, it will try to match whatever is defined in the property. Upon finding a match, it will send the first part until the match, then append the decoy, then append the remainder of the received response (starting with the content of the match). This method is useful if you want to add something looking like an HTML comment, a javascript parameter name, etc.

whenTrue and whenFalse

whenTrue and whenFalse can be used to further refine when to do the injection. This is useful for example to inject a decoy only to authenticated users.

Both keywords behave in the same way, let's explore them with the post-authentication example mentioned in the README:

{
  "decoy": {
    "key": "role",
    "separator": "=",
    "value": "user"
  },
  "inject": {
    "store": {
      "inResponse": ".*",
      "as": "cookie",
      "whenTrue": [
        {
          "key": "SESSION",
          "value": ".*",
          "in": "cookie"
        }
      ],
      "whenFalse": [
        {
          "key": "role",
          "value": ".*",
          "in": "cookie"
        }
      ]
    }
  }
}

In this example, we inject a role=user cookie when these two conditions are met:

  1. 'whenTrue' condition: there exists a cookie named SESSION in the request, set to any value
  2. 'whenFalse' condition: there is not already a cookie named role in the request

whenTrue and whenFalse are defined as arrays, meaning that you may define as many positive and negative conditions as required.

Note that in this example, the cookie will be injected even if the user manually sets a cookie named SESSION. To prevent a leak of the role decoy, you may want to add a condition such as seeing that the word 'Dashboard' is present in the HTTP response body, or that the word 'login' is absent in the HTTP response body. (this is not possible at the moment, we're working on it !)

If you omit the whenFalse role=.* condition, then the role cookie will be reset with every authenticated request. This might be a giveaway that the role cookie is a decoy. On the other hand, resetting the cookie each time (or each time it was modified) is a means to avoid receiving alerts for each and every subsequent requests.

key

The key corresponds to the element to search. whenTrue and whenFalse only support key/value lookups at the moment (with the separator set to '='). The key is mandatory (we're working on making it optional so that you can have simply a regex match with what is specified in the value)

value

The value corresponds to the value of the key. The value is treated as a regular expression.

in

Specifies where to search for the key. Supports: cookie, header, url, getParam, postParam, payload.

  • cookie: straightforward, will search for a cookie having the proper key
  • header: straightforward, will search for a header having the proper key
  • getParam: GET parameters are in the form path?param1=value1&param2=value2. The proper parameter name and value will be matched. Note that this will work only if your application uses GET parameters, for REST APIs use url instead.
  • postParam: POST parameters are in the form param1=value1&param2=value2. The proper parameter name and value will be matched. Note that this will work only if your application uses POST parameters, for json payloads use payload instead.
  • url: for the URL, the content of the key will be ignored, only the content defined in the value will be matched against the full requested path.
  • payload: at the moment, the payload will be queried for key=value. We plan to ignore the key just like with the url in a future commit.
04-inject.mp4