introduce brute force protection for api calls
Signed-off-by: Bjoern Schiessle <bjoern@schiessle.org>
This commit is contained in:
parent
4bbd52b3f9
commit
df296249d6
|
@ -43,8 +43,10 @@ use OC\AppFramework\Middleware\OCSMiddleware;
|
||||||
use OC\AppFramework\Middleware\Security\SecurityMiddleware;
|
use OC\AppFramework\Middleware\Security\SecurityMiddleware;
|
||||||
use OC\AppFramework\Middleware\SessionMiddleware;
|
use OC\AppFramework\Middleware\SessionMiddleware;
|
||||||
use OC\AppFramework\Utility\SimpleContainer;
|
use OC\AppFramework\Utility\SimpleContainer;
|
||||||
|
use OC\AppFramework\Utility\TimeFactory;
|
||||||
use OC\Core\Middleware\TwoFactorMiddleware;
|
use OC\Core\Middleware\TwoFactorMiddleware;
|
||||||
use OC\RichObjectStrings\Validator;
|
use OC\RichObjectStrings\Validator;
|
||||||
|
use OC\Security\Bruteforce\Throttler;
|
||||||
use OCP\AppFramework\IApi;
|
use OCP\AppFramework\IApi;
|
||||||
use OCP\AppFramework\IAppContainer;
|
use OCP\AppFramework\IAppContainer;
|
||||||
use OCP\Files\IAppData;
|
use OCP\Files\IAppData;
|
||||||
|
@ -376,20 +378,25 @@ class DIContainer extends SimpleContainer implements IAppContainer {
|
||||||
*/
|
*/
|
||||||
$app = $this;
|
$app = $this;
|
||||||
$this->registerService('SecurityMiddleware', function($c) use ($app){
|
$this->registerService('SecurityMiddleware', function($c) use ($app){
|
||||||
|
/** @var \OC\Server $server */
|
||||||
|
$server = $app->getServer();
|
||||||
|
|
||||||
return new SecurityMiddleware(
|
return new SecurityMiddleware(
|
||||||
$c['Request'],
|
$c['Request'],
|
||||||
$c['ControllerMethodReflector'],
|
$c['ControllerMethodReflector'],
|
||||||
$app->getServer()->getNavigationManager(),
|
$server->getNavigationManager(),
|
||||||
$app->getServer()->getURLGenerator(),
|
$server->getURLGenerator(),
|
||||||
$app->getServer()->getLogger(),
|
$server->getLogger(),
|
||||||
$app->getServer()->getSession(),
|
$server->getSession(),
|
||||||
$c['AppName'],
|
$c['AppName'],
|
||||||
$app->isLoggedIn(),
|
$app->isLoggedIn(),
|
||||||
$app->isAdminUser(),
|
$app->isAdminUser(),
|
||||||
$app->getServer()->getContentSecurityPolicyManager(),
|
$server->getContentSecurityPolicyManager(),
|
||||||
$app->getServer()->getCsrfTokenManager(),
|
$server->getCsrfTokenManager(),
|
||||||
$app->getServer()->getContentSecurityPolicyNonceManager()
|
$server->getContentSecurityPolicyNonceManager(),
|
||||||
|
$server->getBruteForceThrottler()
|
||||||
);
|
);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->registerService('CORSMiddleware', function($c) {
|
$this->registerService('CORSMiddleware', function($c) {
|
||||||
|
|
|
@ -36,6 +36,7 @@ use OC\AppFramework\Middleware\Security\Exceptions\NotConfirmedException;
|
||||||
use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
|
use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
|
||||||
use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException;
|
use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException;
|
||||||
use OC\AppFramework\Utility\ControllerMethodReflector;
|
use OC\AppFramework\Utility\ControllerMethodReflector;
|
||||||
|
use OC\Security\Bruteforce\Throttler;
|
||||||
use OC\Security\CSP\ContentSecurityPolicyManager;
|
use OC\Security\CSP\ContentSecurityPolicyManager;
|
||||||
use OC\Security\CSP\ContentSecurityPolicyNonceManager;
|
use OC\Security\CSP\ContentSecurityPolicyNonceManager;
|
||||||
use OC\Security\CSRF\CsrfTokenManager;
|
use OC\Security\CSRF\CsrfTokenManager;
|
||||||
|
@ -87,6 +88,8 @@ class SecurityMiddleware extends Middleware {
|
||||||
private $csrfTokenManager;
|
private $csrfTokenManager;
|
||||||
/** @var ContentSecurityPolicyNonceManager */
|
/** @var ContentSecurityPolicyNonceManager */
|
||||||
private $cspNonceManager;
|
private $cspNonceManager;
|
||||||
|
/** @var Throttler */
|
||||||
|
private $throttler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param IRequest $request
|
* @param IRequest $request
|
||||||
|
@ -101,6 +104,7 @@ class SecurityMiddleware extends Middleware {
|
||||||
* @param ContentSecurityPolicyManager $contentSecurityPolicyManager
|
* @param ContentSecurityPolicyManager $contentSecurityPolicyManager
|
||||||
* @param CSRFTokenManager $csrfTokenManager
|
* @param CSRFTokenManager $csrfTokenManager
|
||||||
* @param ContentSecurityPolicyNonceManager $cspNonceManager
|
* @param ContentSecurityPolicyNonceManager $cspNonceManager
|
||||||
|
* @param Throttler $throttler
|
||||||
*/
|
*/
|
||||||
public function __construct(IRequest $request,
|
public function __construct(IRequest $request,
|
||||||
ControllerMethodReflector $reflector,
|
ControllerMethodReflector $reflector,
|
||||||
|
@ -113,7 +117,8 @@ class SecurityMiddleware extends Middleware {
|
||||||
$isAdminUser,
|
$isAdminUser,
|
||||||
ContentSecurityPolicyManager $contentSecurityPolicyManager,
|
ContentSecurityPolicyManager $contentSecurityPolicyManager,
|
||||||
CsrfTokenManager $csrfTokenManager,
|
CsrfTokenManager $csrfTokenManager,
|
||||||
ContentSecurityPolicyNonceManager $cspNonceManager) {
|
ContentSecurityPolicyNonceManager $cspNonceManager,
|
||||||
|
Throttler $throttler) {
|
||||||
$this->navigationManager = $navigationManager;
|
$this->navigationManager = $navigationManager;
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$this->reflector = $reflector;
|
$this->reflector = $reflector;
|
||||||
|
@ -126,6 +131,7 @@ class SecurityMiddleware extends Middleware {
|
||||||
$this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
|
$this->contentSecurityPolicyManager = $contentSecurityPolicyManager;
|
||||||
$this->csrfTokenManager = $csrfTokenManager;
|
$this->csrfTokenManager = $csrfTokenManager;
|
||||||
$this->cspNonceManager = $cspNonceManager;
|
$this->cspNonceManager = $cspNonceManager;
|
||||||
|
$this->throttler = $throttler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -185,6 +191,12 @@ class SecurityMiddleware extends Middleware {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($this->reflector->hasAnnotation('BruteForceProtection')) {
|
||||||
|
$action = $this->request->getRequestUri();
|
||||||
|
$this->throttler->sleepDelay($this->request->getRemoteAddress(), $action);
|
||||||
|
$this->throttler->registerAttempt($action, $this->request->getRemoteAddress());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIXME: Use DI once available
|
* FIXME: Use DI once available
|
||||||
* Checks if app is enabled (also includes a check whether user is allowed to access the resource)
|
* Checks if app is enabled (also includes a check whether user is allowed to access the resource)
|
||||||
|
|
|
@ -189,9 +189,10 @@ class Throttler {
|
||||||
* Get the throttling delay (in milliseconds)
|
* Get the throttling delay (in milliseconds)
|
||||||
*
|
*
|
||||||
* @param string $ip
|
* @param string $ip
|
||||||
|
* @param string $action optionally filter by action
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function getDelay($ip) {
|
public function getDelay($ip, $action = '') {
|
||||||
$cutoffTime = (new \DateTime())
|
$cutoffTime = (new \DateTime())
|
||||||
->sub($this->getCutoff(43200))
|
->sub($this->getCutoff(43200))
|
||||||
->getTimestamp();
|
->getTimestamp();
|
||||||
|
@ -201,6 +202,11 @@ class Throttler {
|
||||||
->from('bruteforce_attempts')
|
->from('bruteforce_attempts')
|
||||||
->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
|
->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
|
||||||
->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($this->getSubnet($ip))));
|
->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($this->getSubnet($ip))));
|
||||||
|
|
||||||
|
if ($action !== '') {
|
||||||
|
$qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
|
||||||
|
}
|
||||||
|
|
||||||
$attempts = count($qb->execute()->fetchAll());
|
$attempts = count($qb->execute()->fetchAll());
|
||||||
|
|
||||||
if ($attempts === 0) {
|
if ($attempts === 0) {
|
||||||
|
@ -225,10 +231,11 @@ class Throttler {
|
||||||
* Will sleep for the defined amount of time
|
* Will sleep for the defined amount of time
|
||||||
*
|
*
|
||||||
* @param string $ip
|
* @param string $ip
|
||||||
|
* @param string $action optionally filter by action
|
||||||
* @return int the time spent sleeping
|
* @return int the time spent sleeping
|
||||||
*/
|
*/
|
||||||
public function sleepDelay($ip) {
|
public function sleepDelay($ip, $action = '') {
|
||||||
$delay = $this->getDelay($ip);
|
$delay = $this->getDelay($ip, $action);
|
||||||
usleep($delay * 1000);
|
usleep($delay * 1000);
|
||||||
return $delay;
|
return $delay;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue