OWASP / OFFAT

The OWASP OFFAT tool autonomously assesses your API for prevalent vulnerabilities, though full compatibility with OAS v3 is pending. The project remains a work in progress, continuously evolving towards completion.
http://owasp.org/OFFAT/
MIT License
453 stars 64 forks source link

False negative related to SQL Injection #100

Open nrathaus opened 5 months ago

nrathaus commented 5 months ago

The endpoint: https://brokencrystals.com/api/testimonials/count?query=%27 is vulnerable to an SQL injection

The endpoint does NOT return 50X error when the SQL injection occurs, thus: STATUS_CODE_FILTER doesn't catch it

I believe it would be a smart idea to look for common SQL errors such as: ' - unterminated quoted string at or near "'"

Other errors are listed here: https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05-Testing_for_SQL_Injection

I can't easily find one 'list' that has all the SQL errors

dmdhrumilmistry commented 4 months ago

It would require writing new tests for SQLi to detect it using BODY_REGEX_FILTER. Additionally finding SQLi precisely can be a challenging task. Maybe, OFFAT can integrate sqlmap into the tool.

nrathaus commented 4 months ago

Here is a short code we can integrate that will generate text (test.rst) that that an be provided to sqlmap to do SQL injection testing

def test_EndPoint(self):
        """./sqlmap.py --batch -r test.rst --answers="involving it=n"""
        tmp_spec = tempfile.NamedTemporaryFile(mode="+a", encoding="utf-8")
        tmp_spec.write("""
{
  "openapi": "3.0.0",
  "paths": {
    "/api/testimonials/count": {
      "get": {
        "operationId": "TestimonialsController_getCount",
        "summary": "",
        "description": "Returns count of all testimonials based on provided sql query",
        "parameters": [
          {
            "name": "query",
            "required": true,
            "in": "query",
            "example": "select count(*) as count from testimonial",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": { "schema": { "type": "string" } }
            }
          }
        },
        "tags": ["Testimonials controller"]
      }
    },
    "/api/products/views": {
      "get": {
        "operationId": "ProductsController_viewProduct",
        "summary": "",
        "description": "Updates the product's 'viewsCount' according to product name provided in the header 'x-product-name' and returns the query result.",
        "parameters": [
          {
            "name": "x-product-name",
            "required": true,
            "in": "header",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": { "description": "" },
          "500": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" },
                    "location": { "type": "string" }
                  }
                }
              }
            }
          }
        },
        "tags": ["Products controller"]
      }
    }
  },
  "info": {
    "title": "Test",
    "description": "info -> description",
    "version": "1.0",
    "contact": {}
  },
  "tags": [],
  "servers": [{ "url": "https://someserver.com" }],
  "components": {
    "schemas": {
    }
  }
}
"""
    )
        tmp_spec.flush()
        obj = BaseParser(tmp_spec.name)

        server_hostname = "brokencrystals.com"
        server_port = ":443"
        server_ssl = "s"

        end_points = obj.specification.get('paths')
        for end_point, end_object in end_points.items():
            for method, method_object in end_object.items():
                parameters = method_object["parameters"]
                req = requests.Request(method=method, url=f"http{server_ssl}://{server_hostname}{server_port}{end_point}")
                req.headers["Host"] = f"{server_hostname}{server_port}"

                for parameter in parameters:
                    if "value" not in parameter:
                      parameter["value"] = "*"
                    if parameter["in"] == "header":
                      req.headers[parameter["name"]] = parameter["value"]
                    if parameter["in"] == "query":
                      req.params[parameter["name"]] =  parameter["value"]
                    if parameter["in"] == "body":
                      req.data.append([parameter["name"], parameter["value"]])
                req = req.prepare()
                print(f"==========\n{self.format_prepped_request(req)}\n==========")

I think integrating this into OFFAT is fairly easy - i.e. OFFAT can generate the file and potentially run sqlmap and bundle the results from it

The issue is that sqlmap isn't built to be easily automated - it doesn't output JSON results for example - so some manual labor related to reading the results is needed