Security dari level bit (Bagian 2: Encoding Teks)

Saat ini masih banyak user dan juga programmer yang masih bingung dengan masalah “encoding” teks (misalnya ASCII, ISO8859-1, UTF-8, UTF-16, UTF32, dsb). Ini merupakan hal dasar yang penting, baik untuk keperluan sehari-hari maupun dalam bidang security. Ada beberapa attack yang berhubungan dengan encoding teks ini.

Encoding teks dan security

Sekilas topik encoding sepertinya hal yang membosankan dari sudut pandang security, tapi ada ada banyak masalah security yang berhubungan dengan encoding teks. Beberapa contohnya:

  • phishing attack memanfaatkan Unicode character yang mirip (character ambiguity)
  • phishing menggunakan karakter khusus untuk marker RTL (right to left) dan LTR (left to right)
  • Encoding alternatif untuk membypass filter
  • Overlong UTF-8 encoding attack
  • Membuat shell code yang bisa lolos encoding tertentu
  • Buffer overflow karena kesalahan penanganan encoding

Berbagai hal di atas sulit dijelaskan jika tidak memiliki pemahaman yang baik mengenai encoding, jadi di tulisan kali ini saya akan membahas mengenai text encoding.

Hadiah ke Hong Kong yang saya menangkan tahun lalu pada dasarnya adalah masalah encoding shellcode supaya lolos dari filter UTF-8. Lengkapnya bisa dibaca di blog saya yang lain.

Sekilas mengenai berbagai encoding

Di bagian ini saya hanya akan bercerita dulu mengenai sejarah encoding. Setalah itu di bagian berikutnya saya akan menuliskan lebih detail mengenai berbagai encoding yang disinggung di bagian ini. Memahami sejarah akan menjawab pertanyaan penting: kenapa sih kok ribet banget harus ada banyak encoding? kalau dari dulu cuma ada satu kan nggak ribet.

Dulu teknologi komputer berkembang pesat dimulai dari Amerika, dan ini memberi pengaruh besar terhadap encoding teks. Ketika membaca berbagai buku atau artikel komputer biasanya dijelaskan bahwa ada tabel ASCII yang memetakan huruf ke angka (atau sebaliknya). ASCII ini merupakan singkatan dari American Standard Code for Information Interchange, dan sesuai namanya ini asalnya dari Amerika.

Sejarah ASCII ini cukup panjang, tapi singkatnya ini adalah standard yang memetakan angka ke simbol (huruf, angka, spasi, tanda baca) serta beberapa karakter kontrol (seperti tanda end of file, new line, dsb). Standard ini hanya memetakan 128 simbol (hanya butuh 7 bit, atau disebut juga sebagai 7-bit character set), karena banyak komputer awal yang memakai 8 bit, maka masih ada 128 simbol yang bisa ditambahkan (istilahnya 8-bit character set).

Berbagai negara menggunakan 128 karakter pertama dari ASCII dan sisanya dipetakan ke karakter masing-masing negara/wilayah. Jadi biasanya kode 65 berarti huruf A di negara manapun, tapi kode 150 bisa menjadi karakter yang berbeda di negara lain. Jadi tiap negara memilki “code page”, atau “character set” yang berbeda. Ada standar ISO-8859 (ISO-8859-1 sampai ISO-8859-16) yang menyatakan “character set” untuk tiap wilayah/bahasa.

Dengan cara ini kita masih bisa dengan cukup mudah mencampurkan teks dua bahasa antara bahasa Inggris dengan satu bahasa lain (misalnya Thai), tapi jika harus mencampurkan lebih dari itu makan akan jadi sangat sulit. Untuk mengatasi ini maka dibuatlah standard Unicode.

Dalam Standard Unicode, hanya ada satu tabel besar untuk semua huruf di dunia ini, setiap simbol dipetakan ke sebuah nomor code point. Tentunya tabel ini sangat besar, jadi 1 karakter tidak bisa lagi dipetakan ke 8 bit (1 byte). Salah satu cara menyimpan karakter unicode adalah dalam bentuk integer 32 bit (4 byte). Cara ini boros memori (karena 1 huruf butuh 4 byte) tapi mudah diproses.

Dalam kehidupan sehari-hari, jarang sekali kita memakai semua huruf di dunia ini dalam satu dokumen, jadi ada cara untuk menghemat memori: kita hanya memakai 16 bit saja. Karena 16 bit hanya bisa dipetakan ke 65536 karakter maka hanya sebagian unicode saja yang dipakai. Unicode dibagi menjadi banyak “plane”. Sebuah plane ini sekedar range code point saja, artinya cuma sekedar nama untuk simbol kesekian sampai kesekian. Plane yang pertama adalah Basic Multilingual Plane (BMP) yang berisi hampir semua karakter di bahasa modern saat ini. Dengan 16 bit, kita bisa memakai BMP saja, dan ada cara khusus untuk memasukkan karakter dari plane lain dengan menggunakan surrogate.

Tapi 16 bit pun kadang masih terlalu boros memori. Berbagai sistem komputer masih sangat berpusat pada American/English, ini meliputi: berbagai perintah command line, berbagai keyword di berbagai bahasa pemrograman dan berbagai standar lain. Jadi seringnya kita hanya perlu menyimpan karakter ASCII, dan sesekali baru butuh karakter bahasa lain.

Dengan pemikiran ini maka ada yang namanya encoding UTF-8 (Unicode Transformation Format-8 bit). Dengan encoding ini, karakter yang ada di tabel ASCII hanya perlu 1 byte, karakter lain di luar itu bisa memakai 2, 3, atau 4 byte. Ini istilahnya adalah variable length encoding. Sebagai catatan ada encoding lain (UTF-1 yang tidak lagi dipakai dan UTF-7 yang juga bukan standar).

Byte order marker

Dalam hampir semua komputer modern, data dibagi dalam byte dan ketika kita berurusan dengan bilangan yang lebih dari 1 byte, kita punya pilihan urutan: little endian dan big endian. Contohnya angka 299 desimal adalah 0x12b dalam heksadesimal. Di memori, ini bisa dituliskan 0x12 0x0b (little endian), atau 0x0b 0x12 (big endian).

Untuk encoding UTF16 dan UTF32, ada pilihan big endian (BE) atau little endian (LE) ketika menyimpan file. Jika data big endian dibaca menjadi little endian, maka hasilnya akan salah, misalnya angka 299 desimal tadi bisa terbaca jadi 2834 desimal. Pada teks Unicode, kita bisa menambahkan BOM (byte order marker) di awal sebuah file untuk menunjukkan file ini disimpan dalam big endian atau little endian.

UTF-32


Seperti telah disinggung di atas:

  • Ini merupakan encoding paling sederhana, hanya array of 32 bit integer
  • ini merupakan encoding paling boros memori
  • untuk mengakses karakter ke n, kita tinggal mengakses elemen integer ke n
UTF32-LE dengan Byte order marker

Kita bisa mencoba membuat file dengan BabelPad (teks editor gratis yang mendukung Unicode dengan baik) dan menyimpannya dalam UTF-32 lalu membukanya dengan editor teks. Ada beberapa variasi yang bisa dicoba:

  • little endian atau big endian
  • dengan atau tanpa byte order marker. Untuk Little endian akan ada FF FE 00 00 di awal dokumen dan untuk big endian 00 00 FE FF
UTF32-LE dengan BOM

Perhatikan urutan byte-byte untuk big endian dan little endian. Untuk teks latin, terlihat sekali boros memori karena banyak byte 00 (dalam huruf latin tiap 1 huruf hanya memakai 1 byte). Sekarang saya contohkan isi teks yang berisi bahasa lain (Thai). Terlihat bahwa karakter Thai memerlukan 2 byte, dan karakter emoji senyum bahagia memakai 3 byte.

UTF-16

Encoding UTF-16 atau UCS-2 (Universal Character Set, 2 Byte) menggunakan 2 byte (16 bit) sebagai unit penyimpanannya. Karena sebagian besar huruf di dunia ini masuk ke basic multilingual plane (BMP), maka sering kali 16 bit sudah cukup (contoh tulisan/bahasa yang masuk ke BMP: Inggris, Arab, Thai, Kanji, Bali, Sunda, Batak). Hanya di kasus tertentu kita perlu menyimpan dengan cara khusus (misalnya emoji yang baru). Di dalam Unicode surrogates.

UTF-16-LE dengan BOM

Kita bisa menyimpan teks sebelumnya dengan encoding UTF-16 dan melihat harsilnya. Terlihat bahwa untuk karakter latin dan Thai, hanya dibutuhkan 2 byte untuk satu karakter, sedangkan untuk karakter emoji dibutuhkan 4 karakter. Empat karakter ini terdiri dari high surrogate (H) dan low surrogate (L) yang perlu dipasangkan untuk merepresentasikan karakter di luar BMP. Formulanya:

0x10000 + (H – 0xD800) x 0x400 + (L – 0xDC00)

Contoh: karakter emoji di contoh tersebut adalah Face with Tears of Joy. Emoji ini memiliki Code Point 128514 atau 0x1F602 dan dengan surrogate dapat direpresentasikan sebagai: 0xD83D 0xDE02. Ini bisa dicek dengan memasukkan angka pertama dan kedua ke formula di atas:

0x10000 + (0xD83D – 0xD800) *0x400 + ( 0xDE02 – 0xDC00)

Sebaliknya kita bisa mendapatkan nilai H dan L untuk sebuah code point C dengan:

L = 0xDC00 + ((C – 0x10000) mod 0x400) H = 0xD800

UTF-16-BE dengan BOM

Di sini mulai ada masalah security: apa jadinya jika ada high surrogate yang tidak memiliki pairing low surrogate? Beberapa sistem mungkin akan mengabaikan bagian itu saja lalu meneruskan memproses datanya, sedangkan sistem lain mungkin akan menolak data yang masuk atau di kasus lain justu meneruskan data apa adanya.

Dalam encoding 16 bit, di bahasa low level seperti C biasanya untuk mengakses karakter ke N kita bisa langsung menggunakan X[n], tapi jika ada karakter surrogate ini tidak benar. Program yang berusaha mengakses dengan cara ini bisa memiliki bug. Cara yang benar biasanya dengan menggunakan iterator, menggunakan accessor khusus (charAt) atau membuat representasi 32 bit di memori agar lebih mudah diakses.

UTF-8

Saat ini UTF-8 merupakan encoding yang palign banyak digunakan, karena sangat hemat penyimpanan (untuk berbagai protokol Internet) dan kompatibel dengan ASCII (untuk 128 huruf pertama). Encoding ini sedikit lebih rumit dari yang lain: untuk code point 0 sampai 0x7f dibutuhkan hanya 1 byte, 0x80 sampai 0x7ff butuh 2 byte, 0x800 sampai 0xffff butuh 3 byte, dan sisanya butuh 4 byte.

Sebagai catatan: UTF-8 tidak memiliki endiannes, tapi kadang file teks di beri byte order mark untuk sekedar menandai bahwa file tersebut menggunakan encoding UTF-8. Adanya BOM kadang membingungkan aplikasi tertentu, jadi kadang ini perlu dihilangkan.

UTF-8 dengan BOM
Tabel diambil dari Wikipedia

Kali ini saya tidak akan membahas detail mengenai UTF-8 karena artikel ini sudah terlalu panjang, dan topik UTF-8 sendiri bisa cukup panjang. Ada beberapa artikel security yang berhubungan dengan UTF-8 ini, misalnya mengenai overly long encoding dan mengenai shellcode yang merupakan teks UTF-8 valid.

ISO 8859 dan encoding lain

Seperti telah dijelaskan sebelumnya bahwa 8 bit tidak cukup untuk mengencode semuanya, maka beberapa konversi bisa dilakukan dan beberapa tidak bisa dilakukan.

  • Teks dalam encoding ISO-8859-X apapun bisa dijadikan Unicode (dengan encoding apa saja)
  • Teks dalam encoding ISO-8859-X hanya bisa dipetakan ke ISO-8859-Y jika semua karakter di teks itu kebetulan ada di target
  • Teks Unicode yang hanya mengandung satu bahasa (atau Inggris dan satu bahasa lain) biasanya bisa diencode ke salah satu ISO-8859-X

Sekarang ini UTF-8 sudah sangat populer dan biasanya encoding lama ini hanya digunakan untuk berinteraksi dengan hardware ataupun software lama.

Penutup

Semoga tulisan ini bisa memberi penjelasan yang bisa dipahami mengenai masalah encoding teks. Saya merasa ilmu encoding teks ini sangat berguna dalam masalah security dan non security, semoga hal tersebut juga berlaku untuk Anda.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.