safe-redirect

Try in Playground
python-flaskSecurityError

0

No tags

CWE-601

Any information given by the user, including through URLs, payloads, or cookies, should be viewed with suspicion and can pose a security risk. This is particularly true when it comes to redirects, as an attacker could use tainted user-provided data to steer users towards a malicious website. To address this issue, one solution is to thoroughly verify user-provided data against a pre-approved list and reject any input that fails to meet the criteria. Another solution is to redesign the application so that redirects are not dependent on user-provided data.

References

  • MITRE, CWE-20 - Improper Input Validation
  • MITRE, CWE-601 - URL Redirection to Untrusted Site ('Open Redirect')
  • OWASP A5:2017-Broken Access Control
  • A01:2021 – Broken Access Control
  • SANS25

Ast Rule: function definition


safe-redirect

How to write a rule
function visit(node, filename, code) {
  if (!useImportFrom(node.context.imports, "flask", "Response")) {
    return;
  }
  if (!hasDecorator(node, "app.route") && !hasDecorator(node, "route")) {
    return;
  }

  const variablesFromRequests = [];
  const responsesVariables = [];

  const checkRedirectCall = (element, variables) => {
    if (element.astType === "return") {
      checkRedirectCall(element.value, variables);
    }

    if (element.astType === "assignment") {
      checkRedirectCall(element.right, variables);
    }


    if (element.astType === "functioncall") {
      if (element.functionName.value === "redirect") {
        if (element.arguments.values && element.arguments.values.length === 1) {
          const firstArgument = element.arguments.values[0].value;
          if (firstArgument.astType === "string" && variables.includes(firstArgument.value)) {
            const error = buildError(element.start.line, element.start.col,
              element.end.line, element.end.col,
              "do not use user-input", "CRITICAL", "SECURITY");

            const edit = buildEditUpdate(firstArgument.start.line, firstArgument.start.col, firstArgument.end.line, firstArgument.end.col, `url_for(${firstArgument.value})`);
            const fix = buildFix(`add mimetype argument`, [edit]);

            addError(error.addFix(fix));
          }
        }
      }
    }
  };



  node.content.elements && node.content.elements.forEach(e => {

    // First, find variables inferred from the requests
    if (e.astType === "assignment") {
      if (e.right.astType === "variableindex") {}
      if (e.left.astType === "string" && e.right.astType === "variableindex" && e.right.variable.value.startsWith("request.")) {
        variablesFromRequests.push(e.left.value);
      }
    }

    // Check if we build a response object
    if (e.astType === "assignment") {
      if (e.right.astType === "variableindex") {}
      if (e.left.astType === "string" && e.right.astType === "functioncall" && e.right.functionName.value === "Response") {
        responsesVariables.push(e.left.value);
      }
    }

    // Check if we assign to a response
    if (e.astType === "assignment" && e.left.astType === "variableindex" && e.right.astType === "string" && variablesFromRequests.includes(e.right.value)) {
      const vi = e.left;
      const possibleValues = responsesVariables.map(r => `${r}.headers`);
      if (possibleValues.includes(vi.variable.value) && vi.index.value === "'Location'") {
        const error = buildError(e.start.line, e.start.col,
          e.end.line, e.end.col,
          "do not use user-input", "CRITICAL", "SECURITY");
        addError(error);

      }
    }

    // check if we have a redirect call
    checkRedirectCall(e, variablesFromRequests);
  });


}

no-error.py

Expected test result: no error

from flask import request, redirect, Response

@app.route('my_redirect')
def flask_redirect():
    url = request.args["next_page"]
    return redirect(url_for(url))

error.py

Expected test result: has error

from flask import request, redirect, Response

@app.route('my_redirect')
def flask_redirect():
    url = request.args["next_page"]
    return redirect(url)

@app.route(bla)
def set_location_header():
    url = request.args["next_page"]
    response = Response("redirect somehwere", 302)
    response.headers['Location'] = url
    return response
Add comment

Log in to add a comment


    Be the first one to leave a comment!

Codiga Logo
Codiga Hub
  • Rulesets
  • Playground
  • Snippets
  • Cookbooks
soc-2 icon

We are SOC-2 Compliance Certified

G2 high performer medal

Codiga – All rights reserved 2022.