2016-02-29 03:57:10 +00:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Copyright © 2015 Brian Wolff
|
|
|
|
|
*
|
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
|
|
|
*
|
|
|
|
|
* @file
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
use MediaWiki\Logger\LoggerFactory;
|
2022-04-13 15:28:26 +00:00
|
|
|
use MediaWiki\MainConfigNames;
|
2023-02-16 12:36:41 +00:00
|
|
|
use MediaWiki\Request\ContentSecurityPolicy;
|
2020-02-13 10:24:10 +00:00
|
|
|
use Psr\Log\LoggerInterface;
|
2022-06-05 23:18:50 +00:00
|
|
|
use Wikimedia\ParamValidator\ParamValidator;
|
2016-02-29 03:57:10 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Api module to receive and log CSP violation reports
|
|
|
|
|
*
|
|
|
|
|
* @ingroup API
|
|
|
|
|
*/
|
|
|
|
|
class ApiCSPReport extends ApiBase {
|
|
|
|
|
|
2020-02-13 10:24:10 +00:00
|
|
|
/** @var LoggerInterface */
|
2016-02-29 03:57:10 +00:00
|
|
|
private $log;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* These reports should be small. Ignore super big reports out of paranoia
|
|
|
|
|
*/
|
2020-05-15 21:36:51 +00:00
|
|
|
private const MAX_POST_SIZE = 8192;
|
2016-02-29 03:57:10 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Logs a content-security-policy violation report from web browser.
|
|
|
|
|
*/
|
|
|
|
|
public function execute() {
|
|
|
|
|
$reportOnly = $this->getParameter( 'reportonly' );
|
|
|
|
|
$logname = $reportOnly ? 'csp-report-only' : 'csp';
|
|
|
|
|
$this->log = LoggerFactory::getInstance( $logname );
|
|
|
|
|
$userAgent = $this->getRequest()->getHeader( 'user-agent' );
|
|
|
|
|
|
|
|
|
|
$this->verifyPostBodyOk();
|
|
|
|
|
$report = $this->getReport();
|
2018-05-18 05:18:20 +00:00
|
|
|
$flags = $this->getFlags( $report, $userAgent );
|
2016-02-29 03:57:10 +00:00
|
|
|
|
|
|
|
|
$warningText = $this->generateLogLine( $flags, $report );
|
|
|
|
|
$this->logReport( $flags, $warningText, [
|
|
|
|
|
// XXX Is it ok to put untrusted data into log??
|
|
|
|
|
'csp-report' => $report,
|
|
|
|
|
'method' => __METHOD__,
|
2019-07-19 21:34:34 +00:00
|
|
|
'user_id' => $this->getUser()->getId() ?: 'logged-out',
|
2016-02-29 03:57:10 +00:00
|
|
|
'user-agent' => $userAgent,
|
|
|
|
|
'source' => $this->getParameter( 'source' ),
|
|
|
|
|
] );
|
|
|
|
|
$this->getResult()->addValue( null, $this->getModuleName(), 'success' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Log CSP report, with a different severity depending on $flags
|
2017-08-11 15:46:31 +00:00
|
|
|
* @param array $flags Flags for this report
|
|
|
|
|
* @param string $logLine text of log entry
|
|
|
|
|
* @param array $context logging context
|
2016-02-29 03:57:10 +00:00
|
|
|
*/
|
|
|
|
|
private function logReport( $flags, $logLine, $context ) {
|
|
|
|
|
if ( in_array( 'false-positive', $flags ) ) {
|
|
|
|
|
// These reports probably don't matter much
|
|
|
|
|
$this->log->debug( $logLine, $context );
|
|
|
|
|
} else {
|
|
|
|
|
// Normal report.
|
|
|
|
|
$this->log->warning( $logLine, $context );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get extra notes about the report.
|
|
|
|
|
*
|
2017-08-11 15:46:31 +00:00
|
|
|
* @param array $report The CSP report
|
2018-05-18 05:18:20 +00:00
|
|
|
* @param string $userAgent
|
2017-08-11 15:46:31 +00:00
|
|
|
* @return array
|
2016-02-29 03:57:10 +00:00
|
|
|
*/
|
2018-05-18 05:18:20 +00:00
|
|
|
private function getFlags( $report, $userAgent ) {
|
2016-02-29 03:57:10 +00:00
|
|
|
$reportOnly = $this->getParameter( 'reportonly' );
|
|
|
|
|
$source = $this->getParameter( 'source' );
|
2022-04-13 15:28:26 +00:00
|
|
|
$falsePositives = $this->getConfig()->get( MainConfigNames::CSPFalsePositiveUrls );
|
2016-02-29 03:57:10 +00:00
|
|
|
|
|
|
|
|
$flags = [];
|
|
|
|
|
if ( $source !== 'internal' ) {
|
|
|
|
|
$flags[] = 'source=' . $source;
|
|
|
|
|
}
|
|
|
|
|
if ( $reportOnly ) {
|
|
|
|
|
$flags[] = 'report-only';
|
|
|
|
|
}
|
2016-08-25 20:32:55 +00:00
|
|
|
|
|
|
|
|
if (
|
Initial support for Content Security Policy, disabled by default
The primary goal here is a defense in depth measure to
stop an attacker who found a bug in the parser allowing
them to insert malicious attributes.
This wouldn't stop someone who could insert a full
script tag (since at current it can't distinguish between
malicious and legit user js). It also would not prevent
DOM-based or reflected XSS for anons, as the nonce value
is guessable for anons when receiving a response cached
by varnish. However, the limited protection of just stopping
stored XSS where the attacker only has control of attributes,
is still a big win in my opinion. (But it wouldn't prevent
someone who has that type of xss from abusing things like
data-ooui attribute).
This will likely break many gadgets. Its expected that any
sort of rollout on Wikimedia will be done very slowly, with
lots of testing and the report-only option to begin with.
This is behind feature flags that are off by default, so
merging this patch should not cause any change in default
behaviour.
This may break some extensions (The most obvious one
is charinsert (See fe648d41005), but will probably need
some testing in report-only mode to see if anything else breaks)
This uses the unsafe-eval option of CSP, in order to
support RL's local storage thingy. For better security,
we may want to remove some of the sillier uses of eval
(e.g. jquery.ui.datepicker.js).
For more info, see spec: https://www.w3.org/TR/CSP2/
Additionally see:
https://www.mediawiki.org/wiki/Requests_for_comment/Content-Security-Policy
Bug: T135963
Change-Id: I80f6f469ba4c0b608385483457df96ccb7429ae5
2016-02-29 04:13:10 +00:00
|
|
|
(
|
|
|
|
|
ContentSecurityPolicy::falsePositiveBrowser( $userAgent ) &&
|
|
|
|
|
$report['blocked-uri'] === "self"
|
|
|
|
|
) ||
|
|
|
|
|
(
|
|
|
|
|
isset( $report['blocked-uri'] ) &&
|
2018-12-21 20:56:57 +00:00
|
|
|
$this->matchUrlPattern( $report['blocked-uri'], $falsePositives )
|
Initial support for Content Security Policy, disabled by default
The primary goal here is a defense in depth measure to
stop an attacker who found a bug in the parser allowing
them to insert malicious attributes.
This wouldn't stop someone who could insert a full
script tag (since at current it can't distinguish between
malicious and legit user js). It also would not prevent
DOM-based or reflected XSS for anons, as the nonce value
is guessable for anons when receiving a response cached
by varnish. However, the limited protection of just stopping
stored XSS where the attacker only has control of attributes,
is still a big win in my opinion. (But it wouldn't prevent
someone who has that type of xss from abusing things like
data-ooui attribute).
This will likely break many gadgets. Its expected that any
sort of rollout on Wikimedia will be done very slowly, with
lots of testing and the report-only option to begin with.
This is behind feature flags that are off by default, so
merging this patch should not cause any change in default
behaviour.
This may break some extensions (The most obvious one
is charinsert (See fe648d41005), but will probably need
some testing in report-only mode to see if anything else breaks)
This uses the unsafe-eval option of CSP, in order to
support RL's local storage thingy. For better security,
we may want to remove some of the sillier uses of eval
(e.g. jquery.ui.datepicker.js).
For more info, see spec: https://www.w3.org/TR/CSP2/
Additionally see:
https://www.mediawiki.org/wiki/Requests_for_comment/Content-Security-Policy
Bug: T135963
Change-Id: I80f6f469ba4c0b608385483457df96ccb7429ae5
2016-02-29 04:13:10 +00:00
|
|
|
) ||
|
|
|
|
|
(
|
|
|
|
|
isset( $report['source-file'] ) &&
|
2018-12-21 20:56:57 +00:00
|
|
|
$this->matchUrlPattern( $report['source-file'], $falsePositives )
|
Initial support for Content Security Policy, disabled by default
The primary goal here is a defense in depth measure to
stop an attacker who found a bug in the parser allowing
them to insert malicious attributes.
This wouldn't stop someone who could insert a full
script tag (since at current it can't distinguish between
malicious and legit user js). It also would not prevent
DOM-based or reflected XSS for anons, as the nonce value
is guessable for anons when receiving a response cached
by varnish. However, the limited protection of just stopping
stored XSS where the attacker only has control of attributes,
is still a big win in my opinion. (But it wouldn't prevent
someone who has that type of xss from abusing things like
data-ooui attribute).
This will likely break many gadgets. Its expected that any
sort of rollout on Wikimedia will be done very slowly, with
lots of testing and the report-only option to begin with.
This is behind feature flags that are off by default, so
merging this patch should not cause any change in default
behaviour.
This may break some extensions (The most obvious one
is charinsert (See fe648d41005), but will probably need
some testing in report-only mode to see if anything else breaks)
This uses the unsafe-eval option of CSP, in order to
support RL's local storage thingy. For better security,
we may want to remove some of the sillier uses of eval
(e.g. jquery.ui.datepicker.js).
For more info, see spec: https://www.w3.org/TR/CSP2/
Additionally see:
https://www.mediawiki.org/wiki/Requests_for_comment/Content-Security-Policy
Bug: T135963
Change-Id: I80f6f469ba4c0b608385483457df96ccb7429ae5
2016-02-29 04:13:10 +00:00
|
|
|
)
|
2016-08-25 20:32:55 +00:00
|
|
|
) {
|
Initial support for Content Security Policy, disabled by default
The primary goal here is a defense in depth measure to
stop an attacker who found a bug in the parser allowing
them to insert malicious attributes.
This wouldn't stop someone who could insert a full
script tag (since at current it can't distinguish between
malicious and legit user js). It also would not prevent
DOM-based or reflected XSS for anons, as the nonce value
is guessable for anons when receiving a response cached
by varnish. However, the limited protection of just stopping
stored XSS where the attacker only has control of attributes,
is still a big win in my opinion. (But it wouldn't prevent
someone who has that type of xss from abusing things like
data-ooui attribute).
This will likely break many gadgets. Its expected that any
sort of rollout on Wikimedia will be done very slowly, with
lots of testing and the report-only option to begin with.
This is behind feature flags that are off by default, so
merging this patch should not cause any change in default
behaviour.
This may break some extensions (The most obvious one
is charinsert (See fe648d41005), but will probably need
some testing in report-only mode to see if anything else breaks)
This uses the unsafe-eval option of CSP, in order to
support RL's local storage thingy. For better security,
we may want to remove some of the sillier uses of eval
(e.g. jquery.ui.datepicker.js).
For more info, see spec: https://www.w3.org/TR/CSP2/
Additionally see:
https://www.mediawiki.org/wiki/Requests_for_comment/Content-Security-Policy
Bug: T135963
Change-Id: I80f6f469ba4c0b608385483457df96ccb7429ae5
2016-02-29 04:13:10 +00:00
|
|
|
// False positive due to:
|
|
|
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1026520
|
|
|
|
|
|
2016-08-25 20:32:55 +00:00
|
|
|
$flags[] = 'false-positive';
|
|
|
|
|
}
|
2016-02-29 03:57:10 +00:00
|
|
|
return $flags;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-21 20:56:57 +00:00
|
|
|
/**
|
|
|
|
|
* @param string $url
|
|
|
|
|
* @param string[] $patterns
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
private function matchUrlPattern( $url, array $patterns ) {
|
|
|
|
|
if ( isset( $patterns[ $url ] ) ) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$bits = wfParseUrl( $url );
|
|
|
|
|
unset( $bits['user'], $bits['pass'], $bits['query'], $bits['fragment'] );
|
|
|
|
|
$bits['path'] = '';
|
|
|
|
|
$serverUrl = wfAssembleUrl( $bits );
|
|
|
|
|
if ( isset( $patterns[$serverUrl] ) ) {
|
|
|
|
|
// The origin of the url matches a pattern,
|
|
|
|
|
// e.g. "https://example.org" matches "https://example.org/foo/b?a#r"
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
foreach ( $patterns as $pattern => $val ) {
|
|
|
|
|
// We only use this pattern if it ends in a slash, this prevents
|
|
|
|
|
// "/foos" from matching "/foo", and "https://good.combo.bad" matching
|
|
|
|
|
// "https://good.com".
|
2022-12-12 18:54:24 +00:00
|
|
|
if ( str_ends_with( $pattern, '/' ) && str_starts_with( $url, $pattern ) ) {
|
2018-12-21 20:56:57 +00:00
|
|
|
// The pattern starts with the same as the url
|
|
|
|
|
// e.g. "https://example.org/foo/" matches "https://example.org/foo/b?a#r"
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-29 03:57:10 +00:00
|
|
|
/**
|
|
|
|
|
* Output an api error if post body is obviously not OK.
|
|
|
|
|
*/
|
|
|
|
|
private function verifyPostBodyOk() {
|
|
|
|
|
$req = $this->getRequest();
|
|
|
|
|
$contentType = $req->getHeader( 'content-type' );
|
|
|
|
|
if ( $contentType !== 'application/json'
|
2017-08-11 13:53:17 +00:00
|
|
|
&& $contentType !== 'application/csp-report'
|
2016-02-29 03:57:10 +00:00
|
|
|
) {
|
|
|
|
|
$this->error( 'wrongformat', __METHOD__ );
|
|
|
|
|
}
|
|
|
|
|
if ( $req->getHeader( 'content-length' ) > self::MAX_POST_SIZE ) {
|
|
|
|
|
$this->error( 'toobig', __METHOD__ );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the report from post body and turn into associative array.
|
|
|
|
|
*
|
2018-05-18 05:18:20 +00:00
|
|
|
* @return array
|
2016-02-29 03:57:10 +00:00
|
|
|
*/
|
|
|
|
|
private function getReport() {
|
|
|
|
|
$postBody = $this->getRequest()->getRawInput();
|
|
|
|
|
if ( strlen( $postBody ) > self::MAX_POST_SIZE ) {
|
|
|
|
|
// paranoia, already checked content-length earlier.
|
|
|
|
|
$this->error( 'toobig', __METHOD__ );
|
|
|
|
|
}
|
|
|
|
|
$status = FormatJson::parse( $postBody, FormatJson::FORCE_ASSOC );
|
|
|
|
|
if ( !$status->isGood() ) {
|
2016-10-19 16:54:25 +00:00
|
|
|
$msg = $status->getErrors()[0]['message'];
|
|
|
|
|
if ( $msg instanceof Message ) {
|
|
|
|
|
$msg = $msg->getKey();
|
|
|
|
|
}
|
|
|
|
|
$this->error( $msg, __METHOD__ );
|
2016-02-29 03:57:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$report = $status->getValue();
|
|
|
|
|
|
|
|
|
|
if ( !isset( $report['csp-report'] ) ) {
|
|
|
|
|
$this->error( 'missingkey', __METHOD__ );
|
|
|
|
|
}
|
|
|
|
|
return $report['csp-report'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get text of log line.
|
|
|
|
|
*
|
2017-08-11 15:46:31 +00:00
|
|
|
* @param array $flags of additional markers for this report
|
|
|
|
|
* @param array $report the csp report
|
|
|
|
|
* @return string Text to put in log
|
2016-02-29 03:57:10 +00:00
|
|
|
*/
|
|
|
|
|
private function generateLogLine( $flags, $report ) {
|
|
|
|
|
$flagText = '';
|
|
|
|
|
if ( $flags ) {
|
2018-02-22 19:24:00 +00:00
|
|
|
$flagText = '[' . implode( ', ', $flags ) . ']';
|
2016-02-29 03:57:10 +00:00
|
|
|
}
|
|
|
|
|
|
ApiCSPReport: Log user ID instead of name, and limit urls to origin
These reports often contain false-positives from gadgets and
browser extensions that use a cross-domain requests for retreiving
information from a web API. (E.g. not for fetching executable JS
code, or for sending data elsewhere.)
Those API requests aren't static like "/foo.js?v2" but rather
dynamic, like /query/input+from+user, containing information about
what the user was reading, who or what they interacted with on
the wiki and/or text they entered or selected specifically.
(e.g. investigating user behaviour, counter-vandalism,
Google Translate tools, WHOIS gadgets, etc.)
Details of such action don't need to be recorded, and shown on
Logstash dashboards by default in the 'message' field. In fact,
I don't think it is needed for anything by default. If there's a
security problem, I imagine the origin suffices for a CSP block
and/or to start investigating.
Same for the user name. I don't want to see "[enwiki] John, referer
/wiki/Topic_read, chrome-extension/xyz, vandal-query.org/George".
These now log: "[enwiki] user_id 123, referer /wiki/Topic_read,
chrome-extension/xyz, vandal-query.org"
The user name still available when purposely investigating (via
public tools) by resolving the user ID.
Bug: T207900
Change-Id: Ic9855400c8cfedfa92b6659a4ad29c4dc28fb256
2019-04-16 23:32:30 +00:00
|
|
|
$blockedOrigin = isset( $report['blocked-uri'] )
|
|
|
|
|
? $this->originFromUrl( $report['blocked-uri'] )
|
|
|
|
|
: 'n/a';
|
2017-10-06 22:17:58 +00:00
|
|
|
$page = $report['document-uri'] ?? 'n/a';
|
ApiCSPReport: Log user ID instead of name, and limit urls to origin
These reports often contain false-positives from gadgets and
browser extensions that use a cross-domain requests for retreiving
information from a web API. (E.g. not for fetching executable JS
code, or for sending data elsewhere.)
Those API requests aren't static like "/foo.js?v2" but rather
dynamic, like /query/input+from+user, containing information about
what the user was reading, who or what they interacted with on
the wiki and/or text they entered or selected specifically.
(e.g. investigating user behaviour, counter-vandalism,
Google Translate tools, WHOIS gadgets, etc.)
Details of such action don't need to be recorded, and shown on
Logstash dashboards by default in the 'message' field. In fact,
I don't think it is needed for anything by default. If there's a
security problem, I imagine the origin suffices for a CSP block
and/or to start investigating.
Same for the user name. I don't want to see "[enwiki] John, referer
/wiki/Topic_read, chrome-extension/xyz, vandal-query.org/George".
These now log: "[enwiki] user_id 123, referer /wiki/Topic_read,
chrome-extension/xyz, vandal-query.org"
The user name still available when purposely investigating (via
public tools) by resolving the user ID.
Bug: T207900
Change-Id: Ic9855400c8cfedfa92b6659a4ad29c4dc28fb256
2019-04-16 23:32:30 +00:00
|
|
|
$line = isset( $report['line-number'] )
|
|
|
|
|
? ':' . $report['line-number']
|
|
|
|
|
: '';
|
2022-08-14 01:10:31 +00:00
|
|
|
return $flagText .
|
ApiCSPReport: Log user ID instead of name, and limit urls to origin
These reports often contain false-positives from gadgets and
browser extensions that use a cross-domain requests for retreiving
information from a web API. (E.g. not for fetching executable JS
code, or for sending data elsewhere.)
Those API requests aren't static like "/foo.js?v2" but rather
dynamic, like /query/input+from+user, containing information about
what the user was reading, who or what they interacted with on
the wiki and/or text they entered or selected specifically.
(e.g. investigating user behaviour, counter-vandalism,
Google Translate tools, WHOIS gadgets, etc.)
Details of such action don't need to be recorded, and shown on
Logstash dashboards by default in the 'message' field. In fact,
I don't think it is needed for anything by default. If there's a
security problem, I imagine the origin suffices for a CSP block
and/or to start investigating.
Same for the user name. I don't want to see "[enwiki] John, referer
/wiki/Topic_read, chrome-extension/xyz, vandal-query.org/George".
These now log: "[enwiki] user_id 123, referer /wiki/Topic_read,
chrome-extension/xyz, vandal-query.org"
The user name still available when purposely investigating (via
public tools) by resolving the user ID.
Bug: T207900
Change-Id: Ic9855400c8cfedfa92b6659a4ad29c4dc28fb256
2019-04-16 23:32:30 +00:00
|
|
|
' Received CSP report: <' . $blockedOrigin . '>' .
|
|
|
|
|
' blocked from being loaded on <' . $page . '>' . $line;
|
2016-02-29 03:57:10 +00:00
|
|
|
}
|
|
|
|
|
|
ApiCSPReport: Log user ID instead of name, and limit urls to origin
These reports often contain false-positives from gadgets and
browser extensions that use a cross-domain requests for retreiving
information from a web API. (E.g. not for fetching executable JS
code, or for sending data elsewhere.)
Those API requests aren't static like "/foo.js?v2" but rather
dynamic, like /query/input+from+user, containing information about
what the user was reading, who or what they interacted with on
the wiki and/or text they entered or selected specifically.
(e.g. investigating user behaviour, counter-vandalism,
Google Translate tools, WHOIS gadgets, etc.)
Details of such action don't need to be recorded, and shown on
Logstash dashboards by default in the 'message' field. In fact,
I don't think it is needed for anything by default. If there's a
security problem, I imagine the origin suffices for a CSP block
and/or to start investigating.
Same for the user name. I don't want to see "[enwiki] John, referer
/wiki/Topic_read, chrome-extension/xyz, vandal-query.org/George".
These now log: "[enwiki] user_id 123, referer /wiki/Topic_read,
chrome-extension/xyz, vandal-query.org"
The user name still available when purposely investigating (via
public tools) by resolving the user ID.
Bug: T207900
Change-Id: Ic9855400c8cfedfa92b6659a4ad29c4dc28fb256
2019-04-16 23:32:30 +00:00
|
|
|
/**
|
|
|
|
|
* @param string $url
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
|
|
|
|
private function originFromUrl( $url ) {
|
|
|
|
|
$bits = wfParseUrl( $url );
|
|
|
|
|
unset( $bits['user'], $bits['pass'], $bits['query'], $bits['fragment'] );
|
|
|
|
|
$bits['path'] = '';
|
|
|
|
|
// e.g. "https://example.org" from "https://example.org/foo/b?a#r"
|
2022-08-14 01:10:31 +00:00
|
|
|
return wfAssembleUrl( $bits );
|
ApiCSPReport: Log user ID instead of name, and limit urls to origin
These reports often contain false-positives from gadgets and
browser extensions that use a cross-domain requests for retreiving
information from a web API. (E.g. not for fetching executable JS
code, or for sending data elsewhere.)
Those API requests aren't static like "/foo.js?v2" but rather
dynamic, like /query/input+from+user, containing information about
what the user was reading, who or what they interacted with on
the wiki and/or text they entered or selected specifically.
(e.g. investigating user behaviour, counter-vandalism,
Google Translate tools, WHOIS gadgets, etc.)
Details of such action don't need to be recorded, and shown on
Logstash dashboards by default in the 'message' field. In fact,
I don't think it is needed for anything by default. If there's a
security problem, I imagine the origin suffices for a CSP block
and/or to start investigating.
Same for the user name. I don't want to see "[enwiki] John, referer
/wiki/Topic_read, chrome-extension/xyz, vandal-query.org/George".
These now log: "[enwiki] user_id 123, referer /wiki/Topic_read,
chrome-extension/xyz, vandal-query.org"
The user name still available when purposely investigating (via
public tools) by resolving the user ID.
Bug: T207900
Change-Id: Ic9855400c8cfedfa92b6659a4ad29c4dc28fb256
2019-04-16 23:32:30 +00:00
|
|
|
}
|
|
|
|
|
|
2016-02-29 03:57:10 +00:00
|
|
|
/**
|
|
|
|
|
* Stop processing the request, and output/log an error
|
|
|
|
|
*
|
2017-08-11 15:46:31 +00:00
|
|
|
* @param string $code error code
|
|
|
|
|
* @param string $method method that made error
|
2016-10-19 16:54:25 +00:00
|
|
|
* @throws ApiUsageException Always
|
2016-02-29 03:57:10 +00:00
|
|
|
*/
|
|
|
|
|
private function error( $code, $method ) {
|
|
|
|
|
$this->log->info( 'Error reading CSP report: ' . $code, [
|
|
|
|
|
'method' => $method,
|
|
|
|
|
'user-agent' => $this->getRequest()->getHeader( 'user-agent' )
|
|
|
|
|
] );
|
2017-05-26 10:19:56 +00:00
|
|
|
// Return 400 on error for user agents to display, e.g. to the console.
|
2016-10-19 16:54:25 +00:00
|
|
|
$this->dieWithError(
|
2017-05-26 10:19:56 +00:00
|
|
|
[ 'apierror-csp-report', wfEscapeWikiText( $code ) ], 'cspreport-' . $code, [], 400
|
2016-10-19 16:54:25 +00:00
|
|
|
);
|
2016-02-29 03:57:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getAllowedParams() {
|
|
|
|
|
return [
|
|
|
|
|
'reportonly' => [
|
2022-06-05 23:18:50 +00:00
|
|
|
ParamValidator::PARAM_TYPE => 'boolean',
|
|
|
|
|
ParamValidator::PARAM_DEFAULT => false
|
2016-02-29 03:57:10 +00:00
|
|
|
],
|
|
|
|
|
'source' => [
|
2022-06-05 23:18:50 +00:00
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_DEFAULT => 'internal',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => false
|
2016-02-29 03:57:10 +00:00
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function mustBePosted() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Mark as internal. This isn't meant to be used by normal api users
|
2017-09-09 20:47:04 +00:00
|
|
|
* @return bool
|
2016-02-29 03:57:10 +00:00
|
|
|
*/
|
|
|
|
|
public function isInternal() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Even if you don't have read rights, we still want your report.
|
2017-09-09 20:47:04 +00:00
|
|
|
* @return bool
|
2016-02-29 03:57:10 +00:00
|
|
|
*/
|
|
|
|
|
public function isReadMode() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2021-12-26 11:13:47 +00:00
|
|
|
* Doesn't touch db, so max lag should be rather irrelevant.
|
2016-02-29 03:57:10 +00:00
|
|
|
*
|
|
|
|
|
* Also, this makes sure that reports aren't lost during lag events.
|
2017-09-09 20:47:04 +00:00
|
|
|
* @return bool
|
2016-02-29 03:57:10 +00:00
|
|
|
*/
|
|
|
|
|
public function shouldCheckMaxLag() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|