Passed
Branch feature/php-docs2 (9ef114)
by Michael
05:19
created

ProtectorMysqlDatabase::injectionFound()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 8
rs 10
1
<?php namespace XoopsModules\Protector;
2
3
if (file_exists(XOOPS_ROOT_PATH . '/class/database/drivers/' . XOOPS_DB_TYPE . '/database.php')) {
4
    require_once XOOPS_ROOT_PATH . '/class/database/drivers/' . XOOPS_DB_TYPE . '/database.php';
5
} else {
6
    require_once XOOPS_ROOT_PATH . '/class/database/' . XOOPS_DB_TYPE . 'database.php';
7
}
8
9
require_once XOOPS_ROOT_PATH . '/class/database/database.php';
10
11
/**
12
 * Class ProtectorMysqlDatabase
13
 */
14
class ProtectorMysqlDatabase extends XoopsMySQLDatabaseProxy
0 ignored issues
show
Bug introduced by
The type XoopsModules\Protector\XoopsMySQLDatabaseProxy was not found. Did you mean XoopsMySQLDatabaseProxy? If so, make sure to prefix the type with \.
Loading history...
15
{
16
    /**
17
     * @var array
18
     */
19
    public $doubtful_requests = array();
20
    public $doubtful_needles  = array(
21
        // 'order by' ,
22
        'concat',
23
        'information_schema',
24
        'select',
25
        'union',
26
        '/*',
27
        /**/
28
        '--',
29
        '#',
30
    );
31
32
    /**
33
     * ProtectorMysqlDatabase constructor.
34
     */
35
    public function __construct()
36
    {
37
        $protector               = Guardian::getInstance();
38
        $this->doubtful_requests = $protector->getDblayertrapDoubtfuls();
39
        $this->doubtful_needles  = array_merge($this->doubtful_needles, $this->doubtful_requests);
40
    }
41
42
    /**
43
     * @param $sql
44
     */
45
    public function injectionFound($sql)
46
    {
47
        $protector = Guardian::getInstance();
48
49
        $protector->last_error_type = 'SQL Injection';
50
        $protector->message .= $sql;
51
        $protector->output_log($protector->last_error_type);
52
        die('SQL Injection found');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
53
    }
54
55
    /**
56
     * @param $sql
57
     *
58
     * @return array
59
     */
60
    public function separateStringsInSQL($sql)
61
    {
62
        $sql            = trim($sql);
63
        $sql_len        = strlen($sql);
64
        $char           = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $char is dead and can be removed.
Loading history...
65
        $string_start   = '';
66
        $in_string      = false;
67
        $sql_wo_string  = '';
68
        $strings        = array();
69
        $current_string = '';
70
71
        for ($i = 0; $i < $sql_len; ++$i) {
72
            $char = $sql[$i];
73
            if ($in_string) {
74
                while (1) {
75
                    $new_i = strpos($sql, $string_start, $i);
76
                    $current_string .= substr($sql, $i, $new_i - $i + 1);
77
                    $i = $new_i;
78
                    if ($i === false) {
79
                        break 2;
80
                    } elseif (/* $string_start == '`' || */
81
                        $sql[$i - 1] !== '\\'
82
                    ) {
83
                        $string_start = '';
84
                        $in_string    = false;
85
                        $strings[]    = $current_string;
86
                        break;
87
                    } else {
88
                        $j                 = 2;
89
                        $escaped_backslash = false;
90
                        while ($i - $j > 0 && $sql[$i - $j] === '\\') {
91
                            $escaped_backslash = !$escaped_backslash;
0 ignored issues
show
introduced by
The condition $escaped_backslash is always false.
Loading history...
92
                            ++$j;
93
                        }
94
                        if ($escaped_backslash) {
95
                            $string_start = '';
96
                            $in_string    = false;
97
                            $strings[]    = $current_string;
98
                            break;
99
                        } else {
100
                            ++$i;
101
                        }
102
                    }
103
                }
104
            } elseif ($char === '"' || $char === "'") { // dare to ignore ``
105
                $in_string      = true;
106
                $string_start   = $char;
107
                $current_string = $char;
108
            } else {
109
                $sql_wo_string .= $char;
110
            }
111
            // dare to ignore comment
112
            // because unescaped ' or " have been already checked in stage1
113
        }
114
115
        return array(
116
            $sql_wo_string,
117
            $strings,
118
        );
119
    }
120
121
    /**
122
     * @param $sql
123
     */
124
    public function checkSql($sql)
125
    {
126
        list($sql_wo_strings, $strings) = $this->separateStringsInSQL($sql);
127
128
        // stage1: addslashes() processed or not
129
        foreach ($this->doubtful_requests as $request) {
130
            if (addslashes($request) != $request) {
131
                if (false !== stripos($sql, trim($request))) {
132
                    // check the request stayed inside of strings as whole
133
                    $ok_flag = false;
134
                    foreach ($strings as $string) {
135
                        if (false !== strpos($string, $request)) {
136
                            $ok_flag = true;
137
                            break;
138
                        }
139
                    }
140
                    if (!$ok_flag) {
141
                        $this->injectionFound($sql);
142
                    }
143
                }
144
            }
145
        }
146
147
        // stage2: doubtful requests exists and outside of quotations ('or")
148
        // $_GET['d'] = '1 UNION SELECT ...'
149
        // NG: select a from b where c=$d
150
        // OK: select a from b where c='$d_escaped'
151
        // $_GET['d'] = '(select ... FROM)'
152
        // NG: select a from b where c=(select ... from)
153
        foreach ($this->doubtful_requests as $request) {
154
            if (false !== strpos($sql_wo_strings, trim($request))) {
155
                $this->injectionFound($sql);
156
            }
157
        }
158
159
        // stage3: comment exists or not without quoted strings (too sensitive?)
160
        if (preg_match('/(\/\*|\-\-|\#)/', $sql_wo_strings, $regs)) {
161
            foreach ($this->doubtful_requests as $request) {
162
                if (false !== strpos($request, $regs[1])) {
163
                    $this->injectionFound($sql);
164
                }
165
            }
166
        }
167
    }
168
169
    /**
170
     * @param string $sql
171
     * @param int    $limit
172
     * @param int    $start
173
     *
174
     * @return mysqli_result|bool query result or FALSE if successful
0 ignored issues
show
Bug introduced by
The type XoopsModules\Protector\mysqli_result was not found. Did you mean mysqli_result? If so, make sure to prefix the type with \.
Loading history...
175
     *                      or TRUE if successful and no result
176
     */
177
    public function query($sql, $limit = 0, $start = 0)
178
    {
179
        $sql4check = substr($sql, 7);
180
        foreach ($this->doubtful_needles as $needle) {
181
            if (false !== stripos($sql4check, $needle)) {
182
                $this->checkSql($sql);
183
                break;
184
            }
185
        }
186
187
        if (!defined('XOOPS_DB_PROXY')) {
188
            $ret = parent::queryF($sql, $limit, $start);
189
        } else {
190
            $ret = parent::query($sql, $limit, $start);
191
        }
192
193
        return $ret;
194
    }
195
}
196