GUI editor to tame mod_security rules

βŒˆβŒ‹ βŽ‡ branch:  modseccfg


Check-in [92bac1ad8c]

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

Overview
Comment:hacky support for [menu]β†’[event] markup
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 92bac1ad8cd49ba0c38dfc7243060c4032cd7502b43b4e2d1bf72cefe1a7f055
User & Date: mario 2020-12-02 22:33:00
Context
2020-12-03
23:16
Read *.local.sh scripts, to preview commands as is check-in: 5bf0e34165 user: mario tags: trunk
2020-12-02
22:33
hacky support for [menu]β†’[event] markup check-in: 92bac1ad8c user: mario tags: trunk
22:32
Remove stale infos, add project meta block check-in: 0fcb10b69d user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to modseccfg/advise.py.

1
2
3
4
5
6
7
8
9
10
# encoding: utf-8
# api: modseccfg
# version: 0.3
# type: data
# category: log
# title: log advise
# description: Some simple pattern detection for common log entries
# license: Apache-2.0
#
# Basically just some keyword lookups to explain the logs.


|







1
2
3
4
5
6
7
8
9
10
# encoding: utf-8
# api: modseccfg
# version: 0.4
# type: data
# category: log
# title: log advise
# description: Some simple pattern detection for common log entries
# license: Apache-2.0
#
# Basically just some keyword lookups to explain the logs.
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42



patterns = {

    "PCRE limits": """Backtracking limits for regex patterns can usually be
    raised.  500000 (500K) is still reasonable for sites not under concrete
    DoS threats.  See →File→SecOptions for SecPcreMatchLimit &
    SecPcreMatchLimitRecursion 500000
    https://stackoverflow.com/questions/18226521/modsecurity-maximum-post-limits-pcre-limit-errors""",

    "tx\.allowed_request_content_type": """Might need adaption if you use any
    REST toolkit with uncommon MIME type uploads (e.g. a DAV or VCS).
    See →File→CRS options to change this variable for allowed POST payloads.""",

    "TX:anomaly_score": """Anomaly scoring mode is enabled.
    'TX:anomaly_score' denotes a transaction variable, used as counter for
    threat indicators. (Concrete causes should be noted in previous log entries.)
    https://www.modsecurity.org/CRS/Documentation/anomaly.html""",
    
    "TX:outgoing_points": """Comodo rules: total score for suspicious output.""",







|





|







22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42



patterns = {

    "PCRE limits": """Backtracking limits for regex patterns can usually be
    raised.  500000 (500K) is still reasonable for sites not under concrete
    DoS threats.  See [File]β†’[SecEngine options] for SecPcreMatchLimit &
    SecPcreMatchLimitRecursion 500000
    https://stackoverflow.com/questions/18226521/modsecurity-maximum-post-limits-pcre-limit-errors""",

    "tx\.allowed_request_content_type": """Might need adaption if you use any
    REST toolkit with uncommon MIME type uploads (e.g. a DAV or VCS).
    See [Fil]e→[CoreRuleSet options] to change this variable for allowed POST payloads.""",

    "TX:anomaly_score": """Anomaly scoring mode is enabled.
    'TX:anomaly_score' denotes a transaction variable, used as counter for
    threat indicators. (Concrete causes should be noted in previous log entries.)
    https://www.modsecurity.org/CRS/Documentation/anomaly.html""",
    
    "TX:outgoing_points": """Comodo rules: total score for suspicious output.""",
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
    "/\.env": """Password crawlers love to look for .env files. Blame GitHub.""",

    "Found another rule with the same id": """Usually indicates that a rule
    file got included twice. Check `apache2ctl -t -D DUMP_INCLUDES` to see if
    there's an Include(Optional) ../* loop.
    https://stackoverflow.com/questions/53082316/apache-modsecurity-another-rule-with-the-same-id-error
    """,    
    "another rule with the same id": """And also, don't redefine rules
    "SecRuleRemove" does not really remove, but disable an existing rule.
    To fully override a condition, it needs to be redeclared with a new id:num.
    Send a bug report if it was caused by modseccfg.
    """,
    
    "SecCollectionTimeout is not yet supported": """mod_security v3 problem.
    https://stackoverflow.com/questions/49286483/seccollectiontimeout-is-not-yet-supported







|







70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
    "/\.env": """Password crawlers love to look for .env files. Blame GitHub.""",

    "Found another rule with the same id": """Usually indicates that a rule
    file got included twice. Check `apache2ctl -t -D DUMP_INCLUDES` to see if
    there's an Include(Optional) ../* loop.
    https://stackoverflow.com/questions/53082316/apache-modsecurity-another-rule-with-the-same-id-error
    """,    
    "another rule with the same id": """And also, don't try to redefine rules.
    "SecRuleRemove" does not really remove, but disable an existing rule.
    To fully override a condition, it needs to be redeclared with a new id:num.
    Send a bug report if it was caused by modseccfg.
    """,
    
    "SecCollectionTimeout is not yet supported": """mod_security v3 problem.
    https://stackoverflow.com/questions/49286483/seccollectiontimeout-is-not-yet-supported
104
105
106
107
108
109
110
111
112

113
114
115
116
117
118
119
120

121
122






123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

154
155
156
157
158
159
160

161
162
    logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
    logdata:'Previous Block Reason: %{ip.reput_block_reason}',\
    logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
    """,
}



rx_url = "https?://\S+|file:///\S+"


# restrict text width
def wrap(text, width=76):
    text = re.sub("\s+", " ", text)
    return "\n".join(textwrap.wrap(text, width))

# callback for window events
def show_url(event):

    if re.match(rx_url, event):
        os.system(f"xdg-open '{event}' &")







# extract urls from text
def split_text_links(text):
    links = re.findall(rx_url, text)
    if links:
        text = re.sub(rx_url, "", text)
    return text, links
    

# scan patterns, show window
def show(mainwindow, data):
    if not data.get("log"):
        return
    log = data["log"]
    
    # look if anything matcheed
    found = {}
    for rx, text in patterns.items():
        m = re.search(rx, log[0], re.I)
        if m:
            found[m.group(0)] = text
    if not found:
        return mainwindow.status("No known log entry.")

    # prepare output
    layout = [[sg.T("Detected log messages", font=("Sans",16,"bold"), pad=(0,5,0,15), text_color="darkgray")]]
    for title, text in found.items():
        text, links = split_text_links(text)
        layout.append([sg.T(title, font=("Ubuntu",12,"bold italic"), pad=(0,10))])
        layout.append([sg.T(wrap(text), pad=(10,2))])
        for url in links:

            layout.append([sg.Button(url, pad=(10,0), font=("Noto Sans Display", 11), use_ttk_buttons=1)])
    layout.append([sg.Button("  Close  ", pad=(10,30,10,5))])

    # show + let main handle button
    w = sg.Window(title="Log patterns", layout=layout, size=(700,500), font="Sans 12")
    mainwindow.win_register(
        w, lambda ev,*d: show_url(ev) or w.close()

    )








|
|
>







|
>
|

>
>
>
>
>
>





|

<







|












|


>
|
|


|

|
>
|
<
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137

138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170

    logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
    logdata:'Previous Block Reason: %{ip.reput_block_reason}',\
    logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
    """,
}


# "url://" or "[menu]β†’[event]"
rx_url = "https?://\S+|file:///\S+|\[\w+\]β†’\[.+?\]"


# restrict text width
def wrap(text, width=76):
    text = re.sub("\s+", " ", text)
    return "\n".join(textwrap.wrap(text, width))

# callback for window events
def show_url(event, main, data):
    print(event)
    if re.match("\w+://", event):
        os.system(f"xdg-open '{event}' &")
    else:
        # very hacky: invoke referenced menu function
        m = re.search("β†’\[(.+?)\]", event)
        if m:
            data["menu"] = m.group(1)
            main.event(m.group(1), data)

# extract urls from text
def split_text_links(text):
    links = re.findall(rx_url, text)
    if links:
        text = re.sub("\w+://\S+", "", text)
    return text, links


# scan patterns, show window
def show(mainwindow, data):
    if not data.get("log"):
        return
    log = data["log"]
    
    # look if anything matched
    found = {}
    for rx, text in patterns.items():
        m = re.search(rx, log[0], re.I)
        if m:
            found[m.group(0)] = text
    if not found:
        return mainwindow.status("No known log entry.")

    # prepare output
    layout = [[sg.T("Detected log messages", font=("Sans",16,"bold"), pad=(0,5,0,15), text_color="darkgray")]]
    for title, text in found.items():
        text, links = split_text_links(text)
        layout.append([sg.T(wrap(title, 64), font=("Ubuntu",12,"bold italic"), pad=(0,10))])
        layout.append([sg.T(wrap(text), pad=(10,2))])
        for url in links:
            color = "red" if (url.find("]") > 0) else "blue"
            layout.append([sg.T(url, k=url, pad=(10,0), font=("Noto Sans Display", 11), text_color=color, enable_events=1)])
    layout = [[sg.Column(layout, scrollable=1, size=(700,430))], [sg.Button("  Close  ")]]

    # show + let main handle button
    w = sg.Window(title="Log patterns", layout=layout, size=(700,500), resizable=1, font="Sans 12")
    mainwindow.win_register(
        w, lambda ev,*d: show_url(ev, mainwindow, data) or w.close()
    )                      # retain handle ↑ to main and current data