# Linux Cap: Cara Elevasi Privilege tanpa Menjadi Root

Jadi kemarin aku coba bikin-bikin aplikasi yang harus *listen* di *restricted port*. *Restricted port* ini yang biasanya ada di rentang 1 - 1024. Di atas itu baru bisa pakai *user* biasa buat *listen port*, misal mau jalanin aplikasi di port 8000 ya tidak ada masalah. Sebagai contoh, aku coba tulis program simpel biar kebayang.

```go
package main

import (
	"log"
	"net/http"
)

func main() {
	handler := http.NewServeMux()

	handler.HandleFunc("/", HandleHealthCheck)

	log.Printf("listening app in localhost:80")
	if err := http.ListenAndServe("localhost:80", handler); err != nil {
		log.Panic(err)
	}
}

func HandleHealthCheck(rw http.ResponseWriter, r *http.Request) {
	rw.Write([]byte("service is healthy"))
}
```

  

Potongan kode di atas adalah aplikasi web sederhana yang jalan di port 80. Port 80 ini kan termasuk di port yang *restricted*, jadi butuh *privilege* untuk dapat menjalankannya. Beberapa hal yang bisa dilakukan untuk jalankan aplikasi ini adalah dengan menjalankannya dengan menjadi root terlebih dahulu.

Untuk Menjalankannya, aku juga bikin bash script untuk *build* dan menjalankan *binary* yang telah dibuat.

```sh
#!/bin/bash
go build -o main app.go

if [[ $1 == '--run' ]]; then
  ./main
fi
```

Untuk menjalankannya tinggal panggil *script*\-nya aja seperti ini

```plaintext
➜  example-linux-cap$ sh build.sh --run
```

Maka hasil keluarannya akan kurang lebih seperti di bawah.

```plaintext
2023/07/29 22:10:23 listening app in localhost:80
2023/07/29 22:10:23 listen tcp 127.0.0.1:80: bind: permission denied
panic: listen tcp 127.0.0.1:80: bind: permission denied

goroutine 1 [running]:
log.Panic({0xc0000bff50?, 0x67e1bb?, 0x0?})
        /usr/lib/go/src/log/log.go:384 +0x65
main.main()
        /home/rendy/Workspace/private/example-linux-cap/app.go:15 +0x105
```

## Menjadi Root 🥚

Cara yang paling mudah adalah dengan menjadi root. Menjalankannya hanya cukup dengan `prefix` sudo. Untuk awalan mari gunakan cara bodoh untuk menjalankan aplikasi tersebut. Kenapa cara bodoh, karena dapat mengakibatkan peretas mendapatkan bug yang ada di aplikasi dan mengeksploitasinya. Tapi tidak apa-apa karena ini bagian dari belajar. Nanti kita juga akan belajar cara yang lebih baik.

Pertama-tama ganti *user* ke root terlebih dahulu.

```plaintext
➜  example-linux-cap$ sudo su
➜  example-linux-cap sudo su
[sudo] password for rendy: 
[root@canvas-mobile example-linux-cap]# whoami
root
```

Setelah menjadi root, mari dicoba kembali untuk menjalankan aplikasi.

```plaintext
[root@canvas-mobile example-linux-cap]# sh build.sh --run
2023/07/29 22:21:38 listening app in localhost:80
```

Aplikasi sukses berjalan. Untuk memastikan, bisa menggunakan perintah curl ke localhost:80. Hasilnya akan seperti di bawah.

```plaintext
➜  example-linux-cap curl localhost:80
service is healthy%
```

Cara ini juga bisa diraih dengan menggunakan sudo, tanpa mengganti user ke root. Caranya adalah seperti ini.

```plaintext
[root@canvas-mobile example-linux-cap]$ sudo sh build.sh --run
2023/07/29 22:21:38 listening app in localhost:80
```

Untuk memastikan, dapat menggunakan perintah curl seperti yang sebelumnya.

```plaintext
➜  example-linux-cap curl localhost:80
service is healthy%
```

Cara ini sukses, tapi sangat tidak dianjurkan demi keamanan *server*, karena ketika sekali saja terkena serangan *exploit*, **seluruh akses di *server* akan juga dapat diambil alih oleh penyerang (*hacker*)**. Fatal banget pengaruhnya.

## Menggunakan Linux Cap 🎩

Sekarang, menuju ke hidangan utama yaitu ke Linux *capabilities*. Sebenarnya *capabilities* ini sudah lama dirilis, sejak kernel versi 2.2, tapi dokumentasinya cukup minim. Kegunaannya juga cukup low level, jadi ini jarang digunakan oleh *end-user*. Tapi sebenarnya fitur ini banyak digunakan untuk menjalankan aplikasi yang *rootless*, seperti [**podman**](https://podman.io/) yang merupakan *tool* untuk memanajemen *container* yang dapat berjalan secara *rootless*.

Kembali ke *linux capabilities*, dilansir dari laman linux man-pages (Halaman manual linux), *capabilities* ini dipecah menjadi lebih kecil-kecil sesuai dengan aksi yang ingin dilakukan. Untuk lengkapnya, bisa dilihat langsung di [halaman dokumentasinya](https://man7.org/linux/man-pages/man7/capabilities.7.html), namun sebagai contoh, ini aku lampirkan sedikit di bawah.

```plaintext
CAP_NET_BIND_SERVICE
    Bind a socket to Internet domain privileged ports (port
    numbers less than 1024).

CAP_NET_BROADCAST
    (Unused)  Make socket broadcasts, and listen to
    multicasts.

CAP_NET_RAW
    •  Use RAW and PACKET sockets;
    •  bind to any address for transparent proxying.
```

Pada kasus ini, yang diperlukan untuk *listen* di *restricted port*, berarti memerlukan *capability*`CAP_NET_BIND_SERVICE`. Untuk memberikan capabilities pada sebuah *file* atau *binary*, diperlukan pengetahuan juga terkait tipe *capability* yang akan dilampirkan. Dikutip dari laman linux man, ada 3 tipe *capability* yang tersedia.

```plaintext
Permitted (formerly known as forced):
    These capabilities are automatically permitted to the
    thread, regardless of the thread's inheritable
    capabilities.

Inheritable (formerly known as allowed):
    This set is ANDed with the thread's inheritable set to
    determine which inheritable capabilities are enabled in
    the permitted set of the thread after the execve(2).

Effective:
    This is not a set, but rather just a single bit.  If this
    bit is set, then during an execve(2) all of the new
    permitted capabilities for the thread are also raised in
    the effective set.  If this bit is not set, then after an
    execve(2), none of the new permitted capabilities is in
    the new effective set.
```

Berdasarkan penjelasan tersebut, kita tidak bisa menggunakan tipe `Inheritable` karena dia tipenya diwariskan, jadi belum *parent process thread* yang akan dijalankan akan memiliki *capability*`CAP_NET_BIND_SERVICE`. Sehingga, kita perlu menambahkan capability `CAP_NET_BIND_SERVICE` dengan tipe ***Permitted*** (untuk memastikan) dan ***Effective***. Setelah mengetahui, berarti *build script* yang telah dibuat tadi perlu dilakukan penambahan. Untuk memberikan capability pada sebuah file, terdapaat perintah program `setcap`.

Untuk menggunakannya, diperlukan minimal 2 argumen yang pertama adalah *capability* nya dalam bentuk string dan target file nya

```plaintext
# setcap <capabilities> <target-file>
```

Berdasarkan dokumentasi, format *capabilities* string berbentuk `<capability>=type`. Untuk *capability* yang dibutuhkan yaitu `CAP_NET_BIND_SERVICE` dan tipenya disingkat, `e` untuk *effective* dan `p` untuk *permitted*. Sehingga formatnya menjadi seperti ini `cap_net_bind_service=ep`. Setelah itu, *build script* yang tadi diubah menjadi seperti ini.

```bash
#!/bin/bash
go build -o main app.go
sudo setcap 'cap_net_bind_service=ep' main

if [[ $1 == '--run' ]]; then
  ./main
fi
```

### The Moment of truth 🥁

Sekarang waktunya membuktikan apakah berhasil menggunakan linux *capabilities*.

```plaintext
➜  example-linux-cap sh build.sh 
➜  example-linux-cap ./main 
2023/07/29 22:55:19 listening app in localhost:80
```

Hasil keluaran terminal di atas menandakan bahwa tanpa *user* root, menjalankan aplikasi web di *restricted port* tetap bisa dicapai menggunakan linux *capabilities*.

Jadi apa yang bisa disimpulkan terkait percobaan ini? Ya tidak semuanya harus menjadi root. Linux *capabilities* memberikan kenyamanan untuk mengatur dan mengerucutkan *permission* dengan lebih detail.

Agar aman dalam menggunakan linux cap adalah, ekspektasinya memberikan *capabilities* pada sebuah *binary* ketika *installation*. Karena di saat itu-lah membutuhkan akses root untuk menambahkan *capability*. Di luar itu, linux kernel yang akan melakukan pengecekan *capability* pada sebuah *binary*. Sehingga scopenya benar-benar kecil, yaitu di level *capability* pada sebuah *thread process*, tidak di level *user*. Dengan terbatasnya *permission* yang diset pada sebuah *binary*, dapat memungkinkan kita memperkecil kemungkinan untuk diretas. Istilah kerennya sih ***Hardening*** -- 🚧.
