diff --git a/build.gradle.kts b/build.gradle.kts index 71cd9a6..48347cf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { kotlin("jvm") version "1.4.10" } -val projectVersion = "0.1.0" +val projectVersion = "0.2.0" repositories { mavenCentral() diff --git a/src/main/kotlin/b4dpxl/EvalScanIssue.kt b/src/main/kotlin/b4dpxl/EvalScanIssue.kt index d6a7397..ce0468c 100644 --- a/src/main/kotlin/b4dpxl/EvalScanIssue.kt +++ b/src/main/kotlin/b4dpxl/EvalScanIssue.kt @@ -2,11 +2,17 @@ package b4dpxl import burp.IHttpRequestResponse import burp.IHttpService -import burp.IRequestInfo import burp.IScanIssue import java.net.URL -class EvalScanIssue(requestResponse: IHttpRequestResponse, url: String, detail: String, severity: String = "Medium", confidence: String = "Firm") : IScanIssue { +class EvalScanIssue(requestResponse: IHttpRequestResponse, url: String, detail: String, severity: String = "Medium", + confidence: Confidence = Confidence.FIRM) : IScanIssue { + + enum class Confidence(val confidence: String) { + CERTAIN("Certain"), + FIRM("Firm"), + TENTATIVE("Tentative") + } val _url: URL val _detail = detail.trim() @@ -29,7 +35,7 @@ class EvalScanIssue(requestResponse: IHttpRequestResponse, url: String, detail: } override fun getIssueName(): String { - return "JavaScript eval() call" + return "JavaScript eval() or Function() call" } override fun getIssueType(): Int { @@ -41,11 +47,16 @@ class EvalScanIssue(requestResponse: IHttpRequestResponse, url: String, detail: } override fun getConfidence(): String { - return _confidence + return _confidence.confidence.toString() } override fun getIssueBackground(): String? { - return """eval() is a dangerous JavaScript function, which executes arbitrary code in the context of the caller. If an attacker can influence the code which is called, they can run custom scripts and exploit vulnerabilities such as Cross-Site Scripting (XSS). Alternative, safe methods can usually be found which do not rely on the eval() and related Function() methods.""" + return """eval() is a dangerous JavaScript function, which executes arbitrary code in the + |context of the caller. If an attacker can influence the code which is called, they can run custom + |scripts and exploit vulnerabilities such as Cross-Site Scripting (XSS). Although not as + |dangerous, calls to Function() or new Function() may also be + |exploited in the same manner. Alternative, safe methods can usually be found which do + |not rely on the eval() and related Function() methods.""".trimMargin() } override fun getRemediationBackground(): String? { diff --git a/src/main/kotlin/b4dpxl/Evaluator.kt b/src/main/kotlin/b4dpxl/Evaluator.kt index f75f072..fe53a7c 100644 --- a/src/main/kotlin/b4dpxl/Evaluator.kt +++ b/src/main/kotlin/b4dpxl/Evaluator.kt @@ -22,7 +22,7 @@ class Evaluator constructor(callbacks: IBurpExtenderCallbacks?) : IProxyListener private val configCallback = "callback" init { - Utilities(callbacks, true) + Utilities(callbacks, false) Utilities.callbacks.setExtensionName(extensionName) Utilities.callbacks.registerProxyListener(this) Utilities.callbacks.registerExtensionStateListener(this) @@ -53,13 +53,13 @@ class Evaluator constructor(callbacks: IBurpExtenderCallbacks?) : IProxyListener break } } - Utilities.println("Loaded ${extensionName}!") + Utilities.println("Loaded ${extensionName}") } override fun processProxyMessage(messageIsRequest: Boolean, proxyMessage: IInterceptedProxyMessage) { if (! enabledMenu.isSelected) { - return; + return } val requestResponse = proxyMessage.messageInfo @@ -86,7 +86,7 @@ class Evaluator constructor(callbacks: IBurpExtenderCallbacks?) : IProxyListener val issue = EvalScanIssue( requestResponse, baseURL, - """

The page called the JavaScript eval() function from ${requestInfo.url}

+ """

The page called the JavaScript ${json.getValue("function")}() function from ${requestInfo.url}

Executed call: ${json.getValue("call")}

@@ -99,8 +99,10 @@ class Evaluator constructor(callbacks: IBurpExtenderCallbacks?) : IProxyListener ${json.getValue("script")}
-

Note: The eval() call was renamed to ${evalNamePrefix}_eval() by ${extensionName}.

-""".trim() +

Note: The ${json.getValue("function")}() call was renamed to ${evalNamePrefix}_${json.getValue("function")}() by ${extensionName}.

+""".trim(), + confidence = if (json.getValue("function") == "eval") EvalScanIssue.Confidence.CERTAIN else + EvalScanIssue.Confidence.FIRM ) var isNewIssue = true @@ -145,33 +147,21 @@ ${json.getValue("script")} val responseInfo = Utilities.helpers.analyzeResponse(requestResponse?.response) val body: String = Utilities.helpers.bytesToString(Utilities.getResponseBody(requestResponse)) - val rex = "\\b(eval\\s*\\()" + val rex = "\\b((eval|Function)\\s*\\()" val pattern = Regex(rex) - if (responseInfo.inferredMimeType.equals("script", true) && pattern.containsMatchIn(body)) { val url = requestInfo.url - Utilities.println("Found ${pattern.findAll(body).count()} eval() to modify in ${url}") + Utilities.println("Found ${pattern.findAll(body).count()} eval() or Function() calls to modify in ${url}") var newBody = """ -function ${evalNamePrefix}_eval(x) { - call = `eval(${"$"}{x})`; - code = ""; - msg = call; - if (${evalNamePrefix}_eval.caller != null) { - msg += "\n> " + String(${evalNamePrefix}_eval.caller).match(/(?<=${evalNamePrefix}_)eval\(.*?\)/)[0]; - } - console.info(`In ${"$"}{location.href}:\n${"$"}{msg}`); - if (${evalNamePrefix}_eval.caller != null) { - code = String(${evalNamePrefix}_eval.caller).replace("${evalNamePrefix}_eval(", "eval("); - console.debug("${evalNamePrefix}_eval() called by: " + code); - } -""" - if (callbackMenu.isSelected) { - newBody += """ +function ${evalNamePrefix}_callback(fn, call, code) {""" + if (callbackMenu.isSelected) { // this function is empty if callbacks are disabled + newBody += """ // send the request back to burp for logging try { data = { "url": location.href, + "function": fn, "call": call, "script": code }; @@ -181,13 +171,35 @@ function ${evalNamePrefix}_eval(x) { headers: {"Content-Type": "application/json", "X-EVALUATOR": "1"} }); } catch (e) {console.error(e);} - """ +""" } - newBody += """ +} +function ${evalNamePrefix}_log(call) { + fn = ${evalNamePrefix}_log.caller; + fn_name = fn.name.match(/(?<=[A-Z]+_)(eval|Function)/)[0]; + code = ""; + msg = call; + if (fn.caller != null) { + msg += "\n> " + String(fn.caller).match(/(?<=[A-Z]+_)(eval|Function)\(.*?\)/)[0]; + } + console.info("In " + location.href + ":\n" + msg); + if (fn.caller != null) { + code = String(fn.caller).replace(/[A-Z]+_(eval|Function)\(/, "$1("); + console.debug(fn_name + "() called by:\n" + code); + } + ${evalNamePrefix}_callback(fn_name, call, code); +} + +function ${evalNamePrefix}_eval(x) { + ${evalNamePrefix}_log(`eval(${"$"}{x})`); return eval(x); } -""" + body.replace(pattern, evalNamePrefix + "_eval(") +function ${evalNamePrefix}_Function(x) { + ${evalNamePrefix}_log(`Function(${"$"}{x})`); + return Function(x); +} +""" + body.trimIndent().replace(pattern, evalNamePrefix + "_$2(") requestResponse?.response = Utilities.helpers.buildHttpMessage( responseInfo.headers,