<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ nmap - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ nmap - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 26 May 2026 16:23:30 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/nmap/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Automate Information Gathering for Ethical Hackers — AutoRecon Tutorial ]]>
                </title>
                <description>
                    <![CDATA[ When you’re doing a penetration test, your first job is to understand the target. Before you touch a single exploit or send a single payload, you need to know what services are running, what ports are open, what technologies are in play, and where th... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-automate-information-gathering-for-ethical-hackers-autorecon-tutorial/</link>
                <guid isPermaLink="false">680a540ef12791f5c752af5e</guid>
                
                    <category>
                        <![CDATA[ ethicalhacking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ hacking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ information gathering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nmap ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Thu, 24 Apr 2025 15:09:02 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1745507318904/b27dc949-dbbb-43c2-85e1-072f91f3971f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you’re doing a penetration test, your first job is to understand the target.</p>
<p>Before you touch a single exploit or send a single payload, you need to know what services are running, what ports are open, what technologies are in play, and where the weak spots might be.</p>
<p>This phase is called <strong>reconnaissance</strong>. It can eat up hours – sometimes even days – if you’re doing it manually.</p>
<p>That’s where <a target="_blank" href="https://github.com/Tib3rius/AutoRecon"><strong>Autorecon</strong></a> comes in.</p>
<h2 id="heading-what-is-autorecon"><strong>What is AutoRecon?</strong></h2>
<p>Autorecon is a tool that automates most of the initial recon work. It’s not a magic box, but it’s close.</p>
<p>Autorecon takes a list of IPs or domain names and runs a series of predefined scans. Then it organizes the output neatly so you don’t waste time parsing through raw Nmap files or rerunning missed commands.</p>
<p>If you’re just starting out with pentesting – whether you’re on your first TryHackMe box or your tenth OSCP practice lab – Autorecon can save you a ton of time. Let’s break down how it works.</p>
<h2 id="heading-what-exactly-does-autorecon-do"><strong>What Exactly Does Autorecon Do?</strong></h2>
<p>At its core, Autorecon does three things:</p>
<ol>
<li><p><strong>Runs Nmap scans</strong> on each target IP or hostname.</p>
</li>
<li><p><strong>Identifies services</strong> running on open ports.</p>
</li>
<li><p><strong>Runs specific enumeration tools</strong> based on those services.</p>
</li>
</ol>
<p>Let’s say you run it against an IP that has ports 22 (SSH), 80 (HTTP), and 139/445 (SMB) open. Autorecon will:</p>
<ul>
<li><p>Use Nmap to check versions and scripts for each port.</p>
</li>
<li><p>Run <code>nikto</code> or <code>gobuster</code> on port 80.</p>
</li>
<li><p>Run <code>enum4linux</code> or <code>smbmap</code> on SMB.</p>
</li>
<li><p>Store everything in organized folders for later review.</p>
</li>
</ul>
<p>That’s what you’d do manually – but faster, cleaner, and without forgetting steps.</p>
<h2 id="heading-how-to-use-autorecon"><strong>How to Use Autorecon</strong></h2>
<p>Let’s walk through a quick example. Assume you have a target at <code>10.129.8.143</code>.</p>
<p>Here’s the basic command:</p>
<pre><code class="lang-plaintext">autorecon 10.129.8.143
</code></pre>
<p>That’s it. No flags, no extra setup. Autorecon takes care of the rest. To understand what is going on behind the scenes, let's add the verbosity <code>-v</code> flag.</p>
<p>Here is a sample result.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745145447038/9132b17d-417e-464b-894e-fb68256e88f8.webp" alt="Autorecon scan result" class="image--center mx-auto" width="1100" height="769" loading="lazy"></p>
<p>Behind the scenes, it creates a folder structure like this:</p>
<pre><code class="lang-plaintext">results/
├── 10.129.8.143/
│   ├── scans/
│   │   ├── nmap/
│   │   └── gobuster/
│   ├── reports/
│   └── notes.txt
</code></pre>
<p>You’ll find full Nmap outputs, service-specific tool results, and even a place to jot down your own observations. All ready to go.</p>
<p>If you want to scan multiple targets, just pass a list:</p>
<pre><code class="lang-plaintext">autorecon targets.txt
</code></pre>
<p>Once Autorecon completes a scan, go to the <code>results/&lt;IP&gt;/scans/</code> folder. Start with the Nmap outputs.</p>
<p>Look for open ports and services:</p>
<ul>
<li><p><strong>Port 80 open?</strong> Check <code>gobuster</code> and <code>nikto</code> outputs in the HTTP folder.</p>
</li>
<li><p><strong>SMB ports open?</strong> Look in the <code>enum4linux</code> and <code>smbmap</code> results to find shared drives or user info.</p>
</li>
<li><p><strong>FTP anonymous login allowed?</strong> Use that access to explore directories.</p>
</li>
</ul>
<p>These findings will give you the next steps – like browsing a web service, crafting a payload, or checking for known exploits.</p>
<h2 id="heading-why-its-a-big-deal-for-beginners"><strong>Why It’s a Big Deal for Beginners</strong></h2>
<p>If you’re new to pentesting, one of the hardest parts is remembering <em>everything</em> you’re supposed to check. You pop open a port, and you think:</p>
<ul>
<li><p>“Wait… Should I run <code>enum4linux</code> on this?”</p>
</li>
<li><p>“What was that flag for aggressive Nmap scanning again?”</p>
</li>
<li><p>“Did I already check this web service with <code>nikto</code>?”</p>
</li>
</ul>
<p>Autorecon takes that mental load off your shoulders. You can focus on analysis, not babysitting scans.</p>
<p>And here’s another benefit: it helps you <strong>learn the process</strong>.</p>
<p>While Autorecon automates recon, it <em>shows you every tool and command</em> it runs. You can open the raw output, read the flags, and understand <em>why</em> it ran those scans.</p>
<p>Example: You’ll see it runs <code>nmap -sV -sC</code> for version detection and scripts. This helps beginners understand which scans map to which services and why they matter.</p>
<p>As it runs, you’ll see all the tools and commands it’s using. You can look at the raw results, see what worked, and gradually build your own workflow.</p>
<h2 id="heading-what-it-scans-by-default"><strong>What It Scans (By Default)</strong></h2>
<p>Here’s a quick overview of what Autorecon runs based on port and service:</p>
<p><strong>Nmap</strong>:</p>
<ul>
<li><p>Quick scan</p>
</li>
<li><p>Full TCP port scan</p>
</li>
<li><p>Service/version detection</p>
</li>
<li><p>NSE scripts</p>
</li>
</ul>
<p><strong>HTTP/HTTPS</strong>:</p>
<ul>
<li><p><code>gobuster</code> (directory brute-forcing)</p>
</li>
<li><p><code>nikto</code> (vulnerability scanner)</p>
</li>
<li><p><code>whatweb</code> (tech detection)</p>
</li>
</ul>
<p><strong>SMB</strong>:</p>
<ul>
<li><p><code>enum4linux-ng</code></p>
</li>
<li><p><code>smbmap</code></p>
</li>
<li><p>Nmap SMB scripts</p>
</li>
</ul>
<p><strong>FTP</strong>:</p>
<ul>
<li><p>Anonymous login check</p>
</li>
<li><p>Nmap FTP scripts</p>
</li>
</ul>
<p><strong>SSH</strong>:</p>
<ul>
<li><p>Banner grab</p>
</li>
<li><p>SSH version check</p>
</li>
</ul>
<p>And that’s just a slice. It handles other services too, like MySQL, SNMP, SMTP, and even RPC.</p>
<h2 id="heading-when-autorecon-is-most-useful"><strong>When Autorecon Is Most Useful</strong></h2>
<p>Autorecon shines in certain situations:</p>
<ul>
<li><p><strong>Training labs</strong>: You get a clear view of your target with minimal setup.</p>
</li>
<li><p><strong>OSCP preparation</strong>: It runs the exact recon tools you’ll need to use on the OSCP exam.</p>
</li>
<li><p><strong>Time-limited pentests</strong>: When you need to hit multiple targets fast, Autorecon keeps your output consistent and saves you from retyping everything.</p>
</li>
</ul>
<p>But it’s not just about speed. It’s about being thorough. With manual scanning, it’s easy to miss something small. Autorecon doesn’t forget.</p>
<h2 id="heading-what-autorecon-doesnt-do"><strong>What Autorecon Doesn’t Do</strong></h2>
<p>Autorecon isn’t an exploit tool. It doesn’t hack anything for you. It doesn’t guess credentials or bypass login pages.</p>
<p>It’s focused purely on reconnaissance. That means you still have to:</p>
<ul>
<li><p>Review scan results</p>
</li>
<li><p>Analyze web services manually (for example, browse the site, test inputs)</p>
</li>
<li><p>Decide which exploits or payloads to run</p>
</li>
</ul>
<p>Also, it can be noisy. If you’re on a real engagement where stealth matters, some scans might raise alarms. In that case, you’d want to run more controlled commands manually.</p>
<h2 id="heading-tips-for-using-autorecon-effectively"><strong>Tips for Using Autorecon Effectively</strong></h2>
<p><strong>Use flags to control scans:</strong><br>To increase verbosity and skip previously scanned hosts:</p>
<pre><code class="lang-plaintext">autorecon -v --only-scans-dir 10.129.8.143
</code></pre>
<p><strong>Customize wordlists for better results:</strong><br>By default, Autorecon uses small wordlists. You can improve this:</p>
<pre><code class="lang-plaintext">autorecon --dirbuster.wordlist /usr/share/seclists/Discovery/Web-Content/raft-medium-words.txt 10.129.8.143
</code></pre>
<p>This makes directory brute-forcing more effective, especially on web targets.</p>
<p><strong>Don’t skip the output</strong>: Read the Nmap files, check the HTML reports. Tools don’t think like humans. You still have to connect the dots.</p>
<h2 id="heading-final-thoughts"><strong>Final Thoughts</strong></h2>
<p>Autorecon doesn’t replace your skills – but it helps supercharge them. Instead of spending 30 minutes typing out scan commands, you can run one command and start analyzing in minutes. This helps beginners stay focused, and it helps pros save time.</p>
<p>So if you’re tired of rerunning the same Nmap scans over and over, or you just want cleaner results and fewer mistakes, let Autorecon do the heavy lifting – so you can focus on the part that really matters: breaking stuff.</p>
<p><em>For more cybersecurity tutorials,</em> <a target="_blank" href="https://newsletter.stealthsecurity.sh/"><strong><em>join our newsletter</em></strong></a><em>. To learn the basics of Offensive Cybersecurity, check out our</em> <a target="_blank" href="https://start.stealthsecurity.sh/"><strong><em>Security Starter Course</em></strong></a><em>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Useful Nmap Scripts for Ethical Hackers ]]>
                </title>
                <description>
                    <![CDATA[ Nmap is short for Network Mapper. It’s an open-source Linux command-line tool for scanning IP addresses and ports in a network and detecting installed applications. Nmap allows network admins to identify devices running on their network, discover ope... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/useful-nmap-scripts-for-ethical-hackers/</link>
                <guid isPermaLink="false">672e23dd41db65fcc7264dc0</guid>
                
                    <category>
                        <![CDATA[ nmap ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Scripting ]]>
                    </category>
                
                    <category>
                        <![CDATA[ networking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ scanning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cybersecurity ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Fri, 08 Nov 2024 14:44:45 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731077044881/75a0f1c6-0aae-4ed6-bcfd-777b2ae2b1b6.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Nmap is short for Network Mapper. It’s an open-source Linux command-line tool for scanning IP addresses and ports in a network and detecting installed applications.</p>
<p>Nmap allows network admins to identify devices running on their network, discover open ports and services, and detect vulnerabilities.</p>
<p>Here is the basic syntax to use nmap:</p>
<pre><code class="lang-plaintext">nmap &lt;ip/url&gt;
</code></pre>
<p>Let’s do a quick scan and see what we can find. We can use the URL <a target="_blank" href="http://scanme.nmap.org">scanme.nmap.org</a> to try out a scan. Nmap allows us to use this server to practice scans.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1050/1*daqo4BGtxBZdWF2TLCxQHw.png" alt="Nmap sample scan" width="1050" height="513" loading="lazy"></p>
<p>As you can see, we have found some open ports and services. These act as entry points for further analysis or exploitation.</p>
<p>Nmap is usually the first tool that ethical hackers learn. <a target="_blank" href="https://www.stealthsecurity.sh/p/nmap-tutorial">Here is a full tutorial if you want to learn more about Nma</a><a target="_blank" href="https://www.stealthsecurity.sh/p/nmap-tutorial">p.</a></p>
<h2 id="heading-nmap-scripting-engine">Nmap Scripting Engine</h2>
<p>A key feature is the Nmap Scripting Engine (NSE). It lets users run scripts to do detailed network scans and gather specific information.</p>
<p>Scripts help you perform a list of actions automatically instead of performing them step by step.</p>
<p>These scripts cover a range of functionalities, from service detection to vulnerability scanning. In this article, we’ll look at a few useful Nmap scripts.</p>
<p>I’ll walk you through each script, explain what it does, and show you how to use it. By the end, you’ll have a solid understanding of how to use these scripts as an ethical hacker.</p>
<blockquote>
<p><strong><em>Note:</em></strong> <em>This tutorial is to help you understand network security. Hacking or even scanning another server without permission is illegal.</em></p>
</blockquote>
<h1 id="heading-http-enum"><strong>HTTP-Enum</strong></h1>
<p>Imagine you’re tasked with checking a website’s security and want to see if there are any hidden pages or directories. You suspect there might be admin panels, login pages, or test files that aren’t linked on the main site.</p>
<p>Finding these hidden areas could reveal critical security weaknesses, such as unprotected admin pages or old files that might still hold sensitive information.</p>
<p>The <code>http-enum</code> script is used to scan a web server and find common directories and files that might be hidden from the main site navigation.</p>
<p>Think of it like opening doors in a building to see what’s behind each one. It searches for paths like login pages, admin panels, config files, and other directories that aren’t typically linked on the main website.</p>
<p>For example, a login page or an admin section may exist at specific paths but aren’t visible to regular users. This information is useful because knowing these locations can help you identify security weak points.</p>
<p>Here is the command to run the http-enum script:</p>
<pre><code class="lang-plaintext">nmap - script http-enum -p 80 &lt;target-ip&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730379312717/5e6300c2-0030-4400-b998-e395c0b69a4f.png" alt="http-enum sample response" width="1413" height="923" loading="lazy"></p>
<p>As you can see, the above sample result shows /login.php, /docs and other exposed URL paths. These can be entry points to find restricted information in a web server.</p>
<h1 id="heading-smb-os-discovery"><strong>SMB-OS-Discovery</strong></h1>
<p>Suppose you’re exploring a company’s network to understand what kind of systems they have in place, specifically in a Windows environment.</p>
<p>Knowing the exact operating system and version of each server helps you assess vulnerabilities. For example, an older version of Windows might have unpatched flaws that need attention.</p>
<p>The <code>smb-os-discovery</code> script targets servers that use the SMB protocol, mainly found in Windows environments, to gather information about the server’s operating system. It can reveal details like the Windows version, the server name, and its domain.</p>
<p>This script helps you understand what type of system you’re dealing with, which is key for checking security flaws specific to that OS.</p>
<p>Here is the syntax to run the smb-os-discovery script.</p>
<pre><code class="lang-plaintext">nmap - script smb-os-discovery -p 445 &lt;target-ip&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730444262829/c8f76aee-d0a6-4203-b572-17df1272211c.png" alt="smb-os-discovery sample result" width="1200" height="850" loading="lazy"></p>
<p>As you can see in the above sample result, the script connects to the SMB service on the target and retrieves OS information. This can help you quickly identify the Windows version and other details about the server.</p>
<h1 id="heading-http-headers"><strong>HTTP-Headers</strong></h1>
<p>Imagine you’re evaluating a website’s configuration and security settings. You want to know what kind of server it’s running, what methods are allowed, and if it’s enforcing HTTPS connections.</p>
<p>These details give you insights into whether the server’s configuration aligns with best practices, helping you spot any missing security settings.</p>
<p>The <code>http-headers</code> script checks the headers sent by a web server when a user connects to it it. Headers tell you the server type (like Apache or NGINX), security settings (like HTTPS requirements), allowed methods, and caching rules.</p>
<p>These details are like the server’s blueprint for communication, often revealing if the server has certain protections enabled.</p>
<p>Here is the syntax to run the http-headers script:</p>
<pre><code class="lang-plaintext">nmap - script http-headers -p 80 &lt;target-ip&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730380306638/703870e4-de5b-4858-b1b2-20634a8598a9.png" alt="http-headers sample response" width="1427" height="905" loading="lazy"></p>
<p>You can see that the sample response shows headers like like <code>X-Powered-By</code>, <code>Set-Cookie</code>, and so on. These headers can help to find security issues such as cross-site scripting (XSS) and clickjacking.</p>
<h1 id="heading-ssh-brute"><strong>SSH-Brute</strong></h1>
<p>Let’s say you’re testing a server’s defenses against unauthorized access through SSH. You know that weak passwords are a common risk, so you need a way to check if any accounts have easily guessable credentials.</p>
<p>This test will help you identify weak SSH logins that need stronger passwords to protect the server.</p>
<p>The <code>ssh-brute</code> script tries to log into an SSH server by guessing usernames and passwords. SSH, or Secure Shell, is often used for remote logins.</p>
<p>If the usernames and passwords are easy to guess, this script might find a way in. It’s a useful test to see if login credentials are strong enough to prevent unauthorized access.</p>
<p>Here is the syntax to run the ssh-brute script:</p>
<pre><code class="lang-plaintext">nmap - script ssh-brute -p 22 &lt;target-ip&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730811335746/c6f6ad27-37d7-467d-8f5c-cd71a37aff0f.jpeg" alt="ssh-brute sample response" width="1219" height="834" loading="lazy"></p>
<p>As you can see, this script tries different username-password combinations on the SSH server. If successful, it will display the correct credentials.</p>
<h1 id="heading-dns-brute"><strong>DNS-Brute</strong></h1>
<p>Imagine you’re mapping out a company’s network and want to see if they have any subdomains that aren’t publicly listed. Each subdomain might serve a different purpose, such as hosting email servers or internal testing sites.</p>
<p>Discovering these subdomains helps you check if any of them are exposing sensitive services.</p>
<p>The <code>dns-brute</code> script helps you find subdomains associated with a given domain by trying out common names, like “www,” “mail,” or “ftp.” Subdomains can host separate services and applications, each with its own set of vulnerabilities.</p>
<p>Here is the syntax to run the dns-brute script:</p>
<pre><code class="lang-plaintext">nmap - script dns-brute &lt;target-domain&gt;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1730379500675/c8c646c3-1d76-440a-8092-6ce26f9aa127.png" alt="dns-brute script" width="1408" height="923" loading="lazy"></p>
<p>As you can see, the script attempts to resolve a list of common subdomains, and finds one internal hostname. Using this script can reveal subdomains that aren’t listed in public records, helping you to gain a fuller picture of an organization’s network layout.</p>
<h1 id="heading-conclusion"><strong>Conclusion</strong></h1>
<p>These Nmap scripts provide a powerful way to audit, troubleshoot, and secure networks. By understanding what each script does and how to use it, you’ll be able to uncover hidden issues and safeguard your infrastructure.</p>
<p><strong>To learn how to build a career in Cybersecurity, check out</strong> <a target="_blank" href="https://book.stealthsecurity.sh/?utm_source=www.stealthsecurity.sh&amp;utm_medium=newsletter&amp;utm_campaign=top-cybersecurity-certifications-you-should-know-about"><strong><em>The Hacker’s Han</em></strong></a><a target="_blank" href="https://book.stealthsecurity.sh/?utm_source=www.stealthsecurity.sh&amp;utm_medium=newsletter&amp;utm_campaign=top-cybersecurity-certifications-you-should-know-about"><strong><em>dbook</em>. To practice ha</strong></a><strong>cking real systems and get help from other hackers, join</strong> <a target="_blank" href="https://www.skool.com/hackershub?utm_source=www.stealthsecurity.sh&amp;utm_medium=newsletter&amp;utm_campaign=top-cybersecurity-certifications-you-should-know-about"><strong><em>The Hacker’</em></strong></a><a target="_blank" href="https://www.skool.com/hackershub?utm_source=www.stealthsecurity.sh&amp;utm_medium=newsletter&amp;utm_campaign=top-cybersecurity-certifications-you-should-know-about"><strong><em>s Hub</em>.</strong></a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Enhance Nmap with Python ]]>
                </title>
                <description>
                    <![CDATA[ Very few pieces of Open Source software generate so much hype as Nmap. It is one of those tools that packs in so many useful features that it can help you make your systems more secure by just running it with a few flags. Nmap ("Network Mapper") is a... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/enhance-nmap-with-python/</link>
                <guid isPermaLink="false">66d8513be33373caf28c5ff4</guid>
                
                    <category>
                        <![CDATA[ computer networking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cybersecurity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nmap ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jose Vicente Nunez ]]>
                </dc:creator>
                <pubDate>Tue, 08 Feb 2022 19:28:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/02/home_nmap.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Very few pieces of Open Source software generate <a target="_blank" href="https://nmap.org/movies/">so much hype</a> as <a target="_blank" href="https://nmap.org/">Nmap</a>. It is one of those tools that packs in so many useful features that it can help you make your systems more secure by just running it with a few flags.</p>
<p>Nmap ("Network Mapper") is a free and open source utility for network discovery and security auditing.</p>
<p>Many systems and network administrators also find it useful for tasks such as network inventory, managing service upgrade schedules, and monitoring host or service uptime.</p>
<p>You can also use it to bypass weak protections, find hidden or mis-configured services, or just to give you a better understanding how networks work.</p>
<h2 id="heading-table-of-contents">Table of contents:</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-you-will-learn-from-this-article">What you will learn from this article</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-nmap-101-identify-all-the-public-services-in-our-network">Nmap 101: Identify all the public services in our network</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-write-an-easy-button-network-ccanner-that-uses-nmap">How to Write an 'easy button' Network Scanner that Uses Nmap</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-make-a-home-network-scanner-a-web-service">How to Make a Home Network Scanner a Web Service</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-did-we-learn">What did we learn?</a></p>
</li>
</ul>
<h2 id="heading-what-you-will-learn-from-this-article">What you will learn from this article</h2>
<p>We will cover the following to illustrate how you can enhance Nmap with Python:</p>
<ul>
<li><p>Write a small script that can scan all the hosts on the local network, making sure it runs with the proper privileges.</p>
</li>
<li><p>Enhance Nmap by correlating services with security advisories.</p>
</li>
<li><p>Convert our scripts into a web-service. Will add basic security (authorization and encryption).</p>
</li>
</ul>
<h3 id="heading-things-you-should-know-and-do-before-starting">Things you should know and do before starting</h3>
<p>Don't worry too much, as I will guide you through the steps. This will be a fun experience, and you'll have all the source code to follow along:</p>
<ul>
<li><p>Be familiar with basic network concepts like <a target="_blank" href="https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing">Classless inter-domain routing (CIDR)</a></p>
</li>
<li><p>Be able to write a program in a scripting language like <a target="_blank" href="https://www.python.org/">Python</a>.</p>
</li>
<li><p>The code from can be installed using a virtual environment. If you are not familiar with a virtual environment, you can read the following: <a target="_blank" href="https://www.redhat.com/sysadmin/packaging-applications-python">Packaging applications to install on other machines with Python</a>.</p>
</li>
</ul>
<h3 id="heading-what-tools-you-will-need-for-this-tutorial">What tools you will need for this tutorial?</h3>
<p>I won't cover the installation of any of these tools, but there is plenty of documentation out there to get you started.</p>
<ul>
<li><p>Install the whole code from this tutorial by following the instructions as explained on the main <a target="_blank" href="http://README.md">README document</a> file on my GitHub <a target="_blank" href="https://github.com/josevnz/home_nmap">official repository site</a>. You will need to <a target="_blank" href="https://git-scm.com/book/en/v2/Getting-Started-Installing-Git">install</a> <a target="_blank" href="https://git-scm.com/docs/gittutorial">Git</a> to clone the code.</p>
</li>
<li><p>A Linux distribution. Fedora, Ubuntu, Kali, feel free to use the one you feel most comfortable with (I used <a target="_blank" href="https://docs.fedoraproject.org/en-US/fedora/rawhide/install-guide/">Fedora</a> 35.)</p>
</li>
<li><p><a target="_blank" href="https://developer.fedoraproject.org/tech/languages/python/python-installation.html">Python interpreter</a>. A good Linux distribution will come with Python pre-installed or at least will make it easier for you to install. I used Python 3.9 here.</p>
</li>
</ul>
<p>Last <em>two things</em>:</p>
<ul>
<li><p>I skipped <strong>some</strong> imports in the code snippets as they do not enhance the code demonstrations. To get the most accurate code, please do clone the public Git repository for this tutorial and open the source code.</p>
</li>
<li><p><em>Only run this examples against your local network</em>. You can be curious, have fun, and learn new things about existing tools without affecting others.</p>
</li>
</ul>
<p>Hacking is about learning!</p>
<h1 id="heading-nmap-101-identify-all-the-public-services-in-our-network">Nmap 101: Identify all the public services in our network</h1>
<h3 id="heading-word-of-caution-the-saying-better-ask-for-forgiveness-than-permission-doesnt-apply-here">Word of caution: The saying 'b_etter ask for forgiveness than permission'_ doesn't apply here</h3>
<p>We do not care about being 'stealth' or triggering an <a target="_blank" href="https://en.wikipedia.org/wiki/Intrusion_detection_system">Intrusion Detection System (IDS)</a> because of our port scanning activity. An IDS normally looks for abnormal network patterns and if it sees a machine opening and closing ports on rapid succession across many hosts that would be considered a port scan attack. Again that won't be the case in our home network because, well, we know it is us running such a scan.</p>
<p>For the same reason you should not launch a port scan on a network you don't own, as Nmap is not 100% stealth (you can always play with randomizing frequency, type of TCP handshake, number of ports opened, use a proxy, and so on and yet you most likely will miss something).</p>
<p>So better behave, OK? :-)</p>
<h3 id="heading-what-do-we-need-to-run-nmap-and-os-fingerprinting">What do we need to run Nmap and OS fingerprinting?</h3>
<p>The goal here is to see what services are running in our network using a command line interface (CLI) script.</p>
<p>Nmap requires elevated privileges to do the OS fingerprinting and scans using raw sockets. You will need to run the commands as root or <a target="_blank" href="https://www.sudo.ws/">su "do" (SUDO)</a> to elevate your permissions. A SUDO rule to do this is similar to this (file /etc/sudoers):</p>
<pre><code class="lang-shell">## Same thing without a password
%wheel    ALL=(ALL)    NOPASSWD: ALL
</code></pre>
<p>This means that anyone on the 'wheel' unix group can run commands as root:</p>
<pre><code class="lang-shell">(2600) [josevnz@dmaf5 2600]$ grep wheel /etc/group
wheel:x:10:josevnz,services

# To confirm we can run commands as root
(2600) [josevnz@dmaf5 2600]$ sudo -l
Matching Defaults entries for josevnz on dmaf5:
    !visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin, env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS",
    env_keep+="MAIL QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE", env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY
    LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE", env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY",
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/var/lib/snapd/snap/bin

User josevnz may run the following commands on dmaf5:
    (ALL) NOPASSWD: ALL
</code></pre>
<p>Next we'll do a quick scan of our local network (in this example is 192.168.1.0/24). I used the -v (verbose) flag to get some progress feedback while scanning for all the ports while also doing OS fingerprinting (-O).</p>
<p>I saved the execution of the Nmap run to an XML file (-oX), which Nmap can use to resume execution if it gets interrupted (--resume):</p>
<pre><code class="lang-shell"># In case the scan is interrupted: nmap --resume $HOME/home_scan.xml
[josevnz@dmaf5 docs]$ sudo nmap -v -n -p- -sT -sV -O --osscan-limit --max-os-tries 1 -oX $HOME/home_scan.xml 192.168.1.0/24
Starting Nmap 7.80 ( https://nmap.org ) at 2021-12-30 16:35 EST
NSE: Loaded 45 scripts for scanning.
Initiating ARP Ping Scan at 16:35
Scanning 254 hosts [1 port/host]
...
# After a while and several cups of Venezuelan coffee...
Network Distance: 1 hop
TCP Sequence Prediction: Difficulty=265 (Good luck!)
IP ID Sequence Generation: All zeros

Nmap scan report for 192.168.1.20
Host is up (0.0097s latency).
Not shown: 65530 closed ports
PORT      STATE    SERVICE      VERSION
36184/tcp filtered unknown
37309/tcp filtered unknown
49323/tcp open     unknown
49376/tcp filtered unknown
62078/tcp open     iphone-sync?
MAC Address: 9E:90:75:3A:D7:XX (Unknown)
...
</code></pre>
<p>The resulting <a target="_blank" href="https://nmap.org/book/output-formats-xml-output.html">XML format file</a> is very verbose:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">host</span> <span class="hljs-attr">starttime</span>=<span class="hljs-string">"1640901327"</span> <span class="hljs-attr">endtime</span>=<span class="hljs-string">"1640902555"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">status</span> <span class="hljs-attr">state</span>=<span class="hljs-string">"up"</span> <span class="hljs-attr">reason</span>=<span class="hljs-string">"arp-response"</span> <span class="hljs-attr">reason_ttl</span>=<span class="hljs-string">"0"</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">address</span> <span class="hljs-attr">addr</span>=<span class="hljs-string">"192.168.1.1"</span> <span class="hljs-attr">addrtype</span>=<span class="hljs-string">"ipv4"</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">address</span> <span class="hljs-attr">addr</span>=<span class="hljs-string">"38:5B:5E:1D:52:99"</span> <span class="hljs-attr">addrtype</span>=<span class="hljs-string">"mac"</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">hostnames</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">hostnames</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ports</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">extraports</span> <span class="hljs-attr">state</span>=<span class="hljs-string">"closed"</span> <span class="hljs-attr">count</span>=<span class="hljs-string">"65523"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">extrareasons</span> <span class="hljs-attr">reason</span>=<span class="hljs-string">"conn-refused"</span> <span class="hljs-attr">count</span>=<span class="hljs-string">"65523"</span>/&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">extraports</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">port</span> <span class="hljs-attr">protocol</span>=<span class="hljs-string">"tcp"</span> <span class="hljs-attr">portid</span>=<span class="hljs-string">"139"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">state</span> <span class="hljs-attr">state</span>=<span class="hljs-string">"open"</span> <span class="hljs-attr">reason</span>=<span class="hljs-string">"syn-ack"</span> <span class="hljs-attr">reason_ttl</span>=<span class="hljs-string">"0"</span>/&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">service</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"netbios-ssn"</span> <span class="hljs-attr">product</span>=<span class="hljs-string">"Samba smbd"</span> <span class="hljs-attr">version</span>=<span class="hljs-string">"3.X - 4.X"</span> <span class="hljs-attr">extrainfo</span>=<span class="hljs-string">"workgroup: ZZZ"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"probed"</span> <span class="hljs-attr">conf</span>=<span class="hljs-string">"10"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">cpe</span>&gt;</span>cpe:/a:samba:samba<span class="hljs-tag">&lt;/<span class="hljs-name">cpe</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">service</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">port</span>&gt;</span>
    ...
</code></pre>
<p>Time to do some coding. Parsing data in many formats is one of Python's strengths. Data is extracted and normalized for all the ports that are not 'closed' using <a target="_blank" href="https://github.com/lxml/lxml">lxml</a>:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OutputParser</span>:</span>
    <span class="hljs-string">"""
    Parse Nmap raw XML output
    """</span>

<span class="hljs-meta">    @staticmethod</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse_nmap_xml</span>(<span class="hljs-params">xml: str</span>) -&gt; (str, Any):</span>
        <span class="hljs-string">"""
        Parse XML and return details for the scanned ports
        @param xml:
        @return: tuple nmaps arguments, port details
        """</span>
        parsed_data = []
        root = ElementTree.fromstring(xml)
        nmap_args = root.attrib[<span class="hljs-string">'args'</span>]
        <span class="hljs-keyword">for</span> host <span class="hljs-keyword">in</span> root.findall(<span class="hljs-string">'host'</span>):
            <span class="hljs-keyword">for</span> address <span class="hljs-keyword">in</span> host.findall(<span class="hljs-string">'address'</span>):
                curr_address = address.attrib[<span class="hljs-string">'addr'</span>]
                data = {
                    <span class="hljs-string">'address'</span>: curr_address,
                    <span class="hljs-string">'ports'</span>: []
                }
                states = host.findall(<span class="hljs-string">'ports/port/state'</span>)
                ports = host.findall(<span class="hljs-string">'ports/port'</span>)
                <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(ports)):
                    <span class="hljs-keyword">if</span> states[i].attrib[<span class="hljs-string">'state'</span>] == <span class="hljs-string">'closed'</span>:
                        <span class="hljs-keyword">continue</span>  <span class="hljs-comment"># Skip closed ports</span>
                    port_id = ports[i].attrib[<span class="hljs-string">'portid'</span>]
                    protocol = ports[i].attrib[<span class="hljs-string">'protocol'</span>]
                    services = ports[i].findall(<span class="hljs-string">'service'</span>)
                    cpe_list = []
                    service_name = <span class="hljs-string">""</span>
                    service_product = <span class="hljs-string">""</span>
                    service_version = <span class="hljs-string">""</span>
                    <span class="hljs-keyword">for</span> service <span class="hljs-keyword">in</span> services:
                        <span class="hljs-keyword">for</span> key <span class="hljs-keyword">in</span> [<span class="hljs-string">'name'</span>, <span class="hljs-string">'product'</span>, <span class="hljs-string">'version'</span>]:
                            <span class="hljs-keyword">if</span> key <span class="hljs-keyword">in</span> service.attrib:
                                <span class="hljs-keyword">if</span> key == <span class="hljs-string">'name'</span>:
                                    service_name = service.attrib[<span class="hljs-string">'name'</span>]
                                <span class="hljs-keyword">elif</span> key == <span class="hljs-string">'product'</span>:
                                    service_product = service.attrib[<span class="hljs-string">'product'</span>]
                                <span class="hljs-keyword">elif</span> key == <span class="hljs-string">'version'</span>:
                                    service_version = service.attrib[<span class="hljs-string">'version'</span>]
                        cpes = service.findall(<span class="hljs-string">'cpe'</span>)
                        <span class="hljs-keyword">for</span> cpe <span class="hljs-keyword">in</span> cpes:
                            cpe_list.append(cpe.text)
                        data[<span class="hljs-string">'ports'</span>].append({
                            <span class="hljs-string">'port_id'</span>: port_id,
                            <span class="hljs-string">'protocol'</span>: protocol,
                            <span class="hljs-string">'service_name'</span>: service_name,
                            <span class="hljs-string">'service_product'</span>: service_product,
                            <span class="hljs-string">'service_version'</span>: service_version,
                            <span class="hljs-string">'cpes'</span>: cpe_list
                        })
                        parsed_data.append(data)
        <span class="hljs-keyword">return</span> nmap_args, parsed_data
</code></pre>
<p>Once the data is collected we can create a nice table in the terminal with the help of <a target="_blank" href="https://github.com/Textualize/rich">Rich</a>.</p>
<p>The table has the following columns:</p>
<ul>
<li><p>Internet Protocol (IP) address</p>
</li>
<li><p>Protocol: On this script it will always be Transfer Control Protocol (TCP)</p>
</li>
<li><p>Port ID: The port number where the service runs</p>
</li>
<li><p>Service: An networked service like Secure Shell (SSH)</p>
</li>
<li><p>Common Platform Enumeration (<a target="_blank" href="https://nvd.nist.gov/products/cpe">CPE</a>): Is a structured naming scheme for information technology systems, software, and packages.</p>
</li>
<li><p>Advisories: Any vulnerability related to the CPE identified by Nmap. Will need to correlate those ourselves.</p>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_scan_table</span>(<span class="hljs-params">*, cli: str</span>) -&gt; Table:</span>
    <span class="hljs-string">"""
    Create a table for the CLI UI
    :param cli: Full Nmap arguments used on the run
    :return: Skeleton table, no data
    """</span>
    nmap_table = Table(title=<span class="hljs-string">f"NMAP run info: <span class="hljs-subst">{cli}</span>"</span>)
    nmap_table.add_column(<span class="hljs-string">"IP"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"cyan"</span>, no_wrap=<span class="hljs-literal">True</span>)
    nmap_table.add_column(<span class="hljs-string">"Protocol"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"cyan"</span>, no_wrap=<span class="hljs-literal">True</span>)
    nmap_table.add_column(<span class="hljs-string">"Port ID"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"magenta"</span>, no_wrap=<span class="hljs-literal">True</span>)
    nmap_table.add_column(<span class="hljs-string">"Service"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"green"</span>)
    nmap_table.add_column(<span class="hljs-string">"CPE"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"blue"</span>)
    nmap_table.add_column(<span class="hljs-string">"Advisories"</span>, justify=<span class="hljs-string">"right"</span>, style=<span class="hljs-string">"blue"</span>)
    <span class="hljs-keyword">return</span> nmap_table
...
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fill_simple_table</span>(<span class="hljs-params">*, exec_data: str, parsed_xml: Dict[Any, Any]</span>) -&gt; Table:</span>
    <span class="hljs-string">"""
    Convenience method to create a simple UI table with Nmap XML output
    :param exec_data: Arguments and options used to run Nmap
    :param parsed_xml: Nmap data as a dictionary
    :return: Populated tabled
    """</span>
    nmap_table = create_scan_table(cli=exec_data)
    <span class="hljs-keyword">for</span> row_data <span class="hljs-keyword">in</span> parsed_xml:
        address = row_data[<span class="hljs-string">'address'</span>]
        ports = row_data[<span class="hljs-string">'ports'</span>]
        <span class="hljs-keyword">for</span> port_data <span class="hljs-keyword">in</span> ports:
            nmap_table.add_row(
                address,
                port_data[<span class="hljs-string">'protocol'</span>],
                port_data[<span class="hljs-string">'port_id'</span>],
                <span class="hljs-string">f"<span class="hljs-subst">{port_data[<span class="hljs-string">'service_name'</span>]}</span> <span class="hljs-subst">{port_data[<span class="hljs-string">'service_product'</span>]}</span> <span class="hljs-subst">{port_data[<span class="hljs-string">'service_version'</span>]}</span>"</span>,
                <span class="hljs-string">"\n"</span>.join(port_data[<span class="hljs-string">'cpes'</span>]),
                <span class="hljs-string">""</span>
            )
    <span class="hljs-keyword">return</span> nmap_table
</code></pre>
<p>The resulting script uses the code above to give the user the whole picture about the local network scan:</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python</span>
<span class="hljs-keyword">import</span> sys
<span class="hljs-keyword">from</span> rich.console <span class="hljs-keyword">import</span> Console
<span class="hljs-keyword">from</span> home_nmap.query <span class="hljs-keyword">import</span> OutputParser
<span class="hljs-keyword">from</span> home_nmap.ui <span class="hljs-keyword">import</span> fill_simple_table

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    console = Console()
    <span class="hljs-keyword">for</span> nmap_xml <span class="hljs-keyword">in</span> sys.argv[<span class="hljs-number">1</span>:]:
        <span class="hljs-keyword">with</span> open(nmap_xml, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> xml:
            xml_data = xml.read()
            rundata, parsed = OutputParser.parse_nmap_xml(xml_data)
            nmap_table = fill_simple_table(exec_data=rundata, parsed_xml=parsed)
            console.print(nmap_table)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/nmap_scan_rpt_noadvisories.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Scan for local network. Advisories column is empty</em></p>
<p>If you notice, the 'Advisories' column is left completely empty. We'll use the <a target="_blank" href="https://www.nist.gov/cybersecurity">NIST cybersecurity website search engine</a> to populate the missing advisories, by-passing the CPE that have <em>version information</em> to avoid false positives.</p>
<p>We use <a target="_blank" href="https://github.com/psf/requests">requests</a> to help us with the HTTP communication:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> dataclasses <span class="hljs-keyword">import</span> dataclass
<span class="hljs-keyword">import</span> requests
IGNORED_CPES = {<span class="hljs-string">"cpe:/o:linux:linux_kernel"</span>}
<span class="hljs-keyword">from</span> cpe <span class="hljs-keyword">import</span> CPE
<span class="hljs-keyword">from</span> lxml <span class="hljs-keyword">import</span> html

<span class="hljs-meta">@dataclass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NIDS</span>:</span>
    summary: str
    link: str
    score: str

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NDISHtml</span>:</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Some CPE return too many false positives,
        so they are ignored right out the bat
        """</span>
        self.raw_html = <span class="hljs-literal">None</span>
        self.parsed_results = []
        self.url = <span class="hljs-string">"https://nvd.nist.gov/vuln/search/results"</span>
        self.ignored_cpes = IGNORED_CPES

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get</span>(<span class="hljs-params">self, cpe: str</span>) -&gt; str:</span>
        <span class="hljs-string">"""
        Run a CPE search on the NDIS website. If the CPE has no version then skip the search
        as it will return too many false positives
        @param cpe: CPE identifier coming from Nmap, like cpe:/a:openbsd:openssh:8.0
        @return:
        """</span>
        params = {
            <span class="hljs-string">'form_type'</span>: <span class="hljs-string">'Basic'</span>,
            <span class="hljs-string">'results_type'</span>: <span class="hljs-string">'overview'</span>,
            <span class="hljs-string">'search_type'</span>: <span class="hljs-string">'all'</span>,
            <span class="hljs-string">'isCpeNameSearch'</span>: <span class="hljs-string">'false'</span>,
            <span class="hljs-string">'query'</span>: cpe
        }
        <span class="hljs-keyword">if</span> cpe <span class="hljs-keyword">in</span> self.ignored_cpes:
            <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>
        valid_cpe = CPE(cpe)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> valid_cpe.get_version()[<span class="hljs-number">0</span>]:
            <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>
        response = requests.get(
            url=self.url,
            params=params
        )
        response.raise_for_status()
        <span class="hljs-keyword">return</span> response.text

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">parse</span>(<span class="hljs-params">self, html_data: str</span>) -&gt; list[NIDS]:</span>
        <span class="hljs-string">"""
        Parse NDIS web search. Not aware that they offer a REST API that doesn't require parsing.
        It is assumed that this method is never called directly by end users, so no further checks are done on the
        HTML file contents.
        @param html_data: RAW HTML used for scrapping
        @return: List of NDIS, if any
        """</span>
        self.parsed_results = []
        <span class="hljs-keyword">if</span> html_data:
            ndis_html = html.fromstring(html_data)
            <span class="hljs-comment"># 1:1 match between 3 elements, use parallel array</span>
            summary = ndis_html.xpath(<span class="hljs-string">"//*[contains(@data-testid, 'vuln-summary')]"</span>)
            cve = ndis_html.xpath(<span class="hljs-string">"//*[contains(@data-testid, 'vuln-detail-link')]"</span>)
            score = ndis_html.xpath(<span class="hljs-string">"//*[contains(@data-testid, 'vuln-cvss2-link')]"</span>)
            <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(summary)):
                ndis = NIDS(
                    summary=summary[i].text,
                    link=<span class="hljs-string">"https://nvd.nist.gov/vuln/detail/"</span> + cve[i].text,
                    score=score[i].text
                )
                self.parsed_results.append(ndis)
        <span class="hljs-keyword">return</span> self.parsed_results
</code></pre>
<p>Then we correlate the Nmap CPES in the results with each one of the advisories, if any:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Any
<span class="hljs-keyword">from</span> dataclasses <span class="hljs-keyword">import</span> dataclass
<span class="hljs-meta">@dataclass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NIDS</span>:</span>
    summary: str
    link: str
    score: str
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NDISHtml</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">correlate_nmap_with_nids</span>(<span class="hljs-params">self, parsed_xml: Any</span>) -&gt; dict[str, list[NIDS]]:</span>
        correlated_cpe = {}
        <span class="hljs-keyword">for</span> row_data <span class="hljs-keyword">in</span> parsed_xml:
            ports = row_data[<span class="hljs-string">'ports'</span>]
            <span class="hljs-keyword">for</span> port_data <span class="hljs-keyword">in</span> ports:
                <span class="hljs-keyword">for</span> cpe <span class="hljs-keyword">in</span> port_data[<span class="hljs-string">'cpes'</span>]:
                    raw_ndis = self.get(cpe)
                    cpes = self.parse(raw_ndis)
                    correlated_cpe[cpe] = cpes
        <span class="hljs-keyword">return</span> correlated_cpe
</code></pre>
<p>The new table speaks for itself:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/nmap_scan_rpt.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Nmap scan results on a nice table</em></p>
<p>More complete, and we can see now that a few of our local services may have a vulnerability!</p>
<p>Can we do better? For example, it would be nice to be able to run Nmap directly from Python instead of parsing the results of a run, so let's code that.</p>
<h1 id="heading-how-to-write-an-easy-button-network-scanner-that-uses-nmap">How to Write an 'easy button' Network Scanner that Uses Nmap</h1>
<h2 id="heading-how-to-wrap-nmap-with-python-subprocessrun">How to wrap Nmap with Python (subprocess.run)</h2>
<p>Nmap doesn't offer a formal API to interact with external programs. For that reason we will run it from Python and save the results into an XML file. We can then use the data any way we want (see the 'subprocess.run' call in method 'scan' from our class NmapRunner):</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NMapRunner</span>:</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Create a Nmap executor
        """</span>
        self.nmap_report_file = <span class="hljs-literal">None</span>
        found_sudo = shutil.which(<span class="hljs-string">'sudo'</span>, mode=os.F_OK | os.X_OK)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> found_sudo:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"SUDO is missing"</span>)
        self.sudo = found_sudo
        found_nmap = shutil.which(<span class="hljs-string">'nmap'</span>, mode=os.F_OK | os.X_OK)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> found_nmap:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"NMAP is missing"</span>)
        self.nmap = found_nmap

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">scan</span>(<span class="hljs-params">
            self,
            *,
            hosts: str,
            sudo: bool = True
    </span>):</span>
        command = []
        <span class="hljs-keyword">if</span> sudo:
            command.append(self.sudo)
        command.append(self.nmap)
        command.extend(__NMAP__FLAGS__)
        command.append(hosts)
        completed = subprocess.run(
            command,
            capture_output=<span class="hljs-literal">True</span>,
            shell=<span class="hljs-literal">False</span>,
            check=<span class="hljs-literal">True</span>
        )
        completed.check_returncode()
        args, data = OutputParser.parse_nmap_xml(completed.stdout.decode(<span class="hljs-string">'utf-8'</span>))
        <span class="hljs-keyword">return</span> args, data, completed.stderr
</code></pre>
<p><em>Security note</em>: The named argument 'shell=False' tells us that we do not want to create a new shell when running our process. This will provide protection against <a target="_blank" href="https://en.wikipedia.org/wiki/Code_injection#Shell_injection">shell injection</a> attacks.</p>
<h2 id="heading-how-to-speed-up-nmap-remember-all-these-flags-in-a-single-place">How to Speed up Nmap (remember all these flags in a single place)</h2>
<p>Your local network has less latency than the Internet. It will also most likely be easier to scan for open ports and OS fingerprinting because there is no firewall between you and the hosts.</p>
<p>Additionally, we are not concerned of triggering an IDS detection, so you can use the following to reduce the amount of time required to complete the port scanning (Variable <strong>NMAP__FLAGS</strong> in package system):</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> shlex
<span class="hljs-comment"># Convert the args for proper usage on the CLI</span>
NMAP_HOME_NETWORK_DEFAULT_FLAGS = {
    <span class="hljs-string">'-n'</span>: <span class="hljs-string">'Never do DNS resolution'</span>,
    <span class="hljs-string">'-sS'</span>: <span class="hljs-string">'TCP SYN scan, recommended'</span>,
    <span class="hljs-string">'-p-'</span>: <span class="hljs-string">'All ports'</span>,
    <span class="hljs-string">'-sV'</span>: <span class="hljs-string">'Probe open ports to determine service/version info'</span>,
    <span class="hljs-string">'-O'</span>: <span class="hljs-string">'OS Probe. Requires sudo/ root'</span>,
    <span class="hljs-string">'-T4'</span>: <span class="hljs-string">'Aggressive timing template'</span>,
    <span class="hljs-string">'-PE'</span>: <span class="hljs-string">'Enable this echo request behavior. Good for internal networks'</span>,
    <span class="hljs-string">'--version-intensity 5'</span>: <span class="hljs-string">'Set version scan intensity. Default is 7'</span>,
    <span class="hljs-string">'--disable-arp-ping'</span>: <span class="hljs-string">'No ARP or ND Ping'</span>,
    <span class="hljs-string">'--max-hostgroup 20'</span>: <span class="hljs-string">'Hostgroup (batch of hosts scanned concurrently) size'</span>,
    <span class="hljs-string">'--min-parallelism 10'</span>: <span class="hljs-string">'Number of probes that may be outstanding for a host group'</span>,
    <span class="hljs-string">'--osscan-limit'</span>: <span class="hljs-string">'Limit OS detection to promising targets'</span>,
    <span class="hljs-string">'--max-os-tries 1'</span>: <span class="hljs-string">'Maximum number of OS detection tries against a target'</span>,
    <span class="hljs-string">'-oX -'</span>: <span class="hljs-string">'Send XML output to STDOUT, avoid creating a temp file'</span>
}
__NMAP__FLAGS__ = shlex.split(<span class="hljs-string">" "</span>.join(NMAP_HOME_NETWORK_DEFAULT_FLAGS.keys()))
</code></pre>
<p>The Nmap documentation also suggests that you can split the total hostlist across several instances of Nmap (it can be no greater than the number of CPUs in the server running the tool) to increase parallelism. But that doesn't come for free. You will need to worry about issues like race conditions and synchronization in concurrent threads running Nmap.</p>
<p>For now we'll keep it simple and let Nmap take care of any optimizations by providing the flags showed above.</p>
<h2 id="heading-how-to-figure-out-the-local-networks-on-the-machine-where-nmap-runs">How to figure out the local networks on the machine where Nmap runs?</h2>
<p>Our Python script can also check interfaces that are up, skip virtual interfaces,: and skip the special loopback interface. Luckily the kernel publishes all the information we need on /proc/net/dev file:</p>
<pre><code class="lang-shell">(2600) [josevnz@dmaf5 2600]$ cat /proc/net/dev
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
    lo: 18303833  303389    0    0    0     0          0         0 18303833  303389    0    0    0     0       0          0
enp2s0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0
  eno1: 1931173135 3908073    0    1    0     0          0    407486 274206691 3289566    0    0    0     0       0          0
</code></pre>
<p>We can parse it like this (class HostIface, method <strong>refresh_interfaces</strong>):</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HostIface</span>:</span>    
    ...

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__refresh_interfaces__</span>(<span class="hljs-params">self, *, skip_loopback: bool = True, only_alive: bool = True</span>) -&gt; Set[str]:</span>
        <span class="hljs-string">"""
        Alive means an interface that has shown any byte activity since the server is up
        Skips the loopback interface by default
        :param only_alive: Skip interfaces with zero bytes activity
        :param skip_loopback
        :return: Set with interface names
        """</span>
        <span class="hljs-keyword">with</span> open(<span class="hljs-string">'/proc/net/dev'</span>, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> dev:
            <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> dev:
                tokens = line.split()
                <span class="hljs-keyword">if</span> tokens[<span class="hljs-number">0</span>].find(<span class="hljs-string">":"</span>) != <span class="hljs-number">-1</span>:
                    name = tokens[<span class="hljs-number">0</span>].split(<span class="hljs-string">':'</span>)[<span class="hljs-number">0</span>]
                    <span class="hljs-keyword">if</span> re.search(<span class="hljs-string">'virbr\\d+|docker'</span>, name):
                        <span class="hljs-keyword">continue</span>  <span class="hljs-comment"># Skip virtual interfaces</span>
                    <span class="hljs-keyword">if</span> only_alive <span class="hljs-keyword">and</span> int(tokens[<span class="hljs-number">1</span>].strip()) == <span class="hljs-number">0</span>:
                        <span class="hljs-keyword">continue</span>
                    <span class="hljs-keyword">if</span> skip_loopback <span class="hljs-keyword">and</span> name == <span class="hljs-string">'lo'</span>:
                        <span class="hljs-keyword">continue</span>
                    self.interfaces.add(name)
        <span class="hljs-keyword">return</span> self.interfaces
</code></pre>
<p>The class HostIface gets the IP address and network masks of each local interface using <a target="_blank" href="https://docs.python.org/3/howto/sockets.html">Socket programming</a>. Then it maps each list of networks for these ip addresses + netmask combinations:</p>
<pre><code class="lang-python">SIOCGIFADDR = <span class="hljs-number">0x8915</span>
SIOCGIFNETMASK = <span class="hljs-number">0x891B</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HostIface</span>:</span>
<span class="hljs-meta">    @staticmethod</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_iface_details</span>(<span class="hljs-params">iface: str</span>):</span>
        <span class="hljs-string">"""
        Get network interface IP using the network interface name
        :return: IP address and network mask
        :param iface: Interface name (like eth0, enp2s0, etc.)
        """</span>
        <span class="hljs-keyword">with</span> socket.socket(socket.AF_INET, socket.SOCK_DGRAM) <span class="hljs-keyword">as</span> s:
            iface_pack = struct.pack(<span class="hljs-string">'256s'</span>, bytes(iface, <span class="hljs-string">'ascii'</span>))
            packed_ip = fcntl.ioctl(s.fileno(), SIOCGIFADDR, iface_pack)[<span class="hljs-number">20</span>:<span class="hljs-number">24</span>]
            packed_netmask = fcntl.ioctl(s.fileno(), SIOCGIFNETMASK, iface_pack)[<span class="hljs-number">20</span>:<span class="hljs-number">24</span>]
        <span class="hljs-keyword">return</span> socket.inet_ntoa(packed_ip), socket.inet_ntoa(packed_netmask)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_local_networks</span>(<span class="hljs-params">self, *, refresh: bool = False</span>) -&gt; List[ipaddress.IPv4Network]:</span>
        <span class="hljs-string">"""
        Get the list of local networks, using all the local IP addresses
        :param refresh: If true, re-read /proc to get list of interfaces
        :return: List of IPv4Network addresses
        """</span>
        local_networks: List[ipaddress.IPv4Network] = []
        <span class="hljs-keyword">for</span> iface <span class="hljs-keyword">in</span> self.get_alive_interfaces(refresh=refresh):
            ip, netmask = self.get_iface_details(iface)
            network: ipaddress.IPv4Network = ipaddress.ip_network(<span class="hljs-string">f"<span class="hljs-subst">{ip}</span>/<span class="hljs-subst">{netmask}</span>"</span>, strict=<span class="hljs-literal">False</span>)
            <span class="hljs-keyword">if</span> network <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> local_networks:
                local_networks.append(network)
        <span class="hljs-keyword">return</span> local_networks
</code></pre>
<p>Note that this is not portable across other OS's like BSD and specially Windows.</p>
<h2 id="heading-how-to-put-together-the-new-nmap-cli-frontend">How to put together the new Nmap CLI frontend</h2>
<p>Now, creating a new CLI for Nmap is straightforward. As a plus, the new frontend also allows you to save your scanning results as a json file (--report optional argument):</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python</span>
<span class="hljs-string">"""
# home_scan.py - A simple host discovery script
This script can scan your home network to show information from all the connected devices.

## References:
* [Nmap reference](https://nmap.org/book/man.html)

# Author
Jose Vicente Nunez Zuleta (kodegeek.com@protonmail.com)
"""</span>
<span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">import</span> re
<span class="hljs-keyword">import</span> sys

<span class="hljs-keyword">from</span> rich.layout <span class="hljs-keyword">import</span> Layout
<span class="hljs-keyword">from</span> rich.live <span class="hljs-keyword">import</span> Live
<span class="hljs-keyword">from</span> rich.console <span class="hljs-keyword">import</span> Console
<span class="hljs-keyword">from</span> rich.logging <span class="hljs-keyword">import</span> RichHandler
<span class="hljs-keyword">from</span> rich.text <span class="hljs-keyword">import</span> Text
<span class="hljs-keyword">from</span> rich.traceback <span class="hljs-keyword">import</span> install
<span class="hljs-keyword">from</span> rich.progress <span class="hljs-keyword">import</span> TimeElapsedColumn, Progress, TextColumn
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> List
<span class="hljs-keyword">import</span> argparse

<span class="hljs-keyword">from</span> home_nmap.nmap <span class="hljs-keyword">import</span> Scanner
<span class="hljs-keyword">from</span> home_nmap.system <span class="hljs-keyword">import</span> HostIface
<span class="hljs-keyword">from</span> home_nmap.ui <span class="hljs-keyword">import</span> create_scan_table, update_scan_table


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_targets</span>(<span class="hljs-params">target_list: List[str], cli_args: argparse.Namespace</span>) -&gt; str:</span>
    <span class="hljs-keyword">if</span> cli_args.target:
        <span class="hljs-keyword">for</span> target <span class="hljs-keyword">in</span> target_list:
            <span class="hljs-string">"""
            This should not happen as the script has an alias for -oX
            """</span>
            <span class="hljs-keyword">if</span> re.search(<span class="hljs-string">"-oX"</span>, target):
                <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Cannot redirect the output to a file by passing -oX. Run this script with --help"</span>)
        <span class="hljs-keyword">return</span> <span class="hljs-string">','</span>.join(target_list)
    <span class="hljs-keyword">return</span> <span class="hljs-string">','</span>.join(HostIface().get_prefixed_local_networks())


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:

    install()
    logging.basicConfig(
        level=<span class="hljs-string">"NOTSET"</span>,
        format=<span class="hljs-string">"%(message)s"</span>,
        datefmt=<span class="hljs-string">"[%X]"</span>,
        handlers=[RichHandler(rich_tracebacks=<span class="hljs-literal">True</span>)]
    )

    console = Console()
    arg_parser = argparse.ArgumentParser(
        description=<span class="hljs-string">"Identify my local networked devices, with open ports"</span>,
        prog=__file__
    )
    arg_parser.add_argument(
        <span class="hljs-string">'--debug'</span>,
        action=<span class="hljs-string">'store_true'</span>,
        default=<span class="hljs-literal">False</span>,
        help=<span class="hljs-string">"Enable debug mode"</span>
    )
    arg_parser.add_argument(
        <span class="hljs-string">'--results'</span>,
        <span class="hljs-string">'-xO'</span>,
        action=<span class="hljs-string">'store'</span>,
        help=<span class="hljs-string">f"If defined, save scan results into this file."</span>
    )
    arg_parser.add_argument(
        <span class="hljs-string">'target'</span>,
        action=<span class="hljs-string">'store'</span>,
        nargs=<span class="hljs-string">'*'</span>,
        help=(<span class="hljs-string">f"One or more targets, in Nmap format (scanme.homenmap.org, microsoft.com/24, 192.168.0.1; "</span>
              <span class="hljs-string">f"10.0.0-255.1-254). If not provided, then scan local networks"</span>)
    )
    args = arg_parser.parse_args()

    current_app_progress = Progress(
        TimeElapsedColumn(),
        TextColumn(<span class="hljs-string">"{task.description}"</span>),
    )
    scanning_task = current_app_progress.add_task(<span class="hljs-string">"[yellow]Waiting[/yellow] for scan results... :hourglass:"</span>)

    <span class="hljs-keyword">try</span>:
        scanner = Scanner()
        scan_targets = get_targets(args.target, args)
        <span class="hljs-keyword">if</span> args.results:
            table_title = <span class="hljs-string">f"Targets: <span class="hljs-subst">{scan_targets}</span>, results file=<span class="hljs-subst">{args.results}</span>"</span>
        <span class="hljs-keyword">else</span>:
            table_title = <span class="hljs-string">f"Targets: <span class="hljs-subst">{scan_targets}</span>"</span>
        results_table = create_scan_table(cli=<span class="hljs-string">f"Targets: <span class="hljs-subst">{table_title}</span>"</span>)
        layout = Layout()
        layout.split(
            Layout(name=<span class="hljs-string">"Scan status"</span>, size=<span class="hljs-number">1</span>),
            Layout(name=<span class="hljs-string">"Scan results"</span>),
        )
        <span class="hljs-keyword">with</span> Live(
                layout,
                console=console,
                screen=<span class="hljs-literal">False</span>,
                redirect_stderr=<span class="hljs-literal">False</span>,
        ) <span class="hljs-keyword">as</span> live:
            layout[<span class="hljs-string">'Scan results'</span>].update(Text(
                text=<span class="hljs-string">f"No results yet (<span class="hljs-subst">{scan_targets}</span>)"</span>, style=<span class="hljs-string">"green"</span>, justify=<span class="hljs-string">"center"</span>)),
            layout[<span class="hljs-string">'Scan status'</span>].update(current_app_progress)
            nmap_args, data, stderr = scanner.scan(hosts=scan_targets)
            update_scan_table(scan_result=data,
                              results_table=results_table,
                              main_layout=layout,
                              progress=current_app_progress,
                              task_id=scanning_task
                              )
        <span class="hljs-keyword">if</span> args.results:
            report_data = {
                <span class="hljs-string">'args'</span>: nmap_args,
                <span class="hljs-string">'scan'</span>: data
            }
            <span class="hljs-keyword">with</span> open(args.results, <span class="hljs-string">'w'</span>) <span class="hljs-keyword">as</span> report_file:
                json.dump(report_data, report_file, indent=<span class="hljs-literal">True</span>)

    <span class="hljs-keyword">except</span> ValueError:
        logging.exception(<span class="hljs-string">"There was an error"</span>)
        sys.exit(<span class="hljs-number">100</span>)
    <span class="hljs-keyword">except</span> KeyboardInterrupt:
        console.log(<span class="hljs-string">"Scan interrupted, exiting..."</span>)
        <span class="hljs-keyword">pass</span>
    sys.exit(<span class="hljs-number">0</span>)
</code></pre>
<p>The code got a little more verbose due the argument parsing and the user interface updates handling, but not too much.</p>
<p>Let's see an example against 127.0.0.1:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/home_scan.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Results of a live Nmap run, enriched with CVE advisories</em></p>
<p>If you are curious how the resulting JSON report looks like when passing the --report flag:</p>
<pre><code class="lang-json">{
 <span class="hljs-attr">"args"</span>: <span class="hljs-string">"/usr/bin/nmap -n -sS -p- -sV -O -T4 -PE --version-intensity 5 --disable-arp-ping --max-hostgroup 20 --min-parallelism 10 --osscan-limit --max-os-tries 1 -oX - 127.0.0.1"</span>,
 <span class="hljs-attr">"scan"</span>: [
  {
   <span class="hljs-attr">"addresses"</span>: [
    {   
     <span class="hljs-attr">"ip"</span>: <span class="hljs-string">"127.0.0.1"</span>
    }   
   ],  
   <span class="hljs-attr">"ports"</span>: [
    {   
     <span class="hljs-attr">"protocol"</span>: <span class="hljs-string">"tcp"</span>,
     <span class="hljs-attr">"port_id"</span>: <span class="hljs-string">"22"</span>,
     <span class="hljs-attr">"service_name"</span>: <span class="hljs-string">"ssh"</span>,
     <span class="hljs-attr">"service_product"</span>: <span class="hljs-string">"OpenSSH"</span>,
     <span class="hljs-attr">"service_version"</span>: <span class="hljs-string">"8.4"</span>,
     <span class="hljs-attr">"cpe"</span>: <span class="hljs-string">"cpe:/o:linux:linux_kernel:2.6.32"</span>
    },  
    {   
     <span class="hljs-attr">"protocol"</span>: <span class="hljs-string">"tcp"</span>,
     <span class="hljs-attr">"port_id"</span>: <span class="hljs-string">"631"</span>,
     <span class="hljs-attr">"service_name"</span>: <span class="hljs-string">"ipp"</span>,
     <span class="hljs-attr">"service_product"</span>: <span class="hljs-string">"CUPS"</span>,
     <span class="hljs-attr">"service_version"</span>: <span class="hljs-string">"2.3"</span>,
     <span class="hljs-attr">"cpe"</span>: <span class="hljs-string">"cpe:/o:linux:linux_kernel:2.6.32"</span>
    },  
...]
}
</code></pre>
<h2 id="heading-what-about-a-gui">What about a GUI?</h2>
<p>Nmap has a very complete GUI called <a target="_blank" href="https://nmap.org/zenmap/">Zenmap</a>, but the whole point was to show you that you can write a nice Text UI in Python as well to display the results.</p>
<p>You can achieve the same by using other popular frameworks like <a target="_blank" href="https://docs.python.org/3/library/tkinter.html">Tkinter</a>, which has incredibly detailed <a target="_blank" href="https://tkdocs.com/tutorial/">documentation</a>. For that reason, we'll not expand this topic any further.</p>
<p>Instead, let me show you how you can build a self-documenting REST-API for Nmap</p>
<h1 id="heading-how-to-make-a-home-network-scanner-a-web-service">How to Make a Home Network Scanner a Web Service</h1>
<p>Sometimes you cannot install Nmap because you lack the elevated privileges to do so or the server has installation constraints (like space or memory).</p>
<p>Or it could be that you want to run the port scanner on a machine that is able to connect to a network not directly accessible from the server you are currently logged in (and bypassing network segregation imposed by firewall). In this case the webservice will act like a proxy to run our Nmap command.</p>
<p>This is also known as "<strong>pivoting</strong>", and it it is a common technique used to bypass firewalls and proxy servers.</p>
<p>Let's take a short detour to talk more about pivoting with Nmap</p>
<h3 id="heading-can-you-run-nmap-through-a-proxy">Can you run Nmap through a proxy?</h3>
<p>Yes, you can use <a target="_blank" href="https://github.com/haad/proxychains">proxychains</a> to run Nmap through a host with better connectivity or to bypass firewall restrictions:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/pivot.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Using pivoting with Nmap and Proxy-chains</em></p>
<p>Say for the sake of argument that host 'External Linux' doesn't have direct connectivity to the network 192.168.1.0/24 but 'Multi homed Linux' does, and it can run a SOCKS-5 proxy.</p>
<p>To gain access to the internal network, we run <a target="_blank" href="https://en.wikipedia.org/wiki/Secure_Shell">SSH</a> forwarding port 9050 (as a SOCKS-5 proxy) under user 'josevnz':</p>
<pre><code class="lang-shell">josevnz@multihomed:~$ ssh  -N -D 9050 josevnz@192.168.1.11
The authenticity of host '192.168.1.11 (192.168.1.11)' can't be established.
ECDSA key fingerprint is SHA256:VIZCaCMb5rN2oL/xuv6CPrG1II+huW44x4TWhyKv8QM.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.1.11' (ECDSA) to the list of known hosts.
</code></pre>
<p>Then we install proxychains on 'External Linux' if is not already there:</p>
<pre><code class="lang-shell"># You either install proxychains first with 
# RedHat: 'sudo dnf -y install proxychains'
# Debian: 'sudo apt-get install proxychains4'
</code></pre>
<p>And create a proxychains.conf file pointing to your SSH SOCKS-5 proxy server:</p>
<pre><code class="lang-shell">cat&lt;&lt;CFG&gt;$HOME/proxychains.conf
strict_chain
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000
[ProxyList]
socks5 192.168.1.11 9050
CFG
</code></pre>
<p>Finally, run Nmap, using a TCP scan:</p>
<pre><code class="lang-shell">[josevnz@external docs]$ proxychains -q -f $HOME/proxychains.conf sudo Nmap -sT 192.168.1.0/24
Starting Nmap 7.80 ( https://nmap.org ) at 2021-12-30 16:06 EST
</code></pre>
<p>Alternatively just tell Nmap itself to use our new SOCKS-5 proxy (documentation <a target="_blank" href="https://nmap.org/book/man-bypass-firewalls-ids.html">says this is still under development</a>):</p>
<pre><code class="lang-shell">[josevnz@external docs]$ sudo nmap -v -sT --proxies socks4://192.168.1.11:9050 192.168.1.0/24
Starting Nmap 7.80 ( https://nmap.org ) at 2021-12-31 09:03 EST
</code></pre>
<p>Now lets go back to code our <a target="_blank" href="https://en.wikipedia.org/wiki/Web_service">web service</a>.</p>
<h2 id="heading-how-to-run-nmap-as-a-web-service">How to run Nmap as a web service</h2>
<p>In any case, running Nmap as a service is not something new (<a target="_blank" href="http://nmap-cgi.tuxfamily.org/">Nmap-cgi</a>). We'll make ours using <a target="_blank" href="https://fastapi.tiangolo.com/">FastAPI</a>.</p>
<p>I put together a web service that shows the current version and also the available network interfaces (home_nmap/main.py):</p>
<pre><code class="lang-python"><span class="hljs-string">"""
# Web service for home_nmap
# Author
Jose Vicente Nunez Zuleta (kodegeek.com@protonmail.com)
"""</span>
<span class="hljs-keyword">from</span> home_nmap <span class="hljs-keyword">import</span> __version__
<span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI

<span class="hljs-keyword">from</span> home_nmap.system <span class="hljs-keyword">import</span> HostIface

app = FastAPI()


<span class="hljs-meta">@app.get("/version")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">version</span>():</span>
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"version"</span>: __version__}


<span class="hljs-meta">@app.get("/local_networks")</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">local_networks</span>():</span>
    hi = HostIface()
    <span class="hljs-keyword">return</span> hi.get_local_networks()
</code></pre>
<p>In FastApi we define the web service endpoints with annotations it takes care of serializing our response back to the client.</p>
<p>Here is how you can start the service using the <a target="_blank" href="https://www.uvicorn.org/">uvicorn</a> web server with the '--reload' flag to detect changes in our code automatically:</p>
<pre><code class="lang-shell">(home_nmap) [josevnz@dmaf5 home_nmap]$ uvicorn home_nmap.main:app --reload
INFO:     Will watch for changes in these directories: ['/home/josevnz/Documents/home_nmap']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [122202] using watchgod
INFO:     Started server process [122204]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
</code></pre>
<p>Getting the home_nmap API version using <a target="_blank" href="https://curl.se/">curl</a>, JSON response pretty print with <a target="_blank" href="https://stedolan.github.io/jq/">jq</a>:</p>
<pre><code class="lang-shell">(home_nmap) [josevnz@dmaf5 rich]$ curl --fail --silent http://127.0.0.1:8000/version| jq '.'
{
  "version": "0.0.1"
}
</code></pre>
<p>Now get the list of local networks calling the '/local_networks' endpoint:</p>
<pre><code class="lang-shell">(home_nmap) [josevnz@dmaf5 rich]$ curl --fail --silent http://127.0.0.1:8000/local_networks| jq '.'
[
  "192.168.1.0/24"
]
</code></pre>
<p>One nice thing about FastApi is that you get automatic documentation for your REST endpoints (<a target="_blank" href="http://127.0.0.1:8000/docs#/">http://127.0.0.1:8000/docs#/</a>):</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/home_nmap_rest_documentation.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Nmap self documenting REST API</em></p>
<p>Not bad for a few lines of code if you ask me.</p>
<h2 id="heading-how-to-implement-the-scanner-service">How to implement the scanner service</h2>
<p>On the 'main.py' file we implement the endpoint to scan the local network and to correlate the CPE with any possible advisories:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Optional
<span class="hljs-keyword">from</span> home_nmap.system <span class="hljs-keyword">import</span> NMapRunner
<span class="hljs-keyword">from</span> home_nmap.query <span class="hljs-keyword">import</span> NDISHtml, target_validator
<span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI, HTTPException
app: FastAPI = FastAPI()

<span class="hljs-meta">@app.get("/scan")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">scan</span>(<span class="hljs-params">
        target: Optional[str] = None,
        full_advisories=True
</span>):</span>
    <span class="hljs-string">"""
    Scan a target to get service information.
    Note, FastAPI has a query validator, but I decided to use my own as I look for bad targets:
    Query(None, min_length=MIN_LEN_TARGET, max_length=MAX_LEN_TARGET)
    @param target: Override local network with custom targets, in Nmap format.
    @param full_advisories: If false, skip the summary information from the advisories
    @return: JSON containing the results of the scan
    """</span>
    <span class="hljs-keyword">try</span>:
        scanner = NMapRunner()
        args, scan_results, stderr = scanner.scan(hosts=target_validator(target))
        enriched_results = {
            <span class="hljs-string">'args'</span>: args,
            <span class="hljs-string">'hosts'</span>: []
        }
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> scan_results:
            <span class="hljs-keyword">raise</span> HTTPException(status_code=<span class="hljs-number">404</span>, detail=<span class="hljs-string">f"Got no results from scanning target=<span class="hljs-subst">{target}</span>"</span>)
        cpe_details = NDISHtml().correlate_nmap_with_nids(scan_results)
        <span class="hljs-keyword">for</span> host_data <span class="hljs-keyword">in</span> scan_results:
            enriched_host_data = {
                <span class="hljs-string">'address'</span>: host_data[<span class="hljs-string">'address'</span>],
                <span class="hljs-string">'ports'</span>: []
            }
            ports = host_data[<span class="hljs-string">'ports'</span>]
            <span class="hljs-keyword">for</span> port_data <span class="hljs-keyword">in</span> ports:
                advisories = []
                <span class="hljs-comment"># Unroll the advisories, if any ...</span>
                <span class="hljs-keyword">for</span> cpe <span class="hljs-keyword">in</span> port_data[<span class="hljs-string">'cpes'</span>]:
                    <span class="hljs-keyword">if</span> cpe <span class="hljs-keyword">in</span> cpe_details:  <span class="hljs-comment"># Service may not have an advisory</span>
                        <span class="hljs-keyword">for</span> nids <span class="hljs-keyword">in</span> cpe_details[cpe]:
                            <span class="hljs-keyword">if</span> full_advisories:
                                advisories.append({
                                    <span class="hljs-string">'link'</span>: nids.link,
                                    <span class="hljs-string">'summary'</span>: nids.summary,
                                    <span class="hljs-string">'score'</span>: nids.score
                                })
                            <span class="hljs-keyword">else</span>:
                                advisories.append({
                                    <span class="hljs-string">'link'</span>: nids.link,
                                    <span class="hljs-string">'summary'</span>: <span class="hljs-string">''</span>,  <span class="hljs-comment"># For consistency</span>
                                    <span class="hljs-string">'score'</span>: nids.score
                                })
                enriched_host_data[<span class="hljs-string">'ports'</span>].append(
                    {
                        <span class="hljs-string">'cpes'</span>: port_data[<span class="hljs-string">'cpes'</span>],
                        <span class="hljs-string">'advisories'</span>: advisories,
                        <span class="hljs-string">'protocol'</span>: port_data[<span class="hljs-string">'protocol'</span>],
                        <span class="hljs-string">'port_id'</span>: port_data[<span class="hljs-string">'port_id'</span>],
                        <span class="hljs-string">'service'</span>: [
                            <span class="hljs-string">f"<span class="hljs-subst">{port_data[<span class="hljs-string">'service_name'</span>]}</span>,"</span>
                            <span class="hljs-string">f"<span class="hljs-subst">{port_data[<span class="hljs-string">'service_product'</span>]}</span>,"</span>
                            <span class="hljs-string">f"<span class="hljs-subst">{port_data[<span class="hljs-string">'service_version'</span>]}</span>"</span>
                        ]
                    }
                )
            enriched_results[<span class="hljs-string">'hosts'</span>].append(enriched_host_data)
        <span class="hljs-keyword">return</span> enriched_results
    <span class="hljs-keyword">except</span> (TypeError, ValueError) <span class="hljs-keyword">as</span> exp:
        <span class="hljs-keyword">raise</span> HTTPException(status_code=<span class="hljs-number">500</span>, detail=str(exp))
</code></pre>
<p>The 'target_validator' function does a few checks on the target to ensure only valid scanning targets are passed (this is the same function we wrote for the CLI program):</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> re
MIN_LEN_TARGET = <span class="hljs-number">9</span>
MAX_LEN_TARGET = <span class="hljs-number">50</span>
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Optional
<span class="hljs-keyword">import</span> shlex
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">target_validator</span>(<span class="hljs-params">target: Optional[str]</span>) -&gt; str:</span>
    <span class="hljs-string">"""
    Simple validator for Nmap target expressions
    @param target: (scanme.homenmap.org, microsoft.com/24, 192.168.0.1; 10.0.0-255.1-254). None or empty are valid
    @return:
    """</span>
    <span class="hljs-keyword">if</span> target:
        regexp_list = [
            <span class="hljs-string">'-[a-z-A-Z][A-Z]*'</span>,
            <span class="hljs-string">'-[a-zA-Z]\\d*'</span>,
            <span class="hljs-string">'--[a-z-]+'</span>
        ]
        <span class="hljs-keyword">if</span> len(target) &lt; MIN_LEN_TARGET:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Provided length for target is too small &lt; <span class="hljs-subst">{MIN_LEN_TARGET}</span>"</span>)
        <span class="hljs-keyword">if</span> len(target) &gt; MAX_LEN_TARGET:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Provided length for target is too big &lt; <span class="hljs-subst">{MAX_LEN_TARGET}</span>"</span>)
        <span class="hljs-keyword">for</span> arg <span class="hljs-keyword">in</span> shlex.split(target):
            <span class="hljs-keyword">for</span> regexp <span class="hljs-keyword">in</span> regexp_list:
                <span class="hljs-keyword">if</span> re.search(regexp, arg):
                    <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"You cannot override Nmap arguments: <span class="hljs-subst">{arg}</span>"</span>)
    <span class="hljs-keyword">return</span> target
</code></pre>
<p>Time to put everything together.</p>
<h3 id="heading-what-does-a-scan-run-look-like-very-verbose-json">What does a scan run look like (very verbose JSON)?</h3>
<p>Here is what the scan result of 2 machines in my local network looks like (the web service is running on dmaf5.home on port 8000):</p>
<pre><code class="lang-shell">[josevnz@dmaf5 ~]$ curl http://dmaf5.home:8000/scan?target=192.168.1.10,23
{"args":"/usr/bin/nmap -n -sS -p- -sV -O -T4 -PE --version-intensity 5 --disable-arp-ping --max-hostgroup 20 --min-parallelism 10 --osscan-limit --max-os-tries 1 -oX - 192.168.1.10,23","hosts":[{"address":"192.168.1.10","ports":[{"cpes":["cpe:/a:openbsd:openssh:8.2p1"],"advisories":[{"link":"https://nvd.nist.gov/vuln/detail/CVE-2021-41617","summary":"sshd in OpenSSH 6.2 through 8.x before 8.8, when certain non-default configurations are used, allows privilege escalation because supplemental groups are not initialized as expected. Helper programs for AuthorizedKeysCommand and AuthorizedPrincipalsCommand may run with privileges associated with group memberships of the sshd process, if the configuration specifies running the command as a different user.","score":"4.4 MEDIUM"},{"link":"https://nvd.nist.gov/vuln/detail/CVE-2016-20012","summary":"OpenSSH through 8.7 allows remote attackers, who have a suspicion that a certain combination of username and public key is known to an SSH server, to test whether this suspicion is correct. This occurs because a challenge is sent only when that combination could be valid for a login session.","score":"4.3 MEDIUM"},{"link":"https://nvd.nist.gov/vuln/detail/CVE-2021-28041","summary":"ssh-agent in OpenSSH before 8.5 has a double free that may be relevant in a few less-common scenarios, such as unconstrained agent-socket access on a legacy operating system, or the forwarding of an agent to an attacker-controlled host.","score":"4.6 MEDIUM"},{"link":"https://nvd.nist.gov/vuln/detail/CVE-2020-15778","summary":"** DISPUTED ** scp in OpenSSH through 8.3p1 allows command injection in the scp.c toremote function, as demonstrated by backtick characters in the destination argument. NOTE: the vendor reportedly has stated that they intentionally omit validation of \"anomalous argument transfers\" because that could \"stand a great chance of breaking existing workflows.\"","score":"6.8 MEDIUM"},{"link":"https://nvd.nist.gov/vuln/detail/CVE-2020-14145","summary":"The client side in OpenSSH 5.7 through 8.4 has an Observable Discrepancy leading to an information leak in the algorithm negotiation. This allows man-in-the-middle attackers to target initial connection attempts (where no host key for the server has been cached by the client). NOTE: some reports state that 8.5 and 8.6 are also affected.","score":"4.3 MEDIUM"}],"protocol":"tcp","port_id":"22","service":[["ssh"],["OpenSSH"],["8.2p1 Ubuntu 4ubuntu0.3"]]},{"cpes":[],"advisories":[],"protocol":"tcp","port_id":"2377","service":[["swarm"],[""],[""]]},{"cpes":[],"advisories":[],"protocol":"tcp","port_id":"7946","service":[["unknown"],[""],[""]]},{"cpes":["cpe:/a:influxdata:influxdb:2.1.1"],"advisories":[],"protocol":"tcp","port_id":"8086","service":[["http"],["InfluxDB http admin"],["2.1.1"]]},{"cpes":[],"advisories":[],"protocol":"tcp","port_id":"9100","service":[["jetdirect"],[""],[""]]},{"cpes":["cpe:/a:protocol_labs:go-ipfs"],"advisories":[],"protocol":"tcp","port_id":"9323","service":[["http"],["Golang net/http server"],[""]]}]},{"address":"DC:A6:32:F9:47:48","ports":[{"cpes":["cpe:/a:openbsd:openssh:8.2p1"],"advisories":[{"link":"https://nvd.nist.gov/vuln/detail/CVE-2021-41617","summary":"sshd in OpenSSH 6.2 through 8.x before 8.8, when certain non-default configurations are used, allows privilege escalation because supplemental groups are not initialized as expected. Helper programs for AuthorizedKeysCommand and AuthorizedPrincipalsCommand may run with privileges associated with group memberships of the sshd process, if the configuration specifies running the command as a different user.","score":"4.4 MEDIUM"},{"link":"https://nvd.nist.gov/vuln/detail/CVE-2016-20012","summary":"OpenSSH through 8.7 allows remote attackers, who have a suspicion that a certain combination of username and public key is known to an SSH server, to test whether this suspicion is correct. This occurs because a challenge is sent only when that combination could be valid for a login session.","score":"4.3 MEDIUM"},{"link":"https://nvd.nist.gov/vuln/detail/CVE-2021-28041","summary":"ssh-agent in OpenSSH before 8.5 has a double free that may be relevant in a few less-common scenarios, such as unconstrained agent-socket access on a legacy operating system, or the forwarding of an agent to an attacker-controlled host.","score":"4.6 MEDIUM"},{"link":"https://nvd.nist.gov/vuln/detail/CVE-2020-15778","summary":"** DISPUTED ** scp in OpenSSH through 8.3p1 allows command injection in the scp.c toremote function, as demonstrated by backtick characters in the destination argument. NOTE: the vendor reportedly has stated that they intentionally omit validation of \"anomalous argument transfers\" because that could \"stand a great chance of breaking existing workflows.\"","score":"6.8 MEDIUM"},{"link":"https://nvd.nist.gov/vuln/detail/CVE-2020-14145","summary":"The client side in OpenSSH 5.7 through 8.4 has an Observable Discrepancy leading to an information leak in the algorithm negotiation. This allows man-in-the-middle attackers to target initial connection attempts (where no host key for the server has been cached by the client). NOTE: some reports state that 8.5 and 8.6 are also affected.","score":"4.3 MEDIUM"}],"protocol":"tcp","port_id":"22","service":[["ssh"],["OpenSSH"],["8.2p1 Ubuntu 4ubuntu0.3"]]},{"cpes":[],"advisories":[],"protocol":"tcp","port_id":"2377","service":[["swarm"],[""],[""]]},{"cpes":[],"advisories":[],"protocol":"tcp","port_id":"7946","service":[["unknown"],[""],[""]]},{"cpes":["cpe:/a:influxdata:influxdb:2.1.1"],"advisories":[],"protocol":"tcp","port_id":"8086","service":[["http"],["InfluxDB http admin"],["2.1.1"]]},{"cpes":[],"advisories":[],"protocol":"tcp","port_id":"9100","service":[["jetdirect"],[""],[""]]},{"cpes":["cpe:/a:protocol_labs:go-ipfs"],"advisories":[],"protocol":"tcp","port_id":"9323","service":[["http"],["Golang net/http server"],[""]]}]},{"address":"192.168.1.23","ports":[{"cpes":["cpe:/a:openbsd:openssh:8.4"],"advisories":[{"link":"https://nvd.nist.gov/vuln/detail/CVE-2021-41617","summary":"sshd in OpenSSH 6.2 through 8.x before 8.8, when certain non-default configurations are used, allows privilege escalation because supplemental groups are not initialized as expected. Helper programs for AuthorizedKeysCommand and AuthorizedPrincipalsCommand may run with privileges associated with group memberships of the sshd process, if the configuration specifies running the command as a different user.","score":"4.4 MEDIUM"},{"link":"https://nvd.nist.gov/vuln/detail/CVE-2016-20012","summary":"OpenSSH through 8.7 allows remote attackers, who have a suspicion that a certain combination of username and public key is known to an SSH server, to test whether this suspicion is correct. This occurs because a challenge is sent only when that combination could be valid for a login session.","score":"4.3 MEDIUM"},{"link":"https://nvd.nist.gov/vuln/detail/CVE-2021-28041","summary":"ssh-agent in OpenSSH before 8.5 has a double free that may be relevant in a few less-common scenarios, such as unconstrained agent-socket access on a legacy operating system, or the forwarding of an agent to an attacker-controlled host.","score":"4.6 MEDIUM"},{"link":"https://nvd.nist.gov/vuln/detail/CVE-2020-14145","summary":"The client side in OpenSSH 5.7 through 8.4 has an Observable Discrepancy leading to an information leak in the algorithm negotiation. This allows man-in-the-middle attackers to target initial connection attempts (where no host key for the server has been cached by the client). NOTE: some reports state that 8.5 and 8.6 are also affected.","score":"4.3 MEDIUM"}],"protocol":"tcp","port_id":"22","service":[["ssh"],["OpenSSH"],["8.4"]]},{"cpes":[],"advisories":[],"protocol":"tcp","port_id":"5355","service":[["llmnr"],[""],[""]]},{"cpes":[],"advisories":[],"protocol":"tcp","port_id":"8443","service":[["https-alt"],[""],[""]]},{"cpes":[],"advisories":[],"protocol":"tcp","port_id":"9100","service":[["jetdirect"],[""],[""]]}]}]}[josevnz@dmaf5 ~]$
</code></pre>
<h2 id="heading-is-this-web-service-secure">Is this web-service secure?</h2>
<p>We exposed our Nmap scanner <em>with no authorization</em>, which means anyone who knows where the service is running can use it. This may not be a big issue on the local network, but it would be good to control who uses our precious resources.</p>
<h3 id="heading-how-to-add-authentication-and-authorization">How to add authentication and authorization</h3>
<p>Right now anyone can call our service. It is a good idea to control who can run Nmap against our home network</p>
<p>There are <a target="_blank" href="https://fastapi.tiangolo.com/tutorial/security/">several ways</a> to make sure our web service can only be used by authorized clients. One way to do it is by requesting a client to provide a key that is also known to the server. This is the approach we'll follow here.</p>
<p>NOTE: As you might have guessed, if someone finds out the key then your service is compromised. To make it more secure you should:</p>
<ul>
<li><p>Stored the key in a safe place, encrypted</p>
</li>
<li><p>Have an expiration date, to purge stale ones</p>
</li>
<li><p>And transit of those keys should go over an encrypted channel, like HTTPS (we'll see about that soon)</p>
</li>
</ul>
<p>We will take advantage of <a target="_blank" href="https://github.com/mrtolkien/fastapi_simple_security">fastapi_simple_security</a> to implement the API security access to our web application. It only requires a few new imports and that we declare a dependency on our REST API endpoints:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI, Depends
<span class="hljs-keyword">from</span> fastapi_simple_security <span class="hljs-keyword">import</span> api_key_router, api_key_security
<span class="hljs-keyword">from</span> fastapi.responses <span class="hljs-keyword">import</span> JSONResponse
<span class="hljs-keyword">from</span> fastapi.encoders <span class="hljs-keyword">import</span> jsonable_encoder
<span class="hljs-keyword">import</span> typing
<span class="hljs-keyword">from</span> home_nmap.system <span class="hljs-keyword">import</span> HostIface
...
app: typing.Union[FastAPI] = FastAPI()
app.include_router(api_key_router, prefix=<span class="hljs-string">"/auth"</span>, tags=[<span class="hljs-string">"_auth"</span>])

<span class="hljs-comment"># Then add a 'dependencies' to each of the endpoints we want to secure</span>
<span class="hljs-meta">@app.get("/local_networks", dependencies=[Depends(api_key_security)])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">local_networks</span>():</span>
    <span class="hljs-string">"""
    Get the available local networks where home_nmap runs
    @return: List with local networks in CIDR format
    """</span>
    response = JSONResponse(jsonable_encoder(HostIface().get_local_networks()))
    <span class="hljs-keyword">return</span> response
...
</code></pre>
<p>If we do not define a secret API key, the framework will provide us with one at startup (but you can override later through the documentation page):</p>
<pre><code class="lang-shell">(home_nmap) [josevnz@dmaf5 home_nmap]$ uuidgen 
23eb5572-1e63-4404-a64b-bcc18b62d4eb
(home_nmap) [josevnz@dmaf5 home_nmap]$ export FASTAPI_SIMPLE_SECURITY_SECRET="23eb5572-1e63-4404-a64b-bcc18b62d4eb"; uvicorn home_nmap.main:app --host 0.0.0.0 --port 8000 --reloadINFO:     Will watch for changes in these directories: ['/home/josevnz/Documents/home_nmap']
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [134702] using watchgod
INFO:     Started server process [134704]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
</code></pre>
<p>Now all the APIs that are protected by the keys have a different decoration in the documentation (a lock next to each endpoint):</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/documentation_shows_secured_endpoints.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Now documentation shows secured end points</em></p>
<p>What happens if we try to get the list of local networks, without our key?</p>
<pre><code class="lang-shell">josevnz@dmaf5 ~]$ curl 'http://127.0.0.1:8000/local_networks' --header 'accept: application/json'
{"detail":"An API key must be passed as query or header"}
</code></pre>
<p>In order to finish the setup, you need to enter your 'secret-key' (<code>23eb5572-1e63-4404-a64b-bcc18b62d4eb</code>) into the docs authentication page. Then go to the <em>/auth/new to get the api-key</em>, which is the one that your clients will use (header, cookie or part of the GET requests). In my case I got this:</p>
<pre><code class="lang-shell">curl 'http://127.0.0.1:8000/auth/new?never_expires=false' \
  --header 'accept: application/json' \
  --header 'secret-key: 23eb5572-1e63-4404-a64b-bcc18b62d4eb'
"e4c03730-02a1-4cb9-8e00-36a63930c064"
</code></pre>
<p>Now let's try again but passing our secret API key:</p>
<pre><code class="lang-shell">[josevnz@dmaf5 home_nmap]$ curl 'http://127.0.0.1:8000/local_networks'  --header 'accept: application/json' --header 'api-key: e4c03730-02a1-4cb9-8e00-36a63930c064'
["192.168.1.0/24"][josevnz@dmaf5 home_nmap]$
</code></pre>
<p>Still, we are not done yet. Assume that someone managed to run a sniffer on your network and is capturing all your HTTP traffic:</p>
<pre><code class="lang-shell">[josevnz@dmaf5 home_nmap]$ tshark -i eno1 -Px -Y http
Capturing on 'eno1'
   72 5.107984320 192.168.1.11 → 192.168.1.25 HTTP 219 GET /local_networks HTTP/1.1 

0000  1c 83 41 28 44 21 dc a6 32 f9 47 48 08 00 45 00   ..A(D!..2.GH..E.
0010  00 cd 7b ca 40 00 40 06 3a ec c0 a8 01 0b c0 a8   ..{.@.@.:.......
0020  01 19 b1 a6 1f 40 ce 1b 2a 22 ab b5 24 3c 80 18   .....@..*"..$&lt;..
0030  01 f6 d0 3d 00 00 01 01 08 0a f3 07 ee 27 9d 96   ...=.........'..
0040  87 76 47 45 54 20 2f 6c 6f 63 61 6c 5f 6e 65 74   .vGET /local_net
0050  77 6f 72 6b 73 20 48 54 54 50 2f 31 2e 31 0d 0a   works HTTP/1.1..
0060  48 6f 73 74 3a 20 64 6d 61 66 35 2e 68 6f 6d 65   Host: dmaf5.home
0070  3a 38 30 30 30 0d 0a 55 73 65 72 2d 41 67 65 6e   :8000..User-Agen
0080  74 3a 20 63 75 72 6c 2f 37 2e 36 38 2e 30 0d 0a   t: curl/7.68.0..
0090  61 63 63 65 70 74 3a 20 61 70 70 6c 69 63 61 74   accept: applicat
00a0  69 6f 6e 2f 6a 73 6f 6e 0d 0a 61 70 69 2d 6b 65   ion/json..api-ke
00b0  79 3a 20 65 34 63 30 33 37 33 30 2d 30 32 61 31   y: e4c03730-02a1
00c0  2d 34 63 62 39 2d 38 65 30 30 2d 33 36 61 36 33   -4cb9-8e00-36a63
00d0  39 33 30 63 30 36 34 0d 0a 0d 0a                  930c064....
</code></pre>
<p>You can clearly see our not-so-secret-anymore API key. Time to add the next layer of protection.</p>
<h3 id="heading-we-need-encryption">We need encryption</h3>
<p>The HTTP protocol is not encrypted. That means that someone using a sniffer (like tcpdump or wireshark) can capture the traffic. For example, if we request the home_nmap version using curl:</p>
<pre><code class="lang-shell">curl http://dmaf5.home:8000/version
</code></pre>
<p>It is possible for someone else running <a target="_blank" href="https://tshark.dev/setup/install/">tshark</a> to see all the traffic (look at the content-type: Application/ Json payload):</p>
<pre><code class="lang-shell">root@dmaf5 ~]# tshark -i eno1 -Px -Y http
Running as user "root" and group "root". This could be dangerous.
Capturing on 'eno1'
  127 4.342379691 192.168.1.11 → 192.168.1.23 HTTP 152 GET /version HTTP/1.1 

0000  1c 83 41 28 44 21 dc a6 32 f9 47 48 08 00 45 00   ..A(D!..2.GH..E.
0010  00 8a c3 8a 40 00 40 06 f3 70 c0 a8 01 0b c0 a8   ....@.@..p......
0020  01 17 c7 68 1f 40 dc af 3c 37 c1 12 e6 69 80 18   ...h.@..&lt;7...i..
0030  01 f6 ff a7 00 00 01 01 08 0a 08 94 d3 55 a8 7c   .............U.|
0040  ec df 47 45 54 20 2f 76 65 72 73 69 6f 6e 20 48   ..GET /version H
0050  54 54 50 2f 31 2e 31 0d 0a 48 6f 73 74 3a 20 64   TTP/1.1..Host: d
0060  6d 61 66 35 2e 68 6f 6d 65 3a 38 30 30 30 0d 0a   maf5.home:8000..
0070  55 73 65 72 2d 41 67 65 6e 74 3a 20 63 75 72 6c   User-Agent: curl
0080  2f 37 2e 36 38 2e 30 0d 0a 41 63 63 65 70 74 3a   /7.68.0..Accept:
0090  20 2a 2f 2a 0d 0a 0d 0a                            */*....

  129 4.344312849 192.168.1.23 → 192.168.1.11 HTTP/JSON 210 HTTP/1.1 200 OK , JavaScript Object Notation (application/json)

0000  dc a6 32 f9 47 48 1c 83 41 28 44 21 08 00 45 00   ..2.GH..A(D!..E.
0010  00 c4 36 78 40 00 40 06 80 49 c0 a8 01 17 c0 a8   ..6x@.@..I......
0020  01 0b 1f 40 c7 68 c1 12 e6 69 dc af 3c 8d 80 18   ...@.h...i..&lt;...
0030  01 fd 84 29 00 00 01 01 08 0a a8 7c ec e1 08 94   ...).......|....
0040  d3 55 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f   .UHTTP/1.1 200 O
0050  4b 0d 0a 64 61 74 65 3a 20 4d 6f 6e 2c 20 31 37   K..date: Mon, 17
0060  20 4a 61 6e 20 32 30 32 32 20 32 30 3a 31 36 3a    Jan 2022 20:16:
0070  32 39 20 47 4d 54 0d 0a 73 65 72 76 65 72 3a 20   29 GMT..server: 
0080  75 76 69 63 6f 72 6e 0d 0a 63 6f 6e 74 65 6e 74   uvicorn..content
0090  2d 6c 65 6e 67 74 68 3a 20 31 39 0d 0a 63 6f 6e   -length: 19..con
00a0  74 65 6e 74 2d 74 79 70 65 3a 20 61 70 70 6c 69   tent-type: appli
00b0  63 61 74 69 6f 6e 2f 6a 73 6f 6e 0d 0a 0d 0a 7b   cation/json....{
00c0  22 76 65 72 73 69 6f 6e 22 3a 22 30 2e 30 2e 31   "version":"0.0.1
00d0  22 7d                                             "}
</code></pre>
<p>We can protect our traffic by encrypting it using <a target="_blank" href="https://en.wikipedia.org/wiki/HTTPS">Hypertext Transfer Protocol Secure (HTTPS)</a>.</p>
<h4 id="heading-how-to-create-the-secure-socket-layer-ssl-certificates">How to create the Secure Socket Layer (SSL) certificates</h4>
<p>Let me show you real quick <a target="_blank" href="https://github.com/rob-blackbourn/ssl-certs">how you can install a self-signed server certificate</a> on Fedora using <a target="_blank" href="https://github.com/cloudflare/cfssl">Cloudflare cfssl</a>. First let's install the tools:</p>
<pre><code class="lang-shell"># On Fedora just do 
sudo dnf install -y golang-github-cloudflare-cfssl
# Or go get github.com/cloudflare/cfssl/cmd/...
</code></pre>
<p>Next step is to create a certificate authority (CA). We will use it to sign other certificates. For that let's create a definition in JSON format:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"CN"</span>: <span class="hljs-string">"Nunez Barrios family Root CA"</span>,
  <span class="hljs-attr">"key"</span>: {
    <span class="hljs-attr">"algo"</span>: <span class="hljs-string">"rsa"</span>,
    <span class="hljs-attr">"size"</span>: <span class="hljs-number">2048</span>
  },
  <span class="hljs-attr">"names"</span>: [
  {
    <span class="hljs-attr">"C"</span>: <span class="hljs-string">"US"</span>,
    <span class="hljs-attr">"L"</span>: <span class="hljs-string">"CT"</span>,
    <span class="hljs-attr">"O"</span>: <span class="hljs-string">"Nunez Barrios"</span>,
    <span class="hljs-attr">"OU"</span>: <span class="hljs-string">"Nunez Barrios Root CA"</span>,
    <span class="hljs-attr">"ST"</span>: <span class="hljs-string">"United States"</span>
  }
 ]
}
</code></pre>
<p>Create the certificate:</p>
<pre><code class="lang-shell">cfssl gencert -initca ca.json | cfssljson -bare ca
</code></pre>
<p>Next we need to create a profile file (cfssl.json), that will specify certain features of the certificates, like expiration in 2 years:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"signing"</span>: {
    <span class="hljs-attr">"default"</span>: {
      <span class="hljs-attr">"expiry"</span>: <span class="hljs-string">"17532h"</span>
    },
    <span class="hljs-attr">"profiles"</span>: {
      <span class="hljs-attr">"intermediate_ca"</span>: {
        <span class="hljs-attr">"usages"</span>: [
            <span class="hljs-string">"signing"</span>,
            <span class="hljs-string">"digital signature"</span>,
            <span class="hljs-string">"key encipherment"</span>,
            <span class="hljs-string">"cert sign"</span>,
            <span class="hljs-string">"crl sign"</span>,
            <span class="hljs-string">"server auth"</span>,
            <span class="hljs-string">"client auth"</span>
        ],
        <span class="hljs-attr">"expiry"</span>: <span class="hljs-string">"17532h"</span>,
        <span class="hljs-attr">"ca_constraint"</span>: {
            <span class="hljs-attr">"is_ca"</span>: <span class="hljs-literal">true</span>,
            <span class="hljs-attr">"max_path_len"</span>: <span class="hljs-number">0</span>, 
            <span class="hljs-attr">"max_path_len_zero"</span>: <span class="hljs-literal">true</span>
        }
      },
      <span class="hljs-attr">"peer"</span>: {
        <span class="hljs-attr">"usages"</span>: [
            <span class="hljs-string">"signing"</span>,
            <span class="hljs-string">"digital signature"</span>,
            <span class="hljs-string">"key encipherment"</span>, 
            <span class="hljs-string">"client auth"</span>,
            <span class="hljs-string">"server auth"</span>
        ],
        <span class="hljs-attr">"expiry"</span>: <span class="hljs-string">"17532h"</span>
      },
      <span class="hljs-attr">"server"</span>: {
        <span class="hljs-attr">"usages"</span>: [
          <span class="hljs-string">"signing"</span>,
          <span class="hljs-string">"digital signing"</span>,
          <span class="hljs-string">"key encipherment"</span>,
          <span class="hljs-string">"server auth"</span>
        ],
        <span class="hljs-attr">"expiry"</span>: <span class="hljs-string">"17532h"</span>
      },
      <span class="hljs-attr">"client"</span>: {
        <span class="hljs-attr">"usages"</span>: [
          <span class="hljs-string">"signing"</span>,
          <span class="hljs-string">"digital signature"</span>,
          <span class="hljs-string">"key encipherment"</span>, 
          <span class="hljs-string">"client auth"</span>
        ],
        <span class="hljs-attr">"expiry"</span>: <span class="hljs-string">"17532h"</span>
      }
    }
  }
}
</code></pre>
<p>Now we create an intermediate certificate (intermediate-ca.json) that will expire in 5 years:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"CN"</span>: <span class="hljs-string">"Barrios Nunez Intermediate CA"</span>,
  <span class="hljs-attr">"key"</span>: {
    <span class="hljs-attr">"algo"</span>: <span class="hljs-string">"rsa"</span>,
    <span class="hljs-attr">"size"</span>: <span class="hljs-number">2048</span>
  },
  <span class="hljs-attr">"names"</span>: [
    {
      <span class="hljs-attr">"C"</span>:  <span class="hljs-string">"US"</span>,
      <span class="hljs-attr">"L"</span>:  <span class="hljs-string">"CT"</span>,
      <span class="hljs-attr">"O"</span>:  <span class="hljs-string">"Barrios Nunez"</span>,
      <span class="hljs-attr">"OU"</span>: <span class="hljs-string">"Barrios Nunez Intermediate CA"</span>,
      <span class="hljs-attr">"ST"</span>: <span class="hljs-string">"USA"</span>
    }
  ],
  <span class="hljs-attr">"ca"</span>: {
    <span class="hljs-attr">"expiry"</span>: <span class="hljs-string">"43830h"</span>
  }
}
</code></pre>
<p>Here's the command to do it:</p>
<pre><code class="lang-shell">cfssl gencert -initca intermediate-ca.json | cfssljson -bare intermediate_ca
cfssl sign -ca ca.pem -ca-key ca-key.pem -config cfssl.json -profile intermediate_ca intermediate_ca.csr | cfssljson -bare intermediate_ca
</code></pre>
<h3 id="heading-next-step-is-to-create-the-host-certificates">Next step is to create the host certificates</h3>
<p>You will need to put your fully-qualified host name (<code>hostname -f</code>) on the host-1.json file. Also, some software expects the IP address (<code>ip address|grep inet</code>) – we will do both:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"CN"</span>: <span class="hljs-string">"dmaf5.home"</span>,
  <span class="hljs-attr">"key"</span>: {
    <span class="hljs-attr">"algo"</span>: <span class="hljs-string">"rsa"</span>,
    <span class="hljs-attr">"size"</span>: <span class="hljs-number">2048</span>
  },
  <span class="hljs-attr">"names"</span>: [
  {
    <span class="hljs-attr">"C"</span>: <span class="hljs-string">"US"</span>,
    <span class="hljs-attr">"L"</span>: <span class="hljs-string">"CT"</span>,
    <span class="hljs-attr">"O"</span>: <span class="hljs-string">"Barrios Nunez"</span>,
    <span class="hljs-attr">"OU"</span>: <span class="hljs-string">"Barrios Nunez Hosts"</span>,
    <span class="hljs-attr">"ST"</span>: <span class="hljs-string">"USA"</span>
  }
  ],
  <span class="hljs-attr">"hosts"</span>: [
    <span class="hljs-string">"dmaf5.home"</span>,
    <span class="hljs-string">"localhost"</span>,
    <span class="hljs-string">"dmaf5"</span>,
    <span class="hljs-string">"192.168.1.23"</span>,
    <span class="hljs-string">"192.168.1.26"</span>
  ]
}
</code></pre>
<p>You can create three certificate types:</p>
<ul>
<li><p>client</p>
</li>
<li><p>server</p>
</li>
<li><p>peer</p>
</li>
</ul>
<p>We'll use only the server certificate, but we'll create all three:</p>
<pre><code class="lang-shell">cfssl gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config cfssl.json -profile=peer host-1.json| cfssljson -bare host-1-peer  # Peer
cfssl gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config cfssl.json -profile=server host-1.json | cfssljson -bare host-1-server  # Server
cfssl gencert -ca intermediate_ca.pem -ca-key intermediate_ca-key.pem -config cfssl.json -profile=client host-1.json | cfssljson -bare host-1-client  # Client
</code></pre>
<p>We are very close now. Install the intermediate certificate into the proper location so the clients on dmaf5 do not complain about the self-signed certificate:</p>
<pre><code class="lang-shell"># The path below is for Fedora, please check your OS documentation to find the right path for you
sudo /bin/cp --preserve --verbose tutorial/intermediate_ca.pem /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
</code></pre>
<p>Restart uvicorn to listen now only on a secure port, using the host key and certificates we just created:</p>
<pre><code class="lang-shell">(home_nmap) [josevnz@dmaf5 home_nmap]$ uvicorn home_nmap.main:app --host 0.0.0.0 --port 8443 --reload --ssl-keyfile=$PWD/tutorial/host-1-server-key.pem --ssl-certfile=$PWD/tutorial/host-1-server.pem
INFO:     Will watch for changes in these directories: ['/home/josevnz/Documents/home_nmap']
INFO:     Uvicorn running on https://0.0.0.0:8443 (Press CTRL+C to quit)
INFO:     Started reloader process [166275] using watchgod
INFO:     Started server process [166277]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     192.168.1.23:47704 - "GET /version HTTP/1.1" 200 OK
</code></pre>
<p>And then test with curl (without the --insecure flag, no complaints from curl):</p>
<pre><code class="lang-shell">[josevnz@dmaf5 ~]$ curl --fail https://dmaf5.home:8443/version
{"version":"0.0.1"}[josevnz@dmaf5 ~]$
</code></pre>
<p>Try again to capture the version of our service using tshark:</p>
<pre><code class="lang-shell"># 'tshark -i eno1 -Px -Y http' doesn't work anymore as the payload is encrypted. So at least lets see how the SSL hello goes
tshark -i eno1 -Y ssl -Px
  343 59.344539258 192.168.1.11 → 192.168.1.23 TLSv1 583 Client Hello

0000  1c 83 41 28 44 21 dc a6 32 f9 47 48 08 00 45 00   ..A(D!..2.GH..E.
0010  02 39 8b 6b 40 00 40 06 29 e1 c0 a8 01 0b c0 a8   .9.k@.@.).......
0020  01 17 93 14 20 fb 10 10 d7 6f 7d ff f7 c1 80 18   .... ....o}.....
0030  01 f6 0b fe 00 00 01 01 08 0a 08 a5 00 20 a8 8d   ............. ..
0040  27 47 16 03 01 02 00 01 00 01 fc 03 03 39 03 ac   'G...........9..
0050  19 7c bd 38 dc e2 cf 72 8b 7e 00 e2 2d fc 68 7a   .|.8...r.~..-.hz
0060  cc af 9c d6 d5 1d ed 94 79 b2 0f c8 cf 20 a3 f8   ........y.... ..
0070  2a 8e 20 c0 d2 c1 57 ee 36 48 2e 8f 46 e7 da 76   *. ...W.6H..F..v
0080  69 67 d1 9d 5a 70 24 0e 7d ea ec 8b e2 a0 00 3e   ig..Zp$.}......&gt;
0090  13 02 13 03 13 01 c0 2c c0 30 00 9f cc a9 cc a8   .......,.0......
00a0  cc aa c0 2b c0 2f 00 9e c0 24 c0 28 00 6b c0 23   ...+./...$.(.k.#
00b0  c0 27 00 67 c0 0a c0 14 00 39 c0 09 c0 13 00 33   .'.g.....9.....3
00c0  00 9d 00 9c 00 3d 00 3c 00 35 00 2f 00 ff 01 00   .....=.&lt;.5./....
00d0  01 75 00 00 00 0f 00 0d 00 00 0a 64 6d 61 66 35   .u.........dmaf5
00e0  2e 68 6f 6d 65 00 0b 00 04 03 00 01 02 00 0a 00   .home...........
00f0  0c 00 0a 00 1d 00 17 00 1e 00 19 00 18 33 74 00   .............3t.
0100  00 00 10 00 0e 00 0c 02 68 32 08 68 74 74 70 2f   ........h2.http/
0110  31 2e 31 00 16 00 00 00 17 00 00 00 31 00 00 00   1.1.........1...
0120  0d 00 2a 00 28 04 03 05 03 06 03 08 07 08 08 08   ..*.(...........
0130  09 08 0a 08 0b 08 04 08 05 08 06 04 01 05 01 06   ................
0140  01 03 03 03 01 03 02 04 02 05 02 06 02 00 2b 00   ..............+.
</code></pre>
<p>Note that it is possible to capture the traffic and decrypt it later if you have access to the private key. That's why it is so important that you keep that file secure.</p>
<p>What about our authorized request using the API key + encryption?</p>
<pre><code class="lang-shell">josevnz@raspberrypi:~$ curl 'https://dmaf5.home:8443/local_networks' --header 'accept: application/json' --header 'api-key: e4c03730-02a1-4cb9-8e00-36a63930c064'
["192.168.1.0/24"]
</code></pre>
<p>Our application setup is now complete.</p>
<h1 id="heading-what-did-we-learn">What did we learn?</h1>
<p>In this article, we covered many topics and went from a very simple XML parser to a self documenting web service. Not bad for a single session!</p>
<p>You should know about the following topics now:</p>
<ul>
<li><p>How to parse an Nmap XML results file, and enrich it with security advisories from NIST</p>
</li>
<li><p>How to enhance Nmap by mixing it with other scripts to automate its execution</p>
</li>
<li><p>How to apply Nmap options to make our local network scan faster</p>
</li>
<li><p>Understand what is pivoting and how you can use it to bypass firewall protections with the help of SSH and tcpproxy</p>
</li>
<li><p>How to write a REST-API on top of our original CLI script and secure it with SSL and basic authentication</p>
</li>
<li><p>How to add authorization to a web service using an API key</p>
</li>
<li><p>How to use tshark to demonstrate how HTTP traffic can be captured, and show the data payload</p>
</li>
<li><p>How to add encryption to a web service, by creating self-signed certificates</p>
</li>
</ul>
<h3 id="heading-and-what-else-could-you-learn-here-are-some-final-suggestions">And what else could you learn? Here are some final suggestions:</h3>
<ul>
<li><p>Check out the official Nmap <a target="_blank" href="https://nmap.org/docs.html">documentation</a>.</p>
</li>
<li><p>The <a target="_blank" href="https://nmap.org/book/osdetect.html">Operating system fingerprinting</a> is fascinating. Figuring out what exactly runs behind a port is an art and a moving target.</p>
</li>
<li><p>Integration with other great <a target="_blank" href="https://en.wikipedia.org/wiki/Penetration_test">penetration testing</a> tools like <a target="_blank" href="https://github.com/rapid7/metasploit-framework">Metasploit</a>, which you guessed, <a target="_blank" href="https://www.offensive-security.com/metasploit-unleashed/custom-scripting/">can also be scripted in Ruby</a>!</p>
</li>
<li><p>Also, as a bonus you have my code that can be installed using <a target="_blank" href="https://pip.pypa.io/en/stable/">pip</a> and can run some unit tests with <a target="_blank" href="https://docs.python.org/3/library/unittest.html">unittest</a>. I welcome pull requests and suggestions.</p>
</li>
</ul>
<p>Feel free to reach out with your comments and <a target="_blank" href="https://github.com/josevnz/home_nmap/issues">bug-reports</a>. I hope you enjoy it using it as much I enjoyed writing it.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
