百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

SpringBoot + nmap4j 获取端口信息

myzbx 2025-09-18 23:46 2 浏览

序言

今天在工作的时候,领导突然安排和我说一个需求,就是根据一个 ip 和 端口去获取对应服务上对应端口的信息,当时主要是为了确定数据库的版本和型号,比如 MySQL、Oracle 这些数据库,我后面尝试发现其他端口也可以获取信息。这个在公司里之前是通过 python 来写的,python 里面刚好有这个模块,但是 Java 没有,所以写这篇文章记录一下,帮助大家以后避免踩坑。代码已经提交到了我的GitHub:github.com/maoshengyzx…

nmap4j

nmap4j 是一个用于 Java 语言的 Nmap 端口扫描器的包装库,它允许 Java 开发者在自己的项目中方便地调用 Nmap 的功能进行网络扫描和探测。GitHub地址: github.com/narkisr/nma… 在运行这个代码之前,我们是需要下载 nmap 的可执行文件,地址为:nmap.org/download.ht…

这里下载第一个,下载后安装就行了。

代码说明

接下来和大家说一下 nmap4j 中的测试代码,这里面有个坑,我当时找了好久,代码在
test/org/nmap4j/Nmap4jTest.java

    public class Nmap4jTest {
	
	
	@Test
	public void basicNmap4jUsageTest() {
		
		try {
               // 这里的路径要改为刚才 nmap 软件的安装路径
			String path = "/usr/local" ;
			
			Nmap4j nmap4j = new Nmap4j( path ) ;
               // 这地方使用了 -oX 后面要跟文件名称
			nmap4j.addFlags( "-sV -T5 -O -oX -" ) ;
			nmap4j.includeHosts( "localhost" ) ;
			nmap4j.execute() ;
			if( !nmap4j.hasError() ) {
                        
				NMapRun nmapRun = nmap4j.getResult() ;  // 这一行一定要注释掉,不然会一直报错
				String output = nmap4j.getOutput() ;  // 这一样代码意义也不大,我直接删掉了
				if( output == null ) {
					fail() ;
				}
				String errors = nmap4j.getExecutionResults().getErrors() ;
				if (errors == null ) {
					fail() ;
				}
			}
		} catch (NMapInitializationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			fail() ;
		} catch (NMapExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			fail() ;
		}
		
	}

}

参数说明:

  • 目标选择参数:
    • -iL :从文件中读取扫描目标列表。例如,nmap -iL targets.txt,会从targets.txt文件中读取每行一个的 IP 地址或域名作为扫描目标。
    • -iR :随机选择指定数量的主机进行扫描。如nmap -iR 100,会随机选取 100 个主机进行扫描。
    • --exclude <host1[,host2,...]> :排除指定的主机或网络不进行扫描。例如,nmap 192.168.1.0/24 --exclude 192.168.1.100,192.168.1.200,将扫描192.168.1.0/24网段,但排除192.168.1.100和192.168.1.200这两台主机。
    • --excludefile <exclude_file> :从文件中读取要排除的主机或网络列表。
  • 扫描类型参数
    • -sS:TCP SYN 扫描,也称为半开放扫描。它发送 SYN 包到目标端口,如果收到 SYN/ACK 响应,就表示端口开放;如果收到 RST 响应,则表示端口关闭。这种扫描方式速度快,且不容易被目标系统记录,相对隐蔽,例如nmap -sS 192.168.1.100。
    • -sT:TCP 连接扫描,通过完整的 TCP 三次握手来确定端口是否开放。这种扫描方式最准确,但也最容易被检测到,如nmap -sT 192.168.1.100。
    • -sU:UDP 扫描,用于检测目标主机上的 UDP 端口是否开放。因为 UDP 是无连接协议,所以判断端口状态相对复杂,nmap -sU 192.168.1.100可对指定主机进行 UDP 扫描。
    • -sF、-sX、-sN:分别是 FIN 扫描、XMAS 扫描和 NULL 扫描。这些扫描方式通过发送特殊标志位的 TCP 包来判断端口状态,常用于绕过一些简单的防火墙检测。
  • 端口指定参数
    • -p :指定要扫描的端口范围。可以是单个端口,如-p 80;也可以是多个端口,如-p 80,443,8080;还可以是端口范围,如-p 1-1000表示扫描 1 到 1000 号端口。
    • --top-ports :扫描最常用的指定数量的端口。例如,nmap --top-ports 100 192.168.1.100会扫描目标主机上最常用的 100 个端口。
    • -F:快速扫描模式,只扫描一些常见的端口,相当于-p 1-1024加上一些其他常用端口。
  • 服务探测参数
    • -sV:启用服务版本探测,尝试确定目标主机上运行的服务及其版本信息。例如,nmap -sV 192.168.1.100可以扫描出目标主机开放端口上运行的服务名称和版本号。
    • --version-intensity :设置服务版本探测的强度,级别越高,探测越全面,但耗时也越长,取值范围是 0 到 9。
  • 操作系统探测参数
    • -O:启用操作系统探测,尝试识别目标主机的操作系统类型和版本。如nmap -O 192.168.1.100。
    • --osscan-limit:限制操作系统探测只对可能的目标进行,这样可以加快扫描速度,但可能会降低准确性。
    • --osscan-guess:更积极地猜测操作系统类型,当 Nmap 不确定时会给出更宽泛的猜测结果。
  • 输出参数
    • -oN :将扫描结果以正常格式保存到指定文件。例如,nmap -oN scan_results.txt 192.168.1.100会把扫描结果保存到scan_results.txt文件中。
    • -oX :将扫描结果以 XML 格式保存到指定文件,方便后续使用脚本或其他工具进行解析和处理。
    • -oG :将扫描结果以 Grep 格式保存,这种格式便于使用文本处理工具进行分析。
    • -oA :以多种格式(包括正常、XML 和 Grep 格式)保存扫描结果,文件名为指定的基本名称加上相应的扩展名。
    • -v:详细输出模式,显示更多的扫描过程信息,如发送的数据包、收到的响应等。使用多个v可以增加详细程度,如-vv、-vvv。

这里给大家看一下改造后的代码

/**
 * 使用 nmap4j 工具进行扫描
 *
 * @param ip    目标 ip
 * @param ports 目标端口
 * @return 端口信息列表
 */
@RequestMapping("/querydb")
public List<NmapPortInfo> querydb(@RequestParam(value = "ip") String ip, @RequestParam("ports") List<String> ports) {
    ArrayList<NmapPortInfo> portInfos = new ArrayList<>();
    // 1.拼接端口
    String portStr = StrUtil.join(",", ports);
    //2. 指定 nmap 路径
    String path = "D:/StudyApps/nmap";
    String fileName = "temp_result.xml";

    Nmap4j nmap4j = new Nmap4j(path);

    //3.读取端口耗时较长,可以使用异步
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
        nmap4j.addFlags("-sV -p " + portStr + " -T5 -O -oX " + fileName);
        nmap4j.includeHosts(ip);
        try {
            nmap4j.execute();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }, threadPoolExecutor);

    future.join();

    //4. 获取端口信息
    return getPortInfo(portInfos, fileName);
}
/**
     * 获取 ip + 端口信息,封装为集合返回前端
     *
     * @param portInfos 返回前端集合
     * @param fileName  临时的 xml 文件
     * @return 信息列表
     */
    @SneakyThrows
    private List<NmapPortInfo> getPortInfo(List<NmapPortInfo> portInfos, String fileName) {
        // 获取项目所在路径
        String projectPath = System.getProperty("user.dir");
        // 拼接文件路径
        String filePath = projectPath + FileUtil.FILE_SEPARATOR + fileName;
        log.info("文件路径:{}", filePath);

        // nmap 返回 xml 格式固定,使用 dom4j 解析
        SAXReader reader = new SAXReader();
        org.dom4j.Document document = reader.read(FileUtil.file(filePath));
        org.dom4j.Element rootElement = document.getRootElement();
        org.dom4j.Element element = rootElement.element("host");

        org.dom4j.Element xmlPorts = element.element("ports");

        List<org.dom4j.Element> port = xmlPorts.elements("port");
        for (org.dom4j.Element port1 : port) {
            Element service = port1.element("service");
            String product = service.attributeValue("product");
            String version = service.attributeValue("version");
            NmapPortInfo nmapPortInfo = new NmapPortInfo(product, version);
            portInfos.add(nmapPortInfo);
        }

        // 删除临时文件
        FileUtil.del(filePath);
        return portInfos;
    }
}

我这里就是没有去按照官网上的写法,我的思路是文件已经下载了我直接去读取 xml 文件解决会更快,这里是使用 dom4j 来读取的 xml 文件。代码就这么多,最后请求是可以获取到数据的:

补充

由于这段代码是在windows上运行的,而在实际的环境中项目都会部署到 Linux 环境汇总,所以我不得不在 Linux 上去运行调试这段代码。代码如下:

/**
 * 使用 nmap4j 工具进行扫描, linux系统
 *
 * @param ip    目标 ip
 * @param ports 目标端口
 * @return 端口信息列表
 */
@GetMapping("/linux/querydb")
@SneakyThrows
public List<NmapPortInfo> linuxQuerydb(@RequestParam(value = "ip") String ip, @RequestParam("ports") List<String> ports) {
    ArrayList<NmapPortInfo> portInfos = new ArrayList<>();
    // 1.拼接端口
    String portStr = StrUtil.join(",", ports);
    String fileName = "temp_result.xml";
    //2. linux namp 命令
    String nmapCommand = "nmap -sV -p " + portStr + " -T5 -O -oX " + fileName + " " + ip;

    //3. 读取端口耗时较长,可以使用异步
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
        Process nampProcess = null;
        try {
            // 3. 运行命令
            nampProcess = Runtime.getRuntime().exec(nmapCommand);
            // 4. 等待命令执行完成
            nampProcess.waitFor();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }, threadPoolExecutor);

    future.join();

    // 5. 获取端口信息
    return getPortInfo(portInfos, fileName);
}

这里的初步思路是直接调用 Linux 的命令执行 nmap (Linux系统中必须下载 nmap)。至于下载这里就不多说了,大家只需要注意下载的版本最好也为 7.95,其他版本下载的 xml 文件有问题,无法解析。

总结

忘记和大家说了,nmap4j 在maven仓库是搜不到的,所以只通过 jar 包的方式来引入,地址为:
master.dl.sourceforge.net/project/nma…

相关推荐

vue:生命周期钩子函数及顺序_列举出5个vue中常用的生命周期钩子函数

一、vue的钩子相关顺序Vue实例有一个完整的生命周期,在newVue()后,会初始化数据,如下://初始化的入口,各种初始化工作initMixin(Vue);//数据绑定的核心方法,包括常用...

最长递增子序列:从经典算法到 Vue3 运行时核心优化

最长递增子序列(LongestIncreasingSubsequence,LIS)正悄然成为性能分水岭。它不仅是面试的高频考点,更是Vue3快速Diff算法赖以实现O(nlogn)...

十分钟掌握Vue 3性能优化:实战技巧与避坑指南

「为什么我的Vue应用越做越卡?」这是最近团队新人最常问的问题。本文将从真实电商项目出发,手把手教你用Vue3的现代特性实现性能飞跃,文末还准备了可复用的优化检查清单!一、先看疗效:优化前后对比优...

JavaScript学习 -- 文本节点_html 文本节点

什么是文本节点在HTML文档中,文本节点是一种特殊的dom节点,它包含文本内容,没有任何标记或属性。<p>这是一段文本节点</p>在上面的代码中,<p>元素包含了...

JavaScript中this指向各种场景_javascript的this指向

在JavaScript中,this的指向是一个核心概念,其值取决于函数的调用方式,而非定义位置(箭头函数除外)。以下是this指向的常见场景及具体说明:1.全局作用域中的this在全局作用域(非...

v-if和v-for的优先级是什么?_v-if和v-for的区别,什么时候用

#一、作用v-if指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回true值的时候被渲染v-for指令基于一个数组来渲染一个列表。v-for指令需要使用iteminitems...

Vue插槽(Slot)深度解析:从匿名到作用域的组件复用革命

在Vue组件化开发中,内容分发始终是核心挑战之一。当我们需要让组件既能保持结构复用,又能灵活定制局部内容时,插槽(Slot)机制应运而生。从基础的匿名插槽到复杂的作用域插槽,Vue的插槽系统逐步解决了...

手摸手带你解决AI应用开发中Markdown渲染问题

使用Markdown-It+VueRender实现安全可控的Markdown渲染在前端项目中,Markdown的渲染经常使用markdown-it。它功能丰富、插件多,但默认的渲染方...

Vue3 新趋势:10 个最强 X 操作!_vue.3

Vue3为前端开发带来了诸多革新,它不仅提升了性能,还提供了更简洁、更强大的API。以下是十个最值得学习和使用的Vue3API,它们将助力你的开发工作迈向新高度。浅层响应式API:shall...

25个React最佳实践小技巧_reactor设计模式

以下是25个React开发中实用的最佳实践与小技巧,覆盖组件设计、状态管理、性能优化、代码规范、错误处理等核心场景,每个技巧均附示例和核心原因,帮助你写出更高效、可维护的React代码。一...

javascript函数的call、apply和bind的原理及作用详解

javascript函数的call、apply和bind本质是用来实现继承的,专业点说法就是改变函数体内部this的指向,当一个对象没有某个功能时,就可以用这3个来从有相关功能的对象里借用过来...

简单介绍一下前端各框架中的模板标签

在各大前端框架、小程序中,此类标签的作用主要是用来帮助我们包裹多个元素。在浏览器实际渲染中会将其移除只渲染其包裹的DOM元素,所以说不会增加额外的DOM节点在小程序中使用小程序中的模板标签是<...

面试官问我,后端一次性返回十万条数据,前端应该怎么处理 ?

问题描述面试官:后端一次性返回10万条数据给你,你如何处理?我:歪嘴一笑,马上给后端发送一百万次请求,干蹦他的服务器,让他给爷哭!问题考察点性能优化意识(能否识别出“10万条数据”会导致性能问题?是...

React系列十 - 高阶组件以及组件补充

源自:coderwhy一.高阶组件1.1.认识高阶组件什么是高阶组件呢?相信很多同学都听说过,也用过高阶函数,它们非常相似,所以我们可以先来回顾一下什么是高阶函数。高阶函数的维基百科定义:至少...

从0开始写一个虚拟滚动组件_虚拟滚动原理

如果一个页面有1W+条数据,该怎么渲染比较好。不管是在我们的实际项目开发中还是在面试的过程中都会遇到类似的问题。相信很多同学会想到分页。当然这也是最传统也是最保底的解决方案了。如果有开发过electr...