introduce brute force protection for api calls

Signed-off-by: Bjoern Schiessle <bjoern@schiessle.org>
This commit is contained in:
Bjoern Schiessle 2017-01-17 11:51:10 +01:00
parent 4bbd52b3f9
commit df296249d6
No known key found for this signature in database
GPG Key ID: 2378A753E2BF04F6
3 changed files with 37 additions and 11 deletions

View File

@ -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) {

View File

@ -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)

View File

@ -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;
} }