Hacking Nmap

Lets have some fun with nmap...

Prelude

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.

Introduction

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.

Lets go!!!

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....

In [1]:
%%bash
mkdir ~/hacking-with-nmap
cd ~/hacking-with-nmap
git init
Initialized empty Git repository in /home/mmaul/hacking-with-nmap/.git/

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.

In [2]:
!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

In [3]:
!nmap --script http-headers -p 80,8000,8080 127.0.0.1
Starting Nmap 7.60 ( https://nmap.org ) at 2018-05-30 09:32 EDT
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00019s latency).

PORT     STATE  SERVICE
80/tcp   closed http
8000/tcp open   http-alt
| http-headers: 
|   Server: SimpleHTTP/0.6 Python/3.6.5
|   Date: Wed, 30 May 2018 13:32:05 GMT
|   Content-type: text/html; charset=utf-8
|   Content-Length: 375
|   
|_  (Request type: HEAD)
8080/tcp closed http-proxy

Nmap done: 1 IP address (1 host up) scanned in 0.22 seconds

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?

In [4]:
!ls /usr/share/nmap/scripts
acarsd-info.nse				ip-forwarding.nse
address-info.nse			ip-geolocation-geoplugin.nse
afp-brute.nse				ip-geolocation-ipinfodb.nse
afp-ls.nse				ip-geolocation-map-bing.nse
afp-path-vuln.nse			ip-geolocation-map-google.nse
afp-serverinfo.nse			ip-geolocation-map-kml.nse
afp-showmount.nse			ip-geolocation-maxmind.nse
ajp-auth.nse				ip-https-discover.nse
ajp-brute.nse				ipidseq.nse
ajp-headers.nse				ipmi-brute.nse
ajp-methods.nse				ipmi-cipher-zero.nse
ajp-request.nse				ipmi-version.nse
allseeingeye-info.nse			ipv6-multicast-mld-list.nse
amqp-info.nse				ipv6-node-info.nse
asn-query.nse				ipv6-ra-flood.nse
auth-owners.nse				irc-botnet-channels.nse
auth-spoof.nse				irc-brute.nse
backorifice-brute.nse			irc-info.nse
backorifice-info.nse			irc-sasl-brute.nse
bacnet-info.nse				irc-unrealircd-backdoor.nse
banner.nse				iscsi-brute.nse
bitcoin-getaddr.nse			iscsi-info.nse
bitcoin-info.nse			isns-info.nse
bitcoinrpc-info.nse			jdwp-exec.nse
bittorrent-discovery.nse		jdwp-info.nse
bjnp-discover.nse			jdwp-inject.nse
broadcast-ataoe-discover.nse		jdwp-version.nse
broadcast-avahi-dos.nse			knx-gateway-discover.nse
broadcast-bjnp-discover.nse		knx-gateway-info.nse
broadcast-db2-discover.nse		krb5-enum-users.nse
broadcast-dhcp6-discover.nse		ldap-brute.nse
broadcast-dhcp-discover.nse		ldap-novell-getpass.nse
broadcast-dns-service-discovery.nse	ldap-rootdse.nse
broadcast-dropbox-listener.nse		ldap-search.nse
broadcast-eigrp-discovery.nse		lexmark-config.nse
broadcast-igmp-discovery.nse		llmnr-resolve.nse
broadcast-listener.nse			lltd-discovery.nse
broadcast-ms-sql-discover.nse		maxdb-info.nse
broadcast-netbios-master-browser.nse	mcafee-epo-agent.nse
broadcast-networker-discover.nse	membase-brute.nse
broadcast-novell-locate.nse		membase-http-info.nse
broadcast-ospf2-discover.nse		memcached-info.nse
broadcast-pc-anywhere.nse		metasploit-info.nse
broadcast-pc-duo.nse			metasploit-msgrpc-brute.nse
broadcast-pim-discovery.nse		metasploit-xmlrpc-brute.nse
broadcast-ping.nse			mikrotik-routeros-brute.nse
broadcast-pppoe-discover.nse		mmouse-brute.nse
broadcast-rip-discover.nse		mmouse-exec.nse
broadcast-ripng-discover.nse		modbus-discover.nse
broadcast-sonicwall-discover.nse	mongodb-brute.nse
broadcast-sybase-asa-discover.nse	mongodb-databases.nse
broadcast-tellstick-discover.nse	mongodb-info.nse
broadcast-upnp-info.nse			mqtt-subscribe.nse
broadcast-versant-locate.nse		mrinfo.nse
broadcast-wake-on-lan.nse		msrpc-enum.nse
broadcast-wpad-discover.nse		ms-sql-brute.nse
broadcast-wsdd-discover.nse		ms-sql-config.nse
broadcast-xdmcp-discover.nse		ms-sql-dac.nse
cassandra-brute.nse			ms-sql-dump-hashes.nse
cassandra-info.nse			ms-sql-empty-password.nse
cccam-version.nse			ms-sql-hasdbaccess.nse
cics-enum.nse				ms-sql-info.nse
cics-info.nse				ms-sql-ntlm-info.nse
cics-user-brute.nse			ms-sql-query.nse
cics-user-enum.nse			ms-sql-tables.nse
citrix-brute-xml.nse			ms-sql-xp-cmdshell.nse
citrix-enum-apps.nse			mtrace.nse
citrix-enum-apps-xml.nse		murmur-version.nse
citrix-enum-servers.nse			mysql-audit.nse
citrix-enum-servers-xml.nse		mysql-brute.nse
clamav-exec.nse				mysql-databases.nse
clock-skew.nse				mysql-dump-hashes.nse
coap-resources.nse			mysql-empty-password.nse
couchdb-databases.nse			mysql-enum.nse
couchdb-stats.nse			mysql-info.nse
creds-summary.nse			mysql-query.nse
cups-info.nse				mysql-users.nse
cups-queue-info.nse			mysql-variables.nse
cvs-brute.nse				mysql-vuln-cve2012-2122.nse
cvs-brute-repository.nse		nat-pmp-info.nse
daap-get-library.nse			nat-pmp-mapport.nse
daytime.nse				nbstat.nse
db2-das-info.nse			ncp-enum-users.nse
dhcp-discover.nse			ncp-serverinfo.nse
dict-info.nse				ndmp-fs-info.nse
distcc-cve2004-2687.nse			ndmp-version.nse
dns-blacklist.nse			nessus-brute.nse
dns-brute.nse				nessus-xmlrpc-brute.nse
dns-cache-snoop.nse			netbus-auth-bypass.nse
dns-check-zone.nse			netbus-brute.nse
dns-client-subnet-scan.nse		netbus-info.nse
dns-fuzz.nse				netbus-version.nse
dns-ip6-arpa-scan.nse			nexpose-brute.nse
dns-nsec3-enum.nse			nfs-ls.nse
dns-nsec-enum.nse			nfs-showmount.nse
dns-nsid.nse				nfs-statfs.nse
dns-random-srcport.nse			nje-node-brute.nse
dns-random-txid.nse			nje-pass-brute.nse
dns-recursion.nse			nntp-ntlm-info.nse
dns-service-discovery.nse		nping-brute.nse
dns-srv-enum.nse			nrpe-enum.nse
dns-update.nse				ntp-info.nse
dns-zeustracker.nse			ntp-monlist.nse
dns-zone-transfer.nse			omp2-brute.nse
docker-version.nse			omp2-enum-targets.nse
domcon-brute.nse			omron-info.nse
domcon-cmd.nse				openlookup-info.nse
domino-enum-users.nse			openvas-otp-brute.nse
dpap-brute.nse				openwebnet-discovery.nse
drda-brute.nse				oracle-brute.nse
drda-info.nse				oracle-brute-stealth.nse
duplicates.nse				oracle-enum-users.nse
eap-info.nse				oracle-sid-brute.nse
enip-info.nse				oracle-tns-version.nse
epmd-info.nse				ovs-agent-version.nse
eppc-enum-processes.nse			p2p-conficker.nse
fcrdns.nse				path-mtu.nse
finger.nse				pcanywhere-brute.nse
fingerprint-strings.nse			pcworx-info.nse
firewalk.nse				pgsql-brute.nse
firewall-bypass.nse			pjl-ready-message.nse
flume-master-info.nse			pop3-brute.nse
fox-info.nse				pop3-capabilities.nse
freelancer-info.nse			pop3-ntlm-info.nse
ftp-anon.nse				pptp-version.nse
ftp-bounce.nse				puppet-naivesigning.nse
ftp-brute.nse				qconn-exec.nse
ftp-libopie.nse				qscan.nse
ftp-proftpd-backdoor.nse		quake1-info.nse
ftp-syst.nse				quake3-info.nse
ftp-vsftpd-backdoor.nse			quake3-master-getservers.nse
ftp-vuln-cve2010-4221.nse		rdp-enum-encryption.nse
ganglia-info.nse			rdp-vuln-ms12-020.nse
giop-info.nse				realvnc-auth-bypass.nse
gkrellm-info.nse			redis-brute.nse
gopher-ls.nse				redis-info.nse
gpsd-info.nse				resolveall.nse
hadoop-datanode-info.nse		reverse-index.nse
hadoop-jobtracker-info.nse		rexec-brute.nse
hadoop-namenode-info.nse		rfc868-time.nse
hadoop-secondary-namenode-info.nse	riak-http-info.nse
hadoop-tasktracker-info.nse		rlogin-brute.nse
hbase-master-info.nse			rmi-dumpregistry.nse
hbase-region-info.nse			rmi-vuln-classloader.nse
hddtemp-info.nse			rpcap-brute.nse
hnap-info.nse				rpcap-info.nse
hostmap-bfk.nse				rpc-grind.nse
hostmap-ip2hosts.nse			rpcinfo.nse
hostmap-robtex.nse			rsync-brute.nse
http-adobe-coldfusion-apsa1301.nse	rsync-list-modules.nse
http-affiliate-id.nse			rtsp-methods.nse
http-apache-negotiation.nse		rtsp-url-brute.nse
http-apache-server-status.nse		rusers.nse
http-aspnet-debug.nse			s7-info.nse
http-auth-finder.nse			samba-vuln-cve-2012-1182.nse
http-auth.nse				script.db
http-avaya-ipoffice-users.nse		servicetags.nse
http-awstatstotals-exec.nse		shodan-api.nse
http-axis2-dir-traversal.nse		sip-brute.nse
http-backup-finder.nse			sip-call-spoof.nse
http-barracuda-dir-traversal.nse	sip-enum-users.nse
http-brute.nse				sip-methods.nse
http-cakephp-version.nse		skypev2-version.nse
http-chrono.nse				smb2-capabilities.nse
http-cisco-anyconnect.nse		smb2-security-mode.nse
http-coldfusion-subzero.nse		smb2-time.nse
http-comments-displayer.nse		smb2-vuln-uptime.nse
http-config-backup.nse			smb-brute.nse
http-cookie-flags.nse			smb-double-pulsar-backdoor.nse
http-cors.nse				smb-enum-domains.nse
http-cross-domain-policy.nse		smb-enum-groups.nse
http-csrf.nse				smb-enum-processes.nse
http-date.nse				smb-enum-sessions.nse
http-default-accounts.nse		smb-enum-shares.nse
http-devframework.nse			smb-enum-users.nse
http-dlink-backdoor.nse			smb-flood.nse
http-dombased-xss.nse			smb-ls.nse
http-domino-enum-passwords.nse		smb-mbenum.nse
http-drupal-enum.nse			smb-os-discovery.nse
http-drupal-enum-users.nse		smb-print-text.nse
http-enum.nse				smb-protocols.nse
http-errors.nse				smb-psexec.nse
http-exif-spider.nse			smb-security-mode.nse
http-favicon.nse			smb-server-stats.nse
http-feed.nse				smb-system-info.nse
http-fetch.nse				smb-vuln-conficker.nse
http-fileupload-exploiter.nse		smb-vuln-cve2009-3103.nse
http-form-brute.nse			smb-vuln-cve-2017-7494.nse
http-form-fuzzer.nse			smb-vuln-ms06-025.nse
http-frontpage-login.nse		smb-vuln-ms07-029.nse
http-generator.nse			smb-vuln-ms08-067.nse
http-git.nse				smb-vuln-ms10-054.nse
http-gitweb-projects-enum.nse		smb-vuln-ms10-061.nse
http-google-malware.nse			smb-vuln-ms17-010.nse
http-grep.nse				smb-vuln-regsvc-dos.nse
http-headers.nse			smtp-brute.nse
http-huawei-hg5xx-vuln.nse		smtp-commands.nse
http-icloud-findmyiphone.nse		smtp-enum-users.nse
http-icloud-sendmsg.nse			smtp-ntlm-info.nse
http-iis-short-name-brute.nse		smtp-open-relay.nse
http-iis-webdav-vuln.nse		smtp-strangeport.nse
http-internal-ip-disclosure.nse		smtp-vuln-cve2010-4344.nse
http-joomla-brute.nse			smtp-vuln-cve2011-1720.nse
http-litespeed-sourcecode-download.nse	smtp-vuln-cve2011-1764.nse
http-ls.nse				sniffer-detect.nse
http-majordomo2-dir-traversal.nse	snmp-brute.nse
http-malware-host.nse			snmp-hh3c-logins.nse
http-mcmp.nse				snmp-info.nse
http-methods.nse			snmp-interfaces.nse
http-method-tamper.nse			snmp-ios-config.nse
http-mobileversion-checker.nse		snmp-netstat.nse
http-ntlm-info.nse			snmp-processes.nse
http-open-proxy.nse			snmp-sysdescr.nse
http-open-redirect.nse			snmp-win32-services.nse
http-passwd.nse				snmp-win32-shares.nse
http-phpmyadmin-dir-traversal.nse	snmp-win32-software.nse
http-phpself-xss.nse			snmp-win32-users.nse
http-php-version.nse			socks-auth-info.nse
http-proxy-brute.nse			socks-brute.nse
http-put.nse				socks-open-proxy.nse
http-qnap-nas-info.nse			ssh2-enum-algos.nse
http-referer-checker.nse		ssh-auth-methods.nse
http-rfi-spider.nse			ssh-brute.nse
http-robots.txt.nse			ssh-hostkey.nse
http-robtex-reverse-ip.nse		ssh-publickey-acceptance.nse
http-robtex-shared-ns.nse		ssh-run.nse
http-security-headers.nse		sshv1.nse
http-server-header.nse			ssl-ccs-injection.nse
http-shellshock.nse			ssl-cert-intaddr.nse
http-sitemap-generator.nse		ssl-cert.nse
http-slowloris-check.nse		ssl-date.nse
http-slowloris.nse			ssl-dh-params.nse
http-sql-injection.nse			ssl-enum-ciphers.nse
http-stored-xss.nse			ssl-heartbleed.nse
http-svn-enum.nse			ssl-known-key.nse
http-svn-info.nse			ssl-poodle.nse
http-title.nse				sslv2-drown.nse
http-tplink-dir-traversal.nse		sslv2.nse
http-trace.nse				sstp-discover.nse
http-traceroute.nse			stun-info.nse
http-unsafe-output-escaping.nse		stun-version.nse
http-useragent-tester.nse		stuxnet-detect.nse
http-userdir-enum.nse			supermicro-ipmi-conf.nse
http-vhosts.nse				svn-brute.nse
http-virustotal.nse			targets-asn.nse
http-vlcstreamer-ls.nse			targets-ipv6-map4to6.nse
http-vmware-path-vuln.nse		targets-ipv6-multicast-echo.nse
http-vuln-cve2006-3392.nse		targets-ipv6-multicast-invalid-dst.nse
http-vuln-cve2009-3960.nse		targets-ipv6-multicast-mld.nse
http-vuln-cve2010-0738.nse		targets-ipv6-multicast-slaac.nse
http-vuln-cve2010-2861.nse		targets-ipv6-wordlist.nse
http-vuln-cve2011-3192.nse		targets-sniffer.nse
http-vuln-cve2011-3368.nse		targets-traceroute.nse
http-vuln-cve2012-1823.nse		targets-xml.nse
http-vuln-cve2013-0156.nse		teamspeak2-version.nse
http-vuln-cve2013-6786.nse		telnet-brute.nse
http-vuln-cve2013-7091.nse		telnet-encryption.nse
http-vuln-cve2014-2126.nse		telnet-ntlm-info.nse
http-vuln-cve2014-2127.nse		tftp-enum.nse
http-vuln-cve2014-2128.nse		tls-nextprotoneg.nse
http-vuln-cve2014-2129.nse		tls-ticketbleed.nse
http-vuln-cve2014-3704.nse		tn3270-screen.nse
http-vuln-cve2014-8877.nse		tor-consensus-checker.nse
http-vuln-cve2015-1427.nse		traceroute-geolocation.nse
http-vuln-cve2015-1635.nse		tso-brute.nse
http-vuln-cve2017-1001000.nse		tso-enum.nse
http-vuln-cve2017-5638.nse		unittest.nse
http-vuln-cve2017-5689.nse		unusual-port.nse
http-vuln-cve2017-8917.nse		upnp-info.nse
http-vuln-misfortune-cookie.nse		url-snarf.nse
http-vuln-wnr1000-creds.nse		ventrilo-info.nse
http-waf-detect.nse			versant-info.nse
http-waf-fingerprint.nse		vmauthd-brute.nse
http-webdav-scan.nse			vmware-version.nse
http-wordpress-brute.nse		vnc-brute.nse
http-wordpress-enum.nse			vnc-info.nse
http-wordpress-users.nse		vnc-title.nse
http-xssed.nse				voldemort-info.nse
iax2-brute.nse				vtam-enum.nse
iax2-version.nse			vuze-dht-info.nse
icap-info.nse				wdb-version.nse
iec-identify.nse			weblogic-t3-info.nse
ike-version.nse				whois-domain.nse
imap-brute.nse				whois-ip.nse
imap-capabilities.nse			wsdd-discover.nse
imap-ntlm-info.nse			x11-access.nse
impress-remote-discover.nse		xdmcp-discover.nse
informix-brute.nse			xmlrpc-methods.nse
informix-query.nse			xmpp-brute.nse
informix-tables.nse			xmpp-info.nse

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

In [5]:
!nmap --script-help http-git
Starting Nmap 7.60 ( https://nmap.org ) at 2018-05-30 09:35 EDT

http-git
Categories: default safe vuln
https://nmap.org/nsedoc/scripts/http-git.html
  Checks for a Git repository found in a website's document root
  /.git/<something>) and retrieves as much repo information as
  possible, including language/framework, remotes, last commit
  message, and repository description.

Perfect it does pretty much what we want, list test it out

In [6]:
!nmap --script http-git -p 8000 127.0.0.1
Starting Nmap 7.60 ( https://nmap.org ) at 2018-05-30 09:36 EDT
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00013s latency).

PORT     STATE SERVICE
8000/tcp open  http-alt
| http-git: 
|   127.0.0.1:8000/.git/
|     Git repository found!
|_    Repository description: Unnamed repository; edit this file 'description' to name the...

Nmap done: 1 IP address (1 host up) scanned in 0.22 seconds

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 -&#45;script http-git -p 8000 -oX - 127.0.0.1 -->
<nmaprun scanner="nmap" args="nmap -&#45;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="&#xa;  127.0.0.1:8000/.git/&#xa;    Git repository found!&#xa;    Repository description: Unnamed repository; edit this file &apos;description&apos; to name the...&#xa;"><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 &apos;description&apos; to name the repository.&#xa;</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

In [7]:
!nmap --script-help=./python-http-git.nse
Starting Nmap 7.60 ( https://nmap.org ) at 2018-05-30 09:42 EDT

python-http-git
Categories: discovery safe
https://nmap.org/nsedoc/scripts/python-http-git.html
  Performs a HEAD request for the root folder ("/") of a web server and displays the HTTP headers returned.

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)
In [8]:
!nmap -d --script=./python-http-git.nse -p 8000 127.0.0.1 
Starting Nmap 7.60 ( https://nmap.org ) at 2018-05-30 09:47 EDT
--------------- Timing report ---------------
  hostgroups: min 1, max 100000
  rtt-timeouts: init 1000, min 100, max 10000
  max-scan-delay: TCP 1000, UDP 1000, SCTP 1000
  parallelism: min 0, max 0
  max-retries: 10, host-timeout: 0
  min-rate: 0, max-rate: 0
---------------------------------------------
NSE: Using Lua 5.3.
NSE: Arguments from CLI: 
NSE: Loaded 1 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 1) scan.
Initiating NSE at 09:47
Completed NSE at 09:47, 0.00s elapsed
Initiating Ping Scan at 09:47
Scanning 127.0.0.1 [2 ports]
Completed Ping Scan at 09:47, 0.00s elapsed (1 total hosts)
Overall sending rates: 9433.96 packets / s.
mass_rdns: Using DNS server 192.168.221.2
Initiating Connect Scan at 09:47
Scanning localhost (127.0.0.1) [1 port]
Discovered open port 8000/tcp on 127.0.0.1
Completed Connect Scan at 09:47, 0.00s elapsed (1 total ports)
Overall sending rates: 4000.00 packets / s.
NSE: Script scanning 127.0.0.1.
NSE: Starting runlevel 1 (of 1) scan.
Initiating NSE at 09:47
NSE: Starting python-http-git against 127.0.0.1:8000.
NSE: [python-http-git 127.0.0.1:8000] HTTP: Host supports HEAD.
NSE: [python-http-git 127.0.0.1:8000] This is the Server attribute in the response:SimpleHTTP/0.6 Python/3.6.5
NSE: Finished python-http-git against 127.0.0.1:8000.
Completed NSE at 09:47, 0.00s elapsed
Nmap scan report for localhost (127.0.0.1)
Host is up, received conn-refused (0.00016s latency).
Scanned at 2018-05-30 09:47:13 EDT for 0s

PORT     STATE SERVICE  REASON
8000/tcp open  http-alt syn-ack
| python-http-git: 
|   Server: SimpleHTTP/0.6 Python/3.6.5
|   Date: Wed, 30 May 2018 13:47:13 GMT
|   Content-type: text/html; charset=utf-8
|   Content-Length: 375
|   
|_  (Request type: HEAD)
Final times for host: srtt: 158 rttvar: 3753  to: 100000

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 1) scan.
Initiating NSE at 09:47
Completed NSE at 09:47, 0.00s elapsed
Read from /usr/bin/../share/nmap: nmap-payloads nmap-services.
Nmap done: 1 IP address (1 host up) scanned in 0.24 seconds

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 andnil``` 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.

In [9]:
!nmap --script=./python-http-git.nse -p 80,8000,8888 127.0.0.1 
Starting Nmap 7.60 ( https://nmap.org ) at 2018-05-30 09:49 EDT
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00025s latency).

PORT     STATE  SERVICE
80/tcp   closed http
8000/tcp open   http-alt
| python-http-git: 
|   Server: SimpleHTTP/0.6 Python/3.6.5
|   Date: Wed, 30 May 2018 13:49:50 GMT
|   Content-type: text/html; charset=utf-8
|   Content-Length: 375
|   
|   (Request type: HEAD)
|_  (Server type: Python HTTP Server)
8888/tcp open   sun-answerbook

Nmap done: 1 IP address (1 host up) scanned in 0.23 seconds

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:

In [11]:
!nmap --script=./python-http-git.nse -p 80,8000,8888 127.0.0.1 
Starting Nmap 7.60 ( https://nmap.org ) at 2018-05-30 09:59 EDT
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00013s latency).

PORT     STATE  SERVICE
80/tcp   closed http
8000/tcp open   http-alt
| python-http-git: 
|   127.0.0.1:8000/.git/
|     Git repository found!
|_    Repository description: Unnamed repository; edit this file 'description' to name the...
8888/tcp open   sun-answerbook

Nmap done: 1 IP address (1 host up) scanned in 0.24 seconds

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.

Wrap-up

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.

Happy Hacking with NMAP!!!!