Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0e250d2
Merge branch 'lightfuzz' into domxsstest
liquidsec Mar 5, 2025
8278ee9
domxss initial
liquidsec Mar 6, 2025
7a451b3
working prototype
liquidsec Mar 7, 2025
d582514
ruff format
liquidsec Mar 7, 2025
2a671e9
fixed ansible and paths
liquidsec Mar 11, 2025
a61f7bb
add wordlist environ
liquidsec Mar 12, 2025
51b72c0
refactor codeql ansible
liquidsec Mar 12, 2025
a02ad49
fixed false positives
liquidsec Mar 13, 2025
0b462ac
reduced cpu load
liquidsec Mar 13, 2025
14042c7
adding dom only mode
liquidsec Mar 13, 2025
ffdc175
Merge branch 'lightfuzz' into codeql-module
liquidsec Mar 13, 2025
5801933
move browser to setup
liquidsec Mar 13, 2025
f096d82
new custom signatures, refactor
liquidsec Mar 15, 2025
37f7b63
initial yara expansion
liquidsec Mar 15, 2025
5feb26f
remove debug
liquidsec Mar 15, 2025
06a4c00
adjusting yara rule
liquidsec Mar 16, 2025
81f51df
changing yara signature
liquidsec Mar 16, 2025
29a5074
adding url into finding event
liquidsec Mar 16, 2025
4024c49
Merge branch 'dev' of https://github.com/blacklanternsecurity/bbot in…
liquidsec Mar 17, 2025
e92b52b
Merge branch 'dev' into codeql-module
liquidsec Mar 17, 2025
e5fcfec
add prototype pollution ql
liquidsec Mar 18, 2025
a602558
fixing dependency issues, cache system to prevent duplicate processing
liquidsec Mar 19, 2025
982238d
refining cache logic
liquidsec Mar 19, 2025
c6ef57d
simplifying method
liquidsec Mar 19, 2025
925ef5f
fixing empty folder bug
liquidsec Mar 19, 2025
63ec8bc
refactor
liquidsec Mar 19, 2025
09f159c
removing debug
liquidsec Mar 19, 2025
01b82d0
adding details field
liquidsec Mar 20, 2025
8a8b4f4
Merge branch 'dev' into codeql-module
liquidsec Aug 28, 2025
fe8fd77
poetry.lock
liquidsec Aug 28, 2025
bafcd75
lint
liquidsec Aug 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bbot/core/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ def lib_dir(self):
def scans_dir(self):
return self.home / "scans"

@property
def wordlist_dir(self):
return Path(__file__).parent.parent / "wordlists"

@property
def config(self):
"""
Expand Down
26 changes: 21 additions & 5 deletions bbot/core/helpers/yara_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,32 @@ def compile_strings(self, strings: list[str], nocase=False):
def compile(self, *args, **kwargs):
return yara.compile(*args, **kwargs)

async def match(self, compiled_rules, text):
async def match(self, compiled_rules, text, full_result=False):
"""
Given a compiled YARA rule and a body of text, return a list of strings that match the rule
Given a compiled YARA rule and a body of text, return matches.

Args:
compiled_rules: Compiled YARA rules
text: Text to match against
full_result (bool): If True, returns full match information including
rule names and metadata. If False, returns only matched strings.

Returns:
If full_result=False: List[str] of matched strings
If full_result=True: List[dict] with full match information including:
- matched_string: The full matched string
- rule: The name of the matched rule
- meta: The metadata of the matched rule
"""
matched_strings = []
results = []
matches = await self.parent_helper.run_in_executor(compiled_rules.match, data=text)
if matches:
for match in matches:
for string_match in match.strings:
for instance in string_match.instances:
matched_string = instance.matched_data.decode("utf-8")
matched_strings.append(matched_string)
return matched_strings
if full_result:
results.append({"matched_string": matched_string, "rule": match.rule, "meta": match.meta})
else:
results.append(matched_string)
return results
549 changes: 549 additions & 0 deletions bbot/modules/codeql.py

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions bbot/presets/web/codeql-intense.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
description: Discover client-side web vulnerabilities using CodeQL. Limit to "error" and "warning" level findings, and include out of scope JS files.

modules:
- httpx
- portfilter
- codeql

config:
url_querystring_remove: False
modules:
excavate:
retain_querystring: True
codeql:
mode: all
min_severity: warning
15 changes: 15 additions & 0 deletions bbot/presets/web/codeql-min.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
description: Discover client-side web vulnerabilities using CodeQL. Limit to "error" level findings, and only analyze the DOM itself.

modules:
- httpx
- portfilter
- codeql

config:
url_querystring_remove: False
modules:
excavate:
retain_querystring: True
codeql:
mode: dom_only
min_severity: error
15 changes: 15 additions & 0 deletions bbot/presets/web/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
description: Discover client-side web vulnerabilities using CodeQL. Limit to "error" level findings, and skip out of scope JS files.

modules:
- httpx
- portfilter
- codeql

config:
url_querystring_remove: False
modules:
excavate:
retain_querystring: True
codeql:
mode: in_scope
min_severity: error
2 changes: 2 additions & 0 deletions bbot/scanner/preset/environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ def prepare(self):
# ensure bbot_tools
environ["BBOT_TOOLS"] = str(self.preset.core.tools_dir)
add_to_path(str(self.preset.core.tools_dir), environ=environ)
# ensure bbot_wordlists
environ["BBOT_WORDLISTS"] = str(self.preset.core.wordlist_dir)
# ensure bbot_cache
environ["BBOT_CACHE"] = str(self.preset.core.cache_dir)
# ensure bbot_temp
Expand Down
100 changes: 100 additions & 0 deletions bbot/wordlists/codeql_queries/dom-xss-jquery-contains.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* @name DOM-based XSS via potentially dangerous jQuery selectors
* @description Untrusted input like location.hash used in potentially dangerous jQuery selectors (such as :contains, has(), or other non-ID selectors) can lead to XSS when jQuery processes the selector.
* @kind path-problem
* @problem.severity error
* @security-severity 7.5
* @precision high
* @id js/dom-xss-jquery-unsafe-selectors
* @tags security
* external/cwe/cwe-079
* external/cwe/cwe-116
*/

import javascript
import DataFlow
import DataFlow::PathGraph

/**
* Taint tracking configuration for location.hash being used unsafely in jQuery selectors.
*/
class HashToJQueryContainsConfig extends TaintTracking::Configuration {
HashToJQueryContainsConfig() { this = "HashToJQueryContainsConfig" }

override predicate isSource(DataFlow::Node source) {
exists(DataFlow::PropRead hashProp |
hashProp = source and
hashProp.getPropertyName() = "hash" and
exists(DataFlow::PropRead locationProp |
locationProp = hashProp.getBase() and
locationProp.getPropertyName() = "location"
)
)
}

override predicate isSink(DataFlow::Node sink) {
exists(DataFlow::CallNode jqueryCall |
(jqueryCall.getCalleeName() = "$" or jqueryCall.getCalleeName() = "jQuery") and
sink = jqueryCall.getArgument(0) and
(
// Check for direct :contains usage in string literals
exists(string val | val = sink.getStringValue() and val.indexOf(":contains(") >= 0) or

// Check for string concatenation or binary expressions that aren't properly sanitized
exists(Expr expr |
expr = sink.asExpr() and
(expr instanceof BinaryExpr or expr instanceof AddExpr) and
not isSafeIdSelector(expr)
)
)
)
or
// Also check for unsafe usage in jQuery has() method
exists(DataFlow::MethodCallNode hasCall |
hasCall.getMethodName() = "has" and
sink = hasCall.getArgument(0) and
not isSafeSelector(hasCall.getArgument(0))
)
}

/**
* Determines if an expression represents a safe ID selector (starting with #)
*/
private predicate isSafeIdSelector(Expr expr) {
// Case: '#' + hash
exists(AddExpr addExpr, StringLiteral hashChar |
addExpr = expr and
hashChar = addExpr.getLeftOperand() and
hashChar.getValue() = "#"
)
or
// Case: $('#' + hash)
exists(StringLiteral strLit |
strLit = expr and
strLit.getValue().charAt(0) = "#"
)
}

/**
* Determines if a node represents a safe selector
*/
private predicate isSafeSelector(DataFlow::Node node) {
exists(StringLiteral strLit |
strLit = node.asExpr() and
strLit.getValue().charAt(0) = "#"
)
or
exists(AddExpr addExpr, StringLiteral hashChar |
addExpr = node.asExpr() and
hashChar = addExpr.getLeftOperand() and
hashChar.getValue() = "#"
)
}
}

/**
* Execute the taint tracking analysis.
*/
from HashToJQueryContainsConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "The value from $@ is used unsafely in a jQuery selector, potentially leading to DOM XSS.", source.getNode(), "location.hash"
53 changes: 53 additions & 0 deletions bbot/wordlists/codeql_queries/xmlhttprequest-to-eval.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @name DOM-based XSS via dangerous eval of XMLHttpRequest responseText
* @description Evaluating untrusted data from an XMLHttpRequest's responseText via eval() can lead to code injection.
* @kind path-problem
* @problem.severity error
* @security.severity 9.5
* @precision high
* @id js/xhr-dynamic-eval-modified
* @tags security
* external/cwe/cwe-95
*/

import javascript
import DataFlow
import DataFlow::PathGraph

/**
* Modified taint tracking configuration to catch cases where `this.responseText`
* flows into an eval call even when used in string concatenation.
*/
class XHRResponseToEvalConfigModified extends TaintTracking::Configuration {
XHRResponseToEvalConfigModified() { this = "XHRResponseToEvalConfigModified" }

override predicate isSource(DataFlow::Node source) {
// Mark any property read of "responseText" as a taint source.
exists(DataFlow::PropRead propRead |
propRead = source and
propRead.getPropertyName() = "responseText"
)
}

override predicate isSink(DataFlow::Node sink) {
// Mark the argument of eval() as a taint sink.
exists(CallExpr call |
call.getCallee().(Identifier).getName() = "eval" and
sink.asExpr() = call.getArgument(0)
)
}

override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
// Propagate taint through binary expressions (e.g. string concatenation).
exists(BinaryExpr binop |
// Check if the tainted value appears in either operand.
(binop.getLeftOperand() = pred.asExpr() or binop.getRightOperand() = pred.asExpr()) and
succ.asExpr() = binop
)
}
}

from XHRResponseToEvalConfigModified config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Untrusted data from XMLHttpRequest.responseText flows to eval() after string concatenation.", source.getNode(), "XMLHttpRequest.responseText"

16 changes: 6 additions & 10 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading