Skip to content

Examples

TarradeMarc edited this page Apr 19, 2024 · 3 revisions

Decoys should let an attacker believe that there is some potential vulnerability to leverage. You can find inspiration by following one or all of the approaches proposed below, or jump to our example catalog.

Table of Contents

  1. Opportunistic approach

  2. Systematic approach

  3. Catalog

Opportunistic approach


Summary

  1. find hack in the news

  2. find exploit details / write-up

  3. re-create vulnerability with a decoy


A new hack is making the news. You look at the details of the hack, and re-create it with a decoy or a set of decoys.

Let's take the Equifax hack as an example. Attackers exploited a vulnerability of their dispute portal server. The vulnerability was due to the portal server running an outdated version of Apache Struts, which had vulnerability CVE-2017-5638.

This vulnerability says that, if the Content-Type request header is invalid, then an error message is sent back to the user - the content of the error depending on the submitted value. Digging further, we see that to trigger the vulnerability, the Content-Type should start with %{

Detect

One thing which can be done is to detect if someone is trying to exploit this vulnerability. We create a detection decoy which does the following:

  • read the request's Content-Type header

  • alert if the Content-Type is malicious

{
  "decoy": {
    "key": "content-type",
    "dynamicValue": ".*%{.*"
  },
  "detect": {
    "seek": {
      "inRequest": ".*",
      "in": "header"
    },
    "alert": {
      "severity": "MEDIUM",
      "whenComplete": true
    }
  }
}

Inject

You may want to also pretend that your server is vulnerable to CVE-2017-5638 by showing an error message when someone injects a malicious Content-Type. That's the job for a second decoy, which does the following:

  • inject an error message to any request containing a malicious Content-Type

This is a separate decoy as we don't follow the usual flow of: 1-inject 2-detect. Here what we are doing is a conditional injection which does not lead to any detection: the error message in itself will probably lead to further attempts at tuning the Content-Type but the exact content of the injection won't play any role here.

{
  "decoy": {
    "key": "com.opensymphony.xwork2.inject.ContainerImpl@d0d2b00"
  },
  "inject": {
    "store": {
      "inResponse": ".*",
      "as": "body",
      "at": {
        "method":"replace",
        "property":"((.|\n)*)"
      },
      "whenTrue": [
        {
          "key": "content-type",
          "value": ".*%{.*",
          "in": "header"
        }
      ]
    }
  }
}

Combined...

Keeping the two decoys split is better for readability. Now, since we did not use the 'string' decoy option of the first decoy, we may combine both into this single, harder to understand, decoy:

{
  "decoy": {
    "key": "content-type",
    "separator": "=",
    "dynamicValue": ".*%{.*"
    "string":"com.opensymphony.xwork2.inject.ContainerImpl@d0d2b00"
  },
  "detect": {
    "seek": {
      "inRequest": ".*",
      "in": "header"
    },
    "alert": {
      "severity": "MEDIUM",
      "whenComplete": true
    }
  }
  "inject": {
    "store": {
      "inResponse": ".*",
      "as": "body"
      "at": {
        "method":"replace",
        "property":"((.|\n)*)"
      },
      "whenTrue": [
        {
          "key": "content-type",
          "value": ".*%{.*",
          "in": "header"
        }
      ]
    }
  }
}

The detection will work with the key and dynamicValue, while the injection will work with the string. Please remember that here we combine two different flows:

First flow:

  1. inject nothing

  2. detect if Content-Type is malicious

Second flow:

  1. inject error message

  2. detect nothing

Systematic approach


Summary

  1. look-up CWE list related to web applications

  2. for each CWE, find related list of CVE

  3. for each CVE, find exploit details / write-up

  4. re-create vulnerability with a decoy


Cloud active defense is about injecting decoys in HTTP responses, meaning that we are interested all kinds of vulnerabilities, as long as these are web related.

A good starting point is the Common Weakness Enumeration (CWE) database, specifically the CWE mapping with the OWASP Top Ten.

There are web-related vulnerabilities which are not part of the Top 10, but this catalog already has more than 1000 entries at the time of writing and should get you covered.

Not all entries will be relevant to cloud active defense or to your application, but many of them should.

Starting with Broken Access Control, the first entry is CWE-22 'path traversal'. This CWE is the parent of CWE-23 'relative path traversal' and CWE-36 'absolute path traversal'.

Now that we have this starting point, we can simply invent decoys, but we can also search for known vulnerabilities within a Common Vulnerabilities and Exposures (CVE) database matching with our CWEs. Looking to existing CVE can help us define realistic decoys, in the sense that actual products had such a vulnerability. By creating an exact copy, we may entail that our web application is actually vulnerable to that CVE. By creating something new inspired by that CVE, we can ensure that this new decoy is somewhat realistic.

To find CVEs, you can use the search function from cvedetails.com which let you refine per CWE. Continuing with our example, we can search for CWE 23 and search for an interesting recent entry.

For example, via this search query we can find CVE-2024-0550 which has a base score of 9.6. In the references, we see this write-up report which explains exactly how to reproduce the issue.

In there, we notice how a POST request sent (as an authenticated user) to /api/admin/user/<user_id> with a payload of {"pfpFilename":"../../anything.db"} enables the attacker to download the requested file by following the POST request with a GET request to /api/system/pfp/<user_id> (as an authenticated user)

Higher-fidelity decoy set

There are many ways to implement a decoy around this vulnerability. An easy decoy can be to alert when detecting a request to /api/admin/user/. This being said, an attacker will likely first check if there is a /api endpoint in the first place. So another approach is to pretend that /api, /api/admin, /api/admin/user, /api/system and /api/system/pfp exist on the server. An alert would then be sent only if a request to /api/admin/user/ is made - or only if a request to /api/admin/user/ with a JSON payload containing the key pfpFilename is sent - or all that, but only if pfpFilename contains a '..' sequence, depending on how specific you want to be.

Exemple configuration looking for '..' sequence in pfpFilename:

A POST /api or POST /api/ request while authenticated gets a '200 OK' reply (first decoy overwrites 'Cannot POST /api' with 'OK', second decoy overwrites '404' status code with '200'

{
  "decoy": {
    "key": "dummy",
    "string": "OK"
  },
  "inject": {
    "store": {
      "inResponse": "api[\/]?$",
      "withVerb": "POST",
      "as": "body",
      "at": {
        "method": "replace",
        "property": "((.|\n)*)"
      },
      "whenTrue": [
        {
          "key": "SESSION",
          "value": ".*",
          "in": "cookie"
        }
      ]
    }
  }
},
{
  "decoy": {
    "key": "200"
  },
  "inject": {
    "store": {
      "inResponse": "api[\/]?$",
      "withVerb": "POST",
      "as": "status",
      "at": {
        "method": "replace",
        "property": "((.|\n)*)"
      },
      "whenTrue": [
        {
          "key": "SESSION",
          "value": ".*",
          "in": "cookie"
        }
      ]
    }
  }
}

Adapt the above /api decoys to handle these paths:

  • /api/admin

  • /api/admin/user

  • /api/system

  • /api/system/pfp

Return an error message and alert for any POST request to /api/admin/user/

    {
      "decoy": {
        "dynamicKey": "/api/admin/user/(.+)",
        "string": "Error: unknown id"
      },
      "inject": {
        "store": {
          "inResponse": "api/admin/user/",
          "withVerb": "POST",
          "as": "body",
          "at": {
            "method": "replace",
            "property": "((.|\n)*)"
          },
          "whenTrue": [
            {
              "key": "SESSION",
              "value": ".*",
              "in": "cookie"
            }
          ]
        }
      },
      "detect": {
        "seek": {
          "inRequest": "api/admin/user/",
          "withVerb": "POST",
          "in": "url",
          "whenTrue": [
            {
              "key": "SESSION",
              "value": ".*",
              "in": "cookie"
            }
          ]
        },
        "alert": {
          "severity": "HIGH",
          "whenSeen": true
        }
      }
    }

Return an error message and alert if a POST request to /api/system/pfp contains a payload pfpFilename with a path traversal attempt:

 {
      "decoy": {
        "dynamicKey": ".*pfpFilename.*\\.\\..*",
        "string": "OK"
      },
      "inject": {
        "store": {
          "inResponse": "api/system/pfp[\/]?$",
          "withVerb": "POST",
          "as": "body",
          "at": {
            "method": "replace",
            "property": "((.|\n)*)"
          },
          "whenTrue": [
            {
              "key": "SESSION",
              "value": ".*",
              "in": "cookie"
            }
          ]
        }
      },
      "detect": {
        "seek": {
          "inRequest": "api/system/pfp[\/]?$",
          "withVerb": "POST",
          "in": "payload"
        },
        "alert": {
          "severity": "HIGH",
          "whenSeen": true
        }
      }
    }

Simpler, broader detection decoy

Now if your application does not already have a /api endpoint, I would go for an alert if a request is sent to /api/admin as an authenticated user. As you can see, this is not directly a decoy to detect a path traversal, but a decoy to detect active reconnaissance. Setting such a decoy for unauthenticated requests will lead to the detection of a large number of bots - this can be considered as noise - but the same decoy for an authenticated session will mean that someone already inside has malicious intentions.

Example configuration for out demo application:

{
  "decoy": {
    "key": "/api/admin"
  },
  "detect": {
    "seek": {
      "inRequest": ".*",
      "withVerb": "POST",
      "in": "url",
      "whenTrue": [
        {
          "key": "SESSION",
          "value": ".*",
          "in": "cookie"
        }
      ],
    },
    "alert": {
      "severity": "HIGH",
      "whenSeen": true
    }
  }
}
Clone this wiki locally