diff --git a/.gitattributes b/.gitattributes index 8b6e58fe0..a6b6a3526 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ *.py text eol=lf +*.conf text eol=lf *_ binary *.dll binary diff --git a/README.md b/README.md index a620dc945..8115c5dd5 100644 --- a/README.md +++ b/README.md @@ -55,5 +55,8 @@ Links Translations ---- -* [Portuguese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pt-BR.md) +* [Chinese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-zh-CN.md) +* [Croatian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-hr-HR.md) +* [Greek](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-gr-GR.md) * [Indonesian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-id-ID.md) +* [Portuguese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pt-BR.md) diff --git a/doc/COPYING b/doc/COPYING index 38a61d291..880d8774b 100644 --- a/doc/COPYING +++ b/doc/COPYING @@ -1,12 +1,12 @@ COPYING -- Describes the terms under which sqlmap is distributed. A copy of the GNU General Public License (GPL) is appended to this file. -sqlmap is (C) 2006-2013 Bernardo Damele Assumpcao Guimaraes, Miroslav Stampar. +sqlmap is (C) 2006-2015 Bernardo Damele Assumpcao Guimaraes, Miroslav Stampar. This program is free software; you may redistribute and/or modify it under the terms of the GNU General Public License as published by the Free -Software Foundation; Version 2 with the clarifications and exceptions -described below. This guarantees your right to use, modify, and +Software Foundation; Version 2 (or later) with the clarifications and +exceptions described below. This guarantees your right to use, modify, and redistribute this software under certain conditions. If you wish to embed sqlmap technology into proprietary software, we sell alternative licenses (contact sales@sqlmap.org). diff --git a/doc/THANKS.md b/doc/THANKS.md index e90ff7f88..931ab73bc 100644 --- a/doc/THANKS.md +++ b/doc/THANKS.md @@ -1,777 +1,796 @@ # Individuals -Andres Tarasco Acuna, +Andres Tarasco Acuna, * for suggesting a feature -Santiago Accurso, +Santiago Accurso, * for reporting a bug -Syed Afzal, +Syed Afzal, * for contributing a WAF script varnish.py -Zaki Akhmad, +Zaki Akhmad, * for suggesting a couple of features -Olu Akindeinde, +Olu Akindeinde, * for reporting a couple of bugs -David Alvarez, +David Alvarez, * for reporting a bug -Sergio Alves, +Sergio Alves, * for reporting a bug -Thomas Anderson, +Thomas Anderson, * for reporting a bug -Chip Andrews, +Chip Andrews, * 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 -Smith Andy, +Smith Andy, * for suggesting a feature -Otavio Augusto, +Otavio Augusto, * for reporting a minor bug -Simon Baker, +Simon Baker, * for reporting some bugs -Ryan Barnett, +Ryan Barnett, * for organizing the ModSecurity SQL injection challenge, http://modsecurity.org/demo/challenge.html -Emiliano Bazaes, +Emiliano Bazaes, * for reporting a minor bug -Daniele Bellucci, +Daniele Bellucci, * for starting sqlmap project and developing it between July and August 2006 -Sebastian Bittig, and the rest of the team at r-tec IT Systeme GmbH +Sebastian Bittig, and the rest of the team at r-tec IT Systeme GmbH * for contributing the DB2 support initial patch: fingerprint and enumeration -Anthony Boynes, +Anthony Boynes, * for reporting several bugs Marcelo Toscani Brandao * for reporting a bug -Velky Brat, +Velky Brat, * for suggesting a minor enhancement to the bisection algorithm -James Briggs, +James Briggs, * for suggesting a minor enhancement -Gianluca Brindisi, +Gianluca Brindisi, * for reporting a couple of bugs -Jack Butler, +Jack Butler, * for contributing the sqlmap site favicon -Ulisses Castro, +Ulisses Castro, * for reporting a bug -Roberto Castrogiovanni, +Roberto Castrogiovanni, * for reporting a minor bug -Cesar Cerrudo, +Cesar Cerrudo, * for his Windows access token kidnapping tool Churrasco included in sqlmap tree as a contrib library and used to run the stand-alone payload stager on the target Windows machine as SYSTEM user if the user wants to perform a privilege escalation attack, http://www.argeniss.com/research/TokenKidnapping.pdf -Karl Chen, +Karl Chen, * for contributing the initial multi-threading patch for the inference algorithm -Y P Chien, +Y P Chien, * for reporting a minor bug -Pierre Chifflier, and Mark Hymers, +Pierre Chifflier, and Mark Hymers, * for uploading and accepting the sqlmap Debian package to the official Debian project repository -Hysia Chow +Hysia Chow * for contributing a couple of WAF scripts -Chris Clements, +Chris Clements, * for reporting a couple of bugs -John Cobb, +John Cobb, * for reporting a minor bug -Andreas Constantinides, +Andreas Constantinides, * for reporting a minor bug -Andre Costa, +Andre Costa, * for reporting a minor bug * for suggesting a minor enhancement -Ulises U. Cune, +Ulises U. Cune, * for reporting a bug -Alessandro Curio, +Alessandro Curio, * for reporting a minor bug -Alessio Dalla Piazza, +Alessio Dalla Piazza, * for reporting a couple of bugs -Sherif El-Deeb, +Sherif El-Deeb, * for reporting a minor bug -Stefano Di Paola, +Stefano Di Paola, * for suggesting good features -Mosk Dmitri, +Mosk Dmitri, * for reporting a minor bug -Meng Dong, +Meng Dong, * for contributing a code for Waffit integration -Carey Evans, +Carey Evans, * for his fcrypt module that allows crypt(3) support on Windows platforms -Shawn Evans, +Shawn Evans, * for suggesting an idea for one tamper script, greatest.py -Adam Faheem, +Adam Faheem, * for reporting a few bugs -James Fisher, +James Fisher, * for contributing two very good feature requests * for his great tool too brute force directories and files names on web/application servers, DirBuster, http://tinyurl.com/dirbuster -Jim Forster, +Jim Forster, * for reporting a bug -Rong-En Fan, +Rong-En Fan, * for commiting the sqlmap 0.5 port to the official FreeBSD project repository -Giorgio Fedon, +Giorgio Fedon, * for suggesting a speed improvement for bisection algorithm * for reporting a bug when running against Microsoft SQL Server 2005 -Kasper Fons, +Kasper Fons, * for reporting several bugs -Jose Fonseca, +Jose Fonseca, * for his Gprof2Dot utility for converting profiler output to dot graph(s) and for his XDot utility to render nicely dot graph(s), both included in sqlmap tree inside extra folder. These libraries are used for sqlmap development purposes only http://code.google.com/p/jrfonseca/wiki/Gprof2Dot http://code.google.com/p/jrfonseca/wiki/XDot -Alan Franzoni, -* for helping me out with Python subprocess library +Alan Franzoni, +* for helping out with Python subprocess library -Harold Fry, +Harold Fry, * for suggesting a minor enhancement -Daniel G. Gamonal, +Daniel G. Gamonal, * for reporting a minor bug -Marcos Mateos Garcia, +Marcos Mateos Garcia, * for reporting a minor bug -Andrew Gecse, +Andrew Gecse, * for reporting a minor issue -Ivan Giacomelli, +Ivan Giacomelli, * for reporting a bug * for suggesting a minor enhancement * for reviewing the documentation -Nico Golde, +Nico Golde, * for reporting a couple of bugs -Oliver Gruskovnjak, +Oliver Gruskovnjak, * for reporting a bug * for contributing a minor patch -Davide Guerri, +Davide Guerri, * for suggesting an enhancement -Dan Guido, +Dan Guido, * for promoting sqlmap in the context of the Penetration Testing and Vulnerability Analysis class at the Polytechnic University of New York, http://isisblogs.poly.edu/courses/pentest/ -David Guimaraes, +David Guimaraes, * for reporting considerable amount of bugs * for suggesting several features -Chris Hall, +Chris Hall, * for coding the prettyprint.py library -Tate Hansen, +Tate Hansen, * for donating to sqlmap development -Mario Heiderich, -Christian Matthies, -Lars H. Strojny, +Mario Heiderich, +Christian Matthies, +Lars H. Strojny, * for their great tool PHPIDS included in sqlmap tree as a set of rules for testing payloads against IDS detection, http://php-ids.org -Kristian Erik Hermansen, +Kristian Erik Hermansen, * for reporting a bug * for donating to sqlmap development -Alexander Hagenah, +Alexander Hagenah, * for reporting a minor bug -Dennis Hecken, +Dennis Hecken, * for reporting a minor bug -Choi Ho, +Choi Ho, * for reporting a minor bug -Jorge Hoya, +Jorge Hoya, * for suggesting a minor enhancement -Will Holcomb, +Will Holcomb, * for his MultipartPostHandler class to handle multipart POST forms and permission to include it within sqlmap source code -Daniel Huckmann, +Daniel Huckmann, * for reporting a couple of bugs -Daliev Ilya, +Daliev Ilya, * for reporting a bug -Jovon Itwaru, +Mehmet İnce, +* for contributing a tamper script xforwardedfor.py + +Jovon Itwaru, * for reporting a minor bug -Prashant Jadhav, +Prashant Jadhav, * for reporting a bug -Dirk Jagdmann, +Dirk Jagdmann, * for reporting a typo in the documentation -Luke Jahnke, +Luke Jahnke, * for reporting a bug when running against MySQL < 5.0 -Andrew Kitis +Andrew Kitis * for contributing a tamper script lowercase.py -David Klein, +David Klein, * for reporting a minor code improvement -Sven Klemm, +Sven Klemm, * for reporting two minor bugs with PostgreSQL -Anant Kochhar, +Anant Kochhar, * for providing with feedback on the user's manual -Dmitriy Kononov, +Dmitriy Kononov, * for reporting a minor bug -Alexander Kornbrust, +Alexander Kornbrust, * for reporting a couple of bugs -Krzysztof Kotowicz, +Krzysztof Kotowicz, * for reporting a minor bug -Nicolas Krassas, +Nicolas Krassas, * for reporting a couple of bugs -Oliver Kuckertz, +Oliver Kuckertz, * for contributing a minor patch -Alex Landa, +Alex Landa, * for contributing a patch adding beta support for XML output -Guido Landi, +Guido Landi, * for reporting a couple of bugs * for the great technical discussions * for Microsoft SQL Server 2000 and Microsoft SQL Server 2005 'sp_replwritetovarbin' stored procedure heap-based buffer overflow (MS09-004) exploit development -* for presenting with me at SOURCE Conference 2009 in Barcelona (Spain) on September 21, 2009 and at CONfidence 2009 in Warsaw (Poland) on November 20, 2009 +* for presenting with Bernardo at SOURCE Conference 2009 in Barcelona (Spain) on September 21, 2009 and at CONfidence 2009 in Warsaw (Poland) on November 20, 2009 -Lee Lawson, +Lee Lawson, * for reporting a minor bug -John J. Lee, and others +John J. Lee, and others * for developing the clientform Python library used by sqlmap to parse forms when --forms switch is specified -Nico Leidecker, +Nico Leidecker, * for providing with feedback on a few features * for reporting a couple of bugs * for his great tool icmpsh included in sqlmap tree to get a command prompt via an out-of-band tunnel over ICMP, http://leidecker.info/downloads/icmpsh.zip -Gabriel Lima, +Gabriel Lima, * for reporting a couple of bugs -Svyatoslav Lisin, +Svyatoslav Lisin, * for suggesting a minor feature -Miguel Lopes, +Miguel Lopes, * for reporting a minor bug -Truong Duc Luong, +Truong Duc Luong, * for reporting a minor bug -Pavol Luptak, +Pavol Luptak, * for reporting a bug when injecting on a POST data parameter -Till Maas, +Till Maas, * for suggesting a minor feature -Michael Majchrowicz, +Michael Majchrowicz, * for extensively beta-testing sqlmap on various MySQL DBMS * for providing really appreciated feedback * for suggesting a lot of ideas and features -Vinícius Henrique Marangoni, +Vinícius Henrique Marangoni, * for contributing a Portuguese translation of README.md -Ahmad Maulana, +Ahmad Maulana, * for contributing a tamper script halfversionedmorekeywords.py -Ferruh Mavituna, +Ferruh Mavituna, * for exchanging ideas on the implementation of a couple of features -David McNab, +David McNab, * for his XMLObject module that allows XML files to be operated on like Python objects -Spencer J. McIntyre, +Spencer J. McIntyre, * for reporting a minor bug * for contributing a patch for OS fingerprinting on DB2 -Brad Merrell, +Brad Merrell, * for reporting a minor bug -Michael Meyer, +Michael Meyer, * for suggesting a minor feature -Enrico Milanese, +Enrico Milanese, * for reporting a minor bug * for sharing some ideas for the PHP backdoor -Liran Mimoni, +Liran Mimoni, * for reporting a minor bug -Marco Mirandola, +Marco Mirandola, * for reporting a minor bug -Devon Mitchell, +Devon Mitchell, * for reporting a minor bug -Anton Mogilin, +Anton Mogilin, * for reporting a few bugs -Sergio Molina, +Sergio Molina, * for reporting a minor bug -Anastasios Monachos, +Anastasios Monachos, * for providing some useful data * for suggesting a feature * for reporting a couple of bugs -Kirill Morozov, +Kirill Morozov, * for reporting a bug * for suggesting a feature -Alejo Murillo Moya, +Alejo Murillo Moya, * for reporting a minor bug * for suggesting a few features -Yonny Mutai, +Yonny Mutai, * for reporting a minor bug -Roberto Nemirovsky, -* for pointing me out some enhancements +Roberto Nemirovsky, +* for pointing out some enhancements -Simone Onofri, +Sebastian Nerz, +* for reporting a (potential) vulnerability in --eval + +Simone Onofri, * for patching the PHP web backdoor to make it work properly also on Windows -Michele Orru, +Michele Orru, * for reporting a couple of bug * for suggesting ideas on how to implement the RESTful API -Shaohua Pan, +Shaohua Pan, * for reporting several bugs * for suggesting a few features -Antonio Parata, +Antonio Parata, * for sharing some ideas for the PHP backdoor -Adrian Pastor, +Adrian Pastor, * for donating to sqlmap development -Christopher Patten, +Christopher Patten, * for reporting a bug in the blind SQL injection bisection algorithm -Zack Payton, +Zack Payton, * for reporting a minor bug -Jaime Penalba, +Jaime Penalba, * for contributing a patch for INSERT/UPDATE generic boundaries -Pedrito Perez, <0ark1ang3l@gmail.com> +Pedrito Perez, <0ark1ang3l(at)gmail.com> * for reporting a couple of bugs -Brandon Perry, +Brandon Perry, * for reporting a couple of bugs -Travis Phillips, +Travis Phillips, * for suggesting a minor enhancement -Mark Pilgrim, +Mark Pilgrim, * for porting chardet package (Universal Encoding Detector) to Python -Steve Pinkham, +Steve Pinkham, * for suggesting a feature * for contributing a new SQL injection vector (MSSQL time-based blind) * for donating to sqlmap development -Adam Pridgen, +Adam Pridgen, * for suggesting some features -Luka Pusic, +Luka Pusic, * for reporting a couple of bugs -Ole Rasmussen, +Ole Rasmussen, * for reporting a bug * for suggesting a feature -Alberto Revelli, -* for inspiring me to write sqlmap user's manual in SGML +Alberto Revelli, +* for inspiring to write sqlmap user's manual in SGML * for his great Microsoft SQL Server take over tool, sqlninja, http://sqlninja.sourceforge.net -David Rhoades, +David Rhoades, * for reporting a bug -Andres Riancho, +Andres Riancho, * 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 * for suggesting a way for handling DNS caching -Jamie Riden, +Jamie Riden, * for reporting a minor bug -Alexander Rigbo, +Alexander Rigbo, * for contributing a minor patch -Antonio Riva, +Antonio Riva, * for reporting a bug when running with python 2.5 -Ethan Robish, +Ethan Robish, * for reporting a bug -Levente Rog, +Levente Rog, * for reporting a minor bug -Andrea Rossi, +Andrea Rossi, * for reporting a minor bug * for suggesting a feature -Frederic Roy, +Frederic Roy, * for reporting a couple of bugs -Vladimir Rutsky, +Vladimir Rutsky, * for suggesting a couple of minor enhancements -Richard Safran, +Richard Safran, * for donating the sqlmap.org domain -Tomoyuki Sakurai, +Tomoyuki Sakurai, * for submitting to the FreeBSD project the sqlmap 0.5 port -Roberto Salgado, +Roberto Salgado, * for contributing considerable amount of tamper scripts -Pedro Jacques Santos Santiago, +Pedro Jacques Santos Santiago, * for reporting considerable amount of bugs -Marek Sarvas, +Marek Sarvas, * for reporting several bugs -Philippe A. R. Schaeffer, +Philippe A. R. Schaeffer, * for reporting a minor bug -Mohd Zamiri Sanin, +Mohd Zamiri Sanin, * for reporting a minor bug -Jorge Santos, +Jorge Santos, * for reporting a minor bug -Sven Schluter, +Sven Schluter, * for contributing a patch * for waiting a number of seconds between each HTTP request -Ryan Sears, +Ryan Sears, * for suggesting a couple of enhancements * for donating to sqlmap development -Uemit Seren, +Uemit Seren, * for reporting a minor adjustment when running with python 2.6 -Shane Sewell, +Shane Sewell, * for suggesting a feature -Ahmed Shawky, +Ahmed Shawky, * for reporting a major bug with improper handling of parameter values * for reporting a bug -Brian Shura, +Brian Shura, * for reporting a bug -Sumit Siddharth, +Sumit Siddharth, * for sharing ideas on the implementation of a couple of features -Andre Silva, +Andre Silva, * for reporting a bug -Benjamin Silva H. +Benjamin Silva H. * for reporting a bug -Duarte Silva +Duarte Silva * for reporting a couple of bugs -M Simkin, +M Simkin, * for suggesting a feature -Konrads Smelkovs, +Konrads Smelkovs, * for reporting a few bugs in --sql-shell and --sql-query on Microsoft SQL Server -Chris Spencer, +Chris Spencer, * for reviewing the user's manual grammar -Michael D. Stenner, +Michael D. Stenner, * for his keepalive module that allows handling of persistent HTTP 1.1 keep-alive connections -Marek Stiefenhofer, +Marek Stiefenhofer, * for reporting a few bugs -Jason Swan, +Jason Swan, * for reporting a bug when enumerating columns on Microsoft SQL Server * for suggesting a couple of improvements -Chilik Tamir, +Chilik Tamir, * for contributing a patch for initial support SOAP requests -Alessandro Tanasi, +Alessandro Tanasi, * for extensively beta-testing sqlmap * for suggesting many features and reporting some bugs * for reviewing the documentation -Andres Tarasco, +Andres Tarasco, * for contributing good feedback -Tom Thumb, +Tom Thumb, * for reporting a major bug -Kazim Bugra Tombul, +Kazim Bugra Tombul, * for reporting a minor bug -Efrain Torres, -* for helping me out to improve the Metasploit Framework sqlmap auxiliary module and for commiting it on the Metasploit official subversion repository +Efrain Torres, +* for helping out to improve the Metasploit Framework sqlmap auxiliary module and for commiting it on the Metasploit official subversion repository * for his great Metasploit WMAP Framework -Sandro Tosi, +Sandro Tosi, * for helping to create sqlmap Debian package correctly -Jacco van Tuijl, +Jacco van Tuijl, * for reporting several bugs -Vitaly Turenko, +Vitaly Turenko, * for reporting a bug -Augusto Urbieta, +Augusto Urbieta, * for reporting a minor bug -Bedirhan Urgun, +Bedirhan Urgun, * for reporting a few bugs * 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 -Kyprianos Vasilopoulos, +Kyprianos Vasilopoulos, * for reporting a couple of minor bugs -Vlado Velichkovski, +Vlado Velichkovski, * for reporting considerable amount of bugs * for suggesting an enhancement -Johnny Venter, +Johnny Venter, * for reporting a couple of bugs -Carlos Gabriel Vergara, +Carlos Gabriel Vergara, * for suggesting couple of good features -Patrick Webster, +Patrick Webster, * for suggesting an enhancement -Ed Williams, +Ed Williams, * for suggesting a minor enhancement -Anthony Zboralski, +Anthony Zboralski, * for providing with detailed feedback * for reporting a few minor bugs * for donating to sqlmap development -Thierry Zoller, +Thierry Zoller, * for reporting a couple of major bugs -Zhen Zhou, +Zhen Zhou, * for suggesting a feature --insane-, +-insane-, * for reporting a minor bug -1ndr4 joe, +1ndr4 joe, * for reporting a couple of bugs -abc abc, +abc abc, * for reporting a minor bug -Abuse 007, +Abuse 007, * for reporting a bug -Alex, +agix, +* for contributing the file upload via certutil.exe functionality + +Alex, * for reporting a minor bug -anonymous anonymous, +anonymous anonymous, * for reporting a couple of bugs -bamboo, +bamboo, * for reporting a couple of bugs -Brandon E., +Brandon E., * for reporting a bug -black zero, +black zero, * for reporting a minor bug -blueBoy, +blueBoy, * for reporting a bug -buawig, +buawig, * for reporting considerable amount of bugs -Bugtrace, +Bugtrace, * for reporting several bugs -cats, +cats, * for reporting a couple of bugs -Christian S, +Christian S, * for reporting a minor bug -clav, +clav, * for reporting a minor bug -dragoun dash, +dragoun dash, * for reporting a minor bug -fufuh, +flsf, +* for contributing WAF scripts 360.py, anquanbao.py, baidu.py, safedog.py +* for contributing a minor patch + +fufuh, * for reporting a bug when running on Windows -Hans Wurst, +Hans Wurst, * for reporting a couple of bugs -james, +Hysia, +* for contributing a Chinese translation of README.md + +james, * for reporting a bug -Joe "Pragmatk", +Joe "Pragmatk", * for reporting a few bugs -John Smith, +John Smith, * for reporting several bugs * for suggesting some features -m4l1c3, +m4l1c3, * for reporting considerable amount of bugs -mariano, +mariano, * for reporting a bug -mitchell, +mitchell, * for reporting a few bugs -Nadzree, +Nadzree, * for reporting a minor bug -nightman, +nightman, * for reporting considerable amount of bugs -Oso Dog osodog123@yahoo.com +Oso Dog osodog123(at)yahoo.com * for reporting a minor bug -pacman730, +pacman730, * for reporting a bug -pentestmonkey, +pentestmonkey, * for reporting several bugs * for suggesting a few minor enhancements -Phat R., +Phat R., * for reporting a few bugs -Phil P, <@superevr> +Phil P, <(at)superevr> * for suggesting a minor enhancement -ragos, +ragos, * for reporting a minor bug -rmillet, +rmillet, * for reporting a bug -Rub3nCT, +Rub3nCT, * for reporting a minor bug -shiftzwei, +shiftzwei, * for reporting a couple of bugs -smith, +smith, * for reporting a minor bug -Soma Cruz, +Soma Cruz, * for reporting a minor bug -Stuffe, +Spiros94, +* for contributing a Greek translation of README.md + +Stuffe, * for reporting a minor bug and a feature request -Sylphid, +Sylphid, * for suggesting some features -syssecurity.info, +syssecurity.info, * for reporting a minor bug -This LittlePiggy, +This LittlePiggy, * for reporting a minor bug -ToR, +ToR, * for reporting considerable amount of bugs * for suggesting a feature -ultramegaman, +ultramegaman, * for reporting a minor bug -Vinicius, +Vinicius, * for reporting a minor bug -wanglei, +wanglei, * for reporting a minor bug -warninggp, +warninggp, * for reporting a few minor bugs -x, +x, * for reporting a bug -zhouhx, +zhouhx, * for contributing a minor patch # Organizations -Black Hat team, +Black Hat team, * for the opportunity to present my research titled 'Advanced SQL injection to operating system full control' at Black Hat Europe 2009 Briefings on April 16, 2009 in Amsterdam (NL). I unveiled and demonstrated some of the sqlmap 0.7 release candidate version new features during my presentation * Homepage: http://goo.gl/BKfs7 * Slides: http://goo.gl/Dh65t * White paper: http://goo.gl/spX3N -SOURCE Conference team, +SOURCE Conference team, * for the opportunity to present my research titled 'Expanding the control over the operating system from the database' at SOURCE Conference 2009 on September 21, 2009 in Barcelona (ES). I unveiled and demonstrated some of the sqlmap 0.8 release candidate version new features during my presentation * Homepage: http://goo.gl/IeXV4 * Slides: http://goo.gl/OKnfj -AthCon Conference team, +AthCon Conference team, * for the opportunity to present my research titled 'Got database access? Own the network!' at AthCon Conference 2010 on June 3, 2010 in Athens (GR). I unveiled and demonstrated some of the sqlmap 0.8 version features during my presentation * Homepage: http://goo.gl/Fs71I * Slides: http://goo.gl/QMfjO -Metasploit Framework development team, +Metasploit Framework development team, * for their powerful tool Metasploit Framework, used by sqlmap, among others things, to create the shellcode and establish an out-of-band connection between sqlmap and the database server * Homepage: http://www.metasploit.com -OWASP Board, +OWASP Board, * for sponsoring part of the sqlmap development in the context of OWASP Spring of Code 2007 * Homepage: http://www.owasp.org diff --git a/doc/THIRD-PARTY.md b/doc/THIRD-PARTY.md index 5debfe274..f2479b31a 100644 --- a/doc/THIRD-PARTY.md +++ b/doc/THIRD-PARTY.md @@ -20,6 +20,8 @@ This file lists bundled packages and their associated licensing terms. * The Oset library located under thirdparty/oset/. Copyright (C) 2010, BlueDynamics Alliance, Austria. Copyright (C) 2009, Raymond Hettinger, and others. +* The PrettyPrint library located under thirdparty/prettyprint/. + Copyright (C) 2010, Chris Hall. * The SocksiPy library located under thirdparty/socks/. Copyright (C) 2006, Dan-Haim. @@ -55,7 +57,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Copyright (C) 2008-2009, Jose Fonseca. * The KeepAlive library located under thirdparty/keepalive/. Copyright (C) 2002-2003, Michael D. Stenner. -* The MultipartPost library located under thirdparty/multipartpost/. +* The MultipartPost library located under thirdparty/multipart/. Copyright (C) 2006, Will Holcomb. * The XDot library located under thirdparty/xdot/. Copyright (C) 2008, Jose Fonseca. @@ -281,8 +283,6 @@ be bound by the terms and conditions of this License Agreement. Copyright (C) 2012, Marcel Hellkamp. * The PageRank library located under thirdparty/pagerank/. Copyright (C) 2010, Corey Goldberg. -* The PrettyPrint library located under thirdparty/prettyprint/. - Copyright (C) 2010, Chris Hall. * The Termcolor library located under thirdparty/termcolor/. Copyright (C) 2008-2011, Volvox Development Team. diff --git a/doc/translations/README-gr-GR.md b/doc/translations/README-gr-GR.md new file mode 100644 index 000000000..8b09ba653 --- /dev/null +++ b/doc/translations/README-gr-GR.md @@ -0,0 +1,53 @@ +sqlmap +== + + +Το sqlmap είναι πρόγραμμα ανοιχτού κώδικα, που αυτοματοποιεί την εύρεση και εκμετάλλευση ευπαθειών τύπου SQL Injection σε βάσεις δεδομένων. Έρχεται με μια δυνατή μηχανή αναγνώρισης ευπαθειών, πολλά εξειδικευμένα χαρακτηριστικά για τον απόλυτο penetration tester όπως και με ένα μεγάλο εύρος επιλογών αρχίζοντας από την αναγνώριση της βάσης δεδομένων, κατέβασμα δεδομένων της βάσης, μέχρι και πρόσβαση στο βαθύτερο σύστημα αρχείων και εκτέλεση εντολών στο απευθείας στο λειτουργικό μέσω εκτός ζώνης συνδέσεων. + +Εικόνες +---- + +![Screenshot](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) + +Μπορείτε να επισκεφτείτε τη [συλλογή από εικόνες](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) που επιδεικνύουν κάποια από τα χαρακτηριστικά. + +Εγκατάσταση +---- + +Έχετε τη δυνατότητα να κατεβάσετε την τελευταία tarball πατώντας [εδώ](https://github.com/sqlmapproject/sqlmap/tarball/master) ή την τελευταία zipball πατώντας [εδώ](https://github.com/sqlmapproject/sqlmap/zipball/master). + +Κατά προτίμηση, μπορείτε να κατεβάσετε το sqlmap κάνοντας κλώνο το [Git](https://github.com/sqlmapproject/sqlmap) αποθετήριο: + + git clone https://github.com/sqlmapproject/sqlmap.git sqlmap-dev + +Το sqlmap λειτουργεί χωρίς περαιτέρω κόπο με την [Python](http://www.python.org/download/) έκδοσης **2.6.x** και **2.7.x** σε όποια πλατφόρμα. + +Χρήση +---- + +Για να δείτε μια βασική λίστα από επιλογές πατήστε: + + python sqlmap.py -h + +Για να πάρετε μια λίστα από όλες τις επιλογές πατήστε: + + python sqlmap.py -hh + +Μπορείτε να δείτε ένα δείγμα λειτουργίας του προγράμματος [εδώ](https://gist.github.com/stamparm/5335217). +Για μια γενικότερη άποψη των δυνατοτήτων του sqlmap, μια λίστα των υποστηριζόμενων χαρακτηριστικών και περιγραφή για όλες τις επιλογές, μαζί με παραδείγματα, καλείστε να συμβουλευτείτε το [εγχειρίδιο χρήστη](https://github.com/sqlmapproject/sqlmap/wiki). + +Σύνδεσμοι +---- + +* Αρχική σελίδα: http://sqlmap.org +* Λήψεις: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) ή [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) +* Commits RSS feed: https://github.com/sqlmapproject/sqlmap/commits/master.atom +* Προβλήματα: https://github.com/sqlmapproject/sqlmap/issues +* Εγχειρίδιο Χρήστη: https://github.com/sqlmapproject/sqlmap/wiki +* Συχνές Ερωτήσεις (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ +* Εγγραφή σε Mailing list: https://lists.sourceforge.net/lists/listinfo/sqlmap-users +* Mailing list RSS feed: http://rss.gmane.org/messages/complete/gmane.comp.security.sqlmap +* Mailing list αρχείο: http://news.gmane.org/gmane.comp.security.sqlmap +* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* Demos: [http://www.youtube.com/user/inquisb/videos](http://www.youtube.com/user/inquisb/videos) +* Εικόνες: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-hr-HR.md b/doc/translations/README-hr-HR.md new file mode 100644 index 000000000..69e2d531d --- /dev/null +++ b/doc/translations/README-hr-HR.md @@ -0,0 +1,53 @@ +sqlmap +== + + +sqlmap je alat namijenjen za penetracijsko testiranje koji automatizira proces detekcije i eksploatacije sigurnosnih propusta SQL injekcije te preuzimanje poslužitelja baze podataka. Dolazi s moćnim mehanizmom za detekciju, mnoštvom korisnih opcija za napredno penetracijsko testiranje te široki spektar opcija od onih za prepoznavanja baze podataka, preko dohvaćanja podataka iz baze, do pristupa zahvaćenom datotečnom sustavu i izvršavanja komandi na operacijskom sustavu korištenjem tzv. "out-of-band" veza. + +Slike zaslona +---- + +![Slika zaslona](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) + +Možete posjetiti [kolekciju slika zaslona](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) gdje se demonstriraju neke od značajki na wiki stranicama. + +Instalacija +---- + +Možete preuzeti zadnji tarball klikom [ovdje](https://github.com/sqlmapproject/sqlmap/tarball/master) ili zadnji zipball klikom [ovdje](https://github.com/sqlmapproject/sqlmap/zipball/master). + +Po mogućnosti, možete preuzeti sqlmap kloniranjem [Git](https://github.com/sqlmapproject/sqlmap) repozitorija: + + git clone https://github.com/sqlmapproject/sqlmap.git sqlmap-dev + +sqlmap radi bez posebnih zahtjeva korištenjem [Python](http://www.python.org/download/) verzije **2.6.x** i/ili **2.7.x** na bilo kojoj platformi. + +Korištenje +---- + +Kako biste dobili listu osnovnih opcija i prekidača koristite: + + python sqlmap.py -h + +Kako biste dobili listu svih opcija i prekidača koristite: + + python sqlmap.py -hh + +Možete pronaći primjer izvršavanja [ovdje](https://gist.github.com/stamparm/5335217). +Kako biste dobili pregled mogućnosti sqlmap-a, liste podržanih značajki te opis svih opcija i prekidača, zajedno s primjerima, preporučen je uvid u [korisnički priručnik](https://github.com/sqlmapproject/sqlmap/wiki). + +Poveznice +---- + +* Početna stranica: http://sqlmap.org +* Preuzimanje: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) ili [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) +* RSS feed promjena u kodu: https://github.com/sqlmapproject/sqlmap/commits/master.atom +* Prijava problema: https://github.com/sqlmapproject/sqlmap/issues +* Korisnički priručnik: https://github.com/sqlmapproject/sqlmap/wiki +* Najčešće postavljena pitanja (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ +* Pretplata na mailing listu: https://lists.sourceforge.net/lists/listinfo/sqlmap-users +* RSS feed mailing liste: http://rss.gmane.org/messages/complete/gmane.comp.security.sqlmap +* Arhiva mailing liste: http://news.gmane.org/gmane.comp.security.sqlmap +* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* Demo: [http://www.youtube.com/user/inquisb/videos](http://www.youtube.com/user/inquisb/videos) +* Slike zaslona: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/doc/translations/README-zh-CN.md b/doc/translations/README-zh-CN.md new file mode 100644 index 000000000..c3b8b2941 --- /dev/null +++ b/doc/translations/README-zh-CN.md @@ -0,0 +1,52 @@ +sqlmap +== + + +sqlmap 是一个开源的渗透测试工具,可以用来自动化的检测,利用SQL注入漏洞,获取数据库服务器的权限。它具有功能强大的检测引擎,针对各种不同类型数据库的渗透测试的功能选项,包括获取数据库中存储的数据,访问操作系统文件甚至可以通过外带数据连接的方式执行操作系统命令。 + +演示截图 +---- + +![截图](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) + +你可以访问 wiki上的 [截图](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) 查看各种用法的演示 + +安装方法 +---- + +你可以点击 [这里](https://github.com/sqlmapproject/sqlmap/tarball/master) 下载最新的 `tar` 打包的源代码 或者点击 [这里](https://github.com/sqlmapproject/sqlmap/zipball/master)下载最新的 `zip` 打包的源代码. + +推荐你从 [Git](https://github.com/sqlmapproject/sqlmap) 仓库获取最新的源代码: + + git clone https://github.com/sqlmapproject/sqlmap.git sqlmap-dev + +sqlmap 可以运行在 [Python](http://www.python.org/download/) **2.6.x** 和 **2.7.x** 版本的任何平台上 + +使用方法 +---- + +通过如下命令可以查看基本的用法及命令行参数: + + python sqlmap.py -h + +通过如下的命令可以查看所有的用法及命令行参数: + + python sqlmap.py -hh + +你可以从 [这里](https://gist.github.com/stamparm/5335217) 看到一个sqlmap 的使用样例。除此以外,你还可以查看 [使用手册](https://github.com/sqlmapproject/sqlmap/wiki)。获取sqlmap所有支持的特性、参数、命令行选项开关及说明的使用帮助。 + +链接 +---- + +* 项目主页: http://sqlmap.org +* 源代码下载: [.tar.gz](https://github.com/sqlmapproject/sqlmap/tarball/master) or [.zip](https://github.com/sqlmapproject/sqlmap/zipball/master) +* RSS 订阅: https://github.com/sqlmapproject/sqlmap/commits/master.atom +* Issue tracker: https://github.com/sqlmapproject/sqlmap/issues +* 使用手册: https://github.com/sqlmapproject/sqlmap/wiki +* 常见问题 (FAQ): https://github.com/sqlmapproject/sqlmap/wiki/FAQ +* 邮件讨论列表: https://lists.sourceforge.net/lists/listinfo/sqlmap-users +* 邮件列表 RSS 订阅: http://rss.gmane.org/messages/complete/gmane.comp.security.sqlmap +* 邮件列表归档: http://news.gmane.org/gmane.comp.security.sqlmap +* Twitter: [@sqlmap](https://twitter.com/sqlmap) +* 教程: [http://www.youtube.com/user/inquisb/videos](http://www.youtube.com/user/inquisb/videos) +* 截图: https://github.com/sqlmapproject/sqlmap/wiki/Screenshots diff --git a/extra/__init__.py b/extra/__init__.py index 5bad2814c..8d7bcd8f0 100644 --- a/extra/__init__.py +++ b/extra/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/extra/beep/__init__.py b/extra/beep/__init__.py index 5bad2814c..8d7bcd8f0 100644 --- a/extra/beep/__init__.py +++ b/extra/beep/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/extra/beep/beep.py b/extra/beep/beep.py index 49c4deafa..cd8ef9be5 100644 --- a/extra/beep/beep.py +++ b/extra/beep/beep.py @@ -3,7 +3,7 @@ """ beep.py - Make a beep sound -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ @@ -45,6 +45,10 @@ def _win_wav_play(filename): winsound.PlaySound(filename, winsound.SND_FILENAME) def _linux_wav_play(filename): + for _ in ("aplay", "paplay", "play"): + if not os.system("%s '%s' 2>/dev/null" % (_, filename)): + return + import ctypes PA_STREAM_PLAYBACK = 1 diff --git a/extra/cloak/__init__.py b/extra/cloak/__init__.py index 5bad2814c..8d7bcd8f0 100644 --- a/extra/cloak/__init__.py +++ b/extra/cloak/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/extra/cloak/cloak.py b/extra/cloak/cloak.py index cddc5555a..a94f6756f 100755 --- a/extra/cloak/cloak.py +++ b/extra/cloak/cloak.py @@ -3,7 +3,7 @@ """ cloak.py - Simple file encryption/compression utility -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/extra/dbgtool/__init__.py b/extra/dbgtool/__init__.py index 5bad2814c..8d7bcd8f0 100644 --- a/extra/dbgtool/__init__.py +++ b/extra/dbgtool/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/extra/dbgtool/dbgtool.py b/extra/dbgtool/dbgtool.py index a59797243..4d3dc8c5e 100644 --- a/extra/dbgtool/dbgtool.py +++ b/extra/dbgtool/dbgtool.py @@ -3,7 +3,7 @@ """ dbgtool.py - Portable executable to ASCII debug script converter -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/extra/mssqlsig/update.py b/extra/mssqlsig/update.py index 902c8ef1e..67d7ee6aa 100644 --- a/extra/mssqlsig/update.py +++ b/extra/mssqlsig/update.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/extra/safe2bin/__init__.py b/extra/safe2bin/__init__.py index 5bad2814c..8d7bcd8f0 100644 --- a/extra/safe2bin/__init__.py +++ b/extra/safe2bin/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/extra/safe2bin/safe2bin.py b/extra/safe2bin/safe2bin.py index bad03a8d2..c91620ec6 100644 --- a/extra/safe2bin/safe2bin.py +++ b/extra/safe2bin/safe2bin.py @@ -3,7 +3,7 @@ """ safe2bin.py - Simple safe(hex) to binary format converter -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/extra/shutils/duplicates.py b/extra/shutils/duplicates.py index 641fb8b76..eac95ccf8 100644 --- a/extra/shutils/duplicates.py +++ b/extra/shutils/duplicates.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +# Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) # See the file 'doc/COPYING' for copying permission # Removes duplicate entries in wordlist like files diff --git a/extra/shutils/regressiontest.py b/extra/shutils/regressiontest.py index a080a2af7..415714430 100755 --- a/extra/shutils/regressiontest.py +++ b/extra/shutils/regressiontest.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +# Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) # See the file 'doc/COPYING' for copying permission import codecs diff --git a/extra/sqlharvest/__init__.py b/extra/sqlharvest/__init__.py index 5bad2814c..8d7bcd8f0 100644 --- a/extra/sqlharvest/__init__.py +++ b/extra/sqlharvest/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/extra/sqlharvest/sqlharvest.py b/extra/sqlharvest/sqlharvest.py index 84de8d7f0..75dae5093 100644 --- a/extra/sqlharvest/sqlharvest.py +++ b/extra/sqlharvest/sqlharvest.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/lib/__init__.py b/lib/__init__.py index 5bad2814c..8d7bcd8f0 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/lib/controller/__init__.py b/lib/controller/__init__.py index 5bad2814c..8d7bcd8f0 100644 --- a/lib/controller/__init__.py +++ b/lib/controller/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/lib/controller/action.py b/lib/controller/action.py index 64150abed..b134cef15 100644 --- a/lib/controller/action.py +++ b/lib/controller/action.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 5c241b8b2..f4c053ec9 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ @@ -15,7 +15,6 @@ from subprocess import Popen as execute from extra.beep.beep import beep from lib.core.agent import agent -from lib.core.common import arrayizeValue from lib.core.common import Backend from lib.core.common import extractRegexResult from lib.core.common import extractTextTagContent @@ -46,7 +45,6 @@ from lib.core.datatype import AttribDict from lib.core.datatype import InjectionDict from lib.core.decorators import cachedmethod from lib.core.dicts import FROM_DUMMY_TABLE -from lib.core.enums import CUSTOM_LOGGING from lib.core.enums import DBMS from lib.core.enums import HEURISTIC_TEST from lib.core.enums import HTTP_HEADER @@ -54,18 +52,21 @@ from lib.core.enums import HTTPMETHOD from lib.core.enums import NULLCONNECTION from lib.core.enums import PAYLOAD from lib.core.enums import PLACE +from lib.core.enums import REDIRECTION from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapSilentQuitException from lib.core.exception import SqlmapUserQuitException +from lib.core.settings import DEFAULT_GET_POST_DELIMITER +from lib.core.settings import DUMMY_XSS_CHECK_APPENDIX from lib.core.settings import FORMAT_EXCEPTION_STRINGS from lib.core.settings import HEURISTIC_CHECK_ALPHABET from lib.core.settings import SUHOSIN_MAX_VALUE_LENGTH -from lib.core.settings import UNKNOWN_DBMS +from lib.core.settings import SUPPORTED_DBMS from lib.core.settings import URI_HTTP_HEADER -from lib.core.settings import LOWER_RATIO_BOUND from lib.core.settings import UPPER_RATIO_BOUND from lib.core.settings import IDS_WAF_CHECK_PAYLOAD +from lib.core.settings import IDS_WAF_CHECK_RATIO from lib.core.threads import getCurrentThreadData from lib.request.connect import Connect as Request from lib.request.inject import checkBooleanExpression @@ -84,31 +85,53 @@ def checkSqlInjection(place, parameter, value): # Set the flag for SQL injection test mode kb.testMode = True - for test in getSortedInjectionTests(): + paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place + tests = getSortedInjectionTests() + seenPayload = set() + + while tests: + test = tests.pop(0) + try: if kb.endDetection: break if conf.dbms is None: + # If the DBMS has not yet been fingerprinted (via simple heuristic check + # or via DBMS-specific payload) and boolean-based blind has been identified + # then attempt to identify with a simple DBMS specific boolean-based + # test what the DBMS may be if not injection.dbms and PAYLOAD.TECHNIQUE.BOOLEAN in injection.data: - if not Backend.getIdentifiedDbms() and not kb.heuristicDbms: - kb.heuristicDbms = heuristicCheckDbms(injection) or UNKNOWN_DBMS + if not Backend.getIdentifiedDbms() and kb.heuristicDbms is False: + kb.heuristicDbms = heuristicCheckDbms(injection) - if not conf.testFilter and (Backend.getErrorParsedDBMSes() or kb.heuristicDbms) not in ([], None, UNKNOWN_DBMS): - if kb.reduceTests is None and Backend.getErrorParsedDBMSes(): - msg = "heuristic (parsing) test showed that the " - msg += "back-end DBMS could be '%s'. " % (Format.getErrorParsedDBMSes() if Backend.getErrorParsedDBMSes() else kb.heuristicDbms) - msg += "Do you want to skip test payloads specific for other DBMSes? [Y/n]" - kb.reduceTests = [] if readInput(msg, default='Y').upper() != 'Y' else (Backend.getErrorParsedDBMSes() or [kb.heuristicDbms]) + # If the DBMS has already been fingerprinted (via DBMS-specific + # error message, simple heuristic check or via DBMS-specific + # payload), ask the user to limit the tests to the fingerprinted + # DBMS + if kb.reduceTests is None and not conf.testFilter and (intersect(Backend.getErrorParsedDBMSes(), \ + SUPPORTED_DBMS, True) or kb.heuristicDbms or injection.dbms): + msg = "it looks like the back-end DBMS is '%s'. " % (Format.getErrorParsedDBMSes() or kb.heuristicDbms or injection.dbms) + msg += "Do you want to skip test payloads specific for other DBMSes? [Y/n]" + kb.reduceTests = (Backend.getErrorParsedDBMSes() or [kb.heuristicDbms]) if readInput(msg, default='Y').upper() == 'Y' else [] - if kb.extendTests is None: - _ = (Format.getErrorParsedDBMSes() if Backend.getErrorParsedDBMSes() else kb.heuristicDbms) - msg = "do you want to include all tests for '%s' " % _ - msg += "extending provided level (%d) and risk (%s)? [Y/n]" % (conf.level, conf.risk) - kb.extendTests = [] if readInput(msg, default='Y').upper() != 'Y' else (Backend.getErrorParsedDBMSes() or [kb.heuristicDbms]) + # If the DBMS has been fingerprinted (via DBMS-specific error + # message, via simple heuristic check or via DBMS-specific + # payload), ask the user to extend the tests to all DBMS-specific, + # regardless of --level and --risk values provided + if kb.extendTests is None and not conf.testFilter and (conf.level < 5 or conf.risk < 3) \ + and (intersect(Backend.getErrorParsedDBMSes(), SUPPORTED_DBMS, True) or \ + kb.heuristicDbms or injection.dbms): + msg = "for the remaining tests, do you want to include all tests " + msg += "for '%s' extending provided " % (Format.getErrorParsedDBMSes() or kb.heuristicDbms or injection.dbms) + msg += "level (%d)" % conf.level if conf.level < 5 else "" + msg += " and " if conf.level < 5 and conf.risk < 3 else "" + msg += "risk (%d)" % conf.risk if conf.risk < 3 else "" + msg += " values? [Y/n]" if conf.level < 5 and conf.risk < 3 else " value? [Y/n]" + kb.extendTests = (Backend.getErrorParsedDBMSes() or [kb.heuristicDbms]) if readInput(msg, default='Y').upper() == 'Y' else [] title = test.title - stype = test.stype + kb.testType = stype = test.stype clause = test.clause unionExtended = False @@ -165,27 +188,56 @@ def checkSqlInjection(place, parameter, value): logger.debug(debugMsg) continue - - # Skip DBMS-specific test if it does not match either the - # previously identified or the user's provided DBMS (either - # from program switch or from parsed error message(s)) + # Parse DBMS-specific payloads' details if "details" in test and "dbms" in test.details: - dbms = test.details.dbms + payloadDbms = test.details.dbms else: - dbms = None + payloadDbms = None - # Skip tests if title is not included by the given filter - if conf.testFilter: - if not any(conf.testFilter in str(item) or re.search(conf.testFilter, str(item), re.I) for item in (test.title, test.vector, dbms)): - debugMsg = "skipping test '%s' because " % title - debugMsg += "its name/vector/dbms is not included by the given filter" + # Skip tests if title, vector or DBMS is not included by the + # given test filter + if conf.testFilter and not any(conf.testFilter in str(item) or \ + re.search(conf.testFilter, str(item), re.I) for item in \ + (test.title, test.vector, payloadDbms)): + debugMsg = "skipping test '%s' because its " % title + debugMsg += "name/vector/DBMS is not included by the given filter" logger.debug(debugMsg) continue - elif not (kb.extendTests and intersect(dbms, kb.extendTests)): + if payloadDbms is not None: + # Skip DBMS-specific test if it does not match the user's + # provided DBMS + if conf.dbms is not None and not intersect(payloadDbms, conf.dbms, True): + debugMsg = "skipping test '%s' because " % title + debugMsg += "the provided DBMS is %s" % conf.dbms + logger.debug(debugMsg) + continue + + # Skip DBMS-specific test if it does not match the + # previously identified DBMS (via DBMS-specific payload) + if injection.dbms is not None and not intersect(payloadDbms, injection.dbms, True): + debugMsg = "skipping test '%s' because the identified " % title + debugMsg += "back-end DBMS is %s" % injection.dbms + logger.debug(debugMsg) + continue + + # Skip DBMS-specific test if it does not match the + # previously identified DBMS (via DBMS-specific error message) + if kb.reduceTests and not intersect(payloadDbms, kb.reduceTests, True): + debugMsg = "skipping test '%s' because the parsed " % title + debugMsg += "error message(s) showed that the back-end DBMS " + debugMsg += "could be %s" % Format.getErrorParsedDBMSes() + logger.debug(debugMsg) + continue + + # If the user did not decide to extend the tests to all + # DBMS-specific or the test payloads is not specific to the + # identified DBMS, then only test for it if both level and risk + # are below the corrisponding configuration's level and risk + # values + if not conf.testFilter and not (kb.extendTests and intersect(payloadDbms, kb.extendTests, True)): # Skip test if the risk is higher than the provided (or default) # value - # Parse test's if test.risk > conf.risk: debugMsg = "skipping test '%s' because the risk (%d) " % (title, test.risk) debugMsg += "is higher than the provided (%d)" % conf.risk @@ -194,35 +246,12 @@ def checkSqlInjection(place, parameter, value): # Skip test if the level is higher than the provided (or default) # value - # Parse test's if test.level > conf.level: debugMsg = "skipping test '%s' because the level (%d) " % (title, test.level) debugMsg += "is higher than the provided (%d)" % conf.level logger.debug(debugMsg) continue - if dbms is not None: - if injection.dbms is not None and not intersect(injection.dbms, dbms): - debugMsg = "skipping test '%s' because " % title - debugMsg += "the back-end DBMS identified is " - debugMsg += "%s" % injection.dbms - logger.debug(debugMsg) - continue - - if conf.dbms is not None and not intersect(conf.dbms.lower(), [_.lower() for _ in arrayizeValue(dbms)]): - debugMsg = "skipping test '%s' because " % title - debugMsg += "the provided DBMS is %s" % conf.dbms - logger.debug(debugMsg) - continue - - if kb.reduceTests and not intersect(dbms, kb.reduceTests): - debugMsg = "skipping test '%s' because " % title - debugMsg += "the parsed error message(s) showed " - debugMsg += "that the back-end DBMS could be " - debugMsg += "%s" % Format.getErrorParsedDBMSes() - logger.debug(debugMsg) - continue - # Skip test if it does not match the same SQL injection clause # already identified by another test clauseMatch = False @@ -234,11 +263,11 @@ def checkSqlInjection(place, parameter, value): if clause != [0] and injection.clause and injection.clause != [0] and not clauseMatch: debugMsg = "skipping test '%s' because the clauses " % title - debugMsg += "differs from the clause already identified" + debugMsg += "differ from the clause already identified" logger.debug(debugMsg) continue - # Skip test if the user provided custom character + # Skip test if the user provided custom character (for UNION-based payloads) if conf.uChar is not None and ("random number" in title or "(NULL)" in title): debugMsg = "skipping test '%s' because the user " % title debugMsg += "provided a specific character, %s" % conf.uChar @@ -248,9 +277,9 @@ def checkSqlInjection(place, parameter, value): infoMsg = "testing '%s'" % title logger.info(infoMsg) - # Force back-end DBMS according to the current - # test value for proper payload unescaping - Backend.forceDbms(dbms[0] if isinstance(dbms, list) else dbms) + # Force back-end DBMS according to the current test DBMS value + # for proper payload unescaping + Backend.forceDbms(payloadDbms[0] if isinstance(payloadDbms, list) else payloadDbms) # Parse test's comment = agent.getComment(test.request) if len(conf.boundaries) > 1 else None @@ -268,7 +297,7 @@ def checkSqlInjection(place, parameter, value): # Skip boundary if the level is higher than the provided (or # default) value # Parse boundary's - if boundary.level > conf.level: + if boundary.level > conf.level and not (kb.extendTests and intersect(payloadDbms, kb.extendTests, True)): continue # Skip boundary if it does not match against test's @@ -298,14 +327,13 @@ def checkSqlInjection(place, parameter, value): # Parse boundary's , and prefix = boundary.prefix if boundary.prefix else "" suffix = boundary.suffix if boundary.suffix else "" + ptype = boundary.ptype # Options --prefix/--suffix have a higher priority (if set by user) prefix = conf.prefix if conf.prefix is not None else prefix suffix = conf.suffix if conf.suffix is not None else suffix comment = None if conf.suffix is not None else comment - ptype = boundary.ptype - # If the previous injections succeeded, we know which prefix, # suffix and parameter type to use for further tests, no # need to cycle through the boundaries for the following tests @@ -313,7 +341,9 @@ def checkSqlInjection(place, parameter, value): condBound &= (injection.prefix != prefix or injection.suffix != suffix) condType = injection.ptype is not None and injection.ptype != ptype - if condBound or condType: + # If the payload is an inline query test for it regardless + # of previously identified injection types + if stype != PAYLOAD.TECHNIQUE.QUERY and (condBound or condType): continue # For each test's @@ -334,6 +364,7 @@ def checkSqlInjection(place, parameter, value): # will likely result in a different content kb.data.setdefault("randomInt", str(randomInt(10))) kb.data.setdefault("randomStr", str(randomStr(10))) + if conf.invalidLogical: _ = int(kb.data.randomInt[:2]) origValue = "%s AND %s=%s" % (value, _, _ + 1) @@ -343,6 +374,7 @@ def checkSqlInjection(place, parameter, value): origValue = kb.data.randomStr[:6] else: origValue = "-%s" % kb.data.randomInt[:4] + templatePayload = agent.payload(place, parameter, value="", newValue=origValue, where=where) elif where == PAYLOAD.WHERE.REPLACE: origValue = "" @@ -352,9 +384,17 @@ def checkSqlInjection(place, parameter, value): # Forge request payload by prepending with boundary's # prefix and appending the boundary's suffix to the # test's ' ' string - boundPayload = agent.prefixQuery(fstPayload, prefix, where, clause) - boundPayload = agent.suffixQuery(boundPayload, comment, suffix, where) - reqPayload = agent.payload(place, parameter, newValue=boundPayload, where=where) + if fstPayload: + boundPayload = agent.prefixQuery(fstPayload, prefix, where, clause) + boundPayload = agent.suffixQuery(boundPayload, comment, suffix, where) + reqPayload = agent.payload(place, parameter, newValue=boundPayload, where=where) + if reqPayload: + if reqPayload in seenPayload: + continue + else: + seenPayload.add(reqPayload) + else: + reqPayload = None # Perform the test's request and check whether or not the # payload was successful @@ -389,12 +429,12 @@ def checkSqlInjection(place, parameter, value): trueResult = Request.queryPage(reqPayload, place, raise404=False) truePage = threadData.lastComparisonPage or "" - if trueResult: + if trueResult and not(truePage == falsePage and not kb.nullConnection): falseResult = Request.queryPage(genCmpPayload(), place, raise404=False) # Perform the test's False request if not falseResult: - infoMsg = "%s parameter '%s' seems to be '%s' injectable " % (place, parameter, title) + infoMsg = "%s parameter '%s' seems to be '%s' injectable " % (paramType, parameter, title) logger.info(infoMsg) injectable = True @@ -403,9 +443,10 @@ def checkSqlInjection(place, parameter, value): trueSet = set(extractTextTagContent(truePage)) falseSet = set(extractTextTagContent(falsePage)) candidates = filter(None, (_.strip() if _.strip() in (kb.pageTemplate or "") and _.strip() not in falsePage and _.strip() not in threadData.lastComparisonHeaders else None for _ in (trueSet - falseSet))) + if candidates: conf.string = candidates[0] - infoMsg = "%s parameter '%s' seems to be '%s' injectable (with --string=\"%s\")" % (place, parameter, title, repr(conf.string).lstrip('u').strip("'")) + infoMsg = "%s parameter '%s' seems to be '%s' injectable (with --string=\"%s\")" % (paramType, parameter, title, repr(conf.string).lstrip('u').strip("'")) logger.info(infoMsg) injectable = True @@ -428,7 +469,7 @@ def checkSqlInjection(place, parameter, value): result = output == "1" if result: - infoMsg = "%s parameter '%s' is '%s' injectable " % (place, parameter, title) + infoMsg = "%s parameter '%s' is '%s' injectable " % (paramType, parameter, title) logger.info(infoMsg) injectable = True @@ -450,7 +491,7 @@ def checkSqlInjection(place, parameter, value): trueResult = Request.queryPage(reqPayload, place, timeBasedCompare=True, raise404=False) if trueResult: - infoMsg = "%s parameter '%s' seems to be '%s' injectable " % (place, parameter, title) + infoMsg = "%s parameter '%s' seems to be '%s' injectable " % (paramType, parameter, title) logger.info(infoMsg) injectable = True @@ -466,7 +507,7 @@ def checkSqlInjection(place, parameter, value): configUnion(test.request.char, test.request.columns) if not Backend.getIdentifiedDbms(): - if kb.heuristicDbms in (None, UNKNOWN_DBMS): + if kb.heuristicDbms is None: warnMsg = "using unescaped version of the test " warnMsg += "because of zero knowledge of the " warnMsg += "back-end DBMS. You can try to " @@ -476,17 +517,28 @@ def checkSqlInjection(place, parameter, value): Backend.forceDbms(kb.heuristicDbms) if unionExtended: - infoMsg = "automatically extending ranges " - infoMsg += "for UNION query injection technique tests as " + infoMsg = "automatically extending ranges for UNION " + infoMsg += "query injection technique tests as " infoMsg += "there is at least one other (potential) " infoMsg += "technique found" singleTimeLogMessage(infoMsg) + elif not injection.data: + _ = test.request.columns.split('-')[-1] + if _.isdigit() and int(_) > 10: + if kb.futileUnion is None: + msg = "it is not recommended to perform " + msg += "extended UNION tests if there is not " + msg += "at least one other (potential) " + msg += "technique found. Do you want to skip? [Y/n] " + kb.futileUnion = readInput(msg, default="Y").strip().upper() == 'N' + if kb.futileUnion is False: + continue # Test for UNION query SQL injection reqPayload, vector = unionTest(comment, place, parameter, value, prefix, suffix) if isinstance(reqPayload, basestring): - infoMsg = "%s parameter '%s' is '%s' injectable" % (place, parameter, title) + infoMsg = "%s parameter '%s' is '%s' injectable" % (paramType, parameter, title) logger.info(infoMsg) injectable = True @@ -497,7 +549,7 @@ def checkSqlInjection(place, parameter, value): kb.previousMethod = method - if conf.dummy: + if conf.dummy or conf.offline: injectable = False # If the injection test was successful feed the injection @@ -522,12 +574,15 @@ def checkSqlInjection(place, parameter, value): for dKey, dValue in test.details.items(): if dKey == "dbms": injection.dbms = dValue + if not isinstance(dValue, list): Backend.setDbms(dValue) else: Backend.forceDbms(dValue[0], True) + elif dKey == "dbms_version" and injection.dbms_version is None and not conf.testFilter: injection.dbms_version = Backend.setVersion(dValue) + elif dKey == "os" and injection.os is None: injection.os = Backend.setOs(dValue) @@ -592,6 +647,7 @@ def checkSqlInjection(place, parameter, value): choice = readInput(msg, default=str(conf.verbose), checkBatch=False).strip() conf.verbose = int(choice) setVerbosity() + tests.insert(0, test) elif choice[0] in ("n", "N"): return None elif choice[0] in ("e", "E"): @@ -627,13 +683,22 @@ def checkSqlInjection(place, parameter, value): return injection def heuristicCheckDbms(injection): - retVal = None + """ + This functions is called when boolean-based blind is identified with a + generic payload and the DBMS has not yet been fingerprinted to attempt + to identify with a simple DBMS specific boolean-based test what the DBMS + may be + """ + retVal = False pushValue(kb.injection) kb.injection = injection - randStr1, randStr2 = randomStr(), randomStr() for dbms in getPublicTypeMembers(DBMS, True): + if not FROM_DUMMY_TABLE.get(dbms, ""): + continue + + randStr1, randStr2 = randomStr(), randomStr() Backend.forceDbms(dbms) if checkBooleanExpression("(SELECT '%s'%s)='%s'" % (randStr1, FROM_DUMMY_TABLE.get(dbms, ""), randStr1)): @@ -645,7 +710,7 @@ def heuristicCheckDbms(injection): kb.injection = popValue() if retVal: - infoMsg = "heuristic (extended) test shows that the back-end DBMS " # not as important as "parsing" counter-part (because of false-positives) + infoMsg = "heuristic (extended) test shows that the back-end DBMS " # Not as important as "parsing" counter-part (because of false-positives) infoMsg += "could be '%s' " % retVal logger.info(infoMsg) @@ -658,7 +723,8 @@ def checkFalsePositives(injection): retVal = injection - if all(_ in (PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in injection.data): + if all(_ in (PAYLOAD.TECHNIQUE.BOOLEAN, PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in injection.data) or\ + (len(injection.data) == 1 and PAYLOAD.TECHNIQUE.UNION in injection.data and "Generic" in injection.data[PAYLOAD.TECHNIQUE.UNION].title): pushValue(kb.injection) infoMsg = "checking if the injection point on %s " % injection.place @@ -671,16 +737,14 @@ def checkFalsePositives(injection): kb.injection = injection for i in xrange(conf.level): - randInt1, randInt2, randInt3 = (_() for j in xrange(3)) + while True: + randInt1, randInt2, randInt3 = (_() for j in xrange(3)) - randInt1 = min(randInt1, randInt2, randInt3) - randInt3 = max(randInt1, randInt2, randInt3) + randInt1 = min(randInt1, randInt2, randInt3) + randInt3 = max(randInt1, randInt2, randInt3) - while randInt1 >= randInt2: - randInt2 = _() - - while randInt2 >= randInt3: - randInt3 = _() + if randInt3 > randInt2 > randInt1: + break if not checkBooleanExpression("%d=%d" % (randInt1, randInt1)): retVal = None @@ -763,20 +827,12 @@ def checkFilteredChars(injection): def heuristicCheckSqlInjection(place, parameter): if kb.nullConnection: - debugMsg = "heuristic check skipped " - debugMsg += "because NULL connection used" - logger.debug(debugMsg) - return None - - if wasLastResponseDBMSError(): - debugMsg = "heuristic check skipped " - debugMsg += "because original page content " - debugMsg += "contains DBMS error" + debugMsg = "heuristic check skipped because NULL connection used" logger.debug(debugMsg) return None origValue = conf.paramDict[place][parameter] - + paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place prefix = "" suffix = "" @@ -788,6 +844,7 @@ def heuristicCheckSqlInjection(place, parameter): suffix = conf.suffix randStr = "" + while '\'' not in randStr: randStr = randomStr(length=10, alphabet=HEURISTIC_CHECK_ALPHABET) @@ -802,8 +859,8 @@ def heuristicCheckSqlInjection(place, parameter): parseFilePaths(page) result = wasLastResponseDBMSError() - infoMsg = "heuristic (basic) test shows that %s " % place - infoMsg += "parameter '%s' might " % parameter + infoMsg = "heuristic (basic) test shows that %s parameter " % paramType + infoMsg += "'%s' might " % parameter def _(page): return any(_ in (page or "") for _ in FORMAT_EXCEPTION_STRINGS) @@ -844,6 +901,22 @@ def heuristicCheckSqlInjection(place, parameter): infoMsg += "not be injectable" logger.warn(infoMsg) + kb.heuristicMode = True + + value = "%s%s%s" % (randomStr(), DUMMY_XSS_CHECK_APPENDIX, randomStr()) + payload = "%s%s%s" % (prefix, "'%s" % value, suffix) + payload = agent.payload(place, parameter, newValue=payload) + page, _ = Request.queryPage(payload, place, content=True, raise404=False) + + paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place + + if value in (page or ""): + infoMsg = "heuristic (XSS) test shows that %s parameter " % paramType + infoMsg += "'%s' might be vulnerable to XSS attacks" % parameter + logger.info(infoMsg) + + kb.heuristicMode = False + return kb.heuristicTest def checkDynParam(place, parameter, value): @@ -860,7 +933,9 @@ def checkDynParam(place, parameter, value): dynResult = None randInt = randomInt() - infoMsg = "testing if %s parameter '%s' is dynamic" % (place, parameter) + paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place + + infoMsg = "testing if %s parameter '%s' is dynamic" % (paramType, parameter) logger.info(infoMsg) try: @@ -868,7 +943,7 @@ def checkDynParam(place, parameter, value): dynResult = Request.queryPage(payload, place, raise404=False) if not dynResult: - infoMsg = "confirming that %s parameter '%s' is dynamic" % (place, parameter) + infoMsg = "confirming that %s parameter '%s' is dynamic" % (paramType, parameter) logger.info(infoMsg) randInt = randomInt() @@ -937,11 +1012,15 @@ def checkStability(): like for instance string matching (--string). """ - infoMsg = "testing if the target URL is stable. This can take a couple of seconds" + infoMsg = "testing if the target URL is stable" logger.info(infoMsg) firstPage = kb.originalPage # set inside checkConnection() - time.sleep(1) + + delay = 1 - (time.time() - (kb.originalPageTime or 0)) + delay = max(0, min(1, delay)) + time.sleep(delay) + secondPage, _ = Request.queryPage(content=True, raise404=False) if kb.redirectChoice: @@ -1060,59 +1139,38 @@ def checkWaf(): Reference: http://seclists.org/nmap-dev/2011/q2/att-1005/http-waf-detect.nse """ - if not conf.checkWaf: - return False + if any((conf.string, conf.notString, conf.regexp, conf.dummy, conf.offline)): + return None - infoMsg = "heuristically checking if the target is protected by " - infoMsg += "some kind of WAF/IPS/IDS" - logger.info(infoMsg) + dbmMsg = "heuristically checking if the target is protected by " + dbmMsg += "some kind of WAF/IPS/IDS" + logger.debug(dbmMsg) retVal = False - - backup = dict(conf.parameters) - payload = "%d %s" % (randomInt(), IDS_WAF_CHECK_PAYLOAD) - conf.parameters = dict(backup) - conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&" - conf.parameters[PLACE.GET] += "%s=%s" % (randomStr(), payload) + value = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + DEFAULT_GET_POST_DELIMITER + value += agent.addPayloadDelimiters("%s=%s" % (randomStr(), payload)) - logger.log(CUSTOM_LOGGING.PAYLOAD, payload) - - kb.matchRatio = None - Request.queryPage() - - if kb.errorIsNone and kb.matchRatio is None: - kb.matchRatio = LOWER_RATIO_BOUND - - conf.parameters = dict(backup) - conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&" - conf.parameters[PLACE.GET] += "%s=%d" % (randomStr(), randomInt()) - - trueResult = Request.queryPage() - - if trueResult: - conf.parameters = dict(backup) - conf.parameters[PLACE.GET] = "" if not conf.parameters.get(PLACE.GET) else conf.parameters[PLACE.GET] + "&" - conf.parameters[PLACE.GET] += "%s=%d %s" % (randomStr(), randomInt(), IDS_WAF_CHECK_PAYLOAD) - - try: - falseResult = Request.queryPage() - except SqlmapConnectionException: - falseResult = None - - if not falseResult: - retVal = True - - conf.parameters = dict(backup) + try: + retVal = Request.queryPage(place=PLACE.GET, value=value, getRatioValue=True, noteResponseTime=False, silent=True)[1] < IDS_WAF_CHECK_RATIO + except SqlmapConnectionException: + retVal = True + finally: + kb.matchRatio = None if retVal: - warnMsg = "it appears that the target is protected. Please " - warnMsg += "consider usage of tamper scripts (option '--tamper')" - logger.warn(warnMsg) - else: - infoMsg = "it appears that the target is not protected" - logger.info(infoMsg) + warnMsg = "heuristics detected that the target " + warnMsg += "is protected by some kind of WAF/IPS/IDS" + logger.critical(warnMsg) + + if not conf.identifyWaf: + message = "do you want sqlmap to try to detect backend " + message += "WAF/IPS/IDS? [y/N] " + output = readInput(message, default="N") + + if output and output[0] in ("Y", "y"): + conf.identifyWaf = True return retVal @@ -1130,6 +1188,8 @@ def identifyWaf(): def _(*args, **kwargs): page, headers, code = None, None, None try: + pushValue(kb.redirectChoice) + kb.redirectChoice = REDIRECTION.NO if kwargs.get("get"): kwargs["get"] = urlencode(kwargs["get"]) kwargs["raise404"] = False @@ -1137,6 +1197,8 @@ def identifyWaf(): page, headers, code = Request.getPage(*args, **kwargs) except Exception: pass + finally: + kb.redirectChoice = popValue() return page or "", headers or {}, code retVal = False @@ -1168,9 +1230,10 @@ def identifyWaf(): if output and output[0] not in ("Y", "y"): raise SqlmapUserQuitException else: - infoMsg = "no WAF/IDS/IPS product has been identified" - logger.info(infoMsg) + warnMsg = "no WAF/IDS/IPS product has been identified" + logger.warn(warnMsg) + kb.testType = None kb.testMode = False return retVal @@ -1186,10 +1249,10 @@ def checkNullConnection(): infoMsg = "testing NULL connection to the target URL" logger.info(infoMsg) - pushValue(kb.pageCompress) - kb.pageCompress = False - try: + pushValue(kb.pageCompress) + kb.pageCompress = False + page, headers, _ = Request.getPage(method=HTTPMETHOD.HEAD) if not page and HTTP_HEADER.CONTENT_LENGTH in (headers or {}): @@ -1219,27 +1282,31 @@ def checkNullConnection(): errMsg = getUnicode(errMsg) raise SqlmapConnectionException(errMsg) - kb.pageCompress = popValue() + finally: + kb.pageCompress = popValue() return kb.nullConnection is not None def checkConnection(suppressOutput=False): - if not any((conf.proxy, conf.tor, conf.dummy)): + if not any((conf.proxy, conf.tor, conf.dummy, conf.offline)): try: + debugMsg = "resolving hostname '%s'" % conf.hostname + logger.debug(debugMsg) socket.getaddrinfo(conf.hostname, None) except socket.gaierror: errMsg = "host '%s' does not exist" % conf.hostname raise SqlmapConnectionException(errMsg) except socket.error, ex: errMsg = "problem occurred while " - errMsg += "resolving a host name '%s' ('%s')" % (conf.hostname, str(ex)) + errMsg += "resolving a host name '%s' ('%s')" % (conf.hostname, ex.message) raise SqlmapConnectionException(errMsg) - if not suppressOutput and not conf.dummy: + if not suppressOutput and not conf.dummy and not conf.offline: infoMsg = "testing connection to the target URL" logger.info(infoMsg) try: + kb.originalPageTime = time.time() page, _ = Request.queryPage(content=True, noteResponseTime=False) kb.originalPage = kb.pageTemplate = page diff --git a/lib/controller/controller.py b/lib/controller/controller.py index e57330d96..d5793767c 100644 --- a/lib/controller/controller.py +++ b/lib/controller/controller.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ @@ -28,7 +28,10 @@ from lib.core.common import getUnicode from lib.core.common import hashDBRetrieve from lib.core.common import hashDBWrite from lib.core.common import intersect +from lib.core.common import isListLike from lib.core.common import parseTargetUrl +from lib.core.common import popValue +from lib.core.common import pushValue from lib.core.common import randomStr from lib.core.common import readInput from lib.core.common import safeCSValue @@ -126,8 +129,8 @@ def _selectInjection(): kb.injection = kb.injections[index] def _formatInjection(inj): - data = "Place: %s\n" % inj.place - data += "Parameter: %s\n" % inj.parameter + paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else inj.place + data = "Parameter: %s (%s)\n" % (inj.parameter, paramType) for stype, sdata in inj.data.items(): title = sdata.title @@ -146,14 +149,17 @@ def _formatInjection(inj): vector = "%s%s" % (vector, comment) data += " Type: %s\n" % PAYLOAD.SQLINJECTION[stype] data += " Title: %s\n" % title - data += " Payload: %s\n" % urldecode(payload, unsafe="&", plusspace=(inj.place == PLACE.POST and kb.postSpaceToPlus)) + data += " Payload: %s\n" % urldecode(payload, unsafe="&", plusspace=(inj.place != PLACE.GET and kb.postSpaceToPlus)) data += " Vector: %s\n\n" % vector if conf.verbose > 1 else "\n" return data def _showInjections(): - header = "sqlmap identified the following injection points with " - header += "a total of %d HTTP(s) requests" % kb.testQueryCount + if kb.testQueryCount > 0: + header = "sqlmap identified the following injection point(s) with " + header += "a total of %d HTTP(s) requests" % kb.testQueryCount + else: + header = "sqlmap resumed the following injection point(s) from stored session" if hasattr(conf, "api"): conf.dumper.string("", kb.injections, content_type=CONTENT_TYPE.TECHNIQUES) @@ -189,7 +195,9 @@ def _randomFillBlankFields(value): return retVal def _saveToHashDB(): - injections = hashDBRetrieve(HASHDB_KEYS.KB_INJECTIONS, True) or [] + injections = hashDBRetrieve(HASHDB_KEYS.KB_INJECTIONS, True) + if not isListLike(injections): + injections = [] injections.extend(_ for _ in kb.injections if _ and _.place is not None and _.parameter is not None) _ = dict() @@ -251,7 +259,7 @@ def start(): return True if conf.url and not any((conf.forms, conf.crawlDepth)): - kb.targets.add((conf.url, conf.method, conf.data, conf.cookie)) + kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None)) if conf.configFile and not kb.targets: errMsg = "you did not edit the configuration file properly, set " @@ -264,13 +272,16 @@ def start(): logger.info(infoMsg) hostCount = 0 + initialHeaders = list(conf.httpHeaders) - for targetUrl, targetMethod, targetData, targetCookie in kb.targets: + for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets: try: conf.url = targetUrl - conf.method = targetMethod + conf.method = targetMethod.upper() if targetMethod else targetMethod conf.data = targetData conf.cookie = targetCookie + conf.httpHeaders = list(initialHeaders) + conf.httpHeaders.extend(targetHeaders or []) initTargetEnv() parseTargetUrl() @@ -308,13 +319,13 @@ def start(): if conf.forms: message = "[#%d] form:\n%s %s" % (hostCount, conf.method or HTTPMETHOD.GET, targetUrl) else: - message = "URL %d:\n%s %s%s" % (hostCount, conf.method or HTTPMETHOD.GET, targetUrl, " (PageRank: %s)" % get_pagerank(targetUrl) if conf.googleDork and conf.pageRank else "") + message = "URL %d:\n%s %s%s" % (hostCount, HTTPMETHOD.GET, targetUrl, " (PageRank: %s)" % get_pagerank(targetUrl) if conf.googleDork and conf.pageRank else "") if conf.cookie: message += "\nCookie: %s" % conf.cookie if conf.data is not None: - message += "\nPOST data: %s" % urlencode(conf.data) if conf.data else "" + message += "\n%s data: %s" % ((conf.method if conf.method != HTTPMETHOD.GET else conf.method) or HTTPMETHOD.POST, urlencode(conf.data) if conf.data else "") if conf.forms: if conf.method == HTTPMETHOD.GET and targetUrl.find("?") == -1: @@ -324,13 +335,13 @@ def start(): test = readInput(message, default="Y") if not test or test[0] in ("y", "Y"): - if conf.method == HTTPMETHOD.POST: - message = "Edit POST data [default: %s]%s: " % (urlencode(conf.data) if conf.data else "None", " (Warning: blank fields detected)" if conf.data and extractRegexResult(EMPTY_FORM_FIELDS_REGEX, conf.data) else "") + if conf.method != HTTPMETHOD.GET: + message = "Edit %s data [default: %s]%s: " % (conf.method, urlencode(conf.data) if conf.data else "None", " (Warning: blank fields detected)" if conf.data and extractRegexResult(EMPTY_FORM_FIELDS_REGEX, conf.data) else "") conf.data = readInput(message, default=conf.data) conf.data = _randomFillBlankFields(conf.data) conf.data = urldecode(conf.data) if conf.data and urlencode(DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data - elif conf.method == HTTPMETHOD.GET: + else: if targetUrl.find("?") > -1: firstPart = targetUrl[:targetUrl.find("?")] secondPart = targetUrl[targetUrl.find("?") + 1:] @@ -366,8 +377,7 @@ def start(): if not checkConnection(suppressOutput=conf.forms) or not checkString() or not checkRegexp(): continue - if conf.checkWaf: - checkWaf() + checkWaf() if conf.identifyWaf: identifyWaf() @@ -420,11 +430,16 @@ def start(): if skip: continue + if kb.testOnlyCustom and place not in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER): + continue + if place not in conf.paramDict: continue paramDict = conf.paramDict[place] + paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place + for parameter, value in paramDict.items(): if not proceed: break @@ -436,7 +451,7 @@ def start(): if paramKey in kb.testedParams: testSqlInj = False - infoMsg = "skipping previously processed %s parameter '%s'" % (place, parameter) + infoMsg = "skipping previously processed %s parameter '%s'" % (paramType, parameter) logger.info(infoMsg) elif parameter in conf.testParameter: @@ -445,70 +460,90 @@ def start(): elif parameter == conf.rParam: testSqlInj = False - infoMsg = "skipping randomizing %s parameter '%s'" % (place, parameter) + infoMsg = "skipping randomizing %s parameter '%s'" % (paramType, parameter) logger.info(infoMsg) elif parameter in conf.skip: testSqlInj = False - infoMsg = "skipping %s parameter '%s'" % (place, parameter) + infoMsg = "skipping %s parameter '%s'" % (paramType, parameter) + logger.info(infoMsg) + + elif parameter == conf.csrfToken: + testSqlInj = False + + infoMsg = "skipping anti-CSRF token parameter '%s'" % parameter logger.info(infoMsg) # Ignore session-like parameters for --level < 4 elif conf.level < 4 and (parameter.upper() in IGNORE_PARAMETERS or parameter.upper().startswith(GOOGLE_ANALYTICS_COOKIE_PREFIX)): testSqlInj = False - infoMsg = "ignoring %s parameter '%s'" % (place, parameter) + infoMsg = "ignoring %s parameter '%s'" % (paramType, parameter) logger.info(infoMsg) - elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech: + elif PAYLOAD.TECHNIQUE.BOOLEAN in conf.tech or conf.skipStatic: check = checkDynParam(place, parameter, value) if not check: - warnMsg = "%s parameter '%s' does not appear dynamic" % (place, parameter) + warnMsg = "%s parameter '%s' does not appear dynamic" % (paramType, parameter) logger.warn(warnMsg) + if conf.skipStatic: + infoMsg = "skipping static %s parameter '%s'" % (paramType, parameter) + logger.info(infoMsg) + + testSqlInj = False else: - infoMsg = "%s parameter '%s' is dynamic" % (place, parameter) + infoMsg = "%s parameter '%s' is dynamic" % (paramType, parameter) logger.info(infoMsg) kb.testedParams.add(paramKey) if testSqlInj: - check = heuristicCheckSqlInjection(place, parameter) + try: + if place == PLACE.COOKIE: + pushValue(kb.mergeCookies) + kb.mergeCookies = False - if check != HEURISTIC_TEST.POSITIVE: - if conf.smart or (kb.ignoreCasted and check == HEURISTIC_TEST.CASTED): - infoMsg = "skipping %s parameter '%s'" % (place, parameter) - logger.info(infoMsg) - continue + check = heuristicCheckSqlInjection(place, parameter) - infoMsg = "testing for SQL injection on %s " % place - infoMsg += "parameter '%s'" % parameter - logger.info(infoMsg) + if check != HEURISTIC_TEST.POSITIVE: + if conf.smart or (kb.ignoreCasted and check == HEURISTIC_TEST.CASTED): + infoMsg = "skipping %s parameter '%s'" % (paramType, parameter) + logger.info(infoMsg) + continue - injection = checkSqlInjection(place, parameter, value) - proceed = not kb.endDetection + infoMsg = "testing for SQL injection on %s " % paramType + infoMsg += "parameter '%s'" % parameter + logger.info(infoMsg) - if injection is not None and injection.place is not None: - kb.injections.append(injection) + injection = checkSqlInjection(place, parameter, value) + proceed = not kb.endDetection - # In case when user wants to end detection phase (Ctrl+C) - if not proceed: - break + if injection is not None and injection.place is not None: + kb.injections.append(injection) - msg = "%s parameter '%s' " % (injection.place, injection.parameter) - msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] " - test = readInput(msg, default="N") + # In case when user wants to end detection phase (Ctrl+C) + if not proceed: + break - if test[0] not in ("y", "Y"): - proceed = False - paramKey = (conf.hostname, conf.path, None, None) - kb.testedParams.add(paramKey) - else: - warnMsg = "%s parameter '%s' is not " % (place, parameter) - warnMsg += "injectable" - logger.warn(warnMsg) + msg = "%s parameter '%s' " % (injection.place, injection.parameter) + msg += "is vulnerable. Do you want to keep testing the others (if any)? [y/N] " + test = readInput(msg, default="N") + + if test[0] not in ("y", "Y"): + proceed = False + paramKey = (conf.hostname, conf.path, None, None) + kb.testedParams.add(paramKey) + else: + warnMsg = "%s parameter '%s' is not " % (paramType, parameter) + warnMsg += "injectable" + logger.warn(warnMsg) + + finally: + if place == PLACE.COOKIE: + kb.mergeCookies = popValue() if len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None): if kb.vainRun and not conf.multipleTargets: @@ -562,6 +597,11 @@ def start(): errMsg += "expression that you have chosen " errMsg += "does not match exclusively True responses" + if not conf.tamper: + errMsg += " If you suspect that there is some kind of protection mechanism " + errMsg += "involved (e.g. WAF) maybe you could retry " + errMsg += "with an option '--tamper' (e.g. '--tamper=space2comment')" + raise SqlmapNotVulnerableException(errMsg) else: # Flush the flag diff --git a/lib/controller/handler.py b/lib/controller/handler.py index 38fece4ca..471070b1b 100644 --- a/lib/controller/handler.py +++ b/lib/controller/handler.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ @@ -71,9 +71,9 @@ def setHandler(): items.remove(_) items.insert(0, _) - for name, aliases, Handler, Connector in items: - if conf.dbms and conf.dbms not in aliases: - debugMsg = "skipping test for %s" % name + for dbms, aliases, Handler, Connector in items: + if conf.dbms and conf.dbms.lower() != dbms and conf.dbms.lower() not in aliases: + debugMsg = "skipping test for %s" % dbms logger.debug(debugMsg) continue @@ -84,7 +84,7 @@ def setHandler(): logger.debug("forcing timeout to 10 seconds") conf.timeout = 10 - dialect = DBMS_DICT[name][3] + dialect = DBMS_DICT[dbms][3] if dialect: sqlalchemy = SQLAlchemy(dialect=dialect) @@ -93,7 +93,10 @@ def setHandler(): if sqlalchemy.connector: conf.dbmsConnector = sqlalchemy else: - conf.dbmsConnector.connect() + try: + conf.dbmsConnector.connect() + except NameError: + pass else: conf.dbmsConnector.connect() diff --git a/lib/core/__init__.py b/lib/core/__init__.py index 5bad2814c..8d7bcd8f0 100644 --- a/lib/core/__init__.py +++ b/lib/core/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/lib/core/agent.py b/lib/core/agent.py index ce78736ee..556f379a9 100644 --- a/lib/core/agent.py +++ b/lib/core/agent.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ @@ -10,6 +10,7 @@ import re from lib.core.common import Backend from lib.core.common import extractRegexResult from lib.core.common import getSQLSnippet +from lib.core.common import getUnicode from lib.core.common import isDBMSVersionAtLeast from lib.core.common import isNumber from lib.core.common import isTechniqueAvailable @@ -19,6 +20,7 @@ from lib.core.common import safeSQLIdentificatorNaming from lib.core.common import singleTimeWarnMessage from lib.core.common import splitFields from lib.core.common import unArrayizeValue +from lib.core.common import urlencode from lib.core.common import zeroDepthSearch from lib.core.data import conf from lib.core.data import kb @@ -26,11 +28,15 @@ from lib.core.data import queries from lib.core.dicts import DUMP_DATA_PREPROCESS from lib.core.dicts import FROM_DUMMY_TABLE from lib.core.enums import DBMS +from lib.core.enums import HTTP_HEADER from lib.core.enums import PAYLOAD from lib.core.enums import PLACE from lib.core.enums import POST_HINT from lib.core.exception import SqlmapNoneDataException +from lib.core.settings import BOUNDARY_BACKSLASH_MARKER from lib.core.settings import CUSTOM_INJECTION_MARK_CHAR +from lib.core.settings import DEFAULT_COOKIE_DELIMITER +from lib.core.settings import DEFAULT_GET_POST_DELIMITER from lib.core.settings import GENERIC_SQL_COMMENT from lib.core.settings import PAYLOAD_DELIMITER from lib.core.settings import REPLACEMENT_MARKER @@ -73,7 +79,9 @@ class Agent(object): retVal = "" - if where is None and isTechniqueAvailable(kb.technique): + if kb.forceWhere: + where = kb.forceWhere + elif where is None and isTechniqueAvailable(kb.technique): where = kb.injection.data[kb.technique].where if kb.injection.place is not None: @@ -84,7 +92,7 @@ class Agent(object): paramString = conf.parameters[place] paramDict = conf.paramDict[place] - origValue = paramDict[parameter] + origValue = getUnicode(paramDict[parameter]) if place == PLACE.URI: paramString = origValue @@ -98,10 +106,8 @@ class Agent(object): origValue = origValue.split(CUSTOM_INJECTION_MARK_CHAR)[0] if kb.postHint in (POST_HINT.SOAP, POST_HINT.XML): origValue = origValue.split('>')[-1] - elif kb.postHint == POST_HINT.JSON: - origValue = extractRegexResult(r"(?s)\"\s*:\s*(?P\d+\Z)", origValue) or extractRegexResult(r'(?s)(?P[^"]+\Z)', origValue) - elif kb.postHint == POST_HINT.JSON_LIKE: - origValue = extractRegexResult(r'(?s)\'\s*:\s*(?P\d+\Z)', origValue) or extractRegexResult(r"(?s)(?P[^']+\Z)", origValue) + elif kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE): + origValue = extractRegexResult(r"(?s)\"\s*:\s*(?P\d+\Z)", origValue) or extractRegexResult(r'(?s)\s*(?P[^"\[,]+\Z)', origValue) else: _ = extractRegexResult(r"(?s)(?P[^\s<>{}();'\"&]+\Z)", origValue) or "" origValue = _.split('=', 1)[1] if '=' in _ else "" @@ -109,6 +115,14 @@ class Agent(object): paramString = origValue origValue = origValue.split(CUSTOM_INJECTION_MARK_CHAR)[0] origValue = origValue[origValue.index(',') + 1:] + match = re.search(r"([^;]+)=(?P[^;]+);?\Z", origValue) + if match: + origValue = match.group("value") + elif ',' in paramString: + header = paramString.split(',')[0] + + if header.upper() == HTTP_HEADER.AUTHORIZATION.upper(): + origValue = origValue.split(' ')[-1].split(':')[-1] if conf.prefix: value = origValue @@ -152,7 +166,36 @@ class Agent(object): elif place in (PLACE.USER_AGENT, PLACE.REFERER, PLACE.HOST): retVal = paramString.replace(origValue, self.addPayloadDelimiters(newValue)) else: - retVal = re.sub(r"(\A|\b)%s=%s" % (re.escape(parameter), re.escape(origValue)), "%s=%s" % (parameter, self.addPayloadDelimiters(newValue.replace("\\", "\\\\"))), paramString) + def _(pattern, repl, string): + retVal = string + match = None + for match in re.finditer(pattern, string): + pass + + if match: + while True: + _ = re.search(r"\\g<([^>]+)>", repl) + if _: + try: + repl = repl.replace(_.group(0), match.group(int(_.group(1)) if _.group(1).isdigit() else _.group(1))) + except IndexError: + break + else: + break + retVal = string[:match.start()] + repl + string[match.end():] + return retVal + + if origValue: + regex = r"(\A|\b)%s=%s%s" % (re.escape(parameter), re.escape(origValue), r"(\Z|\b)" if origValue[-1].isalnum() else "") + retVal = _(regex, "%s=%s" % (parameter, self.addPayloadDelimiters(newValue.replace("\\", "\\\\"))), paramString) + else: + retVal = _(r"(\A|\b)%s=%s(\Z|%s|%s|\s)" % (re.escape(parameter), re.escape(origValue), DEFAULT_GET_POST_DELIMITER, DEFAULT_COOKIE_DELIMITER), "%s=%s\g<2>" % (parameter, self.addPayloadDelimiters(newValue.replace("\\", "\\\\"))), paramString) + + if retVal == paramString and urlencode(parameter) != parameter: + retVal = _(r"(\A|\b)%s=%s" % (re.escape(urlencode(parameter)), re.escape(origValue)), "%s=%s" % (urlencode(parameter), self.addPayloadDelimiters(newValue.replace("\\", "\\\\"))), paramString) + + if retVal: + retVal = retVal.replace(BOUNDARY_BACKSLASH_MARKER, '\\') return retVal @@ -176,6 +219,9 @@ class Agent(object): if conf.direct: return self.payloadDirect(expression) + if expression is None: + return None + expression = self.cleanupPayload(expression) expression = unescaper.escape(expression) query = None @@ -204,7 +250,7 @@ class Agent(object): if not (expression and expression[0] == ';') and not (query and query[-1] in ('(', ')') and expression and expression[0] in ('(', ')')) and not (query and query[-1] == '('): query += " " - query = "%s%s" % (query, expression) + query = "%s%s" % ((query or "").replace('\\', BOUNDARY_BACKSLASH_MARKER), expression) return query @@ -217,6 +263,9 @@ class Agent(object): if conf.direct: return self.payloadDirect(expression) + if expression is None: + return None + expression = self.cleanupPayload(expression) # Take default values if None @@ -238,7 +287,7 @@ class Agent(object): pass elif suffix and not comment: - expression += suffix + expression += suffix.replace('\\', BOUNDARY_BACKSLASH_MARKER) return re.sub(r"(?s);\W*;", ";", expression) @@ -984,7 +1033,7 @@ class Agent(object): """ _ = re.escape(PAYLOAD_DELIMITER) - return re.sub("(%s.*?%s)" % (_, _), ("%s%s%s" % (PAYLOAD_DELIMITER, payload, PAYLOAD_DELIMITER)).replace("\\", r"\\"), value) if value else value + return re.sub("(?s)(%s.*?%s)" % (_, _), ("%s%s%s" % (PAYLOAD_DELIMITER, payload, PAYLOAD_DELIMITER)).replace("\\", r"\\"), value) if value else value def runAsDBMSUser(self, query): if conf.dbmsCred and "Ad Hoc Distributed Queries" not in query: diff --git a/lib/core/bigarray.py b/lib/core/bigarray.py index 04ef7a691..0e42433d8 100644 --- a/lib/core/bigarray.py +++ b/lib/core/bigarray.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ @@ -10,10 +10,27 @@ try: except: import pickle +import itertools import os +import sys import tempfile -from lib.core.settings import BIGARRAY_CHUNK_LENGTH +from lib.core.exception import SqlmapSystemException +from lib.core.settings import BIGARRAY_CHUNK_SIZE + +DEFAULT_SIZE_OF = sys.getsizeof(object()) + +def _size_of(object_): + """ + Returns total size of a given object_ (in bytes) + """ + + retval = sys.getsizeof(object_, DEFAULT_SIZE_OF) + if isinstance(object_, dict): + retval += sum(_size_of(_) for _ in itertools.chain.from_iterable(object_.items())) + elif hasattr(object_, "__iter__"): + retval += sum(_size_of(_) for _ in object_) + return retval class Cache(object): """ @@ -32,15 +49,21 @@ class BigArray(list): def __init__(self): self.chunks = [[]] + self.chunk_length = sys.maxint self.cache = None - self.length = 0 self.filenames = set() + self._os_remove = os.remove + self._size_counter = 0 def append(self, value): self.chunks[-1].append(value) - if len(self.chunks[-1]) >= BIGARRAY_CHUNK_LENGTH: + if self.chunk_length == sys.maxint: + self._size_counter += _size_of(value) + if self._size_counter >= BIGARRAY_CHUNK_SIZE: + self.chunk_length = len(self.chunks[-1]) + self._size_counter = None + if len(self.chunks[-1]) >= self.chunk_length: filename = self._dump(self.chunks[-1]) - del(self.chunks[-1][:]) self.chunks[-1] = filename self.chunks.append([]) @@ -51,8 +74,13 @@ class BigArray(list): def pop(self): if len(self.chunks[-1]) < 1: self.chunks.pop() - with open(self.chunks[-1], "rb") as fp: - self.chunks[-1] = pickle.load(fp) + try: + with open(self.chunks[-1], "rb") as fp: + self.chunks[-1] = pickle.load(fp) + except IOError, ex: + errMsg = "exception occurred while retrieving data " + errMsg += "from a temporary file ('%s')" % ex + raise SqlmapSystemException, errMsg return self.chunks[-1].pop() def index(self, value): @@ -61,21 +89,41 @@ class BigArray(list): return index return ValueError, "%s is not in list" % value - def _dump(self, value): - handle, filename = tempfile.mkstemp(prefix="sqlmapba-") - self.filenames.add(filename) - os.close(handle) - with open(filename, "w+b") as fp: - pickle.dump(value, fp, pickle.HIGHEST_PROTOCOL) - return filename + def _dump(self, chunk): + try: + handle, filename = tempfile.mkstemp() + self.filenames.add(filename) + os.close(handle) + with open(filename, "w+b") as fp: + pickle.dump(chunk, fp, pickle.HIGHEST_PROTOCOL) + return filename + except (OSError, IOError), ex: + errMsg = "exception occurred while storing data " + errMsg += "to a temporary file ('%s'). Please " % ex + errMsg += "make sure that there is enough disk space left. If problem persists, " + errMsg += "try to set environment variable 'TEMP' to a location " + errMsg += "writeable by the current user" + raise SqlmapSystemException, errMsg def _checkcache(self, index): if (self.cache and self.cache.index != index and self.cache.dirty): filename = self._dump(self.cache.data) self.chunks[self.cache.index] = filename if not (self.cache and self.cache.index == index): - with open(self.chunks[index], "rb") as fp: - self.cache = Cache(index, pickle.load(fp), False) + try: + with open(self.chunks[index], "rb") as fp: + self.cache = Cache(index, pickle.load(fp), False) + except IOError, ex: + errMsg = "exception occurred while retrieving data " + errMsg += "from a temporary file ('%s')" % ex + raise SqlmapSystemException, errMsg + + def __getstate__(self): + return self.chunks, self.filenames + + def __setstate__(self, state): + self.__init__() + self.chunks, self.filenames = state def __getslice__(self, i, j): retval = BigArray() @@ -88,8 +136,8 @@ class BigArray(list): def __getitem__(self, y): if y < 0: y += len(self) - index = y / BIGARRAY_CHUNK_LENGTH - offset = y % BIGARRAY_CHUNK_LENGTH + index = y / self.chunk_length + offset = y % self.chunk_length chunk = self.chunks[index] if isinstance(chunk, list): return chunk[offset] @@ -98,8 +146,8 @@ class BigArray(list): return self.cache.data[offset] def __setitem__(self, y, value): - index = y / BIGARRAY_CHUNK_LENGTH - offset = y % BIGARRAY_CHUNK_LENGTH + index = y / self.chunk_length + offset = y % self.chunk_length chunk = self.chunks[index] if isinstance(chunk, list): chunk[offset] = value @@ -116,11 +164,4 @@ class BigArray(list): yield self[i] def __len__(self): - return len(self.chunks[-1]) if len(self.chunks) == 1 else (len(self.chunks) - 1) * BIGARRAY_CHUNK_LENGTH + len(self.chunks[-1]) - - def __del__(self): - for filename in self.filenames: - try: - os.remove(filename) - except: - pass + return len(self.chunks[-1]) if len(self.chunks) == 1 else (len(self.chunks) - 1) * self.chunk_length + len(self.chunks[-1]) diff --git a/lib/core/common.py b/lib/core/common.py index 245763b9b..6f9303876 100755 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ @@ -9,8 +9,12 @@ import codecs import contextlib import cookielib import copy +import getpass +import hashlib import httplib import inspect +import json +import locale import logging import ntpath import os @@ -23,6 +27,7 @@ import sys import tempfile import time import urllib +import urllib2 import urlparse import unicodedata @@ -36,7 +41,9 @@ from subprocess import PIPE from subprocess import Popen as execute from xml.dom import minidom from xml.sax import parse +from xml.sax import SAXParseException +from extra.beep.beep import beep from extra.cloak.cloak import decloak from extra.safe2bin.safe2bin import safecharencode from lib.core.bigarray import BigArray @@ -71,12 +78,13 @@ from lib.core.enums import PAYLOAD from lib.core.enums import REFLECTIVE_COUNTER from lib.core.enums import SORT_ORDER from lib.core.exception import SqlmapDataException -from lib.core.exception import SqlmapFilePathException from lib.core.exception import SqlmapGenericException from lib.core.exception import SqlmapNoneDataException +from lib.core.exception import SqlmapInstallationException from lib.core.exception import SqlmapMissingDependence from lib.core.exception import SqlmapSilentQuitException from lib.core.exception import SqlmapSyntaxException +from lib.core.exception import SqlmapSystemException from lib.core.exception import SqlmapUserQuitException from lib.core.log import LOGGER_HANDLER from lib.core.optiondict import optDict @@ -90,14 +98,14 @@ from lib.core.settings import DBMS_DIRECTORY_DICT from lib.core.settings import DEFAULT_COOKIE_DELIMITER from lib.core.settings import DEFAULT_GET_POST_DELIMITER from lib.core.settings import DEFAULT_MSSQL_SCHEMA -from lib.core.settings import DESCRIPTION -from lib.core.settings import DUMMY_SQL_INJECTION_CHARS from lib.core.settings import DUMMY_USER_INJECTION from lib.core.settings import DYNAMICITY_MARK_LENGTH from lib.core.settings import ERROR_PARSING_REGEXES from lib.core.settings import FORCE_COOKIE_EXPIRATION_TIME from lib.core.settings import FORM_SEARCH_REGEX from lib.core.settings import GENERIC_DOC_ROOT_DIRECTORY_NAMES +from lib.core.settings import GIT_PAGE +from lib.core.settings import GITHUB_REPORT_OAUTH_TOKEN from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_PREFIX from lib.core.settings import HASHDB_MILESTONE_VALUE from lib.core.settings import HOST_ALIASES @@ -110,7 +118,6 @@ from lib.core.settings import LARGE_OUTPUT_THRESHOLD from lib.core.settings import MIN_ENCODED_LEN_CHECK from lib.core.settings import MIN_TIME_RESPONSES from lib.core.settings import MIN_VALID_DELAYED_RESPONSE -from lib.core.settings import ML from lib.core.settings import NETSCAPE_FORMAT_HEADER_COOKIES from lib.core.settings import NULL from lib.core.settings import PARAMETER_AMP_MARKER @@ -127,9 +134,7 @@ from lib.core.settings import REFLECTED_MAX_REGEX_PARTS from lib.core.settings import REFLECTED_REPLACEMENT_REGEX from lib.core.settings import REFLECTED_VALUE_MARKER from lib.core.settings import REFLECTIVE_MISS_THRESHOLD -from lib.core.settings import REVISION from lib.core.settings import SENSITIVE_DATA_REGEX -from lib.core.settings import SITE from lib.core.settings import SUPPORTED_DBMS from lib.core.settings import TEXT_TAG_REGEX from lib.core.settings import TIME_STDEV_COEFF @@ -139,7 +144,6 @@ from lib.core.settings import URI_QUESTION_MARKER from lib.core.settings import URLENCODE_CHAR_LIMIT from lib.core.settings import URLENCODE_FAILSAFE_CHARS from lib.core.settings import USER_AGENT_ALIASES -from lib.core.settings import VERSION from lib.core.settings import VERSION_STRING from lib.core.threads import getCurrentThreadData from lib.utils.sqlalchemy import _sqlalchemy @@ -431,10 +435,9 @@ class Backend: This functions is called to: - 1. Sort the tests, getSortedInjectionTests() - detection phase. - 2. Ask user whether or not skip specific DBMS tests in detection phase, + 1. Ask user whether or not skip specific DBMS tests in detection phase, lib/controller/checks.py - detection phase. - 3. Sort the fingerprint of the DBMS, lib/controller/handler.py - + 2. Sort the fingerprint of the DBMS, lib/controller/handler.py - fingerprint phase. """ @@ -442,6 +445,13 @@ class Backend: @staticmethod def getIdentifiedDbms(): + """ + This functions is called to: + + 1. Sort the tests, getSortedInjectionTests() - detection phase. + 2. Etc. + """ + dbms = None if not kb: @@ -449,13 +459,13 @@ class Backend: elif Backend.getForcedDbms() is not None: dbms = Backend.getForcedDbms() elif Backend.getDbms() is not None: - dbms = kb.dbms - elif conf.get("dbms"): - dbms = conf.dbms - elif Backend.getErrorParsedDBMSes(): - dbms = unArrayizeValue(Backend.getErrorParsedDBMSes()) + dbms = Backend.getDbms() elif kb.get("injection") and kb.injection.dbms: dbms = unArrayizeValue(kb.injection.dbms) + elif Backend.getErrorParsedDBMSes(): + dbms = unArrayizeValue(Backend.getErrorParsedDBMSes()) + elif conf.get("dbms"): + dbms = conf.get("dbms") return aliasToDbmsEnum(dbms) @@ -537,7 +547,6 @@ def paramToDict(place, parameters=None): if place in conf.parameters and not parameters: parameters = conf.parameters[place] - parameters = parameters.replace(", ", ",") parameters = re.sub(r"&(\w{1,4});", r"%s\g<1>%s" % (PARAMETER_AMP_MARKER, PARAMETER_SEMICOLON_MARKER), parameters) if place == PLACE.COOKIE: splitParams = parameters.split(conf.cookieDel or DEFAULT_COOKIE_DELIMITER) @@ -549,20 +558,23 @@ def paramToDict(place, parameters=None): parts = element.split("=") if len(parts) >= 2: - parameter = parts[0].replace(" ", "") + parameter = urldecode(parts[0].replace(" ", "")) + + if not parameter: + continue if conf.paramDel and conf.paramDel == '\n': parts[-1] = parts[-1].rstrip() condition = not conf.testParameter - condition |= parameter in conf.testParameter + condition |= conf.testParameter is not None and parameter in conf.testParameter condition |= place == PLACE.COOKIE and len(intersect((PLACE.COOKIE,), conf.testParameter, True)) > 0 if condition: testableParameters[parameter] = "=".join(parts[1:]) - if not conf.multipleTargets: + if not conf.multipleTargets and not (conf.csrfToken and parameter == conf.csrfToken): _ = urldecode(testableParameters[parameter], convall=True) - if (_.strip(DUMMY_SQL_INJECTION_CHARS) != _\ + if (_.endswith("'") and _.count("'") == 1 or re.search(r'\A9{3,}', _) or re.search(DUMMY_USER_INJECTION, _))\ and not parameter.upper().startswith(GOOGLE_ANALYTICS_COOKIE_PREFIX): warnMsg = "it appears that you have provided tainted parameter values " @@ -572,10 +584,15 @@ def paramToDict(place, parameters=None): warnMsg += "so sqlmap could be able to run properly" logger.warn(warnMsg) - message = "are you sure you want to continue? [y/N] " + message = "are you really sure that you want to continue (sqlmap could have problems)? [y/N] " test = readInput(message, default="N") if test[0] not in ("y", "Y"): raise SqlmapSilentQuitException + elif not _: + warnMsg = "provided value for parameter '%s' is empty. " % parameter + warnMsg += "Please, always use only valid parameter values " + warnMsg += "so sqlmap could be able to run properly" + logger.warn(warnMsg) if conf.testParameter and not testableParameters: paramStr = ", ".join(test for test in conf.testParameter) @@ -834,11 +851,19 @@ def dataToTrafficFile(data): except IOError, ex: errMsg = "something went wrong while trying " errMsg += "to write to the traffic file '%s' ('%s')" % (conf.trafficFile, ex) - raise SqlmapGenericException(errMsg) + raise SqlmapSystemException(errMsg) def dataToDumpFile(dumpFile, data): - dumpFile.write(data) - dumpFile.flush() + try: + dumpFile.write(data) + dumpFile.flush() + except IOError, ex: + if "No space left" in getUnicode(ex): + errMsg = "no space left on output device" + logger.error(errMsg) + else: + raise + def dataToOutFile(filename, data): retVal = None @@ -846,8 +871,13 @@ def dataToOutFile(filename, data): if data: retVal = os.path.join(conf.filePath, filePathToSafeString(filename)) - with codecs.open(retVal, "wb", UNICODE_ENCODING) as f: - f.write(data) + try: + with open(retVal, "w+b") as f: + f.write(data) + except IOError, ex: + errMsg = "something went wrong while trying to write " + errMsg += "to the output file ('%s')" % ex.message + raise SqlmapGenericException(errMsg) return retVal @@ -866,11 +896,11 @@ def readInput(message, default=None, checkBatch=True): elif message[-1] == ']': message += " " - if kb.prependFlag: + if kb.get("prependFlag"): message = "\n%s" % message kb.prependFlag = False - if conf.answers: + if conf.get("answers"): for item in conf.answers.split(','): question = item.split('=')[0].strip() answer = item.split('=')[1] if len(item.split('=')) > 1 else None @@ -886,7 +916,7 @@ def readInput(message, default=None, checkBatch=True): break if retVal is None: - if checkBatch and conf.batch: + if checkBatch and conf.get("batch"): if isListLike(default): options = ",".join(getUnicode(opt, UNICODE_ENCODING) for opt in default) elif default: @@ -902,12 +932,16 @@ def readInput(message, default=None, checkBatch=True): retVal = default else: logging._acquireLock() + + if conf.get("beep"): + beep() + dataToStdout("\r%s" % message, forceOutput=True, bold=True) kb.prependFlag = False try: retVal = raw_input() or default - retVal = getUnicode(retVal, system=True) if retVal else retVal + retVal = getUnicode(retVal, encoding=sys.stdin.encoding) if retVal else retVal except: time.sleep(0.05) # Reference: http://www.gossamer-threads.com/lists/python/python/781893 kb.prependFlag = True @@ -974,13 +1008,33 @@ def sanitizeStr(value): return getUnicode(value).replace("\n", " ").replace("\r", "") +def getHeader(headers, key): + retVal = None + for _ in (headers or {}): + if _.upper() == key.upper(): + retVal = headers[_] + break + return retVal + def checkFile(filename): """ - Checks for file existence + Checks for file existence and readability """ - if not os.path.isfile(filename): - raise SqlmapFilePathException("unable to read file '%s'" % filename) + valid = True + + if filename is None or not os.path.isfile(filename): + valid = False + + if valid: + try: + with open(filename, "rb"): + pass + except: + valid = False + + if not valid: + raise SqlmapSystemException("unable to read file '%s'" % filename) def banner(): """ @@ -1046,13 +1100,18 @@ def setPaths(): paths.SQLMAP_UDF_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "udf") paths.SQLMAP_XML_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "xml") paths.SQLMAP_XML_BANNER_PATH = os.path.join(paths.SQLMAP_XML_PATH, "banner") - paths.SQLMAP_OUTPUT_PATH = paths.get("SQLMAP_OUTPUT_PATH", os.path.join(os.path.expanduser("~"), ".sqlmap", "output")) + paths.SQLMAP_XML_PAYLOADS_PATH = os.path.join(paths.SQLMAP_XML_PATH, "payloads") + _ = os.path.join(os.path.expandvars(os.path.expanduser("~")), ".sqlmap") + paths.SQLMAP_OUTPUT_PATH = getUnicode(paths.get("SQLMAP_OUTPUT_PATH", os.path.join(_, "output")), encoding=sys.getfilesystemencoding()) paths.SQLMAP_DUMP_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "dump") paths.SQLMAP_FILES_PATH = os.path.join(paths.SQLMAP_OUTPUT_PATH, "%s", "files") # sqlmap files - paths.SQLMAP_HISTORY = os.path.join(os.path.expanduser('~'), ".sqlmap_history") + paths.OS_SHELL_HISTORY = os.path.join(_, "os.hst") + paths.SQL_SHELL_HISTORY = os.path.join(_, "sql.hst") + paths.SQLMAP_SHELL_HISTORY = os.path.join(_, "sqlmap.hst") + paths.GITHUB_HISTORY = os.path.join(_, "github.hst") paths.SQLMAP_CONFIG = os.path.join(paths.SQLMAP_ROOT_PATH, "sqlmap-%s.conf" % randomStr()) paths.COMMON_COLUMNS = os.path.join(paths.SQLMAP_TXT_PATH, "common-columns.txt") paths.COMMON_TABLES = os.path.join(paths.SQLMAP_TXT_PATH, "common-tables.txt") @@ -1062,8 +1121,7 @@ def setPaths(): paths.USER_AGENTS = os.path.join(paths.SQLMAP_TXT_PATH, "user-agents.txt") paths.WORDLIST = os.path.join(paths.SQLMAP_TXT_PATH, "wordlist.zip") paths.ERRORS_XML = os.path.join(paths.SQLMAP_XML_PATH, "errors.xml") - paths.PAYLOADS_XML = os.path.join(paths.SQLMAP_XML_PATH, "payloads.xml") - paths.INJECTIONS_XML = os.path.join(paths.SQLMAP_XML_PATH, "injections.xml") + paths.BOUNDARIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "boundaries.xml") paths.LIVE_TESTS_XML = os.path.join(paths.SQLMAP_XML_PATH, "livetests.xml") paths.QUERIES_XML = os.path.join(paths.SQLMAP_XML_PATH, "queries.xml") paths.GENERIC_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "generic.xml") @@ -1072,6 +1130,10 @@ def setPaths(): paths.ORACLE_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "oracle.xml") paths.PGSQL_XML = os.path.join(paths.SQLMAP_XML_BANNER_PATH, "postgresql.xml") + for path in paths.values(): + if any(path.endswith(_) for _ in (".txt", ".xml", ".zip")): + checkFile(path) + def weAreFrozen(): """ Returns whether we are frozen via py2exe. @@ -1131,7 +1193,7 @@ def parseTargetDirect(): raise SqlmapSyntaxException(errMsg) for dbmsName, data in DBMS_DICT.items(): - if conf.dbms in data[0]: + if dbmsName == conf.dbms or conf.dbms.lower() in data[0]: try: if dbmsName in (DBMS.ACCESS, DBMS.SQLITE, DBMS.FIREBIRD): if remote: @@ -1142,7 +1204,9 @@ def parseTargetDirect(): conf.hostname = "localhost" conf.port = 0 elif not remote: - errMsg = "missing remote connection details" + errMsg = "missing remote connection details (e.g. " + errMsg += "'mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME' " + errMsg += "or 'access://DATABASE_FILEPATH')" raise SqlmapSyntaxException(errMsg) if dbmsName in (DBMS.MSSQL, DBMS.SYBASE): @@ -1172,7 +1236,7 @@ def parseTargetDirect(): pass else: errMsg = "sqlmap requires '%s' third-party library " % data[1] - errMsg += "in order to directly connect to the database " + errMsg += "in order to directly connect to the DBMS " errMsg += "%s. You can download it from '%s'" % (dbmsName, data[2]) errMsg += ". Alternative is to use a package 'python-sqlalchemy' " errMsg += "with support for dialect '%s' installed" % data[3] @@ -1193,7 +1257,8 @@ def parseTargetUrl(): errMsg += "on this platform" raise SqlmapGenericException(errMsg) - if not re.search("^http[s]*://", conf.url, re.I): + if not re.search("^http[s]*://", conf.url, re.I) and \ + not re.search("^ws[s]*://", conf.url, re.I): if ":443/" in conf.url: conf.url = "https://" + conf.url else: @@ -1202,7 +1267,14 @@ def parseTargetUrl(): if CUSTOM_INJECTION_MARK_CHAR in conf.url: conf.url = conf.url.replace('?', URI_QUESTION_MARKER) - urlSplit = urlparse.urlsplit(conf.url) + try: + urlSplit = urlparse.urlsplit(conf.url) + except ValueError, ex: + errMsg = "invalid URL '%s' has been given ('%s'). " % (conf.url, ex) + errMsg += "Please be sure that you don't have any leftover characters (e.g. '[' or ']') " + errMsg += "in the hostname part" + raise SqlmapGenericException(errMsg) + hostnamePort = urlSplit.netloc.split(":") if not re.search("\[.+\]", urlSplit.netloc) else filter(None, (re.search("\[.+\]", urlSplit.netloc).group(0), re.search("\](:(?P\d+))?", urlSplit.netloc).group("port"))) conf.scheme = urlSplit.scheme.strip().lower() if not conf.forceSSL else "https" @@ -1214,6 +1286,8 @@ def parseTargetUrl(): try: _ = conf.hostname.encode("idna") + except LookupError: + _ = conf.hostname.encode(UNICODE_ENCODING) except UnicodeError: _ = None @@ -1238,13 +1312,13 @@ def parseTargetUrl(): conf.url = getUnicode("%s://%s:%d%s" % (conf.scheme, ("[%s]" % conf.hostname) if conf.ipv6 else conf.hostname, conf.port, conf.path)) conf.url = conf.url.replace(URI_QUESTION_MARKER, '?') - if not conf.referer and intersect(REFERER_ALIASES, conf.testParameter, True): + if not conf.referer and (intersect(REFERER_ALIASES, conf.testParameter, True) or conf.level >= 3): debugMsg = "setting the HTTP Referer header to the target URL" logger.debug(debugMsg) conf.httpHeaders = filter(lambda (key, value): key != HTTP_HEADER.REFERER, conf.httpHeaders) - conf.httpHeaders.append((HTTP_HEADER.REFERER, conf.url)) + conf.httpHeaders.append((HTTP_HEADER.REFERER, conf.url.replace(CUSTOM_INJECTION_MARK_CHAR, ""))) - if not conf.host and intersect(HOST_ALIASES, conf.testParameter, True): + if not conf.host and (intersect(HOST_ALIASES, conf.testParameter, True) or conf.level >= 5): debugMsg = "setting the HTTP Host header to the target URL" logger.debug(debugMsg) conf.httpHeaders = filter(lambda (key, value): key != HTTP_HEADER.HOST, conf.httpHeaders) @@ -1273,7 +1347,7 @@ def expandAsteriskForColumns(expression): if expression != conf.query: conf.db = db else: - expression = re.sub(r"([^\w])%s" % conf.tbl, "\g<1>%s.%s" % (conf.db, conf.tbl), expression) + expression = re.sub(r"([^\w])%s" % re.escape(conf.tbl), "\g<1>%s.%s" % (conf.db, conf.tbl), expression) else: conf.db = db conf.db = safeSQLIdentificatorNaming(conf.db) @@ -1501,39 +1575,55 @@ def normalizePath(filepath): return retVal +def safeExpandUser(filepath): + """ + Patch for a Python Issue18171 (http://bugs.python.org/issue18171) + """ + + retVal = filepath + + try: + retVal = os.path.expanduser(filepath) + except UnicodeDecodeError: + _ = locale.getdefaultlocale() + retVal = getUnicode(os.path.expanduser(filepath.encode(_[1] if _ and len(_) > 1 else UNICODE_ENCODING))) + + return retVal + def safeStringFormat(format_, params): """ Avoids problems with inappropriate string format strings - >>> safeStringFormat('foobar%d%s', ('1', 2)) - u'foobar12' + >>> safeStringFormat('SELECT foo FROM %s LIMIT %d', ('bar', '1')) + u'SELECT foo FROM bar LIMIT 1' """ if format_.count(PAYLOAD_DELIMITER) == 2: _ = format_.split(PAYLOAD_DELIMITER) - _[1] = _[1].replace("%d", "%s") + _[1] = re.sub(r"(\A|[^A-Za-z0-9])(%d)([^A-Za-z0-9]|\Z)", r"\g<1>%s\g<3>", _[1]) retVal = PAYLOAD_DELIMITER.join(_) else: - retVal = format_.replace("%d", "%s") + retVal = re.sub(r"(\A|[^A-Za-z0-9])(%d)([^A-Za-z0-9]|\Z)", r"\g<1>%s\g<3>", format_) if isinstance(params, basestring): retVal = retVal.replace("%s", params, 1) elif not isListLike(params): retVal = retVal.replace("%s", str(params), 1) else: - count, index = 0, 0 - if retVal.count("%s") == len(params): - while index != -1: - index = retVal.find("%s") - if index != -1: - retVal = retVal[:index] + getUnicode(params[count]) + retVal[index + 2:] - count += 1 + start, end = 0, len(retVal) + match = re.search(r"%s(.+)%s" % (PAYLOAD_DELIMITER, PAYLOAD_DELIMITER), retVal) + if match and PAYLOAD_DELIMITER not in match.group(1): + start, end = match.start(), match.end() + if retVal.count("%s", start, end) == len(params): + for param in params: + index = retVal.find("%s", start) + retVal = retVal[:index] + getUnicode(param) + retVal[index + 2:] else: count = 0 while True: match = re.search(r"(\A|[^A-Za-z0-9])(%s)([^A-Za-z0-9]|\Z)", retVal) if match: - if count > len(params): + if count >= len(params): raise Exception("wrong number of parameters during string formatting") else: retVal = re.sub(r"(\A|[^A-Za-z0-9])(%s)([^A-Za-z0-9]|\Z)", r"\g<1>%s\g<3>" % params[count], retVal, 1) @@ -1671,13 +1761,17 @@ def getConsoleWidth(default=80): width = int(os.getenv("COLUMNS")) else: try: - process = execute("stty size", shell=True, stdout=PIPE, stderr=PIPE) + try: + FNULL = open(os.devnull, 'w') + except IOError: + FNULL = None + process = execute("stty size", shell=True, stdout=PIPE, stderr=FNULL or PIPE) stdout, _ = process.communicate() items = stdout.split() if len(items) == 2 and items[1].isdigit(): width = int(items[1]) - except OSError: + except (OSError, MemoryError): pass if width is None: @@ -1708,8 +1802,14 @@ def parseXmlFile(xmlFile, handler): Parses XML file by a given handler """ - with contextlib.closing(StringIO(readCachedFileContent(xmlFile))) as stream: - parse(stream, handler) + try: + with contextlib.closing(StringIO(readCachedFileContent(xmlFile))) as stream: + parse(stream, handler) + except (SAXParseException, UnicodeError), ex: + errMsg = "something seems to be wrong with " + errMsg += "the file '%s' ('%s'). Please make " % (xmlFile, ex) + errMsg += "sure that you haven't made any changes to it" + raise SqlmapInstallationException, errMsg def getSQLSnippet(dbms, sfile, **variables): """ @@ -1749,7 +1849,7 @@ def getSQLSnippet(dbms, sfile, **variables): if choice and choice[0].lower() == "y": for var in variables: msg = "insert value for variable '%s': " % var - val = readInput(msg) + val = readInput(msg, default="") retVal = retVal.replace(r"%%%s%%" % var, val) return retVal @@ -1763,7 +1863,7 @@ def readCachedFileContent(filename, mode='rb'): with kb.locks.cache: if filename not in kb.cache.content: checkFile(filename) - with codecs.open(filename, mode, UNICODE_ENCODING) as f: + with openFile(filename, mode) as f: kb.cache.content[filename] = f.read() return kb.cache.content[filename] @@ -1828,7 +1928,7 @@ def initCommonOutputs(): kb.commonOutputs = {} key = None - with codecs.open(paths.COMMON_OUTPUTS, 'r', UNICODE_ENCODING) as f: + with openFile(paths.COMMON_OUTPUTS, 'r') as f: for line in f.readlines(): # xreadlines doesn't return unicode strings when codec.open() is used if line.find('#') != -1: line = line[:line.find('#')] @@ -1854,31 +1954,36 @@ def getFileItems(filename, commentPrefix='#', unicode_=True, lowercase=False, un checkFile(filename) - with codecs.open(filename, 'r', UNICODE_ENCODING, errors="ignore") if unicode_ else open(filename, 'r') as f: - for line in (f.readlines() if unicode_ else f.xreadlines()): # xreadlines doesn't return unicode strings when codec.open() is used - if commentPrefix: - if line.find(commentPrefix) != -1: - line = line[:line.find(commentPrefix)] + try: + with openFile(filename, 'r', errors="ignore") if unicode_ else open(filename, 'r') as f: + for line in (f.readlines() if unicode_ else f.xreadlines()): # xreadlines doesn't return unicode strings when codec.open() is used + if commentPrefix: + if line.find(commentPrefix) != -1: + line = line[:line.find(commentPrefix)] - line = line.strip() + line = line.strip() - if not unicode_: - try: - line = str.encode(line) - except UnicodeDecodeError: - continue + if not unicode_: + try: + line = str.encode(line) + except UnicodeDecodeError: + continue - if line: - if lowercase: - line = line.lower() + if line: + if lowercase: + line = line.lower() - if unique and line in retVal: - continue + if unique and line in retVal: + continue - if unique: - retVal[line] = True - else: - retVal.append(line) + if unique: + retVal[line] = True + else: + retVal.append(line) + except (IOError, OSError, MemoryError), ex: + errMsg = "something went wrong while trying " + errMsg += "to read the content of file '%s' ('%s')" % (filename, ex) + raise SqlmapSystemException(errMsg) return retVal if not unique else retVal.keys() @@ -1987,7 +2092,7 @@ def getPartRun(alias=True): else: return retVal -def getUnicode(value, encoding=None, system=False, noneToNull=False): +def getUnicode(value, encoding=None, noneToNull=False): """ Return the unicode representation of the supplied value: @@ -2003,28 +2108,25 @@ def getUnicode(value, encoding=None, system=False, noneToNull=False): return NULL if isListLike(value): - value = list(getUnicode(_, encoding, system, noneToNull) for _ in value) + value = list(getUnicode(_, encoding, noneToNull) for _ in value) return value - if not system: - if isinstance(value, unicode): - return value - elif isinstance(value, basestring): - while True: - try: - return unicode(value, encoding or kb.get("pageEncoding") or UNICODE_ENCODING) - except UnicodeDecodeError, ex: - value = value[:ex.start] + "".join(INVALID_UNICODE_CHAR_FORMAT % ord(_) for _ in value[ex.start:ex.end]) + value[ex.end:] - else: + if isinstance(value, unicode): + return value + elif isinstance(value, basestring): + while True: try: - return unicode(value) - except UnicodeDecodeError: - return unicode(str(value), errors="ignore") # encoding ignored for non-basestring instances + return unicode(value, encoding or kb.get("pageEncoding") or UNICODE_ENCODING) + except UnicodeDecodeError, ex: + try: + return unicode(value, UNICODE_ENCODING) + except: + value = value[:ex.start] + "".join(INVALID_UNICODE_CHAR_FORMAT % ord(_) for _ in value[ex.start:ex.end]) + value[ex.end:] else: try: - return getUnicode(value, sys.getfilesystemencoding() or sys.stdin.encoding) - except: - return getUnicode(value, UNICODE_ENCODING) + return unicode(value) + except UnicodeDecodeError: + return unicode(str(value), errors="ignore") # encoding ignored for non-basestring instances def longestCommonPrefix(*sequences): """ @@ -2179,7 +2281,7 @@ def findMultipartPostBoundary(post): candidates = [] for match in re.finditer(r"(?m)^--(.+?)(--)?$", post or ""): - _ = match.group(1) + _ = match.group(1).strip().strip('-') if _ in done: continue else: @@ -2380,7 +2482,11 @@ def extractTextTagContent(page): [u'Title', u'foobar'] """ - page = re.sub(r"(?si)[^\s>]*%s[^<]*" % REFLECTED_VALUE_MARKER, "", page or "") + page = page or "" + + if REFLECTED_VALUE_MARKER in page: + page = re.sub(r"(?si)[^\s>]*%s[^\s<]*" % REFLECTED_VALUE_MARKER, "", page) + return filter(None, (_.group('result').strip() for _ in re.finditer(TEXT_TAG_REGEX, page))) def trimAlphaNum(value): @@ -2440,6 +2546,9 @@ def findDynamicContent(firstPage, secondPage): are dynamic, proper markings will be made """ + if not firstPage or not secondPage: + return + infoMsg = "searching for dynamic content" logger.info(infoMsg) @@ -2490,11 +2599,11 @@ def removeDynamicContent(page): if prefix is None and suffix is None: continue elif prefix is None: - page = re.sub(r'(?s)^.+%s' % suffix, suffix, page) + page = re.sub(r'(?s)^.+%s' % re.escape(suffix), suffix, page) elif suffix is None: - page = re.sub(r'(?s)%s.+$' % prefix, prefix, page) + page = re.sub(r'(?s)%s.+$' % re.escape(prefix), prefix, page) else: - page = re.sub(r'(?s)%s.+%s' % (prefix, suffix), '%s%s' % (prefix, suffix), page) + page = re.sub(r'(?s)%s.+%s' % (re.escape(prefix), re.escape(suffix)), '%s%s' % (prefix, suffix), page) return page @@ -2568,7 +2677,7 @@ def parseSqliteTableSchema(value): table = {} columns = {} - for match in re.finditer(r"(\w+)\s+(TEXT|NUMERIC|INTEGER|REAL|NONE)\b", value, re.I): + for match in re.finditer(r"(\w+)\s+(INT|INTEGER|TINYINT|SMALLINT|MEDIUMINT|BIGINT|UNSIGNED BIG INT|INT2|INT8|INTEGER|CHARACTER|VARCHAR|VARYING CHARACTER|NCHAR|NATIVE CHARACTER|NVARCHAR|TEXT|CLOB|TEXT|BLOB|NONE|REAL|DOUBLE|DOUBLE PRECISION|FLOAT|REAL|NUMERIC|DECIMAL|BOOLEAN|DATE|DATETIME|NUMERIC)\b", value, re.I): columns[match.group(1)] = match.group(2) table[conf.tbl] = columns @@ -2733,14 +2842,14 @@ def getSortedInjectionTests(): retVal = SORT_ORDER.LAST elif 'details' in test and 'dbms' in test.details: - if test.details.dbms in Backend.getErrorParsedDBMSes(): + if intersect(test.details.dbms, Backend.getIdentifiedDbms()): retVal = SORT_ORDER.SECOND else: retVal = SORT_ORDER.THIRD return retVal - if Backend.getErrorParsedDBMSes(): + if Backend.getIdentifiedDbms(): retVal = sorted(retVal, key=priorityFunction) return retVal @@ -2772,20 +2881,24 @@ def showHttpErrorCodes(): if code in httplib.responses else '?', count) \ for code, count in kb.httpErrorCodes.items()) logger.warn(warnMsg) + if any((str(_).startswith('4') or str(_).startswith('5')) and _ != httplib.INTERNAL_SERVER_ERROR and _ != kb.originalCode for _ in kb.httpErrorCodes.keys()): + msg = "too many 4xx and/or 5xx HTTP error codes " + msg += "could mean that some kind of protection is involved (e.g. WAF)" + logger.debug(msg) -def openFile(filename, mode='r'): +def openFile(filename, mode='r', encoding=UNICODE_ENCODING, errors="replace", buffering=1): """ Returns file handle of a given filename """ try: - return codecs.open(filename, mode, UNICODE_ENCODING, "replace") + return codecs.open(filename, mode, encoding, errors, buffering) except IOError: errMsg = "there has been a file opening error for filename '%s'. " % filename errMsg += "Please check %s permissions on a file " % ("write" if \ mode and ('w' in mode or 'a' in mode or '+' in mode) else "read") errMsg += "and that it's not locked by another process." - raise SqlmapFilePathException(errMsg) + raise SqlmapSystemException(errMsg) def decodeIntToUnicode(value): """ @@ -2800,14 +2913,11 @@ def decodeIntToUnicode(value): if isinstance(value, int): try: - # http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_ord - if Backend.getIdentifiedDbms() in (DBMS.MYSQL,): + if value > 255: _ = "%x" % value if len(_) % 2 == 1: _ = "0%s" % _ - retVal = getUnicode(hexdecode(_)) - elif value > 255: - retVal = unichr(value) + retVal = getUnicode(hexdecode(_), encoding="UTF-16" if Backend.isDbms(DBMS.MSSQL) else None) else: retVal = getUnicode(chr(value)) except: @@ -2820,35 +2930,107 @@ def unhandledExceptionMessage(): Returns detailed message about occurred unhandled exception """ - errMsg = "unhandled exception in %s, retry your " % VERSION_STRING - errMsg += "run with the latest development version from the GitHub " - errMsg += "repository. If the exception persists, please send by e-mail " - errMsg += "to '%s' or open a new issue at '%s' with the following text " % (ML, ISSUES_PAGE) - errMsg += "and any information required to reproduce the bug. The " + errMsg = "unhandled exception occurred in %s. It is recommended to retry your " % VERSION_STRING + errMsg += "run with the latest development version from official GitHub " + errMsg += "repository at '%s'. If the exception persists, please open a new issue " % GIT_PAGE + errMsg += "at '%s' " % ISSUES_PAGE + errMsg += "with the following text and any other information required to " + errMsg += "reproduce the bug. The " errMsg += "developers will try to reproduce the bug, fix it accordingly " - errMsg += "and get back to you.\n" - errMsg += "sqlmap version: %s%s\n" % (VERSION, "-%s" % REVISION if REVISION else "") + errMsg += "and get back to you\n" + errMsg += "sqlmap version: %s\n" % VERSION_STRING[VERSION_STRING.find('/') + 1:] errMsg += "Python version: %s\n" % PYVERSION errMsg += "Operating system: %s\n" % PLATFORM - errMsg += "Command line: %s\n" % " ".join(sys.argv) + errMsg += "Command line: %s\n" % re.sub(r".+?\bsqlmap.py\b", "sqlmap.py", " ".join(sys.argv)) errMsg += "Technique: %s\n" % (enumValueToNameLookup(PAYLOAD.TECHNIQUE, kb.technique) if kb.get("technique") else ("DIRECT" if conf.get("direct") else None)) errMsg += "Back-end DBMS: %s" % ("%s (fingerprinted)" % Backend.getDbms() if Backend.getDbms() is not None else "%s (identified)" % Backend.getIdentifiedDbms()) - return maskSensitiveData(errMsg) + return errMsg + +def createGithubIssue(errMsg, excMsg): + """ + Automatically create a Github issue with unhandled exception information + """ + + issues = [] + try: + issues = getFileItems(paths.GITHUB_HISTORY, unique=True) + except: + pass + finally: + issues = set(issues) + + _ = re.sub(r"'[^']+'", "''", excMsg) + _ = re.sub(r"\s+line \d+", "", _) + _ = re.sub(r'File ".+?/(\w+\.py)', "\g<1>", _) + _ = re.sub(r".+\Z", "", _) + key = hashlib.md5(_).hexdigest()[:8] + + if key in issues: + return + + msg = "\ndo you want to automatically create a new (anonymized) issue " + msg += "with the unhandled exception information at " + msg += "the official Github repository? [y/N] " + try: + test = readInput(msg, default="N") + except: + test = None + + if test and test[0] in ("y", "Y"): + ex = None + errMsg = errMsg[errMsg.find("\n"):] + + + data = {"title": "Unhandled exception (#%s)" % key, "body": "```%s\n```\n```\n%s```" % (errMsg, excMsg)} + req = urllib2.Request(url="https://api.github.com/repos/sqlmapproject/sqlmap/issues", data=json.dumps(data), headers={"Authorization": "token %s" % GITHUB_REPORT_OAUTH_TOKEN.decode("base64")}) + + try: + f = urllib2.urlopen(req) + content = f.read() + except Exception, ex: + content = None + + issueUrl = re.search(r"https://github.com/sqlmapproject/sqlmap/issues/\d+", content or "") + if issueUrl: + infoMsg = "created Github issue can been found at the address '%s'" % issueUrl.group(0) + logger.info(infoMsg) + + try: + with open(paths.GITHUB_HISTORY, "a+b") as f: + f.write("%s\n" % key) + except: + pass + else: + warnMsg = "something went wrong while creating a Github issue" + if ex: + warnMsg += " ('%s')" % ex + if "Unauthorized" in warnMsg: + warnMsg += ". Please update to the latest revision" + logger.warn(warnMsg) def maskSensitiveData(msg): """ Masks sensitive data in the supplied message """ - retVal = msg + retVal = getUnicode(msg) - for item in filter(None, map(lambda x: conf.get(x), ("hostname", "googleDork", "authCred", "proxyCred", "tbl", "db", "col", "user", "cookie", "proxy"))): - regex = SENSITIVE_DATA_REGEX % re.sub("(\W)", r"\\\1", item) + for item in filter(None, map(lambda x: conf.get(x), ("hostname", "googleDork", "authCred", "proxyCred", "tbl", "db", "col", "user", "cookie", "proxy", "rFile", "wFile", "dFile"))): + regex = SENSITIVE_DATA_REGEX % re.sub("(\W)", r"\\\1", getUnicode(item)) while extractRegexResult(regex, retVal): value = extractRegexResult(regex, retVal) retVal = retVal.replace(value, '*' * len(value)) + if not conf.get("hostname"): + match = re.search(r"(?i)sqlmap.+(-u|--url)(\s+|=)([^ ]+)", retVal) + if match: + retVal = retVal.replace(match.group(3), '*' * len(match.group(3))) + + + if getpass.getuser(): + retVal = re.sub(r"(?i)\b%s\b" % re.escape(getpass.getuser()), "*" * len(getpass.getuser()), retVal) + return retVal def listToStrValue(value): @@ -2923,7 +3105,7 @@ def removeReflectiveValues(content, payload, suppressWarning=False): retVal = content - if all([content, payload]) and isinstance(content, unicode) and kb.reflectiveMechanism: + if all([content, payload]) and isinstance(content, unicode) and kb.reflectiveMechanism and not kb.heuristicMode: def _(value): while 2 * REFLECTED_REPLACEMENT_REGEX in value: value = value.replace(2 * REFLECTED_REPLACEMENT_REGEX, REFLECTED_REPLACEMENT_REGEX) @@ -2958,7 +3140,7 @@ def removeReflectiveValues(content, payload, suppressWarning=False): regex = REFLECTED_REPLACEMENT_REGEX.join(parts[1:]) retVal = re.sub(r"(?i)\b%s\b" % regex, REFLECTED_VALUE_MARKER, retVal) - if retVal != content and not kb.heuristicMode: + if retVal != content: kb.reflectiveCounters[REFLECTIVE_COUNTER.HIT] += 1 if not suppressWarning: warnMsg = "reflective value(s) found and filtering out" @@ -3108,7 +3290,7 @@ def expandMnemonics(mnemonics, parser, args): pointer = pointer.next[char] pointer.current.append(option) - for mnemonic in mnemonics.split(','): + for mnemonic in (mnemonics or "").split(','): found = None name = mnemonic.split('=')[0].replace("-", "").strip() value = mnemonic.split('=')[1] if len(mnemonic.split('=')) > 1 else None @@ -3134,7 +3316,10 @@ def expandMnemonics(mnemonics, parser, args): if opt.startswith(name): options[opt] = option - if name in options: + if not options: + warnMsg = "mnemonic '%s' can't be resolved" % name + logger.warn(warnMsg) + elif name in options: found = name debugMsg = "mnemonic '%s' resolved to %s). " % (name, found) logger.debug(debugMsg) @@ -3144,7 +3329,8 @@ def expandMnemonics(mnemonics, parser, args): warnMsg += "Resolved to shortest of those ('%s')" % found logger.warn(warnMsg) - found = options[found] + if found: + found = options[found] else: found = pointer.current[0] debugMsg = "mnemonic '%s' resolved to %s). " % (name, found) @@ -3212,6 +3398,8 @@ def randomizeParameterValue(value): retVal = value + value = re.sub(r"%[0-9a-fA-F]{2}", "", value) + for match in re.finditer('[A-Z]+', value): retVal = retVal.replace(match.group(), randomStr(len(match.group())).upper()) @@ -3251,7 +3439,10 @@ def asciifyUrl(url, forceQuote=False): return url # idna-encode domain - hostname = parts.hostname.encode("idna") + try: + hostname = parts.hostname.encode("idna") + except LookupError: + hostname = parts.hostname.encode(UNICODE_ENCODING) # UTF8-quote the other parts. We check each part individually if # if needs to be quoted - that should catch some additional user @@ -3340,18 +3531,18 @@ def findPageForms(content, url, raise_=False, addToTargets=False): except UnicodeError: pass except ParseError: - warnMsg = "badly formed HTML at the given URL ('%s'). Going to filter it" % url - logger.warning(warnMsg) - response.seek(0) - filtered = _("".join(re.findall(FORM_SEARCH_REGEX, response.read())), response.geturl()) - try: - forms = ParseResponse(filtered, backwards_compat=False) - except ParseError: - errMsg = "no success" - if raise_: - raise SqlmapGenericException(errMsg) - else: - logger.debug(errMsg) + if " 3: + warnMsg = "using too many tamper scripts is usually not " + warnMsg += "a good idea" + logger.warning(warnMsg) + if resolve_priorities and priorities: priorities.sort(reverse=True) kb.tamperFunctions = [] @@ -1009,13 +1036,15 @@ def _setWafFunctions(): sys.path.insert(0, dirname) try: + if filename[:-3] in sys.modules: + del sys.modules[filename[:-3]] module = __import__(filename[:-3]) except ImportError, msg: raise SqlmapSyntaxException("cannot import WAF script '%s' (%s)" % (filename[:-3], msg)) _ = dict(inspect.getmembers(module)) if "detect" not in _: - errMsg = "missing function 'detect(page, headers, code)' " + errMsg = "missing function 'detect(get_page)' " errMsg += "in WAF script '%s'" % found raise SqlmapGenericException(errMsg) else: @@ -1068,7 +1097,12 @@ def _setHTTPProxy(): debugMsg = "setting the HTTP/SOCKS proxy for all HTTP requests" logger.debug(debugMsg) - _ = urlparse.urlsplit(conf.proxy) + try: + _ = urlparse.urlsplit(conf.proxy) + except Exception, ex: + errMsg = "invalid proxy address '%s' ('%s')" % (conf.proxy, ex) + raise SqlmapSyntaxException, errMsg + hostnamePort = _.netloc.split(":") scheme = _.scheme.upper() @@ -1116,21 +1150,63 @@ def _setHTTPProxy(): proxyHandler.__init__(proxyHandler.proxies) -def _setSafeUrl(): +def _setSafeVisit(): """ - Check and set the safe URL options. + Check and set the safe visit options. """ - if not conf.safUrl: + if not any ((conf.safeUrl, conf.safeReqFile)): return - if not re.search("^http[s]*://", conf.safUrl): - if ":443/" in conf.safUrl: - conf.safUrl = "https://" + conf.safUrl - else: - conf.safUrl = "http://" + conf.safUrl + if conf.safeReqFile: + checkFile(conf.safeReqFile) - if conf.saFreq <= 0: - errMsg = "please provide a valid value (>0) for safe frequency (--safe-freq) while using safe URL feature" + raw = readCachedFileContent(conf.safeReqFile) + match = re.search(r"\A([A-Z]+) ([^ ]+) HTTP/[0-9.]+\Z", raw[:raw.find('\n')]) + + if match: + kb.safeReq.method = match.group(1) + kb.safeReq.url = match.group(2) + kb.safeReq.headers = {} + + for line in raw[raw.find('\n') + 1:].split('\n'): + line = line.strip() + if line and ':' in line: + key, value = line.split(':', 1) + value = value.strip() + kb.safeReq.headers[key] = value + if key == HTTP_HEADER.HOST: + if not value.startswith("http"): + scheme = "http" + if value.endswith(":443"): + scheme = "https" + value = "%s://%s" % (scheme, value) + kb.safeReq.url = urlparse.urljoin(value, kb.safeReq.url) + else: + break + + post = None + + if '\r\n\r\n' in raw: + post = raw[raw.find('\r\n\r\n') + 4:] + elif '\n\n' in raw: + post = raw[raw.find('\n\n') + 2:] + + if post and post.strip(): + kb.safeReq.post = post + else: + kb.safeReq.post = None + else: + errMsg = "invalid format of a safe request file" + raise SqlmapSyntaxException, errMsg + else: + if not re.search("^http[s]*://", conf.safeUrl): + if ":443/" in conf.safeUrl: + conf.safeUrl = "https://" + conf.safeUrl + else: + conf.safeUrl = "http://" + conf.safeUrl + + if conf.safeFreq <= 0: + errMsg = "please provide a valid value (>0) for safe frequency (--safe-freq) while using safe visit features" raise SqlmapSyntaxException(errMsg) def _setPrefixSuffix(): @@ -1182,6 +1258,9 @@ def _setHTTPAuthentication(): if not conf.authType and not conf.authCred and not conf.authPrivate: return + if conf.authPrivate and not conf.authType: + conf.authType = AUTH_TYPE.PKI + elif conf.authType and not conf.authCred and not conf.authPrivate: errMsg = "you specified the HTTP authentication type, but " errMsg += "did not provide the credentials" @@ -1192,7 +1271,7 @@ def _setHTTPAuthentication(): errMsg += "but did not provide the type" raise SqlmapSyntaxException(errMsg) - elif conf.authType.lower() not in (AUTH_TYPE.BASIC, AUTH_TYPE.DIGEST, AUTH_TYPE.NTLM, AUTH_TYPE.PKI): + elif (conf.authType or "").lower() not in (AUTH_TYPE.BASIC, AUTH_TYPE.DIGEST, AUTH_TYPE.NTLM, AUTH_TYPE.PKI): errMsg = "HTTP authentication type value must be " errMsg += "Basic, Digest, NTLM or PKI" raise SqlmapSyntaxException(errMsg) @@ -1248,19 +1327,9 @@ def _setHTTPAuthentication(): debugMsg = "setting the HTTP(s) authentication PEM private key" logger.debug(debugMsg) - key_file = os.path.expanduser(conf.authPrivate) - checkFile(key_file) - authHandler = HTTPSPKIAuthHandler(key_file) - -def _setHTTPMethod(): - """ - Check and set the HTTP method to perform HTTP requests through. - """ - - conf.method = HTTPMETHOD.POST if conf.data is not None else HTTPMETHOD.GET - - debugMsg = "setting the HTTP method to %s" % conf.method - logger.debug(debugMsg) + _ = safeExpandUser(conf.authPrivate) + checkFile(_) + authHandler = HTTPSPKIAuthHandler(_) def _setHTTPExtraHeaders(): if conf.headers: @@ -1270,6 +1339,9 @@ def _setHTTPExtraHeaders(): conf.headers = conf.headers.split("\n") if "\n" in conf.headers else conf.headers.split("\\n") for headerValue in conf.headers: + if not headerValue.strip(): + continue + if headerValue.count(':') >= 1: header, value = (_.lstrip() for _ in headerValue.split(":", 1)) @@ -1387,6 +1459,17 @@ def _setHTTPReferer(): conf.httpHeaders.append((HTTP_HEADER.REFERER, conf.referer)) +def _setHTTPHost(): + """ + Set the HTTP Host + """ + + if conf.host: + debugMsg = "setting the HTTP Host header" + logger.debug(debugMsg) + + conf.httpHeaders.append((HTTP_HEADER.HOST, conf.host)) + def _setHTTPCookies(): """ Set the HTTP Cookie header @@ -1428,6 +1511,30 @@ def _checkDependencies(): if conf.dependencies: checkDependencies() +def _createTemporaryDirectory(): + """ + Creates temporary directory for this run. + """ + + try: + if not os.path.isdir(tempfile.gettempdir()): + os.makedirs(tempfile.gettempdir()) + except IOError, ex: + errMsg = "there has been a problem while accessing " + errMsg += "system's temporary directory location(s) ('%s'). Please " % ex.message + errMsg += "make sure that there is enough disk space left. If problem persists, " + errMsg += "try to set environment variable 'TEMP' to a location " + errMsg += "writeable by the current user" + raise SqlmapSystemException, errMsg + + if "sqlmap" not in (tempfile.tempdir or ""): + tempfile.tempdir = tempfile.mkdtemp(prefix="sqlmap", suffix=str(os.getpid())) + + kb.tempDir = tempfile.tempdir + + if not os.path.isdir(tempfile.tempdir): + os.makedirs(tempfile.tempdir) + def _cleanupOptions(): """ Cleanup configuration attributes. @@ -1445,7 +1552,7 @@ def _cleanupOptions(): for key, value in conf.items(): if value and any(key.endswith(_) for _ in ("Path", "File")): - conf[key] = os.path.expanduser(value) + conf[key] = safeExpandUser(value) if conf.testParameter: conf.testParameter = urldecode(conf.testParameter) @@ -1512,8 +1619,8 @@ def _cleanupOptions(): conf.dbms = conf.dbms.capitalize() if conf.testFilter: - if not any([char in conf.testFilter for char in ('.', ')', '(', ']', '[')]): - conf.testFilter = conf.testFilter.replace('*', '.*') + conf.testFilter = conf.testFilter.strip('*+') + conf.testFilter = re.sub(r"([^.])([*+])", "\g<1>.\g<2>", conf.testFilter) if "timeSec" not in kb.explicitSettings: if conf.tor: @@ -1579,6 +1686,13 @@ def _cleanupOptions(): threadData = getCurrentThreadData() threadData.reset() +def _dirtyPatches(): + """ + Place for "dirty" Python related patches + """ + + httplib._MAXLINE = 1 * 1024 * 1024 # to accept overly long result lines (e.g. SQLi results in HTTP header responses) + def _purgeOutput(): """ Safely removes (purges) output directory. @@ -1651,8 +1765,8 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.chars = AttribDict() kb.chars.delimiter = randomStr(length=6, lowercase=True) - kb.chars.start = "%s%s%s" % (KB_CHARS_BOUNDARY_CHAR, randomStr(length=3, lowercase=True), KB_CHARS_BOUNDARY_CHAR) - kb.chars.stop = "%s%s%s" % (KB_CHARS_BOUNDARY_CHAR, randomStr(length=3, lowercase=True), KB_CHARS_BOUNDARY_CHAR) + kb.chars.start = "%s%s%s" % (KB_CHARS_BOUNDARY_CHAR, randomStr(length=3, alphabet=KB_CHARS_LOW_FREQUENCY_ALPHABET), KB_CHARS_BOUNDARY_CHAR) + kb.chars.stop = "%s%s%s" % (KB_CHARS_BOUNDARY_CHAR, randomStr(length=3, alphabet=KB_CHARS_LOW_FREQUENCY_ALPHABET), KB_CHARS_BOUNDARY_CHAR) kb.chars.at, kb.chars.space, kb.chars.dollar, kb.chars.hash_ = ("%s%s%s" % (KB_CHARS_BOUNDARY_CHAR, _, KB_CHARS_BOUNDARY_CHAR) for _ in randomStr(length=4, lowercase=True)) kb.columnExistsChoice = None @@ -1681,6 +1795,8 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.followSitemapRecursion = None kb.forcedDbms = None kb.forcePartialUnion = False + kb.forceWhere = None + kb.futileUnion = None kb.headersFp = {} kb.heuristicDbms = None kb.heuristicMode = False @@ -1707,9 +1823,11 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.multiThreadMode = False kb.negativeLogic = False kb.nullConnection = None + kb.oldMsf = None kb.orderByColumns = None kb.originalCode = None kb.originalPage = None + kb.originalPageTime = None kb.originalTimeDelay = None kb.originalUrls = dict() @@ -1743,15 +1861,21 @@ def _setKnowledgeBaseAttributes(flushAll=True): kb.responseTimes = [] kb.resumeValues = True kb.safeCharEncode = False + kb.safeReq = AttribDict() kb.singleLogFlags = set() kb.reduceTests = None + kb.tlsSNI = None kb.stickyDBMS = False kb.stickyLevel = None + kb.storeCrawlingChoice = None kb.storeHashesChoice = None kb.suppressResumeInfo = False kb.technique = None + kb.tempDir = None kb.testMode = False + kb.testOnlyCustom = False kb.testQueryCount = 0 + kb.testType = None kb.threadContinue = True kb.threadException = False kb.tableExistsChoice = None @@ -1788,11 +1912,11 @@ def _useWizardInterface(): message = "Please enter full target URL (-u): " conf.url = readInput(message, default=None) - message = "POST data (--data) [Enter for None]: " + message = "%s data (--data) [Enter for None]: " % ((conf.method if conf.method != HTTPMETHOD.GET else conf.method) or HTTPMETHOD.POST) conf.data = readInput(message, default=None) if not (filter(lambda _: '=' in unicode(_), (conf.url, conf.data)) or '*' in conf.url): - warnMsg = "no GET and/or POST parameter(s) found for testing " + warnMsg = "no GET and/or %s parameter(s) found for testing " % ((conf.method if conf.method != HTTPMETHOD.GET else conf.method) or HTTPMETHOD.POST) warnMsg += "(e.g. GET parameter 'id' in 'http://www.site.com/vuln.php?id=1'). " if not conf.crawlDepth and not conf.forms: warnMsg += "Will search for forms" @@ -1888,7 +2012,13 @@ def _saveCmdline(): config.set(family, option, value) confFP = openFile(paths.SQLMAP_CONFIG, "wb") - config.write(confFP) + + try: + config.write(confFP) + except IOError, ex: + errMsg = "something went wrong while trying " + errMsg += "to write to the configuration INI file '%s' ('%s')" % (paths.SQLMAP_CONFIG, ex) + raise SqlmapSystemException(errMsg) infoMsg = "saved command line options on '%s' configuration file" % paths.SQLMAP_CONFIG logger.info(infoMsg) @@ -2080,6 +2210,18 @@ def _setTorSocksProxySettings(): socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5 if conf.torType == PROXY_TYPE.SOCKS5 else socks.PROXY_TYPE_SOCKS4, LOCALHOST, conf.torPort or DEFAULT_TOR_SOCKS_PORT) socks.wrapmodule(urllib2) +def _checkWebSocket(): + infoMsg = "checking for WebSocket" + logger.debug(infoMsg) + + if conf.url and (conf.url.startswith("ws:/") or conf.url.startswith("wss:/")): + try: + from websocket import ABNF + except ImportError: + errMsg = "sqlmap requires third-party module 'websocket-client' " + errMsg += "in order to use WebSocket funcionality" + raise SqlmapMissingDependence(errMsg) + def _checkTor(): if not conf.checkTor: return @@ -2170,6 +2312,20 @@ def _basicOptionValidation(): errMsg = "option '--regexp' is incompatible with switch '--null-connection'" raise SqlmapSyntaxException(errMsg) + if conf.regexp: + try: + re.compile(conf.regexp) + except re.error, ex: + errMsg = "invalid regular expression '%s' ('%s')" % (conf.regexp, ex) + raise SqlmapSyntaxException(errMsg) + + if conf.crawlExclude: + try: + re.compile(conf.crawlExclude) + except re.error, ex: + errMsg = "invalid regular expression '%s' ('%s')" % (conf.crawlExclude, ex) + raise SqlmapSyntaxException(errMsg) + if conf.dumpTable and conf.dumpAll: errMsg = "switch '--dump' is incompatible with switch '--dump-all'" raise SqlmapSyntaxException(errMsg) @@ -2178,7 +2334,7 @@ def _basicOptionValidation(): errMsg = "switch '--predict-output' is incompatible with option '--threads' and switch '-o'" raise SqlmapSyntaxException(errMsg) - if conf.threads > MAX_NUMBER_OF_THREADS: + if conf.threads > MAX_NUMBER_OF_THREADS and not conf.get("skipThreadCheck"): errMsg = "maximum number of used threads is %d avoiding potential connection issues" % MAX_NUMBER_OF_THREADS raise SqlmapSyntaxException(errMsg) @@ -2186,6 +2342,30 @@ def _basicOptionValidation(): errMsg = "switch '--forms' requires usage of option '-u' ('--url'), '-g', '-m' or '-x'" raise SqlmapSyntaxException(errMsg) + if conf.crawlExclude and not conf.crawlDepth: + errMsg = "option '--crawl-exclude' requires usage of switch '--crawl'" + raise SqlmapSyntaxException(errMsg) + + if conf.safePost and not conf.safeUrl: + errMsg = "option '--safe-post' requires usage of option '--safe-url'" + raise SqlmapSyntaxException(errMsg) + + if conf.safeFreq and not any((conf.safeUrl, conf.safeReqFile)): + errMsg = "option '--safe-freq' requires usage of option '--safe-url' or '--safe-req'" + raise SqlmapSyntaxException(errMsg) + + if conf.safeReqFile and any((conf.safeUrl, conf.safePost)): + errMsg = "option '--safe-req' is incompatible with option '--safe-url' and option '--safe-post'" + raise SqlmapSyntaxException(errMsg) + + if conf.csrfUrl and not conf.csrfToken: + errMsg = "option '--csrf-url' requires usage of option '--csrf-token'" + raise SqlmapSyntaxException(errMsg) + + if conf.csrfToken and conf.threads > 1: + errMsg = "option '--csrf-url' is incompatible with option '--threads'" + raise SqlmapSyntaxException(errMsg) + if conf.requestFile and conf.url and conf.url != DUMMY_URL: errMsg = "option '-r' is incompatible with option '-u' ('--url')" raise SqlmapSyntaxException(errMsg) @@ -2214,8 +2394,8 @@ def _basicOptionValidation(): errMsg = "switch '--check-tor' requires usage of switch '--tor' (or option '--proxy' with HTTP proxy address using Tor)" raise SqlmapSyntaxException(errMsg) - if conf.torPort is not None and not (isinstance(conf.torPort, int) and conf.torPort > 0): - errMsg = "value for option '--tor-port' must be a positive integer" + if conf.torPort is not None and not (isinstance(conf.torPort, int) and conf.torPort >= 0 and conf.torPort <= 65535): + errMsg = "value for option '--tor-port' must be in range 0-65535" raise SqlmapSyntaxException(errMsg) if conf.torType not in getPublicTypeMembers(PROXY_TYPE, True): @@ -2280,7 +2460,7 @@ def _resolveCrossReferences(): lib.controller.checks.setVerbosity = setVerbosity def initOptions(inputOptions=AttribDict(), overrideOptions=False): - if not inputOptions.disableColoring: + if IS_WIN: coloramainit() _setConfAttributes() @@ -2298,8 +2478,10 @@ def init(): _saveCmdline() _setRequestFromFile() _cleanupOptions() + _dirtyPatches() _purgeOutput() _checkDependencies() + _createTemporaryDirectory() _basicOptionValidation() _setProxyList() _setTorProxySettings() @@ -2310,6 +2492,7 @@ def init(): _setWafFunctions() _setTrafficOutputFP() _resolveCrossReferences() + _checkWebSocket() parseTargetUrl() parseTargetDirect() @@ -2319,12 +2502,12 @@ def init(): _setHTTPExtraHeaders() _setHTTPCookies() _setHTTPReferer() + _setHTTPHost() _setHTTPUserAgent() - _setHTTPMethod() _setHTTPAuthentication() _setHTTPProxy() _setDNSCache() - _setSafeUrl() + _setSafeVisit() _setGoogleDorking() _setBulkMultipleTargets() _setSitemapTargets() @@ -2340,6 +2523,7 @@ def init(): _setWriteFile() _setMetasploit() _setDBMSAuthentication() + loadBoundaries() loadPayloads() _setPrefixSuffix() update() diff --git a/lib/core/optiondict.py b/lib/core/optiondict.py index cfbd02b67..b9adbd67b 100644 --- a/lib/core/optiondict.py +++ b/lib/core/optiondict.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ @@ -23,6 +23,7 @@ optDict = { }, "Request": { + "method": "string", "data": "string", "paramDel": "string", "cookie": "string", @@ -49,9 +50,13 @@ optDict = { "timeout": "float", "retries": "integer", "rParam": "string", - "safUrl": "string", - "saFreq": "integer", + "safeUrl": "string", + "safePost": "string", + "safeReqFile": "string", + "safeFreq": "integer", "skipUrlEncode": "boolean", + "csrfToken": "string", + "csrfUrl": "string", "forceSSL": "boolean", "hpp": "boolean", "evalCode": "string", @@ -68,6 +73,7 @@ optDict = { "Injection": { "testParameter": "string", "skip": "string", + "skipStatic": "boolean", "dbms": "string", "dbmsCred": "string", "os": "string", @@ -185,6 +191,7 @@ optDict = { "batch": "boolean", "charset": "string", "crawlDepth": "integer", + "crawlExclude": "string", "csvDel": "string", "dumpFormat": "string", "eta": "boolean", @@ -202,16 +209,15 @@ optDict = { }, "Miscellaneous": { - "mnemonics": "string", "alert": "string", "answers": "string", "beep": "boolean", - "checkWaf": "boolean", "cleanup": "boolean", "dependencies": "boolean", "disableColoring": "boolean", "googlePage": "integer", "mobile": "boolean", + "offline": "boolean", "pageRank": "boolean", "purgeOutput": "boolean", "smart": "boolean", diff --git a/lib/core/profiling.py b/lib/core/profiling.py index e316c31ee..c212a0bb5 100644 --- a/lib/core/profiling.py +++ b/lib/core/profiling.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/lib/core/readlineng.py b/lib/core/readlineng.py index 1ad455b49..2dc0467c4 100644 --- a/lib/core/readlineng.py +++ b/lib/core/readlineng.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/lib/core/replication.py b/lib/core/replication.py index 72787eb16..c5bbd24cc 100644 --- a/lib/core/replication.py +++ b/lib/core/replication.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ @@ -70,7 +70,7 @@ class Replication(object): try: self.parent.cursor.execute(sql, parameters) except sqlite3.OperationalError, ex: - errMsg = "problem occurred ('%s') while accessing sqlite database " % ex + errMsg = "problem occurred ('%s') while accessing sqlite database " % unicode(ex) errMsg += "located at '%s'. Please make sure that " % self.parent.dbpath errMsg += "it's not used by some other program" raise SqlmapGenericException(errMsg) diff --git a/lib/core/revision.py b/lib/core/revision.py index 5caf32ef8..5319f1aa3 100644 --- a/lib/core/revision.py +++ b/lib/core/revision.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/lib/core/session.py b/lib/core/session.py index ccb529987..68b4e13a4 100644 --- a/lib/core/session.py +++ b/lib/core/session.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ diff --git a/lib/core/settings.py b/lib/core/settings.py index 4f1e23616..325855514 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -1,12 +1,11 @@ #!/usr/bin/env python """ -Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/) +Copyright (c) 2006-2015 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ import os -import random import re import subprocess import string @@ -26,20 +25,23 @@ DESCRIPTION = "automatic SQL injection and database takeover tool" SITE = "http://sqlmap.org" ISSUES_PAGE = "https://github.com/sqlmapproject/sqlmap/issues/new" GIT_REPOSITORY = "git://github.com/sqlmapproject/sqlmap.git" -ML = "sqlmap-users@lists.sourceforge.net" +GIT_PAGE = "https://github.com/sqlmapproject/sqlmap" # colorful banner BANNER = """\033[01;33m _ ___ ___| |_____ ___ ___ \033[01;37m{\033[01;%dm%s\033[01;37m}\033[01;33m |_ -| . | | | .'| . | |___|_ |_|_|_|_|__,| _| - |_| |_| \033[0m\033[4m%s\033[0m\n + |_| |_| \033[0m\033[4;37m%s\033[0m\n """ % ((31 + hash(REVISION) % 6) if REVISION else 30, VERSION_STRING.split('/')[-1], SITE) # Minimum distance of ratio from kb.matchRatio to result in True DIFF_TOLERANCE = 0.05 CONSTANT_RATIO = 0.9 +# Ratio used in heuristic check for WAF/IDS/IPS protected targets +IDS_WAF_CHECK_RATIO = 0.5 + # Lower and upper values for match ratio in case of stable page LOWER_RATIO_BOUND = 0.02 UPPER_RATIO_BOUND = 0.98 @@ -47,6 +49,7 @@ UPPER_RATIO_BOUND = 0.98 # Markers for special cases when parameter values contain html encoded characters PARAMETER_AMP_MARKER = "__AMP__" PARAMETER_SEMICOLON_MARKER = "__SEMICOLON__" +BOUNDARY_BACKSLASH_MARKER = "__BACKSLASH__" PARTIAL_VALUE_MARKER = "__PARTIAL_VALUE__" PARTIAL_HEX_VALUE_MARKER = "__PARTIAL_HEX_VALUE__" URI_QUESTION_MARKER = "__QUESTION_MARK__" @@ -78,6 +81,9 @@ TEXT_TAG_REGEX = r"(?si)<(abbr|acronym|b|blockquote|br|center|cite|code|dt|em|fo # Regular expression used for recognition of IP addresses IP_ADDRESS_REGEX = r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b" +# Regular expression used for recognition of generic "your ip has been blocked" messages +BLOCKED_IP_REGEX = r"(?i)(\A|\b)ip\b.*\b(banned|blocked|block list|firewall)" + # Dumping characters used in GROUP_CONCAT MySQL technique CONCAT_ROW_DELIMITER = ',' CONCAT_VALUE_DELIMITER = '|' @@ -139,10 +145,10 @@ INFERENCE_EQUALS_CHAR = "=" # Character used for operation "not-equals" in inference INFERENCE_NOT_EQUALS_CHAR = "!=" -# String used for representation of unknown dbms +# String used for representation of unknown DBMS UNKNOWN_DBMS = "Unknown" -# String used for representation of unknown dbms version +# String used for representation of unknown DBMS version UNKNOWN_DBMS_VERSION = "Unknown" # Dynamicity mark length used in dynamicity removal engine @@ -201,10 +207,15 @@ DBMS_DIRECTORY_DICT = dict((getattr(DBMS, _), getattr(DBMS_DIRECTORY_NAME, _)) f SUPPORTED_DBMS = MSSQL_ALIASES + MYSQL_ALIASES + PGSQL_ALIASES + ORACLE_ALIASES + SQLITE_ALIASES + ACCESS_ALIASES + FIREBIRD_ALIASES + MAXDB_ALIASES + SYBASE_ALIASES + DB2_ALIASES + HSQLDB_ALIASES SUPPORTED_OS = ("linux", "windows") +DBMS_ALIASES = ((DBMS.MSSQL, MSSQL_ALIASES), (DBMS.MYSQL, MYSQL_ALIASES), (DBMS.PGSQL, PGSQL_ALIASES), (DBMS.ORACLE, ORACLE_ALIASES), (DBMS.SQLITE, SQLITE_ALIASES), (DBMS.ACCESS, ACCESS_ALIASES), (DBMS.FIREBIRD, FIREBIRD_ALIASES), (DBMS.MAXDB, MAXDB_ALIASES), (DBMS.SYBASE, SYBASE_ALIASES), (DBMS.DB2, DB2_ALIASES), (DBMS.HSQLDB, HSQLDB_ALIASES)) + USER_AGENT_ALIASES = ("ua", "useragent", "user-agent") REFERER_ALIASES = ("ref", "referer", "referrer") HOST_ALIASES = ("host",) +# Names that can't be used to name files on Windows OS +WINDOWS_RESERVED_NAMES = ("CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9") + # Items displayed in basic help (-h) output BASIC_HELP_ITEMS = ( "url", @@ -237,6 +248,7 @@ BASIC_HELP_ITEMS = ( "checkTor", "flushSession", "tor", + "sqlmapShell", "wizard", ) @@ -261,7 +273,7 @@ ERROR_PARSING_REGEXES = ( META_CHARSET_REGEX = r'(?si).*]+charset="?(?P[^"> ]+).*' # Regular expression used for parsing refresh info from meta html headers -META_REFRESH_REGEX = r'(?si).*]+content="?[^">]+url=["\']?(?P[^\'">]+).*' +META_REFRESH_REGEX = r'(?si)(?!.*?]+content="?[^">]+url=["\']?(?P[^\'">]+).*' # Regular expression used for parsing empty fields in tested form data EMPTY_FORM_FIELDS_REGEX = r'(&|\A)(?P[^=]+=(&|\Z))' @@ -405,7 +417,7 @@ ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" DUMMY_SQL_INJECTION_CHARS = ";()'" # Simple check against dummy users -DUMMY_USER_INJECTION = r"(?i)[^\w](AND|OR)\s+[^\s]+[=><]|\bUNION\b.+\bSELECT\b|\A-\d+\Z" +DUMMY_USER_INJECTION = r"(?i)[^\w](AND|OR)\s+[^\s]+[=><]|\bUNION\b.+\bSELECT\b|\bSELECT\b.+\bFROM\b|\b(CONCAT|information_schema|SLEEP|DELAY)\b" # Extensions skipped by crawler CRAWL_EXCLUDE_EXTENSIONS = ("gif", "jpg", "jpeg", "image", "jar", "tif", "bmp", "war", "ear", "mpg", "mpeg", "wmv", "mpeg", "scm", "iso", "dmp", "dll", "cab", "so", "avi", "mkv", "bin", "iso", "tar", "png", "pdf", "ps", "wav", "mp3", "mp4", "au", "aiff", "aac", "zip", "rar", "7z", "gz", "flv", "mov", "doc", "docx", "xls", "dot", "dotx", "xlt", "xlsx", "ppt", "pps", "pptx") @@ -420,7 +432,7 @@ BRUTE_TABLE_EXISTS_TEMPLATE = "EXISTS(SELECT %d FROM %s)" BRUTE_COLUMN_EXISTS_TEMPLATE = "EXISTS(SELECT %s FROM %s)" # Payload used for checking of existence of IDS/WAF (dummier the better) -IDS_WAF_CHECK_PAYLOAD = "AND 1=1 UNION ALL SELECT 1,2,3,table_name FROM information_schema.tables WHERE 2>1" +IDS_WAF_CHECK_PAYLOAD = "AND 1=1 UNION ALL SELECT 1,2,3,table_name FROM information_schema.tables WHERE 2>1-- ../../../etc/passwd" # Vectors used for provoking specific WAF/IDS/IPS behavior(s) WAF_ATTACK_VECTORS = ( @@ -434,8 +446,8 @@ WAF_ATTACK_VECTORS = ( # Used for status representation in dictionary attack phase ROTATING_CHARS = ('\\', '|', '|', '/', '-') -# Chunk length (in items) used by BigArray objects (only last chunk and cached one are held in memory) -BIGARRAY_CHUNK_LENGTH = 4096 +# Approximate chunk length (in bytes) used by BigArray objects (only last chunk and cached one are held in memory) +BIGARRAY_CHUNK_SIZE = 1024 * 1024 # Only console display last n table rows TRIM_STDOUT_DUMP_SIZE = 256 @@ -470,6 +482,9 @@ DEFAULT_COOKIE_DELIMITER = ';' # Unix timestamp used for forcing cookie expiration when provided with --load-cookies FORCE_COOKIE_EXPIRATION_TIME = "9999999999" +# Github OAuth token used for creating an automatic Issue for unhandled exceptions +GITHUB_REPORT_OAUTH_TOKEN = "YzQzM2M2YzgzMDExN2I5ZDMyYjAzNTIzODIwZDA2MDFmMmVjODI1Ng==" + # Skip unforced HashDB flush requests below the threshold number of cached items HASHDB_FLUSH_THRESHOLD = 32 @@ -480,7 +495,7 @@ HASHDB_FLUSH_RETRIES = 3 HASHDB_END_TRANSACTION_RETRIES = 3 # Unique milestone value used for forced deprecation of old HashDB values (e.g. when changing hash/pickle mechanism) -HASHDB_MILESTONE_VALUE = "nXkbwIURlN" # rd74b803 "".join(random.sample(string.ascii_letters, 10)) +HASHDB_MILESTONE_VALUE = "JHjrBugdDA" # "".join(random.sample(string.ascii_letters, 10)) # Warn user of possible delay due to large page dump in full UNION query injections LARGE_OUTPUT_THRESHOLD = 1024 ** 2 @@ -504,7 +519,10 @@ MAX_DNS_LABEL = 63 DNS_BOUNDARIES_ALPHABET = re.sub("[a-fA-F]", "", string.ascii_letters) # Alphabet used for heuristic checks -HEURISTIC_CHECK_ALPHABET = ('"', '\'', ')', '(', '[', ']', ',', '.') +HEURISTIC_CHECK_ALPHABET = ('"', '\'', ')', '(', ',', '.') + +# String used for dummy XSS check of a tested parameter value +DUMMY_XSS_CHECK_APPENDIX = "<'\">" # Connection chunk size (processing large responses in chunks to avoid MemoryError crashes - e.g. large table dump in full UNION injections) MAX_CONNECTION_CHUNK_SIZE = 10 * 1024 * 1024 @@ -528,7 +546,7 @@ VALID_TIME_CHARS_RUN_THRESHOLD = 100 CHECK_ZERO_COLUMNS_THRESHOLD = 10 # Boldify all logger messages containing these "patterns" -BOLD_PATTERNS = ("' injectable", "might be injectable", "' is vulnerable", "is not injectable", "test failed", "test passed", "live test final result", "test shows that") +BOLD_PATTERNS = ("' injectable", "might be injectable", "' is vulnerable", "is not injectable", "test failed", "test passed", "live test final result", "test shows that", "the back-end DBMS is", "created Github", "blocked by the target server", "protection is involved") # Generic www root directory names GENERIC_DOC_ROOT_DIRECTORY_NAMES = ("htdocs", "httpdocs", "public", "wwwroot", "www") @@ -540,7 +558,7 @@ MAX_HELP_OPTION_LENGTH = 18 MAX_CONNECT_RETRIES = 100 # Strings for detecting formatting errors -FORMAT_EXCEPTION_STRINGS = ("Type mismatch", "Error converting", "Failed to convert", "System.FormatException", "java.lang.NumberFormatException") +FORMAT_EXCEPTION_STRINGS = ("Type mismatch", "Error converting", "Failed to convert", "System.FormatException", "java.lang.NumberFormatException", "ValueError: invalid literal") # Regular expression used for extracting ASP.NET view state values VIEWSTATE_REGEX = r'(?i)(?P__VIEWSTATE[^"]*)[^>]+value="(?P[^"]+)' @@ -566,6 +584,9 @@ JSON_LIKE_RECOGNITION_REGEX = r"(?s)\A(\s*\[)*\s*\{.*'[^']+'\s*:\s*('[^']+'|\d+) # Regular expression used for detecting multipart POST data MULTIPART_RECOGNITION_REGEX = r"(?i)Content-Disposition:[^;]+;\s*name=" +# Regular expression used for detecting Array-like POST data +ARRAY_LIKE_RECOGNITION_REGEX = r"(\A|%s)(\w+)\[\]=.+%s\2\[\]=" % (DEFAULT_GET_POST_DELIMITER, DEFAULT_GET_POST_DELIMITER) + # Default POST data content-type DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8" @@ -581,15 +602,27 @@ MIN_BINARY_DISK_DUMP_SIZE = 100 # Regular expression used for extracting form tags FORM_SEARCH_REGEX = r"(?si)" +# Maximum number of lines to save in history file +MAX_HISTORY_LENGTH = 1000 + # Minimum field entry length needed for encoded content (hex, base64,...) check MIN_ENCODED_LEN_CHECK = 5 # Timeout in seconds in which Metasploit remote session has to be initialized METASPLOIT_SESSION_TIMEOUT = 300 +# Reference: http://www.postgresql.org/docs/9.0/static/catalog-pg-largeobject.html +LOBLKSIZE = 2048 + +# Suffix used to mark variables having keyword names +EVALCODE_KEYWORD_SUFFIX = "_KEYWORD" + # Reference: http://www.cookiecentral.com/faq/#3.5 NETSCAPE_FORMAT_HEADER_COOKIES = "# Netscape HTTP Cookie File." +# Infixes used for automatic recognition of parameters carrying anti-CSRF tokens +CSRF_TOKEN_PARAMETER_INFIXES = ("csrf", "xsrf") + # Prefixes used in brute force search for web server document root BRUTE_DOC_ROOT_PREFIXES = { OS.LINUX: ("/var/www", "/usr/local/apache", "/usr/local/apache2", "/usr/local/www/apache22", "/usr/local/www/apache24", "/usr/local/httpd", "/var/www/nginx-default", "/srv/www", "/var/www/%TARGET%", "/var/www/vhosts/%TARGET%", "/var/www/virtual/%TARGET%", "/var/www/clients/vhosts/%TARGET%", "/var/www/clients/virtual/%TARGET%"), @@ -605,6 +638,9 @@ BRUTE_DOC_ROOT_TARGET_MARK = "%TARGET%" # Character used as a boundary in kb.chars (preferably less frequent letter) KB_CHARS_BOUNDARY_CHAR = 'q' +# Letters of lower frequency used in kb.chars +KB_CHARS_LOW_FREQUENCY_ALPHABET = "zqxjkvbp" + # CSS style used in HTML dump format HTML_DUMP_CSS_STYLE = """