Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ venv/
__pycache__/
*.pyc
simulation/
adiuvare.egg-info/
24 changes: 22 additions & 2 deletions adiuvare/signals/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,35 @@ def _bool_taut_hit(text: str) -> bool:
return True
return False

QUESTION_RE = _re.compile(
r"(?i)^(how|what|why|when|can|does|do|is|are)\b.*\?"
)

def is_discussion_context(text: str) -> bool:
return bool(QUESTION_RE.match(text))

EXECUTABLE_SCRIPT_RE = _re.compile(r"(?i)<\s*script\b[^>]*>\s*\S[^<]*<\s*/\s*script\s*>")

def is_executable_xss(text: str) -> bool:
return bool(EXECUTABLE_SCRIPT_RE.search(text))

def should_suppress_xss_lib(text: str) -> bool:
return is_discussion_context(text) and not is_executable_xss(text)

def check_sql(text: str) -> tuple[bool, float, str]:
if _bool_taut_hit(text):
return True, 0.92, "bool_taut"
return _scan(sql_pats, text)
hit = _scan(sql_pats, text)
if hit[2] == "select_from" and is_discussion_context(text):
return False, 0.0, ""
return hit


def check_xss(text: str) -> tuple[bool, float, str]:
return _scan(xss_pats, text)
hit = _scan(xss_pats, text)
if hit[2] == "script_tag" and is_discussion_context(text) and not is_executable_xss(text):
return False, 0.0, ""
return hit


def check_path(text: str) -> tuple[bool, float, str]:
Expand Down
7 changes: 3 additions & 4 deletions adiuvare/signals/payload.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from ..core.models import RequestContext, SignalResult
from ..vendor import detect_sqli, detect_xss, normalize
from .base import SoftSignal
from .patterns import check_cmd, check_nosql, check_path, check_sql, check_ssti, check_xss

from .patterns import check_cmd, check_nosql, check_path, check_sql, check_ssti, check_xss, should_suppress_xss_lib

class PayloadSignal(SoftSignal):
name = "payload"
Expand Down Expand Up @@ -32,7 +31,7 @@ async def extract(self, ctx: RequestContext) -> SignalResult:
hits.append((max(sql_lib["conf"], 0.82), sql_lib["fp"] or "sql_lib"))
if sql_pat[0]:
hits.append((sql_pat[1], sql_pat[2]))
if xss_lib["hit"]:
if xss_lib["hit"] and not should_suppress_xss_lib:
Comment thread
aaishii07 marked this conversation as resolved.
Outdated
hits.append((max(xss_lib["conf"] * 0.80, 0.62), "xss_lib"))
if xss_pat[0]:
hits.append((xss_pat[1], xss_pat[2]))
Expand Down Expand Up @@ -63,4 +62,4 @@ async def extract(self, ctx: RequestContext) -> SignalResult:
"ssti_pat": ssti_pat[2],
"nosql_pat": nosql_pat[2],
}
return SignalResult(score=score, reason=top[1], detail=detail)
return SignalResult(score=score, reason=top[1], detail=detail)
2 changes: 2 additions & 0 deletions tests/test_benign_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def test_benign_matrix_stays_clean():
"How does $(HOME) expansion work in Bash?",
"render {{ user.name }} in the template",
"Use $gt for greater-than filters in Mongo docs",
"How do I write SELECT * FROM users in a tutorial?",
"How do I print <script> literally in docs?"
]

for text in cases:
Expand Down
42 changes: 42 additions & 0 deletions tests/test_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,45 @@ def test_payload_keeps_union_phrase_clean():

res = asyncio.run(PayloadSignal().extract(ctx))
assert res.score == 0.0


def test_payload_does_not_suppress_union_select_in_question():
ctx = RequestContext(
identity="u1",
payload="Can you show me how UNION SELECT password FROM users works?",
url="/search",
method="POST",
headers={},
ip="127.0.0.1",
endpoint="/search",
)
res = asyncio.run(PayloadSignal().extract(ctx))
assert res.score >= 0.7


def test_payload_does_not_suppress_executable_script_in_question():
ctx = RequestContext(
identity="u1",
payload="What does <script>document.cookie</script> do?",
url="/comment",
method="POST",
headers={},
ip="127.0.0.1",
endpoint="/comment",
)
res = asyncio.run(PayloadSignal().extract(ctx))
assert res.score >= 0.7


def test_payload_does_not_suppress_boolean_sqli_in_question():
ctx = RequestContext(
identity="u1",
payload="What happens with ' OR 1=1--?",
url="/login",
method="POST",
headers={},
ip="127.0.0.1",
endpoint="/login",
)
res = asyncio.run(PayloadSignal().extract(ctx))
assert res.score >= 0.7