0.0.0-alpha

This commit is contained in:
thosakwe 2017-01-12 18:57:13 -05:00
parent 7dfe94386e
commit e0c14fb18f
7 changed files with 204 additions and 0 deletions

2
.gitignore vendored
View file

@ -25,3 +25,5 @@ doc/api/
# Don't commit pubspec lock file
# (Library packages only! Remove pattern if developing an application package)
pubspec.lock
log.txt

1
.travis.yml Normal file
View file

@ -0,0 +1 @@
language: dart

View file

@ -1,2 +1,16 @@
# security
[![version 0.0.0](https://img.shields.io/badge/pub-v0.0.0--alpha-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.
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
View 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
View 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
View 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
View 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);
});
}