mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-08-04 14:55:40 +00:00
Adding switch --mine-params
This commit is contained in:
parent
ffe124f3e0
commit
075d009bb7
8 changed files with 457 additions and 1 deletions
243
data/txt/common-params.txt
Normal file
243
data/txt/common-params.txt
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
# Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
|
||||
# See the file 'LICENSE' for copying permission
|
||||
|
||||
id
|
||||
page
|
||||
q
|
||||
query
|
||||
search
|
||||
s
|
||||
keyword
|
||||
keywords
|
||||
name
|
||||
username
|
||||
user
|
||||
uid
|
||||
userid
|
||||
email
|
||||
mail
|
||||
pass
|
||||
password
|
||||
pwd
|
||||
token
|
||||
key
|
||||
apikey
|
||||
api_key
|
||||
access_token
|
||||
auth
|
||||
session
|
||||
sid
|
||||
sessionid
|
||||
lang
|
||||
language
|
||||
locale
|
||||
country
|
||||
region
|
||||
city
|
||||
zip
|
||||
sort
|
||||
order
|
||||
orderby
|
||||
dir
|
||||
direction
|
||||
filter
|
||||
category
|
||||
cat
|
||||
type
|
||||
kind
|
||||
class
|
||||
group
|
||||
tag
|
||||
tags
|
||||
status
|
||||
state
|
||||
action
|
||||
act
|
||||
cmd
|
||||
command
|
||||
op
|
||||
operation
|
||||
mode
|
||||
method
|
||||
func
|
||||
function
|
||||
callback
|
||||
jsonp
|
||||
format
|
||||
output
|
||||
view
|
||||
tab
|
||||
step
|
||||
stage
|
||||
debug
|
||||
test
|
||||
dev
|
||||
admin
|
||||
role
|
||||
level
|
||||
priv
|
||||
file
|
||||
filename
|
||||
path
|
||||
dir
|
||||
folder
|
||||
doc
|
||||
document
|
||||
url
|
||||
uri
|
||||
link
|
||||
href
|
||||
redirect
|
||||
redirect_uri
|
||||
return
|
||||
returnurl
|
||||
return_url
|
||||
next
|
||||
target
|
||||
dest
|
||||
destination
|
||||
goto
|
||||
continue
|
||||
ref
|
||||
referer
|
||||
referrer
|
||||
source
|
||||
src
|
||||
from
|
||||
to
|
||||
date
|
||||
year
|
||||
month
|
||||
day
|
||||
time
|
||||
timestamp
|
||||
start
|
||||
end
|
||||
begin
|
||||
finish
|
||||
limit
|
||||
offset
|
||||
count
|
||||
num
|
||||
number
|
||||
size
|
||||
length
|
||||
width
|
||||
height
|
||||
amount
|
||||
price
|
||||
qty
|
||||
quantity
|
||||
value
|
||||
val
|
||||
data
|
||||
input
|
||||
content
|
||||
text
|
||||
msg
|
||||
message
|
||||
body
|
||||
title
|
||||
subject
|
||||
description
|
||||
desc
|
||||
comment
|
||||
note
|
||||
code
|
||||
hash
|
||||
sig
|
||||
signature
|
||||
csrf
|
||||
csrf_token
|
||||
csrftoken
|
||||
nonce
|
||||
salt
|
||||
enc
|
||||
encrypt
|
||||
decrypt
|
||||
base64
|
||||
json
|
||||
xml
|
||||
raw
|
||||
echo
|
||||
reflect
|
||||
show
|
||||
hide
|
||||
display
|
||||
render
|
||||
template
|
||||
tpl
|
||||
theme
|
||||
skin
|
||||
style
|
||||
color
|
||||
font
|
||||
image
|
||||
img
|
||||
photo
|
||||
avatar
|
||||
icon
|
||||
banner
|
||||
video
|
||||
audio
|
||||
media
|
||||
attachment
|
||||
upload
|
||||
download
|
||||
export
|
||||
import
|
||||
backup
|
||||
restore
|
||||
sync
|
||||
refresh
|
||||
reload
|
||||
reset
|
||||
clear
|
||||
flush
|
||||
purge
|
||||
enable
|
||||
disable
|
||||
active
|
||||
enabled
|
||||
visible
|
||||
public
|
||||
private
|
||||
locked
|
||||
verified
|
||||
confirmed
|
||||
approved
|
||||
product
|
||||
item
|
||||
sku
|
||||
model
|
||||
brand
|
||||
vendor
|
||||
seller
|
||||
store
|
||||
shop
|
||||
cart
|
||||
basket
|
||||
checkout
|
||||
payment
|
||||
invoice
|
||||
receipt
|
||||
transaction
|
||||
account
|
||||
profile
|
||||
member
|
||||
customer
|
||||
client
|
||||
company
|
||||
organization
|
||||
org
|
||||
department
|
||||
team
|
||||
project
|
||||
task
|
||||
job
|
||||
event
|
||||
booking
|
||||
reservation
|
||||
appointment
|
||||
schedule
|
||||
calendar
|
||||
|
|
@ -529,6 +529,10 @@ def start():
|
|||
|
||||
checkWaf()
|
||||
|
||||
if conf.mineParams and not any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe, conf.hql)):
|
||||
from lib.utils.paraminer import mineParameters
|
||||
mineParameters()
|
||||
|
||||
if any((conf.graphql, conf.nosql, conf.ldap, conf.xpath, conf.ssti, conf.xxe, conf.hql)) and (conf.reportJson or conf.resultsFile):
|
||||
singleTimeWarnMessage("'--report-json'/'--results-file' do not (yet) capture non-SQL technique (--graphql/--nosql/--ldap/--xpath/--ssti/--xxe/--hql) findings; these are reported on the console only")
|
||||
|
||||
|
|
|
|||
|
|
@ -1589,6 +1589,7 @@ def setPaths(rootPath):
|
|||
paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.txt")
|
||||
paths.COMMON_FILES = os.path.join(paths.SQLMAP_TXT_PATH, "common-files.txt")
|
||||
paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt")
|
||||
paths.COMMON_PARAMETERS = os.path.join(paths.SQLMAP_TXT_PATH, "common-params.txt")
|
||||
paths.SQL_KEYWORDS = os.path.join(paths.SQLMAP_TXT_PATH, "keywords.txt")
|
||||
paths.SMALL_DICT = os.path.join(paths.SQLMAP_TXT_PATH, "smalldict.txt")
|
||||
paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt")
|
||||
|
|
|
|||
|
|
@ -241,6 +241,7 @@ optDict = {
|
|||
"eta": "boolean",
|
||||
"flushSession": "boolean",
|
||||
"forms": "boolean",
|
||||
"mineParams": "boolean",
|
||||
"freshQueries": "boolean",
|
||||
"googlePage": "integer",
|
||||
"harFile": "string",
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from lib.core.enums import OS
|
|||
from thirdparty import six
|
||||
|
||||
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
|
||||
VERSION = "1.10.7.180"
|
||||
VERSION = "1.10.7.181"
|
||||
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
|
||||
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
|
||||
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
|
||||
|
|
@ -92,6 +92,9 @@ LIVE_COOKIES_TIMEOUT = 120
|
|||
LOWER_RATIO_BOUND = 0.02
|
||||
UPPER_RATIO_BOUND = 0.98
|
||||
|
||||
# Number of candidate names probed per request while mining for hidden parameters ('--mine-params')
|
||||
PARAMETER_MINING_BUCKET_SIZE = 25
|
||||
|
||||
# For filling in case of dumb push updates
|
||||
DUMMY_JUNK = "Phah5jue"
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ def vulnTest(tests=None, label="vuln"):
|
|||
("-u <url> --data=\"security_level=4\" -p id --flush-session --technique=B --banner", ("random (non-scanner) User-Agent and browser-like headers to bypass the WAF/IPS", "Type: boolean-based blind", "banner: '3.")), # automatic WAF-bypass against a libinjection-class WAF: tampers cannot help, only the non-scanner User-Agent does
|
||||
("-u <url> --data=\"security_level=5\" -p id --flush-session --technique=B", ("unable to automatically bypass the WAF/IPS", "does not seem to be injectable")), # automatic WAF-bypass honest bail: a libinjection-class WAF that no User-Agent or tamper can defeat
|
||||
("-u <url> -p id --flush-session --technique=B --proof", ("sqlmap proved exploitation of the following injection point", "Parameter: id (GET)", "Technique: boolean-based blind", "TRUE (5/5)", "repeatably", "Retrieved: back-end DBMS banner '3.")), # --proof: report-grade proof in the injection-point style - forces the boolean technique (so a multi-technique point still proves), and actively reads a value out as the strongest proof
|
||||
("-u <base> --mine-params --flush-session --technique=B", ("mining for hidden GET parameters", "found hidden parameter 'id'", "held back parameter(s) that break the base request", "Parameter: id (GET)", "Type: boolean-based blind")), # --mine-params: discover an injectable parameter absent from a bare URL, hold back the raw-SQL sink that would shadow it, then confirm the injection on the mined 'id'
|
||||
("-r <request> --flush-session -v 5 --test-skip=\"heavy\" --save=<config>", ("CloudFlare", "web application technology: Express", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind", "saved command line options to the configuration file")),
|
||||
("-c <config>", ("CloudFlare", "possible DBMS: 'SQLite'", "User-Agent: foobar", "~Type: time-based blind")),
|
||||
("-l <log> --flush-session --skip-waf -vvvvv --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")),
|
||||
|
|
|
|||
|
|
@ -721,6 +721,9 @@ def cmdLineParser(argv=None):
|
|||
general.add_argument("--forms", dest="forms", action="store_true",
|
||||
help="Parse and test forms on target URL")
|
||||
|
||||
general.add_argument("--mine-params", dest="mineParams", action="store_true",
|
||||
help="Mine for hidden (unlinked) GET parameters to test")
|
||||
|
||||
general.add_argument("--fresh-queries", dest="freshQueries", action="store_true",
|
||||
help="Ignore query results stored in session file")
|
||||
|
||||
|
|
|
|||
200
lib/utils/paraminer.py
Normal file
200
lib/utils/paraminer.py
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
|
||||
See the file 'LICENSE' for copying permission
|
||||
"""
|
||||
|
||||
import difflib
|
||||
|
||||
from lib.core.compat import xrange
|
||||
from lib.core.common import getFileItems
|
||||
from lib.core.common import paramToDict
|
||||
from lib.core.common import randomStr
|
||||
from lib.core.common import singleTimeWarnMessage
|
||||
from lib.core.data import conf
|
||||
from lib.core.data import logger
|
||||
from lib.core.data import paths
|
||||
from lib.core.enums import HTTPMETHOD
|
||||
from lib.core.enums import PLACE
|
||||
from lib.core.settings import DIFF_TOLERANCE
|
||||
from lib.core.settings import MAX_DIFFLIB_SEQUENCE_LENGTH
|
||||
from lib.core.settings import PARAMETER_MINING_BUCKET_SIZE
|
||||
from lib.request.connect import Connect as Request
|
||||
|
||||
# Benign, broadly-stable value used both to check whether a discovered parameter is safe to add and
|
||||
# as its seed during testing (a random value would error out an integer/id context and defeat inference)
|
||||
PROBE_VALUE = "1"
|
||||
|
||||
def _canary():
|
||||
return randomStr(10, lowercase=True)
|
||||
|
||||
def _fetch(get):
|
||||
"""
|
||||
Requests the target with the given GET query string, returning a (page, HTTP code) pair.
|
||||
"""
|
||||
|
||||
try:
|
||||
page, _, code = Request.getPage(get=get or None, silent=True, raise404=False)
|
||||
except Exception:
|
||||
page, code = None, None
|
||||
|
||||
return (page or ""), code
|
||||
|
||||
def _ratio(first, second):
|
||||
"""
|
||||
Similarity of two response bodies, mirroring the core page comparison (see comparison.py): an
|
||||
exact match, a length ratio for oversized bodies, otherwise difflib's quick_ratio().
|
||||
"""
|
||||
|
||||
if not first or not second:
|
||||
return 0.0
|
||||
|
||||
if first == second:
|
||||
return 1.0
|
||||
|
||||
if any(len(_) > MAX_DIFFLIB_SEQUENCE_LENGTH for _ in (first, second)):
|
||||
ratio = 1.0 * len(first) / len(second)
|
||||
return ratio if ratio <= 1 else 1.0 / ratio
|
||||
|
||||
return difflib.SequenceMatcher(None, first, second).quick_ratio()
|
||||
|
||||
def _differs(page, base, floor):
|
||||
"""
|
||||
True when 'page' departs from the baseline by more than the target's own dynamic jitter ('floor').
|
||||
"""
|
||||
|
||||
return bool(page) and _ratio(page, base) < floor - DIFF_TOLERANCE
|
||||
|
||||
def _confirm(baseGet, name, base, floor):
|
||||
"""
|
||||
Confirms a single candidate in isolation with two differently-valued probes. Returns 'reflected'
|
||||
when a value is echoed back (caught here even if a shared bucket hid it behind a preceding
|
||||
parameter), 'behavioral' when both probes resemble each other yet depart from the baseline (its
|
||||
presence, not its value, matters), otherwise None.
|
||||
"""
|
||||
|
||||
def _get(value):
|
||||
pair = "%s=%s" % (name, value)
|
||||
return "%s&%s" % (baseGet, pair) if baseGet else pair
|
||||
|
||||
canaries = (_canary(), _canary())
|
||||
first, second = _fetch(_get(canaries[0]))[0], _fetch(_get(canaries[1]))[0]
|
||||
|
||||
if not (first and second):
|
||||
return None
|
||||
|
||||
if (canaries[0] in first or canaries[1] in second) and not any(_ in base for _ in canaries):
|
||||
return "reflected"
|
||||
|
||||
if _ratio(first, second) < floor - DIFF_TOLERANCE: # value-dependent yet not reflected -> unreliable
|
||||
return None
|
||||
|
||||
if _differs(first, base, floor) and _differs(second, base, floor):
|
||||
return "behavioral"
|
||||
|
||||
return None
|
||||
|
||||
def _chunks(sequence, size):
|
||||
for i in xrange(0, len(sequence), size):
|
||||
yield sequence[i:i + size]
|
||||
|
||||
def _discover(candidates, baseGet, base, floor):
|
||||
"""
|
||||
Probes candidate names in buckets (one shared request per bucket, each name carrying its own
|
||||
random canary) and returns the confirmed ones as (name, reason) pairs. Reflection is resolved
|
||||
straight from the bucket response; the rest are confirmed individually only when the bucket
|
||||
actually moved the response, so a target that ignores every candidate stays cheap.
|
||||
"""
|
||||
|
||||
found = []
|
||||
|
||||
for chunk in _chunks(candidates, PARAMETER_MINING_BUCKET_SIZE):
|
||||
canaries = dict((name, _canary()) for name in chunk)
|
||||
query = "&".join("%s=%s" % (name, canaries[name]) for name in chunk)
|
||||
page = _fetch("%s&%s" % (baseGet, query) if baseGet else query)[0]
|
||||
|
||||
if not page:
|
||||
continue
|
||||
|
||||
pending = []
|
||||
for name in chunk:
|
||||
if canaries[name] in page and canaries[name] not in base:
|
||||
found.append((name, "reflected"))
|
||||
else:
|
||||
pending.append(name)
|
||||
|
||||
if pending and _differs(page, base, floor):
|
||||
for name in pending:
|
||||
reason = _confirm(baseGet, name, base, floor)
|
||||
if reason:
|
||||
found.append((name, reason))
|
||||
|
||||
return found
|
||||
|
||||
def _commit(found, baseGet, baseCode):
|
||||
"""
|
||||
Adds the discovered parameters (seeded with PROBE_VALUE) to the GET test scope. One that turns
|
||||
the request into a server error the baseline did not have would shadow and corrupt the testing
|
||||
of every sibling parameter, so it is reported and held back rather than degrading detection.
|
||||
"""
|
||||
|
||||
safe, disruptive = [], []
|
||||
|
||||
for name, _ in found:
|
||||
pair = "%s=%s" % (name, PROBE_VALUE)
|
||||
code = _fetch("%s&%s" % (baseGet, pair) if baseGet else pair)[1]
|
||||
if code is not None and code >= 500 and not (baseCode is not None and baseCode >= 500):
|
||||
disruptive.append(name)
|
||||
else:
|
||||
safe.append(name)
|
||||
|
||||
if disruptive:
|
||||
logger.warning("held back parameter(s) that break the base request with a test value (test them explicitly with '-p'): %s" % ", ".join("'%s'" % _ for _ in disruptive))
|
||||
|
||||
if not safe:
|
||||
return
|
||||
|
||||
logger.info("adding %d discovered parameter(s) to the test scope: %s" % (len(safe), ", ".join("'%s'" % _ for _ in safe)))
|
||||
|
||||
additions = "&".join("%s=%s" % (name, PROBE_VALUE) for name in safe)
|
||||
conf.parameters[PLACE.GET] = "%s&%s" % (baseGet, additions) if baseGet else additions
|
||||
conf.paramDict[PLACE.GET] = paramToDict(PLACE.GET, conf.parameters[PLACE.GET])
|
||||
|
||||
def mineParameters():
|
||||
"""
|
||||
Discovers hidden (unlinked) GET parameters the target still processes and queues the confirmed
|
||||
ones for the regular injection tests, using two independent oracles (value reflection and a
|
||||
behavioral side effect on the response).
|
||||
"""
|
||||
|
||||
if conf.data or (conf.method and conf.method != HTTPMETHOD.GET):
|
||||
singleTimeWarnMessage("'--mine-params' currently supports GET parameters only")
|
||||
return
|
||||
|
||||
baseGet = conf.parameters.get(PLACE.GET) or ""
|
||||
existing = set(conf.paramDict.get(PLACE.GET) or {})
|
||||
candidates = [_ for _ in getFileItems(paths.COMMON_PARAMETERS, unique=True) if _ and _ not in existing]
|
||||
|
||||
if not candidates:
|
||||
return
|
||||
|
||||
logger.info("mining for hidden GET parameters (%d candidate name(s))" % len(candidates))
|
||||
|
||||
base, baseCode = _fetch(baseGet)
|
||||
if not base:
|
||||
singleTimeWarnMessage("could not obtain a baseline response, skipping parameter mining")
|
||||
return
|
||||
|
||||
floor = _ratio(base, _fetch(baseGet)[0]) # the target's own between-request jitter
|
||||
|
||||
found = _discover(candidates, baseGet, base, floor)
|
||||
|
||||
for name, reason in found:
|
||||
logger.info("found hidden parameter '%s' (%s)" % (name, reason))
|
||||
|
||||
if not found:
|
||||
logger.info("no hidden parameters found")
|
||||
return
|
||||
|
||||
_commit(found, baseGet, baseCode)
|
||||
Loading…
Add table
Add a link
Reference in a new issue