Lets have some fun with nmap...
This document is a Jupyter Notebook running Python Kernel, if you wish to run this notebook you will need to install Jupyter, Python3 and Nmap. Some commands are Unix specific so you may need to do a bit of translation if your running under Windows. The Jupyter Puthon Kernel allows running of bash commands by prepending a exclemation point (!) in front of the command or by prepending a code block with %%bash, so if your just following along in a lowly terminal dont include those when you copy and paste.
Nmap is a flexible, extensible network scanning engine. Without even touching on the extensible bits nmap contains many sophisticated useful features. The extensibility parts make nmap a eve more powerful scanning enging but extends it's capabilities in to the realm of vulnerability scanning, network toolkits and exploit delivery.
Right so first thing is RTFM which on UNIX systems is the venerable manpage.
man nmap
NMAP(1) Nmap Reference Guide NMAP(1)
NAME
nmap - Network exploration tool and security / port scanner
SYNOPSIS
nmap [Scan Type...] [Options] {target specification}
DESCRIPTION
Nmap (“Network Mapper”) is an open source tool for network exploration
and security auditing. It was designed to rapidly scan large networks,
although it works fine against single hosts. Nmap uses raw IP packets
in novel ways to determine what hosts are available on the network,
what services (application name and version) those hosts are offering,
what operating systems (and OS versions) they are running, what type of
packet filters/firewalls are in use, and dozens of other
...
We won't include the whole think because it is really really long. You can get the above by doing man nmap
.
You can also find it online at https://linux.die.net/man/1/nmap and more information in the nmap book at https://nmap.org/book/man.html.
What a wall of text, well with great flexibility comes great complexity.. But who reads man pages anyways, your question is probably on Stackoverflow already/... ;)
Seriously though man pages are a tremendous resource, and prehaps a value lost in today's world of Agile development.
Ther is a lot of information about Nmap out there and if your familiar with Nmap and nse you might as well as stop reading now (Seriously keep reading because well be talking about scrpting with Nmap and alot of people haven't done that). The main objective here is to hack with something that is new and unfamilar. It is imposible to know everything about everything, however by honing your analytical skills and intuition; today especially it give you the tools in your toolbox to travel light and get things done.
When I set out to learn something new it helps if I have a purpose real or even contrived, it really helps otherwise I go through a bunch of tutorials and blogs but never end up doing anything with what I'm trying to learn. It just helps it stick.
Here is your missions if you choose to accept it:
Lets supose we thought that developers might be using the builtin Python http-server module for development and that they are doing it in their development directory which is a git repository, they could be exposing credentials that would allow injection of malicious code in to a souce tree that could make it in to production, what a nice place to put a backdoor....
Right so how do we do this? Youre vaugly familiar with nmap, you heard it is really cool and want an excuse to use it, it's a port scanner that would be good for perhaps scanning for ports that have a HTTP server running. But how do we know if the server is a Python http-server? Then how do we get it to find and collect Git repository information?
Dorking about with nmap and browsing on the interwebs we find out about this cool feature of nmap that allows scripting, and that there a lot of built in scripts in /usr/share/nmap/scripts
. There is even one called http-headers that prints out HTTP headers of any HTTP server that nmap connects to. Perhaps we can use HTTP headers to determine if we are connected to a Python http-server.
First we need to set up something to test against, it'ts easy enough to stand up a Python http-server inside a directory with a git repo to test our theory....
%%bash
mkdir ~/hacking-with-nmap
cd ~/hacking-with-nmap
git init
We also need to start the http-server but we are not going to do that in the notbook use a seperate terminal window because Jupyter desn't allow background bash processes.
python3 -m http.server
This prints out:
Serving HTTP on 0.0.0.0 port 8000 ...
Yikes that is listening on all addresses including accessable from other machines on the network and possibly the internet.
We better kill http-server and restart it, on the local loop back port.
!pkill -f "python3 -m http.server"
Now in the terminal window execute:
nohup python3 -m http.server --bind 127.0.0.1 8000 &
What is the business with the ampersand (&) and the nohup. An ampersand at the end of a bash commands executes the command in the background. Eventhough it's in the background it will still wtite to the console which could be really annoying which is why the second tme we started http.server we put a nohup infront of it and why you didn't see Serving HTTP on 0.0.0.0 port 8000
in the terminal this time.
Now we are ready to have go at the Python http-server module with the NMap NSE script http-headers
!nmap --script http-headers -p 80,8000,8080 127.0.0.1
Perfect the Python http-server populates the server field in the HTTP headers now we know how to identify potential targets.
Now to figure out how to find out if the server has a git repo inside and get the information. Uh we guess we could write a scraper but that is work...
I wonder are there any NSE scripts that do something like what we want?
!ls /usr/share/nmap/scripts
Hmm http-git looks interesting. Lets get some more information. From reading the man page we can use the switch --script-help to get more information on a script
!nmap --script-help http-git
Perfect it does pretty much what we want, list test it out
!nmap --script http-git -p 8000 127.0.0.1
We now know how to find the target servers and we know how to find the git repo if it is there. But how do we tie the two together?
We could parse the text output, but that seems like it would be a pain and be brittle. Hmm something from the manpage sticks out nmap can generate XML output to a file with the -oX switch
nmap --script http-git -p 8000 127.0.0.1 -oX -
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE nmaprun>
<?xml-stylesheet href="file:///usr/bin/../share/nmap/nmap.xsl" type="text/xsl"?>
<!-- Nmap 7.40 scan initiated Mon May 28 00:42:10 2018 as: nmap --script http-git -p 8000 -oX - 127.0.0.1 -->
<nmaprun scanner="nmap" args="nmap --script http-git -p 8000 -oX - 127.0.0.1" start="1527482530" startstr="Mon May 28 00:42:10 2018" version="7.40" xmloutputversion="1.04">
<scaninfo type="connect" protocol="tcp" numservices="1" services="8000"/>
<verbose level="0"/>
<debugging level="0"/>
<host starttime="1527482530" endtime="1527482530"><status state="up" reason="conn-refused" reason_ttl="0"/>
<address addr="127.0.0.1" addrtype="ipv4"/>
<hostnames>
<hostname name="localhost" type="PTR"/>
</hostnames>
<ports><port protocol="tcp" portid="8000"><state state="open" reason="syn-ack" reason_ttl="0"/><service name="http-alt" method="table" conf="3"/><script id="http-git" output="
 127.0.0.1:8000/.git/
 Git repository found!
 Repository description: Unnamed repository; edit this file 'description' to name the...
"><table key="127.0.0.1:8000/.git/">
<table key="files-found">
<elem key=".git/COMMIT_EDITMSG">false</elem>
<elem key=".git/description">true</elem>
<elem key=".git/config">true</elem>
<elem key=".git/info/exclude">true</elem>
<elem key=".gitignore">false</elem>
</table>
<elem key="repository-description">Unnamed repository; edit this file 'description' to name the repository.
</elem>
</table>
</script></port>
</ports>
<times srtt="191" rttvar="3775" to="100000"/>
</host>
<runstats><finished time="1527482530" timestr="Mon May 28 00:42:10 2018" elapsed="0.39" summary="Nmap done at Mon May 28 00:42:10 2018; 1 IP address (1 host up) scanned in 0.39 seconds" exit="success"/><hosts up="1" down="0" total="1"/>
</runstats>
</nmaprun>
So that is nice, but, ug now I have to figure out how to deal with the XML (Note using the XML output is perfectly reasonable and good, just not one were going to take)
What if we could mash the two NSE scripts together and make one script that does what we want. Besides wouldn't it be cool if we could make our own NSE scripts that might be a nice think to know how to do.
The NMap NSE uses an embeded Lua interpreter for it's scrpting engine. Lua is an awesome little language similar to Javascript, small light and fast. In many ways a better Javascript than Javascript. The primary similiarities to javascript is the object systems, both Javascript and Lua use prototype based object systems. That is to say new objects are built by making copies of existing objects which serve as ptototypes adding attributes and methods. There some other syntactic differences. But you know what? Wether we know lua or not we only need to know enough to get this working, I'm sure the interwebs can help, enough.
Okay so lets dive right in by looking at http-headers.nse (by Ron Bowes)
local http = require "http"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local table = require "table"
description = [[
Performs a HEAD request for the root folder ("/") of a web server and displays the HTTP headers returned.
]]
---
-- @output
-- PORT STATE SERVICE
-- 80/tcp open http
-- | http-headers:
-- | Date: Fri, 25 Jan 2013 17:39:08 GMT
-- | Server: Apache/2.2.14 (Ubuntu)
-- | Accept-Ranges: bytes
-- | Vary: Accept-Encoding
-- | Connection: close
-- | Content-Type: text/html
-- |
-- |_ (Request type: HEAD)
--
--@args path The path to request, such as <code>/index.php</code>. Default <code>/</code>.
--@args useget Set to force GET requests instead of HEAD.
author = "Ron Bowes"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
portrule = shortport.http
local function fail (err) return stdnse.format_output(false, err) end
action = function(host, port)
local path = stdnse.get_script_args(SCRIPT_NAME..".path") or "/"
local useget = stdnse.get_script_args(SCRIPT_NAME..".useget")
local request_type = "HEAD"
local status = false
local result
-- Check if the user didn't want HEAD to be used
if(useget == nil) then
-- Try using HEAD first
status, result = http.can_use_head(host, port, nil, path)
end
-- If head failed, try using GET
if(status == false) then
stdnse.debug1("HEAD request failed, falling back to GET")
result = http.get(host, port, path)
request_type = "GET"
end
if(result == nil) then
return fail("Header request failed")
end
if(result.rawheader == nil) then
return fail("Header request didn't return a proper header")
end
table.insert(result.rawheader, "(Request type: " .. request_type .. ")")
return stdnse.format_output(true, result.rawheader)
end
Looks simple enough, it seems like NMap NSE scripts uses a protocol, where there are sertain specal variables that get populated and NMAP NSE uses them to do things like display documentation, preform actions durring the scan.
Lets start by copying the code block above in a text editor saving it awith the name python-http-git.nse and change value of the description variable to describe what were going to adapt the script to do. One thing I think you've niticed is that [[ and ]] enclose or delimit a string that spans multiple lines.
description = [[
Performs a HEAD request for the root folder ("/") of a web server and displays the HTTP headers returned.
]]
Lets see if our change took
!nmap --script-help=./python-http-git.nse
Sweet that worked BTW --script-help can be used to get documentation on other nmap scripts....
Now lets get to work, reading through the code we can infer action
is a function that gets called during the scan if a port is open. action
is given the host and port being scanned. Reading further we see that it tries to make a HTTP HEAD request. (A HTTP HEAD request returns only the headers and not the full page, browsers and spider might often make this request to decide if a page has changed since the last time the loaded it and decide wether to reload it again)
We also notice the condition where useget
is checked to be nil
(nil
by the way is Lua's version of false or no value), and reading backwards to the local declaration of useget
it would appear that is an argument that can be used to make HTTP GET requests only. Cool, so now we see you we might be able to pass a NSE script an argument if we wanted to.
Something interesting on line 52 is the stdnse.debug1
function looks like we can use that to print debuging output
Moving on downwe get past the conditions that checked wether to use GET requests (line 46), see if the status of the HEAD request was successful(line 52), wether the result of the request was populated (line 58), and wether it is a valid HTTP header (line 62).
As we get towards the end this is probably where we will want to make out changes...
table.insert(result.rawheader, "(Request type: " .. request_type .. ")")
return stdnse.format_output(true, result.rawheader)
It looks like that result.rawheader
is a Lua table (Oh, a Lua table is a bit like a hash table or a dict or dictionary, it stores key value pairs) and that it is adding a line to it. Interesting I bet that ..
is used to glue pieces of strings together. Finally it is calling stdnse.format_output to format the output and return it back to the NSE.
Looking back when we ran the http-headers NSE script we get the value of the Server HTTP Header value returned by our Python http-server
Server: SimpleHTTP/0.6 Python/3.5.2
Now how do we get the value in the header? It has to be some where in the result. Perhaps we need to re-read the code. We see a line at the top of the program
local http = require "http"
http
must be the library that is being used, we could find it ant try to read the code (The code for the lua libraries that NSE makes avaliable is /usr/share/nmap/nselib
BTW) Or we could search the interwebs for some information... Perhaps we search for 'nse http library' which should find the documentation for the http library for nse (here is the link BTW https://nmap.org/nsedoc/lib/http.html )
After reading into the doc a short way it looks like the http
functions return a table and one of the indexes is head
which is another table where the HEADER attributes are converted to lowercase and used as indexes.
So I think we could get at the value of the Server attribute by doing the following
result.header.server
Lets test this at around line 67 before the table.insert lets try to print out the value in debugging output . Add the line below to python-http-git.nse save it then runit with debug enabled
stdnse.debug1("This is the Server attribute in the response:" .. result.header.server)
!nmap -d --script=./python-http-git.nse -p 8000 127.0.0.1
Wow lots of text. But it is pretty cool as you look through it trying to find out if it worked you can see what Nmap is doing as it preforms the scan and runs your script, pretty cool. Anyways we see that it did infact work:
NSE: [python-http-git 127.0.0.1:8000] This is the Server attribute in the response:SimpleHTTP/0.6 Python/3.5.2
We just need to be determine when the value of result.header.server matches "SimpleHTTP/0.6 Python/3.5.2". Of course version change so we probably want to also take this into account.
Hmm, how do you do this in Lua? I guess we can ask the interwebs again. Perhaps we search for "lua string match"
In the 'Lua Patterns Tutorial' we found on on the interwebs looks like a good place to start ( http://lua-users.org/wiki/PatternsTutorial ).
We learn that lua provides pattern matching on strings similar to regular expressions (It kind seems like regex and C format had a baby). This is primarilly preformed with the string match
function, there first argument is the string and the second is the pattern.
Also we wanted to handle changing version numbers, wel %d
will match any decimal and +
will match the preciding value one or more times (incase the version has a decial in the double or triple digits).
So lets fire up a Lua prompt and test this out, you can do this by typing lua
in your terminal.
$ lua
Lua 5.3.4 Copyright (C) 1994-2017 Lua.org, PUC-Rio
> string.match("SimpleHTTP/0.6 Python/3.5.2","Simple")
Simple
> string.match("SimpleHTTP/0.6 Python/3.5.2","SimpleHTTP/")
SimpleHTTP/
> string.match("SimpleHTTP/0.6 Python/3.5.2","SimpleHTTP/%d")
SimpleHTTP/0
> string.match("SimpleHTTP/0.6 Python/3.5.2","SimpleHTTP/%d+")
SimpleHTTP/0
> string.match("SimpleHTTP/0.6 Python/3.5.2","SimpleHTTP/%d+.%d")
SimpleHTTP/0.6
> string.match("SimpleHTTP/0.6 Python/3.5.2","SimpleHTTP/%d+.%d%s+")
SimpleHTTP/0.6
> string.match("SimpleHTTP/0.6 Python/3.5.2","SimpleHTTP/%d+.%d%s+Python/")
SimpleHTTP/0.6 Python/
> string.match("SimpleHTTP/0.6 Python/3.5.2","SimpleHTTP/%d+.%d%s+Python/%d+.%d")
SimpleHTTP/0.6 Python/3.5
> string.match("SimpleHTTP/0.6 Python/3.5.2","SimpleHTTP/%d+.%d%s+Python/%d+.%d").%d
stdin:1: <name> expected near '%'
> string.match("SimpleHTTP/0.6 Python/3.5.2","SimpleHTTP/%d+.%d%s+Python/%d+.%d.%d")
SimpleHTTP/0.6 Python/3.5.2
> string.match("SimpleHTTP/0.6 Python/3.5.2","SimpleHTTP/%d+.%d+%s+Python/%d+.%d+.%d+")
SimpleHTTP/0.6 Python/3.5.2
> string.match("XimpleHTTP/0.6 Python/3.5.2","SimpleHTTP/%d+.%d+%s+Python/%d+.%d+.%d+")
nil
>
we notice that string.match`` return the matched string if it succeds and
nil``` if it fails.
Our match function call will be:
string.match(result.header.server, "SimpleHTTP/%d+.%d+%s+Python/%d+.%d+.%d+")
Lets change the bottom of the action function at like 67 to look like this
if(result.header and result.header.server and string.match(result.header.server, "SimpleHTTP/%d+.%d+%s+Python/%d+.%d+.%d+")) then
stdnse.debug1("The server is a python HTTP server:" .. result.header.server)
table.insert(result.rawheader, "(Request type: " .. request_type .. ")")
table.insert(result.rawheader, "(Server type: Python HTTP Server)")
return stdnse.format_output(true, result.rawheader)
else
return fail("This is not the server we were looking for")
end
Save the file and we will run the Nmap script again this time without the debuging and also we will not specify ports so perhaps we will see if it is excluding other http servers that may be running (like your Jupyter note book if your following along.
!nmap --script=./python-http-git.nse -p 80,8000,8888 127.0.0.1
Sweet, I love it when a plan comes together..... We can now identify Python http-server servers and have accomplished half of our objective.
The other half of out objective is to be able to determine if there is a Git repo exposed and if possible snarf any credentials and other useful information....
We've found the http-git.nse script (by Alex Weber) that comes with NMap does almost excatly what we want for the second stage, we just need to rip the functionality and include it in our script.
This part is seems really easy after looking at http-get action function is where everything happens. Lets take a look.
-- We consider 200 to mean "okay, file exists and we received its contents".
local STATUS_OK = 200
-- Long strings (like a repository's description) will be truncated to this
-- number of characters in normal output.
local TRUNC_LENGTH = 60
function action(host, port)
local out
-- We can accept a single root, or a table of roots to try
local root_arg = stdnse.get_script_args("http-git.root")
local roots
if type(root_arg) == "table" then
roots = root_arg
elseif type(root_arg) == "string" or type(root_arg) == "number" then
roots = { tostring(root_arg) }
elseif root_arg == nil then -- if we didn't get an argument
roots = { "/" }
end
-- Try each root in succession
for _, root in ipairs(roots) do
root = tostring(root)
root = root or '/'
-- Put a forward slash on the beginning and end of the root, if none was
-- provided. We will print this, so the user will know that we've mangled it
if not string.find(root, "/$") then -- if there is no slash at the end
root = root .. "/"
end
if not string.find(root, "^/") then -- if there is no slash at the beginning
root = "/" .. root
end
-- If we can't get a valid /.git/HEAD, don't even bother continuing
-- We could try for /.git/, but we will not get a 200 if directory
-- listings are disallowed.
local resp = http.get(host, port, root .. ".git/HEAD")
local sha1_pattern = "^%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x"
if resp.status == STATUS_OK and ( resp.body:match("^ref: ") or resp.body:match(sha1_pattern) ) then
out = out or {}
local replies = {}
-- This function returns true if we got a 200 OK when
-- fetching 'filename' from the server
local function ok(filename)
return (replies[filename].status == STATUS_OK)
end
-- These are files that are small, very common, and don't
-- require zlib to read
-- These files are created by creating and using the repository,
-- or by popular development frameworks.
local repo = {
".gitignore",
".git/COMMIT_EDITMSG",
".git/config",
".git/description",
".git/info/exclude",
}
local pl_requests = {} -- pl_requests = pipelined requests (temp)
-- Go through all of the filenames and do an HTTP GET
for _, name in ipairs(repo) do -- for every filename
http.pipeline_add(root .. name, nil, pl_requests)
end
-- Do the requests.
replies = http.pipeline_go(host, port, pl_requests)
if replies == nil then
stdnse.debug1("pipeline_go() error. Aborting.")
return nil
end
for i, reply in ipairs(replies) do
-- We want this to be indexed by filename, not an integer, so we convert it
-- We added to the pipeline in the same order as the filenames, so this is safe.
replies[repo[i]] = reply -- create index by filename
replies[i] = nil -- delete integer-indexed entry
end
-- Mark each file that we tried to get as 'found' (true) or 'not found' (false).
local location = host.ip .. ":" .. port.number .. root .. ".git/"
out[location] = {}
-- A nice shortcut
local loc = out[location]
loc["files-found"] = {}
for name, _ in pairs(replies) do
loc["files-found"][name] = ok(name)
end
-- Look through all the repo files we grabbed and see if we can find anything interesting.
local interesting = { "bug", "key", "passw", "pw", "user", "secret", "uid" }
for name, reply in pairs(replies) do
if ok(name) then
for _, pattern in ipairs(interesting) do
if string.match(reply.body, pattern) then
-- A Lua idiom - don't create this table until we actually have something to put in it
loc["interesting-matches"] = loc["interesting-matches"] or {}
loc["interesting-matches"][name] = loc["interesting-matches"][name] or {}
table.insert(loc["interesting-matches"][name], pattern)
end
end
end
end
if ok(".git/COMMIT_EDITMSG") then
loc["last-commit-message"] = replies[".git/COMMIT_EDITMSG"].body
end
if ok(".git/description") then
loc["repository-description"] = replies[".git/description"].body
end
-- .git/config contains a list of remotes, so we try to extract them.
if ok(".git/config") then
local config = replies[".git/config"].body
local remotes = {}
-- Try to extract URLs of all remotes.
for url in string.gmatch(config, "\n%s*url%s*=%s*(%S*/%S*)") do
table.insert(remotes, url)
end
for _, url in ipairs(remotes) do
loc["remotes"] = loc["remotes"] or {}
table.insert(loc["remotes"], url)
end
end
-- These are files that are used by Git to determine what files to ignore.
-- We use this list to make the loop below (used to determine what kind of
-- application is in the repository) more generic.
local ignorefiles = {
".gitignore",
".git/info/exclude",
}
local fingerprints = {
-- Many of these taken from https://github.com/github/gitignore
{ "%.scala_dependencies", "Scala application" },
{ "npm%-debug%.log", "node.js application" },
{ "joomla%.xml", "Joomla! site" },
{ "jboss/server", "JBoss Java web application" },
{ "wp%-%*%.php", "WordPress site" },
{ "app/config/database%.php", "CakePHP web application" },
{ "sites/default/settings%.php", "Drupal site" },
{ "local_settings%.py", "Django web application" },
{ "/%.bundle", "Ruby on Rails web application" }, -- More specific matches (MyFaces > JSF > Java) on top
{ "%.py[dco]", "Python application" },
{ "%.jsp", "JSP web application" },
{ "%.bundle", "Ruby application" },
{ "%.class", "Java application" },
{ "%.php", "PHP application" },
}
-- The XML produced here is divided by ignorefile and is sorted from first to last
-- in order of specificity. e.g. All JBoss applications are Java applications,
-- but not all Java applications are JBoss. In that case, JBoss and Java will
-- be output, but JBoss will be listed first.
for _, file in ipairs(ignorefiles) do
if ok(file) then -- We only test all fingerprints if we got the file.
for _, fingerprint in ipairs(fingerprints) do
if string.match(replies[file].body, fingerprint[1]) then
loc["project-type"] = loc["project-type"] or {}
loc["project-type"][file] = loc["project-type"][file] or {}
table.insert(loc["project-type"][file], fingerprint[2])
end
end
end
end
end
end
-- If we didn't get anything, we return early. No point doing the
-- normal formatting!
if out == nil then
return nil
end
-- Truncate to TRUNC_LENGTH characters and replace control characters (newlines, etc) with spaces.
local function summarize(str)
str = stdnse.string_or_blank(str, "<unknown>")
local original_length = #str
str = string.sub(str, 1, TRUNC_LENGTH)
str = string.gsub(str, "%c", " ")
if original_length > TRUNC_LENGTH then
str = str .. "..."
end
return str
end
-- We convert the full output to pretty output for -oN
local normalout
for location, info in pairs(out) do
normalout = normalout or {}
-- This table gets converted to a string format_output, and then inserted into the 'normalout' table
local new = {}
-- Headings for each place we found a repo
new["name"] = location
-- How sure are we that this is a Git repository?
local count = { tried = 0, ok = 0 }
for _, found in pairs(info["files-found"]) do
count.tried = count.tried + 1
if found then count.ok = count.ok + 1 end
end
-- If 3 or more of the files we were looking for are not on the server,
-- we are less confident that we got a real Git repository
if count.tried - count.ok <= 2 then
table.insert(new, "Git repository found!")
else -- We already got .git/HEAD, so we add 1 to 'tried' and 'ok'
table.insert(new, "Potential Git repository found (found " .. (count.ok + 1) .. "/" .. (count.tried + 1) .. " expected files)")
end
-- Show what patterns matched what files
for name, matches in pairs(info["interesting-matches"] or {}) do
table.insert(new, ("%s matched patterns '%s'"):format(name, table.concat(matches, "' '")))
end
if info["repository-description"] then
table.insert(new, "Repository description: " .. summarize(info["repository-description"]))
end
if info["last-commit-message"] then
table.insert(new, "Last commit message: " .. summarize(info["last-commit-message"]))
end
-- If we found any remotes in .git/config, process them now
if info["remotes"] then
local old_name = info["remotes"]["name"] -- in case 'name' is a remote
info["remotes"]["name"] = "Remotes:"
-- Remove the newline from format_output's output - it looks funny with it
local temp = string.gsub(stdnse.format_output(true, info["remotes"]), "^\n", "")
-- using 'temp' here because gsub() has multiple return values that insert() will try
-- to use, and I don't know of a better way to prevent that ;)
table.insert(new, temp)
info["remotes"]["name"] = old_name
end
-- Take the first guessed project type from each ignorefile
if info["project-type"] then
for name, types in pairs(info["project-type"]) do
table.insert(new, "Project type: " .. types[1] .. " (guessed from " .. name .. ")")
end
end
-- Insert this location's information.
table.insert(normalout, new)
end
return out, stdnse.format_output(true, normalout)
end
Here is the plan since everything is self contained in the action
function, let's just paste the action
function at the end of out python-http-git file and rename the pasted action
function to http_git_action
like so
function action(host, port)
We also need bring over the STATUS_OK
, and TRUNC_LENGTH
declarations as they are used inside of the http_git_action
function, but we do not need to rename them as they don't seem to collide with anything in our script.
Now to make this work we just need to call the http_git_action
from our action, in the condition where we identify that we have found a Python http-server instance (This is at line 67). We will just comment out the old code and put in the new call
if(result.header and result.header.server and string.match(result.header.server, "SimpleHTTP/%d+.%d+%s+Python/%d+.%d+.%d+")) then
stdnse.debug1("The server is a python HTTP server:" .. result.header.server)
-- table.insert(result.rawheader, "(Request type: " .. request_type .. ")")
-- table.insert(result.rawheader, "(Server type: Python HTTP Server)")
--return stdnse.format_output(true, result.rawheader)
return http_git_action(host,port)
else
return fail("This is not the server we were looking for")
end
Nowto make this interesting let's add something that will make http-git chirp lets paste the config block below onto the end of the .git/config file in our directory.
[sendemail]
smtpencryption = tls
smtpserver = smtp.example.com
smtpuser = pavan.sss1991@example.com
smtppass = I_am_a_password
smtpserverport = 587
Perfect now lets run our code:
!nmap --script=./python-http-git.nse -p 80,8000,8888 127.0.0.1
Perfect, we've accomplished just what we set out to do, obtain git repo information from only computer running a Python http-server where there is an exposed .git directory.
Along the way we've learned a about NMAP, NMap NSE scripting how to use and inspect existing scripts and how they work and how to go about adapting and making our own. Coencidentlly we may have leared a few shell commands, some Lua, a bit about the HTTP protocol, how to start a simple Python HTTP server, and a little about the Git source code control system.
Whew.....
Most importantly however we have learned techniques for working with things that we may not have been exposed to before and utilizing community knowledge (more importatnly how to find the right piece your looking for), as well as exercising our analytical and intuitive thought processes.