Skip to content

Commit

Permalink
Added check for Function() calls
Browse files Browse the repository at this point in the history
  • Loading branch information
b4dpxl committed Jun 16, 2021
1 parent 20e0699 commit ffff2d8
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 33 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
kotlin("jvm") version "1.4.10"
}

val projectVersion = "0.1.0"
val projectVersion = "0.2.0"

repositories {
mavenCentral()
Expand Down
21 changes: 16 additions & 5 deletions src/main/kotlin/b4dpxl/EvalScanIssue.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 {
Expand All @@ -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 """<code>eval()</code> 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 """<code>eval()</code> 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 <code>Function()</code> or <code>new Function()</code> 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? {
Expand Down
66 changes: 39 additions & 27 deletions src/main/kotlin/b4dpxl/Evaluator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -86,7 +86,7 @@ class Evaluator constructor(callbacks: IBurpExtenderCallbacks?) : IProxyListener
val issue = EvalScanIssue(
requestResponse,
baseURL,
"""<p>The page called the JavaScript <code>eval()</code> function from <code>${requestInfo.url}</code></p>
"""<p>The page called the JavaScript <code>${json.getValue("function")}()</code> function from <code>${requestInfo.url}</code></p>
<p>
<b>Executed call:</b> <code>${json.getValue("call")}</code>
</p>
Expand All @@ -99,8 +99,10 @@ class Evaluator constructor(callbacks: IBurpExtenderCallbacks?) : IProxyListener
${json.getValue("script")}
</code></pre>
<br />
<p>Note: The <code>eval()</code> call was renamed to <code>${evalNamePrefix}_eval()</code> by ${extensionName}.</p>
""".trim()
<p>Note: The <code>${json.getValue("function")}()</code> call was renamed to <code>${evalNamePrefix}_${json.getValue("function")}()</code> by ${extensionName}.</p>
""".trim(),
confidence = if (json.getValue("function") == "eval") EvalScanIssue.Confidence.CERTAIN else
EvalScanIssue.Confidence.FIRM
)

var isNewIssue = true
Expand Down Expand Up @@ -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
};
Expand All @@ -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,
Expand Down

0 comments on commit ffff2d8

Please sign in to comment.