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
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
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
|
||||
[![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
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