diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index b1e6dddf2..7a9417871 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -189,7 +189,7 @@ ccc4a717e887652b1fcce073d9409d9c59a3b28548c703a9e453d15845f90cd7 lib/core/patch 9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -a6e15ece62113241870feacc9cda691c64be9b849ce2df169b35ee695a517165 lib/core/settings.py +2db950a79f3f8a4bbb0f35731d4e2eef220150961be55d8ba4b1f9565bdd483a lib/core/settings.py c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py 19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py @@ -249,7 +249,7 @@ da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/craw a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py b0d8ae8513c1f5ffcaa4bf0398790f26bc2180a6acf07bf5b2c86555bf9113f6 lib/utils/dialect.py 51cfab194cd5b6b24d62706fb79db86c852b9e593f4c55c15b35f175e70c9d75 lib/utils/getch.py -417029b70afe672f3121746a7909887aa996766c92547b4566c50343fff76131 lib/utils/gui.py +3c4ad819589fe4fca303706dc87969273a07a04dee85e23f064b39caf1fb80e9 lib/utils/gui.py 972c5db9c9e30ac0f91c0f8d4df4531d0304e151dac99f1399c37c952ba9f935 lib/utils/har.py 0cd3860c03e39bacd1d0fe4cf1a0c605de48ff82f70441319f21d47e38e7e3a9 lib/utils/hashdb.py 71a66ff766a2921106770b26acff380de469222dc893816a7b970b384c927666 lib/utils/hash.py @@ -264,7 +264,7 @@ de4be7e291db0962cd59f9c04b3f7259f846e315df1fd9b323954f89fae0b2db lib/utils/sear 8258d0f54ad94e6101934971af4e55d5540f217c40ddcc594e2fba837b856d35 lib/utils/sgmllib.py 2760c4b82382e501f16bb98edec9531f46e5b286fbf004b346545b9b62f84824 lib/utils/sqlalchemy.py f0e5525a92fe971defc8f74c27942ff9138b1e8251f2e0d9a8bd59285b656084 lib/utils/timeout.py -f19a6761284e689fca7d2e07120193f7b9c4f9c506ecaf87e82c2e97cca4db63 lib/utils/tui.py +f28693d5d2783f3d5069b1df3d12e01730ce783f4a40ef31656ef2c879d2f027 lib/utils/tui.py e430db49aa768ff2cdba76932e30871c366054599c44d91580dde459ab9b6fef lib/utils/versioncheck.py b3c5109394f6c3cdd73a524a737b36cca7ecc56619f2a5f801eb1e7f1bfdb78b lib/utils/wafbypass.py 1b439fc59fd202c21c74978ed9f36d1c309533226c77907eae159461525f9fef lib/utils/xrange.py diff --git a/lib/core/settings.py b/lib/core/settings.py index 2c3537f7e..0a2fc08ab 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from lib.core.enums import OS from thirdparty import six # sqlmap version (...) -VERSION = "1.10.6.158" +VERSION = "1.10.6.159" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) diff --git a/lib/utils/gui.py b/lib/utils/gui.py index ed6dd13b3..97beb328b 100644 --- a/lib/utils/gui.py +++ b/lib/utils/gui.py @@ -208,6 +208,8 @@ class SqlmapGui(object): 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.badges = {} # name -> sidebar count badge label + self.sectionDests = {} # name -> [option dests in that section] self.paneOrder = [] # nav order, for Up/Down navigation self.currentPane = None self.process = None @@ -329,6 +331,7 @@ class SqlmapGui(object): 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.ttk.Button(cmdBar, text="Copy", command=self._copyCommand, takefocus=False).pack(side=tk.RIGHT, padx=(8, 0)) 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", @@ -352,6 +355,12 @@ class SqlmapGui(object): self.window.bind("", lambda e: self._navKey(-1)) for seq in ("", "", ""): self.window.bind_all(seq, self._onWheel) + self.window.bind("", lambda e: self.run()) + self.window.bind("", lambda e: self.run()) + self.window.bind("", lambda e: self.run()) + self.window.bind("", lambda e: self._focusTarget()) + self.window.bind("", lambda e: self.saveConfigDialog()) + self.window.bind("", lambda e: self.loadConfig()) self._enableSelectAll() self._tickStats() self._prebuildPanes() @@ -586,14 +595,17 @@ class SqlmapGui(object): 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)) + badge = tk.Label(row, text="", background=p["mantle"], foreground=p["blue"], font=self.fonts["small"]) + badge.pack(side=tk.RIGHT, padx=(0, 12)) + self.badges[name] = badge 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): + for w in (row, lab, strip, icon, badge): 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.navItems[name] = (row, strip, icon, lab, badge) self.paneOrder.append(name) outer = self.ttk.Frame(self.content, style="Card.TFrame") @@ -618,8 +630,8 @@ class SqlmapGui(object): 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): + row, strip, icon, lab, badge = self.navItems[name] + for w in (row, strip, icon, lab, badge): w.configure(background=bg) def _navKey(self, delta): @@ -643,16 +655,18 @@ class SqlmapGui(object): p = PALETTE if self.currentPane: self.panes[self.currentPane].pack_forget() - row, strip, icon, lab = self.navItems[self.currentPane] + row, strip, icon, lab, badge = 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"]) + badge.configure(background=p["mantle"], foreground=p["blue"]) 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] + row, strip, icon, lab, badge = 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"]) + badge.configure(background=p["blue"], foreground="#ffffff") self._drawIcon(icon, name, "#ffffff") self.currentPane = name self._ensureNavVisible(name) @@ -704,6 +718,7 @@ class SqlmapGui(object): def _buildQuickStartPane(self): name = "Quick start" self._addPane(name, name) + self.sectionDests[name] = [_ for _ in QUICK_START_DESTS if _ in self.optionByDest] def build(inner): self.ttk.Label(inner, text="Quick start", style="Pane.TLabel").grid(row=0, column=0, columnspan=2, sticky="w") @@ -721,6 +736,7 @@ class SqlmapGui(object): def _buildGroupPane(self, group): title = _groupTitle(group) self._addPane(title, NAV_ALIASES.get(title, title)) + self.sectionDests[title] = [_optDest(_) for _ in _groupOptions(group) if _optDest(_)] 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") @@ -806,19 +822,25 @@ class SqlmapGui(object): window.geometry("%dx%d+%d+%d" % (width, height, x, y)) def _updateStats(self): - count = 0 + setDests = set() for dest, (otype, var) in self.widgets.items(): try: if otype == "bool": if var.get(): - count += 1 + setDests.add(dest) else: raw = var.get() if raw not in (None, "") and str(raw) != str(defaults.get(dest, "")): - count += 1 + setDests.add(dest) except Exception: pass + count = len(setDests) self.stat.set("%d option%s set" % (count, "" if count == 1 else "s")) + for name, dests in self.sectionDests.items(): + badge = self.badges.get(name) + if badge is not None: + hits = sum(1 for _ in dests if _ in setDests) + badge.configure(text=(str(hits) if hits else "")) def _buildCommandString(self): parts = ["sqlmap.py"] @@ -850,6 +872,22 @@ class SqlmapGui(object): self.command.set(self._buildCommandString()) self.window.after(1200, self._tickStats) + def _copyCommand(self): + try: + self.window.clipboard_clear() + self.window.clipboard_append(self.command.get()) + self.hint.set("Command copied to clipboard") + except Exception: + pass + + def _focusTarget(self): + try: + self.targetEntry.focus_set() + self.targetEntry.select_range(0, "end") + except Exception: + pass + return "break" + def _collectConfig(self): config = {} for dest, (otype, var) in self.widgets.items(): diff --git a/lib/utils/tui.py b/lib/utils/tui.py index 476eccecc..3f5d6f43e 100644 --- a/lib/utils/tui.py +++ b/lib/utils/tui.py @@ -260,6 +260,37 @@ class NcursesUI: x += len(tab_text) + 1 + def _build_command(self): + """Assemble the equivalent sqlmap command line from the current field values""" + parts = ["sqlmap.py"] + for opt in self.all_options: + flag = opt['label'].split(',')[0].strip() if opt['label'] else "" + if not flag: + continue + value = opt['value'] + if opt['type'] == 'bool': + if value: + parts.append(flag) + elif value not in (None, "") and str(value) != str(opt.get('default') or ""): + text = str(value) + if ' ' in text or '"' in text: + text = '"%s"' % text.replace('"', '\\"') + parts.append("%s %s" % (flag, text)) + return " ".join(parts) + + def _draw_command(self): + """Live preview of the command being built, just above the footer""" + height, width = self.stdscr.getmaxyx() + cmd = "$ " + self._build_command() + if len(cmd) > width - 2: + cmd = cmd[:width - 5] + "..." + try: + self.stdscr.attron(curses.color_pair(8) | curses.A_BOLD) + self.stdscr.addstr(height - 2, 1, cmd.ljust(width - 2)[:width - 2]) + self.stdscr.attroff(curses.color_pair(8) | curses.A_BOLD) + except curses.error: + pass + def _draw_footer(self): """Draw the footer with help text""" height, width = self.stdscr.getmaxyx() @@ -303,12 +334,12 @@ class NcursesUI: pass y += 1 - # Draw options + # Draw options (leave height-2 for the command preview, height-1 for the footer) visible_start = self.scroll_offset - visible_end = visible_start + (height - y - 2) + visible_end = visible_start + (height - y - 3) for i, option in enumerate(tab['options'][visible_start:visible_end], visible_start): - if y >= height - 2: + if y >= height - 3: break is_selected = (i == self.current_field) @@ -374,7 +405,7 @@ class NcursesUI: if len(tab['options']) > visible_end - visible_start: try: self.stdscr.attron(curses.color_pair(6)) - self.stdscr.addstr(height - 2, width - 10, "[More...]") + self.stdscr.addstr(height - 3, width - 10, "[More...]") self.stdscr.attroff(curses.color_pair(6)) except: pass @@ -828,6 +859,7 @@ class NcursesUI: self._draw_header() self._draw_tabs() self._draw_current_tab() + self._draw_command() self._draw_footer() self.stdscr.refresh()