Passed
Branch master (bb419f)
by jvo
01:37
created

NaxsiRules.p_dummy()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.125
Metric Value
cc 1
dl 0
loc 2
ccs 1
cts 2
cp 0.5
crap 1.125
rs 10
1 1
from time import strftime, localtime
2
3 1
from spike.model import db
4 1
from shlex import shlex
5
6
7 1
class NaxsiRules(db.Model):
8 1
    __bind_key__ = 'rules'
9 1
    __tablename__ = 'naxsi_rules'
10
11 1
    id = db.Column(db.Integer, primary_key=True)
12 1
    msg = db.Column(db.String(), nullable=False)
13 1
    detection = db.Column(db.String(1024), nullable=False)
14 1
    mz = db.Column(db.String(1024), nullable=False)
15 1
    score = db.Column(db.String(1024), nullable=False)
16 1
    sid = db.Column(db.Integer, nullable=False, unique=True)
17 1
    ruleset = db.Column(db.String(1024), nullable=False)
18 1
    rmks = db.Column(db.Text, nullable=True, server_default="")
19 1
    active = db.Column(db.Integer, nullable=False, server_default="1")
20 1
    negative = db.Column(db.Integer, nullable=False, server_default='0')
21 1
    timestamp = db.Column(db.Integer, nullable=False)
22
23 1
    warnings = []
24 1
    error = []
25 1
    mr_kw = ["MainRule", "BasicRule", "main_rule", "basic_rule"]
26 1
    static_mz = {"$ARGS_VAR", "$BODY_VAR", "$URL", "$HEADERS_VAR"}
27 1
    full_zones = {"ARGS", "BODY", "URL", "HEADERS", "FILE_EXT", "RAW_BODY"}
28 1
    rx_mz = {"$ARGS_VAR_X", "$BODY_VAR_X", "$URL_X", "$HEADERS_VAR_X"}
29 1
    sub_mz = list(static_mz) + list(full_zones) + list(rx_mz)
30
31 1
    def __init__(self, msg, detection, mz, score, sid, ruleset, rmks, active, negative, timestamp):
32 1
        self.msg = msg
33 1
        self.detection = detection
34 1
        self.mz = mz
35 1
        self.score = score
36 1
        self.sid = sid
37 1
        self.ruleset = ruleset
38 1
        self.rmks = rmks
39 1
        self.active = active
40 1
        self.negative = 1 if negative == 'checked' else 0
41 1
        self.timestamp = timestamp
42
43 1
    def fullstr(self):
44 1
        rdate = strftime("%F - %H:%M", localtime(float(str(self.timestamp))))
45 1
        rmks = "# ".join(self.rmks.strip().split("\n"))
46 1
        return "#\n# sid: {0} | date: {1}\n#\n# {2}\n#\n{3}".format(self.sid, rdate, rmks, self.__str__())
47
48 1
    def __str__(self):
49 1
        negate = 'negative' if self.negative == 1 else ''
50 1
        return 'MainRule {} "{}" "msg:{}" "mz:{}" "s:{}" id:{} ;'.format(
51
            negate, self.detection, self.msg, self.mz, self.score, self.sid)
52
53 1
    def validate(self):
54 1
        self.p_mz(self.mz)
55 1
        self.p_id(self.sid)
56 1
        self.p_detection(self.detection)
57
58 1
        if not self.msg:
59
            self.warnings.append("Rule has no 'msg:'.")
60 1
        if not self.score:
61
            self.error.append("Rule has no score.")
62
63 1
    def fail(self, msg):
64
        self.error.append(msg)
65
        return False
66
67
    # Bellow are parsers for specific parts of a rule
68 1
    def p_dummy(self, s, assign=False):
0 ignored issues
show
Coding Style Naming introduced by
The name s does not conform to the argument naming conventions ([a-z_][a-z0-9_]{1,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Unused Code introduced by
The argument s seems to be unused.
Loading history...
Unused Code introduced by
The argument assign seems to be unused.
Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
69
        return True
70
71 1
    def p_detection(self, s, assign=False):
0 ignored issues
show
Coding Style Naming introduced by
The name s does not conform to the argument naming conventions ([a-z_][a-z0-9_]{1,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
72 1
        if not s.startswith("str:") and not s.startswith("rx:"):
73
            self.fail("detection {} is neither rx: or str:".format(s))
74 1
        if not s.islower():
75 1
            self.warnings.append("detection {} is not lower-case. naxsi is case-insensitive".format(s))
76 1
        if assign is True:
77
            self.detection = s
78 1
        return True
79
80 1
    def p_genericstr(self, s, assign=False):
0 ignored issues
show
Coding Style Naming introduced by
The name s does not conform to the argument naming conventions ([a-z_][a-z0-9_]{1,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Unused Code introduced by
The argument assign seems to be unused.
Loading history...
81
        if s and not s.islower():
82
            self.warnings.append("Pattern ({0}) is not lower-case.".format(s))
83
        return True
84
85 1
    def p_mz(self, s, assign=False):
0 ignored issues
show
Coding Style Naming introduced by
The name s does not conform to the argument naming conventions ([a-z_][a-z0-9_]{1,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
86 1
        has_zone = False
87 1
        mz_state = set()
88 1
        locs = s.split('|')
89 1
        for loc in locs:
90 1
            kw = loc
91 1
            arg = None
92 1
            if loc.startswith("$"):
93
                try:
94
                    kw, arg = loc.split(":")
95
                except ValueError:
96
                    return self.fail("Missing 2nd part after ':' in {0}".format(loc))
97
            # check it is a valid keyword
98 1
            if kw not in self.sub_mz:
99
                return self.fail("'{0}' no a known sub-part of mz : {1}".format(kw, self.sub_mz))
100 1
            mz_state.add(kw)
101
            # verify the rule doesn't attempt to target REGEX and STATIC _VAR/URL at the same time
102 1
            if len(self.rx_mz & mz_state) and len(self.static_mz & mz_state):
103
                return self.fail("You can't mix static $* with regex $*_X ({})".format(str(mz_state)))
104
            # just a gentle reminder
105 1
            if arg and arg.islower() is False:
106
                self.warnings.append("{0} in {1} is not lowercase. naxsi is case-insensitive".format(arg, loc))
107
            # the rule targets an actual zone
108 1
            if kw not in ["$URL", "$URL_X"] and kw in (self.rx_mz | self.full_zones | self.static_mz):
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after in.
Loading history...
109 1
                has_zone = True
110 1
        if has_zone is False:
111
            return self.fail("The rule/whitelist doesn't target any zone.")
112 1
        if assign is True:
113
            self.mz = s
114 1
        return True
115
116 1
    def p_id(self, s, assign=False):
0 ignored issues
show
Coding Style Naming introduced by
The name s does not conform to the argument naming conventions ([a-z_][a-z0-9_]{1,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
117 1
        try:
118 1
            x = int(s)
0 ignored issues
show
Coding Style Naming introduced by
The name x does not conform to the variable naming conventions ([a-z_][a-z0-9_]{1,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
119 1
            if x < 10000:
120 1
                self.warnings.append("rule IDs below 10k are reserved ({0})".format(x))
121
        except ValueError:
122
            self.error.append("id:{0} is not numeric".format(s))
123
            return False
124 1
        if assign is True:
125
            self.sid = x
126 1
        return True
127
128 1
    @staticmethod
129
    def splitter(s):
0 ignored issues
show
Coding Style Naming introduced by
The name s does not conform to the argument naming conventions ([a-z_][a-z0-9_]{1,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
130
        lexer = shlex(s)
131
        lexer.quotes = '"\''
132
        lexer.whitespace_split = True
133
        items = list(iter(lexer.get_token, ''))
134
        return ([i for i in items if i[0] in "\"'"] +
135
                [i for i in items if i[0] not in "\"'"])
136
137 1
    def parse_rule(self, x):
0 ignored issues
show
Coding Style Naming introduced by
The name x does not conform to the argument naming conventions ([a-z_][a-z0-9_]{1,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
138
        """
139
        Parse and validate a full naxsi rule
140
        :param x: raw rule
141
        :return: [True|False, dict]
142
        """
143
        xfrag_kw = {"id:": self.p_id, "str:": self.p_genericstr,
144
                    "rx:": self.p_genericstr, "msg:": self.p_dummy, "mz:": self.p_mz,
145
                    "negative": self.p_dummy, "s:": self.p_dummy}
146
147
        split = self.splitter(x)  # parse string
148
149
150
        sect = set(self.mr_kw) & set(split) # check if it's a MainRule/BasicRule, store&delete kw
151
152
        if len(sect) != 1:
153
            return self.fail("no (or multiple) mainrule/basicrule keyword.")
154
155
        split.remove(sect.pop())
156
157
        if ";" in split:
158
            split.remove(";")
159
160
        while True:  # iterate while there is data, as handlers can defer
161
162
            if not split: # we are done
163
                break
164
165
            for kw in split:
166
                okw = kw
167
                kw = kw.strip()
168
169
                # clean-up quotes or semicolon
170
                if kw.endswith(";"):
171
                    kw = kw[:-1]
172
                if kw.startswith(('"', "'")) and (kw[0] == kw[-1]):
173
                    kw = kw[1:-1]
174
                for frag_kw in xfrag_kw:
175
                    ret = False
176
                    if kw.startswith(frag_kw):
177
                        # parser funcs returns True/False
178
                        ret = xfrag_kw[frag_kw](kw[len(frag_kw):])
179
                        if ret is False:
180
                            return self.fail("parsing of element '{0}' failed.".format(kw))
181
                        if ret is True:
182
                            split.remove(okw)
183
                        break
184
                # we have an item that wasn't successfully parsed
185
                if okw in split and ret is not None:
186
                    return False
187
        return True
188