2
# try to provide minimal multi-version support
4
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
6
from http.server import BaseHTTPRequestHandler, HTTPServer
10
import urllib.parse as urlparse
23
class Handler(BaseHTTPRequestHandler):
28
# if it's a background-forwarded request ...
29
if ("naxsi_sig" in self.headers.keys()):
31
print ("Exception catched.")
32
print ("ExUrl: "+self.headers["naxsi_sig"])
33
nx.eat_rule(self.headers["naxsi_sig"])
36
# user wanna reload its config
37
if (self.path.startswith("/write_and_reload")):
39
print ("writting rules, reloading nginx.")
40
if self.path.find("?servmd5=") != -1:
41
print ("writting and reloading specific server."+self.path[self.path.find("?servmd5=")+9:])
42
nx.dump_rules(self.path[self.path.find("?servmd5=")+9:])
44
print ("writting and reloading all servers.")
48
self.send_response(302)
49
self.send_header('Location', '/')
52
print ("Not reloading anything as user is non-root")
53
self.send_response(200)
55
if sys.version_info > (3, 0):
56
self.wfile.write(bytes("Not root, not reloading anything.", 'utf-8'))
58
self.wfile.write("Not root, not reloading anything.")
60
if params.log is not None:
61
# else, read possible log file, show ui/report
62
self.feed_from_logs(params.log)
63
message = self.ui_report()
64
self.send_response(200)
66
if sys.version_info > (3, 0):
67
self.wfile.write(bytes(message, 'utf-8'))
69
self.wfile.write(message)
70
def feed_from_logs(self, logfile):
72
print ("Opening log file for #1 time.")
73
nx.log_fd = open(params.log)
75
print ("log file already opened")
76
# we're at the begining of the file ...
77
if nx.log_fd.tell() == 0:
78
print ("at the beginning of file.")
80
tmpbuf = nx.log_fd.readline()
84
if tmpbuf.find("NAXSI_FMT: ") != -1 and tmpbuf.find(", client: ") != -1:
85
sys.stdout.write("[eating rule]")
86
nx.eat_rule(tmpbuf[tmpbuf.find("NAXSI_FMT: ") + 11:tmpbuf.find(", client: ")])
89
nbr = nx.get_written_rules_count()
90
nbs = nx.get_exception_count()
113
message += "You currently have "+str(nbr)
114
message += " rules generated by naxsi </b><br>"
115
message += "You have a total of "+str(nbs)+" exceptions hit.</br>"
117
message += "You should reload nginx's config.</br>"
118
message += "<a href='/write_and_reload' style=nx_ko>Write rules and reload for <b>ALL</b> sites.</a></br>"
119
message += nx.display_written_rules()
126
fd = open(params.rules, "r")
128
print ("Unable to open rules file : "+params.rules)
131
rid = re.search('id:([0-9]+)', rules)
134
ptr = re.search('str:([^"]+)', rules)
137
self.static[str(rid.group(1))] = cgi.escape(ptr.group(1))
139
def dump_rules(self, server=None):
141
fd = open(params.dst, "a+")
143
fd = open(params.dst+"."+hashlib.md5(server.encode('utf-8')).hexdigest(), "a+")
144
cur = self.con.cursor()
146
cur.execute("SELECT id, uri, zone, var_name, "
147
"server from tmp_rules where written = 0")
149
cur.execute("SELECT id, uri, zone, var_name, "
150
"server from tmp_rules where written = 0 and server = ?", [server])
152
print ("Writting "+str(len(rr))+" rules.")
153
for i in range(len(rr)):
154
tmprule = "BasicRule wl:"+str(rr[i][0])+" \"mz:"
155
if len(rr[i][1]) > 0:
156
tmprule += "$URL:"+rr[i][1]+"|"
157
if len(rr[i][3]) > 0:
158
tmprule += "$"+rr[i][2]+"_VAR:"+rr[i][3]+"\" "
160
tmprule += rr[i][2]+"\" "
161
tmprule += "; #"+rr[i][4]+"\n"
162
cur.execute("UPDATE tmp_rules SET written=1 WHERE id=? and "
163
"uri=? and zone=? and var_name=? and server=?",
164
[rr[i][0], rr[i][1], rr[i][2], rr[i][3], rr[i][4]])
168
print ("Generated Rule : "+tmprule)
170
def gen_write(self, mid, uri, zone, var_name, server):
171
cur = self.con.cursor()
172
cur.execute("SELECT count(*) from tmp_rules where id=? and uri=? "
173
"and zone=? and var_name=? and server=?",
174
[mid, uri, zone, var_name, server])
178
print ("already present in tmp_rules ...")
180
cur.execute("INSERT INTO tmp_rules (id, uri, zone, var_name, "
181
"server, written) VALUES (?, ?, ?, ?, ?, 0)",
182
[mid, uri, zone, var_name, server])
184
def agreggate_rules(self, mid=0, zone="", var_name=""):
185
cur = self.con.cursor()
186
cur.execute("SELECT id,uri,zone,var_name,server FROM received_sigs"
187
" GROUP BY zone,var_name,id ORDER BY zone,var_name,id")
189
for i in range(len(rr)):
190
if len(rr[i][2]) > 0 and len(rr[i][3]) > 0:
191
self.gen_write(rr[i][0], "", rr[i][2], rr[i][3], rr[i][4])
193
if len(rr[i][3]) <= 0:
194
self.gen_write(rr[i][0], rr[i][1], rr[i][2], rr[i][3], rr[i][4])
197
return self.con.cursor()
198
def get_written_rules_count(self, server=None):
199
cur = self.con.cursor()
201
cur.execute("SELECT COUNT(id) FROM tmp_rules where written = 0")
203
cur.execute("SELECT COUNT(id) FROM tmp_rules where written = 0 and server = ?", [server])
206
def display_written_rules(self):
208
cur = self.con.cursor()
209
cur.execute("SELECT distinct(server) "
213
for i in range(len(rr)):
214
print ("adding elems !")
215
if self.get_written_rules_count(rr[i][0]) > 0:
219
msg += """<a style={4} href='/write_and_reload?servmd5={0}'>
220
[write&reload <b>{1}</b></a>|{2} pending rules|
221
filename:{3}]</br>""".format(rr[i][0], rr[i][0],
222
str(self.get_written_rules_count(rr[i][0])),
223
params.dst+"."+hashlib.md5(rr[i][0].encode('utf-8')).hexdigest(),
226
cur.execute("SELECT id,uri,zone,var_name,server"
227
" FROM tmp_rules where written = 0")
230
msg += "Authorizing :</br>"
231
for i in range(len(rr)):
233
if (str(rr[i][0]) in self.static.keys()):
234
pattern = nx.static[str(rr[i][0])]
235
if len(rr[i][2]) > 0 and len(rr[i][3]) > 0:
236
msg += """<b style=nx_ok>[{0}]</b> -- pattern '{1}'
237
({2}) authorized on URL '{3}' for argument '{4}'
238
of zone {5}</br>""".format(str(rr[i][4]), pattern,
239
str(rr[i][0]), rr[i][1],
242
if len(rr[i][3]) <= 0:
243
msg += """<b style=nx_ok>[{0}]</b> --
244
pattern '{1}' ({2}) authorized on url '{3}'
245
for zone {4}</br>""".format(str(rr[i][4]),
252
def get_exception_count(self):
253
cur = self.con.cursor()
254
cur.execute("SELECT COUNT(id) FROM received_sigs")
257
def eat_rule(self, source):
262
tmpdict = urlparse.parse_qsl(source)
263
for i in range(len(tmpdict)):
264
if (tmpdict[i][0][-1] >= '0' and tmpdict[i][0][-1] <= '9' and
265
tmpdict[i][0][-1] != ridx):
266
currdict["uri"] = uri
267
currdict["server"] = server
268
if ("var_name" not in currdict):
269
currdict["var_name"] = ""
270
currdict["md5"] = hashlib.md5((currdict["uri"]+
274
currdict["var_name"]).encode('utf-8')).hexdigest()
275
# print ('#1 here:'+currdict["md5"])
276
self.fatdict.append(currdict)
278
ridx = tmpdict[i][0][-1]
279
if (tmpdict[i][0].startswith("server")):
280
server = tmpdict[i][1]
281
if (tmpdict[i][0].startswith("uri")):
283
if (tmpdict[i][0].startswith("id")):
284
currdict["id"] = tmpdict[i][1]
285
if (tmpdict[i][0].startswith("zone")):
286
currdict["zone"] = tmpdict[i][1]
287
if (tmpdict[i][0].startswith("var_name")):
288
currdict["var_name"] = tmpdict[i][1]
289
currdict["uri"] = uri
290
currdict["server"] = server
291
if ("var_name" not in currdict):
292
currdict["var_name"] = ""
293
currdict["md5"] = hashlib.md5((currdict["uri"]+currdict["server"]+
294
currdict["id"]+currdict["zone"]+
295
currdict["var_name"]).encode('utf-8')).hexdigest()
296
self.fatdict.append(currdict)
297
self.push_to_db(self.fatdict)
298
def push_to_db(self, dd):
299
cur = self.con.cursor()
300
for i in range(len(dd)):
301
cur.execute("""SELECT count(id) FROM received_sigs WHERE md5=?""", [dd[i]["md5"]])
304
print ("Already present in db.")
307
print ("Pushing to db :")
309
cur.execute("INSERT INTO received_sigs (md5, server, id, uri, zone, var_name) VALUES ("+
310
"?, ?, ?, ?, ?, ?)", [dd[i]["md5"], dd[i]["server"], dd[i]["id"], dd[i]["uri"],
311
dd[i]["zone"], dd[i]["var_name"]])
315
print ("Creating (new) database.")
316
cur = self.con.cursor()
317
cur.execute("CREATE TABLE received_sigs (md5 text, server text, id int, uri text, zone text, var_name text)")
318
cur.execute("CREATE TABLE tmp_rules (id int, uri text, zone text, var_name text, written int, server text)")
320
print ("Finished DB creation.")
321
os.system("touch %s" % params.dst)
323
print ("Touched TMP rules file.")
325
if (self.con is None):
326
self.con = sqlite3.connect(params.db)
327
cur = self.con.cursor()
328
cur.execute("SELECT name FROM sqlite_master WHERE name='received_sigs'")
342
class Params(object):
347
if __name__ == '__main__':
348
parser = argparse.ArgumentParser(description="Naxsi's learning-mode HTTP server.\n"+
349
"Should be run as root (yes scarry), as it will need to perform /etc/init.d/nginx reload.\n"+
350
"Runs fine as non-root, but you'll have to manually restart nginx",
351
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
352
parser.add_argument('--dst', type=str, default='/tmp/naxsi_rules.tmp', help='''Full path to the temp rule file.
353
This file should be included in your naxsi's location configuration file.''')
354
parser.add_argument('--db', type=str, default='naxsi_tmp.db', help='''SQLite database file to use.''')
355
parser.add_argument('--rules', type=str, default='/etc/nginx/naxsi_core.rules', help='''Path to your core rules file.''')
356
parser.add_argument('--cmd', type=str, default='/etc/init.d/nginx reload', help='''Command that will be
357
called to reload nginx's config file''')
358
parser.add_argument('--port', type=int, default=4242, help='''The port the HTTP server will listen to''')
359
parser.add_argument('--log', type=str, default=None, help='''Pickup false positives from log file as well.(ie. /var/log/nginx_error.log)''')
360
parser.add_argument('-n', action='store_true', default=False,
361
help='''Run the daemon as non-root, don't try to reload nginx.''')
362
parser.add_argument('-v', type=int, default=1, help='''Verbosity level 0-3''')
363
args = parser.parse_args(namespace=params)
364
server = HTTPServer(('localhost', params.port), Handler)
367
print ('Starting server, use <Ctrl-C> to stop')
369
server.serve_forever()
370
except KeyboardInterrupt: