1
|
|
|
import logging |
|
|
|
|
2
|
|
|
import re |
3
|
|
|
import string |
4
|
|
|
from time import time, localtime, strftime |
5
|
|
|
|
6
|
|
|
from flask import current_app, Blueprint, render_template, request, redirect, flash, Response |
|
|
|
|
7
|
|
|
|
8
|
|
|
from spike.model import NaxsiRules, NaxsiRuleSets, ValueTemplates |
9
|
|
|
from spike.model import check_constraint, db, check_or_get_latest_sid |
10
|
|
|
|
11
|
|
|
rules = Blueprint('rules', __name__, url_prefix='/rules') |
|
|
|
|
12
|
|
|
|
13
|
|
|
# TODO : merge `ruleset_plain` and `ruleset_view` |
|
|
|
|
14
|
|
|
|
15
|
|
|
|
16
|
|
|
@rules.route("/") |
17
|
|
|
def index(): |
|
|
|
|
18
|
|
|
_rules = NaxsiRules.query.order_by(NaxsiRules.sid.desc()).all() |
19
|
|
|
if not _rules: |
20
|
|
|
flash("no rules found, please create one", "success") |
21
|
|
|
return redirect("/rules/new") |
22
|
|
|
return render_template("rules/index.html", rules=_rules) |
23
|
|
|
|
24
|
|
|
@rules.route("/plain/<int:sid>") |
25
|
|
|
def rule_plain(sid): |
|
|
|
|
26
|
|
|
sid = NaxsiRules.query.filter(NaxsiRules.sid == sid).first() |
27
|
|
|
return Response(__get_textual_representation_rule(sid), mimetype='text/plain') |
28
|
|
|
|
29
|
|
|
|
30
|
|
|
@rules.route("/rulesets/") |
31
|
|
|
def rulesets(): |
|
|
|
|
32
|
|
|
_rulesets = NaxsiRuleSets.query.order_by(NaxsiRuleSets.name).all() |
33
|
|
|
return render_template("rules/rulesets.html", rulesets=_rulesets) |
34
|
|
|
|
35
|
|
|
|
36
|
|
|
def __get_rules_for_ruleset(ruleset, with_header = True): |
|
|
|
|
37
|
|
|
|
38
|
|
|
_rules = NaxsiRules.query.filter( |
39
|
|
|
NaxsiRules.ruleset == ruleset.file, |
40
|
|
|
NaxsiRules.active == 1 |
41
|
|
|
).all() |
42
|
|
|
|
43
|
|
|
nxruleset = NaxsiRuleSets.query.filter(NaxsiRuleSets.file == ruleset.file).first() |
44
|
|
|
db.session.add(nxruleset) |
45
|
|
|
db.session.commit() |
46
|
|
|
text_rules = ''.join(map(__get_textual_representation_rule, _rules)) |
47
|
|
|
|
48
|
|
|
if with_header is False: |
49
|
|
|
return text_rules |
50
|
|
|
|
51
|
|
|
header = current_app.config["RULESET_HEADER"] |
52
|
|
|
header = header.replace("RULESET_DESC", ruleset.name) |
53
|
|
|
header = header.replace("RULESET_FILE", ruleset.file) |
54
|
|
|
header = header.replace( "RULESET_DATE", strftime("%F - %H:%M", localtime(time()))) |
|
|
|
|
55
|
|
|
|
56
|
|
|
return header + text_rules |
57
|
|
|
|
58
|
|
|
|
59
|
|
|
@rules.route("/rulesets/plain/") |
60
|
|
|
@rules.route("/rulesets/plain/<int:rid>") |
61
|
|
|
def ruleset_plain(rid=0): |
62
|
|
|
""" |
63
|
|
|
Show the rule `rid` in plain text |
64
|
|
|
:param int rid: Rule id |
65
|
|
|
""" |
66
|
|
|
if not rid: |
67
|
|
|
out = ''.join(map(__get_rules_for_ruleset, NaxsiRuleSets.query.all())) |
68
|
|
|
else: |
69
|
|
|
out = __get_rules_for_ruleset(NaxsiRuleSets.query.filter(NaxsiRuleSets.id == rid).first()) |
|
|
|
|
70
|
|
|
return Response(out, mimetype='text/plain') |
71
|
|
|
|
72
|
|
|
|
73
|
|
|
@rules.route("/rulesets/view/<int:rid>") |
74
|
|
|
def ruleset_view(rid=0): |
|
|
|
|
75
|
|
|
if not rid: |
76
|
|
|
return redirect("/rulesets/") |
77
|
|
|
ruleset = NaxsiRuleSets.query.filter(NaxsiRuleSets.id == rid).first() |
78
|
|
|
return render_template("rules/ruleset_view.html", r=ruleset, rout=__get_rules_for_ruleset(ruleset)) |
|
|
|
|
79
|
|
|
|
80
|
|
|
@rules.route("/rulesets/new", methods=["POST"]) |
81
|
|
|
def ruleset_new(): # TODO filter parameter |
|
|
|
|
82
|
|
|
rfile = request.form["rfile"].strip().lower() |
83
|
|
|
rname = request.form["rname"].strip().upper() |
84
|
|
|
|
85
|
|
|
cie = check_constraint("ruleset", rfile) |
86
|
|
|
if cie: |
87
|
|
|
flash("ERROR, ruleset exists: %s " % rfile, "error") |
88
|
|
|
return redirect("/rules/rulesets/") |
89
|
|
|
|
90
|
|
|
db.session.add(NaxsiRuleSets(rfile, rname, "naxsi-ruleset: %s" % rfile, 0, int(time()))) |
|
|
|
|
91
|
|
|
try: |
92
|
|
|
db.session.commit() |
93
|
|
|
except: |
|
|
|
|
94
|
|
|
db.session.rollback() |
95
|
|
|
flash("ERROR while trying to create ruleset: %s " % rfile, "error") |
96
|
|
|
|
97
|
|
|
flash("OK created: %s " % rfile, "success") |
98
|
|
|
return redirect("/rules/rulesets/") |
99
|
|
|
|
100
|
|
|
|
101
|
|
|
@rules.route("/select/<path:selector>", methods=["GET"]) |
102
|
|
|
def nx_select(selector=''): |
|
|
|
|
103
|
|
|
if not selector: |
104
|
|
|
return redirect("/rules/") |
105
|
|
|
|
106
|
|
|
sel = str(selector) |
107
|
|
|
logging.info("sel: %s ", sel) |
108
|
|
|
try: |
109
|
|
|
rs_val = sel.split(":")[1] |
110
|
|
|
except: |
|
|
|
|
111
|
|
|
return redirect("/rules/") |
112
|
|
|
|
113
|
|
|
if sel.startswith('r:'): |
114
|
|
|
_rules = NaxsiRules.query.filter(NaxsiRules.ruleset == rs_val).order_by(NaxsiRules.sid.desc()).all() |
|
|
|
|
115
|
|
|
selection = "Search ruleset: %s " % rs_val |
116
|
|
|
elif sel.startswith('id:'): |
117
|
|
|
_rules = NaxsiRules.query.filter(NaxsiRules.sid == rs_val).order_by(NaxsiRules.sid.desc()).all() |
|
|
|
|
118
|
|
|
selection = "Search sid: %s " % rs_val |
119
|
|
|
else: |
120
|
|
|
return redirect("/rules/") |
121
|
|
|
|
122
|
|
|
return render_template("rules/index.html", rules=_rules, selection=selection) |
123
|
|
|
|
124
|
|
|
|
125
|
|
|
@rules.route("/search/", methods=["GET"]) |
126
|
|
|
def search(): |
|
|
|
|
127
|
|
|
terms = request.args.get('s', '') |
128
|
|
|
|
129
|
|
|
if len(terms) < 2: |
130
|
|
|
return redirect('/rules') |
131
|
|
|
|
132
|
|
|
# No fancy injections |
133
|
|
|
whitelist = set(string.ascii_letters + string.digits + ':-_ ') |
134
|
|
|
filtered = ''.join(filter(whitelist.__contains__, terms)) |
135
|
|
|
|
136
|
|
|
if filtered.isdigit(): # get rule by id |
137
|
|
|
_rules = db.session.query(NaxsiRules).filter(NaxsiRules.sid == int(filtered)).all() |
|
|
|
|
138
|
|
|
else: |
139
|
|
|
expression = '%' + filtered + '%' |
140
|
|
|
_rules = db.session.query(NaxsiRules).filter( |
141
|
|
|
db.or_( |
142
|
|
|
NaxsiRules.msg.like(expression), |
143
|
|
|
NaxsiRules.rmks.like(expression), |
144
|
|
|
NaxsiRules.detection.like(expression) |
145
|
|
|
) |
146
|
|
|
).order_by(NaxsiRules.sid.desc()).all() |
147
|
|
|
return render_template("rules/index.html", rules=_rules, selection="Search: %s" % filtered, lsearch=terms) |
|
|
|
|
148
|
|
|
|
149
|
|
|
|
150
|
|
|
@rules.route("/new", methods=["GET", "POST"]) |
151
|
|
|
def new(): |
|
|
|
|
152
|
|
|
sid = check_or_get_latest_sid() |
153
|
|
|
|
154
|
|
|
if request.method == "GET": |
155
|
|
|
mz = ValueTemplates.query.filter(ValueTemplates.name == "naxsi_mz").all() |
|
|
|
|
156
|
|
|
_rulesets = NaxsiRuleSets.query.all() |
157
|
|
|
score = ValueTemplates.query.filter(ValueTemplates.name == "naxsi_score").all() |
158
|
|
|
return render_template("rules/new.html", mz=mz, rulesets=_rulesets, score=score, latestn=sid) |
|
|
|
|
159
|
|
|
|
160
|
|
|
# create new rule |
161
|
|
|
logging.debug('Posted new request: %s', request.form) |
162
|
|
|
|
163
|
|
|
detect = str(request.form["detection"]).strip() |
164
|
|
|
if not detect.startswith("str:") and not detect.startswith("rx:"): |
165
|
|
|
detect = "str:%s" % detect |
166
|
|
|
|
167
|
|
|
mz = "|".join(request.form.getlist("mz")) |
|
|
|
|
168
|
|
|
|
169
|
|
|
try: |
170
|
|
|
if request.form["custom_mz"] == "on": |
171
|
|
|
mz = "%s|%s" % (mz, request.form["custom_mz_val"]) |
|
|
|
|
172
|
|
|
except: |
|
|
|
|
173
|
|
|
pass |
|
|
|
|
174
|
|
|
|
175
|
|
|
score_raw = request.form["score"].strip() |
176
|
|
|
score_val = request.form["score_%s" % score_raw].strip() |
177
|
|
|
score = "%s:%s" % (score_raw, score_val) |
178
|
|
|
rmks = request.form["rmks"] |
179
|
|
|
ruleset = request.form["ruleset"] |
180
|
|
|
negative = 'negative' in request.form and request.form['negative'] == 'checked' |
181
|
|
|
|
182
|
|
|
nrule = NaxsiRules(request.form["msg"], detect, mz, score, sid, ruleset, rmks, "1", negative, int(time())) |
|
|
|
|
183
|
|
|
db.session.add(nrule) |
184
|
|
|
|
185
|
|
|
try: |
186
|
|
|
db.session.commit() |
187
|
|
|
flash("OK: created %s : %s" % (sid, request.form["msg"]), "success") |
188
|
|
|
return redirect("/rules/edit/%s" % sid) |
189
|
|
|
except: |
|
|
|
|
190
|
|
|
flash("ERROR while trying to create %s : %s" % (sid, request.form["msg"]), "error") |
|
|
|
|
191
|
|
|
|
192
|
|
|
return redirect("/rules/new") |
193
|
|
|
|
194
|
|
|
|
195
|
|
|
@rules.route("/edit/<path:sid>", methods=["GET", "POST"]) |
196
|
|
|
def edit(sid=''): |
|
|
|
|
197
|
|
|
if not sid: |
198
|
|
|
return redirect("/rules/") |
199
|
|
|
|
200
|
|
|
rinfo = NaxsiRules.query.filter(NaxsiRules.sid == sid).first() |
201
|
|
|
if not rinfo: |
202
|
|
|
return redirect("/rules/") |
203
|
|
|
|
204
|
|
|
mz = ValueTemplates.query.filter(ValueTemplates.name == "naxsi_mz").all() |
|
|
|
|
205
|
|
|
score = ValueTemplates.query.filter(ValueTemplates.name == "naxsi_score").all() |
206
|
|
|
_rulesets = NaxsiRuleSets.query.all() |
207
|
|
|
rruleset = NaxsiRuleSets.query.filter(NaxsiRuleSets.name == rinfo.ruleset).first() |
208
|
|
|
custom_mz = "" |
209
|
|
|
mz_check = rinfo.mz |
210
|
|
|
if re.search(r"^\$[A-Z]+:(.*)\|[A-Z]+", mz_check): |
211
|
|
|
custom_mz = mz_check |
212
|
|
|
rinfo.mz = "custom" |
213
|
|
|
return render_template("rules/edit.html", mz=mz, rulesets=_rulesets, score=score, rules_info=rinfo, |
|
|
|
|
214
|
|
|
rule_ruleset=rruleset, custom_mz=custom_mz) |
215
|
|
|
|
216
|
|
|
|
217
|
|
|
@rules.route("/save/<path:sid>", methods=["POST"]) |
218
|
|
|
def save(sid=''): # FIXME this is the exact same method as the `new` one. |
|
|
|
|
219
|
|
|
if not sid: |
220
|
|
|
return redirect("/rules/") |
221
|
|
|
|
222
|
|
|
# create new rule |
223
|
|
|
try: |
224
|
|
|
msg = request.form["msg"] |
225
|
|
|
detect = str(request.form["detection"]).strip() |
226
|
|
|
if not detect.startswith("str:") and not detect.startswith("rx:"): |
227
|
|
|
detect = "str:%s" % detect |
228
|
|
|
mz = "|".join(request.form.getlist("mz")) |
|
|
|
|
229
|
|
|
try: |
230
|
|
|
if request.form["custom_mz"] == "on": |
231
|
|
|
if len(mz) > 1: |
232
|
|
|
mz = "%s|%s" % (request.form["custom_mz_val"], mz) |
|
|
|
|
233
|
|
|
else: |
234
|
|
|
mz = "%s" % (request.form["custom_mz_val"]) |
|
|
|
|
235
|
|
|
except: |
|
|
|
|
236
|
|
|
pass |
|
|
|
|
237
|
|
|
score_raw = request.form["score"].strip() |
238
|
|
|
score_val = request.form["score_%s" % score_raw].strip() |
239
|
|
|
score = "%s:%s" % (score_raw, score_val) |
240
|
|
|
# sid = nr["sid"] |
241
|
|
|
rmks = request.form["rmks"] |
242
|
|
|
ruleset = request.form["ruleset"] |
243
|
|
|
active = request.form["active"] |
244
|
|
|
negative = 'negative' in request.form and request.form['negative'] == 'checked' |
245
|
|
|
except: |
|
|
|
|
246
|
|
|
flash('ERROR - please select MZ/Score <a href="javascript:alert(history.back)">Go Back</a>', "error") |
|
|
|
|
247
|
|
|
return redirect("/rules/edit/%s" % sid) |
248
|
|
|
|
249
|
|
|
nrule = NaxsiRules.query.filter(NaxsiRules.sid == sid).first() |
250
|
|
|
nrule.msg = msg |
251
|
|
|
nrule.detection = detect |
252
|
|
|
nrule.mz = mz |
253
|
|
|
nrule.score = score |
254
|
|
|
nrule.ruleset = ruleset |
255
|
|
|
nrule.rmks = rmks |
256
|
|
|
nrule.active = active |
257
|
|
|
nrule.negative = negative |
258
|
|
|
nrule.timestamp = int(time()) |
259
|
|
|
db.session.add(nrule) |
260
|
|
|
try: |
261
|
|
|
db.session.commit() |
262
|
|
|
except: |
|
|
|
|
263
|
|
|
flash("ERROR while trying to update %s : %s" % (sid, msg), "error") |
264
|
|
|
return redirect("/rules/edit/%s" % sid) |
265
|
|
|
|
266
|
|
|
|
267
|
|
|
@rules.route("/view/<path:sid>", methods=["GET"]) |
268
|
|
|
def view(sid=''): |
|
|
|
|
269
|
|
|
if not sid: |
270
|
|
|
return redirect("/rules/") |
271
|
|
|
|
272
|
|
|
rinfo = NaxsiRules.query.filter(NaxsiRules.sid == sid).first() |
273
|
|
|
if not rinfo: |
274
|
|
|
return redirect("/rules/") |
275
|
|
|
|
276
|
|
|
return render_template("rules/view.html", rule=rinfo, rtext=__get_textual_representation_rule(rinfo, full=0)) |
|
|
|
|
277
|
|
|
|
278
|
|
|
|
279
|
|
|
@rules.route("/del/<path:sid>", methods=["GET"]) |
280
|
|
|
def del_sid(sid=''): |
|
|
|
|
281
|
|
|
if not sid: |
282
|
|
|
return redirect("/rules/") |
283
|
|
|
|
284
|
|
|
nrule = NaxsiRules.query.filter(NaxsiRules.sid == sid).first() |
285
|
|
|
if not nrule: |
286
|
|
|
return redirect("/rules/") |
287
|
|
|
|
288
|
|
|
db.session.delete(nrule) |
289
|
|
|
try: |
290
|
|
|
db.session.commit() |
291
|
|
|
flash("OK: deleted %s : %s" % (sid, nrule.msg), "success") |
292
|
|
|
except: |
|
|
|
|
293
|
|
|
flash("ERROR while trying to update %s : %s" % (sid, nrule.msg), "error") |
294
|
|
|
|
295
|
|
|
return redirect("/rules/") |
296
|
|
|
|
297
|
|
|
|
298
|
|
|
@rules.route("/deact/<path:sid>", methods=["GET"]) |
299
|
|
|
def deact_sid(sid=''): |
|
|
|
|
300
|
|
|
if not sid: |
301
|
|
|
return redirect("/rules/") |
302
|
|
|
|
303
|
|
|
nrule = NaxsiRules.query.filter(NaxsiRules.sid == sid).first() |
304
|
|
|
if not nrule: |
305
|
|
|
return redirect("/rules/") |
306
|
|
|
|
307
|
|
|
if nrule.active == 0: |
308
|
|
|
nrule.active = 1 |
309
|
|
|
fm = "reactivate" |
|
|
|
|
310
|
|
|
else: |
311
|
|
|
nrule.active = 0 |
312
|
|
|
fm = "deactivate" |
|
|
|
|
313
|
|
|
|
314
|
|
|
db.session.add(nrule) |
315
|
|
|
try: |
316
|
|
|
db.session.commit() |
317
|
|
|
flash("OK: %s %sd : %s" % (fm, sid, nrule.msg), "success") |
318
|
|
|
except: |
|
|
|
|
319
|
|
|
flash("ERROR while trying to %s %s : %s" % (fm, sid, nrule.msg), "error") |
320
|
|
|
|
321
|
|
|
rinfo = NaxsiRules.query.filter(NaxsiRules.sid == sid).first() |
322
|
|
|
if not rinfo: |
323
|
|
|
return redirect("/rules/") |
324
|
|
|
|
325
|
|
|
mz = ValueTemplates.query.filter(ValueTemplates.name == "naxsi_mz").all() |
|
|
|
|
326
|
|
|
score = ValueTemplates.query.filter(ValueTemplates.name == "naxsi_score").all() |
327
|
|
|
_rulesets = NaxsiRuleSets.query.all() |
328
|
|
|
return render_template("rules/edit.html", mz=mz, rulesets=_rulesets, score=score, rules_info=rinfo) |
|
|
|
|
329
|
|
|
|
330
|
|
|
|
331
|
|
|
def __get_textual_representation_rule(rule, full=1): |
|
|
|
|
332
|
|
|
rdate = strftime("%F - %H:%M", localtime(float(str(rule.timestamp)))) |
333
|
|
|
rmks = "# ".join(rule.rmks.strip().split("\n")) |
334
|
|
|
detect = rule.detection.lower() if rule.detection.startswith("str:") else rule.detection |
|
|
|
|
335
|
|
|
negate = 'negative' if rule.negative == 1 else '' |
336
|
|
|
|
337
|
|
|
if full == 1: |
338
|
|
|
nout = """ |
339
|
|
|
# |
340
|
|
|
# sid: %s | date: %s |
341
|
|
|
# |
342
|
|
|
# %s |
343
|
|
|
# |
344
|
|
|
MainRule %s "%s" "msg:%s" "mz:%s" "s:%s" id:%s ; |
345
|
|
|
|
346
|
|
|
""" % (rule.sid, rdate, rmks, negate, detect, rule.msg, rule.mz, rule.score, rule.sid) |
|
|
|
|
347
|
|
|
else: |
348
|
|
|
nout = """MainRule %s "%s" "msg:%s" "mz:%s" "s:%s" id:%s ;""" % \ |
349
|
|
|
(negate, rule.detection, rule.msg, rule.mz, rule.score, rule.sid) |
350
|
|
|
|
351
|
|
|
return nout |
352
|
|
|
|
The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:
If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.