├── .gitignore ├── QRadar-rules2csv.py ├── QRadar-rules2html.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /QRadar-rules2csv.py: -------------------------------------------------------------------------------- 1 | # python3 script 2 | import sys, os 3 | import json, time 4 | from xml.etree.ElementTree import ElementTree 5 | import xml.etree.ElementTree as ET 6 | import base64 7 | import lxml.etree as etree 8 | 9 | from array import array 10 | from types import * 11 | from html.parser import HTMLParser 12 | from optparse import OptionParser 13 | 14 | import pprint 15 | 16 | class MyHTMLParser(HTMLParser): 17 | 18 | def __init__(self): 19 | HTMLParser.__init__(self) 20 | self.recording = 0 21 | self.data = [] 22 | self.test = [] 23 | 24 | def handle_starttag(self, tag, attrs): 25 | self.recording = 1 26 | self.data='' 27 | 28 | def handle_endtag(self, tag): 29 | self.recording -= 1 30 | 31 | def handle_data(self, data): 32 | self.testArray.append([testSeq,self.recording,data]) 33 | 34 | def main(): 35 | tree = ET.parse(sys.argv[1]) 36 | root = tree.getroot() 37 | rule = [] 38 | htmlout=[] 39 | 40 | global testSeq 41 | 42 | for rule in root.findall('custom_rule'): 43 | fullTestArray=[] 44 | htmlTestArray=[] 45 | htmlRuleDef=[] 46 | parser = MyHTMLParser() 47 | htmlGroupArray=[] 48 | 49 | ruleUUID=rule.find('uuid').text 50 | ruleID=rule.find('id').text 51 | fgroupGet="fgroup_link[id='"+ruleID+"']" 52 | fgroupGet="fgroup_link" 53 | 54 | ruleOrigin=rule.find('origin').text 55 | 56 | 57 | for fgroup_link in root.findall('fgroup_link[item_id="'+ruleID+'"]'): 58 | if fgroup_link is None: print('Error') 59 | 60 | fgroup_link_fgroup_id = fgroup_link.find('fgroup_id').text 61 | fgroup_id = root.find('fgroup[id="'+fgroup_link_fgroup_id+'"]') 62 | fgroup_name = fgroup_id.find('description').text 63 | fgroup_parent_id = fgroup_id.find('parent_id') 64 | fgroup_level_id=0 65 | fgroup_level_id = fgroup_id.find('level_id').text 66 | level=">" 67 | 68 | htmlGroupArray.append('"'+level+str(fgroup_name)+'"') 69 | while not fgroup_parent_id is None: 70 | fgroup_id = root.find('fgroup[id="'+fgroup_parent_id.text+'"]') #find parent node 71 | fgroup_name = fgroup_id.find('description').text #find name 72 | fgroup_level_id=0 73 | fgroup_level_id = fgroup_id.find('level_id').text 74 | level=level+">" 75 | fgroup_parent_id=fgroup_id.find('parent_id') #check if new node has parent 76 | 77 | if not fgroup_parent_id is None: 78 | htmlGroupArray.append('"'+level+str(fgroup_name)+'"') 79 | 80 | detailedRuleData=base64.b64decode(rule.find('rule_data').text) 81 | x = etree.fromstring(detailedRuleData) 82 | m = etree.tostring(x, pretty_print = True) 83 | 84 | drdroot = ET.fromstring(detailedRuleData) 85 | 86 | ruleName=drdroot.find('name').text 87 | ruleType=drdroot.get('type') 88 | htmlRuleDef.append('"'+ruleName+'"') 89 | 90 | htmlRuleDef.append('"'+ruleOrigin+'"') 91 | htmlRuleDef.append('"'+ruleType+'"') 92 | 93 | ruleIsBB=drdroot.get('buildingBlock') 94 | if ruleIsBB is not None and ruleIsBB=='true': 95 | htmlRuleDef.append('"BB"') 96 | else: 97 | htmlRuleDef.append('"Rule"') 98 | 99 | ruleEnabled=drdroot.get('enabled') 100 | if ruleEnabled=='true': 101 | htmlruleEnabled='"Enabled"' 102 | else: 103 | htmlruleEnabled='"Disabled"' 104 | 105 | htmlRuleDef.append(htmlruleEnabled) 106 | htmlRuleDef.append('"'+ruleUUID+'"') 107 | 108 | 109 | # start of ruletest definition 110 | testDefinitions=drdroot.find('testDefinitions') 111 | 112 | negateTextA='' 113 | negateTextA=' [AND NOT] ' 114 | negateTextB='' 115 | testSeq=-1 116 | htmlTestArray=['"'] 117 | for elTests in testDefinitions.findall('test'): 118 | teteststSeq=testSeq+1 119 | testName=elTests.get('name') 120 | testUUID=elTests.get('uid') 121 | testNegate=str(elTests.get('negate')) 122 | ruleText=str(elTests.find('text').text) 123 | htmltest='' 124 | if testNegate=="true" and testNegate is not None: 125 | htmltest=''+negateTextA+'' 126 | else: 127 | htmltest=''+negateTextB+'' 128 | 129 | negateTextA=' [AND NOT] ' 130 | negateTextB=' [AND] ' 131 | 132 | if isinstance(ruleText, str): 133 | parser.close() 134 | parser.testArray=[] 135 | parse=str(parser.feed(ruleText)) 136 | 137 | oldx0=-1 138 | oldx1=-1 139 | for x in parser.testArray: 140 | fullTestArray.append(x) 141 | if str(x[0])!=oldx0: 142 | oldx0=str(x[0]) 143 | if str(x[1])!=oldx1: 144 | oldx1=int(x[1]) 145 | if oldx1==1: 146 | htmltest=''+htmltest+'' 147 | htmltest=htmltest+''+str(x[2])+'' 148 | 149 | htmlTestArray.append(htmltest) 150 | htmlTestArray.append('"') 151 | 152 | actionDefinitions=drdroot.find('actions') 153 | responsDefinitions=drdroot.find('responses') 154 | responsHTML=[] 155 | 156 | forceOffense='false' 157 | 158 | if responsDefinitions is not None: 159 | newevent=responsDefinitions.find('newevent') 160 | if newevent is not None: 161 | neweventqid = str(newevent.get('qid')) 162 | LLC=str('LLC:'+str(newevent.get('lowLevelCategory'))) 163 | CRS=str('CRS:'+str(newevent.get('credibility'))+str(newevent.get('relevance'))+str(newevent.get('severity'))) 164 | forceOffense=str(newevent.get('forceOffenseCreation')) 165 | if forceOffense is not None and forceOffense=='true': 166 | htmlforceOffense='Offense' 167 | else: 168 | htmlforceOffense='No-Offense' 169 | 170 | responsHTML.append(str('"QID:'+str(newevent.get('qid')))+'","'+htmlforceOffense+'","'+LLC+'","'+CRS+'"') 171 | else: 172 | responsHTML.append(str('"","","",""')) 173 | 174 | print(','.join(htmlRuleDef)+','+','.join(htmlTestArray)+','+','.join(responsHTML)+','+','.join(htmlGroupArray)) 175 | 176 | print('END') 177 | 178 | 179 | if __name__ == "__main__": 180 | main() 181 | 182 | -------------------------------------------------------------------------------- /QRadar-rules2html.py: -------------------------------------------------------------------------------- 1 | # python3 script 2 | import sys, os 3 | import json, time 4 | from xml.etree.ElementTree import ElementTree 5 | import xml.etree.ElementTree as ET 6 | import base64 7 | import lxml.etree as etree 8 | 9 | from array import array 10 | from types import * 11 | from html.parser import HTMLParser 12 | from optparse import OptionParser 13 | 14 | import pprint 15 | 16 | class MyHTMLParser(HTMLParser): 17 | 18 | def __init__(self): 19 | HTMLParser.__init__(self) 20 | self.recording = 0 21 | self.data = [] 22 | self.test = [] 23 | 24 | def handle_starttag(self, tag, attrs): 25 | self.recording = 1 26 | self.data='' 27 | 28 | def handle_endtag(self, tag): 29 | self.recording -= 1 30 | 31 | def handle_data(self, data): 32 | self.testArray.append([testSeq,self.recording,data]) 33 | 34 | def main(): 35 | tree = ET.parse(sys.argv[1]) 36 | root = tree.getroot() 37 | rule = [] 38 | htmlheader=['\ 164 | \ 165 | \ 166 | \ 167 | \ 168 | \ 169 | \ 170 | \ 171 | \ 172 | \ 173 | '] 174 | htmlout=htmlheader 175 | htmlfooter=['
NameRuleRespons
'] 176 | htmlout=[] 177 | global testSeq 178 | print(' '.join(htmlheader)) 179 | 180 | for rule in root.findall('custom_rule'): 181 | fullTestArray=[] 182 | htmlTestArray=[] 183 | htmlRuleDef=[] 184 | parser = MyHTMLParser() 185 | 186 | ruleUUID=rule.find('uuid').text 187 | ruleID=rule.find('id').text 188 | fgroupGet="fgroup_link[id='"+ruleID+"']" 189 | fgroupGet="fgroup_link" 190 | 191 | 192 | ruleOrigin=rule.find('origin').text 193 | htmlGroupArray=[] 194 | ''' 195 | for fgroup_link in root.findall('fgroup_link[item_id="'+ruleID+'"]'): 196 | 197 | if fgroup_link is None: print('Error') 198 | 199 | fgroup_link_fgroup_id = fgroup_link.find('fgroup_id').text 200 | fgroup_id = root.find('fgroup[id="'+fgroup_link_fgroup_id+'"]') 201 | fgroup_name = fgroup_id.find('description').text 202 | fgroup_parent_id = fgroup_id.find('parent_id') 203 | fgroup_level_id=0 204 | fgroup_level_id = fgroup_id.find('level_id').text 205 | level=">" 206 | # print(' '+ruleID+' '+str(fgroup_name)) 207 | # htmlGroupArray.insert(0,level+str(fgroup_name)) 208 | 209 | htmlGroupArray.append(''+level+str(fgroup_name)+' ') 210 | while not fgroup_parent_id is None: 211 | fgroup_id = root.find('fgroup[id="'+fgroup_parent_id.text+'"]') #find parent node 212 | fgroup_name = fgroup_id.find('description').text #find name 213 | fgroup_level_id=0 214 | fgroup_level_id = fgroup_id.find('level_id').text 215 | level=level+">" 216 | fgroup_parent_id=fgroup_id.find('parent_id') #check if new node has parent 217 | # print('> '+ruleID+' '+str(fgroup_name)) 218 | if not fgroup_parent_id is None: 219 | htmlGroupArray.append(''+level+str(fgroup_name)+' ') 220 | # htmlGroupArray.append(level+str(fgroup_name)) 221 | # htmlGroupArray.insert(0,'>'+str(fgroup_name)) 222 | # print('/'.join(htmlGroupArray)) 223 | ''' 224 | if True: 225 | detailedRuleData=base64.b64decode(rule.find('rule_data').text) 226 | x = etree.fromstring(detailedRuleData) 227 | m = etree.tostring(x, pretty_print = True) 228 | 229 | drdroot = ET.fromstring(detailedRuleData) 230 | 231 | ruleName=drdroot.find('name').text 232 | 233 | 234 | ruleType=drdroot.get('type') 235 | htmlRuleDef.append(ruleName) 236 | htmlRuleDef.append('
') 237 | 238 | htmlRuleDef.append(''+ruleOrigin+' ') 239 | htmlRuleDef.append(''+ruleType+' ') 240 | 241 | ruleIsBB=drdroot.get('buildingBlock') 242 | if ruleIsBB is not None and ruleIsBB=='true': 243 | htmlRuleDef.append('BuildingBlock ') 244 | else: 245 | htmlRuleDef.append('Rule ') 246 | 247 | ruleEnabled=drdroot.get('enabled') 248 | if ruleEnabled=='true': 249 | htmlruleEnabled='Enabled' 250 | else: 251 | htmlruleEnabled='Disabled' 252 | 253 | htmlRuleDef.append(htmlruleEnabled) 254 | htmlRuleDef.append('
') 255 | 256 | htmlRuleDef.append('
'.join(htmlGroupArray)) 257 | htmlRuleDef.append('
') 258 | 259 | htmlRuleDef.append(ruleUUID) 260 | htmlRuleDef.append('
') 261 | 262 | # start of ruletest definition 263 | testDefinitions=drdroot.find('testDefinitions') 264 | 265 | negateTextA='' 266 | negateTextA=' AND NOT ' 267 | negateTextB='' 268 | testSeq=-1 269 | for elTests in testDefinitions.findall('test'): 270 | teteststSeq=testSeq+1 271 | testName=elTests.get('name') 272 | testUUID=elTests.get('uid') 273 | testNegate=str(elTests.get('negate')) 274 | ruleText=str(elTests.find('text').text) 275 | htmltest='' 276 | if testNegate=="true" and testNegate is not None: 277 | htmltest=''+negateTextA+'' 278 | else: 279 | htmltest=''+negateTextB+'' 280 | 281 | negateTextA=' AND NOT ' 282 | negateTextB=' AND ' 283 | 284 | if isinstance(ruleText, str): 285 | parser.close() 286 | parser.testArray=[] 287 | parse=str(parser.feed(ruleText)) 288 | 289 | oldx0=-1 290 | oldx1=-1 291 | for x in parser.testArray: 292 | fullTestArray.append(x) 293 | if str(x[0])!=oldx0: 294 | oldx0=str(x[0]) 295 | if str(x[1])!=oldx1: 296 | oldx1=int(x[1]) 297 | if oldx1==1: 298 | htmltest=htmltest+'' 299 | # else: 300 | # htmltest=htmltest+'' 301 | htmltest=htmltest+str(x[2])+'' 302 | 303 | htmlTestArray.append(htmltest) 304 | # actionDefinitions=[''] 305 | # responsDefinitions=[] 306 | # actionDefs=ElementTree().getroot() 307 | # responsDefs=[] 308 | # stub=ElementTree().getroot() 309 | 310 | actionDefinitions=drdroot.find('actions') 311 | responsDefinitions=drdroot.find('responses') 312 | responsHTML='' 313 | # if actionDefinitions is not None: responsHTML=responsHTML+str(actionDefinitions.items()) 314 | 315 | forceOffense='false' 316 | 317 | if actionDefinitions is not None: 318 | for actions in actionDefinitions: 319 | S1='';S2='';S3='' 320 | if actions is not None: 321 | if actions.get('value') is not None: S3=actions.get('value') 322 | if actions.get('operation') is not None: S2=actions.get('operation')[3];S1=actions.get('operation')[0] 323 | if S1=='s': S1='=' 324 | if S1=='i': S1='+' 325 | if S1=='d': S1='-' 326 | 327 | responsHTML=responsHTML+str(S2+S3+S1)+'
' 328 | 329 | if responsDefinitions is not None: 330 | responsHTML=responsHTML+str(responsDefinitions.items())+'
' 331 | newevent=responsDefinitions.find('newevent') 332 | if newevent is not None: 333 | htmlGroupArray=[] 334 | ''' 335 | fgroup_link_fgroup_id = fgroup_link.find('fgroup_id').text 336 | fgroup_id = root.find('fgroup[id="'+fgroup_link_fgroup_id+'"]') 337 | fgroup_name = fgroup_id.find('description').text 338 | fgroup_parent_id = fgroup_id.find('parent_id') 339 | fgroup_level_id=0 340 | fgroup_level_id = fgroup_id.find('level_id').text 341 | level=">"''' 342 | neweventqid = str(newevent.get('qid')) 343 | ###fgroup_id = root.find('fgroup[qid="'+fgroup_link_fgroup_id+'"]') 344 | 345 | 346 | 347 | responsHTML=responsHTML+str('QID: '+str(newevent.get('qid')))+'
' 348 | 349 | 350 | LLC=str('LLC : '+str(newevent.get('lowLevelCategory'))) 351 | CRS=str('CRS : '+str(newevent.get('credibility'))+str(newevent.get('relevance'))+str(newevent.get('severity'))) 352 | forceOffense=str(newevent.get('forceOffenseCreation')) 353 | if forceOffense is not None and forceOffense=='true': 354 | htmlforceOffense='Offense ' 355 | else: 356 | htmlforceOffense='No-Offense ' 357 | 358 | 359 | responsHTML=responsHTML+htmlforceOffense+'
' 360 | responsHTML=responsHTML+LLC+'
' 361 | responsHTML=responsHTML+CRS+'
' 362 | 363 | # print(qid.get('qid')) 364 | # print(qid.get('lowLevelCategory')) 365 | # print(qid.get('forceOffenseCreation')) 366 | 367 | 368 | ## for action in responsDefinitions.iter(): 369 | ## if action.tag is not None: 370 | 371 | # print(action.tag) 372 | # print(action.items()) 373 | ## responsHTML=responsHTML+str(action.items())+'X' 374 | 375 | 376 | # print(actionHTML) 377 | # print(responsHTML) 378 | 379 | # actionDefs=[actionDefinitions,ET.Element('')][actionDefinitions is not None] 380 | # responsDefs=[responsDefinitions,ET.Element('empty')][responsDefinitions is not None] 381 | # print(responsDefs) 382 | # if responsDefs is not None: 383 | # for action in responsDefs.iter(): 384 | # print(action.items()) 385 | 386 | # print(actionDefs) 387 | 388 | 389 | #print(prtTestArray) 390 | #print(fullTestArray) 391 | ###### htmlout.append(''+drdroot.find('name').text+''+ruleUUID+''+drdroot.get('type')+''+htmlruleEnabled+''+str(drdroot.find('notes').text)+'') 392 | 393 | htmlout.append('') 394 | htmlout.append(''+(''.join(htmlRuleDef))+'') 395 | # htmlout.append(''+str(ruleUUID)+'

isBB: '+str(ruleIsBB)+'

'+htmlruleEnabled+'

Type: '+drdroot.get('type')+''+drdroot.find('name').text+''+str(drdroot.find('notes').text)+'') 396 | htmlout.append(''+(''.join(htmlTestArray))+'') 397 | htmlout.append(''+(''.join(responsHTML))+'') 398 | # htmlout.append(''+responsHTML+'') 399 | htmlout.append('') 400 | print() 401 | print(' '.join(htmlout)) 402 | htmlout=[] 403 | print(' '.join(htmlfooter)) 404 | 405 | 406 | if __name__ == "__main__": 407 | main() 408 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QRadar-ruleset 2 | QRadar Export the rule set for printing 3 | 4 | 5 | On QRadar system run: 6 | 7 | /opt/qradar/bin/contentManagement.pl --action export -c fgroup -i all 8 | 9 | 10 | copy the resulting file to a workstation and unpack the file: 11 | 12 | tar -xvzf [filename.gz] 13 | 14 | 15 | run the Python script: 16 | 17 | python3 QRadar-rules2csv.py [filename.xml] > rules.csv 18 | OR 19 | python3 QRadar-rule2html.py [filename.xml] > rules.html 20 | 21 | open the resulting rules.htl file in a browser. 22 | OR 23 | Import the rules.csv file into a spreadsheet, field delimiter is "," 24 | 25 | format and print as needed. 26 | 27 | DISCLAIMER: This code is free to use, feedback is welcome, do not call IBM with support issues or questions on this code. 28 | 29 | --------------------------------------------------------------------------------