[PWN2WIN CTF 2021] – illusion

Pada soal ini, saya diberikan sebuah soal yang tidak biasa. Disini saya diminta untuk melakukan koneksi pada server dengan menggunakan netcat. Untuk mengetahui lebih lanjut tentang apa yang diminta pada soal ini, saya mengcopy command nc yang sudah diberikan dari soal dan setelah itu saya menjalankan command tersebut pada terminal.

Ternyata kita diminta untuk memasukkan sebuah hasil yang didapatkan dari menjalankan command hashcash yang diberikan oleh author soal. Hashcash dapat kita download lewat https://zoomadmin.com/HowToInstall/UbuntuPackage/hashcash. Setelah diinstal, maka kita hanya perlu mengcopy command yang telah diberikan pada terminal kita.

-m => Opsi yang dibutuhkan untuk mencetak stempel dari input yang dimasukkan oleh user.

-b (bits) => Opsi ini akan berguna untuk menentukan jumlah bit yang harus ada ketika program membuat stempel. Opsi ini bisa dilanjutkan dengan berapa banyak bit yang kita inginkan. Jika tidak dimasukkan jumlahnya, maka program akan memilih jumlah bit secara default.

-mb25 => Program akan membuat sebuah stempel yang di dalamnya memiliki jumlah bit sebanyak 25 bit.
Penjelasan Syntax Hashcash

Setelah mendapatkan hasil dari command tersebut, kita hanya perlu mengcopy hasilnya kembali ke nc instance kita sebagai solution.

Kemudian kita akan diberikan sebuah url website dan credentials yang dapat kita gunakan. Kita juga diberitahu bahwa kita hanya dapat mengakses website tersebut hanya selama 5 menit. Apabila kita mencoba untuk mengakses url tersebut, maka kita akan diminta untuk memasukkan username dan password.

Kita dapat menggunakan credentials yang sebelumnya sudah diberikan. Usernamenya adalah admin dan passwordnya adalah viiptwwxrvrvtrec. Setelah dimasukkan, maka kita akan dibawa ke page utama dari website tersebut.

Sekarang kita perlu mencari cara untuk mengeksploit service dari website tersebut. Namun, itu akan sangat sulit karena kita hanya memiliki waktu 5 menit sebelum akses yang kita miliki diputus. Tentu saja saya tidak menemukan eksploit apa-apa dalam rentang waktu tersebut.

Dikarenakan masalah tersebut, maka saya perlu mencoba untuk mencari eksploit dari website tersebut dari localhost sebelum mencobanya ke live server. Apabila kita perhatikan pada deskripsi soal, author memberikan source code dari website tersebut dan mungkin saja kita bisa mendapatkan informasi yang menarik.

Ternyata author memberikan sebuah dockerfile yang berarti kita bisa menjalankan website tersebut di localhost via docker. Selain itu, kita juga mengetahui bahwa akan ada sebuah elf program yaitu “readflag” yang dapat kita jalankan untuk membaca flag. Kita harus mengakses program itu untuk membaca flag dikarenakan “flag.txt” akan dimasukkan ke dalam folder root sehingga kita tidak bisa langsung membacanya.

Namun, kita masih perlu mencari suatu file yang mungkin dapat membantu kita dalam memahami backend dari website tersebut. 
Setelah beberapa waktu mencari, saya menemukan sebuah file yang menarik yaitu “index.js”. File tersebut berada di dalam folder “src” dan isinya adalah sebagai berikut.

const express = require(‘express’)
const bodyParser = require(‘body-parser’)
const jsonpatch = require(‘fast-json-patch’)
const ejs = require(‘ejs’)
const basicAuth = require(‘express-basic-auth’)


const app = express()

// Middlewares //
app.use(bodyParser.json())
app.use(basicAuth({
    users: { “admin”: process.env.SECRET || “admin” },
    challenge: true
}))

/////////////////

let services = {
    status: “online”,
    cameras: “online”,
    doors: “online”,
    dome: “online”,
    turrets: “online”
}

// Static folder
app.use(“/static”, express.static(__dirname + “/static”));

// Homepage
app.get(“/”, async (req, res) => {
    const html = await ejs.renderFile(__dirname + “/templates/index.ejs”, {services})
    res.end(html)
})

// API
app.post(“/change_status”, (req, res) => {

    let patch = []

    Object.entries(req.body).forEach(([service, status]) => {

        if (service === “status”){
            res.status(400).end(“Cannot change all services status”)
            return
        }

        patch.push({
            “op”: “replace”,
            “path”: “/” + service,
            “value”: status
        })
    });

    jsonpatch.applyPatch(services, patch)

    if (“offline” in Object.values(services)){
        services.status = “offline”
    }

    res.json(services)
})

app.listen(1337, () => {
    console.log(App listening at port 1337)
})  
index.js

Dari script di atas, kita dapat memahami alur dari backend website tersebut. Salah satu hal yang perlu kita perhatikan adalah bahwa pada script ini diperlukan “ejs” dan juga “fast-json-patch”. 

Apabila kita mencoba untuk mencari informasi di internet, kita dapat menemukan fakta bahwa keduanya adalah suatu fungsi yang vulnerable dan dapat kita eksploitasi. Saya mengambil referensi dari https://github.com/mde/ejs/issues/451 sebagai dasar dari hipotesa yang saya miliki.

Dengan begitu, saya mencoba mempraktekannya pada challenge ini. Saya ingin mencoba apakah saya bisa melakukan Remote Code Execution (RCE) pada website tersebut atau tidak. Saya menggunakan Postman untuk mempermudah pengiriman POST request pada website.

Sebelum mencoba di live instance, saya mencobanya terlebih dahulu pada localhost. Untuk mengakses website yang ada pada localhost, saya perlu melakukan request pada IP milik Virtual Machine (VM) saya. Kita dapat memasukkan IP dari eth0 milik VM sebagai address yang dituju pada aplikasi Postman. IP eth0 bisa didapatkan dengan menuliskan command ifconfig pada terminal di VM kita masing-masing.

Saya melakukan request dalam bentuk POST request dikarenakan website hanya mengizinkan POST request jika ingin mengubah nilai API yang sudah ada. Dan jangan lupa kita juga perlu memasukkan authorization yang sesuai agar kita dapat mengirimkan request ke websitenya (untuk localhost, maka credentialnya adalah admin:admin). Saya mencoba memasukkan payload yang ada pada referensi sebelumnya untuk menguji celah keamanannya.

Kita mengetahui bahwa website ini berjalan dengan menggunakan template dari EJS (Embedded Javascript). Pada EJS ada sebuah property dengan nama OutputFunctionName yang dapat kita manfaatkan untuk melakukan code execution lewat shell. Untuk mengetahui dari mana saya bisa mendapatkan informasi mengenai property tersebut, kita bisa membaca source code dari EJS itu sendiri pada website ini https://github.com/mde/ejs/blob/master/lib/ejs.js#L575. Dan bisa dilihat juga, bahwa pada source code tersebut, ada penggunaan property OutputFunctionName.

Fungsi dari property tersebut sendiri ialah untuk membuat function-function yang dipakai untuk print string (‘echo’, ‘print’) agar outputnya dapat dikeluarkan di dalam scriptlet tags (<%….%>). Informasi lebih lanjut mengenai property ataupun fungsi dari property tersebut dapat dilihat pada link ini https://www.npmjs.com/package/ejs.

Karena sekarang kita tahu bahwa outputFunctionName adalah property bawaan dari EJS dan sudah pasti ada pada script EJS, maka kita juga dapat mengontrol value dari property tersebut dengan eksploit yang kita miliki sebelumnya. Kita dapat merubah value dari dari property tersebut dengan mengaksesnya lewat constructor/prototype. Karena kita ingin mendapatkan shell, maka kita dapat merubah valuenya dengan menggunakan payload ini.

Jika berhasil, maka sebuah file dengan nama “hiya” akan terbentuk pada directory /tmp. Salah satu hal yang perlu diperhatikan adalah kita perlu merefresh page dari website tersebut agar payload kita dijalankan.

Bisa dilihat dari gambar di atas bahwa payload kita berhasil dan sebuah file bernama “hiya” sudah terbentuk. Berarti sudah dipastikan bahwa backend pada website tersebut vulnerable terhadap prototype pollution attack. Sekarang kita hanya perlu mendapatkan shell pada live instance agar kita bisa menjalankan program “readflag”.

Kita bisa mengulangi langkah-langkah yang saya jelaskan sebelumnya untuk mendapatkan akses ke live instance dan mendapatkan credential untuk mengakses website yang dibuat oleh author.

Perlu kita perhatikan bahwa sekarang kita ingin mendapatkan akses shell pada website target. Kita tidak bisa langsung menggunakan IP eth0 dikarenakan IP tersebut adalah sebuah IP private yang berarti IP tersebut hanya bisa mengakses sesama jaringan lokal dan tidak bisa digunakan sebagai akses shell pada website di luar jaringan lokal. Untuk mengatasi permasalahan tersebut kita perlu menggunakan “ngrok” sebagai endpoint untuk mendapatkan shell dari server.

Dengan menggunakan ngrok, maka IP private milik kita akan dapat diakses lewat subdomain ngrok.com. Kita jadi tidak memerlukan IP public pada local machine dan website target juga dapat mengakses IP tersebut dikarenakan IP yang diberikan oleh ngrok adalah sebuah IP public yang berarti bisa diakses oleh semua orang baik yang di dalam jaringan lokal maupun di luar jaringan lokal.

Untuk menjalankan ngrok pada port tertentu, kita dapat menggunakan command “./ngrok tcp <port>“. Perlu diketahui juga bahwa kita harus menginstall ngrok secara manual dari websitenya dikarenakan ngrok bukanlah aplikasi bawaan dari kali linux. Ngrok dapa didownload pada website ini https://ngrok.com/. Berikut adalah penjelasan dari command tersebut:

./ngrok => Adalah sebuah fungsi untuk menjalankan program ngrok.

tcp => Untuk membuka service yang berjalan dengan menggunakan protokol TCP. Saya memilih untuk menggunakan TCP dikarenakan reverse shell yang akan saya gunakan berjalan dengan TCP dan TCP juga lebih bisa diandalkan dibandingkan protokol-protokol lainnya.

port => Port berapa yang ingin kita buka untuk dijadikan akses masuk.
Penjelasan Syntax Ngrok

Apabila kita sudah menjalankan command tersebut, maka kita akan mendapatkan window seperti ini.

Sekarang kita memiliki IP public yaitu 0.tcp.ngrok.io pada port 11223. Untuk mendapatkan shell, saya menggunakan netcat reverse shell yang saya dapatkan dari website ini https://highon.coffee/blog/reverse-shell-cheat-sheet/. Berikut adalah screenshot pada postman dengan payload yang sudah saya sesuaikan.

Jangan lupa untuk menyesuaikan dengan url dan credential yang diberikan agar request dapat diterima dengan baik. Jika sudah disesuaikan, maka payload tersebut akan dijalankan dan kita akan mendapatkan shell pada localhost.

Karena saya sudah mendapatkan shell, maka saya bisa menjalankan program “readflag” dengan ./. Setelah dijalankan, maka saya akan mendapatkan flag dan dengan begitu challenge ini telah selesai.

Leave a comment

Your email address will not be published. Required fields are marked *