~britco/nginx/nginx

« back to all changes in this revision

Viewing changes to debian/modules/naxsi/contrib/rules_generator/http_config.py

  • Committer: Package Import Robot
  • Author(s): Cyril Lavier, Cyril Lavier, Kartik Mistry
  • Date: 2012-04-13 16:58:59 UTC
  • mfrom: (4.2.48 sid)
  • Revision ID: package-import@ubuntu.com-20120413165859-ef6fkd11mudyaspr
Tags: 1.1.19-1
[Cyril Lavier]
* New upstream release.
  + Fixed a buffer overflow in the ngx_http_mp4_module. See: CVE-2012-2089
    for more details.
* debian/copyright:
  + Updated licenses.
* debian/nginx-extras.postinst, debian/nginx-full.postinst,
  debian/nginx-light.postinst, debian/nginx-naxsi.postinst:
  + Removing the debug markers. (Closes: #667894)
* debian/control, debian/rules, debian/copyright,
  debian/modules/nginx-dav-ext-module:
  + Added nginx-dav-ext-module in full and extras.
* debian/modules/naxsi:
  + Updated naxsi to the SVN snapshot (r280) to fix the licence issue with
    OpenSSL.

[Kartik Mistry]
* Misc cleanups in debian/control, debian/copyright.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
# try to provide minimal multi-version support
 
3
try:
 
4
    from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
 
5
except ImportError:
 
6
    from http.server import BaseHTTPRequestHandler, HTTPServer
 
7
try:
 
8
    import urlparse
 
9
except ImportError:
 
10
    import urllib.parse as urlparse
 
11
import sqlite3
 
12
 
 
13
import pprint
 
14
import hashlib
 
15
import sys
 
16
import os
 
17
import argparse
 
18
import re
 
19
import cgi    
 
20
 
 
21
 
 
22
 
 
23
class Handler(BaseHTTPRequestHandler):
 
24
    def do_POST(self):
 
25
        self.do_GET()
 
26
    def do_GET(self):
 
27
        message = ""
 
28
        # if it's a background-forwarded request ...
 
29
        if ("naxsi_sig" in self.headers.keys()):
 
30
            if params.v > 2:
 
31
                print ("Exception catched.")
 
32
                print ("ExUrl: "+self.headers["naxsi_sig"])
 
33
            nx.eat_rule(self.headers["naxsi_sig"])
 
34
            nx.agreggate_rules()
 
35
            return
 
36
        # user wanna reload its config
 
37
        if (self.path.startswith("/write_and_reload")):
 
38
            if params.v > 2:
 
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:])
 
43
            else:
 
44
                print ("writting and reloading all servers.")
 
45
                nx.dump_rules()
 
46
            if params.n is False:
 
47
                os.system(params.cmd)
 
48
                self.send_response(302)
 
49
                self.send_header('Location', '/')
 
50
                self.end_headers()
 
51
            else:
 
52
                print ("Not reloading anything as user is non-root")
 
53
                self.send_response(200)
 
54
                self.end_headers()
 
55
                if sys.version_info > (3, 0):
 
56
                    self.wfile.write(bytes("Not root, not reloading anything.", 'utf-8'))
 
57
                else:
 
58
                    self.wfile.write("Not root, not reloading anything.")
 
59
            return
 
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)
 
65
        self.end_headers()
 
66
        if sys.version_info > (3, 0):
 
67
            self.wfile.write(bytes(message, 'utf-8'))
 
68
        else:
 
69
            self.wfile.write(message)
 
70
    def feed_from_logs(self, logfile):
 
71
        if nx.log_fd is None:
 
72
            print ("Opening log file for #1 time.")
 
73
            nx.log_fd = open(params.log)
 
74
        else:
 
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.")
 
79
        while True:
 
80
            tmpbuf = nx.log_fd.readline()
 
81
            if tmpbuf == '':
 
82
                print ("EOF")
 
83
                return
 
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: ")]) 
 
87
                nx.agreggate_rules()
 
88
    def ui_report(self):
 
89
        nbr = nx.get_written_rules_count()
 
90
        nbs = nx.get_exception_count()
 
91
        message = """<html>
 
92
        <style>
 
93
        .nx_ok {
 
94
        font-size: 100%;
 
95
        color: #99CC00;
 
96
        }
 
97
        .nx_ko {
 
98
        font-size: 100%;
 
99
        color: #FF0000;
 
100
        }
 
101
        .lnk_ok a {
 
102
        color:green
 
103
        }
 
104
        .lnk_ko a {
 
105
        color:red
 
106
        }
 
107
        </style>
 
108
        <b class="""
 
109
        if (nbr > 0):
 
110
            message += "nx_ko> "
 
111
        else:
 
112
            message += "nx_ok> "
 
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>"
 
116
        if (nbr > 2):
 
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()
 
120
        message += "</html>"
 
121
        return (message)
 
122
 
 
123
class NaxsiDB:
 
124
    def read_text(self):
 
125
        try:
 
126
            fd = open(params.rules, "r")
 
127
        except IOError:
 
128
            print ("Unable to open rules file : "+params.rules)
 
129
            return
 
130
        for rules in fd:
 
131
            rid = re.search('id:([0-9]+)', rules)
 
132
            if rid is None:
 
133
                continue
 
134
            ptr = re.search('str:([^"]+)', rules)
 
135
            if ptr is None:
 
136
                continue
 
137
            self.static[str(rid.group(1))] = cgi.escape(ptr.group(1))
 
138
        fd.close()
 
139
    def dump_rules(self, server=None):
 
140
        if server is None:
 
141
            fd = open(params.dst, "a+")
 
142
        else:
 
143
            fd = open(params.dst+"."+hashlib.md5(server.encode('utf-8')).hexdigest(), "a+")
 
144
        cur = self.con.cursor()
 
145
        if server is None:
 
146
            cur.execute("SELECT id, uri, zone, var_name, "
 
147
                        "server from tmp_rules where written = 0")
 
148
        else:
 
149
            cur.execute("SELECT id, uri, zone, var_name, "
 
150
                        "server from tmp_rules where written = 0 and server = ?", [server])
 
151
        rr = cur.fetchall()
 
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]+"\" "
 
159
            else:
 
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]])
 
165
            self.con.commit()
 
166
            fd.write(tmprule)
 
167
            if params.v > 2:
 
168
                print ("Generated Rule : "+tmprule)
 
169
        fd.close()
 
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])
 
175
        ra = cur.fetchone()
 
176
        if (ra[0] >= 1):
 
177
            if params.v > 2:
 
178
                print ("already present in tmp_rules ...")
 
179
            return
 
180
        cur.execute("INSERT INTO tmp_rules (id, uri, zone, var_name, "
 
181
                    "server, written) VALUES (?, ?, ?, ?, ?, 0)",
 
182
                    [mid, uri, zone, var_name, server])
 
183
        self.con.commit()
 
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")
 
188
        rr = cur.fetchall()
 
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])
 
192
                continue
 
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])
 
195
                continue
 
196
    def cursor(self):
 
197
        return self.con.cursor()
 
198
    def get_written_rules_count(self, server=None):
 
199
        cur = self.con.cursor()
 
200
        if server is None:
 
201
            cur.execute("SELECT COUNT(id) FROM tmp_rules where written = 0")
 
202
        else:
 
203
            cur.execute("SELECT COUNT(id) FROM tmp_rules where written = 0 and server = ?", [server])
 
204
        ra = cur.fetchone()
 
205
        return (ra[0])
 
206
    def display_written_rules(self):
 
207
        msg = ""
 
208
        cur = self.con.cursor()
 
209
        cur.execute("SELECT distinct(server) "
 
210
                    " FROM tmp_rules")
 
211
        rr = cur.fetchall()
 
212
        pprint.pprint(rr)
 
213
        for i in range(len(rr)):
 
214
            print ("adding elems !")
 
215
            if self.get_written_rules_count(rr[i][0]) > 0:
 
216
                tmpstyle="lnk_ko"
 
217
            else:
 
218
                tmpstyle="lnk_ok"
 
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(),
 
224
                                         tmpstyle)
 
225
        msg += "</br>"
 
226
        cur.execute("SELECT id,uri,zone,var_name,server"
 
227
                    " FROM tmp_rules where written = 0")
 
228
        rr = cur.fetchall()
 
229
        if len(rr):
 
230
            msg += "Authorizing :</br>"
 
231
        for i in range(len(rr)):
 
232
            pattern = ""
 
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],
 
240
                                           rr[i][3], rr[i][2])
 
241
                continue
 
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]),
 
246
                                       pattern,
 
247
                                       str(rr[i][0]),
 
248
                                       rr[i][1],
 
249
                                       rr[i][2])
 
250
                continue
 
251
        return msg
 
252
    def get_exception_count(self):
 
253
        cur = self.con.cursor()
 
254
        cur.execute("SELECT COUNT(id) FROM received_sigs")
 
255
        ra = cur.fetchone()
 
256
        return (ra[0])
 
257
    def eat_rule(self, source):
 
258
        currdict = {}
 
259
        server = ""
 
260
        uri = ""
 
261
        ridx = '0'
 
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"]+
 
271
                                               currdict["server"]+
 
272
                                               currdict["id"]+
 
273
                                               currdict["zone"]+
 
274
                                               currdict["var_name"]).encode('utf-8')).hexdigest()
 
275
#                print ('#1 here:'+currdict["md5"])
 
276
                self.fatdict.append(currdict)
 
277
                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")):
 
282
                uri = tmpdict[i][1]
 
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"]])
 
302
            ra = cur.fetchone()
 
303
            if (ra[0] >= 1):
 
304
                print ("Already present in db.")
 
305
                continue
 
306
            if params.v > 2:
 
307
                print ("Pushing to db :")
 
308
                pprint.pprint(dd[i])
 
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"]])
 
312
        self.con.commit()
 
313
    def dbcreate(self):
 
314
        if params.v > 2:
 
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)")
 
319
        self.con.commit()
 
320
        print ("Finished DB creation.")
 
321
        os.system("touch %s" % params.dst)
 
322
        if params.v > 2:
 
323
            print ("Touched TMP rules file.")
 
324
    def dbinit(self):
 
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'")
 
329
        ra = cur.fetchone()
 
330
        if (ra is None):
 
331
            self.dbcreate()
 
332
        if params.v > 2:
 
333
            print ("done.")
 
334
    def __init__(self):
 
335
        self.con = None
 
336
        self.fatdict = []
 
337
        self.static = {}
 
338
        self.dbinit()
 
339
        self.log_fd = None
 
340
        return
 
341
 
 
342
class Params(object):
 
343
    pass
 
344
 
 
345
params = Params()
 
346
 
 
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)
 
365
    nx = NaxsiDB()
 
366
    nx.read_text()
 
367
    print ('Starting server, use <Ctrl-C> to stop')
 
368
    try:
 
369
        server.serve_forever()
 
370
    except KeyboardInterrupt:
 
371
        server.shutdown()
 
372
        sys.exit(1)