端口扫描
1-测试 - 服务识别 - 听雨危楼 - 博客园 在渗透测试过程中,服务识别是一个很重要的环节,如果能识别出目标主机的服务、版本等信息,对于后续的渗透测试有重要的帮助。 对于入侵者来说,发现这些运行在目标上的服务,就可以利用这些软件上的漏洞入侵目标,对于网络安全的维护者来说,也可以提前发现系统的漏洞,从而预防这些入侵行为。
很多扫描工具都采用了一种十分简单的方式,就是根据端口来判断服务类型,因为通常常见的服务都会运行在固定的端口上,如下面的表中,列举了常见的服务和所占用的端口。
文件共享服务端口:
| 端口号 | 说明 | 作用 |
|---|---|---|
| 21/22/69 | FTP/TFTP | 允许匿名上传、下载、破解和嗅探攻击 |
| 2049 | NFS 服务 | 配置不当 |
| 139 | Samba 服务 | 破解、未授权访问、远程代码执行 |
| 389 | LDAP(目录访问协议) | 注入、允许匿名访问、使用弱口令 |
远程连接服务端口:
| 端口号 | 说明 | 作用 |
|---|---|---|
| 22 | SSH 远程连接 | 破解、SSH 隧道及内网代理转发、文件传输 |
| 23 | Telnet 远程连接 | 破解、嗅探、弱口令 |
| 3389 | Rdp 远程桌面连接 | Shift 后门(需要 Windows service 2003以下的系统)、破解 |
| 5900 | VNC | 弱口令破解 |
| 5632 | PyAnywhere | 抓密码、代码执行 |
Web 应用服务端口:
| 端口号 | 说明 | 作用 |
|---|---|---|
| 80/443/8080 | 常见 Web 服务端口 | Web 攻击、破解、服务器版本漏洞 |
| 7001/7002 | WebLogic 控制台 | Java 反序列化、弱口令 |
| 8080/8089 | Jboss/Resin/Jetty/Jenkins | 反序列化、控制台弱口令 |
| 9090 | WebSphere 控制台 | Java 反序列化、弱口令 |
| 4848 | ClassFish 控制台 | 弱口令 |
| 1352 | Lotus Domino 邮件服务 | 弱口令、信息泄露、破解 |
| 10000 | Webmin-Web 控制面板 | 弱口令 |
数据库服务端口:
| 端口号 | 说明 | 作用 |
|---|---|---|
| 3306 | MySQL | 注入、提权、破解 |
| 1433 | MSSQL | 注入、提权、SA 弱口令、破解 |
| 1521 | Oracle | TNS 破解、注入、反弹 shell |
| 5432 | PostgreSQL | 破解、注入、弱口令 |
| 27017/27018 | MongoDB | 破解、未授权访问 |
| 6379 | Redis | 可尝试未授权访问、弱口令破解Redis未授权访问漏洞 |
| 5000 | SysBase/DB2 | 破解、注入 |
邮件服务端口:
| 端口号 | 说明 | 作用 |
|---|---|---|
| 25 | SMTP 邮件服务 | 邮件伪造 |
| 110 | POP3协议 | 破解、嗅探 |
| 143 | IMAP 协议 | 破解 |
网络常见协议端口:
| 端口号 | 说明 | 作用 |
|---|---|---|
| 53 | DNS 域名系统 | 允许区域传送、DNS 劫持、缓存投毒、欺骗 |
| 67/68 | DHCP 服务 | 劫持、欺骗 |
| 161 | SNMP 协议 | 破解、搜集目标内网信息 |
特殊服务端口:
| 端口号 | 说明 | 作用 |
|---|---|---|
| 2181 | Zookeeper 服务 | 未授权访问 |
| 8069 | Zabbix 服务 | 远程执行、SQL 注入 |
| 9200/9300 | Elasticsearch | 远程执行 |
| 11211 | Memcache 服务 | 未授权访问 |
| 512/513/514 | Linux Rexec 服务 | 破解、Rlogin 登录 |
| 873 | Rsync 服务 | 匿名访问、文件上传 |
| 3690 | SVN 服务 | SVN 泄露、未授权访问 |
| 50000 | SAP Management Console | 远程执行 |
了解了常见的服务及所占端口号,就可以向目标开放的端口发送探针数据包,根据目标主机返回的 banner 信息与存储总结的 banner 信息进行对比,进而确定运行的服务类型。
著名的 nmap 扫描工具就是采用这种方法,它包含一个十分强大的 banner 库,而且这个库仍然在不断的完善中。
接下来,就按照上面介绍的思路来编写对目标服务进行扫描的程序。
手动实现
centos7.3 + python3.6.8
思路:
- 使用socket确认指定的
host:port是否开放 - 如果是开放端口,则发送探测信息,获取返回的banner信息
- 对返回的banner信息去指纹库进行匹配,识别服务类型
具体代码如下:
import re
import socket
from argparse import ArgumentParser
from os import cpu_count
from concurrent.futures import ThreadPoolExecutor
# banner指纹库,用于匹配开放端口的服务类型,可以自行扩展,该库越大匹配精度越高
SIGNS = (
# 协议 版本 关键字
b'FTP|FTP|^220.*FTP',
b'MySQL|MySQL|mysql_native_password',
b'oracle-https|^220- ora',
b'Telnet|Telnet|Telnet',
b'Telnet|Telnet|^\r\n%connection closed by remote host !\x00$',
b'VNC|VNC|^RFB',
b'IMAP|IMAP|^\* OK.*?IMAP',
b'POP|POP|^\+OK.*?',
b'SMTP|SMTP|^220.*?SMTP',
b'Kangle|Kangle|HTTP.*Kangle',
b'SMTP|SMTP|^554 SMTP',
b'SSH|SSH|^SSH-',
b'HTTPS|HTTPS|Location: https',
b'HTTP|HTTP|HTTP/1.1',
b'HTTP|HTTP|HTTP/1.0',
)
def regex(response, port, host):
"""
利用re.search将返回的banner信息与SIGNS包含的指纹信息进行正则匹配
如果匹配到了,就输出该端口和识别结果,如 [3306] open MySQL
如果没有匹配到,说明SIGNS指纹库没有记录该服务,输出端口和Unrecognized,表示没有识别出来服务类型
"""
# 首先判断判断探测是否被拒绝
if re.search(b'<title>502 Bad Gateway', response):
console = "Service failed to access!!!"
# 循环指纹库进行匹配,匹配到就打印匹配结果,并不在往下进行匹配
for pattern in SIGNS:
pattern = pattern.split(b'|')
if re.search(pattern[-1], response, re.IGNORECASE):
console = "[{}:{}] open {}".format(host, port, pattern[1].decode())
break
# 如果循环完整个指纹库,也没有匹配上,说明该开放端口没有识别出来服务类型
else:
console = "[{}:{}] open {}".format(host, port, 'Unrecognized')
print(console)
def send_msg(host, port):
"""
首先调用socket.connect_ex探测目标主机端口是否开放,如果端口开放,则利用sock.sendall将PROBE探针发送给目标端口
并用sock.recv接收返回的指纹信息
再将指纹信息交给regex进行指纹匹配识别服务类型
"""
PROBE = 'GET / HTTP/1.0\r\n\r\n'
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(10)
result = sock.connect_ex((host, port))
if result == 0:
try:
sock.sendall(PROBE.encode())
response = sock.recv(512)
if response:
regex(response, port, host)
except (ConnectionResetError, socket.timeout) as e:
print('[{}:{}] detect error!! {}'.format(host, port, e))
sock.close()
def main(args):
"""
从args中读取port端口范围,判断是单个端口还是范围端口,然后将端口存储到临时的列表中
使用线程池来处理大量的端口
"""
port_list = []
print('host:{} port: {}'.format(args.host, args.port))
if '-' in args.port:
startPort, endPort = args.port.split('-')
for i in range(int(startPort), int(endPort) + 1):
port_list.append(int(i))
else:
# 如果是单个端口,没有必要使用线程池
send_msg(args.host, int(args.port))
return
# 为了性能考虑,如果端口数量小于cpu_count() * 5, 线程池中线程的数量等于端口的数量
if len(port_list) <= args.threadNum:
threadNum = len(port_list)
else:
# 否则线程池中线程的数量等于cpu_count() * 5
threadNum = args.threadNum
t = ThreadPoolExecutor(threadNum)
for i in port_list:
t.submit(send_msg, args.host, i)
if __name__ == '__main__':
try:
parse = ArgumentParser()
parse.add_argument('--host', dest='host', default='127.0.0.1', type=str, help='获取host')
parse.add_argument('--port', dest='port', default="900-1000", type=str, help='获取port')
parse.add_argument('--threadNum', dest='threadNum', default=cpu_count() * 5, type=str, help='获取port')
args = parse.parse_args()
main(args)
except Exception as e:
print(e)
"""
[root@cs tmp]# python3 service_idt.py --port 1-20000
host:127.0.0.1 port: 1-20000
[127.0.0.1:21] open FTP
[127.0.0.1:25] open SMTP
[127.0.0.1:22] open SSH
[127.0.0.1:3306] open MySQL
[127.0.0.1:5672] open Unrecognized
[127.0.0.1:5676] open Unrecognized
[127.0.0.1:5674] detect error!! [Errno 104] Connection reset by peer
[127.0.0.1:5675] detect error!! [Errno 104] Connection reset by peer
[127.0.0.1:5673] open Unrecognized
[127.0.0.1:6379] open Unrecognized
[127.0.0.1:15672] open HTTP
[127.0.0.1:15675] detect error!! [Errno 104] Connection reset by peer
[127.0.0.1:15674] detect error!! [Errno 104] Connection reset by peer
[127.0.0.1:15676] open HTTP
[127.0.0.1:15673] open HTTP
"""
使用nmap模块实现
import nmap3
nmap = nmap3.Nmap()
nmap.scan_top_ports("127.0.0.1")
"""
{
'127.0.0.1': [{
'host': '127.0.0.1',
'protocol': 'tcp',
'portid': '21',
'state': 'open',
'reason': 'syn-ack',
'reason_ttl': '64',
'service': {
'name': 'ftp',
'method': 'table',
'conf': '3'
}
}, {
'host': '127.0.0.1',
'protocol': 'tcp',
'portid': '22',
'state': 'open',
'reason': 'syn-ack',
'reason_ttl': '64',
'service': {
'name': 'ssh',
'method': 'table',
'conf': '3'
}
}, {
'host': '127.0.0.1',
'protocol': 'tcp',
'portid': '23',
'state': 'closed',
'reason': 'reset',
'reason_ttl': '64',
'service': {
'name': 'telnet',
'method': 'table',
'conf': '3'
}
}, {
'host': '127.0.0.1',
'protocol': 'tcp',
'portid': '25',
'state': 'open',
'reason': 'syn-ack',
'reason_ttl': '64',
'service': {
'name': 'smtp',
'method': 'table',
'conf': '3'
}
}, {
'host': '127.0.0.1',
'protocol': 'tcp',
'portid': '80',
'state': 'closed',
'reason': 'reset',
'reason_ttl': '64',
'service': {
'name': 'http',
'method': 'table',
'conf': '3'
}
}, {
'host': '127.0.0.1',
'protocol': 'tcp',
'portid': '110',
'state': 'closed',
'reason': 'reset',
'reason_ttl': '64',
'service': {
'name': 'pop3',
'method': 'table',
'conf': '3'
}
}, {
'host': '127.0.0.1',
'protocol': 'tcp',
'portid': '139',
'state': 'closed',
'reason': 'reset',
'reason_ttl': '64',
'service': {
'name': 'netbios-ssn',
'method': 'table',
'conf': '3'
}
}, {
'host': '127.0.0.1',
'protocol': 'tcp',
'portid': '443',
'state': 'closed',
'reason': 'reset',
'reason_ttl': '64',
'service': {
'name': 'https',
'method': 'table',
'conf': '3'
}
}, {
'host': '127.0.0.1',
'protocol': 'tcp',
'portid': '445',
'state': 'closed',
'reason': 'reset',
'reason_ttl': '64',
'service': {
'name': 'microsoft-ds',
'method': 'table',
'conf': '3'
}
}, {
'host': '127.0.0.1',
'protocol': 'tcp',
'portid': '3389',
'state': 'closed',
'reason': 'reset',
'reason_ttl': '64',
'service': {
'name': 'ms-wbt-server',
'method': 'table',
'conf': '3'
}
}],
'runtime': {
'time': '1607490186',
'timestr': 'Wed Dec 9 13:03:06 2020',
'summary': 'Nmap done at Wed Dec 9 13:03:06 2020; 1 IP address (1 host up) scanned in 0.07 seconds',
'elapsed': '0.07',
'exit': 'success'
},
'stats': {
'scanner': 'nmap',
'args': '/usr/bin/nmap -oX - --top-ports 10 127.0.0.1',
'start': '1607490186',
'startstr': 'Wed Dec 9 13:03:06 2020',
'version': '7.91',
'xmloutputversion': '1.05'
}
}
"""