Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TFTP Version Script #548

Closed
wants to merge 21 commits into from
Closed

TFTP Version Script #548

wants to merge 21 commits into from

Conversation

mogigoma
Copy link

@mogigoma mogigoma commented Sep 10, 2016

As suggested here, I've made a TFTP version script.

The script could use a lot more testing on different targets. I have successfully tested against a new free download of SolarWinds TFTP and against non-TFTP services and unrecognized TFTP services.

The way matching against fingerprints occurs is the simplest thing I could think of. If there's a good way to either parse a file of fingerprints to generate the hardwired table, that would be good. Also, regexes are not in use, but could be added if necessary. Couldn't find any PCRE exposed, only LPeg which doesn't match up very well with the fingerprint format. I also removed the probe matches. Not sure if that was appropriate.

Finally, if recvfrom() was exposed to the NSE that would be desirable. I worry about receiving packets from other hosts unintentionally, or receiving packets during the brief window during which we're swapping sockets. Not sure if Nmap has protections against that.

Anyways, if you want things changed or hit bugs, let me know and I'll fix them as soon as I can. Next up: nbd-info!

Copy link

@dmiller-nmap dmiller-nmap left a comment

Exciting! I look forward to testing this, and I hope we can gather a good set of fingerprints to cover lots of different TFTP servers.

@@ -11691,22 +11691,6 @@ match msrpc m|^\x04\x06\0\0\x10\0\0\0\0\0\0\0|

match netprobe m|^\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0$| p/Mega System Technologies NetProbe Lite environmental sensor/ d/specialized/

match tftp m|^\0\x05\0\x02\0The IP address is not in the range of allowable addresses\.\0| p/SolarWinds tftpd/ i/IP disallowed/ o/Windows/ cpe:/a:solarwinds:tftp_server/ cpe:/o:microsoft:windows/a

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should leave these match lines here, since they are useful for users who do not use NSE. The tftp-version script could even check to be sure there hasn't already been a match from -sV before proceeding.

Copy link
Author

@mogigoma mogigoma Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restored probes.

local OPCODE_DATA = 3
local OPCODE_ERROR = 5

local responses = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could load these fingerprints from an external file like we do for http-default-accounts or ike-version. Need comments to annotate what these fields are for and what format to use. Probably a more compact representation would be helpful, too:

{
  2, "The IP address is not in the range of allowable addresses.",
  {
    p="SolarWinds tftpd", i="IP disallowed", o="Windows",
    cpe={"a:solarwinds:tftp_server", "o:microsoft:windows/a",}
  }
}

or similar. Table keys that are valid identifiers can be specified without the ["brackets-and-quotes"] syntax.

Copy link
Author

@mogigoma mogigoma Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, compacted slightly and followed the example of ike-version.

}

local record_match = function(port, sw)
if sw.p then

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to peacefully coexist with version info set by other means (i.e. nmap-service-probes). Check out how other version-category scripts do this: http-server-header, snmp-info, etc. Decide whether to append or replace the existing version info.

Copy link
Author

@mogigoma mogigoma Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, should now only append to the version information.

-- should respond back to the port matching the sending script.
--
-- Note that due to API limitations, we can't know if the response came from
-- the host we sent the request to, so we must assume it does.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could do socket:get_info() after this receive to verify this? I agree, there are better underlying C calls for doing this, but to get it working with what we have, that could be an improvement. Also, we should be able to use stdnse.get_timeout to improve the timeout for the receive from the default 30 seconds. This would allow us to retransmit a couple times if necessary without taking too long.

Copy link
Author

@mogigoma mogigoma Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a socket:get_info() check. Not sure what you mean with setting the timeout. Do you mean lower it to something like 5 seconds, and sending something say 3 times if there's no response?

-- Parse the response.
local pkt = parse(res)
if not pkt then
return nil

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some debug statements for these nil returns would be helpful to know at what point we encountered a problem.

Copy link
Author

@mogigoma mogigoma Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, all unsuccessful runs should now have a debug message.


author = "Mak Kolybabi <mak@kolybabi.com>"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "safe", "version"}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think "discovery" is appropriate, because we're not discovering anything, only determining version, which is already covered by "version." Will have to think about "default" and "safe" vs "intrusive" because we're requesting a nonexistent file. How big of a deal is that? I'd guess not many places check TFTP logs, but do you have a better idea?

Copy link
Author

@mogigoma mogigoma Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels non-intrusive to me because:

  1. Most other intrusive scripts are exploits, bruteforcing, or enumeration
  2. There's a small chance of accessing sensitive information due to the random filename
  3. The traffic generated is very minimal


-- Populate the service information by referencing our list of software
-- responses.
identify_software(pkt, port)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about unidentified software? Have identify_software return the match info instead of setting it in the port object directly. Then do the port.version setting here in the action function; if there's no match, return output showing the message and code that were returned and requesting user to submit it to dev@nmap.org.

Copy link
Author

@mogigoma mogigoma Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

@dmiller-nmap
Copy link

dmiller-nmap commented Oct 21, 2016

Been doing some testing, here are my observations:

  1. Sometimes (localhost with -sV) the script misses the response because it happens while the script is waiting on something else and has not bound the new socket to the local port. This could probably be fixed by the recvfrom binding you mentioned, but I'm not sure. What do you think?
  2. The format for fingerprints could stand to be documented better. Pretty simple, I figured it out, but a comment on the first one could be helpful.
  3. When run with -sV, the script output is not shown, so the user doesn't get the prompt to submit. We can use stdnse.verbose to print it instead or in addition.

And here's another fingerprint:

table.insert(fingerprints, {
    1, "File not found", {
      p = "Netkit tftpd or atftpd",
      cpe = {"a:netkit:netkit", "a:lefebvre:atftpd"},                                           
  }})

@mogigoma
Copy link
Author

mogigoma commented Nov 1, 2016

Added the fingerprint format info, the new fingerprint, and the stdnse.verbose() fingerprint submission info.

Assuming I'm reading the man pages right, switching to using a single socket, then calling sendto() followed by recvfrom() will fix the race condition.

@dmiller-nmap
Copy link

dmiller-nmap commented Sep 28, 2022

Just pushed this script! Made some changes based on research into #608 as well as trying to support more indicators for the future (other opcodes, malformed packets, etc. that might be returned by clumsy tftp servers). Looking forward to using it!

@nmap-bot nmap-bot closed this in 894bfd4 Sep 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants