wikimedia /
mediawiki
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * This program is free software; you can redistribute it and/or modify |
||
| 4 | * it under the terms of the GNU General Public License as published by |
||
| 5 | * the Free Software Foundation; either version 2 of the License, or |
||
| 6 | * (at your option) any later version. |
||
| 7 | * |
||
| 8 | * This program is distributed in the hope that it will be useful, |
||
| 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 11 | * GNU General Public License for more details. |
||
| 12 | * |
||
| 13 | * You should have received a copy of the GNU General Public License along |
||
| 14 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
| 15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
| 16 | * http://www.gnu.org/copyleft/gpl.html |
||
| 17 | * |
||
| 18 | * @file |
||
| 19 | */ |
||
| 20 | |||
| 21 | class PhpHttpRequest extends MWHttpRequest { |
||
| 22 | |||
| 23 | private $fopenErrors = []; |
||
| 24 | |||
| 25 | /** |
||
| 26 | * @param string $url |
||
| 27 | * @return string |
||
| 28 | */ |
||
| 29 | protected function urlToTcp( $url ) { |
||
| 30 | $parsedUrl = parse_url( $url ); |
||
| 31 | |||
| 32 | return 'tcp://' . $parsedUrl['host'] . ':' . $parsedUrl['port']; |
||
| 33 | } |
||
| 34 | |||
| 35 | /** |
||
| 36 | * Returns an array with a 'capath' or 'cafile' key |
||
| 37 | * that is suitable to be merged into the 'ssl' sub-array of |
||
| 38 | * a stream context options array. |
||
| 39 | * Uses the 'caInfo' option of the class if it is provided, otherwise uses the system |
||
| 40 | * default CA bundle if PHP supports that, or searches a few standard locations. |
||
| 41 | * @return array |
||
| 42 | * @throws DomainException |
||
| 43 | */ |
||
| 44 | protected function getCertOptions() { |
||
| 45 | $certOptions = []; |
||
| 46 | $certLocations = []; |
||
| 47 | if ( $this->caInfo ) { |
||
| 48 | $certLocations = [ 'manual' => $this->caInfo ]; |
||
| 49 | } elseif ( version_compare( PHP_VERSION, '5.6.0', '<' ) ) { |
||
| 50 | // @codingStandardsIgnoreStart Generic.Files.LineLength |
||
| 51 | // Default locations, based on |
||
| 52 | // https://www.happyassassin.net/2015/01/12/a-note-about-ssltls-trusted-certificate-stores-and-platforms/ |
||
| 53 | // PHP 5.5 and older doesn't have any defaults, so we try to guess ourselves. |
||
| 54 | // PHP 5.6+ gets the CA location from OpenSSL as long as it is not set manually, |
||
| 55 | // so we should leave capath/cafile empty there. |
||
| 56 | // @codingStandardsIgnoreEnd |
||
| 57 | $certLocations = array_filter( [ |
||
| 58 | getenv( 'SSL_CERT_DIR' ), |
||
| 59 | getenv( 'SSL_CERT_PATH' ), |
||
| 60 | '/etc/pki/tls/certs/ca-bundle.crt', # Fedora et al |
||
| 61 | '/etc/ssl/certs', # Debian et al |
||
| 62 | '/etc/pki/tls/certs/ca-bundle.trust.crt', |
||
| 63 | '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem', |
||
| 64 | '/System/Library/OpenSSL', # OSX |
||
| 65 | ] ); |
||
| 66 | } |
||
| 67 | |||
| 68 | foreach ( $certLocations as $key => $cert ) { |
||
| 69 | if ( is_dir( $cert ) ) { |
||
| 70 | $certOptions['capath'] = $cert; |
||
| 71 | break; |
||
| 72 | } elseif ( is_file( $cert ) ) { |
||
| 73 | $certOptions['cafile'] = $cert; |
||
| 74 | break; |
||
| 75 | } elseif ( $key === 'manual' ) { |
||
| 76 | // fail more loudly if a cert path was manually configured and it is not valid |
||
| 77 | throw new DomainException( "Invalid CA info passed: $cert" ); |
||
| 78 | } |
||
| 79 | } |
||
| 80 | |||
| 81 | return $certOptions; |
||
| 82 | } |
||
| 83 | |||
| 84 | /** |
||
| 85 | * Custom error handler for dealing with fopen() errors. |
||
| 86 | * fopen() tends to fire multiple errors in succession, and the last one |
||
| 87 | * is completely useless (something like "fopen: failed to open stream") |
||
| 88 | * so normal methods of handling errors programmatically |
||
| 89 | * like get_last_error() don't work. |
||
| 90 | */ |
||
| 91 | public function errorHandler( $errno, $errstr ) { |
||
| 92 | $n = count( $this->fopenErrors ) + 1; |
||
| 93 | $this->fopenErrors += [ "errno$n" => $errno, "errstr$n" => $errstr ]; |
||
| 94 | } |
||
| 95 | |||
| 96 | public function execute() { |
||
| 97 | |||
| 98 | parent::execute(); |
||
| 99 | |||
| 100 | if ( is_array( $this->postData ) ) { |
||
| 101 | $this->postData = wfArrayToCgi( $this->postData ); |
||
| 102 | } |
||
| 103 | |||
| 104 | if ( $this->parsedUrl['scheme'] != 'http' |
||
| 105 | && $this->parsedUrl['scheme'] != 'https' ) { |
||
| 106 | $this->status->fatal( 'http-invalid-scheme', $this->parsedUrl['scheme'] ); |
||
| 107 | } |
||
| 108 | |||
| 109 | $this->reqHeaders['Accept'] = "*/*"; |
||
| 110 | $this->reqHeaders['Connection'] = 'Close'; |
||
| 111 | if ( $this->method == 'POST' ) { |
||
| 112 | // Required for HTTP 1.0 POSTs |
||
| 113 | $this->reqHeaders['Content-Length'] = strlen( $this->postData ); |
||
| 114 | if ( !isset( $this->reqHeaders['Content-Type'] ) ) { |
||
| 115 | $this->reqHeaders['Content-Type'] = "application/x-www-form-urlencoded"; |
||
| 116 | } |
||
| 117 | } |
||
| 118 | |||
| 119 | // Set up PHP stream context |
||
| 120 | $options = [ |
||
| 121 | 'http' => [ |
||
| 122 | 'method' => $this->method, |
||
| 123 | 'header' => implode( "\r\n", $this->getHeaderList() ), |
||
| 124 | 'protocol_version' => '1.1', |
||
| 125 | 'max_redirects' => $this->followRedirects ? $this->maxRedirects : 0, |
||
| 126 | 'ignore_errors' => true, |
||
| 127 | 'timeout' => $this->timeout, |
||
| 128 | // Curl options in case curlwrappers are installed |
||
| 129 | 'curl_verify_ssl_host' => $this->sslVerifyHost ? 2 : 0, |
||
| 130 | 'curl_verify_ssl_peer' => $this->sslVerifyCert, |
||
| 131 | ], |
||
| 132 | 'ssl' => [ |
||
| 133 | 'verify_peer' => $this->sslVerifyCert, |
||
| 134 | 'SNI_enabled' => true, |
||
| 135 | 'ciphers' => 'HIGH:!SSLv2:!SSLv3:-ADH:-kDH:-kECDH:-DSS', |
||
| 136 | 'disable_compression' => true, |
||
| 137 | ], |
||
| 138 | ]; |
||
| 139 | |||
| 140 | if ( $this->proxy ) { |
||
| 141 | $options['http']['proxy'] = $this->urlToTcp( $this->proxy ); |
||
| 142 | $options['http']['request_fulluri'] = true; |
||
| 143 | } |
||
| 144 | |||
| 145 | if ( $this->postData ) { |
||
| 146 | $options['http']['content'] = $this->postData; |
||
| 147 | } |
||
| 148 | |||
| 149 | if ( $this->sslVerifyHost ) { |
||
| 150 | // PHP 5.6.0 deprecates CN_match, in favour of peer_name which |
||
| 151 | // actually checks SubjectAltName properly. |
||
| 152 | if ( version_compare( PHP_VERSION, '5.6.0', '>=' ) ) { |
||
| 153 | $options['ssl']['peer_name'] = $this->parsedUrl['host']; |
||
| 154 | } else { |
||
| 155 | $options['ssl']['CN_match'] = $this->parsedUrl['host']; |
||
| 156 | } |
||
| 157 | } |
||
| 158 | |||
| 159 | $options['ssl'] += $this->getCertOptions(); |
||
| 160 | |||
| 161 | $context = stream_context_create( $options ); |
||
| 162 | |||
| 163 | $this->headerList = []; |
||
| 164 | $reqCount = 0; |
||
| 165 | $url = $this->url; |
||
| 166 | |||
| 167 | $result = []; |
||
| 168 | |||
| 169 | if ( $this->profiler ) { |
||
| 170 | $profileSection = $this->profiler->scopedProfileIn( |
||
| 171 | __METHOD__ . '-' . $this->profileName |
||
| 172 | ); |
||
| 173 | } |
||
| 174 | do { |
||
| 175 | $reqCount++; |
||
| 176 | $this->fopenErrors = []; |
||
| 177 | set_error_handler( [ $this, 'errorHandler' ] ); |
||
| 178 | $fh = fopen( $url, "r", false, $context ); |
||
| 179 | restore_error_handler(); |
||
| 180 | |||
| 181 | if ( !$fh ) { |
||
| 182 | // HACK for instant commons. |
||
| 183 | // If we are contacting (commons|upload).wikimedia.org |
||
| 184 | // try again with CN_match for en.wikipedia.org |
||
| 185 | // as php does not handle SubjectAltName properly |
||
| 186 | // prior to "peer_name" option in php 5.6 |
||
| 187 | if ( isset( $options['ssl']['CN_match'] ) |
||
| 188 | && ( $options['ssl']['CN_match'] === 'commons.wikimedia.org' |
||
| 189 | || $options['ssl']['CN_match'] === 'upload.wikimedia.org' ) |
||
| 190 | ) { |
||
| 191 | $options['ssl']['CN_match'] = 'en.wikipedia.org'; |
||
| 192 | $context = stream_context_create( $options ); |
||
| 193 | continue; |
||
| 194 | } |
||
| 195 | break; |
||
| 196 | } |
||
| 197 | |||
| 198 | $result = stream_get_meta_data( $fh ); |
||
| 199 | $this->headerList = $result['wrapper_data']; |
||
| 200 | $this->parseHeader(); |
||
| 201 | |||
| 202 | if ( !$this->followRedirects ) { |
||
| 203 | break; |
||
| 204 | } |
||
| 205 | |||
| 206 | # Handle manual redirection |
||
| 207 | if ( !$this->isRedirect() || $reqCount > $this->maxRedirects ) { |
||
| 208 | break; |
||
| 209 | } |
||
| 210 | # Check security of URL |
||
| 211 | $url = $this->getResponseHeader( "Location" ); |
||
| 212 | |||
| 213 | if ( !Http::isValidURI( $url ) ) { |
||
| 214 | $this->logger->debug( __METHOD__ . ": insecure redirection\n" ); |
||
| 215 | break; |
||
| 216 | } |
||
| 217 | } while ( true ); |
||
| 218 | if ( $this->profiler ) { |
||
| 219 | $this->profiler->scopedProfileOut( $profileSection ); |
||
|
0 ignored issues
–
show
|
|||
| 220 | } |
||
| 221 | |||
| 222 | $this->setStatus(); |
||
| 223 | |||
| 224 | if ( $fh === false ) { |
||
| 225 | if ( $this->fopenErrors ) { |
||
| 226 | $this->logger->warning( __CLASS__ |
||
| 227 | . ': error opening connection: {errstr1}', $this->fopenErrors ); |
||
| 228 | } |
||
| 229 | $this->status->fatal( 'http-request-error' ); |
||
| 230 | return $this->status; |
||
| 231 | } |
||
| 232 | |||
| 233 | if ( $result['timed_out'] ) { |
||
| 234 | $this->status->fatal( 'http-timed-out', $this->url ); |
||
| 235 | return $this->status; |
||
| 236 | } |
||
| 237 | |||
| 238 | // If everything went OK, or we received some error code |
||
| 239 | // get the response body content. |
||
| 240 | if ( $this->status->isOK() || (int)$this->respStatus >= 300 ) { |
||
| 241 | while ( !feof( $fh ) ) { |
||
| 242 | $buf = fread( $fh, 8192 ); |
||
| 243 | |||
| 244 | if ( $buf === false ) { |
||
| 245 | $this->status->fatal( 'http-read-error' ); |
||
| 246 | break; |
||
| 247 | } |
||
| 248 | |||
| 249 | if ( strlen( $buf ) ) { |
||
| 250 | call_user_func( $this->callback, $fh, $buf ); |
||
| 251 | } |
||
| 252 | } |
||
| 253 | } |
||
| 254 | fclose( $fh ); |
||
| 255 | |||
| 256 | return $this->status; |
||
| 257 | } |
||
| 258 | } |
||
| 259 |
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.