Skip to content

Commit

Permalink
Add redirect to duck://error on phishing detected
Browse files Browse the repository at this point in the history
  • Loading branch information
not-a-rootkit committed Aug 30, 2024
1 parent ce2983d commit 047f5bb
Showing 1 changed file with 103 additions and 13 deletions.
116 changes: 103 additions & 13 deletions DuckDuckGo/Tab/TabExtensions/SpecialErrorPageTabExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,43 @@ import Foundation
import Navigation
import WebKit
import Combine
import Common
import ContentScopeScripts
import BrowserServicesKit
import SpecialErrorPages
import Common
import PhishingDetection
import PixelKit
import SpecialErrorPages
import os

protocol SpecialErrorPageScriptProvider {
var specialErrorPageUserScript: SpecialErrorPageUserScript? { get }
}

extension UserScripts: SpecialErrorPageScriptProvider {}

enum ErrorType {
case phishing
case ssl
var description: String {
switch self {
case .phishing:
return "Phishing"
case .ssl:
return "SSL"
}
}
}

final class SpecialErrorPageTabExtension {
weak var webView: ErrorPageTabExtensionNavigationDelegate?
private weak var specialErrorPageUserScript: SpecialErrorPageUserScript?
private var shouldBypassSSLError = false
private var urlCredentialCreator: URLCredentialCreating
private var featureFlagger: FeatureFlagger
private var phishingDetector: PhishingSiteDetecting
private var phishingStateManager: PhishingTabStateManager
private var errorPageType: ErrorType?
private var phishingURLExemptions: Set<URL> = []
private let tld = TLD()

private var cancellables = Set<AnyCancellable>()
Expand All @@ -54,6 +73,8 @@ final class SpecialErrorPageTabExtension {
phishingStateManager: PhishingTabStateManager) {
self.featureFlagger = featureFlagger
self.urlCredentialCreator = urlCredentialCreator
self.phishingDetector = phishingDetector
self.phishingStateManager = phishingStateManager
webViewPublisher.sink { [weak self] webView in
self?.webView = webView
}.store(in: &cancellables)
Expand All @@ -79,6 +100,53 @@ final class SpecialErrorPageTabExtension {
}

extension SpecialErrorPageTabExtension: NavigationResponder {
@MainActor
func decidePolicy(for navigationAction: NavigationAction, preferences: inout NavigationPreferences) async -> NavigationActionPolicy? {
let url = navigationAction.url
guard url != URL(string: "about:blank")! else { return .next }
// Add all non-user-initiated navigations after accepting the risk to our exemptions list
if phishingStateManager.didBypassError && navigationAction.navigationType == .other {
phishingURLExemptions.insert(url)
}
self.phishingStateManager.didBypassError = phishingURLExemptions.contains(url)

if self.phishingStateManager.didBypassError || url.isDuckDuckGo || url.isDuckURLScheme {
return .next
}

// Check the URL
let isMalicious = await phishingDetector.checkIsMaliciousIfEnabled(url: url)
self.phishingStateManager.isShowingPhishingError = isMalicious
let errorType = PhishingDetectionError.detected.localizedDescription
if isMalicious {
errorPageType = .phishing
//self.specialErrorPageUserScript?.errorData.kind = "phishing"
if let mainFrameTarget = navigationAction.mainFrameTarget {
failingURL = url
let domain: String = url.host ?? url.toString(decodePunycode: true, dropScheme: true, dropTrailingSlash: true)
errorData = SpecialErrorData(kind: .ssl, errorType: errorType, domain: domain, eTldPlus1: tld.eTLDplus1(failingURL?.host))
if let errorURL = self.generateErrorPageURL(url) {
_ = webView?.load(URLRequest(url: errorURL))
return .cancel
}
} else {
// Malicious iFrame Detected
// PixelKit.fire(PhishingDetectionPixels.iframeLoaded)
let iframeTopUrl = navigationAction.sourceFrame.url
failingURL = iframeTopUrl
let domain: String = iframeTopUrl.host ?? iframeTopUrl.toString(decodePunycode: true, dropScheme: true, dropTrailingSlash: true)
errorData = SpecialErrorData(kind: .phishing, errorType: errorType, domain: domain, eTldPlus1: tld.eTLDplus1(failingURL?.host))
if let errorURL = self.generateErrorPageURL(iframeTopUrl) {
_ = webView?.load(URLRequest(url: errorURL))
return .cancel
}
return .next
}
}
errorPageType = nil
return .next
}

@MainActor
func navigation(_ navigation: Navigation, didFailWith error: WKError) {
let url = error.failingUrl ?? navigation.url
Expand All @@ -91,10 +159,11 @@ extension SpecialErrorPageTabExtension: NavigationResponder {
if featureFlagger.isFeatureOn(.sslCertificatesBypass),
error.errorCode == NSURLErrorServerCertificateUntrusted,
let errorCode = error.userInfo["_kCFStreamErrorCodeKey"] as? Int {
failingURL = url
let domain: String = url.host ?? url.toString(decodePunycode: true, dropScheme: true, dropTrailingSlash: true)
errorData = SpecialErrorData(kind: .ssl, errorType: SSLErrorType.forErrorCode(errorCode).rawValue, domain: domain, eTldPlus1: tld.eTLDplus1(failingURL?.host))
loadSSLErrorHTML(url: url, alternate: shouldPerformAlternateNavigation)
errorPageType = .ssl
failingURL = url
let domain: String = url.host ?? url.toString(decodePunycode: true, dropScheme: true, dropTrailingSlash: true)
errorData = SpecialErrorData(kind: .ssl, errorType: SSLErrorType.forErrorCode(errorCode).rawValue, domain: domain, eTldPlus1: tld.eTLDplus1(failingURL?.host))
loadSSLErrorHTML(url: url, alternate: shouldPerformAlternateNavigation)
}
}
}
Expand All @@ -107,17 +176,28 @@ extension SpecialErrorPageTabExtension: NavigationResponder {
@MainActor
func didReceive(_ challenge: URLAuthenticationChallenge, for navigation: Navigation?) async -> AuthChallengeDisposition? {
guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust else { return nil }
guard shouldBypassSSLError else { return nil}
guard shouldBypassSSLError else { return nil }
guard navigation?.url == webView?.url else { return nil }
guard let credential = urlCredentialCreator.urlCredentialFrom(trust: challenge.protectionSpace.serverTrust) else { return nil }

shouldBypassSSLError = false
return .credential(credential)
}

@MainActor
func generateErrorPageURL(_ url: URL) -> URL? {
guard let urlString = url.absoluteString.data(using: .utf8) else {
Logger.phishingDetection.error("Unable to convert error URL to string data.")
return nil
}
let encodedURL = URLTokenValidator.base64URLEncode(data: urlString)
let token = URLTokenValidator.shared.generateToken(for: url)
let errorURLString = "duck://error?reason=phishing&url=\(encodedURL)&token=\(token)"
return URL(string: errorURLString)
}
}

extension SpecialErrorPageTabExtension: SpecialErrorPageUserScriptDelegate {

func leaveSite() {
guard webView?.canGoBack == true else {
webView?.close()
Expand All @@ -127,22 +207,31 @@ extension SpecialErrorPageTabExtension: SpecialErrorPageUserScriptDelegate {
}

func visitSite() {
shouldBypassSSLError = true
if errorPageType == .phishing {
if let url = webView?.url {
//PixelKit.fire(PhishingDetectionPixels.visitSite)
phishingURLExemptions.insert(url)
self.phishingStateManager.didBypassError = true
self.phishingStateManager.isShowingPhishingError = false
}
} else if errorPageType == .ssl {
shouldBypassSSLError = true
}
_ = webView?.reloadPage()
}

func advancedInfoPresented() {}
}

protocol ErrorPageTabExtensionProtocol: AnyObject, NavigationResponder {}
protocol SpecialErrorPageTabExtensionProtocol: AnyObject, NavigationResponder {}

extension SpecialErrorPageTabExtension: TabExtension, ErrorPageTabExtensionProtocol {
typealias PublicProtocol = ErrorPageTabExtensionProtocol
extension SpecialErrorPageTabExtension: TabExtension, SpecialErrorPageTabExtensionProtocol {
typealias PublicProtocol = SpecialErrorPageTabExtensionProtocol
func getPublicProtocol() -> PublicProtocol { self }
}

extension TabExtensions {
var errorPage: ErrorPageTabExtensionProtocol? {
var errorPage: SpecialErrorPageTabExtensionProtocol? {
resolve(SpecialErrorPageTabExtension.self)
}
}
Expand All @@ -152,6 +241,7 @@ protocol ErrorPageTabExtensionNavigationDelegate: AnyObject {
var canGoBack: Bool { get }
func loadAlternateHTML(_ html: String, baseURL: URL, forUnreachableURL failingURL: URL)
func setDocumentHtml(_ html: String)
func load(_ request: URLRequest) -> WKNavigation?
func goBack() -> WKNavigation?
func close()
func reloadPage() -> WKNavigation?
Expand Down

0 comments on commit 047f5bb

Please sign in to comment.