Subversion Repositories vnag

Rev

Rev 59 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

  1. <?php /* <ViaThinkSoftSignature>
  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==
  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.  *
  32.  * Revision 2023-10-13
  33.  */
  34.  
  35. declare(ticks=1);
  36.  
  37. class OpenBugBountyCheck extends VNag {
  38.         protected $argDomain = null;
  39.         protected $argPrivateAPI = null;
  40.         protected $argIgnoredIds = null;
  41.  
  42.         public function __construct() {
  43.                 parent::__construct();
  44.  
  45.                 $this->registerExpectedStandardArguments('Vvht');
  46.  
  47.                 $this->getHelpManager()->setPluginName('check_openbugbounty');
  48.                 $this->getHelpManager()->setVersion('1.1');
  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.');
  51.                 $this->getHelpManager()->setSyntax('$SCRIPTNAME$ [-d <SingleDomain[,SingleDomain,[...]]> | -d <DomainListFile> | -p <PrivateApiUrl> | -i <IgnoredId,IgnoredId,...> ]');
  52.                 $this->getHelpManager()->setFootNotes('If you encounter bugs, please contact ViaThinkSoft at www.viathinksoft.com');
  53.  
  54.                 // Individual (non-standard) arguments:
  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.'));
  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\'.'));
  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...)'));
  58.         }
  59.  
  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;
  86.                 $unfixed_ignored = 0;
  87.  
  88.                 $this->setStatus(VNag::STATUS_OK);
  89.  
  90.                 $domain = strtolower($domain);
  91.                 $cont = $this->url_get_contents('https://www.openbugbounty.org/api/1/search/?domain='.urlencode($domain));
  92.  
  93.                 $xml = simplexml_load_string($cont);
  94.                 foreach ($xml as $x) {
  95.                         $submission = $x->url;
  96.  
  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 {
  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.  
  113.                 }
  114.  
  115.                 return array($fixed, $unfixed, $unfixed_ignored);
  116.         }
  117.  
  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;
  125.                 $sum_unfixed_ignored = 0;
  126.  
  127.                 $this->setStatus(VNag::STATUS_OK);
  128.  
  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.  
  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++;
  156.                                 $fixed_date = $data['Vulnerability Fixed'];
  157.                                 $this->addVerboseMessage("Fixed issue found at $domain: $submission (fixed: $fixed_date)", VNag::VERBOSITY_ADDITIONAL_INFORMATION);
  158.                                 $this->setStatus(VNag::STATUS_OK);
  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);
  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.  
  178.                 return array($sum_fixed, $sum_unfixed_pending, $sum_unfixed_disclosed, $sum_unfixed_ignored);
  179.         }
  180.  
  181.         protected function cbRun($optional_args=array()) {
  182.                 $domain = $this->argDomain->getValue();
  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.");
  187.                 }
  188.  
  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;
  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);
  202.                 } else if (file_exists($domain)) {
  203.                         // Possibility 2: File containing a list of domains
  204.                         $domains = file($domain);
  205.                         $sum_fixed = 0;
  206.                         $sum_unfixed = 0;
  207.                         $sum_unfixed_ignored = 0;
  208.                         $count = 0;
  209.                         foreach ($domains as $domain) {
  210.                                 $domain = trim($domain);
  211.                                 if ($domain == '') continue;
  212.                                 if ($domain[0] == '#') continue;
  213.                                 list($fixed, $unfixed, $unfixed_ignored) = $this->num_open_bugs_v1($domain);
  214.                                 $sum_fixed += $fixed;
  215.                                 $sum_unfixed += $unfixed;
  216.                                 $sum_unfixed_ignored += $unfixed_ignored;
  217.                                 $count++;
  218.                                 $this->addVerboseMessage("$fixed fixed, $unfixed_ignored ignored, and $unfixed unfixed issues found at $domain", $unfixed > 0 ? VNag::VERBOSITY_SUMMARY : VNag::VERBOSITY_ADDITIONAL_INFORMATION);
  219.                         }
  220.                         $this->setHeadline("$sum_fixed fixed and ".($sum_unfixed + $sum_unfixed_ignored)." unfixed (including $sum_unfixed_ignored ignored) issues found at $count domains", true);
  221.                 } else if (strpos($domain, ',') !== false) {
  222.                         // Possibility 3: Domains separated with comma
  223.                         $domains = explode(',', $domain);
  224.                         $sum_fixed = 0;
  225.                         $sum_unfixed = 0;
  226.                         $sum_unfixed_ignored = 0;
  227.                         $count = 0;
  228.                         foreach ($domains as $domain) {
  229.                                 list($fixed, $unfixed, $unfixed_ignored) = $this->num_open_bugs_v1($domain);
  230.                                 $sum_fixed += $fixed;
  231.                                 $sum_unfixed += $unfixed;
  232.                                 $sum_unfixed_ignored += $unfixed_ignored;
  233.                                 $count++;
  234.                                 $this->addVerboseMessage("$fixed fixed, $unfixed_ignored ignored,  and $unfixed unfixed issues found at $domain", $unfixed > 0 ? VNag::VERBOSITY_SUMMARY : VNag::VERBOSITY_ADDITIONAL_INFORMATION);
  235.                         }
  236.                         $this->setHeadline("$sum_fixed fixed and ".($sum_unfixed + $sum_unfixed_ignored)." unfixed (including $sum_unfixed_ignored ignored) issues found at $count domains", true);
  237.                 } else {
  238.                         // Possibility 4: Single domain
  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);
  241.                 }
  242.         }
  243. }
  244.  
  245.