diff --git a/data/txt/sha256sums.txt b/data/txt/sha256sums.txt index 8f3a258178f..35b989ed021 100644 --- a/data/txt/sha256sums.txt +++ b/data/txt/sha256sums.txt @@ -87,7 +87,7 @@ b0f434f64105bd61ab0f6867b3f681b97fa02b4fb809ac538db382d031f0e609 data/xml/paylo 8b63fda09d5c5e43ad8e6db1db90e5b1017fbe02735f3858843fc52118e3a33a data/xml/queries.xml 0f5a9c84cb57809be8759f483c7d05f54847115e715521ac0ecf390c0aa68465 doc/AUTHORS ce20a4b452f24a97fde7ec9ed816feee12ac148e1fde5f1722772cc866b12740 doc/CHANGELOG.md -c8d5733111c6d1e387904bc14e98815f98f816f6e73f6a664de24c0f1d331d9b doc/THANKS.md +233fb10dff24a2436eb24496db7fadb46659da6745a0d53c744db701188041ef doc/THANKS.md 59697fb4f118a3197f5b3dc9057351797767c8bcc748e0286e3f7ad74ec3afb6 doc/THIRD-PARTY.md 2af9b7a8c5f24de68f9b8b1bcf3a7f2b0e55fdb48b6545e1fc8b13f406ac97c2 doc/translations/README-ar-AR.md c25f7d7f0cc5e13db71994d2b34ada4965e06c87778f1d6c1a103063d25e2c89 doc/translations/README-bg-BG.md @@ -161,13 +161,13 @@ df768bcb9838dc6c46dab9b4a877056cb4742bd6cfaaf438c4a3712c5cc0d264 extra/shutils/ 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 extra/vulnserver/__init__.py 9e5e4d3d9acb767412259895a3ee75e1a5f42d0b9923f17605d771db384a6f60 extra/vulnserver/vulnserver.py b8411d1035bb49b073476404e61e1be7f4c61e205057730e2f7880beadcd5f60 lib/controller/action.py -ced1c82713afc1309c1495485b3d25a11c95af1f7460ea7922dbb96dacac37b4 lib/controller/checks.py +03239569ebcdcb4c445bc778abb8f6fc7e26285a872d302cf3d366fb1c0c85b1 lib/controller/checks.py c1881685bef8504ded32c51abed00ab51849008c84b74e8a66117e5f5041b3df lib/controller/controller.py d69e84f1648cdb907f5d2dd454f03874a4613752b07867510145d51d84b3c56f lib/controller/handler.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/controller/__init__.py b2555d11529689f5d7d02bee0741d3228969e2bf29a2b9140bf1560ff60249e7 lib/core/agent.py ca3e5ce56cb1cae0a8e815425ab6810068004bffe8861d1037c7c87c0ae02477 lib/core/bigarray.py -c84aa815738fbee9cdac0ba93d28db656c9ab76cf73b65e3f6298d857326faa9 lib/core/common.py +d3993ce0d1c73150d87405d1d4479bff189ecad179614b76c216dde65a0e06cd lib/core/common.py f30b4eccdb574731fa7e6ef48e71ea82d4bc99be70a2e27bff230943e9039313 lib/core/compat.py e37bfd314a46699b14e1c8a5ea851d546d3a36bea8e5f37466ef2921ff78fefd lib/core/convert.py c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data.py @@ -175,7 +175,7 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 70fb2528e580b22564899595b0dff6b1bc257c6a99d2022ce3996a3d04e68e4e lib/core/decorators.py 147823c37596bd6a56d677697781f34b8d1d1671d5a2518fbc9468d623c6d07d lib/core/defaults.py 2f44a1bfe6f18aafe64147b99e69aa93cf438c0e7befe59f4e2aee9065c8b7b6 lib/core/dicts.py -3e00b5c4ca385886f57608f7e0695bb70c696ef3454c181bbdfeea746efba96a lib/core/dump.py +8aee07fba24082ee6355a29d01842bc3657194148a7f9062079b5f0a85ec53e3 lib/core/dump.py 23e33f0b457e2a7114c9171ba9b42e1751b71ee3f384bba7fad39e4490adb803 lib/core/enums.py 5387168e5dfedd94ae22af7bb255f27d6baaca50b24179c6b98f4f325f5cc7b4 lib/core/exception.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/core/__init__.py @@ -188,7 +188,7 @@ c03dc585f89642cfd81b087ac2723e3e1bb3bfa8c60e6f5fe58ef3b0113ebfe6 lib/core/data. 48797d6c34dd9bb8a53f7f3794c85f4288d82a9a1d6be7fcf317d388cb20d4b3 lib/core/replication.py 0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py 888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py -c47f468f0b178996607216adf25eaea93c03e20539a4454079cf40cd699b36fe lib/core/settings.py +5d7a0d270747665518827e61ac39282434cf44d1b5a65a508c2ec8ed96d6b5fe lib/core/settings.py cd5a66deee8963ba8e7e9af3dd36eb5e8127d4d68698811c29e789655f507f82 lib/core/shell.py bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subprocessng.py 70ea3768f1b3062b22d20644df41c86238157ec80dd43da40545c620714273c6 lib/core/target.py @@ -196,7 +196,7 @@ bcb5d8090d5e3e0ef2a586ba09ba80eef0c6d51feb0f611ed25299fbb254f725 lib/core/subpr e3e653364d08d04d7492aa40a2bd29c6a28f4d78fecdd6c10f21f6cb28b98b4c lib/core/threads.py b9aacb840310173202f79c2ba125b0243003ee6b44c92eca50424f2bdfc83c02 lib/core/unescaper.py 53e396902cb2546eaa09e77073fcba8be8827ee9ce055cfc899e81b0e6ad4d6d lib/core/update.py -ec11fd5a3f4efd10a1cae288157ac6eb6fb75da4666d76d19f6adf74ac338b5a lib/core/wordlist.py +2400e465fa4d13e4c32795910878c71ff212e4361b46428d57ce43983f5e997c lib/core/wordlist.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/__init__.py 54bfd31ebded3ffa5848df1c644f196eb704116517c7a3d860b5d081e984d821 lib/parse/banner.py 4c56ad26ffb893d37813167de172b6c95c120588bfdc899f102977a2997b9bb9 lib/parse/cmdline.py @@ -208,10 +208,10 @@ c5b258be7485089fac9d9cd179960e774fbd85e62836dc67cce76cc028bb6aeb lib/parse/hand d2e771cdacef25ee3fdc0e0355b92e7cd1b68f5edc2756ffc19f75d183ba2c73 lib/parse/payloads.py 455ab0ec63e55cd56ce4a884b85bdc089223155008cab0f3696da5a33118f95b lib/parse/sitemap.py 1be3da334411657461421b8a26a0f2ff28e1af1e28f1e963c6c92768f9b0847c lib/request/basicauthhandler.py -b34d38c711b1fcbf8004174cc34733a742f55bc91f389f2619e14c1c7c0a63d8 lib/request/basic.py +de8e087e041e3252e6dd60d171a0cfe349f1e11764274108e8e13ba5be992ef3 lib/request/basic.py bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/chunkedhandler.py 09c2d8786fb5280f5f14a7b4345ecb2e7c2ca836ee06a6cf9b51770df923d94c lib/request/comparison.py -9236db2abad1b1d368a3c5a5beb655055fd2445faba57a4172db264b06105bd4 lib/request/connect.py +c4a0759ee29ce8a29648090660dc273494abef9bda52430c38e41675a9b6ac6a lib/request/connect.py 8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py cf019248253a5d7edb7bc474aa020b9e8625d73008a463c56ba2b539d7f2d8ec lib/request/dns.py 92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py @@ -220,14 +220,14 @@ aeeeb5f0148078e30d52208184042efc3618d3f2e840d7221897aae34315824e lib/request/in ada4d305d6ce441f79e52ec3f2fc23869ee2fa87c017723e8f3ed0dfa61cdab4 lib/request/methodrequest.py 43a7fdf64e7ba63c6b2d641c9f999a63c12ac23b43b64fedfce4e05b863de568 lib/request/pkihandler.py b90feeb16e89a844427df42373b0139eb6f6cf3c48ccec32b3e3a3f540c2451e lib/request/rangehandler.py -673fbe28e3031a9be6f1d5b9ee8af4985dd9f69458ca1264e2eb3c3eec8d8c3d lib/request/redirecthandler.py +fa347e74361904d052e4d5c958ebbdf080e4f7003176824a44786108b4d7afc6 lib/request/redirecthandler.py 1bf93c2c251f9c422ecf52d9cae0cd0ff4ea2e24091ee6d019c7a4f69de8e5eb lib/request/templates.py 01600295b17c00d4a5ada4c77aa688cfe36c89934da04c031be7da8040a3b457 lib/takeover/abstraction.py d3c93562d78ebdaf9e22c0ea2e4a62adb12f0ce9e9d9631c1ea000b1a07d04ab lib/takeover/icmpsh.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/takeover/__init__.py 12e729e4828b7e1456ca41dae60cb4d7eca130a8b4c4885dd0f5501dcbda7fe4 lib/takeover/metasploit.py f522436fbd14bdab090a1d305fcac0361800cb8e36c8cbcb47933298376a71e0 lib/takeover/registry.py -f6e5d6e2ff368fa39943b2302982f33c47eb9a12d01419bef50fcf934b2bce34 lib/takeover/udf.py +0787f78e6bd9bb21d4267c95c4c99806711bb57c5518485c2e25f10fcf9c41fc lib/takeover/udf.py 23d73af417604dab460b74cdc230896153f018a6c00d144019491053640a172f lib/takeover/web.py 8cc1e226d4150fe8aa1a056e5d32d858ed6444d3d4e2af7fb4bc08f0bbe9d527 lib/takeover/xp_cmdshell.py 3609556c6c72010ce4cae5ffeeb74437a15a9dc218f77e079655f32e704fdeef lib/techniques/blind/inference.py @@ -241,7 +241,7 @@ f552b6140d4069be6a44792a08f295da8adabc1c4bb6a5e100f222f87144ca9d lib/techniques 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/techniques/union/__init__.py 30cae858e2a5a75b40854399f65ad074e6bb808d56d5ee66b94d4002dc6e101b lib/techniques/union/test.py a8a795f29ec6fd66482926f04b054ed492a033982c3b7837c5d2ea32368acec0 lib/techniques/union/use.py -c771212c97b534f47e74e972e12ada7d341a170c637ed2638cee6546f7b754d2 lib/utils/api.py +5832f1b9cce5e8fe71cc1e07a690fa30f2bc0caa07e734220372a846aae6b95f lib/utils/api.py 442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py @@ -251,7 +251,7 @@ a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps b74a311e1cd30ec62e54684f970c14bfd85ffde225b9ddbbb12b85f3c528f8c2 lib/utils/hashdb.py 71a66ff766a2921106770b26acff380de469222dc893816a7b970b384c927666 lib/utils/hash.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/utils/__init__.py -22ba65391b0a73b1925e5becf8ddab6ba73a196d86e351a2263509aad6676bd7 lib/utils/pivotdumptable.py +e7d31de0e268c129ee11c590eb618f73a85e1022c08b8ed1f77753043c949214 lib/utils/pivotdumptable.py c1dfc3bed0fed9b181f612d1d747955dd2b506dbe99bc9fd481495602371473a lib/utils/progress.py 27afe211030d06db28df85296bfbf698296c94440904c390cef0ff0c259dbbc5 lib/utils/purge.py c853aa08ab24a00a78969408d60684da0ccb33a2a6693492e0acb7c480ffbcd1 lib/utils/safe2bin.py @@ -477,7 +477,7 @@ e2e20e4707abe9ed8b6208837332d2daa4eaca282f847412063f2484dcca8fbd plugins/dbms/v 51c44048e4b335b306f8ed1323fd78ad6935a8c0d6e9d6efe195a9a5a24e46dc plugins/generic/connector.py a967f4ebd101c68a5dcc10ff18c882a8f44a5c3bf06613d951a739ecc3abb9b3 plugins/generic/custom.py 37351d6fb7418e3659bec5c9a6f9f181a606deae74d3bc9fb8c97f495449471f plugins/generic/databases.py -a82834adfe09cd73d69fd954047e09dddcc6c63183994499ce134e27b56e2321 plugins/generic/entries.py +36b7319ac00f8fe1a33496364a76ff165ea2e66db0150f5366a45135366369ca plugins/generic/entries.py d2de7fc135cf0db3eb4ac4a509c23ebec5250a5d8043face7f8c546a09f301b5 plugins/generic/enumeration.py a02ac4ebc1cc488a2aa5ae07e6d0c3d5064e99ded7fd529dfa073735692f11df plugins/generic/filesystem.py efd7177218288f32881b69a7ba3d667dc9178f1009c06a3e1dd4f4a4ee6980db plugins/generic/fingerprint.py @@ -489,8 +489,8 @@ cedf45d33461bd7e5400d06611a63c8a4ffae1a4510030c5696b9d46ed6a9883 plugins/generi 45bfd00f09557e20115e6ce7fb52ff507930d705db215e535f991e5fbf7464de plugins/generic/users.py 1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 plugins/__init__.py 5d72f0af46ff3c9e3fe80300e83cb78749132278e8db88915764a94d7130a04c README.md -7ef0d0ea10d4b19283b1e380d521abb0fdd4c6bf1443b88f7b00af7947fc5e27 sqlmapapi.py -69ca771751f9d996cc07c2cd3f082667949148792ba9db26d08dc953fbf17815 sqlmapapi.yaml +46517f1444c202710e388873960130850ed092e17bd6f4dd5f2fedea3dbb8ffc sqlmapapi.py +e0607378f46f7664349552c628f25c4689569c788fd2364eef3075dd2cce127b sqlmapapi.yaml 627d90f1194335b800cbc9cc78db6697cf9e02e193a83598e0d4d0abb55b63b8 sqlmap.conf 65159b82795604069a2d14ccbd1f66e888a26b05db0401a1ddadb40c665c93dc sqlmap.py eb37a88357522fd7ad00d90cdc5da6b57442b4fec49366aadb2944c4fbf8b804 tamper/0eunion.py diff --git a/doc/THANKS.md b/doc/THANKS.md index 62d4ba136cf..fcc746a266a 100644 --- a/doc/THANKS.md +++ b/doc/THANKS.md @@ -175,7 +175,7 @@ Ivan Giacomelli, * for reviewing the documentation Dimitris Giannitsaros, -* for contributing a REST-JSON API client +* for contributing a REST API client Nico Golde, * for reporting a couple of bugs diff --git a/lib/controller/checks.py b/lib/controller/checks.py index 9e9f1f4fd7d..0181e91731d 100644 --- a/lib/controller/checks.py +++ b/lib/controller/checks.py @@ -1088,7 +1088,7 @@ def _(page): if casting: errMsg = "possible %s casting detected (e.g. '" % ("integer" if origValue.isdigit() else "type") - platform = conf.url.split('.')[-1].lower() + platform = (extractRegexResult(r"\.(?P\w+)(?:\?|\Z)", conf.url) or conf.url.split('.')[-1]).lower() if platform == WEB_PLATFORM.ASP: errMsg += "%s=CInt(request.querystring(\"%s\"))" % (parameter, parameter) elif platform == WEB_PLATFORM.ASPX: diff --git a/lib/core/common.py b/lib/core/common.py index 405f17b8713..efbe262ec43 100644 --- a/lib/core/common.py +++ b/lib/core/common.py @@ -655,7 +655,7 @@ def paramToDict(place, parameters=None): kb.base64Originals[parameter] = oldValue = value value = urldecode(value, convall=True) value = decodeBase64(value, binary=False, encoding=conf.encoding or UNICODE_ENCODING) - parameters = re.sub(r"\b%s(\b|\Z)" % re.escape(oldValue), value, parameters) + parameters = re.sub(r"\b%s(\b|\Z)" % re.escape(oldValue), value.replace('\\', r'\\'), parameters) except: errMsg = "parameter '%s' does not contain " % parameter errMsg += "valid Base64 encoded value ('%s')" % value @@ -4283,7 +4283,10 @@ def safeSQLIdentificatorNaming(name, isTable=False): '[begin]' >>> getText(safeSQLIdentificatorNaming("foobar")) 'foobar' - >>> kb.forceDbms = popValue() + >>> kb.forcedDbms = DBMS.FIREBIRD + >>> getText(safeSQLIdentificatorNaming("foo bar")) + '"foo bar"' + >>> kb.forcedDbms = popValue() """ retVal = name @@ -4303,9 +4306,9 @@ def safeSQLIdentificatorNaming(name, isTable=False): if not conf.noEscape: retVal = unsafeSQLIdentificatorNaming(retVal) - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER): # Note: in SQLite double-quotes are treated as string if column/identifier is non-existent (e.g. SELECT "foobar" FROM users) + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER, DBMS.CLICKHOUSE): # Note: in SQLite double-quotes are treated as string if column/identifier is non-existent (e.g. SELECT "foobar" FROM users) retVal = "`%s`" % retVal - elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE): + elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE, DBMS.FIREBIRD, DBMS.DERBY, DBMS.MAXDB): retVal = "\"%s\"" % retVal elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL): retVal = "\"%s\"" % retVal.upper() @@ -4342,9 +4345,9 @@ def unsafeSQLIdentificatorNaming(name): retVal = name if isinstance(name, six.string_types): - if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER): + if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.ACCESS, DBMS.CUBRID, DBMS.SQLITE, DBMS.SPANNER, DBMS.CLICKHOUSE): retVal = name.replace("`", "") - elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE): + elif Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.DB2, DBMS.HSQLDB, DBMS.H2, DBMS.INFORMIX, DBMS.MONETDB, DBMS.VERTICA, DBMS.MCKOI, DBMS.PRESTO, DBMS.CRATEDB, DBMS.CACHE, DBMS.EXTREMEDB, DBMS.FRONTBASE, DBMS.RAIMA, DBMS.VIRTUOSO, DBMS.SNOWFLAKE, DBMS.FIREBIRD, DBMS.DERBY, DBMS.MAXDB): retVal = name.replace("\"", "") elif Backend.getIdentifiedDbms() in (DBMS.ORACLE, DBMS.ALTIBASE, DBMS.MIMERSQL): retVal = name.replace("\"", "").upper() diff --git a/lib/core/dump.py b/lib/core/dump.py index 7cf44c5b9c0..d55291e5129 100644 --- a/lib/core/dump.py +++ b/lib/core/dump.py @@ -299,8 +299,8 @@ def dbTableColumns(self, tableColumns, content_type=None): colType = columns[column] column = unsafeSQLIdentificatorNaming(column) - maxlength1 = max(maxlength1, len(column or "")) - maxlength2 = max(maxlength2, len(colType or "")) + maxlength1 = max(maxlength1, getConsoleLength(column or "")) + maxlength2 = max(maxlength2, getConsoleLength(colType or "")) maxlength1 = max(maxlength1, len("COLUMN")) lines1 = "-" * (maxlength1 + 2) @@ -337,10 +337,10 @@ def dbTableColumns(self, tableColumns, content_type=None): colType = columns[column] column = unsafeSQLIdentificatorNaming(column) - blank1 = " " * (maxlength1 - len(column)) + blank1 = " " * (maxlength1 - getConsoleLength(column)) if colType is not None: - blank2 = " " * (maxlength2 - len(colType)) + blank2 = " " * (maxlength2 - getConsoleLength(colType)) self._write("| %s%s | %s%s |" % (column, blank1, colType, blank2)) else: self._write("| %s%s |" % (column, blank1)) diff --git a/lib/core/settings.py b/lib/core/settings.py index a4bc5c0429f..9acb384895a 100644 --- a/lib/core/settings.py +++ b/lib/core/settings.py @@ -20,7 +20,7 @@ from thirdparty import six # sqlmap version (...) -VERSION = "1.10.6.68" +VERSION = "1.10.6.83" TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable" TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34} VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE) @@ -474,7 +474,7 @@ SENSITIVE_DATA_REGEX = r"(\s|=)(?P[^\s=]*\b%s\b[^\s]*)\s" # Options to explicitly mask in anonymous (unhandled exception) reports (along with anything carrying the inside) -SENSITIVE_OPTIONS = ("hostname", "answers", "data", "dnsDomain", "googleDork", "authCred", "proxyCred", "tbl", "db", "col", "user", "cookie", "proxy", "fileRead", "fileWrite", "fileDest", "testParameter", "authCred", "sqlQuery", "requestFile", "csrfToken", "csrfData", "csrfUrl", "testParameter") +SENSITIVE_OPTIONS = ("hostname", "answers", "data", "dnsDomain", "googleDork", "proxyCred", "tbl", "db", "col", "user", "cookie", "proxy", "fileRead", "fileWrite", "fileDest", "authCred", "sqlQuery", "requestFile", "csrfToken", "csrfData", "csrfUrl", "testParameter") # Maximum number of threads (avoiding connection issues and/or DoS) MAX_NUMBER_OF_THREADS = 10 @@ -552,7 +552,7 @@ RESTORE_MERGED_OPTIONS = ("col", "db", "dbms", "os", "dnsDomain", "privEsc", "tbl", "regexp", "string", "textOnly", "threads", "timeSec", "tmpPath", "uChar", "user") # Parameters to be ignored in detection phase (upper case) -IGNORE_PARAMETERS = ("__VIEWSTATE", "__VIEWSTATEENCRYPTED", "__VIEWSTATEGENERATOR", "__EVENTARGUMENT", "__EVENTTARGET", "__EVENTVALIDATION", "ASPSESSIONID", "ASP.NET_SESSIONID", "JSESSIONID", "CFID", "CFTOKEN") +IGNORE_PARAMETERS = ("__VIEWSTATE", "__VIEWSTATEENCRYPTED", "__VIEWSTATEGENERATOR", "__EVENTARGUMENT", "__EVENTTARGET", "__EVENTVALIDATION", "__SCROLLPOSITIONX", "__SCROLLPOSITIONY", "__PREVIOUSPAGE", "ASPSESSIONID", "ASP.NET_SESSIONID", "JSESSIONID", "PHPSESSID", "SESSID", "CFID", "CFTOKEN") # Regular expression used for recognition of ASP.NET control parameters ASP_NET_CONTROL_REGEX = r"(?i)\Actl\d+\$" @@ -624,7 +624,7 @@ DUMMY_USER_INJECTION = r"(?i)[^\w](AND|OR)\s+[^\s]+[=><]|\bUNION\b.+\bSELECT\b|\bSELECT\b.+\bFROM\b|\b(CONCAT|information_schema|SLEEP|DELAY|FLOOR\(RAND)\b" # Extensions skipped by crawler -CRAWL_EXCLUDE_EXTENSIONS = frozenset(("3ds", "3g2", "3gp", "7z", "DS_Store", "a", "aac", "accdb", "access", "adp", "ai", "aif", "aiff", "apk", "ar", "asf", "au", "avi", "bak", "bin", "bin", "bk", "bkp", "bmp", "btif", "bz2", "c", "cab", "caf", "cfg", "cgm", "cmx", "com", "conf", "config", "cpio", "cpp", "cr2", "cue", "dat", "db", "dbf", "deb", "debug", "djvu", "dll", "dmg", "dmp", "dng", "doc", "docx", "dot", "dotx", "dra", "dsk", "dts", "dtshd", "dvb", "dwg", "dxf", "dylib", "ear", "ecelp4800", "ecelp7470", "ecelp9600", "egg", "elf", "env", "eol", "eot", "epub", "error", "exe", "f4v", "fbs", "fh", "fla", "flac", "fli", "flv", "fpx", "fst", "fvt", "g3", "gif", "go", "gz", "h", "h261", "h263", "h264", "ico", "ief", "img", "ini", "ipa", "iso", "jar", "java", "jpeg", "jpg", "jpgv", "jpm", "js", "jxr", "ktx", "lock", "log", "lvp", "lz", "lzma", "lzo", "m3u", "m4a", "m4v", "mar", "mdb", "mdi", "mid", "mj2", "mka", "mkv", "mmr", "mng", "mov", "movie", "mp3", "mp4", "mp4a", "mpeg", "mpg", "mpga", "msi", "mxu", "nef", "npx", "nrg", "o", "oga", "ogg", "ogv", "old", "otf", "ova", "ovf", "pbm", "pcx", "pdf", "pea", "pgm", "php", "pic", "pid", "pkg", "png", "pnm", "ppm", "pps", "ppt", "pptx", "ps", "psd", "py", "pya", "pyc", "pyo", "pyv", "qt", "rar", "ras", "raw", "rb", "rgb", "rip", "rlc", "rs", "run", "rz", "s3m", "s7z", "scm", "scpt", "service", "sgi", "shar", "sil", "smv", "so", "sock", "socket", "sqlite", "sqlitedb", "sub", "svc", "swf", "swo", "swp", "sys", "tar", "tbz2", "temp", "tga", "tgz", "tif", "tiff", "tlz", "tmp", "toast", "torrent", "ts", "ts", "ttf", "uvh", "uvi", "uvm", "uvp", "uvs", "uvu", "vbox", "vdi", "vhd", "vhdx", "viv", "vmdk", "vmx", "vob", "vxd", "war", "wav", "wax", "wbmp", "wdp", "weba", "webm", "webp", "whl", "wm", "wma", "wmv", "wmx", "woff", "woff2", "wvx", "xbm", "xif", "xls", "xlsx", "xlt", "xm", "xpi", "xpm", "xwd", "xz", "yaml", "yml", "z", "zip", "zipx")) +CRAWL_EXCLUDE_EXTENSIONS = frozenset(("3ds", "3g2", "3gp", "7z", "DS_Store", "a", "aac", "accdb", "access", "adp", "ai", "aif", "aiff", "apk", "ar", "asf", "au", "avi", "bak", "bin", "bk", "bkp", "bmp", "btif", "bz2", "c", "cab", "caf", "cfg", "cgm", "cmx", "com", "conf", "config", "cpio", "cpp", "cr2", "cue", "dat", "db", "dbf", "deb", "debug", "djvu", "dll", "dmg", "dmp", "dng", "doc", "docx", "dot", "dotx", "dra", "dsk", "dts", "dtshd", "dvb", "dwg", "dxf", "dylib", "ear", "ecelp4800", "ecelp7470", "ecelp9600", "egg", "elf", "env", "eol", "eot", "epub", "error", "exe", "f4v", "fbs", "fh", "fla", "flac", "fli", "flv", "fpx", "fst", "fvt", "g3", "gif", "go", "gz", "h", "h261", "h263", "h264", "ico", "ief", "img", "ini", "ipa", "iso", "jar", "java", "jpeg", "jpg", "jpgv", "jpm", "js", "jxr", "ktx", "lock", "log", "lvp", "lz", "lzma", "lzo", "m3u", "m4a", "m4v", "mar", "mdb", "mdi", "mid", "mj2", "mka", "mkv", "mmr", "mng", "mov", "movie", "mp3", "mp4", "mp4a", "mpeg", "mpg", "mpga", "msi", "mxu", "nef", "npx", "nrg", "o", "oga", "ogg", "ogv", "old", "otf", "ova", "ovf", "pbm", "pcx", "pdf", "pea", "pgm", "php", "pic", "pid", "pkg", "png", "pnm", "ppm", "pps", "ppt", "pptx", "ps", "psd", "py", "pya", "pyc", "pyo", "pyv", "qt", "rar", "ras", "raw", "rb", "rgb", "rip", "rlc", "rs", "run", "rz", "s3m", "s7z", "scm", "scpt", "service", "sgi", "shar", "sil", "smv", "so", "sock", "socket", "sqlite", "sqlitedb", "sub", "svc", "swf", "swo", "swp", "sys", "tar", "tbz2", "temp", "tga", "tgz", "tif", "tiff", "tlz", "tmp", "toast", "torrent", "ts", "ttf", "uvh", "uvi", "uvm", "uvp", "uvs", "uvu", "vbox", "vdi", "vhd", "vhdx", "viv", "vmdk", "vmx", "vob", "vxd", "war", "wav", "wax", "wbmp", "wdp", "weba", "webm", "webp", "whl", "wm", "wma", "wmv", "wmx", "woff", "woff2", "wvx", "xbm", "xif", "xls", "xlsx", "xlt", "xm", "xpi", "xpm", "xwd", "xz", "yaml", "yml", "z", "zip", "zipx")) # Patterns often seen in HTTP headers containing custom injection marking character '*' PROBLEMATIC_CUSTOM_INJECTION_PATTERNS = r"(;q=[^;']+)|(\*/\*)" @@ -826,7 +826,7 @@ MAX_CONNECT_RETRIES = 100 # 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", "__VIEWSTATE[^"]*)[^>]+value="(?P[^"]+)' @@ -840,13 +840,13 @@ # Default adapter to use for bottle server RESTAPI_DEFAULT_ADAPTER = "wsgiref" -# Default REST-JSON API server listen address +# Default REST API server listen address RESTAPI_DEFAULT_ADDRESS = "127.0.0.1" -# Default REST-JSON API server listen port +# Default REST API server listen port RESTAPI_DEFAULT_PORT = 8775 -# Unsupported options by REST-JSON API server +# Unsupported options by REST API server RESTAPI_UNSUPPORTED_OPTIONS = ("sqlShell", "wizard", "evalCode", "alert") # Use "Supplementary Private Use Area-A" @@ -971,14 +971,14 @@ _ = key[len(SQLMAP_ENVIRONMENT_PREFIX) + 1:].upper() if _ in globals(): original = globals()[_] - if isinstance(original, int): + if isinstance(original, bool): + globals()[_] = value.lower() in ('1', 'true') + elif isinstance(original, int): try: globals()[_] = int(value) except ValueError: pass - elif isinstance(original, bool): - globals()[_] = value.lower() in ('1', 'true') elif isinstance(original, (list, tuple)): - globals()[_] = [__.strip() for __ in _.split(',')] + globals()[_] = [__.strip() for __ in value.split(',')] else: globals()[_] = value diff --git a/lib/core/wordlist.py b/lib/core/wordlist.py index 1bb8e42bf2f..d3462f111e9 100644 --- a/lib/core/wordlist.py +++ b/lib/core/wordlist.py @@ -87,8 +87,10 @@ def __next__(self): errMsg += "sure that you haven't made any changes to it" raise SqlmapInstallationException(errMsg) except StopIteration: - self.adjust() - retVal = next(self.iter).rstrip() + if self.index > len(self.filenames): # Note: no more sources (filenames + custom) to switch to + raise + self.adjust() # Note: switch to the next source and retry (gracefully skipping empty ones) + continue if not self.proc_count or self.counter % self.proc_count == self.proc_id: break return retVal diff --git a/lib/request/basic.py b/lib/request/basic.py index 8d1b79a3e3b..c72e946b5e6 100644 --- a/lib/request/basic.py +++ b/lib/request/basic.py @@ -235,13 +235,13 @@ def checkCharEncoding(encoding, warn=True): # Reference: http://docs.python.org/library/codecs.html try: codecs.lookup(encoding) - except: + except LookupError: encoding = None if encoding: try: six.text_type(getBytes(randomStr()), encoding) - except: + except (UnicodeDecodeError, LookupError): if warn: warnMsg = "invalid web page charset '%s'" % encoding singleTimeLogMessage(warnMsg, logging.WARN, encoding) @@ -282,6 +282,8 @@ def decodePage(page, contentEncoding, contentType, percentDecode=True): 'foo&bar' >>> getText(decodePage(b" ", None, "text/html; charset=utf-8")) '\\t' + >>> getText(decodePage(b"J", None, "text/html; charset=utf-8")) + 'J' """ if not page or (conf.nullConnection and len(page) < 2): @@ -346,7 +348,7 @@ def decodePage(page, contentEncoding, contentType, percentDecode=True): if not kb.disableHtmlDecoding: # e.g. Ãëàâà if b"&#" in page: - page = re.sub(b"&#x([0-9a-f]{1,2});", lambda _: decodeHex(_.group(1) if len(_.group(1)) == 2 else b"0%s" % _.group(1)), page) + page = re.sub(b"(?i)&#x([0-9a-f]{1,2});", lambda _: decodeHex(_.group(1) if len(_.group(1)) == 2 else b"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 diff --git a/lib/request/connect.py b/lib/request/connect.py index fe5ebb3959f..d83708db238 100644 --- a/lib/request/connect.py +++ b/lib/request/connect.py @@ -1163,7 +1163,7 @@ def queryPage(value=None, place=None, content=False, getRatioValue=False, silent postUrlEncode = False if conf.hpp: - if not any(conf.url.lower().endswith(_.lower()) for _ in (WEB_PLATFORM.ASP, WEB_PLATFORM.ASPX)): + if (extractRegexResult(r"\.(?P\w+)(?:\?|\Z)", conf.url) or "").lower() not in (WEB_PLATFORM.ASP, WEB_PLATFORM.ASPX): warnMsg = "HTTP parameter pollution should work only against " warnMsg += "ASP(.NET) targets" singleTimeWarnMessage(warnMsg) diff --git a/lib/request/redirecthandler.py b/lib/request/redirecthandler.py index 0c1e9d08694..515c415e519 100644 --- a/lib/request/redirecthandler.py +++ b/lib/request/redirecthandler.py @@ -156,7 +156,7 @@ def http_error_302(self, req, fp, code, msg, headers): try: result = _urllib.request.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers) except _urllib.error.HTTPError as ex: - result = ex + result = error = ex # Dirty hack for https://github.com/sqlmapproject/sqlmap/issues/4046 try: @@ -178,7 +178,7 @@ def _(self): if not hasattr(result, "read"): def _(self, length=None): try: - retVal = getSafeExString(ex) # Note: pyflakes mistakenly marks 'ex' as undefined (NOTE: tested in both Python2 and Python3) + retVal = getSafeExString(error) except: retVal = "" return getBytes(retVal) diff --git a/lib/takeover/udf.py b/lib/takeover/udf.py index 36192805ea5..7a6d51e0336 100644 --- a/lib/takeover/udf.py +++ b/lib/takeover/udf.py @@ -231,8 +231,8 @@ def udfInjectCustom(self): errMsg += "but the database underlying operating system is Linux" raise SqlmapMissingMandatoryOptionException(errMsg) - self.udfSharedLibName = os.path.basename(self.udfLocalFile).split(".")[0] - self.udfSharedLibExt = os.path.basename(self.udfLocalFile).split(".")[1] + self.udfSharedLibName = os.path.splitext(os.path.basename(self.udfLocalFile))[0] + self.udfSharedLibExt = os.path.splitext(self.udfLocalFile)[1][1:] msg = "how many user-defined functions do you want to create " msg += "from the shared library? " diff --git a/lib/utils/api.py b/lib/utils/api.py index 5dfb10eae89..8cd8bcfff41 100644 --- a/lib/utils/api.py +++ b/lib/utils/api.py @@ -78,6 +78,8 @@ class DataStore(object): username = None password = None +RESTAPI_READONLY_OPTIONS = ("api", "taskid", "database") + # API objects class Database(object): filepath = None @@ -91,7 +93,7 @@ def connect(self, who="server"): self.connection = sqlite3.connect(self.database, timeout=3, isolation_level=None, check_same_thread=False) self.cursor = self.connection.cursor() self.lock = threading.Lock() - logger.debug("REST-JSON API %s connected to IPC database" % who) + logger.debug("REST API %s connected to IPC database" % who) def disconnect(self): if self.cursor: @@ -296,6 +298,19 @@ def setRestAPILog(): def is_admin(token): return safeCompareStrings(DataStore.admin_token, token) +def validate_task_options(taskid, options, caller): + if not isinstance(options, dict): + logger.warning("[%s] Invalid JSON options provided to %s()" % (taskid, caller)) + return "Invalid JSON options" + + for key in options: + if key in RESTAPI_UNSUPPORTED_OPTIONS or key in RESTAPI_READONLY_OPTIONS: + logger.warning("[%s] Unsupported option '%s' provided to %s()" % (taskid, key, caller)) + return "Unsupported option '%s'" % key + elif key not in DataStore.tasks[taskid].options: + logger.warning("[%s] Unknown option '%s' provided to %s()" % (taskid, key, caller)) + return "Unknown option '%s'" % key + @hook('before_request') def check_authentication(): if not any((DataStore.username, DataStore.password)): @@ -490,10 +505,9 @@ def option_set(taskid): logger.warning("[%s] Invalid JSON options provided to option_set()" % taskid) return jsonize({"success": False, "message": "Invalid JSON options"}) - for key in request.json: - if key in RESTAPI_UNSUPPORTED_OPTIONS: - logger.warning("[%s] Unsupported option '%s' provided to option_set()" % (taskid, key)) - return jsonize({"success": False, "message": "Unsupported option '%s'" % key}) + message = validate_task_options(taskid, request.json, "option_set") + if message: + return jsonize({"success": False, "message": message}) for option, value in request.json.items(): DataStore.tasks[taskid].set_option(option, value) @@ -516,10 +530,13 @@ def scan_start(taskid): logger.warning("[%s] Invalid JSON options provided to scan_start()" % taskid) return jsonize({"success": False, "message": "Invalid JSON options"}) - for key in request.json: - if key in RESTAPI_UNSUPPORTED_OPTIONS: - logger.warning("[%s] Unsupported option '%s' provided to scan_start()" % (taskid, key)) - return jsonize({"success": False, "message": "Unsupported option '%s'" % key}) + if DataStore.tasks[taskid].engine_process() is not None and not DataStore.tasks[taskid].engine_has_terminated(): + logger.warning("[%s] Scan already running" % taskid) + return jsonize({"success": False, "message": "Scan already running"}) + + message = validate_task_options(taskid, request.json, "scan_start") + if message: + return jsonize({"success": False, "message": message}) # Initialize sqlmap engine's options with user's provided options, if any for option, value in request.json.items(): @@ -601,7 +618,7 @@ def scan_data(taskid): json_data_message.append({"status": status, "type": content_type, "value": dejsonize(value)}) # Read all error messages from the IPC database - for error in DataStore.current_db.execute("SELECT error FROM errors WHERE taskid = ? ORDER BY id ASC", (taskid,)): + for error, in DataStore.current_db.execute("SELECT error FROM errors WHERE taskid = ? ORDER BY id ASC", (taskid,)): json_errors_message.append(error) logger.debug("(%s) Retrieved scan data and error messages" % taskid) @@ -689,11 +706,11 @@ def version(token=None): def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=RESTAPI_DEFAULT_ADAPTER, username=None, password=None, database=None): """ - REST-JSON API server + REST API server """ if not all((username, password)): - logger.critical("REST-JSON API server requires both username and password") + logger.critical("REST API server requires both username and password") DataStore.admin_token = encodeHex(os.urandom(16), binary=False) DataStore.username = username @@ -710,7 +727,7 @@ def server(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, adapter=REST s.bind((host, 0)) port = s.getsockname()[1] - logger.info("Running REST-JSON API server at '%s:%d'.." % (host, port)) + logger.info("Running REST API server at '%s:%d'.." % (host, port)) logger.info("Admin (secret) token: %s" % DataStore.admin_token) logger.debug("IPC database: '%s'" % Database.filepath) @@ -770,7 +787,7 @@ def _client(url, options=None): def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=None, password=None): """ - REST-JSON API client + REST API client """ DataStore.username = username @@ -784,14 +801,14 @@ def client(host=RESTAPI_DEFAULT_ADDRESS, port=RESTAPI_DEFAULT_PORT, username=Non logger.debug(dbgMsg) addr = "http://%s:%d" % (host, port) - logger.info("Starting REST-JSON API client to '%s'..." % addr) + logger.info("Starting REST API client to '%s'..." % addr) try: _client(addr) except Exception as ex: if not isinstance(ex, _urllib.error.HTTPError) or ex.code == _http_client.UNAUTHORIZED: errMsg = "There has been a problem while connecting to the " - errMsg += "REST-JSON API server at '%s' " % addr + errMsg += "REST API server at '%s' " % addr errMsg += "(%s)" % getSafeExString(ex) logger.critical(errMsg) return diff --git a/lib/utils/pivotdumptable.py b/lib/utils/pivotdumptable.py index 70d139ee244..d1f3b9eecfd 100644 --- a/lib/utils/pivotdumptable.py +++ b/lib/utils/pivotdumptable.py @@ -18,6 +18,7 @@ from lib.core.common import unArrayizeValue from lib.core.common import unsafeSQLIdentificatorNaming from lib.core.compat import xrange +from lib.core.convert import getConsoleLength from lib.core.convert import getUnicode from lib.core.data import conf from lib.core.data import kb @@ -58,7 +59,7 @@ def pivotDumpTable(table, colList, count=None, blind=True, alias=None): logger.info(infoMsg) for column in colList: - lengths[column] = len(column) + lengths[column] = getConsoleLength(column) entries[column] = [] return entries, lengths @@ -169,7 +170,7 @@ def _(column, pivotValue): value = "" if isNoneValue(value) else unArrayizeValue(value) - lengths[column] = max(lengths[column], len(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value)))) + lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value)))) entries[column].append(value) except KeyboardInterrupt: diff --git a/plugins/generic/entries.py b/plugins/generic/entries.py index bfbffc44129..0c6f3ea4f9c 100644 --- a/plugins/generic/entries.py +++ b/plugins/generic/entries.py @@ -319,7 +319,7 @@ def dumpTable(self, foundData=None): logger.warning(warnMsg) for column in colList: - lengths[column] = len(column) + lengths[column] = getConsoleLength(column) entries[column] = [] elif not isNumPosStrValue(count): @@ -361,7 +361,7 @@ def dumpTable(self, foundData=None): if column not in entries: entries[column] = BigArray() - lengths[column] = max(lengths[column], len(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value)))) + lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value)))) entries[column].append(value) except KeyboardInterrupt: @@ -432,7 +432,7 @@ def dumpTable(self, foundData=None): value = NULL if column in emptyColumns else inject.getValue(query, union=False, error=False, dump=True) value = '' if value is None else value - lengths[column] = max(lengths[column], len(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value)))) + lengths[column] = max(lengths[column], getConsoleLength(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value)))) entries[column].append(value) except KeyboardInterrupt: @@ -442,7 +442,7 @@ def dumpTable(self, foundData=None): logger.warning(warnMsg) for column, columnEntries in entries.items(): - length = max(lengths[column], len(column)) + length = max(lengths[column], getConsoleLength(column)) kb.data.dumpedTable[column] = {"length": length, "values": columnEntries} diff --git a/sqlmapapi.py b/sqlmapapi.py index 198e43c60c3..4714887f409 100755 --- a/sqlmapapi.py +++ b/sqlmapapi.py @@ -81,7 +81,7 @@ def modulePath(): def main(): """ - REST-JSON API main function + REST API main function """ dirtyPatches() @@ -95,10 +95,10 @@ def main(): # Parse command line options apiparser = ArgumentParser() - apiparser.add_argument("-s", "--server", help="Run as a REST-JSON API server", action="store_true") - apiparser.add_argument("-c", "--client", help="Run as a REST-JSON API client", action="store_true") - apiparser.add_argument("-H", "--host", help="Host of the REST-JSON API server (default \"%s\")" % RESTAPI_DEFAULT_ADDRESS, default=RESTAPI_DEFAULT_ADDRESS) - apiparser.add_argument("-p", "--port", help="Port of the REST-JSON API server (default %d)" % RESTAPI_DEFAULT_PORT, default=RESTAPI_DEFAULT_PORT, type=int) + apiparser.add_argument("-s", "--server", help="Run as a REST API server", action="store_true") + apiparser.add_argument("-c", "--client", help="Run as a REST API client", action="store_true") + apiparser.add_argument("-H", "--host", help="Host of the REST API server (default \"%s\")" % RESTAPI_DEFAULT_ADDRESS, default=RESTAPI_DEFAULT_ADDRESS) + apiparser.add_argument("-p", "--port", help="Port of the REST API server (default %d)" % RESTAPI_DEFAULT_PORT, default=RESTAPI_DEFAULT_PORT, type=int) apiparser.add_argument("--adapter", help="Server (bottle) adapter to use (default \"%s\")" % RESTAPI_DEFAULT_ADAPTER, default=RESTAPI_DEFAULT_ADAPTER) apiparser.add_argument("--database", help="Set IPC database filepath (optional)") apiparser.add_argument("--username", help="Basic authentication username") @@ -106,7 +106,7 @@ def main(): (args, _) = apiparser.parse_known_args() if hasattr(apiparser, "parse_known_args") else apiparser.parse_args() if (args.server or args.client) and not all((args.username, args.password)): - apiparser.error("--username and --password are mandatory for REST-JSON API server/client usage") + apiparser.error("--username and --password are mandatory for REST API server/client usage") # Start the client or the server if args.server: diff --git a/sqlmapapi.yaml b/sqlmapapi.yaml index da89c12ddc5..a5829d7a466 100644 --- a/sqlmapapi.yaml +++ b/sqlmapapi.yaml @@ -1,9 +1,9 @@ openapi: 3.0.3 info: - title: sqlmap REST-JSON API + title: sqlmap REST API version: "1.0.0" description: | - OpenAPI/Swagger specification for sqlmapapi.py, the sqlmap REST-JSON API server. + OpenAPI/Swagger specification for sqlmapapi.py, the sqlmap REST API server. This specification describes the API surface implemented by `lib/utils/api.py`. The API is expected to be protected with HTTP Basic authentication when started @@ -232,7 +232,7 @@ paths: parameters: - $ref: "#/components/parameters/TaskId" requestBody: - required: true + required: false content: application/json: schema: @@ -272,13 +272,7 @@ paths: Sets one or more options on a task. Values are persisted in the task option object and are used when the scan is started. - Hardened behavior: options listed in `x-sqlmap-unsupported-options` should be - rejected here with `success: false`, matching `/scan/{taskid}/start`. - x-sqlmap-unsupported-options: - - sqlShell - - wizard - - evalCode - - alert + Unsupported, read-only, and unknown options are rejected with `success: false`. parameters: - $ref: "#/components/parameters/TaskId" requestBody: @@ -315,6 +309,10 @@ paths: value: success: false message: "Unsupported option 'evalCode'" + unknownOption: + value: + success: false + message: "Unknown option 'doesNotExist'" "401": $ref: "#/components/responses/Unauthorized" @@ -327,13 +325,8 @@ paths: Applies the provided options to the task and starts sqlmap in a separate process. The response contains the spawned engine process ID. - Current API behavior rejects options listed in `x-sqlmap-unsupported-options` - when they are supplied in this request body. - x-sqlmap-unsupported-options: - - sqlShell - - wizard - - evalCode - - alert + Unsupported, read-only, and unknown options are rejected with `success: false`. + Starting a scan for an already running task returns `success: false`. parameters: - $ref: "#/components/parameters/TaskId" requestBody: @@ -364,6 +357,14 @@ paths: value: success: false message: "Unsupported option 'evalCode'" + unknownOption: + value: + success: false + message: "Unknown option 'doesNotExist'" + scanAlreadyRunning: + value: + success: false + message: Scan already running invalidJson: value: success: false @@ -647,10 +648,6 @@ components: message: Invalid start or end value, must be digits schemas: - SuccessFlag: - type: boolean - description: Indicates whether the API action succeeded. - ErrorResponse: type: object required: [success, message] @@ -726,9 +723,9 @@ components: OptionValue: description: Value accepted by sqlmap options. The exact type depends on the option. - nullable: true - oneOf: + anyOf: - type: string + nullable: true - type: boolean - type: integer - type: number @@ -741,8 +738,8 @@ components: type: object description: | Dynamic object containing sqlmap option names and values. Option names map to - sqlmap's internal option dictionary. Unsupported REST API options should be - rejected by endpoints that accept this object. + sqlmap's internal option dictionary. Unsupported, read-only, and unknown + options are rejected by endpoints that accept this object. additionalProperties: $ref: "#/components/schemas/OptionValue" example: @@ -764,8 +761,7 @@ components: OptionGetRequest: type: array - description: List of option names to return. - minItems: 1 + description: List of option names to return. Empty or missing input returns an empty options object. items: type: string minLength: 1 @@ -826,7 +822,16 @@ components: description: Numeric content type stored by sqlmap. example: 0 value: - nullable: true + anyOf: + - type: string + nullable: true + - type: boolean + - type: integer + - type: number + - type: array + items: {} + - type: object + additionalProperties: true description: JSON-decoded scan output value. Shape depends on the content type. additionalProperties: true