Cookie dan Session

Atribut cookie dan pengelolaan session beserta lingkupnya yang menjadi sasaran pencurian.

Daftar isi

Server HTTP melihat setiap permintaan sebagai kejadian yang berdiri sendiri, sedangkan aplikasi nyata membutuhkan ingatan berupa status login dan preferensi pengguna. Cookie mengisi kekosongan itu, dan karena cookie umumnya berisi pengenal sesi (session), mekanisme sekecil ini menjadi sasaran pencurian yang paling sering dicoba. Topik ini membahas cara kerja cookie, atribut yang mempersempit lingkupnya, percobaan langsung di lab, dan jalur pencurian yang ditutup oleh setiap atribut.

Cookie adalah pasangan kunci dan nilai berukuran kecil yang dikirim server melalui header Set-Cookie, disimpan browser, lalu dikirim ulang secara otomatis pada setiap permintaan berikutnya yang cocok lingkupnya. Mekanismenya didefinisikan dalam RFC 6265 yang berjudul HTTP State Management Mechanism, sebuah nama yang menegaskan tujuannya, yaitu mengelola keadaan (state) pada protokol yang tidak mengenal keadaan. Server tidak perlu menghafal apa pun tentang klien, karena ingatan itu disimpan di sisi browser dan diserahkan kembali pada setiap permintaan.

Aplikasi memanfaatkan cookie untuk menyimpan pengenal sesi, sedangkan keadaan sesi yang sesungguhnya, misalnya identitas yang sedang login beserta hak aksesnya, disimpan di server. Pasangan itu bekerja seperti tiket: cookie hanya berisi nomor yang sulit ditebak, dan server mengembalikan detail pemegang nomor tersebut dari penyimpanannya sendiri. Pembagian peran ini bukan sekadar gaya penulisan kode, melainkan konsekuensi dari fakta bahwa isi cookie dapat dibaca dan diubah oleh pemilik perangkatnya. Keamanan sesi dengan demikian bertumpu pada kerahasiaan dan lingkup cookie yang membawa pengenal tersebut.

Header Set-Cookie dapat membawa atribut yang tidak mengubah isi cookie, melainkan mempersempit kapan dan oleh siapa cookie itu digunakan. Tabel berikut merangkum atribut utamanya.

AtributPeran
PathMembatasi pengiriman hanya ke jalur yang cocok.
DomainMenentukan host penerima, termasuk subdomainnya bila diisi.
Expires, Max-AgeMenentukan masa berlaku, dan tanpa keduanya cookie hilang saat browser ditutup.
SecureCookie hanya dikirim melalui HTTPS.
HttpOnlyCookie tidak terbaca oleh JavaScript pada halaman.
SameSiteMengatur pengiriman pada permintaan lintas situs dengan nilai Strict, Lax, atau None.

Skrip berikut membangun dua string Set-Cookie dengan pustaka http.cookies bawaan Python. Cookie pertama adalah cookie sesi yang atribut pelindungnya lengkap, sedangkan cookie kedua hanya preferensi tampilan yang berlaku pada jalur /pengaturan.

python atribut-cookie.py
"""Membangun string Set-Cookie dengan atribut yang mempersempit lingkup."""

from http.cookies import SimpleCookie


def cookie_sesi(nilai):
    kue = SimpleCookie()
    kue["sid"] = nilai
    kue["sid"]["path"] = "/"
    kue["sid"]["max-age"] = 3600
    kue["sid"]["httponly"] = True
    kue["sid"]["secure"] = True
    kue["sid"]["samesite"] = "Lax"
    return kue["sid"].OutputString()


def cookie_tema():
    kue = SimpleCookie()
    kue["tema"] = "gelap"
    kue["tema"]["path"] = "/pengaturan"
    kue["tema"]["samesite"] = "Strict"
    return kue["tema"].OutputString()


print(cookie_sesi("s-demo-2481"))
print(cookie_tema())
text Keluaran
sid=s-demo-2481; HttpOnly; Max-Age=3600; Path=/; SameSite=Lax; Secure
tema=gelap; Path=/pengaturan; SameSite=Strict

Setiap atribut pada baris pertama menutup satu jalur pencurian. HttpOnly menghalangi skrip hasil serangan XSS membaca cookie, Secure menghalangi penyadapan pada HTTP polos, SameSite=Lax menghalangi sebagian besar skenario CSRF, sedangkan Path dan Max-Age membatasi tempat dan lama cookie beredar. Cookie kedua menunjukkan atribut minimum yang wajar untuk data nonkritikal, yaitu lingkup jalur yang sempit tanpa masa berlaku panjang.

Server lab berikut melengkapi percobaan. Rute /masuk mengirim cookie sid dengan atribut pelindung lengkap, rute /pengaturan mengirim cookie tema yang terbatas jalurnya, dan rute lain menggemakan header Cookie yang benar-benar diterima. Seperti server lab pada topik HTTP sebagai Medan Serangan, berkas ini memiliki mode uji mandiri tanpa argumen dan mode layanan dengan argumen --layani.

python server-kue.py
"""Server lab untuk melihat header Set-Cookie dan pemilihan cookie oleh browser.

Rute yang dilayani:
  GET /masuk       mengirim cookie sid  (Path=/, HttpOnly, SameSite=Lax, Max-Age=3600)
  GET /pengaturan  mengirim cookie tema (Path=/pengaturan, SameSite=Strict)
  GET rute lain    menggemakan header Cookie yang diterima

Jalankan tanpa argumen untuk uji mandiri, atau dengan argumen --layani
agar server melayani permintaan sungguhan pada http://127.0.0.1:8000/.
"""
import sys
import threading
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer


class PenanganKue(BaseHTTPRequestHandler):
    """Mengirim Set-Cookie pada rute tertentu dan menggemakan cookie masuk."""

    protocol_version = "HTTP/1.1"

    def _kirim(self, kode, tubuh, set_cookie=None):
        data = tubuh.encode("utf-8")
        self.send_response(kode)
        if set_cookie:
            self.send_header("Set-Cookie", set_cookie)
        self.send_header("Content-Type", "text/plain; charset=utf-8")
        self.send_header("Content-Length", str(len(data)))
        self.end_headers()
        self.wfile.write(data)

    def do_GET(self):
        if self.path == "/masuk":
            self._kirim(
                200,
                "Login dicatat. Cookie sid dikirim.",
                "sid=s-demo-2481; Path=/; HttpOnly; SameSite=Lax; Max-Age=3600",
            )
        elif self.path == "/pengaturan":
            self._kirim(
                200,
                "Preferensi disimpan. Cookie tema dikirim.",
                "tema=gelap; Path=/pengaturan; SameSite=Strict",
            )
        else:
            kue = self.headers.get("Cookie") or "(tanpa Cookie)"
            self._kirim(200, "Server menerima: " + kue)

    def send_response(self, code, message=None):
        # Header Server dan Date tidak dikirim agar keluaran lab deterministik.
        self.log_request(code)
        self.send_response_only(code, message)

    def log_message(self, format, *args):
        pass  # log akses tidak dicetak agar keluaran tetap bersih


def jalankan(port=8000):
    server = ThreadingHTTPServer(("127.0.0.1", port), PenanganKue)
    try:
        server.serve_forever()
    finally:
        server.server_close()


def uji_mandiri():
    server = ThreadingHTTPServer(("127.0.0.1", 8000), PenanganKue)
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    try:
        with urllib.request.urlopen("http://127.0.0.1:8000/masuk", timeout=5) as jawaban:
            print("/masuk      Set-Cookie: " + jawaban.headers.get("Set-Cookie"))
        with urllib.request.urlopen("http://127.0.0.1:8000/pengaturan", timeout=5) as jawaban:
            print("/pengaturan Set-Cookie: " + jawaban.headers.get("Set-Cookie"))
        with urllib.request.urlopen("http://127.0.0.1:8000/profil", timeout=5) as jawaban:
            print("/profil     " + jawaban.read().decode("utf-8"))
    finally:
        server.shutdown()
        server.server_close()


if __name__ == "__main__":
    if "--layani" in sys.argv:
        jalankan()
    else:
        uji_mandiri()
text Keluaran
/masuk      Set-Cookie: sid=s-demo-2481; Path=/; HttpOnly; SameSite=Lax; Max-Age=3600
/pengaturan Set-Cookie: tema=gelap; Path=/pengaturan; SameSite=Strict
/profil     Server menerima: (tanpa Cookie)

Mode layanan digunakan untuk melihat header Set-Cookie secara utuh dengan curl dan opsi -i, persis seperti cara browser membacanya. Jalankan blok berikut, lalu perhatikan header yang menyertai respons.

bash
# jalankan server lab di latar belakang
python3 server-kue.py --layani &
PID=$!
sleep 0.5

# amati header Set-Cookie pada respons
curl -s -i "http://localhost:8000/masuk"
echo

# hentikan server lab
kill $PID
wait $PID 2>/dev/null
echo "server lab dihentikan"
text Keluaran
HTTP/1.1 200 OK
Set-Cookie: sid=s-demo-2481; Path=/; HttpOnly; SameSite=Lax; Max-Age=3600
Content-Type: text/plain; charset=utf-8
Content-Length: 34

Login dicatat. Cookie sid dikirim.
server lab dihentikan

Baris Set-Cookie pada respons di atas itulah yang menyebabkan browser menyimpan cookie sid. Tidak ada yang istimewa dari curl pada proses ini, karena browser pun hanya membaca header yang sama lalu menyimpannya sesuai atributnya. Atribut Max-Age=3600 membuat cookie itu bertahan satu jam, sedangkan tanpa atribut tersebut cookie hanya berlaku selama sesi browser dan hilang setelah ditutup.

Percobaan terakhir meniru mekanisme pemilihan cookie oleh browser menggunakan berkas jar, yaitu penyimpanan cookie milik curl. Dua halaman pertama dikunjungi untuk mengumpulkan cookie, lalu dua permintaan berikutnya memperlihatkan cookie mana yang dikirim ulang untuk setiap jalur.

bash
# jalankan server lab di latar belakang
python3 server-kue.py --layani &
PID=$!
sleep 0.5

# browser berkunjung ke dua halaman, cookie yang diterima disimpan ke jar
curl -s -c toples.txt "http://localhost:8000/masuk" > /dev/null
curl -s -b toples.txt -c toples.txt "http://localhost:8000/pengaturan" > /dev/null

# permintaan ke /profil: hanya cookie yang cocok yang dikirim ulang
curl -s -b toples.txt "http://localhost:8000/profil"
echo

# permintaan ke /pengaturan/simpan
curl -s -b toples.txt "http://localhost:8000/pengaturan/simpan"
echo

# hentikan server lab dan bersihkan jar
kill $PID
wait $PID 2>/dev/null
rm -f toples.txt
echo "server lab dihentikan"
text Keluaran
Server menerima: sid=s-demo-2481
Server menerima: tema=gelap; sid=s-demo-2481
server lab dihentikan

Permintaan pertama hanya menerima sid karena cookie tema terikat pada jalur /pengaturan, sedangkan permintaan ke /pengaturan/simpan menerima keduanya. Bukti tersebut menegaskan bahwa Path benar-benar ditegakkan pada saat pengiriman, bukan sekadar anotasi. Batasnya juga terlihat sekaligus, karena sid yang berakar pada jalur / tetap terkirim ke mana pun.

Session ID sebagai sasaran pencurian#

Pencurian cookie sesi setara dengan pencurian sesi, karena pelaku cukup menyalin pengenal yang dicuri ke browsernya sendiri tanpa menyentuh kata sandi. Jalurnya ada empat dan masing-masing sudah memiliki penangkalnya. Skrip pihak ketiga yang disisipkan melalui celah XSS membaca document.cookie ketika HttpOnly absen. Penyadap pada jaringan membaca cookie ketika Secure absen. Situs jahat memanfaatkan pengiriman otomatis cookie untuk memicu aksi atas nama korban ketika SameSite absen, pola yang dikenal sebagai CSRF (cross-site request forgery). Terakhir, pengenal yang pendek atau lemah ditebak melalui percobaan berulang.

Pengelolaan sesi yang sehat menambahkan dua kebiasaan pada sisi server. Pertama, buat pengenal menggunakan pembangkit acak kriptografis seperti secrets.token_urlsafe pada Python, dengan panjang yang membuat penebakan mustahil secara praktis. Kedua, regenerasikan pengenal setiap kali tingkat akses berubah, terutama setelah login, sehingga pengenal yang ditanam penyerang sebelum login menjadi tidak berlaku, pola yang disebut session fixation. Ketentuan panjang dan rotasi pengenal dirangkum dalam Session Management Cheat Sheet milik OWASP, dan kegagalan pada area ini tercatat sebagai A07 Authentication Failures pada OWASP Top 10:2025.

Kesalahan umum#

Beberapa kekeliruan berikut sering ditemukan saat atribut cookie ditetapkan.

  • Menyimpan data aplikasi di dalam cookie. Isi cookie terlampir pada setiap permintaan dan terbaca di penyimpanan browser, sehingga menaruh data pengguna di sana menambah beban permintaan sekaligus risiko kebocoran. Simpan pengenalnya saja, dan letakkan datanya di server.
  • Menganggap Path sebagai batas keamanan. Atribut ini hanya menyaring pengiriman otomatis, sedangkan skrip dari origin yang sama tetap dapat membaca seluruh cookie halaman melalui document.cookie tanpa memandang jalur. Isolasi antar-origin yang sesungguhnya datang dari kebijakan same-origin, bukan dari Path.
  • Memberi masa berlaku panjang pada cookie sesi. Cookie yang bertahan berbulan-bulan memperpanjang jendela pencurian, sedangkan sesi yang sehat berumur pendek dan dapat dicabut di server. Gunakan Max-Age yang wajar, lalu catat sesi di server agar dapat diakhiri sebelum waktunya.
  • Mengatur Domain pada cookie yang tidak perlu dibagikan. Cookie dengan atribut Domain=contoh.test ikut terkirim ke seluruh subdomain, sehingga satu subdomain yang disusupi cukup untuk menerima cookie itu. Tanpa atribut Domain, cookie menjadi host-only dan hanya kembali ke host yang menyetorkannya.

Ringkasan#

  • Cookie mengatasi sifat stateless HTTP: server mengirim Set-Cookie, dan browser mengembalikannya pada permintaan berikutnya yang cocok lingkupnya.
  • Atribut Path, Domain, Secure, HttpOnly, SameSite, dan Max-Age mempersempit tempat, waktu, dan cara cookie digunakan.
  • Cookie sesi hanya membawa pengenal, sedangkan keadaannya disimpan di server, sehingga pencurian pengenal berarti pembajakan sesi.
  • Pembangkit acak kriptografis dan regenerasi pengenal setelah login menutup serangan penebakan dan session fixation.

Referensi#