Skip to content

Commit b2a3d8e

Browse files
committed
chore: improved query filtering
1 parent a2c7e07 commit b2a3d8e

File tree

2 files changed

+63
-0
lines changed

2 files changed

+63
-0
lines changed

classes/Visualizer/Source/Query.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,42 @@ public function fetch( $as_html = false, $results_as_numeric_array = false, $raw
8585
return false;
8686
}
8787

88+
// if previous check passed, check for disallowed query parts to prevent subqueries and other harmful queries.
89+
$disallow_query_parts = array(
90+
'INSERT',
91+
'UPDATE',
92+
'DELETE',
93+
'RENAME',
94+
'DROP',
95+
'CREATE',
96+
'TRUNCATE',
97+
'ALTER',
98+
'COMMIT',
99+
'ROLLBACK',
100+
'MERGE',
101+
'CALL',
102+
'EXPLAIN',
103+
'LOCK',
104+
'GRANT',
105+
'REVOKE',
106+
'SAVEPOINT',
107+
'TRANSACTION',
108+
'SET',
109+
);
110+
$disallow_regex = implode(
111+
'|',
112+
array_map(
113+
function ( $value ) {
114+
return '\b' . $value . '\b';
115+
}, $disallow_query_parts
116+
)
117+
);
118+
119+
if ( preg_match( '/(' . $disallow_regex . ')/i', $this->_query) !== 0 ) {
120+
$this->_error = __( 'Only SELECT queries are allowed', 'visualizer' );
121+
return false;
122+
}
123+
88124
// impose a limit if no limit clause is provided.
89125
if ( strpos( strtolower( $this->_query ), ' limit ' ) === false ) {
90126
$this->_query .= ' LIMIT ' . apply_filters( 'visualizer_sql_query_limit', 1000, $this->_chart_id );

tests/test-ajax.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,4 +194,31 @@ public function test_ajax_response_get_query_data_subcriber_dissallow() {
194194
$this->assertEquals( 'Action not allowed for this user.', $response->data->msg );
195195
$this->assertFalse( $response->success );
196196
}
197+
198+
/**
199+
* Test the AJAX response for fetching the database data with invalid query.
200+
*/
201+
public function test_ajax_response_get_query_data_invalid_query_subquery() {
202+
$this->_setRole( 'administrator' );
203+
204+
$_GET['security'] = wp_create_nonce( Visualizer_Plugin::ACTION_FETCH_DB_DATA . Visualizer_Plugin::VERSION );
205+
206+
$_POST['params'] = array(
207+
'query' => "UPDATE wp_options SET option_value = ( SELECT role_name FROM role_configurations WHERE condition = 'specific_condition' LIMIT 1 )WHERE option_name = 'default_role';",
208+
'chart_id' => 1,
209+
);
210+
try {
211+
// Trigger the AJAX action
212+
$this->_handleAjax( Visualizer_Plugin::ACTION_FETCH_DB_DATA );
213+
} catch ( WPAjaxDieContinueException $e ) {
214+
// We expected this, do nothing.
215+
}
216+
217+
$response = json_decode( $this->_last_response );
218+
$this->assertIsObject( $response );
219+
$this->assertObjectHasAttribute( 'success', $response );
220+
$this->assertObjectHasAttribute( 'data', $response );
221+
$this->assertEquals( 'Only SELECT queries are allowed', $response->data->msg );
222+
$this->assertFalse( $response->success );
223+
}
197224
}

0 commit comments

Comments
 (0)