第一天
就业简介
就业
CTF比赛、护网行动、实习(项目经历)
简历
1、是否有真实的渗透经历(自己从头到尾的渗透经历)
注意:渗透的合法性和授权性
众测平台:补天 国家漏洞库
补天 - 企业和白帽子共赢的漏洞响应平台,帮助企业建立SRC
未经授权边界:验证漏洞(用无害验证)
2、现在web漏洞 sql注入漏洞 dead
3、web漏洞 护网 整体基线检查 nulei (逐年减少) 前端加密
web前端逆向 加密函数------解密函数(插件:burpsuite autodecoder galaxy)
小程序/app 注意 抓包(app 双向证书绑定 绕过 app 模块 模拟器 真机 nexus5) 前端------后端服务器
云安全: 云安全------> docker k8s 80%
AI
MCP 号称 自动化测试 自动化解题 kali nmap sqlmap
看代码 exp
claude code codex geminicli
claude opus4.5 codex gpt-5.2 gemini3pro glm-4.7 minimax2.1
环境配置
debug调试(重要)
next2shell next.js 0-1分析
python pycharm python3.10 Python Releases for Windows | Python.org
conda env 虚拟环境 python2.7 python.3.10(不相互影响)
java idea jdk1.8 / jdk17(module 限制大部分java,反序列化漏洞 bypass) / jdk21 maven pip
虚拟机:kali Ubuntu Windows
lnmp lamp linux nginx mysql php
wnmp wamp windows nginx mysql php
中间件:nginx apache 大厂 tomcat 开源 idea java weblogic
linux ubuntu22 nginx php-fpm php7.3-php7.4
源码安装:apt安装
apt-get install php7.4-dev autoconf automake
1、源码调试 代码 php C语言(底层)
2、debug底层C语言 pwndbg / vscode
ubuntu安装nginx以及php的部署
1、安装依赖包
apt-get install gcc
apt-get install libpcre3 libpcre3-dev
apt-get install zlib1g zlib1g-dev
sudo apt-get install openssl
sudo apt-get install libssl-dev
2、安装nginx
cd /usr/local
mkdir nginx
cd nginx
tar -xvf nginx-1.21.6.tar.gz
3、编译nginx
/usr/local/nginx/nginx-1.21.6
# 执行命令
./configure
./configure --prefix=/home/centos/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_sub_module
# 执行make命令
make
# 执行make install命令
make install
4、启动nginx
cd /usr/local/nginx/sbin
# 启动nginx
./nginx
5、增加源地址
执行三条命令,添加php的源地址,更新,安装
sudo apt-get install software-properties-common
sudo add-apt-repository -y ppa:ondrej/php
sudo apt-get update
6、安装php
nginx使用php的话要用到php7.3-fpm,所以要安装php-fpm
sudo apt-get install php7.3 php7.3-mysql php7.3-fpm php7.3-curl php7.3-xml php7.3-gd php7.3-mbstring php-memcached php7.3-zip
7、配置php-fpm
把监听端口改掉
listen = /run/php/php7.3-fpm.sock
listen = 127.0.0.1:9000
8、启动php-fpm
sudo service php7.3-fpm start
netstat -lnt | grep 9000
第二天
php底层后门调试
代码层面是xdebug+vscode
1、pwndbg 命令行 不太直观 比较便捷
2、docker(第一你的电脑配置一般,第二 你不想在你系统上安装太多编译工具)
docker有别人的镜像分享
docker run -it --rm --name debug -p 8080:8080 -p 2222:22 tuwen/phpsrc
debug:8.1-backdoor
启动镜像成为一个容器 镜像iso->容器系统
ubuntu 容器 8080------> 8080 192.168.68.129:8080------>容器:8080 容器里22端口 2222
相当于可以利用本地2222登录他的容器
docker 便捷,调试;docker比本地调试稍微麻烦一点,但是省去了安装环境的环节
如何编辑docker容器里面的代码
直接进容器里编辑 不推荐 vim编辑器 我非常熟悉 70 80 90
把代码复制出来 把容器里的代码 复制到物理机 ubuntu 也不太方便
推荐方案 用vscode直接编辑
linux系统登录的两种方案密码
证书登录 私钥 公钥登录 RSA密码环境下最大破解了768
目前通用的RSA算法的密钥长度2048 4096
php8以后大量国际顶尖安全研究员假如php8研发行列,从8开始漏洞大幅度降低减少
C底层调试+PHP代码挑战赛
dns ip--->服务器的ip 三次握手 ---> ssl--->对称加密秘钥加密--->https request--->response
全局函数:$_SERVER(可以获取到所有的环境变量)
eg:$_SERVER http_x_forwarded_for
docker 别人创建好的8.1源码环境 来调试
下载源码
编译源码
vscode链接ubuntu
安装c/c++插件
找到相应函数 下断调试
找到一个匹配的password 让这个password通过sha256 等于ca572756809c324632167240d208681a03b4bd483036581a6190789165e1387a
<?php
$password = trim($_REQUEST['password'] ?? '');
$name = trim($_REQUEST['name'] ?? 'viewsource');
function viewsource() {show_source(__FILE__);}
if (strcmp(hash('sha256', $password), 'ca572756809c324632167240d208681a03b4bd483036581a6190789165e1387a') === 0) {
function readflag() {
echo 'flag';
}
}
//无解 只扫从高级语言的代码层面上没有解法
//调试 看C语言代码
//顶层 vs 非顶层 问题 readflag 非顶层 viewsource 顶层 toplevel
//顶层函数 如何处理 非顶层函数 如何处理
//编译 执行
//1.2.3.4---->执行
//1.词法分析 语法分析 2.AST树的构建 3.将这个语法树 opcode数组 handler $a +$b c层函数
//执行调用
$name();
?>
PHP的执行分为两部分:
- 源代码编译成Zend虚拟机指令(PHP中叫opline)的过程
- 调用`zendparse`完成词法分析、语法分析,生成AST树
- 调用`init_op_array`, `zend_compile_top_stmt`来完成AST到opline数组的转化
- 调用`pass_two`完成编译时到运行时信息的转化,设置每个opcode对应的handler
- Zend虚拟机执行机器指令的过程
- 拿到编译完成后的opline array,依次执行每个opcode,其实就是执行每个opcode对应的handler,完成PHP脚本的执行。
编译函数的代码:PHP在编译"函数定义"的时候,会使用`zend_compile_func_decl`函数:
void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /* {{{ */
{
...
zend_ast_decl *decl = (zend_ast_decl *) ast;
zend_bool is_method = decl->kind == ZEND_AST_METHOD;
if (is_method) {
zend_bool has_body = stmt_ast != NULL;
zend_begin_method_decl(op_array, decl->name, has_body);
} else {
zend_begin_func_decl(result, op_array, decl, toplevel);
if (decl->kind == ZEND_AST_ARROW_FUNC) {
find_implicit_binds(&info, params_ast, stmt_ast);
compile_implicit_lexical_binds(&info, result, op_array);
} else if (uses_ast) {
zend_compile_closure_binding(result, op_array, uses_ast);
}
}
}
这个函数有个挺关键的参数叫 'toplevel' :表示当前的函数定义是否在顶层作用域。跟进用于处理普通函数的 "zend_begin_func_decl"。
php闭包 匿名 箭头函数
匿名函数,也叫闭包函数,允许临时创建一个没有指定名称的函数。最经常用作回调函数callable参数的值。闭包函数也可以作为变量的值来使用,后面要加上分号。
匿名函数:$a() none(匿名函数目前是通过Closure类来实现的)
is_method 类方法 class XYZ{ }
function
static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_ast_decl *decl, zend_bool toplevel) /* {{{ */
{
...
zend_register_seen_symbol(lcname, ZEND_SYMBOL_FUNCTION);
if (toplevel) {
if (UNEXPECTED(zend_hash_add_ptr(CG(function_table), lcname, op_array) == NULL)) {
do_bind_function_error(lcname, op_array, 1);
}
zend_string_release_ex(lcname, 0);
return;
}
/* Generate RTD keys until we find one that isn't in use yet. */
key = NULL;
do {
zend_tmp_string_release(key);
key = zend_build_runtime_definition_key(lcname, decl->start_lineno);
} while (!zend_hash_add_ptr(CG(function_table), key, op_array));
...
}
函数所在作用域造成的opline差异
当toplevel为false时,PHP会调用get_next_op()来生成一个新的opline,而true时则不会.
<?php
function func1() {
echo 'func1';
}
func1();
这里并没有函数定义的opcode,从第5行开始的两个opcode是`INIT_FCALL`和`DO_FCALL`,用于执行函数。
<?php
if (true) {
function func2() {
echo 'func2';
}
}
func2();
PHP编译非顶级作用域函数时,原始函数名和生成的key将会顺序储存在 DECLARE_FUNCTION这个opline的属性中,在执行DECLARE_FUNCTION这个opcode时,才会将真正的原始函数名放进函数表中。
也就是说,作用域如果不是顶级的函数,在编译阶段会先以一个\0开头的函数名被放入函数表中,在执行阶段于DECLARE_FUNCTION的处理器中才会将真正的函数名放入函数表。
绕过trim过滤
trim函数在接收参数的时候会去除掉字符串首尾的空白字符。这里的空白字符包含如下六个字符:<space>\n\r\t\v\0。也就是说, 用户传入的name的第一个\0字符被trim过滤掉了,导致无法正常调用函数。
解决方法:
首先,动态函数调用使用的opcode是INIT_DYNAMIC_CALL,我们使用vld可以看到。然后在PHP源码中找到对应的handler:
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_DYNAMIC_CALL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval *function_name;
zend_execute_data *call;
SAVE_OPLINE();
function_name = RT_CONSTANT(opline, opline->op2);
try_function_name:
if (IS_CONST != IS_CONST && EXPECTED(Z_TYPE_P(function_name) == IS_STRING)) {
call = zend_init_dynamic_call_string(Z_STR_P(function_name), opline->extended_value);
} else if (IS_CONST != IS_CONST && EXPECTED(Z_TYPE_P(function_name) == IS_OBJECT)) {
call = zend_init_dynamic_call_object(function_name, opline->extended_value);
} else if (EXPECTED(Z_TYPE_P(function_name) == IS_ARRAY)) {
call = zend_init_dynamic_call_array(Z_ARRVAL_P(function_name), opline->extended_value);
}
...
}
当函数名是一个字符串时,会执行zend_init_dynamic_call_string:
static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_string *function, uint32_t num_args) /* {{{ */
{
if ((colon = zend_memrchr(ZSTR_VAL(function), ':', ZSTR_LEN(function))) != NULL &&
colon > ZSTR_VAL(function) &&
*(colon-1) == ':'
) {
...
} else {
if (ZSTR_VAL(function)[0] == '\\') {
lcname = zend_string_alloc(ZSTR_LEN(function) - 1, 0);
zend_str_tolower_copy(ZSTR_VAL(lcname), ZSTR_VAL(function) + 1, ZSTR_LEN(function) - 1);
} else {
lcname = zend_string_tolower(function);
}
if (UNEXPECTED((func = zend_hash_find(EG(function_table), lcname)) == NULL)) {
zend_throw_error(NULL, "Call to undefined function %s()", ZSTR_VAL(function));
zend_string_release_ex(lcname, 0);
return NULL;
}
...
}
...
}
PHP 函数编译源码解析
函数签名和参数
第一步:解析 AST 节点
第二步:初始化 op_array
第三步:设置函数属性
第四步:区分函数类型
第五步:处理类方法 vs 普通函数
第六步:隔离作用域
第七步:编译函数体
第八步:编译参数
第九步:生成器特殊处理
| AST 类型 | PHP 代码 | 标志 |
|---|---|---|
ZEND_AST_FUNC_DECL |
function foo() {} |
普通函数 |
ZEND_AST_METHOD |
class A { function foo() {} } |
类方法 |
ZEND_AST_CLOSURE |
$f = function() {}; |
闭包 |
ZEND_AST_ARROW_FUNC |
$f = fn() => $x; |
箭头函数 |
非对称加密 来传递 对称秘钥
第一 确认身份 不能伪造
第二点 解密
CA 公钥
非对称加密 私钥 公钥
server------> 将一些有消息新 公钥 sha384
dsadasdasdasdasdasdasdasdasdasdasdasda CA 私钥进行加密
客户端拿到后 用CA公钥解密后 dsadasdasdasdasdasdasdasdasdasdasdasda
明文公开公钥信息,其他信息
一个信息
身份认证 无法篡改
客户端 sha384 dsadasdasdasdasdasdasdasdasdasdasdasda
客户端生成一个对称秘钥 AES 用服务器的公钥 把客户端生成的对称秘钥 加密
发给服务端
中介人能否解密
安全传递给了服务端
第三、四天
php伪协议
PHP伪协议是PHP自己支持的一种协议与封装协议,简单来说就是PHP定义的一种特殊访问资源的方法。有些伪协议成功执行需要allow_url_fopen和allow_url_include的支持。
allow_url_fopen On/Off 允许或禁止打开URL文件
allow_url_include On/Off 允许或禁止引用URL文件
文件包含的时候,可能遇到的文件包含函数:
1、include
2、require
3、include_once
4、require_once
5、highlight_file
6、show_source
7、flie
8、readfile
9、file_get_contents
10、file_put_contents
11、fopen (比较常见)
常见的PHP伪协议
涉及的相关协议:file://、php://filter、php://input、zip://、compress.bzip2://、compress.zlib://、data://等
1、file:// 用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响
file:// [文件的绝对路径和文件名]
file:// 协议在双off的情况下也可以正常使用;
allow_url_fopen :off/on
allow_url_include:off/on
2、php://协议
条件:
不需要开启allow_url_fopen,仅php://input、 php://stdin、 php://memory 和 php://temp 需要开启allow_url_include。
php:// 访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input,php://filter用于读取源码,php://input用于执行php代码。
(1)php://input(读取原始请求体)
php://input 是个可以访问请求的原始数据的只读流,获取POST请求数据的协议。当675enctype="multipart/form-data" 的时候 php://input 是无效的。php://input 伪协议 成功执行前提php.ini 中的 allow_url_include设置为On
(2)php://filter(编码解码文件)
php://filter可以获取指定文件源码。当它与包含zz函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,让其不执行。从而导致任意文件读取。
php://filter在双off的情况下也可以正常使用;
allow_url_fopen :off/on
allow_url_include:off/o
3、zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名。
zip://, bzip2://, zlib://协议在双off的情况下也可以正常使用;
allow_url_fopen :off/on
allow_url_include:off/on
(1)zip://
作用:访问 ZIP 压缩包内部的文件,而 无需手动解压。
基本语法:zip://压缩包路径#内部文件路径
zip://中只能传入绝对路径。
要用#分隔压缩包和压缩包里的内容,并且#要用url编码%23(即下述POC中#要用%23替换)
只需要是zip的压缩包即可,后缀名可以任意更改。
相同的类型的还有zlib://和bzip2://
(2)bzip://协议(单文件流)
作用:用于 读取 .bz2 压缩文件,读取时自动解压。
基本语法:bzip2://文件路径(没有 #,因为 bzip2 只压缩一个流,不支持目录结构)
(3)zlib协议
作用:读取 gzip / deflate 压缩数据
基本语法:zlib://文件路径
文件包含漏洞
含义:通过PHP函数引入文件时,传入的文件名没有经过合理的验证,从而操作了预想之外的文件,就可能导致意外的文件泄漏甚至恶意代码注入。
环境要求
- allow_url_fopen=On(默认为On) 规定是否允许从远程服务器或者网站检索数据
- allow_url_include=On(php5.2之后默认为Off) 规定是否允许include/require远程文件
常见包含函数
php中常见的文件包含函数有以下四种:
- include()
- require()
- include_once()
- require()_once()
include与require基本是相同的,除了错误处理方面:
- include(),只生成警告(E_WARNING),并且脚本会继续
- require(),会生成致命错误(E_COMPILE_ERROR)并停止脚本
- include_once()与require()_once(),如果文件已包含,则不会包含,其他特性如上
**php://input:**可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。从而导致任意代码执行。
**php://filter:**可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,让其不执行。从而导致任意文件读取。
**zip://:**可以访问压缩包里面的文件。当它与包含函数结合时,zip://流会被当作php文件执行。从而实现任意代码执行。
data:// :同样类似与php://input,可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行。从而导致任意代码执行。
**phar://:**有点类似zip://同样可以导致 任意代码执行。
**包含APACHE日志文件:**WEB服务器一般会将用户的访问记录保存在访问日志中。那么我们可以根据日志记录的内容,精心构造请求,把PHP代码插入到日志文件中,通过文件包含漏洞来执行日志中的PHP代码。
包含SESSION:
利用条件:
- 找到Session内的可控变量
- Session文件可读写,并且知道存储路径
session常见存储路径:
- /var/lib/php/sess_PHPSESSID
- /var/lib/php/sess_PHPSESSID
- /tmp/sess_PHPSESSID
- /tmp/sessions/sess_PHPSESSID
- session文件格式: sess_[phpsessid] ,而 phpsessid 在发送的请求的 cookie 字段中可以看到。