├── Pipfile ├── Pipfile.lock ├── README.md ├── example.finding.json ├── owasp.off.schema.json ├── simpletest.js └── tests ├── badformat.finding.json ├── extradata.finding.json ├── incomplete.finding.json └── test_schema_validation.py /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | jsonschema = "*" 10 | pytest = "*" 11 | jsonref = "*" 12 | 13 | [requires] 14 | python_version = "3.9" 15 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "249b585c123b9a3848ca53818532e057edd2f1a061afe62bc6162738737fb38a" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "attrs": { 20 | "hashes": [ 21 | "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", 22 | "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" 23 | ], 24 | "version": "==19.3.0" 25 | }, 26 | "jsonref": { 27 | "hashes": [ 28 | "sha256:b1e82fa0b62e2c2796a13e5401fe51790b248f6d9bf9d7212a3e31a3501b291f", 29 | "sha256:f3c45b121cf6257eafabdc3a8008763aed1cd7da06dbabc59a9e4d2a5e4e6697" 30 | ], 31 | "index": "pypi", 32 | "version": "==0.2" 33 | }, 34 | "jsonschema": { 35 | "hashes": [ 36 | "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", 37 | "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" 38 | ], 39 | "index": "pypi", 40 | "version": "==3.2.0" 41 | }, 42 | "more-itertools": { 43 | "hashes": [ 44 | "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5", 45 | "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2" 46 | ], 47 | "version": "==8.4.0" 48 | }, 49 | "packaging": { 50 | "hashes": [ 51 | "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", 52 | "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" 53 | ], 54 | "version": "==20.4" 55 | }, 56 | "pluggy": { 57 | "hashes": [ 58 | "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", 59 | "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" 60 | ], 61 | "version": "==0.13.1" 62 | }, 63 | "py": { 64 | "hashes": [ 65 | "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2", 66 | "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342" 67 | ], 68 | "version": "==1.9.0" 69 | }, 70 | "pyparsing": { 71 | "hashes": [ 72 | "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", 73 | "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" 74 | ], 75 | "version": "==2.4.7" 76 | }, 77 | "pyrsistent": { 78 | "hashes": [ 79 | "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3" 80 | ], 81 | "version": "==0.16.0" 82 | }, 83 | "pytest": { 84 | "hashes": [ 85 | "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1", 86 | "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8" 87 | ], 88 | "index": "pypi", 89 | "version": "==5.4.3" 90 | }, 91 | "six": { 92 | "hashes": [ 93 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 94 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 95 | ], 96 | "version": "==1.15.0" 97 | }, 98 | "wcwidth": { 99 | "hashes": [ 100 | "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", 101 | "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" 102 | ], 103 | "version": "==0.2.5" 104 | } 105 | }, 106 | "develop": {} 107 | } 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OFF - OWASP Findings Format 2 | 3 | The OWASP Findings Format is a standardized structure for security items. 4 | 5 | # Why? 6 | 7 | The idea behind OFF is to provide a unified *open independent and trusted* format that tools can export. 8 | 9 | As this standard format is adopted and used, it will facilitate: 10 | 1. Standardized representation of security findings in dashboards and such 11 | 2. Standardized format for mining data out of large sets of findings from different tools 12 | 13 | # How to Use It 14 | 15 | The OFF project initially defines a JSON Schema for findings. Simply produce JSON that meets the validation requirements defined in the schema and offer this as an export option. 16 | 17 | # Setup for Testing 18 | 19 | ## Python 20 | 21 | Set up the environment in python. 22 | 23 | ```sh 24 | pipenv --python 3 25 | git clone https://github.com/owasp/off.git 26 | cd off 27 | pipenv install 28 | pipenv shell 29 | pytest 30 | ``` 31 | 32 | See the `tests/test_schema_validation.py` for an example of how to use the schema in python. 33 | 34 | ## Node.js 35 | 36 | Set up the environment in JavaScript. 37 | 38 | ``` 39 | npm install ajv 40 | node simpletest.js 41 | ``` 42 | 43 | ### Command Line 44 | 45 | We can use AJV to validate from the command line. 46 | ``` 47 | npm install ajv-cli 48 | ajv validate -s owasp.off.schema.json -d example.finding.json 49 | ``` 50 | See: http://epoberezkin.github.io/ajv/#command-line-interface 51 | 52 | # Credits 53 | 54 | The idea for OFF came from a Dallas OWASP Meeting where a participant indicated that the Indianapolis OWASP 55 | Chapter had conceived of this idea and made many exhortations to advance this as a standard. 56 | 57 | # References 58 | 59 | The standard: http://json-schema.org/latest/json-schema-validation.html 60 | 61 | Implementations: 62 | * https://www.npmjs.com/package/json-validation 63 | * https://code.tutsplus.com/tutorials/validating-data-with-json-schema-part-2--cms-25640 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /example.finding.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Remote Code Execution", 3 | "description": "Remote Code Execution", 4 | "detail": "Rails 3.2.12 with globbing routes is vulnerable to directory traversal and remote code execution. Patch or upgrade to 3.2.18 http://brakemanscanner.org/docs/warning_types/remote_code_execution/", 5 | "severity": "medium", 6 | "confidence": "medium", 7 | "priority": "high", 8 | "fingerprint": "2153b4047940d99e694a7e1b7c51b3eff1ca8dee4b796cab17ad1af0763248a2", 9 | "timestamp": "2017-09-14T16:09:39.896Z", 10 | "source": "brakeman", 11 | "location": "config/routes.rb", 12 | "cvss": 3.5, 13 | "cvss3score": 3, 14 | "cvss3vector": "AV:A/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:L/E:U/RL:O/RC:U/CR:L/IR:L/AR:L/MAV:A/MAC:L/MPR:N/MUI:N/MS:U/MC:N/MI:N/MA:N", 15 | "references": [ 16 | "https://www.owasp.org/index.php/Path_Traversal", 17 | "https://www.owasp.org/index.php/Command_Injection" 18 | ], 19 | "cwes": [ 20 | "https://cwe.mitre.org/data/definitions/23.html", 21 | "https://cwe.mitre.org/data/definitions/78.html" 22 | ], 23 | "tags": [ 24 | "java", 25 | "ruby" 26 | ] 27 | } -------------------------------------------------------------------------------- /owasp.off.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "./owasp.off.schema.json", 3 | "title": "Finding", 4 | "description": "OWASP Security Finding", 5 | "type": "object", 6 | "properties": { 7 | "name": { "type": "string", "maxLength": 128 }, 8 | "description": { "type": "string", "maxLength": 256 }, 9 | "detail": { "type": "string", "maxLength": 2048 }, 10 | "severity": { "enum": ["low", "medium", "high"] }, 11 | "confidence": { "enum": ["low", "medium", "high"] }, 12 | "priority": { "enum": ["low", "medium", "high"] }, 13 | "fingerprint": { "type": "string", "maxLength": 256 }, 14 | "timestamp": { "type": "string", "format": "date-time" }, 15 | "location": { "type": "string", "maxLength": 256 }, 16 | "source": { "type": "string", "maxLength": 128 }, 17 | "cvss": { "type": "number", "minimum": 0, "maximum": 10 }, 18 | "cvss3score": { "type": "number", "minimum": 0, "maximum": 10 }, 19 | "cvss3vector": { "type": "string", "maxLength": 128 }, 20 | "cwes": { 21 | "type": "array", 22 | "maxItems": 15, 23 | "items": { 24 | "type": "string", "format": "uri" 25 | } 26 | }, 27 | "references": { 28 | "type": "array", 29 | "maxItems": 15, 30 | "items": { 31 | "type": "string", "format": "uri" 32 | } 33 | }, 34 | "tags": { 35 | "type": "array", 36 | "maxItems": 15, 37 | "items": { 38 | "type": "string" 39 | } 40 | } 41 | 42 | }, 43 | "required": ["name", "description", "severity", "confidence", "timestamp", "location", "source"], 44 | "additionalProperties": false 45 | } -------------------------------------------------------------------------------- /simpletest.js: -------------------------------------------------------------------------------- 1 | // This is an example script that validates different OWASP OFF Finding JSON examples. 2 | // 3 | // See https://github.com/OWASP/off for additional detail. 4 | // 5 | 6 | // Set up a validator 7 | var Ajv = require('ajv'); 8 | var ajv = new Ajv(); 9 | var schema = require('./owasp.off.schema.json') 10 | var validate = ajv.compile(schema) 11 | 12 | // Test a good case (happy path) 13 | var data = require('./example.finding.json') 14 | var valid = validate(data); 15 | if (valid) { 16 | console.log('User data is valid'); 17 | } else { 18 | console.log('User data is INVALID!'); 19 | console.log(ajv.errorsText()); 20 | } 21 | 22 | // // --- THESE ARE NOT WORKING! 23 | 24 | // Test bad format 25 | var data1 = require('./tests/badformat.finding.json') 26 | var valid = validate(data1); 27 | if (valid) { 28 | console.log('Warning: User data is valid but should be invalid.'); 29 | } else { 30 | console.log('Correctly identified invalid format.') 31 | } 32 | 33 | // Test incomplete 34 | var data1 = require('./tests/incomplete.finding.json') 35 | var valid = validate(data1); 36 | if (valid) { 37 | console.log('Warning: User data is valid but should be incomplete.'); 38 | } else { 39 | console.log('Correctly identified imcomplete finding.') 40 | } 41 | 42 | // Test extra 43 | var data2 = require('./tests/extradata.finding.json') 44 | var valid = validate(data2); 45 | if (valid) { 46 | console.log('Warning: User data is valid but should be invalid with extra fields.'); 47 | } else { 48 | console.log('Correctly identified extra data in finding.') 49 | } 50 | -------------------------------------------------------------------------------- /tests/badformat.finding.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Remote Code Execution", 3 | "description": "Remote Code Execution", 4 | "detail": "Rails 3.2.12 with globbing routes is vulnerable to directory traversal and remote code execution. Patch or upgrade to 3.2.18 http://brakemanscanner.org/docs/warning_types/remote_code_execution/", 5 | "severity": "medium", 6 | "confidence": "medium", 7 | "fingerprint": "2153b4047940d99e694a7e1b7c51b3eff1ca8dee4b796cab17ad1af0763248a2", 8 | "timestamp": "2017-09-14T16:09:39.896Z", 9 | "source": "brakeman", 10 | "location": "config/routes.rb", 11 | "cvss": "a", 12 | "references": [ 13 | "https://www.owasp.org/index.php/Path_Traversal", 14 | "https://www.owasp.org/index.php/Command_Injection" 15 | ], 16 | "cwes": [ 17 | "https://cwe.mitre.org/data/definitions/23.html", 18 | "https://cwe.mitre.org/data/definitions/78.html" 19 | ] 20 | } -------------------------------------------------------------------------------- /tests/extradata.finding.json: -------------------------------------------------------------------------------- 1 | { 2 | "extradata": "here", 3 | "name": "Remote Code Execution", 4 | "description": "Remote Code Execution", 5 | "detail": "Rails 3.2.12 with globbing routes is vulnerable to directory traversal and remote code execution. Patch or upgrade to 3.2.18 http://brakemanscanner.org/docs/warning_types/remote_code_execution/", 6 | "severity": "medium", 7 | "confidence": "medium", 8 | "fingerprint": "2153b4047940d99e694a7e1b7c51b3eff1ca8dee4b796cab17ad1af0763248a2", 9 | "timestamp": "2017-09-14T16:09:39.896Z", 10 | "source": "brakeman", 11 | "location": "config/routes.rb", 12 | "cvss": 3.5, 13 | "references": [ 14 | "https://www.owasp.org/index.php/Path_Traversal", 15 | "https://www.owasp.org/index.php/Command_Injection" 16 | ], 17 | "cwes": [ 18 | "https://cwe.mitre.org/data/definitions/23.html", 19 | "https://cwe.mitre.org/data/definitions/78.html" 20 | ] 21 | } -------------------------------------------------------------------------------- /tests/incomplete.finding.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Finding 1", 3 | "description": "Detailed description", 4 | "severity": "low" 5 | } -------------------------------------------------------------------------------- /tests/test_schema_validation.py: -------------------------------------------------------------------------------- 1 | import jsonschema 2 | from jsonschema.exceptions import SchemaError, ValidationError 3 | import json 4 | 5 | schema_file = "owasp.off.schema.json" 6 | 7 | def _load_finding(finding_file): 8 | finding = "" 9 | try: 10 | finding_f = open(finding_file, "r") 11 | finding = json.load(finding_f) 12 | except ValueError: 13 | print("Problem parsing finding file") 14 | return finding 15 | 16 | def _load_json_schema(filename): 17 | schema_file = open(filename, "r") 18 | schema = json.load(schema_file) 19 | return schema 20 | 21 | def _validate(finding, schema): 22 | valid = True 23 | try: 24 | v = jsonschema.Draft7Validator(schema) 25 | for error in sorted(v.iter_errors(finding), key=str): 26 | print(error.message) 27 | valid = False 28 | 29 | except SchemaError as e: 30 | print("There is an error with the schema") 31 | valid = False 32 | except ValidationError as e: 33 | print(e) 34 | print("---------") 35 | print(e.absolute_path) 36 | print("---------") 37 | print(e.absolute_schema_path) 38 | valid = False 39 | return valid 40 | 41 | def test_example_finding(): 42 | data = _load_finding("example.finding.json") 43 | schema = _load_json_schema(schema_file) 44 | result = _validate(data,schema) 45 | assert result == True, "The schema should validate the example file" 46 | 47 | def test_bad_format_finding(): 48 | data = _load_finding("tests/badformat.finding.json") 49 | schema = _load_json_schema(schema_file) 50 | result = _validate(data,schema) 51 | assert result == False, "The schema should not validate the example file" 52 | 53 | def test_extra_data_finding(): 54 | data = _load_finding("tests/extradata.finding.json") 55 | schema = _load_json_schema(schema_file) 56 | result = _validate(data,schema) 57 | assert result == False, "The schema should validate the example file even though it has extra information" 58 | 59 | def test_incomplete_finding(): 60 | data = _load_finding("tests/incomplete.finding.json") 61 | schema = _load_json_schema(schema_file) 62 | result = _validate(data,schema) 63 | assert result == False, "The schema should not validate the example file" 64 | --------------------------------------------------------------------------------