GUI editor to tame mod_security rules

⌈⌋ ⎇ branch:  modseccfg


Check-in [e70973b482]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add $msg placeholder for recipes
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: e70973b482e382239ad5b09234827e309658aa011d2f60d7017440e19af6ed95
User & Date: mario 2021-03-05 16:19:25
Context
2021-03-05
16:19
Add msc_pyparser rule rewriting GUI check-in: c870e3c308 user: mario tags: trunk
16:19
Add $msg placeholder for recipes check-in: e70973b482 user: mario tags: trunk
16:18
Note about emoji bug (albeit already removed all instances) check-in: 8107fab63c user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to modseccfg/recipe.py.

108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
    "URL to DetectionOnly": """
        # Set one URL to DetectionOnly
        #
        SecRule REQUEST_URI "$request_uri" "phase:1,id:$rand,tag:modseccfg,tag:url-detectiononly,t:none,t:lowercase,pass,msg:'DetectionOnly for $request_uri',ctl:ruleEngine=DetectionOnly"
    """,

    #-- from REQUEST-900-EXCLUSION examples
    "Example Exlusions": {

        "Ignore param for tag": """
            # Removing a specific ARGS parameter from inspection for only certain attacks
            SecRule REQUEST_FILENAME "@endsWith $PATH" "id:$rand,phase:2,pass,nolog,tag:modseccfg,tag:ignore-param,ctl:ruleRemoveTargetByTag=attack-sqli;ARGS:pwd"
        """,

        "Ignore param for all": """







|







108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
    "URL to DetectionOnly": """
        # Set one URL to DetectionOnly
        #
        SecRule REQUEST_URI "$request_uri" "phase:1,id:$rand,tag:modseccfg,tag:url-detectiononly,t:none,t:lowercase,pass,msg:'DetectionOnly for $request_uri',ctl:ruleEngine=DetectionOnly"
    """,

    #-- from REQUEST-900-EXCLUSION examples
    "Example Exclusions": {

        "Ignore param for tag": """
            # Removing a specific ARGS parameter from inspection for only certain attacks
            SecRule REQUEST_FILENAME "@endsWith $PATH" "id:$rand,phase:2,pass,nolog,tag:modseccfg,tag:ignore-param,ctl:ruleRemoveTargetByTag=attack-sqli;ARGS:pwd"
        """,

        "Ignore param for all": """
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
            # type: config
            # sort: pre-crs
            # class: apache
            # title: {ServerName}
            # description: early mod_security configuration
            #
            # For SecRule directives that should override CRS rules, or using modseccfg macros etc.
            # SecRuleDisableById settings belong into regular vhost.conf still.
            # (Still not sure about CRS variable overrides.)
            
            <Directory {DocumentRoot}>
               # Use SecRuleRemoveByPath 900130 /app/
               
            </Directory>
        """








|
<







258
259
260
261
262
263
264
265

266
267
268
269
270
271
272
            # type: config
            # sort: pre-crs
            # class: apache
            # title: {ServerName}
            # description: early mod_security configuration
            #
            # For SecRule directives that should override CRS rules, or using modseccfg macros etc.
            # Standard SecRuleDisableById directives belong into the regular vhost.conf still.

            
            <Directory {DocumentRoot}>
               # Use SecRuleRemoveByPath 900130 /app/
               
            </Directory>
        """

300
301
302
303
304
305
306
307
308
309
310
311
312
313
314

# inject recipe list to main menu
def init(menu, **kwargs):
    if conf.get("user_recipes"):
        user_rec()
    i_ls = menu.index(["Recipe"]) # already a list
    menu[i_ls].append(ls(templates.__dict__))
   
def has(name):
    return name in templates.has


# window
class show:








|







299
300
301
302
303
304
305
306
307
308
309
310
311
312
313

# inject recipe list to main menu
def init(menu, **kwargs):
    if conf.get("user_recipes"):
        user_rec()
    i_ls = menu.index(["Recipe"]) # already a list
    menu[i_ls].append(ls(templates.__dict__))

def has(name):
    return name in templates.has


# window
class show:

326
327
328
329
330
331
332


333
334
335
336
337
338
339
                # or better: pluginconf.get_data()
                text = re.sub("[^/]+$", text.lstrip("@"), __file__)
                text = open(text, "r", encoding="utf-8").read()
            text = dedent(text).lstrip()
            text = self.repl(text, vars)
        else:
            text = text(data, vars).lstrip()


        #print(data)
        #print(text)
        
        # create window and dispatch to main event loop
        self.w = sg.Window(title=f"Recipe '{name}'", resizable=1, layout=[
            [sg.Multiline(default_text=text, key="src", size=(90,24), font="Mono 12")],
            [sg.Button(f"Save to {self.fn}", key="save"), sg.Button("Cancel", key="cancel")]







>
>







325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
                # or better: pluginconf.get_data()
                text = re.sub("[^/]+$", text.lstrip("@"), __file__)
                text = open(text, "r", encoding="utf-8").read()
            text = dedent(text).lstrip()
            text = self.repl(text, vars)
        else:
            text = text(data, vars).lstrip()
            if not text:
                return
        #print(data)
        #print(text)
        
        # create window and dispatch to main event loop
        self.w = sg.Window(title=f"Recipe '{name}'", resizable=1, layout=[
            [sg.Multiline(default_text=text, key="src", size=(90,24), font="Mono 12")],
            [sg.Button(f"Save to {self.fn}", key="save"), sg.Button("Cancel", key="cancel")]
368
369
370
371
372
373
374

375
376
377
378
379
380
381
382
383
384
385
386


387
388
389
390
391
392
393
                    return v

    # prepare vars dict from mainwindow event data + selected log line
    def vars(self, data):
        vh = vhosts.vhosts.get(data.get("confn", "-"), {})
        vars = {
            "id": "0",

            "rand": random.randrange(2000,5000),
            "request_uri": "/PATH",
            "confn": data.get("confn"),
            "documentroot": vh.cfg.get("documentroot", "") if vh else "/srv/www"
        }
        if data.get("log"):
            for k,v in re.findall('\[(\w+) "([^"]+)"\]', str(data["log"])):
                if k in ("uri", "request_line",): k = "request_uri"
                if k in ("request_uri",): v = re.escape(v)
                vars[k] = v
        if data.get("rule"):
            vars["id"] = data["rule"][0]


        return vars

    # substitute $varnames in text string
    def repl(self, text, vars):
        text = re.sub(r"\$(\w+)", lambda m,*k: str(vars.get(m.group(1), m.group(0))), text)
        return text








>












>
>







369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
                    return v

    # prepare vars dict from mainwindow event data + selected log line
    def vars(self, data):
        vh = vhosts.vhosts.get(data.get("confn", "-"), {})
        vars = {
            "id": "0",
            "msg": "...",
            "rand": random.randrange(2000,5000),
            "request_uri": "/PATH",
            "confn": data.get("confn"),
            "documentroot": vh.cfg.get("documentroot", "") if vh else "/srv/www"
        }
        if data.get("log"):
            for k,v in re.findall('\[(\w+) "([^"]+)"\]', str(data["log"])):
                if k in ("uri", "request_line",): k = "request_uri"
                if k in ("request_uri",): v = re.escape(v)
                vars[k] = v
        if data.get("rule"):
            vars["id"] = data["rule"][0]
            if vars["id"] in vhosts.rules:
                vars["msg"] = vhosts.rules.get(vars["id"]).msg
        return vars

    # substitute $varnames in text string
    def repl(self, text, vars):
        text = re.sub(r"\$(\w+)", lambda m,*k: str(vars.get(m.group(1), m.group(0))), text)
        return text