├── .gitignore ├── AUTHORS.md ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── TODO ├── bin └── AppVulnMS-cli.py ├── data ├── AppVulnDB │ ├── AppVulnDB.sqlite │ └── schema.sqlite ├── AppVulnXML │ └── AppVulnXML.xsd └── modules │ └── converter │ ├── acunetix │ └── transformation.xslt │ ├── burpsuite-scanner │ └── transformation.xslt │ ├── hp-webinspect │ └── transformation.xslt │ └── ibm-appscan │ └── transformation.xslt ├── env └── requirements.pip └── src ├── __init__.py ├── core ├── __init__.py ├── conf │ ├── __init__.py │ └── appvulnms.py ├── db │ ├── AppVulnDB.py │ └── __init__.py ├── framework.py ├── parser │ ├── AppVulnXMLParser.py │ ├── ArgsParser.py │ ├── HTTPParser.py │ └── __init__.py └── util.py └── modules ├── converter └── xml │ ├── acunetix.py │ ├── burpsuite-scanner.py │ └── ibm-appscan.py └── vms └── appvulndb └── sqlite.py /.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | __pycache__/ 3 | *.py[cod] 4 | *.sw[po] 5 | *.sublime-* 6 | local/ 7 | tmp/ 8 | env/bin 9 | env/lib 10 | env/include 11 | 12 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | 3 | * **Victor Dorneanu** (lead developer) 4 | + Twitter: [@victordorneanu](http://twitter.com/victordorneanu) 5 | + Web: [dornea.nu](http://dornea.nu) 6 | + Blog: [blog.dornea.nu](http://blog.dornea.nu) 7 | + GitHub: [github.com/dorneanu](http://github.com/dorneanu) 8 | + Xing: [xing.to/dorneanu](http://xing.to/dorneanu) 9 | 10 | ## Special Thanks To 11 | 12 | * [nullsecurity](http://nullsecurity.net) - Hack the planet! -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.1 4 | 5 | * First version :) -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # AppVulnMS 2 | 3 | **AppVulnMS** has been released under the *MIT* License: 4 | 5 | 6 | ``` 7 | The MIT License (MIT) 8 | 9 | Copyright (c) 2014 Victor Dorneanu 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | ``` 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This bundle of python scripts is aimed at everyone in the IT security industry who has to inspect, manage and track application vulnerabilities and create nice looking reports. At the moment there is de facto no standard format/layout to describe application vulnerabilities. In the past history there were several suggestions but none of them was established as a standard. 4 | 5 | Besides that every (web) application scanner uses its own reporting format so you can't really compare results from different vendors. This is here the idea for this whole project came of: The ability to compare different scanning results for better vulnerability scan coverage. 6 | 7 | # Technology 8 | 9 | I mainly use Python to to the most important jobs because of its simplicity and OS independence. Among Python 3.x I make heavy use of: 10 | 11 | * **XML** 12 | Used to store vulnerability data and to convert scanning results into preferred layout 13 | 14 | * **XSLT** 15 | XML transformation at its best. Basically there is one XSLT file for every single application scanner to do the transformation part. The rest is performed by Python magic. 16 | 17 | * **SQLite** 18 | I love SQL and I definitely love SQLite for being a lite, useful RDMS. I generally like the idea of putting data into relations and therefor create astonishing results. Just try to do the same with Excel and afterwards you'll love *relational* data bases. 19 | 20 | 21 | # The AppVuln* suite 22 | 23 | The vulnerability management system (VMS) consists of 3 main components 24 | 25 | * **AppVulnXML** 26 | Specifies how to store application vulnerability data and structures it a useful parseable way. In case of HTTP the traffic is being parsed and saved in smaller structures like: URL, headers, POST parameters, data etc. 27 | 28 | * **AppVulnDB** 29 | In order to track all the vulnerabilities found within your favorite scanning tool, you'll need a DB to take of the management itself. AppVulnDB is basically the vulnerability management system mentioned before and records vulnerabilities (and metadata) in the AppVulnXML format. So this component will provide you with an API to import/export AppVulnXML files. 30 | 31 | * **AppVulnMS** 32 | If you put all together, you'll get a VMS able to convert scanning results into AppVulnXML format and finally import them to the AppVulnDB. At the moment the AppVulnMS describes the whole software bundle you'll get. 33 | 34 | 35 | ## AppVulnXML 36 | 37 | The XML describes a way how to handle and store application vulnerability data. Meta data is stored as well. Generally speaking you'll have 3 main section within any XML document: 38 | 39 | * **Scanner information** 40 | Includes details about the scanner itself like name, version etc. 41 | 42 | * **Summary** 43 | You'll get a brief short summary about results: How many findings? How many High/Medium/Log/Informational issues? Scan duration ... 44 | 45 | * **Results** 46 | Last but not least you'll get the results section which contains all vulnerability related data. 47 | 48 | 49 | ### Scanner information 50 | 51 | ```xml 52 | 53 | Favourite scanning tool 54 | x.y.z 55 | 56 | ``` 57 | 58 | ### Summary 59 | 60 | ```xml 61 | 62 | 41 63 | 64 | 65 | 66 | 67 | 7 68 | 0 69 | 1 70 | 33 71 | 72 | 73 | 74 | 75 | ``` 76 | 77 | 78 | ### Results 79 | 80 | ```xml 81 | 82 | 83 | 84 | Authentication Bypass Using SQL Injection 85 | SQL Injection 86 | 87 | High 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | /bank/login.aspx 96 | 97 |
98 | ... 99 |
100 |
101 |
102 | 103 | 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 | ## AppVulnDB 136 | 137 | At the moment the vulnerability DB consists of following tables: 138 | 139 | * **scanner** 140 | Contains scanner related details, e.g. version 141 | 142 | * **scan** 143 | A scan entry contains all summary information about a single scan: 144 | 145 | * *scope* (e.g. Single scan) 146 | 147 | * *target* (e.g. http://example.com) 148 | 149 | * *start/finish date of scan* 150 | 151 | * *scanner ID* (references one entry in the *scanner* table) 152 | 153 | * *number of total vulnerabilities* 154 | 155 | * etc. 156 | 157 | * **vulnerability** 158 | The main table aimed to store all valuable information about a vulnerability: 159 | 160 | * *type* (e.g. XSS, CSRF, Buffer Overflow etc.) 161 | 162 | * *name* (e.g. "XSS in URL bla bla") 163 | 164 | * *description* 165 | 166 | * *error_type* (e.g. "false_positive" or simply empty suggesting that the vulnerability is a real one) 167 | 168 | * *severity* (e.g. High, Medium, Low, Informational) 169 | 170 | * *scanner ID* (reference to *scanner* table) 171 | 172 | * *poc_type_id* (reference to the *poc_type* table) 173 | 174 | * *poc_id* 175 | 176 | * *date added* 177 | 178 | * *comments* 179 | 180 | * **poc_type** 181 | Since one application can communicate through different protocols, different PoC (Proof of Concept) types are needed. Example: If you want to describe a XSS vulnerability you'll need a different level of granularity as a buffer overflow vulnerability. That's why I think different PoC types are needed depending on the protocols the application is able to use. Since the focus of AppVulnMS is more or less on *web* application vulnerabilities, there'll be a default PoC type called *http*. But feel free to add your own types. 182 | 183 | * **poc_type_http** 184 | Handles PoC data for the PoC type *http*: 185 | 186 | * *URL* 187 | 188 | * *method* (e.g. GET, POST, DELETE, HEAD etc.) 189 | 190 | * *request headers* 191 | This field contains all request headers as a parseable XML structure. 192 | 193 | * *request cookies* 194 | This field contains all request cookies as a parseable XML structure. 195 | 196 | * *request_data* 197 | If you have a POST method, then you'll always want to store the POST data as well. 198 | 199 | * *response headers* 200 | 201 | * *response cookies* 202 | 203 | * *response data* 204 | This field contains the response data from server in plain text. 205 | 206 | * *input type* 207 | Describes what kind of input type was used to trigger the payload: Cookie, parameter, request header etc. 208 | 209 | * *input name* 210 | Name of the cookie, parameter, request header etc. 211 | 212 | * *input data* 213 | Contains payload input data sent to the application. 214 | 215 | * *scanner* (reference to the *scanner* table) 216 | 217 | * **reference** 218 | Contains tool specific references related to one vulnerability. 219 | 220 | * *reference type* (e.g. external site, CVE, CWE. NVD etc.) 221 | 222 | * *name of the reference* (e.g. "Read more about SQLi ... ") 223 | 224 | * *reference ID* 225 | This is very handy when it comes to public vulnerabilities data bases like CVE, CWE etc. In that case the reference ID contains the CVE/CWE/[...] ID you can lookup in that particular data base. 226 | 227 | * *URL* 228 | 229 | 230 | Having those vulnerability details one could easily generate fancy pie charts or diagrams using external libraries like [HighCharts](http://www.highcharts.com/) or [D3](http://d3js.org/). No integration with all this kind of libraries is planed. 231 | 232 | 233 | ## AppVulnMS 234 | 235 | The management system itself tries to act as a connecting glue between the scan results, the AppVulnXML files and the AppVulnDB. You could think of some modular framework - although it's not - easy to extend with your new modules. I tried to simplify the basic structure of a single module and give the user the ability to run every module from the command line. Every module should at least have some *--help* option: 236 | 237 | ```shell 238 | 239 | $ python3 bin/AppVulnMS-cli.py m --help 240 | 241 | .... 242 | 243 | ``` 244 | 245 | Each module should have a *parent category* like *convert*, *appvulndb*, *report* and so on: Just have a look inside the *modules* directory. 246 | 247 | ### Available modules 248 | 249 | Currently there are following categories and modules available: 250 | 251 | * **converter** 252 | 253 | * IBM Rational AppScan 8.x 254 | 255 | * Acunetix 9.x 256 | 257 | * PortSwigger Burp Suite 258 | 259 | 260 | * **AppVulnDB** 261 | 262 | * SQLite: SQLite implementation of AppVulnDB 263 | 264 | 265 | # Usage 266 | 267 | Feel free to clone this repository. In order to get this work make sure you have following packages installed: 268 | 269 | * **python 3.x** 270 | 271 | * **python-sqlite** 272 | 273 | * **python-lxml** 274 | Python3 binding for the libxml2 and libxslt libraries 275 | 276 | 277 | ## Basic 278 | 279 | The basic run would be: 280 | 281 | ```shell 282 | 283 | $ python3 bin/AppVulnMS-cli.py -h 284 | 285 | _ __ __ _ __ __ ___ 286 | /_\ _ __ _ _\ \ / / _| |_ _ | \/ / __| 287 | / _ \| '_ \ '_ \ V / || | | ' \| |\/| \__ \ 288 | /_/ \_\ .__/ .__/\_/ \_,_|_|_||_|_| |_|___/ 289 | |_| |_| 290 | 291 | -------------------------------------------- 292 | Application Vulnerability Management System 293 | 294 | 295 | usage: AppVulnMS-cli.py [-h] {m} ... 296 | 297 | positional arguments: 298 | {m} Choose mode 299 | m Interact with available modules 300 | 301 | optional arguments: 302 | -h, --help show this help message and exit 303 | 304 | ``` 305 | 306 | As you can at the moment there is only one mode to run *AppVulnMS* with. In the near future an additional mode "db" should be added which is supposed to handle all the DB actions. Right now the DB activities are bundled into one single module. 307 | 308 | Now let's have a look at the additional parameters for the (m)odule mode: 309 | 310 | ```shell 311 | 312 | $ python3 bin/AppVulnMS-cli.py m -h 313 | 314 | ... 315 | 316 | usage: AppVulnMS-cli.py m [optional args] [positional args] 317 | 318 | ------------------------------------------------------------------------ 319 | In order to list available modules: 320 | $ AppVulnMS-cli.py m -l 321 | 322 | To interact with specific module: 323 | $ AppVulnMS-cli.py m 324 | 325 | To get modules parameters: 326 | $ AppVulnMS-cli.py m --help 327 | ------------------------------------------------------------------------ 328 | 329 | ::: Interact with the available modules. 330 | 331 | positional arguments: 332 | module_name Modules name 333 | module_params Modules parameters 334 | 335 | optional arguments: 336 | -h, --help show this help message and exit 337 | -q, --quiet Keep it quiet 338 | -v, --verbose Add verbosity 339 | -l, --list-modules List available modules 340 | 341 | ``` 342 | 343 | 344 | Let's have a look at the available modules: 345 | 346 | 347 | ```shell 348 | 349 | $ python3 bin/AppVulnMS-cli.py m -l 350 | 351 | ... 352 | 353 | [INFO] Available modules: 354 | ::: vms/appvulndb/sqlite 355 | _ Desc Manages application vulnerabilities in SQLite DB 356 | _ Author Cyneox / nullsecurity.net 357 | _ Version v0.1 358 | _ URL: http://sqlite.org/ 359 | 360 | ::: converter/xml/acunetix 361 | _ Desc Converts Acunetix into WAVXML format 362 | _ Author Cyneox / nullsecurity.net 363 | _ Version v0.1 364 | _ URL: http://www.acunetix.com 365 | 366 | ::: converter/xml/burpsuite-scanner 367 | _ Desc Converts BurpSuite scanner results into WAVXML 368 | _ Author Cyneox / nullsecurity.net 369 | _ Version v0.1 370 | _ URL: http://portswigger.net/burp/ 371 | 372 | ::: converter/xml/ibm-appscan 373 | _ Desc Converts IBM AppScan results into suitable XML using XSLT 374 | _ Author Cyneox / nullsecurity.net 375 | _ Version v0.1 376 | _ URL: http://www-03.ibm.com/software/products/us/en/appscan/ 377 | 378 | ``` 379 | 380 | ## Convert scanning results 381 | 382 | In order to convert scanning results you'll have to export them first and then run AppVulnMS. For Acunetix scanning results that'd be: 383 | 384 | ```shell 385 | 386 | $ python3 bin/AppVulnMS-cli.py m converter/xml/acunetix -i Acunetix-Export.xml -x data/modules/converter/acunetix/transformation.xslt -o Acunetix-Export-Converted.xml 387 | 388 | ``` 389 | 390 | 391 | Explanations: 392 | 393 | * -i Acunetix-Export.xml 394 | Specified the input file. 395 | 396 | * -x data/modules/[...]/transformation.xslt 397 | Specifies the XSLT file to use for the transformation. You could of course use your own one. 398 | 399 | * -o Acunetix-Export-Converted.xml 400 | Where to store output. 401 | 402 | 403 | ## Import files to DB 404 | 405 | Let's have a look at the implemented module and its options: 406 | 407 | 408 | ```shell 409 | $ python3 bin/AppVulnMS-cli.py m vms/appvulndb/sqlite --help 410 | 411 | ... 412 | 413 | usage: AppVulnMS-cli.py [-h] {init,import} ... 414 | 415 | positional arguments: 416 | {init,import} Choose mode 417 | init Create SQLite AppVulnDB 418 | import Import vulns in AppVulnXML format into DB 419 | 420 | optional arguments: 421 | -h, --help show this help message and exit 422 | ``` 423 | 424 | So first of all we'll have to init/create a DB file: 425 | 426 | 427 | ```shell 428 | $ python3 bin/AppVulnMS-cli.py m vms/appvulndb/sqlite init -f appvulndb.sqlite 429 | 430 | ... 431 | 432 | [INFO] Succesfully created DB 433 | ``` 434 | 435 | 436 | And now we could easily import the previous generated AppVulnXML file into the DB: 437 | 438 | 439 | ```shell 440 | $ python3 bin/AppVulnMS-cli.py m vms/appvulndb/sqlite import -d appvulndb.sqlite -f tmp/acunetix.xml 441 | 442 | ... 443 | 444 | [INFO] Successfully imported data into DB 445 | ``` 446 | 447 | 448 | Now you can verify the results using some SQLite client: 449 | 450 | 451 | ```shell 452 | $ echo "SELECT COUNT(*) FROM vulnerability;SELECT * FROM scanner;" | sqlite3 -interactive appvulndb.sqlite 453 | SQLite version 3.8.2 2013-12-06 23:53:30 454 | Enter ".help" for instructions 455 | Enter SQL statements terminated with a ";" 456 | sqlite> SELECT COUNT(*) FROM vulnerability;SELECT * FROM scanner; 457 | 179 458 | 1|Acunetix Web Vulnerability Scanner|9| 459 | sqlite> 460 | ``` 461 | 462 | 463 | # Disclaimer 464 | 465 | This piece of software works as it is. Although its far behind from being only proof of concept, you could still use it in your company. Since I've code it in my free time any credits, "I LIKE"s whatever would be appreciated. If you have any suggestions about new modules, better core design or simply want to say "Hello", don't hesitate to contact me. 466 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | --- v0.2 --------------------------------------------------------------- 2 | 3 | * Add converter/xml/nikto ................................... [pending] 4 | * Add converter/xml/w3af .................................... [pending] 5 | * Add converter/xml/nmap .................................... [pending] 6 | -------------------------------------------------------------------------------- /bin/AppVulnMS-cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2014-02-09 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-06 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | 34 | import argparse 35 | import pprint 36 | import sys,os.path 37 | 38 | # Add folders to Pythons system path 39 | sys.path.append('./src/') 40 | 41 | # Add own modules/packages 42 | from core.parser.ArgsParser import ArgsParser 43 | import core.conf.appvulnms as conf 44 | 45 | 46 | def display_banner(): 47 | """ Display banner """ 48 | print(conf.appvulnms['banner']) 49 | 50 | if __name__ == '__main__': 51 | display_banner() 52 | 53 | # Create parser 54 | parser = ArgsParser(sys.argv[0]) 55 | 56 | # Check for arguments 57 | if (len(sys.argv) > 1): 58 | # Parse options 59 | args = parser.parse(sys.argv[1:]) 60 | 61 | # Run actions 62 | parser.run_actions(args) 63 | else: 64 | parser.print_help() 65 | -------------------------------------------------------------------------------- /data/AppVulnDB/AppVulnDB.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorneanu/appvulnms/306be6f123de95685a8e27b368534de8b27e7b38/data/AppVulnDB/AppVulnDB.sqlite -------------------------------------------------------------------------------- /data/AppVulnDB/schema.sqlite: -------------------------------------------------------------------------------- 1 | CREATE TABLE vulnerability ( 2 | "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 3 | "type" TEXT, 4 | "name" TEXT, 5 | "severity" TEXT, 6 | "description" TEXT, 7 | "scan_id" INTEGER, 8 | "date_added" TEXT, 9 | "error_type" TEXT, 10 | "poc_type_id" INTEGER, 11 | "poc_id" INTEGER, 12 | "comments" TEXT 13 | ); 14 | CREATE TABLE "scanner" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE , "name" TEXT, "version" TEXT, "description" TEXT); 15 | ; 16 | CREATE TABLE "scan" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE , "scope" TEXT, "target" TEXT, "date_started" TEXT, "date_finished" TEXT, "scanner_id" INTEGER, "count_total_vulns" INTEGER, "count_new_vulns" INTEGER, "count_old_vulns" INTEGER, "count_fixed_vulns" INTEGER); 17 | ; 18 | CREATE TABLE "poc_type_http" ( 19 | "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 20 | "vulnerability_id" INTEGER NOT NULL, 21 | "URL" TEXT, 22 | "method" TEXT, 23 | "request_header" TEXT, 24 | "request_cookie" TEXT, 25 | "request_data" TEXT, 26 | "response_header" TEXT, 27 | "response_cookie" TEXT, 28 | "response_data" TEXT, 29 | "input_type" TEXT, 30 | "input_name" TEXT, 31 | "input_payload" TEXT, 32 | "scanner" TEXT 33 | ); 34 | CREATE TABLE "poc_type" ( 35 | "id" INTEGER PRIMARY KEY NOT NULL, 36 | "name" TEXT, 37 | "poc_table_name" TEXT NOT NULL 38 | ); 39 | CREATE TABLE "reference" ( 40 | "id" INTEGER PRIMARY KEY, 41 | "reference_type" TEXT, 42 | "name" TEXT, 43 | "ref_id" INTEGER, 44 | "URL" TEXT 45 | ); 46 | 47 | -- Init some tables 48 | INSERT INTO poc_type (name, poc_table_name) VALUES ("http", "poc_type_http") 49 | -------------------------------------------------------------------------------- /data/AppVulnXML/AppVulnXML.xsd: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 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 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | -------------------------------------------------------------------------------- /data/modules/converter/acunetix/transformation.xslt: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Acunetix Web Vulnerability Scanner 37 | 9 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | true 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 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 | -------------------------------------------------------------------------------- /data/modules/converter/burpsuite-scanner/transformation.xslt: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Portswigger BurpSuite Pro 37 | 38 | 39 | 40 | 41 | No scan duration available 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | true 88 | 89 | 90 | 91 | true 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 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 | -------------------------------------------------------------------------------- /data/modules/converter/hp-webinspect/transformation.xslt: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | HP WebInspect Web Vulnerability Scanner 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 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 | -------------------------------------------------------------------------------- /data/modules/converter/ibm-appscan/transformation.xslt: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | IBM Rational AppScan 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 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 | external-site 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /env/requirements.pip: -------------------------------------------------------------------------------- 1 | lxml 2 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2013-12-02 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-04 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | -------------------------------------------------------------------------------- /src/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorneanu/appvulnms/306be6f123de95685a8e27b368534de8b27e7b38/src/core/__init__.py -------------------------------------------------------------------------------- /src/core/conf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorneanu/appvulnms/306be6f123de95685a8e27b368534de8b27e7b38/src/core/conf/__init__.py -------------------------------------------------------------------------------- /src/core/conf/appvulnms.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2014-01-30 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-05 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | 34 | # Modules specific options/settings 35 | modules = {} 36 | modules['path'] = './src/modules/' 37 | modules['path_delim'] = '/' 38 | modules['path_loadname_delim'] = '::' 39 | modules['module_suffix'] = '.py' 40 | 41 | # appvulnms specific options/settings 42 | appvulnms = {} 43 | appvulnms['banner'] = """ 44 | _ __ __ _ __ __ ___ 45 | /_\ _ __ _ _\ \ / / _| |_ _ | \/ / __| 46 | / _ \| '_ \ '_ \ V / || | | ' \| |\/| \__ \ \ 47 | 48 | /_/ \_\ .__/ .__/\_/ \_,_|_|_||_|_| |_|___/ 49 | |_| |_| 50 | 51 | -------------------------------------------- 52 | Application Vulnerability Management System 53 | 54 | """ 55 | 56 | # Parser settings 57 | appvulnms['usage'] = \ 58 | "%(prog)s [optional args] {positional args}\n\n\ 59 | ------------------------------------------------------------------------\n\ 60 | Choose between the available modes and check parameters:\n\ 61 | \t$ $%(prog)s --help\n\ 62 | ------------------------------------------------------------------------\n\ 63 | " 64 | 65 | appvulnms['module_mode_usage'] = \ 66 | "%(prog)s [optional args] [positional args]\n\n\ 67 | ------------------------------------------------------------------------\n\ 68 | In order to list available modules:\n\ 69 | \t$ %(prog)s -l \n\n\ 70 | To interact with specific module:\n\ 71 | \t$ %(prog)s \n\n\ 72 | To get modules parameters:\n\ 73 | \t$ %(prog)s --help\n\ 74 | ------------------------------------------------------------------------\n\ 75 | " 76 | -------------------------------------------------------------------------------- /src/core/db/AppVulnDB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2014-01-30 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-04 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | 34 | import argparse 35 | import os 36 | import sqlite3 37 | import traceback 38 | from lxml import etree 39 | 40 | from core.framework import Log 41 | from core.parser.AppVulnXMLParser import AppVulnXMLParser 42 | 43 | 44 | class AppVulnDB(): 45 | 46 | """ Manage and imports AppVulnXML files """ 47 | 48 | def __init__(self, appVulnXML_file): 49 | try: 50 | with open(appVulnXML_file) as f: 51 | xml_data = f.read() 52 | self.VulnParser = AppVulnXMLParser(xml_data) 53 | except: 54 | Log.error("Error reading AppVulnXML file\n%s" % traceback.format_exc()) 55 | 56 | def get_scanner(self): 57 | """Return scanner data 58 | 59 | :returns: Dictionary with scanner information 60 | """ 61 | 62 | scanner = {} 63 | data = self.VulnParser.get_scanner() 64 | 65 | try: 66 | if len(data) > 0: 67 | scanner = {} 68 | scanner['name'] = data[0].xpath("Name")[0].text 69 | scanner['version'] = data[0].xpath("Version")[0].text 70 | except: 71 | Log.warn("Failed getting scanner data") 72 | finally: 73 | return scanner 74 | 75 | def get_scan_summary(self): 76 | """Returns scan summary details 77 | 78 | :returns: Dictionary with scan summary information 79 | """ 80 | 81 | summary = {} 82 | data = self.VulnParser.get_summary() 83 | try: 84 | if len(data) > 0: 85 | summary['TotalIssues'] = data[0].xpath("TotalIssues")[0].text 86 | summary['NumHighIssues'] = data[0].xpath("Target/Host/Issues/High")[0].text 87 | summary['NumMediumIssues'] = data[0].xpath("Target/Host/Issues/Medium")[0].text 88 | summary['NumLowIssues'] = data[0].xpath("Target/Host/Issues/Low")[0].text 89 | summary['NumInfoIssues'] = data[0].xpath("Target/Host/Issues/Informational")[0].text 90 | summary['TargetHost'] = data[0].xpath("Target/Host/@name")[0] 91 | summary['ScanDuration'] = data[0].xpath("ScanDuration")[0].text 92 | except: 93 | Log.warn("Failed getting scan summary details") 94 | finally: 95 | return summary 96 | 97 | def get_request_headers(self, poc_xml): 98 | """Get XML tree containing all request headers from POC data 99 | 100 | :param poc_xml: PoC XML data 101 | :returns: Tuple of (headers, cookie, data) 102 | """ 103 | 104 | headers = etree.Element("Headers") 105 | cookies = etree.Element("Cookies") 106 | data = etree.Element("Data") 107 | try: 108 | req_headers = poc_xml.xpath("Request/Parsed/Header") 109 | req_cookies = poc_xml.xpath("Request/Parsed/Header[@name = 'Cookie']") 110 | data = poc_xml.xpath("Request/Parsed/Data")[0] 111 | 112 | for h in req_headers: 113 | headers.append(h) 114 | for c in req_cookies: 115 | cookies.append(c) 116 | except: 117 | Log.warn("Couldn't get request headers") 118 | finally: 119 | return (headers, cookies, data) 120 | 121 | def get_response_headers(self, poc_xml): 122 | """Get XML tree containing all response headers from POC data 123 | 124 | :param poc_xml: PoC XML data 125 | :returns: Tuple of (headers, cookie, response_data) 126 | """ 127 | 128 | headers = etree.Element("Headers") 129 | cookies = etree.Element("Cookies") 130 | data = etree.Element("Data") 131 | 132 | try: 133 | res_headers = poc_xml.xpath("Response/Parsed/Header") 134 | res_cookies = poc_xml.xpath("Response/Parsed/Header[@name = 'Set-Cookie']") 135 | res_data = poc_xml.xpath("Response/Parsed/Data") 136 | 137 | if res_data: 138 | res_data = res_data[0] 139 | else: 140 | res_data = etree.Element('Data') 141 | 142 | for h in res_headers: 143 | headers.append(h) 144 | for c in res_cookies: 145 | cookies.append(c) 146 | except: 147 | Log.warn("Couldn't get response headers") 148 | finally: 149 | return (headers, cookies, res_data) 150 | 151 | def get_payload_data(self, poc_xml): 152 | """ Get payload specific information 153 | 154 | :param poc_xml: POC XML data 155 | :returns: Tuple of (payload_type, payload_name, payload_data) 156 | """ 157 | 158 | payload = poc_xml.xpath("Request/Payload") 159 | try: 160 | if payload: 161 | payload_type = payload[0].xpath("Input/@type")[0] 162 | payload_name = payload[0].xpath("Input/@name")[0] 163 | payload_data = payload[0].xpath("Raw")[0].text 164 | return payload_type, payload_name, payload_data 165 | else: 166 | return '', '', '' 167 | except: 168 | Log.warn("Couldn't get payload data") 169 | return '', '', '' 170 | 171 | def get_references(self, poc_xml): 172 | """ Get XML tree containing references 173 | 174 | :param poc_xml: POC XML data 175 | :returns: etree.Element containing references 176 | """ 177 | references = poc_xml.xpath("References") 178 | 179 | if references: 180 | return references[0] 181 | else: 182 | return etree.Element("References") 183 | 184 | def get_vulns(self): 185 | """ Get all vulnerabilities with detailed data (e.g. header, poc etc.) 186 | 187 | :returns: List of vulnerabilities 188 | """ 189 | appVulnXML_vulns = self.VulnParser.get_vulnerabilities() 190 | vulns = [] 191 | 192 | try: 193 | if len(appVulnXML_vulns) > 0: 194 | for v in appVulnXML_vulns: 195 | # Gather general information 196 | issue = {} 197 | issue['type'] = v.xpath("@type")[0] 198 | issue['description'] = v.xpath("Description")[0].text 199 | issue['severity'] = v.xpath("Severity")[0].text 200 | issue['error_type'] = v.xpath("@error_type")[0] 201 | 202 | # Extract POC information 203 | poc_xml = v.xpath("TestProbe/HTTP")[0] 204 | poc_data = {} 205 | poc_data['URL'] = poc_xml.xpath("Request/URL")[0].text 206 | poc_data['method'] = poc_xml.xpath("Request/@method")[0] 207 | 208 | # Extract request data 209 | headers, cookies, data = self.get_request_headers(poc_xml) 210 | poc_data['req_hdr'] = etree.tostring(headers).decode("utf-8") 211 | poc_data['req_cookies'] = etree.tostring(cookies).decode("utf-8") 212 | poc_data['req_data'] = etree.tostring(data).decode("utf-8") 213 | 214 | # Extract response data 215 | headers, cookies, data = self.get_response_headers(poc_xml) 216 | poc_data['res_hdr'] = etree.tostring(headers).decode("utf-8") 217 | poc_data['res_cookies'] = etree.tostring(cookies).decode("utf-8") 218 | poc_data['res_data'] = etree.tostring(data).decode("utf-8") 219 | 220 | # Extract payload data 221 | payload_type, payload_name, payload_data = self.get_payload_data(poc_xml) 222 | poc_data['payload_type'] = payload_type 223 | poc_data['payload_name'] = payload_name 224 | poc_data['payload_data'] = payload_data 225 | 226 | # Add references 227 | poc_data['references'] = etree.tostring(self.get_references(poc_xml)) 228 | 229 | # Add vulnerability to list 230 | v = {} 231 | v['issue'] = issue 232 | v['poc_data'] = poc_data 233 | vulns.append(v) 234 | 235 | except: 236 | Log.warn("Failed getting vulnerabilities") 237 | finally: 238 | return vulns 239 | -------------------------------------------------------------------------------- /src/core/db/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorneanu/appvulnms/306be6f123de95685a8e27b368534de8b27e7b38/src/core/db/__init__.py -------------------------------------------------------------------------------- /src/core/framework.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2014-06-04 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-05 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | 34 | import imp 35 | import os 36 | import sys 37 | import traceback 38 | from abc import ABCMeta, abstractmethod 39 | from pprint import pprint 40 | 41 | import core.conf.appvulnms as conf 42 | 43 | 44 | class Log(): 45 | 46 | """Implements simple logging functionalities 47 | """ 48 | 49 | def __init__(self): 50 | pass 51 | 52 | @staticmethod 53 | def info(msg): 54 | """Prints a info message 55 | 56 | :param msg: Message to be printed 57 | """ 58 | print("[\033[33mINFO\033[0m]\t%s" % msg) 59 | 60 | @staticmethod 61 | def warn(msg): 62 | """Prints a warning message 63 | 64 | :param msg: Message to be printed 65 | """ 66 | print("[\033[34mWARN\033[0m]\t%s" % msg) 67 | 68 | @staticmethod 69 | def error(msg): 70 | """Print an error message 71 | 72 | :param msg: Message to be printed 73 | """ 74 | print("[\033[31mERROR\033[0m]\t%s\n" % msg) 75 | 76 | 77 | class ModuleCollection(): 78 | 79 | """Easy modules management 80 | 81 | Provides some sort of API in order to organize modules. 82 | """ 83 | 84 | def __init__(self, path): 85 | self.categories = {} 86 | self.modules = {} 87 | self.path = path 88 | 89 | def load_modules(self): 90 | """Loads modules from specified path (self.path)""" 91 | 92 | for dirpath, dirs, files in os.walk(self.path): 93 | 94 | # Filter only Python files 95 | modules = [f for f in files if f.endswith(conf.modules['module_suffix'])] 96 | 97 | if len(modules) > 0: 98 | category_path = dirpath.split('/')[3:] 99 | modules_category = "/".join(category_path) 100 | 101 | # Add new category 102 | if modules_category not in self.categories: 103 | self.categories[modules_category] = [] 104 | 105 | # Seek for available modules 106 | for m in modules: 107 | module = {} 108 | module['name'] = m.split('.')[0] 109 | module['path'] = os.path.join(dirpath, m) 110 | module['disp_name'] = modules_category + conf.modules['path_delim'] + module['name'] 111 | module['load_name'] = module['disp_name'].replace('/', conf.modules['path_loadname_delim']) 112 | 113 | # Open and load module 114 | try: 115 | module_file = open(module['path'], 'rb') 116 | imp.load_source(module['load_name'], module['path'], module_file) 117 | __import__(module['load_name']) 118 | 119 | # Add to list(s) 120 | self.categories[modules_category].append(module['load_name']) 121 | self.modules[module['disp_name']] = module['load_name'] 122 | except: 123 | traceback.print_exc() 124 | 125 | def load_module(self, module): 126 | """Loads specified module 127 | 128 | :param module: Module to load 129 | """ 130 | try: 131 | # FIXME: Load module name from self.modules (e.g. self.modules[module]) 132 | _m = sys.modules[module.replace('/', conf.modules['path_loadname_delim'])] 133 | loaded_module = _m.Module(module) 134 | return loaded_module 135 | except Exception: 136 | Log.error("Error loading module: %s" % traceback.format_exc()) 137 | return 138 | 139 | def show_modules(self): 140 | """Prints all available modules in self.path""" 141 | 142 | Log.info("Available modules:") 143 | for m in self.modules: 144 | module = self.load_module(m) 145 | module.display_info() 146 | print("") 147 | 148 | def show_modules_by_category(self, category): 149 | """Prints all modules available in specified category 150 | 151 | :param category: Category to lookup for modules. 152 | """ 153 | 154 | # TODO: Implement me 155 | pass 156 | 157 | def show_module(self, module): 158 | """Prints information about module 159 | 160 | :param module: Print information about module 161 | """ 162 | 163 | # TODO: Implement me 164 | pass 165 | 166 | 167 | class BaseModule(): 168 | 169 | """Abstract class for inner structure of a module 170 | 171 | Defines abstract methodes which _have_ to be implemented by the 172 | modules subclassing/inheriting this class. 173 | """ 174 | 175 | @abstractmethod 176 | def __init__(self, params): 177 | self.info = 'Module' 178 | self.parameters = {} 179 | pass 180 | 181 | @abstractmethod 182 | def module_load(self, params): 183 | """Loads a module 184 | 185 | :param params: Parameters in order to load module. 186 | """ 187 | 188 | pass 189 | 190 | @abstractmethod 191 | def module_run(self): 192 | """Runs module""" 193 | 194 | pass 195 | 196 | @abstractmethod 197 | def parse_params(self, params): 198 | """Parses parameters and return arguments 199 | 200 | :param params: Module specific parameters 201 | """ 202 | 203 | self.args = self.parser.parse_args(params) 204 | 205 | @abstractmethod 206 | def get_usage(self): 207 | """Displays module usage""" 208 | 209 | pass 210 | 211 | @abstractmethod 212 | def get_description(self): 213 | """Returns module description 214 | 215 | :returns: Modules description 216 | """ 217 | 218 | if 'Description' in self.info: 219 | return self.info['Description'] 220 | else: 221 | return None 222 | 223 | @abstractmethod 224 | def display_info(self): 225 | """Displays basic information about current module""" 226 | 227 | if 'Name' in self.info: 228 | print("::: %s " % self.info['Name']) 229 | 230 | if 'Description' in self.info: 231 | print("\t_ Desc\t\t %s" % self.info['Description']) 232 | 233 | if 'Author' in self.info: 234 | print("\t_ Author\t %s" % self.info['Author']) 235 | 236 | if 'Version' in self.info: 237 | print("\t_ Version\t %s" % self.info['Version']) 238 | 239 | if 'URL' in self.info: 240 | print("\t_ URL:\t\t %s" % self.info['URL']) 241 | 242 | 243 | class AppVulnDB(): 244 | 245 | """Abstract class for inner structure of modules implementing 246 | AppVulnDB functionalities. 247 | 248 | 249 | Defines abstract methodes which _have_ to be implemented by the 250 | modules subclassing/inheriting this class. 251 | """ 252 | 253 | @abstractmethod 254 | def init(self): 255 | """ Creates and inits DB with specified schema """ 256 | pass 257 | 258 | @abstractmethod 259 | def connect(self): 260 | """Connects to DB""" 261 | pass 262 | 263 | @abstractmethod 264 | def import_scan(self): 265 | """Imports scan results from AppVulnXML file""" 266 | pass 267 | 268 | @abstractmethod 269 | def commit(self): 270 | """Commits transactions to DB""" 271 | pass 272 | 273 | @abstractmethod 274 | def close(self): 275 | """Close connection to DB""" 276 | -------------------------------------------------------------------------------- /src/core/parser/AppVulnXMLParser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2014-02-09 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-06 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | 34 | import base64 35 | 36 | from lxml import etree 37 | from core.parser.HTTPParser import HTTPParser 38 | from core.parser.HTTPParser import HTTPRequestParser 39 | from core.parser.HTTPParser import HTTPResponseParser 40 | 41 | 42 | class AppVulnXMLParser(): 43 | 44 | """AppVulnXML parser. Edits XML data""" 45 | 46 | def __init__(self, xml_data): 47 | # Create parser to parse the XML tree and insert new data into it 48 | self.parser = etree.XMLParser(remove_blank_text=True, strip_cdata=False, 49 | ns_clean=True, recover=True, encoding='utf-8') 50 | self.xml_tree = etree.XML(str(xml_data), self.parser) 51 | self.issues = self.xml_tree.xpath("/XmlReport/Results/Vulnerabilities/*") 52 | self.issue_index = 0 53 | 54 | def __len__(self): 55 | """Returns number of available issues 56 | 57 | :returns: Number of available issues 58 | """ 59 | return len(self.issues) 60 | 61 | def __iter__(self): 62 | """Iterator to walk through issues 63 | 64 | :returns: Iterator to iterate through issues 65 | """ 66 | return self 67 | 68 | def __next__(self): 69 | """Walk through issues""" 70 | issue = self.issues[self.issue_index] 71 | if (self.issue_index + 1) < len(self.issues): 72 | self.issue_index += 1 73 | else: 74 | raise StopIteration 75 | 76 | return issue 77 | 78 | def get_root(self): 79 | """Get root of XML document 80 | 81 | :returns: Root XML Element 82 | """ 83 | return self.xml_tree 84 | 85 | def get_xml(self): 86 | """Returns XML tree as string 87 | 88 | :returns: XML tree as string 89 | """ 90 | return etree.tostring(self.xml_tree, pretty_print=True, encoding="utf-8").decode("utf-8") 91 | 92 | def get_scanner(self): 93 | """Returns /XmlReport/Scanner 94 | 95 | :returns: /XmlReport/Scanner as XML document 96 | """ 97 | return self.xml_tree.xpath("/XmlReport/Scanner") 98 | 99 | def get_summary(self): 100 | """Returns /XmlReport/Summary 101 | 102 | :returns: /XmlReport/Summary as XML document 103 | """ 104 | return self.xml_tree.xpath("/XmlReport/Summary") 105 | 106 | def get_vulnerabilities(self): 107 | """Return /XmlReport/Results/Vulnerabilities 108 | 109 | :returns: /XmlReport/Results/Vulnerabilities as XML document 110 | """ 111 | return self.xml_tree.xpath("/XmlReport/Results/Vulnerabilities/*") 112 | 113 | def add_request_data(self, issue, request_data): 114 | """Add parsed request data to the node 115 | 116 | :param issue: Issue as XML document 117 | :param request_data: HTTP request data 118 | """ 119 | request = HTTPRequestParser(request_data) 120 | request.parse_data() 121 | request.set_http_headers() 122 | headers = request.get_headers() 123 | 124 | # Add request attributes method like method 125 | try: 126 | xml_request_node = issue.xpath("TestProbe/HTTP/Request")[0] 127 | xml_request_node.attrib['method'] = request.get_method() 128 | xml_request_node.attrib['version'] = request.get_request_version() 129 | except IndexError: 130 | log.error("Index error") 131 | 132 | # Add parsed data 133 | try: 134 | xml_parsed_traffic = issue.xpath("TestProbe/HTTP/Request/Parsed")[0] 135 | except IndexError: 136 | Log.error("Index error") 137 | 138 | # Iterate through headers and create new XML nodes 139 | for h in headers.keys(): 140 | for v in headers[h]: 141 | # Create new sub-element 142 | header_node = etree.Element('Header', name=h, value=v) 143 | xml_parsed_traffic.append(header_node) 144 | 145 | # Add request data node 146 | request_data_node = etree.Element('Data') 147 | request_data_node.text = etree.CDATA(request.get_request_data()) 148 | xml_parsed_traffic.append(request_data_node) 149 | 150 | def add_response_data(self, issue, response_data, binary_data=False): 151 | """Add parsed response data to the node 152 | 153 | :param issue: Issue as XML document 154 | :param response_data: HTTP response data 155 | :param binary_data: Flag indicating whether responde_data is binary 156 | """ 157 | response = HTTPResponseParser(response_data, binary_data) 158 | response.parse_data() 159 | response.set_http_headers() 160 | headers = response.get_headers() 161 | 162 | # Add response metadata 163 | try: 164 | xml_response_node = issue.xpath("TestProbe/HTTP/Response")[0] 165 | xml_response_node.attrib['version'] = response.get_response_version() 166 | xml_response_node.attrib['status'] = response.get_status() 167 | xml_response_node.attrib['reason'] = response.get_reason() 168 | except IndexError: 169 | log.error("Index error") 170 | 171 | # Add response data 172 | try: 173 | xml_parsed_traffic = issue.xpath("TestProbe/HTTP/Response/Parsed")[0] 174 | except IndexError: 175 | Log.error("Index error") 176 | 177 | # Iterate through headers and create new XML nodes 178 | for h in headers.keys(): 179 | for v in headers[h]: 180 | # Create new sub-element 181 | header_node = etree.Element('Header', name=h, value=v) 182 | xml_parsed_traffic.append(header_node) 183 | 184 | # Add request data node 185 | request_data_node = etree.Element('Data') 186 | request_data_node.text = etree.CDATA(response.get_response_data()) 187 | request_data_node.attrib['base64'] = str(binary_data) 188 | xml_parsed_traffic.append(request_data_node) 189 | 190 | def extract_traffic(self, issue, binary_data=False): 191 | """Extract HTTP traffic from RawTraffic/MergedTraffic and adjust XML in single issue 192 | 193 | :param issue: Issue as XML document 194 | :param binary_data: Flag indicating whether traffic is binary 195 | """ 196 | raw_traffic = issue.xpath("RawTraffic")[0] 197 | raw_request_traffic = issue.xpath("RawTraffic/RequestTraffic") 198 | raw_response_traffic = issue.xpath("RawTraffic/ResponseTraffic") 199 | raw_merged_traffic = issue.xpath("RawTraffic/MergedTraffic") 200 | 201 | # New nodes 202 | request_node = etree.Element("RequestTraffic") 203 | response_node = etree.Element("ResponseTraffic") 204 | request_node.text = '' 205 | response_node.text = '' 206 | 207 | # Add base64 flag to traffic 208 | request_node.attrib['base64'] = 'false' 209 | response_node.attrib['base64'] = 'false' 210 | 211 | # Check if merged traffic is provided 212 | if len(raw_merged_traffic) > 0: 213 | # Split traffic 214 | http_data = HTTPParser.split_http_traffic(raw_merged_traffic[0].text) 215 | 216 | # Adjust XML data 217 | if http_data: 218 | request_node.text = etree.CDATA(http_data['request']) 219 | raw_traffic.append(request_node) 220 | 221 | response_node.text = etree.CDATA(http_data['response']) 222 | raw_traffic.append(response_node) 223 | 224 | # Remove MergedTraffic node 225 | raw_merged_traffic[0].getparent().remove(raw_merged_traffic[0]) 226 | 227 | # Check if request traffic already provided 228 | # TODO: Do the same for request traffic? 229 | if len(raw_request_traffic) > 0: 230 | 231 | if len(raw_request_traffic[0].text) > 0: 232 | base64_flag = False 233 | if 'base64' in raw_request_traffic[0].attrib: 234 | if raw_request_traffic[0].attrib['base64'] == 'true': 235 | base64_flag = True 236 | 237 | # Check if base64 238 | if base64_flag: 239 | # Replace binary data by plaintext data 240 | decoded_request_data = base64.b64decode(raw_request_traffic[0].text).decode("utf-8") 241 | 242 | raw_request_traffic[0].getparent().remove(raw_request_traffic[0]) 243 | new_request_traffic = etree.Element("RequestTraffic") 244 | new_request_traffic.text = etree.CDATA(decoded_request_data) 245 | new_request_traffic.attrib['base64'] = "false" 246 | 247 | # Append new node 248 | raw_traffic.append(new_request_traffic) 249 | 250 | else: 251 | # Add new nodes 252 | raw_traffic.append(request_node) 253 | raw_traffic.append(response_node) 254 | 255 | def add_data(self, binary_data=False): 256 | """Adds request data (e.g. headers) to the XML tree 257 | 258 | :param binary_data: Flag indicating whether data is binary 259 | """ 260 | for issue in self.issues: 261 | # Extract traffic 262 | self.extract_traffic(issue, binary_data) 263 | 264 | # Extract request and response 265 | raw_request_traffic = issue.xpath("RawTraffic/RequestTraffic")[0] 266 | raw_response_traffic = issue.xpath("RawTraffic/ResponseTraffic")[0] 267 | 268 | # Add request data 269 | if raw_request_traffic.text: 270 | base64_flag = False 271 | if 'base64' in raw_request_traffic.attrib: 272 | if raw_request_traffic.attrib['base64'] == 'true': 273 | base64_flag = True 274 | 275 | # Check if base64 276 | if base64_flag: 277 | decoded_request_traffic = base64.b64decode(raw_request_traffic.text) 278 | self.add_request_data(issue, decoded_request_traffic.decode(encoding="utf-8", errors="ignore")) 279 | else: 280 | self.add_request_data(issue, raw_request_traffic.text) 281 | 282 | # Add response data 283 | if raw_response_traffic.text: 284 | base64_flag = False 285 | if 'base64' in raw_response_traffic.attrib: 286 | if raw_response_traffic.attrib['base64'] == 'true': 287 | base64_flag = True 288 | 289 | # Check if base64 290 | if base64_flag: 291 | decoded_response_traffic = base64.b64decode(raw_response_traffic.text) 292 | self.add_response_data( 293 | issue, decoded_response_traffic.decode(encoding="utf-8", errors="ignore"), True) 294 | else: 295 | self.add_response_data(issue, raw_response_traffic.text) 296 | 297 | def get_payload(self, issue): 298 | """Gets issue payload information, e.g. parameter/cookie and value 299 | 300 | :param issue: Issue as XML document 301 | :returns: XML data containing PoC information 302 | """ 303 | raw_query = issue.xpath("TestProbe/Request/Query") 304 | if len(raw_query) > 0: 305 | return raw_query 306 | else: 307 | return None 308 | 309 | def convert_base64_to_plain(self): 310 | """Converts Base64 traffic to plaintext 311 | 312 | For all issue the traffic will be converted to base64. 313 | """ 314 | for issue in self.issues: 315 | raw_traffic = issue.xpath("RawTraffic") 316 | request_traffic = issue.xpath("RawData/RawRequest") 317 | response_traffic = issue.xpath("RawData/RawResponse") 318 | 319 | # Decode request traffic 320 | if len(request_traffic) > 0: 321 | base64_traffic = request_traffic[0].text 322 | traffic = base64.b64decode(base64_traffic) 323 | request_traffic[0].text = etree.CDATA(traffic.decode('utf-8')) 324 | 325 | # Decode response traffic 326 | if len(response_traffic) > 0: 327 | base64_traffic = response_traffic[0].text 328 | traffic = base64.b64decode(base64_traffic) 329 | 330 | # FIXME: Do this better 331 | if len(traffic) < 10000: 332 | response = str(traffic) 333 | else: 334 | response = base64_traffic 335 | 336 | # print(response) 337 | response_traffic[0].text = etree.CDATA(response) 338 | 339 | # Merge traffic data 340 | raw_traffic[0].text = ''.join([request_traffic[0].text, str(response_traffic[0].text)]) 341 | 342 | # Remove RawData 343 | raw_data = issue.xpath("RawData") 344 | issue.remove(raw_data[0]) 345 | 346 | def string(self): 347 | """Returns string respresentation of XML tree 348 | 349 | :returns: Returns string respresentation of XML tree 350 | """ 351 | return etree.tostring(self.xml_tree, 352 | pretty_print=True, 353 | xml_declaration=False 354 | ).decode(encoding="utf-8") 355 | 356 | def __str__(self): 357 | return self.string() 358 | -------------------------------------------------------------------------------- /src/core/parser/ArgsParser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2014-02-02 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-06 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | 34 | import argparse 35 | 36 | import core.framework as framework 37 | import core.conf.appvulnms as conf 38 | 39 | 40 | class ArgsParser(): 41 | 42 | """Implements basic command line parsing""" 43 | 44 | def __init__(self, params): 45 | """ Initialize parser """ 46 | # Add common options 47 | self.base_parser = argparse.ArgumentParser(add_help=False) 48 | self.base_parser.add_argument("-q", "--quiet", dest="quiet", action="store_true", help="Keep it quiet") 49 | self.base_parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Add verbosity") 50 | 51 | # Add AppVulnMS parser where several modes are possible 52 | self.parser = argparse.ArgumentParser() 53 | self.subparsers = self.parser.add_subparsers(dest="action", help="Choose mode") 54 | 55 | # Module mode (m) 56 | self.m_parser = self.subparsers.add_parser( 57 | "m", parents=[self.base_parser], 58 | usage=conf.appvulnms['module_mode_usage'], 59 | help="Interact with available modules", 60 | description="::: Interact with the available modules." 61 | ) 62 | self.m_parser.add_argument("-l", "--list-modules", dest="list_modules", 63 | action="store_true", help="List available modules") 64 | self.m_parser.add_argument("module_name", action="store", nargs="?", help="Modules name") 65 | self.m_parser.add_argument('module_params', action="store", 66 | nargs=argparse.REMAINDER, help="Modules parameters") 67 | 68 | def parse(self, params): 69 | """Parse parameters and return options/args 70 | 71 | :param params: ArgParse parameters 72 | :returns: Arguments as dictionary 73 | """ 74 | args = self.parser.parse_args(params) 75 | return args 76 | 77 | def get_description(self): 78 | """Gets tool description""" 79 | print(conf.appvulnms['banner']) 80 | return "sample description" 81 | 82 | def print_usage(self): 83 | """Print usage message""" 84 | usage = "AppVulnMS.py [options]" 85 | return usage 86 | 87 | def print_help(self): 88 | """Print help message""" 89 | self.parser.print_help() 90 | 91 | def run_actions(self, opts): 92 | """Run actions specified by the parameters 93 | 94 | :param opts: Dictionary containing arguments 95 | """ 96 | 97 | modules_path = conf.modules['path'] 98 | modules_collection = framework.ModuleCollection(modules_path) 99 | modules_collection.load_modules() 100 | 101 | # Show all available modules 102 | if opts.list_modules: 103 | modules_collection.show_modules() 104 | return 105 | 106 | # Show available module options 107 | if opts.module_name: 108 | m = modules_collection.load_module(opts.module_name) 109 | # m.parse_params(self.module_parser) 110 | 111 | # Print modules info if no parameters 112 | if not opts.module_params: 113 | m.display_info() 114 | else: 115 | m.parse_params(opts.module_params) 116 | m.module_run() 117 | return 118 | -------------------------------------------------------------------------------- /src/core/parser/HTTPParser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2014-02-09 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-06 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | 34 | import io 35 | import email 36 | import re 37 | import base64 38 | 39 | from lxml import etree 40 | from http.server import BaseHTTPRequestHandler 41 | from core.framework import Log 42 | from collections import defaultdict 43 | 44 | 45 | class HTTPParser(BaseHTTPRequestHandler): 46 | 47 | """ Simple HTTP Request Handler parser """ 48 | 49 | def __init__(self, http_traffic): 50 | # Parse HTTP traffic 51 | self.rfile = io.BytesIO(http_traffic.encode('utf-8')) 52 | self.raw_requestline = self.rfile.readline() 53 | self.error_code = self.error_message = None 54 | 55 | def parse_data(self): 56 | """ Parse HTTP request and set internal variables. 57 | This code is taken from Python3 standard library 58 | http.server 59 | I have changed the code to call my own parse_headers function. 60 | """ 61 | self.command = None # set in case of error on the first line 62 | self.request_version = version = self.default_request_version 63 | self.close_connection = 1 64 | requestline = str(self.raw_requestline, 'utf-8') 65 | requestline = requestline.rstrip('\r\n') 66 | self.requestline = requestline 67 | words = requestline.split() 68 | if len(words) == 3: 69 | command, path, version = words 70 | if version[:5] != 'HTTP/': 71 | self.send_error(400, "Bad request version (%r)" % version) 72 | return False 73 | try: 74 | base_version_number = version.split('/', 1)[1] 75 | version_number = base_version_number.split(".") 76 | # RFC 2145 section 3.1 says there can be only one "." and 77 | # - major and minor numbers MUST be treated as 78 | # separate integers; 79 | # - HTTP/2.4 is a lower version than HTTP/2.13, which in 80 | # turn is lower than HTTP/12.3; 81 | # - Leading zeros MUST be ignored by recipients. 82 | if len(version_number) != 2: 83 | raise ValueError 84 | version_number = int(version_number[0]), int(version_number[1]) 85 | except (ValueError, IndexError): 86 | self.send_error(400, "Bad request version (%r)" % version) 87 | return False 88 | if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1": 89 | self.close_connection = 0 90 | if version_number >= (2, 0): 91 | self.send_error(505, 92 | "Invalid HTTP Version (%s)" % base_version_number) 93 | return False 94 | elif len(words) == 2: 95 | command, path = words 96 | self.close_connection = 1 97 | if command != 'GET': 98 | self.send_error(400, 99 | "Bad HTTP/0.9 request type (%r)" % command) 100 | return False 101 | elif not words: 102 | return False 103 | else: 104 | self.send_error(400, "Bad request syntax (%r)" % requestline) 105 | return False 106 | self.command, self.path, self.request_version = command, path, version 107 | 108 | # Examine the headers and look for a Connection directive. 109 | try: 110 | self.headers = self.parse_headers(self.rfile) 111 | except http.client.LineTooLong: 112 | self.send_error(400, "Line too long") 113 | return False 114 | 115 | conntype = self.headers.get('Connection', "") 116 | if conntype.lower() == 'close': 117 | self.close_connection = 1 118 | elif (conntype.lower() == 'keep-alive' and 119 | self.protocol_version >= "HTTP/1.1"): 120 | self.close_connection = 0 121 | # Examine the headers and look for an Expect directive 122 | expect = self.headers.get('Expect', "") 123 | if (expect.lower() == "100-continue" and 124 | self.protocol_version >= "HTTP/1.1" and 125 | self.request_version >= "HTTP/1.1"): 126 | if not self.handle_expect_100(): 127 | return False 128 | return True 129 | 130 | def parse_headers(self, fp): 131 | """ Parse HTTP headers and return appropriate object to represent them. 132 | 133 | This code was taken from Python3 standard library 134 | http.client 135 | 136 | I have adapted it to ignore the first line of the request. 137 | """ 138 | headers = [] 139 | while True: 140 | line = fp.readline() 141 | headers.append(line) 142 | if line in (b'\r\n', b'\n', b''): 143 | break 144 | 145 | # Ignore first line of request 146 | hstring = b''.join(headers[1:]).decode('utf-8') 147 | return email.parser.Parser().parsestr(hstring) 148 | 149 | def set_http_headers(self): 150 | """ Set HTTP headers after parsing the request""" 151 | self.http_headers = defaultdict(list) 152 | 153 | if 'headers' in self.__dict__: 154 | # Iterate headers 155 | for k in self.headers.keys(): 156 | if k == 'Cookie': 157 | values = [] 158 | cookies = {} 159 | values = self.headers.get_all(k) 160 | 161 | # Iterate and extract cookies 162 | for v in values: 163 | trimmed = v.replace(" ", "") 164 | pairs = trimmed.split(';') 165 | 166 | for p in pairs: 167 | self.http_headers[k].append(p) 168 | 169 | elif k == 'Set-Cookie': 170 | values = [] 171 | cookies = [] 172 | values = self.headers.get_all(k) 173 | 174 | # Iterate and extract cookies 175 | for v in values: 176 | trimmed = v.replace(" ", "") 177 | self.http_headers[k].append(trimmed) 178 | 179 | else: 180 | values = self.headers.get_all(k) 181 | if len(values) > 1: 182 | self.http_headers[k].append(values) 183 | else: 184 | self.http_headers[k].append(values[0]) 185 | 186 | def send_error(self, code, message): 187 | self.error_code = code 188 | self.error_message = message 189 | 190 | def get_error_code(self): 191 | """ Get request error code """ 192 | if 'error_code' in self.__dict__: 193 | return self.error_code 194 | else: 195 | return None 196 | 197 | def get_method(self): 198 | """ Return method (GET, POST etc.) """ 199 | if 'command' in self.__dict__: 200 | return self.command 201 | else: 202 | return None 203 | 204 | def get_request_version(self): 205 | """ Return used HTTP version """ 206 | if 'request_version' in self.__dict__: 207 | return self.request_version 208 | else: 209 | return None 210 | 211 | def get_url(self): 212 | """ Get requests URL """ 213 | if 'path' in self.__dict__: 214 | return self.path 215 | else: 216 | return None 217 | 218 | def get_data(self): 219 | if 'data' in self.__dict__: 220 | return self.data 221 | else: 222 | return None 223 | 224 | def get_response_data(self): 225 | if 'http_response' in self.__dict__: 226 | return self.http_response 227 | else: 228 | return None 229 | 230 | def get_request(self): 231 | return "%s %s %s" % (self.get_method(), self.get_url(), self.get_request_version()) 232 | 233 | def get_headers(self): 234 | """ Get HTTP headers """ 235 | return self.http_headers 236 | 237 | @staticmethod 238 | def split_http_traffic(http_traffic): 239 | """ Splits HTTP traffic by request and response traffic data 240 | 241 | Returns dictionary: data['request'] ,data['response'] 242 | """ 243 | try: 244 | fp = io.BytesIO(http_traffic.encode('utf-8')) 245 | except: 246 | Log.warn("No HTTP traffic data found") 247 | return 248 | 249 | http_data = dict() 250 | request_data = [] 251 | response_data = [] 252 | 253 | # Fead raw HTTP request traffic 254 | while True: 255 | line = fp.readline() 256 | request_data.append(line) 257 | if line in (b'\r\n', b'\n', b''): 258 | break 259 | 260 | # Read data (e.g. POST data) 261 | while True: 262 | line = fp.readline() 263 | # print(line) 264 | # request_data.append(line) 265 | if line in (b'\r\n', b'\n', b''): 266 | break 267 | else: 268 | request_data.append(line) 269 | 270 | # Read raw HTTP response traffic 271 | while True: 272 | line = fp.readline() 273 | # print(line) 274 | response_data.append(line) 275 | if line in (b''): 276 | break 277 | 278 | # Return data 279 | http_data['request'] = b''.join(request_data).decode('utf-8') 280 | http_data['response'] = b''.join(response_data).decode('utf-8') 281 | return http_data 282 | 283 | def __str__(self): 284 | output = "%s\t %s\t %s" % (self.get_method(), self.get_url(), self.get_data()) 285 | output = self.get_request() 286 | return output 287 | 288 | 289 | class HTTPRequestParser(HTTPParser): 290 | 291 | """ HTTP Request implementation of HTTPParser """ 292 | 293 | def __init__(self, request_text): 294 | super().__init__(request_text) 295 | 296 | def parse_headers(self, fp): 297 | """ Override parent method in order to parse the request 298 | data as well. 299 | """ 300 | headers = [] 301 | while True: 302 | line = fp.readline() 303 | headers.append(line) 304 | if line in (b'\r\n', b'\n', b''): 305 | break 306 | 307 | # Parse request data (e.g, POST dataa) 308 | self.request_data = fp.readline().decode('utf-8') 309 | 310 | # Ignore first line of request 311 | hstring = b''.join(headers[1:]).decode('utf-8') 312 | return email.parser.Parser().parsestr(hstring) 313 | 314 | def get_request_data(self): 315 | """ Return request data (e.g. POST data) """ 316 | return self.request_data 317 | 318 | 319 | class HTTPResponseParser(HTTPParser): 320 | 321 | """ HTTP Response implementation of HTTPParser """ 322 | 323 | def __init__(self, response_text, binary_data=False): 324 | super().__init__(response_text) 325 | self.binary_data = binary_data 326 | 327 | def parse_data(self): 328 | """ Override parent method to parse only headers and data. 329 | No additional logic is required here. 330 | """ 331 | self.statusline = str(self.raw_requestline.decode('utf-8')) 332 | words = self.statusline.split() 333 | 334 | # Parse first line of response 335 | if len(words) >= 3: 336 | self.version = words[0] 337 | self.statuscode = words[1] 338 | self.reason = ' '.join(words[2:]) 339 | 340 | # Parse headers 341 | try: 342 | self.headers = self.parse_headers(self.rfile) 343 | except http.client.LineTooLong: 344 | self.send_error(400, "Line too long") 345 | return False 346 | return True 347 | 348 | def parse_headers(self, fp): 349 | """ Override parent method """ 350 | headers = [] 351 | while True: 352 | line = fp.readline() 353 | headers.append(line) 354 | if line in (b'\r\n', b'\n', b''): 355 | break 356 | 357 | # Get response data 358 | self.body_data = [] 359 | while True: 360 | line = fp.readline() 361 | if line in (b'\r\n', b'\n'): 362 | continue 363 | elif line in (b''): 364 | break 365 | else: 366 | self.body_data.append(line) 367 | 368 | # Set response data 369 | if self.binary_data: 370 | # Bae64-Encode response data 371 | response_data = b''.join(self.body_data) 372 | encoded_response_data = base64.b64encode(response_data) 373 | self.response_data = encoded_response_data 374 | else: 375 | self.response_data = b''.join(self.body_data).decode('utf-8') 376 | 377 | # Merge headers 378 | hstring = b''.join(headers).decode('utf-8') 379 | return email.parser.Parser().parsestr(hstring) 380 | 381 | def get_status(self): 382 | if 'statuscode' in self.__dict__: 383 | return self.statuscode 384 | else: 385 | return '' 386 | 387 | def get_reason(self): 388 | if 'reason' in self.__dict__: 389 | return self.reason 390 | else: 391 | return '' 392 | 393 | def get_response_version(self): 394 | if 'version' in self.__dict__: 395 | return self.version 396 | else: 397 | return '' 398 | 399 | def get_response_data(self): 400 | return self.response_data 401 | -------------------------------------------------------------------------------- /src/core/parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dorneanu/appvulnms/306be6f123de95685a8e27b368534de8b27e7b38/src/core/parser/__init__.py -------------------------------------------------------------------------------- /src/core/util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2014-05-27 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-04 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | 34 | from lxml import etree 35 | 36 | 37 | class XMLTools(): 38 | 39 | """Provides simple XML utilities""" 40 | 41 | 42 | @staticmethod 43 | def write_xml_to_file(xml, filename): 44 | """Writes xml to filename 45 | 46 | :param filename: File to write XML to. 47 | """ 48 | try: 49 | xml_file = open(filename, "w") 50 | xml_file.seek(xml_file.tell()) 51 | xml_file.write(xml) 52 | xml_file.close() 53 | return True 54 | except: 55 | Log.error("Couldn't write to XML to file") 56 | return False 57 | 58 | @staticmethod 59 | def transform_xml(xml_file, xslt_file): 60 | """Transform XML using XSLT 61 | 62 | :param xml_file: XML file to convert 63 | :param xslt_file: XSLT conversion file 64 | """ 65 | 66 | input_xml = xml_file.read().encode('utf-8') 67 | xml_tree = etree.XML(input_xml) 68 | 69 | input_xslt = xslt_file.read().encode('utf-8') 70 | xslt_root = etree.XML(input_xslt) 71 | 72 | # Convert XML using XSLT 73 | transform = etree.XSLT(xslt_root) 74 | converted_xml = transform(xml_tree) 75 | 76 | return converted_xml 77 | -------------------------------------------------------------------------------- /src/modules/converter/xml/acunetix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2014-01-10 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-06 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | 34 | import argparse 35 | from lxml import etree 36 | 37 | import core.framework as framework 38 | from core.parser.AppVulnXMLParser import AppVulnXMLParser 39 | from core.util import XMLTools 40 | 41 | 42 | class Module(framework.BaseModule): 43 | """ 44 | Converts BurpSuite Scanner data into AppVulnXML format 45 | """ 46 | 47 | def __init__(self, params): 48 | framework.BaseModule.__init__(self, params) 49 | self.info = { 50 | 'Name': 'converter/xml/acunetix', 51 | 'Author': 'Cyneox / nullsecurity.net', 52 | 'Description': 'Converts Acunetix into WAVXML format', 53 | 'Version': 'v0.1', 54 | 'URL': 'http://www.acunetix.com' 55 | } 56 | 57 | # Add module parameters 58 | # TODO: Add defaults 59 | self.parser = argparse.ArgumentParser( 60 | usage = self.get_usage(), 61 | description = self.get_description()) 62 | 63 | self.parser.add_argument('-i','--input', 64 | help='Input file', type=argparse.FileType('r'), 65 | dest='input_file') 66 | 67 | self.parser.add_argument('-o','--output', 68 | help='Output file', 69 | dest='output_file') 70 | 71 | self.parser.add_argument('-x', '--xslt', 72 | help='XSLT file', type=argparse.FileType('r'), 73 | dest='xslt_file') 74 | 75 | 76 | def post_actions(self, xml): 77 | """ Perform post actions after XSLT transformation """ 78 | # Add HTTP data to issues 79 | issuesParser = AppVulnXMLParser(xml) 80 | issuesParser.add_data() 81 | 82 | return issuesParser.string() 83 | 84 | 85 | def module_run(self): 86 | try: 87 | # Convert XML file 88 | converted_xml = XMLTools.transform_xml(self.args.input_file, self.args.xslt_file) 89 | 90 | # Post actions 91 | xml_out = self.post_actions(converted_xml) 92 | 93 | # Write to file 94 | XMLTools.write_xml_to_file(xml_out, self.args.output_file) 95 | 96 | except Exception: 97 | Log.error("Error loading module: %s" % traceback.format_exc()) 98 | return 99 | # EOF 100 | -------------------------------------------------------------------------------- /src/modules/converter/xml/burpsuite-scanner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2014-01-10 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-06 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | 34 | import argparse 35 | import re 36 | from lxml import etree 37 | 38 | import core.framework as framework 39 | from core.parser.AppVulnXMLParser import AppVulnXMLParser 40 | from core.util import XMLTools 41 | 42 | 43 | 44 | class Module(framework.BaseModule): 45 | """ 46 | Converts BurpSuite Scanner data into AppVulnXML format 47 | """ 48 | 49 | def __init__(self, params): 50 | framework.BaseModule.__init__(self, params) 51 | self.info = { 52 | 'Name': 'converter/xml/burpsuite-scanner', 53 | 'Author': 'Cyneox / nullsecurity.net', 54 | 'Description': 'Converts BurpSuite scanner results into WAVXML', 55 | 'Version': 'v0.1', 56 | 'URL': 'http://portswigger.net/burp/' 57 | } 58 | 59 | # Add module parameters 60 | # TODO: Add defaults 61 | self.parser = argparse.ArgumentParser( 62 | usage = self.get_usage(), 63 | description = self.get_description()) 64 | 65 | self.parser.add_argument('-i','--input', 66 | help='Input file', type=argparse.FileType('r'), 67 | dest='input_file') 68 | 69 | self.parser.add_argument('-o','--output', 70 | help='Output file', 71 | dest='output_file') 72 | 73 | self.parser.add_argument('-x', '--xslt', 74 | help='XSLT file', type=argparse.FileType('r'), 75 | dest='xslt_file') 76 | 77 | 78 | def set_payload(self, xml): 79 | """ Set payload data for every issue """ 80 | issuesParser = AppVulnXMLParser(xml) 81 | 82 | # Adjust payload information 83 | for i in issuesParser: 84 | payload = i.xpath("TestProbe/HTTP/Request/Payload")[0] 85 | match = re.search(r"\[(.*)parameter(.*)\]", payload.xpath("Raw")[0].text) 86 | 87 | # Any matches? 88 | if match: 89 | payload_input = payload.xpath("Input")[0] 90 | payload_input.attrib['type'] = 'parameter' 91 | payload_input.attrib['name'] = match.group(1).strip() 92 | 93 | return issuesParser.string() 94 | 95 | def post_actions(self, xml): 96 | """ Perform post actions after XSLT transformation """ 97 | # Add HTTP data to issues 98 | issuesParser = AppVulnXMLParser(xml) 99 | issuesParser.add_data(True) 100 | 101 | # Set payload data 102 | new_xml = self.set_payload(issuesParser.string()) 103 | return new_xml 104 | 105 | 106 | def module_run(self): 107 | try: 108 | # Convert XML file 109 | converted_xml = XMLTools.transform_xml(self.args.input_file, self.args.xslt_file) 110 | 111 | # Post actions 112 | xml_out = self.post_actions(converted_xml) 113 | 114 | # Write to file 115 | XMLTools.write_xml_to_file(xml_out, self.args.output_file) 116 | 117 | except Exception: 118 | Log.error("Error loading module: %s" % traceback.format_exc()) 119 | return 120 | -------------------------------------------------------------------------------- /src/modules/converter/xml/ibm-appscan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2014-02-09 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-06 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | 34 | import argparse 35 | import re 36 | from lxml import etree 37 | 38 | import core.framework as framework 39 | from core.parser.AppVulnXMLParser import AppVulnXMLParser 40 | from core.util import XMLTools 41 | 42 | 43 | class Module(framework.BaseModule): 44 | 45 | """ 46 | Converts IBM AppScan Rational scanning data into AppVulnXML format 47 | """ 48 | 49 | def __init__(self, params): 50 | framework.BaseModule.__init__(self, params) 51 | self.info = { 52 | 'Name': 'converter/xml/ibm-appscan', 53 | 'Author': 'Cyneox / nullsecurity.net', 54 | 'Description': 'Converts IBM AppScan results into suitable AppVulnXML format using XSLT', 55 | 'Version': 'v0.1', 56 | 'URL': 'http://www-03.ibm.com/software/products/us/en/appscan/' 57 | } 58 | 59 | # Add module parameters 60 | # TODO: Add defaults 61 | self.parser = argparse.ArgumentParser( 62 | usage=self.get_usage(), 63 | description=self.get_description()) 64 | 65 | self.parser.add_argument('-i', '--input', 66 | help='Input file', type=argparse.FileType('r'), 67 | dest='input_file') 68 | 69 | self.parser.add_argument('-o', '--output', 70 | help='Output file', 71 | dest='output_file') 72 | 73 | self.parser.add_argument('-x', '--xslt', 74 | help='XSLT file', type=argparse.FileType('r'), 75 | dest='xslt_file') 76 | 77 | def set_payload(self, xml): 78 | """ Set payload data for every issue """ 79 | issuesParser = AppVulnXMLParser(xml) 80 | 81 | # Adjust payload information 82 | for i in issuesParser: 83 | payload = i.xpath("TestProbe/HTTP/Request/Payload")[0] 84 | payload_input = payload.xpath("Input")[0] 85 | payload_raw = payload.xpath("Raw")[0] 86 | 87 | if payload_raw.text: 88 | payloads = re.split(r",", payload_raw.text) 89 | 90 | # Iterate through all available payloads 91 | for p in payloads: 92 | match = re.search(r"(%s):(.*) -> (.*)=(.*)" % payload_input.attrib['type'], p) 93 | 94 | # Any matches ? 95 | # If one match found abort search since AppScan is 96 | # providing variants of the same payload 97 | if match: 98 | payload_input.text = etree.CDATA(match.group(4).strip()) 99 | break 100 | 101 | # Return modified XML 102 | return issuesParser.string() 103 | 104 | def post_actions(self, xml): 105 | """ Perform post actions after XSLT transformation """ 106 | # Add HTTP data to issues 107 | issuesParser = AppVulnXMLParser(xml) 108 | issuesParser.add_data() 109 | 110 | # Set payload data 111 | new_xml = self.set_payload(issuesParser.string()) 112 | return new_xml 113 | 114 | def module_run(self): 115 | try: 116 | # Convert XML file 117 | converted_xml = XMLTools.transform_xml(self.args.input_file, self.args.xslt_file) 118 | 119 | # Post actions 120 | xml_out = self.post_actions(converted_xml) 121 | 122 | # Write to file 123 | XMLTools.write_xml_to_file(xml_out, self.args.output_file) 124 | 125 | except Exception: 126 | Log.error("Error loading module: %s" % traceback.format_exc()) 127 | return 128 | # EOF 129 | -------------------------------------------------------------------------------- /src/modules/vms/appvulndb/sqlite.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Author: victor 4 | # @Date: 2014-01-12 5 | # @Last Modified by: victor 6 | # @Last Modified time: 2014-06-05 7 | # @Copyright: 8 | # 9 | # This file is part of the AppVulnMS project. 10 | # 11 | # 12 | # Copyright (c) 2014 Victor Dorneanu 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in all 22 | # copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | # SOFTWARE. 31 | # 32 | # The MIT License (MIT) 33 | 34 | import argparse 35 | import os 36 | import sqlite3 37 | import traceback 38 | from lxml import etree 39 | 40 | import core.framework as framework 41 | from core.framework import Log 42 | from core.parser.AppVulnXMLParser import AppVulnXMLParser 43 | from core.db.AppVulnDB import AppVulnDB 44 | 45 | 46 | class AppVulnDB_SQLite(framework.AppVulnDB): 47 | 48 | """ 49 | SQLite version of AppVulnDB 50 | """ 51 | 52 | def __init__(self, db_file): 53 | # Location for DB schema 54 | self.db_schema = "./data/AppVulnDB/schema.sqlite" 55 | self.db = db_file 56 | 57 | def init(self): 58 | """ Init and creates the DB """ 59 | try: 60 | if not os.path.exists(self.db): 61 | with open(self.db_schema) as f: 62 | schema = f.read() 63 | conn = sqlite3.connect(self.db) 64 | conn.executescript(schema) 65 | conn.close() 66 | return True 67 | else: 68 | Log.error("File exists. Exiting") 69 | return False 70 | except: 71 | Log.error("Error creating DB\n%s" % traceback.format_exc()) 72 | return False 73 | 74 | def connect(self): 75 | """ Connects to DB """ 76 | try: 77 | self.conn = sqlite3.connect(self.db) 78 | except: 79 | Log.error("Couldn't connect to DB") 80 | 81 | def __get_poc_type_id_by_name(self, type_name): 82 | """ Return PoC type id by specified name """ 83 | cursor = self.conn.cursor() 84 | cursor.execute("SELECT id FROM poc_type WHERE name = ?", [type_name]) 85 | poc_type = cursor.fetchone() 86 | if poc_type: 87 | return poc_type[0] 88 | else: 89 | return None 90 | 91 | def __get_poc_table_name_by_id(self, poc_type_id): 92 | """ Return PoC table name by specified PoC type id """ 93 | cursor = self.conn.cursor() 94 | cursor.execute("SELECT poc_table_name FROM poc_type WHERE id = ?", (poc_type_id,)) 95 | poc_table_name = cursor.fetchone() 96 | if poc_table_name: 97 | return poc_table_name[0] 98 | else: 99 | return None 100 | 101 | def __import_scanner(self, scanner): 102 | """ Imports scanner data """ 103 | # Check if scanner already exists 104 | cursor = self.conn.cursor() 105 | cursor.execute("SELECT id FROM scanner WHERE name=? AND version=?", 106 | (scanner['name'], scanner['version'])) 107 | 108 | scanner_ids = cursor.fetchall() 109 | 110 | if len(scanner_ids) == 0: 111 | # Prepare insert query 112 | query = """ 113 | INSERT INTO scanner (name, version) 114 | VALUES (?, ?) 115 | """ 116 | cursor.execute(query, (scanner['name'], scanner['version'])) 117 | return cursor.lastrowid 118 | else: 119 | # Return scanner id 120 | return scanner_ids[0][0] 121 | 122 | def __import_scan_summary(self, summary, scanner_id): 123 | """ Imports summary data """ 124 | cursor = self.conn.cursor() 125 | query = """ 126 | INSERT INTO scan (scope, target, count_total_vulns, scanner_id) 127 | VALUES (?, ?, ?, ?) 128 | """ 129 | cursor.execute(query, (summary['TargetHost'], 130 | summary['TargetHost'], 131 | summary['TotalIssues'], 132 | scanner_id)) 133 | 134 | # Return scan id 135 | return cursor.lastrowid 136 | 137 | def __import_vulns(self, vulns, scan_id, scanner_id): 138 | """ Import vulnerabilities into DB """ 139 | for v in vulns: 140 | # Extract vulnerability details 141 | issue = v['issue'] 142 | poc_data = v['poc_data'] 143 | 144 | # Add extra information 145 | issue['scan_id'] = scan_id 146 | issue['poc_type_id'] = self.__get_poc_type_id_by_name("http") 147 | issue['poc_table_name'] = self.__get_poc_table_name_by_id(issue['poc_type_id']) 148 | 149 | query = """ 150 | INSERT INTO %s 151 | ( 152 | URL, method, request_header, request_cookie, request_data, 153 | response_header, response_cookie, response_data, input_type, input_name, input_payload, 154 | scanner, vulnerability_id 155 | ) 156 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 157 | """ % (issue['poc_table_name']) 158 | 159 | cursor = self.conn.cursor() 160 | cursor.execute(query, 161 | ( 162 | poc_data['URL'], poc_data['method'], poc_data[ 163 | 'req_hdr'], poc_data['req_cookies'], poc_data['req_data'], 164 | poc_data['res_hdr'], poc_data['res_cookies'], poc_data['res_data'], 165 | poc_data['payload_type'], poc_data['payload_name'], poc_data['payload_data'], 166 | scanner_id, 1 167 | ) 168 | ) 169 | poc_id = cursor.lastrowid 170 | 171 | # Prepare insert query for vulnerability data 172 | query = """ 173 | INSERT INTO vulnerability 174 | ( 175 | type, name, severity, description, scan_id, 176 | error_type, poc_type_id, poc_id 177 | ) 178 | VALUES (?, ?, ?, ?, ?, ?, ?, ?) 179 | """ 180 | cursor = self.conn.cursor() 181 | cursor.execute(query, 182 | ( 183 | issue['type'], "FIXME", issue['severity'], issue['description'], issue['scan_id'], 184 | issue['error_type'], issue['poc_type_id'], poc_id 185 | ) 186 | ) 187 | vuln_id = cursor.lastrowid 188 | print(vuln_id) 189 | 190 | def commit(self): 191 | """ Commit transactions """ 192 | self.conn.commit() 193 | 194 | def import_data_from_xml(self, appVulnXML): 195 | """ Imports data from provided AppVulnXML file """ 196 | scanner_id = self.__import_scanner(appVulnXML.get_scanner()) 197 | scan_id = self.__import_scan_summary(appVulnXML.get_scan_summary(), scanner_id) 198 | self.__import_vulns(appVulnXML.get_vulns(), scan_id, scanner_id) 199 | 200 | def import_scan(self, xml_filename): 201 | try: 202 | # Create AppVulnDB handler 203 | appVulnXML = AppVulnDB(xml_filename) 204 | 205 | # Connect to DB 206 | self.connect() 207 | 208 | # Import data 209 | self.import_data_from_xml(appVulnXML) 210 | 211 | # Commit transactions 212 | self.commit() 213 | 214 | # Close connection 215 | self.close() 216 | except: 217 | Log.error("Error importing AppVulnXML\n%s" % traceback.format_exc()) 218 | return False 219 | return True 220 | 221 | def close(self): 222 | """ Close DB """ 223 | self.conn.close() 224 | 225 | 226 | class Module(framework.BaseModule): 227 | 228 | """ 229 | Vulnerability Management System for AppVulnXML issues for SQLite 230 | """ 231 | 232 | def __init__(self, params): 233 | framework.BaseModule.__init__(self, params) 234 | self.info = { 235 | 'Name': 'vms/appvulndb/sqlite', 236 | 'Author': 'Cyneox / nullsecurity.net', 237 | 'Descripbtion': 'Manages application vulnerabilities in SQLite DB ', 238 | 'Version': 'v0.1', 239 | 'URL': 'http://sqlite.org/' 240 | } 241 | # Location for DB schema 242 | self.db_schema_filename = "./data/AppVulnDB/schema.sqlite" 243 | 244 | # Add common options 245 | self.base_parser = argparse.ArgumentParser(add_help=False) 246 | self.base_parser.add_argument("-q", "--quiet", dest="quiet", action="store_true", help="Keep it quiet") 247 | self.base_parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Add verbosity") 248 | 249 | # Add AppVulnDB parser where several modes are possible 250 | self.parser = argparse.ArgumentParser() 251 | self.subparsers = self.parser.add_subparsers(dest="action", help="Choose mode") 252 | 253 | # Add modes 254 | self.mode_init = self.subparsers.add_parser("init", parents=[self.base_parser], help="Create SQLite AppVulnDB") 255 | self.mode_init.add_argument("-f", "--db-file", dest="db_file", action="store", 256 | help="Specify file where to store DB") 257 | 258 | self.mode_import = self.subparsers.add_parser( 259 | "import", parents=[self.base_parser], help="Import vulns in AppVulnXML format into DB") 260 | self.mode_import.add_argument("-f", "--xml-input", dest="appvulnxml_file", 261 | action="store", help="Specify AppVulnXML file") 262 | self.mode_import.add_argument("-d", "--db-file", dest="db_file", action="store", help="AppVulnDB SQLite file") 263 | 264 | def module_run(self): 265 | try: 266 | # Init modus 267 | if self.args.action == "init": 268 | appVulnDB = AppVulnDB_SQLite(self.args.db_file) 269 | 270 | if appVulnDB.init(): 271 | Log.info("Succesfully created DB") 272 | else: 273 | Log.warn("Couldn't create DB") 274 | 275 | # Import modus 276 | elif self.args.action == "import": 277 | appVulnDB = AppVulnDB_SQLite(self.args.db_file) 278 | 279 | if appVulnDB.import_scan(self.args.appvulnxml_file): 280 | Log.info("Successfully imported data into DB") 281 | else: 282 | Log.warn("Couldn't import data") 283 | 284 | # No modus 285 | else: 286 | print("No modus") 287 | 288 | except Exception: 289 | Log.error("Error loading module: %s" % traceback.format_exc()) 290 | return 291 | # EOF 292 | --------------------------------------------------------------------------------