Implement actual policy

This commit is contained in:
James Magahern
2021-10-21 13:24:58 -07:00
parent 70486c49de
commit fc7380ed21
6 changed files with 109 additions and 7 deletions

View File

@@ -91,7 +91,7 @@ extension BrowserViewController: WKNavigationDelegate, WKUIDelegate
var allowJavaScript = tab.javaScriptEnabled var allowJavaScript = tab.javaScriptEnabled
if !allowJavaScript, let host = navigationAction.request.url?.host { if !allowJavaScript, let host = navigationAction.request.url?.host {
// Check origin policy // Check origin policy
allowJavaScript = policyManager.allowedOriginsForScriptResources().contains(host) allowJavaScript = policyManager.scriptPolicy(forOrigin: host).allowsEmbeddedJavaScript()
} }
preferences.allowsContentJavaScript = allowJavaScript preferences.allowsContentJavaScript = allowJavaScript

View File

@@ -84,9 +84,11 @@
_processPool = [[WKProcessPool alloc] _initWithConfiguration:poolConfiguration]; _processPool = [[WKProcessPool alloc] _initWithConfiguration:poolConfiguration];
// Initialize allowed origins now // Initialize allowed origins now
NSDictionary<NSString *, NSNumber *> *policies = [_policyDataSource scriptPolicyTypeByOrigin];
NSArray<NSString *> *allowedOrigins = [[_policyDataSource allowedOriginsForScriptResources] allObjects]; NSArray<NSString *> *allowedOrigins = [[_policyDataSource allowedOriginsForScriptResources] allObjects];
[_processPool _setObject:allowedOrigins forBundleParameter:SBRGetAllowedOriginsKey()]; [_processPool _setObject:allowedOrigins forBundleParameter:SBRGetAllowedOriginsKey()];
[_processPool _setObject:@(_allowAllScripts) forBundleParameter:SBRGetAllScriptsAllowedKey()]; [_processPool _setObject:@(_allowAllScripts) forBundleParameter:SBRGetAllScriptsAllowedKey()];
[_processPool _setObject:policies forBundleParameter:SBRGetPolicyTypeByOriginKey()];
webViewConfiguration.processPool = _processPool; webViewConfiguration.processPool = _processPool;
} }

View File

@@ -35,6 +35,12 @@ NS_SWIFT_NAME(ScriptPolicy)
- (instancetype)initWithSecurityOrigin:(NSString *)origin policyType:(SBRScriptOriginPolicyType)policyType; - (instancetype)initWithSecurityOrigin:(NSString *)origin policyType:(SBRScriptOriginPolicyType)policyType;
/// Returns YES if policy type allows embedded (<script>) JavaScript for the given origin.
- (BOOL)allowsEmbeddedJavaScript;
/// Returns YES if policy allows loading external JavaScript resource with the security origin `resourceOrigin`.
- (BOOL)allowsExternalJavaScriptResourceOrigin:(NSString *)resourceOrigin;
- (instancetype)init NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE;
@end @end

View File

@@ -12,6 +12,32 @@
@implementation SBRScriptPolicy @implementation SBRScriptPolicy
+ (NSSet<NSString *> *)_commonCDNOrigins {
static dispatch_once_t onceToken;
static NSSet<NSString *> *whitelist = nil;
dispatch_once(&onceToken, ^{
whitelist = [NSSet setWithArray:@[
// JSDelivr: Used by bootstrap
@"cdn.jsdelivr.net",
// SourceForge
@"fsdn.net",
// Open Source CDNs
@"cdnjs.com",
@"osscdn.com",
// JQuery
@"code.jquery.com",
// Bootstrap
@"bootstrapcdn.com",
]];
});
return whitelist;
}
+ (NSString *)titleForPolicyType:(SBRScriptOriginPolicyType)policyType + (NSString *)titleForPolicyType:(SBRScriptOriginPolicyType)policyType
{ {
switch (policyType) { switch (policyType) {
@@ -118,4 +144,62 @@
[coder encodeObject:[NSNumber numberWithInteger:_policyType] forKey:@"policyType"]; [coder encodeObject:[NSNumber numberWithInteger:_policyType] forKey:@"policyType"];
} }
- (BOOL)allowsEmbeddedJavaScript
{
return _policyType > SBRScriptOriginPolicyTypeAlpha;
}
- (BOOL)allowsExternalJavaScriptResourceOrigin:(NSString *)resourceOrigin
{
switch (_policyType) {
case SBRScriptOriginPolicyTypeAlpha:
// No scripts allowed whatsoever.
return NO;
case SBRScriptOriginPolicyTypeBravo:
// Only allows on-page scripts, no external scripts whatsoever.
return NO;
case SBRScriptOriginPolicyTypeCharlie:
// Only allow scripts from the exact same origin as the host.
return [_origin isEqualToString:resourceOrigin];
case SBRScriptOriginPolicyTypeDelta: {
// Allow scripts from common CDNs, or CDNs that *appear* to belong to the host.
if ([[[self class] _commonCDNOrigins] containsObject:resourceOrigin]) {
return YES;
}
BOOL looksLikeCDN = NO;
NSArray<NSString *> *hostOriginComponents = [_origin componentsSeparatedByString:@"."];
if ([hostOriginComponents count] > 1) {
// Assume our "family" name is just before the TLD.
NSString *hostFamilyName = [hostOriginComponents objectAtIndex:(hostOriginComponents.count - 2)];
looksLikeCDN = [resourceOrigin containsString:hostFamilyName];
if (!looksLikeCDN) {
NSArray<NSString *> *resourceOriginComponents = [resourceOrigin componentsSeparatedByString:@"."];
if ([resourceOriginComponents count] > 1) {
NSString *resourceFamilyName = [resourceOriginComponents objectAtIndex:(resourceOriginComponents.count - 2)];
// Check and see if the subdomain is "cdn", but also make sure we're not accidentally allowing an ad network CDN.
looksLikeCDN = [[resourceOriginComponents firstObject] isEqualToString:@"cdn"];
BOOL looksLikeAdNetwork = (
[resourceFamilyName hasSuffix:@"ad"] ||
[resourceFamilyName hasSuffix:@"ads"] ||
[resourceFamilyName containsString:@"analy"] // "analy-tics"
);
looksLikeCDN = looksLikeCDN && !looksLikeAdNetwork;
NSLog(@"SBRProcessPlugin: [%@] CDN:%@ AD:%@", resourceOriginComponents, looksLikeCDN ? @"YES" : @"NO", looksLikeAdNetwork ? @"YES" : @"NO");
}
}
}
return looksLikeCDN;
}
case SBRScriptOriginPolicyTypeEcho:
// Anything goes.
return YES;
}
}
@end @end

View File

@@ -59,6 +59,7 @@
CD853BD124E778B800D2BDCC /* History.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CD853BCF24E778B800D2BDCC /* History.xcdatamodeld */; }; CD853BD124E778B800D2BDCC /* History.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CD853BCF24E778B800D2BDCC /* History.xcdatamodeld */; };
CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD853BD324E77BF900D2BDCC /* HistoryItem.swift */; }; CD853BD424E77BF900D2BDCC /* HistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD853BD324E77BF900D2BDCC /* HistoryItem.swift */; };
CD97CF9225D5BE6F00288FEE /* NavigationControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD97CF9125D5BE6F00288FEE /* NavigationControlsView.swift */; }; CD97CF9225D5BE6F00288FEE /* NavigationControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD97CF9125D5BE6F00288FEE /* NavigationControlsView.swift */; };
CD9B88C2272201E900DAAB7E /* SBRScriptPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = CD361CF5271A3718006E9CA5 /* SBRScriptPolicy.m */; };
CDAD9CE8263A2DF200FF7199 /* DocumentControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDAD9CE7263A2DF200FF7199 /* DocumentControlsView.swift */; }; CDAD9CE8263A2DF200FF7199 /* DocumentControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDAD9CE7263A2DF200FF7199 /* DocumentControlsView.swift */; };
CDAD9CEA263A318F00FF7199 /* ShareableURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDAD9CE9263A318F00FF7199 /* ShareableURL.swift */; }; CDAD9CEA263A318F00FF7199 /* ShareableURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDAD9CE9263A318F00FF7199 /* ShareableURL.swift */; };
CDC4A1CF25E9D8F7007D33C6 /* Tagger.js in Resources */ = {isa = PBXBuildFile; fileRef = CDC4A1CE25E9D8F7007D33C6 /* Tagger.js */; }; CDC4A1CF25E9D8F7007D33C6 /* Tagger.js in Resources */ = {isa = PBXBuildFile; fileRef = CDC4A1CE25E9D8F7007D33C6 /* Tagger.js */; };
@@ -631,6 +632,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
CD9B88C2272201E900DAAB7E /* SBRScriptPolicy.m in Sources */,
1ADFF4AA24C8D477006DC7AE /* SBRProcessPlugin.m in Sources */, 1ADFF4AA24C8D477006DC7AE /* SBRProcessPlugin.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

View File

@@ -9,11 +9,13 @@
#import "SBRWebProcessDelegate.h" #import "SBRWebProcessDelegate.h"
#import "SBRWebProcessProxy.h" #import "SBRWebProcessProxy.h"
#import "SBRScriptPolicy.h"
#import <WebKit/_WKRemoteObjectInterface.h> #import <WebKit/_WKRemoteObjectInterface.h>
#import <WebKit/_WKRemoteObjectRegistry.h> #import <WebKit/_WKRemoteObjectRegistry.h>
#import <WebKit/WKWebProcessPlugInBrowserContextController.h> #import <WebKit/WKWebProcessPlugInBrowserContextController.h>
#import <WebKit/WKWebProcessPlugInBrowserContextControllerPrivate.h> #import <WebKit/WKWebProcessPlugInBrowserContextControllerPrivate.h>
#import <WebKit/WKWebProcessPlugInFrame.h>
#import <WebKit/WKWebProcessPlugInLoadDelegate.h> #import <WebKit/WKWebProcessPlugInLoadDelegate.h>
@interface SBRProcessPlugin () <WKWebProcessPlugInLoadDelegate, SBRWebProcessProxy> @interface SBRProcessPlugin () <WKWebProcessPlugInLoadDelegate, SBRWebProcessProxy>
@@ -61,6 +63,7 @@
{ {
_allowedResourceOrigins = [[plugInController parameters] valueForKey:SBRGetAllowedOriginsKey()]; _allowedResourceOrigins = [[plugInController parameters] valueForKey:SBRGetAllowedOriginsKey()];
_allScriptsAllowed = [[[plugInController parameters] valueForKey:SBRGetAllScriptsAllowedKey()] boolValue]; _allScriptsAllowed = [[[plugInController parameters] valueForKey:SBRGetAllScriptsAllowedKey()] boolValue];
_policyTypeByOrigin = [[plugInController parameters] valueForKey:SBRGetPolicyTypeByOriginKey()];
NSLog(@"SBRProcessPlugin: %lu origins allowed, all scripts allowed: %@ ", (unsigned long)_allowedResourceOrigins.count, _allScriptsAllowed ? @"YES" : @"NO"); NSLog(@"SBRProcessPlugin: %lu origins allowed, all scripts allowed: %@ ", (unsigned long)_allowedResourceOrigins.count, _allScriptsAllowed ? @"YES" : @"NO");
} }
@@ -87,15 +90,20 @@
} }
NSURL *requestURL = [request URL]; NSURL *requestURL = [request URL];
NSString *originString = [requestURL host]; NSString *resourceOrigin = [requestURL host];
NSString *requestExtension = [requestURL pathExtension]; NSString *requestExtension = [requestURL pathExtension];
NSString *hostOrigin = [[[controller mainFrame] URL] host];
if (requestExtension.length > 0 && [requestExtension isEqualToString:@"js"]) { if (requestExtension.length > 0 && [requestExtension isEqualToString:@"js"]) {
if ([self allScriptsAllowed] || [_allowedResourceOrigins containsObject:originString]) { NSNumber *policyType = [_policyTypeByOrigin objectForKey:hostOrigin];
NSLog(@"SBRProcessPlugin: Allowing whitelisted requestURL: %@", requestURL); NSLog(@"SBRProcessPlugin: Policy type for %@: %@", hostOrigin, policyType);
[[self processDelegate] webProcessDidAllowScriptWithOrigin:originString];
SBRScriptPolicy *policy = [[SBRScriptPolicy alloc] initWithSecurityOrigin:hostOrigin policyType:[policyType integerValue]];
if ([self allScriptsAllowed] || [policy allowsExternalJavaScriptResourceOrigin:resourceOrigin]) {
NSLog(@"SBRProcessPlugin: Policy allows script requestURL: %@", requestURL);
[[self processDelegate] webProcessDidAllowScriptWithOrigin:resourceOrigin];
} else { } else {
NSLog(@"SBRProcessPlugin: Blocking requestURL: %@", requestURL); NSLog(@"SBRProcessPlugin: Policy disallows script requestURL: %@", requestURL);
[[self processDelegate] webProcessDidBlockScriptWithOrigin:originString]; [[self processDelegate] webProcessDidBlockScriptWithOrigin:resourceOrigin];
request = nil; request = nil;
} }