From 7278af01ee18e962adafc43d3f1021bcaf7215a2 Mon Sep 17 00:00:00 2001 From: Miroslav Stampar Date: Tue, 16 Sep 2014 14:12:43 +0200 Subject: [PATCH] Implementation for an Issue #832 --- lib/core/enums.py | 1 + lib/core/exception.py | 3 ++ lib/core/settings.py | 4 ++ lib/core/shell.py | 43 +++++++++++++++++--- lib/parse/cmdline.py | 78 +++++++++++++++++++++++++++++++------ lib/takeover/abstraction.py | 3 +- sqlmap.py | 12 +++++- 7 files changed, 124 insertions(+), 20 deletions(-) diff --git a/lib/core/enums.py b/lib/core/enums.py index 095c03b9d..b4f8b809f 100644 --- a/lib/core/enums.py +++ b/lib/core/enums.py @@ -342,3 +342,4 @@ class AUTH_TYPE: class AUTOCOMPLETE_TYPE: SQL = 0 OS = 1 + SQLMAP = 2 diff --git a/lib/core/exception.py b/lib/core/exception.py index eb2a6e297..8fe6a7756 100644 --- a/lib/core/exception.py +++ b/lib/core/exception.py @@ -44,6 +44,9 @@ class SqlmapSilentQuitException(SqlmapBaseException): class SqlmapUserQuitException(SqlmapBaseException): pass +class SqlmapShellQuitException(SqlmapBaseException): + pass + class SqlmapSyntaxException(SqlmapBaseException): pass diff --git a/lib/core/settings.py b/lib/core/settings.py index aaca12a20..4a93fdfa5 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -239,6 +239,7 @@ BASIC_HELP_ITEMS = ( "checkTor", "flushSession", "tor", + "sqlmapShell", "wizard", ) @@ -583,6 +584,9 @@ MIN_BINARY_DISK_DUMP_SIZE = 100 # Regular expression used for extracting form tags FORM_SEARCH_REGEX = r"(?si)" +# Maximum number of lines to save in history file +MAX_HISTORY_LENGTH = 1000 + # Minimum field entry length needed for encoded content (hex, base64,...) check MIN_ENCODED_LEN_CHECK = 5 diff --git a/lib/core/shell.py b/lib/core/shell.py index 2064ece46..17e340036 100644 --- a/lib/core/shell.py +++ b/lib/core/shell.py @@ -15,12 +15,39 @@ from lib.core.data import logger from lib.core.data import paths from lib.core.enums import AUTOCOMPLETE_TYPE from lib.core.enums import OS +from lib.core.settings import MAX_HISTORY_LENGTH + +def readlineAvailable(): + """ + Check if the readline is available. By default + it is not in Python default installation on Windows + """ + + return readline._readline is not None + +def clearHistory(): + if not readlineAvailable(): + return + + readline.clear_history() def saveHistory(): + if not readlineAvailable(): + return + historyPath = os.path.expanduser(paths.SQLMAP_SHELL_HISTORY) + try: + os.remove(historyPath) + except: + pass + + readline.set_history_length(MAX_HISTORY_LENGTH) readline.write_history_file(historyPath) def loadHistory(): + if not readlineAvailable(): + return + historyPath = os.path.expanduser(paths.SQLMAP_SHELL_HISTORY) if os.path.exists(historyPath): @@ -47,15 +74,13 @@ class CompleterNG(rlcompleter.Completer): matches.append(word) return matches - -def autoCompletion(completion=None): - # First of all we check if the readline is available, by default - # it is not in Python default installation on Windows - if not readline._readline: + +def autoCompletion(completion=None, os=None, commands=None): + if not readlineAvailable(): return if completion == AUTOCOMPLETE_TYPE.OS: - if Backend.isOs(OS.WINDOWS): + if os == OS.WINDOWS: # Reference: http://en.wikipedia.org/wiki/List_of_DOS_commands completer = CompleterNG({ "copy": None, "del": None, "dir": None, @@ -76,5 +101,11 @@ def autoCompletion(completion=None): readline.set_completer(completer.complete) readline.parse_and_bind("tab: complete") + elif commands: + completer = CompleterNG(dict(((_, None) for _ in commands))) + readline.set_completer_delims(' ') + readline.set_completer(completer.complete) + readline.parse_and_bind("tab: complete") + loadHistory() atexit.register(saveHistory) diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py index ead78b126..6a7777023 100644 --- a/lib/parse/cmdline.py +++ b/lib/parse/cmdline.py @@ -6,6 +6,7 @@ See the file 'doc/COPYING' for copying permission """ import os +import shlex import sys from optparse import OptionError @@ -17,13 +18,21 @@ from lib.core.common import checkDeprecatedOptions from lib.core.common import checkSystemEncoding from lib.core.common import expandMnemonics from lib.core.common import getUnicode +from lib.core.data import cmdLineOptions +from lib.core.data import conf from lib.core.data import logger from lib.core.defaults import defaults +from lib.core.enums import AUTOCOMPLETE_TYPE +from lib.core.exception import SqlmapShellQuitException from lib.core.settings import BASIC_HELP_ITEMS from lib.core.settings import DUMMY_URL from lib.core.settings import IS_WIN from lib.core.settings import MAX_HELP_OPTION_LENGTH from lib.core.settings import VERSION_STRING +from lib.core.shell import autoCompletion +from lib.core.shell import clearHistory +from lib.core.shell import loadHistory +from lib.core.shell import saveHistory def cmdLineParser(): """ @@ -693,6 +702,9 @@ def cmdLineParser(): action="store_true", help="Conduct through tests only if positive heuristic(s)") + miscellaneous.add_option("--sqlmap-shell", dest="sqlmapShell", action="store_true", + help="Prompt for an interactive sqlmap shell") + miscellaneous.add_option("--wizard", dest="wizard", action="store_true", help="Simple wizard interface for beginner users") @@ -765,22 +777,25 @@ def cmdLineParser(): option = parser.get_option("-h") option.help = option.help.capitalize().replace("this help", "basic help") - args = [] + argv = [] + prompt = False advancedHelp = True for arg in sys.argv: - args.append(getUnicode(arg, system=True)) + argv.append(getUnicode(arg, system=True)) - checkDeprecatedOptions(args) + checkDeprecatedOptions(argv) # Hide non-basic options in basic help case for i in xrange(len(sys.argv)): - if sys.argv[i] == '-hh': - sys.argv[i] = '-h' - elif sys.argv[i] == '--version': + if sys.argv[i] == "-hh": + sys.argv[i] = "-h" + elif sys.argv[i] == "--version": print VERSION_STRING raise SystemExit - elif sys.argv[i] == '-h': + elif sys.argv[i] == "--sqlmap-shell": + prompt = True + elif sys.argv[i] == "-h": advancedHelp = False for group in parser.option_groups[:]: found = False @@ -792,17 +807,56 @@ def cmdLineParser(): if not found: parser.option_groups.remove(group) + if prompt: + cmdLineOptions.sqlmapShell = True + + _ = ["x", "q", "exit", "quit", "clear"] + for group in parser.option_groups: + for option in group.option_list: + _.extend(option._long_opts) + _.extend(option._short_opts) + + autoCompletion(AUTOCOMPLETE_TYPE.SQLMAP, commands=_) + + while True: + command = None + + try: + command = raw_input("sqlmap-shell> ").strip() + except (KeyboardInterrupt, EOFError): + print + raise SqlmapShellQuitException + + if not command: + continue + elif command.lower() == "clear": + clearHistory() + print "[i] history cleared" + saveHistory() + elif command.lower() in ("x", "q", "exit", "quit"): + raise SqlmapShellQuitException + elif command[0] != '-': + print "[!] invalid option(s) provided" + print "[i] proper example: '-u http://www.site.com/vuln.php?id=1 --banner'" + else: + saveHistory() + loadHistory() + break + + for arg in shlex.split(command): + argv.append(getUnicode(arg, system=True)) + try: - (args, _) = parser.parse_args(args) + (args, _) = parser.parse_args(argv) except SystemExit: - if '-h' in sys.argv and not advancedHelp: + if "-h" in sys.argv and not advancedHelp: print "\n[!] to see full list of options run with '-hh'" raise # Expand given mnemonic options (e.g. -z "ign,flu,bat") - for i in xrange(len(sys.argv) - 1): - if sys.argv[i] == '-z': - expandMnemonics(sys.argv[i + 1], parser, args) + for i in xrange(len(argv) - 1): + if argv[i] == "-z": + expandMnemonics(argv[i + 1], parser, args) if args.dummy: args.url = args.url or DUMMY_URL diff --git a/lib/takeover/abstraction.py b/lib/takeover/abstraction.py index 030697560..4f51616e2 100644 --- a/lib/takeover/abstraction.py +++ b/lib/takeover/abstraction.py @@ -15,6 +15,7 @@ from lib.core.data import conf from lib.core.data import logger from lib.core.enums import AUTOCOMPLETE_TYPE from lib.core.enums import DBMS +from lib.core.enums import OS from lib.core.exception import SqlmapFilePathException from lib.core.exception import SqlmapUnsupportedFeatureException from lib.core.shell import autoCompletion @@ -117,7 +118,7 @@ class Abstraction(Web, UDF, Xp_cmdshell): infoMsg += "'x' or 'q' and press ENTER" logger.info(infoMsg) - autoCompletion(AUTOCOMPLETE_TYPE.OS) + autoCompletion(AUTOCOMPLETE_TYPE.OS, OS.WINDOWS if Backend.isOs(OS.WINDOWS) else OS.LINUX) while True: command = None diff --git a/sqlmap.py b/sqlmap.py index 5a889d5f9..bb68597e5 100755 --- a/sqlmap.py +++ b/sqlmap.py @@ -33,6 +33,7 @@ from lib.core.data import logger from lib.core.data import paths from lib.core.common import unhandledExceptionMessage from lib.core.exception import SqlmapBaseException +from lib.core.exception import SqlmapShellQuitException from lib.core.exception import SqlmapSilentQuitException from lib.core.exception import SqlmapUserQuitException from lib.core.option import initOptions @@ -101,7 +102,10 @@ def main(): except (SqlmapSilentQuitException, bdb.BdbQuit): pass - except SqlmapBaseException, ex: + except SqlmapShellQuitException: + cmdLineOptions.sqlmapShell = False + + except SqlmapBaseException as ex: errMsg = getUnicode(ex.message) logger.critical(errMsg) sys.exit(1) @@ -138,6 +142,12 @@ def main(): except KeyboardInterrupt: pass + if cmdLineOptions.get("sqlmapShell"): + cmdLineOptions.clear() + conf.clear() + kb.clear() + main() + if hasattr(conf, "api"): try: conf.database_cursor.disconnect()