#!/usr/bin/env python """ Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org) See the file 'LICENSE' for copying permission """ import os import subprocess import sys import tempfile import threading import webbrowser from lib.core.common import getSafeExString from lib.core.common import saveConfig from lib.core.data import paths from lib.core.defaults import defaults from lib.core.enums import MKSTEMP_PREFIX from lib.core.exception import SqlmapMissingDependence from lib.core.exception import SqlmapSystemException from lib.core.settings import DEV_EMAIL_ADDRESS from lib.core.settings import IS_WIN from lib.core.settings import ISSUES_PAGE from lib.core.settings import GIT_PAGE from lib.core.settings import SITE from lib.core.settings import VERSION_STRING from lib.core.settings import WIKI_PAGE from thirdparty.six.moves import queue as _queue # Classic Windows (NT/9x) palette: silver 3D face, navy title/selection, white sunken fields, # black text, and saturated VGA-style accents for the icons (presentation only) PALETTE = { "base": "#c0c0c0", # window / control face (silver) "mantle": "#c0c0c0", # bars (classic is uniform gray, separated by bevels) "crust": "#ffffff", # console / edit background "surface0": "#ffffff", # field (edit) background "surface1": "#808080", # 3D shadow "surface2": "#dfdfdf", # 3D light (soft) "light": "#ffffff", # 3D highlight "dark": "#404040", # 3D dark shadow "text": "#000000", "subtext": "#000000", "overlay": "#404040", "title2": "#1084d0", # active title-bar gradient end "blue": "#000080", # navy: title, selection, accents "sapphire": "#0050b0", "sky": "#0070c0", "green": "#008000", "teal": "#008080", "red": "#c00000", "maroon": "#800000", "mauve": "#9000a8", "pink": "#c000b0", "peach": "#c06000", "yellow": "#c08000", "lavender": "#4858c0", "flamingo": "#c04070", "gold": "#e0a800", } # a distinct accent color per section, so the sidebar icons read as a colorful, scannable set ICON_COLORS = { "Quick start": "yellow", "Target": "red", "Request": "sapphire", "Optimization": "teal", "Injection": "mauve", "Detection": "sky", "Techniques": "maroon", "Fingerprint": "lavender", "Enumeration": "green", "Brute force": "peach", "User-defined function injection": "pink", "File system access": "gold", "Operating system access": "blue", "Windows registry access": "sapphire", "General": "teal", "Miscellaneous": "overlay", } # Options surfaced on the curated "Quick start" pane (by destination), in display order QUICK_START_DESTS = ( "data", "cookie", "dbms", "level", "risk", "technique", "getCurrentUser", "getCurrentDb", "getBanner", "isDba", "getDbs", "getTables", "getColumns", "getPasswordHashes", "dumpTable", "batch", "threads", "proxy", "tor", ) # Short, readable sidebar labels for the (sometimes verbose) option-group titles NAV_ALIASES = { "User-defined function injection": "UDF injection", "Operating system access": "OS access", "Windows registry access": "Windows registry", "File system access": "File system", } TARGET_PLACEHOLDER = "http://www.target.com/vuln.php?id=1" HINT_DEFAULT = "Hover or focus a field to see what it does." # --- parser-backend compatibility (works for both optparse and argparse objects) --- def _parserGroups(parser): groups = getattr(parser, "option_groups", None) if groups is None: groups = [_ for _ in getattr(parser, "_action_groups", []) if getattr(_, "title", None) not in (None, "positional arguments", "optional arguments", "options")] return groups or [] def _groupOptions(group): for attr in ("option_list", "_group_actions"): if hasattr(group, attr): return getattr(group, attr) return [] def _groupTitle(group): return getattr(group, "title", "") or "" def _groupDescription(group): if hasattr(group, "get_description"): return group.get_description() or "" return getattr(group, "description", "") or "" def _optStrings(option): if hasattr(option, "option_strings"): # argparse return list(option.option_strings) return list(getattr(option, "_short_opts", None) or []) + list(getattr(option, "_long_opts", None) or []) def _optDest(option): return getattr(option, "dest", None) def _optHelp(option): return getattr(option, "help", "") or "" def _optChoices(option): return getattr(option, "choices", None) def _optTakesValue(option): if hasattr(option, "takes_value"): # optparse Option try: return option.takes_value() except Exception: pass return getattr(option, "nargs", 1) != 0 # argparse: store_true/false has nargs 0 def _optValueType(option): kind = getattr(option, "type", None) if kind in ("int", int): return "int" if kind in ("float", float): return "float" return "string" def _optionLabel(option): return ", ".join(_optStrings(option)) or (_optDest(option) or "") class _Tooltip(object): """Lightweight hover tooltip for a widget""" def __init__(self, widget, text, tk, palette): self._widget = widget self._text = text self._tk = tk self._palette = palette self._tip = None widget.bind("", self._show, add="+") widget.bind("", self._hide, add="+") widget.bind("", self._hide, add="+") def _show(self, event=None): if self._tip or not self._text: return x = self._widget.winfo_rootx() + 18 y = self._widget.winfo_rooty() + self._widget.winfo_height() + 6 self._tip = tw = self._tk.Toplevel(self._widget) tw.wm_overrideredirect(True) tw.wm_geometry("+%d+%d" % (x, y)) self._tk.Label(tw, text=self._text, justify="left", background=self._palette["surface0"], foreground=self._palette["text"], relief="flat", borderwidth=0, wraplength=460, padx=10, pady=7).pack() def _hide(self, event=None): if self._tip: self._tip.destroy() self._tip = None class SqlmapGui(object): def __init__(self, parser, tk, ttk, scrolledtext, messagebox, filedialog, font): self.parser = parser self.tk = tk self.ttk = ttk self.scrolledtext = scrolledtext self.messagebox = messagebox self.filedialog = filedialog self.font = font self.widgets = {} # dest -> (type, shared Tk variable) self.vars = {} # dest -> shared Tk variable (one per option, bound to every widget for it) self.optionByDest = {} for group in _parserGroups(parser): for option in _groupOptions(group): if _optDest(option): self.optionByDest[_optDest(option)] = option self.panes = {} # name -> outer frame self.navItems = {} # name -> (row frame, accent strip, icon canvas, label) self.canvases = {} # name -> canvas (for wheel binding) self.inners = {} # name -> scrollable inner frame (populated lazily) self.builders = {} # name -> callable that populates the inner frame self.built = set() # names whose content has been built self.paneOrder = [] # nav order, for Up/Down navigation self.currentPane = None self.process = None self.alive = False self.queue = None try: self.window = tk.Tk() except Exception as ex: raise SqlmapSystemException("unable to create GUI window ('%s')" % getSafeExString(ex)) self._initFonts() self._initStyle() self._buildLayout() def _initFonts(self): family = self.font.nametofont("TkDefaultFont").actual("family") self.fonts = { "body": (family, 10), "bodyBold": (family, 10, "bold"), "small": (family, 9), "nav": (family, 10), "title": (family, 18, "bold"), "subtitle": (family, 9), "mono": (self.font.nametofont("TkFixedFont").actual("family"), 10), } def _initStyle(self): p = PALETTE face, light, light2, shadow, dark = p["base"], p["light"], p["surface2"], p["surface1"], p["dark"] navy, white, black, field = p["blue"], "#ffffff", p["text"], p["surface0"] style = self.ttk.Style() if "clam" in style.theme_names(): style.theme_use("clam") style.configure(".", background=face, foreground=black, fieldbackground=field, bordercolor=shadow, lightcolor=light, darkcolor=shadow, troughcolor=face, focuscolor=face, insertcolor=black, font=self.fonts["body"]) for name in ("TFrame", "Bar.TFrame", "Nav.TFrame", "Card.TFrame"): style.configure(name, background=face) style.configure("TLabel", background=face, foreground=black) style.configure("Title.TLabel", background=navy, foreground=white, font=self.fonts["title"]) style.configure("Subtitle.TLabel", background=navy, foreground=white, font=self.fonts["subtitle"]) style.configure("Hint.TLabel", background=face, foreground=p["overlay"], font=self.fonts["small"]) style.configure("Field.TLabel", background=face, foreground=black) style.configure("Desc.TLabel", background=face, foreground=p["overlay"], font=self.fonts["small"]) style.configure("Pane.TLabel", background=face, foreground=navy, font=self.fonts["title"]) style.configure("Stat.TLabel", background=face, foreground=p["overlay"], font=self.fonts["small"]) style.configure("Prompt.TLabel", background=field, foreground=black, font=self.fonts["mono"]) # classic raised 3D push button style.configure("TButton", background=face, foreground=black, relief="raised", borderwidth=2, lightcolor=light, darkcolor=dark, bordercolor=shadow, focuscolor=black, padding=(12, 4)) style.map("TButton", background=[("active", face)], relief=[("pressed", "sunken")]) # sunken white edit fields for name in ("TEntry", "Target.TEntry"): style.configure(name, fieldbackground=field, foreground=black, relief="sunken", borderwidth=2, bordercolor=shadow, lightcolor=shadow, darkcolor=light, insertcolor=black, padding=4) style.configure("TCheckbutton", background=face, foreground=black, focuscolor=face, padding=2, indicatorbackground=field, indicatorforeground=black, indicatorrelief="sunken", indicatorborderwidth=2, bordercolor=shadow, lightcolor=shadow, darkcolor=light) style.map("TCheckbutton", background=[("active", face)], indicatorbackground=[("active", field), ("selected", field)]) style.configure("TCombobox", fieldbackground=field, background=face, foreground=black, arrowcolor=black, relief="sunken", borderwidth=2, bordercolor=shadow, lightcolor=shadow, darkcolor=light, padding=3) # classic chunky scrollbar (raised gray thumb, light trough) style.configure("Vertical.TScrollbar", background=face, troughcolor=light2, bordercolor=shadow, lightcolor=light, darkcolor=dark, arrowcolor=black, relief="raised", width=17) style.map("Vertical.TScrollbar", background=[("active", face)]) self.window.configure(background=face) # --- layout --------------------------------------------------------- def _buildLayout(self): tk = self.tk self.window.title("sqlmap") self.window.minsize(960, 680) self._buildMenu() self._buildHeader() target = self.ttk.Frame(self.window, style="Bar.TFrame", padding=(20, 12, 20, 14)) target.pack(fill=tk.X) labelRow = self.ttk.Frame(target, style="Bar.TFrame") labelRow.pack(fill=tk.X, pady=(0, 4)) self.ttk.Label(labelRow, text="TARGET URL", style="Hint.TLabel").pack(side=tk.LEFT) self.ttk.Label(labelRow, text=" e.g. %s" % TARGET_PLACEHOLDER, style="Stat.TLabel").pack(side=tk.LEFT) urlVar = self._destVar("url", False) self.targetEntry = self.ttk.Entry(target, style="Target.TEntry", textvariable=urlVar) self.targetEntry.pack(fill=tk.X, ipady=2) self.widgets["url"] = ("string", urlVar) body = self.ttk.Frame(self.window, style="TFrame") body.pack(expand=True, fill=tk.BOTH) navHolder = self.ttk.Frame(body, style="Nav.TFrame", width=202) navHolder.pack(side=tk.LEFT, fill=tk.Y) navHolder.pack_propagate(False) self.navCanvas = tk.Canvas(navHolder, background=PALETTE["mantle"], highlightthickness=0, borderwidth=0) navScroll = self.ttk.Scrollbar(navHolder, orient="vertical", command=self.navCanvas.yview, style="Vertical.TScrollbar") self.nav = self.ttk.Frame(self.navCanvas, style="Nav.TFrame") self.nav.bind("", lambda e: self.navCanvas.configure(scrollregion=self.navCanvas.bbox("all"))) navWin = self.navCanvas.create_window((0, 0), window=self.nav, anchor="nw") self.navCanvas.bind("", lambda e: self.navCanvas.itemconfigure(navWin, width=e.width)) self.navCanvas.configure(yscrollcommand=navScroll.set) self.navCanvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) navScroll.pack(side=tk.RIGHT, fill=tk.Y) tk.Frame(body, background=PALETTE["surface1"], width=1).pack(side=tk.LEFT, fill=tk.Y) self.content = self.ttk.Frame(body, style="Card.TFrame") self.content.pack(side=tk.LEFT, expand=True, fill=tk.BOTH) cmdBar = self.ttk.Frame(self.window, style="Bar.TFrame", padding=(20, 8)) cmdBar.pack(fill=tk.X) self.ttk.Label(cmdBar, text="Command:", style="Hint.TLabel").pack(side=tk.LEFT, padx=(0, 8)) self.command = tk.StringVar(value="sqlmap.py") cmdEntry = tk.Entry(cmdBar, textvariable=self.command, font=self.fonts["mono"], bg="#ffffff", fg=PALETTE["blue"], readonlybackground="#ffffff", disabledforeground=PALETTE["blue"], relief="sunken", borderwidth=2, highlightthickness=0, state="readonly") cmdEntry.pack(side=tk.LEFT, fill=tk.X, expand=True) hintBar = self.ttk.Frame(self.window, style="Bar.TFrame", padding=(20, 9)) hintBar.pack(fill=tk.X) self.stat = tk.StringVar(value="") self.ttk.Label(hintBar, textvariable=self.stat, style="Stat.TLabel", anchor="e").pack(side=tk.RIGHT, padx=(12, 0)) self.hint = tk.StringVar(value=HINT_DEFAULT) self.ttk.Label(hintBar, textvariable=self.hint, style="Hint.TLabel", anchor="w").pack(side=tk.LEFT, fill=tk.X, expand=True) self._buildQuickStartPane() for group in _parserGroups(self.parser): self._buildGroupPane(group) self._selectPane("Quick start") self.window.bind("", lambda e: self._navKey(1)) self.window.bind("", lambda e: self._navKey(-1)) for seq in ("", "", ""): self.window.bind_all(seq, self._onWheel) self._enableSelectAll() self._tickStats() self._prebuildPanes() self._center(self.window, 1000, 720) def _prebuildPanes(self): # Tk isn't thread-safe, so widgets must be built on the main thread; instead of blocking, # build the not-yet-visited panes one per idle tick so they are ready (instant) by the time # the user navigates to them, while the UI stays responsive (on-demand build is the fallback) pending = [_ for _ in self.paneOrder if _ not in self.built] def step(): while pending and pending[0] in self.built: pending.pop(0) if not pending: return name = pending.pop(0) try: self.builders[name](self.inners[name]) self.built.add(name) except Exception: pass if pending: self.window.after(30, step) self.window.after(250, step) def _enableSelectAll(self): # Tk binds Ctrl-A to "cursor to line start" by default; rebind it to select-all, # which is what users expect (covers entries, comboboxes and the console text widget) def selectEntry(event): try: event.widget.select_range(0, "end") event.widget.icursor("end") except Exception: pass return "break" def selectText(event): try: event.widget.tag_add("sel", "1.0", "end-1c") except Exception: pass return "break" for cls in ("TEntry", "Entry", "TCombobox"): self.window.bind_class(cls, "", selectEntry) self.window.bind_class(cls, "", selectEntry) for seq in ("", ""): self.window.bind_class("Text", seq, selectText) def _buildMenu(self): p = PALETTE menubar = self.tk.Menu(self.window, bg=p["mantle"], fg=p["text"], activebackground=p["surface0"], activeforeground=p["text"], borderwidth=0) filemenu = self.tk.Menu(menubar, tearoff=0, bg=p["mantle"], fg=p["text"], activebackground=p["surface0"], activeforeground=p["text"]) filemenu.add_command(label="Load configuration...", command=self.loadConfig) filemenu.add_command(label="Save configuration...", command=self.saveConfigDialog) filemenu.add_separator() filemenu.add_command(label="Exit", command=self.window.quit) menubar.add_cascade(label="File", menu=filemenu) menubar.add_command(label="Run", command=self.run) helpmenu = self.tk.Menu(menubar, tearoff=0, bg=p["mantle"], fg=p["text"], activebackground=p["surface0"], activeforeground=p["text"]) helpmenu.add_command(label="Official site", command=lambda: webbrowser.open(SITE)) helpmenu.add_command(label="GitHub", command=lambda: webbrowser.open(GIT_PAGE)) helpmenu.add_command(label="Wiki", command=lambda: webbrowser.open(WIKI_PAGE)) helpmenu.add_command(label="Report issue", command=lambda: webbrowser.open(ISSUES_PAGE)) helpmenu.add_separator() helpmenu.add_command(label="About", command=lambda: self.messagebox.showinfo("About", "%s\n\n (%s)" % (VERSION_STRING, DEV_EMAIL_ADDRESS))) menubar.add_cascade(label="Help", menu=helpmenu) self.window.config(menu=menubar) def _buildHeader(self): self._runHover = False self.header = self.tk.Canvas(self.window, height=76, highlightthickness=0, borderwidth=0, background=PALETTE["base"]) self.header.pack(fill=self.tk.X) self.header.bind("", lambda e: self._drawHeader()) def _interp(self, color1, color2, ratio): a = [int(color1[_:_ + 2], 16) for _ in (1, 3, 5)] b = [int(color2[_:_ + 2], 16) for _ in (1, 3, 5)] return "#%02x%02x%02x" % tuple(int(a[_] + (b[_] - a[_]) * ratio) for _ in range(3)) def _drawHeader(self): p = PALETTE c = self.header c.delete("all") width = c.winfo_width() height = 76 steps = max(1, width // 4) for i in range(steps): c.create_rectangle(i * width / steps, 0, (i + 1) * width / steps + 1, height, outline="", fill=self._interp(p["blue"], p["title2"], i / float(steps))) c.create_text(24, 27, text="sqlmap", anchor="w", fill="#ffffff", font=self.fonts["title"]) c.create_text(122, 31, text=VERSION_STRING.replace("sqlmap/", "v"), anchor="w", fill="#c7d8ef", font=self.fonts["subtitle"]) c.create_text(24, 54, text="automatic SQL injection and database takeover tool", anchor="w", fill="#dfe8f6", font=self.fonts["small"]) self._drawRunButton(width, height) def _drawRunButton(self, width, height): p = PALETTE c = self.header bw, bh = 116, 34 x0 = width - bw - 22 y0 = (height - bh) // 2 x1, y1 = x0 + bw, y0 + bh c.create_rectangle(x0, y0, x1, y1, fill=p["base"], outline="", tags=("runbtn", "runpill")) # classic raised 3D bevel (white top/left, dark bottom/right) c.create_line(x0, y0, x1, y0, fill="#ffffff", tags="runbtn") c.create_line(x0, y0, x0, y1, fill="#ffffff", tags="runbtn") c.create_line(x0, y1, x1, y1, fill=p["dark"], tags="runbtn") c.create_line(x1, y0, x1, y1 + 1, fill=p["dark"], tags="runbtn") c.create_line(x0 + 1, y1 - 1, x1 - 1, y1 - 1, fill=p["surface1"], tags="runbtn") c.create_line(x1 - 1, y0 + 1, x1 - 1, y1 - 1, fill=p["surface1"], tags="runbtn") cy = (y0 + y1) // 2 tx = x0 + 24 c.create_polygon(tx, cy - 6, tx, cy + 6, tx + 10, cy, fill=p["blue"], outline="", tags=("runbtn", "runico")) c.create_text((x0 + x1) // 2 + 8, cy, text="Run", fill=p["text"], font=self.fonts["bodyBold"], tags=("runbtn", "runico")) c.tag_bind("runbtn", "", lambda e: self.run()) c.tag_bind("runbtn", "", lambda e: self._hoverRun(True)) c.tag_bind("runbtn", "", lambda e: self._hoverRun(False)) def _hoverRun(self, on): self._runHover = on self.header.itemconfigure("runpill", fill="#ccccc6" if on else PALETTE["base"]) try: self.header.configure(cursor="hand2" if on else "") except Exception: pass def _drawIcon(self, c, name, col): # minimal line-art icons, drawn as vectors so they render everywhere and need no assets c.delete("all") def line(*pts, **kw): c.create_line(*pts, fill=col, width=2, capstyle="round", joinstyle="round", **kw) def oval(x0, y0, x1, y1, filled=False): c.create_oval(x0, y0, x1, y1, outline=col, width=2, fill=(col if filled else "")) def rect(x0, y0, x1, y1, filled=False): c.create_rectangle(x0, y0, x1, y1, outline=col, width=2, fill=(col if filled else "")) def poly(*pts): c.create_polygon(*pts, fill=col, outline="") def arc(x0, y0, x1, y1, start, extent): c.create_arc(x0, y0, x1, y1, start=start, extent=extent, outline=col, width=2, style="arc") def dot(x, y, r=2): c.create_oval(x - r, y - r, x + r, y + r, fill=col, outline="") def glyph(text, size=11): c.create_text(11, 11, text=text, fill=col, font=(self.fonts["bodyBold"][0], size, "bold")) if name == "Quick start": poly(12, 3, 6, 12, 10, 12, 9, 19, 16, 9, 11, 9) elif name == "Target": oval(4, 4, 18, 18) dot(11, 11, 2) elif name == "Request": line(4, 8, 17, 8, arrow="last") line(18, 14, 5, 14, arrow="last") elif name == "Optimization": arc(4, 6, 18, 20, 0, 180) line(11, 13, 15, 8) elif name == "Injection": # syringe: thumb rest + plunger rod + flange + barrel + needle (no arrowhead, so it reads as a needle not a cross) line(9, 2, 13, 2) line(11, 2, 11, 5) line(6, 5, 16, 5) rect(8, 5, 14, 14) line(11, 14, 11, 20) elif name == "Detection": oval(4, 4, 13, 13) line(12, 12, 18, 18) elif name == "Techniques": oval(7, 7, 15, 15) line(11, 2, 11, 6) line(11, 16, 11, 20) line(2, 11, 6, 11) line(16, 11, 20, 11) elif name == "Fingerprint": # tightly nested tall loops with the gap at the bottom (fingertip ridges), plus a central core arc(3, 1, 19, 21, 285, 330) arc(5, 4, 17, 18, 285, 330) arc(7, 7, 15, 15, 285, 330) arc(9, 10, 13, 12, 285, 330) elif name == "Enumeration": oval(4, 3, 18, 7) line(4, 5, 4, 16) line(18, 5, 18, 16) arc(4, 12, 18, 18, 180, 180) elif name == "Brute force": oval(3, 7, 11, 15) line(9, 11, 19, 11) line(16, 11, 16, 15) line(19, 11, 19, 14) elif name == "User-defined function injection": glyph("fx", 11) elif name == "File system access": poly(3, 7, 8, 7, 10, 9, 19, 9, 19, 17, 3, 17) elif name == "Operating system access": rect(3, 5, 19, 17) line(6, 9, 9, 11) line(6, 13, 9, 13) elif name == "Windows registry access": # the waving Windows flag (4 slanted panes) rather than a plain 2x2 grid poly(4, 6, 10, 5, 10, 11, 4, 12) poly(12, 5, 18, 4, 18, 10, 12, 11) poly(4, 13, 10, 12, 10, 18, 4, 19) poly(12, 12, 18, 11, 18, 17, 12, 18) elif name == "General": line(4, 6, 18, 6) dot(14, 6) line(4, 11, 18, 11) dot(8, 11) line(4, 16, 18, 16) dot(13, 16) elif name == "Miscellaneous": dot(5, 11) dot(11, 11) dot(17, 11) else: dot(11, 11, 3) def _addPane(self, name, navText): p = PALETTE tk = self.tk row = tk.Frame(self.nav, background=p["mantle"]) row.pack(fill=tk.X) strip = tk.Frame(row, background=p["mantle"], width=3) strip.pack(side=tk.LEFT, fill=tk.Y) icon = tk.Canvas(row, width=22, height=22, highlightthickness=0, borderwidth=0, background=p["mantle"]) icon.pack(side=tk.LEFT, padx=(13, 0), pady=8) self._drawIcon(icon, name, self._iconColor(name)) lab = tk.Label(row, text=navText, background=p["mantle"], foreground=p["subtext"], font=self.fonts["nav"], anchor="w", padx=10, pady=9) lab.pack(side=tk.LEFT, fill=tk.X, expand=True) for w in (row, lab, strip, icon): w.bind("", lambda e, n=name: self._selectPane(n)) w.bind("", lambda e, n=name: self._navHover(n, True)) w.bind("", lambda e, n=name: self._navHover(n, False)) self.navItems[name] = (row, strip, icon, lab) self.paneOrder.append(name) outer = self.ttk.Frame(self.content, style="Card.TFrame") canvas = tk.Canvas(outer, background=p["base"], highlightthickness=0, borderwidth=0) scrollbar = self.ttk.Scrollbar(outer, orient="vertical", command=canvas.yview, style="Vertical.TScrollbar") inner = self.ttk.Frame(canvas, style="Card.TFrame", padding=(24, 20)) inner.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))) window_id = canvas.create_window((0, 0), window=inner, anchor="nw") canvas.bind("", lambda e: canvas.itemconfigure(window_id, width=e.width)) canvas.configure(yscrollcommand=scrollbar.set) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") self.panes[name] = outer self.canvases[name] = canvas self.inners[name] = inner return inner def _iconColor(self, name): return PALETTE.get(ICON_COLORS.get(name, "subtext"), PALETTE["subtext"]) def _navHover(self, name, entering): if name == self.currentPane: return bg = PALETTE["surface2"] if entering else PALETTE["mantle"] row, strip, icon, lab = self.navItems[name] for w in (row, strip, icon, lab): w.configure(background=bg) def _navKey(self, delta): try: focused = self.window.focus_get() except Exception: focused = None if isinstance(focused, (self.ttk.Entry, self.ttk.Combobox)): return None if self.paneOrder: index = self.paneOrder.index(self.currentPane) self._selectPane(self.paneOrder[(index + delta) % len(self.paneOrder)]) return "break" def _selectPane(self, name): if name not in self.built: # lazy: populate the pane on first visit self.builders[name](self.inners[name]) self.built.add(name) if self.currentPane == name: return p = PALETTE if self.currentPane: self.panes[self.currentPane].pack_forget() row, strip, icon, lab = self.navItems[self.currentPane] for w in (row, strip, icon): w.configure(background=p["mantle"]) lab.configure(background=p["mantle"], foreground=p["text"], font=self.fonts["nav"]) self._drawIcon(icon, self.currentPane, self._iconColor(self.currentPane)) self.panes[name].pack(expand=True, fill=self.tk.BOTH) row, strip, icon, lab = self.navItems[name] for w in (row, strip, icon): w.configure(background=p["blue"]) lab.configure(background=p["blue"], foreground="#ffffff", font=self.fonts["bodyBold"]) self._drawIcon(icon, name, "#ffffff") self.currentPane = name self._ensureNavVisible(name) if hasattr(self, "hint"): # don't leave the previous section's option hint lingering self.hint.set(HINT_DEFAULT) def _ensureNavVisible(self, name): # scroll the sidebar so the active item stays in view (e.g. when paging with Up/Down) try: row = self.navItems[name][0] self.nav.update_idletasks() total = self.nav.winfo_height() viewH = self.navCanvas.winfo_height() if total <= 1 or viewH <= 1: return top = row.winfo_y() bottom = top + row.winfo_height() curTop = self.navCanvas.yview()[0] * total if top < curTop: self.navCanvas.yview_moveto(float(top) / total) elif bottom > curTop + viewH: self.navCanvas.yview_moveto(float(bottom - viewH) / total) except Exception: pass def _onWheel(self, event): # route the wheel to whichever scroll region the pointer is over (sidebar or content) delta = 1 if getattr(event, "num", None) == 5 or getattr(event, "delta", 0) < 0 else -1 target = None node = self.window.winfo_containing(event.x_root, event.y_root) while node is not None: if node is self.navCanvas: target = self.navCanvas break if self.currentPane and node is self.canvases.get(self.currentPane): target = self.canvases[self.currentPane] break try: node = node.master except Exception: break if target is None and self.currentPane: target = self.canvases.get(self.currentPane) if target is not None: target.yview_scroll(delta, "units") return "break" def _buildQuickStartPane(self): name = "Quick start" self._addPane(name, name) def build(inner): self.ttk.Label(inner, text="Quick start", style="Pane.TLabel").grid(row=0, column=0, columnspan=2, sticky="w") self.ttk.Label(inner, text="The options people reach for most. Set the target above, tick what you want, then Run.", style="Desc.TLabel", wraplength=640, justify="left").grid(row=1, column=0, columnspan=2, sticky="w", pady=(2, 14)) row = 2 for dest in QUICK_START_DESTS: option = self.optionByDest.get(dest) if option is not None: row = self._buildFieldRow(inner, option, row) inner.columnconfigure(1, weight=1) self.builders[name] = build def _buildGroupPane(self, group): title = _groupTitle(group) self._addPane(title, NAV_ALIASES.get(title, title)) def build(inner, group=group, title=title): self.ttk.Label(inner, text=title, style="Pane.TLabel").grid(row=0, column=0, columnspan=2, sticky="w") row = 1 description = _groupDescription(group) if description: self.ttk.Label(inner, text=description, style="Desc.TLabel", wraplength=640, justify="left").grid( row=row, column=0, columnspan=2, sticky="w", pady=(2, 14)) row += 1 for option in _groupOptions(group): row = self._buildFieldRow(inner, option, row) inner.columnconfigure(1, weight=1) self.builders[title] = build def _destVar(self, dest, is_bool): # one shared variable per option, so every widget that edits it (Quick start pane, # the proper group pane, the target bar) reflects into the same value both ways if dest not in self.vars: self.vars[dest] = self.tk.IntVar() if is_bool else self.tk.StringVar() return self.vars[dest] def _buildFieldRow(self, parent, option, row): p = PALETTE tk = self.tk label = _optionLabel(option) helptext = _optHelp(option) dest = _optDest(option) is_bool = not _optTakesValue(option) firstSeen = dest not in self.vars def bindHint(widget): widget.bind("", lambda e: self.hint.set(helptext), add="+") widget.bind("", lambda e: self.hint.set(helptext), add="+") widget.bind("", lambda e: self.hint.set(HINT_DEFAULT), add="+") widget.bind("", lambda e: self.hint.set(HINT_DEFAULT), add="+") if is_bool: var = self._destVar(dest, True) chk = self.ttk.Checkbutton(parent, text=label, variable=var, takefocus=True) chk.grid(row=row, column=0, columnspan=2, sticky="w", pady=5) _Tooltip(chk, helptext, tk, p) bindHint(chk) if firstSeen: self.widgets[dest] = ("bool", var) else: otype = _optValueType(option) var = self._destVar(dest, False) if firstSeen: default = defaults.get(dest) if default not in (None, False): var.set(default) self.widgets[dest] = (otype, var) lab = self.ttk.Label(parent, text=label, style="Field.TLabel") lab.grid(row=row, column=0, sticky="w", padx=(0, 18), pady=6) _Tooltip(lab, helptext, tk, p) bindHint(lab) choices = _optChoices(option) if choices: widget = self.ttk.Combobox(parent, values=list(choices), state="readonly", textvariable=var) else: widget = self.ttk.Entry(parent, textvariable=var) if otype in ("int", "float"): self._constrain(widget, otype) widget.grid(row=row, column=1, sticky="ew", pady=6) _Tooltip(widget, helptext, tk, p) bindHint(widget) return row + 1 def _constrain(self, entry, otype): check = (lambda s: s == "" or s.replace(".", "", 1).isdigit()) if otype == "float" else (lambda s: s == "" or s.isdigit()) vcmd = (self.window.register(lambda proposed: bool(check(proposed))), "%P") entry.configure(validate="key", validatecommand=vcmd) # --- helpers -------------------------------------------------------- def _center(self, window, width=None, height=None): window.update_idletasks() width = width or window.winfo_width() height = height or window.winfo_height() x = window.winfo_screenwidth() // 2 - width // 2 y = window.winfo_screenheight() // 2 - height // 2 window.geometry("%dx%d+%d+%d" % (width, height, x, y)) def _updateStats(self): count = 0 for dest, (otype, var) in self.widgets.items(): try: if otype == "bool": if var.get(): count += 1 else: raw = var.get() if raw not in (None, "") and str(raw) != str(defaults.get(dest, "")): count += 1 except Exception: pass self.stat.set("%d option%s set" % (count, "" if count == 1 else "s")) def _buildCommandString(self): parts = ["sqlmap.py"] for dest, (otype, var) in self.widgets.items(): option = self.optionByDest.get(dest) if option is None: continue strings = _optStrings(option) if not strings: continue flag = strings[0] try: if otype == "bool": if var.get(): parts.append(flag) else: raw = var.get() if raw not in (None, "") and str(raw) != str(defaults.get(dest, "")): value = str(raw) if " " in value or '"' in value: value = '"%s"' % value.replace('"', '\\"') parts.append("%s %s" % (flag, value)) except Exception: pass return " ".join(parts) def _tickStats(self): self._updateStats() self.command.set(self._buildCommandString()) self.window.after(1200, self._tickStats) def _collectConfig(self): config = {} for dest, (otype, var) in self.widgets.items(): try: if otype == "bool": value = bool(var.get()) else: raw = var.get() if raw in (None, ""): value = None elif otype == "int": value = int(raw) elif otype == "float": value = float(raw) else: value = raw except Exception: value = None config[dest] = value for option in self.optionByDest.values(): dest = _optDest(option) if config.get(dest) is None: config[dest] = defaults.get(dest, None) return config def _setWidgetValue(self, dest, value): if dest not in self.widgets: return otype, var = self.widgets[dest] try: if otype == "bool": var.set(1 if value else 0) else: var.set("" if value in (None, False) else value) except Exception: pass # --- actions -------------------------------------------------------- def loadConfig(self): path = self.filedialog.askopenfilename(title="Load configuration", filetypes=[("sqlmap config", "*.conf *.ini"), ("All files", "*.*")]) if not path: return try: from thirdparty.six.moves import configparser as _configparser parser = _configparser.ConfigParser() parser.read(path) count = 0 for section in parser.sections(): for name, value in parser.items(section): if name in self.widgets: if self.widgets[name][0] == "bool": self._setWidgetValue(name, str(value).lower() in ("1", "true", "yes", "on")) else: self._setWidgetValue(name, value) count += 1 self.hint.set("Loaded %d options from %s" % (count, os.path.basename(path))) except Exception as ex: self.messagebox.showerror("Load failed", getSafeExString(ex)) def saveConfigDialog(self): path = self.filedialog.asksaveasfilename(title="Save configuration", defaultextension=".conf", filetypes=[("sqlmap config", "*.conf")]) if not path: return try: saveConfig(self._collectConfig(), path) self.hint.set("Saved configuration to %s" % os.path.basename(path)) except Exception as ex: self.messagebox.showerror("Save failed", getSafeExString(ex)) def run(self): config = self._collectConfig() handle, configFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.CONFIG, text=True) os.close(handle) saveConfig(config, configFile) self.alive = True self.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) self.queue = _queue.Queue() def enqueue(stream, queue): for line in iter(stream.readline, b''): queue.put(line) self.alive = False stream.close() thread = threading.Thread(target=enqueue, args=(self.process.stdout, self.queue)) thread.daemon = True thread.start() self._openConsole() def _openConsole(self): p = PALETTE tk = self.tk top = tk.Toplevel(self.window) top.title("sqlmap - console") top.configure(background=p["crust"]) frame = self.ttk.Frame(top, style="Card.TFrame", padding=10) frame.configure(style="Card.TFrame") frame.pack(fill=tk.BOTH, expand=True) text = self.scrolledtext.ScrolledText(frame, wrap=tk.WORD, bg=p["crust"], fg=p["text"], insertbackground=p["blue"], relief="flat", borderwidth=0, font=self.fonts["mono"], padx=12, pady=10) text.pack(fill=tk.BOTH, expand=True) text.focus() lineBuffer = {"value": ""} def onKey(event): if self.process: if event.char == "\b": lineBuffer["value"] = lineBuffer["value"][:-1] elif event.char: lineBuffer["value"] += event.char def onReturn(event): if self.process: try: self.process.stdin.write(("%s\n" % lineBuffer["value"].strip()).encode()) self.process.stdin.flush() except Exception: pass lineBuffer["value"] = "" text.insert(tk.END, "\n") return "break" text.bind("", onKey) text.bind("", onReturn) def pump(): drained = False try: while True: line = self.queue.get_nowait() text.insert(tk.END, line.decode("utf-8", errors="replace") if isinstance(line, bytes) else line) drained = True except _queue.Empty: pass if drained: text.see(tk.END) if self.alive or not self.queue.empty(): top.after(80, pump) else: text.insert(tk.END, "\n--- process finished ---\n") text.see(tk.END) self._center(top, 900, 580) top.after(80, pump) def runGui(parser): try: from thirdparty.six.moves import tkinter as _tkinter from thirdparty.six.moves import tkinter_scrolledtext as _scrolledtext from thirdparty.six.moves import tkinter_ttk as _ttk from thirdparty.six.moves import tkinter_messagebox as _messagebox from thirdparty.six.moves import tkinter_filedialog as _filedialog from thirdparty.six.moves import tkinter_font as _font except ImportError as ex: raise SqlmapMissingDependence("missing dependence ('%s')" % getSafeExString(ex)) app = SqlmapGui(parser, _tkinter, _ttk, _scrolledtext, _messagebox, _filedialog, _font) app.window.mainloop()