0.0.0-alpha
This commit is contained in:
parent
7dfe94386e
commit
e0c14fb18f
7 changed files with 204 additions and 0 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -25,3 +25,5 @@ doc/api/
|
||||||
# Don't commit pubspec lock file
|
# Don't commit pubspec lock file
|
||||||
# (Library packages only! Remove pattern if developing an application package)
|
# (Library packages only! Remove pattern if developing an application package)
|
||||||
pubspec.lock
|
pubspec.lock
|
||||||
|
|
||||||
|
log.txt
|
1
.travis.yml
Normal file
1
.travis.yml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
language: dart
|
14
README.md
14
README.md
|
@ -1,2 +1,16 @@
|
||||||
# security
|
# security
|
||||||
|
[](https://pub.dartlang.org/packages/angel_security)
|
||||||
|
[](https://travis-ci.org/angel-dart/security)
|
||||||
|
|
||||||
Angel middleware designed to enhance application security.
|
Angel middleware designed to enhance application security.
|
||||||
|
|
||||||
|
Currently far from finished, with incomplete code coverage - **USE AT YOUR OWN RISK!!!**
|
||||||
|
|
||||||
|
## Sanitizing HTML
|
||||||
|
|
||||||
|
```dart
|
||||||
|
app.before.add(sanitizeHtmlInput());
|
||||||
|
|
||||||
|
// Or:
|
||||||
|
app.chain(sanitizeHtmlInput()).get(...)
|
||||||
|
```
|
4
lib/angel_security.dart
Normal file
4
lib/angel_security.dart
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/// Angel middleware designed to enhance application security.
|
||||||
|
library angel_security;
|
||||||
|
|
||||||
|
export 'src/sanitize.dart';
|
50
lib/src/sanitize.dart
Normal file
50
lib/src/sanitize.dart
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
|
||||||
|
final Map<Pattern, String> DEFAULT_SANITIZERS = {
|
||||||
|
new RegExp(
|
||||||
|
r'<\s*s\s*c\s*r\s*i\s*p\s*t\s*>.*<\s*\/\s*s\s*c\s*r\s*i\s*p\s*t\s*>',
|
||||||
|
caseSensitive: false): ''
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Mitigates XSS risk by sanitizing user HTML input.
|
||||||
|
///
|
||||||
|
/// You can also provide a Map of patterns to [replace].
|
||||||
|
RequestMiddleware sanitizeHtmlInput(
|
||||||
|
{bool body: true,
|
||||||
|
bool query: true,
|
||||||
|
Map<Pattern, String> replace: const {}}) {
|
||||||
|
var sanitizers = {}..addAll(DEFAULT_SANITIZERS)..addAll(replace ?? {});
|
||||||
|
|
||||||
|
return (RequestContext req, res) async {
|
||||||
|
if (body) _sanitizeMap(req.body, sanitizers);
|
||||||
|
if (query) _sanitizeMap(req.query, sanitizers);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_sanitize(v, Map<Pattern, String> sanitizers) {
|
||||||
|
if (v is String) {
|
||||||
|
var str = v;
|
||||||
|
|
||||||
|
sanitizers.forEach((needle, replace) {
|
||||||
|
str = str.replaceAll(needle, replace);
|
||||||
|
});
|
||||||
|
|
||||||
|
return HTML_ESCAPE.convert(str);
|
||||||
|
} else if (v is Map) {
|
||||||
|
_sanitizeMap(v, sanitizers);
|
||||||
|
return v;
|
||||||
|
} else if (v is Iterable) {
|
||||||
|
bool isList = v is List;
|
||||||
|
var mapped = v.map((x) => _sanitize(x, sanitizers));
|
||||||
|
return isList ? mapped.toList() : mapped;
|
||||||
|
} else
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sanitizeMap(Map data, Map<Pattern, String> sanitizers) {
|
||||||
|
data.forEach((k, v) {
|
||||||
|
data[k] = _sanitize(v, sanitizers);
|
||||||
|
});
|
||||||
|
}
|
12
pubspec.yaml
Normal file
12
pubspec.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
name: angel_security
|
||||||
|
version: 0.0.0-alpha
|
||||||
|
description: Angel middleware designed to enhance application security.
|
||||||
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
|
homepage: https://github.com/angel-dart/security
|
||||||
|
dependencies:
|
||||||
|
angel_framework: ^1.0.0-dev
|
||||||
|
dev_dependencies:
|
||||||
|
angel_diagnostics: ^1.0.0-dev
|
||||||
|
angel_validate: ^1.0.0-beta
|
||||||
|
angel_test: ^1.0.0-dev
|
||||||
|
test: ^0.12.0
|
121
test/sanitize_test.dart
Normal file
121
test/sanitize_test.dart
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:angel_diagnostics/angel_diagnostics.dart';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_security/angel_security.dart';
|
||||||
|
import 'package:angel_test/angel_test.dart';
|
||||||
|
import 'package:angel_validate/server.dart';
|
||||||
|
import 'package:matcher/matcher.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
final Validator untrustedSchema = new Validator({'html*': isString});
|
||||||
|
|
||||||
|
main() async {
|
||||||
|
Angel app;
|
||||||
|
TestClient client;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
app = new Angel()
|
||||||
|
..chain(validate(untrustedSchema))
|
||||||
|
.chain(sanitizeHtmlInput())
|
||||||
|
.post('/untrusted', (RequestContext req, ResponseContext res) async {
|
||||||
|
String untrusted = req.body['html'];
|
||||||
|
res
|
||||||
|
..contentType = ContentType.HTML
|
||||||
|
..write('''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Potential Security Hole</title>
|
||||||
|
</head>
|
||||||
|
<body>$untrusted</body>
|
||||||
|
</html>''');
|
||||||
|
})
|
||||||
|
..chain(validate(untrustedSchema)).post('/attribute',
|
||||||
|
(RequestContext req, ResponseContext res) async {
|
||||||
|
String untrusted = req.body['html'];
|
||||||
|
res
|
||||||
|
..contentType = ContentType.HTML
|
||||||
|
..write('''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Potential Security Hole</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img src="$untrusted" />
|
||||||
|
</body>
|
||||||
|
</html>''');
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.configure(logRequests(new File('log.txt')));
|
||||||
|
client = await connectTo(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => client.close());
|
||||||
|
|
||||||
|
group('script tag', () {
|
||||||
|
test('normal', () async {
|
||||||
|
var xss = "<script>alert('XSS')</script>";
|
||||||
|
var response = await client.post('/untrusted', body: {'html': xss});
|
||||||
|
print(response.body);
|
||||||
|
expect(response.body.contains(xss), isFalse);
|
||||||
|
expect(response.body.toLowerCase().contains('<script>'), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mixed case', () async {
|
||||||
|
var xss = "<scRIpT>alert('XSS')</sCRIpt>";
|
||||||
|
var response = await client.post('/untrusted', body: {'html': xss});
|
||||||
|
print(response.body);
|
||||||
|
expect(response.body.contains(xss), isFalse);
|
||||||
|
expect(response.body.toLowerCase().contains('<script>'), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('spaces', () async {
|
||||||
|
var xss = "< s c rip t>alert('XSS')</scr ip t>";
|
||||||
|
var response = await client.post('/untrusted', body: {'html': xss});
|
||||||
|
print(response.body);
|
||||||
|
expect(response.body.contains(xss), isFalse);
|
||||||
|
expect(response.body.toLowerCase().contains('<script>'), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('lines', () async {
|
||||||
|
var xss = "<scri\npt>\n\nalert('XSS')\t\n</sc\nri\npt>";
|
||||||
|
var response = await client.post('/untrusted', body: {'html': xss});
|
||||||
|
print(response.body);
|
||||||
|
expect(response.body.contains(xss), isFalse);
|
||||||
|
expect(response.body.toLowerCase().contains('<script>'), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('accents', () async {
|
||||||
|
var xss = '''<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>''';
|
||||||
|
var response = await client.post('/untrusted', body: {'html': xss});
|
||||||
|
print(response.body);
|
||||||
|
expect(response.body.contains(xss), isFalse);
|
||||||
|
expect(response.body.toLowerCase().contains('<script>'), isFalse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('quotes', () async {
|
||||||
|
var xss = '" onclick="<script>alert(\'XSS!\')</script>"';
|
||||||
|
var response = await client.post('/attribute', body: {'html': xss});
|
||||||
|
print(response.body);
|
||||||
|
expect(response.body.contains(xss), isFalse);
|
||||||
|
expect(response.body.toLowerCase().contains('<script>'), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('javascript:evil', () async {
|
||||||
|
var xss = 'javascript:alert(\'XSS!\')';
|
||||||
|
var response = await client.post('/attribute', body: {'html': xss});
|
||||||
|
print(response.body);
|
||||||
|
expect(response.body.contains(xss), isFalse);
|
||||||
|
expect(response.body.toLowerCase().contains('javascript:'), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('style attribute', () async {
|
||||||
|
var xss = "background-image: url(jaVAscRiPt:alert('XSS'))";
|
||||||
|
var response = await client.post('/attribute', body: {'html': xss});
|
||||||
|
print(response.body);
|
||||||
|
expect(response.body.contains(xss), isFalse);
|
||||||
|
expect(response.body.toLowerCase().contains('javascript:'), isFalse);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue