- 전체
- 보안뉴스
- 제로데이취약점
- 해킹프로그래밍
- 웹해킹
- 해킹기법
- 정보보호
- 정보보안기사 - 국가기술자격
- 악성코드분석_리버싱
- 시큐어코딩_개발보안진단원
- CISSP
- CISA
- 모의해킹_penetration-test
- deepweb / tor network
- Kali Linux
모의해킹(Penetration Testing) PortScanner 7. 업그레이드2 : Nmap 활용
Nmap은 포트 스캐너로 유명한 공개 소프트웨어이다. 파이썬으로 제작되지는 않았으나 nmap에서 사용하는 라이브러리를 python-nmap 라이브러리로 구현해 강력한 기능과 훌륭한 호환성을 제공한다. Nmap은 스텔스 기능과 OS 정보, 방화벽 필터링 확인 등 여러 다양한 기능을 제공한다. 이 라이브러리를 사용하여 Nmap의 강력한 기능으로 여러분이 원하는 python 프로그램을 간단히 구현할 수 있다.
python-nmap 사이트
http://xael.org/norman/python/python-nmap/
nmap 공식 사이트
nmap이 설치되어 있지 않다면 먼저 nmap공식 사이트로부터 nmap을 설치한다. 설치과정은 모두 기본 설정으로 진행하면 되기 때문에 따로 가이드하지 않았다.
그림 Nmap 공식사이트 다운로드
그림 windows 전용 nmap 다운로드 링크
nmap 설치 후, python-namp 사이트에서 python-nmap-0.3.1.tar파일을 받는다. c:\python-nmap에 압축을 풀고서, 윈도우 키 + R을 실행창을 연 뒤 ‘cmd’를 실행한다. 그리고 다음 명령어들을 차례대로 입력하자. 폴더 경로는 차이가 있을 수 있다.
1
2
3
|
C:\> cd C:\python-nmap\python-nmap-0.3.1
C:\python-nmap\python-nmap-0.3.1> setup.py build
C:\python-nmap\python-nmap-0.3.1> setup.py install
|
cs |
-
리눅스 환경에서도 같은 방법으로 설치를 진행하면 된다.
압축 해제 -> python setup.py build -> python setup.py install
정상적으로 설치되면 python에서 nmap을 import할 수 있다.
정상적으로 설치가 완료했다면 위 코드가 오류 없이 실행될 것이다.
1
2
|
>>> import nmap
>>> nm = nmap.PortScanner()
|
cs |
먼저 nmap 라이브러리의 PortScanner 클래스를 사용하여 nm을 생성하자.
1
2
|
>>> import nmap # nmap 라이브러리 임포트
>>> nm = nmap.PortScanner() # PortScanner 클래스 nm 객체 생성
|
cs |
nm 객체를 성공적으로 생성했으니, 이제 스캔을 시도해 보자. PortScanner 클래스의 scan함수는 인자를 3개 받을 수 있다. 형식은 다음과 같다.
nm.scan(hosts=’127.0.0.1’, ports=None, arguments=’-sV’)
metasploitable2(192.168.157.133)을 대상으로 20-443 포트를 대상으로 스캔을 시도한다.
1
2
3
4
5
6
7
8
9
10
11
|
>>> nm.scan(hosts='192.168.157.133', ports='20-443', arguments='-sV')
{'nmap': {'scanstats': {'uphosts': u'1', 'timestr': u'Fri May 08 01:00:50 2015', 'downhosts': u'0', 'totalhosts': u'1',
'elapsed': u'20.57'}, 'scaninfo': {u'tcp': {'services': u'20-443', 'method': u'syn'}}, 'command_line': u'nmap -oX - -p 20-443 -sV 192.168.157.133'},
'scan': {u'192.168.157.133': {'status': {'state': u'up', 'reason': u'arp-response'}, 'hostname': '', 'addresses': {u'mac': u'00:0C:29:4D:18:3B', u'ipv4': u'192.168.157.133'},
u'tcp': {139: {'product': u'Samba smbd', 'name': u'netbios-ssn', 'extrainfo': u'workgroup: WORKGROUP', 'reason': u'syn-ack', 'state': u'open', 'version': u'3.X', 'conf': u'10'},
111: {'product': '', 'name': u'rpcbind', 'extrainfo': u'RPC #100000', 'reason': u'syn-ack', 'state': u'open', 'version': u'2', 'conf': u'10'}, 80: {'product': u'Apache httpd',
'name': u'http', 'extrainfo': u'(Ubuntu) DAV/2', 'reason': u'syn-ack', 'state': u'open', 'version': u'2.2.8', 'conf': u'10'}, 53: {'product': u'ISC BIND', 'name': u'domain',
'extrainfo': '', 'reason': u'syn-ack', 'state': u'open', 'version': u'9.4.2', 'conf': u'10'}, 22: {'product': u'OpenSSH', 'name': u'ssh', 'extrainfo': u'protocol 2.0',
'reason': u'syn-ack', 'state': u'open', 'version': u'4.7p1 Debian 8ubuntu1', 'conf': u'10'}, 23: {'product': u'Linux telnetd', 'name': u'telnet', 'extrainfo': '',
'reason': u'syn-ack', 'state': u'open', 'version': '', 'conf': u'10'}, 25: {'product': u'Postfix smtpd', 'name': u'smtp', 'extrainfo': '', 'reason': u'syn-ack', 'state': u'open',
'version': '', 'conf': u'10'}, 21: {'product': u'vsftpd', 'name': u'ftp', 'extrainfo': '', 'reason': u'syn-ack', 'state': u'open', 'version': u'2.3.4', 'conf': u'10'}}}}}
|
cs |
결과는 다중의 dict형식으로 전달된다. 이 결과 값을 result 값에 담아서 더 분석해보자. nmap이라는 key에는 전에 세팅한 nmap 커맨드 설정 값들이 들어있다.
1
2
3
4
5
|
>>>result=_
>>>result['nmap']{'scanstats': {'uphosts': u'1', 'timestr': u'Fri May 08 01:00:50 2015',
'downhosts': u'0', 'totalhosts': u'1', 'elapsed': u'20.57'},
'scaninfo': {u'tcp': {'services': u'20-443', 'method': u'syn'}},
'command_line': u'nmap -oX - -p 20-443 -sV 192.168.157.133'}
|
cs |
scan이라는 key에 결과에 대한 내용이 들어있다. 각 호스트 별(여기서는 192.168.157.133만 스캔했다)로 내용을 정리하고 있다. 호스트 내에는 status, hostname, addresses, tcp 등의 정보를 상세하게 담고 있는 것을 확인할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
>>> result['scan'][u'192.168.157.133']['status']
{'state': u'up', 'reason': u'arp-response'}
>>> result['scan'][u'192.168.157.133']['hostname']
''
>>> result['scan'][u'192.168.157.133']['addresses']
{u'mac': u'00:0C:29:4D:18:3B', u'ipv4': u'192.168.157.133'}
>>> result['scan'][u'192.168.157.133'][u'tcp']
{139:
{'product': u'Samba smbd', 'name': u'netbios-ssn', 'extrainfo': u'workgroup:
WORKGROUP', 'reason': u'syn-ack', 'state': u'open', 'version': u'3.X', 'conf':
u'10'}, 111: {'product': '', 'name': u'rpcbind', 'extrainfo': u'RPC #100000',
'reason': u'syn-ack', 'state': u'open', 'version': u'2', 'conf': u'10'}, 80:
{'product': u'Apache httpd', 'name': u'http', 'extrainfo': u'(Ubuntu) DAV/2',
'reason': u'syn-ack', 'state': u'open', 'version': u'2.2.8', 'conf': u'10'},
53: {'product': u'ISC BIND', 'name': u'domain', 'extrainfo': '', 'reason':
u'syn-ack', 'state': u'open', 'version': u'9.4.2', 'conf': u'10'}, 22:
{'product': u'OpenSSH', 'name': u'ssh', 'extrainfo': u'protocol 2.0', 'reason':
u'syn-ack', 'state': u'open', 'version': u'4.7p1 Debian 8ubuntu1', 'conf':
u'10'}, 23: {'product': u'Linux telnetd', 'name': u'telnet', 'extrainfo': '',
'reason': u'syn-ack', 'state': u'open', 'version': '', 'conf': u'10'}, 25: {'product':
u'Postfix smtpd', 'name': u'smtp', 'extrainfo': '', 'reason': u'syn-ack',
'state': u'open', 'version': '', 'conf': u'10'}, 21: {'product': u'vsftpd',
'name': u'ftp', 'extrainfo': '', 'reason': u'syn-ack', 'state': u'open',
'version': u'2.3.4', 'conf': u'10'}}
|
cs |
위와 같은 방식으로는 결과를 확인하기 어려우니 결과를 예쁘게 출력하는 방법에 대해서 알아보도록 하자. 이 방법은 위에서 파악한 dict 구조대로 for문을 동작시켜 출력한 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
>>> for host in nm.all_hosts():
print('-------------------------------------------------')
print('Host : %s (%s)' %(host, nm[host].hostname()))
print('State : %s' % nm[host].state())
proto = 'tcp'
print('--------')
print('Protocol : %s' %proto)
lport = nm[host][proto].keys()
lport.sort()
for port in lport:
print ('port : %s\tstate : %s' % (port,
str(nm[host][proto][port]['state'])))
-------------------------------------------------
Host : 192.168.157.133 ()
State : up
--------
Protocol : tcp
port : 21 state : open
port : 22 state : open
port : 23 state : open
port : 25 state : open
port : 53 state : open
port : 80 state : open
port : 111 state : open
port : 139 state : open
|
cs |
이번에는 라이브러리에서 제공하는 csv 함수로 결과를 출력해보자.
1
2
3
4
5
6
7
8
9
10
|
>>> print(nm.csv())
host;protocol;port;name;state;product;extrainfo;reason;version;conf
192.168.157.133;tcp;21;ftp;open;vsftpd;;syn-ack;2.3.4;10
192.168.157.133;tcp;22;ssh;open;OpenSSH;protocol 2.0;syn-ack;4.7p1 Debian 8ubuntu1;10
192.168.157.133;tcp;23;telnet;open;Linux telnetd;;syn-ack;;10
192.168.157.133;tcp;25;smtp;open;Postfix smtpd;;syn-ack;;10
192.168.157.133;tcp;53;domain;open;ISC BIND;;syn-ack;9.4.2;10
192.168.157.133;tcp;80;http;open;Apache httpd;(Ubuntu) DAV/2;syn-ack;2.2.8;10
192.168.157.133;tcp;111;rpcbind;open;;RPC #100000;syn-ack;2;10
192.168.157.133;tcp;139;netbios-ssn;open;Samba smbd;workgroup: WORKGROUP;syn-ack;3.X;10
|
cs |
다음과 같은 작업을 추가한다면 csv파일로 생성할 수 있을 것이다.
1
2
3
|
>>> csv = open('result.csv','w')
>>> csv.writelines(nm.csv()replace(‘;’,’,’)
>>> csv.close()
|
cs |
-
ms-csv파일
ms사의 엑셀은 csv파일의 컬럼을 세미콜론(;)이 아닌 쉼표(,)로 구분한다. 위 replace 함수는 세미콜론을 쉼표로 바꿔주는 역할을 한다. 이 파일을 저장하여 엑셀로 실행하면 위와 같은 결과를 볼 수 있다.
nmap 주요 옵션에 대하여 살펴보자. nmap의 스텔스 기능과 다른 주요 기능들을 활용하기 위해서는 nmap의 옵션들에 대하여 알아두는 것이 좋다. nmap에서 제공하는 도움말을 보려면 nmap 설치 후 cmd 창에서 nmap을 입력하면 된다. 하나씩 살펴보도록 하자.
그림 nmap 도움말
스캐닝 옵션
옵션 |
기능 |
-sT |
SYN 오픈 스캔 |
-sS |
SYN 하프 오픈 스캔 |
-sX |
X-mas 스캔 |
-sN |
NULL 스캔 |
-sF |
FIN 스캔 |
-sU |
UDP 스캔 |
-b |
FTP Bounce Scan |
스캐닝 옵션의 몇 가지 개념에 대하여 간략하게 설명하겠다. SYN, ACK, FIN 등에 대한 개념은 네트워크와 관련된 지식이므로 설명하지 않았다.
포트(-p)
옵션 |
기능 |
<portnum>, <portnum> ... |
해당 포트를 스캔 |
<startnum>-<endnum>, <startnum>-<endnum>, ... |
시작 포트부터 마지막 포트까지 스캔 |
OS식별(-O)
: 대상의 OS 정보를 가져온다.
Service/Version 감지(-sV)
: 대상 포트의 서비스와 버전에 대한 정보를 가져온다.
속도
옵션 |
기능 |
-F |
가장 빠르게 |
-T0 ~ T5 |
큰 숫자일수록 빠름 |
지금까지 주요 스캔 옵션에 대하여 살펴보았다. 이제는 단순히 옵션을 삽입함으로 여러 추가적인 기능들을 사용할 수 있다!
. 여기서는 각각 스캔 방식에 따른 동작 원리와 결과에 대하여 정리하였다. 여러분들이 원하는 스캔을 선택하기 바란다.
-
스텔스 스캔 필요성
정상적인 TCP 커넥션을 맺는 경우 상대 어플리케이션에 log를 남기게 된다. nmap은 log를 남기지 않기 위한 비정상적인 패턴으로 연결을 요청하는 스텔스 기능을 제공한다. SYN half Scan, Null Scan, X-Mas Scan, FIN Scan이 스텔스 스캔에 해당한다.
1. SYN Open Scan
완전한 3 way handshake를 맺음으로써 확인하는 방법이다. 닫힌 경우 RST 메시지를 받게 된다. 우리가 전에 만든 포트 스캐너의 방식이다.
그림 3 way handshake - Open
만약 SYN에 대한 응답으로 RST를 받는다면 닫힌 포트이다.
그림 3 way handshake - Closed
그리고 SYN에 대한 응답이 없는 포트는 Filter된 포트이다.
그림 3 way handshake - Filtered
2. SYN half Scan( 스텔스 스캔 )
열려있는 포트에 SYN 패킷을 보내면 서버로부터 SYN+ACK를 받지만 ACK메시지로 3 way handshake를 완성하지 않고, RST를 보냄으로써 로그에 남지 않게 스캔하는 방법이다. SYN Open 스캔과 같이 닫힌 경우에는 서버로부터 RST 메시지를 받는다.
그림 SYN half Scan - Open
3. Null Scan, X-Mas Scan, FIN Scan( 스텔스 스캔 )
Null scan은 Control bit에 아무것도 넣지 않은, X-mas는 모든 bit를 1로 설정, FIN scan은 FIN bit를 설정하여 보내는 패킷이다. 이 메시지를 받은 서버는 열려 있을 경우 응답이 없고, 닫혀진 경우 RST+ACK 응답을 받는다.
그림 Null Scan, X-Mas Scan, FIN Scan - Open
그림 Null Scan, X-Mas Scan, FIN Scan - Closed
4. UDP Scan
UDP Scan의 경우에는 열린 포트는 응답이 없고, 닫힌 포트는 ICMP port Unreachable 메시지를 받는다.
그림 UDP 스캔 - Open
그림 UDP 스캔 - Closed
스텔스 스캔은 어플리케이션 로그만을 우회하는 기법으로 Snort, PortScentry, Scanlogd와 같은 프로그램을 설치하면 로그를 남길 수 있다. 또한 어플리케이션 로그 설정을 통하여 스텔스 스캔에 대한 로그를 기록 할 수 있다.
nmap 스캐너에 대하여 파악이 끝났다. 이제 이것을 모듈로 구성하여 우리가 원하는 기능을 탑재한 통합 스캐너를 만들어보자. 이 프로그램에 다음과 같은 몇 가지 요구사항을 추가하였다.
-
여러 개의 서버와 포트 입력 가능
-
스텔스 기능을 사용
-
실행 시간을 출력
-
파일 이름에 호스트와 스캔을 마친 시간 입력
-
파일을 해당 디렉터리의 scan_result 폴더에 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
#! python
# -*- coding: utf-8 -*-
# PortScanProgram with nmap Library
import nmap
import time
import datetime
import os.path
import os
startTime = time.time() # 시작 시간 기록
nm = nmap.PortScanner() # nmap 객체 생성
hostList = ['192.168.157.133','127.0.0.1'] # 호스트 리스트 작성
portList = '1-440' # 포트 리스트 작성
myArg = '-sS -sV -O' # 옵션 설정
fileList = [] # 파일 이름 저장할 리스트 생성
dirName = 'scan_result' # 폴더 이름 지정
# 폴더 생성
if not os.path.exists('./' + dirName): # 폴더 없을 시 폴더 생성
os.system('mkdir ' + dirName)
print (dirName + " directory is made\n")
else: # 폴더 존재 시 폴더 생성 안함
print (dirName + " directory exists already\n")
if not os.path.isdir(dirName): # 해당 파일이 폴더가 아닐 경우 오류 발생
print ("Err: Same name file exists with directory name")
exit()
# 스캔 시작
for host in hostList:
print "scan %s ..."% host
result = nm.scan(host, portList, myArg) # 스캔 수행
ntime = str(datetime.datetime.now()).replace(':','_') # 날짜 기록
ntime = ntime.replace(' ','_')
filename=ntime+'_'+host+'_result.csv' # 파일 이름 생성
csvFile = open('./'+dirName+'/'+filename,'w') # 파일 열기
try: # OS 정보 쓰기
csvFile.write("os info : " +
(result['scan'][host]['osmatch'][0]['name']) + '\n')
except:
csvFile.write('OS : unknown\n')
csvFile.write(nm.csv().replace(';',',')) # ms-csv형식으로 쓰기
csvFile.close()
fileList.append(filename) # 파일 리스트에 목록 추가
# 결과 출력 : [2) python-nmap 체험하기]에서 사용한 코드를 그대로 사용하였다.
for host in nm.all_hosts():
print('\n\n------------------------result-------------------------')
print('Host : %s ( %s)' %(host, nm[host].hostname()))
print('State : %s' % nm[host].state())
try: # OS 정보 출력
print('OS : '+ result['scan'][host]['osmatch'][0]['name'] + '\n')
except:
print('OS : unknown\n')
proto = 'tcp'
print('--------')
print('Protocol : %s' %proto)
lport = nm[host][proto].keys()
lport.sort()
for port in lport:
print ('port : %s\tstate : %s' % (port,
str(nm[host][proto][port]['state'])))
print "\n\n----------------------------------------------------------"
print "It's Finished!!"
endTime = time.time() # 종료 시간 기록
print "\n\n----------------------------------------------------------"
print "executed Time : " + str(endTime - startTime) # 실행 시간 출력
print "\n\n>>>>>>>>>>> please check your result files"
print "This is your path:\n\t" + os.path.realpath(dirName) + '\n'
for fileName in fileList: # 생성한 파일 목록 출력
print fileName
|
cs |
결과 화면
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
scan_result directory exists already
scan 192.168.157.133 ...
------------------------result-------------------------
Host : 192.168.157.133 ()
State : up
OS : Linux 2.6.9 - 2.6.33
--------
Protocol : tcp
port : 21 state : open
port : 22 state : open
port : 23 state : open
port : 25 state : open
port : 53 state : open
port : 80 state : open
port : 111 state : open
port : 139 state : open
----------------------------------------------------------
scan 127.0.0.1 ...
------------------------result-------------------------
Host : 127.0.0.1 ()
State : up
OS : unknown
--------
Protocol : tcp
port : 1 state : unknown
port : 2 state : unknown
port : 3 state : unknown
port : 4 state : unknown
port : 5 state : unknown
... 중략 ...
----------------------------------------------------------
It's Finished!!
----------------------------------------------------------
executed Time : 20.2920000553
>>>>>>>>>>> please check your result files
This is your path:
C:\Users\gasbugs\Desktop\scan_result
2015-05-10_00_58_51.104000_192.168.157.133_result.csv
2015-05-10_00_58_54.971000_127.0.0.1_result.csv
|
cs |
처음 등장하는 코드들에 대해서 살펴보자.
15
|
myArg = '-sS -sV -O' # 옵션 설정
|
cs |
Argument를 통하여 -sS(SYN Half 스캔), -sV(서비스/버전 출력), -o(운영체제)를 스캔하도록 옵션을 주었다.
19
20
21
22
23
24
25
26
27
28
|
# 폴더 생성
if not os.path.exists('./' + dirName): # 폴더 없을 시 폴더 생성
os.system('mkdir ' + dirName)
print (dirName + " directory is made\n")
else: # 폴더 존재 시 폴더 생성 안함
print (dirName + " directory exists already\n")
if not os.path.isdir(dirName): # 해당 파일이 폴더가 아닐 경우 오류 발생
print ("Err: Same name file exists with directory name")
exit() # 프로그램 종료
|
cs |
첫 번째 if 문을 통하여 폴더가 없는지 검사한다. 폴더가 없으면 폴더를 생성하고, 폴더가 있으면 폴더가 이미 존재한다는 문구를 남긴다.
두 번째 if 문은 해당 이름을 가진 개체가 폴더인지 파일인지 검사한다. 만약 파일일 경우에는 에러를 출력하고 종료한다.
35
36
37
|
ntime = str(datetime.datetime.now()).replace(':','_') # 날짜 기록
ntime = ntime.replace(' ','_')
filename=ntime+'_'+host+'_result.csv' # 파일 이름 생성
|
cs |
datetime.datetime.now()는 현재 시간을 반환해 준다. 윈도우즈 완경에서는 ‘:’를 파일 이름으로 사용할 수 없기 때문에 ‘_’로 변환했다. 마지막에는 날짜와, host 이름, 확장자를 조합하여 파일 이름을 만들었다. 매번 스캔할 때마다 시간에 따라 파일 이름을 설정하기 때문에 파일의 이름이 겹쳐서 이전 결과들이 사라질 위험이 없다.
54
55
56
57
|
try: # OS 정보 출력
print('OS : '+ result['scan'][host]['osmatch'][0]['name'] + '\n')
except:
print('OS : unknown\n')
|
cs |
try-except로 os 출력 메시지를 감쌌다. 만약 os 정보를 못 받아 왔을 때는 osmatch라는 항목이 없기 때문에 오류를 출력하고 프로그램이 종료되어버린다. 그래서 예외처리를 하여 프로그램이 오류 없이 실행되도록 했다.
[출처] https://m.blog.naver.com/isc0304/220374904828
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.