Subversion Repositories vnag

Rev

Rev 77 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
8 daniel-mar 1
<?php
2
 
3
/*
4
 * VNag - Nagios Framework for PHP
5
 * Developed by Daniel Marschall, ViaThinkSoft <www.viathinksoft.com>
6
 * Licensed under the terms of the Apache 2.0 license
7
 *
76 daniel-mar 8
 * Revision 2023-10-13
8 daniel-mar 9
 */
10
 
11
declare(ticks=1);
12
 
13
class OpenBugBountyCheck extends VNag {
14
        protected $argDomain = null;
15 daniel-mar 15
        protected $argPrivateAPI = null;
17 daniel-mar 16
        protected $argIgnoredIds = null;
8 daniel-mar 17
 
18
        public function __construct() {
19
                parent::__construct();
20
 
21
                $this->registerExpectedStandardArguments('Vvht');
22
 
23
                $this->getHelpManager()->setPluginName('check_openbugbounty');
79 daniel-mar 24
                $this->getHelpManager()->setVersion('2023-10-13');
8 daniel-mar 25
                $this->getHelpManager()->setShortDescription('This plugin checks if a domain has unfixed vulnerabilities listed at OpenBugBounty.org.');
26
                $this->getHelpManager()->setCopyright('Copyright (C) 2011-$CURYEAR$ Daniel Marschall, ViaThinkSoft.');
17 daniel-mar 27
                $this->getHelpManager()->setSyntax('$SCRIPTNAME$ [-d <SingleDomain[,SingleDomain,[...]]> | -d <DomainListFile> | -p <PrivateApiUrl> | -i <IgnoredId,IgnoredId,...> ]');
8 daniel-mar 28
                $this->getHelpManager()->setFootNotes('If you encounter bugs, please contact ViaThinkSoft at www.viathinksoft.com');
29
 
30
                // Individual (non-standard) arguments:
9 daniel-mar 31
                $this->addExpectedArgument($this->argDomain = new VNagArgument('d', 'domain', VNagArgument::VALUE_REQUIRED, 'domainOrFile', 'Domain(s) or subdomain(s), separated by comma, to be checked or a file containing domain names.'));
15 daniel-mar 32
                $this->addExpectedArgument($this->argPrivateAPI = new VNagArgument('p', 'privateapi', VNagArgument::VALUE_REQUIRED, 'privateApiUrl', 'A link to your private API (https://www.openbugbounty.org/api/2/...../). Cannot be used together with argument \'-d\'.'));
17 daniel-mar 33
                $this->addExpectedArgument($this->argIgnoredIds = new VNagArgument('i', 'ignoredids', VNagArgument::VALUE_REQUIRED, 'ignoredIds', 'Comma separated list of submission IDs that shall be defined as fixed (because OpenBugBounty often does not mark fixed bugs as fixed, even if you tell them that you have fixed them...)'));
8 daniel-mar 34
        }
35
 
17 daniel-mar 36
        function is_ignored($id) {
37
                $ids = $this->argIgnoredIds->getValue();
38
                if (empty($ids)) return false;
39
 
40
                $ids = explode(',', $ids);
41
                foreach ($ids as $test) {
42
                        if ($id == $test) return true;
43
                }
44
                return false;
45
        }
46
 
47
        static function extract_id_from_url($url) {
48
                // https://www.openbugbounty.org/reports/1019234/
49
                $parts = explode('/', $url);
50
                foreach ($parts as $part) {
51
                        if (is_numeric($part)) return $part;
52
                }
53
                return -1;
54
        }
55
 
56
        function num_open_bugs_v1($domain, $max_cache_time = 3600) { // TODO: make cache time configurable via config
57
                //assert(!empty($this->argDomain->getValue()));
58
                //assert(empty($this->argPrivateAPI->getValue()));
59
 
60
                $fixed = 0;
61
                $unfixed = 0;
42 daniel-mar 62
                $unfixed_ignored = 0;
17 daniel-mar 63
 
64
                $this->setStatus(VNag::STATUS_OK);
65
 
8 daniel-mar 66
                $domain = strtolower($domain);
76 daniel-mar 67
                $cont = $this->url_get_contents('https://www.openbugbounty.org/api/1/search/?domain='.urlencode($domain));
8 daniel-mar 68
 
69
                $xml = simplexml_load_string($cont);
70
                foreach ($xml as $x) {
17 daniel-mar 71
                        $submission = $x->url;
72
 
42 daniel-mar 73
                        if ($x->fixed == '1') {
74
                                $fixed++;
75
                                $this->addVerboseMessage("Fixed issue found at $domain: $submission", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
76
                                $this->setStatus(VNag::STATUS_OK);
77
                        } else if ($this->is_ignored($this->extract_id_from_url($submission))) {
78
                                $unfixed_ignored++;
79
                                $this->addVerboseMessage("Ignored issue found at $domain: $submission", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
80
                                $this->setStatus(VNag::STATUS_OK);
81
                        } else {
17 daniel-mar 82
                                $unfixed++;
83
                                $this->addVerboseMessage("Unfixed issue found at $domain: $submission", VNag::VERBOSITY_SUMMARY);
84
                                $this->setStatus(VNag::STATUS_WARNING);
85
                                // TODO: Unlike the "private" API, the "normal" API does not show if a bug is disclosed (= critical instead of warning)
86
                                //       But we could check if the report is older than XXX months, and then we know that it must be disclosed.
87
                        }
88
 
8 daniel-mar 89
                }
90
 
42 daniel-mar 91
                return array($fixed, $unfixed, $unfixed_ignored);
8 daniel-mar 92
        }
93
 
17 daniel-mar 94
        function num_open_bugs_v2($privateapi, $max_cache_time = 3600) { // TODO: make cache time configurable via config
95
                //assert(empty($this->argDomain->getValue()));
96
                //assert(!empty($this->argPrivateAPI->getValue()));
97
 
98
                $sum_fixed = 0;
99
                $sum_unfixed_pending = 0;
100
                $sum_unfixed_disclosed = 0;
42 daniel-mar 101
                $sum_unfixed_ignored = 0;
17 daniel-mar 102
 
103
                $this->setStatus(VNag::STATUS_OK);
104
 
76 daniel-mar 105
                // Get Private API data
106
                $cont = $this->url_get_contents($privateapi, $max_cache_time);
77 daniel-mar 107
                if ($cont === false) throw new VNagException("This is probably not a correct Private API URL, or the service is down (GET request failed)");
76 daniel-mar 108
                $ary = @json_decode($cont,true);
77 daniel-mar 109
                if ($ary === false) throw new VNagException("This is probably not a correct Private API URL, or the service is down (JSON Decode failed)");
76 daniel-mar 110
 
17 daniel-mar 111
                foreach ($ary as $id => $data) {
112
                        /*
113
                        [Vulnerability Reported] => 21 September, 2017 05:13
114
                        [Vulnerability Verified] => 21 September, 2017 05:14
115
                        [Scheduled Public Disclosure] => 21 October, 2017 05:13
116
                        [Path Status] => Patched
117
                        [Vulnerability Fixed] => 7 August, 2018 21:47
118
                        [Report Url] => https://openbugbounty.org/reports/.../
119
                        [Host] => ...
120
                        [Researcher] => https://openbugbounty.org/researchers/.../
121
                        */
122
 
77 daniel-mar 123
                        if (empty($data['Vulnerability Reported'])) throw new VNagException("This is probably not a correct Private API URL, or the service is down (Missing fields in structure)");
17 daniel-mar 124
 
125
                        $status = isset($data['Patch Status']) ? $data['Patch Status'] : $data['Path Status']; // sic! There is a typo in their API (reported, but not fixed)
126
 
127
                        $submission = $data['Report Url'];
128
                        $domain = $data['Host'];
129
 
130
                        if ($status == 'Patched') {
131
                                $sum_fixed++;
42 daniel-mar 132
                                $fixed_date = $data['Vulnerability Fixed'];
17 daniel-mar 133
                                $this->addVerboseMessage("Fixed issue found at $domain: $submission (fixed: $fixed_date)", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
134
                                $this->setStatus(VNag::STATUS_OK);
42 daniel-mar 135
                        } else if ($this->is_ignored($this->extract_id_from_url($submission))) {
136
                                $sum_unfixed_ignored++;
137
                                $this->addVerboseMessage("Ignored issue found at $domain: $submission", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
138
                                $this->setStatus(VNag::STATUS_OK);
17 daniel-mar 139
                        } else {
140
                                $disclosure = $data['Scheduled Public Disclosure'];
141
                                $time = strtotime(str_replace(',', '', $disclosure));
142
                                if (time() > $time) {
143
                                        $sum_unfixed_disclosed++;
144
                                        $this->addVerboseMessage("Disclosed unfixed issue found at $domain: $submission (disclosure: $disclosure)", VNag::VERBOSITY_SUMMARY);
145
                                        $this->setStatus(VNag::STATUS_CRITICAL);
146
                                } else {
147
                                        $sum_unfixed_pending++;
148
                                        $this->addVerboseMessage("Undisclosed unfixed issue found at $domain: $submission (disclosure: $disclosure)", VNag::VERBOSITY_SUMMARY);
149
                                        $this->setStatus(VNag::STATUS_WARNING);
150
                                }
151
                        }
152
                }
153
 
42 daniel-mar 154
                return array($sum_fixed, $sum_unfixed_pending, $sum_unfixed_disclosed, $sum_unfixed_ignored);
17 daniel-mar 155
        }
156
 
8 daniel-mar 157
        protected function cbRun($optional_args=array()) {
158
                $domain = $this->argDomain->getValue();
15 daniel-mar 159
                $privateapi = $this->argPrivateAPI->getValue();
160
 
161
                if (empty($domain) && empty($privateapi)) {
77 daniel-mar 162
                        throw new VNagException("Please specify a domain or subdomain, a list of domains, or a private API Url.");
8 daniel-mar 163
                }
164
 
15 daniel-mar 165
                if (!empty($domain) && !empty($privateapi)) {
77 daniel-mar 166
                        throw new VNagException("You can either use argument '-d' or '-p', but not both.");
15 daniel-mar 167
                }
168
 
169
                if (!empty($privateapi)) {
170
                        // Possibility 1: Private API (showing all bugs for all of your domains, with detailled information)
171
                        //                https://www.openbugbounty.org/api/2/.../
172
                        $sum_fixed = 0;
173
                        $sum_unfixed_pending = 0;
174
                        $sum_unfixed_disclosed = 0;
42 daniel-mar 175
                        $sum_unfixed_ignored = 0;
176
                        list($sum_fixed, $sum_unfixed_pending, $sum_unfixed_disclosed, $sum_unfixed_ignored) = $this->num_open_bugs_v2($privateapi);
177
                        $this->setHeadline("$sum_fixed fixed and ".($sum_unfixed_pending + $sum_unfixed_disclosed + $sum_unfixed_ignored)." unfixed ($sum_unfixed_pending pending, $sum_unfixed_disclosed disclosed, $sum_unfixed_ignored ignored) issues found at your domain(s)", true);
15 daniel-mar 178
                } else if (file_exists($domain)) {
179
                        // Possibility 2: File containing a list of domains
8 daniel-mar 180
                        $domains = file($domain);
181
                        $sum_fixed = 0;
182
                        $sum_unfixed = 0;
42 daniel-mar 183
                        $sum_unfixed_ignored = 0;
8 daniel-mar 184
                        $count = 0;
185
                        foreach ($domains as $domain) {
186
                                $domain = trim($domain);
187
                                if ($domain == '') continue;
188
                                if ($domain[0] == '#') continue;
42 daniel-mar 189
                                list($fixed, $unfixed, $unfixed_ignored) = $this->num_open_bugs_v1($domain);
8 daniel-mar 190
                                $sum_fixed += $fixed;
191
                                $sum_unfixed += $unfixed;
42 daniel-mar 192
                                $sum_unfixed_ignored += $unfixed_ignored;
8 daniel-mar 193
                                $count++;
42 daniel-mar 194
                                $this->addVerboseMessage("$fixed fixed, $unfixed_ignored ignored, and $unfixed unfixed issues found at $domain", $unfixed > 0 ? VNag::VERBOSITY_SUMMARY : VNag::VERBOSITY_ADDITIONAL_INFORMATION);
8 daniel-mar 195
                        }
42 daniel-mar 196
                        $this->setHeadline("$sum_fixed fixed and ".($sum_unfixed + $sum_unfixed_ignored)." unfixed (including $sum_unfixed_ignored ignored) issues found at $count domains", true);
10 daniel-mar 197
                } else if (strpos($domain, ',') !== false) {
15 daniel-mar 198
                        // Possibility 3: Domains separated with comma
9 daniel-mar 199
                        $domains = explode(',', $domain);
200
                        $sum_fixed = 0;
201
                        $sum_unfixed = 0;
42 daniel-mar 202
                        $sum_unfixed_ignored = 0;
9 daniel-mar 203
                        $count = 0;
204
                        foreach ($domains as $domain) {
42 daniel-mar 205
                                list($fixed, $unfixed, $unfixed_ignored) = $this->num_open_bugs_v1($domain);
9 daniel-mar 206
                                $sum_fixed += $fixed;
207
                                $sum_unfixed += $unfixed;
42 daniel-mar 208
                                $sum_unfixed_ignored += $unfixed_ignored;
9 daniel-mar 209
                                $count++;
42 daniel-mar 210
                                $this->addVerboseMessage("$fixed fixed, $unfixed_ignored ignored,  and $unfixed unfixed issues found at $domain", $unfixed > 0 ? VNag::VERBOSITY_SUMMARY : VNag::VERBOSITY_ADDITIONAL_INFORMATION);
9 daniel-mar 211
                        }
42 daniel-mar 212
                        $this->setHeadline("$sum_fixed fixed and ".($sum_unfixed + $sum_unfixed_ignored)." unfixed (including $sum_unfixed_ignored ignored) issues found at $count domains", true);
8 daniel-mar 213
                } else {
15 daniel-mar 214
                        // Possibility 4: Single domain
42 daniel-mar 215
                        list($sum_fixed, $sum_unfixed, $sum_unfixed_ignored) = $this->num_open_bugs_v1($domain);
216
                        $this->setHeadline("$sum_fixed fixed and ".($sum_unfixed + $sum_unfixed_ignored)." unfixed (including $sum_unfixed_ignored ignored) issues found at $domain", true);
8 daniel-mar 217
                }
218
        }
219
}
9 daniel-mar 220