Subversion Repositories vnag

Rev

Rev 59 | 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>
76 daniel-mar 2
2wqycRfumOucELwlNawMn9GhkBhvG9p6LgHGfuTS6Shfc1innJ/zOmA8LTXeIkwM0
3
XIPKVZzyyDRNkXG2qQYuHRg16fMliE9GzfvMvnL8LPzfFc6Q6R4Ww539h3mz160ru
4
cS0BEOR2Gy+EJYXUcIrBd1Q6BK/g+YRbQOMFollNe1lz2CQK+6DgU9Qk55rKe72vp
5
2Z+4lH4qGNXCm7D07cqUun28uddcYcSruB0j4DzJ5xAhfAhl5Z/k/3SNttIFRj6KB
6
kV+tTEXQ4V8EGVEOk0jdeD/tF0aydJVS+wM+S888Mo9diURP/nBXOBi6hOguRjgYS
7
c5ue8JxxBUo6nJhCPDMKm7OHozXyai0WypkZiHFVHZ5ylvPLHpjbltVZalmH52YZt
8
93hJGWKl1ZykflKPfqNFmRaU28/vBdN5UoC8jo9W/SlcSjaK5PKby7USqh+dR+RSD
9
W6/H/SSiF9fg/qbTQ90KBHEBifYPDFyBBIC2pGwV7IuDHeBo2G7KYlrHBj+ONNOuL
10
fU9x+5kSWwP7ybtemgLAK7QdJCxvFXb4+maIfTQALCJGpiT2//qEScSWPUaQuL2Zf
11
QopPUiOANe4fm61JvmYsQ3K385e96fcoqNkeygXkKsbsbNzf8h4b9wgCVuThZ/2zS
12
YGV+EhyCXOsS3XRyUobe58Yq03vg8Jfr4N3Dcc8YOPc/Zftu/TYR69/jtCSjWZX4/
13
7VsjYhZiBsLtCEPZOMVlqtou0j727qZW+j/1G8yQ/seZKenU/dFTAaVDLnyL4Vjsq
14
1oz/DEaLAwXGnigRZwC2rwn5An4WiXahSm9ci92ElvNk9SdR7BRbCM+JiFauERJda
15
LHEefW8aInsrXaCJeF9EDtyVKaGr3QIwrNX3sOl86sWKv6485EzTrsK5MGPIj6Rbw
16
y+p8AAv/qyH6xxLlHM0pG89W8eWXt7Pd/CBexcfINNO11E1kTvFs+3TkwYdLN8Ppd
17
+QHZqCLEi4cyyymRnSKMO5D0FPvTUUu6HrriUnX3KIJ0Wmmo7FVs8YvBoHYFYVaDo
18
3SbI4bPhayyn5jh3SjUfuojHpAuAGTeTXbyqOqdvLowV91nuaIEpi7duEgOotJpMW
19
pCuerdxtjIm9/xDR4rfV5z2VDO+PbKqcMC+HckfYDq1IGuqwPJbAZL9Gtw8NJ+ieQ
20
OaFUt332WVJcNcwDyHZH4TRc7pc9T6pB9E0GVyREMgGIbnngcQm4Dp5wXvwCJwcgy
21
nERbiPtpIJ6fo1//0h/jhNYFCs+OkFEqMXTSKRhHulRP325DIY05x0bjWbXZjIO8R
22
j0XtvsYhgWnS3rdmw5Xii4FTkeko4F8A5K2JCZA1rgSxqtNW3MzP1o6l9yZ5CkA2/
23
w==
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
 *
76 daniel-mar 32
 * Revision 2023-10-13
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
 
17 daniel-mar 60
        function is_ignored($id) {
61
                $ids = $this->argIgnoredIds->getValue();
62
                if (empty($ids)) return false;
63
 
64
                $ids = explode(',', $ids);
65
                foreach ($ids as $test) {
66
                        if ($id == $test) return true;
67
                }
68
                return false;
69
        }
70
 
71
        static function extract_id_from_url($url) {
72
                // https://www.openbugbounty.org/reports/1019234/
73
                $parts = explode('/', $url);
74
                foreach ($parts as $part) {
75
                        if (is_numeric($part)) return $part;
76
                }
77
                return -1;
78
        }
79
 
80
        function num_open_bugs_v1($domain, $max_cache_time = 3600) { // TODO: make cache time configurable via config
81
                //assert(!empty($this->argDomain->getValue()));
82
                //assert(empty($this->argPrivateAPI->getValue()));
83
 
84
                $fixed = 0;
85
                $unfixed = 0;
42 daniel-mar 86
                $unfixed_ignored = 0;
17 daniel-mar 87
 
88
                $this->setStatus(VNag::STATUS_OK);
89
 
8 daniel-mar 90
                $domain = strtolower($domain);
76 daniel-mar 91
                $cont = $this->url_get_contents('https://www.openbugbounty.org/api/1/search/?domain='.urlencode($domain));
8 daniel-mar 92
 
93
                $xml = simplexml_load_string($cont);
94
                foreach ($xml as $x) {
17 daniel-mar 95
                        $submission = $x->url;
96
 
42 daniel-mar 97
                        if ($x->fixed == '1') {
98
                                $fixed++;
99
                                $this->addVerboseMessage("Fixed issue found at $domain: $submission", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
100
                                $this->setStatus(VNag::STATUS_OK);
101
                        } else if ($this->is_ignored($this->extract_id_from_url($submission))) {
102
                                $unfixed_ignored++;
103
                                $this->addVerboseMessage("Ignored issue found at $domain: $submission", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
104
                                $this->setStatus(VNag::STATUS_OK);
105
                        } else {
17 daniel-mar 106
                                $unfixed++;
107
                                $this->addVerboseMessage("Unfixed issue found at $domain: $submission", VNag::VERBOSITY_SUMMARY);
108
                                $this->setStatus(VNag::STATUS_WARNING);
109
                                // TODO: Unlike the "private" API, the "normal" API does not show if a bug is disclosed (= critical instead of warning)
110
                                //       But we could check if the report is older than XXX months, and then we know that it must be disclosed.
111
                        }
112
 
8 daniel-mar 113
                }
114
 
42 daniel-mar 115
                return array($fixed, $unfixed, $unfixed_ignored);
8 daniel-mar 116
        }
117
 
17 daniel-mar 118
        function num_open_bugs_v2($privateapi, $max_cache_time = 3600) { // TODO: make cache time configurable via config
119
                //assert(empty($this->argDomain->getValue()));
120
                //assert(!empty($this->argPrivateAPI->getValue()));
121
 
122
                $sum_fixed = 0;
123
                $sum_unfixed_pending = 0;
124
                $sum_unfixed_disclosed = 0;
42 daniel-mar 125
                $sum_unfixed_ignored = 0;
17 daniel-mar 126
 
127
                $this->setStatus(VNag::STATUS_OK);
128
 
76 daniel-mar 129
                // Get Private API data
130
                $cont = $this->url_get_contents($privateapi, $max_cache_time);
131
                if ($cont === false) throw new Exception("This is probably not a correct Private API URL, or the service is down (GET request failed)");
132
                $ary = @json_decode($cont,true);
133
                if ($ary === false) throw new Exception("This is probably not a correct Private API URL, or the service is down (JSON Decode failed)");
134
 
17 daniel-mar 135
                foreach ($ary as $id => $data) {
136
                        /*
137
                        [Vulnerability Reported] => 21 September, 2017 05:13
138
                        [Vulnerability Verified] => 21 September, 2017 05:14
139
                        [Scheduled Public Disclosure] => 21 October, 2017 05:13
140
                        [Path Status] => Patched
141
                        [Vulnerability Fixed] => 7 August, 2018 21:47
142
                        [Report Url] => https://openbugbounty.org/reports/.../
143
                        [Host] => ...
144
                        [Researcher] => https://openbugbounty.org/researchers/.../
145
                        */
146
 
147
                        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)");
148
 
149
                        $status = isset($data['Patch Status']) ? $data['Patch Status'] : $data['Path Status']; // sic! There is a typo in their API (reported, but not fixed)
150
 
151
                        $submission = $data['Report Url'];
152
                        $domain = $data['Host'];
153
 
154
                        if ($status == 'Patched') {
155
                                $sum_fixed++;
42 daniel-mar 156
                                $fixed_date = $data['Vulnerability Fixed'];
17 daniel-mar 157
                                $this->addVerboseMessage("Fixed issue found at $domain: $submission (fixed: $fixed_date)", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
158
                                $this->setStatus(VNag::STATUS_OK);
42 daniel-mar 159
                        } else if ($this->is_ignored($this->extract_id_from_url($submission))) {
160
                                $sum_unfixed_ignored++;
161
                                $this->addVerboseMessage("Ignored issue found at $domain: $submission", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
162
                                $this->setStatus(VNag::STATUS_OK);
17 daniel-mar 163
                        } else {
164
                                $disclosure = $data['Scheduled Public Disclosure'];
165
                                $time = strtotime(str_replace(',', '', $disclosure));
166
                                if (time() > $time) {
167
                                        $sum_unfixed_disclosed++;
168
                                        $this->addVerboseMessage("Disclosed unfixed issue found at $domain: $submission (disclosure: $disclosure)", VNag::VERBOSITY_SUMMARY);
169
                                        $this->setStatus(VNag::STATUS_CRITICAL);
170
                                } else {
171
                                        $sum_unfixed_pending++;
172
                                        $this->addVerboseMessage("Undisclosed unfixed issue found at $domain: $submission (disclosure: $disclosure)", VNag::VERBOSITY_SUMMARY);
173
                                        $this->setStatus(VNag::STATUS_WARNING);
174
                                }
175
                        }
176
                }
177
 
42 daniel-mar 178
                return array($sum_fixed, $sum_unfixed_pending, $sum_unfixed_disclosed, $sum_unfixed_ignored);
17 daniel-mar 179
        }
180
 
8 daniel-mar 181
        protected function cbRun($optional_args=array()) {
182
                $domain = $this->argDomain->getValue();
15 daniel-mar 183
                $privateapi = $this->argPrivateAPI->getValue();
184
 
185
                if (empty($domain) && empty($privateapi)) {
186
                        throw new Exception("Please specify a domain or subdomain, a list of domains, or a private API Url.");
8 daniel-mar 187
                }
188
 
15 daniel-mar 189
                if (!empty($domain) && !empty($privateapi)) {
190
                        throw new Exception("You can either use argument '-d' or '-p', but not both.");
191
                }
192
 
193
                if (!empty($privateapi)) {
194
                        // Possibility 1: Private API (showing all bugs for all of your domains, with detailled information)
195
                        //                https://www.openbugbounty.org/api/2/.../
196
                        $sum_fixed = 0;
197
                        $sum_unfixed_pending = 0;
198
                        $sum_unfixed_disclosed = 0;
42 daniel-mar 199
                        $sum_unfixed_ignored = 0;
200
                        list($sum_fixed, $sum_unfixed_pending, $sum_unfixed_disclosed, $sum_unfixed_ignored) = $this->num_open_bugs_v2($privateapi);
201
                        $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 202
                } else if (file_exists($domain)) {
203
                        // Possibility 2: File containing a list of domains
8 daniel-mar 204
                        $domains = file($domain);
205
                        $sum_fixed = 0;
206
                        $sum_unfixed = 0;
42 daniel-mar 207
                        $sum_unfixed_ignored = 0;
8 daniel-mar 208
                        $count = 0;
209
                        foreach ($domains as $domain) {
210
                                $domain = trim($domain);
211
                                if ($domain == '') continue;
212
                                if ($domain[0] == '#') continue;
42 daniel-mar 213
                                list($fixed, $unfixed, $unfixed_ignored) = $this->num_open_bugs_v1($domain);
8 daniel-mar 214
                                $sum_fixed += $fixed;
215
                                $sum_unfixed += $unfixed;
42 daniel-mar 216
                                $sum_unfixed_ignored += $unfixed_ignored;
8 daniel-mar 217
                                $count++;
42 daniel-mar 218
                                $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 219
                        }
42 daniel-mar 220
                        $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 221
                } else if (strpos($domain, ',') !== false) {
15 daniel-mar 222
                        // Possibility 3: Domains separated with comma
9 daniel-mar 223
                        $domains = explode(',', $domain);
224
                        $sum_fixed = 0;
225
                        $sum_unfixed = 0;
42 daniel-mar 226
                        $sum_unfixed_ignored = 0;
9 daniel-mar 227
                        $count = 0;
228
                        foreach ($domains as $domain) {
42 daniel-mar 229
                                list($fixed, $unfixed, $unfixed_ignored) = $this->num_open_bugs_v1($domain);
9 daniel-mar 230
                                $sum_fixed += $fixed;
231
                                $sum_unfixed += $unfixed;
42 daniel-mar 232
                                $sum_unfixed_ignored += $unfixed_ignored;
9 daniel-mar 233
                                $count++;
42 daniel-mar 234
                                $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 235
                        }
42 daniel-mar 236
                        $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 237
                } else {
15 daniel-mar 238
                        // Possibility 4: Single domain
42 daniel-mar 239
                        list($sum_fixed, $sum_unfixed, $sum_unfixed_ignored) = $this->num_open_bugs_v1($domain);
240
                        $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 241
                }
242
        }
243
}
9 daniel-mar 244