This commit is contained in:
thosakwe 2017-01-12 22:11:55 -05:00
parent c3fa7690d1
commit 166bad95f6
7 changed files with 197 additions and 6 deletions

View file

@ -1,10 +1,11 @@
# security
[![version 0.0.0](https://img.shields.io/badge/pub-v0.0.0--alpha-red.svg)](https://pub.dartlang.org/packages/angel_security)
[![version 0.0.0-alpha+1](https://img.shields.io/badge/pub-v0.0.0--alpha+1-red.svg)](https://pub.dartlang.org/packages/angel_security)
[![build status](https://travis-ci.org/angel-dart/security.svg)](https://travis-ci.org/angel-dart/security)
Angel middleware designed to enhance application security.
Angel middleware designed to enhance application security by patching common Web security
holes.
Currently far from finished, with incomplete code coverage - **USE AT YOUR OWN RISK!!!**
Currently unfinished, with incomplete code coverage - **USE AT YOUR OWN RISK!!!**
## Sanitizing HTML
@ -14,3 +15,23 @@ app.before.add(sanitizeHtmlInput());
// Or:
app.chain(sanitizeHtmlInput()).get(...)
```
## CSRF Tokens
```dart
app.chain(verifyCsrfToken()).post('/form', ...);
app.responseFinalizers.add(setCsrfToken());
```
## Banning IP's
```dart
app.before.add(banIp('1.2.3.4'));
// Or a range:
app.before.add(banIp('1.2.3.*'));
app.before.add(banIp('1.2.*.4'));
// Or multiple filters:
app.before.add(banIp(['1.2.3.4', '192.*.*.*', new RegExp(r'1\.2.\3.\4')]));
```

View file

@ -1,4 +1,6 @@
/// Angel middleware designed to enhance application security.
library angel_security;
export 'src/ban.dart';
export 'src/csrf.dart';
export 'src/sanitize.dart';

50
lib/src/ban.dart Normal file
View file

@ -0,0 +1,50 @@
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
/// Throws a 403 Forbidden if the user's IP is banned.
///
/// [filter] can be:
/// A `String`, `RegExp`, `InternetAddress`, or an `Iterable`.
///
/// String can take the following formats:
/// 1. 1.2.3.4
/// 2. 1.2.3.*, 1.2.*.*, etc.
RequestMiddleware banIp(filter,
{String message:
'Your IP address is forbidden from accessing this server.'}) {
var filters = [];
Iterable inputs = filter is Iterable ? filter : [filter];
for (var input in inputs) {
if (input is RegExp || input is InternetAddress)
filters.add(input);
else if (input is String) {
if (!input.contains('*'))
filters.add(input);
else {
filters.add(new RegExp(input.replaceAll('*', '[0-9]+')));
}
} else
throw new ArgumentError('Cannot use $input as an IP filter.');
}
return (RequestContext req, ResponseContext res) async {
var ip = req.ip;
bool check() {
for (var input in filters) {
if (input is RegExp && input.hasMatch(ip))
return false;
else if (input is InternetAddress && input.address == ip)
return false;
else if (input is String && input == ip) return false;
}
return true;
}
if (!check()) throw new AngelHttpException.forbidden(message: message);
return true;
};
}

44
lib/src/csrf.dart Normal file
View file

@ -0,0 +1,44 @@
import 'dart:io';
import 'package:angel_framework/angel_framework.dart';
import 'package:uuid/uuid.dart';
final Uuid _uuid = new Uuid();
/// Ensures that the request contains a correct CSRF token.
RequestMiddleware verifyCsrfToken(
{bool allowCookie: false,
bool allowQuery: true,
String name: 'csrf_token'}) {
return (RequestContext req, res) async {
String csrfToken;
if (allowQuery && req.query.containsKey(name))
csrfToken = req.query[name];
else if (req.body.containsKey(name))
csrfToken = req.body[name];
else if (allowCookie) {
var cookie =
req.cookies.firstWhere((c) => c.name == name, orElse: () => null);
if (cookie != null) csrfToken = cookie.value;
}
if (csrfToken == null || !req.session.containsKey(name))
throw new AngelHttpException.badRequest(message: 'Missing CSRF token.');
String correctToken = req.session[name];
if (csrfToken != correctToken)
throw new AngelHttpException.badRequest(message: 'Invalid CSRF token.');
return true;
};
}
/// Adds a CSRF token to the session, if none is present.
RequestHandler setCsrfToken({String name: 'csrf_token', bool cookie: false}) {
return (RequestContext req, res) async {
if (!req.session.containsKey(name)) req.session[name] = _uuid.v4();
if (cookie) res.cookies.add(new Cookie(name, req.session[name]));
return true;
};
}

View file

@ -1,6 +1,6 @@
name: angel_security
version: 0.0.0-alpha
description: Angel middleware designed to enhance application security.
version: 0.0.0-alpha+1
description: Angel middleware designed to enhance application security by patching common Web security holes.
author: Tobe O <thosakwe@gmail.com>
environment:
sdk: ">=1.19.0"

24
test/ban.dart Normal file
View file

@ -0,0 +1,24 @@
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_security/angel_security.dart';
import 'package:angel_test/angel_test.dart';
import 'package:test/test.dart';
main() {
Angel app;
TestClient client;
setUp(() async {
app = new Angel()..chain(banIp('*.*.*.*')).get('/ban', 'WTF');
client = await connectTo(app);
});
tearDown(() => client.close());
test('ban everyone', () async {
var response = await client.get('/ban');
print(response.body);
expect(response, hasStatus(403));
expect(response.body.contains('WTF'), isFalse);
});
}

50
test/csrf_token.dart Normal file
View file

@ -0,0 +1,50 @@
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_security/angel_security.dart';
import 'package:angel_test/angel_test.dart';
import 'package:http/http.dart' as http;
import 'package:test/test.dart';
final RegExp _sessId = new RegExp(r'DARTSESSID=([^;]+);');
main() async {
Angel app;
TestClient client;
setUp(() async {
app = new Angel()..responseFinalizers.add(setCsrfToken());
app.chain(verifyCsrfToken()).get('/valid', 'Valid!');
client = await connectTo(app);
});
tearDown(() => client.close());
test('need pre-existing token', () async {
var response = await client.get('/valid?csrf_token=evil');
print(response.body);
expect(response, hasStatus(400));
expect(response.body, contains('Missing'));
});
test('fake token', () async {
// Get a valid CSRF, but ignore it.
var response = await client.get('/');
var sessionId = getCookie(response);
response = await client.get('/valid?csrf_token=evil',
headers: {'cookie': 'DARTSESSID=$sessionId'});
print(response.body);
expect(response, hasStatus(400));
expect(response.body.contains('Valid'), isFalse);
expect(response.body, contains('Invalid CSRF token'));
});
}
String getCookie(http.Response response) {
if (response.headers.containsKey('set-cookie')) {
var header = response.headers['set-cookie'];
var match = _sessId.firstMatch(header);
return match?.group(1);
} else
return null;
}