Subversion Repositories vnag

Rev

Rev 16 | 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 /* <ViaThinkSoftSignature>
17 daniel-mar 2
qWEItTVwmWt4bCuj7GpdpdKLH3wLzHxhgQxr8p/AKPM17DRZoAn/i9x4YUloNuiC6
3
ut7naeSIlD/SKCob9Qli9lrbCjlfAsuZUaV1MVuZutycJBp3xSEOLCxLfBWB9DbVq
4
pfAmFuS/ukEWAVipyeddB3RPmsAPDnZlNkQyRXXcB/yO31GYANCB5K6qaUPRoHVc+
5
7AbZK3WfZBBaILYXdFDzincJfCyVxVOtFqdbuwyIQiJKAf6fmcWqJeEWCOGDMve+i
6
bcRpS8QuWbB6rntnmh0NB6JvjpaSkdh0I5m4Qx505OeB6lFgeAVlmszJvUSi3zqVz
7
b2zOvnEfm2+QGjrolHyP4vWAQ5ldwoYCXK/9yLsQ7YjvWM8FmgGIyO+E0Gn+diEMo
8
PivhhivcOyxpa3O9o03WenONl0ERYB5zzrqzxfqGo5Az4Wluy+8vtpCneUf1VmtRT
9
zTLW0KLe/j6OApYgzgKWOxk6cyZMNCpWwDz5jt75xeysEBJSlVe3Z1uNLkD+XFjFm
10
b+0TgAwq1hDS0aHbDBwE5dwO3vCkVOGZoANnNZtU4vzs8Oy9GVHmR8muj563Dex6H
11
7TygozGMfzI9WxttN9HIvPiA5ncsgHDoA9h7BT+AE1cmwWX+0Cs5m6sqlZZ4dLNsT
12
Cwe7vXisS6Q4WTEX9qsldDXrzTgJm7Vmb9wMxwr85nEpViET4uEPbeG8ZsFF1tPIU
13
yJPE+yZUCCgml8AWrAcdNaRfx92QFdsYhSOIcypV3VlSjvWm1NE2dStuTo/dqX5M3
14
fmNt/04tZnX7Mh7MzgvcXncuprAS6qgaOPWo5uFL8nUU4WIOzxYywNqaSpA2VFCz1
15
YewKblZVRJmVMcszKHdpcIWYAqX6UbZ7UGO/B06RoFQ0A7oS3A5aQOuKyjQDIFe5n
16
8MZGCBfp+yINGcJe68yBLu853OSxfs34/6MzbdEjdei2+y4OP8yWU0pJ9adj0ZLxc
17
Ev2ktdqE10UyiSvdxmCnJ3ti/1aS5IFrXiA4lUzsABnZzIOsc6GmrEjSS/G/64zfl
18
eCU3scGGBnD4/YNDtkGoMFOSw5qJUHjaiHWnFHwqov5G8LksPfc7MhZgKqLmadzSA
19
uw6ns9crjn/zeppNCaO0RwN/dnRkGS1dIdZ7r7d44zyuxG4HPWeJOi/6a0mtGScrO
20
tZRjuLQItoOFAImOgZ3QmQELxUKapDArl/wQDl/J+dg7XGIdocpquWQRqi3gsaiu4
21
LW1T0hDvpqEBsj5wgPLr7f4M0hUNzF6sD3WTuj+wZuo+Z2/52/yEqinYUsR29NdJ+
22
8mb/hZ6T5atmeGnhS8pG7y7sj2NsanxEtvucQHdCWT3Di9B+jnUGVlfPtr+3jwKW0
23
A==
8 daniel-mar 24
</ViaThinkSoftSignature> */ ?>
25
<?php
26
 
27
/*
28
 * VNag - Nagios Framework for PHP
29
 * Developed by Daniel Marschall, ViaThinkSoft <www.viathinksoft.com>
30
 * Licensed under the terms of the Apache 2.0 license
31
 *
17 daniel-mar 32
 * Revision 2020-03-07
8 daniel-mar 33
 */
34
 
35
declare(ticks=1);
36
 
37
class OpenBugBountyCheck extends VNag {
38
        protected $argDomain = null;
15 daniel-mar 39
        protected $argPrivateAPI = null;
17 daniel-mar 40
        protected $argIgnoredIds = null;
8 daniel-mar 41
 
42
        public function __construct() {
43
                parent::__construct();
44
 
45
                $this->registerExpectedStandardArguments('Vvht');
46
 
47
                $this->getHelpManager()->setPluginName('check_openbugbounty');
15 daniel-mar 48
                $this->getHelpManager()->setVersion('1.1');
8 daniel-mar 49
                $this->getHelpManager()->setShortDescription('This plugin checks if a domain has unfixed vulnerabilities listed at OpenBugBounty.org.');
50
                $this->getHelpManager()->setCopyright('Copyright (C) 2011-$CURYEAR$ Daniel Marschall, ViaThinkSoft.');
17 daniel-mar 51
                $this->getHelpManager()->setSyntax('$SCRIPTNAME$ [-d <SingleDomain[,SingleDomain,[...]]> | -d <DomainListFile> | -p <PrivateApiUrl> | -i <IgnoredId,IgnoredId,...> ]');
8 daniel-mar 52
                $this->getHelpManager()->setFootNotes('If you encounter bugs, please contact ViaThinkSoft at www.viathinksoft.com');
53
 
54
                // Individual (non-standard) arguments:
9 daniel-mar 55
                $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 56
                $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 57
                $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 58
        }
59
 
60
        protected function get_cache_dir() {
61
                $homedir = @getenv('HOME');
62
                if ($homedir) {
63
                        $try = "${homedir}/.vnag_obb_cache";
64
                        if (is_dir($try)) return $try;
65
                        if (@mkdir($try)) return $try;
66
                }
67
 
68
                $user = posix_getpwuid(posix_geteuid());
69
                if (isset($user['dir'])) {
70
                        $homedir = $user['dir'];
71
                        $try = "${homedir}/.vnag_obb_cache";
72
                        if (is_dir($try)) return $try;
73
                        if (@mkdir($try)) return $try;
74
                }
75
 
76
                if (isset($user['name'])) {
77
                        $username = $user['name'];
78
                        $try = "/tmp/vnag_obb_cache";
79
                        if (is_dir($try)) return $try;
80
                        if (@mkdir($try)) return $try;
81
                }
82
 
83
                return false; // should usually never happen
84
        }
85
 
17 daniel-mar 86
        function is_ignored($id) {
87
                $ids = $this->argIgnoredIds->getValue();
88
                if (empty($ids)) return false;
89
 
90
                $ids = explode(',', $ids);
91
                foreach ($ids as $test) {
92
                        if ($id == $test) return true;
93
                }
94
                return false;
95
        }
96
 
97
        static function extract_id_from_url($url) {
98
                // https://www.openbugbounty.org/reports/1019234/
99
                $parts = explode('/', $url);
100
                foreach ($parts as $part) {
101
                        if (is_numeric($part)) return $part;
102
                }
103
                return -1;
104
        }
105
 
106
        function num_open_bugs_v1($domain, $max_cache_time = 3600) { // TODO: make cache time configurable via config
107
                //assert(!empty($this->argDomain->getValue()));
108
                //assert(empty($this->argPrivateAPI->getValue()));
109
 
110
                $fixed = 0;
111
                $unfixed = 0;
112
 
113
                $this->setStatus(VNag::STATUS_OK);
114
 
8 daniel-mar 115
                $domain = strtolower($domain);
116
                $cache_file = $this->get_cache_dir() . '/' . md5($domain);
117
 
118
                if (file_exists($cache_file) && (time()-filemtime($cache_file) < $max_cache_time)) {
119
                        $cont = file_get_contents($cache_file);
120
                } else {
121
                        $url = 'https://www.openbugbounty.org/api/1/search/?domain='.urlencode($domain);
122
                        $cont = file_get_contents($url);
123
                        file_put_contents($cache_file, $cont);
124
                }
125
 
126
                $xml = simplexml_load_string($cont);
127
                foreach ($xml as $x) {
17 daniel-mar 128
                        $submission = $x->url;
129
 
130
                        if ($fake_fix = $this->is_ignored($this->extract_id_from_url($submission))) $x->fixed = '1';
131
 
132
                        if ($x->fixed == '0') {
133
                                $unfixed++;
134
                                $this->addVerboseMessage("Unfixed issue found at $domain: $submission", VNag::VERBOSITY_SUMMARY);
135
                                $this->setStatus(VNag::STATUS_WARNING);
136
                                // TODO: Unlike the "private" API, the "normal" API does not show if a bug is disclosed (= critical instead of warning)
137
                                //       But we could check if the report is older than XXX months, and then we know that it must be disclosed.
138
                        }
139
 
140
                        if ($x->fixed == '1') {
141
                                $fixed++;
142
                                $tmp = $fake_fix ? ' (fix asserted by operator)' : '';
143
                                $this->addVerboseMessage("Fixed issue found at $domain: $submission$tmp", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
144
                                $this->setStatus(VNag::STATUS_OK);
145
                        }
8 daniel-mar 146
                }
147
 
148
                return array($fixed, $unfixed);
149
        }
150
 
15 daniel-mar 151
        function get_privateapi_data($url, $max_cache_time = 3600) { // TODO: make cache time configurable via config
152
                $url = strtolower($url);
153
                $cache_file = $this->get_cache_dir() . '/' . md5($url);
154
 
155
                if (file_exists($cache_file) && (time()-filemtime($cache_file) < $max_cache_time)) {
156
                        $cont = file_get_contents($cache_file);
157
                } else {
158
                        $cont = file_get_contents($url);
159
                        file_put_contents($cache_file, $cont);
160
                }
161
 
162
                $ary = @json_decode($cont,true);
163
                if (!$ary) throw new Exception("This is probably not a correct Private API URL, or the service is down (JSON Decode failed)");
164
                return $ary;
165
        }
166
 
17 daniel-mar 167
        function num_open_bugs_v2($privateapi, $max_cache_time = 3600) { // TODO: make cache time configurable via config
168
                //assert(empty($this->argDomain->getValue()));
169
                //assert(!empty($this->argPrivateAPI->getValue()));
170
 
171
                $sum_fixed = 0;
172
                $sum_unfixed_pending = 0;
173
                $sum_unfixed_disclosed = 0;
174
 
175
                $this->setStatus(VNag::STATUS_OK);
176
 
177
                $ary = $this->get_privateapi_data($privateapi, $max_cache_time);
178
                foreach ($ary as $id => $data) {
179
                        /*
180
                        [Vulnerability Reported] => 21 September, 2017 05:13
181
                        [Vulnerability Verified] => 21 September, 2017 05:14
182
                        [Scheduled Public Disclosure] => 21 October, 2017 05:13
183
                        [Path Status] => Patched
184
                        [Vulnerability Fixed] => 7 August, 2018 21:47
185
                        [Report Url] => https://openbugbounty.org/reports/.../
186
                        [Host] => ...
187
                        [Researcher] => https://openbugbounty.org/researchers/.../
188
                        */
189
 
190
                        if (empty($data['Vulnerability Reported'])) throw new Exception("This is probably not a correct Private API URL, or the service is down (Missing fields in structure)");
191
 
192
                        $status = isset($data['Patch Status']) ? $data['Patch Status'] : $data['Path Status']; // sic! There is a typo in their API (reported, but not fixed)
193
 
194
                        $submission = $data['Report Url'];
195
                        if ($fake_fix = $this->is_ignored($this->extract_id_from_url($submission))) $status = 'Patched';
196
 
197
                        $domain = $data['Host'];
198
 
199
                        if ($status == 'Patched') {
200
                                $sum_fixed++;
201
                                $fixed_date = $fake_fix ? 'asserted by operator' : $data['Vulnerability Fixed'];
202
                                $this->addVerboseMessage("Fixed issue found at $domain: $submission (fixed: $fixed_date)", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
203
                                $this->setStatus(VNag::STATUS_OK);
204
                        } else {
205
                                $disclosure = $data['Scheduled Public Disclosure'];
206
                                $time = strtotime(str_replace(',', '', $disclosure));
207
                                if (time() > $time) {
208
                                        $sum_unfixed_disclosed++;
209
                                        $this->addVerboseMessage("Disclosed unfixed issue found at $domain: $submission (disclosure: $disclosure)", VNag::VERBOSITY_SUMMARY);
210
                                        $this->setStatus(VNag::STATUS_CRITICAL);
211
                                } else {
212
                                        $sum_unfixed_pending++;
213
                                        $this->addVerboseMessage("Undisclosed unfixed issue found at $domain: $submission (disclosure: $disclosure)", VNag::VERBOSITY_SUMMARY);
214
                                        $this->setStatus(VNag::STATUS_WARNING);
215
                                }
216
                        }
217
                }
218
 
219
                return array($sum_fixed, $sum_unfixed_pending, $sum_unfixed_disclosed);
220
        }
221
 
8 daniel-mar 222
        protected function cbRun($optional_args=array()) {
223
                $domain = $this->argDomain->getValue();
15 daniel-mar 224
                $privateapi = $this->argPrivateAPI->getValue();
225
 
226
                if (empty($domain) && empty($privateapi)) {
227
                        throw new Exception("Please specify a domain or subdomain, a list of domains, or a private API Url.");
8 daniel-mar 228
                }
229
 
15 daniel-mar 230
                if (!empty($domain) && !empty($privateapi)) {
231
                        throw new Exception("You can either use argument '-d' or '-p', but not both.");
232
                }
233
 
234
                if (!empty($privateapi)) {
235
                        // Possibility 1: Private API (showing all bugs for all of your domains, with detailled information)
236
                        //                https://www.openbugbounty.org/api/2/.../
237
                        $sum_fixed = 0;
238
                        $sum_unfixed_pending = 0;
239
                        $sum_unfixed_disclosed = 0;
17 daniel-mar 240
                        list($sum_fixed, $sum_unfixed_pending, $sum_unfixed_disclosed) = $this->num_open_bugs_v2($privateapi);
16 daniel-mar 241
                        if ($this->getVerbosityLevel() == VNag::VERBOSITY_SUMMARY) {
242
                                $this->setHeadline(($sum_unfixed_pending + $sum_unfixed_disclosed)." unfixed ($sum_unfixed_pending pending, $sum_unfixed_disclosed disclosed) issues found at your domains", true);
243
                        } else {
244
                                $this->setHeadline("$sum_fixed fixed and ".($sum_unfixed_pending + $sum_unfixed_disclosed)." unfixed ($sum_unfixed_pending pending, $sum_unfixed_disclosed disclosed) issues found at your domains", true);
245
                        }
15 daniel-mar 246
                } else if (file_exists($domain)) {
247
                        // Possibility 2: File containing a list of domains
8 daniel-mar 248
                        $domains = file($domain);
249
                        $sum_fixed = 0;
250
                        $sum_unfixed = 0;
251
                        $count = 0;
252
                        foreach ($domains as $domain) {
253
                                $domain = trim($domain);
254
                                if ($domain == '') continue;
255
                                if ($domain[0] == '#') continue;
17 daniel-mar 256
                                list($fixed, $unfixed) = $this->num_open_bugs_v1($domain);
8 daniel-mar 257
                                $sum_fixed += $fixed;
258
                                $sum_unfixed += $unfixed;
259
                                $count++;
10 daniel-mar 260
                                $this->addVerboseMessage("$fixed fixed and $unfixed unfixed issues found at $domain", $unfixed > 0 ? VNag::VERBOSITY_SUMMARY : VNag::VERBOSITY_ADDITIONAL_INFORMATION);
8 daniel-mar 261
                        }
16 daniel-mar 262
                        if ($this->getVerbosityLevel() == VNag::VERBOSITY_SUMMARY) {
263
                                $this->setHeadline("$sum_unfixed unfixed issues found at $count domains", true);
264
                        } else {
265
                                $this->setHeadline("$sum_fixed fixed and $sum_unfixed unfixed issues found at $count domains", true);
266
                        }
10 daniel-mar 267
                } else if (strpos($domain, ',') !== false) {
15 daniel-mar 268
                        // Possibility 3: Domains separated with comma
9 daniel-mar 269
                        $domains = explode(',', $domain);
270
                        $sum_fixed = 0;
271
                        $sum_unfixed = 0;
272
                        $count = 0;
273
                        foreach ($domains as $domain) {
17 daniel-mar 274
                                list($fixed, $unfixed) = $this->num_open_bugs_v1($domain);
9 daniel-mar 275
                                $sum_fixed += $fixed;
276
                                $sum_unfixed += $unfixed;
277
                                $count++;
10 daniel-mar 278
                                $this->addVerboseMessage("$fixed fixed and $unfixed unfixed issues found at $domain", $unfixed > 0 ? VNag::VERBOSITY_SUMMARY : VNag::VERBOSITY_ADDITIONAL_INFORMATION);
9 daniel-mar 279
                        }
16 daniel-mar 280
                        if ($this->getVerbosityLevel() == VNag::VERBOSITY_SUMMARY) {
281
                                $this->setHeadline("$sum_unfixed unfixed issues found at $count domains", true);
282
                        } else {
283
                                $this->setHeadline("$sum_fixed fixed and $sum_unfixed unfixed issues found at $count domains", true);
284
                        }
8 daniel-mar 285
                } else {
15 daniel-mar 286
                        // Possibility 4: Single domain
17 daniel-mar 287
                        list($fixed, $unfixed) = $this->num_open_bugs_v1($domain);
16 daniel-mar 288
                        if ($this->getVerbosityLevel() == VNag::VERBOSITY_SUMMARY) {
289
                                $this->setHeadline("$unfixed unfixed issues found at $domain", true);
290
                        } else {
291
                                $this->setHeadline("$fixed fixed and $unfixed unfixed issues found at $domain", true);
292
                        }
8 daniel-mar 293
                }
294
        }
295
}
9 daniel-mar 296