diff options
Diffstat (limited to 'ncat/scripts')
-rw-r--r-- | ncat/scripts/chargen.lua | 7 | ||||
-rw-r--r-- | ncat/scripts/conditional.lua | 51 | ||||
-rw-r--r-- | ncat/scripts/date.lua | 3 | ||||
-rw-r--r-- | ncat/scripts/discard.lua | 11 | ||||
-rw-r--r-- | ncat/scripts/echo.lua | 16 | ||||
-rw-r--r-- | ncat/scripts/hello-luaexec.lua | 4 | ||||
-rw-r--r-- | ncat/scripts/httpd.lua | 402 | ||||
-rw-r--r-- | ncat/scripts/log_ips.sh | 10 | ||||
-rwxr-xr-x | ncat/scripts/p0fme.py | 97 | ||||
-rw-r--r-- | ncat/scripts/whatismyip.lua | 4 |
10 files changed, 605 insertions, 0 deletions
diff --git a/ncat/scripts/chargen.lua b/ncat/scripts/chargen.lua new file mode 100644 index 0000000..70b937f --- /dev/null +++ b/ncat/scripts/chargen.lua @@ -0,0 +1,7 @@ +--chargen.lua - implements the RFC 864 CHARGEN service which basically spams +--the remote user until he decides to close the connection. + +while true do + print "chargen" +end + diff --git a/ncat/scripts/conditional.lua b/ncat/scripts/conditional.lua new file mode 100644 index 0000000..76817b2 --- /dev/null +++ b/ncat/scripts/conditional.lua @@ -0,0 +1,51 @@ +--This is another --lua-exec demo. It displays a menu to a user, waits for her +--input and makes a decision according to what the user entered. All happens +--in an infinite loop. + +--This function reads a line of at most 8096 bytes (or whatever the first +--parameter says) from standard input. Returns the string and a boolean value +--that is true if we hit the newline (defined as "\n") or false if the line had +--to be truncated. This is here because io.stdin:read("*line") could lead to +--memory exhaustion if we received gigabytes of characters with no newline. +function read_line(max_len) + local ret = "" + for i = 1, (max_len or 8096) do + local chr = io.read(1) + if chr == "\n" then + return ret, true + end + ret = ret .. chr + end + + return ret, false +end + +while true do + + print "Here's a menu for you: " + print "1. Repeat the menu." + print "0. Exit." + + io.write "Please enter your choice: " + io.flush(io.stdout) + i = read_line() + + --WARNING! Without this line, the script will go into an infinite loop + --that keeps consuming system resources when the connection gets broken. + --Ncat's subprocesses are NOT killed in this case! + if i == nil then + break + end + + print("You wrote: ", i, ".") + + if i == "0" then + break + elseif i == "1" then + print "As you wish." + else + print "No idea what you meant. Please try again." + end + + print() --print a newline +end diff --git a/ncat/scripts/date.lua b/ncat/scripts/date.lua new file mode 100644 index 0000000..a7116ef --- /dev/null +++ b/ncat/scripts/date.lua @@ -0,0 +1,3 @@ +--Emulates the daytime service (RFC 867). + +print(os.date()) diff --git a/ncat/scripts/discard.lua b/ncat/scripts/discard.lua new file mode 100644 index 0000000..6b386f6 --- /dev/null +++ b/ncat/scripts/discard.lua @@ -0,0 +1,11 @@ +--This script reads data from the standard input and discards them. + +while true do + + data = io.stdin:read(512) + + if data == nil then + break + end + +end diff --git a/ncat/scripts/echo.lua b/ncat/scripts/echo.lua new file mode 100644 index 0000000..3bfea90 --- /dev/null +++ b/ncat/scripts/echo.lua @@ -0,0 +1,16 @@ +--Emulates the RFC 862 echo service, behaving like Unix's "cat" tool. + +while true do + + --We're reading in 1-byte chunks because calls like io.stdin:read(512) would + --wait for full 512 bytes of data before continuing. + data = io.stdin:read(1) + + if data == nil then + break + end + + io.stdout:write(data) + io.stdout:flush() + +end diff --git a/ncat/scripts/hello-luaexec.lua b/ncat/scripts/hello-luaexec.lua new file mode 100644 index 0000000..31bd498 --- /dev/null +++ b/ncat/scripts/hello-luaexec.lua @@ -0,0 +1,4 @@ +--This is a --lua-exec "Hello world" example. In order to send to a client, +--all you need to do is output it to the standard output. + +print "Hello, world!" diff --git a/ncat/scripts/httpd.lua b/ncat/scripts/httpd.lua new file mode 100644 index 0000000..3837e32 --- /dev/null +++ b/ncat/scripts/httpd.lua @@ -0,0 +1,402 @@ +--httpd.lua - a dead simple HTTP server. Expects GET requests and serves files +--matching these requests. Can guess mime based on an extension too. Currently +--disallows any filenames that start or end with "..". + +------------------------------------------------------------------------------ +-- Configuration section -- +------------------------------------------------------------------------------ + +server_headers = { + ["Server"] = "Ncat --lua-exec httpd.lua", + ["Connection"] = "close", +} + +function guess_mime(resource) + if string.sub(resource, -5) == ".html" then return "text/html" end + if string.sub(resource, -4) == ".htm" then return "text/html" end + return "application/octet-stream" +end + +------------------------------------------------------------------------------ +-- End of configuration section -- +------------------------------------------------------------------------------ + +function print_rn(str) + io.stdout:write(str .. "\r\n") + io.stdout:flush() +end + +function debug(str) + io.stderr:write("[" .. os.date() .. "] ") + io.stderr:write(str .. "\n") + io.stderr:flush() +end + +function url_decode(str) + --taken from here: http://lua-users.org/wiki/StringRecipes + return str:gsub("%%(%x%x)", + function(h) return string.char(tonumber(h,16)) end) +end + +--Read a line of at most 8096 bytes (or whatever the first parameter says) +--from standard input. Returns the string and a boolean value that is true if +--we hit the newline (defined as "\n") or false if the line had to be +--truncated. This is here because io.stdin:read("*line") could lead to memory +--exhaustion if we received gigabytes of characters with no newline. +function read_line(max_len) + local ret = "" + for i = 1, (max_len or 8096) do + local chr = io.read(1) + if chr == "\n" then + return ret, true + end + ret = ret .. chr + end + + return ret, false +end + +--The following function and variables was translated from Go to Lua. The +--original code can be found here: +-- +--http://golang.org/src/pkg/unicode/utf8/utf8.go#L45 +local surrogate_min = 0xD800 +local surrogate_max = 0xDFFF + +local t1 = 0x00 -- 0000 0000 +local tx = 0x80 -- 1000 0000 +local t2 = 0xC0 -- 1100 0000 +local t3 = 0xE0 -- 1110 0000 +local t4 = 0xF0 -- 1111 0000 +local t5 = 0xF8 -- 1111 1000 + +local maskx = 0x3F -- 0011 1111 +local mask2 = 0x1F -- 0001 1111 +local mask3 = 0x0F -- 0000 1111 +local mask4 = 0x07 -- 0000 0111 + +local char1_max = 0x7F -- (1<<7) - 1 +local char2_max = 0x07FF -- (1<<11) - 1 +local char3_max = 0xFFFF -- (1<<16) - 1 + +local max_char = 0x10FFFF -- \U0010FFFF + +function get_next_char_len(p) + local n = p:len() + local c0 = p:byte(1) + + --1-byte, 7-bit sequence? + if c0 < tx then + return 1 + end + + --unexpected continuation byte? + if c0 < t2 then + return nil + end + + --need first continuation byte + if n < 2 then + return nil + end + local c1 = p:byte(2) + if c1 < tx or t2 <= c1 then + return nil + end + + --2-byte, 11-bit sequence? + if c0 < t3 then + local l1 = (c0 & mask2) << 6 + local l2 = c1 & maskx + local r = l1 | l2 + if r <= char1_max then + return nil + end + return 2 + end + + --need second continuation byte + if n < 3 then + return nil + end + local c2 = p:byte(3) + if c2 < tx or t2 <= c2 then + return nil + end + + --3-byte, 16-bit sequence? + if c0 < t4 then + local l1 = (c0 & mask3) << 12 + local l2 = (c1 & maskx) << 6 + local l3 = c2 & maskx + local r = l1 | l2 | l3 + if r <= char2_max then + return nil + end + if surrogate_min <= r and r <= surrogate_max then + return nil + end + return 3 + end + + --need third continuation byte + if n < 4 then + return nil + end + local c3 = p:byte(4) + if c3 < tx or t2 <= c3 then + return nil + end + + --4-byte, 21-bit sequence? + if c0 < t5 then + local l1 = (c0 & mask4) << 18 + local l2 = (c1 & maskx) << 12 + local l3 = (c2 & maskx) << 6 + local l4 = c3 & maskx + local r = l1 | l2 | l3 | l4 + if r <= char3_max or max_char < r then + return nil + end + return 4 + end + + --error + return nil +end + +function validate_utf8(s) + local i = 1 + local len = s:len() + while i <= len do + local size = get_next_char_len(s:sub(i)) + if size == nil then + return false + end + i = i + size + end + return true +end + +--Returns a table containing the list of directories resulting from splitting +--the argument by '/'. +function split_path(path) + --[[ + for _, v in pairs({"/a/b/c", "a/b/c", "//a/b/c", "a/b/c/", "a/b/c//"}) do + print(v,table.concat(split_path(v), ',')) + end + + -- /a/b/c ,a,b,c + -- a/b/c a,b,c + -- //a/b/c ,,a,b,c + -- a/b/c/ a,b,c + -- a/b/c// a,b,c, + ]] + local ret = {} + local j = 0 + for i=1, path:len() do + if path:sub(i,i) == '/' then + if j == 0 then + ret[#ret+1] = path:sub(1, i-1) + else + ret[#ret+1] = path:sub(j+1, i-1) + end + j = i + end + end + if j ~= path:len() then + ret[#ret+1] = path:sub(j+1, path:len()) + end + return ret +end + + +function is_path_valid(resource) + --remove the beginning slash + resource = string.sub(resource, 2, string.len(resource)) + + --Windows drive names are not welcome. + if resource:match("^([a-zA-Z]):") then + return false + end + + --if it starts with a dot or a slash or a backslash, forbid any acccess to it. + local first_char = resource:sub(1, 1) + + if first_char == "." then + return false + end + + if first_char == "/" then + return false + end + + if resource:find("\\") then + return false + end + + for _, directory in pairs(split_path(resource)) do + if directory == '' then + return false + end + + if directory == '..' then + return false + end + end + + return true +end + +--Make a response, output it and stop execution. +-- +--It takes an associative array with three optional keys: status (status line) +--and headers, which lists all additional headers to be sent. You can also +--specify "data" - either a function that is expected to return nil at some +--point or a plain string. +function make_response(params) + + --Print the status line. If we got none, assume it's all okay. + if not params["status"] then + params["status"] = "HTTP/1.1 200 OK" + end + print_rn(params["status"]) + + --Send the date. + print_rn("Date: " .. os.date("!%a, %d %b %Y %H:%M:%S GMT")) + + --Send the server headers as described in the configuration. + for key, value in pairs(server_headers) do + print_rn(("%s: %s"):format(key, value)) + end + + --Now send the headers from the parameter, if any. + if params["headers"] then + for key, value in pairs(params["headers"]) do + print_rn(("%s: %s"):format(key, value)) + end + end + + --If there's any data, check if it's a function. + if params["data"] then + + if type(params["data"]) == "function" then + + print_rn("") + debug("Starting buffered output...") + + --run the function and print its contents, until we hit nil. + local f = params["data"] + while true do + local ret = f() + if ret == nil then + debug("Buffered output finished.") + break + end + io.stdout:write(ret) + io.stdout:flush() + end + + else + + --It's a plain string. Send its length and output it. + debug("Just printing the data. Status='" .. params["status"] .. "'") + print_rn("Content-length: " .. params["data"]:len()) + print_rn("") + io.stdout:write(params["data"]) + io.stdout:flush() + + end + else + print_rn("") + end + + os.exit(0) +end + +function make_error(error_str) + make_response({ + ["status"] = "HTTP/1.1 "..error_str, + ["headers"] = {["Content-type"] = "text/html"}, + ["data"] = "<h1>"..error_str.."</h1>", + }) +end + +do_400 = function() make_error("400 Bad Request") end +do_403 = function() make_error("403 Forbidden") end +do_404 = function() make_error("404 Not Found") end +do_405 = function() make_error("405 Method Not Allowed") end +do_414 = function() make_error("414 Request-URI Too Long") end + +------------------------------------------------------------------------------ +-- End of library section -- +------------------------------------------------------------------------------ + +input, success = read_line() + +if not success then + do_414() +end + +if input:sub(-1) == "\r" then + input = input:sub(1,-2) +end + +--We assume that: +-- * a method is alphanumeric uppercase, +-- * resource may contain anything that's not a space, +-- * protocol version is followed by a single space. +method, resource, protocol = input:match("([A-Z]+) ([^ ]+) ?(.*)") + +if resource:find(string.char(0)) ~= nil then + do_400() +end + +if not validate_utf8(resource) then + do_400() +end + +if method ~= "GET" then + do_405() +end + +while true do + + input = read_line() + if input == "" or input == "\r" then + break + end +end + +debug("Got a request for '" .. resource + .. "' (urldecoded: '" .. url_decode(resource) .. "').") +resource = url_decode(resource) + +--make sure that the resource starts with a slash. +if resource:sub(1, 1) ~= '/' then + do_400() --could probably use a fancier error here. +end + +if not is_path_valid(resource) then + do_403() +end + +--try to make all file openings from now on relative to the working directory. +resource = "./" .. resource + +--If it's a directory, try to load index.html from it. +if resource:sub(-1) == "/" then + resource = resource .. '/index.html' +end + +--try to open the file... +f = io.open(resource, "rb") +if f == nil then + do_404() --opening file failed, throw a 404. +end + +--and output it all. +make_response({ + ["data"] = function() return f:read(1024) end, + ["headers"] = {["Content-type"] = guess_mime(resource)}, +}) diff --git a/ncat/scripts/log_ips.sh b/ncat/scripts/log_ips.sh new file mode 100644 index 0000000..8b0f0e5 --- /dev/null +++ b/ncat/scripts/log_ips.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +LOGFILE="log_ips.log" + +MSG="[`date`] Incoming connection from $NCAT_REMOTE_ADDR:$NCAT_REMOTE_PORT" + +echo $MSG >&2 +echo $MSG >> $LOGFILE + +echo "Yeah, hi." diff --git a/ncat/scripts/p0fme.py b/ncat/scripts/p0fme.py new file mode 100755 index 0000000..5f4d42e --- /dev/null +++ b/ncat/scripts/p0fme.py @@ -0,0 +1,97 @@ +#!/usr/bin/python + +from __future__ import print_function # logging, python2-only. + +""" +A script that reads data generated by p0f -f p0f.log, looking for all entries +about an IP read from NCAT_REMOTE_ADDR environment variable. Then it prints out +all the information it has found. To try it out, run "p0f -i any -o p0f.log" +and ncat -l -k --sh-exec "python p0fme.py". + +Script tested under Python versions 2.7 and 3.3. +""" + +P0F_LOG_FILE = "p0f.log" + +import datetime # logging +import sys # logging +import os # environ +import time # sleeping to wait for data +import sys # to flush STDOUT + + +def expand_ipv6(ip): + """ + Expands short IPv6 address like ::1 into an expanded form without trailing + zeros. Copied from: + + http://svn.python.org/projects/python/tags/r31b1/Lib/ipaddr.py + + (Py3 standard library; added some modifications to match p0f's output data) + """ + + new_ip = [] + hextet = ip.split('::') + sep = len(hextet[0].split(':')) + len(hextet[1].split(':')) + new_ip = hextet[0].split(':') + + for _ in range(8 - sep): + new_ip.append('0') + new_ip += hextet[1].split(':') + + # Now need to make sure every hextet is 4 lower case characters. + # If a hextet is < 4 characters, we've got missing leading 0's. + ret_ip = [] + for hextet in new_ip: + if hextet == '': + hextet = '0' + ret_ip.append(hextet.lower()) + return ':'.join(ret_ip) + + +def split_by_equals(str_): + ret = str_.split('=') + return ret[0], ''.join(ret[1:]) + +if __name__ == "__main__": + try: + ip = os.environ['NCAT_REMOTE_ADDR'] + if os.environ['NCAT_PROTO'] != 'TCP': + sys.exit("ERROR: This script works for TCP servers only!") + except KeyError: + sys.exit("ERROR: This script has to be run from inside of Ncat.") + print("[%s] Got a request from %s" % ( + datetime.datetime.now().isoformat(' '), ip), file=sys.stderr) + + print("Hold on, I'm collecting data on you...") + sys.stdout.flush() + time.sleep(3.0) + + if ':' in ip: # We need to expand IPv6 addresses in a specific way. + ip = expand_ipv6(ip) + result = {} + + # Reading the log backward will give us more recent results. + for line in reversed(open(P0F_LOG_FILE).readlines()): + + without_date = line.split('] ') + if without_date == ['\n']: + continue + without_date = ''.join(without_date[1:]) + + # Create a key-value dictionary out of the '|'-separated substrings. + properties = dict(map(split_by_equals, without_date.split('|'))) + + if not properties['cli'].startswith(ip): + continue # Not the IP we're looking for, check next one. + + for key in properties: + if not key in result or result[key] == '???': + result[key] = properties[key] + + if not result: + print("Got nothing on you. Try again and I will, though.") + + # Now that we've finished, print out the results. + for key in sorted(result): + print("%s: %s" % (key, result[key].rstrip())) diff --git a/ncat/scripts/whatismyip.lua b/ncat/scripts/whatismyip.lua new file mode 100644 index 0000000..13ade22 --- /dev/null +++ b/ncat/scripts/whatismyip.lua @@ -0,0 +1,4 @@ +--A "what is my IP" service code. Since most web browsers put up with servers +--not sending proper HTTP headers, you can simply query the service with it. + +print(os.getenv "NCAT_REMOTE_ADDR") |