Esempio n. 1
0
def swallows_exceptions(js_dest: str, exclude: list = None) -> bool:
    """
    Search for ``catch`` blocks that are empty or only have comments.

    See `REQ.161 <https://fluidattacks.com/web/rules/161/>`_.

    See `CWE-391 <https://cwe.mitre.org/data/definitions/391.html>`_.

    :param js_dest: Path to a JavaScript source file or package.
    :param exclude: Paths that contains any string from this list are ignored.
    """
    # Empty() grammar matches 'anything'
    # ~Empty() grammar matches 'not anything' or 'nothing'
    classic = Suppress(Keyword('catch')) + nestedExpr(opener='(', closer=')') \
        + nestedExpr(opener='{', closer='}', content=~Empty())

    modern = Suppress('.' + Keyword('catch')) + nestedExpr(
        opener='(', closer=')', content=~Empty())

    grammar = MatchFirst([classic, modern])
    grammar.ignore(cppStyleComment)

    try:
        matches = lang.path_contains_grammar(grammar, js_dest, LANGUAGE_SPECS,
                                             exclude)
    except FileNotFoundError:
        show_unknown('File does not exist', details=dict(code_dest=js_dest))
    else:
        if matches:
            show_open('Code has empty "catch" blocks',
                      details=dict(matched=matches))
            return True
        show_close('Code does not have empty "catch" blocks',
                   details=dict(code_dest=js_dest))
    return False
Esempio n. 2
0
def has_switch_without_default(js_dest: str, exclude: list = None) -> bool:
    r"""
    Check if all ``switch``\ es have a ``default`` clause.

    See `REQ.161 <https://fluidattacks.com/web/rules/161/>`_.

    See `CWE-478 <https://cwe.mitre.org/data/definitions/478.html>`_.

    :param js_dest: Path to a JavaScript source file or package.
    :param exclude: Paths that contains any string from this list are ignored.
    """
    switch = Keyword('switch') + nestedExpr(opener='(', closer=')')
    switch_block = Suppress(switch) + nestedExpr(opener='{', closer='}')
    switch_block.ignore(cppStyleComment)
    switch_block.ignore(L_CHAR)
    switch_block.ignore(L_STRING)
    switch_block.addCondition(lambda x: not RE_HAVES_DEFAULT.search(str(x)))
    try:
        matches = lang.path_contains_grammar(switch_block, js_dest,
                                             LANGUAGE_SPECS, exclude)
    except FileNotFoundError:
        show_unknown('File does not exist', details=dict(code_dest=js_dest))
        return False
    if matches:
        show_open('Code does not have "switch" with "default" clause',
                  details=dict(matched=matches))
        return True
    show_close('Code has "switch" with "default" clause',
               details=dict(code_dest=js_dest))
    return False
Esempio n. 3
0
def _declares_catch_for_exceptions(
        java_dest: str,
        exceptions_list: list,
        open_msg: str,
        closed_msg: str,
        exclude: list = None) -> bool:
    """Search for the declaration of catch for the given exceptions."""
    any_exception = L_VAR_CHAIN_NAME
    provided_exception = MatchFirst(
        [Keyword(exception) for exception in exceptions_list])

    exception_group = delimitedList(expr=any_exception, delim='|')
    exception_group.addCondition(
        # Ensure that at least one exception in the group is the provided one
        lambda tokens: any(provided_exception.matches(tok) for tok in tokens))

    grammar = Suppress(Keyword('catch')) + nestedExpr(
        opener='(', closer=')', content=(
            exception_group + Suppress(Optional(L_VAR_NAME))))
    grammar.ignore(javaStyleComment)
    grammar.ignore(L_STRING)
    grammar.ignore(L_CHAR)

    try:
        matches = lang.path_contains_grammar(grammar, java_dest,
                                             LANGUAGE_SPECS, exclude)
    except FileNotFoundError:
        show_unknown('File does not exist', details=dict(code_dest=java_dest))
    else:
        if matches:
            show_open(open_msg, details=dict(matched=matches))
            return True
        show_close(closed_msg, details=dict(code_dest=java_dest))
    return False
Esempio n. 4
0
def uses_print_stack_trace(java_dest: str, exclude: list = None) -> bool:
    """
    Search for ``printStackTrace`` calls in a path.

    See `CWE-209 <https://cwe.mitre.org/data/definitions/209.html>`_.

    :param java_dest: Path to a Java source file or package.
    :param exclude: Paths that contains any string from this list are ignored.
    """
    grammar = L_VAR_NAME + '.' + Keyword('printStackTrace')
    grammar.ignore(javaStyleComment)
    grammar.ignore(L_STRING)
    grammar.ignore(L_CHAR)

    try:
        matches = lang.path_contains_grammar(grammar, java_dest,
                                             LANGUAGE_SPECS, exclude)
    except FileNotFoundError:
        show_unknown('File does not exist', details=dict(code_dest=java_dest))
    else:
        if matches:
            show_open('Code uses Throwable.printStackTrace() method',
                      details=dict(matched=matches,
                                   total_vulns=len(matches)))
            return True
        show_close('Code does not use Throwable.printStackTrace() method',
                   details=dict(code_dest=java_dest))
    return False
Esempio n. 5
0
def uses_unencrypted_sockets(code_dest: str,
                             exclude: list = None,
                             lang_specs: dict = None) -> bool:
    """
    Check if there are unencrypted web sockets URI schemes in code (`ws://`).

    :param code_dest: Path to the file or directory to be tested.
    :param exclude: Paths that contains any string from this list are ignored.
    :param lang_specs: Specifications of the language, see
                       fluidasserts.lang.java.LANGUAGE_SPECS for an example.
    """
    encrypted_re = re.compile(r'^wss://.*$', flags=re.I)
    unencrypted_re = re.compile(r'^ws://.*$', flags=re.I)

    encrypted_grammar = MatchFirst([QuotedString('"'), QuotedString("'")])
    unencrypted_grammar = MatchFirst([QuotedString('"'), QuotedString("'")])

    encrypted_grammar.addCondition(lambda x: encrypted_re.search(x[0]))
    unencrypted_grammar.addCondition(lambda x: unencrypted_re.search(x[0]))

    try:
        unencrypted = lang.path_contains_grammar(unencrypted_grammar,
                                                 code_dest, LANGUAGE_SPECS,
                                                 exclude)
    except FileNotFoundError:
        show_unknown('File does not exist', details=dict(code_dest=code_dest))
        return False

    if unencrypted:
        show_open('Code uses web sockets over an unencrypted channel',
                  details=dict(vulnerable_uris=unencrypted))
        return True

    encrypted = lang.path_contains_grammar(encrypted_grammar, code_dest,
                                           LANGUAGE_SPECS, exclude)

    if encrypted:
        msg = 'Code uses web sockets over an encrypted channel'
    else:
        msg = 'Cose does not use web sockets'
    show_close(msg, details=dict(code_dest=code_dest, checked_uris=encrypted))
    return False
Esempio n. 6
0
def has_insecure_randoms(java_dest: str, exclude: list = None) -> bool:
    r"""
    Check if code uses insecure random generators.

    - ``java.util.Random()``.
    - ``java.lang.Math.random()``.

    See `REQ.224 <https://fluidattacks.com/web/rules/224/>`_.

    :param java_dest: Path to a Java source file or package.
    :param exclude: Paths that contains any string from this list are ignored.
    """
    _java = Keyword('java')
    _util = Keyword('util')
    _lang = Keyword('lang')
    _math = Keyword('Math')
    _import = Keyword('import')
    _random_minus = Keyword('random')
    _random_mayus = Keyword('Random')
    _args = nestedExpr()

    insecure_randoms = MatchFirst([
        # util.Random()
        _util + '.' + _random_mayus + _args,
        # Math.random()
        _math + '.' + _random_minus + _args,
        # import java.util.Random
        _import + _java + '.' + _util + '.' + _random_mayus,
        # import java.lang.Math.random
        _import + _java + '.' + _lang + '.' + _math + '.' + _random_minus,
    ])
    insecure_randoms.ignore(javaStyleComment)
    insecure_randoms.ignore(L_CHAR)
    insecure_randoms.ignore(L_STRING)

    try:
        matches = lang.path_contains_grammar(insecure_randoms, java_dest,
                                             LANGUAGE_SPECS, exclude)
    except FileNotFoundError:
        show_unknown('File does not exist', details=dict(location=java_dest))
        return False
    if not matches:
        show_close('Code does not use insecure random generators',
                   details=dict(location=java_dest))
        return False
    show_open('Code uses insecure random generators',
              details=dict(matches=matches))
    return True
Esempio n. 7
0
def uses_insecure_cipher(java_dest: str, algorithm: str,
                         exclude: list = None) -> bool:
    """
    Check if code uses an insecure cipher algorithm.

    See `REQ.148 <https://fluidattacks.com/web/rules/148/>`_.
    See `REQ.149 <https://fluidattacks.com/web/rules/149/>`_.

    :param java_dest: Path to a Java source file or package.
    :param algorithm: Insecure algorithm.
    :param exclude: Paths that contains any string from this list are ignored.
    """
    method = 'Cipher.getInstance("{}")'.format(algorithm.upper())
    op_mode = '/' + oneOf('CBC ECB', caseless=True)
    padding = '/' + oneOf('NoPadding PKCS5Padding', caseless=True)
    algorithm = '"' + CaselessKeyword(algorithm) + Optional(
        op_mode + Optional(padding)) + '"'

    grammar = Suppress(Keyword('Cipher') + '.' + Keyword('getInstance')) + \
        nestedExpr()
    grammar.ignore(javaStyleComment)
    grammar.addCondition(
        # Ensure that at least one token is the provided algorithm
        lambda tokens: tokens.asList() and any(
            algorithm.matches(tok) for tok in tokens[0]))
    try:
        matches = lang.path_contains_grammar(grammar, java_dest,
                                             LANGUAGE_SPECS, exclude)
    except FileNotFoundError:
        show_unknown('File does not exist', details=dict(location=java_dest))
        return False
    if not matches:
        show_close('Code does not use {} method'.format(method),
                   details=dict(location=java_dest))
        return False
    show_open('Code uses {} method'.format(method),
              details=dict(matches=matches))
    return True