From 0d47952611198ef6b1163f366dc03922d20b1475 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 09:42:04 +0200 Subject: Adding upstream version 7.94+git20230807.3be01efb1+dfsg. Signed-off-by: Daniel Baumann --- nselib/unpwdb.lua | 338 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 nselib/unpwdb.lua (limited to 'nselib/unpwdb.lua') diff --git a/nselib/unpwdb.lua b/nselib/unpwdb.lua new file mode 100644 index 0000000..3452639 --- /dev/null +++ b/nselib/unpwdb.lua @@ -0,0 +1,338 @@ +--- +-- Username/password database library. +-- +-- The usernames and passwords functions return +-- multiple values for use with exception handling via +-- nmap.new_try. The first value is the Boolean success +-- indicator, the second value is the closure. +-- +-- The closures can take an argument of "reset" to rewind the list +-- to the beginning. +-- +-- To avoid taking a long time against slow services, the closures will +-- stop returning values (start returning nil) after a +-- certain time. The time depends on the timing template level, and is +-- * -T3 or less: 10 minutes +-- * -T4: 5 minutes +-- * -T5: 3 minutes +-- Time limits are increased by 50% if a custom username or password +-- database is used with the userdb or passdb +-- script arguments. You can control the time limit directly with the +-- unpwdb.timelimit script argument. Use +-- unpwdb.timelimit=0 to disable the time limit. +-- +-- You can select your own username and/or password database to read from with +-- the script arguments userdb and passdb, +-- respectively. Comments are allowed in these files, prefixed with +-- "#!comment:". Comments cannot be on the same line as a +-- username or password because this leaves too much ambiguity, e.g. does the +-- password in "mypass #!comment: blah" contain a space, two +-- spaces, or do they just separate the password from the comment? +-- +-- @usage +-- require("unpwdb") +-- +-- local usernames, passwords +-- local try = nmap.new_try() +-- +-- usernames = try(unpwdb.usernames()) +-- passwords = try(unpwdb.passwords()) +-- +-- for password in passwords do +-- for username in usernames do +-- -- Do something with username and password. +-- end +-- usernames("reset") +-- end +-- +-- @usage +-- nmap --script-args userdb=/tmp/user.lst +-- nmap --script-args unpwdb.timelimit=10m +-- +-- @args userdb The filename of an alternate username database. Default: nselib/data/usernames.lst +-- @args passdb The filename of an alternate password database. Default: nselib/data/passwords.lst +-- @args unpwdb.userlimit The maximum number of usernames +-- usernames will return (default unlimited). +-- @args unpwdb.passlimit The maximum number of passwords +-- passwords will return (default unlimited). +-- @args unpwdb.timelimit The maximum amount of time that any iterator will run +-- before stopping. The value is in seconds by default and you can follow it +-- with ms, s, m, or h for +-- milliseconds, seconds, minutes, or hours. For example, +-- unpwdb.timelimit=30m or unpwdb.timelimit=.5h for +-- 30 minutes. The default depends on the timing template level (see the module +-- description). Use the value 0 to disable the time limit. +-- @author Kris Katterjohn 06/2008 +-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html + +local io = require "io" +local nmap = require "nmap" +local os = require "os" +local stdnse = require "stdnse" +local datetime = require "datetime" +_ENV = stdnse.module("unpwdb", stdnse.seeall) + +local usertable = {} +local passtable = {} + +local customdata = false + +-- So I don't have to type as much :) +local args = nmap.registry.args + +local userfile = function() + if args.userdb then + customdata = true + return args.userdb + end + + return nmap.fetchfile("nselib/data/usernames.lst") +end + +local passfile = function() + if args.passdb then + customdata = true + return args.passdb + end + + return nmap.fetchfile("nselib/data/passwords.lst") +end + +local filltable = function(filename, table) + if #table ~= 0 then + return true + end + + local file, err = io.open(filename, "r") + + if not file then + return false, err + end + + for l in file:lines() do + -- Comments takes up a whole line + if not l:match("#!comment:") then + table[#table + 1] = l + end + end + + file:close() + + return true +end + +table_iterator = function(table) + local i = 1 + + return function(cmd) + if cmd == "reset" then + i = 1 + return + end + local elem = table[i] + if elem then i = i + 1 end + return elem + end +end + +--- Returns the suggested number of seconds to attempt a brute force attack +-- +-- Based on the unpwdb.timelimit script argument, Nmap's timing +-- values (-T4 etc.) and whether or not a user-defined list is +-- used. +-- +-- You can use the script argument notimelimit to make this +-- function return nil, which means the brute-force should run +-- until the list is empty. If notimelimit is not used, be sure to +-- still check for nil return values on the above two functions in +-- case you finish before the time limit is up. +timelimit = function() + -- If we're reading from a user-defined username or password list, + -- we'll give them a timeout 1.5x the default. If the "notimelimit" + -- script argument is used, we return nil. + + -- Easy enough + if args.notimelimit then + return nil + end + if args["unpwdb.timelimit"] then + local limit, err = stdnse.parse_timespec(args["unpwdb.timelimit"]) + if not limit then + error(err) + end + return limit + end + + local t = nmap.timing_level() + if t <= 3 then + return (customdata and 900) or 600 + elseif t == 4 then + return (customdata and 450) or 300 + elseif t == 5 then + return (customdata and 270) or 180 + end +end + +--- Returns a function closure which returns a new username with every call +-- until the username list is exhausted (in which case it returns +-- nil). +-- @return boolean Status. +-- @return function The usernames iterator. +local usernames_raw = function() + local path = userfile() + + if not path then + return false, "Cannot find username list" + end + + local status, err = filltable(path, usertable) + if not status then + return false, ("Error parsing username list: %s"):format(err) + end + + return true, table_iterator(usertable) +end + +--- Returns a function closure which returns a new password with every call +-- until the password list is exhausted (in which case it returns +-- nil). +-- @return boolean Status. +-- @return function The passwords iterator. +local passwords_raw = function() + local path = passfile() + + if not path then + return false, "Cannot find password list" + end + + local status, err = filltable(path, passtable) + if not status then + return false, ("Error parsing password list: %s"):format(err) + end + + return true, table_iterator(passtable) +end + +--- Wraps time and count limits around an iterator. +-- +-- When either limit expires, starts returning nil. Calling the +-- iterator with an argument of "reset" resets the count. +-- @param time_limit Time limit in seconds. Use 0 or nil for no limit. +-- @param count_limit Count limit in seconds. Use 0 or nil for no limit. +-- @param label A string describing the iterator, to be used in verbose print messages. +-- @return boolean Status. +-- @return function The wrapped iterator. +limited_iterator = function(iterator, time_limit, count_limit, label) + time_limit = (time_limit and time_limit > 0) and time_limit + count_limit = (count_limit and count_limit > 0) and count_limit + + local start = os.time() + local count = 0 + label = label or "limited_iterator" + return function(cmd) + if cmd == "reset" then + count = 0 + else + count = count + 1 + end + if count_limit and count > count_limit then + stdnse.verbose1("%s: Count limit %d exceeded.", label, count_limit) + return + end + if time_limit and os.time() - start >= time_limit then + stdnse.verbose1("%s: Time limit %s exceeded.", label, datetime.format_time(time_limit)) + return + end + return iterator(cmd) + end +end + +--- Returns a function closure which returns a new password with every call +-- until the username list is exhausted or either limit expires (in which cases +-- it returns nil). +-- @param time_limit Time limit in seconds. Use 0 for no limit. +-- @param count_limit Count limit in seconds. Use 0 for no limit. +-- @return boolean Status. +-- @return function The usernames iterator. +usernames = function(time_limit, count_limit) + local status, iterator + + status, iterator = usernames_raw() + if not status then + return false, iterator + end + + time_limit = time_limit or timelimit() + if not count_limit and args["unpwdb.userlimit"] then + count_limit = tonumber(args["unpwdb.userlimit"]) + end + + return true, limited_iterator(iterator, time_limit, count_limit, "usernames") +end + +--- Returns a function closure which returns a new password with every call +-- until the password list is exhausted or either limit expires (in which cases +-- it returns nil). +-- @param time_limit Time limit in seconds. Use 0 for no limit. +-- @param count_limit Count limit in seconds. Use 0 for no limit. +-- @return boolean Status. +-- @return function The passwords iterator. +passwords = function(time_limit, count_limit) + local status, iterator + + status, iterator = passwords_raw() + if not status then + return false, iterator + end + + time_limit = time_limit or timelimit() + if not count_limit and args["unpwdb.passlimit"] then + count_limit = tonumber(args["unpwdb.passlimit"]) + end + + return true, limited_iterator(iterator, time_limit, count_limit, "passwords") +end + +--- Returns a new iterator that iterates through its consecutive iterators, +-- basically concatenating them. +-- @param iter1 First iterator to concatenate. +-- @param iter2 Second iterator to concatenate. +-- @return function The concatenated iterators. +function concat_iterators (iter1, iter2) + local function helper (next_iterator, command, first, ...) + if first ~= nil then + return first, ... + elseif next_iterator ~= nil then + return helper(nil, command, next_iterator(command)) + end + end + local function iterator (command) + if command == "reset" then + iter1 "reset" + iter2 "reset" + else + return helper(iter2, command, iter1(command)) + end + end + return iterator +end + +--- Returns a new iterator that filters its results based on the filter. +-- @param iterator Iterator that needs to be filtered +-- @param filter Function that returns bool, which serves as a filter +-- @return function The filtered iterator. +function filter_iterator (iterator, filter) + return function (command) + if command == "reset" then + iterator "reset" + else + local val = iterator(command) + while val and not filter(val) do + val = iterator(command) + end + return val + end + end +end + +return _ENV; -- cgit v1.2.3